@xynogen/pix-9router 0.2.4 → 0.2.6
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 +2 -2
- package/package.json +1 -1
- package/src/data.test.ts +40 -84
- package/src/data.ts +5 -1
- package/src/fetch.ts +1 -1
- package/src/index.ts +1 -1
- package/src/provider.ts +1 -1
- package/src/search.ts +1 -1
- package/src/transcribe.ts +1 -1
package/README.md
CHANGED
|
@@ -33,8 +33,8 @@ export ROUTER_API_KEY="your-key-here"
|
|
|
33
33
|
|
|
34
34
|
## How it works
|
|
35
35
|
|
|
36
|
-
- **Provider**: on load, fetches `/models` from the router and registers them with Pi. [
|
|
37
|
-
- **fetch / search**: POST to `/web/fetch` and `/search` on the router (which proxies to exa). If the router is unreachable, falls back to raw `curl
|
|
36
|
+
- **Provider**: on load, fetches `/models` from the router and registers them with Pi. The [modelgrep](https://modelgrep.com) catalog (via [`@xynogen/pix-data`](https://github.com/xynogen/pix-mono/tree/main/packages/pix-data)'s shared cache) is used internally to fill missing context window / modality fields where the router response omits them. Model list is cached at `~/.cache/pi/9router.json` (TTL 30 min).
|
|
37
|
+
- **fetch / search**: POST to `/web/fetch` and `/search` on the router (which proxies to exa). If the router is unreachable, falls back to `curl` (for `fetch`, raw URL fetch; for `search`, the same `/search` endpoint via curl). Tool output is rendered dimmed so fetched web content reads like faded context rather than primary output.
|
|
38
38
|
- **transcribe**: POST to `/audio/transcriptions` (Deepgram Nova 3 by default). Accepts any audio file path; falls back to curl.
|
|
39
39
|
|
|
40
40
|
## Full distro
|
package/package.json
CHANGED
package/src/data.test.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
import { afterEach,
|
|
1
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
buildModelsDevIndex,
|
|
4
4
|
lookupInIndex,
|
|
5
|
-
type
|
|
6
|
-
type ModelsDevModel,
|
|
5
|
+
type ModelGrepModel,
|
|
7
6
|
routerBaseUrl,
|
|
8
7
|
} from "./data.ts";
|
|
9
8
|
|
|
9
|
+
// modelgrep-shaped fixture builder (buildModelsDevIndex now takes ModelGrepModel[]).
|
|
10
|
+
function mg(
|
|
11
|
+
id: string,
|
|
12
|
+
opts: { name?: string; reasoning?: boolean; input?: string[] } = {},
|
|
13
|
+
): ModelGrepModel {
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
name: opts.name ?? id,
|
|
17
|
+
capabilities: { reasoning: opts.reasoning },
|
|
18
|
+
modality: { input: opts.input },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
// ── routerBaseUrl ────────────────────────────────────────────────────────────
|
|
11
23
|
|
|
12
24
|
describe("routerBaseUrl", () => {
|
|
@@ -36,75 +48,35 @@ describe("routerBaseUrl", () => {
|
|
|
36
48
|
// ── buildModelsDevIndex ──────────────────────────────────────────────────────
|
|
37
49
|
|
|
38
50
|
describe("buildModelsDevIndex", () => {
|
|
39
|
-
const
|
|
40
|
-
anthropic:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
cost: { input: 3, output: 15 },
|
|
48
|
-
},
|
|
49
|
-
"claude-opus-4": {
|
|
50
|
-
id: "claude-opus-4",
|
|
51
|
-
name: "Claude Opus 4",
|
|
52
|
-
reasoning: true,
|
|
53
|
-
limit: { context: 200_000, output: 32_000 },
|
|
54
|
-
cost: { input: 15, output: 75 },
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
openai: {
|
|
59
|
-
models: {
|
|
60
|
-
"gpt-4o": {
|
|
61
|
-
id: "gpt-4o",
|
|
62
|
-
name: "GPT-4o",
|
|
63
|
-
modalities: { input: ["text", "image"], output: ["text"] },
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
it("indexes all models by exact id", () => {
|
|
70
|
-
const idx = buildModelsDevIndex(api);
|
|
51
|
+
const catalog: ModelGrepModel[] = [
|
|
52
|
+
mg("anthropic/claude-sonnet-4-5", { name: "Claude Sonnet 4.5" }),
|
|
53
|
+
mg("anthropic/claude-opus-4", { name: "Claude Opus 4", reasoning: true }),
|
|
54
|
+
mg("openai/gpt-4o", { name: "GPT-4o", input: ["text", "image"] }),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
it("indexes all models by slug", () => {
|
|
58
|
+
const idx = buildModelsDevIndex(catalog);
|
|
71
59
|
expect(idx.has("claude-sonnet-4-5")).toBe(true);
|
|
72
60
|
expect(idx.has("claude-opus-4")).toBe(true);
|
|
73
61
|
expect(idx.has("gpt-4o")).toBe(true);
|
|
74
62
|
});
|
|
75
63
|
|
|
76
|
-
it("indexes normalized
|
|
77
|
-
const
|
|
78
|
-
anthropic:
|
|
79
|
-
|
|
80
|
-
"claude-sonnet-4-5-20250514": {
|
|
81
|
-
id: "claude-sonnet-4-5-20250514",
|
|
82
|
-
name: "Claude Sonnet 4.5",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
const idx = buildModelsDevIndex(apiWithDate);
|
|
88
|
-
// normalized: strip -20250514
|
|
64
|
+
it("indexes normalized slug (strip date suffix)", () => {
|
|
65
|
+
const idx = buildModelsDevIndex([
|
|
66
|
+
mg("anthropic/claude-sonnet-4-5-20250514", { name: "Claude Sonnet 4.5" }),
|
|
67
|
+
]);
|
|
89
68
|
expect(idx.has("claude-sonnet-4-5")).toBe(true);
|
|
90
69
|
});
|
|
91
70
|
|
|
92
|
-
it("handles empty
|
|
93
|
-
|
|
94
|
-
expect(idx.size).toBe(0);
|
|
71
|
+
it("handles empty catalog gracefully", () => {
|
|
72
|
+
expect(buildModelsDevIndex([]).size).toBe(0);
|
|
95
73
|
});
|
|
96
74
|
|
|
97
|
-
it("
|
|
98
|
-
const idx = buildModelsDevIndex(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
it("preserves first-seen on id collision", () => {
|
|
103
|
-
const collide: ModelsDevApi = {
|
|
104
|
-
a: { models: { "gpt-4o": { id: "gpt-4o", name: "First" } } },
|
|
105
|
-
b: { models: { "gpt-4o": { id: "gpt-4o", name: "Second" } } },
|
|
106
|
-
};
|
|
107
|
-
const idx = buildModelsDevIndex(collide);
|
|
75
|
+
it("preserves first-seen on slug collision", () => {
|
|
76
|
+
const idx = buildModelsDevIndex([
|
|
77
|
+
mg("a/gpt-4o", { name: "First" }),
|
|
78
|
+
mg("b/gpt-4o", { name: "Second" }),
|
|
79
|
+
]);
|
|
108
80
|
expect(idx.get("gpt-4o")?.name).toBe("First");
|
|
109
81
|
});
|
|
110
82
|
});
|
|
@@ -112,28 +84,12 @@ describe("buildModelsDevIndex", () => {
|
|
|
112
84
|
// ── lookupInIndex ────────────────────────────────────────────────────────────
|
|
113
85
|
|
|
114
86
|
describe("lookupInIndex", () => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"claude-sonnet-4-5": {
|
|
122
|
-
id: "claude-sonnet-4-5",
|
|
123
|
-
name: "Claude Sonnet 4.5",
|
|
124
|
-
},
|
|
125
|
-
"claude-opus-4": { id: "claude-opus-4", name: "Claude Opus 4" },
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
openai: {
|
|
129
|
-
models: {
|
|
130
|
-
"gpt-4o": { id: "gpt-4o", name: "GPT-4o" },
|
|
131
|
-
"o3-mini": { id: "o3-mini", name: "o3 mini" },
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
index = buildModelsDevIndex(api);
|
|
136
|
-
});
|
|
87
|
+
const index = buildModelsDevIndex([
|
|
88
|
+
mg("anthropic/claude-sonnet-4-5", { name: "Claude Sonnet 4.5" }),
|
|
89
|
+
mg("anthropic/claude-opus-4", { name: "Claude Opus 4" }),
|
|
90
|
+
mg("openai/gpt-4o", { name: "GPT-4o" }),
|
|
91
|
+
mg("openai/o3-mini", { name: "o3 mini" }),
|
|
92
|
+
]);
|
|
137
93
|
|
|
138
94
|
it("finds exact match", () => {
|
|
139
95
|
expect(lookupInIndex("claude-sonnet-4-5", index)?.name).toBe(
|
package/src/data.ts
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { CACHE_DIR, DataSource } from "../../pix-data/src/index.ts";
|
|
20
20
|
|
|
21
|
-
export type {
|
|
21
|
+
export type {
|
|
22
|
+
ModelGrepModel,
|
|
23
|
+
ModelsDevApi,
|
|
24
|
+
ModelsDevModel,
|
|
25
|
+
} from "../../pix-data/src/index.ts";
|
|
22
26
|
// Re-export the shared models.dev API so existing consumers (provider.ts)
|
|
23
27
|
// can keep importing these names from "./data".
|
|
24
28
|
export {
|
package/src/fetch.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* fetch.ts — fetch tool via 9Router API, with curl fallback.
|
|
3
3
|
*
|
|
4
4
|
* Environment:
|
|
5
|
-
* ROUTER_API_BASE — router API base URL (default: https://
|
|
5
|
+
* ROUTER_API_BASE — router API base URL (default: https://9router.example.com/v1)
|
|
6
6
|
* ROUTER_API_KEY — bearer token for the router
|
|
7
7
|
*/
|
|
8
8
|
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - transcribe tool (speech-to-text via audio transcription API)
|
|
9
9
|
*
|
|
10
10
|
* Environment:
|
|
11
|
-
* ROUTER_API_BASE — override base URL (default: https://
|
|
11
|
+
* ROUTER_API_BASE — override base URL (default: https://9router.example.com/v1)
|
|
12
12
|
* ROUTER_API_KEY — bearer token (required for provider + tools)
|
|
13
13
|
*/
|
|
14
14
|
|
package/src/provider.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* router API. Falls back to an empty model list if ROUTER_API_KEY is unset.
|
|
6
6
|
*
|
|
7
7
|
* Environment:
|
|
8
|
-
* ROUTER_API_BASE — override API base URL (default: https://
|
|
8
|
+
* ROUTER_API_BASE — override API base URL (default: https://9router.example.com/v1)
|
|
9
9
|
* ROUTER_API_KEY — bearer token (required for live model list)
|
|
10
10
|
*/
|
|
11
11
|
|
package/src/search.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* search.ts — search tool via 9Router API, with curl fallback.
|
|
3
3
|
*
|
|
4
4
|
* Environment:
|
|
5
|
-
* ROUTER_API_BASE — router API base URL (default: https://
|
|
5
|
+
* ROUTER_API_BASE — router API base URL (default: https://9router.example.com/v1)
|
|
6
6
|
* ROUTER_API_KEY — bearer token for the router
|
|
7
7
|
*/
|
|
8
8
|
|
package/src/transcribe.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Default model: dg/nova-3 (Deepgram Nova 3)
|
|
8
8
|
*
|
|
9
9
|
* Environment:
|
|
10
|
-
* ROUTER_API_BASE — router API base URL (default: https://
|
|
10
|
+
* ROUTER_API_BASE — router API base URL (default: https://9router.example.com/v1)
|
|
11
11
|
* ROUTER_API_KEY — bearer token for the router
|
|
12
12
|
*/
|
|
13
13
|
|