@smithers-orchestrator/agents 0.23.0 → 0.24.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 +9 -4
- package/src/BaseCliAgent/BaseCliAgent.js +72 -7
- package/src/BaseCliAgent/RunCommandResult.ts +4 -0
- package/src/BaseCliAgent/runCommandEffect.js +3 -2
- package/src/diagnostics/getDiagnosticStrategy.js +1 -1
- package/src/mcp/McpToolsetOptions.ts +16 -0
- package/src/mcp/createMcpToolset.d.ts +12 -0
- package/src/mcp/createMcpToolset.js +1 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "AI SDK and CLI agent adapters for Smithers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
"import": "./src/cli-capabilities/index.js",
|
|
36
36
|
"default": "./src/cli-capabilities/index.js"
|
|
37
37
|
},
|
|
38
|
+
"./mcp/createMcpToolset": {
|
|
39
|
+
"types": "./src/mcp/createMcpToolset.d.ts",
|
|
40
|
+
"import": "./src/mcp/createMcpToolset.js",
|
|
41
|
+
"default": "./src/mcp/createMcpToolset.js"
|
|
42
|
+
},
|
|
38
43
|
"./*": {
|
|
39
44
|
"types": "./src/index.d.ts",
|
|
40
45
|
"import": "./src/*.js",
|
|
@@ -51,9 +56,9 @@
|
|
|
51
56
|
"ai": "^6.0.168",
|
|
52
57
|
"effect": "^3.21.1",
|
|
53
58
|
"zod": "^4.3.6",
|
|
54
|
-
"@smithers-orchestrator/
|
|
55
|
-
"@smithers-orchestrator/
|
|
56
|
-
"@smithers-orchestrator/observability": "0.
|
|
59
|
+
"@smithers-orchestrator/driver": "0.24.0",
|
|
60
|
+
"@smithers-orchestrator/errors": "0.24.0",
|
|
61
|
+
"@smithers-orchestrator/observability": "0.24.0"
|
|
57
62
|
},
|
|
58
63
|
"devDependencies": {
|
|
59
64
|
"@types/bun": "latest",
|
|
@@ -451,6 +451,27 @@ function buildStreamResult(result) {
|
|
|
451
451
|
fullStream: fullStream,
|
|
452
452
|
};
|
|
453
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Fallback when truncated stdout lost the per-message usage events: the
|
|
456
|
+
* interpreter's completed event carries the harness usage summary (#277).
|
|
457
|
+
* @param {{ usage?: unknown } | null} completedEvent
|
|
458
|
+
* @returns {CliUsageInfo | undefined}
|
|
459
|
+
*/
|
|
460
|
+
function usageFromCompletedEvent(completedEvent) {
|
|
461
|
+
const u = completedEvent?.usage;
|
|
462
|
+
if (!u || typeof u !== "object" || Array.isArray(u))
|
|
463
|
+
return undefined;
|
|
464
|
+
const num = (value) => (typeof value === "number" && Number.isFinite(value) ? value : undefined);
|
|
465
|
+
const usage = {
|
|
466
|
+
inputTokens: num(u.input_tokens) ?? num(u.inputTokens),
|
|
467
|
+
outputTokens: num(u.output_tokens) ?? num(u.outputTokens),
|
|
468
|
+
cacheReadTokens: num(u.cache_read_input_tokens) ?? num(u.cacheReadTokens),
|
|
469
|
+
cacheWriteTokens: num(u.cache_creation_input_tokens) ?? num(u.cacheWriteTokens),
|
|
470
|
+
reasoningTokens: num(u.reasoning_tokens) ?? num(u.reasoningTokens),
|
|
471
|
+
totalTokens: num(u.total_tokens) ?? num(u.totalTokens),
|
|
472
|
+
};
|
|
473
|
+
return Object.values(usage).some((value) => value !== undefined) ? usage : undefined;
|
|
474
|
+
}
|
|
454
475
|
/**
|
|
455
476
|
* @param {string} raw
|
|
456
477
|
* @returns {CliUsageInfo | undefined}
|
|
@@ -851,6 +872,10 @@ export class BaseCliAgent {
|
|
|
851
872
|
idleTimeoutMs: callTimeouts.idleMs,
|
|
852
873
|
signal: options?.abortSignal,
|
|
853
874
|
maxOutputBytes: this.maxOutputBytes ?? options?.maxOutputBytes,
|
|
875
|
+
// CLI harnesses emit their final result event at the END of
|
|
876
|
+
// the stream; if the capture cap trips, the tail is the part
|
|
877
|
+
// that must survive (#277).
|
|
878
|
+
truncateKeep: "tail",
|
|
854
879
|
onStdout: (chunk) => {
|
|
855
880
|
stdoutEmitter?.push(chunk);
|
|
856
881
|
handleInterpreterChunk("stdout", chunk);
|
|
@@ -863,12 +888,30 @@ export class BaseCliAgent {
|
|
|
863
888
|
flushBufferedLines("stdout", true);
|
|
864
889
|
flushBufferedLines("stderr", true);
|
|
865
890
|
emitEvents(interpreter?.onExit?.(result));
|
|
866
|
-
|
|
891
|
+
if (result.stdoutTruncated) {
|
|
892
|
+
emitEvents({
|
|
893
|
+
type: "action",
|
|
894
|
+
engine: commandSpec.command,
|
|
895
|
+
phase: "completed",
|
|
896
|
+
entryType: "thought",
|
|
897
|
+
action: {
|
|
898
|
+
id: `stdout-truncated-${randomUUID()}`,
|
|
899
|
+
kind: "warning",
|
|
900
|
+
title: "captured stdout truncated",
|
|
901
|
+
detail: {},
|
|
902
|
+
},
|
|
903
|
+
message: "Captured stdout exceeded maxOutputBytes; kept the stream tail. The streamed interpreter answer is used as the result text.",
|
|
904
|
+
ok: true,
|
|
905
|
+
level: "warning",
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
const outputFileText = commandSpec.outputFile
|
|
867
909
|
? yield* Effect.tryPromise({
|
|
868
910
|
try: () => fs.readFile(commandSpec.outputFile, "utf8"),
|
|
869
911
|
catch: (cause) => toSmithersError(cause, "read output file"),
|
|
870
|
-
}).pipe(Effect.catchAll(() => Effect.succeed(
|
|
871
|
-
:
|
|
912
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(null)))
|
|
913
|
+
: null;
|
|
914
|
+
const stdout = typeof outputFileText === "string" ? outputFileText : result.stdout;
|
|
872
915
|
if (result.exitCode && result.exitCode !== 0) {
|
|
873
916
|
const filteredStderr = filterBenignStderr(result.stderr, commandSpec.benignStderrPatterns);
|
|
874
917
|
if (!(commandSpec.command === "codex" && filteredStderr.length === 0)) {
|
|
@@ -934,13 +977,35 @@ export class BaseCliAgent {
|
|
|
934
977
|
}
|
|
935
978
|
}
|
|
936
979
|
}
|
|
937
|
-
const
|
|
938
|
-
?
|
|
980
|
+
const extractedFromStdout = outputFormat === "json" || outputFormat === "stream-json"
|
|
981
|
+
? extractTextFromJsonPayload(rawText)
|
|
939
982
|
: rawText;
|
|
940
|
-
|
|
983
|
+
// The interpreter parses the live stream line-by-line BEFORE the
|
|
984
|
+
// capture cap applies, so its completed answer survives stdout
|
|
985
|
+
// truncation. Prefer it whenever the captured stdout was
|
|
986
|
+
// truncated or yields no final message; otherwise keep the
|
|
987
|
+
// historical extraction so intact runs are unchanged (#277).
|
|
988
|
+
const streamedAnswer = typeof completedEvent?.answer === "string" && completedEvent.answer.trim().length > 0
|
|
989
|
+
? completedEvent.answer
|
|
990
|
+
: undefined;
|
|
991
|
+
// A dedicated final-message file (e.g. codex --output-last-message)
|
|
992
|
+
// is the CLI's authoritative output channel: it holds the complete
|
|
993
|
+
// final message and is immune to the stdout byte cap and to
|
|
994
|
+
// line-by-line stream interpretation. When it parsed as JSON, trust
|
|
995
|
+
// it over the truncation/stream fallbacks, which otherwise surface a
|
|
996
|
+
// short `message` field instead of the full structured object.
|
|
997
|
+
const outputFileJson = typeof outputFileText === "string" && outputFileText.trim() !== ""
|
|
998
|
+
? tryParseJson(outputFileText)
|
|
999
|
+
: null;
|
|
1000
|
+
const extractedText = outputFileJson != null
|
|
1001
|
+
? outputFileText
|
|
1002
|
+
: result.stdoutTruncated || extractedFromStdout == null || extractedFromStdout.trim() === ""
|
|
1003
|
+
? (streamedAnswer ?? extractedFromStdout ?? rawText)
|
|
1004
|
+
: extractedFromStdout;
|
|
1005
|
+
const output = outputFileJson ?? tryParseJson(extractedText);
|
|
941
1006
|
// Extract token usage from raw stdout before text extraction strips it.
|
|
942
1007
|
// Each CLI harness embeds usage differently (NDJSON events, JSON stats, etc.)
|
|
943
|
-
const cliUsage = extractUsageFromOutput(stdout);
|
|
1008
|
+
const cliUsage = extractUsageFromOutput(stdout) ?? usageFromCompletedEvent(completedEvent);
|
|
944
1009
|
const usage = cliUsage ? {
|
|
945
1010
|
inputTokens: cliUsage.inputTokens,
|
|
946
1011
|
inputTokenDetails: {
|
|
@@ -2,4 +2,8 @@ export type RunCommandResult = {
|
|
|
2
2
|
stdout: string;
|
|
3
3
|
stderr: string;
|
|
4
4
|
exitCode: number | null;
|
|
5
|
+
/** True when captured stdout exceeded maxOutputBytes and was truncated. */
|
|
6
|
+
stdoutTruncated?: boolean;
|
|
7
|
+
/** True when captured stderr exceeded maxOutputBytes and was truncated. */
|
|
8
|
+
stderrTruncated?: boolean;
|
|
5
9
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { spawnCaptureEffect } from "@smithers-orchestrator/driver/child-process";
|
|
3
3
|
/**
|
|
4
|
-
* @typedef {{ cwd: string; env: Record<string, string>; input?: string; timeoutMs?: number; idleTimeoutMs?: number; signal?: AbortSignal; maxOutputBytes?: number; onStdout?: (chunk: string) => void; onStderr?: (chunk: string) => void; }} RunCommandOptions
|
|
4
|
+
* @typedef {{ cwd: string; env: Record<string, string>; input?: string; timeoutMs?: number; idleTimeoutMs?: number; signal?: AbortSignal; maxOutputBytes?: number; truncateKeep?: "head" | "tail"; onStdout?: (chunk: string) => void; onStderr?: (chunk: string) => void; }} RunCommandOptions
|
|
5
5
|
*/
|
|
6
6
|
/** @typedef {import("./RunCommandResult.ts").RunCommandResult} RunCommandResult */
|
|
7
7
|
/** @typedef {import("@smithers-orchestrator/errors/SmithersError").SmithersError} SmithersError */
|
|
@@ -13,7 +13,7 @@ import { spawnCaptureEffect } from "@smithers-orchestrator/driver/child-process"
|
|
|
13
13
|
* @returns {Effect.Effect<RunCommandResult, SmithersError>}
|
|
14
14
|
*/
|
|
15
15
|
export function runCommandEffect(command, args, options) {
|
|
16
|
-
const { cwd, env, input, timeoutMs, idleTimeoutMs, signal, maxOutputBytes, onStdout, onStderr, } = options;
|
|
16
|
+
const { cwd, env, input, timeoutMs, idleTimeoutMs, signal, maxOutputBytes, truncateKeep, onStdout, onStderr, } = options;
|
|
17
17
|
return spawnCaptureEffect(command, args, {
|
|
18
18
|
cwd,
|
|
19
19
|
env,
|
|
@@ -22,6 +22,7 @@ export function runCommandEffect(command, args, options) {
|
|
|
22
22
|
timeoutMs,
|
|
23
23
|
idleTimeoutMs,
|
|
24
24
|
maxOutputBytes,
|
|
25
|
+
truncateKeep,
|
|
25
26
|
onStdout,
|
|
26
27
|
onStderr,
|
|
27
28
|
}).pipe(Effect.annotateLogs({
|
|
@@ -113,7 +113,7 @@ const claudeRateLimitCheck = {
|
|
|
113
113
|
"content-type": "application/json",
|
|
114
114
|
},
|
|
115
115
|
body: JSON.stringify({
|
|
116
|
-
model: "claude-
|
|
116
|
+
model: "claude-fable-5",
|
|
117
117
|
messages: [{ role: "user", content: "hi" }],
|
|
118
118
|
}),
|
|
119
119
|
signal: AbortSignal.timeout(4_000),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for shaping the generated toolset. Mirrors the curation knobs on
|
|
3
|
+
* `createOpenApiTools` so MCP and OpenAPI integrations feel the same.
|
|
4
|
+
*/
|
|
5
|
+
export type McpToolsetOptions = {
|
|
6
|
+
/** Only expose these MCP tool names. */
|
|
7
|
+
include?: string[];
|
|
8
|
+
/** Drop these MCP tool names. */
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
/** Prefix applied to every tool name, e.g. `"github_"`. */
|
|
11
|
+
namePrefix?: string;
|
|
12
|
+
/** Identifies this client to the server. */
|
|
13
|
+
clientName?: string;
|
|
14
|
+
/** Client version reported to the server. */
|
|
15
|
+
clientVersion?: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpServerConfig } from "./McpServerConfig.js";
|
|
2
|
+
import type { McpToolset } from "./McpToolset.js";
|
|
3
|
+
import type { McpToolsetOptions } from "./McpToolsetOptions.js";
|
|
4
|
+
|
|
5
|
+
export type { McpServerConfig } from "./McpServerConfig.js";
|
|
6
|
+
export type { McpToolset } from "./McpToolset.js";
|
|
7
|
+
export type { McpToolsetOptions } from "./McpToolsetOptions.js";
|
|
8
|
+
|
|
9
|
+
export declare function createMcpToolset(
|
|
10
|
+
config: McpServerConfig,
|
|
11
|
+
options?: McpToolsetOptions,
|
|
12
|
+
): Promise<McpToolset>;
|
|
@@ -4,18 +4,7 @@ import { dynamicTool, jsonSchema } from "ai";
|
|
|
4
4
|
|
|
5
5
|
/** @typedef {import("./McpServerConfig.ts").McpServerConfig} McpServerConfig */
|
|
6
6
|
/** @typedef {import("./McpToolset.ts").McpToolset} McpToolset */
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Options for shaping the generated toolset. Mirrors the curation knobs on
|
|
10
|
-
* `createOpenApiTools` so MCP and OpenAPI integrations feel the same.
|
|
11
|
-
*
|
|
12
|
-
* @typedef {object} McpToolsetOptions
|
|
13
|
-
* @property {string[]} [include] Only expose these MCP tool names.
|
|
14
|
-
* @property {string[]} [exclude] Drop these MCP tool names.
|
|
15
|
-
* @property {string} [namePrefix] Prefix applied to every tool name (e.g. `"github_"`).
|
|
16
|
-
* @property {string} [clientName] Identifies this client to the server.
|
|
17
|
-
* @property {string} [clientVersion] Client version reported to the server.
|
|
18
|
-
*/
|
|
7
|
+
/** @typedef {import("./McpToolsetOptions.ts").McpToolsetOptions} McpToolsetOptions */
|
|
19
8
|
|
|
20
9
|
/**
|
|
21
10
|
* Connect to an MCP server and expose its tools as AI SDK tools an agent can call.
|