@neutrome/open-ai-router 0.1.8 → 0.1.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/package.json +1 -1
- package/src/app/factory.ts +6 -1
- package/src/index.ts +2 -0
- package/src/router/execute.ts +82 -6
- package/src/router/index.ts +2 -0
package/package.json
CHANGED
package/src/app/factory.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "../router/index.ts";
|
|
15
15
|
import type { RequestContext } from "../auth/types.ts";
|
|
16
16
|
import type { RouterConfig } from "../router/config.ts";
|
|
17
|
-
import type { UsageReport } from "../router/execute.ts";
|
|
17
|
+
import type { UsageReport, ProviderCallReport } from "../router/execute.ts";
|
|
18
18
|
|
|
19
19
|
export type RouterAppAuth = {
|
|
20
20
|
authenticate(request: Request, env: Record<string, unknown>): Promise<object | null>;
|
|
@@ -24,6 +24,7 @@ export type RouterAppAuth = {
|
|
|
24
24
|
export type RouterAppHooks = {
|
|
25
25
|
beforeRequest?(ctx: { request: Request; model: string | null; env: Record<string, unknown> }): Promise<Response | void>;
|
|
26
26
|
onUsage?(report: UsageReport, env: Record<string, unknown>): void;
|
|
27
|
+
onProviderCall?(report: ProviderCallReport, env: Record<string, unknown>): void;
|
|
27
28
|
onToolCall?(report: { toolName: string }, env: Record<string, unknown>): void;
|
|
28
29
|
};
|
|
29
30
|
|
|
@@ -115,6 +116,9 @@ export function createRouterApp(options: RouterAppOptions) {
|
|
|
115
116
|
const onUsage = options.hooks?.onUsage
|
|
116
117
|
? (report: UsageReport) => options.hooks!.onUsage!(report, env)
|
|
117
118
|
: undefined;
|
|
119
|
+
const onProviderCall = options.hooks?.onProviderCall
|
|
120
|
+
? (report: ProviderCallReport) => options.hooks!.onProviderCall!(report, env)
|
|
121
|
+
: undefined;
|
|
118
122
|
|
|
119
123
|
return handler({
|
|
120
124
|
runtime,
|
|
@@ -124,6 +128,7 @@ export function createRouterApp(options: RouterAppOptions) {
|
|
|
124
128
|
...(options.observe ? { observe: options.observe } : {}),
|
|
125
129
|
...(options.upstreamTimeoutMs !== undefined ? { upstreamTimeoutMs: options.upstreamTimeoutMs } : {}),
|
|
126
130
|
...(onUsage ? { onUsage } : {}),
|
|
131
|
+
...(onProviderCall ? { onProviderCall } : {}),
|
|
127
132
|
...(forceStreaming ? { forceStreaming: true } : {}),
|
|
128
133
|
});
|
|
129
134
|
}
|
package/src/index.ts
CHANGED
package/src/router/execute.ts
CHANGED
|
@@ -36,11 +36,23 @@ const DEFAULT_UPSTREAM_TIMEOUT_MS = 120_000;
|
|
|
36
36
|
|
|
37
37
|
export type UsageReport = {
|
|
38
38
|
model: string;
|
|
39
|
+
provider: string;
|
|
39
40
|
tokensInput: number;
|
|
40
41
|
tokensOutput: number;
|
|
41
42
|
cachedTokensInput: number;
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
export type ProviderErrorReport = {
|
|
46
|
+
model: string;
|
|
47
|
+
provider: string;
|
|
48
|
+
status: number;
|
|
49
|
+
message: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type ProviderCallReport =
|
|
53
|
+
| { kind: "usage"; } & UsageReport
|
|
54
|
+
| { kind: "error"; } & ProviderErrorReport;
|
|
55
|
+
|
|
44
56
|
export type RouterExecutionOptions = Pick<
|
|
45
57
|
ExecutionRuntimeOptions,
|
|
46
58
|
"observe" | "requestIdFactory" | "executionIdFactory" | "resolveExecutor"
|
|
@@ -49,6 +61,7 @@ export type RouterExecutionOptions = Pick<
|
|
|
49
61
|
transforms?: Readonly<Record<string, ProgramTransform>>;
|
|
50
62
|
upstreamTimeoutMs?: number;
|
|
51
63
|
onUsage?: (report: UsageReport) => void;
|
|
64
|
+
onProviderCall?: (report: ProviderCallReport) => void;
|
|
52
65
|
};
|
|
53
66
|
|
|
54
67
|
export type HandleChatCompletionsOptions = RouterExecutionOptions & {
|
|
@@ -108,6 +121,7 @@ export function createRouterExecutionRuntime(
|
|
|
108
121
|
runtime,
|
|
109
122
|
ctx,
|
|
110
123
|
options.upstreamTimeoutMs,
|
|
124
|
+
options.onProviderCall,
|
|
111
125
|
),
|
|
112
126
|
};
|
|
113
127
|
|
|
@@ -297,6 +311,7 @@ export function createFetchProviderInvoker(
|
|
|
297
311
|
runtime: RouterRuntime,
|
|
298
312
|
ctx: RequestContext,
|
|
299
313
|
upstreamTimeoutMs = DEFAULT_UPSTREAM_TIMEOUT_MS,
|
|
314
|
+
onProviderCall?: (report: ProviderCallReport) => void,
|
|
300
315
|
): ProviderInvoker {
|
|
301
316
|
return {
|
|
302
317
|
async execute(request, providerCtx) {
|
|
@@ -312,11 +327,15 @@ export function createFetchProviderInvoker(
|
|
|
312
327
|
timed.signal,
|
|
313
328
|
);
|
|
314
329
|
if (!upstream.ok) {
|
|
315
|
-
|
|
330
|
+
const err = await toProviderError(provider.name, upstream);
|
|
331
|
+
reportProviderError(onProviderCall, providerCtx.target, err);
|
|
332
|
+
throw err;
|
|
316
333
|
}
|
|
317
334
|
|
|
318
335
|
const bytes = new Uint8Array(await upstream.arrayBuffer());
|
|
319
|
-
|
|
336
|
+
const response = parseProviderResponse(provider.style, bytes);
|
|
337
|
+
reportProviderUsage(onProviderCall, response, providerCtx.target);
|
|
338
|
+
return response;
|
|
320
339
|
} catch (error) {
|
|
321
340
|
throw normalizeUpstreamAbort(error, timed.signal);
|
|
322
341
|
} finally {
|
|
@@ -339,7 +358,9 @@ export function createFetchProviderInvoker(
|
|
|
339
358
|
timed.signal,
|
|
340
359
|
);
|
|
341
360
|
if (!upstream.ok) {
|
|
342
|
-
|
|
361
|
+
const err = await toProviderError(provider.name, upstream);
|
|
362
|
+
reportProviderError(onProviderCall, providerCtx.target, err);
|
|
363
|
+
throw err;
|
|
343
364
|
}
|
|
344
365
|
if (!isSse(upstream.headers) || !upstream.body) {
|
|
345
366
|
throw new RouterError(
|
|
@@ -350,6 +371,7 @@ export function createFetchProviderInvoker(
|
|
|
350
371
|
}
|
|
351
372
|
|
|
352
373
|
reader = upstream.body.getReader();
|
|
374
|
+
let lastChunkWithUsage: Program | null = null;
|
|
353
375
|
while (true) {
|
|
354
376
|
const { done, value } = await reader.read();
|
|
355
377
|
if (done) {
|
|
@@ -361,13 +383,16 @@ export function createFetchProviderInvoker(
|
|
|
361
383
|
for (const event of split.events) {
|
|
362
384
|
for (const data of eventDataLines(event)) {
|
|
363
385
|
if (data === "[DONE]") {
|
|
386
|
+
reportProviderUsage(onProviderCall, lastChunkWithUsage, providerCtx.target);
|
|
364
387
|
return;
|
|
365
388
|
}
|
|
366
389
|
if (data[0] !== "{") continue;
|
|
367
|
-
|
|
390
|
+
const chunk = parseProviderStreamChunk(
|
|
368
391
|
provider.style,
|
|
369
392
|
encoder.encode(data),
|
|
370
393
|
);
|
|
394
|
+
if (usageObject(chunk) !== undefined) lastChunkWithUsage = chunk;
|
|
395
|
+
yield chunk;
|
|
371
396
|
}
|
|
372
397
|
}
|
|
373
398
|
}
|
|
@@ -375,15 +400,19 @@ export function createFetchProviderInvoker(
|
|
|
375
400
|
if (buffer.length > 0) {
|
|
376
401
|
for (const data of eventDataLines(buffer)) {
|
|
377
402
|
if (data === "[DONE]") {
|
|
403
|
+
reportProviderUsage(onProviderCall, lastChunkWithUsage, providerCtx.target);
|
|
378
404
|
return;
|
|
379
405
|
}
|
|
380
406
|
if (data[0] !== "{") continue;
|
|
381
|
-
|
|
407
|
+
const chunk = parseProviderStreamChunk(
|
|
382
408
|
provider.style,
|
|
383
409
|
encoder.encode(data),
|
|
384
410
|
);
|
|
411
|
+
if (usageObject(chunk) !== undefined) lastChunkWithUsage = chunk;
|
|
412
|
+
yield chunk;
|
|
385
413
|
}
|
|
386
414
|
}
|
|
415
|
+
reportProviderUsage(onProviderCall, lastChunkWithUsage, providerCtx.target);
|
|
387
416
|
} catch (error) {
|
|
388
417
|
throw normalizeUpstreamAbort(error, timed.signal);
|
|
389
418
|
} finally {
|
|
@@ -727,8 +756,55 @@ function reportUsage(
|
|
|
727
756
|
const details = u.prompt_tokens_details as Record<string, unknown> | undefined;
|
|
728
757
|
const cachedTokensInput = details ? asNum(details.cached_tokens) : 0;
|
|
729
758
|
const model = resolved.target.kind === "provider" ? resolved.target.model : resolved.target.alias;
|
|
759
|
+
const provider = resolved.target.kind === "provider" ? (resolved.target.provider ?? "") : "";
|
|
760
|
+
try {
|
|
761
|
+
onUsage({ model, provider, tokensInput, tokensOutput, cachedTokensInput });
|
|
762
|
+
} catch {}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function reportProviderUsage(
|
|
766
|
+
onProviderCall: ((report: ProviderCallReport) => void) | undefined,
|
|
767
|
+
response: Program | null,
|
|
768
|
+
target: Extract<import("@neutrome/lil-engine").ExecutionTarget, { kind: "provider" }>,
|
|
769
|
+
): void {
|
|
770
|
+
if (!onProviderCall) return;
|
|
771
|
+
const usage = response ? usageObject(response) : undefined;
|
|
772
|
+
const u = (usage && typeof usage === "object" ? usage : {}) as Record<string, unknown>;
|
|
773
|
+
const tokensInput = asNum(u.prompt_tokens) || asNum(u.input_tokens);
|
|
774
|
+
const tokensOutput = asNum(u.completion_tokens) || asNum(u.output_tokens);
|
|
775
|
+
const details = u.prompt_tokens_details as Record<string, unknown> | undefined;
|
|
776
|
+
const cachedTokensInput = details ? asNum(details.cached_tokens) : 0;
|
|
777
|
+
try {
|
|
778
|
+
onProviderCall({
|
|
779
|
+
kind: "usage",
|
|
780
|
+
model: target.model,
|
|
781
|
+
provider: target.provider ?? "",
|
|
782
|
+
tokensInput,
|
|
783
|
+
tokensOutput,
|
|
784
|
+
cachedTokensInput,
|
|
785
|
+
});
|
|
786
|
+
} catch {}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function reportProviderError(
|
|
790
|
+
onProviderCall: ((report: ProviderCallReport) => void) | undefined,
|
|
791
|
+
target: Extract<import("@neutrome/lil-engine").ExecutionTarget, { kind: "provider" }>,
|
|
792
|
+
error: ProviderHttpError,
|
|
793
|
+
): void {
|
|
794
|
+
if (!onProviderCall) return;
|
|
795
|
+
let message = `HTTP ${error.status}`;
|
|
796
|
+
try {
|
|
797
|
+
const text = decoder.decode(error.body).slice(0, 500);
|
|
798
|
+
message = text || message;
|
|
799
|
+
} catch {}
|
|
730
800
|
try {
|
|
731
|
-
|
|
801
|
+
onProviderCall({
|
|
802
|
+
kind: "error",
|
|
803
|
+
model: target.model,
|
|
804
|
+
provider: target.provider ?? "",
|
|
805
|
+
status: error.status,
|
|
806
|
+
message,
|
|
807
|
+
});
|
|
732
808
|
} catch {}
|
|
733
809
|
}
|
|
734
810
|
|