shinjuku-shielded 0.3.156 → 0.3.157

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/README.md CHANGED
@@ -6,19 +6,19 @@ answers privately, in a terminal chat or for your agent app. Its `mcp`
6
6
  command runs a local MCP server (stdio) for your agent. Your keys never
7
7
  leave your machine.
8
8
 
9
- This is version 0.3.156. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.157. The npm packages `shinjuku-shield` and
10
10
  `shinjuku-shielded` hold the same files. The command is `shinjuku`.
11
11
 
12
12
  ## Set up
13
13
 
14
14
  ```sh
15
- npm install -g shinjuku@0.3.156
15
+ npm install -g shinjuku@0.3.157
16
16
  shinjuku setup
17
17
  ```
18
18
 
19
19
  Type the two lines as they are. `npx install shinjuku` does not work: npx
20
20
  reads `install` as the name of another npm package. To set up without
21
- installing, type `npx shinjuku@0.3.156 setup`.
21
+ installing, type `npx shinjuku@0.3.157 setup`.
22
22
 
23
23
  Setup makes the wallet on this machine, a private passphrase file, and the
24
24
  checked proof tools (about 191 MB), and writes `config.json`. It also writes
@@ -36,13 +36,13 @@ Every wallet command works after `shinjuku`, for example `shinjuku balance`,
36
36
  Claude Code:
37
37
 
38
38
  ```sh
39
- claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.156 mcp
39
+ claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.157 mcp
40
40
  ```
41
41
 
42
42
  Claude Desktop (`claude_desktop_config.json`) or Cursor (`~/.cursor/mcp.json`):
43
43
 
44
44
  ```json
45
- { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.156", "mcp"] } } }
45
+ { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.157", "mcp"] } } }
46
46
  ```
47
47
 
48
48
  On native Windows, if the host cannot find `npx`, use `"command": "cmd"` and
@@ -55,7 +55,7 @@ Codex (the Codex app and Codex CLI read the same file, `~/.codex/config.toml`):
55
55
  ```toml
56
56
  [mcp_servers.shinjuku]
57
57
  command = "npx"
58
- args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.156", "mcp"]
58
+ args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.157", "mcp"]
59
59
  startup_timeout_sec = 120
60
60
  tool_timeout_sec = 300
61
61
  ```
package/manifest.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": "carbon-shielded-wallet/bundle-manifest/v1",
3
3
  "file": "shinjuku-wallet.mjs",
4
- "sha256": "003fb669ed2395e72fbedad6336c943c92c940fba878d03c4fa03245e2b5ae78",
5
- "bytes": 9056153,
6
- "sourceCommit": "41849a3c692d96c166971d1dd5e13ca84cbceb12",
4
+ "sha256": "46b6176b85190a52572566a760686111c9f01d07b8057e83a3ea1d76a20a3f5f",
5
+ "bytes": 9069628,
6
+ "sourceCommit": "4c41d8d68ac102c3b34f13def9057f631c923ce7",
7
7
  "sourceDirty": false,
8
8
  "installRecordMatchesLockfile": true,
9
9
  "releaseRule": "valid only when two fresh npm ci --ignore-scripts builds on different OSes give the same sha256; installRecordMatchesLockfile true means every entry npm installed equals its package-lock.json entry and every entry not marked optional, devOptional included, is installed: an optional entry (a package for another platform) may be absent",
@@ -1038,5 +1038,5 @@
1038
1038
  "version": "1.8.0"
1039
1039
  }
1040
1040
  ],
1041
- "publishedAt": "2026-11-03T06:19:18.430Z"
1041
+ "publishedAt": "2026-11-03T09:32:37.020Z"
1042
1042
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shielded",
3
- "version": "0.3.156",
3
+ "version": "0.3.157",
4
4
  "mcpName": "io.github.ShinjukuStaition/shinjuku-mcp",
5
5
  "description": "Shinjuku Shielded, the private x402 facilitator on Solana: the self-custody wallet CLI and its MCP server. Shield, pay, and unshield USDC from any MCP agent, under caps you set.",
