steward-cli 0.22.0 → 0.25.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 +806 -775
  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
@@ -44120,7 +44139,7 @@ async function loginGitHubCopilot(interaction) {
44120
44139
  signal
44121
44140
  });
44122
44141
  } catch (err) {
44123
- throw new AIError(`Failed to initiate GitHub device authorization: ${err.message}`, {
44142
+ throw new AIError(`Failed to initiate GitHub device authorization: ${err instanceof Error ? err.message : String(err)}`, {
44124
44143
  code: "oauth",
44125
44144
  provider: "github-copilot",
44126
44145
  cause: err
@@ -44161,18 +44180,21 @@ async function loginGitHubCopilot(interaction) {
44161
44180
  signal
44162
44181
  });
44163
44182
  const data = await res.json();
44164
- if (data?.access_token) {
44183
+ if (typeof data?.access_token === "string") {
44165
44184
  return { status: "complete", value: data.access_token };
44166
44185
  }
44167
44186
  if (data?.error === "authorization_pending") {
44168
44187
  return { status: "pending" };
44169
44188
  }
44170
44189
  if (data?.error === "slow_down") {
44171
- return { status: "slow_down", intervalSeconds: data.interval };
44190
+ return {
44191
+ status: "slow_down",
44192
+ intervalSeconds: typeof data.interval === "number" ? data.interval : undefined
44193
+ };
44172
44194
  }
44173
44195
  return {
44174
44196
  status: "failed",
44175
- message: data?.error_description || data?.error || "Authorization failed"
44197
+ message: typeof data?.error_description === "string" ? data.error_description : typeof data?.error === "string" ? data.error : "Authorization failed"
44176
44198
  };
44177
44199
  }
44178
44200
  });
@@ -44196,15 +44218,14 @@ async function refreshGitHubCopilotToken(githubAccessToken, signal) {
44196
44218
  signal
44197
44219
  });
44198
44220
  } catch (err) {
44199
- throw new AIError(`Failed to fetch Copilot token: ${err.message}`, {
44221
+ throw new AIError(`Failed to fetch Copilot token: ${err instanceof Error ? err.message : String(err)}`, {
44200
44222
  code: "oauth",
44201
44223
  provider: "github-copilot",
44202
44224
  cause: err
44203
44225
  });
44204
44226
  }
44205
44227
  if (!res.ok) {
44206
- const text = await res.text().catch(() => "");
44207
- throw new AIError(`GitHub Copilot token exchange failed (HTTP ${res.status}): ${text}`, {
44228
+ throw new AIError(`GitHub Copilot token exchange failed with HTTP status ${res.status}`, {
44208
44229
  code: "oauth",
44209
44230
  provider: "github-copilot",
44210
44231
  status: res.status
@@ -44239,6 +44260,23 @@ function toGitHubCopilotAuth(credential) {
44239
44260
  }
44240
44261
 
44241
44262
  // src/packages/ai/src/auth/resolver.ts
44263
+ var OAUTH_ADAPTERS = {
44264
+ anthropic: {
44265
+ login: loginAnthropic,
44266
+ refresh: refreshAnthropic,
44267
+ toResolvedAuth: toAnthropicAuth
44268
+ },
44269
+ openrouter: {
44270
+ login: loginOpenRouter,
44271
+ refresh: refreshOpenRouter,
44272
+ toResolvedAuth: toOpenRouterAuth
44273
+ },
44274
+ "github-copilot": {
44275
+ login: loginGitHubCopilot,
44276
+ refresh: refreshGitHubCopilot,
44277
+ toResolvedAuth: toGitHubCopilotAuth
44278
+ }
44279
+ };
44242
44280
  var DEFAULT_REFRESH_MARGIN_MS = 5 * 60 * 1000;
44243
44281
 
44244
44282
  class AuthResolver {
@@ -44286,32 +44324,21 @@ class AuthResolver {
44286
44324
  return;
44287
44325
  }
44288
44326
  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);
44327
+ const adapter = OAUTH_ADAPTERS[provider];
44328
+ if (adapter) {
44329
+ return await adapter.refresh(credential, signal);
44297
44330
  }
44298
44331
  return credential;
44299
44332
  }
44300
44333
  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);
44334
+ const adapter = OAUTH_ADAPTERS[provider];
44335
+ if (adapter) {
44336
+ return adapter.toResolvedAuth(credential);
44309
44337
  }
44310
- return {
44311
- type: "oauth",
44312
- token: credential.accessToken,
44313
- source: "oauth"
44314
- };
44338
+ throw new AIError(`Unsupported OAuth provider: "${provider}"`, {
44339
+ code: "oauth",
44340
+ provider
44341
+ });
44315
44342
  }
44316
44343
  }
44317
44344
 
@@ -44319,35 +44346,96 @@ class AuthResolver {
44319
44346
  import * as fs from "node:fs/promises";
44320
44347
  import * as path from "node:path";
44321
44348
  import * as os2 from "node:os";
44349
+ var fileMutexes = new Map;
44350
+ function validateAuthStorage(data) {
44351
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
44352
+ return false;
44353
+ }
44354
+ const root = data;
44355
+ if (root.version !== 1) {
44356
+ return false;
44357
+ }
44358
+ if (!root.credentials || typeof root.credentials !== "object" || Array.isArray(root.credentials)) {
44359
+ return false;
44360
+ }
44361
+ const credentials = root.credentials;
44362
+ for (const [_, cred] of Object.entries(credentials)) {
44363
+ if (!cred || typeof cred !== "object" || Array.isArray(cred)) {
44364
+ return false;
44365
+ }
44366
+ const c = cred;
44367
+ if (c.type !== "oauth") {
44368
+ return false;
44369
+ }
44370
+ if (typeof c.accessToken !== "string") {
44371
+ return false;
44372
+ }
44373
+ if (c.refreshToken !== undefined && c.refreshToken !== null && typeof c.refreshToken !== "string") {
44374
+ return false;
44375
+ }
44376
+ if (c.expiresAt !== undefined && c.expiresAt !== null && typeof c.expiresAt !== "number") {
44377
+ return false;
44378
+ }
44379
+ }
44380
+ return true;
44381
+ }
44382
+
44322
44383
  class FileCredentialStore {
44323
44384
  dirPath;
44324
44385
  filePath;
44325
- mutex = Promise.resolve();
44386
+ canonicalPath;
44326
44387
  constructor(options) {
44327
44388
  this.dirPath = options?.storageDir ?? path.join(os2.homedir(), ".steward");
44328
44389
  this.filePath = options?.filePath ?? path.join(this.dirPath, "auth.json");
44390
+ this.canonicalPath = path.resolve(this.filePath);
44329
44391
  }
44330
44392
  async ensureDirectory() {
44331
44393
  try {
44332
44394
  await fs.mkdir(this.dirPath, { recursive: true, mode: 448 });
44333
44395
  await fs.chmod(this.dirPath, 448).catch(() => {});
44334
- } catch {}
44396
+ } catch (err) {
44397
+ const isExist = err?.code === "EEXIST";
44398
+ if (!isExist) {
44399
+ throw new AIError(`Failed to create directory "${this.dirPath}": ${err instanceof Error ? err.message : String(err)}`, {
44400
+ code: "auth",
44401
+ cause: err
44402
+ });
44403
+ }
44404
+ }
44335
44405
  }
44336
44406
  async loadData() {
44337
44407
  try {
44338
44408
  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) {
44409
+ let parsed;
44410
+ try {
44411
+ parsed = JSON.parse(content);
44412
+ } catch (jsonErr) {
44413
+ throw new AIError("Invalid or corrupt auth.json schema.", {
44414
+ code: "auth",
44415
+ cause: jsonErr
44416
+ });
44417
+ }
44418
+ if (!validateAuthStorage(parsed)) {
44341
44419
  throw new AIError("Invalid or corrupt auth.json schema.", { code: "auth" });
44342
44420
  }
44421
+ for (const cred of Object.values(parsed.credentials)) {
44422
+ if (cred) {
44423
+ if (cred.expiresAt === null) {
44424
+ delete cred.expiresAt;
44425
+ }
44426
+ if (cred.refreshToken === null) {
44427
+ delete cred.refreshToken;
44428
+ }
44429
+ }
44430
+ }
44343
44431
  return parsed;
44344
44432
  } catch (err) {
44345
- if (err.code === "ENOENT") {
44433
+ if (err?.code === "ENOENT") {
44346
44434
  return { version: 1, credentials: {} };
44347
44435
  }
44348
44436
  if (err instanceof AIError)
44349
44437
  throw err;
44350
- throw new AIError(`Failed to read auth store: ${err.message}`, {
44438
+ throw new AIError(`Failed to read auth store: ${err instanceof Error ? err.message : String(err)}`, {
44351
44439
  code: "auth",
44352
44440
  cause: err
44353
44441
  });
@@ -44364,7 +44452,7 @@ class FileCredentialStore {
44364
44452
  await fs.chmod(this.filePath, 384).catch(() => {});
44365
44453
  } catch (err) {
44366
44454
  await fs.unlink(tempFile).catch(() => {});
44367
- throw new AIError(`Failed to atomically save auth store: ${err.message}`, {
44455
+ throw new AIError(`Failed to atomically save auth store: ${err instanceof Error ? err.message : String(err)}`, {
44368
44456
  code: "auth",
44369
44457
  cause: err
44370
44458
  });
@@ -44389,24 +44477,21 @@ class FileCredentialStore {
44389
44477
  return list;
44390
44478
  }
44391
44479
  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
- });
44480
+ const currentMutex = fileMutexes.get(this.canonicalPath) ?? Promise.resolve();
44481
+ const nextMutex = currentMutex.then(async () => {
44482
+ const data = await this.loadData();
44483
+ const current = data.credentials[provider];
44484
+ const updated = await fn(current);
44485
+ if (updated) {
44486
+ data.credentials[provider] = updated;
44487
+ } else {
44488
+ delete data.credentials[provider];
44489
+ }
44490
+ await this.saveData(data);
44491
+ return updated;
44409
44492
  });
44493
+ fileMutexes.set(this.canonicalPath, nextMutex.catch(() => {}));
44494
+ return await nextMutex;
44410
44495
  }
44411
44496
  async delete(provider) {
44412
44497
  await this.modify(provider, async () => {
@@ -44465,19 +44550,14 @@ class DefaultAuthManager {
44465
44550
  return await this.resolver.resolve(provider, signal);
44466
44551
  }
44467
44552
  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 {
44553
+ const adapter = OAUTH_ADAPTERS[provider];
44554
+ if (!adapter) {
44476
44555
  throw new AIError(`No browser OAuth login is available for "${provider}".`, {
44477
44556
  code: "oauth",
44478
44557
  provider
44479
44558
  });
44480
44559
  }
44560
+ const credential = await adapter.login(interaction);
44481
44561
  await this.store.modify(provider, async () => credential);
44482
44562
  return credential;
44483
44563
  }
@@ -44606,6 +44686,13 @@ var PROVIDER_REGISTRY = {
44606
44686
  envVar: "CUSTOM_API_URL / CUSTOM_API_KEY"
44607
44687
  }
44608
44688
  };
44689
+ function getProviderDescriptor(provider) {
44690
+ const descriptor = PROVIDER_REGISTRY[provider];
44691
+ if (!descriptor) {
44692
+ throw new Error(`Unsupported provider: "${provider}"`);
44693
+ }
44694
+ return descriptor;
44695
+ }
44609
44696
 
44610
44697
  // src/packages/ai/src/models/selection.ts
44611
44698
  var CANONICAL_DEFAULT_EFFORT = "medium";
@@ -44623,26 +44710,36 @@ var PROVIDER_SELECTION_PRIORITY = [
44623
44710
  "custom"
44624
44711
  ];
44625
44712
  function inferProviderFromModelId(modelId) {
44626
- const lower = modelId.toLowerCase();
44627
- if (/^[a-z0-9-_.]+\/[a-z0-9-_.]+$/.test(lower))
44713
+ const lower = modelId.trim().toLowerCase();
44714
+ if (!lower)
44715
+ return null;
44716
+ if (/^[a-z0-9-_.]+\/[a-z0-9-_.]+$/.test(lower)) {
44628
44717
  return "openrouter";
44629
- if (lower.startsWith("copilot/") || lower.startsWith("github-copilot/"))
44718
+ }
44719
+ if (lower.startsWith("copilot/") || lower.startsWith("github-copilot/")) {
44630
44720
  return "github-copilot";
44631
- if (lower.startsWith("gemini-") || lower.startsWith("gemma-"))
44721
+ }
44722
+ if (lower.startsWith("gemini-") || lower.startsWith("gemma-")) {
44632
44723
  return "gemini";
44633
- if (lower.startsWith("claude-"))
44724
+ }
44725
+ if (lower.startsWith("claude-")) {
44634
44726
  return "anthropic";
44635
- if (lower.startsWith("gpt-") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("chatgpt-")) {
44727
+ }
44728
+ if (lower.startsWith("gpt-") || lower.startsWith("chatgpt-") || /^o[1-9]($|-)/.test(lower)) {
44636
44729
  return "openai";
44637
44730
  }
44638
- if (lower.startsWith("deepseek-"))
44731
+ if (lower.startsWith("deepseek-")) {
44639
44732
  return "deepseek";
44640
- if (lower.startsWith("grok-"))
44733
+ }
44734
+ if (lower.startsWith("grok-")) {
44641
44735
  return "xai";
44642
- if (lower.startsWith("codestral") || lower.startsWith("mistral") || lower.startsWith("pixtral") || lower.startsWith("ministral"))
44736
+ }
44737
+ if (lower.startsWith("codestral") || lower.startsWith("mistral") || lower.startsWith("pixtral") || lower.startsWith("ministral")) {
44643
44738
  return "mistral";
44644
- if (lower.includes(":latest") || lower.startsWith("ollama/"))
44739
+ }
44740
+ if (lower.startsWith("ollama/") || /^[a-z0-9_.-]+:[a-z0-9_.-]+$/.test(lower)) {
44645
44741
  return "ollama";
44742
+ }
44646
44743
  return null;
44647
44744
  }
44648
44745
  async function resolveModelSelection(requested, context) {
@@ -44807,6 +44904,15 @@ function parseSSEEvent(raw) {
44807
44904
  }
44808
44905
 
44809
44906
  // src/packages/ai/src/json.ts
44907
+ function sanitizeSurrogates(text) {
44908
+ if (typeof text !== "string") {
44909
+ if (text === null || text === undefined) {
44910
+ return "";
44911
+ }
44912
+ return sanitizeSurrogates(typeof text === "object" ? JSON.stringify(text) : String(text));
44913
+ }
44914
+ return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
44915
+ }
44810
44916
  function parseStreamingJson(partial) {
44811
44917
  const trimmed = partial.trim();
44812
44918
  if (!trimmed) {
@@ -44876,82 +44982,78 @@ function parseStreamingJson(partial) {
44876
44982
  }
44877
44983
 
44878
44984
  // src/packages/ai/src/providers/openai-compatible.ts
44985
+ var STATIC_PROFILES = {
44986
+ openrouter: {
44987
+ provider: "openrouter",
44988
+ baseUrl: "https://openrouter.ai/api/v1",
44989
+ supportsReasoning: true,
44990
+ reasoningFormat: "openrouter",
44991
+ apiPath: "/chat/completions",
44992
+ requiresApiKey: true
44993
+ },
44994
+ deepseek: {
44995
+ provider: "deepseek",
44996
+ baseUrl: "https://api.deepseek.com",
44997
+ supportsReasoning: true,
44998
+ reasoningFormat: "deepseek",
44999
+ apiPath: "/chat/completions",
45000
+ requiresApiKey: true
45001
+ },
45002
+ "github-copilot": {
45003
+ provider: "github-copilot",
45004
+ baseUrl: "https://api.individual.githubcopilot.com",
45005
+ supportsReasoning: true,
45006
+ reasoningFormat: "openai",
45007
+ apiPath: "/chat/completions",
45008
+ requiresApiKey: true
45009
+ },
45010
+ groq: {
45011
+ provider: "groq",
45012
+ baseUrl: "https://api.groq.com/openai/v1",
45013
+ supportsReasoning: true,
45014
+ reasoningFormat: "deepseek",
45015
+ apiPath: "/chat/completions",
45016
+ requiresApiKey: true
45017
+ },
45018
+ xai: {
45019
+ provider: "xai",
45020
+ baseUrl: "https://api.x.ai/v1",
45021
+ supportsReasoning: true,
45022
+ reasoningFormat: "openai",
45023
+ apiPath: "/chat/completions",
45024
+ requiresApiKey: true
45025
+ },
45026
+ mistral: {
45027
+ provider: "mistral",
45028
+ baseUrl: "https://api.mistral.ai/v1",
45029
+ supportsReasoning: false,
45030
+ reasoningFormat: "none",
45031
+ apiPath: "/chat/completions",
45032
+ requiresApiKey: true
45033
+ }
45034
+ };
44879
45035
  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
- };
45036
+ if (provider in STATIC_PROFILES) {
45037
+ return STATIC_PROFILES[provider];
44954
45038
  }
45039
+ if (provider === "ollama") {
45040
+ return {
45041
+ provider: "ollama",
45042
+ baseUrl: customBaseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434/v1",
45043
+ supportsReasoning: true,
45044
+ reasoningFormat: "deepseek",
45045
+ apiPath: "/chat/completions",
45046
+ requiresApiKey: false
45047
+ };
45048
+ }
45049
+ return {
45050
+ provider: "custom",
45051
+ baseUrl: customBaseUrl || process.env.CUSTOM_API_URL || "http://localhost:11434/v1",
45052
+ supportsReasoning: true,
45053
+ reasoningFormat: "openai",
45054
+ apiPath: "/chat/completions",
45055
+ requiresApiKey: false
45056
+ };
44955
45057
  }
44956
45058
  function mapEffort(effort, format) {
44957
45059
  if (effort === "none")
@@ -44974,13 +45076,13 @@ function convertMessages(messages) {
44974
45076
  const converted = [];
44975
45077
  for (const msg of messages) {
44976
45078
  if (msg.role === "system") {
44977
- converted.push({ role: "system", content: msg.content });
45079
+ converted.push({ role: "system", content: sanitizeSurrogates(msg.content) });
44978
45080
  continue;
44979
45081
  }
44980
45082
  if (msg.role === "user") {
44981
45083
  const text = typeof msg.content === "string" ? msg.content : msg.content.map((c) => c.text).join(`
44982
45084
  `);
44983
- converted.push({ role: "user", content: text });
45085
+ converted.push({ role: "user", content: sanitizeSurrogates(text) });
44984
45086
  continue;
44985
45087
  }
44986
45088
  if (msg.role === "assistant") {
@@ -45005,9 +45107,9 @@ function convertMessages(messages) {
45005
45107
  }
45006
45108
  const item = { role: "assistant" };
45007
45109
  if (text)
45008
- item.content = text;
45110
+ item.content = sanitizeSurrogates(text);
45009
45111
  if (reasoning_content)
45010
- item.reasoning_content = reasoning_content;
45112
+ item.reasoning_content = sanitizeSurrogates(reasoning_content);
45011
45113
  if (tool_calls.length > 0)
45012
45114
  item.tool_calls = tool_calls;
45013
45115
  converted.push(item);
@@ -45015,10 +45117,11 @@ function convertMessages(messages) {
45015
45117
  }
45016
45118
  if (msg.role === "tool") {
45017
45119
  for (const res of msg.content) {
45120
+ const outStr = typeof res.output === "string" ? res.output : JSON.stringify(res.output);
45018
45121
  converted.push({
45019
45122
  role: "tool",
45020
45123
  tool_call_id: res.toolCallId,
45021
- content: typeof res.output === "string" ? res.output : JSON.stringify(res.output)
45124
+ content: sanitizeSurrogates(outStr)
45022
45125
  });
45023
45126
  }
45024
45127
  }
@@ -45074,7 +45177,7 @@ function streamOpenAICompatible(options) {
45074
45177
  });
45075
45178
  } catch (err) {
45076
45179
  const isAbort = request.abortSignal?.aborted;
45077
- const error = new AIError(isAbort ? "Inference request aborted." : `Network request to ${profile.provider} failed: ${err.message}`, {
45180
+ const error = new AIError(isAbort ? "Inference request aborted." : `Network request to ${profile.provider} failed: ${err instanceof Error ? err.message : String(err)}`, {
45078
45181
  code: isAbort ? "aborted" : "network",
45079
45182
  provider: request.model.provider,
45080
45183
  cause: err
@@ -45084,10 +45187,9 @@ function streamOpenAICompatible(options) {
45084
45187
  return;
45085
45188
  }
45086
45189
  if (!response.ok) {
45087
- const errBody = await response.text().catch(() => "");
45088
45190
  const isRateLimit = response.status === 429;
45089
45191
  const isAuth = response.status === 401 || response.status === 403;
45090
- const error = new AIError(`${profile.provider} error (HTTP ${response.status}): ${errBody}`, {
45192
+ const error = new AIError(`${profile.provider} request failed with HTTP status ${response.status}`, {
45091
45193
  code: isAuth ? auth?.type === "oauth" ? "oauth" : "auth" : isRateLimit ? "rate-limit" : "provider",
45092
45194
  provider: request.model.provider,
45093
45195
  status: response.status
@@ -45105,8 +45207,7 @@ function streamOpenAICompatible(options) {
45105
45207
  yield { type: "error", error };
45106
45208
  return;
45107
45209
  }
45108
- let textContent = "";
45109
- let reasoningContent = "";
45210
+ const assistantContent = [];
45110
45211
  const toolCallBuffers = new Map;
45111
45212
  const usage = {
45112
45213
  inputTokens: 0,
@@ -45124,19 +45225,24 @@ function streamOpenAICompatible(options) {
45124
45225
  } catch {
45125
45226
  continue;
45126
45227
  }
45127
- if (eventData.usage) {
45228
+ if (eventData.usage && typeof eventData.usage === "object") {
45128
45229
  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;
45230
+ if (typeof u.prompt_tokens === "number")
45231
+ usage.inputTokens = u.prompt_tokens;
45232
+ if (typeof u.completion_tokens === "number")
45233
+ usage.outputTokens = u.completion_tokens;
45234
+ if (typeof u.total_tokens === "number")
45235
+ usage.totalTokens = u.total_tokens;
45236
+ const details = u.completion_tokens_details;
45237
+ if (typeof details?.reasoning_tokens === "number") {
45238
+ usage.reasoningTokens = details.reasoning_tokens;
45134
45239
  }
45135
45240
  }
45136
- const choice = eventData.choices?.[0];
45241
+ const choices = eventData.choices;
45242
+ const choice = choices?.[0];
45137
45243
  if (!choice)
45138
45244
  continue;
45139
- if (choice.finish_reason) {
45245
+ if (choice.finish_reason && typeof choice.finish_reason === "string") {
45140
45246
  const fr = choice.finish_reason;
45141
45247
  if (fr === "stop")
45142
45248
  finishReason = "stop";
@@ -45148,70 +45254,86 @@ function streamOpenAICompatible(options) {
45148
45254
  const delta = choice.delta;
45149
45255
  if (!delta)
45150
45256
  continue;
45151
- const reasoningDelta = delta.reasoning_content || delta.reasoning || delta.reasoning_text || delta.reasoning_details?.text;
45257
+ const reasoningDetails = delta.reasoning_details;
45258
+ 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
45259
  if (reasoningDelta) {
45153
- reasoningContent += reasoningDelta;
45260
+ const lastBlock = assistantContent[assistantContent.length - 1];
45261
+ if (lastBlock && lastBlock.type === "thinking") {
45262
+ lastBlock.thinking += reasoningDelta;
45263
+ } else {
45264
+ assistantContent.push({
45265
+ type: "thinking",
45266
+ thinking: reasoningDelta
45267
+ });
45268
+ }
45154
45269
  yield { type: "reasoning-delta", delta: reasoningDelta };
45155
45270
  }
45156
- if (delta.content) {
45157
- textContent += delta.content;
45158
- yield { type: "text-delta", delta: delta.content };
45271
+ if (typeof delta.content === "string" && delta.content) {
45272
+ const textDelta = delta.content;
45273
+ const lastBlock = assistantContent[assistantContent.length - 1];
45274
+ if (lastBlock && lastBlock.type === "text") {
45275
+ lastBlock.text += textDelta;
45276
+ } else {
45277
+ assistantContent.push({
45278
+ type: "text",
45279
+ text: textDelta
45280
+ });
45281
+ }
45282
+ yield { type: "text-delta", delta: textDelta };
45159
45283
  }
45160
- if (delta.tool_calls && Array.isArray(delta.tool_calls)) {
45284
+ if (Array.isArray(delta.tool_calls)) {
45161
45285
  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 || "",
45286
+ const idx = typeof tc.index === "number" ? tc.index : 0;
45287
+ const fn = tc.function;
45288
+ const tcId = typeof tc.id === "string" ? tc.id : undefined;
45289
+ const fnName = typeof fn?.name === "string" ? fn.name : undefined;
45290
+ const fnArgs = typeof fn?.arguments === "string" ? fn.arguments : undefined;
45291
+ let entry = toolCallBuffers.get(idx);
45292
+ if (!entry) {
45293
+ const buffer = {
45294
+ id: tcId || `call_${idx}`,
45295
+ name: fnName || "",
45168
45296
  rawArgs: ""
45169
45297
  };
45170
- toolCallBuffers.set(idx, buffer);
45298
+ const slot = {
45299
+ type: "tool-call",
45300
+ id: buffer.id,
45301
+ name: buffer.name,
45302
+ arguments: {}
45303
+ };
45304
+ assistantContent.push(slot);
45305
+ entry = { buffer, slot };
45306
+ toolCallBuffers.set(idx, entry);
45171
45307
  if (buffer.name) {
45172
45308
  yield { type: "tool-call-start", id: buffer.id, name: buffer.name };
45173
45309
  }
45174
45310
  } 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 };
45311
+ if (tcId && entry.buffer.id !== tcId) {
45312
+ entry.buffer.id = tcId;
45313
+ entry.slot.id = tcId;
45314
+ }
45315
+ if (!entry.buffer.name && fnName) {
45316
+ entry.buffer.name = fnName;
45317
+ entry.slot.name = fnName;
45318
+ yield { type: "tool-call-start", id: entry.buffer.id, name: entry.buffer.name };
45178
45319
  }
45179
45320
  }
45180
- if (tc.function?.arguments) {
45181
- buffer.rawArgs += tc.function.arguments;
45321
+ if (fnArgs) {
45322
+ entry.buffer.rawArgs += fnArgs;
45182
45323
  yield {
45183
45324
  type: "tool-call-delta",
45184
- id: buffer.id,
45185
- delta: tc.function.arguments
45325
+ id: entry.buffer.id,
45326
+ delta: fnArgs
45186
45327
  };
45187
45328
  }
45188
45329
  }
45189
45330
  }
45190
45331
  }
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);
45332
+ for (const entry of toolCallBuffers.values()) {
45333
+ const parsedArgs = parseStreamingJson(entry.buffer.rawArgs);
45334
+ entry.slot.arguments = parsedArgs;
45213
45335
  finishReason = "tool-use";
45214
- yield { type: "tool-call-end", toolCall };
45336
+ yield { type: "tool-call-end", toolCall: entry.slot };
45215
45337
  }
45216
45338
  usage.totalTokens = usage.totalTokens || usage.inputTokens + usage.outputTokens;
45217
45339
  const finalMessage = {
@@ -45231,7 +45353,7 @@ function streamOpenAICompatible(options) {
45231
45353
  };
45232
45354
  } catch (err) {
45233
45355
  const isAbort = request.abortSignal?.aborted;
45234
- const error = new AIError(isAbort ? "Inference request aborted." : `${profile.provider} stream reading failed: ${err.message}`, {
45356
+ const error = new AIError(isAbort ? "Inference request aborted." : `${profile.provider} stream reading failed: ${err instanceof Error ? err.message : String(err)}`, {
45235
45357
  code: isAbort ? "aborted" : "provider",
45236
45358
  provider: request.model.provider,
45237
45359
  cause: err
@@ -45265,7 +45387,7 @@ function streamOpenAI(options) {
45265
45387
  request,
45266
45388
  auth,
45267
45389
  profile: {
45268
- provider: "custom",
45390
+ provider: "openai",
45269
45391
  baseUrl: "https://api.openai.com/v1",
45270
45392
  supportsReasoning: true,
45271
45393
  reasoningFormat: "openai",
@@ -45302,11 +45424,11 @@ ${msg.content}` : msg.content;
45302
45424
  }
45303
45425
  if (msg.role === "user") {
45304
45426
  if (typeof msg.content === "string") {
45305
- anthropicMessages.push({ role: "user", content: msg.content });
45427
+ anthropicMessages.push({ role: "user", content: sanitizeSurrogates(msg.content) });
45306
45428
  } else {
45307
45429
  const text = msg.content.map((c) => c.text).join(`
45308
45430
  `);
45309
- anthropicMessages.push({ role: "user", content: text });
45431
+ anthropicMessages.push({ role: "user", content: sanitizeSurrogates(text) });
45310
45432
  }
45311
45433
  continue;
45312
45434
  }
@@ -45314,11 +45436,11 @@ ${msg.content}` : msg.content;
45314
45436
  const content = [];
45315
45437
  for (const block of msg.content) {
45316
45438
  if (block.type === "text") {
45317
- content.push({ type: "text", text: block.text });
45439
+ content.push({ type: "text", text: sanitizeSurrogates(block.text) });
45318
45440
  } else if (block.type === "thinking") {
45319
45441
  content.push({
45320
45442
  type: "thinking",
45321
- thinking: block.thinking,
45443
+ thinking: sanitizeSurrogates(block.thinking),
45322
45444
  signature: block.thinkingSignature
45323
45445
  });
45324
45446
  } else if (block.type === "tool-call") {
@@ -45336,10 +45458,11 @@ ${msg.content}` : msg.content;
45336
45458
  if (msg.role === "tool") {
45337
45459
  const content = [];
45338
45460
  for (const res of msg.content) {
45461
+ const outStr = typeof res.output === "string" ? res.output : JSON.stringify(res.output);
45339
45462
  content.push({
45340
45463
  type: "tool_result",
45341
45464
  tool_use_id: res.toolCallId,
45342
- content: typeof res.output === "string" ? res.output : JSON.stringify(res.output),
45465
+ content: sanitizeSurrogates(outStr),
45343
45466
  is_error: res.isError
45344
45467
  });
45345
45468
  }
@@ -45352,10 +45475,11 @@ function streamAnthropic(options) {
45352
45475
  const { request, auth } = options;
45353
45476
  const thinkingConfig = mapAnthropicEffort(request.model.effort);
45354
45477
  const { system, anthropicMessages } = convertMessages2(request.messages);
45355
- const tools = request.tools?.map((t) => ({
45478
+ const tools = request.tools?.map((t, idx, arr) => ({
45356
45479
  name: t.name,
45357
45480
  description: t.description,
45358
- input_schema: t.inputSchema
45481
+ input_schema: t.inputSchema,
45482
+ ...idx === arr.length - 1 ? { cache_control: { type: "ephemeral" } } : {}
45359
45483
  }));
45360
45484
  const body = {
45361
45485
  model: request.model.modelId,
@@ -45364,7 +45488,13 @@ function streamAnthropic(options) {
45364
45488
  stream: true
45365
45489
  };
45366
45490
  if (system) {
45367
- body.system = system;
45491
+ body.system = [
45492
+ {
45493
+ type: "text",
45494
+ text: sanitizeSurrogates(system),
45495
+ cache_control: { type: "ephemeral" }
45496
+ }
45497
+ ];
45368
45498
  }
45369
45499
  if (tools && tools.length > 0) {
45370
45500
  body.tools = tools;
@@ -45379,7 +45509,7 @@ function streamAnthropic(options) {
45379
45509
  "Content-Type": "application/json",
45380
45510
  Accept: "text/event-stream",
45381
45511
  "anthropic-version": "2023-06-01",
45382
- ...auth.headers
45512
+ ...auth.headers || {}
45383
45513
  };
45384
45514
  if (auth.type === "api-key") {
45385
45515
  headers["x-api-key"] = auth.token;
@@ -45399,7 +45529,7 @@ function streamAnthropic(options) {
45399
45529
  });
45400
45530
  } catch (err) {
45401
45531
  const isAbort = request.abortSignal?.aborted;
45402
- const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Anthropic failed: ${err.message}`, {
45532
+ const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Anthropic failed: ${err instanceof Error ? err.message : String(err)}`, {
45403
45533
  code: isAbort ? "aborted" : "network",
45404
45534
  provider: "anthropic",
45405
45535
  cause: err
@@ -45409,10 +45539,9 @@ function streamAnthropic(options) {
45409
45539
  return;
45410
45540
  }
45411
45541
  if (!response.ok) {
45412
- const errBody = await response.text().catch(() => "");
45413
45542
  const isRateLimit = response.status === 429;
45414
45543
  const isAuth = response.status === 401 || response.status === 403;
45415
- const error = new AIError(`Anthropic error (HTTP ${response.status}): ${errBody}`, {
45544
+ const error = new AIError(`Anthropic request failed with HTTP status ${response.status}`, {
45416
45545
  code: isAuth ? auth.type === "oauth" ? "oauth" : "auth" : isRateLimit ? "rate-limit" : "provider",
45417
45546
  provider: "anthropic",
45418
45547
  status: response.status
@@ -45449,69 +45578,91 @@ function streamAnthropic(options) {
45449
45578
  } catch {
45450
45579
  continue;
45451
45580
  }
45452
- const eventType = sse.event || eventData.type;
45581
+ const eventType = typeof sse.event === "string" && sse.event || typeof eventData.type === "string" && eventData.type || "";
45453
45582
  switch (eventType) {
45454
45583
  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;
45584
+ const msg = eventData.message;
45585
+ if (msg?.usage && typeof msg.usage === "object") {
45586
+ const u = msg.usage;
45587
+ if (typeof u.input_tokens === "number")
45588
+ usage.inputTokens = u.input_tokens;
45589
+ if (typeof u.cache_read_input_tokens === "number")
45590
+ usage.cacheReadTokens = u.cache_read_input_tokens;
45591
+ if (typeof u.cache_creation_input_tokens === "number")
45592
+ usage.cacheWriteTokens = u.cache_creation_input_tokens;
45460
45593
  }
45461
45594
  break;
45462
45595
  }
45463
45596
  case "content_block_start": {
45464
45597
  const block = eventData.content_block;
45598
+ if (!block || typeof block !== "object" || typeof block.type !== "string") {
45599
+ const error = new AIError("Malformed Anthropic content_block_start event", {
45600
+ code: "parse",
45601
+ provider: "anthropic"
45602
+ });
45603
+ errorResult = error;
45604
+ yield { type: "error", error };
45605
+ return;
45606
+ }
45465
45607
  if (block.type === "text") {
45466
- currentBlock = { type: "text", text: block.text || "" };
45467
- if (block.text) {
45468
- yield { type: "text-delta", delta: block.text };
45608
+ const blockText = typeof block.text === "string" ? block.text : "";
45609
+ currentBlock = { type: "text", text: blockText };
45610
+ if (blockText) {
45611
+ yield { type: "text-delta", delta: blockText };
45469
45612
  }
45470
45613
  } else if (block.type === "thinking") {
45614
+ const blockThinking = typeof block.thinking === "string" ? block.thinking : "";
45615
+ const blockSig = typeof block.signature === "string" ? block.signature : undefined;
45471
45616
  currentBlock = {
45472
45617
  type: "thinking",
45473
- thinking: block.thinking || "",
45474
- signature: block.signature
45618
+ thinking: blockThinking,
45619
+ signature: blockSig
45475
45620
  };
45476
- if (block.thinking) {
45477
- yield { type: "reasoning-delta", delta: block.thinking };
45621
+ if (blockThinking) {
45622
+ yield { type: "reasoning-delta", delta: blockThinking };
45478
45623
  }
45479
45624
  } else if (block.type === "tool_use") {
45625
+ const blockId = typeof block.id === "string" ? block.id : "";
45626
+ const blockName = typeof block.name === "string" ? block.name : "";
45480
45627
  currentBlock = {
45481
45628
  type: "tool-call",
45482
- id: block.id,
45483
- name: block.name,
45629
+ id: blockId,
45630
+ name: blockName,
45484
45631
  rawArgs: ""
45485
45632
  };
45486
- yield { type: "tool-call-start", id: block.id, name: block.name };
45633
+ yield { type: "tool-call-start", id: blockId, name: blockName };
45487
45634
  }
45488
45635
  break;
45489
45636
  }
45490
45637
  case "content_block_delta": {
45491
45638
  const delta = eventData.delta;
45492
- if (!delta)
45639
+ if (!delta || typeof delta.type !== "string")
45493
45640
  break;
45494
45641
  if (delta.type === "text_delta") {
45642
+ const deltaText = typeof delta.text === "string" ? delta.text : "";
45495
45643
  if (currentBlock && currentBlock.type === "text") {
45496
- currentBlock.text += delta.text;
45644
+ currentBlock.text += deltaText;
45497
45645
  }
45498
- yield { type: "text-delta", delta: delta.text };
45646
+ yield { type: "text-delta", delta: deltaText };
45499
45647
  } else if (delta.type === "thinking_delta") {
45648
+ const deltaThinking = typeof delta.thinking === "string" ? delta.thinking : "";
45500
45649
  if (currentBlock && currentBlock.type === "thinking") {
45501
- currentBlock.thinking += delta.thinking;
45650
+ currentBlock.thinking += deltaThinking;
45502
45651
  }
45503
- yield { type: "reasoning-delta", delta: delta.thinking };
45652
+ yield { type: "reasoning-delta", delta: deltaThinking };
45504
45653
  } else if (delta.type === "signature_delta") {
45654
+ const deltaSig = typeof delta.signature === "string" ? delta.signature : "";
45505
45655
  if (currentBlock && currentBlock.type === "thinking") {
45506
- currentBlock.signature = (currentBlock.signature || "") + delta.signature;
45656
+ currentBlock.signature = (currentBlock.signature || "") + deltaSig;
45507
45657
  }
45508
45658
  } else if (delta.type === "input_json_delta") {
45659
+ const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : "";
45509
45660
  if (currentBlock && currentBlock.type === "tool-call") {
45510
- currentBlock.rawArgs += delta.partial_json;
45661
+ currentBlock.rawArgs += partialJson;
45511
45662
  yield {
45512
45663
  type: "tool-call-delta",
45513
45664
  id: currentBlock.id,
45514
- delta: delta.partial_json
45665
+ delta: partialJson
45515
45666
  };
45516
45667
  }
45517
45668
  }
@@ -45546,8 +45697,9 @@ function streamAnthropic(options) {
45546
45697
  break;
45547
45698
  }
45548
45699
  case "message_delta": {
45549
- if (eventData.delta?.stop_reason) {
45550
- const sr = eventData.delta.stop_reason;
45700
+ const delta = eventData.delta;
45701
+ if (delta && typeof delta.stop_reason === "string") {
45702
+ const sr = delta.stop_reason;
45551
45703
  if (sr === "end_turn" || sr === "stop_sequence")
45552
45704
  finishReason = "stop";
45553
45705
  else if (sr === "max_tokens")
@@ -45555,8 +45707,9 @@ function streamAnthropic(options) {
45555
45707
  else if (sr === "tool_use")
45556
45708
  finishReason = "tool-use";
45557
45709
  }
45558
- if (eventData.usage?.output_tokens) {
45559
- usage.outputTokens = eventData.usage.output_tokens;
45710
+ const u = eventData.usage;
45711
+ if (typeof u?.output_tokens === "number") {
45712
+ usage.outputTokens = u.output_tokens;
45560
45713
  }
45561
45714
  break;
45562
45715
  }
@@ -45566,7 +45719,8 @@ function streamAnthropic(options) {
45566
45719
  }
45567
45720
  case "error": {
45568
45721
  const err = eventData.error;
45569
- const error = new AIError(`Anthropic stream error: ${err?.message || JSON.stringify(err)}`, {
45722
+ const errMsg = typeof err?.message === "string" ? err.message : "Unknown provider error";
45723
+ const error = new AIError(`Anthropic stream error: ${errMsg}`, {
45570
45724
  code: "provider",
45571
45725
  provider: "anthropic"
45572
45726
  });
@@ -45603,7 +45757,7 @@ function streamAnthropic(options) {
45603
45757
  };
45604
45758
  } catch (err) {
45605
45759
  const isAbort = request.abortSignal?.aborted;
45606
- const error = new AIError(isAbort ? "Inference request aborted." : `Stream reading failed: ${err.message}`, {
45760
+ const error = new AIError(isAbort ? "Inference request aborted." : `Stream reading failed: ${err instanceof Error ? err.message : String(err)}`, {
45607
45761
  code: isAbort ? "aborted" : "provider",
45608
45762
  provider: "anthropic",
45609
45763
  cause: err
@@ -45661,7 +45815,7 @@ function sanitizeSchema(schema) {
45661
45815
  function mapGeminiEffort(effort) {
45662
45816
  switch (effort) {
45663
45817
  case "none":
45664
- return { thinkingBudget: 0 };
45818
+ return;
45665
45819
  case "low":
45666
45820
  return { thinkingBudget: 2048 };
45667
45821
  case "medium":
@@ -45677,13 +45831,14 @@ function convertMessages3(messages) {
45677
45831
  const contents = [];
45678
45832
  for (const msg of messages) {
45679
45833
  if (msg.role === "system") {
45834
+ const sanitized = sanitizeSurrogates(msg.content);
45680
45835
  systemText = systemText ? `${systemText}
45681
45836
 
45682
- ${msg.content}` : msg.content;
45837
+ ${sanitized}` : sanitized;
45683
45838
  continue;
45684
45839
  }
45685
45840
  if (msg.role === "user") {
45686
- const text = typeof msg.content === "string" ? msg.content : msg.content.map((c) => c.text).join(`
45841
+ const text = typeof msg.content === "string" ? sanitizeSurrogates(msg.content) : msg.content.map((c) => sanitizeSurrogates(c.text)).join(`
45687
45842
  `);
45688
45843
  contents.push({
45689
45844
  role: "user",
@@ -45695,19 +45850,19 @@ ${msg.content}` : msg.content;
45695
45850
  const parts = [];
45696
45851
  for (const block of msg.content) {
45697
45852
  if (block.type === "text") {
45698
- if (!block.text && !block.thoughtSignature)
45853
+ if (!block.text && !block.textSignature)
45699
45854
  continue;
45700
45855
  parts.push({
45701
- text: block.text || "",
45702
- ...block.thoughtSignature ? { thoughtSignature: block.thoughtSignature } : {}
45856
+ text: sanitizeSurrogates(block.text || ""),
45857
+ ...block.textSignature ? { thoughtSignature: block.textSignature } : {}
45703
45858
  });
45704
45859
  } else if (block.type === "thinking") {
45705
- if (!block.thinking && !block.thoughtSignature)
45860
+ if (!block.thinking && !block.thinkingSignature)
45706
45861
  continue;
45707
45862
  parts.push({
45708
45863
  thought: true,
45709
- text: block.thinking || "",
45710
- ...block.thoughtSignature ? { thoughtSignature: block.thoughtSignature } : {}
45864
+ text: sanitizeSurrogates(block.thinking || ""),
45865
+ ...block.thinkingSignature ? { thoughtSignature: block.thinkingSignature } : {}
45711
45866
  });
45712
45867
  } else if (block.type === "tool-call") {
45713
45868
  parts.push({
@@ -45730,11 +45885,12 @@ ${msg.content}` : msg.content;
45730
45885
  if (msg.role === "tool") {
45731
45886
  const parts = [];
45732
45887
  for (const res of msg.content) {
45888
+ const outStr = typeof res.output === "string" ? res.output : JSON.stringify(res.output);
45733
45889
  parts.push({
45734
45890
  functionResponse: {
45735
45891
  name: res.toolName,
45736
45892
  response: {
45737
- result: res.output
45893
+ result: sanitizeSurrogates(outStr)
45738
45894
  }
45739
45895
  }
45740
45896
  });
@@ -45783,7 +45939,15 @@ function streamGemini(options) {
45783
45939
  body.tools = tools;
45784
45940
  }
45785
45941
  const modelId = request.model.modelId.replace(/^models\//, "");
45786
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:streamGenerateContent?alt=sse&key=${auth.token}`;
45942
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:streamGenerateContent?alt=sse`;
45943
+ const headers = {
45944
+ "Content-Type": "application/json",
45945
+ Accept: "text/event-stream",
45946
+ ...auth.headers || {}
45947
+ };
45948
+ if (auth.token && auth.token !== "none") {
45949
+ headers["x-goog-api-key"] = auth.token;
45950
+ }
45787
45951
  let finalResult;
45788
45952
  let errorResult;
45789
45953
  async function* eventGenerator() {
@@ -45791,16 +45955,13 @@ function streamGemini(options) {
45791
45955
  try {
45792
45956
  response = await fetch(url, {
45793
45957
  method: "POST",
45794
- headers: {
45795
- "Content-Type": "application/json",
45796
- Accept: "text/event-stream"
45797
- },
45958
+ headers,
45798
45959
  body: JSON.stringify(body),
45799
45960
  signal: request.abortSignal
45800
45961
  });
45801
45962
  } catch (err) {
45802
45963
  const isAbort = request.abortSignal?.aborted;
45803
- const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Gemini failed: ${err.message}`, {
45964
+ const error = new AIError(isAbort ? "Inference request aborted." : `Network request to Gemini failed: ${err instanceof Error ? err.message : String(err)}`, {
45804
45965
  code: isAbort ? "aborted" : "network",
45805
45966
  provider: "gemini",
45806
45967
  cause: err
@@ -45810,10 +45971,9 @@ function streamGemini(options) {
45810
45971
  return;
45811
45972
  }
45812
45973
  if (!response.ok) {
45813
- const errBody = await response.text().catch(() => "");
45814
45974
  const isRateLimit = response.status === 429;
45815
45975
  const isAuth = response.status === 401 || response.status === 403;
45816
- const error = new AIError(`Gemini error (HTTP ${response.status}): ${errBody}`, {
45976
+ const error = new AIError(`Gemini request failed with HTTP status ${response.status}`, {
45817
45977
  code: isAuth ? "auth" : isRateLimit ? "rate-limit" : "provider",
45818
45978
  provider: "gemini",
45819
45979
  status: response.status
@@ -45849,15 +46009,19 @@ function streamGemini(options) {
45849
46009
  } catch {
45850
46010
  continue;
45851
46011
  }
45852
- if (eventData.usageMetadata) {
46012
+ if (eventData.usageMetadata && typeof eventData.usageMetadata === "object") {
45853
46013
  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];
46014
+ if (typeof u.promptTokenCount === "number")
46015
+ usage.inputTokens = u.promptTokenCount;
46016
+ if (typeof u.candidatesTokenCount === "number")
46017
+ usage.outputTokens = u.candidatesTokenCount;
46018
+ if (typeof u.totalTokenCount === "number")
46019
+ usage.totalTokens = u.totalTokenCount;
46020
+ }
46021
+ const candidates = eventData.candidates;
46022
+ const candidate = candidates?.[0];
45859
46023
  if (candidate) {
45860
- if (candidate.finishReason) {
46024
+ if (typeof candidate.finishReason === "string") {
45861
46025
  const fr = candidate.finishReason;
45862
46026
  if (fr === "STOP")
45863
46027
  finishReason = "stop";
@@ -45866,21 +46030,25 @@ function streamGemini(options) {
45866
46030
  else if (fr === "SAFETY")
45867
46031
  finishReason = "error";
45868
46032
  }
45869
- if (candidate.content?.parts) {
45870
- for (const part of candidate.content.parts) {
46033
+ const content = candidate.content;
46034
+ if (Array.isArray(content?.parts)) {
46035
+ for (const rawPart of content.parts) {
46036
+ const part = rawPart;
46037
+ const thoughtSig = typeof part.thoughtSignature === "string" ? part.thoughtSignature : undefined;
45871
46038
  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;
46039
+ const thoughtDelta = typeof part.text === "string" ? part.text : "";
46040
+ if (thoughtDelta || thoughtSig) {
46041
+ const lastBlock = assistantContent[assistantContent.length - 1];
46042
+ if (lastBlock && lastBlock.type === "thinking") {
46043
+ lastBlock.thinking += thoughtDelta;
46044
+ if (thoughtSig) {
46045
+ lastBlock.thinkingSignature = thoughtSig;
46046
+ }
45879
46047
  } else {
45880
46048
  assistantContent.push({
45881
46049
  type: "thinking",
45882
46050
  thinking: thoughtDelta,
45883
- thoughtSignature: part.thoughtSignature
46051
+ ...thoughtSig ? { thinkingSignature: thoughtSig } : {}
45884
46052
  });
45885
46053
  }
45886
46054
  if (thoughtDelta) {
@@ -45888,33 +46056,37 @@ function streamGemini(options) {
45888
46056
  }
45889
46057
  }
45890
46058
  } 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;
46059
+ const textDelta = typeof part.text === "string" ? part.text : "";
46060
+ if (textDelta || thoughtSig) {
46061
+ const lastBlock = assistantContent[assistantContent.length - 1];
46062
+ if (lastBlock && lastBlock.type === "text") {
46063
+ lastBlock.text += textDelta;
46064
+ if (thoughtSig) {
46065
+ lastBlock.textSignature = thoughtSig;
46066
+ }
45898
46067
  } else {
45899
46068
  assistantContent.push({
45900
46069
  type: "text",
45901
46070
  text: textDelta,
45902
- thoughtSignature: part.thoughtSignature
46071
+ ...thoughtSig ? { textSignature: thoughtSig } : {}
45903
46072
  });
45904
46073
  }
45905
46074
  if (textDelta) {
45906
46075
  yield { type: "text-delta", delta: textDelta };
45907
46076
  }
45908
46077
  }
45909
- } else if (part.functionCall) {
46078
+ } else if (part.functionCall && typeof part.functionCall === "object") {
46079
+ const fc = part.functionCall;
45910
46080
  toolCallIdx++;
45911
- const toolId = `call_${Date.now()}_${toolCallIdx}`;
46081
+ const toolId = `call_${toolCallIdx}`;
46082
+ const fnName = typeof fc.name === "string" ? fc.name : "";
46083
+ const fnArgs = typeof fc.args === "object" && fc.args !== null ? fc.args : {};
45912
46084
  const toolCall = {
45913
46085
  type: "tool-call",
45914
46086
  id: toolId,
45915
- name: part.functionCall.name,
45916
- arguments: part.functionCall.args || {},
45917
- thoughtSignature: part.thoughtSignature
46087
+ name: fnName,
46088
+ arguments: fnArgs,
46089
+ ...thoughtSig ? { thoughtSignature: thoughtSig } : {}
45918
46090
  };
45919
46091
  assistantContent.push(toolCall);
45920
46092
  finishReason = "tool-use";
@@ -45947,7 +46119,7 @@ function streamGemini(options) {
45947
46119
  };
45948
46120
  } catch (err) {
45949
46121
  const isAbort = request.abortSignal?.aborted;
45950
- const error = new AIError(isAbort ? "Inference request aborted." : `Gemini stream reading failed: ${err.message}`, {
46122
+ const error = new AIError(isAbort ? "Inference request aborted." : `Gemini stream reading failed: ${err instanceof Error ? err.message : String(err)}`, {
45951
46123
  code: isAbort ? "aborted" : "provider",
45952
46124
  provider: "gemini",
45953
46125
  cause: err
@@ -45977,8 +46149,16 @@ function streamGemini(options) {
45977
46149
  // src/packages/ai/src/inference.ts
45978
46150
  function streamInference(request, authManager = createAuthManager(), customBaseUrl) {
45979
46151
  const provider = request.model.provider;
45980
- let streamPromise = null;
45981
46152
  const getStream = async () => {
46153
+ let descriptor;
46154
+ try {
46155
+ descriptor = getProviderDescriptor(provider);
46156
+ } catch {
46157
+ throw new AIError(`Unsupported provider: "${provider}"`, {
46158
+ code: "invalid-request",
46159
+ provider
46160
+ });
46161
+ }
45982
46162
  const resolvedAuth = await authManager.resolve(provider, request.abortSignal);
45983
46163
  if (!resolvedAuth && provider !== "custom" && provider !== "ollama") {
45984
46164
  throw new AIError(`No credentials configured for provider: ${provider}`, {
@@ -45991,30 +46171,28 @@ function streamInference(request, authManager = createAuthManager(), customBaseU
45991
46171
  token: "none",
45992
46172
  source: "custom"
45993
46173
  };
45994
- switch (provider) {
45995
- case "anthropic":
46174
+ switch (descriptor.protocol) {
46175
+ case "anthropic-messages":
45996
46176
  return streamAnthropic({ request, auth });
45997
46177
  case "gemini":
45998
46178
  return streamGemini({ request, auth });
45999
- case "openai":
46179
+ case "openai-chat":
46000
46180
  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":
46181
+ case "openai-compatible":
46009
46182
  return streamOpenAICompatible({ request, auth, customBaseUrl });
46183
+ case "openai-responses":
46184
+ throw new AIError(`Protocol "openai-responses" is not yet implemented for provider "${provider}"`, {
46185
+ code: "invalid-request",
46186
+ provider
46187
+ });
46010
46188
  default:
46011
- throw new AIError(`Unsupported provider: "${provider}"`, {
46189
+ throw new AIError(`Unsupported protocol "${descriptor.protocol}" for provider "${provider}"`, {
46012
46190
  code: "invalid-request",
46013
46191
  provider
46014
46192
  });
46015
46193
  }
46016
46194
  };
46017
- streamPromise = getStream();
46195
+ const streamPromise = getStream();
46018
46196
  return {
46019
46197
  async* [Symbol.asyncIterator]() {
46020
46198
  const activeStream = await streamPromise;
@@ -46058,85 +46236,91 @@ function createAIEngine(options) {
46058
46236
  // src/packages/ai/src/models/discovery.ts
46059
46237
  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
46238
  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", {
46239
+ async function fetchStandardModelsList(options) {
46240
+ const { provider, url, headers, timeoutMs, transformId, filterItem } = options;
46241
+ const response = await fetch(url, {
46063
46242
  method: "GET",
46064
- headers: {
46065
- Authorization: `Bearer ${apiKey}`
46066
- },
46243
+ headers,
46067
46244
  signal: AbortSignal.timeout(timeoutMs)
46068
46245
  });
46069
46246
  if (!response.ok) {
46070
- throw new Error(`OpenAI API request failed: HTTP ${response.status} (${response.statusText})`);
46247
+ throw new Error(`${provider} API request failed: HTTP ${response.status} (${response.statusText})`);
46071
46248
  }
46072
46249
  const payload = await response.json();
46073
46250
  if (!Array.isArray(payload.data)) {
46074
46251
  return [];
46075
46252
  }
46076
- return payload.data.filter((item) => typeof item.id === "string" && !NON_CHAT_MODEL_KEYWORDS.test(item.id)).map((item) => ({
46253
+ const models = [];
46254
+ for (const item of payload.data) {
46255
+ if (!item || typeof item.id !== "string")
46256
+ continue;
46257
+ if (filterItem && !filterItem(item))
46258
+ continue;
46259
+ const finalId = transformId ? transformId(item.id) : item.id;
46260
+ if (!finalId || NON_CHAT_MODEL_KEYWORDS.test(finalId))
46261
+ continue;
46262
+ models.push({
46263
+ provider,
46264
+ modelId: finalId
46265
+ });
46266
+ }
46267
+ return models.sort((a, b) => a.modelId.localeCompare(b.modelId));
46268
+ }
46269
+ async function fetchOpenAIModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46270
+ return await fetchStandardModelsList({
46077
46271
  provider: "openai",
46078
- modelId: item.id
46079
- })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46272
+ url: "https://api.openai.com/v1/models",
46273
+ headers: { Authorization: `Bearer ${apiKey}` },
46274
+ timeoutMs
46275
+ });
46080
46276
  }
46081
46277
  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) => ({
46278
+ return await fetchStandardModelsList({
46097
46279
  provider: "gemini",
46098
- modelId: id
46099
- })).sort((a, b) => a.modelId.localeCompare(b.modelId));
46280
+ url: "https://generativelanguage.googleapis.com/v1beta/openai/models",
46281
+ headers: { Authorization: `Bearer ${apiKey}` },
46282
+ timeoutMs,
46283
+ transformId: (id) => id.replace(/^models\//, "")
46284
+ });
46100
46285
  }
46101
46286
  async function fetchAnthropicModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46102
- const response = await fetch("https://api.anthropic.com/v1/models", {
46103
- method: "GET",
46287
+ return await fetchStandardModelsList({
46288
+ provider: "anthropic",
46289
+ url: "https://api.anthropic.com/v1/models",
46104
46290
  headers: {
46105
46291
  "x-api-key": apiKey,
46106
46292
  "anthropic-version": "2023-06-01"
46107
46293
  },
46108
- signal: AbortSignal.timeout(timeoutMs)
46294
+ timeoutMs
46109
46295
  });
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
46296
  }
46122
46297
  async function fetchDeepSeekModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46123
- const response = await fetch("https://api.deepseek.com/models", {
46124
- method: "GET",
46298
+ return await fetchStandardModelsList({
46299
+ provider: "deepseek",
46300
+ url: "https://api.deepseek.com/models",
46125
46301
  headers: { Authorization: `Bearer ${apiKey}` },
46126
- signal: AbortSignal.timeout(timeoutMs)
46302
+ timeoutMs
46127
46303
  });
46128
- if (!response.ok) {
46129
- throw new Error(`DeepSeek API request failed: HTTP ${response.status} (${response.statusText})`);
46304
+ }
46305
+ function isValidOpenRouterNextUrl(rawUrl) {
46306
+ try {
46307
+ const parsed = new URL(rawUrl, "https://openrouter.ai");
46308
+ return parsed.protocol === "https:" && parsed.origin === "https://openrouter.ai" && parsed.pathname.startsWith("/api/v1/models");
46309
+ } catch {
46310
+ return false;
46130
46311
  }
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
46312
  }
46136
46313
  async function fetchOpenRouterModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46137
46314
  const results = [];
46138
46315
  let url = "https://openrouter.ai/api/v1/models";
46139
- while (url) {
46316
+ const seenUrls = new Set;
46317
+ const MAX_PAGES = 20;
46318
+ let pageCount = 0;
46319
+ while (url && pageCount < MAX_PAGES) {
46320
+ if (seenUrls.has(url))
46321
+ break;
46322
+ seenUrls.add(url);
46323
+ pageCount++;
46140
46324
  const response = await fetch(url, {
46141
46325
  method: "GET",
46142
46326
  headers: { Authorization: `Bearer ${apiKey}` },
@@ -46148,7 +46332,7 @@ async function fetchOpenRouterModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_M
46148
46332
  const payload = await response.json();
46149
46333
  if (Array.isArray(payload.data)) {
46150
46334
  for (const item of payload.data) {
46151
- if (typeof item.id !== "string")
46335
+ if (!item || typeof item.id !== "string")
46152
46336
  continue;
46153
46337
  const hasTextModality = item.architecture?.output_modalities?.includes("text");
46154
46338
  if (hasTextModality === false)
@@ -46158,70 +46342,51 @@ async function fetchOpenRouterModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_M
46158
46342
  }
46159
46343
  }
46160
46344
  }
46161
- const next = payload.links?.next ?? null;
46162
- url = next ? new URL(next, "https://openrouter.ai").toString() : null;
46345
+ const next = payload.links?.next;
46346
+ if (typeof next === "string" && next.trim() && isValidOpenRouterNextUrl(next)) {
46347
+ url = new URL(next, "https://openrouter.ai").toString();
46348
+ } else {
46349
+ url = null;
46350
+ }
46163
46351
  }
46164
46352
  return results.sort((a, b) => a.modelId.localeCompare(b.modelId));
46165
46353
  }
46166
46354
  async function fetchGroqModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46167
- const response = await fetch("https://api.groq.com/openai/v1/models", {
46168
- method: "GET",
46355
+ return await fetchStandardModelsList({
46356
+ provider: "groq",
46357
+ url: "https://api.groq.com/openai/v1/models",
46169
46358
  headers: { Authorization: `Bearer ${apiKey}` },
46170
- signal: AbortSignal.timeout(timeoutMs)
46359
+ timeoutMs
46171
46360
  });
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
46361
  }
46180
46362
  async function fetchXAIModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46181
- const response = await fetch("https://api.x.ai/v1/models", {
46182
- method: "GET",
46363
+ return await fetchStandardModelsList({
46364
+ provider: "xai",
46365
+ url: "https://api.x.ai/v1/models",
46183
46366
  headers: { Authorization: `Bearer ${apiKey}` },
46184
- signal: AbortSignal.timeout(timeoutMs)
46367
+ timeoutMs
46185
46368
  });
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
46369
  }
46194
46370
  async function fetchMistralModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46195
- const response = await fetch("https://api.mistral.ai/v1/models", {
46196
- method: "GET",
46371
+ return await fetchStandardModelsList({
46372
+ provider: "mistral",
46373
+ url: "https://api.mistral.ai/v1/models",
46197
46374
  headers: { Authorization: `Bearer ${apiKey}` },
46198
- signal: AbortSignal.timeout(timeoutMs)
46375
+ timeoutMs
46199
46376
  });
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
46377
  }
46208
46378
  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)
46379
+ const endpoint = `${baseUrl.replace(/\/+$/, "")}/models`;
46380
+ return await fetchStandardModelsList({
46381
+ provider: "ollama",
46382
+ url: endpoint,
46383
+ timeoutMs
46213
46384
  });
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
46385
  }
46222
46386
  async function fetchGitHubCopilotModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46223
- const response = await fetch("https://api.individual.githubcopilot.com/models", {
46224
- method: "GET",
46387
+ return await fetchStandardModelsList({
46388
+ provider: "github-copilot",
46389
+ url: "https://api.individual.githubcopilot.com/models",
46225
46390
  headers: {
46226
46391
  Accept: "application/json",
46227
46392
  Authorization: `Bearer ${apiKey}`,
@@ -46231,16 +46396,21 @@ async function fetchGitHubCopilotModels(apiKey, timeoutMs = DEFAULT_FETCH_TIMEOU
46231
46396
  "Copilot-Integration-Id": "vscode-chat",
46232
46397
  "X-GitHub-Api-Version": "2026-06-01"
46233
46398
  },
46234
- signal: AbortSignal.timeout(timeoutMs)
46399
+ timeoutMs,
46400
+ filterItem: (item) => item.capabilities?.supports?.tool_calls !== false
46235
46401
  });
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
46402
  }
46403
+ var API_KEY_FETCHERS = {
46404
+ openai: fetchOpenAIModels,
46405
+ gemini: fetchGeminiModels,
46406
+ anthropic: fetchAnthropicModels,
46407
+ deepseek: fetchDeepSeekModels,
46408
+ openrouter: fetchOpenRouterModels,
46409
+ "github-copilot": fetchGitHubCopilotModels,
46410
+ groq: fetchGroqModels,
46411
+ xai: fetchXAIModels,
46412
+ mistral: fetchMistralModels
46413
+ };
46244
46414
  async function fetchAvailableModels(authCtx, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
46245
46415
  const result = {
46246
46416
  models: [],
@@ -46259,152 +46429,30 @@ async function fetchAvailableModels(authCtx, timeoutMs = DEFAULT_FETCH_TIMEOUT_M
46259
46429
  }
46260
46430
  };
46261
46431
  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
- }));
46432
+ for (const [providerKey, fetcher] of Object.entries(API_KEY_FETCHERS)) {
46433
+ const provider = providerKey;
46434
+ if (!fetcher)
46435
+ continue;
46436
+ const apiKey = await authCtx.getApiKey(provider);
46437
+ if (apiKey) {
46438
+ tasks.push(fetcher(apiKey, timeoutMs).then((models) => {
46439
+ result.models.push(...models);
46440
+ result.providers[provider] = {
46441
+ status: "available",
46442
+ modelCount: models.length
46443
+ };
46444
+ }).catch((err) => {
46445
+ result.providers[provider] = {
46446
+ status: "error",
46447
+ modelCount: 0,
46448
+ error: err instanceof Error ? err.message : String(err)
46449
+ };
46450
+ }));
46451
+ }
46405
46452
  }
46406
46453
  const ollamaUrl = authCtx.getOllamaEndpoint?.() || "http://localhost:11434/v1";
46407
- tasks.push(fetchOllamaModels(ollamaUrl, 2000).then((models) => {
46454
+ const ollamaTimeout = Math.min(timeoutMs, 3000);
46455
+ tasks.push(fetchOllamaModels(ollamaUrl, ollamaTimeout).then((models) => {
46408
46456
  if (models.length > 0) {
46409
46457
  result.models.push(...models);
46410
46458
  result.providers.ollama = {
@@ -53488,7 +53536,9 @@ function rehydrateSessionHistory(sessionData, projection) {
53488
53536
  textAccum += (textAccum ? `
53489
53537
  ` : "") + part.text;
53490
53538
  } else if (part.type === "tool-call") {
53491
- const rawInput = part.args ?? part.input;
53539
+ const callId = part.id ?? part.toolCallId;
53540
+ const toolName = part.name ?? part.toolName;
53541
+ const rawInput = part.arguments ?? part.args ?? part.input;
53492
53542
  let parsedInput = rawInput ?? {};
53493
53543
  if (typeof rawInput === "string") {
53494
53544
  const trimmed = rawInput.trim();
@@ -53498,12 +53548,14 @@ function rehydrateSessionHistory(sessionData, projection) {
53498
53548
  } catch {}
53499
53549
  }
53500
53550
  }
53501
- toolCallsMap.set(part.toolCallId, {
53502
- id: part.toolCallId,
53503
- name: part.toolName,
53504
- args: parsedInput,
53505
- isError: false
53506
- });
53551
+ if (callId) {
53552
+ toolCallsMap.set(callId, {
53553
+ id: callId,
53554
+ name: toolName,
53555
+ args: parsedInput,
53556
+ isError: false
53557
+ });
53558
+ }
53507
53559
  }
53508
53560
  }
53509
53561
  if (textAccum.trim()) {
@@ -53518,47 +53570,49 @@ function rehydrateSessionHistory(sessionData, projection) {
53518
53570
  } else if (msg.role === "tool") {
53519
53571
  if (Array.isArray(msg.content)) {
53520
53572
  for (const part of msg.content) {
53521
- if (part.type === "tool-result") {
53522
- const existing = toolCallsMap.get(part.toolCallId);
53523
- const isError = Boolean(part.isError);
53524
- const toolName = existing?.name ?? part.toolName ?? "tool";
53525
- const argsSummary = existing?.args ? JSON.stringify(existing.args) : "";
53526
- let outputVal = part.result ?? part.output;
53527
- if (outputVal && typeof outputVal === "object" && outputVal.type === "json" && "value" in outputVal) {
53528
- outputVal = outputVal.value;
53529
- }
53530
- if (typeof outputVal === "string") {
53531
- const trimmed = outputVal.trim();
53532
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
53533
- try {
53534
- outputVal = JSON.parse(trimmed);
53535
- } catch {}
53536
- }
53573
+ const callId = part.toolCallId ?? part.id;
53574
+ if (!callId)
53575
+ continue;
53576
+ const existing = toolCallsMap.get(callId);
53577
+ const isError = Boolean(part.isError);
53578
+ const toolName = existing?.name ?? part.toolName ?? part.name ?? "tool";
53579
+ const args = existing?.args;
53580
+ const argsSummary = args ? JSON.stringify(args) : "";
53581
+ let outputVal = part.output ?? part.result;
53582
+ if (outputVal && typeof outputVal === "object" && outputVal.type === "json" && "value" in outputVal) {
53583
+ outputVal = outputVal.value;
53584
+ }
53585
+ if (typeof outputVal === "string") {
53586
+ const trimmed = outputVal.trim();
53587
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
53588
+ try {
53589
+ outputVal = JSON.parse(trimmed);
53590
+ } catch {}
53537
53591
  }
53538
- const canonicalOutputSummary = formatToolOutputSummary(outputVal, isError);
53539
- const toolLog = turnPresentation?.tools.get(part.toolCallId);
53540
- const status = toolLog ? toolLog.status === "failed" ? "failed" : "completed" : isError ? "failed" : "completed";
53541
- const error = toolLog?.errorMessage ? toolLog.errorMessage : isError ? typeof outputVal === "object" && outputVal !== null ? outputVal.message ?? JSON.stringify(outputVal) : String(outputVal) : undefined;
53542
- const toolOutput = toolLog?.outputSummary ?? canonicalOutputSummary;
53543
- restoredItems.push({
53544
- id: `tool-${part.toolCallId}`,
53545
- turnId: turn.id,
53546
- type: "tool",
53547
- content: "",
53548
- toolData: {
53549
- toolName,
53550
- displayName: toolLog?.displayName,
53551
- icon: toolLog?.icon,
53552
- argsSummary,
53553
- args: existing?.args,
53554
- result: outputVal,
53555
- status,
53556
- durationMs: toolLog?.durationMs,
53557
- error,
53558
- toolOutput
53559
- }
53560
- });
53561
53592
  }
53593
+ const canonicalOutputSummary = formatToolOutputSummary(outputVal, isError);
53594
+ const toolLog = turnPresentation?.tools.get(callId);
53595
+ const status = toolLog ? toolLog.status === "failed" ? "failed" : "completed" : isError ? "failed" : "completed";
53596
+ const error = toolLog?.errorMessage ? toolLog.errorMessage : isError ? typeof outputVal === "object" && outputVal !== null ? outputVal.message ?? JSON.stringify(outputVal) : String(outputVal) : undefined;
53597
+ const toolOutput = toolLog?.outputSummary ?? canonicalOutputSummary;
53598
+ restoredItems.push({
53599
+ id: `tool-${callId}`,
53600
+ turnId: turn.id,
53601
+ type: "tool",
53602
+ content: "",
53603
+ toolData: {
53604
+ toolName,
53605
+ displayName: toolLog?.displayName,
53606
+ icon: toolLog?.icon,
53607
+ argsSummary,
53608
+ args,
53609
+ result: outputVal,
53610
+ status,
53611
+ durationMs: toolLog?.durationMs,
53612
+ error,
53613
+ toolOutput
53614
+ }
53615
+ });
53562
53616
  }
53563
53617
  }
53564
53618
  }
@@ -53893,15 +53947,15 @@ var defaultToolCatalog = new ToolCatalog;
53893
53947
 
53894
53948
  // src/packages/agents/src/tools/read-file/index.ts
53895
53949
  import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync3, realpathSync } from "node:fs";
53896
- import { isAbsolute, resolve, sep } from "node:path";
53950
+ import { isAbsolute, resolve as resolve2, sep } from "node:path";
53897
53951
  var readFileInputSchema = object({
53898
53952
  path: string2().describe("The path of the file to read (relative to current working directory or absolute)."),
53899
53953
  offset: number2().int().min(1).optional().describe("The line number to start reading from (1-indexed). Defaults to 1."),
53900
53954
  limit: number2().int().min(1).max(2000).optional().describe("The maximum number of lines to read. Defaults to 200.")
53901
53955
  });
53902
53956
  function isPathInside(targetPath, basePath) {
53903
- const normTarget = resolve(targetPath);
53904
- const normBase = resolve(basePath);
53957
+ const normTarget = resolve2(targetPath);
53958
+ const normBase = resolve2(basePath);
53905
53959
  if (normTarget === normBase) {
53906
53960
  return true;
53907
53961
  }
@@ -53913,7 +53967,7 @@ function resolveSafeReadPath(cwd, filePath) {
53913
53967
  throw new Error("File path cannot be empty.");
53914
53968
  }
53915
53969
  const trimmed = filePath.trim();
53916
- const resolved = isAbsolute(trimmed) ? resolve(trimmed) : resolve(cwd, trimmed);
53970
+ const resolved = isAbsolute(trimmed) ? resolve2(trimmed) : resolve2(cwd, trimmed);
53917
53971
  if (!isPathInside(resolved, cwd)) {
53918
53972
  throw new Error(`Access denied: path "${filePath}" resolves outside the trusted workspace root ("${cwd}").`);
53919
53973
  }
@@ -54014,12 +54068,12 @@ import { existsSync as existsSync8, readFileSync as readFileSync5, statSync } fr
54014
54068
  // src/packages/services/src/checkpoint/path.ts
54015
54069
  import { createHash } from "node:crypto";
54016
54070
  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";
54071
+ import { dirname as dirname4, isAbsolute as isAbsolute2, relative, resolve as resolve4, sep as sep3 } from "node:path";
54018
54072
 
54019
54073
  // src/packages/services/src/config/settings.ts
54020
54074
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync3 } from "node:fs";
54021
54075
  import { homedir as homedir4 } from "node:os";
54022
- import { join as join6, resolve as resolve2, sep as sep2 } from "node:path";
54076
+ import { join as join6, resolve as resolve3, sep as sep2 } from "node:path";
54023
54077
  function getSettingsDir() {
54024
54078
  if (process.env.STEWARD_SETTINGS_DIR) {
54025
54079
  return process.env.STEWARD_SETTINGS_DIR;
@@ -54073,7 +54127,7 @@ function saveThemeSelection(name) {
54073
54127
  });
54074
54128
  }
54075
54129
  function normalizeFolderPath(inputPath) {
54076
- let absolute = resolve2(inputPath);
54130
+ let absolute = resolve3(inputPath);
54077
54131
  try {
54078
54132
  absolute = realpathSync2(absolute);
54079
54133
  } catch {}
@@ -54145,7 +54199,7 @@ function resolveDirectMutationPath(cwd, filePath) {
54145
54199
  }
54146
54200
  const trimmed = filePath.trim();
54147
54201
  const workspaceRoot = normalizeFolderPath(cwd);
54148
- const resolved = isAbsolute2(trimmed) ? resolve3(trimmed) : resolve3(cwd, trimmed);
54202
+ const resolved = isAbsolute2(trimmed) ? resolve4(trimmed) : resolve4(cwd, trimmed);
54149
54203
  if (!isPathInside2(resolved, workspaceRoot)) {
54150
54204
  throw new Error(`Access denied: path "${filePath}" resolves outside the trusted workspace root ("${cwd}").`);
54151
54205
  }
@@ -54261,16 +54315,16 @@ var writeFileTool = {
54261
54315
  parameters: writeFileInputSchema,
54262
54316
  confirmationPolicy: "never",
54263
54317
  summarize: (args, result) => {
54264
- const lines = result?.linesWritten ?? (args.content ? args.content.split(/\r?\n/).length : 0);
54265
- const filePath = args.file_path;
54318
+ const lines = result?.linesWritten ?? (args?.content ? args.content.split(/\r?\n/).length : 0);
54319
+ const filePath = args?.file_path ?? "file";
54266
54320
  const isNew = result?.isNew ?? true;
54267
54321
  const headline = isNew ? `Created ${filePath} (${lines} line${lines === 1 ? "" : "s"})` : `Wrote ${lines} line${lines === 1 ? "" : "s"} to ${filePath}`;
54268
54322
  return {
54269
54323
  headline,
54270
54324
  detail: {
54271
54325
  kind: "code",
54272
- filePath: args.file_path,
54273
- text: args.content,
54326
+ filePath: args?.file_path ?? filePath,
54327
+ text: args?.content ?? "",
54274
54328
  totalLines: lines
54275
54329
  }
54276
54330
  };
@@ -54609,10 +54663,13 @@ var editFileTool = {
54609
54663
  parameters: editFileInputSchema,
54610
54664
  confirmationPolicy: "never",
54611
54665
  summarize: (args, result) => {
54612
- if (args.old_string === args.new_string) {
54613
- return { headline: `No changes made to ${args.file_path}` };
54666
+ const oldStr = args?.old_string ?? "";
54667
+ const newStr = args?.new_string ?? "";
54668
+ const filePath = args?.file_path ?? "file";
54669
+ if (oldStr === newStr) {
54670
+ return { headline: `No changes made to ${filePath}` };
54614
54671
  }
54615
- const diff = buildUnifiedDiff(args.old_string, args.new_string, 2);
54672
+ const diff = buildUnifiedDiff(oldStr, newStr, 2);
54616
54673
  let diffAdded = 0;
54617
54674
  let diffRemoved = 0;
54618
54675
  for (const hunk of diff.hunks) {
@@ -54630,7 +54687,7 @@ var editFileTool = {
54630
54687
  headline,
54631
54688
  detail: {
54632
54689
  kind: "diff",
54633
- filePath: args.file_path,
54690
+ filePath,
54634
54691
  hunks: diff.hunks
54635
54692
  }
54636
54693
  };
@@ -54732,7 +54789,7 @@ var editFileTool = {
54732
54789
 
54733
54790
  // src/packages/agents/src/tools/glob/index.ts
54734
54791
  import { readdirSync as readdirSync2 } from "node:fs";
54735
- import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve4 } from "node:path";
54792
+ import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve5 } from "node:path";
54736
54793
  var globInputSchema = object({
54737
54794
  pattern: string2().describe('Glob pattern to match files against (e.g. "**/*.ts", "src/*.json").'),
54738
54795
  path: string2().optional().describe("Starting directory path. Defaults to workspace root."),
@@ -54785,9 +54842,9 @@ var globTool = {
54785
54842
  },
54786
54843
  execute: async (args, context) => {
54787
54844
  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);
54845
+ const searchRoot = args.path ? isAbsolute3(args.path) ? resolve5(args.path) : resolve5(context.cwd, args.path) : context.cwd;
54846
+ const normCwd = resolve5(context.cwd);
54847
+ const normTarget = resolve5(searchRoot);
54791
54848
  const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
54792
54849
  if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
54793
54850
  throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
@@ -54814,7 +54871,7 @@ var globTool = {
54814
54871
 
54815
54872
  // src/packages/agents/src/tools/grep/index.ts
54816
54873
  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";
54874
+ import { isAbsolute as isAbsolute4, join as join9, relative as relative3, resolve as resolve6 } from "node:path";
54818
54875
  var grepInputSchema = object({
54819
54876
  pattern: string2().describe("The regex or text pattern to search for across files."),
54820
54877
  path: string2().optional().describe("Target directory or file path to search. Defaults to current directory."),
@@ -54890,9 +54947,9 @@ var grepTool = {
54890
54947
  },
54891
54948
  execute: async (args, context) => {
54892
54949
  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);
54950
+ const searchRoot = args.path ? isAbsolute4(args.path) ? resolve6(args.path) : resolve6(context.cwd, args.path) : context.cwd;
54951
+ const normCwd = resolve6(context.cwd);
54952
+ const normTarget = resolve6(searchRoot);
54896
54953
  const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
54897
54954
  if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
54898
54955
  throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
@@ -54951,7 +55008,7 @@ var grepTool = {
54951
55008
 
54952
55009
  // src/packages/agents/src/tools/list-dir/index.ts
54953
55010
  import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
54954
- import { isAbsolute as isAbsolute5, join as join10, resolve as resolve6 } from "node:path";
55011
+ import { isAbsolute as isAbsolute5, join as join10, resolve as resolve7 } from "node:path";
54955
55012
  var listDirInputSchema = object({
54956
55013
  path: string2().optional().describe("Directory path to list. Defaults to current working directory."),
54957
55014
  recursive: boolean2().optional().describe("If true, lists files recursively (depth 2). Defaults to false.")
@@ -54969,9 +55026,9 @@ var listDirTool = {
54969
55026
  return `Listed ${files} file${files === 1 ? "" : "s"}, ${dirs} director${dirs === 1 ? "y" : "ies"}`;
54970
55027
  },
54971
55028
  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);
55029
+ const targetPath = args.path ? isAbsolute5(args.path) ? resolve7(args.path) : resolve7(context.cwd, args.path) : context.cwd;
55030
+ const normCwd = resolve7(context.cwd);
55031
+ const normTarget = resolve7(targetPath);
54975
55032
  const prefix = normCwd.endsWith("/") ? normCwd : `${normCwd}/`;
54976
55033
  if (normTarget !== normCwd && !normTarget.startsWith(prefix)) {
54977
55034
  throw new Error(`Access denied: path "${args.path}" resolves outside the trusted workspace root ("${context.cwd}").`);
@@ -55407,18 +55464,18 @@ Process error: ${err.message}
55407
55464
  // src/packages/agents/src/tools/bash/command-policy.ts
55408
55465
  import { existsSync as existsSync10, realpathSync as realpathSync4 } from "node:fs";
55409
55466
  import { homedir as homedir6 } from "node:os";
55410
- import { isAbsolute as isAbsolute6, resolve as resolve7, sep as sep4 } from "node:path";
55467
+ import { isAbsolute as isAbsolute6, resolve as resolve8, sep as sep4 } from "node:path";
55411
55468
  function expandHome(filepath) {
55412
55469
  if (filepath === "~") {
55413
55470
  return homedir6();
55414
55471
  }
55415
55472
  if (filepath.startsWith("~/") || filepath.startsWith("~\\")) {
55416
- return resolve7(homedir6(), filepath.slice(2));
55473
+ return resolve8(homedir6(), filepath.slice(2));
55417
55474
  }
55418
55475
  return filepath;
55419
55476
  }
55420
55477
  function normalizeRoot(inputPath) {
55421
- let absolute = resolve7(inputPath);
55478
+ let absolute = resolve8(inputPath);
55422
55479
  try {
55423
55480
  absolute = realpathSync4(absolute);
55424
55481
  } catch {}
@@ -55553,7 +55610,7 @@ function arePathTokensInsideWorkspace(tokens, startIndex, cwd) {
55553
55610
  continue;
55554
55611
  }
55555
55612
  const expanded = expandHome(tok);
55556
- const resolved = isAbsolute6(expanded) ? resolve7(expanded) : resolve7(cwd, expanded);
55613
+ const resolved = isAbsolute6(expanded) ? resolve8(expanded) : resolve8(cwd, expanded);
55557
55614
  if (!isContainedInWorkspace(resolved, cwd)) {
55558
55615
  return false;
55559
55616
  }
@@ -55841,7 +55898,7 @@ var taskReadTool = {
55841
55898
  parameters: taskReadInputSchema,
55842
55899
  confirmationPolicy: "never",
55843
55900
  summarize: (args, result) => {
55844
- return `Task ${args.task_id} · status: ${result?.status ?? "unknown"}`;
55901
+ return `Task ${args?.task_id ?? ""} · status: ${result?.status ?? "unknown"}`;
55845
55902
  },
55846
55903
  execute: async (args, context) => {
55847
55904
  if (!context.shellTasks) {
@@ -55876,7 +55933,7 @@ var taskSendInputTool = {
55876
55933
  parameters: taskSendInputSchema,
55877
55934
  confirmationPolicy: "never",
55878
55935
  summarize: (args) => {
55879
- return `Sent input to ${args.task_id} (${args.input.length} chars)`;
55936
+ return `Sent input to ${args?.task_id ?? ""} (${args?.input?.length ?? 0} chars)`;
55880
55937
  },
55881
55938
  execute: async (args, context) => {
55882
55939
  if (!context.shellTasks) {
@@ -55900,7 +55957,7 @@ var taskKillTool = {
55900
55957
  parameters: taskKillInputSchema,
55901
55958
  confirmationPolicy: "never",
55902
55959
  summarize: (args, result) => {
55903
- return `Stopped task ${args.task_id} · status: ${result?.status ?? "killed"}`;
55960
+ return `Stopped task ${args?.task_id ?? ""} · status: ${result?.status ?? "killed"}`;
55904
55961
  },
55905
55962
  execute: async (args, context) => {
55906
55963
  if (!context.shellTasks) {
@@ -55941,8 +55998,8 @@ import { isIP } from "node:net";
55941
55998
  // package.json
55942
55999
  var package_default = {
55943
56000
  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.",
56001
+ version: "0.25.0",
56002
+ description: "Interactive AI engineering assistant for the terminal.",
55946
56003
  author: "sapirrior",
55947
56004
  license: "MIT",
55948
56005
  files: [
@@ -56127,14 +56184,15 @@ var webFetchTool = {
56127
56184
  confirmationPolicy: "never",
56128
56185
  summarize: (args, result) => {
56129
56186
  if (!result)
56130
- return `Fetching ${args.url}`;
56131
- const chars = result.content.length;
56132
- const truncated = result.isTruncated ? " (truncated)" : "";
56133
- const type = result.contentType.split(";")[0]?.trim() ?? "text";
56187
+ return `Fetching ${args?.url ?? ""}`;
56188
+ const chars = result?.content?.length ?? 0;
56189
+ const truncated = result?.isTruncated ? " (truncated)" : "";
56190
+ const type = result?.contentType?.split(";")[0]?.trim() ?? "text";
56134
56191
  const label = type.includes("html") ? "HTML" : type.includes("json") ? "JSON" : type.includes("markdown") || type.includes("text/plain") ? "text" : type.split("/").pop() ?? "content";
56135
- let host = args.url;
56192
+ let host = args?.url ?? "";
56136
56193
  try {
56137
- host = new URL(args.url).hostname;
56194
+ if (args?.url)
56195
+ host = new URL(args.url).hostname;
56138
56196
  } catch {}
56139
56197
  return `Fetched ${chars.toLocaleString()} chars of ${label} from ${host}${truncated}`;
56140
56198
  },
@@ -56274,9 +56332,9 @@ var webSearchTool = {
56274
56332
  confirmationPolicy: "never",
56275
56333
  summarize: (args, result) => {
56276
56334
  if (!result)
56277
- return `Searching "${args.query}"`;
56278
- const count = result.resultCount;
56279
- const site = args.site ? ` on ${args.site}` : "";
56335
+ return `Searching "${args?.query ?? ""}"`;
56336
+ const count = result?.resultCount ?? 0;
56337
+ const site = args?.site ? ` on ${args.site}` : "";
56280
56338
  return `Found ${count} result${count === 1 ? "" : "s"}${site} via DuckDuckGo`;
56281
56339
  },
56282
56340
  execute: async (args, context) => {
@@ -56296,7 +56354,7 @@ var webSearchTool = {
56296
56354
  // src/packages/agents/src/skills/discovery.ts
56297
56355
  import { existsSync as existsSync11, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
56298
56356
  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";
56357
+ import { isAbsolute as isAbsolute7, join as join12, normalize, resolve as resolve9, sep as sep5 } from "node:path";
56300
56358
  var MAX_SKILL_FILE_SIZE = 64 * 1024;
56301
56359
  function parseSkillFrontmatter(content) {
56302
56360
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -56411,8 +56469,8 @@ function readSkillResource(name, relativePath, cwd = process.cwd(), maxBytes = M
56411
56469
  if (normalizedRelative.startsWith("..") || normalizedRelative.includes(`${sep5}..`)) {
56412
56470
  throw new Error(`Invalid skill resource path: directory traversal ("..") is forbidden (${trimmedPath})`);
56413
56471
  }
56414
- targetFilePath = resolve8(skill.dirPath, normalizedRelative);
56415
- const resolvedDir = resolve8(skill.dirPath);
56472
+ targetFilePath = resolve9(skill.dirPath, normalizedRelative);
56473
+ const resolvedDir = resolve9(skill.dirPath);
56416
56474
  if (!targetFilePath.startsWith(resolvedDir + sep5) && targetFilePath !== resolvedDir) {
56417
56475
  throw new Error(`Resource "${trimmedPath}" resolves outside skill directory`);
56418
56476
  }
@@ -56479,17 +56537,17 @@ var skillReadTool = {
56479
56537
  description: "Loads and reads the instructions (SKILL.md) or a relative resource file of a specialized skill by name. Use this when a task requires specialized guidelines.",
56480
56538
  parameters: skillReadParamsSchema,
56481
56539
  summarizeArgs(args) {
56482
- if (args.path) {
56540
+ if (args?.path) {
56483
56541
  return `name="${args.name}" path="${args.path}"`;
56484
56542
  }
56485
- return `name="${args.name}"`;
56543
+ return `name="${args?.name ?? ""}"`;
56486
56544
  },
56487
56545
  async execute(args, context) {
56488
56546
  const cwd = context?.cwd ?? process.cwd();
56489
56547
  return readSkillResource(args.name, args.path, cwd);
56490
56548
  },
56491
56549
  summarize(args, result) {
56492
- const target = args.path ? `${args.name}/${args.path}` : args.name;
56550
+ const target = args?.path ? `${args.name}/${args.path}` : args?.name ?? "skill";
56493
56551
  const len = result?.content ? ` (${result.content.length} bytes)` : "";
56494
56552
  return `SkillRead: ${target}${len}`;
56495
56553
  }
@@ -56526,17 +56584,19 @@ function summarizeToolResult(toolDef, args, result, isError = false) {
56526
56584
  return;
56527
56585
  }
56528
56586
  if (toolDef.summarize) {
56529
- const sum = toolDef.summarize(args, result);
56530
- if (typeof sum === "string") {
56531
- const [headline, ...rest] = sum.split(`
56587
+ try {
56588
+ const sum = toolDef.summarize(args, result);
56589
+ if (typeof sum === "string") {
56590
+ const [headline, ...rest] = sum.split(`
56532
56591
  `);
56533
- return {
56534
- headline: headline ?? "",
56535
- detail: rest.length > 0 ? { kind: "text", text: rest.join(`
56592
+ return {
56593
+ headline: headline ?? "",
56594
+ detail: rest.length > 0 ? { kind: "text", text: rest.join(`
56536
56595
  `) } : undefined
56537
- };
56538
- }
56539
- return sum;
56596
+ };
56597
+ }
56598
+ return sum;
56599
+ } catch {}
56540
56600
  }
56541
56601
  if (typeof result === "string") {
56542
56602
  const firstLine = result.split(`
@@ -57646,7 +57706,7 @@ import {
57646
57706
  unlinkSync as unlinkSync5,
57647
57707
  writeSync as writeSync5
57648
57708
  } from "node:fs";
57649
- import { dirname as dirname6, join as join15, resolve as resolve9 } from "node:path";
57709
+ import { dirname as dirname6, join as join15, resolve as resolve10 } from "node:path";
57650
57710
  import { randomUUID as randomUUID5 } from "node:crypto";
57651
57711
  function getFileStateOnDisk(absPath) {
57652
57712
  if (!existsSync15(absPath)) {
@@ -57744,7 +57804,7 @@ async function executeRewind(options) {
57744
57804
  if (!fileCp.mutationCommitted)
57745
57805
  continue;
57746
57806
  const relPath = fileCp.relativePath;
57747
- const absPath = resolve9(workspaceRoot, relPath);
57807
+ const absPath = resolve10(workspaceRoot, relPath);
57748
57808
  if (!fileOperationsMap.has(relPath)) {
57749
57809
  fileOperationsMap.set(relPath, {
57750
57810
  relativePath: relPath,
@@ -57895,7 +57955,7 @@ async function recoverPendingCheckpoint(workspaceRoot, sessionId) {
57895
57955
  if (journal.phase === "files-restored" || journal.phase === "files-pending") {
57896
57956
  for (const file of journal.files) {
57897
57957
  if (file.post) {
57898
- const absPath = resolve9(workspaceRoot, file.relativePath);
57958
+ const absPath = resolve10(workspaceRoot, file.relativePath);
57899
57959
  try {
57900
57960
  restoreFileStateToDisk(workspaceHash, absPath, file.post);
57901
57961
  } catch {}
@@ -58669,35 +58729,7 @@ function getDiscoveryAuthContext() {
58669
58729
  const resolved = await authManager.resolve(provider).catch(() => {
58670
58730
  return;
58671
58731
  });
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
- }
58732
+ return resolved?.token && resolved.token !== "none" ? resolved.token : undefined;
58701
58733
  },
58702
58734
  getCustomEndpoint: () => config.custom,
58703
58735
  getOllamaEndpoint: () => config.ollamaBaseUrl
@@ -61892,22 +61924,14 @@ class CommandPaletteController {
61892
61924
  const matching = this.getMatchingCommands(value);
61893
61925
  if (matching.length === 0)
61894
61926
  return false;
61895
- if (this.paletteIdx === 0) {
61896
- this.dismiss();
61897
- return false;
61898
- }
61899
- this.paletteIdx = this.paletteIdx - 1;
61927
+ this.paletteIdx = this.paletteIdx > 0 ? this.paletteIdx - 1 : matching.length - 1;
61900
61928
  return true;
61901
61929
  }
61902
61930
  onDown(value) {
61903
61931
  const matching = this.getMatchingCommands(value);
61904
61932
  if (matching.length === 0)
61905
61933
  return false;
61906
- if (this.paletteIdx >= matching.length - 1) {
61907
- this.dismiss();
61908
- return false;
61909
- }
61910
- this.paletteIdx = this.paletteIdx + 1;
61934
+ this.paletteIdx = this.paletteIdx < matching.length - 1 ? this.paletteIdx + 1 : 0;
61911
61935
  return true;
61912
61936
  }
61913
61937
  onTab(value) {
@@ -62370,25 +62394,32 @@ ${after}`, this.state.cursorPos);
62370
62394
  const matchingCommands = this.commandPalette.getMatchingCommands(effectiveValue);
62371
62395
  const paletteIdx = this.state.paletteIdx ?? this.commandPalette.getSelectedIndex();
62372
62396
  if (this.commandPalette.isSlashMode(effectiveValue) && matchingCommands.length > 0 && effectiveValue !== `/${matchingCommands[0]?.name} `) {
62373
- for (let i = 0;i < matchingCommands.length; i++) {
62374
- const cmd = matchingCommands[i];
62375
- const isSelected = i === paletteIdx;
62376
- const namePadded = `/${cmd.name}`.padEnd(18);
62377
- if (isSelected) {
62378
- lines.push(truncateToWidth2(`${bold(c.text(namePadded))}${bold(c.text(cmd.description))}`, maxCols));
62379
- } else {
62380
- lines.push(truncateToWidth2(`${c.muted(namePadded)}${c.muted(cmd.description)}`, maxCols));
62381
- }
62397
+ const maxVisible = 5;
62398
+ const startIdx = Math.max(0, Math.min(paletteIdx - Math.floor(maxVisible / 2), matchingCommands.length - maxVisible));
62399
+ const visibleCmds = matchingCommands.slice(Math.max(0, startIdx), Math.max(0, startIdx) + maxVisible);
62400
+ for (let relIdx = 0;relIdx < visibleCmds.length; relIdx++) {
62401
+ const cmd = visibleCmds[relIdx];
62402
+ const actualIdx = Math.max(0, startIdx) + relIdx;
62403
+ const isSelected = actualIdx === paletteIdx;
62404
+ const p = isSelected ? c.info(`${figures.pointer} `) : " ";
62405
+ const namePadded = `/${cmd.name}`.padEnd(16);
62406
+ const nameText = isSelected ? c.info(namePadded) : c.text(namePadded);
62407
+ const descText = isSelected ? c.info(cmd.description) : c.muted(cmd.description);
62408
+ lines.push(truncateToWidth2(`${p}${nameText}${descText}`, maxCols));
62382
62409
  }
62383
62410
  }
62384
62411
  const fileMatches = this.state.fileMatches ?? this.autocomplete.getMatches();
62385
62412
  const fileSelectIdx = this.state.fileSelectIdx ?? this.autocomplete.getSelectedIndex();
62386
62413
  if (fileMatches.length > 0) {
62387
62414
  lines.push(c.muted("Matching files (@):"));
62388
- for (let i = 0;i < fileMatches.length; i++) {
62389
- const f = fileMatches[i];
62390
- const isSelected = i === fileSelectIdx;
62391
- const p = isSelected ? c.info(`${figures.pointerBold} `) : " ";
62415
+ const maxVisible = 5;
62416
+ const startIdx = Math.max(0, Math.min(fileSelectIdx - Math.floor(maxVisible / 2), fileMatches.length - maxVisible));
62417
+ const visibleFiles = fileMatches.slice(Math.max(0, startIdx), Math.max(0, startIdx) + maxVisible);
62418
+ for (let relIdx = 0;relIdx < visibleFiles.length; relIdx++) {
62419
+ const f = visibleFiles[relIdx];
62420
+ const actualIdx = Math.max(0, startIdx) + relIdx;
62421
+ const isSelected = actualIdx === fileSelectIdx;
62422
+ const p = isSelected ? c.info(`${figures.pointer} `) : " ";
62392
62423
  const fileText = isSelected ? c.info(f) : c.muted(f);
62393
62424
  lines.push(truncateToWidth2(`${p}${fileText}`, maxCols));
62394
62425
  }
@@ -62451,7 +62482,7 @@ class TrustGate extends Component_default {
62451
62482
  const { selectedIndex } = this.state;
62452
62483
  const isYesSelected = selectedIndex === 0;
62453
62484
  const isNoSelected = selectedIndex === 1;
62454
- const pointer = figures.pointerBold ?? "❯";
62485
+ const pointer = figures.pointer ?? ">";
62455
62486
  const yesOptionText = isYesSelected ? `${c.permission(pointer)} ${bold(c.permission("1. Yes, I trust this folder"))}` : ` ${c.text("1. Yes, I trust this folder")}`;
62456
62487
  const noOptionText = isNoSelected ? `${c.permission(pointer)} ${bold(c.permission("2. No, exit"))}` : ` ${c.text("2. No, exit")}`;
62457
62488
  const safetyCheckText = "You should only proceed if you trust this workspace. Accessing untrusted workspaces may allow malicious code in the repository to compromise security or mislead the assistant.";
@@ -63342,14 +63373,14 @@ class LoginDock extends Component_default {
63342
63373
  if (step === "select-provider") {
63343
63374
  const items = OAUTH_PROVIDERS.map((p, idx) => {
63344
63375
  const isSelected = idx === selectedIndex;
63345
- const pointer = isSelected ? c.selected(`${figures.pointer} `) : " ";
63376
+ const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
63377
+ const name = isSelected ? c.info(bold(p.name)) : c.text(bold(p.name));
63346
63378
  return /* @__PURE__ */ jsxs(Box, {
63347
63379
  direction: "column",
63348
63380
  width: maxCols,
63349
63381
  children: [
63350
63382
  /* @__PURE__ */ jsx(Text, {
63351
- color: isSelected ? "selected" : "text",
63352
- children: `${pointer}${bold(p.name)}`
63383
+ children: `${pointer}${name}`
63353
63384
  }),
63354
63385
  /* @__PURE__ */ jsx(Text, {
63355
63386
  color: "muted",
@@ -63465,8 +63496,9 @@ class ModelPicker extends SelectList {
63465
63496
  onCancel: props.onCancel,
63466
63497
  renderItem: (m, isSelected, maxCols) => {
63467
63498
  const isCurrent = m.provider === props.currentModel.provider && m.modelId === props.currentModel.modelId;
63468
- const pointer = isSelected ? c.selected(`${figures.pointer} `) : " ";
63499
+ const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
63469
63500
  const activeBadge = isCurrent ? bold(c.current(" (active)")) : "";
63501
+ const title = isSelected ? c.info(m.modelId) : isCurrent ? c.current(m.modelId) : c.text(m.modelId);
63470
63502
  const providerName = m.provider.toUpperCase();
63471
63503
  const caps = m.reasoning ? "reasoning" : "chat";
63472
63504
  const meta = `${providerName} • ${caps}`;
@@ -63475,11 +63507,10 @@ class ModelPicker extends SelectList {
63475
63507
  width: maxCols,
63476
63508
  children: [
63477
63509
  /* @__PURE__ */ jsx(Text, {
63478
- color: isSelected ? "selected" : isCurrent ? "current" : "text",
63479
63510
  wrap: false,
63480
63511
  clip: true,
63481
63512
  ellipsis: true,
63482
- children: `${pointer}${m.modelId}${activeBadge}`
63513
+ children: `${pointer}${title}${activeBadge}`
63483
63514
  }),
63484
63515
  /* @__PURE__ */ jsx(Text, {
63485
63516
  color: "muted",
@@ -63515,8 +63546,9 @@ class ThemePicker extends SelectList {
63515
63546
  onCancel: props.onCancel,
63516
63547
  renderItem: (t, isSelected, maxCols) => {
63517
63548
  const isCurrent = t.name === props.currentTheme;
63518
- const pointer = isSelected ? c.selected(`${figures.pointer} `) : " ";
63549
+ const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
63519
63550
  const activeBadge = isCurrent ? bold(c.current(" (active)")) : "";
63551
+ const title = isSelected ? c.info(t.label) : isCurrent ? c.current(t.label) : c.text(t.label);
63520
63552
  const tBrand = resolveThemeColor(t.theme.brand, false)(figures.blackCircle);
63521
63553
  const tSuccess = resolveThemeColor(t.theme.success, false)(figures.blackCircle);
63522
63554
  const tWarning = resolveThemeColor(t.theme.warning, false)(figures.blackCircle);
@@ -63529,11 +63561,10 @@ class ThemePicker extends SelectList {
63529
63561
  width: maxCols,
63530
63562
  children: [
63531
63563
  /* @__PURE__ */ jsx(Text, {
63532
- color: isSelected ? "selected" : isCurrent ? "current" : "text",
63533
63564
  wrap: false,
63534
63565
  clip: true,
63535
63566
  ellipsis: true,
63536
- children: `${pointer}${t.label}${activeBadge}`
63567
+ children: `${pointer}${title}${activeBadge}`
63537
63568
  }),
63538
63569
  /* @__PURE__ */ jsx(Text, {
63539
63570
  color: "muted",
@@ -63573,7 +63604,8 @@ class SessionMenu extends SelectList {
63573
63604
  renderItem: (s, isSelected, maxCols) => {
63574
63605
  const rawTitle = s.name || s.turns?.[0]?.userPrompt || "Untitled Session";
63575
63606
  const cleanTitle = rawTitle.replace(/\s+/g, " ").trim();
63576
- const pointer = isSelected ? c.selected(`${figures.pointer} `) : " ";
63607
+ const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
63608
+ const title = isSelected ? c.info(cleanTitle) : c.text(cleanTitle);
63577
63609
  const shortId = s.id.slice(0, 8);
63578
63610
  const dateStr = s.date ? s.date.split("T")[0] : "";
63579
63611
  const turnsCount = s.turns?.length ?? 0;
@@ -63588,11 +63620,10 @@ class SessionMenu extends SelectList {
63588
63620
  width: maxCols,
63589
63621
  children: [
63590
63622
  /* @__PURE__ */ jsx(Text, {
63591
- color: isSelected ? "selected" : "text",
63592
63623
  wrap: false,
63593
63624
  clip: true,
63594
63625
  ellipsis: true,
63595
- children: `${pointer}${cleanTitle}`
63626
+ children: `${pointer}${title}`
63596
63627
  }),
63597
63628
  /* @__PURE__ */ jsx(Text, {
63598
63629
  color: "muted",
@@ -63945,7 +63976,8 @@ class RewindMenu extends SelectList {
63945
63976
  onSelect: props.onSelect,
63946
63977
  onCancel: props.onCancel,
63947
63978
  renderItem: (item, isSelected, maxCols) => {
63948
- const pointer = isSelected ? c.selected(`${figures.pointer} `) : " ";
63979
+ const pointer = isSelected ? c.info(`${figures.pointer} `) : " ";
63980
+ const title = isSelected ? c.info(item.promptText) : c.text(item.promptText);
63949
63981
  let summaryText;
63950
63982
  if (item.hasCodeChanges) {
63951
63983
  const filePart = c.muted(`${item.changedFileCount} file${item.changedFileCount !== 1 ? "s" : ""} changed`);
@@ -63966,11 +63998,10 @@ class RewindMenu extends SelectList {
63966
63998
  width: maxCols,
63967
63999
  children: [
63968
64000
  /* @__PURE__ */ jsx(Text, {
63969
- color: isSelected ? "selected" : "text",
63970
64001
  wrap: false,
63971
64002
  clip: true,
63972
64003
  ellipsis: true,
63973
- children: `${pointer}${item.promptText}`
64004
+ children: `${pointer}${title}`
63974
64005
  }),
63975
64006
  /* @__PURE__ */ jsx(Text, {
63976
64007
  wrap: false,
@@ -64071,7 +64102,7 @@ class BashPermissionDock extends Component_default {
64071
64102
  const maxCols = Math.max(1, termWidth);
64072
64103
  const { mode, selectedIndex, scrollOffset } = this.state;
64073
64104
  const { command, explanation } = this.props;
64074
- const pointer = figures.pointerBold ?? "❯";
64105
+ const pointer = figures.pointer ?? ">";
64075
64106
  const isYesSelected = selectedIndex === 0;
64076
64107
  const isNoSelected = selectedIndex === 1;
64077
64108
  const divider = c.rule(figures.horizontalLine.repeat(maxCols));
@@ -64381,7 +64412,7 @@ class FilePermissionDock extends Component_default {
64381
64412
  const { mode, selectedIndex, scrollOffset } = this.state;
64382
64413
  const { request } = this.props;
64383
64414
  const { kind, filePath } = request;
64384
- const pointer = figures.pointerBold ?? "❯";
64415
+ const pointer = figures.pointer ?? ">";
64385
64416
  const isYesSelected = selectedIndex === 0;
64386
64417
  const isNoSelected = selectedIndex === 1;
64387
64418
  const divider = c.rule(figures.horizontalLine.repeat(maxCols));