mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat(providers): add Command Code provider (#2199)
Integrated into release/v3.8.0 after syncing the contributor branch, removing unrelated workflow/package-lock changes, and validating Command Code provider, auth, validation, and Responses coverage locally.
This commit is contained in:
@@ -156,6 +156,135 @@ const KIMI_CODING_SHARED = {
|
||||
const buildModels = (ids: readonly string[]): RegistryModel[] =>
|
||||
ids.map((id) => ({ id, name: id }));
|
||||
|
||||
const COMMAND_CODE_MODELS: RegistryModel[] = [
|
||||
{
|
||||
id: "claude-opus-4-7",
|
||||
name: "Claude Opus 4.7 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 200000,
|
||||
maxOutputTokens: 32000,
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4-6",
|
||||
name: "Claude Opus 4.6 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 200000,
|
||||
maxOutputTokens: 32000,
|
||||
},
|
||||
{
|
||||
id: "claude-sonnet-4-6",
|
||||
name: "Claude Sonnet 4.6 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 200000,
|
||||
maxOutputTokens: 16384,
|
||||
},
|
||||
{
|
||||
id: "claude-haiku-4-5-20251001",
|
||||
name: "Claude Haiku 4.5 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 200000,
|
||||
maxOutputTokens: 8192,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.5",
|
||||
name: "GPT-5.5 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 256000,
|
||||
maxOutputTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 256000,
|
||||
maxOutputTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.3-codex",
|
||||
name: "GPT-5.3 Codex (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 256000,
|
||||
maxOutputTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "gpt-5.4-mini",
|
||||
name: "GPT-5.4 Mini (CC)",
|
||||
supportsReasoning: false,
|
||||
contextLength: 256000,
|
||||
maxOutputTokens: 128000,
|
||||
},
|
||||
{
|
||||
id: "deepseek/deepseek-v4-pro",
|
||||
name: "DeepSeek V4 Pro (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 1000000,
|
||||
maxOutputTokens: 384000,
|
||||
},
|
||||
{
|
||||
id: "deepseek/deepseek-v4-flash",
|
||||
name: "DeepSeek V4 Flash (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 1000000,
|
||||
maxOutputTokens: 384000,
|
||||
},
|
||||
{
|
||||
id: "moonshotai/Kimi-K2.6",
|
||||
name: "Kimi K2.6 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 262144,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
{
|
||||
id: "moonshotai/Kimi-K2.5",
|
||||
name: "Kimi K2.5 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 262144,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
{
|
||||
id: "zai-org/GLM-5.1",
|
||||
name: "GLM-5.1 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 200000,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
{
|
||||
id: "zai-org/GLM-5",
|
||||
name: "GLM-5 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 200000,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
{
|
||||
id: "MiniMaxAI/MiniMax-M2.7",
|
||||
name: "MiniMax M2.7 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 1048576,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
{
|
||||
id: "MiniMaxAI/MiniMax-M2.5",
|
||||
name: "MiniMax M2.5 (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 1048576,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
{
|
||||
id: "Qwen/Qwen3.6-Max-Preview",
|
||||
name: "Qwen 3.6 Max (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 1000000,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
{
|
||||
id: "Qwen/Qwen3.6-Plus",
|
||||
name: "Qwen 3.6 Plus (CC)",
|
||||
supportsReasoning: true,
|
||||
contextLength: 1000000,
|
||||
maxOutputTokens: 131072,
|
||||
},
|
||||
];
|
||||
|
||||
const GPT_5_5_CONTEXT_LENGTH = 1050000;
|
||||
const GPT_5_5_CODEX_CAPABILITIES = {
|
||||
targetFormat: "openai-responses",
|
||||
@@ -991,6 +1120,20 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
passthroughModels: true,
|
||||
},
|
||||
|
||||
"command-code": {
|
||||
id: "command-code",
|
||||
alias: "cmd",
|
||||
format: "openai",
|
||||
executor: "command-code",
|
||||
baseUrl: "https://api.commandcode.ai",
|
||||
chatPath: "/alpha/generate",
|
||||
authType: "apikey",
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer ",
|
||||
defaultContextLength: 200000,
|
||||
models: COMMAND_CODE_MODELS,
|
||||
},
|
||||
|
||||
openrouter: {
|
||||
id: "openrouter",
|
||||
alias: "openrouter",
|
||||
|
||||
545
open-sse/executors/commandCode.ts
Normal file
545
open-sse/executors/commandCode.ts
Normal file
@@ -0,0 +1,545 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import { REGISTRY } from "../config/providerRegistry.ts";
|
||||
import { BaseExecutor, mergeUpstreamExtraHeaders, type ExecuteInput } from "./base.ts";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
const COMMAND_CODE_VERSION = "0.24.1";
|
||||
const MAX_COMMAND_CODE_TOKENS = 200_000;
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
function isRecord(value: unknown): value is JsonRecord {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function asRecordArray(value: unknown): JsonRecord[] {
|
||||
return Array.isArray(value) ? value.filter(isRecord) : [];
|
||||
}
|
||||
|
||||
function stringValue(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function numberValue(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function recordOrEmpty(value: unknown): JsonRecord {
|
||||
if (isRecord(value)) return value;
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(value);
|
||||
if (isRecord(parsed)) return parsed;
|
||||
} catch {
|
||||
// Tool argument fragments may be incomplete in streamed deltas.
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function normalizeContentText(content: unknown): string {
|
||||
if (typeof content === "string") return content;
|
||||
return asRecordArray(content)
|
||||
.filter((part) => part.type === "text")
|
||||
.map((part) => stringValue(part.text) || "")
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function convertTools(tools: unknown): unknown[] {
|
||||
return asRecordArray(tools).map((tool) => {
|
||||
const fn = isRecord(tool.function) ? tool.function : tool;
|
||||
return {
|
||||
type: "function",
|
||||
name: stringValue(fn.name) || "",
|
||||
description: stringValue(fn.description) || "",
|
||||
input_schema: isRecord(fn.parameters) ? fn.parameters : {},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function completeToolCallIds(messages: JsonRecord[]): Set<string> {
|
||||
const callIds = new Set<string>();
|
||||
const resultIds = new Set<string>();
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.role === "assistant") {
|
||||
for (const call of asRecordArray(message.tool_calls)) {
|
||||
const id = stringValue(call.id);
|
||||
if (id) callIds.add(id);
|
||||
}
|
||||
} else if (message.role === "tool") {
|
||||
const id = stringValue(message.tool_call_id);
|
||||
if (id) resultIds.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
return new Set([...callIds].filter((id) => resultIds.has(id)));
|
||||
}
|
||||
|
||||
function convertMessages(messages: unknown): { system: string; messages: unknown[] } {
|
||||
const source = asRecordArray(messages);
|
||||
const pairedToolCallIds = completeToolCallIds(source);
|
||||
const out: unknown[] = [];
|
||||
const system: string[] = [];
|
||||
|
||||
for (const message of source) {
|
||||
const role = stringValue(message.role);
|
||||
if (role === "system" || role === "developer") {
|
||||
const text = normalizeContentText(message.content);
|
||||
if (text) system.push(text);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (role === "user") {
|
||||
out.push({ role: "user", content: message.content ?? "" });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (role === "assistant") {
|
||||
const parts: unknown[] = [];
|
||||
const text = normalizeContentText(message.content);
|
||||
if (text) parts.push({ type: "text", text });
|
||||
|
||||
for (const call of asRecordArray(message.tool_calls)) {
|
||||
const id = stringValue(call.id) || "";
|
||||
if (!id || !pairedToolCallIds.has(id)) continue;
|
||||
const fn = isRecord(call.function) ? call.function : {};
|
||||
parts.push({
|
||||
type: "tool-call",
|
||||
toolCallId: id,
|
||||
toolName: stringValue(fn.name) || "",
|
||||
input: recordOrEmpty(fn.arguments),
|
||||
});
|
||||
}
|
||||
|
||||
if (parts.length > 0) out.push({ role: "assistant", content: parts });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (role === "tool") {
|
||||
const toolCallId = stringValue(message.tool_call_id) || "";
|
||||
if (!toolCallId || !pairedToolCallIds.has(toolCallId)) continue;
|
||||
out.push({
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId,
|
||||
toolName: stringValue(message.name) || "",
|
||||
output: { type: "text", value: normalizeContentText(message.content) },
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { system: system.join("\n\n"), messages: out };
|
||||
}
|
||||
|
||||
function clampMaxTokens(value: unknown): number {
|
||||
const numeric = numberValue(value) ?? MAX_COMMAND_CODE_TOKENS;
|
||||
return Math.max(1, Math.min(Math.floor(numeric), MAX_COMMAND_CODE_TOKENS));
|
||||
}
|
||||
|
||||
function buildCommandCodeBody(model: string, body: unknown): JsonRecord {
|
||||
const input = isRecord(body) ? body : {};
|
||||
const converted = convertMessages(input.messages);
|
||||
const explicitSystem = typeof input.system === "string" ? input.system : "";
|
||||
const system = [converted.system, explicitSystem].filter(Boolean).join("\n\n");
|
||||
|
||||
return {
|
||||
config: {
|
||||
workingDir: "/workspace",
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
environment: "omniroute",
|
||||
structure: [],
|
||||
isGitRepo: false,
|
||||
currentBranch: "",
|
||||
mainBranch: "",
|
||||
gitStatus: "",
|
||||
recentCommits: [],
|
||||
},
|
||||
memory: "",
|
||||
taste: "",
|
||||
skills: null,
|
||||
permissionMode: "standard",
|
||||
params: {
|
||||
model,
|
||||
messages: converted.messages,
|
||||
tools: convertTools(input.tools),
|
||||
system,
|
||||
max_tokens: clampMaxTokens(input.max_tokens ?? input.max_completion_tokens),
|
||||
stream: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function parseStreamLine(line: string): unknown | undefined {
|
||||
let trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith(":") || trimmed.startsWith("event:")) return undefined;
|
||||
if (trimmed.startsWith("data:")) trimmed = trimmed.slice(5).trim();
|
||||
if (!trimmed || trimmed === "[DONE]") return undefined;
|
||||
|
||||
try {
|
||||
return JSON.parse(trimmed);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function mapFinishReason(reason: unknown): "stop" | "length" | "tool_calls" {
|
||||
if (reason === "tool-calls" || reason === "tool_calls" || reason === "toolUse")
|
||||
return "tool_calls";
|
||||
if (
|
||||
reason === "length" ||
|
||||
reason === "max_tokens" ||
|
||||
reason === "max-tokens" ||
|
||||
reason === "max_output_tokens"
|
||||
) {
|
||||
return "length";
|
||||
}
|
||||
return "stop";
|
||||
}
|
||||
|
||||
function chatCompletionChunk(
|
||||
id: string,
|
||||
model: string,
|
||||
delta: JsonRecord,
|
||||
finishReason: unknown = null
|
||||
) {
|
||||
return {
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model,
|
||||
choices: [{ index: 0, delta, finish_reason: finishReason }],
|
||||
};
|
||||
}
|
||||
|
||||
function sse(data: unknown): Uint8Array {
|
||||
return encoder.encode(`data: ${JSON.stringify(data)}\n\n`);
|
||||
}
|
||||
|
||||
type AggregateState = {
|
||||
content: string;
|
||||
reasoning: string;
|
||||
toolCalls: JsonRecord[];
|
||||
finishReason: "stop" | "length" | "tool_calls";
|
||||
usage: JsonRecord | null;
|
||||
};
|
||||
|
||||
function applyEventToAggregate(event: JsonRecord, state: AggregateState): void {
|
||||
switch (event.type) {
|
||||
case "text-delta":
|
||||
state.content += stringValue(event.text) || "";
|
||||
break;
|
||||
case "reasoning-delta":
|
||||
state.reasoning += stringValue(event.text) || "";
|
||||
break;
|
||||
case "tool-call": {
|
||||
const args = recordOrEmpty(event.input ?? event.args ?? event.arguments);
|
||||
state.toolCalls.push({
|
||||
id: stringValue(event.toolCallId) || stringValue(event.id) || randomUUID(),
|
||||
type: "function",
|
||||
function: {
|
||||
name: stringValue(event.toolName) || stringValue(event.name) || "",
|
||||
arguments: JSON.stringify(args),
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "finish":
|
||||
state.finishReason = mapFinishReason(event.finishReason);
|
||||
state.usage = isRecord(event.totalUsage) ? event.totalUsage : null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyEventToAggregateOrThrow(event: JsonRecord, state: AggregateState): void {
|
||||
if (event.type === "error") {
|
||||
const error = isRecord(event.error) ? event.error : {};
|
||||
throw new Error(
|
||||
stringValue(error.message) || stringValue(event.error) || "Command Code stream error"
|
||||
);
|
||||
}
|
||||
|
||||
applyEventToAggregate(event, state);
|
||||
}
|
||||
|
||||
function usageFromCommandCode(usage: JsonRecord | null) {
|
||||
if (!usage) return undefined;
|
||||
const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : {};
|
||||
const prompt =
|
||||
(numberValue(usage.inputTokens) || 0) + (numberValue(details.cacheReadTokens) || 0);
|
||||
const completion = numberValue(usage.outputTokens) || 0;
|
||||
return {
|
||||
prompt_tokens: prompt,
|
||||
completion_tokens: completion,
|
||||
total_tokens: prompt + completion,
|
||||
};
|
||||
}
|
||||
|
||||
function createStreamResponse(
|
||||
upstream: Response,
|
||||
model: string,
|
||||
signal?: AbortSignal | null
|
||||
): Response {
|
||||
const id = `chatcmpl-${randomUUID()}`;
|
||||
const reader = upstream.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
let sentRole = false;
|
||||
let closed = false;
|
||||
const state: AggregateState = {
|
||||
content: "",
|
||||
reasoning: "",
|
||||
toolCalls: [],
|
||||
finishReason: "stop",
|
||||
usage: null,
|
||||
};
|
||||
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
if (!reader) {
|
||||
controller.error(new Error("Command Code response missing body"));
|
||||
return;
|
||||
}
|
||||
|
||||
const abort = () => {
|
||||
closed = true;
|
||||
reader.cancel().catch(() => undefined);
|
||||
controller.error(new DOMException("The operation was aborted", "AbortError"));
|
||||
};
|
||||
signal?.addEventListener("abort", abort, { once: true });
|
||||
|
||||
const emitEvent = (event: unknown) => {
|
||||
if (!isRecord(event) || closed) return;
|
||||
if (!sentRole) {
|
||||
sentRole = true;
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, { role: "assistant" })));
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case "text-delta": {
|
||||
const text = stringValue(event.text) || "";
|
||||
if (text) controller.enqueue(sse(chatCompletionChunk(id, model, { content: text })));
|
||||
state.content += text;
|
||||
break;
|
||||
}
|
||||
case "reasoning-delta": {
|
||||
const text = stringValue(event.text) || "";
|
||||
if (text) {
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, { reasoning_content: text })));
|
||||
state.reasoning += text;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "tool-call": {
|
||||
const index = state.toolCalls.length;
|
||||
const args = recordOrEmpty(event.input ?? event.args ?? event.arguments);
|
||||
const toolCall = {
|
||||
id: stringValue(event.toolCallId) || stringValue(event.id) || randomUUID(),
|
||||
type: "function",
|
||||
function: {
|
||||
name: stringValue(event.toolName) || stringValue(event.name) || "",
|
||||
arguments: JSON.stringify(args),
|
||||
},
|
||||
};
|
||||
state.toolCalls.push(toolCall);
|
||||
controller.enqueue(
|
||||
sse(chatCompletionChunk(id, model, { tool_calls: [{ index, ...toolCall }] }))
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "reasoning-end":
|
||||
break;
|
||||
case "finish": {
|
||||
state.finishReason = mapFinishReason(event.finishReason);
|
||||
state.usage = isRecord(event.totalUsage) ? event.totalUsage : null;
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, {}, state.finishReason)));
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
closed = true;
|
||||
controller.close();
|
||||
reader.cancel().catch(() => undefined);
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
const error = isRecord(event.error) ? event.error : {};
|
||||
throw new Error(
|
||||
stringValue(error.message) || stringValue(event.error) || "Command Code stream error"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const pump = async () => {
|
||||
try {
|
||||
for (;;) {
|
||||
if (closed) return;
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
for (const line of lines) emitEvent(parseStreamLine(line));
|
||||
}
|
||||
if (buffer.trim()) emitEvent(parseStreamLine(buffer));
|
||||
if (!closed) {
|
||||
if (!sentRole)
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, { role: "assistant" })));
|
||||
controller.enqueue(sse(chatCompletionChunk(id, model, {}, state.finishReason)));
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(error);
|
||||
} finally {
|
||||
signal?.removeEventListener("abort", abort);
|
||||
try {
|
||||
reader.releaseLock();
|
||||
} catch {
|
||||
// Reader may already be released/cancelled.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pump();
|
||||
},
|
||||
cancel() {
|
||||
closed = true;
|
||||
return reader?.cancel();
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache" },
|
||||
});
|
||||
}
|
||||
|
||||
async function createJsonResponse(
|
||||
upstream: Response,
|
||||
model: string,
|
||||
signal?: AbortSignal | null
|
||||
): Promise<Response> {
|
||||
const reader = upstream.body?.getReader();
|
||||
if (!reader) throw new Error("Command Code response missing body");
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
const state: AggregateState = {
|
||||
content: "",
|
||||
reasoning: "",
|
||||
toolCalls: [],
|
||||
finishReason: "stop",
|
||||
usage: null,
|
||||
};
|
||||
|
||||
try {
|
||||
for (;;) {
|
||||
if (signal?.aborted) throw new DOMException("The operation was aborted", "AbortError");
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
for (const line of lines) {
|
||||
const event = parseStreamLine(line);
|
||||
if (!isRecord(event)) continue;
|
||||
applyEventToAggregateOrThrow(event, state);
|
||||
}
|
||||
}
|
||||
if (buffer.trim()) {
|
||||
const event = parseStreamLine(buffer);
|
||||
if (isRecord(event)) applyEventToAggregateOrThrow(event, state);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
await reader.cancel();
|
||||
} catch {
|
||||
// Reader may already be closed.
|
||||
}
|
||||
try {
|
||||
reader.releaseLock();
|
||||
} catch {
|
||||
// Reader may already be released.
|
||||
}
|
||||
}
|
||||
|
||||
const message: JsonRecord = { role: "assistant", content: state.content };
|
||||
if (state.reasoning) message.reasoning_content = state.reasoning;
|
||||
if (state.toolCalls.length > 0) message.tool_calls = state.toolCalls;
|
||||
|
||||
const payload: JsonRecord = {
|
||||
id: `chatcmpl-${randomUUID()}`,
|
||||
object: "chat.completion",
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model,
|
||||
choices: [{ index: 0, message, finish_reason: state.finishReason }],
|
||||
};
|
||||
const usage = usageFromCommandCode(state.usage);
|
||||
if (usage) payload.usage = usage;
|
||||
|
||||
return new Response(JSON.stringify(payload), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
export class CommandCodeExecutor extends BaseExecutor {
|
||||
constructor(provider = "command-code") {
|
||||
super(provider, REGISTRY["command-code"]);
|
||||
}
|
||||
|
||||
buildUrl() {
|
||||
const baseUrl = (this.config.baseUrl || "https://api.commandcode.ai").replace(/\/$/, "");
|
||||
return `${baseUrl}${this.config.chatPath || "/alpha/generate"}`;
|
||||
}
|
||||
|
||||
async execute({ model, body, stream, credentials, signal, upstreamExtraHeaders }: ExecuteInput) {
|
||||
const apiKey = credentials?.apiKey || credentials?.accessToken;
|
||||
if (!apiKey) throw new Error("Command Code API key required");
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"x-command-code-version": COMMAND_CODE_VERSION,
|
||||
"x-cli-environment": "production",
|
||||
"x-project-slug": "pi-cc",
|
||||
"x-taste-learning": "false",
|
||||
"x-co-flag": "false",
|
||||
"x-session-id": randomUUID(),
|
||||
};
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
|
||||
const transformedBody = buildCommandCodeBody(model, body);
|
||||
const url = this.buildUrl();
|
||||
const upstream = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(transformedBody),
|
||||
signal: signal || undefined,
|
||||
});
|
||||
|
||||
if (!upstream.ok) {
|
||||
const errorText = await upstream.text().catch(() => "");
|
||||
return {
|
||||
response: new Response(errorText || `Command Code API error ${upstream.status}`, {
|
||||
status: upstream.status,
|
||||
statusText: upstream.statusText,
|
||||
headers: upstream.headers,
|
||||
}),
|
||||
url,
|
||||
headers,
|
||||
transformedBody,
|
||||
};
|
||||
}
|
||||
|
||||
const response = stream
|
||||
? createStreamResponse(upstream, model, signal)
|
||||
: await createJsonResponse(upstream, model, signal);
|
||||
|
||||
return { response, url, headers, transformedBody };
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { ChatGptWebExecutor } from "./chatgpt-web.ts";
|
||||
import { BlackboxWebExecutor } from "./blackbox-web.ts";
|
||||
import { MuseSparkWebExecutor } from "./muse-spark-web.ts";
|
||||
import { AzureOpenAIExecutor } from "./azure-openai.ts";
|
||||
import { CommandCodeExecutor } from "./commandCode.ts";
|
||||
import { GitlabExecutor } from "./gitlab.ts";
|
||||
import { NlpCloudExecutor } from "./nlpcloud.ts";
|
||||
import { PetalsExecutor } from "./petals.ts";
|
||||
@@ -39,6 +40,8 @@ const executors = {
|
||||
glmt: new GlmExecutor("glmt"),
|
||||
cu: new CursorExecutor(), // Alias for cursor
|
||||
"azure-openai": new AzureOpenAIExecutor(),
|
||||
"command-code": new CommandCodeExecutor(),
|
||||
cmd: new CommandCodeExecutor(), // Alias
|
||||
gitlab: new GitlabExecutor(),
|
||||
"gitlab-duo": new GitlabExecutor("gitlab-duo"),
|
||||
nlpcloud: new NlpCloudExecutor(),
|
||||
@@ -105,6 +108,7 @@ export { ChatGptWebExecutor } from "./chatgpt-web.ts";
|
||||
export { BlackboxWebExecutor } from "./blackbox-web.ts";
|
||||
export { MuseSparkWebExecutor } from "./muse-spark-web.ts";
|
||||
export { AzureOpenAIExecutor } from "./azure-openai.ts";
|
||||
export { CommandCodeExecutor } from "./commandCode.ts";
|
||||
export { GitlabExecutor } from "./gitlab.ts";
|
||||
export { NlpCloudExecutor } from "./nlpcloud.ts";
|
||||
export { PetalsExecutor } from "./petals.ts";
|
||||
|
||||
42
public/providers/command-code.svg
Normal file
42
public/providers/command-code.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2305 6994 c-371 -13 -682 -39 -893 -74 -598 -103 -963 -350 -1172
|
||||
-795 -126 -267 -186 -576 -222 -1130 -19 -287 -18 -2669 0 -2950 53 -794 172
|
||||
-1175 464 -1481 286 -298 672 -437 1367 -489 616 -47 2694 -46 3267 0 685 56
|
||||
1056 186 1339 470 289 289 424 685 475 1395 22 310 32 1055 27 1915 -6 868
|
||||
-13 1102 -42 1417 -55 589 -188 950 -448 1216 -305 311 -678 435 -1487 493
|
||||
-141 10 -2428 21 -2675 13z m33 -1350 c322 -66 580 -324 646 -646 12 -57 16
|
||||
-136 16 -303 l0 -225 474 0 474 0 5 243 c4 201 8 255 25 318 67 248 222 437
|
||||
447 545 138 67 195 79 370 79 143 -1 154 -2 245 -33 279 -96 476 -307 552
|
||||
-587 29 -111 29 -297 -1 -410 -69 -261 -263 -478 -511 -572 -113 -43 -194 -53
|
||||
-431 -53 l-219 0 0 -474 0 -474 238 -5 c253 -5 310 -14 432 -64 243 -99 438
|
||||
-327 495 -576 47 -209 17 -419 -88 -607 -57 -102 -205 -250 -307 -307 -433
|
||||
-242 -960 -71 -1169 379 -63 134 -74 200 -79 466 l-4 232 -474 0 -474 0 0
|
||||
-219 c0 -149 -5 -243 -14 -294 -60 -312 -299 -565 -611 -649 -112 -30 -298
|
||||
-30 -410 0 -519 139 -776 708 -534 1185 106 210 301 365 538 429 63 17 117 21
|
||||
319 25 l242 5 0 474 0 474 -225 0 c-252 0 -329 11 -452 62 -485 201 -664 796
|
||||
-372 1232 116 174 310 306 514 349 93 20 248 21 343 1z"/>
|
||||
<path d="M2080 5174 c-187 -50 -302 -241 -256 -425 31 -119 118 -216 231 -256
|
||||
42 -15 84 -18 260 -18 l210 0 0 210 c0 176 -3 218 -18 260 -39 111 -136 200
|
||||
-252 230 -72 18 -103 18 -175 -1z"/>
|
||||
<path d="M4705 5176 c-75 -19 -125 -49 -178 -105 -86 -92 -91 -112 -95 -373
|
||||
l-3 -228 185 0 c264 0 337 20 429 119 74 79 92 127 92 241 0 83 -3 102 -27
|
||||
150 -74 150 -249 236 -403 196z"/>
|
||||
<path d="M3000 3525 l0 -475 475 0 475 0 0 475 0 475 -475 0 -475 0 0 -475z"/>
|
||||
<path d="M2051 2550 c-59 -22 -68 -27 -129 -84 -178 -167 -127 -463 98 -574
|
||||
48 -24 67 -27 150 -27 114 0 162 18 241 92 99 92 119 165 119 428 l0 185 -212
|
||||
0 c-184 -1 -220 -3 -267 -20z"/>
|
||||
<path d="M4432 2348 l3 -224 33 -66 c38 -77 92 -130 171 -167 48 -22 70 -26
|
||||
146 -26 82 0 97 3 157 33 77 38 130 92 167 171 22 47 26 70 26 146 0 76 -4 99
|
||||
-26 146 -37 79 -90 133 -167 171 l-66 33 -224 3 -223 3 3 -223z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -556,6 +556,9 @@ interface AddApiKeyModalProps {
|
||||
isCompatible?: boolean;
|
||||
isAnthropic?: boolean;
|
||||
isCcCompatible?: boolean;
|
||||
isCommandCode?: boolean;
|
||||
commandCodeAuthState?: CommandCodeAuthFlowState;
|
||||
onStartCommandCodeAuth?: () => void;
|
||||
onSave: (data: {
|
||||
name: string;
|
||||
apiKey?: string;
|
||||
@@ -566,6 +569,23 @@ interface AddApiKeyModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type CommandCodeAuthFlowState = {
|
||||
phase:
|
||||
| "idle"
|
||||
| "starting"
|
||||
| "polling"
|
||||
| "received"
|
||||
| "applying"
|
||||
| "applied"
|
||||
| "expired"
|
||||
| "error";
|
||||
state: string;
|
||||
authUrl: string;
|
||||
callbackUrl: string;
|
||||
expiresAt: string | null;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
interface EditConnectionModalConnection {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@@ -981,6 +1001,14 @@ export default function ProviderDetailPage() {
|
||||
const [showOAuthModal, _setShowOAuthModal] = useState(false);
|
||||
const [reauthConnection, setReauthConnection] = useState<ConnectionRowConnection | null>(null);
|
||||
const [showAddApiKeyModal, setShowAddApiKeyModal] = useState(false);
|
||||
const [commandCodeAuthState, setCommandCodeAuthState] = useState<CommandCodeAuthFlowState>({
|
||||
phase: "idle",
|
||||
state: "",
|
||||
authUrl: "",
|
||||
callbackUrl: "",
|
||||
expiresAt: null,
|
||||
message: "",
|
||||
});
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showEditNodeModal, setShowEditNodeModal] = useState(false);
|
||||
const [selectedConnection, setSelectedConnection] = useState(null);
|
||||
@@ -1027,8 +1055,11 @@ export default function ProviderDetailPage() {
|
||||
const [savingCodexGlobalFastServiceTier, setSavingCodexGlobalFastServiceTier] = useState(false);
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
const [batchDeleting, setBatchDeleting] = useState(false);
|
||||
const commandCodeAuthWindowRef = useRef<Window | null>(null);
|
||||
const commandCodeAuthTimerRef = useRef<number | null>(null);
|
||||
const isOpenAICompatible = isOpenAICompatibleProvider(providerId);
|
||||
const isCcCompatible = isClaudeCodeCompatibleProvider(providerId);
|
||||
const isCommandCode = providerId === "command-code";
|
||||
const isAnthropicCompatible =
|
||||
isAnthropicCompatibleProvider(providerId) && !isClaudeCodeCompatibleProvider(providerId);
|
||||
const isCompatible = isOpenAICompatible || isAnthropicCompatible || isCcCompatible;
|
||||
@@ -1436,6 +1467,257 @@ export default function ProviderDetailPage() {
|
||||
setShowAddApiKeyModal(true);
|
||||
}, [isOAuth]);
|
||||
|
||||
const clearCommandCodeAuthTimer = useCallback(() => {
|
||||
if (commandCodeAuthTimerRef.current !== null) {
|
||||
window.clearTimeout(commandCodeAuthTimerRef.current);
|
||||
commandCodeAuthTimerRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearCommandCodeAuthTimer();
|
||||
commandCodeAuthWindowRef.current?.close?.();
|
||||
};
|
||||
}, [clearCommandCodeAuthTimer]);
|
||||
|
||||
const handleCloseAddApiKeyModal = useCallback(() => {
|
||||
clearCommandCodeAuthTimer();
|
||||
commandCodeAuthWindowRef.current?.close?.();
|
||||
commandCodeAuthWindowRef.current = null;
|
||||
setCommandCodeAuthState({
|
||||
phase: "idle",
|
||||
state: "",
|
||||
authUrl: "",
|
||||
callbackUrl: "",
|
||||
expiresAt: null,
|
||||
message: "",
|
||||
});
|
||||
setShowAddApiKeyModal(false);
|
||||
}, [clearCommandCodeAuthTimer]);
|
||||
|
||||
const handleCommandCodeAuthApply = useCallback(
|
||||
async (state: string, connectionId?: string, name?: string, setDefault?: boolean) => {
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "applying",
|
||||
message: "Applying browser-approved key…",
|
||||
}));
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/providers/command-code/auth/apply", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ state, connectionId, name, setDefault }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
if (!res.ok) {
|
||||
const errorMessage = data.error || "Failed to apply Command Code auth";
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "error",
|
||||
message: errorMessage,
|
||||
}));
|
||||
notify.error(errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "applied",
|
||||
message: "Command Code connected",
|
||||
}));
|
||||
commandCodeAuthWindowRef.current?.close?.();
|
||||
commandCodeAuthWindowRef.current = null;
|
||||
await fetchConnections();
|
||||
handleCloseAddApiKeyModal();
|
||||
notify.success("Command Code connection added");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error applying Command Code auth:", error);
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "error",
|
||||
message: "Failed to apply Command Code auth",
|
||||
}));
|
||||
notify.error("Failed to apply Command Code auth");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[fetchConnections, handleCloseAddApiKeyModal, notify]
|
||||
);
|
||||
|
||||
const handleStartCommandCodeAuth = useCallback(async () => {
|
||||
if (commandCodeAuthState.phase === "starting" || commandCodeAuthState.phase === "polling") {
|
||||
return;
|
||||
}
|
||||
|
||||
clearCommandCodeAuthTimer();
|
||||
commandCodeAuthWindowRef.current?.close?.();
|
||||
|
||||
const popup = window.open("about:blank", "_blank");
|
||||
setCommandCodeAuthState({
|
||||
phase: "starting",
|
||||
state: "",
|
||||
authUrl: "",
|
||||
callbackUrl: "",
|
||||
expiresAt: null,
|
||||
message: "Opening Command Code Studio…",
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/providers/command-code/auth/start", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
if (!res.ok || !data.state || !data.authUrl) {
|
||||
const errorMessage = data.error || "Failed to start Command Code auth";
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "error",
|
||||
message: errorMessage,
|
||||
}));
|
||||
notify.error(errorMessage);
|
||||
popup?.close?.();
|
||||
return;
|
||||
}
|
||||
|
||||
setCommandCodeAuthState({
|
||||
phase: "polling",
|
||||
state: data.state,
|
||||
authUrl: data.authUrl,
|
||||
callbackUrl: data.callbackUrl || "",
|
||||
expiresAt: data.expiresAt || null,
|
||||
message: "Open the auth URL, approve access, then paste the returned key/JSON/URL below…",
|
||||
});
|
||||
|
||||
if (popup) {
|
||||
try {
|
||||
popup.opener = null;
|
||||
} catch {
|
||||
// Ignore opener cleanup failures.
|
||||
}
|
||||
popup.location.href = data.authUrl;
|
||||
commandCodeAuthWindowRef.current = popup;
|
||||
} else {
|
||||
const fallbackPopup = window.open(data.authUrl, "_blank", "noopener,noreferrer");
|
||||
if (!fallbackPopup) {
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "error",
|
||||
message: "Popup blocked. Please allow popups and try Command Code Connect again.",
|
||||
}));
|
||||
notify.error("Popup blocked. Please allow popups and try Command Code Connect again.");
|
||||
return;
|
||||
}
|
||||
commandCodeAuthWindowRef.current = fallbackPopup;
|
||||
}
|
||||
|
||||
const deadline = data.expiresAt ? new Date(data.expiresAt).getTime() : Date.now() + 180000;
|
||||
const poll = async () => {
|
||||
if (Date.now() >= deadline) {
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "expired",
|
||||
message: "Command Code link expired",
|
||||
}));
|
||||
commandCodeAuthWindowRef.current?.close?.();
|
||||
commandCodeAuthWindowRef.current = null;
|
||||
notify.error("Command Code auth expired");
|
||||
clearCommandCodeAuthTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const statusRes = await fetch(
|
||||
`/api/providers/command-code/auth/status?state=${encodeURIComponent(data.state)}`,
|
||||
{ method: "GET", cache: "no-store" }
|
||||
);
|
||||
const statusData = await statusRes.json().catch(() => ({}));
|
||||
const status = String(statusData.status || statusData.state || statusData.phase || "")
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
|
||||
if (status === "expired") {
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "expired",
|
||||
message: "Command Code link expired",
|
||||
}));
|
||||
commandCodeAuthWindowRef.current?.close?.();
|
||||
commandCodeAuthWindowRef.current = null;
|
||||
notify.error("Command Code auth expired");
|
||||
clearCommandCodeAuthTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === "applied") {
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "applied",
|
||||
message: "Command Code connected",
|
||||
}));
|
||||
commandCodeAuthWindowRef.current?.close?.();
|
||||
commandCodeAuthWindowRef.current = null;
|
||||
await fetchConnections();
|
||||
handleCloseAddApiKeyModal();
|
||||
notify.success("Command Code connection added");
|
||||
clearCommandCodeAuthTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === "received") {
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "received",
|
||||
message: "Browser approved, applying…",
|
||||
}));
|
||||
clearCommandCodeAuthTimer();
|
||||
await handleCommandCodeAuthApply(
|
||||
data.state,
|
||||
statusData.connectionId,
|
||||
statusData.name,
|
||||
statusData.setDefault
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Keep polling until the contract reports a terminal state or timeout.
|
||||
}
|
||||
|
||||
commandCodeAuthTimerRef.current = window.setTimeout(poll, 2000);
|
||||
};
|
||||
|
||||
commandCodeAuthTimerRef.current = window.setTimeout(poll, 1000);
|
||||
} catch (error) {
|
||||
console.error("Error starting Command Code auth:", error);
|
||||
setCommandCodeAuthState((current) => ({
|
||||
...current,
|
||||
phase: "error",
|
||||
message: "Failed to start Command Code auth",
|
||||
}));
|
||||
notify.error("Failed to start Command Code auth");
|
||||
popup?.close?.();
|
||||
commandCodeAuthWindowRef.current = null;
|
||||
clearCommandCodeAuthTimer();
|
||||
}
|
||||
}, [
|
||||
clearCommandCodeAuthTimer,
|
||||
handleCloseAddApiKeyModal,
|
||||
commandCodeAuthState.phase,
|
||||
fetchConnections,
|
||||
handleCommandCodeAuthApply,
|
||||
notify,
|
||||
]);
|
||||
|
||||
const handleOpenCommandCodeConnect = useCallback(() => {
|
||||
setShowAddApiKeyModal(true);
|
||||
void handleStartCommandCodeAuth();
|
||||
}, [handleStartCommandCodeAuth]);
|
||||
|
||||
const handleSaveApiKey = async (formData) => {
|
||||
try {
|
||||
const res = await fetch("/api/providers", {
|
||||
@@ -2950,13 +3232,44 @@ export default function ProviderDetailPage() {
|
||||
)}
|
||||
{!isCompatible ? (
|
||||
<>
|
||||
<Button size="sm" icon="add" onClick={openPrimaryAddFlow}>
|
||||
{providerSupportsPat ? "Add PAT" : t("add")}
|
||||
</Button>
|
||||
{providerId === "qoder" && (
|
||||
<Button size="sm" variant="secondary" onClick={() => setShowOAuthModal(true)}>
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
{isCommandCode ? (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
icon="open_in_new"
|
||||
loading={
|
||||
commandCodeAuthState.phase === "starting" ||
|
||||
commandCodeAuthState.phase === "polling" ||
|
||||
commandCodeAuthState.phase === "applying"
|
||||
}
|
||||
onClick={handleOpenCommandCodeConnect}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="add"
|
||||
onClick={() => setShowAddApiKeyModal(true)}
|
||||
>
|
||||
Manual API key
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button size="sm" icon="add" onClick={openPrimaryAddFlow}>
|
||||
{providerSupportsPat ? "Add PAT" : t("add")}
|
||||
</Button>
|
||||
{providerId === "qoder" && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => setShowOAuthModal(true)}
|
||||
>
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
@@ -2980,13 +3293,38 @@ export default function ProviderDetailPage() {
|
||||
<p className="text-sm text-text-muted mb-4">{t("addFirstConnectionHint")}</p>
|
||||
{!isCompatible && (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button icon="add" onClick={openPrimaryAddFlow}>
|
||||
{providerSupportsPat ? "Add PAT" : t("addConnection")}
|
||||
</Button>
|
||||
{providerId === "qoder" && (
|
||||
<Button variant="secondary" onClick={() => setShowOAuthModal(true)}>
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
{isCommandCode ? (
|
||||
<>
|
||||
<Button
|
||||
icon="open_in_new"
|
||||
loading={
|
||||
commandCodeAuthState.phase === "starting" ||
|
||||
commandCodeAuthState.phase === "polling" ||
|
||||
commandCodeAuthState.phase === "applying"
|
||||
}
|
||||
onClick={handleOpenCommandCodeConnect}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon="add"
|
||||
onClick={() => setShowAddApiKeyModal(true)}
|
||||
>
|
||||
Manual API key
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button icon="add" onClick={openPrimaryAddFlow}>
|
||||
{providerSupportsPat ? "Add PAT" : t("addConnection")}
|
||||
</Button>
|
||||
{providerId === "qoder" && (
|
||||
<Button variant="secondary" onClick={() => setShowOAuthModal(true)}>
|
||||
Experimental OAuth
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -3409,8 +3747,11 @@ export default function ProviderDetailPage() {
|
||||
isCompatible={isCompatible}
|
||||
isAnthropic={isAnthropicProtocolCompatible}
|
||||
isCcCompatible={isCcCompatible}
|
||||
isCommandCode={isCommandCode}
|
||||
commandCodeAuthState={commandCodeAuthState}
|
||||
onStartCommandCodeAuth={handleStartCommandCodeAuth}
|
||||
onSave={handleSaveApiKey}
|
||||
onClose={() => setShowAddApiKeyModal(false)}
|
||||
onClose={handleCloseAddApiKeyModal}
|
||||
/>
|
||||
)}
|
||||
{!isUpstreamProxyProvider && (
|
||||
@@ -5715,6 +6056,52 @@ function formatExcludedModelsInput(value: unknown): string {
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
function extractCommandCodeCredentialInput(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return "";
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed) as unknown;
|
||||
if (parsed && typeof parsed === "object") {
|
||||
const record = parsed as Record<string, unknown>;
|
||||
const direct = record.apiKey || record.api_key || record.key || record.token;
|
||||
if (typeof direct === "string" && direct.trim()) return direct.trim();
|
||||
const nested = record.data;
|
||||
if (nested && typeof nested === "object") {
|
||||
const nestedRecord = nested as Record<string, unknown>;
|
||||
const nestedKey = nestedRecord.apiKey || nestedRecord.api_key || nestedRecord.key;
|
||||
if (typeof nestedKey === "string" && nestedKey.trim()) return nestedKey.trim();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Not JSON; continue with URL/raw parsing.
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(trimmed);
|
||||
const key =
|
||||
url.searchParams.get("apiKey") ||
|
||||
url.searchParams.get("api_key") ||
|
||||
url.searchParams.get("key") ||
|
||||
url.searchParams.get("token");
|
||||
if (key?.trim()) return key.trim();
|
||||
const hash = url.hash.replace(/^#/, "");
|
||||
if (hash) {
|
||||
const hashParams = new URLSearchParams(hash);
|
||||
const hashKey =
|
||||
hashParams.get("apiKey") ||
|
||||
hashParams.get("api_key") ||
|
||||
hashParams.get("key") ||
|
||||
hashParams.get("token");
|
||||
if (hashKey?.trim()) return hashKey.trim();
|
||||
}
|
||||
} catch {
|
||||
// Not a URL; use the raw value.
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function AddApiKeyModal({
|
||||
isOpen,
|
||||
provider,
|
||||
@@ -5722,6 +6109,9 @@ function AddApiKeyModal({
|
||||
isCompatible,
|
||||
isAnthropic,
|
||||
isCcCompatible,
|
||||
isCommandCode,
|
||||
commandCodeAuthState,
|
||||
onStartCommandCodeAuth,
|
||||
onSave,
|
||||
onClose,
|
||||
}: AddApiKeyModalProps) {
|
||||
@@ -5744,6 +6134,18 @@ function AddApiKeyModal({
|
||||
const isWebSessionProvider = isGrokWeb || isPerplexityWeb || isBlackboxWeb || isMuseSparkWeb;
|
||||
const isPetals = provider === "petals";
|
||||
const apiKeyOptional = isSearxng || isPetals || isLocalSelfHostedProvider;
|
||||
const commandCodeAuthPhaseLabel = commandCodeAuthState
|
||||
? {
|
||||
idle: "Ready",
|
||||
starting: "Starting…",
|
||||
polling: "Waiting for browser…",
|
||||
received: "Browser approved",
|
||||
applying: "Applying key…",
|
||||
applied: "Connected",
|
||||
expired: "Link expired",
|
||||
error: "Connection failed",
|
||||
}[commandCodeAuthState.phase]
|
||||
: null;
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
@@ -5767,6 +6169,7 @@ function AddApiKeyModal({
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
const [copiedCommandCodeField, setCopiedCommandCodeField] = useState<string | null>(null);
|
||||
const apiCredentialLabel = isQoder
|
||||
? t("personalAccessTokenLabel")
|
||||
: isWebSessionProvider
|
||||
@@ -5811,12 +6214,15 @@ function AddApiKeyModal({
|
||||
setValidating(true);
|
||||
setSaveError(null);
|
||||
try {
|
||||
const credentialInput = isCommandCode
|
||||
? extractCommandCodeCredentialInput(formData.apiKey)
|
||||
: formData.apiKey;
|
||||
const res = await fetch("/api/providers/validate", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
provider,
|
||||
apiKey: formData.apiKey,
|
||||
apiKey: credentialInput,
|
||||
validationModelId: formData.validationModelId || undefined,
|
||||
customUserAgent: formData.customUserAgent.trim() || undefined,
|
||||
baseUrl: formData.baseUrl.trim() || undefined,
|
||||
@@ -5832,8 +6238,22 @@ function AddApiKeyModal({
|
||||
}
|
||||
};
|
||||
|
||||
const copyCommandCodeValue = async (value: string | undefined, key: string) => {
|
||||
if (!value) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
setCopiedCommandCodeField(key);
|
||||
window.setTimeout(() => setCopiedCommandCodeField(null), 1500);
|
||||
} catch {
|
||||
setSaveError("Copy failed. Select the text and copy it manually.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!provider || (!isCompatible && !apiKeyOptional && !formData.apiKey)) return;
|
||||
const credentialInput = isCommandCode
|
||||
? extractCommandCodeCredentialInput(formData.apiKey)
|
||||
: formData.apiKey;
|
||||
if (!provider || (!isCompatible && !apiKeyOptional && !credentialInput)) return;
|
||||
|
||||
setSaving(true);
|
||||
setSaveError(null);
|
||||
@@ -5863,7 +6283,7 @@ function AddApiKeyModal({
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
provider,
|
||||
apiKey: formData.apiKey,
|
||||
apiKey: credentialInput,
|
||||
validationModelId: formData.validationModelId || undefined,
|
||||
customUserAgent: formData.customUserAgent.trim() || undefined,
|
||||
baseUrl: formData.baseUrl.trim() || undefined,
|
||||
@@ -5883,7 +6303,7 @@ function AddApiKeyModal({
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
if (apiKeyOptional && !formData.apiKey) {
|
||||
if (apiKeyOptional && !credentialInput) {
|
||||
// Bypass validation block for local/optional providers when no key is provided
|
||||
console.debug("Validation failed but apiKey is optional; proceeding to save.");
|
||||
} else {
|
||||
@@ -5926,7 +6346,7 @@ function AddApiKeyModal({
|
||||
|
||||
const payload = {
|
||||
name: formData.name,
|
||||
apiKey: formData.apiKey.trim() || undefined,
|
||||
apiKey: credentialInput.trim() || undefined,
|
||||
priority: formData.priority,
|
||||
testStatus: "active",
|
||||
providerSpecificData:
|
||||
@@ -5961,6 +6381,84 @@ function AddApiKeyModal({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isCommandCode && onStartCommandCodeAuth && (
|
||||
<div className="rounded-lg border border-sky-500/20 bg-sky-500/10 px-3 py-3 text-sm">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="material-symbols-outlined mt-0.5 text-[18px] text-sky-500">
|
||||
open_in_new
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium text-text-main">Browser/manual connect</p>
|
||||
<p className="mt-1 text-xs text-text-muted">
|
||||
Open Command Code Studio, then paste the returned key/JSON/URL into the API key
|
||||
field below.
|
||||
</p>
|
||||
{commandCodeAuthState?.message && (
|
||||
<p className="mt-2 text-xs text-text-muted">
|
||||
{commandCodeAuthPhaseLabel}: {commandCodeAuthState.message}
|
||||
</p>
|
||||
)}
|
||||
{commandCodeAuthState?.authUrl && (
|
||||
<div className="mt-3 space-y-2">
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium text-text-main">Auth URL</p>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={commandCodeAuthState.authUrl}
|
||||
readOnly
|
||||
className="flex-1 font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon={copiedCommandCodeField === "authUrl" ? "check" : "content_copy"}
|
||||
onClick={() =>
|
||||
copyCommandCodeValue(commandCodeAuthState.authUrl, "authUrl")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{commandCodeAuthState.callbackUrl && (
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium text-text-main">Callback URL</p>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={commandCodeAuthState.callbackUrl}
|
||||
readOnly
|
||||
className="flex-1 font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon={
|
||||
copiedCommandCodeField === "callbackUrl" ? "check" : "content_copy"
|
||||
}
|
||||
onClick={() =>
|
||||
copyCommandCodeValue(commandCodeAuthState.callbackUrl, "callbackUrl")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
icon="open_in_new"
|
||||
loading={
|
||||
commandCodeAuthState?.phase === "starting" ||
|
||||
commandCodeAuthState?.phase === "polling" ||
|
||||
commandCodeAuthState?.phase === "applying"
|
||||
}
|
||||
onClick={onStartCommandCodeAuth}
|
||||
>
|
||||
Connect in browser
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
label={t("nameLabel")}
|
||||
value={formData.name}
|
||||
|
||||
101
src/app/api/providers/command-code/auth/apply/route.ts
Normal file
101
src/app/api/providers/command-code/auth/apply/route.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { consumeCommandCodeAuthSecret } from "@/lib/db/commandCodeAuth";
|
||||
import {
|
||||
createProviderConnection,
|
||||
getProviderConnectionById,
|
||||
updateProviderConnection,
|
||||
} from "@/lib/db/providers";
|
||||
import { sanitizeProviderSpecificDataForResponse } from "@/lib/providers/requestDefaults";
|
||||
|
||||
import { commandCodeApplySchema, noStoreJson, stateHashFromState } from "../shared";
|
||||
|
||||
function safeConnection(
|
||||
connection: Record<string, unknown> | null
|
||||
): Record<string, unknown> | null {
|
||||
if (!connection) return null;
|
||||
const result = { ...connection };
|
||||
delete result.apiKey;
|
||||
delete result.accessToken;
|
||||
delete result.refreshToken;
|
||||
delete result.idToken;
|
||||
if (result.providerSpecificData) {
|
||||
result.providerSpecificData = sanitizeProviderSpecificDataForResponse(
|
||||
result.providerSpecificData
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
let body: unknown;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return noStoreJson({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const parsed = commandCodeApplySchema.safeParse(body);
|
||||
if (!parsed.success) return noStoreJson({ error: "Invalid apply payload" }, { status: 400 });
|
||||
|
||||
let existing: Record<string, unknown> | null = null;
|
||||
if (parsed.data.connectionId) {
|
||||
existing = (await getProviderConnectionById(parsed.data.connectionId)) as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
if (!existing || existing.provider !== "command-code" || existing.authType !== "apikey") {
|
||||
return noStoreJson({ error: "Command Code API-key connection not found" }, { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
const consumed = consumeCommandCodeAuthSecret(stateHashFromState(parsed.data.state));
|
||||
if (!consumed) {
|
||||
return noStoreJson(
|
||||
{ error: "No received Command Code API key for this state" },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
let connection: Record<string, unknown> | null;
|
||||
if (parsed.data.connectionId && existing) {
|
||||
connection = (await updateProviderConnection(parsed.data.connectionId, {
|
||||
apiKey: consumed.apiKey,
|
||||
name: parsed.data.name || existing.name || consumed.metadata?.keyName || "Command Code",
|
||||
isActive: true,
|
||||
testStatus: "unknown",
|
||||
providerSpecificData: {
|
||||
...((existing.providerSpecificData as Record<string, unknown> | null) || {}),
|
||||
authAssist: {
|
||||
userId: consumed.metadata?.userId,
|
||||
userName: consumed.metadata?.userName,
|
||||
keyName: consumed.metadata?.keyName,
|
||||
appliedAt: consumed.appliedAt,
|
||||
},
|
||||
},
|
||||
...(parsed.data.setDefault ? { priority: 1 } : {}),
|
||||
})) as Record<string, unknown> | null;
|
||||
} else {
|
||||
connection = (await createProviderConnection({
|
||||
provider: "command-code",
|
||||
authType: "apikey",
|
||||
name: parsed.data.name || consumed.metadata?.keyName || "Command Code",
|
||||
apiKey: consumed.apiKey,
|
||||
priority: parsed.data.setDefault ? 1 : undefined,
|
||||
isActive: true,
|
||||
testStatus: "unknown",
|
||||
providerSpecificData: {
|
||||
authAssist: {
|
||||
userId: consumed.metadata?.userId,
|
||||
userName: consumed.metadata?.userName,
|
||||
keyName: consumed.metadata?.keyName,
|
||||
appliedAt: consumed.appliedAt,
|
||||
},
|
||||
},
|
||||
})) as Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
return noStoreJson({ connection: safeConnection(connection), status: "applied" });
|
||||
}
|
||||
67
src/app/api/providers/command-code/auth/callback/route.ts
Normal file
67
src/app/api/providers/command-code/auth/callback/route.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { markCommandCodeAuthSessionReceived } from "@/lib/db/commandCodeAuth";
|
||||
|
||||
import {
|
||||
callbackCorsHeaders,
|
||||
commandCodeCallbackSchema,
|
||||
MAX_CALLBACK_BODY_BYTES,
|
||||
noStoreJson,
|
||||
readJsonBodyWithLimit,
|
||||
rejectDisallowedCallbackOrigin,
|
||||
stateHashFromState,
|
||||
} from "../shared";
|
||||
|
||||
export async function OPTIONS(request: Request) {
|
||||
return new Response(null, { status: 204, headers: callbackCorsHeaders(request) });
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const originError = rejectDisallowedCallbackOrigin(request);
|
||||
if (originError) return originError;
|
||||
|
||||
let body: unknown;
|
||||
try {
|
||||
body = await readJsonBodyWithLimit(request, MAX_CALLBACK_BODY_BYTES);
|
||||
} catch (error) {
|
||||
const isTooLarge = error instanceof Error && error.message === "BODY_TOO_LARGE";
|
||||
return noStoreJson(
|
||||
{ success: false, error: isTooLarge ? "Request body too large" : "Invalid JSON body" },
|
||||
{ status: isTooLarge ? 413 : 400, headers: callbackCorsHeaders(request) }
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = commandCodeCallbackSchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
return noStoreJson(
|
||||
{ success: false, error: "Invalid callback payload" },
|
||||
{ status: 400, headers: callbackCorsHeaders(request) }
|
||||
);
|
||||
}
|
||||
|
||||
const session = markCommandCodeAuthSessionReceived({
|
||||
stateHash: stateHashFromState(parsed.data.state),
|
||||
apiKey: parsed.data.apiKey,
|
||||
metadata: {
|
||||
userId: parsed.data.userId,
|
||||
userName: parsed.data.userName,
|
||||
keyName: parsed.data.keyName,
|
||||
},
|
||||
});
|
||||
|
||||
if (!session || session.status !== "received") {
|
||||
return noStoreJson(
|
||||
{ success: false, error: "Invalid or expired state" },
|
||||
{ status: 400, headers: callbackCorsHeaders(request) }
|
||||
);
|
||||
}
|
||||
|
||||
return noStoreJson(
|
||||
{
|
||||
success: true,
|
||||
ok: true,
|
||||
status: session.status,
|
||||
expiresAt: session.expiresAt,
|
||||
metadata: session.metadata,
|
||||
},
|
||||
{ headers: callbackCorsHeaders(request) }
|
||||
);
|
||||
}
|
||||
120
src/app/api/providers/command-code/auth/shared.ts
Normal file
120
src/app/api/providers/command-code/auth/shared.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Buffer } from "node:buffer";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { hashCommandCodeAuthState } from "@/lib/db/commandCodeAuth";
|
||||
|
||||
export const COMMAND_CODE_AUTH_TTL_MS = 15 * 60 * 1000;
|
||||
export const COMMAND_CODE_STUDIO_AUTH_URL = "https://commandcode.ai/studio/auth/cli";
|
||||
export const MAX_CALLBACK_BODY_BYTES = 10 * 1024;
|
||||
export const COMMAND_CODE_CLI_CALLBACK_PORTS = [
|
||||
5959, 5960, 5961, 5962, 5963, 5964, 5965, 5966, 5967, 5968,
|
||||
] as const;
|
||||
|
||||
const LOCAL_CALLBACK_ORIGIN = "http://localhost:3000";
|
||||
const PRODUCTION_CALLBACK_ORIGINS = ["https://commandcode.ai", "https://staging.commandcode.ai"];
|
||||
|
||||
export const commandCodeCallbackSchema = z.object({
|
||||
apiKey: z.string().trim().min(1).max(4096),
|
||||
state: z.string().trim().min(32).max(512),
|
||||
userId: z.string().trim().max(256).optional(),
|
||||
userName: z.string().trim().max(256).optional(),
|
||||
keyName: z.string().trim().max(256).optional(),
|
||||
});
|
||||
|
||||
export const commandCodeStateSchema = z.object({
|
||||
state: z.string().trim().min(32).max(512),
|
||||
});
|
||||
|
||||
export const commandCodeApplySchema = commandCodeStateSchema.extend({
|
||||
connectionId: z.string().trim().min(1).max(256).optional(),
|
||||
name: z.string().trim().min(1).max(256).optional(),
|
||||
setDefault: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export function generateCommandCodeState(): string {
|
||||
return randomBytes(32).toString("base64url");
|
||||
}
|
||||
|
||||
export function stateHashFromState(state: string): string {
|
||||
return hashCommandCodeAuthState(state);
|
||||
}
|
||||
|
||||
export function noStoreJson(body: unknown, init: ResponseInit = {}): NextResponse {
|
||||
return NextResponse.json(body, {
|
||||
...init,
|
||||
headers: {
|
||||
"Cache-Control": "no-store",
|
||||
...(init.headers || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function getAllowedCallbackOrigin(origin: string | null): string | null {
|
||||
const allowed =
|
||||
process.env.NODE_ENV === "production"
|
||||
? PRODUCTION_CALLBACK_ORIGINS
|
||||
: [...PRODUCTION_CALLBACK_ORIGINS, LOCAL_CALLBACK_ORIGIN];
|
||||
return origin && allowed.includes(origin) ? origin : null;
|
||||
}
|
||||
|
||||
export function callbackCorsHeaders(request: Request): HeadersInit {
|
||||
const requestHeaders = request.headers.get("access-control-request-headers") || "content-type";
|
||||
const origin = getAllowedCallbackOrigin(request.headers.get("origin"));
|
||||
const headers: Record<string, string> = {
|
||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": requestHeaders,
|
||||
"Access-Control-Allow-Private-Network": "true",
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-store",
|
||||
Vary: "Origin, Access-Control-Request-Headers",
|
||||
};
|
||||
if (origin) headers["Access-Control-Allow-Origin"] = origin;
|
||||
return headers;
|
||||
}
|
||||
|
||||
export function rejectDisallowedCallbackOrigin(request: Request): Response | null {
|
||||
const origin = request.headers.get("origin");
|
||||
if (!origin || getAllowedCallbackOrigin(origin)) return null;
|
||||
return new Response(JSON.stringify({ success: false, error: "Origin not allowed" }), {
|
||||
status: 403,
|
||||
headers: callbackCorsHeaders(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function readJsonBodyWithLimit(request: Request, maxBytes: number): Promise<unknown> {
|
||||
const reader = request.body?.getReader();
|
||||
if (!reader) return request.json();
|
||||
|
||||
const chunks: Uint8Array[] = [];
|
||||
let total = 0;
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
if (!value) continue;
|
||||
total += value.byteLength;
|
||||
if (total > maxBytes) {
|
||||
await reader.cancel();
|
||||
throw new Error("BODY_TOO_LARGE");
|
||||
}
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
const body = new TextDecoder().decode(Buffer.concat(chunks));
|
||||
return JSON.parse(body);
|
||||
}
|
||||
|
||||
export function buildCommandCodeCliCallbackUrl(): string {
|
||||
const configuredPort = process.env.COMMAND_CODE_CALLBACK_PORT || "";
|
||||
const port = /^\d+$/.test(configuredPort)
|
||||
? Number.parseInt(configuredPort, 10)
|
||||
: COMMAND_CODE_CLI_CALLBACK_PORTS[0];
|
||||
const safePort = COMMAND_CODE_CLI_CALLBACK_PORTS.includes(
|
||||
port as (typeof COMMAND_CODE_CLI_CALLBACK_PORTS)[number]
|
||||
)
|
||||
? port
|
||||
: COMMAND_CODE_CLI_CALLBACK_PORTS[0];
|
||||
return `http://localhost:${safePort}/callback`;
|
||||
}
|
||||
28
src/app/api/providers/command-code/auth/start/route.ts
Normal file
28
src/app/api/providers/command-code/auth/start/route.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { createPendingCommandCodeAuthSession } from "@/lib/db/commandCodeAuth";
|
||||
|
||||
import {
|
||||
buildCommandCodeCliCallbackUrl,
|
||||
COMMAND_CODE_AUTH_TTL_MS,
|
||||
COMMAND_CODE_STUDIO_AUTH_URL,
|
||||
generateCommandCodeState,
|
||||
noStoreJson,
|
||||
stateHashFromState,
|
||||
} from "../shared";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
const state = generateCommandCodeState();
|
||||
const expiresAt = new Date(Date.now() + COMMAND_CODE_AUTH_TTL_MS).toISOString();
|
||||
const stateHash = stateHashFromState(state);
|
||||
createPendingCommandCodeAuthSession({ stateHash, expiresAt });
|
||||
|
||||
const callbackUrl = buildCommandCodeCliCallbackUrl();
|
||||
const authUrl = `${COMMAND_CODE_STUDIO_AUTH_URL}?callback=${encodeURIComponent(
|
||||
callbackUrl
|
||||
)}&state=${encodeURIComponent(state)}`;
|
||||
|
||||
return noStoreJson({ state, authUrl, callbackUrl, expiresAt, mode: "manual" });
|
||||
}
|
||||
38
src/app/api/providers/command-code/auth/status/route.ts
Normal file
38
src/app/api/providers/command-code/auth/status/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { getCommandCodeAuthSessionSafeStatus } from "@/lib/db/commandCodeAuth";
|
||||
|
||||
import { commandCodeStateSchema, noStoreJson, stateHashFromState } from "../shared";
|
||||
|
||||
async function readState(request: Request): Promise<string | null> {
|
||||
const urlState = new URL(request.url).searchParams.get("state");
|
||||
if (urlState) return urlState;
|
||||
try {
|
||||
const parsed = commandCodeStateSchema.safeParse(await request.json());
|
||||
return parsed.success ? parsed.data.state : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function handle(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
const state = await readState(request);
|
||||
const parsed = commandCodeStateSchema.safeParse({ state });
|
||||
if (!parsed.success) return noStoreJson({ error: "Invalid state" }, { status: 400 });
|
||||
|
||||
const session = getCommandCodeAuthSessionSafeStatus(stateHashFromState(parsed.data.state));
|
||||
if (!session) return noStoreJson({ status: "not_found" }, { status: 404 });
|
||||
|
||||
return noStoreJson({
|
||||
status: session.status,
|
||||
metadata: session.metadata,
|
||||
expiresAt: session.expiresAt,
|
||||
receivedAt: session.receivedAt,
|
||||
appliedAt: session.appliedAt,
|
||||
});
|
||||
}
|
||||
|
||||
export const GET = handle;
|
||||
export const POST = handle;
|
||||
213
src/lib/db/commandCodeAuth.ts
Normal file
213
src/lib/db/commandCodeAuth.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { createHash, randomUUID } from "crypto";
|
||||
|
||||
import { getDbInstance, rowToCamel } from "./core";
|
||||
import { decrypt, encrypt } from "./encryption";
|
||||
|
||||
export type CommandCodeAuthStatus = "pending" | "received" | "applied" | "expired";
|
||||
|
||||
export interface CommandCodeAuthMetadata {
|
||||
userId?: string;
|
||||
userName?: string;
|
||||
keyName?: string;
|
||||
receivedAt?: string;
|
||||
}
|
||||
|
||||
export interface CommandCodeAuthSafeStatus {
|
||||
id: string;
|
||||
stateHash: string;
|
||||
status: CommandCodeAuthStatus;
|
||||
metadata: CommandCodeAuthMetadata | null;
|
||||
createdAt: string;
|
||||
expiresAt: string;
|
||||
receivedAt: string | null;
|
||||
appliedAt: string | null;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ConsumedCommandCodeAuthSecret extends CommandCodeAuthSafeStatus {
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
type DbRunResult = { changes?: number };
|
||||
type DbStatement<TRow = unknown> = {
|
||||
get: (...params: unknown[]) => TRow | undefined;
|
||||
all: (...params: unknown[]) => TRow[];
|
||||
run: (...params: unknown[]) => DbRunResult;
|
||||
};
|
||||
type DbLike = {
|
||||
prepare: <TRow = unknown>(sql: string) => DbStatement<TRow>;
|
||||
transaction: <T extends (...args: unknown[]) => unknown>(fn: T) => T;
|
||||
};
|
||||
|
||||
type AuthSessionRow = {
|
||||
id: string;
|
||||
state_hash: string;
|
||||
status: CommandCodeAuthStatus;
|
||||
encrypted_api_key?: string | null;
|
||||
metadata_json?: string | null;
|
||||
created_at: string;
|
||||
expires_at: string;
|
||||
received_at?: string | null;
|
||||
applied_at?: string | null;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
function db(): DbLike {
|
||||
return getDbInstance() as unknown as DbLike;
|
||||
}
|
||||
|
||||
export function hashCommandCodeAuthState(state: string): string {
|
||||
return createHash("sha256").update(state, "utf8").digest("hex");
|
||||
}
|
||||
|
||||
function nowIso(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function parseMetadata(value: string | null | undefined): CommandCodeAuthMetadata | null {
|
||||
if (!value) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(value) as CommandCodeAuthMetadata;
|
||||
return parsed && typeof parsed === "object" ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function toSafeStatus(row: AuthSessionRow): CommandCodeAuthSafeStatus {
|
||||
const camel = rowToCamel(row) as Record<string, unknown>;
|
||||
return {
|
||||
id: String(camel.id),
|
||||
stateHash: String(camel.stateHash),
|
||||
status: camel.status as CommandCodeAuthStatus,
|
||||
metadata: parseMetadata(camel.metadataJson as string | null | undefined),
|
||||
createdAt: String(camel.createdAt),
|
||||
expiresAt: String(camel.expiresAt),
|
||||
receivedAt: (camel.receivedAt as string | null | undefined) ?? null,
|
||||
appliedAt: (camel.appliedAt as string | null | undefined) ?? null,
|
||||
updatedAt: String(camel.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
function markExpiredForState(stateHash: string, now = nowIso()): void {
|
||||
db()
|
||||
.prepare(
|
||||
`UPDATE command_code_auth_sessions
|
||||
SET status = 'expired', updated_at = ?
|
||||
WHERE state_hash = ? AND status IN ('pending', 'received') AND expires_at <= ?`
|
||||
)
|
||||
.run(now, stateHash, now);
|
||||
}
|
||||
|
||||
export function createPendingCommandCodeAuthSession(input: {
|
||||
stateHash: string;
|
||||
expiresAt: string;
|
||||
}): CommandCodeAuthSafeStatus {
|
||||
const id = randomUUID();
|
||||
const now = nowIso();
|
||||
db()
|
||||
.prepare(
|
||||
`INSERT INTO command_code_auth_sessions (
|
||||
id, state_hash, status, encrypted_api_key, metadata_json,
|
||||
created_at, expires_at, received_at, applied_at, updated_at
|
||||
) VALUES (?, ?, 'pending', NULL, NULL, ?, ?, NULL, NULL, ?)`
|
||||
)
|
||||
.run(id, input.stateHash, now, input.expiresAt, now);
|
||||
|
||||
const row = db()
|
||||
.prepare<AuthSessionRow>("SELECT * FROM command_code_auth_sessions WHERE id = ?")
|
||||
.get(id);
|
||||
if (!row) throw new Error("Failed to create Command Code auth session");
|
||||
return toSafeStatus(row);
|
||||
}
|
||||
|
||||
export function markCommandCodeAuthSessionReceived(input: {
|
||||
stateHash: string;
|
||||
apiKey: string;
|
||||
metadata?: CommandCodeAuthMetadata;
|
||||
}): CommandCodeAuthSafeStatus | null {
|
||||
const now = nowIso();
|
||||
markExpiredForState(input.stateHash, now);
|
||||
const metadata: CommandCodeAuthMetadata = {
|
||||
...(input.metadata || {}),
|
||||
receivedAt: now,
|
||||
};
|
||||
const encryptedApiKey = encrypt(input.apiKey);
|
||||
db()
|
||||
.prepare(
|
||||
`UPDATE command_code_auth_sessions
|
||||
SET status = 'received', encrypted_api_key = ?, metadata_json = ?, received_at = ?, updated_at = ?
|
||||
WHERE state_hash = ? AND status IN ('pending', 'received') AND expires_at > ?`
|
||||
)
|
||||
.run(encryptedApiKey, JSON.stringify(metadata), now, now, input.stateHash, now);
|
||||
|
||||
return getCommandCodeAuthSessionSafeStatus(input.stateHash);
|
||||
}
|
||||
|
||||
export function getCommandCodeAuthSessionSafeStatus(
|
||||
stateHash: string
|
||||
): CommandCodeAuthSafeStatus | null {
|
||||
markExpiredForState(stateHash);
|
||||
const row = db()
|
||||
.prepare<AuthSessionRow>("SELECT * FROM command_code_auth_sessions WHERE state_hash = ?")
|
||||
.get(stateHash);
|
||||
return row ? toSafeStatus(row) : null;
|
||||
}
|
||||
|
||||
export function consumeCommandCodeAuthSecret(
|
||||
stateHash: string
|
||||
): ConsumedCommandCodeAuthSecret | null {
|
||||
const database = db();
|
||||
return database.transaction(() => {
|
||||
const now = nowIso();
|
||||
database
|
||||
.prepare(
|
||||
`UPDATE command_code_auth_sessions
|
||||
SET status = 'expired', updated_at = ?
|
||||
WHERE state_hash = ? AND status IN ('pending', 'received') AND expires_at <= ?`
|
||||
)
|
||||
.run(now, stateHash, now);
|
||||
|
||||
const row = database
|
||||
.prepare<AuthSessionRow>(
|
||||
`SELECT * FROM command_code_auth_sessions
|
||||
WHERE state_hash = ? AND status = 'received' AND expires_at > ? AND encrypted_api_key IS NOT NULL`
|
||||
)
|
||||
.get(stateHash, now);
|
||||
if (!row?.encrypted_api_key) return null;
|
||||
|
||||
const apiKey = decrypt(row.encrypted_api_key);
|
||||
if (!apiKey) return null;
|
||||
|
||||
const result = database
|
||||
.prepare(
|
||||
`UPDATE command_code_auth_sessions
|
||||
SET status = 'applied', encrypted_api_key = NULL, applied_at = ?, updated_at = ?
|
||||
WHERE id = ? AND status = 'received'`
|
||||
)
|
||||
.run(now, now, row.id);
|
||||
if (!result.changes) return null;
|
||||
|
||||
return {
|
||||
...toSafeStatus({
|
||||
...row,
|
||||
status: "applied",
|
||||
encrypted_api_key: null,
|
||||
applied_at: now,
|
||||
updated_at: now,
|
||||
}),
|
||||
apiKey,
|
||||
};
|
||||
})() as ConsumedCommandCodeAuthSecret | null;
|
||||
}
|
||||
|
||||
export function cleanupExpiredCommandCodeAuthSessions(now = nowIso()): number {
|
||||
const result = db()
|
||||
.prepare(
|
||||
`UPDATE command_code_auth_sessions
|
||||
SET status = 'expired', updated_at = ?
|
||||
WHERE status IN ('pending', 'received') AND expires_at <= ?`
|
||||
)
|
||||
.run(now, now);
|
||||
return result.changes ?? 0;
|
||||
}
|
||||
19
src/lib/db/migrations/055_command_code_auth_sessions.sql
Normal file
19
src/lib/db/migrations/055_command_code_auth_sessions.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Migration 055: Pending browser-assisted Command Code auth sessions
|
||||
CREATE TABLE IF NOT EXISTS command_code_auth_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
state_hash TEXT NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'received', 'applied', 'expired')),
|
||||
encrypted_api_key TEXT,
|
||||
metadata_json TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
expires_at TEXT NOT NULL,
|
||||
received_at TEXT,
|
||||
applied_at TEXT,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_command_code_auth_sessions_state_hash
|
||||
ON command_code_auth_sessions(state_hash);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_command_code_auth_sessions_status_expires
|
||||
ON command_code_auth_sessions(status, expires_at);
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { getEmbeddingProvider } from "@omniroute/open-sse/config/embeddingRegistry.ts";
|
||||
import { getRerankProvider } from "@omniroute/open-sse/config/rerankRegistry.ts";
|
||||
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry.ts";
|
||||
import {
|
||||
buildClaudeCodeCompatibleHeaders,
|
||||
buildClaudeCodeCompatibleValidationPayload,
|
||||
@@ -398,6 +399,53 @@ async function validateDirectChatProvider({ url, headers, body, providerSpecific
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateCommandCodeProvider({ apiKey, providerSpecificData = {} }: any) {
|
||||
const entry = getRegistryEntry("command-code");
|
||||
const baseUrl = normalizeBaseUrl(entry?.baseUrl || "https://api.commandcode.ai");
|
||||
const chatPath = entry?.chatPath || "/alpha/generate";
|
||||
const url = `${baseUrl}${chatPath.startsWith("/") ? chatPath : `/${chatPath}`}`;
|
||||
|
||||
return validateDirectChatProvider({
|
||||
url,
|
||||
providerSpecificData,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"x-command-code-version": "0.24.1",
|
||||
"x-cli-environment": "production",
|
||||
"x-project-slug": "pi-cc",
|
||||
"x-taste-learning": "false",
|
||||
"x-co-flag": "false",
|
||||
"x-session-id": randomUUID(),
|
||||
},
|
||||
body: {
|
||||
config: {
|
||||
workingDir: "/workspace",
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
environment: "omniroute-validation",
|
||||
structure: [],
|
||||
isGitRepo: false,
|
||||
currentBranch: "",
|
||||
mainBranch: "",
|
||||
gitStatus: "",
|
||||
recentCommits: [],
|
||||
},
|
||||
memory: "",
|
||||
taste: "",
|
||||
skills: null,
|
||||
permissionMode: "standard",
|
||||
params: {
|
||||
model: providerSpecificData?.validationModelId || entry?.models?.[0]?.id || "gpt-5.4-mini",
|
||||
messages: [{ role: "user", content: "test" }],
|
||||
tools: [],
|
||||
system: "",
|
||||
max_tokens: 1,
|
||||
stream: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function validateClarifaiProvider({ apiKey, providerSpecificData = {} }: any) {
|
||||
const baseUrl =
|
||||
normalizeBaseUrl(providerSpecificData.baseUrl) || "https://api.clarifai.com/v2/ext/openai/v1";
|
||||
@@ -3005,6 +3053,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
|
||||
const SPECIALTY_VALIDATORS = {
|
||||
qoder: ({ apiKey, providerSpecificData }: any) =>
|
||||
validateQoderCliPat({ apiKey, providerSpecificData }),
|
||||
"command-code": validateCommandCodeProvider,
|
||||
deepgram: validateDeepgramProvider,
|
||||
assemblyai: validateAssemblyAIProvider,
|
||||
nanobanana: validateNanoBananaProvider,
|
||||
|
||||
@@ -77,6 +77,7 @@ const KNOWN_SVGS = new Set([
|
||||
"brave-search",
|
||||
"cartesia",
|
||||
"clarifai",
|
||||
"command-code",
|
||||
"docker-model-runner",
|
||||
"droid",
|
||||
"gemini-cli",
|
||||
|
||||
@@ -184,6 +184,18 @@ export const APIKEY_PROVIDERS = {
|
||||
freeNote: "$200 free credits on signup - multi-model routing gateway",
|
||||
apiHint: "Get $200 free credits at https://agentrouter.org/register — no credit card required.",
|
||||
},
|
||||
"command-code": {
|
||||
id: "command-code",
|
||||
alias: "cmd",
|
||||
name: "Command Code",
|
||||
icon: "terminal",
|
||||
color: "#111827",
|
||||
textIcon: "CC",
|
||||
website: "https://commandcode.ai/",
|
||||
authHint:
|
||||
"Use a Command Code API key. Requests are sent to Command Code's /alpha/generate endpoint.",
|
||||
apiHint: "Create or copy an API key from Command Code, then paste it here as a Bearer token.",
|
||||
},
|
||||
openrouter: {
|
||||
id: "openrouter",
|
||||
alias: "openrouter",
|
||||
|
||||
231
tests/unit/command-code-auth-assist.test.ts
Normal file
231
tests/unit/command-code-auth-assist.test.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-command-code-auth-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
process.env.STORAGE_ENCRYPTION_KEY = "test-command-code-auth-encryption-key";
|
||||
delete process.env.INITIAL_PASSWORD;
|
||||
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
const startRoute = await import("../../src/app/api/providers/command-code/auth/start/route.ts");
|
||||
const callbackRoute =
|
||||
await import("../../src/app/api/providers/command-code/auth/callback/route.ts");
|
||||
const statusRoute = await import("../../src/app/api/providers/command-code/auth/status/route.ts");
|
||||
const applyRoute = await import("../../src/app/api/providers/command-code/auth/apply/route.ts");
|
||||
const providersDb = await import("../../src/lib/db/providers.ts");
|
||||
|
||||
function resetDb() {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function jsonRequest(url: string, body: unknown, headers: HeadersInit = {}) {
|
||||
return new Request(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", ...headers },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
test.beforeEach(() => {
|
||||
delete process.env.OMNIROUTE_PUBLIC_BASE_URL;
|
||||
delete process.env.OMNIROUTE_BASE_URL;
|
||||
delete process.env.BASE_URL;
|
||||
delete process.env.NEXT_PUBLIC_BASE_URL;
|
||||
delete process.env.COMMAND_CODE_CALLBACK_PORT;
|
||||
resetDb();
|
||||
});
|
||||
|
||||
test.after(() => {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("Command Code auth assist start/callback/status/apply keeps state hash and key private", async () => {
|
||||
const startResponse = await startRoute.POST(
|
||||
new Request("http://localhost:20128/api/providers/command-code/auth/start", {
|
||||
method: "POST",
|
||||
headers: { origin: "http://localhost:20128" },
|
||||
})
|
||||
);
|
||||
assert.equal(startResponse.status, 200);
|
||||
assert.equal(startResponse.headers.get("cache-control"), "no-store");
|
||||
const startBody = await startResponse.json();
|
||||
assert.equal(typeof startBody.state, "string");
|
||||
assert.ok(startBody.authUrl.startsWith("https://commandcode.ai/studio/auth/cli?"));
|
||||
assert.ok(!("stateHash" in startBody));
|
||||
|
||||
const authUrl = new URL(startBody.authUrl);
|
||||
const callbackUrl = authUrl.searchParams.get("callback");
|
||||
assert.ok(callbackUrl);
|
||||
assert.equal(callbackUrl, startBody.callbackUrl);
|
||||
assert.equal(callbackUrl, "http://localhost:5959/callback");
|
||||
assert.equal(startBody.mode, "manual");
|
||||
|
||||
const optionsResponse = await callbackRoute.OPTIONS(
|
||||
new Request("http://localhost:20128/api/providers/command-code/auth/callback", {
|
||||
method: "OPTIONS",
|
||||
headers: {
|
||||
origin: "https://commandcode.ai",
|
||||
"access-control-request-headers": "content-type, x-command-code",
|
||||
},
|
||||
})
|
||||
);
|
||||
assert.equal(optionsResponse.status, 204);
|
||||
assert.equal(
|
||||
optionsResponse.headers.get("access-control-allow-origin"),
|
||||
"https://commandcode.ai"
|
||||
);
|
||||
assert.equal(optionsResponse.headers.get("access-control-allow-methods"), "POST, OPTIONS");
|
||||
assert.equal(optionsResponse.headers.get("access-control-allow-private-network"), "true");
|
||||
assert.equal(
|
||||
optionsResponse.headers.get("access-control-allow-headers"),
|
||||
"content-type, x-command-code"
|
||||
);
|
||||
|
||||
const callbackResponse = await callbackRoute.POST(
|
||||
jsonRequest(
|
||||
"http://localhost:20128/api/providers/command-code/auth/callback",
|
||||
{
|
||||
apiKey: "cc_test_secret",
|
||||
state: startBody.state,
|
||||
userId: "user-1",
|
||||
userName: "Ada",
|
||||
keyName: "Studio Key",
|
||||
},
|
||||
{ origin: "https://commandcode.ai" }
|
||||
)
|
||||
);
|
||||
assert.equal(callbackResponse.status, 200);
|
||||
const callbackBody = await callbackResponse.json();
|
||||
assert.equal(callbackBody.success, true);
|
||||
|
||||
const statusResponse = await statusRoute.GET(
|
||||
new Request(
|
||||
`http://localhost:20128/api/providers/command-code/auth/status?state=${encodeURIComponent(
|
||||
startBody.state
|
||||
)}`
|
||||
)
|
||||
);
|
||||
assert.equal(statusResponse.status, 200);
|
||||
const statusBody = await statusResponse.json();
|
||||
assert.equal(statusBody.status, "received");
|
||||
assert.equal(statusBody.metadata.userName, "Ada");
|
||||
assert.ok(!JSON.stringify(statusBody).includes("cc_test_secret"));
|
||||
assert.ok(!("stateHash" in statusBody));
|
||||
|
||||
const applyResponse = await applyRoute.POST(
|
||||
jsonRequest("http://localhost:20128/api/providers/command-code/auth/apply", {
|
||||
state: startBody.state,
|
||||
name: "Command Code Studio",
|
||||
setDefault: true,
|
||||
})
|
||||
);
|
||||
assert.equal(applyResponse.status, 200);
|
||||
const applyBody = await applyResponse.json();
|
||||
assert.equal(applyBody.status, "applied");
|
||||
assert.equal(applyBody.connection.provider, "command-code");
|
||||
assert.equal(applyBody.connection.authType, "apikey");
|
||||
assert.ok(!JSON.stringify(applyBody).includes("cc_test_secret"));
|
||||
assert.ok(!("apiKey" in applyBody.connection));
|
||||
assert.ok(!("stateHash" in applyBody));
|
||||
|
||||
const connections = await providersDb.getProviderConnections({ provider: "command-code" });
|
||||
assert.equal(connections.length, 1);
|
||||
assert.equal(connections[0].apiKey, "cc_test_secret");
|
||||
|
||||
const secondApplyResponse = await applyRoute.POST(
|
||||
jsonRequest("http://localhost:20128/api/providers/command-code/auth/apply", {
|
||||
state: startBody.state,
|
||||
})
|
||||
);
|
||||
assert.equal(secondApplyResponse.status, 409);
|
||||
});
|
||||
|
||||
test("Command Code auth assist keeps auth URL callback on CLI localhost contract", async () => {
|
||||
process.env.OMNIROUTE_PUBLIC_BASE_URL = "https://omniroute.example.com/base-path";
|
||||
|
||||
const startResponse = await startRoute.POST(
|
||||
new Request("http://localhost:20128/api/providers/command-code/auth/start", {
|
||||
method: "POST",
|
||||
headers: { origin: "http://localhost:20128" },
|
||||
})
|
||||
);
|
||||
assert.equal(startResponse.status, 200);
|
||||
const startBody = await startResponse.json();
|
||||
const authUrl = new URL(startBody.authUrl);
|
||||
|
||||
assert.equal(authUrl.searchParams.get("callback"), "http://localhost:5959/callback");
|
||||
assert.equal(startBody.callbackUrl, authUrl.searchParams.get("callback"));
|
||||
});
|
||||
|
||||
test("Command Code auth assist allows only configured CLI callback port range", async () => {
|
||||
process.env.COMMAND_CODE_CALLBACK_PORT = "5962";
|
||||
const configuredPortResponse = await startRoute.POST(
|
||||
new Request("http://localhost:20128/api/providers/command-code/auth/start", {
|
||||
method: "POST",
|
||||
headers: { origin: "http://localhost:20128" },
|
||||
})
|
||||
);
|
||||
const configuredPortBody = await configuredPortResponse.json();
|
||||
assert.equal(
|
||||
new URL(configuredPortBody.authUrl).searchParams.get("callback"),
|
||||
"http://localhost:5962/callback"
|
||||
);
|
||||
|
||||
resetDb();
|
||||
process.env.COMMAND_CODE_CALLBACK_PORT = "20128";
|
||||
const invalidPortResponse = await startRoute.POST(
|
||||
new Request("http://localhost:20128/api/providers/command-code/auth/start", {
|
||||
method: "POST",
|
||||
headers: { origin: "http://localhost:20128" },
|
||||
})
|
||||
);
|
||||
const invalidPortBody = await invalidPortResponse.json();
|
||||
assert.equal(
|
||||
new URL(invalidPortBody.authUrl).searchParams.get("callback"),
|
||||
"http://localhost:5959/callback"
|
||||
);
|
||||
|
||||
resetDb();
|
||||
process.env.COMMAND_CODE_CALLBACK_PORT = "5962abc";
|
||||
const partialPortResponse = await startRoute.POST(
|
||||
new Request("http://localhost:20128/api/providers/command-code/auth/start", {
|
||||
method: "POST",
|
||||
headers: { origin: "http://localhost:20128" },
|
||||
})
|
||||
);
|
||||
const partialPortBody = await partialPortResponse.json();
|
||||
assert.equal(
|
||||
new URL(partialPortBody.authUrl).searchParams.get("callback"),
|
||||
"http://localhost:5959/callback"
|
||||
);
|
||||
});
|
||||
|
||||
test("Command Code callback rejects disallowed origins and oversized bodies", async () => {
|
||||
const disallowed = await callbackRoute.POST(
|
||||
jsonRequest(
|
||||
"http://localhost:20128/api/providers/command-code/auth/callback",
|
||||
{ apiKey: "secret", state: "x".repeat(32) },
|
||||
{ origin: "https://evil.example" }
|
||||
)
|
||||
);
|
||||
assert.equal(disallowed.status, 403);
|
||||
assert.equal((await disallowed.json()).success, false);
|
||||
assert.equal(disallowed.headers.get("access-control-allow-origin"), null);
|
||||
|
||||
const tooLarge = await callbackRoute.POST(
|
||||
new Request("http://localhost:20128/api/providers/command-code/auth/callback", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", origin: "https://commandcode.ai" },
|
||||
body: JSON.stringify({ apiKey: "x".repeat(11 * 1024), state: "s".repeat(64) }),
|
||||
})
|
||||
);
|
||||
assert.equal(tooLarge.status, 413);
|
||||
assert.equal((await tooLarge.json()).success, false);
|
||||
assert.equal(tooLarge.headers.get("access-control-allow-origin"), "https://commandcode.ai");
|
||||
});
|
||||
249
tests/unit/command-code-executor.test.ts
Normal file
249
tests/unit/command-code-executor.test.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-command-code-executor-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
|
||||
const { REGISTRY, getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { CommandCodeExecutor } = await import("../../open-sse/executors/commandCode.ts");
|
||||
const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
const PINNED_COMMAND_CODE_MODELS = [
|
||||
"claude-opus-4-7",
|
||||
"claude-opus-4-6",
|
||||
"claude-sonnet-4-6",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"gpt-5.5",
|
||||
"gpt-5.4",
|
||||
"gpt-5.3-codex",
|
||||
"gpt-5.4-mini",
|
||||
"deepseek/deepseek-v4-pro",
|
||||
"deepseek/deepseek-v4-flash",
|
||||
"moonshotai/Kimi-K2.6",
|
||||
"moonshotai/Kimi-K2.5",
|
||||
"zai-org/GLM-5.1",
|
||||
"zai-org/GLM-5",
|
||||
"MiniMaxAI/MiniMax-M2.7",
|
||||
"MiniMaxAI/MiniMax-M2.5",
|
||||
"Qwen/Qwen3.6-Max-Preview",
|
||||
"Qwen/Qwen3.6-Plus",
|
||||
];
|
||||
|
||||
function commandCodeStream(lines: unknown[], { sse = false } = {}) {
|
||||
const text = lines
|
||||
.map((line) => {
|
||||
const json = JSON.stringify(line);
|
||||
return sse ? `data: ${json}\n\n` : `${json}\n`;
|
||||
})
|
||||
.join("");
|
||||
return new Response(text, { status: 200, headers: { "Content-Type": "application/x-ndjson" } });
|
||||
}
|
||||
|
||||
function toPlainHeaders(headers: any) {
|
||||
if (headers instanceof Headers) return Object.fromEntries(headers.entries());
|
||||
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
|
||||
}
|
||||
|
||||
function parseSsePayloads(sse: string) {
|
||||
return sse
|
||||
.split("\n")
|
||||
.filter((line) => line.startsWith("data: "))
|
||||
.map((line) => line.slice(6).trim())
|
||||
.filter((line) => line && line !== "[DONE]")
|
||||
.map((line) => JSON.parse(line));
|
||||
}
|
||||
|
||||
test.afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
test.after(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("Command Code provider catalog has pinned models and alias lookup", () => {
|
||||
const entry = REGISTRY["command-code"];
|
||||
assert.ok(entry);
|
||||
assert.equal(entry.alias, "cmd");
|
||||
assert.equal(entry.executor, "command-code");
|
||||
assert.equal(entry.baseUrl, "https://api.commandcode.ai");
|
||||
assert.equal(entry.chatPath, "/alpha/generate");
|
||||
assert.deepEqual(
|
||||
entry.models.map((model) => model.id),
|
||||
PINNED_COMMAND_CODE_MODELS
|
||||
);
|
||||
assert.equal(getRegistryEntry("cmd"), entry);
|
||||
});
|
||||
|
||||
test("getExecutor returns the specialized Command Code executor", () => {
|
||||
assert.equal(hasSpecializedExecutor("command-code"), true);
|
||||
assert.ok(getExecutor("command-code") instanceof CommandCodeExecutor);
|
||||
assert.ok(getExecutor("cmd") instanceof CommandCodeExecutor);
|
||||
});
|
||||
|
||||
test("Command Code executor posts wrapped body and required headers to alpha/generate", async () => {
|
||||
const calls: any[] = [];
|
||||
globalThis.fetch = async (url, init = {}) => {
|
||||
calls.push({ url: String(url), init });
|
||||
return commandCodeStream([{ type: "text-delta", text: "hello" }, { type: "finish" }]);
|
||||
};
|
||||
|
||||
const executor = getExecutor("command-code");
|
||||
const { response, url, headers, transformedBody } = await executor.execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: {
|
||||
stream: false,
|
||||
messages: [
|
||||
{ role: "system", content: "You are concise." },
|
||||
{ role: "user", content: "Hi" },
|
||||
],
|
||||
tools: [{ type: "function", function: { name: "lookup", parameters: { type: "object" } } }],
|
||||
max_tokens: 42,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(url, "https://api.commandcode.ai/alpha/generate");
|
||||
assert.equal(calls.length, 1);
|
||||
assert.equal(calls[0].url, "https://api.commandcode.ai/alpha/generate");
|
||||
assert.equal(calls[0].init.method, "POST");
|
||||
assert.equal(headers.Authorization, "Bearer cc_test_key");
|
||||
assert.equal(headers["x-command-code-version"], "0.24.1");
|
||||
assert.equal(headers["x-cli-environment"], "production");
|
||||
assert.equal(headers["x-project-slug"], "pi-cc");
|
||||
assert.equal(headers["x-taste-learning"], "false");
|
||||
assert.equal(headers["x-co-flag"], "false");
|
||||
assert.equal(typeof headers["x-session-id"], "string");
|
||||
|
||||
const posted = JSON.parse(String(calls[0].init.body));
|
||||
assert.deepEqual(posted, transformedBody);
|
||||
for (const key of ["config", "memory", "taste", "skills", "permissionMode", "params"]) {
|
||||
assert.ok(key in posted, `missing ${key}`);
|
||||
}
|
||||
assert.equal(posted.params.model, "gpt-5.4-mini");
|
||||
assert.equal(posted.params.stream, true);
|
||||
assert.equal(posted.params.system, "You are concise.");
|
||||
assert.equal(posted.params.messages[0].role, "user");
|
||||
assert.equal(posted.params.tools[0].name, "lookup");
|
||||
|
||||
const json = await response.json();
|
||||
assert.equal(json.choices[0].message.content, "hello");
|
||||
});
|
||||
|
||||
test("Command Code raw NDJSON stream becomes OpenAI chat SSE chunks", async () => {
|
||||
globalThis.fetch = async () =>
|
||||
commandCodeStream([
|
||||
{ type: "text-delta", text: "Hello" },
|
||||
{ type: "reasoning-delta", text: "thinking" },
|
||||
{ type: "tool-call", toolCallId: "call_1", toolName: "search", input: { q: "docs" } },
|
||||
{ type: "finish", finishReason: "tool-calls" },
|
||||
]);
|
||||
|
||||
const { response } = await getExecutor("command-code").execute({
|
||||
model: "gpt-5.4",
|
||||
stream: true,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: { messages: [{ role: "user", content: "Hi" }] },
|
||||
});
|
||||
|
||||
assert.equal(response.headers.get("Content-Type"), "text/event-stream; charset=utf-8");
|
||||
const sse = await response.text();
|
||||
assert.match(sse, /data: \[DONE\]/);
|
||||
const chunks = parseSsePayloads(sse);
|
||||
assert.equal(chunks[0].object, "chat.completion.chunk");
|
||||
assert.deepEqual(chunks[0].choices[0].delta, { role: "assistant" });
|
||||
assert.equal(chunks[1].choices[0].delta.content, "Hello");
|
||||
assert.equal(chunks[2].choices[0].delta.reasoning_content, "thinking");
|
||||
assert.equal(chunks[3].choices[0].delta.tool_calls[0].function.name, "search");
|
||||
assert.equal(chunks.at(-1).choices[0].finish_reason, "tool_calls");
|
||||
});
|
||||
|
||||
test("Command Code data: SSE lines aggregate into non-stream ChatCompletion JSON", async () => {
|
||||
globalThis.fetch = async () =>
|
||||
commandCodeStream(
|
||||
[
|
||||
{ type: "text-delta", text: "Hel" },
|
||||
{ type: "text-delta", text: "lo" },
|
||||
{ type: "reasoning-delta", text: "because" },
|
||||
{ type: "tool-call", id: "call_2", name: "lookup", arguments: { id: 7 } },
|
||||
{
|
||||
type: "finish",
|
||||
finishReason: "max_tokens",
|
||||
totalUsage: {
|
||||
inputTokens: 3,
|
||||
inputTokenDetails: { cacheReadTokens: 2 },
|
||||
outputTokens: 5,
|
||||
},
|
||||
},
|
||||
],
|
||||
{ sse: true }
|
||||
);
|
||||
|
||||
const { response } = await getExecutor("command-code").execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: { messages: [{ role: "user", content: "Hi" }] },
|
||||
});
|
||||
|
||||
assert.equal(response.headers.get("Content-Type"), "application/json");
|
||||
const json = await response.json();
|
||||
assert.equal(json.object, "chat.completion");
|
||||
assert.equal(json.choices[0].message.content, "Hello");
|
||||
assert.equal(json.choices[0].message.reasoning_content, "because");
|
||||
assert.equal(json.choices[0].message.tool_calls[0].function.arguments, JSON.stringify({ id: 7 }));
|
||||
assert.equal(json.choices[0].finish_reason, "length");
|
||||
assert.deepEqual(json.usage, { prompt_tokens: 5, completion_tokens: 5, total_tokens: 10 });
|
||||
});
|
||||
|
||||
test("Command Code executor surfaces upstream and streamed errors", async () => {
|
||||
globalThis.fetch = async () =>
|
||||
new Response("bad key", { status: 401, statusText: "Unauthorized" });
|
||||
const upstreamFailure = await getExecutor("command-code").execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: { messages: [{ role: "user", content: "Hi" }] },
|
||||
});
|
||||
assert.equal(upstreamFailure.response.status, 401);
|
||||
assert.equal(await upstreamFailure.response.text(), "bad key");
|
||||
|
||||
globalThis.fetch = async () => commandCodeStream([{ type: "error", error: { message: "boom" } }]);
|
||||
await assert.rejects(async () => {
|
||||
await getExecutor("command-code").execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: { messages: [{ role: "user", content: "Hi" }] },
|
||||
});
|
||||
}, /boom/);
|
||||
});
|
||||
|
||||
test("Command Code non-stream aggregation throws when the final error event lacks a trailing newline", async () => {
|
||||
globalThis.fetch = async () =>
|
||||
new Response(
|
||||
`${JSON.stringify({ type: "text-delta", text: "Hello" })}\n${JSON.stringify({
|
||||
type: "error",
|
||||
error: { message: "boom" },
|
||||
})}`,
|
||||
{ status: 200, headers: { "Content-Type": "application/x-ndjson" } }
|
||||
);
|
||||
|
||||
await assert.rejects(async () => {
|
||||
await getExecutor("command-code").execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
body: { messages: [{ role: "user", content: "Hi" }] },
|
||||
});
|
||||
}, /boom/);
|
||||
});
|
||||
@@ -1,8 +1,11 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { validateProviderApiKey, validateClaudeCodeCompatibleProvider } =
|
||||
await import("../../src/lib/providers/validation.ts");
|
||||
const {
|
||||
validateProviderApiKey,
|
||||
validateClaudeCodeCompatibleProvider,
|
||||
validateCommandCodeProvider,
|
||||
} = await import("../../src/lib/providers/validation.ts");
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
@@ -10,6 +13,13 @@ test.afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
function toPlainHeaders(headers: any) {
|
||||
if (headers instanceof Headers) return Object.fromEntries(headers.entries());
|
||||
return Object.fromEntries(
|
||||
Object.entries(headers || {}).map(([key, value]) => [key, String(value)])
|
||||
);
|
||||
}
|
||||
|
||||
function metaAiSseText(content: string, streamingState = "DONE") {
|
||||
return `event: next
|
||||
data: ${JSON.stringify({
|
||||
@@ -73,6 +83,28 @@ test("specialty provider validators cover Deepgram, AssemblyAI, NanoBanana, Elev
|
||||
assert.equal(inworld.valid, true);
|
||||
});
|
||||
|
||||
test("validateCommandCodeProvider ignores caller baseUrl and chatPath overrides", async () => {
|
||||
globalThis.fetch = async (url, init = {}) => {
|
||||
assert.equal(String(url), "https://api.commandcode.ai/alpha/generate");
|
||||
const headers = init.headers as Record<string, string>;
|
||||
assert.equal(headers.Authorization, "Bearer cc-key");
|
||||
const body = JSON.parse(String(init.body));
|
||||
assert.equal(body.params.model, "command-code-validation-model");
|
||||
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
||||
};
|
||||
|
||||
const result = await validateCommandCodeProvider({
|
||||
apiKey: "cc-key",
|
||||
providerSpecificData: {
|
||||
baseUrl: "https://evil.example/api",
|
||||
chatPath: "/v1/chat/completions",
|
||||
validationModelId: "command-code-validation-model",
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.valid, true);
|
||||
});
|
||||
|
||||
test("specialty providers surface network failures and non-auth upstream failures", async () => {
|
||||
globalThis.fetch = async (url) => {
|
||||
const target = String(url);
|
||||
@@ -1876,3 +1908,63 @@ test("specialty validator rejects invalid Runway credentials", async () => {
|
||||
|
||||
assert.equal(runway.error, "Invalid API key");
|
||||
});
|
||||
|
||||
test("validateCommandCodeProvider sends Command Code probe URL, headers, and wrapper body", async () => {
|
||||
const calls: any[] = [];
|
||||
globalThis.fetch = async (url, init = {}) => {
|
||||
calls.push({
|
||||
url: String(url),
|
||||
method: init.method,
|
||||
headers: toPlainHeaders(init.headers),
|
||||
body: JSON.parse(String(init.body)),
|
||||
});
|
||||
return new Response("", { status: 400 });
|
||||
};
|
||||
|
||||
const result = await validateCommandCodeProvider({
|
||||
apiKey: "cc_test_key",
|
||||
providerSpecificData: { validationModelId: "gpt-5.4-mini" },
|
||||
});
|
||||
|
||||
assert.deepEqual(result, { valid: true, error: null });
|
||||
assert.equal(calls.length, 1);
|
||||
assert.equal(calls[0].url, "https://api.commandcode.ai/alpha/generate");
|
||||
assert.equal(calls[0].method, "POST");
|
||||
assert.equal(calls[0].headers.Authorization, "Bearer cc_test_key");
|
||||
assert.equal(calls[0].headers["Content-Type"], "application/json");
|
||||
assert.equal(calls[0].headers["x-command-code-version"], "0.24.1");
|
||||
assert.equal(calls[0].headers["x-cli-environment"], "production");
|
||||
assert.equal(calls[0].headers["x-project-slug"], "pi-cc");
|
||||
assert.equal(calls[0].headers["x-taste-learning"], "false");
|
||||
assert.equal(calls[0].headers["x-co-flag"], "false");
|
||||
assert.equal(typeof calls[0].headers["x-session-id"], "string");
|
||||
assert.equal(calls[0].body.config.environment, "omniroute-validation");
|
||||
assert.equal(calls[0].body.permissionMode, "standard");
|
||||
assert.equal(calls[0].body.params.model, "gpt-5.4-mini");
|
||||
assert.equal(calls[0].body.params.stream, true);
|
||||
assert.equal(calls[0].body.params.max_tokens, 1);
|
||||
});
|
||||
|
||||
for (const status of [400, 422, 429]) {
|
||||
test(`validateCommandCodeProvider accepts ${status} as direct validator auth success`, async () => {
|
||||
globalThis.fetch = async () => new Response("", { status });
|
||||
assert.deepEqual(await validateCommandCodeProvider({ apiKey: "cc_test_key" }), {
|
||||
valid: true,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test("validateCommandCodeProvider rejects auth failures and provider outages", async () => {
|
||||
globalThis.fetch = async () => new Response("unauthorized", { status: 401 });
|
||||
assert.deepEqual(await validateCommandCodeProvider({ apiKey: "bad" }), {
|
||||
valid: false,
|
||||
error: "Invalid API key",
|
||||
});
|
||||
|
||||
globalThis.fetch = async () => new Response("server down", { status: 500 });
|
||||
assert.deepEqual(await validateCommandCodeProvider({ apiKey: "cc_test_key" }), {
|
||||
valid: false,
|
||||
error: "Provider unavailable (500)",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,6 +224,45 @@ test("handleResponsesCore transforms upstream OpenAI SSE into Responses API SSE"
|
||||
assert.match(sse, /data: \[DONE\]/);
|
||||
});
|
||||
|
||||
test("handleResponsesCore transforms Command Code executor SSE through Responses shim", async () => {
|
||||
const { call, result } = await invokeResponsesCore({
|
||||
provider: "command-code",
|
||||
model: "gpt-5.4-mini",
|
||||
credentials: { apiKey: "cc_test_key", providerSpecificData: {} },
|
||||
body: {
|
||||
model: "gpt-5.4-mini",
|
||||
input: "hello command code",
|
||||
},
|
||||
responseFactory() {
|
||||
return new Response(
|
||||
[
|
||||
`data: ${JSON.stringify({ type: "text-delta", text: "command" })}`,
|
||||
"",
|
||||
`data: ${JSON.stringify({ type: "reasoning-delta", text: "thinking" })}`,
|
||||
"",
|
||||
`data: ${JSON.stringify({ type: "finish", finishReason: "stop" })}`,
|
||||
"",
|
||||
].join("\n"),
|
||||
{ status: 200, headers: { "Content-Type": "application/x-ndjson" } }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.success, true);
|
||||
assert.equal(call.url, "https://api.commandcode.ai/alpha/generate");
|
||||
assert.equal(call.headers.Authorization, "Bearer cc_test_key");
|
||||
assert.equal(call.headers["x-command-code-version"], "0.24.1");
|
||||
assert.equal(call.body.params.model, "gpt-5.4-mini");
|
||||
assert.equal(call.body.params.stream, true);
|
||||
|
||||
const sse = await result.response.text();
|
||||
assert.match(sse, /event: response\.created/);
|
||||
assert.match(sse, /event: response\.output_text\.delta/);
|
||||
assert.match(sse, /command/);
|
||||
assert.match(sse, /event: response\.completed/);
|
||||
assert.match(sse, /data: \[DONE\]/);
|
||||
});
|
||||
|
||||
test("handleResponsesCore propagates upstream failures from chatCore unchanged", async () => {
|
||||
const { result } = await invokeResponsesCore({
|
||||
body: {
|
||||
|
||||
Reference in New Issue
Block a user