@affectively/aeon-pages 1.3.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +625 -0
  3. package/examples/basic/aeon.config.ts +39 -0
  4. package/examples/basic/components/Cursor.tsx +86 -0
  5. package/examples/basic/components/OfflineIndicator.tsx +103 -0
  6. package/examples/basic/components/PresenceBar.tsx +77 -0
  7. package/examples/basic/package.json +20 -0
  8. package/examples/basic/pages/index.tsx +80 -0
  9. package/package.json +101 -0
  10. package/packages/analytics/README.md +309 -0
  11. package/packages/analytics/build.ts +35 -0
  12. package/packages/analytics/package.json +50 -0
  13. package/packages/analytics/src/click-tracker.ts +368 -0
  14. package/packages/analytics/src/context-bridge.ts +319 -0
  15. package/packages/analytics/src/data-layer.ts +302 -0
  16. package/packages/analytics/src/gtm-loader.ts +239 -0
  17. package/packages/analytics/src/index.ts +230 -0
  18. package/packages/analytics/src/merkle-tree.ts +489 -0
  19. package/packages/analytics/src/provider.tsx +300 -0
  20. package/packages/analytics/src/types.ts +320 -0
  21. package/packages/analytics/src/use-analytics.ts +296 -0
  22. package/packages/analytics/tsconfig.json +19 -0
  23. package/packages/benchmarks/src/benchmark.test.ts +691 -0
  24. package/packages/cli/dist/index.js +61899 -0
  25. package/packages/cli/package.json +43 -0
  26. package/packages/cli/src/commands/build.test.ts +682 -0
  27. package/packages/cli/src/commands/build.ts +890 -0
  28. package/packages/cli/src/commands/dev.ts +473 -0
  29. package/packages/cli/src/commands/init.ts +409 -0
  30. package/packages/cli/src/commands/start.ts +297 -0
  31. package/packages/cli/src/index.ts +105 -0
  32. package/packages/directives/src/use-aeon.ts +272 -0
  33. package/packages/mcp-server/package.json +51 -0
  34. package/packages/mcp-server/src/index.ts +178 -0
  35. package/packages/mcp-server/src/resources.ts +346 -0
  36. package/packages/mcp-server/src/tools/index.ts +36 -0
  37. package/packages/mcp-server/src/tools/navigation.ts +545 -0
  38. package/packages/mcp-server/tsconfig.json +21 -0
  39. package/packages/react/package.json +40 -0
  40. package/packages/react/src/Link.tsx +388 -0
  41. package/packages/react/src/components/InstallPrompt.tsx +286 -0
  42. package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
  43. package/packages/react/src/components/PushNotifications.tsx +453 -0
  44. package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
  45. package/packages/react/src/hooks/useConflicts.ts +277 -0
  46. package/packages/react/src/hooks/useNetworkState.ts +209 -0
  47. package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
  48. package/packages/react/src/hooks/useServiceWorker.ts +278 -0
  49. package/packages/react/src/hooks.ts +195 -0
  50. package/packages/react/src/index.ts +151 -0
  51. package/packages/react/src/provider.tsx +467 -0
  52. package/packages/react/tsconfig.json +19 -0
  53. package/packages/runtime/README.md +399 -0
  54. package/packages/runtime/build.ts +48 -0
  55. package/packages/runtime/package.json +71 -0
  56. package/packages/runtime/schema.sql +40 -0
  57. package/packages/runtime/src/api-routes.ts +465 -0
  58. package/packages/runtime/src/benchmark.ts +171 -0
  59. package/packages/runtime/src/cache.ts +479 -0
  60. package/packages/runtime/src/durable-object.ts +1341 -0
  61. package/packages/runtime/src/index.ts +360 -0
  62. package/packages/runtime/src/navigation.test.ts +421 -0
  63. package/packages/runtime/src/navigation.ts +422 -0
  64. package/packages/runtime/src/nextjs-adapter.ts +272 -0
  65. package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
  66. package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
  67. package/packages/runtime/src/offline/encryption.test.ts +412 -0
  68. package/packages/runtime/src/offline/encryption.ts +397 -0
  69. package/packages/runtime/src/offline/types.ts +465 -0
  70. package/packages/runtime/src/predictor.ts +371 -0
  71. package/packages/runtime/src/registry.ts +351 -0
  72. package/packages/runtime/src/router/context-extractor.ts +661 -0
  73. package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
  74. package/packages/runtime/src/router/esi-control.ts +541 -0
  75. package/packages/runtime/src/router/esi-cyrano.ts +779 -0
  76. package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
  77. package/packages/runtime/src/router/esi-react.tsx +1065 -0
  78. package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
  79. package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
  80. package/packages/runtime/src/router/esi-translate.ts +503 -0
  81. package/packages/runtime/src/router/esi.ts +666 -0
  82. package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
  83. package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
  84. package/packages/runtime/src/router/index.ts +298 -0
  85. package/packages/runtime/src/router/merkle-capability.ts +473 -0
  86. package/packages/runtime/src/router/speculation.ts +451 -0
  87. package/packages/runtime/src/router/types.ts +630 -0
  88. package/packages/runtime/src/router.test.ts +470 -0
  89. package/packages/runtime/src/router.ts +302 -0
  90. package/packages/runtime/src/server.ts +481 -0
  91. package/packages/runtime/src/service-worker-push.ts +319 -0
  92. package/packages/runtime/src/service-worker.ts +553 -0
  93. package/packages/runtime/src/skeleton-hydrate.ts +237 -0
  94. package/packages/runtime/src/speculation.test.ts +389 -0
  95. package/packages/runtime/src/speculation.ts +486 -0
  96. package/packages/runtime/src/storage.test.ts +1297 -0
  97. package/packages/runtime/src/storage.ts +1048 -0
  98. package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
  99. package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
  100. package/packages/runtime/src/sync/coordinator.test.ts +608 -0
  101. package/packages/runtime/src/sync/coordinator.ts +596 -0
  102. package/packages/runtime/src/tree-compiler.ts +295 -0
  103. package/packages/runtime/src/types.ts +728 -0
  104. package/packages/runtime/src/worker.ts +327 -0
  105. package/packages/runtime/tsconfig.json +20 -0
  106. package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
  107. package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
  108. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
  109. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
  110. package/packages/runtime/wasm/package.json +21 -0
  111. package/packages/runtime/wrangler.toml +41 -0
  112. package/packages/runtime-wasm/Cargo.lock +436 -0
  113. package/packages/runtime-wasm/Cargo.toml +29 -0
  114. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
  115. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
  116. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  117. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
  118. package/packages/runtime-wasm/pkg/package.json +21 -0
  119. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  120. package/packages/runtime-wasm/src/lib.rs +191 -0
  121. package/packages/runtime-wasm/src/render.rs +629 -0
  122. package/packages/runtime-wasm/src/router.rs +298 -0
  123. package/packages/runtime-wasm/src/skeleton.rs +430 -0
  124. package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
