@wopr-network/platform-core 1.51.0 → 1.52.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,7 @@ export interface ProxyDeps {
24
24
  topUpUrl: string;
25
25
  graceBufferCents?: number;
26
26
  providers: ProviderConfig;
27
+ defaultModel?: string;
27
28
  defaultMargin: number;
28
29
  fetchFn: FetchFn;
29
30
  arbitrageRouter?: import("../monetization/arbitrage/router.js").ArbitrageRouter;
@@ -52,6 +52,7 @@ 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,
55
56
  defaultMargin: config.defaultMargin ?? DEFAULT_MARGIN,
56
57
  fetchFn: config.fetchFn ?? fetch,
57
58
  arbitrageRouter: config.arbitrageRouter,
@@ -108,18 +109,24 @@ export function chatCompletions(deps) {
108
109
  return c.json({ error: creditErr }, 402);
109
110
  }
110
111
  // Parse body once — needed for both arbitrage routing and direct proxy.
111
- const body = await c.req.text();
112
+ const rawBody = await c.req.text();
112
113
  let isStreaming = false;
113
114
  let requestModel;
114
115
  let parsedBody;
115
116
  try {
116
- parsedBody = JSON.parse(body);
117
+ parsedBody = JSON.parse(rawBody);
117
118
  isStreaming = parsedBody?.stream === true;
119
+ // Enforce single-model gateway: override whatever model the client sent.
120
+ if (deps.defaultModel && parsedBody) {
121
+ parsedBody.model = deps.defaultModel;
122
+ }
118
123
  requestModel = parsedBody?.model;
119
124
  }
120
125
  catch {
121
126
  // Not valid JSON, assume non-streaming
122
127
  }
128
+ // Re-serialize if model was overridden, otherwise forward raw body.
129
+ const body = deps.defaultModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
123
130
  deps.metrics?.recordGatewayRequest("chat-completions");
124
131
  // WOP-746: Arbitrage routing for non-streaming chat completions.
125
132
  // Mirrors the TTS arbitrage pattern. When arbitrageRouter is present and
@@ -102,6 +102,10 @@ export interface ProviderConfig {
102
102
  }
103
103
  /** Full gateway configuration. */
104
104
  export interface GatewayConfig {
105
+ /** Force all LLM requests to use this model, ignoring the client's model field.
106
+ * When set, the gateway rewrites body.model before forwarding to the upstream provider.
107
+ * This enforces "we serve one model" pricing — clients don't get to choose. */
108
+ defaultModel?: string;
105
109
  /** MeterEmitter instance for usage tracking */
106
110
  meter: MeterEmitter;
107
111
  /** 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.52.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -67,6 +67,7 @@ export interface ProxyDeps {
67
67
  topUpUrl: string;
68
68
  graceBufferCents?: number;
69
69
  providers: ProviderConfig;
70
+ defaultModel?: string;
70
71
  defaultMargin: number;
71
72
  fetchFn: FetchFn;
72
73
  arbitrageRouter?: import("../monetization/arbitrage/router.js").ArbitrageRouter;
@@ -91,6 +92,7 @@ export function buildProxyDeps(config: GatewayConfig): ProxyDeps {
91
92
  topUpUrl: config.topUpUrl ?? "/dashboard/credits",
92
93
  graceBufferCents: config.graceBufferCents,
93
94
  providers: config.providers,
95
+ defaultModel: config.defaultModel,
94
96
  defaultMargin: config.defaultMargin ?? DEFAULT_MARGIN,
95
97
  fetchFn: config.fetchFn ?? fetch,
96
98
  arbitrageRouter: config.arbitrageRouter,
@@ -166,7 +168,7 @@ export function chatCompletions(deps: ProxyDeps) {
166
168
  }
167
169
 
168
170
  // Parse body once — needed for both arbitrage routing and direct proxy.
169
- const body = await c.req.text();
171
+ const rawBody = await c.req.text();
170
172
  let isStreaming = false;
171
173
  let requestModel: string | undefined;
172
174
  let parsedBody:
@@ -179,12 +181,18 @@ export function chatCompletions(deps: ProxyDeps) {
179
181
  }
180
182
  | undefined;
181
183
  try {
182
- parsedBody = JSON.parse(body) as typeof parsedBody;
184
+ parsedBody = JSON.parse(rawBody) as typeof parsedBody;
183
185
  isStreaming = parsedBody?.stream === true;
186
+ // Enforce single-model gateway: override whatever model the client sent.
187
+ if (deps.defaultModel && parsedBody) {
188
+ parsedBody.model = deps.defaultModel;
189
+ }
184
190
  requestModel = parsedBody?.model;
185
191
  } catch {
186
192
  // Not valid JSON, assume non-streaming
187
193
  }
194
+ // Re-serialize if model was overridden, otherwise forward raw body.
195
+ const body = deps.defaultModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
188
196
 
189
197
  deps.metrics?.recordGatewayRequest("chat-completions");
190
198
 
@@ -98,6 +98,10 @@ export interface ProviderConfig {
98
98
 
99
99
  /** Full gateway configuration. */
100
100
  export interface GatewayConfig {
101
+ /** Force all LLM requests to use this model, ignoring the client's model field.
102
+ * When set, the gateway rewrites body.model before forwarding to the upstream provider.
103
+ * This enforces "we serve one model" pricing — clients don't get to choose. */
104
+ defaultModel?: string;
101
105
  /** MeterEmitter instance for usage tracking */
102
106
  meter: MeterEmitter;
103
107
  /** BudgetChecker instance for pre-call budget validation */