steward-cli 0.22.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 (3) hide show
  1. package/README.md +34 -119
  2. package/dist/cli.js +661 -651
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -43610,6 +43610,7 @@ async function startOAuthCallbackServer(options) {
43610
43610
  let settled = false;
43611
43611
  let resolvePromise;
43612
43612
  let rejectPromise;
43613
+ let abortHandler;
43613
43614
  const promise = new Promise((res, rej) => {
43614
43615
  resolvePromise = res;
43615
43616
  rejectPromise = rej;
@@ -43619,7 +43620,11 @@ async function startOAuthCallbackServer(options) {
43619
43620
  const s = server;
43620
43621
  server = null;
43621
43622
  await new Promise((res) => {
43622
- s.close(() => res());
43623
+ try {
43624
+ s.close(() => res());
43625
+ } catch {
43626
+ res();
43627
+ }
43623
43628
  });
43624
43629
  }
43625
43630
  };
@@ -43628,6 +43633,9 @@ async function startOAuthCallbackServer(options) {
43628
43633
  return;
43629
43634
  settled = true;
43630
43635
  clearTimeout(timer);
43636
+ if (signal && abortHandler) {
43637
+ signal.removeEventListener("abort", abortHandler);
43638
+ }
43631
43639
  await closeServer();
43632
43640
  if (error) {
43633
43641
  rejectPromise(error);
@@ -43643,9 +43651,10 @@ async function startOAuthCallbackServer(options) {
43643
43651
  clearTimeout(timer);
43644
43652
  throw new AIError("OAuth login was cancelled.", { code: "aborted" });
43645
43653
  }
43646
- signal.addEventListener("abort", () => {
43654
+ abortHandler = () => {
43647
43655
  finish(undefined, new AIError("OAuth login was cancelled.", { code: "aborted" }));
43648
- });
43656
+ };
43657
+ signal.addEventListener("abort", abortHandler, { once: true });
43649
43658
  }
43650
43659
  server = http.createServer((req, res) => {
43651
43660
  try {
@@ -43694,32 +43703,43 @@ async function startOAuthCallbackServer(options) {
43694
43703
  }));
43695
43704
  }
43696
43705
  });
43697
- await new Promise((resolve, reject) => {
43698
- server.on("error", (err) => {
43699
- clearTimeout(timer);
43700
- if (err.code === "EADDRINUSE") {
43701
- reject(new AIError(`Port ${port} is already in use. Cannot start OAuth callback server.`, {
43702
- code: "oauth",
43703
- cause: err
43704
- }));
43705
- } else {
43706
- reject(new AIError(`Failed to bind OAuth callback server: ${err.message}`, {
43707
- code: "oauth",
43708
- cause: err
43709
- }));
43710
- }
43711
- });
43712
- server.listen(port, host, () => {
43713
- resolve();
43706
+ try {
43707
+ await new Promise((resolve, reject) => {
43708
+ server.on("error", async (err) => {
43709
+ clearTimeout(timer);
43710
+ if (signal && abortHandler) {
43711
+ signal.removeEventListener("abort", abortHandler);
43712
+ }
43713
+ await closeServer();
43714
+ if (err.code === "EADDRINUSE") {
43715
+ reject(new AIError(`Port ${port} is already in use. Cannot start OAuth callback server.`, {
43716
+ code: "oauth",
43717
+ cause: err
43718
+ }));
43719
+ } else {
43720
+ reject(new AIError(`Failed to bind OAuth callback server: ${err.message}`, {
43721
+ code: "oauth",
43722
+ cause: err
43723
+ }));
43724
+ }
43725
+ });
43726
+ server.listen(port, host, () => {
43727
+ resolve();
43728
+ });
43714
43729
  });
43715
- });
43730
+ } catch (listenErr) {
43731
+ await closeServer();
43732
+ throw listenErr;
43733
+ }
43716
43734
  return {
43717
43735
  redirectUri,
43718
43736
  wait: () => promise,
43719
43737
  cancel: () => {
43720
43738
  finish(undefined, new AIError("OAuth callback cancelled.", { code: "aborted" }));
43721
43739
  },
43722
- close: closeServer
43740
+ close: async () => {
43741
+ await finish(undefined, new AIError("OAuth callback server closed.", { code: "aborted" }));
43742
+ }
43723
43743
  };
43724
43744
  }
43725
43745
 
@@ -43774,8 +43794,7 @@ async function exchangeAuthorizationCode(code, state, verifier, signal) {
43774
43794
  signal
43775
43795
  });
43776
43796
  if (!response.ok) {
43777
- const errText = await response.text().catch(() => "");
43778
- throw new AIError(`Anthropic OAuth token exchange failed (HTTP ${response.status}): ${errText}`, {
43797
+ throw new AIError(`Anthropic OAuth token exchange failed with HTTP status ${response.status}`, {
43779
43798
  code: "oauth",
43780
43799
  provider: "anthropic",
43781
43800
  status: response.status
@@ -43878,8 +43897,7 @@ async function refreshAnthropic(credential, signal) {
43878
43897
  signal
43879
43898
  });
43880
43899
  if (!response.ok) {
43881
- const errText = await response.text().catch(() => "");
43882
- throw new AIError(`Anthropic OAuth token refresh failed (HTTP ${response.status}): ${errText}`, {
43900
+ throw new AIError(`Anthropic OAuth token refresh failed with HTTP status ${response.status}`, {
43883
43901
  code: "oauth",
43884
43902
  provider: "anthropic",
43885
43903
  status: response.status
@@ -43939,8 +43957,7 @@ async function exchangeAuthorizationCode2(code, verifier, signal) {
43939
43957
  signal
43940
43958
  });
43941
43959
  if (!response.ok) {
43942
- const errText = await response.text().catch(() => "");
43943
- throw new AIError(`OpenRouter OAuth key exchange failed (HTTP ${response.status}): ${errText}`, {
43960
+ throw new AIError(`OpenRouter OAuth key exchange failed with HTTP status ${response.status}`, {
43944
43961
  code: "oauth",
43945
43962
  provider: "openrouter",
43946
43963
  status: response.status
@@ -44040,14 +44057,16 @@ var SLOW_DOWN_INCREMENT_MS = 5000;
44040
44057
  function abortableSleep(ms, signal) {
44041
44058
  return new Promise((resolve, reject) => {
44042
44059
  if (signal.aborted) {
44043
- reject(new Error("Login cancelled"));
44060
+ reject(new AIError("Login cancelled", { code: "aborted" }));
44044
44061
  return;
44045
44062
  }
44063
+ let timeout;
44046
44064
  const onAbort = () => {
44047
- clearTimeout(timeout);
44048
- reject(new Error("Login cancelled"));
44065
+ if (timeout)
44066
+ clearTimeout(timeout);
44067
+ reject(new AIError("Login cancelled", { code: "aborted" }));
44049
44068
  };
44050
- const timeout = setTimeout(() => {
44069
+ timeout = setTimeout(() => {
44051
44070
  signal.removeEventListener("abort", onAbort);
44052
44071
  resolve();
44053
44072
  }, ms);
@@ -44065,14 +44084,14 @@ async function pollOAuthDeviceCodeFlow(options) {
44065
44084
  }
44066
44085
  while (Date.now() < deadline) {
44067
44086
  if (options.signal.aborted) {
44068
- throw new Error("Login cancelled");
44087
+ throw new AIError("Login cancelled", { code: "aborted" });
44069
44088
  }
44070
44089
  const result = await options.poll();
44071
44090
  if (result.status === "complete") {
44072
44091
  return result.value;
44073
44092
  }
44074
44093
  if (result.status === "failed") {
44075
- throw new Error(result.message);
44094
+ throw new AIError(result.message, { code: "oauth" });
44076
44095
  }
44077
44096
  if (result.status === "slow_down") {
44078
44097
  intervalMs = typeof result.intervalSeconds === "number" && result.intervalSeconds > 0 ? Math.max(MINIMUM_INTERVAL_MS, Math.floor(result.intervalSeconds * 1000)) : Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INCREMENT_MS);
@@ -44082,7 +44101,7 @@ async function pollOAuthDeviceCodeFlow(options) {
44082
44101
  break;
44083
44102
  await abortableSleep(Math.min(intervalMs, remaining), options.signal);
44084
44103
  }
44085
- throw new Error("Device authorization flow timed out.");
44104
+ throw new AIError("Device authorization flow timed out.", { code: "oauth" });
44086
44105
  }
44087
44106
 
44088
44107
  // src/packages/ai/src/auth/oauth/github-copilot.ts
@@ -44203,8 +44222,7 @@ async function refreshGitHubCopilotToken(githubAccessToken, signal) {
44203
44222
  });
44204
44223
  }
44205
44224
  if (!res.ok) {
44206
- const text = await res.text().catch(() => "");
44207
- throw new AIError(`GitHub Copilot token exchange failed (HTTP ${res.status}): ${text}`, {
44225
+ throw new AIError(`GitHub Copilot token exchange failed with HTTP status ${res.status}`, {
44208
44226
  code: "oauth",
44209
44227
  provider: "github-copilot",
44210
44228
  status: res.status
@@ -44239,6 +44257,23 @@ function toGitHubCopilotAuth(credential) {
44239
44257
  }
44240
44258
 
44241
44259
  // src/packages/ai/src/auth/resolver.ts
44260
+ var OAUTH_ADAPTERS = {
44261
+ anthropic: {
44262
+ login: loginAnthropic,
44263
+ refresh: refreshAnthropic,
44264
+ toResolvedAuth: toAnthropicAuth
44265
+ },
44266
+ openrouter: {
44267
+ login: loginOpenRouter,
44268
+ refresh: refreshOpenRouter,
44269
+ toResolvedAuth: toOpenRouterAuth
44270
+ },
44271
+ "github-copilot": {
44272
+ login: loginGitHubCopilot,
44273
+ refresh: refreshGitHubCopilot,
44274
+ toResolvedAuth: toGitHubCopilotAuth
44275
+ }
44276
+ };
44242
44277
  var DEFAULT_REFRESH_MARGIN_MS = 5 * 60 * 1000;
44243
44278
 
44244
44279
  class AuthResolver {
@@ -44286,32 +44321,21 @@ class AuthResolver {
44286
44321
  return;
44287
44322
  }
44288
44323
  async performRefresh(provider, credential, signal) {
44289
- if (provider === "anthropic") {
44290
- return await refreshAnthropic(credential, signal);
44291
- }
44292
- if (provider === "openrouter") {
44293
- return await refreshOpenRouter(credential, signal);
44294
- }
44295
- if (provider === "github-copilot") {
44296
- return await refreshGitHubCopilot(credential, signal);
44324
+ const adapter = OAUTH_ADAPTERS[provider];
44325
+ if (adapter) {
44326
+ return await adapter.refresh(credential, signal);
44297
44327
  }
44298
44328
  return credential;
44299
44329
  }
44300
44330
  formatOAuth(provider, credential) {
44301
- if (provider === "anthropic") {
44302
- return toAnthropicAuth(credential);
44303
- }
44304
- if (provider === "openrouter") {
44305
- return toOpenRouterAuth(credential);
44306
- }
44307
- if (provider === "github-copilot") {
44308
- return toGitHubCopilotAuth(credential);
44331
+ const adapter = OAUTH_ADAPTERS[provider];
44332
+ if (adapter) {
44333
+ return adapter.toResolvedAuth(credential);
44309
44334
  }
44310
- return {
44311
- type: "oauth",
44312
- token: credential.accessToken,
44313
- source: "oauth"
44314
- };
44335
+ throw new AIError(`Unsupported OAuth provider: "${provider}"`, {
44336
+ code: "oauth",
44337
+ provider
44338
+ });
44315
44339
  }
44316
44340
  }
44317
44341
 
@@ -44319,35 +44343,96 @@ class AuthResolver {
44319
44343
  import * as fs from "node:fs/promises";
44320
44344
  import * as path from "node:path";
44321
44345
  import * as os2 from "node:os";
44346
+ var fileMutexes = new Map;
44347
+ function validateAuthStorage(data) {
44348
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
44349
+ return false;
44350
+ }
44351
+ const root = data;
44352
+ if (root.version !== 1) {
44353
+ return false;
44354
+ }
44355
+ if (!root.credentials || typeof root.credentials !== "object" || Array.isArray(root.credentials)) {
44356
+ return false;
44357
+ }
44358
+ const credentials = root.credentials;
44359
+ for (const [_, cred] of Object.entries(credentials)) {
44360
+ if (!cred || typeof cred !== "object" || Array.isArray(cred)) {
44361
+ return false;
44362
+ }
44363
+ const c = cred;
44364
+ if (c.type !== "oauth") {
44365
+ return false;
44366
+ }
44367
+ if (typeof c.accessToken !== "string") {
44368
+ return false;
44369
+ }
44370
+ if (c.refreshToken !== undefined && c.refreshToken !== null && typeof c.refreshToken !== "string") {
44371
+ return false;
44372
+ }
44373
+ if (c.expiresAt !== undefined && c.expiresAt !== null && typeof c.expiresAt !== "number") {
44374
+ return false;
44375
+ }
44376
+ }
44377
+ return true;
44378
+ }
44379
+
44322
44380
  class FileCredentialStore {
44323
44381
  dirPath;
44324
44382
  filePath;
44325
- mutex = Promise.resolve();
44383
+ canonicalPath;
44326
44384
  constructor(options) {
44327
44385
  this.dirPath = options?.storageDir ?? path.join(os2.homedir(), ".steward");
44328
44386
  this.filePath = options?.filePath ?? path.join(this.dirPath, "auth.json");
44387
+ this.canonicalPath = path.resolve(this.filePath);
44329
44388
  }
44330
44389
  async ensureDirectory() {
44331
44390
  try {
44332
44391
  await fs.mkdir(this.dirPath, { recursive: true, mode: 448 });
44333
44392
  await fs.chmod(this.dirPath, 448).catch(() => {});
44334
- } catch {}
44393
+ } catch (err) {
44394
+ const isExist = err?.code === "EEXIST";
44395
+ if (!isExist) {
44396
+ throw new AIError(`Failed to create directory "${this.dirPath}": ${err instanceof Error ? err.message : String(err)}`, {
44397
+ code: "auth",
44398
+ cause: err
44399
+ });
44400
+ }
44401
+ }
44335
44402
  }
44336
44403
  async loadData() {
44337
44404
  try {
44338
44405
  const content = await fs.readFile(this.filePath, "utf-8");
44339
- const parsed = JSON.parse(content);
44340
- if (!parsed || typeof parsed !== "object" || parsed.version !== 1 || !parsed.credentials) {
44406
+ let parsed;
44407
+ try {
44408
+ parsed = JSON.parse(content);
44409
+ } catch (jsonErr) {
44410
+ throw new AIError("Invalid or corrupt auth.json schema.", {
44411
+ code: "auth",
44412
+ cause: jsonErr
44413
+ });
44414
+ }
44415
+ if (!validateAuthStorage(parsed)) {
44341
44416
  throw new AIError("Invalid or corrupt auth.json schema.", { code: "auth" });
44342
44417
  }
44418
+ for (const cred of Object.values(parsed.credentials)) {
44419
+ if (cred) {
44420
+ if (cred.expiresAt === null) {
44421
+ delete cred.expiresAt;
44422
+ }
44423
+ if (cred.refreshToken === null) {
44424
+ delete cred.refreshToken;
44425
+ }
44426
+ }
44427
+ }
44343
44428
  return parsed;
44344
44429
  } catch (err) {
44345
- if (err.code === "ENOENT") {
44430
+ if (err?.code === "ENOENT") {
44346
44431
  return { version: 1, credentials: {} };
44347
44432
  }
44348
44433
  if (err instanceof AIError)
44349
44434
  throw err;
44350
- throw new AIError(`Failed to read auth store: ${err.message}`, {
44435
+ throw new AIError(`Failed to read auth store: ${err instanceof Error ? err.message : String(err)}`, {
44351
44436
  code: "auth",
44352
44437
  cause: err
44353
44438
  });
@@ -44364,7 +44449,7 @@ class FileCredentialStore {
44364
44449
  await fs.chmod(this.filePath, 384).catch(() => {});
44365
44450
  } catch (err) {
44366
44451
  await fs.unlink(tempFile).catch(() => {});
44367
- throw new AIError(`Failed to atomically save auth store: ${err.message}`, {
44452
+ throw new AIError(`Failed to atomically save auth store: ${err instanceof Error ? err.message : String(err)}`, {
44368
44453
  code: "auth",
44369
44454
  cause: err
44370
44455
  });
@@ -44389,24 +44474,21 @@ class FileCredentialStore {
44389
44474
  return list;
44390
44475
  }
44391
44476
  async modify(provider, fn) {
44392
- return new Promise((resolve, reject) => {
44393
- this.mutex = this.mutex.then(async () => {
44394
- try {
44395
- const data = await this.loadData();
44396
- const current = data.credentials[provider];
44397
- const updated = await fn(current);
44398
- if (updated) {
44399
- data.credentials[provider] = updated;
44400
- } else {
44401
- delete data.credentials[provider];
44402
- }
44403
- await this.saveData(data);
44404
- resolve(updated);
44405
- } catch (err) {
44406
- reject(err);
44407
- }
44408
- });
44477
+ const currentMutex = fileMutexes.get(this.canonicalPath) ?? Promise.resolve();
44478
+ const nextMutex = currentMutex.then(async () => {
44479
+ const data = await this.loadData();
44480
+ const current = data.credentials[provider];
44481
+ const updated = await fn(current);
44482
+ if (updated) {
44483
+ data.credentials[provider] = updated;
44484
+ } else {
44485
+ delete data.credentials[provider];
44486
+ }
44487
+ await this.saveData(data);
44488
+ return updated;
44409
44489
  });
44490
+ fileMutexes.set(this.canonicalPath, nextMutex.catch(() => {}));
44491
+ return await nextMutex;
44410
44492
  }
44411
44493
  async delete(provider) {
44412
44494
  await this.modify(provider, async () => {
@@ -44465,19 +44547,14 @@ class DefaultAuthManager {
44465
44547
  return await this.resolver.resolve(provider, signal);
44466
44548
  }
44467
44549
  async login(provider, interaction) {
44468
- let credential;
44469
- if (provider === "anthropic") {
44470
- credential = await loginAnthropic(interaction);
44471
- } else if (provider === "openrouter") {
44472
- credential = await loginOpenRouter(interaction);
44473
- } else if (provider === "github-copilot") {
44474
- credential = await loginGitHubCopilot(interaction);
44475
- } else {
44550
+ const adapter = OAUTH_ADAPTERS[provider];
44551
+ if (!adapter) {
44476
44552
  throw new AIError(`No browser OAuth login is available for "${provider}".`, {
44477
44553
  code: "oauth",
44478
44554
  provider
44479
44555
  });
44480
44556
  }
44557
+ const credential = await adapter.login(interaction);
44481
44558
  await this.store.modify(provider, async () => credential);
44482
44559
  return credential;
44483
44560
  }
@@ -44606,6 +44683,13 @@ var PROVIDER_REGISTRY = {
44606
44683
  envVar: "CUSTOM_API_URL / CUSTOM_API_KEY"
44607
44684
  }
44608
44685
  };
44686
+ function getProviderDescriptor(provider) {
44687
+ const descriptor = PROVIDER_REGISTRY[provider];
44688
+ if (!descriptor) {
44689
+ throw new Error(`Unsupported provider: "${provider}"`);
44690
+ }
44691
+ return descriptor;
44692
+ }
44609
44693
 
44610
44694
  // src/packages/ai/src/models/selection.ts
44611
44695
  var CANONICAL_DEFAULT_EFFORT = "medium";
@@ -44623,26 +44707,36 @@ var PROVIDER_SELECTION_PRIORITY = [
44623
44707
  "custom"
44624
44708
  ];
44625
44709
  function inferProviderFromModelId(modelId) {
44626
- const lower = modelId.toLowerCase();
44627
- if (/^[a-z0-9-_.]+\/[a-z0-9-_.]+$/.test(lower))
44710
+ const lower = modelId.trim().toLowerCase();
44711
+ if (!lower)
44712
+ return null;
44713
+ if (/^[a-z0-9-_.]+\/[a-z0-9-_.]+$/.test(lower)) {
44628
44714
  return "openrouter";
44629
- if (lower.startsWith("copilot/") || lower.startsWith("github-copilot/"))
44715
+ }
44716
+ if (lower.startsWith("copilot/") || lower.startsWith("github-copilot/")) {
44630
44717
  return "github-copilot";
44631
- if (lower.startsWith("gemini-") || lower.startsWith("gemma-"))
44718
+ }
44719
+ if (lower.startsWith("gemini-") || lower.startsWith("gemma-")) {
44632
44720
  return "gemini";
44633
- if (lower.startsWith("claude-"))
44721
+ }
44722
+ if (lower.startsWith("claude-")) {
44634
44723
  return "anthropic";
44635
- if (lower.startsWith("gpt-") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("chatgpt-")) {
44724
+ }
44725
+ if (lower.startsWith("gpt-") || lower.startsWith("chatgpt-") || /^o[1-9]($|-)/.test(lower)) {
44636
44726
  return "openai";
44637
44727
  }
44638
- if (lower.startsWith("deepseek-"))
44728
+ if (lower.startsWith("deepseek-")) {
44639
44729
  return "deepseek";
44640
- if (lower.startsWith("grok-"))
44730
+ }
44731
+ if (lower.startsWith("grok-")) {
44641
44732
  return "xai";
44642
- if (lower.startsWith("codestral") || lower.startsWith("mistral") || lower.startsWith("pixtral") || lower.startsWith("ministral"))
44733
+ }
44734
+ if (lower.startsWith("codestral") || lower.startsWith("mistral") || lower.startsWith("pixtral") || lower.startsWith("ministral")) {
44643
44735
  return "mistral";
44644
- if (lower.includes(":latest") || lower.startsWith("ollama/"))
44736
+ }
44737
+ if (lower.startsWith("ollama/") || /^[a-z0-9_.-]+:[a-z0-9_.-]+$/.test(lower)) {
44645
44738
  return "ollama";
44739
+ }
44646
44740
  return null;
44647
44741
  }
44648
44742
  async function resolveModelSelection(requested, context) {
@@ -44807,6 +44901,9 @@ function parseSSEEvent(raw) {
44807
44901
  }
44808
44902
 
44809
44903
  // src/packages/ai/src/json.ts
44904
+ function sanitizeSurrogates(text) {
44905
+ return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
44906
+ }
44810
44907
  function parseStreamingJson(partial) {
44811
44908
  const trimmed = partial.trim();
44812
44909
  if (!trimmed) {
@@ -44876,82 +44973,78 @@ function parseStreamingJson(partial) {
44876
44973
  }
44877
44974
 
44878
44975
  // src/packages/ai/src/providers/openai-compatible.ts
44976
+ var STATIC_PROFILES = {
44977
+ openrouter: {
44978
+ provider: "openrouter",
44979
+ baseUrl: "https://openrouter.ai/api/v1",
44980
+ supportsReasoning: true,
44981
+ reasoningFormat: "openrouter",
44982
+ apiPath: "/chat/completions",
44983
+ requiresApiKey: true
44984
+ },
44985
+ deepseek: {
44986
+ provider: "deepseek",
44987
+ baseUrl: "https://api.deepseek.com",
44988
+ supportsReasoning: true,
44989
+ reasoningFormat: "deepseek",
44990
+ apiPath: "/chat/completions",
44991
+ requiresApiKey: true
44992
+ },
44993
+ "github-copilot": {
44994
+ provider: "github-copilot",
44995
+ baseUrl: "https://api.individual.githubcopilot.com",
44996
+ supportsReasoning: true,
44997
+ reasoningFormat: "openai",
44998
+ apiPath: "/chat/completions",
44999
+ requiresApiKey: true
45000
+ },
45001
+ groq: {
45002
+ provider: "groq",
45003
+ baseUrl: "https://api.groq.com/openai/v1",
45004
+ supportsReasoning: true,
45005
+ reasoningFormat: "deepseek",
45006
+ apiPath: "/chat/completions",
45007
+ requiresApiKey: true
45008
+ },
45009
+ xai: {
45010
+ provider: "xai",
45011
+ baseUrl: "https://api.x.ai/v1",
45012
+ supportsReasoning: true,
45013
+ reasoningFormat: "openai",
45014
+ apiPath: "/chat/completions",
45015
+ requiresApiKey: true
45016
+ },
45017
+ mistral: {
45018
+ provider: "mistral",
45019
+ baseUrl: "https://api.mistral.ai/v1",
45020
+ supportsReasoning: false,
45021
+ reasoningFormat: "none",
45022
+ apiPath: "/chat/completions",
45023
+ requiresApiKey: true
45024
+ }
45025
+ };
44879
45026
  function getProfile(provider, customBaseUrl) {
44880
- switch (provider) {
44881
- case "openrouter":
44882
- return {
44883
- provider: "openrouter",
44884
- baseUrl: "https://openrouter.ai/api/v1",
44885
- supportsReasoning: true,
44886
- reasoningFormat: "openrouter",
44887
- apiPath: "/chat/completions",
44888
- requiresApiKey: true
44889
- };
44890
- case "deepseek":
44891
- return {
44892
- provider: "deepseek",
44893
- baseUrl: "https://api.deepseek.com",
44894
- supportsReasoning: true,
44895
- reasoningFormat: "deepseek",
44896
- apiPath: "/chat/completions",
44897
- requiresApiKey: true
44898
- };
44899
- case "github-copilot":
44900
- return {
44901
- provider: "github-copilot",
44902
- baseUrl: "https://api.individual.githubcopilot.com",
44903
- supportsReasoning: true,
44904
- reasoningFormat: "openai",
44905
- apiPath: "/chat/completions",
44906
- requiresApiKey: true
44907
- };
44908
- case "groq":
44909
- return {
44910
- provider: "groq",
44911
- baseUrl: "https://api.groq.com/openai/v1",
44912
- supportsReasoning: true,
44913
- reasoningFormat: "deepseek",
44914
- apiPath: "/chat/completions",
44915
- requiresApiKey: true
44916
- };
44917
- case "xai":
44918
- return {
44919
- provider: "xai",
44920
- baseUrl: "https://api.x.ai/v1",
44921
- supportsReasoning: true,
44922
- reasoningFormat: "openai",
44923
- apiPath: "/chat/completions",
44924
- requiresApiKey: true
44925
- };
44926
- case "mistral":
44927
- return {
44928
- provider: "mistral",
44929
- baseUrl: "https://api.mistral.ai/v1",
44930
- supportsReasoning: false,
44931
- reasoningFormat: "none",
44932
- apiPath: "/chat/completions",
44933
- requiresApiKey: true
44934
- };
44935
- case "ollama":
44936
- return {
44937
- provider: "ollama",
44938
- baseUrl: customBaseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434/v1",
44939
- supportsReasoning: true,
44940
- reasoningFormat: "deepseek",
44941
- apiPath: "/chat/completions",
44942
- requiresApiKey: false
44943
- };
44944
- case "custom":
44945
- default:
44946
- return {
44947
- provider: "custom",
44948
- baseUrl: customBaseUrl || process.env.CUSTOM_API_URL || "http://localhost:11434/v1",
44949
- supportsReasoning: true,
44950
- reasoningFormat: "openai",
44951
- apiPath: "/chat/completions",
44952
- requiresApiKey: false
44953
- };
45027
+ if (provider in STATIC_PROFILES) {
45028
+ return STATIC_PROFILES[provider];
44954
45029
  }
45030
+ if (provider === "ollama") {
45031
+ return {
45032
+ provider: "ollama",
45033
+ baseUrl: customBaseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434/v1",
45034
+ supportsReasoning: true,
45035
+ reasoningFormat: "deepseek",
45036
+ apiPath: "/chat/completions",
45037
+ requiresApiKey: false
45038
+ };
45039
+ }
45040
+ return {
45041
+ provider: "custom",
45042
+ baseUrl: customBaseUrl || process.env.CUSTOM_API_URL || "http://localhost:11434/v1",
45043
+ supportsReasoning: true,
45044
+ reasoningFormat: "openai",
45045
+ apiPath: "/chat/completions",
45046
+ requiresApiKey: false
45047
+ };
44955
45048
  }
44956
45049
  function mapEffort(effort, format) {
44957
45050
  if (effort === "none")
@@ -44974,13 +45067,13 @@ function convertMessages(messages) {
44974
45067
  const converted = [];
44975
45068
  for (const msg of messages) {
44976
45069
  if (msg.role === "system") {
44977
- converted.push({ role: "system", content: msg.content });
45070
+ converted.push({ role: "system", content: sanitizeSurrogates(msg.content) });
44978
45071
  continue;
44979
45072
  }
44980
45073
  if (msg.role === "user") {
44981
45074
  const text = typeof msg.content === "string" ? msg.content : msg.content.map((c) => c.text).join(`
44982
45075
  `);
44983
- converted.push({ role: "user", content: text });
45076
+ converted.push({ role: "user", content: sanitizeSurrogates(text) });
44984
45077
  continue;
44985
45078
  }
44986
45079
  if (msg.role === "assistant") {
@@ -45005,9 +45098,9 @@ function convertMessages(messages) {
45005
45098
  }
45006
45099
  const item = { role: "assistant" };
45007
45100
  if (text)
45008
- item.content = text;
45101
+ item.content = sanitizeSurrogates(text);
45009
45102
  if (reasoning_content)
45010
- item.reasoning_content = reasoning_content;
45103
+ item.reasoning_content = sanitizeSurrogates(reasoning_content);
45011
45104
  if (tool_calls.length > 0)
45012
45105
  item.tool_calls = tool_calls;
45013
45106
  converted.push(item);
@@ -45015,10 +45108,11 @@ function convertMessages(messages) {
45015
45108
  }
45016
45109
  if (msg.role === "tool") {
45017
45110
  for (const res of msg.content) {
45111
+ const outStr = typeof res.output === "string" ? res.output : JSON.stringify(res.output);
45018
45112
  converted.push({
45019
45113
  role: "tool",
45020
45114
  tool_call_id: res.toolCallId,
45021
- content: typeof res.output === "string" ? res.output : JSON.stringify(res.output)
45115
+ content: sanitizeSurrogates(outStr)
45022
45116
  });
45023
45117
  }
45024
45118
  }
@@ -45074,7 +45168,7 @@ function streamOpenAICompatible(options) {
45074
45168
  });
45075
45169
  } catch (err) {
45076
45170
  const isAbort = request.abortSignal?.aborted;
45077
- const error = new AIError(isAbort ? "Inference request aborted." : `Network request to ${profile.provider} failed: ${err.message}`, {
45171
+ const error = new AIError(isAbort ? "Inference request aborted." : `Network request to ${profile.provider} failed: ${err instanceof Error ? err.message : String(err)}`, {
45078
45172
  code: isAbort ? "aborted" : "network",
45079
45173
  provider: request.model.provider,
45080
45174
  cause: err
@@ -45084,10 +45178,9 @@ function streamOpenAICompatible(options) {
45084
45178
  return;
45085
45179
  }
45086
45180
  if (!response.ok) {
45087
- const errBody = await response.text().catch(() => "");
45088
45181
  const isRateLimit = response.status === 429;
45089
45182
  const isAuth = response.status === 401 || response.status === 403;
45090
- const error = new AIError(`${profile.provider} error (HTTP ${response.status}): ${errBody}`, {
45183
+ const error = new AIError(`${profile.provider} request failed with HTTP status ${response.status}`, {
45091
45184
  code: isAuth ? auth?.type === "oauth" ? "oauth" : "auth" : isRateLimit ? "rate-limit" : "provider",
45092
45185
  provider: request.model.provider,
45093
45186
  status: response.status
@@ -45105,8 +45198,7 @@ function streamOpenAICompatible(options) {
45105
45198
  yield { type: "error", error };
45106
45199
  return;
45107
45200
  }
45108
- let textContent = "";
45109
- let reasoningContent = "";
45201
+ const assistantContent = [];
45110
45202
  const toolCallBuffers = new Map;
45111
45203
  const usage = {
45112
45204
  inputTokens: 0,
@@ -45124,19 +45216,24 @@ function streamOpenAICompatible(options) {
45124
45216
  } catch {
45125
45217
  continue;
45126
45218
  }
45127
- if (eventData.usage) {
45219
+ if (eventData.usage && typeof eventData.usage === "object") {
45128
45220
  const u = eventData.usage;
45129
- usage.inputTokens = u.prompt_tokens ?? usage.inputTokens;
45130
- usage.outputTokens = u.completion_tokens ?? usage.outputTokens;
45131
- usage.totalTokens = u.total_tokens ?? usage.totalTokens;
45132
- if (u.completion_tokens_details?.reasoning_tokens) {
45133
- usage.reasoningTokens = u.completion_tokens_details.reasoning_tokens;
45221
+ if (typeof u.prompt_tokens === "number")
45222
+ usage.inputTokens = u.prompt_tokens;
45223
+ if (typeof u.completion_tokens === "number")
45224
+ usage.outputTokens = u.completion_tokens;
45225
+ if (typeof u.total_tokens === "number")
45226
+ usage.totalTokens = u.total_tokens;
45227
+ const details = u.completion_tokens_details;
45228
+ if (typeof details?.reasoning_tokens === "number") {
45229
+ usage.reasoningTokens = details.reasoning_tokens;
45134
45230
  }
45135
45231
  }
45136
- const choice = eventData.choices?.[0];
45232
+ const choices = eventData.choices;
45233
+ const choice = choices?.[0];
45137
45234
  if (!choice)
45138
45235
  continue;
45139
- if (choice.finish_reason) {
45236
+ if (choice.finish_reason && typeof choice.finish_reason === "string") {
45140
45237
  const fr = choice.finish_reason;
45141
45238
  if (fr === "stop")
45142
45239
  finishReason = "stop";
@@ -45148,70 +45245,86 @@ function streamOpenAICompatible(options) {
45148
45245
  const delta = choice.delta;
45149
45246
  if (!delta)
45150
45247
  continue;
45151
- const reasoningDelta = delta.reasoning_content || delta.reasoning || delta.reasoning_text || delta.reasoning_details?.text;
45248
+ const reasoningDetails = delta.reasoning_details;
45249
+ const reasoningDelta = typeof delta.reasoning_content === "string" && delta.reasoning_content || typeof delta.reasoning === "string" && delta.reasoning || typeof delta.reasoning_text === "string" && delta.reasoning_text || typeof reasoningDetails?.text === "string" && reasoningDetails.text || "";
45152
45250
  if (reasoningDelta) {
45153
- reasoningContent += reasoningDelta;
45251
+ const lastBlock = assistantContent[assistantContent.length - 1];
45252
+ if (lastBlock && lastBlock.type === "thinking") {
45253
+ lastBlock.thinking += reasoningDelta;
45254
+ } else {
45255
+ assistantContent.push({
45256
+ type: "thinking",
45257
+ thinking: reasoningDelta
45258
+ });
45259
+ }
45154
45260
  yield { type: "reasoning-delta", delta: reasoningDelta };
45155
45261
  }
45156
- if (delta.content) {
45157
- textContent += delta.content;
45158
- yield { type: "text-delta", delta: delta.content };
45262
+ if (typeof delta.content === "string" && delta.content) {
45263
+ const textDelta = delta.content;
45264
+ const lastBlock = assistantContent[assistantContent.length - 1];
45265
+ if (lastBlock && lastBlock.type === "text") {
45266
+ lastBlock.text += textDelta;
45267
+ } else {
45268
+ assistantContent.push({
45269
+ type: "text",
45270
+ text: textDelta
45271
+ });
45272
+ }
45273
+ yield { type: "text-delta", delta: textDelta };
45159
45274
  }
45160
- if (delta.tool_calls && Array.isArray(delta.tool_calls)) {
45275
+ if (Array.isArray(delta.tool_calls)) {
45161
45276
  for (const tc of delta.tool_calls) {
45162
- const idx = tc.index ?? 0;
45163
- let buffer = toolCallBuffers.get(idx);
45164
- if (!buffer) {
45165
- buffer = {
45166
- id: tc.id || `call_${Date.now()}_${idx}`,
45167
- name: tc.function?.name || "",
45277
+ const idx = typeof tc.index === "number" ? tc.index : 0;
45278
+ const fn = tc.function;
45279
+ const tcId = typeof tc.id === "string" ? tc.id : undefined;
45280
+ const fnName = typeof fn?.name === "string" ? fn.name : undefined;
45281
+ const fnArgs = typeof fn?.arguments === "string" ? fn.arguments : undefined;
45282
+ let entry = toolCallBuffers.get(idx);
45283
+ if (!entry) {
45284
+ const buffer = {
45285
+ id: tcId || `call_${idx}`,
45286
+ name: fnName || "",
45168
45287
  rawArgs: ""
45169
45288
  };
45170
- toolCallBuffers.set(idx, buffer);
45289
+ const slot = {
45290
+ type: "tool-call",
45291
+ id: buffer.id,
45292
+ name: buffer.name,
45293
+ arguments: {}
45294
+ };
45295
+ assistantContent.push(slot);
45296
+ entry = { buffer, slot };
45297
+ toolCallBuffers.set(idx, entry);
45171
45298
  if (buffer.name) {
45172
45299
  yield { type: "tool-call-start", id: buffer.id, name: buffer.name };
45173
45300
  }
45174
45301
  } else {
45175
- if (!buffer.name && tc.function?.name) {
45176
- buffer.name = tc.function.name;
45177
- yield { type: "tool-call-start", id: buffer.id, name: buffer.name };
45302
+ if (tcId && entry.buffer.id !== tcId) {
45303
+ entry.buffer.id = tcId;
45304
+ entry.slot.id = tcId;
45305
+ }
45306
+ if (!entry.buffer.name && fnName) {
45307
+ entry.buffer.name = fnName;
45308
+ entry.slot.name = fnName;
45309
+ yield { type: "tool-call-start", id: entry.buffer.id, name: entry.buffer.name };
45178
45310
  }
45179
45311
  }
45180
- if (tc.function?.arguments) {
45181
- buffer.rawArgs += tc.function.arguments;
45312
+ if (fnArgs) {
45313
+ entry.buffer.rawArgs += fnArgs;
45182
45314
  yield {
45183
45315
  type: "tool-call-delta",
45184
- id: buffer.id,
45185
- delta: tc.function.arguments
45316
+ id: entry.buffer.id,
45317
+ delta: fnArgs
45186
45318
  };
45187
45319
  }
45188
45320
  }
45189
45321
  }
45190
45322
  }
45191
- const assistantContent = [];
45192
- if (reasoningContent) {
45193
- assistantContent.push({
45194
- type: "thinking",
45195
- thinking: reasoningContent
45196
- });
45197
- }
45198
- if (textContent) {
45199
- assistantContent.push({
45200
- type: "text",
45201
- text: textContent
45202
- });
45203
- }
45204
- for (const [_, buffer] of toolCallBuffers.entries()) {
45205
- const parsedArgs = parseStreamingJson(buffer.rawArgs);
45206
- const toolCall = {
45207
- type: "tool-call",
45208
- id: buffer.id,
45209
- name: buffer.name,
45210
- arguments: parsedArgs
45211
- };
45212
- assistantContent.push(toolCall);
45323
+ for (const entry of toolCallBuffers.values()) {
45324
+ const parsedArgs = parseStreamingJson(entry.buffer.rawArgs);
45325
+ entry.slot.arguments = parsedArgs;
45213
45326
  finishReason = "tool-use";
45214
- yield { type: "tool-call-end", toolCall };
45327
+ yield { type: "tool-call-end", toolCall: entry.slot };
45215
45328
  }
45216
45329
  usage.totalTokens = usage.totalTokens || usage.inputTokens + usage.outputTokens;
45217
45330
  const finalMessage = {
@@ -45231,7 +45344,7 @@ function streamOpenAICompatible(options) {
45231
45344
  };
45232
45345
  } catch (err) {
45233
45346
  const isAbort = request.abortSignal?.aborted;
45234
- const error = new AIError(isAbort ? "Inference request aborted." : `${profile.provider} stream reading failed: ${err.message}`, {
45347
+ const error = new AIError(isAbort ? "Inference request aborted." : `${profile.provider} stream reading failed: ${err instanceof Error ? err.message : String(err)}`, {
45235
45348
  code: isAbort ? "aborted" : "provider",
45236
45349
  provider: request.model.provider,
45237
45350
  cause: err
@@ -45265,7 +45378,7 @@ function streamOpenAI(options) {
45265
45378
  request,
45266
45379
  auth,
45267
45380
  profile: {
45268
- provider: "custom",
45381
+ provider: "openai",
45269
45382
  baseUrl: "https://api.openai.com/v1",
45270
45383
  supportsReasoning: true,
45271
45384
  reasoningFormat: "openai",
@@ -45302,11 +45415,11 @@ ${msg.content}` : msg.content;
45302
45415
  }
45303
45416
  if (msg.role === "user") {
45304
45417
  if (typeof msg.content === "string") {
45305
- anthropicMessages.push({ role: "user", content: msg.content });
45418
+ anthropicMessages.push({ role: "user", content: sanitizeSurrogates(msg.content) });
45306
45419
  } else {
45307
45420
  const text = msg.content.map((c) => c.text).join(`
45308
45421
  `);
45309
- anthropicMessages.push({ role: "user", content: text });
45422
+ anthropicMessages.push({ role: "user", content: sanitizeSurrogates(text) });
45310
45423
  }
45311
45424
  continue;
45312
45425
  }
@@ -45314,11 +45427,11 @@ ${msg.content}` : msg.content;
45314
45427
  const content = [];
45315
45428
  for (const block of msg.content) {
45316
45429
  if (block.type === "text") {
45317
- content.push({ type: "text", text: block.text });
45430
+ content.push({ type: "text", text: sanitizeSurrogates(block.text) });
45318
45431
  } else if (block.type === "thinking") {
45319
45432
  content.push({
45320
45433
  type: "thinking",
45321
- thinking: block.thinking,
45434
+ thinking: sanitizeSurrogates(block.thinking),
45322
45435
  signature: block.thinkingSignature
45323
45436
  });
45324
45437
  } else if (block.type === "tool-call") {
@@ -45336,10 +45449,11 @@ ${msg.content}` : msg.content;
45336
45449
  if (msg.role === "tool") {
45337
45450
  const content = [];
45338
45451
  for (const res of msg.content) {
45452
+ const outStr = typeof res.output === "string" ? res.output : JSON.stringify(res.output);
45339
45453
  content.push({
45340
45454
  type: "tool_result",
45341
45455
  tool_use_id: res.toolCallId,
45342
- content: typeof res.output === "string" ? res.output : JSON.stringify(res.output),
45456
+ content: sanitizeSurrogates(outStr),
45343
45457
  is_error: res.isError
45344
45458
  });
45345
45459
  }
@@ -45352,10 +45466,11 @@ function streamAnthropic(options) {
45352
45466
  const { request, auth } = options;
45353
45467
  const thinkingConfig = mapAnthropicEffort(request.model.effort);
45354
45468
  const { system, anthropicMessages } = convertMessages2(request.messages);
45355
- const tools = request.tools?.map((t) => ({
45469
+ const tools = request.tools?.map((t, idx, arr) => ({
45356
45470
  name: t.name,
45357
45471
  description: t.description,
45358
- input_schema: t.inputSchema
45472
+ input_schema: t.inputSchema,
45473
+ ...idx === arr.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
45359
45474
  }));
45360
45475
  const body = {
45361
45476
  model: request.model.modelId,
@@ -45364,7 +45479,13 @@ function streamAnthropic(options) {
45364
45479
  stream: true
45365
45480
  };
45366
45481
  if (system) {
45367
- body.system = system;
45482
+ body.system = [
45483
+ {
45484
+ type: "text",
45485
+ text: sanitizeSurrogates(system),
45486
+ cache_control: { type: "ephemeral" }
45487
+ }
45488
+ ];
45368
45489
  }
45369
45490
  if (tools && tools.length > 0) {
45370
45491
  body.tools = tools;
@@ -45379,7 +45500,7 @@ function streamAnthropic(options) {
45379
45500
  "Content-Type": "application/json",
45380
45501
  Accept: "text/event-stream",
45381
45502
  "anthropic-version": "2023-06-01",
45382
- ...auth.headers
45503
+ ...auth.headers || {}
45383
45504
  };
45384
45505
  if (auth.type === "api-key") {
45385
45506
  headers["x-api-key"] = auth.token;
@@ -45399,7 +45520,7 @@ function streamAnthropic(options) {
45399
45520
  });
45400
45521
  } catch (err) {
45401
45522
  const isAbort = request.abortSignal?.aborted;
45402
- const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Anthropic failed: ${err.message}`, {
45523
+ const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Anthropic failed: ${err instanceof Error ? err.message : String(err)}`, {
45403
45524
  code: isAbort ? "aborted" : "network",
45404
45525
  provider: "anthropic",
45405
45526
  cause: err
@@ -45409,10 +45530,9 @@ function streamAnthropic(options) {
45409
45530
  return;
45410
45531
  }
45411
45532
  if (!response.ok) {
45412
- const errBody = await response.text().catch(() => "");
45413
45533
  const isRateLimit = response.status === 429;
45414
45534
  const isAuth = response.status === 401 || response.status === 403;
45415
- const error = new AIError(`Anthropic error (HTTP ${response.status}): ${errBody}`, {
45535
+ const error = new AIError(`Anthropic request failed with HTTP status ${response.status}`, {
45416
45536
  code: isAuth ? auth.type === "oauth" ? "oauth" : "auth" : isRateLimit ? "rate-limit" : "provider",
45417
45537
  provider: "anthropic",
45418
45538
  status: response.status
@@ -45449,69 +45569,91 @@ function streamAnthropic(options) {
45449
45569
  } catch {
45450
45570
  continue;
45451
45571
  }
45452
- const eventType = sse.event || eventData.type;
45572
+ const eventType = typeof sse.event === "string" && sse.event || typeof eventData.type === "string" && eventData.type || "";
45453
45573
  switch (eventType) {
45454
45574
  case "message_start": {
45455
- if (eventData.message?.usage) {
45456
- const u = eventData.message.usage;
45457
- usage.inputTokens = u.input_tokens ?? 0;
45458
- usage.cacheReadTokens = u.cache_read_input_tokens ?? 0;
45459
- usage.cacheWriteTokens = u.cache_creation_input_tokens ?? 0;
45575
+ const msg = eventData.message;
45576
+ if (msg?.usage && typeof msg.usage === "object") {
45577
+ const u = msg.usage;
45578
+ if (typeof u.input_tokens === "number")
45579
+ usage.inputTokens = u.input_tokens;
45580
+ if (typeof u.cache_read_input_tokens === "number")
45581
+ usage.cacheReadTokens = u.cache_read_input_tokens;
45582
+ if (typeof u.cache_creation_input_tokens === "number")
45583
+ usage.cacheWriteTokens = u.cache_creation_input_tokens;
45460
45584
  }
45461
45585
  break;
45462
45586
  }
45463
45587
  case "content_block_start": {
45464
45588
  const block = eventData.content_block;
45589
+ if (!block || typeof block !== "object" || typeof block.type !== "string") {
45590
+ const error = new AIError("Malformed Anthropic content_block_start event", {
45591
+ code: "parse",
45592
+ provider: "anthropic"
45593
+ });
45594
+ errorResult = error;
45595
+ yield { type: "error", error };
45596
+ return;
45597
+ }
45465
45598
  if (block.type === "text") {
45466
- currentBlock = { type: "text", text: block.text || "" };
45467
- if (block.text) {
45468
- yield { type: "text-delta", delta: block.text };
45599
+ const blockText = typeof block.text === "string" ? block.text : "";
45600
+ currentBlock = { type: "text", text: blockText };
45601
+ if (blockText) {
45602
+ yield { type: "text-delta", delta: blockText };
45469
45603
  }
45470
45604
  } else if (block.type === "thinking") {
45605
+ const blockThinking = typeof block.thinking === "string" ? block.thinking : "";
45606
+ const blockSig = typeof block.signature === "string" ? block.signature : undefined;
45471
45607
  currentBlock = {
45472
45608
  type: "thinking",
45473
- thinking: block.thinking || "",
45474
- signature: block.signature
45609
+ thinking: blockThinking,
45610
+ signature: blockSig
45475
45611
  };
45476
- if (block.thinking) {
45477
- yield { type: "reasoning-delta", delta: block.thinking };
45612
+ if (blockThinking) {
45613
+ yield { type: "reasoning-delta", delta: blockThinking };
45478
45614
  }
45479
45615
  } else if (block.type === "tool_use") {
45616
+ const blockId = typeof block.id === "string" ? block.id : "";
45617
+ const blockName = typeof block.name === "string" ? block.name : "";
45480
45618
  currentBlock = {
45481
45619
  type: "tool-call",
45482
- id: block.id,
45483
- name: block.name,
45620
+ id: blockId,
45621
+ name: blockName,
45484
45622
  rawArgs: ""
45485
45623
  };
45486
- yield { type: "tool-call-start", id: block.id, name: block.name };
45624
+ yield { type: "tool-call-start", id: blockId, name: blockName };
45487
45625
  }
45488
45626
  break;
45489
45627
  }
45490
45628
  case "content_block_delta": {
45491
45629
  const delta = eventData.delta;
45492
- if (!delta)
45630
+ if (!delta || typeof delta.type !== "string")
45493
45631
  break;
45494
45632
  if (delta.type === "text_delta") {
45633
+ const deltaText = typeof delta.text === "string" ? delta.text : "";
45495
45634
  if (currentBlock && currentBlock.type === "text") {
45496
- currentBlock.text += delta.text;
45635
+ currentBlock.text += deltaText;
45497
45636
  }
45498
- yield { type: "text-delta", delta: delta.text };
45637
+ yield { type: "text-delta", delta: deltaText };
45499
45638
  } else if (delta.type === "thinking_delta") {
45639
+ const deltaThinking = typeof delta.thinking === "string" ? delta.thinking : "";
45500
45640
  if (currentBlock && currentBlock.type === "thinking") {
45501
- currentBlock.thinking += delta.thinking;
45641
+ currentBlock.thinking += deltaThinking;
45502
45642
  }
45503
- yield { type: "reasoning-delta", delta: delta.thinking };
45643
+ yield { type: "reasoning-delta", delta: deltaThinking };
45504
45644
  } else if (delta.type === "signature_delta") {
45645
+ const deltaSig = typeof delta.signature === "string" ? delta.signature : "";
45505
45646
  if (currentBlock && currentBlock.type === "thinking") {
45506
- currentBlock.signature = (currentBlock.signature || "") + delta.signature;
45647
+ currentBlock.signature = (currentBlock.signature || "") + deltaSig;
45507
45648
  }
45508
45649
  } else if (delta.type === "input_json_delta") {
45650
+ const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : "";
45509
45651
  if (currentBlock && currentBlock.type === "tool-call") {
45510
- currentBlock.rawArgs += delta.partial_json;
45652
+ currentBlock.rawArgs += partialJson;
45511
45653
  yield {
45512
45654
  type: "tool-call-delta",
45513
45655
  id: currentBlock.id,
45514
- delta: delta.partial_json
45656
+ delta: partialJson
45515
45657
  };
45516
45658
  }
45517
45659
  }
@@ -45546,8 +45688,9 @@ function streamAnthropic(options) {
45546
45688
  break;
45547
45689
  }
45548
45690
  case "message_delta": {
45549
- if (eventData.delta?.stop_reason) {
45550
- const sr = eventData.delta.stop_reason;
45691
+ const delta = eventData.delta;
45692
+ if (delta && typeof delta.stop_reason === "string") {
45693
+ const sr = delta.stop_reason;
45551
45694
  if (sr === "end_turn" || sr === "stop_sequence")
45552
45695
  finishReason = "stop";
45553
45696
  else if (sr === "max_tokens")
@@ -45555,8 +45698,9 @@ function streamAnthropic(options) {
45555
45698
  else if (sr === "tool_use")
45556
45699
  finishReason = "tool-use";
45557
45700
  }
45558
- if (eventData.usage?.output_tokens) {
45559
- usage.outputTokens = eventData.usage.output_tokens;
45701
+ const u = eventData.usage;
45702
+ if (typeof u?.output_tokens === "number") {
45703
+ usage.outputTokens = u.output_tokens;
45560
45704
  }
45561
45705
  break;
45562
45706
  }
@@ -45566,7 +45710,8 @@ function streamAnthropic(options) {
45566
45710
  }
45567
45711
  case "error": {
45568
45712
  const err = eventData.error;
45569
- const error = new AIError(`Anthropic stream error: ${err?.message || JSON.stringify(err)}`, {
45713
+ const errMsg = typeof err?.message === "string" ? err.message : "Unknown provider error";
45714
+ const error = new AIError(`Anthropic stream error: ${errMsg}`, {
45570
45715
  code: "provider",
45571
45716
  provider: "anthropic"
45572
45717
  });
@@ -45603,7 +45748,7 @@ function streamAnthropic(options) {
45603
45748
  };
45604
45749
  } catch (err) {
45605
45750
  const isAbort = request.abortSignal?.aborted;
45606
- const error = new AIError(isAbort ? "Inference request aborted." : `Stream reading failed: ${err.message}`, {
45751
+ const error = new AIError(isAbort ? "Inference request aborted." : `Stream reading failed: ${err instanceof Error ? err.message : String(err)}`, {
45607
45752
  code: isAbort ? "aborted" : "provider",
45608
45753
  provider: "anthropic",
45609
45754
  cause: err
@@ -45661,7 +45806,7 @@ function sanitizeSchema(schema) {
45661
45806
  function mapGeminiEffort(effort) {
45662
45807
  switch (effort) {
45663
45808
  case "none":
45664
- return { thinkingBudget: 0 };
45809
+ return;
45665
45810
  case "low":
45666
45811
  return { thinkingBudget: 2048 };
45667
45812
  case "medium":
@@ -45677,13 +45822,14 @@ function convertMessages3(messages) {
45677
45822
  const contents = [];
45678
45823
  for (const msg of messages) {
45679
45824
  if (msg.role === "system") {
45825
+ const sanitized = sanitizeSurrogates(msg.content);
45680
45826
  systemText = systemText ? `${systemText}
45681
45827
 
45682
- ${msg.content}` : msg.content;
45828
+ ${sanitized}` : sanitized;
45683
45829
  continue;
45684
45830
  }
45685
45831
  if (msg.role === "user") {
45686
- const text = typeof msg.content === "string" ? msg.content : msg.content.map((c) => c.text).join(`
45832
+ const text = typeof msg.content === "string" ? sanitizeSurrogates(msg.content) : msg.content.map((c) => sanitizeSurrogates(c.text)).join(`
45687
45833
  `);
45688
45834
  contents.push({
45689
45835
  role: "user",
@@ -45695,19 +45841,19 @@ ${msg.content}` : msg.content;
45695
45841
  const parts = [];
45696
45842
  for (const block of msg.content) {
45697
45843
  if (block.type === "text") {
45698
- if (!block.text && !block.thoughtSignature)
45844
+ if (!block.text && !block.textSignature)
45699
45845
  continue;
45700
45846
  parts.push({
45701
- text: block.text || "",
45702
- ...block.thoughtSignature ? { thoughtSignature: block.thoughtSignature } : {}
45847
+ text: sanitizeSurrogates(block.text || ""),
45848
+ ...block.textSignature ? { thoughtSignature: block.textSignature } : {}
45703
45849
  });
45704
45850
  } else if (block.type === "thinking") {
45705
- if (!block.thinking && !block.thoughtSignature)
45851
+ if (!block.thinking && !block.thinkingSignature)
45706
45852
  continue;
45707
45853
  parts.push({
45708
45854
  thought: true,
45709
- text: block.thinking || "",
45710
- ...block.thoughtSignature ? { thoughtSignature: block.thoughtSignature } : {}
45855
+ text: sanitizeSurrogates(block.thinking || ""),
45856
+ ...block.thinkingSignature ? { thoughtSignature: block.thinkingSignature } : {}
45711
45857
  });
45712
45858
  } else if (block.type === "tool-call") {
45713
45859
  parts.push({
@@ -45734,7 +45880,7 @@ ${msg.content}` : msg.content;
45734
45880
  functionResponse: {
45735
45881
  name: res.toolName,
45736
45882
  response: {
45737
- result: res.output
45883
+ result: sanitizeSurrogates(res.output)
45738
45884
  }
45739
45885
  }
45740
45886
  });
@@ -45783,7 +45929,15 @@ function streamGemini(options) {
45783
45929
  body.tools = tools;
45784
45930
  }
45785
45931
  const modelId = request.model.modelId.replace(/^models\//, "");
45786
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:streamGenerateContent?alt=sse&key=${auth.token}`;
45932
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:streamGenerateContent?alt=sse`;
45933
+ const headers = {
45934
+ "Content-Type": "application/json",
45935
+ Accept: "text/event-stream",
45936
+ ...auth.headers || {}
45937
+ };
45938
+ if (auth.token && auth.token !== "none") {
45939
+ headers["x-goog-api-key"] = auth.token;
45940
+ }
45787
45941
  let finalResult;
45788
45942
  let errorResult;
45789
45943
  async function* eventGenerator() {
@@ -45791,16 +45945,13 @@ function streamGemini(options) {
45791
45945
  try {
45792
45946
  response = await fetch(url, {
45793
45947
  method: "POST",
45794
- headers: {
45795
- "Content-Type": "application/json",
45796
- Accept: "text/event-stream"
45797
- },
45948
+ headers,
45798
45949
  body: JSON.stringify(body),
45799
45950
  signal: request.abortSignal
45800
45951
  });
45801
45952
  } catch (err) {
45802
45953
  const isAbort = request.abortSignal?.aborted;
45803
- const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Gemini failed: ${err.message}`, {
45954
+ const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Gemini failed: ${err instanceof Error ? err.message : String(err)}`, {
45804
45955
  code: isAbort ? "aborted" : "network",
45805
45956
  provider: "gemini",
45806
45957
  cause: err
@@ -45810,10 +45961,9 @@ function streamGemini(options) {
45810
45961
  return;
45811
45962
  }
45812
45963
  if (!response.ok) {
45813
- const errBody = await response.text().catch(() => "");
45814
45964
  const isRateLimit = response.status === 429;
45815
45965
  const isAuth = response.status === 401 || response.status === 403;
45816
- const error = new AIError(`Gemini error (HTTP ${response.status}): ${errBody}`, {
45966
+ const error = new AIError(`Gemini request failed with HTTP status ${response.status}`, {
45817
45967
  code: isAuth ? "auth" : isRateLimit ? "rate-limit" : "provider",
45818
45968
  provider: "gemini",
45819
45969
  status: response.status
@@ -45849,15 +45999,19 @@ function streamGemini(options) {
45849
45999
  } catch {
45850
46000
  continue;
45851
46001
  }
45852
- if (eventData.usageMetadata) {
46002
+ if (eventData.usageMetadata && typeof eventData.usageMetadata === "object") {
45853
46003
  const u = eventData.usageMetadata;
45854
- usage.inputTokens = u.promptTokenCount ?? usage.inputTokens;
45855
- usage.outputTokens = u.candidatesTokenCount ?? usage.outputTokens;
45856
- usage.totalTokens = u.totalTokenCount ?? usage.totalTokens;
45857
- }
45858
- const candidate = eventData.candidates?.[0];
46004
+ if (typeof u.promptTokenCount === "number")
46005
+ usage.inputTokens = u.promptTokenCount;
46006
+ if (typeof u.candidatesTokenCount === "number")
46007
+ usage.outputTokens = u.candidatesTokenCount;
46008
+ if (typeof u.totalTokenCount === "number")
46009
+ usage.totalTokens = u.totalTokenCount;
46010
+ }
46011
+ const candidates = eventData.candidates;
46012
+ const candidate = candidates?.[0];
45859
46013
  if (candidate) {
45860
- if (candidate.finishReason) {
46014
+ if (typeof candidate.finishReason === "string") {
45861
46015
  const fr = candidate.finishReason;
45862
46016
  if (fr === "STOP")
45863
46017
  finishReason = "stop";
@@ -45866,21 +46020,25 @@ function streamGemini(options) {
45866
46020
  else if (fr === "SAFETY")
45867
46021
  finishReason = "error";
45868
46022
  }
45869
- if (candidate.content?.parts) {
45870
- for (const part of candidate.content.parts) {
46023
+ const content = candidate.content;
46024
+ if (Array.isArray(content?.parts)) {
46025
+ for (const rawPart of content.parts) {
46026
+ const part = rawPart;
46027
+ const thoughtSig = typeof part.thoughtSignature === "string" ? part.thoughtSignature : undefined;
45871
46028
  if (part.thought) {
45872
- const thoughtDelta = part.text || "";
45873
- if (thoughtDelta || part.thoughtSignature) {
45874
- const existing = assistantContent.find((c) => c.type === "thinking");
45875
- if (existing) {
45876
- existing.thinking += thoughtDelta;
45877
- if (part.thoughtSignature)
45878
- existing.thoughtSignature = part.thoughtSignature;
46029
+ const thoughtDelta = typeof part.text === "string" ? part.text : "";
46030
+ if (thoughtDelta || thoughtSig) {
46031
+ const lastBlock = assistantContent[assistantContent.length - 1];
46032
+ if (lastBlock && lastBlock.type === "thinking") {
46033
+ lastBlock.thinking += thoughtDelta;
46034
+ if (thoughtSig) {
46035
+ lastBlock.thinkingSignature = thoughtSig;
46036
+ }
45879
46037
  } else {
45880
46038
  assistantContent.push({
45881
46039
  type: "thinking",
45882
46040
  thinking: thoughtDelta,
45883
- thoughtSignature: part.thoughtSignature
46041
+ ...thoughtSig ? { thinkingSignature: thoughtSig } : {}
45884
46042
  });
45885
46043
  }
45886
46044
  if (thoughtDelta) {
@@ -45888,33 +46046,37 @@ function streamGemini(options) {
45888
46046
  }
45889
46047
  }
45890
46048
  } else if (part.text !== undefined) {
45891
- const textDelta = part.text || "";
45892
- if (textDelta || part.thoughtSignature) {
45893
- const existing = assistantContent.find((c) => c.type === "text");
45894
- if (existing) {
45895
- existing.text += textDelta;
45896
- if (part.thoughtSignature)
45897
- existing.thoughtSignature = part.thoughtSignature;
46049
+ const textDelta = typeof part.text === "string" ? part.text : "";
46050
+ if (textDelta || thoughtSig) {
46051
+ const lastBlock = assistantContent[assistantContent.length - 1];
46052
+ if (lastBlock && lastBlock.type === "text") {
46053
+ lastBlock.text += textDelta;
46054
+ if (thoughtSig) {
46055
+ lastBlock.textSignature = thoughtSig;
46056
+ }
45898
46057
  } else {
45899
46058
  assistantContent.push({
45900
46059
  type: "text",
45901
46060
  text: textDelta,
45902
- thoughtSignature: part.thoughtSignature
46061
+ ...thoughtSig ? { textSignature: thoughtSig } : {}
45903
46062
  });
45904
46063
  }
45905
46064
  if (textDelta) {
45906
46065
  yield { type: "text-delta", delta: textDelta };
45907
46066
  }
45908
46067
  }
45909
- } else if (part.functionCall) {
46068
+ } else if (part.functionCall && typeof part.functionCall === "object") {
46069
+ const fc = part.functionCall;
45910
46070
  toolCallIdx++;
45911
- const toolId = `call_${Date.now()}_${toolCallIdx}`;
46071
+ const toolId = `call_${toolCallIdx}`;
46072
+ const fnName = typeof fc.name === "string" ? fc.name : "";
46073
+ const fnArgs = typeof fc.args === "object" && fc.args !== null ? fc.args : {};
45912
46074
  const toolCall = {
45913
46075
  type: "tool-call",
45914
46076
  id: toolId,
45915
- name: part.functionCall.name,
45916
- arguments: part.functionCall.args || {},
45917
- thoughtSignature: part.thoughtSignature
46077
+ name: fnName,
46078
+ arguments: fnArgs,
46079
+ ...thoughtSig ? { thoughtSignature: thoughtSig } : {}
45918
46080
  };
45919
46081
  assistantContent.push(toolCall);
45920
46082
  finishReason = "tool-use";
@@ -45947,7 +46109,7 @@ function streamGemini(options) {
45947
46109
  };
45948
46110
  } catch (err) {
45949
46111
  const isAbort = request.abortSignal?.aborted;
45950
- const error = new AIError(isAbort ? "Inference request aborted." : `Gemini stream reading failed: ${err.message}`, {
46112
+ const error = new AIError(isAbort ? "Inference request aborted." : `Gemini stream reading failed: ${err instanceof Error ? err.message : String(err)}`, {
45951
46113
  code: isAbort ? "aborted" : "provider",
45952
46114
  provider: "gemini",
45953
46115
  cause: err
@@ -45977,8 +46139,16 @@ function streamGemini(options) {
45977
46139
  // src/packages/ai/src/inference.ts
45978
46140
  function streamInference(request, authManager = createAuthManager(), customBaseUrl) {
45979
46141
  const provider = request.model.provider;
45980
- let streamPromise = null;
45981
46142
  const getStream = async () => {
46143
+ let descriptor;
46144
+ try {
46145
+ descriptor = getProviderDescriptor(provider);
46146
+ } catch {
46147
+ throw new AIError(`Unsupported provider: "${provider}"`, {
46148
+ code: "invalid-request",
46149
+ provider
46150
+ });
46151
+ }
45982
46152
  const resolvedAuth = await authManager.resolve(provider, request.abortSignal);
45983
46153
  if (!resolvedAuth && provider !== "custom" && provider !== "ollama") {
45984
46154
  throw new AIError(`No credentials configured for provider: ${provider}`, {
@@ -45991,30 +46161,28 @@ function streamInference(request, authManager = createAuthManager(), customBaseU
45991
46161
  token: "none",
45992
46162
  source: "custom"
45993
46163
  };
45994
- switch (provider) {
45995
- case "anthropic":
46164
+ switch (descriptor.protocol) {
46165
+ case "anthropic-messages":
45996
46166
  return streamAnthropic({ request, auth });
45997
46167
  case "gemini":
45998
46168
  return streamGemini({ request, auth });
45999
- case "openai":
46169
+ case "openai-chat":
46000
46170
  return streamOpenAI({ request, auth });
46001
- case "deepseek":
46002
- case "openrouter":
46003
- case "github-copilot":
46004
- case "groq":
46005
- case "xai":
46006
- case "mistral":
46007
- case "ollama":
46008
- case "custom":
46171
+ case "openai-compatible":
46009
46172
  return streamOpenAICompatible({ request, auth, customBaseUrl });
46173
+ case "openai-responses":
46174
+ throw new AIError(`Protocol "openai-responses" is not yet implemented for provider "${provider}"`, {
46175
+ code: "invalid-request",
46176
+ provider
46177
+ });
46010
46178
  default:
46011
- throw new AIError(`Unsupported provider: "${provider}"`, {
46179
+ throw new AIError(`Unsupported protocol "${descriptor.protocol}" for provider "${provider}"`, {
46012
46180
  code: "invalid-request",
46013
46181
  provider
46014
46182
  });
46015
46183
  }
46016
46184
  };
46017
- streamPromise = getStream();
46185
+ const streamPromise = getStream();
46018
46186
  return {
46019
46187
  async* [Symbol.asyncIterator]() {
46020
46188
  const activeStream = await streamPromise;
@@ -46058,85 +46226,91 @@ function createAIEngine(options) {
46058
46226
  // src/packages/ai/src/models/discovery.ts
46059
46227
  var NON_CHAT_MODEL_KEYWORDS = /(embed|similarity|search-document|dall-e|imagen|veo|whisper|tts|transcribe|speech|voice|audio|image|video|moderation|rerank|robotics|live|aqa|babbage|davinci|lyria|chirp)/i;
46060
46228
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
46061
- async function fetchOpenAIModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46062
- const response = await fetch("https://api.openai.com/v1/models", {
46229
+ async function fetchStandardModelsList(options) {
46230
+ const { provider, url, headers, timeoutMs, transformId, filterItem } = options;
46231
+ const response = await fetch(url, {
46063
46232
  method: "GET",
46064
- headers: {
46065
- Authorization: `Bearer ${apiKey}`
46066
- },
46233
+ headers,
46067
46234
  signal: AbortSignal.timeout(timeoutMs)
46068
46235
  });
46069
46236
  if (!response.ok) {
46070
- throw new Error(`OpenAI API request failed: HTTP ${response.status} (${response.statusText})`);
46237
+ throw new Error(`${provider} API request failed: HTTP ${response.status} (${response.statusText})`);
46071
46238
  }
46072
46239
  const payload = await response.json();
46073
46240
  if (!Array.isArray(payload.data)) {
46074
46241
  return [];
46075
46242
  }
46076
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({
46243
+ const models = [];
46244
+ for (const item of payload.data) {
46245
+ if (!item || typeof item.id !== "string")
46246
+ continue;
46247
+ if (filterItem && !filterItem(item))
46248
+ continue;
46249
+ const finalId = transformId ? transformId(item.id) : item.id;
46250
+ if (!finalId || NON_CHAT_MODEL_KEYWORDS.test(finalId))
46251
+ continue;
46252
+ models.push({
46253
+ provider,
46254
+ modelId: finalId
46255
+ });
46256
+ }
46257
+ return models.sort((a, b) => a.modelId.localeCompare(b.modelId));
46258
+ }
46259
+ async function fetchOpenAIModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46260
+ return await fetchStandardModelsList({
46077
46261
  provider: "openai",
46078
- modelId: item.id
46079
- })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46262
+ url: "https://api.openai.com/v1/models",
46263
+ headers: { Authorization: `Bearer ${apiKey}` },
46264
+ timeoutMs
46265
+ });
46080
46266
  }
46081
46267
  async function fetchGeminiModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46082
- const response = await fetch("https://generativelanguage.googleapis.com/v1beta/openai/models", {
46083
- method: "GET",
46084
- headers: {
46085
- Authorization: `Bearer ${apiKey}`
46086
- },
46087
- signal: AbortSignal.timeout(timeoutMs)
46088
- });
46089
- if (!response.ok) {
46090
- throw new Error(`Gemini API request failed: HTTP ${response.status} (${response.statusText})`);
46091
- }
46092
- const payload = await response.json();
46093
- if (!Array.isArray(payload.data)) {
46094
- return [];
46095
- }
46096
- return payload.data.map((item) => typeof item.id === "string" ? item.id.replace(/^models\//, "") : "").filter((id) => id && !NON_CHAT_MODEL_KEYWORDS.test(id)).map((id) => ({
46268
+ return await fetchStandardModelsList({
46097
46269
  provider: "gemini",
46098
- modelId: id
46099
- })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46270
+ url: "https://generativelanguage.googleapis.com/v1beta/openai/models",
46271
+ headers: { Authorization: `Bearer ${apiKey}` },
46272
+ timeoutMs,
46273
+ transformId: (id) => id.replace(/^models\//, "")
46274
+ });
46100
46275
  }
46101
46276
  async function fetchAnthropicModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46102
- const response = await fetch("https://api.anthropic.com/v1/models", {
46103
- method: "GET",
46277
+ return await fetchStandardModelsList({
46278
+ provider: "anthropic",
46279
+ url: "https://api.anthropic.com/v1/models",
46104
46280
  headers: {
46105
46281
  "x-api-key": apiKey,
46106
46282
  "anthropic-version": "2023-06-01"
46107
46283
  },
46108
- signal: AbortSignal.timeout(timeoutMs)
46284
+ timeoutMs
46109
46285
  });
46110
- if (!response.ok) {
46111
- throw new Error(`Anthropic API request failed: HTTP ${response.status} (${response.statusText})`);
46112
- }
46113
- const payload = await response.json();
46114
- if (!Array.isArray(payload.data)) {
46115
- return [];
46116
- }
46117
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({
46118
- provider: "anthropic",
46119
- modelId: item.id
46120
- })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46121
46286
  }
46122
46287
  async function fetchDeepSeekModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46123
- const response = await fetch("https://api.deepseek.com/models", {
46124
- method: "GET",
46288
+ return await fetchStandardModelsList({
46289
+ provider: "deepseek",
46290
+ url: "https://api.deepseek.com/models",
46125
46291
  headers: { Authorization: `Bearer ${apiKey}` },
46126
- signal: AbortSignal.timeout(timeoutMs)
46292
+ timeoutMs
46127
46293
  });
46128
- if (!response.ok) {
46129
- throw new Error(`DeepSeek API request failed: HTTP ${response.status} (${response.statusText})`);
46294
+ }
46295
+ function isValidOpenRouterNextUrl(rawUrl) {
46296
+ try {
46297
+ const parsed = new URL(rawUrl, "https://openrouter.ai");
46298
+ return parsed.protocol === "https:" && parsed.origin === "https://openrouter.ai" && parsed.pathname.startsWith("/api/v1/models");
46299
+ } catch {
46300
+ return false;
46130
46301
  }
46131
- const payload = await response.json();
46132
- if (!Array.isArray(payload.data))
46133
- return [];
46134
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({ provider: "deepseek", modelId: item.id })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46135
46302
  }
46136
46303
  async function fetchOpenRouterModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46137
46304
  const results = [];
46138
46305
  let url = "https://openrouter.ai/api/v1/models";
46139
- while (url) {
46306
+ const seenUrls = new Set;
46307
+ const MAX_PAGES = 20;
46308
+ let pageCount = 0;
46309
+ while (url && pageCount < MAX_PAGES) {
46310
+ if (seenUrls.has(url))
46311
+ break;
46312
+ seenUrls.add(url);
46313
+ pageCount++;
46140
46314
  const response = await fetch(url, {
46141
46315
  method: "GET",
46142
46316
  headers: { Authorization: `Bearer ${apiKey}` },
@@ -46148,7 +46322,7 @@ async function fetchOpenRouterModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_M
46148
46322
  const payload = await response.json();
46149
46323
  if (Array.isArray(payload.data)) {
46150
46324
  for (const item of payload.data) {
46151
- if (typeof item.id !== "string")
46325
+ if (!item || typeof item.id !== "string")
46152
46326
  continue;
46153
46327
  const hasTextModality = item.architecture?.output_modalities?.includes("text");
46154
46328
  if (hasTextModality === false)
@@ -46158,70 +46332,51 @@ async function fetchOpenRouterModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_M
46158
46332
  }
46159
46333
  }
46160
46334
  }
46161
- const next = payload.links?.next ?? null;
46162
- url = next ? new URL(next, "https://openrouter.ai").toString() : null;
46335
+ const next = payload.links?.next;
46336
+ if (typeof next === "string" && next.trim() && isValidOpenRouterNextUrl(next)) {
46337
+ url = new URL(next, "https://openrouter.ai").toString();
46338
+ } else {
46339
+ url = null;
46340
+ }
46163
46341
  }
46164
46342
  return results.sort((a, b) => a.modelId.localeCompare(b.modelId));
46165
46343
  }
46166
46344
  async function fetchGroqModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46167
- const response = await fetch("https://api.groq.com/openai/v1/models", {
46168
- method: "GET",
46345
+ return await fetchStandardModelsList({
46346
+ provider: "groq",
46347
+ url: "https://api.groq.com/openai/v1/models",
46169
46348
  headers: { Authorization: `Bearer ${apiKey}` },
46170
- signal: AbortSignal.timeout(timeoutMs)
46349
+ timeoutMs
46171
46350
  });
46172
- if (!response.ok) {
46173
- throw new Error(`Groq API request failed: HTTP ${response.status} (${response.statusText})`);
46174
- }
46175
- const payload = await response.json();
46176
- if (!Array.isArray(payload.data))
46177
- return [];
46178
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({ provider: "groq", modelId: item.id })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46179
46351
  }
46180
46352
  async function fetchXAIModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46181
- const response = await fetch("https://api.x.ai/v1/models", {
46182
- method: "GET",
46353
+ return await fetchStandardModelsList({
46354
+ provider: "xai",
46355
+ url: "https://api.x.ai/v1/models",
46183
46356
  headers: { Authorization: `Bearer ${apiKey}` },
46184
- signal: AbortSignal.timeout(timeoutMs)
46357
+ timeoutMs
46185
46358
  });
46186
- if (!response.ok) {
46187
- throw new Error(`xAI API request failed: HTTP ${response.status} (${response.statusText})`);
46188
- }
46189
- const payload = await response.json();
46190
- if (!Array.isArray(payload.data))
46191
- return [];
46192
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({ provider: "xai", modelId: item.id })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46193
46359
  }
46194
46360
  async function fetchMistralModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46195
- const response = await fetch("https://api.mistral.ai/v1/models", {
46196
- method: "GET",
46361
+ return await fetchStandardModelsList({
46362
+ provider: "mistral",
46363
+ url: "https://api.mistral.ai/v1/models",
46197
46364
  headers: { Authorization: `Bearer ${apiKey}` },
46198
- signal: AbortSignal.timeout(timeoutMs)
46365
+ timeoutMs
46199
46366
  });
46200
- if (!response.ok) {
46201
- throw new Error(`Mistral API request failed: HTTP ${response.status} (${response.statusText})`);
46202
- }
46203
- const payload = await response.json();
46204
- if (!Array.isArray(payload.data))
46205
- return [];
46206
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({ provider: "mistral", modelId: item.id })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46207
46367
  }
46208
46368
  async function fetchOllamaModels(baseUrl = "http://localhost:11434/v1", timeoutMs = 3000) {
46209
- const endpoint = baseUrl.replace(/\/+$/, "") + "/models";
46210
- const response = await fetch(endpoint, {
46211
- method: "GET",
46212
- signal: AbortSignal.timeout(timeoutMs)
46369
+ const endpoint = `${baseUrl.replace(/\/+$/, "")}/models`;
46370
+ return await fetchStandardModelsList({
46371
+ provider: "ollama",
46372
+ url: endpoint,
46373
+ timeoutMs
46213
46374
  });
46214
- if (!response.ok) {
46215
- throw new Error(`Ollama API request failed: HTTP ${response.status} (${response.statusText})`);
46216
- }
46217
- const payload = await response.json();
46218
- if (!Array.isArray(payload.data))
46219
- return [];
46220
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({ provider: "ollama", modelId: item.id })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46221
46375
  }
46222
46376
  async function fetchGitHubCopilotModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46223
- const response = await fetch("https://api.individual.githubcopilot.com/models", {
46224
- method: "GET",
46377
+ return await fetchStandardModelsList({
46378
+ provider: "github-copilot",
46379
+ url: "https://api.individual.githubcopilot.com/models",
46225
46380
  headers: {
46226
46381
  Accept: "application/json",
46227
46382
  Authorization: `Bearer ${apiKey}`,
@@ -46231,16 +46386,21 @@ async function fetchGitHubCopilotModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOU
46231
46386
  "Copilot-Integration-Id": "vscode-chat",
46232
46387
  "X-GitHub-Api-Version": "2026-06-01"
46233
46388
  },
46234
- signal: AbortSignal.timeout(timeoutMs)
46389
+ timeoutMs,
46390
+ filterItem: (item) => item.capabilities?.supports?.tool_calls !== false
46235
46391
  });
46236
- if (!response.ok) {
46237
- throw new Error(`GitHub Copilot API request failed: HTTP ${response.status} (${response.statusText})`);
46238
- }
46239
- const payload = await response.json();
46240
- if (!Array.isArray(payload.data))
46241
- return [];
46242
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id) && item.capabilities?.supports?.tool_calls !== false).map((item) => ({ provider: "github-copilot", modelId: item.id })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46243
46392
  }
46393
+ var API_KEY_FETCHERS = {
46394
+ openai: fetchOpenAIModels,
46395
+ gemini: fetchGeminiModels,
46396
+ anthropic: fetchAnthropicModels,
46397
+ deepseek: fetchDeepSeekModels,
46398
+ openrouter: fetchOpenRouterModels,
46399
+ "github-copilot": fetchGitHubCopilotModels,
46400
+ groq: fetchGroqModels,
46401
+ xai: fetchXAIModels,
46402
+ mistral: fetchMistralModels
46403
+ };
46244
46404
  async function fetchAvailableModels(authCtx, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46245
46405
  const result = {
46246
46406
  models: [],
@@ -46259,152 +46419,30 @@ async function fetchAvailableModels(authCtx, timeoutMs = DEFAULT_FETCH_TIMEOUT_M
46259
46419
  }
46260
46420
  };
46261
46421
  const tasks = [];
46262
- const openaiKey = await authCtx.getApiKey("openai");
46263
- if (openaiKey) {
46264
- tasks.push(fetchOpenAIModels(openaiKey, timeoutMs).then((models) => {
46265
- result.models.push(...models);
46266
- result.providers.openai = {
46267
- status: "available",
46268
- modelCount: models.length
46269
- };
46270
- }).catch((err) => {
46271
- result.providers.openai = {
46272
- status: "error",
46273
- modelCount: 0,
46274
- error: err instanceof Error ? err.message : String(err)
46275
- };
46276
- }));
46277
- }
46278
- const geminiKey = await authCtx.getApiKey("gemini");
46279
- if (geminiKey) {
46280
- tasks.push(fetchGeminiModels(geminiKey, timeoutMs).then((models) => {
46281
- result.models.push(...models);
46282
- result.providers.gemini = {
46283
- status: "available",
46284
- modelCount: models.length
46285
- };
46286
- }).catch((err) => {
46287
- result.providers.gemini = {
46288
- status: "error",
46289
- modelCount: 0,
46290
- error: err instanceof Error ? err.message : String(err)
46291
- };
46292
- }));
46293
- }
46294
- const anthropicKey = await authCtx.getApiKey("anthropic");
46295
- if (anthropicKey) {
46296
- tasks.push(fetchAnthropicModels(anthropicKey, timeoutMs).then((models) => {
46297
- result.models.push(...models);
46298
- result.providers.anthropic = {
46299
- status: "available",
46300
- modelCount: models.length
46301
- };
46302
- }).catch((err) => {
46303
- result.providers.anthropic = {
46304
- status: "error",
46305
- modelCount: 0,
46306
- error: err instanceof Error ? err.message : String(err)
46307
- };
46308
- }));
46309
- }
46310
- const deepseekKey = await authCtx.getApiKey("deepseek");
46311
- if (deepseekKey) {
46312
- tasks.push(fetchDeepSeekModels(deepseekKey, timeoutMs).then((models) => {
46313
- result.models.push(...models);
46314
- result.providers.deepseek = {
46315
- status: "available",
46316
- modelCount: models.length
46317
- };
46318
- }).catch((err) => {
46319
- result.providers.deepseek = {
46320
- status: "error",
46321
- modelCount: 0,
46322
- error: err instanceof Error ? err.message : String(err)
46323
- };
46324
- }));
46325
- }
46326
- const openrouterKey = await authCtx.getApiKey("openrouter");
46327
- if (openrouterKey) {
46328
- tasks.push(fetchOpenRouterModels(openrouterKey, timeoutMs).then((models) => {
46329
- result.models.push(...models);
46330
- result.providers.openrouter = {
46331
- status: "available",
46332
- modelCount: models.length
46333
- };
46334
- }).catch((err) => {
46335
- result.providers.openrouter = {
46336
- status: "error",
46337
- modelCount: 0,
46338
- error: err instanceof Error ? err.message : String(err)
46339
- };
46340
- }));
46341
- }
46342
- const copilotKey = await authCtx.getApiKey("github-copilot");
46343
- if (copilotKey) {
46344
- tasks.push(fetchGitHubCopilotModels(copilotKey, timeoutMs).then((models) => {
46345
- result.models.push(...models);
46346
- result.providers["github-copilot"] = {
46347
- status: "available",
46348
- modelCount: models.length
46349
- };
46350
- }).catch((err) => {
46351
- result.providers["github-copilot"] = {
46352
- status: "error",
46353
- modelCount: 0,
46354
- error: err instanceof Error ? err.message : String(err)
46355
- };
46356
- }));
46357
- }
46358
- const groqKey = await authCtx.getApiKey("groq");
46359
- if (groqKey) {
46360
- tasks.push(fetchGroqModels(groqKey, timeoutMs).then((models) => {
46361
- result.models.push(...models);
46362
- result.providers.groq = {
46363
- status: "available",
46364
- modelCount: models.length
46365
- };
46366
- }).catch((err) => {
46367
- result.providers.groq = {
46368
- status: "error",
46369
- modelCount: 0,
46370
- error: err instanceof Error ? err.message : String(err)
46371
- };
46372
- }));
46373
- }
46374
- const xaiKey = await authCtx.getApiKey("xai");
46375
- if (xaiKey) {
46376
- tasks.push(fetchXAIModels(xaiKey, timeoutMs).then((models) => {
46377
- result.models.push(...models);
46378
- result.providers.xai = {
46379
- status: "available",
46380
- modelCount: models.length
46381
- };
46382
- }).catch((err) => {
46383
- result.providers.xai = {
46384
- status: "error",
46385
- modelCount: 0,
46386
- error: err instanceof Error ? err.message : String(err)
46387
- };
46388
- }));
46389
- }
46390
- const mistralKey = await authCtx.getApiKey("mistral");
46391
- if (mistralKey) {
46392
- tasks.push(fetchMistralModels(mistralKey, timeoutMs).then((models) => {
46393
- result.models.push(...models);
46394
- result.providers.mistral = {
46395
- status: "available",
46396
- modelCount: models.length
46397
- };
46398
- }).catch((err) => {
46399
- result.providers.mistral = {
46400
- status: "error",
46401
- modelCount: 0,
46402
- error: err instanceof Error ? err.message : String(err)
46403
- };
46404
- }));
46422
+ for (const [providerKey, fetcher] of Object.entries(API_KEY_FETCHERS)) {
46423
+ const provider = providerKey;
46424
+ if (!fetcher)
46425
+ continue;
46426
+ const apiKey = await authCtx.getApiKey(provider);
46427
+ if (apiKey) {
46428
+ tasks.push(fetcher(apiKey, timeoutMs).then((models) => {
46429
+ result.models.push(...models);
46430
+ result.providers[provider] = {
46431
+ status: "available",
46432
+ modelCount: models.length
46433
+ };
46434
+ }).catch((err) => {
46435
+ result.providers[provider] = {
46436
+ status: "error",
46437
+ modelCount: 0,
46438
+ error: err instanceof Error ? err.message : String(err)
46439
+ };
46440
+ }));
46441
+ }
46405
46442
  }
46406
46443
  const ollamaUrl = authCtx.getOllamaEndpoint?.() || "http://localhost:11434/v1";
46407
- tasks.push(fetchOllamaModels(ollamaUrl, 2000).then((models) => {
46444
+ const ollamaTimeout = Math.min(timeoutMs, 3000);
46445
+ tasks.push(fetchOllamaModels(ollamaUrl, ollamaTimeout).then((models) => {
46408
46446
  if (models.length > 0) {
46409
46447
  result.models.push(...models);
46410
46448
  result.providers.ollama = {
@@ -53893,15 +53931,15 @@ var defaultToolCatalog = new ToolCatalog;
53893
53931
 
53894
53932
  // src/packages/agents/src/tools/read-file/index.ts
53895
53933
  import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync3, realpathSync } from "node:fs";
53896
- import { isAbsolute, resolve, sep } from "node:path";
53934
+ import { isAbsolute, resolve as resolve2, sep } from "node:path";
53897
53935
  var readFileInputSchema = object({
53898
53936
  path: string2().describe("The path of the file to read (relative to current working directory or absolute)."),
53899
53937
  offset: number2().int().min(1).optional().describe("The line number to start reading from (1-indexed). Defaults to 1."),
53900
53938
  limit: number2().int().min(1).max(2000).optional().describe("The maximum number of lines to read. Defaults to 200.")
53901
53939
  });
53902
53940
  function isPathInside(targetPath, basePath) {
53903
- const normTarget = resolve(targetPath);
53904
- const normBase = resolve(basePath);
53941
+ const normTarget = resolve2(targetPath);
53942
+ const normBase = resolve2(basePath);
53905
53943
  if (normTarget === normBase) {
53906
53944
  return true;
53907
53945
  }
@@ -53913,7 +53951,7 @@ function resolveSafeReadPath(cwd, filePath) {
53913
53951
  throw new Error("File path cannot be empty.");
53914
53952
  }
53915
53953
  const trimmed = filePath.trim();
53916
- const resolved = isAbsolute(trimmed) ? resolve(trimmed) : resolve(cwd, trimmed);
53954
+ const resolved = isAbsolute(trimmed) ? resolve2(trimmed) : resolve2(cwd, trimmed);
53917
53955
  if (!isPathInside(resolved, cwd)) {
53918
53956
  throw new Error(`Access denied: path "${filePath}" resolves outside the trusted workspace root ("${cwd}").`);
53919
53957
  }
@@ -54014,12 +54052,12 @@ import { existsSync as existsSync8, readFileSync as readFileSync5, statSync } fr
54014
54052
  // src/packages/services/src/checkpoint/path.ts
54015
54053
  import { createHash } from "node:crypto";
54016
54054
  import { existsSync as existsSync6, lstatSync as lstatSync2, realpathSync as realpathSync3 } from "node:fs";
54017
- import { dirname as dirname4, isAbsolute as isAbsolute2, relative, resolve as resolve3, sep as sep3 } from "node:path";
54055
+ import { dirname as dirname4, isAbsolute as isAbsolute2, relative, resolve as resolve4, sep as sep3 } from "node:path";
54018
54056
 
54019
54057
  // src/packages/services/src/config/settings.ts
54020
54058
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync3 } from "node:fs";
54021
54059
  import { homedir as homedir4 } from "node:os";
54022
- import { join as join6, resolve as resolve2, sep as sep2 } from "node:path";
54060
+ import { join as join6, resolve as resolve3, sep as sep2 } from "node:path";
54023
54061
  function getSettingsDir() {
54024
54062
  if (process.env.STEWARD_SETTINGS_DIR) {
54025
54063
  return process.env.STEWARD_SETTINGS_DIR;
@@ -54073,7 +54111,7 @@ function saveThemeSelection(name) {
54073
54111
  });
54074
54112
  }
54075
54113
  function normalizeFolderPath(inputPath) {
54076
- let absolute = resolve2(inputPath);
54114
+ let absolute = resolve3(inputPath);
54077
54115
  try {
54078
54116
  absolute = realpathSync2(absolute);
54079
54117
  } catch {}
@@ -54145,7 +54183,7 @@ function resolveDirectMutationPath(cwd, filePath) {
54145
54183
  }
54146
54184
  const trimmed = filePath.trim();
54147
54185
  const workspaceRoot = normalizeFolderPath(cwd);
54148
- const resolved = isAbsolute2(trimmed) ? resolve3(trimmed) : resolve3(cwd, trimmed);
54186
+ const resolved = isAbsolute2(trimmed) ? resolve4(trimmed) : resolve4(cwd, trimmed);
54149
54187
  if (!isPathInside2(resolved, workspaceRoot)) {
54150
54188
  throw new Error(`Access denied: path "${filePath}" resolves outside the trusted workspace root ("${cwd}").`);
54151
54189
  }
@@ -54732,7 +54770,7 @@ var editFileTool = {
54732
54770
 
54733
54771
  // src/packages/agents/src/tools/glob/index.ts
54734
54772
  import { readdirSync as readdirSync2 } from "node:fs";
54735
- import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve4 } from "node:path";
54773
+ import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve5 } from "node:path";
54736
54774
  var globInputSchema = object({
54737
54775
  pattern: string2().describe('Glob pattern to match files against (e.g. "**/*.ts", "src/*.json").'),
54738
54776
  path: string2().optional().describe("Starting directory path. Defaults to workspace root."),
@@ -54785,9 +54823,9 @@ var globTool = {
54785
54823
  },
54786
54824
  execute: async (args, context) => {
54787
54825
  const startTime = Date.now();
54788
- const searchRoot = args.path ? isAbsolute3(args.path) ? resolve4(args.path) : resolve4(context.cwd, args.path) : context.cwd;
54789
- const normCwd = resolve4(context.cwd);
54790
- const normTarget = resolve4(searchRoot);
54826
+ const searchRoot = args.path ? isAbsolute3(args.path) ? resolve5(args.path) : resolve5(context.cwd, args.path) : context.cwd;
54827
+ const normCwd = resolve5(context.cwd);
54828
+ const normTarget = resolve5(searchRoot);
54791
54829
  const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
54792
54830
  if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
54793
54831
  throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
@@ -54814,7 +54852,7 @@ var globTool = {
54814
54852
 
54815
54853
  // src/packages/agents/src/tools/grep/index.ts
54816
54854
  import { readFileSync as readFileSync7, readdirSync as readdirSync3 } from "node:fs";
54817
- import { isAbsolute as isAbsolute4, join as join9, relative as relative3, resolve as resolve5 } from "node:path";
54855
+ import { isAbsolute as isAbsolute4, join as join9, relative as relative3, resolve as resolve6 } from "node:path";
54818
54856
  var grepInputSchema = object({
54819
54857
  pattern: string2().describe("The regex or text pattern to search for across files."),
54820
54858
  path: string2().optional().describe("Target directory or file path to search. Defaults to current directory."),
@@ -54890,9 +54928,9 @@ var grepTool = {
54890
54928
  },
54891
54929
  execute: async (args, context) => {
54892
54930
  const startTime = Date.now();
54893
- const searchRoot = args.path ? isAbsolute4(args.path) ? resolve5(args.path) : resolve5(context.cwd, args.path) : context.cwd;
54894
- const normCwd = resolve5(context.cwd);
54895
- const normTarget = resolve5(searchRoot);
54931
+ const searchRoot = args.path ? isAbsolute4(args.path) ? resolve6(args.path) : resolve6(context.cwd, args.path) : context.cwd;
54932
+ const normCwd = resolve6(context.cwd);
54933
+ const normTarget = resolve6(searchRoot);
54896
54934
  const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
54897
54935
  if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
54898
54936
  throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
@@ -54951,7 +54989,7 @@ var grepTool = {
54951
54989
 
54952
54990
  // src/packages/agents/src/tools/list-dir/index.ts
54953
54991
  import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
54954
- import { isAbsolute as isAbsolute5, join as join10, resolve as resolve6 } from "node:path";
54992
+ import { isAbsolute as isAbsolute5, join as join10, resolve as resolve7 } from "node:path";
54955
54993
  var listDirInputSchema = object({
54956
54994
  path: string2().optional().describe("Directory path to list. Defaults to current working directory."),
54957
54995
  recursive: boolean2().optional().describe("If true, lists files recursively (depth 2). Defaults to false.")
@@ -54969,9 +55007,9 @@ var listDirTool = {
54969
55007
  return `Listed ${files} file${files === 1 ? "" : "s"}, ${dirs} director${dirs === 1 ? "y" : "ies"}`;
54970
55008
  },
54971
55009
  execute: async (args, context) => {
54972
- const targetPath = args.path ? isAbsolute5(args.path) ? resolve6(args.path) : resolve6(context.cwd, args.path) : context.cwd;
54973
- const normCwd = resolve6(context.cwd);
54974
- const normTarget = resolve6(targetPath);
55010
+ const targetPath = args.path ? isAbsolute5(args.path) ? resolve7(args.path) : resolve7(context.cwd, args.path) : context.cwd;
55011
+ const normCwd = resolve7(context.cwd);
55012
+ const normTarget = resolve7(targetPath);
54975
55013
  const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
54976
55014
  if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
54977
55015
  throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
@@ -55407,18 +55445,18 @@ Process error: ${err.message}
55407
55445
  // src/packages/agents/src/tools/bash/command-policy.ts
55408
55446
  import { existsSync as existsSync10, realpathSync as realpathSync4 } from "node:fs";
55409
55447
  import { homedir as homedir6 } from "node:os";
55410
- import { isAbsolute as isAbsolute6, resolve as resolve7, sep as sep4 } from "node:path";
55448
+ import { isAbsolute as isAbsolute6, resolve as resolve8, sep as sep4 } from "node:path";
55411
55449
  function expandHome(filepath) {
55412
55450
  if (filepath === "~") {
55413
55451
  return homedir6();
55414
55452
  }
55415
55453
  if (filepath.startsWith("~/") || filepath.startsWith("~\\")) {
55416
- return resolve7(homedir6(), filepath.slice(2));
55454
+ return resolve8(homedir6(), filepath.slice(2));
55417
55455
  }
55418
55456
  return filepath;
55419
55457
  }
55420
55458
  function normalizeRoot(inputPath) {
55421
- let absolute = resolve7(inputPath);
55459
+ let absolute = resolve8(inputPath);
55422
55460
  try {
55423
55461
  absolute = realpathSync4(absolute);
55424
55462
  } catch {}
@@ -55553,7 +55591,7 @@ function arePathTokensInsideWorkspace(tokens, startIndex, cwd) {
55553
55591
  continue;
55554
55592
  }
55555
55593
  const expanded = expandHome(tok);
55556
- const resolved = isAbsolute6(expanded) ? resolve7(expanded) : resolve7(cwd, expanded);
55594
+ const resolved = isAbsolute6(expanded) ? resolve8(expanded) : resolve8(cwd, expanded);
55557
55595
  if (!isContainedInWorkspace(resolved, cwd)) {
55558
55596
  return false;
55559
55597
  }
@@ -55941,8 +55979,8 @@ import { isIP } from "node:net";
55941
55979
  // package.json
55942
55980
  var package_default = {
55943
55981
  name: "steward-cli",
55944
- version: "0.22.0",
55945
- description: "Interactive AI engineering assistant for the terminal with human-in-the-loop control across local and frontier models.",
55982
+ version: "0.23.0",
55983
+ description: "Interactive AI engineering assistant for the terminal.",
55946
55984
  author: "sapirrior",
55947
55985
  license: "MIT",
55948
55986
  files: [
@@ -56296,7 +56334,7 @@ var webSearchTool = {
56296
56334
  // src/packages/agents/src/skills/discovery.ts
56297
56335
  import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
56298
56336
  import { homedir as homedir7 } from "node:os";
56299
- import { isAbsolute as isAbsolute7, join as join12, normalize, resolve as resolve8, sep as sep5 } from "node:path";
56337
+ import { isAbsolute as isAbsolute7, join as join12, normalize, resolve as resolve9, sep as sep5 } from "node:path";
56300
56338
  var MAX_SKILL_FILE_SIZE = 64 * 1024;
56301
56339
  function parseSkillFrontmatter(content) {
56302
56340
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -56411,8 +56449,8 @@ function readSkillResource(name, relativePath, cwd = process.cwd(), maxBytes = M
56411
56449
  if (normalizedRelative.startsWith("..") || normalizedRelative.includes(`${sep5}..`)) {
56412
56450
  throw new Error(`Invalid skill resource path: directory traversal ("..") is forbidden (${trimmedPath})`);
56413
56451
  }
56414
- targetFilePath = resolve8(skill.dirPath, normalizedRelative);
56415
- const resolvedDir = resolve8(skill.dirPath);
56452
+ targetFilePath = resolve9(skill.dirPath, normalizedRelative);
56453
+ const resolvedDir = resolve9(skill.dirPath);
56416
56454
  if (!targetFilePath.startsWith(resolvedDir + sep5) && targetFilePath !== resolvedDir) {
56417
56455
  throw new Error(`Resource "${trimmedPath}" resolves outside skill directory`);
56418
56456
  }
@@ -57646,7 +57684,7 @@ import {
57646
57684
  unlinkSync as unlinkSync5,
57647
57685
  writeSync as writeSync5
57648
57686
  } from "node:fs";
57649
- import { dirname as dirname6, join as join15, resolve as resolve9 } from "node:path";
57687
+ import { dirname as dirname6, join as join15, resolve as resolve10 } from "node:path";
57650
57688
  import { randomUUID as randomUUID5 } from "node:crypto";
57651
57689
  function getFileStateOnDisk(absPath) {
57652
57690
  if (!existsSync15(absPath)) {
@@ -57744,7 +57782,7 @@ async function executeRewind(options) {
57744
57782
  if (!fileCp.mutationCommitted)
57745
57783
  continue;
57746
57784
  const relPath = fileCp.relativePath;
57747
- const absPath = resolve9(workspaceRoot, relPath);
57785
+ const absPath = resolve10(workspaceRoot, relPath);
57748
57786
  if (!fileOperationsMap.has(relPath)) {
57749
57787
  fileOperationsMap.set(relPath, {
57750
57788
  relativePath: relPath,
@@ -57895,7 +57933,7 @@ async function recoverPendingCheckpoint(workspaceRoot, sessionId) {
57895
57933
  if (journal.phase === "files-restored" || journal.phase === "files-pending") {
57896
57934
  for (const file of journal.files) {
57897
57935
  if (file.post) {
57898
- const absPath = resolve9(workspaceRoot, file.relativePath);
57936
+ const absPath = resolve10(workspaceRoot, file.relativePath);
57899
57937
  try {
57900
57938
  restoreFileStateToDisk(workspaceHash, absPath, file.post);
57901
57939
  } catch {}
@@ -58669,35 +58707,7 @@ function getDiscoveryAuthContext() {
58669
58707
  const resolved = await authManager.resolve(provider).catch(() => {
58670
58708
  return;
58671
58709
  });
58672
- if (resolved?.token && resolved.token !== "none") {
58673
- return resolved.token;
58674
- }
58675
- switch (provider) {
58676
- case "openai":
58677
- return config.openaiApiKey;
58678
- case "gemini":
58679
- return config.geminiApiKey;
58680
- case "anthropic":
58681
- return config.anthropicApiKey;
58682
- case "deepseek":
58683
- return config.deepseekApiKey;
58684
- case "openrouter":
58685
- return config.openrouterApiKey;
58686
- case "github-copilot":
58687
- return config.copilotGithubToken;
58688
- case "groq":
58689
- return config.groqApiKey;
58690
- case "xai":
58691
- return config.xaiApiKey;
58692
- case "mistral":
58693
- return config.mistralApiKey;
58694
- case "ollama":
58695
- return "none";
58696
- case "custom":
58697
- return config.custom.apiKey || "none";
58698
- default:
58699
- return;
58700
- }
58710
+ return resolved?.token && resolved.token !== "none" ? resolved.token : undefined;
58701
58711
  },
58702
58712
  getCustomEndpoint: () => config.custom,
58703
58713
  getOllamaEndpoint: () => config.ollamaBaseUrl