mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 10:57:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf22464a5 | ||
|
|
705cf93681 | ||
|
|
0e60432de9 | ||
|
|
32a420db1b | ||
|
|
f56f277606 | ||
|
|
6b325bdbcd | ||
|
|
7ba608fcb2 |
@@ -1,4 +1,4 @@
|
|||||||
import { mkdtempSync, rmSync } from "node:fs";
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
@@ -32,11 +32,13 @@ describe("zai usage source", () => {
|
|||||||
process.env["USERPROFILE"] = homeDir;
|
process.env["USERPROFILE"] = homeDir;
|
||||||
for (const key of [
|
for (const key of [
|
||||||
"APPDATA",
|
"APPDATA",
|
||||||
|
"XDG_CONFIG_HOME",
|
||||||
"COPILOT_TOKEN",
|
"COPILOT_TOKEN",
|
||||||
"GITHUB_TOKEN",
|
"GITHUB_TOKEN",
|
||||||
"GITHUB_PAT",
|
"GITHUB_PAT",
|
||||||
"CURSOR_ACCESS_TOKEN",
|
"CURSOR_ACCESS_TOKEN",
|
||||||
"CURSOR_TOKEN",
|
"CURSOR_TOKEN",
|
||||||
|
"Z_AI_API_KEY",
|
||||||
"ZAI_API_KEY",
|
"ZAI_API_KEY",
|
||||||
"GLM_API_KEY",
|
"GLM_API_KEY",
|
||||||
"GROK_API_KEY",
|
"GROK_API_KEY",
|
||||||
@@ -116,11 +118,61 @@ describe("zai usage source", () => {
|
|||||||
details: expect.arrayContaining([{ id: "status", label: "Status", value: "VALID" }]),
|
details: expect.arrayContaining([{ id: "status", label: "Status", value: "VALID" }]),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("discovery returns a locator when fetch finds zai credentials", async () => {
|
it("fetches Z.ai usage from Z_AI_API_KEY, the name the glm-acp-agent CLI uses", async () => {
|
||||||
const previous = process.env["ZAI_API_KEY"];
|
process.env["Z_AI_API_KEY"] = "zai_cli_token";
|
||||||
try {
|
let authorization: string | null = null;
|
||||||
|
fetchApi = (async (_url: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
authorization = (init?.headers as Record<string, string> | undefined)?.Authorization ?? null;
|
||||||
|
return jsonResponse({ data: [{ productName: "GLM Coding Max", status: "VALID" }] });
|
||||||
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
|
const zai = findProvider(await service().listUsage(), "zai");
|
||||||
|
|
||||||
|
expect(authorization).toBe("Bearer zai_cli_token");
|
||||||
|
expect(zai).toMatchObject({ status: "available", planLabel: "GLM Coding Max" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reads Z.ai usage from the credentials file the glm-acp-agent CLI writes", async () => {
|
||||||
|
const configDir = join(homeDir, ".config", "glm-acp-agent");
|
||||||
|
mkdirSync(configDir, { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
join(configDir, "credentials.json"),
|
||||||
|
JSON.stringify({ z_ai_api_key: "zai_file_token" }),
|
||||||
|
);
|
||||||
|
let authorization: string | null = null;
|
||||||
|
fetchApi = (async (_url: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
authorization = (init?.headers as Record<string, string> | undefined)?.Authorization ?? null;
|
||||||
|
return jsonResponse({ data: [{ productName: "GLM Coding Max", status: "VALID" }] });
|
||||||
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
|
const zai = findProvider(await service().listUsage(), "zai");
|
||||||
|
|
||||||
|
expect(authorization).toBe("Bearer zai_file_token");
|
||||||
|
expect(zai).toMatchObject({ status: "available" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("honours XDG_CONFIG_HOME for the credentials file", async () => {
|
||||||
|
const configDir = join(homeDir, "xdg", "glm-acp-agent");
|
||||||
|
mkdirSync(configDir, { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
join(configDir, "credentials.json"),
|
||||||
|
JSON.stringify({ z_ai_api_key: "zai_xdg_token" }),
|
||||||
|
);
|
||||||
|
process.env["XDG_CONFIG_HOME"] = join(homeDir, "xdg");
|
||||||
|
let authorization: string | null = null;
|
||||||
|
fetchApi = (async (_url: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
authorization = (init?.headers as Record<string, string> | undefined)?.Authorization ?? null;
|
||||||
|
return jsonResponse({ data: [{ productName: "GLM Coding Max", status: "VALID" }] });
|
||||||
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
|
const zai = findProvider(await service().listUsage(), "zai");
|
||||||
|
|
||||||
|
expect(authorization).toBe("Bearer zai_xdg_token");
|
||||||
|
expect(zai).toMatchObject({ status: "available" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("discovery returns a locator when fetch finds zai credentials", async () => {
|
||||||
process.env["ZAI_API_KEY"] = "fixture-token";
|
process.env["ZAI_API_KEY"] = "fixture-token";
|
||||||
let requested = false;
|
let requested = false;
|
||||||
await fetchFirst(async () => {
|
await fetchFirst(async () => {
|
||||||
@@ -131,10 +183,7 @@ it("discovery returns a locator when fetch finds zai credentials", async () => {
|
|||||||
expect(await discover()).toEqual([
|
expect(await discover()).toEqual([
|
||||||
{ key: "default", input: { store: "env", locator: "ZAI_API_KEY" } },
|
{ key: "default", input: { store: "env", locator: "ZAI_API_KEY" } },
|
||||||
]);
|
]);
|
||||||
} finally {
|
});
|
||||||
if (previous === undefined) delete process.env["ZAI_API_KEY"];
|
|
||||||
else process.env["ZAI_API_KEY"] = previous;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("account discovery", () => {
|
describe("account discovery", () => {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { UsageInput } from "../shared/input.js";
|
import type { UsageInput } from "../shared/input.js";
|
||||||
|
import { promises as fs } from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
unavailable,
|
unavailable,
|
||||||
@@ -25,11 +28,16 @@ const ZaiUsageResponseSchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** The credentials file the `glm-acp-agent` CLI writes. */
|
||||||
|
const ZaiCredentialsSchema = z.object({
|
||||||
|
z_ai_api_key: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export async function fetchUsage(
|
export async function fetchUsage(
|
||||||
input: UsageInput,
|
input: UsageInput,
|
||||||
fetchApi: typeof fetch = fetch,
|
fetchApi: typeof fetch = fetch,
|
||||||
): Promise<UsageReport> {
|
): Promise<UsageReport> {
|
||||||
const token = process.env[input.locator];
|
const token = await readToken(input);
|
||||||
if (!token) throw new Error("Z.ai login store no longer exists");
|
if (!token) throw new Error("Z.ai login store no longer exists");
|
||||||
|
|
||||||
const res = await fetchApi("https://api.z.ai/api/biz/subscription/list", {
|
const res = await fetchApi("https://api.z.ai/api/biz/subscription/list", {
|
||||||
@@ -64,9 +72,36 @@ export async function fetchUsage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Z.AI API key comes from the environment or from the credentials file the
|
||||||
|
* `glm-acp-agent` CLI writes. The CLI names the variable `Z_AI_API_KEY`; the
|
||||||
|
* older `ZAI_API_KEY` spelling is still accepted.
|
||||||
|
*/
|
||||||
|
async function readToken(input: UsageInput): Promise<string | undefined> {
|
||||||
|
if (input.store === "env") {
|
||||||
|
return process.env[input.locator];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw: unknown = JSON.parse(await fs.readFile(input.locator, "utf8"));
|
||||||
|
return ZaiCredentialsSchema.parse(raw).z_ai_api_key;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function discover(): Promise<UsageAccount[]> {
|
export async function discover(): Promise<UsageAccount[]> {
|
||||||
for (const locator of ["ZAI_API_KEY", "GLM_API_KEY"]) {
|
for (const locator of ["Z_AI_API_KEY", "ZAI_API_KEY", "GLM_API_KEY"]) {
|
||||||
if (process.env[locator]) return [{ key: "default", input: { store: "env", locator } }];
|
if (process.env[locator]) return [{ key: "default", input: { store: "env", locator } }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const credentialsPath = join(
|
||||||
|
process.env.XDG_CONFIG_HOME || join(homedir(), ".config"),
|
||||||
|
"glm-acp-agent",
|
||||||
|
"credentials.json",
|
||||||
|
);
|
||||||
|
if (await readToken({ store: "file", locator: credentialsPath })) {
|
||||||
|
return [{ key: "default", input: { store: "file", locator: credentialsPath } }];
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
export const inputSchema = z
|
export const inputSchema = z
|
||||||
.object({
|
.object({
|
||||||
store: z.literal("env"),
|
store: z.enum(["env", "file"]),
|
||||||
locator: z.string().min(1),
|
locator: z.string().min(1),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|||||||
Reference in New Issue
Block a user