@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @alexkroman1/aai@1.8.1 build /home/runner/work/agent/agent/packages/aai
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
  ℹ tsdown v0.21.7 powered by rolldown v1.0.0-rc.12
@@ -8,7 +8,7 @@
8
8
  ℹ target: node22
9
9
  ℹ tsconfig: tsconfig.json
10
10
  ℹ Build start
11
- ℹ dist/host/runtime-barrel.js 109.52 kB │ gzip: 30.12 kB
11
+ ℹ dist/host/runtime-barrel.js 110.46 kB │ gzip: 30.38 kB
12
12
  ℹ dist/sdk/protocol.js  5.70 kB │ gzip: 1.92 kB
13
13
  ℹ dist/index.js  2.88 kB │ gzip: 1.24 kB
14
14
  ℹ dist/sdk/manifest-barrel.js  0.36 kB │ gzip: 0.20 kB
@@ -18,7 +18,7 @@
18
18
  ℹ dist/sdk/providers/tts-barrel.js  0.25 kB │ gzip: 0.16 kB
19
19
  ℹ dist/sdk/providers/vector-barrel.js  0.22 kB │ gzip: 0.15 kB
20
20
  ℹ dist/sdk/providers/s2s-barrel.js  0.15 kB │ gzip: 0.12 kB
21
- ℹ dist/_internal-types-CfOAbK6V.js  5.45 kB │ gzip: 1.87 kB
21
+ ℹ dist/_internal-types-8v1qAa4A.js  6.04 kB │ gzip: 2.15 kB
22
22
  ℹ dist/types-DOWVZhb9.js  5.39 kB │ gzip: 2.27 kB
23
23
  ℹ dist/soniox-BQdL0mB5.js  2.03 kB │ gzip: 0.54 kB
24
24
  ℹ dist/constants-y68COEGj.js  1.70 kB │ gzip: 0.76 kB
@@ -28,5 +28,5 @@
28
28
  ℹ dist/s3-BtCMvCod.js  0.76 kB │ gzip: 0.29 kB
29
29
  ℹ dist/pinecone-CeJ69aRs.js  0.48 kB │ gzip: 0.24 kB
30
30
  ℹ dist/openai-realtime-cjPAHMMx.js  0.27 kB │ gzip: 0.19 kB
31
- ℹ 20 files, total: 139.45 kB
32
- ✔ Build complete in 62ms
31
+ ℹ 20 files, total: 140.98 kB
32
+ ✔ Build complete in 42ms
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: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
148
+ parameters: toToolJsonSchema(def.parameters ?? EMPTY_PARAMS)
137
149
  }));
138
150
  }
139
151
  //#endregion
140
- export { toAgentConfig as a, makeSttError as c, agentToolsToSchemas as i, makeTtsError as l, EMPTY_PARAMS as n, ProviderDescriptorSchema as o, ToolSchemaSchema as r, assertProviderTriple as s, AgentConfigSchema as t };
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 makeSttError, i as agentToolsToSchemas, l as makeTtsError, n as EMPTY_PARAMS, s as assertProviderTriple } from "../_internal-types-CfOAbK6V.js";
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: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS)
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
- args: z.record(z.string(), z.unknown()).optional().default({})
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
- const obj = raw;
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, i as agentToolsToSchemas, n as EMPTY_PARAMS, o as ProviderDescriptorSchema, r as ToolSchemaSchema, s as assertProviderTriple, t as AgentConfigSchema } from "../_internal-types-CfOAbK6V.js";
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 };
@@ -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: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS) as ToolSchema["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
- z.object({
59
- type: z.literal("tool.call"),
60
- call_id: z.string(),
61
- name: z.string(),
62
- args: z.record(z.string(), z.unknown()).optional().default({}),
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
- ws.addEventListener("message", (ev) => {
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 });
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -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: z.toJSONSchema(def.parameters ?? EMPTY_PARAMS) as JSONSchema7,
139
+ parameters: toToolJsonSchema(def.parameters ?? EMPTY_PARAMS),
127
140
  }));
128
141
  }