@smithers-orchestrator/agents 0.24.2 → 0.25.1

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 (55) hide show
  1. package/package.json +15 -5
  2. package/src/AgentLike.ts +5 -0
  3. package/src/AmpAgent.js +15 -5
  4. package/src/AmpAgentOptions.ts +6 -0
  5. package/src/BaseCliAgent/BaseCliAgent.js +198 -10
  6. package/src/BaseCliAgent/createAgentStdoutTextEmitter.js +21 -3
  7. package/src/BaseCliAgent/index.d.ts +467 -0
  8. package/src/ClaudeCodeAgent.js +6 -2
  9. package/src/CodexAgent.js +4 -0
  10. package/src/GeminiAgent.js +34 -224
  11. package/src/GeminiAgentOptions.ts +4 -9
  12. package/src/OpenCodeAgent.js +2 -12
  13. package/src/OpenCodeAgentOptions.ts +19 -0
  14. package/src/PiAgent.js +4 -0
  15. package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +0 -1
  16. package/src/cli-capabilities/getCliAgentCapabilityDoctorReport.js +3 -2
  17. package/src/cli-capabilities/getCliAgentCapabilityReport.js +0 -6
  18. package/src/cli-surface/cliAgentSurfaceManifest.js +1 -40
  19. package/src/createElevenLabsTextToSpeechTool.js +128 -0
  20. package/src/createElevenLabsTextToSpeechTool.ts +33 -0
  21. package/src/diagnostics/getDiagnosticStrategy.js +163 -35
  22. package/src/document-parsing/DocumentParsingProvider.ts +13 -0
  23. package/src/document-parsing/DocumentParsingResult.ts +13 -0
  24. package/src/document-parsing/DocumentParsingToolset.ts +4 -0
  25. package/src/document-parsing/DocumentParsingToolsetOptions.ts +9 -0
  26. package/src/document-parsing/createDocumentParsingToolset.d.ts +9 -0
  27. package/src/document-parsing/createDocumentParsingToolset.js +416 -0
  28. package/src/http/CreateHttpToolOptions.ts +4 -0
  29. package/src/http/HttpToolAuth.ts +15 -0
  30. package/src/http/HttpToolInput.ts +11 -0
  31. package/src/http/HttpToolOutput.ts +7 -0
  32. package/src/http/createHttpTool.js +136 -0
  33. package/src/image-generation/ImageGenerationProvider.ts +7 -0
  34. package/src/image-generation/ImageGenerationRequest.ts +8 -0
  35. package/src/image-generation/ImageGenerationResult.ts +10 -0
  36. package/src/image-generation/ImageGenerationToolOptions.ts +10 -0
  37. package/src/image-generation/createImageGenerationTool.d.ts +18 -0
  38. package/src/image-generation/createImageGenerationTool.js +92 -0
  39. package/src/index.d.ts +490 -147
  40. package/src/index.js +23 -5
  41. package/src/streamResultToGenerateResult.js +55 -26
  42. package/src/transcription/createTranscriptionTool.js +182 -0
  43. package/src/transcription/createTranscriptionTool.ts +29 -0
  44. package/src/transcription/index.js +1 -0
  45. package/src/transcription/index.ts +6 -0
  46. package/src/web-search/GroundedWebSearchProvider.ts +21 -0
  47. package/src/web-search/GroundedWebSearchToolset.ts +6 -0
  48. package/src/web-search/createBraveSearchProvider.js +53 -0
  49. package/src/web-search/createExaSearchProvider.js +72 -0
  50. package/src/web-search/createGroundedWebSearchToolset.js +110 -0
  51. package/src/web-search/createSerperSearchProvider.js +63 -0
  52. package/src/web-search/createTavilySearchProvider.js +59 -0
  53. package/src/web-search/index.js +5 -0
  54. package/src/zodToOpenAISchema.js +4 -0
  55. package/src/OpenCodeAgent.ts +0 -43
@@ -1,4 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
2
5
  /** @typedef {import("./DiagnosticCheck.ts").DiagnosticCheck} DiagnosticCheck */
3
6
  /** @typedef {import("./DiagnosticCheckId.ts").DiagnosticCheckId} DiagnosticCheckId */
