Files
9router/open-sse/translator/request/openai-to-commandcode.js
luulam c5ce1ef140 feat(commandcode): quota usage dashboard, CLI-parity request, and connect timeout fixes
- Add CommandCode usage handler mirroring the official CLI /usage
  (whoami → credits/subscriptions → summary) with 5h/weekly/monthly
  quota rows, registered in services/usage.js and registry config
- Parse commandcode quota rows in ProviderLimits (remainingPercentage + $ unit)
- Match official CLI request shape: x-command-code-version 1.10.0,
  User-Agent cli, and config.environment '${platform}-${arch}, Node.js ${version}'
- Fix connect timeout unit confusion: both profile and provider pages
  now use ms with a 1s minimum guard (prevents 60ms footgun)
- Fix fetchT0 ReferenceError in base.js error path and log fetch
  diagnostics only on upstream failure
- Quota Tracker defaults to the Active account filter
- Ignore .commandcode/ CLI local state
2026-08-04 22:34:13 +07:00

217 lines
6.2 KiB
JavaScript

/**
* OpenAI → CommandCode request translator
*
* Upstream `/alpha/generate` schema (verified live with curl 2026-05-07):
* - 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)
* - 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}
*/
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 { DEFAULT_MAX_TOKENS } from "../../config/runtimeConfig.js";
function flattenText(content) {
if (content == null) return "";
if (typeof content === "string") return content;
if (Array.isArray(content)) {
const parts = [];
for (const p of content) {
if (typeof p === "string") parts.push(p);
else if (p && typeof p === "object" && typeof p.text === "string")
parts.push(p.text);
}
return parts.join("\n");
}
return String(content);
}
function toContentBlocks(content) {
if (content == null) return [{ type: OPENAI_BLOCK.TEXT, text: "" }];
if (typeof content === "string")
return [{ type: OPENAI_BLOCK.TEXT, text: content }];
if (Array.isArray(content)) {
const blocks = [];
for (const part of content) {
if (typeof part === "string") {
blocks.push({ type: OPENAI_BLOCK.TEXT, text: part });
} 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);
}
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 });
}
}
}
return blocks.length ? blocks : [{ type: OPENAI_BLOCK.TEXT, text: "" }];
}
return [{ type: OPENAI_BLOCK.TEXT, text: String(content) }];
}
function safeParseJson(s) {
if (s == null) return {};
if (typeof s !== "string") return s;
try {
return JSON.parse(s);
} catch {
return {};
}
}
function convertMessages(messages = []) {
const out = [];
const systemTexts = [];
for (const m of messages) {
if (!m) continue;
const role = m.role;
if (role === ROLE.SYSTEM) {
const t = flattenText(m.content);
if (t) systemTexts.push(t);
continue;
}
if (role === ROLE.TOOL) {
const value =
typeof m.content === "string" ? m.content : flattenText(m.content);
out.push({
role: ROLE.TOOL,
content: [
{
type: "tool-result",
toolCallId: m.tool_call_id || "",
toolName: m.name || "",
output: { type: "text", value },
},
],
});
continue;
}
if (role === ROLE.ASSISTANT) {
const blocks = [];
const text = flattenText(m.content);
if (text) blocks.push({ type: OPENAI_BLOCK.TEXT, text });
if (Array.isArray(m.tool_calls)) {
for (const tc of m.tool_calls) {
const fn = tc.function || {};
blocks.push({
type: "tool-call",
toolCallId: tc.id || "",
toolName: fn.name || "",
input: safeParseJson(fn.arguments),
});
}
}
out.push({
role: ROLE.ASSISTANT,
content: blocks.length
? blocks
: [{ type: OPENAI_BLOCK.TEXT, text: "" }],
});
continue;
}
out.push({ role: ROLE.USER, content: toContentBlocks(m.content) });
}
return { messages: out, system: systemTexts.join("\n\n") };
}
function convertTools(tools) {
if (!Array.isArray(tools) || tools.length === 0) return undefined;
const result = [];
for (const t of tools) {
if (!t) continue;
if (t.type === OPENAI_BLOCK.FUNCTION && t.function) {
result.push({
name: t.function.name,
description: t.function.description,
input_schema: t.function.parameters || { type: "object" },
});
} else if (t.name && (t.input_schema || t.parameters)) {
result.push({
name: t.name,
description: t.description,
input_schema: t.input_schema || t.parameters,
});
}
}
return result.length ? result : undefined;
}
export function openaiToCommandCodeRequest(
model,
body,
stream /* , credentials */,
) {
const { messages, system } = convertMessages(body.messages);
const params = {
model,
messages,
stream: stream !== false,
max_tokens: body.max_tokens ?? body.max_output_tokens ?? DEFAULT_MAX_TOKENS,
temperature: body.temperature ?? 0.3,
};
if (system) params.system = system;
const tools = convertTools(body.tools);
if (tools) params.tools = tools;
if (body.top_p != null) params.top_p = body.top_p;
const today = new Date().toISOString().slice(0, 10);
// environment format mirrors the official command-code CLI (getEnvironmentInfo):
// `${platform}-${arch}, Node.js ${version}` (e.g. "darwin-arm64, Node.js v24.16.0").
const environment = `${process.platform}-${process.arch}, Node.js ${process.version}`;
return {
threadId: randomUUID(),
memory: "",
config: {
workingDir: process.cwd(),
date: today,
environment,
structure: [],
isGitRepo: false,
currentBranch: "",
mainBranch: "",
gitStatus: "",
recentCommits: [],
},
params,
};
}
register(FORMATS.OPENAI, FORMATS.COMMANDCODE, openaiToCommandCodeRequest, null);