@wopr-network/platform-core 1.52.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.
@@ -25,6 +25,8 @@ export interface ProxyDeps {
25
25
  graceBufferCents?: number;
26
26
  providers: ProviderConfig;
27
27
  defaultModel?: string;
28
+ /** Dynamic model resolver — called per-request, overrides defaultModel. Return null to use defaultModel fallback. */
29
+ resolveDefaultModel?: () => string | null;
28
30
  defaultMargin: number;
29
31
  fetchFn: FetchFn;
30
32
  arbitrageRouter?: import("../monetization/arbitrage/router.js").ArbitrageRouter;
@@ -53,6 +53,7 @@ export function buildProxyDeps(config) {
53
53
  graceBufferCents: config.graceBufferCents,
54
54
  providers: config.providers,
55
55
  defaultModel: config.defaultModel,
56
+ resolveDefaultModel: config.resolveDefaultModel,
56
57
  defaultMargin: config.defaultMargin ?? DEFAULT_MARGIN,
57
58
  fetchFn: config.fetchFn ?? fetch,
58
59
  arbitrageRouter: config.arbitrageRouter,
@@ -113,12 +114,13 @@ export function chatCompletions(deps) {
113
114
  let isStreaming = false;
114
115
  let requestModel;
115
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;
116
119
  try {
117
120
  parsedBody = JSON.parse(rawBody);
118
121
  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
+ if (enforcedModel && parsedBody) {
123
+ parsedBody.model = enforcedModel;
122
124
  }
123
125
  requestModel = parsedBody?.model;
124
126
  }
@@ -126,7 +128,7 @@ export function chatCompletions(deps) {
126
128
  // Not valid JSON, assume non-streaming
127
129
  }
128
130
  // Re-serialize if model was overridden, otherwise forward raw body.
129
- const body = deps.defaultModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
131
+ const body = enforcedModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
130
132
  deps.metrics?.recordGatewayRequest("chat-completions");
131
133
  // WOP-746: Arbitrage routing for non-streaming chat completions.
132
134
  // Mirrors the TTS arbitrage pattern. When arbitrageRouter is present and
@@ -102,10 +102,11 @@ 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. */
105
+ /** Static model override rewrites body.model before forwarding to upstream. */
108
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;
109
110
  /** MeterEmitter instance for usage tracking */
110
111
  meter: MeterEmitter;
111
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.52.0",
3
+ "version": "1.53.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -68,6 +68,8 @@ export interface ProxyDeps {
68
68
  graceBufferCents?: number;
69
69
  providers: ProviderConfig;
70
70
  defaultModel?: string;
71
+ /** Dynamic model resolver — called per-request, overrides defaultModel. Return null to use defaultModel fallback. */
72
+ resolveDefaultModel?: () => string | null;
71
73
  defaultMargin: number;
72
74
  fetchFn: FetchFn;
73
75
  arbitrageRouter?: import("../monetization/arbitrage/router.js").ArbitrageRouter;
@@ -93,6 +95,7 @@ export function buildProxyDeps(config: GatewayConfig): ProxyDeps {
93
95
  graceBufferCents: config.graceBufferCents,
94
96
  providers: config.providers,
95
97
  defaultModel: config.defaultModel,
98
+ resolveDefaultModel: config.resolveDefaultModel,
96
99
  defaultMargin: config.defaultMargin ?? DEFAULT_MARGIN,
97
100
  fetchFn: config.fetchFn ?? fetch,
98
101
  arbitrageRouter: config.arbitrageRouter,
@@ -180,19 +183,20 @@ export function chatCompletions(deps: ProxyDeps) {
180
183
  temperature?: number;
181
184
  }
182
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;
183
188
  try {
184
189
  parsedBody = JSON.parse(rawBody) as typeof parsedBody;
185
190
  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;
191
+ if (enforcedModel && parsedBody) {
192
+ parsedBody.model = enforcedModel;
189
193
  }
190
194
  requestModel = parsedBody?.model;
191
195
  } catch {
192
196
  // Not valid JSON, assume non-streaming
193
197
  }
194
198
  // Re-serialize if model was overridden, otherwise forward raw body.
195
- const body = deps.defaultModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
199
+ const body = enforcedModel && parsedBody ? JSON.stringify(parsedBody) : rawBody;
196
200
 
197
201
  deps.metrics?.recordGatewayRequest("chat-completions");
198
202
 
@@ -98,10 +98,11 @@ 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. */
101
+ /** Static model override rewrites body.model before forwarding to upstream. */
104
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;
105
106
  /** MeterEmitter instance for usage tracking */
106
107
  meter: MeterEmitter;
107
108
  /** BudgetChecker instance for pre-call budget validation */