mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-31 13:42:50 +00:00
Compare commits
7
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 { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -32,11 +32,13 @@ describe("zai usage source", () => {
|
||||
process.env["USERPROFILE"] = homeDir;
|
||||
for (const key of [
|
||||
"APPDATA",
|
||||
"XDG_CONFIG_HOME",
|
||||
"COPILOT_TOKEN",
|
||||
"GITHUB_TOKEN",
|
||||
"GITHUB_PAT",
|
||||
"CURSOR_ACCESS_TOKEN",
|
||||
"CURSOR_TOKEN",
|
||||
"Z_AI_API_KEY",
|
||||
"ZAI_API_KEY",
|
||||
"GLM_API_KEY",
|
||||
"GROK_API_KEY",
|
||||
@@ -116,11 +118,61 @@ describe("zai usage source", () => {
|
||||
details: expect.arrayContaining([{ id: "status", label: "Status", value: "VALID" }]),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("discovery returns a locator when fetch finds zai credentials", async () => {
|
||||
const previous = process.env["ZAI_API_KEY"];
|
||||
try {
|
||||
it("fetches Z.ai usage from Z_AI_API_KEY, the name the glm-acp-agent CLI uses", async () => {
|
||||
process.env["Z_AI_API_KEY"] = "zai_cli_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_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";
|
||||
let requested = false;
|
||||
await fetchFirst(async () => {
|
||||
@@ -131,10 +183,7 @@ it("discovery returns a locator when fetch finds zai credentials", async () => {
|
||||
expect(await discover()).toEqual([
|
||||
{ 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", () => {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
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 {
|
||||
unavailable,
|
||||
@@ -25,11 +28,16 @@ const ZaiUsageResponseSchema = z.object({
|
||||
.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(
|
||||
input: UsageInput,
|
||||
fetchApi: typeof fetch = fetch,
|
||||
): 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");
|
||||
|
||||
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[]> {
|
||||
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 } }];
|
||||
}
|
||||
|
||||
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 [];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
export const inputSchema = z
|
||||
.object({
|
||||
store: z.literal("env"),
|
||||
store: z.enum(["env", "file"]),
|
||||
locator: z.string().min(1),
|
||||
})
|
||||
.strict();
|
||||
|
||||
Reference in New Issue
Block a user