@ai-devkit/agent-manager 0.34.0 → 0.36.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 +2 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +25 -0
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +74 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/adapters/AntigravityCliAdapter.test.js +381 -0
- package/dist/__tests__/adapters/AntigravityCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +28 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/CodexAdapter.test.js +7 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/KiroAdapter.test.js +607 -0
- package/dist/__tests__/adapters/KiroAdapter.test.js.map +1 -0
- package/dist/__tests__/capacity/claude.test.js +485 -0
- package/dist/__tests__/capacity/claude.test.js.map +1 -0
- package/dist/__tests__/capacity/openai.test.js +672 -0
- package/dist/__tests__/capacity/openai.test.js.map +1 -0
- package/dist/__tests__/capacity/wham.test.js +144 -0
- package/dist/__tests__/capacity/wham.test.js.map +1 -0
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +18 -0
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
- package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
- package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
- package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
- package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +24 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
- package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -1
- package/dist/__tests__/utils/agents.test.js +6 -0
- package/dist/__tests__/utils/agents.test.js.map +1 -1
- package/dist/__tests__/utils/session.test.js +18 -1
- package/dist/__tests__/utils/session.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +9 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/AntigravityCliAdapter.d.ts +57 -0
- package/dist/adapters/AntigravityCliAdapter.d.ts.map +1 -0
- package/dist/adapters/AntigravityCliAdapter.js +273 -0
- package/dist/adapters/AntigravityCliAdapter.js.map +1 -0
- package/dist/adapters/KiroAdapter.d.ts +46 -0
- package/dist/adapters/KiroAdapter.d.ts.map +1 -0
- package/dist/adapters/KiroAdapter.js +344 -0
- package/dist/adapters/KiroAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +3 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/capacity/claude.d.ts +17 -0
- package/dist/capacity/claude.d.ts.map +1 -0
- package/dist/capacity/claude.js +271 -0
- package/dist/capacity/claude.js.map +1 -0
- package/dist/capacity/codex.d.ts +1 -2
- package/dist/capacity/codex.d.ts.map +1 -1
- package/dist/capacity/codex.js +5 -47
- package/dist/capacity/codex.js.map +1 -1
- package/dist/capacity/index.d.ts +11 -0
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +17 -0
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/openai.d.ts +48 -0
- package/dist/capacity/openai.d.ts.map +1 -0
- package/dist/capacity/openai.js +312 -0
- package/dist/capacity/openai.js.map +1 -0
- package/dist/capacity/wham.d.ts +16 -0
- package/dist/capacity/wham.d.ts.map +1 -0
- package/dist/capacity/wham.js +66 -0
- package/dist/capacity/wham.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
- package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
- package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
- package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
- package/dist/providers/codex/CodexAdapter.d.ts +1 -0
- package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
- package/dist/providers/codex/CodexAdapter.js +8 -0
- package/dist/providers/codex/CodexAdapter.js.map +1 -1
- package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
- package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
- package/dist/providers/copilot/CopilotAdapter.js +24 -9
- package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
- package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
- package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
- package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
- package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
- package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
- package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
- package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
- package/dist/providers/grok/GrokAgentMapper.js +36 -0
- package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
- package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
- package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
- package/dist/providers/grok/GrokCliAdapter.js +98 -0
- package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
- package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
- package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
- package/dist/providers/grok/GrokSessionLocator.js +140 -0
- package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
- package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
- package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
- package/dist/providers/grok/GrokSessionParser.js +122 -0
- package/dist/providers/grok/GrokSessionParser.js.map +1 -0
- package/dist/providers/opencode/OpenCodeAdapter.d.ts +1 -0
- package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -1
- package/dist/providers/opencode/OpenCodeAdapter.js +3 -0
- package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -1
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +2 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -1
- package/dist/providers/opencode/OpenCodeSessionLocator.js +31 -12
- package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -1
- package/dist/providers/pi/PiAdapter.d.ts +1 -0
- package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
- package/dist/providers/pi/PiAdapter.js +10 -0
- package/dist/providers/pi/PiAdapter.js.map +1 -1
- package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
- package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
- package/dist/providers/pi/PiSessionLocator.js +8 -1
- package/dist/providers/pi/PiSessionLocator.js.map +1 -1
- package/dist/readiness/AgentReadiness.d.ts +1 -1
- package/dist/readiness/AgentReadiness.d.ts.map +1 -1
- package/dist/readiness/AgentReadiness.js.map +1 -1
- package/dist/runtime/ManagedAgentRuntime.js +3 -1
- package/dist/runtime/ManagedAgentRuntime.js.map +1 -1
- package/dist/runtime/herdr/HerdrAgentDiscovery.js +10 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -1
- package/dist/utils/agents.d.ts +1 -1
- package/dist/utils/agents.d.ts.map +1 -1
- package/dist/utils/agents.js +11 -0
- package/dist/utils/agents.js.map +1 -1
- package/dist/utils/session.d.ts +2 -0
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +3 -0
- package/dist/utils/session.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +34 -0
- package/src/__tests__/AgentManager.test.ts +60 -0
- package/src/__tests__/adapters/AntigravityCliAdapter.test.ts +324 -0
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
- package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
- package/src/__tests__/adapters/KiroAdapter.test.ts +534 -0
- package/src/__tests__/capacity/claude.test.ts +467 -0
- package/src/__tests__/capacity/fixtures/claude-credentials.json +7 -0
- package/src/__tests__/capacity/fixtures/claude-usage-malformed.json +1 -0
- package/src/__tests__/capacity/fixtures/claude-usage-partial.json +15 -0
- package/src/__tests__/capacity/fixtures/claude-usage.json +63 -0
- package/src/__tests__/capacity/fixtures/openai-auth.json +6 -0
- package/src/__tests__/capacity/fixtures/openai-codex-auth.json +9 -0
- package/src/__tests__/capacity/fixtures/openai-usage-completions.json +67 -0
- package/src/__tests__/capacity/fixtures/openai-usage-responses.json +20 -0
- package/src/__tests__/capacity/fixtures/wham-usage.json +32 -0
- package/src/__tests__/capacity/openai.test.ts +570 -0
- package/src/__tests__/capacity/wham.test.ts +104 -0
- package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
- package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
- package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
- package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
- package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
- package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +18 -0
- package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
- package/src/__tests__/utils/agents.test.ts +7 -0
- package/src/__tests__/utils/session.test.ts +11 -1
- package/src/adapters/AgentAdapter.ts +11 -0
- package/src/adapters/AntigravityCliAdapter.ts +356 -0
- package/src/adapters/KiroAdapter.ts +456 -0
- package/src/adapters/index.ts +3 -1
- package/src/capacity/claude.ts +313 -0
- package/src/capacity/codex.ts +25 -134
- package/src/capacity/index.ts +33 -4
- package/src/capacity/openai.ts +361 -0
- package/src/capacity/wham.ts +84 -0
- package/src/index.ts +11 -2
- package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
- package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
- package/src/providers/codex/CodexAdapter.ts +8 -0
- package/src/providers/copilot/CopilotAdapter.ts +26 -10
- package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
- package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
- package/src/providers/grok/GrokAgentMapper.ts +41 -0
- package/src/providers/grok/GrokCliAdapter.ts +125 -0
- package/src/providers/grok/GrokSessionLocator.ts +179 -0
- package/src/providers/grok/GrokSessionParser.ts +166 -0
- package/src/providers/opencode/OpenCodeAdapter.ts +4 -0
- package/src/providers/opencode/OpenCodeSessionLocator.ts +43 -15
- package/src/providers/pi/PiAdapter.ts +12 -0
- package/src/providers/pi/PiSessionLocator.ts +16 -1
- package/src/readiness/AgentReadiness.ts +4 -1
- package/src/runtime/ManagedAgentRuntime.ts +2 -0
- package/src/runtime/herdr/HerdrAgentDiscovery.ts +5 -1
- package/src/utils/agents.ts +11 -1
- package/src/utils/session.ts +11 -0
- package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
- package/dist/adapters/GrokCliAdapter.d.ts +0 -80
- package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
- package/dist/adapters/GrokCliAdapter.js +0 -310
- package/dist/adapters/GrokCliAdapter.js.map +0 -1
- package/src/adapters/GrokCliAdapter.ts +0 -408
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import type { Availability, CapacityReport, CapacityWindow } from "./types.js";
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
export type ClaudeCapacityOptions = {
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
now?: () => Date;
|
|
16
|
+
cwd?: string;
|
|
17
|
+
platform?: NodeJS.Platform;
|
|
18
|
+
keychainRead?: (service: string) => Promise<string | null>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ClaudeProbeOptions = ClaudeCapacityOptions & {
|
|
22
|
+
checkedAt: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
26
|
+
const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
27
|
+
const SESSION_DURATION_MINUTES = 5 * 60;
|
|
28
|
+
const WEEK_DURATION_MINUTES = 7 * 24 * 60;
|
|
29
|
+
|
|
30
|
+
type Credential = { token: string | null; expired: boolean };
|
|
31
|
+
|
|
32
|
+
function record(value: unknown): Record<string, unknown> | null {
|
|
33
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
34
|
+
? (value as Record<string, unknown>)
|
|
35
|
+
: null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function percent(value: unknown): number | null {
|
|
39
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 100
|
|
40
|
+
? value
|
|
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 credentialsPath(options: ClaudeProbeOptions): string {
|
|
49
|
+
const env = options.env ?? process.env;
|
|
50
|
+
const configured = nonEmptyText(env.CLAUDE_CONFIG_DIR);
|
|
51
|
+
if (configured) {
|
|
52
|
+
const profileRoot = isAbsolute(configured)
|
|
53
|
+
? configured
|
|
54
|
+
: resolve(options.cwd ?? process.cwd(), configured);
|
|
55
|
+
return join(profileRoot, ".credentials.json");
|
|
56
|
+
}
|
|
57
|
+
return join(env.HOME || homedir(), ".claude", ".credentials.json");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function credentialFromPayload(payload: string | null, now: Date): Credential {
|
|
61
|
+
if (!payload) return { token: null, expired: false };
|
|
62
|
+
try {
|
|
63
|
+
return credentialFromRoot(record(JSON.parse(payload)), now);
|
|
64
|
+
} catch {
|
|
65
|
+
return { token: null, expired: false };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function readProfileCredential(options: ClaudeProbeOptions, now: Date): Promise<Credential> {
|
|
70
|
+
try {
|
|
71
|
+
const payload = await (options.readFile ?? readFile)(credentialsPath(options), "utf8");
|
|
72
|
+
return credentialFromPayload(payload, now);
|
|
73
|
+
} catch {
|
|
74
|
+
return { token: null, expired: false };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function readKeychainCredential(options: ClaudeProbeOptions, now: Date): Promise<Credential> {
|
|
79
|
+
const env = options.env ?? process.env;
|
|
80
|
+
const usesDefaultProfile = !nonEmptyText(env.CLAUDE_CONFIG_DIR);
|
|
81
|
+
if (!usesDefaultProfile || (options.platform ?? process.platform) !== "darwin") {
|
|
82
|
+
return { token: null, expired: false };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const payload = await (options.keychainRead ?? readClaudeKeychain)(CLAUDE_KEYCHAIN_SERVICE);
|
|
86
|
+
return credentialFromPayload(payload, now);
|
|
87
|
+
} catch {
|
|
88
|
+
return { token: null, expired: false };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function resolveToken(options: ClaudeProbeOptions): Promise<string> {
|
|
93
|
+
const env = options.env ?? process.env;
|
|
94
|
+
const environmentToken = nonEmptyText(env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
95
|
+
if (environmentToken) return environmentToken;
|
|
96
|
+
|
|
97
|
+
const now = options.now?.() ?? new Date();
|
|
98
|
+
const profileCredential = await readProfileCredential(options, now);
|
|
99
|
+
if (profileCredential.token) return profileCredential.token;
|
|
100
|
+
|
|
101
|
+
const keychainCredential = await readKeychainCredential(options, now);
|
|
102
|
+
if (keychainCredential.token) return keychainCredential.token;
|
|
103
|
+
if (profileCredential.expired || keychainCredential.expired) {
|
|
104
|
+
throw new Error("Claude OAuth credentials expired");
|
|
105
|
+
}
|
|
106
|
+
throw new Error("Claude OAuth credentials not found or malformed");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function credentialFromRoot(root: Record<string, unknown> | null, now: Date): Credential {
|
|
110
|
+
const oauth = record(root?.claudeAiOauth);
|
|
111
|
+
const token = nonEmptyText(oauth?.accessToken);
|
|
112
|
+
if (!token) return { token: null, expired: false };
|
|
113
|
+
const expired =
|
|
114
|
+
typeof oauth?.expiresAt === "number" &&
|
|
115
|
+
Number.isFinite(oauth.expiresAt) &&
|
|
116
|
+
oauth.expiresAt <= now.getTime();
|
|
117
|
+
return { token: expired ? null : token, expired };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function readClaudeKeychain(service: string): Promise<string | null> {
|
|
121
|
+
try {
|
|
122
|
+
const { stdout } = await execFileAsync(
|
|
123
|
+
"/usr/bin/security",
|
|
124
|
+
["find-generic-password", "-s", service, "-w"],
|
|
125
|
+
{ encoding: "utf8", timeout: 1500, maxBuffer: 1024 * 1024 },
|
|
126
|
+
);
|
|
127
|
+
return nonEmptyText(stdout);
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function resetTime(value: unknown): string | null {
|
|
134
|
+
if (typeof value !== "string") return null;
|
|
135
|
+
const milliseconds = Date.parse(value);
|
|
136
|
+
return Number.isNaN(milliseconds) ? null : new Date(milliseconds).toISOString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function usageWindow(
|
|
140
|
+
value: unknown,
|
|
141
|
+
id: string,
|
|
142
|
+
label: string,
|
|
143
|
+
durationMinutes: number,
|
|
144
|
+
): CapacityWindow | null {
|
|
145
|
+
const input = record(value);
|
|
146
|
+
if (!input) return null;
|
|
147
|
+
return {
|
|
148
|
+
id,
|
|
149
|
+
label,
|
|
150
|
+
durationMinutes,
|
|
151
|
+
usedPercent: percent(input.utilization),
|
|
152
|
+
resetsAt: resetTime(input.resets_at),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function slug(value: string): string {
|
|
157
|
+
return value
|
|
158
|
+
.toLowerCase()
|
|
159
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
160
|
+
.replace(/^-|-$/g, "");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function scopedWindows(value: unknown): CapacityWindow[] {
|
|
164
|
+
if (!Array.isArray(value)) return [];
|
|
165
|
+
const seen = new Set<string>();
|
|
166
|
+
const windows: CapacityWindow[] = [];
|
|
167
|
+
for (const entry of value) {
|
|
168
|
+
const limit = record(entry);
|
|
169
|
+
const model = record(record(limit?.scope)?.model);
|
|
170
|
+
const name = nonEmptyText(model?.display_name);
|
|
171
|
+
const identity = nonEmptyText(model?.id) ?? name;
|
|
172
|
+
if (limit?.kind !== "weekly_scoped" || limit.group !== "weekly" || !name || !identity) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const modelSlug = slug(identity);
|
|
176
|
+
if (!modelSlug || modelSlug === "all-models" || modelSlug.endsWith("-all-models")) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const id = `claude:weekly:${modelSlug}`;
|
|
180
|
+
if (seen.has(id)) continue;
|
|
181
|
+
seen.add(id);
|
|
182
|
+
windows.push({
|
|
183
|
+
id,
|
|
184
|
+
label: `${name} weekly`,
|
|
185
|
+
durationMinutes: WEEK_DURATION_MINUTES,
|
|
186
|
+
usedPercent: percent(limit.percent),
|
|
187
|
+
resetsAt: resetTime(limit.resets_at),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return windows;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function finiteNumber(value: unknown): number | null {
|
|
194
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function extraUsageWindow(value: unknown): CapacityWindow | null {
|
|
198
|
+
const extra = record(value);
|
|
199
|
+
if (!extra || extra.is_enabled !== true) return null;
|
|
200
|
+
const limitMinor = finiteNumber(extra.monthly_limit);
|
|
201
|
+
const usedMinor = finiteNumber(extra.used_credits);
|
|
202
|
+
if (limitMinor === null || usedMinor === null) return null;
|
|
203
|
+
const total = limitMinor / 100;
|
|
204
|
+
const current = usedMinor / 100;
|
|
205
|
+
const currency = nonEmptyText(extra.currency)?.toUpperCase() ?? "USD";
|
|
206
|
+
return {
|
|
207
|
+
id: "claude:extra-usage",
|
|
208
|
+
label: `Extra usage · ${currency}`,
|
|
209
|
+
limitType: "CREDIT_LIMIT",
|
|
210
|
+
durationMinutes: null,
|
|
211
|
+
usedPercent:
|
|
212
|
+
percent(extra.utilization) ??
|
|
213
|
+
(total > 0 ? Math.max(0, Math.min(100, (current / total) * 100)) : null),
|
|
214
|
+
resetsAt: null,
|
|
215
|
+
total,
|
|
216
|
+
current,
|
|
217
|
+
remaining: Math.max(0, total - current),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function availability(windows: CapacityWindow[]): Availability {
|
|
222
|
+
const known = windows.flatMap((window) =>
|
|
223
|
+
window.usedPercent === null ? [] : [window.usedPercent],
|
|
224
|
+
);
|
|
225
|
+
if (known.length === 0) return "unknown";
|
|
226
|
+
return known.some((used) => used < 100) ? "yes" : "no";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function retryAfterTime(response: Response, now: Date): string | null {
|
|
230
|
+
const raw = response.headers.get("Retry-After")?.trim();
|
|
231
|
+
if (!raw) return null;
|
|
232
|
+
const seconds = Number(raw);
|
|
233
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
234
|
+
return new Date(now.getTime() + seconds * 1000).toISOString();
|
|
235
|
+
}
|
|
236
|
+
const milliseconds = Date.parse(raw);
|
|
237
|
+
return Number.isNaN(milliseconds) ? null : new Date(milliseconds).toISOString();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function parseClaudeUsage(raw: unknown, checkedAt: string): CapacityReport {
|
|
241
|
+
const input = record(raw);
|
|
242
|
+
if (!input) throw new Error("Claude usage response is malformed");
|
|
243
|
+
const windows = [
|
|
244
|
+
usageWindow(input.five_hour, "session", "Session", SESSION_DURATION_MINUTES),
|
|
245
|
+
usageWindow(input.seven_day, "weekly", "Weekly", WEEK_DURATION_MINUTES),
|
|
246
|
+
usageWindow(
|
|
247
|
+
input.seven_day_sonnet,
|
|
248
|
+
"claude:sonnet:weekly",
|
|
249
|
+
"Sonnet weekly",
|
|
250
|
+
WEEK_DURATION_MINUTES,
|
|
251
|
+
),
|
|
252
|
+
usageWindow(input.seven_day_opus, "claude:opus:weekly", "Opus weekly", WEEK_DURATION_MINUTES),
|
|
253
|
+
...scopedWindows(input.limits),
|
|
254
|
+
extraUsageWindow(input.extra_usage),
|
|
255
|
+
].filter((window): window is CapacityWindow => window !== null);
|
|
256
|
+
return {
|
|
257
|
+
harness: "claude",
|
|
258
|
+
provider: "anthropic",
|
|
259
|
+
generatedAt: checkedAt,
|
|
260
|
+
authenticated: true,
|
|
261
|
+
available: availability(windows),
|
|
262
|
+
windows,
|
|
263
|
+
creditsRemaining: null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function probeClaudeCapacity(options: ClaudeProbeOptions): Promise<CapacityReport> {
|
|
268
|
+
const token = await resolveToken(options);
|
|
269
|
+
const controller = new AbortController();
|
|
270
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 5000);
|
|
271
|
+
let response: Response;
|
|
272
|
+
try {
|
|
273
|
+
response = await (options.fetch ?? globalThis.fetch)(CLAUDE_USAGE_URL, {
|
|
274
|
+
method: "GET",
|
|
275
|
+
headers: {
|
|
276
|
+
Accept: "application/json",
|
|
277
|
+
Authorization: `Bearer ${token}`,
|
|
278
|
+
"Content-Type": "application/json",
|
|
279
|
+
"User-Agent": "claude-code/2.1.0",
|
|
280
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
281
|
+
},
|
|
282
|
+
signal: controller.signal,
|
|
283
|
+
});
|
|
284
|
+
} catch {
|
|
285
|
+
throw new Error("Claude OAuth usage request failed");
|
|
286
|
+
} finally {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
}
|
|
289
|
+
if (response.status === 401) {
|
|
290
|
+
throw new Error("Claude OAuth request unauthorized (HTTP 401)");
|
|
291
|
+
}
|
|
292
|
+
if (response.status === 403) {
|
|
293
|
+
throw new Error("Claude OAuth request forbidden (HTTP 403)");
|
|
294
|
+
}
|
|
295
|
+
if (response.status === 429) {
|
|
296
|
+
const retryAt = retryAfterTime(response, options.now?.() ?? new Date());
|
|
297
|
+
throw new Error(
|
|
298
|
+
retryAt
|
|
299
|
+
? `Claude OAuth usage rate limited until ${retryAt}`
|
|
300
|
+
: "Claude OAuth usage rate limited (HTTP 429)",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
if (!response.ok) {
|
|
304
|
+
throw new Error(`Claude OAuth usage request failed (HTTP ${response.status})`);
|
|
305
|
+
}
|
|
306
|
+
let raw: unknown;
|
|
307
|
+
try {
|
|
308
|
+
raw = await response.json();
|
|
309
|
+
} catch {
|
|
310
|
+
throw new Error("Claude usage response is malformed");
|
|
311
|
+
}
|
|
312
|
+
return parseClaudeUsage(raw, options.checkedAt);
|
|
313
|
+
}
|
package/src/capacity/codex.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { parseWhamUsage, resetTime, safeIdentifier } from "./wham.js";
|
|
4
5
|
import type { CapacityReport, CapacityWindow } from "./types.js";
|
|
5
6
|
|
|
7
|
+
export { toRateWindow } from "./wham.js";
|
|
8
|
+
|
|
6
9
|
type CodexUsageSource = "pat" | "oauth" | "cli";
|
|
7
10
|
type UsageSnapshot = {
|
|
8
11
|
windows: CapacityWindow[];
|
|
@@ -15,13 +18,7 @@ type RpcMessage = { id?: number; method: string; params?: UnknownRecord };
|
|
|
15
18
|
type CliResponses = { rateLimits: unknown; account: unknown };
|
|
16
19
|
type CodexRpc = (messages: RpcMessage[]) => Promise<CliResponses>;
|
|
17
20
|
|
|
18
|
-
export const CODEX_APP_SERVER_ARGS = [
|
|
19
|
-
"-s",
|
|
20
|
-
"read-only",
|
|
21
|
-
"-a",
|
|
22
|
-
"untrusted",
|
|
23
|
-
"app-server",
|
|
24
|
-
] as const;
|
|
21
|
+
export const CODEX_APP_SERVER_ARGS = ["-s", "read-only", "-a", "untrusted", "app-server"] as const;
|
|
25
22
|
|
|
26
23
|
type CodexProbeOptions = {
|
|
27
24
|
installed: boolean;
|
|
@@ -48,91 +45,17 @@ function nonEmptyText(value: unknown): string | null {
|
|
|
48
45
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
function
|
|
52
|
-
const seconds = finiteNumber(value);
|
|
53
|
-
if (seconds !== null) return new Date(seconds * 1000).toISOString();
|
|
54
|
-
if (typeof value === "string" && !Number.isNaN(Date.parse(value)))
|
|
55
|
-
return new Date(value).toISOString();
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function safeIdentifier(value: unknown): string | null {
|
|
60
|
-
const candidate = nonEmptyText(value);
|
|
61
|
-
if (!candidate || !/^[a-z][a-z0-9_-]{0,63}$/i.test(candidate)) return null;
|
|
62
|
-
if (/(?:account|token|secret|key)[_-]?\d{6,}/i.test(candidate)) return null;
|
|
63
|
-
return candidate;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function resolveCodexAuthPath(
|
|
67
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
68
|
-
): string {
|
|
48
|
+
export function resolveCodexAuthPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
69
49
|
const root = env.CODEX_HOME || join(env.HOME || "", ".codex");
|
|
70
50
|
return join(root, "auth.json");
|
|
71
51
|
}
|
|
72
52
|
|
|
73
|
-
export function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
label: string,
|
|
77
|
-
): CapacityWindow | null {
|
|
78
|
-
const input = record(value);
|
|
79
|
-
if (!input) return null;
|
|
80
|
-
const used = finiteNumber(input.used_percent);
|
|
81
|
-
const seconds = finiteNumber(input.limit_window_seconds);
|
|
82
|
-
return {
|
|
83
|
-
id,
|
|
84
|
-
label,
|
|
85
|
-
durationMinutes: seconds === null ? null : seconds / 60,
|
|
86
|
-
usedPercent: used,
|
|
87
|
-
resetsAt: resetTime(input.reset_at),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function extraWindows(value: unknown): CapacityWindow[] {
|
|
92
|
-
if (!Array.isArray(value)) return [];
|
|
93
|
-
return value.flatMap((entry, index) => {
|
|
94
|
-
const limit = record(entry);
|
|
95
|
-
if (!limit) return [];
|
|
96
|
-
const scope = safeIdentifier(limit.limit_name) ?? `extra-${index + 1}`;
|
|
97
|
-
const windows = record(limit.rate_limit) ?? limit;
|
|
98
|
-
return [
|
|
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
|
-
),
|
|
109
|
-
].filter((window): window is CapacityWindow => window !== null);
|
|
110
|
-
});
|
|
53
|
+
export function parseUsage(raw: unknown, source: "pat" | "oauth"): UsageSnapshot {
|
|
54
|
+
const { windows, creditsRemaining } = parseWhamUsage(raw);
|
|
55
|
+
return { windows, creditsRemaining, source };
|
|
111
56
|
}
|
|
112
57
|
|
|
113
|
-
|
|
114
|
-
raw: unknown,
|
|
115
|
-
source: "pat" | "oauth",
|
|
116
|
-
): UsageSnapshot {
|
|
117
|
-
const response = record(raw) ?? {};
|
|
118
|
-
const limits = record(response.rate_limit) ?? {};
|
|
119
|
-
const credits = record(response.credits) ?? {};
|
|
120
|
-
return {
|
|
121
|
-
windows: [
|
|
122
|
-
toRateWindow(limits.primary_window, "session", "Session"),
|
|
123
|
-
toRateWindow(limits.secondary_window, "weekly", "Weekly"),
|
|
124
|
-
...extraWindows(response.additional_rate_limits),
|
|
125
|
-
].filter((window): window is CapacityWindow => window !== null),
|
|
126
|
-
creditsRemaining: finiteNumber(credits.balance),
|
|
127
|
-
source,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function cliWindow(
|
|
132
|
-
value: unknown,
|
|
133
|
-
id: string,
|
|
134
|
-
label: string,
|
|
135
|
-
): CapacityWindow | null {
|
|
58
|
+
function cliWindow(value: unknown, id: string, label: string): CapacityWindow | null {
|
|
136
59
|
const input = record(value);
|
|
137
60
|
if (!input) return null;
|
|
138
61
|
return {
|
|
@@ -144,14 +67,10 @@ function cliWindow(
|
|
|
144
67
|
};
|
|
145
68
|
}
|
|
146
69
|
|
|
147
|
-
function cliSnapshotWindows(
|
|
148
|
-
value: unknown,
|
|
149
|
-
fallbackId: string,
|
|
150
|
-
): CapacityWindow[] {
|
|
70
|
+
function cliSnapshotWindows(value: unknown, fallbackId: string): CapacityWindow[] {
|
|
151
71
|
const snapshot = record(value);
|
|
152
72
|
if (!snapshot) return [];
|
|
153
|
-
const scope =
|
|
154
|
-
safeIdentifier(snapshot.limitId) ?? safeIdentifier(fallbackId) ?? "codex";
|
|
73
|
+
const scope = safeIdentifier(snapshot.limitId) ?? safeIdentifier(fallbackId) ?? "codex";
|
|
155
74
|
return [
|
|
156
75
|
cliWindow(snapshot.primary, `${scope}:primary`, `${scope} primary`),
|
|
157
76
|
cliWindow(snapshot.secondary, `${scope}:secondary`, `${scope} secondary`),
|
|
@@ -167,9 +86,7 @@ export function parseCliUsage(raw: unknown): UsageSnapshot {
|
|
|
167
86
|
for (const [id, snapshot] of Object.entries(buckets))
|
|
168
87
|
windows.push(...cliSnapshotWindows(snapshot, id));
|
|
169
88
|
}
|
|
170
|
-
const unique = [
|
|
171
|
-
...new Map(windows.map((window) => [window.id, window])).values(),
|
|
172
|
-
];
|
|
89
|
+
const unique = [...new Map(windows.map((window) => [window.id, window])).values()];
|
|
173
90
|
return { windows: unique, creditsRemaining: null, source: "cli" };
|
|
174
91
|
}
|
|
175
92
|
|
|
@@ -178,14 +95,11 @@ function capacityFromSnapshot(
|
|
|
178
95
|
context: CodexProbeOptions,
|
|
179
96
|
raw?: unknown,
|
|
180
97
|
): CapacityReport {
|
|
181
|
-
const hasUsage = snapshot.windows.some(
|
|
182
|
-
(window) => window.usedPercent !== null,
|
|
183
|
-
);
|
|
98
|
+
const hasUsage = snapshot.windows.some((window) => window.usedPercent !== null);
|
|
184
99
|
const rateLimits = record(record(raw)?.rateLimits);
|
|
185
100
|
const reached = nonEmptyText(rateLimits?.rateLimitReachedType);
|
|
186
101
|
const resetCredits =
|
|
187
|
-
record(record(raw)?.rateLimitResetCredits) ??
|
|
188
|
-
record(record(raw)?.usageLimitResetCredits);
|
|
102
|
+
record(record(raw)?.rateLimitResetCredits) ?? record(record(raw)?.usageLimitResetCredits);
|
|
189
103
|
return {
|
|
190
104
|
harness: "codex",
|
|
191
105
|
provider: "openai",
|
|
@@ -193,8 +107,7 @@ function capacityFromSnapshot(
|
|
|
193
107
|
authenticated: true,
|
|
194
108
|
available: reached ? "no" : hasUsage ? "yes" : "unknown",
|
|
195
109
|
windows: snapshot.windows,
|
|
196
|
-
creditsRemaining:
|
|
197
|
-
snapshot.creditsRemaining ?? finiteNumber(resetCredits?.availableCount),
|
|
110
|
+
creditsRemaining: snapshot.creditsRemaining ?? finiteNumber(resetCredits?.availableCount),
|
|
198
111
|
};
|
|
199
112
|
}
|
|
200
113
|
|
|
@@ -202,9 +115,7 @@ function jwtExpiry(token: string): number | null {
|
|
|
202
115
|
const part = token.split(".")[1];
|
|
203
116
|
if (!part) return null;
|
|
204
117
|
try {
|
|
205
|
-
return finiteNumber(
|
|
206
|
-
record(JSON.parse(Buffer.from(part, "base64url").toString("utf8")))?.exp,
|
|
207
|
-
);
|
|
118
|
+
return finiteNumber(record(JSON.parse(Buffer.from(part, "base64url").toString("utf8")))?.exp);
|
|
208
119
|
} catch {
|
|
209
120
|
return null;
|
|
210
121
|
}
|
|
@@ -231,10 +142,7 @@ async function fetchJson(
|
|
|
231
142
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
232
143
|
try {
|
|
233
144
|
const response = await fetcher(url, { ...init, signal: controller.signal });
|
|
234
|
-
if (!response.ok)
|
|
235
|
-
throw new Error(
|
|
236
|
-
response.status === 401 ? "unauthorized" : "request failed",
|
|
237
|
-
);
|
|
145
|
+
if (!response.ok) throw new Error(response.status === 401 ? "unauthorized" : "request failed");
|
|
238
146
|
return await response.json();
|
|
239
147
|
} finally {
|
|
240
148
|
clearTimeout(timer);
|
|
@@ -262,10 +170,7 @@ async function apiSnapshot(
|
|
|
262
170
|
return parseUsage(raw, source);
|
|
263
171
|
}
|
|
264
172
|
|
|
265
|
-
function appServerRpc(
|
|
266
|
-
messages: RpcMessage[],
|
|
267
|
-
timeoutMs = 5000,
|
|
268
|
-
): Promise<CliResponses> {
|
|
173
|
+
function appServerRpc(messages: RpcMessage[], timeoutMs = 5000): Promise<CliResponses> {
|
|
269
174
|
return new Promise((resolve, reject) => {
|
|
270
175
|
const child = spawn("codex", CODEX_APP_SERVER_ARGS, {
|
|
271
176
|
stdio: ["pipe", "pipe", "ignore"],
|
|
@@ -281,13 +186,8 @@ function appServerRpc(
|
|
|
281
186
|
if (error) reject(error);
|
|
282
187
|
else resolve(results as CliResponses);
|
|
283
188
|
};
|
|
284
|
-
const timer = setTimeout(
|
|
285
|
-
|
|
286
|
-
timeoutMs,
|
|
287
|
-
);
|
|
288
|
-
child.once("error", () =>
|
|
289
|
-
finish(new Error("codex app-server unavailable")),
|
|
290
|
-
);
|
|
189
|
+
const timer = setTimeout(() => finish(new Error("codex probe timed out")), timeoutMs);
|
|
190
|
+
child.once("error", () => finish(new Error("codex app-server unavailable")));
|
|
291
191
|
child.once("exit", () => {
|
|
292
192
|
if (!settled) finish(new Error("codex app-server exited"));
|
|
293
193
|
});
|
|
@@ -310,8 +210,7 @@ function appServerRpc(
|
|
|
310
210
|
for (const request of messages.slice(1))
|
|
311
211
|
child.stdin.write(`${JSON.stringify(request)}\n`);
|
|
312
212
|
} else if (message.id === 2) {
|
|
313
|
-
if (message.error)
|
|
314
|
-
finish(new Error("codex rate-limit method failed"));
|
|
213
|
+
if (message.error) finish(new Error("codex rate-limit method failed"));
|
|
315
214
|
else results.rateLimits = message.result;
|
|
316
215
|
} else if (message.id === 3) {
|
|
317
216
|
if (message.error) finish(new Error("codex account method failed"));
|
|
@@ -340,9 +239,7 @@ function unavailable(options: CodexProbeOptions): CapacityReport {
|
|
|
340
239
|
return codexUnavailableReport(options.checkedAt);
|
|
341
240
|
}
|
|
342
241
|
|
|
343
|
-
async function cliFallback(
|
|
344
|
-
options: CodexProbeOptions,
|
|
345
|
-
): Promise<CapacityReport> {
|
|
242
|
+
async function cliFallback(options: CodexProbeOptions): Promise<CapacityReport> {
|
|
346
243
|
if (!options.installed) return unavailable(options);
|
|
347
244
|
const messages: RpcMessage[] = [
|
|
348
245
|
{
|
|
@@ -358,8 +255,7 @@ async function cliFallback(
|
|
|
358
255
|
{ id: 3, method: "account/read" },
|
|
359
256
|
];
|
|
360
257
|
try {
|
|
361
|
-
const rpc =
|
|
362
|
-
options.rpc ?? ((requests) => appServerRpc(requests, options.timeoutMs));
|
|
258
|
+
const rpc = options.rpc ?? ((requests) => appServerRpc(requests, options.timeoutMs));
|
|
363
259
|
const response = await rpc(messages);
|
|
364
260
|
const result = capacityFromSnapshot(
|
|
365
261
|
parseCliUsage(response.rateLimits),
|
|
@@ -381,9 +277,7 @@ async function cliFallback(
|
|
|
381
277
|
}
|
|
382
278
|
}
|
|
383
279
|
|
|
384
|
-
export async function probeCodexCapacity(
|
|
385
|
-
options: CodexProbeOptions,
|
|
386
|
-
): Promise<CapacityReport> {
|
|
280
|
+
export async function probeCodexCapacity(options: CodexProbeOptions): Promise<CapacityReport> {
|
|
387
281
|
let parsed: UnknownRecord | null = null;
|
|
388
282
|
try {
|
|
389
283
|
const contents = await (options.readFile ?? readFile)(
|
|
@@ -410,10 +304,7 @@ export async function probeCodexCapacity(
|
|
|
410
304
|
);
|
|
411
305
|
const accountId = nonEmptyText(whoami?.chatgpt_account_id);
|
|
412
306
|
if (!accountId) throw new Error("account unavailable");
|
|
413
|
-
return capacityFromSnapshot(
|
|
414
|
-
await apiSnapshot(pat, accountId, "pat", options),
|
|
415
|
-
options,
|
|
416
|
-
);
|
|
307
|
+
return capacityFromSnapshot(await apiSnapshot(pat, accountId, "pat", options), options);
|
|
417
308
|
} catch {
|
|
418
309
|
// Continue to a separately available OAuth credential before using the CLI.
|
|
419
310
|
}
|
package/src/capacity/index.ts
CHANGED
|
@@ -2,10 +2,13 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { access as fsAccess } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { codexUnavailableReport, probeCodexCapacity } from "./codex.js";
|
|
5
|
+
import { probeClaudeCapacity, type ClaudeCapacityOptions } from "./claude.js";
|
|
6
|
+
import { probeOpenAiCapacity } from "./openai.js";
|
|
5
7
|
import { probeZaiCapacity } from "./zai.js";
|
|
6
8
|
import type { CapacityReport } from "./types.js";
|
|
7
9
|
|
|
8
10
|
export type { CapacityReport, CapacityWindow } from "./types.js";
|
|
11
|
+
export type { ClaudeCapacityOptions } from "./claude.js";
|
|
9
12
|
|
|
10
13
|
export type CapacityProbeOptions = {
|
|
11
14
|
now?: () => Date;
|
|
@@ -22,6 +25,14 @@ export type ZaiCapacityOptions = {
|
|
|
22
25
|
timeoutMs?: number;
|
|
23
26
|
};
|
|
24
27
|
|
|
28
|
+
export type OpenAiCapacityOptions = {
|
|
29
|
+
now?: () => Date;
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
readFile?: (path: string, encoding: BufferEncoding) => Promise<string>;
|
|
32
|
+
fetch?: typeof globalThis.fetch;
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
25
36
|
async function canAccess(target: string, mode: number): Promise<boolean> {
|
|
26
37
|
try {
|
|
27
38
|
await fsAccess(target, mode);
|
|
@@ -55,10 +66,7 @@ export async function getCodexCapacityReport(
|
|
|
55
66
|
options: CapacityProbeOptions = {},
|
|
56
67
|
): Promise<CapacityReport> {
|
|
57
68
|
const generatedAt = (options.now?.() ?? new Date()).toISOString();
|
|
58
|
-
const installed = await isCodexInstalled(
|
|
59
|
-
options.path ?? process.env.PATH ?? "",
|
|
60
|
-
options.access,
|
|
61
|
-
);
|
|
69
|
+
const installed = await isCodexInstalled(options.path ?? process.env.PATH ?? "", options.access);
|
|
62
70
|
try {
|
|
63
71
|
return await (options.probe ?? probeCodexCapacity)({
|
|
64
72
|
installed,
|
|
@@ -78,3 +86,24 @@ export async function getZaiCapacityReport(
|
|
|
78
86
|
checkedAt: (now?.() ?? new Date()).toISOString(),
|
|
79
87
|
});
|
|
80
88
|
}
|
|
89
|
+
|
|
90
|
+
export async function getOpenAiCapacityReport(
|
|
91
|
+
options: OpenAiCapacityOptions = {},
|
|
92
|
+
): Promise<CapacityReport> {
|
|
93
|
+
const { now, ...probeOptions } = options;
|
|
94
|
+
return probeOpenAiCapacity({
|
|
95
|
+
...probeOptions,
|
|
96
|
+
checkedAt: (now?.() ?? new Date()).toISOString(),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function getClaudeCapacityReport(
|
|
101
|
+
options: ClaudeCapacityOptions = {},
|
|
102
|
+
): Promise<CapacityReport> {
|
|
103
|
+
const { now, ...probeOptions } = options;
|
|
104
|
+
return probeClaudeCapacity({
|
|
105
|
+
...probeOptions,
|
|
106
|
+
checkedAt: (now?.() ?? new Date()).toISOString(),
|
|
107
|
+
now,
|
|
108
|
+
});
|
|
109
|
+
}
|