@soederpop/luca 0.0.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/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
4
|
+
import type { Container } from '@soederpop/luca/container'
|
|
5
|
+
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
6
|
+
import { features, Feature } from '@soederpop/luca/feature'
|
|
7
|
+
|
|
8
|
+
declare module '@soederpop/luca/feature' {
|
|
9
|
+
interface AvailableFeatures {
|
|
10
|
+
openaiCodex: typeof OpenAICodex
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// --- Stream JSON types from the Codex CLI (codex exec --json) ---
|
|
15
|
+
|
|
16
|
+
export interface CodexItem {
|
|
17
|
+
id: string
|
|
18
|
+
type: 'agent_message' | 'reasoning' | 'command_execution' | string
|
|
19
|
+
text?: string
|
|
20
|
+
command?: string
|
|
21
|
+
aggregated_output?: string
|
|
22
|
+
exit_code?: number | null
|
|
23
|
+
status?: 'in_progress' | 'completed' | string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CodexItemEvent {
|
|
27
|
+
type: 'item.completed' | 'item.started'
|
|
28
|
+
item: CodexItem
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CodexTurnEvent {
|
|
32
|
+
type: 'turn.completed' | 'turn.started'
|
|
33
|
+
usage?: { input_tokens?: number; cached_input_tokens?: number; output_tokens?: number }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CodexThreadEvent {
|
|
37
|
+
type: 'thread.started'
|
|
38
|
+
thread_id: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Normalized message emitted via session:message for downstream consumers. */
|
|
42
|
+
export interface CodexMessageEvent {
|
|
43
|
+
type: 'message'
|
|
44
|
+
role: 'assistant' | 'system'
|
|
45
|
+
content: Array<{ type: 'text'; text: string } | { type: string; [key: string]: any }>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface CodexExecEvent {
|
|
49
|
+
type: 'exec'
|
|
50
|
+
command: string
|
|
51
|
+
cwd?: string
|
|
52
|
+
exit_code?: number | null
|
|
53
|
+
stdout?: string
|
|
54
|
+
stderr?: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type CodexEvent =
|
|
58
|
+
| CodexItemEvent
|
|
59
|
+
| CodexTurnEvent
|
|
60
|
+
| CodexThreadEvent
|
|
61
|
+
| { type: string; [key: string]: any }
|
|
62
|
+
|
|
63
|
+
// --- Session types ---
|
|
64
|
+
|
|
65
|
+
export interface CodexSession {
|
|
66
|
+
id: string
|
|
67
|
+
status: 'idle' | 'running' | 'completed' | 'error'
|
|
68
|
+
prompt: string
|
|
69
|
+
result?: string
|
|
70
|
+
error?: string
|
|
71
|
+
turns: number
|
|
72
|
+
messages: CodexMessageEvent[]
|
|
73
|
+
executions: CodexExecEvent[]
|
|
74
|
+
items: CodexItem[]
|
|
75
|
+
process?: any
|
|
76
|
+
threadId?: string
|
|
77
|
+
usage?: { input_tokens?: number; output_tokens?: number }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Feature state and options ---
|
|
81
|
+
|
|
82
|
+
export const OpenAICodexStateSchema = FeatureStateSchema.extend({
|
|
83
|
+
sessions: z.record(z.string(), z.any()).describe('Map of session IDs to CodexSession objects'),
|
|
84
|
+
activeSessions: z.array(z.string()).describe('List of currently running session IDs'),
|
|
85
|
+
codexAvailable: z.boolean().describe('Whether the codex CLI binary is available'),
|
|
86
|
+
codexVersion: z.string().optional().describe('Detected codex CLI version string'),
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
export const OpenAICodexOptionsSchema = FeatureOptionsSchema.extend({
|
|
90
|
+
codexPath: z.string().optional().describe('Path to the codex CLI binary'),
|
|
91
|
+
model: z.string().optional().describe('Default model to use for sessions'),
|
|
92
|
+
cwd: z.string().optional().describe('Default working directory for sessions'),
|
|
93
|
+
sandbox: z.enum(['read-only', 'workspace-write', 'danger-full-access']).optional().describe('Sandbox policy for shell commands'),
|
|
94
|
+
approvalMode: z.enum(['suggest', 'auto-edit', 'full-auto']).optional().describe('Approval mode for codex operations'),
|
|
95
|
+
projectDoc: z.string().optional().describe('Path to additional project doc to include'),
|
|
96
|
+
noProjectDoc: z.boolean().optional().describe('Disable automatic codex.md inclusion'),
|
|
97
|
+
fullStdout: z.boolean().optional().describe('Do not truncate stdout/stderr from command outputs'),
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export type OpenAICodexState = z.infer<typeof OpenAICodexStateSchema>
|
|
101
|
+
export type OpenAICodexOptions = z.infer<typeof OpenAICodexOptionsSchema>
|
|
102
|
+
|
|
103
|
+
export interface CodexRunOptions {
|
|
104
|
+
model?: string
|
|
105
|
+
cwd?: string
|
|
106
|
+
sandbox?: 'read-only' | 'workspace-write' | 'danger-full-access'
|
|
107
|
+
approvalMode?: 'suggest' | 'auto-edit' | 'full-auto'
|
|
108
|
+
projectDoc?: string
|
|
109
|
+
noProjectDoc?: boolean
|
|
110
|
+
fullStdout?: boolean
|
|
111
|
+
images?: string[]
|
|
112
|
+
fullAuto?: boolean
|
|
113
|
+
/** Resume a previous session by ID. */
|
|
114
|
+
resumeSessionId?: string
|
|
115
|
+
/** Resume the most recent session. */
|
|
116
|
+
resumeLast?: boolean
|
|
117
|
+
/** Skip all approvals and sandboxing. */
|
|
118
|
+
dangerouslyAutoApproveEverything?: boolean
|
|
119
|
+
/** Additional CLI flags. */
|
|
120
|
+
extraArgs?: string[]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* OpenAI Codex CLI wrapper feature. Spawns and manages Codex sessions
|
|
125
|
+
* as subprocesses, streaming structured JSON events back through the
|
|
126
|
+
* container's event system.
|
|
127
|
+
*
|
|
128
|
+
* Mirrors the ClaudeCode feature pattern: each call to `run()` spawns a
|
|
129
|
+
* `codex exec --json` process, parses NDJSON from stdout line-by-line,
|
|
130
|
+
* and emits typed events on the feature's event bus.
|
|
131
|
+
*
|
|
132
|
+
* @extends Feature
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const codex = container.feature('openaiCodex')
|
|
137
|
+
*
|
|
138
|
+
* // Listen for events
|
|
139
|
+
* codex.on('session:message', ({ sessionId, message }) => console.log(message))
|
|
140
|
+
* codex.on('session:patch', ({ sessionId, patch }) => console.log('File changed:', patch.path))
|
|
141
|
+
*
|
|
142
|
+
* // Run a prompt
|
|
143
|
+
* const session = await codex.run('Fix the failing tests in src/')
|
|
144
|
+
* console.log(session.result)
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export class OpenAICodex extends Feature<OpenAICodexState, OpenAICodexOptions> {
|
|
148
|
+
static override stateSchema = OpenAICodexStateSchema
|
|
149
|
+
static override optionsSchema = OpenAICodexOptionsSchema
|
|
150
|
+
static override shortcut = 'features.openaiCodex' as const
|
|
151
|
+
|
|
152
|
+
static attach(container: Container<AvailableFeatures, any>) {
|
|
153
|
+
container.features.register('openaiCodex', OpenAICodex)
|
|
154
|
+
return container
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
override get initialState(): OpenAICodexState {
|
|
158
|
+
return {
|
|
159
|
+
...super.initialState,
|
|
160
|
+
sessions: {},
|
|
161
|
+
activeSessions: [],
|
|
162
|
+
codexAvailable: false
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @returns The path to the codex CLI binary, falling back to 'codex' on the PATH. */
|
|
167
|
+
get codexPath(): string {
|
|
168
|
+
return this.options.codexPath || 'codex'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if the Codex CLI is available and capture its version.
|
|
173
|
+
*
|
|
174
|
+
* @returns {Promise<boolean>} True if the codex binary was found and responded to --version
|
|
175
|
+
*/
|
|
176
|
+
async checkAvailability(): Promise<boolean> {
|
|
177
|
+
try {
|
|
178
|
+
const proc = this.container.feature('proc')
|
|
179
|
+
const result = await proc.spawnAndCapture(this.codexPath, ['--version'])
|
|
180
|
+
const stdout = result.stdout
|
|
181
|
+
const exitCode = result.exitCode
|
|
182
|
+
|
|
183
|
+
if (exitCode === 0) {
|
|
184
|
+
const version = stdout.trim()
|
|
185
|
+
this.setState({ codexAvailable: true, codexVersion: version })
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.setState({ codexAvailable: false })
|
|
190
|
+
return false
|
|
191
|
+
} catch {
|
|
192
|
+
this.setState({ codexAvailable: false })
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Build the argument array for a codex CLI invocation.
|
|
199
|
+
*/
|
|
200
|
+
private buildArgs(options: CodexRunOptions = {}): string[] {
|
|
201
|
+
const args: string[] = ['exec', '--json']
|
|
202
|
+
|
|
203
|
+
const model = options.model ?? this.options.model
|
|
204
|
+
if (model) args.push('--model', model)
|
|
205
|
+
|
|
206
|
+
const sandbox = options.sandbox ?? this.options.sandbox
|
|
207
|
+
if (sandbox) args.push('--sandbox', sandbox)
|
|
208
|
+
|
|
209
|
+
if (options.fullAuto) {
|
|
210
|
+
args.push('--full-auto')
|
|
211
|
+
} else {
|
|
212
|
+
const approvalMode = options.approvalMode ?? this.options.approvalMode
|
|
213
|
+
if (approvalMode === 'auto-edit') args.push('--auto-edit')
|
|
214
|
+
else if (approvalMode === 'full-auto') args.push('--full-auto')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const noProjectDoc = options.noProjectDoc ?? this.options.noProjectDoc
|
|
218
|
+
if (noProjectDoc) args.push('--no-project-doc')
|
|
219
|
+
|
|
220
|
+
const projectDoc = options.projectDoc ?? this.options.projectDoc
|
|
221
|
+
if (projectDoc) args.push('--project-doc', projectDoc)
|
|
222
|
+
|
|
223
|
+
const fullStdout = options.fullStdout ?? this.options.fullStdout
|
|
224
|
+
if (fullStdout) args.push('--full-stdout')
|
|
225
|
+
|
|
226
|
+
if (options.images?.length) {
|
|
227
|
+
for (const img of options.images) {
|
|
228
|
+
args.push('--image', img)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (options.resumeSessionId) {
|
|
233
|
+
args.push('resume', options.resumeSessionId)
|
|
234
|
+
} else if (options.resumeLast) {
|
|
235
|
+
args.push('resume', '--last')
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (options.dangerouslyAutoApproveEverything) {
|
|
239
|
+
args.push('--dangerously-auto-approve-everything')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (options.extraArgs?.length) {
|
|
243
|
+
args.push(...options.extraArgs)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Read the prompt from stdin to avoid prompt content being parsed as flags.
|
|
247
|
+
args.push('-')
|
|
248
|
+
|
|
249
|
+
return args
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private createSessionId(): string {
|
|
253
|
+
return crypto.randomUUID()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private updateSession(id: string, update: Partial<CodexSession>): void {
|
|
257
|
+
const sessions = { ...this.state.current.sessions }
|
|
258
|
+
const existing = sessions[id]
|
|
259
|
+
if (existing) {
|
|
260
|
+
sessions[id] = { ...existing, ...update }
|
|
261
|
+
this.setState({ sessions })
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Process a parsed JSON event from the Codex CLI stream.
|
|
267
|
+
*
|
|
268
|
+
* The codex CLI (codex exec --json) emits NDJSON with these event types:
|
|
269
|
+
* thread.started — { thread_id }
|
|
270
|
+
* turn.started — (no payload)
|
|
271
|
+
* item.started — { item: { id, type, ... } }
|
|
272
|
+
* item.completed — { item: { id, type, text?, command?, exit_code?, ... } }
|
|
273
|
+
* turn.completed — { usage: { input_tokens, output_tokens } }
|
|
274
|
+
*
|
|
275
|
+
* Item types within item.completed:
|
|
276
|
+
* agent_message — assistant text response
|
|
277
|
+
* reasoning — model thinking/reasoning
|
|
278
|
+
* command_execution — shell command with output
|
|
279
|
+
*/
|
|
280
|
+
private handleEvent(sessionId: string, event: CodexEvent): void {
|
|
281
|
+
this.emit('session:event', { sessionId, event })
|
|
282
|
+
|
|
283
|
+
switch (event.type) {
|
|
284
|
+
case 'thread.started': {
|
|
285
|
+
const threadEvent = event as CodexThreadEvent
|
|
286
|
+
this.updateSession(sessionId, { threadId: threadEvent.thread_id })
|
|
287
|
+
break
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case 'turn.started': {
|
|
291
|
+
const session = this.state.current.sessions[sessionId]
|
|
292
|
+
if (session) {
|
|
293
|
+
this.updateSession(sessionId, { turns: session.turns + 1 })
|
|
294
|
+
}
|
|
295
|
+
break
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
case 'item.completed': {
|
|
299
|
+
const { item } = event as CodexItemEvent
|
|
300
|
+
const session = this.state.current.sessions[sessionId]
|
|
301
|
+
if (!session) break
|
|
302
|
+
|
|
303
|
+
this.updateSession(sessionId, { items: [...session.items, item] })
|
|
304
|
+
|
|
305
|
+
if (item.type === 'agent_message' && item.text) {
|
|
306
|
+
// Normalize to a CodexMessageEvent for downstream consumers
|
|
307
|
+
const msg: CodexMessageEvent = {
|
|
308
|
+
type: 'message',
|
|
309
|
+
role: 'assistant',
|
|
310
|
+
content: [{ type: 'text', text: item.text }]
|
|
311
|
+
}
|
|
312
|
+
this.updateSession(sessionId, { messages: [...session.messages, msg] })
|
|
313
|
+
this.emit('session:delta', { sessionId, text: item.text, role: 'assistant' })
|
|
314
|
+
this.emit('session:message', { sessionId, message: msg })
|
|
315
|
+
} else if (item.type === 'command_execution') {
|
|
316
|
+
const exec: CodexExecEvent = {
|
|
317
|
+
type: 'exec',
|
|
318
|
+
command: item.command || '',
|
|
319
|
+
exit_code: item.exit_code,
|
|
320
|
+
stdout: item.aggregated_output,
|
|
321
|
+
}
|
|
322
|
+
this.updateSession(sessionId, { executions: [...session.executions, exec] })
|
|
323
|
+
this.emit('session:exec', { sessionId, exec })
|
|
324
|
+
} else if (item.type === 'reasoning' && item.text) {
|
|
325
|
+
this.emit('session:reasoning', { sessionId, text: item.text })
|
|
326
|
+
}
|
|
327
|
+
break
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
case 'item.started': {
|
|
331
|
+
const { item } = event as CodexItemEvent
|
|
332
|
+
if (item.type === 'command_execution' && item.command) {
|
|
333
|
+
this.emit('session:exec-start', { sessionId, command: item.command })
|
|
334
|
+
}
|
|
335
|
+
break
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
case 'turn.completed': {
|
|
339
|
+
const turnEvent = event as CodexTurnEvent
|
|
340
|
+
if (turnEvent.usage) {
|
|
341
|
+
this.updateSession(sessionId, { usage: turnEvent.usage })
|
|
342
|
+
}
|
|
343
|
+
break
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
default: {
|
|
347
|
+
// Forward unknown events for extensibility
|
|
348
|
+
this.emit(`session:${event.type}`, { sessionId, event })
|
|
349
|
+
break
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Run a prompt in a new Codex session. Spawns a subprocess,
|
|
356
|
+
* streams NDJSON events, and resolves when the session completes.
|
|
357
|
+
*
|
|
358
|
+
* @param {string} prompt - The natural language instruction for the Codex agent
|
|
359
|
+
* @param {CodexRunOptions} [options] - Optional overrides for model, cwd, sandbox policy, etc.
|
|
360
|
+
* @returns {Promise<CodexSession>} The completed session with result, messages, patches, and executions
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```typescript
|
|
364
|
+
* const session = await codex.run('Fix the failing tests')
|
|
365
|
+
* console.log(session.result)
|
|
366
|
+
*
|
|
367
|
+
* const session = await codex.run('Refactor the auth module', {
|
|
368
|
+
* model: 'o4-mini',
|
|
369
|
+
* fullAuto: true,
|
|
370
|
+
* cwd: '/path/to/project'
|
|
371
|
+
* })
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
async run(prompt: string, options: CodexRunOptions = {}): Promise<CodexSession> {
|
|
375
|
+
const id = this.createSessionId()
|
|
376
|
+
const args = this.buildArgs(options)
|
|
377
|
+
const cwd = options.cwd ?? this.options.cwd ?? (this.container as any).cwd
|
|
378
|
+
|
|
379
|
+
const session: CodexSession = {
|
|
380
|
+
id,
|
|
381
|
+
status: 'running',
|
|
382
|
+
prompt,
|
|
383
|
+
turns: 0,
|
|
384
|
+
messages: [],
|
|
385
|
+
executions: [],
|
|
386
|
+
items: []
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const sessions = { ...this.state.current.sessions, [id]: session }
|
|
390
|
+
const activeSessions = [...this.state.current.activeSessions, id]
|
|
391
|
+
this.setState({ sessions, activeSessions })
|
|
392
|
+
|
|
393
|
+
this.emit('session:start', { sessionId: id, prompt })
|
|
394
|
+
|
|
395
|
+
const proc = this.container.feature('proc').spawn(this.codexPath, args, {
|
|
396
|
+
cwd,
|
|
397
|
+
stdout: 'pipe',
|
|
398
|
+
stderr: 'pipe',
|
|
399
|
+
stdin: Buffer.from(prompt),
|
|
400
|
+
environment: { ...process.env },
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
this.updateSession(id, { process: proc })
|
|
404
|
+
await this.consumeStream(id, proc)
|
|
405
|
+
|
|
406
|
+
return this.state.current.sessions[id]!
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Run a prompt without waiting for completion. Returns the session ID
|
|
411
|
+
* immediately so you can subscribe to events.
|
|
412
|
+
*
|
|
413
|
+
* @param {string} prompt - The natural language instruction for the Codex agent
|
|
414
|
+
* @param {CodexRunOptions} [options] - Optional overrides for model, cwd, sandbox policy, etc.
|
|
415
|
+
* @returns {string} The session ID, which can be used with getSession() or waitForSession()
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```typescript
|
|
419
|
+
* const sessionId = codex.start('Build a REST API for users')
|
|
420
|
+
*
|
|
421
|
+
* codex.on('session:delta', ({ sessionId: sid, text }) => {
|
|
422
|
+
* if (sid === sessionId) process.stdout.write(text)
|
|
423
|
+
* })
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
start(prompt: string, options: CodexRunOptions = {}): string {
|
|
427
|
+
const id = this.createSessionId()
|
|
428
|
+
const args = this.buildArgs(options)
|
|
429
|
+
const cwd = options.cwd ?? this.options.cwd ?? (this.container as any).cwd
|
|
430
|
+
|
|
431
|
+
const session: CodexSession = {
|
|
432
|
+
id,
|
|
433
|
+
status: 'running',
|
|
434
|
+
prompt,
|
|
435
|
+
turns: 0,
|
|
436
|
+
messages: [],
|
|
437
|
+
executions: [],
|
|
438
|
+
items: []
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const sessions = { ...this.state.current.sessions, [id]: session }
|
|
442
|
+
const activeSessions = [...this.state.current.activeSessions, id]
|
|
443
|
+
this.setState({ sessions, activeSessions })
|
|
444
|
+
|
|
445
|
+
this.emit('session:start', { sessionId: id, prompt })
|
|
446
|
+
|
|
447
|
+
const proc = this.container.feature('proc').spawn(this.codexPath, args, {
|
|
448
|
+
cwd,
|
|
449
|
+
stdout: 'pipe',
|
|
450
|
+
stderr: 'pipe',
|
|
451
|
+
stdin: Buffer.from(prompt),
|
|
452
|
+
environment: { ...process.env },
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
this.updateSession(id, { process: proc })
|
|
456
|
+
this.consumeStream(id, proc)
|
|
457
|
+
|
|
458
|
+
return id
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private async consumeStream(sessionId: string, proc: any): Promise<void> {
|
|
462
|
+
if (!proc?.stdout || !proc?.stderr) {
|
|
463
|
+
const error = 'Process streams are not available'
|
|
464
|
+
this.updateSession(sessionId, { status: 'error', error })
|
|
465
|
+
this.emit('session:error', { sessionId, error })
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let buffer = ''
|
|
470
|
+
let lastText = ''
|
|
471
|
+
let stderr = ''
|
|
472
|
+
|
|
473
|
+
proc.stderr.on('data', (chunk: Buffer | string) => {
|
|
474
|
+
stderr += Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk)
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
const stdoutDone = new Promise<void>((resolve, reject) => {
|
|
478
|
+
proc.stdout.on('data', (chunk: Buffer | string) => {
|
|
479
|
+
buffer += Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk)
|
|
480
|
+
const lines = buffer.split('\n')
|
|
481
|
+
buffer = lines.pop() || ''
|
|
482
|
+
|
|
483
|
+
for (const line of lines) {
|
|
484
|
+
const trimmed = line.trim()
|
|
485
|
+
if (!trimmed) continue
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
const event = JSON.parse(trimmed) as CodexEvent
|
|
489
|
+
this.handleEvent(sessionId, event)
|
|
490
|
+
|
|
491
|
+
if (event.type === 'item.completed') {
|
|
492
|
+
const { item } = event as CodexItemEvent
|
|
493
|
+
if (item.type === 'agent_message' && item.text) {
|
|
494
|
+
lastText = item.text
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
this.emit('session:parse-error', { sessionId, line: trimmed })
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
proc.stdout.on('end', () => {
|
|
504
|
+
if (buffer.trim()) {
|
|
505
|
+
try {
|
|
506
|
+
const event = JSON.parse(buffer.trim()) as CodexEvent
|
|
507
|
+
this.handleEvent(sessionId, event)
|
|
508
|
+
} catch {
|
|
509
|
+
// ignore trailing partial data
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
resolve()
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
proc.stdout.on('error', reject)
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
const exitCodePromise = new Promise<number>((resolve, reject) => {
|
|
519
|
+
proc.once('error', reject)
|
|
520
|
+
proc.once('close', (code: number | null) => resolve(code ?? 0))
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
await stdoutDone
|
|
525
|
+
} catch (err) {
|
|
526
|
+
this.updateSession(sessionId, {
|
|
527
|
+
status: 'error',
|
|
528
|
+
error: err instanceof Error ? err.message : String(err)
|
|
529
|
+
})
|
|
530
|
+
this.emit('session:error', { sessionId, error: err })
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
let exitCode = 1
|
|
534
|
+
try {
|
|
535
|
+
exitCode = await exitCodePromise
|
|
536
|
+
} catch (err) {
|
|
537
|
+
this.updateSession(sessionId, {
|
|
538
|
+
status: 'error',
|
|
539
|
+
error: err instanceof Error ? err.message : String(err),
|
|
540
|
+
})
|
|
541
|
+
this.emit('session:error', { sessionId, error: err })
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (exitCode !== 0 && this.state.current.sessions[sessionId]?.status !== 'completed') {
|
|
545
|
+
this.updateSession(sessionId, {
|
|
546
|
+
status: 'error',
|
|
547
|
+
error: stderr || `Process exited with code ${exitCode}`
|
|
548
|
+
})
|
|
549
|
+
this.emit('session:error', { sessionId, error: stderr, exitCode })
|
|
550
|
+
} else if (this.state.current.sessions[sessionId]?.status === 'running') {
|
|
551
|
+
this.updateSession(sessionId, {
|
|
552
|
+
status: 'completed',
|
|
553
|
+
result: lastText || undefined
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
const activeSessions = this.state.current.activeSessions.filter(s => s !== sessionId)
|
|
557
|
+
this.setState({ activeSessions })
|
|
558
|
+
|
|
559
|
+
this.emit('session:result', {
|
|
560
|
+
sessionId,
|
|
561
|
+
result: lastText,
|
|
562
|
+
})
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Kill a running session's subprocess.
|
|
568
|
+
*
|
|
569
|
+
* @param {string} sessionId - The session ID to abort
|
|
570
|
+
* @returns {void}
|
|
571
|
+
*/
|
|
572
|
+
abort(sessionId: string): void {
|
|
573
|
+
const session = this.state.current.sessions[sessionId]
|
|
574
|
+
if (session?.process && session.status === 'running') {
|
|
575
|
+
session.process.kill()
|
|
576
|
+
this.updateSession(sessionId, { status: 'error', error: 'Aborted by user' })
|
|
577
|
+
const activeSessions = this.state.current.activeSessions.filter(id => id !== sessionId)
|
|
578
|
+
this.setState({ activeSessions })
|
|
579
|
+
this.emit('session:abort', { sessionId })
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Retrieve the current state of a session by its ID.
|
|
585
|
+
*
|
|
586
|
+
* @param {string} sessionId - The session ID to look up
|
|
587
|
+
* @returns {CodexSession | undefined} The session object, or undefined if not found
|
|
588
|
+
*/
|
|
589
|
+
getSession(sessionId: string): CodexSession | undefined {
|
|
590
|
+
return this.state.current.sessions[sessionId]
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Wait for a running session to complete or error. Resolves immediately
|
|
595
|
+
* if the session is already in a terminal state.
|
|
596
|
+
*
|
|
597
|
+
* @param {string} sessionId - The session ID to wait for
|
|
598
|
+
* @returns {Promise<CodexSession>} The completed or errored session
|
|
599
|
+
* @throws {Error} If the session ID is not found
|
|
600
|
+
*/
|
|
601
|
+
async waitForSession(sessionId: string): Promise<CodexSession> {
|
|
602
|
+
const session = this.state.current.sessions[sessionId]
|
|
603
|
+
if (!session) throw new Error(`Session ${sessionId} not found`)
|
|
604
|
+
if (session.status === 'completed' || session.status === 'error') return session
|
|
605
|
+
|
|
606
|
+
return new Promise((resolve) => {
|
|
607
|
+
const handler = (data: { sessionId: string }) => {
|
|
608
|
+
if (data.sessionId === sessionId) {
|
|
609
|
+
this.off('session:result')
|
|
610
|
+
this.off('session:error')
|
|
611
|
+
resolve(this.state.current.sessions[sessionId]!)
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
this.on('session:result', handler)
|
|
615
|
+
this.on('session:error', handler)
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Enable the feature. Delegates to the base Feature enable() lifecycle.
|
|
621
|
+
*
|
|
622
|
+
* @param {object} [options] - Options to merge into the feature configuration
|
|
623
|
+
* @returns {Promise<this>} This instance, for chaining
|
|
624
|
+
*/
|
|
625
|
+
override async enable(options: any = {}): Promise<this> {
|
|
626
|
+
await super.enable(options)
|
|
627
|
+
return this
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export default features.register('openaiCodex', OpenAICodex)
|