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:
@@ -5,6 +5,7 @@
|
||||
* - params.system: STRING at top level (Anthropic-style; system messages NOT allowed in messages[])
|
||||
* - params.messages[*].role ∈ {"user","assistant","tool"}
|
||||
* - params.messages[*].content: Array of content blocks (NEVER a string)
|
||||
* - image_url / image source → {type:"image", image:"data:...;base64,...", mimeType}
|
||||
* - tool_use blocks (assistant): {type:"tool-call", toolCallId, toolName, input}
|
||||
* - tool_result blocks (role=user): {type:"tool-result", toolCallId, toolName, output}
|
||||
* - tools[*]: Anthropic plain {name, description, input_schema}
|
||||
@@ -12,10 +13,9 @@
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { randomUUID } from "crypto";
|
||||
import { ROLE, OPENAI_BLOCK } from "../schema/index.js";
|
||||
import { DEFAULT_IMAGE_MIME } from "../schema/index.js";
|
||||
import { parseDataUri } from "../concerns/image.js";
|
||||
import { ROLE, OPENAI_BLOCK, CLAUDE_BLOCK } from "../schema/index.js";
|
||||
import { DEFAULT_MAX_TOKENS } from "../../config/runtimeConfig.js";
|
||||
import { parseDataUri, encodeDataUri } from "../concerns/image.js";
|
||||
|
||||
function flattenText(content) {
|
||||
if (content == null) return "";
|
||||
@@ -32,6 +32,58 @@ function flattenText(content) {
|
||||
return String(content);
|
||||
}
|
||||
|
||||
function toNativeImageBlock(part) {
|
||||
if (!part || typeof part !== "object") return null;
|
||||
|
||||
if (part.type === OPENAI_BLOCK.IMAGE_URL) {
|
||||
const url = typeof part.image_url === "string" ? part.image_url : part.image_url?.url;
|
||||
if (!url) return null;
|
||||
const parsed = parseDataUri(url);
|
||||
if (parsed) {
|
||||
return {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: encodeDataUri(parsed.mimeType, parsed.base64),
|
||||
mimeType: parsed.mimeType,
|
||||
};
|
||||
}
|
||||
if (typeof url === "string" && (url.startsWith("http://") || url.startsWith("https://"))) {
|
||||
return {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: url,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (part.type === OPENAI_BLOCK.IMAGE || part.type === CLAUDE_BLOCK.IMAGE) {
|
||||
if (typeof part.image === "string" && part.image.startsWith("data:")) {
|
||||
const parsed = parseDataUri(part.image);
|
||||
return {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: part.image,
|
||||
mimeType: part.mimeType || parsed?.mimeType || "image/png",
|
||||
};
|
||||
}
|
||||
if (typeof part.image === "string" && (part.image.startsWith("http://") || part.image.startsWith("https://"))) {
|
||||
return {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: part.image,
|
||||
};
|
||||
}
|
||||
const source = part.source;
|
||||
if (source?.type === "base64" && typeof source.data === "string") {
|
||||
const mime = source.media_type || "image/png";
|
||||
return {
|
||||
type: OPENAI_BLOCK.IMAGE,
|
||||
image: encodeDataUri(mime, source.data),
|
||||
mimeType: mime,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function toContentBlocks(content) {
|
||||
if (content == null) return [{ type: OPENAI_BLOCK.TEXT, text: "" }];
|
||||
if (typeof content === "string")
|
||||
@@ -44,30 +96,12 @@ function toContentBlocks(content) {
|
||||
} else if (part && typeof part === "object") {
|
||||
if (part.type === OPENAI_BLOCK.TEXT && typeof part.text === "string") {
|
||||
blocks.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
|
||||
} else if (
|
||||
part.type === OPENAI_BLOCK.IMAGE_URL ||
|
||||
part.type === OPENAI_BLOCK.IMAGE
|
||||
) {
|
||||
// CommandCode `/alpha/generate` accepts {type:"image", image:"<data URI | url>"} —
|
||||
// same shape the official command-code CLI sends (verified from CLI source).
|
||||
const src = part.source;
|
||||
let raw = part.image_url?.url || src?.data || src?.url || "";
|
||||
let parsed = parseDataUri(raw);
|
||||
if (!parsed && src?.type === "base64" && src?.data) {
|
||||
// Claude-style base64 source without a data-URI prefix → wrap it.
|
||||
raw = `data:${src.media_type || DEFAULT_IMAGE_MIME};base64,${src.data}`;
|
||||
parsed = parseDataUri(raw);
|
||||
} else {
|
||||
const image = toNativeImageBlock(part);
|
||||
if (image) blocks.push(image);
|
||||
else if (typeof part.text === "string") {
|
||||
blocks.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
|
||||
}
|
||||
if (parsed) {
|
||||
blocks.push({
|
||||
type: "image",
|
||||
image: `data:${parsed.mimeType};base64,${parsed.base64}`,
|
||||
});
|
||||
} else if (raw) {
|
||||
blocks.push({ type: "image", image: raw });
|
||||
}
|
||||
} else if (typeof part.text === "string") {
|
||||
blocks.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +153,10 @@ function convertMessages(messages = []) {
|
||||
|
||||
if (role === ROLE.ASSISTANT) {
|
||||
const blocks = [];
|
||||
const rc = m.reasoning_content || m.thought || m.reasoning;
|
||||
if (rc || (Array.isArray(m.tool_calls) && m.tool_calls.length > 0)) {
|
||||
blocks.push({ type: "reasoning", text: rc || " " });
|
||||
}
|
||||
const text = flattenText(m.content);
|
||||
if (text) blocks.push({ type: OPENAI_BLOCK.TEXT, text });
|
||||
if (Array.isArray(m.tool_calls)) {
|
||||
|
||||
Reference in New Issue
Block a user