nexrall-code 0.5.101 → 0.5.103

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 (2) hide show
  1. package/dist/index.js +195 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -103250,6 +103250,94 @@ var require_audit = __commonJS({
103250
103250
  }
103251
103251
  });
103252
103252
 
103253
+ // ../core/dist/agent/modelCatalogue.js
103254
+ var require_modelCatalogue = __commonJS({
103255
+ "../core/dist/agent/modelCatalogue.js"(exports2) {
103256
+ "use strict";
103257
+ Object.defineProperty(exports2, "__esModule", { value: true });
103258
+ exports2.getModelCatalogue = getModelCatalogue2;
103259
+ exports2.getCachedModelCatalogue = getCachedModelCatalogue;
103260
+ exports2.liveContextWindowFor = liveContextWindowFor;
103261
+ exports2.liveMaxOutputTokensFor = liveMaxOutputTokensFor;
103262
+ exports2.liveSupportsImageInput = liveSupportsImageInput2;
103263
+ exports2.liveSupportsPdfInput = liveSupportsPdfInput2;
103264
+ exports2.liveReasoningEffortStyle = liveReasoningEffortStyle2;
103265
+ exports2.liveVendorFor = liveVendorFor2;
103266
+ exports2.liveLabelFor = liveLabelFor2;
103267
+ exports2.liveCostMultiplierFor = liveCostMultiplierFor2;
103268
+ exports2.liveSelectableModelIds = liveSelectableModelIds2;
103269
+ exports2._resetModelCatalogueCacheForTests = _resetModelCatalogueCacheForTests;
103270
+ var client_1 = require_client();
103271
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
103272
+ var _state2 = null;
103273
+ var _inFlight = null;
103274
+ async function getModelCatalogue2(opts = {}) {
103275
+ const fresh = _state2 && Date.now() - _state2.fetchedAt < CACHE_TTL_MS;
103276
+ if (fresh && !opts.force)
103277
+ return _state2;
103278
+ if (_inFlight)
103279
+ return _inFlight;
103280
+ _inFlight = (async () => {
103281
+ try {
103282
+ const { models, defaultModel } = await (0, client_1.getCodeModels)();
103283
+ if (!models.length)
103284
+ return _state2;
103285
+ const next = { models, defaultModel, fetchedAt: Date.now() };
103286
+ _state2 = next;
103287
+ return next;
103288
+ } catch {
103289
+ return _state2;
103290
+ } finally {
103291
+ _inFlight = null;
103292
+ }
103293
+ })();
103294
+ return _inFlight;
103295
+ }
103296
+ function getCachedModelCatalogue() {
103297
+ return _state2;
103298
+ }
103299
+ function findModel(id) {
103300
+ if (!id || !_state2)
103301
+ return void 0;
103302
+ return _state2.models.find((m2) => m2.id === id);
103303
+ }
103304
+ function liveContextWindowFor(id, fallback) {
103305
+ return findModel(id)?.contextWindow ?? fallback;
103306
+ }
103307
+ function liveMaxOutputTokensFor(id, fallback) {
103308
+ return findModel(id)?.maxOutputTokens ?? fallback;
103309
+ }
103310
+ function liveSupportsImageInput2(id, fallback) {
103311
+ const m2 = findModel(id);
103312
+ return m2 ? m2.supportsImageInput : fallback;
103313
+ }
103314
+ function liveSupportsPdfInput2(id, fallback) {
103315
+ const m2 = findModel(id);
103316
+ return m2 ? m2.supportsPdfInput : fallback;
103317
+ }
103318
+ function liveReasoningEffortStyle2(id) {
103319
+ return findModel(id)?.reasoningEffortStyle;
103320
+ }
103321
+ function liveVendorFor2(id) {
103322
+ return findModel(id)?.vendor;
103323
+ }
103324
+ function liveLabelFor2(id, fallback) {
103325
+ return findModel(id)?.label ?? fallback;
103326
+ }
103327
+ function liveCostMultiplierFor2(id, fallback) {
103328
+ const m2 = findModel(id);
103329
+ return m2 && typeof m2.costMultiplier === "number" ? m2.costMultiplier : fallback;
103330
+ }
103331
+ function liveSelectableModelIds2() {
103332
+ return _state2 ? _state2.models.filter((m2) => m2.available !== false).map((m2) => m2.id) : null;
103333
+ }
103334
+ function _resetModelCatalogueCacheForTests() {
103335
+ _state2 = null;
103336
+ _inFlight = null;
103337
+ }
103338
+ }
103339
+ });
103340
+
103253
103341
  // ../core/dist/agent/loop.js