6
- "shinjukuRelease": "003fb669",
7
- "shinjukuSha256": "003fb669ed2395e72fbedad6336c943c92c940fba878d03c4fa03245e2b5ae78",
6
+ "shinjukuRelease": "46b6176b",
7
+ "shinjukuSha256": "46b6176b85190a52572566a760686111c9f01d07b8057e83a3ea1d76a20a3f5f",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-shield#readme",
@@ -67027,6 +67027,7 @@ var init_errorCodes = __esm({
67027
67027
  "inference_no_provider",
67028
67028
  "inference_origin_refused",
67029
67029
  "inference_out_of_tokens",
67030
+ "inference_empty_answer",
67030
67031
  // wallet_unshield of a balance in several accounts: the stage code while the automatic join runs (mcp/tools.mjs joiningStage).
67031
67032
  "unshield_joining_accounts",
67032
67033
  // unshield of a fixed amount in a terminal: its own join (joinForFixedSend, AB186) did not get the balance ready.
@@ -83582,15 +83583,19 @@ function memoryRequestMatches(record, { url, request, programId, anyProgram = fa
83582
83583
  function dropOldAnswers(now) {
83583
83584
  for (const [key, entry] of answers) if (now >= entry.atMs + MEMORY_ANSWER_KEEP_MS) answers.delete(key);
83584
83585
  }
83585
- function keepMemoryAnswer(record, resultBase64, now = Date.now()) {
83586
+ function keepMemoryAnswer(record, resultBase64, now = Date.now(), sellerHeaders = []) {
83586
83587
  if (!isMemoryFingerprint(record?.fingerprint) || typeof resultBase64 !== "string") return;
83587
83588
  dropOldAnswers(now);
83588
- keepBounded(answers, record.fingerprint, { resultBase64, atMs: now });
83589
+ keepBounded(answers, record.fingerprint, { resultBase64, atMs: now, sellerHeaders: Array.isArray(sellerHeaders) ? sellerHeaders : [] });
83589
83590
  }
83590
83591
  function memoryAnswer(record, now = Date.now()) {
83591
83592
  dropOldAnswers(now);
83592
83593
  return isMemoryFingerprint(record?.fingerprint) ? answers.get(record.fingerprint)?.resultBase64 : void 0;
83593
83594
  }
83595
+ function memoryAnswerHeaders(record, now = Date.now()) {
83596
+ dropOldAnswers(now);
83597
+ return (isMemoryFingerprint(record?.fingerprint) ? answers.get(record.fingerprint)?.sellerHeaders : void 0) ?? [];
83598
+ }
83594
83599
  function forgetMemoryOnly() {
83595
83600
  requests.clear();
83596
83601
  answers.clear();
@@ -102221,7 +102226,7 @@ function floatOutcomeWords(code2, error, t) {
102221
102226
  case "p03_float_answer_invalid_refund_pending": {
102222
102227
  const amount = amountText(error?.paidAtomic, "the amount paid");
102223
102228
  const fixable = floatSellerCanBeFixed(error);
102224
- const seller = fixable && typeof error?.sellerReason === "string" ? ` ${p.The} refused this request (HTTP ${error.httpStatus}) and said why: ${JSON.stringify(error.sellerReason)}.` : fixable ? ` ${p.The} refused this request (HTTP ${error.httpStatus}) and said why in its first answer; the wallet does not keep ${p.the}'s words.` : typeof error?.sellerReason === "string" ? ` ${p.The} answered HTTP ${error.httpStatus}: ${JSON.stringify(error.sellerReason)}.` : Number.isInteger(error?.httpStatus) ? ` ${p.The} answered HTTP ${error.httpStatus}.` : "";
102229
+ const seller = fixable && error?.sellerReasonHidden === true ? ` ${p.The} refused this request (HTTP ${error.httpStatus}); its own words are not shown, because they can repeat your question.` : fixable && typeof error?.sellerReason === "string" ? ` ${p.The} refused this request (HTTP ${error.httpStatus}) and said why: ${JSON.stringify(error.sellerReason)}.` : fixable ? ` ${p.The} refused this request (HTTP ${error.httpStatus}) and said why in its first answer; the wallet does not keep ${p.the}'s words.` : typeof error?.sellerReason === "string" ? ` ${p.The} answered HTTP ${error.httpStatus}: ${JSON.stringify(error.sellerReason)}.` : Number.isInteger(error?.httpStatus) ? ` ${p.The} answered HTTP ${error.httpStatus}.` : "";
102225
102230
  const late2 = floatChargeTooLate(error) ? ` ${p.The} worked ${error.sellerAnswerSeconds} s before it tried to charge, and its answer says the charge failed. A Solana payment expires about 30 s after it is signed, so no wallet can pay ${p.a} that works this long before it charges.` : "";
102226
102231
  const tryAgain = fixable ? t.ai ? `${p.The} said what is wrong with the request. If ${stillWants(t)} an answer, change the question as it says, then ${t.rerun.toLowerCase()} with ${t.newId}` : `The seller said what is wrong with the request. If ${stillWants(t)} the data, fix the request as the seller says (for example add the query values, headers, or body fields it names), then ${t.rerun.toLowerCase()} with the fixed request and ${t.newId}` : late2 ? t.ai ? `${p.The} works too long before it charges. ${t.ai.other}` : "Do not pay this seller again: it works too long before it charges" : t.ai ? `If ${stillWants(t)} an answer, ${t.askNew}` : `If ${stillWants(t)} the data, try this seller again with ${t.newId}`;
102227
102232
  const why = floatRefundWhy(error, Boolean(t.ai));
@@ -102398,7 +102403,7 @@ function floatOutcomeWords(code2, error, t) {
102398
102403
  return void 0;
102399
102404
  }
102400
102405
  }
102401
- var ATOMIC4, TRANSFER_DEAD_WORDS, payeeOf, floatSellerCanBeFixed, floatSeller4xx, answeredInstead, floatStillSettling, floatChargeTooLate, amountText, withTransaction, PAID_SELLER_FAILED_CODE, stillWants, AI_SERVER_WRONG, QUOTE_REFUSED_WHY;
102406
+ var ATOMIC4, TRANSFER_DEAD_WORDS, payeeOf, floatSellerCanBeFixed, withoutProviderText, floatSeller4xx, answeredInstead, floatStillSettling, floatChargeTooLate, amountText, withTransaction, PAID_SELLER_FAILED_CODE, stillWants, AI_SERVER_WRONG, QUOTE_REFUSED_WHY;
102402
102407
  var init_floatWords = __esm({
102403
102408
  "wallet/src/pay/floatWords.mjs"() {
102404
102409
  "use strict";
@@ -102411,6 +102416,11 @@ var init_floatWords = __esm({
102411
102416
  };
102412
102417
  payeeOf = (ai) => ai ? { the: "the AI provider", The: "The AI provider", this: "the AI provider", a: "an AI provider" } : { the: "the seller", The: "The seller", this: "this seller", a: "a seller" };
102413
102418
  floatSellerCanBeFixed = (error) => floatSeller4xx(error) && !floatChargeTooLate(error) && ![402, 408, 429].includes(error.httpStatus) && (typeof error?.sellerReason === "string" && error.sellerReason.trim() !== "" || error?.sellerFixable === true);
102419
+ withoutProviderText = (facts) => {
102420
+ const { sellerReason, ...rest } = facts ?? {};
102421
+ if (typeof sellerReason !== "string" || !sellerReason.trim()) return rest;
102422
+ return { ...rest, sellerReasonHidden: true, ...floatSellerCanBeFixed(facts) ? { sellerFixable: true } : {} };
102423
+ };
102414
102424
  floatSeller4xx = (error) => Number.isInteger(error?.httpStatus) && error.httpStatus >= 400 && error.httpStatus < 500;
102415
102425
  answeredInstead = (p, status, data) => `${p.the} answered HTTP ${status}${status >= 500 ? " (an error on its side)" : ""} instead of ${data}`;
102416
102426
  floatStillSettling = (code2, error) => code2 === "p03_float_paid_finishing_retry_same_request" && error?.sellerCode === "payment_pending_retry_same_payment";
@@ -103682,10 +103692,10 @@ function markFloatRefundOutcomesShown(store, shown) {
103682
103692
  }
103683
103693
  }
103684
103694
  function toMemoryOnly(store, walName2, record) {
103685
- const { resultBase64, resultSha256: _hash, sellerReason: _reason, ...rest } = record;
103695
+ const { resultBase64, resultSha256: _hash, sellerReason: _reason, sellerHeaders, ...rest } = record;
103686
103696
  const next = { ...rest, fingerprint: memoryOnlyFingerprint(), request: memoryOnlyRequest(record.request), memoryOnly: true };
103687
103697
  store.writeWal(walName2, next);
103688
- if (next.state === "receipt") keepMemoryAnswer(next, resultBase64);
103698
+ if (next.state === "receipt") keepMemoryAnswer(next, resultBase64, void 0, sellerHeaders);
103689
103699
  return next;
103690
103700
  }
103691
103701
  function finishedAtMs(record) {
@@ -104446,7 +104456,7 @@ async function payViaFloat({
104446
104456
  if (record.state === "receipt" && record.memoryOnly === true) {
104447
104457
  const kept3 = memoryAnswer(record);
104448
104458
  if (kept3 === void 0) finished(record);
104449
- return paidResult({ ...record, resultBase64: kept3 }, true);
104459
+ return paidResult({ ...record, resultBase64: kept3, sellerHeaders: memoryAnswerHeaders(record) }, true);
104450
104460
  }
104451
104461
  if (record.state === "receipt") return paidResult(record, true);
104452
104462
  if (record.state === "refund_pending") refundPendingAnswer(record);
@@ -104823,18 +104833,18 @@ async function payViaFloat({
104823
104833
  transaction: isSolanaSignature(settled?.transaction) ? settled.transaction : null,
104824
104834
  network: record.requirement.network
104825
104835
  };
104836
+ const sellerHeaders = keptSellerHeaders(response.headers);
104826
104837
  record = {
104827
104838
  ...record,
104828
104839
  state: "receipt",
104829
104840
  status: response.status,
104830
104841
  receipt,
104831
- ...answerFields(record, resultBase64),
104832
- sellerHeaders: keptSellerHeaders(response.headers),
104842
+ ...answerFields(record, resultBase64, sellerHeaders),
104833
104843
  answeredAtMs: Date.now()
104834
104844
  };
104835
104845
  store.writeWal(walName2, record);
104836
104846
  mark("float-receipt");
104837
- return answered(store, walName2, record, resultBase64);
104847
+ return answered(store, walName2, record, resultBase64, sellerHeaders);
104838
104848
  }
104839
104849
  const sellerReason = await sellerAnswerText(response);
104840
104850
  if (wasKept) keptAgain(response.status, sellerReason);
@@ -105105,18 +105115,18 @@ async function selfBroadcastLeg({ store, walName: walName2, record, sendRequest,
105105
105115
  transaction: isSolanaSignature(settled?.transaction) ? settled.transaction : record.broadcastSignature,
105106
105116
  network: record.requirement.network
105107
105117
  };
105118
+ const sellerHeaders = keptSellerHeaders(response.headers);
105108
105119
  record = {
105109
105120
  ...record,
105110
105121
  state: "receipt",
105111
105122
  status: response.status,
105112
105123
  receipt,
105113
- ...answerFields(record, resultBase64),
105114
- sellerHeaders: keptSellerHeaders(response.headers),
105124
+ ...answerFields(record, resultBase64, sellerHeaders),
105115
105125
  answeredAtMs: Date.now()
105116
105126
  };
105117
105127
  store.writeWal(walName2, record);
105118
105128
  mark("float-receipt");
105119
- return answered(store, walName2, record, resultBase64);
105129
+ return answered(store, walName2, record, resultBase64, sellerHeaders);
105120
105130
  }
105121
105131
  if (response.status >= 500 || response.status === 202) {
105122
105132
  const unansweredReason = await sellerRefusalText(response);
@@ -105188,13 +105198,13 @@ function keptSellerHeaders(headers) {
105188
105198
  }
105189
105199
  return kept3;
105190
105200
  }
105191
- function answerFields(record, resultBase64) {
105192
- return record.memoryOnly === true ? {} : { resultBase64 };
105201
+ function answerFields(record, resultBase64, sellerHeaders) {
105202
+ return record.memoryOnly === true ? {} : { resultBase64, sellerHeaders };
105193
105203
  }
105194
- function answered(store, walName2, record, resultBase64) {
105204
+ function answered(store, walName2, record, resultBase64, sellerHeaders) {
105195
105205
  if (record.memoryOnly !== true) return paidResult(record, false);
105196
- keepMemoryAnswer(record, resultBase64);
105197
- return paidResult({ ...record, resultBase64 }, false);
105206
+ keepMemoryAnswer(record, resultBase64, void 0, sellerHeaders);
105207
+ return paidResult({ ...record, resultBase64, sellerHeaders }, false);
105198
105208
  }
105199
105209
  function paidResult(record, replayed) {
105200
105210
  return {
@@ -105288,7 +105298,7 @@ var init_floatPay = __esm({
105288
105298
  floatPaymentWalName = (requestKey) => `${FLOAT_PAYMENT_WAL_PREFIX2}${requestKey}`;
105289
105299
  floatRecordName = (requestId) => floatPaymentWalName(hash7(requestId));
105290
105300
  FLOAT_KEEP_BYTES_MS = 6e5;
105291
- memoryOnlyNeedsScrub = (record) => record?.memoryOnly === true && (record.resultSha256 !== void 0 || record.request?.bodySha256 !== void 0 || record.request?.headersSha256 !== void 0 || !/^memory-[0-9a-f]{64}$/.test(String(record.fingerprint ?? "")));
105301
+ memoryOnlyNeedsScrub = (record) => record?.memoryOnly === true && (record.resultSha256 !== void 0 || Array.isArray(record.sellerHeaders) && record.sellerHeaders.length > 0 || record.request?.bodySha256 !== void 0 || record.request?.headersSha256 !== void 0 || !/^memory-[0-9a-f]{64}$/.test(String(record.fingerprint ?? "")));
105292
105302
  FLOAT_KEEP_OPEN_REQUEST_MS = 36e5;
105293
105303
  configCache = /* @__PURE__ */ new Map();
105294
105304
  configGeneration = 0;
@@ -109497,6 +109507,16 @@ function refusalSay(a, tool, again, npmVersion) {
109497
109507
  const head = `${paid ? `Paid ${paid} USDC${price2 && positive(fee) ? ` (${price2} price + ${fee} Shinjuku fee)` : ""}` : "Paid"}, but`;
109498
109508
  return firstFit(`${head} the model reached the answer's length limit before it answered.${more}`, `${head} the answer hit its length limit.${more}`);
109499
109509
  }
109510
+ if (code2 === "inference_empty_answer") {
109511
+ const again2 = " Ask again (it pays again) or pick another model.";
109512
+ if (a.replayed === true && a.paid === false) return `Nothing was paid, and the stored answer is empty.${again2}`;
109513
+ if (a.replayed === true) return `Already paid; the stored answer is empty and nothing new was paid.${again2}`;
109514
+ const paid = usdcText2(a.paidAtomic);
109515
+ const price2 = usdcText2(a.priceAtomic);
109516
+ const fee = usdcText2(a.feeAtomic);
109517
+ const head = `${paid ? `Paid ${paid} USDC${price2 && positive(fee) ? ` (${price2} price + ${fee} Shinjuku fee)` : ""}` : "Paid"}, but`;
109518
+ return firstFit(`${head} the AI returned no answer, and no refund comes.${again2}`, `${head} the AI returned no answer.${again2}`);
109519
+ }
109500
109520
  if (code2 === "inference_no_provider") {
109501
109521
  return "Nothing was paid: no AI provider can take this wallet's payment for this model right now. Ask for another model, or ask again later.";
109502
109522
  }
@@ -110953,8 +110973,8 @@ async function offeredPrice(paymentRequired, store, serviceUrl2, target, timing,
110953
110973
  }
110954
110974
  return { rail, amountAtomic: highest.toString(), priceAtomic: highest.toString() };
110955
110975
  }
110956
- function ownRecord(store, requestId) {
110957
- const float = floatRecordOf(store, requestId);
110976
+ function ownRecord(store, requestId, { strict = false } = {}) {
110977
+ const float = strict ? store.readWal(floatRecordName(requestId)) : floatRecordOf(store, requestId);
110958
110978
  if (float) return { kind: "float", record: float };
110959
110979
  const key = sha256Hex2(Buffer.from(requestId, "utf8"));
110960
110980
  const shielded = store.readWal(`resource-payment-${key}`);
@@ -111492,6 +111512,23 @@ async function paidCall({ session, target, method, headers, body, cap, maxPrice,
111492
111512
  }
111493
111513
  mark("fetch-ready");
111494
111514
  const fresh = challenge !== void 0 && Date.now() - challenge.atMs <= PREFETCH_MAX_AGE_MS;
111515
+ const leftDecision = () => {
111516
+ let left = true;
111517
+ let unreadable = false;
111518
+ let after;
111519
+ try {
111520
+ after = ownRecord(store, requestId, { strict: true });
111521
+ left = after === void 0 ? own !== void 0 : committed(after, store) && !closedUnpaid(after, store) && !preparedOnly(after, store);
111522
+ } catch {
111523
+ after = void 0;
111524
+ unreadable = true;
111525
+ }
111526
+ const atomic4 = left ? (after === void 0 ? void 0 : paidAmount(after) ?? price2?.amountAtomic) ?? String(cap) : void 0;
111527
+ return Object.freeze({ left, atomic: atomic4, unreadable, before: leftBefore });
111528
+ };
111529
+ const recordLeft = (decision) => {
111530
+ if (policy && decision.left) policy.record({ amountAtomic: earlierRun ? "0" : decision.atomic, url: target, requestId });
111531
+ };
111495
111532
  const result = await payResource({
111496
111533
  store,
111497
111534
  connection: session.connection,
@@ -111535,19 +111572,54 @@ async function paidCall({ session, target, method, headers, body, cap, maxPrice,
111535
111572
  onCompactList: memoPrefetchOnList(session, store)
111536
111573
  })
111537
111574
  }
111538
- }).finally(markMade);
111539
- const amountAtomic = paidAmount(ownRecord(store, requestId)) ?? price2?.amountAtomic;
111575
+ }).finally(markMade).catch((error) => {
111576
+ const failure2 = error !== null && typeof error === "object" ? error : Object.assign(new Error(String(error)), { cause: error });
111577
+ try {
111578
+ const decision = leftDecision();
111579
+ try {
111580
+ failure2.paidLeft = decision;
111581
+ } catch {
111582
+ }
111583
+ recordLeft(decision);
111584
+ } catch {
111585
+ }
111586
+ throw failure2;
111587
+ });
111588
+ let ownAfter;
111589
+ let afterUnreadable = false;
111590
+ try {
111591
+ ownAfter = ownRecord(store, requestId, { strict: true });
111592
+ } catch {
111593
+ ownAfter = void 0;
111594
+ afterUnreadable = true;
111595
+ }
111596
+ const resultAtomic = POSITIVE_ATOMIC4.test(String(result.paidAtomic ?? "")) ? String(result.paidAtomic) : void 0;
111597
+ const amountAtomic = paidAmount(ownAfter) ?? resultAtomic ?? price2?.amountAtomic ?? (result.replayed ? void 0 : String(cap));
111540
111598
  if (!result.replayed && policy && amountAtomic) policy.record({ amountAtomic: earlierRun ? "0" : amountAtomic, url: target, requestId });
111541
- const bytes2 = Buffer.from(result.resultBase64 ?? "", "base64");
111542
- const payment = {
111543
- transaction: result.receipt?.transaction,
111544
- scheme: result.scheme === "exact" ? "exact" : "shielded-exact",
111545
- amountAtomic: amountAtomic ?? null,
111546
- requestId,
111547
- replayed: result.replayed === true,
111548
- receiptFinality: result.receiptFinality ?? null
111549
- };
111550
- return new Response(bytes2, { status: 200, headers: paidResponseHeaders(result.sellerHeaders, sniffContentType(bytes2), payment) });
111599
+ try {
111600
+ const bytes2 = Buffer.from(result.resultBase64 ?? "", "base64");
111601
+ const payment = {
111602
+ transaction: result.receipt?.transaction,
111603
+ scheme: result.scheme === "exact" ? "exact" : "shielded-exact",
111604
+ amountAtomic: amountAtomic ?? null,
111605
+ requestId,
111606
+ replayed: result.replayed === true,
111607
+ receiptFinality: result.receiptFinality ?? null
111608
+ };
111609
+ return new Response(bytes2, { status: 200, headers: paidResponseHeaders(result.sellerHeaders, sniffContentType(bytes2), payment) });
111610
+ } catch (error) {
111611
+ const failure2 = error !== null && typeof error === "object" ? error : Object.assign(new Error(String(error)), { cause: error });
111612
+ try {
111613
+ failure2.paidLeft = Object.freeze({
111614
+ left: true,
111615
+ atomic: amountAtomic ?? String(cap),
111616
+ unreadable: afterUnreadable,
111617
+ before: leftBefore || result.replayed === true
111618
+ });
111619
+ } catch {
111620
+ }
111621
+ throw failure2;
111622
+ }
111551
111623
  } finally {
111552
111624
  await release();
111553
111625
  }
@@ -139048,6 +139120,21 @@ ${canonical5}`;
139048
139120
  "inference_chat"
139049
139121
  )), paidStore);
139050
139122
  }
139123
+ if (typeof content === "string" && !content.trim()) {
139124
+ const paidText = usdcText3(paidAtomic ?? "");
139125
+ const reasonLogged = result.finishReason === void 0 || result.finishReason === null ? "none" : ["stop", "length", "content_filter", "tool_calls", "function_call"].includes(result.finishReason) ? result.finishReason : "other";
139126
+ log(`MCP_INFERENCE_EMPTY requestId=${requestId} finishReason=${reasonLogged}`);
139127
+ return withRefundNotices(json3(sayFirst(
139128
+ {
139129
+ ok: false,
139130
+ error: "inference_empty_answer",
139131
+ detail: `the AI provider returned an empty answer (finish reason ${JSON.stringify(result.finishReason ?? null)}); ` + (unpaid ? "nothing was paid: this request id's payment never landed (the pool's public history proves it)" : `this call was paid${paidText ? ` (${paidText} USDC)` : ""}${transaction ? ` in transaction ${transaction}` : ""} and no refund comes`),
139132
+ ...paidFields,
139133
+ next: unpaid ? "nothing was paid, and this request id's stored answer is empty. If the user still wants an answer, call inference_chat with a NEW request_id, or another model; it pays then" : receipt.replayed === true ? "this request id was already paid and its stored answer is empty; nothing new was paid. If the user still wants an answer, call inference_chat with a NEW request_id, or another model; it pays again" : `Tell the user: the AI returned no answer, and this call paid ${paidText ? `${paidText} USDC` : "(see paidAtomic)"}; no refund comes. If the user still wants an answer, call inference_chat again with a NEW request_id (reword the question, or pick another model); it pays again. The same request_id returns this same empty answer`
139134
+ },
139135
+ "inference_chat"
139136
+ )), paidStore);
139137
+ }
139051
139138
  return withRefundNotices(json3(sayFirst({
139052
139139
  ok: true,
139053
139140
  ...paidFields,
@@ -145157,7 +145244,8 @@ async function runChat({ io, values, deps = {} }) {
145157
145244
  empty: 0,
145158
145245
  pending: void 0,
145159
145246
  busy: false,
145160
- interrupts: 0
145247
+ interrupts: 0,
145248
+ epoch: 0
145161
145249
  };
145162
145250
  const servedNow = catalogNow;
145163
145251
  servedNow();
@@ -145532,8 +145620,15 @@ ${costLine2(picked.entry.id)}.` + (start ? "" : `
145532
145620
  The next question uses it${state.mode === "talk" ? ", with the conversation so far" : ""}.`);
145533
145621
  };
145534
145622
  let reader;
145623
+ const warnPaidPending = (how) => {
145624
+ if (!state.pending || !state.unsure.has(state.pending) || state.dropWarned === state.pending) return false;
145625
+ state.dropWarned = state.pending;
145626
+ say3(`An answer you may have paid for (${usdcWords(state.unsure.get(state.pending))}) is not finished. Type /retry to finish it: it never pays twice. Or ${how}.`);
145627
+ return true;
145628
+ };
145535
145629
  const onInterrupt = () => {
145536
145630
  if (!state.busy) {
145631
+ if (warnPaidPending("press Ctrl+C again to end the chat and drop it")) return;
145537
145632
  reader?.close();
145538
145633
  return;
145539
145634
  }
@@ -145746,6 +145841,7 @@ When it says ready, type your message here.`);
145746
145841
  state.interrupts = 0;
145747
145842
  }
145748
145843
  state.pending = void 0;
145844
+ const wasUnsure = state.unsure.get(turn);
145749
145845
  state.unsure.delete(turn);
145750
145846
  const replayed = result.receipt?.replayed === true;
145751
145847
  let unpaid = false;
@@ -145756,6 +145852,7 @@ When it says ready, type your message here.`);
145756
145852
  }
145757
145853
  const paid = unpaid ? 0n : typeof result.paidAtomic === "bigint" ? result.paidAtomic : void 0;
145758
145854
  if (paid !== void 0) state.spent += paid;
145855
+ else if (wasUnsure !== void 0 && !unpaid) state.spent += wasUnsure;
145759
145856
  const content = typeof result.content === "string" ? result.content : "";
145760
145857
  const paidText = unpaid ? "nothing was paid: the earlier payment for this answer never went through" : paid === void 0 ? "paid (the amount was not reported)" : replayed ? `already paid ${usdcWords(paid)} earlier for this answer; nothing new was paid` : `paid ${usdcWords(paid)}`;
145761
145858
  const dayRoom2 = dayRoomSentence();
@@ -145767,7 +145864,7 @@ When it says ready, type your message here.`);
145767
145864
  return;
145768
145865
  }
145769
145866
  state.answers += 1;
145770
- if (state.mode === "talk") state.history = [...turn.messages, { role: "assistant", content }];
145867
+ if (state.mode === "talk" && (turn.epoch ?? 0) === state.epoch) state.history = [...turn.messages, { role: "assistant", content }];
145771
145868
  io.out(`${fitColumns(
145772
145869
  content,
145773
145870
  process.stdout.isTTY ? process.stdout.columns : 0,
@@ -145788,28 +145885,45 @@ When it says ready, type your message here.`);
145788
145885
  const failed = async (error, turn) => {
145789
145886
  const code2 = errorCodeOf(error);
145790
145887
  const surelyNothing = nothingPaid(error);
145888
+ const paidLeft = [error, error?.cause].map((one) => one?.paidLeft).find((one) => one && typeof one === "object" && typeof one.left === "boolean");
145889
+ const leftAtomic = paidLeft?.left === true && /^[1-9][0-9]{0,30}$/.test(String(paidLeft.atomic ?? "")) ? BigInt(paidLeft.atomic) : void 0;
145791
145890
  const paidAtomic = error?.details?.receipt ? error.details.paidAtomic : void 0;
145891
+ const wasUnsure = state.unsure.get(turn);
145792
145892
  state.pending = void 0;
145793
145893
  if (error?.details?.receipt) {
145894
+ state.unsure.delete(turn);
145794
145895
  if (typeof paidAtomic === "bigint") state.spent += paidAtomic;
145896
+ else if (wasUnsure !== void 0) state.spent += wasUnsure;
145795
145897
  state.empty += 1;
145796
145898
  say3(`This answer was paid${typeof paidAtomic === "bigint" ? ` (${usdcWords(paidAtomic)})` : ""}, but no usable answer came back from the AI provider. A new question pays again: ask again, or /model to try another model (${modelIds2}). This chat paid ${paidSoFar()} so far.`);
145797
145899
  return;
145798
145900
  }
145799
145901
  if (code2 === "p03_resource_payment_answer_not_kept") {
145902
+ const earlier = error?.earlierPaymentAtomic ?? error?.cause?.earlierPaymentAtomic;
145903
+ state.unsure.delete(turn);
145904
+ if (/^[1-9][0-9]{0,30}$/.test(String(earlier ?? ""))) state.spent += BigInt(earlier);
145905
+ else if (wasUnsure !== void 0) state.spent += wasUnsure;
145800
145906
  state.empty += 1;
145801
145907
  say3(`${answerNotKeptWords(error?.earlierPaymentAtomic ?? error?.cause?.earlierPaymentAtomic)} This chat paid ${paidSoFar()} so far.`);
145802
145908
  return;
145803
145909
  }
145804
- if (/another_request_unresolved$/.test(code2 ?? "") && (error?.memoryOnlyOpen ?? error?.cause?.memoryOnlyOpen) === true) {
145910
+ if (paidLeft?.left !== true && /another_request_unresolved$/.test(code2 ?? "") && (error?.memoryOnlyOpen ?? error?.cause?.memoryOnlyOpen) === true) {
145805
145911
  say3(memoryOnlyOpenWords(error?.closesAtUnix ?? error?.cause?.closesAtUnix));
145806
145912
  return;
145807
145913
  }
145808
145914
  if (code2 === "fetch_proof_tools_unavailable") {
145915
+ const earlier = error?.earlierPaymentAtomic ?? error?.cause?.earlierPaymentAtomic;
145916
+ const heldAtomic = /^[1-9][0-9]{0,30}$/.test(String(earlier ?? "")) ? BigInt(earlier) : leftAtomic ?? wasUnsure;
145917
+ if (heldAtomic !== void 0) {
145918
+ state.unsure.set(turn, heldAtomic);
145919
+ state.pending = turn;
145920
+ say3(`This wallet is missing some setup files, so this try sent nothing and paid nothing new. The earlier payment for this answer (${usdcWords(heldAtomic)}) may be paid already: type /retry to try again (it never pays twice). If you end the chat to run ${commandLine("setup")}, the wallet closes that payment by itself: if it never reached the AI provider, the money comes back.`);
145921
+ return;
145922
+ }
145809
145923
  say3(`This wallet is missing some setup files, so nothing was paid. End the chat (/exit), run ${commandLine("setup")} to install them, then start the chat again: ${commandLine("chat")}`);
145810
145924
  return;
145811
145925
  }
145812
- if (localErrorCodes(error).includes(SPLIT_CODE)) {
145926
+ if (paidLeft?.left !== true && localErrorCodes(error).includes(SPLIT_CODE)) {
145813
145927
  state.pending = turn;
145814
145928
  const why = joinOutcome === "stale" ? "the wallet could not read your balance again just now" : joinOutcome === "unfinished" ? "the wallet did not finish getting it ready in this turn" : joinOutcome === "files" ? void 0 : "part of it is not ready to spend yet (another payment of this wallet, or its change, is not final yet)";
145815
145929
  say3(why === void 0 ? `Your private balance covers this answer in total, but not yet in one payment. Nothing was paid. Get it ready in another terminal: ${commandLine("prepare")}, then type /retry: it asks the same question again.` : `Your private balance covers this answer in total, but ${why}. Nothing was paid. Type /retry in a minute: it gets the balance ready by itself when it needs to, and never pays twice.`);
@@ -145861,14 +145975,36 @@ When it says ready, type /retry: it asks the same question again.` : receivedOnl
145861
145975
  say3(held2 !== void 0 && facts.preparedNotSent === true ? `Nothing was paid: a payment of ${usdcWords(held2)} is prepared for an earlier try of this question and not sent, and the wallet never pays twice for one question. This chat cannot send it. ${closeFirst}` : held2 !== void 0 ? `This question already has a payment of ${usdcWords(held2)} from an earlier try, made with other details, and the wallet never pays twice for one question; nothing more was paid. This chat cannot finish that payment, and the same question typed again is a new question that pays again.` : open4 ? `Nothing was paid: an earlier try of this question was made with other details and is not finished; nothing of it was sent, and it blocks every new question until it is closed. ${closeFirst}` : "Nothing was paid: an earlier try of this question was made with other details. If you still want an answer, type the question again: it goes out as a new question.");
145862
145976
  return;
145863
145977
  }
145864
- const float = typeof code2 === "string" && !CAP_CODES.test(code2) ? floatOutcomeWords(code2, facts, chatFloatTerms()) : void 0;
145978
+ const float = typeof code2 === "string" && !CAP_CODES.test(code2) ? floatOutcomeWords(code2, withoutProviderText(facts), chatFloatTerms()) : void 0;
145865
145979
  if (float) {
145866
- const spent = floatSpent(code2, facts) && /^[1-9][0-9]{0,30}$/.test(String(facts.paidAtomic ?? ""));
145867
- if (spent) state.spent += BigInt(facts.paidAtomic);
145868
- if (["p03_float_seller_unanswered_retry_same_request", "p03_float_seller_refused_after_payment", "p03_float_outcome_not_certain"].includes(code2) && /^[1-9][0-9]{0,30}$/.test(String(facts.paidAtomic ?? ""))) {
145869
- state.unsure.set(turn, BigInt(facts.paidAtomic));
145870
- } else state.unsure.delete(turn);
145871
- if (floatSpent(code2, facts)) state.empty += 1;
145980
+ const keptAmount = () => {
145981
+ try {
145982
+ const kept3 = keptPayment(session.openStore(), turn.requestId);
145983
+ return kept3?.committed === true && kept3.cancelled !== true && /^[1-9][0-9]{0,30}$/.test(String(kept3.amountAtomic ?? "")) ? BigInt(kept3.amountAtomic) : void 0;
145984
+ } catch {
145985
+ return typeof turn.capAtomic === "bigint" && turn.capAtomic > 0n ? turn.capAtomic : void 0;
145986
+ }
145987
+ };
145988
+ const amount = /^[1-9][0-9]{0,30}$/.test(String(facts.paidAtomic ?? "")) ? BigInt(facts.paidAtomic) : paidLeft ? leftAtomic : FLOAT_NOT_PAID.has(code2) ? void 0 : keptAmount();
145989
+ const paidForSure = floatSpent(code2, facts) || code2 === PAID_SELLER_FAILED_CODE;
145990
+ const spent = paidForSure && (amount !== void 0 || wasUnsure !== void 0);
145991
+ if (paidLeft?.left === true && !paidForSure && (FLOAT_NOT_PAID.has(code2) || NOTHING_PAID.test(`${float[1]} ${float[2]}`))) {
145992
+ const held3 = amount ?? leftAtomic ?? (typeof turn.capAtomic === "bigint" && turn.capAtomic > 0n ? turn.capAtomic : void 0);
145993
+ if (held3 !== void 0) state.unsure.set(turn, held3);
145994
+ state.pending = turn;
145995
+ try {
145996
+ (typeof session.timing === "function" ? session.timing() : void 0)?.("chat-answer-failed", code2);
145997
+ } catch {
145998
+ }
145999
+ say3(`The answer did not come back, and the wallet cannot prove this payment${held3 !== void 0 ? ` (${usdcWords(held3)})` : ""} stayed unpaid: it may be paid already. Type /retry to finish it (it never pays twice). This chat counts it until it is certain.`);
146000
+ return;
146001
+ }
146002
+ if (paidForSure) {
146003
+ state.unsure.delete(turn);
146004
+ if (spent) state.spent += amount ?? wasUnsure;
146005
+ } else if (FLOAT_NOT_PAID.has(code2)) state.unsure.delete(turn);
146006
+ else if (amount !== void 0) state.unsure.set(turn, amount);
146007
+ if (paidForSure) state.empty += 1;
145872
146008
  if (floatRetry(code2, facts)) state.pending = turn;
145873
146009
  try {
145874
146010
  const mark = typeof session.timing === "function" ? session.timing() : void 0;
@@ -145881,17 +146017,33 @@ When it says ready, type /retry: it asks the same question again.` : receivedOnl
145881
146017
  }
145882
146018
  let prepared;
145883
146019
  let neverLeft = false;
145884
- if (!surelyNothing) {
146020
+ let keptAtomic;
146021
+ if (paidLeft) {
146022
+ if (paidLeft.left) keptAtomic = leftAtomic ?? (typeof turn.capAtomic === "bigint" && turn.capAtomic > 0n ? turn.capAtomic : void 0);
146023
+ else {
146024
+ let kept3;
146025
+ try {
146026
+ kept3 = keptPayment(session.openStore(), turn.requestId);
146027
+ } catch {
146028
+ kept3 = void 0;
146029
+ }
146030
+ if (kept3?.prepared === true) prepared = /^[1-9][0-9]{0,30}$/.test(String(kept3.amountAtomic ?? "")) ? ` of ${usdcWords(BigInt(kept3.amountAtomic))}` : "";
146031
+ else neverLeft = true;
146032
+ }
146033
+ } else if (!surelyNothing) {
145885
146034
  try {
145886
146035
  const kept3 = keptPayment(session.openStore(), turn.requestId);
146036
+ if (/^[1-9][0-9]{0,30}$/.test(String(kept3?.amountAtomic ?? ""))) keptAtomic = BigInt(kept3.amountAtomic);
145887
146037
  if (kept3 === void 0 || kept3.committed !== true || kept3.cancelled === true) neverLeft = true;
145888
- else if (kept3.prepared === true) prepared = /^[1-9][0-9]{0,30}$/.test(String(kept3.amountAtomic ?? "")) ? ` of ${usdcWords(BigInt(kept3.amountAtomic))}` : "";
146038
+ else if (kept3.prepared === true) prepared = keptAtomic !== void 0 ? ` of ${usdcWords(keptAtomic)}` : "";
145889
146039
  } catch {
145890
146040
  prepared = void 0;
145891
146041
  neverLeft = false;
146042
+ if (typeof turn.capAtomic === "bigint" && turn.capAtomic > 0n) keptAtomic = turn.capAtomic;
145892
146043
  }
145893
146044
  }
145894
- const nothing = surelyNothing || neverLeft;
146045
+ const nothing = paidLeft ? neverLeft : surelyNothing || neverLeft;
146046
+ if (!nothing && prepared === void 0 && keptAtomic !== void 0) state.unsure.set(turn, keptAtomic);
145895
146047
  const again = nothing ? "ask again" : prepared !== void 0 ? "type /retry (it sends that same payment, never a second one)" : "type /retry (it finishes the same payment and never pays twice)";
145896
146048
  if (!nothing) state.pending = turn;
145897
146049
  const paidWords = nothing ? "Nothing was paid." : prepared !== void 0 ? `Nothing was paid: a payment${prepared} is prepared for this question and not sent yet.` : "It may be paid already.";
@@ -145933,8 +146085,10 @@ When it says ready, type /retry: it asks the same question again.` : receivedOnl
145933
146085
  const command = async (text3) => {
145934
146086
  const [name, ...rest] = text3.split(/\s+/);
145935
146087
  const arg = rest.join(" ").trim();
145936
- if (name === "/exit" || name === "/quit" || name === "/q") return true;
146088
+ if (name === "/exit" || name === "/quit" || name === "/q") return !warnPaidPending(`${name} again ends the chat and drops it`);
145937
146089
  if (name === "/new") {
146090
+ if (warnPaidPending("/new again starts a new conversation and drops it")) return false;
146091
+ state.epoch += 1;
145938
146092
  state.history = [];
145939
146093
  state.pending = void 0;
145940
146094
  forgetMemoryOnly();
@@ -145948,10 +146102,10 @@ When it says ready, type /retry: it asks the same question again.` : receivedOnl
145948
146102
  if (!next) say3("Type /mode 1 for one question at a time, or /mode 2 for a conversation.");
145949
146103
  else {
145950
146104
  state.mode = next;
146105
+ state.epoch += 1;
145951
146106
  state.history = [];
145952
- state.pending = void 0;
145953
- forgetMemoryOnly();
145954
- say3(next === "each" ? "Mode now: one question at a time. Each question goes alone, and the chat names each payment." : "Mode now: a conversation. The AI remembers from your next question on, and the running total shows beside you.");
146107
+ if (!state.pending) forgetMemoryOnly();
146108
+ say3((next === "each" ? "Mode now: one question at a time. Each question goes alone, and the chat names each payment." : "Mode now: a conversation. The AI remembers from your next question on, and the running total shows beside you.") + (state.pending ? " Your unfinished answer is kept: /retry finishes it." : ""));
145955
146109
  }
145956
146110
  } else if (name === "/model" || name === "/provider") {
145957
146111
  const now = await servedNow();
@@ -146040,6 +146194,7 @@ When it says ready, type /retry: it asks the same question again.` : receivedOnl
146040
146194
  if (text3.startsWith("/")) {
146041
146195
  if (await command(text3)) break;
146042
146196
  } else {
146197
+ if (warnPaidPending("type your new question again to drop it and ask the new one")) continue;
146043
146198
  if (state.pending) say3("(The unfinished answer is dropped; this is a new question.)");
146044
146199
  state.pending = void 0;
146045
146200
  const now = await servedNow();
@@ -146051,12 +146206,16 @@ When it says ready, type /retry: it asks the same question again.` : receivedOnl
146051
146206
  requestId: `chat-${randomBytes29(8).toString("hex")}`,
146052
146207
  model: state.model,
146053
146208
  liveEntry: liveOf(state.model),
146209
+ epoch: state.epoch,
146054
146210
  messages: state.mode === "each" ? [{ role: "user", content: text3 }] : [...state.history, { role: "user", content: text3 }],
146055
146211
  capAtomic: void 0
146056
146212
  });
146057
146213
  }
146058
146214
  }
146059
146215
  }
146216
+ if (state.pending && state.unsure.has(state.pending)) {
146217
+ say3(`An answer you may have paid for (${usdcWords(state.unsure.get(state.pending))}) was not finished, and the chat keeps no copy of it. The wallet closes that payment by itself: if it never reached the AI provider, the money comes back to your private balance; if it did, your next private question tells you that it was paid.`);
146218
+ }
146060
146219
  say3([
146061
146220
  `Chat ended. It paid ${paidSoFar()} for ${questionsWords()}. The conversation is gone from memory.`,
146062
146221
  ...chatEndLines({ color, columns: process.stderr.columns ?? 0 })
@@ -146069,7 +146228,7 @@ When it says ready, type /retry: it asks the same question again.` : receivedOnl
146069
146228
  await session.close();
146070
146229
  }
146071
146230
  }
146072
- var SPLIT_CODE, DEFAULT_MODEL2, CHAT_DEFAULT_MODEL, CHAT_MAX_TOKENS, CHAT_SHORT_TOKENS, DEFAULT_ANSWER_CAP_ATOMIC, DEFAULT_CHAT_CAP_ATOMIC, CHEAPEST_MODEL, PICK_PAGE, PICK_MOST_BROWSED, PICK_MATCHES, PICK_COLUMNS, PICK_ID_COLUMNS, PICK_NAME_COLUMNS, STILL_WAITING_MS, higherCap, shortInChat, POSITIVE_ATOMIC9, SHORT_QUESTION, SHORT_QUESTION_TOKENS, PASTE_JOIN_MS, CAP_CODES, WAIT_CODES, NO_ANSWER_CODES, HISTORY_CODES, chatFloatTerms, floatRetry, floatSpent, asSentence, upFirst, lowFirst, NOTHING_PAID, CAP_FROM, PROGRESS_LINE, SHOWN_PROGRESS, isProgressLine, fail46, nothingPaid;
146231
+ var SPLIT_CODE, DEFAULT_MODEL2, CHAT_DEFAULT_MODEL, CHAT_MAX_TOKENS, CHAT_SHORT_TOKENS, DEFAULT_ANSWER_CAP_ATOMIC, DEFAULT_CHAT_CAP_ATOMIC, CHEAPEST_MODEL, PICK_PAGE, PICK_MOST_BROWSED, PICK_MATCHES, PICK_COLUMNS, PICK_ID_COLUMNS, PICK_NAME_COLUMNS, STILL_WAITING_MS, higherCap, shortInChat, POSITIVE_ATOMIC9, SHORT_QUESTION, SHORT_QUESTION_TOKENS, PASTE_JOIN_MS, CAP_CODES, WAIT_CODES, NO_ANSWER_CODES, HISTORY_CODES, chatFloatTerms, floatRetry, floatSpent, FLOAT_NOT_PAID, asSentence, upFirst, lowFirst, NOTHING_PAID, CAP_FROM, PROGRESS_LINE, SHOWN_PROGRESS, isProgressLine, fail46, nothingPaid;
146073
146232
  var init_chat = __esm({
146074
146233
  "wallet/src/inference/chat.mjs"() {
146075
146234
  "use strict";
@@ -146151,6 +146310,7 @@ var init_chat = __esm({
146151
146310
  });
146152
146311
  floatRetry = (code2, facts) => /_retry_same_request$|_retry_later$/.test(code2) || ["p03_float_already_paid", "p03_float_config_unreadable"].includes(code2) || code2 === "p03_float_request_refused" && facts.floatCode === "float_quote_in_progress" || code2 === "p03_float_seller_kept_payment" && facts.resent !== true && facts.pruned !== true && facts.httpStatus !== 402;
146153
146312
  floatSpent = (code2, facts) => code2 === "p03_float_receipt_pruned" || code2 === "p03_float_seller_kept_payment" && !floatRetry(code2, facts);
146313
+ FLOAT_NOT_PAID = /* @__PURE__ */ new Set(["p03_float_request_expired_nothing_paid"]);
146154
146314
  asSentence = (text3) => `${text3.charAt(0).toUpperCase()}${text3.slice(1).replace(/[.;\s]+$/, "")}.`;
146155
146315
  upFirst = (text3) => `${text3.charAt(0).toUpperCase()}${text3.slice(1)}`;
146156
146316
  lowFirst = (text3) => `${text3.charAt(0).toLowerCase()}${text3.slice(1)}`;
@@ -153398,7 +153558,7 @@ var INFER_MODEL_IDS = ["claude-opus-5", "deepseek-v4-1-flash", "qwen3-8-flash"];
153398
153558
  var INFER_EXAMPLE_TAIL = `--max-tokens 512 --max-payment 0.02 --max-session 0.1 ${PAYER_SESSION_BASE} --tor`;
153399
153559
  var INFER_COST_LINE = "The AI provider charges its quoted cap for --max-tokens a request (often far below 0.01 USDC; 0.01 USDC on its standard offer, so --max-payment must cover that) and keeps what the answer does not use (no refund of the rest); the wallet pays the AI provider's price plus Shinjuku's fee (Shinjuku pays the AI provider for you), never more than --max-payment a request; the AI provider and the company that runs the model see your prompt and the answer. The chain shows one private payment, not who paid";
153400
153560
  var INFER_STDIN_MAX_BYTES = 1024 * 1024;
153401
- var INFER_STDIN_EXAMPLE = `echo "Summarize x402 in one line" | ${CLI_WORD} infer - --model qwen3-8-flash`;
153561
+ var INFER_STDIN_EXAMPLE = `cat question.txt | ${CLI_WORD} infer -`;
153402
153562
  var CURL_USEPOD_NEXT = `use \`${CLI_WORD} infer\` for the AI provider: it sends the prompt only through --tor or --proxy`;
153403
153563
  function unshieldArgs(command, args) {
153404
153564
  if (args.includes("--amount")) return args;
@@ -153429,6 +153589,7 @@ function inferArgs(command, args) {
153429
153589
  index += 1;
153430
153590
  }
153431
153591
  }
153592
+ if (!out.includes("--prompt") && !out.includes("--help") && !out.includes("-h") && process.stdin.isTTY) out.push("--prompt", "-");
153432
153593
  return out;
153433
153594
  }
153434
153595
  function inferTorGate(env) {
@@ -153448,7 +153609,7 @@ function validateInfer(values, env) {
153448
153609
  }
153449
153610
  if (values.has("--max-tokens") && Number(values.get("--max-tokens")) < 1) throw new UsageError("--max-tokens must be at least 1 (give 256 or more, 512 answers a short question: a reasoning model like qwen3-8-flash thinks first, and with fewer tokens it can stop before it answers, still paid): it sets the most the request can cost");
153450
153611
  if (!values.get("--prompt").trim()) {
153451
- throw new UsageError(`the prompt is empty: pipe it in and write - in its place (private: not in the process list or shell history), like ${INFER_STDIN_EXAMPLE}`);
153612
+ throw new UsageError(`the prompt is empty: run ${CLI_WORD} infer alone in a terminal and type the question when it asks, or pipe it in from a file, like ${INFER_STDIN_EXAMPLE}`);
153452
153613
  }
153453
153614
  if (!values.has(WALLET_FROM_SETUP) && (values.has("--pool") || values.has("--wallet"))) inferTorGate(env);
153454
153615
  }
@@ -153484,6 +153645,22 @@ async function readStdinBytes({ maxBytes }) {
153484
153645
  async function inferPrompt(values, deps) {
153485
153646
  const given = values.get("--prompt");
153486
153647
  if (given !== "-") return given;
153648
+ if (!deps.readStdin && process.stdin.isTTY) {
153649
+ const asker = createInterface2({ input: process.stdin, output: process.stderr, terminal: true });
153650
+ let line;
153651
+ try {
153652
+ line = await new Promise((resolveLine) => {
153653
+ asker.once("close", () => resolveLine(null));
153654
+ asker.question("Type your question, then press Enter: ", resolveLine);
153655
+ });
153656
+ } finally {
153657
+ asker.close();
153658
+ }
153659
+ if (line === null || !String(line).trim()) {
153660
+ throw new UsageError(`no question was typed, so nothing was sent or paid. Run ${cli4()} infer again and type your question, or use ${cli4()} chat`);
153661
+ }
153662
+ return String(line);
153663
+ }
153487
153664
  const raw = await (deps.readStdin ?? readStdinBytes)({ maxBytes: INFER_STDIN_MAX_BYTES });
153488
153665
  const bytes2 = typeof raw === "string" ? Buffer.from(raw, "utf8") : Buffer.from(raw ?? []);
153489
153666
  if (bytes2.length > INFER_STDIN_MAX_BYTES) {
@@ -153643,10 +153820,13 @@ function inferOutput(result, { values, io, model: model2, quiet, requestId, held
153643
153820
  ` : `paid ${paidText}${quiet ? "" : ` request-id ${result.requestId ?? requestId}`}${stopped}
153644
153821
  `);
153645
153822
  }
153823
+ const empty = !String(result.content ?? "").trim();
153824
+ const emptyWords = result.finishReason === "length" ? `The model used all its tokens before its answer started, so no answer came back (${replayed || held ? "paid earlier" : `paid ${paidText}`}). Ask again with a higher --max-tokens, or use another model (${cli4()} infer models).` : `The model sent an empty answer (${replayed || held ? "paid earlier" : `paid ${paidText}`}). Ask again: that is a new question and pays again.`;
153646
153825
  if (values.has("--json")) {
153647
153826
  io.out(`${JSON.stringify(
153648
153827
  {
153649
- ok: true,
153828
+ ok: !empty,
153829
+ ...empty ? { error: "inference_empty_answer", message: emptyWords } : {},
153650
153830
  model: result.model ?? model2,
153651
153831
  content: result.content,
153652
153832
  finishReason: result.finishReason,
@@ -153661,10 +153841,16 @@ function inferOutput(result, { values, io, model: model2, quiet, requestId, held
153661
153841
  (key, value2) => typeof value2 === "bigint" ? value2.toString() : value2
153662
153842
  )}
153663
153843
  `);
153844
+ if (empty) throw new PrintedFailure("inference_empty_answer");
153664
153845
  return;
153665
153846
  }
153666
153847
  for (const line of paidNotices) io.err(`${line}
153667
153848
  `);
153849
+ if (empty) {
153850
+ io.err(`${emptyWords}${quiet ? "" : ` request-id ${result.requestId ?? requestId}`}
153851
+ `);
153852
+ throw new PrintedFailure("inference_empty_answer");
153853
+ }
153668
153854
  io.out(`${result.content ?? ""}
153669
153855
  `);
153670
153856
  if (plainPaid) {
@@ -154243,6 +154429,13 @@ var RanBehindTorFailed = class extends Error {
154243
154429
  this.exitCode = code2;
154244
154430
  }
154245
154431
  };
154432
+ var PrintedFailure = class extends Error {
154433
+ constructor(code2) {
154434
+ super(code2);
154435
+ this.code = code2;
154436
+ this.exitCode = 1;
154437
+ }
154438
+ };
154246
154439
  function chatBeforeValidate(values, deps, args) {
154247
154440
  values.set(CHAT_RAW_ARGS, args);
154248
154441
  if (values.has("--wallet") || values.has("--pool")) return void 0;
@@ -156410,7 +156603,7 @@ var COMMANDS = [
156410
156603
  summary: "Ask an AI model one question and pay for the answer from your private balance (Shielded Inference, through the AI provider's x402 route).",
156411
156604
  env: [],
156412
156605
  flags: [
156413
- flag2("--prompt", "<text>", `what to ask; write - to read it from stdin (the private form), or write it alone, in quotes (${CLI_WORD} infer "Summarize x402 in one line" ...)`, { required: true }),
156606
+ flag2("--prompt", "<text>", `what to ask; with no question in a terminal, infer asks for it (the words stay out of your shell history); write - to read it from stdin (a file or a script), or write it alone, in quotes (${CLI_WORD} infer "Summarize x402 in one line" ...)`, { required: true }),
156414
156607
  flag2("--model", "<id>", `the model (default ${DEFAULT_MODEL2}): ${INFER_MODEL_IDS.join(", ")} (${CLI_WORD} infer models lists their prices)`),
156415
156608
  flag2("--max-tokens", "<n>", "the longest answer to pay for, in tokens; default 4096, 512 for claude-opus-5 (give 256 or more; 512 answers a short question: a reasoning model like qwen3-8-flash thinks first, and with fewer tokens it can stop before it answers, still paid); it sets the most this request can cost", { check: "count" }),
156416
156609
  flag2("--max-payment", "<usdc>", `the most this request may pay in all, in USDC (0.02 = 2 cents). It must be at least 0.011: the wallet never sets a call's ceiling below the AI provider's 0.01 USDC floor on its standard offer, and a call also pays Shinjuku's 0.001 USDC fee, so 0.02 covers it. What a call pays is the AI provider's quote for --max-tokens plus Shinjuku's fee: with ${DEFAULT_MODEL2} and the default ${DEFAULT_MAX_TOKENS} tokens, a short question costs about ${usdcOr(DEFAULT_ANSWER_PAID_ATOMIC)}, at most 0.011 USDC: 0.01 USDC plus the fee when the AI provider offers only its standard payment. A higher ceiling is refused before anything is paid. Default: from your limits (setup or limits sets them), else 0.05`, { check: "atomic", usdc: true }),
@@ -156423,12 +156616,12 @@ var COMMANDS = [
156423
156616
  ],
156424
156617
  examples: [
156425
156618
  `${CLI_WORD} infer "Say hello in three words"`,
156426
- `echo "Summarize x402 in one line" | ${CLI_WORD} infer -`,
156427
- `echo "Write a haiku about tunnels" | ${CLI_WORD} infer - --model claude-opus-5 --json ${INFER_EXAMPLE_TAIL}`
156619
+ `${CLI_WORD} infer`,
156620
+ `cat question.txt | ${CLI_WORD} infer - --model claude-opus-5 --json ${INFER_EXAMPLE_TAIL}`
156428
156621
  ],
156429
156622
  notes: [
156430
156623
  `After setup it needs no flags, as chat: config.json gives the wallet, the model is ${DEFAULT_MODEL2}, the caps are from your limits (setup or limits sets them; else 0.05 USDC a call and 1 USDC a run), and it runs behind Tor by itself (Tor must run, as setup recorded it). A flag you give still wins. With --pool or --wallet the command line is the whole config: then add --tor (or --proxy <url>).`,
156431
- `Private form (recommended): pipe the prompt in and write - in its place, so it is not in the process list or your shell history: ${INFER_STDIN_EXAMPLE}. stdin is read until it ends (UTF-8, at most 1 MiB); an empty one is refused before anything is paid.`,
156624
+ `Private form (recommended): type ${CLI_WORD} infer alone in a terminal, and type the question when it asks: the words are not in the command line, the process list or your shell history. A question typed on the command line (in quotes) is saved in your shell history. For a file or a script, write - and pipe the question in: ${INFER_STDIN_EXAMPLE}; stdin is read until it ends (UTF-8, at most 1 MiB); an empty one is refused before anything is paid.`,
156432
156625
  "It runs only through Tor (or --proxy <url>), never a direct connection: the AI provider would see your IP address next to your prompt, so the command refuses before anything is sent.",
156433
156626
  `${INFER_COST_LINE}. stderr says this before the payment, with the most the request can cost.`,
156434
156627
  "stdout holds the answer; stderr says paid <amount> USDC request-id <id>. An answer cut short by --max-tokens says so. The wallet's own progress lines (Tor, the RPC relay, the pool history walk, the memo reads) show only with --verbose; warnings and failures always show.",
@@ -156453,7 +156646,7 @@ var COMMANDS = [
156453
156646
  flags: [bool2("--json", "print the list as one JSON line (prices in atomic USDC per 1M tokens)")],
156454
156647
  examples: [`${CLI_WORD} infer models`, `${CLI_WORD} infer models --json`],
156455
156648
  notes: [`${INFER_COST_LINE}.`],
156456
- next: `ask one: echo "Summarize x402 in one line" | ${CLI_WORD} infer - --model ${DEFAULT_MODEL2}`,
156649
+ next: `ask one: ${CLI_WORD} infer (it asks for the question), or chat: ${CLI_WORD} chat`,
156457
156650
  run: (args, values, deps, io) => deps.inferModelsMain(values, { ...deps, io })
156458
156651
  },
156459
156652
  {
@@ -159833,7 +160026,7 @@ function failureHint(command, reason, deps, values, floatError) {
159833
160026
  }
159834
160027
  const floatCode = /\b(p03_float_[a-z_]+|wallet_quote_refused_[a-z0-9_]+|p03_resource_paid_seller_failed)\b/.exec(reason)?.[1];
159835
160028
  const facts = REQUEST_ID_TAKEN2.test(reason) ? { ...floatError, ...conflictRecordFacts(reason, values, deps) } : floatError;
159836
- const float = floatCode ? floatOutcomeWords(floatCode, facts, CLI_FLOAT_TERMS(values, deps, command)) : void 0;
160029
+ const float = floatCode ? floatOutcomeWords(floatCode, command?.name === "infer" ? withoutProviderText(facts) : facts, CLI_FLOAT_TERMS(values, deps, command)) : void 0;
159837
160030
  if (float) return `${float[1].replace(/[.;\s]+$/, "")}. ${float[2]}`;
159838
160031
  const pocketHint = pocketFailureHint(reason, command);
159839
160032
  if (pocketHint) return pocketHint;
@@ -160661,12 +160854,14 @@ This copy: ${walletVersionLine(deps.walletVersion())}
160661
160854
  }
160662
160855
  if (isFamily(name)) {
160663
160856
  if (INTERNAL_FAMILIES.has(name) && args.length === 0) throw new UsageError("Unknown command.");
160664
- if (args.length === 0) {
160857
+ const aloneInTerminal = args.length === 0 && COMMAND_BY_NAME.has(name) && process.stdin.isTTY === true;
160858
+ if (args.length === 0 && !aloneInTerminal) {
160665
160859
  io.out(familyOverviewText(name));
160666
160860
  return 0;
160667
160861
  }
160668
160862
  const three = `${name} ${args[0]} ${args[1]}`;
160669
- if (COMMAND_BY_NAME.has(name) && !COMMAND_BY_NAME.has(`${name} ${args[0]}`) && !COMMAND_BY_NAME.has(three)) command = COMMAND_BY_NAME.get(name);
160863
+ if (aloneInTerminal) command = COMMAND_BY_NAME.get(name);
160864
+ else if (COMMAND_BY_NAME.has(name) && !COMMAND_BY_NAME.has(`${name} ${args[0]}`) && !COMMAND_BY_NAME.has(three)) command = COMMAND_BY_NAME.get(name);
160670
160865
  else command = COMMAND_BY_NAME.has(three) ? (args.splice(0, 2), COMMAND_BY_NAME.get(three)) : COMMAND_BY_NAME.get(`${name} ${args.shift()}`);
160671
160866
  if (!command && INTERNAL_FAMILIES.has(name)) throw new UsageError("Unknown command.");
160672
160867
  if (!command) {
@@ -160742,6 +160937,10 @@ This copy: ${walletVersionLine(deps.walletVersion())}
160742
160937
  if (endsProcess) showProgressAgain = () => {
160743
160938
  };
160744
160939
  if (error instanceof RanBehindTorFailed) return error.exitCode;
160940
+ if (error instanceof PrintedFailure) {
160941
+ writeRunResult(deps, command, error.exitCode, error);
160942
+ return error.exitCode;
160943
+ }
160745
160944
  if (error instanceof UsageError) {
160746
160945
  let setUp = false;
160747
160946
  try {
@@ -160861,7 +161060,7 @@ Next: fund that address, then rerun the same command; it continues where it stop
160861
161060
  const blockingLine = typeof blocking === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(blocking) ? `earlier payment: --request-id ${blocking}
160862
161061
  ` : "";
160863
161062
  const payee = command.name === "infer" ? "the AI provider" : "the seller";
160864
- const sellerSaid = error?.sellerReason ?? error?.cause?.sellerReason;
161063
+ const sellerSaid = command.name === "infer" ? void 0 : error?.sellerReason ?? error?.cause?.sellerReason;
160865
161064
  const sellerLine = typeof sellerSaid === "string" && sellerSaid ? `${payee === "the seller" ? "seller" : payee} answered HTTP ${error?.httpStatus ?? error?.cause?.httpStatus}: ${JSON.stringify(sellerSaid)}
160866
161065
  ` : "";
160867
161066
  const landedTx = error?.transaction ?? error?.cause?.transaction;