openzoo 0.50.89 → 0.50.90

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.
@@ -3075,17 +3075,20 @@ async function zooComplete(prompt, log, agentId, parsed = {}, opts = {}) {
3075
3075
  if (r.status === 402) {
3076
3076
  const raw = zooTextFromMessage(data?.choices?.[0]?.message, data)
3077
3077
  || data?.error?.message
3078
- || 'openzoo wallet underfunded.';
3078
+ || 'openzoo payment required (HTTP 402).';
3079
+ data.error = data.error || {};
3080
+ data.error.message = raw;
3079
3081
  try {
3080
- const { withOnrampLink } = await import('./stripeOnramp.js');
3081
- const { loadOrCreateWallet } = await import('./wallet.js');
3082
- const w = loadOrCreateWallet();
3083
- const usd = Number(String(raw).match(/≈\$([0-9.]+)/)?.[1]);
3084
- data.error = data.error || {};
3085
- data.error.message = await withOnrampLink(raw, {
3086
- solana: w.keypair.publicKey.toBase58(),
3087
- usd,
3088
- });
3082
+ const { withOnrampLink, isFundInstruction } = await import('./stripeOnramp.js');
3083
+ if (isFundInstruction(raw)) {
3084
+ const { loadOrCreateWallet } = await import('./wallet.js');
3085
+ const w = loadOrCreateWallet();
3086
+ const usd = Number(String(raw).match(/≈\$([0-9.]+)/)?.[1]);
3087
+ data.error.message = await withOnrampLink(raw, {
3088
+ solana: w.keypair.publicKey.toBase58(),
3089
+ usd,
3090
+ });
3091
+ }
3089
3092
  } catch { /* keep proxy copy */ }
3090
3093
  }
3091
3094
  return { r, data };
package/lib/proxy.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  } from './config.js';
10
10
  import { execSync } from 'node:child_process';
11
11
  import { PayClient, QuoteTooHighError, UnderfundedError } from './pay.js';
12
- import { withOnrampLink } from './stripeOnramp.js';
12
+ import { withOnrampLink, settleFailCopy, isFundInstruction } from './stripeOnramp.js';
13
13
  import { tokenBalance } from './x402.js';
14
14
  import { evmTokenBalance } from './evm.js';
15
15
  import { autoContext } from './autobind.js';
@@ -854,7 +854,6 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
854
854
  // half-kilobyte accepts[] blob at the user instead of the one thing they
855
855
  // need: the price, what the wallet holds, and where to send funds.
856
856
  if (response.status === 402) {
857
- let quoted = '';
858
857
  let usd;
859
858
  let q402 = null;
860
859
  try { q402 = await response.clone().json(); } catch { q402 = null; }
@@ -871,18 +870,18 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
871
870
  return;
872
871
  }
873
872
  try {
874
- const q = q402;
875
- usd = Number(q?.accepts?.[0]?.extra?.billedUsd);
876
- if (Number.isFinite(usd)) quoted = ` This call needs ≈$${usd.toFixed(4)}.`;
877
- } catch { /* body was not the quote after all */ }
878
- const msg = await withOnrampLink(
879
- `openzoo wallet underfunded payment did not settle.${quoted} `
880
- + `Fund it and retry: send USDC (or TOKEN/LEOS for half price) to ${client.address} on Solana, `
881
- + `or USDC to ${client.evmAddress} on Base. Check with: openzoo balance`,
882
- { solana: client.address, usd },
883
- );
884
- log(/ties to your account/i.test(msg) ? 'onramp: whop + copy-paste solana' : 'onramp: no fund blurb');
885
- jsonErr(res, 402, msg);
873
+ usd = Number(q402?.accepts?.[0]?.extra?.billedUsd);
874
+ if (!Number.isFinite(usd)) usd = undefined;
875
+ } catch { usd = undefined; }
876
+ const copy = settleFailCopy(q402);
877
+ let msg = copy.message;
878
+ const wantOnramp = copy.code === 'insufficient_funds'
879
+ || (!paid && isFundInstruction(copy.reason, copy));
880
+ if (wantOnramp) {
881
+ msg = await withOnrampLink(msg, { solana: client.address, usd, code: copy.code });
882
+ }
883
+ log(/ties to your account/i.test(msg) ? 'onramp: whop + copy-paste solana' : `402: ${msg.slice(0, 140)}`);
884
+ jsonErr(res, paid ? copy.status : 402, msg);
886
885
  return;
