@getpaseo/server 0.5.0-beta.5 → 0.5.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.
Files changed (38) hide show
  1. package/dist/server/server/agent/agent-manager.d.ts +1 -0
  2. package/dist/server/server/agent/agent-manager.js +42 -17
  3. package/dist/server/server/agent/agent-sdk-types.d.ts +5 -0
  4. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +1 -0
  5. package/dist/server/server/agent/providers/codex-app-server-agent.js +54 -5
  6. package/dist/server/services/quota-fetcher/providers/claude.d.ts +0 -2
  7. package/dist/server/services/quota-fetcher/providers/claude.js +6 -50
  8. package/dist/server/services/quota-fetcher/providers/codex.d.ts +0 -2
  9. package/dist/server/services/quota-fetcher/providers/codex.js +7 -54
  10. package/dist/server/services/quota-fetcher/providers/kimi.d.ts +0 -4
  11. package/dist/server/services/quota-fetcher/providers/kimi.js +5 -99
  12. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-28615b837fff299849f3363652b95f0f.js → desktop-attachment-bridge-d452c6e787c8d41aa2e1cfafbb6e477b.js} +1 -1
  13. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-28615b837fff299849f3363652b95f0f.js.br → desktop-attachment-bridge-d452c6e787c8d41aa2e1cfafbb6e477b.js.br} +0 -0
  14. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-d452c6e787c8d41aa2e1cfafbb6e477b.js.gz +0 -0
  15. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-99d20cdd7788af37bc5fbd91e0bb49e6.js → desktop-attachment-store-4804f15e0899aeb03f110d60408577f6.js} +1 -1
  16. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-4804f15e0899aeb03f110d60408577f6.js.br +0 -0
  17. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-99d20cdd7788af37bc5fbd91e0bb49e6.js.gz → desktop-attachment-store-4804f15e0899aeb03f110d60408577f6.js.gz} +0 -0
  18. package/dist/server/web-ui/_expo/static/js/web/{index-f38dc1ee96a16e9953f300e24744fb3c.js → index-d7eec3768e89ac8be8b5256d2a46412e.js} +226 -224
  19. package/dist/server/web-ui/_expo/static/js/web/index-d7eec3768e89ac8be8b5256d2a46412e.js.br +0 -0
  20. package/dist/server/web-ui/_expo/static/js/web/index-d7eec3768e89ac8be8b5256d2a46412e.js.gz +0 -0
  21. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-2bc098e7b3216f2c0cf93eeeafaac48e.js → indexeddb-attachment-store-e846e4521dbd9112f5a8aca5b678843c.js} +1 -1
  22. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-e846e4521dbd9112f5a8aca5b678843c.js.br +0 -0
  23. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-e846e4521dbd9112f5a8aca5b678843c.js.gz +0 -0
  24. package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-021ee430e5ecd819e1e2016ed79c78d3.js → native-file-attachment-store-dae92151d814845947244302dbb2a21b.js} +3 -3
  25. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-dae92151d814845947244302dbb2a21b.js.br +0 -0
  26. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-dae92151d814845947244302dbb2a21b.js.gz +0 -0
  27. package/dist/server/web-ui/index.html +1 -1
  28. package/dist/server/web-ui/index.html.br +0 -0
  29. package/dist/server/web-ui/index.html.gz +0 -0
  30. package/package.json +6 -6
  31. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-28615b837fff299849f3363652b95f0f.js.gz +0 -0
  32. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-99d20cdd7788af37bc5fbd91e0bb49e6.js.br +0 -0
  33. package/dist/server/web-ui/_expo/static/js/web/index-f38dc1ee96a16e9953f300e24744fb3c.js.br +0 -0
  34. package/dist/server/web-ui/_expo/static/js/web/index-f38dc1ee96a16e9953f300e24744fb3c.js.gz +0 -0
  35. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-2bc098e7b3216f2c0cf93eeeafaac48e.js.br +0 -0
  36. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-2bc098e7b3216f2c0cf93eeeafaac48e.js.gz +0 -0
  37. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-021ee430e5ecd819e1e2016ed79c78d3.js.br +0 -0
  38. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-021ee430e5ecd819e1e2016ed79c78d3.js.gz +0 -0
@@ -376,6 +376,7 @@ export declare class AgentManager {
376
376
  tryRunOutOfBand(agentId: string, prompt: AgentPromptInput, options?: AgentRunOptions): boolean;
377
377
  appendTimelineItem(agentId: string, item: AgentTimelineItem): Promise<void>;
378
378
  emitLiveTimelineItem(agentId: string, item: AgentTimelineItem): Promise<void>;
379
+ private startPendingForegroundTurn;
379
380
  streamAgent(agentId: string, prompt: AgentPromptInput, options?: AgentRunOptions): AsyncGenerator<AgentStreamEvent>;
380
381
  private finalizeForegroundTurn;
381
382
  private openActiveTurn;
@@ -9,7 +9,7 @@ import { buildArchivedAgentRecord } from "./agent-archive.js";
9
9
  import { InMemoryAgentTimelineStore, } from "./agent-timeline-store.js";
10
10
  import { AGENT_STREAM_COALESCE_DEFAULT_WINDOW_MS, AgentStreamCoalescer, } from "./agent-stream-coalescer.js";
11
11
  import { limitAgentTimelineItemContent } from "./agent-timeline-content.js";
12
- import { AgentRunState } from "./agent-run-state.js";
12
+ import { AgentRunState, } from "./agent-run-state.js";
13
13
  import { invokeRewindCapability } from "./rewind/rewind.js";
14
14
  import { isSystemInjectedEnvelope } from "./agent-prompt.js";
15
15
  import { stripInternalPaseoMcpServer, withRuntimePaseoMcpServer } from "./runtime-mcp-config.js";
@@ -1397,6 +1397,31 @@ export class AgentManager {
1397
1397
  provider: agent.provider,
1398
1398
  });
1399
1399
  }
1400
+ async startPendingForegroundTurn(params) {
1401
+ const { agent, agentId, pendingRun, prompt, options } = params;
1402
+ try {
1403
+ const result = await agent.session.startTurn(prompt, options);
1404
+ if (pendingRun.settled) {
1405
+ throw new Error(`Agent ${agentId} run was canceled before its turn started`);
1406
+ }
1407
+ return result.turnId;
1408
+ }
1409
+ catch (error) {
1410
+ if (pendingRun.settled) {
1411
+ throw error;
1412
+ }
1413
+ agent.pendingReplacement = false;
1414
+ const errorMsg = error instanceof Error ? error.message : "Failed to start turn";
1415
+ await this.handleStreamEvent(agent, {
1416
+ type: "turn_failed",
1417
+ provider: agent.provider,
1418
+ error: errorMsg,
1419
+ });
1420
+ this.finalizeForegroundTurn(agent);
1421
+ this.runs.settleForegroundRun(agentId, pendingRun.token);
1422
+ throw error;
1423
+ }
1424
+ }
1400
1425
  streamAgent(agentId, prompt, options) {
1401
1426
  const existingAgent = this.requireSessionAgent(agentId);
1402
1427
  this.logger.trace({
@@ -1428,22 +1453,13 @@ export class AgentManager {
1428
1453
  const streamForwarder = async function* streamForwarder() {
1429
1454
  let turnId;
1430
1455
  let turnStream = null;
1431
- try {
1432
- const result = await agent.session.startTurn(prompt, options);
1433
- turnId = result.turnId;
1434
- }
1435
- catch (error) {
1436
- agent.pendingReplacement = false;
1437
- const errorMsg = error instanceof Error ? error.message : "Failed to start turn";
1438
- await this.handleStreamEvent(agent, {
1439
- type: "turn_failed",
1440
- provider: agent.provider,
1441
- error: errorMsg,
1442
- });
1443
- this.finalizeForegroundTurn(agent);
1444
- this.runs.settleForegroundRun(agentId, pendingRun.token);
1445
- throw error;
1446
- }
1456
+ turnId = await this.startPendingForegroundTurn({
1457
+ agent,
1458
+ agentId,
1459
+ pendingRun,
1460
+ prompt,
1461
+ options,
1462
+ });
1447
1463
  if (isReplacement) {
1448
1464
  agent.pendingReplacement = false;
1449
1465
  }
@@ -1859,6 +1875,15 @@ export class AgentManager {
1859
1875
  });
1860
1876
  await run.settledPromise;
1861
1877
  }
1878
+ else if (settlement === "timed_out" && run.kind === "foreground") {
1879
+ this.logger.warn({ agentId, kind: run.kind }, "cancelAgentRun: acknowledged pending turn still active after timeout, clearing it");
1880
+ this.runs.settleForegroundRun(agentId, run.token);
1881
+ if (!agent.pendingReplacement) {
1882
+ agent.lifecycle = "idle";
1883
+ this.touchUpdatedAt(agent);
1884
+ this.emitState(agent);
1885
+ }
1886
+ }
1862
1887
  else if (settlement === "timed_out" && run.kind === "autonomous") {
1863
1888
  this.logger.warn({ agentId, kind: run.kind }, "cancelAgentRun: acknowledged turn still active after timeout, force-canceling");
1864
1889
  await this.dispatchSessionEvent(agent, {
@@ -577,6 +577,11 @@ export interface AgentSession {
577
577
  getPendingPermissions(): AgentPermissionRequest[];
578
578
  respondToPermission(requestId: string, response: AgentPermissionResponse): Promise<AgentPermissionResult | void>;
579
579
  describePersistence(): AgentPersistenceHandle | null;
580
+ /**
581
+ * Resolve once every foreground turn that predates this call can no longer run or become active.
582
+ * Calling while already idle is a successful no-op. Reject only when foreground ownership is
583
+ * still uncertain.
584
+ */
580
585
  interrupt(): Promise<void>;
581
586
  /** Release live runtime resources without archiving or deleting the durable native session. */
582
587
  close(): Promise<void>;
@@ -127,6 +127,7 @@ export declare class CodexAppServerAgentSession implements AgentSession {
127
127
  private currentThreadId;
128
128
  private currentTurnId;
129
129
  private pendingForegroundTurnIdentification;
130
+ private pendingForegroundStart;
130
131
  private client;
131
132
  private readonly subscribers;
132
133
  private nextTurnOrdinal;
@@ -587,6 +587,11 @@ function isDefinitiveCodexSteerRejection(error) {
587
587
  /^expected active turn id `[^`]+` but found `[^`]+`$/.test(error.message) ||
588
588
  error.message === "active turn uses a different output schema");
589
589
  }
590
+ function isCodexAlreadyIdleInterrupt(error) {
591
+ return (error instanceof CodexAppServerRpcError &&
592
+ error.code === -32600 &&
593
+ error.message === "no active turn to interrupt");
594
+ }
590
595
  const CodexModelListResponseSchema = z.object({
591
596
  data: z
592
597
  .array(z.object({
@@ -2363,6 +2368,7 @@ export class CodexAppServerAgentSession {
2363
2368
  this.currentThreadId = null;
2364
2369
  this.currentTurnId = null;
2365
2370
  this.pendingForegroundTurnIdentification = null;
2371
+ this.pendingForegroundStart = null;
2366
2372
  this.client = null;
2367
2373
  this.subscribers = new Set();
2368
2374
  this.nextTurnOrdinal = 0;
@@ -3056,9 +3062,18 @@ export class CodexAppServerAgentSession {
3056
3062
  });
3057
3063
  }
3058
3064
  async startTurn(prompt, options) {
3059
- if (this.activeForegroundTurnId) {
3065
+ if (this.activeForegroundTurnId || this.pendingForegroundStart) {
3060
3066
  throw new Error("A foreground turn is already active");
3061
3067
  }
3068
+ let resolveStart;
3069
+ const pendingStart = {
3070
+ promise: new Promise((resolve) => {
3071
+ resolveStart = resolve;
3072
+ }),
3073
+ resolve: () => resolveStart(),
3074
+ cancelRequested: false,
3075
+ };
3076
+ this.pendingForegroundStart = pendingStart;
3062
3077
  this.dismissPendingPlanApprovals("Dismissed by a new prompt");
3063
3078
  try {
3064
3079
  await this.connect();
@@ -3099,6 +3114,9 @@ export class CodexAppServerAgentSession {
3099
3114
  hasDeveloperInstructions: turnStart.hasDeveloperInstructions,
3100
3115
  hasCodexConfig: turnStart.hasCodexConfig,
3101
3116
  });
3117
+ if (pendingStart.cancelRequested) {
3118
+ throw new Error("Codex turn start was interrupted before reaching Codex");
3119
+ }
3102
3120
  await this.client.request("turn/start", turnStart.params, TURN_START_TIMEOUT_MS);
3103
3121
  return { turnId };
3104
3122
  }
@@ -3109,6 +3127,12 @@ export class CodexAppServerAgentSession {
3109
3127
  this.activeClientMessageId = null;
3110
3128
  throw error;
3111
3129
  }
3130
+ finally {
3131
+ if (this.pendingForegroundStart === pendingStart) {
3132
+ this.pendingForegroundStart = null;
3133
+ }
3134
+ pendingStart.resolve();
3135
+ }
3112
3136
  }
3113
3137
  async steerActiveTurn(prompt, options) {
3114
3138
  const client = this.client;
@@ -3518,7 +3542,17 @@ export class CodexAppServerAgentSession {
3518
3542
  });
3519
3543
  }
3520
3544
  async interrupt() {
3545
+ const pendingStart = this.pendingForegroundStart;
3546
+ if (pendingStart) {
3547
+ pendingStart.cancelRequested = true;
3548
+ await pendingStart.promise;
3549
+ }
3521
3550
  if (!this.client || !this.currentThreadId) {
3551
+ if (!this.activeForegroundTurnId &&
3552
+ !this.currentTurnId &&
3553
+ !this.pendingForegroundTurnIdentification) {
3554
+ return;
3555
+ }
3522
3556
  throw new Error("Cannot interrupt Codex before the active thread is initialized");
3523
3557
  }
3524
3558
  let turnId = this.currentTurnId;
@@ -3531,13 +3565,28 @@ export class CodexAppServerAgentSession {
3531
3565
  pendingIdentification?.foregroundTurnId === foregroundTurnId) {
3532
3566
  turnId = await pendingIdentification.promise;
3533
3567
  }
3568
+ if (!turnId && !this.activeForegroundTurnId && !this.currentTurnId) {
3569
+ return;
3570
+ }
3534
3571
  if (!turnId || (foregroundTurnId && this.activeForegroundTurnId !== foregroundTurnId)) {
3535
3572
  throw new Error("Cannot interrupt Codex before turn/started identifies the active turn");
3536
3573
  }
3537
- await this.client.request("turn/interrupt", {
3538
- threadId: this.currentThreadId,
3539
- turnId,
3540
- }, INTERRUPT_TIMEOUT_MS);
3574
+ try {
3575
+ await this.client.request("turn/interrupt", {
3576
+ threadId: this.currentThreadId,
3577
+ turnId,
3578
+ }, INTERRUPT_TIMEOUT_MS);
3579
+ }
3580
+ catch (error) {
3581
+ if (!isCodexAlreadyIdleInterrupt(error)) {
3582
+ throw error;
3583
+ }
3584
+ this.activeForegroundTurnId = null;
3585
+ this.activeClientMessageId = null;
3586
+ this.currentTurnId = null;
3587
+ this.pendingForegroundTurnIdentification?.resolve(null);
3588
+ this.pendingForegroundTurnIdentification = null;
3589
+ }
3541
3590
  }
3542
3591
  async close() {
3543
3592
  this.closed = true;
@@ -31,8 +31,6 @@ export declare class ClaudeQuotaProvider implements ProviderUsageFetcher {
31
31
  private scopedLimitsFromResponse;
32
32
  private readCredentials;
33
33
  private callClaudeApi;
34
- private refreshClaudeToken;
35
- private saveClaudeCredentials;
36
34
  }
37
35
  export {};
38
36
  //# sourceMappingURL=claude.d.ts.map
@@ -8,7 +8,6 @@ import { ApiNumberSchema, fetchProviderApi, toneFromUsedPct, unavailableUsage, w
8
8
  const execFileAsync = promisify(execFile);
9
9
  const CLAUDE_KEYCHAIN_TIMEOUT_MS = 2000;
10
10
  const CLAUDE_OAUTH_BETA = "oauth-2025-04-20";
11
- const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
12
11
  const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
13
12
  const ClaudeCredentialsSchema = z.object({
14
13
  claudeAiOauth: z
@@ -51,10 +50,6 @@ const ClaudeUsageResponseSchema = z.object({
51
50
  })
52
51
  .nullish(),
53
52
  });
54
- const ClaudeTokenRefreshSchema = z.object({
55
- access_token: z.string().optional(),
56
- refresh_token: z.string().optional(),
57
- });
58
53
  const SCOPED_WEEKLY_KIND = "weekly_scoped";
59
54
  function buildClaudePlan(subscriptionType, rateLimitTier) {
60
55
  if (!subscriptionType)
@@ -270,26 +265,12 @@ export class ClaudeQuotaProvider {
270
265
  if (!credentials) {
271
266
  return unavailableUsage(this);
272
267
  }
273
- const { oauth, filePath } = credentials;
268
+ const { oauth } = credentials;
274
269
  const plan = buildClaudePlan(oauth.subscriptionType, oauth.rateLimitTier);
275
- let resp = await this.callClaudeApi(oauth.accessToken);
270
+ const resp = await this.callClaudeApi(oauth.accessToken);
276
271
  if (resp === "NEEDS_AUTH") {
277
- if (!filePath || !oauth.refreshToken) {
278
- return unavailableUsage(this);
279
- }
280
- const refreshed = await this.refreshClaudeToken(oauth.refreshToken);
281
- if (!refreshed?.access_token) {
282
- return unavailableUsage(this);
283
- }
284
- await this.saveClaudeCredentials(filePath, {
285
- ...oauth,
286
- accessToken: refreshed.access_token,
287
- refreshToken: refreshed.refresh_token ?? oauth.refreshToken,
288
- });
289
- resp = await this.callClaudeApi(refreshed.access_token);
290
- if (resp === "NEEDS_AUTH") {
291
- return unavailableUsage(this);
292
- }
272
+ // Read-only on credentials; the Claude CLI owns refresh. See docs/providers.md.
273
+ return unavailableUsage(this);
293
274
  }
294
275
  const scoped = reconcileScopedLimits(legacyScopedLimits(resp), this.scopedLimitsFromResponse(resp.limits));
295
276
  const windows = [...unscopedWindows(resp), ...scopedWindows(scoped)];
@@ -353,7 +334,7 @@ export class ClaudeQuotaProvider {
353
334
  const creds = ClaudeCredentialsSchema.parse(JSON.parse(await fs.readFile(credPath, "utf8")));
354
335
  const oauth = creds.claudeAiOauth;
355
336
  if (oauth?.accessToken) {
356
- return { oauth: { ...oauth, accessToken: oauth.accessToken }, filePath: credPath };
337
+ return { oauth: { ...oauth, accessToken: oauth.accessToken } };
357
338
  }
358
339
  }
359
340
  catch {
@@ -364,7 +345,7 @@ export class ClaudeQuotaProvider {
364
345
  const creds = ClaudeCredentialsSchema.safeParse(await this.readKeychainCredentials());
365
346
  const oauth = creds.success ? creds.data.claudeAiOauth : undefined;
366
347
  if (oauth?.accessToken) {
367
- return { oauth: { ...oauth, accessToken: oauth.accessToken }, filePath: null };
348
+ return { oauth: { ...oauth, accessToken: oauth.accessToken } };
368
349
  }
369
350
  }
370
351
  return null;
@@ -383,30 +364,5 @@ export class ClaudeQuotaProvider {
383
364
  throw new Error(`Claude usage API returned ${res.status}`);
384
365
  return ClaudeUsageResponseSchema.parse(await res.json());
385
366
  }
386
- async refreshClaudeToken(refreshToken) {
387
- const res = await fetchProviderApi(this.fetchApi, "https://platform.claude.com/v1/oauth/token", {
388
- method: "POST",
389
- headers: { "Content-Type": "application/json" },
390
- body: JSON.stringify({
391
- grant_type: "refresh_token",
392
- refresh_token: refreshToken,
393
- client_id: CLAUDE_CLIENT_ID,
394
- scope: "user:profile user:inference user:sessions:claude_code user:mcp_servers",
395
- }),
396
- });
397
- if (!res.ok)
398
- return null;
399
- return ClaudeTokenRefreshSchema.parse(await res.json());
400
- }
401
- async saveClaudeCredentials(credPath, oauth) {
402
- try {
403
- const existing = ClaudeCredentialsSchema.parse(JSON.parse(await fs.readFile(credPath, "utf8")));
404
- existing.claudeAiOauth = oauth;
405
- await fs.writeFile(credPath, JSON.stringify(existing, null, 2), { mode: 0o600 });
406
- }
407
- catch {
408
- // Non-fatal; Claude Code can refresh again on its own next time.
409
- }
410
- }
411
367
  }
412
368
  //# sourceMappingURL=claude.js.map
@@ -16,8 +16,6 @@ export declare class CodexQuotaProvider implements ProviderUsageFetcher {
16
16
  private toUsage;
17
17
  private readCodexAuth;
18
18
  private callCodexApi;
19
- private refreshCodexToken;
20
- private saveCodexAuth;
21
19
  }
22
20
  export {};
23
21
  //# sourceMappingURL=codex.d.ts.map
@@ -3,7 +3,6 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { ApiNumberSchema, balanceToneFromRemaining, toneFromUsedPct, fetchProviderApi, unavailableUsage, windowFromUsedPct, } from "../usage.js";
6
- const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
7
6
  const CodexAuthSchema = z.object({
8
7
  tokens: z
9
8
  .object({
@@ -39,10 +38,6 @@ const CodexUsageResponseSchema = z.object({
39
38
  })
40
39
  .nullish(),
41
40
  });
42
- const CodexTokenRefreshSchema = z.object({
43
- access_token: z.string().optional(),
44
- refresh_token: z.string().optional(),
45
- });
46
41
  function codexWindow(window) {
47
42
  if (!window)
48
43
  return null;
@@ -59,27 +54,16 @@ export class CodexQuotaProvider {
59
54
  this.fetchApi = options.fetch ?? fetch;
60
55
  }
61
56
  async fetchUsage() {
62
- const authRecord = await this.readCodexAuth();
63
- const auth = authRecord?.auth;
57
+ const auth = await this.readCodexAuth();
64
58
  const accessToken = auth?.tokens?.access_token;
65
- if (!authRecord || !auth || !accessToken) {
59
+ if (!auth || !accessToken) {
66
60
  return unavailableUsage(this);
67
61
  }
68
- const { refresh_token, account_id } = auth.tokens ?? {};
69
- let resp = await this.callCodexApi(accessToken, account_id);
62
+ const { account_id } = auth.tokens ?? {};
63
+ const resp = await this.callCodexApi(accessToken, account_id);
70
64
  if (resp === "NEEDS_AUTH") {
71
- if (!refresh_token) {
72
- return unavailableUsage(this);
73
- }
74
- const refreshed = await this.refreshCodexToken(refresh_token);
75
- if (!refreshed?.access_token) {
76
- return unavailableUsage(this);
77
- }
78
- await this.saveCodexAuth(authRecord.path, auth, refreshed);
79
- resp = await this.callCodexApi(refreshed.access_token, account_id);
80
- if (resp === "NEEDS_AUTH") {
81
- return unavailableUsage(this);
82
- }
65
+ // Read-only on credentials; the Codex CLI owns refresh. See docs/providers.md.
66
+ return unavailableUsage(this);
83
67
  }
84
68
  return this.toUsage(resp);
85
69
  }
@@ -148,7 +132,7 @@ export class CodexQuotaProvider {
148
132
  try {
149
133
  const auth = CodexAuthSchema.parse(JSON.parse(await fs.readFile(path, "utf8")));
150
134
  if (auth.tokens?.access_token)
151
- return { auth, path };
135
+ return auth;
152
136
  }
153
137
  catch {
154
138
  continue;
@@ -176,36 +160,5 @@ export class CodexQuotaProvider {
176
160
  return "NEEDS_AUTH";
177
161
  return CodexUsageResponseSchema.parse(JSON.parse(text));
178
162
  }
179
- async refreshCodexToken(refreshToken) {
180
- const params = new URLSearchParams({
181
- grant_type: "refresh_token",
182
- client_id: CODEX_CLIENT_ID,
183
- refresh_token: refreshToken,
184
- });
185
- const res = await fetchProviderApi(this.fetchApi, "https://auth.openai.com/oauth/token", {
186
- method: "POST",
187
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
188
- body: params.toString(),
189
- });
190
- if (!res.ok)
191
- return null;
192
- return CodexTokenRefreshSchema.parse(await res.json());
193
- }
194
- async saveCodexAuth(authPath, original, refreshed) {
195
- try {
196
- const updated = {
197
- ...original,
198
- tokens: {
199
- ...original.tokens,
200
- access_token: refreshed.access_token ?? original.tokens?.access_token,
201
- refresh_token: refreshed.refresh_token ?? original.tokens?.refresh_token,
202
- },
203
- };
204
- await fs.writeFile(authPath, JSON.stringify(updated, null, 2), { mode: 0o600 });
205
- }
206
- catch {
207
- // Non-fatal; the next call can refresh again.
208
- }
209
- }
210
163
  }
211
164
  //# sourceMappingURL=codex.js.map
@@ -14,11 +14,7 @@ export declare class KimiQuotaProvider implements ProviderUsageFetcher {
14
14
  private readonly homeDir?;
15
15
  constructor(options: KimiQuotaProviderOptions);
16
16
  fetchUsage(): Promise<ProviderUsage>;
17
- private fetchUsageResponse;
18
17
  private callUsageApi;
19
- private refreshToken;
20
- private saveRefreshedCredentials;
21
- private credentialsUnchangedSince;
22
18
  private readCredentials;
23
19
  private credentialPaths;
24
20
  private readCredentialFile;
@@ -1,12 +1,9 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { existsSync, promises as fs } from "node:fs";
3
2
  import { homedir } from "node:os";
4
- import { basename, dirname, join } from "node:path";
3
+ import { join } from "node:path";
5
4
  import { z } from "zod";
6
5
  import { ApiNumberSchema, ApiOptionalStringSchema, fetchProviderApi, toneFromUsedPct, unavailableUsage, windowFromUsedPct, } from "../usage.js";
7
- const KIMI_CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
8
6
  const KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
9
- const KIMI_TOKEN_URL = "https://auth.kimi.com/api/oauth/token";
10
7
  const KimiUsageFieldsSchema = z.object({
11
8
  limit: ApiOptionalStringSchema,
12
9
  used: ApiOptionalStringSchema,
@@ -191,13 +188,6 @@ const KimiAuthSchema = z
191
188
  token_type: z.string().nullish(),
192
189
  })
193
190
  .passthrough();
194
- const KimiTokenRefreshSchema = z.object({
195
- access_token: z.string(),
196
- refresh_token: z.string().nullish(),
197
- expires_in: ApiNumberSchema.nullish(),
198
- scope: z.string().nullish(),
199
- token_type: z.string().nullish(),
200
- });
201
191
  export class KimiQuotaProvider {
202
192
  constructor(options) {
203
193
  this.providerId = "kimi";
@@ -210,8 +200,9 @@ export class KimiQuotaProvider {
210
200
  const credentials = await this.readCredentials();
211
201
  if (!credentials)
212
202
  return unavailableUsage(this);
213
- const res = await this.fetchUsageResponse(credentials);
203
+ const res = await this.callUsageApi(credentials.access_token);
214
204
  if (!res.ok) {
205
+ // Read-only on credentials; the Kimi CLI owns refresh. See docs/providers.md.
215
206
  this.logger.debug({ status: res.status }, "Kimi usage fetch failed");
216
207
  return unavailableUsage(this);
217
208
  }
@@ -227,24 +218,6 @@ export class KimiQuotaProvider {
227
218
  error: null,
228
219
  };
229
220
  }
230
- async fetchUsageResponse(record) {
231
- const { credentials, filePath } = record;
232
- const res = await this.callUsageApi(credentials.access_token);
233
- if (res.status !== 401 || !filePath || !credentials.refresh_token)
234
- return res;
235
- const latest = await this.readCredentialFile(filePath);
236
- const retried = latest?.access_token && latest.access_token !== credentials.access_token
237
- ? await this.callUsageApi(latest.access_token)
238
- : res;
239
- if (retried.status !== 401)
240
- return retried;
241
- const refreshTokenUsed = latest?.refresh_token ?? credentials.refresh_token;
242
- const refreshed = await this.refreshToken(refreshTokenUsed);
243
- if (!refreshed)
244
- return retried;
245
- await this.saveRefreshedCredentials({ filePath, refreshTokenUsed, refreshed });
246
- return this.callUsageApi(refreshed.access_token);
247
- }
248
221
  async callUsageApi(token) {
249
222
  return fetchProviderApi(this.fetchApi, KIMI_USAGE_URL, {
250
223
  headers: {
@@ -253,82 +226,15 @@ export class KimiQuotaProvider {
253
226
  },
254
227
  });
255
228
  }
256
- async refreshToken(refreshToken) {
257
- const body = new URLSearchParams({
258
- client_id: KIMI_CLIENT_ID,
259
- grant_type: "refresh_token",
260
- refresh_token: refreshToken,
261
- });
262
- const res = await fetchProviderApi(this.fetchApi, KIMI_TOKEN_URL, {
263
- method: "POST",
264
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
265
- body,
266
- });
267
- if (!res.ok) {
268
- this.logger.debug({ status: res.status }, "Kimi token refresh failed");
269
- return null;
270
- }
271
- const parsed = KimiTokenRefreshSchema.safeParse(await res.json());
272
- return parsed.success ? parsed.data : null;
273
- }
274
- async saveRefreshedCredentials(options) {
275
- const { filePath, refreshTokenUsed, refreshed } = options;
276
- const existing = await this.readCredentialFile(filePath);
277
- if (!existing)
278
- return;
279
- // The Kimi CLI owns this file too. If its refresh token no longer matches the one we
280
- // just spent, the CLI rotated the credentials while our refresh was in flight and its
281
- // copy is newer than ours — writing our merge would strand the CLI on dead tokens.
282
- if (existing.refresh_token !== refreshTokenUsed) {
283
- this.logger.debug("Kimi credentials rotated during refresh; keeping the file on disk");
284
- return;
285
- }
286
- const expiresIn = refreshed.expires_in ?? existing.expires_in;
287
- const merged = {
288
- ...existing,
289
- access_token: refreshed.access_token,
290
- refresh_token: refreshed.refresh_token ?? existing.refresh_token,
291
- expires_in: expiresIn,
292
- expires_at: expiresIn == null ? existing.expires_at : Math.floor(Date.now() / 1000) + expiresIn,
293
- scope: refreshed.scope ?? existing.scope,
294
- token_type: refreshed.token_type ?? existing.token_type,
295
- };
296
- const tempPath = join(dirname(filePath), `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
297
- try {
298
- await fs.writeFile(tempPath, JSON.stringify(merged, null, 2), { mode: 0o600 });
299
- // writeFile above is async I/O that gives the CLI a window to rotate the file on
300
- // its own. Re-read right before the rename and confirm nothing changed since the
301
- // guard above — the check-then-write gap this closes is what the file-level guard
302
- // couldn't cover, since it ran before the write, not right before the replace.
303
- const beforeRename = await this.readCredentialFile(filePath);
304
- if (!this.credentialsUnchangedSince(existing, beforeRename)) {
305
- this.logger.debug("Kimi credentials rotated during refresh; discarding stale merge");
306
- await fs.rm(tempPath, { force: true }).catch(() => undefined);
307
- return;
308
- }
309
- await fs.rename(tempPath, filePath);
310
- }
311
- catch (error) {
312
- this.logger.warn({ err: error }, "Failed to persist refreshed Kimi credentials");
313
- await fs.rm(tempPath, { force: true }).catch(() => undefined);
314
- }
315
- }
316
- credentialsUnchangedSince(baseline, candidate) {
317
- return (candidate?.access_token === baseline.access_token &&
318
- candidate?.refresh_token === baseline.refresh_token);
319
- }
320
229
  async readCredentials() {
321
230
  const environmentToken = process.env["KIMI_TOKEN"] || process.env["KIMI_API_KEY"];
322
231
  if (environmentToken) {
323
- return { credentials: { access_token: environmentToken }, filePath: null };
232
+ return { access_token: environmentToken };
324
233
  }
325
234
  for (const path of this.credentialPaths()) {
326
235
  const credentials = await this.readCredentialFile(path);
327
236
  if (credentials?.access_token) {
328
- return {
329
- credentials: { ...credentials, access_token: credentials.access_token },
330
- filePath: path,
331
- };
237
+ return { ...credentials, access_token: credentials.access_token };
332
238
  }
333
239
  }
334
240
  return null;
@@ -1 +1 @@
1
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},4941,[4284,4285]);
1
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},4943,[4284,4285]);
@@ -1 +1 @@
1
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},4940,[4278]);
1
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},4942,[4278]);