@cleocode/core 2026.6.14 → 2026.6.17

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 (152) hide show
  1. package/dist/check/pr-gate.d.ts +6 -2
  2. package/dist/check/pr-gate.d.ts.map +1 -1
  3. package/dist/check/pr-gate.js +14 -32
  4. package/dist/check/pr-gate.js.map +1 -1
  5. package/dist/config/config-repair.d.ts +70 -0
  6. package/dist/config/config-repair.d.ts.map +1 -0
  7. package/dist/config/config-repair.js +248 -0
  8. package/dist/config/config-repair.js.map +1 -0
  9. package/dist/config/registry.d.ts.map +1 -1
  10. package/dist/config/registry.js +4 -4
  11. package/dist/config/registry.js.map +1 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +11 -10
  14. package/dist/config.js.map +1 -1
  15. package/dist/docs/export-document.js +58 -16
  16. package/dist/docs/export-document.js.map +2 -2
  17. package/dist/gc/cleanup.d.ts +23 -8
  18. package/dist/gc/cleanup.d.ts.map +1 -1
  19. package/dist/gc/cleanup.js +287 -22
  20. package/dist/gc/cleanup.js.map +1 -1
  21. package/dist/gc/janitor.d.ts +149 -0
  22. package/dist/gc/janitor.d.ts.map +1 -0
  23. package/dist/gc/janitor.js +760 -0
  24. package/dist/gc/janitor.js.map +1 -0
  25. package/dist/internal.d.ts +1 -0
  26. package/dist/internal.d.ts.map +1 -1
  27. package/dist/internal.js +3 -0
  28. package/dist/internal.js.map +1 -1
  29. package/dist/llm/cli-ops.d.ts +31 -4
  30. package/dist/llm/cli-ops.d.ts.map +1 -1
  31. package/dist/llm/cli-ops.js +110 -26
  32. package/dist/llm/cli-ops.js.map +1 -1
  33. package/dist/llm/credential-pool.d.ts +62 -0
  34. package/dist/llm/credential-pool.d.ts.map +1 -1
  35. package/dist/llm/credential-pool.js +161 -0
  36. package/dist/llm/credential-pool.js.map +1 -1
  37. package/dist/llm/cross-provider-selector.d.ts +254 -0
  38. package/dist/llm/cross-provider-selector.d.ts.map +1 -0
  39. package/dist/llm/cross-provider-selector.js +635 -0
  40. package/dist/llm/cross-provider-selector.js.map +1 -0
  41. package/dist/llm/index.d.ts +2 -0
  42. package/dist/llm/index.d.ts.map +1 -1
  43. package/dist/llm/index.js +1 -0
  44. package/dist/llm/index.js.map +1 -1
  45. package/dist/llm/local-model-fit.d.ts +260 -0
  46. package/dist/llm/local-model-fit.d.ts.map +1 -0
  47. package/dist/llm/local-model-fit.js +563 -0
  48. package/dist/llm/local-model-fit.js.map +1 -0
  49. package/dist/llm/plugin-facade.js +883 -231
  50. package/dist/llm/plugin-facade.js.map +3 -3
  51. package/dist/llm/provider-registry/builtin/ollama.d.ts +18 -0
  52. package/dist/llm/provider-registry/builtin/ollama.d.ts.map +1 -1
  53. package/dist/llm/provider-registry/builtin/ollama.js +20 -2
  54. package/dist/llm/provider-registry/builtin/ollama.js.map +1 -1
  55. package/dist/llm/role-resolver.d.ts.map +1 -1
  56. package/dist/llm/role-resolver.js +45 -10
  57. package/dist/llm/role-resolver.js.map +1 -1
  58. package/dist/llm/transports/codex-responses.d.ts +111 -88
  59. package/dist/llm/transports/codex-responses.d.ts.map +1 -1
  60. package/dist/llm/transports/codex-responses.js +456 -258
  61. package/dist/llm/transports/codex-responses.js.map +1 -1
  62. package/dist/release/index.d.ts +2 -2
  63. package/dist/release/index.d.ts.map +1 -1
  64. package/dist/release/index.js +1 -1
  65. package/dist/release/index.js.map +1 -1
  66. package/dist/release/reconcile.d.ts +97 -0
  67. package/dist/release/reconcile.d.ts.map +1 -1
  68. package/dist/release/reconcile.js +412 -5
  69. package/dist/release/reconcile.js.map +1 -1
  70. package/dist/resources/backend.d.ts +155 -0
  71. package/dist/resources/backend.d.ts.map +1 -0
  72. package/dist/resources/backend.js +23 -0
  73. package/dist/resources/backend.js.map +1 -0
  74. package/dist/resources/linux-backend.d.ts +167 -0
  75. package/dist/resources/linux-backend.d.ts.map +1 -0
  76. package/dist/resources/linux-backend.js +259 -0
  77. package/dist/resources/linux-backend.js.map +1 -0
  78. package/dist/resources/monitor.d.ts +230 -0
  79. package/dist/resources/monitor.d.ts.map +1 -0
  80. package/dist/resources/monitor.js +255 -0
  81. package/dist/resources/monitor.js.map +1 -0
  82. package/dist/resources/spawn-wrapper.d.ts +265 -0
  83. package/dist/resources/spawn-wrapper.d.ts.map +1 -0
  84. package/dist/resources/spawn-wrapper.js +304 -0
  85. package/dist/resources/spawn-wrapper.js.map +1 -0
  86. package/dist/selfimprove/budget.d.ts +11 -2
  87. package/dist/selfimprove/budget.d.ts.map +1 -1
  88. package/dist/selfimprove/budget.js +11 -2
  89. package/dist/selfimprove/budget.js.map +1 -1
  90. package/dist/selfimprove/fix-gen-context.d.ts +172 -0
  91. package/dist/selfimprove/fix-gen-context.d.ts.map +1 -0
  92. package/dist/selfimprove/fix-gen-context.js +218 -0
  93. package/dist/selfimprove/fix-gen-context.js.map +1 -0
  94. package/dist/selfimprove/fix-gen.d.ts +285 -0
  95. package/dist/selfimprove/fix-gen.d.ts.map +1 -0
  96. package/dist/selfimprove/fix-gen.js +346 -0
  97. package/dist/selfimprove/fix-gen.js.map +1 -0
  98. package/dist/selfimprove/op-source-map.d.ts +92 -0
  99. package/dist/selfimprove/op-source-map.d.ts.map +1 -0
  100. package/dist/selfimprove/op-source-map.js +187 -0
  101. package/dist/selfimprove/op-source-map.js.map +1 -0
  102. package/dist/selfimprove/probe-helper.d.ts +61 -0
  103. package/dist/selfimprove/probe-helper.d.ts.map +1 -0
  104. package/dist/selfimprove/probe-helper.js +64 -0
  105. package/dist/selfimprove/probe-helper.js.map +1 -0
  106. package/dist/selfimprove/run-loop.d.ts +44 -4
  107. package/dist/selfimprove/run-loop.d.ts.map +1 -1
  108. package/dist/selfimprove/run-loop.js +73 -6
  109. package/dist/selfimprove/run-loop.js.map +1 -1
  110. package/dist/selfimprove/scenarios/seeded-code-regression/golden.json +18 -0
  111. package/dist/selfimprove/scenarios/seeded-code-regression/scenario.json +12 -0
  112. package/dist/sentient/cross-project-hygiene.d.ts +11 -2
  113. package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
  114. package/dist/sentient/cross-project-hygiene.js +73 -10
  115. package/dist/sentient/cross-project-hygiene.js.map +1 -1
  116. package/dist/sentient/daemon.d.ts.map +1 -1
  117. package/dist/sentient/daemon.js +7 -2
  118. package/dist/sentient/daemon.js.map +1 -1
  119. package/dist/sentient/worktree-dispatch.d.ts.map +1 -1
  120. package/dist/sentient/worktree-dispatch.js +8 -1
  121. package/dist/sentient/worktree-dispatch.js.map +1 -1
  122. package/dist/setup/index.d.ts +1 -0
  123. package/dist/setup/index.d.ts.map +1 -1
  124. package/dist/setup/index.js +1 -0
  125. package/dist/setup/index.js.map +1 -1
  126. package/dist/setup/sections/models-roles.d.ts +32 -1
  127. package/dist/setup/sections/models-roles.d.ts.map +1 -1
  128. package/dist/setup/sections/models-roles.js +118 -19
  129. package/dist/setup/sections/models-roles.js.map +1 -1
  130. package/dist/setup/whoami-summary.d.ts +34 -0
  131. package/dist/setup/whoami-summary.d.ts.map +1 -0
  132. package/dist/setup/whoami-summary.js +106 -0
  133. package/dist/setup/whoami-summary.js.map +1 -0
  134. package/dist/spawn/branch-lock.d.ts.map +1 -1
  135. package/dist/spawn/branch-lock.js +7 -1
  136. package/dist/spawn/branch-lock.js.map +1 -1
  137. package/dist/store/attachment-repair.d.ts +110 -0
  138. package/dist/store/attachment-repair.d.ts.map +1 -0
  139. package/dist/store/attachment-repair.js +326 -0
  140. package/dist/store/attachment-repair.js.map +1 -0
  141. package/dist/store/attachment-store.d.ts.map +1 -1
  142. package/dist/store/attachment-store.js +24 -7
  143. package/dist/store/attachment-store.js.map +1 -1
  144. package/dist/tasks/evidence.d.ts +34 -2
  145. package/dist/tasks/evidence.d.ts.map +1 -1
  146. package/dist/tasks/evidence.js +293 -35
  147. package/dist/tasks/evidence.js.map +1 -1
  148. package/dist/verification/verify-tools.d.ts.map +1 -1
  149. package/dist/verification/verify-tools.js +5 -6
  150. package/dist/verification/verify-tools.js.map +1 -1
  151. package/package.json +22 -12
  152. package/templates/workflows/release-prepare.yml.tmpl +48 -2
@@ -10804,6 +10804,22 @@ var init_operations_registry = __esm({
10804
10804
  }
10805
10805
  ]
10806
10806
  },
10807
+ // ── selfimprove query: probe (T11988 — seeded-code-regression scenario) ────────
10808
+ // Pure read-only op that returns `{ probe: 'ok', version: probeVersion() }`.
10809
+ // The `probe-helper.ts` intentionally ships with `probeVersion()` returning `2`
10810
+ // (should be `1`) so the seeded-code-regression scenario replay diverges from its
10811
+ // golden (`version: 1`) and gives the fix-gen LLM stage a real, patchable bug.
10812
+ {
10813
+ gateway: "query",
10814
+ domain: "selfimprove",
10815
+ operation: "probe",
10816
+ description: 'selfimprove.probe (query) \u2014 returns { probe: "ok", version: 1 } (health/version probe). Used by the seeded-code-regression scenario to validate the fix-gen pipeline (T11988).',
10817
+ tier: 2,
10818
+ idempotent: true,
10819
+ sessionRequired: false,
10820
+ requiredParams: [],
10821
+ params: []
10822
+ },
10807
10823
  // ── selfimprove mutate: run (T11889 / T11889-D — the self-dogfooding loop) ──
