@alexkroman1/aai 0.10.2 → 0.10.4
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/_internal-types.d.ts +8 -1
- package/dist/_runtime-conformance.d.ts +64 -0
- package/dist/_test-utils.d.ts +70 -0
- package/dist/_utils.d.ts +1 -8
- package/dist/_utils.js +1 -13
- package/dist/builtin-tools.d.ts +1 -5
- package/dist/constants-BbAOvKl_.js +47 -0
- package/dist/constants.d.ts +44 -0
- package/dist/direct-executor-BfHrDdPL.js +1589 -0
- package/dist/direct-executor.d.ts +90 -31
- package/dist/hooks.d.ts +44 -0
- package/dist/hooks.js +58 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +2 -2
- package/dist/internal.d.ts +19 -0
- package/dist/internal.js +209 -0
- package/dist/kv.d.ts +1 -1
- package/dist/kv.js +5 -4
- package/dist/matchers.js +1 -1
- package/dist/protocol.d.ts +3 -29
- package/dist/protocol.js +2 -24
- package/dist/server.d.ts +25 -38
- package/dist/server.js +114 -138
- package/dist/session.d.ts +65 -44
- package/dist/{testing-MRl3SXsI.js → testing-BonJtfHJ.js} +26 -46
- package/dist/testing.d.ts +9 -14
- package/dist/testing.js +2 -2
- package/dist/types.d.ts +24 -226
- package/dist/types.js +6 -22
- package/dist/types.test-d.d.ts +7 -0
- package/dist/unstorage-kv.d.ts +33 -0
- package/dist/vite-plugin.d.ts +15 -0
- package/dist/vite-plugin.js +82 -0
- package/dist/ws-handler.d.ts +1 -2
- package/package.json +29 -84
- package/dist/_internal-types.js +0 -61
- package/dist/_session-ctx.d.ts +0 -73
- package/dist/_session-otel.d.ts +0 -43
- package/dist/_session-persist.d.ts +0 -30
- package/dist/_ssrf.d.ts +0 -30
- package/dist/_ssrf.js +0 -123
- package/dist/direct-executor-Ca0wt5H0.js +0 -572
- package/dist/middleware-core.d.ts +0 -47
- package/dist/middleware-core.js +0 -107
- package/dist/middleware.d.ts +0 -37
- package/dist/runtime.js +0 -53
- package/dist/s2s.js +0 -272
- package/dist/session-BkN9u0ni.js +0 -683
- package/dist/session.js +0 -2
- package/dist/sqlite-kv.d.ts +0 -34
- package/dist/sqlite-kv.js +0 -133
- package/dist/sqlite-vector.d.ts +0 -58
- package/dist/sqlite-vector.js +0 -149
- package/dist/telemetry.d.ts +0 -49
- package/dist/telemetry.js +0 -95
- package/dist/vector.d.ts +0 -85
- package/dist/vector.js +0 -49
- package/dist/worker-entry.d.ts +0 -47
- package/dist/worker-entry.js +0 -70
- package/dist/ws-handler.js +0 -207
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure middleware runner functions — zero runtime dependencies.
|
|
3
|
-
*
|
|
4
|
-
* This module is intentionally dependency-free so it can be bundled into
|
|
5
|
-
* the secure-exec isolate harness (which has no access to node_modules).
|
|
6
|
-
* Only `import type` statements are allowed here.
|
|
7
|
-
*/
|
|
8
|
-
import type { HookContext, Middleware, MiddlewareBlockResult } from "./types.ts";
|
|
9
|
-
/** Result from a middleware tool call interceptor. */
|
|
10
|
-
export type ToolInterceptResult = {
|
|
11
|
-
type: "block";
|
|
12
|
-
reason: string;
|
|
13
|
-
} | {
|
|
14
|
-
type: "result";
|
|
15
|
-
result: string;
|
|
16
|
-
} | {
|
|
17
|
-
type: "args";
|
|
18
|
-
args: Record<string, unknown>;
|
|
19
|
-
} | undefined;
|
|
20
|
-
/**
|
|
21
|
-
* Run all `beforeInput` middleware in order, piping the text through each.
|
|
22
|
-
* Symmetric to {@link runOutputFilters} but for user input.
|
|
23
|
-
*/
|
|
24
|
-
export declare function runInputFilters(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<string>;
|
|
25
|
-
/**
|
|
26
|
-
* Run all `beforeTurn` middleware in order. Returns a block result if any
|
|
27
|
-
* middleware blocks the turn, or `undefined` to proceed.
|
|
28
|
-
*/
|
|
29
|
-
export declare function runBeforeTurnMiddleware(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<MiddlewareBlockResult | undefined>;
|
|
30
|
-
/**
|
|
31
|
-
* Run all `afterTurn` middleware in reverse order.
|
|
32
|
-
*/
|
|
33
|
-
export declare function runAfterTurnMiddleware(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Run all `beforeToolCall` middleware in order. Returns a result that
|
|
36
|
-
* may block execution, provide a cached result, or transform args.
|
|
37
|
-
* Returns `undefined` to proceed with normal execution.
|
|
38
|
-
*/
|
|
39
|
-
export declare function runToolCallInterceptors(middleware: readonly Middleware[], toolName: string, args: Readonly<Record<string, unknown>>, ctx: HookContext): Promise<ToolInterceptResult>;
|
|
40
|
-
/**
|
|
41
|
-
* Run all `afterToolCall` middleware in reverse order.
|
|
42
|
-
*/
|
|
43
|
-
export declare function runAfterToolCallMiddleware(middleware: readonly Middleware[], toolName: string, args: Readonly<Record<string, unknown>>, result: string, ctx: HookContext): Promise<void>;
|
|
44
|
-
/**
|
|
45
|
-
* Run all `beforeOutput` middleware in order, piping the text through each.
|
|
46
|
-
*/
|
|
47
|
-
export declare function runOutputFilters(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<string>;
|
package/dist/middleware-core.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
//#region middleware-core.ts
|
|
2
|
-
/**
|
|
3
|
-
* Run all `beforeInput` middleware in order, piping the text through each.
|
|
4
|
-
* Symmetric to {@link runOutputFilters} but for user input.
|
|
5
|
-
*/
|
|
6
|
-
async function runInputFilters(middleware, text, ctx) {
|
|
7
|
-
let filtered = text;
|
|
8
|
-
for (const mw of middleware) {
|
|
9
|
-
if (!mw.beforeInput) continue;
|
|
10
|
-
try {
|
|
11
|
-
filtered = await mw.beforeInput(filtered, ctx);
|
|
12
|
-
} catch (err) {
|
|
13
|
-
console.warn("Middleware beforeInput failed:", err);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return filtered;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Run all `beforeTurn` middleware in order. Returns a block result if any
|
|
20
|
-
* middleware blocks the turn, or `undefined` to proceed.
|
|
21
|
-
*/
|
|
22
|
-
async function runBeforeTurnMiddleware(middleware, text, ctx) {
|
|
23
|
-
for (const mw of middleware) {
|
|
24
|
-
if (!mw.beforeTurn) continue;
|
|
25
|
-
try {
|
|
26
|
-
const result = await mw.beforeTurn(text, ctx);
|
|
27
|
-
if (result && "block" in result && result.block) return result;
|
|
28
|
-
} catch (err) {
|
|
29
|
-
console.warn("Middleware beforeTurn failed:", err);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Run all `afterTurn` middleware in reverse order.
|
|
35
|
-
*/
|
|
36
|
-
async function runAfterTurnMiddleware(middleware, text, ctx) {
|
|
37
|
-
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
38
|
-
const mw = middleware[i];
|
|
39
|
-
if (!mw?.afterTurn) continue;
|
|
40
|
-
try {
|
|
41
|
-
await mw.afterTurn(text, ctx);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
console.warn("Middleware afterTurn failed:", err);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Run all `beforeToolCall` middleware in order. Returns a result that
|
|
49
|
-
* may block execution, provide a cached result, or transform args.
|
|
50
|
-
* Returns `undefined` to proceed with normal execution.
|
|
51
|
-
*/
|
|
52
|
-
async function runToolCallInterceptors(middleware, toolName, args, ctx) {
|
|
53
|
-
let currentArgs = args;
|
|
54
|
-
for (const mw of middleware) {
|
|
55
|
-
if (!mw.beforeToolCall) continue;
|
|
56
|
-
try {
|
|
57
|
-
const result = await mw.beforeToolCall(toolName, currentArgs, ctx);
|
|
58
|
-
if (!result) continue;
|
|
59
|
-
if ("block" in result && result.block) return {
|
|
60
|
-
type: "block",
|
|
61
|
-
reason: result.reason
|
|
62
|
-
};
|
|
63
|
-
if ("result" in result) return {
|
|
64
|
-
type: "result",
|
|
65
|
-
result: result.result
|
|
66
|
-
};
|
|
67
|
-
if ("args" in result) currentArgs = result.args;
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.warn("Middleware beforeToolCall failed:", err);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (currentArgs !== args) return {
|
|
73
|
-
type: "args",
|
|
74
|
-
args: currentArgs
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Run all `afterToolCall` middleware in reverse order.
|
|
79
|
-
*/
|
|
80
|
-
async function runAfterToolCallMiddleware(middleware, toolName, args, result, ctx) {
|
|
81
|
-
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
82
|
-
const mw = middleware[i];
|
|
83
|
-
if (!mw?.afterToolCall) continue;
|
|
84
|
-
try {
|
|
85
|
-
await mw.afterToolCall(toolName, args, result, ctx);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
console.warn("Middleware afterToolCall failed:", err);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Run all `beforeOutput` middleware in order, piping the text through each.
|
|
93
|
-
*/
|
|
94
|
-
async function runOutputFilters(middleware, text, ctx) {
|
|
95
|
-
let filtered = text;
|
|
96
|
-
for (const mw of middleware) {
|
|
97
|
-
if (!mw.beforeOutput) continue;
|
|
98
|
-
try {
|
|
99
|
-
filtered = await mw.beforeOutput(filtered, ctx);
|
|
100
|
-
} catch (err) {
|
|
101
|
-
console.warn("Middleware beforeOutput failed:", err);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return filtered;
|
|
105
|
-
}
|
|
106
|
-
//#endregion
|
|
107
|
-
export { runAfterToolCallMiddleware, runAfterTurnMiddleware, runBeforeTurnMiddleware, runInputFilters, runOutputFilters, runToolCallInterceptors };
|
package/dist/middleware.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware runner — executes middleware chains for turns, tool calls,
|
|
3
|
-
* and output filtering.
|
|
4
|
-
*
|
|
5
|
-
* Pure runner logic lives in middleware-core.ts (isolate-safe, zero deps).
|
|
6
|
-
* This module re-exports it and adds the HookInvoker interface.
|
|
7
|
-
*/
|
|
8
|
-
import type { StepInfo } from "./types.ts";
|
|
9
|
-
export { runAfterToolCallMiddleware, runAfterTurnMiddleware, runBeforeTurnMiddleware, runInputFilters, runOutputFilters, runToolCallInterceptors, type ToolInterceptResult, } from "./middleware-core.ts";
|
|
10
|
-
/** Generic interface for invoking agent lifecycle hooks, including middleware. */
|
|
11
|
-
export type HookInvoker = {
|
|
12
|
-
onConnect(sessionId: string, timeoutMs?: number): Promise<void>;
|
|
13
|
-
onDisconnect(sessionId: string, timeoutMs?: number): Promise<void>;
|
|
14
|
-
onTurn(sessionId: string, text: string, timeoutMs?: number): Promise<void>;
|
|
15
|
-
onError(sessionId: string, error: {
|
|
16
|
-
message: string;
|
|
17
|
-
}, timeoutMs?: number): Promise<void>;
|
|
18
|
-
onStep(sessionId: string, step: StepInfo, timeoutMs?: number): Promise<void>;
|
|
19
|
-
resolveTurnConfig(sid: string, ms?: number): Promise<{
|
|
20
|
-
maxSteps?: number;
|
|
21
|
-
} | null>;
|
|
22
|
-
filterInput?(sid: string, text: string, ms?: number): Promise<string>;
|
|
23
|
-
beforeTurn?(sid: string, text: string, ms?: number): Promise<string | undefined>;
|
|
24
|
-
afterTurn?(sid: string, text: string, ms?: number): Promise<void>;
|
|
25
|
-
interceptToolCall?(sid: string, tool: string, args: Readonly<Record<string, unknown>>, ms?: number): Promise<{
|
|
26
|
-
type: "block";
|
|
27
|
-
reason: string;
|
|
28
|
-
} | {
|
|
29
|
-
type: "result";
|
|
30
|
-
result: string;
|
|
31
|
-
} | {
|
|
32
|
-
type: "args";
|
|
33
|
-
args: Record<string, unknown>;
|
|
34
|
-
} | undefined>;
|
|
35
|
-
afterToolCall?(sid: string, tool: string, args: Readonly<Record<string, unknown>>, result: string, ms?: number): Promise<void>;
|
|
36
|
-
filterOutput?(sid: string, text: string, ms?: number): Promise<string>;
|
|
37
|
-
};
|
package/dist/runtime.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_STT_SAMPLE_RATE, DEFAULT_TTS_SAMPLE_RATE } from "./protocol.js";
|
|
2
|
-
import { context, trace } from "@opentelemetry/api";
|
|
3
|
-
//#region runtime.ts
|
|
4
|
-
/**
|
|
5
|
-
* Runtime dependencies injected into the session pipeline.
|
|
6
|
-
*
|
|
7
|
-
* Defines the {@link Logger} interface, a default {@link consoleLogger},
|
|
8
|
-
* and the {@link S2SConfig} for Speech-to-Speech endpoint configuration.
|
|
9
|
-
*/
|
|
10
|
-
/** Default console-backed logger. */
|
|
11
|
-
const consoleLogger = {
|
|
12
|
-
info: (msg, ctx) => ctx ? console.log(msg, ctx) : console.log(msg),
|
|
13
|
-
warn: (msg, ctx) => ctx ? console.warn(msg, ctx) : console.warn(msg),
|
|
14
|
-
error: (msg, ctx) => ctx ? console.error(msg, ctx) : console.error(msg),
|
|
15
|
-
debug: (msg, ctx) => ctx ? console.debug(msg, ctx) : console.debug(msg)
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Structured JSON logger for production diagnostics. Each log entry is a
|
|
19
|
-
* single-line JSON object with `timestamp`, `level`, `msg`, and any
|
|
20
|
-
* caller-provided context fields. When an active OpenTelemetry span exists,
|
|
21
|
-
* `trace_id` and `span_id` are included automatically.
|
|
22
|
-
*/
|
|
23
|
-
function jsonLog(level) {
|
|
24
|
-
return (msg, ctx) => {
|
|
25
|
-
const entry = {
|
|
26
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27
|
-
level,
|
|
28
|
-
msg
|
|
29
|
-
};
|
|
30
|
-
const span = trace.getSpan(context.active());
|
|
31
|
-
if (span) {
|
|
32
|
-
const sc = span.spanContext();
|
|
33
|
-
entry.trace_id = sc.traceId;
|
|
34
|
-
entry.span_id = sc.spanId;
|
|
35
|
-
}
|
|
36
|
-
if (ctx) Object.assign(entry, ctx);
|
|
37
|
-
(level === "error" || level === "warn" ? process.stderr : process.stdout).write(`${JSON.stringify(entry)}\n`);
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
const jsonLogger = {
|
|
41
|
-
info: jsonLog("info"),
|
|
42
|
-
warn: jsonLog("warn"),
|
|
43
|
-
error: jsonLog("error"),
|
|
44
|
-
debug: jsonLog("debug")
|
|
45
|
-
};
|
|
46
|
-
/** Default S2S endpoint configuration. */
|
|
47
|
-
const DEFAULT_S2S_CONFIG = {
|
|
48
|
-
wssUrl: "wss://speech-to-speech.us.assemblyai.com/v1/realtime",
|
|
49
|
-
inputSampleRate: DEFAULT_STT_SAMPLE_RATE,
|
|
50
|
-
outputSampleRate: DEFAULT_TTS_SAMPLE_RATE
|
|
51
|
-
};
|
|
52
|
-
//#endregion
|
|
53
|
-
export { DEFAULT_S2S_CONFIG, consoleLogger, jsonLogger };
|
package/dist/s2s.js
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import { consoleLogger } from "./runtime.js";
|
|
2
|
-
import { s2sConnectionDuration, s2sErrorCounter, tracer } from "./telemetry.js";
|
|
3
|
-
import { createNanoEvents } from "nanoevents";
|
|
4
|
-
import { WebSocket } from "ws";
|
|
5
|
-
//#region s2s.ts
|
|
6
|
-
const uint8ToBase64 = (bytes) => Buffer.from(bytes).toString("base64");
|
|
7
|
-
const base64ToUint8 = (base64) => new Uint8Array(Buffer.from(base64, "base64"));
|
|
8
|
-
const WS_OPEN = 1;
|
|
9
|
-
const defaultCreateS2sWebSocket = (url, opts) => new WebSocket(url, { headers: opts.headers });
|
|
10
|
-
function hasStringFields(obj, ...keys) {
|
|
11
|
-
for (const k of keys) if (typeof obj[k] !== "string") return false;
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
function parseAgentTranscript(obj) {
|
|
15
|
-
if (typeof obj.text !== "string") return;
|
|
16
|
-
return {
|
|
17
|
-
type: "transcript.agent",
|
|
18
|
-
text: obj.text,
|
|
19
|
-
reply_id: typeof obj.reply_id === "string" ? obj.reply_id : "",
|
|
20
|
-
item_id: typeof obj.item_id === "string" ? obj.item_id : "",
|
|
21
|
-
interrupted: obj.interrupted === true
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function parseToolCall(obj) {
|
|
25
|
-
if (typeof obj.call_id !== "string" || typeof obj.name !== "string") return;
|
|
26
|
-
const args = obj.args != null && typeof obj.args === "object" && !Array.isArray(obj.args) ? obj.args : {};
|
|
27
|
-
return {
|
|
28
|
-
type: "tool.call",
|
|
29
|
-
call_id: obj.call_id,
|
|
30
|
-
name: obj.name,
|
|
31
|
-
args
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function passthrough(obj) {
|
|
35
|
-
return obj;
|
|
36
|
-
}
|
|
37
|
-
function requireFields(...keys) {
|
|
38
|
-
return (obj) => hasStringFields(obj, ...keys) ? obj : void 0;
|
|
39
|
-
}
|
|
40
|
-
const MESSAGE_VALIDATORS = new Map([
|
|
41
|
-
["session.ready", requireFields("session_id")],
|
|
42
|
-
["session.updated", passthrough],
|
|
43
|
-
["input.speech.started", passthrough],
|
|
44
|
-
["input.speech.stopped", passthrough],
|
|
45
|
-
["reply.content_part.started", passthrough],
|
|
46
|
-
["reply.content_part.done", passthrough],
|
|
47
|
-
["transcript.user.delta", requireFields("text")],
|
|
48
|
-
["transcript.user", requireFields("item_id", "text")],
|
|
49
|
-
["reply.started", requireFields("reply_id")],
|
|
50
|
-
["transcript.agent.delta", requireFields("delta")],
|
|
51
|
-
["transcript.agent", parseAgentTranscript],
|
|
52
|
-
["tool.call", parseToolCall],
|
|
53
|
-
["reply.done", (obj) => ({
|
|
54
|
-
type: "reply.done",
|
|
55
|
-
...typeof obj.status === "string" ? { status: obj.status } : {}
|
|
56
|
-
})],
|
|
57
|
-
["session.error", requireFields("code", "message")],
|
|
58
|
-
["error", requireFields("message")]
|
|
59
|
-
]);
|
|
60
|
-
function parseS2sMessage(obj) {
|
|
61
|
-
const type = obj.type;
|
|
62
|
-
if (typeof type !== "string") return;
|
|
63
|
-
return MESSAGE_VALIDATORS.get(type)?.(obj);
|
|
64
|
-
}
|
|
65
|
-
function dispatchS2sMessage(emitter, msg) {
|
|
66
|
-
switch (msg.type) {
|
|
67
|
-
case "session.ready":
|
|
68
|
-
emitter.emit("ready", { sessionId: msg.session_id });
|
|
69
|
-
break;
|
|
70
|
-
case "session.updated":
|
|
71
|
-
emitter.emit("sessionUpdated", msg);
|
|
72
|
-
break;
|
|
73
|
-
case "input.speech.started":
|
|
74
|
-
emitter.emit("speechStarted");
|
|
75
|
-
break;
|
|
76
|
-
case "input.speech.stopped":
|
|
77
|
-
emitter.emit("speechStopped");
|
|
78
|
-
break;
|
|
79
|
-
case "transcript.user.delta":
|
|
80
|
-
emitter.emit("userTranscriptDelta", { text: msg.text });
|
|
81
|
-
break;
|
|
82
|
-
case "transcript.user":
|
|
83
|
-
emitter.emit("userTranscript", {
|
|
84
|
-
itemId: msg.item_id,
|
|
85
|
-
text: msg.text
|
|
86
|
-
});
|
|
87
|
-
break;
|
|
88
|
-
case "reply.started":
|
|
89
|
-
emitter.emit("replyStarted", { replyId: msg.reply_id });
|
|
90
|
-
break;
|
|
91
|
-
case "transcript.agent.delta":
|
|
92
|
-
emitter.emit("agentTranscriptDelta", { text: msg.delta });
|
|
93
|
-
break;
|
|
94
|
-
case "transcript.agent":
|
|
95
|
-
emitter.emit("agentTranscript", {
|
|
96
|
-
text: msg.text,
|
|
97
|
-
replyId: msg.reply_id,
|
|
98
|
-
itemId: msg.item_id,
|
|
99
|
-
interrupted: msg.interrupted
|
|
100
|
-
});
|
|
101
|
-
break;
|
|
102
|
-
case "tool.call":
|
|
103
|
-
emitter.emit("toolCall", {
|
|
104
|
-
callId: msg.call_id,
|
|
105
|
-
name: msg.name,
|
|
106
|
-
args: msg.args
|
|
107
|
-
});
|
|
108
|
-
break;
|
|
109
|
-
case "reply.done":
|
|
110
|
-
emitter.emit("replyDone", msg.status ? { status: msg.status } : {});
|
|
111
|
-
break;
|
|
112
|
-
case "session.error":
|
|
113
|
-
if (msg.code === "session_not_found" || msg.code === "session_forbidden") emitter.emit("sessionExpired", {
|
|
114
|
-
code: msg.code,
|
|
115
|
-
message: msg.message
|
|
116
|
-
});
|
|
117
|
-
else emitter.emit("error", {
|
|
118
|
-
code: msg.code,
|
|
119
|
-
message: msg.message
|
|
120
|
-
});
|
|
121
|
-
break;
|
|
122
|
-
case "error":
|
|
123
|
-
emitter.emit("error", {
|
|
124
|
-
code: "connection",
|
|
125
|
-
message: msg.message
|
|
126
|
-
});
|
|
127
|
-
break;
|
|
128
|
-
case "reply.content_part.started":
|
|
129
|
-
case "reply.content_part.done": break;
|
|
130
|
-
default: break;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function connectS2s(opts) {
|
|
134
|
-
const { apiKey, config, createWebSocket, logger: log = consoleLogger } = opts;
|
|
135
|
-
return new Promise((resolve, reject) => {
|
|
136
|
-
log.info("S2S connecting", { url: config.wssUrl });
|
|
137
|
-
const connectionSpan = tracer.startSpan("s2s.connection", { attributes: { "aai.s2s.url": config.wssUrl } });
|
|
138
|
-
const connectStart = performance.now();
|
|
139
|
-
const ws = createWebSocket(config.wssUrl, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
140
|
-
const emitter = createNanoEvents();
|
|
141
|
-
let opened = false;
|
|
142
|
-
function send(msg) {
|
|
143
|
-
if (ws.readyState !== WS_OPEN) {
|
|
144
|
-
log.debug("S2S send dropped: socket not open", { type: msg.type });
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const json = JSON.stringify(msg);
|
|
148
|
-
if (msg.type !== "input.audio") log.info(`S2S >> ${msg.type}`, msg.type === "session.update" ? { payload: json } : void 0);
|
|
149
|
-
ws.send(json);
|
|
150
|
-
}
|
|
151
|
-
const handle = {
|
|
152
|
-
on: emitter.on.bind(emitter),
|
|
153
|
-
sendAudio(audio) {
|
|
154
|
-
if (ws.readyState !== WS_OPEN) {
|
|
155
|
-
log.debug("S2S sendAudio dropped: socket not open");
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
ws.send(`{"type":"input.audio","audio":"${uint8ToBase64(audio)}"}`);
|
|
159
|
-
},
|
|
160
|
-
sendToolResult(callId, result) {
|
|
161
|
-
const msg = {
|
|
162
|
-
type: "tool.result",
|
|
163
|
-
call_id: callId,
|
|
164
|
-
result
|
|
165
|
-
};
|
|
166
|
-
log.info("S2S >> tool.result", {
|
|
167
|
-
call_id: callId,
|
|
168
|
-
resultLength: result.length
|
|
169
|
-
});
|
|
170
|
-
send(msg);
|
|
171
|
-
},
|
|
172
|
-
updateSession(sessionConfig) {
|
|
173
|
-
const { systemPrompt, ...rest } = sessionConfig;
|
|
174
|
-
send({
|
|
175
|
-
type: "session.update",
|
|
176
|
-
session: {
|
|
177
|
-
system_prompt: systemPrompt,
|
|
178
|
-
...rest
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
},
|
|
182
|
-
resumeSession(sessionId) {
|
|
183
|
-
send({
|
|
184
|
-
type: "session.resume",
|
|
185
|
-
session_id: sessionId
|
|
186
|
-
});
|
|
187
|
-
},
|
|
188
|
-
close() {
|
|
189
|
-
log.info("S2S closing");
|
|
190
|
-
ws.close();
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
ws.addEventListener("open", () => {
|
|
194
|
-
opened = true;
|
|
195
|
-
log.info("S2S WebSocket open");
|
|
196
|
-
connectionSpan.addEvent("ws.open");
|
|
197
|
-
resolve(handle);
|
|
198
|
-
});
|
|
199
|
-
function tryParseJson(data) {
|
|
200
|
-
try {
|
|
201
|
-
return JSON.parse(String(data));
|
|
202
|
-
} catch {
|
|
203
|
-
log.warn("S2S << invalid JSON", { data: String(data).slice(0, 200) });
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
function handleAudioFastPath(obj) {
|
|
207
|
-
if (obj.type === "reply.audio" && typeof obj.data === "string") {
|
|
208
|
-
const audioBytes = base64ToUint8(obj.data);
|
|
209
|
-
emitter.emit("audio", { audio: audioBytes });
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
function logIncoming(obj) {
|
|
215
|
-
if (obj.type === "reply.audio" || obj.type === "input.audio") return;
|
|
216
|
-
log.info(`S2S << ${obj.type}`, obj.type === "transcript.agent.delta" ? { delta: obj.delta } : void 0);
|
|
217
|
-
}
|
|
218
|
-
function handleS2sMessage(ev) {
|
|
219
|
-
const raw = tryParseJson(ev.data);
|
|
220
|
-
if (raw === void 0) return;
|
|
221
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
222
|
-
log.warn("S2S << non-object JSON message", { type: typeof raw });
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
const obj = raw;
|
|
226
|
-
logIncoming(obj);
|
|
227
|
-
if (handleAudioFastPath(obj)) return;
|
|
228
|
-
const parsed = parseS2sMessage(obj);
|
|
229
|
-
if (!parsed) {
|
|
230
|
-
log.warn(`S2S << unrecognised message type: ${obj.type ?? JSON.stringify(raw).slice(0, 200)}`);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
dispatchS2sMessage(emitter, parsed);
|
|
234
|
-
}
|
|
235
|
-
ws.addEventListener("message", handleS2sMessage);
|
|
236
|
-
ws.addEventListener("close", (ev) => {
|
|
237
|
-
log.info("S2S WebSocket closed", {
|
|
238
|
-
code: ev.code ?? 0,
|
|
239
|
-
reason: ev.reason ?? ""
|
|
240
|
-
});
|
|
241
|
-
const elapsed = (performance.now() - connectStart) / 1e3;
|
|
242
|
-
s2sConnectionDuration.record(elapsed);
|
|
243
|
-
connectionSpan.addEvent("ws.closed", {
|
|
244
|
-
"ws.close.code": ev.code ?? 0,
|
|
245
|
-
"ws.close.reason": ev.reason ?? ""
|
|
246
|
-
});
|
|
247
|
-
connectionSpan.end();
|
|
248
|
-
if (!opened) reject(/* @__PURE__ */ new Error(`WebSocket closed before open (code: ${ev.code ?? 0})`));
|
|
249
|
-
emitter.emit("close");
|
|
250
|
-
});
|
|
251
|
-
ws.addEventListener("error", (ev) => {
|
|
252
|
-
const message = typeof ev.message === "string" ? ev.message : "WebSocket error";
|
|
253
|
-
const errObj = new Error(message);
|
|
254
|
-
log.error("S2S WebSocket error", { error: errObj.message });
|
|
255
|
-
s2sErrorCounter.add(1);
|
|
256
|
-
connectionSpan.setStatus({
|
|
257
|
-
code: 2,
|
|
258
|
-
message: errObj.message
|
|
259
|
-
});
|
|
260
|
-
connectionSpan.recordException(errObj);
|
|
261
|
-
if (!opened) {
|
|
262
|
-
connectionSpan.end();
|
|
263
|
-
reject(errObj);
|
|
264
|
-
} else emitter.emit("error", {
|
|
265
|
-
code: "ws_error",
|
|
266
|
-
message: errObj.message
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
//#endregion
|
|
272
|
-
export { connectS2s, defaultCreateS2sWebSocket };
|