@kaleidorg/mind 0.3.0 → 0.5.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/dist/funnel.d.ts +19 -0
- package/dist/funnel.d.ts.map +1 -1
- package/dist/funnel.js +48 -10
- package/dist/funnel.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/kaleidoswap/contract.d.ts +3 -3
- package/dist/kaleidoswap/contract.d.ts.map +1 -1
- package/dist/kaleidoswap/contract.js +16 -4
- package/dist/kaleidoswap/contract.js.map +1 -1
- package/dist/knowledge/bitcoin-copilot.d.ts.map +1 -1
- package/dist/knowledge/bitcoin-copilot.js +102 -0
- package/dist/knowledge/bitcoin-copilot.js.map +1 -1
- package/dist/knowledge/btc-map.d.ts +14 -17
- package/dist/knowledge/btc-map.d.ts.map +1 -1
- package/dist/knowledge/btc-map.js +66 -266
- package/dist/knowledge/btc-map.js.map +1 -1
- package/dist/lsps1/contract.d.ts.map +1 -1
- package/dist/lsps1/contract.js +28 -10
- package/dist/lsps1/contract.js.map +1 -1
- package/dist/qvac/assistant.d.ts +73 -0
- package/dist/qvac/assistant.d.ts.map +1 -0
- package/dist/qvac/assistant.js +97 -0
- package/dist/qvac/assistant.js.map +1 -0
- package/dist/qvac/config.d.ts +64 -0
- package/dist/qvac/config.d.ts.map +1 -0
- package/dist/qvac/config.js +71 -0
- package/dist/qvac/config.js.map +1 -0
- package/dist/qvac/delegate.d.ts +48 -0
- package/dist/qvac/delegate.d.ts.map +1 -0
- package/dist/qvac/delegate.js +51 -0
- package/dist/qvac/delegate.js.map +1 -0
- package/dist/qvac/index.d.ts +19 -0
- package/dist/qvac/index.d.ts.map +1 -0
- package/dist/qvac/index.js +19 -0
- package/dist/qvac/index.js.map +1 -0
- package/dist/qvac/parse.d.ts +44 -0
- package/dist/qvac/parse.d.ts.map +1 -0
- package/dist/qvac/parse.js +28 -0
- package/dist/qvac/parse.js.map +1 -0
- package/dist/qvac/provider.d.ts +49 -0
- package/dist/qvac/provider.d.ts.map +1 -0
- package/dist/qvac/provider.js +68 -0
- package/dist/qvac/provider.js.map +1 -0
- package/dist/qvac/stream.d.ts +37 -0
- package/dist/qvac/stream.d.ts.map +1 -0
- package/dist/qvac/stream.js +29 -0
- package/dist/qvac/stream.js.map +1 -0
- package/dist/qvac/text.d.ts +19 -0
- package/dist/qvac/text.d.ts.map +1 -0
- package/dist/qvac/text.js +56 -0
- package/dist/qvac/text.js.map +1 -0
- package/dist/qvac/voice.d.ts +69 -0
- package/dist/qvac/voice.d.ts.map +1 -0
- package/dist/qvac/voice.js +51 -0
- package/dist/qvac/voice.js.map +1 -0
- package/dist/recipe/buy-asset-channel.d.ts +26 -0
- package/dist/recipe/buy-asset-channel.d.ts.map +1 -0
- package/dist/recipe/buy-asset-channel.js +112 -0
- package/dist/recipe/buy-asset-channel.js.map +1 -0
- package/dist/recipe/kaleidoswap-atomic.d.ts +26 -18
- package/dist/recipe/kaleidoswap-atomic.d.ts.map +1 -1
- package/dist/recipe/kaleidoswap-atomic.js +101 -63
- package/dist/recipe/kaleidoswap-atomic.js.map +1 -1
- package/dist/recipe/kaleidoswap-channel-order.d.ts +35 -0
- package/dist/recipe/kaleidoswap-channel-order.d.ts.map +1 -0
- package/dist/recipe/kaleidoswap-channel-order.js +493 -0
- package/dist/recipe/kaleidoswap-channel-order.js.map +1 -0
- package/dist/recipe/kaleidoswap-price.d.ts +21 -0
- package/dist/recipe/kaleidoswap-price.d.ts.map +1 -0
- package/dist/recipe/kaleidoswap-price.js +57 -0
- package/dist/recipe/kaleidoswap-price.js.map +1 -0
- package/dist/recipe/runner.d.ts +7 -1
- package/dist/recipe/runner.d.ts.map +1 -1
- package/dist/recipe/runner.js +115 -29
- package/dist/recipe/runner.js.map +1 -1
- package/dist/recipe/swap.d.ts +26 -1
- package/dist/recipe/swap.d.ts.map +1 -1
- package/dist/recipe/swap.js +108 -13
- package/dist/recipe/swap.js.map +1 -1
- package/dist/recipe/types.d.ts +25 -1
- package/dist/recipe/types.d.ts.map +1 -1
- package/dist/skills/registry.d.ts +33 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +45 -1
- package/dist/skills/registry.js.map +1 -1
- package/package.json +15 -1
- package/skills/README.md +3 -0
- package/skills/kaleido-lsps/SKILL.md +101 -43
- package/skills/kaleido-trading/SKILL.md +81 -31
- package/skills/merchant-finder/SKILL.md +96 -66
- package/skills/rgb-lightning-node/SKILL.md +108 -0
- package/skills/wallet-assistant/SKILL.md +32 -21
- package/src/funnel.ts +66 -11
- package/src/index.ts +14 -2
- package/src/kaleidoswap/contract.test.ts +7 -2
- package/src/kaleidoswap/contract.ts +27 -5
- package/src/knowledge/bitcoin-copilot.ts +111 -0
- package/src/knowledge/btc-map.test.ts +53 -96
- package/src/knowledge/btc-map.ts +72 -287
- package/src/lsps1/contract.ts +32 -14
- package/src/qvac/assistant.test.ts +132 -0
- package/src/qvac/assistant.ts +146 -0
- package/src/qvac/config.test.ts +44 -0
- package/src/qvac/config.ts +76 -0
- package/src/qvac/delegate.test.ts +68 -0
- package/src/qvac/delegate.ts +71 -0
- package/src/qvac/index.ts +72 -0
- package/src/qvac/parse.test.ts +52 -0
- package/src/qvac/parse.ts +57 -0
- package/src/qvac/provider.test.ts +107 -0
- package/src/qvac/provider.ts +124 -0
- package/src/qvac/stream.test.ts +79 -0
- package/src/qvac/stream.ts +56 -0
- package/src/qvac/text.test.ts +70 -0
- package/src/qvac/text.ts +60 -0
- package/src/qvac/voice.test.ts +151 -0
- package/src/qvac/voice.ts +122 -0
- package/src/recipe/buy-asset-channel.test.ts +148 -0
- package/src/recipe/buy-asset-channel.ts +118 -0
- package/src/recipe/kaleidoswap-atomic.test.ts +134 -61
- package/src/recipe/kaleidoswap-atomic.ts +112 -66
- package/src/recipe/kaleidoswap-channel-order.test.ts +333 -0
- package/src/recipe/kaleidoswap-channel-order.ts +548 -0
- package/src/recipe/kaleidoswap-price.ts +68 -0
- package/src/recipe/recipe.test.ts +61 -5
- package/src/recipe/runner.ts +128 -31
- package/src/recipe/swap.ts +109 -13
- package/src/recipe/types.ts +25 -1
- package/src/skills/registry.ts +52 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands-free voice-assistant loop — the transcribe → reason → speak cycle that
|
|
3
|
+
* QVAC's `transcribeStream()` makes possible, lifted into shared code so mobile
|
|
4
|
+
* and desktop run the same orchestration.
|
|
5
|
+
*
|
|
6
|
+
* The host owns the I/O: it opens the SDK session (`transcribeStream` with
|
|
7
|
+
* `DEFAULT_VOICE_STREAM_PARAMS`), feeds mic audio via `session.write()`, and
|
|
8
|
+
* supplies `respond` (LLM/funnel turn → reply text) + `speak` (synth + play).
|
|
9
|
+
* This loop does the parts that must be identical everywhere: filter Whisper's
|
|
10
|
+
* silence hallucinations, and gate the mic during playback so the assistant
|
|
11
|
+
* never transcribes its own voice (QVAC's reference uses a mic-gate, not
|
|
12
|
+
* barge-in — we mirror that).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Whisper frequently hallucinates these from silence — drop them so the
|
|
16
|
+
* assistant doesn't answer phantom turns. (QVAC docs cite "you", ".", "Thanks.")
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_IGNORED_UTTERANCES = [
|
|
19
|
+
'you', 'thank you', 'thanks', 'bye', 'okay', '.',
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Should this utterance be handled? False for too-short text or a known Whisper
|
|
23
|
+
* hallucination. Pure + exported so it's directly testable.
|
|
24
|
+
*/
|
|
25
|
+
export function shouldHandleUtterance(text, options = {}) {
|
|
26
|
+
const trimmed = text.trim();
|
|
27
|
+
if (trimmed.length < (options.minChars ?? 3))
|
|
28
|
+
return false;
|
|
29
|
+
const norm = trimmed.toLowerCase().replace(/[.!?,]+$/, '').trim();
|
|
30
|
+
if (!norm)
|
|
31
|
+
return false;
|
|
32
|
+
const ignored = new Set([...(options.ignoredUtterances ?? DEFAULT_IGNORED_UTTERANCES)].map((s) => s.toLowerCase()));
|
|
33
|
+
return !ignored.has(norm);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run the hands-free loop until the session ends or `signal` aborts. Only `text`
|
|
37
|
+
* events drive a turn; `vad`/`segment`/`endOfTurn` events are ignored here (the
|
|
38
|
+
* host can read them off the session separately for UI). Always leaves the mic
|
|
39
|
+
* un-gated on exit.
|
|
40
|
+
*/
|
|
41
|
+
export async function runVoiceAssistant(session, handlers, options = {}) {
|
|
42
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
43
|
+
const cooldown = options.postPlaybackCooldownMs ?? 300;
|
|
44
|
+
let speaking = false;
|
|
45
|
+
handlers.onState?.('listening');
|
|
46
|
+
try {
|
|
47
|
+
for await (const event of session) {
|
|
48
|
+
if (options.signal?.aborted)
|
|
49
|
+
break;
|
|
50
|
+
if (event.type !== 'text' || typeof event.text !== 'string')
|
|
51
|
+
continue;
|
|
52
|
+
// Defensive: ignore anything heard mid-playback (host also gates the mic).
|
|
53
|
+
if (speaking)
|
|
54
|
+
continue;
|
|
55
|
+
const transcript = event.text.trim();
|
|
56
|
+
if (!shouldHandleUtterance(transcript, options))
|
|
57
|
+
continue;
|
|
58
|
+
handlers.onUserText?.(transcript);
|
|
59
|
+
handlers.onState?.('thinking');
|
|
60
|
+
let reply;
|
|
61
|
+
try {
|
|
62
|
+
reply = await handlers.respond(transcript);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
handlers.onState?.('listening');
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (options.signal?.aborted)
|
|
69
|
+
break;
|
|
70
|
+
if (!reply || !reply.trim()) {
|
|
71
|
+
handlers.onState?.('listening');
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
handlers.onReply?.(reply);
|
|
75
|
+
speaking = true;
|
|
76
|
+
handlers.setMicGated?.(true);
|
|
77
|
+
handlers.onState?.('speaking');
|
|
78
|
+
try {
|
|
79
|
+
await handlers.speak(reply);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* keep the loop alive on a playback error */
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await sleep(cooldown);
|
|
86
|
+
speaking = false;
|
|
87
|
+
handlers.setMicGated?.(false);
|
|
88
|
+
handlers.onState?.('listening');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
// Never leave the mic gated if the loop exits mid-turn.
|
|
94
|
+
handlers.setMicGated?.(false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=assistant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.js","sourceRoot":"","sources":["../../src/qvac/assistant.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QVAC model-load configs and constants, shared across every host. These are
|
|
3
|
+
* plain data (no SDK import) so they stay portable and testable; callers merge
|
|
4
|
+
* in SDK-specific bits like `verbosity: VERBOSITY.ERROR` at load time.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* CPU baseline for the local llamacpp model. Used as the GPU fallback and as the
|
|
8
|
+
* base the GPU attempt overrides (device + gpu_layers).
|
|
9
|
+
*/
|
|
10
|
+
export declare const LOCAL_LLM_CONFIG: {
|
|
11
|
+
readonly device: "cpu";
|
|
12
|
+
readonly gpu_layers: 0;
|
|
13
|
+
readonly ctx_size: 2048;
|
|
14
|
+
readonly tools: true;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* GPU (Metal on iPhone) offload — far faster than CPU when llamacpp can init the
|
|
18
|
+
* Metal context in the worklet. Fall back to {@link LOCAL_LLM_CONFIG} if the GPU
|
|
19
|
+
* load throws. ctx 4096 fits the agentic prompt (system + tools + skills + a
|
|
20
|
+
* little history); 2048 overflowed immediately ("prompt exceeds context").
|
|
21
|
+
*/
|
|
22
|
+
export declare const LOCAL_LLM_CONFIG_GPU: {
|
|
23
|
+
readonly device: "gpu";
|
|
24
|
+
readonly gpu_layers: 99;
|
|
25
|
+
readonly ctx_size: 4096;
|
|
26
|
+
readonly tools: true;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Delegated to a desktop provider — it has the RAM to run a big context, so give
|
|
30
|
+
* the agentic prompt plenty of room (Qwen3-600M supports up to 32k). 2048
|
|
31
|
+
* overflowed with the system prompt + tool/skill definitions alone.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DELEGATE_LLM_CONFIG: {
|
|
34
|
+
readonly ctx_size: 16384;
|
|
35
|
+
readonly device: "gpu";
|
|
36
|
+
readonly gpu_layers: 99;
|
|
37
|
+
readonly tools: true;
|
|
38
|
+
};
|
|
39
|
+
/** SUPERTONIC-2 TTS output sample rate (Hz). Used to build the WAV for playback. */
|
|
40
|
+
export declare const TTS_SAMPLE_RATE = 44100;
|
|
41
|
+
/**
|
|
42
|
+
* Default params for a hands-free `transcribeStream()` voice session (Whisper).
|
|
43
|
+
* `emitVadEvents` turns the session into a conversation stream (text + vad +
|
|
44
|
+
* endOfTurn events); `endOfTurnSilenceMs` is how long a pause must last before
|
|
45
|
+
* an utterance is committed — conservative so it doesn't cut speakers off mid
|
|
46
|
+
* sentence or trigger on TTS reverb. Hosts merge in `modelId` + spread these.
|
|
47
|
+
*/
|
|
48
|
+
export declare const DEFAULT_VOICE_STREAM_PARAMS: {
|
|
49
|
+
readonly emitVadEvents: true;
|
|
50
|
+
readonly endOfTurnSilenceMs: 700;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Whisper languages we request directly from the device locale. whisper.cpp
|
|
54
|
+
* supports more, but the QVAC handler rejects "auto"/detect_language for these
|
|
55
|
+
* tiny models, so we pass a concrete code (and fall back to 'en').
|
|
56
|
+
*/
|
|
57
|
+
export declare const WHISPER_LANGS: ReadonlySet<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Best-effort 2-letter Whisper language code from an OS locale string
|
|
60
|
+
* (e.g. "it-IT" → "it"), restricted to codes Whisper handles well. Falls back to
|
|
61
|
+
* 'en'. Pure: the host reads the locale (NativeModules etc.) and passes it here.
|
|
62
|
+
*/
|
|
63
|
+
export declare function normalizeWhisperLang(locale: string | null | undefined): string;
|
|
64
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/qvac/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;CAKnB,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;CAGtB,CAAC;AAEX,oFAAoF;AACpF,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B;;;CAG9B,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAI5C,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAI9E"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QVAC model-load configs and constants, shared across every host. These are
|
|
3
|
+
* plain data (no SDK import) so they stay portable and testable; callers merge
|
|
4
|
+
* in SDK-specific bits like `verbosity: VERBOSITY.ERROR` at load time.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* CPU baseline for the local llamacpp model. Used as the GPU fallback and as the
|
|
8
|
+
* base the GPU attempt overrides (device + gpu_layers).
|
|
9
|
+
*/
|
|
10
|
+
export const LOCAL_LLM_CONFIG = {
|
|
11
|
+
device: 'cpu',
|
|
12
|
+
gpu_layers: 0,
|
|
13
|
+
ctx_size: 2048,
|
|
14
|
+
tools: true,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* GPU (Metal on iPhone) offload — far faster than CPU when llamacpp can init the
|
|
18
|
+
* Metal context in the worklet. Fall back to {@link LOCAL_LLM_CONFIG} if the GPU
|
|
19
|
+
* load throws. ctx 4096 fits the agentic prompt (system + tools + skills + a
|
|
20
|
+
* little history); 2048 overflowed immediately ("prompt exceeds context").
|
|
21
|
+
*/
|
|
22
|
+
export const LOCAL_LLM_CONFIG_GPU = {
|
|
23
|
+
...LOCAL_LLM_CONFIG,
|
|
24
|
+
device: 'gpu',
|
|
25
|
+
gpu_layers: 99, // offload all layers; llamacpp clamps to the model's count
|
|
26
|
+
ctx_size: 4096,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Delegated to a desktop provider — it has the RAM to run a big context, so give
|
|
30
|
+
* the agentic prompt plenty of room (Qwen3-600M supports up to 32k). 2048
|
|
31
|
+
* overflowed with the system prompt + tool/skill definitions alone.
|
|
32
|
+
*/
|
|
33
|
+
export const DELEGATE_LLM_CONFIG = {
|
|
34
|
+
...LOCAL_LLM_CONFIG_GPU,
|
|
35
|
+
ctx_size: 16384,
|
|
36
|
+
};
|
|
37
|
+
/** SUPERTONIC-2 TTS output sample rate (Hz). Used to build the WAV for playback. */
|
|
38
|
+
export const TTS_SAMPLE_RATE = 44100;
|
|
39
|
+
/**
|
|
40
|
+
* Default params for a hands-free `transcribeStream()` voice session (Whisper).
|
|
41
|
+
* `emitVadEvents` turns the session into a conversation stream (text + vad +
|
|
42
|
+
* endOfTurn events); `endOfTurnSilenceMs` is how long a pause must last before
|
|
43
|
+
* an utterance is committed — conservative so it doesn't cut speakers off mid
|
|
44
|
+
* sentence or trigger on TTS reverb. Hosts merge in `modelId` + spread these.
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_VOICE_STREAM_PARAMS = {
|
|
47
|
+
emitVadEvents: true,
|
|
48
|
+
endOfTurnSilenceMs: 700,
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Whisper languages we request directly from the device locale. whisper.cpp
|
|
52
|
+
* supports more, but the QVAC handler rejects "auto"/detect_language for these
|
|
53
|
+
* tiny models, so we pass a concrete code (and fall back to 'en').
|
|
54
|
+
*/
|
|
55
|
+
export const WHISPER_LANGS = new Set([
|
|
56
|
+
'en', 'it', 'es', 'fr', 'de', 'pt', 'nl', 'ru', 'pl', 'uk', 'tr', 'ar',
|
|
57
|
+
'zh', 'ja', 'ko', 'hi', 'id', 'sv', 'no', 'da', 'fi', 'cs', 'ro', 'el',
|
|
58
|
+
'he', 'th', 'vi', 'hu', 'ca',
|
|
59
|
+
]);
|
|
60
|
+
/**
|
|
61
|
+
* Best-effort 2-letter Whisper language code from an OS locale string
|
|
62
|
+
* (e.g. "it-IT" → "it"), restricted to codes Whisper handles well. Falls back to
|
|
63
|
+
* 'en'. Pure: the host reads the locale (NativeModules etc.) and passes it here.
|
|
64
|
+
*/
|
|
65
|
+
export function normalizeWhisperLang(locale) {
|
|
66
|
+
if (!locale)
|
|
67
|
+
return 'en';
|
|
68
|
+
const code = String(locale).split(/[-_]/)[0]?.toLowerCase() ?? 'en';
|
|
69
|
+
return WHISPER_LANGS.has(code) ? code : 'en';
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/qvac/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,KAAK;IACb,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,IAAI;IACd,KAAK,EAAE,IAAI;CACH,CAAC;AAEX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,GAAG,gBAAgB;IACnB,MAAM,EAAE,KAAK;IACb,UAAU,EAAE,EAAE,EAAE,2DAA2D;IAC3E,QAAQ,EAAE,IAAI;CACN,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,GAAG,oBAAoB;IACvB,QAAQ,EAAE,KAAK;CACP,CAAC;AAEX,oFAAoF;AACpF,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,aAAa,EAAE,IAAI;IACnB,kBAAkB,EAAE,GAAG;CACf,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC;IACxD,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IACtE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IACtE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;CAC7B,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAiC;IACpE,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,IAAI,CAAC;IACpE,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
* Security note: a QVAC provider is reachable by anyone who learns its
|
|
8
|
+
* Hyperswarm public key. Advertising with no firewall means any such peer can
|
|
9
|
+
* run inference on your machine. Use {@link allowListFirewall} so a desktop
|
|
10
|
+
* provider serves ONLY its paired phone(s).
|
|
11
|
+
*/
|
|
12
|
+
/** Firewall for `startQVACProvider` — restrict who may delegate to this provider. */
|
|
13
|
+
export interface ProviderFirewall {
|
|
14
|
+
mode: 'allow' | 'deny';
|
|
15
|
+
publicKeys: string[];
|
|
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 declare function allowListFirewall(consumerPublicKeys: Iterable<string>): ProviderFirewall;
|
|
23
|
+
/** Deny these consumer public keys; everyone else may connect. */
|
|
24
|
+
export declare function denyListFirewall(consumerPublicKeys: Iterable<string>): ProviderFirewall;
|
|
25
|
+
/**
|
|
26
|
+
* Parse a comma/space/newline-separated key list (e.g. from an env var or a
|
|
27
|
+
* pairing store) into an allow-list firewall, or `undefined` when none are
|
|
28
|
+
* configured — the caller then advertises openly and should warn.
|
|
29
|
+
*/
|
|
30
|
+
export declare function firewallFromKeyList(raw: string | null | undefined): ProviderFirewall | undefined;
|
|
31
|
+
/** Consumer-side config for `loadModel({ delegate })`. */
|
|
32
|
+
export interface DelegateConfig {
|
|
33
|
+
providerPublicKey: string;
|
|
34
|
+
fallbackToLocal: boolean;
|
|
35
|
+
timeout?: number;
|
|
36
|
+
forceNewConnection?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the `delegate` config for a delegated `loadModel`. `fallbackToLocal`
|
|
40
|
+
* defaults to false (the host owns recovery), matching rate's existing
|
|
41
|
+
* LLM/Whisper/TTS delegated loads.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildDelegateConfig(providerPublicKey: string, opts?: {
|
|
44
|
+
fallbackToLocal?: boolean;
|
|
45
|
+
timeout?: number;
|
|
46
|
+
forceNewConnection?: boolean;
|
|
47
|
+
}): DelegateConfig;
|
|
48
|
+
//# sourceMappingURL=delegate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../../src/qvac/delegate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
* Security note: a QVAC provider is reachable by anyone who learns its
|
|
8
|
+
* Hyperswarm public key. Advertising with no firewall means any such peer can
|
|
9
|
+
* run inference on your machine. Use {@link allowListFirewall} so a desktop
|
|
10
|
+
* provider serves ONLY its paired phone(s).
|
|
11
|
+
*/
|
|
12
|
+
function normalizeKeys(keys) {
|
|
13
|
+
return [...new Set([...keys].map((k) => k.trim()).filter(Boolean))];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Allow ONLY these consumer public keys to delegate (zero-trust). Pass the
|
|
17
|
+
* paired phone(s)' public keys so no one else can use the desktop brain even if
|
|
18
|
+
* they learn its public key.
|
|
19
|
+
*/
|
|
20
|
+
export function allowListFirewall(consumerPublicKeys) {
|
|
21
|
+
return { mode: 'allow', publicKeys: normalizeKeys(consumerPublicKeys) };
|
|
22
|
+
}
|
|
23
|
+
/** Deny these consumer public keys; everyone else may connect. */
|
|
24
|
+
export function denyListFirewall(consumerPublicKeys) {
|
|
25
|
+
return { mode: 'deny', publicKeys: normalizeKeys(consumerPublicKeys) };
|
|
26
|
+
}
|
|
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 function firewallFromKeyList(raw) {
|
|
33
|
+
if (!raw)
|
|
34
|
+
return undefined;
|
|
35
|
+
const keys = raw.split(/[\s,]+/).map((k) => k.trim()).filter(Boolean);
|
|
36
|
+
return keys.length ? allowListFirewall(keys) : undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the `delegate` config for a delegated `loadModel`. `fallbackToLocal`
|
|
40
|
+
* defaults to false (the host owns recovery), matching rate's existing
|
|
41
|
+
* LLM/Whisper/TTS delegated loads.
|
|
42
|
+
*/
|
|
43
|
+
export function buildDelegateConfig(providerPublicKey, opts = {}) {
|
|
44
|
+
return {
|
|
45
|
+
providerPublicKey: providerPublicKey.trim(),
|
|
46
|
+
fallbackToLocal: opts.fallbackToLocal ?? false,
|
|
47
|
+
...(opts.timeout != null ? { timeout: opts.timeout } : {}),
|
|
48
|
+
...(opts.forceNewConnection != null ? { forceNewConnection: opts.forceNewConnection } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=delegate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegate.js","sourceRoot":"","sources":["../../src/qvac/delegate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;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,OAAsF,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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kaleidorg/mind-qvac — the single home for all @qvac/sdk logic behind
|
|
3
|
+
* @kaleidorg/mind. Hosts (rate mobile, desktop provider, cli) supply @qvac/sdk
|
|
4
|
+
* as a peer dependency; this package owns the orchestration so the logic lives
|
|
5
|
+
* in one place instead of drifting copies per host.
|
|
6
|
+
*
|
|
7
|
+
* This first slice exports the platform-agnostic core (pure text helpers, model
|
|
8
|
+
* configs, completion parsing). The QVAC-calling provider/voice/host wrappers
|
|
9
|
+
* land next, on top of these.
|
|
10
|
+
*/
|
|
11
|
+
export { cleanAssistantVisibleText, sanitizeForSupertonic, } from './text.js';
|
|
12
|
+
export { LOCAL_LLM_CONFIG, LOCAL_LLM_CONFIG_GPU, DELEGATE_LLM_CONFIG, TTS_SAMPLE_RATE, DEFAULT_VOICE_STREAM_PARAMS, WHISPER_LANGS, normalizeWhisperLang, } from './config.js';
|
|
13
|
+
export { finalToTurn, type QvacFinalLike, type ParsedTurn, } from './parse.js';
|
|
14
|
+
export { consumeRun, type CompletionEventLike, type CompletionRunLike, type StreamHandlers, type ConsumedTurn, } from './stream.js';
|
|
15
|
+
export { createQvacProvider, type QvacProviderOptions, type QvacTurnInput, } from './provider.js';
|
|
16
|
+
export { createQvacVoice, type QvacVoice, type QvacVoiceOptions, type VoiceSession, type PcmAudio, } from './voice.js';
|
|
17
|
+
export { runVoiceAssistant, shouldHandleUtterance, DEFAULT_IGNORED_UTTERANCES, type VoiceAssistantSession, type VoiceAssistantHandlers, type VoiceAssistantOptions, type VoiceAssistantState, type VoiceTranscriptEvent, } from './assistant.js';
|
|
18
|
+
export { allowListFirewall, denyListFirewall, firewallFromKeyList, buildDelegateConfig, type ProviderFirewall, type DelegateConfig, } from './delegate.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/qvac/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,2BAA2B,EAC3B,aAAa,EACb,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kaleidorg/mind-qvac — the single home for all @qvac/sdk logic behind
|
|
3
|
+
* @kaleidorg/mind. Hosts (rate mobile, desktop provider, cli) supply @qvac/sdk
|
|
4
|
+
* as a peer dependency; this package owns the orchestration so the logic lives
|
|
5
|
+
* in one place instead of drifting copies per host.
|
|
6
|
+
*
|
|
7
|
+
* This first slice exports the platform-agnostic core (pure text helpers, model
|
|
8
|
+
* configs, completion parsing). The QVAC-calling provider/voice/host wrappers
|
|
9
|
+
* land next, on top of these.
|
|
10
|
+
*/
|
|
11
|
+
export { cleanAssistantVisibleText, sanitizeForSupertonic, } from './text.js';
|
|
12
|
+
export { LOCAL_LLM_CONFIG, LOCAL_LLM_CONFIG_GPU, DELEGATE_LLM_CONFIG, TTS_SAMPLE_RATE, DEFAULT_VOICE_STREAM_PARAMS, WHISPER_LANGS, normalizeWhisperLang, } from './config.js';
|
|
13
|
+
export { finalToTurn, } from './parse.js';
|
|
14
|
+
export { consumeRun, } from './stream.js';
|
|
15
|
+
export { createQvacProvider, } from './provider.js';
|
|
16
|
+
export { createQvacVoice, } from './voice.js';
|
|
17
|
+
export { runVoiceAssistant, shouldHandleUtterance, DEFAULT_IGNORED_UTTERANCES, } from './assistant.js';
|
|
18
|
+
export { allowListFirewall, denyListFirewall, firewallFromKeyList, buildDelegateConfig, } from './delegate.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/qvac/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,2BAA2B,EAC3B,aAAa,EACb,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,WAAW,GAGZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,UAAU,GAKX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,GAGnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,eAAe,GAKhB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,GAM3B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,GAGpB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Structural subset of a QVAC `completion().final` we depend on. */
|
|
2
|
+
export interface QvacFinalLike {
|
|
3
|
+
/** Visible assistant text (excludes `<think>` reasoning). */
|
|
4
|
+
contentText?: string;
|
|
5
|
+
/** Raw assistant frame, incl. tool-call framing, for history push-back. */
|
|
6
|
+
raw?: {
|
|
7
|
+
fullText?: string;
|
|
8
|
+
};
|
|
9
|
+
/** Tool calls the model requested this turn (empty ⇒ final answer). */
|
|
10
|
+
toolCalls?: Array<{
|
|
11
|
+
id?: string;
|
|
12
|
+
name: string;
|
|
13
|
+
arguments?: Record<string, unknown>;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Why generation stopped. QVAC 0.13 emits `"length"` when the token budget is
|
|
17
|
+
* exhausted, `"cancelled"` on abort, `undefined` on a natural stop. We surface
|
|
18
|
+
* it so the funnel can tell a truncated tool-call from a complete one.
|
|
19
|
+
*/
|
|
20
|
+
stopReason?: 'length' | 'cancelled' | string;
|
|
21
|
+
}
|
|
22
|
+
export interface ParsedTurn {
|
|
23
|
+
/** Cleaned assistant content for display. */
|
|
24
|
+
text: string;
|
|
25
|
+
/** Raw assistant frame to push back into history for the next turn. */
|
|
26
|
+
rawContent: string;
|
|
27
|
+
/** Tool calls the model requested (arguments defaulted to `{}`). */
|
|
28
|
+
toolCalls: Array<{
|
|
29
|
+
id?: string;
|
|
30
|
+
name: string;
|
|
31
|
+
arguments: Record<string, unknown>;
|
|
32
|
+
}>;
|
|
33
|
+
/** True when generation was cut off by the token budget (incomplete output). */
|
|
34
|
+
truncated: boolean;
|
|
35
|
+
/** Raw stop reason from the SDK, when provided. */
|
|
36
|
+
stopReason?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Map a completion `final` (plus the streamed fallback text) into a ParsedTurn.
|
|
40
|
+
* `rawContent` prefers the SDK's framed `raw.fullText` so the Engine can anchor
|
|
41
|
+
* the next turn; falls back to the visible text when a provider has no raw form.
|
|
42
|
+
*/
|
|
43
|
+
export declare function finalToTurn(final: QvacFinalLike, streamed?: string): ParsedTurn;
|
|
44
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/qvac/parse.ts"],"names":[],"mappings":"AAQA,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,uEAAuE;IACvE,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACtF;;;;OAIG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAC;CAC9C;AAED,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACpF,gFAAgF;IAChF,SAAS,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,SAAK,GAAG,UAAU,CAc3E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure mapping from a QVAC completion `final` frame to the shape the shared
|
|
3
|
+
* @kaleidorg/mind Engine consumes. Kept SDK-free (structural input type) so it
|
|
4
|
+
* is testable without loading a model, and so the same mapping runs on mobile,
|
|
5
|
+
* desktop, and the eval harness.
|
|
6
|
+
*/
|
|
7
|
+
import { cleanAssistantVisibleText } from './text.js';
|
|
8
|
+
/**
|
|
9
|
+
* Map a completion `final` (plus the streamed fallback text) into a ParsedTurn.
|
|
10
|
+
* `rawContent` prefers the SDK's framed `raw.fullText` so the Engine can anchor
|
|
11
|
+
* the next turn; falls back to the visible text when a provider has no raw form.
|
|
12
|
+
*/
|
|
13
|
+
export function finalToTurn(final, streamed = '') {
|
|
14
|
+
const rawText = final.contentText || streamed;
|
|
15
|
+
const text = cleanAssistantVisibleText(rawText);
|
|
16
|
+
return {
|
|
17
|
+
text,
|
|
18
|
+
rawContent: final.raw?.fullText ?? rawText,
|
|
19
|
+
toolCalls: (final.toolCalls ?? []).map((c) => ({
|
|
20
|
+
id: c.id,
|
|
21
|
+
name: c.name,
|
|
22
|
+
arguments: c.arguments ?? {},
|
|
23
|
+
})),
|
|
24
|
+
truncated: final.stopReason === 'length',
|
|
25
|
+
stopReason: final.stopReason,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/qvac/parse.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AA+BtD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAoB,EAAE,QAAQ,GAAG,EAAE;IAC7D,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,IAAI,QAAQ,CAAC;IAC9C,MAAM,IAAI,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO;QACL,IAAI;QACJ,UAAU,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,IAAI,OAAO;QAC1C,SAAS,EAAE,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7C,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE;SAC7B,CAAC,CAAC;QACH,SAAS,EAAE,KAAK,CAAC,UAAU,KAAK,QAAQ;QACxC,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createQvacProvider — turns `@qvac/sdk` `completion()` into the shared
|
|
3
|
+
* `@kaleidorg/mind` `LLMProvider` the Engine/Funnel consumes. This is the one
|
|
4
|
+
* place the SDK is called for inference; every host (rate, desktop provider,
|
|
5
|
+
* cli) uses it instead of hand-rolling its own completion wrapper.
|
|
6
|
+
*
|
|
7
|
+
* The SDK functions are *injected*, not imported, so this package carries no
|
|
8
|
+
* runtime dependency on `@qvac/sdk` (the import below is type-only and erased).
|
|
9
|
+
* Hosts pass their own `completion`/`cancel` — rate the static RN import, the
|
|
10
|
+
* desktop sidecar its lazily-loaded SDK facade — which also makes this provider
|
|
11
|
+
* unit-testable with a fake completion.
|
|
12
|
+
*
|
|
13
|
+
* The host owns model lifecycle (load/unload, local-vs-delegated) and passes
|
|
14
|
+
* `getModelId()` so a turn always runs against the currently-loaded model.
|
|
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.
|
|
18
|
+
*/
|
|
19
|
+
import type * as QvacSdk from '@qvac/sdk';
|
|
20
|
+
import type { LLMProvider, TurnInput } from '../providers/types.js';
|
|
21
|
+
type CompletionFn = typeof QvacSdk.completion;
|
|
22
|
+
type CancelFn = typeof QvacSdk.cancel;
|
|
23
|
+
export interface QvacProviderOptions {
|
|
24
|
+
/** The SDK's `completion` (injected — see module docs). */
|
|
25
|
+
completion: CompletionFn;
|
|
26
|
+
/** The SDK's `cancel` (injected). */
|
|
27
|
+
cancel: CancelFn;
|
|
28
|
+
/** Resolve the loaded model id for this turn (null ⇒ not loaded → throws). */
|
|
29
|
+
getModelId: () => string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Default sampling temperature. Omit to leave it to the SDK/model default —
|
|
32
|
+
* `generationParams` is only sent when a temperature or max-tokens is set, so
|
|
33
|
+
* a host that passes neither preserves the SDK's own defaults.
|
|
34
|
+
*/
|
|
35
|
+
defaultTemperature?: number;
|
|
36
|
+
/** Default max output tokens — caps a turn so it can't ramble. Omit for uncapped. */
|
|
37
|
+
defaultMaxTokens?: number;
|
|
38
|
+
/** Stream the model's `<think>` reasoning, when a host wants to surface it. */
|
|
39
|
+
onThinking?: (token: string) => void;
|
|
40
|
+
}
|
|
41
|
+
/** TurnInput plus the per-call knobs the funnel/voice paths pass through. */
|
|
42
|
+
export interface QvacTurnInput extends TurnInput {
|
|
43
|
+
temperature?: number;
|
|
44
|
+
maxTokens?: number;
|
|
45
|
+
onThinking?: (token: string) => void;
|
|
46
|
+
}
|
|
47
|
+
export declare function createQvacProvider(options: QvacProviderOptions): LLMProvider;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/qvac/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,KAAK,OAAO,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAc,MAAM,uBAAuB,CAAC;AAGhF,KAAK,YAAY,GAAG,OAAO,OAAO,CAAC,UAAU,CAAC;AAC9C,KAAK,QAAQ,GAAG,OAAO,OAAO,CAAC,MAAM,CAAC;AAEtC,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,UAAU,EAAE,YAAY,CAAC;IACzB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC;IACjB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,6EAA6E;AAC7E,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,WAAW,CAwE5E"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { consumeRun } from './stream.js';
|
|
2
|
+
export function createQvacProvider(options) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'qvac',
|
|
5
|
+
async runTurn(input) {
|
|
6
|
+
const modelId = options.getModelId();
|
|
7
|
+
if (!modelId)
|
|
8
|
+
throw new Error('QVAC model not loaded');
|
|
9
|
+
const history = input.system
|
|
10
|
+
? [{ role: 'system', content: input.system }, ...input.messages]
|
|
11
|
+
: input.messages;
|
|
12
|
+
// Tools are forwarded by schema only (name/description/parameters). We
|
|
13
|
+
// carry `parameters` through verbatim (Zod for in-process tools, JSON
|
|
14
|
+
// Schema for MCP) — the model only needs the shape to pick a call; the
|
|
15
|
+
// Engine validates + executes.
|
|
16
|
+
const tools = input.tools.length
|
|
17
|
+
? input.tools.map((t) => ({
|
|
18
|
+
name: t.name,
|
|
19
|
+
description: t.description,
|
|
20
|
+
parameters: t.parameters,
|
|
21
|
+
}))
|
|
22
|
+
: undefined;
|
|
23
|
+
// QVAC 0.13 nests sampling under `generationParams`; top-level
|
|
24
|
+
// `temperature`/`max_tokens` (as older rate code passed) are dropped by
|
|
25
|
+
// validation, so the cap silently no-op'd. Build it here, and only send it
|
|
26
|
+
// when a value is set so a host that passes neither keeps SDK defaults.
|
|
27
|
+
const temp = input.temperature ?? options.defaultTemperature;
|
|
28
|
+
const predict = input.maxTokens ?? options.defaultMaxTokens;
|
|
29
|
+
const generationParams = temp !== undefined || predict !== undefined
|
|
30
|
+
? {
|
|
31
|
+
...(temp !== undefined ? { temp } : {}),
|
|
32
|
+
...(predict !== undefined ? { predict } : {}),
|
|
33
|
+
}
|
|
34
|
+
: undefined;
|
|
35
|
+
const run = options.completion({
|
|
36
|
+
modelId,
|
|
37
|
+
history,
|
|
38
|
+
stream: true,
|
|
39
|
+
// Split `<think>` into separate thinkingDelta events so reasoning never
|
|
40
|
+
// pollutes the visible answer.
|
|
41
|
+
captureThinking: true,
|
|
42
|
+
...(generationParams ? { generationParams } : {}),
|
|
43
|
+
...(tools ? { tools } : {}),
|
|
44
|
+
});
|
|
45
|
+
const result = await consumeRun(run, {
|
|
46
|
+
onToken: input.onToken,
|
|
47
|
+
onThinking: input.onThinking ?? options.onThinking,
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
text: result.text,
|
|
51
|
+
rawContent: result.rawContent,
|
|
52
|
+
toolCalls: result.toolCalls,
|
|
53
|
+
requestId: result.requestId,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async cancel(requestId) {
|
|
57
|
+
// The cancel only lands once the server has begun the request; a same-tick
|
|
58
|
+
// cancel may race the begin and is logged as a no-match by the SDK.
|
|
59
|
+
try {
|
|
60
|
+
await options.cancel({ requestId });
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.warn('[qvac] cancel failed:', err);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/qvac/provider.ts"],"names":[],"mappings":"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"}
|