merge: codex deploy fallback
# Conflicts: # AGENTS.md # TEST.md # config.json # deploy.json # docs/reference/cli.md # docs/reference/microservices.md # docs/reference/observability.md # scripts/cli.ts # scripts/src/microservices.ts # src/components/backend-core/src/microservice-proxy.ts # src/components/microservices/code-queue/src/index.ts # src/components/microservices/code-queue/src/queue-api.ts
This commit is contained in:
@@ -3,18 +3,51 @@ import { runCommand } from "./command";
|
||||
import { type UniDeskConfig, repoRoot } from "./config";
|
||||
import { jsonByteLength, previewJson } from "./preview";
|
||||
|
||||
export function coreInternalFetch(path: string, init?: { method?: string; body?: unknown }): unknown {
|
||||
export function coreInternalFetch(path: string, init?: { method?: string; body?: unknown; maxResponseBytes?: number }): unknown {
|
||||
if (!path.startsWith("/")) throw new Error("core internal path must start with /");
|
||||
const maxResponseBytes = Math.max(1024, Math.floor(init?.maxResponseBytes ?? 5_000_000));
|
||||
const code = `
|
||||
const res = await fetch(${JSON.stringify(`http://127.0.0.1:8080${path}`)}, ${JSON.stringify({
|
||||
method: init?.method ?? "GET",
|
||||
headers: init?.body === undefined ? undefined : { "content-type": "application/json" },
|
||||
body: init?.body === undefined ? undefined : JSON.stringify(init.body),
|
||||
})});
|
||||
const text = await res.text();
|
||||
const maxResponseBytes = ${JSON.stringify(maxResponseBytes)};
|
||||
const reader = res.body?.getReader();
|
||||
const chunks = [];
|
||||
let bytes = 0;
|
||||
let responseTruncated = false;
|
||||
if (reader) {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
if (bytes + value.byteLength > maxResponseBytes) {
|
||||
const keep = Math.max(0, maxResponseBytes - bytes);
|
||||
if (keep > 0) {
|
||||
chunks.push(value.slice(0, keep));
|
||||
bytes += keep;
|
||||
}
|
||||
responseTruncated = true;
|
||||
try { await reader.cancel(); } catch {}
|
||||
break;
|
||||
}
|
||||
chunks.push(value);
|
||||
bytes += value.byteLength;
|
||||
}
|
||||
}
|
||||
const buffer = new Uint8Array(bytes);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
buffer.set(chunk, offset);
|
||||
offset += chunk.byteLength;
|
||||
}
|
||||
const text = new TextDecoder().decode(buffer);
|
||||
let body = null;
|
||||
try { body = text ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
console.log(JSON.stringify({ ok: res.ok, status: res.status, body }));
|
||||
try { body = text && !responseTruncated ? JSON.parse(text) : null; } catch { body = { text }; }
|
||||
if (responseTruncated) {
|
||||
body = { _unideskResponseTruncated: true, maxResponseBytes, bytesRead: bytes, contentLength: res.headers.get("content-length"), textPreview: text };
|
||||
}
|
||||
console.log(JSON.stringify({ ok: res.ok, status: res.status, responseTruncated, responseBytesRead: bytes, responseContentLength: res.headers.get("content-length"), body }));
|
||||
`;
|
||||
const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot);
|
||||
if (result.exitCode !== 0) {
|
||||
@@ -51,6 +84,11 @@ function numberOption(args: string[], name: string, defaultValue: number): numbe
|
||||
return value;
|
||||
}
|
||||
|
||||
function cappedNumberOption(args: string[], name: string, defaultValue: number, maxValue: number): number {
|
||||
const value = numberOption(args, name, defaultValue);
|
||||
return Math.min(value, maxValue);
|
||||
}
|
||||
|
||||
function stringOption(args: string[], name: string): string | undefined {
|
||||
const index = args.indexOf(name);
|
||||
if (index === -1) return undefined;
|
||||
@@ -95,13 +133,34 @@ function methodOption(args: string[], hasBody = false): string {
|
||||
}
|
||||
|
||||
export function summarizeMicroserviceProxyResponse(response: unknown, args: string[]): unknown {
|
||||
if (args.includes("--raw")) return response;
|
||||
const maxBodyBytes = numberOption(args, "--max-body-bytes", 60_000);
|
||||
const full = args.includes("--full");
|
||||
const raw = args.includes("--raw");
|
||||
const maxBodyBytes = full ? numberOption(args, "--max-body-bytes", 5_000_000) : cappedNumberOption(args, "--max-body-bytes", raw ? 120_000 : 60_000, 500_000);
|
||||
if (typeof response !== "object" || response === null || Array.isArray(response)) return response;
|
||||
const record = response as Record<string, unknown>;
|
||||
if (!("body" in record)) return response;
|
||||
if (record.responseTruncated === true) {
|
||||
return {
|
||||
...record,
|
||||
bodyOmitted: true,
|
||||
bodyMaxBytes: maxBodyBytes,
|
||||
rawHint: "The upstream response exceeded the CLI collection cap before JSON parsing; re-run with --raw --full and a specific --max-body-bytes only when the full body is required.",
|
||||
};
|
||||
}
|
||||
const bodyBytes = jsonByteLength(record.body);
|
||||
if (bodyBytes <= maxBodyBytes) return response;
|
||||
if (bodyBytes <= maxBodyBytes) {
|
||||
if (!raw || full) return response;
|
||||
return {
|
||||
...record,
|
||||
outputPolicy: {
|
||||
rawRequested: true,
|
||||
bounded: true,
|
||||
maxBodyBytes,
|
||||
bodyBytes,
|
||||
fullCommand: "Re-run with --raw --full to allow the complete body.",
|
||||
},
|
||||
};
|
||||
}
|
||||
const rest = { ...record };
|
||||
delete rest.body;
|
||||
return {
|
||||
@@ -110,7 +169,9 @@ export function summarizeMicroserviceProxyResponse(response: unknown, args: stri
|
||||
bodyBytes,
|
||||
bodyMaxBytes: maxBodyBytes,
|
||||
bodyPreview: previewJson(record.body, { maxDepth: 3, maxArrayItems: 3, maxObjectKeys: 16, maxStringLength: 320 }),
|
||||
rawHint: "Re-run with --raw for the full body, or add/tighten __unideskArrayLimit=<path>:<limit> in the proxied path.",
|
||||
rawHint: raw && !full
|
||||
? "The --raw response exceeded the default hard limit; re-run with --raw --full for the complete body, or add/tighten __unideskArrayLimit=<path>:<limit> in the proxied path."
|
||||
: "Re-run with --raw --full for the complete body, or add/tighten __unideskArrayLimit=<path>:<limit> in the proxied path.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,7 +198,11 @@ export async function runMicroserviceCommand(_config: UniDeskConfig, args: strin
|
||||
const id = requireId(idArg, "microservice proxy");
|
||||
const path = requireProxyPath(pathArg);
|
||||
const body = requestBodyOption(args);
|
||||
return summarizeMicroserviceProxyResponse(coreInternalFetch(`/api/microservices/${encodeId(id)}/proxy${path}`, { method: methodOption(args, body !== undefined), body }), args);
|
||||
const full = hasFlag(args, "--full");
|
||||
const raw = hasFlag(args, "--raw");
|
||||
const maxBodyBytes = full ? numberOption(args, "--max-body-bytes", 5_000_000) : cappedNumberOption(args, "--max-body-bytes", raw ? 120_000 : 60_000, 500_000);
|
||||
const maxResponseBytes = full ? Math.min(Math.max(maxBodyBytes, 120_000), 5_000_000) : Math.min(Math.max(maxBodyBytes * 3, 240_000), 1_500_000);
|
||||
return summarizeMicroserviceProxyResponse(coreInternalFetch(`/api/microservices/${encodeId(id)}/proxy${path}`, { method: methodOption(args, body !== undefined), body, maxResponseBytes }), args);
|
||||
}
|
||||
throw new Error("microservice command must be one of: list, status, health, diagnostics, tunnel-self-test, proxy");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user