@osdlc/agent-call 0.0.1-stage → 0.4.1
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 +187 -0
- package/CONTRIBUTING.md +65 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +298 -1
- package/SECURITY.md +23 -0
- package/dist/adapters/antigravity.d.ts +15 -0
- package/dist/adapters/antigravity.js +262 -0
- package/dist/adapters/claude.d.ts +29 -0
- package/dist/adapters/claude.js +314 -0
- package/dist/adapters/codex.d.ts +18 -0
- package/dist/adapters/codex.js +374 -0
- package/dist/adapters/custom.d.ts +31 -0
- package/dist/adapters/custom.js +42 -0
- package/dist/adapters/opencode.d.ts +12 -0
- package/dist/adapters/opencode.js +207 -0
- package/dist/adapters/shared.d.ts +40 -0
- package/dist/adapters/shared.js +101 -0
- package/dist/adapters/template.d.ts +43 -0
- package/dist/adapters/template.js +62 -0
- package/dist/adapters/types.d.ts +55 -0
- package/dist/adapters/types.js +1 -0
- package/dist/conversation.d.ts +73 -0
- package/dist/conversation.js +37 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +12 -0
- package/dist/model-args.d.ts +37 -0
- package/dist/model-args.js +58 -0
- package/dist/models.d.ts +9 -0
- package/dist/models.js +12 -0
- package/dist/plan.d.ts +56 -0
- package/dist/plan.js +9 -0
- package/dist/registry.d.ts +21 -0
- package/dist/registry.js +41 -0
- package/dist/run.d.ts +75 -0
- package/dist/run.js +172 -0
- package/dist/spawn.d.ts +10 -0
- package/dist/spawn.js +143 -0
- package/dist/spec.d.ts +132 -0
- package/dist/spec.js +11 -0
- package/package.json +67 -4
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { CONVERSATION_SCHEMA, } from '../conversation.js';
|
|
2
|
+
import { note, TMP_TOKEN } from '../plan.js';
|
|
3
|
+
import { applyPermissionMode, asNumber, asString, buildEffectivePrompt, get, modelFlag, parseJsonLines, tryParseJson, } from './shared.js';
|
|
4
|
+
const MCP_CONFIG_FILE = 'mcp.json';
|
|
5
|
+
/**
|
|
6
|
+
* Claude Code (`claude -p`). The richest surface: permissions, tool rules,
|
|
7
|
+
* system prompts, structured output, sessions, and budget are all native.
|
|
8
|
+
*/
|
|
9
|
+
export function claudeAdapter(options = {}) {
|
|
10
|
+
const binary = options.binary ?? 'claude';
|
|
11
|
+
return {
|
|
12
|
+
name: 'claude',
|
|
13
|
+
binary,
|
|
14
|
+
plan(spec) {
|
|
15
|
+
return planClaude(binary, spec);
|
|
16
|
+
},
|
|
17
|
+
parseResult(planned, raw) {
|
|
18
|
+
return parseClaude(planned, raw);
|
|
19
|
+
},
|
|
20
|
+
toConversation(planned, raw) {
|
|
21
|
+
return claudeConversation(planned, raw);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `--resume=<id>`: `--resume` takes an optional value, so the `=` form keeps an
|
|
27
|
+
* id that starts with `-` bound to it.
|
|
28
|
+
*
|
|
29
|
+
* @experimental Part of the launch-argv helpers, which may change in a minor release.
|
|
30
|
+
*/
|
|
31
|
+
export function claudeResumeFlag(id) {
|
|
32
|
+
return `--resume=${id}`;
|
|
33
|
+
}
|
|
34
|
+
/** `--effort <level>`: passed through as given, so a level the CLI adds later needs no change here. */
|
|
35
|
+
export function claudeEffortArgv(effort) {
|
|
36
|
+
return ['--effort', effort];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Claude argv for session-only plugins: `--plugin-dir` per dir, then a
|
|
40
|
+
* `--settings` disable map. For callers that build claude's command line
|
|
41
|
+
* themselves.
|
|
42
|
+
*
|
|
43
|
+
* @experimental Part of the launch-argv helpers, which may change in a minor release.
|
|
44
|
+
*/
|
|
45
|
+
export function claudePluginArgv(plugins) {
|
|
46
|
+
const argv = plugins.dirs.flatMap((dir) => ['--plugin-dir', dir]);
|
|
47
|
+
if (plugins.disable.length > 0) {
|
|
48
|
+
const enabledPlugins = Object.fromEntries(plugins.disable.map((id) => [id, false]));
|
|
49
|
+
argv.push('--settings', JSON.stringify({ enabledPlugins }));
|
|
50
|
+
}
|
|
51
|
+
return argv;
|
|
52
|
+
}
|
|
53
|
+
function planClaude(binary, spec) {
|
|
54
|
+
const notes = [];
|
|
55
|
+
const tempFiles = [];
|
|
56
|
+
const argv = [binary, '-p'];
|
|
57
|
+
// Prefer the JSON envelope when the caller didn't ask for a specific
|
|
58
|
+
// format: same answer, plus usage/cost/session metadata.
|
|
59
|
+
let format = spec.output?.format ?? 'json';
|
|
60
|
+
if (spec.output?.schema !== undefined && format === 'text') {
|
|
61
|
+
format = 'json';
|
|
62
|
+
note(notes, 'output.format', 'approximated', 'structured output requires the json envelope; text was upgraded to json');
|
|
63
|
+
}
|
|
64
|
+
argv.push('--output-format', format);
|
|
65
|
+
if (format === 'stream-json')
|
|
66
|
+
argv.push('--verbose');
|
|
67
|
+
if (spec.model !== undefined)
|
|
68
|
+
argv.push(...modelFlag(spec.model));
|
|
69
|
+
if (spec.effort !== undefined) {
|
|
70
|
+
argv.push(...claudeEffortArgv(spec.effort));
|
|
71
|
+
note(notes, 'effort', 'native', '--effort');
|
|
72
|
+
}
|
|
73
|
+
if (spec.mcp?.servers !== undefined) {
|
|
74
|
+
// A file, not inline JSON: server `env` holds tokens, and argv is readable
|
|
75
|
+
// by any local user.
|
|
76
|
+
tempFiles.push({
|
|
77
|
+
name: MCP_CONFIG_FILE,
|
|
78
|
+
content: JSON.stringify({ mcpServers: spec.mcp.servers }),
|
|
79
|
+
});
|
|
80
|
+
argv.push('--mcp-config', `${TMP_TOKEN}/${MCP_CONFIG_FILE}`);
|
|
81
|
+
note(notes, 'mcp.servers', 'native', '--mcp-config');
|
|
82
|
+
if (spec.mcp.strict === true)
|
|
83
|
+
argv.push('--strict-mcp-config');
|
|
84
|
+
}
|
|
85
|
+
if (spec.plugins !== undefined) {
|
|
86
|
+
argv.push(...claudePluginArgv(spec.plugins));
|
|
87
|
+
note(notes, 'plugins', 'native', '--plugin-dir / --settings enabledPlugins');
|
|
88
|
+
}
|
|
89
|
+
if (spec.systemPrompt !== undefined) {
|
|
90
|
+
argv.push('--system-prompt', spec.systemPrompt);
|
|
91
|
+
note(notes, 'systemPrompt', 'native', '--system-prompt');
|
|
92
|
+
}
|
|
93
|
+
if (spec.appendSystemPrompt !== undefined) {
|
|
94
|
+
argv.push('--append-system-prompt', spec.appendSystemPrompt);
|
|
95
|
+
note(notes, 'appendSystemPrompt', 'native', '--append-system-prompt');
|
|
96
|
+
}
|
|
97
|
+
const permissions = spec.permissions;
|
|
98
|
+
applyPermissionMode(argv, notes, permissions?.mode, {
|
|
99
|
+
'read-only': {
|
|
100
|
+
argv: ['--permission-mode', 'plan'],
|
|
101
|
+
support: 'approximated',
|
|
102
|
+
detail: 'read-only mapped to --permission-mode plan (analysis only, no mutations)',
|
|
103
|
+
},
|
|
104
|
+
edits: {
|
|
105
|
+
argv: ['--permission-mode', 'acceptEdits'],
|
|
106
|
+
support: 'native',
|
|
107
|
+
detail: '--permission-mode acceptEdits',
|
|
108
|
+
},
|
|
109
|
+
'full-auto': {
|
|
110
|
+
argv: ['--permission-mode', 'acceptEdits', '--allowedTools', 'Bash WebFetch WebSearch'],
|
|
111
|
+
support: 'approximated',
|
|
112
|
+
detail: 'full-auto mapped to acceptEdits plus pre-approved Bash/WebFetch/WebSearch (claude has no workspace sandbox)',
|
|
113
|
+
},
|
|
114
|
+
bypass: {
|
|
115
|
+
argv: ['--dangerously-skip-permissions'],
|
|
116
|
+
support: 'native',
|
|
117
|
+
detail: '--dangerously-skip-permissions',
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
if (permissions?.allowTools !== undefined && permissions.allowTools.length > 0) {
|
|
121
|
+
argv.push('--allowedTools', permissions.allowTools.join(','));
|
|
122
|
+
note(notes, 'permissions.allowTools', 'native', '--allowedTools');
|
|
123
|
+
}
|
|
124
|
+
if (permissions?.denyTools !== undefined && permissions.denyTools.length > 0) {
|
|
125
|
+
argv.push('--disallowedTools', permissions.denyTools.join(','));
|
|
126
|
+
note(notes, 'permissions.denyTools', 'native', '--disallowedTools');
|
|
127
|
+
}
|
|
128
|
+
for (const dir of spec.context?.dirs ?? []) {
|
|
129
|
+
argv.push('--add-dir', dir);
|
|
130
|
+
}
|
|
131
|
+
if ((spec.context?.dirs ?? []).length > 0) {
|
|
132
|
+
note(notes, 'context.dirs', 'native', '--add-dir');
|
|
133
|
+
}
|
|
134
|
+
if (spec.output?.schema !== undefined) {
|
|
135
|
+
argv.push('--json-schema', JSON.stringify(spec.output.schema));
|
|
136
|
+
note(notes, 'output.schema', 'native', '--json-schema');
|
|
137
|
+
}
|
|
138
|
+
const session = spec.session;
|
|
139
|
+
if (session?.id !== undefined)
|
|
140
|
+
argv.push('--session-id', session.id);
|
|
141
|
+
if (session?.resume !== undefined)
|
|
142
|
+
argv.push(claudeResumeFlag(session.resume));
|
|
143
|
+
if (session?.continueLast === true)
|
|
144
|
+
argv.push('--continue');
|
|
145
|
+
if (session?.persist === false) {
|
|
146
|
+
argv.push('--no-session-persistence');
|
|
147
|
+
note(notes, 'session.persist', 'native', '--no-session-persistence');
|
|
148
|
+
}
|
|
149
|
+
if (spec.budgetUsd !== undefined) {
|
|
150
|
+
argv.push('--max-budget-usd', String(spec.budgetUsd));
|
|
151
|
+
note(notes, 'budgetUsd', 'native', '--max-budget-usd');
|
|
152
|
+
}
|
|
153
|
+
argv.push(...(spec.extraArgs ?? []));
|
|
154
|
+
const stdin = buildEffectivePrompt(spec, { systemNative: true, appendSystemNative: true, schemaNative: true }, notes);
|
|
155
|
+
return {
|
|
156
|
+
agent: 'claude',
|
|
157
|
+
argv,
|
|
158
|
+
stdin,
|
|
159
|
+
env: { ...spec.env },
|
|
160
|
+
cwd: spec.cwd,
|
|
161
|
+
outputFormat: format,
|
|
162
|
+
tempFiles,
|
|
163
|
+
notes,
|
|
164
|
+
spec,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function parseClaude(planned, raw) {
|
|
168
|
+
if (planned.outputFormat === 'text') {
|
|
169
|
+
return { text: raw.stdout.trim() };
|
|
170
|
+
}
|
|
171
|
+
if (planned.outputFormat === 'stream-json') {
|
|
172
|
+
const events = parseJsonLines(raw.stdout);
|
|
173
|
+
const result = events.find((e) => get(e, 'type') === 'result');
|
|
174
|
+
if (result === undefined) {
|
|
175
|
+
// The stream ended without its terminal envelope (crash, kill, truncated
|
|
176
|
+
// output). Mirrors the `json` arm's missing-envelope guard below; the
|
|
177
|
+
// events are still returned so callers can mine them, and the exit code
|
|
178
|
+
// and stderr stay on the AgentResult.
|
|
179
|
+
return {
|
|
180
|
+
text: '',
|
|
181
|
+
isError: true,
|
|
182
|
+
errorDetail: 'claude --output-format stream-json produced no result event',
|
|
183
|
+
events,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
...envelopeFields(result, planned),
|
|
188
|
+
events,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const envelope = tryParseJson(raw.stdout);
|
|
192
|
+
if (envelope === undefined) {
|
|
193
|
+
return {
|
|
194
|
+
text: raw.stdout.trim(),
|
|
195
|
+
isError: true,
|
|
196
|
+
errorDetail: 'claude --output-format json produced no parseable envelope',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return envelopeFields(envelope, planned);
|
|
200
|
+
}
|
|
201
|
+
/** Shared between the `json` envelope and the `stream-json` result event. */
|
|
202
|
+
function envelopeFields(envelope, planned) {
|
|
203
|
+
const text = asString(get(envelope, 'result')) ?? '';
|
|
204
|
+
const subtype = asString(get(envelope, 'subtype'));
|
|
205
|
+
const isError = get(envelope, 'is_error') === true || subtype === 'error';
|
|
206
|
+
const parsed = {
|
|
207
|
+
text,
|
|
208
|
+
sessionId: asString(get(envelope, 'session_id')),
|
|
209
|
+
costUsd: asNumber(get(envelope, 'total_cost_usd')),
|
|
210
|
+
usage: claudeUsage(get(envelope, 'usage')),
|
|
211
|
+
stopReason: subtype,
|
|
212
|
+
isError,
|
|
213
|
+
errorDetail: isError ? text || subtype : undefined,
|
|
214
|
+
};
|
|
215
|
+
if (planned.spec.output?.schema !== undefined) {
|
|
216
|
+
parsed.structured = get(envelope, 'structured_output') ?? tryParseJson(text);
|
|
217
|
+
}
|
|
218
|
+
return parsed;
|
|
219
|
+
}
|
|
220
|
+
function claudeUsage(usage) {
|
|
221
|
+
if (usage === null || typeof usage !== 'object')
|
|
222
|
+
return undefined;
|
|
223
|
+
return {
|
|
224
|
+
inputTokens: asNumber(get(usage, 'input_tokens')),
|
|
225
|
+
outputTokens: asNumber(get(usage, 'output_tokens')),
|
|
226
|
+
cacheReadTokens: asNumber(get(usage, 'cache_read_input_tokens')),
|
|
227
|
+
cacheWriteTokens: asNumber(get(usage, 'cache_creation_input_tokens')),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function claudeConversation(planned, raw) {
|
|
231
|
+
const parsed = parseClaude(planned, raw);
|
|
232
|
+
const messages = [
|
|
233
|
+
{ role: 'user', parts: [{ type: 'text', text: planned.stdin ?? '' }] },
|
|
234
|
+
];
|
|
235
|
+
if (planned.outputFormat === 'stream-json') {
|
|
236
|
+
for (const event of parsed.events ?? []) {
|
|
237
|
+
const type = get(event, 'type');
|
|
238
|
+
if (type !== 'assistant' && type !== 'user')
|
|
239
|
+
continue;
|
|
240
|
+
const content = get(event, 'message', 'content');
|
|
241
|
+
const parts = anthropicContentParts(content);
|
|
242
|
+
if (parts.length > 0) {
|
|
243
|
+
messages.push({ role: type === 'assistant' ? 'assistant' : 'tool', parts });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (parsed.text !== '') {
|
|
248
|
+
messages.push({
|
|
249
|
+
role: 'assistant',
|
|
250
|
+
parts: [{ type: 'text', text: parsed.text }],
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const model = firstAssistantModel(parsed.events ?? []);
|
|
254
|
+
return {
|
|
255
|
+
schema: CONVERSATION_SCHEMA,
|
|
256
|
+
agent: 'claude',
|
|
257
|
+
model,
|
|
258
|
+
sessionId: parsed.sessionId,
|
|
259
|
+
messages,
|
|
260
|
+
usage: parsed.usage,
|
|
261
|
+
costUsd: parsed.costUsd,
|
|
262
|
+
stopReason: parsed.stopReason,
|
|
263
|
+
raw: {
|
|
264
|
+
format: `claude/${planned.outputFormat}`,
|
|
265
|
+
data: planned.outputFormat === 'stream-json'
|
|
266
|
+
? parsed.events
|
|
267
|
+
: (tryParseJson(raw.stdout) ?? raw.stdout),
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/** Anthropic message content blocks → normalized parts. */
|
|
272
|
+
function anthropicContentParts(content) {
|
|
273
|
+
if (!Array.isArray(content))
|
|
274
|
+
return [];
|
|
275
|
+
const parts = [];
|
|
276
|
+
for (const block of content) {
|
|
277
|
+
const type = get(block, 'type');
|
|
278
|
+
if (type === 'text') {
|
|
279
|
+
const text = asString(get(block, 'text'));
|
|
280
|
+
if (text !== undefined && text !== '')
|
|
281
|
+
parts.push({ type: 'text', text });
|
|
282
|
+
}
|
|
283
|
+
else if (type === 'thinking') {
|
|
284
|
+
const text = asString(get(block, 'thinking'));
|
|
285
|
+
if (text !== undefined)
|
|
286
|
+
parts.push({ type: 'reasoning', text });
|
|
287
|
+
}
|
|
288
|
+
else if (type === 'tool_use') {
|
|
289
|
+
parts.push({
|
|
290
|
+
type: 'tool-call',
|
|
291
|
+
callId: asString(get(block, 'id')),
|
|
292
|
+
name: asString(get(block, 'name')) ?? 'unknown',
|
|
293
|
+
input: get(block, 'input'),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
else if (type === 'tool_result') {
|
|
297
|
+
parts.push({
|
|
298
|
+
type: 'tool-result',
|
|
299
|
+
callId: asString(get(block, 'tool_use_id')),
|
|
300
|
+
output: get(block, 'content'),
|
|
301
|
+
isError: get(block, 'is_error') === true,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return parts;
|
|
306
|
+
}
|
|
307
|
+
function firstAssistantModel(events) {
|
|
308
|
+
for (const event of events) {
|
|
309
|
+
const model = asString(get(event, 'message', 'model'));
|
|
310
|
+
if (model !== undefined)
|
|
311
|
+
return model;
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentAdapter } from './types.js';
|
|
2
|
+
/** Options for {@link codexAdapter}. */
|
|
3
|
+
export interface CodexAdapterOptions {
|
|
4
|
+
/** Binary to spawn. Default: the CLI's own name. */
|
|
5
|
+
binary?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* OpenAI Codex CLI (`codex exec`). Sandbox modes map cleanly onto the
|
|
9
|
+
* canonical permission postures; structured output is native via
|
|
10
|
+
* `--output-schema`; tool-level rules are not expressible.
|
|
11
|
+
*/
|
|
12
|
+
export declare function codexAdapter(options?: CodexAdapterOptions): AgentAdapter;
|
|
13
|
+
/**
|
|
14
|
+
* `-c model_reasoning_effort="<level>"`, quoted as codex's TOML override
|
|
15
|
+
* expects. The level passes through as given; which levels a model takes
|
|
16
|
+
* is the caller's to know (`codex debug models` lists them).
|
|
17
|
+
*/
|
|
18
|
+
export declare function codexEffortArgv(effort: string): string[];
|