@ambitresearch/genie 1.3.1

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 (301) hide show
  1. package/README.md +15 -0
  2. package/dist/auth/bearer.d.ts +49 -0
  3. package/dist/auth/bearer.d.ts.map +1 -0
  4. package/dist/auth/bearer.js +159 -0
  5. package/dist/auth/bearer.js.map +1 -0
  6. package/dist/auth/oauth/authorize.d.ts +19 -0
  7. package/dist/auth/oauth/authorize.d.ts.map +1 -0
  8. package/dist/auth/oauth/authorize.js +111 -0
  9. package/dist/auth/oauth/authorize.js.map +1 -0
  10. package/dist/auth/oauth/config.d.ts +10 -0
  11. package/dist/auth/oauth/config.d.ts.map +1 -0
  12. package/dist/auth/oauth/config.js +16 -0
  13. package/dist/auth/oauth/config.js.map +1 -0
  14. package/dist/auth/oauth/dcr.d.ts +15 -0
  15. package/dist/auth/oauth/dcr.d.ts.map +1 -0
  16. package/dist/auth/oauth/dcr.js +38 -0
  17. package/dist/auth/oauth/dcr.js.map +1 -0
  18. package/dist/auth/oauth/jwt.d.ts +13 -0
  19. package/dist/auth/oauth/jwt.d.ts.map +1 -0
  20. package/dist/auth/oauth/jwt.js +46 -0
  21. package/dist/auth/oauth/jwt.js.map +1 -0
  22. package/dist/auth/oauth/metadata.d.ts +9 -0
  23. package/dist/auth/oauth/metadata.d.ts.map +1 -0
  24. package/dist/auth/oauth/metadata.js +19 -0
  25. package/dist/auth/oauth/metadata.js.map +1 -0
  26. package/dist/auth/oauth/router.d.ts +32 -0
  27. package/dist/auth/oauth/router.d.ts.map +1 -0
  28. package/dist/auth/oauth/router.js +159 -0
  29. package/dist/auth/oauth/router.js.map +1 -0
  30. package/dist/auth/oauth/store.d.ts +55 -0
  31. package/dist/auth/oauth/store.d.ts.map +1 -0
  32. package/dist/auth/oauth/store.js +63 -0
  33. package/dist/auth/oauth/store.js.map +1 -0
  34. package/dist/auth/oauth/token.d.ts +21 -0
  35. package/dist/auth/oauth/token.d.ts.map +1 -0
  36. package/dist/auth/oauth/token.js +77 -0
  37. package/dist/auth/oauth/token.js.map +1 -0
  38. package/dist/auth/oidc/group-policy.d.ts +43 -0
  39. package/dist/auth/oidc/group-policy.d.ts.map +1 -0
  40. package/dist/auth/oidc/group-policy.js +54 -0
  41. package/dist/auth/oidc/group-policy.js.map +1 -0
  42. package/dist/auth/oidc/verifier.d.ts +42 -0
  43. package/dist/auth/oidc/verifier.d.ts.map +1 -0
  44. package/dist/auth/oidc/verifier.js +145 -0
  45. package/dist/auth/oidc/verifier.js.map +1 -0
  46. package/dist/auth/token-cli.d.ts +9 -0
  47. package/dist/auth/token-cli.d.ts.map +1 -0
  48. package/dist/auth/token-cli.js +98 -0
  49. package/dist/auth/token-cli.js.map +1 -0
  50. package/dist/cli.d.ts +3 -0
  51. package/dist/cli.d.ts.map +1 -0
  52. package/dist/cli.js +149 -0
  53. package/dist/cli.js.map +1 -0
  54. package/dist/config/redact.d.ts +20 -0
  55. package/dist/config/redact.d.ts.map +1 -0
  56. package/dist/config/redact.js +77 -0
  57. package/dist/config/redact.js.map +1 -0
  58. package/dist/config/secrets.d.ts +100 -0
  59. package/dist/config/secrets.d.ts.map +1 -0
  60. package/dist/config/secrets.js +130 -0
  61. package/dist/config/secrets.js.map +1 -0
  62. package/dist/framework/html.d.ts +103 -0
  63. package/dist/framework/html.d.ts.map +1 -0
  64. package/dist/framework/html.js +271 -0
  65. package/dist/framework/html.js.map +1 -0
  66. package/dist/framework/interface.d.ts +130 -0
  67. package/dist/framework/interface.d.ts.map +1 -0
  68. package/dist/framework/interface.js +112 -0
  69. package/dist/framework/interface.js.map +1 -0
  70. package/dist/framework/react.d.ts +133 -0
  71. package/dist/framework/react.d.ts.map +1 -0
  72. package/dist/framework/react.js +243 -0
  73. package/dist/framework/react.js.map +1 -0
  74. package/dist/framework/vue.d.ts +99 -0
  75. package/dist/framework/vue.d.ts.map +1 -0
  76. package/dist/framework/vue.js +375 -0
  77. package/dist/framework/vue.js.map +1 -0
  78. package/dist/index.d.ts +3 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +3 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/llm/client.d.ts +81 -0
  83. package/dist/llm/client.d.ts.map +1 -0
  84. package/dist/llm/client.js +141 -0
  85. package/dist/llm/client.js.map +1 -0
  86. package/dist/llm/component-response.d.ts +111 -0
  87. package/dist/llm/component-response.d.ts.map +1 -0
  88. package/dist/llm/component-response.js +167 -0
  89. package/dist/llm/component-response.js.map +1 -0
  90. package/dist/llm/generated-files.d.ts +10 -0
  91. package/dist/llm/generated-files.d.ts.map +1 -0
  92. package/dist/llm/generated-files.js +26 -0
  93. package/dist/llm/generated-files.js.map +1 -0
  94. package/dist/llm/prompts/CHANGELOG.md +70 -0
  95. package/dist/llm/prompts/generate-component.system.md +99 -0
  96. package/dist/llm/prompts/refine-component.system.md +112 -0
  97. package/dist/llm/prompts.d.ts +66 -0
  98. package/dist/llm/prompts.d.ts.map +1 -0
  99. package/dist/llm/prompts.js +78 -0
  100. package/dist/llm/prompts.js.map +1 -0
  101. package/dist/llm/retry.d.ts +183 -0
  102. package/dist/llm/retry.d.ts.map +1 -0
  103. package/dist/llm/retry.js +475 -0
  104. package/dist/llm/retry.js.map +1 -0
  105. package/dist/llm/schema.d.ts +129 -0
  106. package/dist/llm/schema.d.ts.map +1 -0
  107. package/dist/llm/schema.js +147 -0
  108. package/dist/llm/schema.js.map +1 -0
  109. package/dist/llm/validate.d.ts +107 -0
  110. package/dist/llm/validate.d.ts.map +1 -0
  111. package/dist/llm/validate.js +174 -0
  112. package/dist/llm/validate.js.map +1 -0
  113. package/dist/manifest/compiler.d.ts +112 -0
  114. package/dist/manifest/compiler.d.ts.map +1 -0
  115. package/dist/manifest/compiler.js +306 -0
  116. package/dist/manifest/compiler.js.map +1 -0
  117. package/dist/manifest/ensure.d.ts +46 -0
  118. package/dist/manifest/ensure.d.ts.map +1 -0
  119. package/dist/manifest/ensure.js +49 -0
  120. package/dist/manifest/ensure.js.map +1 -0
  121. package/dist/manifest/index.d.ts +13 -0
  122. package/dist/manifest/index.d.ts.map +1 -0
  123. package/dist/manifest/index.js +13 -0
  124. package/dist/manifest/index.js.map +1 -0
  125. package/dist/middleware/plan-guard.d.ts +167 -0
  126. package/dist/middleware/plan-guard.d.ts.map +1 -0
  127. package/dist/middleware/plan-guard.js +190 -0
  128. package/dist/middleware/plan-guard.js.map +1 -0
  129. package/dist/plans/index.d.ts +71 -0
  130. package/dist/plans/index.d.ts.map +1 -0
  131. package/dist/plans/index.js +252 -0
  132. package/dist/plans/index.js.map +1 -0
  133. package/dist/schemas/component.schema.json +114 -0
  134. package/dist/server.d.ts +96 -0
  135. package/dist/server.d.ts.map +1 -0
  136. package/dist/server.js +258 -0
  137. package/dist/server.js.map +1 -0
  138. package/dist/store/empty-manifest.d.ts +58 -0
  139. package/dist/store/empty-manifest.d.ts.map +1 -0
  140. package/dist/store/empty-manifest.js +59 -0
  141. package/dist/store/empty-manifest.js.map +1 -0
  142. package/dist/store/git-host.d.ts +120 -0
  143. package/dist/store/git-host.d.ts.map +1 -0
  144. package/dist/store/git-host.js +796 -0
  145. package/dist/store/git-host.js.map +1 -0
  146. package/dist/store/index.d.ts +5 -0
  147. package/dist/store/index.d.ts.map +1 -0
  148. package/dist/store/index.js +4 -0
  149. package/dist/store/index.js.map +1 -0
  150. package/dist/store/interface.d.ts +322 -0
  151. package/dist/store/interface.d.ts.map +1 -0
  152. package/dist/store/interface.js +100 -0
  153. package/dist/store/interface.js.map +1 -0
  154. package/dist/store/kit-files.d.ts +88 -0
  155. package/dist/store/kit-files.d.ts.map +1 -0
  156. package/dist/store/kit-files.js +199 -0
  157. package/dist/store/kit-files.js.map +1 -0
  158. package/dist/store/local.d.ts +70 -0
  159. package/dist/store/local.d.ts.map +1 -0
  160. package/dist/store/local.js +651 -0
  161. package/dist/store/local.js.map +1 -0
  162. package/dist/store/manifest.d.ts +44 -0
  163. package/dist/store/manifest.d.ts.map +1 -0
  164. package/dist/store/manifest.js +116 -0
  165. package/dist/store/manifest.js.map +1 -0
  166. package/dist/store/viewer-assets.d.ts +20 -0
  167. package/dist/store/viewer-assets.d.ts.map +1 -0
  168. package/dist/store/viewer-assets.js +118 -0
  169. package/dist/store/viewer-assets.js.map +1 -0
  170. package/dist/sync/anchor.d.ts +125 -0
  171. package/dist/sync/anchor.d.ts.map +1 -0
  172. package/dist/sync/anchor.js +210 -0
  173. package/dist/sync/anchor.js.map +1 -0
  174. package/dist/sync/index.d.ts +15 -0
  175. package/dist/sync/index.d.ts.map +1 -0
  176. package/dist/sync/index.js +15 -0
  177. package/dist/sync/index.js.map +1 -0
  178. package/dist/sync/orchestrator.d.ts +160 -0
  179. package/dist/sync/orchestrator.d.ts.map +1 -0
  180. package/dist/sync/orchestrator.js +288 -0
  181. package/dist/sync/orchestrator.js.map +1 -0
  182. package/dist/tools/bind_kit.d.ts +29 -0
  183. package/dist/tools/bind_kit.d.ts.map +1 -0
  184. package/dist/tools/bind_kit.js +70 -0
  185. package/dist/tools/bind_kit.js.map +1 -0
  186. package/dist/tools/conjure.d.ts +141 -0
  187. package/dist/tools/conjure.d.ts.map +1 -0
  188. package/dist/tools/conjure.js +655 -0
  189. package/dist/tools/conjure.js.map +1 -0
  190. package/dist/tools/conjure_screen.d.ts +174 -0
  191. package/dist/tools/conjure_screen.d.ts.map +1 -0
  192. package/dist/tools/conjure_screen.js +458 -0
  193. package/dist/tools/conjure_screen.js.map +1 -0
  194. package/dist/tools/create_kit.d.ts +32 -0
  195. package/dist/tools/create_kit.d.ts.map +1 -0
  196. package/dist/tools/create_kit.js +131 -0
  197. package/dist/tools/create_kit.js.map +1 -0
  198. package/dist/tools/create_project.d.ts +267 -0
  199. package/dist/tools/create_project.d.ts.map +1 -0
  200. package/dist/tools/create_project.js +546 -0
  201. package/dist/tools/create_project.js.map +1 -0
  202. package/dist/tools/delete_files.d.ts +93 -0
  203. package/dist/tools/delete_files.d.ts.map +1 -0
  204. package/dist/tools/delete_files.js +276 -0
  205. package/dist/tools/delete_files.js.map +1 -0
  206. package/dist/tools/delete_project.d.ts +43 -0
  207. package/dist/tools/delete_project.d.ts.map +1 -0
  208. package/dist/tools/delete_project.js +118 -0
  209. package/dist/tools/delete_project.js.map +1 -0
  210. package/dist/tools/get_kit.d.ts +28 -0
  211. package/dist/tools/get_kit.d.ts.map +1 -0
  212. package/dist/tools/get_kit.js +95 -0
  213. package/dist/tools/get_kit.js.map +1 -0
  214. package/dist/tools/get_project.d.ts +37 -0
  215. package/dist/tools/get_project.d.ts.map +1 -0
  216. package/dist/tools/get_project.js +71 -0
  217. package/dist/tools/get_project.js.map +1 -0
  218. package/dist/tools/list_components.d.ts +42 -0
  219. package/dist/tools/list_components.d.ts.map +1 -0
  220. package/dist/tools/list_components.js +125 -0
  221. package/dist/tools/list_components.js.map +1 -0
  222. package/dist/tools/list_files.d.ts +32 -0
  223. package/dist/tools/list_files.d.ts.map +1 -0
  224. package/dist/tools/list_files.js +109 -0
  225. package/dist/tools/list_files.js.map +1 -0
  226. package/dist/tools/list_kits.d.ts +14 -0
  227. package/dist/tools/list_kits.d.ts.map +1 -0
  228. package/dist/tools/list_kits.js +41 -0
  229. package/dist/tools/list_kits.js.map +1 -0
  230. package/dist/tools/list_projects.d.ts +23 -0
  231. package/dist/tools/list_projects.d.ts.map +1 -0
  232. package/dist/tools/list_projects.js +66 -0
  233. package/dist/tools/list_projects.js.map +1 -0
  234. package/dist/tools/plan.d.ts +16 -0
  235. package/dist/tools/plan.d.ts.map +1 -0
  236. package/dist/tools/plan.js +146 -0
  237. package/dist/tools/plan.js.map +1 -0
  238. package/dist/tools/preview.d.ts +289 -0
  239. package/dist/tools/preview.d.ts.map +1 -0
  240. package/dist/tools/preview.js +672 -0
  241. package/dist/tools/preview.js.map +1 -0
  242. package/dist/tools/read_file.d.ts +40 -0
  243. package/dist/tools/read_file.d.ts.map +1 -0
  244. package/dist/tools/read_file.js +114 -0
  245. package/dist/tools/read_file.js.map +1 -0
  246. package/dist/tools/refine.d.ts +220 -0
  247. package/dist/tools/refine.d.ts.map +1 -0
  248. package/dist/tools/refine.js +501 -0
  249. package/dist/tools/refine.js.map +1 -0
  250. package/dist/tools/validate.d.ts +74 -0
  251. package/dist/tools/validate.d.ts.map +1 -0
  252. package/dist/tools/validate.js +224 -0
  253. package/dist/tools/validate.js.map +1 -0
  254. package/dist/tools/write_files.d.ts +158 -0
  255. package/dist/tools/write_files.d.ts.map +1 -0
  256. package/dist/tools/write_files.js +541 -0
  257. package/dist/tools/write_files.js.map +1 -0
  258. package/dist/transport.d.ts +77 -0
  259. package/dist/transport.d.ts.map +1 -0
  260. package/dist/transport.js +448 -0
  261. package/dist/transport.js.map +1 -0
  262. package/dist/ui/card-asset-broker.d.ts +42 -0
  263. package/dist/ui/card-asset-broker.d.ts.map +1 -0
  264. package/dist/ui/card-asset-broker.js +479 -0
  265. package/dist/ui/card-asset-broker.js.map +1 -0
  266. package/dist/ui/grid-resource.d.ts +252 -0
  267. package/dist/ui/grid-resource.d.ts.map +1 -0
  268. package/dist/ui/grid-resource.js +680 -0
  269. package/dist/ui/grid-resource.js.map +1 -0
  270. package/dist/ui/viewer-static/index.html +95 -0
  271. package/dist/ui/viewer-static/viewer.css +451 -0
  272. package/dist/ui/viewer-static/viewer.js +1405 -0
  273. package/dist/validate/full-scan.d.ts +87 -0
  274. package/dist/validate/full-scan.d.ts.map +1 -0
  275. package/dist/validate/full-scan.js +299 -0
  276. package/dist/validate/full-scan.js.map +1 -0
  277. package/dist/validate/index.d.ts +15 -0
  278. package/dist/validate/index.d.ts.map +1 -0
  279. package/dist/validate/index.js +18 -0
  280. package/dist/validate/index.js.map +1 -0
  281. package/dist/validate/marker.d.ts +89 -0
  282. package/dist/validate/marker.d.ts.map +1 -0
  283. package/dist/validate/marker.js +87 -0
  284. package/dist/validate/marker.js.map +1 -0
  285. package/dist/validate/phash.d.ts +135 -0
  286. package/dist/validate/phash.d.ts.map +1 -0
  287. package/dist/validate/phash.js +307 -0
  288. package/dist/validate/phash.js.map +1 -0
  289. package/dist/validate/render.d.ts +38 -0
  290. package/dist/validate/render.d.ts.map +1 -0
  291. package/dist/validate/render.js +155 -0
  292. package/dist/validate/render.js.map +1 -0
  293. package/dist/watch/index.d.ts +12 -0
  294. package/dist/watch/index.d.ts.map +1 -0
  295. package/dist/watch/index.js +12 -0
  296. package/dist/watch/index.js.map +1 -0
  297. package/dist/watch/watcher.d.ts +65 -0
  298. package/dist/watch/watcher.d.ts.map +1 -0
  299. package/dist/watch/watcher.js +238 -0
  300. package/dist/watch/watcher.js.map +1 -0
  301. package/package.json +70 -0