10808
10824
  // Boots ONE sandbox → replays the named canned scenario → diffs result
10809
10825
  // envelopes vs the golden → on a regression emits ONE leased `selfimprove_dhq`
@@ -11417,6 +11433,31 @@ var init_operations_registry = __esm({
11417
11433
  cli: { positional: true }
11418
11434
  }
11419
11435
  ]
11436
+ },
11437
+ // T11995 — Janitor MVP: orphan process reaper + stale scope/lock/debris sweep
11438
+ {
11439
+ gateway: "mutate",
11440
+ domain: "admin",
11441
+ operation: "janitor.run",
11442
+ description: "admin.janitor.run (mutate) \u2014 run the janitor sweep: reap orphan processes, stop dead cleo-owned scopes, reclaim stale locks, prune tmp debris",
11443
+ tier: 1,
11444
+ idempotent: true,
11445
+ sessionRequired: false,
11446
+ requiredParams: [],
11447
+ params: [
11448
+ {
11449
+ name: "dryRun",
11450
+ type: "boolean",
11451
+ required: false,
11452
+ description: "Report planned actions without mutating anything."
11453
+ },
11454
+ {
11455
+ name: "gracePeriodMs",
11456
+ type: "number",
11457
+ required: false,
11458
+ description: "Minimum age (ms) of an unregistered process before reap eligibility (default: 600000)."
11459
+ }
11460
+ ]
11420
11461
  }
11421
11462
  ];
11422
11463
  }
