@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.04a1e5b9e484
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/README.md +165 -1
- package/dist/bridge/bridge-chat.types.d.ts +111 -0
- package/dist/bridge/bridge-chat.types.js +401 -0
- package/dist/bridge/bridge.base.d.ts +9 -1
- package/dist/bridge/bridge.service.d.ts +129 -3
- package/dist/bridge/bridge.service.js +2427 -70
- package/dist/bridge/bridge.types.d.ts +218 -2
- package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
- package/dist/bridge/dispatch-result-outbox.js +62 -0
- package/dist/bridge/dispatch-result-worker.d.ts +2 -0
- package/dist/bridge/dispatch-result-worker.js +122 -0
- package/dist/bridge/internals.d.ts +2 -0
- package/dist/bridge/internals.js +1 -0
- package/dist/bridge/readiness-result-outbox.d.ts +21 -0
- package/dist/bridge/readiness-result-outbox.js +54 -0
- package/dist/bridge/readiness-result-worker.d.ts +2 -0
- package/dist/bridge/readiness-result-worker.js +121 -0
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.d.ts +2 -0
- package/dist/cli/cli.help.js +217 -0
- package/dist/cli/cli.service.d.ts +55 -7
- package/dist/cli/cli.service.js +429 -219
- package/dist/cli/local-config.d.ts +7 -0
- package/dist/cli/local-config.js +51 -3
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
- package/dist/runtime/attempt-database-broker.d.ts +89 -0
- package/dist/runtime/attempt-database-broker.js +390 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
- package/dist/runtime/attempt-path-operation.d.ts +21 -0
- package/dist/runtime/attempt-path-operation.js +94 -0
- package/dist/runtime/attempt-private-directory.d.ts +6 -0
- package/dist/runtime/attempt-private-directory.js +54 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +102 -0
- package/dist/runtime/attempt-process-supervisor.js +421 -0
- package/dist/runtime/canonical-source-file.d.ts +3 -0
- package/dist/runtime/canonical-source-file.js +54 -0
- package/dist/runtime/docker-engine-probe.d.ts +90 -0
- package/dist/runtime/docker-engine-probe.js +333 -0
- package/dist/runtime/generated-output-reset.d.ts +20 -0
- package/dist/runtime/generated-output-reset.js +157 -0
- package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
- package/dist/runtime/host-database-capability-boundary.js +62 -0
- package/dist/runtime/host-diagnostics.d.ts +11 -0
- package/dist/runtime/host-diagnostics.js +192 -0
- package/dist/runtime/host-execution-root.d.ts +4 -0
- package/dist/runtime/host-execution-root.js +39 -0
- package/dist/runtime/host-listener-inspector.d.ts +51 -0
- package/dist/runtime/host-listener-inspector.js +385 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +58 -0
- package/dist/runtime/host-runtime-contract.js +813 -0
- package/dist/runtime/host-runtime-lease.d.ts +217 -0
- package/dist/runtime/host-runtime-lease.js +2487 -0
- package/dist/runtime/host-runtime-promotion.d.ts +32 -0
- package/dist/runtime/host-runtime-promotion.js +206 -0
- package/dist/runtime/memory-capacity.d.ts +70 -0
- package/dist/runtime/memory-capacity.js +123 -0
- package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
- package/dist/runtime/native-test-postgres-fallback.js +91 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +286 -0
- package/dist/runtime/pinned-directory-process.d.ts +9 -0
- package/dist/runtime/pinned-directory-process.js +61 -0
- package/dist/runtime/pinned-source-directory.d.ts +17 -0
- package/dist/runtime/pinned-source-directory.js +94 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
- package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
- package/dist/runtime/prepared-runtime-identity.js +218 -0
- package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
- package/dist/runtime/prepared-runtime-readiness.js +285 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +161 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +84 -0
- package/dist/sandbox/preflight.js +521 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +209 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +6 -0
- package/dist/sandbox/repository-lock.js +129 -0
- package/dist/sandbox/runtime.d.ts +302 -0
- package/dist/sandbox/runtime.js +2368 -0
- package/dist/sandbox/sandbox.types.d.ts +93 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +225 -0
- package/dist/sandbox/workspace.service.d.ts +13 -0
- package/dist/sandbox/workspace.service.js +74 -0
- package/dist/session/managed-attempt-tools.d.ts +28 -0
- package/dist/session/managed-attempt-tools.js +1016 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +106 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +337 -0
- package/dist/session/managed-verification-timeouts.d.ts +8 -0
- package/dist/session/managed-verification-timeouts.js +17 -0
- package/dist/session/phase-completion.artifacts.d.ts +27 -0
- package/dist/session/phase-completion.artifacts.js +829 -0
- package/dist/session/phase-completion.d.ts +53 -42
- package/dist/session/phase-completion.helpers.d.ts +117 -0
- package/dist/session/phase-completion.helpers.js +707 -0
- package/dist/session/phase-completion.js +125 -3007
- package/dist/session/phase-completion.prompts.d.ts +14 -0
- package/dist/session/phase-completion.prompts.js +233 -0
- package/dist/session/phase-completion.runtime.d.ts +49 -0
- package/dist/session/phase-completion.runtime.js +840 -0
- package/dist/session/phase-completion.schemas.d.ts +3 -0
- package/dist/session/phase-completion.schemas.js +859 -0
- package/dist/session/phase-completion.validation.d.ts +38 -0
- package/dist/session/phase-completion.validation.js +918 -0
- package/dist/session/pre-publication-validation.d.ts +75 -0
- package/dist/session/pre-publication-validation.js +661 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
- package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
- package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
- package/dist/session/provider-cli/provider-cli.process.js +874 -0
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +22 -0
- package/dist/session/provider-cli/provider-cli.runtime.js +92 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
- package/dist/session/provider-cli/provider-cli.service.js +1258 -2497
- package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
- package/dist/session/provider-cli/provider-output-state.js +817 -0
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +448 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +586 -59
- package/dist/session/session.types.d.ts +324 -3
- package/dist/session/session.types.js +939 -10
- package/dist/session/unreferenced-exports.d.ts +12 -0
- package/dist/session/unreferenced-exports.js +59 -0
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/git/git.js +2 -1
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
- package/dist/shared/process/dotnet-toolchain.js +307 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +79 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +331 -0
- package/dist/shared/process/process.d.ts +3 -0
- package/dist/shared/process/process.js +13 -7
- package/dist/shared/process/windows-launch.d.ts +8 -0
- package/dist/shared/process/windows-launch.js +41 -0
- package/dist/shared/provider-capabilities.d.ts +4 -0
- package/dist/shared/provider-capabilities.js +88 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +47 -4
- package/dist/shared/time/monotonic.d.ts +1 -0
- package/dist/shared/time/monotonic.js +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +82 -10
- package/dist/worktree/dependency-warmer.d.ts +53 -1
- package/dist/worktree/dependency-warmer.js +619 -11
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +16 -3
- package/dist/worktree/worktree.service.js +334 -54
- package/dist/worktree/worktree.types.d.ts +9 -1
- package/package.json +12 -7
- package/runtime/windows-job-supervisor.ps1 +722 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import { addUsage, emptyUsage, readUsageNumber } from "./provider-usage.js";
|
|
2
|
+
export function messageFromError(error) {
|
|
3
|
+
return error instanceof Error ? error.message : String(error);
|
|
4
|
+
}
|
|
5
|
+
export function clip(value, maxLength) {
|
|
6
|
+
if (maxLength <= 0) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
if (value.length <= maxLength) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
const ellipsis = "...";
|
|
13
|
+
if (maxLength <= ellipsis.length) {
|
|
14
|
+
return ellipsis.slice(0, maxLength);
|
|
15
|
+
}
|
|
16
|
+
return `${value.slice(0, maxLength - ellipsis.length)}${ellipsis}`;
|
|
17
|
+
}
|
|
18
|
+
export function createProviderOutputState(provider) {
|
|
19
|
+
const normalized = provider.trim().toLowerCase();
|
|
20
|
+
if (normalized === "cursor") {
|
|
21
|
+
return new CursorStreamJsonState();
|
|
22
|
+
}
|
|
23
|
+
if (normalized === "claude") {
|
|
24
|
+
return new ClaudeStreamJsonState();
|
|
25
|
+
}
|
|
26
|
+
return new CodexJsonState();
|
|
27
|
+
}
|
|
28
|
+
class CursorStreamJsonState {
|
|
29
|
+
sessionId = null;
|
|
30
|
+
finalMessage = null;
|
|
31
|
+
emittedSession = false;
|
|
32
|
+
accumulatedUsage = null;
|
|
33
|
+
activeToolCallCount = 0;
|
|
34
|
+
get sessionLabel() {
|
|
35
|
+
return this.sessionId ? `session ${this.sessionId}` : null;
|
|
36
|
+
}
|
|
37
|
+
get providerThreadId() {
|
|
38
|
+
return this.sessionId;
|
|
39
|
+
}
|
|
40
|
+
get usage() {
|
|
41
|
+
return this.accumulatedUsage;
|
|
42
|
+
}
|
|
43
|
+
get hasActiveToolCall() {
|
|
44
|
+
return this.activeToolCallCount > 0;
|
|
45
|
+
}
|
|
46
|
+
consume(line) {
|
|
47
|
+
const event = parseJsonLine(line);
|
|
48
|
+
if (!event) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const type = stringField(event, "type");
|
|
52
|
+
if (type === "system" && stringField(event, "subtype") === "init") {
|
|
53
|
+
this.sessionId = stringField(event, "session_id");
|
|
54
|
+
if (this.sessionId && !this.emittedSession) {
|
|
55
|
+
this.emittedSession = true;
|
|
56
|
+
return [{
|
|
57
|
+
role: "runner",
|
|
58
|
+
kind: "checkpoint",
|
|
59
|
+
content: `Cursor session started: ${this.sessionId}.`,
|
|
60
|
+
eventType: "provider_thread_started",
|
|
61
|
+
data: JSON.stringify({ threadId: this.sessionId })
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (type === "assistant") {
|
|
66
|
+
const text = assistantMessageText(event);
|
|
67
|
+
if (text) {
|
|
68
|
+
this.finalMessage = text;
|
|
69
|
+
return [{
|
|
70
|
+
role: "assistant",
|
|
71
|
+
kind: "message",
|
|
72
|
+
content: clip(text, 4000),
|
|
73
|
+
eventType: "provider_message"
|
|
74
|
+
}];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (type === "tool_call") {
|
|
78
|
+
const subtype = stringField(event, "subtype");
|
|
79
|
+
const toolCall = objectField(event, "tool_call");
|
|
80
|
+
if (subtype === "started") {
|
|
81
|
+
this.activeToolCallCount += 1;
|
|
82
|
+
const summary = formatCursorToolCallStarted(toolCall);
|
|
83
|
+
if (summary) {
|
|
84
|
+
return [{
|
|
85
|
+
role: "worker",
|
|
86
|
+
kind: "checkpoint",
|
|
87
|
+
content: summary,
|
|
88
|
+
eventType: "provider_tool_call"
|
|
89
|
+
}];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (subtype === "completed") {
|
|
93
|
+
this.activeToolCallCount = Math.max(0, this.activeToolCallCount - 1);
|
|
94
|
+
const completed = formatCursorToolCallCompleted(toolCall);
|
|
95
|
+
if (completed) {
|
|
96
|
+
return [{
|
|
97
|
+
role: "worker",
|
|
98
|
+
kind: completed.kind,
|
|
99
|
+
content: completed.content,
|
|
100
|
+
eventType: completed.kind === "file_edit" ? "provider_file_edit" : "provider_tool_call"
|
|
101
|
+
}];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (type === "result" && stringField(event, "subtype") === "success") {
|
|
106
|
+
const result = stringField(event, "result");
|
|
107
|
+
if (result) {
|
|
108
|
+
this.finalMessage = result;
|
|
109
|
+
}
|
|
110
|
+
this.accumulateUsage(event);
|
|
111
|
+
}
|
|
112
|
+
if (type === "error" || (type === "result" && event.is_error === true)) {
|
|
113
|
+
return [{
|
|
114
|
+
role: "runner",
|
|
115
|
+
kind: "error",
|
|
116
|
+
content: clip(line.trim(), 1000),
|
|
117
|
+
eventType: "provider_cli_error"
|
|
118
|
+
}];
|
|
119
|
+
}
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
accumulateUsage(event) {
|
|
123
|
+
const usage = objectField(event, "usage");
|
|
124
|
+
const durationMs = readUsageNumber(event, "duration_ms") || readUsageNumber(event, "duration_api_ms");
|
|
125
|
+
if (Object.keys(usage).length === 0 && durationMs === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const uncachedInputTokens = readUsageNumber(usage, "inputTokens");
|
|
129
|
+
const cacheReadTokens = readUsageNumber(usage, "cacheReadTokens");
|
|
130
|
+
const cacheCreationTokens = readUsageNumber(usage, "cacheWriteTokens");
|
|
131
|
+
this.accumulatedUsage = addUsage(this.accumulatedUsage ?? emptyUsage("provider_reported"), {
|
|
132
|
+
inputTokens: uncachedInputTokens + cacheReadTokens + cacheCreationTokens,
|
|
133
|
+
uncachedInputTokens,
|
|
134
|
+
outputTokens: readUsageNumber(usage, "outputTokens"),
|
|
135
|
+
cacheReadTokens,
|
|
136
|
+
cacheCreationTokens,
|
|
137
|
+
providerDurationMs: durationMs > 0 ? durationMs : null,
|
|
138
|
+
source: "provider_reported",
|
|
139
|
+
inputTokenSemantics: "excludes_cache_tokens",
|
|
140
|
+
rawUsage: [{ provider: "cursor", usage }]
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function formatCursorToolCallStarted(toolCall) {
|
|
145
|
+
const readArgs = objectField(objectField(toolCall, "readToolCall"), "args");
|
|
146
|
+
const readPath = stringField(readArgs, "path");
|
|
147
|
+
if (readPath) {
|
|
148
|
+
return `Reading ${readPath}`;
|
|
149
|
+
}
|
|
150
|
+
const writeArgs = objectField(objectField(toolCall, "writeToolCall"), "args");
|
|
151
|
+
const writePath = stringField(writeArgs, "path");
|
|
152
|
+
if (writePath) {
|
|
153
|
+
return `Writing ${writePath}`;
|
|
154
|
+
}
|
|
155
|
+
const shellArgs = objectField(objectField(toolCall, "shellToolCall"), "args");
|
|
156
|
+
const command = stringField(shellArgs, "command");
|
|
157
|
+
if (command) {
|
|
158
|
+
return `Shell command started: ${clip(command, 500)}`;
|
|
159
|
+
}
|
|
160
|
+
const fn = objectField(toolCall, "function");
|
|
161
|
+
const name = stringField(fn, "name");
|
|
162
|
+
if (name) {
|
|
163
|
+
return `Tool call started: ${name}`;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function formatCursorToolCallCompleted(toolCall) {
|
|
168
|
+
const writeArgs = objectField(objectField(toolCall, "writeToolCall"), "args");
|
|
169
|
+
const writePath = stringField(writeArgs, "path");
|
|
170
|
+
const writeResult = objectField(objectField(objectField(toolCall, "writeToolCall"), "result"), "success");
|
|
171
|
+
const writtenPath = stringField(writeResult, "path") ?? writePath;
|
|
172
|
+
if (writtenPath) {
|
|
173
|
+
return {
|
|
174
|
+
kind: "file_edit",
|
|
175
|
+
content: `Wrote ${writtenPath}`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const readArgs = objectField(objectField(toolCall, "readToolCall"), "args");
|
|
179
|
+
const readPath = stringField(readArgs, "path");
|
|
180
|
+
if (readPath) {
|
|
181
|
+
const readResult = objectField(objectField(objectField(toolCall, "readToolCall"), "result"), "success");
|
|
182
|
+
const totalLines = readResult.totalLines;
|
|
183
|
+
const lineSummary = typeof totalLines === "number" ? ` (${totalLines} lines)` : "";
|
|
184
|
+
return {
|
|
185
|
+
kind: "checkpoint",
|
|
186
|
+
content: `Read ${readPath}${lineSummary}`
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const shellArgs = objectField(objectField(toolCall, "shellToolCall"), "args");
|
|
190
|
+
const command = stringField(shellArgs, "command");
|
|
191
|
+
if (command) {
|
|
192
|
+
return {
|
|
193
|
+
kind: "checkpoint",
|
|
194
|
+
content: `Shell command completed: ${clip(command, 500)}`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const fn = objectField(toolCall, "function");
|
|
198
|
+
const name = stringField(fn, "name");
|
|
199
|
+
if (name) {
|
|
200
|
+
return {
|
|
201
|
+
kind: "checkpoint",
|
|
202
|
+
content: `Tool call completed: ${name}`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
function assistantMessageText(event) {
|
|
208
|
+
const message = objectField(event, "message");
|
|
209
|
+
const content = message.content;
|
|
210
|
+
if (!Array.isArray(content)) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const parts = content
|
|
214
|
+
.map((entry) => {
|
|
215
|
+
if (!entry || typeof entry !== "object") {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const block = entry;
|
|
219
|
+
return stringField(block, "text");
|
|
220
|
+
})
|
|
221
|
+
.filter((text) => Boolean(text));
|
|
222
|
+
if (parts.length === 0) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return parts.join("");
|
|
226
|
+
}
|
|
227
|
+
export function parseJsonLine(line) {
|
|
228
|
+
const trimmed = line.trim();
|
|
229
|
+
if (!trimmed.startsWith("{")) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
let parsed;
|
|
233
|
+
try {
|
|
234
|
+
parsed = JSON.parse(trimmed);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
240
|
+
? parsed
|
|
241
|
+
: null;
|
|
242
|
+
}
|
|
243
|
+
const claudeFileEditTools = new Set(["edit", "multiedit", "write", "notebookedit"]);
|
|
244
|
+
/**
|
|
245
|
+
* Parses `claude -p --output-format stream-json --verbose` output. Each line is one of:
|
|
246
|
+
* `system/init`, `assistant` (text + tool_use blocks), `user` (tool_result blocks), or the
|
|
247
|
+
* terminal `result` event that carries the aggregated usage for the whole run.
|
|
248
|
+
*/
|
|
249
|
+
class ClaudeStreamJsonState {
|
|
250
|
+
sessionId = null;
|
|
251
|
+
finalMessage = null;
|
|
252
|
+
emittedSession = false;
|
|
253
|
+
accumulatedUsage = null;
|
|
254
|
+
activeToolUses = new Map();
|
|
255
|
+
get sessionLabel() {
|
|
256
|
+
return this.sessionId ? `session ${this.sessionId}` : null;
|
|
257
|
+
}
|
|
258
|
+
get providerThreadId() {
|
|
259
|
+
return this.sessionId;
|
|
260
|
+
}
|
|
261
|
+
get usage() {
|
|
262
|
+
return this.accumulatedUsage;
|
|
263
|
+
}
|
|
264
|
+
get hasActiveToolCall() {
|
|
265
|
+
return this.activeToolUses.size > 0;
|
|
266
|
+
}
|
|
267
|
+
consume(line) {
|
|
268
|
+
const event = parseJsonLine(line);
|
|
269
|
+
if (!event) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
const type = stringField(event, "type");
|
|
273
|
+
if (type === "system" && stringField(event, "subtype") === "init") {
|
|
274
|
+
return this.consumeInit(event);
|
|
275
|
+
}
|
|
276
|
+
if (type === "assistant") {
|
|
277
|
+
return this.consumeAssistant(event);
|
|
278
|
+
}
|
|
279
|
+
if (type === "user") {
|
|
280
|
+
return this.consumeToolResults(event);
|
|
281
|
+
}
|
|
282
|
+
if (type === "result") {
|
|
283
|
+
return this.consumeResult(event, line);
|
|
284
|
+
}
|
|
285
|
+
if (type === "error") {
|
|
286
|
+
return [providerCliError(line)];
|
|
287
|
+
}
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
consumeInit(event) {
|
|
291
|
+
const events = [];
|
|
292
|
+
this.sessionId = stringField(event, "session_id") ?? this.sessionId;
|
|
293
|
+
if (this.sessionId && !this.emittedSession) {
|
|
294
|
+
this.emittedSession = true;
|
|
295
|
+
events.push({
|
|
296
|
+
role: "runner",
|
|
297
|
+
kind: "checkpoint",
|
|
298
|
+
content: `Claude session started: ${this.sessionId}.`,
|
|
299
|
+
eventType: "provider_thread_started"
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const servers = Array.isArray(event.mcp_servers) ? event.mcp_servers : [];
|
|
303
|
+
for (const entry of servers) {
|
|
304
|
+
if (!entry || typeof entry !== "object") {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const server = entry;
|
|
308
|
+
const name = stringField(server, "name");
|
|
309
|
+
const status = stringField(server, "status");
|
|
310
|
+
if (name && status && status !== "connected") {
|
|
311
|
+
events.push({
|
|
312
|
+
role: "runner",
|
|
313
|
+
kind: "checkpoint",
|
|
314
|
+
content: `Claude MCP server ${name} is ${status}.`,
|
|
315
|
+
eventType: "provider_mcp_server_status",
|
|
316
|
+
data: JSON.stringify({ server_name: name, status })
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return events;
|
|
321
|
+
}
|
|
322
|
+
consumeAssistant(event) {
|
|
323
|
+
const events = [];
|
|
324
|
+
const text = assistantMessageText(event);
|
|
325
|
+
if (text) {
|
|
326
|
+
this.finalMessage = text;
|
|
327
|
+
events.push({
|
|
328
|
+
role: "assistant",
|
|
329
|
+
kind: "message",
|
|
330
|
+
content: clip(text, 4000),
|
|
331
|
+
eventType: "provider_message"
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
for (const block of messageContentBlocks(event)) {
|
|
335
|
+
if (stringField(block, "type") !== "tool_use") {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const id = stringField(block, "id");
|
|
339
|
+
const name = stringField(block, "name");
|
|
340
|
+
if (!id || !name) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const toolUse = { name, input: objectField(block, "input") };
|
|
344
|
+
this.activeToolUses.set(id, toolUse);
|
|
345
|
+
events.push(...formatClaudeToolUseStarted(toolUse));
|
|
346
|
+
}
|
|
347
|
+
return events;
|
|
348
|
+
}
|
|
349
|
+
consumeToolResults(event) {
|
|
350
|
+
const events = [];
|
|
351
|
+
for (const block of messageContentBlocks(event)) {
|
|
352
|
+
if (stringField(block, "type") !== "tool_result") {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const id = stringField(block, "tool_use_id");
|
|
356
|
+
const toolUse = id ? this.activeToolUses.get(id) : undefined;
|
|
357
|
+
if (!id || !toolUse) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
this.activeToolUses.delete(id);
|
|
361
|
+
events.push(...formatClaudeToolUseCompleted(toolUse, block.is_error === true, toolResultText(block)));
|
|
362
|
+
}
|
|
363
|
+
return events;
|
|
364
|
+
}
|
|
365
|
+
consumeResult(event, line) {
|
|
366
|
+
this.activeToolUses.clear();
|
|
367
|
+
this.accumulateUsage(event);
|
|
368
|
+
const result = stringField(event, "result");
|
|
369
|
+
const subtype = stringField(event, "subtype") ?? "";
|
|
370
|
+
const failed = event.is_error === true || subtype.startsWith("error");
|
|
371
|
+
if (!failed) {
|
|
372
|
+
if (result) {
|
|
373
|
+
this.finalMessage = result;
|
|
374
|
+
}
|
|
375
|
+
return [{
|
|
376
|
+
role: "runner",
|
|
377
|
+
kind: "checkpoint",
|
|
378
|
+
content: "Claude turn completed.",
|
|
379
|
+
eventType: "provider_turn_completed"
|
|
380
|
+
}];
|
|
381
|
+
}
|
|
382
|
+
return [providerCliError(line)];
|
|
383
|
+
}
|
|
384
|
+
accumulateUsage(event) {
|
|
385
|
+
const usage = objectField(event, "usage");
|
|
386
|
+
const durationMs = readUsageNumber(event, "duration_ms") || readUsageNumber(event, "duration_api_ms");
|
|
387
|
+
if (Object.keys(usage).length === 0 && durationMs === 0) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
// Anthropic's input_tokens excludes cache reads and writes; normalize to a cache-inclusive total.
|
|
391
|
+
const uncachedInputTokens = readUsageNumber(usage, "input_tokens");
|
|
392
|
+
const cacheReadTokens = readUsageNumber(usage, "cache_read_input_tokens");
|
|
393
|
+
const cacheCreationTokens = readUsageNumber(usage, "cache_creation_input_tokens");
|
|
394
|
+
this.accumulatedUsage = addUsage(this.accumulatedUsage ?? emptyUsage("provider_reported"), {
|
|
395
|
+
inputTokens: uncachedInputTokens + cacheReadTokens + cacheCreationTokens,
|
|
396
|
+
uncachedInputTokens,
|
|
397
|
+
outputTokens: readUsageNumber(usage, "output_tokens"),
|
|
398
|
+
cacheReadTokens,
|
|
399
|
+
cacheCreationTokens,
|
|
400
|
+
providerDurationMs: durationMs > 0 ? durationMs : null,
|
|
401
|
+
source: "provider_reported",
|
|
402
|
+
inputTokenSemantics: "excludes_cache_tokens",
|
|
403
|
+
rawUsage: [{ provider: "claude", usage }]
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function providerCliError(line) {
|
|
408
|
+
return {
|
|
409
|
+
role: "runner",
|
|
410
|
+
kind: "error",
|
|
411
|
+
content: clip(line.trim(), 1000),
|
|
412
|
+
eventType: "provider_cli_error"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function messageContentBlocks(event) {
|
|
416
|
+
const content = objectField(event, "message").content;
|
|
417
|
+
if (!Array.isArray(content)) {
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
420
|
+
return content.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry));
|
|
421
|
+
}
|
|
422
|
+
function toolResultText(block) {
|
|
423
|
+
const content = block.content;
|
|
424
|
+
if (typeof content === "string") {
|
|
425
|
+
return content.trim() || null;
|
|
426
|
+
}
|
|
427
|
+
if (!Array.isArray(content)) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
const text = content
|
|
431
|
+
.map((entry) => entry && typeof entry === "object" ? stringField(entry, "text") : null)
|
|
432
|
+
.filter((part) => Boolean(part))
|
|
433
|
+
.join("\n")
|
|
434
|
+
.trim();
|
|
435
|
+
return text || null;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Claude Code exposes MCP tools as `mcp__<server>__<tool>` and replaces characters outside
|
|
439
|
+
* `[A-Za-z0-9_-]` with `_`, so `forge.complete_qa` arrives as `mcp__forge__forge_complete_qa`.
|
|
440
|
+
*/
|
|
441
|
+
export function parseClaudeMcpToolName(name) {
|
|
442
|
+
const match = /^mcp__(.+?)__(.+)$/u.exec(name);
|
|
443
|
+
if (!match) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
const server = match[1] ?? "";
|
|
447
|
+
const rawTool = match[2] ?? "";
|
|
448
|
+
const tool = server === "forge" && rawTool.startsWith("forge_") ? `forge.${rawTool.slice("forge_".length)}` : rawTool;
|
|
449
|
+
return server && tool ? { server, tool } : null;
|
|
450
|
+
}
|
|
451
|
+
function claudeMcpEvents(toolUse, phase, errorText) {
|
|
452
|
+
const mcp = parseClaudeMcpToolName(toolUse.name);
|
|
453
|
+
if (!mcp) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
const item = {
|
|
457
|
+
type: "mcp_tool_call",
|
|
458
|
+
server: mcp.server,
|
|
459
|
+
tool: mcp.tool,
|
|
460
|
+
status: phase
|
|
461
|
+
};
|
|
462
|
+
if (phase === "failed") {
|
|
463
|
+
item.error = errorText ? clip(errorText, 600) : "MCP tool call failed.";
|
|
464
|
+
}
|
|
465
|
+
return formatCodexMcpToolCallEvents(phase === "started" ? "item.started" : "item.completed", { item });
|
|
466
|
+
}
|
|
467
|
+
function formatClaudeToolUseStarted(toolUse) {
|
|
468
|
+
const mcp = claudeMcpEvents(toolUse, "started", null);
|
|
469
|
+
if (mcp) {
|
|
470
|
+
return mcp;
|
|
471
|
+
}
|
|
472
|
+
const normalized = toolUse.name.toLowerCase();
|
|
473
|
+
const filePath = stringField(toolUse.input, "file_path") ?? stringField(toolUse.input, "notebook_path") ?? stringField(toolUse.input, "path");
|
|
474
|
+
let content;
|
|
475
|
+
if (normalized === "bash") {
|
|
476
|
+
const command = stringField(toolUse.input, "command");
|
|
477
|
+
content = command ? `Shell command started: ${clip(command, 500)}` : "Shell command started.";
|
|
478
|
+
}
|
|
479
|
+
else if (normalized === "read" && filePath) {
|
|
480
|
+
content = `Reading ${filePath}`;
|
|
481
|
+
}
|
|
482
|
+
else if (claudeFileEditTools.has(normalized) && filePath) {
|
|
483
|
+
content = `Writing ${filePath}`;
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
content = `Tool call started: ${toolUse.name}`;
|
|
487
|
+
}
|
|
488
|
+
return [{
|
|
489
|
+
role: "worker",
|
|
490
|
+
kind: "checkpoint",
|
|
491
|
+
content,
|
|
492
|
+
eventType: normalized === "bash" ? "provider_command" : "provider_tool_call"
|
|
493
|
+
}];
|
|
494
|
+
}
|
|
495
|
+
function formatClaudeToolUseCompleted(toolUse, isError, resultText) {
|
|
496
|
+
const mcp = claudeMcpEvents(toolUse, isError ? "failed" : "completed", resultText);
|
|
497
|
+
if (mcp) {
|
|
498
|
+
return mcp;
|
|
499
|
+
}
|
|
500
|
+
const normalized = toolUse.name.toLowerCase();
|
|
501
|
+
const filePath = stringField(toolUse.input, "file_path") ?? stringField(toolUse.input, "notebook_path") ?? stringField(toolUse.input, "path");
|
|
502
|
+
if (claudeFileEditTools.has(normalized) && filePath && !isError) {
|
|
503
|
+
return [{
|
|
504
|
+
role: "worker",
|
|
505
|
+
kind: "file_edit",
|
|
506
|
+
content: `Wrote ${filePath}`,
|
|
507
|
+
eventType: "provider_file_edit"
|
|
508
|
+
}];
|
|
509
|
+
}
|
|
510
|
+
const status = isError ? "failed" : "completed";
|
|
511
|
+
let content;
|
|
512
|
+
if (normalized === "bash") {
|
|
513
|
+
const command = stringField(toolUse.input, "command");
|
|
514
|
+
content = command ? `Shell command ${status}: ${clip(command, 500)}` : `Shell command ${status}.`;
|
|
515
|
+
}
|
|
516
|
+
else if (normalized === "read" && filePath) {
|
|
517
|
+
content = isError ? `Read failed: ${filePath}` : `Read ${filePath}`;
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
content = `Tool call ${status}: ${toolUse.name}`;
|
|
521
|
+
}
|
|
522
|
+
return [{
|
|
523
|
+
role: "worker",
|
|
524
|
+
kind: "checkpoint",
|
|
525
|
+
content,
|
|
526
|
+
eventType: normalized === "bash" ? "provider_command" : "provider_tool_call"
|
|
527
|
+
}];
|
|
528
|
+
}
|
|
529
|
+
class CodexJsonState {
|
|
530
|
+
threadId = null;
|
|
531
|
+
finalMessage = null;
|
|
532
|
+
emittedThread = false;
|
|
533
|
+
accumulatedUsage = null;
|
|
534
|
+
activeToolCallCount = 0;
|
|
535
|
+
get sessionLabel() {
|
|
536
|
+
return this.threadId ? `thread ${this.threadId}` : null;
|
|
537
|
+
}
|
|
538
|
+
get providerThreadId() {
|
|
539
|
+
return this.threadId;
|
|
540
|
+
}
|
|
541
|
+
get usage() {
|
|
542
|
+
return this.accumulatedUsage;
|
|
543
|
+
}
|
|
544
|
+
get hasActiveToolCall() {
|
|
545
|
+
return this.activeToolCallCount > 0;
|
|
546
|
+
}
|
|
547
|
+
consume(line) {
|
|
548
|
+
const event = parseJsonLine(line);
|
|
549
|
+
if (!event) {
|
|
550
|
+
return [];
|
|
551
|
+
}
|
|
552
|
+
const type = stringField(event, "type") ?? "";
|
|
553
|
+
this.trackToolActivity(type, event);
|
|
554
|
+
if (type === "thread.started") {
|
|
555
|
+
this.threadId = stringField(event, "thread_id");
|
|
556
|
+
if (this.threadId && !this.emittedThread) {
|
|
557
|
+
this.emittedThread = true;
|
|
558
|
+
return [{
|
|
559
|
+
role: "runner",
|
|
560
|
+
kind: "checkpoint",
|
|
561
|
+
content: `Codex thread started: ${this.threadId}.`,
|
|
562
|
+
eventType: "provider_thread_started",
|
|
563
|
+
data: JSON.stringify({ threadId: this.threadId })
|
|
564
|
+
}];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (type === "turn.completed") {
|
|
568
|
+
this.accumulateUsage(event);
|
|
569
|
+
return [{
|
|
570
|
+
role: "runner",
|
|
571
|
+
kind: "checkpoint",
|
|
572
|
+
content: "Codex turn completed.",
|
|
573
|
+
eventType: "provider_turn_completed"
|
|
574
|
+
}];
|
|
575
|
+
}
|
|
576
|
+
if (type === "turn.failed" || type === "error") {
|
|
577
|
+
return [{
|
|
578
|
+
role: "runner",
|
|
579
|
+
kind: "error",
|
|
580
|
+
content: clip(line.trim(), 1000),
|
|
581
|
+
eventType: "provider_cli_error"
|
|
582
|
+
}];
|
|
583
|
+
}
|
|
584
|
+
if (type === "item.started" || type === "item.completed" || type === "item.updated") {
|
|
585
|
+
const item = objectField(event, "item");
|
|
586
|
+
const itemType = stringField(item, "type");
|
|
587
|
+
if (itemType === "agent_message") {
|
|
588
|
+
const text = stringField(item, "text");
|
|
589
|
+
if (text) {
|
|
590
|
+
this.finalMessage = text;
|
|
591
|
+
return [{
|
|
592
|
+
role: "assistant",
|
|
593
|
+
kind: "message",
|
|
594
|
+
content: clip(text, 4000),
|
|
595
|
+
eventType: "provider_message"
|
|
596
|
+
}];
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (itemType === "command_execution") {
|
|
600
|
+
const command = stringField(item, "command");
|
|
601
|
+
const status = stringField(item, "status");
|
|
602
|
+
if (command) {
|
|
603
|
+
return [{
|
|
604
|
+
role: "worker",
|
|
605
|
+
kind: "checkpoint",
|
|
606
|
+
content: `Codex command ${status || (type === "item.started" ? "started" : "completed")}: ${clip(command, 500)}`,
|
|
607
|
+
eventType: "provider_command"
|
|
608
|
+
}];
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const mcpEvents = formatCodexMcpToolCallEvents(type, event);
|
|
613
|
+
if (mcpEvents.length > 0) {
|
|
614
|
+
return mcpEvents;
|
|
615
|
+
}
|
|
616
|
+
return [];
|
|
617
|
+
}
|
|
618
|
+
trackToolActivity(type, event) {
|
|
619
|
+
if (type !== "item.started" && type !== "item.completed") {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const item = objectField(event, "item");
|
|
623
|
+
const itemType = (stringField(item, "type") ?? "").toLowerCase();
|
|
624
|
+
const toolName = mcpToolName(item);
|
|
625
|
+
const isTool = itemType === "command_execution"
|
|
626
|
+
|| itemType.includes("mcp")
|
|
627
|
+
|| Boolean(toolName);
|
|
628
|
+
if (!isTool) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
this.activeToolCallCount = type === "item.started"
|
|
632
|
+
? this.activeToolCallCount + 1
|
|
633
|
+
: Math.max(0, this.activeToolCallCount - 1);
|
|
634
|
+
}
|
|
635
|
+
accumulateUsage(event) {
|
|
636
|
+
const usage = objectField(event, "usage");
|
|
637
|
+
if (Object.keys(usage).length === 0) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const inputTokens = readUsageNumber(usage, "input_tokens");
|
|
641
|
+
const cacheReadTokens = readUsageNumber(usage, "cached_input_tokens");
|
|
642
|
+
this.accumulatedUsage = addUsage(this.accumulatedUsage ?? emptyUsage("provider_reported"), {
|
|
643
|
+
inputTokens,
|
|
644
|
+
uncachedInputTokens: Math.max(0, inputTokens - cacheReadTokens),
|
|
645
|
+
outputTokens: readUsageNumber(usage, "output_tokens"),
|
|
646
|
+
cacheReadTokens,
|
|
647
|
+
reasoningOutputTokens: readUsageNumber(usage, "reasoning_output_tokens"),
|
|
648
|
+
source: "provider_reported",
|
|
649
|
+
inputTokenSemantics: "includes_cached_input",
|
|
650
|
+
rawUsage: [{ provider: "codex", usage }]
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
export function formatCodexMcpToolCallEvents(type, event) {
|
|
655
|
+
if (type !== "item.started" && type !== "item.updated" && type !== "item.completed") {
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
const item = objectField(event, "item");
|
|
659
|
+
const itemType = stringField(item, "type") ?? "";
|
|
660
|
+
const serverName = mcpServerName(item);
|
|
661
|
+
const toolName = mcpToolName(item);
|
|
662
|
+
const isMcpItem = itemType.toLowerCase().includes("mcp")
|
|
663
|
+
|| Boolean(serverName)
|
|
664
|
+
|| (toolName ? isForgeCompletionTool(toolName) || toolName.toLowerCase().includes("mcp") : false);
|
|
665
|
+
if (!isMcpItem || !toolName) {
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
668
|
+
const status = normalizedString(stringField(item, "status")
|
|
669
|
+
?? stringField(event, "status")
|
|
670
|
+
?? (type === "item.started" ? "started" : type === "item.completed" ? "completed" : "updated"));
|
|
671
|
+
const failed = status === "failed" || status === "error" || status === "cancelled" || Boolean(item.error);
|
|
672
|
+
const completed = failed || type === "item.completed" || status === "completed" || status === "succeeded" || status === "success";
|
|
673
|
+
const started = type === "item.started" || status === "started" || status === "running" || status === "in_progress";
|
|
674
|
+
const data = safeProviderMcpData(item, serverName ?? "unknown", toolName, status);
|
|
675
|
+
const label = `${serverName ?? "unknown"}.${toolName}`;
|
|
676
|
+
if (failed) {
|
|
677
|
+
const summary = mcpErrorSummary(item) ?? "MCP tool call failed.";
|
|
678
|
+
const events = [{
|
|
679
|
+
role: "tool",
|
|
680
|
+
kind: "error",
|
|
681
|
+
content: `MCP tool call failed: ${label} - ${summary}`,
|
|
682
|
+
eventType: "provider_mcp_call_failed",
|
|
683
|
+
data
|
|
684
|
+
}];
|
|
685
|
+
if (isForgeCompletionTool(toolName)) {
|
|
686
|
+
events.push({
|
|
687
|
+
role: "tool",
|
|
688
|
+
kind: "error",
|
|
689
|
+
content: `Forge completion tool validation failed: ${label} - ${summary}`,
|
|
690
|
+
eventType: "completion_tool_validation_failed",
|
|
691
|
+
data
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return events;
|
|
695
|
+
}
|
|
696
|
+
if (completed) {
|
|
697
|
+
return [{
|
|
698
|
+
role: "tool",
|
|
699
|
+
kind: "checkpoint",
|
|
700
|
+
content: `MCP tool call completed: ${label}.`,
|
|
701
|
+
eventType: "provider_mcp_call_completed",
|
|
702
|
+
data
|
|
703
|
+
}];
|
|
704
|
+
}
|
|
705
|
+
if (started) {
|
|
706
|
+
return [{
|
|
707
|
+
role: "tool",
|
|
708
|
+
kind: "checkpoint",
|
|
709
|
+
content: `MCP tool call started: ${label}.`,
|
|
710
|
+
eventType: "provider_mcp_call_started",
|
|
711
|
+
data
|
|
712
|
+
}];
|
|
713
|
+
}
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
function mcpServerName(item) {
|
|
717
|
+
return stringField(item, "server")
|
|
718
|
+
?? stringField(item, "server_name")
|
|
719
|
+
?? stringField(item, "mcp_server")
|
|
720
|
+
?? stringField(objectField(item, "mcp"), "server")
|
|
721
|
+
?? stringField(objectField(item, "mcp"), "server_name");
|
|
722
|
+
}
|
|
723
|
+
function mcpToolName(item) {
|
|
724
|
+
return stringField(item, "tool")
|
|
725
|
+
?? stringField(item, "tool_name")
|
|
726
|
+
?? stringField(item, "name")
|
|
727
|
+
?? stringField(objectField(item, "function"), "name")
|
|
728
|
+
?? stringField(objectField(item, "mcp"), "tool")
|
|
729
|
+
?? stringField(objectField(item, "mcp"), "tool_name");
|
|
730
|
+
}
|
|
731
|
+
function mcpErrorSummary(item) {
|
|
732
|
+
const direct = stringField(item, "error")
|
|
733
|
+
?? stringField(item, "error_message")
|
|
734
|
+
?? stringField(item, "message");
|
|
735
|
+
if (direct) {
|
|
736
|
+
return clip(direct, 600);
|
|
737
|
+
}
|
|
738
|
+
const messages = uniqueErrorMessages([
|
|
739
|
+
item.error,
|
|
740
|
+
item.result,
|
|
741
|
+
item.content,
|
|
742
|
+
]);
|
|
743
|
+
return messages.length > 0 ? clip(messages.join(" Caused by: "), 600) : null;
|
|
744
|
+
}
|
|
745
|
+
function uniqueErrorMessages(values) {
|
|
746
|
+
const messages = [];
|
|
747
|
+
const seenMessages = new Set();
|
|
748
|
+
const seenObjects = new Set();
|
|
749
|
+
const visit = (value, depth) => {
|
|
750
|
+
if (depth > 8 || value === null || value === undefined)
|
|
751
|
+
return;
|
|
752
|
+
if (typeof value === "string") {
|
|
753
|
+
const message = value.trim();
|
|
754
|
+
if (message.length > 0 && !seenMessages.has(message)) {
|
|
755
|
+
seenMessages.add(message);
|
|
756
|
+
messages.push(message);
|
|
757
|
+
}
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (typeof value !== "object" || seenObjects.has(value))
|
|
761
|
+
return;
|
|
762
|
+
seenObjects.add(value);
|
|
763
|
+
if (Array.isArray(value)) {
|
|
764
|
+
for (const entry of value)
|
|
765
|
+
visit(entry, depth + 1);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const record = value;
|
|
769
|
+
for (const key of ["message", "summary", "error", "cause", "data", "details", "result", "content"]) {
|
|
770
|
+
if (key in record)
|
|
771
|
+
visit(record[key], depth + 1);
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
for (const value of values)
|
|
775
|
+
visit(value, 0);
|
|
776
|
+
return messages;
|
|
777
|
+
}
|
|
778
|
+
function safeProviderMcpData(item, serverName, toolName, status) {
|
|
779
|
+
const elapsedMs = numericField(item, "elapsed_ms") ?? numericField(item, "duration_ms");
|
|
780
|
+
return JSON.stringify(stripUndefined({
|
|
781
|
+
server_name: serverName,
|
|
782
|
+
tool_name: toolName,
|
|
783
|
+
status,
|
|
784
|
+
elapsed_ms: elapsedMs,
|
|
785
|
+
error_summary: mcpErrorSummary(item)
|
|
786
|
+
}));
|
|
787
|
+
}
|
|
788
|
+
function isForgeCompletionTool(toolName) {
|
|
789
|
+
const normalized = toolName.trim().toLowerCase();
|
|
790
|
+
return normalized.startsWith("forge.complete_")
|
|
791
|
+
|| normalized.startsWith("complete_")
|
|
792
|
+
|| normalized.includes("forge.complete_")
|
|
793
|
+
|| normalized.includes("complete_architect_plan");
|
|
794
|
+
}
|
|
795
|
+
export function objectField(source, key) {
|
|
796
|
+
const value = source[key];
|
|
797
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
798
|
+
? value
|
|
799
|
+
: {};
|
|
800
|
+
}
|
|
801
|
+
export function stringField(source, key) {
|
|
802
|
+
const value = source[key];
|
|
803
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
804
|
+
}
|
|
805
|
+
export function numericField(source, key) {
|
|
806
|
+
const value = source[key];
|
|
807
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
808
|
+
}
|
|
809
|
+
export function stripUndefined(value) {
|
|
810
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
811
|
+
}
|
|
812
|
+
export function normalizedString(value) {
|
|
813
|
+
return value.trim().toLowerCase().replace(/-/g, "_");
|
|
814
|
+
}
|
|
815
|
+
export function isNodeError(error) {
|
|
816
|
+
return error instanceof Error && "code" in error;
|
|
817
|
+
}
|