zelari-code 2.37.1 → 2.37.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38416,7 +38416,7 @@ var CORE_VERSION;
38416
38416
  var init_version = __esm({
38417
38417
  "packages/core/dist/version.js"() {
38418
38418
  "use strict";
38419
- CORE_VERSION = "2.37.1";
38419
+ CORE_VERSION = "2.37.2";
38420
38420
  }
38421
38421
  });
38422
38422
 
@@ -48283,6 +48283,7 @@ function frozenProfile(input) {
48283
48283
  Object.freeze(input.buildRecovery);
48284
48284
  Object.freeze(input.sampling);
48285
48285
  Object.freeze(input.compaction);
48286
+ if (input.stream) Object.freeze(input.stream);
48286
48287
  return Object.freeze(input);
48287
48288
  }
48288
48289
  function resolveHarnessProfile(model, providerId) {
@@ -48355,6 +48356,18 @@ var init_capabilities = __esm({
48355
48356
  buildRecovery: { forceToolChoice: true, maxForcedTurns: 1 },
48356
48357
  sampling: { temperature: 0.7 },
48357
48358
  compaction: { ...SHARED_COMPACTION },
48359
+ // Copied from Grok Build (`~/.grok/config.toml` + user-guide):
48360
+ // inference_idle_timeout_secs = 600
48361
+ // max_retries = 8
48362
+ // xAI SDK timeout = 3600s on reasoning models
48363
+ // grok-4.6 xhigh reasons with hidden tokens + SSE keep-alives and no
48364
+ // content for minutes; a 5-min useful-token idle kills BUILD.
48365
+ stream: {
48366
+ idleMs: 6e5,
48367
+ firstTokenIdleMs: 6e5,
48368
+ maxMs: 36e5,
48369
+ maxRetries: 8
48370
+ },
48358
48371
  profile: "grok"
48359
48372
  });
48360
48373
  MINIMAX_M3_CAPS = frozenProfile({
@@ -50979,18 +50992,40 @@ var openai_compatible_exports = {};
50979
50992
  __export(openai_compatible_exports, {
50980
50993
  PROVIDER_CONNECT_TIMEOUT_MS: () => PROVIDER_CONNECT_TIMEOUT_MS,
50981
50994
  PROVIDER_ENDPOINTS: () => PROVIDER_ENDPOINTS,
50995
+ PROVIDER_FIRST_TOKEN_IDLE_MS: () => PROVIDER_FIRST_TOKEN_IDLE_MS,
50982
50996
  PROVIDER_STREAM_IDLE_MS: () => PROVIDER_STREAM_IDLE_MS,
50983
50997
  PROVIDER_STREAM_MAX_MS: () => PROVIDER_STREAM_MAX_MS,
50984
50998
  dataUriFromImage: () => dataUriFromImage,
50999
+ glmModelLooksVision: () => glmModelLooksVision,
51000
+ isGlmCodingEndpoint: () => isGlmCodingEndpoint,
51001
+ isTextOnlyContentRejection: () => isTextOnlyContentRejection,
50985
51002
  modelSupportsVision: () => modelSupportsVision,
50986
51003
  openaiCompatibleProvider: () => openaiCompatibleProvider,
50987
51004
  parseCachedPromptTokens: () => parseCachedPromptTokens,
50988
51005
  providerConfigFor: () => providerConfigFor,
50989
51006
  providerFromEnv: () => providerFromEnv,
50990
51007
  readChunkWithTimeout: () => readChunkWithTimeout,
51008
+ resetTextOnlyVisionMemory: () => resetTextOnlyVisionMemory,
50991
51009
  resolveActiveProvider: () => resolveActiveProvider2,
50992
- resolveBaseUrl: () => resolveBaseUrl
50993
- });
51010
+ resolveBaseUrl: () => resolveBaseUrl,
51011
+ resolveStreamTimeouts: () => resolveStreamTimeouts
51012
+ });
51013
+ function resolveStreamTimeouts(capabilities) {
51014
+ const envIdle = process.env.ZELARI_PROVIDER_STREAM_IDLE_MS ?? process.env.ZELARI_PROVIDER_TIMEOUT_MS;
51015
+ const envFirst = process.env.ZELARI_PROVIDER_FIRST_TOKEN_IDLE_MS;
51016
+ const envMax = process.env.ZELARI_PROVIDER_STREAM_MAX_MS;
51017
+ const envRetries = process.env.ZELARI_PROVIDER_MAX_RETRIES;
51018
+ const idleMs = envIdle ? PROVIDER_STREAM_IDLE_MS : capabilities.stream?.idleMs ?? PROVIDER_STREAM_IDLE_MS;
51019
+ const firstRaw = envFirst ? PROVIDER_FIRST_TOKEN_IDLE_MS : capabilities.stream?.firstTokenIdleMs ?? PROVIDER_FIRST_TOKEN_IDLE_MS;
51020
+ const maxMs = envMax ? PROVIDER_STREAM_MAX_MS : capabilities.stream?.maxMs ?? PROVIDER_STREAM_MAX_MS;
51021
+ const maxRetries = envRetries ? MAX_RETRIES : capabilities.stream?.maxRetries ?? MAX_RETRIES;
51022
+ return {
51023
+ idleMs,
51024
+ firstTokenIdleMs: Math.max(firstRaw, idleMs),
51025
+ maxMs,
51026
+ maxRetries
51027
+ };
51028
+ }
50994
51029
  function abortableSleep(ms, signal) {
50995
51030
  return new Promise((resolve9) => {
50996
51031
  if (signal?.aborted) return resolve9();
@@ -51009,6 +51044,12 @@ function isTimeoutAbortMessage(msg) {
51009
51044
  const m = msg.toLowerCase();
51010
51045
  return m.includes("aborted due to timeout") || m.includes("timeout") || m.includes("the operation was aborted");
51011
51046
  }
51047
+ function formatStreamIdleError(elapsedMs, budgetMs, kind2) {
51048
+ const elapsedS = Math.max(1, Math.round(elapsedMs / 1e3));
51049
+ const budgetS = Math.max(1, Math.round(budgetMs / 1e3));
51050
+ const why = kind2 === "keep-alive" ? "no content tokens \u2014 keep-alive frames don't count" : "no tokens";
51051
+ return `Provider stream idle for ${elapsedS}s of ${budgetS}s (${why}). The model/gateway stalled \u2014 try again or switch model. Override with ZELARI_PROVIDER_STREAM_IDLE_MS or ZELARI_PROVIDER_FIRST_TOKEN_IDLE_MS.`;
51052
+ }
51012
51053
  async function readChunkWithTimeout(reader, opts) {
51013
51054
  if (opts.signal?.aborted) {
51014
51055
  throw new Error("aborted");
@@ -51022,9 +51063,7 @@ async function readChunkWithTimeout(reader, opts) {
51022
51063
  }
51023
51064
  const idleElapsed = now - opts.lastUsefulAt();
51024
51065
  if (idleElapsed >= opts.idleMs) {
51025
- throw new Error(
51026
- `Provider stream idle for ${Math.round(idleElapsed / 1e3)}s (no content tokens \u2014 keep-alive frames don't count). The model/gateway stalled \u2014 try again or switch model. Override with ZELARI_PROVIDER_STREAM_IDLE_MS.`
51027
- );
51066
+ throw new Error(formatStreamIdleError(idleElapsed, opts.idleMs, "keep-alive"));
51028
51067
  }
51029
51068
  const waitMs = Math.min(opts.idleMs - idleElapsed, remaining);
51030
51069
  let idleTimer;
@@ -51034,11 +51073,8 @@ async function readChunkWithTimeout(reader, opts) {
51034
51073
  reader.read(),
51035
51074
  new Promise((_, reject) => {
51036
51075
  idleTimer = setTimeout(() => {
51037
- reject(
51038
- new Error(
51039
- `Provider stream idle for ${Math.round(waitMs / 1e3)}s (no tokens). The model/gateway stalled \u2014 try again or switch model. Override with ZELARI_PROVIDER_STREAM_IDLE_MS.`
51040
- )
51041
- );
51076
+ const elapsed = Date.now() - opts.lastUsefulAt();
51077
+ reject(new Error(formatStreamIdleError(elapsed, opts.idleMs, "silence")));
51042
51078
  }, waitMs);
51043
51079
  if (opts.signal) {
51044
51080
  onAbort = () => reject(new Error("aborted"));
@@ -51060,9 +51096,52 @@ function backoffDelay(attempt, retryAfterHeader) {
51060
51096
  }
51061
51097
  return Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CAP_MS);
51062
51098
  }
51063
- function modelSupportsVision(_model) {
51064
- const force = process.env.ZELARI_VISION;
51065
- return !(force === "0" || force === "false" || force === "off");
51099
+ function visionMemoryKey(model, probe) {
51100
+ return `${probe?.providerId ?? ""}::${model}::${probe?.baseUrl ?? ""}`;
51101
+ }
51102
+ function resetTextOnlyVisionMemory() {
51103
+ textOnlyVisionMemory.clear();
51104
+ }
51105
+ function isGlmCodingEndpoint(baseUrl) {
51106
+ if (!baseUrl) return false;
51107
+ return /\/coding(\/|$)/i.test(baseUrl);
51108
+ }
51109
+ function glmModelLooksVision(model) {
51110
+ const n = model.trim().toLowerCase();
51111
+ if (!n) return false;
51112
+ if (/(?:^|[-_/.])(?:vl|vision)(?:[-_/.]|$)/.test(n)) return true;
51113
+ return /^glm[-_.]?[\w.]*v(?:-|$)/.test(n);
51114
+ }
51115
+ function glmModelIsTextOnly(model) {
51116
+ const n = model.trim().toLowerCase();
51117
+ if (!n.startsWith("glm")) return false;
51118
+ return !glmModelLooksVision(n);
51119
+ }
51120
+ function isTextOnlyContentRejection(status, body) {
51121
+ if (status !== 400) return false;
51122
+ if (!/messages\.content\.type/i.test(body)) return false;
51123
+ return /allowed values/i.test(body) || /\[\s*['"]text['"]\s*\]/.test(body) || /取值范围/.test(body) || /is invalid/i.test(body);
51124
+ }
51125
+ function messagesHaveImageUrl(messages) {
51126
+ for (const m of messages) {
51127
+ const content = m.content;
51128
+ if (!Array.isArray(content)) continue;
51129
+ for (const part of content) {
51130
+ if (part && typeof part === "object" && part.type === "image_url") {
51131
+ return true;
51132
+ }
51133
+ }
51134
+ }
51135
+ return false;
51136
+ }
51137
+ function modelSupportsVision(model, probe) {
51138
+ const force = (process.env.ZELARI_VISION ?? "").trim().toLowerCase();
51139
+ if (force === "0" || force === "false" || force === "off") return false;
51140
+ if (force === "1" || force === "true" || force === "on") return true;
51141
+ if (textOnlyVisionMemory.has(visionMemoryKey(model, probe))) return false;
51142
+ if (isGlmCodingEndpoint(probe?.baseUrl)) return false;
51143
+ if (glmModelIsTextOnly(model)) return false;
51144
+ return true;
51066
51145
  }
51067
51146
  function dataUriFromImage(img) {
51068
51147
  return `data:${img.mime};base64,${img.dataBase64}`;
@@ -51182,29 +51261,38 @@ function positiveEnvInt(name) {
51182
51261
  function openaiCompatibleProvider(config2) {
51183
51262
  return async function* (params) {
51184
51263
  const capabilities = capabilitiesFor(params.model, config2.providerId);
51185
- const vision = modelSupportsVision(params.model);
51264
+ const streamTimeouts = resolveStreamTimeouts(capabilities);
51265
+ const visionProbe = {
51266
+ providerId: config2.providerId,
51267
+ baseUrl: config2.baseUrl
51268
+ };
51269
+ let vision = modelSupportsVision(params.model, visionProbe);
51186
51270
  const msgsIn = params.messages;
51187
- let toolRunImages = [];
51188
- const messages = msgsIn.flatMap((m, i) => {
51189
- const cacheable = !(m.role === "user" && m.images && m.images.length > 0);
51190
- if (cacheable) {
51191
- const cached2 = messageMappingCache.get(m);
51192
- if (cached2) return [cached2];
51193
- }
51194
- const mapped = mapAgentMessage(m, vision);
51195
- if (cacheable) messageMappingCache.set(m, mapped);
51196
- if (m.role === "tool") {
51197
- if (m.images && m.images.length > 0) toolRunImages.push(...m.images);
51198
- const next = msgsIn[i + 1];
51199
- const runEnds = !next || next.role !== "tool";
51200
- if (runEnds && vision && toolRunImages.length > 0) {
51201
- const followUp = imagesFollowUpMessage(toolRunImages);
51202
- toolRunImages = [];
51203
- return [mapped, followUp];
51204
- }
51205
- }
51206
- return [mapped];
51207
- });
51271
+ const wireMessages = (visionFlag) => {
51272
+ let toolRunImages = [];
51273
+ return msgsIn.flatMap((m, i) => {
51274
+ const cacheable = visionFlag && !(m.role === "user" && m.images && m.images.length > 0);
51275
+ if (cacheable) {
51276
+ const cached2 = messageMappingCache.get(m);
51277
+ if (cached2) return [cached2];
51278
+ }
51279
+ const mapped = mapAgentMessage(m, visionFlag);
51280
+ if (cacheable) messageMappingCache.set(m, mapped);
51281
+ if (m.role === "tool") {
51282
+ if (m.images && m.images.length > 0) toolRunImages.push(...m.images);
51283
+ const next = msgsIn[i + 1];
51284
+ const runEnds = !next || next.role !== "tool";
51285
+ if (runEnds && visionFlag && toolRunImages.length > 0) {
51286
+ const followUp = imagesFollowUpMessage(toolRunImages);
51287
+ toolRunImages = [];
51288
+ return [mapped, followUp];
51289
+ }
51290
+ if (runEnds) toolRunImages = [];
51291
+ }
51292
+ return [mapped];
51293
+ });
51294
+ };
51295
+ let messages = wireMessages(vision);
51208
51296
  const generation = params.generation;
51209
51297
  const body = {
51210
51298
  // Use `params.model` (per-call override from AgentHarness, e.g. for
@@ -51254,6 +51342,7 @@ function openaiCompatibleProvider(config2) {
51254
51342
  const recoveryAttempt = generation?.recoveryAttempt ?? 1;
51255
51343
  const forceRecoveryTool = generation?.toolChoice === "required" && capabilities.buildRecovery.forceToolChoice && recoveryAttempt <= capabilities.buildRecovery.maxForcedTurns;
51256
51344
  body.tool_choice = forceRecoveryTool ? "required" : "auto";
51345
+ if (config2.providerId === "glm") body.tool_stream = true;
51257
51346
  }
51258
51347
  const headers3 = {
51259
51348
  "Content-Type": "application/json",
@@ -51268,7 +51357,7 @@ function openaiCompatibleProvider(config2) {
51268
51357
  let response;
51269
51358
  let lastErrText = "";
51270
51359
  let lastStatus = 0;
51271
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
51360
+ for (let attempt = 0; attempt <= streamTimeouts.maxRetries; attempt += 1) {
51272
51361
  if (params.signal?.aborted) {
51273
51362
  yield { kind: "error", message: "aborted" };
51274
51363
  return;
@@ -51309,7 +51398,7 @@ function openaiCompatibleProvider(config2) {
51309
51398
  yield { kind: "error", message: "aborted" };
51310
51399
  return;
51311
51400
  }
51312
- const maxAttempts = isTimeoutAbortMessage(lastErrText) ? Math.min(1, MAX_RETRIES) : MAX_RETRIES;
51401
+ const maxAttempts = isTimeoutAbortMessage(lastErrText) ? Math.min(1, streamTimeouts.maxRetries) : streamTimeouts.maxRetries;
51313
51402
  if (attempt < maxAttempts) {
51314
51403
  await abortableSleep(backoffDelay(attempt, null), params.signal);
51315
51404
  continue;
@@ -51320,7 +51409,14 @@ function openaiCompatibleProvider(config2) {
51320
51409
  if (response.ok && response.body) break;
51321
51410
  lastStatus = response.status;
51322
51411
  lastErrText = await response.text().catch(() => "");
51323
- if (!RETRYABLE_STATUSES.has(response.status) || attempt >= MAX_RETRIES) break;
51412
+ if (vision && isTextOnlyContentRejection(lastStatus, lastErrText) && messagesHaveImageUrl(messages)) {
51413
+ textOnlyVisionMemory.add(visionMemoryKey(params.model, visionProbe));
51414
+ vision = false;
51415
+ messages = wireMessages(false);
51416
+ body.messages = messages;
51417
+ continue;
51418
+ }
51419
+ if (!RETRYABLE_STATUSES.has(response.status) || attempt >= streamTimeouts.maxRetries) break;
51324
51420
  const retryAfter = response.headers.get("retry-after");
51325
51421
  await abortableSleep(backoffDelay(attempt, retryAfter), params.signal);
51326
51422
  }
@@ -51366,17 +51462,19 @@ function openaiCompatibleProvider(config2) {
51366
51462
  }
51367
51463
  toolCallAccumulator.clear();
51368
51464
  };
51369
- const streamDeadline = Date.now() + PROVIDER_STREAM_MAX_MS;
51465
+ const streamDeadline = Date.now() + streamTimeouts.maxMs;
51370
51466
  let lastUsefulAt = Date.now();
51467
+ let emittedUseful = false;
51371
51468
  const markUseful = () => {
51372
51469
  lastUsefulAt = Date.now();
51470
+ emittedUseful = true;
51373
51471
  };
51374
51472
  try {
51375
51473
  while (true) {
51376
51474
  let chunk;
51377
51475
  try {
51378
51476
  chunk = await readChunkWithTimeout(reader, {
51379
- idleMs: PROVIDER_STREAM_IDLE_MS,
51477
+ idleMs: emittedUseful ? streamTimeouts.idleMs : streamTimeouts.firstTokenIdleMs,
51380
51478
  deadlineMs: streamDeadline,
51381
51479
  signal: params.signal,
51382
51480
  lastUsefulAt: () => lastUsefulAt
@@ -51431,17 +51529,33 @@ function openaiCompatibleProvider(config2) {
51431
51529
  }
51432
51530
  };
51433
51531
  }
51434
- if (typeof delta?.content === "string" && delta.content.length > 0) {
51532
+ const content = delta?.content;
51533
+ if (typeof content === "string" && content.length > 0) {
51435
51534
  markUseful();
51436
- yield { kind: "text", delta: delta.content };
51535
+ yield { kind: "text", delta: content };
51536
+ } else if (Array.isArray(content)) {
51537
+ for (const part of content) {
51538
+ if (!part || typeof part !== "object") continue;
51539
+ const p3 = part;
51540
+ const text = typeof p3.text === "string" ? p3.text : "";
51541
+ if (!text) continue;
51542
+ markUseful();
51543
+ const partType = typeof p3.type === "string" ? p3.type : "text";
51544
+ yield {
51545
+ kind: partType === "reasoning" || partType === "thinking" ? "thinking" : "text",
51546
+ delta: text
51547
+ };
51548
+ }
51437
51549
  }
51438
- const reasoning = delta?.reasoning_content ?? delta?.reasoning;
51439
- if (typeof reasoning === "string" && reasoning.length > 0) {
51550
+ const reasoningRaw = delta?.reasoning_content ?? delta?.reasoning ?? delta?.thinking;
51551
+ const reasoning = typeof reasoningRaw === "string" ? reasoningRaw : reasoningRaw && typeof reasoningRaw === "object" && typeof reasoningRaw.text === "string" ? reasoningRaw.text : reasoningRaw && typeof reasoningRaw === "object" && typeof reasoningRaw.content === "string" ? reasoningRaw.content : "";
51552
+ if (reasoning.length > 0) {
51440
51553
  markUseful();
51441
51554
  yield { kind: "thinking", delta: reasoning };
51442
51555
  }
51443
51556
  const details = delta?.reasoning_details;
51444
- if (Array.isArray(details)) {
51557
+ if (Array.isArray(details) && details.length > 0) {
51558
+ markUseful();
51445
51559
  for (const d of details) {
51446
51560
  if (!d || typeof d !== "object") continue;
51447
51561
  const t = d.text;
@@ -51461,6 +51575,7 @@ function openaiCompatibleProvider(config2) {
51461
51575
  }
51462
51576
  }
51463
51577
  if (Array.isArray(delta?.tool_calls)) {
51578
+ markUseful();
51464
51579
  for (const tc of delta.tool_calls) {
51465
51580
  const idx = tc.index ?? 0;
51466
51581
  const existing = toolCallAccumulator.get(idx) ?? {
@@ -51527,7 +51642,7 @@ async function providerConfigFor(providerId) {
51527
51642
  ...extraFromStored(providerId)
51528
51643
  };
51529
51644
  }
51530
- var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_CONNECT_TIMEOUT_MS, PROVIDER_STREAM_IDLE_MS, PROVIDER_STREAM_MAX_MS, PROVIDER_ENDPOINTS, messageMappingCache;
51645
+ var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_CONNECT_TIMEOUT_MS, PROVIDER_STREAM_IDLE_MS, PROVIDER_FIRST_TOKEN_IDLE_MS, PROVIDER_STREAM_MAX_MS, textOnlyVisionMemory, PROVIDER_ENDPOINTS, messageMappingCache;
51531
51646
  var init_openai_compatible = __esm({
51532
51647
  "src/cli/provider/openai-compatible.ts"() {
51533
51648
  "use strict";
@@ -51553,11 +51668,17 @@ var init_openai_compatible = __esm({
51553
51668
  const n = raw ? Number.parseInt(raw, 10) : 3e5;
51554
51669
  return Number.isFinite(n) && n >= 15e3 ? n : 3e5;
51555
51670
  })();
51671
+ PROVIDER_FIRST_TOKEN_IDLE_MS = (() => {
51672
+ const raw = process.env.ZELARI_PROVIDER_FIRST_TOKEN_IDLE_MS;
51673
+ const n = raw ? Number.parseInt(raw, 10) : 6e5;
51674
+ return Number.isFinite(n) && n >= 15e3 ? n : 6e5;
51675
+ })();
51556
51676
  PROVIDER_STREAM_MAX_MS = (() => {
51557
51677
  const raw = process.env.ZELARI_PROVIDER_STREAM_MAX_MS;
51558
51678
  const n = raw ? Number.parseInt(raw, 10) : 18e5;
51559
51679
  return Number.isFinite(n) && n >= 6e4 ? n : 18e5;
51560
51680
  })();
51681
+ textOnlyVisionMemory = /* @__PURE__ */ new Set();
51561
51682
  PROVIDER_ENDPOINTS = {
51562
51683
  "openai-compatible": "https://api.x.ai/v1",
51563
51684
  "minimax": "https://api.minimax.io/v1",