@code-yeongyu/senpi-ai 2026.10.1 → 2026.10.3

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.
Files changed (45) hide show
  1. package/README.md +4 -3
  2. package/dist/api/anthropic-messages.js +153 -29
  3. package/dist/api/anthropic-tool-references.d.ts +14 -0
  4. package/dist/api/anthropic-tool-references.js +107 -0
  5. package/dist/api/constrained-sampling.d.ts +8 -2
  6. package/dist/api/constrained-sampling.js +19 -8
  7. package/dist/api/openai-responses-shared.js +7 -5
  8. package/dist/api-registry.d.ts +5 -1
  9. package/dist/api-registry.js +16 -6
  10. package/dist/auth/oauth/anthropic.js +52 -1
  11. package/dist/compat.js +4 -1
  12. package/dist/env-api-keys.d.ts +5 -0
  13. package/dist/env-api-keys.js +5 -0
  14. package/dist/providers/anthropic.js +24 -2
  15. package/dist/providers/data/.manifest.json +1 -1
  16. package/dist/providers/data/alibaba-token-plan.json +1 -1
  17. package/dist/providers/data/amazon-bedrock.json +1 -1
  18. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  19. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  20. package/dist/providers/data/fireworks.json +1 -1
  21. package/dist/providers/data/moonshotai-cn.json +1 -1
  22. package/dist/providers/data/moonshotai.json +1 -1
  23. package/dist/providers/data/nvidia.json +1 -1
  24. package/dist/providers/data/opencode.json +1 -1
  25. package/dist/providers/data/opengateway.json +1 -1
  26. package/dist/providers/data/openrouter.json +1 -1
  27. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  28. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  29. package/dist/providers/data/qwen-token-plan.json +1 -1
  30. package/dist/providers/data/radius.json +1 -1
  31. package/dist/providers/data/together.json +1 -1
  32. package/dist/providers/data/venice.json +1 -1
  33. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  34. package/dist/providers/data/zai-coding-cn.json +1 -1
  35. package/dist/providers/data/zai.json +1 -1
  36. package/dist/providers/opengateway-catalog.d.ts +43 -0
  37. package/dist/providers/opengateway-catalog.js +151 -0
  38. package/dist/providers/opengateway-refresh.d.ts +20 -0
  39. package/dist/providers/opengateway-refresh.js +129 -0
  40. package/dist/providers/opengateway.js +13 -2
  41. package/dist/utils/oauth-page.js +1 -1
  42. package/dist/utils/openai-input-cap.d.ts +4 -0
  43. package/dist/utils/openai-input-cap.js +18 -0
  44. package/dist/utils/overflow.js +2 -1
  45. package/package.json +6 -2
