@twelvehart/orcats 0.0.0-seed.0 → 0.2.3
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 +48 -0
- package/NOTICE +5 -0
- package/README.md +399 -0
- package/bin/orcats +4 -0
- package/dist/backends/acp-client.d.ts +74 -0
- package/dist/backends/acp-client.d.ts.map +1 -0
- package/dist/backends/acp-run.d.ts +31 -0
- package/dist/backends/acp-run.d.ts.map +1 -0
- package/dist/backends/claude-run.d.ts +35 -0
- package/dist/backends/claude-run.d.ts.map +1 -0
- package/dist/backends/claude-stream-json.d.ts +15 -0
- package/dist/backends/claude-stream-json.d.ts.map +1 -0
- package/dist/backends/codex-jsonl.d.ts +34 -0
- package/dist/backends/codex-jsonl.d.ts.map +1 -0
- package/dist/backends/codex-run.d.ts +40 -0
- package/dist/backends/codex-run.d.ts.map +1 -0
- package/dist/backends/codex.d.ts +5 -0
- package/dist/backends/codex.d.ts.map +1 -0
- package/dist/backends/conversation-config.d.ts +12 -0
- package/dist/backends/conversation-config.d.ts.map +1 -0
- package/dist/backends/index.d.ts +14 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/opencode-run.d.ts +48 -0
- package/dist/backends/opencode-run.d.ts.map +1 -0
- package/dist/backends/opencode-sse.d.ts +35 -0
- package/dist/backends/opencode-sse.d.ts.map +1 -0
- package/dist/backends/pi-rpc.d.ts +21 -0
- package/dist/backends/pi-rpc.d.ts.map +1 -0
- package/dist/backends/pi-run.d.ts +21 -0
- package/dist/backends/pi-run.d.ts.map +1 -0
- package/dist/backends/select.d.ts +17 -0
- package/dist/backends/select.d.ts.map +1 -0
- package/dist/backends/subprocess-run.d.ts +68 -0
- package/dist/backends/subprocess-run.d.ts.map +1 -0
- package/dist/backends/types.d.ts +17 -0
- package/dist/backends/types.d.ts.map +1 -0
- package/dist/backends/unsupported.d.ts +9 -0
- package/dist/backends/unsupported.d.ts.map +1 -0
- package/dist/baseline/index.d.ts +56 -0
- package/dist/baseline/index.d.ts.map +1 -0
- package/dist/cli/args.d.ts +22 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/embedded.d.ts +7 -0
- package/dist/cli/embedded.d.ts.map +1 -0
- package/dist/cli/main.d.ts +6 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/conversation/ask-user.d.ts +17 -0
- package/dist/conversation/ask-user.d.ts.map +1 -0
- package/dist/conversation/conversation.d.ts +46 -0
- package/dist/conversation/conversation.d.ts.map +1 -0
- package/dist/conversation/harness.d.ts +14 -0
- package/dist/conversation/harness.d.ts.map +1 -0
- package/dist/conversation/index.d.ts +5 -0
- package/dist/conversation/index.d.ts.map +1 -0
- package/dist/conversation/queue.d.ts +15 -0
- package/dist/conversation/queue.d.ts.map +1 -0
- package/dist/flow/accessors.d.ts +13 -0
- package/dist/flow/accessors.d.ts.map +1 -0
- package/dist/flow/approvals.d.ts +6 -0
- package/dist/flow/approvals.d.ts.map +1 -0
- package/dist/flow/args.d.ts +4 -0
- package/dist/flow/args.d.ts.map +1 -0
- package/dist/flow/context.d.ts +36 -0
- package/dist/flow/context.d.ts.map +1 -0
- package/dist/flow/index.d.ts +5 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/loop/builder/index.d.ts +17 -0
- package/dist/loop/builder/index.d.ts.map +1 -0
- package/dist/loop/builder/types.d.ts +97 -0
- package/dist/loop/builder/types.d.ts.map +1 -0
- package/dist/loop/context/compaction.d.ts +38 -0
- package/dist/loop/context/compaction.d.ts.map +1 -0
- package/dist/loop/context/index.d.ts +4 -0
- package/dist/loop/context/index.d.ts.map +1 -0
- package/dist/loop/context/offload.d.ts +34 -0
- package/dist/loop/context/offload.d.ts.map +1 -0
- package/dist/loop/context/types.d.ts +13 -0
- package/dist/loop/context/types.d.ts.map +1 -0
- package/dist/loop/engine/bridge.d.ts +21 -0
- package/dist/loop/engine/bridge.d.ts.map +1 -0
- package/dist/loop/engine/index.d.ts +73 -0
- package/dist/loop/engine/index.d.ts.map +1 -0
- package/dist/loop/execution.d.ts +68 -0
- package/dist/loop/execution.d.ts.map +1 -0
- package/dist/loop/fanout.d.ts +148 -0
- package/dist/loop/fanout.d.ts.map +1 -0
- package/dist/loop/firing.d.ts +54 -0
- package/dist/loop/firing.d.ts.map +1 -0
- package/dist/loop/graph/analyze.d.ts +8 -0
- package/dist/loop/graph/analyze.d.ts.map +1 -0
- package/dist/loop/graph/index.d.ts +3 -0
- package/dist/loop/graph/index.d.ts.map +1 -0
- package/dist/loop/graph/types.d.ts +35 -0
- package/dist/loop/graph/types.d.ts.map +1 -0
- package/dist/loop/index.d.ts +7 -0
- package/dist/loop/index.d.ts.map +1 -0
- package/dist/loop/io/index.d.ts +4 -0
- package/dist/loop/io/index.d.ts.map +1 -0
- package/dist/loop/io/linear.d.ts +142 -0
- package/dist/loop/io/linear.d.ts.map +1 -0
- package/dist/loop/io/sink.d.ts +47 -0
- package/dist/loop/io/sink.d.ts.map +1 -0
- package/dist/loop/io/source.d.ts +64 -0
- package/dist/loop/io/source.d.ts.map +1 -0
- package/dist/loop/serve.d.ts +97 -0
- package/dist/loop/serve.d.ts.map +1 -0
- package/dist/loop/state/index.d.ts +5 -0
- package/dist/loop/state/index.d.ts.map +1 -0
- package/dist/loop/state/manifest.d.ts +36 -0
- package/dist/loop/state/manifest.d.ts.map +1 -0
- package/dist/loop/state/port.d.ts +33 -0
- package/dist/loop/state/port.d.ts.map +1 -0
- package/dist/loop/state/snapshot.d.ts +15 -0
- package/dist/loop/state/snapshot.d.ts.map +1 -0
- package/dist/loop/state/sqlite.d.ts +31 -0
- package/dist/loop/state/sqlite.d.ts.map +1 -0
- package/dist/loop/termination-contract.d.ts +28 -0
- package/dist/loop/termination-contract.d.ts.map +1 -0
- package/dist/loop/termination.d.ts +33 -0
- package/dist/loop/termination.d.ts.map +1 -0
- package/dist/model/backend-config.d.ts +30 -0
- package/dist/model/backend-config.d.ts.map +1 -0
- package/dist/model/brand.d.ts +9 -0
- package/dist/model/brand.d.ts.map +1 -0
- package/dist/model/errors.d.ts +11 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/index.d.ts +8 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/json-schema.d.ts +4 -0
- package/dist/model/json-schema.d.ts.map +1 -0
- package/dist/model/result.d.ts +3 -0
- package/dist/model/result.d.ts.map +1 -0
- package/dist/model/schemas.d.ts +435 -0
- package/dist/model/schemas.d.ts.map +1 -0
- package/dist/model/structured.d.ts +9 -0
- package/dist/model/structured.d.ts.map +1 -0
- package/dist/monitor/index.d.ts +139 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/plan/index.d.ts +3 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/persistent.d.ts +23 -0
- package/dist/plan/persistent.d.ts.map +1 -0
- package/dist/plan/verdict.d.ts +9 -0
- package/dist/plan/verdict.d.ts.map +1 -0
- package/dist/review/index.d.ts +4 -0
- package/dist/review/index.d.ts.map +1 -0
- package/dist/review/loop.d.ts +117 -0
- package/dist/review/loop.d.ts.map +1 -0
- package/dist/review/reviewers.d.ts +13 -0
- package/dist/review/reviewers.d.ts.map +1 -0
- package/dist/review/tool.d.ts +20 -0
- package/dist/review/tool.d.ts.map +1 -0
- package/dist/run-output/index.d.ts +112 -0
- package/dist/run-output/index.d.ts.map +1 -0
- package/dist/runner/index.d.ts +2 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/typecheck.d.ts +23 -0
- package/dist/runner/typecheck.d.ts.map +1 -0
- package/dist/test-utils/fakes.d.ts +76 -0
- package/dist/test-utils/fakes.d.ts.map +1 -0
- package/dist/test-utils/fixtures.d.ts +4 -0
- package/dist/test-utils/fixtures.d.ts.map +1 -0
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/tier2.d.ts +8 -0
- package/dist/test-utils/tier2.d.ts.map +1 -0
- package/dist/tools/fs.d.ts +11 -0
- package/dist/tools/fs.d.ts.map +1 -0
- package/dist/tools/git.d.ts +10 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/github.d.ts +13 -0
- package/dist/tools/github.d.ts.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/linear.d.ts +117 -0
- package/dist/tools/linear.d.ts.map +1 -0
- package/dist/tools/process.d.ts +40 -0
- package/dist/tools/process.d.ts.map +1 -0
- package/dist/tools/terminal.d.ts +23 -0
- package/dist/tools/terminal.d.ts.map +1 -0
- package/package.json +89 -9
- package/src/backends/acp-client.ts +324 -0
- package/src/backends/acp-run.ts +636 -0
- package/src/backends/claude-run.ts +226 -0
- package/src/backends/claude-stream-json.ts +273 -0
- package/src/backends/codex-jsonl.ts +339 -0
- package/src/backends/codex-run.ts +177 -0
- package/src/backends/codex.ts +84 -0
- package/src/backends/conversation-config.ts +26 -0
- package/src/backends/index.ts +13 -0
- package/src/backends/opencode-run.ts +432 -0
- package/src/backends/opencode-sse.ts +360 -0
- package/src/backends/pi-rpc.ts +242 -0
- package/src/backends/pi-run.ts +154 -0
- package/src/backends/select.ts +77 -0
- package/src/backends/subprocess-run.ts +305 -0
- package/src/backends/types.ts +29 -0
- package/src/backends/unsupported.ts +48 -0
- package/src/baseline/index.ts +437 -0
- package/src/cli/args.ts +109 -0
- package/src/cli/embedded.ts +206 -0
- package/src/cli/main.ts +332 -0
- package/src/cli/version.ts +1 -0
- package/src/conversation/ask-user.ts +105 -0
- package/src/conversation/conversation.ts +109 -0
- package/src/conversation/harness.ts +39 -0
- package/src/conversation/index.ts +4 -0
- package/src/conversation/queue.ts +86 -0
- package/src/flow/accessors.ts +43 -0
- package/src/flow/approvals.ts +10 -0
- package/src/flow/args.ts +26 -0
- package/src/flow/context.ts +106 -0
- package/src/flow/index.ts +4 -0
- package/src/index.ts +17 -0
- package/src/loop/builder/index.ts +346 -0
- package/src/loop/builder/types.ts +131 -0
- package/src/loop/context/compaction.ts +175 -0
- package/src/loop/context/index.ts +3 -0
- package/src/loop/context/offload.ts +79 -0
- package/src/loop/context/types.ts +17 -0
- package/src/loop/engine/bridge.ts +61 -0
- package/src/loop/engine/index.ts +166 -0
- package/src/loop/execution.ts +453 -0
- package/src/loop/fanout.ts +444 -0
- package/src/loop/firing.ts +188 -0
- package/src/loop/graph/analyze.ts +128 -0
- package/src/loop/graph/index.ts +2 -0
- package/src/loop/graph/types.ts +45 -0
- package/src/loop/index.ts +11 -0
- package/src/loop/io/index.ts +3 -0
- package/src/loop/io/linear.ts +714 -0
- package/src/loop/io/sink.ts +170 -0
- package/src/loop/io/source.ts +255 -0
- package/src/loop/serve.ts +307 -0
- package/src/loop/state/index.ts +4 -0
- package/src/loop/state/manifest.ts +56 -0
- package/src/loop/state/port.ts +43 -0
- package/src/loop/state/snapshot.ts +129 -0
- package/src/loop/state/sqlite.ts +265 -0
- package/src/loop/termination-contract.ts +89 -0
- package/src/loop/termination.ts +156 -0
- package/src/model/backend-config.ts +34 -0
- package/src/model/brand.ts +11 -0
- package/src/model/errors.ts +28 -0
- package/src/model/index.ts +7 -0
- package/src/model/json-schema.ts +14 -0
- package/src/model/result.ts +28 -0
- package/src/model/schemas.ts +218 -0
- package/src/model/structured.ts +26 -0
- package/src/monitor/index.ts +367 -0
- package/src/plan/index.ts +2 -0
- package/src/plan/persistent.ts +98 -0
- package/src/plan/verdict.ts +3 -0
- package/src/review/index.ts +3 -0
- package/src/review/loop.ts +365 -0
- package/src/review/reviewers.ts +92 -0
- package/src/review/tool.ts +41 -0
- package/src/run-output/index.ts +319 -0
- package/src/runner/index.ts +1 -0
- package/src/runner/typecheck.ts +72 -0
- package/src/test-utils/fakes.ts +260 -0
- package/src/test-utils/fixtures.ts +33 -0
- package/src/test-utils/index.ts +3 -0
- package/src/test-utils/tier2.ts +12 -0
- package/src/tools/fs.ts +44 -0
- package/src/tools/git.ts +34 -0
- package/src/tools/github.ts +25 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/linear.ts +428 -0
- package/src/tools/process.ts +156 -0
- package/src/tools/terminal.ts +116 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
claudeAcpCommand,
|
|
4
|
+
runAcpConversation
|
|
5
|
+
} from "./acp-run.ts";
|
|
6
|
+
import {
|
|
7
|
+
createClaudeStreamConsumer,
|
|
8
|
+
type ClaudeStreamOptions
|
|
9
|
+
} from "./claude-stream-json.ts";
|
|
10
|
+
import { composeBackendPrompt } from "./conversation-config.ts";
|
|
11
|
+
import {
|
|
12
|
+
errorMessage,
|
|
13
|
+
runSubprocessConversation,
|
|
14
|
+
type SubprocessProcess,
|
|
15
|
+
type SubprocessSpawner
|
|
16
|
+
} from "./subprocess-run.ts";
|
|
17
|
+
import type { AutonomousRequest, LlmBackend } from "./types.ts";
|
|
18
|
+
import { StreamConversation } from "../conversation/index.ts";
|
|
19
|
+
import { backendFailed, jsonSchemaFromZod, type BackendConfig } from "../model/index.ts";
|
|
20
|
+
|
|
21
|
+
export type ClaudeProcess = SubprocessProcess;
|
|
22
|
+
export type ClaudeProcessSpawner = SubprocessSpawner;
|
|
23
|
+
export type ClaudeTransport = "acp" | "stream-json";
|
|
24
|
+
|
|
25
|
+
export interface ClaudeBackendOptions {
|
|
26
|
+
readonly command?: string;
|
|
27
|
+
readonly cwd?: string;
|
|
28
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
29
|
+
readonly capacity?: number;
|
|
30
|
+
readonly spawnProcess?: ClaudeProcessSpawner;
|
|
31
|
+
readonly spawnAcpProcess?: import("./acp-client.ts").AcpProcessSpawner;
|
|
32
|
+
readonly transport?: ClaudeTransport;
|
|
33
|
+
readonly acpCancelTimeoutMs?: number;
|
|
34
|
+
readonly config?: BackendConfig<"claude">;
|
|
35
|
+
readonly inactivityTimeoutMs?: number;
|
|
36
|
+
readonly wallClockTimeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ResolvedClaudeConfig<Output> {
|
|
40
|
+
model?: string;
|
|
41
|
+
systemPrompt?: string;
|
|
42
|
+
readOnly?: boolean;
|
|
43
|
+
selfManagedGit?: boolean;
|
|
44
|
+
retryAttempts?: number;
|
|
45
|
+
schema?: z.ZodType<Output>;
|
|
46
|
+
resumeSessionId?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function claudeStreamJsonArgs<Output>(config: ResolvedClaudeConfig<Output>): readonly string[] {
|
|
50
|
+
return [
|
|
51
|
+
"--print",
|
|
52
|
+
"--input-format",
|
|
53
|
+
"stream-json",
|
|
54
|
+
"--output-format",
|
|
55
|
+
"stream-json",
|
|
56
|
+
"--verbose",
|
|
57
|
+
"--include-partial-messages",
|
|
58
|
+
...(config.model ? ["--model", config.model] : []),
|
|
59
|
+
...(config.resumeSessionId ? ["--resume", config.resumeSessionId] : []),
|
|
60
|
+
"--permission-mode",
|
|
61
|
+
config.readOnly ? "plan" : "bypassPermissions",
|
|
62
|
+
...(config.schema ? ["--json-schema", JSON.stringify(jsonSchemaFromZod(config.schema))] : [])
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function userTurnLine(text: string): string {
|
|
67
|
+
return JSON.stringify({
|
|
68
|
+
type: "user",
|
|
69
|
+
message: { role: "user", content: [{ type: "text", text }] }
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function runClaudeConversation<Output>(
|
|
74
|
+
request: AutonomousRequest<Output, "claude">,
|
|
75
|
+
options: ClaudeBackendOptions,
|
|
76
|
+
conversation: StreamConversation<"claude">,
|
|
77
|
+
setProcess: (process: ClaudeProcess) => void
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
const command = options.command ?? "claude";
|
|
80
|
+
const config = resolveClaudeConfig(request, options);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const args = claudeStreamJsonArgs(config);
|
|
84
|
+
const consumerOptions: ClaudeStreamOptions<Output> =
|
|
85
|
+
config.schema === undefined ? {} : { schema: config.schema };
|
|
86
|
+
|
|
87
|
+
await runSubprocessConversation({
|
|
88
|
+
backend: "claude",
|
|
89
|
+
command,
|
|
90
|
+
args,
|
|
91
|
+
conversation,
|
|
92
|
+
setProcess,
|
|
93
|
+
stdin: "pipe",
|
|
94
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
95
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
96
|
+
...(options.spawnProcess === undefined ? {} : { spawnProcess: options.spawnProcess }),
|
|
97
|
+
...(options.inactivityTimeoutMs === undefined ? {} : { inactivityTimeoutMs: options.inactivityTimeoutMs }),
|
|
98
|
+
...(options.wallClockTimeoutMs === undefined ? {} : { wallClockTimeoutMs: options.wallClockTimeoutMs }),
|
|
99
|
+
onStart: (process) => {
|
|
100
|
+
process.write?.(`${userTurnLine(composeBackendPrompt(request.prompt, config))}\n`);
|
|
101
|
+
process.endStdin?.();
|
|
102
|
+
},
|
|
103
|
+
createConsumer: () => createClaudeStreamConsumer(conversation, consumerOptions)
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (!conversation.signal.aborted) {
|
|
107
|
+
conversation.fail(backendFailed("claude", errorMessage(error)));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveClaudeConfig<Output>(
|
|
113
|
+
request: AutonomousRequest<Output, "claude">,
|
|
114
|
+
options: ClaudeBackendOptions
|
|
115
|
+
): ResolvedClaudeConfig<Output> {
|
|
116
|
+
const optionConfig = options.config;
|
|
117
|
+
const requestConfig = request.config;
|
|
118
|
+
const config: ResolvedClaudeConfig<Output> = {};
|
|
119
|
+
const model = requestConfig?.model ?? optionConfig?.model;
|
|
120
|
+
if (model !== undefined) config.model = model;
|
|
121
|
+
const systemPrompt = requestConfig?.systemPrompt ?? optionConfig?.systemPrompt;
|
|
122
|
+
if (systemPrompt !== undefined) config.systemPrompt = systemPrompt;
|
|
123
|
+
const readOnly = requestConfig?.readOnly ?? optionConfig?.readOnly;
|
|
124
|
+
if (readOnly !== undefined) config.readOnly = readOnly;
|
|
125
|
+
const selfManagedGit = requestConfig?.selfManagedGit ?? optionConfig?.selfManagedGit;
|
|
126
|
+
if (selfManagedGit !== undefined) config.selfManagedGit = selfManagedGit;
|
|
127
|
+
const retryAttempts = requestConfig?.retry?.attempts ?? optionConfig?.retry?.attempts;
|
|
128
|
+
if (retryAttempts !== undefined) config.retryAttempts = retryAttempts;
|
|
129
|
+
const schema =
|
|
130
|
+
requestConfig?.structuredOutput?.schema ??
|
|
131
|
+
request.schema ??
|
|
132
|
+
(optionConfig?.structuredOutput?.schema as z.ZodType<Output> | undefined);
|
|
133
|
+
if (schema !== undefined) config.schema = schema;
|
|
134
|
+
if (requestConfig?.resumeSessionId !== undefined)
|
|
135
|
+
config.resumeSessionId = String(requestConfig.resumeSessionId);
|
|
136
|
+
return config;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function claude(options: ClaudeBackendOptions = {}): LlmBackend<"claude"> {
|
|
140
|
+
return {
|
|
141
|
+
tag: "claude",
|
|
142
|
+
autonomous<Output = unknown>(request: AutonomousRequest<Output, "claude">) {
|
|
143
|
+
let child: ClaudeProcess | undefined;
|
|
144
|
+
let cancelAcp: (() => Promise<void>) | undefined;
|
|
145
|
+
const config = resolveClaudeConfig(request, options);
|
|
146
|
+
const transport = resolveClaudeTransport(options, config);
|
|
147
|
+
const useAcp = transport === "acp";
|
|
148
|
+
const conversation = new StreamConversation({
|
|
149
|
+
backend: "claude",
|
|
150
|
+
capacity: options.capacity ?? 256,
|
|
151
|
+
canAskUser: false,
|
|
152
|
+
onCancel: async () => {
|
|
153
|
+
if (useAcp && cancelAcp) {
|
|
154
|
+
try {
|
|
155
|
+
await cancelAcp();
|
|
156
|
+
} catch (error) {
|
|
157
|
+
conversation.fail(backendFailed("claude", `Claude ACP cancellation failed: ${errorMessage(error)}`));
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
child?.kill("SIGTERM");
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
queueMicrotask(() => {
|
|
166
|
+
if (conversation.signal.aborted) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (useAcp) {
|
|
170
|
+
const acp = claudeAcpCommand();
|
|
171
|
+
void runAcpConversation(
|
|
172
|
+
request,
|
|
173
|
+
{
|
|
174
|
+
backend: "claude",
|
|
175
|
+
command: acp.command,
|
|
176
|
+
args: acp.args,
|
|
177
|
+
config,
|
|
178
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
179
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
180
|
+
...(options.spawnAcpProcess === undefined ? {} : { spawnProcess: options.spawnAcpProcess }),
|
|
181
|
+
...(options.acpCancelTimeoutMs === undefined ? {} : { cancelTimeoutMs: options.acpCancelTimeoutMs }),
|
|
182
|
+
...(options.wallClockTimeoutMs === undefined ? {} : { requestTimeoutMs: options.wallClockTimeoutMs }),
|
|
183
|
+
...(options.inactivityTimeoutMs === undefined ? {} : { inactivityTimeoutMs: options.inactivityTimeoutMs }),
|
|
184
|
+
setProcess: (process) => {
|
|
185
|
+
child = process;
|
|
186
|
+
if (conversation.signal.aborted) {
|
|
187
|
+
process.kill("SIGTERM");
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
setCancel: (cancel) => {
|
|
191
|
+
cancelAcp = cancel;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
conversation
|
|
195
|
+
);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
void runClaudeConversation(request, options, conversation, (process) => {
|
|
199
|
+
child = process;
|
|
200
|
+
if (conversation.signal.aborted) {
|
|
201
|
+
process.kill("SIGTERM");
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return conversation;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function resolveClaudeTransport<Output>(
|
|
212
|
+
options: ClaudeBackendOptions,
|
|
213
|
+
config: ResolvedClaudeConfig<Output>
|
|
214
|
+
): ClaudeTransport {
|
|
215
|
+
const transport = options.transport ?? process.env.ORCA_CLAUDE_TRANSPORT;
|
|
216
|
+
if (transport === "stream-json") {
|
|
217
|
+
return "stream-json";
|
|
218
|
+
}
|
|
219
|
+
if (transport !== undefined && transport !== "acp") {
|
|
220
|
+
throw new Error(`unsupported Claude transport ${transport}`);
|
|
221
|
+
}
|
|
222
|
+
if (config.model !== undefined || config.resumeSessionId !== undefined) {
|
|
223
|
+
return "stream-json";
|
|
224
|
+
}
|
|
225
|
+
return "acp";
|
|
226
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
backendFailed,
|
|
4
|
+
parseStructuredOutput,
|
|
5
|
+
sessionId,
|
|
6
|
+
structuredOutputValidationFailed,
|
|
7
|
+
type RuntimeError
|
|
8
|
+
} from "../model/index.ts";
|
|
9
|
+
import {
|
|
10
|
+
collectConversation,
|
|
11
|
+
StreamConversation,
|
|
12
|
+
type ConversationCapture
|
|
13
|
+
} from "../conversation/index.ts";
|
|
14
|
+
|
|
15
|
+
interface ClaudeStreamLine {
|
|
16
|
+
readonly type?: string;
|
|
17
|
+
readonly subtype?: string;
|
|
18
|
+
readonly session_id?: string;
|
|
19
|
+
readonly result?: string;
|
|
20
|
+
readonly structured_output?: unknown;
|
|
21
|
+
readonly is_error?: boolean;
|
|
22
|
+
readonly event?: {
|
|
23
|
+
readonly type?: string;
|
|
24
|
+
readonly delta?: {
|
|
25
|
+
readonly type?: string;
|
|
26
|
+
readonly text?: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
readonly message?: {
|
|
30
|
+
readonly content?: readonly ClaudeContentBlock[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type ClaudeContentBlock =
|
|
35
|
+
| {
|
|
36
|
+
readonly type: "tool_use";
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly input: unknown;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
readonly type: "tool_result";
|
|
43
|
+
readonly tool_use_id: string;
|
|
44
|
+
readonly content: unknown;
|
|
45
|
+
readonly is_error?: boolean;
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
readonly type: "text";
|
|
49
|
+
readonly text: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type ClaudeParseResult = ConversationCapture<"claude">;
|
|
53
|
+
|
|
54
|
+
export interface ClaudeStreamOptions<Output = unknown> {
|
|
55
|
+
readonly schema?: z.ZodType<Output>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function collectClaudeStreamJson(lines: readonly string[]): Promise<ClaudeParseResult> {
|
|
59
|
+
return collectConversation({
|
|
60
|
+
backend: "claude",
|
|
61
|
+
consume: async (conversation) => {
|
|
62
|
+
await consumeClaudeStreamJson(lines, conversation);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function consumeClaudeStreamJson<Output = unknown>(
|
|
68
|
+
lines: readonly string[],
|
|
69
|
+
conversation: StreamConversation<"claude">,
|
|
70
|
+
options: ClaudeStreamOptions<Output> = {}
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
for (const raw of lines) {
|
|
73
|
+
if (raw.trim() === "") {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const line = parseClaudeLine(raw);
|
|
78
|
+
await consumeClaudeLine(line, conversation, options);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ClaudeStreamConsumer {
|
|
83
|
+
readonly signal: AbortSignal;
|
|
84
|
+
consume(raw: string): Promise<void>;
|
|
85
|
+
finish(): void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createClaudeStreamConsumer<Output = unknown>(
|
|
89
|
+
conversation: StreamConversation<"claude">,
|
|
90
|
+
options: ClaudeStreamOptions<Output> = {}
|
|
91
|
+
): ClaudeStreamConsumer {
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
get signal() {
|
|
96
|
+
return controller.signal;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async consume(raw: string): Promise<void> {
|
|
100
|
+
if (controller.signal.aborted || raw.trim() === "") {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let line: ClaudeStreamLine;
|
|
105
|
+
try {
|
|
106
|
+
line = parseClaudeLine(raw);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
controller.abort();
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
conversation.fail(backendFailed("claude", `invalid claude stream-json: ${message}`));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await consumeClaudeLine(line, conversation, options);
|
|
115
|
+
if (line.type === "result") {
|
|
116
|
+
controller.abort();
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
finish(): void {
|
|
121
|
+
if (controller.signal.aborted) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
controller.abort();
|
|
125
|
+
conversation.fail(
|
|
126
|
+
backendFailed("claude", "claude exited cleanly but never sent a result message")
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseClaudeLine(raw: string): ClaudeStreamLine {
|
|
133
|
+
return JSON.parse(raw) as ClaudeStreamLine;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function consumeClaudeLine<Output>(
|
|
137
|
+
line: ClaudeStreamLine,
|
|
138
|
+
conversation: StreamConversation<"claude">,
|
|
139
|
+
options: ClaudeStreamOptions<Output>
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
if (line.type === "stream_event") {
|
|
142
|
+
await consumeStreamEvent(line, conversation);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (line.type === "assistant" || line.type === "user") {
|
|
147
|
+
await consumeMessage(line, conversation);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (line.type === "result") {
|
|
152
|
+
await consumeResult(line, conversation, options);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function consumeStreamEvent(
|
|
157
|
+
line: ClaudeStreamLine,
|
|
158
|
+
conversation: StreamConversation<"claude">
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
const delta = line.event?.delta;
|
|
161
|
+
if (line.event?.type === "content_block_delta" && delta?.type === "text_delta") {
|
|
162
|
+
await conversation.emit({ type: "assistant_text_delta", text: delta.text ?? "" });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function consumeMessage(
|
|
167
|
+
line: ClaudeStreamLine,
|
|
168
|
+
conversation: StreamConversation<"claude">
|
|
169
|
+
): Promise<void> {
|
|
170
|
+
const content = line.message?.content ?? [];
|
|
171
|
+
let endsAssistantTurn = false;
|
|
172
|
+
|
|
173
|
+
for (const block of content) {
|
|
174
|
+
switch (block.type) {
|
|
175
|
+
case "tool_use":
|
|
176
|
+
await conversation.emit({
|
|
177
|
+
type: "assistant_tool_call",
|
|
178
|
+
id: block.id,
|
|
179
|
+
name: block.name,
|
|
180
|
+
input: block.input
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
183
|
+
case "tool_result":
|
|
184
|
+
await conversation.emit({
|
|
185
|
+
type: "tool_result",
|
|
186
|
+
toolCallId: block.tool_use_id,
|
|
187
|
+
output: block.content,
|
|
188
|
+
isError: block.is_error
|
|
189
|
+
});
|
|
190
|
+
break;
|
|
191
|
+
case "text":
|
|
192
|
+
await conversation.emit({ type: "assistant_text_delta", text: block.text });
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (line.type === "assistant" && (block.type === "tool_use" || block.type === "text")) {
|
|
197
|
+
endsAssistantTurn = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (endsAssistantTurn) {
|
|
202
|
+
await conversation.emit({ type: "assistant_turn_end" });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function consumeResult<Output>(
|
|
207
|
+
line: ClaudeStreamLine,
|
|
208
|
+
conversation: StreamConversation<"claude">,
|
|
209
|
+
options: ClaudeStreamOptions<Output>
|
|
210
|
+
): Promise<void> {
|
|
211
|
+
const structuredOutput = line.structured_output;
|
|
212
|
+
const output =
|
|
213
|
+
structuredOutput === undefined ? line.result ?? "" : JSON.stringify(structuredOutput);
|
|
214
|
+
const session = sessionId("claude", line.session_id ?? "");
|
|
215
|
+
|
|
216
|
+
if (line.subtype === "success" && !line.is_error) {
|
|
217
|
+
const structured = parseClaudeStructuredOutput(options.schema, output, structuredOutput);
|
|
218
|
+
if (structured.type === "failed") {
|
|
219
|
+
conversation.fail(structured.error);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
conversation.succeed({
|
|
223
|
+
backend: "claude",
|
|
224
|
+
sessionId: session,
|
|
225
|
+
output,
|
|
226
|
+
...(structured.value === undefined ? {} : { structured: structured.value })
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await conversation.emit({
|
|
232
|
+
type: "error",
|
|
233
|
+
code: "backend_failed",
|
|
234
|
+
message: "session failed (see message above)"
|
|
235
|
+
});
|
|
236
|
+
conversation.fail(backendFailed("claude", `claude session failed: ${output}`));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function parseClaudeStructuredOutput<Output>(
|
|
240
|
+
schema: z.ZodType<Output> | undefined,
|
|
241
|
+
output: string,
|
|
242
|
+
structuredOutput: unknown
|
|
243
|
+
):
|
|
244
|
+
| { readonly type: "success"; readonly value?: Output }
|
|
245
|
+
| { readonly type: "failed"; readonly error: RuntimeError } {
|
|
246
|
+
if (!schema) {
|
|
247
|
+
return { type: "success" };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let raw: unknown;
|
|
251
|
+
if (structuredOutput !== undefined) {
|
|
252
|
+
raw = structuredOutput;
|
|
253
|
+
} else {
|
|
254
|
+
try {
|
|
255
|
+
raw = JSON.parse(output) as unknown;
|
|
256
|
+
} catch {
|
|
257
|
+
return {
|
|
258
|
+
type: "failed",
|
|
259
|
+
error: structuredOutputValidationFailed({
|
|
260
|
+
raw: output,
|
|
261
|
+
issues: ["Claude structured output was not valid JSON"]
|
|
262
|
+
})
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const parsed = parseStructuredOutput(schema, raw);
|
|
268
|
+
if (parsed.isErr()) {
|
|
269
|
+
return { type: "failed", error: parsed.error };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { type: "success", value: parsed.value.value };
|
|
273
|
+
}
|