@oscharko-dev/keiko-server 0.2.7 → 0.2.9
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/.tsbuildinfo +1 -1
- package/dist/chat-handlers.d.ts +18 -2
- package/dist/chat-handlers.d.ts.map +1 -1
- package/dist/chat-handlers.js +185 -3
- package/dist/command-runner-errors.d.ts +17 -0
- package/dist/command-runner-errors.d.ts.map +1 -0
- package/dist/command-runner-errors.js +37 -0
- package/dist/command-runner-evidence.d.ts +23 -0
- package/dist/command-runner-evidence.d.ts.map +1 -0
- package/dist/command-runner-evidence.js +69 -0
- package/dist/command-runner-routes.d.ts +7 -0
- package/dist/command-runner-routes.d.ts.map +1 -0
- package/dist/command-runner-routes.js +175 -0
- package/dist/command-runner.d.ts +29 -0
- package/dist/command-runner.d.ts.map +1 -0
- package/dist/command-runner.js +348 -0
- package/dist/conversation-prompt.d.ts +2 -2
- package/dist/conversation-prompt.d.ts.map +1 -1
- package/dist/conversation-prompt.js +17 -1
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +3 -0
- package/dist/deps.d.ts +28 -1
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +288 -13
- package/dist/discussion-prompt.d.ts +4 -0
- package/dist/discussion-prompt.d.ts.map +1 -0
- package/dist/discussion-prompt.js +19 -0
- package/dist/editor/agentActionAudit.d.ts +18 -0
- package/dist/editor/agentActionAudit.d.ts.map +1 -0
- package/dist/editor/agentActionAudit.js +80 -0
- package/dist/editor/agentRoutes.d.ts +1 -0
- package/dist/editor/agentRoutes.d.ts.map +1 -1
- package/dist/editor/agentRoutes.js +292 -55
- package/dist/editor/agentSessionRegistry.d.ts +35 -0
- package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
- package/dist/editor/agentSessionRegistry.js +243 -0
- package/dist/editor/completionRoutes.d.ts.map +1 -1
- package/dist/editor/completionRoutes.js +5 -10
- package/dist/editor/languageRoutes.d.ts +12 -1
- package/dist/editor/languageRoutes.d.ts.map +1 -1
- package/dist/editor/languageRoutes.js +71 -8
- package/dist/editor/languageService.d.ts +3 -2
- package/dist/editor/languageService.d.ts.map +1 -1
- package/dist/editor/languageService.js +41 -3
- package/dist/editor/languageServiceHost.d.ts.map +1 -1
- package/dist/editor/languageServiceHost.js +2 -2
- package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
- package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageOperation.js +436 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
- package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
- package/dist/editor/lsp/hostLanguageProviders.js +161 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
- package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
- package/dist/editor/lsp/lspFrameCodec.js +164 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
- package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
- package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
- package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
- package/dist/editor/lsp/lspLanguageProvider.js +29 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
- package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
- package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
- package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
- package/dist/editor/lsp/lspNodeAdapter.js +230 -0
- package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
- package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
- package/dist/editor/lsp/lspProcessManager.js +255 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
- package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
- package/dist/editor/lsp/lspRestartThrottle.js +24 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
- package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
- package/dist/editor/lsp/lspStatusRoute.js +22 -0
- package/dist/editor/lsp/lspTransport.d.ts +19 -0
- package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
- package/dist/editor/lsp/lspTransport.js +55 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
- package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
- package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
- package/dist/files.d.ts +63 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +799 -1
- package/dist/gateway-readiness.d.ts +6 -0
- package/dist/gateway-readiness.d.ts.map +1 -0
- package/dist/gateway-readiness.js +624 -0
- package/dist/gateway-setup.d.ts +2 -0
- package/dist/gateway-setup.d.ts.map +1 -1
- package/dist/gateway-setup.js +275 -11
- package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
- package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetProjection.js +206 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
- package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/actionSheetRoutes.js +293 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
- package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
- package/dist/gitDelivery/commitRoutes.d.ts +23 -0
- package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/commitRoutes.js +204 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
- package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/evidenceRoutes.js +101 -0
- package/dist/gitDelivery/execution.d.ts +38 -0
- package/dist/gitDelivery/execution.d.ts.map +1 -0
- package/dist/gitDelivery/execution.js +117 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
- package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/localMutationRoutes.js +165 -0
- package/dist/gitDelivery/mergeExecution.d.ts +63 -0
- package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
- package/dist/gitDelivery/mergeExecution.js +168 -0
- package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
- package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/mergeRoutes.js +218 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
- package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
- package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
- package/dist/gitDelivery/prExecution.d.ts +54 -0
- package/dist/gitDelivery/prExecution.d.ts.map +1 -0
- package/dist/gitDelivery/prExecution.js +192 -0
- package/dist/gitDelivery/prRoutes.d.ts +12 -0
- package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/prRoutes.js +256 -0
- package/dist/gitDelivery/pushExecution.d.ts +43 -0
- package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
- package/dist/gitDelivery/pushExecution.js +124 -0
- package/dist/gitDelivery/pushRoutes.d.ts +12 -0
- package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/pushRoutes.js +200 -0
- package/dist/gitDelivery/requestGuards.d.ts +15 -0
- package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
- package/dist/gitDelivery/requestGuards.js +97 -0
- package/dist/gitDelivery/syncEvidence.d.ts +37 -0
- package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
- package/dist/gitDelivery/syncEvidence.js +85 -0
- package/dist/gitDelivery/syncExecution.d.ts +30 -0
- package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
- package/dist/gitDelivery/syncExecution.js +266 -0
- package/dist/gitDelivery/syncRoutes.d.ts +13 -0
- package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
- package/dist/gitDelivery/syncRoutes.js +200 -0
- package/dist/gitPorcelainStatus.d.ts +15 -0
- package/dist/gitPorcelainStatus.d.ts.map +1 -0
- package/dist/gitPorcelainStatus.js +104 -0
- package/dist/gitRepositoryReads.d.ts +10 -0
- package/dist/gitRepositoryReads.d.ts.map +1 -0
- package/dist/gitRepositoryReads.js +314 -0
- package/dist/gitRepositoryRoutes.d.ts +7 -0
- package/dist/gitRepositoryRoutes.d.ts.map +1 -0
- package/dist/gitRepositoryRoutes.js +221 -0
- package/dist/gitRoutes.d.ts +66 -0
- package/dist/gitRoutes.d.ts.map +1 -0
- package/dist/gitRoutes.js +543 -0
- package/dist/governed-workflow.d.ts +2 -0
- package/dist/governed-workflow.d.ts.map +1 -1
- package/dist/governed-workflow.js +4 -0
- package/dist/grounded-qa-hybrid.d.ts.map +1 -1
- package/dist/grounded-qa-hybrid.js +2 -0
- package/dist/grounded-qa-multi-source.d.ts.map +1 -1
- package/dist/grounded-qa-multi-source.js +1 -0
- package/dist/grounded-qa.d.ts +11 -0
- package/dist/grounded-qa.d.ts.map +1 -1
- package/dist/grounded-qa.js +14 -4
- package/dist/headers.d.ts +4 -1
- package/dist/headers.d.ts.map +1 -1
- package/dist/headers.js +11 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/local-knowledge-grounded-qa.d.ts.map +1 -1
- package/dist/local-knowledge-grounded-qa.js +11 -2
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
- package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
- package/dist/read-handlers.d.ts +5 -0
- package/dist/read-handlers.d.ts.map +1 -1
- package/dist/read-handlers.js +57 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +260 -12
- package/dist/run-engine.d.ts.map +1 -1
- package/dist/run-engine.js +3 -0
- package/dist/run-handlers.d.ts +0 -1
- package/dist/run-handlers.d.ts.map +1 -1
- package/dist/run-handlers.js +64 -211
- package/dist/run-request.d.ts +11 -0
- package/dist/run-request.d.ts.map +1 -1
- package/dist/run-request.js +158 -10
- package/dist/runtime/capabilityDetector.d.ts +38 -0
- package/dist/runtime/capabilityDetector.d.ts.map +1 -0
- package/dist/runtime/capabilityDetector.js +443 -0
- package/dist/runtime/capabilityRoutes.d.ts +9 -0
- package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
- package/dist/runtime/capabilityRoutes.js +45 -0
- package/dist/runtime/containerEngineDetector.d.ts +17 -0
- package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
- package/dist/runtime/containerEngineDetector.js +222 -0
- package/dist/runtime/containerRoutes.d.ts +8 -0
- package/dist/runtime/containerRoutes.d.ts.map +1 -0
- package/dist/runtime/containerRoutes.js +207 -0
- package/dist/runtime/containerRunner-errors.d.ts +18 -0
- package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
- package/dist/runtime/containerRunner-errors.js +42 -0
- package/dist/runtime/containerRunner-evidence.d.ts +24 -0
- package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
- package/dist/runtime/containerRunner-evidence.js +74 -0
- package/dist/runtime/containerRunner.d.ts +37 -0
- package/dist/runtime/containerRunner.d.ts.map +1 -0
- package/dist/runtime/containerRunner.js +443 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +24 -4
- package/dist/store/db.d.ts.map +1 -1
- package/dist/store/db.js +2 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/messages.d.ts +2 -1
- package/dist/store/messages.d.ts.map +1 -1
- package/dist/store/messages.js +46 -4
- package/dist/store/schema.d.ts +1 -1
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +68 -1
- package/dist/store/types.d.ts +3 -2
- package/dist/store/types.d.ts.map +1 -1
- package/dist/task-workspace/active-store.d.ts +21 -0
- package/dist/task-workspace/active-store.d.ts.map +1 -0
- package/dist/task-workspace/active-store.js +55 -0
- package/dist/task-workspace/authorization.d.ts +7 -0
- package/dist/task-workspace/authorization.d.ts.map +1 -0
- package/dist/task-workspace/authorization.js +54 -0
- package/dist/task-workspace/binding.d.ts +3 -0
- package/dist/task-workspace/binding.d.ts.map +1 -0
- package/dist/task-workspace/binding.js +22 -0
- package/dist/task-workspace/cleanup.d.ts +4 -0
- package/dist/task-workspace/cleanup.d.ts.map +1 -0
- package/dist/task-workspace/cleanup.js +428 -0
- package/dist/task-workspace/errors.d.ts +14 -0
- package/dist/task-workspace/errors.d.ts.map +1 -0
- package/dist/task-workspace/errors.js +81 -0
- package/dist/task-workspace/evidence.d.ts +32 -0
- package/dist/task-workspace/evidence.d.ts.map +1 -0
- package/dist/task-workspace/evidence.js +52 -0
- package/dist/task-workspace/field-safety.d.ts +3 -0
- package/dist/task-workspace/field-safety.d.ts.map +1 -0
- package/dist/task-workspace/field-safety.js +42 -0
- package/dist/task-workspace/health.d.ts +4 -0
- package/dist/task-workspace/health.d.ts.map +1 -0
- package/dist/task-workspace/health.js +163 -0
- package/dist/task-workspace/lifecycle.d.ts +3 -0
- package/dist/task-workspace/lifecycle.d.ts.map +1 -0
- package/dist/task-workspace/lifecycle.js +248 -0
- package/dist/task-workspace/locks.d.ts +13 -0
- package/dist/task-workspace/locks.d.ts.map +1 -0
- package/dist/task-workspace/locks.js +44 -0
- package/dist/task-workspace/managed-root.d.ts +7 -0
- package/dist/task-workspace/managed-root.d.ts.map +1 -0
- package/dist/task-workspace/managed-root.js +98 -0
- package/dist/task-workspace/mutex.d.ts +8 -0
- package/dist/task-workspace/mutex.d.ts.map +1 -0
- package/dist/task-workspace/mutex.js +82 -0
- package/dist/task-workspace/naming.d.ts +15 -0
- package/dist/task-workspace/naming.d.ts.map +1 -0
- package/dist/task-workspace/naming.js +0 -0
- package/dist/task-workspace/provisioning.d.ts +3 -0
- package/dist/task-workspace/provisioning.d.ts.map +1 -0
- package/dist/task-workspace/provisioning.js +528 -0
- package/dist/task-workspace/reconciliation.d.ts +15 -0
- package/dist/task-workspace/reconciliation.d.ts.map +1 -0
- package/dist/task-workspace/reconciliation.js +274 -0
- package/dist/task-workspace/repair.d.ts +3 -0
- package/dist/task-workspace/repair.d.ts.map +1 -0
- package/dist/task-workspace/repair.js +286 -0
- package/dist/task-workspace/routes.d.ts +19 -0
- package/dist/task-workspace/routes.d.ts.map +1 -0
- package/dist/task-workspace/routes.js +481 -0
- package/dist/task-workspace/store.d.ts +12 -0
- package/dist/task-workspace/store.d.ts.map +1 -0
- package/dist/task-workspace/store.js +128 -0
- package/dist/task-workspace/types.d.ts +170 -0
- package/dist/task-workspace/types.d.ts.map +1 -0
- package/dist/task-workspace/types.js +5 -0
- package/dist/voice-action-governance.d.ts +23 -0
- package/dist/voice-action-governance.d.ts.map +1 -0
- package/dist/voice-action-governance.js +126 -0
- package/dist/voice-handlers.d.ts +6 -0
- package/dist/voice-handlers.d.ts.map +1 -0
- package/dist/voice-handlers.js +570 -0
- package/dist/voice-realtime-grounded-tool.d.ts +31 -0
- package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
- package/dist/voice-realtime-grounded-tool.js +322 -0
- package/dist/voice-realtime.d.ts +69 -0
- package/dist/voice-realtime.d.ts.map +1 -0
- package/dist/voice-realtime.js +787 -0
- package/dist/workspace-state-handlers.d.ts +5 -0
- package/dist/workspace-state-handlers.d.ts.map +1 -0
- package/dist/workspace-state-handlers.js +106 -0
- package/package.json +20 -19
- package/dist/grounded-handoff.d.ts +0 -4
- package/dist/grounded-handoff.d.ts.map +0 -1
- package/dist/grounded-handoff.js +0 -445
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { resolveVoiceCapability, } from "@oscharko-dev/keiko-model-gateway";
|
|
2
|
+
import { stripUnsafeFormatChars } from "@oscharko-dev/keiko-contracts/text-safety";
|
|
3
|
+
import { validateProjectPath } from "./store/validation.js";
|
|
4
|
+
import { currentGatewayConfig } from "./deps.js";
|
|
5
|
+
import { errorBody } from "./routes.js";
|
|
6
|
+
import { isVoiceDisabledByPolicy } from "./read-handlers.js";
|
|
7
|
+
import { buildVoiceTurnMemoryResult, desktopChatErrorResult, parseMemoryRequest, } from "./chat-handlers.js";
|
|
8
|
+
import { resolveConversationMemoryContext } from "./memory-conversation-context.js";
|
|
9
|
+
import { runGroundedAskInput } from "./grounded-qa.js";
|
|
10
|
+
const MAX_BODY_BYTES = 128_000;
|
|
11
|
+
const MAX_TEXT_CHARS = 16_000;
|
|
12
|
+
const MAX_CALL_ID_CHARS = 200;
|
|
13
|
+
const MAX_CACHE_ENTRIES = 128;
|
|
14
|
+
const SAFE_CALL_ID = /^[\x21-\x7e]+$/;
|
|
15
|
+
class BodyTooLargeError extends Error {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("body too large");
|
|
18
|
+
this.name = "BodyTooLargeError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const responseCache = new Map();
|
|
22
|
+
const responseInFlight = new Map();
|
|
23
|
+
function readBody(req) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let total = 0;
|
|
27
|
+
let capped = false;
|
|
28
|
+
req.on("data", (chunk) => {
|
|
29
|
+
total += chunk.length;
|
|
30
|
+
if (total > MAX_BODY_BYTES) {
|
|
31
|
+
if (!capped) {
|
|
32
|
+
capped = true;
|
|
33
|
+
chunks.length = 0;
|
|
34
|
+
reject(new BodyTooLargeError());
|
|
35
|
+
req.resume();
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
chunks.push(chunk);
|
|
40
|
+
});
|
|
41
|
+
req.on("end", () => {
|
|
42
|
+
if (!capped)
|
|
43
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
44
|
+
});
|
|
45
|
+
req.on("error", reject);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function isRecord(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50
|
+
}
|
|
51
|
+
function isRouteResult(value) {
|
|
52
|
+
return isRecord(value) && typeof value.status === "number" && "body" in value;
|
|
53
|
+
}
|
|
54
|
+
async function readJsonObject(req) {
|
|
55
|
+
let raw;
|
|
56
|
+
try {
|
|
57
|
+
raw = await readBody(req);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof BodyTooLargeError) {
|
|
61
|
+
return {
|
|
62
|
+
status: 413,
|
|
63
|
+
body: errorBody("PAYLOAD_TOO_LARGE", "Request body exceeds the size limit."),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = raw.length === 0 ? {} : JSON.parse(raw);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Request body is not valid JSON.") };
|
|
74
|
+
}
|
|
75
|
+
if (!isRecord(parsed)) {
|
|
76
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Request body must be a JSON object.") };
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
function readRequiredString(body, key, maxChars = MAX_TEXT_CHARS) {
|
|
81
|
+
const value = typeof body[key] === "string" ? body[key].trim() : "";
|
|
82
|
+
if (value.length === 0 || value.length > maxChars) {
|
|
83
|
+
return {
|
|
84
|
+
status: 400,
|
|
85
|
+
body: errorBody("BAD_REQUEST", `${key} must be a non-empty bounded string.`),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function readOptionalText(body, key) {
|
|
91
|
+
if (body[key] === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (typeof body[key] !== "string") {
|
|
94
|
+
return { status: 400, body: errorBody("BAD_REQUEST", `${key} must be a string when provided.`) };
|
|
95
|
+
}
|
|
96
|
+
const value = body[key].trim();
|
|
97
|
+
if (value.length === 0 || value.length > MAX_TEXT_CHARS) {
|
|
98
|
+
return {
|
|
99
|
+
status: 400,
|
|
100
|
+
body: errorBody("BAD_REQUEST", `${key} must be a non-empty bounded string when provided.`),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
function parseRequest(body) {
|
|
106
|
+
const chatId = readRequiredString(body, "chatId", MAX_CALL_ID_CHARS);
|
|
107
|
+
if (isRouteResult(chatId))
|
|
108
|
+
return chatId;
|
|
109
|
+
const projectPath = readRequiredString(body, "projectPath");
|
|
110
|
+
if (isRouteResult(projectPath))
|
|
111
|
+
return projectPath;
|
|
112
|
+
const callId = readRequiredString(body, "callId", MAX_CALL_ID_CHARS);
|
|
113
|
+
if (isRouteResult(callId))
|
|
114
|
+
return callId;
|
|
115
|
+
if (!SAFE_CALL_ID.test(callId)) {
|
|
116
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "callId contains invalid characters.") };
|
|
117
|
+
}
|
|
118
|
+
const query = readRequiredString(body, "query");
|
|
119
|
+
if (isRouteResult(query))
|
|
120
|
+
return query;
|
|
121
|
+
const userTranscript = readOptionalText(body, "userTranscript");
|
|
122
|
+
if (isRouteResult(userTranscript))
|
|
123
|
+
return userTranscript;
|
|
124
|
+
const modelId = readOptionalText(body, "modelId");
|
|
125
|
+
if (isRouteResult(modelId))
|
|
126
|
+
return modelId;
|
|
127
|
+
const memory = parseMemoryRequest(body.memory);
|
|
128
|
+
if (isRouteResult(memory))
|
|
129
|
+
return memory;
|
|
130
|
+
return { chatId, projectPath, callId, query, userTranscript, modelId, memory };
|
|
131
|
+
}
|
|
132
|
+
function normalizeText(text) {
|
|
133
|
+
return text.replace(/\s+/gu, " ").trim().toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
function cacheKey(request) {
|
|
136
|
+
return `${request.chatId}:${request.callId}:${normalizeText(request.query)}`;
|
|
137
|
+
}
|
|
138
|
+
function rememberResponse(key, response) {
|
|
139
|
+
responseCache.set(key, response);
|
|
140
|
+
while (responseCache.size > MAX_CACHE_ENTRIES) {
|
|
141
|
+
const first = responseCache.keys().next().value;
|
|
142
|
+
if (first === undefined)
|
|
143
|
+
return;
|
|
144
|
+
responseCache.delete(first);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function realtimeGroundedVoiceAvailable(resolution) {
|
|
148
|
+
return (resolution.available &&
|
|
149
|
+
resolution.profile === "full-realtime" &&
|
|
150
|
+
resolution.capabilities.realtimeVoice &&
|
|
151
|
+
resolution.transport.webrtcMedia);
|
|
152
|
+
}
|
|
153
|
+
function validateVoiceToolCapability(deps) {
|
|
154
|
+
const resolution = resolveVoiceCapability(currentGatewayConfig(deps) ?? { providers: [] }, {
|
|
155
|
+
policyDisabled: isVoiceDisabledByPolicy(deps.env),
|
|
156
|
+
});
|
|
157
|
+
if (realtimeGroundedVoiceAvailable(resolution)) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
status: 403,
|
|
162
|
+
body: errorBody("VOICE_TOOL_UNAVAILABLE", "Realtime grounded voice is not available for this deployment."),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function normalizeProjectPath(projectPath, deps) {
|
|
166
|
+
try {
|
|
167
|
+
return validateProjectPath(projectPath, { mustExist: false });
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return desktopChatErrorResult(error, deps);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function findValidatedChat(deps, projectPath, chatId) {
|
|
174
|
+
const chat = deps.store.findChatById(chatId);
|
|
175
|
+
if (chat?.projectPath !== projectPath) {
|
|
176
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Chat not found.") };
|
|
177
|
+
}
|
|
178
|
+
return chat;
|
|
179
|
+
}
|
|
180
|
+
function citationMarker(index, citation) {
|
|
181
|
+
return citation.marker !== undefined ? `[${String(citation.marker)}]` : `[${String(index + 1)}]`;
|
|
182
|
+
}
|
|
183
|
+
function summarizeFolderCitation(citation, index) {
|
|
184
|
+
return {
|
|
185
|
+
marker: citationMarker(index, citation),
|
|
186
|
+
label: citation.scopePath,
|
|
187
|
+
...(citation.source !== undefined ? { source: citation.source } : {}),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function summarizeKnowledgeCitation(citation) {
|
|
191
|
+
return {
|
|
192
|
+
marker: citation.marker,
|
|
193
|
+
label: citation.label,
|
|
194
|
+
...(citation.source !== undefined ? { source: citation.source } : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function summarizeCitations(answer) {
|
|
198
|
+
if (answer.groundingKind === "local-knowledge") {
|
|
199
|
+
return answer.citations.slice(0, 8).map(summarizeKnowledgeCitation);
|
|
200
|
+
}
|
|
201
|
+
const folder = answer.citations.slice(0, 8).map(summarizeFolderCitation);
|
|
202
|
+
if (answer.groundingKind === "hybrid") {
|
|
203
|
+
return [...folder, ...answer.knowledgeCitations.slice(0, 8).map(summarizeKnowledgeCitation)];
|
|
204
|
+
}
|
|
205
|
+
return folder;
|
|
206
|
+
}
|
|
207
|
+
function buildToolOutput(answer) {
|
|
208
|
+
return {
|
|
209
|
+
status: "ok",
|
|
210
|
+
answer: answer.content,
|
|
211
|
+
groundingKind: answer.groundingKind,
|
|
212
|
+
elapsedMs: answer.elapsedMs,
|
|
213
|
+
citations: summarizeCitations(answer),
|
|
214
|
+
...(answer.evidenceRunId !== undefined ? { evidenceRunId: answer.evidenceRunId } : {}),
|
|
215
|
+
persisted: {
|
|
216
|
+
userMessageId: answer.userMessageId,
|
|
217
|
+
assistantMessageId: answer.assistantMessageId,
|
|
218
|
+
},
|
|
219
|
+
instruction: "Speak this answer faithfully in a concise voice-friendly way. Do not add facts that are not in the answer.",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function isGroundedAnswer(value) {
|
|
223
|
+
return (isRecord(value) &&
|
|
224
|
+
(value.groundingKind === "connected-context" ||
|
|
225
|
+
value.groundingKind === "local-knowledge" ||
|
|
226
|
+
value.groundingKind === "hybrid") &&
|
|
227
|
+
typeof value.userMessageId === "string" &&
|
|
228
|
+
typeof value.assistantMessageId === "string" &&
|
|
229
|
+
typeof value.content === "string");
|
|
230
|
+
}
|
|
231
|
+
function messagesForAnswer(deps, answer) {
|
|
232
|
+
const user = deps.store.findMessageById(answer.userMessageId);
|
|
233
|
+
const assistant = deps.store.findMessageById(answer.assistantMessageId);
|
|
234
|
+
if (user === undefined || assistant === undefined) {
|
|
235
|
+
return {
|
|
236
|
+
status: 500,
|
|
237
|
+
body: errorBody("INTERNAL", "Grounded voice turn was not persisted correctly."),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return [user, assistant];
|
|
241
|
+
}
|
|
242
|
+
async function buildMemory(deps, request, projectPath, chat, messages) {
|
|
243
|
+
if (request.memory === undefined)
|
|
244
|
+
return undefined;
|
|
245
|
+
const memoryContext = resolveConversationMemoryContext(deps, projectPath, request.chatId);
|
|
246
|
+
if (isRouteResult(memoryContext))
|
|
247
|
+
return memoryContext;
|
|
248
|
+
return buildVoiceTurnMemoryResult(deps, {
|
|
249
|
+
chatId: request.chatId,
|
|
250
|
+
projectPath,
|
|
251
|
+
messages: messages.map((message) => ({
|
|
252
|
+
role: message.role === "assistant" ? "assistant" : "user",
|
|
253
|
+
content: message.content,
|
|
254
|
+
timestamp: message.timestamp,
|
|
255
|
+
})),
|
|
256
|
+
memory: request.memory,
|
|
257
|
+
}, chat, memoryContext);
|
|
258
|
+
}
|
|
259
|
+
async function buildRealtimeGroundedToolResponse(deps, request, projectPath, chat) {
|
|
260
|
+
const content = stripUnsafeFormatChars(request.userTranscript ?? request.query).trim();
|
|
261
|
+
const grounded = await runGroundedAskInput({
|
|
262
|
+
chatId: request.chatId,
|
|
263
|
+
content,
|
|
264
|
+
modelId: request.modelId,
|
|
265
|
+
}, deps);
|
|
266
|
+
if (grounded.status !== 200)
|
|
267
|
+
return grounded;
|
|
268
|
+
if (!isGroundedAnswer(grounded.body)) {
|
|
269
|
+
return { status: 500, body: errorBody("INTERNAL", "Grounded voice tool returned no answer.") };
|
|
270
|
+
}
|
|
271
|
+
const answer = grounded.body;
|
|
272
|
+
const messages = messagesForAnswer(deps, answer);
|
|
273
|
+
if (isRouteResult(messages))
|
|
274
|
+
return messages;
|
|
275
|
+
const updatedChat = deps.store.findChatById(request.chatId) ?? chat;
|
|
276
|
+
const memory = await buildMemory(deps, request, projectPath, updatedChat, messages);
|
|
277
|
+
if (isRouteResult(memory))
|
|
278
|
+
return memory;
|
|
279
|
+
return {
|
|
280
|
+
chat: updatedChat,
|
|
281
|
+
messages,
|
|
282
|
+
groundedAnswer: answer,
|
|
283
|
+
toolOutput: buildToolOutput(answer),
|
|
284
|
+
...(memory === undefined ? {} : { memory }),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export async function handleRealtimeGroundedVoiceTool(ctx, deps) {
|
|
288
|
+
const body = await readJsonObject(ctx.req);
|
|
289
|
+
if (isRouteResult(body))
|
|
290
|
+
return body;
|
|
291
|
+
const request = parseRequest(body);
|
|
292
|
+
if (isRouteResult(request))
|
|
293
|
+
return request;
|
|
294
|
+
const key = cacheKey(request);
|
|
295
|
+
const cached = responseCache.get(key);
|
|
296
|
+
if (cached !== undefined) {
|
|
297
|
+
return { status: 200, body: cached };
|
|
298
|
+
}
|
|
299
|
+
const capability = validateVoiceToolCapability(deps);
|
|
300
|
+
if (capability !== undefined)
|
|
301
|
+
return capability;
|
|
302
|
+
const projectPath = normalizeProjectPath(request.projectPath, deps);
|
|
303
|
+
if (isRouteResult(projectPath))
|
|
304
|
+
return projectPath;
|
|
305
|
+
const chat = findValidatedChat(deps, projectPath, request.chatId);
|
|
306
|
+
if (isRouteResult(chat))
|
|
307
|
+
return chat;
|
|
308
|
+
const inFlight = responseInFlight.get(key);
|
|
309
|
+
if (inFlight !== undefined) {
|
|
310
|
+
const result = await inFlight;
|
|
311
|
+
return isRouteResult(result) ? result : { status: 200, body: result };
|
|
312
|
+
}
|
|
313
|
+
const responsePromise = buildRealtimeGroundedToolResponse(deps, request, projectPath, chat);
|
|
314
|
+
responseInFlight.set(key, responsePromise);
|
|
315
|
+
const response = await responsePromise.finally(() => {
|
|
316
|
+
responseInFlight.delete(key);
|
|
317
|
+
});
|
|
318
|
+
if (isRouteResult(response))
|
|
319
|
+
return response;
|
|
320
|
+
rememberResponse(key, response);
|
|
321
|
+
return { status: 200, body: response };
|
|
322
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
3
|
+
import { type RealtimeNegotiationOutcome } from "@oscharko-dev/keiko-model-gateway";
|
|
4
|
+
import { type VoiceControlMessage, type VoicePersona, type VoiceProfile, type VoiceProviderLocality, type VoiceSessionChatContext } from "@oscharko-dev/keiko-contracts";
|
|
5
|
+
import { type UiHandlerDeps } from "./deps.js";
|
|
6
|
+
export declare const VOICE_CONTROL_PATH = "/api/voice/control";
|
|
7
|
+
interface SessionState {
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
readonly idempotencyKey: string;
|
|
10
|
+
readonly profile: VoiceProfile;
|
|
11
|
+
readonly providerLocality: VoiceProviderLocality | undefined;
|
|
12
|
+
readonly persona: VoicePersona | undefined;
|
|
13
|
+
readonly chatContext: VoiceSessionChatContext | undefined;
|
|
14
|
+
hostSeq: number;
|
|
15
|
+
lastClientSeq: number;
|
|
16
|
+
readonly replay: VoiceControlMessage[];
|
|
17
|
+
detachedAt: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
type NegotiateFn = (offerSdp: string, persona: VoicePersona | undefined, chatContext: VoiceSessionChatContext | undefined, signal: AbortSignal) => Promise<RealtimeNegotiationOutcome>;
|
|
20
|
+
export interface VoiceControlPlane {
|
|
21
|
+
handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): boolean;
|
|
22
|
+
closeAll(): void;
|
|
23
|
+
}
|
|
24
|
+
export interface VoiceControlSocket {
|
|
25
|
+
send(data: string): void;
|
|
26
|
+
close(code: number, reason: string): void;
|
|
27
|
+
}
|
|
28
|
+
export interface VoiceControlConnectionOptions {
|
|
29
|
+
readonly socket: VoiceControlSocket;
|
|
30
|
+
readonly session: SessionState;
|
|
31
|
+
readonly negotiate: NegotiateFn;
|
|
32
|
+
readonly redact: (value: unknown) => unknown;
|
|
33
|
+
}
|
|
34
|
+
export declare class VoiceControlConnection {
|
|
35
|
+
private readonly socket;
|
|
36
|
+
private readonly session;
|
|
37
|
+
private readonly negotiate;
|
|
38
|
+
private readonly redact;
|
|
39
|
+
private negotiation;
|
|
40
|
+
private closed;
|
|
41
|
+
constructor(options: VoiceControlConnectionOptions);
|
|
42
|
+
start(resume: boolean): void;
|
|
43
|
+
receive(raw: string): Promise<void>;
|
|
44
|
+
dispose(): void;
|
|
45
|
+
private dispatchIn;
|
|
46
|
+
private cancelNegotiation;
|
|
47
|
+
private handleOffer;
|
|
48
|
+
private recordTranscript;
|
|
49
|
+
private emitError;
|
|
50
|
+
private build;
|
|
51
|
+
private emit;
|
|
52
|
+
private record;
|
|
53
|
+
private dispatchOut;
|
|
54
|
+
private fail;
|
|
55
|
+
private shutdown;
|
|
56
|
+
}
|
|
57
|
+
export interface VoiceControlPlaneDeps {
|
|
58
|
+
readonly port: number;
|
|
59
|
+
readonly handlerDeps: () => UiHandlerDeps;
|
|
60
|
+
}
|
|
61
|
+
export interface AliveControlSocket {
|
|
62
|
+
isAlive?: boolean;
|
|
63
|
+
ping(): void;
|
|
64
|
+
terminate(): void;
|
|
65
|
+
}
|
|
66
|
+
export declare function sweepControlHeartbeat(sockets: Iterable<AliveControlSocket>): void;
|
|
67
|
+
export declare function createVoiceControlPlane(planeDeps: VoiceControlPlaneDeps): VoiceControlPlane;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=voice-realtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-realtime.d.ts","sourceRoot":"","sources":["../src/voice-realtime.ts"],"names":[],"mappings":"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"}
|