@livekit/agents 1.2.0 → 1.2.1
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/_exceptions.cjs.map +1 -1
- package/dist/_exceptions.d.ts.map +1 -1
- package/dist/_exceptions.js.map +1 -1
- package/dist/beta/workflows/task_group.cjs +7 -4
- package/dist/beta/workflows/task_group.cjs.map +1 -1
- package/dist/beta/workflows/task_group.d.ts.map +1 -1
- package/dist/beta/workflows/task_group.js +7 -4
- package/dist/beta/workflows/task_group.js.map +1 -1
- package/dist/inference/interruption/http_transport.cjs.map +1 -1
- package/dist/inference/interruption/http_transport.d.cts +3 -1
- package/dist/inference/interruption/http_transport.d.ts +3 -1
- package/dist/inference/interruption/http_transport.d.ts.map +1 -1
- package/dist/inference/interruption/http_transport.js.map +1 -1
- package/dist/inference/interruption/ws_transport.cjs +37 -32
- package/dist/inference/interruption/ws_transport.cjs.map +1 -1
- package/dist/inference/interruption/ws_transport.d.ts.map +1 -1
- package/dist/inference/interruption/ws_transport.js +37 -32
- package/dist/inference/interruption/ws_transport.js.map +1 -1
- package/dist/inference/tts.cjs.map +1 -1
- package/dist/inference/tts.d.cts +42 -4
- package/dist/inference/tts.d.ts +42 -4
- package/dist/inference/tts.d.ts.map +1 -1
- package/dist/inference/tts.js.map +1 -1
- package/dist/inference/tts.test.cjs +72 -0
- package/dist/inference/tts.test.cjs.map +1 -1
- package/dist/inference/tts.test.js +72 -0
- package/dist/inference/tts.test.js.map +1 -1
- package/dist/llm/chat_context.cjs +102 -31
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +102 -31
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/chat_context.test.cjs +123 -5
- package/dist/llm/chat_context.test.cjs.map +1 -1
- package/dist/llm/chat_context.test.js +123 -5
- package/dist/llm/chat_context.test.js.map +1 -1
- package/dist/llm/fallback_adapter.cjs +2 -0
- package/dist/llm/fallback_adapter.cjs.map +1 -1
- package/dist/llm/fallback_adapter.d.ts.map +1 -1
- package/dist/llm/fallback_adapter.js +2 -0
- package/dist/llm/fallback_adapter.js.map +1 -1
- package/dist/llm/index.cjs +2 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +2 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/utils.cjs +89 -0
- package/dist/llm/utils.cjs.map +1 -1
- package/dist/llm/utils.d.cts +8 -0
- package/dist/llm/utils.d.ts +8 -0
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +88 -0
- package/dist/llm/utils.js.map +1 -1
- package/dist/llm/utils.test.cjs +90 -0
- package/dist/llm/utils.test.cjs.map +1 -1
- package/dist/llm/utils.test.js +98 -2
- package/dist/llm/utils.test.js.map +1 -1
- package/dist/stt/stt.cjs +8 -0
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.cts +8 -0
- package/dist/stt/stt.d.ts +8 -0
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +8 -0
- package/dist/stt/stt.js.map +1 -1
- package/dist/tts/fallback_adapter.cjs +6 -0
- package/dist/tts/fallback_adapter.cjs.map +1 -1
- package/dist/tts/fallback_adapter.d.ts.map +1 -1
- package/dist/tts/fallback_adapter.js +6 -0
- package/dist/tts/fallback_adapter.js.map +1 -1
- package/dist/typed_promise.cjs +48 -0
- package/dist/typed_promise.cjs.map +1 -0
- package/dist/typed_promise.d.cts +24 -0
- package/dist/typed_promise.d.ts +24 -0
- package/dist/typed_promise.d.ts.map +1 -0
- package/dist/typed_promise.js +28 -0
- package/dist/typed_promise.js.map +1 -0
- package/dist/utils.cjs +2 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.js +2 -2
- package/dist/utils.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -2
- package/src/_exceptions.ts +5 -0
- package/src/beta/workflows/task_group.ts +14 -5
- package/src/inference/interruption/http_transport.ts +2 -1
- package/src/inference/interruption/ws_transport.ts +44 -34
- package/src/inference/tts.test.ts +87 -0
- package/src/inference/tts.ts +46 -6
- package/src/llm/chat_context.test.ts +137 -5
- package/src/llm/chat_context.ts +119 -38
- package/src/llm/fallback_adapter.ts +5 -2
- package/src/llm/index.ts +2 -0
- package/src/llm/utils.test.ts +103 -2
- package/src/llm/utils.ts +128 -0
- package/src/stt/stt.ts +9 -1
- package/src/tts/fallback_adapter.ts +9 -2
- package/src/typed_promise.ts +67 -0
- package/src/utils.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/llm/chat_context.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame, VideoFrame } from '@livekit/rtc-node';\nimport { createImmutableArray, shortuuid } from '../utils.js';\nimport type { LLM } from './llm.js';\nimport { type ProviderFormat, toChatCtx } from './provider_format/index.js';\nimport type { JSONObject, JSONValue, ToolContext } from './tool_context.js';\n\nexport type ChatRole = 'developer' | 'system' | 'user' | 'assistant';\nexport interface ImageContent {\n id: string;\n\n type: 'image_content';\n\n /**\n * Either a string URL or a VideoFrame object.\n */\n image: string | VideoFrame;\n\n inferenceDetail: 'auto' | 'high' | 'low';\n\n inferenceWidth?: number;\n\n inferenceHeight?: number;\n\n mimeType?: string;\n\n _cache: Record<any, any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n}\n\nexport interface AudioContent {\n type: 'audio_content';\n\n frame: AudioFrame[];\n\n transcript?: string;\n}\n\nexport type ChatContent = ImageContent | AudioContent | string;\n\nexport function createImageContent(params: {\n image: string | VideoFrame;\n id?: string;\n inferenceDetail?: 'auto' | 'high' | 'low';\n inferenceWidth?: number;\n inferenceHeight?: number;\n mimeType?: string;\n}): ImageContent {\n const {\n image,\n id = shortuuid('img_'),\n inferenceDetail = 'auto',\n inferenceWidth,\n inferenceHeight,\n mimeType,\n } = params;\n\n return {\n id,\n type: 'image_content',\n image,\n inferenceDetail,\n inferenceWidth,\n inferenceHeight,\n mimeType,\n _cache: {},\n };\n}\n\nexport function createAudioContent(params: {\n frame: AudioFrame[];\n transcript?: string;\n}): AudioContent {\n const { frame, transcript } = params;\n\n return {\n type: 'audio_content',\n frame,\n transcript,\n };\n}\n\nexport interface MetricsReport {\n startedSpeakingAt?: number;\n stoppedSpeakingAt?: number;\n transcriptionDelay?: number;\n endOfTurnDelay?: number;\n onUserTurnCompletedDelay?: number;\n llmNodeTtft?: number;\n ttsNodeTtfb?: number;\n e2eLatency?: number;\n}\n\nexport class ChatMessage {\n readonly id: string;\n\n readonly type = 'message' as const;\n\n readonly role: ChatRole;\n\n content: ChatContent[];\n\n interrupted: boolean;\n\n transcriptConfidence?: number;\n\n extra: Record<string, unknown>;\n\n metrics: MetricsReport;\n\n hash?: Uint8Array;\n\n createdAt: number;\n\n constructor(params: {\n role: ChatRole;\n content: ChatContent[] | string;\n id?: string;\n interrupted?: boolean;\n createdAt?: number;\n transcriptConfidence?: number;\n metrics?: MetricsReport;\n extra?: Record<string, unknown>;\n }) {\n const {\n role,\n content,\n id = shortuuid('item_'),\n interrupted = false,\n createdAt = Date.now(),\n transcriptConfidence,\n metrics = {},\n extra = {},\n } = params;\n this.id = id;\n this.role = role;\n this.content = Array.isArray(content) ? content : [content];\n this.interrupted = interrupted;\n this.createdAt = createdAt;\n this.transcriptConfidence = transcriptConfidence;\n this.metrics = metrics;\n this.extra = extra;\n }\n\n static create(params: {\n role: ChatRole;\n content: ChatContent[] | string;\n id?: string;\n interrupted?: boolean;\n createdAt?: number;\n transcriptConfidence?: number;\n metrics?: MetricsReport;\n extra?: Record<string, unknown>;\n }) {\n return new ChatMessage(params);\n }\n\n /**\n * Returns a single string with all text parts of the message joined by new\n * lines. If no string content is present, returns `null`.\n */\n get textContent(): string | undefined {\n const parts = this.content.filter((c): c is string => typeof c === 'string');\n return parts.length > 0 ? parts.join('\\n') : undefined;\n }\n\n toJSONContent(): JSONValue[] {\n return this.content.map((c) => {\n if (typeof c === 'string') {\n return c as JSONValue;\n } else if (c.type === 'image_content') {\n return {\n id: c.id,\n type: c.type,\n image: c.image,\n inferenceDetail: c.inferenceDetail,\n inferenceWidth: c.inferenceWidth,\n inferenceHeight: c.inferenceHeight,\n mimeType: c.mimeType,\n } as JSONObject;\n } else {\n return {\n type: c.type,\n transcript: c.transcript,\n } as JSONObject;\n }\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n role: this.role,\n content: this.toJSONContent(),\n interrupted: this.interrupted,\n };\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n if (this.transcriptConfidence !== undefined) {\n result.transcriptConfidence = this.transcriptConfidence;\n }\n if (Object.keys(this.metrics).length > 0) {\n result.metrics = { ...this.metrics };\n }\n if (Object.keys(this.extra).length > 0) {\n result.extra = this.extra as JSONValue;\n }\n\n return result;\n }\n}\n\nexport class FunctionCall {\n readonly id: string;\n\n readonly type = 'function_call' as const;\n\n callId: string;\n\n args: string;\n\n name: string;\n\n createdAt: number;\n\n extra: Record<string, unknown>;\n /**\n * Optional grouping identifier for parallel tool calls.\n */\n groupId?: string;\n\n /**\n * Opaque signature for Gemini thinking mode.\n * When using Gemini 3+ models with thinking enabled, this signature must be\n * preserved and returned with function responses to maintain thought context.\n */\n thoughtSignature?: string;\n\n constructor(params: {\n callId: string;\n name: string;\n args: string;\n id?: string;\n createdAt?: number;\n extra?: Record<string, unknown>;\n groupId?: string;\n thoughtSignature?: string;\n }) {\n const {\n callId,\n name,\n args,\n id = shortuuid('item_'),\n createdAt = Date.now(),\n extra = {},\n groupId,\n thoughtSignature,\n } = params;\n this.id = id;\n this.callId = callId;\n this.args = args;\n this.name = name;\n this.createdAt = createdAt;\n this.extra = { ...extra };\n this.groupId = groupId;\n this.thoughtSignature =\n thoughtSignature ??\n (typeof this.extra.google === 'object' && this.extra.google !== null\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this.extra.google as any).thoughtSignature ||\n (this.extra.google as any).thought_signature\n : undefined);\n }\n\n static create(params: {\n callId: string;\n name: string;\n args: string;\n id?: string;\n createdAt?: number;\n extra?: Record<string, unknown>;\n groupId?: string;\n thoughtSignature?: string;\n }) {\n return new FunctionCall(params);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n callId: this.callId,\n name: this.name,\n args: this.args,\n };\n\n if (Object.keys(this.extra).length > 0) {\n result.extra = this.extra as JSONValue;\n }\n\n if (this.groupId) {\n result.groupId = this.groupId;\n }\n\n if (this.thoughtSignature) {\n result.thoughtSignature = this.thoughtSignature;\n }\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n return result;\n }\n}\n\nexport class FunctionCallOutput {\n readonly id: string;\n\n readonly type = 'function_call_output' as const;\n\n name = '';\n\n callId: string;\n\n output: string;\n\n isError: boolean;\n\n createdAt: number;\n\n constructor(params: {\n callId: string;\n output: string;\n isError: boolean;\n id?: string;\n createdAt?: number;\n name?: string;\n }) {\n const {\n callId,\n output,\n isError,\n id = shortuuid('item_'),\n createdAt = Date.now(),\n name = '',\n } = params;\n this.id = id;\n this.callId = callId;\n this.output = output;\n this.isError = isError;\n this.name = name;\n this.createdAt = createdAt;\n }\n\n static create(params: {\n callId: string;\n output: string;\n isError: boolean;\n id?: string;\n createdAt?: number;\n name?: string;\n }) {\n return new FunctionCallOutput(params);\n }\n\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n name: this.name,\n callId: this.callId,\n output: this.output,\n isError: this.isError,\n };\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n return result;\n }\n}\n\nexport class AgentHandoffItem {\n readonly id: string;\n\n readonly type = 'agent_handoff' as const;\n\n oldAgentId: string | undefined;\n\n newAgentId: string;\n\n createdAt: number;\n\n constructor(params: {\n oldAgentId?: string;\n newAgentId: string;\n id?: string;\n createdAt?: number;\n }) {\n const { oldAgentId, newAgentId, id = shortuuid('item_'), createdAt = Date.now() } = params;\n this.id = id;\n this.oldAgentId = oldAgentId;\n this.newAgentId = newAgentId;\n this.createdAt = createdAt;\n }\n\n static create(params: {\n oldAgentId?: string;\n newAgentId: string;\n id?: string;\n createdAt?: number;\n }) {\n return new AgentHandoffItem(params);\n }\n\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n newAgentId: this.newAgentId,\n };\n\n if (this.oldAgentId !== undefined) {\n result.oldAgentId = this.oldAgentId;\n }\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n return result;\n }\n}\n\n// TODO(parity): Add AgentConfigUpdate type to ChatItem union\nexport type ChatItem = ChatMessage | FunctionCall | FunctionCallOutput | AgentHandoffItem;\n\nexport class ChatContext {\n protected _items: ChatItem[];\n\n constructor(items?: ChatItem[]) {\n this._items = items ? items : [];\n }\n\n static empty(): ChatContext {\n return new ChatContext([]);\n }\n\n get items(): ChatItem[] {\n return this._items;\n }\n\n set items(items: ChatItem[]) {\n this._items = items;\n }\n\n /**\n * Add a new message to the context and return it.\n */\n addMessage(params: {\n role: ChatRole;\n content: ChatContent[] | string;\n id?: string;\n interrupted?: boolean;\n createdAt?: number;\n transcriptConfidence?: number;\n metrics?: MetricsReport;\n extra?: Record<string, unknown>;\n }): ChatMessage {\n const msg = new ChatMessage(params);\n if (params.createdAt !== undefined) {\n const idx = this.findInsertionIndex(params.createdAt);\n this._items.splice(idx, 0, msg);\n } else {\n this._items.push(msg);\n }\n return msg;\n }\n\n /**\n * Insert a single item or multiple items based on their `createdAt` field so\n * that the array keeps its chronological order.\n */\n insert(item: ChatItem | ChatItem[]): void {\n const arr = Array.isArray(item) ? item : [item];\n for (const it of arr) {\n const idx = this.findInsertionIndex(it.createdAt);\n this._items.splice(idx, 0, it);\n }\n }\n\n getById(itemId: string): ChatItem | undefined {\n return this._items.find((i) => i.id === itemId);\n }\n\n indexById(itemId: string): number | undefined {\n const idx = this._items.findIndex((i) => i.id === itemId);\n return idx !== -1 ? idx : undefined;\n }\n\n // TODO(parity): Add excludeConfigUpdate option when AgentConfigUpdate is ported\n copy(\n options: {\n excludeFunctionCall?: boolean;\n excludeInstructions?: boolean;\n excludeEmptyMessage?: boolean;\n excludeHandoff?: boolean;\n toolCtx?: ToolContext<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n } = {},\n ): ChatContext {\n const {\n excludeFunctionCall = false,\n excludeInstructions = false,\n excludeEmptyMessage = false,\n excludeHandoff = false,\n toolCtx,\n } = options;\n const items: ChatItem[] = [];\n\n const isToolCallOrOutput = (item: ChatItem): item is FunctionCall | FunctionCallOutput =>\n ['function_call', 'function_call_output'].includes(item.type);\n const isChatMessage = (item: ChatItem): item is ChatMessage => item.type === 'message';\n\n for (const item of this._items) {\n if (excludeFunctionCall && isToolCallOrOutput(item)) {\n continue;\n }\n\n if (\n excludeInstructions &&\n isChatMessage(item) &&\n ['system', 'developer'].includes(item.role)\n ) {\n continue;\n }\n\n if (excludeEmptyMessage && isChatMessage(item) && item.content.length === 0) {\n continue;\n }\n\n if (excludeHandoff && item.type === 'agent_handoff') {\n continue;\n }\n\n if (toolCtx !== undefined && isToolCallOrOutput(item) && toolCtx[item.name] === undefined) {\n continue;\n }\n\n items.push(item);\n }\n\n return new ChatContext(items);\n }\n\n // TODO(parity): Add excludeConfigUpdate option when AgentConfigUpdate is ported\n merge(\n other: ChatContext,\n options: {\n excludeFunctionCall?: boolean;\n excludeInstructions?: boolean;\n } = {},\n ): ChatContext {\n const { excludeFunctionCall = false, excludeInstructions = false } = options;\n const existingIds = new Set(this._items.map((item) => item.id));\n\n for (const item of other.items) {\n if (excludeFunctionCall && ['function_call', 'function_call_output'].includes(item.type)) {\n continue;\n }\n\n if (\n excludeInstructions &&\n item.type === 'message' &&\n (item.role === 'system' || item.role === 'developer')\n ) {\n continue;\n }\n\n if (existingIds.has(item.id)) {\n continue;\n }\n\n const idx = this.findInsertionIndex(item.createdAt);\n this._items.splice(idx, 0, item);\n existingIds.add(item.id);\n }\n\n return this;\n }\n\n truncate(maxItems: number): ChatContext {\n if (maxItems <= 0) return this;\n\n const instructions = this._items.find((i) => i.type === 'message' && i.role === 'system') as\n | ChatMessage\n | undefined;\n\n let newItems = this._items.slice(-maxItems);\n\n // Ensure the first item is not a function-call artefact.\n while (\n newItems.length > 0 &&\n ['function_call', 'function_call_output'].includes(newItems[0]!.type)\n ) {\n newItems.shift();\n }\n\n if (instructions) {\n // At this point `instructions` is defined, so it is safe to pass to `includes`.\n if (!newItems.includes(instructions)) {\n newItems = [instructions, ...newItems];\n }\n }\n\n // replace the items in place to keep the reference\n this._items.splice(0, this._items.length, ...newItems);\n return this;\n }\n\n toJSON(\n options: {\n excludeImage?: boolean;\n excludeAudio?: boolean;\n excludeTimestamp?: boolean;\n excludeFunctionCall?: boolean;\n } = {},\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): JSONObject {\n const {\n excludeImage = true,\n excludeAudio = true,\n excludeTimestamp = true,\n excludeFunctionCall = false,\n } = options;\n\n const items: ChatItem[] = [];\n\n for (const item of this._items) {\n let processedItem = item;\n\n if (excludeFunctionCall && ['function_call', 'function_call_output'].includes(item.type)) {\n continue;\n }\n\n if (item.type === 'message') {\n processedItem = ChatMessage.create({\n role: item.role,\n content: item.content,\n id: item.id,\n interrupted: item.interrupted,\n createdAt: item.createdAt,\n transcriptConfidence: item.transcriptConfidence,\n metrics: item.metrics,\n extra: item.extra,\n });\n\n // Filter content based on options\n if (excludeImage) {\n processedItem.content = processedItem.content.filter((c) => {\n return !(typeof c === 'object' && c.type === 'image_content');\n });\n }\n\n if (excludeAudio) {\n processedItem.content = processedItem.content.filter((c) => {\n return !(typeof c === 'object' && c.type === 'audio_content');\n });\n }\n }\n\n items.push(processedItem);\n }\n\n return {\n items: items.map((item) => item.toJSON(excludeTimestamp)),\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async toProviderFormat(format: ProviderFormat, injectDummyUserMessage: boolean = true) {\n return await toChatCtx(format, this, injectDummyUserMessage);\n }\n\n /**\n * Internal helper used by `truncate` & `addMessage` to find the correct\n * insertion index for a timestamp so the list remains sorted.\n */\n private findInsertionIndex(createdAt: number): number {\n for (let i = this._items.length - 1; i >= 0; i -= 1) {\n const item = this._items[i];\n if (item!.createdAt <= createdAt) {\n return i + 1;\n }\n }\n return 0;\n }\n\n /**\n * Return true if `other` has the same sequence of items with matching\n * essential fields (IDs, types, and payload) as this context.\n *\n * Comparison rules:\n * - Messages: compares the full `content` list, `role` and `interrupted`.\n * - Function calls: compares `name`, `callId`, and `args`.\n * - Function call outputs: compares `name`, `callId`, `output`, and `isError`.\n *\n * Does not consider timestamps or other metadata.\n */\n isEquivalent(other: ChatContext): boolean {\n if (this === other) {\n return true;\n }\n\n if (this.items.length !== other.items.length) {\n return false;\n }\n\n for (let i = 0; i < this.items.length; i++) {\n const a = this.items[i]!;\n const b = other.items[i]!;\n\n if (a.id !== b.id || a.type !== b.type) {\n return false;\n }\n\n if (a.type === 'message' && b.type === 'message') {\n if (\n a.role !== b.role ||\n a.interrupted !== b.interrupted ||\n !this.compareContent(a.content, b.content)\n ) {\n return false;\n }\n } else if (a.type === 'function_call' && b.type === 'function_call') {\n if (\n a.name !== b.name ||\n a.callId !== b.callId ||\n a.args !== b.args ||\n a.thoughtSignature !== b.thoughtSignature ||\n a.groupId !== b.groupId ||\n JSON.stringify(a.extra) !== JSON.stringify(b.extra)\n ) {\n return false;\n }\n } else if (a.type === 'function_call_output' && b.type === 'function_call_output') {\n if (\n a.name !== b.name ||\n a.callId !== b.callId ||\n a.output !== b.output ||\n a.isError !== b.isError\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n /**\n * Compare two content arrays for equality.\n */\n private compareContent(a: ChatContent[], b: ChatContent[]): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n for (let i = 0; i < a.length; i++) {\n const contentA = a[i]!;\n const contentB = b[i]!;\n\n if (typeof contentA === 'string' && typeof contentB === 'string') {\n if (contentA !== contentB) {\n return false;\n }\n continue;\n }\n\n if (typeof contentA !== typeof contentB) {\n return false;\n }\n\n if (typeof contentA === 'object' && typeof contentB === 'object') {\n if (contentA.type === 'image_content' && contentB.type === 'image_content') {\n if (\n contentA.id !== contentB.id ||\n contentA.image !== contentB.image ||\n contentA.inferenceDetail !== contentB.inferenceDetail ||\n contentA.inferenceWidth !== contentB.inferenceWidth ||\n contentA.inferenceHeight !== contentB.inferenceHeight ||\n contentA.mimeType !== contentB.mimeType\n ) {\n return false;\n }\n } else if (contentA.type === 'audio_content' && contentB.type === 'audio_content') {\n if (contentA.frame.length !== contentB.frame.length) {\n return false;\n }\n if (contentA.transcript !== contentB.transcript) {\n return false;\n }\n } else {\n return false;\n }\n }\n }\n\n return true;\n }\n\n async _summarize(llm: LLM, options: { keepLastTurns?: number } = {}): Promise<ChatContext> {\n const { keepLastTurns = 2 } = options;\n\n const toSummarize: ChatMessage[] = [];\n for (const item of this._items) {\n if (item.type !== 'message') continue;\n if (item.role !== 'user' && item.role !== 'assistant') continue;\n if (item.extra?.is_summary === true) continue;\n\n const text = (item.textContent ?? '').trim();\n if (text) {\n toSummarize.push(item);\n }\n }\n\n if (toSummarize.length === 0) {\n return this;\n }\n\n const tailN = Math.max(0, Math.min(toSummarize.length, keepLastTurns * 2));\n let head: ChatMessage[];\n let tail: ChatMessage[];\n if (tailN === 0) {\n head = toSummarize;\n tail = [];\n } else {\n head = toSummarize.slice(0, -tailN);\n tail = toSummarize.slice(-tailN);\n }\n\n if (head.length === 0) {\n return this;\n }\n\n const sourceText = head\n .map((m) => `${m.role}: ${(m.textContent ?? '').trim()}`)\n .join('\\n')\n .trim();\n\n if (!sourceText) {\n return this;\n }\n\n // TODO: refactor this into LLMStream.collect API.\n const promptCtx = new ChatContext();\n promptCtx.addMessage({\n role: 'system',\n content:\n 'Compress older chat history into a short, faithful summary.\\n' +\n 'Focus on user goals, constraints, decisions, key facts/preferences/entities, and pending tasks.\\n' +\n 'Exclude chit-chat and greetings. Be concise.',\n });\n promptCtx.addMessage({\n role: 'user',\n content: `Conversation to summarize:\\n\\n${sourceText}`,\n });\n\n const chunks: string[] = [];\n for await (const chunk of llm.chat({ chatCtx: promptCtx })) {\n if (chunk.delta?.content) {\n chunks.push(chunk.delta.content);\n }\n }\n\n const summary = chunks.join('').trim();\n if (!summary) {\n return this;\n }\n\n const tailStartTs = tail.length > 0 ? tail[0]!.createdAt : Infinity;\n\n const preserved: ChatItem[] = [];\n for (const it of this._items) {\n if (\n (it.type === 'function_call' || it.type === 'function_call_output') &&\n it.createdAt < tailStartTs\n ) {\n continue;\n }\n\n if (it.type === 'message' && (it.role === 'user' || it.role === 'assistant')) {\n continue;\n }\n\n preserved.push(it);\n }\n\n this._items = preserved;\n\n const createdAtHint =\n tail.length > 0 ? tail[0]!.createdAt - 1e-3 : head[head.length - 1]!.createdAt + 1e-3;\n\n this.addMessage({\n role: 'assistant',\n content: `[history summary]\\n${summary}`,\n createdAt: createdAtHint,\n extra: { is_summary: true },\n });\n\n for (const msg of tail) {\n this.insert(msg);\n }\n\n return this;\n }\n\n /**\n * Indicates whether the context is read-only\n */\n get readonly(): boolean {\n return false;\n }\n}\n\nexport class ReadonlyChatContext extends ChatContext {\n static readonly errorMsg =\n 'Please use .copy() and agent.update_chat_ctx() to modify the chat context.';\n\n constructor(items: ChatItem[]) {\n super(createImmutableArray(items, ReadonlyChatContext.errorMsg));\n }\n\n get items(): ChatItem[] {\n return this._items;\n }\n\n set items(items: ChatItem[]) {\n throw new Error(\n `Cannot set items on a read-only chat context. ${ReadonlyChatContext.errorMsg}`,\n );\n }\n\n get readonly(): boolean {\n return true;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/llm/chat_context.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame, VideoFrame } from '@livekit/rtc-node';\nimport { createImmutableArray, shortuuid } from '../utils.js';\nimport type { LLM } from './llm.js';\nimport { type ProviderFormat, toChatCtx } from './provider_format/index.js';\nimport type { JSONObject, JSONValue, ToolContext } from './tool_context.js';\n\nexport type ChatRole = 'developer' | 'system' | 'user' | 'assistant';\nexport interface ImageContent {\n id: string;\n\n type: 'image_content';\n\n /**\n * Either a string URL or a VideoFrame object.\n */\n image: string | VideoFrame;\n\n inferenceDetail: 'auto' | 'high' | 'low';\n\n inferenceWidth?: number;\n\n inferenceHeight?: number;\n\n mimeType?: string;\n\n _cache: Record<any, any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n}\n\nexport interface AudioContent {\n type: 'audio_content';\n\n frame: AudioFrame[];\n\n transcript?: string;\n}\n\nexport type ChatContent = ImageContent | AudioContent | string;\n\nexport function createImageContent(params: {\n image: string | VideoFrame;\n id?: string;\n inferenceDetail?: 'auto' | 'high' | 'low';\n inferenceWidth?: number;\n inferenceHeight?: number;\n mimeType?: string;\n}): ImageContent {\n const {\n image,\n id = shortuuid('img_'),\n inferenceDetail = 'auto',\n inferenceWidth,\n inferenceHeight,\n mimeType,\n } = params;\n\n return {\n id,\n type: 'image_content',\n image,\n inferenceDetail,\n inferenceWidth,\n inferenceHeight,\n mimeType,\n _cache: {},\n };\n}\n\nexport function createAudioContent(params: {\n frame: AudioFrame[];\n transcript?: string;\n}): AudioContent {\n const { frame, transcript } = params;\n\n return {\n type: 'audio_content',\n frame,\n transcript,\n };\n}\n\nexport interface MetricsReport {\n startedSpeakingAt?: number;\n stoppedSpeakingAt?: number;\n transcriptionDelay?: number;\n endOfTurnDelay?: number;\n onUserTurnCompletedDelay?: number;\n llmNodeTtft?: number;\n ttsNodeTtfb?: number;\n e2eLatency?: number;\n}\n\nexport class ChatMessage {\n readonly id: string;\n\n readonly type = 'message' as const;\n\n readonly role: ChatRole;\n\n content: ChatContent[];\n\n interrupted: boolean;\n\n transcriptConfidence?: number;\n\n extra: Record<string, unknown>;\n\n metrics: MetricsReport;\n\n hash?: Uint8Array;\n\n createdAt: number;\n\n constructor(params: {\n role: ChatRole;\n content: ChatContent[] | string;\n id?: string;\n interrupted?: boolean;\n createdAt?: number;\n transcriptConfidence?: number;\n metrics?: MetricsReport;\n extra?: Record<string, unknown>;\n }) {\n const {\n role,\n content,\n id = shortuuid('item_'),\n interrupted = false,\n createdAt = Date.now(),\n transcriptConfidence,\n metrics = {},\n extra = {},\n } = params;\n this.id = id;\n this.role = role;\n this.content = Array.isArray(content) ? content : [content];\n this.interrupted = interrupted;\n this.createdAt = createdAt;\n this.transcriptConfidence = transcriptConfidence;\n this.metrics = metrics;\n this.extra = extra;\n }\n\n static create(params: {\n role: ChatRole;\n content: ChatContent[] | string;\n id?: string;\n interrupted?: boolean;\n createdAt?: number;\n transcriptConfidence?: number;\n metrics?: MetricsReport;\n extra?: Record<string, unknown>;\n }) {\n return new ChatMessage(params);\n }\n\n /**\n * Returns a single string with all text parts of the message joined by new\n * lines. If no string content is present, returns `null`.\n */\n get textContent(): string | undefined {\n const parts = this.content.filter((c): c is string => typeof c === 'string');\n return parts.length > 0 ? parts.join('\\n') : undefined;\n }\n\n toJSONContent(): JSONValue[] {\n return this.content.map((c) => {\n if (typeof c === 'string') {\n return c as JSONValue;\n } else if (c.type === 'image_content') {\n return {\n id: c.id,\n type: c.type,\n image: c.image,\n inferenceDetail: c.inferenceDetail,\n inferenceWidth: c.inferenceWidth,\n inferenceHeight: c.inferenceHeight,\n mimeType: c.mimeType,\n } as JSONObject;\n } else {\n return {\n type: c.type,\n transcript: c.transcript,\n } as JSONObject;\n }\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n role: this.role,\n content: this.toJSONContent(),\n interrupted: this.interrupted,\n };\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n if (this.transcriptConfidence !== undefined) {\n result.transcriptConfidence = this.transcriptConfidence;\n }\n if (Object.keys(this.metrics).length > 0) {\n result.metrics = { ...this.metrics };\n }\n if (Object.keys(this.extra).length > 0) {\n result.extra = this.extra as JSONValue;\n }\n\n return result;\n }\n}\n\nexport class FunctionCall {\n readonly id: string;\n\n readonly type = 'function_call' as const;\n\n callId: string;\n\n args: string;\n\n name: string;\n\n createdAt: number;\n\n extra: Record<string, unknown>;\n /**\n * Optional grouping identifier for parallel tool calls.\n */\n groupId?: string;\n\n /**\n * Opaque signature for Gemini thinking mode.\n * When using Gemini 3+ models with thinking enabled, this signature must be\n * preserved and returned with function responses to maintain thought context.\n */\n thoughtSignature?: string;\n\n constructor(params: {\n callId: string;\n name: string;\n args: string;\n id?: string;\n createdAt?: number;\n extra?: Record<string, unknown>;\n groupId?: string;\n thoughtSignature?: string;\n }) {\n const {\n callId,\n name,\n args,\n id = shortuuid('item_'),\n createdAt = Date.now(),\n extra = {},\n groupId,\n thoughtSignature,\n } = params;\n this.id = id;\n this.callId = callId;\n this.args = args;\n this.name = name;\n this.createdAt = createdAt;\n this.extra = { ...extra };\n this.groupId = groupId;\n this.thoughtSignature =\n thoughtSignature ??\n (typeof this.extra.google === 'object' && this.extra.google !== null\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this.extra.google as any).thoughtSignature ||\n (this.extra.google as any).thought_signature\n : undefined);\n }\n\n static create(params: {\n callId: string;\n name: string;\n args: string;\n id?: string;\n createdAt?: number;\n extra?: Record<string, unknown>;\n groupId?: string;\n thoughtSignature?: string;\n }) {\n return new FunctionCall(params);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n callId: this.callId,\n name: this.name,\n args: this.args,\n };\n\n if (Object.keys(this.extra).length > 0) {\n result.extra = this.extra as JSONValue;\n }\n\n if (this.groupId) {\n result.groupId = this.groupId;\n }\n\n if (this.thoughtSignature) {\n result.thoughtSignature = this.thoughtSignature;\n }\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n return result;\n }\n}\n\nexport class FunctionCallOutput {\n readonly id: string;\n\n readonly type = 'function_call_output' as const;\n\n name = '';\n\n callId: string;\n\n output: string;\n\n isError: boolean;\n\n createdAt: number;\n\n constructor(params: {\n callId: string;\n output: string;\n isError: boolean;\n id?: string;\n createdAt?: number;\n name?: string;\n }) {\n const {\n callId,\n output,\n isError,\n id = shortuuid('item_'),\n createdAt = Date.now(),\n name = '',\n } = params;\n this.id = id;\n this.callId = callId;\n this.output = output;\n this.isError = isError;\n this.name = name;\n this.createdAt = createdAt;\n }\n\n static create(params: {\n callId: string;\n output: string;\n isError: boolean;\n id?: string;\n createdAt?: number;\n name?: string;\n }) {\n return new FunctionCallOutput(params);\n }\n\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n name: this.name,\n callId: this.callId,\n output: this.output,\n isError: this.isError,\n };\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n return result;\n }\n}\n\nexport class AgentHandoffItem {\n readonly id: string;\n\n readonly type = 'agent_handoff' as const;\n\n oldAgentId: string | undefined;\n\n newAgentId: string;\n\n createdAt: number;\n\n constructor(params: {\n oldAgentId?: string;\n newAgentId: string;\n id?: string;\n createdAt?: number;\n }) {\n const { oldAgentId, newAgentId, id = shortuuid('item_'), createdAt = Date.now() } = params;\n this.id = id;\n this.oldAgentId = oldAgentId;\n this.newAgentId = newAgentId;\n this.createdAt = createdAt;\n }\n\n static create(params: {\n oldAgentId?: string;\n newAgentId: string;\n id?: string;\n createdAt?: number;\n }) {\n return new AgentHandoffItem(params);\n }\n\n toJSON(excludeTimestamp: boolean = false): JSONValue {\n const result: JSONValue = {\n id: this.id,\n type: this.type,\n newAgentId: this.newAgentId,\n };\n\n if (this.oldAgentId !== undefined) {\n result.oldAgentId = this.oldAgentId;\n }\n\n if (!excludeTimestamp) {\n result.createdAt = this.createdAt;\n }\n\n return result;\n }\n}\n\n// TODO(parity): Add AgentConfigUpdate type to ChatItem union\nexport type ChatItem = ChatMessage | FunctionCall | FunctionCallOutput | AgentHandoffItem;\n\nexport class ChatContext {\n protected _items: ChatItem[];\n\n constructor(items?: ChatItem[]) {\n this._items = items ? items : [];\n }\n\n static empty(): ChatContext {\n return new ChatContext([]);\n }\n\n get items(): ChatItem[] {\n return this._items;\n }\n\n set items(items: ChatItem[]) {\n this._items = items;\n }\n\n /**\n * Add a new message to the context and return it.\n */\n addMessage(params: {\n role: ChatRole;\n content: ChatContent[] | string;\n id?: string;\n interrupted?: boolean;\n createdAt?: number;\n transcriptConfidence?: number;\n metrics?: MetricsReport;\n extra?: Record<string, unknown>;\n }): ChatMessage {\n const msg = new ChatMessage(params);\n if (params.createdAt !== undefined) {\n const idx = this.findInsertionIndex(params.createdAt);\n this._items.splice(idx, 0, msg);\n } else {\n this._items.push(msg);\n }\n return msg;\n }\n\n /**\n * Insert a single item or multiple items based on their `createdAt` field so\n * that the array keeps its chronological order.\n */\n insert(item: ChatItem | ChatItem[]): void {\n const arr = Array.isArray(item) ? item : [item];\n for (const it of arr) {\n const idx = this.findInsertionIndex(it.createdAt);\n this._items.splice(idx, 0, it);\n }\n }\n\n getById(itemId: string): ChatItem | undefined {\n return this._items.find((i) => i.id === itemId);\n }\n\n indexById(itemId: string): number | undefined {\n const idx = this._items.findIndex((i) => i.id === itemId);\n return idx !== -1 ? idx : undefined;\n }\n\n // TODO(parity): Add excludeConfigUpdate option when AgentConfigUpdate is ported\n copy(\n options: {\n excludeFunctionCall?: boolean;\n excludeInstructions?: boolean;\n excludeEmptyMessage?: boolean;\n excludeHandoff?: boolean;\n toolCtx?: ToolContext<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n } = {},\n ): ChatContext {\n const {\n excludeFunctionCall = false,\n excludeInstructions = false,\n excludeEmptyMessage = false,\n excludeHandoff = false,\n toolCtx,\n } = options;\n const items: ChatItem[] = [];\n\n const isToolCallOrOutput = (item: ChatItem): item is FunctionCall | FunctionCallOutput =>\n ['function_call', 'function_call_output'].includes(item.type);\n const isChatMessage = (item: ChatItem): item is ChatMessage => item.type === 'message';\n\n for (const item of this._items) {\n if (excludeFunctionCall && isToolCallOrOutput(item)) {\n continue;\n }\n\n if (\n excludeInstructions &&\n isChatMessage(item) &&\n ['system', 'developer'].includes(item.role)\n ) {\n continue;\n }\n\n if (excludeEmptyMessage && isChatMessage(item) && item.content.length === 0) {\n continue;\n }\n\n if (excludeHandoff && item.type === 'agent_handoff') {\n continue;\n }\n\n if (toolCtx !== undefined && isToolCallOrOutput(item) && toolCtx[item.name] === undefined) {\n continue;\n }\n\n items.push(item);\n }\n\n return new ChatContext(items);\n }\n\n // TODO(parity): Add excludeConfigUpdate option when AgentConfigUpdate is ported\n merge(\n other: ChatContext,\n options: {\n excludeFunctionCall?: boolean;\n excludeInstructions?: boolean;\n } = {},\n ): ChatContext {\n const { excludeFunctionCall = false, excludeInstructions = false } = options;\n const existingIds = new Set(this._items.map((item) => item.id));\n\n for (const item of other.items) {\n if (excludeFunctionCall && ['function_call', 'function_call_output'].includes(item.type)) {\n continue;\n }\n\n if (\n excludeInstructions &&\n item.type === 'message' &&\n (item.role === 'system' || item.role === 'developer')\n ) {\n continue;\n }\n\n if (existingIds.has(item.id)) {\n continue;\n }\n\n const idx = this.findInsertionIndex(item.createdAt);\n this._items.splice(idx, 0, item);\n existingIds.add(item.id);\n }\n\n return this;\n }\n\n truncate(maxItems: number): ChatContext {\n if (maxItems <= 0) return this;\n\n const instructions = this._items.find((i) => i.type === 'message' && i.role === 'system') as\n | ChatMessage\n | undefined;\n\n let newItems = this._items.slice(-maxItems);\n\n // Ensure the first item is not a function-call artefact.\n while (\n newItems.length > 0 &&\n ['function_call', 'function_call_output'].includes(newItems[0]!.type)\n ) {\n newItems.shift();\n }\n\n if (instructions) {\n // At this point `instructions` is defined, so it is safe to pass to `includes`.\n if (!newItems.includes(instructions)) {\n newItems = [instructions, ...newItems];\n }\n }\n\n // replace the items in place to keep the reference\n this._items.splice(0, this._items.length, ...newItems);\n return this;\n }\n\n toJSON(\n options: {\n excludeImage?: boolean;\n excludeAudio?: boolean;\n excludeTimestamp?: boolean;\n excludeFunctionCall?: boolean;\n } = {},\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): JSONObject {\n const {\n excludeImage = true,\n excludeAudio = true,\n excludeTimestamp = true,\n excludeFunctionCall = false,\n } = options;\n\n const items: ChatItem[] = [];\n\n for (const item of this._items) {\n let processedItem = item;\n\n if (excludeFunctionCall && ['function_call', 'function_call_output'].includes(item.type)) {\n continue;\n }\n\n if (item.type === 'message') {\n processedItem = ChatMessage.create({\n role: item.role,\n content: item.content,\n id: item.id,\n interrupted: item.interrupted,\n createdAt: item.createdAt,\n transcriptConfidence: item.transcriptConfidence,\n metrics: item.metrics,\n extra: item.extra,\n });\n\n // Filter content based on options\n if (excludeImage) {\n processedItem.content = processedItem.content.filter((c) => {\n return !(typeof c === 'object' && c.type === 'image_content');\n });\n }\n\n if (excludeAudio) {\n processedItem.content = processedItem.content.filter((c) => {\n return !(typeof c === 'object' && c.type === 'audio_content');\n });\n }\n }\n\n items.push(processedItem);\n }\n\n return {\n items: items.map((item) => item.toJSON(excludeTimestamp)),\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async toProviderFormat(format: ProviderFormat, injectDummyUserMessage: boolean = true) {\n return await toChatCtx(format, this, injectDummyUserMessage);\n }\n\n /**\n * Internal helper used by `truncate` & `addMessage` to find the correct\n * insertion index for a timestamp so the list remains sorted.\n */\n private findInsertionIndex(createdAt: number): number {\n for (let i = this._items.length - 1; i >= 0; i -= 1) {\n const item = this._items[i];\n if (item!.createdAt <= createdAt) {\n return i + 1;\n }\n }\n return 0;\n }\n\n /**\n * Return true if `other` has the same sequence of items with matching\n * essential fields (IDs, types, and payload) as this context.\n *\n * Comparison rules:\n * - Messages: compares the full `content` list, `role` and `interrupted`.\n * - Function calls: compares `name`, `callId`, and `args`.\n * - Function call outputs: compares `name`, `callId`, `output`, and `isError`.\n *\n * Does not consider timestamps or other metadata.\n */\n isEquivalent(other: ChatContext): boolean {\n if (this === other) {\n return true;\n }\n\n if (this.items.length !== other.items.length) {\n return false;\n }\n\n for (let i = 0; i < this.items.length; i++) {\n const a = this.items[i]!;\n const b = other.items[i]!;\n\n if (a.id !== b.id || a.type !== b.type) {\n return false;\n }\n\n if (a.type === 'message' && b.type === 'message') {\n if (\n a.role !== b.role ||\n a.interrupted !== b.interrupted ||\n !this.compareContent(a.content, b.content)\n ) {\n return false;\n }\n } else if (a.type === 'function_call' && b.type === 'function_call') {\n if (\n a.name !== b.name ||\n a.callId !== b.callId ||\n a.args !== b.args ||\n a.thoughtSignature !== b.thoughtSignature ||\n a.groupId !== b.groupId ||\n JSON.stringify(a.extra) !== JSON.stringify(b.extra)\n ) {\n return false;\n }\n } else if (a.type === 'function_call_output' && b.type === 'function_call_output') {\n if (\n a.name !== b.name ||\n a.callId !== b.callId ||\n a.output !== b.output ||\n a.isError !== b.isError\n ) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n /**\n * Compare two content arrays for equality.\n */\n private compareContent(a: ChatContent[], b: ChatContent[]): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n for (let i = 0; i < a.length; i++) {\n const contentA = a[i]!;\n const contentB = b[i]!;\n\n if (typeof contentA === 'string' && typeof contentB === 'string') {\n if (contentA !== contentB) {\n return false;\n }\n continue;\n }\n\n if (typeof contentA !== typeof contentB) {\n return false;\n }\n\n if (typeof contentA === 'object' && typeof contentB === 'object') {\n if (contentA.type === 'image_content' && contentB.type === 'image_content') {\n if (\n contentA.id !== contentB.id ||\n contentA.image !== contentB.image ||\n contentA.inferenceDetail !== contentB.inferenceDetail ||\n contentA.inferenceWidth !== contentB.inferenceWidth ||\n contentA.inferenceHeight !== contentB.inferenceHeight ||\n contentA.mimeType !== contentB.mimeType\n ) {\n return false;\n }\n } else if (contentA.type === 'audio_content' && contentB.type === 'audio_content') {\n if (contentA.frame.length !== contentB.frame.length) {\n return false;\n }\n if (contentA.transcript !== contentB.transcript) {\n return false;\n }\n } else {\n return false;\n }\n }\n }\n\n return true;\n }\n\n async _summarize(llm: LLM, options: { keepLastTurns?: number } = {}): Promise<ChatContext> {\n const { keepLastTurns = 2 } = options;\n\n // Split the history into a head/tail over the full item stream so recent\n // tool calls/outputs stay attached to the turns they belong to.\n const msgBudget = keepLastTurns * 2;\n let splitIdx = this._items.length;\n\n if (msgBudget > 0) {\n let msgCount = 0;\n let foundSplit = false;\n for (let i = this._items.length - 1; i >= 0; i -= 1) {\n const item = this._items[i]!;\n if (item.type === 'message' && (item.role === 'user' || item.role === 'assistant')) {\n msgCount += 1;\n if (msgCount >= msgBudget) {\n splitIdx = i;\n foundSplit = true;\n break;\n }\n }\n }\n\n if (!foundSplit) {\n return this;\n }\n }\n\n if (splitIdx === 0) {\n return this;\n }\n\n const headItems = this._items.slice(0, splitIdx);\n const tailItems = this._items.slice(splitIdx);\n\n const toSummarize: Array<ChatMessage | FunctionCall | FunctionCallOutput> = [];\n for (const item of headItems) {\n if (item.type === 'message') {\n if (item.role !== 'user' && item.role !== 'assistant') continue;\n if (item.extra?.is_summary === true) continue;\n\n const text = (item.textContent ?? '').trim();\n if (text) {\n toSummarize.push(item);\n }\n } else if (item.type === 'function_call' || item.type === 'function_call_output') {\n toSummarize.push(item);\n }\n }\n\n if (toSummarize.length === 0) {\n return this;\n }\n\n const sourceText = toSummarize\n .map((item) => {\n if (item.type === 'message') {\n return toXml(item.role, (item.textContent ?? '').trim());\n }\n\n return functionCallItemToMessage(item).textContent ?? '';\n })\n .join('\\n')\n .trim();\n\n if (!sourceText) {\n return this;\n }\n\n // TODO: refactor this into LLMStream.collect API.\n const promptCtx = new ChatContext();\n promptCtx.addMessage({\n role: 'system',\n content: [\n 'Compress older conversation history into a short, faithful summary.',\n '',\n 'The conversation is formatted as XML. Here is how to read it:',\n '- <user>...</user> - something the user said.',\n '- <assistant>...</assistant> - something the assistant said.',\n '- <function_call name=\"...\" call_id=\"...\">...</function_call> - the assistant invoked an action.',\n '- <function_call_output name=\"...\" call_id=\"...\">...</function_call_output> - the result of that action. May contain <error>...</error> if it failed.',\n '',\n 'Guidelines:',\n '- Distill the information learned from function call outputs into the summary. Do not mention that a tool or function was called; just preserve the knowledge gained.',\n '- Focus on user goals, constraints, decisions, key facts, preferences, entities, and any pending or unresolved tasks.',\n '- Omit greetings, filler, and chit-chat.',\n '- Be concise.',\n ].join('\\n'),\n });\n promptCtx.addMessage({\n role: 'user',\n content: `Conversation to summarize:\\n\\n${sourceText}`,\n });\n\n const chunks: string[] = [];\n for await (const chunk of llm.chat({ chatCtx: promptCtx })) {\n if (chunk.delta?.content) {\n chunks.push(chunk.delta.content);\n }\n }\n\n const summary = chunks.join('').trim();\n if (!summary) {\n return this;\n }\n\n const preserved: ChatItem[] = [];\n for (const it of headItems) {\n if (it.type === 'message' && (it.role === 'user' || it.role === 'assistant')) {\n continue;\n }\n\n if (it.type === 'function_call' || it.type === 'function_call_output') {\n continue;\n }\n\n preserved.push(it);\n }\n\n this._items = preserved;\n\n const createdAtHint =\n tailItems.length > 0\n ? tailItems[0]!.createdAt - 1e-6\n : headItems[headItems.length - 1]!.createdAt + 1e-6;\n\n this.addMessage({\n role: 'assistant',\n content: toXml('chat_history_summary', summary),\n createdAt: createdAtHint,\n extra: { is_summary: true },\n });\n\n this._items.push(...tailItems);\n\n return this;\n }\n\n /**\n * Indicates whether the context is read-only\n */\n get readonly(): boolean {\n return false;\n }\n}\n\nfunction toAttrsStr(attrs?: Record<string, unknown>): string | undefined {\n if (!attrs) {\n return undefined;\n }\n\n return Object.entries(attrs)\n .map(([key, value]) => `${key}=\"${String(value)}\"`)\n .join(' ');\n}\n\nfunction toXml(tagName: string, content?: string, attrs?: Record<string, unknown>): string {\n const attrsStr = toAttrsStr(attrs);\n if (content) {\n return [attrsStr ? `<${tagName} ${attrsStr}>` : `<${tagName}>`, content, `</${tagName}>`].join(\n '\\n',\n );\n }\n\n return attrsStr ? `<${tagName} ${attrsStr} />` : `<${tagName} />`;\n}\n\nfunction functionCallItemToMessage(item: FunctionCall | FunctionCallOutput): ChatMessage {\n if (item.type === 'function_call') {\n return new ChatMessage({\n role: 'user',\n content: [\n toXml('function_call', item.args, {\n name: item.name,\n call_id: item.callId,\n }),\n ],\n createdAt: item.createdAt,\n extra: { is_function_call: true },\n });\n }\n\n return new ChatMessage({\n role: 'assistant',\n content: [\n toXml('function_call_output', item.isError ? toXml('error', item.output) : item.output, {\n name: item.name,\n call_id: item.callId,\n }),\n ],\n createdAt: item.createdAt,\n extra: { is_function_call_output: true },\n });\n}\n\nexport class ReadonlyChatContext extends ChatContext {\n static readonly errorMsg =\n 'Please use .copy() and agent.update_chat_ctx() to modify the chat context.';\n\n constructor(items: ChatItem[]) {\n super(createImmutableArray(items, ReadonlyChatContext.errorMsg));\n }\n\n get items(): ChatItem[] {\n return this._items;\n }\n\n set items(items: ChatItem[]) {\n throw new Error(\n `Cannot set items on a read-only chat context. ${ReadonlyChatContext.errorMsg}`,\n );\n }\n\n get readonly(): boolean {\n return true;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,6 +4,14 @@ var import_log = require("../log.cjs");
|
|
|
4
4
|
var import_fake_llm = require("../voice/testing/fake_llm.cjs");
|
|
5
5
|
var import_chat_context = require("./chat_context.cjs");
|
|
6
6
|
(0, import_log.initializeLogger)({ pretty: false, level: "error" });
|
|
7
|
+
const summaryXml = (summary) => ["<chat_history_summary>", summary, "</chat_history_summary>"].join("\n");
|
|
8
|
+
class TrackingFakeLLM extends import_fake_llm.FakeLLM {
|
|
9
|
+
chatCalls = 0;
|
|
10
|
+
chat(...args) {
|
|
11
|
+
this.chatCalls += 1;
|
|
12
|
+
return super.chat(...args);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
7
15
|
(0, import_vitest.describe)("ChatContext.toJSON", () => {
|
|
8
16
|
(0, import_vitest.it)("should match snapshot for empty context", () => {
|
|
9
17
|
const context = new import_chat_context.ChatContext();
|
|
@@ -236,25 +244,48 @@ var import_chat_context = require("./chat_context.cjs");
|
|
|
236
244
|
});
|
|
237
245
|
});
|
|
238
246
|
(0, import_vitest.describe)("ChatContext._summarize", () => {
|
|
239
|
-
(0, import_vitest.it)("
|
|
247
|
+
(0, import_vitest.it)("includes function calls in the summarization source and keeps chronological order", async () => {
|
|
240
248
|
const ctx = new import_chat_context.ChatContext();
|
|
241
249
|
ctx.addMessage({ role: "system", content: "System prompt", createdAt: 0 });
|
|
242
250
|
ctx.addMessage({ role: "user", content: "hello", createdAt: 1e3 });
|
|
243
251
|
ctx.addMessage({ role: "assistant", content: "hi there", createdAt: 2e3 });
|
|
252
|
+
ctx.insert(
|
|
253
|
+
import_chat_context.FunctionCall.create({
|
|
254
|
+
callId: "call_1",
|
|
255
|
+
name: "lookup",
|
|
256
|
+
args: '{"order":"123"}',
|
|
257
|
+
createdAt: 2500
|
|
258
|
+
})
|
|
259
|
+
);
|
|
244
260
|
ctx.insert(
|
|
245
261
|
new import_chat_context.FunctionCallOutput({
|
|
246
262
|
callId: "call_1",
|
|
247
263
|
name: "lookup",
|
|
248
|
-
output: '{"
|
|
264
|
+
output: '{"status":"delivered"}',
|
|
249
265
|
isError: false,
|
|
250
|
-
createdAt:
|
|
266
|
+
createdAt: 2600
|
|
251
267
|
})
|
|
252
268
|
);
|
|
253
269
|
ctx.addMessage({ role: "user", content: "my color is blue", createdAt: 3e3 });
|
|
254
270
|
ctx.addMessage({ role: "assistant", content: "noted", createdAt: 4e3 });
|
|
255
271
|
const fake = new import_fake_llm.FakeLLM([
|
|
256
272
|
{
|
|
257
|
-
input:
|
|
273
|
+
input: [
|
|
274
|
+
"Conversation to summarize:",
|
|
275
|
+
"",
|
|
276
|
+
"<user>",
|
|
277
|
+
"hello",
|
|
278
|
+
"</user>",
|
|
279
|
+
"<assistant>",
|
|
280
|
+
"hi there",
|
|
281
|
+
"</assistant>",
|
|
282
|
+
'<function_call name="lookup" call_id="call_1">',
|
|
283
|
+
'{"order":"123"}',
|
|
284
|
+
"</function_call>",
|
|
285
|
+
'<function_call_output name="lookup" call_id="call_1">',
|
|
286
|
+
'{"status":"delivered"}',
|
|
287
|
+
"</function_call_output>"
|
|
288
|
+
].join("\n"),
|
|
258
289
|
content: "condensed head"
|
|
259
290
|
}
|
|
260
291
|
]);
|
|
@@ -269,11 +300,98 @@ var import_chat_context = require("./chat_context.cjs");
|
|
|
269
300
|
if (!summary || summary.type !== "message") {
|
|
270
301
|
throw new Error("summary message is missing");
|
|
271
302
|
}
|
|
272
|
-
(0, import_vitest.expect)(summary.
|
|
303
|
+
(0, import_vitest.expect)(summary.textContent).toBe(summaryXml("condensed head"));
|
|
304
|
+
(0, import_vitest.expect)(summary.createdAt).toBeCloseTo(2999.999999, 6);
|
|
305
|
+
(0, import_vitest.expect)(ctx.items.filter((item) => item.type === "function_call")).toHaveLength(0);
|
|
306
|
+
(0, import_vitest.expect)(ctx.items.filter((item) => item.type === "function_call_output")).toHaveLength(0);
|
|
307
|
+
const createdAts = ctx.items.map((item) => item.createdAt);
|
|
308
|
+
const sorted = [...createdAts].sort((a, b) => a - b);
|
|
309
|
+
(0, import_vitest.expect)(createdAts).toEqual(sorted);
|
|
310
|
+
});
|
|
311
|
+
(0, import_vitest.it)("preserves interleaved tool items that belong to the recent tail", async () => {
|
|
312
|
+
const ctx = new import_chat_context.ChatContext();
|
|
313
|
+
ctx.addMessage({ role: "system", content: "System prompt", createdAt: 0 });
|
|
314
|
+
ctx.addMessage({ role: "user", content: "my earbuds are broken", createdAt: 1e3 });
|
|
315
|
+
ctx.addMessage({
|
|
316
|
+
role: "assistant",
|
|
317
|
+
content: "Can you share your order number?",
|
|
318
|
+
createdAt: 2e3
|
|
319
|
+
});
|
|
320
|
+
ctx.addMessage({ role: "user", content: "Order #123", createdAt: 3e3 });
|
|
321
|
+
ctx.insert(
|
|
322
|
+
import_chat_context.FunctionCall.create({
|
|
323
|
+
callId: "call_2",
|
|
324
|
+
name: "lookup_order",
|
|
325
|
+
args: '{"order":"123"}',
|
|
326
|
+
createdAt: 3500
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
ctx.insert(
|
|
330
|
+
new import_chat_context.FunctionCallOutput({
|
|
331
|
+
callId: "call_2",
|
|
332
|
+
name: "lookup_order",
|
|
333
|
+
output: '{"status":"delivered"}',
|
|
334
|
+
isError: false,
|
|
335
|
+
createdAt: 3600
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
ctx.addMessage({
|
|
339
|
+
role: "assistant",
|
|
340
|
+
content: "Found your order. Let me check the warranty.",
|
|
341
|
+
createdAt: 4e3
|
|
342
|
+
});
|
|
343
|
+
ctx.addMessage({ role: "user", content: "Thanks.", createdAt: 5e3 });
|
|
344
|
+
ctx.addMessage({ role: "assistant", content: "You are under warranty.", createdAt: 6e3 });
|
|
345
|
+
const fake = new import_fake_llm.FakeLLM([
|
|
346
|
+
{
|
|
347
|
+
input: [
|
|
348
|
+
"Conversation to summarize:",
|
|
349
|
+
"",
|
|
350
|
+
"<user>",
|
|
351
|
+
"my earbuds are broken",
|
|
352
|
+
"</user>",
|
|
353
|
+
"<assistant>",
|
|
354
|
+
"Can you share your order number?",
|
|
355
|
+
"</assistant>"
|
|
356
|
+
].join("\n"),
|
|
357
|
+
content: "older summary"
|
|
358
|
+
}
|
|
359
|
+
]);
|
|
360
|
+
await ctx._summarize(fake, { keepLastTurns: 2 });
|
|
361
|
+
const functionItems = ctx.items.filter(
|
|
362
|
+
(item) => item.type === "function_call" || item.type === "function_call_output"
|
|
363
|
+
);
|
|
364
|
+
(0, import_vitest.expect)(functionItems).toHaveLength(2);
|
|
365
|
+
(0, import_vitest.expect)(functionItems.map((item) => item.createdAt)).toEqual([3500, 3600]);
|
|
366
|
+
const rawTailMessages = ctx.items.filter(
|
|
367
|
+
(item) => {
|
|
368
|
+
var _a;
|
|
369
|
+
return item.type === "message" && (item.role === "user" || item.role === "assistant") && ((_a = item.extra) == null ? void 0 : _a.is_summary) !== true;
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
(0, import_vitest.expect)(rawTailMessages).toHaveLength(4);
|
|
373
|
+
(0, import_vitest.expect)(rawTailMessages.map((item) => item.textContent)).toEqual([
|
|
374
|
+
"Order #123",
|
|
375
|
+
"Found your order. Let me check the warranty.",
|
|
376
|
+
"Thanks.",
|
|
377
|
+
"You are under warranty."
|
|
378
|
+
]);
|
|
273
379
|
const createdAts = ctx.items.map((item) => item.createdAt);
|
|
274
380
|
const sorted = [...createdAts].sort((a, b) => a - b);
|
|
275
381
|
(0, import_vitest.expect)(createdAts).toEqual(sorted);
|
|
276
382
|
});
|
|
383
|
+
(0, import_vitest.it)("skips summarization when the recent-turn budget already covers the history", async () => {
|
|
384
|
+
const ctx = new import_chat_context.ChatContext();
|
|
385
|
+
ctx.addMessage({ role: "system", content: "System prompt", createdAt: 0 });
|
|
386
|
+
ctx.addMessage({ role: "user", content: "hello", createdAt: 1e3 });
|
|
387
|
+
ctx.addMessage({ role: "assistant", content: "hi there", createdAt: 2e3 });
|
|
388
|
+
const llm = new TrackingFakeLLM();
|
|
389
|
+
const originalIds = ctx.items.map((item) => item.id);
|
|
390
|
+
const result = await ctx._summarize(llm, { keepLastTurns: 1 });
|
|
391
|
+
(0, import_vitest.expect)(result).toBe(ctx);
|
|
392
|
+
(0, import_vitest.expect)(llm.chatCalls).toBe(0);
|
|
393
|
+
(0, import_vitest.expect)(ctx.items.map((item) => item.id)).toEqual(originalIds);
|
|
394
|
+
});
|
|
277
395
|
});
|
|
278
396
|
(0, import_vitest.describe)("ReadonlyChatContext with immutable array", () => {
|
|
279
397
|
(0, import_vitest.it)("should have readonly property set to true", () => {
|