@kaleidorg/mind 0.8.0 → 0.9.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 +7 -5
- package/dist/bitrefill/index.d.ts +4 -0
- package/dist/bitrefill/index.d.ts.map +1 -0
- package/dist/bitrefill/index.js +3 -0
- package/dist/bitrefill/index.js.map +1 -0
- package/dist/capabilities.d.ts +3 -3
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +4 -4
- package/dist/capabilities.js.map +1 -1
- package/dist/engine/answer.d.ts +37 -0
- package/dist/engine/answer.d.ts.map +1 -0
- package/dist/engine/answer.js +35 -0
- package/dist/engine/answer.js.map +1 -0
- package/dist/engine.d.ts +15 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +171 -170
- package/dist/engine.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/fastpath/fastpath.d.ts +6 -1
- package/dist/fastpath/fastpath.d.ts.map +1 -1
- package/dist/fastpath/fastpath.js +16 -2
- package/dist/fastpath/fastpath.js.map +1 -1
- package/dist/fastpath/render.d.ts +2 -0
- package/dist/fastpath/render.d.ts.map +1 -0
- package/dist/fastpath/render.js +58 -0
- package/dist/fastpath/render.js.map +1 -0
- package/dist/flashnet/index.d.ts +5 -0
- package/dist/flashnet/index.d.ts.map +1 -0
- package/dist/flashnet/index.js +4 -0
- package/dist/flashnet/index.js.map +1 -0
- package/dist/funnel.d.ts.map +1 -1
- package/dist/funnel.js +11 -14
- package/dist/funnel.js.map +1 -1
- package/dist/index.d.ts +6 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -27
- package/dist/index.js.map +1 -1
- package/dist/kaleidoswap/index.d.ts +8 -0
- package/dist/kaleidoswap/index.d.ts.map +1 -0
- package/dist/kaleidoswap/index.js +7 -0
- package/dist/kaleidoswap/index.js.map +1 -0
- package/dist/knowledge/index.d.ts +9 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +6 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/lsps1/index.d.ts +4 -0
- package/dist/lsps1/index.d.ts.map +1 -0
- package/dist/lsps1/index.js +3 -0
- package/dist/lsps1/index.js.map +1 -0
- package/dist/providers/types.d.ts +16 -3
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +3 -3
- package/dist/qvac/index.d.ts +0 -1
- package/dist/qvac/index.d.ts.map +1 -1
- package/dist/qvac/index.js +0 -1
- package/dist/qvac/index.js.map +1 -1
- package/dist/qvac/provider.d.ts +15 -6
- package/dist/qvac/provider.d.ts.map +1 -1
- package/dist/qvac/provider.js +14 -16
- package/dist/qvac/provider.js.map +1 -1
- package/dist/qvac/stream.d.ts +4 -3
- package/dist/qvac/stream.d.ts.map +1 -1
- package/dist/qvac/stream.js.map +1 -1
- package/dist/qvac/voice.d.ts +1 -1
- package/dist/submarine/index.d.ts +5 -0
- package/dist/submarine/index.d.ts.map +1 -0
- package/dist/submarine/index.js +4 -0
- package/dist/submarine/index.js.map +1 -0
- package/dist/tools/in-process.d.ts +2 -2
- package/dist/tools/in-process.js +2 -2
- package/package.json +32 -2
- package/src/bitrefill/index.ts +13 -0
- package/src/capabilities.ts +7 -7
- package/src/context/context.test.ts +2 -2
- package/src/engine/answer.ts +66 -0
- package/src/engine.test.ts +39 -0
- package/src/engine.ts +199 -188
- package/src/evidence.ts +1 -1
- package/src/fastpath/fastpath.ts +22 -2
- package/src/fastpath/render.test.ts +39 -0
- package/src/fastpath/render.ts +56 -0
- package/src/flashnet/index.ts +14 -0
- package/src/funnel.mind.test.ts +11 -15
- package/src/funnel.ts +11 -15
- package/src/index.ts +10 -107
- package/src/kaleidoswap/index.ts +19 -0
- package/src/knowledge/index.ts +14 -0
- package/src/lsps1/index.ts +13 -0
- package/src/providers/types.ts +16 -3
- package/src/qvac/index.ts +0 -8
- package/src/qvac/provider.test.ts +20 -17
- package/src/qvac/provider.ts +28 -26
- package/src/qvac/stream.ts +4 -3
- package/src/qvac/voice.ts +1 -1
- package/src/submarine/index.ts +17 -0
- package/src/tools/in-process.ts +2 -2
- package/dist/qvac/delegate.d.ts +0 -50
- package/dist/qvac/delegate.d.ts.map +0 -1
- package/dist/qvac/delegate.js +0 -53
- package/dist/qvac/delegate.js.map +0 -1
- package/src/qvac/delegate.test.ts +0 -68
- package/src/qvac/delegate.ts +0 -73
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Flashnet (Spark-native AMM): tool contract and swap recipe. */
|
|
2
|
+
export {
|
|
3
|
+
FLASHNET_TOOLS,
|
|
4
|
+
FLASHNET_SPEND_TOOLS,
|
|
5
|
+
isFlashnetSpendTool,
|
|
6
|
+
getFlashnetTool,
|
|
7
|
+
bindFlashnetTools,
|
|
8
|
+
} from './contract.js';
|
|
9
|
+
export type {
|
|
10
|
+
FlashnetToolDef,
|
|
11
|
+
FlashnetHandler,
|
|
12
|
+
BindFlashnetOptions,
|
|
13
|
+
} from './contract.js';
|
|
14
|
+
export { flashnetSwapRecipe } from '../recipe/flashnet-swap.js';
|
package/src/funnel.mind.test.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* Fully deterministic (no node/model/maker), so it runs in CI. Live tool
|
|
18
18
|
* execution against a real node is the separate mcp.live.test.ts.
|
|
19
19
|
*/
|
|
20
|
+
import type { FastIntent } from './fastpath/fastpath.js';
|
|
20
21
|
import { describe, expect, it } from 'vitest';
|
|
21
22
|
import { Funnel } from './funnel.js';
|
|
22
23
|
import { ToolRegistry } from './tools/registry.js';
|
|
@@ -83,7 +84,7 @@ function searchMerchants(query: string): string {
|
|
|
83
84
|
*/
|
|
84
85
|
function buildMind(
|
|
85
86
|
provider: LLMProvider,
|
|
86
|
-
opts: { skills?: Skill[]; log?: (m: string) => void } = {},
|
|
87
|
+
opts: { skills?: Skill[]; log?: (m: string) => void; fastIntents?: FastIntent[] } = {},
|
|
87
88
|
): { funnel: Funnel; calls: Array<{ name: string; args: any }> } {
|
|
88
89
|
const calls: Array<{ name: string; args: any }> = [];
|
|
89
90
|
const tool = (name: string, response: any, spend = false) => ({
|
|
@@ -140,27 +141,21 @@ function buildMind(
|
|
|
140
141
|
]);
|
|
141
142
|
|
|
142
143
|
return {
|
|
143
|
-
funnel: new Funnel({ provider, tools, recipes: DESKTOP_RECIPES, maxTurns: 8, skills: opts.skills, log: opts.log }),
|
|
144
|
+
funnel: new Funnel({ provider, tools, recipes: DESKTOP_RECIPES, maxTurns: 8, skills: opts.skills, log: opts.log, ...(opts.fastIntents ? { fastIntents: opts.fastIntents } : {}) }),
|
|
144
145
|
calls,
|
|
145
146
|
};
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
describe('desktop mind — balance', () => {
|
|
149
|
-
it('
|
|
150
|
-
const { funnel, calls } = buildMind(
|
|
151
|
-
scripted([
|
|
152
|
-
{ text: '', toolCalls: [{ name: 'rln_get_balances', arguments: {} }] },
|
|
153
|
-
{ text: 'You have 1,949,753 sats in Lightning.' },
|
|
154
|
-
]),
|
|
155
|
-
);
|
|
150
|
+
it('answers "what\'s my balance?" on the fast path with rln_get_balances, no model', async () => {
|
|
151
|
+
const { funnel, calls } = buildMind(scripted([]));
|
|
156
152
|
|
|
157
153
|
const res = await funnel.runTurn("what's my balance?");
|
|
158
154
|
|
|
159
|
-
expect(res.tier).toBe('
|
|
160
|
-
expect(calls.map((c) => c.name)).
|
|
161
|
-
|
|
162
|
-
expect((
|
|
163
|
-
expect(res.text).toBeTruthy();
|
|
155
|
+
expect(res.tier).toBe('fast');
|
|
156
|
+
expect(calls.map((c) => c.name)).toEqual(['rln_get_balances']);
|
|
157
|
+
expect((res.data as { lightning_balance_sat?: number })?.lightning_balance_sat).toBe(1_949_753);
|
|
158
|
+
expect(res.text).toBe('On-chain: 100,000 sats spendable.\nLightning: 1,949,753 sats.');
|
|
164
159
|
});
|
|
165
160
|
});
|
|
166
161
|
|
|
@@ -359,7 +354,8 @@ describe('desktop mind — skill scoping (real skills)', () => {
|
|
|
359
354
|
{ text: '', toolCalls: [{ name: 'rln_get_balances', arguments: {} }] },
|
|
360
355
|
{ text: 'You have 1,949,753 sats.' },
|
|
361
356
|
]),
|
|
362
|
-
|
|
357
|
+
// Fast path off, so the balance ask exercises skill scoping.
|
|
358
|
+
{ skills: SKILLS, log: (m) => logs.push(m), fastIntents: [] },
|
|
363
359
|
);
|
|
364
360
|
|
|
365
361
|
const res = await funnel.runTurn("what's my balance?");
|
package/src/funnel.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { Engine } from './engine.js';
|
|
23
23
|
import type { ToolCrushOptions } from './context/compress.js';
|
|
24
24
|
import type { ToolRegistry } from './tools/registry.js';
|
|
25
|
+
import { defaultRenderFast } from './fastpath/render.js';
|
|
25
26
|
import { FastPath, WALLET_FAST_INTENTS } from './fastpath/fastpath.js';
|
|
26
27
|
import type { FastIntent } from './fastpath/fastpath.js';
|
|
27
28
|
import { RecipeRegistry, runRecipe } from './recipe/runner.js';
|
|
@@ -176,18 +177,6 @@ export interface FunnelOptions {
|
|
|
176
177
|
topKRag?: number;
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
function defaultRenderFast(intent: string, r: any): string {
|
|
180
|
-
if (intent === 'balance') {
|
|
181
|
-
const sats = Number(r?.total_sats ?? 0);
|
|
182
|
-
const n = r?.layers?.length ?? 0;
|
|
183
|
-
return `You have ${sats.toLocaleString()} sats${n > 1 ? ` across ${n} layers` : ''}.`;
|
|
184
|
-
}
|
|
185
|
-
if (intent === 'address') {
|
|
186
|
-
return r?.address ? `Here's your receive address:\n\n\`${r.address}\`` : 'No address available right now.';
|
|
187
|
-
}
|
|
188
|
-
return `Bitcoin is $${Number(r?.price_usd ?? 0).toLocaleString()}.`;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
180
|
export class Funnel {
|
|
192
181
|
private readonly provider: LLMProvider;
|
|
193
182
|
private readonly registry: ToolRegistry;
|
|
@@ -251,9 +240,16 @@ export class Funnel {
|
|
|
251
240
|
// tool — a partial tool surface (e.g. desktop without the core aggregate
|
|
252
241
|
// helpers) falls through to the agentic tier instead of erroring.
|
|
253
242
|
const fast = this.fastPath.select(text);
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
243
|
+
let fastTool: string | undefined;
|
|
244
|
+
for (const name of fast?.tools ?? []) {
|
|
245
|
+
if (await this.registry.getDef(name)) {
|
|
246
|
+
fastTool = name;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (fast && fastTool) {
|
|
251
|
+
this.log(`tier=fast-path → ${fastTool}`);
|
|
252
|
+
const r = await this.registry.execute(fastTool, fast.args);
|
|
257
253
|
return { text: this.renderFast(fast.intent.name, r), tier: 'fast', route: fast.intent.name, intent: fast.intent.name, data: r };
|
|
258
254
|
}
|
|
259
255
|
|
package/src/index.ts
CHANGED
|
@@ -71,102 +71,9 @@ export {
|
|
|
71
71
|
export { annotateRgbBalances, fixRgbBalanceUnits, formatRgbAmount } from './context/rgb-units.js';
|
|
72
72
|
export type { ArgValidation, UngroundedItem, WalletAction } from './guards.js';
|
|
73
73
|
|
|
74
|
-
// ── KaleidoSwap maker tool contract (single source of truth) ────────────────
|
|
75
|
-
export {
|
|
76
|
-
KALEIDOSWAP_TOOLS,
|
|
77
|
-
KALEIDOSWAP_SPEND_TOOLS,
|
|
78
|
-
isKaleidoswapSpendTool,
|
|
79
|
-
getKaleidoswapTool,
|
|
80
|
-
kaleidoswapTools,
|
|
81
|
-
bindKaleidoswapTools,
|
|
82
|
-
} from './kaleidoswap/contract.js';
|
|
83
|
-
export type {
|
|
84
|
-
KaleidoswapGroup,
|
|
85
|
-
KaleidoswapToolDef,
|
|
86
|
-
KaleidoswapHandler,
|
|
87
|
-
BindKaleidoswapOptions,
|
|
88
|
-
} from './kaleidoswap/contract.js';
|
|
89
|
-
|
|
90
|
-
// ── LSPS1 (Lightning Service Provider channel orders) ───────────────────────
|
|
91
|
-
export {
|
|
92
|
-
LSPS1_TOOLS,
|
|
93
|
-
LSPS1_SPEND_TOOLS,
|
|
94
|
-
isLsps1SpendTool,
|
|
95
|
-
getLsps1Tool,
|
|
96
|
-
bindLsps1Tools,
|
|
97
|
-
} from './lsps1/contract.js';
|
|
98
|
-
export type {
|
|
99
|
-
Lsps1ToolDef,
|
|
100
|
-
Lsps1Handler,
|
|
101
|
-
BindLsps1Options,
|
|
102
|
-
} from './lsps1/contract.js';
|
|
103
|
-
|
|
104
|
-
// ── KaleidoSwap /v2 submarine swaps (pay Lightning from Liquid) ─────────────
|
|
105
|
-
export {
|
|
106
|
-
SUBMARINE_TOOLS,
|
|
107
|
-
SUBMARINE_SPEND_TOOLS,
|
|
108
|
-
SUBMARINE_FROM_ASSETS,
|
|
109
|
-
isSubmarineSpendTool,
|
|
110
|
-
getSubmarineTool,
|
|
111
|
-
formatSubmarineAmount,
|
|
112
|
-
bindSubmarineTools,
|
|
113
|
-
} from './submarine/contract.js';
|
|
114
|
-
export type {
|
|
115
|
-
SubmarineToolDef,
|
|
116
|
-
SubmarineFromAsset,
|
|
117
|
-
SubmarineHandler,
|
|
118
|
-
BindSubmarineOptions,
|
|
119
|
-
} from './submarine/contract.js';
|
|
120
|
-
|
|
121
|
-
// ── Bitrefill (gift cards / mobile top-ups / eSIMs) ─────────────────────────
|
|
122
|
-
export {
|
|
123
|
-
BITREFILL_TOOLS,
|
|
124
|
-
BITREFILL_SPEND_TOOLS,
|
|
125
|
-
isBitrefillSpendTool,
|
|
126
|
-
getBitrefillTool,
|
|
127
|
-
bindBitrefillTools,
|
|
128
|
-
} from './bitrefill/contract.js';
|
|
129
|
-
export type {
|
|
130
|
-
BitrefillToolDef,
|
|
131
|
-
BitrefillHandler,
|
|
132
|
-
BindBitrefillOptions,
|
|
133
|
-
} from './bitrefill/contract.js';
|
|
134
|
-
|
|
135
|
-
// ── Flashnet (Spark-native AMM — swaps over Spark) ──────────────────────────
|
|
136
|
-
export {
|
|
137
|
-
FLASHNET_TOOLS,
|
|
138
|
-
FLASHNET_SPEND_TOOLS,
|
|
139
|
-
isFlashnetSpendTool,
|
|
140
|
-
getFlashnetTool,
|
|
141
|
-
bindFlashnetTools,
|
|
142
|
-
} from './flashnet/contract.js';
|
|
143
|
-
export type {
|
|
144
|
-
FlashnetToolDef,
|
|
145
|
-
FlashnetHandler,
|
|
146
|
-
BindFlashnetOptions,
|
|
147
|
-
} from './flashnet/contract.js';
|
|
148
|
-
|
|
149
|
-
// ── KaleidoSwap recipes (opt-in — register via Funnel.recipes) ──
|
|
150
|
-
// price recipe is read-only (quote-only); atomic recipe runs the full swap.
|
|
151
|
-
// Register the price recipe FIRST so phrasings like "BTC price" are answered
|
|
152
|
-
// without firing any spend.
|
|
153
|
-
export { kaleidoswapPriceRecipe } from './recipe/kaleidoswap-price.js';
|
|
154
|
-
export { kaleidoswapAtomicRecipe } from './recipe/kaleidoswap-atomic.js';
|
|
155
|
-
export { flashnetSwapRecipe } from './recipe/flashnet-swap.js';
|
|
156
|
-
export {
|
|
157
|
-
kaleidoswapChannelOrderRecipe,
|
|
158
|
-
extractChannelOrder,
|
|
159
|
-
} from './recipe/kaleidoswap-channel-order.js';
|
|
160
|
-
|
|
161
|
-
// ── Buy-an-asset-channel recipe (opt-in — register via Funnel.recipes) ─────
|
|
162
|
-
export { buyAssetChannelRecipe, extractBuyAsset } from './recipe/buy-asset-channel.js';
|
|
163
|
-
|
|
164
74
|
// ── Issue-an-RGB-asset recipe (opt-in — register via Funnel.recipes) ───────
|
|
165
75
|
export { issueAssetRecipe, extractIssueAsset } from './recipe/issue-asset.js';
|
|
166
76
|
|
|
167
|
-
// ── Submarine-pay recipe (opt-in — register via Funnel.recipes, before payments) ──
|
|
168
|
-
export { submarinePayRecipe, extractSubmarinePay } from './recipe/submarine-pay.js';
|
|
169
|
-
|
|
170
77
|
// ── Recipes (mobile multi-step: "recipes, not planning") ───────────────────
|
|
171
78
|
export { runRecipe, extractSlots, RecipeRegistry } from './recipe/runner.js';
|
|
172
79
|
export type { RunRecipeOptions } from './recipe/runner.js';
|
|
@@ -224,20 +131,16 @@ export type { ToolCrushOptions, CrushResult } from './context/compress.js';
|
|
|
224
131
|
export { capabilityProfile } from './capabilities.js';
|
|
225
132
|
export type { CapabilityInput, MindCapabilities } from './capabilities.js';
|
|
226
133
|
|
|
227
|
-
// ──
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
export
|
|
232
|
-
export
|
|
233
|
-
export
|
|
234
|
-
export
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
BtcMapFetch,
|
|
238
|
-
LocationProvider,
|
|
239
|
-
LatLng,
|
|
240
|
-
} from './knowledge/btc-map.js';
|
|
134
|
+
// ── Domain packs ─────────────────────────────────────────────────────────────
|
|
135
|
+
// Each has its own subpath (`@kaleidorg/mind/kaleidoswap`, `/lsps1`,
|
|
136
|
+
// `/submarine`, `/bitrefill`, `/flashnet`, `/knowledge`). The root re-exports
|
|
137
|
+
// them for compatibility; 1.0 drops these re-exports.
|
|
138
|
+
export * from './kaleidoswap/index.js';
|
|
139
|
+
export * from './lsps1/index.js';
|
|
140
|
+
export * from './submarine/index.js';
|
|
141
|
+
export * from './bitrefill/index.js';
|
|
142
|
+
export * from './flashnet/index.js';
|
|
143
|
+
export * from './knowledge/index.js';
|
|
241
144
|
|
|
242
145
|
export { Engine } from './engine.js';
|
|
243
146
|
export type { EngineOptions, AgenticOptions, AgenticResult, ComposedSkill } from './engine.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** KaleidoSwap maker: tool contract and recipes (price, atomic swap, channel order, buy asset channel). */
|
|
2
|
+
export {
|
|
3
|
+
KALEIDOSWAP_TOOLS,
|
|
4
|
+
KALEIDOSWAP_SPEND_TOOLS,
|
|
5
|
+
isKaleidoswapSpendTool,
|
|
6
|
+
getKaleidoswapTool,
|
|
7
|
+
kaleidoswapTools,
|
|
8
|
+
bindKaleidoswapTools,
|
|
9
|
+
} from './contract.js';
|
|
10
|
+
export type {
|
|
11
|
+
KaleidoswapGroup,
|
|
12
|
+
KaleidoswapToolDef,
|
|
13
|
+
KaleidoswapHandler,
|
|
14
|
+
BindKaleidoswapOptions,
|
|
15
|
+
} from './contract.js';
|
|
16
|
+
export { kaleidoswapPriceRecipe } from '../recipe/kaleidoswap-price.js';
|
|
17
|
+
export { kaleidoswapAtomicRecipe } from '../recipe/kaleidoswap-atomic.js';
|
|
18
|
+
export { kaleidoswapChannelOrderRecipe, extractChannelOrder } from '../recipe/kaleidoswap-channel-order.js';
|
|
19
|
+
export { buyAssetChannelRecipe, extractBuyAsset } from '../recipe/buy-asset-channel.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Knowledge packs and corpus adapters for RAG, and the BTC Map merchant tool. */
|
|
2
|
+
export { BITCOIN_COPILOT_DOCS } from './bitcoin-copilot.js';
|
|
3
|
+
export { walletHistoryToDocuments, contactsToDocuments } from './wallet.js';
|
|
4
|
+
export type { WalletTx, Contact } from './wallet.js';
|
|
5
|
+
export { merchantsToDocuments } from './merchants.js';
|
|
6
|
+
export type { Merchant } from './merchants.js';
|
|
7
|
+
export { createBtcMapToolSource } from './btc-map.js';
|
|
8
|
+
export type {
|
|
9
|
+
BtcMapToolOptions,
|
|
10
|
+
BtcMapMerchant,
|
|
11
|
+
BtcMapFetch,
|
|
12
|
+
LocationProvider,
|
|
13
|
+
LatLng,
|
|
14
|
+
} from './btc-map.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** LSPS1 Lightning Service Provider channel orders: tool contract. */
|
|
2
|
+
export {
|
|
3
|
+
LSPS1_TOOLS,
|
|
4
|
+
LSPS1_SPEND_TOOLS,
|
|
5
|
+
isLsps1SpendTool,
|
|
6
|
+
getLsps1Tool,
|
|
7
|
+
bindLsps1Tools,
|
|
8
|
+
} from './contract.js';
|
|
9
|
+
export type {
|
|
10
|
+
Lsps1ToolDef,
|
|
11
|
+
Lsps1Handler,
|
|
12
|
+
BindLsps1Options,
|
|
13
|
+
} from './contract.js';
|
package/src/providers/types.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* LLMProvider — the only thing the Engine talks to for inference.
|
|
3
3
|
*
|
|
4
4
|
* Each host implements this over its own LLM transport:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
5
|
+
* - QVAC on-device (rate, desktop sidecar, CLI): `@kaleidorg/mind/qvac`
|
|
6
|
+
* - any OpenAI-compatible server (Ollama, LM Studio, hosted APIs):
|
|
7
|
+
* `@kaleidorg/mind/openai`
|
|
8
8
|
*
|
|
9
9
|
* The core package never imports any LLM SDK — it only depends on this
|
|
10
10
|
* interface, so it stays pure TS and bundles anywhere.
|
|
@@ -23,6 +23,17 @@ export interface TurnInput {
|
|
|
23
23
|
* empty or the provider has no such control.
|
|
24
24
|
*/
|
|
25
25
|
toolChoice?: ToolChoice;
|
|
26
|
+
/**
|
|
27
|
+
* `'off'` asks the provider to skip reasoning for this turn (e.g. a forced
|
|
28
|
+
* tool call). Ignored by providers without reasoning control.
|
|
29
|
+
*/
|
|
30
|
+
thinking?: 'off';
|
|
31
|
+
/**
|
|
32
|
+
* Same value on every call of one agentic run, whose history only grows.
|
|
33
|
+
* Providers with a session cache (QVAC `kvCache`) can then send only the new
|
|
34
|
+
* message instead of the whole prompt.
|
|
35
|
+
*/
|
|
36
|
+
sessionKey?: string;
|
|
26
37
|
/** Visible content tokens as they stream. */
|
|
27
38
|
onToken?: (token: string) => void;
|
|
28
39
|
signal?: AbortSignal;
|
|
@@ -82,6 +93,8 @@ export interface LLMProvider {
|
|
|
82
93
|
readonly name: string;
|
|
83
94
|
/** Run one completion turn. */
|
|
84
95
|
runTurn(input: TurnInput): Promise<TurnOutput>;
|
|
96
|
+
/** Drop whatever the provider cached for `sessionKey` (end of an agentic run). */
|
|
97
|
+
endSession?(sessionKey: string): Promise<void>;
|
|
85
98
|
/** Cancel an in-flight turn by request id, if the provider supports it. */
|
|
86
99
|
cancel?(requestId: string): Promise<void>;
|
|
87
100
|
}
|
package/src/qvac/index.ts
CHANGED
|
@@ -96,23 +96,6 @@ describe('createQvacProvider.runTurn', () => {
|
|
|
96
96
|
expect(calls[0].generationParams).toBeUndefined();
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
it('caps thinking by tokens — cancels the run and returns a fallback', async () => {
|
|
100
|
-
const cancel = vi.fn(async () => {});
|
|
101
|
-
const { fn } = fakeCompletion(
|
|
102
|
-
{ contentText: '', toolCalls: [], raw: { fullText: '' }, stopReason: 'cancelled' },
|
|
103
|
-
[{ type: 'thinkingDelta', text: 'z'.repeat(400) }], // ~100 tokens, budget 4 (+ backstop headroom)
|
|
104
|
-
);
|
|
105
|
-
const p = createQvacProvider({
|
|
106
|
-
completion: fn as any,
|
|
107
|
-
cancel: cancel as any,
|
|
108
|
-
getModelId: () => 'm1',
|
|
109
|
-
maxThinkingTokens: 4,
|
|
110
|
-
});
|
|
111
|
-
const out = await p.runTurn({ messages: [{ role: 'user', content: 'think hard' }], tools: [] });
|
|
112
|
-
expect(cancel).toHaveBeenCalledWith({ requestId: 'req-1' });
|
|
113
|
-
expect(out.text).toMatch(/thinking budget/i);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
99
|
it('sends the thinking cap as the SDK reasoning_budget', async () => {
|
|
117
100
|
const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
|
|
118
101
|
const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1', maxThinkingTokens: 128 });
|
|
@@ -120,6 +103,26 @@ describe('createQvacProvider.runTurn', () => {
|
|
|
120
103
|
expect(calls[0].generationParams).toEqual({ reasoning_budget: 128 });
|
|
121
104
|
});
|
|
122
105
|
|
|
106
|
+
it("sends reasoning_budget 0 when a turn asks for thinking 'off'", async () => {
|
|
107
|
+
const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
|
|
108
|
+
const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1', maxThinkingTokens: 128 });
|
|
109
|
+
await p.runTurn({ messages: [{ role: 'user', content: 'x' }], tools: [], thinking: 'off' });
|
|
110
|
+
expect(calls[0].generationParams).toEqual({ reasoning_budget: 0 });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('uses the session key as kvCache when sessionCache is on, and deletes it at the end', async () => {
|
|
114
|
+
const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
|
|
115
|
+
const deleted: unknown[] = [];
|
|
116
|
+
const on = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1', sessionCache: true, deleteCache: (async (p: unknown) => void deleted.push(p)) as any });
|
|
117
|
+
const off = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1' });
|
|
118
|
+
await on.runTurn({ messages: [{ role: 'user', content: 'x' }], tools: [], sessionKey: 'run-1' });
|
|
119
|
+
await off.runTurn({ messages: [{ role: 'user', content: 'x' }], tools: [], sessionKey: 'run-1' });
|
|
120
|
+
await on.endSession!('run-1');
|
|
121
|
+
expect(calls[0].kvCache).toBe('run-1');
|
|
122
|
+
expect(calls[1].kvCache).toBeUndefined();
|
|
123
|
+
expect(deleted).toEqual([{ kvCacheKey: 'run-1' }]);
|
|
124
|
+
});
|
|
125
|
+
|
|
123
126
|
it('keeps the reasoning budget below the output cap', async () => {
|
|
124
127
|
const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
|
|
125
128
|
const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1', defaultMaxTokens: 512, maxThinkingTokens: 512 });
|
package/src/qvac/provider.ts
CHANGED
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
* desktop sidecar its lazily-loaded SDK facade — which also makes this provider
|
|
11
11
|
* unit-testable with a fake completion.
|
|
12
12
|
*
|
|
13
|
-
* The host owns model lifecycle (load/unload
|
|
13
|
+
* The host owns model lifecycle (load/unload) and passes
|
|
14
14
|
* `getModelId()` so a turn always runs against the currently-loaded model.
|
|
15
15
|
* Tools are forwarded by schema only; the Engine executes them via its
|
|
16
|
-
* ToolSources, so signing
|
|
17
|
-
* delegated to a desktop peer.
|
|
16
|
+
* ToolSources, so signing and spending stay on the host.
|
|
18
17
|
*/
|
|
19
18
|
import type * as QvacSdk from '@qvac/sdk';
|
|
20
19
|
import type { InferenceMetrics, LLMProvider, TurnInput, TurnOutput } from '../providers/types.js';
|
|
@@ -24,6 +23,7 @@ import { toQvacTools } from './tools.js';
|
|
|
24
23
|
|
|
25
24
|
type CompletionFn = typeof QvacSdk.completion;
|
|
26
25
|
type CancelFn = typeof QvacSdk.cancel;
|
|
26
|
+
type DeleteCacheFn = typeof QvacSdk.deleteCache;
|
|
27
27
|
|
|
28
28
|
export interface QvacProviderOptions {
|
|
29
29
|
/** The SDK's `completion` (injected — see module docs). */
|
|
@@ -43,11 +43,20 @@ export interface QvacProviderOptions {
|
|
|
43
43
|
/**
|
|
44
44
|
* Cap `<think>` reasoning at this many TOKENS (not seconds — tok/s varies).
|
|
45
45
|
* Sent as the SDK's `reasoning_budget`, so the model closes its reasoning and
|
|
46
|
-
* answers.
|
|
47
|
-
* short fallback is returned instead of hanging on "Thinking…". Omit for
|
|
48
|
-
* unlimited reasoning.
|
|
46
|
+
* answers. Kept below the output cap. Omit for unlimited reasoning.
|
|
49
47
|
*/
|
|
50
48
|
maxThinkingTokens?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Experimental. Keep each agentic run in a QVAC KV-cache session
|
|
51
|
+
* (`kvCache: sessionKey`), so calls after the first send only the new
|
|
52
|
+
* message instead of re-reading the whole prompt. Needs `deleteCache` to
|
|
53
|
+
* drop the session at the end. In the rgb-agent eval (Qwen3.5 2B) it cut
|
|
54
|
+
* time to first token from ~7 s to ~0.2 s, but the model copied a Lightning
|
|
55
|
+
* invoice correctly in 4/10 runs with it vs 10/10 without. Off by default.
|
|
56
|
+
*/
|
|
57
|
+
sessionCache?: boolean;
|
|
58
|
+
/** The SDK's `deleteCache` (injected); used with `sessionCache`. */
|
|
59
|
+
deleteCache?: DeleteCacheFn;
|
|
51
60
|
/** Stream the model's `<think>` reasoning, when a host wants to surface it. */
|
|
52
61
|
onThinking?: (token: string) => void;
|
|
53
62
|
/**
|
|
@@ -67,11 +76,6 @@ export interface QvacTurnInput extends TurnInput {
|
|
|
67
76
|
onStats?: (stats: QvacTurnStats) => void;
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
/** Shown when a turn is cut off because it blew its thinking-token budget. */
|
|
71
|
-
const THINKING_BUDGET_FALLBACK =
|
|
72
|
-
'I spent my whole thinking budget on that one without landing an answer. ' +
|
|
73
|
-
'Try asking again, more specifically.';
|
|
74
|
-
|
|
75
79
|
export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
76
80
|
return {
|
|
77
81
|
name: 'qvac',
|
|
@@ -96,7 +100,7 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
|
96
100
|
const predict = input.maxTokens ?? options.defaultMaxTokens;
|
|
97
101
|
// A thinking budget at or above the output cap never binds: the model can
|
|
98
102
|
// spend the whole turn reasoning and return no answer. Keep half for it.
|
|
99
|
-
const thinkingCap = input.maxThinkingTokens ?? options.maxThinkingTokens;
|
|
103
|
+
const thinkingCap = input.thinking === 'off' ? 0 : (input.maxThinkingTokens ?? options.maxThinkingTokens);
|
|
100
104
|
const maxThinkingTokens =
|
|
101
105
|
thinkingCap !== undefined && predict !== undefined && thinkingCap >= predict
|
|
102
106
|
? Math.floor(predict / 2)
|
|
@@ -119,6 +123,7 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
|
119
123
|
// Split `<think>` into separate thinkingDelta events so reasoning never
|
|
120
124
|
// pollutes the visible answer.
|
|
121
125
|
captureThinking: true,
|
|
126
|
+
...(options.sessionCache && input.sessionKey ? { kvCache: input.sessionKey } : {}),
|
|
122
127
|
...(generationParams ? { generationParams } : {}),
|
|
123
128
|
...(tools ? { tools } : {}),
|
|
124
129
|
} as unknown as Parameters<CompletionFn>[0]);
|
|
@@ -143,24 +148,12 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
|
143
148
|
const result = await consumeRun(run, {
|
|
144
149
|
onToken: input.onToken,
|
|
145
150
|
onThinking: input.onThinking ?? options.onThinking,
|
|
146
|
-
// Backstop only: the SDK enforces the budget itself, and our count is a
|
|
147
|
-
// char-based estimate, so leave headroom before cancelling.
|
|
148
|
-
maxThinkingTokens:
|
|
149
|
-
maxThinkingTokens === undefined ? undefined : Math.ceil(maxThinkingTokens * 1.25) + 32,
|
|
150
|
-
// Cancel the in-flight run the moment the thinking budget is blown — the
|
|
151
|
-
// SDK keeps generating otherwise. Fire-and-forget; `final` then resolves.
|
|
152
|
-
onThinkingBudgetExceeded: () => {
|
|
153
|
-
void options.cancel({ requestId: run.requestId }).catch(() => {});
|
|
154
|
-
},
|
|
155
151
|
});
|
|
156
152
|
|
|
157
153
|
// Surface the real per-turn inference stats (backend device + throughput).
|
|
158
154
|
if (result.stats) (input.onStats ?? options.onStats)?.(result.stats);
|
|
159
155
|
|
|
160
|
-
|
|
161
|
-
// instead of an empty bubble so the agentic loop ends cleanly.
|
|
162
|
-
const text =
|
|
163
|
-
result.text || (result.thinkingBudgetExceeded ? THINKING_BUDGET_FALLBACK : result.text);
|
|
156
|
+
const text = result.text;
|
|
164
157
|
const promptTokens = result.stats?.promptTokens;
|
|
165
158
|
const generated = result.stats?.generatedTokens;
|
|
166
159
|
const totalTokens =
|
|
@@ -189,7 +182,7 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
|
189
182
|
};
|
|
190
183
|
|
|
191
184
|
const incomplete =
|
|
192
|
-
!result.text && result.toolCalls.length === 0 &&
|
|
185
|
+
!result.text && result.toolCalls.length === 0 && !!result.truncated;
|
|
193
186
|
return {
|
|
194
187
|
text,
|
|
195
188
|
rawContent: result.rawContent,
|
|
@@ -201,6 +194,15 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
|
|
|
201
194
|
};
|
|
202
195
|
},
|
|
203
196
|
|
|
197
|
+
async endSession(sessionKey: string): Promise<void> {
|
|
198
|
+
if (!options.sessionCache || !options.deleteCache) return;
|
|
199
|
+
try {
|
|
200
|
+
await options.deleteCache({ kvCacheKey: sessionKey });
|
|
201
|
+
} catch (err) {
|
|
202
|
+
console.warn('[qvac] deleteCache failed:', err);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
204
206
|
async cancel(requestId: string): Promise<void> {
|
|
205
207
|
// The cancel only lands once the server has begun the request; a same-tick
|
|
206
208
|
// cancel may race the begin and is logged as a no-match by the SDK.
|
package/src/qvac/stream.ts
CHANGED
|
@@ -28,9 +28,10 @@ export interface StreamHandlers {
|
|
|
28
28
|
/** The model's `<think>` reasoning, streamed separately. */
|
|
29
29
|
onThinking?: (token: string) => void;
|
|
30
30
|
/**
|
|
31
|
-
* Stop
|
|
32
|
-
* from characters)
|
|
33
|
-
*
|
|
31
|
+
* Stop forwarding once `<think>` reasoning exceeds this many tokens
|
|
32
|
+
* (estimated from characters) and call `onThinkingBudgetExceeded`. The QVAC
|
|
33
|
+
* provider uses the SDK's `reasoning_budget` instead; this is for callers
|
|
34
|
+
* driving `completion()` themselves.
|
|
34
35
|
*/
|
|
35
36
|
maxThinkingTokens?: number;
|
|
36
37
|
/**
|
package/src/qvac/voice.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* injected (type-only `@qvac/sdk` import, erased at build) so this carries no
|
|
5
5
|
* runtime SDK dependency and is unit-testable with fakes.
|
|
6
6
|
*
|
|
7
|
-
* The host still owns model lifecycle (download, load
|
|
7
|
+
* The host still owns model lifecycle (download, load) and
|
|
8
8
|
* audio I/O (mic capture, playback). It passes the loaded model-id resolvers;
|
|
9
9
|
* this module does the SDK calls + the text gating that must be identical
|
|
10
10
|
* everywhere (payment-string redaction, U+0060 refusal, file:// stripping).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** KaleidoSwap /v2 submarine swaps (pay Lightning from Liquid): tool contract and recipe. */
|
|
2
|
+
export {
|
|
3
|
+
SUBMARINE_TOOLS,
|
|
4
|
+
SUBMARINE_SPEND_TOOLS,
|
|
5
|
+
SUBMARINE_FROM_ASSETS,
|
|
6
|
+
isSubmarineSpendTool,
|
|
7
|
+
getSubmarineTool,
|
|
8
|
+
formatSubmarineAmount,
|
|
9
|
+
bindSubmarineTools,
|
|
10
|
+
} from './contract.js';
|
|
11
|
+
export type {
|
|
12
|
+
SubmarineToolDef,
|
|
13
|
+
SubmarineFromAsset,
|
|
14
|
+
SubmarineHandler,
|
|
15
|
+
BindSubmarineOptions,
|
|
16
|
+
} from './contract.js';
|
|
17
|
+
export { submarinePayRecipe, extractSubmarinePay } from '../recipe/submarine-pay.js';
|
package/src/tools/in-process.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* InProcessToolSource — tools whose handlers run in the same process.
|
|
3
3
|
*
|
|
4
4
|
* Used by the mobile wallet: the handlers call the device's wallet adapters
|
|
5
|
-
* (Spark / Arkade / RGB) directly, so signing happens on the
|
|
6
|
-
* the model
|
|
5
|
+
* (Spark / Arkade / RGB) directly, so signing happens on the device even when
|
|
6
|
+
* the model runs on a remote server.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { ToolDef } from '../types.js';
|
package/dist/qvac/delegate.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delegation helpers — the provider firewall (who may connect) and the
|
|
3
|
-
* consumer-side delegate config. Pure data builders (no `@qvac/sdk` import) so
|
|
4
|
-
* they stay shared + testable; the host passes the result to
|
|
5
|
-
* `startQVACProvider({ firewall })` / `loadModel({ delegate })`.
|
|
6
|
-
*
|
|
7
|
-
* P2P delegation exists in @qvac/sdk 0.13–0.18 only; 0.19 removed both calls.
|
|
8
|
-
*
|
|
9
|
-
* Security note: a QVAC provider is reachable by anyone who learns its
|
|
10
|
-
* Hyperswarm public key. Advertising with no firewall means any such peer can
|
|
11
|
-
* run inference on your machine. Use {@link allowListFirewall} so a desktop
|
|
12
|
-
* provider serves ONLY its paired phone(s).
|
|
13
|
-
*/
|
|
14
|
-
/** Firewall for `startQVACProvider` — restrict who may delegate to this provider. */
|
|
15
|
-
export interface ProviderFirewall {
|
|
16
|
-
mode: 'allow' | 'deny';
|
|
17
|
-
publicKeys: string[];
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Allow ONLY these consumer public keys to delegate (zero-trust). Pass the
|
|
21
|
-
* paired phone(s)' public keys so no one else can use the desktop brain even if
|
|
22
|
-
* they learn its public key.
|
|
23
|
-
*/
|
|
24
|
-
export declare function allowListFirewall(consumerPublicKeys: Iterable<string>): ProviderFirewall;
|
|
25
|
-
/** Deny these consumer public keys; everyone else may connect. */
|
|
26
|
-
export declare function denyListFirewall(consumerPublicKeys: Iterable<string>): ProviderFirewall;
|
|
27
|
-
/**
|
|
28
|
-
* Parse a comma/space/newline-separated key list (e.g. from an env var or a
|
|
29
|
-
* pairing store) into an allow-list firewall, or `undefined` when none are
|
|
30
|
-
* configured — the caller then advertises openly and should warn.
|
|
31
|
-
*/
|
|
32
|
-
export declare function firewallFromKeyList(raw: string | null | undefined): ProviderFirewall | undefined;
|
|
33
|
-
/** Consumer-side config for `loadModel({ delegate })`. */
|
|
34
|
-
export interface DelegateConfig {
|
|
35
|
-
providerPublicKey: string;
|
|
36
|
-
fallbackToLocal: boolean;
|
|
37
|
-
timeout?: number;
|
|
38
|
-
forceNewConnection?: boolean;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Build the `delegate` config for a delegated `loadModel`. `fallbackToLocal`
|
|
42
|
-
* defaults to false (the host owns recovery), matching rate's existing
|
|
43
|
-
* LLM/Whisper/TTS delegated loads.
|
|
44
|
-
*/
|
|
45
|
-
export declare function buildDelegateConfig(providerPublicKey: string, opts?: {
|
|
46
|
-
fallbackToLocal?: boolean;
|
|
47
|
-
timeout?: number;
|
|
48
|
-
forceNewConnection?: boolean;
|
|
49
|
-
}): DelegateConfig;
|
|
50
|
-
//# sourceMappingURL=delegate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../../src/qvac/delegate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,gBAAgB,CAExF;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,gBAAgB,CAEvF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAIhG;AAED,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,iBAAiB,EAAE,MAAM,EACzB,IAAI,GAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAO,GACvF,cAAc,CAOhB"}
|