@kaleidorg/mind 0.8.1 → 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.
Files changed (85) hide show
  1. package/README.md +7 -5
  2. package/dist/bitrefill/index.d.ts +4 -0
  3. package/dist/bitrefill/index.d.ts.map +1 -0
  4. package/dist/bitrefill/index.js +3 -0
  5. package/dist/bitrefill/index.js.map +1 -0
  6. package/dist/capabilities.d.ts +3 -3
  7. package/dist/capabilities.d.ts.map +1 -1
  8. package/dist/capabilities.js +4 -4
  9. package/dist/capabilities.js.map +1 -1
  10. package/dist/engine/answer.d.ts +37 -0
  11. package/dist/engine/answer.d.ts.map +1 -0
  12. package/dist/engine/answer.js +35 -0
  13. package/dist/engine/answer.js.map +1 -0
  14. package/dist/engine.d.ts +9 -3
  15. package/dist/engine.d.ts.map +1 -1
  16. package/dist/engine.js +159 -175
  17. package/dist/engine.js.map +1 -1
  18. package/dist/evidence.d.ts +1 -1
  19. package/dist/evidence.d.ts.map +1 -1
  20. package/dist/flashnet/index.d.ts +5 -0
  21. package/dist/flashnet/index.d.ts.map +1 -0
  22. package/dist/flashnet/index.js +4 -0
  23. package/dist/flashnet/index.js.map +1 -0
  24. package/dist/index.d.ts +6 -23
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +10 -27
  27. package/dist/index.js.map +1 -1
  28. package/dist/kaleidoswap/index.d.ts +8 -0
  29. package/dist/kaleidoswap/index.d.ts.map +1 -0
  30. package/dist/kaleidoswap/index.js +7 -0
  31. package/dist/kaleidoswap/index.js.map +1 -0
  32. package/dist/knowledge/index.d.ts +9 -0
  33. package/dist/knowledge/index.d.ts.map +1 -0
  34. package/dist/knowledge/index.js +6 -0
  35. package/dist/knowledge/index.js.map +1 -0
  36. package/dist/lsps1/index.d.ts +4 -0
  37. package/dist/lsps1/index.d.ts.map +1 -0
  38. package/dist/lsps1/index.js +3 -0
  39. package/dist/lsps1/index.js.map +1 -0
  40. package/dist/providers/types.d.ts +3 -3
  41. package/dist/providers/types.js +3 -3
  42. package/dist/qvac/index.d.ts +0 -1
  43. package/dist/qvac/index.d.ts.map +1 -1
  44. package/dist/qvac/index.js +0 -1
  45. package/dist/qvac/index.js.map +1 -1
  46. package/dist/qvac/provider.d.ts +9 -9
  47. package/dist/qvac/provider.d.ts.map +1 -1
  48. package/dist/qvac/provider.js +2 -15
  49. package/dist/qvac/provider.js.map +1 -1
  50. package/dist/qvac/stream.d.ts +4 -3
  51. package/dist/qvac/stream.d.ts.map +1 -1
  52. package/dist/qvac/stream.js.map +1 -1
  53. package/dist/qvac/voice.d.ts +1 -1
  54. package/dist/submarine/index.d.ts +5 -0
  55. package/dist/submarine/index.d.ts.map +1 -0
  56. package/dist/submarine/index.js +4 -0
  57. package/dist/submarine/index.js.map +1 -0
  58. package/dist/tools/in-process.d.ts +2 -2
  59. package/dist/tools/in-process.js +2 -2
  60. package/package.json +32 -2
  61. package/src/bitrefill/index.ts +13 -0
  62. package/src/capabilities.ts +7 -7
  63. package/src/context/context.test.ts +2 -2
  64. package/src/engine/answer.ts +66 -0
  65. package/src/engine.ts +185 -194
  66. package/src/evidence.ts +1 -1
  67. package/src/flashnet/index.ts +14 -0
  68. package/src/index.ts +10 -107
  69. package/src/kaleidoswap/index.ts +19 -0
  70. package/src/knowledge/index.ts +14 -0
  71. package/src/lsps1/index.ts +13 -0
  72. package/src/providers/types.ts +3 -3
  73. package/src/qvac/index.ts +0 -8
  74. package/src/qvac/provider.test.ts +0 -17
  75. package/src/qvac/provider.ts +11 -28
  76. package/src/qvac/stream.ts +4 -3
  77. package/src/qvac/voice.ts +1 -1
  78. package/src/submarine/index.ts +17 -0
  79. package/src/tools/in-process.ts +2 -2
  80. package/dist/qvac/delegate.d.ts +0 -50
  81. package/dist/qvac/delegate.d.ts.map +0 -1
  82. package/dist/qvac/delegate.js +0 -53
  83. package/dist/qvac/delegate.js.map +0 -1
  84. package/src/qvac/delegate.test.ts +0 -68
  85. package/src/qvac/delegate.ts +0 -73
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
- // ── Knowledge packs + corpus adapters (for RAG) ────────────────────────────
228
- export { BITCOIN_COPILOT_DOCS } from './knowledge/bitcoin-copilot.js';
229
- export { walletHistoryToDocuments, contactsToDocuments } from './knowledge/wallet.js';
230
- export type { WalletTx, Contact } from './knowledge/wallet.js';
231
- export { merchantsToDocuments } from './knowledge/merchants.js';
232
- export type { Merchant } from './knowledge/merchants.js';
233
- export { createBtcMapToolSource } from './knowledge/btc-map.js';
234
- export type {
235
- BtcMapToolOptions,
236
- BtcMapMerchant,
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';
@@ -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
- * - rate (mobile): wraps @qvac/sdk completion() (local or P2P-delegated)
6
- * - desktop-app: wraps @qvac/sdk completion() in Node
7
- * - kaleidoagent: could wrap Anthropic/OpenAI
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.
package/src/qvac/index.ts CHANGED
@@ -75,11 +75,3 @@ export {
75
75
  type VoiceTranscriptEvent,
76
76
  } from './assistant.js';
77
77
 
78
- export {
79
- allowListFirewall,
80
- denyListFirewall,
81
- firewallFromKeyList,
82
- buildDelegateConfig,
83
- type ProviderFirewall,
84
- type DelegateConfig,
85
- } from './delegate.js';
@@ -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 });
@@ -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, local-vs-delegated) and passes
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/spending stays on the host even when inference is
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';
@@ -44,15 +43,16 @@ export interface QvacProviderOptions {
44
43
  /**
45
44
  * Cap `<think>` reasoning at this many TOKENS (not seconds — tok/s varies).
46
45
  * Sent as the SDK's `reasoning_budget`, so the model closes its reasoning and
47
- * answers. If the stream still runs well past it, the run is cancelled and a
48
- * short fallback is returned instead of hanging on "Thinking…". Omit for
49
- * unlimited reasoning.
46
+ * answers. Kept below the output cap. Omit for unlimited reasoning.
50
47
  */
51
48
  maxThinkingTokens?: number;
52
49
  /**
53
- * Keep each agentic run in a QVAC KV-cache session (`kvCache: sessionKey`),
54
- * so calls after the first send only the new message instead of re-reading
55
- * the whole prompt. Needs `deleteCache` to drop the session at the end.
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
56
  */
57
57
  sessionCache?: boolean;
58
58
  /** The SDK's `deleteCache` (injected); used with `sessionCache`. */
@@ -76,11 +76,6 @@ export interface QvacTurnInput extends TurnInput {
76
76
  onStats?: (stats: QvacTurnStats) => void;
77
77
  }
78
78
 
79
- /** Shown when a turn is cut off because it blew its thinking-token budget. */
80
- const THINKING_BUDGET_FALLBACK =
81
- 'I spent my whole thinking budget on that one without landing an answer. ' +
82
- 'Try asking again, more specifically.';
83
-
84
79
  export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
85
80
  return {
86
81
  name: 'qvac',
@@ -153,24 +148,12 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
153
148
  const result = await consumeRun(run, {
154
149
  onToken: input.onToken,
155
150
  onThinking: input.onThinking ?? options.onThinking,
156
- // Backstop only: the SDK enforces the budget itself, and our count is a
157
- // char-based estimate, so leave headroom before cancelling.
158
- maxThinkingTokens:
159
- maxThinkingTokens === undefined ? undefined : Math.ceil(maxThinkingTokens * 1.25) + 32,
160
- // Cancel the in-flight run the moment the thinking budget is blown — the
161
- // SDK keeps generating otherwise. Fire-and-forget; `final` then resolves.
162
- onThinkingBudgetExceeded: () => {
163
- void options.cancel({ requestId: run.requestId }).catch(() => {});
164
- },
165
151
  });
166
152
 
167
153
  // Surface the real per-turn inference stats (backend device + throughput).
168
154
  if (result.stats) (input.onStats ?? options.onStats)?.(result.stats);
169
155
 
170
- // A turn cut off mid-reasoning has no visible answer — return a short note
171
- // instead of an empty bubble so the agentic loop ends cleanly.
172
- const text =
173
- result.text || (result.thinkingBudgetExceeded ? THINKING_BUDGET_FALLBACK : result.text);
156
+ const text = result.text;
174
157
  const promptTokens = result.stats?.promptTokens;
175
158
  const generated = result.stats?.generatedTokens;
176
159
  const totalTokens =
@@ -199,7 +182,7 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
199
182
  };
200
183
 
201
184
  const incomplete =
202
- !result.text && result.toolCalls.length === 0 && (result.thinkingBudgetExceeded || !!result.truncated);
185
+ !result.text && result.toolCalls.length === 0 && !!result.truncated;
203
186
  return {
204
187
  text,
205
188
  rawContent: result.rawContent,
@@ -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 the run once `<think>` reasoning exceeds this many tokens (estimated
32
- * from characters). A backstop for the SDK's own `reasoning_budget`. Omit for
33
- * unlimited reasoning.
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, local-vs-delegated) and
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';
@@ -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 phone even when
6
- * the model's inference is delegated to a desktop over P2P.
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';
@@ -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"}
@@ -1,53 +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
- function normalizeKeys(keys) {
15
- return [...new Set([...keys].map((k) => k.trim()).filter(Boolean))];
16
- }
17
- /**
18
- * Allow ONLY these consumer public keys to delegate (zero-trust). Pass the
19
- * paired phone(s)' public keys so no one else can use the desktop brain even if
20
- * they learn its public key.
21
- */
22
- export function allowListFirewall(consumerPublicKeys) {
23
- return { mode: 'allow', publicKeys: normalizeKeys(consumerPublicKeys) };
24
- }
25
- /** Deny these consumer public keys; everyone else may connect. */
26
- export function denyListFirewall(consumerPublicKeys) {
27
- return { mode: 'deny', publicKeys: normalizeKeys(consumerPublicKeys) };
28
- }
29
- /**
30
- * Parse a comma/space/newline-separated key list (e.g. from an env var or a
31
- * pairing store) into an allow-list firewall, or `undefined` when none are
32
- * configured — the caller then advertises openly and should warn.
33
- */
34
- export function firewallFromKeyList(raw) {
35
- if (!raw)
36
- return undefined;
37
- const keys = raw.split(/[\s,]+/).map((k) => k.trim()).filter(Boolean);
38
- return keys.length ? allowListFirewall(keys) : undefined;
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 function buildDelegateConfig(providerPublicKey, opts = {}) {
46
- return {
47
- providerPublicKey: providerPublicKey.trim(),
48
- fallbackToLocal: opts.fallbackToLocal ?? false,
49
- ...(opts.timeout != null ? { timeout: opts.timeout } : {}),
50
- ...(opts.forceNewConnection != null ? { forceNewConnection: opts.forceNewConnection } : {}),
51
- };
52
- }
53
- //# sourceMappingURL=delegate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"delegate.js","sourceRoot":"","sources":["../../src/qvac/delegate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,SAAS,aAAa,CAAC,IAAsB;IAC3C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,kBAAoC;IACpE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAAE,CAAC;AAC1E,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,gBAAgB,CAAC,kBAAoC;IACnE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAAE,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA8B;IAChE,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3D,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,iBAAyB,EACzB,IAAI,GAAkF,EAAE;IAExF,OAAO;QACL,iBAAiB,EAAE,iBAAiB,CAAC,IAAI,EAAE;QAC3C,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,KAAK;QAC9C,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5F,CAAC;AACJ,CAAC"}
@@ -1,68 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- allowListFirewall,
4
- denyListFirewall,
5
- firewallFromKeyList,
6
- buildDelegateConfig,
7
- } from './delegate.js';
8
-
9
- describe('allowListFirewall', () => {
10
- it('builds an allow-list, trimming + de-duping keys', () => {
11
- expect(allowListFirewall([' k1 ', 'k2', 'k1', ''])).toEqual({
12
- mode: 'allow',
13
- publicKeys: ['k1', 'k2'],
14
- });
15
- });
16
-
17
- it('is empty for no keys (caller must decide: open vs refuse)', () => {
18
- expect(allowListFirewall([])).toEqual({ mode: 'allow', publicKeys: [] });
19
- });
20
- });
21
-
22
- describe('denyListFirewall', () => {
23
- it('builds a deny-list', () => {
24
- expect(denyListFirewall(['bad'])).toEqual({ mode: 'deny', publicKeys: ['bad'] });
25
- });
26
- });
27
-
28
- describe('firewallFromKeyList', () => {
29
- it('parses comma/space/newline-separated keys into an allow-list', () => {
30
- expect(firewallFromKeyList('k1, k2\nk3 k4')).toEqual({
31
- mode: 'allow',
32
- publicKeys: ['k1', 'k2', 'k3', 'k4'],
33
- });
34
- });
35
-
36
- it('returns undefined for empty/missing input (advertise openly)', () => {
37
- expect(firewallFromKeyList('')).toBeUndefined();
38
- expect(firewallFromKeyList(' ')).toBeUndefined();
39
- expect(firewallFromKeyList(null)).toBeUndefined();
40
- expect(firewallFromKeyList(undefined)).toBeUndefined();
41
- });
42
- });
43
-
44
- describe('buildDelegateConfig', () => {
45
- it('defaults fallbackToLocal to false and trims the key', () => {
46
- expect(buildDelegateConfig(' pk ')).toEqual({
47
- providerPublicKey: 'pk',
48
- fallbackToLocal: false,
49
- });
50
- });
51
-
52
- it('passes through fallbackToLocal, timeout, forceNewConnection when set', () => {
53
- expect(
54
- buildDelegateConfig('pk', { fallbackToLocal: true, timeout: 60000, forceNewConnection: true }),
55
- ).toEqual({
56
- providerPublicKey: 'pk',
57
- fallbackToLocal: true,
58
- timeout: 60000,
59
- forceNewConnection: true,
60
- });
61
- });
62
-
63
- it('omits optional fields that are not set', () => {
64
- const cfg = buildDelegateConfig('pk', { fallbackToLocal: false });
65
- expect('timeout' in cfg).toBe(false);
66
- expect('forceNewConnection' in cfg).toBe(false);
67
- });
68
- });
@@ -1,73 +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
-
15
- /** Firewall for `startQVACProvider` — restrict who may delegate to this provider. */
16
- export interface ProviderFirewall {
17
- mode: 'allow' | 'deny';
18
- publicKeys: string[];
19
- }
20
-
21
- function normalizeKeys(keys: Iterable<string>): string[] {
22
- return [...new Set([...keys].map((k) => k.trim()).filter(Boolean))];
23
- }
24
-
25
- /**
26
- * Allow ONLY these consumer public keys to delegate (zero-trust). Pass the
27
- * paired phone(s)' public keys so no one else can use the desktop brain even if
28
- * they learn its public key.
29
- */
30
- export function allowListFirewall(consumerPublicKeys: Iterable<string>): ProviderFirewall {
31
- return { mode: 'allow', publicKeys: normalizeKeys(consumerPublicKeys) };
32
- }
33
-
34
- /** Deny these consumer public keys; everyone else may connect. */
35
- export function denyListFirewall(consumerPublicKeys: Iterable<string>): ProviderFirewall {
36
- return { mode: 'deny', publicKeys: normalizeKeys(consumerPublicKeys) };
37
- }
38
-
39
- /**
40
- * Parse a comma/space/newline-separated key list (e.g. from an env var or a
41
- * pairing store) into an allow-list firewall, or `undefined` when none are
42
- * configured — the caller then advertises openly and should warn.
43
- */
44
- export function firewallFromKeyList(raw: string | null | undefined): ProviderFirewall | undefined {
45
- if (!raw) return undefined;
46
- const keys = raw.split(/[\s,]+/).map((k) => k.trim()).filter(Boolean);
47
- return keys.length ? allowListFirewall(keys) : undefined;
48
- }
49
-
50
- /** Consumer-side config for `loadModel({ delegate })`. */
51
- export interface DelegateConfig {
52
- providerPublicKey: string;
53
- fallbackToLocal: boolean;
54
- timeout?: number;
55
- forceNewConnection?: boolean;
56
- }
57
-
58
- /**
59
- * Build the `delegate` config for a delegated `loadModel`. `fallbackToLocal`
60
- * defaults to false (the host owns recovery), matching rate's existing
61
- * LLM/Whisper/TTS delegated loads.
62
- */
63
- export function buildDelegateConfig(
64
- providerPublicKey: string,
65
- opts: { fallbackToLocal?: boolean; timeout?: number; forceNewConnection?: boolean } = {},
66
- ): DelegateConfig {
67
- return {
68
- providerPublicKey: providerPublicKey.trim(),
69
- fallbackToLocal: opts.fallbackToLocal ?? false,
70
- ...(opts.timeout != null ? { timeout: opts.timeout } : {}),
71
- ...(opts.forceNewConnection != null ? { forceNewConnection: opts.forceNewConnection } : {}),
72
- };
73
- }