@xynogen/pix-9router 0.3.22 → 0.4.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.
- package/package.json +1 -1
- package/src/provider.ts +70 -27
package/package.json
CHANGED
package/src/provider.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* ROUTER_API_KEY — bearer token (required for live model list)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type { RefreshModelsContext } from "@earendil-works/pi-ai";
|
|
12
13
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
14
|
import type { ModelsDevModel, RouterModel } from "./data.js";
|
|
14
15
|
import { fetchModelsDevIndex, lookupInIndex, routerBaseUrl, routerModels } from "./data.js";
|
|
@@ -26,6 +27,33 @@ const ZERO_COST = {
|
|
|
26
27
|
// Fallback pattern-based detection if models.dev lookup fails
|
|
27
28
|
const IMAGE_CAPABLE_PATTERNS = [/claude/i, /gpt-5/i, /gpt-4/i, /kimi-k2/i, /hy3/i];
|
|
28
29
|
|
|
30
|
+
interface RouterModelsResponse {
|
|
31
|
+
data?: RouterModel[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const COMPAT = {
|
|
35
|
+
supportsDeveloperRole: false,
|
|
36
|
+
supportsUsageInStreaming: false,
|
|
37
|
+
maxTokensField: "max_tokens",
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
function toModelConfig(devIndex: Map<string, ModelsDevModel>) {
|
|
41
|
+
return (model: RouterModel) => {
|
|
42
|
+
const id = model.id ?? "";
|
|
43
|
+
const devModel = lookupInIndex(id, devIndex);
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
name: getModelName(model, devModel),
|
|
47
|
+
reasoning: getReasoning(model, devModel),
|
|
48
|
+
input: getInputTypes(model, devModel),
|
|
49
|
+
cost: ZERO_COST,
|
|
50
|
+
contextWindow: getContextWindow(model, devModel),
|
|
51
|
+
maxTokens: getMaxTokens(model, devModel),
|
|
52
|
+
compat: COMPAT,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
29
57
|
export function getInputTypes(model: RouterModel, devModel?: ModelsDevModel): ("text" | "image")[] {
|
|
30
58
|
if (devModel?.modalities?.input) {
|
|
31
59
|
const inputs = devModel.modalities.input.filter(
|
|
@@ -82,39 +110,54 @@ export default async function registerProvider(pi: ExtensionAPI): Promise<void>
|
|
|
82
110
|
return;
|
|
83
111
|
}
|
|
84
112
|
|
|
85
|
-
|
|
113
|
+
// Upstream moved OpenAI `compat` settings from the provider level to the
|
|
114
|
+
// per-model level (ProviderModelConfig.compat). Applied in toModelConfig.
|
|
115
|
+
|
|
116
|
+
// Register from disk cache only — startup must not block on network.
|
|
117
|
+
const cached = routerModels.getCached();
|
|
118
|
+
pi.registerProvider("9router", providerConfig(apiKey, cached, new Map()));
|
|
119
|
+
|
|
120
|
+
// Background refresh; re-registering after startup applies immediately.
|
|
121
|
+
void Promise.all([
|
|
86
122
|
routerModels.get(),
|
|
87
123
|
fetchModelsDevIndex().catch(() => new Map<string, ModelsDevModel>()),
|
|
88
|
-
])
|
|
124
|
+
])
|
|
125
|
+
.then(([models, devIndex]) =>
|
|
126
|
+
pi.registerProvider("9router", providerConfig(apiKey, models, devIndex)),
|
|
127
|
+
)
|
|
128
|
+
.catch(() => {});
|
|
129
|
+
}
|
|
89
130
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
maxTokensField: "max_tokens",
|
|
97
|
-
} as const;
|
|
98
|
-
|
|
99
|
-
pi.registerProvider("9router", {
|
|
131
|
+
function providerConfig(
|
|
132
|
+
apiKey: string,
|
|
133
|
+
models: RouterModel[],
|
|
134
|
+
devIndex: Map<string, ModelsDevModel>,
|
|
135
|
+
) {
|
|
136
|
+
return {
|
|
100
137
|
name: "9Router",
|
|
101
138
|
baseUrl: routerBaseUrl(),
|
|
102
139
|
apiKey: "$ROUTER_API_KEY",
|
|
103
140
|
api: "openai-completions",
|
|
104
141
|
headers: { "User-Agent": "pi-coding-agent" },
|
|
105
|
-
models: models.map((
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
models: models.map(toModelConfig(devIndex)),
|
|
143
|
+
// Live fetch on /model refresh — bypasses the disk cache.
|
|
144
|
+
// Pi calls refreshModels once per provider at every startup with
|
|
145
|
+
// allowNetwork:false (awaited, no timeout) — never touch the network
|
|
146
|
+
// there; serve the registered snapshot instead.
|
|
147
|
+
async refreshModels({ signal, allowNetwork }: RefreshModelsContext) {
|
|
148
|
+
if (!allowNetwork) return models.map(toModelConfig(devIndex));
|
|
149
|
+
const res = await fetch(`${routerBaseUrl()}/models`, {
|
|
150
|
+
signal: AbortSignal.any([AbortSignal.timeout(8_000), ...(signal ? [signal] : [])]),
|
|
151
|
+
headers: {
|
|
152
|
+
Authorization: `Bearer ${apiKey}`,
|
|
153
|
+
"User-Agent": "pi-coding-agent",
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
if (!res.ok) throw new Error(`9router /models: ${res.status}`);
|
|
157
|
+
const raw = (await res.json()) as RouterModelsResponse;
|
|
158
|
+
const list = (raw.data ?? []).filter((m) => Boolean(m.id));
|
|
159
|
+
const fresh = await fetchModelsDevIndex().catch(() => new Map<string, ModelsDevModel>());
|
|
160
|
+
return list.map(toModelConfig(fresh));
|
|
161
|
+
},
|
|
162
|
+
};
|
|
120
163
|
}
|