@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 +129 -2
- package/dist/cli/index.cjs +220 -59
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +146 -11
- package/dist/cli/index.d.ts +146 -11
- package/dist/cli/index.js +214 -59
- package/dist/cli/index.js.map +1 -1
- package/dist/langchain-bridge.cjs +232 -0
- package/dist/langchain-bridge.cjs.map +1 -0
- package/dist/langchain-bridge.d.cts +77 -0
- package/dist/langchain-bridge.d.ts +77 -0
- package/dist/langchain-bridge.js +229 -0
- package/dist/langchain-bridge.js.map +1 -0
- package/package.json +18 -6
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
|
|
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`.
|
package/dist/cli/index.cjs
CHANGED
|
@@ -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
|
|
626
|
+
function isRecord3(value) {
|
|
447
627
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
448
628
|
}
|
|
449
629
|
function validateCapabilities(manifest) {
|
|
450
|
-
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
|
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 ??
|
|
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 (!
|
|
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 && !
|
|
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 (!
|
|
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" &&
|
|
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" &&
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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.
|
|
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
|