@smithers-orchestrator/agents 0.21.0 → 0.23.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 (34) hide show
  1. package/package.json +6 -5
  2. package/src/AmpAgent.js +26 -19
  3. package/src/AntigravityAgent.js +53 -18
  4. package/src/AntigravityAgentOptions.ts +45 -4
  5. package/src/BaseCliAgent/AgentGenerateOptions.ts +12 -0
  6. package/src/BaseCliAgent/BaseCliAgent.js +19 -1
  7. package/src/BaseCliAgent/runRpcCommandEffect.js +7 -3
  8. package/src/BaseCliAgent/taskContextEnv.js +31 -0
  9. package/src/BaseCliAgent/truncateToBytes.js +18 -1
  10. package/src/ClaudeCodeAgent.js +19 -1
  11. package/src/CodexAgent.js +15 -8
  12. package/src/ForgeAgent.js +26 -19
  13. package/src/HermesAgent.js +41 -0
  14. package/src/HermesAgentOptions.ts +41 -0
  15. package/src/VibeAgent.js +214 -0
  16. package/src/VibeAgentOptions.ts +11 -0
  17. package/src/agent-contract/createSmithersAgentContract.js +1 -0
  18. package/src/agent-contract/renderSmithersAgentPromptGuidance.js +4 -0
  19. package/src/capability-registry/AgentCapabilityRegistry.ts +1 -1
  20. package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +4 -1
  21. package/src/cli-capabilities/CliAgentCapabilityReportEntry.ts +2 -0
  22. package/src/cli-capabilities/getCliAgentCapabilityDoctorReport.js +48 -1
  23. package/src/cli-capabilities/getCliAgentCapabilityReport.js +24 -0
  24. package/src/cli-capabilities/index.js +5 -0
  25. package/src/cli-surface/CliAgentSurfaceTypes.ts +34 -0
  26. package/src/cli-surface/cliAgentSurfaceManifest.js +490 -0
  27. package/src/cli-surface/index.js +5 -0
  28. package/src/diagnostics/runDiagnostics.js +9 -1
  29. package/src/index.d.ts +715 -380
  30. package/src/index.js +27 -0
  31. package/src/mcp/McpServerConfig.ts +19 -0
  32. package/src/mcp/McpToolset.ts +17 -0
  33. package/src/mcp/createMcpToolset.js +94 -0
  34. package/src/sanitizeForOpenAI.js +20 -17
