@visual-z/xeno 0.8.3 → 0.9.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 (348) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2066
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1713
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3001
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/canvas.ts +0 -96
  312. package/src/tui/ui/colors.ts +0 -70
  313. package/src/tui/ui/composer.ts +0 -200
  314. package/src/tui/ui/control-panel.ts +0 -25
  315. package/src/tui/ui/control-sidebar.ts +0 -49
  316. package/src/tui/ui/dialog.ts +0 -398
  317. package/src/tui/ui/draft.ts +0 -23
  318. package/src/tui/ui/duration.ts +0 -16
  319. package/src/tui/ui/edge.ts +0 -186
  320. package/src/tui/ui/image-size.ts +0 -44
  321. package/src/tui/ui/ink.ts +0 -210
  322. package/src/tui/ui/items.ts +0 -42
  323. package/src/tui/ui/keys.ts +0 -40
  324. package/src/tui/ui/layout.ts +0 -74
  325. package/src/tui/ui/list-model.ts +0 -86
  326. package/src/tui/ui/list-view.ts +0 -172
  327. package/src/tui/ui/mascot.ts +0 -67
  328. package/src/tui/ui/messages.ts +0 -518
  329. package/src/tui/ui/motion.ts +0 -28
  330. package/src/tui/ui/paths.ts +0 -10
  331. package/src/tui/ui/pictures.ts +0 -108
  332. package/src/tui/ui/screen.ts +0 -548
  333. package/src/tui/ui/status.ts +0 -403
  334. package/src/tui/ui/styled.ts +0 -85
  335. package/src/tui/ui/tool-card.ts +0 -150
  336. package/src/tui/width.ts +0 -567
  337. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  338. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  339. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  340. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  341. package/src/vendor/pi-rich/html.ts +0 -51
  342. package/src/vendor/pi-rich/image-convert.ts +0 -49
  343. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  344. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  345. package/src/vendor/pi-rich/image-resize.ts +0 -123
  346. package/src/vendor/pi-rich/mime.ts +0 -116
  347. package/src/vendor/pi-rich/photon.ts +0 -139
  348. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,168 +0,0 @@
