fix: align opencode workspace event root

This commit is contained in:
UniDesk Codex
2026-06-30 13:56:27 +08:00
parent 6bf9a49f6d
commit 77403faa9a
3 changed files with 113 additions and 12 deletions
@@ -74,6 +74,7 @@ async function proxyProviderRequest(clientReq, clientRes, url) {
let statusCode = 0;
let errorCode = "";
let timeout = null;
let streamStats = createOpenAIStreamStats(false);
const settle = () => {
if (settled) return;
settled = true;
@@ -95,7 +96,8 @@ async function proxyProviderRequest(clientReq, clientRes, url) {
"http.response.status_class": statusCode ? `${Math.floor(statusCode / 100)}xx` : "unknown",
"opencode.provider.elapsed_ms": elapsedMs,
"opencode.provider.timeout_ms": timeoutMs,
"opencode.provider.phase": errorCode ? "failed" : "complete"
"opencode.provider.phase": errorCode ? "failed" : "complete",
...openAIStreamStatsAttributes(streamStats)
}
});
resolve();
@@ -112,12 +114,13 @@ async function proxyProviderRequest(clientReq, clientRes, url) {
}, (upstreamRes) => {
statusCode = upstreamRes.statusCode || 502;
const filterReasoning = shouldFilterOpenAIReasoning(route, upstreamRes.headers);
streamStats = createOpenAIStreamStats(filterReasoning);
clientRes.writeHead(statusCode, {
...providerResponseHeaders(upstreamRes.headers),
traceparent: traceparent(trace.traceId, startSpanId),
"x-hwlab-otel-trace-id": trace.traceId
});
const responseStream = filterReasoning ? upstreamRes.pipe(openAIReasoningFilterStream()) : upstreamRes;
const responseStream = filterReasoning ? upstreamRes.pipe(openAIReasoningFilterStream(streamStats)) : upstreamRes;
responseStream.pipe(clientRes);
upstreamRes.on("end", settle);
upstreamRes.on("close", settle);
@@ -190,7 +193,45 @@ function shouldFilterOpenAIReasoning(route, headers) {
return route === `${publicBasePath}/chat/completions` && /^text\/event-stream(?:\s*;|$)/iu.test(firstHeaderValue(headers?.["content-type"]));
}
function openAIReasoningFilterStream() {
function createOpenAIStreamStats(filterEnabled) {
return {
filterEnabled: Boolean(filterEnabled),
dataLines: 0,
outputDataLines: 0,
doneLines: 0,
jsonErrors: 0,
choices: 0,
contentChunks: 0,
contentChars: 0,
reasoningChunks: 0,
reasoningChars: 0,
reasoningOnlyChoicesDropped: 0,
emptyDeltaChoicesDropped: 0,
finishChoices: 0,
usageChunks: 0
};
}
function openAIStreamStatsAttributes(stats) {
return {
"opencode.provider.sse.filter_enabled": stats.filterEnabled,
"opencode.provider.sse.data_lines": stats.dataLines,
"opencode.provider.sse.output_data_lines": stats.outputDataLines,
"opencode.provider.sse.done_lines": stats.doneLines,
"opencode.provider.sse.json_errors": stats.jsonErrors,
"opencode.provider.sse.choice_count": stats.choices,
"opencode.provider.sse.content_chunks": stats.contentChunks,
"opencode.provider.sse.content_chars": stats.contentChars,
"opencode.provider.sse.reasoning_chunks": stats.reasoningChunks,
"opencode.provider.sse.reasoning_chars": stats.reasoningChars,
"opencode.provider.sse.reasoning_only_choices_dropped": stats.reasoningOnlyChoicesDropped,
"opencode.provider.sse.empty_delta_choices_dropped": stats.emptyDeltaChoicesDropped,
"opencode.provider.sse.finish_choices": stats.finishChoices,
"opencode.provider.sse.usage_chunks": stats.usageChunks
};
}
function openAIReasoningFilterStream(stats = createOpenAIStreamStats(true)) {
let pending = "";
return new Transform({
transform(chunk, _encoding, callback) {
@@ -199,46 +240,76 @@ function openAIReasoningFilterStream() {
while (newlineIndex >= 0) {
const line = pending.slice(0, newlineIndex + 1);
pending = pending.slice(newlineIndex + 1);
const next = filterOpenAISseLine(line);
if (next) this.push(next);
const next = filterOpenAISseLine(line, stats);
if (next) {
stats.outputDataLines += isOpenAIDataLine(next) ? 1 : 0;
this.push(next);
}
newlineIndex = pending.indexOf("\n");
}
callback();
},
flush(callback) {
if (pending) {
const next = filterOpenAISseLine(pending);
if (next) this.push(next);
const next = filterOpenAISseLine(pending, stats);
if (next) {
stats.outputDataLines += isOpenAIDataLine(next) ? 1 : 0;
this.push(next);
}
}
callback();
}
});
}
function filterOpenAISseLine(line) {
function isOpenAIDataLine(line) {
return /^data:\s*/u.test(line);
}
function filterOpenAISseLine(line, stats = createOpenAIStreamStats(true)) {
const match = line.match(/^(data:\s*)(.*?)(\r?\n)?$/u);
if (!match) return line;
const [, prefix, payload, lineEnding = ""] = match;
const text = payload.trim();
if (!text || text === "[DONE]") return line;
if (!text) return line;
stats.dataLines += 1;
if (text === "[DONE]") {
stats.doneLines += 1;
return line;
}
try {
const parsed = JSON.parse(text);
const choices = Array.isArray(parsed.choices) ? parsed.choices.map(filterOpenAIChoiceReasoning).filter(Boolean) : parsed.choices;
if (parsed.usage != null) stats.usageChunks += 1;
const choices = Array.isArray(parsed.choices) ? parsed.choices.map((choice) => filterOpenAIChoiceReasoning(choice, stats)).filter(Boolean) : parsed.choices;
if (Array.isArray(parsed.choices)) {
if (choices.length === 0 && parsed.usage === null) return "";
parsed.choices = choices;
}
return `${prefix}${JSON.stringify(parsed)}${lineEnding}`;
} catch {
stats.jsonErrors += 1;
return line;
}
}
function filterOpenAIChoiceReasoning(choice) {
function filterOpenAIChoiceReasoning(choice, stats = createOpenAIStreamStats(true)) {
if (!choice || typeof choice !== "object") return choice;
const next = { ...choice };
stats.choices += 1;
if (next.finish_reason != null) stats.finishChoices += 1;
if (next.delta && typeof next.delta === "object") {
const delta = { ...next.delta };
const reasoningText = [delta.reasoning_content, delta.reasoning, delta.reasoning_details]
.filter((value) => typeof value === "string")
.join("");
if (reasoningText) {
stats.reasoningChunks += 1;
stats.reasoningChars += reasoningText.length;
}
if (typeof delta.content === "string" && delta.content.length > 0) {
stats.contentChunks += 1;
stats.contentChars += delta.content.length;
}
delete delta.reasoning_content;
delete delta.reasoning;
delete delta.reasoning_details;
@@ -252,7 +323,11 @@ function filterOpenAIChoiceReasoning(choice) {
delete next.message.reasoning_details;
}
const deltaEmpty = next.delta && typeof next.delta === "object" && Object.keys(next.delta).length === 0;
if (deltaEmpty && next.finish_reason == null && next.logprobs == null) return null;
if (deltaEmpty && next.finish_reason == null && next.logprobs == null) {
stats.emptyDeltaChoicesDropped += 1;
stats.reasoningOnlyChoicesDropped += 1;
return null;
}
return next;
}
+21
View File
@@ -5842,6 +5842,27 @@ function opencodeServerManifest({ profile = "v03", source, deploy = null, catalo
spec: {
serviceAccountName: name,
securityContext: { fsGroup: 1000, fsGroupChangePolicy: "OnRootMismatch" },
initContainers: [{
name: "opencode-workspace-git-init",
image: helperImage,
imagePullPolicy: "IfNotPresent",
command: ["/bin/sh", "-ec"],
args: [
[
"set -eu",
"mkdir -p /workspace",
"if [ ! -d /workspace/.git ]; then",
" git -C /workspace init",
"fi",
"git -C /workspace config user.name 'HWLAB OpenCode' || true",
"git -C /workspace config user.email 'opencode@hwlab.local' || true",
"chgrp -R 1000 /workspace/.git 2>/dev/null || true",
"chmod -R g+rwX /workspace/.git 2>/dev/null || true"
].join("\n")
],
resources: { requests: { cpu: "25m", memory: "64Mi" }, limits: { cpu: "250m", memory: "256Mi" } },
volumeMounts: [{ name: "workspace", mountPath: "/workspace" }]
}],
containers: [{
name,
image: "ghcr.io/anomalyco/opencode:1.17.7",
+5
View File
@@ -761,6 +761,11 @@ test("v03 render includes D518 secret-plane smoke on D518 gitops root", async ()
assert.equal(opencodeDeployment.metadata?.annotations?.["hwlab.pikastech.local/opencode-provider-proxy-boot-sh"], "deploy/runtime/boot/opencode-provider-proxy.sh");
assert.equal(opencodeDeployment.metadata?.annotations?.["hwlab.pikastech.local/values-printed"], "false");
assert.match(opencodeDeployment.spec?.template?.metadata?.annotations?.["hwlab.pikastech.local/opencode-config-sha256"] ?? "", /^[a-f0-9]{64}$/u);
const opencodeGitInit = opencodeDeployment.spec?.template?.spec?.initContainers?.find((container) => container.name === "opencode-workspace-git-init");
assert.ok(opencodeGitInit, "expected opencode workspace git init container");
assert.deepEqual(opencodeGitInit.command, ["/bin/sh", "-ec"]);
assert.match(opencodeGitInit.args?.[0] ?? "", /git -C \/workspace init/u);
assert.ok(opencodeGitInit.volumeMounts?.some((entry) => entry.name === "workspace" && entry.mountPath === "/workspace"));
const opencodeContainer = collectContainersFromItem(opencodeDeployment).find((container) => container.name === "opencode-server");
assert.deepEqual(opencodeContainer?.command, ["/bin/sh", "-ec"]);
assert.equal(opencodeContainer?.args?.[0], "exec opencode serve --hostname 0.0.0.0 --port 4096");