gravityyy-proxyyy / src /format /openai-stream.js
proxy-edit
feat: surface Gemini search grounding as OpenAI url_citation annotations
d059023
Raw
History Blame Contribute Delete
4.23 kB
/**
* OpenAI Chat Completions streaming conversion helpers.
*/
import crypto from 'crypto';
function mapFinishReason(stopReason) {
if (stopReason === 'max_tokens') return 'length';
if (stopReason === 'tool_use') return 'tool_calls';
return 'stop';
}
export function createOpenAIStreamState(requestedModel, includeUsage = false) {
return {
id: `chatcmpl_${crypto.randomUUID().replace(/-/g, '')}`,
created: Math.floor(Date.now() / 1000),
model: requestedModel,
includeUsage,
started: false,
finishReason: null,
promptTokens: 0,
completionTokens: 0,
toolCallIndexes: new Map(),
nextToolCallIndex: 0
};
}
function makeChunk(state, delta, finishReason = null, usage = undefined) {
const chunk = {
id: state.id,
object: 'chat.completion.chunk',
created: state.created,
model: state.model,
choices: [{
index: 0,
delta,
finish_reason: finishReason,
logprobs: null
}]
};
if (usage !== undefined) chunk.usage = usage;
return chunk;
}
export function convertAnthropicEventToOpenAIChunks(event, state) {
const chunks = [];
if (!event || !state) return chunks;
if (event.type === 'message_start') {
state.id = event.message?.id?.replace(/^msg_/, 'chatcmpl_') || state.id;
state.promptTokens = event.message?.usage?.input_tokens || 0;
if (!state.started) {
state.started = true;
chunks.push(makeChunk(state, { role: 'assistant', content: '' }));
}
return chunks;
}
if (!state.started) {
state.started = true;
chunks.push(makeChunk(state, { role: 'assistant', content: '' }));
}
if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
const toolIndex = state.nextToolCallIndex++;
state.toolCallIndexes.set(event.index, toolIndex);
chunks.push(makeChunk(state, {
tool_calls: [{
index: toolIndex,
id: event.content_block.id,
type: 'function',
function: {
name: event.content_block.name,
arguments: ''
}
}]
}));
return chunks;
}
if (event.type === 'content_block_delta') {
if (event.delta?.type === 'text_delta') {
chunks.push(makeChunk(state, { content: event.delta.text || '' }));
} else if (event.delta?.type === 'thinking_delta') {
chunks.push(makeChunk(state, { reasoning_content: event.delta.thinking || '' }));
} else if (event.delta?.type === 'input_json_delta') {
const toolIndex = state.toolCallIndexes.get(event.index) ?? 0;
chunks.push(makeChunk(state, {
tool_calls: [{
index: toolIndex,
function: {
arguments: event.delta.partial_json || ''
}
}]
}));
}
return chunks;
}
if (event.type === 'message_delta') {
state.finishReason = mapFinishReason(event.delta?.stop_reason);
state.completionTokens = event.usage?.output_tokens || 0;
// Emit search grounding (citations) just before the finish chunk.
if (event.grounding?.annotations?.length > 0) {
chunks.push(makeChunk(state, {
annotations: event.grounding.annotations,
grounding: event.grounding
}));
}
chunks.push(makeChunk(state, {}, state.finishReason));
return chunks;
}
if (event.type === 'message_stop' && state.includeUsage) {
chunks.push({
id: state.id,
object: 'chat.completion.chunk',
created: state.created,
model: state.model,
choices: [],
usage: {
prompt_tokens: state.promptTokens,
completion_tokens: state.completionTokens,
total_tokens: state.promptTokens + state.completionTokens
}
});
}
return chunks;
}