@sema-agent/client-core 0.110.0 → 0.112.0

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 (243) hide show
  1. package/CHANGELOG.md +244 -0
  2. package/README.md +59 -18
  3. package/dist/abortableSleep.d.ts +7 -0
  4. package/dist/abortableSleep.js +37 -0
  5. package/dist/adapt/arms.js +25 -4
  6. package/dist/adapt/toolCards.d.ts +3 -1
  7. package/dist/adapt/toolCards.js +4 -2
  8. package/dist/adapter/activeRunSelfHeal.d.ts +39 -0
  9. package/dist/adapter/activeRunSelfHeal.js +171 -3
  10. package/dist/adapter/downstream/eventToSdkMessage.js +10 -10
  11. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
  12. package/dist/adapter/downstream/terminalToSdkResult.js +11 -0
  13. package/dist/adapter/runStream.d.ts +5 -0
  14. package/dist/adapter/runStream.js +15 -3
  15. package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
  16. package/dist/attachmentRoute.d.ts +20 -0
  17. package/dist/attachmentRoute.js +39 -0
  18. package/dist/attachmentsWireCaps.js +1 -1
  19. package/dist/builtinOutputStyles.d.ts +12 -0
  20. package/dist/builtinOutputStyles.js +147 -0
  21. package/dist/checkpointRowVerdict.d.ts +3 -0
  22. package/dist/checkpointRowVerdict.js +14 -1
  23. package/dist/compactionStatus.d.ts +9 -0
  24. package/dist/compactionStatus.js +29 -5
  25. package/dist/consoleCaPin.d.ts +136 -0
  26. package/dist/consoleCaPin.js +827 -0
  27. package/dist/controlRouter.js +2 -1
  28. package/dist/coreValuePorts.d.ts +39 -1
  29. package/dist/coreValuePorts.js +10 -0
  30. package/dist/decideReceipt.d.ts +2 -1
  31. package/dist/deviceRebind.js +3 -3
  32. package/dist/displayUntrusted.d.ts +1 -0
  33. package/dist/displayUntrusted.js +6 -0
  34. package/dist/effectiveFacts.d.ts +9 -0
  35. package/dist/effectiveFacts.js +33 -0
  36. package/dist/engineAddress.d.ts +1 -0
  37. package/dist/engineAddress.js +4 -1
  38. package/dist/engineCredentialRelease.d.ts +54 -0
  39. package/dist/engineCredentialRelease.js +146 -0
  40. package/dist/engineErrorCodes.d.ts +1 -0
  41. package/dist/engineErrorCodes.js +1 -0
  42. package/dist/engineHealthProbeVerdict.d.ts +24 -0
  43. package/dist/engineHealthProbeVerdict.js +123 -0
  44. package/dist/engineNoticeCodes.js +5 -7
  45. package/dist/engineRepoint.d.ts +12 -5
  46. package/dist/engineRepoint.js +44 -19
  47. package/dist/engineRowRef.d.ts +3 -0
  48. package/dist/engineRowRef.js +11 -1
  49. package/dist/engineSwapCrossing.d.ts +57 -0
  50. package/dist/engineSwapCrossing.js +297 -0
  51. package/dist/engineUnavailable.d.ts +45 -0
  52. package/dist/engineUnavailable.js +496 -0
  53. package/dist/engineUrl.js +4 -2
  54. package/dist/eventLeg.js +3 -3
  55. package/dist/gateFailureCodes.js +2 -2
  56. package/dist/generated/sdkFactTables.d.ts +2 -1
  57. package/dist/generated/sdkFactTables.js +14 -0
  58. package/dist/generated/settingsSchemaFns.d.ts +1 -0
  59. package/dist/generated/settingsSchemaFns.js +3 -0
  60. package/dist/generated/settingsSchemaHooksClassifier.js +38 -9
  61. package/dist/generated/settingsSchemaTables.d.ts +56 -0
  62. package/dist/generated/settingsSchemaTables.js +19 -0
  63. package/dist/generated/settingsSchemaTypes.d.ts +23 -13
  64. package/dist/generated/settingsSchemaUrlShape.d.ts +1 -0
  65. package/dist/generated/settingsSchemaUrlShape.js +17 -0
  66. package/dist/hitl/approvalDelivery.d.ts +1 -0
  67. package/dist/hitl/approvalDelivery.js +1 -1
  68. package/dist/hitl/approvalResolution.d.ts +1 -1
  69. package/dist/hitl/approvalResolution.js +2 -1
  70. package/dist/hitl/askGateWire.js +5 -6
  71. package/dist/hitl/decisionRefusal.d.ts +6 -0
  72. package/dist/hitl/decisionRefusal.js +19 -10
  73. package/dist/hitl/decisionRefusalCarrier.d.ts +3 -0
  74. package/dist/hitl/decisionRefusalCarrier.js +348 -0
  75. package/dist/hitl/decisionSend.d.ts +7 -0
  76. package/dist/hitl/decisionSend.js +34 -12
  77. package/dist/hitl/editedApprovalAcceptance.js +1 -1
  78. package/dist/hitl/elicitationRefusal.d.ts +4 -0
  79. package/dist/hitl/elicitationRefusal.js +23 -0
  80. package/dist/hitl/frameRouter.d.ts +2 -2
  81. package/dist/hitl/frameRouter.js +8 -15
  82. package/dist/hitl/gateIdentity.d.ts +5 -0
  83. package/dist/hitl/gateIdentity.js +23 -0
  84. package/dist/hitl/hitlBridge.d.ts +6 -0
  85. package/dist/hitl/hitlBridge.js +10 -4
  86. package/dist/hitl/hitlHostSurface.d.ts +2 -1
  87. package/dist/hitl/hitlHostSurface.js +7 -2
  88. package/dist/hitl/localAllowRedemption.d.ts +70 -0
  89. package/dist/hitl/localAllowRedemption.js +276 -0
  90. package/dist/hitl/parkOwnership.js +2 -1
  91. package/dist/hitl/parkResolver.js +14 -4
  92. package/dist/hitl/persistedRulesWire.d.ts +16 -0
  93. package/dist/hitl/persistedRulesWire.js +70 -0
  94. package/dist/hitl/planReviewWire.d.ts +3 -0
  95. package/dist/hitl/planReviewWire.js +23 -6
  96. package/dist/hitl/policyDenyCause.d.ts +1 -1
  97. package/dist/hitl/policyDenyCause.js +1 -1
  98. package/dist/hitl/resumeRunningCard.d.ts +1 -0
  99. package/dist/hitl/resumeRunningCard.js +1 -0
  100. package/dist/hitl/screenCardCarriers.d.ts +15 -0
  101. package/dist/hitl/screenCardCarriers.js +41 -0
  102. package/dist/hitl/screenDecisionCards.d.ts +98 -0
  103. package/dist/hitl/screenDecisionCards.js +363 -0
  104. package/dist/hitl/sessionPolicyWire.js +2 -2
  105. package/dist/hitl/standingRows.d.ts +6 -2
  106. package/dist/hitl/standingRows.js +27 -2
  107. package/dist/hitl/toolApprovalWire.d.ts +30 -8
  108. package/dist/hitl/toolApprovalWire.js +264 -121
  109. package/dist/hooksReaderGenerations.js +1 -1
  110. package/dist/hooksWireCaps.js +3 -3
  111. package/dist/hostSettingNoticeArm.d.ts +3 -0
  112. package/dist/hostSettingNoticeArm.js +12 -0
  113. package/dist/hostTurnSettings.d.ts +3 -0
  114. package/dist/hostTurnSettings.js +38 -5
  115. package/dist/humanTurnRead.js +11 -1
  116. package/dist/i18n/messages.en.d.ts +292 -0
  117. package/dist/i18n/messages.en.js +298 -7
  118. package/dist/i18n/messages.zh.js +316 -6
  119. package/dist/i18n/translate.d.ts +3 -0
  120. package/dist/i18n/translate.js +78 -10
  121. package/dist/i18n/zh.d.ts +1 -0
  122. package/dist/i18n/zh.js +1 -0
  123. package/dist/index.d.ts +71 -20
  124. package/dist/index.js +44 -13
  125. package/dist/interactiveHalt.js +2 -1
  126. package/dist/interactiveInterrupt.js +20 -2
  127. package/dist/liveModelCatalog.d.ts +4 -0
  128. package/dist/liveModelCatalog.js +12 -3
  129. package/dist/localConfig/documents.d.ts +61 -0
  130. package/dist/localConfig/documents.js +51 -0
  131. package/dist/localConfig/kernel.d.ts +149 -0
  132. package/dist/localConfig/kernel.js +597 -0
  133. package/dist/localConfig/mcp/edit.d.ts +161 -0
  134. package/dist/localConfig/mcp/edit.js +846 -0
  135. package/dist/localConfig/mcp/servers.d.ts +165 -0
  136. package/dist/localConfig/mcp/servers.js +776 -0
  137. package/dist/localConfig/mcp/views.d.ts +170 -0
  138. package/dist/localConfig/mcp/views.js +429 -0
  139. package/dist/localConfig/modelPool.d.ts +262 -0
  140. package/dist/localConfig/modelPool.js +1489 -0
  141. package/dist/localConfig/modelPoolWords.d.ts +13 -0
  142. package/dist/localConfig/modelPoolWords.js +20 -0
  143. package/dist/localConfig/receiptShape.d.ts +7 -0
  144. package/dist/localConfig/receiptShape.js +135 -0
  145. package/dist/localConfig/settingsWrite.d.ts +127 -0
  146. package/dist/localConfig/settingsWrite.js +237 -0
  147. package/dist/localConfig/skills.d.ts +124 -0
  148. package/dist/localConfig/skills.js +397 -0
  149. package/dist/localConfig/skillsWords.d.ts +10 -0
  150. package/dist/localConfig/skillsWords.js +5 -0
  151. package/dist/localEngineSeatReading.d.ts +2 -2
  152. package/dist/localEngineSeatReading.js +5 -3
  153. package/dist/localEngineSpawn.d.ts +0 -7
  154. package/dist/localEngineSpawn.js +0 -92
  155. package/dist/localEngineStep.d.ts +2 -2
  156. package/dist/localEngineStep.js +2 -0
  157. package/dist/mcpProbeWire.js +2 -1
  158. package/dist/mcpReconnect.js +3 -3
  159. package/dist/mcpWireCaps.js +1 -1
  160. package/dist/memoryVerbsWire.js +3 -2
  161. package/dist/model/engineModelWords.d.ts +3 -0
  162. package/dist/model/engineModelWords.js +13 -0
  163. package/dist/model/onboardJudgement.d.ts +14 -4
  164. package/dist/model/onboardJudgement.js +91 -139
  165. package/dist/model/subagentModelWord.d.ts +21 -2
  166. package/dist/model/subagentModelWord.js +100 -29
  167. package/dist/modelCapabilityProbe.js +3 -40
  168. package/dist/modelConfigGapNotice.d.ts +20 -0
  169. package/dist/modelConfigGapNotice.js +102 -0
  170. package/dist/modsKernelIdentity.js +2 -47
  171. package/dist/notifications.d.ts +19 -0
  172. package/dist/notifications.js +114 -12
  173. package/dist/ownKey.d.ts +7 -0
  174. package/dist/ownKey.js +71 -8
  175. package/dist/pendingUserRow.d.ts +4 -0
  176. package/dist/pendingUserRow.js +16 -0
  177. package/dist/permissionsDeclaration.d.ts +3 -1
  178. package/dist/permissionsDeclaration.js +33 -9
  179. package/dist/planReviewCardFacts.d.ts +3 -0
  180. package/dist/planReviewCardFacts.js +52 -2
  181. package/dist/pluginHooksWire.d.ts +9 -1
  182. package/dist/pluginHooksWire.js +40 -6
  183. package/dist/printToolResultFrame.js +4 -2
  184. package/dist/promptBlockedCopy.js +12 -7
  185. package/dist/promptSuggestionBatch.d.ts +7 -0
  186. package/dist/promptSuggestionBatch.js +9 -0
  187. package/dist/promptSuggestionsTail.d.ts +39 -0
  188. package/dist/promptSuggestionsTail.js +116 -0
  189. package/dist/request/taskRequest.js +10 -6
  190. package/dist/requestCoordinate.d.ts +1 -0
  191. package/dist/requestCoordinate.js +13 -0
  192. package/dist/resumeRefusalCopy.d.ts +3 -1
  193. package/dist/resumeRefusalCopy.js +12 -4
  194. package/dist/rewindArchiveReading.js +4 -4
  195. package/dist/runCancelContext.d.ts +2 -1
  196. package/dist/runCancelContext.js +1 -0
  197. package/dist/runLedgerSnapshot.js +3 -3
  198. package/dist/runPermissionModeSwitch.js +3 -4
  199. package/dist/scenarioRefusal.js +11 -13
  200. package/dist/scenarioSkills.js +11 -6
  201. package/dist/seam.d.ts +16 -2
  202. package/dist/seam.js +10 -0
  203. package/dist/seatContract.d.ts +259 -9
  204. package/dist/seatContract.js +444 -15
  205. package/dist/sessionCostReading.d.ts +70 -0
  206. package/dist/sessionCostReading.js +150 -0
  207. package/dist/sessionHooksRoster.d.ts +4 -0
  208. package/dist/sessionHooksRoster.js +158 -0
  209. package/dist/sessionIdSegment.d.ts +1 -0
  210. package/dist/sessionIdSegment.js +4 -1
  211. package/dist/sessionMemoryErase.js +2 -1
  212. package/dist/sessionMemoryStatus.js +2 -2
  213. package/dist/sessionReadRedaction.d.ts +7 -0
  214. package/dist/sessionReadRedaction.js +26 -0
  215. package/dist/sessionSyncFileHistory.js +8 -9
  216. package/dist/settingsHooksSalvage.js +2 -1
  217. package/dist/sha256Bytes.d.ts +1 -0
  218. package/dist/sha256Bytes.js +46 -0
  219. package/dist/skillOverrideChain.d.ts +83 -0
  220. package/dist/skillOverrideChain.js +378 -0
  221. package/dist/skillsWireCaps.d.ts +3 -0
  222. package/dist/skillsWireCaps.js +16 -1
  223. package/dist/sseIdleTriage.js +2 -1
  224. package/dist/storeDurabilityWord.d.ts +15 -0
  225. package/dist/storeDurabilityWord.js +18 -0
  226. package/dist/storePostureCapability.d.ts +7 -3
  227. package/dist/storePostureCapability.js +69 -39
  228. package/dist/subagent/engineSubagentResume.js +2 -2
  229. package/dist/subagent/engineSubagentTail.js +2 -1
  230. package/dist/subagentContentStore.d.ts +11 -0
  231. package/dist/subagentContentStore.js +28 -2
  232. package/dist/toolResult.d.ts +1 -0
  233. package/dist/toolResult.js +2 -0
  234. package/dist/turnFailurePresentation.js +4 -10
  235. package/dist/userAttachmentEnvelope.d.ts +2 -1
  236. package/dist/userAttachmentEnvelope.js +10 -0
  237. package/dist/wireErrorTriage.js +2 -1
  238. package/dist/wireFailureShape.d.ts +4 -0
  239. package/dist/wireFailureShape.js +141 -19
  240. package/dist/wireRefusalCopy.d.ts +6 -5
  241. package/dist/wireRefusalCopy.js +49 -92
  242. package/docs/INTEGRATION-CLIENTS.md +2718 -17
  243. package/package.json +7 -3
