@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,141 @@
1
+ /**
2
+ * LLM client wrapper (M2-01) — genie's single point of contact with the
3
+ * operator's configured OpenAI-compatible chat-completions endpoint.
4
+ *
5
+ * genie calls a **configurable**
6
+ * OpenAI-compatible endpoint. LiteLLM is the reference gateway, but Ollama /
7
+ * OpenAI / vLLM / any compatible endpoint work the same way — no provider URL
8
+ * or API key is ever hardcoded here. Both come from env vars
9
+ * (`GENIE_LLM_BASE_URL` / `GENIE_LLM_API_KEY`), validated at construction so a
10
+ * misconfigured deployment fails fast (`MissingLLMConfigError`) rather than
11
+ * failing confusingly deep inside a generation call.
12
+ *
13
+ * All future generation verbs (`conjure` / `refine`, M2-03/M2-04) call through
14
+ * `createChatCompletion` rather than importing the `openai` SDK directly, so
15
+ * this module is the one place that knows about env vars, timeouts, and
16
+ * request logging for LLM calls.
17
+ *
18
+ * Out of scope (per the M2-01 issue): retry/backoff (M2-06) — `withRetry` in
19
+ * a later issue wraps `createChatCompletion` itself — and structured-output
20
+ * schema validation (M2-07).
21
+ */
22
+ import OpenAI from "openai";
23
+ // ─── Env var names ───────────────────────────────────────────────────────────
24
+ /** Base URL of the operator's OpenAI-compatible endpoint. No default (AC3). */
25
+ export const GENIE_LLM_BASE_URL_ENV = "GENIE_LLM_BASE_URL";
26
+ /** API key/token for the configured endpoint. No default (AC3). */
27
+ export const GENIE_LLM_API_KEY_ENV = "GENIE_LLM_API_KEY";
28
+ /** Per-request timeout override, in milliseconds. */
29
+ export const GENIE_LLM_REQUEST_TIMEOUT_MS_ENV = "GENIE_LLM_REQUEST_TIMEOUT_MS";
30
+ /** Default request timeout: 120 000 ms (AC5). */
31
+ export const DEFAULT_LLM_REQUEST_TIMEOUT_MS = 120_000;
32
+ // ─── Errors ──────────────────────────────────────────────────────────────────
33
+ /**
34
+ * Thrown when one or both required LLM env vars are unset (AC2). Callers
35
+ * should let this propagate at startup — there is no safe fallback endpoint
36
+ * to construct a client against (AC3).
37
+ */
38
+ export class MissingLLMConfigError extends Error {
39
+ missing;
40
+ constructor(missing) {
41
+ super(`Missing required LLM configuration: ${missing.join(", ")}. ` +
42
+ `genie has no default LLM endpoint — set ${GENIE_LLM_BASE_URL_ENV} to an ` +
43
+ `operator-configured OpenAI-compatible endpoint and ${GENIE_LLM_API_KEY_ENV} ` +
44
+ `to its API key before starting genie.`);
45
+ this.missing = missing;
46
+ this.name = "MissingLLMConfigError";
47
+ }
48
+ }
49
+ // ─── Config resolution ───────────────────────────────────────────────────────
50
+ /**
51
+ * Resolve `GENIE_LLM_REQUEST_TIMEOUT_MS`, falling back to the 120s default
52
+ * (AC5) when unset, blank, non-numeric, or non-positive. Mirrors the
53
+ * established fallback shape of `getPlanTTL` in `plans/index.ts`.
54
+ */
55
+ export function resolveTimeoutMs(env = process.env) {
56
+ const raw = env[GENIE_LLM_REQUEST_TIMEOUT_MS_ENV];
57
+ if (raw) {
58
+ const parsed = parseInt(raw, 10);
59
+ if (!isNaN(parsed) && parsed > 0) {
60
+ return parsed;
61
+ }
62
+ }
63
+ return DEFAULT_LLM_REQUEST_TIMEOUT_MS;
64
+ }
65
+ /**
66
+ * Construct an `openai` SDK client from env vars (AC1). Throws
67
+ * `MissingLLMConfigError` synchronously if `GENIE_LLM_BASE_URL` or
68
+ * `GENIE_LLM_API_KEY` is unset or blank (AC2/AC3) — no default base URL is
69
+ * ever substituted.
70
+ *
71
+ * Exported as a factory (in addition to the `llmClient` singleton below) so
72
+ * tests can construct additional clients against a stub server, and so a
73
+ * future preflight/health-check path can validate config without relying on
74
+ * module-load side effects.
75
+ */
76
+ export function createLLMClient(env = process.env) {
77
+ // Trim before checking: a whitespace-only value (e.g. a stray " " from a
78
+ // misconfigured .env file) is truthy and would otherwise slip past the
79
+ // `!baseURL`/`!apiKey` checks below, then fail later with a confusing
80
+ // URL-parse or auth error instead of the clear MissingLLMConfigError this
81
+ // function exists to raise (AC2/AC3).
82
+ const baseURL = env[GENIE_LLM_BASE_URL_ENV]?.trim();
83
+ const apiKey = env[GENIE_LLM_API_KEY_ENV]?.trim();
84
+ const missing = [];
85
+ if (!baseURL)
86
+ missing.push(GENIE_LLM_BASE_URL_ENV);
87
+ if (!apiKey)
88
+ missing.push(GENIE_LLM_API_KEY_ENV);
89
+ if (missing.length > 0)
90
+ throw new MissingLLMConfigError(missing);
91
+ return new OpenAI({
92
+ baseURL,
93
+ apiKey,
94
+ timeout: resolveTimeoutMs(env),
95
+ // The `openai` SDK retries 429/5xx/408/409 internally by default
96
+ // (maxRetries: 2) — invisibly to this module. M2-06 owns retry/backoff
97
+ // for this wrapper (typed `RateLimitedError`/`TransientError`, honouring
98
+ // `Retry-After`, a `GENIE_LLM_RETRY_MAX`-configurable cap, one log line
99
+ // per attempt) and wraps `createChatCompletion` from the outside
100
+ // (`withRetry(createChatCompletion)`). Leaving the SDK's own retries
101
+ // enabled would silently double up on that policy — M2-06's "each
102
+ // attempt logged" and "max N retries total" guarantees would both be
103
+ // undermined by extra attempts happening a layer below where it can
104
+ // neither see nor count them. So this client makes exactly one attempt;
105
+ // all retry policy lives in one place (M2-06).
106
+ maxRetries: 0,
107
+ });
108
+ }
109
+ // ─── Singleton ───────────────────────────────────────────────────────────────
110
+ /**
111
+ * The shared `openai` client every generation verb calls through (AC1).
112
+ * Constructed once at module load from env vars — a deployment missing
113
+ * `GENIE_LLM_BASE_URL`/`GENIE_LLM_API_KEY` fails the moment anything imports
114
+ * this module, rather than partway through a generation request.
115
+ */
116
+ export const llmClient = createLLMClient();
117
+ /**
118
+ * Thin wrapper around `llmClient.chat.completions.create` (AC4). Emits a
119
+ * structured `{ model, promptTokens, completionTokens, latencyMs }` log line
120
+ * to **stderr** on success — never stdout, which on the stdio transport *is*
121
+ * the JSON-RPC protocol stream (see `transport.ts` / `tools/plan.ts`'s audit
122
+ * log for the same convention).
123
+ *
124
+ * Does not retry and does not swallow errors: a rejected `create()` call
125
+ * propagates to the caller untouched so M2-06's `withRetry(createChatCompletion)`
126
+ * can wrap this function directly.
127
+ */
128
+ export async function createChatCompletion(input) {
129
+ const startedAt = performance.now();
130
+ const completion = await llmClient.chat.completions.create(input);
131
+ const latencyMs = Math.round(performance.now() - startedAt);
132
+ process.stderr.write(JSON.stringify({
133
+ event: "llm.chat_completion",
134
+ model: completion.model,
135
+ promptTokens: completion.usage?.prompt_tokens ?? null,
136
+ completionTokens: completion.usage?.completion_tokens ?? null,
137
+ latencyMs,
138
+ }) + "\n");
139
+ return completion;
140
+ }
141
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/llm/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,gFAAgF;AAEhF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAC3D,mEAAmE;AACnE,MAAM,CAAC,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AACzD,qDAAqD;AACrD,MAAM,CAAC,MAAM,gCAAgC,GAAG,8BAA8B,CAAC;AAE/E,iDAAiD;AACjD,MAAM,CAAC,MAAM,8BAA8B,GAAG,OAAO,CAAC;AAEtD,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAClB;IAA5B,YAA4B,OAA0B;QACpD,KAAK,CACH,uCAAuC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAC3D,2CAA2C,sBAAsB,SAAS;YAC1E,sDAAsD,qBAAqB,GAAG;YAC9E,uCAAuC,CAC1C,CAAC;QANwB,YAAO,GAAP,OAAO,CAAmB;QAOpD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACnE,MAAM,GAAG,GAAG,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAClD,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,8BAA8B,CAAC;AACxC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,0EAA0E;IAC1E,uEAAuE;IACvE,sEAAsE;IACtE,0EAA0E;IAC1E,sCAAsC;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,sBAAsB,CAAC,EAAE,IAAI,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,GAAG,CAAC,qBAAqB,CAAC,EAAE,IAAI,EAAE,CAAC;IAElD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACjD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAEjE,OAAO,IAAI,MAAM,CAAC;QAChB,OAAO;QACP,MAAM;QACN,OAAO,EAAE,gBAAgB,CAAC,GAAG,CAAC;QAC9B,iEAAiE;QACjE,uEAAuE;QACvE,yEAAyE;QACzE,wEAAwE;QACxE,iEAAiE;QACjE,qEAAqE;QACrE,kEAAkE;QAClE,qEAAqE;QACrE,oEAAoE;QACpE,wEAAwE;QACxE,+CAA+C;QAC/C,UAAU,EAAE,CAAC;KACd,CAAC,CAAC;AACL,CAAC;AAED,gFAAgF;AAEhF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAW,eAAe,EAAE,CAAC;AASnD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAA0B;IAE1B,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IAE5D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,CAAC,SAAS,CAAC;QACb,KAAK,EAAE,qBAAqB;QAC5B,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,YAAY,EAAE,UAAU,CAAC,KAAK,EAAE,aAAa,IAAI,IAAI;QACrD,gBAAgB,EAAE,UAAU,CAAC,KAAK,EAAE,iBAAiB,IAAI,IAAI;QAC7D,SAAS;KACV,CAAC,GAAG,IAAI,CACV,CAAC;IAEF,OAAO,UAAU,CAAC;AACpB,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Shared component-generation response harness (M2-03/M2-04).
3
+ *
4
+ * `conjure` (M2-03 · DRO-250) and `refine` (M2-04 · DRO-251) are the same
5
+ * request/validate/retry machine wrapped around two different prompts:
6
+ * - build the messages,
7
+ * - demand structured output via `response_format: { type: "json_schema", … }`,
8
+ * - parse + validate the reply against `COMPONENT_SCHEMA` (M2-02) with Ajv,
9
+ * - retry EXACTLY ONCE with the validation error + prior output fed back.
10
+ *
11
+ * That machinery lived inline in `conjure.ts`; M2-04's AC6 is literally "same
12
+ * retry-once pattern as M2-03", so rather than clone a second (drift-prone) copy
13
+ * of the Ajv compile, the fence-stripper, the retry-feedback wording, and the
14
+ * two-attempt loop, both verbs import them from here. `refine` reusing this file
15
+ * is what makes "same pattern" a fact of the code, not a hope about two parallel
16
+ * implementations staying in step.
17
+ *
18
+ * What stays tool-side: each verb owns its own **`buildMessages`** (the prompt is
19
+ * the whole point of the difference between the two verbs) and its own per-call
20
+ * structured log + typed error (their AC10/AC8 field sets differ). This module
21
+ * owns everything between "here are the messages" and "here is a validated
22
+ * component or a reason it failed".
23
+ */
24
+ import { type ErrorObject } from "ajv";
25
+ import { type ValidatedComponent } from "./schema.js";
26
+ import type { ChatCompletionInput, ChatCompletionResult } from "./client.js";
27
+ /** Token/cost accounting summed across the (up to two) model calls. */
28
+ export interface UsageInfo {
29
+ promptTokens: number;
30
+ completionTokens: number;
31
+ totalTokens: number;
32
+ }
33
+ /** A fresh zeroed usage accumulator. */
34
+ export declare function emptyUsage(): UsageInfo;
35
+ /** Add one completion's token counts into a running {@link UsageInfo}. */
36
+ export declare function addUsage(into: UsageInfo, completion: ChatCompletionResult): void;
37
+ /** The chat-completion seam. Production supplies a lazy wrapper over the M2-01
38
+ * `createChatCompletion`; tests inject a stub so no real endpoint or
39
+ * `GENIE_LLM_*` env is needed. Shared by both generation verbs. */
40
+ export type ChatCompletionFn = (input: ChatCompletionInput) => Promise<ChatCompletionResult>;
41
+ /** Structured warn/telemetry to stderr — never stdout (the stdio transport's
42
+ * stdout IS the JSON-RPC stream; same convention as client.ts / plan.ts). */
43
+ export declare function logStderr(payload: Record<string, unknown>): void;
44
+ /** Render Ajv errors as a compact, model-readable bullet list for the retry. */
45
+ export declare function formatAjvErrors(errors: ErrorObject[] | null | undefined): string;
46
+ /**
47
+ * Some endpoints wrap JSON in a ```json fence despite `response_format`. Strip a
48
+ * single leading/trailing fence defensively before parsing so a cosmetically
49
+ * fenced-but-valid reply doesn't cost a retry.
50
+ */
51
+ export declare function stripJsonFence(raw: string): string;
52
+ /** Outcome of parsing + schema-validating one model reply. */
53
+ export type ParseResult = {
54
+ ok: true;
55
+ component: ValidatedComponent;
56
+ } | {
57
+ ok: false;
58
+ reason: string;
59
+ };
60
+ /** Parse + schema-validate one model reply. Returns the component or a reason. */
61
+ export declare function parseAndValidate(raw: string | null | undefined): ParseResult;
62
+ /**
63
+ * The `response_format` demanded on every generation call. Wraps
64
+ * COMPONENT_SCHEMA in the OpenAI structured-output envelope
65
+ * `{ name, description, schema }` — the real wire shape a compliant endpoint
66
+ * (and LiteLLM's passthrough) expects.
67
+ */
68
+ export declare function buildComponentResponseFormat(): ChatCompletionInput["response_format"];
69
+ /** Context appended to a user message on the ONE retry: the validation error and
70
+ * the model's prior (invalid) output, so it can self-correct (M2-03 AC8). */
71
+ export interface RetryContext {
72
+ reason: string;
73
+ previous: string;
74
+ }
75
+ /**
76
+ * Append the shared retry-feedback block to a user instruction. Same wording for
77
+ * both verbs so the model sees a consistent "you failed validation, fix exactly
78
+ * this" contract regardless of which verb it is answering. Kept as a function
79
+ * (not a template inlined per tool) so the wording — which the M2-03 tests pin
80
+ * (`"failed schema validation"`, `"previous"`) — lives in exactly one place.
81
+ */
82
+ export declare function appendRetryFeedback(userText: string, retry: RetryContext): string;
83
+ /** Builds the messages for one attempt. `retry` is `undefined` on the first
84
+ * attempt and carries the validation feedback on the (single) retry. */
85
+ export type BuildMessagesFn = (retry: RetryContext | undefined) => ChatCompletionInput["messages"];
86
+ export type ValidateGeneratedComponent = (component: ValidatedComponent) => string | undefined;
87
+ /** Result of {@link runComponentGeneration}: the parse outcome plus the accounting
88
+ * a caller needs for its per-call log (attempts + summed usage). */
89
+ export interface GenerationRun {
90
+ outcome: ParseResult;
91
+ usage: UsageInfo;
92
+ /** 1 if the first reply validated, 2 if it took the retry. */
93
+ attempts: number;
94
+ }
95
+ /**
96
+ * Run the shared generation loop: one attempt, and — only if it fails schema
97
+ * validation — exactly one retry with the error + prior output fed back
98
+ * (M2-03 AC8 / M2-04 AC6). `buildMessages` is the sole per-verb input, so
99
+ * `conjure` and `refine` share this identical control flow while each keeps its
100
+ * own prompt. Usage is summed across both attempts; `attempts` reports whether
101
+ * the retry fired. The caller decides what to do with a still-invalid
102
+ * `outcome` (throw its own typed error) and logs its own per-call line.
103
+ */
104
+ export declare function runComponentGeneration(params: {
105
+ chat: ChatCompletionFn;
106
+ model: string;
107
+ buildMessages: BuildMessagesFn;
108
+ /** Optional tool-specific validation applied after the shared JSON schema. */
109
+ validateGeneratedComponent?: ValidateGeneratedComponent;
110
+ }): Promise<GenerationRun>;
111
+ //# sourceMappingURL=component-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-response.d.ts","sourceRoot":"","sources":["../../src/llm/component-response.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AASH,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAEnE,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAE7E,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wCAAwC;AACxC,wBAAgB,UAAU,IAAI,SAAS,CAEtC;AAED,0EAA0E;AAC1E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,oBAAoB,GAAG,IAAI,CAIhF;AAED;;mEAEmE;AACnE,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAE7F;6EAC6E;AAC7E,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEhE;AAaD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKhF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,kBAAkB,CAAA;CAAE,GAC3C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,CAc5E;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CASrF;AAID;6EAC6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CASjF;AAID;wEACwE;AACxE,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,KAAK,mBAAmB,CAAC,UAAU,CAAC,CAAC;AACnG,MAAM,MAAM,0BAA0B,GAAG,CAAC,SAAS,EAAE,kBAAkB,KAAK,MAAM,GAAG,SAAS,CAAC;AAE/F;oEACoE;AACpE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,eAAe,CAAC;IAC/B,8EAA8E;IAC9E,0BAA0B,CAAC,EAAE,0BAA0B,CAAC;CACzD,GAAG,OAAO,CAAC,aAAa,CAAC,CAmCzB"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Shared component-generation response harness (M2-03/M2-04).
3
+ *
4
+ * `conjure` (M2-03 · DRO-250) and `refine` (M2-04 · DRO-251) are the same
5
+ * request/validate/retry machine wrapped around two different prompts:
6
+ * - build the messages,
7
+ * - demand structured output via `response_format: { type: "json_schema", … }`,
8
+ * - parse + validate the reply against `COMPONENT_SCHEMA` (M2-02) with Ajv,
9
+ * - retry EXACTLY ONCE with the validation error + prior output fed back.
10
+ *
11
+ * That machinery lived inline in `conjure.ts`; M2-04's AC6 is literally "same
12
+ * retry-once pattern as M2-03", so rather than clone a second (drift-prone) copy
13
+ * of the Ajv compile, the fence-stripper, the retry-feedback wording, and the
14
+ * two-attempt loop, both verbs import them from here. `refine` reusing this file
15
+ * is what makes "same pattern" a fact of the code, not a hope about two parallel
16
+ * implementations staying in step.
17
+ *
18
+ * What stays tool-side: each verb owns its own **`buildMessages`** (the prompt is
19
+ * the whole point of the difference between the two verbs) and its own per-call
20
+ * structured log + typed error (their AC10/AC8 field sets differ). This module
21
+ * owns everything between "here are the messages" and "here is a validated
22
+ * component or a reason it failed".
23
+ */
24
+ // Named `Ajv` import (not default): ajv@8 is CJS (`module.exports = Ajv` +
25
+ // `module.exports.Ajv = Ajv`) with a `.d.ts` `export declare class Ajv`. Under
26
+ // this repo's `verbatimModuleSyntax` + NodeNext, the *default* import resolves
27
+ // to the module namespace object (TS2351 "not constructable"); the *named* `Ajv`
28
+ // is both a constructable typed class and a cjs-module-lexer-detectable runtime
29
+ // export, so it typechecks under `tsc` and runs under both vite (tests) and node
30
+ // (compiled dist). (Carried verbatim from conjure.ts when this harness was
31
+ // extracted — same reasoning, one copy.)
32
+ import { Ajv } from "ajv";
33
+ import { COMPONENT_SCHEMA } from "./schema.js";
34
+ /** A fresh zeroed usage accumulator. */
35
+ export function emptyUsage() {
36
+ return { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
37
+ }
38
+ /** Add one completion's token counts into a running {@link UsageInfo}. */
39
+ export function addUsage(into, completion) {
40
+ into.promptTokens += completion.usage?.prompt_tokens ?? 0;
41
+ into.completionTokens += completion.usage?.completion_tokens ?? 0;
42
+ into.totalTokens += completion.usage?.total_tokens ?? 0;
43
+ }
44
+ /** Structured warn/telemetry to stderr — never stdout (the stdio transport's
45
+ * stdout IS the JSON-RPC stream; same convention as client.ts / plan.ts). */
46
+ export function logStderr(payload) {
47
+ process.stderr.write(JSON.stringify(payload) + "\n");
48
+ }
49
+ // ── Structured-output validation ──────────────────────────────────────────────
50
+ //
51
+ // Compile the validator once against COMPONENT_SCHEMA with the SAME Ajv
52
+ // configuration the schema's own tests and M2-07's `validateComponent` use —
53
+ // `{ strict: true, allErrors: true }`. `strict: true` surfaces schema-keyword
54
+ // mistakes at compile time; `allErrors` lets a retry prompt name *every* problem
55
+ // rather than only the first.
56
+ const ajv = new Ajv({ strict: true, allErrors: true });
57
+ const validateComponent = ajv.compile(COMPONENT_SCHEMA);
58
+ /** Render Ajv errors as a compact, model-readable bullet list for the retry. */
59
+ export function formatAjvErrors(errors) {
60
+ if (!errors || errors.length === 0)
61
+ return "(no detail)";
62
+ return errors
63
+ .map((e) => `- ${e.instancePath || "(root)"} ${e.message ?? "is invalid"}`)
64
+ .join("\n");
65
+ }
66
+ /**
67
+ * Some endpoints wrap JSON in a ```json fence despite `response_format`. Strip a
68
+ * single leading/trailing fence defensively before parsing so a cosmetically
69
+ * fenced-but-valid reply doesn't cost a retry.
70
+ */
71
+ export function stripJsonFence(raw) {
72
+ const trimmed = raw.trim();
73
+ const fence = /^```(?:json)?\s*\n([\s\S]*?)\n```$/;
74
+ const m = fence.exec(trimmed);
75
+ return m ? m[1].trim() : trimmed;
76
+ }
77
+ /** Parse + schema-validate one model reply. Returns the component or a reason. */
78
+ export function parseAndValidate(raw) {
79
+ if (!raw || raw.trim() === "") {
80
+ return { ok: false, reason: "The response was empty — return a JSON object." };
81
+ }
82
+ let parsed;
83
+ try {
84
+ parsed = JSON.parse(stripJsonFence(raw));
85
+ }
86
+ catch (err) {
87
+ return { ok: false, reason: `The response was not valid JSON (${String(err)}).` };
88
+ }
89
+ if (validateComponent(parsed)) {
90
+ return { ok: true, component: parsed };
91
+ }
92
+ return { ok: false, reason: formatAjvErrors(validateComponent.errors) };
93
+ }
94
+ /**
95
+ * The `response_format` demanded on every generation call. Wraps
96
+ * COMPONENT_SCHEMA in the OpenAI structured-output envelope
97
+ * `{ name, description, schema }` — the real wire shape a compliant endpoint
98
+ * (and LiteLLM's passthrough) expects.
99
+ */
100
+ export function buildComponentResponseFormat() {
101
+ return {
102
+ type: "json_schema",
103
+ json_schema: {
104
+ name: "GenieComponent",
105
+ description: COMPONENT_SCHEMA.description,
106
+ schema: COMPONENT_SCHEMA,
107
+ },
108
+ };
109
+ }
110
+ /**
111
+ * Append the shared retry-feedback block to a user instruction. Same wording for
112
+ * both verbs so the model sees a consistent "you failed validation, fix exactly
113
+ * this" contract regardless of which verb it is answering. Kept as a function
114
+ * (not a template inlined per tool) so the wording — which the M2-03 tests pin
115
+ * (`"failed schema validation"`, `"previous"`) — lives in exactly one place.
116
+ */
117
+ export function appendRetryFeedback(userText, retry) {
118
+ return (userText +
119
+ "\n\n## Your previous attempt failed schema validation\n" +
120
+ "Fix exactly these problems and return corrected JSON — nothing else:\n" +
121
+ retry.reason +
122
+ "\n\n### Your previous (invalid) output\n" +
123
+ retry.previous);
124
+ }
125
+ /**
126
+ * Run the shared generation loop: one attempt, and — only if it fails schema
127
+ * validation — exactly one retry with the error + prior output fed back
128
+ * (M2-03 AC8 / M2-04 AC6). `buildMessages` is the sole per-verb input, so
129
+ * `conjure` and `refine` share this identical control flow while each keeps its
130
+ * own prompt. Usage is summed across both attempts; `attempts` reports whether
131
+ * the retry fired. The caller decides what to do with a still-invalid
132
+ * `outcome` (throw its own typed error) and logs its own per-call line.
133
+ */
134
+ export async function runComponentGeneration(params) {
135
+ const { chat, model, buildMessages, validateGeneratedComponent } = params;
136
+ const usage = emptyUsage();
137
+ const responseFormat = buildComponentResponseFormat();
138
+ // Attempt 1.
139
+ const first = await chat({
140
+ model,
141
+ messages: buildMessages(undefined),
142
+ response_format: responseFormat,
143
+ });
144
+ addUsage(usage, first);
145
+ const firstRaw = first.choices[0]?.message?.content ?? null;
146
+ let outcome = applyGeneratedComponentValidation(parseAndValidate(firstRaw), validateGeneratedComponent);
147
+ let attempts = 1;
148
+ // Attempt 2 (retry once) — feed the validation error + prior output back.
149
+ if (!outcome.ok) {
150
+ const second = await chat({
151
+ model,
152
+ messages: buildMessages({ reason: outcome.reason, previous: firstRaw ?? "(empty)" }),
153
+ response_format: responseFormat,
154
+ });
155
+ addUsage(usage, second);
156
+ attempts = 2;
157
+ outcome = applyGeneratedComponentValidation(parseAndValidate(second.choices[0]?.message?.content ?? null), validateGeneratedComponent);
158
+ }
159
+ return { outcome, usage, attempts };
160
+ }
161
+ function applyGeneratedComponentValidation(outcome, validateGeneratedComponent) {
162
+ if (!outcome.ok || validateGeneratedComponent === undefined)
163
+ return outcome;
164
+ const reason = validateGeneratedComponent(outcome.component);
165
+ return reason === undefined ? outcome : { ok: false, reason };
166
+ }
167
+ //# sourceMappingURL=component-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-response.js","sourceRoot":"","sources":["../../src/llm/component-response.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ValidatedComponent } from "./schema.js";
2
+ export type GeneratedFileWithEncoding = ValidatedComponent["files"][number] & {
3
+ /** Encoding required when forwarding `content` to write_files as inline data. */
4
+ encoding: "utf-8" | "base64";
5
+ };
6
+ /** Normalize model-supplied MIME/encoding from the path used by the kit store. */
7
+ export declare function normalizeGeneratedFiles(files: ValidatedComponent["files"]): GeneratedFileWithEncoding[];
8
+ /** Reject binary generated files whose content cannot be safely decoded. */
9
+ export declare function validateGeneratedBinaryContent(component: ValidatedComponent, ignoredPaths?: ReadonlySet<string>): string | undefined;
10
+ //# sourceMappingURL=generated-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-files.d.ts","sourceRoot":"","sources":["../../src/llm/generated-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtD,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG;IAC5E,iFAAiF;IACjF,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B,CAAC;AAEF,kFAAkF;AAClF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,GACjC,yBAAyB,EAAE,CAS7B;AAED,4EAA4E;AAC5E,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,kBAAkB,EAC7B,YAAY,GAAE,WAAW,CAAC,MAAM,CAAa,GAC5C,MAAM,GAAG,SAAS,CAYpB"}
@@ -0,0 +1,26 @@
1
+ import { isTextMime, isValidBase64Content, resolveMime } from "../store/kit-files.js";
2
+ /** Normalize model-supplied MIME/encoding from the path used by the kit store. */
3
+ export function normalizeGeneratedFiles(files) {
4
+ return files.map((file) => {
5
+ const mimeType = resolveMime(file.path);
6
+ return {
7
+ ...file,
8
+ mimeType,
9
+ encoding: isTextMime(mimeType) ? "utf-8" : "base64",
10
+ };
11
+ });
12
+ }
13
+ /** Reject binary generated files whose content cannot be safely decoded. */
14
+ export function validateGeneratedBinaryContent(component, ignoredPaths = new Set()) {
15
+ for (const [index, file] of component.files.entries()) {
16
+ if (ignoredPaths.has(file.path))
17
+ continue;
18
+ const mimeType = resolveMime(file.path);
19
+ if (!isTextMime(mimeType) && !isValidBase64Content(file.content)) {
20
+ return (`- /files/${index}/content must be valid base64 for binary file ` +
21
+ `"${file.path}" (${mimeType}).`);
22
+ }
23
+ }
24
+ return undefined;
25
+ }
26
+ //# sourceMappingURL=generated-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-files.js","sourceRoot":"","sources":["../../src/llm/generated-files.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAOtF,kFAAkF;AAClF,MAAM,UAAU,uBAAuB,CACrC,KAAkC;IAElC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO;YACL,GAAG,IAAI;YACP,QAAQ;YACR,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;SACpD,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,8BAA8B,CAC5C,SAA6B,EAC7B,eAAoC,IAAI,GAAG,EAAE;IAE7C,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QACtD,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1C,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACjE,OAAO,CACL,YAAY,KAAK,gDAAgD;gBACjE,IAAI,IAAI,CAAC,IAAI,MAAM,QAAQ,IAAI,CAChC,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,70 @@
1
+ # Prompt changelog
2
+
3
+ Version history for genie's LLM system prompts. The _effective_ version of a
4
+ prompt at runtime is the git blob hash of its `.md` file (logged on every
5
+ `conjure`/`refine` call as `promptVersion`), so this file is the human-readable
6
+ narrative companion to that hash — it explains _why_ a prompt changed, not just
7
+ _that_ it did.
8
+
9
+ Per the M2-03 issue: "80% of the engineering effort here is in iterating on the
10
+ system prompt. Track edits in `prompts/CHANGELOG.md`." Add a dated entry every
11
+ time a `*.system.md` here changes.
12
+
13
+ ## generate-component.system.md
14
+
15
+ ### v1 — 2026-07-03 (M2-03 / DRO-250)
16
+
17
+ Initial hand-authored system prompt for `conjure`.
18
+
19
+ Hand-authored rather than adapted from Anthropic's `canvas-design` skill: that
20
+ skill targets Anthropic's hosted Claude Design product and its `@dsCard` /
21
+ `_ds_*` conventions, which are Anthropic-interop shapes genie deliberately keeps
22
+ out of its native surface (CLAUDE.md hard rule 1). genie's native contract —
23
+ `@genie` markers, `components/<group>/<Name>/` layout, the embedded-tier
24
+ `default-src 'none'` CSP, and G-5 byte-identical previews — has no public
25
+ equivalent to lift, so v1 encodes genie's own constraints directly.
26
+
27
+ Covers:
28
+
29
+ - The strict JSON-only output contract (mirrors `COMPONENT_SCHEMA`: top-level
30
+ `componentName` / `group` / `files` / `manifestEntry`, the
31
+ `components/<group>/<Name>/` path layout, and the required
32
+ `<Name>.html` preview).
33
+ - The `@genie` first-line marker (`<!-- @genie group="<group>" -->`) — the
34
+ literal first line M3-01 validates post-hoc.
35
+ - The embedded-tier CSP wall (`default-src 'none'`): no external assets, system
36
+ fonts only, self-contained inline CSS, byte-identical across
37
+ `file://`/`localhost`/`genie://` (RFC G-5).
38
+ - Match-the-kit / honesty / accessibility / one-component-per-call house rules.
39
+ - Reference-image and reference-page handling.
40
+ - Retry behavior: on a re-send with an appended validation error, fix exactly
41
+ what the error names and return corrected JSON (supports AC8's retry-once).
42
+
43
+ ## refine-component.system.md
44
+
45
+ ### v1 — 2026-07-03 (M2-04 / DRO-251)
46
+
47
+ Initial hand-authored system prompt for `refine` — the iterate-on-an-existing-
48
+ component verb. Shares `conjure`'s output contract (the same `COMPONENT_SCHEMA`
49
+ shape: top-level `componentName` / `group` / `files` / `manifestEntry`, the
50
+ `components/<group>/<Name>/` layout, the `@genie` first-line marker, and the
51
+ embedded-tier `default-src 'none'` CSP), because `refine` returns the same kind
52
+ of artifact `conjure` does — a full, validated component — just derived from
53
+ existing source plus an instruction rather than from a blank-slate prompt.
54
+
55
+ Where it deliberately diverges from `generate-component.system.md`:
56
+
57
+ - **Edit, don't rewrite.** The cardinal rule: change only what the `instruction`
58
+ requires and return everything else byte-for-byte. Keep the same
59
+ `componentName`/`group` (renaming is not a refinement), preserve unrelated
60
+ markup/props/whitespace, and don't drop files — so the unified diff the tool
61
+ computes (AC5) shows only the requested change, nothing incidental.
62
+ - **Whole files, not a patch.** The model still returns the COMPONENT_SCHEMA
63
+ file set (full updated contents), never a diff — the tool derives the
64
+ informational diff itself (AC5: "files are the source of truth").
65
+ - **Region scoping.** When a region crop (vision) or its coordinates are
66
+ attached, the instruction is scoped to that rectangle: edit there, leave the
67
+ rest untouched (AC4/AC7).
68
+ - Retry behavior mirrors `conjure` (AC6 "same retry-once pattern as M2-03"):
69
+ on a re-send with an appended validation error, fix exactly what it names —
70
+ including a changed `componentName`/`group` — and return corrected JSON.