@neutrome/open-ai-router 0.1.18 → 0.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.
@@ -1,5 +1,4 @@
1
1
  import {
2
- createAuditEvent,
3
2
  emitChatCompletionsResponse,
4
3
  emitProviderRequest,
5
4
  emitProviderResponse,
@@ -14,20 +13,24 @@ import {
14
13
  setStreaming,
15
14
  type Program,
16
15
  type ProviderStyle,
17
- withTools,
18
16
  usageObject,
19
- type ExecutionTarget,
20
17
  } from "@neutrome/lil-engine";
18
+ import { withTools } from "@neutrome/lilsdk-ts/tools";
21
19
  import {
22
20
  createExecutionRuntime,
23
- ExecutionError,
24
- type ExecutionRuntime,
25
- type ExecutionRuntimeOptions,
26
- type Executor,
27
- type ProgramTransform,
28
- type ProviderInvoker,
29
- } from "@neutrome/lil-engine";
30
- import type { RequestContext, TargetAuthContext } from "../auth/types.ts";
21
+ } from "./execution-runtime.ts";
22
+ import { ExecutionError } from "./errors.ts";
23
+ import type {
24
+ ExecutionTarget,
25
+ ExecutionRuntime,
26
+ ExecutionRuntimeOptions,
27
+ Executor,
28
+ ProgramTransform,
29
+ ProviderInvocationContext,
30
+ ProviderInvoker,
31
+ } from "./execution-types.ts";
32
+ import { createAuditEvent } from "./audit.ts";
33
+ import type { RequestContext, TargetAuthContext } from "../upstream-auth/types.ts";
31
34
  import { cloneForwardHeaders, isSse } from "../util/headers.ts";
32
35
  import { eventDataLines, splitSseEvents } from "../util/sse.ts";
33
36
  import { renderProgramAsLil } from "../observer.ts";
@@ -46,7 +49,7 @@ export type RouterExecutionOptions = Pick<
46
49
  ExecutionRuntimeOptions,
47
50
  "observe" | "requestIdFactory" | "executionIdFactory" | "resolveExecutor"
48
51
  > & {
49
- executors?: Readonly<Record<string, Executor>>;
52
+ executorImplementations?: Readonly<Record<string, Executor>>;
50
53
  transforms?: Readonly<Record<string, ProgramTransform>>;
51
54
  upstreamTimeoutMs?: number;
52
55
  remoteMcpClientFactory?: RemoteMcpClientFactory;
@@ -55,21 +58,25 @@ export type RouterExecutionOptions = Pick<
55
58
  export type HandleChatCompletionsOptions = RouterExecutionOptions & {
56
59
  runtime: RouterRuntime;
57
60
  ctx: RequestContext;
61
+ program?: Program;
58
62
  };
59
63
 
60
64
  export type HandleResponsesOptions = RouterExecutionOptions & {
61
65
  runtime: RouterRuntime;
62
66
  ctx: RequestContext;
67
+ program?: Program;
63
68
  };
64
69
 
65
70
  export type HandleAnthropicMessagesOptions = RouterExecutionOptions & {
66
71
  runtime: RouterRuntime;
67
72
  ctx: RequestContext;
73
+ program?: Program;
68
74
  };
69
75
 
70
76
  export type HandleGoogleGenAIOptions = RouterExecutionOptions & {
71
77
  runtime: RouterRuntime;
72
78
  ctx: RequestContext;
79
+ program?: Program;
73
80
  forceStreaming?: boolean;
74
81
  };
75
82
 
@@ -102,7 +109,7 @@ export function createRouterExecutionRuntime(
102
109
  ): ExecutionRuntime {
103
110
  const runtimeOptions: ExecutionRuntimeOptions = {
104
111
  signal: ctx.request.signal,
105
- resolveTarget(request) {
112
+ resolveTarget(request: Program) {
106
113
  return resolveRequestTarget(runtime, request).target;
107
114
  },
108
115
  providerInvoker: createFetchProviderInvoker(
@@ -112,8 +119,8 @@ export function createRouterExecutionRuntime(
112
119
  ),
113
120
  };
114
121
 
115
- if (options.executors) {
116
- runtimeOptions.executors = options.executors;
122
+ if (options.executorImplementations) {
123
+ runtimeOptions.executorImplementations = options.executorImplementations;
117
124
  }
118
125
  if (options.transforms) {
119
126
  runtimeOptions.transforms = options.transforms;
@@ -129,23 +136,23 @@ export function createRouterExecutionRuntime(
129
136
  }
130
137
 
131
138
  const userResolveExecutor = options.resolveExecutor;
132
- runtimeOptions.resolveExecutor = (name) => {
139
+ runtimeOptions.resolveExecutor = (name: string) => {
133
140
  if (userResolveExecutor) {
134
141
  const result = userResolveExecutor(name);
135
142
  if (result) return result;
136
143
  }
137
- return resolveNamespaceExecutor(runtime, name);
144
+ return resolveModelNamespaceExecutor(runtime, name);
138
145
  };
139
146
 
140
147
  return createExecutionRuntime(runtimeOptions);
141
148
  }
142
149
 
143
- function resolveNamespaceExecutor(
150
+ function resolveModelNamespaceExecutor(
144
151
  runtime: RouterRuntime,
145
152
  name: string,
146
153
  ): Executor | null {
147
- const executor = runtime.executors.get(name);
148
- if (!executor) return null;
154
+ const modelNamespace = runtime.modelNamespaces.get(name);
155
+ if (!modelNamespace) return null;
149
156
 
150
157
  return {
151
158
  async execute(request, ctx) {
@@ -167,8 +174,7 @@ export async function handleChatCompletions(
167
174
  options: HandleChatCompletionsOptions,
168
175
  ): Promise<Response> {
169
176
  try {
170
- const requestBody = new Uint8Array(await options.ctx.request.arrayBuffer());
171
- const request = parseChatCompletionsRequest(requestBody);
177
+ const request = options.program ?? await parseRequestProgram("chat-completions", options.ctx.request);
172
178
  const incomingExecutionId = `incoming_${crypto.randomUUID()}`;
173
179
  emitObservedProgram(options.observe, "request.received", request, {
174
180
  requestId: incomingExecutionId,
@@ -240,14 +246,13 @@ async function handleProviderStyle(
240
246
  options: RouterExecutionOptions & {
241
247
  runtime: RouterRuntime;
242
248
  ctx: RequestContext;
249
+ program?: Program;
243
250
  },
244
251
  mutateRequest?: (request: Program) => Program,
245
252
  ): Promise<Response> {
246
253
  try {
247
- const requestBody = new Uint8Array(await options.ctx.request.arrayBuffer());
248
- const request = mutateRequest
249
- ? mutateRequest(parseProviderRequest(style, requestBody))
250
- : parseProviderRequest(style, requestBody);
254
+ const baseRequest = options.program ?? await parseRequestProgram(style, options.ctx.request);
255
+ const request = mutateRequest ? mutateRequest(baseRequest) : baseRequest;
251
256
  const incomingExecutionId = `incoming_${crypto.randomUUID()}`;
252
257
  emitObservedProgram(options.observe, "request.received", request, {
253
258
  requestId: incomingExecutionId,
@@ -312,6 +317,16 @@ export function resolveRequestTarget(
312
317
  }
313
318
  }
314
319
 
320
+ export async function parseRequestProgram(
321
+ style: ProviderStyle,
322
+ request: Request,
323
+ ): Promise<Program> {
324
+ const body = new Uint8Array(await request.arrayBuffer());
325
+ return style === "chat-completions"
326
+ ? parseChatCompletionsRequest(body)
327
+ : parseProviderRequest(style, body);
328
+ }
329
+
315
330
  export function createFetchProviderInvoker(
316
331
  runtime: RouterRuntime,
317
332
  ctx: RequestContext,
@@ -556,13 +571,13 @@ function prepareRemoteMcpExecution(
556
571
  return {
557
572
  target: {
558
573
  kind: "executor",
559
- executor: executorName,
574
+ executorId: executorName,
560
575
  alias: resolved.requestedModel,
561
576
  },
562
577
  options: {
563
578
  ...options,
564
- executors: {
565
- ...(options.executors ?? {}),
579
+ executorImplementations: {
580
+ ...(options.executorImplementations ?? {}),
566
581
  [executorName]: withTools(remoteMcp.tools, resolved.target),
567
582
  },
568
583
  },
@@ -598,7 +613,7 @@ function routingHeaders(resolved: ResolvedTarget): Record<string, string> {
598
613
  "x-openairouter-engine": "open-ai-router",
599
614
  "x-openairouter-source": resolved.source,
600
615
  "x-openairouter-namespace": resolved.namespace,
601
- "x-openairouter-executor-id": resolved.target.executor,
616
+ "x-openairouter-executor-id": resolved.target.executorId,
602
617
  "x-openairouter-model-id": resolved.target.alias,
603
618
  };
604
619
  }
@@ -622,7 +637,7 @@ async function fetchProvider(
622
637
  headers.set(key, value);
623
638
  }
624
639
 
625
- for (const driver of runtime.authChain) {
640
+ for (const driver of runtime.upstreamAuthChain) {
626
641
  const targetCtx: TargetAuthContext = { ...ctx, providerName: provider.name };
627
642
  const auth = await driver.collectTarget(targetCtx);
628
643
  if (!auth) {
@@ -819,7 +834,7 @@ function createUpstreamSignal(
819
834
  }
820
835
 
821
836
  function emitProviderUsage(
822
- providerCtx: import("@neutrome/lil-engine").ProviderInvocationContext,
837
+ providerCtx: ProviderInvocationContext,
823
838
  response: Program | null,
824
839
  provider: string,
825
840
  requestBytes: number,
@@ -0,0 +1,327 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ cloneProgram,
4
+ createProgram,
5
+ Opcode,
6
+ } from "@neutrome/lil-engine";
7
+ import {
8
+ createAuditEvent,
9
+ createExecutionRuntime,
10
+ createInMemoryAuditSink,
11
+ ExecutionError,
12
+ executionTargetAuditRef,
13
+ executionTargetId,
14
+ } from "./index.ts";
15
+ import { observeExecutionStream } from "@neutrome/lilsdk-ts/stream";
16
+
17
+ describe("router execution runtime", () => {
18
+ it("creates audit events with a stable envelope", () => {
19
+ const event = createAuditEvent({
20
+ kind: "executor.started",
21
+ requestId: "req_1",
22
+ executionId: "exec_1",
23
+ target: { kind: "executor", id: "enei-1" },
24
+ });
25
+
26
+ expect(event.kind).toBe("executor.started");
27
+ expect(event.requestId).toBe("req_1");
28
+ expect(event.executionId).toBe("exec_1");
29
+ expect(event.target).toEqual({ kind: "executor", id: "enei-1" });
30
+ expect(typeof event.timestamp).toBe("string");
31
+ expect(event.timestamp.length).toBeGreaterThan(0);
32
+ });
33
+
34
+ it("collects audit events in memory", () => {
35
+ const sink = createInMemoryAuditSink();
36
+ sink.observe(createAuditEvent({
37
+ kind: "program.validated",
38
+ requestId: "req_2",
39
+ executionId: "exec_2",
40
+ data: { issues: 0 },
41
+ }));
42
+
43
+ expect(sink.events).toHaveLength(1);
44
+ expect(sink.events[0]?.kind).toBe("program.validated");
45
+ expect(sink.events[0]?.data).toEqual({ issues: 0 });
46
+ });
47
+
48
+ it("normalizes execution target identifiers", () => {
49
+ expect(executionTargetId({
50
+ kind: "provider",
51
+ provider: "openrouter",
52
+ model: "google/gemma-4-31b-it",
53
+ })).toBe("openrouter/google/gemma-4-31b-it");
54
+
55
+ expect(executionTargetAuditRef({
56
+ kind: "executor",
57
+ executorId: "enei",
58
+ alias: "enei-1",
59
+ })).toEqual({
60
+ kind: "executor",
61
+ id: "enei/enei-1",
62
+ });
63
+ });
64
+
65
+ it("invokes nested executors without router re-entry and preserves request identity", async () => {
66
+ const sink = createInMemoryAuditSink();
67
+ const runtime = createExecutionRuntime({
68
+ observe: sink.observe,
69
+ executorImplementations: {
70
+ parent: {
71
+ async execute(request, ctx) {
72
+ const childResult = await ctx.invoke(request, {
73
+ target: { kind: "executor", executorId: "child", alias: "child" },
74
+ });
75
+ return childResult;
76
+ },
77
+ async *stream() {
78
+ return;
79
+ },
80
+ },
81
+ child: {
82
+ async execute(request) {
83
+ const result = cloneProgram(request);
84
+ result.code.push({
85
+ opcode: Opcode.RESP_DONE,
86
+ value: { kind: "string", value: "stop" },
87
+ });
88
+ return result;
89
+ },
90
+ async *stream() {
91
+ return;
92
+ },
93
+ },
94
+ },
95
+ });
96
+
97
+ const request = createProgram({
98
+ code: [
99
+ { opcode: Opcode.SET_MODEL, value: { kind: "string", value: "enei-1" } },
100
+ ],
101
+ });
102
+
103
+ const result = await runtime.execute(request, {
104
+ target: { kind: "executor", executorId: "parent", alias: "parent" },
105
+ requestId: "req_nested",
106
+ executionId: "exec_root",
107
+ });
108
+
109
+ expect(result.code.at(-1)).toEqual({
110
+ opcode: Opcode.RESP_DONE,
111
+ value: { kind: "string", value: "stop" },
112
+ });
113
+
114
+ const started = sink.events.filter((event) => event.kind === "executor.started");
115
+ expect(started).toHaveLength(2);
116
+ expect(started[0]?.requestId).toBe("req_nested");
117
+ expect(started[1]?.requestId).toBe("req_nested");
118
+ expect(started[1]?.parentExecutionId).toBe("exec_root");
119
+ expect(started[0]?.data?.depth).toBe(0);
120
+ expect(started[1]?.data?.depth).toBe(1);
121
+ });
122
+
123
+ it("applies target transforms before provider invocation", async () => {
124
+ const providerCalls: string[] = [];
125
+ const runtime = createExecutionRuntime({
126
+ providerInvoker: {
127
+ async execute(request) {
128
+ const marker = request.code.find((instruction) =>
129
+ instruction.opcode === Opcode.SET_META &&
130
+ instruction.value.kind === "key_string" &&
131
+ instruction.value.key === "transform",
132
+ );
133
+ providerCalls.push(
134
+ marker?.value.kind === "key_string" ? marker.value.value : "missing",
135
+ );
136
+ return cloneProgram(request);
137
+ },
138
+ async *stream() {
139
+ return;
140
+ },
141
+ },
142
+ transforms: {
143
+ slwin: {
144
+ name: "slwin",
145
+ capabilities: ["write_messages"],
146
+ apply(program) {
147
+ const result = cloneProgram(program);
148
+ result.code.push({
149
+ opcode: Opcode.SET_META,
150
+ value: { kind: "key_string", key: "transform", value: "slwin" },
151
+ });
152
+ return result;
153
+ },
154
+ },
155
+ },
156
+ });
157
+
158
+ await runtime.execute(createProgram(), {
159
+ target: {
160
+ kind: "provider",
161
+ provider: "openrouter",
162
+ model: "gpt-4o",
163
+ transforms: ["slwin"],
164
+ },
165
+ });
166
+
167
+ expect(providerCalls).toEqual(["slwin"]);
168
+ });
169
+
170
+ it("streams through the provider invoker", async () => {
171
+ const runtime = createExecutionRuntime({
172
+ providerInvoker: {
173
+ async execute() {
174
+ throw new Error("not used");
175
+ },
176
+ async *stream(request) {
177
+ yield cloneProgram(request);
178
+ yield createProgram({
179
+ code: [
180
+ { opcode: Opcode.STREAM_DELTA, value: { kind: "string", value: "hello" } },
181
+ ],
182
+ });
183
+ },
184
+ },
185
+ });
186
+
187
+ const outputs = [];
188
+ for await (const chunk of runtime.stream(createProgram(), {
189
+ target: { kind: "provider", provider: "openrouter", model: "gpt-4o" },
190
+ })) {
191
+ outputs.push(chunk);
192
+ }
193
+
194
+ expect(outputs).toHaveLength(2);
195
+ expect(outputs[1]?.code[0]).toEqual({
196
+ opcode: Opcode.STREAM_DELTA,
197
+ value: { kind: "string", value: "hello" },
198
+ });
199
+ });
200
+
201
+ it("enforces recursion limits with typed errors", async () => {
202
+ const sink = createInMemoryAuditSink();
203
+ const runtime = createExecutionRuntime({
204
+ observe: sink.observe,
205
+ maxDepth: 1,
206
+ executorImplementations: {
207
+ parent: {
208
+ async execute(request, ctx) {
209
+ return ctx.invoke(request, {
210
+ target: { kind: "executor", executorId: "child", alias: "child" },
211
+ });
212
+ },
213
+ async *stream() {
214
+ return;
215
+ },
216
+ },
217
+ child: {
218
+ async execute(request, ctx) {
219
+ return ctx.invoke(request, {
220
+ target: { kind: "executor", executorId: "leaf", alias: "leaf" },
221
+ });
222
+ },
223
+ async *stream() {
224
+ return;
225
+ },
226
+ },
227
+ leaf: {
228
+ async execute(request) {
229
+ return request;
230
+ },
231
+ async *stream() {
232
+ return;
233
+ },
234
+ },
235
+ },
236
+ });
237
+
238
+ await expect(runtime.execute(createProgram(), {
239
+ target: { kind: "executor", executorId: "parent", alias: "parent" },
240
+ })).rejects.toMatchObject({
241
+ kind: "recursion_limit",
242
+ stage: "target_resolution",
243
+ } satisfies Partial<ExecutionError>);
244
+
245
+ const failed = sink.events.find((event) => event.kind === "execution.failed");
246
+ expect(failed?.errorKind).toBe("recursion_limit");
247
+ expect(failed?.data?.depth).toBe(2);
248
+ });
249
+
250
+ it("validates request and stream chunk shapes with typed validation errors", async () => {
251
+ const runtime = createExecutionRuntime({
252
+ providerInvoker: {
253
+ async execute(request) {
254
+ return request;
255
+ },
256
+ async *stream() {
257
+ yield createProgram({
258
+ code: [
259
+ { opcode: Opcode.MSG_START, value: { kind: "none" } },
260
+ ],
261
+ });
262
+ },
263
+ },
264
+ });
265
+
266
+ await expect(runtime.execute(createProgram({
267
+ code: [
268
+ { opcode: Opcode.EXT_DATA, value: { kind: "key_json", key: "provider", value: new Uint8Array([123, 125]) } },
269
+ ],
270
+ }), {
271
+ target: { kind: "provider", provider: "openrouter", model: "gpt-4o" },
272
+ })).rejects.toMatchObject({
273
+ kind: "validation",
274
+ stage: "request",
275
+ } satisfies Partial<ExecutionError>);
276
+
277
+ const iterator = runtime.stream(createProgram(), {
278
+ target: { kind: "provider", provider: "openrouter", model: "gpt-4o" },
279
+ })[Symbol.asyncIterator]();
280
+ await expect(iterator.next()).rejects.toMatchObject({
281
+ kind: "validation",
282
+ stage: "stream_chunk",
283
+ } satisfies Partial<ExecutionError>);
284
+ });
285
+
286
+ it("observes text-first streams and tool-call streams", async () => {
287
+ const textObserved = await observeExecutionStream((async function* () {
288
+ yield createProgram({
289
+ code: [
290
+ { opcode: Opcode.STREAM_DELTA, value: { kind: "string", value: "hello" } },
291
+ ],
292
+ });
293
+ yield createProgram({
294
+ code: [
295
+ { opcode: Opcode.STREAM_DELTA, value: { kind: "string", value: " world" } },
296
+ { opcode: Opcode.RESP_DONE, value: { kind: "string", value: "stop" } },
297
+ ],
298
+ });
299
+ })());
300
+
301
+ expect(textObserved).toEqual({
302
+ mode: "text",
303
+ transcript: "hello world",
304
+ emittedText: true,
305
+ });
306
+
307
+ const toolObserved = await observeExecutionStream((async function* () {
308
+ yield createProgram({
309
+ code: [
310
+ { opcode: Opcode.STREAM_TOOL_DELTA, value: { kind: "json", value: new Uint8Array([123, 125]) } },
311
+ ],
312
+ });
313
+ yield createProgram({
314
+ code: [
315
+ { opcode: Opcode.RESP_DONE, value: { kind: "string", value: "tool_calls" } },
316
+ ],
317
+ });
318
+ })());
319
+
320
+ expect(toolObserved.mode).toBe("tool");
321
+ if (toolObserved.mode === "tool") {
322
+ expect(toolObserved.chunks).toHaveLength(2);
323
+ expect(toolObserved.emittedText).toBe(false);
324
+ expect(toolObserved.transcript).toBe("");
325
+ }
326
+ });
327
+ });