@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.
- package/CHANGELOG.md +112 -0
- package/README.md +625 -0
- package/examples/basic/aeon.config.ts +39 -0
- package/examples/basic/components/Cursor.tsx +86 -0
- package/examples/basic/components/OfflineIndicator.tsx +103 -0
- package/examples/basic/components/PresenceBar.tsx +77 -0
- package/examples/basic/package.json +20 -0
- package/examples/basic/pages/index.tsx +80 -0
- package/package.json +101 -0
- package/packages/analytics/README.md +309 -0
- package/packages/analytics/build.ts +35 -0
- package/packages/analytics/package.json +50 -0
- package/packages/analytics/src/click-tracker.ts +368 -0
- package/packages/analytics/src/context-bridge.ts +319 -0
- package/packages/analytics/src/data-layer.ts +302 -0
- package/packages/analytics/src/gtm-loader.ts +239 -0
- package/packages/analytics/src/index.ts +230 -0
- package/packages/analytics/src/merkle-tree.ts +489 -0
- package/packages/analytics/src/provider.tsx +300 -0
- package/packages/analytics/src/types.ts +320 -0
- package/packages/analytics/src/use-analytics.ts +296 -0
- package/packages/analytics/tsconfig.json +19 -0
- package/packages/benchmarks/src/benchmark.test.ts +691 -0
- package/packages/cli/dist/index.js +61899 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/commands/build.test.ts +682 -0
- package/packages/cli/src/commands/build.ts +890 -0
- package/packages/cli/src/commands/dev.ts +473 -0
- package/packages/cli/src/commands/init.ts +409 -0
- package/packages/cli/src/commands/start.ts +297 -0
- package/packages/cli/src/index.ts +105 -0
- package/packages/directives/src/use-aeon.ts +272 -0
- package/packages/mcp-server/package.json +51 -0
- package/packages/mcp-server/src/index.ts +178 -0
- package/packages/mcp-server/src/resources.ts +346 -0
- package/packages/mcp-server/src/tools/index.ts +36 -0
- package/packages/mcp-server/src/tools/navigation.ts +545 -0
- package/packages/mcp-server/tsconfig.json +21 -0
- package/packages/react/package.json +40 -0
- package/packages/react/src/Link.tsx +388 -0
- package/packages/react/src/components/InstallPrompt.tsx +286 -0
- package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
- package/packages/react/src/components/PushNotifications.tsx +453 -0
- package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
- package/packages/react/src/hooks/useConflicts.ts +277 -0
- package/packages/react/src/hooks/useNetworkState.ts +209 -0
- package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
- package/packages/react/src/hooks/useServiceWorker.ts +278 -0
- package/packages/react/src/hooks.ts +195 -0
- package/packages/react/src/index.ts +151 -0
- package/packages/react/src/provider.tsx +467 -0
- package/packages/react/tsconfig.json +19 -0
- package/packages/runtime/README.md +399 -0
- package/packages/runtime/build.ts +48 -0
- package/packages/runtime/package.json +71 -0
- package/packages/runtime/schema.sql +40 -0
- package/packages/runtime/src/api-routes.ts +465 -0
- package/packages/runtime/src/benchmark.ts +171 -0
- package/packages/runtime/src/cache.ts +479 -0
- package/packages/runtime/src/durable-object.ts +1341 -0
- package/packages/runtime/src/index.ts +360 -0
- package/packages/runtime/src/navigation.test.ts +421 -0
- package/packages/runtime/src/navigation.ts +422 -0
- package/packages/runtime/src/nextjs-adapter.ts +272 -0
- package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
- package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
- package/packages/runtime/src/offline/encryption.test.ts +412 -0
- package/packages/runtime/src/offline/encryption.ts +397 -0
- package/packages/runtime/src/offline/types.ts +465 -0
- package/packages/runtime/src/predictor.ts +371 -0
- package/packages/runtime/src/registry.ts +351 -0
- package/packages/runtime/src/router/context-extractor.ts +661 -0
- package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
- package/packages/runtime/src/router/esi-control.ts +541 -0
- package/packages/runtime/src/router/esi-cyrano.ts +779 -0
- package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
- package/packages/runtime/src/router/esi-react.tsx +1065 -0
- package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
- package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
- package/packages/runtime/src/router/esi-translate.ts +503 -0
- package/packages/runtime/src/router/esi.ts +666 -0
- package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
- package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
- package/packages/runtime/src/router/index.ts +298 -0
- package/packages/runtime/src/router/merkle-capability.ts +473 -0
- package/packages/runtime/src/router/speculation.ts +451 -0
- package/packages/runtime/src/router/types.ts +630 -0
- package/packages/runtime/src/router.test.ts +470 -0
- package/packages/runtime/src/router.ts +302 -0
- package/packages/runtime/src/server.ts +481 -0
- package/packages/runtime/src/service-worker-push.ts +319 -0
- package/packages/runtime/src/service-worker.ts +553 -0
- package/packages/runtime/src/skeleton-hydrate.ts +237 -0
- package/packages/runtime/src/speculation.test.ts +389 -0
- package/packages/runtime/src/speculation.ts +486 -0
- package/packages/runtime/src/storage.test.ts +1297 -0
- package/packages/runtime/src/storage.ts +1048 -0
- package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
- package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
- package/packages/runtime/src/sync/coordinator.test.ts +608 -0
- package/packages/runtime/src/sync/coordinator.ts +596 -0
- package/packages/runtime/src/tree-compiler.ts +295 -0
- package/packages/runtime/src/types.ts +728 -0
- package/packages/runtime/src/worker.ts +327 -0
- package/packages/runtime/tsconfig.json +20 -0
- package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
- package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
- package/packages/runtime/wasm/package.json +21 -0
- package/packages/runtime/wrangler.toml +41 -0
- package/packages/runtime-wasm/Cargo.lock +436 -0
- package/packages/runtime-wasm/Cargo.toml +29 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
- package/packages/runtime-wasm/pkg/package.json +21 -0
- package/packages/runtime-wasm/src/hydrate.rs +352 -0
- package/packages/runtime-wasm/src/lib.rs +191 -0
- package/packages/runtime-wasm/src/render.rs +629 -0
- package/packages/runtime-wasm/src/router.rs +298 -0
- package/packages/runtime-wasm/src/skeleton.rs +430 -0
- 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
|
+
}
|