@zhiman_innies/innies-codex 0.122.51 → 0.122.54

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.
@@ -21,7 +21,6 @@ const DASHSCOPE_MODEL = "qwen3.6-27b";
21
21
  const PRIVATE_27B_MODEL = "qwen36_27b";
22
22
  const PRIVATE_27B_PROVIDER = "zhiman_27b";
23
23
  const QWEN_MODELS = new Set([DEFAULT_MODEL, DASHSCOPE_MODEL, PRIVATE_27B_MODEL]);
24
- const LEGACY_MODELS = new Set(["qwen-plus", "qwen-plus-latest"]);
25
24
  const DEFAULT_HOME_DIR = ".inniescoder";
26
25
  const DEFAULT_CATALOG_FILENAME = "catalog.json";
27
26
  const DEFAULT_CONFIG_FILENAME = "config.toml";
@@ -237,12 +236,12 @@ function ensureInniesCatalog(catalogPath) {
237
236
  return;
238
237
  }
239
238
 
240
- if (shouldReplaceLegacyCatalog(existingCatalog)) {
239
+ if (catalogShapeIsStale(existingCatalog)) {
241
240
  writeJsonFile(catalogPath, defaultCatalog);
242
241
  }
243
242
  }
244
243
 
245
- function shouldReplaceLegacyCatalog(catalog) {
244
+ function catalogShapeIsStale(catalog) {
246
245
  if (!catalog || !Array.isArray(catalog.models)) {
247
246
  return false;
248
247
  }
@@ -278,7 +277,7 @@ function shouldReplaceLegacyCatalog(catalog) {
278
277
  return true;
279
278
  }
280
279
 
281
- return slugs.some((slug) => LEGACY_MODELS.has(slug));
280
+ return false;
282
281
  }
283
282
 
284
283
  function ensureInniesConfig(configPath, catalogPath, state) {
@@ -300,15 +299,33 @@ function defaultInniesConfig(catalogPath, managedDefault) {
300
299
  const lines = managedDefault
301
300
  ? managedDefaultLines(catalogPath, managedDefault)
302
301
  : [`model_catalog_json = ${JSON.stringify(catalogPath)}`];
302
+ // The documentation comment block (model_reasoning_effort /
303
+ // model_verbosity / etc.) is emitted AFTER the active root settings
304
+ // — this matches the byte ordering that `normalizeInniesConfig`
305
+ // produces on re-init (which prepends active managed lines and
306
+ // appends the rest as unmanaged content, including any preserved
307
+ // comments). With this ordering, init 1's output is bytewise equal
308
+ // to init 2+'s normalized output, preserving idempotency. The
309
+ // comments are preserved across re-inits because
310
+ // `stripManagedRootSettings` only strips active `key = ...` lines,
311
+ // not commented ones.
312
+ const rootDocumentation = [
313
+ `# Managed defaults — override by uncommenting and editing.`,
314
+ `# innies-config.js will keep these in sync on every \`innies\` run,`,
315
+ `# but will NEVER overwrite a user-supplied value.`,
316
+ `# model_reasoning_effort = "low" # thinking toggle: low | medium | high | xhigh (or omit for model default)`,
317
+ `# model_verbosity = "low" # optional: GPT-5 only (low | medium | high)`,
318
+ ];
303
319
  return [
304
320
  ...lines,
305
321
  "",
322
+ ...rootDocumentation,
323
+ "",
306
324
  defaultZhiman35bProviderBlock(),
307
325
  "",
308
326
  defaultZhiman27bProviderBlock(),
309
327
  "",
310
328
  defaultDashscopeProviderBlock(),
311
- "",
312
329
  ].join("\n");
313
330
  }
314
331
 
@@ -337,19 +354,26 @@ function normalizeInniesConfig(contents, catalogPath, state) {
337
354
 
338
355
  updated = normalizeManagedProviderBlocks(stripReservedProviderBlocks(updated));
339
356
  if (!updated.includes(ZHIMAN_35B_PROVIDER_HEADER)) {
340
- updated = `${updated.trimEnd()}\n\n${defaultZhiman35bProviderBlock()}\n`;
357
+ updated = `${updated.trimEnd()}\n\n${defaultZhiman35bProviderBlock()}`;
341
358
  }
342
359
  if (!updated.includes(ZHIMAN_27B_PROVIDER_HEADER)) {
343
- updated = `${updated.trimEnd()}\n\n${defaultZhiman27bProviderBlock()}\n`;
360
+ updated = `${updated.trimEnd()}\n\n${defaultZhiman27bProviderBlock()}`;
344
361
  }
345
362
  if (!updated.includes(DASHSCOPE_PROVIDER_HEADER)) {
346
- updated = `${updated.trimEnd()}\n\n${defaultDashscopeProviderBlock()}\n`;
363
+ updated = `${updated.trimEnd()}\n\n${defaultDashscopeProviderBlock()}`;
347
364
  }
348
365
 
349
366
  return updated;
350
367
  }
351
368
 
352
369
  function managedDefaultLines(catalogPath, managedDefault) {
370
+ // IMPORTANT: only ACTIVE settings belong here. Comment lines
371
+ // (`# model_reasoning_effort = "..."`) belong in
372
+ // `defaultInniesConfig` (one-time generation) — they would
373
+ // otherwise be PREPENDED on every re-init and accumulate forever,
374
+ // breaking bytewise idempotency. `stripManagedRootSettings` only
375
+ // strips active `key = ...` lines (not commented ones), so anything
376
+ // we emit here MUST be active (no leading `#`).
353
377
  return [
354
378
  `model_provider = ${JSON.stringify(managedDefault.provider)}`,
355
379
  `model = ${JSON.stringify(managedDefault.model)}`,
@@ -358,7 +382,9 @@ function managedDefaultLines(catalogPath, managedDefault) {
358
382
  }
359
383
 
360
384
  function catalogOnlyManagedLines(catalogPath) {
361
- return [`model_catalog_json = ${JSON.stringify(catalogPath)}`];
385
+ return [
386
+ `model_catalog_json = ${JSON.stringify(catalogPath)}`,
387
+ ];
362
388
  }
363
389
 
364
390
  function preservedUserManagedLines(contents, catalogPath) {
@@ -424,23 +450,36 @@ function determineModelSelectionState(contents, previousState) {
424
450
  return previousState;
425
451
  }
426
452
 
453
+ // Goal 4: model switching must NOT cause local-config corruption.
454
+ // When the user has actively chosen one of the supported NON-DEFAULT
455
+ // models (DASHSCOPE_MODEL=qwen3.6-27b, PRIVATE_27B_MODEL=qwen36_27b),
456
+ // we lock the state to USER_SELECTED so subsequent `innies` runs
457
+ // never strip-and-rewrite their model_provider/model with the managed
458
+ // default qwen35_35b. The previous behaviour returned
459
+ // `previousState` here, which silently reset the user's selection
460
+ // when innies-state.json was missing or corrupted (e.g. after a
461
+ // version upgrade that lost the state file) — see manual e2e test
462
+ // `state-loss reset` in this commit's changelog.
427
463
  if (
428
464
  currentModel === DASHSCOPE_MODEL &&
429
465
  (currentProvider == null || currentProvider === "dashscope")
430
466
  ) {
431
- return previousState;
467
+ return {
468
+ model_selection_state: MODEL_SELECTION_STATES.USER_SELECTED,
469
+ };
432
470
  }
433
471
 
434
472
  if (
435
473
  currentModel === PRIVATE_27B_MODEL &&
436
474
  (currentProvider == null || currentProvider === PRIVATE_27B_PROVIDER)
437
475
  ) {
438
- return previousState;
476
+ return {
477
+ model_selection_state: MODEL_SELECTION_STATES.USER_SELECTED,
478
+ };
439
479
  }
440
480
 
441
481
  if (
442
482
  currentModel != null &&
443
- !LEGACY_MODELS.has(currentModel) &&
444
483
  (currentModel !== DEFAULT_MODEL || currentProvider !== DEFAULT_PROVIDER) &&
445
484
  (currentModel !== DASHSCOPE_MODEL || currentProvider !== "dashscope") &&
446
485
  (currentModel !== PRIVATE_27B_MODEL || currentProvider !== PRIVATE_27B_PROVIDER)
@@ -615,8 +654,9 @@ function normalizeProviderBlock(contents, provider) {
615
654
  }
616
655
 
617
656
  function defaultZhiman35bProviderBlock() {
618
- // The freshly-generated provider block intentionally does NOT include
619
- // `base_url` or `env_key`. The user must configure both:
657
+ // The freshly-generated provider block intentionally does NOT prefill
658
+ // `base_url` or `env_key` (commented placeholders only). The user must
659
+ // configure both:
620
660
  //
621
661
  // base_url = "http://your-private-deployment/v1"
622
662
  // env_key = "ZHIMAN_API_KEY" # or any other env var name
@@ -626,6 +666,19 @@ function defaultZhiman35bProviderBlock() {
626
666
  // binary call a real network endpoint on first run, which is
627
667
  // explicitly disallowed by the innies-codex install contract.
628
668
  //
669
+ // The block also exposes ALL recognized ModelProviderInfo fields as
670
+ // commented placeholders so the operator can see what knobs are
671
+ // available without reading the upstream schema:
672
+ // - http_headers / env_http_headers: per-request headers (literal
673
+ // or env-var indirection)
674
+ // - query_params: extra URL query params
675
+ // - request_max_retries / stream_max_retries: retry budgets
676
+ // - stream_idle_timeout_ms: per-stream idle cap (defends against
677
+ // hung first-token — see client.rs::ZHIMAN_STREAM_REQUEST_TIMEOUT)
678
+ // - request_body_extras: free-form JSON merged into every
679
+ // chat-completions request body (qwen uses this for the nested
680
+ // `chat_template_kwargs` thinking toggle)
681
+ //
629
682
  // NOTE: the env-var name is `ZHIMAN_API_KEY` (NOT `ZHIMAN_35B_API_KEY`)
630
683
  // because the Rust builtin factory at
631
684
  // `codex-rs/model-provider-info/src/lib.rs` hardcodes that single
@@ -640,6 +693,14 @@ function defaultZhiman35bProviderBlock() {
640
693
  `# base_url = "http://your-private-deployment/v1" # FILL IN: private vLLM / OpenAI-compatible endpoint`,
641
694
  `# env_key = "ZHIMAN_API_KEY" # FILL IN: name of the env var holding your API key`,
642
695
  `wire_api = "${DEFAULT_PROVIDER_WIRE_API}"`,
696
+ `# http_headers = { "X-Custom" = "value" } # optional: literal per-request headers`,
697
+ `# env_http_headers = { "X-Tenant" = "TENANT_ID_ENV" } # optional: headers from env-var indirection`,
698
+ `# query_params = { "api-version" = "2024-10" } # optional: extra URL query params`,
699
+ `# request_max_retries = 4 # optional: HTTP retry budget`,
700
+ `# stream_max_retries = 5 # optional: SSE stream reconnect retry budget`,
701
+ `# stream_idle_timeout_ms = 300000 # optional: idle cap (default 300_000 = 5 min)`,
702
+ `# request_body_extras = { chat_template_kwargs = { enable_thinking = false } } # optional: qwen thinking toggle (private vLLM)`,
703
+ `# experimental_bearer_token = "" # discouraged: prefer env_key`,
643
704
  ].join("\n");
644
705
  }
645
706
 
@@ -659,6 +720,14 @@ function defaultZhiman27bProviderBlock() {
659
720
  `# base_url = "http://your-private-deployment/v1" # FILL IN: private vLLM / OpenAI-compatible endpoint`,
660
721
  `# env_key = "ZHIMAN_API_KEY" # FILL IN: name of the env var holding your API key`,
661
722
  `wire_api = "${DEFAULT_PROVIDER_WIRE_API}"`,
723
+ `# http_headers = { "X-Custom" = "value" } # optional: literal per-request headers`,
724
+ `# env_http_headers = { "X-Tenant" = "TENANT_ID_ENV" } # optional: headers from env-var indirection`,
725
+ `# query_params = { "api-version" = "2024-10" } # optional: extra URL query params`,
726
+ `# request_max_retries = 4 # optional: HTTP retry budget`,
727
+ `# stream_max_retries = 5 # optional: SSE stream reconnect retry budget`,
728
+ `# stream_idle_timeout_ms = 300000 # optional: idle cap (default 300_000 = 5 min)`,
729
+ `# request_body_extras = { chat_template_kwargs = { enable_thinking = false } } # optional: qwen thinking toggle (private vLLM)`,
730
+ `# experimental_bearer_token = "" # discouraged: prefer env_key`,
662
731
  ].join("\n");
663
732
  }
664
733
 
@@ -670,11 +739,25 @@ function defaultDashscopeProviderBlock() {
670
739
  // either uncomment this line and set DASHSCOPE_API_KEY, or leave
671
740
  // it commented and set DASHSCOPE_API_KEY as a regular env var, both
672
741
  // paths converge.
742
+ //
743
+ // DashScope-specific note: the top-level `enable_thinking` flag is
744
+ // the canonical thinking toggle (NOT the nested `chat_template_kwargs`
745
+ // form, which DashScope rejects with HTTP 400). Use
746
+ // `request_body_extras` to inject it if you do not want to set
747
+ // `model_reasoning_effort` at the root.
673
748
  return [
674
749
  DASHSCOPE_PROVIDER_HEADER,
675
750
  'name = "DashScope"',
676
751
  `# base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" # FILL IN: DashScope OpenAI-compatible endpoint`,
677
752
  `# env_key = "DASHSCOPE_API_KEY" # FILL IN: name of the env var holding your DashScope API key`,
678
753
  `wire_api = "${DEFAULT_PROVIDER_WIRE_API}"`,
754
+ `# http_headers = { "X-Custom" = "value" } # optional: literal per-request headers`,
755
+ `# env_http_headers = { "X-Tenant" = "TENANT_ID_ENV" } # optional: headers from env-var indirection`,
756
+ `# query_params = { "api-version" = "2024-10" } # optional: extra URL query params`,
757
+ `# request_max_retries = 4 # optional: HTTP retry budget`,
758
+ `# stream_max_retries = 5 # optional: SSE stream reconnect retry budget`,
759
+ `# stream_idle_timeout_ms = 300000 # optional: idle cap (default 300_000 = 5 min)`,
760
+ `# request_body_extras = { enable_thinking = false } # optional: DashScope thinking toggle (top-level form)`,
761
+ `# experimental_bearer_token = "" # discouraged: prefer env_key`,
679
762
  ].join("\n");
680
763
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhiman_innies/innies-codex",
3
- "version": "0.122.51",
3
+ "version": "0.122.54",
4
4
  "license": "Apache-2.0",
5
5
  "bin": {
6
6
  "innies": "bin/innies.js"
@@ -23,9 +23,9 @@
23
23
  "postinstall": "node bin/innies-init.js"
24
24
  },
25
25
  "optionalDependencies": {
26
- "@zhiman_innies/innies-codex-darwin-x64": "0.122.51-darwin-x64",
27
- "@zhiman_innies/innies-codex-darwin-arm64": "0.122.51-darwin-arm64",
28
- "@zhiman_innies/innies-codex-win32-x64": "0.122.51-win32-x64",
29
- "@zhiman_innies/innies-codex-win32-arm64": "0.122.51-win32-arm64"
26
+ "@zhiman_innies/innies-codex-darwin-x64": "0.122.54-darwin-x64",
27
+ "@zhiman_innies/innies-codex-darwin-arm64": "0.122.54-darwin-arm64",
28
+ "@zhiman_innies/innies-codex-win32-x64": "0.122.54-win32-x64",
29
+ "@zhiman_innies/innies-codex-win32-arm64": "0.122.54-win32-arm64"
30
30
  }
31
31
  }