@wopr-network/platform-core 1.51.0 → 1.53.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.
@@ -24,6 +24,9 @@ export interface ProxyDeps {
24
24
  topUpUrl: string;
25
25
  graceBufferCents?: number;
26
26
  providers: ProviderConfig;
27
+ defaultModel?: string;
28
+ /** Dynamic model resolver — called per-request, overrides defaultModel. Return null to use defaultModel fallback. */
29
+ resolveDefaultModel?: () => string | null;
27
30
  defaultMargin: number;
28
31
  fetchFn: FetchFn;
29
32
  arbitrageRouter?: import("../monetization/arbitrage/router.js").ArbitrageRouter;
@@ -52,6 +52,8 @@ export function buildProxyDeps(config) {
52
52
  topUpUrl: config.topUpUrl ?? "/dashboard/credits",
53
53
  graceBufferCents: config.graceBufferCents,
54
54
  providers: config.providers,
55
+ defaultModel: config.defaultModel,
56
+ resolveDefaultModel: config.resolveDefaultModel,
55
57
  defaultMargin: config.defaultMargin ?? DEFAULT_MARGIN,
56
58
  fetchFn: config.fetchFn ?? fetch,
57
59
  arbitrageRouter: config.arbitrageRouter,
@@ -108,18 +110,25 @@ export function chatCompletions(deps) {
108
110
  return c.json({ error: creditErr }, 402);
109
111
  }
110
112
  // Parse body once — needed for both arbitrage routing and direct proxy.
111
- const body = await c.req.text();
113
+ const rawBody = await c.req.text();
112
114
  let isStreaming = false;
113
115
  let requestModel;
114
116
  let parsedBody;
117
+ // Resolve the enforced model once — dynamic DB resolver takes priority over static env var.
118
+ const enforcedModel = deps.resolveDefaultModel?.() ?? deps.defaultModel ?? null;
115
119
  try {
116
- parsedBody = JSON.parse(body);
120
+ parsedBody = JSON.parse(rawBody);
117
121
  isStreaming = parsedBody?.stream === true;
122
+ if (enforcedModel && parsedBody) {
123
+ parsedBody.model = enforcedModel;
124
+ }
118
125
  requestModel = parsedBody?.model;
119
126
  }
120
127
  catch {
121
128
  // Not valid JSON, assume non-streaming
122
129
  }
130
+ // Re-serialize if model was overridden, otherwise forward raw body.
131
+ const body = enforcedModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
123
132
  deps.metrics?.recordGatewayRequest("chat-completions");
124
133
  // WOP-746: Arbitrage routing for non-streaming chat completions.
125
134
  // Mirrors the TTS arbitrage pattern. When arbitrageRouter is present and
@@ -102,6 +102,11 @@ export interface ProviderConfig {
102
102
  }
103
103
  /** Full gateway configuration. */
104
104
  export interface GatewayConfig {
105
+ /** Static model override — rewrites body.model before forwarding to upstream. */
106
+ defaultModel?: string;
107
+ /** Dynamic model resolver — called per-request, takes priority over defaultModel.
108
+ * Return null to fall back to defaultModel / client-specified. */
109
+ resolveDefaultModel?: () => string | null;
105
110
  /** MeterEmitter instance for usage tracking */
106
111
  meter: MeterEmitter;
107
112
  /** BudgetChecker instance for pre-call budget validation */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.51.0",
3
+ "version": "1.53.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -67,6 +67,9 @@ export interface ProxyDeps {
67
67
  topUpUrl: string;
68
68
  graceBufferCents?: number;
69
69
  providers: ProviderConfig;
70
+ defaultModel?: string;
71
+ /** Dynamic model resolver — called per-request, overrides defaultModel. Return null to use defaultModel fallback. */
72
+ resolveDefaultModel?: () => string | null;
70
73
  defaultMargin: number;
71
74
  fetchFn: FetchFn;
72
75
  arbitrageRouter?: import("../monetization/arbitrage/router.js").ArbitrageRouter;
@@ -91,6 +94,8 @@ export function buildProxyDeps(config: GatewayConfig): ProxyDeps {
91
94
  topUpUrl: config.topUpUrl ?? "/dashboard/credits",
92
95
  graceBufferCents: config.graceBufferCents,
93
96
  providers: config.providers,
97
+ defaultModel: config.defaultModel,
98
+ resolveDefaultModel: config.resolveDefaultModel,
94
99
  defaultMargin: config.defaultMargin ?? DEFAULT_MARGIN,
95
100
  fetchFn: config.fetchFn ?? fetch,
96
101
  arbitrageRouter: config.arbitrageRouter,
@@ -166,7 +171,7 @@ export function chatCompletions(deps: ProxyDeps) {
166
171
  }
167
172
 
168
173
  // Parse body once — needed for both arbitrage routing and direct proxy.
169
- const body = await c.req.text();
174
+ const rawBody = await c.req.text();
170
175
  let isStreaming = false;
171
176
  let requestModel: string | undefined;
172
177
  let parsedBody:
@@ -178,13 +183,20 @@ export function chatCompletions(deps: ProxyDeps) {
178
183
  temperature?: number;
179
184
  }
180
185
  | undefined;
186
+ // Resolve the enforced model once — dynamic DB resolver takes priority over static env var.
187
+ const enforcedModel = deps.resolveDefaultModel?.() ?? deps.defaultModel ?? null;
181
188
  try {
182
- parsedBody = JSON.parse(body) as typeof parsedBody;
189
+ parsedBody = JSON.parse(rawBody) as typeof parsedBody;
183
190
  isStreaming = parsedBody?.stream === true;
191
+ if (enforcedModel && parsedBody) {
192
+ parsedBody.model = enforcedModel;
193
+ }
184
194
  requestModel = parsedBody?.model;
185
195
  } catch {
186
196
  // Not valid JSON, assume non-streaming
187
197
  }
198
+ // Re-serialize if model was overridden, otherwise forward raw body.
199
+ const body = enforcedModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
188
200
 
189
201
  deps.metrics?.recordGatewayRequest("chat-completions");
190
202
 
@@ -98,6 +98,11 @@ export interface ProviderConfig {
98
98
 
99
99
  /** Full gateway configuration. */
100
100
  export interface GatewayConfig {
101
+ /** Static model override — rewrites body.model before forwarding to upstream. */
102
+ defaultModel?: string;
103
+ /** Dynamic model resolver — called per-request, takes priority over defaultModel.
104
+ * Return null to fall back to defaultModel / client-specified. */
105
+ resolveDefaultModel?: () => string | null;
101
106
  /** MeterEmitter instance for usage tracking */
102
107
  meter: MeterEmitter;
103
108
  /** BudgetChecker instance for pre-call budget validation */