@zhachory1/mewrite-ai 0.65.3

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 (238) hide show
  1. package/README.md +1330 -0
  2. package/dist/api-registry.d.ts +20 -0
  3. package/dist/api-registry.d.ts.map +1 -0
  4. package/dist/api-registry.js +44 -0
  5. package/dist/api-registry.js.map +1 -0
  6. package/dist/bedrock-provider.d.ts +5 -0
  7. package/dist/bedrock-provider.d.ts.map +1 -0
  8. package/dist/bedrock-provider.js +6 -0
  9. package/dist/bedrock-provider.js.map +1 -0
  10. package/dist/cache/__tests__/compaction.test.d.ts +2 -0
  11. package/dist/cache/__tests__/compaction.test.d.ts.map +1 -0
  12. package/dist/cache/__tests__/compaction.test.js +100 -0
  13. package/dist/cache/__tests__/compaction.test.js.map +1 -0
  14. package/dist/cache/__tests__/layers.test.d.ts +2 -0
  15. package/dist/cache/__tests__/layers.test.d.ts.map +1 -0
  16. package/dist/cache/__tests__/layers.test.js +71 -0
  17. package/dist/cache/__tests__/layers.test.js.map +1 -0
  18. package/dist/cache/__tests__/policy.test.d.ts +2 -0
  19. package/dist/cache/__tests__/policy.test.d.ts.map +1 -0
  20. package/dist/cache/__tests__/policy.test.js +79 -0
  21. package/dist/cache/__tests__/policy.test.js.map +1 -0
  22. package/dist/cache/__tests__/tool-serializer.test.d.ts +2 -0
  23. package/dist/cache/__tests__/tool-serializer.test.d.ts.map +1 -0
  24. package/dist/cache/__tests__/tool-serializer.test.js +64 -0
  25. package/dist/cache/__tests__/tool-serializer.test.js.map +1 -0
  26. package/dist/cache/compaction.d.ts +30 -0
  27. package/dist/cache/compaction.d.ts.map +1 -0
  28. package/dist/cache/compaction.js +50 -0
  29. package/dist/cache/compaction.js.map +1 -0
  30. package/dist/cache/index.d.ts +5 -0
  31. package/dist/cache/index.d.ts.map +1 -0
  32. package/dist/cache/index.js +5 -0
  33. package/dist/cache/index.js.map +1 -0
  34. package/dist/cache/layers.d.ts +17 -0
  35. package/dist/cache/layers.d.ts.map +1 -0
  36. package/dist/cache/layers.js +48 -0
  37. package/dist/cache/layers.js.map +1 -0
  38. package/dist/cache/policy.d.ts +40 -0
  39. package/dist/cache/policy.d.ts.map +1 -0
  40. package/dist/cache/policy.js +50 -0
  41. package/dist/cache/policy.js.map +1 -0
  42. package/dist/cache/tool-serializer.d.ts +8 -0
  43. package/dist/cache/tool-serializer.d.ts.map +1 -0
  44. package/dist/cache/tool-serializer.js +35 -0
  45. package/dist/cache/tool-serializer.js.map +1 -0
  46. package/dist/cli.d.ts +3 -0
  47. package/dist/cli.d.ts.map +1 -0
  48. package/dist/cli.js +116 -0
  49. package/dist/cli.js.map +1 -0
  50. package/dist/env-api-keys.d.ts +27 -0
  51. package/dist/env-api-keys.d.ts.map +1 -0
  52. package/dist/env-api-keys.js +223 -0
  53. package/dist/env-api-keys.js.map +1 -0
  54. package/dist/index.d.ts +32 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +21 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/models.d.ts +34 -0
  59. package/dist/models.d.ts.map +1 -0
  60. package/dist/models.generated.d.ts +14370 -0
  61. package/dist/models.generated.d.ts.map +1 -0
  62. package/dist/models.generated.js +14181 -0
  63. package/dist/models.generated.js.map +1 -0
  64. package/dist/models.js +140 -0
  65. package/dist/models.js.map +1 -0
  66. package/dist/oauth.d.ts +2 -0
  67. package/dist/oauth.d.ts.map +1 -0
  68. package/dist/oauth.js +2 -0
  69. package/dist/oauth.js.map +1 -0
  70. package/dist/providers/amazon-bedrock.d.ts +20 -0
  71. package/dist/providers/amazon-bedrock.d.ts.map +1 -0
  72. package/dist/providers/amazon-bedrock.js +651 -0
  73. package/dist/providers/amazon-bedrock.js.map +1 -0
  74. package/dist/providers/anthropic-capabilities.d.ts +65 -0
  75. package/dist/providers/anthropic-capabilities.d.ts.map +1 -0
  76. package/dist/providers/anthropic-capabilities.js +155 -0
  77. package/dist/providers/anthropic-capabilities.js.map +1 -0
  78. package/dist/providers/anthropic-discovery.d.ts +49 -0
  79. package/dist/providers/anthropic-discovery.d.ts.map +1 -0
  80. package/dist/providers/anthropic-discovery.js +218 -0
  81. package/dist/providers/anthropic-discovery.js.map +1 -0
  82. package/dist/providers/anthropic.d.ts +40 -0
  83. package/dist/providers/anthropic.d.ts.map +1 -0
  84. package/dist/providers/anthropic.js +766 -0
  85. package/dist/providers/anthropic.js.map +1 -0
  86. package/dist/providers/azure-openai-responses.d.ts +15 -0
  87. package/dist/providers/azure-openai-responses.d.ts.map +1 -0
  88. package/dist/providers/azure-openai-responses.js +176 -0
  89. package/dist/providers/azure-openai-responses.js.map +1 -0
  90. package/dist/providers/faux.d.ts +56 -0
  91. package/dist/providers/faux.d.ts.map +1 -0
  92. package/dist/providers/faux.js +367 -0
  93. package/dist/providers/faux.js.map +1 -0
  94. package/dist/providers/github-copilot-headers.d.ts +8 -0
  95. package/dist/providers/github-copilot-headers.d.ts.map +1 -0
  96. package/dist/providers/github-copilot-headers.js +29 -0
  97. package/dist/providers/github-copilot-headers.js.map +1 -0
  98. package/dist/providers/google-gemini-cli.d.ts +74 -0
  99. package/dist/providers/google-gemini-cli.d.ts.map +1 -0
  100. package/dist/providers/google-gemini-cli.js +776 -0
  101. package/dist/providers/google-gemini-cli.js.map +1 -0
  102. package/dist/providers/google-shared.d.ts +65 -0
  103. package/dist/providers/google-shared.d.ts.map +1 -0
  104. package/dist/providers/google-shared.js +312 -0
  105. package/dist/providers/google-shared.js.map +1 -0
  106. package/dist/providers/google-vertex.d.ts +15 -0
  107. package/dist/providers/google-vertex.d.ts.map +1 -0
  108. package/dist/providers/google-vertex.js +419 -0
  109. package/dist/providers/google-vertex.js.map +1 -0
  110. package/dist/providers/google.d.ts +13 -0
  111. package/dist/providers/google.d.ts.map +1 -0
  112. package/dist/providers/google.js +374 -0
  113. package/dist/providers/google.js.map +1 -0
  114. package/dist/providers/mistral.d.ts +22 -0
  115. package/dist/providers/mistral.d.ts.map +1 -0
  116. package/dist/providers/mistral.js +501 -0
  117. package/dist/providers/mistral.js.map +1 -0
  118. package/dist/providers/openai-codex-responses.d.ts +9 -0
  119. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  120. package/dist/providers/openai-codex-responses.js +741 -0
  121. package/dist/providers/openai-codex-responses.js.map +1 -0
  122. package/dist/providers/openai-completions.d.ts +15 -0
  123. package/dist/providers/openai-completions.d.ts.map +1 -0
  124. package/dist/providers/openai-completions.js +753 -0
  125. package/dist/providers/openai-completions.js.map +1 -0
  126. package/dist/providers/openai-responses-shared.d.ts +17 -0
  127. package/dist/providers/openai-responses-shared.d.ts.map +1 -0
  128. package/dist/providers/openai-responses-shared.js +470 -0
  129. package/dist/providers/openai-responses-shared.js.map +1 -0
  130. package/dist/providers/openai-responses.d.ts +13 -0
  131. package/dist/providers/openai-responses.d.ts.map +1 -0
  132. package/dist/providers/openai-responses.js +190 -0
  133. package/dist/providers/openai-responses.js.map +1 -0
  134. package/dist/providers/register-builtins.d.ts +38 -0
  135. package/dist/providers/register-builtins.d.ts.map +1 -0
  136. package/dist/providers/register-builtins.js +261 -0
  137. package/dist/providers/register-builtins.js.map +1 -0
  138. package/dist/providers/simple-options.d.ts +8 -0
  139. package/dist/providers/simple-options.d.ts.map +1 -0
  140. package/dist/providers/simple-options.js +35 -0
  141. package/dist/providers/simple-options.js.map +1 -0
  142. package/dist/providers/transform-messages.d.ts +8 -0
  143. package/dist/providers/transform-messages.d.ts.map +1 -0
  144. package/dist/providers/transform-messages.js +155 -0
  145. package/dist/providers/transform-messages.js.map +1 -0
  146. package/dist/registry/fetcher.d.ts +26 -0
  147. package/dist/registry/fetcher.d.ts.map +1 -0
  148. package/dist/registry/fetcher.js +69 -0
  149. package/dist/registry/fetcher.js.map +1 -0
  150. package/dist/registry/index.d.ts +8 -0
  151. package/dist/registry/index.d.ts.map +1 -0
  152. package/dist/registry/index.js +8 -0
  153. package/dist/registry/index.js.map +1 -0
  154. package/dist/registry/loader.d.ts +38 -0
  155. package/dist/registry/loader.d.ts.map +1 -0
  156. package/dist/registry/loader.js +90 -0
  157. package/dist/registry/loader.js.map +1 -0
  158. package/dist/registry/merger.d.ts +21 -0
  159. package/dist/registry/merger.d.ts.map +1 -0
  160. package/dist/registry/merger.js +90 -0
  161. package/dist/registry/merger.js.map +1 -0
  162. package/dist/registry/schema.d.ts +130 -0
  163. package/dist/registry/schema.d.ts.map +1 -0
  164. package/dist/registry/schema.js +103 -0
  165. package/dist/registry/schema.js.map +1 -0
  166. package/dist/stream.d.ts +8 -0
  167. package/dist/stream.d.ts.map +1 -0
  168. package/dist/stream.js +27 -0
  169. package/dist/stream.js.map +1 -0
  170. package/dist/types.d.ts +296 -0
  171. package/dist/types.d.ts.map +1 -0
  172. package/dist/types.js +2 -0
  173. package/dist/types.js.map +1 -0
  174. package/dist/utils/event-stream.d.ts +21 -0
  175. package/dist/utils/event-stream.d.ts.map +1 -0
  176. package/dist/utils/event-stream.js +81 -0
  177. package/dist/utils/event-stream.js.map +1 -0
  178. package/dist/utils/hash.d.ts +3 -0
  179. package/dist/utils/hash.d.ts.map +1 -0
  180. package/dist/utils/hash.js +14 -0
  181. package/dist/utils/hash.js.map +1 -0
  182. package/dist/utils/json-parse.d.ts +9 -0
  183. package/dist/utils/json-parse.d.ts.map +1 -0
  184. package/dist/utils/json-parse.js +29 -0
  185. package/dist/utils/json-parse.js.map +1 -0
  186. package/dist/utils/oauth/anthropic.d.ts +25 -0
  187. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  188. package/dist/utils/oauth/anthropic.js +336 -0
  189. package/dist/utils/oauth/anthropic.js.map +1 -0
  190. package/dist/utils/oauth/github-copilot.d.ts +30 -0
  191. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  192. package/dist/utils/oauth/github-copilot.js +293 -0
  193. package/dist/utils/oauth/github-copilot.js.map +1 -0
  194. package/dist/utils/oauth/google-antigravity.d.ts +26 -0
  195. package/dist/utils/oauth/google-antigravity.d.ts.map +1 -0
  196. package/dist/utils/oauth/google-antigravity.js +376 -0
  197. package/dist/utils/oauth/google-antigravity.js.map +1 -0
  198. package/dist/utils/oauth/google-gemini-cli.d.ts +26 -0
  199. package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -0
  200. package/dist/utils/oauth/google-gemini-cli.js +482 -0
  201. package/dist/utils/oauth/google-gemini-cli.js.map +1 -0
  202. package/dist/utils/oauth/index.d.ts +61 -0
  203. package/dist/utils/oauth/index.d.ts.map +1 -0
  204. package/dist/utils/oauth/index.js +131 -0
  205. package/dist/utils/oauth/index.js.map +1 -0
  206. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  207. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  208. package/dist/utils/oauth/oauth-page.js +105 -0
  209. package/dist/utils/oauth/oauth-page.js.map +1 -0
  210. package/dist/utils/oauth/openai-codex.d.ts +34 -0
  211. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  212. package/dist/utils/oauth/openai-codex.js +374 -0
  213. package/dist/utils/oauth/openai-codex.js.map +1 -0
  214. package/dist/utils/oauth/pkce.d.ts +13 -0
  215. package/dist/utils/oauth/pkce.d.ts.map +1 -0
  216. package/dist/utils/oauth/pkce.js +31 -0
  217. package/dist/utils/oauth/pkce.js.map +1 -0
  218. package/dist/utils/oauth/types.d.ts +49 -0
  219. package/dist/utils/oauth/types.d.ts.map +1 -0
  220. package/dist/utils/oauth/types.js +2 -0
  221. package/dist/utils/oauth/types.js.map +1 -0
  222. package/dist/utils/overflow.d.ts +53 -0
  223. package/dist/utils/overflow.d.ts.map +1 -0
  224. package/dist/utils/overflow.js +132 -0
  225. package/dist/utils/overflow.js.map +1 -0
  226. package/dist/utils/sanitize-unicode.d.ts +22 -0
  227. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  228. package/dist/utils/sanitize-unicode.js +26 -0
  229. package/dist/utils/sanitize-unicode.js.map +1 -0
  230. package/dist/utils/typebox-helpers.d.ts +17 -0
  231. package/dist/utils/typebox-helpers.d.ts.map +1 -0
  232. package/dist/utils/typebox-helpers.js +21 -0
  233. package/dist/utils/typebox-helpers.js.map +1 -0
  234. package/dist/utils/validation.d.ts +18 -0
  235. package/dist/utils/validation.d.ts.map +1 -0
  236. package/dist/utils/validation.js +80 -0
  237. package/dist/utils/validation.js.map +1 -0
  238. package/package.json +129 -0
