@cleocode/core 2026.6.14 → 2026.6.15

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 (114) 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 +17 -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/llm/cli-ops.d.ts +16 -0
  22. package/dist/llm/cli-ops.d.ts.map +1 -1
  23. package/dist/llm/cli-ops.js +32 -5
  24. package/dist/llm/cli-ops.js.map +1 -1
  25. package/dist/llm/cross-provider-selector.d.ts +249 -0
  26. package/dist/llm/cross-provider-selector.d.ts.map +1 -0
  27. package/dist/llm/cross-provider-selector.js +572 -0
  28. package/dist/llm/cross-provider-selector.js.map +1 -0
  29. package/dist/llm/index.d.ts +2 -0
  30. package/dist/llm/index.d.ts.map +1 -1
  31. package/dist/llm/index.js +1 -0
  32. package/dist/llm/index.js.map +1 -1
  33. package/dist/llm/local-model-fit.d.ts +260 -0
  34. package/dist/llm/local-model-fit.d.ts.map +1 -0
  35. package/dist/llm/local-model-fit.js +563 -0
  36. package/dist/llm/local-model-fit.js.map +1 -0
  37. package/dist/llm/plugin-facade.js +761 -230
  38. package/dist/llm/plugin-facade.js.map +3 -3
  39. package/dist/llm/provider-registry/builtin/ollama.d.ts +18 -0
  40. package/dist/llm/provider-registry/builtin/ollama.d.ts.map +1 -1
  41. package/dist/llm/provider-registry/builtin/ollama.js +20 -2
  42. package/dist/llm/provider-registry/builtin/ollama.js.map +1 -1
  43. package/dist/llm/role-resolver.d.ts.map +1 -1
  44. package/dist/llm/role-resolver.js +45 -10
  45. package/dist/llm/role-resolver.js.map +1 -1
  46. package/dist/llm/transports/codex-responses.d.ts +111 -88
  47. package/dist/llm/transports/codex-responses.d.ts.map +1 -1
  48. package/dist/llm/transports/codex-responses.js +456 -258
  49. package/dist/llm/transports/codex-responses.js.map +1 -1
  50. package/dist/release/index.d.ts +2 -2
  51. package/dist/release/index.d.ts.map +1 -1
  52. package/dist/release/index.js +1 -1
  53. package/dist/release/index.js.map +1 -1
  54. package/dist/release/reconcile.d.ts +97 -0
  55. package/dist/release/reconcile.d.ts.map +1 -1
  56. package/dist/release/reconcile.js +412 -5
  57. package/dist/release/reconcile.js.map +1 -1
  58. package/dist/resources/backend.d.ts +155 -0
  59. package/dist/resources/backend.d.ts.map +1 -0
  60. package/dist/resources/backend.js +23 -0
  61. package/dist/resources/backend.js.map +1 -0
  62. package/dist/resources/linux-backend.d.ts +167 -0
  63. package/dist/resources/linux-backend.d.ts.map +1 -0
  64. package/dist/resources/linux-backend.js +259 -0
  65. package/dist/resources/linux-backend.js.map +1 -0
  66. package/dist/resources/monitor.d.ts +230 -0
  67. package/dist/resources/monitor.d.ts.map +1 -0
  68. package/dist/resources/monitor.js +255 -0
  69. package/dist/resources/monitor.js.map +1 -0
  70. package/dist/resources/spawn-wrapper.d.ts +265 -0
  71. package/dist/resources/spawn-wrapper.d.ts.map +1 -0
  72. package/dist/resources/spawn-wrapper.js +304 -0
  73. package/dist/resources/spawn-wrapper.js.map +1 -0
  74. package/dist/selfimprove/budget.d.ts +11 -2
  75. package/dist/selfimprove/budget.d.ts.map +1 -1
  76. package/dist/selfimprove/budget.js +11 -2
  77. package/dist/selfimprove/budget.js.map +1 -1
  78. package/dist/selfimprove/fix-gen.d.ts +229 -0
  79. package/dist/selfimprove/fix-gen.d.ts.map +1 -0
  80. package/dist/selfimprove/fix-gen.js +266 -0
  81. package/dist/selfimprove/fix-gen.js.map +1 -0
  82. package/dist/selfimprove/run-loop.d.ts +44 -4
  83. package/dist/selfimprove/run-loop.d.ts.map +1 -1
  84. package/dist/selfimprove/run-loop.js +73 -6
  85. package/dist/selfimprove/run-loop.js.map +1 -1
  86. package/dist/sentient/cross-project-hygiene.d.ts +11 -2
  87. package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
  88. package/dist/sentient/cross-project-hygiene.js +73 -10
  89. package/dist/sentient/cross-project-hygiene.js.map +1 -1
  90. package/dist/sentient/daemon.d.ts.map +1 -1
  91. package/dist/sentient/daemon.js +7 -2
  92. package/dist/sentient/daemon.js.map +1 -1
  93. package/dist/sentient/worktree-dispatch.d.ts.map +1 -1
  94. package/dist/sentient/worktree-dispatch.js +8 -1
  95. package/dist/sentient/worktree-dispatch.js.map +1 -1
  96. package/dist/spawn/branch-lock.d.ts.map +1 -1
  97. package/dist/spawn/branch-lock.js +7 -1
  98. package/dist/spawn/branch-lock.js.map +1 -1
  99. package/dist/store/attachment-repair.d.ts +110 -0
  100. package/dist/store/attachment-repair.d.ts.map +1 -0
  101. package/dist/store/attachment-repair.js +326 -0
  102. package/dist/store/attachment-repair.js.map +1 -0
  103. package/dist/store/attachment-store.d.ts.map +1 -1
  104. package/dist/store/attachment-store.js +24 -7
  105. package/dist/store/attachment-store.js.map +1 -1
  106. package/dist/tasks/evidence.d.ts +34 -2
  107. package/dist/tasks/evidence.d.ts.map +1 -1
  108. package/dist/tasks/evidence.js +293 -35
  109. package/dist/tasks/evidence.js.map +1 -1
  110. package/dist/verification/verify-tools.d.ts.map +1 -1
  111. package/dist/verification/verify-tools.js +5 -6
  112. package/dist/verification/verify-tools.js.map +1 -1
  113. package/package.json +22 -12
  114. package/templates/workflows/release-prepare.yml.tmpl +48 -2
