@neutrome/open-ai-router 0.4.2 → 0.5.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 (62) hide show
  1. package/.dev.vars.example +2 -0
  2. package/README.md +5 -1
  3. package/package.json +5 -4
  4. package/src/admission/access.ts +0 -1
  5. package/src/admission/types.ts +0 -1
  6. package/src/app/factory.test.ts +40 -34
  7. package/src/app/factory.ts +120 -173
  8. package/src/app/google-genai-route.ts +48 -0
  9. package/src/app/http-utils.ts +14 -0
  10. package/src/app/model-listing.ts +29 -0
  11. package/src/app/response-lifecycle.ts +66 -0
  12. package/src/app/types.ts +38 -0
  13. package/src/index.ts +11 -43
  14. package/src/otlp.ts +148 -0
  15. package/src/router/audit.ts +5 -12
  16. package/src/router/config.ts +4 -4
  17. package/src/router/error-codec.ts +170 -0
  18. package/src/router/errors.ts +38 -1
  19. package/src/router/execute.test.ts +177 -107
  20. package/src/router/execute.ts +68 -924
  21. package/src/router/execution-events.ts +39 -0
  22. package/src/router/execution-invocation.ts +144 -0
  23. package/src/router/execution-observation.ts +161 -0
  24. package/src/router/execution-resolution.ts +120 -0
  25. package/src/router/execution-runtime.test.ts +156 -83
  26. package/src/router/execution-runtime.ts +144 -542
  27. package/src/router/execution-transforms.ts +82 -0
  28. package/src/router/execution-types.ts +8 -19
  29. package/src/router/index.ts +6 -5
  30. package/src/router/mcp.ts +12 -6
  31. package/src/router/provider-invoker.ts +134 -0
  32. package/src/router/provider-stream.ts +192 -0
  33. package/src/router/provider-telemetry.ts +59 -0
  34. package/src/router/remote-mcp-execution.ts +61 -0
  35. package/src/router/request-program.ts +16 -0
  36. package/src/router/request-target.ts +56 -0
  37. package/src/router/resolve.test.ts +43 -10
  38. package/src/router/resolve.ts +50 -28
  39. package/src/router/response-delivery.ts +141 -0
  40. package/src/router/runtime.test.ts +56 -0
  41. package/src/router/runtime.ts +79 -9
  42. package/src/router/targets.ts +3 -1
  43. package/src/router/upstream-client.ts +137 -0
  44. package/src/telemetry.test.ts +50 -0
  45. package/src/telemetry.ts +316 -0
  46. package/src/trace-labels.ts +29 -0
  47. package/src/upstream-auth/env.ts +15 -3
  48. package/src/upstream-auth/proxy.ts +6 -1
  49. package/src/upstream-auth/registry.ts +3 -1
  50. package/src/util/glob.ts +1 -1
  51. package/src/util/model-syntax.ts +3 -3
  52. package/src/worker.ts +12 -6
  53. package/worker-configuration.d.ts +195 -164
  54. package/pnpm-workspace.yaml +0 -4
  55. package/src/admission/jwt.test.ts +0 -96
  56. package/src/admission/jwt.ts +0 -221
  57. package/src/app/handlers.ts +0 -198
  58. package/src/example.test.ts +0 -377
  59. package/src/example.ts +0 -73
  60. package/src/observer.test.ts +0 -54
  61. package/src/observer.ts +0 -315
  62. package/src/timing.ts +0 -36
@@ -1,32 +1,45 @@
1
- import { cloneProgram, validateProgram } from "@neutrome/lil-engine";
1
+ import { cloneProgram } from "@neutrome/lil-engine";
2
2
  import type { Program, ValidationMode } from "@neutrome/lil-engine";
3
3
  import type { ExecutorContext } from "@neutrome/lilsdk-ts";
4
- import { createAuditEvent } from "./audit.ts";
5
- import { ExecutionError, isExecutionError } from "./errors.ts";
6
- import { executionTargetAuditRef, executionTargetId } from "./targets.ts";
4
+ import { validateProgramOrThrow as validateObservedProgram } from "./execution-observation.ts";
5
+ import {
6
+ ensureActiveSignal as assertActiveSignal,
7
+ providerInvokerOrThrow as requireProviderInvoker,
8
+ resolveExecutorOrThrow as resolveRuntimeExecutor,
9
+ resolveTargetOrThrow as resolveRuntimeTarget,
10
+ throwIfRecursionExceeded as throwIfDepthExceeded,
11
+ } from "./execution-resolution.ts";
12
+ import {
13
+ invokeExecution,
14
+ streamExecution,
15
+ type InvocationOptions,
16
+ } from "./execution-invocation.ts";
17
+ import { applyExecutionTransforms } from "./execution-transforms.ts";
7
18
  import type {
8
- AuditEvent,
9
19
  ExecutionRuntime,
10
20
  ExecutionRuntimeOptions,
11
21
  ExecutionTarget,
12
- Executor,
13
22
  InvokeOptions,
14
- ProgramTransform,
15
23
  ProviderInvocationContext,
16
- ProviderInvoker,
17
24
  } from "./execution-types.ts";
18
25
 
19
26
  const DEFAULT_MAX_DEPTH = 8;
20
27
 
21
- export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): ExecutionRuntime {
28
+ export function createExecutionRuntime(
29
+ options: ExecutionRuntimeOptions = {},
30
+ ): ExecutionRuntime {
22
31
  let requestCounter = 0;
23
32
  let executionCounter = 0;
24
33
 
25
- const requestIdFactory = options.requestIdFactory ?? (() => `req_${++requestCounter}`);
26
- const executionIdFactory = options.executionIdFactory ?? (() => `exec_${++executionCounter}`);
34
+ const requestIdFactory =
35
+ options.requestIdFactory ?? (() => `req_${++requestCounter}`);
36
+ const executionIdFactory =
37
+ options.executionIdFactory ?? (() => `exec_${++executionCounter}`);
27
38
  const observe = options.observe ?? (() => {});
28
39
  const fallbackSignal = options.signal ?? new AbortController().signal;
29
- const executorImplementations = new Map(Object.entries(options.executorImplementations ?? {}));
40
+ const executorImplementations = new Map(
41
+ Object.entries(options.executorImplementations ?? {}),
42
+ );
30
43
  const transforms = new Map(Object.entries(options.transforms ?? {}));
31
44
  const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
32
45
  const validatePrograms = options.validatePrograms ?? true;
@@ -40,19 +53,19 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
40
53
  },
41
54
  };
42
55
 