@@ -0,0 +1,35 @@
1
+ export function buildBaseOptions(model, options, apiKey) {
2
+ return {
3
+ temperature: options?.temperature,
4
+ maxTokens: options?.maxTokens || Math.min(model.maxTokens, 32000),
5
+ signal: options?.signal,
6
+ apiKey: apiKey || options?.apiKey,
7
+ cacheRetention: options?.cacheRetention,
8
+ sessionId: options?.sessionId,
9
+ headers: options?.headers,
10
+ onPayload: options?.onPayload,
11
+ maxRetryDelayMs: options?.maxRetryDelayMs,
12
+ metadata: options?.metadata,
13
+ };
14
+ }
15
+ export function clampReasoning(effort) {
16
+ return effort === "xhigh" ? "high" : effort;
17
+ }
18
+ export function adjustMaxTokensForThinking(baseMaxTokens, modelMaxTokens, reasoningLevel, customBudgets) {
19
+ const defaultBudgets = {
20
+ minimal: 1024,
21
+ low: 2048,
22
+ medium: 8192,
23
+ high: 16384,
24
+ };
25
+ const budgets = { ...defaultBudgets, ...customBudgets };
26
+ const minOutputTokens = 1024;
27
+ const level = clampReasoning(reasoningLevel);
28
+ let thinkingBudget = budgets[level];
29
+ const maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
30
+ if (maxTokens <= thinkingBudget) {
31
+ thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
32
+ }
33
+ return { maxTokens, thinkingBudget };
34
+ }
35
+ //# sourceMappingURL=simple-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simple-options.js","sourceRoot":"","sources":["../../src/providers/simple-options.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,KAAiB,EAAE,OAA6B,EAAE,MAAe,EAAiB;IAClH,OAAO;QACN,WAAW,EAAE,OAAO,EAAE,WAAW;QACjC,SAAS,EAAE,OAAO,EAAE,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC;QACjE,MAAM,EAAE,OAAO,EAAE,MAAM;QACvB,MAAM,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM;QACjC,cAAc,EAAE,OAAO,EAAE,cAAc;QACvC,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,eAAe,EAAE,OAAO,EAAE,eAAe;QACzC,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC3B,CAAC;AAAA,CACF;AAED,MAAM,UAAU,cAAc,CAAC,MAAiC,EAA+C;IAC9G,OAAO,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AAAA,CAC5C;AAED,MAAM,UAAU,0BAA0B,CACzC,aAAqB,EACrB,cAAsB,EACtB,cAA6B,EAC7B,aAA+B,EACiB;IAChD,MAAM,cAAc,GAAoB;QACvC,OAAO,EAAE,IAAI;QACb,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,IAAI;QACZ,IAAI,EAAE,KAAK;KACX,CAAC;IACF,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,aAAa,EAAE,CAAC;IAExD,MAAM,eAAe,GAAG,IAAI,CAAC;IAC7B,MAAM,KAAK,GAAG,cAAc,CAAC,cAAc,CAAE,CAAC;IAC9C,IAAI,cAAc,GAAG,OAAO,CAAC,KAAK,CAAE,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,cAAc,EAAE,cAAc,CAAC,CAAC;IAE3E,IAAI,SAAS,IAAI,cAAc,EAAE,CAAC;QACjC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,eAAe,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;AAAA,CACrC","sourcesContent":["import type { Api, Model, SimpleStreamOptions, StreamOptions, ThinkingBudgets, ThinkingLevel } from \"../types.js\";\n\nexport function buildBaseOptions(model: Model<Api>, options?: SimpleStreamOptions, apiKey?: string): StreamOptions {\n\treturn {\n\t\ttemperature: options?.temperature,\n\t\tmaxTokens: options?.maxTokens || Math.min(model.maxTokens, 32000),\n\t\tsignal: options?.signal,\n\t\tapiKey: apiKey || options?.apiKey,\n\t\tcacheRetention: options?.cacheRetention,\n\t\tsessionId: options?.sessionId,\n\t\theaders: options?.headers,\n\t\tonPayload: options?.onPayload,\n\t\tmaxRetryDelayMs: options?.maxRetryDelayMs,\n\t\tmetadata: options?.metadata,\n\t};\n}\n\nexport function clampReasoning(effort: ThinkingLevel | undefined): Exclude<ThinkingLevel, \"xhigh\"> | undefined {\n\treturn effort === \"xhigh\" ? \"high\" : effort;\n}\n\nexport function adjustMaxTokensForThinking(\n\tbaseMaxTokens: number,\n\tmodelMaxTokens: number,\n\treasoningLevel: ThinkingLevel,\n\tcustomBudgets?: ThinkingBudgets,\n): { maxTokens: number; thinkingBudget: number } {\n\tconst defaultBudgets: ThinkingBudgets = {\n\t\tminimal: 1024,\n\t\tlow: 2048,\n\t\tmedium: 8192,\n\t\thigh: 16384,\n\t};\n\tconst budgets = { ...defaultBudgets, ...customBudgets };\n\n\tconst minOutputTokens = 1024;\n\tconst level = clampReasoning(reasoningLevel)!;\n\tlet thinkingBudget = budgets[level]!;\n\tconst maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);\n\n\tif (maxTokens <= thinkingBudget) {\n\t\tthinkingBudget = Math.max(0, maxTokens - minOutputTokens);\n\t}\n\n\treturn { maxTokens, thinkingBudget };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { Api, AssistantMessage, Message, Model } from "../types.js";
2
+ /**
3
+ * Normalize tool call ID for cross-provider compatibility.
4
+ * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
5
+ * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
6
+ */
7
+ export declare function transformMessages<TApi extends Api>(messages: Message[], model: Model<TApi>, normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string): Message[];
8
+ //# sourceMappingURL=transform-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform-messages.d.ts","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAA+B,MAAM,aAAa,CAAC;AAEtG;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,GAAG,EACjD,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,MAAM,GACxF,OAAO,EAAE,CAgKX","sourcesContent":["import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from \"../types.js\";\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\n\t// First pass: transform messages (thinking blocks, tool call ID normalization)\n\tconst transformed = messages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block) => {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking is opaque encrypted content, only valid for the same model.\n\t\t\t\t\t// Drop it for cross-model to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn isSameModel ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// For same model: keep thinking blocks with signatures (needed for replay)\n\t\t\t\t\t// even if the thinking text is empty (OpenAI encrypted reasoning)\n\t\t\t\t\tif (isSameModel && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\n\tfor (let i = 0; i < transformed.length; i++) {\n\t\tconst msg = transformed[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Normalize tool call ID for cross-provider compatibility.
3
+ * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
4
+ * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
5
+ */
6
+ export function transformMessages(messages, model, normalizeToolCallId) {
7
+ // Build a map of original tool call IDs to normalized IDs
8
+ const toolCallIdMap = new Map();
9
+ // First pass: transform messages (thinking blocks, tool call ID normalization)
10
+ const transformed = messages.map((msg) => {
11
+ // User messages pass through unchanged
12
+ if (msg.role === "user") {
13
+ return msg;
14
+ }
15
+ // Handle toolResult messages - normalize toolCallId if we have a mapping
16
+ if (msg.role === "toolResult") {
17
+ const normalizedId = toolCallIdMap.get(msg.toolCallId);
18
+ if (normalizedId && normalizedId !== msg.toolCallId) {
19
+ return { ...msg, toolCallId: normalizedId };
20
+ }
21
+ return msg;
22
+ }
23
+ // Assistant messages need transformation check
24
+ if (msg.role === "assistant") {
25
+ const assistantMsg = msg;
26
+ const isSameModel = assistantMsg.provider === model.provider &&
27
+ assistantMsg.api === model.api &&
28
+ assistantMsg.model === model.id;
29
+ const transformedContent = assistantMsg.content.flatMap((block) => {
30
+ if (block.type === "thinking") {
31
+ // Redacted thinking is opaque encrypted content, only valid for the same model.
32
+ // Drop it for cross-model to avoid API errors.
33
+ if (block.redacted) {
34
+ return isSameModel ? block : [];
35
+ }
36
+ // For same model: keep thinking blocks with signatures (needed for replay)
37
+ // even if the thinking text is empty (OpenAI encrypted reasoning)
38
+ if (isSameModel && block.thinkingSignature)
39
+ return block;
40
+ // Skip empty thinking blocks, convert others to plain text
41
+ if (!block.thinking || block.thinking.trim() === "")
42
+ return [];
43
+ if (isSameModel)
44
+ return block;
45
+ return {
46
+ type: "text",
47
+ text: block.thinking,
48
+ };
49
+ }
50
+ if (block.type === "text") {
51
+ if (isSameModel)
52
+ return block;
53
+ return {
54
+ type: "text",
55
+ text: block.text,
56
+ };
57
+ }
58
+ if (block.type === "toolCall") {
59
+ const toolCall = block;
60
+ let normalizedToolCall = toolCall;
61
+ if (!isSameModel && toolCall.thoughtSignature) {
62
+ normalizedToolCall = { ...toolCall };
63
+ delete normalizedToolCall.thoughtSignature;
64
+ }
65
+ if (!isSameModel && normalizeToolCallId) {
66
+ const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
67
+ if (normalizedId !== toolCall.id) {
68
+ toolCallIdMap.set(toolCall.id, normalizedId);
69
+ normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
70
+ }
71
+ }
72
+ return normalizedToolCall;
73
+ }
74
+ return block;
75
+ });
76
+ return {
77
+ ...assistantMsg,
78
+ content: transformedContent,
79
+ };
80
+ }
81
+ return msg;
82
+ });
83
+ // Second pass: insert synthetic empty tool results for orphaned tool calls
84
+ // This preserves thinking signatures and satisfies API requirements
85
+ const result = [];
86
+ let pendingToolCalls = [];
87
+ let existingToolResultIds = new Set();
88
+ for (let i = 0; i < transformed.length; i++) {
89
+ const msg = transformed[i];
90
+ if (msg.role === "assistant") {
91
+ // If we have pending orphaned tool calls from a previous assistant, insert synthetic results now
92
+ if (pendingToolCalls.length > 0) {
93
+ for (const tc of pendingToolCalls) {
94
+ if (!existingToolResultIds.has(tc.id)) {
95
+ result.push({
96
+ role: "toolResult",
97
+ toolCallId: tc.id,
98
+ toolName: tc.name,
99
+ content: [{ type: "text", text: "No result provided" }],
100
+ isError: true,
101
+ timestamp: Date.now(),
102
+ });
103
+ }
104
+ }
105
+ pendingToolCalls = [];
106
+ existingToolResultIds = new Set();
107
+ }
108
+ // Skip errored/aborted assistant messages entirely.
109
+ // These are incomplete turns that shouldn't be replayed:
110
+ // - May have partial content (reasoning without message, incomplete tool calls)
111
+ // - Replaying them can cause API errors (e.g., OpenAI "reasoning without following item")
112
+ // - The model should retry from the last valid state
113
+ const assistantMsg = msg;
114
+ if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
115
+ continue;
116
+ }
117
+ // Track tool calls from this assistant message
118
+ const toolCalls = assistantMsg.content.filter((b) => b.type === "toolCall");
119
+ if (toolCalls.length > 0) {
120
+ pendingToolCalls = toolCalls;
121
+ existingToolResultIds = new Set();
122
+ }
123
+ result.push(msg);
124
+ }
125
+ else if (msg.role === "toolResult") {
126
+ existingToolResultIds.add(msg.toolCallId);
127
+ result.push(msg);
128
+ }
129
+ else if (msg.role === "user") {
130
+ // User message interrupts tool flow - insert synthetic results for orphaned calls
131
+ if (pendingToolCalls.length > 0) {
132
+ for (const tc of pendingToolCalls) {
133
+ if (!existingToolResultIds.has(tc.id)) {
134
+ result.push({
135
+ role: "toolResult",
136
+ toolCallId: tc.id,
137
+ toolName: tc.name,
138
+ content: [{ type: "text", text: "No result provided" }],
139
+ isError: true,
140
+ timestamp: Date.now(),
141
+ });
142
+ }
143
+ }
144
+ pendingToolCalls = [];
145
+ existingToolResultIds = new Set();
146
+ }
147
+ result.push(msg);
148
+ }
149
+ else {
150
+ result.push(msg);
151
+ }
152
+ }
153
+ return result;
154
+ }
155
+ //# sourceMappingURL=transform-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform-messages.js","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from \"../types.js\";\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\n\t// First pass: transform messages (thinking blocks, tool call ID normalization)\n\tconst transformed = messages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block) => {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking is opaque encrypted content, only valid for the same model.\n\t\t\t\t\t// Drop it for cross-model to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn isSameModel ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// For same model: keep thinking blocks with signatures (needed for replay)\n\t\t\t\t\t// even if the thinking text is empty (OpenAI encrypted reasoning)\n\t\t\t\t\tif (isSameModel && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\n\tfor (let i = 0; i < transformed.length; i++) {\n\t\tconst msg = transformed[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * WS15: Registry fetcher — HTTP fetch + atomic cache write.
3
+ *
4
+ * Fetches the registry from the remote URL, validates it, and writes to
5
+ * ~/.cave/agent/registry-cache.json atomically (write-then-rename).
6
+ */
7
+ import { type Registry } from "./schema.js";
8
+ export type FetchChannel = "stable" | "beta";
9
+ export type FetchRegistryResult = {
10
+ ok: true;
11
+ registry: Registry;
12
+ cached: boolean;
13
+ } | {
14
+ ok: false;
15
+ error: string;
16
+ };
17
+ /**
18
+ * Fetch registry JSON from remote URL, validate, and atomically write to
19
+ * cachePath. Returns the validated Registry on success.
20
+ *
21
+ * @param cachePath Absolute path to write the cache file.
22
+ * @param channel "stable" (default) or "beta".
23
+ * @param fetchImpl Injectable fetch (defaults to globalThis.fetch / Node 18+).
24
+ */
25
+ export declare function fetchAndCacheRegistry(cachePath: string, channel?: FetchChannel, fetchImpl?: typeof fetch): Promise<FetchRegistryResult>;
26
+ //# sourceMappingURL=fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../../src/registry/fetcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,KAAK,QAAQ,EAAoB,MAAM,aAAa,CAAC;AAE9D,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;AAU7C,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnH;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAC1C,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,YAAuB,EAChC,SAAS,GAAE,OAAO,KAAwB,GACxC,OAAO,CAAC,mBAAmB,CAAC,CA+C9B","sourcesContent":["/**\n * WS15: Registry fetcher — HTTP fetch + atomic cache write.\n *\n * Fetches the registry from the remote URL, validates it, and writes to\n * ~/.cave/agent/registry-cache.json atomically (write-then-rename).\n */\n\nimport { mkdirSync, renameSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { type Registry, validateRegistry } from \"./schema.js\";\n\nexport type FetchChannel = \"stable\" | \"beta\";\n\nconst REGISTRY_URLS: Record<FetchChannel, string> = {\n\tstable: \"https://raw.githubusercontent.com/cave-cli/registry/main/registry.json\",\n\tbeta: \"https://raw.githubusercontent.com/cave-cli/registry/canary/registry.json\",\n};\n\n/** Default timeout for registry fetch (ms) */\nconst FETCH_TIMEOUT_MS = 10_000;\n\nexport type FetchRegistryResult = { ok: true; registry: Registry; cached: boolean } | { ok: false; error: string };\n\n/**\n * Fetch registry JSON from remote URL, validate, and atomically write to\n * cachePath. Returns the validated Registry on success.\n *\n * @param cachePath Absolute path to write the cache file.\n * @param channel \"stable\" (default) or \"beta\".\n * @param fetchImpl Injectable fetch (defaults to globalThis.fetch / Node 18+).\n */\nexport async function fetchAndCacheRegistry(\n\tcachePath: string,\n\tchannel: FetchChannel = \"stable\",\n\tfetchImpl: typeof fetch = globalThis.fetch,\n): Promise<FetchRegistryResult> {\n\tconst url = REGISTRY_URLS[channel];\n\n\tlet raw: unknown;\n\ttry {\n\t\tconst controller = new AbortController();\n\t\tconst timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n\t\tconst response = await fetchImpl(url, { signal: controller.signal });\n\t\tclearTimeout(timer);\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `HTTP ${response.status} fetching registry from ${url}`,\n\t\t\t};\n\t\t}\n\n\t\traw = await response.json();\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `Failed to fetch registry: ${err instanceof Error ? err.message : String(err)}`,\n\t\t};\n\t}\n\n\tconst result = validateRegistry(raw);\n\tif (!result.ok) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `Registry validation failed:\\n${result.errors.join(\"\\n\")}`,\n\t\t};\n\t}\n\n\t// Atomic write: write to tmp then rename\n\ttry {\n\t\tmkdirSync(dirname(cachePath), { recursive: true });\n\t\tconst tmpPath = join(tmpdir(), `cave-registry-${Date.now()}.json`);\n\t\twriteFileSync(tmpPath, JSON.stringify(result.registry, null, 2), \"utf-8\");\n\t\trenameSync(tmpPath, cachePath);\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `Failed to write registry cache: ${err instanceof Error ? err.message : String(err)}`,\n\t\t};\n\t}\n\n\treturn { ok: true, registry: result.registry, cached: true };\n}\n"]}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * WS15: Registry fetcher — HTTP fetch + atomic cache write.
3
+ *
4
+ * Fetches the registry from the remote URL, validates it, and writes to
5
+ * ~/.cave/agent/registry-cache.json atomically (write-then-rename).
6
+ */
7
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { dirname, join } from "node:path";
10
+ import { validateRegistry } from "./schema.js";
11
+ const REGISTRY_URLS = {
12
+ stable: "https://raw.githubusercontent.com/cave-cli/registry/main/registry.json",
13
+ beta: "https://raw.githubusercontent.com/cave-cli/registry/canary/registry.json",
14
+ };
15
+ /** Default timeout for registry fetch (ms) */
16
+ const FETCH_TIMEOUT_MS = 10_000;
17
+ /**
18
+ * Fetch registry JSON from remote URL, validate, and atomically write to
19
+ * cachePath. Returns the validated Registry on success.
20
+ *
21
+ * @param cachePath Absolute path to write the cache file.
22
+ * @param channel "stable" (default) or "beta".
23
+ * @param fetchImpl Injectable fetch (defaults to globalThis.fetch / Node 18+).
24
+ */
25
+ export async function fetchAndCacheRegistry(cachePath, channel = "stable", fetchImpl = globalThis.fetch) {
26
+ const url = REGISTRY_URLS[channel];
27
+ let raw;
28
+ try {
29
+ const controller = new AbortController();
30
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
31
+ const response = await fetchImpl(url, { signal: controller.signal });
32
+ clearTimeout(timer);
33
+ if (!response.ok) {
34
+ return {
35
+ ok: false,
36
+ error: `HTTP ${response.status} fetching registry from ${url}`,
37
+ };
38
+ }
39
+ raw = await response.json();
40
+ }
41
+ catch (err) {
42
+ return {
43
+ ok: false,
44
+ error: `Failed to fetch registry: ${err instanceof Error ? err.message : String(err)}`,
45
+ };
46
+ }
47
+ const result = validateRegistry(raw);
48
+ if (!result.ok) {
49
+ return {
50
+ ok: false,
51
+ error: `Registry validation failed:\n${result.errors.join("\n")}`,
52
+ };
53
+ }
54
+ // Atomic write: write to tmp then rename
55
+ try {
56
+ mkdirSync(dirname(cachePath), { recursive: true });
57
+ const tmpPath = join(tmpdir(), `cave-registry-${Date.now()}.json`);
58
+ writeFileSync(tmpPath, JSON.stringify(result.registry, null, 2), "utf-8");
59
+ renameSync(tmpPath, cachePath);
60
+ }
61
+ catch (err) {
62
+ return {
63
+ ok: false,
64
+ error: `Failed to write registry cache: ${err instanceof Error ? err.message : String(err)}`,
65
+ };
66
+ }
67
+ return { ok: true, registry: result.registry, cached: true };
68
+ }
69
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../../src/registry/fetcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAiB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAI9D,MAAM,aAAa,GAAiC;IACnD,MAAM,EAAE,wEAAwE;IAChF,IAAI,EAAE,0EAA0E;CAChF,CAAC;AAEF,8CAA8C;AAC9C,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAIhC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,SAAiB,EACjB,OAAO,GAAiB,QAAQ,EAChC,SAAS,GAAiB,UAAU,CAAC,KAAK,EACX;IAC/B,MAAM,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAEnC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACJ,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;QACrE,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACrE,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YAClB,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,QAAQ,CAAC,MAAM,2BAA2B,GAAG,EAAE;aAC9D,CAAC;QACH,CAAC;QAED,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO;YACN,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;SACtF,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO;YACN,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,gCAAgC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SACjE,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,IAAI,CAAC;QACJ,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACnE,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1E,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO;YACN,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,mCAAmC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;SAC5F,CAAC;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAAA,CAC7D","sourcesContent":["/**\n * WS15: Registry fetcher — HTTP fetch + atomic cache write.\n *\n * Fetches the registry from the remote URL, validates it, and writes to\n * ~/.cave/agent/registry-cache.json atomically (write-then-rename).\n */\n\nimport { mkdirSync, renameSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { type Registry, validateRegistry } from \"./schema.js\";\n\nexport type FetchChannel = \"stable\" | \"beta\";\n\nconst REGISTRY_URLS: Record<FetchChannel, string> = {\n\tstable: \"https://raw.githubusercontent.com/cave-cli/registry/main/registry.json\",\n\tbeta: \"https://raw.githubusercontent.com/cave-cli/registry/canary/registry.json\",\n};\n\n/** Default timeout for registry fetch (ms) */\nconst FETCH_TIMEOUT_MS = 10_000;\n\nexport type FetchRegistryResult = { ok: true; registry: Registry; cached: boolean } | { ok: false; error: string };\n\n/**\n * Fetch registry JSON from remote URL, validate, and atomically write to\n * cachePath. Returns the validated Registry on success.\n *\n * @param cachePath Absolute path to write the cache file.\n * @param channel \"stable\" (default) or \"beta\".\n * @param fetchImpl Injectable fetch (defaults to globalThis.fetch / Node 18+).\n */\nexport async function fetchAndCacheRegistry(\n\tcachePath: string,\n\tchannel: FetchChannel = \"stable\",\n\tfetchImpl: typeof fetch = globalThis.fetch,\n): Promise<FetchRegistryResult> {\n\tconst url = REGISTRY_URLS[channel];\n\n\tlet raw: unknown;\n\ttry {\n\t\tconst controller = new AbortController();\n\t\tconst timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n\t\tconst response = await fetchImpl(url, { signal: controller.signal });\n\t\tclearTimeout(timer);\n\n\t\tif (!response.ok) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `HTTP ${response.status} fetching registry from ${url}`,\n\t\t\t};\n\t\t}\n\n\t\traw = await response.json();\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `Failed to fetch registry: ${err instanceof Error ? err.message : String(err)}`,\n\t\t};\n\t}\n\n\tconst result = validateRegistry(raw);\n\tif (!result.ok) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `Registry validation failed:\\n${result.errors.join(\"\\n\")}`,\n\t\t};\n\t}\n\n\t// Atomic write: write to tmp then rename\n\ttry {\n\t\tmkdirSync(dirname(cachePath), { recursive: true });\n\t\tconst tmpPath = join(tmpdir(), `cave-registry-${Date.now()}.json`);\n\t\twriteFileSync(tmpPath, JSON.stringify(result.registry, null, 2), \"utf-8\");\n\t\trenameSync(tmpPath, cachePath);\n\t} catch (err) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: `Failed to write registry cache: ${err instanceof Error ? err.message : String(err)}`,\n\t\t};\n\t}\n\n\treturn { ok: true, registry: result.registry, cached: true };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * WS15: Catwalk-style provider/model registry — public API.
3
+ */
4
+ export * from "./fetcher.js";
5
+ export * from "./loader.js";
6
+ export * from "./merger.js";
7
+ export * from "./schema.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC","sourcesContent":["/**\n * WS15: Catwalk-style provider/model registry — public API.\n */\n\nexport * from \"./fetcher.js\";\nexport * from \"./loader.js\";\nexport * from \"./merger.js\";\nexport * from \"./schema.js\";\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * WS15: Catwalk-style provider/model registry — public API.
3
+ */
4
+ export * from "./fetcher.js";
5
+ export * from "./loader.js";
6
+ export * from "./merger.js";
7
+ export * from "./schema.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC","sourcesContent":["/**\n * WS15: Catwalk-style provider/model registry — public API.\n */\n\nexport * from \"./fetcher.js\";\nexport * from \"./loader.js\";\nexport * from \"./merger.js\";\nexport * from \"./schema.js\";\n"]}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * WS15: Registry loader — three-tier fallback chain.
3
+ *
4
+ * Resolution order (first readable + valid wins):
5
+ * 1. ~/.cave/agent/registry.json (user override)
6
+ * 2. ~/.cave/agent/registry-cache.json (fetched cache)
7
+ * 3. <package>/registry/registry.json (bundled fallback)
8
+ */
9
+ import { type Registry } from "./schema.js";
10
+ /** User override path: ~/.cave/agent/registry.json */
11
+ export declare function getUserOverridePath(configDir?: string): string;
12
+ /** Fetched cache path: ~/.cave/agent/registry-cache.json */
13
+ export declare function getCachePath(configDir?: string): string;
14
+ /**
15
+ * Bundled fallback path: <package-root>/registry/registry.json
16
+ *
17
+ * Works from both src/ (ts-node / tsx) and dist/ (compiled) since the
18
+ * registry/ dir lives at the package root, one or two levels up from
19
+ * src/registry/ or dist/registry/.
20
+ */
21
+ export declare function getBundledRegistryPath(): string;
22
+ export type LoadRegistrySource = "user-override" | "cache" | "bundled";
23
+ export type LoadRegistryResult = {
24
+ ok: true;
25
+ registry: Registry;
26
+ source: LoadRegistrySource;
27
+ } | {
28
+ ok: false;
29
+ error: string;
30
+ tried: string[];
31
+ };
32
+ /**
33
+ * Load the active registry following the three-tier fallback chain.
34
+ *
35
+ * @param configDir Override ~/.cave/agent base dir (useful in tests).
36
+ */
37
+ export declare function loadRegistry(configDir?: string): LoadRegistryResult;
38
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/registry/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAAE,KAAK,QAAQ,EAAoB,MAAM,aAAa,CAAC;AAS9D,sDAAsD;AACtD,wBAAgB,mBAAmB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAY/C;AAMD,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvE,MAAM,MAAM,kBAAkB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,GAC5D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAMjD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAqCnE","sourcesContent":["/**\n * WS15: Registry loader — three-tier fallback chain.\n *\n * Resolution order (first readable + valid wins):\n * 1. ~/.cave/agent/registry.json (user override)\n * 2. ~/.cave/agent/registry-cache.json (fetched cache)\n * 3. <package>/registry/registry.json (bundled fallback)\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type Registry, validateRegistry } from \"./schema.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/** User override path: ~/.cave/agent/registry.json */\nexport function getUserOverridePath(configDir?: string): string {\n\tconst base = configDir ?? join(homedir(), \".cave\", \"agent\");\n\treturn join(base, \"registry.json\");\n}\n\n/** Fetched cache path: ~/.cave/agent/registry-cache.json */\nexport function getCachePath(configDir?: string): string {\n\tconst base = configDir ?? join(homedir(), \".cave\", \"agent\");\n\treturn join(base, \"registry-cache.json\");\n}\n\n/**\n * Bundled fallback path: <package-root>/registry/registry.json\n *\n * Works from both src/ (ts-node / tsx) and dist/ (compiled) since the\n * registry/ dir lives at the package root, one or two levels up from\n * src/registry/ or dist/registry/.\n */\nexport function getBundledRegistryPath(): string {\n\t// __dirname is:\n\t// src: packages/ai/src/registry (4 levels up = repo root)\n\t// dist: packages/ai/dist/registry (4 levels up = repo root)\n\t// Structure: .../registry -> .../src|dist -> packages/ai -> packages -> repo-root\n\tconst repoRoot = join(__dirname, \"..\", \"..\", \"..\", \"..\");\n\tconst repoPath = join(repoRoot, \"registry\", \"registry.json\");\n\tif (existsSync(repoPath)) return repoPath;\n\n\t// Secondary fallback: probe 3 levels up (edge case for flattened dist)\n\tconst altRoot = join(__dirname, \"..\", \"..\", \"..\");\n\treturn join(altRoot, \"..\", \"registry\", \"registry.json\");\n}\n\n// ---------------------------------------------------------------------------\n// Load result type\n// ---------------------------------------------------------------------------\n\nexport type LoadRegistrySource = \"user-override\" | \"cache\" | \"bundled\";\n\nexport type LoadRegistryResult =\n\t| { ok: true; registry: Registry; source: LoadRegistrySource }\n\t| { ok: false; error: string; tried: string[] };\n\n// ---------------------------------------------------------------------------\n// Core loader\n// ---------------------------------------------------------------------------\n\n/**\n * Load the active registry following the three-tier fallback chain.\n *\n * @param configDir Override ~/.cave/agent base dir (useful in tests).\n */\nexport function loadRegistry(configDir?: string): LoadRegistryResult {\n\tconst candidates: Array<{ source: LoadRegistrySource; path: string }> = [\n\t\t{ source: \"user-override\", path: getUserOverridePath(configDir) },\n\t\t{ source: \"cache\", path: getCachePath(configDir) },\n\t\t{ source: \"bundled\", path: getBundledRegistryPath() },\n\t];\n\n\tconst tried: string[] = [];\n\n\tfor (const { source, path } of candidates) {\n\t\tif (!existsSync(path)) {\n\t\t\ttried.push(`${source}: ${path} (not found)`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet raw: unknown;\n\t\ttry {\n\t\t\traw = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\t} catch (err) {\n\t\t\ttried.push(`${source}: ${path} (parse error: ${err instanceof Error ? err.message : String(err)})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst result = validateRegistry(raw);\n\t\tif (!result.ok) {\n\t\t\ttried.push(`${source}: ${path} (invalid: ${result.errors.join(\"; \")})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn { ok: true, registry: result.registry, source };\n\t}\n\n\treturn {\n\t\tok: false,\n\t\terror: \"No valid registry found in any location\",\n\t\ttried,\n\t};\n}\n"]}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * WS15: Registry loader — three-tier fallback chain.
3
+ *
4
+ * Resolution order (first readable + valid wins):
5
+ * 1. ~/.cave/agent/registry.json (user override)
6
+ * 2. ~/.cave/agent/registry-cache.json (fetched cache)
7
+ * 3. <package>/registry/registry.json (bundled fallback)
8
+ */
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { validateRegistry } from "./schema.js";
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ // ---------------------------------------------------------------------------
17
+ // Path helpers
18
+ // ---------------------------------------------------------------------------
19
+ /** User override path: ~/.cave/agent/registry.json */
20
+ export function getUserOverridePath(configDir) {
21
+ const base = configDir ?? join(homedir(), ".cave", "agent");
22
+ return join(base, "registry.json");
23
+ }
24
+ /** Fetched cache path: ~/.cave/agent/registry-cache.json */
25
+ export function getCachePath(configDir) {
26
+ const base = configDir ?? join(homedir(), ".cave", "agent");
27
+ return join(base, "registry-cache.json");
28
+ }
29
+ /**
30
+ * Bundled fallback path: <package-root>/registry/registry.json
31
+ *
32
+ * Works from both src/ (ts-node / tsx) and dist/ (compiled) since the
33
+ * registry/ dir lives at the package root, one or two levels up from
34
+ * src/registry/ or dist/registry/.
35
+ */
36
+ export function getBundledRegistryPath() {
37
+ // __dirname is:
38
+ // src: packages/ai/src/registry (4 levels up = repo root)
39
+ // dist: packages/ai/dist/registry (4 levels up = repo root)
40
+ // Structure: .../registry -> .../src|dist -> packages/ai -> packages -> repo-root
41
+ const repoRoot = join(__dirname, "..", "..", "..", "..");
42
+ const repoPath = join(repoRoot, "registry", "registry.json");
43
+ if (existsSync(repoPath))
44
+ return repoPath;
45
+ // Secondary fallback: probe 3 levels up (edge case for flattened dist)
46
+ const altRoot = join(__dirname, "..", "..", "..");
47
+ return join(altRoot, "..", "registry", "registry.json");
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Core loader
51
+ // ---------------------------------------------------------------------------
52
+ /**
53
+ * Load the active registry following the three-tier fallback chain.
54
+ *
55
+ * @param configDir Override ~/.cave/agent base dir (useful in tests).
56
+ */
57
+ export function loadRegistry(configDir) {
58
+ const candidates = [
59
+ { source: "user-override", path: getUserOverridePath(configDir) },
60
+ { source: "cache", path: getCachePath(configDir) },
61
+ { source: "bundled", path: getBundledRegistryPath() },
62
+ ];
63
+ const tried = [];
64
+ for (const { source, path } of candidates) {
65
+ if (!existsSync(path)) {
66
+ tried.push(`${source}: ${path} (not found)`);
67
+ continue;
68
+ }
69
+ let raw;
70
+ try {
71
+ raw = JSON.parse(readFileSync(path, "utf-8"));
72
+ }
73
+ catch (err) {
74
+ tried.push(`${source}: ${path} (parse error: ${err instanceof Error ? err.message : String(err)})`);
75
+ continue;
76
+ }
77
+ const result = validateRegistry(raw);
78
+ if (!result.ok) {
79
+ tried.push(`${source}: ${path} (invalid: ${result.errors.join("; ")})`);
80
+ continue;
81
+ }
82
+ return { ok: true, registry: result.registry, source };
83
+ }
84
+ return {
85
+ ok: false,
86
+ error: "No valid registry found in any location",
87
+ tried,
88
+ };
89
+ }
90
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/registry/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAiB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,sDAAsD;AACtD,MAAM,UAAU,mBAAmB,CAAC,SAAkB,EAAU;IAC/D,MAAM,IAAI,GAAG,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;AAAA,CACnC;AAED,4DAA4D;AAC5D,MAAM,UAAU,YAAY,CAAC,SAAkB,EAAU;IACxD,MAAM,IAAI,GAAG,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;AAAA,CACzC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,GAAW;IAChD,gBAAgB;IAChB,gEAAgE;IAChE,gEAAgE;IAChE,kFAAkF;IAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAC7D,IAAI,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAE1C,uEAAuE;IACvE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;AAAA,CACxD;AAYD,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,SAAkB,EAAsB;IACpE,MAAM,UAAU,GAAwD;QACvE,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,mBAAmB,CAAC,SAAS,CAAC,EAAE;QACjE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE;QAClD,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,sBAAsB,EAAE,EAAE;KACrD,CAAC;IAEF,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,CAAC;QAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,cAAc,CAAC,CAAC;YAC7C,SAAS;QACV,CAAC;QAED,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACJ,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,kBAAkB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpG,SAAS;QACV,CAAC;QAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,cAAc,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACxE,SAAS;QACV,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACxD,CAAC;IAED,OAAO;QACN,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,yCAAyC;QAChD,KAAK;KACL,CAAC;AAAA,CACF","sourcesContent":["/**\n * WS15: Registry loader — three-tier fallback chain.\n *\n * Resolution order (first readable + valid wins):\n * 1. ~/.cave/agent/registry.json (user override)\n * 2. ~/.cave/agent/registry-cache.json (fetched cache)\n * 3. <package>/registry/registry.json (bundled fallback)\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type Registry, validateRegistry } from \"./schema.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/** User override path: ~/.cave/agent/registry.json */\nexport function getUserOverridePath(configDir?: string): string {\n\tconst base = configDir ?? join(homedir(), \".cave\", \"agent\");\n\treturn join(base, \"registry.json\");\n}\n\n/** Fetched cache path: ~/.cave/agent/registry-cache.json */\nexport function getCachePath(configDir?: string): string {\n\tconst base = configDir ?? join(homedir(), \".cave\", \"agent\");\n\treturn join(base, \"registry-cache.json\");\n}\n\n/**\n * Bundled fallback path: <package-root>/registry/registry.json\n *\n * Works from both src/ (ts-node / tsx) and dist/ (compiled) since the\n * registry/ dir lives at the package root, one or two levels up from\n * src/registry/ or dist/registry/.\n */\nexport function getBundledRegistryPath(): string {\n\t// __dirname is:\n\t// src: packages/ai/src/registry (4 levels up = repo root)\n\t// dist: packages/ai/dist/registry (4 levels up = repo root)\n\t// Structure: .../registry -> .../src|dist -> packages/ai -> packages -> repo-root\n\tconst repoRoot = join(__dirname, \"..\", \"..\", \"..\", \"..\");\n\tconst repoPath = join(repoRoot, \"registry\", \"registry.json\");\n\tif (existsSync(repoPath)) return repoPath;\n\n\t// Secondary fallback: probe 3 levels up (edge case for flattened dist)\n\tconst altRoot = join(__dirname, \"..\", \"..\", \"..\");\n\treturn join(altRoot, \"..\", \"registry\", \"registry.json\");\n}\n\n// ---------------------------------------------------------------------------\n// Load result type\n// ---------------------------------------------------------------------------\n\nexport type LoadRegistrySource = \"user-override\" | \"cache\" | \"bundled\";\n\nexport type LoadRegistryResult =\n\t| { ok: true; registry: Registry; source: LoadRegistrySource }\n\t| { ok: false; error: string; tried: string[] };\n\n// ---------------------------------------------------------------------------\n// Core loader\n// ---------------------------------------------------------------------------\n\n/**\n * Load the active registry following the three-tier fallback chain.\n *\n * @param configDir Override ~/.cave/agent base dir (useful in tests).\n */\nexport function loadRegistry(configDir?: string): LoadRegistryResult {\n\tconst candidates: Array<{ source: LoadRegistrySource; path: string }> = [\n\t\t{ source: \"user-override\", path: getUserOverridePath(configDir) },\n\t\t{ source: \"cache\", path: getCachePath(configDir) },\n\t\t{ source: \"bundled\", path: getBundledRegistryPath() },\n\t];\n\n\tconst tried: string[] = [];\n\n\tfor (const { source, path } of candidates) {\n\t\tif (!existsSync(path)) {\n\t\t\ttried.push(`${source}: ${path} (not found)`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet raw: unknown;\n\t\ttry {\n\t\t\traw = JSON.parse(readFileSync(path, \"utf-8\"));\n\t\t} catch (err) {\n\t\t\ttried.push(`${source}: ${path} (parse error: ${err instanceof Error ? err.message : String(err)})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst result = validateRegistry(raw);\n\t\tif (!result.ok) {\n\t\t\ttried.push(`${source}: ${path} (invalid: ${result.errors.join(\"; \")})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn { ok: true, registry: result.registry, source };\n\t}\n\n\treturn {\n\t\tok: false,\n\t\terror: \"No valid registry found in any location\",\n\t\ttried,\n\t};\n}\n"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * WS15: Registry merger — convert RegistryProvider entries into the
3
+ * @zhachory1/mewrite-ai Model<Api> shape so they can be injected into ModelRegistry.
4
+ *
5
+ * Compatibility constraint: existing model-registry.ts consumers keep
6
+ * working. The merger produces models in the same shape as getModels()
7
+ * outputs — existing code paths are unchanged.
8
+ */
9
+ import type { Api, Model } from "../types.js";
10
+ import type { Registry, RegistryModel, RegistryProvider } from "./schema.js";
11
+ /**
12
+ * Convert a single RegistryModel + its parent RegistryProvider into
13
+ * the Model<Api> shape used by @zhachory1/mewrite-ai's internal registry.
14
+ */
15
+ export declare function registryModelToModel(provider: RegistryProvider, model: RegistryModel): Model<Api> | undefined;
16
+ /**
17
+ * Convert all models from a Registry into Model<Api>[] entries.
18
+ * Unknown provider kinds are silently skipped.
19
+ */
20
+ export declare function registryToModels(registry: Registry): Model<Api>[];
21
+ //# sourceMappingURL=merger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merger.d.ts","sourceRoot":"","sources":["../../src/registry/merger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA0C7E;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAyB7G;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CASjE","sourcesContent":["/**\n * WS15: Registry merger — convert RegistryProvider entries into the\n * @zhachory1/mewrite-ai Model<Api> shape so they can be injected into ModelRegistry.\n *\n * Compatibility constraint: existing model-registry.ts consumers keep\n * working. The merger produces models in the same shape as getModels()\n * outputs — existing code paths are unchanged.\n */\n\nimport type { Api, Model } from \"../types.js\";\nimport type { Registry, RegistryModel, RegistryProvider } from \"./schema.js\";\n\n// ---------------------------------------------------------------------------\n// Kind → Api mapping\n// ---------------------------------------------------------------------------\n\n/** Map registry provider kinds to the @zhachory1/mewrite-ai Api string */\nfunction kindToApi(kind: RegistryProvider[\"kind\"]): Api | undefined {\n\tswitch (kind) {\n\t\tcase \"anthropic\":\n\t\t\treturn \"anthropic\";\n\t\tcase \"openai\":\n\t\t\treturn \"openai-responses\";\n\t\tcase \"google\":\n\t\t\treturn \"google\";\n\t\tcase \"openrouter\":\n\t\t\treturn \"openai-completions\";\n\t\tcase \"mistral\":\n\t\t\treturn \"openai-completions\";\n\t\tcase \"bedrock\":\n\t\t\treturn \"bedrock-converse-stream\";\n\t\tcase \"gemini-cli\":\n\t\t\treturn \"google\";\n\t\tcase \"vertex\":\n\t\t\treturn \"google-vertex\";\n\t\tcase \"xai\":\n\t\t\treturn \"openai-completions\";\n\t\tcase \"groq\":\n\t\t\treturn \"openai-completions\";\n\t\tcase \"cerebras\":\n\t\t\treturn \"openai-completions\";\n\t\tcase \"other\":\n\t\t\treturn \"openai-completions\";\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Converter\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a single RegistryModel + its parent RegistryProvider into\n * the Model<Api> shape used by @zhachory1/mewrite-ai's internal registry.\n */\nexport function registryModelToModel(provider: RegistryProvider, model: RegistryModel): Model<Api> | undefined {\n\tconst api = kindToApi(provider.kind);\n\tif (!api) return undefined;\n\n\tconst caps = model.capabilities ?? [];\n\n\treturn {\n\t\tid: model.id,\n\t\tname: model.displayName,\n\t\tapi,\n\t\tprovider: provider.id,\n\t\tbaseUrl: provider.baseUrl ?? \"\",\n\t\treasoning: caps.includes(\"reasoning\"),\n\t\tinput: caps.includes(\"vision\") ? [\"text\", \"image\"] : [\"text\"],\n\t\tcost: {\n\t\t\tinput: model.inputCostPerMtok ?? 0,\n\t\t\toutput: model.outputCostPerMtok ?? 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t},\n\t\tcontextWindow: model.contextWindow,\n\t\tmaxTokens: model.maxOutputTokens ?? 16384,\n\t\theaders: undefined,\n\t\tcompat: undefined,\n\t} as Model<Api>;\n}\n\n/**\n * Convert all models from a Registry into Model<Api>[] entries.\n * Unknown provider kinds are silently skipped.\n */\nexport function registryToModels(registry: Registry): Model<Api>[] {\n\tconst result: Model<Api>[] = [];\n\tfor (const provider of registry.providers) {\n\t\tfor (const model of provider.models) {\n\t\t\tconst m = registryModelToModel(provider, model);\n\t\t\tif (m) result.push(m);\n\t\t}\n\t}\n\treturn result;\n}\n"]}