genaicode 2.1.0 → 2.4.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 +45 -0
- package/README.md +150 -35
- package/dist/agents/cli-agent.d.ts +28 -0
- package/dist/agents/cli-agent.js +57 -0
- package/dist/agents/cli-agent.js.map +1 -0
- package/dist/agents/discovery.d.ts +10 -0
- package/dist/agents/discovery.js +31 -0
- package/dist/agents/discovery.js.map +1 -0
- package/dist/agents/drivers/claude.d.ts +25 -0
- package/dist/agents/drivers/claude.js +184 -0
- package/dist/agents/drivers/claude.js.map +1 -0
- package/dist/agents/drivers/codex-live.d.ts +17 -0
- package/dist/agents/drivers/codex-live.js +182 -0
- package/dist/agents/drivers/codex-live.js.map +1 -0
- package/dist/agents/drivers/codex.d.ts +27 -0
- package/dist/agents/drivers/codex.js +176 -0
- package/dist/agents/drivers/codex.js.map +1 -0
- package/dist/agents/drivers/copilot.d.ts +27 -0
- package/dist/agents/drivers/copilot.js +199 -0
- package/dist/agents/drivers/copilot.js.map +1 -0
- package/dist/agents/drivers/cursor.d.ts +23 -0
- package/dist/agents/drivers/cursor.js +174 -0
- package/dist/agents/drivers/cursor.js.map +1 -0
- package/dist/agents/drivers/gemini.d.ts +18 -0
- package/dist/agents/drivers/gemini.js +118 -0
- package/dist/agents/drivers/gemini.js.map +1 -0
- package/dist/agents/drivers/json.d.ts +6 -0
- package/dist/agents/drivers/json.js +20 -0
- package/dist/agents/drivers/json.js.map +1 -0
- package/dist/agents/drivers/muse-live.d.ts +17 -0
- package/dist/agents/drivers/muse-live.js +112 -0
- package/dist/agents/drivers/muse-live.js.map +1 -0
- package/dist/agents/drivers/muse.d.ts +12 -0
- package/dist/agents/drivers/muse.js +87 -0
- package/dist/agents/drivers/muse.js.map +1 -0
- package/dist/agents/drivers/opencode.d.ts +17 -0
- package/dist/agents/drivers/opencode.js +103 -0
- package/dist/agents/drivers/opencode.js.map +1 -0
- package/dist/agents/env.d.ts +16 -0
- package/dist/agents/env.js +46 -0
- package/dist/agents/env.js.map +1 -0
- package/dist/agents/event-queue.d.ts +44 -0
- package/dist/agents/event-queue.js +123 -0
- package/dist/agents/event-queue.js.map +1 -0
- package/dist/agents/hosted.d.ts +33 -0
- package/dist/agents/hosted.js +129 -0
- package/dist/agents/hosted.js.map +1 -0
- package/dist/agents/live-agent.d.ts +37 -0
- package/dist/agents/live-agent.js +142 -0
- package/dist/agents/live-agent.js.map +1 -0
- package/dist/agents/prepare.d.ts +33 -0
- package/dist/agents/prepare.js +52 -0
- package/dist/agents/prepare.js.map +1 -0
- package/dist/agents/process.d.ts +39 -0
- package/dist/agents/process.js +140 -0
- package/dist/agents/process.js.map +1 -0
- package/dist/agents/rpc.d.ts +29 -0
- package/dist/agents/rpc.js +101 -0
- package/dist/agents/rpc.js.map +1 -0
- package/dist/agents/runtime.d.ts +26 -0
- package/dist/agents/runtime.js +97 -0
- package/dist/agents/runtime.js.map +1 -0
- package/dist/agents/types.d.ts +158 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/agents/verify.d.ts +36 -0
- package/dist/agents/verify.js +43 -0
- package/dist/agents/verify.js.map +1 -0
- package/dist/agents.d.ts +36 -0
- package/dist/agents.js +19 -0
- package/dist/agents.js.map +1 -0
- package/dist/core/client.d.ts +7 -1
- package/dist/core/client.js +18 -3
- package/dist/core/client.js.map +1 -1
- package/dist/core/middleware.js +2 -0
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/types.d.ts +51 -0
- package/dist/providers/anthropic-converter.js +32 -6
- package/dist/providers/anthropic-converter.js.map +1 -1
- package/dist/providers/anthropic.js +3 -1
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/google-converter.js +70 -3
- package/dist/providers/google-converter.js.map +1 -1
- package/dist/providers/google.d.ts +8 -0
- package/dist/providers/google.js +3 -0
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/openai-converter.d.ts +2 -0
- package/dist/providers/openai-converter.js +16 -0
- package/dist/providers/openai-converter.js.map +1 -1
- package/dist/providers/openai.js +1 -0
- package/dist/providers/openai.js.map +1 -1
- package/docs/agents.md +269 -0
- package/docs/pivot.md +8 -1
- package/docs/semver.md +6 -0
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `genaicode/agents` subpath (experimental): run installed coding-agent CLIs behind one
|
|
8
|
+
`CodingAgent` interface. Drivers `claude()`, `codex()`, `muse()`; agent-neutral
|
|
9
|
+
`AgentEvent` stream and `AgentResult`; abort, timeout and process-group kill;
|
|
10
|
+
`detectAgents` / `findExecutable`; `cliAgent()` for custom drivers. See
|
|
11
|
+
[docs/agents.md](docs/agents.md).
|
|
12
|
+
- Live agent sessions over JSON-RPC: `codexLive()` (`codex app-server`) and `museLive()`
|
|
13
|
+
(`muse serve`) with `AgentRun.steer()` and `AgentTask.onApproval`; `liveAgent()` and
|
|
14
|
+
`RpcPeer` for custom live drivers; `approval-resolved` event.
|
|
15
|
+
- `AgentTask.mcpServers` (HTTP and stdio) for `claude`, `codex` and `codexLive`; header
|
|
16
|
+
secrets stay out of argv. Drivers can set up and clean up per task via `prepare`.
|
|
17
|
+
- `scrubEnv`, `withoutProviderCredentials` and `PROVIDER_CREDENTIAL_VARS` for child
|
|
18
|
+
environments; `runWithVerify` verify-and-repair loop.
|
|
19
|
+
- Drivers `gemini()`, `cursor()` and `opencode()`; `hostedAgent()` adapts a
|
|
20
|
+
`HostedAgentProvider` (start / poll / send / cancel) to `CodingAgent`.
|
|
21
|
+
- Driver `copilot()` for GitHub Copilot CLI (`--output-format json`), with MCP servers via
|
|
22
|
+
`--additional-mcp-config` and `allowTools` / `denyTools` permission patterns.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
|
|
26
|
+
- The core `genaicode` entry is unchanged and still spawns nothing.
|
|
27
|
+
|
|
28
|
+
## 2.2.0 — 2026-07-26
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Portable `responseFormat` on `GenerationRequest` / request builders
|
|
33
|
+
(`text` | `json` | `json_schema`), mapped by OpenAI and Google adapters
|
|
34
|
+
- Portable `thinking` controls (`false` | `{ budgetTokens?, level? }`), mapped by
|
|
35
|
+
Anthropic (budget / disable) and Google (budget or level)
|
|
36
|
+
- `.json()` sets `responseFormat: { type: 'json' }` when no format was already chosen
|
|
37
|
+
- Capability flags: `jsonResponse`, `thinking`
|
|
38
|
+
|
|
39
|
+
### Notes
|
|
40
|
+
|
|
41
|
+
- Additive only; provider `generationConfig` / Anthropic `thinking` factory options still
|
|
42
|
+
work as escape hatches for vendor-only knobs.
|
|
43
|
+
- On Google, `thinking: false` and `budgetTokens: 0` map to `thinkingLevel: MINIMAL`
|
|
44
|
+
(Gemini 3 rejects `thinkingBudget: 0`). JSON `responseFormat` also defaults Google
|
|
45
|
+
thinking to `MINIMAL` when unset, so small token budgets are not spent only on thoughts.
|
|
46
|
+
- Provider E2E covers JSON response format and thinking knobs when credentials are set.
|
|
47
|
+
|
|
3
48
|
## 2.1.0 — 2026-07-25
|
|
4
49
|
|
|
5
50
|
Publishes the Phase 3–4 work already on `master`. npm `2.0.0` shipped the 2.0
|
package/README.md
CHANGED
|
@@ -12,38 +12,44 @@ GenAIcode is a small TypeScript toolkit for using LLMs in backend code.
|
|
|
12
12
|
|
|
13
13
|
It sits between raw provider SDKs and full agent frameworks: one prompt representation,
|
|
14
14
|
thin provider adapters, a convenient request API, and lightweight conversation chains.
|
|
15
|
-
|
|
15
|
+
The core does not inspect repositories, execute shell commands, edit files, or run an agent UI.
|
|
16
|
+
The opt-in [`genaicode/agents`](#coding-agents) subpath drives the coding-agent CLIs you
|
|
17
|
+
already have (Claude Code, Codex, GitHub Copilot CLI, Cursor, Gemini CLI, opencode, Muse)
|
|
18
|
+
behind one task and event API, so you can orchestrate them from backend code.
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
**Like jQuery**, the common case starts with one small function and becomes more specific
|
|
21
|
+
through chaining—configure a request, follow up across multiple prompts, and keep history
|
|
22
|
+
portable without adopting a full agent framework.
|
|
18
23
|
|
|
19
|
-
GenAIcode 1.x was a coding agent
|
|
20
|
-
|
|
21
|
-
conversation chains, and plugins.
|
|
24
|
+
GenAIcode 1.x was a coding agent; see [Migration from 1.x](#migration-from-1x) if you need
|
|
25
|
+
the old product or the 2.0 scope decisions.
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
orchestration are not deprecated compatibility features; they have been removed from
|
|
25
|
-
2.0. The GenAIcode name, `PromptItem` model, provider converters, and extensibility
|
|
26
|
-
continue here in a smaller and more focused form.
|
|
27
|
-
|
|
28
|
-
The original coding agent remains available from the preserved
|
|
29
|
-
[`1.x` branch](https://github.com/gtanczyk/genaicode/tree/1.x) and the 1.x npm releases:
|
|
27
|
+
## Install
|
|
30
28
|
|
|
31
29
|
```bash
|
|
32
|
-
|
|
30
|
+
npm install genaicode
|
|
33
31
|
```
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
starting the old agent.
|
|
33
|
+
Node.js 20 or newer is required.
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
## Why this vs a raw SDK
|
|
39
36
|
|
|
40
|
-
|
|
37
|
+
Provider SDKs are the right tool when you call one vendor and own every request shape
|
|
38
|
+
yourself. The cost shows up when the same backend needs a second provider, a test double,
|
|
39
|
+
or shared policy (timeouts, JSON parsing, rate limits, fallback) without copying glue.
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
npm install genaicode
|
|
44
|
-
```
|
|
41
|
+
GenAIcode is that thin shared layer:
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
- One portable prompt/tool IR (`PromptItem`) instead of OpenAI messages vs Anthropic
|
|
44
|
+
blocks vs Gemini contents.
|
|
45
|
+
- One request API (`.text()`, `.json()`, `.stream()`, chains) over those adapters.
|
|
46
|
+
- A tiny `ModelProvider` seam so tests and custom gateways do not mock vendor HTTP.
|
|
47
|
+
- Opt-in middleware and retry helpers—no hidden tool runners, no silent retries, no
|
|
48
|
+
agent loop.
|
|
49
|
+
|
|
50
|
+
If you are happy importing one SDK and never swapping models or providers, stay on the
|
|
51
|
+
SDK. If you want the call site to look the same while the edge stays replaceable, use
|
|
52
|
+
GenAIcode.
|
|
47
53
|
|
|
48
54
|
## A prompt in three lines
|
|
49
55
|
|
|
@@ -55,8 +61,9 @@ const ai = genaicode(openai({ model: 'your-model-name' }));
|
|
|
55
61
|
const answer = await ai('Explain why the sky is blue in two sentences.').text();
|
|
56
62
|
```
|
|
57
63
|
|
|
58
|
-
The client is callable on purpose.
|
|
59
|
-
|
|
64
|
+
The client is callable on purpose. Configuration is ordinary method chaining—system
|
|
65
|
+
instructions, temperature, token limits, and more—without a separate options object or
|
|
66
|
+
framework setup:
|
|
60
67
|
|
|
61
68
|
```ts
|
|
62
69
|
const result = await ai('Create a release note from these commits')
|
|
@@ -66,12 +73,68 @@ const result = await ai('Create a release note from these commits')
|
|
|
66
73
|
.text();
|
|
67
74
|
```
|
|
68
75
|
|
|
69
|
-
Builders are immutable, so a configured base request can be safely reused
|
|
76
|
+
Builders are immutable, so a configured base request can be safely reused:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const releaseNote = ai('Create a release note from these commits')
|
|
80
|
+
.system('You are a concise technical writer.')
|
|
81
|
+
.temperature(0.2);
|
|
82
|
+
|
|
83
|
+
const short = await releaseNote.maxOutputTokens(200).text();
|
|
84
|
+
const long = await releaseNote.maxOutputTokens(800).text();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Coding agents
|
|
88
|
+
|
|
89
|
+
`genaicode/agents` turns the coding-agent CLIs installed on a machine into one API. Give
|
|
90
|
+
any of them a task (a prompt and a working directory) and get back the same event stream
|
|
91
|
+
and result, whichever agent does the work. It is how you script, compare, or chain coding
|
|
92
|
+
agents from a job, a bot, or a CI step without writing a parser per vendor.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { claude, codex, copilot, cursor, detectAgents } from 'genaicode/agents';
|
|
96
|
+
|
|
97
|
+
const [found] = detectAgents([claude(), codex(), copilot(), cursor()]).filter(({ path }) => path);
|
|
98
|
+
if (!found) throw new Error('No coding agent installed.');
|
|
99
|
+
|
|
100
|
+
const run = found.agent.run({ prompt: 'Add a unit test for parseDate', cwd: '/path/to/repo', timeoutMs: 20 * 60_000 });
|
|
101
|
+
for await (const event of run) {
|
|
102
|
+
if (event.type === 'tool-start') console.log('⚙', event.name);
|
|
103
|
+
if (event.type === 'file-change') console.log('edited', event.paths.join(', '));
|
|
104
|
+
}
|
|
105
|
+
const result = await run.result; // { status, ok, text, sessionId, usage, error, ... }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| Agent | Driver | Extras |
|
|
109
|
+
| ---------------- | ----------------------------- | ------------------------------------------ |
|
|
110
|
+
| Claude Code | `claude()` | MCP servers, max turns, effort, cost |
|
|
111
|
+
| Codex | `codex()`, `codexLive()` | MCP servers; live: `steer()`, approvals |
|
|
112
|
+
| GitHub Copilot | `copilot()` | MCP servers, allow/deny tool patterns |
|
|
113
|
+
| Cursor | `cursor()` | streamed text deltas |
|
|
114
|
+
| Gemini CLI | `gemini()` | approval mode |
|
|
115
|
+
| opencode | `opencode()` | `provider/model` ids, variants |
|
|
116
|
+
| Muse | `muse()`, `museLive()` | max turns; live: `steer()` |
|
|
117
|
+
| Your own CLI/API | `cliAgent()`, `hostedAgent()` | plug in any JSON-lines CLI or hosted agent |
|
|
118
|
+
|
|
119
|
+
On top of the drivers:
|
|
120
|
+
|
|
121
|
+
- **Steer a running task** with `run.steer(text)`, and answer permission prompts with
|
|
122
|
+
`onApproval` (live drivers).
|
|
123
|
+
- **Attach MCP servers per task** with `mcpServers`. Header secrets stay out of argv.
|
|
124
|
+
- **Verify and repair**: `runWithVerify` runs your check (tests, lint) after the agent and
|
|
125
|
+
sends failures back for another attempt.
|
|
126
|
+
- **Keep credentials apart**: `withoutProviderCredentials()` strips your app's LLM API keys
|
|
127
|
+
from the agent's environment, so it bills its own login.
|
|
128
|
+
|
|
129
|
+
Importing `genaicode` never spawns anything; only `genaicode/agents` does. The agent uses
|
|
130
|
+
its own credentials, billing, and permission settings. GenAIcode does not sandbox it,
|
|
131
|
+
choose a model, or retry it. See [docs/agents.md](docs/agents.md) for events, options per
|
|
132
|
+
driver, and writing your own driver.
|
|
70
133
|
|
|
71
134
|
## Chaining prompts
|
|
72
135
|
|
|
73
136
|
A chain remembers successful user and assistant turns. Each new prompt sees the complete
|
|
74
|
-
history:
|
|
137
|
+
history, so multi-step work stays in ordinary application code:
|
|
75
138
|
|
|
76
139
|
```ts
|
|
77
140
|
import { system } from 'genaicode';
|
|
@@ -145,6 +208,37 @@ const value = await ai('Return {"count": 3}.').json({
|
|
|
145
208
|
});
|
|
146
209
|
```
|
|
147
210
|
|
|
211
|
+
Calling `.json()` also sets `responseFormat: { type: 'json' }` on the request when you have
|
|
212
|
+
not already chosen a format, so providers that support JSON mode (OpenAI, Gemini/Vertex)
|
|
213
|
+
are asked for JSON rather than free text.
|
|
214
|
+
|
|
215
|
+
## Response format and thinking
|
|
216
|
+
|
|
217
|
+
Portable request fields cover the two knobs backends usually poke through provider-specific
|
|
218
|
+
config:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
const verdict = await ai(promptText)
|
|
222
|
+
.responseFormat({ type: 'json' })
|
|
223
|
+
.thinking({ level: 'minimal' }) // or { budgetTokens: 0 } / false to disable
|
|
224
|
+
.temperature(0)
|
|
225
|
+
.json((value) => VerdictSchema.parse(value));
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- `responseFormat`: `{ type: 'text' | 'json' }` or
|
|
229
|
+
`{ type: 'json_schema', name, schema, strict? }`.
|
|
230
|
+
- `thinking`: `false` to disable, or `{ budgetTokens?, level? }` (`minimal` |
|
|
231
|
+
`low` | `medium` | `high`). Prefer one of budget or level — some providers reject both.
|
|
232
|
+
On Google/Gemini, `false` and `budgetTokens: 0` map to `thinkingLevel: MINIMAL`
|
|
233
|
+
(or `LOW` for models like Gemini 3.7 and Gemini 3 Pro where `MINIMAL` is unsupported)
|
|
234
|
+
because Gemini 3 rejects `thinkingBudget: 0`. JSON `responseFormat` without an
|
|
235
|
+
explicit `thinking` setting or provider `generationConfig.thinkingConfig` default also
|
|
236
|
+
defaults Google thinking to `MINIMAL` (or `LOW` on Gemini 3.7 / Pro).
|
|
237
|
+
|
|
238
|
+
Providers map what they support and ignore the rest. `ProviderCapabilities.jsonResponse`
|
|
239
|
+
and `ProviderCapabilities.thinking` advertise support. Vendor-specific escapes such as
|
|
240
|
+
Vertex `generationConfig` remain available for anything not covered here.
|
|
241
|
+
|
|
148
242
|
## PromptItem: the portable prompt IR
|
|
149
243
|
|
|
150
244
|
`PromptItem` is GenAIcode's provider-neutral intermediate representation:
|
|
@@ -226,13 +320,7 @@ Hook-style plugins remain the extension point. Built-in helpers cover common bac
|
|
|
226
320
|
needs without restoring a global registry:
|
|
227
321
|
|
|
228
322
|
```ts
|
|
229
|
-
import {
|
|
230
|
-
cachePlugin,
|
|
231
|
-
fallbackPlugin,
|
|
232
|
-
genaicode,
|
|
233
|
-
rateLimitPlugin,
|
|
234
|
-
timingPlugin,
|
|
235
|
-
} from 'genaicode';
|
|
323
|
+
import { cachePlugin, fallbackPlugin, genaicode, rateLimitPlugin, timingPlugin } from 'genaicode';
|
|
236
324
|
import { anthropic, openai } from 'genaicode/providers';
|
|
237
325
|
|
|
238
326
|
const ai = genaicode(openai({ model: 'your-model-name' }), {
|
|
@@ -341,6 +429,9 @@ are set:
|
|
|
341
429
|
- Anthropic: `ANTHROPIC_API_KEY`, `ANTHROPIC_MODEL`
|
|
342
430
|
- Gemini: `GEMINI_API_KEY`, `GEMINI_MODEL`
|
|
343
431
|
|
|
432
|
+
Beyond the smoke call, E2E also covers portable `responseFormat: { type: 'json' }`
|
|
433
|
+
(OpenAI, Gemini) and `thinking` (Anthropic disable; Gemini disable / `level: 'minimal'`).
|
|
434
|
+
|
|
344
435
|
CI/CD is configured in `.github/workflows/provider-e2e.yaml`. Each provider runs in its own
|
|
345
436
|
job and only starts when both required secrets are configured in GitHub Actions.
|
|
346
437
|
|
|
@@ -397,13 +488,37 @@ Framework-shaped examples live under `examples/` (`http-handler`, `queue-worker`
|
|
|
397
488
|
|
|
398
489
|
## Design boundaries
|
|
399
490
|
|
|
400
|
-
- Backend library, not a coding agent.
|
|
491
|
+
- Backend library, not a coding agent. `genaicode/agents` drives external agent CLIs; it
|
|
492
|
+
adds no agent loop, prompts, or repository tools of its own.
|
|
401
493
|
- Provider-neutral core with no global configuration.
|
|
402
494
|
- Explicit models and credentials; environment variables are only provider defaults.
|
|
403
495
|
- Immutable request builders.
|
|
404
496
|
- Conversation history is explicit; loops remain ordinary application code.
|
|
405
497
|
- No hidden tool execution or hidden retries.
|
|
406
498
|
- Third-party providers and middleware use stable TypeScript contracts.
|
|
407
|
-
- Provider SDKs stay behind the `genaicode/providers` subpath
|
|
499
|
+
- Provider SDKs stay behind the `genaicode/providers` subpath; process spawning stays
|
|
500
|
+
behind `genaicode/agents`.
|
|
501
|
+
|
|
502
|
+
## Migration from 1.x
|
|
503
|
+
|
|
504
|
+
GenAIcode 1.x was a coding agent. Version 2.0 deliberately replaces that product with a
|
|
505
|
+
small backend LLM toolkit: a jQuery-like layer for portable prompts, provider adapters,
|
|
506
|
+
conversation chains, and plugins.
|
|
507
|
+
|
|
508
|
+
The coding-agent CLI, browser UI, repository tools, shell execution, and agent
|
|
509
|
+
orchestration are not deprecated compatibility features; they have been removed from
|
|
510
|
+
2.0. The GenAIcode name, `PromptItem` model, provider converters, and extensibility
|
|
511
|
+
continue here in a smaller and more focused form.
|
|
512
|
+
|
|
513
|
+
The original coding agent remains available from the preserved
|
|
514
|
+
[`1.x` branch](https://github.com/gtanczyk/genaicode/tree/1.x) and the 1.x npm releases:
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
npx genaicode@1
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Running `npx genaicode` with version 2.x prints this migration guidance instead of
|
|
521
|
+
starting the old agent.
|
|
408
522
|
|
|
409
|
-
See [the pivot plan](docs/pivot.md) for
|
|
523
|
+
See [the pivot plan](docs/pivot.md) for the full scope, migration decisions, and the
|
|
524
|
+
roadmap.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type PreparedRun } from './prepare.js';
|
|
2
|
+
import { type AgentOutcome } from './runtime.js';
|
|
3
|
+
import type { AgentCapabilities, AgentEvent, AgentTask, CodingAgent } from './types.js';
|
|
4
|
+
export type { AgentOutcome } from './runtime.js';
|
|
5
|
+
/** Per-run decoder for one vendor's JSON event stream. */
|
|
6
|
+
export interface AgentOutputParser {
|
|
7
|
+
/** Map one parsed stdout JSON value to zero or more events. */
|
|
8
|
+
event(value: unknown): AgentEvent[];
|
|
9
|
+
/** The terminal verdict the agent itself reported, if it reported one. */
|
|
10
|
+
outcome?(): AgentOutcome | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface CliAgentDefinition {
|
|
13
|
+
name: string;
|
|
14
|
+
command: string;
|
|
15
|
+
capabilities?: AgentCapabilities;
|
|
16
|
+
/** Full argument list for a task, prompt included. */
|
|
17
|
+
args(task: AgentTask): string[];
|
|
18
|
+
/** Replaces `args` when a task needs setup: extra env, temp files to clean up. */
|
|
19
|
+
prepare?(task: AgentTask): PreparedRun;
|
|
20
|
+
createParser(): AgentOutputParser;
|
|
21
|
+
/** Adjust the child environment (the caller's `task.env` or `process.env`). */
|
|
22
|
+
env?(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build a `CodingAgent` from a headless CLI: spawn, decode its JSON lines, and
|
|
26
|
+
* fold them into an `AgentResult`. Drivers such as `claude()` are thin definitions.
|
|
27
|
+
*/
|
|
28
|
+
export declare function cliAgent(definition: CliAgentDefinition): CodingAgent;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { planSpawn } from './prepare.js';
|
|
2
|
+
import { startProcess } from './process.js';
|
|
3
|
+
import { invalidCwd, RunRecorder } from './runtime.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build a `CodingAgent` from a headless CLI: spawn, decode its JSON lines, and
|
|
6
|
+
* fold them into an `AgentResult`. Drivers such as `claude()` are thin definitions.
|
|
7
|
+
*/
|
|
8
|
+
export function cliAgent(definition) {
|
|
9
|
+
return {
|
|
10
|
+
name: definition.name,
|
|
11
|
+
command: definition.command,
|
|
12
|
+
capabilities: definition.capabilities ?? {},
|
|
13
|
+
run: (task) => runCliAgent(definition, task),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function runCliAgent(definition, task) {
|
|
17
|
+
const recorder = new RunRecorder(definition.name, definition.command);
|
|
18
|
+
const iterate = () => recorder.events.iterate();
|
|
19
|
+
const plan = planSpawn(definition, task, invalidCwd(task.cwd));
|
|
20
|
+
if (!plan.ok) {
|
|
21
|
+
const result = recorder.finish({ exitCode: null, signal: null, reason: 'spawn-error', error: new Error(plan.error) }, undefined);
|
|
22
|
+
return { result: Promise.resolve(result), abort() { }, [Symbol.asyncIterator]: iterate };
|
|
23
|
+
}
|
|
24
|
+
const parser = definition.createParser();
|
|
25
|
+
const handle = startProcess({
|
|
26
|
+
command: definition.command,
|
|
27
|
+
args: plan.args,
|
|
28
|
+
cwd: task.cwd,
|
|
29
|
+
env: plan.env,
|
|
30
|
+
timeoutMs: task.timeoutMs,
|
|
31
|
+
signal: task.signal,
|
|
32
|
+
onLine(line) {
|
|
33
|
+
let value;
|
|
34
|
+
try {
|
|
35
|
+
value = JSON.parse(line);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
recorder.emit({ type: 'raw', line });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const event of parser.event(value))
|
|
42
|
+
recorder.emit(event);
|
|
43
|
+
},
|
|
44
|
+
onStderr: (text) => recorder.emit({ type: 'stderr', text }),
|
|
45
|
+
});
|
|
46
|
+
// A consumer that falls behind pauses the agent instead of buffering its output without bound.
|
|
47
|
+
recorder.events.onPressure = (paused) => (paused ? handle.pause() : handle.resume());
|
|
48
|
+
return {
|
|
49
|
+
result: handle.exit.then((exit) => {
|
|
50
|
+
plan.cleanup();
|
|
51
|
+
return recorder.finish(exit, parser.outcome?.());
|
|
52
|
+
}),
|
|
53
|
+
abort: handle.kill,
|
|
54
|
+
[Symbol.asyncIterator]: iterate,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=cli-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-agent.js","sourceRoot":"","sources":["../../src/agents/cli-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CodingAgent } from './types.js';
|
|
2
|
+
/** Resolve an executable the way a shell would: absolute path, or the first match on PATH. */
|
|
3
|
+
export declare function findExecutable(command: string, env?: NodeJS.ProcessEnv): string | null;
|
|
4
|
+
export interface AgentAvailability {
|
|
5
|
+
agent: CodingAgent;
|
|
6
|
+
/** Resolved executable, or null when the agent's command is not installed. */
|
|
7
|
+
path: string | null;
|
|
8
|
+
}
|
|
9
|
+
/** Report which agents have their CLI installed. Presence only; login and flags are the agent's concern. */
|
|
10
|
+
export declare function detectAgents(agents: readonly CodingAgent[], env?: NodeJS.ProcessEnv): AgentAvailability[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from 'node:fs';
|
|
2
|
+
import { delimiter, isAbsolute, join } from 'node:path';
|
|
3
|
+
/** Resolve an executable the way a shell would: absolute path, or the first match on PATH. */
|
|
4
|
+
export function findExecutable(command, env = process.env) {
|
|
5
|
+
const windows = process.platform === 'win32';
|
|
6
|
+
const extensions = windows ? ['', ...(env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';')] : [''];
|
|
7
|
+
const candidates = isAbsolute(command)
|
|
8
|
+
? extensions.map((extension) => command + extension)
|
|
9
|
+
: (env.PATH ?? env.Path ?? '')
|
|
10
|
+
.split(delimiter)
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.flatMap((dir) => extensions.map((extension) => join(dir, command + extension)));
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
try {
|
|
15
|
+
if (!statSync(candidate).isFile())
|
|
16
|
+
continue;
|
|
17
|
+
if (!windows)
|
|
18
|
+
accessSync(candidate, constants.X_OK);
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** Report which agents have their CLI installed. Presence only; login and flags are the agent's concern. */
|
|
28
|
+
export function detectAgents(agents, env = process.env) {
|
|
29
|
+
return agents.map((agent) => ({ agent, path: findExecutable(agent.command, env) }));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/agents/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGxD,8FAA8F;AAC9F,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,MAAyB,OAAO,CAAC,GAAG;IAClF,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,qBAAqB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/F,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC;QACpC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,OAAO,GAAG,SAAS,CAAC;QACpD,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;aACzB,KAAK,CAAC,SAAS,CAAC;aAChB,MAAM,CAAC,OAAO,CAAC;aACf,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACvF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE;gBAAE,SAAS;YAC5C,IAAI,CAAC,OAAO;gBAAE,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;YACpD,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAQD,4GAA4G;AAC5G,MAAM,UAAU,YAAY,CAC1B,MAA8B,EAC9B,MAAyB,OAAO,CAAC,GAAG;IAEpC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AACtF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type AgentOutputParser } from '../cli-agent.js';
|
|
2
|
+
import type { AgentTask, CodingAgent, McpServer } from '../types.js';
|
|
3
|
+
export type ClaudePermissionMode = 'acceptEdits' | 'auto' | 'bypassPermissions' | 'dontAsk' | 'manual' | 'plan' | (string & Record<never, never>);
|
|
4
|
+
export interface ClaudeAgentOptions {
|
|
5
|
+
/** Executable name or path. Default `claude`. */
|
|
6
|
+
command?: string;
|
|
7
|
+
/** Default `acceptEdits`: file edits run unattended, other tools follow Claude Code settings. */
|
|
8
|
+
permissionMode?: ClaudePermissionMode;
|
|
9
|
+
/** Passed as `--allowedTools`. */
|
|
10
|
+
allowedTools?: readonly string[];
|
|
11
|
+
/** Passed as `--disallowedTools`. */
|
|
12
|
+
disallowedTools?: readonly string[];
|
|
13
|
+
/** With `task.mcpServers`: ignore MCP servers from the user's own Claude Code config. Default false. */
|
|
14
|
+
strictMcpConfig?: boolean;
|
|
15
|
+
/** With `task.mcpServers`: pre-allow every tool of those servers. Default true (headless runs cannot ask). */
|
|
16
|
+
allowMcpTools?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Claude Code in print mode (`claude -p --output-format stream-json`). */
|
|
19
|
+
export declare function claude(options?: ClaudeAgentOptions): CodingAgent;
|
|
20
|
+
/** The `--mcp-config` document for a set of servers. */
|
|
21
|
+
export declare function claudeMcpConfig(servers: readonly McpServer[]): {
|
|
22
|
+
mcpServers: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
export declare function claudeArgs(task: AgentTask, options?: ClaudeAgentOptions, mcpConfigPath?: string): string[];
|
|
25
|
+
export declare function createClaudeParser(): AgentOutputParser;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { cliAgent } from '../cli-agent.js';
|
|
5
|
+
import { isHttpServer } from '../prepare.js';
|
|
6
|
+
import { isObject, numberField, positionalPrompt, stringField } from './json.js';
|
|
7
|
+
const EDIT_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
8
|
+
/** Claude Code in print mode (`claude -p --output-format stream-json`). */
|
|
9
|
+
export function claude(options = {}) {
|
|
10
|
+
return cliAgent({
|
|
11
|
+
name: 'claude',
|
|
12
|
+
command: options.command ?? 'claude',
|
|
13
|
+
capabilities: { effort: ['low', 'medium', 'high', 'xhigh', 'max'], maxTurns: true, usage: true, mcp: true },
|
|
14
|
+
args: (task) => claudeArgs(task, options),
|
|
15
|
+
prepare: (task) => prepareClaude(task, options),
|
|
16
|
+
createParser: createClaudeParser,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** Write the task's MCP servers to a private temp file, so header secrets stay out of argv. */
|
|
20
|
+
function prepareClaude(task, options) {
|
|
21
|
+
if (!task.mcpServers?.length)
|
|
22
|
+
return { args: claudeArgs(task, options) };
|
|
23
|
+
const dir = mkdtempSync(join(tmpdir(), 'genaicode-claude-mcp-'));
|
|
24
|
+
const file = join(dir, 'mcp.json');
|
|
25
|
+
writeFileSync(file, JSON.stringify(claudeMcpConfig(task.mcpServers)), { mode: 0o600 });
|
|
26
|
+
return {
|
|
27
|
+
args: claudeArgs(task, options, file),
|
|
28
|
+
cleanup: () => rmSync(dir, { recursive: true, force: true }),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** The `--mcp-config` document for a set of servers. */
|
|
32
|
+
export function claudeMcpConfig(servers) {
|
|
33
|
+
const mcpServers = {};
|
|
34
|
+
for (const server of servers) {
|
|
35
|
+
mcpServers[server.name] = isHttpServer(server)
|
|
36
|
+
? { type: 'http', url: server.url, ...(server.headers ? { headers: server.headers } : {}) }
|
|
37
|
+
: {
|
|
38
|
+
command: server.command,
|
|
39
|
+
args: [...(server.args ?? [])],
|
|
40
|
+
...(server.env ? { env: server.env } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { mcpServers };
|
|
44
|
+
}
|
|
45
|
+
export function claudeArgs(task, options = {}, mcpConfigPath) {
|
|
46
|
+
const args = ['-p', '--verbose'];
|
|
47
|
+
// Multi-value flags come first: a single-value flag must follow them before the prompt.
|
|
48
|
+
if (mcpConfigPath)
|
|
49
|
+
args.push('--mcp-config', mcpConfigPath);
|
|
50
|
+
if (mcpConfigPath && options.strictMcpConfig)
|
|
51
|
+
args.push('--strict-mcp-config');
|
|
52
|
+
const allowed = [
|
|
53
|
+
...(options.allowedTools ?? []),
|
|
54
|
+
...(mcpConfigPath && (options.allowMcpTools ?? true) ? (task.mcpServers ?? []).map((s) => `mcp__${s.name}`) : []),
|
|
55
|
+
];
|
|
56
|
+
if (allowed.length)
|
|
57
|
+
args.push('--allowedTools', allowed.join(','));
|
|
58
|
+
if (options.disallowedTools?.length)
|
|
59
|
+
args.push('--disallowedTools', options.disallowedTools.join(','));
|
|
60
|
+
args.push('--output-format', 'stream-json', '--permission-mode', options.permissionMode ?? 'acceptEdits');
|
|
61
|
+
if (task.model)
|
|
62
|
+
args.push('--model', task.model);
|
|
63
|
+
if (task.effort)
|
|
64
|
+
args.push('--effort', task.effort);
|
|
65
|
+
if (task.maxTurns !== undefined)
|
|
66
|
+
args.push('--max-turns', String(task.maxTurns));
|
|
67
|
+
if (task.extraArgs)
|
|
68
|
+
args.push(...task.extraArgs);
|
|
69
|
+
return [...args, ...positionalPrompt(task.prompt)];
|
|
70
|
+
}
|
|
71
|
+
export function createClaudeParser() {
|
|
72
|
+
const toolNames = new Map();
|
|
73
|
+
let outcome;
|
|
74
|
+
let sessionSent = false;
|
|
75
|
+
let lastMessage;
|
|
76
|
+
return {
|
|
77
|
+
outcome: () => outcome,
|
|
78
|
+
event(value) {
|
|
79
|
+
if (!isObject(value))
|
|
80
|
+
return [];
|
|
81
|
+
const events = [];
|
|
82
|
+
const sessionId = stringField(value, 'session_id');
|
|
83
|
+
if (sessionId && !sessionSent) {
|
|
84
|
+
sessionSent = true;
|
|
85
|
+
events.push({ type: 'session', sessionId, ...modelOf(value) });
|
|
86
|
+
}
|
|
87
|
+
switch (value.type) {
|
|
88
|
+
case 'assistant':
|
|
89
|
+
for (const block of contentBlocks(value.message)) {
|
|
90
|
+
if (block.type === 'text' && typeof block.text === 'string' && block.text.trim()) {
|
|
91
|
+
lastMessage = block.text;
|
|
92
|
+
events.push({ type: 'message', text: block.text });
|
|
93
|
+
}
|
|
94
|
+
else if (block.type === 'tool_use' && typeof block.name === 'string') {
|
|
95
|
+
const id = typeof block.id === 'string' ? block.id : undefined;
|
|
96
|
+
if (id)
|
|
97
|
+
toolNames.set(id, block.name);
|
|
98
|
+
events.push({ type: 'tool-start', ...(id ? { id } : {}), name: block.name, input: block.input });
|
|
99
|
+
const path = stringField(block.input, 'file_path') ?? stringField(block.input, 'notebook_path');
|
|
100
|
+
if (EDIT_TOOLS.has(block.name) && path)
|
|
101
|
+
events.push({ type: 'file-change', paths: [path] });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case 'user':
|
|
106
|
+
for (const block of contentBlocks(value.message)) {
|
|
107
|
+
if (block.type !== 'tool_result')
|
|
108
|
+
continue;
|
|
109
|
+
const id = typeof block.tool_use_id === 'string' ? block.tool_use_id : undefined;
|
|
110
|
+
const name = id ? toolNames.get(id) : undefined;
|
|
111
|
+
const output = toolOutput(block.content);
|
|
112
|
+
events.push({
|
|
113
|
+
type: 'tool-end',
|
|
114
|
+
...(id ? { id } : {}),
|
|
115
|
+
...(name ? { name } : {}),
|
|
116
|
+
isError: block.is_error === true,
|
|
117
|
+
...(output !== undefined ? { output } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 'stream_event': {
|
|
122
|
+
const delta = isObject(value.event) ? value.event.delta : undefined;
|
|
123
|
+
const text = stringField(delta, 'text');
|
|
124
|
+
if (stringField(delta, 'type') === 'text_delta' && text)
|
|
125
|
+
events.push({ type: 'text-delta', text });
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'result': {
|
|
129
|
+
const failed = value.is_error === true || (value.subtype !== undefined && value.subtype !== 'success');
|
|
130
|
+
const text = stringField(value, 'result');
|
|
131
|
+
outcome = failed
|
|
132
|
+
? { ok: false, error: text || `Claude stopped: ${String(value.subtype ?? 'error')}.` }
|
|
133
|
+
: { ok: true };
|
|
134
|
+
const usage = value.usage;
|
|
135
|
+
if (isObject(usage)) {
|
|
136
|
+
const inputTokens = numberField(usage, 'input_tokens');
|
|
137
|
+
const outputTokens = numberField(usage, 'output_tokens');
|
|
138
|
+
const cachedInputTokens = numberField(usage, 'cache_read_input_tokens');
|
|
139
|
+
const costUsd = numberField(value, 'total_cost_usd');
|
|
140
|
+
events.push({
|
|
141
|
+
type: 'usage',
|
|
142
|
+
usage: {
|
|
143
|
+
...(inputTokens !== undefined ? { inputTokens } : {}),
|
|
144
|
+
...(outputTokens !== undefined ? { outputTokens } : {}),
|
|
145
|
+
...(cachedInputTokens !== undefined ? { cachedInputTokens } : {}),
|
|
146
|
+
...(inputTokens !== undefined && outputTokens !== undefined
|
|
147
|
+
? { totalTokens: inputTokens + outputTokens }
|
|
148
|
+
: {}),
|
|
149
|
+
},
|
|
150
|
+
...(costUsd !== undefined ? { costUsd } : {}),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// The result repeats the last assistant message; only surface it when it differs.
|
|
154
|
+
if (!failed && text && text !== lastMessage)
|
|
155
|
+
events.push({ type: 'message', text });
|
|
156
|
+
if (failed)
|
|
157
|
+
events.push({ type: 'error', message: outcome.error });
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return events;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function contentBlocks(message) {
|
|
166
|
+
const content = isObject(message) ? message.content : undefined;
|
|
167
|
+
return Array.isArray(content) ? content.filter(isObject) : [];
|
|
168
|
+
}
|
|
169
|
+
function modelOf(value) {
|
|
170
|
+
const model = stringField(value, 'model');
|
|
171
|
+
return model ? { model } : {};
|
|
172
|
+
}
|
|
173
|
+
function toolOutput(content) {
|
|
174
|
+
if (typeof content === 'string')
|
|
175
|
+
return content;
|
|
176
|
+
if (!Array.isArray(content))
|
|
177
|
+
return undefined;
|
|
178
|
+
const text = content
|
|
179
|
+
.map((part) => stringField(part, 'text'))
|
|
180
|
+
.filter((part) => part !== undefined)
|
|
181
|
+
.join('\n');
|
|
182
|
+
return text || undefined;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=claude.js.map
|