@@ -17608,7 +17649,7 @@ var init_sql = __esm({
17608
17649
  return new SQL([new StringChunk(str)]);
17609
17650
  }
17610
17651
  _sql.raw = raw;
17611
- function join28(chunks, separator) {
17652
+ function join29(chunks, separator) {
17612
17653
  const result = [];
17613
17654
  for (const [i, chunk] of chunks.entries()) {
17614
17655
  if (i > 0 && separator !== void 0) result.push(separator);
@@ -17616,7 +17657,7 @@ var init_sql = __esm({
17616
17657
  }
17617
17658
  return new SQL(result);
17618
17659
  }
17619
- _sql.join = join28;
17660
+ _sql.join = join29;
17620
17661
  function identifier(value) {
17621
17662
  return new Name(value);
17622
17663
  }
@@ -20003,7 +20044,7 @@ var init_select2 = __esm({
20003
20044
  const baseTableName = this.tableName;
20004
20045
  const tableName = getTableLikeName(table);
20005
20046
  for (const item of extractUsedTable(table)) this.usedTables.add(item);
20006
- if (typeof tableName === "string" && this.config.joins?.some((join28) => join28.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
20047
+ if (typeof tableName === "string" && this.config.joins?.some((join29) => join29.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
20007
20048
  if (!this.isPartialSelect) {
20008
20049
  if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === "string") this.config.fields = { [baseTableName]: this.config.fields };
20009
20050
  if (typeof tableName === "string" && !is(table, SQL)) {
@@ -23813,7 +23854,7 @@ var init_dialect = __esm({
23813
23854
  if (!joins2) return;
23814
23855
  const withEntries = Object.entries(joins2).filter(([_, v]) => v);
23815
23856
  if (!withEntries.length) return;
23816
- return sql.join(withEntries.map(([k, join28]) => {
23857
+ return sql.join(withEntries.map(([k, join29]) => {
23817
23858
  const relation = tableConfig.relations[k];
23818
23859
  const isSingle2 = is(relation, One);
23819
23860
  const targetTable = aliasedTable(relation.targetTable, `d${currentDepth + 1}`);
@@ -23824,7 +23865,7 @@ var init_dialect = __esm({
23824
23865
  table: targetTable,
23825
23866
  mode: isSingle2 ? "first" : "many",
23826
23867
  schema,
23827
- queryConfig: join28,
23868
+ queryConfig: join29,
23828
23869
  tableConfig: schema[relation.targetTableName],
23829
23870
  relationWhere: filter2,
23830
23871
  isNested: true,
@@ -23838,7 +23879,7 @@ var init_dialect = __esm({
23838
23879
  key: k,
23839
23880
  selection: innerQuery.selection,
23840
23881
  isArray: !isSingle2,
23841
- isOptional: (relation.optional ?? false) || join28 !== true && !!join28.where
23882
+ isOptional: (relation.optional ?? false) || join29 !== true && !!join29.where
23842
23883
  });
23843
23884
  const jsonColumns = sql.join(innerQuery.selection.map((s) => {
23844
23885
  return sql`${sql.raw(this.escapeString(s.key))}, ${s.selection ? sql`${jsonb3}(${sql.identifier(s.key)})` : sql.identifier(s.key)}`;
@@ -24659,7 +24700,7 @@ var init_update = __esm({
24659
24700
  createJoin(joinType) {
24660
24701
  return ((table, on) => {
24661
24702
  const tableName = getTableLikeName(table);
24662
- if (typeof tableName === "string" && this.config.joins.some((join28) => join28.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
24703
+ if (typeof tableName === "string" && this.config.joins.some((join29) => join29.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
24663
24704
  if (typeof on === "function") {
24664
24705
  const from = this.config.from ? is(table, SQLiteTable) ? table[Table.Symbol.Columns] : is(table, Subquery) ? table._.selectedFields : is(table, SQLiteViewBase) ? table[ViewBaseConfig].selectedFields : void 0 : void 0;
24665
24706
  on = on(new Proxy(this.config.table[Table.Symbol.Columns], new SelectionProxyHandler({
@@ -25348,10 +25389,10 @@ var init_session4 = __esm({
25348
25389
  NodeSQLitePreparedQuery = class extends SQLitePreparedQuery {
25349
25390
  static [entityKind] = "SQLJsPreparedQuery";
25350
25391
  jitMapper;
25351
- constructor(stmt, query, logger5, fields, executeMethod, useJitMappers, customResultMapper, isRqbV2Query, rqbConfig) {
25392
+ constructor(stmt, query, logger6, fields, executeMethod, useJitMappers, customResultMapper, isRqbV2Query, rqbConfig) {
25352
25393
  super("sync", executeMethod, query);
25353
25394
  this.stmt = stmt;
25354
- this.logger = logger5;
25395
+ this.logger = logger6;
25355
25396
  this.fields = fields;
25356
25397
  this.useJitMappers = useJitMappers;
25357
25398
  this.customResultMapper = customResultMapper;
@@ -25367,10 +25408,10 @@ var init_session4 = __esm({
25367
25408
  all(placeholderValues = {}) {
25368
25409
  if (this.isRqbV2Query) return this.allRqbV2(placeholderValues);
25369
25410
  const { stmt } = this;
25370
- const { fields, joinsNotNullableMap, logger: logger5, query, customResultMapper } = this;
25411
+ const { fields, joinsNotNullableMap, logger: logger6, query, customResultMapper } = this;
25371
25412
  if (!fields && !customResultMapper) {
25372
25413
  const params = fillPlaceholders(query.params, placeholderValues);
25373
- logger5.logQuery(query.sql, params);
25414
+ logger6.logQuery(query.sql, params);
25374
25415
  stmt.setReturnArrays(false);
25375
25416
  return stmt.all(...params);
25376
25417
  }
@@ -25381,9 +25422,9 @@ var init_session4 = __esm({
25381
25422
  }
25382
25423
  allRqbV2(placeholderValues = {}) {
25383
25424
  const { stmt } = this;
25384
- const { logger: logger5, query, customResultMapper } = this;
25425
+ const { logger: logger6, query, customResultMapper } = this;
25385
25426
  const params = fillPlaceholders(query.params, placeholderValues);
25386
- logger5.logQuery(query.sql, params);
25427
+ logger6.logQuery(query.sql, params);
25387
25428
  stmt.setReturnArrays(false);
25388
25429
  const rows = stmt.all(...params).map((row) => ({ ...row }));
25389
25430
  return this.useJitMappers ? (this.jitMapper = this.jitMapper ?? makeJitRqbMapper(this.rqbConfig))(rows) : customResultMapper(rows);
@@ -25429,9 +25470,9 @@ var init_session4 = __esm({
25429
25470
  import { DatabaseSync } from "node:sqlite";
25430
25471
  function construct(client, config2 = {}) {
25431
25472
  const dialect = new SQLiteSyncDialect();
25432
- let logger5;
25433
- if (config2.logger === true) logger5 = new DefaultLogger();
25434
- else if (config2.logger !== false) logger5 = config2.logger;
25473
+ let logger6;
25474
+ if (config2.logger === true) logger6 = new DefaultLogger();
25475
+ else if (config2.logger !== false) logger6 = config2.logger;
25435
25476
  let schema;
25436
25477
  if (config2.schema) {
25437
25478
  const tablesConfig = extractTablesRelationalConfig(config2.schema, createTableRelationsHelpers);
@@ -25443,7 +25484,7 @@ function construct(client, config2 = {}) {
25443
25484
  }
25444
25485
  const relations = config2.relations ?? {};
25445
25486
  const db = new NodeSQLiteDatabase("sync", dialect, new NodeSQLiteSession(client, dialect, relations, schema, {
25446
- logger: logger5,
25487
+ logger: logger6,
25447
25488
  useJitMappers: jitCompatCheck(config2.jit)
25448
25489
  }), relations, schema);
25449
25490
  db.$client = client;
@@ -40041,8 +40082,6 @@ __export(config_exports, {
40041
40082
  setConfigValue: () => setConfigValue
40042
40083
  });
40043
40084
  import { existsSync as existsSync16 } from "node:fs";
40044
- import { mkdir as mkdir3, writeFile } from "node:fs/promises";
40045
- import { dirname as dirname8 } from "node:path";
40046
40085
  function getNestedValue(obj, path) {
40047
40086
  const parts = path.split(".");
40048
40087
  let current = obj;
@@ -40165,9 +40204,7 @@ function parseConfigValue(value) {
40165
40204
  async function setConfigValue(key, value, cwd, opts) {
40166
40205
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
40167
40206
  if (!existsSync16(configPath)) {
40168
- const dir = dirname8(configPath);
40169
- await mkdir3(dir, { recursive: true });
40170
- await writeFile(configPath, "{}", "utf-8");
40207
+ await atomicWriteJson(configPath, {});
40171
40208
  }
40172
40209
  const config2 = await readJson(configPath) ?? {};
40173
40210
  const parsedValue = parseConfigValue(value);
@@ -40179,9 +40216,7 @@ async function applyStrictnessPreset(preset, cwd, opts) {
40179
40216
  const definition = STRICTNESS_PRESETS[preset];
40180
40217
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
40181
40218
  if (!existsSync16(configPath)) {
40182
- const dir = dirname8(configPath);
40183
- await mkdir3(dir, { recursive: true });
40184
- await writeFile(configPath, "{}", "utf-8");
40219
+ await atomicWriteJson(configPath, {});
40185
40220
  }
40186
40221
  const config2 = await readJson(configPath) ?? {};
40187
40222
  for (const [key, value] of Object.entries(definition.values)) {
@@ -40210,6 +40245,7 @@ var init_config = __esm({
40210
40245
  "use strict";
40211
40246
  init_fallback_model();
40212
40247
  init_paths();
40248
+ init_atomic();
40213
40249
  init_json2();
40214
40250
  DEFAULTS = {
40215
40251
  version: "2.10.0",
@@ -40320,6 +40356,12 @@ var init_config = __esm({
40320
40356
  // (T10383). The active runtime itself ships in T10512.
40321
40357
  leadRollup: {
40322
40358
  mode: "passive"
40359
+ },
40360
+ // T11984: daemon lifecycle — postinstall respects operator state by default.
40361
+ // Set daemon.autoStart = false (via global config.json) for a persistent
40362
+ // opt-out that survives npm upgrades.
40363
+ daemon: {
40364
+ autoStart: true
40323
40365
  }
40324
40366
  };
40325
40367
  ENV_MAP = {
@@ -41544,15 +41586,27 @@ var init_credentials2 = __esm({
41544
41586
  });
41545
41587
 
41546
41588
  // packages/core/src/llm/credentials-store.ts
41589
+ var credentials_store_exports = {};
41590
+ __export(credentials_store_exports, {
41591
+ _resetPermsWarningForTests: () => _resetPermsWarningForTests,
41592
+ _resetRoundRobinForTests: () => _resetRoundRobinForTests,
41593
+ addCredential: () => addCredential,
41594
+ credentialsStorePath: () => credentialsStorePath,
41595
+ getCredentialByLabel: () => getCredentialByLabel,
41596
+ listCredentials: () => listCredentials,
41597
+ pickCredentialForProvider: () => pickCredentialForProvider,
41598
+ pickCredentialForProviderSync: () => pickCredentialForProviderSync,
41599
+ removeCredential: () => removeCredential
41600
+ });
41547
41601
  import { chmodSync, existsSync as existsSync22, mkdirSync as mkdirSync7, readFileSync as readFileSync13, statSync as statSync6, writeFileSync as writeFileSync7 } from "node:fs";
41548
- import { dirname as dirname9, join as join23 } from "node:path";
41602
+ import { dirname as dirname8, join as join23 } from "node:path";
41549
41603
  import { getCleoHome as getCleoHome7 } from "@cleocode/paths";
41550
41604
  function credentialsStorePath() {
41551
41605
  return join23(getCleoHome7(), "llm-credentials.json");
41552
41606
  }
41553
41607
  function ensureFileInitialized(path) {
41554
41608
  if (existsSync22(path)) return;
41555
- const dir = dirname9(path);
41609
+ const dir = dirname8(path);
41556
41610
  if (!existsSync22(dir)) {
41557
41611
  mkdirSync7(dir, { recursive: true, mode: 448 });
41558
41612
  } else {
@@ -41587,6 +41641,9 @@ function checkPermsOnRead(path) {
41587
41641
  } catch {
41588
41642
  }
41589
41643
  }
41644
+ function _resetPermsWarningForTests() {
41645
+ _warnedLoosePerms = false;
41646
+ }
41590
41647
  function emptyStore() {
41591
41648
  return { version: 1, defaultStrategy: "priorityWithFallback", credentials: [] };
41592
41649
  }
@@ -41629,6 +41686,9 @@ function isStoredCredential(value) {
41629
41686
  if (typeof accessToken !== "string") return false;
41630
41687
  return true;
41631
41688
  }
41689
+ function _resetRoundRobinForTests() {
41690
+ _rrIndex.clear();
41691
+ }
41632
41692
  function eligibleForProvider(data, provider, preferLabel) {
41633
41693
  const now = Date.now();
41634
41694
  const all = data.credentials.filter((c) => {
@@ -41732,6 +41792,35 @@ async function addCredential(input) {
41732
41792
  clearAnthropicKeyCache();
41733
41793
  return inserted;
41734
41794
  }
41795
+ async function removeCredential(provider, label) {
41796
+ const path = credentialsStorePath();
41797
+ const snapshot = readStoreSync();
41798
+ if (!snapshot.credentials.some((c) => c.provider === provider && c.label === label)) {
41799
+ return false;
41800
+ }
41801
+ ensureFileInitialized(path);
41802
+ let removed = false;
41803
+ await withLock2(
41804
+ path,
41805
+ (current) => {
41806
+ const data = current ?? emptyStore();
41807
+ if (data.version !== 1 || !Array.isArray(data.credentials)) {
41808
+ return emptyStore();
41809
+ }
41810
+ const before = data.credentials.length;
41811
+ data.credentials = data.credentials.filter(
41812
+ (c) => !(c.provider === provider && c.label === label)
41813
+ );
41814
+ removed = data.credentials.length < before;
41815
+ data.defaultStrategy ??= "priorityWithFallback";
41816
+ return data;
41817
+ },
41818
+ // SECURITY (S-01/S-02): see addCredential for rationale.
41819
+ { mode: 384 }
41820
+ );
41821
+ if (removed) clearAnthropicKeyCache();
41822
+ return removed;
41823
+ }
41735
41824
  async function pickCredentialForProvider(provider, opts = {}) {
41736
41825
  return pickCredentialForProviderSync(provider, opts);
41737
41826
  }
@@ -41871,7 +41960,7 @@ var init_gemini = __esm({
41871
41960
  // packages/core/src/llm/stable-device-id.ts
41872
41961
  import { randomUUID as randomUUID2 } from "node:crypto";
41873
41962
  import { existsSync as existsSync23, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "node:fs";
41874
- import { dirname as dirname10, join as join24 } from "node:path";
41963
+ import { dirname as dirname9, join as join24 } from "node:path";
41875
41964
  import { getCleoHome as getCleoHome8 } from "@cleocode/paths";
41876
41965
  function getStableDeviceId() {
41877
41966
  if (_cachedDeviceId !== null) return _cachedDeviceId;
@@ -41888,7 +41977,7 @@ function getStableDeviceId() {
41888
41977
  }
41889
41978
  const fresh = randomUUID2();
41890
41979
  try {
41891
- const dir = dirname10(path);
41980
+ const dir = dirname9(path);
41892
41981
  if (!existsSync23(dir)) {
41893
41982
  mkdirSync8(dir, { recursive: true });
41894
41983
  }
@@ -42034,11 +42123,11 @@ var init_ollama = __esm({
42034
42123
  displayName: "Ollama (local)",
42035
42124
  authTypes: ["api_key"],
42036
42125
  baseUrl: "http://localhost:11434",
42037
- defaultModel: "llama3",
42126
+ defaultModel: "gemma4:e4b",
42038
42127
  aliases: ["ollama-local"],
42039
42128
  // Hermes-parity routing/catalog fields (T11756). Local on-device tier.
42040
42129
  tier: "local",
42041
- defaultAuxModel: "llama3",
42130
+ defaultAuxModel: "gemma4:e2b",
42042
42131
  defaultMaxTokens: 2048,
42043
42132
  envVars: ["OLLAMA_API_KEY", "OLLAMA_BASE_URL"],
42044
42133
  supportsThinkingBudget: false
@@ -42272,6 +42361,15 @@ var init_loader = __esm({
42272
42361
  });
42273
42362
 
42274
42363
  // packages/core/src/llm/provider-registry/index.ts
42364
+ var provider_registry_exports = {};
42365
+ __export(provider_registry_exports, {
42366
+ _resetRegistryForTesting: () => _resetRegistryForTesting,
42367
+ discoverPlugins: () => discoverPlugins,
42368
+ ensureDiscovered: () => ensureDiscovered,
42369
+ getProviderProfile: () => getProviderProfile,
42370
+ listProviders: () => listProviders,
42371
+ registerProvider: () => registerProvider
42372
+ });
42275
42373
  function registerProvider(profile) {
42276
42374
  const key = profile.name.toLowerCase();
42277
42375
  _registry.set(key, profile);
@@ -42294,12 +42392,25 @@ async function ensureDiscovered() {
42294
42392
  }
42295
42393
  return _discoveryPromise;
42296
42394
  }
42395
+ async function discoverPlugins() {
42396
+ return ensureDiscovered();
42397
+ }
42297
42398
  async function getProviderProfile(name15) {
42298
42399
  await ensureDiscovered();
42299
42400
  const key = name15.toLowerCase();
42300
42401
  const canonical = _aliases.get(key) ?? key;
42301
42402
  return _registry.get(canonical);
42302
42403
  }
42404
+ async function listProviders() {
42405
+ await ensureDiscovered();
42406
+ const profiles = [..._registry.values()];
42407
+ return profiles.sort((a, b) => a.name.localeCompare(b.name));
42408
+ }
42409
+ function _resetRegistryForTesting() {
42410
+ _registry.clear();
42411
+ _aliases.clear();
42412
+ _discoveryPromise = null;
42413
+ }
42303
42414
  var _registry, _aliases, _discoveryPromise, BUILTIN_PROFILES;
42304
42415
  var init_provider_registry = __esm({
42305
42416
  "packages/core/src/llm/provider-registry/index.ts"() {
@@ -42424,6 +42535,60 @@ function isInCooldown(credential) {
42424
42535
  function sortByPriorityDesc(entries) {
42425
42536
  return [...entries].sort((a, b) => b.priority - a.priority);
42426
42537
  }
42538
+ async function refreshExpiredOAuthForProvider(provider) {
42539
+ const entries = await listCredentials(provider);
42540
+ const now = Date.now();
42541
+ let attempted = 0;
42542
+ let refreshed = 0;
42543
+ let lastError = null;
42544
+ for (const entry of entries) {
42545
+ if (entry.authType !== "oauth") continue;
42546
+ if (!entry.refreshToken) continue;
42547
+ if (entry.disabled === true) continue;
42548
+ const isExpiredOrNearExpiry = typeof entry.expiresAt === "number" && entry.expiresAt > 0 && entry.expiresAt <= now + PROACTIVE_REFRESH_FLOOR_MS;
42549
+ if (!isExpiredOrNearExpiry) continue;
42550
+ const key = `${provider}::${entry.label}`;
42551
+ const negExpiry = _refreshNegativeCache.get(key);
42552
+ if (typeof negExpiry === "number" && now < negExpiry) {
42553
+ attempted += 1;
42554
+ lastError = lastError ?? new Error(
42555
+ `OAuth refresh suppressed for ${provider}::${entry.label} (negative cache active)`
42556
+ );
42557
+ continue;
42558
+ }
42559
+ attempted += 1;
42560
+ let inFlight = _refreshInFlight.get(key);
42561
+ if (!inFlight) {
42562
+ const pool = new CredentialPool(provider);
42563
+ const expiresAtBefore = entry.expiresAt;
42564
+ inFlight = pool.proactiveRefresh(entry.label).then(async (wasAttempted) => {
42565
+ if (!wasAttempted) {
42566
+ return true;
42567
+ }
42568
+ const after = await getCredentialByLabel(provider, entry.label);
42569
+ const didAdvance = after !== null && typeof after.expiresAt === "number" && after.expiresAt > (expiresAtBefore ?? 0);
42570
+ if (!didAdvance) {
42571
+ throw new Error(
42572
+ `OAuth refresh token exchange failed for ${provider}::${entry.label} \u2014 the stored token may be revoked. Run \`cleo login ${provider}\` to re-authenticate.`
42573
+ );
42574
+ }
42575
+ return true;
42576
+ }).finally(() => {
42577
+ _refreshInFlight.delete(key);
42578
+ });
42579
+ _refreshInFlight.set(key, inFlight);
42580
+ }
42581
+ try {
42582
+ await inFlight;
42583
+ refreshed += 1;
42584
+ } catch (err) {
42585
+ lastError = err instanceof Error ? err : new Error(String(err));
42586
+ _refreshNegativeCache.set(key, Date.now() + REFRESH_NEGATIVE_CACHE_TTL_MS);
42587
+ }
42588
+ }
42589
+ const actionableHint = attempted > 0 && refreshed === 0 && lastError !== null ? `OAuth refresh failed for provider '${provider}'. Run \`cleo login ${provider}\` to re-authenticate.` : null;
42590
+ return { attempted, refreshed, lastError, actionableHint };
42591
+ }
42427
42592
  function debugLog(msg, ...rest) {
42428
42593
  if (process.env["CLEO_DEBUG"]) {
42429
42594
  console.debug(`[cleo:credential-pool] ${msg}`, ...rest);
@@ -42435,7 +42600,7 @@ function getCredentialPool() {
42435
42600
  }
42436
42601
  return _singleton;
42437
42602
  }
42438
- var COOLDOWN_AUTH_MS, COOLDOWN_RATE_LIMIT_MS, COOLDOWN_DEFAULT_MS, PROACTIVE_REFRESH_FLOOR_MS, PoolExhaustedError, CredentialPool, POOL_SEED_CACHE_TTL_MS, UnifiedCredentialPool, _singleton;
42603
+ var COOLDOWN_AUTH_MS, COOLDOWN_RATE_LIMIT_MS, COOLDOWN_DEFAULT_MS, PROACTIVE_REFRESH_FLOOR_MS, PoolExhaustedError, _refreshInFlight, REFRESH_NEGATIVE_CACHE_TTL_MS, _refreshNegativeCache, CredentialPool, POOL_SEED_CACHE_TTL_MS, UnifiedCredentialPool, _singleton;
42439
42604
  var init_credential_pool = __esm({
42440
42605
  "packages/core/src/llm/credential-pool.ts"() {
42441
42606
  "use strict";
@@ -42472,6 +42637,9 @@ var init_credential_pool = __esm({
42472
42637
  /** Stable LAFS error code. */
42473
42638
  code = "E_LLM_POOL_EXHAUSTED";
42474
42639
  };
42640
+ _refreshInFlight = /* @__PURE__ */ new Map();
42641
+ REFRESH_NEGATIVE_CACHE_TTL_MS = 3e4;
42642
+ _refreshNegativeCache = /* @__PURE__ */ new Map();
42475
42643
  CredentialPool = class {
42476
42644
  /**
42477
42645
  * @param provider - The LLM transport this pool manages.
@@ -43232,6 +43400,38 @@ var init_error_classifier = __esm({
43232
43400
  });
43233
43401
 
43234
43402
  // packages/core/src/llm/catalog-cache.ts
43403
+ import {
43404
+ existsSync as existsSync25,
43405
+ mkdirSync as mkdirSync10,
43406
+ readdirSync as readdirSync6,
43407
+ readFileSync as readFileSync15,
43408
+ statSync as statSync8,
43409
+ symlinkSync,
43410
+ unlinkSync as unlinkSync7,
43411
+ writeFileSync as writeFileSync9
43412
+ } from "node:fs";
43413
+ import { homedir as homedir4 } from "node:os";
43414
+ import { join as join27 } from "node:path";
43415
+ function getCatalogDir() {
43416
+ if (process.env["CLEO_DATA_DIR"]) {
43417
+ return join27(process.env["CLEO_DATA_DIR"], "llm-catalog");
43418
+ }
43419
+ const xdg = process.env["XDG_DATA_HOME"] ?? join27(homedir4(), ".local", "share");
43420
+ return join27(xdg, "cleo", "llm-catalog");
43421
+ }
43422
+ function findLatestCacheFile(dir) {
43423
+ if (!existsSync25(dir)) return null;
43424
+ const files = readdirSync6(dir).filter((f) => f.endsWith("-models.json")).map((f) => ({ name: f, mtime: statSync8(join27(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
43425
+ return files.length > 0 ? join27(dir, files[0].name) : null;
43426
+ }
43427
+ function readCacheFile(filePath) {
43428
+ try {
43429
+ const raw = readFileSync15(filePath, "utf-8");
43430
+ return JSON.parse(raw);
43431
+ } catch {
43432
+ return null;
43433
+ }
43434
+ }
43235
43435
  var init_catalog_cache = __esm({
43236
43436
  "packages/core/src/llm/catalog-cache.ts"() {
43237
43437
  "use strict";
@@ -43239,16 +43439,16 @@ var init_catalog_cache = __esm({
43239
43439
  });
43240
43440
 
43241
43441
  // packages/core/src/llm/model-metadata.ts
43242
- import { readFileSync as readFileSync15 } from "node:fs";
43243
- import { dirname as dirname11, join as join27 } from "node:path";
43442
+ import { readFileSync as readFileSync16 } from "node:fs";
43443
+ import { dirname as dirname10, join as join28 } from "node:path";
43244
43444
  import { fileURLToPath as fileURLToPath3 } from "node:url";
43245
43445
  function getCuratedTable() {
43246
43446
  if (_curated !== void 0) {
43247
43447
  return _curated;
43248
43448
  }
43249
- const thisDir = dirname11(fileURLToPath3(import.meta.url));
43250
- const jsonPath = join27(thisDir, "curated-models.json");
43251
- const raw = JSON.parse(readFileSync15(jsonPath, "utf-8"));
43449
+ const thisDir = dirname10(fileURLToPath3(import.meta.url));
43450
+ const jsonPath = join28(thisDir, "curated-models.json");
43451
+ const raw = JSON.parse(readFileSync16(jsonPath, "utf-8"));
43252
43452
  _curated = raw.models;
43253
43453
  return _curated;
43254
43454
  }
@@ -46088,7 +46288,69 @@ var init_chat_completions = __esm({
46088
46288
  });
46089
46289
 
46090
46290
  // packages/core/src/llm/transports/codex-responses.ts
46091
- import { OpenAI as OpenAIClient } from "openai";
46291
+ function resolveCodexUrl(baseUrl) {
46292
+ const DEFAULT_BASE = "https://chatgpt.com/backend-api";
46293
+ const raw = baseUrl?.trim() ? baseUrl.trim() : DEFAULT_BASE;
46294
+ const normalized = raw.replace(/\/+$/, "");
46295
+ if (normalized.endsWith("/codex/responses")) return normalized;
46296
+ if (normalized.endsWith("/codex")) return `${normalized}/responses`;
46297
+ return `${normalized}/codex/responses`;
46298
+ }
46299
+ async function buildHttpError(res) {
46300
+ let bodyDetail = "";
46301
+ try {
46302
+ const text3 = await res.text();
46303
+ if (text3) {
46304
+ try {
46305
+ const parsed = JSON.parse(text3);
46306
+ const err = parsed["error"];
46307
+ bodyDetail = err?.["message"] ?? text3;
46308
+ } catch {
46309
+ bodyDetail = text3;
46310
+ }
46311
+ }
46312
+ } catch {
46313
+ }
46314
+ return bodyDetail ? `${res.status} ${bodyDetail}` : `${res.status} status code (no body)`;
46315
+ }
46316
+ function buildInputItems(request) {
46317
+ const items = [];
46318
+ for (const msg of request.messages) {
46319
+ if (msg.role === "tool") {
46320
+ items.push({
46321
+ type: "function_call_output",
46322
+ call_id: msg.toolUseId ?? `call_${Date.now().toString(36)}`,
46323
+ output: extractTextContent3(msg)
46324
+ });
46325
+ continue;
46326
+ }
46327
+ const contentItems = buildContentList(msg);
46328
+ if (contentItems.length === 1 && contentItems[0]?.["type"] === "input_text") {
46329
+ items.push({
46330
+ type: "message",
46331
+ role: msg.role === "assistant" ? "assistant" : "user",
46332
+ content: contentItems[0]["text"]
46333
+ });
46334
+ } else {
46335
+ items.push({
46336
+ type: "message",
46337
+ role: msg.role === "assistant" ? "assistant" : "user",
46338
+ content: contentItems
46339
+ });
46340
+ }
46341
+ }
46342
+ return items;
46343
+ }
46344
+ function buildTools(tools) {
46345
+ if (!tools || tools.length === 0) return [];
46346
+ return tools.map((t) => ({
46347
+ type: "function",
46348
+ name: t.name,
46349
+ description: t.description,
46350
+ parameters: t.inputSchema,
46351
+ strict: false
46352
+ }));
46353
+ }
46092
46354
  function extractTextContent3(message) {
46093
46355
  if (typeof message.content === "string") return message.content;
46094
46356
  return message.content.filter(
@@ -46108,38 +46370,77 @@ function buildContentList(message) {
46108
46370
  return { type: "input_image", image_url: imageUrl, detail: "auto" };
46109
46371
  });
46110
46372
  }
46111
- function extractTextFromOutput(output) {
46112
- const parts = [];
46113
- for (const item of output) {
46114
- const it = item;
46115
- if (it["type"] !== "message") continue;
46116
- const content = it["content"];
46117
- if (!Array.isArray(content)) continue;
46118
- for (const part of content) {
46119
- if (part["type"] === "output_text" && typeof part["text"] === "string") {
46120
- parts.push(part["text"]);
46121
- }
46122
- }
46123
- }
46124
- return parts.join("");
46125
- }
46126
46373
  function extractToolCallsFromOutput(output) {
46127
46374
  const calls = [];
46128
46375
  for (const item of output) {
46129
- const it = item;
46130
- if (it["type"] !== "function_call") continue;
46376
+ if (item["type"] !== "function_call") continue;
46131
46377
  calls.push({
46132
- id: typeof it["call_id"] === "string" ? it["call_id"] : null,
46133
- name: typeof it["name"] === "string" ? it["name"] : "unknown",
46134
- arguments: typeof it["arguments"] === "string" ? it["arguments"] : "{}",
46378
+ id: typeof item["call_id"] === "string" ? item["call_id"] : null,
46379
+ name: typeof item["name"] === "string" ? item["name"] : "unknown",
46380
+ arguments: typeof item["arguments"] === "string" ? item["arguments"] : "{}",
46135
46381
  providerData: {
46136
- call_id: it["call_id"],
46137
- response_item_id: it["id"]
46382
+ call_id: item["call_id"],
46383
+ response_item_id: item["id"]
46138
46384
  }
46139
46385
  });
46140
46386
  }
46141
46387
  return calls;
46142
46388
  }
46389
+ function normalizeUsage(usage) {
46390
+ const inputTokens = usage["input_tokens"] ?? 0;
46391
+ const outputTokens = usage["output_tokens"] ?? 0;
46392
+ const normalized = { inputTokens, outputTokens };
46393
+ const details = usage["input_tokens_details"];
46394
+ const cached2 = details?.["cached_tokens"];
46395
+ if (typeof cached2 === "number" && cached2 > 0) {
46396
+ normalized.cachedTokens = cached2;
46397
+ }
46398
+ return normalized;
46399
+ }
46400
+ async function* parseSSE(body, signal) {
46401
+ const reader = body.getReader();
46402
+ const decoder = new TextDecoder();
46403
+ let buffer = "";
46404
+ const onAbort = () => {
46405
+ void reader.cancel().catch(() => void 0);
46406
+ };
46407
+ signal?.addEventListener("abort", onAbort, { once: true });
46408
+ try {
46409
+ while (true) {
46410
+ if (signal?.aborted) throw new Error("Request was aborted");
46411
+ const { done, value } = await reader.read();
46412
+ if (signal?.aborted) throw new Error("Request was aborted");
46413
+ if (done) break;
46414
+ buffer += decoder.decode(value, { stream: true });
46415
+ let idx = buffer.indexOf("\n\n");
46416
+ while (idx !== -1) {
46417
+ const chunk = buffer.slice(0, idx);
46418
+ buffer = buffer.slice(idx + 2);
46419
+ const dataLines = chunk.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim());
46420
+ if (dataLines.length > 0) {
46421
+ const data = dataLines.join("\n").trim();
46422
+ if (data && data !== "[DONE]") {
46423
+ try {
46424
+ yield JSON.parse(data);
46425
+ } catch {
46426
+ }
46427
+ }
46428
+ }
46429
+ idx = buffer.indexOf("\n\n");
46430
+ }
46431
+ }
46432
+ } finally {
46433
+ signal?.removeEventListener("abort", onAbort);
46434
+ try {
46435
+ await reader.cancel();
46436
+ } catch {
46437
+ }
46438
+ try {
46439
+ reader.releaseLock();
46440
+ } catch {
46441
+ }
46442
+ }
46443
+ }
46143
46444
  var CodexResponsesTransport;
46144
46445
  var init_codex_responses = __esm({
46145
46446
  "packages/core/src/llm/transports/codex-responses.ts"() {
@@ -46156,111 +46457,207 @@ var init_codex_responses = __esm({
46156
46457
  * @see ADR-072 §Type lock-in
46157
46458
  */
46158
46459
  apiMode = "codex_responses";
46159
- /** Underlying OpenAI SDK client. */
46160
- _client;
46460
+ /** API key / OAuth bearer token. */
46461
+ _apiKey;
46462
+ /**
46463
+ * Resolved endpoint URL (the full `/codex/responses` path).
46464
+ * Derived once at construction so the URL normalisation runs once.
46465
+ */
46466
+ _endpointUrl;
46467
+ /** Extra headers merged into every request (auth, originator, etc.). */
46468
+ _defaultHeaders;
46161
46469
  /**
46162
46470
  * Construct a CodexResponsesTransport.
46163
46471
  *
46164
- * @param opts - Construction options including provider, API key, and
46165
- * optional base URL / default headers.
46472
+ * @param opts - Construction options: provider, API key, optional base URL
46473
+ * and default headers (auth + Cloudflare-bypass headers for OAuth callers).
46166
46474
  */
46167
46475
  constructor(opts) {
46168
46476
  this.provider = opts.provider;
46169
- this._client = new OpenAIClient({
46170
- apiKey: opts.apiKey,
46171
- baseURL: opts.baseUrl,
46172
- defaultHeaders: opts.defaultHeaders
46173
- });
46477
+ this._apiKey = opts.apiKey;
46478
+ this._endpointUrl = resolveCodexUrl(opts.baseUrl);
46479
+ this._defaultHeaders = opts.defaultHeaders ?? {};
46174
46480
  }
46481
+ // ---------------------------------------------------------------------------
46482
+ // LlmTransport — complete()
46483
+ // ---------------------------------------------------------------------------
46175
46484
  /**
46176
- * Execute a single completion using the OpenAI Responses API.
46485
+ * Execute a single (non-streaming) completion using the Responses API.
46177
46486
  *
46178
- * Maps the provider-neutral {@link TransportRequest} to
46179
- * `openai.responses.create()` and normalizes the {@link Response} into a
46180
- * {@link NormalizedResponse}.
46487
+ * The Codex ChatGPT backend mandates `stream: true` on every request and
46488
+ * returns `400 {"detail":"Stream must be set to true"}` for `stream: false`.
46489
+ * This method therefore always sends `stream: true` with
46490
+ * `Accept: text/event-stream`, internally consumes the SSE stream, and
46491
+ * aggregates text deltas + tool calls + usage into a {@link NormalizedResponse}
46492
+ * — identical to calling `stream()` and collecting all chunks.
46181
46493
  *
46182
46494
  * @param request - Provider-neutral request parameters.
46183
46495
  * @param _ctx - Transport context (unused; `request.signal` handles abort).
46184
46496
  * @returns Normalized response envelope.
46185
46497
  */
46186
46498
  async complete(request, _ctx) {
46187
- const input = this._buildInputItems(request);
46188
- const tools = this._buildTools(request.tools);
46189
- const params = {
46190
- model: request.model,
46191
- input,
46192
- max_output_tokens: request.maxTokens,
46193
- temperature: request.temperature ?? 0.7
46499
+ const body = this._buildBody(request, true);
46500
+ const headers = this._buildHeaders(true);
46501
+ const res = await fetch(this._endpointUrl, {
46502
+ method: "POST",
46503
+ headers,
46504
+ body: JSON.stringify(body),
46505
+ signal: request.signal
46506
+ });
46507
+ if (!res.ok) {
46508
+ throw new Error(await buildHttpError(res));
46509
+ }
46510
+ if (!res.body) {
46511
+ throw new Error("codex_responses: response body is null (no SSE stream)");
46512
+ }
46513
+ const textParts = [];
46514
+ const toolCalls = [];
46515
+ let finalUsage = null;
46516
+ let finishReason = null;
46517
+ let responseId = null;
46518
+ let responseModel = null;
46519
+ for await (const event of parseSSE(res.body, request.signal)) {
46520
+ const evType = event["type"];
46521
+ if (evType === "response.output_text.delta") {
46522
+ const delta = event["delta"];
46523
+ if (delta) textParts.push(delta);
46524
+ continue;
46525
+ }
46526
+ if (evType === "response.output_item.done") {
46527
+ const item = event["item"];
46528
+ if (item?.["type"] === "function_call") {
46529
+ toolCalls.push({
46530
+ id: typeof item["call_id"] === "string" ? item["call_id"] : null,
46531
+ name: typeof item["name"] === "string" ? item["name"] : "unknown",
46532
+ arguments: typeof item["arguments"] === "string" ? item["arguments"] : "{}",
46533
+ providerData: {
46534
+ call_id: item["call_id"],
46535
+ response_item_id: item["id"]
46536
+ }
46537
+ });
46538
+ }
46539
+ continue;
46540
+ }
46541
+ if (evType === "response.completed") {
46542
+ const completedResponse = event["response"];
46543
+ if (completedResponse) {
46544
+ finishReason = completedResponse["status"] ?? "completed";
46545
+ responseId = completedResponse["id"] ?? null;
46546
+ responseModel = completedResponse["model"] ?? null;
46547
+ const usage = completedResponse["usage"];
46548
+ if (usage) finalUsage = normalizeUsage(usage);
46549
+ const output = completedResponse["output"] ?? [];
46550
+ const embeddedCalls = extractToolCallsFromOutput(
46551
+ output
46552
+ );
46553
+ if (embeddedCalls.length > 0 && toolCalls.length === 0) {
46554
+ toolCalls.push(...embeddedCalls);
46555
+ }
46556
+ }
46557
+ break;
46558
+ }
46559
+ if (evType === "response.failed" || evType === "response.incomplete") {
46560
+ const failedResponse = event["response"];
46561
+ if (failedResponse) {
46562
+ finishReason = failedResponse["status"] ?? evType;
46563
+ const usage = failedResponse["usage"];
46564
+ if (usage) finalUsage = normalizeUsage(usage);
46565
+ }
46566
+ break;
46567
+ }
46568
+ if (evType === "error") {
46569
+ const code = event["code"];
46570
+ const message = event["message"];
46571
+ throw new Error(`Codex error: ${message ?? code ?? JSON.stringify(event)}`);
46572
+ }
46573
+ }
46574
+ const content = textParts.join("") || null;
46575
+ return {
46576
+ id: responseId ?? `resp-${Date.now().toString(36)}`,
46577
+ model: responseModel ?? request.model,
46578
+ content,
46579
+ toolCalls: toolCalls.length > 0 ? toolCalls : null,
46580
+ stopReason: finishReason ?? "completed",
46581
+ usage: finalUsage ?? { inputTokens: 0, outputTokens: 0 },
46582
+ providerData: {
46583
+ codex_response_id: responseId
46584
+ },
46585
+ raw: {}
46194
46586
  };
46195
- if (request.system) params["instructions"] = request.system;
46196
- if (tools.length > 0) params["tools"] = tools;
46197
- const response = await this._client.responses.create(
46198
- params,
46199
- { signal: request.signal }
46200
- );
46201
- return this._normalize(response, request.model);
46202
46587
  }
46588
+ // ---------------------------------------------------------------------------
46589
+ // LlmTransport — stream()
46590
+ // ---------------------------------------------------------------------------
46203
46591
  /**
46204
- * Stream a completion using the OpenAI Responses API SSE stream.
46592
+ * Stream a completion using the Responses API SSE stream.
46205
46593
  *
46206
46594
  * Yields {@link NormalizedDelta} chunks from `response.output_text.delta`
46207
- * events. The final delta is emitted when `response.completed` fires and
46208
- * carries `stopReason` and `usage`.
46595
+ * events parsed from the raw SSE stream. The final delta carries
46596
+ * `stopReason` and `usage` from `response.completed`.
46597
+ *
46598
+ * Wire shape:
46599
+ * - `store: false` (required — codex backend rejects absent or true)
46600
+ * - `stream: true`
46601
+ * - `Accept: text/event-stream`
46602
+ * - `OpenAI-Beta: responses=experimental`
46209
46603
  *
46210
46604
  * @param request - Provider-neutral request parameters.
46211
46605
  * @param _ctx - Transport context (unused; `request.signal` handles abort).
46212
46606
  * @returns Async iterable of normalized delta chunks.
46213
46607
  */
46214
46608
  async *stream(request, _ctx) {
46215
- const input = this._buildInputItems(request);
46216
- const tools = this._buildTools(request.tools);
46217
- const params = {
46218
- model: request.model,
46219
- input,
46220
- max_output_tokens: request.maxTokens,
46221
- temperature: request.temperature ?? 0.7,
46222
- stream: true
46223
- };
46224
- if (request.system) params["instructions"] = request.system;
46225
- if (tools.length > 0) params["tools"] = tools;
46226
- const responseStream = await this._client.responses.create(
46227
- params,
46228
- { signal: request.signal }
46229
- );
46609
+ const body = this._buildBody(request, true);
46610
+ const headers = this._buildHeaders(true);
46611
+ const res = await fetch(this._endpointUrl, {
46612
+ method: "POST",
46613
+ headers,
46614
+ body: JSON.stringify(body),
46615
+ signal: request.signal
46616
+ });
46617
+ if (!res.ok) {
46618
+ throw new Error(await buildHttpError(res));
46619
+ }
46620
+ if (!res.body) {
46621
+ throw new Error("codex_responses: response body is null (no SSE stream)");
46622
+ }
46230
46623
  let finishReason = null;
46231
46624
  let finalUsage = null;
46232
- for await (const event of responseStream) {
46233
- const ev = event;
46234
- const evType = ev["type"];
46625
+ for await (const event of parseSSE(res.body, request.signal)) {
46626
+ const evType = event["type"];
46235
46627
  if (evType === "response.output_text.delta") {
46236
- const delta = ev["delta"];
46628
+ const delta = event["delta"];
46237
46629
  if (delta) {
46238
46630
  yield { text: delta, reasoning: "", stopReason: null, usage: null };
46239
46631
  }
46240
46632
  continue;
46241
46633
  }
46242
46634
  if (evType === "response.completed") {
46243
- const completedResponse = ev["response"] ?? null;
46635
+ const completedResponse = event["response"];
46244
46636
  if (completedResponse) {
46245
- finishReason = completedResponse.status ?? "completed";
46246
- const usage = completedResponse.usage;
46637
+ finishReason = completedResponse["status"] ?? "completed";
46638
+ const usage = completedResponse["usage"];
46247
46639
  if (usage) {
46248
- finalUsage = this._normalizeUsage(usage);
46640
+ finalUsage = normalizeUsage(usage);
46249
46641
  }
46250
46642
  }
46251
46643
  break;
46252
46644
  }
46253
46645
  if (evType === "response.failed" || evType === "response.incomplete") {
46254
- const failedResponse = ev["response"] ?? null;
46646
+ const failedResponse = event["response"];
46255
46647
  if (failedResponse) {
46256
- finishReason = failedResponse.status ?? evType;
46257
- const usage = failedResponse.usage;
46648
+ finishReason = failedResponse["status"] ?? evType;
46649
+ const usage = failedResponse["usage"];
46258
46650
  if (usage) {
46259
- finalUsage = this._normalizeUsage(usage);
46651
+ finalUsage = normalizeUsage(usage);
46260
46652
  }
46261
46653
  }
46262
46654
  break;
46263
46655
  }
46656
+ if (evType === "error") {
46657
+ const code = event["code"];
46658
+ const message = event["message"];
46659
+ throw new Error(`Codex error: ${message ?? code ?? JSON.stringify(event)}`);
46660
+ }
46264
46661
  }
46265
46662
  yield {
46266
46663
  text: "",
@@ -46270,128 +46667,60 @@ var init_codex_responses = __esm({
46270
46667
  };
46271
46668
  }
46272
46669
  // ---------------------------------------------------------------------------
46273
- // Input items construction
46670
+ // Private helpers
46274
46671
  // ---------------------------------------------------------------------------
46275
46672
  /**
46276
- * Convert provider-neutral messages to the Responses API `input` array.
46673
+ * Build the HTTP headers for a Codex request.
46277
46674
  *
46278
- * Mapping rules:
46279
- * - `user` / `assistant` string messages → `EasyInputMessage` with `type:'message'`.
46280
- * - `user` / `assistant` multimodal blocks → content list with `input_text` /
46281
- * `input_image` items.
46282
- * - `tool` messages (role=`'tool'`) → `function_call_output` items using
46283
- * `toolUseId` as `call_id`.
46675
+ * Merges the auth/cloudflare headers from `_defaultHeaders` with the
46676
+ * mandatory Codex-specific headers:
46677
+ * - `OpenAI-Beta: responses=experimental`
46678
+ * - `content-type: application/json`
46679
+ * - `accept: text/event-stream` (always — both complete() and stream() use SSE)
46284
46680
  *
46285
- * Multi-turn tool replay: when an assistant message's raw content is a
46286
- * `function_call` JSON envelope (injected by the agent loop after a tool
46287
- * call round-trip), the item is converted to a `function_call` input item
46288
- * so the Responses API can continue the conversation.
46681
+ * The Authorization header is injected only if not already present in
46682
+ * `_defaultHeaders` (OAuth callers pre-set it via buildCodexOAuthHeaders).
46289
46683
  *
46290
- * @param request - Full transport request.
46291
- * @returns Responses API input items array.
46684
+ * @param streaming - Whether this is a streaming (SSE) or completion call.
46685
+ * @returns Plain string record of all request headers.
46292
46686
  */
46293
- _buildInputItems(request) {
46294
- const items = [];
46295
- for (const msg of request.messages) {
46296
- if (msg.role === "tool") {
46297
- items.push({
46298
- type: "function_call_output",
46299
- call_id: msg.toolUseId ?? `call_${Date.now().toString(36)}`,
46300
- output: typeof msg.content === "string" ? msg.content : extractTextContent3(msg)
46301
- });
46302
- continue;
46303
- }
46304
- const contentItems = buildContentList(msg);
46305
- if (contentItems.length === 1 && contentItems[0]?.["type"] === "input_text") {
46306
- items.push({
46307
- type: "message",
46308
- role: msg.role === "assistant" ? "assistant" : "user",
46309
- content: contentItems[0]["text"]
46310
- });
46311
- } else {
46312
- items.push({
46313
- type: "message",
46314
- role: msg.role === "assistant" ? "assistant" : "user",
46315
- content: contentItems
46316
- });
46317
- }
46687
+ _buildHeaders(streaming) {
46688
+ const headers = {
46689
+ ...this._defaultHeaders,
46690
+ "OpenAI-Beta": "responses=experimental",
46691
+ "content-type": "application/json",
46692
+ accept: streaming ? "text/event-stream" : "application/json"
46693
+ };
46694
+ const hasAuth = Object.keys(headers).some((k) => k.toLowerCase() === "authorization");
46695
+ if (!hasAuth) {
46696
+ headers["Authorization"] = `Bearer ${this._apiKey}`;
46318
46697
  }
46319
- return items;
46320
- }
46321
- // ---------------------------------------------------------------------------
46322
- // Tool conversion
46323
- // ---------------------------------------------------------------------------
46324
- /**
46325
- * Convert provider-neutral tool definitions to the Responses API
46326
- * `{type: 'function', name, description, parameters, strict}` shape.
46327
- *
46328
- * @param tools - Provider-neutral tool definitions (may be undefined).
46329
- * @returns Responses API tool objects, or empty array.
46330
- */
46331
- _buildTools(tools) {
46332
- if (!tools || tools.length === 0) return [];
46333
- return tools.map((t) => ({
46334
- type: "function",
46335
- name: t.name,
46336
- description: t.description,
46337
- parameters: t.inputSchema,
46338
- strict: false
46339
- }));
46698
+ return headers;
46340
46699
  }
46341
- // ---------------------------------------------------------------------------
46342
- // Response normalization
46343
- // ---------------------------------------------------------------------------
46344
46700
  /**
46345
- * Normalize an OpenAI Responses API {@link Response} into a
46346
- * {@link NormalizedResponse}.
46701
+ * Build the JSON request body for a Codex Responses API call.
46347
46702
  *
46348
- * Mapping rules:
46349
- * - `content` — from `response.output_text` shortcut (SDK convenience
46350
- * property that aggregates all output_text parts).
46351
- * - `toolCalls` — from `output` items of `type: 'function_call'`.
46352
- * - `stopReason` — from `response.status` (completed / failed / incomplete).
46353
- * - `usage` — `input_tokens` → `inputTokens`, `output_tokens` → `outputTokens`.
46354
- * - `cachedTokens` — from `usage.input_tokens_details.cached_tokens`.
46355
- * - `raw` — unmodified SDK response.
46703
+ * Mandatory fields per the Codex backend:
46704
+ * - `store: false` — backend rejects absent or `true`.
46705
+ * - `instructions` — system prompt (defaulting to empty string when absent).
46706
+ * - `input` — array of Responses API input items.
46356
46707
  *
46357
- * @param response - Raw Responses API response object.
46358
- * @param requestedModel - Model string from the originating request.
46359
- * @returns Normalized response envelope.
46360
- */
46361
- _normalize(response, requestedModel) {
46362
- const content = response.output_text && response.output_text.length > 0 ? response.output_text : extractTextFromOutput(response.output);
46363
- const toolCalls = extractToolCallsFromOutput(response.output);
46364
- const usage = this._normalizeUsage(response.usage ?? null);
46365
- return {
46366
- id: response.id ?? `resp-${Date.now().toString(36)}`,
46367
- model: String(response.model ?? requestedModel),
46368
- content: content || null,
46369
- toolCalls: toolCalls.length > 0 ? toolCalls : null,
46370
- stopReason: response.status ?? "completed",
46371
- usage,
46372
- providerData: {
46373
- codex_response_id: response.id
46374
- },
46375
- raw: response
46376
- };
46377
- }
46378
- /**
46379
- * Normalize OpenAI Responses API usage into {@link NormalizedUsage}.
46380
- *
46381
- * @param usage - Raw usage object (may be null when the API omits it).
46382
- * @returns Normalized usage with optional `cachedTokens`.
46708
+ * @param request - Provider-neutral request.
46709
+ * @param streaming - Whether to enable SSE streaming.
46710
+ * @returns Serialisable request body.
46383
46711
  */
46384
- _normalizeUsage(usage) {
46385
- if (!usage) return { inputTokens: 0, outputTokens: 0 };
46386
- const normalized = {
46387
- inputTokens: usage.input_tokens ?? 0,
46388
- outputTokens: usage.output_tokens ?? 0
46712
+ _buildBody(request, streaming) {
46713
+ const body = {
46714
+ model: request.model,
46715
+ input: buildInputItems(request),
46716
+ instructions: request.system ?? "",
46717
+ store: false,
46718
+ stream: streaming
46389
46719
  };
46390
- const cached2 = usage.input_tokens_details?.cached_tokens;
46391
- if (typeof cached2 === "number" && cached2 > 0) {
46392
- normalized.cachedTokens = cached2;
46393
- }
46394
- return normalized;
46720
+ if (request.temperature != null) body.temperature = request.temperature;
46721
+ const tools = buildTools(request.tools);
46722
+ if (tools.length > 0) body.tools = tools;
46723
+ return body;
46395
46724
  }
46396
46725
  };
46397
46726
  }
@@ -75237,6 +75566,318 @@ var init_model_runner = __esm({
75237
75566
  }
75238
75567
  });
75239
75568
 
75569
+ // packages/core/src/llm/catalog-model-resolver.ts
75570
+ var catalog_model_resolver_exports = {};
75571
+ __export(catalog_model_resolver_exports, {
75572
+ _resetCatalogModelResolverCache: () => _resetCatalogModelResolverCache,
75573
+ catalogKeyForProvider: () => catalogKeyForProvider,
75574
+ listProviderModels: () => listProviderModels,
75575
+ resolveProviderDefaultModel: () => resolveProviderDefaultModel,
75576
+ validateModelForProvider: () => validateModelForProvider
75577
+ });
75578
+ function getDiskCatalog() {
75579
+ if (_catalog !== void 0) return _catalog;
75580
+ const dir = getCatalogDir();
75581
+ const latest = findLatestCacheFile(dir);
75582
+ if (!latest) {
75583
+ _catalog = null;
75584
+ return null;
75585
+ }
75586
+ _catalog = readCacheFile(latest);
75587
+ return _catalog;
75588
+ }
75589
+ function resolveProviderDefaultModel(providerCatalogKey, catalogOverride) {
75590
+ const catalog = catalogOverride ?? getDiskCatalog();
75591
+ if (!catalog) return null;
75592
+ const provider = catalog[providerCatalogKey];
75593
+ if (!provider?.models) return null;
75594
+ let latestId = null;
75595
+ let latestDate = "";
75596
+ for (const [id, entry] of Object.entries(provider.models)) {
75597
+ const rd = entry.release_date;
75598
+ if (!rd) continue;
75599
+ if (rd > latestDate) {
75600
+ latestDate = rd;
75601
+ latestId = id;
75602
+ }
75603
+ }
75604
+ return latestId;
75605
+ }
75606
+ function validateModelForProvider(model, providerCatalogKey, catalogOverride) {
75607
+ const catalog = catalogOverride ?? getDiskCatalog();
75608
+ if (!catalog) {
75609
+ return { valid: true, reason: "catalog-unavailable" };
75610
+ }
75611
+ const provider = catalog[providerCatalogKey];
75612
+ if (!provider?.models) {
75613
+ return { valid: true, reason: "provider-not-in-catalog" };
75614
+ }
75615
+ const found = Object.hasOwn(provider.models, model);
75616
+ return { valid: found, reason: found ? "found" : "not-found" };
75617
+ }
75618
+ function listProviderModels(providerCatalogKey, limit = 12, catalogOverride) {
75619
+ const catalog = catalogOverride ?? getDiskCatalog();
75620
+ if (!catalog) return [];
75621
+ const provider = catalog[providerCatalogKey];
75622
+ if (!provider?.models) return [];
75623
+ const entries = Object.entries(provider.models);
75624
+ const sorted = [...entries].sort(([, a], [, b]) => {
75625
+ const da = a.release_date ?? "";
75626
+ const db = b.release_date ?? "";
75627
+ if (da === db) return 0;
75628
+ return db.localeCompare(da);
75629
+ });
75630
+ return sorted.slice(0, Math.max(0, limit)).map(([id]) => id);
75631
+ }
75632
+ function catalogKeyForProvider(providerName) {
75633
+ const PROVIDER_KEY_MAP = {
75634
+ gemini: "google"
75635
+ // CLEO name → models.dev key overrides only (identical mappings are omitted)
75636
+ };
75637
+ return PROVIDER_KEY_MAP[providerName.toLowerCase()] ?? providerName.toLowerCase();
75638
+ }
75639
+ function _resetCatalogModelResolverCache() {
75640
+ _catalog = void 0;
75641
+ }
75642
+ var _catalog;
75643
+ var init_catalog_model_resolver = __esm({
75644
+ "packages/core/src/llm/catalog-model-resolver.ts"() {
75645
+ "use strict";
75646
+ init_catalog_cache();
75647
+ }
75648
+ });
75649
+
75650
+ // packages/core/src/llm/cross-provider-selector.ts
75651
+ import { totalmem } from "node:os";
75652
+ async function probeOllamaAlive(baseUrl) {
75653
+ const cached2 = _ollamaProbeCache.get(baseUrl);
75654
+ if (cached2 && Date.now() - cached2.checkedAt < OLLAMA_PROBE_TTL_MS) {
75655
+ return cached2.alive;
75656
+ }
75657
+ let alive = false;
75658
+ try {
75659
+ const controller = new AbortController();
75660
+ const timeoutId = setTimeout(() => controller.abort(), OLLAMA_PROBE_TIMEOUT_MS);
75661
+ try {
75662
+ const res = await fetch(baseUrl, { signal: controller.signal });
75663
+ alive = res.ok || res.status < 500;
75664
+ } finally {
75665
+ clearTimeout(timeoutId);
75666
+ }
75667
+ } catch {
75668
+ alive = false;
75669
+ }
75670
+ _ollamaProbeCache.set(baseUrl, { alive, checkedAt: Date.now() });
75671
+ return alive;
75672
+ }
75673
+ function ollamaDefaultModelForTier(tier, ramBytesOverride) {
75674
+ const ramBytes = ramBytesOverride ?? totalmem();
75675
+ const gb = ramBytes / 1024 ** 3;
75676
+ if (gb >= 8) {
75677
+ return tier === "fast" || tier === "local" ? "gemma4:e2b" : "gemma4:e4b";
75678
+ }
75679
+ if (gb >= 4) {
75680
+ return "gemma4:e2b";
75681
+ }
75682
+ logger4.warn(
75683
+ { ramGb: gb.toFixed(1), tier },
75684
+ "cross-provider-selector: RAM < 4 GB; selecting qwen2:0.5b (proof-of-life only \u2014 not suitable for production)"
75685
+ );
75686
+ return "qwen2:0.5b";
75687
+ }
75688
+ function isProvisioned(provider) {
75689
+ if (provider === "ollama") {
75690
+ if (process.env["OLLAMA_HOST"]?.trim()) return true;
75691
+ const stored = pickCredentialForProviderSync(provider, { strategy: "priorityOnly" });
75692
+ return stored !== null;
75693
+ }
75694
+ const cred = resolveCredentials(provider);
75695
+ return (cred.apiKey?.trim().length ?? 0) > 0;
75696
+ }
75697
+ function scoreProvider(provider, providerTier, taskTier, ollamaAlive, costInputPerMillion) {
75698
+ let score = TIER_BASE[providerTier];
75699
+ if (taskTier === "frontier" && providerTier === "frontier") {
75700
+ score += TASK_MATCH_BONUS_FRONTIER;
75701
+ } else if ((taskTier === "fast" || taskTier === "local") && (providerTier === "fast" || providerTier === "local")) {
75702
+ score += TASK_MATCH_BONUS_FAST;
75703
+ }
75704
+ if (providerTier === "local" && ollamaAlive) {
75705
+ score += LOCALITY_BONUS;
75706
+ }
75707
+ if ((providerTier === "frontier" || providerTier === "standard") && provider !== "ollama") {
75708
+ score += PROVISIONED_CLOUD_BIAS;
75709
+ }
75710
+ const penalty = Math.min(Math.floor(costInputPerMillion / 10), MAX_COST_PENALTY);
75711
+ score -= penalty;
75712
+ return score;
75713
+ }
75714
+ function roleTierFor(role) {
75715
+ switch (role) {
75716
+ case "consolidation":
75717
+ case "judgement":
75718
+ case "hygiene":
75719
+ return "frontier";
75720
+ case "extraction":
75721
+ case "derivation":
75722
+ return "standard";
75723
+ default:
75724
+ return "fast";
75725
+ }
75726
+ }
75727
+ async function selectBestProvisioned(role, opts) {
75728
+ const taskTier = roleTierFor(role);
75729
+ for (const id of BUILTIN_PROVIDER_IDS) {
75730
+ const creds = await listCredentials(id);
75731
+ const hasExpiredOAuth = creds.some(
75732
+ (c) => c.authType === "oauth" && c.refreshToken && typeof c.expiresAt === "number" && c.expiresAt > 0 && c.expiresAt <= Date.now()
75733
+ );
75734
+ if (!hasExpiredOAuth) continue;
75735
+ try {
75736
+ const result = await refreshExpiredOAuthForProvider(id);
75737
+ if (result.actionableHint) {
75738
+ logger4.warn(
75739
+ { provider: id, hint: result.actionableHint },
75740
+ "cross-provider-selector: OAuth refresh-on-use failed \u2014 credential will be excluded from resolution"
75741
+ );
75742
+ } else if (result.refreshed > 0) {
75743
+ logger4.debug(
75744
+ { provider: id, refreshed: result.refreshed },
75745
+ "cross-provider-selector: renewed expired OAuth credential via refresh-on-use"
75746
+ );
75747
+ }
75748
+ } catch {
75749
+ }
75750
+ }
75751
+ const provisionedIds = [];
75752
+ for (const id of BUILTIN_PROVIDER_IDS) {
75753
+ if (isProvisioned(id)) {
75754
+ provisionedIds.push(id);
75755
+ }
75756
+ }
75757
+ if (provisionedIds.length === 0) {
75758
+ logger4.warn(
75759
+ {
75760
+ providers_checked: BUILTIN_PROVIDER_IDS.slice(),
75761
+ reason: "no-provisioned-provider",
75762
+ role,
75763
+ projectRoot: opts.projectRoot
75764
+ },
75765
+ "resolveLLMForSystem: no provider has a usable credential; returning null-credential envelope"
75766
+ );
75767
+ return null;
75768
+ }
75769
+ const { getProviderProfile: getProviderProfile2 } = await Promise.resolve().then(() => (init_provider_registry(), provider_registry_exports));
75770
+ const { resolveProviderDefaultModel: resolveProviderDefaultModel2, catalogKeyForProvider: catalogKeyForProvider2 } = await Promise.resolve().then(() => (init_catalog_model_resolver(), catalog_model_resolver_exports));
75771
+ const { pickCredentialForProvider: pickCredentialForProvider2 } = await Promise.resolve().then(() => (init_credentials_store(), credentials_store_exports));
75772
+ const ollamaProvisioned = provisionedIds.includes("ollama");
75773
+ let ollamaAlive = false;
75774
+ if (ollamaProvisioned) {
75775
+ const profile = await getProviderProfile2("ollama");
75776
+ const ollamaBase = profile?.baseUrl ?? "http://localhost:11434";
75777
+ ollamaAlive = await probeOllamaAlive(ollamaBase);
75778
+ }
75779
+ const scored = [];
75780
+ for (const id of provisionedIds) {
75781
+ const profile = await getProviderProfile2(id);
75782
+ const tier = profile?.tier ?? "standard";
75783
+ const costInput = 0;
75784
+ const score = scoreProvider(id, tier, taskTier, ollamaAlive, costInput);
75785
+ const storedCred = await pickCredentialForProvider2(id, { strategy: "priorityOnly" });
75786
+ const credentialPriority = storedCred?.priority ?? Number.MAX_SAFE_INTEGER;
75787
+ scored.push({ id, tier, score, credentialPriority });
75788
+ }
75789
+ scored.sort((a, b) => {
75790
+ if (b.score !== a.score) return b.score - a.score;
75791
+ return a.credentialPriority - b.credentialPriority;
75792
+ });
75793
+ const winner = scored[0];
75794
+ if (!winner) return null;
75795
+ let model;
75796
+ if (winner.id === "ollama") {
75797
+ const ollamaProfile2 = await getProviderProfile2("ollama");
75798
+ const ramBytes = totalmem();
75799
+ const gb = ramBytes / 1024 ** 3;
75800
+ if (gb >= 8) {
75801
+ model = (winner.tier === "fast" || taskTier === "fast" ? ollamaProfile2?.defaultAuxModel : ollamaProfile2?.defaultModel) ?? ollamaDefaultModelForTier(winner.tier);
75802
+ } else if (gb >= 4) {
75803
+ model = ollamaProfile2?.defaultAuxModel ?? "gemma4:e2b";
75804
+ } else {
75805
+ model = ollamaDefaultModelForTier(winner.tier, ramBytes);
75806
+ }
75807
+ const catalogKey = catalogKeyForProvider2(winner.id);
75808
+ const catalogModel = resolveProviderDefaultModel2(catalogKey);
75809
+ if (!catalogModel?.startsWith("gemma4")) {
75810
+ logger4.info(
75811
+ { provider: "ollama", selectedModel: model },
75812
+ "cross-provider-selector: gemma4 family not in catalog snapshot \u2014 run `cleo llm refresh-catalog` to pull latest"
75813
+ );
75814
+ }
75815
+ } else {
75816
+ try {
75817
+ const catalogKey = catalogKeyForProvider2(winner.id);
75818
+ const catalogModel = resolveProviderDefaultModel2(catalogKey);
75819
+ const profileModel = (await getProviderProfile2(winner.id))?.defaultModel;
75820
+ model = catalogModel ?? profileModel ?? "unknown";
75821
+ } catch {
75822
+ model = (await getProviderProfile2(winner.id))?.defaultModel ?? "unknown";
75823
+ }
75824
+ }
75825
+ logger4.debug(
75826
+ {
75827
+ winner: winner.id,
75828
+ score: winner.score,
75829
+ taskTier,
75830
+ model,
75831
+ provisionedCount: provisionedIds.length
75832
+ },
75833
+ "cross-provider-selector: selected best provisioned provider"
75834
+ );
75835
+ return {
75836
+ provider: winner.id,
75837
+ model,
75838
+ credentialLabel: void 0,
75839
+ source: "cross-provider"
75840
+ };
75841
+ }
75842
+ var logger4, BUILTIN_PROVIDER_IDS, TIER_BASE, PROVISIONED_CLOUD_BIAS, LOCALITY_BONUS, TASK_MATCH_BONUS_FRONTIER, TASK_MATCH_BONUS_FAST, MAX_COST_PENALTY, OLLAMA_PROBE_TIMEOUT_MS, OLLAMA_PROBE_TTL_MS, _ollamaProbeCache;
75843
+ var init_cross_provider_selector = __esm({
75844
+ "packages/core/src/llm/cross-provider-selector.ts"() {
75845
+ "use strict";
75846
+ init_logger2();
75847
+ init_credential_pool();
75848
+ init_credentials2();
75849
+ init_credentials_store();
75850
+ logger4 = getLogger("llm-cross-provider-selector");
75851
+ BUILTIN_PROVIDER_IDS = [
75852
+ "anthropic",
75853
+ "openai",
75854
+ "gemini",
75855
+ "moonshot",
75856
+ "openrouter",
75857
+ "bedrock",
75858
+ "deepseek",
75859
+ "xai",
75860
+ "groq",
75861
+ "kimi-code",
75862
+ "ollama"
75863
+ ];
75864
+ TIER_BASE = {
75865
+ frontier: 100,
75866
+ standard: 70,
75867
+ fast: 50,
75868
+ local: 120
75869
+ };
75870
+ PROVISIONED_CLOUD_BIAS = 60;
75871
+ LOCALITY_BONUS = 30;
75872
+ TASK_MATCH_BONUS_FRONTIER = 20;
75873
+ TASK_MATCH_BONUS_FAST = 15;
75874
+ MAX_COST_PENALTY = 10;
75875
+ OLLAMA_PROBE_TIMEOUT_MS = 200;
75876
+ OLLAMA_PROBE_TTL_MS = 3e4;
75877
+ _ollamaProbeCache = /* @__PURE__ */ new Map();
75878
+ }
75879
+ });
75880
+
75240
75881
  // packages/core/src/llm/sealed-credential.ts
75241
75882
  function tokenPreview(token, authType) {
75242
75883
  const prefix = authType === "oauth" ? "oat-\u2026" : "\u2026";
@@ -75285,14 +75926,14 @@ function getRegisteredSystemDefault(key) {
75285
75926
  if (!isResolvableSystemDefault(record2.defaults)) return void 0;
75286
75927
  return record2.defaults;
75287
75928
  }
75288
- var logger4, REGISTRY;
75929
+ var logger5, REGISTRY;
75289
75930
  var init_system_of_use_registry = __esm({
75290
75931
  "packages/core/src/llm/system-of-use-registry.ts"() {
75291
75932
  "use strict";
75292
75933
  init_src();
75293
75934
  init_logger2();
75294
75935
  init_system_key();
75295
- logger4 = getLogger("llm-system-of-use-registry");
75936
+ logger5 = getLogger("llm-system-of-use-registry");
75296
75937
  REGISTRY = /* @__PURE__ */ new Map();
75297
75938
  }
75298
75939
  });
@@ -75380,7 +76021,7 @@ function resolveRegisteredSystemDefault(llm, systemKey) {
75380
76021
  }
75381
76022
  return void 0;
75382
76023
  }
75383
- function selectProviderModel(llm, role, systemKey, profileOverride) {
76024
+ async function selectProviderModel(llm, role, systemKey, profileOverride, projectRoot) {
75384
76025
  const roleEntry = llm?.roles?.[role];
75385
76026
  const pinnedProfile = resolveNamedProfile(llm, profileOverride, "profile");
75386
76027
  if (pinnedProfile) return pinnedProfile;
@@ -75409,6 +76050,15 @@ function selectProviderModel(llm, role, systemKey, profileOverride) {
75409
76050
  if (defaultProfile) return defaultProfile;
75410
76051
  const registeredDefault = resolveRegisteredSystemDefault(llm, systemKey);
75411
76052
  if (registeredDefault) return registeredDefault;
76053
+ try {
76054
+ const crossProviderResult = await selectBestProvisioned(role, {
76055
+ projectRoot: projectRoot ?? ""
76056
+ });
76057
+ if (crossProviderResult !== null) {
76058
+ return crossProviderResult;
76059
+ }
76060
+ } catch {
76061
+ }
75412
76062
  return {
75413
76063
  provider: IMPLICIT_FALLBACK_PROVIDER,
75414
76064
  model: IMPLICIT_FALLBACK_MODEL,
@@ -75468,11 +76118,12 @@ async function resolveLLMForRole(role, opts) {
75468
76118
  config2 = void 0;
75469
76119
  }
75470
76120
  const llmBlock = readLlmBlock(config2);
75471
- const { provider, model, credentialLabel, source } = selectProviderModel(
76121
+ const { provider, model, credentialLabel, source } = await selectProviderModel(
75472
76122
  llmBlock,
75473
76123
  role,
75474
76124
  opts?.systemKey,
75475
- opts?.profileOverride
76125
+ opts?.profileOverride,
76126
+ projectRoot
75476
76127
  );
75477
76128
  const { credential, usedLabel } = await resolveCredentialForRole(
75478
76129
  provider,
@@ -75550,6 +76201,7 @@ var init_role_resolver = __esm({
75550
76201
  init_credential_pool();
75551
76202
  init_credentials2();
75552
76203
  init_credentials_store();
76204
+ init_cross_provider_selector();
75553
76205
  init_fallback_model();
75554
76206
  init_sealed_credential();
75555
76207
  init_system_of_use_registry();