@adecore/agents 0.0.1 → 0.17.0-beta.2
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 +48 -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,397 @@
|
|
|
1
|
+
import { accessSync, constants } from 'node:fs';
|
|
2
|
+
import { attachmentImageMime, type ChatSkill } from '@adecore/agent-contracts';
|
|
3
|
+
import { codexServiceTier, codexThreadOptions } from '../providers/codex.ts';
|
|
4
|
+
import type { ApprovalDecision, BackendEvent, BackendHost, BackendLaunch, ChatBackend, TurnInput } from './backend.ts';
|
|
5
|
+
import { CodexProtocol } from './codex-protocol.ts';
|
|
6
|
+
import { attachmentNote } from './input.ts';
|
|
7
|
+
import { CodexTransport, DEFAULT_CODEX_CLIENT, type CodexClientInfo, type CodexFrame } from './codex-transport.ts';
|
|
8
|
+
import { errorText } from '../error-text.ts';
|
|
9
|
+
|
|
10
|
+
function textInput(text: string) {
|
|
11
|
+
return [{ type: 'text', text, text_elements: [] }];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* The enabled skills of a `skills/list` answer; Codex reports one entry per folder it was asked about. */
|
|
19
|
+
export function parseSkillsList(result: unknown): ChatSkill[] {
|
|
20
|
+
const data = isRecord(result) && Array.isArray(result.data) ? result.data : [];
|
|
21
|
+
const skills = new Map<string, ChatSkill>();
|
|
22
|
+
for (const entry of data) {
|
|
23
|
+
for (const skill of isRecord(entry) && Array.isArray(entry.skills) ? entry.skills : []) {
|
|
24
|
+
if (!isRecord(skill) || typeof skill.name !== 'string' || skill.enabled === false || skills.has(skill.name)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
skills.set(skill.name, {
|
|
28
|
+
name: skill.name,
|
|
29
|
+
description: typeof skill.description === 'string' ? skill.description : '',
|
|
30
|
+
source: skill.pluginId ? 'plugin' : skill.scope === 'repo' ? 'project' : 'user'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return [...skills.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CodexBackendOptions {
|
|
38
|
+
client?: CodexClientInfo;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* One `codex app-server` process on JSON-RPC over stdio. `start` handshakes (initialize, then
|
|
43
|
+
* thread/start or thread/resume) before the first turn goes out, so every later request knows the
|
|
44
|
+
* thread id. What the host tells the agent goes in as the thread's developer instructions, which
|
|
45
|
+
* Codex keeps with the thread. The runtime modes are an approval policy plus a sandbox.
|
|
46
|
+
*/
|
|
47
|
+
export class CodexBackend implements ChatBackend {
|
|
48
|
+
readonly acknowledgesTurns = true;
|
|
49
|
+
private promptId: string | null = null;
|
|
50
|
+
private readonly launch: BackendLaunch;
|
|
51
|
+
private readonly host: BackendHost;
|
|
52
|
+
private readonly protocol: CodexProtocol;
|
|
53
|
+
private readonly client: CodexClientInfo;
|
|
54
|
+
private transport: CodexTransport | null = null;
|
|
55
|
+
private threadId = '';
|
|
56
|
+
/*
|
|
57
|
+
* Codex 0.154 ignores developer instructions on `thread/resume` (measured), so a thread started
|
|
58
|
+
* before anything was linked would never hear of its links; the first prompt of a resumed process says so instead.
|
|
59
|
+
*/
|
|
60
|
+
private contextPending: string | null = null;
|
|
61
|
+
private imageInputSupported: boolean | null = null;
|
|
62
|
+
/* A turn asked for that Codex has not named yet; `turn/interrupt` needs that name. */
|
|
63
|
+
private turnUnnamed = false;
|
|
64
|
+
/* A stop that came in while the turn was unnamed, sent once `turn/started` names it. */
|
|
65
|
+
private interruptOwed = false;
|
|
66
|
+
|
|
67
|
+
constructor(launch: BackendLaunch, host: BackendHost, options: CodexBackendOptions = {}) {
|
|
68
|
+
this.launch = launch;
|
|
69
|
+
this.host = host;
|
|
70
|
+
this.client = options.client ?? DEFAULT_CODEX_CLIENT;
|
|
71
|
+
this.protocol = new CodexProtocol(launch.generation);
|
|
72
|
+
this.threadId = launch.resume ?? '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get running(): boolean {
|
|
76
|
+
return this.transport !== null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get pid(): number | null {
|
|
80
|
+
return this.transport?.pid ?? null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async start(): Promise<void> {
|
|
84
|
+
if (this.transport) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const transport = new CodexTransport({
|
|
88
|
+
command: this.launch.command,
|
|
89
|
+
cwd: this.launch.cwd,
|
|
90
|
+
env: this.launch.env,
|
|
91
|
+
...(this.launch.spawn ? { spawn: this.launch.spawn } : {}),
|
|
92
|
+
onError: (error) => this.fail(error),
|
|
93
|
+
onFrame: (frame) => this.handleFrame(transport, frame),
|
|
94
|
+
onExit: (exitCode, stderr) => this.handleExit(transport, exitCode, stderr)
|
|
95
|
+
});
|
|
96
|
+
this.transport = transport;
|
|
97
|
+
await transport.request('initialize', { clientInfo: this.client, capabilities: { experimentalApi: true, requestAttestation: false } });
|
|
98
|
+
transport.notify('initialized', {});
|
|
99
|
+
const tier = codexServiceTier(this.launch.selection);
|
|
100
|
+
const params = {
|
|
101
|
+
cwd: this.launch.cwd,
|
|
102
|
+
model: this.launch.selection.model,
|
|
103
|
+
...(tier === null ? {} : { serviceTier: tier }),
|
|
104
|
+
...codexThreadOptions(this.launch.runtimeMode)
|
|
105
|
+
};
|
|
106
|
+
const instructions = this.launch.instructions === null ? {} : { developerInstructions: this.launch.instructions };
|
|
107
|
+
let result: unknown;
|
|
108
|
+
if (this.launch.resume) {
|
|
109
|
+
try {
|
|
110
|
+
result = await transport.request('thread/resume', { threadId: this.launch.resume, excludeTurns: true, ...params });
|
|
111
|
+
this.contextPending = this.launch.resumeNote;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// The thread is gone from Codex's store; a fresh one keeps the chat usable.
|
|
114
|
+
this.emit({ type: 'note', level: 'warning', text: `Codex could not resume its thread (${errorText(error)}). Started a new one.` });
|
|
115
|
+
result = await transport.request('thread/start', { ...params, ...instructions });
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
result = await transport.request('thread/start', { ...params, ...instructions });
|
|
119
|
+
}
|
|
120
|
+
this.imageInputSupported = await this.readImageSupport(transport);
|
|
121
|
+
for (const event of this.protocol.threadReady(result)) {
|
|
122
|
+
if (event.type === 'session' && event.agentSessionId) {
|
|
123
|
+
this.threadId = event.agentSessionId;
|
|
124
|
+
}
|
|
125
|
+
this.emit(event);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
sendTurn(input: TurnInput): void {
|
|
130
|
+
const images = input.attachments.filter((attachment) => attachmentImageMime(attachment) !== null);
|
|
131
|
+
if (images.length > 0 && this.imageInputSupported === false) {
|
|
132
|
+
throw new Error(`${this.launch.modelName} does not support image input. Choose a model that accepts images.`);
|
|
133
|
+
}
|
|
134
|
+
for (const attachment of images) {
|
|
135
|
+
try {
|
|
136
|
+
accessSync(attachment.path, constants.R_OK);
|
|
137
|
+
} catch {
|
|
138
|
+
throw new Error(`Could not read attached image "${attachment.name}". Attach it again and retry.`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const parts: string[] = [];
|
|
142
|
+
if (this.contextPending !== null) {
|
|
143
|
+
parts.push(`${this.contextPending}\n\n`);
|
|
144
|
+
this.contextPending = null;
|
|
145
|
+
}
|
|
146
|
+
if (input.preamble !== null) {
|
|
147
|
+
parts.push(`${input.preamble}\n\n`);
|
|
148
|
+
}
|
|
149
|
+
parts.push(input.text);
|
|
150
|
+
const note = attachmentNote(input.attachments);
|
|
151
|
+
if (note !== '') {
|
|
152
|
+
parts.push(`\n\n${note}`);
|
|
153
|
+
}
|
|
154
|
+
const effort = this.launch.selection.options.effort;
|
|
155
|
+
const tier = codexServiceTier(this.launch.selection);
|
|
156
|
+
this.turnUnnamed = true;
|
|
157
|
+
this.promptId = input.promptId ?? null;
|
|
158
|
+
this.request(
|
|
159
|
+
'turn/start',
|
|
160
|
+
{
|
|
161
|
+
threadId: this.threadId,
|
|
162
|
+
input: [...textInput(parts.join('')), ...images.map((attachment) => ({ type: 'localImage', path: attachment.path }))],
|
|
163
|
+
model: this.launch.selection.model,
|
|
164
|
+
...(typeof effort === 'string' ? { effort } : {}),
|
|
165
|
+
...(tier === null ? {} : { serviceTier: tier })
|
|
166
|
+
},
|
|
167
|
+
() => this.acceptPrompt(input.promptId)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private acceptPrompt(promptId: string | undefined): void {
|
|
172
|
+
if (promptId !== undefined && this.promptId === promptId) {
|
|
173
|
+
this.emit({ type: 'turn.accepted', promptId });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private fail(error: unknown): void {
|
|
178
|
+
this.protocol.forgetPending();
|
|
179
|
+
this.emit({ type: 'failed', message: errorText(error), processAlive: this.running });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private respond(requestId: string, rpcId: number | string, result: unknown): boolean {
|
|
183
|
+
try {
|
|
184
|
+
if (!this.transport) {
|
|
185
|
+
throw new Error('Codex is not running');
|
|
186
|
+
}
|
|
187
|
+
this.transport.respond(rpcId, result);
|
|
188
|
+
return true;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
this.emit({ type: 'request.withdrawn', requestId });
|
|
191
|
+
this.fail(error);
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private async readImageSupport(transport: CodexTransport): Promise<boolean | null> {
|
|
197
|
+
let cursor: string | null = null;
|
|
198
|
+
const seen = new Set<string>();
|
|
199
|
+
try {
|
|
200
|
+
do {
|
|
201
|
+
const result = await transport.request('model/list', { includeHidden: true, ...(cursor === null ? {} : { cursor }) });
|
|
202
|
+
if (!isRecord(result) || !Array.isArray(result.data)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const model = result.data.find((entry) => isRecord(entry) && entry.model === this.launch.selection.model);
|
|
206
|
+
if (isRecord(model)) {
|
|
207
|
+
return Array.isArray(model.inputModalities) ? model.inputModalities.includes('image') : null;
|
|
208
|
+
}
|
|
209
|
+
cursor = typeof result.nextCursor === 'string' ? result.nextCursor : null;
|
|
210
|
+
if (cursor !== null) {
|
|
211
|
+
if (seen.has(cursor)) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
seen.add(cursor);
|
|
215
|
+
}
|
|
216
|
+
} while (cursor !== null);
|
|
217
|
+
} catch {
|
|
218
|
+
// Older app-servers may not report modalities; their turn response remains authoritative.
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
compact(): void {
|
|
224
|
+
this.turnUnnamed = true;
|
|
225
|
+
this.request('thread/compact/start', { threadId: this.threadId });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Not a turn: a name Codex refuses is not something the person has to hear about. */
|
|
229
|
+
setTitle(title: string): void {
|
|
230
|
+
if (!this.transport || this.threadId === '') {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
void this.transport.request('thread/name/set', { threadId: this.threadId, name: title }).catch(() => undefined);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Codex keeps its own skill index, so the running app-server is the authority on what it will run. */
|
|
237
|
+
async listSkills(): Promise<ChatSkill[]> {
|
|
238
|
+
const transport = this.transport;
|
|
239
|
+
if (!transport) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
return parseSkillsList(await transport.request('skills/list', { cwds: [this.launch.cwd] }));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
listThreadItems(params: { threadId: string; cursor?: string; limit: number; sortDirection: 'asc' | 'desc' }): Promise<unknown> {
|
|
246
|
+
const transport = this.transport;
|
|
247
|
+
if (!transport) {
|
|
248
|
+
return Promise.reject(new Error('thread/items/list failed: Codex is not running'));
|
|
249
|
+
}
|
|
250
|
+
return transport.request('thread/items/list', params);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interrupt(): void {
|
|
254
|
+
const turnId = this.protocol.turnId;
|
|
255
|
+
if (!this.transport) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!turnId) {
|
|
259
|
+
this.interruptOwed = this.turnUnnamed;
|
|
260
|
+
// A child request can open a host turn after the root's native turn already ended.
|
|
261
|
+
if (!this.turnUnnamed) {
|
|
262
|
+
this.emit({ type: 'turn.done', state: 'aborted', costUsd: 0 });
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
void this.transport.request('turn/interrupt', { threadId: this.threadId, turnId }).catch(() => {
|
|
267
|
+
// A turn that already ended has nothing to interrupt.
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
stopTask(taskId: string): void {
|
|
272
|
+
if (!this.transport) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
void this.transport.request('thread/backgroundTerminals/terminate', { threadId: this.threadId, processId: taskId }).catch(() => {
|
|
276
|
+
// A process that already ended has nothing to terminate; its item says so on its own.
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
respondApproval(requestId: string, decision: ApprovalDecision): boolean {
|
|
281
|
+
if (!this.transport) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
const answer = this.protocol.approvalDecision(requestId, decision);
|
|
285
|
+
if (!answer) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
return this.respond(requestId, answer.rpcId, answer.result);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
respondQuestion(requestId: string, answers: Record<string, string>): boolean {
|
|
292
|
+
if (!this.transport) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
const answer = this.protocol.questionAnswer(requestId, answers);
|
|
296
|
+
if (!answer) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
if (answer.kind === 'respond') {
|
|
300
|
+
return this.respond(requestId, answer.rpcId, answer.result);
|
|
301
|
+
}
|
|
302
|
+
const turnId = this.protocol.turnId;
|
|
303
|
+
if (!turnId) {
|
|
304
|
+
// Codex refuses a steer without the turn it is meant for, and there is none left to join.
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
this.request('turn/steer', { threadId: this.threadId, expectedTurnId: turnId, input: textInput(answer.text) });
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Codex asks its async question once and waits; forgetting it locally is all a dismissal is. */
|
|
312
|
+
dismissRequest(requestId: string): boolean {
|
|
313
|
+
return this.protocol.dismissQuestion(requestId);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
declineRequest(requestId: string, _message: string): boolean {
|
|
317
|
+
const answer = this.protocol.declineRequest(requestId);
|
|
318
|
+
if (!this.transport || !answer) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return this.respond(requestId, answer.rpcId, answer.result);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
stop(): void {
|
|
325
|
+
this.transport?.stop();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
dispose(): Promise<void> {
|
|
329
|
+
const transport = this.transport;
|
|
330
|
+
this.transport = null;
|
|
331
|
+
return transport?.dispose() ?? Promise.resolve();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* A request whose failure the person has to hear about, since it is the turn that cannot go on. */
|
|
335
|
+
private request(method: string, params: unknown, accepted?: () => void): void {
|
|
336
|
+
const transport = this.transport;
|
|
337
|
+
if (!transport) {
|
|
338
|
+
this.emit({ type: 'failed', message: `${method} failed: Codex is not running` });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
void transport
|
|
342
|
+
.request(method, params)
|
|
343
|
+
.then(() => {
|
|
344
|
+
if (this.transport === transport) {
|
|
345
|
+
accepted?.();
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
.catch((error: unknown) => {
|
|
349
|
+
if (this.transport === transport) {
|
|
350
|
+
this.emit({ type: 'failed', message: errorText(error) });
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private handleFrame(transport: CodexTransport, frame: CodexFrame): void {
|
|
356
|
+
if (this.transport !== transport) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const events = this.protocol.handle(frame);
|
|
360
|
+
// A server request the protocol did not take still needs an answer, or Codex waits forever.
|
|
361
|
+
if (frame.id !== undefined && events.length === 0 && (typeof frame.id === 'number' || typeof frame.id === 'string')) {
|
|
362
|
+
transport.respondError(frame.id, -32601, `${this.client.title} does not handle ${String(frame.method)}`);
|
|
363
|
+
}
|
|
364
|
+
if (frame.method === 'turn/started' && this.protocol.turnId !== null) {
|
|
365
|
+
this.acceptPrompt(this.promptId ?? undefined);
|
|
366
|
+
}
|
|
367
|
+
for (const event of events) {
|
|
368
|
+
this.emit(event);
|
|
369
|
+
}
|
|
370
|
+
if (this.turnUnnamed && this.protocol.turnId !== null) {
|
|
371
|
+
this.turnUnnamed = false;
|
|
372
|
+
if (this.interruptOwed) {
|
|
373
|
+
this.interruptOwed = false;
|
|
374
|
+
this.interrupt();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private handleExit(transport: CodexTransport, exitCode: number | null, stderr: string | null): void {
|
|
380
|
+
if (this.transport !== transport) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this.transport = null;
|
|
384
|
+
this.protocol.forgetPending();
|
|
385
|
+
this.emit({ type: 'exit', exitCode, ...(stderr === null ? {} : { stderr }) });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private emit(event: BackendEvent): void {
|
|
389
|
+
// A turn that ended or never started leaves no stop for the next one to take.
|
|
390
|
+
if (event.type === 'turn.done' || event.type === 'failed' || event.type === 'exit') {
|
|
391
|
+
this.turnUnnamed = false;
|
|
392
|
+
this.interruptOwed = false;
|
|
393
|
+
this.promptId = null;
|
|
394
|
+
}
|
|
395
|
+
this.host.onEvent(event);
|
|
396
|
+
}
|
|
397
|
+
}
|