@kaleidorg/mind 0.6.4 → 0.7.0
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 +257 -0
- package/dist/autonomy/risk.js.map +1 -1
- package/dist/autonomy/run-state.d.ts.map +1 -1
- package/dist/autonomy/run-state.js.map +1 -1
- package/dist/autonomy/task-store.d.ts.map +1 -1
- package/dist/autonomy/task-store.js.map +1 -1
- package/dist/bitrefill/contract.js.map +1 -1
- package/dist/context/budget.js.map +1 -1
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/context/builder.js.map +1 -1
- package/dist/context/compress.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js.map +1 -1
- package/dist/fastpath/fastpath.d.ts.map +1 -1
- package/dist/fastpath/fastpath.js.map +1 -1
- package/dist/flashnet/contract.js.map +1 -1
- package/dist/funnel.d.ts.map +1 -1
- package/dist/funnel.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kaleidoswap/contract.js.map +1 -1
- package/dist/knowledge/btc-map.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/lsps1/contract.js.map +1 -1
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js.map +1 -1
- package/dist/qvac/assistant.js.map +1 -1
- package/dist/qvac/config.d.ts +6 -6
- package/dist/qvac/config.d.ts.map +1 -1
- package/dist/qvac/delegate.d.ts +2 -0
- package/dist/qvac/delegate.d.ts.map +1 -1
- package/dist/qvac/delegate.js +2 -0
- package/dist/qvac/delegate.js.map +1 -1
- package/dist/qvac/index.d.ts +1 -0
- package/dist/qvac/index.d.ts.map +1 -1
- package/dist/qvac/index.js +1 -0
- package/dist/qvac/index.js.map +1 -1
- package/dist/qvac/parse.d.ts +5 -2
- package/dist/qvac/parse.d.ts.map +1 -1
- package/dist/qvac/parse.js.map +1 -1
- package/dist/qvac/provider.d.ts.map +1 -1
- package/dist/qvac/provider.js +8 -13
- package/dist/qvac/provider.js.map +1 -1
- package/dist/qvac/stream.d.ts.map +1 -1
- package/dist/qvac/stream.js +29 -1
- package/dist/qvac/stream.js.map +1 -1
- package/dist/qvac/tools.d.ts +32 -0
- package/dist/qvac/tools.d.ts.map +1 -0
- package/dist/qvac/tools.js +84 -0
- package/dist/qvac/tools.js.map +1 -0
- package/dist/qvac/voice.js.map +1 -1
- package/dist/rag/retriever.d.ts.map +1 -1
- package/dist/rag/tool.js.map +1 -1
- package/dist/rag/vector-store.d.ts.map +1 -1
- package/dist/rag/vector-store.js.map +1 -1
- package/dist/recipe/issue-asset.d.ts +16 -0
- package/dist/recipe/issue-asset.d.ts.map +1 -0
- package/dist/recipe/issue-asset.js +94 -0
- package/dist/recipe/issue-asset.js.map +1 -0
- package/dist/recipe/runner.d.ts.map +1 -1
- package/dist/recipe/runner.js.map +1 -1
- package/dist/skills/loader.js.map +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-wallet.d.ts +79 -0
- package/dist/testing/mock-wallet.d.ts.map +1 -0
- package/dist/testing/mock-wallet.js +180 -0
- package/dist/testing/mock-wallet.js.map +1 -0
- package/dist/testing/scripted-provider.d.ts +17 -0
- package/dist/testing/scripted-provider.d.ts.map +1 -0
- package/dist/testing/scripted-provider.js +26 -0
- package/dist/testing/scripted-provider.js.map +1 -0
- package/dist/tools/in-process.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/wallet/confirm.d.ts.map +1 -1
- package/dist/wallet/confirm.js +21 -1
- package/dist/wallet/confirm.js.map +1 -1
- package/dist/wallet/contract.d.ts.map +1 -1
- package/dist/wallet/contract.js +36 -0
- package/dist/wallet/contract.js.map +1 -1
- package/package.json +10 -5
- package/skills/README.md +1 -1
- package/skills/kaleido-node/SKILL.md +63 -0
- package/skills/kaleido-trading/SKILL.md +15 -2
- package/skills/kaleido-trading/references/api.md +61 -0
- package/skills/kaleido-trading/references/assets.md +57 -0
- package/skills/kaleido-trading/references/atomic.md +93 -0
- package/skills/paid-data/SKILL.md +59 -6
- package/skills/rgb-lightning-node/SKILL.md +116 -11
- package/src/index.ts +3 -0
- package/src/qvac/delegate.ts +2 -0
- package/src/qvac/index.ts +2 -0
- package/src/qvac/parse.ts +5 -2
- package/src/qvac/provider.test.ts +38 -0
- package/src/qvac/provider.ts +10 -14
- package/src/qvac/stream.test.ts +28 -0
- package/src/qvac/stream.ts +30 -1
- package/src/qvac/tools.test.ts +86 -0
- package/src/qvac/tools.ts +106 -0
- package/src/recipe/issue-asset.test.ts +92 -0
- package/src/recipe/issue-asset.ts +95 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/mock-wallet.ts +211 -0
- package/src/testing/scripted-provider.ts +37 -0
- package/src/wallet/confirm.test.ts +16 -0
- package/src/wallet/confirm.ts +21 -1
- package/src/wallet/contract.test.ts +38 -0
- package/src/wallet/contract.ts +36 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# KaleidoSwap maker API (what the tools call)
|
|
2
|
+
|
|
3
|
+
Base URLs: `https://api.kaleidoswap.com` (mainnet),
|
|
4
|
+
`https://api.signet.kaleidoswap.com` (signet, `KALEIDO_NETWORK=signet` in
|
|
5
|
+
kaleido-mcp). The MCP tools wrap these endpoints; you normally never call them
|
|
6
|
+
directly. Amounts on the wire are raw units (see `assets.md`).
|
|
7
|
+
|
|
8
|
+
| Endpoint | Tool |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `GET /api/v1/market/assets` | `kaleidoswap_get_assets` |
|
|
11
|
+
| `GET /api/v1/market/pairs` | `kaleidoswap_get_pairs` |
|
|
12
|
+
| `POST /api/v1/market/quote` | `kaleidoswap_get_quote` |
|
|
13
|
+
| `POST /api/v1/swaps/init` | `kaleidoswap_atomic_init` |
|
|
14
|
+
| `POST /api/v1/swaps/execute` | `kaleidoswap_atomic_execute` |
|
|
15
|
+
| `POST /api/v1/swaps/atomic/status` | `kaleidoswap_atomic_status` |
|
|
16
|
+
| `GET /api/v1/lsps1/get_info` | `kaleidoswap_lsp_get_info` |
|
|
17
|
+
| `POST /api/v1/lsps1/estimate_fees` | `kaleidoswap_lsp_estimate_fees` |
|
|
18
|
+
| `POST /api/v1/lsps1/create_order` | `kaleidoswap_lsp_create_order` |
|
|
19
|
+
| `POST /api/v1/lsps1/get_order` | `kaleidoswap_lsp_get_order` |
|
|
20
|
+
|
|
21
|
+
## Pairs
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
[{
|
|
25
|
+
"base": { "ticker": "BTC", "precision": 11 },
|
|
26
|
+
"quote": { "ticker": "USDT", "precision": 6 },
|
|
27
|
+
"routes": [
|
|
28
|
+
{ "from_layer": "BTC_LN", "to_layer": "RGB_LN", "min_amount": 50000, "max_amount": 10000000000 },
|
|
29
|
+
{ "from_layer": "RGB_LN", "to_layer": "BTC_LN", "min_amount": 1000000, "max_amount": 999999000000 }
|
|
30
|
+
]
|
|
31
|
+
}]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`min_amount` / `max_amount` are raw units of the route's `from` asset.
|
|
35
|
+
|
|
36
|
+
## Quote
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"rfq_id": "uuid", "expires_at": "2026-01-01T00:01:00Z",
|
|
41
|
+
"from_asset": { "ticker": "BTC", "layer": "BTC_LN", "amount_raw": 100000000, "amount_display": 0.001 },
|
|
42
|
+
"to_asset": { "ticker": "USDT", "layer": "RGB_LN", "amount_raw": 65763000, "amount_display": 65.763 },
|
|
43
|
+
"price": 65763.0
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Swap
|
|
48
|
+
|
|
49
|
+
- `init` → `{ swapstring, payment_hash, access_token }` (`access_token` only here)
|
|
50
|
+
- `execute` takes `{ swapstring, taker_pubkey, payment_hash }`
|
|
51
|
+
- `status` takes `{ payment_hash, access_token }` →
|
|
52
|
+
`{ swap: { status: "Waiting" | "Pending" | "Succeeded" | "Expired" | "Failed" } }`
|
|
53
|
+
|
|
54
|
+
## LSPS1 orders
|
|
55
|
+
|
|
56
|
+
`estimate_fees` / `create_order` take `{ client_pubkey, lsp_balance_sat,
|
|
57
|
+
client_balance_sat, channel_expiry_blocks }`. `create_order` returns
|
|
58
|
+
`{ order_id, bolt11_invoice, order_total_sat }`; `get_order` reports
|
|
59
|
+
`PENDING → CHANNEL_OPENING → COMPLETED | FAILED`. Good defaults:
|
|
60
|
+
`lsp_balance_sat = 500000`, `client_balance_sat = 0`,
|
|
61
|
+
`channel_expiry_blocks = 4320`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Assets, units and precision
|
|
2
|
+
|
|
3
|
+
Asset ids differ between signet and mainnet and may rotate on signet. Always
|
|
4
|
+
discover them with `kaleidoswap_get_assets()` (cache for at most ~5 minutes);
|
|
5
|
+
never hard-code them.
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{ "asset_id": "BTC" | "rgb:...", "ticker": "USDT", "name": "...", "precision": 6 }
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Some environments omit BTC from the assets list; `kaleidoswap_get_pairs()`
|
|
12
|
+
always includes it.
|
|
13
|
+
|
|
14
|
+
## Units
|
|
15
|
+
|
|
16
|
+
BTC uses **millisatoshis** as its raw unit (precision 11):
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
1 BTC = 100,000,000 sats = 1e11 msat
|
|
20
|
+
0.001 BTC = 100,000 sats = 1e8 msat
|
|
21
|
+
1 sat = 1,000 msat
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
RGB assets use their own precision:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
raw = round(display × 10^precision)
|
|
28
|
+
display = raw / 10^precision
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Asset | Typical precision | Raw unit |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| BTC | 11 | msat |
|
|
34
|
+
| USDT | 6 | micro-USDT |
|
|
35
|
+
| XAUT | 9 | nano-XAUT |
|
|
36
|
+
|
|
37
|
+
Read precision from the API; the table is illustrative.
|
|
38
|
+
|
|
39
|
+
## Which unit each tool takes
|
|
40
|
+
|
|
41
|
+
| Tool | Units |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `kaleidoswap_get_quote` (kaleido-mcp) | display (`0.001` BTC, `65.0` USDT) |
|
|
44
|
+
| `kaleidoswap_atomic_init` (kaleido-mcp) | raw: pass `quote.*.amount_raw` unchanged |
|
|
45
|
+
| `kaleidoswap_get_quote` (in-app contract) | sats for BTC, asset units otherwise |
|
|
46
|
+
| `rln_send_asset` / `rln_create_rgb_invoice` (kaleido-mcp) | display units |
|
|
47
|
+
|
|
48
|
+
## Mapping user words
|
|
49
|
+
|
|
50
|
+
- "BTC" over Lightning → `asset_id: "BTC"`, layer `BTC_LN`; on-chain → `BTC_L1`.
|
|
51
|
+
- "USDT" → the asset whose `ticker === "USDT"`. "USD" usually means USDT: confirm.
|
|
52
|
+
- "gold" / "XAUT" → `ticker === "XAUT"`. Confirm "gold".
|
|
53
|
+
|
|
54
|
+
## Layers
|
|
55
|
+
|
|
56
|
+
`BTC_L1`, `BTC_LN`, `BTC_SPARK`, `BTC_ARKADE`, `RGB_L1`, `RGB_LN`. Use the
|
|
57
|
+
values from `kaleidoswap_get_pairs()` routes verbatim.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Atomic swap over kaleido-mcp
|
|
2
|
+
|
|
3
|
+
Every KaleidoSwap swap settles as an atomic HTLC swap between the maker and
|
|
4
|
+
the user's RGB Lightning Node (RLN). There is no deposit or order flow on
|
|
5
|
+
kaleido-mcp 0.3.0+. If either side fails, nothing settles. Typical end-to-end
|
|
6
|
+
time: 2–15 s.
|
|
7
|
+
|
|
8
|
+
These are kaleido-mcp's argument names. In-app wallets that bind the
|
|
9
|
+
`@kaleidorg/mind` KaleidoSwap contract use the simpler `from_asset` /
|
|
10
|
+
`to_asset` / `amount` quote and run the chain through a recipe instead.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
0. kaleidoswap_get_pairs() → pairs + routes[{ from_layer, to_layer, min_amount, max_amount }]
|
|
16
|
+
kaleidoswap_get_assets() → asset_id + precision per ticker (ids differ per network)
|
|
17
|
+
|
|
18
|
+
1. kaleidoswap_get_quote({
|
|
19
|
+
from_asset_id: "BTC", from_layer: "BTC_LN", from_amount: 0.001, // display units
|
|
20
|
+
to_asset_id: "<USDT asset_id>", to_layer: "RGB_LN"
|
|
21
|
+
})
|
|
22
|
+
→ { rfq_id, expires_at, from_asset: { amount_raw, amount_display },
|
|
23
|
+
to_asset: { amount_raw, amount_display }, price }
|
|
24
|
+
Pass exactly one of from_amount (sell a fixed input) or to_amount (buy a fixed output).
|
|
25
|
+
Show the user amount in → amount out → rate, and wait for a yes.
|
|
26
|
+
|
|
27
|
+
2. kaleidoswap_atomic_init({ rfq_id,
|
|
28
|
+
from_asset_id, from_amount_raw: quote.from_asset.amount_raw,
|
|
29
|
+
to_asset_id, to_amount_raw: quote.to_asset.amount_raw })
|
|
30
|
+
→ { swapstring, payment_hash, access_token }
|
|
31
|
+
access_token is returned only here; keep it for status.
|
|
32
|
+
|
|
33
|
+
3. rln_atomic_taker({ swapstring }) ← whitelist the incoming HTLC. MUST precede execute.
|
|
34
|
+
rln_get_node_info() → { pubkey } (the taker_pubkey)
|
|
35
|
+
|
|
36
|
+
4. kaleidoswap_atomic_execute({ swapstring, taker_pubkey, payment_hash })
|
|
37
|
+
|
|
38
|
+
5. kaleidoswap_atomic_status({ payment_hash, access_token }) ← poll every 2 s
|
|
39
|
+
Waiting → Pending → Succeeded | Failed | Expired
|
|
40
|
+
|
|
41
|
+
6. rln_refresh_transfers() ← after an RGB leg, so balances update
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`amount_raw` values are already in the maker's raw units (msat for BTC, atomic
|
|
45
|
+
units for RGB). Never convert them again. `payment_hash` identifies the swap
|
|
46
|
+
and is not an order id.
|
|
47
|
+
|
|
48
|
+
## Errors
|
|
49
|
+
|
|
50
|
+
| Situation | What to do |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Quote expired (~60 s) | Re-quote. Never reuse an rfq_id or swapstring. |
|
|
53
|
+
| `rln_atomic_taker` fails | Do NOT execute. Re-quote and restart. |
|
|
54
|
+
| Status `Expired` / `Failed` | Nothing settled. Check liquidity on both legs (`rln_list_channels`), then re-quote. |
|
|
55
|
+
| Polling > 120 s | Check `rln_get_swap({ payment_hash, taker: true })` before retrying. Never start a second swap for the same trade while one may still settle. |
|
|
56
|
+
|
|
57
|
+
## Liquidity prerequisites
|
|
58
|
+
|
|
59
|
+
The swap needs outbound capacity on the asset you send and inbound capacity on
|
|
60
|
+
the asset you receive. If either side is short, `atomic_execute` fails. Buy a
|
|
61
|
+
channel first (skill: `kaleido-lsps`).
|
|
62
|
+
|
|
63
|
+
## Reading the result
|
|
64
|
+
|
|
65
|
+
An RGB asset received into a Lightning channel shows up in
|
|
66
|
+
`rln_get_asset_balance({ asset_id }).offchain_outbound`, not in `spendable`
|
|
67
|
+
(which is on-chain only). `offchain_outbound + offchain_inbound` is the
|
|
68
|
+
channel's capacity for that asset. The node's own view of swaps:
|
|
69
|
+
`rln_list_swaps()` / `rln_get_swap({ payment_hash, taker: true })`.
|
|
70
|
+
|
|
71
|
+
## Cross-layer moves
|
|
72
|
+
|
|
73
|
+
| From | To | How |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| BTC_LN | RGB_LN (USDT/XAUT) | atomic swap |
|
|
76
|
+
| RGB_LN | BTC_LN | atomic swap |
|
|
77
|
+
| RGB_LN | RGB_LN (e.g. XAUT → USDT) | atomic swap |
|
|
78
|
+
| BTC_LN | BTC_SPARK | not a swap: create a Spark Lightning invoice, pay it from the node (`rln_pay_invoice`) |
|
|
79
|
+
| BTC_SPARK | BTC_LN | create an invoice on the node (`rln_create_ln_invoice`), pay it from Spark |
|
|
80
|
+
| BTC_LN | BTC_L1 | close a channel (`rln_close_channel`), slow |
|
|
81
|
+
|
|
82
|
+
Other layers can appear in `kaleidoswap_get_pairs()` routes but are not
|
|
83
|
+
executable through these tools.
|
|
84
|
+
|
|
85
|
+
## Safety
|
|
86
|
+
|
|
87
|
+
1. Confirm from / to / amount / rate before `atomic_init`.
|
|
88
|
+
2. Never hard-code asset ids or layers; read them from the API.
|
|
89
|
+
3. Re-quote if execution would start more than ~30 s after the quote.
|
|
90
|
+
4. Check `rln_list_swaps()` for a pending taker swap before starting another.
|
|
91
|
+
5. Dry run: when asked for a dry run, quote and report only; never call
|
|
92
|
+
`kaleidoswap_atomic_init`, `rln_atomic_taker`, `kaleidoswap_atomic_execute`,
|
|
93
|
+
`rln_send_asset` or `rln_pay_invoice`.
|
|
@@ -1,12 +1,65 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: paid-data
|
|
3
|
-
description: Fetch premium or paywalled data
|
|
4
|
-
tools: fetch_paid_resource
|
|
5
|
-
triggers: premium, paid, l402, feed, subscription, unlock, paywall
|
|
3
|
+
description: Fetch premium or paywalled data behind an HTTP 402 Lightning payment (L402 or MPP, the Machine Payments Protocol) — paid feeds, pay-per-call APIs, unlockable resources — without signing up or holding an API key. Also finds paid APIs in the public 402index registry. Triggers when the user wants premium, paid, gated or unlockable data, or asks for a paid API.
|
|
4
|
+
tools: fetch_paid_resource, search_paid_apis, mpp_request_challenge, mpp_parse_challenge_header, mpp_submit_credential, rln_mpp_pay, spark_mpp_pay, l402_request_challenge, l402_fetch_resource
|
|
5
|
+
triggers: premium, paid, l402, mpp, 402, feed, subscription, unlock, paywall, pay per call, paid api, gated
|
|
6
|
+
metadata:
|
|
7
|
+
author: kaleidoswap
|
|
8
|
+
version: "0.2.0"
|
|
6
9
|
---
|
|
7
10
|
|
|
8
11
|
# Paid data
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
Servers gate a resource behind an HTTP 402 Lightning challenge; you pay the
|
|
14
|
+
invoice and present the proof. L402 is the Lightning-only subset of MPP. Tell
|
|
15
|
+
the user what was paid and what came back.
|
|
16
|
+
|
|
17
|
+
Use whichever tools you have:
|
|
18
|
+
|
|
19
|
+
## In-app: one call
|
|
20
|
+
|
|
21
|
+
`fetch_paid_resource({ url })` fetches, pays small invoices automatically
|
|
22
|
+
(capped by the host) and returns the data. Anything above the cap is declined;
|
|
23
|
+
report that instead of retrying.
|
|
24
|
+
|
|
25
|
+
## Over kaleido-mcp: three steps
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
1. mpp_request_challenge({ url })
|
|
29
|
+
→ { challenge_id, invoice, amount_sats, intent, expires_at, macaroon? }
|
|
30
|
+
|
|
31
|
+
2. rln_mpp_pay({ invoice, challenge_id, macaroon? }) ← pays from the RGB Lightning Node
|
|
32
|
+
(or spark_mpp_pay with the same arguments, from the Spark wallet; better for
|
|
33
|
+
tiny amounts where Lightning routing may fail)
|
|
34
|
+
→ { paid, payment_hash, preimage?, credential: "<JSON string>" }
|
|
35
|
+
|
|
36
|
+
3. mpp_submit_credential({ url, credential }) ← pass credential verbatim
|
|
37
|
+
→ { ok, status, data, receipt }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Finish all three before `expires_at` (~60 s); a credential is single-use, so
|
|
41
|
+
restart from step 1 if it expires. Keep the `receipt` as proof of payment.
|
|
42
|
+
|
|
43
|
+
- **Discovery:** `search_paid_apis({ query, protocol?, health: "healthy" })`
|
|
44
|
+
lists registered endpoints with their price; pick a healthy one, then run the
|
|
45
|
+
flow on its `url`.
|
|
46
|
+
- **Own fetch:** with a `WWW-Authenticate` header already in hand, use
|
|
47
|
+
`mpp_parse_challenge_header({ url, www_authenticate })` instead of step 1.
|
|
48
|
+
- **Legacy L402-only servers:** `l402_request_challenge` /
|
|
49
|
+
`l402_fetch_resource`. Prefer the `mpp_*` tools; they handle both.
|
|
50
|
+
- **Sessions:** `intent: "session"` challenges allow pay-once, then cheap
|
|
51
|
+
repeat calls. Not every server offers them; fall back to `charge`.
|
|
52
|
+
|
|
53
|
+
| Error | Meaning | Fix |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `Expected HTTP 402` | URL is not payment-gated | Fetch it normally |
|
|
56
|
+
| `payment failed` | Route or balance problem | Check balances; try `spark_mpp_pay` |
|
|
57
|
+
| `401 after submit` | Bad or reused credential | Redo steps 1–3 |
|
|
58
|
+
| `challenge expired` | Too slow between steps | Restart from step 1 |
|
|
59
|
+
|
|
60
|
+
## Rules
|
|
61
|
+
|
|
62
|
+
1. Show `amount_sats` before paying; ask for a yes above 1,000 sats.
|
|
63
|
+
2. Only pay challenges for URLs the user asked for.
|
|
64
|
+
3. Stop after two failures in a row and report; don't loop.
|
|
65
|
+
4. Dry run: report the price and what you would fetch; never pay.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rgb-lightning-node
|
|
3
|
-
description: "Drive the user's local RGB Lightning Node (RLN) — read its pubkey/status, list channels and their capacities, check RGB asset balances, manage channels/peers, whitelist a swap, or create Lightning/RGB receive invoices. Triggers when the user asks about the node, their channels or capacities, needs an invoice, or is mid-atomic-swap and the maker needs the node pubkey or a swapstring whitelisted."
|
|
4
|
-
tools: rln_get_node_info, rln_get_balances, rln_list_channels, rln_list_assets, rln_get_asset_balance, rln_open_channel, rln_close_channel, rln_connect_peer, rln_get_channel_id,
|
|
5
|
-
triggers: node, nodeinfo, pubkey, peer, channels, channel capacity, list channels, inbound, capacity, asset balance, whitelist, taker, swapstring, invoice, receive, rgb invoice, ln invoice
|
|
3
|
+
description: "Drive the user's local RGB Lightning Node (RLN) — read its pubkey/status, list channels and their capacities, check RGB asset balances, manage channels/peers, whitelist a swap, or create Lightning/RGB receive invoices. Triggers when the user asks about the node, their channels or capacities, needs an invoice, wants to issue/mint a new RGB token or NFT, or is mid-atomic-swap and the maker needs the node pubkey or a swapstring whitelisted."
|
|
4
|
+
tools: rln_get_node_info, rln_get_balances, rln_list_channels, rln_list_assets, rln_get_asset_balance, rln_refresh_transfers, rln_get_address, rln_send_btc, rln_send_asset, rln_pay_invoice, rln_open_channel, rln_close_channel, rln_connect_peer, rln_get_channel_id, rln_atomic_taker, rln_list_swaps, rln_get_swap, rln_list_payments, rln_create_ln_invoice, rln_create_rgb_invoice, rln_list_transfers, rln_create_utxos, rln_issue_asset
|
|
5
|
+
triggers: node, nodeinfo, pubkey, peer, channels, channel capacity, list channels, open channel, close channel, inbound, capacity, asset balance, whitelist, taker, swapstring, swaps, payments, invoice, receive, send asset, send rgb, on-chain address, deposit, rgb invoice, ln invoice, issue, mint, new token, nft, utxos, transfers
|
|
6
6
|
metadata:
|
|
7
7
|
author: kaleidoswap
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.3.2"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# RGB Lightning Node (taker-side)
|
|
@@ -79,6 +79,58 @@ hold / what's my USDT balance".
|
|
|
79
79
|
Balance for one RGB asset by id. Use after `rln_list_assets` gave you the id,
|
|
80
80
|
or when the user names a specific asset.
|
|
81
81
|
|
|
82
|
+
| Field | Meaning |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `settled` / `spendable` | On-chain (RGB_L1), UTXO-bound |
|
|
85
|
+
| `future` | Pending settlement |
|
|
86
|
+
| `offchain_outbound` | In a Lightning channel: what you can **send** |
|
|
87
|
+
| `offchain_inbound` | Channel receive capacity for this asset |
|
|
88
|
+
|
|
89
|
+
For USDT/XAUT held in an RGB channel, report `offchain_outbound`; `spendable`
|
|
90
|
+
is 0 and that is normal.
|
|
91
|
+
|
|
92
|
+
### `rln_refresh_transfers` — no args
|
|
93
|
+
Syncs pending RGB transfers. Call it before re-reading a balance or transfer
|
|
94
|
+
status that looks stale (e.g. right after an invoice was paid or a swap filled).
|
|
95
|
+
|
|
96
|
+
### `rln_get_address` — no args
|
|
97
|
+
An on-chain BTC address of the node, for funding it. Not an RGB invoice and not
|
|
98
|
+
a Lightning invoice.
|
|
99
|
+
|
|
100
|
+
### `rln_send_btc` — { address, amount_sat, fee_rate? } — 🔒 confirm-gated
|
|
101
|
+
Sends on-chain BTC from the node. Only when the user gave both the address and
|
|
102
|
+
the amount.
|
|
103
|
+
|
|
104
|
+
### `rln_send_asset` — 🔒 confirm-gated
|
|
105
|
+
Sends an RGB asset to the recipient encoded in an RGB invoice. Use the argument
|
|
106
|
+
names of the schema you were given: in-app wallets take `{ asset, amount, to }`
|
|
107
|
+
(ticker or asset_id, units, the invoice); kaleido-mcp takes
|
|
108
|
+
`{ asset_id, amount, recipient_id }`. Never invent a recipient.
|
|
109
|
+
|
|
110
|
+
### `rln_pay_invoice` — { invoice } — 🔒 confirm-gated
|
|
111
|
+
Pays a BOLT11 Lightning invoice from the node. Pass the full invoice string.
|
|
112
|
+
|
|
113
|
+
### Channels and peers
|
|
114
|
+
- `rln_connect_peer { peer_pubkey_and_addr }` — `pubkey@host:port`. Needed
|
|
115
|
+
before opening a channel to a peer the node has never seen.
|
|
116
|
+
- `rln_open_channel { peer_pubkey_and_addr, capacity_sat, asset_id?, asset_amount?, push_msat?, is_public? }`
|
|
117
|
+
— 🔒 confirm-gated. Locks on-chain BTC (and optionally an RGB asset) into a
|
|
118
|
+
channel. Opening is asynchronous: report the `temporary_channel_id` and
|
|
119
|
+
suggest checking `rln_list_channels`.
|
|
120
|
+
- `rln_get_channel_id { temporary_channel_id }` — the final `channel_id` once
|
|
121
|
+
the channel is established.
|
|
122
|
+
- `rln_close_channel { channel_id, peer_pubkey, force? }` — 🔒 confirm-gated.
|
|
123
|
+
Both values come from `rln_list_channels`. `force` only for an unresponsive
|
|
124
|
+
peer.
|
|
125
|
+
|
|
126
|
+
### `rln_list_payments` — { limit? }
|
|
127
|
+
Recent Lightning payments, sent and received. Use for "did I get paid" /
|
|
128
|
+
"what did I pay" over Lightning.
|
|
129
|
+
|
|
130
|
+
### `rln_list_swaps` / `rln_get_swap { payment_hash, taker? }`
|
|
131
|
+
Atomic swaps as the node sees them (HTLC status). For the maker-side status use
|
|
132
|
+
`kaleidoswap_atomic_status`.
|
|
133
|
+
|
|
82
134
|
### `rln_atomic_taker` — { swapstring } — 🔒 confirm-gated
|
|
83
135
|
Tell the node "I accept this swap." Args: the `swapstring` returned by
|
|
84
136
|
`kaleidoswap_atomic_init`. The node validates and stores it; **no funds move
|
|
@@ -92,22 +144,67 @@ Call this **after** `kaleidoswap_atomic_init` and **before**
|
|
|
92
144
|
### `rln_create_ln_invoice` — Lightning invoice for receiving sats
|
|
93
145
|
Args:
|
|
94
146
|
- `amount_sats` (optional) — omit for an amountless invoice.
|
|
95
|
-
- `expiry_sec` (default 3600) — invoice TTL in seconds.
|
|
96
|
-
- `asset_id` + `asset_amount` — optional, for RGB-over-Lightning.
|
|
147
|
+
- `expiry_sec` (default 3600, kaleido-mcp only) — invoice TTL in seconds.
|
|
97
148
|
|
|
98
149
|
Use when the user wants to **receive** a Lightning payment. Do NOT call inside
|
|
99
150
|
an atomic swap flow unless the user explicitly asked to invoice someone.
|
|
100
151
|
|
|
101
152
|
### `rln_create_rgb_invoice` — on-chain RGB receive invoice
|
|
102
|
-
Args:
|
|
103
|
-
- `
|
|
104
|
-
- `
|
|
105
|
-
|
|
106
|
-
|
|
153
|
+
Args (use the names in your schema):
|
|
154
|
+
- in-app wallets: `asset` (ticker or asset_id) + `amount`.
|
|
155
|
+
- kaleido-mcp: `asset_id` (omit for an any-asset invoice), `amount` (display
|
|
156
|
+
units), `duration_seconds` (default 86400).
|
|
157
|
+
|
|
158
|
+
Reply with the full `invoice` string; the payer needs all of it.
|
|
107
159
|
|
|
108
160
|
Use when the user wants to **receive** an RGB asset directly (not over
|
|
109
161
|
Lightning). Outside the atomic swap flow.
|
|
110
162
|
|
|
163
|
+
### `rln_list_transfers` — { asset_id }
|
|
164
|
+
Transfers for one asset (issuance, sends, receives) with `status`
|
|
165
|
+
(`WaitingCounterparty`, `WaitingConfirmations`, `Settled`, `Failed`). Use it to
|
|
166
|
+
answer "has my RGB invoice been paid?". Pass the `asset_id` (`rgb:…`) from
|
|
167
|
+
`rln_list_assets` or `rln_issue_asset`; in-app wallets also accept a ticker.
|
|
168
|
+
|
|
169
|
+
### `rln_create_utxos` — { num?, size?, up_to?, fee_rate? } — 🔒 confirm-gated
|
|
170
|
+
Creates colorable UTXOs (spends a little on-chain BTC). A fresh node needs
|
|
171
|
+
these before it can **issue** or **receive** RGB assets. Call it when an RGB
|
|
172
|
+
call fails with "no available UTXOs", then retry the original action. The
|
|
173
|
+
defaults (`num` 5, `fee_rate` 1) are fine; set `up_to: true` to only top up to
|
|
174
|
+
`num` free UTXOs.
|
|
175
|
+
|
|
176
|
+
### `rln_issue_asset` — { name, ticker?, amount?, precision?, schema?, details? } — 🔒 confirm-gated
|
|
177
|
+
Creates a **new** RGB asset owned by this node. `schema`: `NIA` fungible token
|
|
178
|
+
(default), `CFA` collectible, `UDA` unique asset / NFT (supply 1).
|
|
179
|
+
- `ticker` (uppercase, 1–8 letters/digits) is required for `NIA` and `UDA`.
|
|
180
|
+
- `amount` is the total supply in display units, required for `NIA` and `CFA`;
|
|
181
|
+
the raw supply is `amount × 10^precision` (`precision` defaults to 0).
|
|
182
|
+
- `details` is an optional description for `CFA` and `UDA`.
|
|
183
|
+
|
|
184
|
+
Reply with the returned `asset_id` — the user needs it to invoice or send the
|
|
185
|
+
new asset.
|
|
186
|
+
|
|
187
|
+
Examples:
|
|
188
|
+
- "issue 1000 TICKET tokens called Hackathon Ticket" →
|
|
189
|
+
`rln_issue_asset { name: "Hackathon Ticket", ticker: "TICKET", amount: 1000 }`
|
|
190
|
+
- "mint an NFT called Genesis Badge" →
|
|
191
|
+
`rln_issue_asset { name: "Genesis Badge", ticker: "GENESISB", schema: "UDA" }`
|
|
192
|
+
- "has anyone paid my TICKET invoice?" → `rln_refresh_transfers` →
|
|
193
|
+
`rln_list_transfers { asset_id: "<TICKET asset_id from rln_list_assets>" }`
|
|
194
|
+
|
|
195
|
+
kaleido-mcp exposes these three as `wdk_issue_asset`, `wdk_create_utxos` and
|
|
196
|
+
`wdk_list_transfers`, with the same arguments and `rln_*` aliases.
|
|
197
|
+
|
|
198
|
+
### Recipe: issue your own RGB asset
|
|
199
|
+
1. `rln_get_node_info` — the node is reachable and unlocked.
|
|
200
|
+
2. `rln_create_utxos {}` — skip if the node already has free colored UTXOs;
|
|
201
|
+
wait for the funding tx to confirm before issuing.
|
|
202
|
+
3. `rln_issue_asset { name, ticker, amount }` — confirm with the user first.
|
|
203
|
+
4. `rln_create_rgb_invoice` on the receiver's node, then `rln_send_asset` with
|
|
204
|
+
the new `asset_id` to distribute it.
|
|
205
|
+
5. `rln_refresh_transfers` → `rln_list_transfers { asset_id }` until the
|
|
206
|
+
transfer is `Settled`.
|
|
207
|
+
|
|
111
208
|
## The maker / node split
|
|
112
209
|
|
|
113
210
|
A user-driven swap on KaleidoSwap is a two-service flow. Keep them straight:
|
|
@@ -125,6 +222,14 @@ The node's two contributions to the swap are the **pubkey** and the
|
|
|
125
222
|
**whitelist ack** — nothing more. Don't reach for `/makerinit` or
|
|
126
223
|
`/makerexecute`; those are for nodes that act AS the maker, which is not us.
|
|
127
224
|
|
|
225
|
+
## Safety
|
|
226
|
+
|
|
227
|
+
- Show amount, recipient and asset before any send; the engine also asks for
|
|
228
|
+
confirmation on every 🔒 tool.
|
|
229
|
+
- Flag a send above half of the available balance.
|
|
230
|
+
- If the node is unreachable or the wallet is locked, use the `kaleido-node`
|
|
231
|
+
skill (start / unlock) before retrying.
|
|
232
|
+
|
|
128
233
|
## Reply style
|
|
129
234
|
|
|
130
235
|
- One short sentence built from the tool result.
|
package/src/index.ts
CHANGED
|
@@ -129,6 +129,9 @@ export {
|
|
|
129
129
|
// ── Buy-an-asset-channel recipe (opt-in — register via Funnel.recipes) ─────
|
|
130
130
|
export { buyAssetChannelRecipe, extractBuyAsset } from './recipe/buy-asset-channel.js';
|
|
131
131
|
|
|
132
|
+
// ── Issue-an-RGB-asset recipe (opt-in — register via Funnel.recipes) ───────
|
|
133
|
+
export { issueAssetRecipe, extractIssueAsset } from './recipe/issue-asset.js';
|
|
134
|
+
|
|
132
135
|
// ── Recipes (mobile multi-step: "recipes, not planning") ───────────────────
|
|
133
136
|
export { runRecipe, extractSlots, RecipeRegistry } from './recipe/runner.js';
|
|
134
137
|
export type { RunRecipeOptions } from './recipe/runner.js';
|
package/src/qvac/delegate.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* they stay shared + testable; the host passes the result to
|
|
5
5
|
* `startQVACProvider({ firewall })` / `loadModel({ delegate })`.
|
|
6
6
|
*
|
|
7
|
+
* P2P delegation exists in @qvac/sdk 0.13–0.18 only; 0.19 removed both calls.
|
|
8
|
+
*
|
|
7
9
|
* Security note: a QVAC provider is reachable by anyone who learns its
|
|
8
10
|
* Hyperswarm public key. Advertising with no firewall means any such peer can
|
|
9
11
|
* run inference on your machine. Use {@link allowListFirewall} so a desktop
|
package/src/qvac/index.ts
CHANGED
package/src/qvac/parse.ts
CHANGED
|
@@ -17,6 +17,9 @@ export interface QvacTurnStats {
|
|
|
17
17
|
tokensPerSecond?: number;
|
|
18
18
|
totalTokens?: number;
|
|
19
19
|
promptTokens?: number;
|
|
20
|
+
/** Tokens generated this turn (what the SDK reports; `totalTokens` is not emitted). */
|
|
21
|
+
generatedTokens?: number;
|
|
22
|
+
timeToFirstToken?: number;
|
|
20
23
|
contextSize?: number;
|
|
21
24
|
totalTime?: number;
|
|
22
25
|
}
|
|
@@ -30,8 +33,8 @@ export interface QvacFinalLike {
|
|
|
30
33
|
/** Tool calls the model requested this turn (empty ⇒ final answer). */
|
|
31
34
|
toolCalls?: Array<{ id?: string; name: string; arguments?: Record<string, unknown> }>;
|
|
32
35
|
/**
|
|
33
|
-
* Why generation stopped
|
|
34
|
-
*
|
|
36
|
+
* Why generation stopped: `"length"` when the token budget is exhausted,
|
|
37
|
+
* `"cancelled"` on abort, `"eos"`/`"stopSequence"`/`undefined` on a natural stop. We surface
|
|
35
38
|
* it so the funnel can tell a truncated tool-call from a complete one.
|
|
36
39
|
*/
|
|
37
40
|
stopReason?: 'length' | 'cancelled' | string;
|
|
@@ -77,6 +77,18 @@ describe('createQvacProvider.runTurn', () => {
|
|
|
77
77
|
expect(params.generationParams).toEqual({ temp: 0.9, predict: 99 });
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
it('sends JSON-Schema tools in the SDK Tool shape so arguments survive', async () => {
|
|
81
|
+
const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
|
|
82
|
+
const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1' });
|
|
83
|
+
await p.runTurn({
|
|
84
|
+
messages: [{ role: 'user', content: 'x' }],
|
|
85
|
+
tools: [{ name: 'echo', description: 'e', parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } }],
|
|
86
|
+
});
|
|
87
|
+
expect(calls[0].tools).toEqual([
|
|
88
|
+
{ type: 'function', name: 'echo', description: 'e', parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } },
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
|
|
80
92
|
it('omits generationParams when no temperature/maxTokens is set (keeps SDK defaults)', async () => {
|
|
81
93
|
const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
|
|
82
94
|
const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1' });
|
|
@@ -101,6 +113,32 @@ describe('createQvacProvider.runTurn', () => {
|
|
|
101
113
|
expect(out.text).toMatch(/thinking budget/i);
|
|
102
114
|
});
|
|
103
115
|
|
|
116
|
+
it('returns a cancelled turn when the SDK rejects final on abort', async () => {
|
|
117
|
+
const cancel = vi.fn(async () => {});
|
|
118
|
+
const fn = () => ({
|
|
119
|
+
requestId: 'req-a',
|
|
120
|
+
events: (async function* () {})(),
|
|
121
|
+
final: Promise.reject(Object.assign(new Error('cancelled'), { requestId: 'req-a', partial: {} })),
|
|
122
|
+
});
|
|
123
|
+
const ac = new AbortController();
|
|
124
|
+
ac.abort();
|
|
125
|
+
const p = createQvacProvider({ completion: fn as any, cancel: cancel as any, getModelId: () => 'm1' });
|
|
126
|
+
const out = await p.runTurn({ messages: [{ role: 'user', content: 'x' }], tools: [], signal: ac.signal });
|
|
127
|
+
expect(cancel).toHaveBeenCalledWith({ requestId: 'req-a' });
|
|
128
|
+
expect(out.inference?.status).toBe('cancelled');
|
|
129
|
+
expect(out.toolCalls).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('derives token counts from the SDK generatedTokens stat', async () => {
|
|
133
|
+
const { fn } = fakeCompletion({
|
|
134
|
+
contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' },
|
|
135
|
+
stats: { promptTokens: 100, generatedTokens: 20, tokensPerSecond: 12, backendDevice: 'gpu' },
|
|
136
|
+
});
|
|
137
|
+
const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1' });
|
|
138
|
+
const out = await p.runTurn({ messages: [{ role: 'user', content: 'x' }], tools: [] });
|
|
139
|
+
expect(out.inference).toMatchObject({ promptTokens: 100, totalTokens: 120, completionTokens: 20, backendDevice: 'gpu' });
|
|
140
|
+
});
|
|
141
|
+
|
|
104
142
|
it('streams visible content tokens to onToken', async () => {
|
|
105
143
|
const { fn } = fakeCompletion(
|
|
106
144
|
{ contentText: 'Hi there', toolCalls: [], raw: { fullText: 'Hi there' } },
|
package/src/qvac/provider.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type * as QvacSdk from '@qvac/sdk';
|
|
|
20
20
|
import type { InferenceMetrics, LLMProvider, TurnInput, TurnOutput } from '../providers/types.js';
|
|
21
21
|
import type { QvacTurnStats } from './parse.js';
|
|
22
22
|
import { consumeRun } from './stream.js';
|
|
23
|
+
import { toQvacTools } from './tools.js';
|
|
23
24
|
|
|
24
25
|
type CompletionFn = typeof QvacSdk.completion;
|
|
25
26
|
type CancelFn = typeof QvacSdk.cancel;
|
|
@@ -82,19 +83,11 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
|
82
83
|
? [{ role: 'system', content: input.system }, ...input.messages]
|
|
83
84
|
: input.messages;
|
|
84
85
|
|
|
85
|
-
// Tools are forwarded by schema only
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
? input.tools.map((t) => ({
|
|
91
|
-
name: t.name,
|
|
92
|
-
description: t.description,
|
|
93
|
-
parameters: t.parameters,
|
|
94
|
-
}))
|
|
95
|
-
: undefined;
|
|
96
|
-
|
|
97
|
-
// QVAC 0.13 nests sampling under `generationParams`; top-level
|
|
86
|
+
// Tools are forwarded by schema only — the Engine validates + executes.
|
|
87
|
+
// JSON-Schema tools are normalised to the SDK's Tool shape (see tools.ts).
|
|
88
|
+
const tools = toQvacTools(input.tools);
|
|
89
|
+
|
|
90
|
+
// QVAC (0.13+) nests sampling under `generationParams`; top-level
|
|
98
91
|
// `temperature`/`max_tokens` (as older rate code passed) are dropped by
|
|
99
92
|
// validation, so the cap silently no-op'd. Build it here, and only send it
|
|
100
93
|
// when a value is set so a host that passes neither keeps SDK defaults.
|
|
@@ -155,8 +148,11 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
|
155
148
|
// instead of an empty bubble so the agentic loop ends cleanly.
|
|
156
149
|
const text =
|
|
157
150
|
result.text || (result.thinkingBudgetExceeded ? THINKING_BUDGET_FALLBACK : result.text);
|
|
158
|
-
const totalTokens = result.stats?.totalTokens;
|
|
159
151
|
const promptTokens = result.stats?.promptTokens;
|
|
152
|
+
const generated = result.stats?.generatedTokens;
|
|
153
|
+
const totalTokens =
|
|
154
|
+
result.stats?.totalTokens ??
|
|
155
|
+
(typeof generated === 'number' && typeof promptTokens === 'number' ? promptTokens + generated : undefined);
|
|
160
156
|
const inference: InferenceMetrics = {
|
|
161
157
|
requestId: result.requestId,
|
|
162
158
|
durationMs: result.timing.durationMs,
|
package/src/qvac/stream.test.ts
CHANGED
|
@@ -112,4 +112,32 @@ describe('consumeRun', () => {
|
|
|
112
112
|
);
|
|
113
113
|
expect(out.timing).toEqual({ ttftMs: 45, durationMs: 90 });
|
|
114
114
|
});
|
|
115
|
+
|
|
116
|
+
it('folds a rejected final (InferenceCancelledError) into a cancelled turn', async () => {
|
|
117
|
+
class InferenceCancelledError extends Error {
|
|
118
|
+
constructor(readonly requestId: string, readonly partial: { text?: string }) {
|
|
119
|
+
super('cancelled');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const run: CompletionRunLike = {
|
|
123
|
+
requestId: 'req-c',
|
|
124
|
+
events: (async function* () {
|
|
125
|
+
yield { type: 'contentDelta', text: 'Partial' };
|
|
126
|
+
})(),
|
|
127
|
+
final: Promise.reject(new InferenceCancelledError('req-c', { text: 'Partial' })),
|
|
128
|
+
};
|
|
129
|
+
const out = await consumeRun(run);
|
|
130
|
+
expect(out.stopReason).toBe('cancelled');
|
|
131
|
+
expect(out.text).toBe('Partial');
|
|
132
|
+
expect(out.toolCalls).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('re-throws a rejected final that is not a cancellation', async () => {
|
|
136
|
+
const run: CompletionRunLike = {
|
|
137
|
+
requestId: 'req-e',
|
|
138
|
+
events: (async function* () {})(),
|
|
139
|
+
final: Promise.reject(new Error('boom')),
|
|
140
|
+
};
|
|
141
|
+
await expect(consumeRun(run)).rejects.toThrow('boom');
|
|
142
|
+
});
|
|
115
143
|
});
|