@cleocode/adapters 2026.4.47 → 2026.4.49
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/dist/cant-context.d.ts +132 -1
- package/dist/cant-context.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19765 -375
- package/dist/index.js.map +4 -4
- package/dist/providers/claude-code/adapter.d.ts +12 -6
- package/dist/providers/claude-code/adapter.d.ts.map +1 -1
- package/dist/providers/claude-code/hooks.d.ts.map +1 -1
- package/dist/providers/claude-code/spawn.d.ts.map +1 -1
- package/dist/providers/claude-sdk/index.d.ts +18 -0
- package/dist/providers/claude-sdk/index.d.ts.map +1 -0
- package/dist/providers/claude-sdk/mcp-registry.d.ts +40 -0
- package/dist/providers/claude-sdk/mcp-registry.d.ts.map +1 -0
- package/dist/providers/claude-sdk/session-store.d.ts +78 -0
- package/dist/providers/claude-sdk/session-store.d.ts.map +1 -0
- package/dist/providers/claude-sdk/spawn.d.ts +79 -0
- package/dist/providers/claude-sdk/spawn.d.ts.map +1 -0
- package/dist/providers/claude-sdk/tool-bridge.d.ts +38 -0
- package/dist/providers/claude-sdk/tool-bridge.d.ts.map +1 -0
- package/dist/providers/openai-sdk/adapter.d.ts +77 -0
- package/dist/providers/openai-sdk/adapter.d.ts.map +1 -0
- package/dist/providers/openai-sdk/guardrails.d.ts +67 -0
- package/dist/providers/openai-sdk/guardrails.d.ts.map +1 -0
- package/dist/providers/openai-sdk/handoff.d.ts +94 -0
- package/dist/providers/openai-sdk/handoff.d.ts.map +1 -0
- package/dist/providers/openai-sdk/index.d.ts +39 -0
- package/dist/providers/openai-sdk/index.d.ts.map +1 -0
- package/dist/providers/openai-sdk/install.d.ts +61 -0
- package/dist/providers/openai-sdk/install.d.ts.map +1 -0
- package/dist/providers/openai-sdk/spawn.d.ts +146 -0
- package/dist/providers/openai-sdk/spawn.d.ts.map +1 -0
- package/dist/providers/openai-sdk/tracing.d.ts +89 -0
- package/dist/providers/openai-sdk/tracing.d.ts.map +1 -0
- package/dist/providers/shared/conduit-trace-writer.d.ts +72 -0
- package/dist/providers/shared/conduit-trace-writer.d.ts.map +1 -0
- package/dist/providers/shared/sdk-result-mapper.d.ts +51 -0
- package/dist/providers/shared/sdk-result-mapper.d.ts.map +1 -0
- package/package.json +5 -3
- package/src/cant-context.ts +397 -3
- package/src/index.ts +24 -2
- package/src/providers/claude-code/adapter.ts +41 -4
- package/src/providers/claude-code/hooks.ts +7 -1
- package/src/providers/claude-code/spawn.ts +5 -1
- package/src/providers/claude-sdk/__tests__/spawn.test.ts +448 -0
- package/src/providers/claude-sdk/index.ts +18 -0
- package/src/providers/claude-sdk/mcp-registry.ts +96 -0
- package/src/providers/claude-sdk/session-store.ts +103 -0
- package/src/providers/claude-sdk/spawn.ts +242 -0
- package/src/providers/claude-sdk/tool-bridge.ts +51 -0
- package/src/providers/openai-sdk/__tests__/openai-sdk-spawn.test.ts +716 -0
- package/src/providers/openai-sdk/adapter.ts +138 -0
- package/src/providers/openai-sdk/guardrails.ts +158 -0
- package/src/providers/openai-sdk/handoff.ts +187 -0
- package/src/providers/openai-sdk/index.ts +55 -0
- package/src/providers/openai-sdk/install.ts +135 -0
- package/src/providers/openai-sdk/manifest.json +45 -0
- package/src/providers/openai-sdk/spawn.ts +300 -0
- package/src/providers/openai-sdk/tracing.ts +175 -0
- package/src/providers/shared/conduit-trace-writer.ts +101 -0
- package/src/providers/shared/sdk-result-mapper.ts +83 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude SDK Spawn Provider
|
|
3
|
+
*
|
|
4
|
+
* Implements `AdapterSpawnProvider` using the `@anthropic-ai/claude-agent-sdk`
|
|
5
|
+
* programmatic API instead of shelling out to the `claude` CLI.
|
|
6
|
+
*
|
|
7
|
+
* Differences from `ClaudeCodeSpawnProvider`:
|
|
8
|
+
* - Uses SDK `query()` instead of a detached child process
|
|
9
|
+
* - Awaits full completion before returning (synchronous output capture)
|
|
10
|
+
* - Session IDs from the SDK enable future multi-turn resumption
|
|
11
|
+
* - No temp files, no OS PIDs — tracking is purely in-memory session IDs
|
|
12
|
+
* - `canSpawn()` checks for `ANTHROPIC_API_KEY` rather than CLI availability
|
|
13
|
+
*
|
|
14
|
+
* CANT enrichment is identical to the CLI provider: `buildCantEnrichedPrompt()`
|
|
15
|
+
* is called before `query()` and the result is passed as the SDK prompt string.
|
|
16
|
+
*
|
|
17
|
+
* @task T581
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { AdapterSpawnProvider, SpawnContext, SpawnResult } from '@cleocode/contracts';
|
|
21
|
+
import { getErrorMessage } from '@cleocode/contracts';
|
|
22
|
+
import { getServers } from './mcp-registry.js';
|
|
23
|
+
import { SessionStore } from './session-store.js';
|
|
24
|
+
import { resolveTools } from './tool-bridge.js';
|
|
25
|
+
|
|
26
|
+
/** Model used when no model is specified in spawn options. */
|
|
27
|
+
const DEFAULT_MODEL = 'claude-sonnet-4-5';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Spawn provider that uses the Anthropic Claude Agent SDK for programmatic
|
|
31
|
+
* subagent execution.
|
|
32
|
+
*
|
|
33
|
+
* Each call to `spawn()` runs a full SDK `query()` to completion and
|
|
34
|
+
* captures the output. Sessions are tracked in `SessionStore` so callers
|
|
35
|
+
* can inspect active sessions via `listRunning()` and cancel them via
|
|
36
|
+
* `terminate()`.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* The `permissionMode: 'bypassPermissions'` + `allowDangerouslySkipPermissions: true`
|
|
40
|
+
* combination mirrors the `--dangerously-skip-permissions` flag used by
|
|
41
|
+
* the CLI provider. Both are required by the SDK when bypassing all tool
|
|
42
|
+
* permission prompts.
|
|
43
|
+
*/
|
|
44
|
+
export class ClaudeSDKSpawnProvider implements AdapterSpawnProvider {
|
|
45
|
+
/** In-memory session registry. */
|
|
46
|
+
private readonly sessions = new SessionStore();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check whether the SDK can be used in the current environment.
|
|
50
|
+
*
|
|
51
|
+
* Returns `true` if `ANTHROPIC_API_KEY` is set. No binary check is needed
|
|
52
|
+
* because the SDK manages the Claude Code subprocess internally.
|
|
53
|
+
*
|
|
54
|
+
* @returns `true` when an API key is present
|
|
55
|
+
*/
|
|
56
|
+
async canSpawn(): Promise<boolean> {
|
|
57
|
+
return !!process.env.ANTHROPIC_API_KEY;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Spawn a subagent using the Claude Agent SDK.
|
|
62
|
+
*
|
|
63
|
+
* Enriches the prompt via CANT context, runs the SDK `query()` to
|
|
64
|
+
* completion, captures all assistant text output, and returns a
|
|
65
|
+
* `SpawnResult` with the final output and exit code.
|
|
66
|
+
*
|
|
67
|
+
* @param context - Spawn context with taskId, prompt, options
|
|
68
|
+
* @returns Resolved spawn result (status: 'completed' or 'failed')
|
|
69
|
+
*/
|
|
70
|
+
async spawn(context: SpawnContext): Promise<SpawnResult> {
|
|
71
|
+
const instanceId = `sdk-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
72
|
+
const startTime = new Date().toISOString();
|
|
73
|
+
|
|
74
|
+
// Register in session store immediately so listRunning() reflects it
|
|
75
|
+
// before the async query starts.
|
|
76
|
+
this.sessions.add({
|
|
77
|
+
instanceId,
|
|
78
|
+
sessionId: undefined,
|
|
79
|
+
taskId: context.taskId,
|
|
80
|
+
startTime,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// CANT enrichment — best-effort, identical to ClaudeCodeSpawnProvider.
|
|
85
|
+
let enrichedPrompt = context.prompt;
|
|
86
|
+
try {
|
|
87
|
+
const { buildCantEnrichedPrompt } = await import('../../cant-context.js');
|
|
88
|
+
enrichedPrompt = await buildCantEnrichedPrompt({
|
|
89
|
+
projectDir: context.workingDirectory ?? process.cwd(),
|
|
90
|
+
basePrompt: context.prompt,
|
|
91
|
+
agentName: (context.options?.agentName as string) ?? undefined,
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
// CANT enrichment unavailable — use raw prompt
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Lazy-import the SDK to avoid hard failures when ANTHROPIC_API_KEY
|
|
98
|
+
// is absent (canSpawn() guards the normal path, but tests may import
|
|
99
|
+
// this module without the key).
|
|
100
|
+
const { query } = await import('@anthropic-ai/claude-agent-sdk');
|
|
101
|
+
|
|
102
|
+
// Build allowedTools from the spawn context or fall back to CLEO defaults.
|
|
103
|
+
const toolAllowlist = context.options?.toolAllowlist as string[] | undefined;
|
|
104
|
+
const allowedTools = resolveTools(toolAllowlist);
|
|
105
|
+
|
|
106
|
+
// Resolve available MCP servers for the working directory.
|
|
107
|
+
const workDir = context.workingDirectory ?? process.cwd();
|
|
108
|
+
const mcpServers = getServers(workDir);
|
|
109
|
+
|
|
110
|
+
// Resume support: pass a prior session ID if provided.
|
|
111
|
+
const resumeSessionId = context.options?.resumeSessionId as string | undefined;
|
|
112
|
+
|
|
113
|
+
const sdkQuery = query({
|
|
114
|
+
prompt: enrichedPrompt,
|
|
115
|
+
options: {
|
|
116
|
+
cwd: workDir,
|
|
117
|
+
model: (context.options?.model as string) ?? DEFAULT_MODEL,
|
|
118
|
+
allowedTools,
|
|
119
|
+
permissionMode: 'bypassPermissions',
|
|
120
|
+
allowDangerouslySkipPermissions: true,
|
|
121
|
+
...(Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),
|
|
122
|
+
...(resumeSessionId ? { resume: resumeSessionId } : {}),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Stream messages from the SDK, collecting text output and session ID.
|
|
127
|
+
const textParts: string[] = [];
|
|
128
|
+
let exitCode = 0;
|
|
129
|
+
let finalError: string | undefined;
|
|
130
|
+
|
|
131
|
+
for await (const message of sdkQuery) {
|
|
132
|
+
// Capture the session ID from the first message that carries it.
|
|
133
|
+
if ('session_id' in message && typeof message.session_id === 'string') {
|
|
134
|
+
this.sessions.setSessionId(instanceId, message.session_id);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (message.type === 'assistant') {
|
|
138
|
+
// Aggregate text blocks from assistant messages.
|
|
139
|
+
for (const block of message.message.content) {
|
|
140
|
+
if (block.type === 'text') {
|
|
141
|
+
textParts.push(block.text);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} else if (message.type === 'result') {
|
|
145
|
+
if (message.subtype === 'success') {
|
|
146
|
+
// The result field on success contains the final summary text.
|
|
147
|
+
if (message.result) {
|
|
148
|
+
textParts.push(message.result);
|
|
149
|
+
}
|
|
150
|
+
exitCode = message.is_error ? 1 : 0;
|
|
151
|
+
} else {
|
|
152
|
+
// Error subtypes: error_max_turns, error_during_execution, etc.
|
|
153
|
+
exitCode = 1;
|
|
154
|
+
if ('errors' in message && Array.isArray(message.errors) && message.errors.length > 0) {
|
|
155
|
+
finalError = (message.errors as string[]).join('; ');
|
|
156
|
+
} else {
|
|
157
|
+
// Fallback: use the subtype string as the error description so
|
|
158
|
+
// `finalError` is always truthy for non-success result messages.
|
|
159
|
+
finalError = String(message.subtype);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const endTime = new Date().toISOString();
|
|
166
|
+
this.sessions.remove(instanceId);
|
|
167
|
+
|
|
168
|
+
const output = textParts.join('\n').trim();
|
|
169
|
+
|
|
170
|
+
if (finalError) {
|
|
171
|
+
return {
|
|
172
|
+
instanceId,
|
|
173
|
+
taskId: context.taskId,
|
|
174
|
+
providerId: 'claude-sdk',
|
|
175
|
+
status: 'failed',
|
|
176
|
+
output,
|
|
177
|
+
exitCode,
|
|
178
|
+
startTime,
|
|
179
|
+
endTime,
|
|
180
|
+
error: finalError,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
instanceId,
|
|
186
|
+
taskId: context.taskId,
|
|
187
|
+
providerId: 'claude-sdk',
|
|
188
|
+
status: 'completed',
|
|
189
|
+
output,
|
|
190
|
+
exitCode,
|
|
191
|
+
startTime,
|
|
192
|
+
endTime,
|
|
193
|
+
};
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const endTime = new Date().toISOString();
|
|
196
|
+
this.sessions.remove(instanceId);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
instanceId,
|
|
200
|
+
taskId: context.taskId,
|
|
201
|
+
providerId: 'claude-sdk',
|
|
202
|
+
status: 'failed',
|
|
203
|
+
startTime,
|
|
204
|
+
endTime,
|
|
205
|
+
exitCode: 1,
|
|
206
|
+
error: getErrorMessage(error),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* List sessions currently tracked as active (spawned but not yet completed).
|
|
213
|
+
*
|
|
214
|
+
* Because SDK sessions run to completion inside `spawn()`, this list is
|
|
215
|
+
* typically empty unless concurrent spawns are in flight.
|
|
216
|
+
*
|
|
217
|
+
* @returns Array of in-flight spawn results
|
|
218
|
+
*/
|
|
219
|
+
async listRunning(): Promise<SpawnResult[]> {
|
|
220
|
+
return this.sessions.listActive().map((entry) => ({
|
|
221
|
+
instanceId: entry.instanceId,
|
|
222
|
+
taskId: entry.taskId,
|
|
223
|
+
providerId: 'claude-sdk',
|
|
224
|
+
status: 'running' as const,
|
|
225
|
+
startTime: entry.startTime,
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Remove a session from tracking.
|
|
231
|
+
*
|
|
232
|
+
* The underlying SDK query runs inside `spawn()` and cannot be cancelled
|
|
233
|
+
* externally once the async iterator is in flight. Removing the entry from
|
|
234
|
+
* the store prevents it from appearing in `listRunning()` but does not
|
|
235
|
+
* interrupt the in-progress HTTP request.
|
|
236
|
+
*
|
|
237
|
+
* @param instanceId - ID of the spawn instance to terminate
|
|
238
|
+
*/
|
|
239
|
+
async terminate(instanceId: string): Promise<void> {
|
|
240
|
+
this.sessions.remove(instanceId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Bridge for Claude SDK Spawn Provider
|
|
3
|
+
*
|
|
4
|
+
* Maps CLEO's tool allowlist (string names) to the SDK's `allowedTools`
|
|
5
|
+
* option format. The SDK accepts plain tool name strings such as
|
|
6
|
+
* `"Read"`, `"Bash"`, `"Edit"` for built-in Claude Code tools and
|
|
7
|
+
* `"mcp__<server>__<tool>"` for MCP-backed tools.
|
|
8
|
+
*
|
|
9
|
+
* @task T581
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default CLEO tool set passed to the SDK when no explicit allowlist
|
|
14
|
+
* is provided in `SpawnContext.options.toolAllowlist`.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors the standard agent tool surface used by the Claude Code CLI
|
|
17
|
+
* `--dangerously-skip-permissions` mode.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_TOOLS: readonly string[] = [
|
|
20
|
+
'Read',
|
|
21
|
+
'Write',
|
|
22
|
+
'Edit',
|
|
23
|
+
'Bash',
|
|
24
|
+
'Glob',
|
|
25
|
+
'Grep',
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolves a CLEO tool allowlist to the SDK `allowedTools` array.
|
|
30
|
+
*
|
|
31
|
+
* When `allowlist` is undefined or empty, the default CLEO tool set is
|
|
32
|
+
* returned. When an explicit list is provided, it is returned as-is so
|
|
33
|
+
* callers can pass MCP tool strings (`mcp__server__tool`) alongside
|
|
34
|
+
* built-in names without transformation.
|
|
35
|
+
*
|
|
36
|
+
* @param allowlist - Optional array of tool names from `SpawnContext.options`
|
|
37
|
+
* @returns Array of SDK-compatible tool name strings
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* resolveTools(); // ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep']
|
|
42
|
+
* resolveTools(['Read', 'Bash']); // ['Read', 'Bash']
|
|
43
|
+
* resolveTools(['mcp__brain__search']); // ['mcp__brain__search']
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function resolveTools(allowlist?: string[]): string[] {
|
|
47
|
+
if (!allowlist || allowlist.length === 0) {
|
|
48
|
+
return [...DEFAULT_TOOLS];
|
|
49
|
+
}
|
|
50
|
+
return [...allowlist];
|
|
51
|
+
}
|