@ai-devkit/agent-manager 0.33.1 → 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/__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__/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/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -1
- 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 +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{adapters → providers/opencode}/OpenCodeAdapter.d.ts +5 -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/package.json +1 -1
- 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__/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/adapters/index.ts +1 -1
- 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 +8 -3
- 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/dist/__tests__/adapters/OpenCodeAdapter.test.js +0 -636
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +0 -1
- package/dist/adapters/OpenCodeAdapter.d.ts.map +0 -1
- package/dist/adapters/OpenCodeAdapter.js +0 -289
- package/dist/adapters/OpenCodeAdapter.js.map +0 -1
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +0 -544
- package/src/adapters/OpenCodeAdapter.ts +0 -373
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { OpenCodeSessionParser } from "../../../providers/opencode/OpenCodeSessionParser.js";
|
|
4
|
+
|
|
5
|
+
describe("OpenCodeSessionParser", () => {
|
|
6
|
+
let db: Database.Database;
|
|
7
|
+
let parser: OpenCodeSessionParser;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
db = new Database(":memory:");
|
|
11
|
+
db.exec(`
|
|
12
|
+
CREATE TABLE message (id TEXT, session_id TEXT, data TEXT, time_created INTEGER, time_updated INTEGER);
|
|
13
|
+
CREATE TABLE part (message_id TEXT, session_id TEXT, data TEXT, time_created INTEGER);
|
|
14
|
+
`);
|
|
15
|
+
parser = new OpenCodeSessionParser();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
db.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("extracts session stats from OpenCode messages and parts", () => {
|
|
23
|
+
insertMessage(db, {
|
|
24
|
+
id: "msg-user",
|
|
25
|
+
sessionId: "sess-1",
|
|
26
|
+
role: "user",
|
|
27
|
+
timeCreated: 1000,
|
|
28
|
+
timeUpdated: 1000,
|
|
29
|
+
});
|
|
30
|
+
insertMessage(db, {
|
|
31
|
+
id: "msg-assistant",
|
|
32
|
+
sessionId: "sess-1",
|
|
33
|
+
role: "assistant",
|
|
34
|
+
timeCreated: 2000,
|
|
35
|
+
timeUpdated: 2500,
|
|
36
|
+
completed: 2400,
|
|
37
|
+
});
|
|
38
|
+
insertPart(db, {
|
|
39
|
+
messageId: "msg-user",
|
|
40
|
+
sessionId: "sess-1",
|
|
41
|
+
data: { type: "text", text: "Plan the migration" },
|
|
42
|
+
timeCreated: 1000,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(parser.getSessionStats(db, "sess-1")).toEqual({
|
|
46
|
+
lastRole: "assistant",
|
|
47
|
+
lastTimeUpdated: 2500,
|
|
48
|
+
lastAssistantCompleted: true,
|
|
49
|
+
lastAssistantErrored: false,
|
|
50
|
+
summary: "Plan the migration",
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("orders stats by message creation time, not metadata update time", () => {
|
|
55
|
+
insertMessage(db, {
|
|
56
|
+
id: "msg-user",
|
|
57
|
+
sessionId: "sess-1",
|
|
58
|
+
role: "user",
|
|
59
|
+
timeCreated: 1000,
|
|
60
|
+
timeUpdated: 3000,
|
|
61
|
+
});
|
|
62
|
+
insertMessage(db, {
|
|
63
|
+
id: "msg-assistant",
|
|
64
|
+
sessionId: "sess-1",
|
|
65
|
+
role: "assistant",
|
|
66
|
+
timeCreated: 2000,
|
|
67
|
+
timeUpdated: 2100,
|
|
68
|
+
completed: 2200,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(parser.getSessionStats(db, "sess-1").lastRole).toBe("assistant");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("reads visible conversation text and hides verbose-only parts by default", () => {
|
|
75
|
+
insertMessage(db, {
|
|
76
|
+
id: "msg-user",
|
|
77
|
+
sessionId: "sess-1",
|
|
78
|
+
role: "user",
|
|
79
|
+
timeCreated: 1000,
|
|
80
|
+
timeUpdated: 1000,
|
|
81
|
+
});
|
|
82
|
+
insertMessage(db, {
|
|
83
|
+
id: "msg-assistant",
|
|
84
|
+
sessionId: "sess-1",
|
|
85
|
+
role: "assistant",
|
|
86
|
+
timeCreated: 2000,
|
|
87
|
+
timeUpdated: 2000,
|
|
88
|
+
});
|
|
89
|
+
insertPart(db, {
|
|
90
|
+
messageId: "msg-user",
|
|
91
|
+
sessionId: "sess-1",
|
|
92
|
+
data: { type: "text", text: "Hello" },
|
|
93
|
+
timeCreated: 1000,
|
|
94
|
+
});
|
|
95
|
+
insertPart(db, {
|
|
96
|
+
messageId: "msg-assistant",
|
|
97
|
+
sessionId: "sess-1",
|
|
98
|
+
data: { type: "reasoning", reasoning: "private thought" },
|
|
99
|
+
timeCreated: 2000,
|
|
100
|
+
});
|
|
101
|
+
insertPart(db, {
|
|
102
|
+
messageId: "msg-assistant",
|
|
103
|
+
sessionId: "sess-1",
|
|
104
|
+
data: { type: "tool", tool: "read_file" },
|
|
105
|
+
timeCreated: 3000,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(parser.getConversation(db, "sess-1")).toEqual([{ role: "user", content: "Hello" }]);
|
|
109
|
+
expect(parser.getConversation(db, "sess-1", { verbose: true })).toEqual([
|
|
110
|
+
{ role: "user", content: "Hello" },
|
|
111
|
+
{ role: "assistant", content: "[thinking] private thought" },
|
|
112
|
+
{ role: "assistant", content: "[tool: read_file]" },
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
function insertMessage(
|
|
118
|
+
db: Database.Database,
|
|
119
|
+
input: {
|
|
120
|
+
id: string;
|
|
121
|
+
sessionId: string;
|
|
122
|
+
role: string;
|
|
123
|
+
timeCreated: number;
|
|
124
|
+
timeUpdated: number;
|
|
125
|
+
completed?: number;
|
|
126
|
+
},
|
|
127
|
+
): void {
|
|
128
|
+
db.prepare(
|
|
129
|
+
"INSERT INTO message (id, session_id, data, time_created, time_updated) VALUES (?, ?, ?, ?, ?)",
|
|
130
|
+
).run(
|
|
131
|
+
input.id,
|
|
132
|
+
input.sessionId,
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
role: input.role,
|
|
135
|
+
time: input.completed === undefined ? {} : { completed: input.completed },
|
|
136
|
+
}),
|
|
137
|
+
input.timeCreated,
|
|
138
|
+
input.timeUpdated,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function insertPart(
|
|
143
|
+
db: Database.Database,
|
|
144
|
+
input: { messageId: string; sessionId: string; data: unknown; timeCreated: number },
|
|
145
|
+
): void {
|
|
146
|
+
db.prepare("INSERT INTO part (message_id, session_id, data, time_created) VALUES (?, ?, ?, ?)")
|
|
147
|
+
.run(input.messageId, input.sessionId, JSON.stringify(input.data), input.timeCreated);
|
|
148
|
+
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { CodexAdapter } from "../providers/codex/CodexAdapter.js";
|
|
|
3
3
|
export { CopilotAdapter } from "../providers/copilot/CopilotAdapter.js";
|
|
4
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;
|