@haoyiyin/9router 0.2.0 → 0.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haoyiyin/9router",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "description": "Pi extension — 9Router provider (OpenCode Free oc/-free models + router models) + fetch/search/transcribe tools",
package/src/data.ts CHANGED
@@ -37,13 +37,17 @@ export interface RouterModel {
37
37
  name?: string;
38
38
  context_window?: number;
39
39
  contextWindow?: number;
40
+ /** 9router also advertises the OpenAI-style top-level aliases */
41
+ context_length?: number;
40
42
  max_tokens?: number;
41
43
  maxTokens?: number;
44
+ max_completion_tokens?: number;
42
45
  owned_by?: string;
43
46
  /** 9router v1 API nests these inside capabilities */
44
47
  capabilities?: {
45
48
  contextWindow?: number;
46
49
  maxOutput?: number;
50
+ max_output?: number;
47
51
  reasoning?: boolean;
48
52
  thinkingFormat?: string;
49
53
  thinkingCanDisable?: boolean;
package/src/provider.ts CHANGED
@@ -16,13 +16,10 @@ import { fetchModelsDevIndex, lookupInIndex, routerBaseUrl, routerModels } from
16
16
  import { getZenFreeModels } from "./zen.ts";
17
17
  import { loadConfig, saveConfig, type RouterConfig } from "./config.ts";
18
18
 
19
- // ponytail: 128k was the old generic fallback; combo members bottom out at 200k,
20
- // so the default now matches the smallest combo upstream. Raise again if the
21
- // router ever exposes combo capabilities.contextWindow.
22
- const DEFAULT_CONTEXT_WINDOW = 200_000;
23
- // ponytail: 16k was the old generic fallback; combo members bottom out at 128k,
24
- // so the default now matches the smallest combo upstream max output.
25
- const DEFAULT_MAX_TOKENS = 128_000;
19
+ // ponytail: pi's ProviderModelConfig requires both numbers, so a model the
20
+ // router leaves blank still needs a value. Used only when nothing is advertised.
21
+ const FALLBACK_CONTEXT_WINDOW = 128_000;
22
+ const FALLBACK_MAX_TOKENS = 16_384;
26
23
 
27
24
  const ZERO_COST = {
28
25
  input: 0,
@@ -50,23 +47,47 @@ export function getModelName(model: RouterModel, devModel?: ModelsDevModel): str
50
47
  return model.name || devModel?.name || model.id || "unknown";
51
48
  }
52
49
 
53
- export function getContextWindow(model: RouterModel, devModel?: ModelsDevModel): number {
54
- return (
55
- model.context_window ||
56
- model.contextWindow ||
57
- model.capabilities?.contextWindow ||
58
- devModel?.limit?.context ||
59
- DEFAULT_CONTEXT_WINDOW
50
+ /** First positive finite number. 0/null/NaN are "not advertised", not a real limit. */
51
+ function firstPositive(...values: unknown[]): number | undefined {
52
+ for (const v of values) {
53
+ if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
54
+ }
55
+ return undefined;
56
+ }
57
+
58
+ /**
59
+ * Context window straight from the router. `fallback` applies only when the
60
+ * router (and models.dev) advertise nothing — never ahead of an API value.
61
+ */
62
+ export function getContextWindow(
63
+ model: RouterModel,
64
+ devModel?: ModelsDevModel,
65
+ fallback?: number,
66
+ ): number | undefined {
67
+ return firstPositive(
68
+ model.context_window,
69
+ model.contextWindow,
70
+ model.context_length,
71
+ model.capabilities?.contextWindow,
72
+ devModel?.limit?.context,
73
+ fallback,
60
74
  );
61
75
  }
62
76
 
63
- export function getMaxTokens(model: RouterModel, devModel?: ModelsDevModel): number {
64
- return (
65
- model.max_tokens ||
66
- model.maxTokens ||
67
- model.capabilities?.maxOutput ||
68
- devModel?.limit?.output ||
69
- DEFAULT_MAX_TOKENS
77
+ /** Max output straight from the router. `fallback` only when nothing is advertised. */
78
+ export function getMaxTokens(
79
+ model: RouterModel,
80
+ devModel?: ModelsDevModel,
81
+ fallback?: number,
82
+ ): number | undefined {
83
+ return firstPositive(
84
+ model.max_tokens,
85
+ model.maxTokens,
86
+ model.max_completion_tokens,
87
+ model.capabilities?.maxOutput,
88
+ model.capabilities?.max_output,
89
+ devModel?.limit?.output,
90
+ fallback,
70
91
  );
71
92
  }
72
93
 
@@ -226,8 +247,8 @@ export async function registerProvider(pi: ExtensionAPI): Promise<void> {
226
247
  reasoning: comboOptIn ? comboOptIn.reasoning : getReasoning(model, devModel),
227
248
  input: getInputTypes(model, devModel),
228
249
  cost: ZERO_COST,
229
- contextWindow: getContextWindow(model, devModel),
230
- maxTokens: getMaxTokens(model, devModel),
250
+ contextWindow: getContextWindow(model, devModel, FALLBACK_CONTEXT_WINDOW)!,
251
+ maxTokens: getMaxTokens(model, devModel, FALLBACK_MAX_TOKENS)!,
231
252
  thinkingLevelMap: comboOptIn ? comboOptIn.thinkingLevelMap : getThinkingLevelMap(model),
232
253
  compat: {
233
254
  ...COMPAT,