Files
hosted-gateway/src/pricing/estimate.ts
T
Felix FaerberandClaude Opus 4.7 9503b19378 pricing: budget maxPayment generously to avoid PaymentTooHigh reverts
The previous formula computed maxPayment from the estimated input length
plus offering.maxContextTokens — but the on-chain offering for some
provider models has maxContextTokens=0, so output cost was 0 and the
posted maxPayment only covered the (under-estimated) input side. The
provider's claimJob then reverted with PaymentTooHigh, leaving the
gateway hung waiting for a PSS job_deliver that never came.

Match the t4t/container homelab gateway: assume both prompt and
completion can be (max_tokens + 1M-token headroom), budget at the full
split rate, let the JobEscrow contract refund the unused portion. The
provider's actual claim is always smaller, so PaymentTooHigh can't fire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 17:29:50 +03:00

52 lines
2.2 KiB
TypeScript

import type { ModelOffering } from '../t4t/lib/types';
import { env } from '../env';
const BPS_DIVISOR = 10_000n;
const TOKENS_PER_MILLION = 1_000_000n;
// Conservative ceiling matches the t4t/container homelab gateway. We assume
// prompt and completion could each be `max_tokens + headroom` and budget both
// sides at the model's full split rate. The provider always claims the actual
// (smaller) amount; the JobEscrow contract refunds the remainder to the client.
//
// Why so generous: providers may apply chat templates that inflate the prompt
// token count well beyond the user-visible messages, and the on-chain
// `maxContextTokens` is sometimes unset (== 0) — in which case the previous
// per-actual-input formula posted a too-low maxPayment and the provider's
// claimJob reverted with PaymentTooHigh.
const DEFAULT_MAX_TOKENS = 1024n;
const HEADROOM_TOKENS = 1_000_000n;
export interface ChatRequest {
model: string;
max_tokens?: number;
messages?: Array<{ content: string }>;
}
/**
* Rough upper-bound xBZZ cost for a single chat completion. Mirrors the
* `maxPayment` semantics on JobEscrow.postJob.
*
* Returns `{ raw, withMarkup }` so the caller can:
* - post the *raw* amount on-chain as `maxPayment` (markup is operator
* revenue, not paid to providers)
* - reserve the *with-markup* amount off-chain
*/
export function estimateMaxXbzz(
req: ChatRequest,
offering: Pick<ModelOffering, 'inputPricePerMillionTokens' | 'outputPricePerMillionTokens' | 'maxContextTokens'>,
): { rawWei: bigint; withMarkupWei: bigint } {
const maxTokens = BigInt(req.max_tokens ?? DEFAULT_MAX_TOKENS);
const budget = maxTokens + HEADROOM_TOKENS;
const inPay = BigInt(offering.inputPricePerMillionTokens) * budget;
const outPay = BigInt(offering.outputPricePerMillionTokens) * budget;
const rawWei = (inPay + outPay) / TOKENS_PER_MILLION;
const withMarkupWei = (rawWei * (BPS_DIVISOR + BigInt(env.MARKUP_BPS))) / BPS_DIVISOR;
return { rawWei, withMarkupWei };
}
/** Apply the same markup function used at reservation time to a settled cost. */
export function applyMarkup(rawWei: bigint): bigint {
return (rawWei * (BPS_DIVISOR + BigInt(env.MARKUP_BPS))) / BPS_DIVISOR;
}