fix: bridge deepseek codex responses

This commit is contained in:
Codex
2026-05-26 08:28:02 +08:00
parent 24e0b94a09
commit f9efaa0b20
11 changed files with 403 additions and 12 deletions
@@ -0,0 +1,243 @@
#!/usr/bin/env node
import { createServer, request as httpRequest } from "node:http";
import { request as httpsRequest } from "node:https";
import { decompress } from "fzstd";
const serviceId = "hwlab-deepseek-responses-bridge";
const port = parsePositiveInteger(process.env.PORT, 4000);
const upstreamBaseUrl = new URL(process.env.HWLAB_DEEPSEEK_BRIDGE_UPSTREAM || "http://127.0.0.1:4001");
const defaultModel = String(process.env.HWLAB_DEEPSEEK_BRIDGE_MODEL || process.env.HWLAB_CODE_AGENT_DEEPSEEK_MODEL || "deepseek-chat").trim() || "deepseek-chat";
const maxBodyBytes = parsePositiveInteger(process.env.HWLAB_DEEPSEEK_BRIDGE_MAX_BODY_BYTES, 64 * 1024 * 1024);
const hopByHopHeaders = new Set([
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade"
]);
const server = createServer(async (clientReq, clientRes) => {
try {
const url = new URL(clientReq.url || "/", "http://hwlab-deepseek-bridge.local");
if (url.pathname === "/health" || url.pathname === "/health/live" || url.pathname === "/health/liveliness") {
sendJson(clientRes, 200, healthPayload());
return;
}
if (url.pathname === "/health/readiness") {
await sendReadiness(clientRes);
return;
}
if (clientReq.method === "GET" && (url.pathname === "/v1/models" || url.pathname === "/models")) {
await sendCodexModels(clientReq, clientRes, url);
return;
}
await proxyRequest(clientReq, clientRes, url);
} catch (error) {
sendJson(clientRes, 502, {
error: {
code: "deepseek_bridge_proxy_failed",
message: "DeepSeek Responses bridge failed to proxy request",
reason: error.message
}
});
}
});
server.listen(port, "0.0.0.0", () => {
process.stdout.write(JSON.stringify({ serviceId, status: "listening", port, upstream: upstreamBaseUrl.origin }) + "\n");
});
async function sendReadiness(res) {
const upstream = await fetchUpstreamJson("/health/readiness").catch((error) => ({ ok: false, status: 502, payload: { error: error.message } }));
sendJson(res, upstream.ok ? 200 : 503, {
...healthPayload(),
status: upstream.ok ? "ready" : "degraded",
upstream: {
ok: upstream.ok,
status: upstream.status,
payload: upstream.payload
}
});
}
async function sendCodexModels(clientReq, clientRes, url) {
const upstream = await fetchUpstreamJson(`${url.pathname}${url.search}`, clientReq.headers).catch((error) => ({ ok: false, status: 502, payload: { error: error.message } }));
const models = codexModelsFromUpstream(upstream.payload);
sendJson(clientRes, 200, {
models: models.length > 0 ? models : [codexModel(defaultModel)],
upstream: {
ok: upstream.ok,
status: upstream.status,
source: upstream.ok ? "litellm" : "fallback"
}
});
}
async function proxyRequest(clientReq, clientRes, url) {
const startedAt = Date.now();
const originalBody = await readRequestBody(clientReq);
const { body, headers, decompressed } = decodeRequestBody(originalBody, clientReq.headers);
const targetUrl = new URL(`${url.pathname}${url.search}`, upstreamBaseUrl);
const requestImpl = targetUrl.protocol === "https:" ? httpsRequest : httpRequest;
const upstreamReq = requestImpl({
protocol: targetUrl.protocol,
hostname: targetUrl.hostname,
port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
method: clientReq.method,
path: `${targetUrl.pathname}${targetUrl.search}`,
headers
}, (upstreamRes) => {
const responseHeaders = filterResponseHeaders(upstreamRes.headers);
clientRes.writeHead(upstreamRes.statusCode || 502, responseHeaders);
upstreamRes.pipe(clientRes);
upstreamRes.on("end", () => {
process.stderr.write(JSON.stringify({
serviceId,
method: clientReq.method,
path: url.pathname,
status: upstreamRes.statusCode || null,
decompressed,
elapsedMs: Date.now() - startedAt
}) + "\n");
});
});
upstreamReq.on("error", (error) => {
if (!clientRes.headersSent) {
sendJson(clientRes, 502, {
error: {
code: "deepseek_bridge_upstream_error",
message: "DeepSeek bridge upstream request failed",
reason: error.message
}
});
} else {
clientRes.end();
}
});
upstreamReq.end(body);
}
function decodeRequestBody(body, headers) {
const encoding = String(headers["content-encoding"] || "").trim().toLowerCase();
const decoded = encoding === "zstd" ? Buffer.from(decompress(new Uint8Array(body))) : body;
const forwarded = filterRequestHeaders(headers);
if (encoding === "zstd") delete forwarded["content-encoding"];
forwarded["content-length"] = String(decoded.length);
forwarded.host = upstreamBaseUrl.host;
return { body: decoded, headers: forwarded, decompressed: encoding === "zstd" };
}
async function readRequestBody(req) {
const chunks = [];
let size = 0;
for await (const chunk of req) {
size += chunk.length;
if (size > maxBodyBytes) throw new Error(`request body exceeds ${maxBodyBytes} bytes`);
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
async function fetchUpstreamJson(pathAndSearch, headers = {}) {
const targetUrl = new URL(pathAndSearch, upstreamBaseUrl);
const response = await fetch(targetUrl, {
headers: {
accept: "application/json",
...filterRequestHeaders(headers, { keepContentHeaders: false }),
host: upstreamBaseUrl.host
}
});
const text = await response.text();
let payload = null;
try {
payload = text ? JSON.parse(text) : null;
} catch {
payload = { text };
}
return { ok: response.ok, status: response.status, payload };
}
function codexModelsFromUpstream(payload) {
if (payload && typeof payload === "object" && Array.isArray(payload.models)) {
return payload.models.map(normalizeCodexModel).filter(Boolean);
}
if (payload && typeof payload === "object" && Array.isArray(payload.data)) {
return payload.data
.map((item) => String(item?.id || item?.model_name || "").trim())
.filter(Boolean)
.map(codexModel);
}
return [];
}
function normalizeCodexModel(model) {
const slug = String(model?.slug || model?.id || "").trim();
return slug ? { ...codexModel(slug), ...model, slug } : null;
}
function codexModel(slug) {
return {
slug,
display_name: slug,
description: `${slug} via HWLAB DeepSeek bridge`,
default_reasoning_level: null,
supported_reasoning_levels: [],
shell_type: "shell_command",
visibility: "list",
supported_in_api: true,
priority: 0
};
}
function filterRequestHeaders(headers, options = {}) {
const keepContentHeaders = options.keepContentHeaders !== false;
const result = {};
for (const [name, value] of Object.entries(headers)) {
const lower = name.toLowerCase();
if (hopByHopHeaders.has(lower)) continue;
if (lower === "host") continue;
if (!keepContentHeaders && (lower === "content-length" || lower === "content-type" || lower === "content-encoding")) continue;
result[lower] = value;
}
return result;
}
function filterResponseHeaders(headers) {
const result = {};
for (const [name, value] of Object.entries(headers)) {
if (!hopByHopHeaders.has(name.toLowerCase())) result[name] = value;
}
return result;
}
function healthPayload() {
return {
serviceId,
status: "ok",
upstream: upstreamBaseUrl.origin,
model: defaultModel,
zstdRequestBody: "decode"
};
}
function sendJson(res, statusCode, payload) {
const body = JSON.stringify(payload, null, 2);
res.writeHead(statusCode, {
"content-type": "application/json; charset=utf-8",
"content-length": Buffer.byteLength(body)
});
res.end(body);
}
function parsePositiveInteger(value, fallback) {
const parsed = Number(value);
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
}
@@ -0,0 +1,116 @@
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { createServer } from "node:http";
import test from "node:test";
const zstdPayload = Buffer.from([
0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x58, 0x49, 0x01, 0x00, 0x7b, 0x22, 0x6d,
0x6f, 0x64, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x64, 0x65, 0x65, 0x70, 0x73,
0x65, 0x65, 0x6b, 0x2d, 0x63, 0x68, 0x61, 0x74, 0x22, 0x2c, 0x22, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x22, 0x3a, 0x22, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
0x22, 0x7d, 0xc0, 0x09, 0xab, 0xba
]);
const decodedPayload = JSON.stringify({ model: "deepseek-chat", input: "hello" });
test("DeepSeek Responses bridge decompresses Codex zstd body and maps models catalog", async () => {
const upstream = await startUpstream();
const bridge = await startBridge(upstream.port);
try {
const response = await fetch(`http://127.0.0.1:${bridge.port}/v1/responses`, {
method: "POST",
headers: {
"content-type": "application/json",
"content-encoding": "zstd"
},
body: zstdPayload
});
assert.equal(response.status, 200);
assert.equal(upstream.captured.encoding, null);
assert.equal(upstream.captured.body, decodedPayload);
const modelsResponse = await fetch(`http://127.0.0.1:${bridge.port}/v1/models?client_version=test`);
assert.equal(modelsResponse.status, 200);
const models = await modelsResponse.json();
assert.equal(Array.isArray(models.models), true);
assert.equal(models.models[0].slug, "deepseek-chat");
} finally {
await bridge.stop();
await upstream.stop();
}
});
async function startUpstream() {
let captured = null;
const server = createServer(async (request, response) => {
if ((request.url || "").startsWith("/v1/models")) {
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({ data: [{ id: "deepseek-chat" }] }));
return;
}
const chunks = [];
for await (const chunk of request) chunks.push(chunk);
captured = {
url: request.url,
method: request.method,
encoding: request.headers["content-encoding"] || null,
length: request.headers["content-length"] || null,
body: Buffer.concat(chunks).toString("utf8")
};
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({ ok: true }));
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
return {
port: server.address().port,
get captured() {
return captured;
},
stop: () => new Promise((resolve) => server.close(resolve))
};
}
async function startBridge(upstreamPort) {
const port = await freePort();
const child = spawn(process.execPath, ["cmd/hwlab-deepseek-responses-bridge/main.mjs"], {
cwd: process.cwd(),
env: {
...process.env,
PORT: String(port),
HWLAB_DEEPSEEK_BRIDGE_UPSTREAM: `http://127.0.0.1:${upstreamPort}`,
HWLAB_DEEPSEEK_BRIDGE_MODEL: "deepseek-chat"
},
stdio: ["ignore", "pipe", "pipe"]
});
let stderr = "";
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`bridge did not start: ${stderr}`)), 5000);
child.stdout.on("data", (chunk) => {
if (chunk.toString().includes("listening")) {
clearTimeout(timer);
resolve();
}
});
child.on("exit", (code) => {
clearTimeout(timer);
reject(new Error(`bridge exited early code=${code}: ${stderr}`));
});
});
return {
port,
stop: async () => {
child.kill("SIGTERM");
await new Promise((resolve) => child.once("exit", resolve));
}
};
}
async function freePort() {
const server = createServer();
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = server.address().port;
await new Promise((resolve) => server.close(resolve));
return port;
}
+7 -2
View File
@@ -25,8 +25,13 @@ stdio session supervisor,并证明 `/workspace/hwlab` 可读写、`CODEX_HOME=
可创建和复用同一个 Codex thread/session。
Codex token boundary 仍由授权路径把 `OPENAI_API_KEY` 注入到 DEV runtime。DEV Pod
必须使用 `HWLAB_CODE_AGENT_OPENAI_BASE_URL` 指向受控 DEV egress/proxy 路径
`http://172.26.26.227:17680/v1/responses`,不能直接指向 public `api.openai.com`
必须使用 `HWLAB_CODE_AGENT_OPENAI_BASE_URL` 指向受控 DEV egress/proxy 路径,不能
直接指向 public `api.openai.com`G14 默认 `deepseek` profile 指向
`http://hwlab-deepseek-proxy.<namespace>.svc.cluster.local:4000/v1/responses`;该
Service 必须先进入 `hwlab-deepseek-responses-bridge`,由 bridge 解压 Codex
Responses 的 zstd request body、规范化 `/v1/models` 返回,再转发到同 Pod 内
LiteLLM 4001。`codex-api` profile 仍可指向
`http://172.26.26.227:17680/v1/responses` 作为旧 Responses 通道。
Runner 不得尝试修补、读取、回显或替换该 Secret。若 DEV runtime 缺少该授权凭证注入,
`provider_unavailable``error.missingEnv` 包含 `OPENAI_API_KEY` 必须判为
+4 -2
View File
@@ -45,10 +45,12 @@ G14 Code Agent 通过同一个 repo-owned Codex app-server stdio runner 承载
| Profile | 默认 | Model | Base URL | 说明 |
| --- | --- | --- | --- | --- |
| `deepseek` | 是 | `HWLAB_CODE_AGENT_DEEPSEEK_MODEL`,默认 `deepseek-chat` | `HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL`,默认 `http://hwlab-deepseek-proxy.<namespace>.svc.cluster.local:4000/v1/responses` | G14 集群内 LiteLLM DeepSeek bridge。 |
| `deepseek` | 是 | `HWLAB_CODE_AGENT_DEEPSEEK_MODEL`,默认 `deepseek-chat` | `HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL`,默认 `http://hwlab-deepseek-proxy.<namespace>.svc.cluster.local:4000/v1/responses` | G14 集群内 DeepSeek Responses bridgeService 4000 先进入 `hwlab-deepseek-responses-bridge`,再转发到同 Pod 内 LiteLLM 4001。 |
| `codex-api` | 否 | `HWLAB_CODE_AGENT_CODEX_API_MODEL`,默认 `gpt-5.5` | `HWLAB_CODE_AGENT_CODEX_API_BASE_URL`,默认 `http://172.26.26.227:17680/v1/responses` | 保留旧 Codex/OpenAI-compatible Responses API 通道。 |
`hwlab-cloud-api` 仍以 `HWLAB_CODE_AGENT_PROVIDER=codex-stdio` 运行。`HWLAB_CODE_AGENT_MODEL``HWLAB_CODE_AGENT_OPENAI_BASE_URL` 可作为 runtime-default 兜底,但前端默认 profile 是 `deepseek`,用户可以切到 `codex-api`。Codex app-server 启动参数必须同时设置 provider base URL、`model``review_model`,否则 LiteLLM `/v1/responses` 会收到 `model=None`。旧 `codex-api` profile 指向 `172.26.26.227`cloud-api 的 `NO_PROXY/no_proxy` 必须包含该 host 或私网段,避免旧通道被 G14 外网代理误拦截。
`hwlab-cloud-api` 仍以 `HWLAB_CODE_AGENT_PROVIDER=codex-stdio` 运行。`HWLAB_CODE_AGENT_MODEL``HWLAB_CODE_AGENT_OPENAI_BASE_URL` 可作为 runtime-default 兜底,但前端默认 profile 是 `deepseek`,用户可以切到 `codex-api`。Codex app-server 启动参数必须同时设置 provider base URL、provider `name``model``review_model`,否则 Codex/LiteLLM 链路可能在模型目录或 `/v1/responses` 阶段退化成 `model=None`。旧 `codex-api` profile 指向 `172.26.26.227`cloud-api 的 `NO_PROXY/no_proxy` 必须包含该 host 或私网段,避免旧通道被 G14 外网代理误拦截。
Codex app-server 当前要求 provider `wire_api="responses"`,不得把 DeepSeek profile 切到旧 `chat` wire API。Codex 发往 `/v1/responses` 的请求体可能带 `Content-Encoding: zstd`,而 LiteLLM DeepSeek bridge 默认不能按 Codex 期望解压该请求体,也不能直接返回 Codex 期望的 `models` 目录格式。因此 GitOps 中的 `hwlab-deepseek-proxy` Pod 必须包含 repo-owned `hwlab-deepseek-responses-bridge` sidecarService 端口 4000 指向 bridgebridge 对 zstd request body 解压、删除 `Content-Encoding`/重写 `Content-Length` 后转发给 4001 的 LiteLLM,并把 `GET /v1/models` 转成 Codex-style `{ "models": [...] }` 目录。模型、密钥和真实推理仍由 LiteLLM 管理;bridge 不新增业务 gate,也不得把兼容失败伪装成 SOURCE/legacy blocker。
DeepSeek proxy manifest 是 GitOps desired state 的一部分,DEV/PROD 分别生成在 `runtime-dev/deepseek-proxy.yaml``runtime-prod/deepseek-proxy.yaml`。DeepSeek key 仍通过 `hwlab-code-agent-provider/openai-api-key` Secret 注入到 proxy 容器;文档、trace、health、issue 和日志不得打印 Secret 值。
@@ -1224,6 +1224,7 @@ test("Codex app-server args pin repo-owned DEV responses egress without leaking
assert.deepEqual(args.slice(-2), ["--listen", "stdio://"]);
assert.ok(args.includes("model_provider=\"OpenAI\""));
assert.ok(args.includes("model_providers.OpenAI.base_url=\"http://172.26.26.227:17680/v1\""));
assert.ok(args.includes("model_providers.OpenAI.name=\"OpenAI\""));
assert.ok(args.includes("model_providers.OpenAI.wire_api=\"responses\""));
assert.ok(args.includes("model=\"deepseek-chat\""));
assert.ok(args.includes("review_model=\"deepseek-chat\""));
+2
View File
@@ -1210,6 +1210,8 @@ function codexAppServerProviderConfigArgs(env = process.env) {
"-c",
`model_providers.OpenAI.base_url=${tomlString(baseUrl)}`,
"-c",
"model_providers.OpenAI.name=\"OpenAI\"",
"-c",
"model_providers.OpenAI.wire_api=\"responses\"",
"-c",
"model_providers.OpenAI.requires_openai_auth=true"
+6
View File
@@ -9,6 +9,7 @@
"version": "0.0.0-l0",
"dependencies": {
"@openai/codex": "^0.128.0",
"fzstd": "0.1.1",
"pg": "^8.21.0",
"playwright": "1.59.1"
}
@@ -149,6 +150,11 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/fzstd": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/fzstd/-/fzstd-0.1.1.tgz",
"integrity": "sha512-dkuVSOKKwh3eas5VkJy1AW1vFpet8TA/fGmVA5krThl8YcOVE/8ZIoEA1+U1vEn5ckxxhLirSdY837azmbaNHA=="
},
"node_modules/pg": {
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz",
+2 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -845,7 +845,7 @@ function dockerfile(baseImage, port) {
"ENV HWLAB_CODE_AGENT_SKILLS_DIRS=/app/skills:/root/.agents/skills:/home/ubuntu/.agents/skills",
"COPY package.json ./package.json",
"COPY package-lock.json* ./",
"RUN if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; elif [ -f package-lock.json ]; then npm ci --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && codex_version=\"$(node -p \"require('./node_modules/@openai/codex/package.json').version\")\" && case \"$(uname -m)\" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts \"@openai/codex-linux-x64@npm:@openai/codex@${codex_version}-linux-x64\" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts \"@openai/codex-linux-arm64@npm:@openai/codex@${codex_version}-linux-arm64\" ;; esac",
"RUN if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && codex_version=\"$(node -p \"require('./node_modules/@openai/codex/package.json').version\")\" && case \"$(uname -m)\" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts \"@openai/codex-linux-x64@npm:@openai/codex@${codex_version}-linux-x64\" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts \"@openai/codex-linux-arm64@npm:@openai/codex@${codex_version}-linux-arm64\" ;; esac",
"COPY internal ./internal",
"COPY cmd ./cmd",
"COPY scripts ./scripts",
+1
View File
@@ -315,6 +315,7 @@ async function runLocalContractSmoke() {
assert.equal(codexAppServerProviderBaseUrl(providerEnv), "http://172.26.26.227:17680/v1");
const providerArgs = codexAppServerArgs(providerEnv).join(" ");
assert.match(providerArgs, /model_providers\.OpenAI\.base_url="http:\/\/172\.26\.26\.227:17680\/v1"/u);
assert.match(providerArgs, /model_providers\.OpenAI\.name="OpenAI"/u);
assert.match(providerArgs, /model_providers\.OpenAI\.wire_api="responses"/u);
assert.match(providerArgs, /model_providers\.OpenAI\.requires_openai_auth=true/u);
assert.equal(providerArgs.includes("test-openai-key-material"), false);
+20 -6
View File
@@ -1362,8 +1362,9 @@ remotePort = ${edgeRemotePort}
};
}
function deepSeekProxyManifest({ profile = "dev" } = {}) {
function deepSeekProxyManifest({ profile = "dev", source, registryPrefix = defaultRegistryPrefix } = {}) {
const namespace = namespaceNameForProfile(profile);
const bridgeImage = imageFor(registryPrefix, "hwlab-cloud-api", source.short);
const labels = {
"app.kubernetes.io/name": "hwlab-deepseek-proxy",
"app.kubernetes.io/part-of": "hwlab",
@@ -1394,14 +1395,27 @@ function deepSeekProxyManifest({ profile = "dev" } = {}) {
metadata: { labels },
spec: {
containers: [{
name: "responses-bridge",
image: bridgeImage,
imagePullPolicy: "IfNotPresent",
command: ["node", "/app/cmd/hwlab-deepseek-responses-bridge/main.mjs"],
env: [
{ name: "PORT", value: "4000" },
{ name: "HWLAB_DEEPSEEK_BRIDGE_UPSTREAM", value: "http://127.0.0.1:4001" },
{ name: "HWLAB_DEEPSEEK_BRIDGE_MODEL", value: deepSeekProfileModel }
],
ports: [{ name: "http", containerPort: 4000 }],
readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 },
livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 }
}, {
name: "litellm",
image: deepSeekProxyImage,
imagePullPolicy: "IfNotPresent",
args: ["--config", "/etc/litellm/config.yaml", "--host", "0.0.0.0", "--port", "4000"],
args: ["--config", "/etc/litellm/config.yaml", "--host", "0.0.0.0", "--port", "4001"],
env: [{ name: "DEEPSEEK_API_KEY", valueFrom: { secretKeyRef: { name: "hwlab-code-agent-provider", key: "openai-api-key", optional: true } } }],
ports: [{ name: "http", containerPort: 4000 }],
readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 },
livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 },
ports: [{ name: "litellm-http", containerPort: 4001 }],
readinessProbe: { httpGet: { path: "/health/readiness", port: "litellm-http" }, initialDelaySeconds: 10, periodSeconds: 10 },
livenessProbe: { httpGet: { path: "/health/liveliness", port: "litellm-http" }, initialDelaySeconds: 20, periodSeconds: 20 },
volumeMounts: [{ name: "config", mountPath: "/etc/litellm", readOnly: true }]
}],
volumes: [{ name: "config", configMap: { name: "hwlab-deepseek-proxy-config" } }]
@@ -1698,7 +1712,7 @@ async function plannedFiles(args) {
putJson(`${runtimePath}/services.yaml`, transformListNamespace(services, namespace, profileLabels, annotations));
putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile));
putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, source, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile }));
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile }));
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, registryPrefix: args.registryPrefix }));
if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix }));
putJson(`${runtimePath}/g14-frpc.yaml`, g14FrpcManifest({ profile }));
}