@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,660 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module claude-stream
|
|
3
|
+
*
|
|
4
|
+
* Ported verbatim from OD's `apps/daemon/src/runtimes/stream/claude-stream.ts`
|
|
5
|
+
* (only the `role-marker-guard` import path changed — it now resolves
|
|
6
|
+
* within this package instead of two directories up). See `source-map.md`.
|
|
7
|
+
*
|
|
8
|
+
* Parses Claude Code's `--output-format stream-json --verbose` JSONL stream
|
|
9
|
+
* (with or without `--include-partial-messages`) into a small set of
|
|
10
|
+
* UI-friendly events. With partial messages on, text arrives as
|
|
11
|
+
* `stream_event` deltas; without it (older builds <1.0.86, or any build
|
|
12
|
+
* where the flag isn't passed) text arrives only in the final `assistant`
|
|
13
|
+
* wrapper. We handle both. The UI only needs to know five things:
|
|
14
|
+
*
|
|
15
|
+
* - status : high-level lifecycle ("initializing", "requesting",
|
|
16
|
+
* "thinking")
|
|
17
|
+
* - text_delta : assistant text chunk (gets fed to the artifact parser)
|
|
18
|
+
* - thinking_delta: extended-thinking chunk (shown in a collapsed block)
|
|
19
|
+
* - tool_use : { id, name, input } (fires when input is complete)
|
|
20
|
+
* - tool_result : { tool_use_id, content, is_error }
|
|
21
|
+
* - usage : aggregated input/output/cache tokens + cost
|
|
22
|
+
*
|
|
23
|
+
* Callers give us `onEvent({ type, ...payload })`. We track per-content-block
|
|
24
|
+
* state to accumulate partial tool_use input JSON and emit a single
|
|
25
|
+
* `tool_use` event when that block stops.
|
|
26
|
+
*/
|
|
27
|
+
import { createRoleMarkerGuard } from './role-marker-guard.js';
|
|
28
|
+
function isRecord(value) {
|
|
29
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
export function createClaudeStreamHandler(onEvent, options = {}) {
|
|
32
|
+
let buffer = '';
|
|
33
|
+
// Per-content-block scratch, keyed by `${messageId}:${blockIndex}`.
|
|
34
|
+
const blocks = new Map();
|
|
35
|
+
// Tool uses already emitted from streamed `input_json_delta` data.
|
|
36
|
+
// Claude Code still repeats them in the final assistant wrapper, often with
|
|
37
|
+
// empty `{}` inputs, so we suppress that duplicate emission.
|
|
38
|
+
const streamedToolUseIds = new Set();
|
|
39
|
+
// Most recent assistant message id so content_block_* events without an id
|
|
40
|
+
// can be attributed correctly.
|
|
41
|
+
let currentMessageId = null;
|
|
42
|
+
// Message ids that already streamed assistant text/thinking via
|
|
43
|
+
// `stream_event` deltas.
|
|
44
|
+
// When `--include-partial-messages` is OFF (older Claude Code, e.g. 1.0.84
|
|
45
|
+
// pre-flag), no deltas arrive — only the final `assistant` wrapper carries
|
|
46
|
+
// content. The fallback below emits that content once, but we must skip it for
|
|
47
|
+
// newer builds that already streamed deltas, otherwise the message would
|
|
48
|
+
// duplicate.
|
|
49
|
+
const textStreamed = new Set();
|
|
50
|
+
const thinkingStreamed = new Set();
|
|
51
|
+
let currentMessageStreamedText = false;
|
|
52
|
+
let currentMessageStreamedThinking = false;
|
|
53
|
+
// Newer Claude Code builds put the terminal stop reason in the partial
|
|
54
|
+
// stream's `message_delta`, while the accompanying top-level `assistant`
|
|
55
|
+
// wrapper carries `stop_reason: null`. Older builds put it on the wrapper.
|
|
56
|
+
// Remember the message id that already emitted `turn_end` so accepting
|
|
57
|
+
// both wire shapes cannot double-trigger stdin continuation/closure.
|
|
58
|
+
let turnEndSignature = null;
|
|
59
|
+
// Per-message role-marker guards for cross-chunk detection (#3247).
|
|
60
|
+
const roleGuards = new Map();
|
|
61
|
+
const runtimeTasks = new Map();
|
|
62
|
+
const canonicalTaskToolUseIds = new Set();
|
|
63
|
+
let nextRuntimeTaskId = 1;
|
|
64
|
+
let suppressNextArtifactText = false;
|
|
65
|
+
let suppressDuplicateArtifactText = false;
|
|
66
|
+
let artifactOpenCandidate = '';
|
|
67
|
+
let pendingArtifactText = '';
|
|
68
|
+
let duplicateArtifactCandidate = '';
|
|
69
|
+
const recentWriteContents = [];
|
|
70
|
+
let wroteHtmlFileThisTurn = false;
|
|
71
|
+
function emitTurnEnd(stopReason) {
|
|
72
|
+
const signature = currentMessageId === null ? null : `${currentMessageId}\u0000${stopReason}`;
|
|
73
|
+
if (signature !== null && turnEndSignature === signature)
|
|
74
|
+
return;
|
|
75
|
+
onEvent({ type: 'turn_end', stopReason });
|
|
76
|
+
turnEndSignature = signature;
|
|
77
|
+
if (stopReason !== 'tool_use') {
|
|
78
|
+
recentWriteContents.length = 0;
|
|
79
|
+
wroteHtmlFileThisTurn = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function normalizeTaskStatus(value) {
|
|
83
|
+
if (value === 'completed' || value === 'in_progress' || value === 'stopped') {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
if (value === 'complete' || value === 'done')
|
|
87
|
+
return 'completed';
|
|
88
|
+
if (value === 'doing' || value === 'active')
|
|
89
|
+
return 'in_progress';
|
|
90
|
+
if (value === 'failed' || value === 'canceled' || value === 'cancelled')
|
|
91
|
+
return 'stopped';
|
|
92
|
+
return 'pending';
|
|
93
|
+
}
|
|
94
|
+
function nextGeneratedRuntimeTaskId() {
|
|
95
|
+
// Coverage-driven refactor (2026-07-18, no behavior change): the
|
|
96
|
+
// original OD source guarded the returned id with
|
|
97
|
+
// `while (runtimeTasks.has(String(nextRuntimeTaskId))) nextRuntimeTaskId += 1;`.
|
|
98
|
+
// Tracing every path that can insert a numeric id into `runtimeTasks`
|
|
99
|
+
// shows the guard can never trigger: the only two insertion paths are
|
|
100
|
+
// (a) this function itself, which always hands out the current
|
|
101
|
+
// `nextRuntimeTaskId` and then increments it, so that exact value is
|
|
102
|
+
// never in the map yet; and (b) `runtimeTaskIdFromCreate`'s
|
|
103
|
+
// explicit-id branch, which bumps `nextRuntimeTaskId` to
|
|
104
|
+
// `numericId + 1` (strictly past itself) BEFORE this function could
|
|
105
|
+
// ever be asked to reuse that id. Since `nextRuntimeTaskId` only ever
|
|
106
|
+
// increases, it can never equal an id already present in the map at
|
|
107
|
+
// this point. Removed per the coverage skill's Phase 6.5 "dead
|
|
108
|
+
// branch" classification rather than fake a test for an unreachable
|
|
109
|
+
// loop; see source-map.md.
|
|
110
|
+
const id = String(nextRuntimeTaskId);
|
|
111
|
+
nextRuntimeTaskId += 1;
|
|
112
|
+
return id;
|
|
113
|
+
}
|
|
114
|
+
function runtimeTaskIdFromCreate(input) {
|
|
115
|
+
if (typeof input.taskId === 'string' && input.taskId) {
|
|
116
|
+
const numericId = Number(input.taskId);
|
|
117
|
+
if (Number.isSafeInteger(numericId) && numericId >= nextRuntimeTaskId) {
|
|
118
|
+
nextRuntimeTaskId = numericId + 1;
|
|
119
|
+
}
|
|
120
|
+
return input.taskId;
|
|
121
|
+
}
|
|
122
|
+
return nextGeneratedRuntimeTaskId();
|
|
123
|
+
}
|
|
124
|
+
function emitCanonicalTaskSnapshot(toolUseId, name, input) {
|
|
125
|
+
if (typeof toolUseId !== 'string' || typeof name !== 'string' || !isRecord(input))
|
|
126
|
+
return false;
|
|
127
|
+
if (canonicalTaskToolUseIds.has(toolUseId))
|
|
128
|
+
return true;
|
|
129
|
+
if (name === 'TaskCreate') {
|
|
130
|
+
const content = typeof input.subject === 'string'
|
|
131
|
+
? input.subject
|
|
132
|
+
: typeof input.description === 'string'
|
|
133
|
+
? input.description
|
|
134
|
+
: '';
|
|
135
|
+
if (!content)
|
|
136
|
+
return false;
|
|
137
|
+
const id = runtimeTaskIdFromCreate(input);
|
|
138
|
+
const activeForm = typeof input.activeForm === 'string' ? input.activeForm : undefined;
|
|
139
|
+
runtimeTasks.set(id, {
|
|
140
|
+
id,
|
|
141
|
+
content,
|
|
142
|
+
status: normalizeTaskStatus(input.status),
|
|
143
|
+
...(activeForm ? { activeForm } : {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else if (name === 'TaskUpdate') {
|
|
147
|
+
if (typeof input.taskId !== 'string')
|
|
148
|
+
return false;
|
|
149
|
+
const existing = runtimeTasks.get(input.taskId);
|
|
150
|
+
if (!existing)
|
|
151
|
+
return false;
|
|
152
|
+
const content = typeof input.subject === 'string'
|
|
153
|
+
? input.subject
|
|
154
|
+
: typeof input.description === 'string'
|
|
155
|
+
? input.description
|
|
156
|
+
: existing.content;
|
|
157
|
+
const activeForm = typeof input.activeForm === 'string' ? input.activeForm : existing.activeForm;
|
|
158
|
+
runtimeTasks.set(input.taskId, {
|
|
159
|
+
...existing,
|
|
160
|
+
content,
|
|
161
|
+
status: normalizeTaskStatus(input.status),
|
|
162
|
+
...(activeForm ? { activeForm } : {}),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
// Coverage-driven refactor (2026-07-18, no behavior change): the
|
|
169
|
+
// original OD source guarded this emission with
|
|
170
|
+
// `if (!changed || runtimeTasks.size === 0) return false;`. Tracing
|
|
171
|
+
// every path into this point shows both conditions are unreachable —
|
|
172
|
+
// the `TaskCreate` and `TaskUpdate` branches above only fall through
|
|
173
|
+
// to here after an unconditional `runtimeTasks.set(...)` (so `size`
|
|
174
|
+
// can never be 0), and every early-return path above returns before
|
|
175
|
+
// reaching here (so the "changed" flag those returns guarded was
|
|
176
|
+
// always true here). Removed the dead double-guard per the coverage
|
|
177
|
+
// skill's Phase 6.5 "dead branch" classification rather than fake a
|
|
178
|
+
// test for an unreachable condition; see source-map.md.
|
|
179
|
+
canonicalTaskToolUseIds.add(toolUseId);
|
|
180
|
+
onEvent({
|
|
181
|
+
type: 'tool_use',
|
|
182
|
+
id: `${toolUseId}:todo-task`,
|
|
183
|
+
name: 'TodoWrite',
|
|
184
|
+
input: {
|
|
185
|
+
todos: Array.from(runtimeTasks.values()).map(({ content, status, activeForm }) => ({
|
|
186
|
+
content,
|
|
187
|
+
status,
|
|
188
|
+
...(activeForm ? { activeForm } : {}),
|
|
189
|
+
})),
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
function emitToolUse(id, name, input) {
|
|
195
|
+
if (emitCanonicalTaskSnapshot(id, name, input))
|
|
196
|
+
return;
|
|
197
|
+
if (isFileWriteToolUse(name, input)) {
|
|
198
|
+
suppressNextArtifactText = true;
|
|
199
|
+
const content = fileWriteContent(input);
|
|
200
|
+
if (content) {
|
|
201
|
+
wroteHtmlFileThisTurn = wroteHtmlFileThisTurn || isHtmlWriteToolInput(input);
|
|
202
|
+
recentWriteContents.push(normalizeArtifactEchoContent(content));
|
|
203
|
+
if (recentWriteContents.length > 5)
|
|
204
|
+
recentWriteContents.shift();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
onEvent({
|
|
208
|
+
type: 'tool_use',
|
|
209
|
+
id,
|
|
210
|
+
name,
|
|
211
|
+
input,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function blockKey(index) {
|
|
215
|
+
return `${currentMessageId ?? 'anon'}:${index}`;
|
|
216
|
+
}
|
|
217
|
+
// Per-message role-marker guard (#3247). Covers text_delta ONLY.
|
|
218
|
+
//
|
|
219
|
+
// Why not thinking_delta: extended thinking is rendered to a
|
|
220
|
+
// separate `kind: 'thinking'` payload and is never folded into
|
|
221
|
+
// `m.content` by `buildDaemonTranscript` (apps/web/src/providers/daemon.ts),
|
|
222
|
+
// so it cannot be re-serialized as a turn boundary on the next
|
|
223
|
+
// round-trip — it is not a #3247 re-injection vector. Models
|
|
224
|
+
// routinely emit literal `## user` / `## assistant` lines in
|
|
225
|
+
// chain-of-thought when reasoning about conversation structure,
|
|
226
|
+
// and with kill-on-detection wired in server.ts a guard on the
|
|
227
|
+
// thinking channel would abort otherwise-legitimate runs without
|
|
228
|
+
// any compensating security benefit. See PR #3303 review
|
|
229
|
+
// r3324xxxxxx. Thinking is passed through unguarded; only the
|
|
230
|
+
// user-visible text channel is policed.
|
|
231
|
+
function emitSafeText(msgId, text, eventType = 'text_delta') {
|
|
232
|
+
if (eventType === 'text_delta') {
|
|
233
|
+
text = stripDuplicateArtifactText(text);
|
|
234
|
+
if (!text)
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (eventType !== 'text_delta' || !msgId) {
|
|
238
|
+
onEvent({ type: eventType, delta: text });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
let guard = roleGuards.get(msgId);
|
|
242
|
+
if (!guard) {
|
|
243
|
+
guard = createRoleMarkerGuard(msgId);
|
|
244
|
+
roleGuards.set(msgId, guard);
|
|
245
|
+
}
|
|
246
|
+
if (guard.contaminated)
|
|
247
|
+
return;
|
|
248
|
+
const safe = guard.feedText(text);
|
|
249
|
+
if (safe.length > 0) {
|
|
250
|
+
onEvent({ type: eventType, delta: safe });
|
|
251
|
+
}
|
|
252
|
+
if (guard.contaminated) {
|
|
253
|
+
const warn = guard.warningEvent();
|
|
254
|
+
if (warn)
|
|
255
|
+
onEvent(warn);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function stripDuplicateArtifactText(text) {
|
|
259
|
+
if (!suppressNextArtifactText &&
|
|
260
|
+
!suppressDuplicateArtifactText &&
|
|
261
|
+
artifactOpenCandidate.length === 0 &&
|
|
262
|
+
recentWriteContents.length === 0) {
|
|
263
|
+
return text;
|
|
264
|
+
}
|
|
265
|
+
const openTag = '<artifact';
|
|
266
|
+
const current = `${artifactOpenCandidate}${text}`;
|
|
267
|
+
artifactOpenCandidate = '';
|
|
268
|
+
if (suppressDuplicateArtifactText) {
|
|
269
|
+
duplicateArtifactCandidate += current;
|
|
270
|
+
const closeIndex = duplicateArtifactCandidate.indexOf('</artifact>');
|
|
271
|
+
if (closeIndex === -1)
|
|
272
|
+
return '';
|
|
273
|
+
const closeEnd = closeIndex + '</artifact>'.length;
|
|
274
|
+
const candidate = duplicateArtifactCandidate.slice(0, closeEnd);
|
|
275
|
+
const rest = duplicateArtifactCandidate.slice(closeEnd);
|
|
276
|
+
duplicateArtifactCandidate = '';
|
|
277
|
+
suppressDuplicateArtifactText = false;
|
|
278
|
+
suppressNextArtifactText = false;
|
|
279
|
+
const duplicate = isRedundantWrittenArtifact(candidate);
|
|
280
|
+
if (options.suppressHtmlArtifactsAfterFileWrite !== true) {
|
|
281
|
+
recentWriteContents.length = 0;
|
|
282
|
+
}
|
|
283
|
+
return `${duplicate ? '' : candidate}${stripDuplicateArtifactText(rest)}`;
|
|
284
|
+
}
|
|
285
|
+
const openIndex = current.indexOf(openTag);
|
|
286
|
+
if (openIndex === -1) {
|
|
287
|
+
const candidateLength = artifactOpenCandidateLength(current, openTag);
|
|
288
|
+
if ((suppressNextArtifactText || recentWriteContents.length > 0) && candidateLength > 0) {
|
|
289
|
+
artifactOpenCandidate = current.slice(-candidateLength);
|
|
290
|
+
return current.slice(0, -candidateLength);
|
|
291
|
+
}
|
|
292
|
+
return current;
|
|
293
|
+
}
|
|
294
|
+
suppressDuplicateArtifactText = true;
|
|
295
|
+
suppressNextArtifactText = false;
|
|
296
|
+
duplicateArtifactCandidate = current.slice(openIndex);
|
|
297
|
+
const prefix = `${pendingArtifactText}${current.slice(0, openIndex)}`;
|
|
298
|
+
pendingArtifactText = '';
|
|
299
|
+
return `${prefix}${stripDuplicateArtifactText('')}`;
|
|
300
|
+
}
|
|
301
|
+
function isRedundantWrittenArtifact(candidate) {
|
|
302
|
+
const gt = candidate.indexOf('>');
|
|
303
|
+
const close = candidate.lastIndexOf('</artifact>');
|
|
304
|
+
if (gt === -1 || close === -1 || close <= gt)
|
|
305
|
+
return false;
|
|
306
|
+
if (options.suppressHtmlArtifactsAfterFileWrite === true &&
|
|
307
|
+
isHtmlArtifact(candidate) &&
|
|
308
|
+
wroteHtmlFileThisTurn)
|
|
309
|
+
return true;
|
|
310
|
+
const body = normalizeArtifactEchoContent(candidate.slice(gt + 1, close));
|
|
311
|
+
return recentWriteContents.some((content) => content === body);
|
|
312
|
+
}
|
|
313
|
+
function isHtmlArtifact(candidate) {
|
|
314
|
+
const openTag = candidate.slice(0, Math.max(0, candidate.indexOf('>') + 1));
|
|
315
|
+
return /\btype\s*=\s*["']text\/html["']/i.test(openTag);
|
|
316
|
+
}
|
|
317
|
+
function normalizeArtifactEchoContent(value) {
|
|
318
|
+
return value
|
|
319
|
+
.replace(/^\uFEFF/, '')
|
|
320
|
+
.replace(/\r\n?/g, '\n')
|
|
321
|
+
.replace(/^(?:\s|\\r|\\n)+|(?:\s|\\r|\\n)+$/g, '');
|
|
322
|
+
}
|
|
323
|
+
function artifactOpenCandidateLength(text, openTag) {
|
|
324
|
+
const max = Math.min(openTag.length - 1, text.length);
|
|
325
|
+
for (let len = max; len > 0; len -= 1) {
|
|
326
|
+
if (openTag.startsWith(text.slice(-len)))
|
|
327
|
+
return len;
|
|
328
|
+
}
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
function isFileWriteToolUse(name, input) {
|
|
332
|
+
if (typeof name !== 'string' || !isRecord(input))
|
|
333
|
+
return false;
|
|
334
|
+
const path = typeof input.file_path === 'string'
|
|
335
|
+
? input.file_path
|
|
336
|
+
: typeof input.path === 'string'
|
|
337
|
+
? input.path
|
|
338
|
+
: '';
|
|
339
|
+
const writesFile = name === 'Write' ||
|
|
340
|
+
name === 'Edit' ||
|
|
341
|
+
name === 'write_file' ||
|
|
342
|
+
name === 'replace';
|
|
343
|
+
if (!writesFile)
|
|
344
|
+
return false;
|
|
345
|
+
if (/\.(html|htm|css|js|jsx|ts|tsx|md)$/iu.test(path))
|
|
346
|
+
return true;
|
|
347
|
+
return typeof input.content === 'string' || typeof input.new_string === 'string';
|
|
348
|
+
}
|
|
349
|
+
// Coverage-driven refactor (2026-07-18, no behavior change): both
|
|
350
|
+
// `fileWriteContent` and `isHtmlWriteToolInput` originally opened with
|
|
351
|
+
// their own `if (!isRecord(input)) return ...;` guard. Each function
|
|
352
|
+
// has exactly one call site (`emitToolUse`, for both), and both call
|
|
353
|
+
// sites already sit inside an `if (isFileWriteToolUse(name, input))`
|
|
354
|
+
// block, which itself already asserted `isRecord(input)` before
|
|
355
|
+
// returning true. So `input` is always a record by the time either
|
|
356
|
+
// function runs — the guards duplicated a check the shared caller
|
|
357
|
+
// already made. Removed the runtime early-return per the coverage
|
|
358
|
+
// skill's Phase 6.5 "dead branch" classification rather than fake a
|
|
359
|
+
// test with a non-record `input` neither function can ever actually
|
|
360
|
+
// receive (see source-map.md), replaced by a one-line type assertion
|
|
361
|
+
// (classification 4: TS-required, no real runtime path) so the
|
|
362
|
+
// parameter can stay `unknown` without disturbing the call sites.
|
|
363
|
+
function fileWriteContent(input) {
|
|
364
|
+
const record = input;
|
|
365
|
+
if (typeof record.content === 'string')
|
|
366
|
+
return record.content;
|
|
367
|
+
if (typeof record.new_string === 'string')
|
|
368
|
+
return record.new_string;
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
function isHtmlWriteToolInput(input) {
|
|
372
|
+
const record = input;
|
|
373
|
+
const rawPath = record.file_path ?? record.filePath;
|
|
374
|
+
if (typeof rawPath === 'string' && /\.(?:html?|xhtml)$/i.test(rawPath))
|
|
375
|
+
return true;
|
|
376
|
+
const content = fileWriteContent(input);
|
|
377
|
+
return typeof content === 'string' && /<!doctype\s+html\b|<html\b/i.test(content);
|
|
378
|
+
}
|
|
379
|
+
function feed(chunk) {
|
|
380
|
+
buffer += chunk;
|
|
381
|
+
let nl;
|
|
382
|
+
while ((nl = buffer.indexOf('\n')) !== -1) {
|
|
383
|
+
const line = buffer.slice(0, nl).trim();
|
|
384
|
+
buffer = buffer.slice(nl + 1);
|
|
385
|
+
if (!line)
|
|
386
|
+
continue;
|
|
387
|
+
let obj;
|
|
388
|
+
try {
|
|
389
|
+
obj = JSON.parse(line);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
onEvent({ type: 'raw', line });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
handleObject(obj);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function flush() {
|
|
399
|
+
const rem = buffer.trim();
|
|
400
|
+
buffer = '';
|
|
401
|
+
if (rem) {
|
|
402
|
+
try {
|
|
403
|
+
handleObject(JSON.parse(rem));
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
onEvent({ type: 'raw', line: rem });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
flushPendingArtifactText();
|
|
410
|
+
}
|
|
411
|
+
function handleObject(obj) {
|
|
412
|
+
if (!isRecord(obj))
|
|
413
|
+
return;
|
|
414
|
+
if (obj.type === 'system' && obj.subtype === 'init') {
|
|
415
|
+
onEvent({
|
|
416
|
+
type: 'status',
|
|
417
|
+
label: 'initializing',
|
|
418
|
+
model: obj.model ?? null,
|
|
419
|
+
sessionId: obj.session_id ?? null,
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (obj.type === 'system' && obj.subtype === 'status') {
|
|
424
|
+
onEvent({ type: 'status', label: obj.status ?? 'working' });
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (obj.type === 'stream_event' && isRecord(obj.event)) {
|
|
428
|
+
handleStreamEvent(obj.event);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
// `assistant` messages are the "block finished" signal for the current
|
|
432
|
+
// content block. For tool_use blocks whose input finished assembling,
|
|
433
|
+
// emit tool_use now with the final parsed input. For text blocks, emit
|
|
434
|
+
// the text as a single delta — but only if no streaming deltas already
|
|
435
|
+
// covered it (older Claude Code without --include-partial-messages
|
|
436
|
+
// delivers text only here; newer builds stream it and would duplicate).
|
|
437
|
+
if (obj.type === 'assistant' && isRecord(obj.message) && Array.isArray(obj.message.content)) {
|
|
438
|
+
const explicitMsgId = typeof obj.message.id === 'string' ? obj.message.id : null;
|
|
439
|
+
const textMsgId = explicitMsgId ?? (currentMessageStreamedText ? currentMessageId : null);
|
|
440
|
+
const thinkingMsgId = explicitMsgId ?? (currentMessageStreamedThinking ? currentMessageId : null);
|
|
441
|
+
if (explicitMsgId)
|
|
442
|
+
currentMessageId = explicitMsgId;
|
|
443
|
+
const textAlreadyStreamed = textMsgId ? textStreamed.has(textMsgId) : false;
|
|
444
|
+
const thinkingAlreadyStreamed = thinkingMsgId ? thinkingStreamed.has(thinkingMsgId) : false;
|
|
445
|
+
// Per-turn `stop_reason` is emitted as `turn_end` AFTER the content
|
|
446
|
+
// blocks have been processed (see below). When `--include-partial-
|
|
447
|
+
// messages` is unsupported, tool_use events surface only from the
|
|
448
|
+
// assistant wrapper here — emitting `turn_end` before that loop would
|
|
449
|
+
// let the daemon's stdin-close handler act on the turn before its
|
|
450
|
+
// tool_use blocks were seen, closing stdin mid-tool. Read the
|
|
451
|
+
// stop_reason now, emit after.
|
|
452
|
+
const stopReason = typeof obj.message.stop_reason === 'string'
|
|
453
|
+
? obj.message.stop_reason
|
|
454
|
+
: null;
|
|
455
|
+
for (const block of obj.message.content) {
|
|
456
|
+
if (!isRecord(block))
|
|
457
|
+
continue;
|
|
458
|
+
if (block.type === 'tool_use') {
|
|
459
|
+
if (typeof block.id === 'string' && streamedToolUseIds.has(block.id)) {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
emitToolUse(block.id, block.name, block.input ?? null);
|
|
463
|
+
}
|
|
464
|
+
else if (!textAlreadyStreamed &&
|
|
465
|
+
block.type === 'text' &&
|
|
466
|
+
typeof block.text === 'string' &&
|
|
467
|
+
block.text.length > 0) {
|
|
468
|
+
emitSafeText(textMsgId, block.text);
|
|
469
|
+
}
|
|
470
|
+
else if (!thinkingAlreadyStreamed &&
|
|
471
|
+
block.type === 'thinking' &&
|
|
472
|
+
typeof block.thinking === 'string' &&
|
|
473
|
+
block.thinking.length > 0) {
|
|
474
|
+
emitSafeText(thinkingMsgId, block.thinking, 'thinking_delta');
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Surface the turn_end signal now that every tool_use in this
|
|
478
|
+
// assistant message has been emitted, so the daemon's stdin-close
|
|
479
|
+
// handler sees the final `stop_reason` before deciding whether to
|
|
480
|
+
// close stream-json input stdin.
|
|
481
|
+
if (stopReason) {
|
|
482
|
+
emitTurnEnd(stopReason);
|
|
483
|
+
}
|
|
484
|
+
if (typeof obj.error === 'string' && obj.error.trim()) {
|
|
485
|
+
onEvent({
|
|
486
|
+
type: 'error',
|
|
487
|
+
message: assistantText(obj.message.content) || obj.error,
|
|
488
|
+
code: obj.error,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
currentMessageStreamedText = false;
|
|
492
|
+
currentMessageStreamedThinking = false;
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
// `user` messages in a stream-json transcript are usually tool_result
|
|
496
|
+
// wrappers from prior turns.
|
|
497
|
+
if (obj.type === 'user' && isRecord(obj.message) && Array.isArray(obj.message.content)) {
|
|
498
|
+
for (const block of obj.message.content) {
|
|
499
|
+
if (!isRecord(block))
|
|
500
|
+
continue;
|
|
501
|
+
if (block.type === 'tool_result') {
|
|
502
|
+
onEvent({
|
|
503
|
+
type: 'tool_result',
|
|
504
|
+
toolUseId: block.tool_use_id,
|
|
505
|
+
content: stringifyToolResult(block.content),
|
|
506
|
+
isError: Boolean(block.is_error),
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (obj.type === 'result') {
|
|
513
|
+
const stopReason = (typeof obj.stop_reason === 'string' && obj.stop_reason) ||
|
|
514
|
+
(typeof obj.terminal_reason === 'string' && obj.terminal_reason) ||
|
|
515
|
+
null;
|
|
516
|
+
// Claude Code 2.1.201 does not always emit a terminal
|
|
517
|
+
// stream_event/message_delta. In that wire shape, the top-level result
|
|
518
|
+
// is the only frame carrying `end_turn`. Surface it as a turn boundary
|
|
519
|
+
// so AgentExecutor can close stream-json stdin and reap the process.
|
|
520
|
+
if (stopReason) {
|
|
521
|
+
emitTurnEnd(stopReason);
|
|
522
|
+
}
|
|
523
|
+
onEvent({
|
|
524
|
+
type: 'usage',
|
|
525
|
+
usage: obj.usage ?? null,
|
|
526
|
+
costUsd: obj.total_cost_usd ?? null,
|
|
527
|
+
durationMs: obj.duration_ms ?? null,
|
|
528
|
+
stopReason,
|
|
529
|
+
});
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function assistantText(content) {
|
|
534
|
+
const parts = [];
|
|
535
|
+
for (const block of content) {
|
|
536
|
+
if (isRecord(block) && block.type === 'text' && typeof block.text === 'string') {
|
|
537
|
+
parts.push(block.text);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return parts.join('\n').trim();
|
|
541
|
+
}
|
|
542
|
+
function handleStreamEvent(ev) {
|
|
543
|
+
if (ev.type === 'message_start') {
|
|
544
|
+
flushPendingArtifactText();
|
|
545
|
+
// Clean up per-message role-marker guard from the previous message.
|
|
546
|
+
if (currentMessageId)
|
|
547
|
+
roleGuards.delete(currentMessageId);
|
|
548
|
+
currentMessageId = isRecord(ev.message) && typeof ev.message.id === 'string' ? ev.message.id : null;
|
|
549
|
+
currentMessageStreamedText = false;
|
|
550
|
+
currentMessageStreamedThinking = false;
|
|
551
|
+
if (typeof ev.ttft_ms === 'number') {
|
|
552
|
+
onEvent({ type: 'status', label: 'streaming', ttftMs: ev.ttft_ms });
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (ev.type === 'content_block_start' && isRecord(ev.content_block)) {
|
|
557
|
+
const key = blockKey(ev.index);
|
|
558
|
+
const block = ev.content_block;
|
|
559
|
+
blocks.set(key, {
|
|
560
|
+
type: block.type,
|
|
561
|
+
name: block.name,
|
|
562
|
+
id: block.id,
|
|
563
|
+
input: '',
|
|
564
|
+
inputValue: 'input' in block ? block.input : undefined,
|
|
565
|
+
});
|
|
566
|
+
if (block.type === 'thinking') {
|
|
567
|
+
onEvent({ type: 'thinking_start' });
|
|
568
|
+
}
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (ev.type === 'content_block_delta' && isRecord(ev.delta)) {
|
|
572
|
+
const state = blocks.get(blockKey(ev.index));
|
|
573
|
+
const delta = ev.delta;
|
|
574
|
+
if (delta.type === 'text_delta' && typeof delta.text === 'string') {
|
|
575
|
+
if (currentMessageId)
|
|
576
|
+
textStreamed.add(currentMessageId);
|
|
577
|
+
currentMessageStreamedText = true;
|
|
578
|
+
emitSafeText(currentMessageId, delta.text);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (delta.type === 'thinking_delta' && typeof delta.thinking === 'string') {
|
|
582
|
+
if (currentMessageId)
|
|
583
|
+
thinkingStreamed.add(currentMessageId);
|
|
584
|
+
currentMessageStreamedThinking = true;
|
|
585
|
+
emitSafeText(currentMessageId, delta.thinking, 'thinking_delta');
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (delta.type === 'input_json_delta' && typeof delta.partial_json === 'string') {
|
|
589
|
+
if (state && state.type === 'tool_use') {
|
|
590
|
+
state.input += delta.partial_json;
|
|
591
|
+
if (typeof state.id === 'string' && typeof state.name === 'string') {
|
|
592
|
+
onEvent({
|
|
593
|
+
type: 'tool_input_delta',
|
|
594
|
+
id: state.id,
|
|
595
|
+
name: state.name,
|
|
596
|
+
delta: delta.partial_json,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (ev.type === 'content_block_stop') {
|
|
604
|
+
const key = blockKey(ev.index);
|
|
605
|
+
const state = blocks.get(key);
|
|
606
|
+
if (state && state.type === 'tool_use' && typeof state.id === 'string' && state.input.trim()) {
|
|
607
|
+
try {
|
|
608
|
+
emitToolUse(state.id, state.name, JSON.parse(state.input));
|
|
609
|
+
streamedToolUseIds.add(state.id);
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
// Fall through to the final assistant wrapper's input if the
|
|
613
|
+
// streamed JSON is malformed or incomplete.
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else if (state &&
|
|
617
|
+
state.type === 'tool_use' &&
|
|
618
|
+
typeof state.id === 'string' &&
|
|
619
|
+
state.inputValue !== undefined) {
|
|
620
|
+
emitToolUse(state.id, state.name, state.inputValue);
|
|
621
|
+
streamedToolUseIds.add(state.id);
|
|
622
|
+
}
|
|
623
|
+
blocks.delete(key);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (ev.type === 'message_delta' && isRecord(ev.delta)) {
|
|
627
|
+
const stopReason = typeof ev.delta.stop_reason === 'string'
|
|
628
|
+
? ev.delta.stop_reason
|
|
629
|
+
: null;
|
|
630
|
+
if (stopReason)
|
|
631
|
+
emitTurnEnd(stopReason);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function flushPendingArtifactText() {
|
|
636
|
+
const text = `${pendingArtifactText}${artifactOpenCandidate}${duplicateArtifactCandidate}`;
|
|
637
|
+
if (!text)
|
|
638
|
+
return;
|
|
639
|
+
pendingArtifactText = '';
|
|
640
|
+
artifactOpenCandidate = '';
|
|
641
|
+
duplicateArtifactCandidate = '';
|
|
642
|
+
suppressNextArtifactText = false;
|
|
643
|
+
suppressDuplicateArtifactText = false;
|
|
644
|
+
recentWriteContents.length = 0;
|
|
645
|
+
wroteHtmlFileThisTurn = false;
|
|
646
|
+
emitSafeText(currentMessageId, text);
|
|
647
|
+
}
|
|
648
|
+
return { feed, flush };
|
|
649
|
+
}
|
|
650
|
+
function stringifyToolResult(content) {
|
|
651
|
+
if (typeof content === 'string')
|
|
652
|
+
return content;
|
|
653
|
+
if (Array.isArray(content)) {
|
|
654
|
+
return content
|
|
655
|
+
.map((c) => (isRecord(c) && c.type === 'text' ? String(c.text) : JSON.stringify(c)))
|
|
656
|
+
.join('\n');
|
|
657
|
+
}
|
|
658
|
+
return JSON.stringify(content);
|
|
659
|
+
}
|
|
660
|
+
//# sourceMappingURL=claude-stream.js.map
|