@@ -1 +1 @@
1
- {"openai-completions":{"chat:glm-4.6v":{"id":"glm-4.6v","name":"GLM-4.6V","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"input":["text","image"],"cost":{"input":0.3,"output":0.9,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":128000,"maxTokens":32768,"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:glm-5.3":{"id":"glm-5.3","name":"GLM-5.3","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":1.4,"output":4.4,"cacheRead":0.26,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.3-flash":{"id":"glm-5.3-flash","name":"GLM-5.3-Flash","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text","image"],"cost":{"input":0.15,"output":0.5,"cacheRead":0.03,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:glm-5.3-highspeed":{"id":"glm-5.3-highspeed","name":"GLM-5.3 Highspeed","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"}}}
1
+ {"openai-completions":{"chat:glm-4.6v":{"id":"glm-4.6v","name":"GLM-4.6V","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"input":["text","image"],"cost":{"input":0.3,"output":0.9,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":128000,"maxTokens":32768,"thinkingLevelMap":{"minimal":null,"low":null,"medium":null,"xhigh":null,"max":null},"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:glm-5.3":{"id":"glm-5.3","name":"GLM-5.3","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":1.4,"output":4.4,"cacheRead":0.26,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.3-flash":{"id":"glm-5.3-flash","name":"GLM-5.3-Flash","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text","image"],"cost":{"input":0.15,"output":0.5,"cacheRead":0.03,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:glm-5.3-highspeed":{"id":"glm-5.3-highspeed","name":"GLM-5.3 Highspeed","api":"openai-completions","provider":"zai-coding-cn","baseUrl":"https://open.bigmodel.cn/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"}}}
@@ -1 +1 @@
1
- {"openai-completions":{"chat:glm-4.7":{"id":"glm-4.7","name":"GLM-4.7","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"input":["text"],"cost":{"input":0.6,"output":2.2,"cacheRead":0.11,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":204800,"maxTokens":131072,"type":"chat"},"chat:glm-5-turbo":{"id":"glm-5-turbo","name":"GLM-5-Turbo","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"input":["text"],"cost":{"input":1.2,"output":4,"cacheRead":0.24,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":200000,"maxTokens":131072,"type":"chat"},"chat:glm-5.2":{"id":"glm-5.2","name":"GLM-5.2","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":1.4,"output":4.4,"cacheRead":0.26,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.2-highspeed":{"id":"glm-5.2-highspeed","name":"GLM-5.2 Highspeed","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.3":{"id":"glm-5.3","name":"GLM-5.3","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":1.4,"output":4.4,"cacheRead":0.26,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.3-flash":{"id":"glm-5.3-flash","name":"GLM-5.3-Flash","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text","image"],"cost":{"input":0.15,"output":0.5,"cacheRead":0.03,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:glm-5.3-highspeed":{"id":"glm-5.3-highspeed","name":"GLM-5.3 Highspeed","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"}}}
1
+ {"openai-completions":{"chat:glm-4.7":{"id":"glm-4.7","name":"GLM-4.7","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"input":["text"],"cost":{"input":0.6,"output":2.2,"cacheRead":0.11,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":204800,"maxTokens":131072,"thinkingLevelMap":{"minimal":null,"low":null,"medium":null,"xhigh":null,"max":null},"type":"chat"},"chat:glm-5-turbo":{"id":"glm-5-turbo","name":"GLM-5-Turbo","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"input":["text"],"cost":{"input":1.2,"output":4,"cacheRead":0.24,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":200000,"maxTokens":131072,"thinkingLevelMap":{"minimal":null,"low":null,"medium":null,"xhigh":null,"max":null},"type":"chat"},"chat:glm-5.2":{"id":"glm-5.2","name":"GLM-5.2","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":1.4,"output":4.4,"cacheRead":0.26,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.2-highspeed":{"id":"glm-5.2-highspeed","name":"GLM-5.2 Highspeed","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.3":{"id":"glm-5.3","name":"GLM-5.3","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":1.4,"output":4.4,"cacheRead":0.26,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"},"chat:glm-5.3-flash":{"id":"glm-5.3-flash","name":"GLM-5.3-Flash","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text","image"],"cost":{"input":0.15,"output":0.5,"cacheRead":0.03,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"inputLimits":{"images":{"resize":{"maxWidth":2000,"maxHeight":2000,"maxBytes":4718592,"jpegQuality":80}}},"type":"chat"},"chat:glm-5.3-highspeed":{"id":"glm-5.3-highspeed","name":"GLM-5.3 Highspeed","api":"openai-completions","provider":"zai","baseUrl":"https://api.z.ai/api/coding/paas/v4","reasoning":true,"thinkingLevelMap":{"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","thinkingFormat":"zai","supportsStrictMode":true,"zaiToolStream":true},"contextWindow":1000000,"maxTokens":131072,"type":"chat"}}}
@@ -0,0 +1,43 @@
1
+ import type { ModelCost, ModelCostTier } from "../types.ts";
2
+ export declare const OPENGATEWAY_BASE_URL = "https://apis.opengateway.ai/v1";
3
+ export declare const OPENGATEWAY_MODELS_URL = "https://apis.opengateway.ai/v1/models";
4
+ export declare const OPENGATEWAY_PRICES_URL = "https://opengateway.ai/api/model-prices";
5
+ export interface OpenGatewayListedModel {
6
+ id: string;
7
+ status?: string;
8
+ inputModalities: readonly string[];
9
+ endpoints: readonly string[];
10
+ /** Provider route ids in the gateway's preference order. */
11
+ routes: readonly string[];
12
+ contextWindow?: number;
13
+ maxOutputTokens?: number;
14
+ }
15
+ /** Published per-million-token prices; a field is absent when the gateway does not publish it. */
16
+ export interface OpenGatewayPrice {
17
+ input?: number;
18
+ output?: number;
19
+ cacheRead?: number;
20
+ cacheWrite?: number;
21
+ tiers: ModelCostTier[];
22
+ }
23
+ interface PriceRoute {
24
+ route: string;
25
+ modelId: string;
26
+ price: OpenGatewayPrice;
27
+ }
28
+ export type OpenGatewayPriceTable = readonly PriceRoute[];
29
+ export declare function parseOpenGatewayListing(value: unknown): OpenGatewayListedModel[];
30
+ /** Chat-completions models that can still be called. Retired models are listed but rejected. */
31
+ export declare function isServableChatModel(model: OpenGatewayListedModel): boolean;
32
+ /** `z-ai/glm-5.3-ultrafast` -> its base `z-ai/glm-5.3` plus the tier label, or undefined. */
33
+ export declare function servingTierBase(id: string): {
34
+ baseId: string;
35
+ label: string;
36
+ } | undefined;
37
+ export declare function parseOpenGatewayPriceTable(value: unknown): OpenGatewayPriceTable;
38
+ /** The price billed for a listed model: its preferred route's price, else any route's. */
39
+ export declare function openGatewayPrice(model: OpenGatewayListedModel, table: OpenGatewayPriceTable): OpenGatewayPrice | undefined;
40
+ /** Overlay published gateway prices on a fallback cost; unpublished fields keep the fallback. */
41
+ export declare function withGatewayPrice(fallback: ModelCost, price: OpenGatewayPrice | undefined): ModelCost;
42
+ export {};
43
+ //# sourceMappingURL=opengateway-catalog.d.ts.map
@@ -0,0 +1,151 @@
1
+ // OpenGateway's public catalog data, shared by the build-time generator
2
+ // (scripts/generate-models-opengateway.ts) and the runtime refresh
3
+ // (opengateway-refresh.ts). Both endpoints are public and need no API key.
4
+ //
5
+ // - GET https://apis.opengateway.ai/v1/models lists every model the gateway
6
+ // serves: lifecycle status, modalities, endpoints, the provider routes in
7
+ // preference order, and (for most models) context window and max output.
8
+ // - GET https://opengateway.ai/api/model-prices is the price table the
9
+ // gateway bills from, keyed per provider route, with the effective
10
+ // (discounted) per-token price and long-context tiers.
11
+ export const OPENGATEWAY_BASE_URL = "https://apis.opengateway.ai/v1";
12
+ export const OPENGATEWAY_MODELS_URL = `${OPENGATEWAY_BASE_URL}/models`;
13
+ export const OPENGATEWAY_PRICES_URL = "https://opengateway.ai/api/model-prices";
14
+ const SERVING_TIER_SUFFIXES = [
15
+ { suffix: "-ultrafast", label: "Ultrafast" },
16
+ ];
17
+ function isRecord(value) {
18
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19
+ }
20
+ function positiveInteger(value) {
21
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
22
+ }
23
+ function stringList(value) {
24
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
25
+ }
26
+ export function parseOpenGatewayListing(value) {
27
+ if (!isRecord(value) || !Array.isArray(value.data)) {
28
+ throw new Error("OpenGateway model listing is not a { data: [...] } list");
29
+ }
30
+ const models = [];
31
+ for (const entry of value.data) {
32
+ if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.includes("/"))
33
+ continue;
34
+ const modalities = isRecord(entry.modalities) ? entry.modalities : {};
35
+ const routes = Array.isArray(entry.providers)
36
+ ? entry.providers.flatMap((route) => (isRecord(route) && typeof route.id === "string" ? [route.id] : []))
37
+ : [];
38
+ models.push({
39
+ id: entry.id,
40
+ status: typeof entry.status === "string" ? entry.status : undefined,
41
+ inputModalities: stringList(modalities.input),
42
+ endpoints: stringList(entry.endpoints),
43
+ routes,
44
+ contextWindow: positiveInteger(entry.context_window),
45
+ maxOutputTokens: positiveInteger(entry.max_output_tokens),
46
+ });
47
+ }
48
+ if (models.length === 0)
49
+ throw new Error("OpenGateway model listing has no models");
50
+ return models;
51
+ }
52
+ /** Chat-completions models that can still be called. Retired models are listed but rejected. */
53
+ export function isServableChatModel(model) {
54
+ return model.endpoints.includes("chat_completions") && model.status !== "retired";
55
+ }
56
+ /** `z-ai/glm-5.3-ultrafast` -> its base `z-ai/glm-5.3` plus the tier label, or undefined. */
57
+ export function servingTierBase(id) {
58
+ for (const { suffix, label } of SERVING_TIER_SUFFIXES) {
59
+ if (id.endsWith(suffix) && id.length > suffix.length)
60
+ return { baseId: id.slice(0, -suffix.length), label };
61
+ }
62
+ return undefined;
63
+ }
64
+ function perMillion(value) {
65
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
66
+ return undefined;
67
+ // Per-token decimals (2e-7) scale to float noise (0.19999999999999998); 12 significant digits remove it.
68
+ return Number((value * 1_000_000).toPrecision(12));
69
+ }
70
+ function parseTiers(axisPrices) {
71
+ if (!isRecord(axisPrices))
72
+ return [];
73
+ const tiers = [];
74
+ for (const [key, value] of Object.entries(axisPrices)) {
75
+ // Keys look like "AxisKey(threshold=272000, tier=null)"; only plain context thresholds are input tiers.
76
+ const match = /^AxisKey\(threshold=(\d+), tier=null\)$/.exec(key);
77
+ if (!match || !isRecord(value) || value.isEmpty === true)
78
+ continue;
79
+ const input = perMillion(value.input);
80
+ const output = perMillion(value.output);
81
+ if (input === undefined || output === undefined)
82
+ continue;
83
+ tiers.push({
84
+ inputTokensAbove: Number(match[1]),
85
+ input,
86
+ output,
87
+ cacheRead: perMillion(value.cacheRead) ?? 0,
88
+ cacheWrite: perMillion(value.cacheCreation) ?? 0,
89
+ });
90
+ }
91
+ return tiers.sort((left, right) => left.inputTokensAbove - right.inputTokensAbove);
92
+ }
93
+ export function parseOpenGatewayPriceTable(value) {
94
+ if (!isRecord(value))
95
+ throw new Error("OpenGateway price table is not an object");
96
+ const routes = [];
97
+ for (const entry of Object.values(value)) {
98
+ if (!isRecord(entry) || typeof entry.provider !== "string")
99
+ continue;
100
+ if (typeof entry.modelOwner !== "string" || typeof entry.modelName !== "string")
101
+ continue;
102
+ const pricing = isRecord(entry.pricing) && isRecord(entry.pricing.current) ? entry.pricing.current : undefined;
103
+ // The effective price already carries the gateway's discount; the list price is the fallback.
104
+ const effective = pricing && isRecord(pricing.effectivePrice) ? pricing.effectivePrice : entry;
105
+ routes.push({
106
+ route: entry.provider,
107
+ modelId: `${entry.modelOwner}/${entry.modelName}`,
108
+ price: {
109
+ input: perMillion(effective.inputCostPerToken),
110
+ output: perMillion(effective.outputCostPerToken),
111
+ cacheRead: perMillion(effective.cacheReadInputTokenCost),
112
+ cacheWrite: perMillion(effective.cacheCreationInputTokenCost),
113
+ tiers: parseTiers(effective.axisPrices),
114
+ },
115
+ });
116
+ }
117
+ if (routes.length === 0)
118
+ throw new Error("OpenGateway price table has no priced routes");
119
+ return routes;
120
+ }
121
+ /** The price billed for a listed model: its preferred route's price, else any route's. */
122
+ export function openGatewayPrice(model, table) {
123
+ const candidates = table.filter((entry) => entry.modelId === model.id);
124
+ for (const route of model.routes) {
125
+ const preferred = candidates.find((entry) => entry.route === route);
126
+ if (preferred)
127
+ return preferred.price;
128
+ }
129
+ return candidates[0]?.price;
130
+ }
131
+ /** Overlay published gateway prices on a fallback cost; unpublished fields keep the fallback. */
132
+ export function withGatewayPrice(fallback, price) {
133
+ if (!price)
134
+ return fallback;
135
+ const cost = {
136
+ input: price.input ?? fallback.input,
137
+ output: price.output ?? fallback.output,
138
+ cacheRead: price.cacheRead ?? fallback.cacheRead,
139
+ cacheWrite: price.cacheWrite ?? fallback.cacheWrite,
140
+ };
141
+ // A published route price owns its tiers: no gateway tiers means flat billing.
142
+ if (price.input !== undefined && price.output !== undefined) {
143
+ if (price.tiers.length > 0)
144
+ cost.tiers = price.tiers;
145
+ }
146
+ else if (fallback.tiers) {
147
+ cost.tiers = fallback.tiers;
148
+ }
149
+ return cost;
150
+ }
151
+ //# sourceMappingURL=opengateway-catalog.js.map
@@ -0,0 +1,20 @@
1
+ import type { RefreshModelsContext } from "../models.ts";
2
+ import type { Model } from "../types.ts";
3
+ import { type OpenGatewayListedModel, type OpenGatewayPriceTable } from "./opengateway-catalog.ts";
4
+ type OpenGatewayModel = Model<"openai-completions">;
5
+ export declare const OPENGATEWAY_REFRESH_INTERVAL_MS: number;
6
+ /** Servable gateway models the shipped catalog lacks; only these need the price table. */
7
+ export declare function unshippedServableModels(shipped: readonly OpenGatewayModel[], listing: readonly OpenGatewayListedModel[]): OpenGatewayListedModel[];
8
+ export declare function overlayOpenGatewayCatalog(shipped: readonly OpenGatewayModel[], listing: readonly OpenGatewayListedModel[], prices: OpenGatewayPriceTable): OpenGatewayModel[];
9
+ /**
10
+ * Catalog state for the provider: `getModels()` is synchronous, `refresh()` restores the
11
+ * persisted list and revalidates it against the gateway at most hourly. A persisted list
12
+ * older than the shipped catalog is ignored, so an upgrade never resurrects metadata the
13
+ * new release corrected.
14
+ */
15
+ export declare function createOpenGatewayCatalog(shipped: readonly OpenGatewayModel[], shippedGeneratedAt: number | undefined): {
16
+ getModels: () => readonly OpenGatewayModel[];
17
+ refresh: (context: RefreshModelsContext) => Promise<void>;
18
+ };
19
+ export {};
20
+ //# sourceMappingURL=opengateway-refresh.d.ts.map
@@ -0,0 +1,129 @@
1
+ // Runtime refresh for the OpenGateway provider. A successful gateway listing is
2
+ // authoritative for availability; the shipped catalog (generated from the gateway
3
+ // plus models.dev) supplies metadata and is the offline fallback:
4
+ //
5
+ // - a servable model the shipped catalog lacks is added, built from its shipped
6
+ // serving-tier base when there is one, priced from the gateway price table;
7
+ // - a shipped model the gateway retired, or no longer lists, is removed;
8
+ // - shipped rows keep their generated metadata (input caps, thinking maps,
9
+ // prices). Correcting those is the scheduled catalog regeneration's job.
10
+ //
11
+ // Any fetch or parse failure keeps the last good list and surfaces as a refresh error.
12
+ import { isModelType } from "../utils/model-operations.js";
13
+ import { applyOpenAiInputCap } from "../utils/openai-input-cap.js";
14
+ import { isServableChatModel, OPENGATEWAY_BASE_URL, OPENGATEWAY_MODELS_URL, OPENGATEWAY_PRICES_URL, openGatewayPrice, parseOpenGatewayListing, parseOpenGatewayPriceTable, servingTierBase, withGatewayPrice, } from "./opengateway-catalog.js";
15
+ export const OPENGATEWAY_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
16
+ const REQUEST_TIMEOUT_MS = 15_000;
17
+ /** Max output for an added model the gateway publishes no limit for and that has no shipped base. */
18
+ const UNPUBLISHED_MAX_OUTPUT_TOKENS = 32768;
19
+ const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
20
+ function addedModel(item, shippedById, prices) {
21
+ const price = openGatewayPrice(item, prices);
22
+ // An unpriced model would bill as free in usage accounting; wait for the regeneration instead.
23
+ if (price?.input === undefined || price.output === undefined)
24
+ return undefined;
25
+ const tier = servingTierBase(item.id);
26
+ const base = tier ? shippedById.get(tier.baseId) : undefined;
27
+ const contextWindow = item.contextWindow ?? base?.contextWindow;
28
+ if (contextWindow === undefined)
29
+ return undefined;
30
+ const template = base
31
+ ? { ...base, name: `${base.name} ${tier?.label}` }
32
+ : {
33
+ id: item.id,
34
+ name: item.id,
35
+ api: "openai-completions",
36
+ provider: "opengateway",
37
+ baseUrl: OPENGATEWAY_BASE_URL,
38
+ compat: { supportsDeveloperRole: false },
39
+ reasoning: false,
40
+ input: ["text"],
41
+ cost: ZERO_COST,
42
+ contextWindow,
43
+ maxTokens: UNPUBLISHED_MAX_OUTPUT_TOKENS,
44
+ };
45
+ const model = {
46
+ ...template,
47
+ id: item.id,
48
+ input: item.inputModalities.includes("image") ? ["text", "image"] : ["text"],
49
+ cost: withGatewayPrice(ZERO_COST, price),
50
+ contextWindow,
51
+ maxTokens: Math.min(item.maxOutputTokens ?? template.maxTokens, contextWindow),
52
+ };
53
+ applyOpenAiInputCap(model);
54
+ return model;
55
+ }
56
+ /** Servable gateway models the shipped catalog lacks; only these need the price table. */
57
+ export function unshippedServableModels(shipped, listing) {
58
+ const shippedIds = new Set(shipped.map((model) => model.id));
59
+ return listing.filter((item) => isServableChatModel(item) && !shippedIds.has(item.id));
60
+ }
61
+ export function overlayOpenGatewayCatalog(shipped, listing, prices) {
62
+ const servable = new Set(listing.filter(isServableChatModel).map((item) => item.id));
63
+ const shippedById = new Map(shipped.map((model) => [model.id, model]));
64
+ const added = unshippedServableModels(shipped, listing).flatMap((item) => addedModel(item, shippedById, prices) ?? []);
65
+ return [...shipped.filter((model) => servable.has(model.id)), ...added];
66
+ }
67
+ async function fetchJson(url, signal) {
68
+ const response = await fetch(url, { headers: { accept: "application/json" }, signal });
69
+ if (!response.ok)
70
+ throw new Error(`OpenGateway catalog request failed: ${url} returned ${response.status}`);
71
+ return response.json();
72
+ }
73
+ async function fetchRefreshedCatalog(shipped, signal) {
74
+ const listing = parseOpenGatewayListing(await fetchJson(OPENGATEWAY_MODELS_URL, signal));
75
+ const prices = unshippedServableModels(shipped, listing).length > 0
76
+ ? parseOpenGatewayPriceTable(await fetchJson(OPENGATEWAY_PRICES_URL, signal))
77
+ : [];
78
+ const refreshed = overlayOpenGatewayCatalog(shipped, listing, prices);
79
+ if (refreshed.length === 0)
80
+ throw new Error("OpenGateway listing has no servable chat models; keeping the last good catalog");
81
+ return refreshed;
82
+ }
83
+ function isOpenGatewayChatModel(model) {
84
+ return model.provider === "opengateway" && isModelType(model, "chat") && model.api === "openai-completions";
85
+ }
86
+ /**
87
+ * Catalog state for the provider: `getModels()` is synchronous, `refresh()` restores the
88
+ * persisted list and revalidates it against the gateway at most hourly. A persisted list
89
+ * older than the shipped catalog is ignored, so an upgrade never resurrects metadata the
90
+ * new release corrected.
91
+ */
92
+ export function createOpenGatewayCatalog(shipped, shippedGeneratedAt) {
93
+ let current = shipped;
94
+ return {
95
+ getModels: () => current,
96
+ refresh: async (context) => {
97
+ const stored = context.stored;
98
+ const storedCheckedAt = stored?.checkedAt;
99
+ const usable = stored !== undefined &&
100
+ storedCheckedAt !== undefined &&
101
+ (shippedGeneratedAt === undefined || storedCheckedAt > shippedGeneratedAt);
102
+ if (usable && stored) {
103
+ const restored = stored.models.filter(isOpenGatewayChatModel);
104
+ if (!(await context.publish({
105
+ update: () => {
106
+ current = restored;
107
+ },
108
+ })))
109
+ return;
110
+ }
111
+ if (!context.allowNetwork || context.signal.aborted)
112
+ return;
113
+ const age = Date.now() - (storedCheckedAt ?? 0);
114
+ if (!context.force && usable && age >= 0 && age < OPENGATEWAY_REFRESH_INTERVAL_MS)
115
+ return;
116
+ const signal = AbortSignal.any([context.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]);
117
+ const refreshed = await fetchRefreshedCatalog(shipped, signal);
118
+ if (context.signal.aborted)
119
+ return;
120
+ await context.publish({
121
+ persist: { models: refreshed, checkedAt: Date.now() },
122
+ update: () => {
123
+ current = refreshed;
124
+ },
125
+ });
126
+ },
127
+ };
128
+ }
129
+ //# sourceMappingURL=opengateway-refresh.js.map
@@ -1,15 +1,26 @@
1
1
  import { openAICompletionsApi } from "../api/openai-completions.lazy.js";
2
2
  import { envApiKeyAuth } from "../auth/helpers.js";
3
3
  import { createProvider } from "../models.js";
4
+ import modelDataManifest from "./data/.manifest.json" with { type: "json" };
4
5
  import { OPENGATEWAY_MODELS } from "./opengateway.models.js";
6
+ import { createOpenGatewayCatalog } from "./opengateway-refresh.js";
5
7
  export function opengatewayProvider() {
6
- return createProvider({
8
+ const shipped = Object.values(OPENGATEWAY_MODELS);
9
+ const generatedAt = Date.parse(modelDataManifest.generatedAt);
10
+ const catalog = createOpenGatewayCatalog(shipped, Number.isNaN(generatedAt) ? undefined : generatedAt);
11
+ const provider = createProvider({
7
12
  id: "opengateway",
8
13
  name: "OpenGateway",
9
14
  baseUrl: "https://apis.opengateway.ai/v1",
10
15
  auth: { apiKey: envApiKeyAuth("OpenGateway API key", ["OPENGATEWAY_API_KEY"]) },
11
- models: Object.values(OPENGATEWAY_MODELS),
16
+ models: shipped,
12
17
  api: openAICompletionsApi(),
13
18
  });
19
+ return {
20
+ ...provider,
21
+ getModels: catalog.getModels,
22
+ getAllModels: catalog.getModels,
23
+ refreshModels: catalog.refresh,
24
+ };
14
25
  }
15
26
  //# sourceMappingURL=opengateway.js.map
@@ -1,4 +1,4 @@
1
- const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;
1
+ const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#F09082" d="M165.29 165.29H517.36V400H400V282.65H165.29Z"/><path fill="#4D9ABF" d="M165.29 282.65H282.65V400H400V517.36H282.65V634.72H165.29Z"/><path fill="#F1BE58" d="M517.36 400H634.72V634.72H517.36Z"/></svg>`;
2
2
  function escapeHtml(value) {
3
3
  return value
4
4
  .replaceAll("&", "&amp;")
@@ -0,0 +1,4 @@
1
+ import type { Api, Model } from "../types.ts";
2
+ /** OpenAI's input/output split follows GPT-5/6 models across gateway providers. */
3
+ export declare function applyOpenAiInputCap(model: Model<Api>): void;
4
+ //# sourceMappingURL=openai-input-cap.d.ts.map
@@ -0,0 +1,18 @@
1
+ const DOCUMENTED_INPUT_CAPS = new Map([
2
+ [400000, 272000],
3
+ [1050000, 922000],
4
+ ]);
5
+ const GATEWAY_ID_PREFIX = /^(?:[a-z]{2}\.)?(?:global\.)?openai[./]/;
6
+ /** OpenAI's input/output split follows GPT-5/6 models across gateway providers. */
7
+ export function applyOpenAiInputCap(model) {
8
+ const bare = model.id.replace(GATEWAY_ID_PREFIX, "");
9
+ if (!/^gpt-(?:5|6)(?:[.-]|$)/.test(bare))
10
+ return;
11
+ // Historical models.dev output metadata duplicated GPT-5 Pro's input sub-limit.
12
+ if (bare === "gpt-5-pro" && model.maxTokens === 272000)
13
+ model.maxTokens = 128000;
14
+ if (model.maxTokens === 128000) {
15
+ model.contextWindow = DOCUMENTED_INPUT_CAPS.get(model.contextWindow) ?? model.contextWindow;
16
+ }
17
+ }
18
+ //# sourceMappingURL=openai-input-cap.js.map
@@ -29,7 +29,7 @@
29
29
  * - kiro-lb gateways: "Request payload is 1095225 bytes, over the 1085435 byte limit Kiro accepts." / "Request payload is N tokens, over the M token limit Kiro accepts." (HTTP 400 local payload guard)
30
30
  * - Kiro upstream via kiro-lb: "Model context limit reached. Conversation size exceeds model capacity." (CONTENT_LENGTH_EXCEEDS_THRESHOLD token overflow)
31
31
  * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
32
- * - z.ai: `{"code":"1261","message":"Prompt too long"}` or silent overflow via usage.input > contextWindow
32
+ * - z.ai: `{"code":"1261","message":"Prompt too long"}`, `{"code":"1261","message":"Prompt exceeds max length"}` (CN endpoint), or silent overflow via usage.input > contextWindow
33
33
  * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason "length"
34
34
  * with output=0 (no room left to generate). Detected via stopReason "length" + zero output +
35
35
  * input filling the context window.
@@ -42,6 +42,7 @@ const OVERFLOW_PATTERNS = [
42
42
  /^Context window exhausted: /, // pi-ai pre-flight guard: no answer room left, provider never called
43
43
  /^The conversation is too long to resend \(about \d+ tokens, limit \d+\)/, // anthropic-subscription cold-seed budget: re-send refused before dispatch
44
44
  /prompt (?:is )?too long/i, // Anthropic and z.ai token overflow
45
+ /prompt exceeds max length/i, // z.ai CN endpoint token overflow
45
46
  /request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)
46
47
  /input is too long for requested model/i, // Amazon Bedrock
47
48
  /exceeds (?:(?:the|this) )?(?:model'?s )?context window/i, // OpenAI (Completions & Responses API)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-ai",
3
- "version": "2026.10.1",
3
+ "version": "2026.10.3",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,6 +15,10 @@
15
15
  "types": "./dist/index.d.ts",
16
16
  "import": "./dist/index.js"
17
17
  },
18
+ "./models": {
19
+ "types": "./dist/models.d.ts",
20
+ "import": "./dist/models.js"
21
+ },
18
22
  "./compat": {
19
23
  "types": "./dist/compat.d.ts",
20
24
  "import": "./dist/compat.js"
@@ -84,7 +88,7 @@
84
88
  "dependencies": {
85
89
  "@anthropic-ai/sdk": "0.127.0",
86
90
  "@aws-sdk/client-bedrock-runtime": "3.1136.0",
87
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.1",
91
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.3",
88
92
  "@google/genai": "2.23.0",
89
93
  "@smithy/node-http-handler": "4.12.1",
90
94
  "http-proxy-agent": "9.1.0",