@sdods/agents 0.2.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/LICENSE +17 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/adapter/claude-code.d.ts +29 -0
- package/dist/adapter/claude-code.js +195 -0
- package/dist/adapter/claude.d.ts +42 -0
- package/dist/adapter/claude.js +216 -0
- package/dist/adapter/cli-common.d.ts +46 -0
- package/dist/adapter/cli-common.js +145 -0
- package/dist/adapter/codex.d.ts +31 -0
- package/dist/adapter/codex.js +252 -0
- package/dist/adapter/factory.d.ts +41 -0
- package/dist/adapter/factory.js +102 -0
- package/dist/adapter/fake.d.ts +39 -0
- package/dist/adapter/fake.js +91 -0
- package/dist/adapter/index.d.ts +11 -0
- package/dist/adapter/index.js +11 -0
- package/dist/adapter/json-schema.d.ts +11 -0
- package/dist/adapter/json-schema.js +17 -0
- package/dist/adapter/mcp-bridge.d.ts +35 -0
- package/dist/adapter/mcp-bridge.js +122 -0
- package/dist/adapter/ollama.d.ts +62 -0
- package/dist/adapter/ollama.js +272 -0
- package/dist/adapter/openai-compat.d.ts +44 -0
- package/dist/adapter/openai-compat.js +200 -0
- package/dist/adapter/profile.d.ts +54 -0
- package/dist/adapter/profile.js +42 -0
- package/dist/adapter/tool-loop.d.ts +64 -0
- package/dist/adapter/tool-loop.js +192 -0
- package/dist/adapter/types.d.ts +109 -0
- package/dist/adapter/types.js +20 -0
- package/dist/claude-code/install.d.ts +36 -0
- package/dist/claude-code/install.js +183 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/jobs/runner.d.ts +80 -0
- package/dist/jobs/runner.js +197 -0
- package/dist/roles/index.d.ts +50 -0
- package/dist/roles/index.js +125 -0
- package/dist/roles/steps.d.ts +28 -0
- package/dist/roles/steps.js +75 -0
- package/package.json +50 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type AgentEvent, type CompleteRequest, type CompleteResult, type LlmAdapter, type RunAgentOptions, type RunAgentResult, type TokenUsage } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Claude adapter: `runAgent` uses the Claude Agent SDK (query + in-process MCP tools + the
|
|
4
|
+
* bundled Playwright MCP); `complete`/`stream` use the Anthropic Messages API.
|
|
5
|
+
* The SDK modules are imported lazily so the package loads without them (FakeAdapter, tests).
|
|
6
|
+
*/
|
|
7
|
+
export interface ClaudeAdapterOptions {
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
/** injected for tests */
|
|
11
|
+
sdk?: {
|
|
12
|
+
query: (params: {
|
|
13
|
+
prompt: string;
|
|
14
|
+
options?: Record<string, unknown>;
|
|
15
|
+
}) => AsyncIterable<Record<string, any>>;
|
|
16
|
+
tool: (...a: any[]) => any;
|
|
17
|
+
createSdkMcpServer: (o: any) => any;
|
|
18
|
+
};
|
|
19
|
+
anthropic?: {
|
|
20
|
+
messages: {
|
|
21
|
+
create: (p: any) => Promise<any>;
|
|
22
|
+
stream?: (p: any) => AsyncIterable<any>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
withPlaywrightMcp?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare class ClaudeAdapter implements LlmAdapter {
|
|
28
|
+
private readonly opts;
|
|
29
|
+
readonly provider: "claude";
|
|
30
|
+
readonly defaultModel: string;
|
|
31
|
+
constructor(opts?: ClaudeAdapterOptions);
|
|
32
|
+
private ensureKey;
|
|
33
|
+
private sdk;
|
|
34
|
+
private anthropic;
|
|
35
|
+
complete(req: CompleteRequest): Promise<CompleteResult>;
|
|
36
|
+
stream(req: CompleteRequest): AsyncIterable<string>;
|
|
37
|
+
runAgent(o: RunAgentOptions): Promise<RunAgentResult>;
|
|
38
|
+
}
|
|
39
|
+
/** Convert Claude Agent SDK messages into SDODS agent events. */
|
|
40
|
+
export declare function normalize(msg: Record<string, any>): AgentEvent[];
|
|
41
|
+
export declare function usageOf(u: Record<string, unknown> | undefined): TokenUsage | undefined;
|
|
42
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { DEFAULT_MODELS, AgentsConfigError, } from './types.js';
|
|
2
|
+
export class ClaudeAdapter {
|
|
3
|
+
opts;
|
|
4
|
+
provider = 'claude';
|
|
5
|
+
defaultModel;
|
|
6
|
+
constructor(opts = {}) {
|
|
7
|
+
this.opts = opts;
|
|
8
|
+
this.defaultModel = opts.model ?? process.env.SDODS_CLAUDE_MODEL ?? DEFAULT_MODELS.claude;
|
|
9
|
+
}
|
|
10
|
+
ensureKey() {
|
|
11
|
+
const key = this.opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
12
|
+
if (!key) {
|
|
13
|
+
throw new AgentsConfigError('ANTHROPIC_API_KEY is not set.', 'Export ANTHROPIC_API_KEY (your own key, billed by Anthropic) or use --adapter fake / --dry-run.');
|
|
14
|
+
}
|
|
15
|
+
return key;
|
|
16
|
+
}
|
|
17
|
+
async sdk() {
|
|
18
|
+
if (this.opts.sdk)
|
|
19
|
+
return this.opts.sdk;
|
|
20
|
+
const mod = (await import('@anthropic-ai/claude-agent-sdk'));
|
|
21
|
+
return mod;
|
|
22
|
+
}
|
|
23
|
+
async anthropic() {
|
|
24
|
+
if (this.opts.anthropic)
|
|
25
|
+
return this.opts.anthropic;
|
|
26
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
27
|
+
return new Anthropic({ apiKey: this.ensureKey() });
|
|
28
|
+
}
|
|
29
|
+
async complete(req) {
|
|
30
|
+
this.ensureKey();
|
|
31
|
+
const client = await this.anthropic();
|
|
32
|
+
const res = await client.messages.create({
|
|
33
|
+
model: req.model ?? this.defaultModel,
|
|
34
|
+
max_tokens: req.maxTokens ?? 16_000,
|
|
35
|
+
system: req.system,
|
|
36
|
+
messages: [{ role: 'user', content: req.prompt }],
|
|
37
|
+
thinking: { type: 'adaptive' },
|
|
38
|
+
...(req.effort ? { output_config: { effort: req.effort } } : {}),
|
|
39
|
+
});
|
|
40
|
+
const text = res.content
|
|
41
|
+
.filter((c) => c.type === 'text')
|
|
42
|
+
.map((c) => c.text ?? '')
|
|
43
|
+
.join('');
|
|
44
|
+
return { text, usage: usageOf(res.usage), model: res.model };
|
|
45
|
+
}
|
|
46
|
+
async *stream(req) {
|
|
47
|
+
this.ensureKey();
|
|
48
|
+
const client = await this.anthropic();
|
|
49
|
+
if (!client.messages.stream) {
|
|
50
|
+
const r = await this.complete(req);
|
|
51
|
+
yield r.text;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const s = client.messages.stream({
|
|
55
|
+
model: req.model ?? this.defaultModel,
|
|
56
|
+
max_tokens: req.maxTokens ?? 64_000,
|
|
57
|
+
system: req.system,
|
|
58
|
+
messages: [{ role: 'user', content: req.prompt }],
|
|
59
|
+
});
|
|
60
|
+
for await (const ev of s) {
|
|
61
|
+
if (ev?.type === 'content_block_delta' && ev.delta?.type === 'text_delta')
|
|
62
|
+
yield ev.delta.text;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async runAgent(o) {
|
|
66
|
+
this.ensureKey();
|
|
67
|
+
const sdk = await this.sdk();
|
|
68
|
+
const tools = o.tools.map((t) => sdk.tool(t.name, t.description, t.inputSchema, async (args) => t.handler(args), { annotations: t.annotations }));
|
|
69
|
+
const mcpServers = {
|
|
70
|
+
sdods: sdk.createSdkMcpServer({ name: 'sdods', version: '0.1.0', tools }),
|
|
71
|
+
};
|
|
72
|
+
if (this.opts.withPlaywrightMcp !== false)
|
|
73
|
+
mcpServers.playwright = {
|
|
74
|
+
type: 'stdio',
|
|
75
|
+
command: 'npx',
|
|
76
|
+
args: ['playwright', 'mcp', '--headless'],
|
|
77
|
+
};
|
|
78
|
+
for (const [name, cfg] of Object.entries(o.mcpServers ?? {})) {
|
|
79
|
+
if (cfg.transport === 'http' && cfg.url)
|
|
80
|
+
mcpServers[name] = { type: 'http', url: cfg.url, headers: cfg.headers };
|
|
81
|
+
else if (cfg.command)
|
|
82
|
+
mcpServers[name] = {
|
|
83
|
+
type: 'stdio',
|
|
84
|
+
command: cfg.command,
|
|
85
|
+
args: cfg.args ?? [],
|
|
86
|
+
env: cfg.env,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const abort = new AbortController();
|
|
90
|
+
o.signal?.addEventListener('abort', () => abort.abort(), { once: true });
|
|
91
|
+
const q = sdk.query({
|
|
92
|
+
prompt: o.prompt,
|
|
93
|
+
options: {
|
|
94
|
+
systemPrompt: o.system,
|
|
95
|
+
model: o.model ?? this.defaultModel,
|
|
96
|
+
maxTurns: o.maxTurns ?? 40,
|
|
97
|
+
maxBudgetUsd: o.maxBudgetUsd,
|
|
98
|
+
cwd: o.cwd,
|
|
99
|
+
abortController: abort,
|
|
100
|
+
permissionMode: 'default',
|
|
101
|
+
allowedTools: [
|
|
102
|
+
'Read',
|
|
103
|
+
'Glob',
|
|
104
|
+
'Grep',
|
|
105
|
+
'mcp__sdods__*',
|
|
106
|
+
'mcp__playwright__*',
|
|
107
|
+
...Object.keys(o.mcpServers ?? {}).map((n) => `mcp__${n}__*`),
|
|
108
|
+
],
|
|
109
|
+
disallowedTools: [
|
|
110
|
+
'Bash',
|
|
111
|
+
'Write',
|
|
112
|
+
'Edit',
|
|
113
|
+
'MultiEdit',
|
|
114
|
+
'NotebookEdit',
|
|
115
|
+
'WebFetch',
|
|
116
|
+
'WebSearch',
|
|
117
|
+
],
|
|
118
|
+
mcpServers,
|
|
119
|
+
canUseTool: async () => ({
|
|
120
|
+
behavior: 'deny',
|
|
121
|
+
message: 'SDODS agents may only use MCP tools and read-only file tools.',
|
|
122
|
+
}),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
let text = '';
|
|
126
|
+
let turns = 0;
|
|
127
|
+
let toolCalls = 0;
|
|
128
|
+
let result;
|
|
129
|
+
for await (const msg of q) {
|
|
130
|
+
const ev = normalize(msg);
|
|
131
|
+
for (const e of ev) {
|
|
132
|
+
o.onEvent?.(e);
|
|
133
|
+
if (e.type === 'text')
|
|
134
|
+
text += e.text;
|
|
135
|
+
if (e.type === 'tool_call')
|
|
136
|
+
toolCalls++;
|
|
137
|
+
if (e.type === 'result') {
|
|
138
|
+
turns = e.turns;
|
|
139
|
+
result = {
|
|
140
|
+
text,
|
|
141
|
+
costUsd: e.costUsd,
|
|
142
|
+
usage: e.usage,
|
|
143
|
+
turns,
|
|
144
|
+
stopReason: e.stopReason,
|
|
145
|
+
isError: e.isError,
|
|
146
|
+
toolCalls,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return result ?? { text, turns, stopReason: 'end', toolCalls };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Convert Claude Agent SDK messages into SDODS agent events. */
|
|
155
|
+
export function normalize(msg) {
|
|
156
|
+
const out = [];
|
|
157
|
+
switch (msg?.type) {
|
|
158
|
+
case 'assistant': {
|
|
159
|
+
for (const block of msg.message?.content ?? []) {
|
|
160
|
+
if (block.type === 'text' && block.text)
|
|
161
|
+
out.push({ type: 'text', text: block.text });
|
|
162
|
+
if (block.type === 'tool_use')
|
|
163
|
+
out.push({ type: 'tool_call', id: block.id, name: block.name, input: block.input });
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case 'user': {
|
|
168
|
+
for (const block of msg.message?.content ?? []) {
|
|
169
|
+
if (block?.type === 'tool_result')
|
|
170
|
+
out.push({
|
|
171
|
+
type: 'tool_result',
|
|
172
|
+
id: block.tool_use_id,
|
|
173
|
+
output: block.content,
|
|
174
|
+
isError: block.is_error,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case 'result': {
|
|
180
|
+
const usage = msg.usage ? usageOf(msg.usage) : undefined;
|
|
181
|
+
out.push({
|
|
182
|
+
type: 'result',
|
|
183
|
+
costUsd: msg.total_cost_usd,
|
|
184
|
+
usage,
|
|
185
|
+
turns: msg.num_turns ?? 0,
|
|
186
|
+
stopReason: msg.subtype ?? msg.stop_reason ?? 'end',
|
|
187
|
+
isError: Boolean(msg.is_error),
|
|
188
|
+
});
|
|
189
|
+
if (typeof msg.result === 'string' && msg.result)
|
|
190
|
+
out.push({ type: 'text', text: msg.result });
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'system':
|
|
194
|
+
case 'status':
|
|
195
|
+
if (msg.subtype || msg.message)
|
|
196
|
+
out.push({ type: 'status', message: String(msg.subtype ?? msg.message) });
|
|
197
|
+
break;
|
|
198
|
+
default:
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
export function usageOf(u) {
|
|
204
|
+
if (!u)
|
|
205
|
+
return undefined;
|
|
206
|
+
return {
|
|
207
|
+
inputTokens: num(u.input_tokens),
|
|
208
|
+
outputTokens: num(u.output_tokens),
|
|
209
|
+
cacheReadTokens: num(u.cache_read_input_tokens),
|
|
210
|
+
cacheWriteTokens: num(u.cache_creation_input_tokens),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function num(v) {
|
|
214
|
+
return typeof v === 'number' ? v : undefined;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ExternalMcpServerConfig } from './types.js';
|
|
2
|
+
/** Where the sdods MCP server for a project comes from (same resolution as packages/mcp). */
|
|
3
|
+
export interface McpStdioSpec {
|
|
4
|
+
command: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface CliAdapterContext {
|
|
8
|
+
rootDir?: string;
|
|
9
|
+
project?: string;
|
|
10
|
+
env?: string;
|
|
11
|
+
/** injected for tests: binary name/path override */
|
|
12
|
+
bin?: string;
|
|
13
|
+
/** include the bundled Playwright MCP server (default true) */
|
|
14
|
+
withPlaywrightMcp?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Locate a binary on PATH (or an explicit path). Returns null when missing. */
|
|
17
|
+
export declare function findOnPath(bin: string): string | null;
|
|
18
|
+
export declare function sdodsMcpServerSpec(ctx: CliAdapterContext): McpStdioSpec;
|
|
19
|
+
export declare function playwrightMcpSpec(): McpStdioSpec;
|
|
20
|
+
/** Full MCP server map for a run: sdods + playwright + the project's external servers. */
|
|
21
|
+
export declare function mcpServerMap(ctx: CliAdapterContext, external?: Record<string, ExternalMcpServerConfig>): Record<string, Record<string, unknown>>;
|
|
22
|
+
export declare function writeTempJson(prefix: string, value: unknown): string;
|
|
23
|
+
export interface JsonlRunOptions {
|
|
24
|
+
cwd?: string;
|
|
25
|
+
env?: NodeJS.ProcessEnv;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
/** called for every non-empty stdout line; JSON lines are parsed, others passed as strings */
|
|
28
|
+
onLine: (line: unknown, raw: string) => void;
|
|
29
|
+
/** feed this to stdin then close it (default: stdin ignored) */
|
|
30
|
+
stdin?: string;
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface JsonlRunResult {
|
|
34
|
+
exitCode: number;
|
|
35
|
+
stderr: string;
|
|
36
|
+
signal?: NodeJS.Signals | null;
|
|
37
|
+
}
|
|
38
|
+
/** Spawn a CLI that emits one JSON object per stdout line and stream the lines back. */
|
|
39
|
+
export declare function runJsonl(command: string, args: string[], o: JsonlRunOptions): Promise<JsonlRunResult>;
|
|
40
|
+
/** Resolve the binary or throw a clear configuration error with install/login hints. */
|
|
41
|
+
export declare function requireCli(bin: string, install: string): string;
|
|
42
|
+
export declare function runQuiet(command: string, args: string[], timeoutMs?: number): {
|
|
43
|
+
ok: boolean;
|
|
44
|
+
stdout: string;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=cli-common.d.ts.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { delimiter, join } from 'node:path';
|
|
5
|
+
import { resolveCliInvocation } from '@sdods/mcp';
|
|
6
|
+
import { AgentsConfigError } from './types.js';
|
|
7
|
+
/** Locate a binary on PATH (or an explicit path). Returns null when missing. */
|
|
8
|
+
export function findOnPath(bin) {
|
|
9
|
+
if (bin.includes('/'))
|
|
10
|
+
return bin;
|
|
11
|
+
const path = process.env.PATH ?? '';
|
|
12
|
+
const exts = process.platform === 'win32' ? ['.cmd', '.exe', ''] : [''];
|
|
13
|
+
for (const dir of path.split(delimiter)) {
|
|
14
|
+
if (!dir)
|
|
15
|
+
continue;
|
|
16
|
+
for (const ext of exts) {
|
|
17
|
+
const candidate = join(dir, bin + ext);
|
|
18
|
+
try {
|
|
19
|
+
const r = spawnSync('test', ['-x', candidate]);
|
|
20
|
+
if (r.status === 0)
|
|
21
|
+
return candidate;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* ignore */
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export function sdodsMcpServerSpec(ctx) {
|
|
31
|
+
const rootDir = ctx.rootDir ?? process.cwd();
|
|
32
|
+
const inv = resolveCliInvocation(rootDir);
|
|
33
|
+
const args = [...inv.args, 'mcp', '--caps', 'all'];
|
|
34
|
+
if (ctx.project)
|
|
35
|
+
args.push('--project', ctx.project);
|
|
36
|
+
if (ctx.env)
|
|
37
|
+
args.push('--env', ctx.env);
|
|
38
|
+
return { command: inv.command, args };
|
|
39
|
+
}
|
|
40
|
+
export function playwrightMcpSpec() {
|
|
41
|
+
return { command: 'npx', args: ['playwright', 'mcp', '--headless'] };
|
|
42
|
+
}
|
|
43
|
+
/** Full MCP server map for a run: sdods + playwright + the project's external servers. */
|
|
44
|
+
export function mcpServerMap(ctx, external = {}) {
|
|
45
|
+
const servers = {
|
|
46
|
+
sdods: { type: 'stdio', ...sdodsMcpServerSpec(ctx) },
|
|
47
|
+
};
|
|
48
|
+
if (ctx.withPlaywrightMcp !== false)
|
|
49
|
+
servers.playwright = { type: 'stdio', ...playwrightMcpSpec() };
|
|
50
|
+
for (const [name, cfg] of Object.entries(external)) {
|
|
51
|
+
if (cfg.transport === 'http' && cfg.url)
|
|
52
|
+
servers[name] = { type: 'http', url: cfg.url, headers: cfg.headers ?? {} };
|
|
53
|
+
else if (cfg.command)
|
|
54
|
+
servers[name] = {
|
|
55
|
+
type: 'stdio',
|
|
56
|
+
command: cfg.command,
|
|
57
|
+
args: cfg.args ?? [],
|
|
58
|
+
env: cfg.env ?? {},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return servers;
|
|
62
|
+
}
|
|
63
|
+
export function writeTempJson(prefix, value) {
|
|
64
|
+
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
65
|
+
const file = join(dir, 'mcp.json');
|
|
66
|
+
writeFileSync(file, JSON.stringify(value, null, 2));
|
|
67
|
+
return file;
|
|
68
|
+
}
|
|
69
|
+
/** Spawn a CLI that emits one JSON object per stdout line and stream the lines back. */
|
|
70
|
+
export function runJsonl(command, args, o) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const child = spawn(command, args, {
|
|
73
|
+
cwd: o.cwd,
|
|
74
|
+
env: { ...process.env, ...o.env },
|
|
75
|
+
stdio: [o.stdin === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'],
|
|
76
|
+
});
|
|
77
|
+
let stderr = '';
|
|
78
|
+
let buffer = '';
|
|
79
|
+
const handleChunk = (chunk) => {
|
|
80
|
+
buffer += chunk.toString('utf8');
|
|
81
|
+
let idx;
|
|
82
|
+
while ((idx = buffer.indexOf('\n')) >= 0) {
|
|
83
|
+
const raw = buffer.slice(0, idx).replace(/\r$/, '');
|
|
84
|
+
buffer = buffer.slice(idx + 1);
|
|
85
|
+
emit(raw);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const emit = (raw) => {
|
|
89
|
+
const line = raw.trim();
|
|
90
|
+
if (!line)
|
|
91
|
+
return;
|
|
92
|
+
if (line.startsWith('{') || line.startsWith('[')) {
|
|
93
|
+
try {
|
|
94
|
+
o.onLine(JSON.parse(line), raw);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* not JSON */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
o.onLine(raw, raw);
|
|
102
|
+
};
|
|
103
|
+
child.stdout?.on('data', handleChunk);
|
|
104
|
+
child.stderr?.on('data', (c) => {
|
|
105
|
+
stderr += c.toString('utf8');
|
|
106
|
+
});
|
|
107
|
+
const timer = o.timeoutMs ? setTimeout(() => child.kill('SIGTERM'), o.timeoutMs) : undefined;
|
|
108
|
+
const onAbort = () => child.kill('SIGTERM');
|
|
109
|
+
o.signal?.addEventListener('abort', onAbort, { once: true });
|
|
110
|
+
child.on('error', (e) => {
|
|
111
|
+
if (timer)
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
reject(e);
|
|
114
|
+
});
|
|
115
|
+
child.on('close', (code, signal) => {
|
|
116
|
+
if (timer)
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
o.signal?.removeEventListener('abort', onAbort);
|
|
119
|
+
if (buffer.trim())
|
|
120
|
+
emit(buffer);
|
|
121
|
+
resolve({ exitCode: code ?? (signal ? 130 : 1), stderr, signal });
|
|
122
|
+
});
|
|
123
|
+
if (o.stdin !== undefined && child.stdin) {
|
|
124
|
+
child.stdin.end(o.stdin);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** Resolve the binary or throw a clear configuration error with install/login hints. */
|
|
129
|
+
export function requireCli(bin, install) {
|
|
130
|
+
const found = findOnPath(bin);
|
|
131
|
+
if (!found) {
|
|
132
|
+
throw new AgentsConfigError(`The ${bin} CLI is not installed or not on PATH.`, install);
|
|
133
|
+
}
|
|
134
|
+
return found;
|
|
135
|
+
}
|
|
136
|
+
export function runQuiet(command, args, timeoutMs = 15_000) {
|
|
137
|
+
try {
|
|
138
|
+
const r = spawnSync(command, args, { encoding: 'utf8', timeout: timeoutMs });
|
|
139
|
+
return { ok: r.status === 0, stdout: `${r.stdout ?? ''}${r.stderr ?? ''}` };
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return { ok: false, stdout: '' };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=cli-common.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type CliAdapterContext } from './cli-common.js';
|
|
2
|
+
import { type AgentEvent, type CompleteRequest, type CompleteResult, type LlmAdapter, type RunAgentOptions, type RunAgentResult, type TokenUsage } from './types.js';
|
|
3
|
+
/** Codex `exec --json` event stream → SDODS events. */
|
|
4
|
+
export declare function normalizeCodexEvent(ev: Record<string, any>): AgentEvent[];
|
|
5
|
+
export declare function codexUsage(u: Record<string, any> | undefined): TokenUsage | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Runs SDODS agent roles through the user's logged-in OpenAI Codex CLI (`codex exec --json`).
|
|
8
|
+
* No OPENAI_API_KEY is needed when Codex is logged in with a ChatGPT account. The sdods MCP
|
|
9
|
+
* server and the bundled Playwright MCP are injected with `-c mcp_servers.*` overrides; the run
|
|
10
|
+
* is ephemeral, read-only sandboxed, and the system prompt is prepended to the task.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CodexCliAdapter implements LlmAdapter {
|
|
13
|
+
private readonly ctx;
|
|
14
|
+
readonly provider: "codex";
|
|
15
|
+
readonly defaultModel: string;
|
|
16
|
+
constructor(ctx?: CliAdapterContext & {
|
|
17
|
+
model?: string;
|
|
18
|
+
});
|
|
19
|
+
bin(): string;
|
|
20
|
+
static loginStatus(bin?: string): {
|
|
21
|
+
installed: boolean;
|
|
22
|
+
loggedIn: boolean | null;
|
|
23
|
+
detail: string;
|
|
24
|
+
};
|
|
25
|
+
private baseArgs;
|
|
26
|
+
private mcpArgs;
|
|
27
|
+
complete(req: CompleteRequest): Promise<CompleteResult>;
|
|
28
|
+
stream(req: CompleteRequest): AsyncIterable<string>;
|
|
29
|
+
runAgent(o: RunAgentOptions): Promise<RunAgentResult>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=codex.d.ts.map
|