@alexkroman1/aai 1.8.1 → 1.8.2
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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +6 -0
- package/dist/{_internal-types-CfOAbK6V.js → _internal-types-8v1qAa4A.js} +14 -2
- package/dist/host/runtime-barrel.js +42 -17
- package/dist/host/transports/types.d.ts +7 -0
- package/dist/sdk/_internal-types.d.ts +9 -0
- package/dist/sdk/manifest-barrel.js +1 -1
- package/host/builtin-tools.ts +2 -2
- package/host/runtime.ts +9 -0
- package/host/s2s.ts +41 -22
- package/host/transports/pipeline-transport.ts +23 -0
- package/host/transports/types.ts +7 -0
- package/package.json +1 -1
- package/sdk/_internal-types.ts +14 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @alexkroman1/aai@1.8.
|
|
2
|
+
> @alexkroman1/aai@1.8.2 build /home/runner/work/agent/agent/packages/aai
|
|
3
3
|
> tsdown && tsc -p tsconfig.build.json
|
|
4
4
|
|
|
5
5
|
[34mℹ[39m [34mtsdown v0.21.7[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.12[39m
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[34mℹ[39m target: [34mnode22[39m
|
|
9
9
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
10
10
|
[34mℹ[39m Build start
|
|
11
|
-
[34mℹ[39m [2mdist/[22m[1mhost/runtime-barrel.js[22m [
|
|
11
|
+
[34mℹ[39m [2mdist/[22m[1mhost/runtime-barrel.js[22m [2m110.46 kB[22m [2m│ gzip: 30.38 kB[22m
|
|
12
12
|
[34mℹ[39m [2mdist/[22m[1msdk/protocol.js[22m [2m 5.70 kB[22m [2m│ gzip: 1.92 kB[22m
|
|
13
13
|
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m 2.88 kB[22m [2m│ gzip: 1.24 kB[22m
|
|
14
14
|
[34mℹ[39m [2mdist/[22m[1msdk/manifest-barrel.js[22m [2m 0.36 kB[22m [2m│ gzip: 0.20 kB[22m
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
[34mℹ[39m [2mdist/[22m[1msdk/providers/tts-barrel.js[22m [2m 0.25 kB[22m [2m│ gzip: 0.16 kB[22m
|
|
19
19
|
[34mℹ[39m [2mdist/[22m[1msdk/providers/vector-barrel.js[22m [2m 0.22 kB[22m [2m│ gzip: 0.15 kB[22m
|
|
20
20
|
[34mℹ[39m [2mdist/[22m[1msdk/providers/s2s-barrel.js[22m [2m 0.15 kB[22m [2m│ gzip: 0.12 kB[22m
|
|
21
|
-
[34mℹ[39m [2mdist/[22m_internal-types-
|
|
21
|
+
[34mℹ[39m [2mdist/[22m_internal-types-8v1qAa4A.js [2m 6.04 kB[22m [2m│ gzip: 2.15 kB[22m
|
|
22
22
|
[34mℹ[39m [2mdist/[22mtypes-DOWVZhb9.js [2m 5.39 kB[22m [2m│ gzip: 2.27 kB[22m
|
|
23
23
|
[34mℹ[39m [2mdist/[22msoniox-BQdL0mB5.js [2m 2.03 kB[22m [2m│ gzip: 0.54 kB[22m
|
|
24
24
|
[34mℹ[39m [2mdist/[22mconstants-y68COEGj.js [2m 1.70 kB[22m [2m│ gzip: 0.76 kB[22m
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
[34mℹ[39m [2mdist/[22ms3-BtCMvCod.js [2m 0.76 kB[22m [2m│ gzip: 0.29 kB[22m
|
|
29
29
|
[34mℹ[39m [2mdist/[22mpinecone-CeJ69aRs.js [2m 0.48 kB[22m [2m│ gzip: 0.24 kB[22m
|
|
30
30
|
[34mℹ[39m [2mdist/[22mopenai-realtime-cjPAHMMx.js [2m 0.27 kB[22m [2m│ gzip: 0.19 kB[22m
|
|
31
|
-
[34mℹ[39m 20 files, total:
|
|
32
|
-
[32m✔[39m Build complete in [
|
|
31
|
+
[34mℹ[39m 20 files, total: 140.98 kB
|
|
32
|
+
[32m✔[39m Build complete in [32m42ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @alexkroman1/aai
|
|
2
2
|
|
|
3
|
+
## 1.8.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- bb06b4e: Fix S2S tool calls arriving with empty args. Strip the $schema keyword from Zod-generated JSON Schema for tool parameters — some S2S providers ship the dialect URI to the underlying model and emit tool calls with empty args even when required params are listed. Also accept both 'arguments' and 'args' field names on the wire. Pipeline transport now surfaces tool-result stream parts as tool_call_done so the client UI flips pending → done.
|
|
8
|
+
|
|
3
9
|
## 1.8.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -128,13 +128,25 @@ const ToolSchemaSchema = z.object({
|
|
|
128
128
|
parameters: z.record(z.string(), z.unknown())
|
|
129
129
|
});
|
|
130
130
|
const EMPTY_PARAMS = z.object({});
|
|
131
|
+
/**
|
|
132
|
+
* Convert a Zod schema to the JSON Schema shape that S2S providers expect.
|
|
133
|
+
* Strips the `$schema` keyword: `z.toJSONSchema` (Zod v4) tags output with
|
|
134
|
+
* the JSON Schema 2020-12 dialect URI, and some Realtime/S2S providers
|
|
135
|
+
* either reject the field outright or ship it through to the underlying
|
|
136
|
+
* model with a malformed function spec — observed empirically as tool
|
|
137
|
+
* calls that arrive with `args: {}` even when required params are listed.
|
|
138
|
+
*/
|
|
139
|
+
function toToolJsonSchema(zodSchema) {
|
|
140
|
+
const { $schema: _omit, ...rest } = z.toJSONSchema(zodSchema);
|
|
141
|
+
return rest;
|
|
142
|
+
}
|
|
131
143
|
function agentToolsToSchemas(tools) {
|
|
132
144
|
return Object.entries(tools).map(([name, def]) => ({
|
|
133
145
|
type: "function",
|
|
134
146
|
name,
|
|
135
147
|
description: def.description,
|
|
136
|
-
parameters:
|
|
148
|
+
parameters: toToolJsonSchema(def.parameters ?? EMPTY_PARAMS)
|
|
137
149
|
}));
|
|
138
150
|
}
|
|
139
151
|
//#endregion
|
|
140
|
-
export { toAgentConfig as a,
|
|
152
|
+
export { toAgentConfig as a, assertProviderTriple as c, agentToolsToSchemas as i, makeSttError as l, EMPTY_PARAMS as n, toToolJsonSchema as o, ToolSchemaSchema as r, ProviderDescriptorSchema as s, AgentConfigSchema as t, makeTtsError as u };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { r as DEFAULT_SYSTEM_PROMPT } from "../types-DOWVZhb9.js";
|
|
2
|
-
import { _ as TOOL_EXECUTION_TIMEOUT_MS, a as DEFAULT_SHUTDOWN_TIMEOUT_MS, c as FETCH_TIMEOUT_MS, d as MAX_PAGE_CHARS, g as RUN_CODE_TIMEOUT_MS, h as PIPELINE_FLUSH_TIMEOUT_MS, l as MAX_HTML_BYTES, m as MAX_WS_PAYLOAD_BYTES, o as DEFAULT_STT_SAMPLE_RATE, p as MAX_VALUE_SIZE, s as DEFAULT_TTS_SAMPLE_RATE, t as AGENT_CSP } from "../constants-y68COEGj.js";
|
|
2
|
+
import { _ as TOOL_EXECUTION_TIMEOUT_MS, a as DEFAULT_SHUTDOWN_TIMEOUT_MS, c as FETCH_TIMEOUT_MS, d as MAX_PAGE_CHARS, f as MAX_TOOL_RESULT_CHARS, g as RUN_CODE_TIMEOUT_MS, h as PIPELINE_FLUSH_TIMEOUT_MS, l as MAX_HTML_BYTES, m as MAX_WS_PAYLOAD_BYTES, o as DEFAULT_STT_SAMPLE_RATE, p as MAX_VALUE_SIZE, s as DEFAULT_TTS_SAMPLE_RATE, t as AGENT_CSP } from "../constants-y68COEGj.js";
|
|
3
3
|
import { i as toolError, n as errorDetail, r as errorMessage, t as parseWsUpgradeParams } from "../ws-upgrade-CG8-by1n.js";
|
|
4
4
|
import { ClientMessageSchema, VectorRequestSchema, buildReadyConfig, lenientParse } from "../sdk/protocol.js";
|
|
5
|
-
import { a as toAgentConfig, c as
|
|
5
|
+
import { a as toAgentConfig, c as assertProviderTriple, i as agentToolsToSchemas, l as makeSttError, n as EMPTY_PARAMS, o as toToolJsonSchema, u as makeTtsError } from "../_internal-types-8v1qAa4A.js";
|
|
6
6
|
import { a as MISTRAL_KIND, d as ANTHROPIC_KIND, l as GOOGLE_KIND, r as OPENAI_KIND, s as GROQ_KIND } from "../xai-BDI61Y2M.js";
|
|
7
7
|
import { a as DEEPGRAM_KIND, r as ELEVENLABS_KIND, s as ASSEMBLYAI_KIND, t as SONIOX_KIND } from "../soniox-BQdL0mB5.js";
|
|
8
8
|
import { a as CARTESIA_KIND, n as RIME_KIND } from "../rime-58p9mDR8.js";
|
|
@@ -311,7 +311,7 @@ function resolveAllBuiltins(names, opts) {
|
|
|
311
311
|
type: "function",
|
|
312
312
|
name: toolName,
|
|
313
313
|
description: def.description,
|
|
314
|
-
parameters:
|
|
314
|
+
parameters: toToolJsonSchema(def.parameters ?? EMPTY_PARAMS)
|
|
315
315
|
});
|
|
316
316
|
if (def.guidance) guidance.push(def.guidance);
|
|
317
317
|
}
|
|
@@ -2141,6 +2141,14 @@ function createPipelineTransport(opts) {
|
|
|
2141
2141
|
onDelta(out);
|
|
2142
2142
|
ttsSession?.sendText(out);
|
|
2143
2143
|
}
|
|
2144
|
+
function emitToolResult(part) {
|
|
2145
|
+
const callId = part.toolCallId ?? "";
|
|
2146
|
+
if (!callId) return;
|
|
2147
|
+
const raw = part.output ?? part.result ?? "";
|
|
2148
|
+
const str = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
2149
|
+
const truncated = str.length > 4e3 ? str.slice(0, MAX_TOOL_RESULT_CHARS) : str;
|
|
2150
|
+
callbacks.onToolCallDone?.(callId, truncated);
|
|
2151
|
+
}
|
|
2144
2152
|
return function handlePart(part) {
|
|
2145
2153
|
switch (part.type) {
|
|
2146
2154
|
case "text-delta":
|
|
@@ -2154,6 +2162,9 @@ function createPipelineTransport(opts) {
|
|
|
2154
2162
|
callbacks.onToolCall(part.toolCallId ?? "", part.toolName ?? "", input);
|
|
2155
2163
|
return;
|
|
2156
2164
|
}
|
|
2165
|
+
case "tool-result":
|
|
2166
|
+
emitToolResult(part);
|
|
2167
|
+
return;
|
|
2157
2168
|
case "error": {
|
|
2158
2169
|
const msg = errorMessage(part.error);
|
|
2159
2170
|
log.error("LLM stream error", {
|
|
@@ -2396,8 +2407,14 @@ const S2sMessageSchema = z.discriminatedUnion("type", [
|
|
|
2396
2407
|
type: z.literal("tool.call"),
|
|
2397
2408
|
call_id: z.string(),
|
|
2398
2409
|
name: z.string(),
|
|
2399
|
-
|
|
2400
|
-
|
|
2410
|
+
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
2411
|
+
args: z.record(z.string(), z.unknown()).optional()
|
|
2412
|
+
}).transform((m) => ({
|
|
2413
|
+
type: m.type,
|
|
2414
|
+
call_id: m.call_id,
|
|
2415
|
+
name: m.name,
|
|
2416
|
+
args: m.arguments ?? m.args ?? {}
|
|
2417
|
+
})),
|
|
2401
2418
|
z.object({
|
|
2402
2419
|
type: z.literal("reply.done"),
|
|
2403
2420
|
status: z.string().optional()
|
|
@@ -2545,6 +2562,20 @@ function connectS2s(opts) {
|
|
|
2545
2562
|
if (type === "reply.audio" || type === "input.audio" || type === "reply.done" || type === "session.error") return;
|
|
2546
2563
|
log.info(`S2S << ${type}`);
|
|
2547
2564
|
}
|
|
2565
|
+
function handleObject(obj, raw) {
|
|
2566
|
+
logIncoming(obj.type);
|
|
2567
|
+
if (obj.type === "tool.call") log.info("S2S << tool.call payload", { payload: JSON.stringify(obj) });
|
|
2568
|
+
if (obj.type === "reply.audio" && typeof obj.data === "string") {
|
|
2569
|
+
callbacks.onAudio(base64ToUint8(obj.data));
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
const parsed = parseS2sMessage(obj);
|
|
2573
|
+
if (!parsed) {
|
|
2574
|
+
log.warn(`S2S << unrecognised message type: ${obj.type ?? JSON.stringify(raw).slice(0, 200)}`);
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
dispatchS2sMessage(callbacks, parsed, dispatchState, dispatchCtx);
|
|
2578
|
+
}
|
|
2548
2579
|
ws.addEventListener("message", (ev) => {
|
|
2549
2580
|
let raw;
|
|
2550
2581
|
try {
|
|
@@ -2557,18 +2588,7 @@ function connectS2s(opts) {
|
|
|
2557
2588
|
log.warn("S2S << non-object JSON message", { type: typeof raw });
|
|
2558
2589
|
return;
|
|
2559
2590
|
}
|
|
2560
|
-
|
|
2561
|
-
logIncoming(obj.type);
|
|
2562
|
-
if (obj.type === "reply.audio" && typeof obj.data === "string") {
|
|
2563
|
-
callbacks.onAudio(base64ToUint8(obj.data));
|
|
2564
|
-
return;
|
|
2565
|
-
}
|
|
2566
|
-
const parsed = parseS2sMessage(obj);
|
|
2567
|
-
if (!parsed) {
|
|
2568
|
-
log.warn(`S2S << unrecognised message type: ${obj.type ?? JSON.stringify(raw).slice(0, 200)}`);
|
|
2569
|
-
return;
|
|
2570
|
-
}
|
|
2571
|
-
dispatchS2sMessage(callbacks, parsed, dispatchState, dispatchCtx);
|
|
2591
|
+
handleObject(raw, raw);
|
|
2572
2592
|
});
|
|
2573
2593
|
ws.addEventListener("close", (ev) => {
|
|
2574
2594
|
const code = ev.code ?? 0;
|
|
@@ -3200,6 +3220,11 @@ function createRuntime(opts) {
|
|
|
3200
3220
|
toolName: name,
|
|
3201
3221
|
args
|
|
3202
3222
|
}) : (id, name, args) => bindCore().onToolCall(id, name, args),
|
|
3223
|
+
...isPipeline ? { onToolCallDone: (id, result) => sessionOpts.client.event({
|
|
3224
|
+
type: "tool_call_done",
|
|
3225
|
+
toolCallId: id,
|
|
3226
|
+
result
|
|
3227
|
+
}) } : {},
|
|
3203
3228
|
onError: (code, message) => bindCore().onError(code, message),
|
|
3204
3229
|
onSpeechStarted: () => bindCore().onSpeechStarted(),
|
|
3205
3230
|
onSpeechStopped: () => bindCore().onSpeechStopped()
|
|
@@ -13,6 +13,13 @@ export type TransportCallbacks = {
|
|
|
13
13
|
onUserTranscript(text: string): void;
|
|
14
14
|
onAgentTranscript(text: string, interrupted: boolean): void;
|
|
15
15
|
onToolCall(callId: string, name: string, args: Record<string, unknown>): void;
|
|
16
|
+
/**
|
|
17
|
+
* Tool execution finished. Pipeline mode invokes this from the
|
|
18
|
+
* `tool-result` stream part so the client UI can mark the call done.
|
|
19
|
+
* S2S transports leave this unset — SessionCore.onToolCall emits the
|
|
20
|
+
* `tool_call_done` event itself after dispatching the tool.
|
|
21
|
+
*/
|
|
22
|
+
onToolCallDone?(callId: string, result: string): void;
|
|
16
23
|
onError(code: SessionErrorCode, message: string): void;
|
|
17
24
|
onSpeechStarted(): void;
|
|
18
25
|
onSpeechStopped(): void;
|
|
@@ -85,4 +85,13 @@ export type ToolSchema = {
|
|
|
85
85
|
parameters: JSONSchema7;
|
|
86
86
|
};
|
|
87
87
|
export declare const EMPTY_PARAMS: z.ZodObject<{}, z.core.$strip>;
|
|
88
|
+
/**
|
|
89
|
+
* Convert a Zod schema to the JSON Schema shape that S2S providers expect.
|
|
90
|
+
* Strips the `$schema` keyword: `z.toJSONSchema` (Zod v4) tags output with
|
|
91
|
+
* the JSON Schema 2020-12 dialect URI, and some Realtime/S2S providers
|
|
92
|
+
* either reject the field outright or ship it through to the underlying
|
|
93
|
+
* model with a malformed function spec — observed empirically as tool
|
|
94
|
+
* calls that arrive with `args: {}` even when required params are listed.
|
|
95
|
+
*/
|
|
96
|
+
export declare function toToolJsonSchema(zodSchema: z.ZodTypeAny): JSONSchema7;
|
|
88
97
|
export declare function agentToolsToSchemas(tools: Readonly<Record<string, ToolDef>>): ToolSchema[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as toAgentConfig,
|
|
1
|
+
import { a as toAgentConfig, c as assertProviderTriple, i as agentToolsToSchemas, n as EMPTY_PARAMS, r as ToolSchemaSchema, s as ProviderDescriptorSchema, t as AgentConfigSchema } from "../_internal-types-8v1qAa4A.js";
|
|
2
2
|
export { AgentConfigSchema, EMPTY_PARAMS, ProviderDescriptorSchema, ToolSchemaSchema, agentToolsToSchemas, assertProviderTriple, toAgentConfig };
|
package/host/builtin-tools.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { convert } from "html-to-text";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
import { EMPTY_PARAMS, type ToolSchema } from "../sdk/_internal-types.ts";
|
|
12
|
+
import { EMPTY_PARAMS, type ToolSchema, toToolJsonSchema } from "../sdk/_internal-types.ts";
|
|
13
13
|
import { FETCH_TIMEOUT_MS, MAX_HTML_BYTES, MAX_PAGE_CHARS } from "../sdk/constants.ts";
|
|
14
14
|
import type { ToolDef } from "../sdk/types.ts";
|
|
15
15
|
import { createRunCode } from "./_run-code.ts";
|
|
@@ -242,7 +242,7 @@ export function resolveAllBuiltins(
|
|
|
242
242
|
type: "function",
|
|
243
243
|
name: toolName,
|
|
244
244
|
description: def.description,
|
|
245
|
-
parameters:
|
|
245
|
+
parameters: toToolJsonSchema(def.parameters ?? EMPTY_PARAMS) as ToolSchema["parameters"],
|
|
246
246
|
});
|
|
247
247
|
if (def.guidance) guidance.push(def.guidance);
|
|
248
248
|
}
|
package/host/runtime.ts
CHANGED
|
@@ -513,6 +513,15 @@ export function createRuntime(opts: RuntimeOptions): Runtime {
|
|
|
513
513
|
? (id, name, args) =>
|
|
514
514
|
sessionOpts.client.event({ type: "tool_call", toolCallId: id, toolName: name, args })
|
|
515
515
|
: (id, name, args) => bindCore().onToolCall(id, name, args),
|
|
516
|
+
// Pipeline: emit `tool_call_done` when streamText surfaces the
|
|
517
|
+
// `tool-result` part so the UI can flip status from pending → done.
|
|
518
|
+
// S2S transports never set this; SessionCore.onToolCall emits done itself.
|
|
519
|
+
...(isPipeline
|
|
520
|
+
? {
|
|
521
|
+
onToolCallDone: (id: string, result: string) =>
|
|
522
|
+
sessionOpts.client.event({ type: "tool_call_done", toolCallId: id, result }),
|
|
523
|
+
}
|
|
524
|
+
: {}),
|
|
516
525
|
onError: (code, message) => bindCore().onError(code, message),
|
|
517
526
|
onSpeechStarted: () => bindCore().onSpeechStarted(),
|
|
518
527
|
onSpeechStopped: () => bindCore().onSpeechStopped(),
|
package/host/s2s.ts
CHANGED
|
@@ -55,12 +55,23 @@ const S2sMessageSchema = z.discriminatedUnion("type", [
|
|
|
55
55
|
item_id: z.string().optional().default(""),
|
|
56
56
|
interrupted: z.boolean().optional().default(false),
|
|
57
57
|
}),
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
// AssemblyAI's S2S protocol delivers tool args under `arguments`; older
|
|
59
|
+
// implementations and our internal tests use `args`. Accept either, with
|
|
60
|
+
// `arguments` taking precedence so the live wire format wins.
|
|
61
|
+
z
|
|
62
|
+
.object({
|
|
63
|
+
type: z.literal("tool.call"),
|
|
64
|
+
call_id: z.string(),
|
|
65
|
+
name: z.string(),
|
|
66
|
+
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
67
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
68
|
+
})
|
|
69
|
+
.transform((m) => ({
|
|
70
|
+
type: m.type,
|
|
71
|
+
call_id: m.call_id,
|
|
72
|
+
name: m.name,
|
|
73
|
+
args: m.arguments ?? m.args ?? {},
|
|
74
|
+
})),
|
|
64
75
|
z.object({ type: z.literal("reply.done"), status: z.string().optional() }),
|
|
65
76
|
z.object({ type: z.literal("session.error"), code: z.string(), message: z.string() }),
|
|
66
77
|
z.object({ type: z.literal("error"), message: z.string() }),
|
|
@@ -303,27 +314,20 @@ export function connectS2s(opts: ConnectS2sOptions): Promise<S2sHandle> {
|
|
|
303
314
|
log.info(`S2S << ${type}`);
|
|
304
315
|
}
|
|
305
316
|
|
|
306
|
-
|
|
307
|
-
let raw: unknown;
|
|
308
|
-
try {
|
|
309
|
-
raw = JSON.parse(String(ev.data));
|
|
310
|
-
} catch {
|
|
311
|
-
log.warn("S2S << invalid JSON", { data: String(ev.data).slice(0, 200) });
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
316
|
-
log.warn("S2S << non-object JSON message", { type: typeof raw });
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const obj = raw as Record<string, unknown>;
|
|
317
|
+
function handleObject(obj: Record<string, unknown>, raw: unknown): void {
|
|
320
318
|
logIncoming(obj.type);
|
|
321
|
-
|
|
319
|
+
// Log the full tool.call payload so we can diagnose provider-side
|
|
320
|
+
// empty-args problems — the underlying LLM emitting a function call
|
|
321
|
+
// without populating its required parameters. Without the full
|
|
322
|
+
// payload we cannot tell apart "field-name mismatch" from
|
|
323
|
+
// "model emitted no args."
|
|
324
|
+
if (obj.type === "tool.call") {
|
|
325
|
+
log.info("S2S << tool.call payload", { payload: JSON.stringify(obj) });
|
|
326
|
+
}
|
|
322
327
|
if (obj.type === "reply.audio" && typeof obj.data === "string") {
|
|
323
328
|
callbacks.onAudio(base64ToUint8(obj.data));
|
|
324
329
|
return;
|
|
325
330
|
}
|
|
326
|
-
|
|
327
331
|
const parsed = parseS2sMessage(obj);
|
|
328
332
|
if (!parsed) {
|
|
329
333
|
log.warn(
|
|
@@ -332,6 +336,21 @@ export function connectS2s(opts: ConnectS2sOptions): Promise<S2sHandle> {
|
|
|
332
336
|
return;
|
|
333
337
|
}
|
|
334
338
|
dispatchS2sMessage(callbacks, parsed, dispatchState, dispatchCtx);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
ws.addEventListener("message", (ev) => {
|
|
342
|
+
let raw: unknown;
|
|
343
|
+
try {
|
|
344
|
+
raw = JSON.parse(String(ev.data));
|
|
345
|
+
} catch {
|
|
346
|
+
log.warn("S2S << invalid JSON", { data: String(ev.data).slice(0, 200) });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
350
|
+
log.warn("S2S << non-object JSON message", { type: typeof raw });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
handleObject(raw as Record<string, unknown>, raw);
|
|
335
354
|
});
|
|
336
355
|
|
|
337
356
|
ws.addEventListener("close", (ev) => {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
DEFAULT_MAX_HISTORY,
|
|
15
15
|
DEFAULT_STT_SAMPLE_RATE,
|
|
16
16
|
DEFAULT_TTS_SAMPLE_RATE,
|
|
17
|
+
MAX_TOOL_RESULT_CHARS,
|
|
17
18
|
PIPELINE_FLUSH_TIMEOUT_MS,
|
|
18
19
|
} from "../../sdk/constants.ts";
|
|
19
20
|
import type { SessionErrorCode } from "../../sdk/protocol.ts";
|
|
@@ -235,6 +236,25 @@ export function createPipelineTransport(opts: PipelineTransportOptions): Transpo
|
|
|
235
236
|
ttsSession?.sendText(out);
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
function emitToolResult(part: {
|
|
240
|
+
readonly toolCallId?: string;
|
|
241
|
+
readonly output?: unknown;
|
|
242
|
+
}): void {
|
|
243
|
+
// Inline execution finished — surface completion so the client UI can
|
|
244
|
+
// flip the tool-call from "pending" to "done". Schema requires a
|
|
245
|
+
// string result capped at MAX_TOOL_RESULT_CHARS.
|
|
246
|
+
const callId = part.toolCallId ?? "";
|
|
247
|
+
if (!callId) return;
|
|
248
|
+
const raw =
|
|
249
|
+
(part as { output?: unknown; result?: unknown }).output ??
|
|
250
|
+
(part as { result?: unknown }).result ??
|
|
251
|
+
"";
|
|
252
|
+
const str = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
253
|
+
const truncated =
|
|
254
|
+
str.length > MAX_TOOL_RESULT_CHARS ? str.slice(0, MAX_TOOL_RESULT_CHARS) : str;
|
|
255
|
+
callbacks.onToolCallDone?.(callId, truncated);
|
|
256
|
+
}
|
|
257
|
+
|
|
238
258
|
return function handlePart(part: {
|
|
239
259
|
readonly type: string;
|
|
240
260
|
readonly text?: string;
|
|
@@ -257,6 +277,9 @@ export function createPipelineTransport(opts: PipelineTransportOptions): Transpo
|
|
|
257
277
|
callbacks.onToolCall(part.toolCallId ?? "", part.toolName ?? "", input);
|
|
258
278
|
return;
|
|
259
279
|
}
|
|
280
|
+
case "tool-result":
|
|
281
|
+
emitToolResult(part);
|
|
282
|
+
return;
|
|
260
283
|
case "error": {
|
|
261
284
|
const msg = errorMessage(part.error);
|
|
262
285
|
log.error("LLM stream error", { message: msg, sid: opts.sid });
|
package/host/transports/types.ts
CHANGED
|
@@ -17,6 +17,13 @@ export type TransportCallbacks = {
|
|
|
17
17
|
onUserTranscript(text: string): void;
|
|
18
18
|
onAgentTranscript(text: string, interrupted: boolean): void;
|
|
19
19
|
onToolCall(callId: string, name: string, args: Record<string, unknown>): void;
|
|
20
|
+
/**
|
|
21
|
+
* Tool execution finished. Pipeline mode invokes this from the
|
|
22
|
+
* `tool-result` stream part so the client UI can mark the call done.
|
|
23
|
+
* S2S transports leave this unset — SessionCore.onToolCall emits the
|
|
24
|
+
* `tool_call_done` event itself after dispatching the tool.
|
|
25
|
+
*/
|
|
26
|
+
onToolCallDone?(callId: string, result: string): void;
|
|
20
27
|
onError(code: SessionErrorCode, message: string): void;
|
|
21
28
|
onSpeechStarted(): void;
|
|
22
29
|
onSpeechStopped(): void;
|
package/package.json
CHANGED
package/sdk/_internal-types.ts
CHANGED
|
@@ -118,11 +118,24 @@ export type ToolSchema = {
|
|
|
118
118
|
|
|
119
119
|
export const EMPTY_PARAMS = z.object({});
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Convert a Zod schema to the JSON Schema shape that S2S providers expect.
|
|
123
|
+
* Strips the `$schema` keyword: `z.toJSONSchema` (Zod v4) tags output with
|
|
124
|
+
* the JSON Schema 2020-12 dialect URI, and some Realtime/S2S providers
|
|
125
|
+
* either reject the field outright or ship it through to the underlying
|
|
126
|
+
* model with a malformed function spec — observed empirically as tool
|
|
127
|
+
* calls that arrive with `args: {}` even when required params are listed.
|
|
128
|
+
*/
|
|
129
|
+
export function toToolJsonSchema(zodSchema: z.ZodTypeAny): JSONSchema7 {
|
|
130
|
+
const { $schema: _omit, ...rest } = z.toJSONSchema(zodSchema) as Record<string, unknown>;
|
|
131
|
+
return rest as JSONSchema7;
|
|
132
|
+
}
|
|
133
|
+
|
|
121
134
|
export function agentToolsToSchemas(tools: Readonly<Record<string, ToolDef>>): ToolSchema[] {
|
|
122
135
|
return Object.entries(tools).map(([name, def]) => ({
|
|
123
136
|
type: "function",
|
|
124
137
|
name,
|
|
125
138
|
description: def.description,
|
|
126
|
-
parameters:
|
|
139
|
+
parameters: toToolJsonSchema(def.parameters ?? EMPTY_PARAMS),
|
|
127
140
|
}));
|
|
128
141
|
}
|