@jini-ai/agent-runtime 0.1.0
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 +201 -0
- package/README.md +43 -0
- package/dist/acp-model-probe.d.ts +48 -0
- package/dist/acp-model-probe.d.ts.map +1 -0
- package/dist/acp-model-probe.js +15 -0
- package/dist/acp-model-probe.js.map +1 -0
- package/dist/agent-protocol/acp/account-failure.d.ts +54 -0
- package/dist/agent-protocol/acp/account-failure.d.ts.map +1 -0
- package/dist/agent-protocol/acp/account-failure.js +37 -0
- package/dist/agent-protocol/acp/account-failure.js.map +1 -0
- package/dist/agent-protocol/acp/constants.d.ts +26 -0
- package/dist/agent-protocol/acp/constants.d.ts.map +1 -0
- package/dist/agent-protocol/acp/constants.js +39 -0
- package/dist/agent-protocol/acp/constants.js.map +1 -0
- package/dist/agent-protocol/acp/index.d.ts +14 -0
- package/dist/agent-protocol/acp/index.d.ts.map +1 -0
- package/dist/agent-protocol/acp/index.js +14 -0
- package/dist/agent-protocol/acp/index.js.map +1 -0
- package/dist/agent-protocol/acp/json.d.ts +82 -0
- package/dist/agent-protocol/acp/json.d.ts.map +1 -0
- package/dist/agent-protocol/acp/json.js +144 -0
- package/dist/agent-protocol/acp/json.js.map +1 -0
- package/dist/agent-protocol/acp/models.d.ts +112 -0
- package/dist/agent-protocol/acp/models.d.ts.map +1 -0
- package/dist/agent-protocol/acp/models.js +293 -0
- package/dist/agent-protocol/acp/models.js.map +1 -0
- package/dist/agent-protocol/acp/rpc.d.ts +107 -0
- package/dist/agent-protocol/acp/rpc.d.ts.map +1 -0
- package/dist/agent-protocol/acp/rpc.js +162 -0
- package/dist/agent-protocol/acp/rpc.js.map +1 -0
- package/dist/agent-protocol/acp/session-params.d.ts +55 -0
- package/dist/agent-protocol/acp/session-params.d.ts.map +1 -0
- package/dist/agent-protocol/acp/session-params.js +81 -0
- package/dist/agent-protocol/acp/session-params.js.map +1 -0
- package/dist/agent-protocol/acp/session.d.ts +101 -0
- package/dist/agent-protocol/acp/session.d.ts.map +1 -0
- package/dist/agent-protocol/acp/session.js +832 -0
- package/dist/agent-protocol/acp/session.js.map +1 -0
- package/dist/agent-protocol/acp/text-suppression.d.ts +73 -0
- package/dist/agent-protocol/acp/text-suppression.d.ts.map +1 -0
- package/dist/agent-protocol/acp/text-suppression.js +229 -0
- package/dist/agent-protocol/acp/text-suppression.js.map +1 -0
- package/dist/agent-protocol/acp/types.d.ts +27 -0
- package/dist/agent-protocol/acp/types.d.ts.map +1 -0
- package/dist/agent-protocol/acp/types.js +2 -0
- package/dist/agent-protocol/acp/types.js.map +1 -0
- package/dist/agent-protocol/acp/updates.d.ts +166 -0
- package/dist/agent-protocol/acp/updates.d.ts.map +1 -0
- package/dist/agent-protocol/acp/updates.js +268 -0
- package/dist/agent-protocol/acp/updates.js.map +1 -0
- package/dist/agent-protocol/core/index.d.ts +6 -0
- package/dist/agent-protocol/core/index.d.ts.map +1 -0
- package/dist/agent-protocol/core/index.js +6 -0
- package/dist/agent-protocol/core/index.js.map +1 -0
- package/dist/agent-protocol/core/json-line-stream.d.ts +42 -0
- package/dist/agent-protocol/core/json-line-stream.d.ts.map +1 -0
- package/dist/agent-protocol/core/json-line-stream.js +333 -0
- package/dist/agent-protocol/core/json-line-stream.js.map +1 -0
- package/dist/agent-protocol/index.d.ts +8 -0
- package/dist/agent-protocol/index.d.ts.map +1 -0
- package/dist/agent-protocol/index.js +8 -0
- package/dist/agent-protocol/index.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/events.d.ts +26 -0
- package/dist/agent-protocol/pi-rpc/events.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/events.js +167 -0
- package/dist/agent-protocol/pi-rpc/events.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/index.d.ts +11 -0
- package/dist/agent-protocol/pi-rpc/index.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/index.js +11 -0
- package/dist/agent-protocol/pi-rpc/index.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/internal.d.ts +45 -0
- package/dist/agent-protocol/pi-rpc/internal.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/internal.js +41 -0
- package/dist/agent-protocol/pi-rpc/internal.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/models.d.ts +21 -0
- package/dist/agent-protocol/pi-rpc/models.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/models.js +48 -0
- package/dist/agent-protocol/pi-rpc/models.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/session.d.ts +106 -0
- package/dist/agent-protocol/pi-rpc/session.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/session.js +371 -0
- package/dist/agent-protocol/pi-rpc/session.js.map +1 -0
- package/dist/amr-model-cache.d.ts +40 -0
- package/dist/amr-model-cache.d.ts.map +1 -0
- package/dist/amr-model-cache.js +79 -0
- package/dist/amr-model-cache.js.map +1 -0
- package/dist/amr-profile-resolver.d.ts +21 -0
- package/dist/amr-profile-resolver.d.ts.map +1 -0
- package/dist/amr-profile-resolver.js +4 -0
- package/dist/amr-profile-resolver.js.map +1 -0
- package/dist/artifact-taxonomy.d.ts +27 -0
- package/dist/artifact-taxonomy.d.ts.map +1 -0
- package/dist/artifact-taxonomy.js +5 -0
- package/dist/artifact-taxonomy.js.map +1 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +332 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities.d.ts +9 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +2 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/claude-stream.d.ts +37 -0
- package/dist/claude-stream.d.ts.map +1 -0
- package/dist/claude-stream.js +660 -0
- package/dist/claude-stream.js.map +1 -0
- package/dist/copilot-stream.d.ts +37 -0
- package/dist/copilot-stream.d.ts.map +1 -0
- package/dist/copilot-stream.js +136 -0
- package/dist/copilot-stream.js.map +1 -0
- package/dist/defs/aider.d.ts +13 -0
- package/dist/defs/aider.d.ts.map +1 -0
- package/dist/defs/aider.js +63 -0
- package/dist/defs/aider.js.map +1 -0
- package/dist/defs/amp.d.ts +12 -0
- package/dist/defs/amp.d.ts.map +1 -0
- package/dist/defs/amp.js +56 -0
- package/dist/defs/amp.js.map +1 -0
- package/dist/defs/amr.d.ts +37 -0
- package/dist/defs/amr.d.ts.map +1 -0
- package/dist/defs/amr.js +303 -0
- package/dist/defs/amr.js.map +1 -0
- package/dist/defs/antigravity.d.ts +25 -0
- package/dist/defs/antigravity.d.ts.map +1 -0
- package/dist/defs/antigravity.js +213 -0
- package/dist/defs/antigravity.js.map +1 -0
- package/dist/defs/claude.d.ts +30 -0
- package/dist/defs/claude.d.ts.map +1 -0
- package/dist/defs/claude.js +130 -0
- package/dist/defs/claude.js.map +1 -0
- package/dist/defs/codebuddy.d.ts +26 -0
- package/dist/defs/codebuddy.d.ts.map +1 -0
- package/dist/defs/codebuddy.js +124 -0
- package/dist/defs/codebuddy.js.map +1 -0
- package/dist/defs/codex.d.ts +30 -0
- package/dist/defs/codex.d.ts.map +1 -0
- package/dist/defs/codex.js +196 -0
- package/dist/defs/codex.js.map +1 -0
- package/dist/defs/copilot.d.ts +12 -0
- package/dist/defs/copilot.d.ts.map +1 -0
- package/dist/defs/copilot.js +88 -0
- package/dist/defs/copilot.js.map +1 -0
- package/dist/defs/cursor-agent.d.ts +27 -0
- package/dist/defs/cursor-agent.d.ts.map +1 -0
- package/dist/defs/cursor-agent.js +93 -0
- package/dist/defs/cursor-agent.js.map +1 -0
- package/dist/defs/deepseek.d.ts +12 -0
- package/dist/defs/deepseek.d.ts.map +1 -0
- package/dist/defs/deepseek.js +53 -0
- package/dist/defs/deepseek.js.map +1 -0
- package/dist/defs/devin.d.ts +12 -0
- package/dist/defs/devin.d.ts.map +1 -0
- package/dist/defs/devin.js +45 -0
- package/dist/defs/devin.js.map +1 -0
- package/dist/defs/grok-build.d.ts +26 -0
- package/dist/defs/grok-build.d.ts.map +1 -0
- package/dist/defs/grok-build.js +105 -0
- package/dist/defs/grok-build.js.map +1 -0
- package/dist/defs/hermes.d.ts +13 -0
- package/dist/defs/hermes.d.ts.map +1 -0
- package/dist/defs/hermes.js +51 -0
- package/dist/defs/hermes.js.map +1 -0
- package/dist/defs/index.d.ts +33 -0
- package/dist/defs/index.d.ts.map +1 -0
- package/dist/defs/index.js +33 -0
- package/dist/defs/index.js.map +1 -0
- package/dist/defs/kilo.d.ts +12 -0
- package/dist/defs/kilo.d.ts.map +1 -0
- package/dist/defs/kilo.js +20 -0
- package/dist/defs/kilo.js.map +1 -0
- package/dist/defs/kimi.d.ts +13 -0
- package/dist/defs/kimi.d.ts.map +1 -0
- package/dist/defs/kimi.js +26 -0
- package/dist/defs/kimi.js.map +1 -0
- package/dist/defs/kiro.d.ts +12 -0
- package/dist/defs/kiro.d.ts.map +1 -0
- package/dist/defs/kiro.js +20 -0
- package/dist/defs/kiro.js.map +1 -0
- package/dist/defs/mimo.d.ts +13 -0
- package/dist/defs/mimo.d.ts.map +1 -0
- package/dist/defs/mimo.js +26 -0
- package/dist/defs/mimo.js.map +1 -0
- package/dist/defs/opencode.d.ts +27 -0
- package/dist/defs/opencode.d.ts.map +1 -0
- package/dist/defs/opencode.js +89 -0
- package/dist/defs/opencode.js.map +1 -0
- package/dist/defs/pi.d.ts +18 -0
- package/dist/defs/pi.d.ts.map +1 -0
- package/dist/defs/pi.js +94 -0
- package/dist/defs/pi.js.map +1 -0
- package/dist/defs/qoder.d.ts +11 -0
- package/dist/defs/qoder.d.ts.map +1 -0
- package/dist/defs/qoder.js +50 -0
- package/dist/defs/qoder.js.map +1 -0
- package/dist/defs/qwen.d.ts +11 -0
- package/dist/defs/qwen.d.ts.map +1 -0
- package/dist/defs/qwen.js +28 -0
- package/dist/defs/qwen.js.map +1 -0
- package/dist/defs/reasonix.d.ts +20 -0
- package/dist/defs/reasonix.d.ts.map +1 -0
- package/dist/defs/reasonix.js +60 -0
- package/dist/defs/reasonix.js.map +1 -0
- package/dist/defs/shared.d.ts +23 -0
- package/dist/defs/shared.d.ts.map +1 -0
- package/dist/defs/shared.js +58 -0
- package/dist/defs/shared.js.map +1 -0
- package/dist/defs/trae-cli.d.ts +14 -0
- package/dist/defs/trae-cli.d.ts.map +1 -0
- package/dist/defs/trae-cli.js +22 -0
- package/dist/defs/trae-cli.js.map +1 -0
- package/dist/defs/vibe.d.ts +12 -0
- package/dist/defs/vibe.d.ts.map +1 -0
- package/dist/defs/vibe.js +20 -0
- package/dist/defs/vibe.js.map +1 -0
- package/dist/detection.d.ts +14 -0
- package/dist/detection.d.ts.map +1 -0
- package/dist/detection.js +266 -0
- package/dist/detection.js.map +1 -0
- package/dist/diagnostics.d.ts +8 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +95 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/env.d.ts +19 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +95 -0
- package/dist/env.js.map +1 -0
- package/dist/executables.d.ts +24 -0
- package/dist/executables.d.ts.map +1 -0
- package/dist/executables.js +349 -0
- package/dist/executables.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/invocation.d.ts +6 -0
- package/dist/invocation.d.ts.map +1 -0
- package/dist/invocation.js +47 -0
- package/dist/invocation.js.map +1 -0
- package/dist/json-event-stream.d.ts +9 -0
- package/dist/json-event-stream.d.ts.map +1 -0
- package/dist/json-event-stream.js +856 -0
- package/dist/json-event-stream.js.map +1 -0
- package/dist/launch.d.ts +12 -0
- package/dist/launch.d.ts.map +1 -0
- package/dist/launch.js +221 -0
- package/dist/launch.js.map +1 -0
- package/dist/mcp.d.ts +44 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +12 -0
- package/dist/mcp.js.map +1 -0
- package/dist/metadata.d.ts +24 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +100 -0
- package/dist/metadata.js.map +1 -0
- package/dist/mmd-routes.d.ts +12 -0
- package/dist/mmd-routes.d.ts.map +1 -0
- package/dist/mmd-routes.js +146 -0
- package/dist/mmd-routes.js.map +1 -0
- package/dist/model-registry.d.ts +120 -0
- package/dist/model-registry.d.ts.map +1 -0
- package/dist/model-registry.js +77 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/models.d.ts +16 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +93 -0
- package/dist/models.js.map +1 -0
- package/dist/opencode-log.d.ts +16 -0
- package/dist/opencode-log.d.ts.map +1 -0
- package/dist/opencode-log.js +163 -0
- package/dist/opencode-log.js.map +1 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +29 -0
- package/dist/paths.js.map +1 -0
- package/dist/pi-models.d.ts +13 -0
- package/dist/pi-models.d.ts.map +1 -0
- package/dist/pi-models.js +36 -0
- package/dist/pi-models.js.map +1 -0
- package/dist/prompt-augmenter.d.ts +60 -0
- package/dist/prompt-augmenter.d.ts.map +1 -0
- package/dist/prompt-augmenter.js +26 -0
- package/dist/prompt-augmenter.js.map +1 -0
- package/dist/prompt-budget.d.ts +20 -0
- package/dist/prompt-budget.d.ts.map +1 -0
- package/dist/prompt-budget.js +226 -0
- package/dist/prompt-budget.js.map +1 -0
- package/dist/prompt-file.d.ts +7 -0
- package/dist/prompt-file.d.ts.map +1 -0
- package/dist/prompt-file.js +27 -0
- package/dist/prompt-file.js.map +1 -0
- package/dist/providers/aihubmix.d.ts +99 -0
- package/dist/providers/aihubmix.d.ts.map +1 -0
- package/dist/providers/aihubmix.js +239 -0
- package/dist/providers/aihubmix.js.map +1 -0
- package/dist/providers/anthropic-messages.d.ts +116 -0
- package/dist/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/providers/anthropic-messages.js +313 -0
- package/dist/providers/anthropic-messages.js.map +1 -0
- package/dist/providers/azure-chat.d.ts +99 -0
- package/dist/providers/azure-chat.d.ts.map +1 -0
- package/dist/providers/azure-chat.js +164 -0
- package/dist/providers/azure-chat.js.map +1 -0
- package/dist/providers/connection-guard.d.ts +67 -0
- package/dist/providers/connection-guard.d.ts.map +1 -0
- package/dist/providers/connection-guard.js +211 -0
- package/dist/providers/connection-guard.js.map +1 -0
- package/dist/providers/elevenlabs.d.ts +28 -0
- package/dist/providers/elevenlabs.d.ts.map +1 -0
- package/dist/providers/elevenlabs.js +138 -0
- package/dist/providers/elevenlabs.js.map +1 -0
- package/dist/providers/google-messages.d.ts +108 -0
- package/dist/providers/google-messages.d.ts.map +1 -0
- package/dist/providers/google-messages.js +284 -0
- package/dist/providers/google-messages.js.map +1 -0
- package/dist/providers/google.d.ts +20 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +42 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/index.d.ts +39 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +39 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/model-catalog.d.ts +32 -0
- package/dist/providers/model-catalog.d.ts.map +1 -0
- package/dist/providers/model-catalog.js +394 -0
- package/dist/providers/model-catalog.js.map +1 -0
- package/dist/providers/oauth-callback-server.d.ts +35 -0
- package/dist/providers/oauth-callback-server.d.ts.map +1 -0
- package/dist/providers/oauth-callback-server.js +217 -0
- package/dist/providers/oauth-callback-server.js.map +1 -0
- package/dist/providers/oauth-credentials.d.ts +29 -0
- package/dist/providers/oauth-credentials.d.ts.map +1 -0
- package/dist/providers/oauth-credentials.js +70 -0
- package/dist/providers/oauth-credentials.js.map +1 -0
- package/dist/providers/oauth-provider.d.ts +95 -0
- package/dist/providers/oauth-provider.d.ts.map +1 -0
- package/dist/providers/oauth-provider.js +130 -0
- package/dist/providers/oauth-provider.js.map +1 -0
- package/dist/providers/oauth-tokens.d.ts +35 -0
- package/dist/providers/oauth-tokens.d.ts.map +1 -0
- package/dist/providers/oauth-tokens.js +136 -0
- package/dist/providers/oauth-tokens.js.map +1 -0
- package/dist/providers/ollama-chat.d.ts +106 -0
- package/dist/providers/ollama-chat.d.ts.map +1 -0
- package/dist/providers/ollama-chat.js +292 -0
- package/dist/providers/ollama-chat.js.map +1 -0
- package/dist/providers/openai-chat.d.ts +147 -0
- package/dist/providers/openai-chat.d.ts.map +1 -0
- package/dist/providers/openai-chat.js +302 -0
- package/dist/providers/openai-chat.js.map +1 -0
- package/dist/providers/pkce.d.ts +81 -0
- package/dist/providers/pkce.d.ts.map +1 -0
- package/dist/providers/pkce.js +176 -0
- package/dist/providers/pkce.js.map +1 -0
- package/dist/providers/sse-decode.d.ts +47 -0
- package/dist/providers/sse-decode.d.ts.map +1 -0
- package/dist/providers/sse-decode.js +109 -0
- package/dist/providers/sse-decode.js.map +1 -0
- package/dist/providers/token-params.d.ts +29 -0
- package/dist/providers/token-params.d.ts.map +1 -0
- package/dist/providers/token-params.js +41 -0
- package/dist/providers/token-params.js.map +1 -0
- package/dist/providers/turn-end-guard.d.ts +37 -0
- package/dist/providers/turn-end-guard.d.ts.map +1 -0
- package/dist/providers/turn-end-guard.js +42 -0
- package/dist/providers/turn-end-guard.js.map +1 -0
- package/dist/providers/types.d.ts +36 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +13 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/qoder-stream.d.ts +19 -0
- package/dist/qoder-stream.d.ts.map +1 -0
- package/dist/qoder-stream.js +167 -0
- package/dist/qoder-stream.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +58 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolution.d.ts +2 -0
- package/dist/resolution.d.ts.map +1 -0
- package/dist/resolution.js +19 -0
- package/dist/resolution.js.map +1 -0
- package/dist/role-marker-guard.d.ts +53 -0
- package/dist/role-marker-guard.d.ts.map +1 -0
- package/dist/role-marker-guard.js +280 -0
- package/dist/role-marker-guard.js.map +1 -0
- package/dist/telemetry-sink.d.ts +35 -0
- package/dist/telemetry-sink.d.ts.map +1 -0
- package/dist/telemetry-sink.js +5 -0
- package/dist/telemetry-sink.js.map +1 -0
- package/dist/terminal-launch.d.ts +11 -0
- package/dist/terminal-launch.d.ts.map +1 -0
- package/dist/terminal-launch.js +123 -0
- package/dist/terminal-launch.js.map +1 -0
- package/dist/types.d.ts +185 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function safeParseJson(value) {
|
|
5
|
+
if (value == null)
|
|
6
|
+
return null;
|
|
7
|
+
if (typeof value === 'object')
|
|
8
|
+
return value;
|
|
9
|
+
if (typeof value !== 'string')
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(value);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function stringifyContent(value) {
|
|
19
|
+
if (typeof value === 'string')
|
|
20
|
+
return value;
|
|
21
|
+
if (value == null)
|
|
22
|
+
return '';
|
|
23
|
+
try {
|
|
24
|
+
return JSON.stringify(value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Genuinely unreachable through this module's real call graph, not just
|
|
28
|
+
// unobserved: every one of this function's 8 call sites passes either
|
|
29
|
+
// `obj` itself or a field/subfield of `obj` (e.g. `statePart.output`,
|
|
30
|
+
// `obj.content`, `item.aggregated_output`), and `obj` is only ever
|
|
31
|
+
// produced one way in this file — `handleLine`'s `obj = JSON.parse(line)`
|
|
32
|
+
// (this module's sole exported entry point, `createJsonEventStreamHandler`,
|
|
33
|
+
// routes every event through it). A value that came out of `JSON.parse`
|
|
34
|
+
// can only be `null`/a string/a plain finite number/a boolean/an array/a
|
|
35
|
+
// plain object — the JSON grammar has no circular-reference or BigInt
|
|
36
|
+
// production — so `JSON.stringify` can never throw on it or on any of
|
|
37
|
+
// its nested fields. Kept (not deleted) as protection against a future
|
|
38
|
+
// caller that feeds this module data from somewhere other than
|
|
39
|
+
// `JSON.parse`, the same "keep the defensive branch, document it, don't
|
|
40
|
+
// force a test around impossible data" call `@jini-ai/cli`'s
|
|
41
|
+
// `defaultReadFile`'s `finish()` guard made on 2026-07-22 — see
|
|
42
|
+
// `source-map.md`'s 2026-07-22 entry for the full re-derivation.
|
|
43
|
+
return String(value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function parseJsonObjectsFromContent(value) {
|
|
47
|
+
// No `!trimmed` early-return: this function's only real call site
|
|
48
|
+
// (`connectorToolSelectionErrorMessage`, below) has already checked
|
|
49
|
+
// `content.includes('CONNECTOR_TOOL_NOT_FOUND')` before calling in, so
|
|
50
|
+
// `value` can never trim down to empty here. The guard was dead code for
|
|
51
|
+
// every real caller; removed rather than padded with a test, since the
|
|
52
|
+
// fallback path below already produces the same `[]` result for an empty
|
|
53
|
+
// string on its own (safeParseJson('') throws -> null -> not a record;
|
|
54
|
+
// ''.split(/\r?\n/u) yields [''] -> same outcome), so nothing changes
|
|
55
|
+
// for a hypothetical future caller either.
|
|
56
|
+
const trimmed = value.trim();
|
|
57
|
+
const direct = safeParseJson(trimmed);
|
|
58
|
+
if (isRecord(direct))
|
|
59
|
+
return [direct];
|
|
60
|
+
const objects = [];
|
|
61
|
+
for (const line of trimmed.split(/\r?\n/u)) {
|
|
62
|
+
const parsedLine = safeParseJson(line.trim());
|
|
63
|
+
if (isRecord(parsedLine))
|
|
64
|
+
objects.push(parsedLine);
|
|
65
|
+
}
|
|
66
|
+
return objects;
|
|
67
|
+
}
|
|
68
|
+
function extractConnectorApiError(value) {
|
|
69
|
+
if (isRecord(value.error)) {
|
|
70
|
+
if (typeof value.error.code === 'string')
|
|
71
|
+
return value.error;
|
|
72
|
+
if (isRecord(value.error.data) && isRecord(value.error.data.error)) {
|
|
73
|
+
const wrappedError = value.error.data.error;
|
|
74
|
+
if (typeof wrappedError.code === 'string')
|
|
75
|
+
return wrappedError;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
function connectorToolSelectionErrorMessage(content) {
|
|
81
|
+
if (!content.includes('CONNECTOR_TOOL_NOT_FOUND'))
|
|
82
|
+
return null;
|
|
83
|
+
let error = null;
|
|
84
|
+
for (const parsed of parseJsonObjectsFromContent(content)) {
|
|
85
|
+
const parsedError = extractConnectorApiError(parsed);
|
|
86
|
+
if (parsedError?.code === 'CONNECTOR_TOOL_NOT_FOUND') {
|
|
87
|
+
error = parsedError;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!error)
|
|
92
|
+
return null;
|
|
93
|
+
const details = isRecord(error.details) ? error.details : {};
|
|
94
|
+
const connectorId = typeof details.connectorId === 'string' && details.connectorId
|
|
95
|
+
? details.connectorId
|
|
96
|
+
: undefined;
|
|
97
|
+
const toolName = typeof details.toolName === 'string' && details.toolName
|
|
98
|
+
? details.toolName
|
|
99
|
+
: 'the requested connector tool';
|
|
100
|
+
const target = connectorId
|
|
101
|
+
? `Connector tool ${toolName} is not allowed for connector ${connectorId}.`
|
|
102
|
+
: `Connector tool ${toolName} is not allowed.`;
|
|
103
|
+
return `${target} Re-list the connector catalog and choose one of the currently allowed read-only tools.`;
|
|
104
|
+
}
|
|
105
|
+
function extractErrorMessage(value, fallback) {
|
|
106
|
+
if (typeof value === 'string') {
|
|
107
|
+
const parsed = safeParseJson(value);
|
|
108
|
+
if (parsed && typeof parsed === 'object') {
|
|
109
|
+
return extractErrorMessage(parsed, value);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
if (isRecord(value)) {
|
|
114
|
+
if (typeof value.detail === 'string' && value.detail)
|
|
115
|
+
return value.detail;
|
|
116
|
+
if (typeof value.message === 'string' && value.message) {
|
|
117
|
+
return extractErrorMessage(value.message, value.message);
|
|
118
|
+
}
|
|
119
|
+
if (typeof value.error === 'string' && value.error)
|
|
120
|
+
return value.error;
|
|
121
|
+
if (value.error && typeof value.error === 'object') {
|
|
122
|
+
return extractErrorMessage(value.error, fallback);
|
|
123
|
+
}
|
|
124
|
+
if (value.data && typeof value.data === 'object') {
|
|
125
|
+
const dataMessage = extractErrorMessage(value.data, '');
|
|
126
|
+
if (dataMessage)
|
|
127
|
+
return dataMessage;
|
|
128
|
+
}
|
|
129
|
+
if (typeof value.name === 'string' && value.name)
|
|
130
|
+
return value.name;
|
|
131
|
+
}
|
|
132
|
+
return fallback;
|
|
133
|
+
}
|
|
134
|
+
function isRecoverableCodexReconnect(message) {
|
|
135
|
+
return (message.startsWith('Reconnecting...') &&
|
|
136
|
+
(message.includes('timeout waiting for child process to exit') ||
|
|
137
|
+
message.includes('stream disconnected before completion')));
|
|
138
|
+
}
|
|
139
|
+
function formatOpenCodeUsage(tokens) {
|
|
140
|
+
if (!isRecord(tokens))
|
|
141
|
+
return null;
|
|
142
|
+
const usage = {};
|
|
143
|
+
if (typeof tokens.input === 'number')
|
|
144
|
+
usage.input_tokens = tokens.input;
|
|
145
|
+
if (typeof tokens.output === 'number')
|
|
146
|
+
usage.output_tokens = tokens.output;
|
|
147
|
+
if (typeof tokens.reasoning === 'number')
|
|
148
|
+
usage.thought_tokens = tokens.reasoning;
|
|
149
|
+
if (isRecord(tokens.cache)) {
|
|
150
|
+
if (typeof tokens.cache.read === 'number')
|
|
151
|
+
usage.cached_read_tokens = tokens.cache.read;
|
|
152
|
+
if (typeof tokens.cache.write === 'number')
|
|
153
|
+
usage.cached_write_tokens = tokens.cache.write;
|
|
154
|
+
}
|
|
155
|
+
return Object.keys(usage).length > 0 ? usage : null;
|
|
156
|
+
}
|
|
157
|
+
function handleOpenCodeEvent(obj, onEvent, state) {
|
|
158
|
+
if (!isRecord(obj))
|
|
159
|
+
return false;
|
|
160
|
+
const part = isRecord(obj.part) ? obj.part : {};
|
|
161
|
+
if (obj.type === 'step_start') {
|
|
162
|
+
// `sessionID` is OpenCode's own session handle (capture-style resume).
|
|
163
|
+
// Surface it on the step-start status so the daemon can persist it to
|
|
164
|
+
// `agent_sessions` and replay it as `run -s <id>` next turn. OpenCode
|
|
165
|
+
// stamps it on every stream event; step_start is the turn opener, so a
|
|
166
|
+
// create turn always exposes it here.
|
|
167
|
+
const sessionId = typeof obj.sessionID === 'string' && obj.sessionID.length > 0
|
|
168
|
+
? obj.sessionID
|
|
169
|
+
: null;
|
|
170
|
+
onEvent({ type: 'status', label: 'running', sessionId });
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (obj.type === 'text' && typeof part.text === 'string' && part.text.length > 0) {
|
|
174
|
+
onEvent({ type: 'text_delta', delta: part.text });
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
if (obj.type === 'tool_use' && typeof part.tool === 'string' && typeof part.callID === 'string') {
|
|
178
|
+
const statePart = isRecord(part.state) ? part.state : null;
|
|
179
|
+
const key = `${obj.sessionID || 'session'}:${part.callID}`;
|
|
180
|
+
if (!state.openCodeToolUses.has(key)) {
|
|
181
|
+
state.openCodeToolUses.add(key);
|
|
182
|
+
onEvent({
|
|
183
|
+
type: 'tool_use',
|
|
184
|
+
id: part.callID,
|
|
185
|
+
name: part.tool,
|
|
186
|
+
input: safeParseJson(statePart?.input) ?? statePart?.input ?? null,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (statePart?.status === 'completed') {
|
|
190
|
+
onEvent({
|
|
191
|
+
type: 'tool_result',
|
|
192
|
+
toolUseId: part.callID,
|
|
193
|
+
content: stringifyContent(statePart.output),
|
|
194
|
+
isError: false,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (obj.type === 'step_finish') {
|
|
200
|
+
const usage = formatOpenCodeUsage(part.tokens);
|
|
201
|
+
if (usage) {
|
|
202
|
+
onEvent({
|
|
203
|
+
type: 'usage',
|
|
204
|
+
usage,
|
|
205
|
+
costUsd: typeof part.cost === 'number' ? part.cost : undefined,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (obj.type === 'error') {
|
|
211
|
+
// OpenCode emits structured error frames on stdout (e.g. provider auth
|
|
212
|
+
// failures, network errors, schema mismatches) and still exits 0. Surface
|
|
213
|
+
// them as proper `error` events so server.ts's `sendAgentEvent` wrapper
|
|
214
|
+
// can flip the run to `failed` and forward a visible SSE error to the
|
|
215
|
+
// chat UI. Previously we downgraded these to `type:'raw'`, which is not
|
|
216
|
+
// rendered as an assistant message — the run looked like a fast clean
|
|
217
|
+
// success while the user actually got nothing back. See issue #691.
|
|
218
|
+
//
|
|
219
|
+
// Shape mirrors the qoder-stream contract (`{type, message, raw}`) so
|
|
220
|
+
// the daemon's existing error-handling path recognises it without
|
|
221
|
+
// further wiring.
|
|
222
|
+
const message = extractErrorMessage(obj.error ?? obj.message, 'OpenCode error');
|
|
223
|
+
onEvent({ type: 'error', message, raw: stringifyContent(obj) });
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
function handleGeminiEvent(obj, onEvent, state) {
|
|
229
|
+
if (!isRecord(obj))
|
|
230
|
+
return false;
|
|
231
|
+
const isAssistantTextMessage = obj.type === 'message' &&
|
|
232
|
+
obj.role === 'assistant' &&
|
|
233
|
+
typeof obj.content === 'string' &&
|
|
234
|
+
obj.content.length > 0;
|
|
235
|
+
if (!isAssistantTextMessage) {
|
|
236
|
+
flushPendingArtifactText(state, onEvent);
|
|
237
|
+
}
|
|
238
|
+
if (obj.type === 'init') {
|
|
239
|
+
onEvent({
|
|
240
|
+
type: 'status',
|
|
241
|
+
label: 'initializing',
|
|
242
|
+
model: typeof obj.model === 'string' ? obj.model : undefined,
|
|
243
|
+
});
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (obj.type === 'message' && obj.role === 'user') {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (obj.type === 'message' &&
|
|
250
|
+
obj.role === 'assistant' &&
|
|
251
|
+
typeof obj.content === 'string' &&
|
|
252
|
+
obj.content.length > 0) {
|
|
253
|
+
const delta = stripDuplicateArtifactText(obj.content, state);
|
|
254
|
+
if (delta)
|
|
255
|
+
onEvent({ type: 'text_delta', delta });
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
if (obj.type === 'tool_use' &&
|
|
259
|
+
typeof obj.tool_id === 'string' &&
|
|
260
|
+
typeof obj.tool_name === 'string') {
|
|
261
|
+
const input = safeParseJson(obj.parameters) ?? obj.parameters ?? null;
|
|
262
|
+
if (obj.tool_name === 'write_todos') {
|
|
263
|
+
const todoInput = todoWriteInputFromParsedValue(input);
|
|
264
|
+
if (todoInput) {
|
|
265
|
+
onEvent({
|
|
266
|
+
type: 'tool_use',
|
|
267
|
+
id: `${obj.tool_id}:todo-native`,
|
|
268
|
+
name: 'TodoWrite',
|
|
269
|
+
input: todoInput,
|
|
270
|
+
});
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (isFileWriteToolUse(obj.tool_name, input)) {
|
|
275
|
+
state.suppressNextArtifactText = true;
|
|
276
|
+
}
|
|
277
|
+
onEvent({
|
|
278
|
+
type: 'tool_use',
|
|
279
|
+
id: obj.tool_id,
|
|
280
|
+
name: obj.tool_name,
|
|
281
|
+
input,
|
|
282
|
+
});
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
if (obj.type === 'tool_result' && typeof obj.tool_id === 'string') {
|
|
286
|
+
const error = isRecord(obj.error) ? obj.error : null;
|
|
287
|
+
const errorMessage = error ? extractErrorMessage(error, '') : '';
|
|
288
|
+
const output = typeof obj.output === 'string'
|
|
289
|
+
? obj.output
|
|
290
|
+
: errorMessage || stringifyContent(obj.output);
|
|
291
|
+
onEvent({
|
|
292
|
+
type: 'tool_result',
|
|
293
|
+
toolUseId: obj.tool_id,
|
|
294
|
+
content: output,
|
|
295
|
+
isError: obj.status === 'error' || Boolean(error),
|
|
296
|
+
});
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (obj.type === 'error') {
|
|
300
|
+
const severity = typeof obj.severity === 'string' ? obj.severity.toLowerCase() : '';
|
|
301
|
+
const message = extractErrorMessage(obj.message ?? obj.error, severity === 'warning' ? 'Gemini CLI warning' : 'Gemini CLI error');
|
|
302
|
+
if (severity === 'warning') {
|
|
303
|
+
onEvent({ type: 'status', label: 'warning', detail: message });
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
onEvent({ type: 'error', message, raw: stringifyContent(obj) });
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
if (obj.type === 'result') {
|
|
311
|
+
if (obj.status === 'error' || isRecord(obj.error)) {
|
|
312
|
+
onEvent({
|
|
313
|
+
type: 'error',
|
|
314
|
+
message: extractErrorMessage(obj.error, 'Gemini CLI error'),
|
|
315
|
+
raw: stringifyContent(obj),
|
|
316
|
+
});
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if (!isRecord(obj.stats))
|
|
320
|
+
return true;
|
|
321
|
+
const usage = {};
|
|
322
|
+
if (typeof obj.stats.input_tokens === 'number')
|
|
323
|
+
usage.input_tokens = obj.stats.input_tokens;
|
|
324
|
+
if (typeof obj.stats.output_tokens === 'number')
|
|
325
|
+
usage.output_tokens = obj.stats.output_tokens;
|
|
326
|
+
if (typeof obj.stats.cached === 'number')
|
|
327
|
+
usage.cached_read_tokens = obj.stats.cached;
|
|
328
|
+
onEvent({
|
|
329
|
+
type: 'usage',
|
|
330
|
+
usage,
|
|
331
|
+
durationMs: typeof obj.stats.duration_ms === 'number' ? obj.stats.duration_ms : undefined,
|
|
332
|
+
});
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
function handleKimiEvent(obj, onEvent) {
|
|
338
|
+
if (!isRecord(obj))
|
|
339
|
+
return false;
|
|
340
|
+
if (obj.role === 'assistant' && Array.isArray(obj.tool_calls)) {
|
|
341
|
+
for (const rawCall of obj.tool_calls) {
|
|
342
|
+
const call = isRecord(rawCall) ? rawCall : null;
|
|
343
|
+
const fn = isRecord(call?.function) ? call.function : null;
|
|
344
|
+
const id = typeof call?.id === 'string' && call.id.trim()
|
|
345
|
+
? call.id.trim()
|
|
346
|
+
: null;
|
|
347
|
+
const name = typeof fn?.name === 'string' && fn.name.trim()
|
|
348
|
+
? fn.name.trim()
|
|
349
|
+
: null;
|
|
350
|
+
if (!id || !name)
|
|
351
|
+
continue;
|
|
352
|
+
const input = safeParseJson(fn?.arguments) ?? fn?.arguments ?? null;
|
|
353
|
+
onEvent({ type: 'tool_use', id, name, input });
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (obj.role === 'tool' &&
|
|
358
|
+
typeof obj.tool_call_id === 'string' &&
|
|
359
|
+
obj.tool_call_id.trim()) {
|
|
360
|
+
onEvent({
|
|
361
|
+
type: 'tool_result',
|
|
362
|
+
toolUseId: obj.tool_call_id.trim(),
|
|
363
|
+
content: stringifyContent(obj.content),
|
|
364
|
+
isError: false,
|
|
365
|
+
});
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
if (obj.role === 'assistant' &&
|
|
369
|
+
typeof obj.content === 'string' &&
|
|
370
|
+
obj.content.length > 0) {
|
|
371
|
+
onEvent({ type: 'text_delta', delta: obj.content });
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
if (obj.role === 'meta' && obj.type === 'session.resume_hint') {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
function extractCursorText(message) {
|
|
380
|
+
const content = isRecord(message) ? message.content : undefined;
|
|
381
|
+
const blocks = Array.isArray(content) ? content : [];
|
|
382
|
+
return blocks
|
|
383
|
+
.filter((block) => isRecord(block) && block.type === 'text' && typeof block.text === 'string')
|
|
384
|
+
.map((block) => block.text)
|
|
385
|
+
.join('');
|
|
386
|
+
}
|
|
387
|
+
function normalizeTodoStatus(value) {
|
|
388
|
+
const status = typeof value === 'string'
|
|
389
|
+
? value.trim().toLowerCase().replace(/[-\s]+/g, '_')
|
|
390
|
+
: '';
|
|
391
|
+
if (status === 'completed' || status === 'complete' || status === 'done' || status.startsWith('completed')) {
|
|
392
|
+
return 'completed';
|
|
393
|
+
}
|
|
394
|
+
if (status === 'in_progress' || status === 'doing' || status === 'active' || status.startsWith('in_progress')) {
|
|
395
|
+
return 'in_progress';
|
|
396
|
+
}
|
|
397
|
+
if (status === 'stopped' ||
|
|
398
|
+
status === 'failed' ||
|
|
399
|
+
status === 'blocked' ||
|
|
400
|
+
status === 'canceled' ||
|
|
401
|
+
status === 'cancelled' ||
|
|
402
|
+
status.startsWith('stopped') ||
|
|
403
|
+
status.startsWith('failed') ||
|
|
404
|
+
status.startsWith('blocked') ||
|
|
405
|
+
status.startsWith('canceled') ||
|
|
406
|
+
status.startsWith('cancelled')) {
|
|
407
|
+
return 'stopped';
|
|
408
|
+
}
|
|
409
|
+
return 'pending';
|
|
410
|
+
}
|
|
411
|
+
// Takes `unknown[]`, not `unknown`: every real call site
|
|
412
|
+
// (`todoWriteInputFromParsedValue`'s three call sites and
|
|
413
|
+
// `codexTodoListInput`) already runs `Array.isArray(...)` before calling in,
|
|
414
|
+
// so a runtime `!Array.isArray` guard here was dead code for every real
|
|
415
|
+
// caller. Narrowed the parameter type to remove the branch instead of
|
|
416
|
+
// padding a test around unreachable data.
|
|
417
|
+
function todoWriteInputFromItems(items) {
|
|
418
|
+
const todos = items
|
|
419
|
+
.map((raw) => {
|
|
420
|
+
if (!isRecord(raw))
|
|
421
|
+
return null;
|
|
422
|
+
const content = typeof raw.content === 'string'
|
|
423
|
+
? raw.content
|
|
424
|
+
: typeof raw.label === 'string'
|
|
425
|
+
? raw.label
|
|
426
|
+
: typeof raw.description === 'string'
|
|
427
|
+
? raw.description
|
|
428
|
+
: typeof raw.text === 'string'
|
|
429
|
+
? raw.text
|
|
430
|
+
: '';
|
|
431
|
+
if (!content)
|
|
432
|
+
return null;
|
|
433
|
+
return {
|
|
434
|
+
content,
|
|
435
|
+
status: raw.completed === true
|
|
436
|
+
? 'completed'
|
|
437
|
+
: normalizeTodoStatus(raw.status),
|
|
438
|
+
};
|
|
439
|
+
})
|
|
440
|
+
.filter((todo) => todo !== null);
|
|
441
|
+
return todos.length > 0 ? { todos } : null;
|
|
442
|
+
}
|
|
443
|
+
function todoWriteInputFromParsedValue(value) {
|
|
444
|
+
if (Array.isArray(value))
|
|
445
|
+
return todoWriteInputFromItems(value);
|
|
446
|
+
if (!isRecord(value))
|
|
447
|
+
return null;
|
|
448
|
+
if (Array.isArray(value.todos))
|
|
449
|
+
return todoWriteInputFromItems(value.todos);
|
|
450
|
+
if (Array.isArray(value.todo))
|
|
451
|
+
return todoWriteInputFromItems(value.todo);
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
function stripDuplicateArtifactText(text, state) {
|
|
455
|
+
if (!state.suppressNextArtifactText &&
|
|
456
|
+
!state.suppressDuplicateArtifactText &&
|
|
457
|
+
state.artifactOpenCandidate.length === 0) {
|
|
458
|
+
return text;
|
|
459
|
+
}
|
|
460
|
+
const openTag = '<artifact';
|
|
461
|
+
const current = `${state.artifactOpenCandidate}${text}`;
|
|
462
|
+
state.artifactOpenCandidate = '';
|
|
463
|
+
if (state.suppressDuplicateArtifactText) {
|
|
464
|
+
const closeIndex = current.indexOf('</artifact>');
|
|
465
|
+
if (closeIndex === -1)
|
|
466
|
+
return '';
|
|
467
|
+
state.suppressDuplicateArtifactText = false;
|
|
468
|
+
state.suppressNextArtifactText = false;
|
|
469
|
+
return stripDuplicateArtifactText(current.slice(closeIndex + '</artifact>'.length), state);
|
|
470
|
+
}
|
|
471
|
+
const openIndex = current.indexOf(openTag);
|
|
472
|
+
if (openIndex === -1) {
|
|
473
|
+
const candidateLength = artifactOpenCandidateLength(current, openTag);
|
|
474
|
+
if (state.suppressNextArtifactText && candidateLength > 0) {
|
|
475
|
+
state.artifactOpenCandidate = current.slice(-candidateLength);
|
|
476
|
+
return current.slice(0, -candidateLength);
|
|
477
|
+
}
|
|
478
|
+
// Reaching here with `suppressNextArtifactText` false is structurally
|
|
479
|
+
// unreachable. To get past the top guard without it, `artifactOpenCandidate`
|
|
480
|
+
// must have been non-empty at entry — but the only place that sets a
|
|
481
|
+
// non-empty candidate is the branch right above (which requires this same
|
|
482
|
+
// flag to be true when it does), and the only place that clears the
|
|
483
|
+
// candidate other than the unconditional reset at the top of this function
|
|
484
|
+
// (`flushPendingArtifactText`) clears the flag in the same breath. So a
|
|
485
|
+
// leftover non-empty candidate always implies the flag is still true too.
|
|
486
|
+
// No `if` needed — it's always true by the time we get here.
|
|
487
|
+
state.suppressNextArtifactText = false;
|
|
488
|
+
return current;
|
|
489
|
+
}
|
|
490
|
+
state.suppressDuplicateArtifactText = true;
|
|
491
|
+
state.suppressNextArtifactText = false;
|
|
492
|
+
const prefix = `${state.pendingArtifactText}${current.slice(0, openIndex)}`;
|
|
493
|
+
state.pendingArtifactText = '';
|
|
494
|
+
return `${prefix}${stripDuplicateArtifactText(current.slice(openIndex), state)}`;
|
|
495
|
+
}
|
|
496
|
+
function artifactOpenCandidateLength(text, openTag) {
|
|
497
|
+
const max = Math.min(openTag.length - 1, text.length);
|
|
498
|
+
for (let len = max; len > 0; len -= 1) {
|
|
499
|
+
if (openTag.startsWith(text.slice(-len)))
|
|
500
|
+
return len;
|
|
501
|
+
}
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
function flushPendingArtifactText(state, onEvent) {
|
|
505
|
+
const delta = `${state.pendingArtifactText}${state.artifactOpenCandidate}`;
|
|
506
|
+
if (!delta)
|
|
507
|
+
return;
|
|
508
|
+
state.pendingArtifactText = '';
|
|
509
|
+
state.artifactOpenCandidate = '';
|
|
510
|
+
state.suppressNextArtifactText = false;
|
|
511
|
+
onEvent({ type: 'text_delta', delta });
|
|
512
|
+
}
|
|
513
|
+
function isFileWriteToolUse(toolName, input) {
|
|
514
|
+
if (!isRecord(input))
|
|
515
|
+
return false;
|
|
516
|
+
const path = typeof input.file_path === 'string'
|
|
517
|
+
? input.file_path
|
|
518
|
+
: typeof input.path === 'string'
|
|
519
|
+
? input.path
|
|
520
|
+
: '';
|
|
521
|
+
const writesFile = toolName === 'write_file' ||
|
|
522
|
+
toolName === 'write' ||
|
|
523
|
+
toolName === 'replace' ||
|
|
524
|
+
toolName === 'edit';
|
|
525
|
+
if (!writesFile)
|
|
526
|
+
return false;
|
|
527
|
+
if (/\.(html|htm|css|js|jsx|ts|tsx|md)$/iu.test(path))
|
|
528
|
+
return true;
|
|
529
|
+
return typeof input.content === 'string' || typeof input.new_string === 'string';
|
|
530
|
+
}
|
|
531
|
+
function codexTodoListInput(item) {
|
|
532
|
+
if (item.type !== 'todo_list' || !Array.isArray(item.items))
|
|
533
|
+
return null;
|
|
534
|
+
return todoWriteInputFromItems(item.items);
|
|
535
|
+
}
|
|
536
|
+
function emitCodexTodoList(item, onEvent) {
|
|
537
|
+
if (typeof item.id !== 'string')
|
|
538
|
+
return false;
|
|
539
|
+
const input = codexTodoListInput(item);
|
|
540
|
+
if (!input)
|
|
541
|
+
return false;
|
|
542
|
+
onEvent({
|
|
543
|
+
type: 'tool_use',
|
|
544
|
+
id: item.id,
|
|
545
|
+
name: 'TodoWrite',
|
|
546
|
+
input,
|
|
547
|
+
});
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
function emitCursorTextDelta(text, onEvent, state) {
|
|
551
|
+
// Timestamped assistant events WITHOUT `model_call_id` are cursor-agent's
|
|
552
|
+
// real-time incremental deltas (`--stream-partial-output`): the final turn
|
|
553
|
+
// text is the in-order concatenation of every such delta. Emit each one
|
|
554
|
+
// verbatim — do NOT dedupe by content. Legitimately repeated deltas
|
|
555
|
+
// (`"ha"`, `"ha"` -> `"haha"`) or a delta that happens to be a prefix of
|
|
556
|
+
// earlier text are real content, not duplicates; content-based prefix or
|
|
557
|
+
// equality checks would silently drop them. Duplicate suppression and
|
|
558
|
+
// dropped-chunk recovery belong to the buffered terminal replay paths
|
|
559
|
+
// (`model_call_id` and no-timestamp events) via reconcileCursorTurnReplay.
|
|
560
|
+
//
|
|
561
|
+
// No `!text` guard: this function's only real call site
|
|
562
|
+
// (`handleCursorEvent`, below) already returns early on `if (!text) return
|
|
563
|
+
// false;` before ever calling in, so `text` is always non-empty here. The
|
|
564
|
+
// guard was dead code for the one real caller; removed rather than padded
|
|
565
|
+
// with a test.
|
|
566
|
+
state.cursorTextSoFar += text;
|
|
567
|
+
onEvent({ type: 'text_delta', delta: text });
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Reconcile a Cursor terminal replay against the text already emitted for the
|
|
571
|
+
* CURRENT turn. A terminal replay (either a `model_call_id` message or a
|
|
572
|
+
* non-timestamped final assistant message) carries the full text for the
|
|
573
|
+
* current turn only, so it must be compared against
|
|
574
|
+
* `cursorTextSoFar.slice(cursorTurnStart)` — NOT the whole cross-turn buffer,
|
|
575
|
+
* which would miss the current-turn prefix on later turns and re-append the
|
|
576
|
+
* whole replay (duplicate output like "secondsecond turn").
|
|
577
|
+
*
|
|
578
|
+
* Only a verified prefix permits suffix recovery: if the emitted turn text is
|
|
579
|
+
* a prefix of the replay (including the empty case where no chunk arrived),
|
|
580
|
+
* emit the missing suffix. On divergence (a non-final chunk was dropped, so
|
|
581
|
+
* the emitted text is not a prefix) leave the append-only stream untouched
|
|
582
|
+
* rather than duplicate already-shown text. Always advances the turn boundary.
|
|
583
|
+
*/
|
|
584
|
+
function reconcileCursorTurnReplay(text, onEvent, state) {
|
|
585
|
+
const emittedTurn = state.cursorTextSoFar.slice(state.cursorTurnStart);
|
|
586
|
+
if (text && text !== emittedTurn && text.startsWith(emittedTurn)) {
|
|
587
|
+
const suffix = text.slice(emittedTurn.length);
|
|
588
|
+
if (suffix)
|
|
589
|
+
onEvent({ type: 'text_delta', delta: suffix });
|
|
590
|
+
state.cursorTextSoFar += suffix;
|
|
591
|
+
}
|
|
592
|
+
state.cursorTurnStart = state.cursorTextSoFar.length;
|
|
593
|
+
}
|
|
594
|
+
function handleCursorEvent(obj, onEvent, state) {
|
|
595
|
+
if (!isRecord(obj))
|
|
596
|
+
return false;
|
|
597
|
+
if (obj.type === 'system' && obj.subtype === 'init') {
|
|
598
|
+
onEvent({
|
|
599
|
+
type: 'status',
|
|
600
|
+
label: 'initializing',
|
|
601
|
+
model: typeof obj.model === 'string' ? obj.model : undefined,
|
|
602
|
+
});
|
|
603
|
+
return true;
|
|
604
|
+
}
|
|
605
|
+
if (obj.type === 'assistant' && obj.message) {
|
|
606
|
+
// Cursor sends a final assistant message that replays the full text for
|
|
607
|
+
// the current turn — either tagged with `model_call_id`, or (fallback)
|
|
608
|
+
// as a non-timestamped terminal assistant message. Both are reconciled
|
|
609
|
+
// against the current turn's emitted text via reconcileCursorTurnReplay.
|
|
610
|
+
if (typeof obj.model_call_id === 'string') {
|
|
611
|
+
const text = extractCursorText(obj.message);
|
|
612
|
+
reconcileCursorTurnReplay(text, onEvent, state);
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
const text = extractCursorText(obj.message);
|
|
616
|
+
if (!text)
|
|
617
|
+
return false;
|
|
618
|
+
if (typeof obj.timestamp_ms === 'number') {
|
|
619
|
+
// Incremental streaming chunk within a turn — accumulate as usual.
|
|
620
|
+
emitCursorTextDelta(text, onEvent, state);
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
// Non-timestamped final assistant message: a terminal replay that marks a
|
|
624
|
+
// turn boundary. Reconcile against the current turn (not the whole
|
|
625
|
+
// cross-turn buffer) so later fallback-terminated turns do not duplicate
|
|
626
|
+
// output, then advance the turn boundary.
|
|
627
|
+
reconcileCursorTurnReplay(text, onEvent, state);
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
if (obj.type === 'result' && isRecord(obj.usage)) {
|
|
631
|
+
const usage = {};
|
|
632
|
+
if (typeof obj.usage.inputTokens === 'number')
|
|
633
|
+
usage.input_tokens = obj.usage.inputTokens;
|
|
634
|
+
if (typeof obj.usage.outputTokens === 'number')
|
|
635
|
+
usage.output_tokens = obj.usage.outputTokens;
|
|
636
|
+
if (typeof obj.usage.cacheReadTokens === 'number') {
|
|
637
|
+
usage.cached_read_tokens = obj.usage.cacheReadTokens;
|
|
638
|
+
}
|
|
639
|
+
if (typeof obj.usage.cacheWriteTokens === 'number') {
|
|
640
|
+
usage.cached_write_tokens = obj.usage.cacheWriteTokens;
|
|
641
|
+
}
|
|
642
|
+
onEvent({
|
|
643
|
+
type: 'usage',
|
|
644
|
+
usage,
|
|
645
|
+
durationMs: typeof obj.duration_ms === 'number' ? obj.duration_ms : undefined,
|
|
646
|
+
});
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
function handleCodexEvent(obj, onEvent, state) {
|
|
652
|
+
if (!isRecord(obj))
|
|
653
|
+
return false;
|
|
654
|
+
if (obj.type === 'error') {
|
|
655
|
+
const message = extractErrorMessage(obj.message ?? obj.error, 'Codex error');
|
|
656
|
+
// Reconnecting events are recoverable — treat as status warning, not fatal
|
|
657
|
+
if (isRecoverableCodexReconnect(message)) {
|
|
658
|
+
onEvent({ type: 'status', label: message });
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
if (!state.codexErrorEmitted) {
|
|
662
|
+
state.codexErrorEmitted = true;
|
|
663
|
+
onEvent({ type: 'error', message });
|
|
664
|
+
}
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
if (obj.type === 'turn.failed') {
|
|
668
|
+
if (!state.codexErrorEmitted) {
|
|
669
|
+
state.codexErrorEmitted = true;
|
|
670
|
+
onEvent({
|
|
671
|
+
type: 'error',
|
|
672
|
+
message: extractErrorMessage(obj.error ?? obj.message, 'Codex turn failed'),
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
if (obj.type === 'thread.started') {
|
|
678
|
+
// `thread_id` is Codex's own session handle, surfaced on the same
|
|
679
|
+
// `sessionId` status channel claude uses (claude-stream.ts). It serves two
|
|
680
|
+
// consumers: (1) the daemon persists it to `agent_sessions` and replays it
|
|
681
|
+
// as `exec resume <thread_id>` on the next turn (capture-style resume), and
|
|
682
|
+
// (2) it identifies this run's rollout file
|
|
683
|
+
// (`$CODEX_HOME/sessions/**/rollout-*-<thread_id>.jsonl`), the only place
|
|
684
|
+
// codex records per-call usage, which run_finished reads to recover the
|
|
685
|
+
// turn's first-call cache hit (codex's stream usage is cumulative only).
|
|
686
|
+
// Codex emits this both for a fresh `exec` and for `exec resume` (echoing
|
|
687
|
+
// the resumed id), so it is a stable capture point either way.
|
|
688
|
+
const threadId = typeof obj.thread_id === 'string' && obj.thread_id.length > 0
|
|
689
|
+
? obj.thread_id
|
|
690
|
+
: null;
|
|
691
|
+
onEvent({ type: 'status', label: 'initializing', sessionId: threadId });
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
if (obj.type === 'turn.started') {
|
|
695
|
+
state.codexPreviousEventWasAgentMessage = false;
|
|
696
|
+
state.codexLastAgentMessageEndedWithNewline = false;
|
|
697
|
+
onEvent({ type: 'status', label: 'thinking' });
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
if (obj.type === 'item.started' && isRecord(obj.item)) {
|
|
701
|
+
const item = obj.item;
|
|
702
|
+
if (emitCodexTodoList(item, onEvent)) {
|
|
703
|
+
state.codexPreviousEventWasAgentMessage = false;
|
|
704
|
+
state.codexLastAgentMessageEndedWithNewline = false;
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
if (item.type === 'command_execution' && typeof item.id === 'string') {
|
|
708
|
+
state.codexPreviousEventWasAgentMessage = false;
|
|
709
|
+
state.codexLastAgentMessageEndedWithNewline = false;
|
|
710
|
+
if (!state.codexToolUses.has(item.id)) {
|
|
711
|
+
state.codexToolUses.add(item.id);
|
|
712
|
+
onEvent({
|
|
713
|
+
type: 'tool_use',
|
|
714
|
+
id: item.id,
|
|
715
|
+
name: 'Bash',
|
|
716
|
+
input: {
|
|
717
|
+
command: typeof item.command === 'string' ? item.command : '',
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (obj.type === 'item.updated' && isRecord(obj.item)) {
|
|
725
|
+
const item = obj.item;
|
|
726
|
+
if (emitCodexTodoList(item, onEvent)) {
|
|
727
|
+
state.codexPreviousEventWasAgentMessage = false;
|
|
728
|
+
state.codexLastAgentMessageEndedWithNewline = false;
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (obj.type === 'item.completed' && isRecord(obj.item)) {
|
|
733
|
+
const item = obj.item;
|
|
734
|
+
if (emitCodexTodoList(item, onEvent)) {
|
|
735
|
+
state.codexPreviousEventWasAgentMessage = false;
|
|
736
|
+
state.codexLastAgentMessageEndedWithNewline = false;
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
if (item.type === 'command_execution' && typeof item.id === 'string') {
|
|
740
|
+
state.codexPreviousEventWasAgentMessage = false;
|
|
741
|
+
state.codexLastAgentMessageEndedWithNewline = false;
|
|
742
|
+
if (!state.codexToolUses.has(item.id)) {
|
|
743
|
+
state.codexToolUses.add(item.id);
|
|
744
|
+
onEvent({
|
|
745
|
+
type: 'tool_use',
|
|
746
|
+
id: item.id,
|
|
747
|
+
name: 'Bash',
|
|
748
|
+
input: {
|
|
749
|
+
command: typeof item.command === 'string' ? item.command : '',
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
const content = stringifyContent(item.aggregated_output ?? '');
|
|
754
|
+
onEvent({
|
|
755
|
+
type: 'tool_result',
|
|
756
|
+
toolUseId: item.id,
|
|
757
|
+
content,
|
|
758
|
+
isError: typeof item.exit_code === 'number' ? item.exit_code !== 0 : item.status === 'failed',
|
|
759
|
+
});
|
|
760
|
+
const connectorToolError = connectorToolSelectionErrorMessage(content);
|
|
761
|
+
if (connectorToolError && !state.codexErrorEmitted) {
|
|
762
|
+
state.codexErrorEmitted = true;
|
|
763
|
+
onEvent({ type: 'error', message: connectorToolError });
|
|
764
|
+
}
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (obj.type === 'item.completed' &&
|
|
769
|
+
isRecord(obj.item) &&
|
|
770
|
+
obj.item.type === 'agent_message' &&
|
|
771
|
+
typeof obj.item.text === 'string' &&
|
|
772
|
+
obj.item.text.length > 0) {
|
|
773
|
+
const text = obj.item.text;
|
|
774
|
+
const needsBoundary = state.codexPreviousEventWasAgentMessage &&
|
|
775
|
+
!state.codexLastAgentMessageEndedWithNewline &&
|
|
776
|
+
!text.startsWith('\n');
|
|
777
|
+
const delta = needsBoundary ? `\n${text}` : text;
|
|
778
|
+
onEvent({ type: 'text_delta', delta });
|
|
779
|
+
state.codexPreviousEventWasAgentMessage = true;
|
|
780
|
+
state.codexLastAgentMessageEndedWithNewline = text.endsWith('\n');
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
if (obj.type === 'turn.completed' && isRecord(obj.usage)) {
|
|
784
|
+
const usage = {};
|
|
785
|
+
if (typeof obj.usage.input_tokens === 'number')
|
|
786
|
+
usage.input_tokens = obj.usage.input_tokens;
|
|
787
|
+
if (typeof obj.usage.output_tokens === 'number')
|
|
788
|
+
usage.output_tokens = obj.usage.output_tokens;
|
|
789
|
+
if (typeof obj.usage.reasoning_output_tokens === 'number') {
|
|
790
|
+
usage.thought_tokens = obj.usage.reasoning_output_tokens;
|
|
791
|
+
}
|
|
792
|
+
if (typeof obj.usage.cached_input_tokens === 'number') {
|
|
793
|
+
usage.cached_read_tokens = obj.usage.cached_input_tokens;
|
|
794
|
+
}
|
|
795
|
+
onEvent({ type: 'usage', usage });
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
export function createJsonEventStreamHandler(kind, onEvent) {
|
|
801
|
+
let buffer = '';
|
|
802
|
+
const state = {
|
|
803
|
+
cursorTextSoFar: '',
|
|
804
|
+
cursorTurnStart: 0,
|
|
805
|
+
openCodeToolUses: new Set(),
|
|
806
|
+
codexToolUses: new Set(),
|
|
807
|
+
codexErrorEmitted: false,
|
|
808
|
+
codexPreviousEventWasAgentMessage: false,
|
|
809
|
+
codexLastAgentMessageEndedWithNewline: false,
|
|
810
|
+
suppressNextArtifactText: false,
|
|
811
|
+
suppressDuplicateArtifactText: false,
|
|
812
|
+
artifactOpenCandidate: '',
|
|
813
|
+
pendingArtifactText: '',
|
|
814
|
+
};
|
|
815
|
+
function handleLine(line) {
|
|
816
|
+
let obj;
|
|
817
|
+
try {
|
|
818
|
+
obj = JSON.parse(line);
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
onEvent({ type: 'raw', line });
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (kind === 'opencode' && handleOpenCodeEvent(obj, onEvent, state))
|
|
825
|
+
return;
|
|
826
|
+
if (kind === 'gemini' && handleGeminiEvent(obj, onEvent, state))
|
|
827
|
+
return;
|
|
828
|
+
if (kind === 'kimi' && handleKimiEvent(obj, onEvent))
|
|
829
|
+
return;
|
|
830
|
+
if (kind === 'cursor-agent' && handleCursorEvent(obj, onEvent, state))
|
|
831
|
+
return;
|
|
832
|
+
if (kind === 'codex' && handleCodexEvent(obj, onEvent, state))
|
|
833
|
+
return;
|
|
834
|
+
onEvent({ type: 'raw', line });
|
|
835
|
+
}
|
|
836
|
+
function feed(chunk) {
|
|
837
|
+
buffer += chunk;
|
|
838
|
+
let nl;
|
|
839
|
+
while ((nl = buffer.indexOf('\n')) !== -1) {
|
|
840
|
+
const line = buffer.slice(0, nl).trim();
|
|
841
|
+
buffer = buffer.slice(nl + 1);
|
|
842
|
+
if (!line)
|
|
843
|
+
continue;
|
|
844
|
+
handleLine(line);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function flush() {
|
|
848
|
+
const rem = buffer.trim();
|
|
849
|
+
buffer = '';
|
|
850
|
+
if (rem)
|
|
851
|
+
handleLine(rem);
|
|
852
|
+
flushPendingArtifactText(state, onEvent);
|
|
853
|
+
}
|
|
854
|
+
return { feed, flush };
|
|
855
|
+
}
|
|
856
|
+
//# sourceMappingURL=json-event-stream.js.map
|