@nhtio/adk 1.20260924.0 → 1.20260928.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/CHANGELOG.md +87 -0
- package/artifact_methods-Cg0go7Q4.mjs +1 -0
- package/artifact_methods-CxeKTocm.cjs +1 -0
- package/batteries/artifacts/ecmascript.cjs +1 -1
- package/batteries/artifacts/ecmascript.mjs +1 -1
- package/batteries/artifacts/toon.cjs +1 -1
- package/batteries/artifacts/toon.mjs +1 -1
- package/batteries/artifacts/xml.cjs +1 -1
- package/batteries/artifacts/xml.mjs +1 -1
- package/batteries/artifacts/yaml.cjs +1 -1
- package/batteries/artifacts/yaml.mjs +1 -1
- package/batteries/artifacts.cjs +4 -4
- package/batteries/artifacts.mjs +4 -4
- package/batteries/dev-tools/forge.cjs +3 -3
- package/batteries/dev-tools/forge.mjs +3 -3
- package/batteries/dev-tools.cjs +3 -3
- package/batteries/dev-tools.mjs +3 -3
- package/batteries/encoding.cjs +7 -7
- package/batteries/encoding.mjs +7 -7
- package/batteries/generation/gemini/adapter.cjs +1 -1
- package/batteries/generation/gemini/adapter.mjs +1 -1
- package/batteries/generation/local_diffusion/adapter.cjs +1 -1
- package/batteries/generation/local_diffusion/adapter.mjs +1 -1
- package/batteries/generation/openai/adapter.cjs +1 -1
- package/batteries/generation/openai/adapter.mjs +1 -1
- package/batteries/llm/anthropic_messages/adapter.cjs +6 -6
- package/batteries/llm/anthropic_messages/adapter.mjs +6 -6
- package/batteries/llm/anthropic_messages/count_tokens.cjs +1 -1
- package/batteries/llm/anthropic_messages/count_tokens.mjs +1 -1
- package/batteries/llm/anthropic_messages/helpers.cjs +3 -3
- package/batteries/llm/anthropic_messages/helpers.mjs +3 -3
- package/batteries/llm/anthropic_messages/validation.cjs +2 -2
- package/batteries/llm/anthropic_messages/validation.mjs +2 -2
- package/batteries/llm/anthropic_messages.cjs +2 -2
- package/batteries/llm/anthropic_messages.mjs +2 -2
- package/batteries/llm/bedrock_converse/adapter.cjs +162 -21
- package/batteries/llm/bedrock_converse/adapter.cjs.map +1 -1
- package/batteries/llm/bedrock_converse/adapter.mjs +162 -21
- package/batteries/llm/bedrock_converse/adapter.mjs.map +1 -1
- package/batteries/llm/bedrock_converse/helpers.cjs +1 -1
- package/batteries/llm/bedrock_converse/helpers.mjs +1 -1
- package/batteries/llm/bedrock_converse/validation.cjs +2 -2
- package/batteries/llm/bedrock_converse/validation.mjs +2 -2
- package/batteries/llm/bedrock_converse.cjs +2 -2
- package/batteries/llm/bedrock_converse.mjs +2 -2
- package/batteries/llm/claude_code_cli/adapter.cjs +66 -25
- package/batteries/llm/claude_code_cli/adapter.cjs.map +1 -1
- package/batteries/llm/claude_code_cli/adapter.mjs +66 -25
- package/batteries/llm/claude_code_cli/adapter.mjs.map +1 -1
- package/batteries/llm/claude_code_cli/helpers.cjs +3 -3
- package/batteries/llm/claude_code_cli/helpers.mjs +3 -3
- package/batteries/llm/claude_code_cli/line_queue.d.ts +39 -0
- package/batteries/llm/claude_code_cli/message_id_state.d.ts +48 -0
- package/batteries/llm/claude_code_cli/types.d.ts +76 -0
- package/batteries/llm/claude_code_cli/validation.cjs +5 -2
- package/batteries/llm/claude_code_cli/validation.cjs.map +1 -1
- package/batteries/llm/claude_code_cli/validation.mjs +5 -2
- package/batteries/llm/claude_code_cli/validation.mjs.map +1 -1
- package/batteries/llm/claude_code_cli/wire.cjs.map +1 -1
- package/batteries/llm/claude_code_cli/wire.d.ts +45 -1
- package/batteries/llm/claude_code_cli/wire.mjs.map +1 -1
- package/batteries/llm/claude_code_cli/wrapper.d.ts +5 -4
- package/batteries/llm/claude_code_cli.cjs +2 -2
- package/batteries/llm/claude_code_cli.mjs +2 -2
- package/batteries/llm/gemini_generate_content/adapter.cjs +160 -19
- package/batteries/llm/gemini_generate_content/adapter.cjs.map +1 -1
- package/batteries/llm/gemini_generate_content/adapter.mjs +160 -19
- package/batteries/llm/gemini_generate_content/adapter.mjs.map +1 -1
- package/batteries/llm/gemini_generate_content/helpers.cjs +1 -1
- package/batteries/llm/gemini_generate_content/helpers.mjs +1 -1
- package/batteries/llm/gemini_generate_content/validation.cjs +2 -2
- package/batteries/llm/gemini_generate_content/validation.mjs +2 -2
- package/batteries/llm/gemini_generate_content.cjs +1 -1
- package/batteries/llm/gemini_generate_content.mjs +1 -1
- package/batteries/llm/litert_lm/adapter.cjs +6 -6
- package/batteries/llm/litert_lm/adapter.mjs +6 -6
- package/batteries/llm/litert_lm/helpers.cjs +3 -3
- package/batteries/llm/litert_lm/helpers.mjs +3 -3
- package/batteries/llm/litert_lm/validation.cjs +2 -2
- package/batteries/llm/litert_lm/validation.mjs +2 -2
- package/batteries/llm/litert_lm.cjs +1 -1
- package/batteries/llm/litert_lm.mjs +1 -1
- package/batteries/llm/ollama/adapter.cjs +6 -6
- package/batteries/llm/ollama/adapter.mjs +6 -6
- package/batteries/llm/ollama/helpers.cjs +4 -4
- package/batteries/llm/ollama/helpers.mjs +4 -4
- package/batteries/llm/ollama/validation.cjs +2 -2
- package/batteries/llm/ollama/validation.mjs +2 -2
- package/batteries/llm/ollama.cjs +2 -2
- package/batteries/llm/ollama.mjs +2 -2
- package/batteries/llm/openai_chat_completions/adapter.cjs +6 -6
- package/batteries/llm/openai_chat_completions/adapter.mjs +6 -6
- package/batteries/llm/openai_chat_completions/helpers.cjs +4 -4
- package/batteries/llm/openai_chat_completions/helpers.mjs +4 -4
- package/batteries/llm/openai_chat_completions/validation.cjs +2 -2
- package/batteries/llm/openai_chat_completions/validation.mjs +2 -2
- package/batteries/llm/openai_chat_completions.cjs +2 -2
- package/batteries/llm/openai_chat_completions.mjs +2 -2
- package/batteries/llm/openai_responses/adapter.cjs +6 -6
- package/batteries/llm/openai_responses/adapter.mjs +6 -6
- package/batteries/llm/openai_responses/helpers.cjs +3 -3
- package/batteries/llm/openai_responses/helpers.mjs +3 -3
- package/batteries/llm/openai_responses/validation.cjs +2 -2
- package/batteries/llm/openai_responses/validation.mjs +2 -2
- package/batteries/llm/openai_responses.cjs +2 -2
- package/batteries/llm/openai_responses.mjs +2 -2
- package/batteries/llm/transformers_js/adapter.cjs +6 -6
- package/batteries/llm/transformers_js/adapter.mjs +6 -6
- package/batteries/llm/transformers_js/helpers.cjs +3 -3
- package/batteries/llm/transformers_js/helpers.mjs +3 -3
- package/batteries/llm/transformers_js/validation.cjs +2 -2
- package/batteries/llm/transformers_js/validation.mjs +2 -2
- package/batteries/llm/transformers_js.cjs +1 -1
- package/batteries/llm/transformers_js.mjs +1 -1
- package/batteries/llm/webllm_chat_completions/adapter.cjs +6 -6
- package/batteries/llm/webllm_chat_completions/adapter.mjs +6 -6
- package/batteries/llm/webllm_chat_completions/helpers.cjs +1 -1
- package/batteries/llm/webllm_chat_completions/helpers.mjs +1 -1
- package/batteries/llm/webllm_chat_completions/validation.cjs +2 -2
- package/batteries/llm/webllm_chat_completions/validation.mjs +2 -2
- package/batteries/llm/webllm_chat_completions.cjs +2 -2
- package/batteries/llm/webllm_chat_completions.mjs +2 -2
- package/batteries/llm.cjs +2 -2
- package/batteries/llm.mjs +2 -2
- package/batteries/media/forge.cjs +4 -4
- package/batteries/media/forge.mjs +4 -4
- package/batteries/media/lint.cjs +1 -1
- package/batteries/media/lint.mjs +1 -1
- package/batteries/orchestration/dispatch_reasoner.cjs +4 -4
- package/batteries/orchestration/dispatch_reasoner.mjs +4 -4
- package/batteries/orchestration/executor.cjs +2 -2
- package/batteries/orchestration/executor.mjs +2 -2
- package/batteries/orchestration/forge.cjs +4 -4
- package/batteries/orchestration/forge.mjs +4 -4
- package/batteries/orchestration/validation.cjs +2 -2
- package/batteries/orchestration/validation.mjs +2 -2
- package/batteries/orchestration.cjs +4 -4
- package/batteries/orchestration.mjs +4 -4
- package/batteries/sandbox/js.cjs +1 -1
- package/batteries/sandbox/js.mjs +1 -1
- package/batteries/sandbox/tools.cjs +1 -1
- package/batteries/sandbox/tools.mjs +1 -1
- package/batteries/sandbox.cjs +3 -3
- package/batteries/sandbox.mjs +3 -3
- package/batteries/skills.cjs +9 -9
- package/batteries/skills.mjs +9 -9
- package/batteries/tools/_shared.cjs +1 -1
- package/batteries/tools/_shared.mjs +1 -1
- package/batteries/tools/color.cjs +2 -2
- package/batteries/tools/color.mjs +2 -2
- package/batteries/tools/comparison.cjs +2 -2
- package/batteries/tools/comparison.mjs +2 -2
- package/batteries/tools/data_structure.cjs +2 -2
- package/batteries/tools/data_structure.mjs +2 -2
- package/batteries/tools/datetime_extended.cjs +3 -3
- package/batteries/tools/datetime_extended.mjs +3 -3
- package/batteries/tools/datetime_math.cjs +3 -3
- package/batteries/tools/datetime_math.mjs +3 -3
- package/batteries/tools/encoding.cjs +2 -2
- package/batteries/tools/encoding.mjs +2 -2
- package/batteries/tools/formatting.cjs +2 -2
- package/batteries/tools/formatting.mjs +2 -2
- package/batteries/tools/geo_basics.cjs +2 -2
- package/batteries/tools/geo_basics.mjs +2 -2
- package/batteries/tools/math.cjs +2 -2
- package/batteries/tools/math.mjs +2 -2
- package/batteries/tools/memory.cjs +5 -5
- package/batteries/tools/memory.mjs +5 -5
- package/batteries/tools/parsing.cjs +3 -3
- package/batteries/tools/parsing.mjs +3 -3
- package/batteries/tools/retrievables.cjs +5 -5
- package/batteries/tools/retrievables.mjs +5 -5
- package/batteries/tools/scrapper.cjs +1 -1
- package/batteries/tools/scrapper.mjs +1 -1
- package/batteries/tools/searxng.cjs +1 -1
- package/batteries/tools/searxng.mjs +1 -1
- package/batteries/tools/standing_instructions.cjs +3 -3
- package/batteries/tools/standing_instructions.mjs +3 -3
- package/batteries/tools/statistics.cjs +3 -3
- package/batteries/tools/statistics.mjs +3 -3
- package/batteries/tools/string_processing.cjs +2 -2
- package/batteries/tools/string_processing.mjs +2 -2
- package/batteries/tools/structured_data.cjs +2 -2
- package/batteries/tools/structured_data.mjs +2 -2
- package/batteries/tools/text_analysis.cjs +3 -3
- package/batteries/tools/text_analysis.mjs +3 -3
- package/batteries/tools/text_comparison.cjs +2 -2
- package/batteries/tools/text_comparison.mjs +2 -2
- package/batteries/tools/time.cjs +3 -3
- package/batteries/tools/time.mjs +3 -3
- package/batteries/tools/unit_conversion.cjs +2 -2
- package/batteries/tools/unit_conversion.mjs +2 -2
- package/batteries/tools.cjs +2 -2
- package/batteries/tools.mjs +2 -2
- package/batteries/validation.cjs +4 -4
- package/batteries/validation.mjs +4 -4
- package/batteries/vector/conformance.cjs +59 -0
- package/batteries/vector/conformance.cjs.map +1 -1
- package/batteries/vector/conformance.mjs +59 -0
- package/batteries/vector/conformance.mjs.map +1 -1
- package/batteries/vector/lancedb.cjs +1 -1
- package/batteries/vector/lancedb.cjs.map +1 -1
- package/batteries/vector/lancedb.mjs +1 -1
- package/batteries/vector/lancedb.mjs.map +1 -1
- package/batteries/vector/retrievable.cjs +1 -1
- package/batteries/vector/retrievable.mjs +1 -1
- package/batteries/vector/sqlite_vec/index.d.ts +1 -1
- package/batteries/vector/sqlite_vec.cjs +19 -30
- package/batteries/vector/sqlite_vec.cjs.map +1 -1
- package/batteries/vector/sqlite_vec.mjs +20 -31
- package/batteries/vector/sqlite_vec.mjs.map +1 -1
- package/batteries.cjs +4 -4
- package/batteries.mjs +4 -4
- package/{chat_common-B7fvGTKv.cjs → chat_common-CNayzS1V.cjs} +2 -2
- package/{chat_common-B7fvGTKv.cjs.map → chat_common-CNayzS1V.cjs.map} +1 -1
- package/{chat_common-CeoHwSsz.mjs → chat_common-DKw8FDol.mjs} +2 -2
- package/{chat_common-CeoHwSsz.mjs.map → chat_common-DKw8FDol.mjs.map} +1 -1
- package/claude-code-cli-wrapper.cjs +104 -22
- package/claude-code-cli-wrapper.cjs.map +1 -1
- package/claude-code-cli-wrapper.mjs +104 -22
- package/claude-code-cli-wrapper.mjs.map +1 -1
- package/{common-7gCR3zQu.cjs → common-CqetsItm.cjs} +7 -7
- package/{common-7gCR3zQu.cjs.map → common-CqetsItm.cjs.map} +1 -1
- package/{common-B5maOoF_.mjs → common-oI1niK9Z.mjs} +7 -7
- package/{common-B5maOoF_.mjs.map → common-oI1niK9Z.mjs.map} +1 -1
- package/common.cjs +7 -7
- package/common.mjs +7 -7
- package/{dispatch_runner-DO7jo_gl.mjs → dispatch_runner-BZDXUe0G.mjs} +5 -5
- package/{dispatch_runner-DO7jo_gl.mjs.map → dispatch_runner-BZDXUe0G.mjs.map} +1 -1
- package/{dispatch_runner-FlriTyoq.cjs → dispatch_runner-DDUxXJC3.cjs} +5 -5
- package/{dispatch_runner-FlriTyoq.cjs.map → dispatch_runner-DDUxXJC3.cjs.map} +1 -1
- package/dispatch_runner.cjs +1 -1
- package/dispatch_runner.mjs +1 -1
- package/{ecmascript-bgwVyZG1.mjs → ecmascript-B9Z2C24U.mjs} +3 -3
- package/{ecmascript-bgwVyZG1.mjs.map → ecmascript-B9Z2C24U.mjs.map} +1 -1
- package/{ecmascript-BydSB03Y.cjs → ecmascript-BYfmjg7y.cjs} +3 -3
- package/{ecmascript-BydSB03Y.cjs.map → ecmascript-BYfmjg7y.cjs.map} +1 -1
- package/eslint/rules.cjs +1 -1
- package/eslint/rules.mjs +1 -1
- package/eslint.cjs +2 -2
- package/eslint.mjs +2 -2
- package/exceptions.cjs +1 -1
- package/exceptions.mjs +1 -1
- package/forge.cjs +2 -2
- package/forge.mjs +2 -2
- package/guards.cjs +7 -7
- package/guards.mjs +7 -7
- package/{helpers-Dcp4CrOz.cjs → helpers-DPlh_7Bn.cjs} +3 -3
- package/{helpers-Dcp4CrOz.cjs.map → helpers-DPlh_7Bn.cjs.map} +1 -1
- package/{helpers-C1T0DV8Q.mjs → helpers-mbn_EsDV.mjs} +3 -3
- package/{helpers-C1T0DV8Q.mjs.map → helpers-mbn_EsDV.mjs.map} +1 -1
- package/index.cjs +11 -11
- package/index.mjs +11 -11
- package/mcp/adk-docs-corpus.json +2 -2
- package/package.json +425 -425
- package/{retrievable-BBwiACtw.cjs → retrievable-Bq-O3k6r.cjs} +3 -3
- package/{retrievable-BBwiACtw.cjs.map → retrievable-Bq-O3k6r.cjs.map} +1 -1
- package/{retrievable-CrGKx4sc.mjs → retrievable-CXTAUuuv.mjs} +3 -3
- package/{retrievable-CrGKx4sc.mjs.map → retrievable-CXTAUuuv.mjs.map} +1 -1
- package/{scrapper-BibKj8qE.cjs → scrapper-B8xG9lTD.cjs} +4 -4
- package/{scrapper-BibKj8qE.cjs.map → scrapper-B8xG9lTD.cjs.map} +1 -1
- package/{scrapper-_JzgbLG5.mjs → scrapper-DiQ5FfDL.mjs} +4 -4
- package/{scrapper-_JzgbLG5.mjs.map → scrapper-DiQ5FfDL.mjs.map} +1 -1
- package/{searxng-D-nA2gEI.mjs → searxng-BFTJ6rTE.mjs} +4 -4
- package/{searxng-D-nA2gEI.mjs.map → searxng-BFTJ6rTE.mjs.map} +1 -1
- package/{searxng-C6xrLW-v.cjs → searxng-EJhWRnIE.cjs} +4 -4
- package/{searxng-C6xrLW-v.cjs.map → searxng-EJhWRnIE.cjs.map} +1 -1
- package/server.json +2 -2
- package/skills/adk-assembly/SKILL.md +2 -2
- package/{spooled_artifact-BLgbGT8b.cjs → spooled_artifact---DmicFW.cjs} +384 -384
- package/spooled_artifact---DmicFW.cjs.map +1 -0
- package/{spooled_artifact-BkOe4-j-.mjs → spooled_artifact-Bw_7swIp.mjs} +385 -385
- package/spooled_artifact-Bw_7swIp.mjs.map +1 -0
- package/spooled_artifact.cjs +2 -2
- package/spooled_artifact.mjs +2 -2
- package/{spooled_markdown_artifact-8zdejfcJ.cjs → spooled_markdown_artifact-Rjzj-kyh.cjs} +3 -3
- package/{spooled_markdown_artifact-8zdejfcJ.cjs.map → spooled_markdown_artifact-Rjzj-kyh.cjs.map} +1 -1
- package/{spooled_markdown_artifact-Dnl-lCGv.mjs → spooled_markdown_artifact-vd6eNtIP.mjs} +3 -3
- package/{spooled_markdown_artifact-Dnl-lCGv.mjs.map → spooled_markdown_artifact-vd6eNtIP.mjs.map} +1 -1
- package/{thought-BrQt6HAb.mjs → thought-CxSaF6Xf.mjs} +3 -3
- package/{thought-BrQt6HAb.mjs.map → thought-CxSaF6Xf.mjs.map} +1 -1
- package/{thought-COeZTQ-B.cjs → thought-Dp3sMWW6.cjs} +3 -3
- package/{thought-COeZTQ-B.cjs.map → thought-Dp3sMWW6.cjs.map} +1 -1
- package/{tokenizable-DDIdXbJN.cjs → tokenizable-BcodRtoW.cjs} +29 -29
- package/{tokenizable-DDIdXbJN.cjs.map → tokenizable-BcodRtoW.cjs.map} +1 -1
- package/{tokenizable-xPXj1AQX.mjs → tokenizable-VB4Av8GF.mjs} +30 -30
- package/{tokenizable-xPXj1AQX.mjs.map → tokenizable-VB4Av8GF.mjs.map} +1 -1
- package/{tool_call-JbjeUpRK.cjs → tool_call-B9px4W9k.cjs} +4 -4
- package/{tool_call-JbjeUpRK.cjs.map → tool_call-B9px4W9k.cjs.map} +1 -1
- package/{tool_call-8crmZsO8.mjs → tool_call-Cfaj-6zK.mjs} +4 -4
- package/{tool_call-8crmZsO8.mjs.map → tool_call-Cfaj-6zK.mjs.map} +1 -1
- package/{tools-BC4NJqZD.mjs → tools-D0MBDh2R.mjs} +6 -6
- package/{tools-BC4NJqZD.mjs.map → tools-D0MBDh2R.mjs.map} +1 -1
- package/{tools-D4mdzi0a.cjs → tools-aNbi6JyA.cjs} +6 -6
- package/{tools-D4mdzi0a.cjs.map → tools-aNbi6JyA.cjs.map} +1 -1
- package/{toon-BA9Q7NHG.mjs → toon-BSTQnHed.mjs} +5 -5
- package/{toon-BA9Q7NHG.mjs.map → toon-BSTQnHed.mjs.map} +1 -1
- package/{toon-b-0pFKZ8.cjs → toon-DmLwZ6H1.cjs} +5 -5
- package/{toon-b-0pFKZ8.cjs.map → toon-DmLwZ6H1.cjs.map} +1 -1
- package/{turn_runner-BmVXcBZ2.mjs → turn_runner-BHHD6h77.mjs} +5 -5
- package/{turn_runner-BmVXcBZ2.mjs.map → turn_runner-BHHD6h77.mjs.map} +1 -1
- package/{turn_runner-DX-3s3ih.cjs → turn_runner-Dj7JsUTQ.cjs} +5 -5
- package/{turn_runner-DX-3s3ih.cjs.map → turn_runner-Dj7JsUTQ.cjs.map} +1 -1
- package/turn_runner.cjs +1 -1
- package/turn_runner.mjs +1 -1
- package/{xml-B1JIGLpU.mjs → xml-CYseDztH.mjs} +5 -5
- package/{xml-B1JIGLpU.mjs.map → xml-CYseDztH.mjs.map} +1 -1
- package/{xml-fvwTsoRv.cjs → xml-zJul8jMQ.cjs} +5 -5
- package/{xml-fvwTsoRv.cjs.map → xml-zJul8jMQ.cjs.map} +1 -1
- package/{yaml-1mP4uxdf.mjs → yaml-BPBoiOjx.mjs} +5 -5
- package/{yaml-1mP4uxdf.mjs.map → yaml-BPBoiOjx.mjs.map} +1 -1
- package/{yaml-DPUTumla.cjs → yaml-D0PzoRAr.cjs} +5 -5
- package/{yaml-DPUTumla.cjs.map → yaml-D0PzoRAr.cjs.map} +1 -1
- package/artifact_methods-7KatxCSd.cjs +0 -1
- package/artifact_methods-q52ETLwI.mjs +0 -1
- package/spooled_artifact-BLgbGT8b.cjs.map +0 -1
- package/spooled_artifact-BkOe4-j-.mjs.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, synchronous per-spawn message-id state machine. Wrapper-only — never imported by
|
|
3
|
+
* `adapter.ts` (mirrors `cli_protocol.ts`'s own header note: no `@nhtio/adk/*` imports anywhere,
|
|
4
|
+
* so it stays safe for the wrapper's self-contained-process boundary — Decision A/C).
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Extracted out of `wrapper.ts`'s `startTurn` closure (issue #43, fix B follow-up) so the
|
|
8
|
+
* claim-and-reset step is provable WITHOUT any I/O or timing: every exported function here does
|
|
9
|
+
* ONE synchronous state transition and returns before `wrapper.ts` ever reaches an `await`. This
|
|
10
|
+
* matters because `wrapper.ts`'s own NDJSON line handler is `void handleClaudeLine(line)` —
|
|
11
|
+
* unawaited — so several lines from one stdout chunk (or across chunks/`data` events) can have
|
|
12
|
+
* their handler bodies INTERLEAVED at each other's `await` points. A version of this state machine
|
|
13
|
+
* that mutated its fields AFTER an `await writeEvent(...)` (as the original fix-B patch did) let a
|
|
14
|
+
* second, distinct assistant message's handler read the stale, not-yet-reset id and collide with
|
|
15
|
+
* the just-sealed one — the exact race a verification pass caught against f3d7fde. Moving the
|
|
16
|
+
* claim/reset into a single synchronous function call makes that race structurally impossible: by
|
|
17
|
+
* the time ANY caller reaches its own first `await`, this module's state has already been fully
|
|
18
|
+
* transitioned, with no in-between tick for a second line's synchronous prefix to observe.
|
|
19
|
+
*/
|
|
20
|
+
/** Mutable per-spawn state, created once per `startTurn` call. Never shared across spawns. */
|
|
21
|
+
export interface MessageIdState {
|
|
22
|
+
/** The wrapper-local id of the message currently accumulating partial deltas, if any. */
|
|
23
|
+
partialMessageId: string | undefined;
|
|
24
|
+
/** Bumped once per SEALED message, so the next distinct message mints a fresh wrapper-local id. */
|
|
25
|
+
messageIndex: number;
|
|
26
|
+
}
|
|
27
|
+
/** Fresh state for a new `startTurn` spawn. */
|
|
28
|
+
export declare const createMessageIdState: () => MessageIdState;
|
|
29
|
+
/**
|
|
30
|
+
* Claim the wrapper-local id for a `stream_event` partial delta. Synchronously assigns
|
|
31
|
+
* `partialMessageId` if this is the first delta seen for the in-progress message, so every
|
|
32
|
+
* subsequent partial delta (and the eventual complete-message line) reuses the SAME id — call
|
|
33
|
+
* this, and only this, before ever `await`-ing the corresponding `writeEvent`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const claimPartialDeltaId: (state: MessageIdState) => string;
|
|
36
|
+
/**
|
|
37
|
+
* Claim the wrapper-local id for a complete (`assistant`/`user`) message line, and atomically
|
|
38
|
+
* reset the state for whatever distinct message this SAME spawn may still emit next (text ->
|
|
39
|
+
* tool_use -> more text all arrive within one spawn — issue #43, fix B). `hadPriorPartialDelta`
|
|
40
|
+
* tells the caller whether a `stream_event` delta already carried this message's text (in which
|
|
41
|
+
* case the complete line's own text must NOT be re-sent as the delta, or the adapter's per-id
|
|
42
|
+
* accumulator would duplicate it) — matching the wrapper's pre-existing `partialMessageId ===
|
|
43
|
+
* undefined ? text : ''` decision, now made in the same atomic step as the claim itself.
|
|
44
|
+
*/
|
|
45
|
+
export declare const claimCompleteMessageId: (state: MessageIdState) => {
|
|
46
|
+
id: string;
|
|
47
|
+
hadPriorPartialDelta: boolean;
|
|
48
|
+
};
|
|
@@ -263,4 +263,80 @@ export interface ClaudeCodeCliAdapterOptions {
|
|
|
263
263
|
* session-state configuration, and never accepts a value string starting with `-`.
|
|
264
264
|
*/
|
|
265
265
|
extraArgs?: ClaudeCodeCliExtraArg[];
|
|
266
|
+
/**
|
|
267
|
+
* Overrides the executable spawned for the wrapper process. Defaults to `process.execPath` — on
|
|
268
|
+
* an Electron main process (issue #42), `process.execPath` there is the Electron binary itself,
|
|
269
|
+
* not a Node binary, so spawning it boots a copy of Electron rather than plain Node unless
|
|
270
|
+
* `ELECTRON_RUN_AS_NODE` is both set (see `autoDetectElectronHost`) AND honored — which it is not
|
|
271
|
+
* once the host's packaged binary has its `RunAsNode` Fuse disabled; the env var is silently
|
|
272
|
+
* ignored in that case and the spawned copy boots as a full Electron app regardless. As of issue
|
|
273
|
+
* #42 Part A, this no longer affects whether the wrapper exits cleanly (it always does, via an
|
|
274
|
+
* explicit `process.exit(0)`) — it only affects resource cost: a full Electron app boots GPU/
|
|
275
|
+
* network/renderer utility processes the wrapper never needed, for every dispatch. Set this to
|
|
276
|
+
* an actual Node binary path (e.g. one a consumer bundles alongside their Electron app
|
|
277
|
+
* specifically to run this battery) to avoid that cost — most valuable when
|
|
278
|
+
* `process.versions.electron` is set AND the host's fuse is disabled, since
|
|
279
|
+
* `autoDetectElectronHost`'s own `ELECTRON_RUN_AS_NODE` mitigation cannot help at all in that
|
|
280
|
+
* specific case (see its own doc comment for the measured evidence), but worthwhile even with
|
|
281
|
+
* the fuse enabled if avoiding the Electron-boot cost matters more than the small code-path
|
|
282
|
+
* difference. Has no effect on the `claude` grandchild binary itself (`claudeBin`), only on the
|
|
283
|
+
* wrapper's own host process.
|
|
284
|
+
*/
|
|
285
|
+
wrapperExecPath?: string;
|
|
286
|
+
/**
|
|
287
|
+
* Additional environment variables merged into the wrapper's spawn env, applied AFTER
|
|
288
|
+
* `autoDetectElectronHost`'s own `ELECTRON_RUN_AS_NODE` default — so a key set here (including
|
|
289
|
+
* explicit `undefined`, which deletes the key from the child's env entirely) always wins over
|
|
290
|
+
* that default. Everything else about the parent's own `process.env` is inherited as-is (execa's
|
|
291
|
+
* normal behavior); this is only for additions/overrides, not a replacement env map.
|
|
292
|
+
*/
|
|
293
|
+
wrapperEnv?: Record<string, string | undefined>;
|
|
294
|
+
/**
|
|
295
|
+
* Whether to auto-detect an Electron main-process host (`process.versions.electron !== undefined`)
|
|
296
|
+
* and default the wrapper's spawn env to include `ELECTRON_RUN_AS_NODE: '1'` — the documented
|
|
297
|
+
* Electron mechanism for making a spawned copy of the Electron binary behave as plain Node
|
|
298
|
+
* instead of booting a full second Electron app that runs the wrapper script as its main script.
|
|
299
|
+
* Default `true`; every plain-Node host is unaffected either way (`process.versions.electron` is
|
|
300
|
+
* `undefined` there, so this default is inert), and this can be disabled if a consumer has their
|
|
301
|
+
* own mitigation or supplies `wrapperExecPath` instead.
|
|
302
|
+
*
|
|
303
|
+
* @remarks
|
|
304
|
+
* **This option is a resource optimization, not a correctness requirement.** Earlier revisions of
|
|
305
|
+
* this doc comment (pre-issue-#42-Part-A) framed it as the fix for an Electron host hanging
|
|
306
|
+
* indefinitely; that is no longer accurate and this paragraph corrects it. The wrapper's own
|
|
307
|
+
* explicit `process.exit(0)` on the normal-completion path (issue #42, Part A) now guarantees a
|
|
308
|
+
* clean exit unconditionally, regardless of whether this option is enabled, disabled, or
|
|
309
|
+
* defeated by a disabled `RunAsNode` fuse — correctness no longer depends on this option at all.
|
|
310
|
+
* What it still controls is which of two ways a spawned Electron binary reaches that same clean
|
|
311
|
+
* exit:
|
|
312
|
+
*
|
|
313
|
+
* Measured (this fix's own investigation, scratch harnesses against a real copy of
|
|
314
|
+
* `Electron.app`, both with the `RunAsNode` Electron Fuse left at its default (ON) and explicitly
|
|
315
|
+
* disabled via `npx @electron/fuses`, and separately re-measured end-to-end against the BUILT
|
|
316
|
+
* wrapper after Part A landed):
|
|
317
|
+
* - Fuse ON, `ELECTRON_RUN_AS_NODE=1` (this option's default effect): spawned copy runs as plain
|
|
318
|
+
* Node (`process.type === undefined`) — cheap, no GPU/network/renderer utility processes ever
|
|
319
|
+
* boot. Exits 0.
|
|
320
|
+
* - Fuse ON, no env var (this option disabled): spawned copy boots as a full Electron app
|
|
321
|
+
* (`process.type === 'browser'`) — measurably more expensive (GPU process, network service
|
|
322
|
+
* process, etc., all spun up and torn down for a wrapper that never needed any of them). Still
|
|
323
|
+
* exits 0 with Part A in place.
|
|
324
|
+
* - Fuse OFF, either way: `ELECTRON_RUN_AS_NODE` is silently ignored by the Electron runtime
|
|
325
|
+
* itself — this option categorically cannot make the spawned copy behave as plain Node once
|
|
326
|
+
* the fuse is disabled. The spawned copy boots as a full Electron app regardless (the same
|
|
327
|
+
* "expensive" path as the previous bullet). With Part A in place, it still exits 0 — just
|
|
328
|
+
* wastefully, having booted machinery it didn't need. `child_process.fork()` from within a
|
|
329
|
+
* genuine Electron main process throws synchronously in this state (`"...fork() is not
|
|
330
|
+
* supported when the runAsNode fuse is disabled; use utilityProcess.fork() instead"`), and
|
|
331
|
+
* Electron's own suggested replacement, `utilityProcess.fork()`, throws synchronously the
|
|
332
|
+
* instant a non-`'ignore'` stdin is requested (`"stdin value other than ignore is not
|
|
333
|
+
* supported."`) — incompatible with this wrapper's NDJSON-over-stdin protocol regardless of
|
|
334
|
+
* fuse state, so neither is a substitute spawn mechanism.
|
|
335
|
+
*
|
|
336
|
+
* `wrapperExecPath` (pointing at a real bundled Node binary) remains the recommended mitigation
|
|
337
|
+
* for a fuse-disabled Electron host — not because it is needed for the process to exit (Part A
|
|
338
|
+
* already guarantees that), but because it avoids the resource cost of booting a full Electron
|
|
339
|
+
* app per dispatch, the same reason this option exists at all when the fuse is enabled.
|
|
340
|
+
*/
|
|
341
|
+
autoDetectElectronHost?: boolean;
|
|
266
342
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../../chunk-Cek0wNdY.cjs");
|
|
3
|
-
const require_tokenizable = require("../../../tokenizable-
|
|
4
|
-
const require_common = require("../../../common-
|
|
3
|
+
const require_tokenizable = require("../../../tokenizable-BcodRtoW.cjs");
|
|
4
|
+
const require_common = require("../../../common-CqetsItm.cjs");
|
|
5
5
|
require("../../../guards.cjs");
|
|
6
6
|
const require_batteries_llm_claude_code_cli_exceptions = require("./exceptions.cjs");
|
|
7
7
|
let _nhtio_validation = require("@nhtio/validation");
|
|
@@ -86,6 +86,9 @@ var claudeCodeCliOptionsSchema = _nhtio_validation.validator.object({
|
|
|
86
86
|
unsupportedMediaPolicy: unsupportedMediaPolicySchema,
|
|
87
87
|
unsupportedResultMediaPolicy: unsupportedMediaPolicySchema,
|
|
88
88
|
extraArgs: extraArgsSchema,
|
|
89
|
+
wrapperExecPath: _nhtio_validation.validator.string().min(1).optional(),
|
|
90
|
+
wrapperEnv: _nhtio_validation.validator.object().pattern(_nhtio_validation.validator.string(), _nhtio_validation.validator.string().optional()).optional(),
|
|
91
|
+
autoDetectElectronHost: _nhtio_validation.validator.boolean().default(true),
|
|
89
92
|
streamIdleTimeoutMs: _nhtio_validation.validator.number().integer().min(0).default(6e4),
|
|
90
93
|
startupTimeoutMs: _nhtio_validation.validator.number().integer().min(0).default(45e3),
|
|
91
94
|
disposeGraceMs: _nhtio_validation.validator.number().integer().min(0).default(2e3),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.cjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/validation.ts"],"sourcesContent":["/**\n * Runtime validation schema and wrapper for Claude Code CLI adapter options.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/validation\n *\n * @remarks\n * Schema and call-site wrapper for validating `ClaudeCodeCliAdapterOptions`. Used at construction\n * time and at the start of every iteration against the merged options shape (stash > executor >\n * constructor). Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` on failure — same hard-fail policy as\n * every other ADK contract.\n *\n * Three invariants enforced here that have no equivalent in the other LLM batteries:\n * - `apiKey`/`authToken` are mutually exclusive AND at least one is required (`.xor`).\n * - `extraArgs` entries are validated against a strict six-flag allowlist, with per-flag value\n * arity, and every value string is rejected if it starts with `-` — the fix for the `--betas`\n * variadic-value injection hazard (a value string spelling another flag would otherwise reach\n * the CLI's own argv parser as a distinct token).\n * - `process.platform` is checked at validation time: this battery is POSIX-only in v1 (reliable\n * process-group control requires POSIX process groups), mirroring\n * `src/batteries/sandbox/node/srt_enforcer.ts`'s own platform-boundary precedent.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { validator, ValidationError } from '@nhtio/validation'\nimport { E_INVALID_CLAUDE_CODE_CLI_OPTIONS } from './exceptions'\nimport { byteStoreSchema, TokenEncoding } from '@nhtio/adk/common'\nimport type { ClaudeCodeCliAdapterOptions } from './types'\n\n// ─── Sub-schemas ──────────────────────────────────────────────────────────────\n\nconst bucketLabelSchema = validator\n .string()\n .valid('standingInstructions', 'memories', 'retrievables', 'timeline')\n\nconst bucketOrderSchema = validator\n .array()\n .items(bucketLabelSchema)\n .unique()\n .default(['standingInstructions', 'memories', 'retrievables', 'timeline'])\n\nconst tokenEncodingSchema = validator\n .alternatives(\n // Known values are suggestions from the canonical list, not a whitelist: consumers may provide\n // a custom or newer tokenizer name. The field accepts any non-empty string, explicit null, or\n // absent (undefined = \"no token counting\"). `.optional()` preserves the null/undefined\n // disposition required by adk/require-validator-any-required.\n validator\n .string()\n .min(1)\n .description(`Known encodings: ${TokenEncoding.join(', ')}`),\n validator.any().valid(null).optional()\n )\n .default(null)\n\nconst unsupportedMediaPolicySchema = validator\n .alternatives(\n validator.string().valid('throw', 'fallback-stash', 'synthetic-description'),\n validator\n .object({\n mode: validator.string().valid('fallback-stash').required(),\n stashKeys: validator.array().items(validator.string().min(1)).required(),\n })\n .unknown(false)\n )\n .default('throw')\n\nconst helperSchema = validator.function()\n\nconst helpersSchema = validator\n .object({\n descriptionToChatCompletionsJsonSchema: helperSchema.optional(),\n renderUntrustedContent: helperSchema.optional(),\n renderTrustedContent: helperSchema.optional(),\n renderStandingInstructions: helperSchema.optional(),\n renderMemories: helperSchema.optional(),\n renderRetrievables: helperSchema.optional(),\n renderRetrievableHandleBody: helperSchema.optional(),\n renderRetrievableSafetyDirective: helperSchema.optional(),\n renderFirstPartyRetrievables: helperSchema.optional(),\n renderThirdPartyPublicRetrievables: helperSchema.optional(),\n renderThirdPartyPrivateRetrievables: helperSchema.optional(),\n renderThought: helperSchema.optional(),\n filterThoughts: helperSchema.optional(),\n toolsToChatCompletionsTools: helperSchema.optional(),\n renderChatCompletionsSystemPrompt: helperSchema.optional(),\n renderClaudeCodeCliTimelineMessage: helperSchema.optional(),\n renderClaudeCodeCliToolCallResult: helperSchema.optional(),\n buildClaudeCodeCliPrompt: helperSchema.optional(),\n })\n .unknown(false)\n\n// `extraArgs`: a structured allowlist, not a flat string[]. Every value string, in every\n// position, is rejected if it starts with `-` — the fix for the --betas injection hazard.\nconst NOT_A_FLAG_RE = /^(?!-).+$/\n\nconst singleValueSchema = validator.string().min(1).pattern(NOT_A_FLAG_RE)\nconst multiValueSchema = validator\n .array()\n .items(validator.string().min(1).pattern(NOT_A_FLAG_RE))\n .min(1)\n\nconst extraArgSchema = validator\n .alternatives(\n // --betas is the only flag accepting a string[] value.\n validator\n .object({\n flag: validator.string().valid('--betas').required(),\n value: multiValueSchema.required(),\n })\n .unknown(false),\n // --prompt-suggestions is the only flag with an OPTIONAL value.\n validator\n .object({\n flag: validator.string().valid('--prompt-suggestions').required(),\n value: singleValueSchema.optional(),\n })\n .unknown(false),\n // Every remaining allowlisted flag requires a single non-empty, non-flag-shaped string value.\n validator\n .object({\n flag: validator.string().valid('--effort', '--agent', '--json-schema', '--name').required(),\n value: singleValueSchema.required(),\n })\n .unknown(false)\n )\n .required()\n\nconst extraArgsSchema = validator.array().items(extraArgSchema).optional()\n\n// ─── Top-level schema ─────────────────────────────────────────────────────────\n\n/**\n * Validator schema for `ClaudeCodeCliAdapterOptions`. Used by `validateOptions` at construction\n * time and again at the start of every iteration after options have been merged (stash > executor\n * > constructor). Rejects unknown top-level keys so typos fail loud.\n */\nexport const claudeCodeCliOptionsSchema = validator\n .object<ClaudeCodeCliAdapterOptions>({\n // ADK control\n execa: validator.function().optional(),\n wrapperPath: validator.string().optional(),\n claudeBin: validator.string().default('claude'),\n appendSystemPrompt: validator.string().optional(),\n apiKey: validator.string().optional(),\n authToken: validator.string().optional(),\n baseURL: validator.string().optional(),\n cwd: validator.string().optional(),\n addDir: validator.array().items(validator.string().min(1)).optional(),\n disallowedTools: validator.array().items(validator.string().min(1)).default([]),\n maxTurns: validator\n .number()\n .valid(1)\n .messages({\n 'any.only':\n 'maxTurns is deprecated and inert: single-turn dispatch is the fixed battery contract; ' +\n 'only maxTurns: 1 is accepted, and the option is ignored because argv always carries --max-turns 1.',\n })\n .optional(),\n contextWindow: validator.number().integer().min(1).optional(),\n tokenEncoding: tokenEncodingSchema,\n maxBudgetUsd: validator.number().min(0).optional(),\n fallbackModel: validator.array().items(validator.string().min(1)).optional(),\n selfIdentity: validator.string().min(1).default('assistant'),\n toolCallIdFilter: validator.function().optional(),\n autoAck: validator.boolean().default(false),\n forwardSubagentText: validator.boolean().default(false),\n bucketOrder: bucketOrderSchema,\n thoughtSurfacing: validator\n .string()\n .valid('all-self', 'latest-self', 'all')\n .default('all-self'),\n replayCompatibility: validator.array().items(validator.string().min(1)).default([]),\n helpers: helpersSchema.optional(),\n spoolStore: byteStoreSchema.optional(),\n unsupportedMediaPolicy: unsupportedMediaPolicySchema,\n unsupportedResultMediaPolicy: unsupportedMediaPolicySchema,\n extraArgs: extraArgsSchema,\n\n // CLI-native safety caps / timeouts\n streamIdleTimeoutMs: validator.number().integer().min(0).default(60_000),\n startupTimeoutMs: validator.number().integer().min(0).default(45_000),\n disposeGraceMs: validator.number().integer().min(0).default(2_000),\n mcpToolIdleTimeoutMs: validator.number().integer().min(0).optional(),\n disableTelemetry: validator.boolean().optional(),\n disableErrorReporting: validator.boolean().optional(),\n disableNonessentialTraffic: validator.boolean().optional(),\n\n // Required\n model: validator.string().required(),\n })\n .unknown(false)\n // Exactly one of apiKey/authToken must be set.\n .xor('apiKey', 'authToken')\n // POSIX-only in v1: reliable process-group control requires POSIX process groups, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent — refuse rather\n // than silently degrade.\n .custom((value, helpers) => {\n // POSIX-only in v1: reliable process-group control requires process.kill(-pid, signal),\n // which has no Windows equivalent — refuse rather than silently degrade, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent.\n if (process.platform !== 'darwin' && process.platform !== 'linux') {\n return helpers.error('any.invalid')\n }\n return value\n })\n\nconst isValidationError = (value: unknown): value is ValidationError =>\n isError(value) && Array.isArray((value as ValidationError).details)\n\nconst formatValidationDetails = (err: ValidationError): string =>\n err.details.map((d) => d.message).join(' and ')\n\n/**\n * Validates an arbitrary input against `claudeCodeCliOptionsSchema` and returns the resolved\n * options shape. Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` (carrying the validator's error\n * report on `cause`) on failure.\n *\n * @param input - The raw options object to validate.\n * @returns The resolved options object with defaults filled in.\n */\nexport const validateOptions = (input: unknown): ClaudeCodeCliAdapterOptions => {\n const { value, error } = claudeCodeCliOptionsSchema.validate(input, {\n abortEarly: false,\n convert: false,\n })\n if (error) {\n throw new E_INVALID_CLAUDE_CODE_CLI_OPTIONS([formatValidationDetails(error)], { cause: error })\n }\n return value as ClaudeCodeCliAdapterOptions\n}\n\n// suppress unused import warning when the alias isn't referenced\nvoid isValidationError\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validation.cjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/validation.ts"],"sourcesContent":["/**\n * Runtime validation schema and wrapper for Claude Code CLI adapter options.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/validation\n *\n * @remarks\n * Schema and call-site wrapper for validating `ClaudeCodeCliAdapterOptions`. Used at construction\n * time and at the start of every iteration against the merged options shape (stash > executor >\n * constructor). Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` on failure — same hard-fail policy as\n * every other ADK contract.\n *\n * Three invariants enforced here that have no equivalent in the other LLM batteries:\n * - `apiKey`/`authToken` are mutually exclusive AND at least one is required (`.xor`).\n * - `extraArgs` entries are validated against a strict six-flag allowlist, with per-flag value\n * arity, and every value string is rejected if it starts with `-` — the fix for the `--betas`\n * variadic-value injection hazard (a value string spelling another flag would otherwise reach\n * the CLI's own argv parser as a distinct token).\n * - `process.platform` is checked at validation time: this battery is POSIX-only in v1 (reliable\n * process-group control requires POSIX process groups), mirroring\n * `src/batteries/sandbox/node/srt_enforcer.ts`'s own platform-boundary precedent.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { validator, ValidationError } from '@nhtio/validation'\nimport { E_INVALID_CLAUDE_CODE_CLI_OPTIONS } from './exceptions'\nimport { byteStoreSchema, TokenEncoding } from '@nhtio/adk/common'\nimport type { ClaudeCodeCliAdapterOptions } from './types'\n\n// ─── Sub-schemas ──────────────────────────────────────────────────────────────\n\nconst bucketLabelSchema = validator\n .string()\n .valid('standingInstructions', 'memories', 'retrievables', 'timeline')\n\nconst bucketOrderSchema = validator\n .array()\n .items(bucketLabelSchema)\n .unique()\n .default(['standingInstructions', 'memories', 'retrievables', 'timeline'])\n\nconst tokenEncodingSchema = validator\n .alternatives(\n // Known values are suggestions from the canonical list, not a whitelist: consumers may provide\n // a custom or newer tokenizer name. The field accepts any non-empty string, explicit null, or\n // absent (undefined = \"no token counting\"). `.optional()` preserves the null/undefined\n // disposition required by adk/require-validator-any-required.\n validator\n .string()\n .min(1)\n .description(`Known encodings: ${TokenEncoding.join(', ')}`),\n validator.any().valid(null).optional()\n )\n .default(null)\n\nconst unsupportedMediaPolicySchema = validator\n .alternatives(\n validator.string().valid('throw', 'fallback-stash', 'synthetic-description'),\n validator\n .object({\n mode: validator.string().valid('fallback-stash').required(),\n stashKeys: validator.array().items(validator.string().min(1)).required(),\n })\n .unknown(false)\n )\n .default('throw')\n\nconst helperSchema = validator.function()\n\nconst helpersSchema = validator\n .object({\n descriptionToChatCompletionsJsonSchema: helperSchema.optional(),\n renderUntrustedContent: helperSchema.optional(),\n renderTrustedContent: helperSchema.optional(),\n renderStandingInstructions: helperSchema.optional(),\n renderMemories: helperSchema.optional(),\n renderRetrievables: helperSchema.optional(),\n renderRetrievableHandleBody: helperSchema.optional(),\n renderRetrievableSafetyDirective: helperSchema.optional(),\n renderFirstPartyRetrievables: helperSchema.optional(),\n renderThirdPartyPublicRetrievables: helperSchema.optional(),\n renderThirdPartyPrivateRetrievables: helperSchema.optional(),\n renderThought: helperSchema.optional(),\n filterThoughts: helperSchema.optional(),\n toolsToChatCompletionsTools: helperSchema.optional(),\n renderChatCompletionsSystemPrompt: helperSchema.optional(),\n renderClaudeCodeCliTimelineMessage: helperSchema.optional(),\n renderClaudeCodeCliToolCallResult: helperSchema.optional(),\n buildClaudeCodeCliPrompt: helperSchema.optional(),\n })\n .unknown(false)\n\n// `extraArgs`: a structured allowlist, not a flat string[]. Every value string, in every\n// position, is rejected if it starts with `-` — the fix for the --betas injection hazard.\nconst NOT_A_FLAG_RE = /^(?!-).+$/\n\nconst singleValueSchema = validator.string().min(1).pattern(NOT_A_FLAG_RE)\nconst multiValueSchema = validator\n .array()\n .items(validator.string().min(1).pattern(NOT_A_FLAG_RE))\n .min(1)\n\nconst extraArgSchema = validator\n .alternatives(\n // --betas is the only flag accepting a string[] value.\n validator\n .object({\n flag: validator.string().valid('--betas').required(),\n value: multiValueSchema.required(),\n })\n .unknown(false),\n // --prompt-suggestions is the only flag with an OPTIONAL value.\n validator\n .object({\n flag: validator.string().valid('--prompt-suggestions').required(),\n value: singleValueSchema.optional(),\n })\n .unknown(false),\n // Every remaining allowlisted flag requires a single non-empty, non-flag-shaped string value.\n validator\n .object({\n flag: validator.string().valid('--effort', '--agent', '--json-schema', '--name').required(),\n value: singleValueSchema.required(),\n })\n .unknown(false)\n )\n .required()\n\nconst extraArgsSchema = validator.array().items(extraArgSchema).optional()\n\n// ─── Top-level schema ─────────────────────────────────────────────────────────\n\n/**\n * Validator schema for `ClaudeCodeCliAdapterOptions`. Used by `validateOptions` at construction\n * time and again at the start of every iteration after options have been merged (stash > executor\n * > constructor). Rejects unknown top-level keys so typos fail loud.\n */\nexport const claudeCodeCliOptionsSchema = validator\n .object<ClaudeCodeCliAdapterOptions>({\n // ADK control\n execa: validator.function().optional(),\n wrapperPath: validator.string().optional(),\n claudeBin: validator.string().default('claude'),\n appendSystemPrompt: validator.string().optional(),\n apiKey: validator.string().optional(),\n authToken: validator.string().optional(),\n baseURL: validator.string().optional(),\n cwd: validator.string().optional(),\n addDir: validator.array().items(validator.string().min(1)).optional(),\n disallowedTools: validator.array().items(validator.string().min(1)).default([]),\n maxTurns: validator\n .number()\n .valid(1)\n .messages({\n 'any.only':\n 'maxTurns is deprecated and inert: single-turn dispatch is the fixed battery contract; ' +\n 'only maxTurns: 1 is accepted, and the option is ignored because argv always carries --max-turns 1.',\n })\n .optional(),\n contextWindow: validator.number().integer().min(1).optional(),\n tokenEncoding: tokenEncodingSchema,\n maxBudgetUsd: validator.number().min(0).optional(),\n fallbackModel: validator.array().items(validator.string().min(1)).optional(),\n selfIdentity: validator.string().min(1).default('assistant'),\n toolCallIdFilter: validator.function().optional(),\n autoAck: validator.boolean().default(false),\n forwardSubagentText: validator.boolean().default(false),\n bucketOrder: bucketOrderSchema,\n thoughtSurfacing: validator\n .string()\n .valid('all-self', 'latest-self', 'all')\n .default('all-self'),\n replayCompatibility: validator.array().items(validator.string().min(1)).default([]),\n helpers: helpersSchema.optional(),\n spoolStore: byteStoreSchema.optional(),\n unsupportedMediaPolicy: unsupportedMediaPolicySchema,\n unsupportedResultMediaPolicy: unsupportedMediaPolicySchema,\n extraArgs: extraArgsSchema,\n wrapperExecPath: validator.string().min(1).optional(),\n wrapperEnv: validator\n .object()\n .pattern(validator.string(), validator.string().optional())\n .optional(),\n autoDetectElectronHost: validator.boolean().default(true),\n\n // CLI-native safety caps / timeouts\n streamIdleTimeoutMs: validator.number().integer().min(0).default(60_000),\n startupTimeoutMs: validator.number().integer().min(0).default(45_000),\n disposeGraceMs: validator.number().integer().min(0).default(2_000),\n mcpToolIdleTimeoutMs: validator.number().integer().min(0).optional(),\n disableTelemetry: validator.boolean().optional(),\n disableErrorReporting: validator.boolean().optional(),\n disableNonessentialTraffic: validator.boolean().optional(),\n\n // Required\n model: validator.string().required(),\n })\n .unknown(false)\n // Exactly one of apiKey/authToken must be set.\n .xor('apiKey', 'authToken')\n // POSIX-only in v1: reliable process-group control requires POSIX process groups, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent — refuse rather\n // than silently degrade.\n .custom((value, helpers) => {\n // POSIX-only in v1: reliable process-group control requires process.kill(-pid, signal),\n // which has no Windows equivalent — refuse rather than silently degrade, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent.\n if (process.platform !== 'darwin' && process.platform !== 'linux') {\n return helpers.error('any.invalid')\n }\n return value\n })\n\nconst isValidationError = (value: unknown): value is ValidationError =>\n isError(value) && Array.isArray((value as ValidationError).details)\n\nconst formatValidationDetails = (err: ValidationError): string =>\n err.details.map((d) => d.message).join(' and ')\n\n/**\n * Validates an arbitrary input against `claudeCodeCliOptionsSchema` and returns the resolved\n * options shape. Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` (carrying the validator's error\n * report on `cause`) on failure.\n *\n * @param input - The raw options object to validate.\n * @returns The resolved options object with defaults filled in.\n */\nexport const validateOptions = (input: unknown): ClaudeCodeCliAdapterOptions => {\n const { value, error } = claudeCodeCliOptionsSchema.validate(input, {\n abortEarly: false,\n convert: false,\n })\n if (error) {\n throw new E_INVALID_CLAUDE_CODE_CLI_OPTIONS([formatValidationDetails(error)], { cause: error })\n }\n return value as ClaudeCodeCliAdapterOptions\n}\n\n// suppress unused import warning when the alias isn't referenced\nvoid isValidationError\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as TokenEncoding } from "../../../tokenizable-
|
|
2
|
-
import { o as byteStoreSchema } from "../../../common-
|
|
1
|
+
import { n as TokenEncoding } from "../../../tokenizable-VB4Av8GF.mjs";
|
|
2
|
+
import { o as byteStoreSchema } from "../../../common-oI1niK9Z.mjs";
|
|
3
3
|
import "../../../guards.mjs";
|
|
4
4
|
import { E_INVALID_CLAUDE_CODE_CLI_OPTIONS } from "./exceptions.mjs";
|
|
5
5
|
import { validator } from "@nhtio/validation";
|
|
@@ -84,6 +84,9 @@ var claudeCodeCliOptionsSchema = validator.object({
|
|
|
84
84
|
unsupportedMediaPolicy: unsupportedMediaPolicySchema,
|
|
85
85
|
unsupportedResultMediaPolicy: unsupportedMediaPolicySchema,
|
|
86
86
|
extraArgs: extraArgsSchema,
|
|
87
|
+
wrapperExecPath: validator.string().min(1).optional(),
|
|
88
|
+
wrapperEnv: validator.object().pattern(validator.string(), validator.string().optional()).optional(),
|
|
89
|
+
autoDetectElectronHost: validator.boolean().default(true),
|
|
87
90
|
streamIdleTimeoutMs: validator.number().integer().min(0).default(6e4),
|
|
88
91
|
startupTimeoutMs: validator.number().integer().min(0).default(45e3),
|
|
89
92
|
disposeGraceMs: validator.number().integer().min(0).default(2e3),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.mjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/validation.ts"],"sourcesContent":["/**\n * Runtime validation schema and wrapper for Claude Code CLI adapter options.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/validation\n *\n * @remarks\n * Schema and call-site wrapper for validating `ClaudeCodeCliAdapterOptions`. Used at construction\n * time and at the start of every iteration against the merged options shape (stash > executor >\n * constructor). Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` on failure — same hard-fail policy as\n * every other ADK contract.\n *\n * Three invariants enforced here that have no equivalent in the other LLM batteries:\n * - `apiKey`/`authToken` are mutually exclusive AND at least one is required (`.xor`).\n * - `extraArgs` entries are validated against a strict six-flag allowlist, with per-flag value\n * arity, and every value string is rejected if it starts with `-` — the fix for the `--betas`\n * variadic-value injection hazard (a value string spelling another flag would otherwise reach\n * the CLI's own argv parser as a distinct token).\n * - `process.platform` is checked at validation time: this battery is POSIX-only in v1 (reliable\n * process-group control requires POSIX process groups), mirroring\n * `src/batteries/sandbox/node/srt_enforcer.ts`'s own platform-boundary precedent.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { validator, ValidationError } from '@nhtio/validation'\nimport { E_INVALID_CLAUDE_CODE_CLI_OPTIONS } from './exceptions'\nimport { byteStoreSchema, TokenEncoding } from '@nhtio/adk/common'\nimport type { ClaudeCodeCliAdapterOptions } from './types'\n\n// ─── Sub-schemas ──────────────────────────────────────────────────────────────\n\nconst bucketLabelSchema = validator\n .string()\n .valid('standingInstructions', 'memories', 'retrievables', 'timeline')\n\nconst bucketOrderSchema = validator\n .array()\n .items(bucketLabelSchema)\n .unique()\n .default(['standingInstructions', 'memories', 'retrievables', 'timeline'])\n\nconst tokenEncodingSchema = validator\n .alternatives(\n // Known values are suggestions from the canonical list, not a whitelist: consumers may provide\n // a custom or newer tokenizer name. The field accepts any non-empty string, explicit null, or\n // absent (undefined = \"no token counting\"). `.optional()` preserves the null/undefined\n // disposition required by adk/require-validator-any-required.\n validator\n .string()\n .min(1)\n .description(`Known encodings: ${TokenEncoding.join(', ')}`),\n validator.any().valid(null).optional()\n )\n .default(null)\n\nconst unsupportedMediaPolicySchema = validator\n .alternatives(\n validator.string().valid('throw', 'fallback-stash', 'synthetic-description'),\n validator\n .object({\n mode: validator.string().valid('fallback-stash').required(),\n stashKeys: validator.array().items(validator.string().min(1)).required(),\n })\n .unknown(false)\n )\n .default('throw')\n\nconst helperSchema = validator.function()\n\nconst helpersSchema = validator\n .object({\n descriptionToChatCompletionsJsonSchema: helperSchema.optional(),\n renderUntrustedContent: helperSchema.optional(),\n renderTrustedContent: helperSchema.optional(),\n renderStandingInstructions: helperSchema.optional(),\n renderMemories: helperSchema.optional(),\n renderRetrievables: helperSchema.optional(),\n renderRetrievableHandleBody: helperSchema.optional(),\n renderRetrievableSafetyDirective: helperSchema.optional(),\n renderFirstPartyRetrievables: helperSchema.optional(),\n renderThirdPartyPublicRetrievables: helperSchema.optional(),\n renderThirdPartyPrivateRetrievables: helperSchema.optional(),\n renderThought: helperSchema.optional(),\n filterThoughts: helperSchema.optional(),\n toolsToChatCompletionsTools: helperSchema.optional(),\n renderChatCompletionsSystemPrompt: helperSchema.optional(),\n renderClaudeCodeCliTimelineMessage: helperSchema.optional(),\n renderClaudeCodeCliToolCallResult: helperSchema.optional(),\n buildClaudeCodeCliPrompt: helperSchema.optional(),\n })\n .unknown(false)\n\n// `extraArgs`: a structured allowlist, not a flat string[]. Every value string, in every\n// position, is rejected if it starts with `-` — the fix for the --betas injection hazard.\nconst NOT_A_FLAG_RE = /^(?!-).+$/\n\nconst singleValueSchema = validator.string().min(1).pattern(NOT_A_FLAG_RE)\nconst multiValueSchema = validator\n .array()\n .items(validator.string().min(1).pattern(NOT_A_FLAG_RE))\n .min(1)\n\nconst extraArgSchema = validator\n .alternatives(\n // --betas is the only flag accepting a string[] value.\n validator\n .object({\n flag: validator.string().valid('--betas').required(),\n value: multiValueSchema.required(),\n })\n .unknown(false),\n // --prompt-suggestions is the only flag with an OPTIONAL value.\n validator\n .object({\n flag: validator.string().valid('--prompt-suggestions').required(),\n value: singleValueSchema.optional(),\n })\n .unknown(false),\n // Every remaining allowlisted flag requires a single non-empty, non-flag-shaped string value.\n validator\n .object({\n flag: validator.string().valid('--effort', '--agent', '--json-schema', '--name').required(),\n value: singleValueSchema.required(),\n })\n .unknown(false)\n )\n .required()\n\nconst extraArgsSchema = validator.array().items(extraArgSchema).optional()\n\n// ─── Top-level schema ─────────────────────────────────────────────────────────\n\n/**\n * Validator schema for `ClaudeCodeCliAdapterOptions`. Used by `validateOptions` at construction\n * time and again at the start of every iteration after options have been merged (stash > executor\n * > constructor). Rejects unknown top-level keys so typos fail loud.\n */\nexport const claudeCodeCliOptionsSchema = validator\n .object<ClaudeCodeCliAdapterOptions>({\n // ADK control\n execa: validator.function().optional(),\n wrapperPath: validator.string().optional(),\n claudeBin: validator.string().default('claude'),\n appendSystemPrompt: validator.string().optional(),\n apiKey: validator.string().optional(),\n authToken: validator.string().optional(),\n baseURL: validator.string().optional(),\n cwd: validator.string().optional(),\n addDir: validator.array().items(validator.string().min(1)).optional(),\n disallowedTools: validator.array().items(validator.string().min(1)).default([]),\n maxTurns: validator\n .number()\n .valid(1)\n .messages({\n 'any.only':\n 'maxTurns is deprecated and inert: single-turn dispatch is the fixed battery contract; ' +\n 'only maxTurns: 1 is accepted, and the option is ignored because argv always carries --max-turns 1.',\n })\n .optional(),\n contextWindow: validator.number().integer().min(1).optional(),\n tokenEncoding: tokenEncodingSchema,\n maxBudgetUsd: validator.number().min(0).optional(),\n fallbackModel: validator.array().items(validator.string().min(1)).optional(),\n selfIdentity: validator.string().min(1).default('assistant'),\n toolCallIdFilter: validator.function().optional(),\n autoAck: validator.boolean().default(false),\n forwardSubagentText: validator.boolean().default(false),\n bucketOrder: bucketOrderSchema,\n thoughtSurfacing: validator\n .string()\n .valid('all-self', 'latest-self', 'all')\n .default('all-self'),\n replayCompatibility: validator.array().items(validator.string().min(1)).default([]),\n helpers: helpersSchema.optional(),\n spoolStore: byteStoreSchema.optional(),\n unsupportedMediaPolicy: unsupportedMediaPolicySchema,\n unsupportedResultMediaPolicy: unsupportedMediaPolicySchema,\n extraArgs: extraArgsSchema,\n\n // CLI-native safety caps / timeouts\n streamIdleTimeoutMs: validator.number().integer().min(0).default(60_000),\n startupTimeoutMs: validator.number().integer().min(0).default(45_000),\n disposeGraceMs: validator.number().integer().min(0).default(2_000),\n mcpToolIdleTimeoutMs: validator.number().integer().min(0).optional(),\n disableTelemetry: validator.boolean().optional(),\n disableErrorReporting: validator.boolean().optional(),\n disableNonessentialTraffic: validator.boolean().optional(),\n\n // Required\n model: validator.string().required(),\n })\n .unknown(false)\n // Exactly one of apiKey/authToken must be set.\n .xor('apiKey', 'authToken')\n // POSIX-only in v1: reliable process-group control requires POSIX process groups, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent — refuse rather\n // than silently degrade.\n .custom((value, helpers) => {\n // POSIX-only in v1: reliable process-group control requires process.kill(-pid, signal),\n // which has no Windows equivalent — refuse rather than silently degrade, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent.\n if (process.platform !== 'darwin' && process.platform !== 'linux') {\n return helpers.error('any.invalid')\n }\n return value\n })\n\nconst isValidationError = (value: unknown): value is ValidationError =>\n isError(value) && Array.isArray((value as ValidationError).details)\n\nconst formatValidationDetails = (err: ValidationError): string =>\n err.details.map((d) => d.message).join(' and ')\n\n/**\n * Validates an arbitrary input against `claudeCodeCliOptionsSchema` and returns the resolved\n * options shape. Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` (carrying the validator's error\n * report on `cause`) on failure.\n *\n * @param input - The raw options object to validate.\n * @returns The resolved options object with defaults filled in.\n */\nexport const validateOptions = (input: unknown): ClaudeCodeCliAdapterOptions => {\n const { value, error } = claudeCodeCliOptionsSchema.validate(input, {\n abortEarly: false,\n convert: false,\n })\n if (error) {\n throw new E_INVALID_CLAUDE_CODE_CLI_OPTIONS([formatValidationDetails(error)], { cause: error })\n }\n return value as ClaudeCodeCliAdapterOptions\n}\n\n// suppress unused import warning when the alias isn't referenced\nvoid isValidationError\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validation.mjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/validation.ts"],"sourcesContent":["/**\n * Runtime validation schema and wrapper for Claude Code CLI adapter options.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/validation\n *\n * @remarks\n * Schema and call-site wrapper for validating `ClaudeCodeCliAdapterOptions`. Used at construction\n * time and at the start of every iteration against the merged options shape (stash > executor >\n * constructor). Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` on failure — same hard-fail policy as\n * every other ADK contract.\n *\n * Three invariants enforced here that have no equivalent in the other LLM batteries:\n * - `apiKey`/`authToken` are mutually exclusive AND at least one is required (`.xor`).\n * - `extraArgs` entries are validated against a strict six-flag allowlist, with per-flag value\n * arity, and every value string is rejected if it starts with `-` — the fix for the `--betas`\n * variadic-value injection hazard (a value string spelling another flag would otherwise reach\n * the CLI's own argv parser as a distinct token).\n * - `process.platform` is checked at validation time: this battery is POSIX-only in v1 (reliable\n * process-group control requires POSIX process groups), mirroring\n * `src/batteries/sandbox/node/srt_enforcer.ts`'s own platform-boundary precedent.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { validator, ValidationError } from '@nhtio/validation'\nimport { E_INVALID_CLAUDE_CODE_CLI_OPTIONS } from './exceptions'\nimport { byteStoreSchema, TokenEncoding } from '@nhtio/adk/common'\nimport type { ClaudeCodeCliAdapterOptions } from './types'\n\n// ─── Sub-schemas ──────────────────────────────────────────────────────────────\n\nconst bucketLabelSchema = validator\n .string()\n .valid('standingInstructions', 'memories', 'retrievables', 'timeline')\n\nconst bucketOrderSchema = validator\n .array()\n .items(bucketLabelSchema)\n .unique()\n .default(['standingInstructions', 'memories', 'retrievables', 'timeline'])\n\nconst tokenEncodingSchema = validator\n .alternatives(\n // Known values are suggestions from the canonical list, not a whitelist: consumers may provide\n // a custom or newer tokenizer name. The field accepts any non-empty string, explicit null, or\n // absent (undefined = \"no token counting\"). `.optional()` preserves the null/undefined\n // disposition required by adk/require-validator-any-required.\n validator\n .string()\n .min(1)\n .description(`Known encodings: ${TokenEncoding.join(', ')}`),\n validator.any().valid(null).optional()\n )\n .default(null)\n\nconst unsupportedMediaPolicySchema = validator\n .alternatives(\n validator.string().valid('throw', 'fallback-stash', 'synthetic-description'),\n validator\n .object({\n mode: validator.string().valid('fallback-stash').required(),\n stashKeys: validator.array().items(validator.string().min(1)).required(),\n })\n .unknown(false)\n )\n .default('throw')\n\nconst helperSchema = validator.function()\n\nconst helpersSchema = validator\n .object({\n descriptionToChatCompletionsJsonSchema: helperSchema.optional(),\n renderUntrustedContent: helperSchema.optional(),\n renderTrustedContent: helperSchema.optional(),\n renderStandingInstructions: helperSchema.optional(),\n renderMemories: helperSchema.optional(),\n renderRetrievables: helperSchema.optional(),\n renderRetrievableHandleBody: helperSchema.optional(),\n renderRetrievableSafetyDirective: helperSchema.optional(),\n renderFirstPartyRetrievables: helperSchema.optional(),\n renderThirdPartyPublicRetrievables: helperSchema.optional(),\n renderThirdPartyPrivateRetrievables: helperSchema.optional(),\n renderThought: helperSchema.optional(),\n filterThoughts: helperSchema.optional(),\n toolsToChatCompletionsTools: helperSchema.optional(),\n renderChatCompletionsSystemPrompt: helperSchema.optional(),\n renderClaudeCodeCliTimelineMessage: helperSchema.optional(),\n renderClaudeCodeCliToolCallResult: helperSchema.optional(),\n buildClaudeCodeCliPrompt: helperSchema.optional(),\n })\n .unknown(false)\n\n// `extraArgs`: a structured allowlist, not a flat string[]. Every value string, in every\n// position, is rejected if it starts with `-` — the fix for the --betas injection hazard.\nconst NOT_A_FLAG_RE = /^(?!-).+$/\n\nconst singleValueSchema = validator.string().min(1).pattern(NOT_A_FLAG_RE)\nconst multiValueSchema = validator\n .array()\n .items(validator.string().min(1).pattern(NOT_A_FLAG_RE))\n .min(1)\n\nconst extraArgSchema = validator\n .alternatives(\n // --betas is the only flag accepting a string[] value.\n validator\n .object({\n flag: validator.string().valid('--betas').required(),\n value: multiValueSchema.required(),\n })\n .unknown(false),\n // --prompt-suggestions is the only flag with an OPTIONAL value.\n validator\n .object({\n flag: validator.string().valid('--prompt-suggestions').required(),\n value: singleValueSchema.optional(),\n })\n .unknown(false),\n // Every remaining allowlisted flag requires a single non-empty, non-flag-shaped string value.\n validator\n .object({\n flag: validator.string().valid('--effort', '--agent', '--json-schema', '--name').required(),\n value: singleValueSchema.required(),\n })\n .unknown(false)\n )\n .required()\n\nconst extraArgsSchema = validator.array().items(extraArgSchema).optional()\n\n// ─── Top-level schema ─────────────────────────────────────────────────────────\n\n/**\n * Validator schema for `ClaudeCodeCliAdapterOptions`. Used by `validateOptions` at construction\n * time and again at the start of every iteration after options have been merged (stash > executor\n * > constructor). Rejects unknown top-level keys so typos fail loud.\n */\nexport const claudeCodeCliOptionsSchema = validator\n .object<ClaudeCodeCliAdapterOptions>({\n // ADK control\n execa: validator.function().optional(),\n wrapperPath: validator.string().optional(),\n claudeBin: validator.string().default('claude'),\n appendSystemPrompt: validator.string().optional(),\n apiKey: validator.string().optional(),\n authToken: validator.string().optional(),\n baseURL: validator.string().optional(),\n cwd: validator.string().optional(),\n addDir: validator.array().items(validator.string().min(1)).optional(),\n disallowedTools: validator.array().items(validator.string().min(1)).default([]),\n maxTurns: validator\n .number()\n .valid(1)\n .messages({\n 'any.only':\n 'maxTurns is deprecated and inert: single-turn dispatch is the fixed battery contract; ' +\n 'only maxTurns: 1 is accepted, and the option is ignored because argv always carries --max-turns 1.',\n })\n .optional(),\n contextWindow: validator.number().integer().min(1).optional(),\n tokenEncoding: tokenEncodingSchema,\n maxBudgetUsd: validator.number().min(0).optional(),\n fallbackModel: validator.array().items(validator.string().min(1)).optional(),\n selfIdentity: validator.string().min(1).default('assistant'),\n toolCallIdFilter: validator.function().optional(),\n autoAck: validator.boolean().default(false),\n forwardSubagentText: validator.boolean().default(false),\n bucketOrder: bucketOrderSchema,\n thoughtSurfacing: validator\n .string()\n .valid('all-self', 'latest-self', 'all')\n .default('all-self'),\n replayCompatibility: validator.array().items(validator.string().min(1)).default([]),\n helpers: helpersSchema.optional(),\n spoolStore: byteStoreSchema.optional(),\n unsupportedMediaPolicy: unsupportedMediaPolicySchema,\n unsupportedResultMediaPolicy: unsupportedMediaPolicySchema,\n extraArgs: extraArgsSchema,\n wrapperExecPath: validator.string().min(1).optional(),\n wrapperEnv: validator\n .object()\n .pattern(validator.string(), validator.string().optional())\n .optional(),\n autoDetectElectronHost: validator.boolean().default(true),\n\n // CLI-native safety caps / timeouts\n streamIdleTimeoutMs: validator.number().integer().min(0).default(60_000),\n startupTimeoutMs: validator.number().integer().min(0).default(45_000),\n disposeGraceMs: validator.number().integer().min(0).default(2_000),\n mcpToolIdleTimeoutMs: validator.number().integer().min(0).optional(),\n disableTelemetry: validator.boolean().optional(),\n disableErrorReporting: validator.boolean().optional(),\n disableNonessentialTraffic: validator.boolean().optional(),\n\n // Required\n model: validator.string().required(),\n })\n .unknown(false)\n // Exactly one of apiKey/authToken must be set.\n .xor('apiKey', 'authToken')\n // POSIX-only in v1: reliable process-group control requires POSIX process groups, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent — refuse rather\n // than silently degrade.\n .custom((value, helpers) => {\n // POSIX-only in v1: reliable process-group control requires process.kill(-pid, signal),\n // which has no Windows equivalent — refuse rather than silently degrade, mirroring\n // src/batteries/sandbox/node/srt_enforcer.ts's own platform-boundary precedent.\n if (process.platform !== 'darwin' && process.platform !== 'linux') {\n return helpers.error('any.invalid')\n }\n return value\n })\n\nconst isValidationError = (value: unknown): value is ValidationError =>\n isError(value) && Array.isArray((value as ValidationError).details)\n\nconst formatValidationDetails = (err: ValidationError): string =>\n err.details.map((d) => d.message).join(' and ')\n\n/**\n * Validates an arbitrary input against `claudeCodeCliOptionsSchema` and returns the resolved\n * options shape. Throws `E_INVALID_CLAUDE_CODE_CLI_OPTIONS` (carrying the validator's error\n * report on `cause`) on failure.\n *\n * @param input - The raw options object to validate.\n * @returns The resolved options object with defaults filled in.\n */\nexport const validateOptions = (input: unknown): ClaudeCodeCliAdapterOptions => {\n const { value, error } = claudeCodeCliOptionsSchema.validate(input, {\n abortEarly: false,\n convert: false,\n })\n if (error) {\n throw new E_INVALID_CLAUDE_CODE_CLI_OPTIONS([formatValidationDetails(error)], { cause: error })\n }\n return value as ClaudeCodeCliAdapterOptions\n}\n\n// suppress unused import warning when the alias isn't referenced\nvoid isValidationError\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire.cjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/wire.ts"],"sourcesContent":["/**\n * The normalized adapter↔wrapper protocol shared by every CLI-harness LLM battery.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/wire\n *\n * @remarks\n * Zero imports by design: this module is the seam between `adapter.ts` (which runs in the ADK\n * process and imports ADK barrels freely) and `wrapper.ts` (which runs as a separate spawned\n * process and must import nothing from `@nhtio/adk/*`). Depending on either side would break the\n * boundary, so this file depends on neither.\n *\n * The wrapper↔adapter protocol itself is deliberately harness-agnostic — `WrapperRunCommand` /\n * `WrapperEvent` name no Claude-Code-specific concept — so a future Codex-CLI or Pi-agent battery\n * can reuse this exact module, writing only its own wrapper.\n */\n\n/** One entry in a `--json-schema`/`--effort`-style `extraArgs` escape hatch. */\nexport interface ClaudeCodeCliExtraArg {\n /** The exact CLI flag spelling. Restricted to a small, deliberately-chosen allowlist. */\n flag: '--effort' | '--agent' | '--betas' | '--json-schema' | '--name' | '--prompt-suggestions'\n /**\n * The flag's value. Required for every flag except `--prompt-suggestions` (optional, matching\n * the CLI's own `[value]` bracket syntax). A plain `string` for every flag except `--betas`,\n * which accepts `string[]` (matching its own `<betas...>` variadic arity). Every individual\n * value string, in every position, must not start with `-` — this is what makes it structurally\n * impossible for a value to be interpreted by the CLI's own parser as a separate flag.\n */\n value?: string | string[]\n}\n\n/** A bridged ADK tool's JSON-Schema-rendered description, as exposed to the CLI over MCP. */\nexport interface WrapperBridgedTool {\n /** The tool's raw name — matches `ctx.tools.visible()`, NOT the `mcp__<server>__<name>` permission spelling. */\n name: string\n /** Human/model-facing description. */\n description: string\n /** Plain JSON-Schema-shaped input schema (never a Zod schema — see Decision F in the design). */\n inputSchema: Record<string, unknown>\n}\n\n/** Explicit auth credential to forward to the grandchild's environment. Exactly one of the two fields is set. */\nexport interface WrapperAuth {\n /** Forwarded as `ANTHROPIC_API_KEY`. */\n apiKey?: string\n /** Forwarded as `ANTHROPIC_AUTH_TOKEN`. */\n authToken?: string\n /** Forwarded as `ANTHROPIC_BASE_URL`. */\n baseUrl?: string\n}\n\n/**\n * The one command `adapter.ts` sends per dispatch iteration, immediately after the wrapper's\n * `ready` event arrives. Exactly one `run` command is accepted per wrapper process lifetime — the\n * wrapper is spawned fresh per dispatch iteration, so there is no multi-run session.\n */\nexport interface WrapperRunCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'run'\n /** The fully-rendered history, as one `-p` positional prompt string. */\n prompt: string\n /** Forwarded verbatim to `--append-system-prompt`, when set. */\n appendSystemPrompt?: string\n /** The model identifier, forwarded to `--model`. */\n model?: string\n /** Working directory for the grandchild, forwarded to `--cwd`-equivalent spawn option. */\n cwd?: string\n /** Additional directories to allow tool access to, forwarded to `--add-dir`. */\n addDir?: string[]\n /**\n * The exact MCP-bridged tool names the grandchild is allowed to call, ALREADY filtered by the\n * adapter to exclude `disallowedTools`. Always sent, never omitted at this layer — the wrapper\n * itself decides whether to emit `--allowedTools` (omitted entirely when this array is empty,\n * since the flag is variadic and a bare `--allowedTools` with nothing after it would swallow the\n * next argv token).\n */\n allowedTools: string[]\n /** Forwarded to `--max-budget-usd`. */\n maxBudgetUsd?: number\n /** Forwarded to `--fallback-model` as one comma-joined value, never as separate argv tokens. */\n fallbackModel?: string[]\n /** Explicit auth credential(s) for the grandchild's environment. */\n auth?: WrapperAuth\n /** Mapped to `DISABLE_TELEMETRY` on the grandchild's env, never a CLI flag. */\n disableTelemetry?: boolean\n /** Mapped to `DISABLE_ERROR_REPORTING` on the grandchild's env, never a CLI flag. */\n disableErrorReporting?: boolean\n /** Mapped to `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` on the grandchild's env, never a CLI flag. */\n disableNonessentialTraffic?: boolean\n /** Mapped to `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT` on the grandchild's env, never a CLI flag. */\n mcpToolIdleTimeoutMs?: number\n /** Path to the `claude` binary to spawn. */\n claudeBin: string\n /** Forwarded to `--forward-subagent-text` when true. */\n forwardSubagentText?: boolean\n /**\n * Governs how the adapter rendered an ADK tool-result's unsupported `Media` kind (or oversized\n * inline `SpooledArtifact`) into the outbound `WrapperToolCallResponseCommand` — informational\n * only, since the adapter has already applied the policy before this command is sent.\n */\n unsupportedResultMediaPolicy: string\n /** The JSON-Schema-rendered subset of `ctx.tools.visible()` the wrapper exposes over MCP, already pre-filtered to exclude `disallowedTools`. */\n bridgedTools: WrapperBridgedTool[]\n /** Pre-validated additional argv entries, appended after every constructed flag and before the `--` prompt separator. */\n extraArgs?: ClaudeCodeCliExtraArg[]\n}\n\n/** One MCP content block a tool-call response may carry. */\nexport type WrapperToolResultContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n\n/**\n * The adapter's answer to a `tool_call_request` — a finished `CallToolResult`-shaped payload the\n * wrapper hands straight to the CLI's MCP bridge with no further interpretation.\n */\nexport interface WrapperToolCallResponseCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'tool_call_response'\n /** Correlates with the `requestId` on the originating `tool_call_request` event. */\n requestId: string\n /** A finished `CallToolResult`-shaped payload, handed straight to the CLI's MCP bridge. */\n results: {\n /** MCP content blocks to return for the call. */\n content: WrapperToolResultContentBlock[]\n /** Whether the tool call itself failed (as opposed to the wrapper/transport). */\n isError?: boolean\n }\n}\n\n/** Graceful-stop advisory sent to the wrapper (e.g. on `ctx.abortSignal` firing). */\nexport interface WrapperShutdownCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'shutdown'\n}\n\n/** The full adapter→wrapper command union. */\nexport type WrapperCommand =\n | WrapperRunCommand\n | WrapperToolCallResponseCommand\n | WrapperShutdownCommand\n\n// ─── Wrapper → adapter events ──────────────────────────────────────────────\n\n/** The bridge's HTTP listener is bound and the wrapper is about to spawn `claude`. */\nexport interface WrapperReadyEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'ready'\n}\n\n/** Mirrors Claude's own `system/init` stream-json event. */\nexport interface WrapperInitEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'init'\n /** The model Claude reports it initialized with. */\n model?: string\n /** The built-in tool names Claude reports as available (expected empty under `--tools \"\"`). */\n tools?: string[]\n /** Any MCP server connection errors Claude reported during its own startup handshake. */\n mcpServerErrors?: string[]\n /** The original, unmodified `system/init` stream-json line. */\n raw?: unknown\n}\n\n/** A streamed chunk of assistant text or reasoning. */\nexport interface WrapperMessageDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'message_delta'\n /** Identifier correlating deltas belonging to the same in-progress message. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this message id. */\n isComplete?: boolean\n}\n\n/** A streamed chunk of reasoning/thinking text. */\nexport interface WrapperThoughtDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'thought_delta'\n /** Identifier correlating deltas belonging to the same in-progress thought. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this thought id. */\n isComplete?: boolean\n}\n\n/** A real ADK tool the wrapper's MCP bridge is asking the adapter to execute. */\nexport interface WrapperToolCallRequestEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'tool_call_request'\n /** Correlates with the `requestId` the adapter must echo back on its `tool_call_response`. */\n requestId: string\n /** The bridged tool's raw name, matching `ctx.tools.visible()`. */\n tool: string\n /** The call arguments Claude supplied, as received from the MCP `CallTool` request. */\n args: unknown\n}\n\n/** Mirrors Claude's own `system/api_retry` stream-json event. Observability only. */\nexport interface WrapperRetryEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'retry'\n /** The retry attempt number. */\n attempt: number\n /** The maximum number of retries Claude will attempt. */\n maxRetries?: number\n /** The delay, in milliseconds, before the next retry. */\n retryDelayMs?: number\n /** The HTTP status code that triggered the retry. */\n errorStatus?: number\n /** The error message associated with the retry. */\n error?: string\n}\n\n/** The terminal event for a dispatch iteration. */\nexport interface WrapperResultEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'result'\n /** The final assistant-facing result text, when present. */\n resultText?: string\n /** Claude's own session identifier for this turn. */\n sessionId?: string\n /** Total cost, in USD, Claude reports for this turn. */\n totalCostUsd?: number\n /** Token/usage accounting Claude reports for this turn. */\n usage?: Record<string, unknown>\n /** Whether this turn ended in an error (e.g. `--max-turns`/`--max-budget-usd` exhaustion). */\n isError: boolean\n /** Claude's machine-readable reason the turn stopped. */\n subtype?: string\n /** Claude's own stated reason the turn stopped. */\n stopReason?: string\n /** The original, unmodified terminal `result` stream-json line. */\n raw?: unknown\n}\n\n/** A wrapper-level failure (spawn error, unexpected exit, MCP bridge startup failure). */\nexport interface WrapperErrorEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'error'\n /** Human-readable summary of the failure. */\n message: string\n /** Additional detail, when available (e.g. the underlying error's message). */\n detail?: string\n}\n\n/** A generic diagnostic passthrough, never fatal. */\nexport interface WrapperLogEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'log'\n /** Severity of the diagnostic. */\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error'\n /** A short machine-readable category for the diagnostic (e.g. `'malformed-stream-json'`). */\n kind: string\n /** Human-readable message. */\n message: string\n /** Additional structured detail, when available. */\n payload?: unknown\n}\n\n/**\n * The wrapper's bridge HTTP listener and `claude` grandchild have both been torn down and the\n * wrapper is about to exit.\n */\nexport interface WrapperShutdownCompleteEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'shutdown_complete'\n}\n\n/** The full wrapper→adapter event union. */\nexport type WrapperEvent =\n | WrapperReadyEvent\n | WrapperInitEvent\n | WrapperMessageDeltaEvent\n | WrapperThoughtDeltaEvent\n | WrapperToolCallRequestEvent\n | WrapperRetryEvent\n | WrapperResultEvent\n | WrapperErrorEvent\n | WrapperLogEvent\n | WrapperShutdownCompleteEvent\n\n// ─── Encoding ───────────────────────────────────────────────────────────────\n\n/** Encode a `WrapperCommand` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperCommand = (command: WrapperCommand): string =>\n `${JSON.stringify(command)}\\n`\n\n/** Encode a `WrapperEvent` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperEvent = (event: WrapperEvent): string => `${JSON.stringify(event)}\\n`\n\n// ─── Byte-oriented NDJSON line framing ─────────────────────────────────────\n\nconst LF = 0x0a\nconst CR = 0x0d\n\n/**\n * Create an incremental, byte-oriented NDJSON line reader. Generalizes\n * `local_diffusion/protocol.ts`'s `createFrameReader` discipline (bounded memory via a hard\n * `maxLineBytes` cap enforced while consuming, fatal-UTF8-decode, malformed-line-is-non-fatal) for\n * pure JSON-per-line framing with no tag-prefixed grammar. `onLine` receives each decoded line and\n * returns the parsed value, or `undefined` for a line that failed to parse — the reader itself\n * never throws and never classifies content, it only frames bytes into lines.\n *\n * @throws RangeError if `maxLineBytes` is provided but is not a positive, finite, safe integer.\n */\nexport const createNdjsonLineReader = <T>(\n onLine: (raw: string) => T | undefined,\n opts?: { maxLineBytes?: number }\n): { push(chunk: Uint8Array): void; end(): void } => {\n if (\n opts?.maxLineBytes !== undefined &&\n (!Number.isSafeInteger(opts.maxLineBytes) || opts.maxLineBytes < 1)\n ) {\n throw new RangeError(\n `maxLineBytes must be a positive safe integer, received ${String(opts.maxLineBytes)}`\n )\n }\n const cap = opts?.maxLineBytes ?? 1_048_576\n const segments: Uint8Array[] = []\n let pending = 0\n let discarding = false\n let ended = false\n\n const resetLine = (): void => {\n segments.length = 0\n pending = 0\n }\n\n const assemble = (chunk: Uint8Array, start: number, end: number): Uint8Array => {\n const tail = end - start\n if (segments.length === 0) return chunk.subarray(start, end)\n const line = new Uint8Array(pending + tail)\n let at = 0\n for (const seg of segments) {\n line.set(seg, at)\n at += seg.length\n }\n if (tail > 0) line.set(chunk.subarray(start, end), at)\n return line\n }\n\n const decodeLine = (bytes: Uint8Array): void => {\n let end = bytes.length\n if (end > 0 && bytes[end - 1] === CR) end -= 1\n if (end === 0) return\n const slice = bytes.subarray(0, end)\n let text: string\n try {\n text = new TextDecoder('utf-8', { fatal: true }).decode(slice)\n } catch {\n // Invalid UTF-8 — not a fatal condition for the reader; the caller cannot parse it either,\n // so treat it exactly like a line onLine failed to parse (return undefined, no callback).\n return\n }\n onLine(text)\n }\n\n const consume = (chunk: Uint8Array): void => {\n let pos = 0\n if (discarding) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) return\n discarding = false\n pos = nl + 1\n }\n while (pos < chunk.length) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) {\n if (pending + (chunk.length - pos) > cap) {\n resetLine()\n discarding = true\n } else if (chunk.length > pos) {\n const seg = chunk.subarray(pos, chunk.length)\n segments.push(seg)\n pending += seg.length\n }\n return\n }\n if (pending + (nl - pos) > cap) {\n resetLine()\n } else {\n const line = assemble(chunk, pos, nl)\n resetLine()\n decodeLine(line)\n }\n pos = nl + 1\n }\n }\n\n return {\n push(chunk) {\n if (!ended) consume(chunk)\n },\n end() {\n if (ended) return\n ended = true\n resetLine()\n discarding = false\n },\n }\n}\n"],"mappings":";;;;AA8RA,IAAa,wBAAwB,YACnC,GAAG,KAAK,UAAU,OAAO,EAAE;;AAG7B,IAAa,sBAAsB,UAAgC,GAAG,KAAK,UAAU,KAAK,EAAE;AAI5F,IAAM,KAAK;AACX,IAAM,KAAK;;;;;;;;;;;AAYX,IAAa,0BACX,QACA,SACmD;CACnD,IACE,MAAM,iBAAiB,KAAA,MACtB,CAAC,OAAO,cAAc,KAAK,YAAY,KAAK,KAAK,eAAe,IAEjE,MAAM,IAAI,WACR,0DAA0D,OAAO,KAAK,YAAY,GACpF;CAEF,MAAM,MAAM,MAAM,gBAAgB;CAClC,MAAM,WAAyB,CAAC;CAChC,IAAI,UAAU;CACd,IAAI,aAAa;CACjB,IAAI,QAAQ;CAEZ,MAAM,kBAAwB;EAC5B,SAAS,SAAS;EAClB,UAAU;CACZ;CAEA,MAAM,YAAY,OAAmB,OAAe,QAA4B;EAC9E,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,WAAW,GAAG,OAAO,MAAM,SAAS,OAAO,GAAG;EAC3D,MAAM,OAAO,IAAI,WAAW,UAAU,IAAI;EAC1C,IAAI,KAAK;EACT,KAAK,MAAM,OAAO,UAAU;GAC1B,KAAK,IAAI,KAAK,EAAE;GAChB,MAAM,IAAI;EACZ;EACA,IAAI,OAAO,GAAG,KAAK,IAAI,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE;EACrD,OAAO;CACT;CAEA,MAAM,cAAc,UAA4B;EAC9C,IAAI,MAAM,MAAM;EAChB,IAAI,MAAM,KAAK,MAAM,MAAM,OAAO,IAAI,OAAO;EAC7C,IAAI,QAAQ,GAAG;EACf,MAAM,QAAQ,MAAM,SAAS,GAAG,GAAG;EACnC,IAAI;EACJ,IAAI;GACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;EAC/D,QAAQ;GAGN;EACF;EACA,OAAO,IAAI;CACb;CAEA,MAAM,WAAW,UAA4B;EAC3C,IAAI,MAAM;EACV,IAAI,YAAY;GACd,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;GACf,aAAa;GACb,MAAM,KAAK;EACb;EACA,OAAO,MAAM,MAAM,QAAQ;GACzB,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;IACb,IAAI,WAAW,MAAM,SAAS,OAAO,KAAK;KACxC,UAAU;KACV,aAAa;IACf,OAAO,IAAI,MAAM,SAAS,KAAK;KAC7B,MAAM,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM;KAC5C,SAAS,KAAK,GAAG;KACjB,WAAW,IAAI;IACjB;IACA;GACF;GACA,IAAI,WAAW,KAAK,OAAO,KACzB,UAAU;QACL;IACL,MAAM,OAAO,SAAS,OAAO,KAAK,EAAE;IACpC,UAAU;IACV,WAAW,IAAI;GACjB;GACA,MAAM,KAAK;EACb;CACF;CAEA,OAAO;EACL,KAAK,OAAO;GACV,IAAI,CAAC,OAAO,QAAQ,KAAK;EAC3B;EACA,MAAM;GACJ,IAAI,OAAO;GACX,QAAQ;GACR,UAAU;GACV,aAAa;EACf;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"wire.cjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/wire.ts"],"sourcesContent":["/**\n * The normalized adapter↔wrapper protocol shared by every CLI-harness LLM battery.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/wire\n *\n * @remarks\n * Zero imports by design: this module is the seam between `adapter.ts` (which runs in the ADK\n * process and imports ADK barrels freely) and `wrapper.ts` (which runs as a separate spawned\n * process and must import nothing from `@nhtio/adk/*`). Depending on either side would break the\n * boundary, so this file depends on neither.\n *\n * The wrapper↔adapter protocol itself is deliberately harness-agnostic — `WrapperRunCommand` /\n * `WrapperEvent` name no Claude-Code-specific concept — so a future Codex-CLI or Pi-agent battery\n * can reuse this exact module, writing only its own wrapper.\n */\n\n/** One entry in a `--json-schema`/`--effort`-style `extraArgs` escape hatch. */\nexport interface ClaudeCodeCliExtraArg {\n /** The exact CLI flag spelling. Restricted to a small, deliberately-chosen allowlist. */\n flag: '--effort' | '--agent' | '--betas' | '--json-schema' | '--name' | '--prompt-suggestions'\n /**\n * The flag's value. Required for every flag except `--prompt-suggestions` (optional, matching\n * the CLI's own `[value]` bracket syntax). A plain `string` for every flag except `--betas`,\n * which accepts `string[]` (matching its own `<betas...>` variadic arity). Every individual\n * value string, in every position, must not start with `-` — this is what makes it structurally\n * impossible for a value to be interpreted by the CLI's own parser as a separate flag.\n */\n value?: string | string[]\n}\n\n/** A bridged ADK tool's JSON-Schema-rendered description, as exposed to the CLI over MCP. */\nexport interface WrapperBridgedTool {\n /** The tool's raw name — matches `ctx.tools.visible()`, NOT the `mcp__<server>__<name>` permission spelling. */\n name: string\n /** Human/model-facing description. */\n description: string\n /** Plain JSON-Schema-shaped input schema (never a Zod schema — see Decision F in the design). */\n inputSchema: Record<string, unknown>\n}\n\n/** Explicit auth credential to forward to the grandchild's environment. Exactly one of the two fields is set. */\nexport interface WrapperAuth {\n /** Forwarded as `ANTHROPIC_API_KEY`. */\n apiKey?: string\n /** Forwarded as `ANTHROPIC_AUTH_TOKEN`. */\n authToken?: string\n /** Forwarded as `ANTHROPIC_BASE_URL`. */\n baseUrl?: string\n}\n\n/**\n * The one command `adapter.ts` sends per dispatch iteration, immediately after the wrapper's\n * `ready` event arrives. Exactly one `run` command is accepted per wrapper process lifetime — the\n * wrapper is spawned fresh per dispatch iteration, so there is no multi-run session.\n */\nexport interface WrapperRunCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'run'\n /** The fully-rendered history, as one `-p` positional prompt string. */\n prompt: string\n /** Forwarded verbatim to `--append-system-prompt`, when set. */\n appendSystemPrompt?: string\n /** The model identifier, forwarded to `--model`. */\n model?: string\n /** Working directory for the grandchild, forwarded to `--cwd`-equivalent spawn option. */\n cwd?: string\n /** Additional directories to allow tool access to, forwarded to `--add-dir`. */\n addDir?: string[]\n /**\n * The exact MCP-bridged tool names the grandchild is allowed to call, ALREADY filtered by the\n * adapter to exclude `disallowedTools`. Always sent, never omitted at this layer — the wrapper\n * itself decides whether to emit `--allowedTools` (omitted entirely when this array is empty,\n * since the flag is variadic and a bare `--allowedTools` with nothing after it would swallow the\n * next argv token).\n */\n allowedTools: string[]\n /** Forwarded to `--max-budget-usd`. */\n maxBudgetUsd?: number\n /** Forwarded to `--fallback-model` as one comma-joined value, never as separate argv tokens. */\n fallbackModel?: string[]\n /** Explicit auth credential(s) for the grandchild's environment. */\n auth?: WrapperAuth\n /** Mapped to `DISABLE_TELEMETRY` on the grandchild's env, never a CLI flag. */\n disableTelemetry?: boolean\n /** Mapped to `DISABLE_ERROR_REPORTING` on the grandchild's env, never a CLI flag. */\n disableErrorReporting?: boolean\n /** Mapped to `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` on the grandchild's env, never a CLI flag. */\n disableNonessentialTraffic?: boolean\n /** Mapped to `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT` on the grandchild's env, never a CLI flag. */\n mcpToolIdleTimeoutMs?: number\n /** Path to the `claude` binary to spawn. */\n claudeBin: string\n /** Forwarded to `--forward-subagent-text` when true. */\n forwardSubagentText?: boolean\n /**\n * Governs how the adapter rendered an ADK tool-result's unsupported `Media` kind (or oversized\n * inline `SpooledArtifact`) into the outbound `WrapperToolCallResponseCommand` — informational\n * only, since the adapter has already applied the policy before this command is sent.\n */\n unsupportedResultMediaPolicy: string\n /** The JSON-Schema-rendered subset of `ctx.tools.visible()` the wrapper exposes over MCP, already pre-filtered to exclude `disallowedTools`. */\n bridgedTools: WrapperBridgedTool[]\n /** Pre-validated additional argv entries, appended after every constructed flag and before the `--` prompt separator. */\n extraArgs?: ClaudeCodeCliExtraArg[]\n}\n\n/** One MCP content block a tool-call response may carry. */\nexport type WrapperToolResultContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n\n/**\n * The adapter's answer to a `tool_call_request` — a finished `CallToolResult`-shaped payload the\n * wrapper hands straight to the CLI's MCP bridge with no further interpretation.\n */\nexport interface WrapperToolCallResponseCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'tool_call_response'\n /** Correlates with the `requestId` on the originating `tool_call_request` event. */\n requestId: string\n /** A finished `CallToolResult`-shaped payload, handed straight to the CLI's MCP bridge. */\n results: {\n /** MCP content blocks to return for the call. */\n content: WrapperToolResultContentBlock[]\n /** Whether the tool call itself failed (as opposed to the wrapper/transport). */\n isError?: boolean\n }\n}\n\n/** Graceful-stop advisory sent to the wrapper (e.g. on `ctx.abortSignal` firing). */\nexport interface WrapperShutdownCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'shutdown'\n}\n\n/** The full adapter→wrapper command union. */\nexport type WrapperCommand =\n | WrapperRunCommand\n | WrapperToolCallResponseCommand\n | WrapperShutdownCommand\n\n// ─── Wrapper → adapter events ──────────────────────────────────────────────\n\n/** The bridge's HTTP listener is bound and the wrapper is about to spawn `claude`. */\nexport interface WrapperReadyEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'ready'\n}\n\n/**\n * The `claude` grandchild has just been spawned, detached in its own process group. Emitted once,\n * immediately after `spawn()` returns and before any stream-json line has been parsed — issue\n * #42 defect #1's additive protocol extension: without this, the adapter's own SIGKILL escalation\n * in `gracefulShutdown()` could only ever target the wrapper's own pid, never the grandchild's\n * process group, so escalating past a wrapper that ignores SIGTERM would orphan the grandchild's\n * entire group (SIGKILL cannot be caught by any handler, so the wrapper's own\n * `process.on('exit', ...)` group-cleanup can never run in that case). A consumer/adapter build\n * that predates this event simply never sees it — the union member is additive, and every\n * existing branch keeps working unchanged.\n */\nexport interface WrapperGrandchildSpawnedEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'grandchild_spawned'\n /**\n * The grandchild's OS pid. Spawned with `detached: true` (its own process group leader), so on\n * POSIX this pid also equals the process group id (pgid) — the adapter negates it\n * (`process.kill(-pid, 'SIGKILL')`) to signal the whole group, not just this one process.\n */\n pid: number\n}\n\n/**\n * The `claude` grandchild's OS process has exited (observed via the wrapper's own\n * `grandchild.on('exit', ...)` handler) — issue #42 round-2 defect #1's additive protocol\n * extension. Emitted unconditionally on that exit, independent of whether it was expected\n * (`sawResult`) or already mid-shutdown, and independent of whether the wrapper itself goes on to\n * exit cleanly afterwards.\n *\n * @remarks\n * Exists solely so the adapter can clear its own cached `grandchildPid`: once the grandchild is\n * gone, its pid/pgid is free for the OS to reuse for an entirely unrelated process group, and the\n * adapter's SIGKILL-escalation path in `gracefulShutdown()` must never signal a pid it no longer\n * has positive evidence is still the grandchild's own group. The wrapper itself can observe (and\n * emit) this even while stuck elsewhere in its own shutdown sequence — Node still delivers a\n * `ChildProcess` `'exit'` event to a handler registered on it regardless of what else the process is\n * doing — unless the wrapper is wedged badly enough to never run any JS at all, in which case\n * nothing it could emit would help regardless. A consumer/adapter build that predates this event\n * simply never sees it — the union member is additive, and every existing branch keeps working\n * unchanged.\n */\nexport interface WrapperGrandchildExitedEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'grandchild_exited'\n}\n\n/** Mirrors Claude's own `system/init` stream-json event. */\nexport interface WrapperInitEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'init'\n /** The model Claude reports it initialized with. */\n model?: string\n /** The built-in tool names Claude reports as available (expected empty under `--tools \"\"`). */\n tools?: string[]\n /** Any MCP server connection errors Claude reported during its own startup handshake. */\n mcpServerErrors?: string[]\n /** The original, unmodified `system/init` stream-json line. */\n raw?: unknown\n}\n\n/** A streamed chunk of assistant text or reasoning. */\nexport interface WrapperMessageDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'message_delta'\n /** Identifier correlating deltas belonging to the same in-progress message. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this message id. */\n isComplete?: boolean\n}\n\n/** A streamed chunk of reasoning/thinking text. */\nexport interface WrapperThoughtDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'thought_delta'\n /** Identifier correlating deltas belonging to the same in-progress thought. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this thought id. */\n isComplete?: boolean\n}\n\n/** A real ADK tool the wrapper's MCP bridge is asking the adapter to execute. */\nexport interface WrapperToolCallRequestEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'tool_call_request'\n /** Correlates with the `requestId` the adapter must echo back on its `tool_call_response`. */\n requestId: string\n /** The bridged tool's raw name, matching `ctx.tools.visible()`. */\n tool: string\n /** The call arguments Claude supplied, as received from the MCP `CallTool` request. */\n args: unknown\n}\n\n/** Mirrors Claude's own `system/api_retry` stream-json event. Observability only. */\nexport interface WrapperRetryEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'retry'\n /** The retry attempt number. */\n attempt: number\n /** The maximum number of retries Claude will attempt. */\n maxRetries?: number\n /** The delay, in milliseconds, before the next retry. */\n retryDelayMs?: number\n /** The HTTP status code that triggered the retry. */\n errorStatus?: number\n /** The error message associated with the retry. */\n error?: string\n}\n\n/** The terminal event for a dispatch iteration. */\nexport interface WrapperResultEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'result'\n /** The final assistant-facing result text, when present. */\n resultText?: string\n /** Claude's own session identifier for this turn. */\n sessionId?: string\n /** Total cost, in USD, Claude reports for this turn. */\n totalCostUsd?: number\n /** Token/usage accounting Claude reports for this turn. */\n usage?: Record<string, unknown>\n /** Whether this turn ended in an error (e.g. `--max-turns`/`--max-budget-usd` exhaustion). */\n isError: boolean\n /** Claude's machine-readable reason the turn stopped. */\n subtype?: string\n /** Claude's own stated reason the turn stopped. */\n stopReason?: string\n /** The original, unmodified terminal `result` stream-json line. */\n raw?: unknown\n}\n\n/** A wrapper-level failure (spawn error, unexpected exit, MCP bridge startup failure). */\nexport interface WrapperErrorEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'error'\n /** Human-readable summary of the failure. */\n message: string\n /** Additional detail, when available (e.g. the underlying error's message). */\n detail?: string\n}\n\n/** A generic diagnostic passthrough, never fatal. */\nexport interface WrapperLogEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'log'\n /** Severity of the diagnostic. */\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error'\n /** A short machine-readable category for the diagnostic (e.g. `'malformed-stream-json'`). */\n kind: string\n /** Human-readable message. */\n message: string\n /** Additional structured detail, when available. */\n payload?: unknown\n}\n\n/**\n * The wrapper's bridge HTTP listener and `claude` grandchild have both been torn down and the\n * wrapper is about to exit.\n */\nexport interface WrapperShutdownCompleteEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'shutdown_complete'\n}\n\n/** The full wrapper→adapter event union. */\nexport type WrapperEvent =\n | WrapperReadyEvent\n | WrapperGrandchildSpawnedEvent\n | WrapperGrandchildExitedEvent\n | WrapperInitEvent\n | WrapperMessageDeltaEvent\n | WrapperThoughtDeltaEvent\n | WrapperToolCallRequestEvent\n | WrapperRetryEvent\n | WrapperResultEvent\n | WrapperErrorEvent\n | WrapperLogEvent\n | WrapperShutdownCompleteEvent\n\n// ─── Encoding ───────────────────────────────────────────────────────────────\n\n/** Encode a `WrapperCommand` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperCommand = (command: WrapperCommand): string =>\n `${JSON.stringify(command)}\\n`\n\n/** Encode a `WrapperEvent` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperEvent = (event: WrapperEvent): string => `${JSON.stringify(event)}\\n`\n\n// ─── Byte-oriented NDJSON line framing ─────────────────────────────────────\n\nconst LF = 0x0a\nconst CR = 0x0d\n\n/**\n * Create an incremental, byte-oriented NDJSON line reader. Generalizes\n * `local_diffusion/protocol.ts`'s `createFrameReader` discipline (bounded memory via a hard\n * `maxLineBytes` cap enforced while consuming, fatal-UTF8-decode, malformed-line-is-non-fatal) for\n * pure JSON-per-line framing with no tag-prefixed grammar. `onLine` receives each decoded line and\n * returns the parsed value, or `undefined` for a line that failed to parse — the reader itself\n * never throws and never classifies content, it only frames bytes into lines.\n *\n * @throws RangeError if `maxLineBytes` is provided but is not a positive, finite, safe integer.\n */\nexport const createNdjsonLineReader = <T>(\n onLine: (raw: string) => T | undefined,\n opts?: { maxLineBytes?: number }\n): { push(chunk: Uint8Array): void; end(): void } => {\n if (\n opts?.maxLineBytes !== undefined &&\n (!Number.isSafeInteger(opts.maxLineBytes) || opts.maxLineBytes < 1)\n ) {\n throw new RangeError(\n `maxLineBytes must be a positive safe integer, received ${String(opts.maxLineBytes)}`\n )\n }\n const cap = opts?.maxLineBytes ?? 1_048_576\n const segments: Uint8Array[] = []\n let pending = 0\n let discarding = false\n let ended = false\n\n const resetLine = (): void => {\n segments.length = 0\n pending = 0\n }\n\n const assemble = (chunk: Uint8Array, start: number, end: number): Uint8Array => {\n const tail = end - start\n if (segments.length === 0) return chunk.subarray(start, end)\n const line = new Uint8Array(pending + tail)\n let at = 0\n for (const seg of segments) {\n line.set(seg, at)\n at += seg.length\n }\n if (tail > 0) line.set(chunk.subarray(start, end), at)\n return line\n }\n\n const decodeLine = (bytes: Uint8Array): void => {\n let end = bytes.length\n if (end > 0 && bytes[end - 1] === CR) end -= 1\n if (end === 0) return\n const slice = bytes.subarray(0, end)\n let text: string\n try {\n text = new TextDecoder('utf-8', { fatal: true }).decode(slice)\n } catch {\n // Invalid UTF-8 — not a fatal condition for the reader; the caller cannot parse it either,\n // so treat it exactly like a line onLine failed to parse (return undefined, no callback).\n return\n }\n onLine(text)\n }\n\n const consume = (chunk: Uint8Array): void => {\n let pos = 0\n if (discarding) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) return\n discarding = false\n pos = nl + 1\n }\n while (pos < chunk.length) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) {\n if (pending + (chunk.length - pos) > cap) {\n resetLine()\n discarding = true\n } else if (chunk.length > pos) {\n const seg = chunk.subarray(pos, chunk.length)\n segments.push(seg)\n pending += seg.length\n }\n return\n }\n if (pending + (nl - pos) > cap) {\n resetLine()\n } else {\n const line = assemble(chunk, pos, nl)\n resetLine()\n decodeLine(line)\n }\n pos = nl + 1\n }\n }\n\n return {\n push(chunk) {\n if (!ended) consume(chunk)\n },\n end() {\n if (ended) return\n ended = true\n resetLine()\n discarding = false\n },\n }\n}\n"],"mappings":";;;;AA8UA,IAAa,wBAAwB,YACnC,GAAG,KAAK,UAAU,OAAO,EAAE;;AAG7B,IAAa,sBAAsB,UAAgC,GAAG,KAAK,UAAU,KAAK,EAAE;AAI5F,IAAM,KAAK;AACX,IAAM,KAAK;;;;;;;;;;;AAYX,IAAa,0BACX,QACA,SACmD;CACnD,IACE,MAAM,iBAAiB,KAAA,MACtB,CAAC,OAAO,cAAc,KAAK,YAAY,KAAK,KAAK,eAAe,IAEjE,MAAM,IAAI,WACR,0DAA0D,OAAO,KAAK,YAAY,GACpF;CAEF,MAAM,MAAM,MAAM,gBAAgB;CAClC,MAAM,WAAyB,CAAC;CAChC,IAAI,UAAU;CACd,IAAI,aAAa;CACjB,IAAI,QAAQ;CAEZ,MAAM,kBAAwB;EAC5B,SAAS,SAAS;EAClB,UAAU;CACZ;CAEA,MAAM,YAAY,OAAmB,OAAe,QAA4B;EAC9E,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,WAAW,GAAG,OAAO,MAAM,SAAS,OAAO,GAAG;EAC3D,MAAM,OAAO,IAAI,WAAW,UAAU,IAAI;EAC1C,IAAI,KAAK;EACT,KAAK,MAAM,OAAO,UAAU;GAC1B,KAAK,IAAI,KAAK,EAAE;GAChB,MAAM,IAAI;EACZ;EACA,IAAI,OAAO,GAAG,KAAK,IAAI,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE;EACrD,OAAO;CACT;CAEA,MAAM,cAAc,UAA4B;EAC9C,IAAI,MAAM,MAAM;EAChB,IAAI,MAAM,KAAK,MAAM,MAAM,OAAO,IAAI,OAAO;EAC7C,IAAI,QAAQ,GAAG;EACf,MAAM,QAAQ,MAAM,SAAS,GAAG,GAAG;EACnC,IAAI;EACJ,IAAI;GACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;EAC/D,QAAQ;GAGN;EACF;EACA,OAAO,IAAI;CACb;CAEA,MAAM,WAAW,UAA4B;EAC3C,IAAI,MAAM;EACV,IAAI,YAAY;GACd,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;GACf,aAAa;GACb,MAAM,KAAK;EACb;EACA,OAAO,MAAM,MAAM,QAAQ;GACzB,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;IACb,IAAI,WAAW,MAAM,SAAS,OAAO,KAAK;KACxC,UAAU;KACV,aAAa;IACf,OAAO,IAAI,MAAM,SAAS,KAAK;KAC7B,MAAM,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM;KAC5C,SAAS,KAAK,GAAG;KACjB,WAAW,IAAI;IACjB;IACA;GACF;GACA,IAAI,WAAW,KAAK,OAAO,KACzB,UAAU;QACL;IACL,MAAM,OAAO,SAAS,OAAO,KAAK,EAAE;IACpC,UAAU;IACV,WAAW,IAAI;GACjB;GACA,MAAM,KAAK;EACb;CACF;CAEA,OAAO;EACL,KAAK,OAAO;GACV,IAAI,CAAC,OAAO,QAAQ,KAAK;EAC3B;EACA,MAAM;GACJ,IAAI,OAAO;GACX,QAAQ;GACR,UAAU;GACV,aAAa;EACf;CACF;AACF"}
|
|
@@ -137,6 +137,50 @@ export interface WrapperReadyEvent {
|
|
|
137
137
|
/** Discriminant for the {@link WrapperEvent} union. */
|
|
138
138
|
type: 'ready';
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* The `claude` grandchild has just been spawned, detached in its own process group. Emitted once,
|
|
142
|
+
* immediately after `spawn()` returns and before any stream-json line has been parsed — issue
|
|
143
|
+
* #42 defect #1's additive protocol extension: without this, the adapter's own SIGKILL escalation
|
|
144
|
+
* in `gracefulShutdown()` could only ever target the wrapper's own pid, never the grandchild's
|
|
145
|
+
* process group, so escalating past a wrapper that ignores SIGTERM would orphan the grandchild's
|
|
146
|
+
* entire group (SIGKILL cannot be caught by any handler, so the wrapper's own
|
|
147
|
+
* `process.on('exit', ...)` group-cleanup can never run in that case). A consumer/adapter build
|
|
148
|
+
* that predates this event simply never sees it — the union member is additive, and every
|
|
149
|
+
* existing branch keeps working unchanged.
|
|
150
|
+
*/
|
|
151
|
+
export interface WrapperGrandchildSpawnedEvent {
|
|
152
|
+
/** Discriminant for the {@link WrapperEvent} union. */
|
|
153
|
+
type: 'grandchild_spawned';
|
|
154
|
+
/**
|
|
155
|
+
* The grandchild's OS pid. Spawned with `detached: true` (its own process group leader), so on
|
|
156
|
+
* POSIX this pid also equals the process group id (pgid) — the adapter negates it
|
|
157
|
+
* (`process.kill(-pid, 'SIGKILL')`) to signal the whole group, not just this one process.
|
|
158
|
+
*/
|
|
159
|
+
pid: number;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The `claude` grandchild's OS process has exited (observed via the wrapper's own
|
|
163
|
+
* `grandchild.on('exit', ...)` handler) — issue #42 round-2 defect #1's additive protocol
|
|
164
|
+
* extension. Emitted unconditionally on that exit, independent of whether it was expected
|
|
165
|
+
* (`sawResult`) or already mid-shutdown, and independent of whether the wrapper itself goes on to
|
|
166
|
+
* exit cleanly afterwards.
|
|
167
|
+
*
|
|
168
|
+
* @remarks
|
|
169
|
+
* Exists solely so the adapter can clear its own cached `grandchildPid`: once the grandchild is
|
|
170
|
+
* gone, its pid/pgid is free for the OS to reuse for an entirely unrelated process group, and the
|
|
171
|
+
* adapter's SIGKILL-escalation path in `gracefulShutdown()` must never signal a pid it no longer
|
|
172
|
+
* has positive evidence is still the grandchild's own group. The wrapper itself can observe (and
|
|
173
|
+
* emit) this even while stuck elsewhere in its own shutdown sequence — Node still delivers a
|
|
174
|
+
* `ChildProcess` `'exit'` event to a handler registered on it regardless of what else the process is
|
|
175
|
+
* doing — unless the wrapper is wedged badly enough to never run any JS at all, in which case
|
|
176
|
+
* nothing it could emit would help regardless. A consumer/adapter build that predates this event
|
|
177
|
+
* simply never sees it — the union member is additive, and every existing branch keeps working
|
|
178
|
+
* unchanged.
|
|
179
|
+
*/
|
|
180
|
+
export interface WrapperGrandchildExitedEvent {
|
|
181
|
+
/** Discriminant for the {@link WrapperEvent} union. */
|
|
182
|
+
type: 'grandchild_exited';
|
|
183
|
+
}
|
|
140
184
|
/** Mirrors Claude's own `system/init` stream-json event. */
|
|
141
185
|
export interface WrapperInitEvent {
|
|
142
186
|
/** Discriminant for the {@link WrapperEvent} union. */
|
|
@@ -250,7 +294,7 @@ export interface WrapperShutdownCompleteEvent {
|
|
|
250
294
|
type: 'shutdown_complete';
|
|
251
295
|
}
|
|
252
296
|
/** The full wrapper→adapter event union. */
|
|
253
|
-
export type WrapperEvent = WrapperReadyEvent | WrapperInitEvent | WrapperMessageDeltaEvent | WrapperThoughtDeltaEvent | WrapperToolCallRequestEvent | WrapperRetryEvent | WrapperResultEvent | WrapperErrorEvent | WrapperLogEvent | WrapperShutdownCompleteEvent;
|
|
297
|
+
export type WrapperEvent = WrapperReadyEvent | WrapperGrandchildSpawnedEvent | WrapperGrandchildExitedEvent | WrapperInitEvent | WrapperMessageDeltaEvent | WrapperThoughtDeltaEvent | WrapperToolCallRequestEvent | WrapperRetryEvent | WrapperResultEvent | WrapperErrorEvent | WrapperLogEvent | WrapperShutdownCompleteEvent;
|
|
254
298
|
/** Encode a `WrapperCommand` as one NDJSON line, including its terminating newline. */
|
|
255
299
|
export declare const encodeWrapperCommand: (command: WrapperCommand) => string;
|
|
256
300
|
/** Encode a `WrapperEvent` as one NDJSON line, including its terminating newline. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wire.mjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/wire.ts"],"sourcesContent":["/**\n * The normalized adapter↔wrapper protocol shared by every CLI-harness LLM battery.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/wire\n *\n * @remarks\n * Zero imports by design: this module is the seam between `adapter.ts` (which runs in the ADK\n * process and imports ADK barrels freely) and `wrapper.ts` (which runs as a separate spawned\n * process and must import nothing from `@nhtio/adk/*`). Depending on either side would break the\n * boundary, so this file depends on neither.\n *\n * The wrapper↔adapter protocol itself is deliberately harness-agnostic — `WrapperRunCommand` /\n * `WrapperEvent` name no Claude-Code-specific concept — so a future Codex-CLI or Pi-agent battery\n * can reuse this exact module, writing only its own wrapper.\n */\n\n/** One entry in a `--json-schema`/`--effort`-style `extraArgs` escape hatch. */\nexport interface ClaudeCodeCliExtraArg {\n /** The exact CLI flag spelling. Restricted to a small, deliberately-chosen allowlist. */\n flag: '--effort' | '--agent' | '--betas' | '--json-schema' | '--name' | '--prompt-suggestions'\n /**\n * The flag's value. Required for every flag except `--prompt-suggestions` (optional, matching\n * the CLI's own `[value]` bracket syntax). A plain `string` for every flag except `--betas`,\n * which accepts `string[]` (matching its own `<betas...>` variadic arity). Every individual\n * value string, in every position, must not start with `-` — this is what makes it structurally\n * impossible for a value to be interpreted by the CLI's own parser as a separate flag.\n */\n value?: string | string[]\n}\n\n/** A bridged ADK tool's JSON-Schema-rendered description, as exposed to the CLI over MCP. */\nexport interface WrapperBridgedTool {\n /** The tool's raw name — matches `ctx.tools.visible()`, NOT the `mcp__<server>__<name>` permission spelling. */\n name: string\n /** Human/model-facing description. */\n description: string\n /** Plain JSON-Schema-shaped input schema (never a Zod schema — see Decision F in the design). */\n inputSchema: Record<string, unknown>\n}\n\n/** Explicit auth credential to forward to the grandchild's environment. Exactly one of the two fields is set. */\nexport interface WrapperAuth {\n /** Forwarded as `ANTHROPIC_API_KEY`. */\n apiKey?: string\n /** Forwarded as `ANTHROPIC_AUTH_TOKEN`. */\n authToken?: string\n /** Forwarded as `ANTHROPIC_BASE_URL`. */\n baseUrl?: string\n}\n\n/**\n * The one command `adapter.ts` sends per dispatch iteration, immediately after the wrapper's\n * `ready` event arrives. Exactly one `run` command is accepted per wrapper process lifetime — the\n * wrapper is spawned fresh per dispatch iteration, so there is no multi-run session.\n */\nexport interface WrapperRunCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'run'\n /** The fully-rendered history, as one `-p` positional prompt string. */\n prompt: string\n /** Forwarded verbatim to `--append-system-prompt`, when set. */\n appendSystemPrompt?: string\n /** The model identifier, forwarded to `--model`. */\n model?: string\n /** Working directory for the grandchild, forwarded to `--cwd`-equivalent spawn option. */\n cwd?: string\n /** Additional directories to allow tool access to, forwarded to `--add-dir`. */\n addDir?: string[]\n /**\n * The exact MCP-bridged tool names the grandchild is allowed to call, ALREADY filtered by the\n * adapter to exclude `disallowedTools`. Always sent, never omitted at this layer — the wrapper\n * itself decides whether to emit `--allowedTools` (omitted entirely when this array is empty,\n * since the flag is variadic and a bare `--allowedTools` with nothing after it would swallow the\n * next argv token).\n */\n allowedTools: string[]\n /** Forwarded to `--max-budget-usd`. */\n maxBudgetUsd?: number\n /** Forwarded to `--fallback-model` as one comma-joined value, never as separate argv tokens. */\n fallbackModel?: string[]\n /** Explicit auth credential(s) for the grandchild's environment. */\n auth?: WrapperAuth\n /** Mapped to `DISABLE_TELEMETRY` on the grandchild's env, never a CLI flag. */\n disableTelemetry?: boolean\n /** Mapped to `DISABLE_ERROR_REPORTING` on the grandchild's env, never a CLI flag. */\n disableErrorReporting?: boolean\n /** Mapped to `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` on the grandchild's env, never a CLI flag. */\n disableNonessentialTraffic?: boolean\n /** Mapped to `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT` on the grandchild's env, never a CLI flag. */\n mcpToolIdleTimeoutMs?: number\n /** Path to the `claude` binary to spawn. */\n claudeBin: string\n /** Forwarded to `--forward-subagent-text` when true. */\n forwardSubagentText?: boolean\n /**\n * Governs how the adapter rendered an ADK tool-result's unsupported `Media` kind (or oversized\n * inline `SpooledArtifact`) into the outbound `WrapperToolCallResponseCommand` — informational\n * only, since the adapter has already applied the policy before this command is sent.\n */\n unsupportedResultMediaPolicy: string\n /** The JSON-Schema-rendered subset of `ctx.tools.visible()` the wrapper exposes over MCP, already pre-filtered to exclude `disallowedTools`. */\n bridgedTools: WrapperBridgedTool[]\n /** Pre-validated additional argv entries, appended after every constructed flag and before the `--` prompt separator. */\n extraArgs?: ClaudeCodeCliExtraArg[]\n}\n\n/** One MCP content block a tool-call response may carry. */\nexport type WrapperToolResultContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n\n/**\n * The adapter's answer to a `tool_call_request` — a finished `CallToolResult`-shaped payload the\n * wrapper hands straight to the CLI's MCP bridge with no further interpretation.\n */\nexport interface WrapperToolCallResponseCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'tool_call_response'\n /** Correlates with the `requestId` on the originating `tool_call_request` event. */\n requestId: string\n /** A finished `CallToolResult`-shaped payload, handed straight to the CLI's MCP bridge. */\n results: {\n /** MCP content blocks to return for the call. */\n content: WrapperToolResultContentBlock[]\n /** Whether the tool call itself failed (as opposed to the wrapper/transport). */\n isError?: boolean\n }\n}\n\n/** Graceful-stop advisory sent to the wrapper (e.g. on `ctx.abortSignal` firing). */\nexport interface WrapperShutdownCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'shutdown'\n}\n\n/** The full adapter→wrapper command union. */\nexport type WrapperCommand =\n | WrapperRunCommand\n | WrapperToolCallResponseCommand\n | WrapperShutdownCommand\n\n// ─── Wrapper → adapter events ──────────────────────────────────────────────\n\n/** The bridge's HTTP listener is bound and the wrapper is about to spawn `claude`. */\nexport interface WrapperReadyEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'ready'\n}\n\n/** Mirrors Claude's own `system/init` stream-json event. */\nexport interface WrapperInitEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'init'\n /** The model Claude reports it initialized with. */\n model?: string\n /** The built-in tool names Claude reports as available (expected empty under `--tools \"\"`). */\n tools?: string[]\n /** Any MCP server connection errors Claude reported during its own startup handshake. */\n mcpServerErrors?: string[]\n /** The original, unmodified `system/init` stream-json line. */\n raw?: unknown\n}\n\n/** A streamed chunk of assistant text or reasoning. */\nexport interface WrapperMessageDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'message_delta'\n /** Identifier correlating deltas belonging to the same in-progress message. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this message id. */\n isComplete?: boolean\n}\n\n/** A streamed chunk of reasoning/thinking text. */\nexport interface WrapperThoughtDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'thought_delta'\n /** Identifier correlating deltas belonging to the same in-progress thought. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this thought id. */\n isComplete?: boolean\n}\n\n/** A real ADK tool the wrapper's MCP bridge is asking the adapter to execute. */\nexport interface WrapperToolCallRequestEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'tool_call_request'\n /** Correlates with the `requestId` the adapter must echo back on its `tool_call_response`. */\n requestId: string\n /** The bridged tool's raw name, matching `ctx.tools.visible()`. */\n tool: string\n /** The call arguments Claude supplied, as received from the MCP `CallTool` request. */\n args: unknown\n}\n\n/** Mirrors Claude's own `system/api_retry` stream-json event. Observability only. */\nexport interface WrapperRetryEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'retry'\n /** The retry attempt number. */\n attempt: number\n /** The maximum number of retries Claude will attempt. */\n maxRetries?: number\n /** The delay, in milliseconds, before the next retry. */\n retryDelayMs?: number\n /** The HTTP status code that triggered the retry. */\n errorStatus?: number\n /** The error message associated with the retry. */\n error?: string\n}\n\n/** The terminal event for a dispatch iteration. */\nexport interface WrapperResultEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'result'\n /** The final assistant-facing result text, when present. */\n resultText?: string\n /** Claude's own session identifier for this turn. */\n sessionId?: string\n /** Total cost, in USD, Claude reports for this turn. */\n totalCostUsd?: number\n /** Token/usage accounting Claude reports for this turn. */\n usage?: Record<string, unknown>\n /** Whether this turn ended in an error (e.g. `--max-turns`/`--max-budget-usd` exhaustion). */\n isError: boolean\n /** Claude's machine-readable reason the turn stopped. */\n subtype?: string\n /** Claude's own stated reason the turn stopped. */\n stopReason?: string\n /** The original, unmodified terminal `result` stream-json line. */\n raw?: unknown\n}\n\n/** A wrapper-level failure (spawn error, unexpected exit, MCP bridge startup failure). */\nexport interface WrapperErrorEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'error'\n /** Human-readable summary of the failure. */\n message: string\n /** Additional detail, when available (e.g. the underlying error's message). */\n detail?: string\n}\n\n/** A generic diagnostic passthrough, never fatal. */\nexport interface WrapperLogEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'log'\n /** Severity of the diagnostic. */\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error'\n /** A short machine-readable category for the diagnostic (e.g. `'malformed-stream-json'`). */\n kind: string\n /** Human-readable message. */\n message: string\n /** Additional structured detail, when available. */\n payload?: unknown\n}\n\n/**\n * The wrapper's bridge HTTP listener and `claude` grandchild have both been torn down and the\n * wrapper is about to exit.\n */\nexport interface WrapperShutdownCompleteEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'shutdown_complete'\n}\n\n/** The full wrapper→adapter event union. */\nexport type WrapperEvent =\n | WrapperReadyEvent\n | WrapperInitEvent\n | WrapperMessageDeltaEvent\n | WrapperThoughtDeltaEvent\n | WrapperToolCallRequestEvent\n | WrapperRetryEvent\n | WrapperResultEvent\n | WrapperErrorEvent\n | WrapperLogEvent\n | WrapperShutdownCompleteEvent\n\n// ─── Encoding ───────────────────────────────────────────────────────────────\n\n/** Encode a `WrapperCommand` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperCommand = (command: WrapperCommand): string =>\n `${JSON.stringify(command)}\\n`\n\n/** Encode a `WrapperEvent` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperEvent = (event: WrapperEvent): string => `${JSON.stringify(event)}\\n`\n\n// ─── Byte-oriented NDJSON line framing ─────────────────────────────────────\n\nconst LF = 0x0a\nconst CR = 0x0d\n\n/**\n * Create an incremental, byte-oriented NDJSON line reader. Generalizes\n * `local_diffusion/protocol.ts`'s `createFrameReader` discipline (bounded memory via a hard\n * `maxLineBytes` cap enforced while consuming, fatal-UTF8-decode, malformed-line-is-non-fatal) for\n * pure JSON-per-line framing with no tag-prefixed grammar. `onLine` receives each decoded line and\n * returns the parsed value, or `undefined` for a line that failed to parse — the reader itself\n * never throws and never classifies content, it only frames bytes into lines.\n *\n * @throws RangeError if `maxLineBytes` is provided but is not a positive, finite, safe integer.\n */\nexport const createNdjsonLineReader = <T>(\n onLine: (raw: string) => T | undefined,\n opts?: { maxLineBytes?: number }\n): { push(chunk: Uint8Array): void; end(): void } => {\n if (\n opts?.maxLineBytes !== undefined &&\n (!Number.isSafeInteger(opts.maxLineBytes) || opts.maxLineBytes < 1)\n ) {\n throw new RangeError(\n `maxLineBytes must be a positive safe integer, received ${String(opts.maxLineBytes)}`\n )\n }\n const cap = opts?.maxLineBytes ?? 1_048_576\n const segments: Uint8Array[] = []\n let pending = 0\n let discarding = false\n let ended = false\n\n const resetLine = (): void => {\n segments.length = 0\n pending = 0\n }\n\n const assemble = (chunk: Uint8Array, start: number, end: number): Uint8Array => {\n const tail = end - start\n if (segments.length === 0) return chunk.subarray(start, end)\n const line = new Uint8Array(pending + tail)\n let at = 0\n for (const seg of segments) {\n line.set(seg, at)\n at += seg.length\n }\n if (tail > 0) line.set(chunk.subarray(start, end), at)\n return line\n }\n\n const decodeLine = (bytes: Uint8Array): void => {\n let end = bytes.length\n if (end > 0 && bytes[end - 1] === CR) end -= 1\n if (end === 0) return\n const slice = bytes.subarray(0, end)\n let text: string\n try {\n text = new TextDecoder('utf-8', { fatal: true }).decode(slice)\n } catch {\n // Invalid UTF-8 — not a fatal condition for the reader; the caller cannot parse it either,\n // so treat it exactly like a line onLine failed to parse (return undefined, no callback).\n return\n }\n onLine(text)\n }\n\n const consume = (chunk: Uint8Array): void => {\n let pos = 0\n if (discarding) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) return\n discarding = false\n pos = nl + 1\n }\n while (pos < chunk.length) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) {\n if (pending + (chunk.length - pos) > cap) {\n resetLine()\n discarding = true\n } else if (chunk.length > pos) {\n const seg = chunk.subarray(pos, chunk.length)\n segments.push(seg)\n pending += seg.length\n }\n return\n }\n if (pending + (nl - pos) > cap) {\n resetLine()\n } else {\n const line = assemble(chunk, pos, nl)\n resetLine()\n decodeLine(line)\n }\n pos = nl + 1\n }\n }\n\n return {\n push(chunk) {\n if (!ended) consume(chunk)\n },\n end() {\n if (ended) return\n ended = true\n resetLine()\n discarding = false\n },\n }\n}\n"],"mappings":";;AA8RA,IAAa,wBAAwB,YACnC,GAAG,KAAK,UAAU,OAAO,EAAE;;AAG7B,IAAa,sBAAsB,UAAgC,GAAG,KAAK,UAAU,KAAK,EAAE;AAI5F,IAAM,KAAK;AACX,IAAM,KAAK;;;;;;;;;;;AAYX,IAAa,0BACX,QACA,SACmD;CACnD,IACE,MAAM,iBAAiB,KAAA,MACtB,CAAC,OAAO,cAAc,KAAK,YAAY,KAAK,KAAK,eAAe,IAEjE,MAAM,IAAI,WACR,0DAA0D,OAAO,KAAK,YAAY,GACpF;CAEF,MAAM,MAAM,MAAM,gBAAgB;CAClC,MAAM,WAAyB,CAAC;CAChC,IAAI,UAAU;CACd,IAAI,aAAa;CACjB,IAAI,QAAQ;CAEZ,MAAM,kBAAwB;EAC5B,SAAS,SAAS;EAClB,UAAU;CACZ;CAEA,MAAM,YAAY,OAAmB,OAAe,QAA4B;EAC9E,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,WAAW,GAAG,OAAO,MAAM,SAAS,OAAO,GAAG;EAC3D,MAAM,OAAO,IAAI,WAAW,UAAU,IAAI;EAC1C,IAAI,KAAK;EACT,KAAK,MAAM,OAAO,UAAU;GAC1B,KAAK,IAAI,KAAK,EAAE;GAChB,MAAM,IAAI;EACZ;EACA,IAAI,OAAO,GAAG,KAAK,IAAI,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE;EACrD,OAAO;CACT;CAEA,MAAM,cAAc,UAA4B;EAC9C,IAAI,MAAM,MAAM;EAChB,IAAI,MAAM,KAAK,MAAM,MAAM,OAAO,IAAI,OAAO;EAC7C,IAAI,QAAQ,GAAG;EACf,MAAM,QAAQ,MAAM,SAAS,GAAG,GAAG;EACnC,IAAI;EACJ,IAAI;GACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;EAC/D,QAAQ;GAGN;EACF;EACA,OAAO,IAAI;CACb;CAEA,MAAM,WAAW,UAA4B;EAC3C,IAAI,MAAM;EACV,IAAI,YAAY;GACd,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;GACf,aAAa;GACb,MAAM,KAAK;EACb;EACA,OAAO,MAAM,MAAM,QAAQ;GACzB,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;IACb,IAAI,WAAW,MAAM,SAAS,OAAO,KAAK;KACxC,UAAU;KACV,aAAa;IACf,OAAO,IAAI,MAAM,SAAS,KAAK;KAC7B,MAAM,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM;KAC5C,SAAS,KAAK,GAAG;KACjB,WAAW,IAAI;IACjB;IACA;GACF;GACA,IAAI,WAAW,KAAK,OAAO,KACzB,UAAU;QACL;IACL,MAAM,OAAO,SAAS,OAAO,KAAK,EAAE;IACpC,UAAU;IACV,WAAW,IAAI;GACjB;GACA,MAAM,KAAK;EACb;CACF;CAEA,OAAO;EACL,KAAK,OAAO;GACV,IAAI,CAAC,OAAO,QAAQ,KAAK;EAC3B;EACA,MAAM;GACJ,IAAI,OAAO;GACX,QAAQ;GACR,UAAU;GACV,aAAa;EACf;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"wire.mjs","names":[],"sources":["../../../../src/batteries/llm/claude_code_cli/wire.ts"],"sourcesContent":["/**\n * The normalized adapter↔wrapper protocol shared by every CLI-harness LLM battery.\n *\n * @module @nhtio/adk/batteries/llm/claude_code_cli/wire\n *\n * @remarks\n * Zero imports by design: this module is the seam between `adapter.ts` (which runs in the ADK\n * process and imports ADK barrels freely) and `wrapper.ts` (which runs as a separate spawned\n * process and must import nothing from `@nhtio/adk/*`). Depending on either side would break the\n * boundary, so this file depends on neither.\n *\n * The wrapper↔adapter protocol itself is deliberately harness-agnostic — `WrapperRunCommand` /\n * `WrapperEvent` name no Claude-Code-specific concept — so a future Codex-CLI or Pi-agent battery\n * can reuse this exact module, writing only its own wrapper.\n */\n\n/** One entry in a `--json-schema`/`--effort`-style `extraArgs` escape hatch. */\nexport interface ClaudeCodeCliExtraArg {\n /** The exact CLI flag spelling. Restricted to a small, deliberately-chosen allowlist. */\n flag: '--effort' | '--agent' | '--betas' | '--json-schema' | '--name' | '--prompt-suggestions'\n /**\n * The flag's value. Required for every flag except `--prompt-suggestions` (optional, matching\n * the CLI's own `[value]` bracket syntax). A plain `string` for every flag except `--betas`,\n * which accepts `string[]` (matching its own `<betas...>` variadic arity). Every individual\n * value string, in every position, must not start with `-` — this is what makes it structurally\n * impossible for a value to be interpreted by the CLI's own parser as a separate flag.\n */\n value?: string | string[]\n}\n\n/** A bridged ADK tool's JSON-Schema-rendered description, as exposed to the CLI over MCP. */\nexport interface WrapperBridgedTool {\n /** The tool's raw name — matches `ctx.tools.visible()`, NOT the `mcp__<server>__<name>` permission spelling. */\n name: string\n /** Human/model-facing description. */\n description: string\n /** Plain JSON-Schema-shaped input schema (never a Zod schema — see Decision F in the design). */\n inputSchema: Record<string, unknown>\n}\n\n/** Explicit auth credential to forward to the grandchild's environment. Exactly one of the two fields is set. */\nexport interface WrapperAuth {\n /** Forwarded as `ANTHROPIC_API_KEY`. */\n apiKey?: string\n /** Forwarded as `ANTHROPIC_AUTH_TOKEN`. */\n authToken?: string\n /** Forwarded as `ANTHROPIC_BASE_URL`. */\n baseUrl?: string\n}\n\n/**\n * The one command `adapter.ts` sends per dispatch iteration, immediately after the wrapper's\n * `ready` event arrives. Exactly one `run` command is accepted per wrapper process lifetime — the\n * wrapper is spawned fresh per dispatch iteration, so there is no multi-run session.\n */\nexport interface WrapperRunCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'run'\n /** The fully-rendered history, as one `-p` positional prompt string. */\n prompt: string\n /** Forwarded verbatim to `--append-system-prompt`, when set. */\n appendSystemPrompt?: string\n /** The model identifier, forwarded to `--model`. */\n model?: string\n /** Working directory for the grandchild, forwarded to `--cwd`-equivalent spawn option. */\n cwd?: string\n /** Additional directories to allow tool access to, forwarded to `--add-dir`. */\n addDir?: string[]\n /**\n * The exact MCP-bridged tool names the grandchild is allowed to call, ALREADY filtered by the\n * adapter to exclude `disallowedTools`. Always sent, never omitted at this layer — the wrapper\n * itself decides whether to emit `--allowedTools` (omitted entirely when this array is empty,\n * since the flag is variadic and a bare `--allowedTools` with nothing after it would swallow the\n * next argv token).\n */\n allowedTools: string[]\n /** Forwarded to `--max-budget-usd`. */\n maxBudgetUsd?: number\n /** Forwarded to `--fallback-model` as one comma-joined value, never as separate argv tokens. */\n fallbackModel?: string[]\n /** Explicit auth credential(s) for the grandchild's environment. */\n auth?: WrapperAuth\n /** Mapped to `DISABLE_TELEMETRY` on the grandchild's env, never a CLI flag. */\n disableTelemetry?: boolean\n /** Mapped to `DISABLE_ERROR_REPORTING` on the grandchild's env, never a CLI flag. */\n disableErrorReporting?: boolean\n /** Mapped to `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` on the grandchild's env, never a CLI flag. */\n disableNonessentialTraffic?: boolean\n /** Mapped to `CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT` on the grandchild's env, never a CLI flag. */\n mcpToolIdleTimeoutMs?: number\n /** Path to the `claude` binary to spawn. */\n claudeBin: string\n /** Forwarded to `--forward-subagent-text` when true. */\n forwardSubagentText?: boolean\n /**\n * Governs how the adapter rendered an ADK tool-result's unsupported `Media` kind (or oversized\n * inline `SpooledArtifact`) into the outbound `WrapperToolCallResponseCommand` — informational\n * only, since the adapter has already applied the policy before this command is sent.\n */\n unsupportedResultMediaPolicy: string\n /** The JSON-Schema-rendered subset of `ctx.tools.visible()` the wrapper exposes over MCP, already pre-filtered to exclude `disallowedTools`. */\n bridgedTools: WrapperBridgedTool[]\n /** Pre-validated additional argv entries, appended after every constructed flag and before the `--` prompt separator. */\n extraArgs?: ClaudeCodeCliExtraArg[]\n}\n\n/** One MCP content block a tool-call response may carry. */\nexport type WrapperToolResultContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n\n/**\n * The adapter's answer to a `tool_call_request` — a finished `CallToolResult`-shaped payload the\n * wrapper hands straight to the CLI's MCP bridge with no further interpretation.\n */\nexport interface WrapperToolCallResponseCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'tool_call_response'\n /** Correlates with the `requestId` on the originating `tool_call_request` event. */\n requestId: string\n /** A finished `CallToolResult`-shaped payload, handed straight to the CLI's MCP bridge. */\n results: {\n /** MCP content blocks to return for the call. */\n content: WrapperToolResultContentBlock[]\n /** Whether the tool call itself failed (as opposed to the wrapper/transport). */\n isError?: boolean\n }\n}\n\n/** Graceful-stop advisory sent to the wrapper (e.g. on `ctx.abortSignal` firing). */\nexport interface WrapperShutdownCommand {\n /** Discriminant for the {@link WrapperCommand} union. */\n type: 'shutdown'\n}\n\n/** The full adapter→wrapper command union. */\nexport type WrapperCommand =\n | WrapperRunCommand\n | WrapperToolCallResponseCommand\n | WrapperShutdownCommand\n\n// ─── Wrapper → adapter events ──────────────────────────────────────────────\n\n/** The bridge's HTTP listener is bound and the wrapper is about to spawn `claude`. */\nexport interface WrapperReadyEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'ready'\n}\n\n/**\n * The `claude` grandchild has just been spawned, detached in its own process group. Emitted once,\n * immediately after `spawn()` returns and before any stream-json line has been parsed — issue\n * #42 defect #1's additive protocol extension: without this, the adapter's own SIGKILL escalation\n * in `gracefulShutdown()` could only ever target the wrapper's own pid, never the grandchild's\n * process group, so escalating past a wrapper that ignores SIGTERM would orphan the grandchild's\n * entire group (SIGKILL cannot be caught by any handler, so the wrapper's own\n * `process.on('exit', ...)` group-cleanup can never run in that case). A consumer/adapter build\n * that predates this event simply never sees it — the union member is additive, and every\n * existing branch keeps working unchanged.\n */\nexport interface WrapperGrandchildSpawnedEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'grandchild_spawned'\n /**\n * The grandchild's OS pid. Spawned with `detached: true` (its own process group leader), so on\n * POSIX this pid also equals the process group id (pgid) — the adapter negates it\n * (`process.kill(-pid, 'SIGKILL')`) to signal the whole group, not just this one process.\n */\n pid: number\n}\n\n/**\n * The `claude` grandchild's OS process has exited (observed via the wrapper's own\n * `grandchild.on('exit', ...)` handler) — issue #42 round-2 defect #1's additive protocol\n * extension. Emitted unconditionally on that exit, independent of whether it was expected\n * (`sawResult`) or already mid-shutdown, and independent of whether the wrapper itself goes on to\n * exit cleanly afterwards.\n *\n * @remarks\n * Exists solely so the adapter can clear its own cached `grandchildPid`: once the grandchild is\n * gone, its pid/pgid is free for the OS to reuse for an entirely unrelated process group, and the\n * adapter's SIGKILL-escalation path in `gracefulShutdown()` must never signal a pid it no longer\n * has positive evidence is still the grandchild's own group. The wrapper itself can observe (and\n * emit) this even while stuck elsewhere in its own shutdown sequence — Node still delivers a\n * `ChildProcess` `'exit'` event to a handler registered on it regardless of what else the process is\n * doing — unless the wrapper is wedged badly enough to never run any JS at all, in which case\n * nothing it could emit would help regardless. A consumer/adapter build that predates this event\n * simply never sees it — the union member is additive, and every existing branch keeps working\n * unchanged.\n */\nexport interface WrapperGrandchildExitedEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'grandchild_exited'\n}\n\n/** Mirrors Claude's own `system/init` stream-json event. */\nexport interface WrapperInitEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'init'\n /** The model Claude reports it initialized with. */\n model?: string\n /** The built-in tool names Claude reports as available (expected empty under `--tools \"\"`). */\n tools?: string[]\n /** Any MCP server connection errors Claude reported during its own startup handshake. */\n mcpServerErrors?: string[]\n /** The original, unmodified `system/init` stream-json line. */\n raw?: unknown\n}\n\n/** A streamed chunk of assistant text or reasoning. */\nexport interface WrapperMessageDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'message_delta'\n /** Identifier correlating deltas belonging to the same in-progress message. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this message id. */\n isComplete?: boolean\n}\n\n/** A streamed chunk of reasoning/thinking text. */\nexport interface WrapperThoughtDeltaEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'thought_delta'\n /** Identifier correlating deltas belonging to the same in-progress thought. */\n id: string\n /** The incremental text chunk. */\n delta: string\n /** Set on the final delta for this thought id. */\n isComplete?: boolean\n}\n\n/** A real ADK tool the wrapper's MCP bridge is asking the adapter to execute. */\nexport interface WrapperToolCallRequestEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'tool_call_request'\n /** Correlates with the `requestId` the adapter must echo back on its `tool_call_response`. */\n requestId: string\n /** The bridged tool's raw name, matching `ctx.tools.visible()`. */\n tool: string\n /** The call arguments Claude supplied, as received from the MCP `CallTool` request. */\n args: unknown\n}\n\n/** Mirrors Claude's own `system/api_retry` stream-json event. Observability only. */\nexport interface WrapperRetryEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'retry'\n /** The retry attempt number. */\n attempt: number\n /** The maximum number of retries Claude will attempt. */\n maxRetries?: number\n /** The delay, in milliseconds, before the next retry. */\n retryDelayMs?: number\n /** The HTTP status code that triggered the retry. */\n errorStatus?: number\n /** The error message associated with the retry. */\n error?: string\n}\n\n/** The terminal event for a dispatch iteration. */\nexport interface WrapperResultEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'result'\n /** The final assistant-facing result text, when present. */\n resultText?: string\n /** Claude's own session identifier for this turn. */\n sessionId?: string\n /** Total cost, in USD, Claude reports for this turn. */\n totalCostUsd?: number\n /** Token/usage accounting Claude reports for this turn. */\n usage?: Record<string, unknown>\n /** Whether this turn ended in an error (e.g. `--max-turns`/`--max-budget-usd` exhaustion). */\n isError: boolean\n /** Claude's machine-readable reason the turn stopped. */\n subtype?: string\n /** Claude's own stated reason the turn stopped. */\n stopReason?: string\n /** The original, unmodified terminal `result` stream-json line. */\n raw?: unknown\n}\n\n/** A wrapper-level failure (spawn error, unexpected exit, MCP bridge startup failure). */\nexport interface WrapperErrorEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'error'\n /** Human-readable summary of the failure. */\n message: string\n /** Additional detail, when available (e.g. the underlying error's message). */\n detail?: string\n}\n\n/** A generic diagnostic passthrough, never fatal. */\nexport interface WrapperLogEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'log'\n /** Severity of the diagnostic. */\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error'\n /** A short machine-readable category for the diagnostic (e.g. `'malformed-stream-json'`). */\n kind: string\n /** Human-readable message. */\n message: string\n /** Additional structured detail, when available. */\n payload?: unknown\n}\n\n/**\n * The wrapper's bridge HTTP listener and `claude` grandchild have both been torn down and the\n * wrapper is about to exit.\n */\nexport interface WrapperShutdownCompleteEvent {\n /** Discriminant for the {@link WrapperEvent} union. */\n type: 'shutdown_complete'\n}\n\n/** The full wrapper→adapter event union. */\nexport type WrapperEvent =\n | WrapperReadyEvent\n | WrapperGrandchildSpawnedEvent\n | WrapperGrandchildExitedEvent\n | WrapperInitEvent\n | WrapperMessageDeltaEvent\n | WrapperThoughtDeltaEvent\n | WrapperToolCallRequestEvent\n | WrapperRetryEvent\n | WrapperResultEvent\n | WrapperErrorEvent\n | WrapperLogEvent\n | WrapperShutdownCompleteEvent\n\n// ─── Encoding ───────────────────────────────────────────────────────────────\n\n/** Encode a `WrapperCommand` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperCommand = (command: WrapperCommand): string =>\n `${JSON.stringify(command)}\\n`\n\n/** Encode a `WrapperEvent` as one NDJSON line, including its terminating newline. */\nexport const encodeWrapperEvent = (event: WrapperEvent): string => `${JSON.stringify(event)}\\n`\n\n// ─── Byte-oriented NDJSON line framing ─────────────────────────────────────\n\nconst LF = 0x0a\nconst CR = 0x0d\n\n/**\n * Create an incremental, byte-oriented NDJSON line reader. Generalizes\n * `local_diffusion/protocol.ts`'s `createFrameReader` discipline (bounded memory via a hard\n * `maxLineBytes` cap enforced while consuming, fatal-UTF8-decode, malformed-line-is-non-fatal) for\n * pure JSON-per-line framing with no tag-prefixed grammar. `onLine` receives each decoded line and\n * returns the parsed value, or `undefined` for a line that failed to parse — the reader itself\n * never throws and never classifies content, it only frames bytes into lines.\n *\n * @throws RangeError if `maxLineBytes` is provided but is not a positive, finite, safe integer.\n */\nexport const createNdjsonLineReader = <T>(\n onLine: (raw: string) => T | undefined,\n opts?: { maxLineBytes?: number }\n): { push(chunk: Uint8Array): void; end(): void } => {\n if (\n opts?.maxLineBytes !== undefined &&\n (!Number.isSafeInteger(opts.maxLineBytes) || opts.maxLineBytes < 1)\n ) {\n throw new RangeError(\n `maxLineBytes must be a positive safe integer, received ${String(opts.maxLineBytes)}`\n )\n }\n const cap = opts?.maxLineBytes ?? 1_048_576\n const segments: Uint8Array[] = []\n let pending = 0\n let discarding = false\n let ended = false\n\n const resetLine = (): void => {\n segments.length = 0\n pending = 0\n }\n\n const assemble = (chunk: Uint8Array, start: number, end: number): Uint8Array => {\n const tail = end - start\n if (segments.length === 0) return chunk.subarray(start, end)\n const line = new Uint8Array(pending + tail)\n let at = 0\n for (const seg of segments) {\n line.set(seg, at)\n at += seg.length\n }\n if (tail > 0) line.set(chunk.subarray(start, end), at)\n return line\n }\n\n const decodeLine = (bytes: Uint8Array): void => {\n let end = bytes.length\n if (end > 0 && bytes[end - 1] === CR) end -= 1\n if (end === 0) return\n const slice = bytes.subarray(0, end)\n let text: string\n try {\n text = new TextDecoder('utf-8', { fatal: true }).decode(slice)\n } catch {\n // Invalid UTF-8 — not a fatal condition for the reader; the caller cannot parse it either,\n // so treat it exactly like a line onLine failed to parse (return undefined, no callback).\n return\n }\n onLine(text)\n }\n\n const consume = (chunk: Uint8Array): void => {\n let pos = 0\n if (discarding) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) return\n discarding = false\n pos = nl + 1\n }\n while (pos < chunk.length) {\n const nl = chunk.indexOf(LF, pos)\n if (nl === -1) {\n if (pending + (chunk.length - pos) > cap) {\n resetLine()\n discarding = true\n } else if (chunk.length > pos) {\n const seg = chunk.subarray(pos, chunk.length)\n segments.push(seg)\n pending += seg.length\n }\n return\n }\n if (pending + (nl - pos) > cap) {\n resetLine()\n } else {\n const line = assemble(chunk, pos, nl)\n resetLine()\n decodeLine(line)\n }\n pos = nl + 1\n }\n }\n\n return {\n push(chunk) {\n if (!ended) consume(chunk)\n },\n end() {\n if (ended) return\n ended = true\n resetLine()\n discarding = false\n },\n }\n}\n"],"mappings":";;AA8UA,IAAa,wBAAwB,YACnC,GAAG,KAAK,UAAU,OAAO,EAAE;;AAG7B,IAAa,sBAAsB,UAAgC,GAAG,KAAK,UAAU,KAAK,EAAE;AAI5F,IAAM,KAAK;AACX,IAAM,KAAK;;;;;;;;;;;AAYX,IAAa,0BACX,QACA,SACmD;CACnD,IACE,MAAM,iBAAiB,KAAA,MACtB,CAAC,OAAO,cAAc,KAAK,YAAY,KAAK,KAAK,eAAe,IAEjE,MAAM,IAAI,WACR,0DAA0D,OAAO,KAAK,YAAY,GACpF;CAEF,MAAM,MAAM,MAAM,gBAAgB;CAClC,MAAM,WAAyB,CAAC;CAChC,IAAI,UAAU;CACd,IAAI,aAAa;CACjB,IAAI,QAAQ;CAEZ,MAAM,kBAAwB;EAC5B,SAAS,SAAS;EAClB,UAAU;CACZ;CAEA,MAAM,YAAY,OAAmB,OAAe,QAA4B;EAC9E,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,WAAW,GAAG,OAAO,MAAM,SAAS,OAAO,GAAG;EAC3D,MAAM,OAAO,IAAI,WAAW,UAAU,IAAI;EAC1C,IAAI,KAAK;EACT,KAAK,MAAM,OAAO,UAAU;GAC1B,KAAK,IAAI,KAAK,EAAE;GAChB,MAAM,IAAI;EACZ;EACA,IAAI,OAAO,GAAG,KAAK,IAAI,MAAM,SAAS,OAAO,GAAG,GAAG,EAAE;EACrD,OAAO;CACT;CAEA,MAAM,cAAc,UAA4B;EAC9C,IAAI,MAAM,MAAM;EAChB,IAAI,MAAM,KAAK,MAAM,MAAM,OAAO,IAAI,OAAO;EAC7C,IAAI,QAAQ,GAAG;EACf,MAAM,QAAQ,MAAM,SAAS,GAAG,GAAG;EACnC,IAAI;EACJ,IAAI;GACF,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,KAAK;EAC/D,QAAQ;GAGN;EACF;EACA,OAAO,IAAI;CACb;CAEA,MAAM,WAAW,UAA4B;EAC3C,IAAI,MAAM;EACV,IAAI,YAAY;GACd,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;GACf,aAAa;GACb,MAAM,KAAK;EACb;EACA,OAAO,MAAM,MAAM,QAAQ;GACzB,MAAM,KAAK,MAAM,QAAQ,IAAI,GAAG;GAChC,IAAI,OAAO,IAAI;IACb,IAAI,WAAW,MAAM,SAAS,OAAO,KAAK;KACxC,UAAU;KACV,aAAa;IACf,OAAO,IAAI,MAAM,SAAS,KAAK;KAC7B,MAAM,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM;KAC5C,SAAS,KAAK,GAAG;KACjB,WAAW,IAAI;IACjB;IACA;GACF;GACA,IAAI,WAAW,KAAK,OAAO,KACzB,UAAU;QACL;IACL,MAAM,OAAO,SAAS,OAAO,KAAK,EAAE;IACpC,UAAU;IACV,WAAW,IAAI;GACjB;GACA,MAAM,KAAK;EACb;CACF;CAEA,OAAO;EACL,KAAK,OAAO;GACV,IAAI,CAAC,OAAO,QAAQ,KAAK;EAC3B;EACA,MAAM;GACJ,IAAI,OAAO;GACX,QAAQ;GACR,UAAU;GACV,aAAa;EACf;CACF;AACF"}
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
8
|
* Self-contained with respect to `@nhtio/adk/*` — no such imports anywhere in this file or its
|
|
9
|
-
*
|
|
10
|
-
* `@modelcontextprotocol/sdk`, and those
|
|
11
|
-
* asset (see Decision C in the design notes) —
|
|
12
|
-
*
|
|
9
|
+
* five siblings (`wire.ts`, `cli_protocol.ts`, `mcp_bridge.ts`, `message_id_state.ts`,
|
|
10
|
+
* `line_queue.ts`). Only `node:*` builtins, `@modelcontextprotocol/sdk`, and those five
|
|
11
|
+
* battery-local modules. Ships as a sibling dist asset (see Decision C in the design notes) —
|
|
12
|
+
* never imported as a library, only ever spawned by file path via
|
|
13
|
+
* `execa(process.execPath, [wrapperPath])` from `adapter.ts`.
|
|
13
14
|
*
|
|
14
15
|
* Carries no `@module` JSDoc tag: it is invisible to the `@module`-tag scraper that builds the
|
|
15
16
|
* public `exports` map, and is added to `vite.config.mts`'s `build.lib.entry` as an explicit extra
|