feat: add code queue services and baidu netdisk
This commit is contained in:
@@ -0,0 +1,597 @@
|
||||
import { type UniDeskConfig } from "./config";
|
||||
import { coreInternalFetch } from "./microservices";
|
||||
|
||||
const defaultToolLimit = 8;
|
||||
const defaultTraceLimit = 80;
|
||||
const maxTraceLimit = 500;
|
||||
const defaultOutputLimit = 20;
|
||||
const defaultTextPreviewChars = 12_000;
|
||||
|
||||
interface CodexTaskOptions {
|
||||
trace: boolean;
|
||||
traceLimit: number;
|
||||
traceMode: "tail" | "after" | "before";
|
||||
afterSeq: number;
|
||||
beforeSeq: number | null;
|
||||
toolLimit: number;
|
||||
full: boolean;
|
||||
rawSummary: boolean;
|
||||
}
|
||||
|
||||
interface CodexOutputOptions {
|
||||
limit: number;
|
||||
mode: "tail" | "after" | "before";
|
||||
afterSeq: number;
|
||||
beforeSeq: number | null;
|
||||
fullText: boolean;
|
||||
maxTextChars: number;
|
||||
}
|
||||
|
||||
type CodexResponseFetcher = (path: string) => unknown;
|
||||
type AsyncCodexResponseFetcher = (path: string) => Promise<unknown>;
|
||||
|
||||
function requireTaskId(value: string | undefined, command: string): string {
|
||||
if (value === undefined || value.trim().length === 0) throw new Error(`${command} requires task id`);
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
||||
}
|
||||
|
||||
function asArray(value: unknown): unknown[] {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
function asString(value: unknown): string {
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
||||
function asNumber(value: unknown, fallback = 0): number {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function asBoolean(value: unknown): boolean {
|
||||
return value === true || value === 1 || value === "1" || value === "true";
|
||||
}
|
||||
|
||||
function stringList(value: unknown): string[] {
|
||||
return asArray(value).map((item) => String(item ?? "")).filter((item) => item.length > 0);
|
||||
}
|
||||
|
||||
function fmtDuration(ms: unknown): string {
|
||||
const value = Number(ms);
|
||||
if (!Number.isFinite(value) || value < 0) return "--";
|
||||
const totalSeconds = Math.floor(value / 1000);
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
function upstreamError(response: unknown): string {
|
||||
const record = asRecord(response);
|
||||
if (record === null) return String(response);
|
||||
const body = asRecord(record.body);
|
||||
const bodyError = body?.error;
|
||||
if (typeof bodyError === "string") return bodyError;
|
||||
const status = typeof record.status === "number" ? `HTTP ${record.status}` : "upstream request failed";
|
||||
return `${status}: ${JSON.stringify(response).slice(0, 1200)}`;
|
||||
}
|
||||
|
||||
function unwrapCodexResponse(response: unknown): { upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } {
|
||||
const record = asRecord(response);
|
||||
if (record?.ok !== true) throw new Error(upstreamError(response));
|
||||
const body = asRecord(record.body);
|
||||
if (body?.ok !== true) throw new Error(upstreamError(response));
|
||||
return { upstream: { ok: record.ok, status: record.status }, body };
|
||||
}
|
||||
|
||||
function positiveIntegerOption(args: string[], names: string[], defaultValue: number, maxValue = Number.MAX_SAFE_INTEGER): number {
|
||||
for (const name of names) {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) continue;
|
||||
const raw = args[index + 1];
|
||||
const value = Number(raw);
|
||||
if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
|
||||
return Math.min(value, maxValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function nonNegativeNumberOption(args: string[], names: string[], defaultValue: number): number {
|
||||
for (const name of names) {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) continue;
|
||||
const raw = args[index + 1];
|
||||
const value = Number(raw);
|
||||
if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be a non-negative number`);
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function nullablePositiveNumberOption(args: string[], names: string[]): number | null {
|
||||
for (const name of names) {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) continue;
|
||||
const raw = args[index + 1];
|
||||
const value = Number(raw);
|
||||
if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive number`);
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasFlag(args: string[], name: string): boolean {
|
||||
return args.includes(name);
|
||||
}
|
||||
|
||||
function textView(text: string, full: boolean, maxChars: number): Record<string, unknown> {
|
||||
const truncated = !full && text.length > maxChars;
|
||||
return {
|
||||
text: truncated ? text.slice(0, maxChars) : text,
|
||||
chars: text.length,
|
||||
truncated,
|
||||
omittedChars: truncated ? text.length - maxChars : 0,
|
||||
};
|
||||
}
|
||||
|
||||
function compactText(text: unknown, full: boolean, maxChars: number): Record<string, unknown> {
|
||||
return textView(asString(text), full, maxChars);
|
||||
}
|
||||
|
||||
function compactLastAssistant(value: unknown, full: boolean): Record<string, unknown> {
|
||||
const record = asRecord(value) ?? {};
|
||||
return {
|
||||
at: record.at ?? null,
|
||||
seq: record.seq ?? null,
|
||||
source: record.source ?? "none",
|
||||
...textView(asString(record.text), full, 4000),
|
||||
};
|
||||
}
|
||||
|
||||
function compactToolSummary(value: unknown, full: boolean): Record<string, unknown> {
|
||||
const record = asRecord(value) ?? {};
|
||||
const items = asArray(record.items).map((item) => {
|
||||
const line = asRecord(item) ?? {};
|
||||
return {
|
||||
seq: line.seq ?? null,
|
||||
at: line.at ?? null,
|
||||
kind: line.kind ?? null,
|
||||
title: line.title ?? "",
|
||||
status: line.status ?? null,
|
||||
commandPreview: compactText(line.commandPreview, full, 1200),
|
||||
commandOmittedLines: line.commandOmittedLines ?? 0,
|
||||
outputPreview: compactText(line.outputPreview, full, 800),
|
||||
outputOmittedLines: line.outputOmittedLines ?? 0,
|
||||
rawSeqs: line.rawSeqs ?? [],
|
||||
};
|
||||
});
|
||||
return {
|
||||
count: record.count ?? 0,
|
||||
returned: record.returned ?? items.length,
|
||||
limit: record.limit ?? items.length,
|
||||
truncated: record.truncated ?? false,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
function compactSummary(summary: unknown, options: CodexTaskOptions, taskId: string): Record<string, unknown> {
|
||||
const record = asRecord(summary) ?? {};
|
||||
const transcriptCount = asNumber(record.transcriptCount, 0);
|
||||
const transcriptMaxSeq = transcriptCount > 0 ? record.transcriptMaxSeq ?? null : null;
|
||||
const initialPrompt = asString(record.initialPrompt ?? record.prompt);
|
||||
return {
|
||||
id: record.id ?? taskId,
|
||||
queueId: record.queueId ?? null,
|
||||
status: record.status ?? null,
|
||||
providerId: record.providerId ?? null,
|
||||
model: record.model ?? null,
|
||||
agentPort: record.agentPort ?? null,
|
||||
agentPortInfo: record.agentPortInfo ?? null,
|
||||
reasoningEffort: record.reasoningEffort ?? null,
|
||||
cwd: record.cwd ?? null,
|
||||
attempts: {
|
||||
currentAttempt: record.currentAttempt ?? null,
|
||||
maxAttempts: record.maxAttempts ?? null,
|
||||
currentMode: record.currentMode ?? null,
|
||||
judgeFailCount: record.judgeFailCount ?? null,
|
||||
judgeFailRetryLimit: record.judgeFailRetryLimit ?? null,
|
||||
attemptRecords: asArray(record.attempts).map((attempt) => compactAttemptCycle(attempt, options.full)),
|
||||
},
|
||||
thread: {
|
||||
codexThreadId: record.codexThreadId ?? null,
|
||||
activeTurnId: record.activeTurnId ?? null,
|
||||
cancelRequested: record.cancelRequested ?? null,
|
||||
},
|
||||
timing: record.timing ?? null,
|
||||
createdAt: record.createdAt ?? null,
|
||||
startedAt: record.startedAt ?? null,
|
||||
updatedAt: record.updatedAt ?? null,
|
||||
finishedAt: record.finishedAt ?? null,
|
||||
initialPrompt: textView(initialPrompt, options.full, 3000),
|
||||
basePrompt: textView(asString(record.basePrompt), options.full, 2000),
|
||||
referenceTaskIds: record.referenceTaskIds ?? [],
|
||||
referenceInjection: record.referenceInjection ?? null,
|
||||
lastAssistantMessage: compactLastAssistant(record.lastAssistantMessage, options.full),
|
||||
lastJudge: record.lastJudge ?? null,
|
||||
lastError: record.lastError ?? null,
|
||||
toolSummary: compactToolSummary(record.toolSummary, options.full),
|
||||
counts: {
|
||||
transcript: record.transcriptCount ?? null,
|
||||
output: record.outputCount ?? null,
|
||||
events: record.eventCount ?? null,
|
||||
},
|
||||
traceDisclosure: {
|
||||
included: options.trace,
|
||||
renderer: "shared trace-summary/trace-steps progressive abstraction; CLI and WebUI diverge only at final rendering",
|
||||
total: record.transcriptCount ?? null,
|
||||
maxSeq: transcriptMaxSeq,
|
||||
defaultPage: `bun scripts/cli.ts codex task ${taskId} --trace --limit ${defaultTraceLimit}`,
|
||||
firstPage: `bun scripts/cli.ts codex task ${taskId} --trace --from-start --limit ${defaultTraceLimit}`,
|
||||
nextPageTemplate: `bun scripts/cli.ts codex task ${taskId} --trace --after-seq <nextAfterSeq> --limit ${defaultTraceLimit}`,
|
||||
previousPageTemplate: `bun scripts/cli.ts codex task ${taskId} --trace --before-seq <previousBeforeSeq> --limit ${defaultTraceLimit}`,
|
||||
rawOutputTemplate: `bun scripts/cli.ts codex output ${taskId} --after-seq <rawSeq> --limit ${defaultOutputLimit}`,
|
||||
fullTextSummary: `bun scripts/cli.ts codex task ${taskId} --full --tool-limit ${Math.max(options.toolLimit, defaultToolLimit)}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function traceKindLabel(kind: unknown): string {
|
||||
const value = String(kind || "");
|
||||
if (value === "ran") return "Ran";
|
||||
if (value === "explored") return "Explored";
|
||||
if (value === "edited") return "Edited";
|
||||
if (value === "error") return "Error";
|
||||
if (value === "system") return "System";
|
||||
return "Message";
|
||||
}
|
||||
|
||||
function transcriptLineToStep(line: unknown): Record<string, unknown> {
|
||||
const record = asRecord(line) ?? {};
|
||||
const summaryLines = stringList(record.summaryLines);
|
||||
const fallbackSummary = [record.commandPreview, record.bodyPreview, record.title].map((value) => asString(value).trim()).filter(Boolean);
|
||||
return {
|
||||
seq: record.seq ?? null,
|
||||
at: record.at ?? null,
|
||||
kind: record.kind ?? "message",
|
||||
title: record.title ?? "",
|
||||
status: record.status ?? null,
|
||||
durationMs: record.durationMs ?? null,
|
||||
rawSeqs: record.rawSeqs ?? [],
|
||||
summaryLines: summaryLines.length > 0 ? summaryLines : fallbackSummary.slice(0, 4),
|
||||
hasDetail: true,
|
||||
};
|
||||
}
|
||||
|
||||
function compactTraceStep(step: unknown, taskId: string): Record<string, unknown> {
|
||||
const record = asRecord(step) ?? {};
|
||||
const seq = record.seq ?? null;
|
||||
return {
|
||||
seq,
|
||||
at: record.at ?? null,
|
||||
kind: record.kind ?? "message",
|
||||
label: traceKindLabel(record.kind),
|
||||
title: record.title ?? "",
|
||||
status: record.status ?? null,
|
||||
durationMs: record.durationMs ?? null,
|
||||
duration: fmtDuration(record.durationMs),
|
||||
rawSeqs: record.rawSeqs ?? [],
|
||||
summaryLines: stringList(record.summaryLines),
|
||||
hasDetail: asBoolean(record.hasDetail),
|
||||
detailCommand: seq === null ? null : `bun scripts/cli.ts microservice proxy code-queue /api/tasks/${encodeURIComponent(taskId)}/trace-step?seq=${encodeURIComponent(String(seq))} --raw`,
|
||||
};
|
||||
}
|
||||
|
||||
function compactAttemptCycle(value: unknown, full: boolean): Record<string, unknown> {
|
||||
const record = asRecord(value) ?? {};
|
||||
return {
|
||||
index: record.index ?? null,
|
||||
synthetic: record.synthetic ?? false,
|
||||
label: record.label ?? null,
|
||||
mode: record.mode ?? null,
|
||||
terminalStatus: record.terminalStatus ?? null,
|
||||
appServerExitCode: record.appServerExitCode ?? null,
|
||||
appServerSignal: record.appServerSignal ?? null,
|
||||
error: record.error ?? null,
|
||||
stderrTail: textView(asString(record.stderrTail), full, 1200),
|
||||
startedAt: record.startedAt ?? null,
|
||||
finishedAt: record.finishedAt ?? null,
|
||||
startSeq: record.startSeq ?? null,
|
||||
endSeq: record.endSeq ?? null,
|
||||
execution: record.execution ?? null,
|
||||
finalResponse: textView(asString(record.finalResponsePreview ?? record.finalResponse), full, 3000),
|
||||
judge: record.judge ?? null,
|
||||
feedbackPrompt: textView(asString(record.feedbackPromptPreview), full, 1800),
|
||||
};
|
||||
}
|
||||
|
||||
function traceStepCounts(steps: Record<string, unknown>[]): Record<string, number> {
|
||||
return steps.reduce<Record<string, number>>((counts, step) => {
|
||||
const kind = String(step.kind || "message");
|
||||
counts[kind] = (counts[kind] ?? 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function renderTraceConsoleRows(summary: Record<string, unknown>, steps: Record<string, unknown>[]): string[] {
|
||||
const rows: string[] = [];
|
||||
const execution = asRecord(summary.execution) ?? {};
|
||||
const counts = traceStepCounts(steps);
|
||||
rows.push([
|
||||
`task=${summary.id ?? ""}`,
|
||||
`status=${summary.status ?? ""}`,
|
||||
`port=${summary.agentPort ?? "codex"}`,
|
||||
`model=${summary.model ?? ""}`,
|
||||
`updated=${summary.updatedAt ?? ""}`,
|
||||
].filter((item) => !item.endsWith("=")).join(" "));
|
||||
rows.push(`progressive steps=${steps.length} tools=${execution.toolCallCount ?? 0} read=${execution.readCount ?? counts.explored ?? 0} edit=${execution.editCount ?? counts.edited ?? 0} run=${execution.runCount ?? counts.ran ?? 0} errors=${counts.error ?? 0}`);
|
||||
for (const step of steps) {
|
||||
const head = [`#${step.seq ?? "?"}`, `[${step.label ?? traceKindLabel(step.kind)}]`, step.title ?? ""]
|
||||
.filter((item) => String(item).length > 0)
|
||||
.join(" ");
|
||||
rows.push(`${head}${step.status ? ` (${step.status})` : ""}${step.duration && step.duration !== "--" ? ` ${step.duration}` : ""}`);
|
||||
for (const line of stringList(step.summaryLines).slice(0, 4)) rows.push(` ${line}`);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function compactProgressiveTrace(summaryBody: Record<string, unknown>, steps: Record<string, unknown>[], taskId: string, full: boolean): Record<string, unknown> {
|
||||
const summary = asRecord(summaryBody.summary) ?? summaryBody;
|
||||
return {
|
||||
taskId: summary.id ?? taskId,
|
||||
queueId: summary.queueId ?? null,
|
||||
status: summary.status ?? null,
|
||||
updatedAt: summary.updatedAt ?? null,
|
||||
model: summary.model ?? null,
|
||||
agentPort: summary.agentPort ?? null,
|
||||
agentPortInfo: summary.agentPortInfo ?? null,
|
||||
prompt: summary.prompt ?? null,
|
||||
execution: summary.execution ?? null,
|
||||
attempts: asArray(summary.attempts).map((attempt) => compactAttemptCycle(attempt, full)),
|
||||
displayedStepSeqs: steps.map((step) => step.seq ?? null),
|
||||
renderedRows: renderTraceConsoleRows(summary, steps),
|
||||
};
|
||||
}
|
||||
|
||||
function compactTracePage(body: Record<string, unknown>, taskId: string, limit: number, summaryBody: Record<string, unknown> | null, options: CodexTaskOptions): Record<string, unknown> {
|
||||
const stepsSource = asArray(body.steps).length > 0 ? asArray(body.steps) : asArray(body.transcript).map(transcriptLineToStep);
|
||||
const steps = stepsSource.map((step) => compactTraceStep(step, taskId));
|
||||
const summary = summaryBody === null ? null : asRecord(summaryBody.summary) ?? summaryBody;
|
||||
const nextAfterSeq = body.nextAfterSeq ?? null;
|
||||
const previousBeforeSeq = body.previousBeforeSeq ?? null;
|
||||
const omittedInPage = steps.some((item) => {
|
||||
const line = asRecord(item) ?? {};
|
||||
return asNumber(line.bodyOmittedLines, 0) > 0 || asNumber(line.commandOmittedLines, 0) > 0;
|
||||
});
|
||||
const hasRawSeqs = steps.some((item) => asArray(item.rawSeqs).length > 0);
|
||||
return {
|
||||
taskId: body.taskId ?? taskId,
|
||||
queueId: body.queueId ?? null,
|
||||
status: body.status ?? null,
|
||||
updatedAt: body.updatedAt ?? null,
|
||||
agentPort: body.agentPort ?? summary?.agentPort ?? null,
|
||||
agentPortInfo: body.agentPortInfo ?? summary?.agentPortInfo ?? null,
|
||||
mode: body.mode ?? null,
|
||||
limit,
|
||||
returned: steps.length,
|
||||
total: body.total ?? null,
|
||||
maxSeq: body.maxSeq ?? null,
|
||||
afterSeq: body.afterSeq ?? null,
|
||||
nextAfterSeq,
|
||||
beforeSeq: body.beforeSeq ?? null,
|
||||
previousBeforeSeq,
|
||||
hasMore: body.hasMore ?? false,
|
||||
hasBefore: body.hasBefore ?? false,
|
||||
steps,
|
||||
progressive: summaryBody === null ? null : compactProgressiveTrace(summaryBody, steps, taskId, options.full),
|
||||
commands: {
|
||||
next: body.hasMore === true && nextAfterSeq !== null ? `bun scripts/cli.ts codex task ${taskId} --trace --after-seq ${nextAfterSeq} --limit ${limit}` : null,
|
||||
previous: body.hasBefore === true && previousBeforeSeq !== null ? `bun scripts/cli.ts codex task ${taskId} --trace --before-seq ${previousBeforeSeq} --limit ${limit}` : null,
|
||||
tail: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${limit}`,
|
||||
first: `bun scripts/cli.ts codex task ${taskId} --trace --from-start --limit ${limit}`,
|
||||
stepDetailTemplate: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/${taskId}/trace-step?seq=<seq> --raw`,
|
||||
rawOutput: omittedInPage || hasRawSeqs ? `Use rawSeqs on each trace step, e.g. bun scripts/cli.ts codex output ${taskId} --after-seq <rawSeqBefore> --limit ${defaultOutputLimit}` : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function parseTaskOptions(args: string[]): CodexTaskOptions {
|
||||
const beforeSeq = nullablePositiveNumberOption(args, ["--before-seq", "--beforeSeq"]);
|
||||
const afterSeq = nonNegativeNumberOption(args, ["--after-seq", "--afterSeq"], 0);
|
||||
const fromStart = hasFlag(args, "--from-start") || hasFlag(args, "--first");
|
||||
const trace = hasFlag(args, "--trace") || beforeSeq !== null || args.some((arg) => arg === "--after-seq" || arg === "--afterSeq") || fromStart || hasFlag(args, "--tail");
|
||||
const traceMode = beforeSeq !== null ? "before" : fromStart || args.some((arg) => arg === "--after-seq" || arg === "--afterSeq") ? "after" : "tail";
|
||||
return {
|
||||
trace,
|
||||
traceLimit: positiveIntegerOption(args, ["--trace-limit", "--limit"], defaultTraceLimit, maxTraceLimit),
|
||||
traceMode,
|
||||
afterSeq,
|
||||
beforeSeq,
|
||||
toolLimit: positiveIntegerOption(args, ["--tool-limit"], defaultToolLimit, 500),
|
||||
full: hasFlag(args, "--full") || hasFlag(args, "--raw-summary"),
|
||||
rawSummary: hasFlag(args, "--raw-summary"),
|
||||
};
|
||||
}
|
||||
|
||||
function parseOutputOptions(args: string[]): CodexOutputOptions {
|
||||
const beforeSeq = nullablePositiveNumberOption(args, ["--before-seq", "--beforeSeq"]);
|
||||
const afterSeq = nonNegativeNumberOption(args, ["--after-seq", "--afterSeq"], 0);
|
||||
const fromStart = hasFlag(args, "--from-start") || hasFlag(args, "--first");
|
||||
const mode = beforeSeq !== null ? "before" : fromStart || args.some((arg) => arg === "--after-seq" || arg === "--afterSeq") ? "after" : "tail";
|
||||
return {
|
||||
limit: positiveIntegerOption(args, ["--limit"], defaultOutputLimit, maxTraceLimit),
|
||||
mode,
|
||||
afterSeq,
|
||||
beforeSeq,
|
||||
fullText: hasFlag(args, "--full-text") || hasFlag(args, "--raw"),
|
||||
maxTextChars: positiveIntegerOption(args, ["--max-text-chars"], defaultTextPreviewChars, 500_000),
|
||||
};
|
||||
}
|
||||
|
||||
function queryString(params: Record<string, string | number | boolean | null | undefined>): string {
|
||||
const search = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) search.set(key, String(value));
|
||||
}
|
||||
const text = search.toString();
|
||||
return text.length > 0 ? `?${text}` : "";
|
||||
}
|
||||
|
||||
function codexTaskSummary(taskId: string, options: CodexTaskOptions, fetcher: CodexResponseFetcher): unknown {
|
||||
const summaryPath = `/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: options.toolLimit })}`;
|
||||
const summaryResponse = unwrapCodexResponse(fetcher(summaryPath));
|
||||
const summary = summaryResponse.body.summary;
|
||||
const result: Record<string, unknown> = {
|
||||
upstream: summaryResponse.upstream,
|
||||
summary: compactSummary(summary, options, taskId),
|
||||
};
|
||||
if (options.rawSummary) result.rawSummary = summary;
|
||||
if (options.trace) {
|
||||
const traceParams: Record<string, string | number | boolean | null> = { limit: options.traceLimit };
|
||||
if (options.traceMode === "tail") traceParams.tail = 1;
|
||||
if (options.traceMode === "after") traceParams.afterSeq = options.afterSeq;
|
||||
if (options.traceMode === "before") traceParams.beforeSeq = options.beforeSeq;
|
||||
const traceSummaryResponse = unwrapCodexResponse(fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/trace-summary`));
|
||||
const traceResponse = unwrapCodexResponse(fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/trace-steps${queryString(traceParams)}`));
|
||||
result.trace = compactTracePage(traceResponse.body, taskId, options.traceLimit, traceSummaryResponse.body, options);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function compactOutputPage(body: Record<string, unknown>, taskId: string, limit: number): Record<string, unknown> {
|
||||
const output = asArray(body.output);
|
||||
const nextAfterSeq = body.nextAfterSeq ?? null;
|
||||
const previousBeforeSeq = body.previousBeforeSeq ?? null;
|
||||
return {
|
||||
taskId: body.taskId ?? taskId,
|
||||
queueId: body.queueId ?? null,
|
||||
status: body.status ?? null,
|
||||
updatedAt: body.updatedAt ?? null,
|
||||
mode: body.mode ?? null,
|
||||
limit,
|
||||
returned: output.length,
|
||||
total: body.total ?? null,
|
||||
maxSeq: body.maxSeq ?? null,
|
||||
afterSeq: body.afterSeq ?? null,
|
||||
nextAfterSeq,
|
||||
beforeSeq: body.beforeSeq ?? null,
|
||||
previousBeforeSeq,
|
||||
hasMore: body.hasMore ?? false,
|
||||
hasBefore: body.hasBefore ?? false,
|
||||
output,
|
||||
commands: {
|
||||
next: body.hasMore === true && nextAfterSeq !== null ? `bun scripts/cli.ts codex output ${taskId} --after-seq ${nextAfterSeq} --limit ${limit}` : null,
|
||||
previous: body.hasBefore === true && previousBeforeSeq !== null ? `bun scripts/cli.ts codex output ${taskId} --before-seq ${previousBeforeSeq} --limit ${limit}` : null,
|
||||
tail: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${limit}`,
|
||||
first: `bun scripts/cli.ts codex output ${taskId} --from-start --limit ${limit}`,
|
||||
fullText: `bun scripts/cli.ts codex output ${taskId} --after-seq <rawSeqBefore> --limit ${limit} --full-text`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function codexTaskOutput(taskId: string, options: CodexOutputOptions, fetcher: CodexResponseFetcher): unknown {
|
||||
const params: Record<string, string | number | boolean | null> = {
|
||||
limit: options.limit,
|
||||
fullText: options.fullText ? 1 : 0,
|
||||
maxTextChars: options.maxTextChars,
|
||||
};
|
||||
if (options.mode === "tail") params.tail = 1;
|
||||
if (options.mode === "after") params.afterSeq = options.afterSeq;
|
||||
if (options.mode === "before") params.beforeSeq = options.beforeSeq;
|
||||
const response = unwrapCodexResponse(fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/output${queryString(params)}`));
|
||||
return { upstream: response.upstream, outputPage: compactOutputPage(response.body, taskId, options.limit) };
|
||||
}
|
||||
|
||||
export function codexTaskQuery(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||||
return codexTaskSummary(taskId, parseTaskOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
export function codexOutputQuery(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||||
return codexTaskOutput(taskId, parseOutputOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
async function codexTaskSummaryAsync(taskId: string, options: CodexTaskOptions, fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
const summaryPath = `/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: options.toolLimit })}`;
|
||||
const summaryResponse = unwrapCodexResponse(await fetcher(summaryPath));
|
||||
const summary = summaryResponse.body.summary;
|
||||
const result: Record<string, unknown> = {
|
||||
upstream: summaryResponse.upstream,
|
||||
summary: compactSummary(summary, options, taskId),
|
||||
};
|
||||
if (options.rawSummary) result.rawSummary = summary;
|
||||
if (options.trace) {
|
||||
const traceParams: Record<string, string | number | boolean | null> = { limit: options.traceLimit };
|
||||
if (options.traceMode === "tail") traceParams.tail = 1;
|
||||
if (options.traceMode === "after") traceParams.afterSeq = options.afterSeq;
|
||||
if (options.traceMode === "before") traceParams.beforeSeq = options.beforeSeq;
|
||||
const traceSummaryResponse = unwrapCodexResponse(await fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/trace-summary`));
|
||||
const traceResponse = unwrapCodexResponse(await fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/trace-steps${queryString(traceParams)}`));
|
||||
result.trace = compactTracePage(traceResponse.body, taskId, options.traceLimit, traceSummaryResponse.body, options);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function codexTaskOutputAsync(taskId: string, options: CodexOutputOptions, fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
const params: Record<string, string | number | boolean | null> = {
|
||||
limit: options.limit,
|
||||
fullText: options.fullText ? 1 : 0,
|
||||
maxTextChars: options.maxTextChars,
|
||||
};
|
||||
if (options.mode === "tail") params.tail = 1;
|
||||
if (options.mode === "after") params.afterSeq = options.afterSeq;
|
||||
if (options.mode === "before") params.beforeSeq = options.beforeSeq;
|
||||
const response = unwrapCodexResponse(await fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/output${queryString(params)}`));
|
||||
return { upstream: response.upstream, outputPage: compactOutputPage(response.body, taskId, options.limit) };
|
||||
}
|
||||
|
||||
export async function codexTaskQueryAsync(taskId: string, optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
return codexTaskSummaryAsync(taskId, parseTaskOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
export async function codexOutputQueryAsync(taskId: string, optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
return codexTaskOutputAsync(taskId, parseOutputOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
function requireQueueId(args: string[], command: string): string {
|
||||
const index = args.indexOf("--queue");
|
||||
const raw = index === -1 ? args[0] : args[index + 1];
|
||||
if (raw === undefined || raw.trim().length === 0) throw new Error(`${command} requires queue id, for example --queue default`);
|
||||
return raw.trim();
|
||||
}
|
||||
|
||||
function codeQueues(): unknown {
|
||||
return unwrapCodexResponse(coreInternalFetch("/api/microservices/code-queue/proxy/api/queues"));
|
||||
}
|
||||
|
||||
function codexCreateQueue(queueId: string): unknown {
|
||||
return unwrapCodexResponse(coreInternalFetch("/api/microservices/code-queue/proxy/api/queues", { method: "POST", body: { queueId } }));
|
||||
}
|
||||
|
||||
function codexMoveTask(taskId: string, queueId: string): unknown {
|
||||
return unwrapCodexResponse(coreInternalFetch(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/move`, { method: "POST", body: { queueId } }));
|
||||
}
|
||||
|
||||
export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {
|
||||
const [action = "task", taskIdArg] = args;
|
||||
if (action === "task" || action === "summary" || action === "show") {
|
||||
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
|
||||
return codexTaskQuery(taskId, args.slice(2));
|
||||
}
|
||||
if (action === "output") {
|
||||
const taskId = requireTaskId(taskIdArg, "codex output");
|
||||
return codexOutputQuery(taskId, args.slice(2));
|
||||
}
|
||||
if (action === "queues") return codeQueues();
|
||||
if (action === "queue") {
|
||||
const sub = taskIdArg ?? "list";
|
||||
if (sub === "list") return codeQueues();
|
||||
if (sub === "create") return codexCreateQueue(requireQueueId(args.slice(2), "queue create"));
|
||||
}
|
||||
if (action === "move") {
|
||||
const taskId = requireTaskId(taskIdArg, "codex move");
|
||||
return codexMoveTask(taskId, requireQueueId(args.slice(2), "codex move"));
|
||||
}
|
||||
throw new Error("codex command must be one of: task, summary, show, output, queues, queue list, queue create, move");
|
||||
}
|
||||
Reference in New Issue
Block a user