@absolutejs/voice 0.0.1
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 +150 -0
- package/dist/absolutejs/src/angular/angularDeps.d.ts +2 -0
- package/dist/absolutejs/src/angular/angularPatch.d.ts +1 -0
- package/dist/absolutejs/src/angular/injectorPatch.d.ts +1 -0
- package/dist/absolutejs/src/angular/islands.d.ts +3 -0
- package/dist/absolutejs/src/angular/lowerDeferSyntax.d.ts +18 -0
- package/dist/absolutejs/src/angular/lowerServerIslands.d.ts +1 -0
- package/dist/absolutejs/src/angular/pageHandler.d.ts +26 -0
- package/dist/absolutejs/src/angular/resolveAngularPackage.d.ts +7 -0
- package/dist/absolutejs/src/angular/ssrRender.d.ts +16 -0
- package/dist/absolutejs/src/angular/ssrSanitizer.d.ts +3 -0
- package/dist/absolutejs/src/build/angularLinkerPlugin.d.ts +11 -0
- package/dist/absolutejs/src/build/buildAngularVendor.d.ts +4 -0
- package/dist/absolutejs/src/build/buildDepVendor.d.ts +2 -0
- package/dist/absolutejs/src/build/buildReactVendor.d.ts +8 -0
- package/dist/absolutejs/src/build/buildSvelteVendor.d.ts +6 -0
- package/dist/absolutejs/src/build/buildVueVendor.d.ts +6 -0
- package/dist/absolutejs/src/build/compileAngular.d.ts +29 -0
- package/dist/absolutejs/src/build/compileSvelte.d.ts +13 -0
- package/dist/absolutejs/src/build/compileVue.d.ts +19 -0
- package/dist/absolutejs/src/build/generateManifest.d.ts +2 -0
- package/dist/absolutejs/src/build/generateReactIndexes.d.ts +1 -0
- package/dist/absolutejs/src/build/htmlScriptHMRPlugin.d.ts +13 -0
- package/dist/absolutejs/src/build/islandEntries.d.ts +32 -0
- package/dist/absolutejs/src/build/nativeRewrite.d.ts +5 -0
- package/dist/absolutejs/src/build/optimizeHtmlImages.d.ts +2 -0
- package/dist/absolutejs/src/build/outputLogs.d.ts +1 -0
- package/dist/absolutejs/src/build/resolvePackageImport.d.ts +9 -0
- package/dist/absolutejs/src/build/rewriteImports.d.ts +7 -0
- package/dist/absolutejs/src/build/rewriteReactImports.d.ts +7 -0
- package/dist/absolutejs/src/build/scanConventions.d.ts +5 -0
- package/dist/absolutejs/src/build/scanCssEntryPoints.d.ts +1 -0
- package/dist/absolutejs/src/build/scanEntryPoints.d.ts +1 -0
- package/dist/absolutejs/src/build/staticIslandPages.d.ts +9 -0
- package/dist/absolutejs/src/build/updateAssetPaths.d.ts +1 -0
- package/dist/absolutejs/src/build/wrapHTMLScript.d.ts +17 -0
- package/dist/absolutejs/src/cli/scripts/telemetry.d.ts +5 -0
- package/dist/absolutejs/src/cli/telemetryEvent.d.ts +1 -0
- package/dist/absolutejs/src/client/islandStore.d.ts +22 -0
- package/dist/absolutejs/src/client/streamSwap.d.ts +11 -0
- package/dist/absolutejs/src/constants.d.ts +60 -0
- package/dist/absolutejs/src/core/build.d.ts +9 -0
- package/dist/absolutejs/src/core/currentIslandRegistry.d.ts +7 -0
- package/dist/absolutejs/src/core/devBuild.d.ts +6 -0
- package/dist/absolutejs/src/core/devRouteRegistrationCallsite.d.ts +2 -0
- package/dist/absolutejs/src/core/devVendorPaths.d.ts +13 -0
- package/dist/absolutejs/src/core/index.d.ts +7 -0
- package/dist/absolutejs/src/core/islandManifest.d.ts +3 -0
- package/dist/absolutejs/src/core/islandMarkupAttributes.d.ts +12 -0
- package/dist/absolutejs/src/core/islandPageContext.d.ts +12 -0
- package/dist/absolutejs/src/core/islandSsr.d.ts +6 -0
- package/dist/absolutejs/src/core/islands.d.ts +16 -0
- package/dist/absolutejs/src/core/loadIslandRegistry.d.ts +1 -0
- package/dist/absolutejs/src/core/lookup.d.ts +1 -0
- package/dist/absolutejs/src/core/pageHandlers.d.ts +6 -0
- package/dist/absolutejs/src/core/prepare.d.ts +274 -0
- package/dist/absolutejs/src/core/prerender.d.ts +31 -0
- package/dist/absolutejs/src/core/renderIslandMarkup.d.ts +13 -0
- package/dist/absolutejs/src/core/responseEnhancers.d.ts +10 -0
- package/dist/absolutejs/src/core/staticStreaming.d.ts +25 -0
- package/dist/absolutejs/src/core/streamingSlotRegistrar.d.ts +16 -0
- package/dist/absolutejs/src/core/streamingSlotRegistry.d.ts +2 -0
- package/dist/absolutejs/src/core/streamingSlotWarningScope.d.ts +4 -0
- package/dist/absolutejs/src/core/svelteServerModule.d.ts +1 -0
- package/dist/absolutejs/src/core/wrapPageHandlerWithStreamingSlots.d.ts +2 -0
- package/dist/absolutejs/src/dev/assetStore.d.ts +8 -0
- package/dist/absolutejs/src/dev/buildHMRClient.d.ts +1 -0
- package/dist/absolutejs/src/dev/clientManager.d.ts +31 -0
- package/dist/absolutejs/src/dev/configResolver.d.ts +14 -0
- package/dist/absolutejs/src/dev/dependencyGraph.d.ts +10 -0
- package/dist/absolutejs/src/dev/devCert.d.ts +11 -0
- package/dist/absolutejs/src/dev/fileHashTracker.d.ts +2 -0
- package/dist/absolutejs/src/dev/fileWatcher.d.ts +4 -0
- package/dist/absolutejs/src/dev/moduleMapper.d.ts +27 -0
- package/dist/absolutejs/src/dev/moduleServer.d.ts +21 -0
- package/dist/absolutejs/src/dev/moduleVersionTracker.d.ts +7 -0
- package/dist/absolutejs/src/dev/pathUtils.d.ts +5 -0
- package/dist/absolutejs/src/dev/reactComponentClassifier.d.ts +2 -0
- package/dist/absolutejs/src/dev/rebuildTrigger.d.ts +10 -0
- package/dist/absolutejs/src/dev/simpleHTMLHMR.d.ts +4 -0
- package/dist/absolutejs/src/dev/simpleHTMXHMR.d.ts +4 -0
- package/dist/absolutejs/src/dev/transformCache.d.ts +6 -0
- package/dist/absolutejs/src/dev/webSocket.d.ts +9 -0
- package/dist/absolutejs/src/index.d.ts +5 -0
- package/dist/absolutejs/src/islands/pageMetadata.d.ts +13 -0
- package/dist/absolutejs/src/islands/sourceMetadata.d.ts +10 -0
- package/dist/absolutejs/src/plugins/devtoolsJson.d.ts +58 -0
- package/dist/absolutejs/src/plugins/hmr.d.ts +138 -0
- package/dist/absolutejs/src/plugins/imageOptimizer.d.ts +67 -0
- package/dist/absolutejs/src/plugins/index.d.ts +3 -0
- package/dist/absolutejs/src/plugins/networking.d.ts +2 -0
- package/dist/absolutejs/src/plugins/pageRouter.d.ts +1 -0
- package/dist/absolutejs/src/react/Island.d.ts +2 -0
- package/dist/absolutejs/src/react/bridgeInternals.d.ts +1 -0
- package/dist/absolutejs/src/react/createIsland.d.ts +2 -0
- package/dist/absolutejs/src/react/hooks/useIslandStore.d.ts +3 -0
- package/dist/absolutejs/src/react/index.d.ts +4 -0
- package/dist/absolutejs/src/react/pageHandler.d.ts +21 -0
- package/dist/absolutejs/src/svelte/lowerAwaitSlotSyntax.d.ts +4 -0
- package/dist/absolutejs/src/svelte/lowerIslandSyntax.d.ts +4 -0
- package/dist/absolutejs/src/svelte/pageHandler.d.ts +23 -0
- package/dist/absolutejs/src/svelte/renderToReadableStream.d.ts +14 -0
- package/dist/absolutejs/src/utils/cleanStaleOutputs.d.ts +1 -0
- package/dist/absolutejs/src/utils/cleanup.d.ts +8 -0
- package/dist/absolutejs/src/utils/commonAncestor.d.ts +1 -0
- package/dist/absolutejs/src/utils/defineConfig.d.ts +2 -0
- package/dist/absolutejs/src/utils/defineEnv.d.ts +10 -0
- package/dist/absolutejs/src/utils/escapeScriptContent.d.ts +1 -0
- package/dist/absolutejs/src/utils/generateHeadElement.d.ts +4 -0
- package/dist/absolutejs/src/utils/generateSitemap.d.ts +6 -0
- package/dist/absolutejs/src/utils/getDurationString.d.ts +1 -0
- package/dist/absolutejs/src/utils/getEnv.d.ts +1 -0
- package/dist/absolutejs/src/utils/imageProcessing.d.ts +33 -0
- package/dist/absolutejs/src/utils/index.d.ts +9 -0
- package/dist/absolutejs/src/utils/jsonLd.d.ts +2 -0
- package/dist/absolutejs/src/utils/loadConfig.d.ts +1 -0
- package/dist/absolutejs/src/utils/logger.d.ts +55 -0
- package/dist/absolutejs/src/utils/networking.d.ts +2 -0
- package/dist/absolutejs/src/utils/normalizePath.d.ts +9 -0
- package/dist/absolutejs/src/utils/registerClientScript.d.ts +36 -0
- package/dist/absolutejs/src/utils/resolveConvention.d.ts +9 -0
- package/dist/absolutejs/src/utils/ssrErrorPage.d.ts +1 -0
- package/dist/absolutejs/src/utils/startupBanner.d.ts +9 -0
- package/dist/absolutejs/src/utils/streamingSlotMetricSink.d.ts +18 -0
- package/dist/absolutejs/src/utils/streamingSlots.d.ts +76 -0
- package/dist/absolutejs/src/utils/stringModifiers.d.ts +3 -0
- package/dist/absolutejs/src/utils/validateSafePath.d.ts +1 -0
- package/dist/absolutejs/src/vue/pageHandler.d.ts +29 -0
- package/dist/absolutejs/types/ai.d.ts +4631 -0
- package/dist/absolutejs/types/angular.d.ts +29 -0
- package/dist/absolutejs/types/build.d.ts +67 -0
- package/dist/absolutejs/types/cli.d.ts +19 -0
- package/dist/absolutejs/types/client.d.ts +65 -0
- package/dist/absolutejs/types/conventions.d.ts +20 -0
- package/dist/absolutejs/types/env.d.ts +2 -0
- package/dist/absolutejs/types/image.d.ts +77 -0
- package/dist/absolutejs/types/index.d.ts +19 -0
- package/dist/absolutejs/types/island.d.ts +48 -0
- package/dist/absolutejs/types/jsonLd.d.ts +271 -0
- package/dist/absolutejs/types/messages.d.ts +144 -0
- package/dist/absolutejs/types/metadata.d.ts +49 -0
- package/dist/absolutejs/types/react.d.ts +2 -0
- package/dist/absolutejs/types/session.d.ts +16 -0
- package/dist/absolutejs/types/sitemap.d.ts +14 -0
- package/dist/absolutejs/types/svelte.d.ts +2 -0
- package/dist/absolutejs/types/telemetry.d.ts +15 -0
- package/dist/absolutejs/types/tool.d.ts +11 -0
- package/dist/absolutejs/types/typeGuards.d.ts +5 -0
- package/dist/absolutejs/types/vue.d.ts +14 -0
- package/dist/absolutejs/types/websocket.d.ts +6 -0
- package/dist/angular/index.js +505 -0
- package/dist/client/index.js +521 -0
- package/dist/index.js +693 -0
- package/dist/react/index.js +489 -0
- package/dist/svelte/index.js +456 -0
- package/dist/voice/src/angular/index.d.ts +1 -0
- package/dist/voice/src/angular/voice-stream.service.d.ts +15 -0
- package/dist/voice/src/client/actions.d.ts +58 -0
- package/dist/voice/src/client/connection.d.ts +12 -0
- package/dist/voice/src/client/createVoiceStream.d.ts +14 -0
- package/dist/voice/src/client/index.d.ts +3 -0
- package/dist/voice/src/client/microphone.d.ts +11 -0
- package/dist/voice/src/client/store.d.ts +7 -0
- package/dist/voice/src/index.d.ts +5 -0
- package/dist/voice/src/logger.d.ts +8 -0
- package/dist/voice/src/memoryStore.d.ts +2 -0
- package/dist/voice/src/plugin.d.ts +40 -0
- package/dist/voice/src/react/index.d.ts +1 -0
- package/dist/voice/src/react/useVoiceStream.d.ts +13 -0
- package/dist/voice/src/session.d.ts +2 -0
- package/dist/voice/src/store.d.ts +6 -0
- package/dist/voice/src/svelte/createVoiceStream.d.ts +14 -0
- package/dist/voice/src/svelte/index.d.ts +1 -0
- package/dist/voice/src/turnDetection.d.ts +3 -0
- package/dist/voice/src/types.d.ts +318 -0
- package/dist/voice/src/vue/index.d.ts +1 -0
- package/dist/voice/src/vue/useVoiceStream.d.ts +13 -0
- package/dist/vue/index.js +496 -0
- package/package.json +100 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __name = (target, name) => {
|
|
6
|
+
Object.defineProperty(target, "name", {
|
|
7
|
+
value: name,
|
|
8
|
+
enumerable: false,
|
|
9
|
+
configurable: true
|
|
10
|
+
});
|
|
11
|
+
return target;
|
|
12
|
+
};
|
|
13
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
14
|
+
var __typeError = (msg) => {
|
|
15
|
+
throw TypeError(msg);
|
|
16
|
+
};
|
|
17
|
+
var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
18
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
19
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
20
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
21
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
22
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
23
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
24
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
25
|
+
var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
26
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({
|
|
27
|
+
kind: __decoratorStrings[kind],
|
|
28
|
+
name,
|
|
29
|
+
metadata,
|
|
30
|
+
addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
|
|
31
|
+
});
|
|
32
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
33
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
34
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
|
|
35
|
+
flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
38
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
39
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
40
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
41
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
42
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
|
|
43
|
+
get [name]() {
|
|
44
|
+
return __privateGet(this, extra);
|
|
45
|
+
},
|
|
46
|
+
set [name](x) {
|
|
47
|
+
__privateSet(this, extra, x);
|
|
48
|
+
}
|
|
49
|
+
}, name));
|
|
50
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
51
|
+
for (var i = decorators.length - 1;i >= 0; i--) {
|
|
52
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
53
|
+
if (k) {
|
|
54
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
|
|
55
|
+
if (k ^ 3)
|
|
56
|
+
access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
57
|
+
if (k > 2)
|
|
58
|
+
access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
59
|
+
}
|
|
60
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
|
|
61
|
+
done._ = 1;
|
|
62
|
+
if (k ^ 4 || it === undefined)
|
|
63
|
+
__expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
64
|
+
else if (typeof it !== "object" || it === null)
|
|
65
|
+
__typeError("Object expected");
|
|
66
|
+
else
|
|
67
|
+
__expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
68
|
+
}
|
|
69
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/plugin.ts
|
|
73
|
+
import { Elysia } from "elysia";
|
|
74
|
+
|
|
75
|
+
// src/logger.ts
|
|
76
|
+
var noop = () => {};
|
|
77
|
+
var createNoopLogger = () => ({
|
|
78
|
+
debug: noop,
|
|
79
|
+
error: noop,
|
|
80
|
+
info: noop,
|
|
81
|
+
warn: noop
|
|
82
|
+
});
|
|
83
|
+
var resolveLogger = (logger) => ({
|
|
84
|
+
...createNoopLogger(),
|
|
85
|
+
...logger
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// src/store.ts
|
|
89
|
+
var createId = () => crypto.randomUUID();
|
|
90
|
+
var createVoiceSessionRecord = (id) => ({
|
|
91
|
+
committedTurnIds: [],
|
|
92
|
+
createdAt: Date.now(),
|
|
93
|
+
currentTurn: {
|
|
94
|
+
finalText: "",
|
|
95
|
+
partialText: "",
|
|
96
|
+
transcripts: []
|
|
97
|
+
},
|
|
98
|
+
id,
|
|
99
|
+
reconnect: { attempts: 0 },
|
|
100
|
+
status: "active",
|
|
101
|
+
transcripts: [],
|
|
102
|
+
turns: []
|
|
103
|
+
});
|
|
104
|
+
var resetVoiceSessionRecord = (id, existing) => ({
|
|
105
|
+
...createVoiceSessionRecord(id),
|
|
106
|
+
metadata: existing?.metadata
|
|
107
|
+
});
|
|
108
|
+
var toVoiceSessionSummary = (session) => ({
|
|
109
|
+
createdAt: session.createdAt,
|
|
110
|
+
id: session.id,
|
|
111
|
+
lastActivityAt: session.lastActivityAt,
|
|
112
|
+
status: session.status,
|
|
113
|
+
turnCount: session.turns.length
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// src/turnDetection.ts
|
|
117
|
+
var DEFAULT_SILENCE_MS = 700;
|
|
118
|
+
var buildTurnText = (transcripts, partialText) => {
|
|
119
|
+
const finalText = transcripts.map((transcript) => transcript.text.trim()).filter(Boolean).join(" ").trim();
|
|
120
|
+
if (finalText) {
|
|
121
|
+
return finalText;
|
|
122
|
+
}
|
|
123
|
+
return partialText.trim();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/session.ts
|
|
127
|
+
var DEFAULT_RECONNECT_TIMEOUT = 30000;
|
|
128
|
+
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
|
|
129
|
+
var toError = (value) => value instanceof Error ? value : new Error(String(value));
|
|
130
|
+
var createEmptyCurrentTurn = () => ({
|
|
131
|
+
finalText: "",
|
|
132
|
+
partialText: "",
|
|
133
|
+
transcripts: []
|
|
134
|
+
});
|
|
135
|
+
var cloneTranscript = (transcript) => ({ ...transcript });
|
|
136
|
+
var setTurnResult = (session, turnId, input) => {
|
|
137
|
+
session.turns = session.turns.map((turn) => turn.id === turnId ? {
|
|
138
|
+
...turn,
|
|
139
|
+
assistantText: input.assistantText ?? turn.assistantText,
|
|
140
|
+
result: input.result ?? turn.result
|
|
141
|
+
} : turn);
|
|
142
|
+
};
|
|
143
|
+
var createVoiceSession = (options) => {
|
|
144
|
+
const logger = resolveLogger(options.logger);
|
|
145
|
+
const reconnect = {
|
|
146
|
+
maxAttempts: options.reconnect.maxAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS,
|
|
147
|
+
strategy: options.reconnect.strategy ?? "resume-last-turn",
|
|
148
|
+
timeout: options.reconnect.timeout ?? DEFAULT_RECONNECT_TIMEOUT
|
|
149
|
+
};
|
|
150
|
+
const turnDetection = {
|
|
151
|
+
silenceMs: options.turnDetection.silenceMs ?? DEFAULT_SILENCE_MS
|
|
152
|
+
};
|
|
153
|
+
let socket = options.socket;
|
|
154
|
+
let sttSession = null;
|
|
155
|
+
let silenceTimer = null;
|
|
156
|
+
const clearSilenceTimer = () => {
|
|
157
|
+
if (!silenceTimer) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
clearTimeout(silenceTimer);
|
|
161
|
+
silenceTimer = null;
|
|
162
|
+
};
|
|
163
|
+
const send = async (message) => {
|
|
164
|
+
try {
|
|
165
|
+
await Promise.resolve(socket.send(JSON.stringify(message)));
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logger.warn("voice socket send failed", {
|
|
168
|
+
error: toError(error).message,
|
|
169
|
+
sessionId: options.id,
|
|
170
|
+
type: message.type
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const readSession = async () => options.store.getOrCreate(options.id);
|
|
175
|
+
const writeSession = async (mutate) => {
|
|
176
|
+
const session = await options.store.getOrCreate(options.id);
|
|
177
|
+
mutate(session);
|
|
178
|
+
await options.store.set(options.id, session);
|
|
179
|
+
return session;
|
|
180
|
+
};
|
|
181
|
+
const closeAdapter = async (reason) => {
|
|
182
|
+
if (!sttSession) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const activeSession = sttSession;
|
|
186
|
+
sttSession = null;
|
|
187
|
+
try {
|
|
188
|
+
await activeSession.close(reason);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger.warn("voice stt close failed", {
|
|
191
|
+
error: toError(error).message,
|
|
192
|
+
sessionId: options.id
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const scheduleSilenceCommit = () => {
|
|
197
|
+
clearSilenceTimer();
|
|
198
|
+
silenceTimer = setTimeout(() => {
|
|
199
|
+
api.commitTurn("silence");
|
|
200
|
+
}, turnDetection.silenceMs);
|
|
201
|
+
};
|
|
202
|
+
const handleError = async (event) => {
|
|
203
|
+
await send({
|
|
204
|
+
message: event.error.message,
|
|
205
|
+
recoverable: event.recoverable,
|
|
206
|
+
type: "error"
|
|
207
|
+
});
|
|
208
|
+
if (!event.recoverable) {
|
|
209
|
+
await api.fail(event.error);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const handleClose = async (event) => {
|
|
213
|
+
if (event.recoverable === false) {
|
|
214
|
+
await api.fail(new Error(event.reason ?? "Speech-to-text session closed"));
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const handlePartial = async (transcript) => {
|
|
218
|
+
await writeSession((session) => {
|
|
219
|
+
session.currentTurn.lastAudioAt = Date.now();
|
|
220
|
+
session.currentTurn.partialText = transcript.text;
|
|
221
|
+
session.lastActivityAt = Date.now();
|
|
222
|
+
session.status = "active";
|
|
223
|
+
});
|
|
224
|
+
await send({
|
|
225
|
+
transcript,
|
|
226
|
+
type: "partial"
|
|
227
|
+
});
|
|
228
|
+
scheduleSilenceCommit();
|
|
229
|
+
};
|
|
230
|
+
const handleFinal = async (transcript) => {
|
|
231
|
+
await writeSession((session) => {
|
|
232
|
+
const alreadyPresent = session.currentTurn.transcripts.some((existing) => existing.id === transcript.id);
|
|
233
|
+
if (!alreadyPresent) {
|
|
234
|
+
session.currentTurn.transcripts = [
|
|
235
|
+
...session.currentTurn.transcripts,
|
|
236
|
+
cloneTranscript(transcript)
|
|
237
|
+
];
|
|
238
|
+
session.transcripts = [
|
|
239
|
+
...session.transcripts,
|
|
240
|
+
cloneTranscript(transcript)
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
session.currentTurn.finalText = buildTurnText(session.currentTurn.transcripts, session.currentTurn.partialText);
|
|
244
|
+
session.currentTurn.lastAudioAt = Date.now();
|
|
245
|
+
session.lastActivityAt = Date.now();
|
|
246
|
+
session.status = "active";
|
|
247
|
+
});
|
|
248
|
+
await send({
|
|
249
|
+
transcript,
|
|
250
|
+
type: "final"
|
|
251
|
+
});
|
|
252
|
+
scheduleSilenceCommit();
|
|
253
|
+
};
|
|
254
|
+
const ensureAdapter = async () => {
|
|
255
|
+
if (sttSession) {
|
|
256
|
+
return sttSession;
|
|
257
|
+
}
|
|
258
|
+
sttSession = await options.stt.open({
|
|
259
|
+
format: {
|
|
260
|
+
channels: 1,
|
|
261
|
+
container: "raw",
|
|
262
|
+
encoding: "pcm_s16le",
|
|
263
|
+
sampleRateHz: 16000
|
|
264
|
+
},
|
|
265
|
+
sessionId: options.id
|
|
266
|
+
});
|
|
267
|
+
sttSession.on("partial", ({ transcript }) => {
|
|
268
|
+
handlePartial(transcript);
|
|
269
|
+
});
|
|
270
|
+
sttSession.on("final", ({ transcript }) => {
|
|
271
|
+
handleFinal(transcript);
|
|
272
|
+
});
|
|
273
|
+
sttSession.on("endOfTurn", ({ reason }) => {
|
|
274
|
+
clearSilenceTimer();
|
|
275
|
+
api.commitTurn(reason);
|
|
276
|
+
});
|
|
277
|
+
sttSession.on("error", (event) => {
|
|
278
|
+
handleError(event);
|
|
279
|
+
});
|
|
280
|
+
sttSession.on("close", (event) => {
|
|
281
|
+
handleClose(event);
|
|
282
|
+
});
|
|
283
|
+
return sttSession;
|
|
284
|
+
};
|
|
285
|
+
const completeTurn = async (session, turn) => {
|
|
286
|
+
const output = await options.route.onTurn({
|
|
287
|
+
api,
|
|
288
|
+
context: options.context,
|
|
289
|
+
session,
|
|
290
|
+
turn
|
|
291
|
+
});
|
|
292
|
+
if (output?.assistantText) {
|
|
293
|
+
await writeSession((currentSession) => {
|
|
294
|
+
setTurnResult(currentSession, turn.id, {
|
|
295
|
+
assistantText: output.assistantText
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
await send({
|
|
299
|
+
text: output.assistantText,
|
|
300
|
+
turnId: turn.id,
|
|
301
|
+
type: "assistant"
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (output?.result !== undefined) {
|
|
305
|
+
await writeSession((currentSession) => {
|
|
306
|
+
setTurnResult(currentSession, turn.id, {
|
|
307
|
+
result: output.result
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (output?.complete) {
|
|
312
|
+
await api.complete(output.result);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const api = {
|
|
316
|
+
id: options.id,
|
|
317
|
+
close: async (reason) => {
|
|
318
|
+
clearSilenceTimer();
|
|
319
|
+
await closeAdapter(reason);
|
|
320
|
+
await Promise.resolve(socket.close(1000, reason));
|
|
321
|
+
},
|
|
322
|
+
commitTurn: async (reason = "manual") => {
|
|
323
|
+
clearSilenceTimer();
|
|
324
|
+
const session = await readSession();
|
|
325
|
+
if (session.status === "completed" || session.status === "failed") {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const text = buildTurnText(session.currentTurn.transcripts, session.currentTurn.partialText);
|
|
329
|
+
if (!text) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const turn = {
|
|
333
|
+
committedAt: Date.now(),
|
|
334
|
+
id: createId(),
|
|
335
|
+
text,
|
|
336
|
+
transcripts: session.currentTurn.transcripts.length > 0 ? session.currentTurn.transcripts.map(cloneTranscript) : [
|
|
337
|
+
{
|
|
338
|
+
id: createId(),
|
|
339
|
+
isFinal: false,
|
|
340
|
+
text
|
|
341
|
+
}
|
|
342
|
+
]
|
|
343
|
+
};
|
|
344
|
+
const updatedSession = await writeSession((currentSession) => {
|
|
345
|
+
currentSession.committedTurnIds = [
|
|
346
|
+
...currentSession.committedTurnIds,
|
|
347
|
+
turn.id
|
|
348
|
+
];
|
|
349
|
+
currentSession.currentTurn = createEmptyCurrentTurn();
|
|
350
|
+
currentSession.lastActivityAt = Date.now();
|
|
351
|
+
currentSession.status = "active";
|
|
352
|
+
currentSession.turns = [...currentSession.turns, turn];
|
|
353
|
+
});
|
|
354
|
+
logger.info("voice turn committed", {
|
|
355
|
+
reason,
|
|
356
|
+
sessionId: options.id,
|
|
357
|
+
turnId: turn.id
|
|
358
|
+
});
|
|
359
|
+
await send({
|
|
360
|
+
turn,
|
|
361
|
+
type: "turn"
|
|
362
|
+
});
|
|
363
|
+
await completeTurn(updatedSession, turn);
|
|
364
|
+
},
|
|
365
|
+
complete: async (result) => {
|
|
366
|
+
clearSilenceTimer();
|
|
367
|
+
const session = await writeSession((currentSession) => {
|
|
368
|
+
if (currentSession.status === "completed") {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
currentSession.lastActivityAt = Date.now();
|
|
372
|
+
currentSession.status = "completed";
|
|
373
|
+
if (result !== undefined && currentSession.turns.length > 0) {
|
|
374
|
+
const lastTurn = currentSession.turns.at(-1);
|
|
375
|
+
if (lastTurn) {
|
|
376
|
+
setTurnResult(currentSession, lastTurn.id, {
|
|
377
|
+
result
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
await send({
|
|
383
|
+
sessionId: options.id,
|
|
384
|
+
type: "complete"
|
|
385
|
+
});
|
|
386
|
+
await closeAdapter("complete");
|
|
387
|
+
await options.route.onComplete({
|
|
388
|
+
api,
|
|
389
|
+
context: options.context,
|
|
390
|
+
session
|
|
391
|
+
});
|
|
392
|
+
},
|
|
393
|
+
connect: async (nextSocket) => {
|
|
394
|
+
socket = nextSocket;
|
|
395
|
+
const existingSession = await options.store.get(options.id);
|
|
396
|
+
let session = existingSession ?? createVoiceSessionRecord(options.id);
|
|
397
|
+
let shouldFireOnSession = !existingSession;
|
|
398
|
+
if (existingSession?.status === "reconnecting") {
|
|
399
|
+
const nextAttempts = existingSession.reconnect.attempts + 1;
|
|
400
|
+
const reconnectExpired = existingSession.reconnect.lastDisconnectAt !== undefined && Date.now() - existingSession.reconnect.lastDisconnectAt > reconnect.timeout;
|
|
401
|
+
const tooManyAttempts = nextAttempts > reconnect.maxAttempts;
|
|
402
|
+
if (reconnect.strategy === "fail" && (reconnectExpired || tooManyAttempts)) {
|
|
403
|
+
await api.fail(new Error("Voice session reconnect policy exhausted"));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (reconnect.strategy === "restart" && (reconnectExpired || tooManyAttempts)) {
|
|
407
|
+
session = resetVoiceSessionRecord(options.id, existingSession);
|
|
408
|
+
shouldFireOnSession = true;
|
|
409
|
+
} else {
|
|
410
|
+
session = {
|
|
411
|
+
...existingSession,
|
|
412
|
+
reconnect: {
|
|
413
|
+
...existingSession.reconnect,
|
|
414
|
+
attempts: nextAttempts
|
|
415
|
+
},
|
|
416
|
+
status: "active"
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
await options.store.set(options.id, session);
|
|
421
|
+
await send({
|
|
422
|
+
sessionId: options.id,
|
|
423
|
+
status: session.status,
|
|
424
|
+
type: "session"
|
|
425
|
+
});
|
|
426
|
+
if (shouldFireOnSession) {
|
|
427
|
+
await options.route.onSession?.({
|
|
428
|
+
api,
|
|
429
|
+
context: options.context,
|
|
430
|
+
session
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
if (session.status === "completed") {
|
|
434
|
+
await send({
|
|
435
|
+
sessionId: options.id,
|
|
436
|
+
type: "complete"
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
await ensureAdapter();
|
|
441
|
+
},
|
|
442
|
+
disconnect: async (event) => {
|
|
443
|
+
clearSilenceTimer();
|
|
444
|
+
await closeAdapter(event?.reason);
|
|
445
|
+
if (reconnect.strategy === "fail") {
|
|
446
|
+
await api.fail(new Error(event?.reason ?? "Voice socket disconnected"));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
await writeSession((session) => {
|
|
450
|
+
if (session.status === "completed" || session.status === "failed") {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
session.lastActivityAt = Date.now();
|
|
454
|
+
session.reconnect.lastDisconnectAt = Date.now();
|
|
455
|
+
session.status = "reconnecting";
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
fail: async (error) => {
|
|
459
|
+
clearSilenceTimer();
|
|
460
|
+
const session = await writeSession((currentSession) => {
|
|
461
|
+
currentSession.lastActivityAt = Date.now();
|
|
462
|
+
currentSession.status = "failed";
|
|
463
|
+
});
|
|
464
|
+
const resolvedError = toError(error);
|
|
465
|
+
await send({
|
|
466
|
+
message: resolvedError.message,
|
|
467
|
+
recoverable: false,
|
|
468
|
+
type: "error"
|
|
469
|
+
});
|
|
470
|
+
await closeAdapter("failed");
|
|
471
|
+
await options.route.onError?.({
|
|
472
|
+
api,
|
|
473
|
+
context: options.context,
|
|
474
|
+
error: resolvedError,
|
|
475
|
+
session,
|
|
476
|
+
sessionId: options.id
|
|
477
|
+
});
|
|
478
|
+
},
|
|
479
|
+
receiveAudio: async (audio) => {
|
|
480
|
+
const session = await readSession();
|
|
481
|
+
if (session.status === "completed" || session.status === "failed") {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const adapter = await ensureAdapter();
|
|
485
|
+
await writeSession((currentSession) => {
|
|
486
|
+
currentSession.currentTurn.lastAudioAt = Date.now();
|
|
487
|
+
currentSession.lastActivityAt = Date.now();
|
|
488
|
+
currentSession.status = "active";
|
|
489
|
+
});
|
|
490
|
+
await adapter.send(audio);
|
|
491
|
+
},
|
|
492
|
+
snapshot: async () => readSession()
|
|
493
|
+
};
|
|
494
|
+
return api;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/plugin.ts
|
|
498
|
+
var isArrayBufferView = (value) => typeof value === "object" && value !== null && ArrayBuffer.isView(value);
|
|
499
|
+
var isVoiceClientMessage = (value) => {
|
|
500
|
+
if (!value || typeof value !== "object" || !("type" in value)) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
switch (value.type) {
|
|
504
|
+
case "close":
|
|
505
|
+
return true;
|
|
506
|
+
case "end_turn":
|
|
507
|
+
return true;
|
|
508
|
+
case "ping":
|
|
509
|
+
return true;
|
|
510
|
+
case "start":
|
|
511
|
+
return !("sessionId" in value) || typeof value.sessionId === "string";
|
|
512
|
+
default:
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
var parseClientMessage = (raw) => {
|
|
517
|
+
if (typeof raw === "string") {
|
|
518
|
+
try {
|
|
519
|
+
const parsed = JSON.parse(raw);
|
|
520
|
+
return isVoiceClientMessage(parsed) ? parsed : null;
|
|
521
|
+
} catch {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (isVoiceClientMessage(raw)) {
|
|
526
|
+
return raw;
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
};
|
|
530
|
+
var resolveSessionId = (runtime, ws) => {
|
|
531
|
+
const existing = runtime.socketSessions.get(ws);
|
|
532
|
+
if (existing) {
|
|
533
|
+
return existing;
|
|
534
|
+
}
|
|
535
|
+
const query = ws.data && typeof ws.data === "object" && "query" in ws.data ? ws.data.query : undefined;
|
|
536
|
+
const providedSessionId = typeof query?.sessionId === "string" && query.sessionId.trim() ? query.sessionId.trim() : createId();
|
|
537
|
+
runtime.socketSessions.set(ws, providedSessionId);
|
|
538
|
+
return providedSessionId;
|
|
539
|
+
};
|
|
540
|
+
var toAudioChunk = (raw) => {
|
|
541
|
+
if (raw instanceof ArrayBuffer) {
|
|
542
|
+
return raw;
|
|
543
|
+
}
|
|
544
|
+
if (isArrayBufferView(raw)) {
|
|
545
|
+
return raw;
|
|
546
|
+
}
|
|
547
|
+
return null;
|
|
548
|
+
};
|
|
549
|
+
var createSocketAdapter = (ws) => ({
|
|
550
|
+
close: async (code, reason) => {
|
|
551
|
+
ws.close(code, reason);
|
|
552
|
+
},
|
|
553
|
+
send: async (data) => {
|
|
554
|
+
ws.send(data);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
var voice = (config) => {
|
|
558
|
+
const runtime = {
|
|
559
|
+
activeSessions: new Map,
|
|
560
|
+
logger: resolveLogger(config.logger),
|
|
561
|
+
socketSessions: new WeakMap
|
|
562
|
+
};
|
|
563
|
+
return new Elysia({ name: "absolutejs-voice" }).ws(config.path, {
|
|
564
|
+
close: async (ws, code, reason) => {
|
|
565
|
+
const sessionId = runtime.socketSessions.get(ws);
|
|
566
|
+
if (!sessionId) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const session = runtime.activeSessions.get(sessionId);
|
|
570
|
+
runtime.activeSessions.delete(sessionId);
|
|
571
|
+
if (session) {
|
|
572
|
+
await session.disconnect({
|
|
573
|
+
code,
|
|
574
|
+
reason,
|
|
575
|
+
recoverable: true,
|
|
576
|
+
type: "close"
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
message: async (ws, raw) => {
|
|
581
|
+
const sessionId = resolveSessionId(runtime, ws);
|
|
582
|
+
const current = runtime.activeSessions.get(sessionId);
|
|
583
|
+
const message = parseClientMessage(raw);
|
|
584
|
+
if (message) {
|
|
585
|
+
if (message.type === "ping") {
|
|
586
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
587
|
+
}
|
|
588
|
+
if (message.type === "end_turn" && current) {
|
|
589
|
+
await current.commitTurn("manual");
|
|
590
|
+
}
|
|
591
|
+
if (message.type === "close" && current) {
|
|
592
|
+
await current.close(message.reason);
|
|
593
|
+
runtime.activeSessions.delete(sessionId);
|
|
594
|
+
}
|
|
595
|
+
if (message.type === "start" && message.sessionId && message.sessionId !== sessionId) {
|
|
596
|
+
runtime.socketSessions.set(ws, message.sessionId);
|
|
597
|
+
}
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const audio = toAudioChunk(raw);
|
|
601
|
+
if (!audio) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const session = current ?? createVoiceSession({
|
|
605
|
+
context: ws.data,
|
|
606
|
+
id: sessionId,
|
|
607
|
+
logger: config.logger,
|
|
608
|
+
reconnect: {
|
|
609
|
+
maxAttempts: config.reconnect?.maxAttempts ?? 10,
|
|
610
|
+
strategy: config.reconnect?.strategy ?? "resume-last-turn",
|
|
611
|
+
timeout: config.reconnect?.timeout ?? 30000
|
|
612
|
+
},
|
|
613
|
+
route: {
|
|
614
|
+
onComplete: config.onComplete,
|
|
615
|
+
onError: config.onError,
|
|
616
|
+
onSession: config.onSession,
|
|
617
|
+
onTurn: config.onTurn
|
|
618
|
+
},
|
|
619
|
+
socket: createSocketAdapter(ws),
|
|
620
|
+
store: config.session,
|
|
621
|
+
stt: config.stt,
|
|
622
|
+
turnDetection: {
|
|
623
|
+
silenceMs: config.turnDetection?.silenceMs ?? 700
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
if (!current) {
|
|
627
|
+
runtime.activeSessions.set(sessionId, session);
|
|
628
|
+
await session.connect(createSocketAdapter(ws));
|
|
629
|
+
}
|
|
630
|
+
await session.receiveAudio(audio);
|
|
631
|
+
},
|
|
632
|
+
open: async (ws) => {
|
|
633
|
+
const sessionId = resolveSessionId(runtime, ws);
|
|
634
|
+
const existing = runtime.activeSessions.get(sessionId);
|
|
635
|
+
if (existing) {
|
|
636
|
+
await existing.close("superseded");
|
|
637
|
+
runtime.activeSessions.delete(sessionId);
|
|
638
|
+
}
|
|
639
|
+
const session = createVoiceSession({
|
|
640
|
+
context: ws.data,
|
|
641
|
+
id: sessionId,
|
|
642
|
+
logger: config.logger,
|
|
643
|
+
reconnect: {
|
|
644
|
+
maxAttempts: config.reconnect?.maxAttempts ?? 10,
|
|
645
|
+
strategy: config.reconnect?.strategy ?? "resume-last-turn",
|
|
646
|
+
timeout: config.reconnect?.timeout ?? 30000
|
|
647
|
+
},
|
|
648
|
+
route: {
|
|
649
|
+
onComplete: config.onComplete,
|
|
650
|
+
onError: config.onError,
|
|
651
|
+
onSession: config.onSession,
|
|
652
|
+
onTurn: config.onTurn
|
|
653
|
+
},
|
|
654
|
+
socket: createSocketAdapter(ws),
|
|
655
|
+
store: config.session,
|
|
656
|
+
stt: config.stt,
|
|
657
|
+
turnDetection: {
|
|
658
|
+
silenceMs: config.turnDetection?.silenceMs ?? 700
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
runtime.activeSessions.set(sessionId, session);
|
|
662
|
+
await session.connect(createSocketAdapter(ws));
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
};
|
|
666
|
+
// src/memoryStore.ts
|
|
667
|
+
var createVoiceMemoryStore = () => {
|
|
668
|
+
const sessions = new Map;
|
|
669
|
+
const get = async (id) => sessions.get(id);
|
|
670
|
+
const getOrCreate = async (id) => {
|
|
671
|
+
let session = sessions.get(id);
|
|
672
|
+
if (!session) {
|
|
673
|
+
session = createVoiceSessionRecord(id);
|
|
674
|
+
sessions.set(id, session);
|
|
675
|
+
}
|
|
676
|
+
return session;
|
|
677
|
+
};
|
|
678
|
+
const set = async (id, value) => {
|
|
679
|
+
sessions.set(id, value);
|
|
680
|
+
};
|
|
681
|
+
const list = async () => Array.from(sessions.values()).map((session) => toVoiceSessionSummary(session)).sort((first, second) => (second.lastActivityAt ?? second.createdAt) - (first.lastActivityAt ?? first.createdAt));
|
|
682
|
+
const remove = async (id) => {
|
|
683
|
+
sessions.delete(id);
|
|
684
|
+
};
|
|
685
|
+
return { get, getOrCreate, list, remove, set };
|
|
686
|
+
};
|
|
687
|
+
export {
|
|
688
|
+
voice,
|
|
689
|
+
createVoiceSessionRecord,
|
|
690
|
+
createVoiceSession,
|
|
691
|
+
createVoiceMemoryStore,
|
|
692
|
+
createId
|
|
693
|
+
};
|