@@ -0,0 +1,41 @@
1
+ import type { openai } from "@ai-sdk/openai";
2
+ import type { ToolSet } from "ai";
3
+ import type { SdkAgentOptions } from "./SdkAgentOptions";
4
+
5
+ /**
6
+ * Options for {@link HermesAgent}.
7
+ *
8
+ * Hermes (Nous Research) exposes an OpenAI-compatible HTTP API
9
+ * (`/v1/chat/completions`), so a Hermes agent is reached the same way as any
10
+ * OpenAI-compatible endpoint: point `baseURL` at the Hermes server. These mirror
11
+ * the string-model form of `OpenAIAgentOptions`.
12
+ */
13
+ export type HermesAgentOptions<
14
+ CALL_OPTIONS = never,
15
+ TOOLS extends ToolSet = {},
16
+ > = Omit<
17
+ SdkAgentOptions<CALL_OPTIONS, TOOLS, ReturnType<typeof openai>>,
18
+ "model"
19
+ > & {
20
+ /**
21
+ * Model name exposed by your Hermes server. Defaults to `"hermes"`; override
22
+ * with whatever model id the server advertises.
23
+ */
24
+ model?: string;
25
+ /**
26
+ * Base URL of the Hermes OpenAI-compatible API, e.g. `http://127.0.0.1:5123/v1`.
27
+ * Falls back to the `HERMES_BASE_URL` environment variable.
28
+ */
29
+ baseURL?: string;
30
+ /**
31
+ * API key sent to the Hermes server. Falls back to `HERMES_API_KEY`, then
32
+ * `"hermes"` (local servers commonly ignore the value).
33
+ */
34
+ apiKey?: string;
35
+ /**
36
+ * Enable AI SDK native structured output. Off by default because a local
37
+ * Hermes server may not honor JSON-schema response formats — leaving it off
38
+ * makes Smithers fall back to prompt-based JSON extraction.
39
+ */
40
+ nativeStructuredOutput?: boolean;
41
+ };
@@ -0,0 +1,214 @@
1
+ import {
2
+ BaseCliAgent,
3
+ pushFlag,
4
+ isRecord,
5
+ asString,
6
+ createSyntheticIdGenerator,
7
+ } from "./BaseCliAgent/index.js";
8
+
9
+ /** @typedef {import("./BaseCliAgent/index.ts").BaseCliAgentOptions} BaseCliAgentOptions */
10
+ /** @typedef {import("./capability-registry/index.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
11
+ /** @typedef {import("./BaseCliAgent/index.ts").CliOutputInterpreter} CliOutputInterpreter */
12
+
13
+ /**
14
+ * @param {VibeAgentOptions} [opts]
15
+ * @returns {AgentCapabilityRegistry}
16
+ */
17
+ export function createVibeCapabilityRegistry(opts = {}) {
18
+ return {
19
+ version: 1,
20
+ engine: "vibe",
21
+ runtimeTools: {},
22
+ mcp: {
23
+ bootstrap: "project-config",
24
+ supportsProjectScope: true,
25
+ supportsUserScope: true,
26
+ },
27
+ skills: {
28
+ supportsSkills: true,
29
+ installMode: "plugin",
30
+ smithersSkillIds: [],
31
+ },
32
+ humanInteraction: {
33
+ supportsUiRequests: false,
34
+ methods: [],
35
+ },
36
+ builtIns: ["default"],
37
+ };
38
+ }
39
+
40
+ /**
41
+ * @typedef {BaseCliAgentOptions & {
42
+ * agent?: string;
43
+ * maxTurns?: number;
44
+ * maxPrice?: number;
45
+ * maxTokens?: number;
46
+ * enabledTools?: string[];
47
+ * sessionId?: string;
48
+ * continueSession?: boolean;
49
+ * }} VibeAgentOptions
50
+ */
51
+
52
+ export class VibeAgent extends BaseCliAgent {
53
+ /** @type {VibeAgentOptions} */
54
+ opts;
55
+ /** @type {AgentCapabilityRegistry} */
56
+ capabilities;
57
+ /** @type {"vibe"} */
58
+ cliEngine = "vibe";
59
+ /** @type {string | undefined} */
60
+ issuedSessionId;
61
+
62
+ /**
63
+ * @param {VibeAgentOptions} [opts]
64
+ */
65
+ constructor(opts = {}) {
66
+ super({ ...opts, yolo: opts.yolo ?? false });
67
+ this.opts = opts;
68
+ this.capabilities = createVibeCapabilityRegistry(opts);
69
+ }
70
+
71
+ /**
72
+ * @returns {CliOutputInterpreter}
73
+ */
74
+ createOutputInterpreter() {
75
+ let finalAnswer = "";
76
+ let didEmitStarted = false;
77
+ let didEmitCompleted = false;
78
+ const nextSyntheticId = createSyntheticIdGenerator();
79
+
80
+ /**
81
+ * @param {string} line
82
+ * @returns {import("./BaseCliAgent/index.ts").AgentCliEvent[]}
83
+ */
84
+ const parseLine = (line) => {
85
+ const trimmed = line.trim();
86
+ if (!trimmed) return [];
87
+
88
+ /** @type {Record<string, unknown>} */
89
+ let payload;
90
+ try {
91
+ payload = JSON.parse(trimmed);
92
+ } catch {
93
+ return [];
94
+ }
95
+
96
+ if (!isRecord(payload)) return [];
97
+ const role = asString(payload.role);
98
+ const content = asString(payload.content);
99
+ if (role !== "assistant" || !content) return [];
100
+
101
+ const events = [];
102
+
103
+ if (!didEmitStarted) {
104
+ didEmitStarted = true;
105
+ events.push({
106
+ type: "started",
107
+ engine: this.cliEngine,
108
+ title: "Vibe",
109
+ resume: this.issuedSessionId,
110
+ });
111
+ }
112
+
113
+ const id = nextSyntheticId("vibe-message");
114
+ finalAnswer += content;
115
+
116
+ events.push({
117
+ type: "action",
118
+ engine: this.cliEngine,
119
+ phase: "updated",
120
+ entryType: "message",
121
+ action: { id, kind: "turn", title: "assistant" },
122
+ message: content,
123
+ });
124
+
125
+ return events;
126
+ };
127
+
128
+ return {
129
+ onStdoutLine: parseLine,
130
+
131
+ onExit: (result) => {
132
+ if (didEmitCompleted) return [];
133
+ didEmitCompleted = true;
134
+
135
+ const ok = (result.exitCode ?? 0) === 0;
136
+
137
+ const events = [];
138
+ if (!didEmitStarted) {
139
+ events.push({
140
+ type: "started",
141
+ engine: this.cliEngine,
142
+ title: "Vibe",
143
+ resume: this.issuedSessionId,
144
+ });
145
+ }
146
+
147
+ events.push({
148
+ type: "completed",
149
+ engine: this.cliEngine,
150
+ ok,
151
+ answer: ok ? finalAnswer || undefined : undefined,
152
+ error: ok
153
+ ? undefined
154
+ : result.stderr?.trim() ||
155
+ `vibe exited with code ${result.exitCode ?? -1}`,
156
+ resume: this.issuedSessionId,
157
+ });
158
+
159
+ return events;
160
+ },
161
+ };
162
+ }
163
+
164
+ /**
165
+ * @param {{ prompt: string; systemPrompt?: string; cwd: string; options: any }} params
166
+ */
167
+ async buildCommand(params) {
168
+ const args = [];
169
+
170
+ const resumeSession = typeof params.options?.resumeSession === "string"
171
+ ? params.options.resumeSession
172
+ : undefined;
173
+ const effectiveSession = resumeSession ?? this.opts.sessionId;
174
+ this.issuedSessionId = effectiveSession;
175
+ if (this.opts.continueSession && !effectiveSession) {
176
+ args.push("-c");
177
+ }
178
+ pushFlag(args, "--resume", effectiveSession);
179
+
180
+ pushFlag(args, "--agent", this.opts.agent);
181
+ if (this.opts.maxTurns !== undefined) {
182
+ args.push("--max-turns", String(this.opts.maxTurns));
183
+ }
184
+ if (this.opts.maxPrice !== undefined) {
185
+ args.push("--max-price", String(this.opts.maxPrice));
186
+ }
187
+ if (this.opts.maxTokens !== undefined) {
188
+ args.push("--max-tokens", String(this.opts.maxTokens));
189
+ }
190
+ if (this.opts.enabledTools?.length) {
191
+ for (const tool of this.opts.enabledTools) {
192
+ args.push("--enabled-tools", tool);
193
+ }
194
+ }
195
+
196
+ args.push("--trust");
197
+ args.push("--output", "streaming");
198
+ pushFlag(args, "--workdir", params.cwd);
199
+
200
+ if (this.extraArgs?.length) args.push(...this.extraArgs);
201
+
202
+ const systemPrefix = params.systemPrompt
203
+ ? `${params.systemPrompt}\n\n`
204
+ : "";
205
+ const fullPrompt = `${systemPrefix}${params.prompt ?? ""}`;
206
+ pushFlag(args, "--prompt", fullPrompt);
207
+
208
+ return {
209
+ command: "vibe",
210
+ args,
211
+ outputFormat: "stream-json",
212
+ };
213
+ }
214
+ }
@@ -0,0 +1,11 @@
1
+ import type { BaseCliAgentOptions } from "./BaseCliAgent/BaseCliAgentOptions";
2
+
3
+ export type VibeAgentOptions = BaseCliAgentOptions & {
4
+ agent?: string;
5
+ maxTurns?: number;
6
+ maxPrice?: number;
7
+ maxTokens?: number;
8
+ enabledTools?: string[];
9
+ sessionId?: string;
10
+ continueSession?: boolean;
11
+ };
@@ -30,6 +30,7 @@ const WORKFLOW_TOOL_NAMES = new Set([
30
30
  ]);
