@neutrome/open-ai-router 0.6.0 → 0.6.2
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 +3 -3
- package/src/router/execute.ts +10 -2
- package/src/router/resolve.test.ts +28 -0
- package/src/router/resolve.ts +35 -0
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.2",
|
|
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.4.
|
|
15
|
-
"@neutrome/lilsdk": "0.4.
|
|
14
|
+
"@neutrome/lil-engine": "0.4.1",
|
|
15
|
+
"@neutrome/lilsdk": "0.4.1"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^25.9.3",
|
package/src/router/execute.ts
CHANGED
|
@@ -55,7 +55,11 @@ import type {
|
|
|
55
55
|
import type { RequestContext } from "../upstream-auth/types.ts";
|
|
56
56
|
import { isSse } from "../util/headers.ts";
|
|
57
57
|
import { eventDataLines, splitSseEvents } from "../util/sse.ts";
|
|
58
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
resolveInvocationTarget,
|
|
60
|
+
resolveNestedInvocationTarget,
|
|
61
|
+
type ResolvedTarget,
|
|
62
|
+
} from "./resolve.ts";
|
|
59
63
|
import type { RemoteMcpClientFactory } from "./mcp.ts";
|
|
60
64
|
import type { ProviderRuntime, RouterRuntime } from "./runtime.ts";
|
|
61
65
|
import { observeTimedExecution } from "./execution-events.ts";
|
|
@@ -113,7 +117,11 @@ export function createRouterExecutionRuntime(
|
|
|
113
117
|
): ExecutionRuntime {
|
|
114
118
|
const runtimeOptions: ExecutionRuntimeOptions = {
|
|
115
119
|
signal: ctx.request.signal,
|
|
116
|
-
resolveTarget(request: Program) {
|
|
120
|
+
resolveTarget(request: Program, invokeOptions) {
|
|
121
|
+
if (invokeOptions?.parentExecutionId) {
|
|
122
|
+
const model = getModel(request);
|
|
123
|
+
if (model) return resolveNestedInvocationTarget(runtime, model).target;
|
|
124
|
+
}
|
|
117
125
|
return resolveRequestTarget(runtime, request).target;
|
|
118
126
|
},
|
|
119
127
|
providerInvoker: createFetchProviderInvoker(
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
listConfiguredModels,
|
|
5
5
|
resolveInvocationTarget,
|
|
6
6
|
resolveInvocationTargets,
|
|
7
|
+
resolveNestedInvocationTarget,
|
|
7
8
|
} from "./resolve.ts";
|
|
8
9
|
|
|
9
10
|
describe("router resolution", () => {
|
|
@@ -72,6 +73,33 @@ describe("router resolution", () => {
|
|
|
72
73
|
});
|
|
73
74
|
});
|
|
74
75
|
|
|
76
|
+
it("allows nested executors to invoke unexported providers", () => {
|
|
77
|
+
const runtime = createRouterRuntime({
|
|
78
|
+
config: {
|
|
79
|
+
providers: {
|
|
80
|
+
emma: {
|
|
81
|
+
apiBaseUrl: "https://api.example.test/v1",
|
|
82
|
+
style: "chat-completions",
|
|
83
|
+
exports: [],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
modelNamespaces: {},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(() =>
|
|
91
|
+
resolveInvocationTarget(runtime, "emma/openai/gpt-oss-120b"),
|
|
92
|
+
).toThrow("does not exist");
|
|
93
|
+
expect(
|
|
94
|
+
resolveNestedInvocationTarget(runtime, "emma/openai/gpt-oss-120b").target,
|
|
95
|
+
).toEqual({
|
|
96
|
+
kind: "provider",
|
|
97
|
+
provider: "emma",
|
|
98
|
+
model: "openai/gpt-oss-120b",
|
|
99
|
+
transforms: [],
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
75
103
|
it("requires configured suffixes", () => {
|
|
76
104
|
const runtime = createRouterRuntime({
|
|
77
105
|
config: {
|
package/src/router/resolve.ts
CHANGED
|
@@ -27,6 +27,41 @@ export function resolveInvocationTarget(
|
|
|
27
27
|
return candidates[0]!;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export function resolveNestedInvocationTarget(
|
|
31
|
+
runtime: RouterRuntime,
|
|
32
|
+
requestedModel: string,
|
|
33
|
+
): ResolvedTarget {
|
|
34
|
+
const resolved = resolveInvocationTargets(runtime, requestedModel);
|
|
35
|
+
if (resolved.length > 0) return resolved[0]!;
|
|
36
|
+
|
|
37
|
+
const parsed = parseModelSyntax(requestedModel, runtime.modelSuffixes);
|
|
38
|
+
const qualified = splitQualified(parsed.baseModel);
|
|
39
|
+
if (!qualified) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`The model \`${requestedModel}\` does not exist or you do not have access to it.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const provider = runtime.providers.get(qualified.namespace);
|
|
45
|
+
if (!provider || runtime.modelNamespaces.has(qualified.namespace)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`The model \`${requestedModel}\` does not exist or you do not have access to it.`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
requestedModel,
|
|
52
|
+
namespace: qualified.namespace,
|
|
53
|
+
source: "provider",
|
|
54
|
+
suffixes: parsed.suffixes,
|
|
55
|
+
mcps: [],
|
|
56
|
+
target: {
|
|
57
|
+
kind: "provider",
|
|
58
|
+
provider: qualified.namespace,
|
|
59
|
+
model: qualified.model,
|
|
60
|
+
transforms: parsed.suffixes.map((suffix) => suffix.raw),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
30
65
|
export function resolveInvocationTargets(
|
|
31
66
|
runtime: RouterRuntime,
|
|
32
67
|
requestedModel: string,
|