@@ -0,0 +1,99 @@
1
+ You are **genie**, a UI-component generator that renders a single component
2
+ against the caller's own **UI kit**. You return one component as a strict JSON
3
+ object — never prose, never a chat reply. Your entire response is consumed by a
4
+ machine (`response_format: json_schema`), so it must be valid against the schema
5
+ the caller supplies and nothing else.
6
+
7
+ # What you produce
8
+
9
+ One component, as a file set plus the metadata needed to card it in a preview
10
+ grid. The caller hands you:
11
+
12
+ - **`kit`** — a description of the target UI kit: its tokens, primitives,
13
+ naming, and house style. Treat this as the source of truth for look and feel.
14
+ Match it. Do not invent a second design language on top of it.
15
+ - **`prompt`** — what the component should be, in natural language.
16
+ - **`group`** — the kit category the component belongs to (kebab-case, e.g.
17
+ `actions`, `inputs`, `feedback`). If the caller does not give you one, pick
18
+ the most fitting kebab-case category yourself.
19
+ - **`framework`** — `react`, `vue`, or `html`. Emit the implementation file in
20
+ this framework. The `.html` preview (below) is always plain HTML/CSS
21
+ regardless of framework.
22
+ - Optionally a **reference image** and/or **reference page** to match.
23
+
24
+ # Output contract
25
+
26
+ Return an object with exactly these top-level keys:
27
+
28
+ - **`componentName`** — PascalCase, 2–64 chars (e.g. `Button`, `PriceCard`).
29
+ - **`group`** — kebab-case, echoing the resolved category.
30
+ - **`files`** — the component's files (1–12). Every path MUST be
31
+ `components/<group>/<ComponentName>/<filename>` — same `<group>` and
32
+ `<ComponentName>` as the fields above. At minimum emit:
33
+ - the framework implementation — `<ComponentName>.tsx` (react),
34
+ `<ComponentName>.vue` (vue), or `<ComponentName>.html` (html);
35
+ - a **`<ComponentName>.html` preview** (required — see below);
36
+ - a `meta.json` with the component's group and viewport.
37
+ You may also add `<ComponentName>.d.ts` (types) and a
38
+ `<ComponentName>.prompt.md` (a short note on what you built and the props).
39
+ - **`manifestEntry`** — `{ viewport: { width, height }, subtitle?, tags? }`.
40
+ `viewport` is the natural preview size of the card in CSS pixels (a button is
41
+ small, e.g. `{ "width": 320, "height": 140 }`; a full card is larger). Keep
42
+ width and height between 1 and 4096. `subtitle` is a one-line description;
43
+ `tags` are a few kebab/lowercase keywords.
44
+
45
+ Set each file's `mimeType` honestly (`text/html`, `text/tsx`, `text/plain`,
46
+ `application/json`, …). File `content` is the full file, 1–65536 chars.
47
+
48
+ # The `.html` preview is the contract surface — get it exactly right
49
+
50
+ Exactly one file must be `components/<group>/<ComponentName>/<ComponentName>.html`
51
+ (the directory name and the filename share the component name). This preview is
52
+ what the grid renders, and it runs under a **strict embedded Content-Security
53
+ Policy: `default-src 'none'`**. That is a hard wall. Inside it:
54
+
55
+ 1. **First line, byte-for-byte:** `<!-- @genie group="<group>" -->` where
56
+ `<group>` is the resolved group. This marker is validated as the literal
57
+ first line — no blank line, no doctype before it, nothing.
58
+ 2. **No external anything.** No `<link>`, no web fonts, no `<script src>`, no
59
+ remote `<img src>`, no CDN, no Google Fonts. `default-src 'none'` blocks all
60
+ of it and the card would render broken.
61
+ 3. **Self-contained.** Inline the CSS in a `<style>` block. Use **system fonts
62
+ only** (`system-ui, -apple-system, Segoe UI, Roboto, sans-serif`, or a
63
+ monospace stack). Embed any imagery as inline SVG or a `data:` URI.
64
+ 4. **Byte-identical everywhere.** The same preview must render the same whether
65
+ loaded from `file://`, `localhost`, or the embedded `genie://` surface — so
66
+ never depend on the origin, absolute URLs, or ambient host state.
67
+ 5. Honor the kit's tokens (colors, spacing, radius, type scale) as literal CSS
68
+ values in the preview, since it cannot import the kit's stylesheet.
69
+
70
+ The framework implementation file (`.tsx`/`.vue`) is the real component a
71
+ developer drops into their app; the `.html` preview is a faithful static render
72
+ of it for the grid. Keep them visually consistent.
73
+
74
+ # House rules
75
+
76
+ - **Match the kit, don't reinvent it.** If the kit names a clay accent, a radius
77
+ scale, a spacing unit — use those exact values.
78
+ - **Be honest about provenance.** Don't claim to import kit files you weren't
79
+ given; reproduce the kit's _look_ from what the caller described.
80
+ - **Accessibility is not optional.** Semantic elements, a visible focus state,
81
+ labelled controls, sufficient contrast, `alt`/`aria` where needed.
82
+ - **One component per call.** Don't emit a whole page or a component library —
83
+ just the single component the prompt asks for, plus its preview and meta.
84
+ - **Determinism over flair.** No random ids, no timestamps, no "generated at"
85
+ noise. The same request should produce a stable result.
86
+
87
+ # If a reference image or page is attached
88
+
89
+ Treat it as the visual target: match its layout, proportions, color, and
90
+ copy where sensible, while still expressing the kit's tokens. The reference
91
+ shows _what_; the kit governs _how_.
92
+
93
+ # On a retry
94
+
95
+ If the caller re-sends your previous attempt with a validation error appended,
96
+ that attempt failed schema validation. Read the error, fix exactly what it
97
+ names (a bad path, a missing `<ComponentName>.html`, a missing `viewport`, a
98
+ name that isn't PascalCase, …), and return a fully corrected object. Do not
99
+ apologize or explain — just return valid JSON.
@@ -0,0 +1,112 @@
1
+ You are **genie**, iterating on a UI component that already exists. The caller
2
+ hands you the component's current source files and an **instruction** describing
3
+ a change ("make the border radius softer", "tighten the padding", "add a
4
+ disabled state"). You return the **complete, updated component** as a strict
5
+ JSON object — never prose, never a chat reply, never a patch or diff. Your
6
+ entire response is consumed by a machine (`response_format: json_schema`), so it
7
+ must be valid against the schema the caller supplies and nothing else.
8
+
9
+ # What you are given
10
+
11
+ - **The current files** — the component's existing source, each as a labelled
12
+ block with its kit-relative path (`components/<group>/<Name>/…`) and full
13
+ content. These are the source of truth for what the component is *today*.
14
+ - **`instruction`** — a natural-language description of the change to make.
15
+ Apply exactly this. Do not redesign the component, rename it, or "improve"
16
+ things the instruction did not ask about.
17
+ - Optionally a **region crop** — a screenshot of a specific rectangle of the
18
+ component's rendered preview, attached as a reference image. When present, the
19
+ instruction is about *that* element/area; focus your edit there and leave the
20
+ rest of the component untouched.
21
+ - Optionally the pixel **coordinates** of that region (in the preview's own
22
+ coordinate space), described in text, for when the crop image itself could not
23
+ be produced.
24
+
25
+ # The cardinal rule: edit, don't rewrite
26
+
27
+ This is a *refinement*. Change only what the instruction requires. Everything
28
+ the instruction does not mention MUST come back byte-for-byte as it was:
29
+
30
+ - Keep the **same `componentName`** and the **same `group`** (and therefore the
31
+ same `components/<group>/<Name>/…` file paths). Renaming is not a refinement.
32
+ - Preserve unrelated markup, classes, props, comments, and whitespace. A caller
33
+ reading the diff between your output and the original should see *only* the
34
+ change they asked for — nothing incidental.
35
+ - Do not drop files. If the component had a `<Name>.tsx`, a `<Name>.html`, and a
36
+ `meta.json`, return all three (updated where the change touches them, verbatim
37
+ where it does not). You may add a file only if the instruction genuinely
38
+ requires one.
39
+ - Keep the framework implementation (`.tsx`/`.vue`/`.html`) and the `.html`
40
+ preview visually consistent after the edit — if you change the radius in one,
41
+ change it in the other.
42
+
43
+ # Output contract
44
+
45
+ Return an object with exactly these top-level keys:
46
+
47
+ - **`componentName`** — PascalCase, echoing the EXISTING component's name
48
+ unchanged.
49
+ - **`group`** — kebab-case, echoing the EXISTING group unchanged.
50
+ - **`files`** — the component's full file set (1–12) after your edit. Every path
51
+ MUST be `components/<group>/<ComponentName>/<filename>` — the same `<group>`
52
+ and `<ComponentName>` as the originals. At minimum return the framework
53
+ implementation, the `<ComponentName>.html` preview, and `meta.json`.
54
+ - **`manifestEntry`** — `{ viewport: { width, height }, subtitle?, tags? }`.
55
+ Carry the original viewport forward unless the instruction changes the
56
+ component's natural size. Keep width and height between 1 and 4096.
57
+
58
+ Set each file's `mimeType` honestly (`text/html`, `text/tsx`, `text/plain`,
59
+ `application/json`, …). File `content` is the full file, 1–65536 chars — the
60
+ WHOLE file after the edit, not a fragment.
61
+
62
+ # The `.html` preview is the contract surface — keep it exact
63
+
64
+ Exactly one file must be `components/<group>/<ComponentName>/<ComponentName>.html`
65
+ (the directory name and the filename share the component name). This preview is
66
+ what the grid renders, under a **strict embedded Content-Security Policy:
67
+ `default-src 'none'`**. That is a hard wall. It was true of the component you
68
+ were given and it must stay true after your edit:
69
+
70
+ 1. **First line, byte-for-byte:** `<!-- @genie group="<group>" -->` where
71
+ `<group>` is the (unchanged) resolved group. This marker is validated as the
72
+ literal first line — no blank line, no doctype before it, nothing.
73
+ 2. **No external anything.** No `<link>`, no web fonts, no `<script src>`, no
74
+ remote `<img src>`, no CDN, no Google Fonts. `default-src 'none'` blocks all
75
+ of it and the card would render broken.
76
+ 3. **Self-contained.** Inline the CSS in a `<style>` block. Use **system fonts
77
+ only** (`system-ui, -apple-system, Segoe UI, Roboto, sans-serif`, or a
78
+ monospace stack). Embed any imagery as inline SVG or a `data:` URI.
79
+ 4. **Byte-identical everywhere.** The same preview must render the same whether
80
+ loaded from `file://`, `localhost`, or the embedded `genie://` surface — so
81
+ never depend on the origin, absolute URLs, or ambient host state.
82
+ 5. Honor the kit's tokens (colors, spacing, radius, type scale) as literal CSS
83
+ values in the preview, since it cannot import the kit's stylesheet.
84
+
85
+ # If a region crop or coordinates are given
86
+
87
+ The instruction is scoped to that rectangle. Identify which element(s) the crop
88
+ covers, apply the change there, and leave the rest of the component exactly as
89
+ it was. The crop is *where*; the instruction is *what*. If only coordinates are
90
+ given (no image), reason about which element sits at those coordinates from the
91
+ preview's own layout.
92
+
93
+ # House rules
94
+
95
+ - **Match the existing kit, don't reinvent it.** Reuse the component's existing
96
+ tokens, class names, and structure. A refinement inherits the look already
97
+ established; it does not introduce a second design language.
98
+ - **Determinism over flair.** No random ids, no timestamps, no "generated at"
99
+ noise. The same source + instruction should produce a stable result.
100
+ - **Accessibility is not optional.** Preserve (and where the edit touches them,
101
+ maintain) semantic elements, visible focus states, labelled controls,
102
+ sufficient contrast, `alt`/`aria` where needed.
103
+ - **One component per call.** Refine the single component you were given — never
104
+ emit a second component or a whole page.
105
+
106
+ # On a retry
107
+
108
+ If the caller re-sends your previous attempt with a validation error appended,
109
+ that attempt failed schema validation. Read the error, fix exactly what it names
110
+ (a bad path, a missing `<ComponentName>.html`, a missing `viewport`, a name that
111
+ isn't PascalCase, a changed `componentName`/`group`, …), and return a fully
112
+ corrected object. Do not apologize or explain — just return valid JSON.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * System-prompt loader + versioning (M2-03 · AC5).
3
+ *
4
+ * `conjure` (and later `refine`, M2-04) must load their system prompt from a
5
+ * versioned `*.system.md` file under `prompts/`, and log the prompt's version
6
+ * on every model call (AC5 / AC10). This module owns both halves: reading the
7
+ * markdown off disk once, and deriving a stable content-address for it.
8
+ *
9
+ * ── Why the git *blob* hash is the version ────────────────────────────────────
10
+ * AC5 says "versioned (commit hash logged on every call)". A literal repo commit
11
+ * hash is the wrong unit here: it changes when *any* file in the repo changes,
12
+ * would be null in a shrink-wrapped npm tarball with no `.git`, and can't be
13
+ * computed at runtime without shelling out to git. What we actually want is a
14
+ * fingerprint of *this prompt's bytes* that (a) changes iff the prompt text
15
+ * changes, (b) is computable offline from the file alone, and (c) is
16
+ * independently verifiable. `git hash-object` is exactly that — the SHA-1 of
17
+ * `"blob <byteLength>\0<content>"` — so `promptVersion` for a given file equals
18
+ * `git hash-object <file>`, letting a reviewer confirm which prompt shipped from
19
+ * a log line alone. The prompt CHANGELOG.md carries the human narrative; this
20
+ * hash is the machine-checkable pin.
21
+ *
22
+ * ── Resolution ────────────────────────────────────────────────────────────────
23
+ * Prompts sit next to this module (`llm/prompts/*.system.md`) and are copied
24
+ * into `dist/llm/prompts/` by the build's copy-prompts step, so resolving
25
+ * relative to `import.meta.url` works identically under `tsx` (src) and `node`
26
+ * (dist). Read once and memoized: the file is immutable for the process's life,
27
+ * and re-reading + re-hashing on every `conjure` call would be pointless I/O on
28
+ * genie's hottest path.
29
+ */
30
+ /** The system prompt for `conjure` (AC5). */
31
+ export declare const GENERATE_COMPONENT_SYSTEM_PROMPT_FILE = "generate-component.system.md";
32
+ /** The system prompt for `refine` (M2-04 · DRO-251). Loaded + versioned by the
33
+ * exact same machinery as the `conjure` prompt — `refine` logs its git blob hash
34
+ * as `promptVersion` on every model call too. */
35
+ export declare const REFINE_COMPONENT_SYSTEM_PROMPT_FILE = "refine-component.system.md";
36
+ /** A loaded prompt: its text and the content-address logged as its version. */
37
+ export interface LoadedPrompt {
38
+ /** The prompt's file name (e.g. `generate-component.system.md`). */
39
+ file: string;
40
+ /** Full markdown text of the prompt. */
41
+ text: string;
42
+ /**
43
+ * The prompt's version — the git blob hash of {@link text}, identical to
44
+ * `git hash-object <file>`. Logged as `promptVersion` on every model call
45
+ * (AC5). Changes iff the prompt's bytes change.
46
+ */
47
+ version: string;
48
+ }
49
+ /**
50
+ * The git blob object id of `content` — `sha1("blob " + byteLength + "\0" +
51
+ * content)`, byte-for-byte what `git hash-object` computes. Exported for direct
52
+ * testing against a known `git hash-object` value.
53
+ */
54
+ export declare function gitBlobHash(content: string): string;
55
+ /**
56
+ * Load a prompt from `llm/prompts/<file>`, returning its text and version.
57
+ * Memoized: the first call reads + hashes; subsequent calls return the cached
58
+ * result. Throws if the file is missing (a packaging bug worth failing loudly on
59
+ * — a prompt-less generation verb has nothing to send the model).
60
+ */
61
+ export declare function loadPrompt(file: string): LoadedPrompt;
62
+ /** Convenience: load the `conjure` system prompt (AC5). */
63
+ export declare function loadGenerateComponentSystemPrompt(): LoadedPrompt;
64
+ /** Convenience: load the `refine` system prompt (M2-04 · DRO-251). */
65
+ export declare function loadRefineComponentSystemPrompt(): LoadedPrompt;
66
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/llm/prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AASH,6CAA6C;AAC7C,eAAO,MAAM,qCAAqC,iCAAiC,CAAC;AAEpF;;iDAEiD;AACjD,eAAO,MAAM,mCAAmC,+BAA+B,CAAC;AAEhF,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMnD;AAKD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAQrD;AAED,2DAA2D;AAC3D,wBAAgB,iCAAiC,IAAI,YAAY,CAEhE;AAED,sEAAsE;AACtE,wBAAgB,+BAA+B,IAAI,YAAY,CAE9D"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * System-prompt loader + versioning (M2-03 · AC5).
3
+ *
4
+ * `conjure` (and later `refine`, M2-04) must load their system prompt from a
5
+ * versioned `*.system.md` file under `prompts/`, and log the prompt's version
6
+ * on every model call (AC5 / AC10). This module owns both halves: reading the
7
+ * markdown off disk once, and deriving a stable content-address for it.
8
+ *
9
+ * ── Why the git *blob* hash is the version ────────────────────────────────────
10
+ * AC5 says "versioned (commit hash logged on every call)". A literal repo commit
11
+ * hash is the wrong unit here: it changes when *any* file in the repo changes,
12
+ * would be null in a shrink-wrapped npm tarball with no `.git`, and can't be
13
+ * computed at runtime without shelling out to git. What we actually want is a
14
+ * fingerprint of *this prompt's bytes* that (a) changes iff the prompt text
15
+ * changes, (b) is computable offline from the file alone, and (c) is
16
+ * independently verifiable. `git hash-object` is exactly that — the SHA-1 of
17
+ * `"blob <byteLength>\0<content>"` — so `promptVersion` for a given file equals
18
+ * `git hash-object <file>`, letting a reviewer confirm which prompt shipped from
19
+ * a log line alone. The prompt CHANGELOG.md carries the human narrative; this
20
+ * hash is the machine-checkable pin.
21
+ *
22
+ * ── Resolution ────────────────────────────────────────────────────────────────
23
+ * Prompts sit next to this module (`llm/prompts/*.system.md`) and are copied
24
+ * into `dist/llm/prompts/` by the build's copy-prompts step, so resolving
25
+ * relative to `import.meta.url` works identically under `tsx` (src) and `node`
26
+ * (dist). Read once and memoized: the file is immutable for the process's life,
27
+ * and re-reading + re-hashing on every `conjure` call would be pointless I/O on
28
+ * genie's hottest path.
29
+ */
30
+ import { readFileSync } from "node:fs";
31
+ import { createHash } from "node:crypto";
32
+ import { dirname, join } from "node:path";
33
+ import { fileURLToPath } from "node:url";
34
+ const PROMPTS_DIR = join(dirname(fileURLToPath(import.meta.url)), "prompts");
35
+ /** The system prompt for `conjure` (AC5). */
36
+ export const GENERATE_COMPONENT_SYSTEM_PROMPT_FILE = "generate-component.system.md";
37
+ /** The system prompt for `refine` (M2-04 · DRO-251). Loaded + versioned by the
38
+ * exact same machinery as the `conjure` prompt — `refine` logs its git blob hash
39
+ * as `promptVersion` on every model call too. */
40
+ export const REFINE_COMPONENT_SYSTEM_PROMPT_FILE = "refine-component.system.md";
41
+ /**
42
+ * The git blob object id of `content` — `sha1("blob " + byteLength + "\0" +
43
+ * content)`, byte-for-byte what `git hash-object` computes. Exported for direct
44
+ * testing against a known `git hash-object` value.
45
+ */
46
+ export function gitBlobHash(content) {
47
+ const bytes = Buffer.from(content, "utf-8");
48
+ const header = Buffer.from(`blob ${bytes.length}\0`, "utf-8");
49
+ return createHash("sha1")
50
+ .update(Buffer.concat([header, bytes]))
51
+ .digest("hex");
52
+ }
53
+ // Memoize by file name — each prompt is immutable for the process lifetime.
54
+ const cache = new Map();
55
+ /**
56
+ * Load a prompt from `llm/prompts/<file>`, returning its text and version.
57
+ * Memoized: the first call reads + hashes; subsequent calls return the cached
58
+ * result. Throws if the file is missing (a packaging bug worth failing loudly on
59
+ * — a prompt-less generation verb has nothing to send the model).
60
+ */
61
+ export function loadPrompt(file) {
62
+ const cached = cache.get(file);
63
+ if (cached)
64
+ return cached;
65
+ const text = readFileSync(join(PROMPTS_DIR, file), "utf-8");
66
+ const loaded = { file, text, version: gitBlobHash(text) };
67
+ cache.set(file, loaded);
68
+ return loaded;
69
+ }
70
+ /** Convenience: load the `conjure` system prompt (AC5). */
71
+ export function loadGenerateComponentSystemPrompt() {
72
+ return loadPrompt(GENERATE_COMPONENT_SYSTEM_PROMPT_FILE);
73
+ }
74
+ /** Convenience: load the `refine` system prompt (M2-04 · DRO-251). */
75
+ export function loadRefineComponentSystemPrompt() {
76
+ return loadPrompt(REFINE_COMPONENT_SYSTEM_PROMPT_FILE);
77
+ }
78
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/llm/prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AAE7E,6CAA6C;AAC7C,MAAM,CAAC,MAAM,qCAAqC,GAAG,8BAA8B,CAAC;AAEpF;;iDAEiD;AACjD,MAAM,CAAC,MAAM,mCAAmC,GAAG,4BAA4B,CAAC;AAgBhF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,MAAM,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9D,OAAO,UAAU,CAAC,MAAM,CAAC;SACtB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;SACtC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED,4EAA4E;AAC5E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;IACxE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,iCAAiC;IAC/C,OAAO,UAAU,CAAC,qCAAqC,CAAC,CAAC;AAC3D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,+BAA+B;IAC7C,OAAO,UAAU,CAAC,mCAAmC,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,183 @@
1
+ /** Env var that overrides the default retry ceiling (AC5). */
2
+ export declare const RETRY_MAX_ENV = "GENIE_LLM_RETRY_MAX";
3
+ /** Default retry ceiling when the env var is unset / invalid (AC5). */
4
+ export declare const DEFAULT_RETRY_MAX = 3;
5
+ /** Base backoff delay: 1 s (AC4). */
6
+ export declare const BASE_DELAY_MS = 1000;
7
+ /** Backoff cap: 30 s (AC4). */
8
+ export declare const MAX_DELAY_MS = 30000;
9
+ /** Jitter multiplier applied symmetrically around the exponential value (AC4: ±20 %). */
10
+ export declare const JITTER_RATIO = 0.2;
11
+ /**
12
+ * Hard ceiling on an upstream-honoured `Retry-After` value once it's used as
13
+ * an actual `sleep()` duration (Copilot review on PR #126 / DRO-253).
14
+ * `MAX_DELAY_MS` only bounds OUR OWN computed exponential backoff — a value
15
+ * entirely within this module's control — but an honoured `Retry-After` is
16
+ * trusted verbatim by `parseRetryAfter`. A misconfigured or hostile upstream
17
+ * sending e.g. `Retry-After: 86400` (one day) would otherwise stall the
18
+ * in-flight call — and on the stdio MCP transport, the whole in-flight
19
+ * request — for that entire duration: `GENIE_LLM_REQUEST_TIMEOUT_MS` only
20
+ * bounds each individual HTTP request, not the sleep between retries.
21
+ *
22
+ * Set to 60 s — double `MAX_DELAY_MS` — so an explicit upstream signal is
23
+ * still granted more deference than our own guessed-at exponential
24
+ * schedule, while remaining bounded to a duration reasonable for a
25
+ * synchronous MCP tool call. (The reviewer described this figure as the
26
+ * `openai` SDK's own clamp; the SDK actually applies no such clamp — see
27
+ * `retryRequest`/`calculateDefaultRetryTimeoutMillis` in
28
+ * `openai/client.js`, which honours an unclamped `Retry-After` exactly as
29
+ * this module did before this fix. 60 s is picked on its own merits here,
30
+ * not inherited from upstream SDK behaviour.)
31
+ *
32
+ * Only the `sleep()` duration is clamped. `RateLimitedError.retryAfterMs`
33
+ * (surfaced to the caller on exhaustion, see `withRetry` below)
34
+ * deliberately keeps the TRUE, unclamped upstream value — that field's
35
+ * contract is "what did the upstream ask for" (e.g. so a UI layer can show
36
+ * "come back tomorrow"), not "how long did we actually wait".
37
+ */
38
+ export declare const MAX_RETRY_AFTER_MS = 60000;
39
+ /**
40
+ * Thrown when a 429 rate-limit response was retried the full budget and the
41
+ * upstream never gave a non-429 answer (AC6). Kept distinct from
42
+ * `TransientError` so callers that want to surface `ERR_RATE_LIMITED`
43
+ * separately from generic-transient-failure can branch on the type — matches
44
+ * the FR-046 PRD note about surfacing rate-limits distinctly.
45
+ *
46
+ * `retryAfterMs` carries the last `Retry-After` value (in milliseconds) the
47
+ * upstream sent, so a UI layer can show "come back in ~30 s" without having
48
+ * to unwrap the cause itself. Undefined if the final response carried no
49
+ * `Retry-After` header.
50
+ */
51
+ export declare class RateLimitedError extends Error {
52
+ readonly attempts: number;
53
+ readonly cause: Error;
54
+ readonly retryAfterMs?: number | undefined;
55
+ readonly name = "RateLimitedError";
56
+ constructor(message: string, attempts: number, cause: Error, retryAfterMs?: number | undefined);
57
+ }
58
+ /**
59
+ * Thrown when a 5xx / network error was retried the full budget and the
60
+ * upstream never recovered (AC6). Callers that don't care about the
61
+ * distinction from `RateLimitedError` should catch `TransientError |
62
+ * RateLimitedError` as a union; both extend `Error` and both preserve the
63
+ * originating cause on `.cause`.
64
+ */
65
+ export declare class TransientError extends Error {
66
+ readonly attempts: number;
67
+ readonly cause: Error;
68
+ readonly name = "TransientError";
69
+ constructor(message: string, attempts: number, cause: Error);
70
+ }
71
+ /**
72
+ * Resolve `GENIE_LLM_RETRY_MAX`, falling back to `DEFAULT_RETRY_MAX` when
73
+ * unset, blank, non-numeric, or negative. Mirrors the established fallback
74
+ * shape of `resolveTimeoutMs` in `client.ts` and `getPlanTTL` in
75
+ * `plans/index.ts`.
76
+ *
77
+ * A concrete `"0"` is a legitimate operator choice — one attempt, no retries
78
+ * — and must NOT be collapsed with "unset". An operator running against a
79
+ * test lab where they own the retry policy externally sets 0 and expects
80
+ * exactly 1 attempt, not 4.
81
+ *
82
+ * Requires the trimmed value to be ENTIRELY digits (Copilot review on PR
83
+ * #126 / DRO-253): `parseInt` alone silently accepts a leading-numeric
84
+ * prefix — `parseInt("5s", 10)` and `parseInt("3.5", 10)` both parse as
85
+ * valid (5 and 3 respectively) despite neither being a clean integer,
86
+ * contradicting this function's own "non-numeric → default" contract and
87
+ * silently misconfiguring an operator who fat-fingered the env var (e.g.
88
+ * copy-pasted a value with a trailing unit). The regex guard rejects
89
+ * anything that isn't purely `[0-9]+` before `parseInt` ever sees it.
90
+ */
91
+ export declare function resolveRetryMax(env?: NodeJS.ProcessEnv): number;
92
+ /**
93
+ * Parse an HTTP `Retry-After` header (or the non-standard `retry-after-ms`)
94
+ * into milliseconds (AC3).
95
+ *
96
+ * Precedence:
97
+ * 1. `retry-after-ms` (millisecond precision, non-standard but common)
98
+ * 2. `retry-after` as fractional seconds (RFC 9110 delay-seconds form)
99
+ * 3. `retry-after` as HTTP-date (RFC 9110 date form) → delta from now,
100
+ * clamped to 0 if the date is in the past.
101
+ *
102
+ * Returns `null` for absent, empty, or unparseable values — the caller then
103
+ * falls back to the jittered exponential schedule (AC4).
104
+ *
105
+ * The two-level parse (seconds first, HTTP-date fallback) matches the openai
106
+ * SDK's own `retryRequest` logic in `openai/client.js` so anyone already
107
+ * relying on either header form gets consistent behaviour whether they hit
108
+ * this wrapper or the SDK's own internal retry.
109
+ */
110
+ export declare function parseRetryAfter(headers: Headers | undefined): number | null;
111
+ /**
112
+ * Compute the sleep duration for the given retry `attempt` (1-indexed —
113
+ * `attempt=1` is the delay before the first retry, i.e. after the initial
114
+ * attempt failed). Base 1 s, doubles per attempt, capped at 30 s, then a
115
+ * uniform ±20 % jitter applied on top (AC4).
116
+ *
117
+ * The cap is applied to the exponential value BEFORE the jitter multiplier,
118
+ * so at high attempt counts the delay is still bounded by
119
+ * `MAX_DELAY_MS * (1 + JITTER_RATIO)` on paper — but then clamped back to
120
+ * `MAX_DELAY_MS` a second time, so the effective ceiling is exactly 30 s
121
+ * (a strict interpretation of "cap 30 s" in the spec).
122
+ *
123
+ * `random` is injectable so tests can drive deterministic values without
124
+ * needing to stub `Math.random` globally.
125
+ */
126
+ export declare function jitteredBackoff(attempt: number, random?: () => number): number;
127
+ /**
128
+ * Options accepted by `withRetry`. All injectable to keep the wrapper unit-
129
+ * testable without wall-clock waits or global `Math.random` / `process.env`
130
+ * stubbing.
131
+ */
132
+ export interface WithRetryOptions {
133
+ /**
134
+ * Sleep injection — defaults to a `setTimeout`-backed promise. Tests pass
135
+ * `() => Promise.resolve()` to skip real waits without needing fake
136
+ * timers or a global sleep stub.
137
+ */
138
+ sleep?: (ms: number) => Promise<void>;
139
+ /**
140
+ * Random source for jitter — defaults to `Math.random`. Tests pass
141
+ * `() => 0.5` for deterministic midpoint behaviour.
142
+ */
143
+ random?: () => number;
144
+ /**
145
+ * Explicit retry ceiling. When set, overrides both the env var and the
146
+ * default — useful in tests that need to force a specific number of
147
+ * attempts without touching `process.env`. Total attempts = `1 + maxRetries`.
148
+ */
149
+ maxRetries?: number;
150
+ /**
151
+ * Env source for `maxRetries` resolution when `maxRetries` isn't set.
152
+ * Defaults to `process.env`. Tests pass `{}` to force the default.
153
+ */
154
+ env?: NodeJS.ProcessEnv;
155
+ }
156
+ /**
157
+ * Wrap an async LLM handler with retry/backoff (AC1). Returns a function
158
+ * with the SAME argument list and return type as the input handler, so
159
+ * `withRetry(createChatCompletion)` is a drop-in replacement for
160
+ * `createChatCompletion` at every call site.
161
+ *
162
+ * Order of operations per attempt:
163
+ * 1. Call the handler.
164
+ * 2. On success → return the result immediately.
165
+ * 3. On failure → classify (AC2). Non-retryable errors propagate as-is
166
+ * (the original error object, not a wrapped one — a caller that
167
+ * already has instanceof/status handling for BadRequestError et al.
168
+ * keeps working unchanged).
169
+ * 4. On retryable failure with remaining budget → emit an `llm.retry`
170
+ * log line (AC7), sleep for `Retry-After` (AC3) or the jittered
171
+ * backoff (AC4), then loop.
172
+ * 5. On retryable failure with exhausted budget → throw
173
+ * `RateLimitedError` (if the tail-cause was a 429) or `TransientError`
174
+ * (otherwise), preserving the original error on `.cause` (AC6).
175
+ *
176
+ * A handler that transitions from one retryable kind to another across
177
+ * attempts (say 429 → 503 → 500) throws whichever kind matches the LAST
178
+ * failing attempt, on the reasoning that "what the caller sees now" is the
179
+ * most useful discriminator for their fallback branch — a 429-turned-503
180
+ * indicates a genuinely degraded upstream, not a persistent rate-limit.
181
+ */
182
+ export declare function withRetry<A extends readonly unknown[], R>(handler: (...args: A) => Promise<R>, opts?: WithRetryOptions): (...args: A) => Promise<R>;
183
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/llm/retry.ts"],"names":[],"mappings":"AA4CA,8DAA8D;AAC9D,eAAO,MAAM,aAAa,wBAAwB,CAAC;AACnD,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,qCAAqC;AACrC,eAAO,MAAM,aAAa,OAAQ,CAAC;AACnC,+BAA+B;AAC/B,eAAO,MAAM,YAAY,QAAS,CAAC;AACnC,yFAAyF;AACzF,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAgDzC;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;aAIvB,QAAQ,EAAE,MAAM;aAChB,KAAK,EAAE,KAAK;aACZ,YAAY,CAAC,EAAE,MAAM;IALvC,QAAQ,CAAC,IAAI,sBAAsB;gBAEjC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,EACZ,YAAY,CAAC,EAAE,MAAM,YAAA;CAIxC;AAED;;;;;;GAMG;AACH,qBAAa,cAAe,SAAQ,KAAK;aAIrB,QAAQ,EAAE,MAAM;aAChB,KAAK,EAAE,KAAK;IAJ9B,QAAQ,CAAC,IAAI,oBAAoB;gBAE/B,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK;CAI/B;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAY5E;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CA4C3E;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,MAAoB,GAAG,MAAM,CAK3F;AA2HD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,SAAS,OAAO,EAAE,EAAE,CAAC,EACvD,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EACnC,IAAI,GAAE,gBAAqB,GAC1B,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAqE5B"}