31
31
  const APPROVAL_TOOL_NAMES = new Set([
32
32
  "approve",
33
+ "ask_human",
33
34
  "deny",
34
35
  "list_pending_approvals",
35
36
  "resolve_approval",
@@ -77,5 +77,9 @@ export function renderSmithersAgentPromptGuidance(contract, options = {}) {
77
77
  if (destructiveTools.length > 0) {
78
78
  lines.push(`Potentially destructive tools: ${joinToolNames(destructiveTools, prefix)}. Confirm intent before using them unless the user already asked for that action.`);
79
79
  }
80
+ const askHumanTool = contract.tools.find((tool) => tool.name === "ask_human");
81
+ if (askHumanTool) {
82
+ lines.push(`When you are blocked, uncertain, missing information, or about to take an irreversible or destructive action, you MUST call ${displayToolName(askHumanTool, prefix)} (or run \`smithers ask-human "<question>"\`) to ask a human and wait for the decision. Never guess or proceed on an assumption — if the request comes back blocked (cancelled/expired), stop and do not proceed.`);
83
+ }
80
84
  return lines.join("\n");
81
85
  }
@@ -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" | "antigravity" | "gemini" | "kimi" | "pi" | "amp" | "forge" | "opencode";
5
+ engine: "claude-code" | "codex" | "antigravity" | "gemini" | "kimi" | "pi" | "amp" | "forge" | "opencode" | "vibe";
6
6
  runtimeTools: Record<string, AgentToolDescriptor>;
7
7
  mcp: {
8
8
  bootstrap: "inline-config" | "project-config" | "allow-list" | "unsupported";
@@ -1,8 +1,11 @@
1
1
  export type CliAgentCapabilityAdapterId =
2
2
  | "claude"
3
+ | "amp"
3
4
  | "antigravity"
4
5
  | "codex"
6
+ | "forge"
5
7
  | "gemini"
6
8
  | "kimi"
7
9
  | "opencode"
8
- | "pi";
10
+ | "pi"
11
+ | "vibe";
@@ -1,4 +1,5 @@
1
1
  import type { AgentCapabilityRegistry } from "../capability-registry";
2
+ import type { CliAgentSurfaceManifestEntry } from "../cli-surface/CliAgentSurfaceTypes";
2
3
  import type { CliAgentCapabilityAdapterId } from "./CliAgentCapabilityAdapterId";
3
4
 
4
5
  export type CliAgentCapabilityReportEntry = {
@@ -6,4 +7,5 @@ export type CliAgentCapabilityReportEntry = {
6
7
  binary: string;
7
8
  fingerprint: string;
8
9
  capabilities: AgentCapabilityRegistry;
10
+ surface: CliAgentSurfaceManifestEntry;
9
11
  };
@@ -1,5 +1,8 @@
1
1
  import { getCliAgentCapabilityReport } from "./getCliAgentCapabilityReport.js";
2
2
  /** @typedef {import("./CliAgentCapabilityDoctorReport.ts").CliAgentCapabilityDoctorReport} CliAgentCapabilityDoctorReport */
3
+ /** @typedef {import("./CliAgentCapabilityDoctorReport.ts").CliAgentCapabilityIssue} CliAgentCapabilityIssue */
4
+ /** @typedef {import("./CliAgentCapabilityReportEntry.ts").CliAgentCapabilityReportEntry} CliAgentCapabilityReportEntry */
5
+ /** @typedef {import("../capability-registry/AgentCapabilityRegistry.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
3
6
 
4
7
  /**
5
8
  * @param {AgentCapabilityRegistry} registry
@@ -71,12 +74,56 @@ function diagnoseCapabilityRegistry(registry) {
71
74
  }
72
75
  return issues;
73
76
  }
77
+
78
+ /**
79
+ * @param {CliAgentCapabilityReportEntry} entry
80
+ * @returns {CliAgentCapabilityIssue[]}
81
+ */
82
+ function diagnoseSurfaceContract(entry) {
83
+ const issues = [];
84
+ const emitted = new Set(entry.surface.emittedFlags);
85
+ for (const unsupported of entry.surface.unsupportedFlags) {
86
+ if (emitted.has(unsupported.flag)) {
87
+ issues.push({
88
+ code: "unsupported-flag-emitted",
89
+ message: `${entry.id} emits ${unsupported.flag}, but the surface manifest marks it unsupported.${unsupported.replacement ? ` Replacement: ${unsupported.replacement}.` : ""}`,
90
+ severity: "error",
91
+ });
92
+ }
93
+ }
94
+ if (entry.surface.binary !== entry.binary) {
95
+ issues.push({
96
+ code: "surface-binary-mismatch",
97
+ message: `${entry.id} capability binary is ${entry.binary}, but the surface manifest says ${entry.surface.binary}.`,
98
+ severity: "error",
99
+ });
100
+ }
101
+ if (entry.surface.docsUrls.length === 0) {
102
+ issues.push({
103
+ code: "missing-surface-docs",
104
+ message: `${entry.id} has no source URL recorded for its CLI surface.`,
105
+ severity: "warning",
106
+ });
107
+ }
108
+ if (entry.surface.resume.kind !== "none" && entry.surface.resume.emitted.length === 0) {
109
+ issues.push({
110
+ code: "missing-resume-emission",
111
+ message: `${entry.id} declares ${entry.surface.resume.kind} resume support but no emitted resume tokens.`,
112
+ severity: "warning",
113
+ });
114
+ }
115
+ return issues;
116
+ }
117
+
74
118
  /**
75
119
  * @returns {CliAgentCapabilityDoctorReport}
76
120
  */
77
121
  export function getCliAgentCapabilityDoctorReport() {
78
122
  const agents = getCliAgentCapabilityReport().map((entry) => {
79
- const issues = diagnoseCapabilityRegistry(entry.capabilities);
123
+ const issues = [
124
+ ...diagnoseCapabilityRegistry(entry.capabilities),
125
+ ...diagnoseSurfaceContract(entry),
126
+ ];
80
127
  return {
81
128
  ...entry,
82
129
  ok: issues.length === 0,
@@ -1,14 +1,23 @@
1
1
  import { hashCapabilityRegistry, normalizeCapabilityRegistry, } from "../capability-registry/index.js";
2
+ import { createAmpCapabilityRegistry } from "../AmpAgent.js";
2
3
  import { createAntigravityCapabilityRegistry } from "../AntigravityAgent.js";
3
4
  import { createClaudeCodeCapabilityRegistry } from "../ClaudeCodeAgent.js";
4
5
  import { createCodexCapabilityRegistry } from "../CodexAgent.js";
6
+ import { createForgeCapabilityRegistry } from "../ForgeAgent.js";
5
7
  import { createGeminiCapabilityRegistry } from "../GeminiAgent.js";
6
8
  import { createKimiCapabilityRegistry } from "../KimiAgent.js";
7
9
  import { createOpenCodeCapabilityRegistry } from "../OpenCodeAgent.js";
8
10
  import { createPiCapabilityRegistry } from "../PiAgent.js";
11
+ import { createVibeCapabilityRegistry } from "../VibeAgent.js";
12
+ import { getCliAgentSurfaceManifestEntry } from "../cli-surface/index.js";
9
13
  /** @typedef {import("./CliAgentCapabilityReportEntry.ts").CliAgentCapabilityReportEntry} CliAgentCapabilityReportEntry */
10
14
 
11
15
  const CLI_AGENT_CAPABILITY_ADAPTERS = [
16
+ {
17
+ id: "amp",
18
+ binary: "amp",
19
+ buildRegistry: () => createAmpCapabilityRegistry(),
20
+ },
12
21
  {
13
22
  id: "claude",
14
23
  binary: "claude",
@@ -29,6 +38,11 @@ const CLI_AGENT_CAPABILITY_ADAPTERS = [
29
38
  binary: "gemini",
30
39
  buildRegistry: () => createGeminiCapabilityRegistry(),
31
40
  },
41
+ {
42
+ id: "forge",
43
+ binary: "forge",
44
+ buildRegistry: () => createForgeCapabilityRegistry(),
45
+ },
32
46
  {
33
47
  id: "kimi",
34
48
  binary: "kimi",
@@ -44,6 +58,11 @@ const CLI_AGENT_CAPABILITY_ADAPTERS = [
44
58
  binary: "pi",
45
59
  buildRegistry: () => createPiCapabilityRegistry(),
46
60
  },
61
+ {
62
+ id: "vibe",
63
+ binary: "vibe",
64
+ buildRegistry: () => createVibeCapabilityRegistry(),
65
+ },
47
66
  ];
48
67
  /**
49
68
  * @returns {CliAgentCapabilityReportEntry[]}
@@ -54,11 +73,16 @@ export function getCliAgentCapabilityReport() {
54
73
  if (!capabilities) {
55
74
  throw new Error(`Capability registry missing for adapter ${adapter.id}`);
56
75
  }
76
+ const surface = getCliAgentSurfaceManifestEntry(adapter.id);
77
+ if (!surface) {
78
+ throw new Error(`CLI surface manifest missing for adapter ${adapter.id}`);
79
+ }
57
80
  return {
58
81
  id: adapter.id,
59
82
  binary: adapter.binary,
60
83
  fingerprint: hashCapabilityRegistry(capabilities),
61
84
  capabilities,
85
+ surface,
62
86
  };
63
87
  });
64
88
  }
@@ -9,3 +9,8 @@
9
9
  export { getCliAgentCapabilityReport } from "./getCliAgentCapabilityReport.js";
10
10
  export { getCliAgentCapabilityDoctorReport } from "./getCliAgentCapabilityDoctorReport.js";
11
11
  export { formatCliAgentCapabilityDoctorReport } from "./formatCliAgentCapabilityDoctorReport.js";
12
+ export {
13
+ CLI_AGENT_SURFACE_MANIFEST,
14
+ getCliAgentSurfaceManifestEntry,
15
+ listCliAgentSurfaceManifests,
16
+ } from "../cli-surface/index.js";
@@ -0,0 +1,34 @@
1
+ import type { CliAgentCapabilityAdapterId } from "../cli-capabilities/CliAgentCapabilityAdapterId";
2
+
3
+ export type CliAgentSurfaceOptionMapping = {
4
+ option: string;
5
+ flag?: string;
6
+ env?: string;
7
+ notes?: string;
8
+ };
9
+
10
+ export type CliAgentUnsupportedFlag = {
11
+ flag: string;
12
+ replacement?: string;
13
+ reason: string;
14
+ };
15
+
16
+ export type CliAgentSurfaceResumeContract = {
17
+ kind: "flag" | "subcommand" | "env" | "none";
18
+ emitted: string[];
19
+ notes: string;
20
+ };
21
+
22
+ export type CliAgentSurfaceManifestEntry = {
23
+ id: CliAgentCapabilityAdapterId;
24
+ displayName: string;
25
+ binary: string;
26
+ packageExport: string;
27
+ defaultOutputFormat: "text" | "json" | "stream-json" | "rpc";
28
+ docsUrls: string[];
29
+ emittedFlags: string[];
30
+ supportedFlags: string[];
31
+ unsupportedFlags: CliAgentUnsupportedFlag[];
32
+ optionMappings: CliAgentSurfaceOptionMapping[];
33
+ resume: CliAgentSurfaceResumeContract;
34
+ };