@smithers-orchestrator/agents 0.16.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +65 -0
  3. package/src/AgentLike.ts +28 -0
  4. package/src/AmpAgent.js +232 -0
  5. package/src/AmpAgentOptions.ts +26 -0
  6. package/src/AnthropicAgent.js +54 -0
  7. package/src/AnthropicAgentOptions.ts +8 -0
  8. package/src/BaseCliAgent/AgentCliActionKind.ts +10 -0
  9. package/src/BaseCliAgent/AgentCliEvent.ts +44 -0
  10. package/src/BaseCliAgent/BaseCliAgent.js +874 -0
  11. package/src/BaseCliAgent/BaseCliAgentOptions.ts +13 -0
  12. package/src/BaseCliAgent/CliOutputInterpreter.ts +8 -0
  13. package/src/BaseCliAgent/CliUsageInfo.ts +7 -0
  14. package/src/BaseCliAgent/CodexConfigOverrides.ts +3 -0
  15. package/src/BaseCliAgent/PiExtensionUiRequest.ts +10 -0
  16. package/src/BaseCliAgent/PiExtensionUiResponse.ts +7 -0
  17. package/src/BaseCliAgent/RunCommandResult.ts +5 -0
  18. package/src/BaseCliAgent/buildGenerateResult.js +57 -0
  19. package/src/BaseCliAgent/combineNonEmpty.js +8 -0
  20. package/src/BaseCliAgent/createAgentStdoutTextEmitter.js +198 -0
  21. package/src/BaseCliAgent/extractPrompt.js +88 -0
  22. package/src/BaseCliAgent/extractTextFromJsonValue.js +46 -0
  23. package/src/BaseCliAgent/index.js +32 -0
  24. package/src/BaseCliAgent/normalizeCodexConfig.js +22 -0
  25. package/src/BaseCliAgent/parseHelpers.js +111 -0
  26. package/src/BaseCliAgent/pushFlag.js +18 -0
  27. package/src/BaseCliAgent/pushList.js +10 -0
  28. package/src/BaseCliAgent/resolveTimeouts.js +24 -0
  29. package/src/BaseCliAgent/runCommandEffect.js +32 -0
  30. package/src/BaseCliAgent/runRpcCommandEffect.js +365 -0
  31. package/src/BaseCliAgent/truncateToBytes.js +13 -0
  32. package/src/BaseCliAgent/tryParseJson.js +18 -0
  33. package/src/ClaudeCodeAgent.js +455 -0
  34. package/src/ClaudeCodeAgentOptions.ts +52 -0
  35. package/src/CodexAgent.js +593 -0
  36. package/src/CodexAgentOptions.ts +23 -0
  37. package/src/ForgeAgent.js +128 -0
  38. package/src/ForgeAgentOptions.ts +14 -0
  39. package/src/GeminiAgent.js +273 -0
  40. package/src/GeminiAgentOptions.ts +20 -0
  41. package/src/KimiAgent.js +260 -0
  42. package/src/KimiAgentOptions.ts +21 -0
  43. package/src/OpenAIAgent.js +54 -0
  44. package/src/OpenAIAgentOptions.ts +8 -0
  45. package/src/PiAgent.js +468 -0
  46. package/src/PiAgentOptions.ts +40 -0
  47. package/src/SdkAgentOptions.ts +16 -0
  48. package/src/agent-contract/SmithersAgentContract.ts +10 -0
  49. package/src/agent-contract/SmithersAgentContractTool.ts +8 -0
  50. package/src/agent-contract/SmithersAgentToolCategory.ts +6 -0
  51. package/src/agent-contract/SmithersListedTool.ts +4 -0
  52. package/src/agent-contract/SmithersToolSurface.ts +1 -0
  53. package/src/agent-contract/createSmithersAgentContract.js +188 -0
  54. package/src/agent-contract/index.js +10 -0
  55. package/src/agent-contract/renderSmithersAgentPromptGuidance.js +81 -0
  56. package/src/capability-registry/AgentCapabilityRegistry.ts +22 -0
  57. package/src/capability-registry/AgentToolDescriptor.ts +4 -0
  58. package/src/capability-registry/hashCapabilityRegistry.js +43 -0
  59. package/src/capability-registry/index.js +8 -0
  60. package/src/capability-registry/normalizeCapabilityRegistry.js +52 -0
  61. package/src/capability-registry/normalizeCapabilityStringList.js +9 -0
  62. package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +6 -0
  63. package/src/cli-capabilities/CliAgentCapabilityDoctorReport.ts +18 -0
  64. package/src/cli-capabilities/CliAgentCapabilityReportEntry.ts +9 -0
  65. package/src/cli-capabilities/formatCliAgentCapabilityDoctorReport.js +24 -0
  66. package/src/cli-capabilities/getCliAgentCapabilityDoctorReport.js +92 -0
  67. package/src/cli-capabilities/getCliAgentCapabilityReport.js +52 -0
  68. package/src/cli-capabilities/index.js +11 -0
  69. package/src/diagnostics/DiagnosticCheck.ts +11 -0
  70. package/src/diagnostics/DiagnosticCheckId.ts +4 -0
  71. package/src/diagnostics/DiagnosticContext.ts +4 -0
  72. package/src/diagnostics/DiagnosticReport.ts +9 -0
  73. package/src/diagnostics/enrichReportWithErrorAnalysis.js +34 -0
  74. package/src/diagnostics/formatDiagnosticSummary.js +17 -0
  75. package/src/diagnostics/getDiagnosticStrategy.js +503 -0
  76. package/src/diagnostics/index.js +13 -0
  77. package/src/diagnostics/launchDiagnostics.js +16 -0
  78. package/src/diagnostics/runDiagnostics.js +52 -0
  79. package/src/index.d.ts +872 -0
  80. package/src/index.js +39 -0
  81. package/src/resolveSdkModel.js +9 -0
  82. package/src/sanitizeForOpenAI.js +47 -0
  83. package/src/streamResultToGenerateResult.js +70 -0
  84. package/src/zodToOpenAISchema.js +16 -0
