@astralyn/pi 0.84.14 → 0.84.16

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 (185) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cli.js +2435 -18
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +12 -6
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/types.d.ts +4 -1
  7. package/dist/core/extensions/types.d.ts.map +1 -1
  8. package/dist/core/extensions/types.js.map +1 -1
  9. package/dist/core/tools/bash.d.ts +1 -0
  10. package/dist/core/tools/bash.d.ts.map +1 -1
  11. package/dist/core/tools/bash.js +4 -1
  12. package/dist/core/tools/bash.js.map +1 -1
  13. package/dist/core/tools/find.d.ts +2 -0
  14. package/dist/core/tools/find.d.ts.map +1 -1
  15. package/dist/core/tools/find.js +18 -15
  16. package/dist/core/tools/find.js.map +1 -1
  17. package/dist/extensions/background/actions.d.ts.map +1 -1
  18. package/dist/extensions/background/actions.js +3 -2
  19. package/dist/extensions/background/actions.js.map +1 -1
  20. package/dist/extensions/background/index.d.ts +5 -4
  21. package/dist/extensions/background/index.d.ts.map +1 -1
  22. package/dist/extensions/background/index.js +9 -5
  23. package/dist/extensions/background/index.js.map +1 -1
  24. package/dist/extensions/background/manager.d.ts.map +1 -1
  25. package/dist/extensions/background/manager.js +8 -9
  26. package/dist/extensions/background/manager.js.map +1 -1
  27. package/dist/extensions/background/registry.d.ts +4 -1
  28. package/dist/extensions/background/registry.d.ts.map +1 -1
  29. package/dist/extensions/background/registry.js +30 -17
  30. package/dist/extensions/background/registry.js.map +1 -1
  31. package/dist/extensions/background/task-view.d.ts +6 -0
  32. package/dist/extensions/background/task-view.d.ts.map +1 -1
  33. package/dist/extensions/background/task-view.js +11 -6
  34. package/dist/extensions/background/task-view.js.map +1 -1
  35. package/dist/extensions/deepwiki/client.d.ts.map +1 -1
  36. package/dist/extensions/deepwiki/client.js +3 -0
  37. package/dist/extensions/deepwiki/client.js.map +1 -1
  38. package/dist/extensions/deepwiki/execute.d.ts +2 -1
  39. package/dist/extensions/deepwiki/execute.d.ts.map +1 -1
  40. package/dist/extensions/deepwiki/execute.js +17 -2
  41. package/dist/extensions/deepwiki/execute.js.map +1 -1
  42. package/dist/extensions/deepwiki/http.d.ts +1 -0
  43. package/dist/extensions/deepwiki/http.d.ts.map +1 -1
  44. package/dist/extensions/deepwiki/http.js +16 -2
  45. package/dist/extensions/deepwiki/http.js.map +1 -1
  46. package/dist/extensions/deepwiki/schema.d.ts +1 -1
  47. package/dist/extensions/deepwiki/schema.d.ts.map +1 -1
  48. package/dist/extensions/deepwiki/schema.js +8 -2
  49. package/dist/extensions/deepwiki/schema.js.map +1 -1
  50. package/dist/extensions/question/dialog.d.ts.map +1 -1
  51. package/dist/extensions/question/dialog.js +80 -9
  52. package/dist/extensions/question/dialog.js.map +1 -1
  53. package/dist/extensions/question/index.d.ts.map +1 -1
  54. package/dist/extensions/question/index.js +8 -0
  55. package/dist/extensions/question/index.js.map +1 -1
  56. package/dist/extensions/question/schema.d.ts +2 -2
  57. package/dist/extensions/question/schema.d.ts.map +1 -1
  58. package/dist/extensions/question/schema.js +41 -11
  59. package/dist/extensions/question/schema.js.map +1 -1
  60. package/dist/extensions/question/types.d.ts +1 -1
  61. package/dist/extensions/question/types.d.ts.map +1 -1
  62. package/dist/extensions/question/types.js.map +1 -1
  63. package/dist/extensions/router/probe.d.ts.map +1 -1
  64. package/dist/extensions/router/probe.js +10 -29
  65. package/dist/extensions/router/probe.js.map +1 -1
  66. package/dist/extensions/router/register.d.ts.map +1 -1
  67. package/dist/extensions/router/register.js +16 -6
  68. package/dist/extensions/router/register.js.map +1 -1
  69. package/dist/extensions/router/store.d.ts.map +1 -1
  70. package/dist/extensions/router/store.js +108 -51
  71. package/dist/extensions/router/store.js.map +1 -1
  72. package/dist/extensions/router/ui.d.ts +6 -0
  73. package/dist/extensions/router/ui.d.ts.map +1 -1
  74. package/dist/extensions/router/ui.js +14 -6
  75. package/dist/extensions/router/ui.js.map +1 -1
  76. package/dist/extensions/statusline/index.d.ts.map +1 -1
  77. package/dist/extensions/statusline/index.js +40 -43
  78. package/dist/extensions/statusline/index.js.map +1 -1
  79. package/dist/extensions/subagent/activity.d.ts +7 -1
  80. package/dist/extensions/subagent/activity.d.ts.map +1 -1
  81. package/dist/extensions/subagent/activity.js +69 -6
  82. package/dist/extensions/subagent/activity.js.map +1 -1
  83. package/dist/extensions/subagent/agents-command.js +1 -1
  84. package/dist/extensions/subagent/agents-command.js.map +1 -1
  85. package/dist/extensions/subagent/agents.d.ts.map +1 -1
  86. package/dist/extensions/subagent/agents.js +0 -3
  87. package/dist/extensions/subagent/agents.js.map +1 -1
  88. package/dist/extensions/subagent/budget.d.ts.map +1 -1
  89. package/dist/extensions/subagent/budget.js +29 -2
  90. package/dist/extensions/subagent/budget.js.map +1 -1
  91. package/dist/extensions/subagent/cancellation.d.ts +4 -4
  92. package/dist/extensions/subagent/cancellation.d.ts.map +1 -1
  93. package/dist/extensions/subagent/cancellation.js +31 -13
  94. package/dist/extensions/subagent/cancellation.js.map +1 -1
  95. package/dist/extensions/subagent/constants.d.ts +1 -0
  96. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  97. package/dist/extensions/subagent/constants.js +1 -0
  98. package/dist/extensions/subagent/constants.js.map +1 -1
  99. package/dist/extensions/subagent/index.d.ts.map +1 -1
  100. package/dist/extensions/subagent/index.js +1 -0
  101. package/dist/extensions/subagent/index.js.map +1 -1
  102. package/dist/extensions/subagent/render.d.ts +8 -1
  103. package/dist/extensions/subagent/render.d.ts.map +1 -1
  104. package/dist/extensions/subagent/render.js +155 -178
  105. package/dist/extensions/subagent/render.js.map +1 -1
  106. package/dist/extensions/subagent/runner.d.ts +1 -0
  107. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  108. package/dist/extensions/subagent/runner.js +53 -44
  109. package/dist/extensions/subagent/runner.js.map +1 -1
  110. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  111. package/dist/extensions/subagent/sdk-runner.js +0 -3
  112. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  113. package/dist/extensions/subagent/settings.d.ts +1 -1
  114. package/dist/extensions/subagent/settings.d.ts.map +1 -1
  115. package/dist/extensions/subagent/settings.js +74 -25
  116. package/dist/extensions/subagent/settings.js.map +1 -1
  117. package/dist/extensions/subagent/state.d.ts +1 -0
  118. package/dist/extensions/subagent/state.d.ts.map +1 -1
  119. package/dist/extensions/subagent/state.js +46 -16
  120. package/dist/extensions/subagent/state.js.map +1 -1
  121. package/dist/extensions/subagent/types.d.ts +0 -7
  122. package/dist/extensions/subagent/types.d.ts.map +1 -1
  123. package/dist/extensions/subagent/types.js.map +1 -1
  124. package/dist/extensions/subagent/ui/agents-dialog.js +1 -1
  125. package/dist/extensions/subagent/ui/agents-dialog.js.map +1 -1
  126. package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -1
  127. package/dist/extensions/subagent/ui/model-list.js +3 -4
  128. package/dist/extensions/subagent/ui/model-list.js.map +1 -1
  129. package/dist/extensions/subagent/ui/model-picker.d.ts.map +1 -1
  130. package/dist/extensions/subagent/ui/model-picker.js +2 -1
  131. package/dist/extensions/subagent/ui/model-picker.js.map +1 -1
  132. package/dist/extensions/web-search/format.d.ts.map +1 -1
  133. package/dist/extensions/web-search/format.js +36 -15
  134. package/dist/extensions/web-search/format.js.map +1 -1
  135. package/dist/extensions/web-search/index.d.ts.map +1 -1
  136. package/dist/extensions/web-search/index.js +11 -8
  137. package/dist/extensions/web-search/index.js.map +1 -1
  138. package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -1
  139. package/dist/extensions/web-search/providers/deepseek.js +36 -5
  140. package/dist/extensions/web-search/providers/deepseek.js.map +1 -1
  141. package/dist/extensions/web-search/providers/http.d.ts +2 -7
  142. package/dist/extensions/web-search/providers/http.d.ts.map +1 -1
  143. package/dist/extensions/web-search/providers/http.js +12 -57
  144. package/dist/extensions/web-search/providers/http.js.map +1 -1
  145. package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -1
  146. package/dist/extensions/web-search/providers/minimax.js +18 -6
  147. package/dist/extensions/web-search/providers/minimax.js.map +1 -1
  148. package/dist/extensions/web-search/render.d.ts.map +1 -1
  149. package/dist/extensions/web-search/render.js +16 -5
  150. package/dist/extensions/web-search/render.js.map +1 -1
  151. package/dist/extensions/web-search/results.d.ts +10 -3
  152. package/dist/extensions/web-search/results.d.ts.map +1 -1
  153. package/dist/extensions/web-search/results.js +25 -7
  154. package/dist/extensions/web-search/results.js.map +1 -1
  155. package/dist/image-resize-worker.js +2 -0
  156. package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  157. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  158. package/dist/modes/interactive/components/tool-execution.js +7 -0
  159. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  160. package/dist/modes/interactive/components/tool-group.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/tool-group.js +10 -0
  162. package/dist/modes/interactive/components/tool-group.js.map +1 -1
  163. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  164. package/dist/modes/interactive/interactive-mode.js +3 -40
  165. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  166. package/dist/modes/interactive/theme/theme.d.ts +0 -4
  167. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  168. package/dist/modes/interactive/theme/theme.js +16 -23
  169. package/dist/modes/interactive/theme/theme.js.map +1 -1
  170. package/dist/rpc-entry.js +2435 -10
  171. package/dist/utils/http-response.d.ts +10 -0
  172. package/dist/utils/http-response.d.ts.map +1 -0
  173. package/dist/utils/http-response.js +68 -0
  174. package/dist/utils/http-response.js.map +1 -0
  175. package/docs/bundled/extensions/background.md +4 -3
  176. package/docs/bundled/extensions/deepwiki.md +7 -5
  177. package/docs/bundled/extensions/question.md +6 -4
  178. package/docs/bundled/extensions/router.md +1 -1
  179. package/docs/bundled/extensions/statusline.md +1 -0
  180. package/docs/bundled/extensions/subagent.md +29 -39
  181. package/docs/bundled/extensions/web-search.md +3 -3
  182. package/docs/development.md +8 -3
  183. package/docs/extensions.md +2 -2
  184. package/npm-shrinkwrap.json +110 -109
  185. package/package.json +4 -2
