@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.
- package/.dev.vars.example +2 -0
- package/README.md +5 -1
- package/package.json +5 -4
- package/src/admission/access.ts +0 -1
- package/src/admission/types.ts +0 -1
- package/src/app/factory.test.ts +40 -34
- package/src/app/factory.ts +120 -173
- package/src/app/google-genai-route.ts +48 -0
- package/src/app/http-utils.ts +14 -0
- package/src/app/model-listing.ts +29 -0
- package/src/app/response-lifecycle.ts +66 -0
- package/src/app/types.ts +38 -0
- package/src/index.ts +11 -43
- package/src/otlp.ts +148 -0
- package/src/router/audit.ts +5 -12
- package/src/router/config.ts +4 -4
- package/src/router/error-codec.ts +170 -0
- package/src/router/errors.ts +38 -1
- package/src/router/execute.test.ts +177 -107
- package/src/router/execute.ts +68 -924
- package/src/router/execution-events.ts +39 -0
- package/src/router/execution-invocation.ts +144 -0
- package/src/router/execution-observation.ts +161 -0
- package/src/router/execution-resolution.ts +120 -0
- package/src/router/execution-runtime.test.ts +156 -83
- package/src/router/execution-runtime.ts +144 -542
- package/src/router/execution-transforms.ts +82 -0
- package/src/router/execution-types.ts +8 -19
- package/src/router/index.ts +6 -5
- package/src/router/mcp.ts +12 -6
- package/src/router/provider-invoker.ts +134 -0
- package/src/router/provider-stream.ts +192 -0
- package/src/router/provider-telemetry.ts +59 -0
- package/src/router/remote-mcp-execution.ts +61 -0
- package/src/router/request-program.ts +16 -0
- package/src/router/request-target.ts +56 -0
- package/src/router/resolve.test.ts +43 -10
- package/src/router/resolve.ts +50 -28
- package/src/router/response-delivery.ts +141 -0
- package/src/router/runtime.test.ts +56 -0
- package/src/router/runtime.ts +79 -9
- package/src/router/targets.ts +3 -1
- package/src/router/upstream-client.ts +137 -0
- package/src/telemetry.test.ts +50 -0
- package/src/telemetry.ts +316 -0
- package/src/trace-labels.ts +29 -0
- package/src/upstream-auth/env.ts +15 -3
- package/src/upstream-auth/proxy.ts +6 -1
- package/src/upstream-auth/registry.ts +3 -1
- package/src/util/glob.ts +1 -1
- package/src/util/model-syntax.ts +3 -3
- package/src/worker.ts +12 -6
- package/worker-configuration.d.ts +195 -164
- package/pnpm-workspace.yaml +0 -4
- package/src/admission/jwt.test.ts +0 -96
- package/src/admission/jwt.ts +0 -221
- package/src/app/handlers.ts +0 -198
- package/src/example.test.ts +0 -377
- package/src/example.ts +0 -73
- package/src/observer.test.ts +0 -54
- package/src/observer.ts +0 -315
- package/src/timing.ts +0 -36
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createExecutionEvent } from "@neutrome/lilsdk-ts";
|
|
2
|
+
import type { ExecutionEvent } from "@neutrome/lilsdk-ts";
|
|
3
|
+
|
|
4
|
+
type TimedExecutionInput = {
|
|
5
|
+
observe?: ((event: ExecutionEvent) => void) | undefined;
|
|
6
|
+
kind: string;
|
|
7
|
+
requestId: string;
|
|
8
|
+
executionId: string;
|
|
9
|
+
parentExecutionId?: string | undefined;
|
|
10
|
+
startedAtMs: number;
|
|
11
|
+
finishedAtMs?: number | undefined;
|
|
12
|
+
data?: Record<string, unknown> | undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function observeTimedExecution(input: TimedExecutionInput): void {
|
|
16
|
+
if (!input.observe) return;
|
|
17
|
+
const data: Record<string, unknown> = {
|
|
18
|
+
...(input.data ?? {}),
|
|
19
|
+
startedAt: new Date(input.startedAtMs).toISOString(),
|
|
20
|
+
};
|
|
21
|
+
if (input.finishedAtMs !== undefined) {
|
|
22
|
+
data.finishedAt = new Date(input.finishedAtMs).toISOString();
|
|
23
|
+
data.durationMs = Math.max(0, input.finishedAtMs - input.startedAtMs);
|
|
24
|
+
}
|
|
25
|
+
input.observe(
|
|
26
|
+
createExecutionEvent({
|
|
27
|
+
kind: input.kind,
|
|
28
|
+
requestId: input.requestId,
|
|
29
|
+
executionId: input.executionId,
|
|
30
|
+
...(input.parentExecutionId
|
|
31
|
+
? { parentExecutionId: input.parentExecutionId }
|
|
32
|
+
: {}),
|
|
33
|
+
timestamp: new Date(
|
|
34
|
+
input.finishedAtMs ?? input.startedAtMs,
|
|
35
|
+
).toISOString(),
|
|
36
|
+
data,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Program, ValidationMode } from "@neutrome/lil-engine";
|
|
2
|
+
import type { ExecutorContext } from "@neutrome/lilsdk-ts";
|
|
3
|
+
import {
|
|
4
|
+
finishInvocation,
|
|
5
|
+
observeExecutionError,
|
|
6
|
+
startInvocation,
|
|
7
|
+
type ExecutionObservation,
|
|
8
|
+
} from "./execution-observation.ts";
|
|
9
|
+
import { normalizeExecutionError } from "./execution-resolution.ts";
|
|
10
|
+
import type {
|
|
11
|
+
ExecutionTarget,
|
|
12
|
+
Executor,
|
|
13
|
+
ProviderInvocationContext,
|
|
14
|
+
ProviderInvoker,
|
|
15
|
+
} from "./execution-types.ts";
|
|
16
|
+
|
|
17
|
+
type InvocationIdentity = {
|
|
18
|
+
requestId: string;
|
|
19
|
+
executionId: string;
|
|
20
|
+
parentExecutionId: string | undefined;
|
|
21
|
+
target: ExecutionTarget;
|
|
22
|
+
depth: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type InvocationOptions = {
|
|
26
|
+
program: Program;
|
|
27
|
+
identity: InvocationIdentity;
|
|
28
|
+
signal: AbortSignal;
|
|
29
|
+
maxDepth: number;
|
|
30
|
+
observe: ExecutionObservation;
|
|
31
|
+
executor: Executor | undefined;
|
|
32
|
+
providerInvoker: ProviderInvoker | undefined;
|
|
33
|
+
createExecutorContext(): ExecutorContext;
|
|
34
|
+
createProviderContext(
|
|
35
|
+
target: Extract<ExecutionTarget, { kind: "provider" }>,
|
|
36
|
+
): ProviderInvocationContext;
|
|
37
|
+
validate(
|
|
38
|
+
program: Program,
|
|
39
|
+
stage: "provider_result" | "executor_result" | "stream_chunk",
|
|
40
|
+
mode: ValidationMode,
|
|
41
|
+
): void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export async function invokeExecution(
|
|
45
|
+
options: InvocationOptions,
|
|
46
|
+
): Promise<Program> {
|
|
47
|
+
const primitive = invocationPrimitive(options);
|
|
48
|
+
const timing = startInvocation(
|
|
49
|
+
options.identity,
|
|
50
|
+
options.maxDepth,
|
|
51
|
+
false,
|
|
52
|
+
options.observe,
|
|
53
|
+
);
|
|
54
|
+
try {
|
|
55
|
+
const result = await primitive.execute(options.program);
|
|
56
|
+
options.validate(result, primitive.resultStage, "program");
|
|
57
|
+
finishInvocation(options.identity, timing, false, options.observe);
|
|
58
|
+
return result;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw observedFailure(
|
|
61
|
+
options,
|
|
62
|
+
error,
|
|
63
|
+
primitive.failureKind,
|
|
64
|
+
primitive.stage,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function* streamExecution(
|
|
70
|
+
options: InvocationOptions,
|
|
71
|
+
): AsyncGenerator<Program> {
|
|
72
|
+
const primitive = invocationPrimitive(options);
|
|
73
|
+
const timing = startInvocation(
|
|
74
|
+
options.identity,
|
|
75
|
+
options.maxDepth,
|
|
76
|
+
true,
|
|
77
|
+
options.observe,
|
|
78
|
+
);
|
|
79
|
+
try {
|
|
80
|
+
for await (const chunk of primitive.stream(options.program)) {
|
|
81
|
+
options.validate(chunk, "stream_chunk", "stream_chunk");
|
|
82
|
+
yield chunk;
|
|
83
|
+
}
|
|
84
|
+
finishInvocation(options.identity, timing, true, options.observe);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw observedFailure(options, error, "stream", "stream_chunk");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function invocationPrimitive(options: InvocationOptions): {
|
|
91
|
+
execute(program: Program): Promise<Program>;
|
|
92
|
+
stream(program: Program): AsyncIterable<Program>;
|
|
93
|
+
resultStage: "provider_result" | "executor_result";
|
|
94
|
+
failureKind: "provider" | "executor";
|
|
95
|
+
stage: "provider_invoke" | "executor_invoke";
|
|
96
|
+
} {
|
|
97
|
+
if (options.identity.target.kind === "executor") {
|
|
98
|
+
const executor = options.executor;
|
|
99
|
+
if (!executor) throw new Error("Missing executor invocation primitive");
|
|
100
|
+
const context = options.createExecutorContext();
|
|
101
|
+
return {
|
|
102
|
+
execute: (program) => executor.execute(program, context),
|
|
103
|
+
stream: (program) => executor.stream(program, context),
|
|
104
|
+
resultStage: "executor_result",
|
|
105
|
+
failureKind: "executor",
|
|
106
|
+
stage: "executor_invoke",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const providerInvoker = options.providerInvoker;
|
|
111
|
+
if (!providerInvoker)
|
|
112
|
+
throw new Error("Missing provider invocation primitive");
|
|
113
|
+
const context = options.createProviderContext(options.identity.target);
|
|
114
|
+
return {
|
|
115
|
+
execute: (program) => providerInvoker.execute(program, context),
|
|
116
|
+
stream: (program) => providerInvoker.stream(program, context),
|
|
117
|
+
resultStage: "provider_result",
|
|
118
|
+
failureKind: "provider",
|
|
119
|
+
stage: "provider_invoke",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function observedFailure(
|
|
124
|
+
options: InvocationOptions,
|
|
125
|
+
cause: unknown,
|
|
126
|
+
fallbackKind: "provider" | "executor" | "stream",
|
|
127
|
+
stage: "provider_invoke" | "executor_invoke" | "stream_chunk",
|
|
128
|
+
) {
|
|
129
|
+
const error = normalizeExecutionError(
|
|
130
|
+
cause,
|
|
131
|
+
fallbackKind,
|
|
132
|
+
stage,
|
|
133
|
+
options.identity.target,
|
|
134
|
+
options.identity.depth,
|
|
135
|
+
options.maxDepth,
|
|
136
|
+
);
|
|
137
|
+
observeExecutionError(
|
|
138
|
+
error,
|
|
139
|
+
options.identity,
|
|
140
|
+
options.maxDepth,
|
|
141
|
+
options.observe,
|
|
142
|
+
);
|
|
143
|
+
return error;
|
|
144
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { validateProgram } from "@neutrome/lil-engine";
|
|
2
|
+
import type { Program, ValidationMode } from "@neutrome/lil-engine";
|
|
3
|
+
import { createExecutionEvent } from "@neutrome/lilsdk-ts";
|
|
4
|
+
import type { ExecutionEvent } from "@neutrome/lilsdk-ts";
|
|
5
|
+
import { ExecutionError } from "./errors.ts";
|
|
6
|
+
import { executionTargetAuditRef, executionTargetId } from "./targets.ts";
|
|
7
|
+
import type { ExecutionTarget } from "./execution-types.ts";
|
|
8
|
+
|
|
9
|
+
export type ExecutionObservation = (event: ExecutionEvent) => void;
|
|
10
|
+
|
|
11
|
+
type ExecutionIdentity = {
|
|
12
|
+
requestId: string;
|
|
13
|
+
executionId: string;
|
|
14
|
+
parentExecutionId: string | undefined;
|
|
15
|
+
target: ExecutionTarget;
|
|
16
|
+
depth: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function startInvocation(
|
|
20
|
+
identity: ExecutionIdentity,
|
|
21
|
+
maxDepth: number,
|
|
22
|
+
streaming: boolean,
|
|
23
|
+
observe: ExecutionObservation,
|
|
24
|
+
): { startedMs: number; startedAt: string } {
|
|
25
|
+
const startedMs = Date.now();
|
|
26
|
+
const startedAt = new Date(startedMs).toISOString();
|
|
27
|
+
const { target } = identity;
|
|
28
|
+
observe(
|
|
29
|
+
createExecutionEvent({
|
|
30
|
+
kind: `${target.kind === "provider" ? "provider" : "executor"}.${streaming ? "stream_started" : "started"}`,
|
|
31
|
+
requestId: identity.requestId,
|
|
32
|
+
executionId: identity.executionId,
|
|
33
|
+
target: executionTargetAuditRef(target),
|
|
34
|
+
data: {
|
|
35
|
+
...(target.kind === "provider"
|
|
36
|
+
? { provider: target.provider ?? "", model: target.model }
|
|
37
|
+
: { executorId: target.executorId, alias: target.alias }),
|
|
38
|
+
transforms: target.transforms ?? [],
|
|
39
|
+
depth: identity.depth,
|
|
40
|
+
maxDepth,
|
|
41
|
+
startedAt,
|
|
42
|
+
},
|
|
43
|
+
...parent(identity.parentExecutionId),
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
return { startedMs, startedAt };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function finishInvocation(
|
|
50
|
+
identity: ExecutionIdentity,
|
|
51
|
+
timing: { startedMs: number; startedAt: string },
|
|
52
|
+
streaming: boolean,
|
|
53
|
+
observe: ExecutionObservation,
|
|
54
|
+
): void {
|
|
55
|
+
const finishedMs = Date.now();
|
|
56
|
+
const { target } = identity;
|
|
57
|
+
observe(
|
|
58
|
+
createExecutionEvent({
|
|
59
|
+
kind: `${target.kind === "provider" ? "provider" : "executor"}.${streaming ? "stream_finished" : "finished"}`,
|
|
60
|
+
requestId: identity.requestId,
|
|
61
|
+
executionId: identity.executionId,
|
|
62
|
+
target: executionTargetAuditRef(target),
|
|
63
|
+
data: {
|
|
64
|
+
...(target.kind === "provider"
|
|
65
|
+
? { provider: target.provider ?? "", model: target.model }
|
|
66
|
+
: {}),
|
|
67
|
+
startedAt: timing.startedAt,
|
|
68
|
+
finishedAt: new Date(finishedMs).toISOString(),
|
|
69
|
+
durationMs: finishedMs - timing.startedMs,
|
|
70
|
+
},
|
|
71
|
+
...parent(identity.parentExecutionId),
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function observeExecutionError(
|
|
77
|
+
error: ExecutionError,
|
|
78
|
+
identity: ExecutionIdentity,
|
|
79
|
+
maxDepth: number,
|
|
80
|
+
observe: ExecutionObservation,
|
|
81
|
+
): void {
|
|
82
|
+
observe(
|
|
83
|
+
createExecutionEvent({
|
|
84
|
+
kind: "execution.failed",
|
|
85
|
+
requestId: identity.requestId,
|
|
86
|
+
executionId: identity.executionId,
|
|
87
|
+
target: executionTargetAuditRef(identity.target),
|
|
88
|
+
errorKind: error.kind,
|
|
89
|
+
data: {
|
|
90
|
+
message: error.message,
|
|
91
|
+
stage: error.stage,
|
|
92
|
+
depth: identity.depth,
|
|
93
|
+
maxDepth,
|
|
94
|
+
...(error.details ?? {}),
|
|
95
|
+
},
|
|
96
|
+
...parent(identity.parentExecutionId),
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function validateProgramOrThrow(
|
|
102
|
+
program: Program,
|
|
103
|
+
context: ExecutionIdentity & {
|
|
104
|
+
stage:
|
|
105
|
+
| "request"
|
|
106
|
+
| "transform_output"
|
|
107
|
+
| "provider_result"
|
|
108
|
+
| "executor_result"
|
|
109
|
+
| "stream_chunk";
|
|
110
|
+
mode: ValidationMode;
|
|
111
|
+
emitSuccess: boolean;
|
|
112
|
+
},
|
|
113
|
+
options: {
|
|
114
|
+
enabled: boolean;
|
|
115
|
+
maxDepth: number;
|
|
116
|
+
observe: ExecutionObservation;
|
|
117
|
+
},
|
|
118
|
+
): void {
|
|
119
|
+
if (!options.enabled) return;
|
|
120
|
+
|
|
121
|
+
const result = validateProgram(program, { mode: context.mode });
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
throw new ExecutionError(
|
|
124
|
+
"validation",
|
|
125
|
+
`Program validation failed during ${context.stage}`,
|
|
126
|
+
{
|
|
127
|
+
stage: context.stage,
|
|
128
|
+
details: {
|
|
129
|
+
issues: result.issues,
|
|
130
|
+
depth: context.depth,
|
|
131
|
+
maxDepth: options.maxDepth,
|
|
132
|
+
target: executionTargetId(context.target),
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (!context.emitSuccess) return;
|
|
138
|
+
|
|
139
|
+
options.observe(
|
|
140
|
+
createExecutionEvent({
|
|
141
|
+
kind: "program.validated",
|
|
142
|
+
requestId: context.requestId,
|
|
143
|
+
executionId: context.executionId,
|
|
144
|
+
target: executionTargetAuditRef(context.target),
|
|
145
|
+
data: {
|
|
146
|
+
stage: context.stage,
|
|
147
|
+
mode: context.mode,
|
|
148
|
+
issues: 0,
|
|
149
|
+
depth: context.depth,
|
|
150
|
+
maxDepth: options.maxDepth,
|
|
151
|
+
},
|
|
152
|
+
...parent(context.parentExecutionId),
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parent(parentExecutionId: string | undefined): {
|
|
158
|
+
parentExecutionId?: string;
|
|
159
|
+
} {
|
|
160
|
+
return parentExecutionId === undefined ? {} : { parentExecutionId };
|
|
161
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Program } from "@neutrome/lil-engine";
|
|
2
|
+
import { ExecutionError, isExecutionError } from "./errors.ts";
|
|
3
|
+
import { executionTargetId } from "./targets.ts";
|
|
4
|
+
import type {
|
|
5
|
+
ExecutionRuntimeOptions,
|
|
6
|
+
ExecutionTarget,
|
|
7
|
+
Executor,
|
|
8
|
+
InvokeOptions,
|
|
9
|
+
ProviderInvoker,
|
|
10
|
+
} from "./execution-types.ts";
|
|
11
|
+
|
|
12
|
+
export function resolveTargetOrThrow(
|
|
13
|
+
options: ExecutionRuntimeOptions,
|
|
14
|
+
request: Program,
|
|
15
|
+
invokeOptions: InvokeOptions,
|
|
16
|
+
): ExecutionTarget {
|
|
17
|
+
if (invokeOptions.target) return invokeOptions.target;
|
|
18
|
+
if (options.resolveTarget) {
|
|
19
|
+
try {
|
|
20
|
+
return options.resolveTarget(request, invokeOptions);
|
|
21
|
+
} catch (cause) {
|
|
22
|
+
throw new ExecutionError(
|
|
23
|
+
"resolution",
|
|
24
|
+
cause instanceof Error
|
|
25
|
+
? cause.message
|
|
26
|
+
: "Execution target resolution failed",
|
|
27
|
+
{ stage: "target_resolution", cause },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new ExecutionError("resolution", "No execution target specified", {
|
|
32
|
+
stage: "target_resolution",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function resolveExecutorOrThrow(
|
|
37
|
+
options: ExecutionRuntimeOptions,
|
|
38
|
+
executors: Map<string, Executor>,
|
|
39
|
+
name: string,
|
|
40
|
+
): Promise<Executor> {
|
|
41
|
+
const cached = executors.get(name);
|
|
42
|
+
if (cached) return cached;
|
|
43
|
+
|
|
44
|
+
const resolved = await options.resolveExecutor?.(name);
|
|
45
|
+
if (resolved) {
|
|
46
|
+
executors.set(name, resolved);
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
49
|
+
throw new ExecutionError("resolution", `Unknown executor: ${name}`, {
|
|
50
|
+
stage: "target_resolution",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function providerInvokerOrThrow(
|
|
55
|
+
options: ExecutionRuntimeOptions,
|
|
56
|
+
): ProviderInvoker {
|
|
57
|
+
if (options.providerInvoker) return options.providerInvoker;
|
|
58
|
+
throw new ExecutionError("provider", "No provider invoker configured", {
|
|
59
|
+
stage: "provider_invoke",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function throwIfRecursionExceeded(
|
|
64
|
+
depth: number,
|
|
65
|
+
maxDepth: number,
|
|
66
|
+
): void {
|
|
67
|
+
if (depth <= maxDepth) return;
|
|
68
|
+
throw new ExecutionError(
|
|
69
|
+
"recursion_limit",
|
|
70
|
+
`Execution recursion depth ${depth} exceeds maxDepth ${maxDepth}`,
|
|
71
|
+
{ stage: "target_resolution", details: { depth, maxDepth } },
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ensureActiveSignal(
|
|
76
|
+
signal: AbortSignal,
|
|
77
|
+
target: ExecutionTarget,
|
|
78
|
+
depth: number,
|
|
79
|
+
maxDepth: number,
|
|
80
|
+
): void {
|
|
81
|
+
if (!signal.aborted) return;
|
|
82
|
+
throw new ExecutionError("cancellation", "Execution aborted", {
|
|
83
|
+
stage: "request",
|
|
84
|
+
details: { depth, maxDepth, target: executionTargetId(target) },
|
|
85
|
+
cause: signal.reason,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function normalizeExecutionError(
|
|
90
|
+
error: unknown,
|
|
91
|
+
fallbackKind: "provider" | "executor" | "stream",
|
|
92
|
+
fallbackStage: "provider_invoke" | "executor_invoke" | "stream_chunk",
|
|
93
|
+
target: ExecutionTarget,
|
|
94
|
+
depth: number,
|
|
95
|
+
maxDepth: number,
|
|
96
|
+
): ExecutionError {
|
|
97
|
+
if (isExecutionError(error)) return error;
|
|
98
|
+
if (isAbortLike(error)) {
|
|
99
|
+
return new ExecutionError("cancellation", "Execution aborted", {
|
|
100
|
+
stage: fallbackStage,
|
|
101
|
+
details: { depth, maxDepth, target: executionTargetId(target) },
|
|
102
|
+
cause: error,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return new ExecutionError(
|
|
106
|
+
fallbackKind,
|
|
107
|
+
error instanceof Error ? error.message : String(error),
|
|
108
|
+
{
|
|
109
|
+
stage: fallbackStage,
|
|
110
|
+
details: { depth, maxDepth, target: executionTargetId(target) },
|
|
111
|
+
cause: error,
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isAbortLike(error: unknown): boolean {
|
|
117
|
+
return error instanceof DOMException
|
|
118
|
+
? error.name === "AbortError"
|
|
119
|
+
: error instanceof Error && error.name === "AbortError";
|
|
120
|
+
}
|