mtok-bridge 0.3.2 → 0.3.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtok-bridge",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
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": {
package/src/bridge.mjs CHANGED
@@ -55,7 +55,7 @@ export async function serveChat({ body, authHeader, apiKey, models, upstream })
55
55
  // `baseUrl` is the API root (e.g. https://api.openai.com/v1 or a local model server); `key` is
56
56
  // its bearer token (optional for a keyless local server). This is what makes the bridge portable:
57
57
  // point it at a provider, or at ollama / LM Studio / vLLM on localhost.
58
- export function httpUpstream({ baseUrl, key }) {
58
+ export function httpUpstream({ baseUrl, key, timeoutMs }) {
59
59
  const url = String(baseUrl || '').replace(/\/$/, '') + '/chat/completions';
60
60
  return async (payload) => {
61
61
  const res = await fetch(url, {
@@ -65,6 +65,7 @@ export function httpUpstream({ baseUrl, key }) {
65
65
  ...(key ? { authorization: `Bearer ${key}` } : {}),
66
66
  },
67
67
  body: JSON.stringify(payload),
68
+ ...(timeoutMs == null ? {} : { signal: AbortSignal.timeout(timeoutMs) }),
68
69
  });
69
70
  const text = await res.text();
70
71
  let json;
@@ -282,6 +282,24 @@ export function createServeCore({
282
282
  return { status: 402, body: { error: 'contract_mode_required', detail: 'this relay only serves contract-mode draws; the platform is not reporting a dripContractAddress' } };
283
283
  }
284
284
  if (!drawPaidTxHash) return { status: 402, body: { error: 'draw_payment_required', detail: 'contract mode requires drawPaidTxHash before upstream delivery' } };
285
+ let storedKey = cacheKey;
286
+ // The redemption interface may be sync (fs store) or async (a Workers KV
287
+ // store): await normalizes both, and identity-awaits cost nothing under the
288
+ // host's per-booking lock.
289
+ let redemptionState = await redemption.state(storedKey);
290
+ // Preserve an upgrade's pre-scheme redemption log; missing this alias
291
+ // would let a legacy paid draw run upstream again after relay upgrade.
292
+ if (!redemptionState && oldLegacyKey) {
293
+ const oldLegacyState = await redemption.state(oldLegacyKey);
294
+ if (oldLegacyState) {
295
+ storedKey = oldLegacyKey;
296
+ redemptionState = oldLegacyState;
297
+ } else {
298
+ // Checking the legacy marker may have refreshed a prefixed record
299
+ // appended by another upgraded process.
300
+ redemptionState = await redemption.state(cacheKey);
301
+ }
302
+ }
285
303
  let paid;
286
304
  try {
287
305
  paid = await verifier.verifyDrawPaid(drawPaidTxHash, {
@@ -295,16 +313,15 @@ export function createServeCore({
295
313
  requestHash,
296
314
  sellerWallet,
297
315
  feeRecipient: currentFeeRecipient,
298
- // #580: refuse a payment older than the redemption window. The JSONL
299
- // payload cache AND the claim markers are both aged out at boot (#600),
300
- // so past retention this age bound is the sole replay defense (its
301
- // skip-on-unreadable-block residual is named in redemption.mjs). An
302
- // honest retry is seconds-to-minutes old, never days.
303
- maxPaidAgeMs: redemption.retentionMs,
316
+ // A known completion spends no new inference. New claims need a verified
317
+ // age because both payload and claim markers expire after retention.
318
+ maxPaidAgeMs: redemptionState === 'complete' ? undefined : redemption.retentionMs,
304
319
  });
305
320
  } catch (e) {
321
+ if (e.name === 'TimeoutError') return { status: 503, body: { error: 'relay_timeout', _bookingId: bookingId } };
306
322
  return { status: 402, body: { error: 'payment_unverified', detail: e.message } };
307
323
  }
324
+ if (paid?.reason === 'payment_age_unavailable') return { status: 503, body: { error: 'payment_age_unavailable', detail: 'payment age could not be verified; retry this same paid draw', _bookingId: bookingId } };
308
325
  if (!paid?.ok) return { status: 402, body: { error: 'payment_unverified', detail: paid?.reason || 'unknown' } };
309
326
  const expectedFee = configuredFeeAtomic({
310
327
  sellerUsdAtomic: paid.event.sellerUsdAtomic,
@@ -328,24 +345,6 @@ export function createServeCore({
328
345
  return { status: 403, body: { error: 'payer_denied', detail: 'payer screening failed: ' + e.message } };
329
346
  }
330
347
  }
331
- let storedKey = cacheKey;
332
- // The redemption interface may be sync (fs store) or async (a Workers KV
333
- // store): await normalizes both, and identity-awaits cost nothing under the
334
- // host's per-booking lock.
335
- let redemptionState = await redemption.state(storedKey);
336
- // Preserve an upgrade's pre-scheme redemption log; missing this alias
337
- // would let a legacy paid draw run upstream again after relay upgrade.
338
- if (!redemptionState && oldLegacyKey) {
339
- const oldLegacyState = await redemption.state(oldLegacyKey);
340
- if (oldLegacyState) {
341
- storedKey = oldLegacyKey;
342
- redemptionState = oldLegacyState;
343
- } else {
344
- // Checking the legacy marker may have refreshed a prefixed record
345
- // appended by another upgraded process.
346
- redemptionState = await redemption.state(cacheKey);
347
- }
348
- }
349
348
  if (redemptionState === 'complete') return { status: 200, body: await redemption.get(storedKey) };
350
349
  if (redemptionState === 'pending') {
351
350
  return { status: 409, body: { error: 'draw_pending', detail: 'this paid draw was already claimed; refusing to run upstream again', _bookingId: bookingId } };
@@ -388,6 +387,7 @@ export function createServeCore({
388
387
  try {
389
388
  completion = await upstream(safeRequest);
390
389
  } catch (e) {
390
+ if (e.name === 'TimeoutError') return { status: 503, body: { error: 'relay_timeout', _bookingId: bookingId } };
391
391
  return { status: 502, body: { error: 'upstream_error', detail: e.message } };
392
392
  }
393
393