@neutrome/open-ai-router 0.4.1 → 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 +248 -72
  26. package/src/router/execution-runtime.ts +144 -535
  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 = 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,
109
- 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 ?? [],
94
+ signal,
153
95
  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 = 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,165 +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
- 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
- for (const transformName of target.transforms ?? []) {
391
- const transform = transforms.get(transformName);
392
- if (!transform) {
393
- throw new ExecutionError(
394
- "transform",
395
- `Unknown transform: ${transformName}`,
205
+ return executeTarget(
206
+ cloneProgram(request),
396
207
  {
397
- stage: "transform",
398
- details: { transform: transformName, target: executionTargetId(target), depth, maxDepth },
399
- },
400
- );
401
- }
402
- try {
403
- current = transform.apply(current, { observe, signal });
404
- } catch (error) {
405
- throw new ExecutionError(
406
- "transform",
407
- `Transform ${transform.name} failed`,
408
- {
409
- stage: "transform",
410
- details: { transform: transform.name, target: executionTargetId(target), depth, maxDepth },
411
- cause: error,
208
+ ...invokeOptions,
209
+ requestId,
210
+ parentExecutionId: executionId,
412
211
  },
212
+ depth + 1,
413
213
  );
414
- }
415
- validateProgramOrThrow(current, {
416
- stage: "transform_output",
417
- mode: "program",
418
- requestId,
419
- executionId,
420
- parentExecutionId,
421
- target,
422
- depth,
423
- emitSuccess: true,
424
- });
425
- observe(createAuditEvent({
426
- kind: "transform.applied",
427
- requestId,
428
- executionId,
429
- target: executionTargetAuditRef(target),
430
- data: {
431
- transform: transform.name,
432
- target: executionTargetId(target),
433
- depth,
434
- maxDepth,
435
- },
436
- ...withParentExecutionId(parentExecutionId),
437
- }));
438
- }
439
- return current;
440
- }
441
-
442
- function resolveTargetOrThrow(
443
- request: ReturnType<typeof cloneProgram>,
444
- invokeOptions: InvokeOptions,
445
- ): ExecutionTarget {
446
- if (invokeOptions.target) {
447
- return invokeOptions.target;
448
- }
449
- if (options.resolveTarget) {
450
- try {
451
- return options.resolveTarget(request, invokeOptions);
452
- } catch (error) {
453
- throw new ExecutionError(
454
- "resolution",
455
- error instanceof Error ? error.message : "Execution target resolution failed",
214
+ },
215
+ async *invokeStream(request, invokeOptions = {}) {
216
+ yield* streamTarget(
217
+ cloneProgram(request),
456
218
  {
457
- stage: "target_resolution",
458
- cause: error,
219
+ ...invokeOptions,
220
+ requestId,
221
+ parentExecutionId: executionId,
459
222
  },
223
+ depth + 1,
460
224
  );
461
- }
462
- }
463
- throw new ExecutionError("resolution", "No execution target specified", {
464
- stage: "target_resolution",
465
- });
466
- }
467
-
468
- async function getExecutorOrThrow(name: string): Promise<Executor> {
469
- const cached = executorImplementations.get(name);
470
- if (cached) {
471
- return cached;
472
- }
473
-
474
- const resolved = await options.resolveExecutor?.(name);
475
- if (resolved) {
476
- executorImplementations.set(name, resolved);
477
- return resolved;
478
- }
479
-
480
- const executor = executorImplementations.get(name);
481
- if (!executor) {
482
- throw new ExecutionError("resolution", `Unknown executor: ${name}`, {
483
- stage: "target_resolution",
484
- });
485
- }
486
- return executor;
487
- }
488
-
489
- function getProviderInvokerOrThrow(): ProviderInvoker {
490
- if (!options.providerInvoker) {
491
- throw new ExecutionError("provider", "No provider invoker configured", {
492
- stage: "provider_invoke",
493
- });
494
- }
495
- return options.providerInvoker;
496
- }
497
-
498
- function observeExecutionError(
499
- error: ExecutionError,
500
- requestId: string,
501
- executionId: string,
502
- parentExecutionId: string | undefined,
503
- target: ExecutionTarget,
504
- depth: number,
505
- ): void {
506
- observe(createAuditEvent({
507
- kind: "execution.failed",
508
- requestId,
509
- executionId,
510
- target: executionTargetAuditRef(target),
511
- errorKind: error.kind,
512
- data: {
513
- message: error.message,
514
- stage: error.stage,
515
- depth,
516
- maxDepth,
517
- ...(error.details ?? {}),
518
225
  },
519
- ...withParentExecutionId(parentExecutionId),
520
- }));
226
+ observe,
227
+ signal,
228
+ };
521
229
  }
