@bsvkey/x402-bsv-client 0.4.0 → 0.4.2
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 +13 -3
- package/package.json +1 -1
- package/usage-receipt.js +22 -1
package/README.md
CHANGED
|
@@ -91,18 +91,28 @@ sequence has no gap or replay, the running totals reconcile
|
|
|
91
91
|
(`cumSats`/`cumTokens`), spending never exceeds the funded amount, and each
|
|
92
92
|
**charge** recomputes from the published formula (you can be charged less, never
|
|
93
93
|
more). Single-receipt helpers: `verifyReceipt(receipt)` → `{ ok, signer }`,
|
|
94
|
-
`verifyCharge(receipt)`, and `verifyMeter(receipt, {
|
|
94
|
+
`verifyCharge(receipt)`, and `verifyMeter(receipt, { messages, completion })` (or
|
|
95
|
+
`{ system, prompt, completion }` on the raw `/v1/infer` path — see below).
|
|
95
96
|
|
|
96
97
|
### Verify the meter (the token count itself)
|
|
97
98
|
|
|
98
99
|
The v2 receipt binds the exact bytes and is metered by a pinned, deterministic
|
|
99
|
-
tokenizer, so you recompute the token count from what you sent and received
|
|
100
|
+
tokenizer, so you recompute the token count from what you sent and received.
|
|
101
|
+
|
|
102
|
+
**On the OpenAI-compatible `/v1/chat/completions` path, pass the same `messages`
|
|
103
|
+
array you sent** — that endpoint meters the *flattened* messages (system joined
|
|
104
|
+
with `\n`; every other turn rendered as `User: …` / `Assistant: …`, joined with
|
|
105
|
+
`\n`), so `verifyMeter` needs the messages to reproduce that transform for you:
|
|
100
106
|
|
|
101
107
|
```js
|
|
102
108
|
import { verifyMeter } from '@bsvkey/x402-bsv-client/usage-receipt';
|
|
103
|
-
const m = verifyMeter(receipt, {
|
|
109
|
+
const m = verifyMeter(receipt, { messages, completion }); // { ok } / { ok:false, reason }
|
|
104
110
|
```
|
|
105
111
|
|
|
112
|
+
On the broker-native `/v1/infer` path (raw fields), pass `{ system, prompt, completion }`
|
|
113
|
+
instead. (`messagesToPrompt(messages)` is exported if you want the flattened
|
|
114
|
+
`{ system, prompt }` yourself.)
|
|
115
|
+
|
|
106
116
|
**What this proves:** the broker signed these exact numbers (non-repudiable), the
|
|
107
117
|
token count is the published function of the exact bytes you exchanged, the charge
|
|
108
118
|
is the published function of those tokens, none were double-counted, and the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsvkey/x402-bsv-client",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Pay an x402 HTTP 402 in BSV — the missing bsv-p2pkh payment builder for x402 agents. Also verifies BSVKey inference usage receipts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/usage-receipt.js
CHANGED
|
@@ -62,6 +62,23 @@ export function computeClaimId(obj) {
|
|
|
62
62
|
export function meterInputText(system, prompt) {
|
|
63
63
|
return (system ? String(system) + '\n\n' : '') + String(prompt || '');
|
|
64
64
|
}
|
|
65
|
+
// The OpenAI-compatible endpoint (/v1/chat/completions) meters the FLATTENED
|
|
66
|
+
// chat messages, not the raw fields: system messages are joined with '\n', and
|
|
67
|
+
// every other turn becomes "User: <content>" / "Assistant: <content>", joined
|
|
68
|
+
// with '\n'. To recompute inputDigest for a chat call, pass the SAME messages
|
|
69
|
+
// array you sent (verifyMeter does this for you when given { messages }). This is
|
|
70
|
+
// byte-identical to the broker's src/http/openai.js messagesToPrompt.
|
|
71
|
+
export function messagesToPrompt(messages) {
|
|
72
|
+
const systemParts = [], turns = [];
|
|
73
|
+
for (const m of (Array.isArray(messages) ? messages : [])) {
|
|
74
|
+
const content = typeof m.content === 'string'
|
|
75
|
+
? m.content
|
|
76
|
+
: Array.isArray(m.content) ? m.content.map((p) => p.text || '').join('') : '';
|
|
77
|
+
if (m.role === 'system') systemParts.push(content);
|
|
78
|
+
else turns.push(`${m.role === 'assistant' ? 'Assistant' : 'User'}: ${content}`);
|
|
79
|
+
}
|
|
80
|
+
return { system: systemParts.join('\n') || undefined, prompt: turns.join('\n') };
|
|
81
|
+
}
|
|
65
82
|
export function estimateTokens(text) {
|
|
66
83
|
if (!text) return 0;
|
|
67
84
|
const byChars = Math.ceil(text.length / 4);
|
|
@@ -111,8 +128,12 @@ export function verifyCharge(receipt) {
|
|
|
111
128
|
}
|
|
112
129
|
|
|
113
130
|
// The meter: recompute token counts + byte digests from the EXACT bytes you hold.
|
|
114
|
-
|
|
131
|
+
// Pass EITHER the raw { system, prompt } (broker-native /v1/infer) OR the exact
|
|
132
|
+
// { messages } array you sent to /v1/chat/completions (the OpenAI shim, which meters
|
|
133
|
+
// the flattened messages). If `messages` is given it takes precedence.
|
|
134
|
+
export function verifyMeter(receipt, { system, prompt, completion, messages } = {}) {
|
|
115
135
|
if (receipt.meter !== METER_ID) return { ok: false, reason: `unknown_meter:${receipt.meter}` };
|
|
136
|
+
if (messages !== undefined) { const f = messagesToPrompt(messages); system = f.system; prompt = f.prompt; }
|
|
116
137
|
const input = meterInputText(system, prompt);
|
|
117
138
|
const output = String(completion || '');
|
|
118
139
|
if (sha256hex(input) !== receipt.inputDigest) return { ok: false, reason: 'inputDigest_mismatch' };
|