@adecore/agents 0.0.1 → 0.17.0-beta.1
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/LICENSE +110 -0
- package/README.md +33 -0
- package/dist/async.d.ts +2 -0
- package/dist/async.js +31 -0
- package/dist/chat/attachment-store.d.ts +10 -0
- package/dist/chat/attachment-store.js +93 -0
- package/dist/chat/backend.d.ts +214 -0
- package/dist/chat/backend.js +1 -0
- package/dist/chat/background-work.d.ts +8 -0
- package/dist/chat/background-work.js +33 -0
- package/dist/chat/bookmark-store.d.ts +25 -0
- package/dist/chat/bookmark-store.js +141 -0
- package/dist/chat/chat-core.d.ts +311 -0
- package/dist/chat/chat-core.js +1025 -0
- package/dist/chat/chat-log.d.ts +33 -0
- package/dist/chat/chat-log.js +163 -0
- package/dist/chat/chat-process.d.ts +46 -0
- package/dist/chat/chat-process.js +190 -0
- package/dist/chat/chat-session.d.ts +209 -0
- package/dist/chat/chat-session.js +1511 -0
- package/dist/chat/chat-store.d.ts +37 -0
- package/dist/chat/chat-store.js +202 -0
- package/dist/chat/chat-title.d.ts +10 -0
- package/dist/chat/chat-title.js +74 -0
- package/dist/chat/claude-backend.d.ts +35 -0
- package/dist/chat/claude-backend.js +214 -0
- package/dist/chat/claude-protocol.d.ts +41 -0
- package/dist/chat/claude-protocol.js +642 -0
- package/dist/chat/claude-title.d.ts +11 -0
- package/dist/chat/claude-title.js +97 -0
- package/dist/chat/claude-transcript.d.ts +28 -0
- package/dist/chat/claude-transcript.js +211 -0
- package/dist/chat/codex-backend.d.ts +51 -0
- package/dist/chat/codex-backend.js +364 -0
- package/dist/chat/codex-protocol.d.ts +56 -0
- package/dist/chat/codex-protocol.js +767 -0
- package/dist/chat/codex-thread.d.ts +32 -0
- package/dist/chat/codex-thread.js +131 -0
- package/dist/chat/codex-transport.d.ts +42 -0
- package/dist/chat/codex-transport.js +188 -0
- package/dist/chat/composer-preferences.d.ts +15 -0
- package/dist/chat/composer-preferences.js +46 -0
- package/dist/chat/context-breakdown.d.ts +2 -0
- package/dist/chat/context-breakdown.js +74 -0
- package/dist/chat/delta-coalescer.d.ts +12 -0
- package/dist/chat/delta-coalescer.js +55 -0
- package/dist/chat/errors.d.ts +4 -0
- package/dist/chat/errors.js +4 -0
- package/dist/chat/fake-claude.d.ts +3 -0
- package/dist/chat/fake-claude.js +982 -0
- package/dist/chat/fake-cli.d.ts +25 -0
- package/dist/chat/fake-cli.js +157 -0
- package/dist/chat/fake-codex.d.ts +10 -0
- package/dist/chat/fake-codex.js +502 -0
- package/dist/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
- package/dist/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
- package/dist/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
- package/dist/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
- package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
- package/dist/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
- package/dist/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
- package/dist/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
- package/dist/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
- package/dist/chat/input.d.ts +19 -0
- package/dist/chat/input.js +71 -0
- package/dist/chat/limit-resume.d.ts +11 -0
- package/dist/chat/limit-resume.js +68 -0
- package/dist/chat/projector.d.ts +60 -0
- package/dist/chat/projector.js +860 -0
- package/dist/chat/request-summary.d.ts +3 -0
- package/dist/chat/request-summary.js +143 -0
- package/dist/chat/subagent-projection.d.ts +8 -0
- package/dist/chat/subagent-projection.js +40 -0
- package/dist/chat/subagent-reader.d.ts +66 -0
- package/dist/chat/subagent-reader.js +396 -0
- package/dist/chat/subagent-settlement.d.ts +6 -0
- package/dist/chat/subagent-settlement.js +103 -0
- package/dist/chat/thread.d.ts +30 -0
- package/dist/chat/thread.js +147 -0
- package/dist/chat/wake-chat.d.ts +20 -0
- package/dist/chat/wake-chat.js +27 -0
- package/dist/client-sinks.d.ts +9 -0
- package/dist/client-sinks.js +34 -0
- package/dist/coded-error.d.ts +5 -0
- package/dist/coded-error.js +17 -0
- package/dist/context/argv.d.ts +11 -0
- package/dist/context/argv.js +58 -0
- package/dist/context/refusal.d.ts +9 -0
- package/dist/context/refusal.js +38 -0
- package/dist/context/verb.d.ts +85 -0
- package/dist/context/verb.js +233 -0
- package/dist/error-text.d.ts +5 -0
- package/dist/error-text.js +24 -0
- package/dist/events.d.ts +12 -0
- package/dist/events.js +1 -0
- package/dist/fs.d.ts +9 -0
- package/dist/fs.js +107 -0
- package/dist/host/agent-host.d.ts +28 -0
- package/dist/host/agent-host.js +105 -0
- package/dist/host/environment.d.ts +7 -0
- package/dist/host/environment.js +22 -0
- package/dist/host/handlers.d.ts +30 -0
- package/dist/host/handlers.js +111 -0
- package/dist/host/memory-port.d.ts +2 -0
- package/dist/host/memory-port.js +25 -0
- package/dist/host/wiring.d.ts +40 -0
- package/dist/host/wiring.js +120 -0
- package/dist/lineage.d.ts +44 -0
- package/dist/lineage.js +134 -0
- package/dist/messages/deliver-message.d.ts +21 -0
- package/dist/messages/deliver-message.js +42 -0
- package/dist/messages/deliver-notice.d.ts +25 -0
- package/dist/messages/deliver-notice.js +75 -0
- package/dist/messages/notice-notes.d.ts +15 -0
- package/dist/messages/notice-notes.js +26 -0
- package/dist/messages/notice-store.d.ts +30 -0
- package/dist/messages/notice-store.js +120 -0
- package/dist/modes.d.ts +11 -0
- package/dist/modes.js +28 -0
- package/dist/outbox/manual-clock.d.ts +10 -0
- package/dist/outbox/manual-clock.js +25 -0
- package/dist/outbox/outbox-worker.d.ts +44 -0
- package/dist/outbox/outbox-worker.js +173 -0
- package/dist/outbox/outbox.d.ts +37 -0
- package/dist/outbox/outbox.js +76 -0
- package/dist/providers/accounts/accounts.d.ts +20 -0
- package/dist/providers/accounts/accounts.js +158 -0
- package/dist/providers/accounts/launch.d.ts +17 -0
- package/dist/providers/accounts/launch.js +30 -0
- package/dist/providers/accounts/service.d.ts +87 -0
- package/dist/providers/accounts/service.js +538 -0
- package/dist/providers/accounts/shadow-home.d.ts +8 -0
- package/dist/providers/accounts/shadow-home.js +70 -0
- package/dist/providers/accounts/status.d.ts +12 -0
- package/dist/providers/accounts/status.js +85 -0
- package/dist/providers/accounts/store.d.ts +14 -0
- package/dist/providers/accounts/store.js +112 -0
- package/dist/providers/accounts/test-accounts.d.ts +12 -0
- package/dist/providers/accounts/test-accounts.js +38 -0
- package/dist/providers/accounts/variables.d.ts +27 -0
- package/dist/providers/accounts/variables.js +94 -0
- package/dist/providers/catalog.d.ts +17 -0
- package/dist/providers/catalog.js +76 -0
- package/dist/providers/claude-models.json +114 -0
- package/dist/providers/claude-provider.d.ts +4 -0
- package/dist/providers/claude-provider.js +29 -0
- package/dist/providers/claude.d.ts +15 -0
- package/dist/providers/claude.js +105 -0
- package/dist/providers/codex-models.json +135 -0
- package/dist/providers/codex-provider.d.ts +4 -0
- package/dist/providers/codex-provider.js +27 -0
- package/dist/providers/codex-rules.d.ts +9 -0
- package/dist/providers/codex-rules.js +35 -0
- package/dist/providers/codex.d.ts +14 -0
- package/dist/providers/codex.js +52 -0
- package/dist/providers/detect.d.ts +6 -0
- package/dist/providers/detect.js +30 -0
- package/dist/providers/provider.d.ts +25 -0
- package/dist/providers/provider.js +1 -0
- package/dist/providers/registry.d.ts +28 -0
- package/dist/providers/registry.js +118 -0
- package/dist/record-directory.d.ts +27 -0
- package/dist/record-directory.js +114 -0
- package/dist/run-process.d.ts +13 -0
- package/dist/run-process.js +42 -0
- package/dist/serializer.d.ts +9 -0
- package/dist/serializer.js +34 -0
- package/dist/skills.d.ts +21 -0
- package/dist/skills.js +174 -0
- package/dist/tasks/background-limit.d.ts +16 -0
- package/dist/tasks/background-limit.js +55 -0
- package/dist/tasks/end-children.d.ts +34 -0
- package/dist/tasks/end-children.js +53 -0
- package/dist/tasks/give-task.d.ts +14 -0
- package/dist/tasks/give-task.js +33 -0
- package/dist/tasks/task-coordinator.d.ts +52 -0
- package/dist/tasks/task-coordinator.js +323 -0
- package/dist/tasks/task-store.d.ts +49 -0
- package/dist/tasks/task-store.js +197 -0
- package/dist/tasks/task-work.d.ts +47 -0
- package/dist/tasks/task-work.js +28 -0
- package/dist/tasks/waiting-child.d.ts +51 -0
- package/dist/tasks/waiting-child.js +143 -0
- package/dist/tasks/wake-parent.d.ts +19 -0
- package/dist/tasks/wake-parent.js +109 -0
- package/dist/tasks/wiring.d.ts +51 -0
- package/dist/tasks/wiring.js +151 -0
- package/dist/title-file.d.ts +2 -0
- package/dist/title-file.js +55 -0
- package/dist/usage/accounts.d.ts +15 -0
- package/dist/usage/accounts.js +64 -0
- package/dist/usage/aggregate.d.ts +12 -0
- package/dist/usage/aggregate.js +154 -0
- package/dist/usage/exchange.d.ts +16 -0
- package/dist/usage/exchange.js +84 -0
- package/dist/usage/index-file.d.ts +15 -0
- package/dist/usage/index-file.js +123 -0
- package/dist/usage/limits/monitor.d.ts +50 -0
- package/dist/usage/limits/monitor.js +179 -0
- package/dist/usage/limits/normalize.d.ts +21 -0
- package/dist/usage/limits/normalize.js +160 -0
- package/dist/usage/limits/probe.d.ts +9 -0
- package/dist/usage/limits/probe.js +113 -0
- package/dist/usage/prices-snapshot.json +1071 -0
- package/dist/usage/pricing.d.ts +34 -0
- package/dist/usage/pricing.js +186 -0
- package/dist/usage/projects.d.ts +17 -0
- package/dist/usage/projects.js +49 -0
- package/dist/usage/readers/claude.d.ts +3 -0
- package/dist/usage/readers/claude.js +59 -0
- package/dist/usage/readers/codex.d.ts +23 -0
- package/dist/usage/readers/codex.js +155 -0
- package/dist/usage/record.d.ts +15 -0
- package/dist/usage/record.js +39 -0
- package/dist/usage/roots.d.ts +12 -0
- package/dist/usage/roots.js +29 -0
- package/dist/usage/scanner.d.ts +30 -0
- package/dist/usage/scanner.js +234 -0
- package/dist/usage/usage-service.d.ts +35 -0
- package/dist/usage/usage-service.js +130 -0
- package/dist/watch-seam.d.ts +29 -0
- package/dist/watch-seam.js +78 -0
- package/dist/watch-test-helpers.d.ts +28 -0
- package/dist/watch-test-helpers.js +86 -0
- package/package.json +42 -3
- package/src/async.ts +40 -0
- package/src/chat/attachment-store.ts +108 -0
- package/src/chat/backend.ts +199 -0
- package/src/chat/background-work.ts +42 -0
- package/src/chat/bookmark-store.ts +166 -0
- package/src/chat/chat-core.ts +1228 -0
- package/src/chat/chat-log.ts +187 -0
- package/src/chat/chat-process.ts +237 -0
- package/src/chat/chat-session.ts +1723 -0
- package/src/chat/chat-store.ts +261 -0
- package/src/chat/chat-title.ts +90 -0
- package/src/chat/claude-backend.ts +235 -0
- package/src/chat/claude-protocol.ts +684 -0
- package/src/chat/claude-title.ts +106 -0
- package/src/chat/claude-transcript.ts +233 -0
- package/src/chat/codex-backend.ts +397 -0
- package/src/chat/codex-protocol.ts +872 -0
- package/src/chat/codex-thread.ts +162 -0
- package/src/chat/codex-transport.ts +224 -0
- package/src/chat/composer-preferences.ts +67 -0
- package/src/chat/context-breakdown.ts +80 -0
- package/src/chat/delta-coalescer.ts +62 -0
- package/src/chat/errors.ts +25 -0
- package/src/chat/fake-claude.ts +1006 -0
- package/src/chat/fake-cli.ts +199 -0
- package/src/chat/fake-codex.ts +534 -0
- package/src/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
- package/src/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
- package/src/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
- package/src/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
- package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
- package/src/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
- package/src/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
- package/src/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
- package/src/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
- package/src/chat/input.ts +89 -0
- package/src/chat/limit-resume.ts +74 -0
- package/src/chat/projector.ts +969 -0
- package/src/chat/request-summary.ts +168 -0
- package/src/chat/subagent-projection.ts +45 -0
- package/src/chat/subagent-reader.ts +469 -0
- package/src/chat/subagent-settlement.ts +111 -0
- package/src/chat/thread.ts +167 -0
- package/src/chat/wake-chat.ts +45 -0
- package/src/client-sinks.ts +39 -0
- package/src/coded-error.ts +18 -0
- package/src/context/argv.ts +61 -0
- package/src/context/refusal.ts +50 -0
- package/src/context/verb.ts +392 -0
- package/src/error-text.ts +28 -0
- package/src/events.ts +9 -0
- package/src/fs.ts +118 -0
- package/src/host/agent-host.ts +124 -0
- package/src/host/environment.ts +31 -0
- package/src/host/handlers.ts +142 -0
- package/src/host/memory-port.ts +27 -0
- package/src/host/wiring.ts +164 -0
- package/src/lineage.ts +159 -0
- package/src/messages/deliver-message.ts +61 -0
- package/src/messages/deliver-notice.ts +120 -0
- package/src/messages/notice-notes.ts +34 -0
- package/src/messages/notice-store.ts +142 -0
- package/src/modes.ts +38 -0
- package/src/outbox/manual-clock.ts +31 -0
- package/src/outbox/outbox-worker.ts +214 -0
- package/src/outbox/outbox.ts +106 -0
- package/src/providers/accounts/accounts.ts +192 -0
- package/src/providers/accounts/launch.ts +55 -0
- package/src/providers/accounts/service.ts +632 -0
- package/src/providers/accounts/shadow-home.ts +85 -0
- package/src/providers/accounts/status.ts +106 -0
- package/src/providers/accounts/store.ts +158 -0
- package/src/providers/accounts/test-accounts.ts +48 -0
- package/src/providers/accounts/variables.ts +132 -0
- package/src/providers/catalog.ts +90 -0
- package/src/providers/claude-models.json +114 -0
- package/src/providers/claude-provider.ts +32 -0
- package/src/providers/claude.ts +123 -0
- package/src/providers/codex-models.json +135 -0
- package/src/providers/codex-provider.ts +30 -0
- package/src/providers/codex-rules.ts +54 -0
- package/src/providers/codex.ts +69 -0
- package/src/providers/detect.ts +40 -0
- package/src/providers/provider.ts +46 -0
- package/src/providers/registry.ts +150 -0
- package/src/record-directory.ts +136 -0
- package/src/run-process.ts +61 -0
- package/src/serializer.ts +41 -0
- package/src/skills.ts +199 -0
- package/src/tasks/background-limit.ts +72 -0
- package/src/tasks/end-children.ts +100 -0
- package/src/tasks/give-task.ts +52 -0
- package/src/tasks/task-coordinator.ts +384 -0
- package/src/tasks/task-store.ts +236 -0
- package/src/tasks/task-work.ts +62 -0
- package/src/tasks/waiting-child.ts +203 -0
- package/src/tasks/wake-parent.ts +142 -0
- package/src/tasks/wiring.ts +231 -0
- package/src/title-file.ts +57 -0
- package/src/usage/accounts.ts +86 -0
- package/src/usage/aggregate.ts +220 -0
- package/src/usage/exchange.ts +93 -0
- package/src/usage/index-file.ts +178 -0
- package/src/usage/limits/monitor.ts +239 -0
- package/src/usage/limits/normalize.ts +188 -0
- package/src/usage/limits/probe.ts +120 -0
- package/src/usage/prices-snapshot.json +1071 -0
- package/src/usage/pricing.ts +236 -0
- package/src/usage/projects.ts +65 -0
- package/src/usage/readers/claude.ts +60 -0
- package/src/usage/readers/codex.ts +196 -0
- package/src/usage/record.ts +60 -0
- package/src/usage/roots.ts +39 -0
- package/src/usage/scanner.ts +265 -0
- package/src/usage/usage-service.ts +161 -0
- package/src/watch-seam.ts +111 -0
- package/src/watch-test-helpers.ts +102 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { UsageLimitsProvider, UsageProvider, UsageWindow } from '@adecore/agent-contracts';
|
|
2
|
+
|
|
3
|
+
/* Everything a full read of a provider says about its plan, ready to be published as it is. */
|
|
4
|
+
export interface ProviderReading {
|
|
5
|
+
plan: string | null;
|
|
6
|
+
windows: UsageWindow[];
|
|
7
|
+
cost: { sessionUsd: number } | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* What a running turn reports in passing: a few windows, sometimes without their reset or duration. */
|
|
11
|
+
export interface LimitsUpdate {
|
|
12
|
+
kind: UsageProvider;
|
|
13
|
+
// The account of the chat whose turn reported it; absent is the default account.
|
|
14
|
+
account?: string;
|
|
15
|
+
plan?: string | null;
|
|
16
|
+
windows: Partial<UsageWindow>[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const MINUTE_MS = 60_000;
|
|
20
|
+
const FIVE_HOURS_MS = 5 * 60 * MINUTE_MS;
|
|
21
|
+
const WEEK_MS = 7 * 24 * 60 * MINUTE_MS;
|
|
22
|
+
const MONTH_MS = 30 * 24 * 60 * MINUTE_MS;
|
|
23
|
+
|
|
24
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function num(value: unknown): number | null {
|
|
29
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fraction(value: number): number {
|
|
33
|
+
return Math.min(1, Math.max(0, value));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* A window is named after how long it lasts, never after the position it came in. */
|
|
37
|
+
export function kindOfDuration(durationMs: number | null): UsageWindow['kind'] {
|
|
38
|
+
if (durationMs === null) {
|
|
39
|
+
return 'other';
|
|
40
|
+
}
|
|
41
|
+
if (durationMs >= MONTH_MS) {
|
|
42
|
+
return 'monthly';
|
|
43
|
+
}
|
|
44
|
+
if (durationMs >= WEEK_MS) {
|
|
45
|
+
return 'weekly';
|
|
46
|
+
}
|
|
47
|
+
return 'session';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const CLAUDE_WINDOWS: { key: string; id: string; label: string; kind: UsageWindow['kind']; durationMs: number }[] = [
|
|
51
|
+
{ key: 'five_hour', id: 'five_hour', label: 'Session', kind: 'session', durationMs: FIVE_HOURS_MS },
|
|
52
|
+
{ key: 'seven_day', id: 'seven_day', label: 'Weekly', kind: 'weekly', durationMs: WEEK_MS },
|
|
53
|
+
{ key: 'seven_day_opus', id: 'seven_day_opus', label: 'Weekly · Opus', kind: 'weekly', durationMs: WEEK_MS },
|
|
54
|
+
{ key: 'seven_day_sonnet', id: 'seven_day_sonnet', label: 'Weekly · Sonnet', kind: 'weekly', durationMs: WEEK_MS }
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function slug(value: string): string {
|
|
58
|
+
return value
|
|
59
|
+
.toLowerCase()
|
|
60
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
61
|
+
.replace(/^_|_$/g, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* What `get_usage` answers. Claude reports a percent and an ISO moment there, while the event a
|
|
66
|
+
* running turn sends reports a fraction and epoch seconds; both are turned into a fraction and epoch
|
|
67
|
+
* milliseconds here, so nothing past this file has to remember which of the two it is holding.
|
|
68
|
+
*/
|
|
69
|
+
export function readClaudeUsage(response: unknown): ProviderReading | { unavailable: UsageLimitsProvider['unavailable'] } {
|
|
70
|
+
if (!isRecord(response)) {
|
|
71
|
+
return { unavailable: { reason: 'failed', message: 'Claude Code returned no readable usage' } };
|
|
72
|
+
}
|
|
73
|
+
const plan = typeof response.subscription_type === 'string' ? response.subscription_type : null;
|
|
74
|
+
const limits = isRecord(response.rate_limits) ? response.rate_limits : null;
|
|
75
|
+
if (response.rate_limits_available === false || limits === null) {
|
|
76
|
+
// An API key, Bedrock or Vertex account has no plan windows at all, which is not a failure.
|
|
77
|
+
return { unavailable: { reason: 'no-subscription', message: null } };
|
|
78
|
+
}
|
|
79
|
+
const windows: UsageWindow[] = [];
|
|
80
|
+
const push = (id: string, label: string, kind: UsageWindow['kind'], durationMs: number, entry: unknown): void => {
|
|
81
|
+
const window = isRecord(entry) ? entry : null;
|
|
82
|
+
const utilization = window === null ? null : num(window.utilization);
|
|
83
|
+
if (utilization === null) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const resetsAt = typeof window?.resets_at === 'string' ? Date.parse(window.resets_at) : Number.NaN;
|
|
87
|
+
windows.push({ id, label, kind, used: fraction(utilization / 100), resetsAt: Number.isNaN(resetsAt) ? null : resetsAt, durationMs });
|
|
88
|
+
};
|
|
89
|
+
for (const known of CLAUDE_WINDOWS) {
|
|
90
|
+
push(known.id, known.label, known.kind, known.durationMs, limits[known.key]);
|
|
91
|
+
}
|
|
92
|
+
for (const entry of Array.isArray(limits.model_scoped) ? limits.model_scoped : []) {
|
|
93
|
+
const name = isRecord(entry) && typeof entry.display_name === 'string' ? entry.display_name : null;
|
|
94
|
+
if (name !== null) {
|
|
95
|
+
push(`seven_day_${slug(name)}`, `Weekly · ${name}`, 'weekly', WEEK_MS, entry);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const session = isRecord(response.session) ? response.session : null;
|
|
99
|
+
const sessionUsd = session === null ? null : num(session.total_cost_usd);
|
|
100
|
+
return { plan, windows, cost: sessionUsd === null ? null : { sessionUsd } };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* The `rate_limit_event` a turn streams: one window, a fraction, and seconds where the read had ISO. */
|
|
104
|
+
export function readClaudeEvent(info: unknown): LimitsUpdate | null {
|
|
105
|
+
const event = isRecord(info) ? info : null;
|
|
106
|
+
const type = event !== null && typeof event.rateLimitType === 'string' ? event.rateLimitType : null;
|
|
107
|
+
const utilization = event === null ? null : num(event.utilization);
|
|
108
|
+
if (type === null || utilization === null) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const known = CLAUDE_WINDOWS.find((window) => window.key === type);
|
|
112
|
+
const resetsAt = num(event?.resetsAt);
|
|
113
|
+
return {
|
|
114
|
+
kind: 'claude',
|
|
115
|
+
windows: [
|
|
116
|
+
{
|
|
117
|
+
id: known?.id ?? type,
|
|
118
|
+
...(known ? { label: known.label, kind: known.kind, durationMs: known.durationMs } : {}),
|
|
119
|
+
used: fraction(utilization),
|
|
120
|
+
...(resetsAt === null ? {} : { resetsAt: Math.round(resetsAt * 1000) })
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* A Codex rate limit snapshot, from a read or from the notification a turn sends. Only the main
|
|
128
|
+
* allowance counts: the other ids are one model's own budget and read as a second plan.
|
|
129
|
+
*/
|
|
130
|
+
export function readCodexLimits(snapshot: unknown): ProviderReading | null {
|
|
131
|
+
const limits = isRecord(snapshot) ? snapshot : null;
|
|
132
|
+
if (limits === null || (typeof limits.limitId === 'string' && limits.limitId !== 'codex')) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const plan = typeof limits.planType === 'string' ? limits.planType : null;
|
|
136
|
+
const windows: UsageWindow[] = [];
|
|
137
|
+
for (const [position, entry] of [
|
|
138
|
+
['primary', limits.primary],
|
|
139
|
+
['secondary', limits.secondary]
|
|
140
|
+
] as const) {
|
|
141
|
+
const window = isRecord(entry) ? entry : null;
|
|
142
|
+
const usedPercent = window === null ? null : num(window.usedPercent);
|
|
143
|
+
if (usedPercent === null) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const minutes = num(window?.windowDurationMins);
|
|
147
|
+
const durationMs = minutes === null ? null : Math.round(minutes * MINUTE_MS);
|
|
148
|
+
const kind = kindOfDuration(durationMs);
|
|
149
|
+
const resetsAt = num(window?.resetsAt);
|
|
150
|
+
windows.push({
|
|
151
|
+
id: position,
|
|
152
|
+
kind,
|
|
153
|
+
label: kind === 'session' ? 'Session' : kind === 'weekly' ? 'Weekly' : kind === 'monthly' ? 'Monthly' : 'Usage',
|
|
154
|
+
used: fraction(usedPercent / 100),
|
|
155
|
+
resetsAt: resetsAt === null ? null : Math.round(resetsAt * 1000),
|
|
156
|
+
durationMs
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return { plan, windows, cost: null };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/*
|
|
163
|
+
* A sparse update folded onto what a full read drew. The row keeps its label, its reset and its
|
|
164
|
+
* length when the event leaves them out, which is what makes a mid-turn number land on the bar it
|
|
165
|
+
* belongs to instead of drawing a second one beside it.
|
|
166
|
+
*/
|
|
167
|
+
export function mergeWindows(known: readonly UsageWindow[], updates: readonly Partial<UsageWindow>[]): UsageWindow[] {
|
|
168
|
+
const merged = known.map((window) => ({ ...window }));
|
|
169
|
+
for (const update of updates) {
|
|
170
|
+
if (update.id === undefined || update.used === undefined) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const at = merged.findIndex((window) => window.id === update.id);
|
|
174
|
+
if (at === -1) {
|
|
175
|
+
merged.push({
|
|
176
|
+
id: update.id,
|
|
177
|
+
kind: update.kind ?? 'other',
|
|
178
|
+
label: update.label ?? 'Usage',
|
|
179
|
+
used: update.used,
|
|
180
|
+
resetsAt: update.resetsAt ?? null,
|
|
181
|
+
durationMs: update.durationMs ?? null
|
|
182
|
+
});
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
merged[at] = { ...merged[at]!, ...update } as UsageWindow;
|
|
186
|
+
}
|
|
187
|
+
return merged;
|
|
188
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { withTimeout } from '../../async.ts';
|
|
2
|
+
import type { UsageLimitsProvider } from '@adecore/agent-contracts';
|
|
3
|
+
import { spawnChatProcess, type ChatProcess, type SpawnChatProcess } from '../../chat/chat-process.ts';
|
|
4
|
+
import { CodexTransport, DEFAULT_CODEX_CLIENT, type CodexClientInfo } from '../../chat/codex-transport.ts';
|
|
5
|
+
import { readClaudeUsage, readCodexLimits, type ProviderReading } from './normalize.ts';
|
|
6
|
+
import { errorText } from '../../error-text.ts';
|
|
7
|
+
|
|
8
|
+
/* A CLI that has not answered by now is not going to; the next pass tries again. */
|
|
9
|
+
const PROBE_TIMEOUT_MS = 20_000;
|
|
10
|
+
const CODEX_READ_TIMEOUT_MS = 3_000;
|
|
11
|
+
|
|
12
|
+
export type ProbeResult = ProviderReading | { unavailable: UsageLimitsProvider['unavailable'] };
|
|
13
|
+
|
|
14
|
+
function failure(message: string): ProbeResult {
|
|
15
|
+
return { unavailable: { reason: 'failed', message } };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function timeoutOf<T>(work: Promise<T>, ms: number, what: string): Promise<T> {
|
|
19
|
+
return withTimeout(work, ms, `${what} did not answer in time`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* Asks Claude Code what is left of the plan, without a turn and without a token of our own. A
|
|
24
|
+
* `claude -p` on the stream-json protocol answers `get_usage` before any prompt, but only once it
|
|
25
|
+
* has been initialized: without that first control request the process waits and says nothing.
|
|
26
|
+
*/
|
|
27
|
+
export async function probeClaude(command: readonly string[], env: Record<string, string>, spawn: SpawnChatProcess = spawnChatProcess): Promise<ProbeResult> {
|
|
28
|
+
let child: ChatProcess;
|
|
29
|
+
try {
|
|
30
|
+
child = spawn({
|
|
31
|
+
command: [...command, '-p', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose'],
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
env,
|
|
34
|
+
onExit: () => undefined
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return failure(errorText(error));
|
|
38
|
+
}
|
|
39
|
+
const write = (frame: unknown): void => {
|
|
40
|
+
child.stdin.write(`${JSON.stringify(frame)}\n`);
|
|
41
|
+
child.stdin.flush();
|
|
42
|
+
};
|
|
43
|
+
const read = async (): Promise<ProbeResult> => {
|
|
44
|
+
write({ type: 'control_request', request_id: 'probe-init', request: { subtype: 'initialize', hooks: {} } });
|
|
45
|
+
let asked = false;
|
|
46
|
+
let buffer = '';
|
|
47
|
+
const decoder = new TextDecoder();
|
|
48
|
+
for await (const chunk of child.stdout as unknown as AsyncIterable<Uint8Array>) {
|
|
49
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
50
|
+
let at = buffer.indexOf('\n');
|
|
51
|
+
for (; at !== -1; at = buffer.indexOf('\n')) {
|
|
52
|
+
const line = buffer.slice(0, at);
|
|
53
|
+
buffer = buffer.slice(at + 1);
|
|
54
|
+
let frame: unknown;
|
|
55
|
+
try {
|
|
56
|
+
frame = JSON.parse(line);
|
|
57
|
+
} catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (typeof frame !== 'object' || frame === null) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const envelope = frame as { type?: unknown; request_id?: unknown; response?: { subtype?: unknown; response?: unknown } };
|
|
64
|
+
if (envelope.type !== 'control_response') {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!asked) {
|
|
68
|
+
asked = true;
|
|
69
|
+
write({ type: 'control_request', request_id: 'probe-usage', request: { subtype: 'get_usage' } });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (envelope.response?.subtype === 'error') {
|
|
73
|
+
return failure('Claude Code refused to report its usage');
|
|
74
|
+
}
|
|
75
|
+
return readClaudeUsage(envelope.response?.response ?? envelope.response);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return failure('Claude Code closed without reporting its usage');
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
return await timeoutOf(read(), PROBE_TIMEOUT_MS, 'Claude Code');
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return failure(errorText(error));
|
|
85
|
+
} finally {
|
|
86
|
+
child.stdin.end();
|
|
87
|
+
child.kill('SIGTERM');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* The same question to Codex: the app-server answers it over JSON-RPC right after the handshake. */
|
|
92
|
+
export async function probeCodex(
|
|
93
|
+
command: readonly string[],
|
|
94
|
+
env: Record<string, string>,
|
|
95
|
+
client: CodexClientInfo = DEFAULT_CODEX_CLIENT
|
|
96
|
+
): Promise<ProbeResult> {
|
|
97
|
+
let transport: CodexTransport | null = null;
|
|
98
|
+
try {
|
|
99
|
+
transport = new CodexTransport({
|
|
100
|
+
command: [...command],
|
|
101
|
+
cwd: process.cwd(),
|
|
102
|
+
env,
|
|
103
|
+
onFrame: () => undefined,
|
|
104
|
+
onExit: () => undefined
|
|
105
|
+
});
|
|
106
|
+
const open = transport;
|
|
107
|
+
await timeoutOf(open.request('initialize', { clientInfo: client, capabilities: { experimentalApi: true } }), PROBE_TIMEOUT_MS, 'Codex');
|
|
108
|
+
open.notify('initialized', {});
|
|
109
|
+
const answer = await timeoutOf(open.request('account/rateLimits/read', undefined), CODEX_READ_TIMEOUT_MS, 'Codex');
|
|
110
|
+
const snapshot = typeof answer === 'object' && answer !== null ? (answer as { rateLimits?: unknown }).rateLimits : null;
|
|
111
|
+
const reading = readCodexLimits(snapshot);
|
|
112
|
+
// An account on an API key has no plan windows; Codex answers, with nothing in it.
|
|
113
|
+
return reading ?? { unavailable: { reason: 'no-subscription', message: null } };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return failure(errorText(error));
|
|
116
|
+
} finally {
|
|
117
|
+
transport?.end();
|
|
118
|
+
transport?.kill();
|
|
119
|
+
}
|
|
120
|
+
}
|