1
- /** OAuth is a user-owned repair operation; the SDK owns discovery, PKCE and issuer/resource binding. */
2
- import { randomUUID } from "node:crypto";
3
- import { auth, checkResourceAllowed, resourceUrlFromServerUrl, StreamableHTTPClientTransport,
4
- type OAuthClientProvider, type OAuthClientInformationContext, type StoredOAuthClientInformation, type StoredOAuthTokens,
5
- type OAuthClientMetadata, type OAuthDiscoveryState, type FetchLike } from "@modelcontextprotocol/client";
6
- import type { McpOAuthConfig } from "../core/mcp-config.ts";
7
- import { isJsonValue } from "../core/tool-contract.ts";
8
- import type { McpCredentials, McpCredentialsStore } from "../store/mcp-credentials.ts";
9
- import { boundedResponseBytes } from "./web-http.ts";
10
-
11
- export class McpAuthRequired extends Error { constructor() { super("MCP authentication requires an explicit login"); this.name = "McpAuthRequired"; } }
12
- /** The SDK warns with OAuth error_description; normalize peer errors before they can reach its console path. */
13
- export function safeOAuthFetch(fetcher: FetchLike): FetchLike {
14
- return async (input, init) => {
15
- let response: Response;
16
- try { response = await fetcher(input, init); } catch { throw new McpAuthRequired(); }
17
- if (response.ok) return response;
18
- let code = "server_error";
19
- try {
20
- const value: unknown = JSON.parse(new TextDecoder().decode(await boundedResponseBytes(response, 16 * 1024)));
21
- if (value && typeof value === "object" && "error" in value && typeof value.error === "string"
22
- && ["invalid_request", "invalid_client", "invalid_grant", "unauthorized_client", "unsupported_grant_type", "invalid_scope", "access_denied", "server_error", "temporarily_unavailable"].includes(value.error)) code = value.error;
23
- } catch { /* Unknown peer bodies contribute status only, never diagnostics containing secrets. */ }
24
- return Response.json({ error: code, error_description: "OAuth request rejected" }, { status: response.status, headers: { "content-type": "application/json" } });
25
- };
26
- }
27
- function savedClient(value: unknown): value is StoredOAuthClientInformation {
28
- return !!value && typeof value === "object" && "client_id" in value && typeof value.client_id === "string";
29
- }
30
- function savedTokens(value: unknown): value is StoredOAuthTokens {
31
- return !!value && typeof value === "object" && "access_token" in value && typeof value.access_token === "string"
32
- && "token_type" in value && typeof value.token_type === "string";
33
- }
34
- /** A per-resource provider; discovery/verifier/state exist only during this owned process's login. */
35
- export class McpOAuthProvider implements OAuthClientProvider {
36
- private readonly url: string;
37
- private readonly config: McpOAuthConfig;
38
- private readonly store: McpCredentialsStore;
39
- private readonly env: Readonly<Record<string, string | undefined>>;
40
- private credentials: McpCredentials;
41
- private verifier: string | undefined;
42
- private discovery: OAuthDiscoveryState | undefined;
43
- private expectedState: string | undefined;
44
- private redirect: ((url: URL) => void | Promise<void>) | undefined;
45
- private refreshing: Promise<void> | undefined;
46
- private resourceMetadataUrl: URL | undefined;
47
- private challengedScope: string | undefined;
48
- private constructor(url: string, config: McpOAuthConfig,
49
- store: McpCredentialsStore, env: Readonly<Record<string, string | undefined>>, credentials?: McpCredentials) {
50
- this.url = url; this.config = config; this.store = store; this.env = env;
51
- this.credentials = credentials ?? { version: 1, serverUrl: url, redirectUrl: config.redirectUrl ?? "http://127.0.0.1:0/callback",
52
- clients: {}, tokens: {}, expiresAt: {}, activeIssuer: null, ...(config.clientId ? { clientIdentity: config.clientId } : {}) };
53
- if (config.redirectUrl) this.credentials.redirectUrl = config.redirectUrl;
54
- }
55
- static async create(url: string, config: McpOAuthConfig, store: McpCredentialsStore, env: Readonly<Record<string, string | undefined>>) {
56
- return new McpOAuthProvider(url, config, store, env, await store.read(url, config.clientId));
57
- }
58
- get redirectUrl() { return this.credentials.redirectUrl; }
59
- get clientMetadata(): OAuthClientMetadata {
60
- return { client_name: "xeno", application_type: "native", redirect_uris: [this.redirectUrl], response_types: ["code"],
61
- grant_types: ["authorization_code", "refresh_token"], token_endpoint_auth_method: this.config.clientSecretEnv ? "client_secret_basic" : "none",
62
- ...(this.config.scope ? { scope: this.config.scope } : {}) };
63
- }
64
- clientInformation(ctx?: OAuthClientInformationContext): StoredOAuthClientInformation | undefined {
65
- if (this.config.clientId) {
66
- const secret = this.config.clientSecretEnv ? this.env[this.config.clientSecretEnv] : undefined;
67
- if (this.config.clientSecretEnv && !secret) throw new McpAuthRequired();
68
- return { client_id: this.config.clientId, ...(secret ? { client_secret: secret } : {}), ...(ctx ? { issuer: ctx.issuer } : {}) };
69
- }
70
- const value = ctx ? this.credentials.clients[ctx.issuer] : undefined;
71
- return savedClient(value) ? value : undefined;
72
- }
73
- async saveClientInformation(info: StoredOAuthClientInformation, ctx?: OAuthClientInformationContext) {
74
- const issuer = ctx?.issuer ?? info.issuer;
75
- if (!issuer || !isJsonValue(info)) throw new McpAuthRequired();
76
- this.credentials.clients[issuer] = info;
77
- await this.store.write(this.credentials);
78
- }
79
- tokens(ctx?: OAuthClientInformationContext): StoredOAuthTokens | undefined {
80
- const issuer = ctx?.issuer ?? this.credentials.activeIssuer, value = issuer ? this.credentials.tokens[issuer] : undefined;
81
- return savedTokens(value) ? value : undefined;
82
- }
83
- async saveTokens(tokens: StoredOAuthTokens, ctx?: OAuthClientInformationContext) {
84
- const issuer = ctx?.issuer ?? tokens.issuer;
85
- if (!issuer || !isJsonValue(tokens)) throw new McpAuthRequired();
86
- this.credentials.tokens[issuer] = tokens; this.credentials.activeIssuer = issuer;
87
- if (tokens.expires_in !== undefined) this.credentials.expiresAt[issuer] = Date.now() + tokens.expires_in * 1_000;
88
- else delete this.credentials.expiresAt[issuer];
89
- await this.store.write(this.credentials);
90
- }
91
- state() { this.expectedState = randomUUID(); return this.expectedState; }
92
- redirectToAuthorization(url: URL) {
93
- if (!this.redirect) throw new McpAuthRequired();
94
- return this.redirect(url);
95
- }
96
- saveCodeVerifier(verifier: string) { this.verifier = verifier; }
97
- codeVerifier() { if (!this.verifier) throw new McpAuthRequired(); return this.verifier; }
98
- saveDiscoveryState(state: OAuthDiscoveryState) { this.discovery = state; }
99
- discoveryState() { return this.discovery; }
100
- setChallenge(resourceMetadataUrl: URL | undefined, scope: string | undefined) {
101
- this.resourceMetadataUrl = resourceMetadataUrl; this.challengedScope = scope; this.discovery = undefined;
102
- }
103
- private authOptions(fetcher: FetchLike) {
104
- const scope = this.config.scope ?? this.challengedScope;
105
- return { serverUrl: this.url, fetchFn: safeOAuthFetch(fetcher), ...(this.resourceMetadataUrl ? { resourceMetadataUrl: this.resourceMetadataUrl } : {}), ...(scope ? { scope } : {}) };
106
- }
107
- async validateResourceURL(serverUrl: string | URL, resource?: string) {
108
- const expected = resourceUrlFromServerUrl(serverUrl);
109
- if (resource && !checkResourceAllowed({ requestedResource: expected, configuredResource: resource })) throw new McpAuthRequired();
110
- return expected;
111
- }
112
- async invalidateCredentials(scope: "all" | "client" | "tokens" | "verifier" | "discovery") {
113
- if (scope === "all" || scope === "client") this.credentials.clients = {};
114
- if (scope === "all" || scope === "tokens") { this.credentials.tokens = {}; this.credentials.expiresAt = {}; this.credentials.activeIssuer = null; }
115
- if (scope === "all" || scope === "verifier") this.verifier = undefined;
116
- if (scope === "all" || scope === "discovery") this.discovery = undefined;
117
- if (scope === "all") await this.store.remove(this.url, this.config.clientId); else await this.store.write(this.credentials);
118
- }
119
- async token(fetcher: FetchLike): Promise<string | undefined> {
120
- const issuer = this.credentials.activeIssuer, expiresAt = issuer ? this.credentials.expiresAt[issuer] : undefined;
121
- // Assumed 30s skew prevents sending a token that expires while the new request is in flight.
122
- if (expiresAt !== undefined && expiresAt <= Date.now() + 30_000) {
123
- if (!this.refreshing) {
124
- this.refreshing = (async () => {
125
- if (!this.tokens()?.refresh_token) { await this.invalidateCredentials("tokens"); throw new McpAuthRequired(); }
126
- try { if (await auth(this, this.authOptions(fetcher)) !== "AUTHORIZED") throw new McpAuthRequired(); }
127
- catch { await this.invalidateCredentials("tokens"); throw new McpAuthRequired(); }
128
- })().finally(() => { this.refreshing = undefined; });
129
- }
130
- await this.refreshing;
131
- }
132
- return this.tokens()?.access_token;
133
- }
134
- async login(onAuthorizationUrl: (url: URL) => void | Promise<void>, fetcher: FetchLike, signal: AbortSignal): Promise<void> {
135
- if (this.redirect) throw new McpAuthRequired();
136
- signal.throwIfAborted();
137
- const callback = new URL(this.redirectUrl);
138
- let resolveCallback: (value: URLSearchParams) => void = () => {}, rejectCallback: (error: Error) => void = () => {};
139
- const received = new Promise<URLSearchParams>((resolve, reject) => { resolveCallback = resolve; rejectCallback = reject; });
140
- // Reject handlers are installed before starting auth; cancellation never leaves an unhandled callback promise.
141
- void received.catch(() => {});
142
- const server = Bun.serve({ hostname: "127.0.0.1", port: Number(callback.port), fetch: request => {
143
- const current = new URL(request.url);
144
- if (current.pathname !== callback.pathname) return new Response("Not found", { status: 404 });
145
- if (request.method !== "GET" || !this.expectedState || current.searchParams.get("state") !== this.expectedState) return new Response("Invalid authorization state", { status: 400 });
146
- if (current.searchParams.has("error") || !current.searchParams.get("code")) {
147
- rejectCallback(new McpAuthRequired()); return new Response("Authorization was not completed", { status: 400 });
148
- }
149
- resolveCallback(current.searchParams); return new Response("Authorization received. Return to xeno.", { headers: { "content-type": "text/plain" } });
150
- } });
151
- const selected = new URL(callback.pathname, server.url).href;
152
- if (selected !== this.credentials.redirectUrl && !this.config.clientId) this.credentials.clients = {};
153
- this.credentials.redirectUrl = selected; this.redirect = onAuthorizationUrl;
154
- const onAbort = () => rejectCallback(new McpAuthRequired());
155
- signal.addEventListener("abort", onAbort, { once: true });
156
- try {
157
- const safeFetch = safeOAuthFetch(fetcher);
158
- const result = await auth(this, this.authOptions(fetcher));
159
- if (result === "AUTHORIZED") return;
160
- const params = await received;
161
- const transport = new StreamableHTTPClientTransport(new URL(this.url), { authProvider: this, fetch: safeFetch, onInsufficientScope: "throw" });
162
- try { await transport.finishAuth(params); } finally { await transport.close(); }
163
- } finally {
164
- signal.removeEventListener("abort", onAbort); server.stop(true); this.redirect = undefined;
165
- this.expectedState = undefined; this.verifier = undefined; this.discovery = undefined;
166
- }
167
- }
168
- }
@@ -1,51 +0,0 @@
1
- /** MCP 2025-11-25 defaults to 2020-12; validation never repairs the frozen invocation. */
2
- import { Ajv, type ValidateFunction } from "ajv";
3
- import { Ajv2019 } from "ajv/dist/2019.js";
4
- import { Ajv2020 } from "ajv/dist/2020.js";
5
- import { addFormats } from "@modelcontextprotocol/client/validators/ajv";
6
- import type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator } from "@modelcontextprotocol/client";
7
- import { NOT_STARTED, ToolFault } from "../core/tool-failure.ts";
8
-
9
- export class McpSchemaError extends Error {
10
- constructor() { super("MCP schema is invalid or uses an unsupported dialect, reference, keyword or format; repair the server schema before reloading."); this.name = "McpSchemaError"; }
11
- }
12
-
13
- /** The official client's format package is bundled in its public validator entry; no extra dependency. */
14
- export class McpSchemas implements jsonSchemaValidator {
15
- private readonly compiled = new Map<string, ValidateFunction>();
16
- validator(schema: unknown): ValidateFunction {
17
- if (!schema || typeof schema !== "object" || Array.isArray(schema)) throw new McpSchemaError();
18
- const key = JSON.stringify(schema), cached = this.compiled.get(key);
19
- if (cached) return cached;
20
- try {
21
- const dialect = (schema as Record<string, unknown>)["$schema"];
22
- const options = { strictSchema: true, strictNumbers: true, strictTypes: false, strictTuples: false, strictRequired: false,
23
- validateFormats: true, coerceTypes: false, useDefaults: false, removeAdditional: false, allErrors: false, logger: false as const };
24
- // Strict schema rejects unknown semantics, while strict style checks are disabled:
25
- // valid JSON Schema need not spell out every tuple bound or repeat inherited types.
26
- const ajv = dialect === undefined || dialect === "https://json-schema.org/draft/2020-12/schema" ? new Ajv2020(options)
27
- : dialect === "https://json-schema.org/draft/2019-09/schema" ? new Ajv2019(options)
28
- : dialect === "http://json-schema.org/draft-07/schema#" || dialect === "http://json-schema.org/draft-07/schema" ? new Ajv(options)
29
- : undefined;
30
- if (!ajv) throw new McpSchemaError();
31
- addFormats(ajv);
32
- // MCP's header annotation is interpreted by the official transport, not a validation constraint.
33
- ajv.addKeyword({ keyword: "x-mcp-header", schemaType: "string", valid: true });
34
- // Ajv validates this entire JSON schema; the assertion only bridges the SDK's wider wire type.
35
- const validate = ajv.compile(structuredClone(schema) as JsonSchemaType);
36
- this.compiled.set(key, validate); return validate;
37
- } catch { throw new McpSchemaError(); }
38
- }
39
- getValidator<T>(schema: JsonSchemaType): JsonSchemaValidator<T> {
40
- const validate = this.validator(schema);
41
- return input => validate(input) ? { valid: true, data: input as T, errorMessage: undefined }
42
- : { valid: false, data: undefined, errorMessage: "MCP structured output failed the advertised schema." };
43
- }
44
- prepare(schema: unknown): (input: unknown) => unknown {
45
- const validate = this.validator(schema);
46
- return input => {
47
- if (!validate(input)) throw new ToolFault("invalid_input", "MCP arguments do not satisfy the complete advertised schema. Correct the input before calling the remote tool.", NOT_STARTED);
48
- return input;
49
- };
50
- }
51
- }
@@ -1,232 +0,0 @@
1
- /** Config parsing owns provider wire vocabulary; identity/filter policy lives in core. */
2
- import { mergeCatalogs, modelEnabled } from "../core/catalog-merge.ts";
3
- import { modelKey } from "../core/session-position.ts";
4
- import { THINKING_LEVELS } from "../core/thinking.ts";
5
- import { SOURCE_PROVIDERS } from "./model-data.ts";
6
- import { DEFAULT_COMPAT, type ModelSpec, type Effort } from "./model.ts";
7
- import { isProviderApi, SDK_APIS } from "./provider-protocols.ts";
8
- import { providerAccess, setProviderAccess, type ProviderAccess } from "./provider-access.ts";
9
-
10
- function object(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }
11
- function own(value: Record<string, unknown>, key: string): boolean { return Object.hasOwn(value, key); }
12
- function positive(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value > 0; }
13
- function nonnegative(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value) && value >= 0; }
14
- function error(path: string, message: string): never { throw new Error(`${path}: ${message}`); }
15
- function strings(value: unknown, path: string): string[] | undefined {
16
- if (value === undefined) return undefined;
17
- if (!Array.isArray(value) || !value.every(x => typeof x === "string")) error(path, "must be an array of strings");
18
- return value as string[];
19
- }
20
- function stringMap(value: unknown, path: string, nullable = false): Record<string, string | null> | undefined {
21
- if (value === undefined) return undefined;
22
- if (!object(value) || !Object.values(value).every(x => typeof x === "string" || (nullable && x === null))) error(path, "must contain string values" + (nullable ? " or null" : ""));
23
- return Object.fromEntries(Object.entries(value as Record<string, string | null>).map(([key, v]) => [key, typeof v === "string" ? configValue(v) : v]));
24
- }
25
- /** OpenCode's env reference and Pi's interpolation share one resolver. No implicit uppercase lookup. */
26
- function configValue(value: string): string { return value.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, "$${$1}"); }
27
- const RESERVED_BODY = ["model", "modelId", "messages", "system", "systemInstruction", "system_instruction", "instructions", "input", "contents", "context", "tools", "toolConfig", "stream", "previous_response_id", "store", "conversation", "conversation_id", "sessionId", "session_id", "httpOptions", "http_options", "headers", "apiKey", "authToken", "baseUrl", "baseURL", "fetch", "abortSignal", "signal", "onPayload", "onResponse"];
28
- const REQUEST_OPTIONS = ["temperature", "samplingParams", "cacheRetention", "timeoutMs", "maxRetryDelayMs", "metadata"];
29
-
30
- function payloadPatch(value: unknown, path: string): Record<string, unknown> | undefined {
31
- if (value === undefined) return undefined;
32
- if (!object(value)) error(path, "must be an object");
33
- for (const [key, child] of Object.entries(value)) {
34
- if (RESERVED_BODY.includes(key)) error(`${path}.${key}`, "is owned by the log projection and request adapter");
35
- if ((key === "config" || key === "options") && !object(child)) error(`${path}.${key}`, "must be an object");
36
- if (object(child)) payloadPatch(child, `${path}.${key}`);
37
- }
38
- return value;
39
- }
40
-
41
- function accessPatch(raw: Record<string, unknown>, path: string, parent: ProviderAccess = {}): ProviderAccess {
42
- if (raw["options"] !== undefined && !object(raw["options"])) error(`${path}.options`, "must be an object");
43
- const options = object(raw["options"]) ? raw["options"] : {};
44
- const apiKey = own(raw, "apiKey") ? raw["apiKey"] : options["apiKey"];
45
- if (apiKey !== undefined && (typeof apiKey !== "string" || !apiKey.length)) error(`${path}.apiKey`, "must be a non-empty string; use auth: false for a keyless endpoint");
46
- const auth = raw["auth"];
47
- if (auth !== undefined && auth !== false && auth !== "ambient") error(`${path}.auth`, "must be false or ambient");
48
- const authHeader = raw["authHeader"];
49
- if (authHeader !== undefined && typeof authHeader !== "boolean" && (typeof authHeader !== "string" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(authHeader))) error(`${path}.authHeader`, "must be a boolean or header name");
50
- const headers = stringMap(raw["headers"] ?? options["headers"] ?? raw["defaultHeaders"], `${path}.headers`, true);
51
- if (headers && Object.keys(headers).some(key => !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(key))) error(`${path}.headers`, "contains an invalid header name");
52
- const env = stringMap(raw["env"], `${path}.env`) as Record<string, string> | undefined;
53
- if (env && Object.keys(env).some(key => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))) error(`${path}.env`, "contains an invalid variable name");
54
- const body = payloadPatch(raw["body"] ?? raw["extraBody"] ?? options["body"] ?? options["extraBody"], `${path}.body`);
55
- const requestOptions = Object.fromEntries(Object.entries(options).filter(([key]) => !["apiKey", "baseURL", "baseUrl", "headers", "body", "extraBody"].includes(key)));
56
- for (const key of Object.keys(requestOptions)) {
57
- if (!REQUEST_OPTIONS.includes(key)) error(`${path}.options.${key}`, "is not a configurable request option; use env for native cloud settings or body for API parameters");
58
- }
59
- payloadPatch(requestOptions["samplingParams"], `${path}.options.samplingParams`);
60
- for (const key of ["temperature", "timeoutMs", "maxRetryDelayMs"]) if (requestOptions[key] !== undefined && !nonnegative(requestOptions[key])) error(`${path}.options.${key}`, "must be a finite nonnegative number");
61
- if (requestOptions["cacheRetention"] !== undefined && !["none", "short", "long"].includes(String(requestOptions["cacheRetention"]))) error(`${path}.options.cacheRetention`, "must be none, short or long");
62
- stringMap(requestOptions["metadata"], `${path}.options.metadata`);
63
- return { ...parent,
64
- ...(typeof apiKey === "string" ? { apiKey: configValue(apiKey) } : {}),
65
- ...(auth === false || auth === "ambient" ? { auth } : {}),
66
- ...(typeof authHeader === "boolean" || typeof authHeader === "string" ? { authHeader } : {}),
67
- ...(headers ? { headers: { ...parent.headers, ...headers } } : {}),
68
- ...(env ? { env: { ...parent.env, ...env } } : {}),
69
- ...(object(body) ? { body: { ...parent.body, ...body } } : {}),
70
- ...(Object.keys(requestOptions).length ? { options: { ...parent.options, ...requestOptions } } : {}) };
71
- }
72
-
73
- function modelPatch(raw: Record<string, unknown>, base: ModelSpec | undefined, provider: string, id: string, path: string, partial: boolean): ModelSpec {
74
- const options = object(raw["options"]) ? raw["options"] : {};
75
- const endpoint = raw["baseUrl"] ?? raw["baseURL"] ?? options["baseURL"] ?? options["baseUrl"];
76
- if (raw["npm"] !== undefined && (typeof raw["npm"] !== "string" || !SDK_APIS[raw["npm"]]) && raw["api"] === undefined) error(`${path}.npm`, "unsupported SDK; select a supported api explicitly");
77
- const api = raw["api"] ?? (typeof raw["npm"] === "string" ? SDK_APIS[raw["npm"]] : undefined) ?? base?.api ?? SOURCE_PROVIDERS[provider]?.api;
78
- if (!isProviderApi(api)) error(`${path}.api`, "unknown or missing protocol");
79
- if (endpoint !== undefined && typeof endpoint !== "string") error(`${path}.baseUrl`, "must be a string");
80
- const spec: ModelSpec = { ...(base ?? {}), provider, id, name: base?.name ?? id, api,
81
- reasoning: base?.reasoning ?? false, ...(partial ? {} : { driver: "pi" }),
82
- ...(typeof endpoint === "string" ? { baseUrl: configValue(endpoint) } : base?.baseUrl !== undefined ? {} : SOURCE_PROVIDERS[provider]?.baseUrl ? { baseUrl: SOURCE_PROVIDERS[provider].baseUrl } : {}) };
83
- if (raw["api"] !== undefined && raw["api"] !== base?.api) { delete spec.compat; delete spec.nativeCompat; delete spec.thinkingLevelMap; }
84
- if (typeof raw["name"] === "string" && raw["name"].length) spec.name = raw["name"];
85
- if (own(raw, "reasoning")) { if (typeof raw["reasoning"] !== "boolean") error(`${path}.reasoning`, "must be boolean"); spec.reasoning = raw["reasoning"]; }
86
- const limit = object(raw["limit"]) ? raw["limit"] : {};
87
- for (const [key, value] of [["contextWindow", raw["contextWindow"] ?? limit["context"]], ["maxTokens", raw["maxTokens"] ?? limit["output"]]] as const) {
88
- if (value !== undefined) { if (!positive(value)) error(`${path}.${key}`, "must be a finite positive number"); spec[key] = value; }
89
- }
90
- if (own(raw, "input")) {
91
- if (!Array.isArray(raw["input"]) || !raw["input"].every(x => x === "text" || x === "image")) error(`${path}.input`, "must contain only text or image");
92
- spec.input = raw["input"] as ("text" | "image")[];
93
- }
94
- if (raw["credentialEnv"] !== undefined) {
95
- const names = strings(raw["credentialEnv"], `${path}.credentialEnv`)!;
96
- if (names.some(name => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))) error(`${path}.credentialEnv`, "must contain variable names");
97
- spec.credentialEnv = names;
98
- }
99
- const price = raw["price"] ?? raw["cost"];
100
- if (price !== undefined) {
101
- if (!object(price) || !nonnegative(price["input"]) || !nonnegative(price["output"]) || ["cacheRead", "cacheWrite"].some(k => own(price, k) && !nonnegative(price[k]))) error(`${path}.price`, "requires finite nonnegative input/output and optional cache prices");
102
- spec.price = { input: price["input"], output: price["output"],
103
- ...(nonnegative(price["cacheRead"]) ? { cacheRead: price["cacheRead"] } : {}),
104
- ...(nonnegative(price["cacheWrite"]) ? { cacheWrite: price["cacheWrite"] } : {}) };
105
- }
106
- if (raw["cache"] !== undefined) { if (raw["cache"] !== "explicit" && raw["cache"] !== "automatic") error(`${path}.cache`, "must be explicit or automatic"); spec.cache = raw["cache"]; }
107
- if (raw["rates"] !== undefined) {
108
- const rates = raw["rates"];
109
- if (!object(rates) || !nonnegative(rates["read"]) || !nonnegative(rates["write"])) error(`${path}.rates`, "requires finite nonnegative read/write");
110
- spec.rates = { read: rates["read"], write: rates["write"] };
111
- }
112
- if (raw["thinkingLevelMap"] !== undefined) {
113
- const map = raw["thinkingLevelMap"];
114
- if (!object(map) || !Object.entries(map).every(([key, value]) => (THINKING_LEVELS as readonly string[]).includes(key) && (value === null || typeof value === "string"))) error(`${path}.thinkingLevelMap`, "requires supported effort keys and string/null values");
115
- spec.thinkingLevelMap = map as Partial<Record<Effort, string | null>>;
116
- }
117
- for (const key of ["compat", "nativeCompat", "samplingParams"] as const) {
118
- const value = raw[key];
119
- if (value !== undefined) {
120
- if (!object(value)) error(`${path}.${key}`, "must be an object");
121
- if (key === "samplingParams") payloadPatch(value, `${path}.${key}`);
122
- else for (const [field, flag] of Object.entries(value)) {
123
- if (/^(supports|requires)/.test(field) && typeof flag !== "boolean") error(`${path}.${key}.${field}`, "must be boolean");
124
- if (field === "maxTokensField" && flag !== "max_tokens" && flag !== "max_completion_tokens") error(`${path}.${key}.${field}`, "must be max_tokens or max_completion_tokens");
125
- }
126
- if (key === "compat") {
127
- spec.nativeCompat = { ...spec.nativeCompat, ...value };
128
- spec.compat = { ...DEFAULT_COMPAT, ...base?.compat, ...value } as NonNullable<ModelSpec["compat"]>;
129
- } else spec[key] = { ...spec[key], ...value };
130
- }
131
- }
132
- if (raw["driver"] === "pi") spec.driver = "pi";
133
- if (typeof raw["wireId"] === "string") spec.wireId = raw["wireId"];
134
- return spec;
135
- }
136
-
137
- export function parseModelConfiguration(json: unknown, builtin: readonly ModelSpec[] = []): { models: ModelSpec[]; errors: string[] } {
138
- let models = [...builtin];
139
- const errors: string[] = [];
140
- const report = (err: unknown) => errors.push(err instanceof Error ? err.message : "invalid model configuration");
141
- if (!Array.isArray(json) && !object(json)) return { models, errors: ["user catalog must be an array or an object with models/providers/provider"] };
142
- const root = object(json) ? json : {};
143
- const rows = Array.isArray(json) ? json : root["models"];
144
- if (rows !== undefined && !Array.isArray(rows)) errors.push("user catalog .models is not an array");
145
- const indexed = new Map(models.map(model => [modelKey(model), model]));
146
- if (Array.isArray(rows)) for (const [i, row] of rows.entries()) {
147
- const path = `models[${i}]` + (object(row) && typeof row["id"] === "string" ? ` (${row["id"]})` : "");
148
- try {
149
- if (!object(row)) error(path, "must be an object");
150
- if (typeof row["id"] !== "string" || !row["id"].length) error(path, "has no id");
151
- if (typeof row["provider"] !== "string" || !row["provider"].length) error(path, "has no provider");
152
- const id = row["id"], provider = row["provider"];
153
- if (row["disabled"] === true || row["enabled"] === false) { indexed.delete(modelKey({ provider, id })); continue; }
154
- const previous = indexed.get(modelKey({ provider, id }));
155
- const model = modelPatch(row, previous, provider, id, path, true);
156
- const access = accessPatch(row, path, previous ? providerAccess(previous) : {});
157
- if (Object.keys(access).length) { model.driver = "pi"; setProviderAccess(model, access); }
158
- indexed.set(modelKey(model), model);
159
- } catch (err) {
160
- report(err);
161
- if (object(row) && typeof row["id"] === "string" && typeof row["provider"] === "string") {
162
- const key = modelKey({ id: row["id"], provider: row["provider"] });
163
- const previous = indexed.get(key);
164
- if (previous) { const copy = { ...previous }; setProviderAccess(copy, { invalid: `${path}: invalid model configuration` }); indexed.set(key, copy); }
165
- }
166
- }
167
- }
168
- models = [...indexed.values()];
169
- for (const groupKey of ["providers", "provider"] as const) {
170
- const providers = root[groupKey];
171
- if (providers === undefined) continue;
172
- if (!object(providers)) { errors.push(`${groupKey}: must be an object`); continue; }
173
- for (const [provider, config] of Object.entries(providers)) {
174
- const path = `${groupKey}.${provider}`;
175
- try {
176
- if (!object(config)) error(path, "must be an object");
177
- if (config["disabled"] === true || config["enabled"] === false) { models = models.filter(model => model.provider !== provider); continue; }
178
- const access = accessPatch(config, path);
179
- const allow = strings(config["allowlist"] ?? config["whitelist"], `${path}.allowlist`);
180
- const deny = strings(config["denylist"] ?? config["blacklist"], `${path}.denylist`);
181
- const peers: ModelSpec[] = [];
182
- const defaults = { ...config }; delete defaults["name"];
183
- for (const previous of models.filter(model => model.provider === provider)) {
184
- const next = modelPatch(defaults, previous, provider, previous.id, path, false);
185
- setProviderAccess(next, accessPatch(config, path, providerAccess(previous))); peers.push(next);
186
- }
187
- const definitions = config["models"];
188
- if (definitions !== undefined && !Array.isArray(definitions) && !object(definitions)) error(`${path}.models`, "must be an array or object");
189
- const additions: [string | undefined, unknown][] = Array.isArray(definitions) ? definitions.map(row => [undefined, row]) : object(definitions) ? Object.entries(definitions) : [];
190
- const patches = config["modelOverrides"];
191
- if (patches !== undefined && !object(patches)) error(`${path}.modelOverrides`, "must be an object");
192
- const entries = [...additions.map(([id, row]) => ({ id, row, override: false })), ...Object.entries(object(patches) ? patches : {}).map(([id, row]) => ({ id, row, override: true }))];
193
- for (const [index, entry] of entries.entries()) {
194
- const label = `${path}.${entry.override ? "modelOverrides" : "models"}.${entry.id ?? index}`;
195
- try {
196
- if (!object(entry.row)) error(label, "must be an object");
197
- const id = entry.id ?? entry.row["id"];
198
- if (typeof id !== "string" || !id.length) error(label, "has no id");
199
- if (entry.row["disabled"] === true || entry.row["enabled"] === false) { const at = peers.findIndex(model => model.id === id); if (at >= 0) peers.splice(at, 1); continue; }
200
- const base = peers.find(model => model.id === id);
201
- if (entry.override && !base) error(label, "cannot override an unknown model");
202
- const inherited = base ?? modelPatch(defaults, undefined, provider, id, path, false);
203
- const model = modelPatch(entry.row, inherited, provider, id, label, false);
204
- if (entry.id && typeof entry.row["id"] === "string" && entry.row["id"] !== id) model.wireId = entry.row["id"];
205
- setProviderAccess(model, accessPatch(entry.row, label, base ? providerAccess(base) : access));
206
- const at = peers.findIndex(candidate => candidate.id === id);
207
- if (at < 0) peers.push(model); else peers[at] = model;
208
- } catch (err) {
209
- report(err);
210
- const id = entry.id ?? (object(entry.row) ? entry.row["id"] : undefined);
211
- const at = peers.findIndex(model => model.id === id);
212
- if (at >= 0) { const copy = { ...peers[at]! }; setProviderAccess(copy, { invalid: `${label}: invalid model configuration` }); peers[at] = copy; }
213
- }
214
- }
215
- models = mergeCatalogs(models.filter(model => model.provider !== provider), peers.filter(model => modelEnabled(model.id, allow, deny)));
216
- } catch (err) {
217
- report(err);
218
- // An invalid explicit provider config must not silently use a different credential/endpoint.
219
- models = models.map(model => {
220
- if (model.provider !== provider) return model;
221
- const copy = { ...model }; setProviderAccess(copy, { invalid: `${path}: invalid provider configuration` }); return copy;
222
- });
223
- }
224
- }
225
- }
226
- try {
227
- const enabled = strings(root["enabled_providers"], "enabled_providers");
228
- const disabled = strings(root["disabled_providers"], "disabled_providers");
229
- models = models.filter(model => modelEnabled(model.provider, enabled, disabled));
230
- } catch (err) { report(err); }
231
- return { models, errors };
232
- }
@@ -1,27 +0,0 @@
1
- The generated model catalog contains data adapted from:
2
-
3
- Pi (https://github.com/earendil-works/pi)
4
- Copyright (c) 2025 Mario Zechner
5
-
6
- Models.dev (https://github.com/anomalyco/models.dev)
7
- Copyright (c) 2025 models.dev
8
-
9
- Both sources are distributed under the following MIT License:
10
-
11
- Permission is hereby granted, free of charge, to any person obtaining a copy
12
- of this software and associated documentation files (the "Software"), to deal
13
- in the Software without restriction, including without limitation the rights
14
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
- copies of the Software, and to permit persons to whom the Software is
16
- furnished to do so, subject to the following conditions:
17
-
18
- The above copyright notice and this permission notice shall be included in all
19
- copies or substantial portions of the Software.
20
-
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
- SOFTWARE.