@@ -0,0 +1,541 @@
1
+ /**
2
+ * ESI Control Language
3
+ *
4
+ * Adds conditional display and structured output validation to ESI.
5
+ * Uses Zod for schema validation to ensure trusted, typed results.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Conditional display based on inference
10
+ * <ESI.If
11
+ * prompt="Is this user likely to churn?"
12
+ * schema={z.object({ likelihood: z.number(), reason: z.string() })}
13
+ * when={(result) => result.likelihood > 0.7}
14
+ * >
15
+ * <RetentionOffer />
16
+ * </ESI.If>
17
+ *
18
+ * // Structured output with Zod
19
+ * <ESI.Infer
20
+ * schema={z.object({
21
+ * sentiment: z.enum(['positive', 'negative', 'neutral']),
22
+ * confidence: z.number(),
23
+ * topics: z.array(z.string()),
24
+ * })}
25
+ * >
26
+ * Analyze this text: {userInput}
27
+ * </ESI.Infer>
28
+ *
29
+ * // Switch/match on inference result
30
+ * <ESI.Match
31
+ * prompt="Classify user intent"
32
+ * schema={z.object({ intent: z.enum(['browse', 'buy', 'support', 'leave']) })}
33
+ * >
34
+ * <ESI.Case match={(r) => r.intent === 'buy'}><CheckoutCTA /></ESI.Case>
35
+ * <ESI.Case match={(r) => r.intent === 'support'}><HelpWidget /></ESI.Case>
36
+ * <ESI.Default><BrowsePrompt /></ESI.Default>
37
+ * </ESI.Match>
38
+ * ```
39
+ */
40
+
41
+ import type { ZodType, ZodTypeDef, z } from 'zod';
42
+ import type { ESIDirective, ESIParams, ESIResult, UserContext } from './types';
43
+
44
+ // ============================================================================
45
+ // Schema Types
46
+ // ============================================================================
47
+
48
+ /**
49
+ * ESI with Zod schema for validated, typed output
50
+ */
51
+ export interface ESISchemaParams<T> extends Omit<ESIParams, 'model'> {
52
+ /** Zod schema for output validation */
53
+ schema: ZodType<T, ZodTypeDef, unknown>;
54
+
55
+ /** Model defaults to 'llm' for structured output */
56
+ model?: 'llm';
57
+
58
+ /** Retry on validation failure */
59
+ retryOnValidationError?: boolean;
60
+
61
+ /** Max retries */
62
+ maxRetries?: number;
63
+ }
64
+
65
+ export interface ESISchemaResult<T> extends Omit<ESIResult, 'output'> {
66
+ /** Validated, typed output */
67
+ data?: T;
68
+
69
+ /** Raw output before validation */
70
+ rawOutput?: string;
71
+
72
+ /** Validation errors if any */
73
+ validationErrors?: string[];
74
+ }
75
+
76
+ // ============================================================================
77
+ // Control Flow Types
78
+ // ============================================================================
79
+
80
+ /**
81
+ * Condition function type
82
+ */
83
+ export type ESICondition<T> = (result: T, context: UserContext) => boolean;
84
+
85
+ /**
86
+ * ESI.If directive - conditional rendering based on inference
87
+ */
88
+ export interface ESIIfDirective<T> {
89
+ /** Unique ID */
90
+ id: string;
91
+
92
+ /** Prompt to evaluate */
93
+ prompt: string;
94
+
95
+ /** Schema for structured output */
96
+ schema: ZodType<T, ZodTypeDef, unknown>;
97
+
98
+ /** Condition to check */
99
+ when: ESICondition<T>;
100
+
101
+ /** Inference params */
102
+ params?: Partial<ESIParams>;
103
+
104
+ /** Cache the condition result */
105
+ cacheCondition?: boolean;
106
+ }
107
+
108
+ /**
109
+ * ESI.Match directive - switch/case based on inference
110
+ */
111
+ export interface ESIMatchDirective<T> {
112
+ /** Unique ID */
113
+ id: string;
114
+
115
+ /** Prompt to evaluate */
116
+ prompt: string;
117
+
118
+ /** Schema for structured output */
119
+ schema: ZodType<T, ZodTypeDef, unknown>;
120
+
121
+ /** Cases to match */
122
+ cases: Array<{
123
+ match: ESICondition<T>;
124
+ id: string;
125
+ }>;
126
+
127
+ /** Default case ID if no match */
128
+ defaultCase?: string;
129
+
130
+ /** Inference params */
131
+ params?: Partial<ESIParams>;
132
+ }
133
+
134
+ /**
135
+ * Result of control flow evaluation
136
+ */
137
+ export interface ESIControlResult<T> {
138
+ /** The directive ID */
139
+ id: string;
140
+
141
+ /** Whether condition was met (for If) */
142
+ conditionMet?: boolean;
143
+
144
+ /** Matched case ID (for Match) */
145
+ matchedCase?: string;
146
+
147
+ /** The validated data */
148
+ data?: T;
149
+
150
+ /** Inference result */
151
+ inferenceResult: ESISchemaResult<T>;
152
+ }
153
+
154
+ // ============================================================================
155
+ // Schema Validation
156
+ // ============================================================================
157
+
158
+ /**
159
+ * Generate a JSON schema prompt suffix for structured output
160
+ */
161
+ export function generateSchemaPrompt<T>(
162
+ schema: ZodType<T, ZodTypeDef, unknown>,
163
+ ): string {
164
+ // Extract schema description for the prompt
165
+ // This helps the LLM understand the expected output format
166
+ const schemaDescription = describeZodSchema(schema);
167
+
168
+ return `
169
+
170
+ Respond with valid JSON matching this schema:
171
+ ${schemaDescription}
172
+
173
+ Output ONLY the JSON, no markdown, no explanation.`;
174
+ }
175
+
176
+ function getSchemaDef(schema: unknown): Record<string, unknown> | null {
177
+ if (!schema || typeof schema !== 'object' || !('_def' in schema)) {
178
+ return null;
179
+ }
180
+
181
+ const def = (schema as { _def?: unknown })._def;
182
+ if (!def || typeof def !== 'object') {
183
+ return null;
184
+ }
185
+
186
+ return def as Record<string, unknown>;
187
+ }
188
+
189
+ /**
190
+ * Describe a Zod schema in a way the LLM can understand
191
+ */
192
+ function describeZodSchema<T>(schema: ZodType<T, ZodTypeDef, unknown>): string {
193
+ const def = getSchemaDef(schema);
194
+ if (!def) {
195
+ return 'JSON value';
196
+ }
197
+
198
+ if (def.typeName === 'ZodObject') {
199
+ const rawShape = def.shape as
200
+ | Record<string, ZodType<unknown, ZodTypeDef, unknown>>
201
+ | (() => Record<string, ZodType<unknown, ZodTypeDef, unknown>>);
202
+ const shape = typeof rawShape === 'function' ? rawShape() : rawShape;
203
+ if (!shape || typeof shape !== 'object') {
204
+ return 'object';
205
+ }
206
+
207
+ const fields = Object.entries(shape).map(([key, fieldSchema]) => {
208
+ const fieldDef = getSchemaDef(fieldSchema);
209
+ return ` "${key}": ${fieldDef ? describeZodType(fieldDef) : 'any'}`;
210
+ });
211
+ return `{\n${fields.join(',\n')}\n}`;
212
+ }
213
+
214
+ return describeZodType(def);
215
+ }
216
+
217
+ /**
218
+ * Describe a Zod type
219
+ */
220
+ function describeZodType(def: Record<string, unknown>): string {
221
+ const typeName =
222
+ typeof def.typeName === 'string' ? (def.typeName as string) : null;
223
+ if (!typeName) {
224
+ return 'any';
225
+ }
226
+
227
+ switch (typeName) {
228
+ case 'ZodString':
229
+ return 'string';
230
+ case 'ZodNumber':
231
+ return 'number';
232
+ case 'ZodBoolean':
233
+ return 'boolean';
234
+ case 'ZodArray': {
235
+ const innerType = def.type as ZodType<unknown, ZodTypeDef, unknown>;
236
+ if (!innerType || typeof innerType !== 'object') {
237
+ return 'array';
238
+ }
239
+ const innerDef = getSchemaDef(innerType);
240
+ return innerDef ? `array of ${describeZodType(innerDef)}` : 'array';
241
+ }
242
+ case 'ZodEnum': {
243
+ const values = def.values as string[];
244
+ return `one of: ${values.map((v) => `"${v}"`).join(' | ')}`;
245
+ }
246
+ case 'ZodLiteral':
247
+ return JSON.stringify(def.value);
248
+ case 'ZodOptional': {
249
+ const optionalType = def.innerType as ZodType<
250
+ unknown,
251
+ ZodTypeDef,
252
+ unknown
253
+ >;
254
+ if (!optionalType || typeof optionalType !== 'object') {
255
+ return 'any (optional)';
256
+ }
257
+ const optionalDef = getSchemaDef(optionalType);
258
+ return optionalDef
259
+ ? `${describeZodType(optionalDef)} (optional)`
260
+ : 'any (optional)';
261
+ }
262
+ case 'ZodNullable': {
263
+ const nullableType = def.innerType as ZodType<
264
+ unknown,
265
+ ZodTypeDef,
266
+ unknown
267
+ >;
268
+ if (!nullableType || typeof nullableType !== 'object') {
269
+ return 'any or null';
270
+ }
271
+ const nullableDef = getSchemaDef(nullableType);
272
+ return nullableDef
273
+ ? `${describeZodType(nullableDef)} or null`
274
+ : 'any or null';
275
+ }
276
+ case 'ZodObject':
277
+ return 'object';
278
+ default:
279
+ return 'any';
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Parse and validate LLM output against a Zod schema
285
+ */
286
+ export function parseWithSchema<T>(
287
+ output: string,
288
+ schema: ZodType<T, ZodTypeDef, unknown>,
289
+ ): { success: true; data: T } | { success: false; errors: string[] } {
290
+ // Try to extract JSON from the output
291
+ let jsonStr = output.trim();
292
+
293
+ // Handle markdown code blocks
294
+ if (jsonStr.startsWith('```')) {
295
+ const match = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
296
+ if (match) {
297
+ jsonStr = match[1].trim();
298
+ }
299
+ }
300
+
301
+ // Try to parse JSON
302
+ let parsed: unknown;
303
+ try {
304
+ parsed = JSON.parse(jsonStr);
305
+ } catch (e) {
306
+ // Try to find JSON object in the output
307
+ const jsonMatch = jsonStr.match(/\{[\s\S]*\}/);
308
+ if (jsonMatch) {
309
+ try {
310
+ parsed = JSON.parse(jsonMatch[0]);
311
+ } catch {
312
+ return {
313
+ success: false,
314
+ errors: [
315
+ `Failed to parse JSON: ${e instanceof Error ? e.message : 'Unknown error'}`,
316
+ ],
317
+ };
318
+ }
319
+ } else {
320
+ return {
321
+ success: false,
322
+ errors: [`No valid JSON found in output`],
323
+ };
324
+ }
325
+ }
326
+
327
+ // Validate against schema
328
+ const result = schema.safeParse(parsed);
329
+
330
+ if (result.success) {
331
+ return { success: true, data: result.data };
332
+ }
333
+
334
+ return {
335
+ success: false,
336
+ errors: result.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`),
337
+ };
338
+ }
339
+
340
+ // ============================================================================
341
+ // Control Flow Processor
342
+ // ============================================================================
343
+
344
+ export interface ESIControlProcessor {
345
+ /**
346
+ * Process an ESI.If directive
347
+ */
348
+ processIf<T>(
349
+ directive: ESIIfDirective<T>,
350
+ context: UserContext,
351
+ ): Promise<ESIControlResult<T>>;
352
+
353
+ /**
354
+ * Process an ESI.Match directive
355
+ */
356
+ processMatch<T>(
357
+ directive: ESIMatchDirective<T>,
358
+ context: UserContext,
359
+ ): Promise<ESIControlResult<T>>;
360
+
361
+ /**
362
+ * Process an ESI directive with schema validation
363
+ */
364
+ processWithSchema<T>(
365
+ prompt: string,
366
+ schema: ZodType<T, ZodTypeDef, unknown>,
367
+ params: Partial<ESIParams>,
368
+ context: UserContext,
369
+ ): Promise<ESISchemaResult<T>>;
370
+ }
371
+
372
+ /**
373
+ * Create a control processor that wraps an ESI processor
374
+ */
375
+ export function createControlProcessor(
376
+ processESI: (
377
+ directive: ESIDirective,
378
+ context: UserContext,
379
+ ) => Promise<ESIResult>,
380
+ ): ESIControlProcessor {
381
+ return {
382
+ async processWithSchema<T>(
383
+ prompt: string,
384
+ schema: ZodType<T, ZodTypeDef, unknown>,
385
+ params: Partial<ESIParams>,
386
+ context: UserContext,
387
+ ): Promise<ESISchemaResult<T>> {
388
+ // Add schema prompt
389
+ const fullPrompt = prompt + generateSchemaPrompt(schema);
390
+
391
+ const directive: ESIDirective = {
392
+ id: `esi-schema-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
393
+ params: {
394
+ model: 'llm',
395
+ ...params,
396
+ },
397
+ content: {
398
+ type: 'text',
399
+ value: fullPrompt,
400
+ },
401
+ };
402
+
403
+ const result = await processESI(directive, context);
404
+
405
+ if (!result.success || !result.output) {
406
+ return {
407
+ ...result,
408
+ validationErrors: result.error ? [result.error] : ['No output'],
409
+ };
410
+ }
411
+
412
+ const parseResult = parseWithSchema(result.output, schema);
413
+
414
+ if (parseResult.success) {
415
+ return {
416
+ ...result,
417
+ data: parseResult.data,
418
+ rawOutput: result.output,
419
+ };
420
+ }
421
+
422
+ // Retry logic could go here
423
+ return {
424
+ ...result,
425
+ rawOutput: result.output,
426
+ validationErrors: parseResult.errors,
427
+ };
428
+ },
429
+
430
+ async processIf<T>(
431
+ directive: ESIIfDirective<T>,
432
+ context: UserContext,
433
+ ): Promise<ESIControlResult<T>> {
434
+ const schemaResult = await this.processWithSchema(
435
+ directive.prompt,
436
+ directive.schema,
437
+ directive.params || {},
438
+ context,
439
+ );
440
+
441
+ let conditionMet = false;
442
+
443
+ if (schemaResult.data !== undefined) {
444
+ try {
445
+ conditionMet = directive.when(schemaResult.data, context);
446
+ } catch (e) {
447
+ // Condition evaluation failed
448
+ conditionMet = false;
449
+ }
450
+ }
451
+
452
+ return {
453
+ id: directive.id,
454
+ conditionMet,
455
+ data: schemaResult.data,
456
+ inferenceResult: schemaResult,
457
+ };
458
+ },
459
+
460
+ async processMatch<T>(
461
+ directive: ESIMatchDirective<T>,
462
+ context: UserContext,
463
+ ): Promise<ESIControlResult<T>> {
464
+ const schemaResult = await this.processWithSchema(
465
+ directive.prompt,
466
+ directive.schema,
467
+ directive.params || {},
468
+ context,
469
+ );
470
+
471
+ let matchedCase: string | undefined;
472
+
473
+ if (schemaResult.data !== undefined) {
474
+ for (const caseItem of directive.cases) {
475
+ try {
476
+ if (caseItem.match(schemaResult.data, context)) {
477
+ matchedCase = caseItem.id;
478
+ break;
479
+ }
480
+ } catch {
481
+ // Continue to next case
482
+ }
483
+ }
484
+
485
+ // Use default if no match
486
+ if (!matchedCase && directive.defaultCase) {
487
+ matchedCase = directive.defaultCase;
488
+ }
489
+ }
490
+
491
+ return {
492
+ id: directive.id,
493
+ matchedCase,
494
+ data: schemaResult.data,
495
+ inferenceResult: schemaResult,
496
+ };
497
+ },
498
+ };
499
+ }
500
+
501
+ // ============================================================================
502
+ // Directive Builders
503
+ // ============================================================================
504
+
505
+ /**
506
+ * Create an ESI.If directive
507
+ */
508
+ export function esiIf<T>(
509
+ prompt: string,
510
+ schema: ZodType<T, ZodTypeDef, unknown>,
511
+ when: ESICondition<T>,
512
+ options: Partial<ESIParams> = {},
513
+ ): ESIIfDirective<T> {
514
+ return {
515
+ id: `esi-if-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
516
+ prompt,
517
+ schema,
518
+ when,
519
+ params: options,
520
+ };
521
+ }
522
+
523
+ /**
524
+ * Create an ESI.Match directive
525
+ */
526
+ export function esiMatch<T>(
527
+ prompt: string,
528
+ schema: ZodType<T, ZodTypeDef, unknown>,
529
+ cases: Array<{ match: ESICondition<T>; id: string }>,
530
+ defaultCase?: string,
531
+ options: Partial<ESIParams> = {},
532
+ ): ESIMatchDirective<T> {
533
+ return {
534
+ id: `esi-match-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
535
+ prompt,
536
+ schema,
537
+ cases,
538
+ defaultCase,
539
+ params: options,
540
+ };
541
+ }