@getpaseo/server 0.1.97 → 0.1.99
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/server/server/agent/agent-manager.d.ts +11 -3
- package/dist/server/server/agent/agent-manager.js +96 -24
- package/dist/server/server/agent/agent-prompt.d.ts +1 -1
- package/dist/server/server/agent/agent-prompt.js +3 -10
- package/dist/server/server/agent/agent-sdk-types.d.ts +20 -9
- package/dist/server/server/agent/create-agent/create.d.ts +2 -0
- package/dist/server/server/agent/create-agent/create.js +8 -7
- package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
- package/dist/server/server/agent/lifecycle-command.js +9 -2
- package/dist/server/server/agent/mcp-server.js +254 -115
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +8 -3
- package/dist/server/server/agent/provider-registry.js +58 -25
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +37 -16
- package/dist/server/server/agent/providers/acp-agent.d.ts +5 -3
- package/dist/server/server/agent/providers/acp-agent.js +32 -19
- package/dist/server/server/agent/providers/claude/agent.d.ts +2 -2
- package/dist/server/server/agent/providers/claude/agent.js +261 -167
- package/dist/server/server/agent/providers/claude/models.js +7 -3
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +6 -4
- package/dist/server/server/agent/providers/codex-app-server-agent.js +48 -25
- package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -31
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +0 -1
- package/dist/server/server/agent/providers/generic-acp-agent.js +2 -108
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -3
- package/dist/server/server/agent/providers/mock-load-test-agent.js +5 -5
- package/dist/server/server/agent/providers/mock-slow-provider.d.ts +2 -3
- package/dist/server/server/agent/providers/mock-slow-provider.js +3 -6
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
- package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
- package/dist/server/server/agent/providers/opencode-agent.d.ts +6 -3
- package/dist/server/server/agent/providers/opencode-agent.js +61 -107
- package/dist/server/server/agent/providers/pi/agent.d.ts +2 -3
- package/dist/server/server/agent/providers/pi/agent.js +11 -63
- package/dist/server/server/agent/providers/pi/cli-runtime.js +2 -2
- package/dist/server/server/agent/providers/pi/runtime.d.ts +1 -1
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +1 -1
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +1 -1
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +32 -2
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
- package/dist/server/server/session/agent-config/agent-config-session.d.ts +50 -0
- package/dist/server/server/session/agent-config/agent-config-session.js +98 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +120 -0
- package/dist/server/server/session/chat/chat-schedule-loop-session.js +489 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +142 -0
- package/dist/server/server/session/checkout/checkout-session.js +925 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +50 -0
- package/dist/server/server/session/daemon/daemon-session.js +98 -0
- package/dist/server/server/session/files/workspace-files-session.d.ts +43 -0
- package/dist/server/server/session/files/workspace-files-session.js +218 -0
- package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
- package/dist/server/server/session/project-config/project-config-session.js +125 -0
- package/dist/server/server/session/provider/provider-catalog-session.d.ts +74 -0
- package/dist/server/server/session/provider/provider-catalog-session.js +339 -0
- package/dist/server/server/session/voice/voice-session.d.ts +166 -0
- package/dist/server/server/session/voice/voice-session.js +893 -0
- package/dist/server/server/{voice → session/voice}/voice-turn-controller.d.ts +2 -2
- package/dist/server/server/{voice → session/voice}/voice-turn-controller.js +2 -2
- package/dist/server/server/session.d.ts +23 -207
- package/dist/server/server/session.js +2319 -5102
- package/dist/server/server/speech/providers/openai/runtime.js +3 -4
- package/dist/server/server/websocket-server.d.ts +1 -0
- package/dist/server/server/websocket-server.js +11 -0
- package/dist/server/server/workspace-archive-service.js +2 -3
- package/dist/server/server/workspace-directory.js +5 -5
- package/dist/server/server/workspace-reconciliation-service.js +2 -2
- package/dist/server/server/worktree-core.d.ts +1 -0
- package/dist/server/server/worktree-core.js +5 -1
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +47 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/utils/checkout-git.d.ts +6 -0
- package/dist/server/utils/directory-suggestions.js +98 -2
- package/package.json +5 -5
|
@@ -38,15 +38,14 @@ export function validateOpenAiCredentialRequirements(params) {
|
|
|
38
38
|
missingOpenAiCredentialsFor.push("dictation.stt");
|
|
39
39
|
}
|
|
40
40
|
if (missingOpenAiCredentialsFor.length > 0) {
|
|
41
|
-
logger.
|
|
41
|
+
logger.warn({
|
|
42
42
|
requestedProviders: {
|
|
43
43
|
dictationStt: providers.dictationStt.provider,
|
|
44
44
|
voiceStt: providers.voiceStt.provider,
|
|
45
45
|
voiceTts: providers.voiceTts.provider,
|
|
46
46
|
},
|
|
47
47
|
missingOpenAiCredentialsFor,
|
|
48
|
-
}, "Invalid speech configuration: OpenAI provider selected but credentials are missing");
|
|
49
|
-
throw new Error(`Missing OpenAI credentials for configured speech features: ${missingOpenAiCredentialsFor.join(", ")}`);
|
|
48
|
+
}, "Invalid speech configuration: OpenAI provider selected but credentials are missing — speech features will be unavailable");
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
function createOpenAiStt(apiKey, openaiConfig, logger) {
|
|
@@ -105,7 +104,7 @@ export function initializeOpenAiSpeechServices(params) {
|
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
else if (needsAnyOpenAi) {
|
|
108
|
-
|
|
107
|
+
// validateOpenAiCredentialRequirements already warned about missing credentials
|
|
109
108
|
}
|
|
110
109
|
return {
|
|
111
110
|
turnDetectionService,
|
|
@@ -93,6 +93,7 @@ export declare class VoiceAssistantWebSocketServer {
|
|
|
93
93
|
private eventLoopDelayMonitor;
|
|
94
94
|
private unsubscribeSpeechReadiness;
|
|
95
95
|
private unsubscribeDaemonConfigChange;
|
|
96
|
+
private readonly providerUsageService;
|
|
96
97
|
private unsubscribeTerminalActivity;
|
|
97
98
|
constructor(server: HTTPServer, logger: pino.Logger, serverId: string, agentManager: AgentManager, agentStorage: AgentStorage, downloadTokenStore: DownloadTokenStore, paseoHome: string, daemonConfigStore: DaemonConfigStore, mcpBaseUrl: string | null, wsConfig: WebSocketServerConfig, auth?: DaemonAuthConfig, speech?: SpeechService | null, terminalManager?: TerminalManager | null, dictation?: {
|
|
98
99
|
finalTimeoutMs?: number;
|
|
@@ -14,6 +14,7 @@ import { buildAgentAttentionNotificationPayload, findLatestPermissionRequest, }
|
|
|
14
14
|
import { createGitHubService } from "../services/github-service.js";
|
|
15
15
|
import { extractWsBearerProtocol, extractWsBearerToken, isBearerTokenValid, } from "./auth.js";
|
|
16
16
|
import { WebSocketRuntimeMetricsWindow, } from "./websocket/runtime-metrics.js";
|
|
17
|
+
import { ProviderUsageService } from "../services/quota-fetcher/service.js";
|
|
17
18
|
const WS_CLOSE_DAEMON_AUTH_FAILED = 4401;
|
|
18
19
|
function resolveTerminalAttentionReason(input) {
|
|
19
20
|
if (input.attentionReason === "finished")
|
|
@@ -298,6 +299,9 @@ export class VoiceAssistantWebSocketServer {
|
|
|
298
299
|
this.logger.warn({ err, agentId: params.agentId }, "Failed to broadcast agent attention");
|
|
299
300
|
});
|
|
300
301
|
});
|
|
302
|
+
this.providerUsageService = new ProviderUsageService({
|
|
303
|
+
logger: this.logger,
|
|
304
|
+
});
|
|
301
305
|
this.wss = this.createWebSocketServer(server, wsConfig, auth);
|
|
302
306
|
this.startRuntimeMetricsInterval();
|
|
303
307
|
this.logger.info("WebSocket server initialized on /ws");
|
|
@@ -649,6 +653,7 @@ export class VoiceAssistantWebSocketServer {
|
|
|
649
653
|
tts: () => this.speech?.resolveTts() ?? null,
|
|
650
654
|
terminalManager: this.terminalManager,
|
|
651
655
|
providerSnapshotManager: this.providerSnapshotManager,
|
|
656
|
+
providerUsageService: this.providerUsageService,
|
|
652
657
|
serviceProxy: this.serviceProxy ?? undefined,
|
|
653
658
|
scriptRuntimeStore: this.scriptRuntimeStore ?? undefined,
|
|
654
659
|
workspaceSetupSnapshots: this.workspaceSetupSnapshots,
|
|
@@ -809,8 +814,14 @@ export class VoiceAssistantWebSocketServer {
|
|
|
809
814
|
workspaceMultiplicity: true,
|
|
810
815
|
// COMPAT(projectRemove): added in v0.1.97, drop the gate when floor >= v0.1.97.
|
|
811
816
|
projectRemove: true,
|
|
817
|
+
// COMPAT(projectAdd): added in v0.1.97, drop the gate when floor >= v0.1.97.
|
|
818
|
+
projectAdd: true,
|
|
812
819
|
// COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
|
|
813
820
|
worktreeRestore: true,
|
|
821
|
+
// COMPAT(providerUsageList): added in v0.1.98, drop the gate when daemon floor >= v0.1.98.
|
|
822
|
+
providerUsageList: true,
|
|
823
|
+
// COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
|
|
824
|
+
agentDetach: true,
|
|
814
825
|
},
|
|
815
826
|
};
|
|
816
827
|
}
|
|
@@ -212,9 +212,8 @@ export async function killTerminalsForWorkspace(dependencies, workspaceId) {
|
|
|
212
212
|
}
|
|
213
213
|
}));
|
|
214
214
|
}
|
|
215
|
-
// Archiving the last workspace of a project leaves the project
|
|
216
|
-
//
|
|
217
|
-
// parent project here.
|
|
215
|
+
// Archiving the last workspace of a project leaves the project record active.
|
|
216
|
+
// The user removes the project explicitly, so we never archive the parent here.
|
|
218
217
|
export async function archivePersistedWorkspaceRecord(input) {
|
|
219
218
|
const existingWorkspace = await input.workspaceRegistry.get(input.workspaceId);
|
|
220
219
|
if (!existingWorkspace) {
|
|
@@ -295,9 +295,9 @@ export class WorkspaceDirectory {
|
|
|
295
295
|
const candidates = [...agentTimestamps, ...terminalTimestamps].sort();
|
|
296
296
|
return candidates.at(-1) ?? null;
|
|
297
297
|
}
|
|
298
|
-
// Project parents that have no active workspaces.
|
|
299
|
-
//
|
|
300
|
-
//
|
|
298
|
+
// Project parents that have no active workspaces. The wire field is the
|
|
299
|
+
// sidebar projection bucket for projects whose workspace list is currently
|
|
300
|
+
// empty; it is not a separate domain record.
|
|
301
301
|
async listEmptyProjects() {
|
|
302
302
|
const [persistedWorkspaces, persistedProjects] = await Promise.all([
|
|
303
303
|
this.deps.workspaceRegistry.list(),
|
|
@@ -359,8 +359,8 @@ export class WorkspaceDirectory {
|
|
|
359
359
|
const nextCursor = hasMore && pagedEntries.length > 0
|
|
360
360
|
? this.pager.encode(pagedEntries[pagedEntries.length - 1], sort)
|
|
361
361
|
: null;
|
|
362
|
-
//
|
|
363
|
-
// them without them
|
|
362
|
+
// Project parents with no active workspaces ride only on the first page so
|
|
363
|
+
// the sidebar can render them without duplicating them across pagination.
|
|
364
364
|
const projectIdFilter = filter?.projectId?.trim();
|
|
365
365
|
const emptyProjects = cursorToken
|
|
366
366
|
? []
|
|
@@ -98,8 +98,8 @@ export class WorkspaceReconciliationService {
|
|
|
98
98
|
// 2. Merge duplicate active project records that point at the same repo root.
|
|
99
99
|
await this.mergeDuplicateProjectsByRoot(activeProjects, workspacesByProject, changes);
|
|
100
100
|
// 3. Reconcile git metadata for active projects whose directories still exist.
|
|
101
|
-
//
|
|
102
|
-
//
|
|
101
|
+
// Projects persist until explicitly removed, even when they currently have
|
|
102
|
+
// zero active workspaces, so they still reconcile their own metadata.
|
|
103
103
|
// Skip projects archived earlier in this pass (e.g. merged duplicates) so we
|
|
104
104
|
// don't resurrect them by upserting a stale, non-archived copy.
|
|
105
105
|
const archivedProjectIds = new Set(changes
|
|
@@ -6,6 +6,9 @@ export async function createWorktreeCore(input, deps) {
|
|
|
6
6
|
const requestedWorktreeSlug = input.worktreeSlug
|
|
7
7
|
? normalizeWorktreeSlug(input.worktreeSlug)
|
|
8
8
|
: undefined;
|
|
9
|
+
const requestedBranchName = input.branchName
|
|
10
|
+
? validateWorktreeSlug(input.branchName.trim())
|
|
11
|
+
: undefined;
|
|
9
12
|
let intentInput;
|
|
10
13
|
if (input.action === "checkout") {
|
|
11
14
|
intentInput = {
|
|
@@ -27,6 +30,7 @@ export async function createWorktreeCore(input, deps) {
|
|
|
27
30
|
intentInput = {
|
|
28
31
|
action: "branch-off",
|
|
29
32
|
refName: input.refName,
|
|
33
|
+
branchName: requestedBranchName,
|
|
30
34
|
worktreeSlug,
|
|
31
35
|
};
|
|
32
36
|
}
|
|
@@ -37,7 +41,7 @@ export async function createWorktreeCore(input, deps) {
|
|
|
37
41
|
let normalizedSlug;
|
|
38
42
|
switch (intent.kind) {
|
|
39
43
|
case "branch-off": {
|
|
40
|
-
normalizedSlug = intent.branchName;
|
|
44
|
+
normalizedSlug = requestedWorktreeSlug ?? normalizeWorktreeSlug(intent.branchName);
|
|
41
45
|
break;
|
|
42
46
|
}
|
|
43
47
|
case "checkout-branch": {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderUsageFetcher, ProviderUsageFetcherFactoryOptions, ProviderUsageFetcherManifestEntry } from "./provider.js";
|
|
2
|
+
export declare const PROVIDER_USAGE_FETCHERS: readonly ProviderUsageFetcherManifestEntry[];
|
|
3
|
+
export declare function createProviderUsageFetchers(options: ProviderUsageFetcherFactoryOptions): ProviderUsageFetcher[];
|
|
4
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ClaudeQuotaProvider } from "./providers/claude.js";
|
|
2
|
+
import { CodexQuotaProvider } from "./providers/codex.js";
|
|
3
|
+
import { CopilotQuotaProvider } from "./providers/copilot.js";
|
|
4
|
+
import { CursorQuotaProvider } from "./providers/cursor.js";
|
|
5
|
+
import { GrokQuotaProvider } from "./providers/grok.js";
|
|
6
|
+
import { KimiQuotaProvider } from "./providers/kimi.js";
|
|
7
|
+
import { ZaiQuotaProvider } from "./providers/zai.js";
|
|
8
|
+
export const PROVIDER_USAGE_FETCHERS = [
|
|
9
|
+
{
|
|
10
|
+
providerId: "claude",
|
|
11
|
+
create: (options) => new ClaudeQuotaProvider({
|
|
12
|
+
logger: options.logger,
|
|
13
|
+
fetch: options.fetch,
|
|
14
|
+
}),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
providerId: "codex",
|
|
18
|
+
create: (options) => new CodexQuotaProvider({
|
|
19
|
+
logger: options.logger,
|
|
20
|
+
fetch: options.fetch,
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
providerId: "copilot",
|
|
25
|
+
create: (options) => new CopilotQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
providerId: "cursor",
|
|
29
|
+
create: (options) => new CursorQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
providerId: "zai",
|
|
33
|
+
create: (options) => new ZaiQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
providerId: "grok",
|
|
37
|
+
create: (options) => new GrokQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
providerId: "kimi",
|
|
41
|
+
create: (options) => new KimiQuotaProvider({ logger: options.logger, fetch: options.fetch }),
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
export function createProviderUsageFetchers(options) {
|
|
45
|
+
return PROVIDER_USAGE_FETCHERS.map((entry) => entry.create(options));
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../server/messages.js";
|
|
3
|
+
export type ProviderApiFetch = typeof fetch;
|
|
4
|
+
export interface ProviderUsageFetcher {
|
|
5
|
+
readonly providerId: string;
|
|
6
|
+
readonly displayName: string;
|
|
7
|
+
fetchUsage(): Promise<ProviderUsage>;
|
|
8
|
+
}
|
|
9
|
+
export interface ProviderUsageFetcherFactoryOptions {
|
|
10
|
+
logger: Logger;
|
|
11
|
+
fetch?: ProviderApiFetch;
|
|
12
|
+
}
|
|
13
|
+
export interface ProviderUsageFetcherManifestEntry {
|
|
14
|
+
readonly providerId: string;
|
|
15
|
+
create(options: ProviderUsageFetcherFactoryOptions): ProviderUsageFetcher;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../../server/messages.js";
|
|
3
|
+
import type { ProviderApiFetch, ProviderUsageFetcher } from "../provider.js";
|
|
4
|
+
interface ClaudeQuotaProviderOptions {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
claudeHome?: string;
|
|
7
|
+
claudeKeychainReader?: () => Promise<unknown | null>;
|
|
8
|
+
platform?: typeof process.platform;
|
|
9
|
+
fetch?: ProviderApiFetch;
|
|
10
|
+
}
|
|
11
|
+
export declare class ClaudeQuotaProvider implements ProviderUsageFetcher {
|
|
12
|
+
readonly providerId = "claude";
|
|
13
|
+
readonly displayName = "Claude";
|
|
14
|
+
private readonly claudeHome;
|
|
15
|
+
private readonly readKeychainCredentials;
|
|
16
|
+
private readonly platform;
|
|
17
|
+
private readonly fetchApi;
|
|
18
|
+
constructor(options: ClaudeQuotaProviderOptions);
|
|
19
|
+
fetchUsage(): Promise<ProviderUsage>;
|
|
20
|
+
private readCredentials;
|
|
21
|
+
private callClaudeApi;
|
|
22
|
+
private refreshClaudeToken;
|
|
23
|
+
private saveClaudeCredentials;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { ApiNumberSchema, fetchProviderApi, unavailableUsage, windowFromUsedPct, } from "../usage.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const CLAUDE_KEYCHAIN_TIMEOUT_MS = 2000;
|
|
10
|
+
const CLAUDE_OAUTH_BETA = "oauth-2025-04-20";
|
|
11
|
+
const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
12
|
+
const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
13
|
+
const ClaudeCredentialsSchema = z.object({
|
|
14
|
+
claudeAiOauth: z
|
|
15
|
+
.object({
|
|
16
|
+
accessToken: z.string().optional(),
|
|
17
|
+
refreshToken: z.string().optional(),
|
|
18
|
+
subscriptionType: z.string().optional(),
|
|
19
|
+
rateLimitTier: z.string().optional(),
|
|
20
|
+
})
|
|
21
|
+
.optional(),
|
|
22
|
+
});
|
|
23
|
+
const ClaudeUsageWindowSchema = z.object({
|
|
24
|
+
utilization: ApiNumberSchema,
|
|
25
|
+
resets_at: z.string().optional(),
|
|
26
|
+
});
|
|
27
|
+
const ClaudeUsageResponseSchema = z.object({
|
|
28
|
+
five_hour: ClaudeUsageWindowSchema.nullish(),
|
|
29
|
+
seven_day: ClaudeUsageWindowSchema.nullish(),
|
|
30
|
+
seven_day_opus: ClaudeUsageWindowSchema.nullish(),
|
|
31
|
+
seven_day_omelette: ClaudeUsageWindowSchema.nullish(),
|
|
32
|
+
extra_usage: z
|
|
33
|
+
.object({
|
|
34
|
+
is_enabled: z.boolean().optional(),
|
|
35
|
+
})
|
|
36
|
+
.nullish(),
|
|
37
|
+
});
|
|
38
|
+
const ClaudeTokenRefreshSchema = z.object({
|
|
39
|
+
access_token: z.string().optional(),
|
|
40
|
+
refresh_token: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
function buildClaudePlan(subscriptionType, rateLimitTier) {
|
|
43
|
+
if (!subscriptionType)
|
|
44
|
+
return null;
|
|
45
|
+
const label = subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1);
|
|
46
|
+
const tier = rateLimitTier?.split("_").pop();
|
|
47
|
+
return tier ? `${label} ${tier}` : label;
|
|
48
|
+
}
|
|
49
|
+
async function readClaudeKeychainCredentials() {
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execFileAsync("security", ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"], { timeout: CLAUDE_KEYCHAIN_TIMEOUT_MS });
|
|
52
|
+
const raw = stdout.trim();
|
|
53
|
+
if (!raw)
|
|
54
|
+
return null;
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class ClaudeQuotaProvider {
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.providerId = "claude";
|
|
64
|
+
this.displayName = "Claude";
|
|
65
|
+
this.claudeHome =
|
|
66
|
+
options.claudeHome || process.env["CLAUDE_HOME"] || join(homedir(), ".claude");
|
|
67
|
+
this.readKeychainCredentials = options.claudeKeychainReader ?? readClaudeKeychainCredentials;
|
|
68
|
+
this.platform = options.platform ?? process.platform;
|
|
69
|
+
this.fetchApi = options.fetch ?? fetch;
|
|
70
|
+
}
|
|
71
|
+
async fetchUsage() {
|
|
72
|
+
const credentials = await this.readCredentials();
|
|
73
|
+
if (!credentials) {
|
|
74
|
+
return unavailableUsage(this);
|
|
75
|
+
}
|
|
76
|
+
const { oauth, filePath } = credentials;
|
|
77
|
+
const plan = buildClaudePlan(oauth.subscriptionType, oauth.rateLimitTier);
|
|
78
|
+
let resp = await this.callClaudeApi(oauth.accessToken);
|
|
79
|
+
if (resp === "NEEDS_AUTH") {
|
|
80
|
+
if (!filePath || !oauth.refreshToken) {
|
|
81
|
+
return unavailableUsage(this);
|
|
82
|
+
}
|
|
83
|
+
const refreshed = await this.refreshClaudeToken(oauth.refreshToken);
|
|
84
|
+
if (!refreshed?.access_token) {
|
|
85
|
+
return unavailableUsage(this);
|
|
86
|
+
}
|
|
87
|
+
await this.saveClaudeCredentials(filePath, {
|
|
88
|
+
...oauth,
|
|
89
|
+
accessToken: refreshed.access_token,
|
|
90
|
+
refreshToken: refreshed.refresh_token ?? oauth.refreshToken,
|
|
91
|
+
});
|
|
92
|
+
resp = await this.callClaudeApi(refreshed.access_token);
|
|
93
|
+
if (resp === "NEEDS_AUTH") {
|
|
94
|
+
return unavailableUsage(this);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const windows = [];
|
|
98
|
+
if (resp.five_hour) {
|
|
99
|
+
windows.push(windowFromUsedPct({
|
|
100
|
+
id: "five_hour",
|
|
101
|
+
label: "Session",
|
|
102
|
+
utilizationPct: resp.five_hour.utilization,
|
|
103
|
+
resetsAt: resp.five_hour.resets_at ?? null,
|
|
104
|
+
tone: "ok",
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
if (resp.seven_day) {
|
|
108
|
+
windows.push(windowFromUsedPct({
|
|
109
|
+
id: "weekly",
|
|
110
|
+
label: "Weekly",
|
|
111
|
+
utilizationPct: resp.seven_day.utilization,
|
|
112
|
+
resetsAt: resp.seven_day.resets_at ?? null,
|
|
113
|
+
tone: "ok",
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
if (resp.seven_day_opus) {
|
|
117
|
+
windows.push(windowFromUsedPct({
|
|
118
|
+
id: "weekly_opus",
|
|
119
|
+
label: "Weekly · Opus",
|
|
120
|
+
utilizationPct: resp.seven_day_opus.utilization,
|
|
121
|
+
resetsAt: resp.seven_day_opus.resets_at ?? null,
|
|
122
|
+
tone: "ok",
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
if (resp.seven_day_omelette) {
|
|
126
|
+
windows.push(windowFromUsedPct({
|
|
127
|
+
id: "weekly_omelette",
|
|
128
|
+
label: "Weekly · Omelette",
|
|
129
|
+
utilizationPct: resp.seven_day_omelette.utilization,
|
|
130
|
+
resetsAt: resp.seven_day_omelette.resets_at ?? null,
|
|
131
|
+
tone: "ok",
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
const details = [];
|
|
135
|
+
const extraUsageEnabled = resp.extra_usage?.is_enabled;
|
|
136
|
+
if (extraUsageEnabled !== undefined) {
|
|
137
|
+
details.push({
|
|
138
|
+
id: "extra_usage",
|
|
139
|
+
label: "Extra usage",
|
|
140
|
+
value: extraUsageEnabled ? "Enabled" : "Disabled",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
providerId: this.providerId,
|
|
145
|
+
displayName: this.displayName,
|
|
146
|
+
status: "available",
|
|
147
|
+
planLabel: plan,
|
|
148
|
+
windows,
|
|
149
|
+
balances: [],
|
|
150
|
+
details,
|
|
151
|
+
error: null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async readCredentials() {
|
|
155
|
+
const credPath = join(this.claudeHome, ".credentials.json");
|
|
156
|
+
if (existsSync(credPath)) {
|
|
157
|
+
try {
|
|
158
|
+
const creds = ClaudeCredentialsSchema.parse(JSON.parse(await fs.readFile(credPath, "utf8")));
|
|
159
|
+
const oauth = creds.claudeAiOauth;
|
|
160
|
+
if (oauth?.accessToken) {
|
|
161
|
+
return { oauth: { ...oauth, accessToken: oauth.accessToken }, filePath: credPath };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Fall through to the macOS Keychain below.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (this.platform === "darwin") {
|
|
169
|
+
const creds = ClaudeCredentialsSchema.safeParse(await this.readKeychainCredentials());
|
|
170
|
+
const oauth = creds.success ? creds.data.claudeAiOauth : undefined;
|
|
171
|
+
if (oauth?.accessToken) {
|
|
172
|
+
return { oauth: { ...oauth, accessToken: oauth.accessToken }, filePath: null };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
async callClaudeApi(token) {
|
|
178
|
+
const res = await fetchProviderApi(this.fetchApi, "https://api.anthropic.com/api/oauth/usage", {
|
|
179
|
+
headers: {
|
|
180
|
+
Authorization: `Bearer ${token}`,
|
|
181
|
+
Accept: "application/json",
|
|
182
|
+
"anthropic-beta": CLAUDE_OAUTH_BETA,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
if (res.status === 401 || res.status === 403)
|
|
186
|
+
return "NEEDS_AUTH";
|
|
187
|
+
if (!res.ok)
|
|
188
|
+
throw new Error(`Claude usage API returned ${res.status}`);
|
|
189
|
+
return ClaudeUsageResponseSchema.parse(await res.json());
|
|
190
|
+
}
|
|
191
|
+
async refreshClaudeToken(refreshToken) {
|
|
192
|
+
const res = await fetchProviderApi(this.fetchApi, "https://platform.claude.com/v1/oauth/token", {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: { "Content-Type": "application/json" },
|
|
195
|
+
body: JSON.stringify({
|
|
196
|
+
grant_type: "refresh_token",
|
|
197
|
+
refresh_token: refreshToken,
|
|
198
|
+
client_id: CLAUDE_CLIENT_ID,
|
|
199
|
+
scope: "user:profile user:inference user:sessions:claude_code user:mcp_servers",
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
if (!res.ok)
|
|
203
|
+
return null;
|
|
204
|
+
return ClaudeTokenRefreshSchema.parse(await res.json());
|
|
205
|
+
}
|
|
206
|
+
async saveClaudeCredentials(credPath, oauth) {
|
|
207
|
+
try {
|
|
208
|
+
const existing = ClaudeCredentialsSchema.parse(JSON.parse(await fs.readFile(credPath, "utf8")));
|
|
209
|
+
existing.claudeAiOauth = oauth;
|
|
210
|
+
await fs.writeFile(credPath, JSON.stringify(existing, null, 2), { mode: 0o600 });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Non-fatal; Claude Code can refresh again on its own next time.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProviderUsage } from "../../../server/messages.js";
|
|
3
|
+
import type { ProviderApiFetch, ProviderUsageFetcher } from "../provider.js";
|
|
4
|
+
interface CodexQuotaProviderOptions {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
codexHome?: string;
|
|
7
|
+
fetch?: ProviderApiFetch;
|
|
8
|
+
}
|
|
9
|
+
export declare class CodexQuotaProvider implements ProviderUsageFetcher {
|
|
10
|
+
readonly providerId = "codex";
|
|
11
|
+
readonly displayName = "Codex";
|
|
12
|
+
private readonly codexHome;
|
|
13
|
+
private readonly fetchApi;
|
|
14
|
+
constructor(options: CodexQuotaProviderOptions);
|
|
15
|
+
fetchUsage(): Promise<ProviderUsage>;
|
|
16
|
+
private toUsage;
|
|
17
|
+
private readCodexAuth;
|
|
18
|
+
private callCodexApi;
|
|
19
|
+
private refreshCodexToken;
|
|
20
|
+
private saveCodexAuth;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=codex.d.ts.map
|