UNPKG

@openai/agents-openai

Version:

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

328 lines 12.1 kB
import { UserError, } from '@openai/agents-core'; export function convertToolChoice(toolChoice) { if (toolChoice == undefined || toolChoice == null) return undefined; if (toolChoice === 'auto') return 'auto'; if (toolChoice === 'required') return 'required'; if (toolChoice === 'none') return 'none'; return { type: 'function', function: { name: toolChoice }, }; } export function extractAllAssistantContent(content) { if (typeof content === 'string') { return content; } const out = []; for (const c of content) { if (c.type === 'output_text') { out.push({ type: 'text', text: c.text, ...c.providerData, }); } else if (c.type === 'refusal') { out.push({ type: 'refusal', refusal: c.refusal, ...c.providerData, }); } else if (c.type === 'audio' || c.type === 'image') { // ignoring audio as it is handled on the assistant message level continue; } else { const exhaustive = c; // ensures that the type is exhaustive throw new Error(`Unknown content: ${JSON.stringify(exhaustive)}`); } } return out; } export function extractAllUserContent(content) { if (typeof content === 'string') { return content; } const out = []; for (const c of content) { if (c.type === 'input_text') { out.push({ type: 'text', text: c.text, ...c.providerData }); } else if (c.type === 'input_image') { // The Chat Completions API only accepts image URLs. If we see a file reference we reject it // early so callers get an actionable error instead of a cryptic API response. const imageSource = typeof c.image === 'string' ? c.image : typeof c.imageUrl === 'string' ? c.imageUrl : undefined; if (!imageSource) { throw new Error(`Only image URLs are supported for input_image: ${JSON.stringify(c)}`); } const { image_url, ...rest } = c.providerData || {}; out.push({ type: 'image_url', image_url: { url: imageSource, ...image_url, }, ...rest, }); } else if (c.type === 'input_file') { // Chat Completions API supports file inputs via the "file" content part type. // See: https://platform.openai.com/docs/guides/pdf-files?api-mode=chat const file = {}; if (typeof c.file === 'string') { const value = c.file.trim(); if (value.startsWith('data:')) { file.file_data = value; } else { throw new UserError(`Chat Completions only supports data URLs for file input. If you're trying to pass an uploaded file's ID, use an object with the id property instead: ${JSON.stringify(c)}`); } } else if (c.file && typeof c.file === 'object' && 'id' in c.file) { file.file_id = c.file.id; } else { throw new UserError(`File input requires a data URL or file ID: ${JSON.stringify(c)}`); } // Handle filename from the content item or providerData if (c.filename) { file.filename = c.filename; } else if (c.providerData?.filename) { file.filename = c.providerData.filename; } const { filename: _filename, ...rest } = c.providerData || {}; out.push({ type: 'file', file, ...rest, }); } else if (c.type === 'audio') { const { input_audio, ...rest } = c.providerData || {}; out.push({ type: 'input_audio', input_audio: { data: c.audio, ...input_audio, }, ...rest, }); } else { const exhaustive = c; // ensures that the type is exhaustive throw new Error(`Unknown content: ${JSON.stringify(exhaustive)}`); } } return out; } function isMessageItem(item) { if (item.type === 'message') { return true; } if (typeof item.type === 'undefined' && typeof item.role === 'string') { return true; } return false; } export function itemsToMessages(items) { if (typeof items === 'string') { return [{ role: 'user', content: items }]; } const result = []; let currentAssistantMsg = null; const flushAssistantMessage = () => { if (currentAssistantMsg) { if (!currentAssistantMsg.tool_calls || currentAssistantMsg.tool_calls.length === 0) { delete currentAssistantMsg.tool_calls; } result.push(currentAssistantMsg); currentAssistantMsg = null; } }; const ensureAssistantMessage = () => { if (!currentAssistantMsg) { currentAssistantMsg = { role: 'assistant', tool_calls: [] }; } return currentAssistantMsg; }; for (const item of items) { if (isMessageItem(item)) { const { content, role, providerData } = item; flushAssistantMessage(); if (role === 'assistant') { const assistant = { role: 'assistant', content: extractAllAssistantContent(content), ...providerData, }; if (Array.isArray(content)) { const audio = content.find((c) => c.type === 'audio'); if (audio) { assistant.audio = { id: '', // setting this to empty ID and expecting that the user sets providerData.id ...audio.providerData, }; } } result.push(assistant); } else if (role === 'user') { result.push({ role, content: extractAllUserContent(content), ...providerData, }); } else if (role === 'system') { result.push({ role: 'system', content: content, ...providerData, }); } } else if (item.type === 'reasoning') { const asst = ensureAssistantMessage(); // @ts-expect-error - reasoning is not supported in the official Chat Completion API spec // this is handling third party providers that support reasoning asst.reasoning = item.rawContent?.[0]?.text; continue; } else if (item.type === 'hosted_tool_call') { if (item.name === 'file_search_call') { const asst = ensureAssistantMessage(); const toolCalls = asst.tool_calls ?? []; const fileSearch = item; const { function: functionData, ...rest } = fileSearch.providerData ?? {}; const { arguments: argumentData, ...remainingFunctionData } = functionData ?? {}; toolCalls.push({ id: fileSearch.id || '', type: 'function', function: { name: 'file_search_call', arguments: JSON.stringify({ queries: fileSearch.providerData?.queries ?? [], status: fileSearch.status, ...argumentData, }), ...remainingFunctionData, }, ...rest, }); asst.tool_calls = toolCalls; continue; } else { throw new UserError('Hosted tool calls are not supported for chat completions. Got item: ' + JSON.stringify(item)); } } else if (item.type === 'computer_call' || item.type === 'computer_call_result' || item.type === 'shell_call' || item.type === 'shell_call_output' || item.type === 'apply_patch_call' || item.type === 'apply_patch_call_output') { throw new UserError('Computer use calls are not supported for chat completions. Got item: ' + JSON.stringify(item)); } else if (item.type === 'function_call') { const asst = ensureAssistantMessage(); const toolCalls = asst.tool_calls ?? []; const funcCall = item; toolCalls.push({ id: funcCall.callId, type: 'function', function: { name: funcCall.name, arguments: funcCall.arguments ?? '{}', }, }); asst.tool_calls = toolCalls; Object.assign(asst, funcCall.providerData); } else if (item.type === 'function_call_result') { flushAssistantMessage(); const funcOutput = item; const toolContent = normalizeFunctionCallOutputForChat(funcOutput.output); result.push({ role: 'tool', tool_call_id: funcOutput.callId, content: toolContent, ...funcOutput.providerData, }); } else if (item.type === 'unknown') { result.push({ ...item.providerData, }); } else if (item.type === 'compaction') { throw new UserError('Compaction items are not supported for chat completions. Please use the Responses API when working with compaction.'); } else { const exhaustive = item; // ensures that the type is exhaustive throw new Error(`Unknown item type: ${JSON.stringify(exhaustive)}`); } } flushAssistantMessage(); return result; } function normalizeFunctionCallOutputForChat(output) { if (typeof output === 'string') { return output; } if (Array.isArray(output)) { const textOnly = output.every((item) => item.type === 'input_text'); if (!textOnly) { throw new UserError('Only text tool outputs are supported for chat completions.'); } return output.map((item) => item.text).join(''); } if (isRecord(output) && output.type === 'text' && typeof output.text === 'string') { return output.text; } throw new UserError('Only text tool outputs are supported for chat completions. Got item: ' + JSON.stringify(output)); } function isRecord(value) { return typeof value === 'object' && value !== null; } export function toolToOpenAI(tool) { if (tool.type === 'function') { return { type: 'function', function: { name: tool.name, description: tool.description || '', parameters: tool.parameters, strict: tool.strict, }, }; } throw new Error(`Hosted tools are not supported with the ChatCompletions API. Got tool type: ${tool.type}, tool: ${JSON.stringify(tool)}`); } export function convertHandoffTool(handoff) { return { type: 'function', function: { name: handoff.toolName, description: handoff.toolDescription || '', parameters: handoff.inputJsonSchema, }, }; } //# sourceMappingURL=openaiChatCompletionsConverter.mjs.map