@agentskit/adapters 0.16.0 → 0.17.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/README.md CHANGED
@@ -39,6 +39,7 @@ Docs: [package guide](https://www.agentskit.io/docs/reference/packages/adapters)
39
39
  - **Embedder functions built in** — the same adapter pattern covers text embeddings, so you can reuse provider config for both chat and RAG
40
40
  - **One-line local AI** — `ollama({ model: 'llama3.1' })` for fully offline agents with no API key required
41
41
  - **CLI-backed agents** — `@agentskit/adapters/cli` normalizes text, JSON, and ACP-based local LLM CLIs
42
+ - **LangChain bridge** — `@agentskit/adapters/langchain-bridge` turns any adapter into a LangChain `BaseChatModel` for `createAgent`
42
43
 
43
44
  ## Install
44
45
 
@@ -106,8 +107,8 @@ const adapter = createCliAdapter(resolveCliManifest(manifest, { mode: 'review-sa
106
107
 
107
108
  The generic factories are `createCliAdapter` (`exec-text`),
108
109
  `createJsonCliAdapter` (`exec-json`), and `createAcpCliAdapter` (ACP v1 over
109
- JSON lines). The built-in manifests cover Codex, Claude Code, Grok CLI, and
110
- OpenCode. `resolveCliManifest` keeps command, argv, protocol, provider id, and
110
+ JSON lines). The built-in manifests cover Codex, Claude Code (text and JSON
111
+ output), Grok CLI, and OpenCode. `resolveCliManifest` keeps command, argv, protocol, provider id, and
111
112
  mode explicit; `diagnoseCliProviderManifest` verifies availability and an
112
113
  optional version pattern. `review-safe` is the default: no shell, automatic
113
114
  installation, native login, MCP, plugins, or terminal tools. Use
@@ -121,6 +122,92 @@ is awaited before the adapter finishes, including when input or output fails.
121
122
  an OS, filesystem, process, or network sandbox. Use `@agentskit/sandbox` when a
122
123
  real isolation boundary is required.
123
124
 
125
+ ### `exec-text` vs `exec-json`: what each protocol can return
126
+
127
+ The protocol decides which stream chunks a manifest can produce. Pick the
128
+ manifest by what you need back, not by the CLI name:
129
+
130
+ | Protocol | Factory | Chunks emitted | Not available |
131
+ | ----------- | ---------------------- | --------------------------------------- | ---------------------------------------------- |
132
+ | `exec-text` | `createCliAdapter` | `text` (streamed), `done` | `structuredOutput`, `reasoning`, `tool_call`, usage |
133
+ | `exec-json` | `createJsonCliAdapter` | `text`, `reasoning`, `tool_call`, `usage`, `done` | streaming (one response per process) |
134
+ | `acp` | `createAcpCliAdapter` | `text`, `reasoning` (streamed), `done` | tool calls (rejected), MCP, plugins, terminal |
135
+
136
+ `validateCliProviderManifest` rejects a manifest that claims a capability its
137
+ protocol cannot deliver, and `requiredCapabilities` fails before spawning for
138
+ the same reason. In practice: the `claude-code` and `codex` manifests are
139
+ `exec-text` and only ever yield plain text. For tool calls, usage, or
140
+ structured output from Claude Code use `claude-code-json` instead.
141
+
142
+ The same table is enforced at compile time. `CliProviderManifest` is a union
143
+ discriminated by `protocol`, `capabilities` is typed as
144
+ `CliCapabilitiesFor<protocol>`, and `getCliProviderManifest('claude-code')`
145
+ returns `CliProviderManifestFor<'exec-text'>`, so this does not type-check:
146
+
147
+ ```ts
148
+ const manifest = getCliProviderManifest('claude-code')!
149
+ resolveCliManifest(manifest, { requiredCapabilities: { tools: true } })
150
+ // ^^^^^ not assignable: exec-text has no tools/structuredOutput
151
+ ```
152
+
153
+ ### Claude Code
154
+
155
+ Two first-party manifests wrap the `claude` executable:
156
+
157
+ - `claude-code` — `claude -p`, `exec-text`. Streams the final answer as text.
158
+ - `claude-code-json` — `claude -p --output-format json`, `exec-json`. Parses
159
+ the result envelope into `text`, `tool_call`, and `usage` chunks with
160
+ `session_id`, `total_cost_usd`, `duration_ms`, and `num_turns` as metadata.
161
+ `is_error` or a non-`success` subtype fails closed.
162
+
163
+ ```ts
164
+ import { createJsonCliAdapter, getCliProviderManifest, resolveCliManifest } from '@agentskit/adapters/cli'
165
+
166
+ const manifest = getCliProviderManifest('claude-code-json')
167
+ if (!manifest) throw new Error('provider manifest is unavailable')
168
+ const adapter = createJsonCliAdapter(resolveCliManifest(manifest, { mode: 'trusted-local' }))
169
+ ```
170
+
171
+ When the request carries `context.tools`, the tool list and a
172
+ `{ text?, toolCalls }` reply contract are appended to Claude Code's system
173
+ prompt via `--append-system-prompt`; `claude-code-json` recognizes that object
174
+ (fenced or bare) in the `result` string and emits `tool_call` chunks. Plain prose results stay `text`.
175
+ `structured_output` from `--json-schema` is honored the same way. Tool
176
+ execution itself remains the consumer's responsibility: the adapter never
177
+ enables Claude Code's own tools, MCP, or plugins.
178
+
179
+ ### Prompt serialization
180
+
181
+ `createCliAdapter` and `createJsonCliAdapter` default to writing the raw
182
+ `AdapterRequest` as one JSON line on stdin. That is the right contract for a
183
+ purpose-built CLI, but agentic CLIs read stdin as a user prompt, and Claude
184
+ Code refuses a raw JSON request containing `systemPrompt` as an apparent
185
+ prompt-injection attempt. The first-party manifests therefore use two
186
+ exported serializers:
187
+
188
+ - `serializeCliPrompt` (used by `codex`) writes labelled blocks:
189
+
190
+ ```text
191
+ [system]
192
+ You are a reviewer.
193
+
194
+ [user]
195
+ review this
196
+ ```
197
+
198
+ - `serializeCliMessages` plus `claudeCodeRequestArgs` (used by `claude-code`
199
+ and `claude-code-json`) writes only the conversation to stdin (a single user
200
+ message is written bare) and passes the system prompt and the tool contract
201
+ through `--append-system-prompt`. Verified live against Claude Code 2.1:
202
+ the same instructions inside the user prompt, even as labelled `[system]`
203
+ blocks, are flagged as an injection attempt, whereas through the system
204
+ flag a request with `context.tools` comes back as a `tool_call` chunk.
205
+
206
+ A manifest may declare `serializeRequest`, `requestArgs` (extra per-request
207
+ argv, appended after `args`), `parseOutput`, and `parse`; `resolveCliManifest`
208
+ forwards them to the factory. `buildCliSystemPrompt(request)` returns the
209
+ system prompt plus tool contract for other CLIs with a system-prompt flag.
210
+
124
211
  Use `buildArgs(request)` only for CLIs that require the prompt in argv; it is
125
212
  request-aware and still uses direct, shell-free spawning. Set
126
213
  `serializeRequest: () => ''` when the provider does not consume stdin. For
@@ -129,6 +216,46 @@ before the normal `parse(value)` callback runs.
129
216
  For CLIs that write the final response to a file, `outputFile` reads that file
130
217
  after process completion with the same byte limit and abort handling.
131
218
 
219
+ ## LangChain bridge: use any adapter as a LangChain chat model
220
+
221
+ `langchain()` and `langgraph()` expose a LangChain runnable as an
222
+ `AdapterFactory`. The `@agentskit/adapters/langchain-bridge` subpath goes the
223
+ other way: `adapterToLangChainModel(adapter)` wraps any `AdapterFactory`
224
+ (mock, Ollama, a CLI-backed adapter, ...) as a real `BaseChatModel`, so it can
225
+ replace `ChatAnthropic`/`ChatOpenAI` in `createAgent`, `AgentNode`, or a plain
226
+ chain. It needs `@langchain/core` (optional peer dependency).
227
+
228
+ ```ts
229
+ import { createAgent } from 'langchain'
230
+ import { tool } from '@langchain/core/tools'
231
+ import { mockAdapter } from '@agentskit/adapters'
232
+ import { adapterToLangChainModel } from '@agentskit/adapters/langchain-bridge'
233
+
234
+ const add = tool(async ({ a, b }) => String(a + b), {
235
+ name: 'add',
236
+ description: 'Add two numbers',
237
+ schema: { type: 'object', properties: { a: { type: 'number' }, b: { type: 'number' } }, required: ['a', 'b'] },
238
+ })
239
+ const model = adapterToLangChainModel(mockAdapter({ response: [/* ... */] }), { modelName: 'mock' })
240
+ const agent = createAgent({ model, tools: [add] })
241
+ ```
242
+
243
+ - `bindTools()` forwards LangChain tools (structured tools, OpenAI-format
244
+ definitions, runnable tools) as AgentsKit `ToolDefinition`s on
245
+ `context.tools`; `tool_choice` lands in `context.metadata.toolChoice`.
246
+ - System messages become both a `system` message and `context.systemPrompt`;
247
+ AI messages keep `tool_calls`; tool messages keep `tool_call_id`.
248
+ - `tool_call` chunks become `AIMessage.tool_calls` (args parsed from JSON),
249
+ `usage` chunks become `usage_metadata`, `reasoning` chunks land in
250
+ `additional_kwargs.reasoning`, `error` chunks throw, and `.stream()` yields
251
+ `AIMessageChunk`s with `tool_call_chunks`.
252
+ - Every response is a real `AIMessage`/`AIMessageChunk` instance, so
253
+ `wrapModelCall` middleware and `responseFormat` work together. The default
254
+ `profile` reports `toolCalling` and no native `structuredOutput`, so
255
+ `createAgent` uses its tool strategy, which any tool-calling adapter can
256
+ satisfy. Pass `profile: { structuredOutput: true }` only for adapters that
257
+ honour a provider-side JSON schema.
258
+
132
259
  ## Stream guarantees
133
260
 
134
261
  - A stream terminates exactly once with `done` or `error`; terminal errors carry an `Error` in `metadata.error`.
@@ -366,6 +366,162 @@ async function abortableWrite(handle, input, signal) {
366
366
  if (onError) handle.child.stdin.removeListener("error", onError);
367
367
  }
368
368
  }
369
+ var cliError = (message, cause) => new core.AdapterError({ code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED, message, cause });
370
+ function isRecord(value) {
371
+ return typeof value === "object" && value !== null && !Array.isArray(value);
372
+ }
373
+ function parseCliJsonResponse(value) {
374
+ if (!isRecord(value)) throw cliError("CLI JSON response must be an object");
375
+ const chunks = [];
376
+ if (typeof value.text === "string") chunks.push({ type: "text", content: value.text });
377
+ if (typeof value.reasoning === "string") chunks.push({ type: "reasoning", content: value.reasoning });
378
+ if (Array.isArray(value.toolCalls)) {
379
+ for (const item of value.toolCalls) {
380
+ if (!isRecord(item) || typeof item.id !== "string" || typeof item.name !== "string" || typeof item.args !== "string") {
381
+ throw cliError("CLI JSON toolCalls must contain string id, name, and args fields");
382
+ }
383
+ JSON.parse(item.args);
384
+ chunks.push({ type: "tool_call", toolCall: { id: item.id, name: item.name, args: item.args } });
385
+ }
386
+ }
387
+ if (value.usage !== void 0) {
388
+ if (!isRecord(value.usage)) {
389
+ throw cliError("CLI JSON usage must contain numeric promptTokens, completionTokens, and totalTokens");
390
+ }
391
+ const promptTokens = value.usage.promptTokens;
392
+ const completionTokens = value.usage.completionTokens;
393
+ const totalTokens = value.usage.totalTokens;
394
+ if (typeof promptTokens !== "number" || typeof completionTokens !== "number" || typeof totalTokens !== "number") {
395
+ throw cliError("CLI JSON usage must contain numeric promptTokens, completionTokens, and totalTokens");
396
+ }
397
+ chunks.push({ type: "usage", usage: { promptTokens, completionTokens, totalTokens } });
398
+ }
399
+ if (value.metadata !== void 0) {
400
+ if (!isRecord(value.metadata)) throw cliError("CLI JSON metadata must be an object");
401
+ if (chunks.length === 0) throw cliError("CLI JSON response contains no semantic output");
402
+ chunks[chunks.length - 1] = { ...chunks[chunks.length - 1], metadata: value.metadata };
403
+ }
404
+ if (chunks.length === 0) throw cliError("CLI JSON response contains no semantic output");
405
+ return chunks;
406
+ }
407
+
408
+ // src/cli/prompt.ts
409
+ function serializeCliPrompt(request, options = {}) {
410
+ const blocks = [];
411
+ const system = request.context?.systemPrompt;
412
+ if (options.system !== false && system) blocks.push(`[system]
413
+ ${system}`);
414
+ const tools = request.context?.tools;
415
+ if (options.tools !== false && tools && tools.length > 0) blocks.push(`[tools]
416
+ ${describeTools(tools)}`);
417
+ const messages = request.messages.filter((message) => message.role !== "system" || options.system !== false);
418
+ if (blocks.length === 0 && messages.length === 1 && messages[0].role === "user") return `${messages[0].content}
419
+ `;
420
+ for (const message of messages) blocks.push(`[${message.role}]
421
+ ${message.content}`);
422
+ return `${blocks.join("\n\n")}
423
+ `;
424
+ }
425
+ function serializeCliMessages(request) {
426
+ return serializeCliPrompt(request, { system: false, tools: false });
427
+ }
428
+ function buildCliSystemPrompt(request) {
429
+ const parts = [];
430
+ if (request.context?.systemPrompt) parts.push(request.context.systemPrompt);
431
+ const tools = request.context?.tools;
432
+ if (tools && tools.length > 0) parts.push(describeTools(tools));
433
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
434
+ }
435
+ function claudeCodeRequestArgs(request) {
436
+ const system = buildCliSystemPrompt(request);
437
+ return system ? ["--append-system-prompt", system] : [];
438
+ }
439
+ var TOOL_CONTRACT = [
440
+ "The application running you exposes the tools listed below; it executes them, not you.",
441
+ "To call one or more of them, reply with only a single JSON object",
442
+ '{"text"?: string, "toolCalls": [{"id": string, "name": string, "args": string}]} where `args` is a JSON-encoded',
443
+ "string of the tool arguments and `id` is any unique string. When no tool call is needed, reply with plain text."
444
+ ].join(" ");
445
+ function describeTools(tools) {
446
+ const definitions = tools.map((tool) => ({ name: tool.name, description: tool.description, schema: tool.schema }));
447
+ return `${TOOL_CONTRACT}
448
+ ${JSON.stringify(definitions, null, 2)}`;
449
+ }
450
+ function isRecord2(value) {
451
+ return typeof value === "object" && value !== null && !Array.isArray(value);
452
+ }
453
+ function claudeCodeError(message, cause) {
454
+ return new core.AdapterError({
455
+ code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED,
456
+ message,
457
+ cause,
458
+ hint: 'Run `claude -p --output-format json "ping"` manually to inspect the envelope emitted by the installed Claude Code version.'
459
+ });
460
+ }
461
+ function number(value) {
462
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
463
+ }
464
+ var RESPONSE_KEYS = ["text", "reasoning", "toolCalls"];
465
+ var FENCE = "```";
466
+ function unfence(text) {
467
+ if (!text.startsWith(FENCE) || !text.endsWith(FENCE) || text.length < FENCE.length * 2) return text;
468
+ let inner = text.slice(FENCE.length, text.length - FENCE.length);
469
+ if (inner.startsWith("json")) inner = inner.slice("json".length);
470
+ return inner.trim();
471
+ }
472
+ function embeddedResponse(result) {
473
+ const candidate = unfence(result.trim());
474
+ if (!candidate.startsWith("{") || !candidate.endsWith("}")) return void 0;
475
+ let value;
476
+ try {
477
+ value = JSON.parse(candidate);
478
+ } catch {
479
+ return void 0;
480
+ }
481
+ if (!isRecord2(value) || !RESPONSE_KEYS.some((key) => key in value)) return void 0;
482
+ return value;
483
+ }
484
+ function parseClaudeCodeJsonResponse(value) {
485
+ if (!isRecord2(value) || value.type !== "result") throw claudeCodeError("Claude Code JSON output must be a `result` envelope");
486
+ const result = typeof value.result === "string" ? value.result : "";
487
+ if (value.is_error === true || typeof value.subtype === "string" && value.subtype !== "success") {
488
+ throw claudeCodeError(`Claude Code returned ${String(value.subtype ?? "error")}${result ? `: ${result}` : ""}`);
489
+ }
490
+ const chunks = [];
491
+ const structured = isRecord2(value.structured_output) ? value.structured_output : void 0;
492
+ if (structured && RESPONSE_KEYS.some((key) => key in structured)) {
493
+ chunks.push(...parseCliJsonResponse(structured));
494
+ } else if (structured) {
495
+ chunks.push({ type: "text", content: JSON.stringify(structured) });
496
+ } else {
497
+ const embedded = embeddedResponse(result);
498
+ if (embedded) chunks.push(...parseCliJsonResponse(embedded));
499
+ else if (result) chunks.push({ type: "text", content: result });
500
+ }
501
+ if (isRecord2(value.usage)) {
502
+ const promptTokens = number(value.usage.input_tokens) + number(value.usage.cache_creation_input_tokens) + number(value.usage.cache_read_input_tokens);
503
+ const completionTokens = number(value.usage.output_tokens);
504
+ chunks.push({ type: "usage", usage: { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens } });
505
+ }
506
+ if (chunks.length === 0) throw claudeCodeError("Claude Code JSON output contains no semantic output");
507
+ const metadata = {};
508
+ for (const key of ["session_id", "total_cost_usd", "duration_ms", "num_turns", "subtype"]) {
509
+ if (value[key] !== void 0) metadata[key] = value[key];
510
+ }
511
+ const last = chunks[chunks.length - 1];
512
+ chunks[chunks.length - 1] = { ...last, metadata: { ...last.metadata, ...metadata } };
513
+ return chunks;
514
+ }
515
+ function parseClaudeCodeJsonOutput(stdout) {
516
+ const trimmed = stdout.trim();
517
+ try {
518
+ return JSON.parse(trimmed);
519
+ } catch (error) {
520
+ const start = trimmed.lastIndexOf("\n{");
521
+ if (start < 0) throw claudeCodeError(`Claude Code stdout is not a JSON envelope: ${error instanceof Error ? error.message : String(error)}`, error);
522
+ return JSON.parse(trimmed.slice(start + 1));
523
+ }
524
+ }
369
525
  var MODES = ["review-safe", "trusted-local", "restricted-environment"];
370
526
  var CAPABILITIES = /* @__PURE__ */ new Set([
371
527
  "streaming",
@@ -388,9 +544,13 @@ var manifests = [
388
544
  capabilities: { streaming: true, nativeAuth: true },
389
545
  supportedModes: MODES,
390
546
  credentialEnv: ["OPENAI_API_KEY", "CODEX_API_KEY"],
391
- docsUrl: "https://github.com/openai/codex"
547
+ docsUrl: "https://github.com/openai/codex",
548
+ serializeRequest: serializeCliPrompt
392
549
  },
393
550
  {
551
+ // Plain-text transport: streams Claude Code's final answer as text.
552
+ // No structured output, reasoning, or tool calls; use `claude-code-json`
553
+ // for those.
394
554
  id: "claude-code",
395
555
  name: "Claude Code",
396
556
  command: "claude",
@@ -400,7 +560,27 @@ var manifests = [
400
560
  capabilities: { streaming: true, nativeAuth: true },
401
561
  supportedModes: MODES,
402
562
  credentialEnv: ["ANTHROPIC_API_KEY"],
403
- docsUrl: "https://docs.anthropic.com/en/docs/claude-code/cli-usage"
563
+ docsUrl: "https://docs.anthropic.com/en/docs/claude-code/cli-usage",
564
+ serializeRequest: serializeCliMessages,
565
+ requestArgs: claudeCodeRequestArgs
566
+ },
567
+ {
568
+ // Structured transport: one `claude -p --output-format json` envelope per
569
+ // request, parsed into text, tool_call, and usage chunks. Not streaming.
570
+ id: "claude-code-json",
571
+ name: "Claude Code (JSON output)",
572
+ command: "claude",
573
+ args: ["-p", "--output-format", "json"],
574
+ diagnosticArgs: ["--version"],
575
+ protocol: "exec-json",
576
+ capabilities: { structuredOutput: true, nativeAuth: true },
577
+ supportedModes: MODES,
578
+ credentialEnv: ["ANTHROPIC_API_KEY"],
579
+ docsUrl: "https://docs.anthropic.com/en/docs/claude-code/cli-usage",
580
+ serializeRequest: serializeCliMessages,
581
+ requestArgs: claudeCodeRequestArgs,
582
+ parseOutput: parseClaudeCodeJsonOutput,
583
+ parse: parseClaudeCodeJsonResponse
404
584
  },
405
585
  {
406
586
  id: "grok",
@@ -443,13 +623,14 @@ function validateArgs(values, label) {
443
623
  if (value.includes("\0")) throw manifestError(`CLI manifest ${label} contains a null byte`);
444
624
  }
445
625
  }
446
- function isRecord(value) {
626
+ function isRecord3(value) {
447
627
  return typeof value === "object" && value !== null && !Array.isArray(value);
448
628
  }
449
629
  function validateCapabilities(manifest) {
450
- for (const capability of Object.keys(manifest.capabilities)) {
630
+ const declared = manifest.capabilities;
631
+ for (const capability of Object.keys(declared)) {
451
632
  if (!CAPABILITIES.has(capability)) throw manifestError(`CLI manifest declares unknown capability: ${capability}`);
452
- if (manifest.capabilities[capability] !== true) throw manifestError(`CLI manifest capability ${capability} must be true or omitted`);
633
+ if (declared[capability] !== true) throw manifestError(`CLI manifest capability ${capability} must be true or omitted`);
453
634
  }
454
635
  const unsupportedByProtocol = {
455
636
  "exec-text": ["structuredOutput", "reasoning", "tools", "mcp", "plugins", "terminal"],
@@ -457,11 +638,11 @@ function validateCapabilities(manifest) {
457
638
  acp: ["tools", "mcp", "plugins", "terminal"]
458
639
  };
459
640
  for (const capability of unsupportedByProtocol[manifest.protocol]) {
460
- if (manifest.capabilities[capability] === true) throw manifestError(`CLI manifest ${manifest.id} declares unsupported ${capability} for ${manifest.protocol}`);
641
+ if (declared[capability] === true) throw manifestError(`CLI manifest ${manifest.id} declares unsupported ${capability} for ${manifest.protocol}`);
461
642
  }
462
643
  }
463
644
  function validateCliProviderManifest(manifest) {
464
- if (!isRecord(manifest)) throw manifestError("CLI manifest must be an object");
645
+ if (!isRecord3(manifest)) throw manifestError("CLI manifest must be an object");
465
646
  if (typeof manifest.id !== "string" || typeof manifest.name !== "string" || typeof manifest.command !== "string") {
466
647
  throw manifestError("CLI manifest id, name, and command must be strings");
467
648
  }
@@ -471,7 +652,7 @@ function validateCliProviderManifest(manifest) {
471
652
  if (manifest.protocol !== "exec-text" && manifest.protocol !== "exec-json" && manifest.protocol !== "acp") {
472
653
  throw manifestError("CLI manifest protocol is unsupported");
473
654
  }
474
- if (!isRecord(manifest.capabilities) || !Array.isArray(manifest.supportedModes)) throw manifestError("CLI manifest capabilities and supportedModes are required");
655
+ if (!isRecord3(manifest.capabilities) || !Array.isArray(manifest.supportedModes)) throw manifestError("CLI manifest capabilities and supportedModes are required");
475
656
  if (!manifest.supportedModes.every((value) => typeof value === "string")) throw manifestError("CLI manifest supportedModes must be a string array");
476
657
  const candidate = manifest;
477
658
  nonEmpty(candidate.id, "id");
@@ -498,6 +679,12 @@ function validateCliProviderManifest(manifest) {
498
679
  }
499
680
  }
500
681
  if (candidate.docsUrl !== void 0 && typeof candidate.docsUrl !== "string") throw manifestError(`CLI manifest ${candidate.id} docsUrl must be a string`);
682
+ for (const key of ["serializeRequest", "requestArgs", "parseOutput", "parse"]) {
683
+ if (candidate[key] !== void 0 && typeof candidate[key] !== "function") throw manifestError(`CLI manifest ${candidate.id} ${key} must be a function`);
684
+ if ((key === "parseOutput" || key === "parse") && candidate[key] !== void 0 && candidate.protocol !== "exec-json") {
685
+ throw manifestError(`CLI manifest ${candidate.id} declares ${key}, which only applies to the exec-json protocol`);
686
+ }
687
+ }
501
688
  validateCapabilities(candidate);
502
689
  }
503
690
  function listCliProviderManifests() {
@@ -517,9 +704,12 @@ function resolveCliManifest(manifest, options = {}) {
517
704
  validateCliProviderManifest(manifest);
518
705
  const mode = options.mode ?? "review-safe";
519
706
  if (!manifest.supportedModes.includes(mode)) throw manifestError(`CLI manifest ${manifest.id} does not support mode: ${mode}`);
707
+ const args = [...manifest.args, ...options.args ?? []];
708
+ const requestArgs = manifest.requestArgs;
520
709
  return {
521
710
  command: manifest.command,
522
- args: [...manifest.args, ...options.args ?? []],
711
+ args,
712
+ buildArgs: requestArgs ? (request) => [...args, ...requestArgs(request)] : void 0,
523
713
  mode,
524
714
  cwd: options.cwd,
525
715
  env: options.env,
@@ -529,7 +719,10 @@ function resolveCliManifest(manifest, options = {}) {
529
719
  providerId: manifest.id,
530
720
  protocol: manifest.protocol,
531
721
  onDiagnostic: options.onDiagnostic,
532
- requiredCapabilities: options.requiredCapabilities
722
+ requiredCapabilities: options.requiredCapabilities,
723
+ serializeRequest: manifest.serializeRequest,
724
+ parseOutput: manifest.parseOutput,
725
+ parse: manifest.parse
533
726
  };
534
727
  }
535
728
  function adapterCapabilities(manifest) {
@@ -555,7 +748,6 @@ function manifestCapabilities(manifest) {
555
748
  // src/cli/index.ts
556
749
  var defaultSerialize = (request) => `${JSON.stringify(request)}
557
750
  `;
558
- var cliError = (message, cause) => new core.AdapterError({ code: core.ErrorCodes.AK_ADAPTER_STREAM_FAILED, message, cause });
559
751
  function createFactory(capabilities, run) {
560
752
  return {
561
753
  capabilities,
@@ -639,43 +831,6 @@ async function* runText(request, signal, options) {
639
831
  await closeProcess(handle);
640
832
  }
641
833
  }
642
- function isRecord2(value) {
643
- return typeof value === "object" && value !== null && !Array.isArray(value);
644
- }
645
- function standardJsonParser(value) {
646
- if (!isRecord2(value)) throw cliError("CLI JSON response must be an object");
647
- const chunks = [];
648
- if (typeof value.text === "string") chunks.push({ type: "text", content: value.text });
649
- if (typeof value.reasoning === "string") chunks.push({ type: "reasoning", content: value.reasoning });
650
- if (Array.isArray(value.toolCalls)) {
651
- for (const item of value.toolCalls) {
652
- if (!isRecord2(item) || typeof item.id !== "string" || typeof item.name !== "string" || typeof item.args !== "string") {
653
- throw cliError("CLI JSON toolCalls must contain string id, name, and args fields");
654
- }
655
- JSON.parse(item.args);
656
- chunks.push({ type: "tool_call", toolCall: { id: item.id, name: item.name, args: item.args } });
657
- }
658
- }
659
- if (value.usage !== void 0) {
660
- if (!isRecord2(value.usage)) {
661
- throw cliError("CLI JSON usage must contain numeric promptTokens, completionTokens, and totalTokens");
662
- }
663
- const promptTokens = value.usage.promptTokens;
664
- const completionTokens = value.usage.completionTokens;
665
- const totalTokens = value.usage.totalTokens;
666
- if (typeof promptTokens !== "number" || typeof completionTokens !== "number" || typeof totalTokens !== "number") {
667
- throw cliError("CLI JSON usage must contain numeric promptTokens, completionTokens, and totalTokens");
668
- }
669
- chunks.push({ type: "usage", usage: { promptTokens, completionTokens, totalTokens } });
670
- }
671
- if (value.metadata !== void 0) {
672
- if (!isRecord2(value.metadata)) throw cliError("CLI JSON metadata must be an object");
673
- if (chunks.length === 0) throw cliError("CLI JSON response contains no semantic output");
674
- chunks[chunks.length - 1] = { ...chunks[chunks.length - 1], metadata: value.metadata };
675
- }
676
- if (chunks.length === 0) throw cliError("CLI JSON response contains no semantic output");
677
- return chunks;
678
- }
679
834
  function normalizeJsonChunks(chunks) {
680
835
  if (chunks.length === 0) throw cliError("CLI JSON parser returned no chunks");
681
836
  if (chunks.some((chunk) => !isStreamChunk(chunk))) throw cliError("CLI JSON parser returned an invalid stream chunk");
@@ -687,7 +842,7 @@ function normalizeJsonChunks(chunks) {
687
842
  return last.type === "done" || last.type === "error" ? [...chunks] : [...chunks, { type: "done" }];
688
843
  }
689
844
  function isStreamChunk(value) {
690
- return isRecord2(value) && typeof value.type === "string" && (/* @__PURE__ */ new Set([
845
+ return isRecord(value) && typeof value.type === "string" && (/* @__PURE__ */ new Set([
691
846
  "text",
692
847
  "tool_call",
693
848
  "tool_result",
@@ -712,20 +867,20 @@ async function* runJson(request, signal, options) {
712
867
  } catch (error) {
713
868
  throw cliError(`CLI returned malformed JSON: ${error instanceof Error ? error.message : String(error)}`, error);
714
869
  }
715
- const chunks = normalizeJsonChunks((options.parse ?? standardJsonParser)(value));
870
+ const chunks = normalizeJsonChunks((options.parse ?? parseCliJsonResponse)(value));
716
871
  for (const chunk of chunks) yield chunk;
717
872
  } finally {
718
873
  await closeProcess(handle);
719
874
  }
720
875
  }
721
876
  function parseAcpMessage(value) {
722
- if (!isRecord2(value) || value.jsonrpc !== "2.0") throw cliError("ACP message must be a JSON-RPC 2.0 object");
877
+ if (!isRecord(value) || value.jsonrpc !== "2.0") throw cliError("ACP message must be a JSON-RPC 2.0 object");
723
878
  if (typeof value.method === "string") {
724
879
  if (value.id === void 0) return { kind: "notification", method: value.method, params: value.params };
725
880
  return { kind: "request", method: value.method };
726
881
  }
727
882
  if (typeof value.id === "string" || typeof value.id === "number" || value.id === null) {
728
- if (value.error !== void 0 && !isRecord2(value.error)) throw cliError("ACP error response is malformed");
883
+ if (value.error !== void 0 && !isRecord(value.error)) throw cliError("ACP error response is malformed");
729
884
  return { kind: "response", id: value.id, result: value.result, error: value.error };
730
885
  }
731
886
  throw cliError("ACP message has no method or response id");
@@ -778,12 +933,12 @@ ${request.context.systemPrompt}
778
933
  ${message.content}`).join("\n\n");
779
934
  }
780
935
  function acpUpdateChunk(params, sessionId) {
781
- if (!isRecord2(params) || params.sessionId !== sessionId || !isRecord2(params.update)) return void 0;
936
+ if (!isRecord(params) || params.sessionId !== sessionId || !isRecord(params.update)) return void 0;
782
937
  const update = params.update;
783
- if (update.sessionUpdate === "agent_message_chunk" && isRecord2(update.content) && update.content.type === "text" && typeof update.content.text === "string") {
938
+ if (update.sessionUpdate === "agent_message_chunk" && isRecord(update.content) && update.content.type === "text" && typeof update.content.text === "string") {
784
939
  return { type: "text", content: update.content.text };
785
940
  }
786
- if (update.sessionUpdate === "agent_thought_chunk" && isRecord2(update.content) && update.content.type === "text" && typeof update.content.text === "string") {
941
+ if (update.sessionUpdate === "agent_thought_chunk" && isRecord(update.content) && update.content.type === "text" && typeof update.content.text === "string") {
787
942
  return { type: "reasoning", content: update.content.text };
788
943
  }
789
944
  if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") {
@@ -806,14 +961,14 @@ async function* runAcp(request, signal, options) {
806
961
  clientInfo: options.clientInfo ?? { name: "@agentskit/adapters", version: "0.16.0" }
807
962
  }), signal);
808
963
  const initialize = await nextAcpResponse(iterator, initializeId);
809
- if (!isRecord2(initialize.result) || initialize.result.protocolVersion !== 1) throw cliError("ACP agent did not negotiate protocol version 1");
964
+ if (!isRecord(initialize.result) || initialize.result.protocolVersion !== 1) throw cliError("ACP agent did not negotiate protocol version 1");
810
965
  const sessionIdRequest = nextId();
811
966
  await abortableWrite(handle, acpRequest(sessionIdRequest, "session/new", {
812
967
  cwd: options.cwd ?? process.cwd(),
813
968
  mcpServers: []
814
969
  }), signal);
815
970
  const sessionResponse = await nextAcpResponse(iterator, sessionIdRequest);
816
- if (!isRecord2(sessionResponse.result) || typeof sessionResponse.result.sessionId !== "string") throw cliError("ACP session/new response has no sessionId");
971
+ if (!isRecord(sessionResponse.result) || typeof sessionResponse.result.sessionId !== "string") throw cliError("ACP session/new response has no sessionId");
817
972
  const sessionId = sessionResponse.result.sessionId;
818
973
  const promptId = nextId();
819
974
  await abortableWrite(handle, acpRequest(promptId, "session/prompt", {
@@ -826,7 +981,7 @@ async function* runAcp(request, signal, options) {
826
981
  if (next.done) throw cliError("ACP process closed before session/prompt completed");
827
982
  if (next.value.kind === "response" && next.value.id === promptId) {
828
983
  if (next.value.error) throw cliError(`ACP prompt failed: ${next.value.error.message ?? "unknown error"}`);
829
- if (!isRecord2(next.value.result) || next.value.result.stopReason !== "end_turn") throw cliError("ACP prompt did not complete with end_turn");
984
+ if (!isRecord(next.value.result) || next.value.result.stopReason !== "end_turn") throw cliError("ACP prompt did not complete with end_turn");
830
985
  break;
831
986
  }
832
987
  if (next.value.kind === "request") throw cliError(`ACP agent request is unsupported in this adapter: ${next.value.method}`);
@@ -871,6 +1026,8 @@ function createAcpCliAdapter(options) {
871
1026
  }, (request, signal) => runAcp(request, signal, options));
872
1027
  }
873
1028
 
1029
+ exports.buildCliSystemPrompt = buildCliSystemPrompt;
1030
+ exports.claudeCodeRequestArgs = claudeCodeRequestArgs;
874
1031
  exports.createAcpCliAdapter = createAcpCliAdapter;
875
1032
  exports.createCliAdapter = createCliAdapter;
876
1033
  exports.createJsonCliAdapter = createJsonCliAdapter;
@@ -879,8 +1036,12 @@ exports.diagnoseCliProviderManifest = diagnoseCliProviderManifest;
879
1036
  exports.getCliProviderManifest = getCliProviderManifest;
880
1037
  exports.listCliProviderManifests = listCliProviderManifests;
881
1038
  exports.manifestCapabilities = manifestCapabilities;
882
- exports.parseCliJsonResponse = standardJsonParser;
1039
+ exports.parseClaudeCodeJsonOutput = parseClaudeCodeJsonOutput;
1040
+ exports.parseClaudeCodeJsonResponse = parseClaudeCodeJsonResponse;
1041
+ exports.parseCliJsonResponse = parseCliJsonResponse;
883
1042
  exports.resolveCliManifest = resolveCliManifest;
1043
+ exports.serializeCliMessages = serializeCliMessages;
1044
+ exports.serializeCliPrompt = serializeCliPrompt;
884
1045
  exports.validateCliProviderManifest = validateCliProviderManifest;
885
1046
  //# sourceMappingURL=index.cjs.map
886
1047
  //# sourceMappingURL=index.cjs.map