@@ -1 +1 @@
1
- {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/extensions/router/probe.ts"],"names":[],"mappings":"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","sourcesContent":["/** Fetch OpenAI-compatible model catalog from a relay baseUrl. */\n\nimport { DEFAULTS, formatError } from \"./constants.ts\";\n\nexport interface ProbeModel {\n\tid: string;\n\tname?: string;\n}\n\nexport type ProbeResult = { ok: true; models: ProbeModel[]; truncated: boolean } | { ok: false; error: string };\n\nexport async function probeRelayModels(opts: {\n\tbaseUrl: string;\n\tapiKey?: string;\n\tsignal?: AbortSignal;\n\ttimeoutMs?: number;\n}): Promise<ProbeResult> {\n\tlet baseUrl: URL;\n\ttry {\n\t\tbaseUrl = new URL(opts.baseUrl);\n\t} catch {\n\t\treturn { ok: false, error: \"Base URL is not a valid URL.\" };\n\t}\n\tif (baseUrl.protocol !== \"http:\" && baseUrl.protocol !== \"https:\") {\n\t\treturn { ok: false, error: `Unsupported protocol: ${baseUrl.protocol}` };\n\t}\n\n\tif (opts.signal?.aborted) return { ok: false, error: \"Cancelled.\" };\n\tconst controller = new AbortController();\n\tconst timeoutMs = opts.timeoutMs ?? DEFAULTS.probeTimeoutMs;\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\tconst onOuterAbort = () => controller.abort();\n\topts.signal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\n\ttry {\n\t\tconst url = appendPath(baseUrl, \"models\");\n\t\tconst headers: Record<string, string> = { Accept: \"application/json\" };\n\t\tif (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;\n\n\t\tconst response = await fetch(url, { headers, signal: controller.signal });\n\t\tif (!response.ok) {\n\t\t\tconst body = await readTextBounded(response, 4_096);\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `HTTP ${response.status}${body ? `: ${body.slice(0, 400)}` : \"\"}`,\n\t\t\t};\n\t\t}\n\t\tconst text = await readTextBounded(response, DEFAULTS.probeBodyBytes);\n\t\tlet json: unknown;\n\t\ttry {\n\t\t\tjson = JSON.parse(text);\n\t\t} catch {\n\t\t\treturn { ok: false, error: \"Model catalog response is not JSON.\" };\n\t\t}\n\t\tconst models = parseOpenAIModels(json);\n\t\tif (models === null) {\n\t\t\treturn { ok: false, error: \"JSON has no OpenAI-style `data` array of model ids.\" };\n\t\t}\n\t\tconst sorted = dedupeSort(models);\n\t\tconst truncated = sorted.length > DEFAULTS.probeMaxModels;\n\t\treturn {\n\t\t\tok: true,\n\t\t\tmodels: sorted.slice(0, DEFAULTS.probeMaxModels),\n\t\t\ttruncated,\n\t\t};\n\t} catch (error) {\n\t\tif (controller.signal.aborted) {\n\t\t\treturn { ok: false, error: opts.signal?.aborted ? \"Cancelled.\" : `Timed out after ${timeoutMs}ms.` };\n\t\t}\n\t\treturn { ok: false, error: formatError(error) };\n\t} finally {\n\t\tclearTimeout(timer);\n\t\topts.signal?.removeEventListener(\"abort\", onOuterAbort);\n\t}\n}\n\nfunction appendPath(base: URL, segment: string): URL {\n\tconst url = new URL(base.href);\n\tconst path = url.pathname.replace(/\\/+$/, \"\");\n\turl.pathname = `${path}/${segment}`;\n\treturn url;\n}\n\nfunction parseOpenAIModels(json: unknown): ProbeModel[] | null {\n\tif (!json || typeof json !== \"object\") return null;\n\tconst data = (json as { data?: unknown }).data;\n\tif (!Array.isArray(data)) return null;\n\tconst models: ProbeModel[] = [];\n\tfor (const item of data) {\n\t\tif (!item || typeof item !== \"object\") continue;\n\t\tconst record = item as Record<string, unknown>;\n\t\tconst id = typeof record.id === \"string\" ? record.id.trim() : \"\";\n\t\tif (!id) continue;\n\t\tconst name =\n\t\t\ttypeof record.name === \"string\" && record.name.trim() && record.name.trim() !== id\n\t\t\t\t? record.name.trim()\n\t\t\t\t: undefined;\n\t\tmodels.push(name ? { id, name } : { id });\n\t}\n\treturn models;\n}\n\nfunction dedupeSort(models: ProbeModel[]): ProbeModel[] {\n\tconst map = new Map<string, ProbeModel>();\n\tfor (const model of models) {\n\t\tif (!map.has(model.id)) map.set(model.id, model);\n\t}\n\treturn [...map.values()].sort((a, b) => a.id.localeCompare(b.id));\n}\n\nasync function readTextBounded(response: Response, maxBytes: number): Promise<string> {\n\tconst reader = response.body?.getReader();\n\tif (!reader) return await response.text();\n\tconst chunks: Uint8Array[] = [];\n\tlet total = 0;\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\tif (!value) continue;\n\t\ttotal += value.byteLength;\n\t\tif (total > maxBytes) {\n\t\t\tawait reader.cancel();\n\t\t\tthrow new Error(`Response exceeds ${maxBytes} bytes.`);\n\t\t}\n\t\tchunks.push(value);\n\t}\n\tconst merged = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tmerged.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\treturn new TextDecoder().decode(merged);\n}\n"]}
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/extensions/router/probe.ts"],"names":[],"mappings":"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","sourcesContent":["/** Fetch OpenAI-compatible model catalog from a relay baseUrl. */\n\nimport { readResponseTextBounded } from \"../../utils/http-response.ts\";\nimport { DEFAULTS, formatError } from \"./constants.ts\";\n\nexport interface ProbeModel {\n\tid: string;\n\tname?: string;\n}\n\nexport type ProbeResult = { ok: true; models: ProbeModel[]; truncated: boolean } | { ok: false; error: string };\n\nexport async function probeRelayModels(opts: {\n\tbaseUrl: string;\n\tapiKey?: string;\n\tsignal?: AbortSignal;\n\ttimeoutMs?: number;\n}): Promise<ProbeResult> {\n\tlet baseUrl: URL;\n\ttry {\n\t\tbaseUrl = new URL(opts.baseUrl);\n\t} catch {\n\t\treturn { ok: false, error: \"Base URL is not a valid URL.\" };\n\t}\n\tif (baseUrl.protocol !== \"http:\" && baseUrl.protocol !== \"https:\") {\n\t\treturn { ok: false, error: `Unsupported protocol: ${baseUrl.protocol}` };\n\t}\n\n\tif (opts.signal?.aborted) return { ok: false, error: \"Cancelled.\" };\n\tconst controller = new AbortController();\n\tconst timeoutMs = opts.timeoutMs ?? DEFAULTS.probeTimeoutMs;\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\tconst onOuterAbort = () => controller.abort();\n\topts.signal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\n\ttry {\n\t\tconst url = appendPath(baseUrl, \"models\");\n\t\tconst headers: Record<string, string> = { Accept: \"application/json\" };\n\t\tif (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;\n\n\t\tconst response = await fetch(url, { headers, signal: controller.signal });\n\t\tif (!response.ok) {\n\t\t\tconst body = await readResponseTextBounded(response, {\n\t\t\t\tmaxBytes: 4_096,\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `HTTP ${response.status}${body ? `: ${body.slice(0, 400)}` : \"\"}`,\n\t\t\t};\n\t\t}\n\t\tconst text = await readResponseTextBounded(response, {\n\t\t\tmaxBytes: DEFAULTS.probeBodyBytes,\n\t\t\toverflowMessage: `Response exceeds ${DEFAULTS.probeBodyBytes} bytes.`,\n\t\t\tsignal: controller.signal,\n\t\t});\n\t\tlet json: unknown;\n\t\ttry {\n\t\t\tjson = JSON.parse(text);\n\t\t} catch {\n\t\t\treturn { ok: false, error: \"Model catalog response is not JSON.\" };\n\t\t}\n\t\tconst models = parseOpenAIModels(json);\n\t\tif (models === null) {\n\t\t\treturn { ok: false, error: \"JSON has no OpenAI-style `data` array of model ids.\" };\n\t\t}\n\t\tconst sorted = dedupeSort(models);\n\t\tconst truncated = sorted.length > DEFAULTS.probeMaxModels;\n\t\treturn {\n\t\t\tok: true,\n\t\t\tmodels: sorted.slice(0, DEFAULTS.probeMaxModels),\n\t\t\ttruncated,\n\t\t};\n\t} catch (error) {\n\t\tif (controller.signal.aborted) {\n\t\t\treturn { ok: false, error: opts.signal?.aborted ? \"Cancelled.\" : `Timed out after ${timeoutMs}ms.` };\n\t\t}\n\t\treturn { ok: false, error: formatError(error) };\n\t} finally {\n\t\tclearTimeout(timer);\n\t\topts.signal?.removeEventListener(\"abort\", onOuterAbort);\n\t}\n}\n\nfunction appendPath(base: URL, segment: string): URL {\n\tconst url = new URL(base.href);\n\tconst path = url.pathname.replace(/\\/+$/, \"\");\n\turl.pathname = `${path}/${segment}`;\n\treturn url;\n}\n\nfunction parseOpenAIModels(json: unknown): ProbeModel[] | null {\n\tif (!json || typeof json !== \"object\") return null;\n\tconst data = (json as { data?: unknown }).data;\n\tif (!Array.isArray(data)) return null;\n\tconst models: ProbeModel[] = [];\n\tfor (const item of data) {\n\t\tif (!item || typeof item !== \"object\") continue;\n\t\tconst record = item as Record<string, unknown>;\n\t\tconst id = typeof record.id === \"string\" ? record.id.trim() : \"\";\n\t\tif (!id) continue;\n\t\tconst name =\n\t\t\ttypeof record.name === \"string\" && record.name.trim() && record.name.trim() !== id\n\t\t\t\t? record.name.trim()\n\t\t\t\t: undefined;\n\t\tmodels.push(name ? { id, name } : { id });\n\t}\n\treturn models;\n}\n\nfunction dedupeSort(models: ProbeModel[]): ProbeModel[] {\n\tconst map = new Map<string, ProbeModel>();\n\tfor (const model of models) {\n\t\tif (!map.has(model.id)) map.set(model.id, model);\n\t}\n\treturn [...map.values()].sort((a, b) => a.id.localeCompare(b.id));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/extensions/router/register.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK1D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;EAYlD;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CA8BxE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAc3E;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAOrE"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/extensions/router/register.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAc1D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;EAYlD;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CA+BxE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAe3E;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAOrE"}
@@ -4,8 +4,16 @@
4
4
  import { ROUTER_API } from "./constants.js";
