@smithers-orchestrator/agents 0.17.0 → 0.18.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/package.json +4 -4
- package/src/AgentLike.ts +2 -0
- package/src/AnthropicAgent.js +7 -1
- package/src/BaseCliAgent/BaseCliAgent.js +86 -4
- package/src/BaseCliAgent/extractTextFromJsonValue.js +2 -0
- package/src/OpenAIAgent.js +7 -1
- package/src/OpenCodeAgent.js +495 -0
- package/src/OpenCodeAgent.ts +43 -0
- package/src/__type-tests__/AgentLike.assignability.test-d.ts +5 -0
- package/src/capability-registry/AgentCapabilityRegistry.ts +1 -1
- package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +1 -0
- package/src/cli-capabilities/getCliAgentCapabilityReport.js +6 -0
- package/src/index.d.ts +41 -2
- package/src/index.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "AI SDK and CLI agent adapters for Smithers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"ai": "^6.0.168",
|
|
51
51
|
"effect": "^3.21.1",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
|
-
"@smithers-orchestrator/
|
|
54
|
-
"@smithers-orchestrator/
|
|
55
|
-
"@smithers-orchestrator/
|
|
53
|
+
"@smithers-orchestrator/errors": "0.18.0",
|
|
54
|
+
"@smithers-orchestrator/observability": "0.18.0",
|
|
55
|
+
"@smithers-orchestrator/driver": "0.18.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/bun": "latest",
|
package/src/AgentLike.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type AgentLike = {
|
|
|
12
12
|
tools?: Record<string, unknown>;
|
|
13
13
|
/** Optional structured capability registry for cache and diagnostics */
|
|
14
14
|
capabilities?: AgentCapabilityRegistry;
|
|
15
|
+
/** True when the agent consumes outputSchema through a native structured-output API. */
|
|
16
|
+
supportsNativeStructuredOutput?: boolean;
|
|
15
17
|
/**
|
|
16
18
|
* Generates a response or action based on the provided arguments.
|
|
17
19
|
*
|
package/src/AnthropicAgent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
2
|
-
import { ToolLoopAgent, } from "ai";
|
|
2
|
+
import { Output, ToolLoopAgent, } from "ai";
|
|
3
3
|
import { resolveSdkModel } from "./resolveSdkModel.js";
|
|
4
4
|
import { streamResultToGenerateResult } from "./streamResultToGenerateResult.js";
|
|
5
5
|
/** @typedef {import("ai").AgentCallParameters} AgentCallParameters */
|
|
@@ -17,6 +17,7 @@ import { streamResultToGenerateResult } from "./streamResultToGenerateResult.js"
|
|
|
17
17
|
|
|
18
18
|
export class AnthropicAgent extends ToolLoopAgent {
|
|
19
19
|
hijackEngine = "anthropic-sdk";
|
|
20
|
+
supportsNativeStructuredOutput = true;
|
|
20
21
|
/**
|
|
21
22
|
* @param {AnthropicAgentOptions<CALL_OPTIONS, TOOLS>} opts
|
|
22
23
|
*/
|
|
@@ -35,11 +36,15 @@ export class AnthropicAgent extends ToolLoopAgent {
|
|
|
35
36
|
const promptArgs = "messages" in args
|
|
36
37
|
? { messages: args.messages }
|
|
37
38
|
: { prompt: args.prompt };
|
|
39
|
+
const outputArgs = args.outputSchema
|
|
40
|
+
? { output: Output.object({ schema: args.outputSchema }) }
|
|
41
|
+
: {};
|
|
38
42
|
if (!args.onStdout) {
|
|
39
43
|
return super.generate({
|
|
40
44
|
options: args.options,
|
|
41
45
|
abortSignal: args.abortSignal,
|
|
42
46
|
...promptArgs,
|
|
47
|
+
...outputArgs,
|
|
43
48
|
timeout: args.timeout,
|
|
44
49
|
onStepFinish: args.onStepFinish,
|
|
45
50
|
});
|
|
@@ -48,6 +53,7 @@ export class AnthropicAgent extends ToolLoopAgent {
|
|
|
48
53
|
options: args.options,
|
|
49
54
|
abortSignal: args.abortSignal,
|
|
50
55
|
...promptArgs,
|
|
56
|
+
...outputArgs,
|
|
51
57
|
timeout: args.timeout,
|
|
52
58
|
onStepFinish: args.onStepFinish,
|
|
53
59
|
}).then((stream) => streamResultToGenerateResult(stream, args.onStdout));
|
|
@@ -281,15 +281,67 @@ function extractTextFromJsonPayload(raw) {
|
|
|
281
281
|
return text;
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
|
+
// OpenCode-style CLIs emit a final "finish" or "done" event with the
|
|
285
|
+
// complete response text directly on the payload. Prefer this over
|
|
286
|
+
// concatenating all text_delta chunks which would duplicate content.
|
|
287
|
+
if (type === "finish" || type === "done") {
|
|
288
|
+
const text = typeof parsed?.text === "string" ? parsed.text : undefined;
|
|
289
|
+
if (text)
|
|
290
|
+
return text;
|
|
291
|
+
}
|
|
292
|
+
// OpenCode nd-JSON format: "text" events carry part.text with finalized
|
|
293
|
+
// text chunks. Accumulate these as a fallback when the interpreter's
|
|
294
|
+
// completed event isn't surfaced properly.
|
|
295
|
+
if (type === "text" && parsed?.part?.text) {
|
|
296
|
+
// Don't return early — accumulate via the chunks path below
|
|
297
|
+
}
|
|
284
298
|
}
|
|
285
299
|
const chunks = [];
|
|
286
300
|
for (const parsed of parsedLines) {
|
|
287
|
-
|
|
301
|
+
let text;
|
|
302
|
+
if (parsed?.type === "text" && typeof parsed?.part?.text === "string") {
|
|
303
|
+
text = parsed.part.text;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
text = extractTextFromJsonValue(parsed);
|
|
307
|
+
}
|
|
288
308
|
if (text)
|
|
289
309
|
chunks.push(text);
|
|
290
310
|
}
|
|
291
311
|
return chunks.length ? chunks.join("") : undefined;
|
|
292
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* @param {string} raw
|
|
315
|
+
* @returns {string}
|
|
316
|
+
*/
|
|
317
|
+
function stripOscSequences(raw) {
|
|
318
|
+
return raw.replace(/\x1b\]0;[^\x07]*\x07/g, "");
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* @param {string} raw
|
|
322
|
+
* @returns {string | undefined}
|
|
323
|
+
*/
|
|
324
|
+
function extractErrorFromJsonPayload(raw) {
|
|
325
|
+
const trimmed = stripOscSequences(raw).trim();
|
|
326
|
+
if (!trimmed)
|
|
327
|
+
return undefined;
|
|
328
|
+
const lines = trimmed.split(/\r?\n/).filter(Boolean);
|
|
329
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
330
|
+
try {
|
|
331
|
+
const parsed = JSON.parse(lines[i]);
|
|
332
|
+
if (parsed?.type !== "error")
|
|
333
|
+
continue;
|
|
334
|
+
const message = parsed?.error?.data?.message ?? parsed?.error?.message ?? parsed?.error?.name;
|
|
335
|
+
if (typeof message === "string" && message.trim()) {
|
|
336
|
+
return message.trim();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
293
345
|
/**
|
|
294
346
|
* @param {string[]} args
|
|
295
347
|
* @returns {string | undefined}
|
|
@@ -403,7 +455,7 @@ function buildStreamResult(result) {
|
|
|
403
455
|
* @returns {CliUsageInfo | undefined}
|
|
404
456
|
*/
|
|
405
457
|
export function extractUsageFromOutput(raw) {
|
|
406
|
-
const lines = raw.split(/\r?\n/).filter(Boolean);
|
|
458
|
+
const lines = stripOscSequences(raw).split(/\r?\n/).filter(Boolean);
|
|
407
459
|
const usage = {};
|
|
408
460
|
let found = false;
|
|
409
461
|
for (const line of lines) {
|
|
@@ -453,6 +505,25 @@ export function extractUsageFromOutput(raw) {
|
|
|
453
505
|
found = true;
|
|
454
506
|
continue;
|
|
455
507
|
}
|
|
508
|
+
if (parsed.type === "step_finish" && parsed.part?.tokens && typeof parsed.part.tokens === "object") {
|
|
509
|
+
const tokens = parsed.part.tokens;
|
|
510
|
+
const input = tokens.input ?? 0;
|
|
511
|
+
const output = tokens.output ?? 0;
|
|
512
|
+
const total = tokens.total ?? 0;
|
|
513
|
+
const reasoning = tokens.reasoning ?? 0;
|
|
514
|
+
const cacheRead = tokens.cache?.read ?? 0;
|
|
515
|
+
const cacheWrite = tokens.cache?.write ?? 0;
|
|
516
|
+
if (input > 0 || output > 0 || total > 0 || reasoning > 0 || cacheRead > 0 || cacheWrite > 0) {
|
|
517
|
+
usage.inputTokens = (usage.inputTokens ?? 0) + input;
|
|
518
|
+
usage.outputTokens = (usage.outputTokens ?? 0) + output;
|
|
519
|
+
usage.totalTokens = (usage.totalTokens ?? 0) + total;
|
|
520
|
+
usage.reasoningTokens = (usage.reasoningTokens ?? 0) + reasoning;
|
|
521
|
+
usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cacheRead;
|
|
522
|
+
usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + cacheWrite;
|
|
523
|
+
found = true;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
456
527
|
if (parsed.usage && typeof parsed.usage === "object") {
|
|
457
528
|
const u = parsed.usage;
|
|
458
529
|
const inTok = u.input_tokens ?? u.inputTokens ?? u.prompt_tokens ?? 0;
|
|
@@ -690,6 +761,7 @@ export class BaseCliAgent {
|
|
|
690
761
|
const interpreter = this.createOutputInterpreter();
|
|
691
762
|
let stdoutBuffer = "";
|
|
692
763
|
let stderrBuffer = "";
|
|
764
|
+
let completedEvent = null;
|
|
693
765
|
/**
|
|
694
766
|
* @param {AgentCliEvent[] | AgentCliEvent | null | undefined} eventPayload
|
|
695
767
|
*/
|
|
@@ -698,6 +770,9 @@ export class BaseCliAgent {
|
|
|
698
770
|
return;
|
|
699
771
|
const events = Array.isArray(eventPayload) ? eventPayload : [eventPayload];
|
|
700
772
|
for (const event of events) {
|
|
773
|
+
if (event?.type === "completed") {
|
|
774
|
+
completedEvent = event;
|
|
775
|
+
}
|
|
701
776
|
logAgentCliEvent(event, commandLogAnnotations, span);
|
|
702
777
|
if (!options?.onEvent)
|
|
703
778
|
continue;
|
|
@@ -779,7 +854,11 @@ export class BaseCliAgent {
|
|
|
779
854
|
if (result.exitCode && result.exitCode !== 0) {
|
|
780
855
|
const filteredStderr = filterBenignStderr(result.stderr, commandSpec.benignStderrPatterns);
|
|
781
856
|
if (!(commandSpec.command === "codex" && filteredStderr.length === 0)) {
|
|
782
|
-
const
|
|
857
|
+
const structuredError = (commandSpec.outputFormat === "json" || commandSpec.outputFormat === "stream-json")
|
|
858
|
+
? extractErrorFromJsonPayload(result.stdout)
|
|
859
|
+
: undefined;
|
|
860
|
+
const errorText = structuredError ||
|
|
861
|
+
filteredStderr ||
|
|
783
862
|
result.stdout.trim() ||
|
|
784
863
|
`CLI exited with code ${result.exitCode}`;
|
|
785
864
|
const nonRetryable = classifyNonRetryableAgentError(errorText, commandSpec.command);
|
|
@@ -808,6 +887,9 @@ export class BaseCliAgent {
|
|
|
808
887
|
return yield* Effect.fail(new SmithersError("AGENT_CLI_ERROR", errorText));
|
|
809
888
|
}
|
|
810
889
|
}
|
|
890
|
+
if (completedEvent?.ok === false) {
|
|
891
|
+
return yield* Effect.fail(new SmithersError("AGENT_CLI_ERROR", completedEvent.error || "CLI agent reported an error"));
|
|
892
|
+
}
|
|
811
893
|
// Some CLIs may print extra banners to stdout. Allow individual agents
|
|
812
894
|
// to provide patterns so this logic stays opt-in and agent-specific.
|
|
813
895
|
const stdoutBannerPatterns = commandSpec.stdoutBannerPatterns ?? [];
|
|
@@ -853,7 +935,7 @@ export class BaseCliAgent {
|
|
|
853
935
|
textTokens: undefined,
|
|
854
936
|
reasoningTokens: cliUsage.reasoningTokens,
|
|
855
937
|
},
|
|
856
|
-
totalTokens: (cliUsage.inputTokens ?? 0) + (cliUsage.outputTokens ?? 0) || undefined,
|
|
938
|
+
totalTokens: cliUsage.totalTokens ?? ((cliUsage.inputTokens ?? 0) + (cliUsage.outputTokens ?? 0) || undefined),
|
|
857
939
|
} : undefined;
|
|
858
940
|
const tokenTotals = extractAgentTokenTotals(usage);
|
|
859
941
|
stdoutEmitter?.flush(extractedText);
|
|
@@ -32,6 +32,8 @@ export function extractTextFromJsonValue(value) {
|
|
|
32
32
|
if (parts.trim())
|
|
33
33
|
return parts;
|
|
34
34
|
}
|
|
35
|
+
if (record.type === "text" && record.part)
|
|
36
|
+
return extractTextFromJsonValue(record.part);
|
|
35
37
|
if (record.response)
|
|
36
38
|
return extractTextFromJsonValue(record.response);
|
|
37
39
|
if (record.message)
|
package/src/OpenAIAgent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { openai } from "@ai-sdk/openai";
|
|
2
|
-
import { ToolLoopAgent, } from "ai";
|
|
2
|
+
import { Output, ToolLoopAgent, } from "ai";
|
|
3
3
|
import { resolveSdkModel } from "./resolveSdkModel.js";
|
|
4
4
|
import { streamResultToGenerateResult } from "./streamResultToGenerateResult.js";
|
|
5
5
|
/** @typedef {import("ai").AgentCallParameters} AgentCallParameters */
|
|
@@ -17,6 +17,7 @@ import { streamResultToGenerateResult } from "./streamResultToGenerateResult.js"
|
|
|
17
17
|
|
|
18
18
|
export class OpenAIAgent extends ToolLoopAgent {
|
|
19
19
|
hijackEngine = "openai-sdk";
|
|
20
|
+
supportsNativeStructuredOutput = true;
|
|
20
21
|
/**
|
|
21
22
|
* @param {OpenAIAgentOptions<CALL_OPTIONS, TOOLS>} opts
|
|
22
23
|
*/
|
|
@@ -35,11 +36,15 @@ export class OpenAIAgent extends ToolLoopAgent {
|
|
|
35
36
|
const promptArgs = "messages" in args
|
|
36
37
|
? { messages: args.messages }
|
|
37
38
|
: { prompt: args.prompt };
|
|
39
|
+
const outputArgs = args.outputSchema
|
|
40
|
+
? { output: Output.object({ schema: args.outputSchema }) }
|
|
41
|
+
: {};
|
|
38
42
|
if (!args.onStdout) {
|
|
39
43
|
return super.generate({
|
|
40
44
|
options: args.options,
|
|
41
45
|
abortSignal: args.abortSignal,
|
|
42
46
|
...promptArgs,
|
|
47
|
+
...outputArgs,
|
|
43
48
|
timeout: args.timeout,
|
|
44
49
|
onStepFinish: args.onStepFinish,
|
|
45
50
|
});
|
|
@@ -48,6 +53,7 @@ export class OpenAIAgent extends ToolLoopAgent {
|
|
|
48
53
|
options: args.options,
|
|
49
54
|
abortSignal: args.abortSignal,
|
|
50
55
|
...promptArgs,
|
|
56
|
+
...outputArgs,
|
|
51
57
|
timeout: args.timeout,
|
|
52
58
|
onStepFinish: args.onStepFinish,
|
|
53
59
|
}).then((stream) => streamResultToGenerateResult(stream, args.onStdout));
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCliAgent,
|
|
3
|
+
pushFlag,
|
|
4
|
+
isRecord,
|
|
5
|
+
asString,
|
|
6
|
+
truncate,
|
|
7
|
+
toolKindFromName,
|
|
8
|
+
shouldSurfaceUnparsedStdout,
|
|
9
|
+
createSyntheticIdGenerator,
|
|
10
|
+
} from "./BaseCliAgent/index.js";
|
|
11
|
+
import { normalizeCapabilityStringList } from "./capability-registry/index.js";
|
|
12
|
+
|
|
13
|
+
/** @typedef {import("./BaseCliAgent/index.ts").BaseCliAgentOptions} BaseCliAgentOptions */
|
|
14
|
+
/** @typedef {import("./capability-registry/index.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {BaseCliAgentOptions & {
|
|
18
|
+
* model?: string;
|
|
19
|
+
* agentName?: string;
|
|
20
|
+
* attachFiles?: string[];
|
|
21
|
+
* continueSession?: boolean;
|
|
22
|
+
* sessionId?: string;
|
|
23
|
+
* variant?: "high" | "medium" | "low";
|
|
24
|
+
* }} OpenCodeAgentOptions
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** @typedef {import("./BaseCliAgent/index.ts").CliOutputInterpreter} CliOutputInterpreter */
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {OpenCodeAgentOptions} [opts] Currently unused — kept for API
|
|
31
|
+
* consistency with other agents (e.g. ClaudeCodeAgent uses opts to resolve
|
|
32
|
+
* builtIns based on tool allow/deny lists). OpenCode does not yet expose
|
|
33
|
+
* CLI flags for restricting built-in tools, so the set is static.
|
|
34
|
+
* @returns {AgentCapabilityRegistry}
|
|
35
|
+
*/
|
|
36
|
+
export function createOpenCodeCapabilityRegistry(opts = {}) {
|
|
37
|
+
return {
|
|
38
|
+
version: 1,
|
|
39
|
+
engine: "opencode",
|
|
40
|
+
runtimeTools: {},
|
|
41
|
+
mcp: {
|
|
42
|
+
bootstrap: "project-config",
|
|
43
|
+
supportsProjectScope: true,
|
|
44
|
+
supportsUserScope: true,
|
|
45
|
+
},
|
|
46
|
+
skills: {
|
|
47
|
+
supportsSkills: true,
|
|
48
|
+
installMode: "plugin",
|
|
49
|
+
smithersSkillIds: [],
|
|
50
|
+
},
|
|
51
|
+
humanInteraction: {
|
|
52
|
+
supportsUiRequests: false,
|
|
53
|
+
methods: [],
|
|
54
|
+
},
|
|
55
|
+
builtIns: normalizeCapabilityStringList([
|
|
56
|
+
"read",
|
|
57
|
+
"write",
|
|
58
|
+
"edit",
|
|
59
|
+
"apply_patch",
|
|
60
|
+
"bash",
|
|
61
|
+
"glob",
|
|
62
|
+
"grep",
|
|
63
|
+
"list",
|
|
64
|
+
"webfetch",
|
|
65
|
+
"websearch",
|
|
66
|
+
"codesearch",
|
|
67
|
+
"question",
|
|
68
|
+
"task",
|
|
69
|
+
"todowrite",
|
|
70
|
+
"skill",
|
|
71
|
+
]),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* CLI agent wrapper for OpenCode (https://opencode.ai).
|
|
77
|
+
*
|
|
78
|
+
* Shells out to `opencode run` in non-interactive mode with `--format json`
|
|
79
|
+
* for streaming nd-JSON output. Parses AgentCliEvents from the JSON stream.
|
|
80
|
+
*
|
|
81
|
+
* Usage:
|
|
82
|
+
* const agent = new OpenCodeAgent({
|
|
83
|
+
* model: "anthropic/claude-opus-4-20250514",
|
|
84
|
+
* yolo: true,
|
|
85
|
+
* });
|
|
86
|
+
* const result = await agent.generate({
|
|
87
|
+
* messages: [{ role: "user", content: "Fix the bug" }],
|
|
88
|
+
* });
|
|
89
|
+
*/
|
|
90
|
+
export class OpenCodeAgent extends BaseCliAgent {
|
|
91
|
+
/** @type {OpenCodeAgentOptions} */
|
|
92
|
+
opts;
|
|
93
|
+
/** @type {AgentCapabilityRegistry} */
|
|
94
|
+
capabilities;
|
|
95
|
+
/** @type {"opencode"} */
|
|
96
|
+
cliEngine = "opencode";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {OpenCodeAgentOptions} [opts]
|
|
100
|
+
*/
|
|
101
|
+
constructor(opts = {}) {
|
|
102
|
+
super(opts);
|
|
103
|
+
this.opts = opts;
|
|
104
|
+
this.capabilities = createOpenCodeCapabilityRegistry(opts);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create an output interpreter that parses OpenCode's nd-JSON streaming format.
|
|
109
|
+
*
|
|
110
|
+
* OpenCode `--format json` emits one JSON object per line (verified from source:
|
|
111
|
+
* packages/opencode/src/cli/cmd/run.ts). The envelope is:
|
|
112
|
+
*
|
|
113
|
+
* { type, timestamp: number, sessionID: string, ...payload }
|
|
114
|
+
*
|
|
115
|
+
* Event types:
|
|
116
|
+
* step_start → { part: { type:"step-start", id, sessionID, messageID } }
|
|
117
|
+
* text → { part: { type:"text", text, time: { start, end } } }
|
|
118
|
+
* tool_use → { part: { type:"tool", tool, callID, state: { status, ... } } }
|
|
119
|
+
* step_finish → { part: { type:"step-finish", reason, tokens, cost } }
|
|
120
|
+
* reasoning → { part: { type:"reasoning", text } }
|
|
121
|
+
* error → { error: { name, data: { message } } }
|
|
122
|
+
*
|
|
123
|
+
* We map these to Smithers' AgentCliEvent union (started | action | completed).
|
|
124
|
+
*
|
|
125
|
+
* @returns {CliOutputInterpreter}
|
|
126
|
+
*/
|
|
127
|
+
createOutputInterpreter() {
|
|
128
|
+
let fullText = "";
|
|
129
|
+
let sessionId = "";
|
|
130
|
+
let didEmitStarted = false;
|
|
131
|
+
let didEmitCompleted = false;
|
|
132
|
+
let terminalError = null;
|
|
133
|
+
|
|
134
|
+
// Accumulate tokens across multiple step_finish events
|
|
135
|
+
let totalInputTokens = 0;
|
|
136
|
+
let totalOutputTokens = 0;
|
|
137
|
+
let totalTokens = 0;
|
|
138
|
+
|
|
139
|
+
const nextSyntheticId = createSyntheticIdGenerator();
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} title
|
|
143
|
+
* @param {string} message
|
|
144
|
+
* @param {"warning" | "error"} [level]
|
|
145
|
+
* @returns {import("./BaseCliAgent/index.ts").AgentCliEvent}
|
|
146
|
+
*/
|
|
147
|
+
const warningAction = (title, message, level = "warning") => ({
|
|
148
|
+
type: "action",
|
|
149
|
+
engine: this.cliEngine,
|
|
150
|
+
phase: "completed",
|
|
151
|
+
entryType: "thought",
|
|
152
|
+
action: {
|
|
153
|
+
id: nextSyntheticId("opencode-warning"),
|
|
154
|
+
kind: "warning",
|
|
155
|
+
title,
|
|
156
|
+
detail: {},
|
|
157
|
+
},
|
|
158
|
+
message,
|
|
159
|
+
ok: level !== "error",
|
|
160
|
+
level,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {string} line
|
|
165
|
+
* @returns {import("./BaseCliAgent/index.ts").AgentCliEvent[]}
|
|
166
|
+
*/
|
|
167
|
+
const parseLine = (line) => {
|
|
168
|
+
// Strip OSC terminal escape sequences (e.g. title-setting "\x1b]0;...\x07")
|
|
169
|
+
// that OpenCode emits inline with JSON events on stdout.
|
|
170
|
+
const cleaned = line.replace(/\x1b\]0;[^\x07]*\x07/g, "");
|
|
171
|
+
const trimmed = cleaned.trim();
|
|
172
|
+
if (!trimmed) return [];
|
|
173
|
+
|
|
174
|
+
/** @type {Record<string, unknown>} */
|
|
175
|
+
let payload;
|
|
176
|
+
try {
|
|
177
|
+
payload = JSON.parse(trimmed);
|
|
178
|
+
} catch {
|
|
179
|
+
if (!shouldSurfaceUnparsedStdout(trimmed)) return [];
|
|
180
|
+
return [warningAction("stdout", truncate(trimmed, 220))];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!isRecord(payload)) return [];
|
|
184
|
+
|
|
185
|
+
const eventType = asString(payload.type);
|
|
186
|
+
if (!eventType) return [];
|
|
187
|
+
|
|
188
|
+
// Capture sessionID from the envelope (present on every event)
|
|
189
|
+
const envelopeSessionId = asString(payload.sessionID);
|
|
190
|
+
if (envelopeSessionId) {
|
|
191
|
+
sessionId = envelopeSessionId;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const part = isRecord(payload.part) ? payload.part : null;
|
|
195
|
+
|
|
196
|
+
// --- step_start: session/step beginning ---
|
|
197
|
+
if (eventType === "step_start") {
|
|
198
|
+
if (!didEmitStarted) {
|
|
199
|
+
didEmitStarted = true;
|
|
200
|
+
return [
|
|
201
|
+
{
|
|
202
|
+
type: "started",
|
|
203
|
+
engine: this.cliEngine,
|
|
204
|
+
title: "OpenCode",
|
|
205
|
+
resume: sessionId || undefined,
|
|
206
|
+
detail: sessionId ? { sessionId } : undefined,
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// --- text: finalized text chunk from the model ---
|
|
214
|
+
if (eventType === "text") {
|
|
215
|
+
const text = part ? asString(part.text) : null;
|
|
216
|
+
if (text) {
|
|
217
|
+
fullText += text;
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
type: "action",
|
|
221
|
+
engine: this.cliEngine,
|
|
222
|
+
phase: "updated",
|
|
223
|
+
entryType: "message",
|
|
224
|
+
action: {
|
|
225
|
+
id: nextSyntheticId("opencode-text"),
|
|
226
|
+
kind: "note",
|
|
227
|
+
title: "assistant",
|
|
228
|
+
detail: {},
|
|
229
|
+
},
|
|
230
|
+
message: text,
|
|
231
|
+
ok: true,
|
|
232
|
+
level: "info",
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --- tool_use: tool completed or errored ---
|
|
240
|
+
if (eventType === "tool_use" && part) {
|
|
241
|
+
const toolName = asString(part.tool) ?? "tool";
|
|
242
|
+
const callID = asString(part.callID) ?? nextSyntheticId("opencode-tool");
|
|
243
|
+
const state = isRecord(part.state) ? part.state : null;
|
|
244
|
+
const status = state ? asString(state.status) : null;
|
|
245
|
+
const isError = status === "error";
|
|
246
|
+
|
|
247
|
+
const events = [];
|
|
248
|
+
|
|
249
|
+
// Emit a "started" action for the tool
|
|
250
|
+
events.push({
|
|
251
|
+
type: "action",
|
|
252
|
+
engine: this.cliEngine,
|
|
253
|
+
phase: "started",
|
|
254
|
+
entryType: "thought",
|
|
255
|
+
action: {
|
|
256
|
+
id: callID,
|
|
257
|
+
kind: toolKindFromName(toolName),
|
|
258
|
+
title: toolName,
|
|
259
|
+
detail: state && isRecord(state.input)
|
|
260
|
+
? { input: state.input }
|
|
261
|
+
: {},
|
|
262
|
+
},
|
|
263
|
+
message: `Running ${toolName}`,
|
|
264
|
+
level: "info",
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Emit a "completed" action for the tool
|
|
268
|
+
const output = state
|
|
269
|
+
? asString(state.output) ?? asString(state.error)
|
|
270
|
+
: undefined;
|
|
271
|
+
events.push({
|
|
272
|
+
type: "action",
|
|
273
|
+
engine: this.cliEngine,
|
|
274
|
+
phase: "completed",
|
|
275
|
+
entryType: "thought",
|
|
276
|
+
action: {
|
|
277
|
+
id: callID,
|
|
278
|
+
kind: toolKindFromName(toolName),
|
|
279
|
+
title: toolName,
|
|
280
|
+
detail: {},
|
|
281
|
+
},
|
|
282
|
+
message: output ? truncate(output, 300) : undefined,
|
|
283
|
+
ok: !isError,
|
|
284
|
+
level: isError ? "warning" : "info",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return events;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- step_finish: step completed with token usage ---
|
|
291
|
+
if (eventType === "step_finish" && part) {
|
|
292
|
+
const tokens = isRecord(part.tokens) ? part.tokens : null;
|
|
293
|
+
if (tokens) {
|
|
294
|
+
const input = typeof tokens.input === "number" ? tokens.input : 0;
|
|
295
|
+
const output = typeof tokens.output === "number" ? tokens.output : 0;
|
|
296
|
+
const total = typeof tokens.total === "number" ? tokens.total : 0;
|
|
297
|
+
totalInputTokens += input;
|
|
298
|
+
totalOutputTokens += output;
|
|
299
|
+
totalTokens += total;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const reason = asString(part.reason);
|
|
303
|
+
// Only emit "completed" on the final step (reason: "stop")
|
|
304
|
+
if (reason === "stop") {
|
|
305
|
+
if (didEmitCompleted) return [];
|
|
306
|
+
didEmitCompleted = true;
|
|
307
|
+
|
|
308
|
+
return [
|
|
309
|
+
{
|
|
310
|
+
type: "completed",
|
|
311
|
+
engine: this.cliEngine,
|
|
312
|
+
ok: true,
|
|
313
|
+
answer: fullText || undefined,
|
|
314
|
+
resume: sessionId || undefined,
|
|
315
|
+
usage: {
|
|
316
|
+
inputTokens: totalInputTokens,
|
|
317
|
+
outputTokens: totalOutputTokens,
|
|
318
|
+
totalTokens: totalTokens,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --- reasoning: model thinking (only with --thinking flag) ---
|
|
328
|
+
if (eventType === "reasoning") {
|
|
329
|
+
// Surface reasoning as a thought action, don't accumulate into fullText
|
|
330
|
+
const text = part ? asString(part.text) : null;
|
|
331
|
+
if (text) {
|
|
332
|
+
return [
|
|
333
|
+
{
|
|
334
|
+
type: "action",
|
|
335
|
+
engine: this.cliEngine,
|
|
336
|
+
phase: "updated",
|
|
337
|
+
entryType: "thought",
|
|
338
|
+
action: {
|
|
339
|
+
id: nextSyntheticId("opencode-reasoning"),
|
|
340
|
+
kind: "note",
|
|
341
|
+
title: "reasoning",
|
|
342
|
+
detail: {},
|
|
343
|
+
},
|
|
344
|
+
message: truncate(text, 500),
|
|
345
|
+
ok: true,
|
|
346
|
+
level: "info",
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
}
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// --- error: session error ---
|
|
354
|
+
if (eventType === "error") {
|
|
355
|
+
const errorObj = isRecord(payload.error) ? payload.error : null;
|
|
356
|
+
const errorData = errorObj && isRecord(errorObj.data) ? errorObj.data : null;
|
|
357
|
+
const errorName = errorObj ? asString(errorObj.name) : null;
|
|
358
|
+
const errorMessage = errorData
|
|
359
|
+
? asString(errorData.message)
|
|
360
|
+
: errorName ?? "OpenCode reported an error";
|
|
361
|
+
terminalError = errorMessage ?? "OpenCode reported an error";
|
|
362
|
+
|
|
363
|
+
if (didEmitCompleted) {
|
|
364
|
+
return [warningAction("error", errorMessage ?? "OpenCode reported an error", "error")];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
didEmitCompleted = true;
|
|
368
|
+
return [
|
|
369
|
+
{
|
|
370
|
+
type: "completed",
|
|
371
|
+
engine: this.cliEngine,
|
|
372
|
+
ok: false,
|
|
373
|
+
answer: fullText || undefined,
|
|
374
|
+
error: errorMessage ?? "OpenCode reported an error",
|
|
375
|
+
},
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return [];
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
onStdoutLine: parseLine,
|
|
384
|
+
|
|
385
|
+
onStderrLine: (line) => {
|
|
386
|
+
const trimmed = line.trim();
|
|
387
|
+
if (!trimmed) return [];
|
|
388
|
+
return [warningAction("stderr", truncate(trimmed, 220))];
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
onExit: (result) => {
|
|
392
|
+
if (didEmitCompleted) return [];
|
|
393
|
+
const isSuccess = (result.exitCode ?? 0) === 0 && !terminalError;
|
|
394
|
+
didEmitCompleted = true;
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
type: "completed",
|
|
398
|
+
engine: this.cliEngine,
|
|
399
|
+
ok: isSuccess,
|
|
400
|
+
answer: isSuccess ? fullText || undefined : undefined,
|
|
401
|
+
error: isSuccess
|
|
402
|
+
? undefined
|
|
403
|
+
: terminalError ?? `OpenCode exited with code ${result.exitCode ?? -1}`,
|
|
404
|
+
},
|
|
405
|
+
];
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Build the CLI command spec for `opencode run`.
|
|
412
|
+
*
|
|
413
|
+
* @param {{ prompt: string; systemPrompt?: string; cwd: string; options: any }} params
|
|
414
|
+
*/
|
|
415
|
+
async buildCommand(params) {
|
|
416
|
+
const resumeSession = typeof params.options?.resumeSession === "string"
|
|
417
|
+
? params.options.resumeSession
|
|
418
|
+
: undefined;
|
|
419
|
+
const args = ["run"];
|
|
420
|
+
|
|
421
|
+
// Model selection
|
|
422
|
+
pushFlag(args, "-m", this.opts.model ?? this.model);
|
|
423
|
+
|
|
424
|
+
// Working directory
|
|
425
|
+
pushFlag(args, "--dir", params.cwd);
|
|
426
|
+
|
|
427
|
+
// Streaming nd-JSON output
|
|
428
|
+
pushFlag(args, "--format", "json");
|
|
429
|
+
|
|
430
|
+
// Named agent config
|
|
431
|
+
pushFlag(args, "--agent", this.opts.agentName);
|
|
432
|
+
|
|
433
|
+
// File attachments: -f file1 -f file2 (repeated flag)
|
|
434
|
+
if (this.opts.attachFiles) {
|
|
435
|
+
for (const file of this.opts.attachFiles) {
|
|
436
|
+
args.push("-f", file);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Session continuation
|
|
441
|
+
const explicitSession = resumeSession ?? this.opts.sessionId;
|
|
442
|
+
if (this.opts.continueSession && !explicitSession) {
|
|
443
|
+
args.push("--continue");
|
|
444
|
+
}
|
|
445
|
+
pushFlag(args, "--session", explicitSession);
|
|
446
|
+
|
|
447
|
+
// Variant / reasoning effort
|
|
448
|
+
pushFlag(args, "--variant", this.opts.variant);
|
|
449
|
+
|
|
450
|
+
// Yolo mode: auto-approve all tool calls.
|
|
451
|
+
// OpenCode parses OPENCODE_PERMISSION with JSON.parse() and expects a
|
|
452
|
+
// permission object. '{"*":"allow"}' grants blanket approval for every
|
|
453
|
+
// tool category. See: packages/opencode/src/config/config.ts
|
|
454
|
+
const yoloEnabled = this.opts.yolo ?? this.yolo;
|
|
455
|
+
const env = {};
|
|
456
|
+
if (yoloEnabled) {
|
|
457
|
+
env.OPENCODE_PERMISSION = '{"*":"allow"}';
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Extra args from constructor
|
|
461
|
+
if (this.extraArgs?.length) {
|
|
462
|
+
args.push(...this.extraArgs);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const systemPrefix = params.systemPrompt
|
|
466
|
+
? `${params.systemPrompt}\n\n`
|
|
467
|
+
: "";
|
|
468
|
+
const fullPrompt = `${systemPrefix}${params.prompt ?? ""}`;
|
|
469
|
+
|
|
470
|
+
// When flags like -f (yargs [array] type) are present, subsequent
|
|
471
|
+
// positional arguments can be consumed as flag values. Insert '--'
|
|
472
|
+
// to tell yargs to stop parsing flags and treat the rest as positional.
|
|
473
|
+
if (fullPrompt) {
|
|
474
|
+
args.push("--", fullPrompt);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
command: "opencode",
|
|
479
|
+
args,
|
|
480
|
+
outputFormat: "stream-json",
|
|
481
|
+
env: Object.keys(env).length > 0 ? env : undefined,
|
|
482
|
+
stdoutBannerPatterns: [
|
|
483
|
+
// OpenCode may print a version banner
|
|
484
|
+
/^opencode\s+v[\d.]+/gim,
|
|
485
|
+
// Strip OSC terminal title-setting sequences (ESC ] 0 ; ... BEL)
|
|
486
|
+
// OpenCode emits these even with --format json
|
|
487
|
+
/\x1b\]0;[^\x07]*\x07/g,
|
|
488
|
+
],
|
|
489
|
+
stdoutErrorPatterns: [
|
|
490
|
+
/^error:/im,
|
|
491
|
+
/^fatal:/im,
|
|
492
|
+
],
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type CliOutputInterpreter, BaseCliAgent } from "./BaseCliAgent";
|
|
2
|
+
import type { BaseCliAgentOptions } from "./BaseCliAgent";
|
|
3
|
+
import { type AgentCapabilityRegistry } from "./capability-registry";
|
|
4
|
+
|
|
5
|
+
export type OpenCodeAgentOptions = BaseCliAgentOptions & {
|
|
6
|
+
/** Model identifier (e.g., "anthropic/claude-opus-4-20250514", "openai/gpt-5.4") */
|
|
7
|
+
model?: string;
|
|
8
|
+
/** OpenCode agent name (maps to --agent flag, selects predefined agent config) */
|
|
9
|
+
agentName?: string;
|
|
10
|
+
/** Files to attach to the prompt via -f flags */
|
|
11
|
+
attachFiles?: string[];
|
|
12
|
+
/** Continue a previous session */
|
|
13
|
+
continueSession?: boolean;
|
|
14
|
+
/** Resume a specific session by ID */
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
/** Provider-specific model variant/reasoning effort level */
|
|
17
|
+
variant?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export declare function createOpenCodeCapabilityRegistry(
|
|
21
|
+
opts?: OpenCodeAgentOptions
|
|
22
|
+
): AgentCapabilityRegistry;
|
|
23
|
+
|
|
24
|
+
export declare class OpenCodeAgent extends BaseCliAgent {
|
|
25
|
+
private readonly opts: OpenCodeAgentOptions;
|
|
26
|
+
readonly capabilities: AgentCapabilityRegistry;
|
|
27
|
+
readonly cliEngine: "opencode";
|
|
28
|
+
constructor(opts?: OpenCodeAgentOptions);
|
|
29
|
+
createOutputInterpreter(): CliOutputInterpreter;
|
|
30
|
+
buildCommand(params: {
|
|
31
|
+
prompt: string;
|
|
32
|
+
systemPrompt?: string;
|
|
33
|
+
cwd: string;
|
|
34
|
+
options: any;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
command: string;
|
|
37
|
+
args: string[];
|
|
38
|
+
outputFormat: "stream-json";
|
|
39
|
+
env?: Record<string, string>;
|
|
40
|
+
stdoutBannerPatterns: RegExp[];
|
|
41
|
+
stdoutErrorPatterns: RegExp[];
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
@@ -13,6 +13,11 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
type AssertAssignable<T extends AgentLike> = T;
|
|
15
15
|
|
|
16
|
+
type _CustomNativeStructuredAgent = AssertAssignable<{
|
|
17
|
+
supportsNativeStructuredOutput: true;
|
|
18
|
+
generate: () => Promise<unknown>;
|
|
19
|
+
}>;
|
|
20
|
+
|
|
16
21
|
type _ConcreteAgentsAreAgentLike = [
|
|
17
22
|
AssertAssignable<AmpAgent>,
|
|
18
23
|
AssertAssignable<AnthropicAgent>,
|
|
@@ -2,7 +2,7 @@ import type { AgentToolDescriptor } from "./AgentToolDescriptor";
|
|
|
2
2
|
|
|
3
3
|
export type AgentCapabilityRegistry = {
|
|
4
4
|
version: 1;
|
|
5
|
-
engine: "claude-code" | "codex" | "gemini" | "kimi" | "pi" | "amp" | "forge";
|
|
5
|
+
engine: "claude-code" | "codex" | "gemini" | "kimi" | "pi" | "amp" | "forge" | "opencode";
|
|
6
6
|
runtimeTools: Record<string, AgentToolDescriptor>;
|
|
7
7
|
mcp: {
|
|
8
8
|
bootstrap: "inline-config" | "project-config" | "allow-list" | "unsupported";
|
|
@@ -3,6 +3,7 @@ import { createClaudeCodeCapabilityRegistry } from "../ClaudeCodeAgent.js";
|
|
|
3
3
|
import { createCodexCapabilityRegistry } from "../CodexAgent.js";
|
|
4
4
|
import { createGeminiCapabilityRegistry } from "../GeminiAgent.js";
|
|
5
5
|
import { createKimiCapabilityRegistry } from "../KimiAgent.js";
|
|
6
|
+
import { createOpenCodeCapabilityRegistry } from "../OpenCodeAgent.js";
|
|
6
7
|
import { createPiCapabilityRegistry } from "../PiAgent.js";
|
|
7
8
|
/** @typedef {import("./CliAgentCapabilityReportEntry.ts").CliAgentCapabilityReportEntry} CliAgentCapabilityReportEntry */
|
|
8
9
|
|
|
@@ -27,6 +28,11 @@ const CLI_AGENT_CAPABILITY_ADAPTERS = [
|
|
|
27
28
|
binary: "kimi",
|
|
28
29
|
buildRegistry: () => createKimiCapabilityRegistry(),
|
|
29
30
|
},
|
|
31
|
+
{
|
|
32
|
+
id: "opencode",
|
|
33
|
+
binary: "opencode",
|
|
34
|
+
buildRegistry: () => createOpenCodeCapabilityRegistry(),
|
|
35
|
+
},
|
|
30
36
|
{
|
|
31
37
|
id: "pi",
|
|
32
38
|
binary: "pi",
|
package/src/index.d.ts
CHANGED
|
@@ -112,7 +112,7 @@ type AgentToolDescriptor$1 = {
|
|
|
112
112
|
|
|
113
113
|
type AgentCapabilityRegistry$3 = {
|
|
114
114
|
version: 1;
|
|
115
|
-
engine: "claude-code" | "codex" | "gemini" | "kimi" | "pi" | "amp" | "forge";
|
|
115
|
+
engine: "claude-code" | "codex" | "gemini" | "kimi" | "pi" | "amp" | "forge" | "opencode";
|
|
116
116
|
runtimeTools: Record<string, AgentToolDescriptor$1>;
|
|
117
117
|
mcp: {
|
|
118
118
|
bootstrap: "inline-config" | "project-config" | "allow-list" | "unsupported";
|
|
@@ -151,6 +151,8 @@ type AgentLike$1 = {
|
|
|
151
151
|
tools?: Record<string, unknown>;
|
|
152
152
|
/** Optional structured capability registry for cache and diagnostics */
|
|
153
153
|
capabilities?: AgentCapabilityRegistry$1;
|
|
154
|
+
/** True when the agent consumes outputSchema through a native structured-output API. */
|
|
155
|
+
supportsNativeStructuredOutput?: boolean;
|
|
154
156
|
/**
|
|
155
157
|
* Generates a response or action based on the provided arguments.
|
|
156
158
|
*
|
|
@@ -762,6 +764,42 @@ declare class ForgeAgent extends BaseCliAgent {
|
|
|
762
764
|
type CliOutputInterpreter = CliOutputInterpreter$8;
|
|
763
765
|
type ForgeAgentOptions = ForgeAgentOptions$1;
|
|
764
766
|
|
|
767
|
+
type OpenCodeAgentOptions$1 = BaseCliAgentOptions$1 & {
|
|
768
|
+
/** Model identifier (e.g., "anthropic/claude-opus-4-20250514", "openai/gpt-5.4") */
|
|
769
|
+
model?: string;
|
|
770
|
+
/** OpenCode agent name (maps to --agent flag, selects predefined agent config) */
|
|
771
|
+
agentName?: string;
|
|
772
|
+
/** Files to attach to the prompt via -f flags */
|
|
773
|
+
attachFiles?: string[];
|
|
774
|
+
/** Continue a previous session */
|
|
775
|
+
continueSession?: boolean;
|
|
776
|
+
/** Resume a specific session by ID */
|
|
777
|
+
sessionId?: string;
|
|
778
|
+
/** Provider-specific model variant/reasoning effort level */
|
|
779
|
+
variant?: string;
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
declare class OpenCodeAgent extends BaseCliAgent {
|
|
783
|
+
constructor(opts?: OpenCodeAgentOptions);
|
|
784
|
+
opts: OpenCodeAgentOptions$1;
|
|
785
|
+
capabilities: AgentCapabilityRegistry$3;
|
|
786
|
+
cliEngine: "opencode";
|
|
787
|
+
createOutputInterpreter(): CliOutputInterpreter;
|
|
788
|
+
buildCommand(params: {
|
|
789
|
+
prompt: string;
|
|
790
|
+
systemPrompt?: string;
|
|
791
|
+
cwd: string;
|
|
792
|
+
options: any;
|
|
793
|
+
}): Promise<{
|
|
794
|
+
command: string;
|
|
795
|
+
args: string[];
|
|
796
|
+
outputFormat: "stream-json";
|
|
797
|
+
env?: Record<string, string>;
|
|
798
|
+
stdoutBannerPatterns: RegExp[];
|
|
799
|
+
stdoutErrorPatterns: RegExp[];
|
|
800
|
+
}>;
|
|
801
|
+
}
|
|
802
|
+
|
|
765
803
|
/**
|
|
766
804
|
* @param {CreateSmithersAgentContractOptions} options
|
|
767
805
|
* @returns {SmithersAgentContract}
|
|
@@ -821,6 +859,7 @@ type AgentCapabilityRegistry = AgentCapabilityRegistry$3;
|
|
|
821
859
|
type AgentLike = AgentLike$1;
|
|
822
860
|
type AgentToolDescriptor = AgentToolDescriptor$1;
|
|
823
861
|
type AnthropicAgentOptions<CALL_OPTIONS = never, TOOLS = ai.ToolSet> = AnthropicAgentOptions$2<CALL_OPTIONS, TOOLS>;
|
|
862
|
+
type OpenCodeAgentOptions = OpenCodeAgentOptions$1;
|
|
824
863
|
type OpenAIAgentOptions<CALL_OPTIONS = never, TOOLS = ai.ToolSet> = OpenAIAgentOptions$2<CALL_OPTIONS, TOOLS>;
|
|
825
864
|
type PiAgentOptions = PiAgentOptions$2;
|
|
826
865
|
type PiExtensionUiRequest = PiExtensionUiRequest$1;
|
|
@@ -831,4 +870,4 @@ type SmithersAgentToolCategory = SmithersAgentToolCategory$1;
|
|
|
831
870
|
type SmithersListedTool = SmithersListedTool$2;
|
|
832
871
|
type SmithersToolSurface = SmithersToolSurface$2;
|
|
833
872
|
|
|
834
|
-
export { type AgentCapabilityRegistry, type AgentGenerateOptions, type AgentLike, type AgentToolDescriptor, AmpAgent, AnthropicAgent, type AnthropicAgentOptions, BaseCliAgent, ClaudeCodeAgent, CodexAgent, ForgeAgent, GeminiAgent, KimiAgent, OpenAIAgent, type OpenAIAgentOptions, PiAgent, type PiAgentOptions, type PiExtensionUiRequest, type PiExtensionUiResponse, type SmithersAgentContract, type SmithersAgentContractTool, type SmithersAgentToolCategory, type SmithersListedTool, type SmithersToolSurface, createSmithersAgentContract, hashCapabilityRegistry, renderSmithersAgentPromptGuidance, sanitizeForOpenAI, zodToOpenAISchema };
|
|
873
|
+
export { type AgentCapabilityRegistry, type AgentGenerateOptions, type AgentLike, type AgentToolDescriptor, AmpAgent, AnthropicAgent, type AnthropicAgentOptions, BaseCliAgent, ClaudeCodeAgent, CodexAgent, ForgeAgent, GeminiAgent, KimiAgent, OpenAIAgent, type OpenAIAgentOptions, OpenCodeAgent, type OpenCodeAgentOptions, PiAgent, type PiAgentOptions, type PiExtensionUiRequest, type PiExtensionUiResponse, type SmithersAgentContract, type SmithersAgentContractTool, type SmithersAgentToolCategory, type SmithersListedTool, type SmithersToolSurface, createSmithersAgentContract, hashCapabilityRegistry, renderSmithersAgentPromptGuidance, sanitizeForOpenAI, zodToOpenAISchema };
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
/** @typedef {import("./PiAgentOptions.ts").PiAgentOptions} PiAgentOptions */
|
|
17
17
|
/** @typedef {import("./BaseCliAgent/PiExtensionUiRequest.ts").PiExtensionUiRequest} PiExtensionUiRequest */
|
|
18
18
|
/** @typedef {import("./BaseCliAgent/PiExtensionUiResponse.ts").PiExtensionUiResponse} PiExtensionUiResponse */
|
|
19
|
+
/** @typedef {import("./OpenCodeAgent.ts").OpenCodeAgentOptions} OpenCodeAgentOptions */
|
|
19
20
|
/** @typedef {import("./agent-contract/SmithersAgentContract.ts").SmithersAgentContract} SmithersAgentContract */
|
|
20
21
|
/** @typedef {import("./agent-contract/SmithersAgentContractTool.ts").SmithersAgentContractTool} SmithersAgentContractTool */
|
|
21
22
|
/** @typedef {import("./agent-contract/SmithersAgentToolCategory.ts").SmithersAgentToolCategory} SmithersAgentToolCategory */
|
|
@@ -34,6 +35,7 @@ export { GeminiAgent } from "./GeminiAgent.js";
|
|
|
34
35
|
export { PiAgent } from "./PiAgent.js";
|
|
35
36
|
export { KimiAgent } from "./KimiAgent.js";
|
|
36
37
|
export { ForgeAgent } from "./ForgeAgent.js";
|
|
38
|
+
export { OpenCodeAgent } from "./OpenCodeAgent.js";
|
|
37
39
|
export { createSmithersAgentContract } from "./agent-contract/createSmithersAgentContract.js";
|
|
38
40
|
export { renderSmithersAgentPromptGuidance } from "./agent-contract/renderSmithersAgentPromptGuidance.js";
|
|
39
41
|
export { zodToOpenAISchema } from "./zodToOpenAISchema.js";
|