@@ -0,0 +1,1489 @@
1
+ import { PROVIDER_PRESETS, inferFamily } from '../model/providerPresets.js';
2
+ import { SETTINGS_MODEL_API_WORDS } from '../generated/settingsSchemaTables.js';
3
+ import { SETTINGS_NARROW_AT_MENTION, SETTINGS_RESOLVE_ACTIVE_TIERS } from '../generated/settingsSchemaFns.js';
4
+ import { isLoopbackWireUrl } from '../engineWireSdk.js';
5
+ import { resolveCatalogDefaultModel } from '../model/catalogDefault.js';
6
+ import { TIER_ORDER, isTier, resolveTierBinding } from '../model/tierVocabulary.js';
7
+ import { computeDeleteBlockers, computeDeleteWarnings, resolveEntryVision } from '../model/modelSupplyRules.js';
8
+ import { routeKeyOf } from '../coreValuePorts.js';
9
+ import { applyProbeToEntry } from '../modelCapabilityProbe.js';
10
+ import { KEY_SLOT_PREFIX, keyEnvNameForEntry, onboardWriteVerdict, poolEntryNameOf, onboardBaseUrlProblem, onboardEntryCredentialIssue, onboardKeyCarriedBy, onboardRedactKey, } from '../model/onboardJudgement.js';
11
+ import { ownSnapshotRecord, ownSnapshotRecordFaithful, readOwnDataValue } from '../ownKey.js';
12
+ import { msg, renderMessage } from '../i18n/translate.js';
13
+ import { applyLocalConfigIntent, previewLocalConfigIntent } from './kernel.js';
14
+ import { settingsWritePlan } from './settingsWrite.js';
15
+ import { MODEL_POOL_CHANNEL_SOURCES, MODEL_POOL_EDIT_KINDS, MODEL_POOL_MAX_BATCH } from './modelPoolWords.js';
16
+ export { MODEL_POOL_EDIT_KINDS, MODEL_POOL_CHANNEL_SOURCES, MODEL_POOL_CREDENTIAL_KINDS, MODEL_POOL_COMPLETENESS, MODEL_POOL_MAX_BATCH, MODEL_POOL_PROBE_STATES, MODEL_POOL_DEFAULT_KEEP_REASONS } from './modelPoolWords.js';
17
+ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
18
+ function own(v, key) {
19
+ if (typeof v !== 'object' || v === null)
20
+ return undefined;
21
+ try {
22
+ return readOwnDataValue(v, key);
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ function cloneDoc(v) {
29
+ const snap = ownSnapshotRecordFaithful(v);
30
+ return snap === undefined ? undefined : JSON.parse(JSON.stringify(snap));
31
+ }
32
+ const freezeDoc = (v) => ownSnapshotRecord(v) ?? Object.freeze({});
33
+ function sameJson(a, b) {
34
+ if (a === b)
35
+ return true;
36
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
37
+ return false;
38
+ if (Array.isArray(a) !== Array.isArray(b))
39
+ return false;
40
+ if (Array.isArray(a) && Array.isArray(b))
41
+ return a.length === b.length && a.every((x, i) => sameJson(x, b[i]));
42
+ const ka = Object.keys(a);
43
+ const kb = Object.keys(b);
44
+ return ka.length === kb.length && ka.every((k) => Object.hasOwn(b, k) && sameJson(a[k], b[k]));
45
+ }
46
+ const en = (key, params) => renderMessage(msg(key, params));
47
+ const strOf = (v) => (typeof v === 'string' && v !== '' ? v : undefined);
48
+ const posInt = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v > 0;
49
+ const probeStateOf = (verdict) => (typeof verdict !== 'string' || verdict === '' ? 'unprobed' : verdict === 'ok' ? 'verified' : 'unverified');
50
+ const ADD_REFUSAL_ORDER = Object.freeze(['changed_during_write', 'credential_unpaired', 'probe_failed', 'probe_unverified', 'invalid_request']);
51
+ const batchRefusalOf = (problems) => ADD_REFUSAL_ORDER.find((r) => problems.some((p) => p.reason === r)) ?? 'invalid_request';
52
+ const CATALOG_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/i;
53
+ const refuse = (reason, problems = [], detail) => Object.freeze({ ok: false, reason, problems: Object.freeze([...problems]), ...(detail === undefined ? {} : { detail }) });
54
+ const problem = (reason, detail, at) => Object.freeze({ reason, detail, ...(at?.index === undefined ? {} : { index: at.index }), ...(at?.name === undefined ? {} : { name: at.name }) });
55
+ function modelsDocOf(raw) {
56
+ if (raw === null || raw === undefined) {
57
+ const models = [];
58
+ return { ok: true, doc: { models }, models };
59
+ }
60
+ const doc = cloneDoc(raw);
61
+ if (doc === undefined)
62
+ return { ok: false, detail: en('onboard.plan.notObject') };
63
+ const list = doc['models'];
64
+ if (list !== undefined && !Array.isArray(list))
65
+ return { ok: false, detail: en('onboard.plan.modelsNotList') };
66
+ const models = Array.isArray(list) ? list : [];
67
+ if (models.some((m) => !isRecord(m)))
68
+ return { ok: false, detail: en('onboard.plan.modelsItemNotObject') };
69
+ doc['models'] = models;
70
+ return { ok: true, doc, models: models };
71
+ }
72
+ function defaultSlotOf(doc) {
73
+ const roles = doc['roles'];
74
+ if (roles !== undefined && !isRecord(roles))
75
+ return { kind: 'unreadable' };
76
+ const target = isRecord(roles) ? roles['default'] : undefined;
77
+ if (target !== undefined) {
78
+ if (!isRecord(target))
79
+ return { kind: 'unreadable' };
80
+ const model = target['model'];
81
+ const select = target['select'];
82
+ if (model !== undefined && typeof model !== 'string')
83
+ return { kind: 'unreadable' };
84
+ if ((model === undefined || model === '') && select !== undefined && !isRecord(select))
85
+ return { kind: 'unreadable' };
86
+ return { kind: 'set', target, legacy: false };
87
+ }
88
+ const legacy = doc['default'];
89
+ if (legacy === undefined)
90
+ return { kind: 'absent' };
91
+ return typeof legacy === 'string' && legacy !== '' ? { kind: 'set', target: { model: legacy }, legacy: true } : { kind: 'unreadable' };
92
+ }
93
+ function withDefaultFolded(doc) {
94
+ if (!Object.hasOwn(doc, 'default'))
95
+ return doc;
96
+ const slot = defaultSlotOf(doc);
97
+ if (slot.kind !== 'set')
98
+ return doc;
99
+ const out = { ...doc };
100
+ delete out['default'];
101
+ if (slot.legacy)
102
+ out['roles'] = { ...(isRecord(doc['roles']) ? doc['roles'] : {}), default: slot.target };
103
+ return out;
104
+ }
105
+ function catalogRowsOf(items) {
106
+ const rows = [];
107
+ const seen = new Set();
108
+ let unreadable = 0;
109
+ for (const m of items) {
110
+ const n = isRecord(m) ? strOf(m['name']) : undefined;
111
+ if (n === undefined || !CATALOG_NAME_RE.test(n) || seen.has(n) || typeof m['id'] !== 'string') {
112
+ unreadable += 1;
113
+ continue;
114
+ }
115
+ seen.add(n);
116
+ rows.push(m);
117
+ }
118
+ return { rows, unreadable };
119
+ }
120
+ function settingsDocOf(raw) {
121
+ if (raw === null || raw === undefined)
122
+ return { ok: true, doc: {}, env: {} };
123
+ const doc = cloneDoc(raw);
124
+ if (doc === undefined)
125
+ return { ok: false, detail: en('onboard.plan.envNotObject') };
126
+ const env = doc['env'];
127
+ if (env !== undefined && !isRecord(env))
128
+ return { ok: false, detail: en('onboard.plan.envNotObject') };
129
+ return { ok: true, doc, env: isRecord(env) ? env : {} };
130
+ }
131
+ const entryName = (m) => strOf(m['name']);
132
+ const isEnabled = (m) => m['enabled'] !== false;
133
+ const isShellSlotIn = (env) => (ref) => typeof ref === 'string' && ref.startsWith(KEY_SLOT_PREFIX) && typeof env[ref] === 'string';
134
+ function referencedSlotsOf(models) {
135
+ const out = new Set();
136
+ for (const m of models)
137
+ if (typeof m['apiKeyEnv'] === 'string')
138
+ out.add(m['apiKeyEnv'].toUpperCase());
139
+ return out;
140
+ }
141
+ function orphanKeySlotsOf(models, env) {
142
+ const referenced = referencedSlotsOf(models);
143
+ const isSlot = isShellSlotIn(env);
144
+ return Object.keys(env).filter((k) => isSlot(k) && !referenced.has(k.toUpperCase()));
145
+ }
146
+ function envModelOf(env) {
147
+ return [env['MODEL_ID'], env['MODEL_CHEAP_ID']].find((v) => typeof v === 'string' && v.trim() !== '');
148
+ }
149
+ const envServesModels = (models, env) => models.some(isEnabled) ? undefined : envModelOf(env);
150
+ function tierGroupsOf(doc) {
151
+ const g = doc['tierGroups'];
152
+ return Array.isArray(g) ? g.filter(isRecord) : [];
153
+ }
154
+ function legacyTierGroupsOf(settings) {
155
+ const raw = settings['semaTierGroups'];
156
+ if (!isRecord(raw) || typeof raw['active'] !== 'string' || !isRecord(raw['groups']))
157
+ return null;
158
+ const groups = {};
159
+ const defaultTiers = {};
160
+ for (const [name, def] of Object.entries(raw['groups'])) {
161
+ if (!isRecord(def) || !isRecord(def['tiers']))
162
+ continue;
163
+ const tiers = {};
164
+ for (const [k, v] of Object.entries(def['tiers']))
165
+ if (isTier(k) && typeof v === 'string' && v !== '')
166
+ tiers[k] = v;
167
+ groups[name] = { tiers };
168
+ defaultTiers[name] = isTier(def['defaultTier']) ? def['defaultTier'] : undefined;
169
+ }
170
+ if (Object.keys(groups).length === 0)
171
+ return null;
172
+ const active = Object.hasOwn(groups, raw['active']) ? raw['active'] : Object.keys(groups)[0];
173
+ return { active, groups, defaultTiers };
174
+ }
175
+ function legacyRostersOf(settings) {
176
+ const raw = settings['semaRosters'];
177
+ if (!isRecord(raw) || typeof raw['active'] !== 'string' || !isRecord(raw['rosters']))
178
+ return null;
179
+ const rosters = {};
180
+ for (const [name, def] of Object.entries(raw['rosters'])) {
181
+ if (!isRecord(def) || !isRecord(def['slots']))
182
+ continue;
183
+ const slots = {};
184
+ for (const [k, v] of Object.entries(def['slots']))
185
+ if (typeof v === 'string')
186
+ slots[k] = v;
187
+ rosters[name] = { slots };
188
+ }
189
+ return { active: raw['active'], rosters };
190
+ }
191
+ function settingsOrEmpty(raw) {
192
+ const r = settingsDocOf(raw);
193
+ return r.ok ? r.doc : (cloneDoc(raw) ?? {});
194
+ }
195
+ function settingsAfter(doc, edits) {
196
+ if (edits.length === 0)
197
+ return { ok: true, next: doc };
198
+ const r = settingsWritePlan({ doc: 'userSettings', current: doc, trust: 'no_trust_step', edits });
199
+ if (!r.ok)
200
+ return { ok: false, reason: r.reason, ...(r.detail === undefined ? {} : { detail: r.detail }) };
201
+ return { ok: true, next: cloneDoc(r.next) ?? {} };
202
+ }
203
+ function shadowDisclosuresOf(settings) {
204
+ const model = own(settings, 'model');
205
+ return typeof model === 'string' && model.trim() !== '' ? Object.freeze([Object.freeze({ kind: 'shadowed_by', layer: 'userSettings', detail: 'model' })]) : Object.freeze([]);
206
+ }
207
+ const noneDefault = (disclosures) => Object.freeze({ kind: 'none', disclosures });
208
+ const unknownDefault = (disclosures) => Object.freeze({ kind: 'unknown', disclosures });
209
+ export function effectiveDefaultModelOf(input) {
210
+ const disclosures = shadowDisclosuresOf(own(input, 'userSettings'));
211
+ try {
212
+ const raw = own(input, 'modelsDoc');
213
+ if (raw === null || raw === undefined)
214
+ return noneDefault(disclosures);
215
+ const doc = cloneDoc(raw);
216
+ if (doc === undefined)
217
+ return unknownDefault(disclosures);
218
+ const list = doc['models'];
219
+ if (list !== undefined && !Array.isArray(list))
220
+ return unknownDefault(disclosures);
221
+ const folded = withDefaultFolded(doc);
222
+ if (defaultSlotOf(folded).kind === 'unreadable')
223
+ return unknownDefault(disclosures);
224
+ const enabled = catalogRowsOf(Array.isArray(list) ? list : []).rows.filter(isEnabled).map((m) => strOf(m['name']));
225
+ if (enabled.length === 0)
226
+ return noneDefault(disclosures);
227
+ const set = new Set(enabled);
228
+ const r = resolveCatalogDefaultModel(folded, (n) => set.has(n), enabled[0]);
229
+ if (r.kind === 'select')
230
+ return Object.freeze({ kind: 'select', select: r.select, disclosures });
231
+ return Object.freeze({ kind: 'named', name: r.name, source: r.source, dangling: r.dangling, disclosures });
232
+ }
233
+ catch {
234
+ return unknownDefault(disclosures);
235
+ }
236
+ }
237
+ const keepDefault = (reason) => Object.freeze({ kind: 'unchanged', reason });
238
+ export function defaultModelChoiceOf(existing, batch, declined) {
239
+ try {
240
+ if (declined === true)
241
+ return keepDefault('declined');
242
+ if (!Array.isArray(batch) || batch.length === 0)
243
+ return keepDefault('nothing_added');
244
+ const kind = own(existing, 'kind');
245
+ if (kind === 'named') {
246
+ const source = own(existing, 'source');
247
+ if (typeof source !== 'string')
248
+ return keepDefault('existing_unknown');
249
+ if (source !== 'fallback')
250
+ return keepDefault('has_default');
251
+ }
252
+ else if (kind === 'select')
253
+ return keepDefault('has_default');
254
+ else if (kind === 'unknown')
255
+ return keepDefault('existing_unknown');
256
+ else if (kind !== 'none')
257
+ return keepDefault('existing_unknown');
258
+ for (let i = 0; i < batch.length; i++)
259
+ if (own(own(batch, String(i)), 'probe') === 'verified')
260
+ return Object.freeze({ kind: 'pick', index: i });
261
+ return keepDefault('none_verified');
262
+ }
263
+ catch {
264
+ return keepDefault('existing_unknown');
265
+ }
266
+ }
267
+ function specOf(v, index) {
268
+ const bad = (detail) => ({ ok: false, problem: problem('invalid_request', detail, { index }) });
269
+ if (!isRecord(v))
270
+ return bad(en('onboard.refusal.input'));
271
+ const modelId = own(v, 'modelId');
272
+ const api = own(v, 'api');
273
+ const baseUrl = own(v, 'baseUrl');
274
+ const cred = own(v, 'credential');
275
+ if (typeof modelId !== 'string' || modelId.trim() === '' || modelId.length > 512)
276
+ return bad(en('onboard.refusal.input'));
277
+ if (!SETTINGS_MODEL_API_WORDS.some((w) => w === api))
278
+ return bad(en('onboard.refusal.input'));
279
+ if (typeof baseUrl !== 'string')
280
+ return bad(en('onboard.baseUrl.invalid'));
281
+ const urlProblem = onboardBaseUrlProblem(baseUrl);
282
+ if (urlProblem !== undefined)
283
+ return bad(urlProblem);
284
+ const credKind = own(cred, 'kind');
285
+ const apiKey = own(cred, 'apiKey');
286
+ let credential;
287
+ if (credKind === 'none')
288
+ credential = { kind: 'none' };
289
+ else if (credKind === 'key' && typeof apiKey === 'string' && apiKey.trim() !== '')
290
+ credential = { kind: 'key', apiKey };
291
+ else
292
+ return bad(en('onboard.refusal.input'));
293
+ const label = own(v, 'label');
294
+ const vision = own(v, 'vision');
295
+ const ctx = own(v, 'contextWindow');
296
+ const max = own(v, 'maxTokens');
297
+ const channel = own(v, 'channel');
298
+ const probeRaw = own(v, 'probe');
299
+ const accept = own(v, 'acceptUnverified');
300
+ if ((label !== undefined && (typeof label !== 'string' || label.length > 200)) || (vision !== undefined && typeof vision !== 'boolean'))
301
+ return bad(en('onboard.refusal.input'));
302
+ if (accept !== undefined && typeof accept !== 'boolean')
303
+ return bad(en('onboard.refusal.input'));
304
+ if ((ctx !== undefined && !posInt(ctx)) || (max !== undefined && !posInt(max)))
305
+ return bad(en('onboard.refusal.input'));
306
+ if (channel !== undefined && (typeof channel !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(channel)))
307
+ return bad(en('onboard.refusal.input'));
308
+ let probe;
309
+ if (probeRaw !== undefined) {
310
+ const verdict = own(probeRaw, 'verdict');
311
+ const at = own(probeRaw, 'at');
312
+ if (typeof verdict !== 'string' || verdict === '' || typeof at !== 'number' || !Number.isFinite(at))
313
+ return bad(en('onboard.refusal.input'));
314
+ probe = { verdict: verdict, at };
315
+ }
316
+ const thinking = own(v, 'thinking');
317
+ return {
318
+ ok: true,
319
+ spec: {
320
+ modelId,
321
+ api: api,
322
+ baseUrl,
323
+ credential,
324
+ ...(typeof label === 'string' && label !== '' ? { label } : {}),
325
+ ...(typeof vision === 'boolean' ? { vision } : {}),
326
+ ...(posInt(ctx) ? { contextWindow: ctx } : {}),
327
+ ...(posInt(max) ? { maxTokens: max } : {}),
328
+ ...(typeof channel === 'string' ? { channel } : {}),
329
+ ...(probe === undefined ? {} : { probe }),
330
+ ...(accept === true ? { acceptUnverified: true } : {}),
331
+ ...(thinking === undefined ? {} : { thinking }),
332
+ },
333
+ };
334
+ }
335
+ function otherIssuesOf(models, skip, env, say) {
336
+ const out = [];
337
+ for (const m of models) {
338
+ const name = entryName(m);
339
+ if (!isEnabled(m) || (name !== undefined && skip.has(name)))
340
+ continue;
341
+ const issue = onboardEntryCredentialIssue(m, env);
342
+ if (issue !== undefined)
343
+ out.push(Object.freeze({ name: name ?? '?', modelId: strOf(m['id']) ?? '?', issue: say(issue) }));
344
+ }
345
+ return out;
346
+ }
347
+ function planAdd(rawEntries, defaultIndex, modelsRaw, settingsRaw, env, pins, declineDefault) {
348
+ if (!Array.isArray(rawEntries) || rawEntries.length === 0 || rawEntries.length > MODEL_POOL_MAX_BATCH)
349
+ return refuse('invalid_request', [problem('invalid_request', en('onboard.refusal.input'))]);
350
+ const specs = [];
351
+ const problems = [];
352
+ for (let i = 0; i < rawEntries.length; i++) {
353
+ const r = specOf(own(rawEntries, String(i)), i);
354
+ if (r.ok)
355
+ specs.push(r.spec);
356
+ else
357
+ problems.push(r.problem);
358
+ }
359
+ const keys = specs.flatMap((s) => (s.credential.kind === 'key' ? [s.credential.apiKey] : []));
360
+ const say = (t) => keys.reduce((acc, k) => onboardRedactKey(acc, k), t);
361
+ if (problems.length > 0)
362
+ return refuse('invalid_request', problems.map((p) => ({ ...p, detail: say(p.detail) })));
363
+ if (defaultIndex !== undefined && (!Number.isSafeInteger(defaultIndex) || defaultIndex < 0 || defaultIndex >= specs.length)) {
364
+ return refuse('invalid_request', [problem('invalid_request', en('onboard.refusal.input'))]);
365
+ }
366
+ const mread = modelsDocOf(modelsRaw);
367
+ if (!mread.ok)
368
+ return refuse('scope_unreadable', [], mread.detail);
369
+ const { models } = mread;
370
+ const doc = withDefaultFolded(mread.doc);
371
+ const envModel = envServesModels(models, env);
372
+ if (envModel !== undefined)
373
+ return refuse('env_catalog_present', [], say(en('onboard.plan.envCatalogPresent', { model: envModel })));
374
+ const sread = settingsDocOf(settingsRaw);
375
+ if (!sread.ok)
376
+ return refuse('scope_unreadable', [], sread.detail);
377
+ const prevEnv = sread.env;
378
+ const isShellSlot = isShellSlotIn(prevEnv);
379
+ const original = models.length;
380
+ const taken = new Set(models.map(entryName).filter((n) => n !== undefined));
381
+ const usedSlots = new Set();
382
+ for (const m of models)
383
+ if (typeof m['apiKeyEnv'] === 'string')
384
+ usedSlots.add(m['apiKeyEnv'].toUpperCase());
385
+ for (const k of Object.keys(prevEnv))
386
+ usedSlots.add(k.toUpperCase());
387
+ for (const [k, v] of Object.entries(env))
388
+ if (typeof v === 'string')
389
+ usedSlots.add(k.toUpperCase());
390
+ const referenced = referencedSlotsOf(models);
391
+ const orphan = (slot) => isShellSlot(slot) && !referenced.has(slot.toUpperCase());
392
+ const planned = [];
393
+ const replaced = [];
394
+ const slotEdits = [];
395
+ const envView = { ...env };
396
+ const targets = new Set();
397
+ for (let i = 0; i < specs.length; i++) {
398
+ const e = specs[i];
399
+ const pin = pins?.entries[i];
400
+ const verdict = onboardWriteVerdict(e.probe?.verdict, e.acceptUnverified === true);
401
+ if (!verdict.write) {
402
+ problems.push(problem(verdict.refusal, say(en(verdict.refusal === 'probe_failed' ? 'model.pool.add.probeFailed' : 'model.pool.add.probeUnverified', { model: e.modelId })), { index: i }));
403
+ continue;
404
+ }
405
+ const targetKey = routeKeyOf(e.baseUrl);
406
+ if (targetKey === undefined) {
407
+ problems.push(problem('credential_unpaired', say(en('onboard.catalog.judgeAbsent', { name: e.modelId })), { index: i }));
408
+ continue;
409
+ }
410
+ const sig = `${e.modelId}\u0000${e.api}\u0000${targetKey}`;
411
+ if (targets.has(sig)) {
412
+ problems.push(problem('invalid_request', say(en('model.pool.add.duplicate', { model: e.modelId })), { index: i }));
413
+ continue;
414
+ }
415
+ targets.add(sig);
416
+ const keyed = e.credential.kind === 'key';
417
+ const sameTarget = (m) => m['id'] === e.modelId && m['api'] === e.api && typeof m['baseUrl'] === 'string' && routeKeyOf(m['baseUrl']) === targetKey;
418
+ const existingIdx = models.findIndex((m, idx) => idx < original &&
419
+ sameTarget(m) &&
420
+ isEnabled(m) &&
421
+ m['sealedApiKey'] === undefined &&
422
+ (keyed ? m['apiKeyEnv'] === undefined || isShellSlot(m['apiKeyEnv']) : m['apiKeyEnv'] === undefined));
423
+ let name;
424
+ if (pin !== undefined) {
425
+ name = pin.name;
426
+ const holder = models.findIndex((m) => entryName(m) === name);
427
+ if (holder >= 0 && holder !== existingIdx) {
428
+ problems.push(problem('changed_during_write', say(en('model.pool.add.pinMoved', { name })), { index: i, name }));
429
+ continue;
430
+ }
431
+ }
432
+ else
433
+ name = existingIdx >= 0 ? String(models[existingIdx]['name']) : poolEntryNameOf(e.modelId, e.channel ?? 'custom', taken);
434
+ taken.add(name);
435
+ let keyEnv = null;
436
+ if (keyed) {
437
+ const apiKey = e.credential.apiKey;
438
+ if (pin !== undefined) {
439
+ if (pin.keyEnv === null || prevEnv[pin.keyEnv] !== apiKey) {
440
+ problems.push(problem('changed_during_write', say(en('model.pool.add.pinMoved', { name })), { index: i, name }));
441
+ continue;
442
+ }
443
+ keyEnv = pin.keyEnv;
444
+ }
445
+ else {
446
+ const existingRef = existingIdx >= 0 ? models[existingIdx]['apiKeyEnv'] : undefined;
447
+ const sharedByOther = typeof existingRef === 'string' &&
448
+ models.some((m, idx) => idx !== existingIdx && typeof m['apiKeyEnv'] === 'string' && m['apiKeyEnv'].toUpperCase() === existingRef.toUpperCase());
449
+ if (isShellSlot(existingRef) && !sharedByOther && prevEnv[existingRef] === apiKey)
450
+ keyEnv = existingRef;
451
+ else {
452
+ const baseSlot = keyEnvNameForEntry(name);
453
+ let slot = baseSlot;
454
+ if (!(orphan(slot) && !slotEdits.some((s) => s.member === slot)))
455
+ for (let n = 2; usedSlots.has(slot.toUpperCase()); n++)
456
+ slot = `${baseSlot}_${n}`;
457
+ keyEnv = slot;
458
+ }
459
+ usedSlots.add(keyEnv.toUpperCase());
460
+ if (prevEnv[keyEnv] !== apiKey)
461
+ slotEdits.push({ intent: 'modelKeySlot', member: keyEnv, op: 'set', value: apiKey });
462
+ }
463
+ envView[keyEnv] = apiKey;
464
+ if (onboardKeyCarriedBy(name, apiKey) || onboardKeyCarriedBy(keyEnv, apiKey)) {
465
+ problems.push(problem('invalid_request', say(en('onboard.input.keyInFields')), { index: i }));
466
+ continue;
467
+ }
468
+ }
469
+ const base = existingIdx >= 0 ? models[existingIdx] : {};
470
+ let entry = {
471
+ ...base,
472
+ name,
473
+ id: e.modelId,
474
+ provider: e.api === 'anthropic-messages' ? 'anthropic' : 'gateway',
475
+ api: e.api,
476
+ baseUrl: e.baseUrl,
477
+ ...(typeof base['contextWindow'] === 'number' ? {} : e.contextWindow !== undefined ? { contextWindow: e.contextWindow } : {}),
478
+ ...(typeof base['maxTokens'] === 'number' ? {} : e.maxTokens !== undefined ? { maxTokens: e.maxTokens } : {}),
479
+ ...(keyEnv === null ? {} : { apiKeyEnv: keyEnv }),
480
+ semaChannel: typeof base['semaChannel'] === 'string' ? base['semaChannel'] : (e.channel ?? 'custom'),
481
+ ...(typeof base['semaLabel'] === 'string' ? {} : e.label !== undefined ? { semaLabel: e.label } : {}),
482
+ ...(e.probe === undefined ? {} : { semaProbe: e.probe.verdict, semaProbeAt: e.probe.at }),
483
+ };
484
+ if (keyEnv === null)
485
+ delete entry['apiKeyEnv'];
486
+ if (e.probe === undefined) {
487
+ delete entry['semaProbe'];
488
+ delete entry['semaProbeAt'];
489
+ }
490
+ if (e.vision !== undefined) {
491
+ entry['vision'] = e.vision;
492
+ entry['semaVision'] = e.vision;
493
+ }
494
+ else if (entry['vision'] === undefined) {
495
+ const fam = resolveEntryVision({ modelId: e.modelId }).value;
496
+ if (fam !== undefined)
497
+ entry['vision'] = fam;
498
+ }
499
+ if (e.thinking !== undefined) {
500
+ const probed = applyProbeToEntry({ ...(typeof entry['reasoning'] === 'boolean' ? { reasoning: entry['reasoning'] } : {}), ...(entry['compat'] !== undefined ? { compat: entry['compat'] } : {}) }, e.thinking);
501
+ const next = { ...entry };
502
+ if (typeof probed.reasoning === 'boolean')
503
+ next['reasoning'] = probed.reasoning;
504
+ if (probed.compat !== undefined)
505
+ next['compat'] = probed.compat;
506
+ else
507
+ delete next['compat'];
508
+ entry = next;
509
+ }
510
+ const issue = onboardEntryCredentialIssue(entry, envView);
511
+ if (issue !== undefined) {
512
+ problems.push(problem('credential_unpaired', say(issue), { index: i, name }));
513
+ continue;
514
+ }
515
+ const prevRef = existingIdx >= 0 ? models[existingIdx]['apiKeyEnv'] : undefined;
516
+ if (isShellSlot(prevRef) && prevRef.toUpperCase() !== (keyEnv ?? '').toUpperCase())
517
+ replaced.push(prevRef);
518
+ if (existingIdx >= 0)
519
+ models[existingIdx] = entry;
520
+ else
521
+ models.push(entry);
522
+ planned.push(Object.freeze({ name, modelId: e.modelId, keyEnv, updatedExisting: existingIdx >= 0, probe: probeStateOf(entry['semaProbe']) }));
523
+ }
524
+ if (problems.length > 0)
525
+ return refuse(batchRefusalOf(problems), problems);
526
+ let defaultAt = defaultIndex;
527
+ if (defaultAt === undefined) {
528
+ const choice = defaultModelChoiceOf(effectiveDefaultModelOf({ modelsDoc: modelsRaw, userSettings: settingsRaw }), planned, declineDefault);
529
+ if (choice.kind === 'pick')
530
+ defaultAt = choice.index;
531
+ }
532
+ if (defaultAt !== undefined) {
533
+ const roles = isRecord(doc['roles']) ? doc['roles'] : {};
534
+ roles['default'] = { model: planned[defaultAt].name };
535
+ doc['roles'] = roles;
536
+ }
537
+ const settings = settingsAfter(sread.doc, slotEdits);
538
+ if (!settings.ok)
539
+ return refuse(settings.reason, [], settings.detail);
540
+ const skip = new Set(planned.map((p) => p.name));
541
+ const stillReferenced = referencedSlotsOf(models);
542
+ const releasedKeySlots = replaced.filter((r, i) => replaced.indexOf(r) === i && !stillReferenced.has(r.toUpperCase()));
543
+ return okPlan(modelsRaw, doc, settingsRaw, settings.next, { entries: planned, releasedKeySlots, otherIssues: otherIssuesOf(models, skip, envView, say) });
544
+ }
545
+ function okPlan(modelsRaw, nextModels, settingsRaw, nextSettings, extra) {
546
+ const before = modelsRaw === null || modelsRaw === undefined ? undefined : cloneDoc(modelsRaw);
547
+ const sBefore = settingsRaw === null || settingsRaw === undefined ? {} : (cloneDoc(settingsRaw) ?? {});
548
+ return Object.freeze({
549
+ ok: true,
550
+ nextModelsDoc: freezeDoc(nextModels),
551
+ nextSettingsDoc: freezeDoc(nextSettings),
552
+ modelsChanged: before === undefined ? !sameJson(nextModels, { models: [] }) : !sameJson(before, nextModels),
553
+ settingsChanged: !sameJson(sBefore, nextSettings),
554
+ entries: Object.freeze([...(extra.entries ?? [])]),
555
+ releasedKeySlots: Object.freeze([...(extra.releasedKeySlots ?? [])]),
556
+ warnings: Object.freeze([...(extra.warnings ?? [])]),
557
+ otherIssues: Object.freeze([...(extra.otherIssues ?? [])]),
558
+ disclosures: Object.freeze([...(extra.disclosures ?? [])]),
559
+ });
560
+ }
561
+ function bindingsOf(group) {
562
+ const out = {};
563
+ if (isRecord(group['tiers']))
564
+ for (const [k, v] of Object.entries(group['tiers']))
565
+ if (isTier(k) && typeof v === 'string' && v !== '')
566
+ out[k] = v;
567
+ return out;
568
+ }
569
+ function detachEntry(doc, name, surviving) {
570
+ const groups = tierGroupsOf(doc);
571
+ if (groups.length > 0) {
572
+ const kept = [];
573
+ for (const g of groups) {
574
+ const had = Object.keys(bindingsOf(g)).length;
575
+ const tiers = isRecord(g['tiers']) ? { ...g['tiers'] } : {};
576
+ for (const [k, v] of Object.entries(tiers))
577
+ if (v === name)
578
+ delete tiers[k];
579
+ const left = Object.keys(tiers).filter((k) => isTier(k) && typeof tiers[k] === 'string').length;
580
+ if (had > 0 && left === 0)
581
+ continue;
582
+ kept.push({ ...g, tiers });
583
+ }
584
+ doc['tierGroups'] = kept;
585
+ if (typeof doc['activeTierGroup'] === 'string' && !kept.some((g) => g['name'] === doc['activeTierGroup']))
586
+ delete doc['activeTierGroup'];
587
+ }
588
+ const am = doc['atMention'];
589
+ if (isRecord(am) && am['mode'] === 'only' && Array.isArray(am['models'])) {
590
+ const narrowed = SETTINGS_NARROW_AT_MENTION(am, surviving);
591
+ doc['atMention'] = narrowed;
592
+ }
593
+ }
594
+ function planRemove(name, modelsRaw, settingsRaw) {
595
+ if (typeof name !== 'string' || name === '')
596
+ return refuse('invalid_request');
597
+ const mread = modelsDocOf(modelsRaw);
598
+ if (!mread.ok)
599
+ return refuse('scope_unreadable', [], mread.detail);
600
+ const sread = settingsDocOf(settingsRaw);
601
+ if (!sread.ok)
602
+ return refuse('scope_unreadable', [], sread.detail);
603
+ const { doc, models } = mread;
604
+ const idx = models.findIndex((m) => entryName(m) === name);
605
+ if (idx < 0)
606
+ return refuse('not_found', [problem('not_found', en('model.pool.notFound', { name }), { name })]);
607
+ const blockers = computeDeleteBlockers({ id: name, doc: withDefaultFolded(doc), rosters: legacyRostersOf(sread.doc) });
608
+ if (blockers.length > 0)
609
+ return refuse('entry_in_use', blockers.map((b) => problem('entry_in_use', `${b.where}: ${b.fix}`, { name })));
610
+ const surviving = models.filter((_, i) => i !== idx).map(entryName).filter((n) => n !== undefined);
611
+ const warnings = computeDeleteWarnings({ id: name, doc: doc, tierGroups: legacyTierGroupsOf(sread.doc), survivingIds: surviving });
612
+ const removed = models[idx];
613
+ models.splice(idx, 1);
614
+ detachEntry(doc, name, new Set(surviving));
615
+ const ref = removed['apiKeyEnv'];
616
+ const released = isShellSlotIn(sread.env)(ref) && !models.some((m) => typeof m['apiKeyEnv'] === 'string' && m['apiKeyEnv'].toUpperCase() === ref.toUpperCase()) ? [ref] : [];
617
+ return okPlan(modelsRaw, doc, settingsRaw, sread.doc, { releasedKeySlots: released, warnings });
618
+ }
619
+ function planUpdate(edit, modelsRaw, settingsRaw) {
620
+ const name = own(edit, 'name');
621
+ if (typeof name !== 'string' || name === '')
622
+ return refuse('invalid_request');
623
+ const label = own(edit, 'label');
624
+ const vision = own(edit, 'vision');
625
+ const ctx = own(edit, 'contextWindow');
626
+ const max = own(edit, 'maxTokens');
627
+ if (label === undefined && vision === undefined && ctx === undefined && max === undefined)
628
+ return refuse('invalid_request');
629
+ if ((label !== undefined && label !== null && (typeof label !== 'string' || label.length > 200)) || (vision !== undefined && vision !== null && typeof vision !== 'boolean'))
630
+ return refuse('invalid_request');
631
+ if ((ctx !== undefined && !posInt(ctx)) || (max !== undefined && !posInt(max)))
632
+ return refuse('invalid_request');
633
+ const mread = modelsDocOf(modelsRaw);
634
+ if (!mread.ok)
635
+ return refuse('scope_unreadable', [], mread.detail);
636
+ const { doc, models } = mread;
637
+ const m = models.find((x) => entryName(x) === name);
638
+ if (m === undefined)
639
+ return refuse('not_found', [problem('not_found', en('model.pool.notFound', { name }), { name })]);
640
+ if (label === null || label === '')
641
+ delete m['semaLabel'];
642
+ else if (typeof label === 'string')
643
+ m['semaLabel'] = label;
644
+ if (vision === null) {
645
+ delete m['semaVision'];
646
+ const fam = typeof m['id'] === 'string' ? resolveEntryVision({ modelId: m['id'] }).value : undefined;
647
+ if (fam === undefined)
648
+ delete m['vision'];
649
+ else
650
+ m['vision'] = fam;
651
+ }
652
+ else if (typeof vision === 'boolean') {
653
+ m['vision'] = vision;
654
+ m['semaVision'] = vision;
655
+ }
656
+ if (posInt(ctx))
657
+ m['contextWindow'] = ctx;
658
+ if (posInt(max))
659
+ m['maxTokens'] = max;
660
+ return okPlan(modelsRaw, doc, settingsRaw, settingsOrEmpty(settingsRaw), {});
661
+ }
662
+ function planSetDefault(name, modelsRaw, settingsRaw) {
663
+ if (typeof name !== 'string' || name === '')
664
+ return refuse('invalid_request');
665
+ const mread = modelsDocOf(modelsRaw);
666
+ if (!mread.ok)
667
+ return refuse('scope_unreadable', [], mread.detail);
668
+ const { models } = mread;
669
+ const doc = withDefaultFolded(mread.doc);
670
+ const m = models.find((x) => entryName(x) === name);
671
+ if (m === undefined || !isEnabled(m))
672
+ return refuse('not_found', [problem('not_found', en('model.pool.notFound', { name }), { name })]);
673
+ const roles = isRecord(doc['roles']) ? doc['roles'] : {};
674
+ roles['default'] = { model: name };
675
+ doc['roles'] = roles;
676
+ return okPlan(modelsRaw, doc, settingsRaw, settingsOrEmpty(settingsRaw), { disclosures: shadowDisclosuresOf(settingsRaw) });
677
+ }
678
+ function planImportEnv(modelsRaw, settingsRaw, env) {
679
+ const mread = modelsDocOf(modelsRaw);
680
+ if (!mread.ok)
681
+ return refuse('scope_unreadable', [], mread.detail);
682
+ const envModel = envServesModels(mread.models, env);
683
+ if (envModel !== undefined)
684
+ return refuse('env_catalog_present', [], en('model.pool.importEnv.notYet', { model: envModel }));
685
+ return okPlan(modelsRaw, mread.doc, settingsRaw, settingsOrEmpty(settingsRaw), {});
686
+ }
687
+ function planTier(kind, edit, modelsRaw, settingsRaw) {
688
+ const group = own(edit, 'group');
689
+ if (typeof group !== 'string' || !CATALOG_NAME_RE.test(group) || group.length > 64)
690
+ return refuse('invalid_request', [problem('invalid_request', en('model.pool.tier.badGroup'))]);
691
+ const mread = modelsDocOf(modelsRaw);
692
+ if (!mread.ok)
693
+ return refuse('scope_unreadable', [], mread.detail);
694
+ const { doc, models } = mread;
695
+ const names = new Set(models.map(entryName).filter((n) => n !== undefined));
696
+ const groups = tierGroupsOf(doc);
697
+ const at = groups.findIndex((g) => g['name'] === group);
698
+ const missing = () => refuse('not_found', [problem('not_found', en('model.pool.tier.noGroup', { group }), { name: group })]);
699
+ if (kind === 'addTierGroup') {
700
+ if (at >= 0)
701
+ return refuse('already_exists', [problem('already_exists', en('model.pool.tier.groupExists', { group }), { name: group })]);
702
+ const tiersRaw = own(edit, 'tiers');
703
+ const tiers = {};
704
+ if (tiersRaw !== undefined) {
705
+ if (!isRecord(tiersRaw))
706
+ return refuse('invalid_request');
707
+ for (const [k, v] of Object.entries(tiersRaw)) {
708
+ if (!isTier(k) || typeof v !== 'string')
709
+ return refuse('invalid_request');
710
+ if (!names.has(v))
711
+ return refuse('not_found', [problem('not_found', en('model.pool.notFound', { name: v }), { name: v })]);
712
+ tiers[k] = v;
713
+ }
714
+ }
715
+ doc['tierGroups'] = [...(Array.isArray(doc['tierGroups']) ? doc['tierGroups'] : []), { name: group, tiers }];
716
+ }
717
+ else {
718
+ if (at < 0)
719
+ return missing();
720
+ if (kind === 'switchTierGroup')
721
+ doc['activeTierGroup'] = group;
722
+ else if (kind === 'removeTierGroup') {
723
+ doc['tierGroups'] = doc['tierGroups'].filter((g) => !(isRecord(g) && g['name'] === group));
724
+ if (doc['activeTierGroup'] === group)
725
+ delete doc['activeTierGroup'];
726
+ }
727
+ else {
728
+ const tier = own(edit, 'tier');
729
+ const target = own(edit, 'name');
730
+ if (!isTier(tier) || (target !== null && typeof target !== 'string'))
731
+ return refuse('invalid_request');
732
+ if (typeof target === 'string' && !names.has(target))
733
+ return refuse('not_found', [problem('not_found', en('model.pool.notFound', { name: target }), { name: target })]);
734
+ const g = groups[at];
735
+ const tiers = isRecord(g['tiers']) ? { ...g['tiers'] } : {};
736
+ if (target === null)
737
+ delete tiers[tier];
738
+ else
739
+ tiers[tier] = target;
740
+ g['tiers'] = tiers;
741
+ }
742
+ }
743
+ return okPlan(modelsRaw, doc, settingsRaw, settingsOrEmpty(settingsRaw), {});
744
+ }
745
+ function migratedTierGroups(legacy, names) {
746
+ const taken = new Set();
747
+ const groups = [];
748
+ let active;
749
+ let activeDefault;
750
+ for (const [display, def] of Object.entries(legacy.groups)) {
751
+ const tiers = {};
752
+ for (const [t, id] of Object.entries(def.tiers))
753
+ if (isTier(t) && typeof id === 'string' && names.has(id))
754
+ tiers[t] = id;
755
+ if (Object.keys(tiers).length === 0)
756
+ continue;
757
+ let slug = display.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[._-]+/, '') || 'group';
758
+ const stem = slug;
759
+ for (let i = 2; taken.has(slug); i++)
760
+ slug = `${stem}-${i}`;
761
+ taken.add(slug);
762
+ groups.push({ name: slug, tiers });
763
+ if (display === legacy.active) {
764
+ active = slug;
765
+ const bound = resolveTierBinding({ tiers }, legacy.defaultTiers[display] ?? 'pro');
766
+ if (bound !== undefined && names.has(bound))
767
+ activeDefault = bound;
768
+ }
769
+ }
770
+ return { groups, active, activeDefault };
771
+ }
772
+ function rostersAsTierGroups(settings) {
773
+ const r = legacyRostersOf(settings);
774
+ if (r === null)
775
+ return null;
776
+ const groups = {};
777
+ for (const [name, def] of Object.entries(r.rosters)) {
778
+ const tiers = {};
779
+ if (def.slots['main'])
780
+ tiers['pro'] = def.slots['main'];
781
+ if (def.slots['cheap'] && def.slots['cheap'] !== def.slots['main'])
782
+ tiers['flash'] = def.slots['cheap'];
783
+ if (Object.keys(tiers).length > 0)
784
+ groups[name] = { tiers };
785
+ }
786
+ if (Object.keys(groups).length === 0)
787
+ return null;
788
+ return { active: Object.hasOwn(groups, r.active) ? r.active : Object.keys(groups)[0], groups, defaultTiers: {} };
789
+ }
790
+ function planMigrate(modelsRaw, settingsRaw) {
791
+ const mread = modelsDocOf(modelsRaw);
792
+ if (!mread.ok)
793
+ return refuse('scope_unreadable', [], mread.detail);
794
+ const sread = settingsDocOf(settingsRaw);
795
+ if (!sread.ok)
796
+ return refuse('scope_unreadable', [], sread.detail);
797
+ const { models } = mread;
798
+ const doc = withDefaultFolded(mread.doc);
799
+ const settings = sread.doc;
800
+ const legacy = legacyTierGroupsOf(settings) ?? rostersAsTierGroups(settings);
801
+ const disclosures = [];
802
+ if (legacy !== null) {
803
+ const names = new Set(models.map(entryName).filter((n) => n !== undefined));
804
+ const m = migratedTierGroups(legacy, names);
805
+ const catalogGroups = tierGroupsOf(doc);
806
+ const present = new Map();
807
+ for (const g of catalogGroups)
808
+ if (typeof g['name'] === 'string' && !present.has(g['name']))
809
+ present.set(g['name'], g);
810
+ const rawList = doc['tierGroups'];
811
+ const merged = Array.isArray(rawList) ? [...rawList] : [];
812
+ const overridden = [];
813
+ for (const g of m.groups) {
814
+ const name = g['name'];
815
+ const there = present.get(name);
816
+ if (there === undefined)
817
+ merged.push(g);
818
+ else if (!sameJson(bindingsOf(there), g['tiers']))
819
+ overridden.push(name);
820
+ }
821
+ if (merged.length > (Array.isArray(rawList) ? rawList.length : 0))
822
+ doc['tierGroups'] = merged;
823
+ if (overridden.length > 0)
824
+ disclosures.push(Object.freeze({ kind: 'tier_group_overridden', layer: 'models', detail: overridden.join(', ') }));
825
+ if (catalogGroups.length === 0) {
826
+ if (m.groups.length > 0 && m.active !== undefined)
827
+ doc['activeTierGroup'] = m.active;
828
+ if (m.activeDefault !== undefined && defaultSlotOf(doc).kind === 'absent')
829
+ doc['roles'] = { ...(isRecord(doc['roles']) ? doc['roles'] : {}), default: { model: m.activeDefault } };
830
+ }
831
+ }
832
+ const edits = [];
833
+ if (settings['semaTierGroups'] !== undefined)
834
+ edits.push({ intent: 'legacyTierGroups', op: 'delete' });
835
+ if (settings['semaRosters'] !== undefined)
836
+ edits.push({ intent: 'legacyRosters', op: 'delete' });
837
+ const after = settingsAfter(settings, edits);
838
+ if (!after.ok)
839
+ return refuse(after.reason, [], after.detail);
840
+ return okPlan(modelsRaw, doc, settingsRaw, after.next, { disclosures });
841
+ }
842
+ function planSweep(modelsRaw, settingsRaw) {
843
+ const mread = modelsDocOf(modelsRaw);
844
+ if (!mread.ok)
845
+ return refuse('scope_unreadable', [], mread.detail);
846
+ const sread = settingsDocOf(settingsRaw);
847
+ if (!sread.ok)
848
+ return refuse('scope_unreadable', [], sread.detail);
849
+ const untouched = modelsRaw === null || modelsRaw === undefined ? { models: [] } : (cloneDoc(modelsRaw) ?? { models: [] });
850
+ const orphans = orphanKeySlotsOf(mread.models, sread.env);
851
+ if (orphans.length === 0)
852
+ return okPlan(modelsRaw, untouched, settingsRaw, sread.doc, {});
853
+ const after = settingsAfter(sread.doc, orphans.map((member) => ({ intent: 'modelKeySlot', member, op: 'delete' })));
854
+ if (!after.ok)
855
+ return refuse(after.reason, [], after.detail);
856
+ return okPlan(modelsRaw, untouched, settingsRaw, after.next, { releasedKeySlots: orphans });
857
+ }
858
+ export function poolWritePlan(edit, modelsDoc, settingsDoc, effectiveEnv) {
859
+ return planWith(edit, modelsDoc, settingsDoc, effectiveEnv, undefined);
860
+ }
861
+ function envOf(v) {
862
+ const out = {};
863
+ if (!isRecord(v))
864
+ return out;
865
+ for (const k of Object.keys(v)) {
866
+ const val = own(v, k);
867
+ if (typeof val === 'string')
868
+ out[k] = val;
869
+ }
870
+ return out;
871
+ }
872
+ function planWith(edit, modelsDoc, settingsDoc, effectiveEnv, pins, declineDefault = false) {
873
+ try {
874
+ const kind = own(edit, 'kind');
875
+ const env = envOf(effectiveEnv);
876
+ switch (MODEL_POOL_EDIT_KINDS.find((w) => w === kind)) {
877
+ case 'add':
878
+ return planAdd(own(edit, 'entries'), own(edit, 'defaultIndex'), modelsDoc, settingsDoc, env, pins, declineDefault);
879
+ case 'importChannel': {
880
+ const entries = channelImportEntries(own(edit, 'profile'), own(edit, 'modelIds'), own(edit, 'acceptUnverified'));
881
+ if (entries === undefined)
882
+ return refuse('invalid_request', [problem('invalid_request', en('model.pool.channel.badPick'))]);
883
+ return planAdd(entries, own(edit, 'defaultIndex'), modelsDoc, settingsDoc, env, pins, declineDefault);
884
+ }
885
+ case 'update':
886
+ return planUpdate(edit, modelsDoc, settingsDoc);
887
+ case 'remove':
888
+ return planRemove(own(edit, 'name'), modelsDoc, settingsDoc);
889
+ case 'setDefault':
890
+ return planSetDefault(own(edit, 'name'), modelsDoc, settingsDoc);
891
+ case 'importEnvModels':
892
+ return planImportEnv(modelsDoc, settingsDoc, env);
893
+ case 'bindTier':
894
+ case 'switchTierGroup':
895
+ case 'addTierGroup':
896
+ case 'removeTierGroup':
897
+ return planTier(kind, edit, modelsDoc, settingsDoc);
898
+ case 'migrateTierGroups':
899
+ return planMigrate(modelsDoc, settingsDoc);
900
+ case 'sweepOrphanKeySlots':
901
+ return planSweep(modelsDoc, settingsDoc);
902
+ default:
903
+ return refuse('invalid_request');
904
+ }
905
+ }
906
+ catch {
907
+ return refuse('invalid_request');
908
+ }
909
+ }
910
+ function pinsOf(v) {
911
+ const list = own(v, 'entries');
912
+ if (!Array.isArray(list))
913
+ return undefined;
914
+ const entries = [];
915
+ for (let i = 0; i < list.length; i++) {
916
+ const e = own(list, String(i));
917
+ const name = own(e, 'name');
918
+ const keyEnv = own(e, 'keyEnv');
919
+ if (typeof name !== 'string' || (keyEnv !== null && typeof keyEnv !== 'string'))
920
+ return undefined;
921
+ entries.push(Object.freeze({ name, keyEnv }));
922
+ }
923
+ return Object.freeze({ entries: Object.freeze(entries) });
924
+ }
925
+ const docFrom = (r) => r === undefined ? 'unreadable' : r.status === 'loaded' ? r.doc : r.status === 'absent' ? null : 'unreadable';
926
+ export function modelPoolIntentOf(edit, effectiveEnv, probe) {
927
+ const kind = own(edit, 'kind');
928
+ const record = (p) => {
929
+ if (probe !== undefined)
930
+ probe.plan = p;
931
+ return p;
932
+ };
933
+ const refuseStep = (p) => ({ kind: 'refuse', reason: p.reason, ...(p.detail !== undefined ? { detail: p.detail } : p.problems[0] !== undefined ? { detail: p.problems[0].detail } : {}) });
934
+ const needsSettings = kind === 'add' || kind === 'importChannel' || kind === 'remove' || kind === 'migrateTierGroups' || kind === 'sweepOrphanKeySlots';
935
+ const read = (input) => {
936
+ const models = docFrom(input.fresh.models);
937
+ const settings = docFrom(input.fresh.userSettings);
938
+ if (models === 'unreadable' || (settings === 'unreadable' && needsSettings))
939
+ return undefined;
940
+ return { models, settings: settings === 'unreadable' ? null : settings };
941
+ };
942
+ const modelsStep = (role, withPins) => ({
943
+ doc: 'models',
944
+ role,
945
+ snapshot: ['userSettings'],
946
+ plan: (input) => {
947
+ const r = read(input);
948
+ if (r === undefined)
949
+ return { kind: 'refuse', reason: 'scope_unreadable' };
950
+ const previewUnseen = own(input, 'mode') === 'preview' && own(input.carried, 'prepWrites') === true;
951
+ const pinsRaw = withPins && !previewUnseen ? own(input.carried, 'pins') : undefined;
952
+ const pins = pinsRaw === undefined ? undefined : pinsOf(pinsRaw);
953
+ if (pinsRaw !== undefined && pins === undefined)
954
+ return { kind: 'refuse', reason: 'invalid_request' };
955
+ const p = record(planWith(edit, r.models, r.settings, effectiveEnv, pins));
956
+ if (!p.ok)
957
+ return refuseStep(p);
958
+ const carry = { released: [...p.releasedKeySlots] };
959
+ const disclose = p.disclosures.length > 0 ? { disclose: p.disclosures } : {};
960
+ return p.modelsChanged ? { kind: 'write', next: p.nextModelsDoc, carry, ...disclose } : { kind: 'unchanged', carry, ...disclose };
961
+ },
962
+ });
963
+ const slotCleanup = {
964
+ doc: 'userSettings',
965
+ role: 'cleanup',
966
+ residue: 'orphan_key_slot',
967
+ snapshot: ['models'],
968
+ plan: (input) => {
969
+ const r = read(input);
970
+ if (r === undefined)
971
+ return { kind: 'refuse', reason: 'scope_unreadable' };
972
+ const released = own(input.carried, 'released');
973
+ const slots = Array.isArray(released) ? released.filter((s) => typeof s === 'string') : [];
974
+ const models = modelsDocOf(r.models);
975
+ if (!models.ok)
976
+ return { kind: 'refuse', reason: 'scope_unreadable' };
977
+ const still = referencedSlotsOf(models.models);
978
+ const edits = slots.filter((s) => !still.has(s.toUpperCase())).map((s) => ({ intent: 'modelKeySlot', member: s, op: 'delete' }));
979
+ const sread = settingsDocOf(r.settings);
980
+ if (!sread.ok)
981
+ return { kind: 'refuse', reason: 'scope_unreadable' };
982
+ const after = settingsAfter(sread.doc, edits);
983
+ if (!after.ok)
984
+ return { kind: 'refuse', reason: after.reason };
985
+ return sameJson(sread.doc, after.next) ? { kind: 'unchanged' } : { kind: 'write', next: freezeDoc(after.next) };
986
+ },
987
+ };
988
+ if (kind === 'add' || kind === 'importChannel') {
989
+ const prepare = {
990
+ doc: 'userSettings',
991
+ role: 'prepare',
992
+ residue: 'orphan_key_slot',
993
+ lock: ['models'],
994
+ plan: (input) => {
995
+ const r = read(input);
996
+ if (r === undefined)
997
+ return { kind: 'refuse', reason: 'scope_unreadable' };
998
+ const p = record(planWith(edit, r.models, r.settings, effectiveEnv, undefined));
999
+ if (!p.ok)
1000
+ return refuseStep(p);
1001
+ const carry = { pins: { entries: p.entries.map((e) => ({ name: e.name, keyEnv: e.keyEnv })) }, prepWrites: p.settingsChanged };
1002
+ return p.settingsChanged ? { kind: 'write', next: p.nextSettingsDoc, carry } : { kind: 'unchanged', carry };
1003
+ },
1004
+ };
1005
+ return Object.freeze({ appliesOn: 'engine_reload', steps: Object.freeze([prepare, modelsStep('effect', true), slotCleanup]) });
1006
+ }
1007
+ if (kind === 'remove')
1008
+ return Object.freeze({ appliesOn: 'engine_reload', steps: Object.freeze([modelsStep('effect', false), slotCleanup]) });
1009
+ if (kind === 'migrateTierGroups') {
1010
+ const cleanup = {
1011
+ doc: 'userSettings',
1012
+ role: 'cleanup',
1013
+ residue: 'legacy_tier_groups_left',
1014
+ snapshot: ['models'],
1015
+ plan: (input) => {
1016
+ const r = read(input);
1017
+ if (r === undefined)
1018
+ return { kind: 'refuse', reason: 'scope_unreadable' };
1019
+ const p = planWith(edit, r.models, r.settings, effectiveEnv, undefined);
1020
+ if (!p.ok)
1021
+ return { kind: 'refuse', reason: p.reason };
1022
+ if (own(input, 'mode') !== 'preview' && p.modelsChanged)
1023
+ return { kind: 'refuse', reason: 'changed_during_write' };
1024
+ return p.settingsChanged ? { kind: 'write', next: p.nextSettingsDoc } : { kind: 'unchanged' };
1025
+ },
1026
+ };
1027
+ return Object.freeze({ appliesOn: 'engine_reload', steps: Object.freeze([modelsStep('effect', false), cleanup]) });
1028
+ }
1029
+ if (kind === 'sweepOrphanKeySlots') {
1030
+ const sweep = {
1031
+ doc: 'userSettings',
1032
+ role: 'effect',
1033
+ lock: ['models'],
1034
+ plan: (input) => {
1035
+ const r = read(input);
1036
+ if (r === undefined)
1037
+ return { kind: 'refuse', reason: 'scope_unreadable' };
1038
+ const p = record(planWith(edit, r.models, r.settings, effectiveEnv, undefined));
1039
+ if (!p.ok)
1040
+ return refuseStep(p);
1041
+ return p.settingsChanged ? { kind: 'write', next: p.nextSettingsDoc } : { kind: 'unchanged' };
1042
+ },
1043
+ };
1044
+ return Object.freeze({ appliesOn: 'next_engine_start', steps: Object.freeze([sweep]) });
1045
+ }
1046
+ return Object.freeze({ appliesOn: 'engine_reload', steps: Object.freeze([modelsStep('effect', false)]) });
1047
+ }
1048
+ export async function editModelPoolWith(edit, store, facts, effectiveEnv, opts) {
1049
+ const probe = { plan: undefined };
1050
+ const intent = modelPoolIntentOf(edit, effectiveEnv, probe);
1051
+ const receipt = own(opts, 'dryRun') === true ? await previewLocalConfigIntent(intent, store, facts) : await applyLocalConfigIntent(intent, store, facts);
1052
+ const p = probe.plan;
1053
+ if (p === undefined)
1054
+ return Object.freeze({ receipt });
1055
+ if (!p.ok)
1056
+ return Object.freeze({ receipt, ...(p.problems.length > 0 ? { problems: p.problems } : {}) });
1057
+ return Object.freeze({
1058
+ receipt,
1059
+ ...(p.entries.length > 0 ? { entries: p.entries } : {}),
1060
+ ...(p.warnings.length > 0 ? { warnings: p.warnings } : {}),
1061
+ ...(p.otherIssues.length > 0 ? { otherIssues: p.otherIssues } : {}),
1062
+ });
1063
+ }
1064
+ export function onboardWritePlan(input) {
1065
+ const e = own(input, 'entry');
1066
+ const apiKey = own(e, 'apiKey');
1067
+ const say = (t) => (typeof apiKey === 'string' && apiKey !== '' ? onboardRedactKey(t, apiKey) : t);
1068
+ const fail = (refusal, detail) => Object.freeze({ ok: false, refusal, detail: say(detail) });
1069
+ try {
1070
+ if (own(input, 'engineMode') === 'cloud')
1071
+ return fail('engine_is_remote', en('onboard.refusal.engineIsRemote'));
1072
+ const modelsRaw = own(input, 'modelsDoc');
1073
+ if (modelsRaw !== undefined && !isRecord(modelsRaw))
1074
+ return fail('catalog_unreadable', en('onboard.plan.notObject'));
1075
+ const envRaw = own(input, 'settingsEnv');
1076
+ const probe = own(input, 'probe');
1077
+ const verdict = own(probe, 'verdict');
1078
+ const now = own(input, 'now');
1079
+ const spec = {
1080
+ modelId: own(e, 'modelId'),
1081
+ api: own(e, 'api'),
1082
+ baseUrl: own(e, 'baseUrl'),
1083
+ credential: { kind: 'key', apiKey },
1084
+ label: own(e, 'providerName'),
1085
+ contextWindow: own(e, 'contextWindow'),
1086
+ maxTokens: own(e, 'maxTokens'),
1087
+ channel: own(e, 'channel'),
1088
+ probe: { verdict, at: now },
1089
+ acceptUnverified: true,
1090
+ ...(own(input, 'thinking') === undefined ? {} : { thinking: own(input, 'thinking') }),
1091
+ };
1092
+ const plan = planWith({ kind: 'add', entries: [spec], ...(own(input, 'makeDefault') === true ? { defaultIndex: 0 } : {}) }, modelsRaw === undefined ? null : modelsRaw, envRaw === undefined ? null : { env: envRaw }, own(input, 'effectiveEnv'), undefined, own(input, 'makeDefault') !== true);
1093
+ if (!plan.ok) {
1094
+ const first = plan.problems[0];
1095
+ const detail = plan.detail ?? first?.detail ?? en('onboard.refusal.input');
1096
+ if (plan.reason === 'scope_unreadable')
1097
+ return fail('catalog_unreadable', detail);
1098
+ if (plan.reason === 'env_catalog_present')
1099
+ return fail('env_catalog_present', detail);
1100
+ if (plan.reason === 'credential_unpaired')
1101
+ return fail('credential_unpaired', detail);
1102
+ return fail('input', detail);
1103
+ }
1104
+ const planned = plan.entries[0];
1105
+ const nextEnv = own(plan.nextSettingsDoc, 'env');
1106
+ return Object.freeze({
1107
+ ok: true,
1108
+ entryName: planned.name,
1109
+ keyEnv: planned.keyEnv ?? '',
1110
+ updatedExisting: planned.updatedExisting,
1111
+ nextModelsDoc: plan.nextModelsDoc,
1112
+ nextSettingsEnv: (isRecord(nextEnv) ? nextEnv : {}),
1113
+ otherIssues: plan.otherIssues,
1114
+ });
1115
+ }
1116
+ catch {
1117
+ return fail('input', en('onboard.refusal.input'));
1118
+ }
1119
+ }
1120
+ const CC_ENV = Object.freeze({ baseUrl: 'ANTHROPIC_BASE_URL', authToken: 'ANTHROPIC_AUTH_TOKEN', apiKey: 'ANTHROPIC_API_KEY' });
1121
+ const CC_MODEL_KEYS = Object.freeze(['ANTHROPIC_DEFAULT_OPUS_MODEL', 'ANTHROPIC_DEFAULT_FABLE_MODEL', 'ANTHROPIC_MODEL', 'ANTHROPIC_DEFAULT_SONNET_MODEL']);
1122
+ const numOf = (v) => (posInt(v) ? v : undefined);
1123
+ function jsonTextOf(text) {
1124
+ try {
1125
+ return { ok: true, value: JSON.parse(text) };
1126
+ }
1127
+ catch {
1128
+ return { ok: false };
1129
+ }
1130
+ }
1131
+ function modelRowsOf(list) {
1132
+ if (!Array.isArray(list))
1133
+ return [];
1134
+ const out = [];
1135
+ for (let i = 0; i < list.length && out.length < 256; i++) {
1136
+ const m = own(list, String(i));
1137
+ const id = strOf(own(m, 'id'));
1138
+ if (id === undefined || out.some((x) => x.id === id))
1139
+ continue;
1140
+ const ctx = numOf(own(m, 'contextWindow')) ?? numOf(own(m, 'context_length'));
1141
+ const max = numOf(own(m, 'maxTokens')) ?? numOf(own(m, 'max_tokens'));
1142
+ out.push({ id, ...(ctx === undefined ? {} : { contextWindow: ctx }), ...(max === undefined ? {} : { maxTokens: max }) });
1143
+ }
1144
+ return out;
1145
+ }
1146
+ function ccEnvProfile(source, id, name, env, topModel, isCurrent) {
1147
+ const baseUrl = strOf(own(env, CC_ENV.baseUrl));
1148
+ if (baseUrl === undefined)
1149
+ return undefined;
1150
+ const key = strOf(own(env, CC_ENV.authToken)) ?? strOf(own(env, CC_ENV.apiKey));
1151
+ const raw = CC_MODEL_KEYS.map((k) => strOf(own(env, k))).find((v) => v !== undefined) ?? strOf(topModel);
1152
+ const models = raw === undefined ? [] : [{ id: raw.replace(/\[[0-9]+[kKmM]\]$/, '').trim() }].filter((m) => m.id !== '');
1153
+ return Object.freeze({ source, id, name, baseUrl, api: 'anthropic-messages', ...(key === undefined ? {} : { apiKey: key }), models, ...(isCurrent === undefined ? {} : { isCurrent }) });
1154
+ }
1155
+ export function channelProfilesOf(source, data, env) {
1156
+ const out = [];
1157
+ try {
1158
+ if (source === 'cc-switch') {
1159
+ if (!Array.isArray(data))
1160
+ return Object.freeze(out);
1161
+ for (let i = 0; i < data.length && out.length < 256; i++) {
1162
+ const row = own(data, String(i));
1163
+ const id = strOf(own(row, 'id')) ?? (typeof own(row, 'id') === 'number' ? String(own(row, 'id')) : undefined);
1164
+ if (id === undefined)
1165
+ continue;
1166
+ const name = strOf(own(row, 'name')) ?? id;
1167
+ const isCurrent = own(row, 'is_current') === 1 || own(row, 'is_current') === true;
1168
+ const raw = own(row, 'settings_config');
1169
+ const parsed = typeof raw === 'string' ? jsonTextOf(raw) : { ok: true, value: raw };
1170
+ if (!parsed.ok)
1171
+ continue;
1172
+ const cfg = parsed.value;
1173
+ const base = strOf(own(cfg, 'baseUrl'));
1174
+ const models = modelRowsOf(own(cfg, 'models'));
1175
+ if (base !== undefined) {
1176
+ const key = strOf(own(cfg, 'apiKey')) ?? strOf(own(own(cfg, 'env'), CC_ENV.authToken)) ?? strOf(own(own(cfg, 'env'), CC_ENV.apiKey));
1177
+ out.push(Object.freeze({ source, id, name, baseUrl: base, api: 'anthropic-messages', ...(key === undefined ? {} : { apiKey: key }), models, isCurrent }));
1178
+ continue;
1179
+ }
1180
+ const p = ccEnvProfile(source, id, name, own(cfg, 'env'), own(cfg, 'model'), isCurrent);
1181
+ if (p !== undefined)
1182
+ out.push(p);
1183
+ }
1184
+ }
1185
+ else if (source === 'claude-settings') {
1186
+ const p = ccEnvProfile(source, 'settings', 'Claude Code settings', own(data, 'env'), own(data, 'model'));
1187
+ if (p !== undefined)
1188
+ out.push(p);
1189
+ }
1190
+ else if (source === 'openclaw') {
1191
+ const provs = own(own(data, 'models'), 'providers');
1192
+ if (isRecord(provs)) {
1193
+ for (const id of Object.keys(provs).slice(0, 256)) {
1194
+ const pv = own(provs, id);
1195
+ const base = strOf(own(pv, 'baseUrl'));
1196
+ if (base === undefined)
1197
+ continue;
1198
+ const key = strOf(own(pv, 'apiKey'));
1199
+ out.push(Object.freeze({ source, id, name: id, baseUrl: base, api: own(pv, 'api') === 'anthropic-messages' ? 'anthropic-messages' : 'openai-completions', ...(key === undefined ? {} : { apiKey: key }), models: modelRowsOf(own(pv, 'models')) }));
1200
+ }
1201
+ }
1202
+ }
1203
+ else if (source === 'hermes') {
1204
+ const provs = own(data, 'providers');
1205
+ if (isRecord(provs)) {
1206
+ for (const id of Object.keys(provs).slice(0, 256)) {
1207
+ const pv = own(provs, id);
1208
+ const base = strOf(own(pv, 'api')) ?? strOf(own(pv, 'base_url'));
1209
+ if (base === undefined)
1210
+ continue;
1211
+ const keyEnvName = strOf(own(pv, 'key_env'));
1212
+ const key = strOf(own(pv, 'api_key')) ?? (keyEnvName === undefined ? undefined : strOf(env?.[keyEnvName]));
1213
+ const mdict = own(pv, 'models');
1214
+ const models = [];
1215
+ if (isRecord(mdict)) {
1216
+ for (const mid of Object.keys(mdict).slice(0, 256)) {
1217
+ const mv = own(mdict, mid);
1218
+ const ctx = numOf(own(mv, 'context_length'));
1219
+ const max = numOf(own(mv, 'max_tokens'));
1220
+ models.push({ id: mid, ...(ctx === undefined ? {} : { contextWindow: ctx }), ...(max === undefined ? {} : { maxTokens: max }) });
1221
+ }
1222
+ }
1223
+ const def = strOf(own(pv, 'default_model'));
1224
+ if (def !== undefined && !models.some((m) => m.id === def))
1225
+ models.push({ id: def });
1226
+ const transport = strOf(own(pv, 'transport'));
1227
+ out.push(Object.freeze({ source, id, name: id, baseUrl: base, api: transport !== undefined && /anthropic/i.test(transport) ? 'anthropic-messages' : 'openai-completions', ...(key === undefined ? {} : { apiKey: key }), models }));
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ catch {
1233
+ return Object.freeze([...out]);
1234
+ }
1235
+ return Object.freeze(out);
1236
+ }
1237
+ function channelImportEntries(profile, modelIds, acceptUnverified) {
1238
+ if (!isRecord(profile) || !Array.isArray(modelIds) || modelIds.length === 0)
1239
+ return undefined;
1240
+ const source = MODEL_POOL_CHANNEL_SOURCES.find((w) => w === own(profile, 'source'));
1241
+ const baseUrl = strOf(own(profile, 'baseUrl'));
1242
+ const api = own(profile, 'api');
1243
+ const apiKey = strOf(own(profile, 'apiKey'));
1244
+ const models = modelRowsOf(own(profile, 'models'));
1245
+ if (source === undefined || baseUrl === undefined || !SETTINGS_MODEL_API_WORDS.some((w) => w === api))
1246
+ return undefined;
1247
+ const out = [];
1248
+ for (let i = 0; i < modelIds.length; i++) {
1249
+ const id = own(modelIds, String(i));
1250
+ const row = models.find((m) => m.id === id);
1251
+ if (row === undefined)
1252
+ return undefined;
1253
+ const fam = inferFamily(row.id);
1254
+ const ctx = row.contextWindow ?? fam?.contextWindow;
1255
+ const max = row.maxTokens ?? fam?.perModelCap;
1256
+ out.push({
1257
+ modelId: row.id,
1258
+ api: api,
1259
+ baseUrl,
1260
+ credential: apiKey === undefined ? { kind: 'none' } : { kind: 'key', apiKey },
1261
+ channel: source,
1262
+ ...(strOf(own(profile, 'name')) === undefined ? {} : { label: strOf(own(profile, 'name')) }),
1263
+ ...(ctx === undefined ? {} : { contextWindow: ctx }),
1264
+ ...(max === undefined ? {} : { maxTokens: max }),
1265
+ ...(acceptUnverified === true ? { acceptUnverified: true } : {}),
1266
+ });
1267
+ }
1268
+ return out;
1269
+ }
1270
+ function seatEntrySpec(x, probe) {
1271
+ return Object.freeze({
1272
+ modelId: x.modelId,
1273
+ api: x.api,
1274
+ baseUrl: x.baseUrl,
1275
+ credential: x.credential,
1276
+ ...(x.label === undefined ? {} : { label: x.label }),
1277
+ ...(x.vision === undefined ? {} : { vision: x.vision }),
1278
+ ...(x.contextWindow === undefined ? {} : { contextWindow: x.contextWindow }),
1279
+ ...(x.maxTokens === undefined ? {} : { maxTokens: x.maxTokens }),
1280
+ ...(x.channel === undefined ? {} : { channel: x.channel }),
1281
+ ...(x.acceptUnverified === true ? { acceptUnverified: true } : {}),
1282
+ ...(probe === undefined ? {} : { probe }),
1283
+ });
1284
+ }
1285
+ export function modelPoolEditOfSeat(edit, profiles, probes) {
1286
+ try {
1287
+ if (edit.kind === 'add') {
1288
+ if (!Array.isArray(edit.entries))
1289
+ return undefined;
1290
+ const entries = edit.entries.map((x, i) => seatEntrySpec(x, Array.isArray(probes) ? probes[i] : undefined));
1291
+ return Object.freeze({ kind: 'add', entries: Object.freeze(entries), ...(edit.defaultIndex === undefined ? {} : { defaultIndex: edit.defaultIndex }) });
1292
+ }
1293
+ if (own(edit, 'kind') !== 'importChannel')
1294
+ return edit;
1295
+ const source = own(edit, 'source');
1296
+ const id = own(edit, 'profileId');
1297
+ const profile = Array.isArray(profiles) ? profiles.find((p) => own(p, 'source') === source && own(p, 'id') === id) : undefined;
1298
+ if (profile === undefined)
1299
+ return undefined;
1300
+ const modelIds = own(edit, 'modelIds');
1301
+ const defaultIndex = own(edit, 'defaultIndex');
1302
+ return Object.freeze({
1303
+ kind: 'importChannel',
1304
+ profile,
1305
+ modelIds: Array.isArray(modelIds) ? [...modelIds] : [],
1306
+ ...(typeof defaultIndex === 'number' ? { defaultIndex } : {}),
1307
+ ...(own(edit, 'acceptUnverified') === true ? { acceptUnverified: true } : {}),
1308
+ });
1309
+ }
1310
+ catch {
1311
+ return undefined;
1312
+ }
1313
+ }
1314
+ const visionView = (r) => Object.freeze({ source: r.source, ...(r.value === undefined ? {} : { value: r.value }) });
1315
+ function providersOf() {
1316
+ return Object.freeze(PROVIDER_PRESETS.map((p) => {
1317
+ return Object.freeze({
1318
+ id: p.id,
1319
+ name: p.name,
1320
+ baseUrl: p.baseUrl,
1321
+ api: p.api,
1322
+ authEnv: p.authEnv,
1323
+ ...(p.consoleUrl === undefined ? {} : { consoleUrl: p.consoleUrl }),
1324
+ ...(p.signupUrl === undefined ? (p.consoleUrl === undefined ? {} : { signupUrl: p.consoleUrl }) : { signupUrl: p.signupUrl }),
1325
+ keyOptional: isLoopbackWireUrl(p.baseUrl),
1326
+ models: Object.freeze(p.models.map((m) => Object.freeze({ id: m.id, contextWindow: m.contextWindow, maxTokens: m.maxTokens }))),
1327
+ });
1328
+ }));
1329
+ }
1330
+ export function modelPoolViewOf(readings) {
1331
+ const env = envOf(own(readings, 'effectiveEnv'));
1332
+ const mr = own(readings, 'models');
1333
+ const sr = own(readings, 'userSettings');
1334
+ const settingsRaw = own(sr, 'status') === 'loaded' ? own(sr, 'doc') : null;
1335
+ const settings = settingsDocOf(settingsRaw);
1336
+ const sdoc = settings.ok ? settings.doc : {};
1337
+ const legacyTierSettings = sdoc['semaTierGroups'] !== undefined || sdoc['semaRosters'] !== undefined;
1338
+ const status = own(mr, 'status');
1339
+ const settingsEnv = settings.ok ? settings.env : {};
1340
+ const empty = (catalog) => Object.freeze({
1341
+ catalog,
1342
+ completeness: catalog === 'unreadable' ? 'catalog_incomplete' : 'complete',
1343
+ unreadableRows: 0,
1344
+ entries: Object.freeze([]),
1345
+ roles: Object.freeze([]),
1346
+ tierGroups: Object.freeze([]),
1347
+ defaultModel: catalog === 'absent' ? effectiveDefaultModelOf({ modelsDoc: null, userSettings: settingsRaw }) : unknownDefault(shadowDisclosuresOf(settingsRaw)),
1348
+ legacyTierSettings,
1349
+ orphanKeySlots: catalog === 'absent' ? orphanKeySlotsOf([], settingsEnv).length : 0,
1350
+ envModels: Object.freeze(catalog === 'unreadable' ? [] : [env['MODEL_ID'], env['MODEL_CHEAP_ID']].filter((v) => typeof v === 'string' && v.trim() !== '')),
1351
+ providers: providersOf(),
1352
+ });
1353
+ if (status === 'absent')
1354
+ return empty('absent');
1355
+ if (status !== 'loaded')
1356
+ return empty('unreadable');
1357
+ const raw = cloneDoc(own(mr, 'doc'));
1358
+ if (raw === undefined)
1359
+ return empty('unreadable');
1360
+ const list = raw['models'];
1361
+ if (list !== undefined && !Array.isArray(list))
1362
+ return empty('unreadable');
1363
+ const items = Array.isArray(list) ? list : [];
1364
+ const { rows: good, unreadable } = catalogRowsOf(items);
1365
+ const doc = { ...raw, models: good };
1366
+ const def = effectiveDefaultModelOf({ modelsDoc: raw, userSettings: settingsRaw });
1367
+ const blockDoc = withDefaultFolded(doc);
1368
+ const groups = tierGroupsOf(raw).filter((g) => typeof g['name'] === 'string');
1369
+ const active = typeof raw['activeTierGroup'] === 'string' ? raw['activeTierGroup'] : undefined;
1370
+ const rosters = legacyRostersOf(sdoc);
1371
+ const legacyTiers = legacyTierGroupsOf(sdoc);
1372
+ const names = good.map((m) => entryName(m));
1373
+ const entries = good.map((m) => {
1374
+ const name = entryName(m);
1375
+ const keyEnv = strOf(m['apiKeyEnv']);
1376
+ const keyless = keyEnv === undefined && m['sealedApiKey'] === undefined;
1377
+ const probeWord = strOf(m['semaProbe']);
1378
+ const probeState = probeStateOf(probeWord);
1379
+ const at = typeof m['semaProbeAt'] === 'number' && Number.isFinite(m['semaProbeAt']) ? m['semaProbeAt'] : undefined;
1380
+ const visionIntent = typeof m['semaVision'] === 'boolean' ? m['semaVision'] : typeof m['vision'] === 'boolean' ? m['vision'] : undefined;
1381
+ let issue;
1382
+ try {
1383
+ issue = isEnabled(m) ? onboardEntryCredentialIssue(m, env) : undefined;
1384
+ }
1385
+ catch {
1386
+ issue = undefined;
1387
+ }
1388
+ const tiers = [];
1389
+ for (const g of groups)
1390
+ for (const [t, v] of Object.entries(bindingsOf(g)))
1391
+ if (v === name)
1392
+ tiers.push(Object.freeze({ group: g['name'], tier: t }));
1393
+ return Object.freeze({
1394
+ name,
1395
+ modelId: m['id'],
1396
+ api: strOf(m['api']) ?? 'openai-completions',
1397
+ baseUrl: typeof m['baseUrl'] === 'string' ? m['baseUrl'] : '',
1398
+ enabled: isEnabled(m),
1399
+ ...(strOf(m['semaLabel']) === undefined ? {} : { label: strOf(m['semaLabel']) }),
1400
+ vision: visionView(resolveEntryVision({ modelId: m['id'], ...(visionIntent === undefined ? {} : { vision: visionIntent }) })),
1401
+ keyless,
1402
+ ...(keyEnv === undefined ? {} : { keyEnv }),
1403
+ ...(posInt(m['contextWindow']) ? { contextWindow: m['contextWindow'] } : {}),
1404
+ ...(posInt(m['maxTokens']) ? { maxTokens: m['maxTokens'] } : {}),
1405
+ probe: probeState,
1406
+ ...(probeState === 'unverified' && probeWord !== undefined ? { probeVerdict: probeWord } : {}),
1407
+ ...(probeWord === undefined || at === undefined ? {} : { probedAt: at }),
1408
+ isDefault: def.kind === 'named' && def.name === name,
1409
+ tiers: Object.freeze(tiers),
1410
+ deleteBlockers: Object.freeze(computeDeleteBlockers({ id: name, doc: blockDoc, rosters })),
1411
+ deleteWarnings: Object.freeze(computeDeleteWarnings({ id: name, doc: doc, tierGroups: legacyTiers, survivingIds: names.filter((x) => x !== name) })),
1412
+ ...(issue === undefined ? {} : { credentialIssue: issue }),
1413
+ });
1414
+ });
1415
+ const roles = [];
1416
+ if (isRecord(raw['roles'])) {
1417
+ for (const [role, target] of Object.entries(raw['roles'])) {
1418
+ if (!isRecord(target))
1419
+ continue;
1420
+ if (typeof target['model'] === 'string' && target['model'] !== '')
1421
+ roles.push(Object.freeze({ role, model: target['model'] }));
1422
+ else if (isRecord(target['select']))
1423
+ roles.push(Object.freeze({ role, select: true }));
1424
+ }
1425
+ }
1426
+ const tierGroups = groups.map((g) => {
1427
+ const tiers = bindingsOf(g);
1428
+ const resolved = {};
1429
+ for (const t of Object.keys(tiers).length > 0 ? TIER_ORDER : []) {
1430
+ const b = resolveTierBinding({ tiers }, t);
1431
+ if (b !== undefined)
1432
+ resolved[t] = b;
1433
+ }
1434
+ return Object.freeze({ name: g['name'], active: g['name'] === active, tiers: Object.freeze(tiers), resolved: Object.freeze(resolved) });
1435
+ });
1436
+ let activeTiers;
1437
+ try {
1438
+ activeTiers = SETTINGS_RESOLVE_ACTIVE_TIERS(raw);
1439
+ }
1440
+ catch {
1441
+ activeTiers = undefined;
1442
+ }
1443
+ return Object.freeze({
1444
+ catalog: 'read',
1445
+ completeness: unreadable > 0 ? 'catalog_incomplete' : 'complete',
1446
+ unreadableRows: unreadable,
1447
+ entries: Object.freeze(entries),
1448
+ roles: Object.freeze(roles),
1449
+ tierGroups: Object.freeze(tierGroups),
1450
+ ...(active !== undefined && activeTiers !== undefined ? { activeTierGroup: active } : {}),
1451
+ defaultModel: def,
1452
+ legacyTierSettings,
1453
+ orphanKeySlots: items.every(isRecord) ? orphanKeySlotsOf(items, settingsEnv).length : 0,
1454
+ envModels: Object.freeze(good.some(isEnabled) ? [] : [env['MODEL_ID'], env['MODEL_CHEAP_ID']].filter((v) => typeof v === 'string' && v.trim() !== '')),
1455
+ providers: providersOf(),
1456
+ });
1457
+ }
1458
+ export function modelPoolRowEditOf(row, action) {
1459
+ try {
1460
+ const name = own(row, 'name');
1461
+ if (typeof name !== 'string' || name === '')
1462
+ return undefined;
1463
+ const kind = own(action, 'kind');
1464
+ if (kind === 'remove')
1465
+ return own(row, 'deleteBlockers') !== undefined && (row.deleteBlockers?.length ?? 0) > 0 ? undefined : Object.freeze({ kind: 'remove', name });
1466
+ if (kind === 'setDefault')
1467
+ return row.enabled === false || row.isDefault === true ? undefined : Object.freeze({ kind: 'setDefault', name });
1468
+ if (kind === 'bindTier') {
1469
+ const group = own(action, 'group');
1470
+ const tier = own(action, 'tier');
1471
+ return typeof group === 'string' && isTier(tier) ? Object.freeze({ kind: 'bindTier', group, tier, name }) : undefined;
1472
+ }
1473
+ if (kind === 'update') {
1474
+ const a = action;
1475
+ return Object.freeze({
1476
+ kind: 'update',
1477
+ name,
1478
+ ...(a.label === undefined ? {} : { label: a.label }),
1479
+ ...(a.vision === undefined ? {} : { vision: a.vision }),
1480
+ ...(a.contextWindow === undefined ? {} : { contextWindow: a.contextWindow }),
1481
+ ...(a.maxTokens === undefined ? {} : { maxTokens: a.maxTokens }),
1482
+ });
1483
+ }
1484
+ return undefined;
1485
+ }
1486
+ catch {
1487
+ return undefined;
1488
+ }
1489
+ }