@livekit/agents 1.0.34 → 1.0.35

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.
Files changed (57) hide show
  1. package/dist/cli.cjs.map +1 -1
  2. package/dist/inference/api_protos.d.cts +4 -4
  3. package/dist/inference/api_protos.d.ts +4 -4
  4. package/dist/inference/llm.cjs +30 -3
  5. package/dist/inference/llm.cjs.map +1 -1
  6. package/dist/inference/llm.d.cts +3 -1
  7. package/dist/inference/llm.d.ts +3 -1
  8. package/dist/inference/llm.d.ts.map +1 -1
  9. package/dist/inference/llm.js +30 -3
  10. package/dist/inference/llm.js.map +1 -1
  11. package/dist/ipc/inference_proc_executor.cjs.map +1 -1
  12. package/dist/ipc/job_proc_executor.cjs.map +1 -1
  13. package/dist/ipc/job_proc_lazy_main.cjs +1 -1
  14. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  15. package/dist/ipc/job_proc_lazy_main.js +1 -1
  16. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  17. package/dist/llm/chat_context.cjs +20 -2
  18. package/dist/llm/chat_context.cjs.map +1 -1
  19. package/dist/llm/chat_context.d.cts +9 -0
  20. package/dist/llm/chat_context.d.ts +9 -0
  21. package/dist/llm/chat_context.d.ts.map +1 -1
  22. package/dist/llm/chat_context.js +20 -2
  23. package/dist/llm/chat_context.js.map +1 -1
  24. package/dist/llm/llm.cjs.map +1 -1
  25. package/dist/llm/llm.d.cts +1 -0
  26. package/dist/llm/llm.d.ts +1 -0
  27. package/dist/llm/llm.d.ts.map +1 -1
  28. package/dist/llm/llm.js.map +1 -1
  29. package/dist/llm/provider_format/openai.cjs +43 -20
  30. package/dist/llm/provider_format/openai.cjs.map +1 -1
  31. package/dist/llm/provider_format/openai.d.ts.map +1 -1
  32. package/dist/llm/provider_format/openai.js +43 -20
  33. package/dist/llm/provider_format/openai.js.map +1 -1
  34. package/dist/llm/provider_format/openai.test.cjs +35 -0
  35. package/dist/llm/provider_format/openai.test.cjs.map +1 -1
  36. package/dist/llm/provider_format/openai.test.js +35 -0
  37. package/dist/llm/provider_format/openai.test.js.map +1 -1
  38. package/dist/llm/provider_format/utils.cjs +1 -1
  39. package/dist/llm/provider_format/utils.cjs.map +1 -1
  40. package/dist/llm/provider_format/utils.d.ts.map +1 -1
  41. package/dist/llm/provider_format/utils.js +1 -1
  42. package/dist/llm/provider_format/utils.js.map +1 -1
  43. package/dist/voice/background_audio.cjs.map +1 -1
  44. package/dist/voice/generation.cjs +2 -1
  45. package/dist/voice/generation.cjs.map +1 -1
  46. package/dist/voice/generation.d.ts.map +1 -1
  47. package/dist/voice/generation.js +2 -1
  48. package/dist/voice/generation.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/inference/llm.ts +42 -3
  51. package/src/ipc/job_proc_lazy_main.ts +1 -1
  52. package/src/llm/chat_context.ts +32 -2
  53. package/src/llm/llm.ts +1 -0
  54. package/src/llm/provider_format/openai.test.ts +40 -0
  55. package/src/llm/provider_format/openai.ts +46 -19
  56. package/src/llm/provider_format/utils.ts +5 -1
  57. package/src/voice/generation.ts +1 -0