522
230
 
523
231
  function createProviderInvocationContext(
@@ -546,24 +254,15 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
546
254
  return { parentExecutionId };
547
255
  }
548
256
 
549
- function throwIfRecursionExceeded(depth: number): void {
550
- if (depth <= maxDepth) {
551
- return;
552
- }
553
- throw new ExecutionError(
554
- "recursion_limit",
555
- `Execution recursion depth ${depth} exceeds maxDepth ${maxDepth}`,
556
- {
557
- stage: "target_resolution",
558
- details: { depth, maxDepth },
559
- },
560
- );
561
- }
562
-
563
257
  function validateProgramOrThrow(
564
258
  program: Program,
565
259
  context: {
566
- 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";
567
266
  mode: ValidationMode;
568
267
  requestId: string;
569
268
  executionId: string;
@@ -573,100 +272,10 @@ export function createExecutionRuntime(options: ExecutionRuntimeOptions = {}): E
573
272
  emitSuccess: boolean;
574
273
  },
575
274
  ): void {
576
- if (!validatePrograms) {
577
- return;
578
- }
579
-
580
- const result = validateProgram(program, { mode: context.mode });
581
- if (!result.ok) {
582
- throw new ExecutionError(
583
- "validation",
584
- `Program validation failed during ${context.stage}`,
585
- {
586
- stage: context.stage,
587
- details: {
588
- issues: result.issues,
589
- depth: context.depth,
590
- maxDepth,
591
- target: executionTargetId(context.target),
592
- },
593
- },
594
- );
595
- }
596
-
597
- if (context.emitSuccess) {
598
- observe(createAuditEvent({
599
- kind: "program.validated",
600
- requestId: context.requestId,
601
- executionId: context.executionId,
602
- target: executionTargetAuditRef(context.target),
603
- data: {
604
- stage: context.stage,
605
- mode: context.mode,
606
- issues: 0,
607
- depth: context.depth,
608
- maxDepth,
609
- },
610
- ...withParentExecutionId(context.parentExecutionId),
611
- }));
612
- }
613
- }
614
-
615
- function ensureActiveSignal(
616
- signal: AbortSignal,
617
- stage: "request",
618
- target: ExecutionTarget,
619
- depth: number,
620
- ): void {
621
- if (!signal.aborted) {
622
- return;
623
- }
624
- throw new ExecutionError(
625
- "cancellation",
626
- "Execution aborted",
627
- {
628
- stage,
629
- details: { depth, maxDepth, target: executionTargetId(target) },
630
- cause: signal.reason,
631
- },
632
- );
633
- }
634
-
635
- function normalizeExecutionError(
636
- error: unknown,
637
- fallbackKind: "provider" | "executor" | "stream",
638
- fallbackStage: "provider_invoke" | "executor_invoke" | "stream_chunk",
639
- target: ExecutionTarget,
640
- depth: number,
641
- ): ExecutionError {
642
- if (isExecutionError(error)) {
643
- return error;
644
- }
645
- if (isAbortLike(error)) {
646
- return new ExecutionError(
647
- "cancellation",
648
- "Execution aborted",
649
- {
650
- stage: fallbackStage,
651
- details: { depth, maxDepth, target: executionTargetId(target) },
652
- cause: error,
653
- },
654
- );
655
- }
656
- return new ExecutionError(
657
- fallbackKind,
658
- error instanceof Error ? error.message : String(error),
659
- {
660
- stage: fallbackStage,
661
- details: { depth, maxDepth, target: executionTargetId(target) },
662
- cause: error,
663
- },
664
- );
665
- }
666
-
667
- function isAbortLike(error: unknown): boolean {
668
- return error instanceof DOMException
669
- ? error.name === "AbortError"
670
- : error instanceof Error && error.name === "AbortError";
275
+ validateObservedProgram(program, context, {
276
+ enabled: validatePrograms,
277
+ maxDepth,
278
+ observe,
279
+ });
671
280
  }
672
281
  }