promptloom 1.0.0

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/dist/index.cjs ADDED
@@ -0,0 +1,522 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CACHE_BOUNDARY: () => CACHE_BOUNDARY,
24
+ PromptCompiler: () => PromptCompiler,
25
+ SectionCache: () => SectionCache,
26
+ ToolCache: () => ToolCache,
27
+ checkBudget: () => checkBudget,
28
+ compileTool: () => compileTool,
29
+ compileTools: () => compileTools,
30
+ createBudgetTracker: () => createBudgetTracker,
31
+ defineTool: () => defineTool,
32
+ dynamicSection: () => dynamicSection,
33
+ estimateTokens: () => estimateTokens,
34
+ estimateTokensForFileType: () => estimateTokensForFileType,
35
+ parseTokenBudget: () => parseTokenBudget,
36
+ resolveSections: () => resolveSections,
37
+ section: () => section,
38
+ splitAtBoundary: () => splitAtBoundary,
39
+ toAnthropic: () => toAnthropic,
40
+ toAnthropicBlocks: () => toAnthropicBlocks,
41
+ toBedrock: () => toBedrock,
42
+ toOpenAI: () => toOpenAI
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/section.ts
47
+ function section(name, compute, options) {
48
+ return { name, compute, cacheBreak: false, when: options?.when };
49
+ }
50
+ function dynamicSection(name, compute, options) {
51
+ return { name, compute, cacheBreak: true, when: options?.when };
52
+ }
53
+ var SectionCache = class {
54
+ cache = /* @__PURE__ */ new Map();
55
+ get(name) {
56
+ return this.cache.get(name);
57
+ }
58
+ has(name) {
59
+ return this.cache.has(name);
60
+ }
61
+ set(name, value) {
62
+ this.cache.set(name, value);
63
+ }
64
+ clear() {
65
+ this.cache.clear();
66
+ }
67
+ get size() {
68
+ return this.cache.size;
69
+ }
70
+ };
71
+ async function resolveSections(sections, cache, context) {
72
+ const ctx = context ?? {};
73
+ const active = sections.filter((s) => !s.when || s.when(ctx));
74
+ return Promise.all(
75
+ active.map(async (s) => {
76
+ if (!s.cacheBreak && cache.has(s.name)) {
77
+ return cache.get(s.name) ?? null;
78
+ }
79
+ const value = await s.compute();
80
+ if (!s.cacheBreak) {
81
+ cache.set(s.name, value);
82
+ }
83
+ return value;
84
+ })
85
+ );
86
+ }
87
+
88
+ // src/tool.ts
89
+ var ToolCache = class {
90
+ cache = /* @__PURE__ */ new Map();
91
+ get(key) {
92
+ return this.cache.get(key);
93
+ }
94
+ set(key, tool) {
95
+ this.cache.set(key, tool);
96
+ }
97
+ has(key) {
98
+ return this.cache.has(key);
99
+ }
100
+ clear() {
101
+ this.cache.clear();
102
+ }
103
+ };
104
+ function toolCacheKey(def) {
105
+ return def.inputSchema ? `${def.name}:${JSON.stringify(def.inputSchema)}` : def.name;
106
+ }
107
+ async function resolveToolPrompt(prompt) {
108
+ return typeof prompt === "function" ? await prompt() : prompt;
109
+ }
110
+ async function compileTool(def, cache, cacheScope) {
111
+ const key = toolCacheKey(def);
112
+ const cached = cache.get(key);
113
+ if (cached) return cached;
114
+ const description = await resolveToolPrompt(def.prompt);
115
+ const compiled = {
116
+ name: def.name,
117
+ description,
118
+ input_schema: def.inputSchema,
119
+ ...cacheScope !== void 0 && cacheScope !== null ? { cache_control: { type: "ephemeral", scope: cacheScope } } : {},
120
+ ...def.deferred ? { defer_loading: true } : {}
121
+ };
122
+ cache.set(key, compiled);
123
+ return compiled;
124
+ }
125
+ async function compileTools(defs, cache, cacheScope) {
126
+ const sorted = [...defs].sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity));
127
+ return Promise.all(sorted.map((def) => compileTool(def, cache, cacheScope)));
128
+ }
129
+ var TOOL_DEFAULTS = {
130
+ concurrencySafe: false,
131
+ readOnly: false,
132
+ deferred: false
133
+ };
134
+ function defineTool(def) {
135
+ return { ...TOOL_DEFAULTS, ...def };
136
+ }
137
+
138
+ // src/tokens.ts
139
+ var DEFAULT_BYTES_PER_TOKEN = 4;
140
+ function estimateTokens(content, bytesPerToken = DEFAULT_BYTES_PER_TOKEN) {
141
+ return Math.round(content.length / bytesPerToken);
142
+ }
143
+ var EXTENSION_DENSITY = {
144
+ json: 2,
145
+ jsonl: 2,
146
+ yaml: 3,
147
+ yml: 3,
148
+ xml: 2,
149
+ csv: 3,
150
+ tsv: 3
151
+ };
152
+ function estimateTokensForFileType(content, extension) {
153
+ const bpt = EXTENSION_DENSITY[extension.toLowerCase()] ?? DEFAULT_BYTES_PER_TOKEN;
154
+ return estimateTokens(content, bpt);
155
+ }
156
+ function createBudgetTracker() {
157
+ return {
158
+ continuationCount: 0,
159
+ lastDeltaTokens: 0,
160
+ lastGlobalTurnTokens: 0,
161
+ startedAt: Date.now()
162
+ };
163
+ }
164
+ function checkBudget(tracker, currentTokens, config) {
165
+ const { budget, completionThreshold = 0.9, diminishingThreshold = 500 } = config;
166
+ if (budget <= 0) {
167
+ return { action: "stop", reason: "no_budget", pct: 0 };
168
+ }
169
+ const pct = Math.round(currentTokens / budget * 100);
170
+ const deltaSinceLastCheck = currentTokens - tracker.lastGlobalTurnTokens;
171
+ const isDiminishing = tracker.continuationCount >= 3 && deltaSinceLastCheck < diminishingThreshold && tracker.lastDeltaTokens < diminishingThreshold;
172
+ tracker.lastDeltaTokens = deltaSinceLastCheck;
173
+ tracker.lastGlobalTurnTokens = currentTokens;
174
+ if (isDiminishing) {
175
+ return { action: "stop", reason: "diminishing_returns", pct };
176
+ }
177
+ if (currentTokens >= budget * completionThreshold) {
178
+ return { action: "stop", reason: "budget_reached", pct };
179
+ }
180
+ tracker.continuationCount++;
181
+ const fmt = (n) => new Intl.NumberFormat("en-US").format(n);
182
+ return {
183
+ action: "continue",
184
+ pct,
185
+ nudgeMessage: `Stopped at ${pct}% of token target (${fmt(currentTokens)} / ${fmt(budget)}). Keep working \u2014 do not summarize.`
186
+ };
187
+ }
188
+ function parseTokenBudget(text) {
189
+ const MULTIPLIERS = {
190
+ k: 1e3,
191
+ m: 1e6,
192
+ b: 1e9
193
+ };
194
+ const startMatch = text.match(/^\s*\+(\d+(?:\.\d+)?)\s*(k|m|b)\b/i);
195
+ if (startMatch) {
196
+ return parseFloat(startMatch[1]) * MULTIPLIERS[startMatch[2].toLowerCase()];
197
+ }
198
+ const endMatch = text.match(/\s\+(\d+(?:\.\d+)?)\s*(k|m|b)\s*[.!?]?\s*$/i);
199
+ if (endMatch) {
200
+ return parseFloat(endMatch[1]) * MULTIPLIERS[endMatch[2].toLowerCase()];
201
+ }
202
+ const verboseMatch = text.match(/\b(?:use|spend)\s+(\d+(?:\.\d+)?)\s*(k|m|b)\s*tokens?\b/i);
203
+ if (verboseMatch) {
204
+ return parseFloat(verboseMatch[1]) * MULTIPLIERS[verboseMatch[2].toLowerCase()];
205
+ }
206
+ return null;
207
+ }
208
+
209
+ // src/compiler.ts
210
+ var PromptCompiler = class {
211
+ entries = [];
212
+ tools = [];
213
+ sectionCache;
214
+ toolCache;
215
+ options;
216
+ constructor(options = {}) {
217
+ this.options = {
218
+ defaultCacheScope: options.defaultCacheScope ?? "org",
219
+ dynamicCacheScope: options.dynamicCacheScope ?? null,
220
+ bytesPerToken: options.bytesPerToken ?? 4,
221
+ enableGlobalCache: options.enableGlobalCache ?? false
222
+ };
223
+ this.sectionCache = new SectionCache();
224
+ this.toolCache = new ToolCache();
225
+ }
226
+ // ─── Zone API ────────────────────────────────────────────────
227
+ /**
228
+ * Start a new cache zone. All sections after this marker are compiled
229
+ * into a single CacheBlock with the specified scope.
230
+ *
231
+ * Generalizes Claude Code's `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` to support
232
+ * N zones instead of just 2.
233
+ *
234
+ * @example
235
+ * pc.zone(null) // no-cache zone (attribution, headers)
236
+ * pc.zone('global') // globally cacheable (identity, rules)
237
+ * pc.zone('org') // org-level cacheable
238
+ * pc.zone(null) // session-specific (dynamic context)
239
+ */
240
+ zone(scope) {
241
+ this.entries.push({ __type: "zone", scope });
242
+ return this;
243
+ }
244
+ /**
245
+ * Insert a cache boundary. Shorthand for starting a new zone with
246
+ * `dynamicCacheScope` (default: null).
247
+ *
248
+ * Equivalent to Claude Code's `SYSTEM_PROMPT_DYNAMIC_BOUNDARY`.
249
+ * Only effective when `enableGlobalCache` is true (otherwise a no-op
250
+ * since there's no scope difference to split on).
251
+ *
252
+ * For explicit multi-zone control, use `zone()` instead.
253
+ */
254
+ boundary() {
255
+ if (this.options.enableGlobalCache) {
256
+ return this.zone(this.options.dynamicCacheScope);
257
+ }
258
+ return this;
259
+ }
260
+ // ─── Section API ─────────────────────────────────────────────
261
+ /**
262
+ * Add a static section. Computed once and cached for the session.
263
+ *
264
+ * Equivalent to Claude Code's `systemPromptSection()`.
265
+ *
266
+ * @param options.when - Conditional predicate. Section is skipped when false.
267
+ */
268
+ static(name, content, options) {
269
+ const compute = typeof content === "string" ? () => content : content;
270
+ this.entries.push({ name, compute, cacheBreak: false, when: options?.when });
271
+ return this;
272
+ }
273
+ /**
274
+ * Add a dynamic section. Recomputed every compile() call.
275
+ *
276
+ * Equivalent to Claude Code's `DANGEROUS_uncachedSystemPromptSection()`.
277
+ *
278
+ * @param options.when - Conditional predicate. Section is skipped when false.
279
+ */
280
+ dynamic(name, compute, options) {
281
+ this.entries.push({ name, compute, cacheBreak: true, when: options?.when });
282
+ return this;
283
+ }
284
+ // ─── Tool API ────────────────────────────────────────────────
285
+ /**
286
+ * Register a tool with an embedded prompt.
287
+ *
288
+ * Tools with `deferred: true` are compiled separately and excluded
289
+ * from the main tool list. They can be discovered on demand.
290
+ *
291
+ * Tools with `order` fields are sorted for stable serialization
292
+ * (cache hit optimization).
293
+ */
294
+ tool(def) {
295
+ this.tools.push(def);
296
+ return this;
297
+ }
298
+ // ─── Compile ─────────────────────────────────────────────────
299
+ /**
300
+ * Compile the prompt: resolve sections per zone, compile tools,
301
+ * separate deferred tools, and estimate tokens.
302
+ *
303
+ * @param context - Optional context for conditional section evaluation.
304
+ * Sections with `when` predicates are evaluated against this.
305
+ */
306
+ async compile(context) {
307
+ const zoneGroups = this.groupIntoZones();
308
+ const blocks = [];
309
+ for (const group of zoneGroups) {
310
+ const resolved = await resolveSections(group.sections, this.sectionCache, context);
311
+ const text2 = resolved.filter((s) => s !== null).join("\n\n");
312
+ if (text2) {
313
+ blocks.push({ text: text2, cacheScope: group.scope });
314
+ }
315
+ }
316
+ const inlineToolDefs = this.tools.filter((t) => !t.deferred);
317
+ const deferredToolDefs = this.tools.filter((t) => t.deferred);
318
+ const compiledTools = await compileTools(inlineToolDefs, this.toolCache);
319
+ const compiledDeferred = await compileTools(deferredToolDefs, this.toolCache);
320
+ const bpt = this.options.bytesPerToken;
321
+ const systemPromptTokens = blocks.reduce(
322
+ (sum, b) => sum + estimateTokens(b.text, bpt),
323
+ 0
324
+ );
325
+ const toolTokens = compiledTools.reduce(
326
+ (sum, t) => sum + this.estimateToolTokens(t),
327
+ 0
328
+ );
329
+ const deferredToolTokens = compiledDeferred.reduce(
330
+ (sum, t) => sum + this.estimateToolTokens(t),
331
+ 0
332
+ );
333
+ const tokens = {
334
+ systemPrompt: systemPromptTokens,
335
+ tools: toolTokens,
336
+ deferredTools: deferredToolTokens,
337
+ total: systemPromptTokens + toolTokens
338
+ };
339
+ const text = blocks.map((b) => b.text).join("\n\n");
340
+ return {
341
+ blocks,
342
+ tools: compiledTools,
343
+ deferredTools: compiledDeferred,
344
+ tokens,
345
+ text
346
+ };
347
+ }
348
+ // ─── Cache Management ────────────────────────────────────────
349
+ /** Clear all caches. Call on `/clear` or `/compact`. */
350
+ clearCache() {
351
+ this.sectionCache.clear();
352
+ this.toolCache.clear();
353
+ }
354
+ /** Clear only section cache (tools stay cached) */
355
+ clearSectionCache() {
356
+ this.sectionCache.clear();
357
+ }
358
+ /** Clear only tool cache (forces prompt re-resolution) */
359
+ clearToolCache() {
360
+ this.toolCache.clear();
361
+ }
362
+ // ─── Inspection ──────────────────────────────────────────────
363
+ /** Get the number of registered sections */
364
+ get sectionCount() {
365
+ return this.entries.filter((e) => !("__type" in e)).length;
366
+ }
367
+ /** Get the number of registered tools (inline + deferred) */
368
+ get toolCount() {
369
+ return this.tools.length;
370
+ }
371
+ /** List registered section names with their types */
372
+ listSections() {
373
+ return this.entries.map((e) => {
374
+ if ("__type" in e) {
375
+ return { name: `zone:${e.scope ?? "none"}`, type: "zone" };
376
+ }
377
+ return {
378
+ name: e.name,
379
+ type: e.cacheBreak ? "dynamic" : "static"
380
+ };
381
+ });
382
+ }
383
+ /** List registered tool names */
384
+ listTools() {
385
+ return this.tools.map((t) => t.name);
386
+ }
387
+ // ─── Internal ────────────────────────────────────────────────
388
+ /**
389
+ * Group entries into zones.
390
+ *
391
+ * Entries before any zone marker belong to the "initial zone" whose
392
+ * scope is determined by `enableGlobalCache` and `defaultCacheScope`.
393
+ */
394
+ groupIntoZones() {
395
+ const initialScope = this.options.enableGlobalCache ? "global" : this.options.defaultCacheScope;
396
+ const zones = [];
397
+ let currentZone = {
398
+ scope: initialScope,
399
+ sections: []
400
+ };
401
+ for (const entry of this.entries) {
402
+ if ("__type" in entry) {
403
+ if (currentZone.sections.length > 0) {
404
+ zones.push(currentZone);
405
+ }
406
+ currentZone = { scope: entry.scope, sections: [] };
407
+ } else {
408
+ currentZone.sections.push(entry);
409
+ }
410
+ }
411
+ if (currentZone.sections.length > 0) {
412
+ zones.push(currentZone);
413
+ }
414
+ return zones;
415
+ }
416
+ estimateToolTokens(tool) {
417
+ return estimateTokens(tool.description, this.options.bytesPerToken) + estimateTokens(JSON.stringify(tool.input_schema), 2);
418
+ }
419
+ };
420
+
421
+ // src/boundary.ts
422
+ var CACHE_BOUNDARY = "__PROMPTLOOM_CACHE_BOUNDARY__";
423
+ function splitAtBoundary(prompt, options = {}) {
424
+ const {
425
+ staticScope = "global",
426
+ dynamicScope = null,
427
+ fallbackScope = "org"
428
+ } = options;
429
+ const boundaryIndex = prompt.indexOf(CACHE_BOUNDARY);
430
+ if (boundaryIndex === -1) {
431
+ return prompt.trim() ? [{ text: prompt, cacheScope: fallbackScope }] : [];
432
+ }
433
+ const before = prompt.slice(0, boundaryIndex).trim();
434
+ const after = prompt.slice(boundaryIndex + CACHE_BOUNDARY.length).trim();
435
+ const blocks = [];
436
+ if (before) {
437
+ blocks.push({ text: before, cacheScope: staticScope });
438
+ }
439
+ if (after) {
440
+ blocks.push({ text: after, cacheScope: dynamicScope });
441
+ }
442
+ return blocks;
443
+ }
444
+
445
+ // src/providers.ts
446
+ function toAnthropic(result) {
447
+ return {
448
+ system: result.blocks.map((block) => ({
449
+ type: "text",
450
+ text: block.text,
451
+ ...block.cacheScope !== null ? { cache_control: { type: "ephemeral" } } : {}
452
+ })),
453
+ tools: [...result.tools, ...result.deferredTools].map((tool) => ({
454
+ name: tool.name,
455
+ description: tool.description,
456
+ input_schema: tool.input_schema,
457
+ ...tool.cache_control ? { cache_control: tool.cache_control } : {},
458
+ ...tool.defer_loading ? { defer_loading: true } : {}
459
+ }))
460
+ };
461
+ }
462
+ function toOpenAI(result) {
463
+ return {
464
+ system: result.text,
465
+ tools: result.tools.map((tool) => ({
466
+ type: "function",
467
+ function: {
468
+ name: tool.name,
469
+ description: tool.description,
470
+ parameters: tool.input_schema
471
+ }
472
+ }))
473
+ };
474
+ }
475
+ function toBedrock(result) {
476
+ return {
477
+ system: result.blocks.flatMap((block) => [
478
+ { text: block.text },
479
+ ...block.cacheScope !== null ? [{ cachePoint: { type: "default" } }] : []
480
+ ]),
481
+ toolConfig: {
482
+ tools: result.tools.map((tool) => ({
483
+ toolSpec: {
484
+ name: tool.name,
485
+ description: tool.description,
486
+ inputSchema: { jsonSchema: tool.input_schema }
487
+ }
488
+ }))
489
+ }
490
+ };
491
+ }
492
+ function toAnthropicBlocks(blocks, enableCaching = true) {
493
+ return blocks.map((block) => ({
494
+ type: "text",
495
+ text: block.text,
496
+ ...enableCaching && block.cacheScope !== null ? { cache_control: { type: "ephemeral" } } : {}
497
+ }));
498
+ }
499
+ // Annotate the CommonJS export names for ESM import in node:
500
+ 0 && (module.exports = {
501
+ CACHE_BOUNDARY,
502
+ PromptCompiler,
503
+ SectionCache,
504
+ ToolCache,
505
+ checkBudget,
506
+ compileTool,
507
+ compileTools,
508
+ createBudgetTracker,
509
+ defineTool,
510
+ dynamicSection,
511
+ estimateTokens,
512
+ estimateTokensForFileType,
513
+ parseTokenBudget,
514
+ resolveSections,
515
+ section,
516
+ splitAtBoundary,
517
+ toAnthropic,
518
+ toAnthropicBlocks,
519
+ toBedrock,
520
+ toOpenAI
521
+ });
522
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/section.ts","../src/tool.ts","../src/tokens.ts","../src/compiler.ts","../src/boundary.ts","../src/providers.ts"],"sourcesContent":["/**\n * promptloom — Prompt Compiler\n *\n * Weave production-grade LLM prompts with cache boundaries,\n * tool injection, and token budgeting.\n *\n * Reverse-engineered from Claude Code's 7-layer prompt architecture.\n *\n * @example\n * ```ts\n * import { PromptCompiler } from 'promptloom'\n *\n * const pc = new PromptCompiler()\n *\n * pc.zone(null) // no-cache header\n * pc.static('header', 'x-model: claude')\n *\n * pc.zone('global') // globally cacheable\n * pc.static('identity', 'You are a coding assistant.')\n * pc.static('rules', 'Follow clean code principles.')\n *\n * pc.zone(null) // session-specific\n * pc.dynamic('context', async () => `Branch: main`)\n * pc.static('opus_only', 'Use extended thinking.', {\n * when: (ctx) => ctx.model?.includes('opus'),\n * })\n *\n * pc.tool({\n * name: 'read_file',\n * prompt: 'Read a file. Always use absolute paths.',\n * inputSchema: { type: 'object', properties: { path: { type: 'string' } } },\n * })\n *\n * pc.tool({\n * name: 'web_search',\n * prompt: 'Search the web.',\n * inputSchema: { type: 'object', properties: { query: { type: 'string' } } },\n * deferred: true, // loaded on demand, not in system prompt\n * })\n *\n * const result = await pc.compile({ model: 'claude-opus-4-6' })\n * ```\n */\n\n// Core\nexport { PromptCompiler } from './compiler.ts'\n\n// Section helpers\nexport { section, dynamicSection, SectionCache, resolveSections } from './section.ts'\n\n// Cache boundary (low-level utility, kept for backward compat)\nexport { CACHE_BOUNDARY, splitAtBoundary } from './boundary.ts'\n\n// Provider formatters\nexport { toAnthropic, toOpenAI, toBedrock, toAnthropicBlocks } from './providers.ts'\n\n// Tool helpers\nexport { defineTool, ToolCache, compileTool, compileTools } from './tool.ts'\n\n// Token utilities\nexport {\n estimateTokens,\n estimateTokensForFileType,\n createBudgetTracker,\n checkBudget,\n parseTokenBudget,\n} from './tokens.ts'\n\n// Types\nexport type {\n Section,\n ComputeFn,\n WhenPredicate,\n CacheScope,\n CacheBlock,\n CompileContext,\n SectionOptions,\n ZoneMarker,\n Entry,\n ToolDef,\n CompiledTool,\n JsonSchema,\n TokenEstimate,\n TokenBudgetConfig,\n CompilerOptions,\n CompileResult,\n ProviderFormat,\n} from './types.ts'\nexport type { BudgetTracker, BudgetDecision } from './tokens.ts'\nexport type { SplitOptions } from './boundary.ts'\nexport type {\n AnthropicCacheControl,\n AnthropicTextBlock,\n AnthropicTool,\n OpenAITool,\n BedrockSystemBlock,\n BedrockTool,\n} from './providers.ts'\n","/**\n * promptloom — Section management\n *\n * Implements the two-tier section system from Claude Code:\n * - `section()`: cached within session, computed once\n * - `dynamicSection()`: recomputed every compile() call (cacheBreak: true)\n *\n * Extended with conditional inclusion via `when` predicates,\n * mirroring Claude Code's `feature()` and `process.env.USER_TYPE` gates.\n */\n\nimport type { CompileContext, ComputeFn, Section, SectionOptions, WhenPredicate } from './types.ts'\n\n/**\n * Create a static section. Content is computed once and cached.\n *\n * Use for: identity prompts, rules, style guides — anything that\n * doesn't change between turns.\n */\nexport function section(name: string, compute: ComputeFn, options?: SectionOptions): Section {\n return { name, compute, cacheBreak: false, when: options?.when }\n}\n\n/**\n * Create a dynamic section. Content is recomputed every compile() call.\n *\n * Claude Code calls this `DANGEROUS_uncachedSystemPromptSection` —\n * the naming reflects that dynamic sections break prompt cache stability.\n *\n * Use for: MCP server instructions, real-time status, anything that\n * changes between turns.\n */\nexport function dynamicSection(name: string, compute: ComputeFn, options?: SectionOptions): Section {\n return { name, compute, cacheBreak: true, when: options?.when }\n}\n\n// ─── Section Cache ───────────────────────────────────────────────\n\n/**\n * In-memory section cache.\n *\n * Mirrors `STATE.systemPromptSectionCache` from Claude Code.\n * Static sections are computed once per session and cached here.\n * Dynamic sections (cacheBreak: true) bypass the cache entirely.\n */\nexport class SectionCache {\n private cache = new Map<string, string | null>()\n\n get(name: string): string | null | undefined {\n return this.cache.get(name)\n }\n\n has(name: string): boolean {\n return this.cache.has(name)\n }\n\n set(name: string, value: string | null): void {\n this.cache.set(name, value)\n }\n\n clear(): void {\n this.cache.clear()\n }\n\n get size(): number {\n return this.cache.size\n }\n}\n\n/**\n * Resolve an array of sections, using cache for static ones.\n *\n * Mirrors `resolveSystemPromptSections()` from Claude Code:\n * - Sections with a `when` predicate are filtered by compile context\n * - Static sections: check cache first, compute if missing\n * - Dynamic sections: always recompute\n * - All sections resolved in parallel via Promise.all\n */\nexport async function resolveSections(\n sections: Section[],\n cache: SectionCache,\n context?: CompileContext,\n): Promise<(string | null)[]> {\n // Filter by when predicate\n const ctx = context ?? {}\n const active = sections.filter((s) => !s.when || s.when(ctx))\n\n return Promise.all(\n active.map(async (s) => {\n // Dynamic sections always recompute\n if (!s.cacheBreak && cache.has(s.name)) {\n return cache.get(s.name) ?? null\n }\n\n const value = await s.compute()\n // Only cache static sections\n if (!s.cacheBreak) {\n cache.set(s.name, value)\n }\n return value\n }),\n )\n}\n","/**\n * promptloom — Tool prompt management\n *\n * In Claude Code, every tool has its own `prompt.ts` — a \"user manual\"\n * written for the LLM. Tool prompts are:\n * - Computed once per session and cached (avoids mid-session drift)\n * - Injected into the tool's `description` field in the API request\n * - Can be static strings or async functions\n * - Optionally deferred (loaded on demand via ToolSearchTool)\n *\n * This module provides the tool registry and compilation logic.\n */\n\nimport type { CompiledTool, JsonSchema, ToolDef, CacheScope } from './types.ts'\n\n/**\n * Cache for resolved tool prompts.\n *\n * Mirrors Claude Code's `getToolSchemaCache()`:\n * \"Prevents mid-session GrowthBook flips from churning serialized tools array.\n * One generation per session unless cache is cleared.\"\n */\nexport class ToolCache {\n private cache = new Map<string, CompiledTool>()\n\n get(key: string): CompiledTool | undefined {\n return this.cache.get(key)\n }\n\n set(key: string, tool: CompiledTool): void {\n this.cache.set(key, tool)\n }\n\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n clear(): void {\n this.cache.clear()\n }\n}\n\n/**\n * Generate a stable cache key for a tool.\n *\n * Includes the inputSchema hash so tools with dynamic schemas\n * get separate cache entries.\n */\nfunction toolCacheKey(def: ToolDef): string {\n return def.inputSchema\n ? `${def.name}:${JSON.stringify(def.inputSchema)}`\n : def.name\n}\n\n/**\n * Resolve a tool's prompt (string or async function) to a string.\n */\nasync function resolveToolPrompt(prompt: ToolDef['prompt']): Promise<string> {\n return typeof prompt === 'function' ? await prompt() : prompt\n}\n\n/**\n * Compile a single tool definition into API-ready format.\n *\n * Uses session-level cache: first call computes, subsequent calls return cached.\n * Deferred tools get `defer_loading: true` in their schema.\n */\nexport async function compileTool(\n def: ToolDef,\n cache: ToolCache,\n cacheScope?: CacheScope,\n): Promise<CompiledTool> {\n const key = toolCacheKey(def)\n const cached = cache.get(key)\n if (cached) return cached\n\n const description = await resolveToolPrompt(def.prompt)\n\n const compiled: CompiledTool = {\n name: def.name,\n description,\n input_schema: def.inputSchema,\n ...(cacheScope !== undefined && cacheScope !== null\n ? { cache_control: { type: 'ephemeral' as const, scope: cacheScope } }\n : {}),\n ...(def.deferred ? { defer_loading: true as const } : {}),\n }\n\n cache.set(key, compiled)\n return compiled\n}\n\n/**\n * Compile all tool definitions, resolving prompts in parallel.\n *\n * Tools are sorted by explicit `order` field first, then by insertion order.\n * This ensures stable serialization for prompt cache hits.\n */\nexport async function compileTools(\n defs: ToolDef[],\n cache: ToolCache,\n cacheScope?: CacheScope,\n): Promise<CompiledTool[]> {\n // Sort by explicit order (stable sort preserves insertion order for equal keys)\n const sorted = [...defs].sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity))\n return Promise.all(sorted.map((def) => compileTool(def, cache, cacheScope)))\n}\n\n// ─── Tool Builder Helpers ────────────────────────────────────────\n\n/**\n * Fail-closed defaults for tool safety flags.\n *\n * Mirrors Claude Code's TOOL_DEFAULTS:\n * \"If a tool author forgets to declare safety, assume it's unsafe.\"\n */\nconst TOOL_DEFAULTS = {\n concurrencySafe: false,\n readOnly: false,\n deferred: false,\n} as const\n\n/**\n * Create a tool definition with safe defaults.\n */\nexport function defineTool(def: ToolDef): ToolDef {\n return { ...TOOL_DEFAULTS, ...def }\n}\n","/**\n * promptloom — Token estimation and budget utilities\n *\n * Mirrors Claude Code's token counting strategy:\n * - Rough estimation based on byte length (fast, no API call)\n * - File-type-aware estimation (JSON is denser → 2 bytes/token)\n * - Budget tracking with diminishing returns detection\n * - Natural language budget parsing (+500k, spend 2M tokens)\n */\n\nimport type { TokenBudgetConfig } from './types.ts'\n\n// ─── Rough Estimation ────────────────────────────────────────────\n\nconst DEFAULT_BYTES_PER_TOKEN = 4\n\n/**\n * Estimate token count from a string using byte-length heuristic.\n *\n * Claude Code uses 4 bytes/token as default, 2 for dense formats like JSON.\n * This is intentionally approximate — accurate counting requires an API call.\n */\nexport function estimateTokens(\n content: string,\n bytesPerToken = DEFAULT_BYTES_PER_TOKEN,\n): number {\n return Math.round(content.length / bytesPerToken)\n}\n\n/** Bytes-per-token by file extension. Dense formats use fewer bytes. */\nconst EXTENSION_DENSITY: Record<string, number> = {\n json: 2,\n jsonl: 2,\n yaml: 3,\n yml: 3,\n xml: 2,\n csv: 3,\n tsv: 3,\n}\n\n/**\n * Estimate tokens with file-type awareness.\n * JSON/XML are denser (more tokens per byte) than natural language.\n */\nexport function estimateTokensForFileType(\n content: string,\n extension: string,\n): number {\n const bpt = EXTENSION_DENSITY[extension.toLowerCase()] ?? DEFAULT_BYTES_PER_TOKEN\n return estimateTokens(content, bpt)\n}\n\n// ─── Budget Tracking ─────────────────────────────────────────────\n\nexport interface BudgetTracker {\n continuationCount: number\n lastDeltaTokens: number\n lastGlobalTurnTokens: number\n startedAt: number\n}\n\nexport type BudgetDecision =\n | { action: 'continue'; nudgeMessage: string; pct: number }\n | { action: 'stop'; reason: 'budget_reached' | 'diminishing_returns' | 'no_budget'; pct: number }\n\nexport function createBudgetTracker(): BudgetTracker {\n return {\n continuationCount: 0,\n lastDeltaTokens: 0,\n lastGlobalTurnTokens: 0,\n startedAt: Date.now(),\n }\n}\n\n/**\n * Check token budget and decide whether to continue.\n *\n * Mirrors Claude Code's `checkTokenBudget()`:\n * - Continue if below completion threshold (default 90%)\n * - Detect diminishing returns (3+ continuations with tiny deltas)\n * - Return nudge messages to keep the model working\n */\nexport function checkBudget(\n tracker: BudgetTracker,\n currentTokens: number,\n config: TokenBudgetConfig,\n): BudgetDecision {\n const { budget, completionThreshold = 0.9, diminishingThreshold = 500 } = config\n\n if (budget <= 0) {\n return { action: 'stop', reason: 'no_budget', pct: 0 }\n }\n\n const pct = Math.round((currentTokens / budget) * 100)\n const deltaSinceLastCheck = currentTokens - tracker.lastGlobalTurnTokens\n\n // Detect diminishing returns: 3+ continuations with tiny deltas\n const isDiminishing =\n tracker.continuationCount >= 3 &&\n deltaSinceLastCheck < diminishingThreshold &&\n tracker.lastDeltaTokens < diminishingThreshold\n\n // Update tracker state\n tracker.lastDeltaTokens = deltaSinceLastCheck\n tracker.lastGlobalTurnTokens = currentTokens\n\n if (isDiminishing) {\n return { action: 'stop', reason: 'diminishing_returns', pct }\n }\n\n if (currentTokens >= budget * completionThreshold) {\n return { action: 'stop', reason: 'budget_reached', pct }\n }\n\n tracker.continuationCount++\n const fmt = (n: number) => new Intl.NumberFormat('en-US').format(n)\n return {\n action: 'continue',\n pct,\n nudgeMessage: `Stopped at ${pct}% of token target (${fmt(currentTokens)} / ${fmt(budget)}). Keep working — do not summarize.`,\n }\n}\n\n// ─── Token Budget Parsing ────────────────────────────────────────\n\n/**\n * Parse a token budget from natural language.\n *\n * Mirrors Claude Code's `parseTokenBudget()` from `src/utils/tokenBudget.ts`.\n *\n * Supported syntaxes:\n * - Shorthand at start: \"+500k\", \"+2M\", \"+1.5b\"\n * - Shorthand at end: \"do this task. +500k.\"\n * - Verbose: \"use 500k tokens\", \"spend 2M tokens\"\n *\n * @returns The parsed budget in tokens, or null if no budget found.\n *\n * @example\n * parseTokenBudget('+500k') // 500_000\n * parseTokenBudget('spend 2M tokens') // 2_000_000\n * parseTokenBudget('+1.5b') // 1_500_000_000\n * parseTokenBudget('hello world') // null\n */\nexport function parseTokenBudget(text: string): number | null {\n const MULTIPLIERS: Record<string, number> = {\n k: 1_000,\n m: 1_000_000,\n b: 1_000_000_000,\n }\n\n // Shorthand at start: +500k\n const startMatch = text.match(/^\\s*\\+(\\d+(?:\\.\\d+)?)\\s*(k|m|b)\\b/i)\n if (startMatch) {\n return parseFloat(startMatch[1]!) * MULTIPLIERS[startMatch[2]!.toLowerCase()]!\n }\n\n // Shorthand at end: ... do this. +500k.\n const endMatch = text.match(/\\s\\+(\\d+(?:\\.\\d+)?)\\s*(k|m|b)\\s*[.!?]?\\s*$/i)\n if (endMatch) {\n return parseFloat(endMatch[1]!) * MULTIPLIERS[endMatch[2]!.toLowerCase()]!\n }\n\n // Verbose: use 500k tokens / spend 2M tokens\n const verboseMatch = text.match(/\\b(?:use|spend)\\s+(\\d+(?:\\.\\d+)?)\\s*(k|m|b)\\s*tokens?\\b/i)\n if (verboseMatch) {\n return parseFloat(verboseMatch[1]!) * MULTIPLIERS[verboseMatch[2]!.toLowerCase()]!\n }\n\n return null\n}\n","/**\n * promptloom — The Prompt Compiler\n *\n * Implements Claude Code's prompt assembly pattern, generalized:\n *\n * 1. Multi-zone cache scoping (N blocks, not just 2)\n * 2. Conditional section inclusion (when predicates)\n * 3. Static/dynamic section caching\n * 4. Tool schemas with embedded prompts and deferred loading\n * 5. Stable tool ordering for cache hits\n * 6. Token estimation and budget tracking\n *\n * Usage:\n *\n * const pc = new PromptCompiler()\n *\n * // Zone 1: no-cache header\n * pc.zone(null)\n * pc.static('attribution', 'x-model: claude')\n *\n * // Zone 2: globally cacheable\n * pc.zone('global')\n * pc.static('identity', 'You are a coding assistant.')\n * pc.static('rules', () => loadRules())\n *\n * // Zone 3: session-specific\n * pc.zone(null)\n * pc.dynamic('git', async () => gitStatus())\n * pc.static('opus_only', 'Use extended thinking.', {\n * when: (ctx) => ctx.model?.includes('opus'),\n * })\n *\n * // Tools\n * pc.tool({ name: 'bash', prompt: '...', inputSchema: {...} })\n * pc.tool({ name: 'rare_tool', prompt: '...', inputSchema: {...}, deferred: true })\n *\n * const result = await pc.compile({ model: 'claude-opus-4-6' })\n */\n\nimport type {\n CacheBlock,\n CacheScope,\n CompileContext,\n CompileResult,\n CompilerOptions,\n ComputeFn,\n Entry,\n Section,\n SectionOptions,\n TokenEstimate,\n ToolDef,\n ZoneMarker,\n} from './types.ts'\nimport { SectionCache, resolveSections } from './section.ts'\nimport { ToolCache, compileTools } from './tool.ts'\nimport { estimateTokens } from './tokens.ts'\n\nexport class PromptCompiler {\n private entries: Entry[] = []\n private tools: ToolDef[] = []\n private sectionCache: SectionCache\n private toolCache: ToolCache\n private options: Required<CompilerOptions>\n\n constructor(options: CompilerOptions = {}) {\n this.options = {\n defaultCacheScope: options.defaultCacheScope ?? 'org',\n dynamicCacheScope: options.dynamicCacheScope ?? null,\n bytesPerToken: options.bytesPerToken ?? 4,\n enableGlobalCache: options.enableGlobalCache ?? false,\n }\n this.sectionCache = new SectionCache()\n this.toolCache = new ToolCache()\n }\n\n // ─── Zone API ────────────────────────────────────────────────\n\n /**\n * Start a new cache zone. All sections after this marker are compiled\n * into a single CacheBlock with the specified scope.\n *\n * Generalizes Claude Code's `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` to support\n * N zones instead of just 2.\n *\n * @example\n * pc.zone(null) // no-cache zone (attribution, headers)\n * pc.zone('global') // globally cacheable (identity, rules)\n * pc.zone('org') // org-level cacheable\n * pc.zone(null) // session-specific (dynamic context)\n */\n zone(scope: CacheScope): this {\n this.entries.push({ __type: 'zone', scope })\n return this\n }\n\n /**\n * Insert a cache boundary. Shorthand for starting a new zone with\n * `dynamicCacheScope` (default: null).\n *\n * Equivalent to Claude Code's `SYSTEM_PROMPT_DYNAMIC_BOUNDARY`.\n * Only effective when `enableGlobalCache` is true (otherwise a no-op\n * since there's no scope difference to split on).\n *\n * For explicit multi-zone control, use `zone()` instead.\n */\n boundary(): this {\n if (this.options.enableGlobalCache) {\n return this.zone(this.options.dynamicCacheScope)\n }\n // When global cache is disabled, boundary is a no-op\n // (all sections use defaultCacheScope anyway)\n return this\n }\n\n // ─── Section API ─────────────────────────────────────────────\n\n /**\n * Add a static section. Computed once and cached for the session.\n *\n * Equivalent to Claude Code's `systemPromptSection()`.\n *\n * @param options.when - Conditional predicate. Section is skipped when false.\n */\n static(name: string, content: string | ComputeFn, options?: SectionOptions): this {\n const compute = typeof content === 'string' ? () => content : content\n this.entries.push({ name, compute, cacheBreak: false, when: options?.when })\n return this\n }\n\n /**\n * Add a dynamic section. Recomputed every compile() call.\n *\n * Equivalent to Claude Code's `DANGEROUS_uncachedSystemPromptSection()`.\n *\n * @param options.when - Conditional predicate. Section is skipped when false.\n */\n dynamic(name: string, compute: ComputeFn, options?: SectionOptions): this {\n this.entries.push({ name, compute, cacheBreak: true, when: options?.when })\n return this\n }\n\n // ─── Tool API ────────────────────────────────────────────────\n\n /**\n * Register a tool with an embedded prompt.\n *\n * Tools with `deferred: true` are compiled separately and excluded\n * from the main tool list. They can be discovered on demand.\n *\n * Tools with `order` fields are sorted for stable serialization\n * (cache hit optimization).\n */\n tool(def: ToolDef): this {\n this.tools.push(def)\n return this\n }\n\n // ─── Compile ─────────────────────────────────────────────────\n\n /**\n * Compile the prompt: resolve sections per zone, compile tools,\n * separate deferred tools, and estimate tokens.\n *\n * @param context - Optional context for conditional section evaluation.\n * Sections with `when` predicates are evaluated against this.\n */\n async compile(context?: CompileContext): Promise<CompileResult> {\n // 1. Group entries into zones\n const zoneGroups = this.groupIntoZones()\n\n // 2. Resolve each zone's sections → CacheBlock[]\n const blocks: CacheBlock[] = []\n for (const group of zoneGroups) {\n const resolved = await resolveSections(group.sections, this.sectionCache, context)\n const text = resolved.filter((s): s is string => s !== null).join('\\n\\n')\n if (text) {\n blocks.push({ text, cacheScope: group.scope })\n }\n }\n\n // 3. Separate inline vs deferred tools\n const inlineToolDefs = this.tools.filter((t) => !t.deferred)\n const deferredToolDefs = this.tools.filter((t) => t.deferred)\n\n // 4. Compile tools (prompts resolved and cached, sorted by order)\n const compiledTools = await compileTools(inlineToolDefs, this.toolCache)\n const compiledDeferred = await compileTools(deferredToolDefs, this.toolCache)\n\n // 5. Estimate tokens\n const bpt = this.options.bytesPerToken\n const systemPromptTokens = blocks.reduce(\n (sum, b) => sum + estimateTokens(b.text, bpt),\n 0,\n )\n const toolTokens = compiledTools.reduce(\n (sum, t) => sum + this.estimateToolTokens(t),\n 0,\n )\n const deferredToolTokens = compiledDeferred.reduce(\n (sum, t) => sum + this.estimateToolTokens(t),\n 0,\n )\n const tokens: TokenEstimate = {\n systemPrompt: systemPromptTokens,\n tools: toolTokens,\n deferredTools: deferredToolTokens,\n total: systemPromptTokens + toolTokens,\n }\n\n // 6. Build the plain text version\n const text = blocks.map((b) => b.text).join('\\n\\n')\n\n return {\n blocks,\n tools: compiledTools,\n deferredTools: compiledDeferred,\n tokens,\n text,\n }\n }\n\n // ─── Cache Management ────────────────────────────────────────\n\n /** Clear all caches. Call on `/clear` or `/compact`. */\n clearCache(): void {\n this.sectionCache.clear()\n this.toolCache.clear()\n }\n\n /** Clear only section cache (tools stay cached) */\n clearSectionCache(): void {\n this.sectionCache.clear()\n }\n\n /** Clear only tool cache (forces prompt re-resolution) */\n clearToolCache(): void {\n this.toolCache.clear()\n }\n\n // ─── Inspection ──────────────────────────────────────────────\n\n /** Get the number of registered sections */\n get sectionCount(): number {\n return this.entries.filter((e): e is Section => !('__type' in e)).length\n }\n\n /** Get the number of registered tools (inline + deferred) */\n get toolCount(): number {\n return this.tools.length\n }\n\n /** List registered section names with their types */\n listSections(): Array<{ name: string; type: 'static' | 'dynamic' | 'zone' }> {\n return this.entries.map((e) => {\n if ('__type' in e) {\n return { name: `zone:${e.scope ?? 'none'}`, type: 'zone' as const }\n }\n return {\n name: e.name,\n type: e.cacheBreak ? 'dynamic' as const : 'static' as const,\n }\n })\n }\n\n /** List registered tool names */\n listTools(): string[] {\n return this.tools.map((t) => t.name)\n }\n\n // ─── Internal ────────────────────────────────────────────────\n\n /**\n * Group entries into zones.\n *\n * Entries before any zone marker belong to the \"initial zone\" whose\n * scope is determined by `enableGlobalCache` and `defaultCacheScope`.\n */\n private groupIntoZones(): Array<{ scope: CacheScope; sections: Section[] }> {\n const initialScope = this.options.enableGlobalCache\n ? 'global'\n : this.options.defaultCacheScope\n\n const zones: Array<{ scope: CacheScope; sections: Section[] }> = []\n let currentZone: { scope: CacheScope; sections: Section[] } = {\n scope: initialScope,\n sections: [],\n }\n\n for (const entry of this.entries) {\n if ('__type' in entry) {\n // Zone marker: finalize current zone and start a new one\n if (currentZone.sections.length > 0) {\n zones.push(currentZone)\n }\n currentZone = { scope: entry.scope, sections: [] }\n } else {\n currentZone.sections.push(entry)\n }\n }\n\n // Don't forget the last zone\n if (currentZone.sections.length > 0) {\n zones.push(currentZone)\n }\n\n return zones\n }\n\n private estimateToolTokens(tool: { description: string; input_schema: Record<string, unknown> }): number {\n return (\n estimateTokens(tool.description, this.options.bytesPerToken) +\n estimateTokens(JSON.stringify(tool.input_schema), 2) // schemas are dense\n )\n }\n}\n","/**\n * promptloom — Cache boundary splitting\n *\n * Implements Claude Code's cache boundary mechanism:\n * - A sentinel string divides the prompt into static (cacheable) and dynamic zones\n * - Content before the boundary gets `cacheScope: 'global'` (cross-org cache)\n * - Content after gets `cacheScope: null` (session-specific)\n *\n * This directly maps to Claude API's `cache_control` field on text blocks.\n */\n\nimport type { CacheBlock, CacheScope } from './types.ts'\n\n/** The sentinel string used to mark the cache boundary */\nexport const CACHE_BOUNDARY = '__PROMPTLOOM_CACHE_BOUNDARY__'\n\nexport interface SplitOptions {\n /** Cache scope for content before the boundary (default: 'global') */\n staticScope?: CacheScope\n /** Cache scope for content after the boundary (default: null) */\n dynamicScope?: CacheScope\n /** Fallback scope when no boundary is found (default: 'org') */\n fallbackScope?: CacheScope\n}\n\n/**\n * Split a prompt string at the cache boundary into annotated blocks.\n *\n * Mirrors Claude Code's `splitSysPromptPrefix()`:\n *\n * - If boundary found:\n * [static content → staticScope] + [dynamic content → dynamicScope]\n *\n * - If no boundary:\n * [entire content → fallbackScope]\n */\nexport function splitAtBoundary(\n prompt: string,\n options: SplitOptions = {},\n): CacheBlock[] {\n const {\n staticScope = 'global',\n dynamicScope = null,\n fallbackScope = 'org',\n } = options\n\n const boundaryIndex = prompt.indexOf(CACHE_BOUNDARY)\n\n if (boundaryIndex === -1) {\n // No boundary — single block with fallback scope\n return prompt.trim()\n ? [{ text: prompt, cacheScope: fallbackScope }]\n : []\n }\n\n const before = prompt.slice(0, boundaryIndex).trim()\n const after = prompt.slice(boundaryIndex + CACHE_BOUNDARY.length).trim()\n\n const blocks: CacheBlock[] = []\n if (before) {\n blocks.push({ text: before, cacheScope: staticScope })\n }\n if (after) {\n blocks.push({ text: after, cacheScope: dynamicScope })\n }\n return blocks\n}\n\n/**\n * Convert cache blocks to Anthropic API text block format.\n *\n * Mirrors Claude Code's `buildSystemPromptBlocks()`.\n */\nexport function toAnthropicBlocks(\n blocks: CacheBlock[],\n enableCaching = true,\n): Array<{ type: 'text'; text: string; cache_control?: { type: 'ephemeral' } }> {\n return blocks.map((block) => ({\n type: 'text' as const,\n text: block.text,\n ...(enableCaching && block.cacheScope !== null\n ? { cache_control: { type: 'ephemeral' as const } }\n : {}),\n }))\n}\n","/**\n * promptloom — Multi-provider output formatting\n *\n * Claude Code supports multiple API providers:\n * - Anthropic (1P): cache_control with scope on text blocks\n * - AWS Bedrock: cache_control without scope\n * - Google Vertex: cache_control without scope\n * - OpenAI: single string system prompt, no caching API\n *\n * This module formats CompileResult for each provider.\n */\n\nimport type { CacheBlock, CompiledTool, CompileResult } from './types.ts'\n\n// ─── Anthropic ───────────────────────────────────────────────────\n\nexport interface AnthropicCacheControl {\n type: 'ephemeral'\n ttl?: '5m' | '1h'\n}\n\nexport interface AnthropicTextBlock {\n type: 'text'\n text: string\n cache_control?: AnthropicCacheControl\n}\n\nexport interface AnthropicTool {\n name: string\n description: string\n input_schema: Record<string, unknown>\n cache_control?: AnthropicCacheControl\n defer_loading?: true\n}\n\n/**\n * Format compile result for Anthropic Messages API.\n *\n * Mirrors Claude Code's `buildSystemPromptBlocks()`.\n * Blocks with a non-null cacheScope get `cache_control: { type: 'ephemeral' }`.\n */\nexport function toAnthropic(result: CompileResult): {\n system: AnthropicTextBlock[]\n tools: AnthropicTool[]\n} {\n return {\n system: result.blocks.map((block) => ({\n type: 'text' as const,\n text: block.text,\n ...(block.cacheScope !== null\n ? { cache_control: { type: 'ephemeral' as const } }\n : {}),\n })),\n tools: [...result.tools, ...result.deferredTools].map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: tool.input_schema,\n ...(tool.cache_control ? { cache_control: tool.cache_control } : {}),\n ...(tool.defer_loading ? { defer_loading: true as const } : {}),\n })),\n }\n}\n\n// ─── OpenAI ──────────────────────────────────────────────────────\n\nexport interface OpenAITool {\n type: 'function'\n function: {\n name: string\n description: string\n parameters: Record<string, unknown>\n }\n}\n\n/**\n * Format compile result for OpenAI Chat Completions API.\n *\n * OpenAI uses a single string for system prompt (no block-level caching).\n * Tools are wrapped in the `{ type: 'function', function: {...} }` format.\n */\nexport function toOpenAI(result: CompileResult): {\n system: string\n tools: OpenAITool[]\n} {\n return {\n system: result.text,\n tools: result.tools.map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input_schema,\n },\n })),\n }\n}\n\n// ─── AWS Bedrock ─────────────────────────────────────────────────\n\nexport type BedrockSystemBlock =\n | { text: string }\n | { cachePoint: { type: 'default' } }\n\nexport interface BedrockTool {\n toolSpec: {\n name: string\n description: string\n inputSchema: { jsonSchema: Record<string, unknown> }\n }\n}\n\n/**\n * Format compile result for AWS Bedrock Converse API.\n *\n * Bedrock uses `cachePoint: { type: 'default' }` instead of Anthropic's\n * `cache_control: { type: 'ephemeral' }`. Tools use `toolSpec` wrapper.\n */\nexport function toBedrock(result: CompileResult): {\n system: BedrockSystemBlock[]\n toolConfig: { tools: BedrockTool[] }\n} {\n return {\n system: result.blocks.flatMap((block): BedrockSystemBlock[] => [\n { text: block.text },\n ...(block.cacheScope !== null\n ? [{ cachePoint: { type: 'default' as const } }]\n : []),\n ]),\n toolConfig: {\n tools: result.tools.map((tool) => ({\n toolSpec: {\n name: tool.name,\n description: tool.description,\n inputSchema: { jsonSchema: tool.input_schema },\n },\n })),\n },\n }\n}\n\n// ─── Convenience: toAnthropicBlocks (backward compat) ────────────\n\n/**\n * Convert cache blocks to Anthropic API text block format.\n *\n * @deprecated Use `toAnthropic(result).system` instead for full formatting.\n * Kept for backward compatibility.\n */\nexport function toAnthropicBlocks(\n blocks: CacheBlock[],\n enableCaching = true,\n): AnthropicTextBlock[] {\n return blocks.map((block) => ({\n type: 'text' as const,\n text: block.text,\n ...(enableCaching && block.cacheScope !== null\n ? { cache_control: { type: 'ephemeral' as const } }\n : {}),\n }))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBO,SAAS,QAAQ,MAAc,SAAoB,SAAmC;AAC3F,SAAO,EAAE,MAAM,SAAS,YAAY,OAAO,MAAM,SAAS,KAAK;AACjE;AAWO,SAAS,eAAe,MAAc,SAAoB,SAAmC;AAClG,SAAO,EAAE,MAAM,SAAS,YAAY,MAAM,MAAM,SAAS,KAAK;AAChE;AAWO,IAAM,eAAN,MAAmB;AAAA,EAChB,QAAQ,oBAAI,IAA2B;AAAA,EAE/C,IAAI,MAAyC;AAC3C,WAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EAC5B;AAAA,EAEA,IAAI,MAAuB;AACzB,WAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EAC5B;AAAA,EAEA,IAAI,MAAc,OAA4B;AAC5C,SAAK,MAAM,IAAI,MAAM,KAAK;AAAA,EAC5B;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;AAWA,eAAsB,gBACpB,UACA,OACA,SAC4B;AAE5B,QAAM,MAAM,WAAW,CAAC;AACxB,QAAM,SAAS,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAG,CAAC;AAE5D,SAAO,QAAQ;AAAA,IACb,OAAO,IAAI,OAAO,MAAM;AAEtB,UAAI,CAAC,EAAE,cAAc,MAAM,IAAI,EAAE,IAAI,GAAG;AACtC,eAAO,MAAM,IAAI,EAAE,IAAI,KAAK;AAAA,MAC9B;AAEA,YAAM,QAAQ,MAAM,EAAE,QAAQ;AAE9B,UAAI,CAAC,EAAE,YAAY;AACjB,cAAM,IAAI,EAAE,MAAM,KAAK;AAAA,MACzB;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;;;AChFO,IAAM,YAAN,MAAgB;AAAA,EACb,QAAQ,oBAAI,IAA0B;AAAA,EAE9C,IAAI,KAAuC;AACzC,WAAO,KAAK,MAAM,IAAI,GAAG;AAAA,EAC3B;AAAA,EAEA,IAAI,KAAa,MAA0B;AACzC,SAAK,MAAM,IAAI,KAAK,IAAI;AAAA,EAC1B;AAAA,EAEA,IAAI,KAAsB;AACxB,WAAO,KAAK,MAAM,IAAI,GAAG;AAAA,EAC3B;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AACF;AAQA,SAAS,aAAa,KAAsB;AAC1C,SAAO,IAAI,cACP,GAAG,IAAI,IAAI,IAAI,KAAK,UAAU,IAAI,WAAW,CAAC,KAC9C,IAAI;AACV;AAKA,eAAe,kBAAkB,QAA4C;AAC3E,SAAO,OAAO,WAAW,aAAa,MAAM,OAAO,IAAI;AACzD;AAQA,eAAsB,YACpB,KACA,OACA,YACuB;AACvB,QAAM,MAAM,aAAa,GAAG;AAC5B,QAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,MAAI,OAAQ,QAAO;AAEnB,QAAM,cAAc,MAAM,kBAAkB,IAAI,MAAM;AAEtD,QAAM,WAAyB;AAAA,IAC7B,MAAM,IAAI;AAAA,IACV;AAAA,IACA,cAAc,IAAI;AAAA,IAClB,GAAI,eAAe,UAAa,eAAe,OAC3C,EAAE,eAAe,EAAE,MAAM,aAAsB,OAAO,WAAW,EAAE,IACnE,CAAC;AAAA,IACL,GAAI,IAAI,WAAW,EAAE,eAAe,KAAc,IAAI,CAAC;AAAA,EACzD;AAEA,QAAM,IAAI,KAAK,QAAQ;AACvB,SAAO;AACT;AAQA,eAAsB,aACpB,MACA,OACA,YACyB;AAEzB,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,aAAa,EAAE,SAAS,SAAS;AACrF,SAAO,QAAQ,IAAI,OAAO,IAAI,CAAC,QAAQ,YAAY,KAAK,OAAO,UAAU,CAAC,CAAC;AAC7E;AAUA,IAAM,gBAAgB;AAAA,EACpB,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,UAAU;AACZ;AAKO,SAAS,WAAW,KAAuB;AAChD,SAAO,EAAE,GAAG,eAAe,GAAG,IAAI;AACpC;;;ACjHA,IAAM,0BAA0B;AAQzB,SAAS,eACd,SACA,gBAAgB,yBACR;AACR,SAAO,KAAK,MAAM,QAAQ,SAAS,aAAa;AAClD;AAGA,IAAM,oBAA4C;AAAA,EAChD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAMO,SAAS,0BACd,SACA,WACQ;AACR,QAAM,MAAM,kBAAkB,UAAU,YAAY,CAAC,KAAK;AAC1D,SAAO,eAAe,SAAS,GAAG;AACpC;AAeO,SAAS,sBAAqC;AACnD,SAAO;AAAA,IACL,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,sBAAsB;AAAA,IACtB,WAAW,KAAK,IAAI;AAAA,EACtB;AACF;AAUO,SAAS,YACd,SACA,eACA,QACgB;AAChB,QAAM,EAAE,QAAQ,sBAAsB,KAAK,uBAAuB,IAAI,IAAI;AAE1E,MAAI,UAAU,GAAG;AACf,WAAO,EAAE,QAAQ,QAAQ,QAAQ,aAAa,KAAK,EAAE;AAAA,EACvD;AAEA,QAAM,MAAM,KAAK,MAAO,gBAAgB,SAAU,GAAG;AACrD,QAAM,sBAAsB,gBAAgB,QAAQ;AAGpD,QAAM,gBACJ,QAAQ,qBAAqB,KAC7B,sBAAsB,wBACtB,QAAQ,kBAAkB;AAG5B,UAAQ,kBAAkB;AAC1B,UAAQ,uBAAuB;AAE/B,MAAI,eAAe;AACjB,WAAO,EAAE,QAAQ,QAAQ,QAAQ,uBAAuB,IAAI;AAAA,EAC9D;AAEA,MAAI,iBAAiB,SAAS,qBAAqB;AACjD,WAAO,EAAE,QAAQ,QAAQ,QAAQ,kBAAkB,IAAI;AAAA,EACzD;AAEA,UAAQ;AACR,QAAM,MAAM,CAAC,MAAc,IAAI,KAAK,aAAa,OAAO,EAAE,OAAO,CAAC;AAClE,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA,cAAc,cAAc,GAAG,sBAAsB,IAAI,aAAa,CAAC,MAAM,IAAI,MAAM,CAAC;AAAA,EAC1F;AACF;AAsBO,SAAS,iBAAiB,MAA6B;AAC5D,QAAM,cAAsC;AAAA,IAC1C,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAGA,QAAM,aAAa,KAAK,MAAM,oCAAoC;AAClE,MAAI,YAAY;AACd,WAAO,WAAW,WAAW,CAAC,CAAE,IAAI,YAAY,WAAW,CAAC,EAAG,YAAY,CAAC;AAAA,EAC9E;AAGA,QAAM,WAAW,KAAK,MAAM,6CAA6C;AACzE,MAAI,UAAU;AACZ,WAAO,WAAW,SAAS,CAAC,CAAE,IAAI,YAAY,SAAS,CAAC,EAAG,YAAY,CAAC;AAAA,EAC1E;AAGA,QAAM,eAAe,KAAK,MAAM,0DAA0D;AAC1F,MAAI,cAAc;AAChB,WAAO,WAAW,aAAa,CAAC,CAAE,IAAI,YAAY,aAAa,CAAC,EAAG,YAAY,CAAC;AAAA,EAClF;AAEA,SAAO;AACT;;;AChHO,IAAM,iBAAN,MAAqB;AAAA,EAClB,UAAmB,CAAC;AAAA,EACpB,QAAmB,CAAC;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,UAA2B,CAAC,GAAG;AACzC,SAAK,UAAU;AAAA,MACb,mBAAmB,QAAQ,qBAAqB;AAAA,MAChD,mBAAmB,QAAQ,qBAAqB;AAAA,MAChD,eAAe,QAAQ,iBAAiB;AAAA,MACxC,mBAAmB,QAAQ,qBAAqB;AAAA,IAClD;AACA,SAAK,eAAe,IAAI,aAAa;AACrC,SAAK,YAAY,IAAI,UAAU;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,KAAK,OAAyB;AAC5B,SAAK,QAAQ,KAAK,EAAE,QAAQ,QAAQ,MAAM,CAAC;AAC3C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,WAAiB;AACf,QAAI,KAAK,QAAQ,mBAAmB;AAClC,aAAO,KAAK,KAAK,KAAK,QAAQ,iBAAiB;AAAA,IACjD;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,MAAc,SAA6B,SAAgC;AAChF,UAAM,UAAU,OAAO,YAAY,WAAW,MAAM,UAAU;AAC9D,SAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,YAAY,OAAO,MAAM,SAAS,KAAK,CAAC;AAC3E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,MAAc,SAAoB,SAAgC;AACxE,SAAK,QAAQ,KAAK,EAAE,MAAM,SAAS,YAAY,MAAM,MAAM,SAAS,KAAK,CAAC;AAC1E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,KAAK,KAAoB;AACvB,SAAK,MAAM,KAAK,GAAG;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,QAAQ,SAAkD;AAE9D,UAAM,aAAa,KAAK,eAAe;AAGvC,UAAM,SAAuB,CAAC;AAC9B,eAAW,SAAS,YAAY;AAC9B,YAAM,WAAW,MAAM,gBAAgB,MAAM,UAAU,KAAK,cAAc,OAAO;AACjF,YAAMA,QAAO,SAAS,OAAO,CAAC,MAAmB,MAAM,IAAI,EAAE,KAAK,MAAM;AACxE,UAAIA,OAAM;AACR,eAAO,KAAK,EAAE,MAAAA,OAAM,YAAY,MAAM,MAAM,CAAC;AAAA,MAC/C;AAAA,IACF;AAGA,UAAM,iBAAiB,KAAK,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AAC3D,UAAM,mBAAmB,KAAK,MAAM,OAAO,CAAC,MAAM,EAAE,QAAQ;AAG5D,UAAM,gBAAgB,MAAM,aAAa,gBAAgB,KAAK,SAAS;AACvE,UAAM,mBAAmB,MAAM,aAAa,kBAAkB,KAAK,SAAS;AAG5E,UAAM,MAAM,KAAK,QAAQ;AACzB,UAAM,qBAAqB,OAAO;AAAA,MAChC,CAAC,KAAK,MAAM,MAAM,eAAe,EAAE,MAAM,GAAG;AAAA,MAC5C;AAAA,IACF;AACA,UAAM,aAAa,cAAc;AAAA,MAC/B,CAAC,KAAK,MAAM,MAAM,KAAK,mBAAmB,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,UAAM,qBAAqB,iBAAiB;AAAA,MAC1C,CAAC,KAAK,MAAM,MAAM,KAAK,mBAAmB,CAAC;AAAA,MAC3C;AAAA,IACF;AACA,UAAM,SAAwB;AAAA,MAC5B,cAAc;AAAA,MACd,OAAO;AAAA,MACP,eAAe;AAAA,MACf,OAAO,qBAAqB;AAAA,IAC9B;AAGA,UAAM,OAAO,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,MAAM;AAElD,WAAO;AAAA,MACL;AAAA,MACA,OAAO;AAAA,MACP,eAAe;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,aAAa,MAAM;AACxB,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA;AAAA,EAGA,oBAA0B;AACxB,SAAK,aAAa,MAAM;AAAA,EAC1B;AAAA;AAAA,EAGA,iBAAuB;AACrB,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA;AAAA;AAAA,EAKA,IAAI,eAAuB;AACzB,WAAO,KAAK,QAAQ,OAAO,CAAC,MAAoB,EAAE,YAAY,EAAE,EAAE;AAAA,EACpE;AAAA;AAAA,EAGA,IAAI,YAAoB;AACtB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,eAA6E;AAC3E,WAAO,KAAK,QAAQ,IAAI,CAAC,MAAM;AAC7B,UAAI,YAAY,GAAG;AACjB,eAAO,EAAE,MAAM,QAAQ,EAAE,SAAS,MAAM,IAAI,MAAM,OAAgB;AAAA,MACpE;AACA,aAAO;AAAA,QACL,MAAM,EAAE;AAAA,QACR,MAAM,EAAE,aAAa,YAAqB;AAAA,MAC5C;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,YAAsB;AACpB,WAAO,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,iBAAoE;AAC1E,UAAM,eAAe,KAAK,QAAQ,oBAC9B,WACA,KAAK,QAAQ;AAEjB,UAAM,QAA2D,CAAC;AAClE,QAAI,cAA0D;AAAA,MAC5D,OAAO;AAAA,MACP,UAAU,CAAC;AAAA,IACb;AAEA,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,YAAY,OAAO;AAErB,YAAI,YAAY,SAAS,SAAS,GAAG;AACnC,gBAAM,KAAK,WAAW;AAAA,QACxB;AACA,sBAAc,EAAE,OAAO,MAAM,OAAO,UAAU,CAAC,EAAE;AAAA,MACnD,OAAO;AACL,oBAAY,SAAS,KAAK,KAAK;AAAA,MACjC;AAAA,IACF;AAGA,QAAI,YAAY,SAAS,SAAS,GAAG;AACnC,YAAM,KAAK,WAAW;AAAA,IACxB;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,mBAAmB,MAA8E;AACvG,WACE,eAAe,KAAK,aAAa,KAAK,QAAQ,aAAa,IAC3D,eAAe,KAAK,UAAU,KAAK,YAAY,GAAG,CAAC;AAAA,EAEvD;AACF;;;AC5SO,IAAM,iBAAiB;AAsBvB,SAAS,gBACd,QACA,UAAwB,CAAC,GACX;AACd,QAAM;AAAA,IACJ,cAAc;AAAA,IACd,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB,IAAI;AAEJ,QAAM,gBAAgB,OAAO,QAAQ,cAAc;AAEnD,MAAI,kBAAkB,IAAI;AAExB,WAAO,OAAO,KAAK,IACf,CAAC,EAAE,MAAM,QAAQ,YAAY,cAAc,CAAC,IAC5C,CAAC;AAAA,EACP;AAEA,QAAM,SAAS,OAAO,MAAM,GAAG,aAAa,EAAE,KAAK;AACnD,QAAM,QAAQ,OAAO,MAAM,gBAAgB,eAAe,MAAM,EAAE,KAAK;AAEvE,QAAM,SAAuB,CAAC;AAC9B,MAAI,QAAQ;AACV,WAAO,KAAK,EAAE,MAAM,QAAQ,YAAY,YAAY,CAAC;AAAA,EACvD;AACA,MAAI,OAAO;AACT,WAAO,KAAK,EAAE,MAAM,OAAO,YAAY,aAAa,CAAC;AAAA,EACvD;AACA,SAAO;AACT;;;ACzBO,SAAS,YAAY,QAG1B;AACA,SAAO;AAAA,IACL,QAAQ,OAAO,OAAO,IAAI,CAAC,WAAW;AAAA,MACpC,MAAM;AAAA,MACN,MAAM,MAAM;AAAA,MACZ,GAAI,MAAM,eAAe,OACrB,EAAE,eAAe,EAAE,MAAM,YAAqB,EAAE,IAChD,CAAC;AAAA,IACP,EAAE;AAAA,IACF,OAAO,CAAC,GAAG,OAAO,OAAO,GAAG,OAAO,aAAa,EAAE,IAAI,CAAC,UAAU;AAAA,MAC/D,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,MACnB,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,MAClE,GAAI,KAAK,gBAAgB,EAAE,eAAe,KAAc,IAAI,CAAC;AAAA,IAC/D,EAAE;AAAA,EACJ;AACF;AAmBO,SAAS,SAAS,QAGvB;AACA,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO,MAAM,IAAI,CAAC,UAAU;AAAA,MACjC,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,YAAY,KAAK;AAAA,MACnB;AAAA,IACF,EAAE;AAAA,EACJ;AACF;AAsBO,SAAS,UAAU,QAGxB;AACA,SAAO;AAAA,IACL,QAAQ,OAAO,OAAO,QAAQ,CAAC,UAAgC;AAAA,MAC7D,EAAE,MAAM,MAAM,KAAK;AAAA,MACnB,GAAI,MAAM,eAAe,OACrB,CAAC,EAAE,YAAY,EAAE,MAAM,UAAmB,EAAE,CAAC,IAC7C,CAAC;AAAA,IACP,CAAC;AAAA,IACD,YAAY;AAAA,MACV,OAAO,OAAO,MAAM,IAAI,CAAC,UAAU;AAAA,QACjC,UAAU;AAAA,UACR,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA,UAClB,aAAa,EAAE,YAAY,KAAK,aAAa;AAAA,QAC/C;AAAA,MACF,EAAE;AAAA,IACJ;AAAA,EACF;AACF;AAUO,SAAS,kBACd,QACA,gBAAgB,MACM;AACtB,SAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC5B,MAAM;AAAA,IACN,MAAM,MAAM;AAAA,IACZ,GAAI,iBAAiB,MAAM,eAAe,OACtC,EAAE,eAAe,EAAE,MAAM,YAAqB,EAAE,IAChD,CAAC;AAAA,EACP,EAAE;AACJ;","names":["text"]}