mtok-bridge 0.3.0 → 0.3.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/serve-core.mjs +25 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtok-bridge",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Serve any model as an OpenAI-compatible API with a key. No payment, no market, runs anywhere node runs. The transport core behind mtok.market's seller relay.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -119,20 +119,36 @@ export function configuredFeeAtomic({ sellerUsdAtomic, feeAddress, feeBps }) {
119
119
  return (BigInt(sellerUsdAtomic || 0) * bps + 5000n) / 10000n;
120
120
  }
121
121
 
122
- // Conservative tokenizer-independent upper estimate: a tokenizer cannot consume
123
- // more text tokens than UTF-8 bytes, plus the chat envelope around each message.
124
- // The core accepts plain-text messages only, so no unpriced multimodal parts
125
- // can bypass this bound. Pure, dependency-free, no I/O.
122
+ // Tokenizer-independent input estimate, byte-aware with a safety margin.
123
+ //
124
+ // #626: this used to count one token per UTF-8 byte, i.e. a true worst-case
125
+ // bound (a tokenizer cannot emit more text tokens than bytes). That bound is
126
+ // correct and roughly 4x too pessimistic for real text, and the over-estimate
127
+ // is NOT free: boundServe refuses a draw whose estimated input cost alone meets
128
+ // the payment, and that refusal happens AFTER the buyer has paid on chain. A
129
+ // real buyer sending a ~4KB prompt on a budget that comfortably covered it was
130
+ // refused every night for two weeks and auto-disputed, silently.
131
+ //
132
+ // So estimate realistically and keep the margin explicit. BYTES_PER_TOKEN_EST
133
+ // of 3.2 is the English average (~4 bytes/token) with ~25% headroom, and
134
+ // staying in BYTES rather than characters keeps multibyte prompts from reading
135
+ // artificially cheap. The seller's residual exposure when an estimate lands
136
+ // low is bounded: actual usage is metered from the upstream response after the
137
+ // serve, and the output cap is computed from whatever budget the input
138
+ // estimate left, so an under-estimate eats into output headroom rather than
139
+ // running unpriced.
126
140
  export const MESSAGE_OVERHEAD_TOKENS = 4;
141
+ export const BYTES_PER_TOKEN_EST = 3.2;
127
142
  export function estimateInputTokens(messages) {
128
143
  const utf8 = new TextEncoder();
129
- let tokens = 3; // reply priming
144
+ let bytes = 0;
145
+ let envelope = 3; // reply priming
130
146
  for (const m of messages ?? []) {
131
- tokens += MESSAGE_OVERHEAD_TOKENS;
132
- tokens += utf8.encode(String(m?.role ?? '')).length;
133
- tokens += utf8.encode(typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content ?? null)).length;
147
+ envelope += MESSAGE_OVERHEAD_TOKENS;
148
+ bytes += utf8.encode(String(m?.role ?? '')).length;
149
+ bytes += utf8.encode(typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content ?? null)).length;
134
150
  }
135
- return tokens;
151
+ return envelope + Math.ceil(bytes / BYTES_PER_TOKEN_EST);
136
152
  }
137
153
 
138
154
  // Bound a serve against the paid budget in BOTH legs (#495/#460). The relay used