@@ -189,6 +189,12 @@ export class FunctionCall {
189
189
 
190
190
  createdAt: number;
191
191
 
192
+ extra: Record<string, unknown>;
193
+ /**
194
+ * Optional grouping identifier for parallel tool calls.
195
+ */
196
+ groupId?: string;
197
+
192
198
  /**
193
199
  * Opaque signature for Gemini thinking mode.
194
200
  * When using Gemini 3+ models with thinking enabled, this signature must be
@@ -202,6 +208,8 @@ export class FunctionCall {
202
208
  args: string;
203
209
  id?: string;
204
210
  createdAt?: number;
211
+ extra?: Record<string, unknown>;
212
+ groupId?: string;
205
213
  thoughtSignature?: string;
206
214
  }) {
207
215
  const {
@@ -210,6 +218,8 @@ export class FunctionCall {
210
218
  args,
211
219
  id = shortuuid('item_'),
212
220
  createdAt = Date.now(),
221
+ extra = {},
222
+ groupId,
213
223
  thoughtSignature,
214
224
  } = params;
215
225
  this.id = id;
@@ -217,7 +227,15 @@ export class FunctionCall {
217
227
  this.args = args;
218
228
  this.name = name;
219
229
  this.createdAt = createdAt;
220
- this.thoughtSignature = thoughtSignature;
230
+ this.extra = { ...extra };
231
+ this.groupId = groupId;
232
+ this.thoughtSignature =
233
+ thoughtSignature ??
234
+ (typeof this.extra.google === 'object' && this.extra.google !== null
235
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
+ (this.extra.google as any).thoughtSignature ||
237
+ (this.extra.google as any).thought_signature
238
+ : undefined);
221
239
  }
222
240
 
223
241
  static create(params: {
@@ -226,6 +244,8 @@ export class FunctionCall {
226
244
  args: string;
227
245
  id?: string;
228
246
  createdAt?: number;
247
+ extra?: Record<string, unknown>;
248
+ groupId?: string;
229
249
  thoughtSignature?: string;
230
250
  }) {
231
251
  return new FunctionCall(params);
@@ -241,6 +261,14 @@ export class FunctionCall {
241
261
  args: this.args,
242
262
  };
243
263
 
264
+ if (Object.keys(this.extra).length > 0) {
265
+ result.extra = this.extra as JSONValue;
266
+ }
267
+
268
+ if (this.groupId) {
269
+ result.groupId = this.groupId;
270
+ }
271
+
244
272
  if (this.thoughtSignature) {
245
273
  result.thoughtSignature = this.thoughtSignature;
246
274
  }
@@ -627,7 +655,9 @@ export class ChatContext {
627
655
  a.name !== b.name ||
628
656
  a.callId !== b.callId ||
629
657
  a.args !== b.args ||
630
- a.thoughtSignature !== b.thoughtSignature
658
+ a.thoughtSignature !== b.thoughtSignature ||
659
+ a.groupId !== b.groupId ||
660
+ JSON.stringify(a.extra) !== JSON.stringify(b.extra)
631
661
  ) {
632
662
  return false;
633
663
  }
package/src/llm/llm.ts CHANGED
@@ -17,6 +17,7 @@ export interface ChoiceDelta {
17
17
  role: ChatRole;
18
18
  content?: string;
19
19
  toolCalls?: FunctionCall[];
20
+ extra?: Record<string, unknown>;
20
21
  }
21
22
 
22
23
  export interface CompletionUsage {
@@ -258,6 +258,46 @@ describe('toChatCtx', () => {
258
258
  ]);
259
259
  });
260
260
 
261
+ it('should include provider-specific extra content on tool calls', async () => {
262
+ const ctx = ChatContext.empty();
263
+ const msg = ctx.addMessage({ role: 'assistant', content: 'Running tool' });
264
+
265
+ const toolCall = FunctionCall.create({
266
+ id: `${msg.id}/tool_1`,
267
+ callId: 'call_789',
268
+ name: 'google_call',
269
+ args: '{}',
270
+ extra: { google: { thoughtSignature: 'sig-123' } },
271
+ });
272
+ const toolOutput = FunctionCallOutput.create({
273
+ callId: 'call_789',
274
+ output: '{"result": "ok"}',
275
+ isError: false,
276
+ });
277
+
278
+ ctx.insert([toolCall, toolOutput]);
279
+
280
+ const result = await toChatCtx(ctx);
281
+
282
+ expect(result[0]).toEqual({
283
+ role: 'assistant',
284
+ content: 'Running tool',
285
+ tool_calls: [
286
+ {
287
+ type: 'function',
288
+ id: 'call_789',
289
+ function: { name: 'google_call', arguments: '{}' },
290
+ extra_content: { google: { thoughtSignature: 'sig-123' } },
291
+ },
292
+ ],
293
+ });
294
+ expect(result[1]).toEqual({
295
+ role: 'tool',
296
+ tool_call_id: 'call_789',
297
+ content: '{"result": "ok"}',
298
+ });
299
+ });
300
+
261
301
  it('should handle multiple tool calls in one message', async () => {
262
302
  const ctx = ChatContext.empty();
263
303
 
@@ -17,11 +17,20 @@ export async function toChatCtx(chatCtx: ChatContext, injectDummyUserMessage: bo
17
17
  ? await toChatItem(group.message)
18
18
  : { role: 'assistant' };
19
19
 
20
- const toolCalls = group.toolCalls.map((toolCall) => ({
21
- type: 'function',
22
- id: toolCall.callId,
23
- function: { name: toolCall.name, arguments: toolCall.args },
24
- }));
20
+ const toolCalls = group.toolCalls.map((toolCall) => {
21
+ const tc: Record<string, any> = {
22
+ type: 'function',
23
+ id: toolCall.callId,
24
+ function: { name: toolCall.name, arguments: toolCall.args },
25
+ };
26
+
27
+ // Include provider-specific extra content (e.g., Google thought signatures)
28
+ const googleExtra = getGoogleExtra(toolCall);
29
+ if (googleExtra) {
30
+ tc.extra_content = { google: googleExtra };
31
+ }
32
+ return tc;
33
+ });
25
34
 
26
35
  if (toolCalls.length > 0) {
27
36
  message['tool_calls'] = toolCalls;
@@ -53,24 +62,33 @@ async function toChatItem(item: ChatItem) {
53
62
  }
54
63
  }
55
64
 
56
- const content =
57
- listContent.length == 0
58
- ? textContent
59
- : textContent.length == 0
60
- ? listContent
61
- : [...listContent, { type: 'text', text: textContent }];
65
+ const result: Record<string, any> = { role: item.role };
66
+ if (listContent.length === 0) {
67
+ result.content = textContent;
68
+ } else {
69
+ if (textContent.length > 0) {
70
+ listContent.push({ type: 'text', text: textContent });
71
+ }
72
+ result.content = listContent;
73
+ }
62
74
 
63
- return { role: item.role, content };
75
+ return result;
64
76
  } else if (item.type === 'function_call') {
77
+ const tc: Record<string, any> = {
78
+ id: item.callId,
79
+ type: 'function',
80
+ function: { name: item.name, arguments: item.args },
81
+ };
82
+
83
+ // Include provider-specific extra content (e.g., Google thought signatures)
84
+ const googleExtra = getGoogleExtra(item);
85
+ if (googleExtra) {
86
+ tc.extra_content = { google: googleExtra };
87
+ }
88
+
65
89
  return {
66
90
  role: 'assistant',
67
- tool_calls: [
68
- {
69
- id: item.callId,
70
- type: 'function',
71
- function: { name: item.name, arguments: item.args },
72
- },
73
- ],
91
+ tool_calls: [tc],
74
92
  };
75
93
  } else if (item.type === 'function_call_output') {
76
94
  return {
@@ -84,6 +102,15 @@ async function toChatItem(item: ChatItem) {
84
102
  throw new Error(`Unsupported item type: ${item['type']}`);
85
103
  }
86
104
 
105
+ function getGoogleExtra(
106
+ item: Partial<{ extra?: Record<string, unknown>; thoughtSignature?: string }>,
107
+ ): Record<string, unknown> | undefined {
108
+ const googleExtra =
109
+ (item.extra?.google as Record<string, unknown> | undefined) ||
110
+ (item.thoughtSignature ? { thoughtSignature: item.thoughtSignature } : undefined);
111
+ return googleExtra;
112
+ }
113
+
87
114
  async function toImageContent(content: ImageContent) {
88
115
  const cacheKey = 'serialized_image'; // TODO: use hash of encoding options if available
89
116
  let serialized: SerializedImage;
@@ -133,7 +133,11 @@ export function groupToolCalls(chatCtx: ChatContext) {
133
133
 
134
134
  if (isAssistantMessage || isFunctionCall) {
135
135
  // only assistant messages and function calls can be grouped
136
- const groupId = item.id.split('/')[0]!;
136
+ // For function calls, use group_id if available (for parallel function calls),
137
+ // otherwise fall back to id-based grouping for backwards compatibility
138
+ const groupId =
139
+ item.type === 'function_call' && item.groupId ? item.groupId : item.id.split('/')[0]!;
140
+
137
141
  if (itemGroups[groupId] === undefined) {
138
142
  itemGroups[groupId] = ChatItemGroup.create();
139
143
 
@@ -444,6 +444,7 @@ export function performLLMInference(
444
444
  args: tool.args,
445
445
  // Preserve thought signature for Gemini 3+ thinking mode
446
446
  thoughtSignature: tool.thoughtSignature,
447
+ extra: tool.extra || {},
447
448
  });
448
449
 
449
450
  data.generatedToolCalls.push(toolCall);