@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,85 @@
|
|
|
1
|
+
import { withTimeout } from '../../async.js';
|
|
2
|
+
import { CodexTransport } from '../../chat/codex-transport.js';
|
|
3
|
+
import { runProcess } from '../../run-process.js';
|
|
4
|
+
/* A CLI that has not said who is signed in by now is not going to; the next pass asks again. */
|
|
5
|
+
const ASK_TIMEOUT_MS = 20_000;
|
|
6
|
+
function text(value) {
|
|
7
|
+
return typeof value === 'string' && value !== '' ? value : null;
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/* `claude auth status --json`, as Claude Code 2.1.282 prints it: `loggedIn`, `email`, `orgName`, `subscriptionType`. */
|
|
13
|
+
export function readClaudeAuthStatus(output) {
|
|
14
|
+
let status;
|
|
15
|
+
try {
|
|
16
|
+
status = JSON.parse(output);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error('Claude Code did not say who is signed in');
|
|
20
|
+
}
|
|
21
|
+
if (!isRecord(status) || typeof status.loggedIn !== 'boolean') {
|
|
22
|
+
throw new Error('Claude Code did not say who is signed in');
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
signedIn: status.loggedIn,
|
|
26
|
+
email: text(status.email),
|
|
27
|
+
plan: text(status.subscriptionType),
|
|
28
|
+
organization: text(status.orgName)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/*
|
|
32
|
+
* The app-server's `account/read`, as Codex 0.157 answers it: `account` is null while nobody signed in,
|
|
33
|
+
* `{ type: 'chatgpt', email, planType }` for a plan and `{ type: 'apiKey' }` for a key. A model
|
|
34
|
+
* provider without OpenAI's login needs no account at all, which `requiresOpenaiAuth` says.
|
|
35
|
+
*/
|
|
36
|
+
export function readCodexAccount(answer) {
|
|
37
|
+
if (!isRecord(answer)) {
|
|
38
|
+
throw new Error('Codex did not say who is signed in');
|
|
39
|
+
}
|
|
40
|
+
const account = answer.account;
|
|
41
|
+
if (!isRecord(account)) {
|
|
42
|
+
return { signedIn: answer.requiresOpenaiAuth === false, email: null, plan: null, organization: null };
|
|
43
|
+
}
|
|
44
|
+
return { signedIn: true, email: text(account.email), plan: text(account.planType), organization: null };
|
|
45
|
+
}
|
|
46
|
+
async function askClaude(command, env) {
|
|
47
|
+
const { stdout } = await runProcess([...command, 'auth', 'status', '--json'], {
|
|
48
|
+
env,
|
|
49
|
+
timeoutMs: ASK_TIMEOUT_MS,
|
|
50
|
+
timeoutMessage: 'Claude Code did not answer in time'
|
|
51
|
+
});
|
|
52
|
+
// Signed out exits 1 and still prints the status, so the output decides and the exit code does not.
|
|
53
|
+
return readClaudeAuthStatus(stdout);
|
|
54
|
+
}
|
|
55
|
+
async function askCodex(command, env, client) {
|
|
56
|
+
const transport = new CodexTransport({
|
|
57
|
+
command: [...command],
|
|
58
|
+
cwd: process.cwd(),
|
|
59
|
+
env: env,
|
|
60
|
+
onFrame: () => undefined,
|
|
61
|
+
onExit: () => undefined
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
await withTimeout(transport.request('initialize', { clientInfo: client, capabilities: { experimentalApi: true } }), ASK_TIMEOUT_MS, 'Codex did not answer in time');
|
|
65
|
+
transport.notify('initialized', {});
|
|
66
|
+
const answer = await withTimeout(transport.request('account/read', { refreshToken: false }), ASK_TIMEOUT_MS, 'Codex did not answer in time');
|
|
67
|
+
return readCodexAccount(answer);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
transport.end();
|
|
71
|
+
transport.kill();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/* Starts the CLI in the account's environment and asks it; only Claude Code and Codex can say. */
|
|
75
|
+
export function askAccountAs(client) {
|
|
76
|
+
return (kind, command, env) => {
|
|
77
|
+
if (kind === 'claude') {
|
|
78
|
+
return askClaude(command, env);
|
|
79
|
+
}
|
|
80
|
+
if (kind === 'codex') {
|
|
81
|
+
return askCodex(command, env, client);
|
|
82
|
+
}
|
|
83
|
+
return Promise.reject(new Error(`${kind} cannot say who is signed in`));
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type AgentKind, type ProviderAccountMap } from '@adecore/agent-contracts';
|
|
2
|
+
import { CodedError } from '../../coded-error.ts';
|
|
3
|
+
import type { ChatProvider } from '../provider.ts';
|
|
4
|
+
import { type Env } from './accounts.ts';
|
|
5
|
+
import { type AccountsHost } from './variables.ts';
|
|
6
|
+
export declare function accountsPath(home: string): string;
|
|
7
|
+
export type StoredAccounts = Record<string, unknown>;
|
|
8
|
+
export declare function readAccounts(path: string, providerOf: (kind: AgentKind) => ChatProvider, env: Env, host?: AccountsHost): Promise<StoredAccounts>;
|
|
9
|
+
export declare function writeAccounts(path: string, stored: StoredAccounts): Promise<void>;
|
|
10
|
+
export declare function wireAccounts(stored: StoredAccounts): ProviderAccountMap;
|
|
11
|
+
export declare class InvalidAccountError extends CodedError<'invalid-account'> {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare function acceptAccounts(saved: ProviderAccountMap, before: StoredAccounts, providerOf: (kind: AgentKind) => ChatProvider, env: Env, isDirectory: (path: string) => boolean, host?: AccountsHost): StoredAccounts;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ProviderAccountIdSchema, ProviderAccountSchema } from '@adecore/agent-contracts';
|
|
4
|
+
import { CodedError } from '../../coded-error.js';
|
|
5
|
+
import { isNotFound, writeAtomic } from '../../fs.js';
|
|
6
|
+
import { accountFolder, accountProblem, defaultFolder, expandHome, folderVariablesOf, isDefaultAccount, isKnownKind, readAccount, withDefaults } from './accounts.js';
|
|
7
|
+
import { DEFAULT_ACCOUNTS_HOST, redactVariables } from './variables.js';
|
|
8
|
+
const FILE_VERSION = 1;
|
|
9
|
+
export function accountsPath(home) {
|
|
10
|
+
return join(home, 'providers.json');
|
|
11
|
+
}
|
|
12
|
+
/* Every entry that does not parse is dropped on its own; a file that is not JSON at all reads as none. */
|
|
13
|
+
export async function readAccounts(path, providerOf, env, host = DEFAULT_ACCOUNTS_HOST) {
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(await readFile(path, 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
if (isNotFound(e) || e instanceof SyntaxError) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
const entries = typeof parsed === 'object' && parsed !== null ? parsed.accounts : null;
|
|
25
|
+
if (typeof entries !== 'object' || entries === null || Array.isArray(entries)) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
const stored = {};
|
|
29
|
+
for (const [id, raw] of Object.entries(entries)) {
|
|
30
|
+
if (!ProviderAccountIdSchema.safeParse(id).success) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const account = readAccount(id, raw, providerOf, env, host);
|
|
34
|
+
if (account !== null) {
|
|
35
|
+
stored[id] = account;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return stored;
|
|
39
|
+
}
|
|
40
|
+
export async function writeAccounts(path, stored) {
|
|
41
|
+
await writeAtomic(path, `${JSON.stringify({ version: FILE_VERSION, accounts: stored }, null, 2)}\n`, 0o600);
|
|
42
|
+
}
|
|
43
|
+
/* What a client reads of an entry of a kind it may not know either: the fields this version knows. */
|
|
44
|
+
function wireAccount(entry) {
|
|
45
|
+
const parsed = ProviderAccountSchema.safeParse(entry);
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
return { kind: String(entry.kind) };
|
|
48
|
+
}
|
|
49
|
+
return parsed.data.env === undefined ? parsed.data : { ...parsed.data, env: redactVariables(parsed.data.env) };
|
|
50
|
+
}
|
|
51
|
+
export function wireAccounts(stored) {
|
|
52
|
+
return withDefaults(Object.fromEntries(Object.entries(stored).map(([id, entry]) => [id, wireAccount(entry)])));
|
|
53
|
+
}
|
|
54
|
+
export class InvalidAccountError extends CodedError {
|
|
55
|
+
constructor(message) {
|
|
56
|
+
super('invalid-account', message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function storedFolders(entry) {
|
|
60
|
+
const parsed = ProviderAccountSchema.safeParse(entry);
|
|
61
|
+
return parsed.success ? { home: parsed.data.home, shadowHome: parsed.data.shadowHome } : {};
|
|
62
|
+
}
|
|
63
|
+
/*
|
|
64
|
+
* What is wrong with the folders of an account a person points somewhere new, or null. An account
|
|
65
|
+
* whose folders did not change is left alone, so one whose folder went missing can still be renamed.
|
|
66
|
+
*/
|
|
67
|
+
function linkProblem(id, account, before, provider, env, isDirectory) {
|
|
68
|
+
if (isDefaultAccount(id, account) || provider.home === undefined || account.home === undefined) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const was = storedFolders(before);
|
|
72
|
+
if (was.home === account.home && was.shadowHome === account.shadowHome) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
for (const folder of [account.home, account.shadowHome]) {
|
|
76
|
+
if (folder !== undefined && !isDirectory(expandHome(folder, env))) {
|
|
77
|
+
return `${folder} is not a folder on this machine`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (accountFolder(id, account, provider, env) === defaultFolder(provider, env)) {
|
|
81
|
+
return `${defaultFolder(provider, env)} is the folder of the default ${provider.name} account`;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/*
|
|
86
|
+
* The whole map a person saved, as the file will hold it. A mistake in an account of a known kind
|
|
87
|
+
* refuses the save; an account of an unknown kind keeps the entry the file had under its id, which a
|
|
88
|
+
* client that knows the kind no better than this host could only have cut short. Sensitive values
|
|
89
|
+
* are still in it: the caller puts them in the keychain before the file is written.
|
|
90
|
+
*/
|
|
91
|
+
export function acceptAccounts(saved, before, providerOf, env, isDirectory, host = DEFAULT_ACCOUNTS_HOST) {
|
|
92
|
+
const stored = {};
|
|
93
|
+
const reserved = { host, folderVariables: folderVariablesOf(providerOf) };
|
|
94
|
+
for (const [id, account] of Object.entries(withDefaults(saved))) {
|
|
95
|
+
if (!isKnownKind(account.kind)) {
|
|
96
|
+
if (isKnownKind(id)) {
|
|
97
|
+
throw new InvalidAccountError(`"${id}" is the id of the default ${id} account`);
|
|
98
|
+
}
|
|
99
|
+
const kept = before[id];
|
|
100
|
+
const keptKind = typeof kept === 'object' && kept !== null ? kept.kind : undefined;
|
|
101
|
+
stored[id] = keptKind === account.kind ? kept : account;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const provider = providerOf(account.kind);
|
|
105
|
+
const problem = accountProblem(id, account, provider, env, reserved) ?? linkProblem(id, account, before[id], provider, env, isDirectory);
|
|
106
|
+
if (problem !== null) {
|
|
107
|
+
throw new InvalidAccountError(`${id}: ${problem}`);
|
|
108
|
+
}
|
|
109
|
+
stored[id] = account;
|
|
110
|
+
}
|
|
111
|
+
return stored;
|
|
112
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProviderAccountMap } from '@adecore/agent-contracts';
|
|
2
|
+
import { ProviderAccountsService } from './service.ts';
|
|
3
|
+
import type { SecretStore } from './variables.ts';
|
|
4
|
+
export declare function memorySecrets(values?: Map<string, string>): SecretStore & {
|
|
5
|
+
values: Map<string, string>;
|
|
6
|
+
};
|
|
7
|
+
export declare function testAccounts(options: {
|
|
8
|
+
home: string;
|
|
9
|
+
env: Record<string, string | undefined>;
|
|
10
|
+
accounts: ProviderAccountMap;
|
|
11
|
+
folders: Set<string>;
|
|
12
|
+
}): Promise<ProviderAccountsService>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { providerFor } from '../registry.js';
|
|
2
|
+
import { ProviderAccountsService } from './service.js';
|
|
3
|
+
import { accountsPath, writeAccounts } from './store.js';
|
|
4
|
+
/* A keychain in memory; `values` is what it holds, by key. */
|
|
5
|
+
export function memorySecrets(values = new Map()) {
|
|
6
|
+
return {
|
|
7
|
+
values,
|
|
8
|
+
read: async (key) => values.get(key) ?? null,
|
|
9
|
+
write: async (key, value) => {
|
|
10
|
+
values.set(key, value);
|
|
11
|
+
},
|
|
12
|
+
remove: async (key) => {
|
|
13
|
+
values.delete(key);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/*
|
|
18
|
+
* The accounts of a machine as a test wants them: written to `providers.json` under `home` as
|
|
19
|
+
* they are, with only the folders in `folders` on disk, and every CLI signed in without being asked.
|
|
20
|
+
*/
|
|
21
|
+
export async function testAccounts(options) {
|
|
22
|
+
const service = new ProviderAccountsService({
|
|
23
|
+
home: options.home,
|
|
24
|
+
providers: {
|
|
25
|
+
list: async () => ['claude', 'codex'].map((kind) => ({ kind, installed: true })),
|
|
26
|
+
get: (kind) => providerFor(kind)
|
|
27
|
+
},
|
|
28
|
+
env: options.env,
|
|
29
|
+
ask: async () => ({ signedIn: true, email: null, plan: null, organization: null }),
|
|
30
|
+
folderExists: (path) => options.folders.has(path),
|
|
31
|
+
prepareShadow: async () => ({ linked: [], unshared: [], sharedLogin: false }),
|
|
32
|
+
secrets: memorySecrets()
|
|
33
|
+
});
|
|
34
|
+
// Written and not saved, so an account whose folder is already gone is there the way a person left it.
|
|
35
|
+
await writeAccounts(accountsPath(options.home), options.accounts);
|
|
36
|
+
await service.load();
|
|
37
|
+
return service;
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ProviderAccountVariable } from '@adecore/agent-contracts';
|
|
2
|
+
import { CodedError } from '../../coded-error.ts';
|
|
3
|
+
export declare class SecretsUnavailableError extends CodedError<'secrets-unavailable'> {
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
export interface SecretStore {
|
|
7
|
+
read(key: string): Promise<string | null>;
|
|
8
|
+
write(key: string, value: string): Promise<void>;
|
|
9
|
+
remove(key: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function secretKey(accountId: string, name: string): string;
|
|
12
|
+
export interface AccountsHost {
|
|
13
|
+
name: string;
|
|
14
|
+
variablePrefixes: readonly string[];
|
|
15
|
+
keychainPrefix: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_ACCOUNTS_HOST: AccountsHost;
|
|
18
|
+
export interface ReservedVariables {
|
|
19
|
+
host: AccountsHost;
|
|
20
|
+
folderVariables: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function isReservedName(name: string, reserved: ReservedVariables): boolean;
|
|
23
|
+
export declare function variablesProblem(variables: readonly ProviderAccountVariable[], reserved: ReservedVariables): string | null;
|
|
24
|
+
export declare function redactVariables(variables: readonly ProviderAccountVariable[]): ProviderAccountVariable[];
|
|
25
|
+
export declare function keychainService(host: AccountsHost, home: string): string;
|
|
26
|
+
export declare function keychainSecrets(service: string): SecretStore;
|
|
27
|
+
export declare function platformSecrets(host: AccountsHost, home: string): SecretStore | null;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { CodedError } from '../../coded-error.js';
|
|
4
|
+
import { runProcess } from '../../run-process.js';
|
|
5
|
+
/*
|
|
6
|
+
* The variables of an account, and where a sensitive value is kept: the keychain of this machine,
|
|
7
|
+
* never `providers.json` and never the wire.
|
|
8
|
+
*/
|
|
9
|
+
export class SecretsUnavailableError extends CodedError {
|
|
10
|
+
constructor() {
|
|
11
|
+
super('secrets-unavailable', 'This machine has no keychain to keep a sensitive value in.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function secretKey(accountId, name) {
|
|
15
|
+
return `${accountId}/${name}`;
|
|
16
|
+
}
|
|
17
|
+
/* The names that decide which folder and which executable a CLI talks to. */
|
|
18
|
+
const RESERVED = new Set(['HOME', 'PATH']);
|
|
19
|
+
export const DEFAULT_ACCOUNTS_HOST = { name: 'the app', variablePrefixes: [], keychainPrefix: 'agents' };
|
|
20
|
+
export function isReservedName(name, reserved) {
|
|
21
|
+
return RESERVED.has(name) || reserved.host.variablePrefixes.some((prefix) => name.startsWith(prefix)) || reserved.folderVariables.includes(name);
|
|
22
|
+
}
|
|
23
|
+
/* `security` prints anything else as hex, so a read could not tell such a value from its own encoding. */
|
|
24
|
+
function isPrintableAscii(value) {
|
|
25
|
+
return /^[\x20-\x7e]*$/.test(value);
|
|
26
|
+
}
|
|
27
|
+
/* What is wrong with the variables of an account, or null when nothing is. A secret that is only redacted is judged by the service. */
|
|
28
|
+
export function variablesProblem(variables, reserved) {
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const variable of variables) {
|
|
31
|
+
if (seen.has(variable.name)) {
|
|
32
|
+
return `${variable.name} is set twice`;
|
|
33
|
+
}
|
|
34
|
+
seen.add(variable.name);
|
|
35
|
+
if (isReservedName(variable.name, reserved)) {
|
|
36
|
+
return `${variable.name} is set by ${reserved.host.name} itself`;
|
|
37
|
+
}
|
|
38
|
+
if (variable.sensitive && !isPrintableAscii(variable.value)) {
|
|
39
|
+
return `The value of ${variable.name} can only hold printable ASCII characters`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/* The variables as a client may read them: a sensitive value never leaves the machine. */
|
|
45
|
+
export function redactVariables(variables) {
|
|
46
|
+
return variables.map((variable) => (variable.sensitive ? { name: variable.name, value: '', sensitive: true, valueRedacted: true } : variable));
|
|
47
|
+
}
|
|
48
|
+
/* A data folder of its own (a development one beside an installed one) keeps its values apart from the other's. */
|
|
49
|
+
export function keychainService(host, home) {
|
|
50
|
+
return `${host.keychainPrefix}-provider-env-${createHash('sha256').update(resolve(home)).digest('hex').slice(0, 12)}`;
|
|
51
|
+
}
|
|
52
|
+
// `security` exits with this when no item matches.
|
|
53
|
+
const NOT_FOUND = 44;
|
|
54
|
+
async function run(args, stdin) {
|
|
55
|
+
const { exitCode, stdout, stderr } = await runProcess(['security', ...args], { ...(stdin === undefined ? {} : { stdin }) });
|
|
56
|
+
return { code: exitCode ?? 1, stdout, stderr };
|
|
57
|
+
}
|
|
58
|
+
/*
|
|
59
|
+
* Generic passwords in the login keychain, through the `security` CLI. A value goes in over stdin as
|
|
60
|
+
* hex in its interactive mode: on the command line any process could read it, and the password
|
|
61
|
+
* prompt cuts it at 128 characters.
|
|
62
|
+
*/
|
|
63
|
+
export function keychainSecrets(service) {
|
|
64
|
+
return {
|
|
65
|
+
read: async (key) => {
|
|
66
|
+
const { code, stdout, stderr } = await run(['find-generic-password', '-s', service, '-a', key, '-w']);
|
|
67
|
+
if (code === NOT_FOUND) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (code !== 0) {
|
|
71
|
+
throw new Error(`The keychain did not give ${key}: ${stderr.trim()}`);
|
|
72
|
+
}
|
|
73
|
+
return stdout.endsWith('\n') ? stdout.slice(0, -1) : stdout;
|
|
74
|
+
},
|
|
75
|
+
write: async (key, value) => {
|
|
76
|
+
const hex = Buffer.from(value, 'utf8').toString('hex');
|
|
77
|
+
const quoted = `"${key.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
78
|
+
const { code, stderr } = await run(['-i'], `add-generic-password -U -s ${service} -a ${quoted} -X ${hex}\n`);
|
|
79
|
+
if (code !== 0) {
|
|
80
|
+
throw new Error(`The keychain did not take ${key}: ${stderr.trim()}`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
remove: async (key) => {
|
|
84
|
+
const { code, stderr } = await run(['delete-generic-password', '-s', service, '-a', key]);
|
|
85
|
+
if (code !== 0 && code !== NOT_FOUND) {
|
|
86
|
+
throw new Error(`The keychain did not remove ${key}: ${stderr.trim()}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/* The keychain on macOS; elsewhere none, so a sensitive variable is refused. */
|
|
92
|
+
export function platformSecrets(host, home) {
|
|
93
|
+
return process.platform === 'darwin' ? keychainSecrets(keychainService(host, home)) : null;
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ModelCatalogData, ModelInfo, ModelSelection } from '@adecore/agent-contracts';
|
|
2
|
+
export type ModelCatalogInput = Omit<ModelCatalogData, 'updatedAt'> & {
|
|
3
|
+
updatedAt?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare class ModelCatalog {
|
|
6
|
+
private readonly shippedAt;
|
|
7
|
+
private data;
|
|
8
|
+
constructor(data?: ModelCatalogInput);
|
|
9
|
+
get defaultModel(): string;
|
|
10
|
+
replace(data: ModelCatalogData): boolean;
|
|
11
|
+
list(): ModelInfo[];
|
|
12
|
+
nameOf(slug: string): string;
|
|
13
|
+
resolveModel(name: string): string | null;
|
|
14
|
+
normalize(selection: Partial<ModelSelection> | undefined): ModelSelection;
|
|
15
|
+
contextWindowFor(selection: ModelSelection): number | null;
|
|
16
|
+
private profileOf;
|
|
17
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import manifest from './claude-models.json' with { type: 'json' };
|
|
2
|
+
function timeOf(data) {
|
|
3
|
+
return data.updatedAt === undefined ? 0 : Date.parse(data.updatedAt);
|
|
4
|
+
}
|
|
5
|
+
/*
|
|
6
|
+
* The models a provider offers and what each one can be asked for. Bundled as JSON so a new
|
|
7
|
+
* model is a data change; a profile is only needed for a new combination of options. A host may
|
|
8
|
+
* swap in a newer copy while it runs, never one older than the copy it was built with.
|
|
9
|
+
*/
|
|
10
|
+
export class ModelCatalog {
|
|
11
|
+
shippedAt;
|
|
12
|
+
data;
|
|
13
|
+
constructor(data = manifest) {
|
|
14
|
+
this.shippedAt = timeOf(data);
|
|
15
|
+
this.data = data;
|
|
16
|
+
}
|
|
17
|
+
get defaultModel() {
|
|
18
|
+
return this.data.defaultModel;
|
|
19
|
+
}
|
|
20
|
+
/* Takes a validated copy unless it is older than the shipped one; answers whether the models changed. */
|
|
21
|
+
replace(data) {
|
|
22
|
+
if (timeOf(data) < this.shippedAt) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const changed = JSON.stringify(data) !== JSON.stringify(this.data);
|
|
26
|
+
this.data = data;
|
|
27
|
+
return changed;
|
|
28
|
+
}
|
|
29
|
+
list() {
|
|
30
|
+
return this.data.models.map((model) => ({
|
|
31
|
+
slug: model.slug,
|
|
32
|
+
name: model.name,
|
|
33
|
+
badge: model.badge,
|
|
34
|
+
legacy: model.legacy === true,
|
|
35
|
+
isDefault: model.slug === this.data.defaultModel,
|
|
36
|
+
options: this.profileOf(model.slug)?.options ?? []
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/* What a person reads for a model, so an error in a thread names it the way the picker does. */
|
|
40
|
+
nameOf(slug) {
|
|
41
|
+
return this.data.models.find((entry) => entry.slug === slug)?.name ?? slug;
|
|
42
|
+
}
|
|
43
|
+
/* Accepts a slug or an alias; unknown models answer null so the caller can decide what to do. */
|
|
44
|
+
resolveModel(name) {
|
|
45
|
+
const model = this.data.models.find((entry) => entry.slug === name || entry.aliases?.includes(name));
|
|
46
|
+
return model?.slug ?? null;
|
|
47
|
+
}
|
|
48
|
+
/* Fills in defaults and drops options the model does not have, so a selection is always complete. */
|
|
49
|
+
normalize(selection) {
|
|
50
|
+
const model = (selection?.model && this.resolveModel(selection.model)) || this.defaultModel;
|
|
51
|
+
const options = {};
|
|
52
|
+
for (const descriptor of this.profileOf(model)?.options ?? []) {
|
|
53
|
+
const given = selection?.options?.[descriptor.id];
|
|
54
|
+
if (descriptor.type === 'select') {
|
|
55
|
+
options[descriptor.id] =
|
|
56
|
+
typeof given === 'string' && descriptor.choices.some((choice) => choice.id === given) ? given : descriptor.defaultChoice;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
options[descriptor.id] = typeof given === 'boolean' ? given : descriptor.defaultValue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { model, options };
|
|
63
|
+
}
|
|
64
|
+
contextWindowFor(selection) {
|
|
65
|
+
const profile = this.profileOf(selection.model);
|
|
66
|
+
if (!profile) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const chosen = selection.options.contextWindow;
|
|
70
|
+
return (typeof chosen === 'string' ? profile.contextWindowTokens[chosen] : undefined) ?? profile.contextWindowTokens['*'] ?? null;
|
|
71
|
+
}
|
|
72
|
+
profileOf(slug) {
|
|
73
|
+
const model = this.data.models.find((entry) => entry.slug === slug);
|
|
74
|
+
return model ? this.data.profiles[model.profile] : undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"updatedAt": "2026-09-28T19:00:00Z",
|
|
3
|
+
"defaultModel": "claude-sonnet-5-5",
|
|
4
|
+
"profiles": {
|
|
5
|
+
"frontier": {
|
|
6
|
+
"options": [
|
|
7
|
+
{
|
|
8
|
+
"id": "effort",
|
|
9
|
+
"label": "Reasoning",
|
|
10
|
+
"type": "select",
|
|
11
|
+
"defaultChoice": "high",
|
|
12
|
+
"choices": [
|
|
13
|
+
{ "id": "low", "label": "Low" },
|
|
14
|
+
{ "id": "medium", "label": "Medium" },
|
|
15
|
+
{ "id": "high", "label": "High" },
|
|
16
|
+
{ "id": "xhigh", "label": "Extra high" },
|
|
17
|
+
{ "id": "max", "label": "Max" },
|
|
18
|
+
{ "id": "ultrathink", "label": "Ultrathink", "description": "Asks for the longest thinking in the prompt itself" }
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "contextWindow",
|
|
23
|
+
"label": "Context window",
|
|
24
|
+
"type": "select",
|
|
25
|
+
"defaultChoice": "1m",
|
|
26
|
+
"choices": [
|
|
27
|
+
{ "id": "200k", "label": "200k" },
|
|
28
|
+
{ "id": "1m", "label": "1M" }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"contextWindowTokens": { "200k": 200000, "1m": 1000000 }
|
|
33
|
+
},
|
|
34
|
+
"opus": {
|
|
35
|
+
"options": [
|
|
36
|
+
{
|
|
37
|
+
"id": "effort",
|
|
38
|
+
"label": "Reasoning",
|
|
39
|
+
"type": "select",
|
|
40
|
+
"defaultChoice": "high",
|
|
41
|
+
"choices": [
|
|
42
|
+
{ "id": "low", "label": "Low" },
|
|
43
|
+
{ "id": "medium", "label": "Medium" },
|
|
44
|
+
{ "id": "high", "label": "High" },
|
|
45
|
+
{ "id": "xhigh", "label": "Extra high" },
|
|
46
|
+
{ "id": "max", "label": "Max" },
|
|
47
|
+
{ "id": "ultrathink", "label": "Ultrathink", "description": "Asks for the longest thinking in the prompt itself" }
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "contextWindow",
|
|
52
|
+
"label": "Context window",
|
|
53
|
+
"type": "select",
|
|
54
|
+
"defaultChoice": "1m",
|
|
55
|
+
"choices": [
|
|
56
|
+
{ "id": "200k", "label": "200k" },
|
|
57
|
+
{ "id": "1m", "label": "1M" }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "fastMode",
|
|
62
|
+
"label": "Fast mode",
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"defaultValue": false,
|
|
65
|
+
"description": "Answers sooner and spends usage faster"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"contextWindowTokens": { "200k": 200000, "1m": 1000000 }
|
|
69
|
+
},
|
|
70
|
+
"sonnet": {
|
|
71
|
+
"options": [
|
|
72
|
+
{
|
|
73
|
+
"id": "effort",
|
|
74
|
+
"label": "Reasoning",
|
|
75
|
+
"type": "select",
|
|
76
|
+
"defaultChoice": "high",
|
|
77
|
+
"choices": [
|
|
78
|
+
{ "id": "low", "label": "Low" },
|
|
79
|
+
{ "id": "medium", "label": "Medium" },
|
|
80
|
+
{ "id": "high", "label": "High" },
|
|
81
|
+
{ "id": "xhigh", "label": "Extra high" },
|
|
82
|
+
{ "id": "max", "label": "Max" },
|
|
83
|
+
{ "id": "ultrathink", "label": "Ultrathink", "description": "Asks for the longest thinking in the prompt itself" }
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "contextWindow",
|
|
88
|
+
"label": "Context window",
|
|
89
|
+
"type": "select",
|
|
90
|
+
"defaultChoice": "200k",
|
|
91
|
+
"choices": [
|
|
92
|
+
{ "id": "200k", "label": "200k" },
|
|
93
|
+
{ "id": "1m", "label": "1M" }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"contextWindowTokens": { "200k": 200000, "1m": 1000000 }
|
|
98
|
+
},
|
|
99
|
+
"haiku": {
|
|
100
|
+
"options": [{ "id": "thinking", "label": "Thinking", "type": "boolean", "defaultValue": true }],
|
|
101
|
+
"contextWindowTokens": { "*": 200000 }
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"models": [
|
|
105
|
+
{ "slug": "claude-fable-5-1", "name": "Claude Fable 5.1", "profile": "frontier", "aliases": ["fable"] },
|
|
106
|
+
{ "slug": "claude-opus-5-5", "name": "Claude Opus 5.5", "badge": "new", "profile": "opus", "aliases": ["opus"] },
|
|
107
|
+
{ "slug": "claude-opus-5", "name": "Claude Opus 5", "profile": "opus" },
|
|
108
|
+
{ "slug": "claude-sonnet-5-5", "name": "Claude Sonnet 5.5", "badge": "new", "profile": "sonnet", "aliases": ["sonnet"] },
|
|
109
|
+
{ "slug": "claude-sonnet-5", "name": "Claude Sonnet 5", "profile": "sonnet" },
|
|
110
|
+
{ "slug": "claude-haiku-4-5", "name": "Claude Haiku 4.5", "profile": "haiku", "aliases": ["haiku"] },
|
|
111
|
+
{ "slug": "claude-opus-4-6", "name": "Claude Opus 4.6", "profile": "frontier", "legacy": true },
|
|
112
|
+
{ "slug": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6", "profile": "sonnet", "legacy": true }
|
|
113
|
+
]
|
|
114
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ClaudeBackend } from '../chat/claude-backend.js';
|
|
2
|
+
import { ModelCatalog } from './catalog.js';
|
|
3
|
+
import { CLAUDE_CAPABILITIES, CLAUDE_RESUME_COMMAND } from './claude.js';
|
|
4
|
+
import { detectCli } from './detect.js';
|
|
5
|
+
export function createClaudeProvider(options = {}) {
|
|
6
|
+
return {
|
|
7
|
+
kind: 'claude',
|
|
8
|
+
name: 'Claude Code',
|
|
9
|
+
catalog: new ModelCatalog(),
|
|
10
|
+
capabilities: CLAUDE_CAPABILITIES,
|
|
11
|
+
command: ['claude'],
|
|
12
|
+
resumeCommand: CLAUDE_RESUME_COMMAND,
|
|
13
|
+
// Read from the strings of Claude Code 2.1.282; never HOME, which moves the keychain lookup along with it.
|
|
14
|
+
home: {
|
|
15
|
+
env: 'CLAUDE_CONFIG_DIR',
|
|
16
|
+
fallback: '.claude',
|
|
17
|
+
loginEnv: ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'CLAUDE_CODE_OAUTH_TOKEN'],
|
|
18
|
+
loginCommand: 'claude auth login'
|
|
19
|
+
},
|
|
20
|
+
detect: detectCli,
|
|
21
|
+
oneShotArgs: (prompt) => ['-p', prompt, '--output-format', 'text'],
|
|
22
|
+
// `-p` without a prompt argument reads the prompt from stdin.
|
|
23
|
+
oneShotStdinArgs: ['-p', '--output-format', 'text'],
|
|
24
|
+
// `claude [options] [prompt]`: the prompt is the positional, and without -p the session stays.
|
|
25
|
+
firstPromptArgs: (prompt) => [prompt],
|
|
26
|
+
createBackend: (launch, host) => new ClaudeBackend(launch, host, options)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export const claudeProvider = createClaudeProvider();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ModelSelection, ProviderCapabilities, RuntimeMode } from '@adecore/agent-contracts';
|
|
2
|
+
export declare function claudeRuntimeMode(mode: string): RuntimeMode | undefined;
|
|
3
|
+
interface ClaudeLaunch {
|
|
4
|
+
selection: ModelSelection;
|
|
5
|
+
runtimeMode: RuntimeMode;
|
|
6
|
+
resume: string | null;
|
|
7
|
+
allowedTools?: readonly string[];
|
|
8
|
+
folders?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function claudeArgs(launch: ClaudeLaunch): string[];
|
|
11
|
+
export declare function claudeEnv(selection: ModelSelection): Record<string, string>;
|
|
12
|
+
export declare const CLAUDE_CAPABILITIES: ProviderCapabilities;
|
|
13
|
+
export declare const CLAUDE_RESUME_COMMAND = "claude {flags} --resume {id}";
|
|
14
|
+
export declare function promptPrefix(selection: ModelSelection): string;
|
|
15
|
+
export {};
|