Spaces:
Running
Running
| import { type ChatCompletionChunk } from "~/services/copilot/create-chat-completions" | |
| import { | |
| type AnthropicStreamEventData, | |
| type AnthropicStreamState, | |
| } from "./anthropic-types" | |
| import { mapOpenAIStopReasonToAnthropic } from "./utils" | |
| function isToolBlockOpen(state: AnthropicStreamState): boolean { | |
| if (!state.contentBlockOpen) { | |
| return false | |
| } | |
| // Check if the current block index corresponds to any known tool call | |
| return Object.values(state.toolCalls).some( | |
| (tc) => tc.anthropicBlockIndex === state.contentBlockIndex, | |
| ) | |
| } | |
| // eslint-disable-next-line max-lines-per-function, complexity | |
| export function translateChunkToAnthropicEvents( | |
| chunk: ChatCompletionChunk, | |
| state: AnthropicStreamState, | |
| ): Array<AnthropicStreamEventData> { | |
| const events: Array<AnthropicStreamEventData> = [] | |
| if (chunk.choices.length === 0) { | |
| return events | |
| } | |
| const choice = chunk.choices[0] | |
| const { delta } = choice | |
| if (!state.messageStartSent) { | |
| events.push({ | |
| type: "message_start", | |
| message: { | |
| id: chunk.id, | |
| type: "message", | |
| role: "assistant", | |
| content: [], | |
| model: chunk.model, | |
| stop_reason: null, | |
| stop_sequence: null, | |
| usage: { | |
| input_tokens: | |
| (chunk.usage?.prompt_tokens ?? 0) | |
| - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0), | |
| output_tokens: 0, // Will be updated in message_delta when finished | |
| ...(chunk.usage?.prompt_tokens_details?.cached_tokens | |
| !== undefined && { | |
| cache_read_input_tokens: | |
| chunk.usage.prompt_tokens_details.cached_tokens, | |
| }), | |
| }, | |
| }, | |
| }) | |
| state.messageStartSent = true | |
| } | |
| if (delta.content) { | |
| if (isToolBlockOpen(state)) { | |
| // A tool block was open, so close it before starting a text block. | |
| events.push({ | |
| type: "content_block_stop", | |
| index: state.contentBlockIndex, | |
| }) | |
| state.contentBlockIndex++ | |
| state.contentBlockOpen = false | |
| } | |
| if (!state.contentBlockOpen) { | |
| events.push({ | |
| type: "content_block_start", | |
| index: state.contentBlockIndex, | |
| content_block: { | |
| type: "text", | |
| text: "", | |
| }, | |
| }) | |
| state.contentBlockOpen = true | |
| } | |
| events.push({ | |
| type: "content_block_delta", | |
| index: state.contentBlockIndex, | |
| delta: { | |
| type: "text_delta", | |
| text: delta.content, | |
| }, | |
| }) | |
| } | |
| if (delta.tool_calls) { | |
| for (const toolCall of delta.tool_calls) { | |
| if (toolCall.id && toolCall.function?.name) { | |
| // New tool call starting. | |
| if (state.contentBlockOpen) { | |
| // Close any previously open block. | |
| events.push({ | |
| type: "content_block_stop", | |
| index: state.contentBlockIndex, | |
| }) | |
| state.contentBlockIndex++ | |
| state.contentBlockOpen = false | |
| } | |
| const anthropicBlockIndex = state.contentBlockIndex | |
| state.toolCalls[toolCall.index] = { | |
| id: toolCall.id, | |
| name: toolCall.function.name, | |
| anthropicBlockIndex, | |
| } | |
| events.push({ | |
| type: "content_block_start", | |
| index: anthropicBlockIndex, | |
| content_block: { | |
| type: "tool_use", | |
| id: toolCall.id, | |
| name: toolCall.function.name, | |
| input: {}, | |
| }, | |
| }) | |
| state.contentBlockOpen = true | |
| } | |
| if (toolCall.function?.arguments) { | |
| const toolCallInfo = state.toolCalls[toolCall.index] | |
| // Tool call can still be empty | |
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | |
| if (toolCallInfo) { | |
| events.push({ | |
| type: "content_block_delta", | |
| index: toolCallInfo.anthropicBlockIndex, | |
| delta: { | |
| type: "input_json_delta", | |
| partial_json: toolCall.function.arguments, | |
| }, | |
| }) | |
| } | |
| } | |
| } | |
| } | |
| if (choice.finish_reason) { | |
| if (state.contentBlockOpen) { | |
| events.push({ | |
| type: "content_block_stop", | |
| index: state.contentBlockIndex, | |
| }) | |
| state.contentBlockOpen = false | |
| } | |
| events.push( | |
| { | |
| type: "message_delta", | |
| delta: { | |
| stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason), | |
| stop_sequence: null, | |
| }, | |
| usage: { | |
| input_tokens: | |
| (chunk.usage?.prompt_tokens ?? 0) | |
| - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0), | |
| output_tokens: chunk.usage?.completion_tokens ?? 0, | |
| ...(chunk.usage?.prompt_tokens_details?.cached_tokens | |
| !== undefined && { | |
| cache_read_input_tokens: | |
| chunk.usage.prompt_tokens_details.cached_tokens, | |
| }), | |
| }, | |
| }, | |
| { | |
| type: "message_stop", | |
| }, | |
| ) | |
| } | |
| return events | |
| } | |
| export function translateErrorToAnthropicErrorEvent(): AnthropicStreamEventData { | |
| return { | |
| type: "error", | |
| error: { | |
| type: "api_error", | |
| message: "An unexpected error occurred during streaming.", | |
| }, | |
| } | |
| } | |