@@ -0,0 +1,128 @@
1
+ import { BaseCliAgent, pushFlag, } from "./BaseCliAgent/index.js";
2
+ import { randomUUID } from "node:crypto";
3
+ /** @typedef {import("./BaseCliAgent/BaseCliAgentOptions.ts").BaseCliAgentOptions} BaseCliAgentOptions */
4
+ /** @typedef {import("./BaseCliAgent/CliOutputInterpreter.ts").CliOutputInterpreter} CliOutputInterpreter */
5
+ /** @typedef {import("./ForgeAgentOptions.ts").ForgeAgentOptions} ForgeAgentOptions */
6
+
7
+ export class ForgeAgent extends BaseCliAgent {
8
+ opts;
9
+ capabilities;
10
+ cliEngine = "forge";
11
+ issuedConversationId;
12
+ /**
13
+ * @param {ForgeAgentOptions} [opts]
14
+ */
15
+ constructor(opts = {}) {
16
+ super(opts);
17
+ this.opts = opts;
18
+ this.capabilities = {
19
+ version: 1,
20
+ engine: "forge",
21
+ runtimeTools: {},
22
+ mcp: {
23
+ bootstrap: "unsupported",
24
+ supportsProjectScope: false,
25
+ supportsUserScope: false,
26
+ },
27
+ skills: {
28
+ supportsSkills: false,
29
+ smithersSkillIds: [],
30
+ },
31
+ humanInteraction: {
32
+ supportsUiRequests: false,
33
+ methods: [],
34
+ },
35
+ builtIns: ["default"],
36
+ };
37
+ }
38
+ /**
39
+ * @returns {CliOutputInterpreter}
40
+ */
41
+ createOutputInterpreter() {
42
+ let emittedStarted = false;
43
+ return {
44
+ onStdoutLine: () => {
45
+ if (emittedStarted)
46
+ return [];
47
+ emittedStarted = true;
48
+ return [{
49
+ type: "started",
50
+ engine: this.cliEngine,
51
+ title: "Forge",
52
+ resume: this.issuedConversationId,
53
+ }];
54
+ },
55
+ onExit: (result) => {
56
+ const started = !emittedStarted && this.issuedConversationId
57
+ ? [{
58
+ type: "started",
59
+ engine: this.cliEngine,
60
+ title: "Forge",
61
+ resume: this.issuedConversationId,
62
+ }]
63
+ : [];
64
+ return [
65
+ ...started,
66
+ {
67
+ type: "completed",
68
+ engine: this.cliEngine,
69
+ ok: !result.exitCode || result.exitCode === 0,
70
+ answer: result.stdout.trim() || undefined,
71
+ error: result.exitCode && result.exitCode !== 0
72
+ ? result.stderr.trim() || `Forge exited with code ${result.exitCode}`
73
+ : undefined,
74
+ resume: this.issuedConversationId,
75
+ },
76
+ ];
77
+ },
78
+ };
79
+ }
80
+ /**
81
+ * @param {{ prompt: string; systemPrompt?: string; cwd: string; options: any; }} params
82
+ */
83
+ async buildCommand(params) {
84
+ const args = [];
85
+ // Model
86
+ pushFlag(args, "--model", this.opts.model ?? this.model);
87
+ // Provider
88
+ pushFlag(args, "--provider", this.opts.provider);
89
+ // Agent type
90
+ pushFlag(args, "--agent", this.opts.agent);
91
+ // Conversation ID
92
+ const resumeSession = typeof params.options?.resumeSession === "string"
93
+ ? params.options.resumeSession
94
+ : undefined;
95
+ this.issuedConversationId = resumeSession ?? this.opts.conversationId ?? randomUUID();
96
+ pushFlag(args, "--conversation-id", this.issuedConversationId);
97
+ // Sandbox
98
+ pushFlag(args, "--sandbox", this.opts.sandbox);
99
+ // Restricted mode
100
+ if (this.opts.restricted)
101
+ args.push("--restricted");
102
+ // Verbose
103
+ if (this.opts.verbose)
104
+ args.push("--verbose");
105
+ // Workflow file
106
+ pushFlag(args, "--workflow", this.opts.workflow);
107
+ // Event JSON
108
+ pushFlag(args, "--event", this.opts.event);
109
+ // Conversation file
110
+ pushFlag(args, "--conversation", this.opts.conversation);
111
+ // Directory — default to cwd
112
+ pushFlag(args, "-C", this.opts.directory ?? params.cwd);
113
+ if (this.extraArgs?.length)
114
+ args.push(...this.extraArgs);
115
+ // Build prompt with system prompt prepended
116
+ const systemPrefix = params.systemPrompt
117
+ ? `${params.systemPrompt}\n\n`
118
+ : "";
119
+ const fullPrompt = `${systemPrefix}${params.prompt ?? ""}`;
120
+ // Pass prompt via --prompt flag
121
+ pushFlag(args, "--prompt", fullPrompt);
122
+ return {
123
+ command: "forge",
124
+ args,
125
+ outputFormat: "text",
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,14 @@
1
+ import type { BaseCliAgentOptions } from "./BaseCliAgent/BaseCliAgentOptions";
2
+
3
+ export type ForgeAgentOptions = BaseCliAgentOptions & {
4
+ directory?: string;
5
+ provider?: string;
6
+ agent?: string;
7
+ conversationId?: string;
8
+ sandbox?: string;
9
+ restricted?: boolean;
10
+ verbose?: boolean;
11
+ workflow?: string;
12
+ event?: string;
13
+ conversation?: string;
14
+ };
@@ -0,0 +1,273 @@
1
+ import { BaseCliAgent, pushFlag, pushList, isRecord, asString, truncate, toolKindFromName, createSyntheticIdGenerator, } from "./BaseCliAgent/index.js";
2
+ import { normalizeCapabilityStringList, } from "./capability-registry/index.js";
3
+ /** @typedef {import("./BaseCliAgent/BaseCliAgentOptions.ts").BaseCliAgentOptions} BaseCliAgentOptions */
4
+ /** @typedef {import("./capability-registry/AgentCapabilityRegistry.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
5
+ /** @typedef {import("./BaseCliAgent/CliOutputInterpreter.ts").CliOutputInterpreter} CliOutputInterpreter */
6
+ /** @typedef {import("./GeminiAgentOptions.ts").GeminiAgentOptions} GeminiAgentOptions */
7
+
8
+ /**
9
+ * @param {GeminiAgentOptions} opts
10
+ */
11
+ function resolveGeminiBuiltIns(opts) {
12
+ return opts.allowedTools?.length
13
+ ? normalizeCapabilityStringList(opts.allowedTools)
14
+ : ["default"];
15
+ }
16
+ /**
17
+ * @param {GeminiAgentOptions} [opts]
18
+ * @returns {AgentCapabilityRegistry}
19
+ */
20
+ export function createGeminiCapabilityRegistry(opts = {}) {
21
+ return {
22
+ version: 1,
23
+ engine: "gemini",
24
+ runtimeTools: {},
25
+ mcp: {
26
+ bootstrap: "allow-list",
27
+ supportsProjectScope: false,
28
+ supportsUserScope: true,
29
+ },
30
+ skills: {
31
+ supportsSkills: false,
32
+ smithersSkillIds: [],
33
+ },
34
+ humanInteraction: {
35
+ supportsUiRequests: false,
36
+ methods: [],
37
+ },
38
+ builtIns: resolveGeminiBuiltIns(opts),
39
+ };
40
+ }
41
+ export class GeminiAgent extends BaseCliAgent {
42
+ opts;
43
+ capabilities;
44
+ cliEngine = "gemini";
45
+ /**
46
+ * @param {GeminiAgentOptions} [opts]
47
+ */
48
+ constructor(opts = {}) {
49
+ super(opts);
50
+ this.opts = opts;
51
+ this.capabilities = createGeminiCapabilityRegistry(opts);
52
+ }
53
+ /**
54
+ * @returns {CliOutputInterpreter}
55
+ */
56
+ createOutputInterpreter() {
57
+ let sessionId;
58
+ let finalAnswer = "";
59
+ let emittedStarted = false;
60
+ let didEmitCompleted = false;
61
+ const nextSyntheticId = createSyntheticIdGenerator();
62
+ /**
63
+ * @param {string} line
64
+ * @returns {AgentCliEvent[]}
65
+ */
66
+ const parseLine = (line) => {
67
+ const trimmed = line.trim();
68
+ if (!trimmed)
69
+ return [];
70
+ let payload;
71
+ try {
72
+ payload = JSON.parse(trimmed);
73
+ }
74
+ catch {
75
+ return [];
76
+ }
77
+ if (!isRecord(payload))
78
+ return [];
79
+ const type = asString(payload.type);
80
+ if (!type)
81
+ return [];
82
+ if (type === "init") {
83
+ const resume = asString(payload.session_id);
84
+ if (resume) {
85
+ sessionId = resume;
86
+ }
87
+ emittedStarted = true;
88
+ return [{
89
+ type: "started",
90
+ engine: this.cliEngine,
91
+ title: "Gemini CLI",
92
+ resume: sessionId,
93
+ detail: {
94
+ model: asString(payload.model),
95
+ },
96
+ }];
97
+ }
98
+ if (type === "MESSAGE") {
99
+ const role = asString(payload.role);
100
+ const content = asString(payload.content);
101
+ if (role === "assistant" && content) {
102
+ if (payload.delta === true) {
103
+ finalAnswer += content;
104
+ }
105
+ else {
106
+ finalAnswer = content;
107
+ }
108
+ }
109
+ return [];
110
+ }
111
+ if (type === "TOOL_USE") {
112
+ const toolName = asString(payload.tool_name) ?? "tool";
113
+ const toolId = asString(payload.tool_id) ?? nextSyntheticId("gemini-tool");
114
+ return [{
115
+ type: "action",
116
+ engine: this.cliEngine,
117
+ phase: "started",
118
+ entryType: "thought",
119
+ action: {
120
+ id: toolId,
121
+ kind: toolKindFromName(toolName),
122
+ title: toolName,
123
+ detail: {
124
+ parameters: payload.parameters,
125
+ },
126
+ },
127
+ message: `Running ${toolName}`,
128
+ level: "info",
129
+ }];
130
+ }
131
+ if (type === "TOOL_RESULT") {
132
+ const toolId = asString(payload.tool_id) ?? nextSyntheticId("gemini-tool");
133
+ const ok = asString(payload.status) !== "error";
134
+ const error = isRecord(payload.error) ? asString(payload.error.message) : undefined;
135
+ const output = asString(payload.output);
136
+ return [{
137
+ type: "action",
138
+ engine: this.cliEngine,
139
+ phase: "completed",
140
+ entryType: "thought",
141
+ action: {
142
+ id: toolId,
143
+ kind: "tool",
144
+ title: "tool result",
145
+ detail: {
146
+ status: asString(payload.status),
147
+ output: output ? truncate(output, 400) : undefined,
148
+ },
149
+ },
150
+ message: error ?? output,
151
+ ok,
152
+ level: ok ? "info" : "warning",
153
+ }];
154
+ }
155
+ if (type === "ERROR") {
156
+ return [{
157
+ type: "action",
158
+ engine: this.cliEngine,
159
+ phase: "completed",
160
+ entryType: "thought",
161
+ action: {
162
+ id: nextSyntheticId("gemini-warning"),
163
+ kind: "warning",
164
+ title: "warning",
165
+ detail: {
166
+ severity: asString(payload.severity),
167
+ },
168
+ },
169
+ message: asString(payload.message),
170
+ ok: asString(payload.severity) !== "error",
171
+ level: asString(payload.severity) === "error" ? "error" : "warning",
172
+ }];
173
+ }
174
+ if (type === "RESULT") {
175
+ if (didEmitCompleted)
176
+ return [];
177
+ didEmitCompleted = true;
178
+ return [{
179
+ type: "completed",
180
+ engine: this.cliEngine,
181
+ ok: asString(payload.status) !== "error",
182
+ answer: finalAnswer || asString(payload.response),
183
+ resume: sessionId,
184
+ usage: isRecord(payload.stats) ? payload.stats : undefined,
185
+ }];
186
+ }
187
+ return [];
188
+ };
189
+ return {
190
+ onStdoutLine: parseLine,
191
+ onExit: (result) => {
192
+ if (didEmitCompleted)
193
+ return [];
194
+ if (result.exitCode === 0)
195
+ return [];
196
+ didEmitCompleted = true;
197
+ return [{
198
+ type: "completed",
199
+ engine: this.cliEngine,
200
+ ok: false,
201
+ answer: finalAnswer || undefined,
202
+ error: result.stderr.trim() || `Gemini exited with code ${result.exitCode}`,
203
+ resume: sessionId,
204
+ }];
205
+ },
206
+ };
207
+ }
208
+ /**
209
+ * @param {{ prompt: string; systemPrompt?: string; cwd: string; options: any; }} params
210
+ */
211
+ async buildCommand(params) {
212
+ const args = [];
213
+ const yoloEnabled = this.opts.yolo ?? this.yolo;
214
+ // Default to "json" output format to separate model responses from tool
215
+ // output text. With "text" format, tool call results (file contents etc.)
216
+ // are concatenated into the response, making JSON extraction unreliable.
217
+ const outputFormat = this.opts.outputFormat ??
218
+ (params.options?.onEvent ? "stream-json" : "json");
219
+ const resumeSession = typeof params.options?.resumeSession === "string"
220
+ ? params.options.resumeSession
221
+ : this.opts.resume;
222
+ if (this.opts.debug)
223
+ args.push("--debug");
224
+ pushFlag(args, "--model", this.opts.model ?? this.model);
225
+ if (this.opts.sandbox)
226
+ args.push("--sandbox");
227
+ if (this.opts.approvalMode) {
228
+ pushFlag(args, "--approval-mode", this.opts.approvalMode);
229
+ }
230
+ else if (yoloEnabled) {
231
+ args.push("--yolo");
232
+ }
233
+ if (this.opts.experimentalAcp)
234
+ args.push("--experimental-acp");
235
+ pushList(args, "--allowed-mcp-server-names", this.opts.allowedMcpServerNames);
236
+ if (this.opts.allowedTools !== undefined) {
237
+ if (this.opts.allowedTools.length === 0) {
238
+ pushFlag(args, "--allowed-tools", "");
239
+ }
240
+ else {
241
+ pushList(args, "--allowed-tools", this.opts.allowedTools);
242
+ }
243
+ }
244
+ pushList(args, "--extensions", this.opts.extensions);
245
+ if (this.opts.listExtensions)
246
+ args.push("--list-extensions");
247
+ pushFlag(args, "--resume", resumeSession);
248
+ if (this.opts.listSessions)
249
+ args.push("--list-sessions");
250
+ pushFlag(args, "--delete-session", this.opts.deleteSession);
251
+ pushList(args, "--include-directories", this.opts.includeDirectories);
252
+ if (this.opts.screenReader)
253
+ args.push("--screen-reader");
254
+ pushFlag(args, "--output-format", outputFormat);
255
+ if (this.extraArgs?.length)
256
+ args.push(...this.extraArgs);
257
+ const systemPrefix = params.systemPrompt
258
+ ? `${params.systemPrompt}\n\n`
259
+ : "";
260
+ // Reinforce JSON output requirement in the prompt for Gemini models which
261
+ // tend to forget structured output instructions on long responses.
262
+ const jsonReminder = params.prompt?.includes("REQUIRED OUTPUT")
263
+ ? "\n\nREMINDER: Your response MUST end with a ```json code fence containing the required JSON object. Do NOT skip this step — the pipeline will reject your response without it.\n"
264
+ : "";
265
+ const fullPrompt = `${systemPrefix}${params.prompt ?? ""}${jsonReminder}`;
266
+ args.push("--prompt", fullPrompt);
267
+ return {
268
+ command: "gemini",
269
+ args,
270
+ outputFormat,
271
+ };
272
+ }
273
+ }
@@ -0,0 +1,20 @@
1
+ import type { BaseCliAgentOptions } from "./BaseCliAgent/BaseCliAgentOptions";
2
+
3
+ export type GeminiAgentOptions = BaseCliAgentOptions & {
4
+ debug?: boolean;
5
+ model?: string;
6
+ sandbox?: boolean;
7
+ yolo?: boolean;
8
+ approvalMode?: "default" | "auto_edit" | "yolo" | "plan";
9
+ experimentalAcp?: boolean;
10
+ allowedMcpServerNames?: string[];
11
+ allowedTools?: string[];
12
+ extensions?: string[];
13
+ listExtensions?: boolean;
14
+ resume?: string;
15
+ listSessions?: boolean;
16
+ deleteSession?: string;
17
+ includeDirectories?: string[];
18
+ screenReader?: boolean;
19
+ outputFormat?: "text" | "json" | "stream-json";
20
+ };