diff --git a/open-sse/translator/concerns/finishReasonMap.js b/open-sse/translator/concerns/finishReasonMap.js new file mode 100644 index 00000000..62d5a4ce --- /dev/null +++ b/open-sse/translator/concerns/finishReasonMap.js @@ -0,0 +1,43 @@ +// Concern #6: finish_reason / stop_reason mapping. +// One entry per direction; switch by special format, default handles common providers. + +// upstream finish/stop reason → OpenAI finish_reason +export function toOpenAIFinish(reason, format) { + switch (format) { + case "claude": + switch (reason) { + case "end_turn": return "stop"; + case "max_tokens": return "length"; + case "tool_use": return "tool_calls"; + case "stop_sequence": return "stop"; + default: return "stop"; + } + case "commandcode": + switch (reason) { + case "stop": return "stop"; + case "length": return "length"; + case "tool-calls": + case "tool_use": return "tool_calls"; + case "content-filter": return "content_filter"; + case "error": return "stop"; + default: return reason || "stop"; + } + default: + return reason || "stop"; + } +} + +// OpenAI finish_reason → upstream stop reason +export function fromOpenAIFinish(reason, format) { + switch (format) { + case "claude": + switch (reason) { + case "stop": return "end_turn"; + case "length": return "max_tokens"; + case "tool_calls": return "tool_use"; + default: return "end_turn"; + } + default: + return reason; + } +} diff --git a/open-sse/translator/response/claude-to-openai.js b/open-sse/translator/response/claude-to-openai.js index ff5ccafe..b13ba8e5 100644 --- a/open-sse/translator/response/claude-to-openai.js +++ b/open-sse/translator/response/claude-to-openai.js @@ -3,6 +3,7 @@ import { FORMATS } from "../formats.js"; import { buildChunk } from "../helpers/chunkBuilder.js"; import { buildUsage } from "../helpers/usageHelper.js"; import { reasoningDelta } from "../helpers/reasoningHelper.js"; +import { toOpenAIFinish } from "../concerns/finishReasonMap.js"; // Create OpenAI chunk helper function createChunk(state, delta, finishReason = null) { @@ -164,16 +165,7 @@ export function claudeToOpenAIResponse(chunk, state) { return results.length > 0 ? results : null; } -// Convert Claude stop_reason to OpenAI finish_reason -function convertStopReason(reason) { - switch (reason) { - case "end_turn": return "stop"; - case "max_tokens": return "length"; - case "tool_use": return "tool_calls"; - case "stop_sequence": return "stop"; - default: return "stop"; - } -} +const convertStopReason = (reason) => toOpenAIFinish(reason, "claude"); // Register register(FORMATS.CLAUDE, FORMATS.OPENAI, null, claudeToOpenAIResponse); diff --git a/open-sse/translator/response/commandcode-to-openai.js b/open-sse/translator/response/commandcode-to-openai.js index 77483955..a11d800d 100644 --- a/open-sse/translator/response/commandcode-to-openai.js +++ b/open-sse/translator/response/commandcode-to-openai.js @@ -19,6 +19,7 @@ import { register } from "../index.js"; import { FORMATS } from "../formats.js"; import { buildChunk } from "../helpers/chunkBuilder.js"; import { reasoningDelta } from "../helpers/reasoningHelper.js"; +import { toOpenAIFinish } from "../concerns/finishReasonMap.js"; function ensureState(state, model) { if (!state.responseId) { @@ -43,17 +44,7 @@ function makeChunk(state, delta, finishReason = null) { ); } -function mapFinishReason(reason) { - switch (reason) { - case "stop": return "stop"; - case "length": return "length"; - case "tool-calls": - case "tool_use": return "tool_calls"; - case "content-filter": return "content_filter"; - case "error": return "stop"; - default: return reason || "stop"; - } -} +const mapFinishReason = (reason) => toOpenAIFinish(reason, "commandcode"); export function convertCommandCodeToOpenAI(chunk, state) { if (!chunk) return null; diff --git a/open-sse/translator/response/openai-to-claude.js b/open-sse/translator/response/openai-to-claude.js index 6a46551d..f2e20e84 100644 --- a/open-sse/translator/response/openai-to-claude.js +++ b/open-sse/translator/response/openai-to-claude.js @@ -1,5 +1,6 @@ import { register } from "../index.js"; import { FORMATS } from "../formats.js"; +import { fromOpenAIFinish } from "../concerns/finishReasonMap.js"; // Prefix for Claude OAuth tool names (must match request translator) const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_"; @@ -252,15 +253,7 @@ export function openaiToClaudeResponse(chunk, state) { return results.length > 0 ? results : null; } -// Convert OpenAI finish_reason to Claude stop_reason -function convertFinishReason(reason) { - switch (reason) { - case "stop": return "end_turn"; - case "length": return "max_tokens"; - case "tool_calls": return "tool_use"; - default: return "end_turn"; - } -} +const convertFinishReason = (reason) => fromOpenAIFinish(reason, "claude"); // Register register(FORMATS.OPENAI, FORMATS.CLAUDE, null, openaiToClaudeResponse);