@ai-devkit/agent-manager 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AgentManager.d.ts +11 -2
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +37 -6
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +212 -5
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/capacity/codex.test.js +2 -1
- package/dist/__tests__/capacity/codex.test.js.map +1 -1
- package/dist/__tests__/capacity/index.test.js +6 -3
- package/dist/__tests__/capacity/index.test.js.map +1 -1
- package/dist/__tests__/capacity/zai.test.js +213 -0
- package/dist/__tests__/capacity/zai.test.js.map +1 -0
- package/dist/__tests__/{adapters → providers/copilot}/CopilotAdapter.test.js +19 -14
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/{adapters → providers/gemini}/GeminiCliAdapter.test.js +37 -29
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +189 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js +89 -0
- package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js +94 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js +147 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js.map +1 -0
- package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js +164 -0
- package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js.map +1 -0
- package/dist/__tests__/runtime/HerdrAgentRuntime.test.js +37 -1
- package/dist/__tests__/runtime/HerdrAgentRuntime.test.js.map +1 -1
- package/dist/__tests__/utils/AgentRegistry.test.js +20 -13
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
- package/dist/adapters/index.d.ts +3 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/capacity/codex.d.ts +1 -0
- package/dist/capacity/codex.d.ts.map +1 -1
- package/dist/capacity/codex.js +9 -4
- package/dist/capacity/codex.js.map +1 -1
- package/dist/capacity/index.d.ts +8 -0
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +10 -9
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/types.d.ts +8 -0
- package/dist/capacity/types.d.ts.map +1 -1
- package/dist/capacity/types.js.map +1 -1
- package/dist/capacity/zai.d.ts +14 -0
- package/dist/capacity/zai.d.ts.map +1 -0
- package/dist/capacity/zai.js +203 -0
- package/dist/capacity/zai.js.map +1 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/{adapters → providers/copilot}/CopilotAdapter.d.ts +11 -25
- package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotAdapter.js +108 -0
- package/dist/providers/copilot/CopilotAdapter.js.map +1 -0
- package/dist/providers/copilot/CopilotAgentMapper.d.ts +9 -0
- package/dist/providers/copilot/CopilotAgentMapper.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotAgentMapper.js +37 -0
- package/dist/providers/copilot/CopilotAgentMapper.js.map +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts +19 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.js +41 -0
- package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -0
- package/dist/providers/copilot/CopilotSessionParser.d.ts +30 -0
- package/dist/providers/copilot/CopilotSessionParser.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotSessionParser.js +181 -0
- package/dist/providers/copilot/CopilotSessionParser.js.map +1 -0
- package/dist/providers/gemini/GeminiAgentMapper.d.ts +9 -0
- package/dist/providers/gemini/GeminiAgentMapper.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiAgentMapper.js +37 -0
- package/dist/providers/gemini/GeminiAgentMapper.js.map +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts +52 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.js +188 -0
- package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -0
- package/dist/providers/gemini/GeminiSessionLocator.d.ts +30 -0
- package/dist/providers/gemini/GeminiSessionLocator.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiSessionLocator.js +142 -0
- package/dist/providers/gemini/GeminiSessionLocator.js.map +1 -0
- package/dist/providers/gemini/GeminiSessionParser.d.ts +94 -0
- package/dist/providers/gemini/GeminiSessionParser.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiSessionParser.js +166 -0
- package/dist/providers/gemini/GeminiSessionParser.js.map +1 -0
- package/dist/{adapters → providers/opencode}/OpenCodeAdapter.d.ts +6 -12
- package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeAdapter.js +86 -0
- package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.d.ts +11 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.js +50 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.js.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +21 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.js +99 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionParser.d.ts +18 -0
- package/dist/providers/opencode/OpenCodeSessionParser.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionParser.js +104 -0
- package/dist/providers/opencode/OpenCodeSessionParser.js.map +1 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts +18 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js +183 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts +5 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts.map +1 -1
- package/dist/runtime/herdr/HerdrAgentRuntime.js +26 -8
- package/dist/runtime/herdr/HerdrAgentRuntime.js.map +1 -1
- package/dist/runtime/herdr/HerdrResponseParsers.d.ts +2 -0
- package/dist/runtime/herdr/HerdrResponseParsers.d.ts.map +1 -1
- package/dist/runtime/herdr/HerdrResponseParsers.js +12 -1
- package/dist/runtime/herdr/HerdrResponseParsers.js.map +1 -1
- package/dist/utils/AgentRegistry.d.ts.map +1 -1
- package/dist/utils/AgentRegistry.js +5 -5
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +67 -5
- package/src/__tests__/AgentManager.test.ts +231 -5
- package/src/__tests__/capacity/codex.test.ts +94 -26
- package/src/__tests__/capacity/fixtures/zai-auth.json +6 -0
- package/src/__tests__/capacity/fixtures/zai-quota-invalid.json +7 -0
- package/src/__tests__/capacity/fixtures/zai-quota.json +38 -0
- package/src/__tests__/capacity/index.test.ts +10 -3
- package/src/__tests__/capacity/zai.test.ts +186 -0
- package/src/__tests__/{adapters → providers/copilot}/CopilotAdapter.test.ts +13 -16
- package/src/__tests__/{adapters → providers/gemini}/GeminiCliAdapter.test.ts +39 -31
- package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +197 -0
- package/src/__tests__/providers/opencode/OpenCodeAgentMapper.test.ts +83 -0
- package/src/__tests__/providers/opencode/OpenCodeSessionLocator.test.ts +84 -0
- package/src/__tests__/providers/opencode/OpenCodeSessionParser.test.ts +148 -0
- package/src/__tests__/runtime/HerdrAgentDiscovery.test.ts +131 -0
- package/src/__tests__/runtime/HerdrAgentRuntime.test.ts +20 -1
- package/src/__tests__/utils/AgentRegistry.test.ts +20 -10
- package/src/adapters/index.ts +3 -3
- package/src/capacity/codex.ts +96 -28
- package/src/capacity/index.ts +29 -11
- package/src/capacity/types.ts +8 -0
- package/src/capacity/zai.ts +237 -0
- package/src/index.ts +18 -6
- package/src/providers/copilot/CopilotAdapter.ts +145 -0
- package/src/providers/copilot/CopilotAgentMapper.ts +36 -0
- package/src/providers/copilot/CopilotSessionLocator.ts +57 -0
- package/src/providers/copilot/CopilotSessionParser.ts +289 -0
- package/src/providers/gemini/GeminiAgentMapper.ts +36 -0
- package/src/providers/gemini/GeminiCliAdapter.ts +266 -0
- package/src/providers/gemini/GeminiSessionLocator.ts +183 -0
- package/src/providers/gemini/GeminiSessionParser.ts +271 -0
- package/src/providers/opencode/OpenCodeAdapter.ts +112 -0
- package/src/providers/opencode/OpenCodeAgentMapper.ts +61 -0
- package/src/providers/opencode/OpenCodeSessionLocator.ts +123 -0
- package/src/providers/opencode/OpenCodeSessionParser.ts +145 -0
- package/src/runtime/herdr/HerdrAgentDiscovery.ts +178 -0
- package/src/runtime/herdr/HerdrAgentRuntime.ts +33 -7
- package/src/runtime/herdr/HerdrResponseParsers.ts +11 -1
- package/src/utils/AgentRegistry.ts +8 -6
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +0 -1
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +0 -1
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +0 -635
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +0 -1
- package/dist/adapters/CopilotAdapter.d.ts.map +0 -1
- package/dist/adapters/CopilotAdapter.js +0 -329
- package/dist/adapters/CopilotAdapter.js.map +0 -1
- package/dist/adapters/GeminiCliAdapter.d.ts +0 -98
- package/dist/adapters/GeminiCliAdapter.d.ts.map +0 -1
- package/dist/adapters/GeminiCliAdapter.js +0 -513
- package/dist/adapters/GeminiCliAdapter.js.map +0 -1
- package/dist/adapters/OpenCodeAdapter.d.ts.map +0 -1
- package/dist/adapters/OpenCodeAdapter.js +0 -286
- package/dist/adapters/OpenCodeAdapter.js.map +0 -1
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +0 -543
- package/src/adapters/CopilotAdapter.ts +0 -483
- package/src/adapters/GeminiCliAdapter.ts +0 -699
- package/src/adapters/OpenCodeAdapter.ts +0 -369
|
@@ -92,24 +92,34 @@ describe("AgentRegistry", () => {
|
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
it("
|
|
95
|
+
it("preserves existing identity and routing fields for same-pid refreshes", () => {
|
|
96
96
|
const runtimeRef = { session: "default", paneId: "w1:p2", agentName: "requested-name" };
|
|
97
97
|
|
|
98
|
-
registry.register(
|
|
98
|
+
registry.register(
|
|
99
|
+
makeEntry({
|
|
100
|
+
name: "old-name",
|
|
101
|
+
pid: process.pid,
|
|
102
|
+
runtime: "tmux",
|
|
103
|
+
runtimeRef: { session: "old-name" },
|
|
104
|
+
cwd: "/original",
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
99
107
|
registry.register(
|
|
100
108
|
makeEntry({
|
|
101
109
|
name: "requested-name",
|
|
102
110
|
pid: process.pid,
|
|
103
111
|
runtime: "herdr",
|
|
104
112
|
runtimeRef,
|
|
113
|
+
cwd: "/incoming",
|
|
105
114
|
}),
|
|
106
115
|
);
|
|
107
116
|
|
|
108
|
-
expect(registry.lookup("
|
|
109
|
-
expect(registry.lookup("
|
|
117
|
+
expect(registry.lookup("requested-name")).toBeNull();
|
|
118
|
+
expect(registry.lookup("old-name")).toMatchObject({
|
|
110
119
|
pid: process.pid,
|
|
111
|
-
runtime: "
|
|
112
|
-
runtimeRef,
|
|
120
|
+
runtime: "tmux",
|
|
121
|
+
runtimeRef: { session: "old-name" },
|
|
122
|
+
cwd: "/original",
|
|
113
123
|
});
|
|
114
124
|
});
|
|
115
125
|
|
|
@@ -127,11 +137,11 @@ describe("AgentRegistry", () => {
|
|
|
127
137
|
expect(registry.lookup("a")?.runtimeRef).toBeNull();
|
|
128
138
|
});
|
|
129
139
|
|
|
130
|
-
it("
|
|
140
|
+
it("preserves an existing generated fallback for the same pid", () => {
|
|
131
141
|
registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, runtimeRef: null }));
|
|
132
142
|
registry.register(makeEntry({ name: "custom-name", runtimeRef: { session: "custom-name" } }));
|
|
133
|
-
expect(registry.lookup("custom-name")
|
|
134
|
-
expect(registry.lookup(`ai-devkit-${process.pid}`)).toBeNull();
|
|
143
|
+
expect(registry.lookup("custom-name")).toBeNull();
|
|
144
|
+
expect(registry.lookup(`ai-devkit-${process.pid}`)?.runtimeRef).toBeNull();
|
|
135
145
|
expect(registry.list()).toHaveLength(1);
|
|
136
146
|
});
|
|
137
147
|
|
|
@@ -186,7 +196,7 @@ describe("AgentRegistry", () => {
|
|
|
186
196
|
registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, runtimeRef: null }));
|
|
187
197
|
|
|
188
198
|
expect(registry.list()).toHaveLength(1);
|
|
189
|
-
expect(registry.lookup(
|
|
199
|
+
expect(registry.lookup(`ai-devkit-${process.pid}`)?.pid).toBe(process.pid);
|
|
190
200
|
});
|
|
191
201
|
|
|
192
202
|
it("cleans up a cross-type row when its pid has been reused", () => {
|
package/src/adapters/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { ClaudeCodeAdapter } from "../providers/claude/ClaudeCodeAdapter.js";
|
|
2
2
|
export { CodexAdapter } from "../providers/codex/CodexAdapter.js";
|
|
3
|
-
export { CopilotAdapter } from "
|
|
4
|
-
export { GeminiCliAdapter } from "
|
|
3
|
+
export { CopilotAdapter } from "../providers/copilot/CopilotAdapter.js";
|
|
4
|
+
export { GeminiCliAdapter } from "../providers/gemini/GeminiCliAdapter.js";
|
|
5
5
|
export { GrokCliAdapter } from "./GrokCliAdapter.js";
|
|
6
|
-
export { OpenCodeAdapter } from "
|
|
6
|
+
export { OpenCodeAdapter } from "../providers/opencode/OpenCodeAdapter.js";
|
|
7
7
|
export { PiAdapter } from "../providers/pi/PiAdapter.js";
|
|
8
8
|
export { AgentStatus } from "./AgentAdapter.js";
|
|
9
9
|
export type {
|
package/src/capacity/codex.ts
CHANGED
|
@@ -15,7 +15,13 @@ type RpcMessage = { id?: number; method: string; params?: UnknownRecord };
|
|
|
15
15
|
type CliResponses = { rateLimits: unknown; account: unknown };
|
|
16
16
|
type CodexRpc = (messages: RpcMessage[]) => Promise<CliResponses>;
|
|
17
17
|
|
|
18
|
-
export const CODEX_APP_SERVER_ARGS = [
|
|
18
|
+
export const CODEX_APP_SERVER_ARGS = [
|
|
19
|
+
"-s",
|
|
20
|
+
"read-only",
|
|
21
|
+
"-a",
|
|
22
|
+
"untrusted",
|
|
23
|
+
"app-server",
|
|
24
|
+
] as const;
|
|
19
25
|
|
|
20
26
|
type CodexProbeOptions = {
|
|
21
27
|
installed: boolean;
|
|
@@ -57,12 +63,18 @@ function safeIdentifier(value: unknown): string | null {
|
|
|
57
63
|
return candidate;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
export function resolveCodexAuthPath(
|
|
66
|
+
export function resolveCodexAuthPath(
|
|
67
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
68
|
+
): string {
|
|
61
69
|
const root = env.CODEX_HOME || join(env.HOME || "", ".codex");
|
|
62
70
|
return join(root, "auth.json");
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
export function toRateWindow(
|
|
73
|
+
export function toRateWindow(
|
|
74
|
+
value: unknown,
|
|
75
|
+
id: string,
|
|
76
|
+
label: string,
|
|
77
|
+
): CapacityWindow | null {
|
|
66
78
|
const input = record(value);
|
|
67
79
|
if (!input) return null;
|
|
68
80
|
const used = finiteNumber(input.used_percent);
|
|
@@ -84,13 +96,24 @@ function extraWindows(value: unknown): CapacityWindow[] {
|
|
|
84
96
|
const scope = safeIdentifier(limit.limit_name) ?? `extra-${index + 1}`;
|
|
85
97
|
const windows = record(limit.rate_limit) ?? limit;
|
|
86
98
|
return [
|
|
87
|
-
toRateWindow(
|
|
88
|
-
|
|
99
|
+
toRateWindow(
|
|
100
|
+
windows.primary_window,
|
|
101
|
+
`${scope}:primary`,
|
|
102
|
+
`${scope} primary`,
|
|
103
|
+
),
|
|
104
|
+
toRateWindow(
|
|
105
|
+
windows.secondary_window,
|
|
106
|
+
`${scope}:secondary`,
|
|
107
|
+
`${scope} secondary`,
|
|
108
|
+
),
|
|
89
109
|
].filter((window): window is CapacityWindow => window !== null);
|
|
90
110
|
});
|
|
91
111
|
}
|
|
92
112
|
|
|
93
|
-
export function parseUsage(
|
|
113
|
+
export function parseUsage(
|
|
114
|
+
raw: unknown,
|
|
115
|
+
source: "pat" | "oauth",
|
|
116
|
+
): UsageSnapshot {
|
|
94
117
|
const response = record(raw) ?? {};
|
|
95
118
|
const limits = record(response.rate_limit) ?? {};
|
|
96
119
|
const credits = record(response.credits) ?? {};
|
|
@@ -105,7 +128,11 @@ export function parseUsage(raw: unknown, source: "pat" | "oauth"): UsageSnapshot
|
|
|
105
128
|
};
|
|
106
129
|
}
|
|
107
130
|
|
|
108
|
-
function cliWindow(
|
|
131
|
+
function cliWindow(
|
|
132
|
+
value: unknown,
|
|
133
|
+
id: string,
|
|
134
|
+
label: string,
|
|
135
|
+
): CapacityWindow | null {
|
|
109
136
|
const input = record(value);
|
|
110
137
|
if (!input) return null;
|
|
111
138
|
return {
|
|
@@ -117,10 +144,14 @@ function cliWindow(value: unknown, id: string, label: string): CapacityWindow |
|
|
|
117
144
|
};
|
|
118
145
|
}
|
|
119
146
|
|
|
120
|
-
function cliSnapshotWindows(
|
|
147
|
+
function cliSnapshotWindows(
|
|
148
|
+
value: unknown,
|
|
149
|
+
fallbackId: string,
|
|
150
|
+
): CapacityWindow[] {
|
|
121
151
|
const snapshot = record(value);
|
|
122
152
|
if (!snapshot) return [];
|
|
123
|
-
const scope =
|
|
153
|
+
const scope =
|
|
154
|
+
safeIdentifier(snapshot.limitId) ?? safeIdentifier(fallbackId) ?? "codex";
|
|
124
155
|
return [
|
|
125
156
|
cliWindow(snapshot.primary, `${scope}:primary`, `${scope} primary`),
|
|
126
157
|
cliWindow(snapshot.secondary, `${scope}:secondary`, `${scope} secondary`),
|
|
@@ -136,7 +167,9 @@ export function parseCliUsage(raw: unknown): UsageSnapshot {
|
|
|
136
167
|
for (const [id, snapshot] of Object.entries(buckets))
|
|
137
168
|
windows.push(...cliSnapshotWindows(snapshot, id));
|
|
138
169
|
}
|
|
139
|
-
const unique = [
|
|
170
|
+
const unique = [
|
|
171
|
+
...new Map(windows.map((window) => [window.id, window])).values(),
|
|
172
|
+
];
|
|
140
173
|
return { windows: unique, creditsRemaining: null, source: "cli" };
|
|
141
174
|
}
|
|
142
175
|
|
|
@@ -145,18 +178,23 @@ function capacityFromSnapshot(
|
|
|
145
178
|
context: CodexProbeOptions,
|
|
146
179
|
raw?: unknown,
|
|
147
180
|
): CapacityReport {
|
|
148
|
-
const hasUsage = snapshot.windows.some(
|
|
181
|
+
const hasUsage = snapshot.windows.some(
|
|
182
|
+
(window) => window.usedPercent !== null,
|
|
183
|
+
);
|
|
149
184
|
const rateLimits = record(record(raw)?.rateLimits);
|
|
150
185
|
const reached = nonEmptyText(rateLimits?.rateLimitReachedType);
|
|
151
186
|
const resetCredits =
|
|
152
|
-
record(record(raw)?.rateLimitResetCredits) ??
|
|
187
|
+
record(record(raw)?.rateLimitResetCredits) ??
|
|
188
|
+
record(record(raw)?.usageLimitResetCredits);
|
|
153
189
|
return {
|
|
154
|
-
|
|
190
|
+
harness: "codex",
|
|
191
|
+
provider: "openai",
|
|
155
192
|
generatedAt: context.checkedAt,
|
|
156
193
|
authenticated: true,
|
|
157
194
|
available: reached ? "no" : hasUsage ? "yes" : "unknown",
|
|
158
195
|
windows: snapshot.windows,
|
|
159
|
-
creditsRemaining:
|
|
196
|
+
creditsRemaining:
|
|
197
|
+
snapshot.creditsRemaining ?? finiteNumber(resetCredits?.availableCount),
|
|
160
198
|
};
|
|
161
199
|
}
|
|
162
200
|
|
|
@@ -164,7 +202,9 @@ function jwtExpiry(token: string): number | null {
|
|
|
164
202
|
const part = token.split(".")[1];
|
|
165
203
|
if (!part) return null;
|
|
166
204
|
try {
|
|
167
|
-
return finiteNumber(
|
|
205
|
+
return finiteNumber(
|
|
206
|
+
record(JSON.parse(Buffer.from(part, "base64url").toString("utf8")))?.exp,
|
|
207
|
+
);
|
|
168
208
|
} catch {
|
|
169
209
|
return null;
|
|
170
210
|
}
|
|
@@ -191,7 +231,10 @@ async function fetchJson(
|
|
|
191
231
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
192
232
|
try {
|
|
193
233
|
const response = await fetcher(url, { ...init, signal: controller.signal });
|
|
194
|
-
if (!response.ok)
|
|
234
|
+
if (!response.ok)
|
|
235
|
+
throw new Error(
|
|
236
|
+
response.status === 401 ? "unauthorized" : "request failed",
|
|
237
|
+
);
|
|
195
238
|
return await response.json();
|
|
196
239
|
} finally {
|
|
197
240
|
clearTimeout(timer);
|
|
@@ -209,14 +252,20 @@ async function apiSnapshot(
|
|
|
209
252
|
fetcher,
|
|
210
253
|
"https://chatgpt.com/backend-api/wham/usage",
|
|
211
254
|
{
|
|
212
|
-
headers: {
|
|
255
|
+
headers: {
|
|
256
|
+
Authorization: `Bearer ${token}`,
|
|
257
|
+
"ChatGPT-Account-Id": accountId,
|
|
258
|
+
},
|
|
213
259
|
},
|
|
214
260
|
options.timeoutMs ?? 5000,
|
|
215
261
|
);
|
|
216
262
|
return parseUsage(raw, source);
|
|
217
263
|
}
|
|
218
264
|
|
|
219
|
-
function appServerRpc(
|
|
265
|
+
function appServerRpc(
|
|
266
|
+
messages: RpcMessage[],
|
|
267
|
+
timeoutMs = 5000,
|
|
268
|
+
): Promise<CliResponses> {
|
|
220
269
|
return new Promise((resolve, reject) => {
|
|
221
270
|
const child = spawn("codex", CODEX_APP_SERVER_ARGS, {
|
|
222
271
|
stdio: ["pipe", "pipe", "ignore"],
|
|
@@ -232,8 +281,13 @@ function appServerRpc(messages: RpcMessage[], timeoutMs = 5000): Promise<CliResp
|
|
|
232
281
|
if (error) reject(error);
|
|
233
282
|
else resolve(results as CliResponses);
|
|
234
283
|
};
|
|
235
|
-
const timer = setTimeout(
|
|
236
|
-
|
|
284
|
+
const timer = setTimeout(
|
|
285
|
+
() => finish(new Error("codex probe timed out")),
|
|
286
|
+
timeoutMs,
|
|
287
|
+
);
|
|
288
|
+
child.once("error", () =>
|
|
289
|
+
finish(new Error("codex app-server unavailable")),
|
|
290
|
+
);
|
|
237
291
|
child.once("exit", () => {
|
|
238
292
|
if (!settled) finish(new Error("codex app-server exited"));
|
|
239
293
|
});
|
|
@@ -256,7 +310,8 @@ function appServerRpc(messages: RpcMessage[], timeoutMs = 5000): Promise<CliResp
|
|
|
256
310
|
for (const request of messages.slice(1))
|
|
257
311
|
child.stdin.write(`${JSON.stringify(request)}\n`);
|
|
258
312
|
} else if (message.id === 2) {
|
|
259
|
-
if (message.error)
|
|
313
|
+
if (message.error)
|
|
314
|
+
finish(new Error("codex rate-limit method failed"));
|
|
260
315
|
else results.rateLimits = message.result;
|
|
261
316
|
} else if (message.id === 3) {
|
|
262
317
|
if (message.error) finish(new Error("codex account method failed"));
|
|
@@ -269,10 +324,11 @@ function appServerRpc(messages: RpcMessage[], timeoutMs = 5000): Promise<CliResp
|
|
|
269
324
|
});
|
|
270
325
|
}
|
|
271
326
|
|
|
272
|
-
function
|
|
327
|
+
export function codexUnavailableReport(checkedAt: string): CapacityReport {
|
|
273
328
|
return {
|
|
274
|
-
|
|
275
|
-
|
|
329
|
+
harness: "codex",
|
|
330
|
+
provider: "openai",
|
|
331
|
+
generatedAt: checkedAt,
|
|
276
332
|
authenticated: null,
|
|
277
333
|
available: "unknown",
|
|
278
334
|
windows: [],
|
|
@@ -280,7 +336,13 @@ function unavailable(options: CodexProbeOptions): CapacityReport {
|
|
|
280
336
|
};
|
|
281
337
|
}
|
|
282
338
|
|
|
283
|
-
|
|
339
|
+
function unavailable(options: CodexProbeOptions): CapacityReport {
|
|
340
|
+
return codexUnavailableReport(options.checkedAt);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function cliFallback(
|
|
344
|
+
options: CodexProbeOptions,
|
|
345
|
+
): Promise<CapacityReport> {
|
|
284
346
|
if (!options.installed) return unavailable(options);
|
|
285
347
|
const messages: RpcMessage[] = [
|
|
286
348
|
{
|
|
@@ -296,7 +358,8 @@ async function cliFallback(options: CodexProbeOptions): Promise<CapacityReport>
|
|
|
296
358
|
{ id: 3, method: "account/read" },
|
|
297
359
|
];
|
|
298
360
|
try {
|
|
299
|
-
const rpc =
|
|
361
|
+
const rpc =
|
|
362
|
+
options.rpc ?? ((requests) => appServerRpc(requests, options.timeoutMs));
|
|
300
363
|
const response = await rpc(messages);
|
|
301
364
|
const result = capacityFromSnapshot(
|
|
302
365
|
parseCliUsage(response.rateLimits),
|
|
@@ -318,7 +381,9 @@ async function cliFallback(options: CodexProbeOptions): Promise<CapacityReport>
|
|
|
318
381
|
}
|
|
319
382
|
}
|
|
320
383
|
|
|
321
|
-
export async function probeCodexCapacity(
|
|
384
|
+
export async function probeCodexCapacity(
|
|
385
|
+
options: CodexProbeOptions,
|
|
386
|
+
): Promise<CapacityReport> {
|
|
322
387
|
let parsed: UnknownRecord | null = null;
|
|
323
388
|
try {
|
|
324
389
|
const contents = await (options.readFile ?? readFile)(
|
|
@@ -345,7 +410,10 @@ export async function probeCodexCapacity(options: CodexProbeOptions): Promise<Ca
|
|
|
345
410
|
);
|
|
346
411
|
const accountId = nonEmptyText(whoami?.chatgpt_account_id);
|
|
347
412
|
if (!accountId) throw new Error("account unavailable");
|
|
348
|
-
return capacityFromSnapshot(
|
|
413
|
+
return capacityFromSnapshot(
|
|
414
|
+
await apiSnapshot(pat, accountId, "pat", options),
|
|
415
|
+
options,
|
|
416
|
+
);
|
|
349
417
|
} catch {
|
|
350
418
|
// Continue to a separately available OAuth credential before using the CLI.
|
|
351
419
|
}
|
package/src/capacity/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { constants } from "node:fs";
|
|
2
2
|
import { access as fsAccess } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { probeCodexCapacity } from "./codex.js";
|
|
4
|
+
import { codexUnavailableReport, probeCodexCapacity } from "./codex.js";
|
|
5
|
+
import { probeZaiCapacity } from "./zai.js";
|
|
5
6
|
import type { CapacityReport } from "./types.js";
|
|
6
7
|
|
|
7
8
|
export type { CapacityReport, CapacityWindow } from "./types.js";
|
|
@@ -13,6 +14,14 @@ export type CapacityProbeOptions = {
|
|
|
13
14
|
probe?: typeof probeCodexCapacity;
|
|
14
15
|
};
|
|
15
16
|
|
|
17
|
+
export type ZaiCapacityOptions = {
|
|
18
|
+
now?: () => Date;
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;
|
|
21
|
+
fetch?: typeof globalThis.fetch;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
16
25
|
async function canAccess(target: string, mode: number): Promise<boolean> {
|
|
17
26
|
try {
|
|
18
27
|
await fsAccess(target, mode);
|
|
@@ -46,17 +55,26 @@ export async function getCodexCapacityReport(
|
|
|
46
55
|
options: CapacityProbeOptions = {},
|
|
47
56
|
): Promise<CapacityReport> {
|
|
48
57
|
const generatedAt = (options.now?.() ?? new Date()).toISOString();
|
|
49
|
-
const installed = await isCodexInstalled(
|
|
58
|
+
const installed = await isCodexInstalled(
|
|
59
|
+
options.path ?? process.env.PATH ?? "",
|
|
60
|
+
options.access,
|
|
61
|
+
);
|
|
50
62
|
try {
|
|
51
|
-
return await (options.probe ?? probeCodexCapacity)({
|
|
63
|
+
return await (options.probe ?? probeCodexCapacity)({
|
|
64
|
+
installed,
|
|
65
|
+
checkedAt: generatedAt,
|
|
66
|
+
});
|
|
52
67
|
} catch {
|
|
53
|
-
return
|
|
54
|
-
provider: "codex",
|
|
55
|
-
generatedAt,
|
|
56
|
-
authenticated: null,
|
|
57
|
-
available: "unknown",
|
|
58
|
-
windows: [],
|
|
59
|
-
creditsRemaining: null,
|
|
60
|
-
};
|
|
68
|
+
return codexUnavailableReport(generatedAt);
|
|
61
69
|
}
|
|
62
70
|
}
|
|
71
|
+
|
|
72
|
+
export async function getZaiCapacityReport(
|
|
73
|
+
options: ZaiCapacityOptions = {},
|
|
74
|
+
): Promise<CapacityReport> {
|
|
75
|
+
const { now, ...probeOptions } = options;
|
|
76
|
+
return probeZaiCapacity({
|
|
77
|
+
...probeOptions,
|
|
78
|
+
checkedAt: (now?.() ?? new Date()).toISOString(),
|
|
79
|
+
});
|
|
80
|
+
}
|
package/src/capacity/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type Availability = "yes" | "no" | "unknown";
|
|
2
|
+
export type CapacityLimitType = "TOKENS_LIMIT" | "CREDIT_LIMIT" | "TIME_LIMIT";
|
|
2
3
|
|
|
3
4
|
export interface CapacityWindow {
|
|
4
5
|
id: string;
|
|
@@ -6,9 +7,16 @@ export interface CapacityWindow {
|
|
|
6
7
|
durationMinutes: number | null;
|
|
7
8
|
usedPercent: number | null;
|
|
8
9
|
resetsAt: string | null;
|
|
10
|
+
limitType?: CapacityLimitType;
|
|
11
|
+
total?: number | null;
|
|
12
|
+
current?: number | null;
|
|
13
|
+
remaining?: number | null;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export interface CapacityReport {
|
|
17
|
+
/** Harness whose quota pool is measured, e.g. "pi" or "codex". */
|
|
18
|
+
harness: string;
|
|
19
|
+
/** Model provider backing the harness, e.g. "zai" or "openai". */
|
|
12
20
|
provider: string;
|
|
13
21
|
generatedAt: string;
|
|
14
22
|
authenticated: boolean | null;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type {
|
|
5
|
+
Availability,
|
|
6
|
+
CapacityLimitType,
|
|
7
|
+
CapacityReport,
|
|
8
|
+
CapacityWindow,
|
|
9
|
+
} from "./types.js";
|
|
10
|
+
|
|
11
|
+
const ZAI_QUOTA_URL = "https://api.z.ai/api/monitor/usage/quota/limit";
|
|
12
|
+
const MISSING_API_KEY_MESSAGE =
|
|
13
|
+
"z.ai API key not found; set Z_AI_API_KEY or configure Pi provider zai";
|
|
14
|
+
const LIMIT_METADATA = {
|
|
15
|
+
TOKENS_LIMIT: { id: "tokens", label: "Tokens" },
|
|
16
|
+
CREDIT_LIMIT: { id: "credit", label: "Credit" },
|
|
17
|
+
TIME_LIMIT: { id: "time", label: "Time" },
|
|
18
|
+
} as const satisfies Record<CapacityLimitType, { id: string; label: string }>;
|
|
19
|
+
const UNIT_METADATA = new Map<number, { minutes: number; label: string }>([
|
|
20
|
+
[1, { minutes: 24 * 60, label: "day" }],
|
|
21
|
+
[3, { minutes: 60, label: "hour" }],
|
|
22
|
+
[5, { minutes: 1, label: "minute" }],
|
|
23
|
+
[6, { minutes: 7 * 24 * 60, label: "week" }],
|
|
24
|
+
]);
|
|
25
|
+
type UnknownRecord = Record<string, unknown>;
|
|
26
|
+
|
|
27
|
+
export type ZaiCredentialOptions = {
|
|
28
|
+
env?: NodeJS.ProcessEnv;
|
|
29
|
+
readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ZaiProbeOptions = ZaiCredentialOptions & {
|
|
33
|
+
checkedAt: string;
|
|
34
|
+
fetch?: typeof globalThis.fetch;
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function record(value: unknown): UnknownRecord | null {
|
|
39
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
40
|
+
? (value as UnknownRecord)
|
|
41
|
+
: null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function nonEmptyText(value: unknown): string | null {
|
|
45
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function optionalNumber(value: unknown, field: string): number | null {
|
|
49
|
+
if (value === undefined || value === null) return null;
|
|
50
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
51
|
+
throw new Error(`Invalid z.ai quota ${field}`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function clampPercent(value: number): number {
|
|
57
|
+
return Math.max(0, Math.min(100, value));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function integer(value: unknown): number | null {
|
|
61
|
+
return typeof value === "number" && Number.isInteger(value) ? value : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isCapacityLimitType(value: string): value is CapacityLimitType {
|
|
65
|
+
return Object.hasOwn(LIMIT_METADATA, value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function durationMinutes(
|
|
69
|
+
unit: number,
|
|
70
|
+
number: number,
|
|
71
|
+
type: CapacityLimitType,
|
|
72
|
+
): number | null {
|
|
73
|
+
if (type === "TIME_LIMIT" && unit === 5 && number === 1) return 30 * 24 * 60;
|
|
74
|
+
const metadata = UNIT_METADATA.get(unit);
|
|
75
|
+
return metadata === undefined || number <= 0
|
|
76
|
+
? null
|
|
77
|
+
: metadata.minutes * number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function windowLabel(
|
|
81
|
+
type: CapacityLimitType,
|
|
82
|
+
unit: number,
|
|
83
|
+
number: number,
|
|
84
|
+
): string {
|
|
85
|
+
if (type === "TIME_LIMIT" && unit === 5 && number === 1)
|
|
86
|
+
return "MCP · monthly";
|
|
87
|
+
const kind = LIMIT_METADATA[type].label;
|
|
88
|
+
const unitLabel = UNIT_METADATA.get(unit)?.label;
|
|
89
|
+
if (!unitLabel) return kind;
|
|
90
|
+
return `${kind} · ${number} ${unitLabel}${number === 1 ? "" : "s"}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resetTime(value: unknown): string | null {
|
|
94
|
+
const milliseconds = optionalNumber(value, "limit nextResetTime");
|
|
95
|
+
if (milliseconds === null) return null;
|
|
96
|
+
const date = new Date(milliseconds);
|
|
97
|
+
if (Number.isNaN(date.getTime()))
|
|
98
|
+
throw new Error("Invalid z.ai quota limit nextResetTime");
|
|
99
|
+
return date.toISOString();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function availability(windows: CapacityWindow[]): Availability {
|
|
103
|
+
if (windows.length === 0) return "unknown";
|
|
104
|
+
if (
|
|
105
|
+
windows.some(
|
|
106
|
+
(window) => window.usedPercent !== null && window.usedPercent < 100,
|
|
107
|
+
)
|
|
108
|
+
) {
|
|
109
|
+
return "yes";
|
|
110
|
+
}
|
|
111
|
+
return "no";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseLimit(value: unknown, index: number): CapacityWindow | null {
|
|
115
|
+
const limit = record(value);
|
|
116
|
+
if (!limit) throw new Error("Invalid z.ai quota limit entry");
|
|
117
|
+
const unit = integer(limit.unit);
|
|
118
|
+
const count = integer(limit.number);
|
|
119
|
+
if (typeof limit.type !== "string" || unit === null || count === null) {
|
|
120
|
+
throw new Error("Invalid z.ai quota limit entry");
|
|
121
|
+
}
|
|
122
|
+
const serverPercent = optionalNumber(limit.percentage, "limit percentage");
|
|
123
|
+
if (serverPercent === null)
|
|
124
|
+
throw new Error("Invalid z.ai quota limit percentage");
|
|
125
|
+
if (!isCapacityLimitType(limit.type)) return null;
|
|
126
|
+
|
|
127
|
+
const total = optionalNumber(limit.usage, "limit usage");
|
|
128
|
+
const current = optionalNumber(limit.currentValue, "limit currentValue");
|
|
129
|
+
const remaining = optionalNumber(limit.remaining, "limit remaining");
|
|
130
|
+
let consumed =
|
|
131
|
+
total !== null && remaining !== null ? total - remaining : null;
|
|
132
|
+
if (current !== null) consumed = Math.max(consumed ?? current, current);
|
|
133
|
+
|
|
134
|
+
let usedPercent = serverPercent;
|
|
135
|
+
if (total !== null && total > 0 && consumed !== null) {
|
|
136
|
+
usedPercent = (consumed / total) * 100;
|
|
137
|
+
}
|
|
138
|
+
const metadata = LIMIT_METADATA[limit.type];
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
id: `zai:${metadata.id}:${index + 1}`,
|
|
142
|
+
label: windowLabel(limit.type, unit, count),
|
|
143
|
+
limitType: limit.type,
|
|
144
|
+
durationMinutes: durationMinutes(unit, count, limit.type),
|
|
145
|
+
usedPercent: clampPercent(usedPercent),
|
|
146
|
+
resetsAt: resetTime(limit.nextResetTime),
|
|
147
|
+
total,
|
|
148
|
+
current,
|
|
149
|
+
remaining,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function resolveZaiApiKey(
|
|
154
|
+
options: ZaiCredentialOptions = {},
|
|
155
|
+
): Promise<string> {
|
|
156
|
+
const env = options.env ?? process.env;
|
|
157
|
+
const environmentKey = nonEmptyText(env.Z_AI_API_KEY);
|
|
158
|
+
if (environmentKey) return environmentKey;
|
|
159
|
+
|
|
160
|
+
const authPath = join(env.HOME || homedir(), ".pi", "agent", "auth.json");
|
|
161
|
+
let parsed: UnknownRecord;
|
|
162
|
+
try {
|
|
163
|
+
const contents = await (options.readFile ?? readFile)(authPath, "utf8");
|
|
164
|
+
const root = record(JSON.parse(contents));
|
|
165
|
+
if (!root) throw new Error("invalid");
|
|
166
|
+
parsed = root;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (record(error)?.code === "ENOENT") {
|
|
169
|
+
throw new Error(MISSING_API_KEY_MESSAGE);
|
|
170
|
+
}
|
|
171
|
+
throw new Error("z.ai Pi auth file is malformed");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const credential = record(parsed.zai);
|
|
175
|
+
if (!credential) {
|
|
176
|
+
throw new Error(MISSING_API_KEY_MESSAGE);
|
|
177
|
+
}
|
|
178
|
+
const key =
|
|
179
|
+
credential.type === "api_key" ? nonEmptyText(credential.key) : null;
|
|
180
|
+
if (!key) throw new Error("z.ai Pi auth file has a malformed zai credential");
|
|
181
|
+
return key;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function parseZaiQuota(raw: unknown, checkedAt: string): CapacityReport {
|
|
185
|
+
const root = record(raw);
|
|
186
|
+
const data = record(root?.data);
|
|
187
|
+
if (
|
|
188
|
+
root?.success !== true ||
|
|
189
|
+
root.code !== 200 ||
|
|
190
|
+
!data ||
|
|
191
|
+
!Array.isArray(data.limits)
|
|
192
|
+
) {
|
|
193
|
+
throw new Error("Invalid z.ai quota response");
|
|
194
|
+
}
|
|
195
|
+
const windows = data.limits
|
|
196
|
+
.map(parseLimit)
|
|
197
|
+
.filter((window): window is CapacityWindow => window !== null);
|
|
198
|
+
return {
|
|
199
|
+
harness: "pi",
|
|
200
|
+
provider: "zai",
|
|
201
|
+
generatedAt: checkedAt,
|
|
202
|
+
authenticated: true,
|
|
203
|
+
available: availability(windows),
|
|
204
|
+
windows,
|
|
205
|
+
creditsRemaining: null,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function probeZaiCapacity(
|
|
210
|
+
options: ZaiProbeOptions,
|
|
211
|
+
): Promise<CapacityReport> {
|
|
212
|
+
const key = await resolveZaiApiKey(options);
|
|
213
|
+
const controller = new AbortController();
|
|
214
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 5000);
|
|
215
|
+
let response: Response;
|
|
216
|
+
try {
|
|
217
|
+
response = await (options.fetch ?? globalThis.fetch)(ZAI_QUOTA_URL, {
|
|
218
|
+
method: "GET",
|
|
219
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
});
|
|
222
|
+
} catch {
|
|
223
|
+
throw new Error("z.ai quota request failed");
|
|
224
|
+
} finally {
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
}
|
|
227
|
+
if (response.status !== 200) {
|
|
228
|
+
throw new Error(`z.ai quota request failed: HTTP ${response.status}`);
|
|
229
|
+
}
|
|
230
|
+
let raw: unknown;
|
|
231
|
+
try {
|
|
232
|
+
raw = await response.json();
|
|
233
|
+
} catch {
|
|
234
|
+
throw new Error("z.ai quota response is not valid JSON");
|
|
235
|
+
}
|
|
236
|
+
return parseZaiQuota(raw, options.checkedAt);
|
|
237
|
+
}
|