@@ -17608,7 +17608,7 @@ var init_sql = __esm({
17608
17608
  return new SQL([new StringChunk(str)]);
17609
17609
  }
17610
17610
  _sql.raw = raw;
17611
- function join28(chunks, separator) {
17611
+ function join29(chunks, separator) {
17612
17612
  const result = [];
17613
17613
  for (const [i, chunk] of chunks.entries()) {
17614
17614
  if (i > 0 && separator !== void 0) result.push(separator);
@@ -17616,7 +17616,7 @@ var init_sql = __esm({
17616
17616
  }
17617
17617
  return new SQL(result);
17618
17618
  }
17619
- _sql.join = join28;
17619
+ _sql.join = join29;
17620
17620
  function identifier(value) {
17621
17621
  return new Name(value);
17622
17622
  }
@@ -20003,7 +20003,7 @@ var init_select2 = __esm({
20003
20003
  const baseTableName = this.tableName;
20004
20004
  const tableName = getTableLikeName(table);
20005
20005
  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`);
20006
+ if (typeof tableName === "string" && this.config.joins?.some((join29) => join29.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
20007
20007
  if (!this.isPartialSelect) {
20008
20008
  if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === "string") this.config.fields = { [baseTableName]: this.config.fields };
20009
20009
  if (typeof tableName === "string" && !is(table, SQL)) {
@@ -23813,7 +23813,7 @@ var init_dialect = __esm({
23813
23813
  if (!joins2) return;
23814
23814
  const withEntries = Object.entries(joins2).filter(([_, v]) => v);
23815
23815
  if (!withEntries.length) return;
23816
- return sql.join(withEntries.map(([k, join28]) => {
23816
+ return sql.join(withEntries.map(([k, join29]) => {
23817
23817
  const relation = tableConfig.relations[k];
23818
23818
  const isSingle2 = is(relation, One);
23819
23819
  const targetTable = aliasedTable(relation.targetTable, `d${currentDepth + 1}`);
@@ -23824,7 +23824,7 @@ var init_dialect = __esm({
23824
23824
  table: targetTable,
23825
23825
  mode: isSingle2 ? "first" : "many",
23826
23826
  schema,
23827
- queryConfig: join28,
23827
+ queryConfig: join29,
23828
23828
  tableConfig: schema[relation.targetTableName],
23829
23829
  relationWhere: filter2,
23830
23830
  isNested: true,
@@ -23838,7 +23838,7 @@ var init_dialect = __esm({
23838
23838
  key: k,
23839
23839
  selection: innerQuery.selection,
23840
23840
  isArray: !isSingle2,
23841
- isOptional: (relation.optional ?? false) || join28 !== true && !!join28.where
23841
+ isOptional: (relation.optional ?? false) || join29 !== true && !!join29.where
23842
23842
  });
23843
23843
  const jsonColumns = sql.join(innerQuery.selection.map((s) => {
23844
23844
  return sql`${sql.raw(this.escapeString(s.key))}, ${s.selection ? sql`${jsonb3}(${sql.identifier(s.key)})` : sql.identifier(s.key)}`;
@@ -24659,7 +24659,7 @@ var init_update = __esm({
24659
24659
  createJoin(joinType) {
24660
24660
  return ((table, on) => {
24661
24661
  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`);
24662
+ if (typeof tableName === "string" && this.config.joins.some((join29) => join29.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
24663
24663
  if (typeof on === "function") {
24664
24664
  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
24665
  on = on(new Proxy(this.config.table[Table.Symbol.Columns], new SelectionProxyHandler({
@@ -25348,10 +25348,10 @@ var init_session4 = __esm({
25348
25348
  NodeSQLitePreparedQuery = class extends SQLitePreparedQuery {
25349
25349
  static [entityKind] = "SQLJsPreparedQuery";
25350
25350
  jitMapper;
25351
- constructor(stmt, query, logger5, fields, executeMethod, useJitMappers, customResultMapper, isRqbV2Query, rqbConfig) {
25351
+ constructor(stmt, query, logger6, fields, executeMethod, useJitMappers, customResultMapper, isRqbV2Query, rqbConfig) {
25352
25352
  super("sync", executeMethod, query);
25353
25353
  this.stmt = stmt;
25354
- this.logger = logger5;
25354
+ this.logger = logger6;
25355
25355
  this.fields = fields;
25356
25356
  this.useJitMappers = useJitMappers;
25357
25357
  this.customResultMapper = customResultMapper;
@@ -25367,10 +25367,10 @@ var init_session4 = __esm({
25367
25367
  all(placeholderValues = {}) {
25368
25368
  if (this.isRqbV2Query) return this.allRqbV2(placeholderValues);
25369
25369
  const { stmt } = this;
25370
- const { fields, joinsNotNullableMap, logger: logger5, query, customResultMapper } = this;
25370
+ const { fields, joinsNotNullableMap, logger: logger6, query, customResultMapper } = this;
25371
25371
  if (!fields && !customResultMapper) {
25372
25372
  const params = fillPlaceholders(query.params, placeholderValues);
25373
- logger5.logQuery(query.sql, params);
25373
+ logger6.logQuery(query.sql, params);
25374
25374
  stmt.setReturnArrays(false);
25375
25375
  return stmt.all(...params);
25376
25376
  }
@@ -25381,9 +25381,9 @@ var init_session4 = __esm({
25381
25381
  }
25382
25382
  allRqbV2(placeholderValues = {}) {
25383
25383
  const { stmt } = this;
25384
- const { logger: logger5, query, customResultMapper } = this;
25384
+ const { logger: logger6, query, customResultMapper } = this;
25385
25385
  const params = fillPlaceholders(query.params, placeholderValues);
25386
- logger5.logQuery(query.sql, params);
25386
+ logger6.logQuery(query.sql, params);
25387
25387
  stmt.setReturnArrays(false);
25388
25388
  const rows = stmt.all(...params).map((row) => ({ ...row }));
25389
25389
  return this.useJitMappers ? (this.jitMapper = this.jitMapper ?? makeJitRqbMapper(this.rqbConfig))(rows) : customResultMapper(rows);
@@ -25429,9 +25429,9 @@ var init_session4 = __esm({
25429
25429
  import { DatabaseSync } from "node:sqlite";
25430
25430
  function construct(client, config2 = {}) {
25431
25431
  const dialect = new SQLiteSyncDialect();
25432
- let logger5;
25433
- if (config2.logger === true) logger5 = new DefaultLogger();
25434
- else if (config2.logger !== false) logger5 = config2.logger;
25432
+ let logger6;
25433
+ if (config2.logger === true) logger6 = new DefaultLogger();
25434
+ else if (config2.logger !== false) logger6 = config2.logger;
25435
25435
  let schema;
25436
25436
  if (config2.schema) {
25437
25437
  const tablesConfig = extractTablesRelationalConfig(config2.schema, createTableRelationsHelpers);
@@ -25443,7 +25443,7 @@ function construct(client, config2 = {}) {
25443
25443
  }
25444
25444
  const relations = config2.relations ?? {};
25445
25445
  const db = new NodeSQLiteDatabase("sync", dialect, new NodeSQLiteSession(client, dialect, relations, schema, {
25446
- logger: logger5,
25446
+ logger: logger6,
25447
25447
  useJitMappers: jitCompatCheck(config2.jit)
25448
25448
  }), relations, schema);
25449
25449
  db.$client = client;
@@ -40041,8 +40041,6 @@ __export(config_exports, {
40041
40041
  setConfigValue: () => setConfigValue
40042
40042
  });
40043
40043
  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
40044
  function getNestedValue(obj, path) {
40047
40045
  const parts = path.split(".");
40048
40046
  let current = obj;
@@ -40165,9 +40163,7 @@ function parseConfigValue(value) {
40165
40163
  async function setConfigValue(key, value, cwd, opts) {
40166
40164
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
40167
40165
  if (!existsSync16(configPath)) {
40168
- const dir = dirname8(configPath);
40169
- await mkdir3(dir, { recursive: true });
40170
- await writeFile(configPath, "{}", "utf-8");
40166
+ await atomicWriteJson(configPath, {});
40171
40167
  }
40172
40168
  const config2 = await readJson(configPath) ?? {};
40173
40169
  const parsedValue = parseConfigValue(value);
@@ -40179,9 +40175,7 @@ async function applyStrictnessPreset(preset, cwd, opts) {
40179
40175
  const definition = STRICTNESS_PRESETS[preset];
40180
40176
  const configPath = opts?.global ? getGlobalConfigPath() : getConfigPath(cwd);
40181
40177
  if (!existsSync16(configPath)) {
40182
- const dir = dirname8(configPath);
40183
- await mkdir3(dir, { recursive: true });
40184
- await writeFile(configPath, "{}", "utf-8");
40178
+ await atomicWriteJson(configPath, {});
40185
40179
  }
40186
40180
  const config2 = await readJson(configPath) ?? {};
40187
40181
  for (const [key, value] of Object.entries(definition.values)) {
@@ -40210,6 +40204,7 @@ var init_config = __esm({
40210
40204
  "use strict";
40211
40205
  init_fallback_model();
40212
40206
  init_paths();
40207
+ init_atomic();
40213
40208
  init_json2();
40214
40209
  DEFAULTS = {
40215
40210
  version: "2.10.0",
@@ -40320,6 +40315,12 @@ var init_config = __esm({
40320
40315
  // (T10383). The active runtime itself ships in T10512.
40321
40316
  leadRollup: {
40322
40317
  mode: "passive"
40318
+ },
40319
+ // T11984: daemon lifecycle — postinstall respects operator state by default.
40320
+ // Set daemon.autoStart = false (via global config.json) for a persistent
40321
+ // opt-out that survives npm upgrades.
40322
+ daemon: {
40323
+ autoStart: true
40323
40324
  }
40324
40325
  };
40325
40326
  ENV_MAP = {
@@ -41544,15 +41545,27 @@ var init_credentials2 = __esm({
41544
41545
  });
41545
41546
 
41546
41547
  // packages/core/src/llm/credentials-store.ts
41548
+ var credentials_store_exports = {};
41549
+ __export(credentials_store_exports, {
41550
+ _resetPermsWarningForTests: () => _resetPermsWarningForTests,
41551
+ _resetRoundRobinForTests: () => _resetRoundRobinForTests,
41552
+ addCredential: () => addCredential,
41553
+ credentialsStorePath: () => credentialsStorePath,
41554
+ getCredentialByLabel: () => getCredentialByLabel,
41555
+ listCredentials: () => listCredentials,
41556
+ pickCredentialForProvider: () => pickCredentialForProvider,
41557
+ pickCredentialForProviderSync: () => pickCredentialForProviderSync,
41558
+ removeCredential: () => removeCredential
41559
+ });
41547
41560
  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";
41561
+ import { dirname as dirname8, join as join23 } from "node:path";
41549
41562
  import { getCleoHome as getCleoHome7 } from "@cleocode/paths";
41550
41563
  function credentialsStorePath() {
41551
41564
  return join23(getCleoHome7(), "llm-credentials.json");
41552
41565
  }
41553
41566
  function ensureFileInitialized(path) {
41554
41567
  if (existsSync22(path)) return;
41555
- const dir = dirname9(path);
41568
+ const dir = dirname8(path);
41556
41569
  if (!existsSync22(dir)) {
41557
41570
  mkdirSync7(dir, { recursive: true, mode: 448 });
41558
41571
  } else {
@@ -41587,6 +41600,9 @@ function checkPermsOnRead(path) {
41587
41600
  } catch {
41588
41601
  }
41589
41602
  }
41603
+ function _resetPermsWarningForTests() {
41604
+ _warnedLoosePerms = false;
41605
+ }
41590
41606
  function emptyStore() {
41591
41607
  return { version: 1, defaultStrategy: "priorityWithFallback", credentials: [] };
41592
41608
  }
@@ -41629,6 +41645,9 @@ function isStoredCredential(value) {
41629
41645
  if (typeof accessToken !== "string") return false;
41630
41646
  return true;
41631
41647
  }
41648
+ function _resetRoundRobinForTests() {
41649
+ _rrIndex.clear();
41650
+ }
41632
41651
  function eligibleForProvider(data, provider, preferLabel) {
41633
41652
  const now = Date.now();
41634
41653
  const all = data.credentials.filter((c) => {
@@ -41732,6 +41751,35 @@ async function addCredential(input) {
41732
41751
  clearAnthropicKeyCache();
41733
41752
  return inserted;
41734
41753
  }
41754
+ async function removeCredential(provider, label) {
41755
+ const path = credentialsStorePath();
41756
+ const snapshot = readStoreSync();
41757
+ if (!snapshot.credentials.some((c) => c.provider === provider && c.label === label)) {
41758
+ return false;
41759
+ }
41760
+ ensureFileInitialized(path);
41761
+ let removed = false;
41762
+ await withLock2(
41763
+ path,
41764
+ (current) => {
41765
+ const data = current ?? emptyStore();
41766
+ if (data.version !== 1 || !Array.isArray(data.credentials)) {
41767
+ return emptyStore();
41768
+ }
41769
+ const before = data.credentials.length;
41770
+ data.credentials = data.credentials.filter(
41771
+ (c) => !(c.provider === provider && c.label === label)
41772
+ );
41773
+ removed = data.credentials.length < before;
41774
+ data.defaultStrategy ??= "priorityWithFallback";
41775
+ return data;
41776
+ },
41777
+ // SECURITY (S-01/S-02): see addCredential for rationale.
41778
+ { mode: 384 }
41779
+ );
41780
+ if (removed) clearAnthropicKeyCache();
41781
+ return removed;
41782
+ }
41735
41783
  async function pickCredentialForProvider(provider, opts = {}) {
41736
41784
  return pickCredentialForProviderSync(provider, opts);
41737
41785
  }
@@ -41871,7 +41919,7 @@ var init_gemini = __esm({
41871
41919
  // packages/core/src/llm/stable-device-id.ts
41872
41920
  import { randomUUID as randomUUID2 } from "node:crypto";
41873
41921
  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";
41922
+ import { dirname as dirname9, join as join24 } from "node:path";
41875
41923
  import { getCleoHome as getCleoHome8 } from "@cleocode/paths";
41876
41924
  function getStableDeviceId() {
41877
41925
  if (_cachedDeviceId !== null) return _cachedDeviceId;
@@ -41888,7 +41936,7 @@ function getStableDeviceId() {
41888
41936
  }
41889
41937
  const fresh = randomUUID2();
41890
41938
  try {
41891
- const dir = dirname10(path);
41939
+ const dir = dirname9(path);
41892
41940
  if (!existsSync23(dir)) {
41893
41941
  mkdirSync8(dir, { recursive: true });
41894
41942
  }
@@ -42034,11 +42082,11 @@ var init_ollama = __esm({
42034
42082
  displayName: "Ollama (local)",
42035
42083
  authTypes: ["api_key"],
42036
42084
  baseUrl: "http://localhost:11434",
42037
- defaultModel: "llama3",
42085
+ defaultModel: "gemma4:e4b",
42038
42086
  aliases: ["ollama-local"],
42039
42087
  // Hermes-parity routing/catalog fields (T11756). Local on-device tier.
42040
42088
  tier: "local",
42041
- defaultAuxModel: "llama3",
42089
+ defaultAuxModel: "gemma4:e2b",
42042
42090
  defaultMaxTokens: 2048,
42043
42091
  envVars: ["OLLAMA_API_KEY", "OLLAMA_BASE_URL"],
42044
42092
  supportsThinkingBudget: false
@@ -42272,6 +42320,15 @@ var init_loader = __esm({
42272
42320
  });
42273
42321
 
42274
42322
  // packages/core/src/llm/provider-registry/index.ts
42323
+ var provider_registry_exports = {};
42324
+ __export(provider_registry_exports, {
42325
+ _resetRegistryForTesting: () => _resetRegistryForTesting,
42326
+ discoverPlugins: () => discoverPlugins,
42327
+ ensureDiscovered: () => ensureDiscovered,
42328
+ getProviderProfile: () => getProviderProfile,
42329
+ listProviders: () => listProviders,
42330
+ registerProvider: () => registerProvider
42331
+ });
42275
42332
  function registerProvider(profile) {
42276
42333
  const key = profile.name.toLowerCase();
42277
42334
  _registry.set(key, profile);
@@ -42294,12 +42351,25 @@ async function ensureDiscovered() {
42294
42351
  }
42295
42352
  return _discoveryPromise;
42296
42353
  }
42354
+ async function discoverPlugins() {
42355
+ return ensureDiscovered();
42356
+ }
42297
42357
  async function getProviderProfile(name15) {
42298
42358
  await ensureDiscovered();
42299
42359
  const key = name15.toLowerCase();
42300
42360
  const canonical = _aliases.get(key) ?? key;
42301
42361
  return _registry.get(canonical);
42302
42362
  }
42363
+ async function listProviders() {
42364
+ await ensureDiscovered();
42365
+ const profiles = [..._registry.values()];
42366
+ return profiles.sort((a, b) => a.name.localeCompare(b.name));
42367
+ }
42368
+ function _resetRegistryForTesting() {
42369
+ _registry.clear();
42370
+ _aliases.clear();
42371
+ _discoveryPromise = null;
42372
+ }
42303
42373
  var _registry, _aliases, _discoveryPromise, BUILTIN_PROFILES;
42304
42374
  var init_provider_registry = __esm({
42305
42375
  "packages/core/src/llm/provider-registry/index.ts"() {
@@ -43232,6 +43302,38 @@ var init_error_classifier = __esm({
43232
43302
  });
43233
43303
 
43234
43304
  // packages/core/src/llm/catalog-cache.ts
43305
+ import {
43306
+ existsSync as existsSync25,
43307
+ mkdirSync as mkdirSync10,
43308
+ readdirSync as readdirSync6,
43309
+ readFileSync as readFileSync15,
43310
+ statSync as statSync8,
43311
+ symlinkSync,
43312
+ unlinkSync as unlinkSync7,
43313
+ writeFileSync as writeFileSync9
43314
+ } from "node:fs";
43315
+ import { homedir as homedir4 } from "node:os";
43316
+ import { join as join27 } from "node:path";
43317
+ function getCatalogDir() {
43318
+ if (process.env["CLEO_DATA_DIR"]) {
43319
+ return join27(process.env["CLEO_DATA_DIR"], "llm-catalog");
43320
+ }
43321
+ const xdg = process.env["XDG_DATA_HOME"] ?? join27(homedir4(), ".local", "share");
43322
+ return join27(xdg, "cleo", "llm-catalog");
43323
+ }
43324
+ function findLatestCacheFile(dir) {
43325
+ if (!existsSync25(dir)) return null;
43326
+ 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);
43327
+ return files.length > 0 ? join27(dir, files[0].name) : null;
43328
+ }
43329
+ function readCacheFile(filePath) {
43330
+ try {
43331
+ const raw = readFileSync15(filePath, "utf-8");
43332
+ return JSON.parse(raw);
43333
+ } catch {
43334
+ return null;
43335
+ }
43336
+ }
43235
43337
  var init_catalog_cache = __esm({
43236
43338
  "packages/core/src/llm/catalog-cache.ts"() {
43237
43339
  "use strict";
@@ -43239,16 +43341,16 @@ var init_catalog_cache = __esm({
43239
43341
  });
43240
43342
 
43241
43343
  // 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";
43344
+ import { readFileSync as readFileSync16 } from "node:fs";
43345
+ import { dirname as dirname10, join as join28 } from "node:path";
43244
43346
  import { fileURLToPath as fileURLToPath3 } from "node:url";
43245
43347
  function getCuratedTable() {
43246
43348
  if (_curated !== void 0) {
43247
43349
  return _curated;
43248
43350
  }
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"));
43351
+ const thisDir = dirname10(fileURLToPath3(import.meta.url));
43352
+ const jsonPath = join28(thisDir, "curated-models.json");
43353
+ const raw = JSON.parse(readFileSync16(jsonPath, "utf-8"));
43252
43354
  _curated = raw.models;
43253
43355
  return _curated;
43254
43356
  }
@@ -46088,7 +46190,69 @@ var init_chat_completions = __esm({
46088
46190
  });
46089
46191
 
46090
46192
  // packages/core/src/llm/transports/codex-responses.ts
46091
- import { OpenAI as OpenAIClient } from "openai";
46193
+ function resolveCodexUrl(baseUrl) {
46194
+ const DEFAULT_BASE = "https://chatgpt.com/backend-api";
46195
+ const raw = baseUrl?.trim() ? baseUrl.trim() : DEFAULT_BASE;
46196
+ const normalized = raw.replace(/\/+$/, "");
46197
+ if (normalized.endsWith("/codex/responses")) return normalized;
46198
+ if (normalized.endsWith("/codex")) return `${normalized}/responses`;
46199
+ return `${normalized}/codex/responses`;
46200
+ }
46201
+ async function buildHttpError(res) {
46202
+ let bodyDetail = "";
46203
+ try {
46204
+ const text3 = await res.text();
46205
+ if (text3) {
46206
+ try {
46207
+ const parsed = JSON.parse(text3);
46208
+ const err = parsed["error"];
46209
+ bodyDetail = err?.["message"] ?? text3;
46210
+ } catch {
46211
+ bodyDetail = text3;
46212
+ }
46213
+ }
46214
+ } catch {
46215
+ }
46216
+ return bodyDetail ? `${res.status} ${bodyDetail}` : `${res.status} status code (no body)`;
46217
+ }
46218
+ function buildInputItems(request) {
46219
+ const items = [];
46220
+ for (const msg of request.messages) {
46221
+ if (msg.role === "tool") {
46222
+ items.push({
46223
+ type: "function_call_output",
46224
+ call_id: msg.toolUseId ?? `call_${Date.now().toString(36)}`,
46225
+ output: extractTextContent3(msg)
46226
+ });
46227
+ continue;
46228
+ }
46229
+ const contentItems = buildContentList(msg);
46230
+ if (contentItems.length === 1 && contentItems[0]?.["type"] === "input_text") {
46231
+ items.push({
46232
+ type: "message",
46233
+ role: msg.role === "assistant" ? "assistant" : "user",
46234
+ content: contentItems[0]["text"]
46235
+ });
46236
+ } else {
46237
+ items.push({
46238
+ type: "message",
46239
+ role: msg.role === "assistant" ? "assistant" : "user",
46240
+ content: contentItems
46241
+ });
46242
+ }
46243
+ }
46244
+ return items;
46245
+ }
46246
+ function buildTools(tools) {
46247
+ if (!tools || tools.length === 0) return [];
46248
+ return tools.map((t) => ({
46249
+ type: "function",
46250
+ name: t.name,
46251
+ description: t.description,
46252
+ parameters: t.inputSchema,
46253
+ strict: false
46254
+ }));
46255
+ }
46092
46256
  function extractTextContent3(message) {
46093
46257
  if (typeof message.content === "string") return message.content;
46094
46258
  return message.content.filter(
@@ -46108,38 +46272,77 @@ function buildContentList(message) {
46108
46272
  return { type: "input_image", image_url: imageUrl, detail: "auto" };
46109
46273
  });
46110
46274
  }
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
46275
  function extractToolCallsFromOutput(output) {
46127
46276
  const calls = [];
46128
46277
  for (const item of output) {
46129
- const it = item;
46130
- if (it["type"] !== "function_call") continue;
46278
+ if (item["type"] !== "function_call") continue;
46131
46279
  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"] : "{}",
46280
+ id: typeof item["call_id"] === "string" ? item["call_id"] : null,
46281
+ name: typeof item["name"] === "string" ? item["name"] : "unknown",
46282
+ arguments: typeof item["arguments"] === "string" ? item["arguments"] : "{}",
46135
46283
  providerData: {
46136
- call_id: it["call_id"],
46137
- response_item_id: it["id"]
46284
+ call_id: item["call_id"],
46285
+ response_item_id: item["id"]
46138
46286
  }
46139
46287
  });
46140
46288
  }
46141
46289
  return calls;
46142
46290
  }
46291
+ function normalizeUsage(usage) {
46292
+ const inputTokens = usage["input_tokens"] ?? 0;
46293
+ const outputTokens = usage["output_tokens"] ?? 0;
46294
+ const normalized = { inputTokens, outputTokens };
46295
+ const details = usage["input_tokens_details"];
46296
+ const cached2 = details?.["cached_tokens"];
46297
+ if (typeof cached2 === "number" && cached2 > 0) {
46298
+ normalized.cachedTokens = cached2;
46299
+ }
46300
+ return normalized;
46301
+ }
46302
+ async function* parseSSE(body, signal) {
46303
+ const reader = body.getReader();
46304
+ const decoder = new TextDecoder();
46305
+ let buffer = "";
46306
+ const onAbort = () => {
46307
+ void reader.cancel().catch(() => void 0);
46308
+ };
46309
+ signal?.addEventListener("abort", onAbort, { once: true });
46310
+ try {
46311
+ while (true) {
46312
+ if (signal?.aborted) throw new Error("Request was aborted");
46313
+ const { done, value } = await reader.read();
46314
+ if (signal?.aborted) throw new Error("Request was aborted");
46315
+ if (done) break;
46316
+ buffer += decoder.decode(value, { stream: true });
46317
+ let idx = buffer.indexOf("\n\n");
46318
+ while (idx !== -1) {
46319
+ const chunk = buffer.slice(0, idx);
46320
+ buffer = buffer.slice(idx + 2);
46321
+ const dataLines = chunk.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim());
46322
+ if (dataLines.length > 0) {
46323
+ const data = dataLines.join("\n").trim();
46324
+ if (data && data !== "[DONE]") {
46325
+ try {
46326
+ yield JSON.parse(data);
46327
+ } catch {
46328
+ }
46329
+ }
46330
+ }
46331
+ idx = buffer.indexOf("\n\n");
46332
+ }
46333
+ }
46334
+ } finally {
46335
+ signal?.removeEventListener("abort", onAbort);
46336
+ try {
46337
+ await reader.cancel();
46338
+ } catch {
46339
+ }
46340
+ try {
46341
+ reader.releaseLock();
46342
+ } catch {
46343
+ }
46344
+ }
46345
+ }
46143
46346
  var CodexResponsesTransport;
46144
46347
  var init_codex_responses = __esm({
46145
46348
  "packages/core/src/llm/transports/codex-responses.ts"() {
@@ -46156,111 +46359,207 @@ var init_codex_responses = __esm({
46156
46359
  * @see ADR-072 §Type lock-in
46157
46360
  */
46158
46361
  apiMode = "codex_responses";
46159
- /** Underlying OpenAI SDK client. */
46160
- _client;
46362
+ /** API key / OAuth bearer token. */
46363
+ _apiKey;
46364
+ /**
46365
+ * Resolved endpoint URL (the full `/codex/responses` path).
46366
+ * Derived once at construction so the URL normalisation runs once.
46367
+ */
46368
+ _endpointUrl;
46369
+ /** Extra headers merged into every request (auth, originator, etc.). */
46370
+ _defaultHeaders;
46161
46371
  /**
46162
46372
  * Construct a CodexResponsesTransport.
46163
46373
  *
46164
- * @param opts - Construction options including provider, API key, and
46165
- * optional base URL / default headers.
46374
+ * @param opts - Construction options: provider, API key, optional base URL
46375
+ * and default headers (auth + Cloudflare-bypass headers for OAuth callers).
46166
46376
  */
46167
46377
  constructor(opts) {
46168
46378
  this.provider = opts.provider;
46169
- this._client = new OpenAIClient({
46170
- apiKey: opts.apiKey,
46171
- baseURL: opts.baseUrl,
46172
- defaultHeaders: opts.defaultHeaders
46173
- });
46379
+ this._apiKey = opts.apiKey;
46380
+ this._endpointUrl = resolveCodexUrl(opts.baseUrl);
46381
+ this._defaultHeaders = opts.defaultHeaders ?? {};
46174
46382
  }
46383
+ // ---------------------------------------------------------------------------
46384
+ // LlmTransport — complete()
46385
+ // ---------------------------------------------------------------------------
46175
46386
  /**
46176
- * Execute a single completion using the OpenAI Responses API.
46387
+ * Execute a single (non-streaming) completion using the Responses API.
46177
46388
  *
46178
- * Maps the provider-neutral {@link TransportRequest} to
46179
- * `openai.responses.create()` and normalizes the {@link Response} into a
46180
- * {@link NormalizedResponse}.
46389
+ * The Codex ChatGPT backend mandates `stream: true` on every request and
46390
+ * returns `400 {"detail":"Stream must be set to true"}` for `stream: false`.
46391
+ * This method therefore always sends `stream: true` with
46392
+ * `Accept: text/event-stream`, internally consumes the SSE stream, and
46393
+ * aggregates text deltas + tool calls + usage into a {@link NormalizedResponse}
46394
+ * — identical to calling `stream()` and collecting all chunks.
46181
46395
  *
46182
46396
  * @param request - Provider-neutral request parameters.
46183
46397
  * @param _ctx - Transport context (unused; `request.signal` handles abort).
46184
46398
  * @returns Normalized response envelope.
46185
46399
  */
46186
46400
  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
46401
+ const body = this._buildBody(request, true);
46402
+ const headers = this._buildHeaders(true);
46403
+ const res = await fetch(this._endpointUrl, {
46404
+ method: "POST",
46405
+ headers,
46406
+ body: JSON.stringify(body),
46407
+ signal: request.signal
46408
+ });
46409
+ if (!res.ok) {
46410
+ throw new Error(await buildHttpError(res));
46411
+ }
46412
+ if (!res.body) {
46413
+ throw new Error("codex_responses: response body is null (no SSE stream)");
46414
+ }
46415
+ const textParts = [];
46416
+ const toolCalls = [];
46417
+ let finalUsage = null;
46418
+ let finishReason = null;
46419
+ let responseId = null;
46420
+ let responseModel = null;
46421
+ for await (const event of parseSSE(res.body, request.signal)) {
46422
+ const evType = event["type"];
46423
+ if (evType === "response.output_text.delta") {
46424
+ const delta = event["delta"];
46425
+ if (delta) textParts.push(delta);
46426
+ continue;
46427
+ }
46428
+ if (evType === "response.output_item.done") {
46429
+ const item = event["item"];
46430
+ if (item?.["type"] === "function_call") {
46431
+ toolCalls.push({
46432
+ id: typeof item["call_id"] === "string" ? item["call_id"] : null,
46433
+ name: typeof item["name"] === "string" ? item["name"] : "unknown",
46434
+ arguments: typeof item["arguments"] === "string" ? item["arguments"] : "{}",
46435
+ providerData: {
46436
+ call_id: item["call_id"],
46437
+ response_item_id: item["id"]
46438
+ }
46439
+ });
46440
+ }
46441
+ continue;
46442
+ }
46443
+ if (evType === "response.completed") {
46444
+ const completedResponse = event["response"];
46445
+ if (completedResponse) {
46446
+ finishReason = completedResponse["status"] ?? "completed";
46447
+ responseId = completedResponse["id"] ?? null;
46448
+ responseModel = completedResponse["model"] ?? null;
46449
+ const usage = completedResponse["usage"];
46450
+ if (usage) finalUsage = normalizeUsage(usage);
46451
+ const output = completedResponse["output"] ?? [];
46452
+ const embeddedCalls = extractToolCallsFromOutput(
46453
+ output
46454
+ );
46455
+ if (embeddedCalls.length > 0 && toolCalls.length === 0) {
46456
+ toolCalls.push(...embeddedCalls);
46457
+ }
46458
+ }
46459
+ break;
46460
+ }
46461
+ if (evType === "response.failed" || evType === "response.incomplete") {
46462
+ const failedResponse = event["response"];
46463
+ if (failedResponse) {
46464
+ finishReason = failedResponse["status"] ?? evType;
46465
+ const usage = failedResponse["usage"];
46466
+ if (usage) finalUsage = normalizeUsage(usage);
46467
+ }
46468
+ break;
46469
+ }
46470
+ if (evType === "error") {
46471
+ const code = event["code"];
46472
+ const message = event["message"];
46473
+ throw new Error(`Codex error: ${message ?? code ?? JSON.stringify(event)}`);
46474
+ }
46475
+ }
46476
+ const content = textParts.join("") || null;
46477
+ return {
46478
+ id: responseId ?? `resp-${Date.now().toString(36)}`,
46479
+ model: responseModel ?? request.model,
46480
+ content,
46481
+ toolCalls: toolCalls.length > 0 ? toolCalls : null,
46482
+ stopReason: finishReason ?? "completed",
46483
+ usage: finalUsage ?? { inputTokens: 0, outputTokens: 0 },
46484
+ providerData: {
46485
+ codex_response_id: responseId
46486
+ },
46487
+ raw: {}
46194
46488
  };
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
46489
  }
46490
+ // ---------------------------------------------------------------------------
46491
+ // LlmTransport — stream()
46492
+ // ---------------------------------------------------------------------------
46203
46493
  /**
46204
- * Stream a completion using the OpenAI Responses API SSE stream.
46494
+ * Stream a completion using the Responses API SSE stream.
46205
46495
  *
46206
46496
  * 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`.
46497
+ * events parsed from the raw SSE stream. The final delta carries
46498
+ * `stopReason` and `usage` from `response.completed`.
46499
+ *
46500
+ * Wire shape:
46501
+ * - `store: false` (required — codex backend rejects absent or true)
46502
+ * - `stream: true`
46503
+ * - `Accept: text/event-stream`
46504
+ * - `OpenAI-Beta: responses=experimental`
46209
46505
  *
46210
46506
  * @param request - Provider-neutral request parameters.
46211
46507
  * @param _ctx - Transport context (unused; `request.signal` handles abort).
46212
46508
  * @returns Async iterable of normalized delta chunks.
46213
46509
  */
46214
46510
  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
- );
46511
+ const body = this._buildBody(request, true);
46512
+ const headers = this._buildHeaders(true);
46513
+ const res = await fetch(this._endpointUrl, {
46514
+ method: "POST",
46515
+ headers,
46516
+ body: JSON.stringify(body),
46517
+ signal: request.signal
46518
+ });
46519
+ if (!res.ok) {
46520
+ throw new Error(await buildHttpError(res));
46521
+ }
46522
+ if (!res.body) {
46523
+ throw new Error("codex_responses: response body is null (no SSE stream)");
46524
+ }
46230
46525
  let finishReason = null;
46231
46526
  let finalUsage = null;
46232
- for await (const event of responseStream) {
46233
- const ev = event;
46234
- const evType = ev["type"];
46527
+ for await (const event of parseSSE(res.body, request.signal)) {
46528
+ const evType = event["type"];
46235
46529
  if (evType === "response.output_text.delta") {
46236
- const delta = ev["delta"];
46530
+ const delta = event["delta"];
46237
46531
  if (delta) {
46238
46532
  yield { text: delta, reasoning: "", stopReason: null, usage: null };
46239
46533
  }
46240
46534
  continue;
46241
46535
  }
46242
46536
  if (evType === "response.completed") {
46243
- const completedResponse = ev["response"] ?? null;
46537
+ const completedResponse = event["response"];
46244
46538
  if (completedResponse) {
46245
- finishReason = completedResponse.status ?? "completed";
46246
- const usage = completedResponse.usage;
46539
+ finishReason = completedResponse["status"] ?? "completed";
46540
+ const usage = completedResponse["usage"];
46247
46541
  if (usage) {
46248
- finalUsage = this._normalizeUsage(usage);
46542
+ finalUsage = normalizeUsage(usage);
46249
46543
  }
46250
46544
  }
46251
46545
  break;
46252
46546
  }
46253
46547
  if (evType === "response.failed" || evType === "response.incomplete") {
46254
- const failedResponse = ev["response"] ?? null;
46548
+ const failedResponse = event["response"];
46255
46549
  if (failedResponse) {
46256
- finishReason = failedResponse.status ?? evType;
46257
- const usage = failedResponse.usage;
46550
+ finishReason = failedResponse["status"] ?? evType;
46551
+ const usage = failedResponse["usage"];
46258
46552
  if (usage) {
46259
- finalUsage = this._normalizeUsage(usage);
46553
+ finalUsage = normalizeUsage(usage);
46260
46554
  }
46261
46555
  }
46262
46556
  break;
46263
46557
  }
46558
+ if (evType === "error") {
46559
+ const code = event["code"];
46560
+ const message = event["message"];
46561
+ throw new Error(`Codex error: ${message ?? code ?? JSON.stringify(event)}`);
46562
+ }
46264
46563
  }
46265
46564
  yield {
46266
46565
  text: "",
@@ -46270,128 +46569,60 @@ var init_codex_responses = __esm({
46270
46569
  };
46271
46570
  }
46272
46571
  // ---------------------------------------------------------------------------
46273
- // Input items construction
46572
+ // Private helpers
46274
46573
  // ---------------------------------------------------------------------------
46275
46574
  /**
46276
- * Convert provider-neutral messages to the Responses API `input` array.
46575
+ * Build the HTTP headers for a Codex request.
46277
46576
  *
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`.
46577
+ * Merges the auth/cloudflare headers from `_defaultHeaders` with the
46578
+ * mandatory Codex-specific headers:
46579
+ * - `OpenAI-Beta: responses=experimental`
46580
+ * - `content-type: application/json`
46581
+ * - `accept: text/event-stream` (always — both complete() and stream() use SSE)
46284
46582
  *
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.
46583
+ * The Authorization header is injected only if not already present in
46584
+ * `_defaultHeaders` (OAuth callers pre-set it via buildCodexOAuthHeaders).
46289
46585
  *
46290
- * @param request - Full transport request.
46291
- * @returns Responses API input items array.
46586
+ * @param streaming - Whether this is a streaming (SSE) or completion call.
46587
+ * @returns Plain string record of all request headers.
46292
46588
  */
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
- }
46589
+ _buildHeaders(streaming) {
46590
+ const headers = {
46591
+ ...this._defaultHeaders,
46592
+ "OpenAI-Beta": "responses=experimental",
46593
+ "content-type": "application/json",
46594
+ accept: streaming ? "text/event-stream" : "application/json"
46595
+ };
46596
+ const hasAuth = Object.keys(headers).some((k) => k.toLowerCase() === "authorization");
46597
+ if (!hasAuth) {
46598
+ headers["Authorization"] = `Bearer ${this._apiKey}`;
46318
46599
  }
46319
- return items;
46600
+ return headers;
46320
46601
  }
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
- }));
46340
- }
46341
- // ---------------------------------------------------------------------------
46342
- // Response normalization
46343
- // ---------------------------------------------------------------------------
46344
46602
  /**
46345
- * Normalize an OpenAI Responses API {@link Response} into a
46346
- * {@link NormalizedResponse}.
46603
+ * Build the JSON request body for a Codex Responses API call.
46347
46604
  *
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.
46605
+ * Mandatory fields per the Codex backend:
46606
+ * - `store: false` — backend rejects absent or `true`.
46607
+ * - `instructions` — system prompt (defaulting to empty string when absent).
46608
+ * - `input` — array of Responses API input items.
46356
46609
  *
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`.
46610
+ * @param request - Provider-neutral request.
46611
+ * @param streaming - Whether to enable SSE streaming.
46612
+ * @returns Serialisable request body.
46383
46613
  */
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
46614
+ _buildBody(request, streaming) {
46615
+ const body = {
46616
+ model: request.model,
46617
+ input: buildInputItems(request),
46618
+ instructions: request.system ?? "",
46619
+ store: false,
46620
+ stream: streaming
46389
46621
  };
46390
- const cached2 = usage.input_tokens_details?.cached_tokens;
46391
- if (typeof cached2 === "number" && cached2 > 0) {
46392
- normalized.cachedTokens = cached2;
46393
- }
46394
- return normalized;
46622
+ if (request.temperature != null) body.temperature = request.temperature;
46623
+ const tools = buildTools(request.tools);
46624
+ if (tools.length > 0) body.tools = tools;
46625
+ return body;
46395
46626
  }
46396
46627
  };
46397
46628
  }
@@ -75237,6 +75468,295 @@ var init_model_runner = __esm({
75237
75468
  }
75238
75469
  });
75239
75470
 
75471
+ // packages/core/src/llm/catalog-model-resolver.ts
75472
+ var catalog_model_resolver_exports = {};
75473
+ __export(catalog_model_resolver_exports, {
75474
+ _resetCatalogModelResolverCache: () => _resetCatalogModelResolverCache,
75475
+ catalogKeyForProvider: () => catalogKeyForProvider,
75476
+ listProviderModels: () => listProviderModels,
75477
+ resolveProviderDefaultModel: () => resolveProviderDefaultModel,
75478
+ validateModelForProvider: () => validateModelForProvider
75479
+ });
75480
+ function getDiskCatalog() {
75481
+ if (_catalog !== void 0) return _catalog;
75482
+ const dir = getCatalogDir();
75483
+ const latest = findLatestCacheFile(dir);
75484
+ if (!latest) {
75485
+ _catalog = null;
75486
+ return null;
75487
+ }
75488
+ _catalog = readCacheFile(latest);
75489
+ return _catalog;
75490
+ }
75491
+ function resolveProviderDefaultModel(providerCatalogKey, catalogOverride) {
75492
+ const catalog = catalogOverride ?? getDiskCatalog();
75493
+ if (!catalog) return null;
75494
+ const provider = catalog[providerCatalogKey];
75495
+ if (!provider?.models) return null;
75496
+ let latestId = null;
75497
+ let latestDate = "";
75498
+ for (const [id, entry] of Object.entries(provider.models)) {
75499
+ const rd = entry.release_date;
75500
+ if (!rd) continue;
75501
+ if (rd > latestDate) {
75502
+ latestDate = rd;
75503
+ latestId = id;
75504
+ }
75505
+ }
75506
+ return latestId;
75507
+ }
75508
+ function validateModelForProvider(model, providerCatalogKey, catalogOverride) {
75509
+ const catalog = catalogOverride ?? getDiskCatalog();
75510
+ if (!catalog) {
75511
+ return { valid: true, reason: "catalog-unavailable" };
75512
+ }
75513
+ const provider = catalog[providerCatalogKey];
75514
+ if (!provider?.models) {
75515
+ return { valid: true, reason: "provider-not-in-catalog" };
75516
+ }
75517
+ const found = Object.hasOwn(provider.models, model);
75518
+ return { valid: found, reason: found ? "found" : "not-found" };
75519
+ }
75520
+ function listProviderModels(providerCatalogKey, limit = 12, catalogOverride) {
75521
+ const catalog = catalogOverride ?? getDiskCatalog();
75522
+ if (!catalog) return [];
75523
+ const provider = catalog[providerCatalogKey];
75524
+ if (!provider?.models) return [];
75525
+ const entries = Object.entries(provider.models);
75526
+ const sorted = [...entries].sort(([, a], [, b]) => {
75527
+ const da = a.release_date ?? "";
75528
+ const db = b.release_date ?? "";
75529
+ if (da === db) return 0;
75530
+ return db.localeCompare(da);
75531
+ });
75532
+ return sorted.slice(0, Math.max(0, limit)).map(([id]) => id);
75533
+ }
75534
+ function catalogKeyForProvider(providerName) {
75535
+ const PROVIDER_KEY_MAP = {
75536
+ gemini: "google"
75537
+ // CLEO name → models.dev key overrides only (identical mappings are omitted)
75538
+ };
75539
+ return PROVIDER_KEY_MAP[providerName.toLowerCase()] ?? providerName.toLowerCase();
75540
+ }
75541
+ function _resetCatalogModelResolverCache() {
75542
+ _catalog = void 0;
75543
+ }
75544
+ var _catalog;
75545
+ var init_catalog_model_resolver = __esm({
75546
+ "packages/core/src/llm/catalog-model-resolver.ts"() {
75547
+ "use strict";
75548
+ init_catalog_cache();
75549
+ }
75550
+ });
75551
+
75552
+ // packages/core/src/llm/cross-provider-selector.ts
75553
+ import { totalmem } from "node:os";
75554
+ async function probeOllamaAlive(baseUrl) {
75555
+ const cached2 = _ollamaProbeCache.get(baseUrl);
75556
+ if (cached2 && Date.now() - cached2.checkedAt < OLLAMA_PROBE_TTL_MS) {
75557
+ return cached2.alive;
75558
+ }
75559
+ let alive = false;
75560
+ try {
75561
+ const controller = new AbortController();
75562
+ const timeoutId = setTimeout(() => controller.abort(), OLLAMA_PROBE_TIMEOUT_MS);
75563
+ try {
75564
+ const res = await fetch(baseUrl, { signal: controller.signal });
75565
+ alive = res.ok || res.status < 500;
75566
+ } finally {
75567
+ clearTimeout(timeoutId);
75568
+ }
75569
+ } catch {
75570
+ alive = false;
75571
+ }
75572
+ _ollamaProbeCache.set(baseUrl, { alive, checkedAt: Date.now() });
75573
+ return alive;
75574
+ }
75575
+ function ollamaDefaultModelForTier(tier, ramBytesOverride) {
75576
+ const ramBytes = ramBytesOverride ?? totalmem();
75577
+ const gb = ramBytes / 1024 ** 3;
75578
+ if (gb >= 8) {
75579
+ return tier === "fast" || tier === "local" ? "gemma4:e2b" : "gemma4:e4b";
75580
+ }
75581
+ if (gb >= 4) {
75582
+ return "gemma4:e2b";
75583
+ }
75584
+ logger4.warn(
75585
+ { ramGb: gb.toFixed(1), tier },
75586
+ "cross-provider-selector: RAM < 4 GB; selecting qwen2:0.5b (proof-of-life only \u2014 not suitable for production)"
75587
+ );
75588
+ return "qwen2:0.5b";
75589
+ }
75590
+ function isProvisioned(provider) {
75591
+ if (provider === "ollama") {
75592
+ if (process.env["OLLAMA_HOST"]?.trim()) return true;
75593
+ const stored = pickCredentialForProviderSync(provider, { strategy: "priorityOnly" });
75594
+ return stored !== null;
75595
+ }
75596
+ const cred = resolveCredentials(provider);
75597
+ return (cred.apiKey?.trim().length ?? 0) > 0;
75598
+ }
75599
+ function scoreProvider(provider, providerTier, taskTier, ollamaAlive, costInputPerMillion) {
75600
+ let score = TIER_BASE[providerTier];
75601
+ if (taskTier === "frontier" && providerTier === "frontier") {
75602
+ score += TASK_MATCH_BONUS_FRONTIER;
75603
+ } else if ((taskTier === "fast" || taskTier === "local") && (providerTier === "fast" || providerTier === "local")) {
75604
+ score += TASK_MATCH_BONUS_FAST;
75605
+ }
75606
+ if (providerTier === "local" && ollamaAlive) {
75607
+ score += LOCALITY_BONUS;
75608
+ }
75609
+ if ((providerTier === "frontier" || providerTier === "standard") && provider !== "ollama") {
75610
+ score += PROVISIONED_CLOUD_BIAS;
75611
+ }
75612
+ const penalty = Math.min(Math.floor(costInputPerMillion / 10), MAX_COST_PENALTY);
75613
+ score -= penalty;
75614
+ return score;
75615
+ }
75616
+ function roleTierFor(role) {
75617
+ switch (role) {
75618
+ case "consolidation":
75619
+ case "judgement":
75620
+ case "hygiene":
75621
+ return "frontier";
75622
+ case "extraction":
75623
+ case "derivation":
75624
+ return "standard";
75625
+ default:
75626
+ return "fast";
75627
+ }
75628
+ }
75629
+ async function selectBestProvisioned(role, opts) {
75630
+ const taskTier = roleTierFor(role);
75631
+ const provisionedIds = [];
75632
+ for (const id of BUILTIN_PROVIDER_IDS) {
75633
+ if (isProvisioned(id)) {
75634
+ provisionedIds.push(id);
75635
+ }
75636
+ }
75637
+ if (provisionedIds.length === 0) {
75638
+ logger4.warn(
75639
+ {
75640
+ providers_checked: BUILTIN_PROVIDER_IDS.slice(),
75641
+ reason: "no-provisioned-provider",
75642
+ role,
75643
+ projectRoot: opts.projectRoot
75644
+ },
75645
+ "resolveLLMForSystem: no provider has a usable credential; returning null-credential envelope"
75646
+ );
75647
+ return null;
75648
+ }
75649
+ const { getProviderProfile: getProviderProfile2 } = await Promise.resolve().then(() => (init_provider_registry(), provider_registry_exports));
75650
+ const { resolveProviderDefaultModel: resolveProviderDefaultModel2, catalogKeyForProvider: catalogKeyForProvider2 } = await Promise.resolve().then(() => (init_catalog_model_resolver(), catalog_model_resolver_exports));
75651
+ const { pickCredentialForProvider: pickCredentialForProvider2 } = await Promise.resolve().then(() => (init_credentials_store(), credentials_store_exports));
75652
+ const ollamaProvisioned = provisionedIds.includes("ollama");
75653
+ let ollamaAlive = false;
75654
+ if (ollamaProvisioned) {
75655
+ const profile = await getProviderProfile2("ollama");
75656
+ const ollamaBase = profile?.baseUrl ?? "http://localhost:11434";
75657
+ ollamaAlive = await probeOllamaAlive(ollamaBase);
75658
+ }
75659
+ const scored = [];
75660
+ for (const id of provisionedIds) {
75661
+ const profile = await getProviderProfile2(id);
75662
+ const tier = profile?.tier ?? "standard";
75663
+ const costInput = 0;
75664
+ const score = scoreProvider(id, tier, taskTier, ollamaAlive, costInput);
75665
+ const storedCred = await pickCredentialForProvider2(id, { strategy: "priorityOnly" });
75666
+ const credentialPriority = storedCred?.priority ?? Number.MAX_SAFE_INTEGER;
75667
+ scored.push({ id, tier, score, credentialPriority });
75668
+ }
75669
+ scored.sort((a, b) => {
75670
+ if (b.score !== a.score) return b.score - a.score;
75671
+ return a.credentialPriority - b.credentialPriority;
75672
+ });
75673
+ const winner = scored[0];
75674
+ if (!winner) return null;
75675
+ let model;
75676
+ if (winner.id === "ollama") {
75677
+ const ollamaProfile2 = await getProviderProfile2("ollama");
75678
+ const ramBytes = totalmem();
75679
+ const gb = ramBytes / 1024 ** 3;
75680
+ if (gb >= 8) {
75681
+ model = (winner.tier === "fast" || taskTier === "fast" ? ollamaProfile2?.defaultAuxModel : ollamaProfile2?.defaultModel) ?? ollamaDefaultModelForTier(winner.tier);
75682
+ } else if (gb >= 4) {
75683
+ model = ollamaProfile2?.defaultAuxModel ?? "gemma4:e2b";
75684
+ } else {
75685
+ model = ollamaDefaultModelForTier(winner.tier, ramBytes);
75686
+ }
75687
+ const catalogKey = catalogKeyForProvider2(winner.id);
75688
+ const catalogModel = resolveProviderDefaultModel2(catalogKey);
75689
+ if (!catalogModel?.startsWith("gemma4")) {
75690
+ logger4.info(
75691
+ { provider: "ollama", selectedModel: model },
75692
+ "cross-provider-selector: gemma4 family not in catalog snapshot \u2014 run `cleo llm refresh-catalog` to pull latest"
75693
+ );
75694
+ }
75695
+ } else {
75696
+ try {
75697
+ const catalogKey = catalogKeyForProvider2(winner.id);
75698
+ const catalogModel = resolveProviderDefaultModel2(catalogKey);
75699
+ const profileModel = (await getProviderProfile2(winner.id))?.defaultModel;
75700
+ model = catalogModel ?? profileModel ?? "unknown";
75701
+ } catch {
75702
+ model = (await getProviderProfile2(winner.id))?.defaultModel ?? "unknown";
75703
+ }
75704
+ }
75705
+ logger4.debug(
75706
+ {
75707
+ winner: winner.id,
75708
+ score: winner.score,
75709
+ taskTier,
75710
+ model,
75711
+ provisionedCount: provisionedIds.length
75712
+ },
75713
+ "cross-provider-selector: selected best provisioned provider"
75714
+ );
75715
+ return {
75716
+ provider: winner.id,
75717
+ model,
75718
+ credentialLabel: void 0,
75719
+ source: "cross-provider"
75720
+ };
75721
+ }
75722
+ 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;
75723
+ var init_cross_provider_selector = __esm({
75724
+ "packages/core/src/llm/cross-provider-selector.ts"() {
75725
+ "use strict";
75726
+ init_logger2();
75727
+ init_credentials2();
75728
+ init_credentials_store();
75729
+ logger4 = getLogger("llm-cross-provider-selector");
75730
+ BUILTIN_PROVIDER_IDS = [
75731
+ "anthropic",
75732
+ "openai",
75733
+ "gemini",
75734
+ "moonshot",
75735
+ "openrouter",
75736
+ "bedrock",
75737
+ "deepseek",
75738
+ "xai",
75739
+ "groq",
75740
+ "kimi-code",
75741
+ "ollama"
75742
+ ];
75743
+ TIER_BASE = {
75744
+ frontier: 100,
75745
+ standard: 70,
75746
+ fast: 50,
75747
+ local: 120
75748
+ };
75749
+ PROVISIONED_CLOUD_BIAS = 60;
75750
+ LOCALITY_BONUS = 30;
75751
+ TASK_MATCH_BONUS_FRONTIER = 20;
75752
+ TASK_MATCH_BONUS_FAST = 15;
75753
+ MAX_COST_PENALTY = 10;
75754
+ OLLAMA_PROBE_TIMEOUT_MS = 200;
75755
+ OLLAMA_PROBE_TTL_MS = 3e4;
75756
+ _ollamaProbeCache = /* @__PURE__ */ new Map();
75757
+ }
75758
+ });
75759
+
75240
75760
  // packages/core/src/llm/sealed-credential.ts
75241
75761
  function tokenPreview(token, authType) {
75242
75762
  const prefix = authType === "oauth" ? "oat-\u2026" : "\u2026";
@@ -75285,14 +75805,14 @@ function getRegisteredSystemDefault(key) {
75285
75805
  if (!isResolvableSystemDefault(record2.defaults)) return void 0;
75286
75806
  return record2.defaults;
75287
75807
  }
75288
- var logger4, REGISTRY;
75808
+ var logger5, REGISTRY;
75289
75809
  var init_system_of_use_registry = __esm({
75290
75810
  "packages/core/src/llm/system-of-use-registry.ts"() {
75291
75811
  "use strict";
75292
75812
  init_src();
75293
75813
  init_logger2();
75294
75814
  init_system_key();
75295
- logger4 = getLogger("llm-system-of-use-registry");
75815
+ logger5 = getLogger("llm-system-of-use-registry");
75296
75816
  REGISTRY = /* @__PURE__ */ new Map();
75297
75817
  }
75298
75818
  });
@@ -75380,7 +75900,7 @@ function resolveRegisteredSystemDefault(llm, systemKey) {
75380
75900
  }
75381
75901
  return void 0;
75382
75902
  }
75383
- function selectProviderModel(llm, role, systemKey, profileOverride) {
75903
+ async function selectProviderModel(llm, role, systemKey, profileOverride, projectRoot) {
75384
75904
  const roleEntry = llm?.roles?.[role];
75385
75905
  const pinnedProfile = resolveNamedProfile(llm, profileOverride, "profile");
75386
75906
  if (pinnedProfile) return pinnedProfile;
@@ -75409,6 +75929,15 @@ function selectProviderModel(llm, role, systemKey, profileOverride) {
75409
75929
  if (defaultProfile) return defaultProfile;
75410
75930
  const registeredDefault = resolveRegisteredSystemDefault(llm, systemKey);
75411
75931
  if (registeredDefault) return registeredDefault;
75932
+ try {
75933
+ const crossProviderResult = await selectBestProvisioned(role, {
75934
+ projectRoot: projectRoot ?? ""
75935
+ });
75936
+ if (crossProviderResult !== null) {
75937
+ return crossProviderResult;
75938
+ }
75939
+ } catch {
75940
+ }
75412
75941
  return {
75413
75942
  provider: IMPLICIT_FALLBACK_PROVIDER,
75414
75943
  model: IMPLICIT_FALLBACK_MODEL,
@@ -75468,11 +75997,12 @@ async function resolveLLMForRole(role, opts) {
75468
75997
  config2 = void 0;
75469
75998
  }
75470
75999
  const llmBlock = readLlmBlock(config2);
75471
- const { provider, model, credentialLabel, source } = selectProviderModel(
76000
+ const { provider, model, credentialLabel, source } = await selectProviderModel(
75472
76001
  llmBlock,
75473
76002
  role,
75474
76003
  opts?.systemKey,
75475
- opts?.profileOverride
76004
+ opts?.profileOverride,
76005
+ projectRoot
75476
76006
  );
75477
76007
  const { credential, usedLabel } = await resolveCredentialForRole(
75478
76008
  provider,
@@ -75550,6 +76080,7 @@ var init_role_resolver = __esm({
75550
76080
  init_credential_pool();
75551
76081
  init_credentials2();
75552
76082
  init_credentials_store();
76083
+ init_cross_provider_selector();
75553
76084
  init_fallback_model();
75554
76085
  init_sealed_credential();
75555
76086
  init_system_of_use_registry();