103254
103342
  var require_loop = __commonJS({
103255
103343
  "../core/dist/agent/loop.js"(exports2) {
@@ -103355,6 +103443,7 @@ var require_loop = __commonJS({
103355
103443
  var audit_1 = require_audit();
103356
103444
  var memory_1 = require_memory();
103357
103445
  var safeSlice_1 = require_safeSlice();
103446
+ var modelCatalogue_1 = require_modelCatalogue();
103358
103447
  var fs9 = __importStar(__require("fs"));
103359
103448
  var path8 = __importStar(__require("path"));
103360
103449
  var child_process_1 = __require("child_process");
@@ -104184,6 +104273,16 @@ ${partial}` : "",
104184
104273
  "gpt-5.4-mini": 272e3,
104185
104274
  "gpt-4.1": 1047576,
104186
104275
  "gpt-4o-mini": 128e3,
104276
+ // GPT-5.6 family: MEASURED 2026-09-03 via a 400 (same technique as
104277
+ // gpt-5.4's 922_000 above) — and it is the EXACT SAME 922,000-token
104278
+ // ceiling on all three sizes. This CONTRADICTS the publicly documented
104279
+ // figure (openai.com/index/gpt-5-6 + OpenRouter's model card both
104280
+ // advertise 1,050,000) — see backend services/providers/modelRegistry.js's
104281
+ // own comment on these rows for the measured 400 body. Guessing 1.05M here
104282
+ // would fire auto-compaction ~12% past the real wall.
104283
+ "gpt-5.6-sol": 922e3,
104284
+ "gpt-5.6-terra": 922e3,
104285
+ "gpt-5.6-luna": 922e3,
104187
104286
  // DeepSeek, by real model id (documented — see backend/services/providers/
104188
104287
  // modelRegistry.js's own TODO(unverified-by-400): DeepSeek accepts an
104189
104288
  // oversized max_completion_tokens without rejecting it, so there was no 400
@@ -104202,7 +104301,8 @@ ${partial}` : "",
104202
104301
  "glm-5.3": 1048576
104203
104302
  };
104204
104303
  function contextWindowFor2(model) {
104205
- return MODEL_CONTEXT_TOKENS[model ?? "turbo"] ?? 128e3;
104304
+ const fallback = MODEL_CONTEXT_TOKENS[model ?? "turbo"] ?? 128e3;
104305
+ return (0, modelCatalogue_1.liveContextWindowFor)(model, fallback);
104206
104306
  }
104207
104307
  function envFraction(name, fallback) {
104208
104308
  const v = Number(process.env[name]);
@@ -104523,7 +104623,7 @@ Continue the work from here.` }] });
104523
104623
  const settings = (0, rules_1.loadSettings)(opts.workDir);
104524
104624
  if (!resolveAutoCompact(void 0, settings.raw))
104525
104625
  return false;
104526
- const contextWindow = MODEL_CONTEXT_TOKENS[opts.model ?? "turbo"] ?? 1e6;
104626
+ const contextWindow = (0, modelCatalogue_1.liveContextWindowFor)(opts.model, MODEL_CONTEXT_TOKENS[opts.model ?? "turbo"] ?? 1e6);
104527
104627
  let bodyBytes = estimateBodyBytes2(messages);
104528
104628
  let tokenGuess = estimateTokensRough2(messages);
104529
104629
  const overPruneThreshold = () => tokenGuess > contextWindow * AUTO_PRUNE_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
@@ -104638,7 +104738,7 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
104638
104738
  const autoContinue = resolveAutoContinue(options.autoContinue, settings.raw);
104639
104739
  const autoCompact = resolveAutoCompact(options.autoCompact, settings.raw);
104640
104740
  const verifyNudgeOn = resolveVerificationNudge(settings.raw);
104641
- const contextWindow = MODEL_CONTEXT_TOKENS[model] ?? 2e5;
104741
+ const contextWindow = (0, modelCatalogue_1.liveContextWindowFor)(model, MODEL_CONTEXT_TOKENS[model] ?? 2e5);
104642
104742
  let lastPromptTokens = 0;
104643
104743
  let compacting = false;
104644
104744
  const hardCap = autoContinue ? Math.max(maxIterations, MAX_ITERATIONS_CEILING) : maxIterations;
@@ -107326,6 +107426,7 @@ var require_dist3 = __commonJS({
107326
107426
  __exportStar(require_client(), exports2);
107327
107427
  __exportStar(require_executor(), exports2);
107328
107428
  __exportStar(require_loop(), exports2);
107429
+ __exportStar(require_modelCatalogue(), exports2);
107329
107430
  __exportStar(require_testIntegrity(), exports2);
107330
107431
  __exportStar(require_editCompleteness(), exports2);
107331
107432
  __exportStar(require_securityLint(), exports2);
@@ -153152,6 +153253,9 @@ var MODEL_LABELS = {
153152
153253
  "claude-opus-5": "Claude Opus 5",
153153
153254
  "claude-fable-5": "Claude Fable 5",
153154
153255
  "claude-haiku-4-5-20251001": "Claude Haiku 4.5",
153256
+ "gpt-5.6-sol": "GPT-5.6 Sol",
153257
+ "gpt-5.6-terra": "GPT-5.6 Terra",
153258
+ "gpt-5.6-luna": "GPT-5.6 Luna",
153155
153259
  "gpt-5.4": "GPT-5.4",
153156
153260
  "gpt-5.4-mini": "GPT-5.4 Mini",
153157
153261
  "gpt-4.1": "GPT-4.1",
@@ -153172,6 +153276,9 @@ var SELECTABLE_MODELS = [
153172
153276
  "claude-sonnet-5",
153173
153277
  "claude-opus-5",
153174
153278
  "claude-fable-5",
153279
+ "gpt-5.6-sol",
153280
+ "gpt-5.6-terra",
153281
+ "gpt-5.6-luna",
153175
153282
  "gpt-5.4",
153176
153283
  "gpt-5.4-mini",
153177
153284
  "gpt-4.1",
@@ -153184,6 +153291,13 @@ var MODEL_COST_MULTIPLIER = {
153184
153291
  "claude-sonnet-5": 1.5,
153185
153292
  "claude-opus-5": 3,
153186
153293
  "claude-fable-5": 5,
153294
+ // GPT-5.6 family (2026-07 launch, Terra/Luna repriced 2026-07-30) — same
153295
+ // relative-to-gpt-4.1/qwen3.7-max (=1x) signal as every other row here,
153296
+ // off backend migration 132's per-1M-token seed prices (Sol $5/$30, Terra
153297
+ // $2/$12, Luna $0.20/$1.20 vs gpt-4.1's $2/$8).
153298
+ "gpt-5.6-sol": 2,
153299
+ "gpt-5.6-terra": 1.2,
153300
+ "gpt-5.6-luna": 0.1,
153187
153301
  "gpt-5.4": 2,
153188
153302
  "gpt-5.4-mini": 0.5,
153189
153303
  "gpt-4.1": 1,
@@ -153197,17 +153311,29 @@ var MODEL_COST_MULTIPLIER = {
153197
153311
  "glm-5.3": 0.6
153198
153312
  };
153199
153313
  function modelWithCostHint(id) {
153200
- const mult = MODEL_COST_MULTIPLIER[id];
153314
+ const mult = (0, import_code_core4.liveCostMultiplierFor)(id, MODEL_COST_MULTIPLIER[id]);
153201
153315
  return mult === void 0 ? id : `${id} (${mult}x)`;
153202
153316
  }
153203
153317
  var NO_VISION_MODELS = /* @__PURE__ */ new Set(["deepseek-v4-pro", "deepseek-v4-flash", "qwen3.7-max", "glm-5.3"]);
153204
153318
  var NO_PDF_MODELS = /* @__PURE__ */ new Set([
153319
+ "gpt-5.6-sol",
153320
+ "gpt-5.6-terra",
153321
+ "gpt-5.6-luna",
153205
153322
  "gpt-5.4",
153206
153323
  "gpt-5.4-mini",
153207
153324
  "gpt-4.1",
153208
153325
  "gpt-4o-mini",
153209
153326
  ...NO_VISION_MODELS
153210
153327
  ]);
153328
+ function needsVisionSidecar(model) {
153329
+ return !(0, import_code_core4.liveSupportsImageInput)(model, !NO_VISION_MODELS.has(model));
153330
+ }
153331
+ function needsPdfSidecar(model) {
153332
+ return !(0, import_code_core4.liveSupportsPdfInput)(model, !NO_PDF_MODELS.has(model));
153333
+ }
153334
+ function selectableModelIds() {
153335
+ return (0, import_code_core4.liveSelectableModelIds)() ?? SELECTABLE_MODELS;
153336
+ }
153211
153337
  function normaliseModelId(model) {
153212
153338
  const raw = (model ?? "").trim();
153213
153339
  if (!raw)
@@ -153215,6 +153341,14 @@ function normaliseModelId(model) {
153215
153341
  return LEGACY_MODEL_ALIASES[raw.toLowerCase()] ?? raw;
153216
153342
  }
153217
153343
  var MODEL_EFFORT_STYLE = {
153344
+ // GPT-5.6 Sol/Terra/Luna hit the SAME "reasoning_effort + tools" 400 as
153345
+ // gpt-5.4/mini below (confirmed by multiple third-party integrations
153346
+ // 2026-07: github.com/BerriAI/litellm#33221, github.com/danny-avila/
153347
+ // LibreChat#14231) — OPENAI_GATED already handles that (skip when tools
153348
+ // are present), so no new style is needed for this family.
153349
+ "gpt-5.6-sol": "openai_gated",
153350
+ "gpt-5.6-terra": "openai_gated",
153351
+ "gpt-5.6-luna": "openai_gated",
153218
153352
  "gpt-5.4": "openai_gated",
153219
153353
  "gpt-5.4-mini": "openai_gated",
153220
153354
  "gpt-4.1": "none",
@@ -153251,8 +153385,21 @@ var EFFORT_STYLE_CONFIG = {
153251
153385
  // No reasoning-effort concept on this model at all (gpt-4.1, gpt-4o-mini).
153252
153386
  none: { levels: ["medium"], names: ["N/A"] }
153253
153387
  };
153388
+ var _BACKEND_STYLE_TO_LOCAL = {
153389
+ openai_gated: "openai_gated",
153390
+ deepseek: "deepseek",
153391
+ qwen_thinking_toggle: "qwen",
153392
+ glm_thinking_level: "glm",
153393
+ none: "none"
153394
+ };
153254
153395
  function effortStyleFor(modelId) {
153255
- return MODEL_EFFORT_STYLE[normaliseModelId(modelId)] ?? "anthropic";
153396
+ const id = normaliseModelId(modelId);
153397
+ if ((0, import_code_core4.liveVendorFor)(id) === "Anthropic")
153398
+ return "anthropic";
153399
+ const liveStyle = (0, import_code_core4.liveReasoningEffortStyle)(id);
153400
+ if (liveStyle && _BACKEND_STYLE_TO_LOCAL[liveStyle])
153401
+ return _BACKEND_STYLE_TO_LOCAL[liveStyle];
153402
+ return MODEL_EFFORT_STYLE[id] ?? "anthropic";
153256
153403
  }
153257
153404
  function effortConfigFor(modelId) {
153258
153405
  return EFFORT_STYLE_CONFIG[effortStyleFor(modelId)];
@@ -153269,7 +153416,7 @@ function defaultEffortForModel(modelId) {
153269
153416
  }
153270
153417
  function resolveModelLabel(alias) {
153271
153418
  const id = normaliseModelId(alias);
153272
- return MODEL_LABELS[id] ?? id;
153419
+ return (0, import_code_core4.liveLabelFor)(id, MODEL_LABELS[id] ?? id);
153273
153420
  }
153274
153421
  function tryExec(cmd, cwd2) {
153275
153422
  try {
@@ -153717,6 +153864,41 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env4
153717
153864
  resultText += text;
153718
153865
  emit({ type: "text", text });
153719
153866
  },
153867
+ // ── Reasoning-phase liveness ──────────────────────────────────────
153868
+ // These used to be UNWIRED here while runTurn() (interactive) wired all
153869
+ // three, and the asymmetry was not cosmetic: on a reasoning model at high
153870
+ // effort the thinking phase emits no text and no tool calls, so a headless
153871
+ // consumer saw ZERO events for the entire phase and could not distinguish
153872
+ // "actively reasoning" from "process wedged".
153873
+ //
153874
+ // Measured on a Terminal-Bench 4.0 trial (2026-08-30, deepseek-v4-pro
153875
+ // --effort max): nex-output.jsonl sat at 4 lines for 65 MINUTES while
153876
+ // tcpdump inside the container's netns showed ~25 packets/s still flowing
153877
+ // and the turn ultimately reported 310,872 output tokens. Nothing was
153878
+ // actually wrong — but every signal available to the harness (log line
153879
+ // count, file mtime) said "hung", and the socket/CPU forensics needed to
153880
+ // prove otherwise are not something a CI wrapper can do.
153881
+ //
153882
+ // `thinking_progress` is the load-bearing one: it carries the backend's
153883
+ // cumulative output-token count (routes/code.js's sendProgress, already
153884
+ // throttled to <=5/s server-side, so this cannot flood the log) and fires
153885
+ // DURING the phase. That makes it a real heartbeat.
153886
+ onThinkingProgress: (tokens) => {
153887
+ emit({ type: "thinking_progress", tokens });
153888
+ },
153889
+ // Fires once per turn at message_complete with the full reasoning text.
153890
+ // Typed `thinking` deliberately: the bench's ATIF converter (atif.py)
153891
+ // already routes exactly this event type into the trajectory's reasoning
153892
+ // buffer, so wiring it here also fills in reasoning that was previously
153893
+ // dropped on the floor for every headless run.
153894
+ onThinking: (text) => {
153895
+ emit({ type: "thinking", text });
153896
+ },
153897
+ // onThinkingDelta is intentionally NOT wired. Its chunks concatenate to the
153898
+ // same string `onThinking` emits in full above, so emitting both would
153899
+ // duplicate the entire reasoning trace — on the 310k-token turn measured
153900
+ // above that is megabytes of redundant NDJSON — while adding no liveness
153901
+ // signal `thinking_progress` does not already provide.
153720
153902
  // System notice (mid-run auto-prune/auto-compact) — emit as its own event
153721
153903
  // type instead of falling through to onText, so a stream-json consumer
153722
153904
  // doesn't see compaction housekeeping text mixed into the model's `text`
@@ -153862,6 +154044,8 @@ async function startChatSession(options) {
153862
154044
  const headless = options.outputFormat === "json" || options.outputFormat === "stream-json";
153863
154045
  const { workDir } = options;
153864
154046
  let modelAlias = options.model;
154047
+ void (0, import_code_core4.getModelCatalogue)().catch(() => {
154048
+ });
153865
154049
  const canPrompt = !headless && !options.prompt && !options.stdinText && process.stdin.isTTY === true && process.stdout.isTTY === true;
153866
154050
  if (!trustGrantedByEnv()) {
153867
154051
  if (!canPrompt) {
@@ -154403,10 +154587,11 @@ ${dirList}`;
154403
154587
  return;
154404
154588
  case "/model": {
154405
154589
  const requested = arg.trim();
154590
+ const models = selectableModelIds();
154406
154591
  if (!requested) {
154407
154592
  console.log(source_default.dim(` Current: ${source_default.cyan(resolveModelLabel(modelAlias))}`));
154408
- console.log(source_default.dim(` Options: ${SELECTABLE_MODELS.map(modelWithCostHint).join(" \xB7 ")}`));
154409
- } else if (SELECTABLE_MODELS.includes(normaliseModelId(requested))) {
154593
+ console.log(source_default.dim(` Options: ${models.map(modelWithCostHint).join(" \xB7 ")}`));
154594
+ } else if (models.includes(normaliseModelId(requested))) {
154410
154595
  modelAlias = normaliseModelId(requested);
154411
154596
  const clamped = clampEffortForModel(effortLevel, modelAlias);
154412
154597
  const effortChanged = clamped !== effortLevel;
@@ -154416,7 +154601,7 @@ ${dirList}`;
154416
154601
  console.log(source_default.dim(` Effort reset \u2192 ${effortLevel} (previous level not available on this model)`));
154417
154602
  }
154418
154603
  } else {
154419
- console.log(source_default.red(` Unknown: ${requested}. Options: ${SELECTABLE_MODELS.join(", ")}`));
154604
+ console.log(source_default.red(` Unknown: ${requested}. Options: ${models.join(", ")}`));
154420
154605
  }
154421
154606
  rl.prompt();
154422
154607
  return;
@@ -154545,7 +154730,7 @@ ${content}
154545
154730
  const label = caption ? `[Attached: ${rel}]
154546
154731
  ${caption}` : `[Attached: ${rel}]`;
154547
154732
  const content = [{ type: "text", text: label }];
154548
- const needsSidecar = isPdf ? NO_PDF_MODELS.has(model) : NO_VISION_MODELS.has(model);
154733
+ const needsSidecar = isPdf ? needsPdfSidecar(model) : needsVisionSidecar(model);
154549
154734
  if (needsSidecar) {
154550
154735
  console.log(source_default.dim(` ${resolveModelLabel(modelAlias)} can't read ${isPdf ? "PDFs" : "images"} directly \u2014 describing ${rel} with Claude Sonnet 5 first\u2026`));
154551
154736
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.101",
3
+ "version": "0.5.103",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",