feat(opencode-go): add muse-spark-1.3-contributor and fix parallel tool calls on Responses paths (#3819)
- Add muse-spark-1.3-contributor as responses-only model on OpenCode Go with dedicated executor - Key Responses→chat streaming tool calls by item_id to prevent parallel tool calls merging into index 0 - Standardize tool coercions and call_id clamping in Responses API translation
This commit is contained in:
@@ -6,12 +6,15 @@
|
||||
*/
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { normalizeResponsesInput } from "../formats/responsesApi.js";
|
||||
import {
|
||||
normalizeResponsesInput,
|
||||
clampResponsesCallId,
|
||||
coerceResponsesArguments,
|
||||
coerceResponsesOutput,
|
||||
} from "../formats/responsesApi.js";
|
||||
import { ROLE, OPENAI_BLOCK, RESPONSES_ITEM } from "../schema/index.js";
|
||||
|
||||
// Responses API enforces max 64 chars on call_id (#393)
|
||||
const MAX_CALL_ID_LEN = 64;
|
||||
const clampCallId = (id) => (typeof id === "string" && id.length > MAX_CALL_ID_LEN ? id.substring(0, MAX_CALL_ID_LEN) : id);
|
||||
const MAX_TOOL_NAME_LEN = 128;
|
||||
|
||||
/**
|
||||
* Convert OpenAI Responses API request to OpenAI Chat Completions format
|
||||
@@ -249,6 +252,23 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plain text from a system/developer message for Responses instructions.
|
||||
* Array content (text parts) is joined; anything else falls back to "" rather
|
||||
* than leaking "[object Object]" upstream.
|
||||
*/
|
||||
function extractInstructionsText(content) {
|
||||
if (typeof content === "string") return content;
|
||||
if (Array.isArray(content)) {
|
||||
return content.map((c) => {
|
||||
if (typeof c?.text === "string") return c.text;
|
||||
if (typeof c?.content === "string") return c.content;
|
||||
return "";
|
||||
}).filter(Boolean).join("\n");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure object schema always has properties field (required by Codex Responses API)
|
||||
*/
|
||||
@@ -327,7 +347,7 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
// Use the first instruction-bearing message as instructions.
|
||||
// OpenAI recommends role="developer" for GPT-5/Codex as the system-level prompt.
|
||||
if (!hasSystemMessage) {
|
||||
result.instructions = typeof msg.content === "string" ? msg.content : "";
|
||||
result.instructions = extractInstructionsText(msg.content);
|
||||
hasSystemMessage = true;
|
||||
}
|
||||
continue; // Skip instruction messages in input
|
||||
@@ -378,26 +398,24 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
// Convert tool calls
|
||||
if (msg.role === ROLE.ASSISTANT && msg.tool_calls) {
|
||||
for (const tc of msg.tool_calls) {
|
||||
// Skip nameless calls — strict Responses upstreams reject them (#444)
|
||||
const name = typeof tc.function?.name === "string" ? tc.function.name.trim() : "";
|
||||
if (!name) continue;
|
||||
result.input.push({
|
||||
type: RESPONSES_ITEM.FUNCTION_CALL,
|
||||
call_id: clampCallId(tc.id),
|
||||
name: tc.function?.name || "_unknown",
|
||||
arguments: tc.function?.arguments || "{}"
|
||||
call_id: clampResponsesCallId(tc.id),
|
||||
name: name.slice(0, MAX_TOOL_NAME_LEN),
|
||||
arguments: coerceResponsesArguments(tc.function?.arguments)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Convert tool results - output must be a string for Responses API
|
||||
if (msg.role === ROLE.TOOL) {
|
||||
const output = typeof msg.content === "string"
|
||||
? msg.content
|
||||
: Array.isArray(msg.content)
|
||||
? msg.content.map(c => c.text || JSON.stringify(c)).join("")
|
||||
: JSON.stringify(msg.content);
|
||||
result.input.push({
|
||||
type: RESPONSES_ITEM.FUNCTION_CALL_OUTPUT,
|
||||
call_id: clampCallId(msg.tool_call_id),
|
||||
output
|
||||
call_id: clampResponsesCallId(msg.tool_call_id),
|
||||
output: coerceResponsesOutput(msg.content)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -411,16 +429,19 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
result.tools = body.tools.map(tool => {
|
||||
if (tool.type === OPENAI_BLOCK.FUNCTION) {
|
||||
// Strict upstreams reject nameless/overlong tool declarations
|
||||
const name = typeof tool.function?.name === "string" ? tool.function.name.trim() : "";
|
||||
if (!name) return null;
|
||||
return {
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
name: tool.function.name,
|
||||
name: name.slice(0, MAX_TOOL_NAME_LEN),
|
||||
description: String(tool.function.description || ""),
|
||||
parameters: normalizeToolParameters(tool.function.parameters),
|
||||
strict: tool.function.strict
|
||||
};
|
||||
}
|
||||
return tool;
|
||||
});
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
// Pass through other relevant fields
|
||||
|
||||
Reference in New Issue
Block a user