4
7
  /** @typedef {import("./DiagnosticContext.ts").DiagnosticContext} DiagnosticContext */
@@ -190,24 +193,121 @@ const claudeStrategy = {
190
193
  // ---------------------------------------------------------------------------
191
194
  // Codex strategy
192
195
  // ---------------------------------------------------------------------------
193
- // Combined API key validation + rate limit check via GET /v1/models (free, no tokens)
194
- const codexApiKeyAndRateLimitCheck = [
195
- {
196
+ /**
197
+ * Resolve the OpenAI models endpoint, honoring OPENAI_BASE_URL (Azure, proxies,
198
+ * OpenAI-compatible gateways, and hermetic test fixtures) the same way the
199
+ * OpenAI SDK and codex do. Defaults to the public API, so existing behavior is
200
+ * unchanged when the variable is unset.
201
+ * @param {Record<string, string | undefined>} env
202
+ */
203
+ function openaiModelsUrl(env) {
204
+ const base = (env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/+$/, "");
205
+ return `${base}/models`;
206
+ }
207
+ /**
208
+ * Resolve the Codex CLI config directory the same way the `codex` binary does:
209
+ * an explicit `CODEX_HOME` wins, otherwise `~/.codex` (honoring `$HOME`).
210
+ * @param {Record<string, string | undefined>} env
211
+ * @returns {string}
212
+ */
213
+ function resolveCodexHome(env) {
214
+ const explicit = env.CODEX_HOME?.trim();
215
+ if (explicit) {
216
+ return explicit;
217
+ }
218
+ return join(env.HOME?.trim() || homedir(), ".codex");
219
+ }
220
+ /**
221
+ * @typedef {{ apiKey: string; keySource: string } | { subscription: true } | { missing: true }} OpenAiCredentials
222
+ */
223
+ /**
224
+ * Read OpenAI credentials from `<CODEX_HOME>/auth.json`, the file `codex login`
225
+ * writes. Mirrors the codex binary's own auth resolution: a stored API key, or
226
+ * ChatGPT subscription tokens. Returns null when no usable credentials are
227
+ * present (file missing, unreadable, malformed, or empty).
228
+ * @param {Record<string, string | undefined>} env
229
+ * @returns {{ kind: "apiKey"; apiKey: string } | { kind: "subscription" } | null}
230
+ */
231
+ function readCodexCliAuth(env) {
232
+ try {
233
+ const raw = readFileSync(join(resolveCodexHome(env), "auth.json"), "utf8");
234
+ const parsed = JSON.parse(raw);
235
+ if (typeof parsed?.OPENAI_API_KEY === "string" && parsed.OPENAI_API_KEY.trim()) {
236
+ return { kind: "apiKey", apiKey: parsed.OPENAI_API_KEY.trim() };
237
+ }
238
+ if (typeof parsed?.tokens?.access_token === "string" && parsed.tokens.access_token.trim()) {
239
+ return { kind: "subscription" };
240
+ }
241
+ return null;
242
+ }
243
+ catch {
244
+ return null;
245
+ }
246
+ }
247
+ /**
248
+ * Resolve the OpenAI credentials a codex/pi invocation will actually use. An env
249
+ * `OPENAI_API_KEY` always wins. When it is absent and `codexCliAuth` is set, fall
250
+ * back to `<CODEX_HOME>/auth.json` (subscription tokens or a stored API key) the
251
+ * same way the codex binary does (#448). pi leaves `codexCliAuth` off — it reads
252
+ * the env var (or `--api-key`), not codex's auth.json.
253
+ * @param {Record<string, string | undefined>} env
254
+ * @param {boolean} codexCliAuth
255
+ * @returns {OpenAiCredentials}
256
+ */
257
+ function resolveOpenAiCredentials(env, codexCliAuth) {
258
+ const envKey = env.OPENAI_API_KEY;
259
+ if (envKey) {
260
+ return { apiKey: envKey, keySource: "OPENAI_API_KEY" };
261
+ }
262
+ if (codexCliAuth) {
263
+ const auth = readCodexCliAuth(env);
264
+ if (auth?.kind === "apiKey") {
265
+ return { apiKey: auth.apiKey, keySource: "Codex CLI auth.json API key" };
266
+ }
267
+ if (auth?.kind === "subscription") {
268
+ return { subscription: true };
269
+ }
270
+ }
271
+ return { missing: true };
272
+ }
273
+ /**
274
+ * OpenAI API-key validity check via GET /v1/models (free, no tokens).
275
+ *
276
+ * Validates whatever concrete key the invocation will use — env `OPENAI_API_KEY`
277
+ * or, when `codexCliAuth` is set, a key stored in `<CODEX_HOME>/auth.json`. A
278
+ * stored key can be invalid/exhausted just like an env key, so it is probed, not
279
+ * trusted on presence. ChatGPT subscription tokens can't be probed cheaply, so
280
+ * (like the Claude subscription check) their presence passes (#448).
281
+ * @param {{ codexCliAuth: boolean }} options
282
+ * @returns {DiagnosticCheckDef}
283
+ */
284
+ function openaiApiKeyCheck({ codexCliAuth }) {
285
+ return {
196
286
  id: "api_key_valid",
197
287
  run: async (ctx) => {
198
288
  const start = performance.now();
199
- const apiKey = ctx.env.OPENAI_API_KEY;
200
- if (!apiKey) {
289
+ const creds = resolveOpenAiCredentials(ctx.env, codexCliAuth);
290
+ if ("subscription" in creds) {
291
+ return {
292
+ id: "api_key_valid",
293
+ status: "pass",
294
+ message: "No OPENAI_API_KEY set — using Codex CLI subscription auth (CODEX_HOME/auth.json)",
295
+ durationMs: performance.now() - start,
296
+ };
297
+ }
298
+ if ("missing" in creds) {
201
299
  return {
202
300
  id: "api_key_valid",
203
301
  status: "fail",
204
- message: "OPENAI_API_KEY not set",
302
+ message: codexCliAuth
303
+ ? "OPENAI_API_KEY not set and no Codex CLI auth found — run `codex login` or set OPENAI_API_KEY"
304
+ : "OPENAI_API_KEY not set",
205
305
  durationMs: performance.now() - start,
206
306
  };
207
307
  }
208
308
  try {
209
- const res = await fetch("https://api.openai.com/v1/models", {
210
- headers: { Authorization: `Bearer ${apiKey}` },
309
+ const res = await fetch(openaiModelsUrl(ctx.env), {
310
+ headers: { Authorization: `Bearer ${creds.apiKey}` },
211
311
  signal: AbortSignal.timeout(4_000),
212
312
  });
213
313
  const elapsed = performance.now() - start;
@@ -215,7 +315,7 @@ const codexApiKeyAndRateLimitCheck = [
215
315
  return {
216
316
  id: "api_key_valid",
217
317
  status: "fail",
218
- message: "OPENAI_API_KEY is invalid (401 Unauthorized)",
318
+ message: `${creds.keySource} is invalid (401 Unauthorized)`,
219
319
  durationMs: elapsed,
220
320
  };
221
321
  }
@@ -223,14 +323,14 @@ const codexApiKeyAndRateLimitCheck = [
223
323
  return {
224
324
  id: "api_key_valid",
225
325
  status: "fail",
226
- message: "OPENAI_API_KEY lacks permission (403 Forbidden)",
326
+ message: `${creds.keySource} lacks permission (403 Forbidden)`,
227
327
  durationMs: elapsed,
228
328
  };
229
329
  }
230
330
  return {
231
331
  id: "api_key_valid",
232
332
  status: "pass",
233
- message: "OPENAI_API_KEY is valid",
333
+ message: `${creds.keySource} is valid`,
234
334
  durationMs: elapsed,
235
335
  };
236
336
  }
@@ -243,23 +343,34 @@ const codexApiKeyAndRateLimitCheck = [
243
343
  };
244
344
  }
245
345
  },
246
- },
247
- {
346
+ };
347
+ }
348
+ /**
349
+ * Rate-limit probe via GET /v1/models (free, no tokens). Probes the same key the
350
+ * api-key check resolves (env or Codex CLI auth.json), so a stored key's quota is
351
+ * checked too; subscription/no-key resolve to a non-blocking skip.
352
+ * @param {{ codexCliAuth: boolean }} options
353
+ * @returns {DiagnosticCheckDef}
354
+ */
355
+ function openaiRateLimitCheck({ codexCliAuth }) {
356
+ return {
248
357
  id: "rate_limit_status",
249
358
  run: async (ctx) => {
250
359
  const start = performance.now();
251
- const apiKey = ctx.env.OPENAI_API_KEY;
252
- if (!apiKey) {
360
+ const creds = resolveOpenAiCredentials(ctx.env, codexCliAuth);
361
+ if (!("apiKey" in creds)) {
253
362
  return {
254
363
  id: "rate_limit_status",
255
364
  status: "skip",
256
- message: "No API key — cannot check rate limits",
365
+ message: "subscription" in creds
366
+ ? "Subscription mode — cannot probe rate limits via API"
367
+ : "No API key — cannot check rate limits",
257
368
  durationMs: 0,
258
369
  };
259
370
  }
260
371
  try {
261
- const res = await fetch("https://api.openai.com/v1/models", {
262
- headers: { Authorization: `Bearer ${apiKey}` },
372
+ const res = await fetch(openaiModelsUrl(ctx.env), {
373
+ headers: { Authorization: `Bearer ${creds.apiKey}` },
263
374
  signal: AbortSignal.timeout(4_000),
264
375
  });
265
376
  const elapsed = performance.now() - start;
@@ -313,7 +424,13 @@ const codexApiKeyAndRateLimitCheck = [
313
424
  };
314
425
  }
315
426
  },
316
- },
427
+ };
428
+ }
429
+ // Codex resolves auth from `<CODEX_HOME>/auth.json` (subscription tokens or a
430
+ // stored API key) when OPENAI_API_KEY is absent, so its checks honor that.
431
+ const codexApiKeyAndRateLimitCheck = [
432
+ openaiApiKeyCheck({ codexCliAuth: true }),
433
+ openaiRateLimitCheck({ codexCliAuth: true }),
317
434
  ];
318
435
  const codexStrategy = {
319
436
  agentId: "codex",
@@ -432,15 +549,6 @@ const googleRateLimitCheck = {
432
549
  }
433
550
  },
434
551
  };
435
- const geminiStrategy = {
436
- agentId: "gemini",
437
- command: "gemini",
438
- checks: [
439
- checkCliInstalled("gemini", "Gemini CLI"),
440
- googleAuthCheck,
441
- googleRateLimitCheck,
442
- ],
443
- };
444
552
  const antigravityAuthSkip = {
445
553
  id: "api_key_valid",
446
554
  run: async () => {
@@ -473,12 +581,11 @@ const antigravityStrategy = {
473
581
  ],
474
582
  };
475
583
  // ---------------------------------------------------------------------------
476
- // Pi strategy
584
+ // Pi strategy helpers — dispatch checks based on which provider pi is using
477
585
  // ---------------------------------------------------------------------------
478
586
  /**
479
587
  * 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).
588
+ * `provider/model` prefix, or a bare model id's well-known prefix.
482
589
  * @param {DiagnosticHints | undefined} hints
483
590
  * @returns {string}
484
591
  */
@@ -514,12 +621,31 @@ function resolvePiProvider(hints) {
514
621
  function piProviderChecks(hints) {
515
622
  const raw = resolvePiProvider(hints);
516
623
  if (raw === "openai" || raw === "openai-codex" || raw === "azure" || raw === "azure-openai") {
517
- return [...codexApiKeyAndRateLimitCheck];
624
+ // pi reads OPENAI_API_KEY from the env (or --api-key), not codex's
625
+ // auth.json, so it still requires the key — no Codex CLI auth fallback.
626
+ return [
627
+ openaiApiKeyCheck({ codexCliAuth: false }),
628
+ openaiRateLimitCheck({ codexCliAuth: false }),
629
+ ];
518
630
  }
519
631
  if (raw === "anthropic" || raw === "claude") {
520
632
  return [claudeApiKeyCheck, claudeRateLimitCheck];
521
633
  }
522
- return [googleAuthCheck, googleRateLimitCheck];
634
+ if (raw === "google" || raw === "gemini") {
635
+ return [googleAuthCheck, googleRateLimitCheck];
636
+ }
637
+ // Unknown provider — skip preflight, pi handles its own auth
638
+ return [
639
+ {
640
+ id: "api_key_valid",
641
+ run: async () => ({
642
+ id: "api_key_valid",
643
+ status: "skip",
644
+ message: `Pi provider "${raw || "unset"}" — passing auth to pi`,
645
+ durationMs: 0,
646
+ }),
647
+ },
648
+ ];
523
649
  }
524
650
  /**
525
651
  * pi accepts credentials via the `--api-key` option instead of an environment
@@ -542,7 +668,10 @@ export function diagnosticApiKeyEnv(command, hints) {
542
668
  if (raw === "anthropic" || raw === "claude") {
543
669
  return { ANTHROPIC_API_KEY: hints.apiKey };
544
670
  }
545
- return { GOOGLE_API_KEY: hints.apiKey };
671
+ if (raw === "google" || raw === "gemini") {
672
+ return { GOOGLE_API_KEY: hints.apiKey };
673
+ }
674
+ return undefined;
546
675
  }
547
676
  // ---------------------------------------------------------------------------
548
677
  // Amp strategy
@@ -586,7 +715,6 @@ const strategies = {
586
715
  codex: codexStrategy,
587
716
  antigravity: antigravityStrategy,
588
717
  agy: antigravityStrategy,
589
- gemini: geminiStrategy,
590
718
  amp: ampStrategy,
591
719
  };
592
720
  /**
@@ -0,0 +1,13 @@
1
+ import type { DocumentParsingResult } from "./DocumentParsingResult.ts";
2
+
3
+ export type DocumentParsingProvider = {
4
+ name: "firecrawl" | "mistral-ocr" | "llamaparse" | string;
5
+ parseDocument: (input: {
6
+ source:
7
+ | { type: "url"; url: string }
8
+ | { type: "base64"; data: string; mimeType?: string; filename?: string }
9
+ | { type: "text"; text: string; filename?: string };
10
+ outputFormat?: "text" | "markdown" | "json";
11
+ instructions?: string;
12
+ }) => Promise<DocumentParsingResult>;
13
+ };
@@ -0,0 +1,13 @@
1
+ export type DocumentParsingResult = {
2
+ provider: "firecrawl" | "mistral-ocr" | "llamaparse" | string;
3
+ text: string;
4
+ markdown?: string;
5
+ pages?: Array<{
6
+ index: number;
7
+ text?: string;
8
+ markdown?: string;
9
+ images?: unknown[];
10
+ }>;
11
+ metadata?: Record<string, unknown>;
12
+ raw?: unknown;
13
+ };
@@ -0,0 +1,4 @@
1
+ export type DocumentParsingToolset = {
2
+ tools: Record<string, import("ai").Tool>;
3
+ toolNames: string[];
4
+ };
@@ -0,0 +1,9 @@
1
+ import type { DocumentParsingProvider } from "./DocumentParsingProvider.ts";
2
+
3
+ export type DocumentParsingToolsetOptions = {
4
+ provider?: "firecrawl" | "mistral-ocr" | "llamaparse" | DocumentParsingProvider;
5
+ apiKey?: string;
6
+ baseUrl?: string;
7
+ toolName?: string;
8
+ fetch?: typeof fetch;
9
+ };
@@ -0,0 +1,9 @@
1
+ import type { DocumentParsingToolset } from "./DocumentParsingToolset.js";
2
+ import type { DocumentParsingToolsetOptions } from "./DocumentParsingToolsetOptions.js";
3
+
4
+ export type { DocumentParsingProvider } from "./DocumentParsingProvider.js";
5
+ export type { DocumentParsingResult } from "./DocumentParsingResult.js";
6
+ export type { DocumentParsingToolset } from "./DocumentParsingToolset.js";
7
+ export type { DocumentParsingToolsetOptions } from "./DocumentParsingToolsetOptions.js";
8
+
9
+ export declare function createDocumentParsingToolset(options?: DocumentParsingToolsetOptions): DocumentParsingToolset;