@smithers-orchestrator/agents 0.24.0 → 0.24.2
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/agents",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.2",
|
|
4
4
|
"description": "AI SDK and CLI agent adapters for Smithers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"ai": "^6.0.168",
|
|
57
57
|
"effect": "^3.21.1",
|
|
58
58
|
"zod": "^4.3.6",
|
|
59
|
-
"@smithers-orchestrator/driver": "0.24.
|
|
60
|
-
"@smithers-orchestrator/errors": "0.24.
|
|
61
|
-
"@smithers-orchestrator/observability": "0.24.
|
|
59
|
+
"@smithers-orchestrator/driver": "0.24.2",
|
|
60
|
+
"@smithers-orchestrator/errors": "0.24.2",
|
|
61
|
+
"@smithers-orchestrator/observability": "0.24.2"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/bun": "latest",
|
|
@@ -862,7 +862,7 @@ export class BaseCliAgent {
|
|
|
862
862
|
}
|
|
863
863
|
flushBufferedLines(stream, false);
|
|
864
864
|
};
|
|
865
|
-
diagnosticsPromise = launchDiagnostics(commandSpec.command, commandEnv, cwd);
|
|
865
|
+
diagnosticsPromise = launchDiagnostics(commandSpec.command, commandEnv, cwd, this.diagnosticHints?.());
|
|
866
866
|
return Effect.gen(this, function* () {
|
|
867
867
|
const result = yield* runCommandEffect(commandSpec.command, commandSpec.args, {
|
|
868
868
|
cwd,
|
|
@@ -1088,4 +1088,10 @@ export class BaseCliAgent {
|
|
|
1088
1088
|
createOutputInterpreter() {
|
|
1089
1089
|
return undefined;
|
|
1090
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* @returns {{ provider?: string; model?: string } | undefined}
|
|
1093
|
+
*/
|
|
1094
|
+
diagnosticHints() {
|
|
1095
|
+
return undefined;
|
|
1096
|
+
}
|
|
1091
1097
|
}
|
package/src/CodexAgent.js
CHANGED
|
@@ -59,6 +59,15 @@ export class CodexAgent extends BaseCliAgent {
|
|
|
59
59
|
super(opts);
|
|
60
60
|
this.opts = opts;
|
|
61
61
|
this.capabilities = createCodexCapabilityRegistry(opts);
|
|
62
|
+
// Native structured output (`codex exec --output-schema`) constrains the
|
|
63
|
+
// model to emit only final JSON and makes it refuse tool calls ("tool calls
|
|
64
|
+
// are constrained by a JSON response schema"), which breaks any agentic task
|
|
65
|
+
// (read/edit/run). It is therefore OPT-IN: by default Codex is treated like
|
|
66
|
+
// the other CLI engines (supportsNativeStructuredOutput=false), so the engine
|
|
67
|
+
// prompt-injects the schema and extracts JSON from the agent's final text,
|
|
68
|
+
// leaving tool use intact. Set nativeStructuredOutput:true for pure, tool-free
|
|
69
|
+
// extraction tasks that want strict schema enforcement.
|
|
70
|
+
this.supportsNativeStructuredOutput = opts.nativeStructuredOutput === true;
|
|
62
71
|
}
|
|
63
72
|
/**
|
|
64
73
|
* @returns {CliOutputInterpreter}
|
|
@@ -548,10 +557,12 @@ export class CodexAgent extends BaseCliAgent {
|
|
|
548
557
|
// turn.completed with token usage for metrics. extractUsageFromOutput
|
|
549
558
|
// in BaseCliAgent will parse these automatically.
|
|
550
559
|
args.push("--json");
|
|
551
|
-
// Auto-wire output schema from task context if not explicitly set
|
|
560
|
+
// Auto-wire output schema from task context if not explicitly set — only when
|
|
561
|
+
// native structured output is opted in. Otherwise the engine handles the schema
|
|
562
|
+
// via prompt-injection and Codex keeps full tool access (see constructor note).
|
|
552
563
|
// Skip when resuming — `codex exec resume` does not accept --output-schema.
|
|
553
564
|
let schemaCleanupFile = null;
|
|
554
|
-
if (!resumeSession && !this.opts.outputSchema && params.options?.outputSchema) {
|
|
565
|
+
if (!resumeSession && this.opts.nativeStructuredOutput === true && !this.opts.outputSchema && params.options?.outputSchema) {
|
|
555
566
|
const schema = params.options.outputSchema;
|
|
556
567
|
const { z } = await import("zod");
|
|
557
568
|
let jsonSchema = z.toJSONSchema(schema);
|
package/src/CodexAgentOptions.ts
CHANGED
|
@@ -17,6 +17,17 @@ export type CodexAgentOptions = BaseCliAgentOptions & {
|
|
|
17
17
|
skipGitRepoCheck?: boolean;
|
|
18
18
|
addDir?: string[];
|
|
19
19
|
outputSchema?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Opt in to Codex's native structured output (`codex exec --output-schema`).
|
|
22
|
+
*
|
|
23
|
+
* Defaults to `false`. Native structured output makes the model emit only the
|
|
24
|
+
* final JSON and refuse tool calls, so it BREAKS agentic tasks (read/edit/run) —
|
|
25
|
+
* Codex returns `blocked` with no changes. Left off, Smithers treats Codex like
|
|
26
|
+
* the other CLI engines: it prompt-injects the schema and extracts JSON from the
|
|
27
|
+
* agent's final message, so tool use stays intact. Enable only for pure, tool-free
|
|
28
|
+
* extraction tasks that need strict schema enforcement.
|
|
29
|
+
*/
|
|
30
|
+
nativeStructuredOutput?: boolean;
|
|
20
31
|
color?: "always" | "never" | "auto";
|
|
21
32
|
json?: boolean;
|
|
22
33
|
outputLastMessage?: string;
|
package/src/PiAgent.js
CHANGED
|
@@ -104,11 +104,14 @@ export class PiAgent extends BaseCliAgent {
|
|
|
104
104
|
: undefined;
|
|
105
105
|
const effectiveSession = resumeSession ?? this.opts.session;
|
|
106
106
|
this.issuedSessionRef = effectiveSession;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// pi's --print (non-interactive: process prompt and exit) and --mode
|
|
108
|
+
// are independent. Apply --print to every non-RPC mode so json task
|
|
109
|
+
// executions also process one prompt and exit instead of lingering as
|
|
110
|
+
// an interactive session (#284).
|
|
111
|
+
if (params.mode !== "rpc" && this.opts.print !== false) {
|
|
112
|
+
args.push("--print");
|
|
110
113
|
}
|
|
111
|
-
|
|
114
|
+
if (params.mode !== "text") {
|
|
112
115
|
args.push("--mode", params.mode);
|
|
113
116
|
}
|
|
114
117
|
pushFlag(args, "--provider", this.opts.provider);
|
|
@@ -197,7 +200,9 @@ export class PiAgent extends BaseCliAgent {
|
|
|
197
200
|
createOutputInterpreter() {
|
|
198
201
|
let sessionId = this.issuedSessionRef;
|
|
199
202
|
let emittedStarted = false;
|
|
203
|
+
let emittedCompleted = false;
|
|
200
204
|
let finalAnswer = "";
|
|
205
|
+
let finalUsage;
|
|
201
206
|
/**
|
|
202
207
|
* @param {unknown} value
|
|
203
208
|
*/
|
|
@@ -231,6 +236,17 @@ export class PiAgent extends BaseCliAgent {
|
|
|
231
236
|
}];
|
|
232
237
|
};
|
|
233
238
|
/**
|
|
239
|
+
* @param {Record<string, unknown>} payload
|
|
240
|
+
*/
|
|
241
|
+
const captureUsage = (payload) => {
|
|
242
|
+
const candidate = (payload && typeof payload.usage === "object" && payload.usage) ||
|
|
243
|
+
(payload && typeof payload.message === "object" && payload.message && typeof payload.message.usage === "object" && payload.message.usage) ||
|
|
244
|
+
undefined;
|
|
245
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
246
|
+
finalUsage = candidate;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
234
250
|
* @param {string} line
|
|
235
251
|
* @returns {AgentCliEvent[]}
|
|
236
252
|
*/
|
|
@@ -267,6 +283,7 @@ export class PiAgent extends BaseCliAgent {
|
|
|
267
283
|
return startedEvents();
|
|
268
284
|
}
|
|
269
285
|
if (type === "message_end" || type === "turn_end") {
|
|
286
|
+
captureUsage(payload);
|
|
270
287
|
const message = payload.message;
|
|
271
288
|
if (message?.role === "assistant") {
|
|
272
289
|
const extracted = extractTextFromJsonValue(message);
|
|
@@ -276,6 +293,33 @@ export class PiAgent extends BaseCliAgent {
|
|
|
276
293
|
}
|
|
277
294
|
return startedEvents();
|
|
278
295
|
}
|
|
296
|
+
if (type === "agent_end") {
|
|
297
|
+
captureUsage(payload);
|
|
298
|
+
if (Array.isArray(payload.messages)) {
|
|
299
|
+
for (let i = payload.messages.length - 1; i >= 0; i--) {
|
|
300
|
+
const message = payload.messages[i];
|
|
301
|
+
if (message?.role === "assistant") {
|
|
302
|
+
const extracted = extractTextFromJsonValue(message);
|
|
303
|
+
if (extracted) {
|
|
304
|
+
finalAnswer = extracted;
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
emittedCompleted = true;
|
|
311
|
+
return [
|
|
312
|
+
...startedEvents(),
|
|
313
|
+
{
|
|
314
|
+
type: "completed",
|
|
315
|
+
engine: this.cliEngine,
|
|
316
|
+
ok: true,
|
|
317
|
+
answer: finalAnswer || undefined,
|
|
318
|
+
usage: finalUsage,
|
|
319
|
+
resume: sessionId,
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
}
|
|
279
323
|
if (type === "tool_execution_start") {
|
|
280
324
|
const toolName = asString(payload.toolName) ?? "tool";
|
|
281
325
|
const toolId = asString(payload.toolCallId) ?? toolName;
|
|
@@ -355,6 +399,9 @@ export class PiAgent extends BaseCliAgent {
|
|
|
355
399
|
const started = !emittedStarted && sessionId
|
|
356
400
|
? startedEvents()
|
|
357
401
|
: [];
|
|
402
|
+
if (emittedCompleted) {
|
|
403
|
+
return started;
|
|
404
|
+
}
|
|
358
405
|
return [
|
|
359
406
|
...started,
|
|
360
407
|
{
|
|
@@ -362,6 +409,7 @@ export class PiAgent extends BaseCliAgent {
|
|
|
362
409
|
engine: this.cliEngine,
|
|
363
410
|
ok: !result.exitCode || result.exitCode === 0,
|
|
364
411
|
answer: finalAnswer || undefined,
|
|
412
|
+
usage: finalUsage,
|
|
365
413
|
error: result.exitCode && result.exitCode !== 0
|
|
366
414
|
? result.stderr.trim() || `PI exited with code ${result.exitCode}`
|
|
367
415
|
: undefined,
|
|
@@ -394,7 +442,7 @@ export class PiAgent extends BaseCliAgent {
|
|
|
394
442
|
const cwd = this.cwd ?? options?.rootDir ?? process.cwd();
|
|
395
443
|
const env = { ...process.env, ...this.env };
|
|
396
444
|
const args = this.buildArgs({ prompt, cwd, options, mode });
|
|
397
|
-
const diagnosticsPromise = launchDiagnostics("pi", env, cwd);
|
|
445
|
+
const diagnosticsPromise = launchDiagnostics("pi", env, cwd, this.diagnosticHints());
|
|
398
446
|
const interpreter = this.createOutputInterpreter();
|
|
399
447
|
/**
|
|
400
448
|
* @param {AgentCliEvent[] | AgentCliEvent | null | undefined} payload
|
|
@@ -465,4 +513,14 @@ export class PiAgent extends BaseCliAgent {
|
|
|
465
513
|
outputFormat: mode,
|
|
466
514
|
};
|
|
467
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* @returns {{ provider?: string; model?: string; apiKey?: string }}
|
|
518
|
+
*/
|
|
519
|
+
diagnosticHints() {
|
|
520
|
+
return {
|
|
521
|
+
provider: this.opts.provider,
|
|
522
|
+
model: this.opts.model ?? this.model,
|
|
523
|
+
apiKey: this.opts.apiKey,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
468
526
|
}
|
|
@@ -9,6 +9,9 @@ import { spawnSync } from "node:child_process";
|
|
|
9
9
|
/**
|
|
10
10
|
* @typedef {{ id: DiagnosticCheckId; run: (ctx: DiagnosticContext) => Promise<DiagnosticCheck>; }} DiagnosticCheckDef
|
|
11
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {{ provider?: string; model?: string; apiKey?: string }} DiagnosticHints
|
|
14
|
+
*/
|
|
12
15
|
|
|
13
16
|
// ---------------------------------------------------------------------------
|
|
14
17
|
// Shared check helpers
|
|
@@ -472,15 +475,75 @@ const antigravityStrategy = {
|
|
|
472
475
|
// ---------------------------------------------------------------------------
|
|
473
476
|
// Pi strategy
|
|
474
477
|
// ---------------------------------------------------------------------------
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Resolve the effective pi provider family from an explicit `--provider`, a
|
|
480
|
+
* `provider/model` prefix, or a bare model id's well-known prefix. Returns ""
|
|
481
|
+
* when undeterminable so callers fall back to pi's default (google) (#284).
|
|
482
|
+
* @param {DiagnosticHints | undefined} hints
|
|
483
|
+
* @returns {string}
|
|
484
|
+
*/
|
|
485
|
+
function resolvePiProvider(hints) {
|
|
486
|
+
const explicit = (hints?.provider || "").trim().toLowerCase();
|
|
487
|
+
if (explicit) {
|
|
488
|
+
return explicit;
|
|
489
|
+
}
|
|
490
|
+
const model = typeof hints?.model === "string" ? hints.model.trim().toLowerCase() : "";
|
|
491
|
+
if (!model) {
|
|
492
|
+
return "";
|
|
493
|
+
}
|
|
494
|
+
if (model.includes("/")) {
|
|
495
|
+
return model.split("/")[0];
|
|
496
|
+
}
|
|
497
|
+
// Bare model id (no provider prefix) — infer the provider family from
|
|
498
|
+
// common id prefixes so diagnostics probe the right backend.
|
|
499
|
+
if (model.startsWith("gpt-") || model.startsWith("o1-") || model.startsWith("o3-") || model.startsWith("o4-") || model.startsWith("chatgpt")) {
|
|
500
|
+
return "openai";
|
|
501
|
+
}
|
|
502
|
+
if (model.startsWith("claude")) {
|
|
503
|
+
return "anthropic";
|
|
504
|
+
}
|
|
505
|
+
if (model.startsWith("gemini")) {
|
|
506
|
+
return "google";
|
|
507
|
+
}
|
|
508
|
+
return "";
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* @param {DiagnosticHints | undefined} hints
|
|
512
|
+
* @returns {DiagnosticCheckDef[]}
|
|
513
|
+
*/
|
|
514
|
+
function piProviderChecks(hints) {
|
|
515
|
+
const raw = resolvePiProvider(hints);
|
|
516
|
+
if (raw === "openai" || raw === "openai-codex" || raw === "azure" || raw === "azure-openai") {
|
|
517
|
+
return [...codexApiKeyAndRateLimitCheck];
|
|
518
|
+
}
|
|
519
|
+
if (raw === "anthropic" || raw === "claude") {
|
|
520
|
+
return [claudeApiKeyCheck, claudeRateLimitCheck];
|
|
521
|
+
}
|
|
522
|
+
return [googleAuthCheck, googleRateLimitCheck];
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* pi accepts credentials via the `--api-key` option instead of an environment
|
|
526
|
+
* variable. Diagnostics only see the process env, so map an explicit apiKey to
|
|
527
|
+
* the env var the selected provider's checks read — otherwise an apiKey-only pi
|
|
528
|
+
* run is misreported as "key missing" (#284). Returns undefined when there is
|
|
529
|
+
* nothing to inject.
|
|
530
|
+
* @param {string} command
|
|
531
|
+
* @param {DiagnosticHints | undefined} hints
|
|
532
|
+
* @returns {Record<string, string> | undefined}
|
|
533
|
+
*/
|
|
534
|
+
export function diagnosticApiKeyEnv(command, hints) {
|
|
535
|
+
if (command !== "pi" || !hints?.apiKey) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
const raw = resolvePiProvider(hints);
|
|
539
|
+
if (raw === "openai" || raw === "openai-codex" || raw === "azure" || raw === "azure-openai") {
|
|
540
|
+
return { OPENAI_API_KEY: hints.apiKey };
|
|
541
|
+
}
|
|
542
|
+
if (raw === "anthropic" || raw === "claude") {
|
|
543
|
+
return { ANTHROPIC_API_KEY: hints.apiKey };
|
|
544
|
+
}
|
|
545
|
+
return { GOOGLE_API_KEY: hints.apiKey };
|
|
546
|
+
}
|
|
484
547
|
// ---------------------------------------------------------------------------
|
|
485
548
|
// Amp strategy
|
|
486
549
|
// ---------------------------------------------------------------------------
|
|
@@ -524,13 +587,20 @@ const strategies = {
|
|
|
524
587
|
antigravity: antigravityStrategy,
|
|
525
588
|
agy: antigravityStrategy,
|
|
526
589
|
gemini: geminiStrategy,
|
|
527
|
-
pi: piStrategy,
|
|
528
590
|
amp: ampStrategy,
|
|
529
591
|
};
|
|
530
592
|
/**
|
|
531
593
|
* @param {string} command
|
|
594
|
+
* @param {DiagnosticHints} [hints]
|
|
532
595
|
* @returns {AgentDiagnosticStrategy | null}
|
|
533
596
|
*/
|
|
534
|
-
export function getDiagnosticStrategy(command) {
|
|
597
|
+
export function getDiagnosticStrategy(command, hints) {
|
|
598
|
+
if (command === "pi") {
|
|
599
|
+
return {
|
|
600
|
+
agentId: "pi",
|
|
601
|
+
command: "pi",
|
|
602
|
+
checks: [checkCliInstalled("pi", "Pi"), ...piProviderChecks(hints)],
|
|
603
|
+
};
|
|
604
|
+
}
|
|
535
605
|
return strategies[command] ?? null;
|
|
536
606
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDiagnosticStrategy } from "./getDiagnosticStrategy.js";
|
|
1
|
+
import { diagnosticApiKeyEnv, getDiagnosticStrategy } from "./getDiagnosticStrategy.js";
|
|
2
2
|
import { runDiagnostics } from "./runDiagnostics.js";
|
|
3
3
|
/** @typedef {import("./DiagnosticReport.ts").DiagnosticReport} DiagnosticReport */
|
|
4
4
|
|
|
@@ -6,11 +6,14 @@ import { runDiagnostics } from "./runDiagnostics.js";
|
|
|
6
6
|
* @param {string} command
|
|
7
7
|
* @param {Record<string, string>} env
|
|
8
8
|
* @param {string} cwd
|
|
9
|
+
* @param {{ provider?: string; model?: string; apiKey?: string }} [hints]
|
|
9
10
|
* @returns {Promise<DiagnosticReport> | null}
|
|
10
11
|
*/
|
|
11
|
-
export function launchDiagnostics(command, env, cwd) {
|
|
12
|
-
const strategy = getDiagnosticStrategy(command);
|
|
12
|
+
export function launchDiagnostics(command, env, cwd, hints) {
|
|
13
|
+
const strategy = getDiagnosticStrategy(command, hints);
|
|
13
14
|
if (!strategy)
|
|
14
15
|
return null;
|
|
15
|
-
|
|
16
|
+
const apiKeyEnv = diagnosticApiKeyEnv(command, hints);
|
|
17
|
+
const effectiveEnv = apiKeyEnv ? { ...env, ...apiKeyEnv } : env;
|
|
18
|
+
return runDiagnostics(strategy, { env: effectiveEnv, cwd }).catch(() => null);
|
|
16
19
|
}
|