@zhiman_innies/innies-codex 0.122.50 → 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.
Files changed (2) hide show
  1. package/bin/innies-config.js +125 -25
  2. package/package.json +5 -5
@@ -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)
@@ -492,11 +531,16 @@ function normalizeManagedProviderBlocks(contents) {
492
531
  // NOTE: we deliberately do NOT auto-inject `base_url` or `env_key`
493
532
  // into user blocks. The user must configure these themselves (either
494
533
  // in the TOML block or via the corresponding env var: ZHIMAN_API_KEY
495
- // / DASHSCOPE_API_KEY / ZHIMAN_35B_API_KEY). The Rust builtin
496
- // providers fall back to the env var when `env_key` is absent from
497
- // the TOML, so the only thing that is strictly required from the
498
- // user is `base_url` (or the ZHIMAN_BASE_URL / DASHSCOPE_BASE_URL env
499
- // var). We only normalize `wire_api` to a known-good value.
534
+ // / DASHSCOPE_API_KEY). The Rust builtin providers fall back to the
535
+ // env var when `env_key` is absent from the TOML, so the only thing
536
+ // that is strictly required from the user is `base_url` (or the
537
+ // ZHIMAN_BASE_URL / DASHSCOPE_BASE_URL env var). We only normalize
538
+ // `wire_api` to a known-good value. (Historically the comment also
539
+ // mentioned `ZHIMAN_35B_API_KEY`, but the Rust factory at
540
+ // `codex-rs/model-provider-info/src/lib.rs` hardcodes a single
541
+ // `ZHIMAN_API_KEY` env-var name for ALL `zhiman_*` provider blocks —
542
+ // there is no per-model differentiation. Users who need separate
543
+ // env vars must override `env_key` in the TOML block directly.)
500
544
  let updated = normalizeProviderBlock(contents, {
501
545
  providerHeader: ZHIMAN_35B_PROVIDER_HEADER,
502
546
  });
@@ -610,22 +654,53 @@ function normalizeProviderBlock(contents, provider) {
610
654
  }
611
655
 
612
656
  function defaultZhiman35bProviderBlock() {
613
- // The freshly-generated provider block intentionally does NOT include
614
- // `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:
615
660
  //
616
661
  // base_url = "http://your-private-deployment/v1"
617
- // env_key = "ZHIMAN_35B_API_KEY" # or any other env var name
662
+ // env_key = "ZHIMAN_API_KEY" # or any other env var name
618
663
  //
619
- // (or set the ZHIMAN_BASE_URL and ZHIMAN_35B_API_KEY environment
664
+ // (or set the ZHIMAN_BASE_URL and ZHIMAN_API_KEY environment
620
665
  // variables). Generating a default `base_url` here would make the
621
666
  // binary call a real network endpoint on first run, which is
622
667
  // explicitly disallowed by the innies-codex install contract.
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
+ //
682
+ // NOTE: the env-var name is `ZHIMAN_API_KEY` (NOT `ZHIMAN_35B_API_KEY`)
683
+ // because the Rust builtin factory at
684
+ // `codex-rs/model-provider-info/src/lib.rs` hardcodes that single
685
+ // env-var name for ALL `zhiman_*` provider blocks — there is no
686
+ // per-model env-var differentiation. If you need different keys for
687
+ // the 27B vs 35B deployments, override `env_key` in the TOML block
688
+ // directly (e.g. `env_key = "ZHIMAN_35B_API_KEY"`) instead of relying
689
+ // on the factory default.
623
690
  return [
624
691
  ZHIMAN_35B_PROVIDER_HEADER,
625
692
  'name = "zhiman_35b"',
626
693
  `# base_url = "http://your-private-deployment/v1" # FILL IN: private vLLM / OpenAI-compatible endpoint`,
627
- `# env_key = "ZHIMAN_35B_API_KEY" # FILL IN: name of the env var holding your API key`,
694
+ `# env_key = "ZHIMAN_API_KEY" # FILL IN: name of the env var holding your API key`,
628
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`,
629
704
  ].join("\n");
630
705
  }
631
706
 
@@ -634,14 +709,25 @@ function defaultZhiman27bProviderBlock() {
634
709
  // deployment. Same install-contract rule: do NOT prefill `base_url` or
635
710
  // `env_key` — the user must configure both, otherwise the binary
636
711
  // would call a real network endpoint on first run. Distinct provider
637
- // name (`zhiman_27b` vs `zhiman_35b`) and env var so the two private
638
- // deployments can be configured independently.
712
+ // name (`zhiman_27b` vs `zhiman_35b`) so the two private deployments
713
+ // can be configured independently. NOTE: the env-var name is still
714
+ // `ZHIMAN_API_KEY` (NOT `ZHIMAN_27B_API_KEY`) because the factory
715
+ // hardcodes that single name for ALL zhiman_* blocks — see the
716
+ // matching note in `defaultZhiman35bProviderBlock`.
639
717
  return [
640
718
  ZHIMAN_27B_PROVIDER_HEADER,
641
719
  'name = "zhiman_27b"',
642
720
  `# base_url = "http://your-private-deployment/v1" # FILL IN: private vLLM / OpenAI-compatible endpoint`,
643
- `# env_key = "ZHIMAN_27B_API_KEY" # FILL IN: name of the env var holding your API key`,
721
+ `# env_key = "ZHIMAN_API_KEY" # FILL IN: name of the env var holding your API key`,
644
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`,
645
731
  ].join("\n");
646
732
  }
647
733
 
@@ -653,11 +739,25 @@ function defaultDashscopeProviderBlock() {
653
739
  // either uncomment this line and set DASHSCOPE_API_KEY, or leave
654
740
  // it commented and set DASHSCOPE_API_KEY as a regular env var, both
655
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.
656
748
  return [
657
749
  DASHSCOPE_PROVIDER_HEADER,
658
750
  'name = "DashScope"',
659
751
  `# base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" # FILL IN: DashScope OpenAI-compatible endpoint`,
660
752
  `# env_key = "DASHSCOPE_API_KEY" # FILL IN: name of the env var holding your DashScope API key`,
661
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`,
662
762
  ].join("\n");
663
763
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhiman_innies/innies-codex",
3
- "version": "0.122.50",
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.50-darwin-x64",
27
- "@zhiman_innies/innies-codex-darwin-arm64": "0.122.50-darwin-arm64",
28
- "@zhiman_innies/innies-codex-win32-x64": "0.122.50-win32-x64",
29
- "@zhiman_innies/innies-codex-win32-arm64": "0.122.50-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
  }