5
5
  import { toRegisterModel } from "./presets.js";
6
6
  import { streamRouterCodex } from "./stream.js";
7
- /** Provider ids currently registered by this extension in the process. */
8
- const registeredIds = new Set();
7
+ /** Provider ids registered by this extension, isolated to each SDK host. */
8
+ const registeredIdsByApi = new WeakMap();
9
+ function registeredIdsFor(pi) {
10
+ let ids = registeredIdsByApi.get(pi);
11
+ if (!ids) {
12
+ ids = new Set();
13
+ registeredIdsByApi.set(pi, ids);
14
+ }
15
+ return ids;
16
+ }
9
17
  export function toProviderConfig(relay) {
10
18
  return {
11
19
  name: relay.id,
@@ -20,6 +28,7 @@ export function toProviderConfig(relay) {
20
28
  };
21
29
  }
22
30
  export function applyRouterFile(pi, file) {
31
+ const registeredIds = registeredIdsFor(pi);
23
32
  const nextIds = new Set(file.relays.map((relay) => relay.id));
24
33
  for (const id of [...registeredIds]) {
25
34
  if (!nextIds.has(id)) {
@@ -40,7 +49,7 @@ export function applyRouterFile(pi, file) {
40
49
  pi.unregisterProvider(relay.id);
41
50
  }
42
51
  catch {
43
- // ignore
52
+ // Ignore stale registrations after /reload.
44
53
  }
45
54
  registeredIds.delete(relay.id);
46
55
  }
@@ -51,13 +60,14 @@ export function applyRouterFile(pi, file) {
51
60
  }
52
61
  }
53
62
  export function registerOneRelay(pi, relay) {
63
+ const registeredIds = registeredIdsFor(pi);
54
64
  if (relay.models.length === 0) {
55
65
  if (registeredIds.has(relay.id)) {
56
66
  try {
57
67
  pi.unregisterProvider(relay.id);
58
68
  }
59
69
  catch {
60
- // ignore
70
+ // Ignore stale registrations after /reload.
61
71
  }
62
72
  registeredIds.delete(relay.id);
63
73
  }
@@ -71,8 +81,8 @@ export function unregisterOneRelay(pi, id) {
71
81
  pi.unregisterProvider(id);
72
82
  }
73
83
  catch {
74
- // ignore
84
+ // Ignore stale registrations after /reload.
75
85
  }
76
- registeredIds.delete(id);
86
+ registeredIdsFor(pi).delete(id);
77
87
  }
78
88
  //# sourceMappingURL=register.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/extensions/router/register.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Apply router.json relays to Pi via registerProvider (config form + streamSimple).\n */\n\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { ROUTER_API } from \"./constants.ts\";\nimport { toRegisterModel } from \"./presets.ts\";\nimport { streamRouterCodex } from \"./stream.ts\";\nimport type { RelayConfig, RouterFile } from \"./types.ts\";\n\n/** Provider ids currently registered by this extension in the process. */\nconst registeredIds = new Set<string>();\n\nexport function toProviderConfig(relay: RelayConfig) {\n\treturn {\n\t\tname: relay.id,\n\t\tbaseUrl: relay.baseUrl.replace(/\\/+$/, \"\"),\n\t\tapiKey: relay.apiKey,\n\t\tapi: ROUTER_API,\n\t\tmodels: relay.models.map((model) => ({\n\t\t\t...toRegisterModel(model),\n\t\t\tapi: ROUTER_API,\n\t\t})),\n\t\tstreamSimple: streamRouterCodex,\n\t};\n}\n\nexport function applyRouterFile(pi: ExtensionAPI, file: RouterFile): void {\n\tconst nextIds = new Set(file.relays.map((relay) => relay.id));\n\n\tfor (const id of [...registeredIds]) {\n\t\tif (!nextIds.has(id)) {\n\t\t\ttry {\n\t\t\t\tpi.unregisterProvider(id);\n\t\t\t} catch {\n\t\t\t\t// Provider may already be gone after /reload.\n\t\t\t}\n\t\t\tregisteredIds.delete(id);\n\t\t}\n\t}\n\n\tfor (const relay of file.relays) {\n\t\t// Only register relays that have at least one model; empty catalog is not selectable.\n\t\tif (relay.models.length === 0) {\n\t\t\tif (registeredIds.has(relay.id)) {\n\t\t\t\ttry {\n\t\t\t\t\tpi.unregisterProvider(relay.id);\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore\n\t\t\t\t}\n\t\t\t\tregisteredIds.delete(relay.id);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tpi.registerProvider(relay.id, toProviderConfig(relay));\n\t\tregisteredIds.add(relay.id);\n\t}\n}\n\nexport function registerOneRelay(pi: ExtensionAPI, relay: RelayConfig): void {\n\tif (relay.models.length === 0) {\n\t\tif (registeredIds.has(relay.id)) {\n\t\t\ttry {\n\t\t\t\tpi.unregisterProvider(relay.id);\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t\tregisteredIds.delete(relay.id);\n\t\t}\n\t\treturn;\n\t}\n\tpi.registerProvider(relay.id, toProviderConfig(relay));\n\tregisteredIds.add(relay.id);\n}\n\nexport function unregisterOneRelay(pi: ExtensionAPI, id: string): void {\n\ttry {\n\t\tpi.unregisterProvider(id);\n\t} catch {\n\t\t// ignore\n\t}\n\tregisteredIds.delete(id);\n}\n"]}
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/extensions/router/register.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Apply router.json relays to Pi via registerProvider (config form + streamSimple).\n */\n\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { ROUTER_API } from \"./constants.ts\";\nimport { toRegisterModel } from \"./presets.ts\";\nimport { streamRouterCodex } from \"./stream.ts\";\nimport type { RelayConfig, RouterFile } from \"./types.ts\";\n\n/** Provider ids registered by this extension, isolated to each SDK host. */\nconst registeredIdsByApi = new WeakMap<ExtensionAPI, Set<string>>();\n\nfunction registeredIdsFor(pi: ExtensionAPI): Set<string> {\n\tlet ids = registeredIdsByApi.get(pi);\n\tif (!ids) {\n\t\tids = new Set<string>();\n\t\tregisteredIdsByApi.set(pi, ids);\n\t}\n\treturn ids;\n}\n\nexport function toProviderConfig(relay: RelayConfig) {\n\treturn {\n\t\tname: relay.id,\n\t\tbaseUrl: relay.baseUrl.replace(/\\/+$/, \"\"),\n\t\tapiKey: relay.apiKey,\n\t\tapi: ROUTER_API,\n\t\tmodels: relay.models.map((model) => ({\n\t\t\t...toRegisterModel(model),\n\t\t\tapi: ROUTER_API,\n\t\t})),\n\t\tstreamSimple: streamRouterCodex,\n\t};\n}\n\nexport function applyRouterFile(pi: ExtensionAPI, file: RouterFile): void {\n\tconst registeredIds = registeredIdsFor(pi);\n\tconst nextIds = new Set(file.relays.map((relay) => relay.id));\n\n\tfor (const id of [...registeredIds]) {\n\t\tif (!nextIds.has(id)) {\n\t\t\ttry {\n\t\t\t\tpi.unregisterProvider(id);\n\t\t\t} catch {\n\t\t\t\t// Provider may already be gone after /reload.\n\t\t\t}\n\t\t\tregisteredIds.delete(id);\n\t\t}\n\t}\n\n\tfor (const relay of file.relays) {\n\t\t// Only register relays that have at least one model; empty catalog is not selectable.\n\t\tif (relay.models.length === 0) {\n\t\t\tif (registeredIds.has(relay.id)) {\n\t\t\t\ttry {\n\t\t\t\t\tpi.unregisterProvider(relay.id);\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore stale registrations after /reload.\n\t\t\t\t}\n\t\t\t\tregisteredIds.delete(relay.id);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tpi.registerProvider(relay.id, toProviderConfig(relay));\n\t\tregisteredIds.add(relay.id);\n\t}\n}\n\nexport function registerOneRelay(pi: ExtensionAPI, relay: RelayConfig): void {\n\tconst registeredIds = registeredIdsFor(pi);\n\tif (relay.models.length === 0) {\n\t\tif (registeredIds.has(relay.id)) {\n\t\t\ttry {\n\t\t\t\tpi.unregisterProvider(relay.id);\n\t\t\t} catch {\n\t\t\t\t// Ignore stale registrations after /reload.\n\t\t\t}\n\t\t\tregisteredIds.delete(relay.id);\n\t\t}\n\t\treturn;\n\t}\n\tpi.registerProvider(relay.id, toProviderConfig(relay));\n\tregisteredIds.add(relay.id);\n}\n\nexport function unregisterOneRelay(pi: ExtensionAPI, id: string): void {\n\ttry {\n\t\tpi.unregisterProvider(id);\n\t} catch {\n\t\t// Ignore stale registrations after /reload.\n\t}\n\tregisteredIdsFor(pi).delete(id);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/extensions/router/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAoB,UAAU,EAAoB,MAAM,YAAY,CAAC;AAE9F,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC,CAU1D;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAqBvD;AA+CD,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BpE;AAeD,wBAAsB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAOzE;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAKjE"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/extensions/router/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAoB,UAAU,EAAoB,MAAM,YAAY,CAAC;AAM9F,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC,CAU1D;AA4BD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA0BvD;AA4ED,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BpE;AAeD,wBAAsB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAOzE;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAKjE"}
@@ -5,7 +5,10 @@ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
5
5
  import { dirname, join } from "node:path";
6
6
  import { getAgentDir } from "../../config.js";
7
7
  import { withFileMutationQueue } from "../../core/tools/file-mutation-queue.js";
8
- import { CONFIG_VERSION, isValidRelayId } from "./constants.js";
8
+ import { CONFIG_VERSION, isValidRelayId, THINKING_LEVELS } from "./constants.js";
9
+ const ROOT_FIELDS = ["version", "relays"];
10
+ const RELAY_FIELDS = ["id", "baseUrl", "apiKey", "models"];
11
+ const MODEL_FIELDS = ["id", "name", "reasoning", "input", "contextWindow", "maxTokens", "thinkingLevelMap"];
9
12
  export function getRouterConfigPath() {
10
13
  return join(getAgentDir(), "router.json");
11
14
  }
@@ -25,6 +28,31 @@ export async function loadRouterFile() {
25
28
  }
26
29
  return parseRouterFile(raw);
27
30
  }
31
+ function expectObject(value, path) {
32
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
33
+ throw new Error(`${path} must be an object.`);
34
+ }
35
+ return value;
36
+ }
37
+ function rejectUnknownFields(record, allowed, path) {
38
+ const unknown = Object.keys(record).filter((key) => !allowed.includes(key));
39
+ if (unknown.length > 0)
40
+ throw new Error(`${path} has unsupported field(s): ${unknown.join(", ")}.`);
41
+ }
42
+ function expectString(value, path, allowEmpty = false) {
43
+ if (typeof value !== "string")
44
+ throw new Error(`${path} must be a string.`);
45
+ const trimmed = value.trim();
46
+ if (!allowEmpty && !trimmed)
47
+ throw new Error(`${path} must not be empty.`);
48
+ return allowEmpty ? value : trimmed;
49
+ }
50
+ function expectPositiveInteger(value, path) {
51
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
52
+ throw new Error(`${path} must be a positive integer.`);
53
+ }
54
+ return value;
55
+ }
28
56
  export function parseRouterFile(raw) {
29
57
  let parsed;
30
58
  try {
@@ -33,71 +61,100 @@ export function parseRouterFile(raw) {
33
61
  catch {
34
62
  throw new Error("router.json is not valid JSON.");
35
63
  }
36
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
37
- throw new Error("router.json must be a JSON object.");
64
+ const root = expectObject(parsed, "router.json");
65
+ rejectUnknownFields(root, ROOT_FIELDS, "router.json");
66
+ const version = root.version ?? CONFIG_VERSION;
67
+ if (version !== CONFIG_VERSION) {
68
+ throw new Error(`router.json has unsupported version ${String(version)}.`);
38
69
  }
39
- const root = parsed;
40
- const relaysRaw = root.relays;
41
- if (relaysRaw !== undefined && !Array.isArray(relaysRaw)) {
70
+ if (root.relays !== undefined && !Array.isArray(root.relays)) {
42
71
  throw new Error("router.json relays must be an array.");
43
72
  }
44
- const relays = [];
45
- for (const item of relaysRaw ?? []) {
46
- const relay = normalizeRelay(item);
47
- if (relay)
48
- relays.push(relay);
73
+ const relays = (root.relays ?? []).map((value, index) => parseRelay(value, `router.json relays[${index}]`));
74
+ const relayIds = new Set();
75
+ for (const relay of relays) {
76
+ if (relayIds.has(relay.id))
77
+ throw new Error(`router.json contains duplicate relay id "${relay.id}".`);
78
+ relayIds.add(relay.id);
49
79
  }
50
80
  return { version: CONFIG_VERSION, relays };
51
81
  }
52
- function normalizeRelay(value) {
53
- if (!value || typeof value !== "object" || Array.isArray(value))
54
- return undefined;
55
- const record = value;
56
- const id = typeof record.id === "string" ? record.id.trim() : "";
57
- const baseUrl = typeof record.baseUrl === "string" ? record.baseUrl.trim() : "";
58
- const apiKey = typeof record.apiKey === "string" ? record.apiKey : "";
59
- if (!id || !isValidRelayId(id) || !baseUrl)
60
- return undefined;
61
- const models = [];
62
- if (Array.isArray(record.models)) {
63
- for (const model of record.models) {
64
- const normalized = normalizeModel(model);
65
- if (normalized)
66
- models.push(normalized);
67
- }
82
+ function parseRelay(value, path) {
83
+ const record = expectObject(value, path);
84
+ rejectUnknownFields(record, RELAY_FIELDS, path);
85
+ const id = expectString(record.id, `${path}.id`);
86
+ if (!isValidRelayId(id))
87
+ throw new Error(`${path}.id must be a valid provider id without '/'.`);
88
+ const baseUrl = expectString(record.baseUrl, `${path}.baseUrl`);
89
+ try {
90
+ const url = new URL(baseUrl);
91
+ if (url.protocol !== "http:" && url.protocol !== "https:")
92
+ throw new Error("unsupported protocol");
93
+ }
94
+ catch {
95
+ throw new Error(`${path}.baseUrl must be an http or https URL.`);
96
+ }
97
+ const apiKey = expectString(record.apiKey, `${path}.apiKey`, true);
98
+ if (record.models !== undefined && !Array.isArray(record.models)) {
99
+ throw new Error(`${path}.models must be an array.`);
100
+ }
101
+ const models = (record.models ?? []).map((model, index) => parseModel(model, `${path}.models[${index}]`));
102
+ const modelIds = new Set();
103
+ for (const model of models) {
104
+ if (modelIds.has(model.id))
105
+ throw new Error(`${path}.models contains duplicate id "${model.id}".`);
106
+ modelIds.add(model.id);
68
107
  }
69
108
  return { id, baseUrl, apiKey, models };
70
109
  }
71
- function normalizeModel(value) {
72
- if (!value || typeof value !== "object" || Array.isArray(value))
73
- return undefined;
74
- const record = value;
75
- const id = typeof record.id === "string" ? record.id.trim() : "";
76
- if (!id)
77
- return undefined;
78
- const model = { id };
79
- if (typeof record.name === "string" && record.name.trim())
80
- model.name = record.name.trim();
81
- if (typeof record.reasoning === "boolean")
110
+ function parseModel(value, path) {
111
+ const record = expectObject(value, path);
112
+ rejectUnknownFields(record, MODEL_FIELDS, path);
113
+ const model = { id: expectString(record.id, `${path}.id`) };
114
+ if (record.name !== undefined) {
115
+ const name = expectString(record.name, `${path}.name`, true).trim();
116
+ if (name)
117
+ model.name = name;
118
+ }
119
+ if (record.reasoning !== undefined) {
120
+ if (typeof record.reasoning !== "boolean")
121
+ throw new Error(`${path}.reasoning must be a boolean.`);
82
122
  model.reasoning = record.reasoning;
83
- if (Array.isArray(record.input)) {
84
- const input = record.input.filter((item) => item === "text" || item === "image");
85
- if (input.includes("text"))
86
- model.input = input.includes("image") ? ["text", "image"] : ["text"];
87
123
  }
88
- if (typeof record.contextWindow === "number" && Number.isFinite(record.contextWindow) && record.contextWindow > 0) {
89
- model.contextWindow = Math.floor(record.contextWindow);
124
+ if (record.input !== undefined) {
125
+ if (!Array.isArray(record.input) || record.input.some((item) => item !== "text" && item !== "image")) {
126
+ throw new Error(`${path}.input must contain only "text" and "image".`);
127
+ }
128
+ const input = new Set(record.input);
129
+ if (!input.has("text"))
130
+ throw new Error(`${path}.input must include "text".`);
131
+ model.input = input.has("image") ? ["text", "image"] : ["text"];
132
+ }
133
+ if (record.contextWindow !== undefined) {
134
+ model.contextWindow = expectPositiveInteger(record.contextWindow, `${path}.contextWindow`);
90
135
  }
91
- if (typeof record.maxTokens === "number" && Number.isFinite(record.maxTokens) && record.maxTokens > 0) {
92
- model.maxTokens = Math.floor(record.maxTokens);
136
+ if (record.maxTokens !== undefined) {
137
+ model.maxTokens = expectPositiveInteger(record.maxTokens, `${path}.maxTokens`);
93
138
  }
94
- if (record.thinkingLevelMap &&
95
- typeof record.thinkingLevelMap === "object" &&
96
- !Array.isArray(record.thinkingLevelMap)) {
97
- model.thinkingLevelMap = record.thinkingLevelMap;
139
+ if (record.thinkingLevelMap !== undefined) {
140
+ model.thinkingLevelMap = parseThinkingLevelMap(record.thinkingLevelMap, `${path}.thinkingLevelMap`);
98
141
  }
99
142
  return model;
100
143
  }
144
+ function parseThinkingLevelMap(value, path) {
145
+ const record = expectObject(value, path);
146
+ const map = {};
147
+ for (const [level, target] of Object.entries(record)) {
148
+ if (!THINKING_LEVELS.includes(level)) {
149
+ throw new Error(`${path} has unsupported thinking level "${level}".`);
150
+ }
151
+ if (target !== null && (typeof target !== "string" || target.trim().length === 0)) {
152
+ throw new Error(`${path}.${level} must be a non-empty string or null.`);
153
+ }
154
+ map[level] = target === null ? null : target.trim();
155
+ }
156
+ return map;
157
+ }
101
158
  export async function saveRouterFile(file) {
102
159
  const filePath = getRouterConfigPath();
103
160
  const payload = {
@@ -122,7 +179,7 @@ export async function saveRouterFile(file) {
122
179
  await unlink(tempPath);
123
180
  }
124
181
  catch {
125
- // ignore
182
+ // Ignore cleanup errors and preserve the original failure.
126
183
  }
127
184
  throw error;
128
185
  }
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/extensions/router/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for ~/.pi/agent/router.json\n */\n\nimport { mkdir, readFile, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { withFileMutationQueue } from \"../../core/tools/file-mutation-queue.ts\";\nimport { CONFIG_VERSION, isValidRelayId } from \"./constants.ts\";\nimport type { RelayConfig, RelayModelConfig, RouterFile, ThinkingLevelMap } from \"./types.ts\";\n\nexport function getRouterConfigPath(): string {\n\treturn join(getAgentDir(), \"router.json\");\n}\n\nexport function emptyRouterFile(): RouterFile {\n\treturn { version: CONFIG_VERSION, relays: [] };\n}\n\nexport async function loadRouterFile(): Promise<RouterFile> {\n\tconst filePath = getRouterConfigPath();\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(filePath, \"utf8\");\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return emptyRouterFile();\n\t\tthrow error;\n\t}\n\treturn parseRouterFile(raw);\n}\n\nexport function parseRouterFile(raw: string): RouterFile {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\tthrow new Error(\"router.json is not valid JSON.\");\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(\"router.json must be a JSON object.\");\n\t}\n\tconst root = parsed as Record<string, unknown>;\n\tconst relaysRaw = root.relays;\n\tif (relaysRaw !== undefined && !Array.isArray(relaysRaw)) {\n\t\tthrow new Error(\"router.json relays must be an array.\");\n\t}\n\tconst relays: RelayConfig[] = [];\n\tfor (const item of relaysRaw ?? []) {\n\t\tconst relay = normalizeRelay(item);\n\t\tif (relay) relays.push(relay);\n\t}\n\treturn { version: CONFIG_VERSION, relays };\n}\n\nfunction normalizeRelay(value: unknown): RelayConfig | undefined {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n\tconst record = value as Record<string, unknown>;\n\tconst id = typeof record.id === \"string\" ? record.id.trim() : \"\";\n\tconst baseUrl = typeof record.baseUrl === \"string\" ? record.baseUrl.trim() : \"\";\n\tconst apiKey = typeof record.apiKey === \"string\" ? record.apiKey : \"\";\n\tif (!id || !isValidRelayId(id) || !baseUrl) return undefined;\n\tconst models: RelayModelConfig[] = [];\n\tif (Array.isArray(record.models)) {\n\t\tfor (const model of record.models) {\n\t\t\tconst normalized = normalizeModel(model);\n\t\t\tif (normalized) models.push(normalized);\n\t\t}\n\t}\n\treturn { id, baseUrl, apiKey, models };\n}\n\nfunction normalizeModel(value: unknown): RelayModelConfig | undefined {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n\tconst record = value as Record<string, unknown>;\n\tconst id = typeof record.id === \"string\" ? record.id.trim() : \"\";\n\tif (!id) return undefined;\n\tconst model: RelayModelConfig = { id };\n\tif (typeof record.name === \"string\" && record.name.trim()) model.name = record.name.trim();\n\tif (typeof record.reasoning === \"boolean\") model.reasoning = record.reasoning;\n\tif (Array.isArray(record.input)) {\n\t\tconst input = record.input.filter((item): item is \"text\" | \"image\" => item === \"text\" || item === \"image\");\n\t\tif (input.includes(\"text\")) model.input = input.includes(\"image\") ? [\"text\", \"image\"] : [\"text\"];\n\t}\n\tif (typeof record.contextWindow === \"number\" && Number.isFinite(record.contextWindow) && record.contextWindow > 0) {\n\t\tmodel.contextWindow = Math.floor(record.contextWindow);\n\t}\n\tif (typeof record.maxTokens === \"number\" && Number.isFinite(record.maxTokens) && record.maxTokens > 0) {\n\t\tmodel.maxTokens = Math.floor(record.maxTokens);\n\t}\n\tif (\n\t\trecord.thinkingLevelMap &&\n\t\ttypeof record.thinkingLevelMap === \"object\" &&\n\t\t!Array.isArray(record.thinkingLevelMap)\n\t) {\n\t\tmodel.thinkingLevelMap = record.thinkingLevelMap as ThinkingLevelMap;\n\t}\n\treturn model;\n}\n\nexport async function saveRouterFile(file: RouterFile): Promise<void> {\n\tconst filePath = getRouterConfigPath();\n\tconst payload: RouterFile = {\n\t\tversion: CONFIG_VERSION,\n\t\trelays: file.relays.map((relay) => ({\n\t\t\tid: relay.id,\n\t\t\tbaseUrl: relay.baseUrl,\n\t\t\tapiKey: relay.apiKey,\n\t\t\tmodels: relay.models.map((model) => serializeModel(model)),\n\t\t})),\n\t};\n\tconst serialized = `${JSON.stringify(payload, null, 2)}\\n`;\n\tawait mkdir(dirname(filePath), { recursive: true });\n\tawait withFileMutationQueue(filePath, async () => {\n\t\tconst tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;\n\t\ttry {\n\t\t\tawait writeFile(tempPath, serialized, { encoding: \"utf8\", mode: 0o600 });\n\t\t\tawait rename(tempPath, filePath);\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait unlink(tempPath);\n\t\t\t} catch {\n\t\t\t\t// ignore\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t});\n}\n\nfunction serializeModel(model: RelayModelConfig): RelayModelConfig {\n\tconst out: RelayModelConfig = { id: model.id };\n\tif (model.name) out.name = model.name;\n\tif (model.reasoning !== undefined) out.reasoning = model.reasoning;\n\tif (model.input) out.input = model.input;\n\tif (model.contextWindow !== undefined) out.contextWindow = model.contextWindow;\n\tif (model.maxTokens !== undefined) out.maxTokens = model.maxTokens;\n\tif (model.thinkingLevelMap && Object.keys(model.thinkingLevelMap).length > 0) {\n\t\tout.thinkingLevelMap = model.thinkingLevelMap;\n\t}\n\treturn out;\n}\n\nexport async function upsertRelay(relay: RelayConfig): Promise<RouterFile> {\n\tconst file = await loadRouterFile();\n\tconst index = file.relays.findIndex((entry) => entry.id === relay.id);\n\tif (index >= 0) file.relays[index] = relay;\n\telse file.relays.push(relay);\n\tawait saveRouterFile(file);\n\treturn file;\n}\n\nexport async function removeRelay(id: string): Promise<RouterFile> {\n\tconst file = await loadRouterFile();\n\tfile.relays = file.relays.filter((entry) => entry.id !== id);\n\tawait saveRouterFile(file);\n\treturn file;\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/extensions/router/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistence for ~/.pi/agent/router.json\n */\n\nimport { mkdir, readFile, rename, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { withFileMutationQueue } from \"../../core/tools/file-mutation-queue.ts\";\nimport { CONFIG_VERSION, isValidRelayId, THINKING_LEVELS } from \"./constants.ts\";\nimport type { RelayConfig, RelayModelConfig, RouterFile, ThinkingLevelMap } from \"./types.ts\";\n\nconst ROOT_FIELDS = [\"version\", \"relays\"] as const;\nconst RELAY_FIELDS = [\"id\", \"baseUrl\", \"apiKey\", \"models\"] as const;\nconst MODEL_FIELDS = [\"id\", \"name\", \"reasoning\", \"input\", \"contextWindow\", \"maxTokens\", \"thinkingLevelMap\"] as const;\n\nexport function getRouterConfigPath(): string {\n\treturn join(getAgentDir(), \"router.json\");\n}\n\nexport function emptyRouterFile(): RouterFile {\n\treturn { version: CONFIG_VERSION, relays: [] };\n}\n\nexport async function loadRouterFile(): Promise<RouterFile> {\n\tconst filePath = getRouterConfigPath();\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(filePath, \"utf8\");\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return emptyRouterFile();\n\t\tthrow error;\n\t}\n\treturn parseRouterFile(raw);\n}\n\nfunction expectObject(value: unknown, path: string): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(`${path} must be an object.`);\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction rejectUnknownFields(record: Record<string, unknown>, allowed: readonly string[], path: string): void {\n\tconst unknown = Object.keys(record).filter((key) => !allowed.includes(key));\n\tif (unknown.length > 0) throw new Error(`${path} has unsupported field(s): ${unknown.join(\", \")}.`);\n}\n\nfunction expectString(value: unknown, path: string, allowEmpty = false): string {\n\tif (typeof value !== \"string\") throw new Error(`${path} must be a string.`);\n\tconst trimmed = value.trim();\n\tif (!allowEmpty && !trimmed) throw new Error(`${path} must not be empty.`);\n\treturn allowEmpty ? value : trimmed;\n}\n\nfunction expectPositiveInteger(value: unknown, path: string): number {\n\tif (typeof value !== \"number\" || !Number.isInteger(value) || value <= 0) {\n\t\tthrow new Error(`${path} must be a positive integer.`);\n\t}\n\treturn value;\n}\n\nexport function parseRouterFile(raw: string): RouterFile {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\tthrow new Error(\"router.json is not valid JSON.\");\n\t}\n\tconst root = expectObject(parsed, \"router.json\");\n\trejectUnknownFields(root, ROOT_FIELDS, \"router.json\");\n\tconst version = root.version ?? CONFIG_VERSION;\n\tif (version !== CONFIG_VERSION) {\n\t\tthrow new Error(`router.json has unsupported version ${String(version)}.`);\n\t}\n\tif (root.relays !== undefined && !Array.isArray(root.relays)) {\n\t\tthrow new Error(\"router.json relays must be an array.\");\n\t}\n\n\tconst relays = ((root.relays as unknown[] | undefined) ?? []).map((value, index) =>\n\t\tparseRelay(value, `router.json relays[${index}]`),\n\t);\n\tconst relayIds = new Set<string>();\n\tfor (const relay of relays) {\n\t\tif (relayIds.has(relay.id)) throw new Error(`router.json contains duplicate relay id \"${relay.id}\".`);\n\t\trelayIds.add(relay.id);\n\t}\n\treturn { version: CONFIG_VERSION, relays };\n}\n\nfunction parseRelay(value: unknown, path: string): RelayConfig {\n\tconst record = expectObject(value, path);\n\trejectUnknownFields(record, RELAY_FIELDS, path);\n\tconst id = expectString(record.id, `${path}.id`);\n\tif (!isValidRelayId(id)) throw new Error(`${path}.id must be a valid provider id without '/'.`);\n\tconst baseUrl = expectString(record.baseUrl, `${path}.baseUrl`);\n\ttry {\n\t\tconst url = new URL(baseUrl);\n\t\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") throw new Error(\"unsupported protocol\");\n\t} catch {\n\t\tthrow new Error(`${path}.baseUrl must be an http or https URL.`);\n\t}\n\tconst apiKey = expectString(record.apiKey, `${path}.apiKey`, true);\n\tif (record.models !== undefined && !Array.isArray(record.models)) {\n\t\tthrow new Error(`${path}.models must be an array.`);\n\t}\n\tconst models = ((record.models as unknown[] | undefined) ?? []).map((model, index) =>\n\t\tparseModel(model, `${path}.models[${index}]`),\n\t);\n\tconst modelIds = new Set<string>();\n\tfor (const model of models) {\n\t\tif (modelIds.has(model.id)) throw new Error(`${path}.models contains duplicate id \"${model.id}\".`);\n\t\tmodelIds.add(model.id);\n\t}\n\treturn { id, baseUrl, apiKey, models };\n}\n\nfunction parseModel(value: unknown, path: string): RelayModelConfig {\n\tconst record = expectObject(value, path);\n\trejectUnknownFields(record, MODEL_FIELDS, path);\n\tconst model: RelayModelConfig = { id: expectString(record.id, `${path}.id`) };\n\tif (record.name !== undefined) {\n\t\tconst name = expectString(record.name, `${path}.name`, true).trim();\n\t\tif (name) model.name = name;\n\t}\n\tif (record.reasoning !== undefined) {\n\t\tif (typeof record.reasoning !== \"boolean\") throw new Error(`${path}.reasoning must be a boolean.`);\n\t\tmodel.reasoning = record.reasoning;\n\t}\n\tif (record.input !== undefined) {\n\t\tif (!Array.isArray(record.input) || record.input.some((item) => item !== \"text\" && item !== \"image\")) {\n\t\t\tthrow new Error(`${path}.input must contain only \"text\" and \"image\".`);\n\t\t}\n\t\tconst input = new Set(record.input as Array<\"text\" | \"image\">);\n\t\tif (!input.has(\"text\")) throw new Error(`${path}.input must include \"text\".`);\n\t\tmodel.input = input.has(\"image\") ? [\"text\", \"image\"] : [\"text\"];\n\t}\n\tif (record.contextWindow !== undefined) {\n\t\tmodel.contextWindow = expectPositiveInteger(record.contextWindow, `${path}.contextWindow`);\n\t}\n\tif (record.maxTokens !== undefined) {\n\t\tmodel.maxTokens = expectPositiveInteger(record.maxTokens, `${path}.maxTokens`);\n\t}\n\tif (record.thinkingLevelMap !== undefined) {\n\t\tmodel.thinkingLevelMap = parseThinkingLevelMap(record.thinkingLevelMap, `${path}.thinkingLevelMap`);\n\t}\n\treturn model;\n}\n\nfunction parseThinkingLevelMap(value: unknown, path: string): ThinkingLevelMap {\n\tconst record = expectObject(value, path);\n\tconst map: ThinkingLevelMap = {};\n\tfor (const [level, target] of Object.entries(record)) {\n\t\tif (!(THINKING_LEVELS as readonly string[]).includes(level)) {\n\t\t\tthrow new Error(`${path} has unsupported thinking level \"${level}\".`);\n\t\t}\n\t\tif (target !== null && (typeof target !== \"string\" || target.trim().length === 0)) {\n\t\t\tthrow new Error(`${path}.${level} must be a non-empty string or null.`);\n\t\t}\n\t\tmap[level as keyof ThinkingLevelMap] = target === null ? null : target.trim();\n\t}\n\treturn map;\n}\n\nexport async function saveRouterFile(file: RouterFile): Promise<void> {\n\tconst filePath = getRouterConfigPath();\n\tconst payload: RouterFile = {\n\t\tversion: CONFIG_VERSION,\n\t\trelays: file.relays.map((relay) => ({\n\t\t\tid: relay.id,\n\t\t\tbaseUrl: relay.baseUrl,\n\t\t\tapiKey: relay.apiKey,\n\t\t\tmodels: relay.models.map((model) => serializeModel(model)),\n\t\t})),\n\t};\n\tconst serialized = `${JSON.stringify(payload, null, 2)}\\n`;\n\tawait mkdir(dirname(filePath), { recursive: true });\n\tawait withFileMutationQueue(filePath, async () => {\n\t\tconst tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;\n\t\ttry {\n\t\t\tawait writeFile(tempPath, serialized, { encoding: \"utf8\", mode: 0o600 });\n\t\t\tawait rename(tempPath, filePath);\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait unlink(tempPath);\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors and preserve the original failure.\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t});\n}\n\nfunction serializeModel(model: RelayModelConfig): RelayModelConfig {\n\tconst out: RelayModelConfig = { id: model.id };\n\tif (model.name) out.name = model.name;\n\tif (model.reasoning !== undefined) out.reasoning = model.reasoning;\n\tif (model.input) out.input = model.input;\n\tif (model.contextWindow !== undefined) out.contextWindow = model.contextWindow;\n\tif (model.maxTokens !== undefined) out.maxTokens = model.maxTokens;\n\tif (model.thinkingLevelMap && Object.keys(model.thinkingLevelMap).length > 0) {\n\t\tout.thinkingLevelMap = model.thinkingLevelMap;\n\t}\n\treturn out;\n}\n\nexport async function upsertRelay(relay: RelayConfig): Promise<RouterFile> {\n\tconst file = await loadRouterFile();\n\tconst index = file.relays.findIndex((entry) => entry.id === relay.id);\n\tif (index >= 0) file.relays[index] = relay;\n\telse file.relays.push(relay);\n\tawait saveRouterFile(file);\n\treturn file;\n}\n\nexport async function removeRelay(id: string): Promise<RouterFile> {\n\tconst file = await loadRouterFile();\n\tfile.relays = file.relays.filter((entry) => entry.id !== id);\n\tawait saveRouterFile(file);\n\treturn file;\n}\n"]}
@@ -5,11 +5,17 @@
5
5
  * Searchable pickers follow Pi's native /model interaction pattern.
6
6
  */
7
7
  import type { ExtensionAPI, ExtensionCommandContext } from "../../core/extensions/types.ts";
8
+ import { type ModelChecklistItem } from "./dialog.ts";
9
+ import type { RelayModelConfig } from "./types.ts";
8
10
  export declare function isCurrentRouterModel(current: {
9
11
  provider: string;
10
12
  id: string;
11
13
  } | undefined, relayId: string, modelId: string): boolean;
12
14
  export declare function runRouterCommand(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void>;
15
+ export declare function mergeCatalogWithConfigured(configured: ReadonlyArray<RelayModelConfig>, catalog: ReadonlyArray<{
16
+ id: string;
17
+ name?: string;
18
+ }>, activeModelId?: string): ModelChecklistItem[];
13
19
  /** Resolve only keys that can be safely used by the catalog probe. */
14
20
  export declare function resolveProbeApiKey(apiKey: string): {
15
21
  value?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../src/extensions/router/ui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AA6F5F,wBAAgB,oBAAoB,CACnC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACrD,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACb,OAAO,CAET;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,uBAAuB,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBlH;AAqtBD,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBrF"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../src/extensions/router/ui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAK5F,OAAO,EAIN,KAAK,kBAAkB,EACvB,MAAM,aAAa,CAAC;AAiBrB,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAkEhE,wBAAgB,oBAAoB,CACnC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACrD,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACb,OAAO,CAET;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,uBAAuB,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBlH;AAmoBD,wBAAgB,0BAA0B,CACzC,UAAU,EAAE,aAAa,CAAC,gBAAgB,CAAC,EAC3C,OAAO,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACrD,aAAa,CAAC,EAAE,MAAM,GACpB,kBAAkB,EAAE,CAuBtB;AAkED,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBrF"}
@@ -307,11 +307,11 @@ async function editRelayFlow(ctx, pi, dialogs, initial) {
307
307
  continue;
308
308
  }
309
309
  if (choice === "apiKey") {
310
- const next = await promptText(ctx, dialogs, `Relay · ${relay.id} · API key`, relay.apiKey, (value) => value.trim() ? undefined : "API key is required.");
310
+ const next = await promptText(ctx, dialogs, `Relay · ${relay.id} · API key`, "Enter a new key · blank keeps the current value");
311
311
  if (next === undefined)
312
312
  continue;
313
313
  const trimmed = next.trim();
314
- if (trimmed === relay.apiKey)
314
+ if (!trimmed || trimmed === relay.apiKey)
315
315
  continue;
316
316
  relay.apiKey = trimmed;
317
317
  await persistRelay(ctx, pi, relay);
@@ -564,12 +564,17 @@ async function fetchAndSelectModels(ctx, pi, dialogs, relay) {
564
564
  return;
565
565
  if (result.truncated)
566
566
  ctx.ui.notify("Catalog truncated to 2,000 models.", "warning");
567
- const catalog = mergeCatalogWithConfigured(relay.models, result.models);
568
- const initiallySelected = new Set(relay.models.map((model) => model.id));
569
567
  const activeModelId = ctx.model?.provider === relay.id ? ctx.model.id : undefined;
570
- const currentModelId = activeModelId && initiallySelected.has(activeModelId) ? activeModelId : undefined;
568
+ const catalog = mergeCatalogWithConfigured(relay.models, result.models, activeModelId);
569
+ const initiallySelected = new Set(relay.models.map((model) => model.id));
570
+ if (activeModelId)
571
+ initiallySelected.add(activeModelId);
572
+ const currentModelId = activeModelId;
571
573
  const protectedIds = currentModelId ? new Set([currentModelId]) : undefined;
572
574
  const preserved = new Map(relay.models.map((model) => [model.id, structuredClone(model)]));
575
+ if (activeModelId && !preserved.has(activeModelId)) {
576
+ preserved.set(activeModelId, createDefaultModelConfig(activeModelId));
577
+ }
573
578
  const saver = new RelayAutoSaver(ctx, pi, relay);
574
579
  const applySelection = (selectedIds) => {
575
580
  if (currentModelId && !selectedIds.includes(currentModelId)) {
@@ -642,7 +647,7 @@ function mergeAddedModels(previous, additions) {
642
647
  }
643
648
  return result;
644
649
  }
645
- function mergeCatalogWithConfigured(configured, catalog) {
650
+ export function mergeCatalogWithConfigured(configured, catalog, activeModelId) {
646
651
  const items = new Map();
647
652
  const configuredById = new Map(configured.map((model) => [model.id, model]));
648
653
  for (const model of catalog) {
@@ -661,6 +666,9 @@ function mergeCatalogWithConfigured(configured, catalog) {
661
666
  });
662
667
  }
663
668
  }
669
+ if (activeModelId && !items.has(activeModelId)) {
670
+ items.set(activeModelId, { id: activeModelId, name: activeModelId, unavailable: true });
671
+ }
664
672
  return [...items.values()].sort((left, right) => left.id.localeCompare(right.id));
665
673
  }
666
674
  function applySelectionIfChanged(relay, selectedIds, apply) {