887
886
  }
888
887
  await relay(res, response, meterStreamed);
@@ -103,8 +103,58 @@ export function whopFundBlurb(solana) {
103
103
  ].join('\n');
104
104
  }
105
105
 
106
+ /**
107
+ * Genuine empty-wallet / fund-me copy. A post-pay settle failure
108
+ * ("payment did not settle" with a gateway reason and no underfunded
109
+ * wording) is NOT this — those wallets are often funded; the 402 is
110
+ * the facilitator or upstream.
111
+ */
112
+ export function isFundInstruction(text, extra = {}) {
113
+ const code = extra.code ?? extra.advice?.code;
114
+ if (String(code || '') === 'insufficient_funds') return true;
115
+ const s = String(text || '');
116
+ if (!s) return false;
117
+ if (/\b(?:wallet underfunded|empty wallet|wallet is empty|needs more than the wallet holds|insufficient[_\s]funds)\b/i.test(s)) return true;
118
+ if (/\bunderfunded\b/i.test(s)) return true;
119
+ if (/\bsend (?:usdc|a few cents)\b/i.test(s)) return true;
120
+ if (/\bno offered payment row is affordable/i.test(s)) return true;
121
+ return false;
122
+ }
123
+
124
+ function gatewayReason(q402) {
125
+ if (!q402 || typeof q402 !== 'object') return '';
126
+ const err = q402.error;
127
+ const advice = q402.advice;
128
+ if (typeof err?.message === 'string' && err.message.trim()) return err.message.trim();
129
+ if (typeof err === 'string' && err.trim()) return err.trim();
130
+ if (typeof advice?.message === 'string' && advice.message.trim()) return advice.message.trim();
131
+ if (typeof advice === 'string' && advice.trim()) return advice.trim();
132
+ if (advice && typeof advice === 'object') {
133
+ const bits = [advice.code, advice.reason, advice.detail].filter((x) => typeof x === 'string' && x.trim());
134
+ if (bits.length) return bits.join(': ');
135
+ }
136
+ return '';
137
+ }
138
+
139
+ /**
140
+ * Copy for a 402 AFTER PayClient already signed and retried (paid:true).
141
+ * Never "wallet underfunded" — that string is reserved for preflight
142
+ * empty-wallet errors. Prefix stays greppable as "payment did not settle".
143
+ */
144
+ export function settleFailCopy(q402) {
145
+ const reason = gatewayReason(q402);
146
+ const code = q402?.advice?.code || q402?.error?.code || '';
147
+ const fund = isFundInstruction(reason, { code, advice: q402?.advice });
148
+ const message = reason
149
+ ? `openzoo payment did not settle: ${reason}`
150
+ : 'openzoo payment did not settle';
151
+ const upstreamish = /upstream|facilitator|internal(?: server)? error|settle(?:ment)? (?:failed|error)/i.test(reason) && !fund;
152
+ return { message, status: upstreamish ? 502 : 402, fund, reason, code: String(code || '') };
153
+ }
154
+
106
155
  export async function withOnrampLink(text, dest) {
107
156
  const body = String(text || '').trim();
157
+ if (!isFundInstruction(body, dest)) return body;
108
158
  const blurb = whopFundBlurb(dest?.solana);
109
159
  if (!blurb) return body;
110
160
  if (/ties to your account/i.test(body) && body.includes(String(dest.solana))) return body;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.89",
4
- "description": "Local x402-paying proxy + MCP server for openzoo.fun point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
3
+ "version": "0.50.90",
4
+ "description": "Local x402-paying proxy + MCP server for openzoo.fun \u2014 point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {