@neutrome/open-ai-router 0.6.8 → 0.6.10
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/README.md +1 -1
- package/package.json +4 -3
- package/src/app/factory.ts +1 -1
- package/src/router/error-codec.ts +2 -2
- package/src/router/execute.test.ts +1 -1
- package/src/router/execute.ts +5 -9
- package/src/router/execution-resolution.ts +2 -2
- package/src/router/provider-stream.ts +4 -4
- package/src/router/provider-telemetry.ts +2 -2
- package/src/router/remote-mcp-execution.ts +5 -2
- package/src/router/request-program.ts +1 -2
- package/src/router/response-delivery.ts +4 -4
- package/src/telemetry/chat-normalization.ts +16 -15
- package/src/telemetry.test.ts +5 -5
package/README.md
CHANGED
|
@@ -30,4 +30,4 @@ Notes:
|
|
|
30
30
|
|
|
31
31
|
- `RouterConfig` is the canonical router config surface.
|
|
32
32
|
- `/v1/models` lists configured executor aliases.
|
|
33
|
-
- Executor contracts and `
|
|
33
|
+
- Executor contracts and `createToolsExecutor` come from `@neutrome/lilsdk`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neutrome/open-ai-router",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts"
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"hono": "^4.12.18",
|
|
12
12
|
"posthog-node": "^5.41.0",
|
|
13
13
|
"zod": "^4.2.1",
|
|
14
|
-
"@neutrome/lil-engine": "0.
|
|
15
|
-
"@neutrome/lilsdk": "0.
|
|
14
|
+
"@neutrome/lil-engine": "0.5.1",
|
|
15
|
+
"@neutrome/lilsdk": "0.5.1"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^25.9.3",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"cf-typegen": "wrangler types",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
28
|
+
"npm:publish": "pnpm publish --no-git-checks",
|
|
28
29
|
"dev": "wrangler dev",
|
|
29
30
|
"preview": "wrangler preview",
|
|
30
31
|
"deploy": "wrangler deploy"
|
package/src/app/factory.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
appendErrorBlock,
|
|
3
3
|
createProgram,
|
|
4
4
|
emitProviderError,
|
|
5
|
-
|
|
5
|
+
getFirstErrorBlock,
|
|
6
6
|
parseProviderError,
|
|
7
7
|
type Program,
|
|
8
8
|
type ProviderStyle,
|
|
@@ -21,7 +21,7 @@ export async function toProviderError(
|
|
|
21
21
|
contentType: response.headers.get("content-type"),
|
|
22
22
|
});
|
|
23
23
|
const summary =
|
|
24
|
-
|
|
24
|
+
getFirstErrorBlock(program)?.message ||
|
|
25
25
|
`Provider ${provider.name} returned ${response.status}`;
|
|
26
26
|
return new ProviderHttpError(
|
|
27
27
|
summary,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendAssistantMessage } from "@neutrome/
|
|
1
|
+
import { appendAssistantMessage } from "@neutrome/lil-engine";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import type { RequestContext } from "../upstream-auth/types.ts";
|
|
4
4
|
import { handleChatCompletions } from "./execute.ts";
|
package/src/router/execute.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
emitChatCompletionsResponse,
|
|
3
2
|
emitProviderError,
|
|
4
3
|
emitProviderRequest,
|
|
5
4
|
emitProviderResponse,
|
|
6
5
|
emitProviderStreamChunk,
|
|
7
6
|
getModel,
|
|
7
|
+
getUsage,
|
|
8
8
|
isStreaming,
|
|
9
|
-
parseChatCompletionsRequest,
|
|
10
9
|
parseProviderRequest,
|
|
11
10
|
parseProviderResponse,
|
|
12
11
|
parseProviderStreamChunk,
|
|
13
|
-
setModel,
|
|
14
|
-
setStreaming,
|
|
15
12
|
type Program,
|
|
16
13
|
type ProviderStyle,
|
|
17
|
-
|
|
14
|
+
setModel,
|
|
15
|
+
setStreaming,
|
|
18
16
|
} from "@neutrome/lil-engine";
|
|
19
17
|
import { createExecutionRuntime } from "./execution-runtime.ts";
|
|
20
18
|
import { createFetchProviderInvoker } from "./provider-invoker.ts";
|
|
@@ -165,10 +163,8 @@ export function createRouterExecutionRuntime(
|
|
|
165
163
|
export async function handleChatCompletions(
|
|
166
164
|
options: HandleChatCompletionsOptions,
|
|
167
165
|
): Promise<Response> {
|
|
168
|
-
return handleRequestStyle(
|
|
169
|
-
"chat-completions",
|
|
170
|
-
options,
|
|
171
|
-
emitChatCompletionsResponse,
|
|
166
|
+
return handleRequestStyle("chat-completions", options, (program) =>
|
|
167
|
+
emitProviderResponse("chat-completions", program),
|
|
172
168
|
);
|
|
173
169
|
}
|
|
174
170
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFirstErrorBlock, type Program } from "@neutrome/lil-engine";
|
|
2
2
|
import {
|
|
3
3
|
ExecutionError,
|
|
4
4
|
isExecutionError,
|
|
@@ -100,7 +100,7 @@ export function normalizeExecutionError(
|
|
|
100
100
|
): ExecutionError {
|
|
101
101
|
if (isExecutionError(error)) return error;
|
|
102
102
|
if (error instanceof ProviderHttpError) {
|
|
103
|
-
const upstream =
|
|
103
|
+
const upstream = getFirstErrorBlock(error.program);
|
|
104
104
|
return new ExecutionError("provider", error.message, {
|
|
105
105
|
stage: fallbackStage,
|
|
106
106
|
details: {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
emitProviderRequest,
|
|
3
|
+
getUsage,
|
|
3
4
|
isStreaming,
|
|
4
5
|
parseProviderStreamChunk,
|
|
5
|
-
setModel,
|
|
6
6
|
type Program,
|
|
7
|
-
|
|
7
|
+
setModel,
|
|
8
8
|
} from "@neutrome/lil-engine";
|
|
9
9
|
import { RouterError } from "./errors.ts";
|
|
10
10
|
import { toProviderError } from "./error-codec.ts";
|
|
@@ -155,7 +155,7 @@ export async function* streamProvider(
|
|
|
155
155
|
encoder.encode(data),
|
|
156
156
|
);
|
|
157
157
|
responseChunks.push(chunk);
|
|
158
|
-
if (
|
|
158
|
+
if (getUsage(chunk) !== undefined) lastChunkWithUsage = chunk;
|
|
159
159
|
yield chunk;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -181,7 +181,7 @@ export async function* streamProvider(
|
|
|
181
181
|
encoder.encode(data),
|
|
182
182
|
);
|
|
183
183
|
responseChunks.push(chunk);
|
|
184
|
-
if (
|
|
184
|
+
if (getUsage(chunk) !== undefined) lastChunkWithUsage = chunk;
|
|
185
185
|
yield chunk;
|
|
186
186
|
}
|
|
187
187
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getUsage, type Program } from "@neutrome/lil-engine";
|
|
2
2
|
import { createExecutionEvent } from "@neutrome/lilsdk";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import {
|
|
@@ -21,7 +21,7 @@ export function emitProviderUsage(
|
|
|
21
21
|
responseBytes: number,
|
|
22
22
|
streamChunks?: readonly Program[],
|
|
23
23
|
): void {
|
|
24
|
-
const usage = response ?
|
|
24
|
+
const usage = response ? getUsage(response) : undefined;
|
|
25
25
|
const u = (usage && typeof usage === "object" ? usage : {}) as Record<
|
|
26
26
|
string,
|
|
27
27
|
unknown
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createToolsExecutor } from "@neutrome/lilsdk/tools";
|
|
2
2
|
import type {
|
|
3
3
|
ExecutionTarget,
|
|
4
4
|
Executor,
|
|
@@ -49,7 +49,10 @@ export function prepareRemoteMcpExecution(
|
|
|
49
49
|
...options,
|
|
50
50
|
executorImplementations: {
|
|
51
51
|
...(options.executorImplementations ?? {}),
|
|
52
|
-
[executorName]:
|
|
52
|
+
[executorName]: createToolsExecutor(
|
|
53
|
+
resolved.requestedModel,
|
|
54
|
+
remoteMcp.tools,
|
|
55
|
+
),
|
|
53
56
|
},
|
|
54
57
|
},
|
|
55
58
|
close: remoteMcp.close,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
parseChatCompletionsRequest,
|
|
3
2
|
parseProviderRequest,
|
|
4
3
|
type Program,
|
|
5
4
|
type ProviderStyle,
|
|
@@ -11,6 +10,6 @@ export async function parseRequestProgram(
|
|
|
11
10
|
): Promise<Program> {
|
|
12
11
|
const body = new Uint8Array(await request.arrayBuffer());
|
|
13
12
|
return style === "chat-completions"
|
|
14
|
-
?
|
|
13
|
+
? parseProviderRequest("chat-completions", body)
|
|
15
14
|
: parseProviderRequest(style, body);
|
|
16
15
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
delta,
|
|
2
3
|
emitProviderError,
|
|
3
4
|
emitProviderStreamChunk,
|
|
4
|
-
repackStream,
|
|
5
5
|
type Program,
|
|
6
6
|
type ProviderStyle,
|
|
7
7
|
} from "@neutrome/lil-engine";
|
|
@@ -21,9 +21,9 @@ export async function streamExecutionResponse(
|
|
|
21
21
|
input: { requestId: string; executionId: string; startedAtMs: number },
|
|
22
22
|
cleanup?: () => Promise<void>,
|
|
23
23
|
): Promise<Response> {
|
|
24
|
-
const iterator =
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const iterator = delta
|
|
25
|
+
.repack(chunks, { target: responseStyle })
|
|
26
|
+
[Symbol.asyncIterator]();
|
|
27
27
|
|
|
28
28
|
const first = await iterator.next();
|
|
29
29
|
observeTimedExecution({
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
delta,
|
|
3
|
+
emitProviderRequest,
|
|
4
|
+
emitProviderResponse,
|
|
5
|
+
getUsage,
|
|
6
6
|
Opcode,
|
|
7
|
-
|
|
7
|
+
type Program,
|
|
8
8
|
} from "@neutrome/lil-engine";
|
|
9
9
|
|
|
10
10
|
const decoder = new TextDecoder();
|
|
@@ -13,7 +13,7 @@ const decoder = new TextDecoder();
|
|
|
13
13
|
export function normalizeChatRequest(
|
|
14
14
|
program: Program,
|
|
15
15
|
): Record<string, unknown> {
|
|
16
|
-
const request = json(
|
|
16
|
+
const request = json(emitProviderRequest("chat-completions", program));
|
|
17
17
|
delete request.stream;
|
|
18
18
|
return request;
|
|
19
19
|
}
|
|
@@ -21,7 +21,7 @@ export function normalizeChatRequest(
|
|
|
21
21
|
export function normalizeChatResponse(
|
|
22
22
|
program: Program,
|
|
23
23
|
): Record<string, unknown> {
|
|
24
|
-
return json(
|
|
24
|
+
return json(emitProviderResponse("chat-completions", program));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function normalizeChatStream(
|
|
@@ -39,7 +39,7 @@ export function normalizeChatStream(
|
|
|
39
39
|
let usage: unknown;
|
|
40
40
|
|
|
41
41
|
for (const chunk of chunks) {
|
|
42
|
-
const chunkUsage =
|
|
42
|
+
const chunkUsage = getUsage(chunk);
|
|
43
43
|
if (chunkUsage !== undefined) usage = chunkUsage;
|
|
44
44
|
for (const instruction of chunk.code) {
|
|
45
45
|
if (
|
|
@@ -68,13 +68,14 @@ export function normalizeChatStream(
|
|
|
68
68
|
)
|
|
69
69
|
reasoning.push(instruction.value.value);
|
|
70
70
|
if (instruction.opcode !== Opcode.STREAM_TOOL_DELTA) continue;
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
73
|
-
const tool = tools.get(
|
|
74
|
-
if (
|
|
75
|
-
if (
|
|
76
|
-
if (
|
|
77
|
-
|
|
71
|
+
const toolCall = delta.decodeToolCall(instruction);
|
|
72
|
+
if (!toolCall) continue;
|
|
73
|
+
const tool = tools.get(toolCall.index) ?? { arguments: "" };
|
|
74
|
+
if (toolCall.id !== undefined) tool.id = toolCall.id;
|
|
75
|
+
if (toolCall.name !== undefined) tool.name = toolCall.name;
|
|
76
|
+
if (toolCall.arguments !== undefined)
|
|
77
|
+
tool.arguments += toolCall.arguments;
|
|
78
|
+
tools.set(toolCall.index, tool);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
package/src/telemetry.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createExecutionEvent } from "@neutrome/lilsdk";
|
|
2
|
-
import {
|
|
2
|
+
import { delta } from "@neutrome/lil-engine";
|
|
3
3
|
import { describe, expect, it, vi } from "vitest";
|
|
4
4
|
import {
|
|
5
5
|
createRouterTelemetry,
|
|
@@ -185,10 +185,10 @@ describe("trace chat normalization", () => {
|
|
|
185
185
|
it("concatenates stream chunks into a chat completion response", () => {
|
|
186
186
|
expect(
|
|
187
187
|
normalizeChatStream([
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
delta.start(),
|
|
189
|
+
delta.text("hello "),
|
|
190
|
+
delta.text("world"),
|
|
191
|
+
delta.end("length"),
|
|
192
192
|
]),
|
|
193
193
|
).toEqual({
|
|
194
194
|
object: "chat.completion",
|