From ed6dbf57301811d66028e8c9b2f3538cd3d0ca6f Mon Sep 17 00:00:00 2001 From: david Date: Thu, 27 Aug 2026 13:16:51 +0200 Subject: [PATCH] fix: quote Surplus text offers by multiplier --- supplier/src/surplus/client.ts | 33 +++++++--- supplier/src/surplus/controller.ts | 18 +++--- supplier/src/surplus/policy.ts | 91 +++++++++++++++------------ supplier/src/surplus/selection.ts | 8 +-- supplier/src/surplus/state.ts | 5 ++ tests/unit/surplus-client.test.ts | 10 ++- tests/unit/surplus-controller.test.ts | 19 ++++-- tests/unit/surplus-policy.test.ts | 23 ++++--- 8 files changed, 121 insertions(+), 86 deletions(-) diff --git a/supplier/src/surplus/client.ts b/supplier/src/surplus/client.ts index 478fbca..2223e5c 100644 --- a/supplier/src/surplus/client.ts +++ b/supplier/src/surplus/client.ts @@ -4,6 +4,7 @@ export interface SurplusOffer { sellerBaseUrl: string; status: "active" | "inactive"; capDailyUsd: number | null; + costMultiplierPpm: number | null; inputMicroUsdPer1m: number | null; outputMicroUsdPer1m: number | null; } @@ -50,16 +51,14 @@ export interface SurplusOfferWrite { model: string; apiKey: string; sellerBaseUrl: string; - inputUsdPer1m: number; - outputUsdPer1m: number; + costMultiplier: number; dailyCapUsd: number; payoutAddress: string; idempotencyKey: string; } export interface SurplusOfferPatch { - inputUsdPer1m: number; - outputUsdPer1m: number; + costMultiplier: number; dailyCapUsd: number; idempotencyKey: string; } @@ -223,9 +222,8 @@ export class SurplusClient { model: input.model, api_key: input.apiKey, seller_base_url: input.sellerBaseUrl, - pricing_mode: "per_token", - price_input_per_1m: input.inputUsdPer1m, - price_output_per_1m: input.outputUsdPer1m, + pricing_mode: "cost_multiplier", + cost_multiplier: input.costMultiplier, cap_daily_usd: input.dailyCapUsd, payout_address: input.payoutAddress, }), @@ -239,9 +237,8 @@ export class SurplusClient { method: "PATCH", headers: mutationHeaders(patch.idempotencyKey), body: JSON.stringify({ - pricing_mode: "per_token", - price_input_per_1m: patch.inputUsdPer1m, - price_output_per_1m: patch.outputUsdPer1m, + pricing_mode: "cost_multiplier", + cost_multiplier: patch.costMultiplier, cap_daily_usd: patch.dailyCapUsd, }), }); @@ -387,6 +384,10 @@ function parseOffer(value: unknown, index: number): SurplusOffer { sellerBaseUrl: string(item.seller_base_url, `Surplus offer ${index}.seller_base_url`), status, capDailyUsd: optionalNonNegativeNumber(item.cap_daily_usd, `Surplus offer ${index}.cap_daily_usd`), + costMultiplierPpm: optionalMultiplierPpm( + item.cost_multiplier, + `Surplus offer ${index}.cost_multiplier`, + ), inputMicroUsdPer1m: optionalMicroUsd(item.price_input_per_1m, `Surplus offer ${index}.price_input_per_1m`), outputMicroUsdPer1m: optionalMicroUsd(item.price_output_per_1m, `Surplus offer ${index}.price_output_per_1m`), }; @@ -516,6 +517,18 @@ function optionalNonNegativeNumber(value: unknown, field: string): number | null return nonNegativeNumber(value, field); } +function optionalMultiplierPpm(value: unknown, field: string): number | null { + if (value === null || value === undefined) return null; + if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) { + throw new Error(`${field} must be a positive finite number`); + } + const ppm = Math.round(value * 1_000_000); + if (!Number.isSafeInteger(ppm) || ppm <= 0) { + throw new Error(`${field} cannot be represented at six decimal places`); + } + return ppm; +} + function sanitizedErrorSnippet( value: string, secrets: readonly string[], diff --git a/supplier/src/surplus/controller.ts b/supplier/src/surplus/controller.ts index 64800dd..f2a4a1c 100644 --- a/supplier/src/surplus/controller.ts +++ b/supplier/src/surplus/controller.ts @@ -14,7 +14,7 @@ import type { } from "./openRouterKey.js"; import { formatMicroUsd, - quoteSurplusPrice, + quoteSurplusMultiplier, } from "./policy.js"; import { eligibleCompetitors, @@ -236,7 +236,7 @@ export class SurplusSellerController { this.config.sellerWallet, ownOfferIds, ); - const quote = quoteSurplusPrice({ + const quote = quoteSurplusMultiplier({ upstreamInputMicroUsdPer1m: candidate.upstreamInputMicroUsdPer1m, upstreamOutputMicroUsdPer1m: candidate.upstreamOutputMicroUsdPer1m, recoveryBps: this.config.recoveryBps, @@ -259,6 +259,7 @@ export class SurplusSellerController { intent = { model: candidate.model, providerModelId: candidate.providerModelId, + costMultiplierPpm: quote.costMultiplierPpm, inputMicroUsdPer1m: quote.inputMicroUsdPer1m, outputMicroUsdPer1m: quote.outputMicroUsdPer1m, dailyCapUsd: Number(this.config.perOfferCapUsd), @@ -296,8 +297,7 @@ export class SurplusSellerController { model: intent.model, apiKey: this.config.providerApiKey, sellerBaseUrl: this.config.providerBaseUrl, - inputUsdPer1m: intent.inputMicroUsdPer1m / 1_000_000, - outputUsdPer1m: intent.outputMicroUsdPer1m / 1_000_000, + costMultiplier: intent.costMultiplierPpm / 1_000_000, dailyCapUsd: intent.dailyCapUsd, payoutAddress: this.config.payoutAddress, idempotencyKey: intent.idempotencyKey, @@ -478,7 +478,7 @@ export class SurplusSellerController { this.config.sellerWallet, ownIds, ); - const quote = quoteSurplusPrice({ + const quote = quoteSurplusMultiplier({ upstreamInputMicroUsdPer1m: Math.ceil(selected.inputUsdPer1m * 1_000_000), upstreamOutputMicroUsdPer1m: Math.ceil(selected.outputUsdPer1m * 1_000_000), recoveryBps: this.config.recoveryBps, @@ -487,16 +487,16 @@ export class SurplusSellerController { }); const nextIntent: SurplusOfferIntent = { ...currentState.intent, + costMultiplierPpm: quote.costMultiplierPpm, inputMicroUsdPer1m: quote.inputMicroUsdPer1m, outputMicroUsdPer1m: quote.outputMicroUsdPer1m, }; - const priceChanged = managed.inputMicroUsdPer1m !== quote.inputMicroUsdPer1m || - managed.outputMicroUsdPer1m !== quote.outputMicroUsdPer1m || + const priceChanged = + managed.costMultiplierPpm !== quote.costMultiplierPpm || managed.capDailyUsd !== Number(this.config.perOfferCapUsd); if (this.config.live && priceChanged) { await this.client.updateOffer(currentState.offerId, { - inputUsdPer1m: quote.inputUsdPer1m, - outputUsdPer1m: quote.outputUsdPer1m, + costMultiplier: quote.costMultiplier, dailyCapUsd: Number(this.config.perOfferCapUsd), idempotencyKey: this.mutationId(), }); diff --git a/supplier/src/surplus/policy.ts b/supplier/src/surplus/policy.ts index 6b71f03..1f85327 100644 --- a/supplier/src/surplus/policy.ts +++ b/supplier/src/surplus/policy.ts @@ -4,7 +4,7 @@ export interface SurplusCompetitor { outputMicroUsdPer1m: number; } -export interface SurplusPricePolicyInput { +export interface SurplusMultiplierPolicyInput { upstreamInputMicroUsdPer1m: number; upstreamOutputMicroUsdPer1m: number; recoveryBps: number; @@ -12,23 +12,23 @@ export interface SurplusPricePolicyInput { competitors: SurplusCompetitor[]; } -export interface SurplusPriceQuote { +export interface SurplusMultiplierQuote { + costMultiplierPpm: number; + costMultiplier: number; inputMicroUsdPer1m: number; outputMicroUsdPer1m: number; - inputUsdPer1m: number; - outputUsdPer1m: number; - floorInputMicroUsdPer1m: number; - floorOutputMicroUsdPer1m: number; + floorMultiplierPpm: number; competitorId: string | null; competitive: boolean; } const BPS_SCALE = 10_000n; +const PPM_SCALE = 1_000_000n; const MICRO_USD = 1_000_000; -export function quoteSurplusPrice( - input: SurplusPricePolicyInput, -): SurplusPriceQuote { +export function quoteSurplusMultiplier( + input: SurplusMultiplierPolicyInput, +): SurplusMultiplierQuote { positiveSafeInteger( input.upstreamInputMicroUsdPer1m, "upstream input price", @@ -40,32 +40,50 @@ export function quoteSurplusPrice( boundedBps(input.recoveryBps, "recoveryBps", 1, 10_000); boundedBps(input.undercutBps, "undercutBps", 0, 9_999); - const floorInput = multiplyBpsCeil( - input.upstreamInputMicroUsdPer1m, - input.recoveryBps, - ); - const floorOutput = multiplyBpsCeil( - input.upstreamOutputMicroUsdPer1m, - input.recoveryBps, - ); - + const floorMultiplierPpm = input.recoveryBps * 100; const competitor = cheapestCompetitor(input.competitors); - const candidateInput = competitor - ? undercut(competitor.inputMicroUsdPer1m, input.undercutBps) - : floorInput; - const candidateOutput = competitor - ? undercut(competitor.outputMicroUsdPer1m, input.undercutBps) - : floorOutput; - const quotedInput = Math.max(floorInput, candidateInput); - const quotedOutput = Math.max(floorOutput, candidateOutput); + const competitorMultiplierPpm = competitor + ? Math.min( + ratioPpm( + competitor.inputMicroUsdPer1m, + input.upstreamInputMicroUsdPer1m, + ), + ratioPpm( + competitor.outputMicroUsdPer1m, + input.upstreamOutputMicroUsdPer1m, + ), + ) + : floorMultiplierPpm; + let undercutMultiplierPpm = Number( + BigInt(competitorMultiplierPpm) * + BigInt(10_000 - input.undercutBps) / + BPS_SCALE, + ); + if ( + input.undercutBps > 0 && + undercutMultiplierPpm >= competitorMultiplierPpm + ) { + undercutMultiplierPpm = Math.max(1, competitorMultiplierPpm - 1); + } + const quotedMultiplierPpm = Math.max( + floorMultiplierPpm, + undercutMultiplierPpm, + ); + const quotedInput = applyMultiplier( + input.upstreamInputMicroUsdPer1m, + quotedMultiplierPpm, + ); + const quotedOutput = applyMultiplier( + input.upstreamOutputMicroUsdPer1m, + quotedMultiplierPpm, + ); return { + costMultiplierPpm: quotedMultiplierPpm, + costMultiplier: quotedMultiplierPpm / Number(PPM_SCALE), inputMicroUsdPer1m: quotedInput, outputMicroUsdPer1m: quotedOutput, - inputUsdPer1m: quotedInput / MICRO_USD, - outputUsdPer1m: quotedOutput / MICRO_USD, - floorInputMicroUsdPer1m: floorInput, - floorOutputMicroUsdPer1m: floorOutput, + floorMultiplierPpm, competitorId: competitor?.id ?? null, competitive: competitor === null || ( quotedInput < competitor.inputMicroUsdPer1m && @@ -119,17 +137,12 @@ function compareCompetitors( ); } -function undercut(value: number, bps: number): number { - const discounted = Number( - (BigInt(value) * BigInt(10_000 - bps)) / BPS_SCALE, - ); - if (discounted >= value && value > 1) return value - 1; - return Math.max(1, discounted); +function ratioPpm(numerator: number, denominator: number): number { + return Number(BigInt(numerator) * PPM_SCALE / BigInt(denominator)); } -function multiplyBpsCeil(value: number, bps: number): number { - const numerator = BigInt(value) * BigInt(bps); - return Number((numerator + BPS_SCALE - 1n) / BPS_SCALE); +function applyMultiplier(value: number, multiplierPpm: number): number { + return Number(BigInt(value) * BigInt(multiplierPpm) / PPM_SCALE); } function positiveSafeInteger(value: number, field: string): void { diff --git a/supplier/src/surplus/selection.ts b/supplier/src/surplus/selection.ts index 10baaff..bc55a33 100644 --- a/supplier/src/surplus/selection.ts +++ b/supplier/src/surplus/selection.ts @@ -4,10 +4,10 @@ import type { SurplusOrderBookOffer, } from "./client.js"; import { - quoteSurplusPrice, + quoteSurplusMultiplier, usdPer1mToMicroUsd, type SurplusCompetitor, - type SurplusPriceQuote, + type SurplusMultiplierQuote, } from "./policy.js"; export interface SurplusModelCandidate { @@ -17,7 +17,7 @@ export interface SurplusModelCandidate { upstreamOutputMicroUsdPer1m: number; requests24h: number; volume24h: number; - summaryQuote: SurplusPriceQuote; + summaryQuote: SurplusMultiplierQuote; } export function selectSurplusModelCandidates( @@ -54,7 +54,7 @@ export function selectSurplusModelCandidates( model.outputUsdPer1m, `${model.model} discovery output price`, ); - const quote = quoteSurplusPrice({ + const quote = quoteSurplusMultiplier({ upstreamInputMicroUsdPer1m: inputPrice, upstreamOutputMicroUsdPer1m: outputPrice, recoveryBps, diff --git a/supplier/src/surplus/state.ts b/supplier/src/surplus/state.ts index 51e0e0b..5815fb6 100644 --- a/supplier/src/surplus/state.ts +++ b/supplier/src/surplus/state.ts @@ -14,6 +14,7 @@ import { randomUUID } from "node:crypto"; export interface SurplusOfferIntent { model: string; providerModelId: string; + costMultiplierPpm: number; inputMicroUsdPer1m: number; outputMicroUsdPer1m: number; dailyCapUsd: number; @@ -266,6 +267,10 @@ function parseIntent(value: unknown): SurplusOfferIntent { intent.providerModelId, "Surplus state intent.providerModelId", ), + costMultiplierPpm: positiveInteger( + intent.costMultiplierPpm, + "Surplus state intent.costMultiplierPpm", + ), inputMicroUsdPer1m: positiveInteger( intent.inputMicroUsdPer1m, "Surplus state intent.inputMicroUsdPer1m", diff --git a/tests/unit/surplus-client.test.ts b/tests/unit/surplus-client.test.ts index a75e2cc..18a29f8 100644 --- a/tests/unit/surplus-client.test.ts +++ b/tests/unit/surplus-client.test.ts @@ -222,7 +222,7 @@ describe("SurplusClient", () => { expect(String(fetchFn.mock.calls[1][0])).toContain("alpha%3Amodel"); }); - it("sends one absolute PATCH with plain USD prices and the cap", async () => { + it("sends one absolute cost-multiplier PATCH with the cap", async () => { let request: RequestInit | undefined; const client = new SurplusClient({ apiBaseUrl: "https://api.surplusintelligence.ai", @@ -235,17 +235,15 @@ describe("SurplusClient", () => { }); await client.updateOffer("offer/one", { - inputUsdPer1m: 0.003972, - outputUsdPer1m: 0.007944, + costMultiplier: 0.04995, dailyCapUsd: 1, idempotencyKey: "mutation-1", }); expect(request?.method).toBe("PATCH"); expect(JSON.parse(String(request?.body))).toEqual({ - pricing_mode: "per_token", - price_input_per_1m: 0.003972, - price_output_per_1m: 0.007944, + pricing_mode: "cost_multiplier", + cost_multiplier: 0.04995, cap_daily_usd: 1, }); const headers = new Headers(request?.headers); diff --git a/tests/unit/surplus-controller.test.ts b/tests/unit/surplus-controller.test.ts index f4df5fb..9b28ce1 100644 --- a/tests/unit/surplus-controller.test.ts +++ b/tests/unit/surplus-controller.test.ts @@ -147,8 +147,9 @@ class FakeSurplusClient implements SurplusControllerClient { sellerBaseUrl: input.sellerBaseUrl, status: "active", capDailyUsd: input.dailyCapUsd, - inputMicroUsdPer1m: Math.round(input.inputUsdPer1m * 1_000_000), - outputMicroUsdPer1m: Math.round(input.outputUsdPer1m * 1_000_000), + costMultiplierPpm: Math.round(input.costMultiplier * 1_000_000), + inputMicroUsdPer1m: Math.floor(79_520 * input.costMultiplier), + outputMicroUsdPer1m: Math.floor(159_040 * input.costMultiplier), }; return this.offer.id; } @@ -157,8 +158,15 @@ class FakeSurplusClient implements SurplusControllerClient { this.externalCalls += 1; this.patchPayload = patch; if (!this.offer) throw new Error("missing fake offer"); - this.offer.inputMicroUsdPer1m = Math.round(patch.inputUsdPer1m * 1_000_000); - this.offer.outputMicroUsdPer1m = Math.round(patch.outputUsdPer1m * 1_000_000); + this.offer.costMultiplierPpm = Math.round( + patch.costMultiplier * 1_000_000, + ); + this.offer.inputMicroUsdPer1m = Math.floor( + 79_520 * patch.costMultiplier, + ); + this.offer.outputMicroUsdPer1m = Math.floor( + 159_040 * patch.costMultiplier, + ); this.offer.capDailyUsd = patch.dailyCapUsd; } @@ -223,8 +231,7 @@ describe("SurplusSellerController", () => { expect(client.createCalls).toBe(1); expect(client.createdPayload).toMatchObject({ model: "beta-model", - inputUsdPer1m: 0.003972, - outputUsdPer1m: 0.007944, + costMultiplier: 0.04995, dailyCapUsd: 1, payoutAddress: SELLER_WALLET, }); diff --git a/tests/unit/surplus-policy.test.ts b/tests/unit/surplus-policy.test.ts index fde43fb..f6b8a00 100644 --- a/tests/unit/surplus-policy.test.ts +++ b/tests/unit/surplus-policy.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { - quoteSurplusPrice, + quoteSurplusMultiplier, } from "../../supplier/src/surplus/policy.js"; import { eligibleCompetitors, @@ -41,8 +41,8 @@ function market(model: string, requests24h: number): SurplusMarketSummary { } describe("Surplus bounded price policy", () => { - it("uses exact recovery floors and undercuts in integer micro-USD", () => { - const quote = quoteSurplusPrice({ + it("uses an exact recovery multiplier and undercuts in integer micro-USD", () => { + const quote = quoteSurplusMultiplier({ upstreamInputMicroUsdPer1m: 79_520, upstreamOutputMicroUsdPer1m: 159_040, recoveryBps: 490, @@ -54,17 +54,16 @@ describe("Surplus bounded price policy", () => { }], }); - expect(quote.floorInputMicroUsdPer1m).toBe(3_897); - expect(quote.floorOutputMicroUsdPer1m).toBe(7_793); + expect(quote.floorMultiplierPpm).toBe(49_000); + expect(quote.costMultiplierPpm).toBe(49_950); + expect(quote.costMultiplier).toBe(0.04995); expect(quote.inputMicroUsdPer1m).toBe(3_972); expect(quote.outputMicroUsdPer1m).toBe(7_944); - expect(quote.inputUsdPer1m).toBe(0.003972); - expect(quote.outputUsdPer1m).toBe(0.007944); expect(quote.competitive).toBe(true); }); it("keeps the recovery floor when the cheapest offer is too low", () => { - const quote = quoteSurplusPrice({ + const quote = quoteSurplusMultiplier({ upstreamInputMicroUsdPer1m: 79_520, upstreamOutputMicroUsdPer1m: 159_040, recoveryBps: 490, @@ -76,8 +75,8 @@ describe("Surplus bounded price policy", () => { }], }); - expect(quote.inputMicroUsdPer1m).toBe(3_897); - expect(quote.outputMicroUsdPer1m).toBe(7_793); + expect(quote.floorMultiplierPpm).toBe(49_000); + expect(quote.costMultiplierPpm).toBe(49_000); expect(quote.competitive).toBe(false); }); @@ -91,9 +90,9 @@ describe("Surplus bounded price policy", () => { const left = { id: "left", inputMicroUsdPer1m: 100, outputMicroUsdPer1m: 300 }; const right = { id: "right", inputMicroUsdPer1m: 200, outputMicroUsdPer1m: 200 }; - expect(quoteSurplusPrice({ ...input, competitors: [right, left] }).competitorId) + expect(quoteSurplusMultiplier({ ...input, competitors: [right, left] }).competitorId) .toBe("left"); - expect(quoteSurplusPrice({ ...input, competitors: [left, right] }).competitorId) + expect(quoteSurplusMultiplier({ ...input, competitors: [left, right] }).competitorId) .toBe("left"); }); });