Merge origin/master (v0.5.81) into gitea/new_feature
Resolve conflicts: - streamingHandler.js: merge buildStreamErrorBytes onAbortTerminal + local shouldPersistRequestDetail & streamStatusForContent - capabilities.js: preserve server-injected user-asserted caps and models.dev catalog lookup; wire CommandCode /alpha/generate caps inside resolve() - commandcode.js (services/usage): adopt upstream whoami + billing credits/subscriptions with 5h/weekly rate windows and plan caps - openai-to-commandcode.js: merge toNativeImageBlock (data-URI & http(s) support) and assistant reasoning_content preservation - commandcode-to-openai.js: adopt upstream mid-stream error throw for clean retry and abortion - tests: sync commandcode test suite and exclude .next from vitest config
This commit is contained in:
@@ -183,27 +183,12 @@ export function commandCodeToOpenAIResponse(chunk, state) {
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
// Terminal upstream failure (AI SDK v5 error event) — NOT content. Emit an
|
||||
// OpenAI-shaped error chunk (chunk.error) so downstream — parseSSEToOpenAIResponse
|
||||
// for non-streaming, OpenAI SDK clients for streaming — treats the request as
|
||||
// failed instead of surfacing fake success content like "[CommandCode error: ...]".
|
||||
state.finishReason = OPENAI_FINISH.STOP;
|
||||
const errVal = event.error ?? event.message ?? "unknown";
|
||||
const errStr =
|
||||
typeof errVal === "string"
|
||||
? errVal
|
||||
: typeof errVal?.message === "string"
|
||||
? errVal.message
|
||||
: JSON.stringify(errVal);
|
||||
const errType =
|
||||
typeof errVal === "string"
|
||||
? "upstream_error"
|
||||
: errVal?.type || "upstream_error";
|
||||
const errChunk = makeChunk(state, {});
|
||||
errChunk.error = { message: errStr, type: errType };
|
||||
out.push(errChunk);
|
||||
out.push(makeChunk(state, {}, OPENAI_FINISH.STOP));
|
||||
break;
|
||||
typeof errVal === "string" ? errVal : JSON.stringify(errVal);
|
||||
// Mid-stream error: throw rather than emitting as fake content with finish_reason: "stop"
|
||||
// This ensures the downstream stream handler marks the stream as errored/aborted.
|
||||
throw new Error(`[CommandCode error: ${errStr}]`);
|
||||
}
|
||||
// Silently ignore: start, start-step, reasoning-start, reasoning-end, text-start, text-end,
|
||||
// provider-metadata, message-metadata, etc. They carry no client-visible content.
|
||||
|
||||
@@ -22,7 +22,7 @@ function emitFunctionCall(functionCall, state, signature = null) {
|
||||
const toolCallIndex = state.functionIndex++;
|
||||
const callId = functionCall.id || `${fcName}-${Date.now()}-${toolCallIndex}`;
|
||||
if (signature) {
|
||||
storeGeminiThoughtSignature(callId, signature, state.sessionId);
|
||||
storeGeminiThoughtSignature(callId, signature, state.sessionId, state.model);
|
||||
}
|
||||
const toolCall = {
|
||||
id: callId,
|
||||
@@ -52,7 +52,7 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||
// Initialize state
|
||||
if (!state.messageId) {
|
||||
state.messageId = response.responseId || `msg_${Date.now()}`;
|
||||
state.model = response.modelVersion || "gemini";
|
||||
state.model = response.modelVersion || state.model || "gemini";
|
||||
state.functionIndex = 0;
|
||||
state.geminiToolCallCount = 0;
|
||||
results.push(buildChunk(chunkMeta(state), { role: ROLE.ASSISTANT }, null));
|
||||
|
||||
@@ -46,6 +46,14 @@ function convertFinishReason(reason) {
|
||||
* Convert one OpenAI-format chunk (from KiroExecutor) into Claude SSE events.
|
||||
* Returns an array of Claude events, or null when the chunk yields nothing.
|
||||
*/
|
||||
// Kiro only accepts sanitized tool names; the request translator leaves the
|
||||
// reverse map on the stream state so calls come back under the client's names.
|
||||
function restoreToolName(stateOrData, name) {
|
||||
const raw = name || "";
|
||||
const map = stateOrData?.toolNameMap || stateOrData?._toolNameMap;
|
||||
return map && typeof map.get === "function" && map.has(raw) ? map.get(raw) : raw;
|
||||
}
|
||||
|
||||
export function kiroToClaudeResponse(chunk, state) {
|
||||
// KiroExecutor emits chat.completion.chunk objects; tolerate string chunks
|
||||
// by attempting a parse (defensive — the direct path is always objects).
|
||||
@@ -161,7 +169,7 @@ export function kiroToClaudeResponse(chunk, state) {
|
||||
const toolBlockIndex = state.nextBlockIndex++;
|
||||
state.toolCalls.set(idx, {
|
||||
id: tc.id,
|
||||
name: tc.function?.name || "",
|
||||
name: restoreToolName(state, tc.function?.name),
|
||||
blockIndex: toolBlockIndex,
|
||||
});
|
||||
results.push({
|
||||
@@ -170,7 +178,7 @@ export function kiroToClaudeResponse(chunk, state) {
|
||||
content_block: {
|
||||
type: "tool_use",
|
||||
id: tc.id,
|
||||
name: tc.function?.name || "",
|
||||
name: restoreToolName(state, tc.function?.name),
|
||||
input: {},
|
||||
},
|
||||
});
|
||||
@@ -246,7 +254,7 @@ export function kiroToClaudeNonStreaming(data) {
|
||||
content.push({
|
||||
type: "tool_use",
|
||||
id: tc.id || `toolu_${Date.now()}`,
|
||||
name: tc.function?.name || "",
|
||||
name: restoreToolName(data, tc.function?.name),
|
||||
input,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,13 +20,38 @@ function chunkMeta(state) {
|
||||
* Parse Kiro SSE event and convert to OpenAI format
|
||||
* Kiro events: assistantResponseEvent, codeEvent, supplementaryWebLinksEvent, etc.
|
||||
*/
|
||||
// Kiro only accepts sanitized tool names; the request translator leaves the
|
||||
// reverse map on the stream state so calls come back under the client's names.
|
||||
function restoreToolName(state, name) {
|
||||
const raw = name || "";
|
||||
const map = state?.toolNameMap;
|
||||
return map && typeof map.get === "function" && map.has(raw) ? map.get(raw) : raw;
|
||||
}
|
||||
|
||||
export function kiroToOpenAIResponse(chunk, state) {
|
||||
|
||||
if (!chunk) return null;
|
||||
|
||||
// If chunk is already in OpenAI format (from executor transform), return as-is
|
||||
// If chunk is already in OpenAI format (from executor transform), return it
|
||||
// with the client's tool names restored.
|
||||
if (chunk.object === "chat.completion.chunk" && chunk.choices) {
|
||||
return chunk;
|
||||
if (!state?.toolNameMap?.size) return chunk;
|
||||
return {
|
||||
...chunk,
|
||||
choices: chunk.choices.map((choice) => {
|
||||
const calls = choice?.delta?.tool_calls;
|
||||
if (!Array.isArray(calls)) return choice;
|
||||
return {
|
||||
...choice,
|
||||
delta: {
|
||||
...choice.delta,
|
||||
tool_calls: calls.map((tc) => tc?.function?.name
|
||||
? { ...tc, function: { ...tc.function, name: restoreToolName(state, tc.function.name) } }
|
||||
: tc),
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// Handle string chunk (raw SSE data)
|
||||
@@ -109,7 +134,7 @@ export function kiroToOpenAIResponse(chunk, state) {
|
||||
state.hadToolUse = true;
|
||||
const toolUse = data.toolUseEvent || data;
|
||||
const toolCallId = toolUse.toolUseId || fallbackToolCallId();
|
||||
const toolName = toolUse.name || "";
|
||||
const toolName = restoreToolName(state, toolUse.name);
|
||||
const toolInput = toolUse.input || {};
|
||||
|
||||
const openaiChunk = buildChunk(chunkMeta(state), {
|
||||
|
||||
Reference in New Issue
Block a user