43
- async function executeTarget(
56
+ async function prepareExecution(
44
57
  request: ReturnType<typeof cloneProgram>,
45
- invokeOptions: InvokeOptions = {},
58
+ invokeOptions: InvokeOptions,
46
59
  depth: number,
47
60
  ) {
48
- throwIfRecursionExceeded(depth);
61
+ throwIfDepthExceeded(depth, maxDepth);
49
62
  const requestId = invokeOptions.requestId ?? requestIdFactory();
50
63
  const executionId = invokeOptions.executionId ?? executionIdFactory();
51
64
  const parentExecutionId = invokeOptions.parentExecutionId;
52
65
  const signal = fallbackSignal;
53
- const target = resolveTargetOrThrow(request, invokeOptions);
66
+ const target = resolveRuntimeTarget(options, request, invokeOptions);
54
67
 
55
- ensureActiveSignal(signal, "request", target, depth);
68
+ assertActiveSignal(signal, target, depth, maxDepth);
56
69
  validateProgramOrThrow(request, {
57
70
  stage: "request",
58
71
  mode: "program",
@@ -64,287 +77,117 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
64
77
  emitSuccess: true,
65
78
  });
66
79
 
67
- const transformed = await applyTransforms(
68
- request,
69
- target,
80
+ return {
70
81
  requestId,
71
82
  executionId,
72
83
  parentExecutionId,
73
84
  signal,
74
- depth,
75
- );
76
-
77
- if (target.kind === "executor") {
78
- const executor = await getExecutorOrThrow(target.executorId);
79
- const startedMs = Date.now();
80
- const startedAt = new Date(startedMs).toISOString();
81
- observe(createAuditEvent({
82
- kind: "executor.started",
83
- requestId,
84
- executionId,
85
- target: executionTargetAuditRef(target),
86
- data: {
87
- executorId: target.executorId,
88
- alias: target.alias,
89
- transforms: target.transforms ?? [],
90
- depth,
91
- maxDepth,
92
- startedAt,
93
- },
94
- ...withParentExecutionId(parentExecutionId),
95
- }));
96
-
97
- try {
98
- const result = await executor.execute(
99
- transformed,
100
- createExecutionContext(requestId, executionId, parentExecutionId, signal, depth),
101
- );
102
- validateProgramOrThrow(result, {
103
- stage: "executor_result",
104
- mode: "program",
85
+ target,
86
+ transformed: await applyExecutionTransforms({
87
+ program: request,
88
+ target,
89
+ transforms,
90
+ context: createExecutionContext(
105
91
  requestId,
106
92
  executionId,
107
93
  parentExecutionId,
108
- target,
94
+ signal,
109
95
  depth,
110
- emitSuccess: true,
111
- });
112
- const finishedMs = Date.now();
113
- observe(createAuditEvent({
114
- kind: "executor.finished",
115
- requestId,
116
- executionId,
117
- target: executionTargetAuditRef(target),
118
- data: {
119
- startedAt,
120
- finishedAt: new Date(finishedMs).toISOString(),
121
- durationMs: finishedMs - startedMs,
122
- },
123
- ...withParentExecutionId(parentExecutionId),
124
- }));
125
- return result;
126
- } catch (error) {
127
- const normalized = normalizeExecutionError(error, "executor", "executor_invoke", target, depth);
128
- observeExecutionError(normalized, requestId, executionId, parentExecutionId, target, depth);
129
- throw normalized;
130
- }
131
- }
132
-
133
- const providerInvoker = getProviderInvokerOrThrow();
134
- const startedMs = Date.now();
135
- const startedAt = new Date(startedMs).toISOString();
136
- const providerCtx = createProviderInvocationContext(
137
- requestId,
138
- executionId,
139
- parentExecutionId,
140
- target,
141
- signal,
142
- );
143
-
144
- observe(createAuditEvent({
145
- kind: "provider.started",
146
- requestId,
147
- executionId,
148
- target: executionTargetAuditRef(target),
149
- data: {
150
- model: target.model,
151
- provider: target.provider ?? "",
152
- transforms: target.transforms ?? [],
153
- depth,
154
- maxDepth,
155
- startedAt,
156
- },
157
- ...withParentExecutionId(parentExecutionId),
158
- }));
159
-
160
- try {
161
- const result = await providerInvoker.execute(transformed, providerCtx);
162
- validateProgramOrThrow(result, {
163
- stage: "provider_result",
164
- mode: "program",
96
+ ),
165
97
  requestId,
166
98
  executionId,
167
99
  parentExecutionId,
168
- target,
169
100
  depth,
170
- emitSuccess: true,
171
- });
172
- const finishedMs = Date.now();
173
- observe(createAuditEvent({
174
- kind: "provider.finished",
175
- requestId,
176
- executionId,
177
- target: executionTargetAuditRef(target),
178
- data: {
179
- provider: target.provider ?? "",
180
- model: target.model,
181
- startedAt,
182
- finishedAt: new Date(finishedMs).toISOString(),
183
- durationMs: finishedMs - startedMs,
184
- },
185
- ...withParentExecutionId(parentExecutionId),
186
- }));
187
- return result;
188
- } catch (error) {
189
- const normalized = normalizeExecutionError(error, "provider", "provider_invoke", target, depth);
190
- observeExecutionError(normalized, requestId, executionId, parentExecutionId, target, depth);
191
- throw normalized;
192
- }
101
+ maxDepth,
102
+ observe,
103
+ validate: (program) =>
104
+ validateProgramOrThrow(program, {
105
+ stage: "transform_output",
106
+ mode: "program",
107
+ requestId,
108
+ executionId,
109
+ parentExecutionId,
110
+ target,
111
+ depth,
112
+ emitSuccess: true,
113
+ }),
114
+ }),
115
+ };
193
116
  }
194
117
 
195
- async function* streamTarget(
118
+ async function executeTarget(
196
119
  request: ReturnType<typeof cloneProgram>,
197
120
  invokeOptions: InvokeOptions = {},
198
121
  depth: number,
199
122
  ) {
200
- throwIfRecursionExceeded(depth);
201
- const requestId = invokeOptions.requestId ?? requestIdFactory();
202
- const executionId = invokeOptions.executionId ?? executionIdFactory();
203
- const parentExecutionId = invokeOptions.parentExecutionId;
204
- const signal = fallbackSignal;
205
- const target = resolveTargetOrThrow(request, invokeOptions);
206
-
207
- ensureActiveSignal(signal, "request", target, depth);
208
- validateProgramOrThrow(request, {
209
- stage: "request",
210
- mode: "program",
211
- requestId,
212
- executionId,
213
- parentExecutionId,
214
- target,
215
- depth,
216
- emitSuccess: true,
217
- });
218
-
219
- const transformed = await applyTransforms(
220
- request,
221
- target,
222
- requestId,
223
- executionId,
224
- parentExecutionId,
225
- signal,
226
- depth,
123
+ return invokeExecution(
124
+ await prepareInvocation(request, invokeOptions, depth),
227
125
  );
126
+ }
228
127
 
229
- if (target.kind === "executor") {
230
- const executor = await getExecutorOrThrow(target.executorId);
231
- const startedMs = Date.now();
232
- const startedAt = new Date(startedMs).toISOString();
233
- observe(createAuditEvent({
234
- kind: "executor.stream_started",
235
- requestId,
236
- executionId,
237
- target: executionTargetAuditRef(target),
238
- data: {
239
- executorId: target.executorId,
240
- alias: target.alias,
241
- transforms: target.transforms ?? [],
242
- depth,
243
- maxDepth,
244
- startedAt,
245
- },
246
- ...withParentExecutionId(parentExecutionId),
247
- }));
248
-
249
- try {
250
- for await (const chunk of executor.stream(
251
- transformed,
252
- createExecutionContext(requestId, executionId, parentExecutionId, signal, depth),
253
- )) {
254
- validateProgramOrThrow(chunk, {
255
- stage: "stream_chunk",
256
- mode: "stream_chunk",
257
- requestId,
258
- executionId,
259
- parentExecutionId,
260
- target,
261
- depth,
262
- emitSuccess: false,
263
- });
264
- yield chunk;
265
- }
266
- const finishedMs = Date.now();
267
- observe(createAuditEvent({
268
- kind: "executor.stream_finished",
269
- requestId,
270
- executionId,
271
- target: executionTargetAuditRef(target),
272
- data: {
273
- startedAt,
274
- finishedAt: new Date(finishedMs).toISOString(),
275
- durationMs: finishedMs - startedMs,
276
- },
277
- ...withParentExecutionId(parentExecutionId),
278
- }));
279
- return;
280
- } catch (error) {
281
- const normalized = normalizeExecutionError(error, "stream", "stream_chunk", target, depth);
282
- observeExecutionError(normalized, requestId, executionId, parentExecutionId, target, depth);
283
- throw normalized;
284
- }
285
- }
286
-
287
- const providerInvoker = getProviderInvokerOrThrow();
288
- const startedMs = Date.now();
289
- const startedAt = new Date(startedMs).toISOString();
290
- const providerCtx = createProviderInvocationContext(
291
- requestId,
292
- executionId,
293
- parentExecutionId,
294
- target,
295
- signal,
128
+ async function* streamTarget(
129
+ request: ReturnType<typeof cloneProgram>,
130
+ invokeOptions: InvokeOptions = {},
131
+ depth: number,
132
+ ) {
133
+ yield* streamExecution(
134
+ await prepareInvocation(request, invokeOptions, depth),
296
135
  );
136
+ }
297
137
 
298
- observe(createAuditEvent({
299
- kind: "provider.stream_started",
300
- requestId,
301
- executionId,
302
- target: executionTargetAuditRef(target),
303
- data: {
304
- model: target.model,
305
- provider: target.provider ?? "",
306
- transforms: target.transforms ?? [],
138
+ async function prepareInvocation(
139
+ request: Program,
140
+ invokeOptions: InvokeOptions,
141
+ depth: number,
142
+ ): Promise<InvocationOptions> {
143
+ const prepared = await prepareExecution(request, invokeOptions, depth);
144
+ const executor =
145
+ prepared.target.kind === "executor"
146
+ ? await resolveRuntimeExecutor(
147
+ options,
148
+ executorImplementations,
149
+ prepared.target.executorId,
150
+ )
151
+ : undefined;
152
+ return {
153
+ program: prepared.transformed,
154
+ identity: { ...prepared, depth },
155
+ signal: prepared.signal,
156
+ maxDepth,
157
+ observe,
158
+ executor,
159
+ providerInvoker:
160
+ prepared.target.kind === "provider"
161
+ ? requireProviderInvoker(options)
162
+ : undefined,
163
+ createExecutorContext: () =>
164
+ createExecutionContext(
165
+ prepared.requestId,
166
+ prepared.executionId,
167
+ prepared.parentExecutionId,
168
+ prepared.signal,
307
169
  depth,
308
- maxDepth,
309
- startedAt,
310
- },
311
- ...withParentExecutionId(parentExecutionId),
312
- }));
313
-
314
- try {
315
- for await (const chunk of providerInvoker.stream(transformed, providerCtx)) {
316
- validateProgramOrThrow(chunk, {
317
- stage: "stream_chunk",
318
- mode: "stream_chunk",
319
- requestId,
320
- executionId,
321
- parentExecutionId,
170
+ ),
171
+ createProviderContext: (target) =>
172
+ createProviderInvocationContext(
173
+ prepared.requestId,
174
+ prepared.executionId,
175
+ prepared.parentExecutionId,
322
176
  target,
177
+ prepared.signal,
178
+ ),
179
+ validate: (program, stage, mode) =>
180
+ validateProgramOrThrow(program, {
181
+ stage,
182
+ mode,
183
+ requestId: prepared.requestId,
184
+ executionId: prepared.executionId,
185
+ parentExecutionId: prepared.parentExecutionId,
186
+ target: prepared.target,
323
187
  depth,
324
- emitSuccess: false,
325
- });
326
- yield chunk;
327
- }
328
- const finishedMs = Date.now();
329
- observe(createAuditEvent({
330
- kind: "provider.stream_finished",
331
- requestId,
332
- executionId,
333
- target: executionTargetAuditRef(target),
334
- data: {
335
- provider: target.provider ?? "",
336
- model: target.model,
337
- startedAt,
338
- finishedAt: new Date(finishedMs).toISOString(),
339
- durationMs: finishedMs - startedMs,
340
- },
341
- ...withParentExecutionId(parentExecutionId),
342
- }));
343
- } catch (error) {
344
- const normalized = normalizeExecutionError(error, "stream", "stream_chunk", target, depth);
345
- observeExecutionError(normalized, requestId, executionId, parentExecutionId, target, depth);
346
- throw normalized;
347
- }
188
+ emitSuccess: stage !== "stream_chunk",
189
+ }),
190
+ };
348
191
  }
349
192
 
350
193
  function createExecutionContext(
@@ -359,172 +202,30 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
359
202
  executionId,
360
203
  ...(parentExecutionId ? { parentExecutionId } : {}),
361
204
  async invoke(request, invokeOptions = {}) {
362
- return executeTarget(cloneProgram(request), {
363
- ...invokeOptions,
364
- requestId,
365
- parentExecutionId: executionId,
366
- }, depth + 1);
367
- },
368
- async *invokeStream(request, invokeOptions = {}) {
369
- yield* streamTarget(cloneProgram(request), {
370
- ...invokeOptions,
371
- requestId,
372
- parentExecutionId: executionId,
373
- }, depth + 1);
374
- },
375
- observe,
376
- signal,
377
- };
378
- }
379
-
380
- async function applyTransforms(
381
- program: ReturnType<typeof cloneProgram>,
382
- target: ExecutionTarget,
383
- requestId: string,
384
- executionId: string,
385
- parentExecutionId: string | undefined,
386
- signal: AbortSignal,
387
- depth: number,
388
- ) {
389
- let current = cloneProgram(program);
390
- const transformContext = createExecutionContext(
391
- requestId,
392
- executionId,
393
- parentExecutionId,
394
- signal,
395
- depth,
396
- );
397
- for (const transformName of target.transforms ?? []) {
398
- const transform = transforms.get(transformName);
399
- if (!transform) {
400
- throw new ExecutionError(
401
- "transform",
402
- `Unknown transform: ${transformName}`,
205
+ return executeTarget(
206
+ cloneProgram(request),
403
207
  {
404
- stage: "transform",
405
- details: { transform: transformName, target: executionTargetId(target), depth, maxDepth },
406
- },
407
- );
408
- }
409
- try {
410
- current = await transform.apply(current, transformContext);
411
- } catch (error) {
412
- throw new ExecutionError(
413
- "transform",
414
- `Transform ${transform.name} failed`,
415
- {
416
- stage: "transform",
417
- details: { transform: transform.name, target: executionTargetId(target), depth, maxDepth },
418
- cause: error,
208
+ ...invokeOptions,
209
+ requestId,
210
+ parentExecutionId: executionId,
419
211
  },
212
+ depth + 1,
420
213
  );
421
- }
422
- validateProgramOrThrow(current, {
423
- stage: "transform_output",
424
- mode: "program",
425
- requestId,
426
- executionId,
427
- parentExecutionId,
428
- target,
429
- depth,
430
- emitSuccess: true,
431
- });
432
- observe(createAuditEvent({
433
- kind: "transform.applied",
434
- requestId,
435
- executionId,
436
- target: executionTargetAuditRef(target),
437
- data: {
438
- transform: transform.name,
439
- target: executionTargetId(target),
440
- depth,
441
- maxDepth,
442
- },
443
- ...withParentExecutionId(parentExecutionId),
444
- }));
445
- }
446
- return current;
447
- }
448
-
449
- function resolveTargetOrThrow(
450
- request: ReturnType<typeof cloneProgram>,
451
- invokeOptions: InvokeOptions,
452
- ): ExecutionTarget {
453
- if (invokeOptions.target) {
454
- return invokeOptions.target;
455
- }
456
- if (options.resolveTarget) {
457
- try {
458
- return options.resolveTarget(request, invokeOptions);
459
- } catch (error) {
460
- throw new ExecutionError(
461
- "resolution",
462
- error instanceof Error ? error.message : "Execution target resolution failed",
214
+ },
215
+ async *invokeStream(request, invokeOptions = {}) {
216
+ yield* streamTarget(
217
+ cloneProgram(request),
463
218
  {
464
- stage: "target_resolution",
465
- cause: error,
219
+ ...invokeOptions,
220
+ requestId,
221
+ parentExecutionId: executionId,
466
222
  },
223
+ depth + 1,
467
224
  );
468
- }
469
- }
470
- throw new ExecutionError("resolution", "No execution target specified", {
471
- stage: "target_resolution",
472
- });
473
- }
474
-
475
- async function getExecutorOrThrow(name: string): Promise<Executor> {
476
- const cached = executorImplementations.get(name);
477
- if (cached) {
478
- return cached;
479
- }
480
-
481
- const resolved = await options.resolveExecutor?.(name);
482
- if (resolved) {
483
- executorImplementations.set(name, resolved);
484
- return resolved;
485
- }
486
-
487
- const executor = executorImplementations.get(name);
488
- if (!executor) {
489
- throw new ExecutionError("resolution", `Unknown executor: ${name}`, {
490
- stage: "target_resolution",
491
- });
492
- }
493
- return executor;
494
- }
495
-
496
- function getProviderInvokerOrThrow(): ProviderInvoker {
497
- if (!options.providerInvoker) {
498
- throw new ExecutionError("provider", "No provider invoker configured", {
499
- stage: "provider_invoke",
500
- });
501
- }
502
- return options.providerInvoker;
503
- }
504
-
505
- function observeExecutionError(
506
- error: ExecutionError,
507
- requestId: string,
508
- executionId: string,
509
- parentExecutionId: string | undefined,
510
- target: ExecutionTarget,
511
- depth: number,
512
- ): void {
513
- observe(createAuditEvent({
514
- kind: "execution.failed",
515
- requestId,
516
- executionId,
517
- target: executionTargetAuditRef(target),
518
- errorKind: error.kind,
519
- data: {
520
- message: error.message,
521
- stage: error.stage,
522
- depth,
523
- maxDepth,
524
- ...(error.details ?? {}),
525
225
  },
526
- ...withParentExecutionId(parentExecutionId),
527
- }));
226
+ observe,
227
+ signal,
228
+ };
528
229
  }
529
230
 
530
231
  function createProviderInvocationContext(
@@ -553,24 +254,15 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
553
254
  return { parentExecutionId };
554
255
  }
555
256
 
556
- function throwIfRecursionExceeded(depth: number): void {
557
- if (depth <= maxDepth) {
558
- return;
559
- }
560
- throw new ExecutionError(
561
- "recursion_limit",
562
- `Execution recursion depth ${depth} exceeds maxDepth ${maxDepth}`,
563
- {
564
- stage: "target_resolution",
565
- details: { depth, maxDepth },
566
- },
567
- );
568
- }
569
-
570
257
  function validateProgramOrThrow(
571
258
  program: Program,
572
259
  context: {
573
- stage: "request" | "transform_output" | "provider_result" | "executor_result" | "stream_chunk";
260
+ stage:
261
+ | "request"
262
+ | "transform_output"
263
+ | "provider_result"
264
+ | "executor_result"
265
+ | "stream_chunk";
574
266
  mode: ValidationMode;
575
267
  requestId: string;
576
268
  executionId: string;
@@ -580,100 +272,10 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
580
272
  emitSuccess: boolean;
581
273
  },
582
274
  ): void {
583
- if (!validatePrograms) {
584
- return;
585
- }
586
-
587
- const result = validateProgram(program, { mode: context.mode });
588
- if (!result.ok) {
589
- throw new ExecutionError(
590
- "validation",
591
- `Program validation failed during ${context.stage}`,
592
- {
593
- stage: context.stage,
594
- details: {
595
- issues: result.issues,
596
- depth: context.depth,
597
- maxDepth,
598
- target: executionTargetId(context.target),
599
- },
600
- },
601
- );
602
- }
603
-
604
- if (context.emitSuccess) {
605
- observe(createAuditEvent({
606
- kind: "program.validated",
607
- requestId: context.requestId,
608
- executionId: context.executionId,
609
- target: executionTargetAuditRef(context.target),
610
- data: {
611
- stage: context.stage,
612
- mode: context.mode,
613
- issues: 0,
614
- depth: context.depth,
615
- maxDepth,
616
- },
617
- ...withParentExecutionId(context.parentExecutionId),
618
- }));
619
- }
620
- }
621
-
622
- function ensureActiveSignal(
623
- signal: AbortSignal,
624
- stage: "request",
625
- target: ExecutionTarget,
626
- depth: number,
627
- ): void {
628
- if (!signal.aborted) {
629
- return;
630
- }
631
- throw new ExecutionError(
632
- "cancellation",
633
- "Execution aborted",
634
- {
635
- stage,
636
- details: { depth, maxDepth, target: executionTargetId(target) },
637
- cause: signal.reason,
638
- },
639
- );
640
- }
641
-
642
- function normalizeExecutionError(
643
- error: unknown,
644
- fallbackKind: "provider" | "executor" | "stream",
645
- fallbackStage: "provider_invoke" | "executor_invoke" | "stream_chunk",
646
- target: ExecutionTarget,
647
- depth: number,
648
- ): ExecutionError {
649
- if (isExecutionError(error)) {
650
- return error;
651
- }
652
- if (isAbortLike(error)) {
653
- return new ExecutionError(
654
- "cancellation",
655
- "Execution aborted",
656
- {
657
- stage: fallbackStage,
658
- details: { depth, maxDepth, target: executionTargetId(target) },
659
- cause: error,
660
- },
661
- );
662
- }
663
- return new ExecutionError(
664
- fallbackKind,
665
- error instanceof Error ? error.message : String(error),
666
- {
667
- stage: fallbackStage,
668
- details: { depth, maxDepth, target: executionTargetId(target) },
669
- cause: error,
670
- },
671
- );
672
- }
673
-
674
- function isAbortLike(error: unknown): boolean {
675
- return error instanceof DOMException
676
- ? error.name === "AbortError"
677
- : error instanceof Error && error.name === "AbortError";
275
+ validateObservedProgram(program, context, {
276
+ enabled: validatePrograms,
277
+ maxDepth,
278
+ observe,
279
+ });
678
280
  }
679
281
  }