@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.
- package/README.md +15 -0
- package/dist/auth/bearer.d.ts +49 -0
- package/dist/auth/bearer.d.ts.map +1 -0
- package/dist/auth/bearer.js +159 -0
- package/dist/auth/bearer.js.map +1 -0
- package/dist/auth/oauth/authorize.d.ts +19 -0
- package/dist/auth/oauth/authorize.d.ts.map +1 -0
- package/dist/auth/oauth/authorize.js +111 -0
- package/dist/auth/oauth/authorize.js.map +1 -0
- package/dist/auth/oauth/config.d.ts +10 -0
- package/dist/auth/oauth/config.d.ts.map +1 -0
- package/dist/auth/oauth/config.js +16 -0
- package/dist/auth/oauth/config.js.map +1 -0
- package/dist/auth/oauth/dcr.d.ts +15 -0
- package/dist/auth/oauth/dcr.d.ts.map +1 -0
- package/dist/auth/oauth/dcr.js +38 -0
- package/dist/auth/oauth/dcr.js.map +1 -0
- package/dist/auth/oauth/jwt.d.ts +13 -0
- package/dist/auth/oauth/jwt.d.ts.map +1 -0
- package/dist/auth/oauth/jwt.js +46 -0
- package/dist/auth/oauth/jwt.js.map +1 -0
- package/dist/auth/oauth/metadata.d.ts +9 -0
- package/dist/auth/oauth/metadata.d.ts.map +1 -0
- package/dist/auth/oauth/metadata.js +19 -0
- package/dist/auth/oauth/metadata.js.map +1 -0
- package/dist/auth/oauth/router.d.ts +32 -0
- package/dist/auth/oauth/router.d.ts.map +1 -0
- package/dist/auth/oauth/router.js +159 -0
- package/dist/auth/oauth/router.js.map +1 -0
- package/dist/auth/oauth/store.d.ts +55 -0
- package/dist/auth/oauth/store.d.ts.map +1 -0
- package/dist/auth/oauth/store.js +63 -0
- package/dist/auth/oauth/store.js.map +1 -0
- package/dist/auth/oauth/token.d.ts +21 -0
- package/dist/auth/oauth/token.d.ts.map +1 -0
- package/dist/auth/oauth/token.js +77 -0
- package/dist/auth/oauth/token.js.map +1 -0
- package/dist/auth/oidc/group-policy.d.ts +43 -0
- package/dist/auth/oidc/group-policy.d.ts.map +1 -0
- package/dist/auth/oidc/group-policy.js +54 -0
- package/dist/auth/oidc/group-policy.js.map +1 -0
- package/dist/auth/oidc/verifier.d.ts +42 -0
- package/dist/auth/oidc/verifier.d.ts.map +1 -0
- package/dist/auth/oidc/verifier.js +145 -0
- package/dist/auth/oidc/verifier.js.map +1 -0
- package/dist/auth/token-cli.d.ts +9 -0
- package/dist/auth/token-cli.d.ts.map +1 -0
- package/dist/auth/token-cli.js +98 -0
- package/dist/auth/token-cli.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +149 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/redact.d.ts +20 -0
- package/dist/config/redact.d.ts.map +1 -0
- package/dist/config/redact.js +77 -0
- package/dist/config/redact.js.map +1 -0
- package/dist/config/secrets.d.ts +100 -0
- package/dist/config/secrets.d.ts.map +1 -0
- package/dist/config/secrets.js +130 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/framework/html.d.ts +103 -0
- package/dist/framework/html.d.ts.map +1 -0
- package/dist/framework/html.js +271 -0
- package/dist/framework/html.js.map +1 -0
- package/dist/framework/interface.d.ts +130 -0
- package/dist/framework/interface.d.ts.map +1 -0
- package/dist/framework/interface.js +112 -0
- package/dist/framework/interface.js.map +1 -0
- package/dist/framework/react.d.ts +133 -0
- package/dist/framework/react.d.ts.map +1 -0
- package/dist/framework/react.js +243 -0
- package/dist/framework/react.js.map +1 -0
- package/dist/framework/vue.d.ts +99 -0
- package/dist/framework/vue.d.ts.map +1 -0
- package/dist/framework/vue.js +375 -0
- package/dist/framework/vue.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +81 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +141 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/component-response.d.ts +111 -0
- package/dist/llm/component-response.d.ts.map +1 -0
- package/dist/llm/component-response.js +167 -0
- package/dist/llm/component-response.js.map +1 -0
- package/dist/llm/generated-files.d.ts +10 -0
- package/dist/llm/generated-files.d.ts.map +1 -0
- package/dist/llm/generated-files.js +26 -0
- package/dist/llm/generated-files.js.map +1 -0
- package/dist/llm/prompts/CHANGELOG.md +70 -0
- package/dist/llm/prompts/generate-component.system.md +99 -0
- package/dist/llm/prompts/refine-component.system.md +112 -0
- package/dist/llm/prompts.d.ts +66 -0
- package/dist/llm/prompts.d.ts.map +1 -0
- package/dist/llm/prompts.js +78 -0
- package/dist/llm/prompts.js.map +1 -0
- package/dist/llm/retry.d.ts +183 -0
- package/dist/llm/retry.d.ts.map +1 -0
- package/dist/llm/retry.js +475 -0
- package/dist/llm/retry.js.map +1 -0
- package/dist/llm/schema.d.ts +129 -0
- package/dist/llm/schema.d.ts.map +1 -0
- package/dist/llm/schema.js +147 -0
- package/dist/llm/schema.js.map +1 -0
- package/dist/llm/validate.d.ts +107 -0
- package/dist/llm/validate.d.ts.map +1 -0
- package/dist/llm/validate.js +174 -0
- package/dist/llm/validate.js.map +1 -0
- package/dist/manifest/compiler.d.ts +112 -0
- package/dist/manifest/compiler.d.ts.map +1 -0
- package/dist/manifest/compiler.js +306 -0
- package/dist/manifest/compiler.js.map +1 -0
- package/dist/manifest/ensure.d.ts +46 -0
- package/dist/manifest/ensure.d.ts.map +1 -0
- package/dist/manifest/ensure.js +49 -0
- package/dist/manifest/ensure.js.map +1 -0
- package/dist/manifest/index.d.ts +13 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +13 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/middleware/plan-guard.d.ts +167 -0
- package/dist/middleware/plan-guard.d.ts.map +1 -0
- package/dist/middleware/plan-guard.js +190 -0
- package/dist/middleware/plan-guard.js.map +1 -0
- package/dist/plans/index.d.ts +71 -0
- package/dist/plans/index.d.ts.map +1 -0
- package/dist/plans/index.js +252 -0
- package/dist/plans/index.js.map +1 -0
- package/dist/schemas/component.schema.json +114 -0
- package/dist/server.d.ts +96 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +258 -0
- package/dist/server.js.map +1 -0
- package/dist/store/empty-manifest.d.ts +58 -0
- package/dist/store/empty-manifest.d.ts.map +1 -0
- package/dist/store/empty-manifest.js +59 -0
- package/dist/store/empty-manifest.js.map +1 -0
- package/dist/store/git-host.d.ts +120 -0
- package/dist/store/git-host.d.ts.map +1 -0
- package/dist/store/git-host.js +796 -0
- package/dist/store/git-host.js.map +1 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +4 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/interface.d.ts +322 -0
- package/dist/store/interface.d.ts.map +1 -0
- package/dist/store/interface.js +100 -0
- package/dist/store/interface.js.map +1 -0
- package/dist/store/kit-files.d.ts +88 -0
- package/dist/store/kit-files.d.ts.map +1 -0
- package/dist/store/kit-files.js +199 -0
- package/dist/store/kit-files.js.map +1 -0
- package/dist/store/local.d.ts +70 -0
- package/dist/store/local.d.ts.map +1 -0
- package/dist/store/local.js +651 -0
- package/dist/store/local.js.map +1 -0
- package/dist/store/manifest.d.ts +44 -0
- package/dist/store/manifest.d.ts.map +1 -0
- package/dist/store/manifest.js +116 -0
- package/dist/store/manifest.js.map +1 -0
- package/dist/store/viewer-assets.d.ts +20 -0
- package/dist/store/viewer-assets.d.ts.map +1 -0
- package/dist/store/viewer-assets.js +118 -0
- package/dist/store/viewer-assets.js.map +1 -0
- package/dist/sync/anchor.d.ts +125 -0
- package/dist/sync/anchor.d.ts.map +1 -0
- package/dist/sync/anchor.js +210 -0
- package/dist/sync/anchor.js.map +1 -0
- package/dist/sync/index.d.ts +15 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +15 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/orchestrator.d.ts +160 -0
- package/dist/sync/orchestrator.d.ts.map +1 -0
- package/dist/sync/orchestrator.js +288 -0
- package/dist/sync/orchestrator.js.map +1 -0
- package/dist/tools/bind_kit.d.ts +29 -0
- package/dist/tools/bind_kit.d.ts.map +1 -0
- package/dist/tools/bind_kit.js +70 -0
- package/dist/tools/bind_kit.js.map +1 -0
- package/dist/tools/conjure.d.ts +141 -0
- package/dist/tools/conjure.d.ts.map +1 -0
- package/dist/tools/conjure.js +655 -0
- package/dist/tools/conjure.js.map +1 -0
- package/dist/tools/conjure_screen.d.ts +174 -0
- package/dist/tools/conjure_screen.d.ts.map +1 -0
- package/dist/tools/conjure_screen.js +458 -0
- package/dist/tools/conjure_screen.js.map +1 -0
- package/dist/tools/create_kit.d.ts +32 -0
- package/dist/tools/create_kit.d.ts.map +1 -0
- package/dist/tools/create_kit.js +131 -0
- package/dist/tools/create_kit.js.map +1 -0
- package/dist/tools/create_project.d.ts +267 -0
- package/dist/tools/create_project.d.ts.map +1 -0
- package/dist/tools/create_project.js +546 -0
- package/dist/tools/create_project.js.map +1 -0
- package/dist/tools/delete_files.d.ts +93 -0
- package/dist/tools/delete_files.d.ts.map +1 -0
- package/dist/tools/delete_files.js +276 -0
- package/dist/tools/delete_files.js.map +1 -0
- package/dist/tools/delete_project.d.ts +43 -0
- package/dist/tools/delete_project.d.ts.map +1 -0
- package/dist/tools/delete_project.js +118 -0
- package/dist/tools/delete_project.js.map +1 -0
- package/dist/tools/get_kit.d.ts +28 -0
- package/dist/tools/get_kit.d.ts.map +1 -0
- package/dist/tools/get_kit.js +95 -0
- package/dist/tools/get_kit.js.map +1 -0
- package/dist/tools/get_project.d.ts +37 -0
- package/dist/tools/get_project.d.ts.map +1 -0
- package/dist/tools/get_project.js +71 -0
- package/dist/tools/get_project.js.map +1 -0
- package/dist/tools/list_components.d.ts +42 -0
- package/dist/tools/list_components.d.ts.map +1 -0
- package/dist/tools/list_components.js +125 -0
- package/dist/tools/list_components.js.map +1 -0
- package/dist/tools/list_files.d.ts +32 -0
- package/dist/tools/list_files.d.ts.map +1 -0
- package/dist/tools/list_files.js +109 -0
- package/dist/tools/list_files.js.map +1 -0
- package/dist/tools/list_kits.d.ts +14 -0
- package/dist/tools/list_kits.d.ts.map +1 -0
- package/dist/tools/list_kits.js +41 -0
- package/dist/tools/list_kits.js.map +1 -0
- package/dist/tools/list_projects.d.ts +23 -0
- package/dist/tools/list_projects.d.ts.map +1 -0
- package/dist/tools/list_projects.js +66 -0
- package/dist/tools/list_projects.js.map +1 -0
- package/dist/tools/plan.d.ts +16 -0
- package/dist/tools/plan.d.ts.map +1 -0
- package/dist/tools/plan.js +146 -0
- package/dist/tools/plan.js.map +1 -0
- package/dist/tools/preview.d.ts +289 -0
- package/dist/tools/preview.d.ts.map +1 -0
- package/dist/tools/preview.js +672 -0
- package/dist/tools/preview.js.map +1 -0
- package/dist/tools/read_file.d.ts +40 -0
- package/dist/tools/read_file.d.ts.map +1 -0
- package/dist/tools/read_file.js +114 -0
- package/dist/tools/read_file.js.map +1 -0
- package/dist/tools/refine.d.ts +220 -0
- package/dist/tools/refine.d.ts.map +1 -0
- package/dist/tools/refine.js +501 -0
- package/dist/tools/refine.js.map +1 -0
- package/dist/tools/validate.d.ts +74 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +224 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/tools/write_files.d.ts +158 -0
- package/dist/tools/write_files.d.ts.map +1 -0
- package/dist/tools/write_files.js +541 -0
- package/dist/tools/write_files.js.map +1 -0
- package/dist/transport.d.ts +77 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +448 -0
- package/dist/transport.js.map +1 -0
- package/dist/ui/card-asset-broker.d.ts +42 -0
- package/dist/ui/card-asset-broker.d.ts.map +1 -0
- package/dist/ui/card-asset-broker.js +479 -0
- package/dist/ui/card-asset-broker.js.map +1 -0
- package/dist/ui/grid-resource.d.ts +252 -0
- package/dist/ui/grid-resource.d.ts.map +1 -0
- package/dist/ui/grid-resource.js +680 -0
- package/dist/ui/grid-resource.js.map +1 -0
- package/dist/ui/viewer-static/index.html +95 -0
- package/dist/ui/viewer-static/viewer.css +451 -0
- package/dist/ui/viewer-static/viewer.js +1405 -0
- package/dist/validate/full-scan.d.ts +87 -0
- package/dist/validate/full-scan.d.ts.map +1 -0
- package/dist/validate/full-scan.js +299 -0
- package/dist/validate/full-scan.js.map +1 -0
- package/dist/validate/index.d.ts +15 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +18 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate/marker.d.ts +89 -0
- package/dist/validate/marker.d.ts.map +1 -0
- package/dist/validate/marker.js +87 -0
- package/dist/validate/marker.js.map +1 -0
- package/dist/validate/phash.d.ts +135 -0
- package/dist/validate/phash.d.ts.map +1 -0
- package/dist/validate/phash.js +307 -0
- package/dist/validate/phash.js.map +1 -0
- package/dist/validate/render.d.ts +38 -0
- package/dist/validate/render.d.ts.map +1 -0
- package/dist/validate/render.js +155 -0
- package/dist/validate/render.js.map +1 -0
- package/dist/watch/index.d.ts +12 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +12 -0
- package/dist/watch/index.js.map +1 -0
- package/dist/watch/watcher.d.ts +65 -0
- package/dist/watch/watcher.d.ts.map +1 -0
- package/dist/watch/watcher.js +238 -0
- package/dist/watch/watcher.js.map +1 -0
- 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":"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"}
|
|
@@ -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.
|