fix: codify #164 runtime credential and db contracts
This commit is contained in:
@@ -6,11 +6,17 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import {
|
||||
DEV_CODE_AGENT_PROVIDER_CONTRACT,
|
||||
codeAgentSecretRefPlaceholder,
|
||||
inspectCodeAgentProviderEnv
|
||||
} from "./code-agent-contract.mjs";
|
||||
|
||||
const DEFAULT_CODE_AGENT_TIMEOUT_MS = 120000;
|
||||
const DEFAULT_CODEX_COMMAND = "codex";
|
||||
const DEFAULT_MODEL = "gpt-5.2";
|
||||
const DEFAULT_MODEL = DEV_CODE_AGENT_PROVIDER_CONTRACT.model;
|
||||
const DEFAULT_PROJECT_ID = "prj_hwlab-cloud-workbench";
|
||||
const CODE_AGENT_PROVIDER_SECRET_REF = "hwlab-code-agent-provider/openai-api-key";
|
||||
const CODE_AGENT_PROVIDER_SECRET_REF = codeAgentSecretRefPlaceholder().replace("secretRef:", "");
|
||||
const CODE_AGENT_SYSTEM_PROMPT = [
|
||||
"你是 HWLAB 云工作台的 Code Agent。",
|
||||
"请用中文直接回答用户的工作台问题。",
|
||||
@@ -127,13 +133,18 @@ export function validateCodeAgentChatSchema(payload) {
|
||||
|
||||
export function describeCodeAgentAvailability(env = process.env, options = {}) {
|
||||
const providerPlan = resolveProviderPlan(env, options);
|
||||
const missingEnv = [];
|
||||
const providerContract = inspectCodeAgentProviderEnv(env);
|
||||
const missingEnv = providerPlan.mode === "openai"
|
||||
? providerContract.missingEnv
|
||||
: [];
|
||||
|
||||
if (!env.OPENAI_API_KEY) {
|
||||
if (providerPlan.mode !== "openai" && !env.OPENAI_API_KEY) {
|
||||
missingEnv.push("OPENAI_API_KEY");
|
||||
}
|
||||
|
||||
const blocked = missingEnv.length > 0;
|
||||
const blocked = providerPlan.mode === "openai"
|
||||
? !providerContract.ready
|
||||
: missingEnv.length > 0;
|
||||
return {
|
||||
endpoint: "POST /v1/agent/chat",
|
||||
provider: providerPlan.provider,
|
||||
@@ -157,23 +168,15 @@ export function describeCodeAgentAvailability(env = process.env, options = {}) {
|
||||
"availability.status"
|
||||
],
|
||||
status: blocked ? "blocked" : "available",
|
||||
blocker: blocked ? "凭证缺口" : null,
|
||||
blocker: blocked ? (providerPlan.mode === "openai" ? providerContract.blocker : "凭证缺口") : null,
|
||||
reason: blocked ? "provider_unavailable" : null,
|
||||
summary: blocked
|
||||
? `真实后端已接入,但当前 DEV provider Secret ${CODE_AGENT_PROVIDER_SECRET_REF} 未注入,因此不能返回真实回复。`
|
||||
: "真实后端已接入,发送会走真实 /v1/agent/chat;只有实际 completed 回复才标记 DEV-LIVE。",
|
||||
? `真实后端已接入,但当前 DEV provider Secret ${CODE_AGENT_PROVIDER_SECRET_REF} 或 DEV egress/base-url contract 未满足,因此不能返回真实回复。`
|
||||
: "真实后端已接入,发送会走 DEV egress/proxy 后的真实 /v1/agent/chat;只有实际 completed 回复才标记 DEV-LIVE。",
|
||||
missingEnv,
|
||||
secretRefs: blocked
|
||||
? [
|
||||
{
|
||||
env: "OPENAI_API_KEY",
|
||||
secretName: "hwlab-code-agent-provider",
|
||||
secretKey: "openai-api-key",
|
||||
present: false,
|
||||
redacted: true
|
||||
}
|
||||
]
|
||||
: [],
|
||||
secretRefs: blocked ? providerContract.secretRefs : [],
|
||||
egress: providerContract.egress,
|
||||
safety: providerContract.safety,
|
||||
ready: !blocked
|
||||
};
|
||||
}
|
||||
@@ -233,16 +236,18 @@ async function callConfiguredProvider({
|
||||
}
|
||||
|
||||
async function callOpenAiResponses({ providerPlan, message, conversationId, traceId, timeoutMs, env }) {
|
||||
if (!env.OPENAI_API_KEY) {
|
||||
throw providerUnavailable("OPENAI_API_KEY is not configured for openai-responses", {
|
||||
missingEnv: ["OPENAI_API_KEY"],
|
||||
const providerContract = inspectCodeAgentProviderEnv(env);
|
||||
if (!providerContract.ready) {
|
||||
throw providerUnavailable(providerContract.blocker, {
|
||||
missingEnv: providerContract.missingEnv,
|
||||
provider: providerPlan.provider,
|
||||
model: providerPlan.model,
|
||||
backend: providerPlan.backend
|
||||
backend: providerPlan.backend,
|
||||
egress: providerContract.egress
|
||||
});
|
||||
}
|
||||
|
||||
const endpoint = env.HWLAB_CODE_AGENT_OPENAI_BASE_URL || "https://api.openai.com/v1/responses";
|
||||
const endpoint = env.HWLAB_CODE_AGENT_OPENAI_BASE_URL;
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), effectiveTimeout(timeoutMs));
|
||||
let response;
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { ENVIRONMENT_DEV } from "../protocol/index.mjs";
|
||||
|
||||
export const DEV_CODE_AGENT_PROVIDER_CONTRACT = Object.freeze({
|
||||
contractVersion: "v1",
|
||||
environment: ENVIRONMENT_DEV,
|
||||
provider: "openai",
|
||||
runtimeProvider: "openai-responses",
|
||||
backend: "hwlab-cloud-api/openai-responses",
|
||||
model: "gpt-5.5",
|
||||
requiredEnv: Object.freeze([
|
||||
"OPENAI_API_KEY",
|
||||
"HWLAB_CODE_AGENT_OPENAI_BASE_URL"
|
||||
]),
|
||||
secretRefs: Object.freeze([
|
||||
Object.freeze({
|
||||
env: "OPENAI_API_KEY",
|
||||
secretName: "hwlab-code-agent-provider",
|
||||
secretKey: "openai-api-key"
|
||||
})
|
||||
]),
|
||||
egress: Object.freeze({
|
||||
env: "HWLAB_CODE_AGENT_OPENAI_BASE_URL",
|
||||
target: "dev-egress-proxy",
|
||||
defaultBaseUrl: "http://172.26.26.227:17680/v1/responses",
|
||||
forbiddenDirectBaseUrl: "https://api.openai.com/v1/responses",
|
||||
reason: "DEV cloud-api pods must use the D601 egress/proxy path for OpenAI Responses."
|
||||
}),
|
||||
forbidden: Object.freeze({
|
||||
prodAllowed: false,
|
||||
secretPlaintextAllowed: false,
|
||||
directPublicOpenAiFromDevPod: false
|
||||
})
|
||||
});
|
||||
|
||||
export function codeAgentSecretRefPlaceholder() {
|
||||
const ref = DEV_CODE_AGENT_PROVIDER_CONTRACT.secretRefs[0];
|
||||
return `secretRef:${ref.secretName}/${ref.secretKey}`;
|
||||
}
|
||||
|
||||
export function buildCodeAgentProviderManifestPlaceholder() {
|
||||
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
|
||||
return {
|
||||
contractVersion: contract.contractVersion,
|
||||
environment: contract.environment,
|
||||
provider: contract.provider,
|
||||
runtimeProvider: contract.runtimeProvider,
|
||||
backend: contract.backend,
|
||||
model: contract.model,
|
||||
requiredEnv: [...contract.requiredEnv],
|
||||
secretRefs: contract.secretRefs.map((item) => ({ ...item })),
|
||||
egress: { ...contract.egress },
|
||||
fixtureEvidence: {
|
||||
providerConnected: false,
|
||||
summary: "This manifest contract records provider env names, Secret reference names, and DEV egress policy only. It is not live provider evidence."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function inspectCodeAgentProviderEnv(env = {}) {
|
||||
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
|
||||
const missingEnv = contract.requiredEnv.filter((name) => !hasEnvValue(env, name));
|
||||
const secretRef = contract.secretRefs[0];
|
||||
const baseUrl = firstNonEmpty(env.HWLAB_CODE_AGENT_OPENAI_BASE_URL);
|
||||
const directPublicOpenAi =
|
||||
baseUrl === contract.egress.forbiddenDirectBaseUrl ||
|
||||
/^https:\/\/api\.openai\.com\/v1\/responses\/?$/u.test(baseUrl);
|
||||
const egressReady = Boolean(baseUrl) && !directPublicOpenAi;
|
||||
const ready = missingEnv.length === 0 && egressReady;
|
||||
|
||||
return {
|
||||
contractVersion: contract.contractVersion,
|
||||
environment: contract.environment,
|
||||
provider: contract.runtimeProvider,
|
||||
model: firstNonEmpty(env.HWLAB_CODE_AGENT_MODEL, contract.model),
|
||||
backend: contract.backend,
|
||||
status: ready ? "available" : "blocked",
|
||||
ready,
|
||||
requiredEnv: contract.requiredEnv.map((name) => ({
|
||||
name,
|
||||
present: hasEnvValue(env, name),
|
||||
redacted: name === secretRef.env,
|
||||
source: name === secretRef.env ? "k8s-secret-ref" : "runtime-env"
|
||||
})),
|
||||
missingEnv,
|
||||
secretRefs: [
|
||||
{
|
||||
env: secretRef.env,
|
||||
secretName: secretRef.secretName,
|
||||
secretKey: secretRef.secretKey,
|
||||
present: hasEnvValue(env, secretRef.env),
|
||||
envInjected: hasEnvValue(env, secretRef.env),
|
||||
secretPresent: "not_observed_by_runtime",
|
||||
secretKeyPresent: "not_observed_by_runtime",
|
||||
redacted: true
|
||||
}
|
||||
],
|
||||
egress: {
|
||||
env: contract.egress.env,
|
||||
present: hasEnvValue(env, contract.egress.env),
|
||||
target: contract.egress.target,
|
||||
baseUrlConfigured: Boolean(baseUrl),
|
||||
directPublicOpenAi,
|
||||
ready: egressReady,
|
||||
valueRedacted: true
|
||||
},
|
||||
safety: {
|
||||
devOnly: true,
|
||||
prodAllowed: contract.forbidden.prodAllowed,
|
||||
secretsRead: false,
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true,
|
||||
directPublicOpenAiFromDevPod: directPublicOpenAi
|
||||
},
|
||||
blocker: ready ? null : codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl })
|
||||
};
|
||||
}
|
||||
|
||||
function codeAgentProviderBlocker({ missingEnv, directPublicOpenAi, baseUrl }) {
|
||||
if (directPublicOpenAi) {
|
||||
return "DEV Code Agent OpenAI base URL points at public api.openai.com instead of the DEV egress/proxy path";
|
||||
}
|
||||
if (!baseUrl) {
|
||||
return "DEV Code Agent OpenAI base URL is missing; provider calls must use the DEV egress/proxy path";
|
||||
}
|
||||
return `Code Agent provider runtime config is missing ${missingEnv.join(", ")}`;
|
||||
}
|
||||
|
||||
function hasEnvValue(env, name) {
|
||||
return typeof env?.[name] === "string" && env[name].trim().length > 0;
|
||||
}
|
||||
|
||||
function firstNonEmpty(...values) {
|
||||
for (const value of values) {
|
||||
if (typeof value === "string" && value.trim()) return value.trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -23,6 +23,12 @@ export const DEV_DB_ENV_CONTRACT = Object.freeze({
|
||||
port: 5432,
|
||||
portName: "postgres"
|
||||
}),
|
||||
readinessLayers: Object.freeze([
|
||||
"dns",
|
||||
"tcp",
|
||||
"auth",
|
||||
"schema"
|
||||
]),
|
||||
nonSecretDefaults: Object.freeze({
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require",
|
||||
HWLAB_CLOUD_DB_SERVICE_NAME: "cloud-api-db",
|
||||
@@ -33,7 +39,11 @@ export const DEV_DB_ENV_CONTRACT = Object.freeze({
|
||||
forbidden: Object.freeze({
|
||||
prodAllowed: false,
|
||||
secretPlaintextAllowed: false,
|
||||
liveDbEvidenceFromFixtures: false
|
||||
liveDbEvidenceFromFixtures: false,
|
||||
invalidRuntimeHosts: Object.freeze([
|
||||
".invalid",
|
||||
"hwlab-dev-db.invalid"
|
||||
])
|
||||
})
|
||||
});
|
||||
|
||||
@@ -96,6 +106,7 @@ export function buildDbHealthContract(env = process.env) {
|
||||
liveDbEvidence: connected,
|
||||
liveDbConnectedEvidence: connected
|
||||
},
|
||||
readinessLayers: buildReadinessLayers(connection),
|
||||
blocker: configReady ? "live DB connection has not been attempted by this health contract" : missingEnvBlocker(missingEnv),
|
||||
evidence: configReady ? "env_presence_only_no_live_db" : "env_contract_blocked"
|
||||
};
|
||||
@@ -124,6 +135,8 @@ export function buildDbEnvManifestPlaceholder() {
|
||||
requiredEnv: [...DEV_DB_ENV_CONTRACT.requiredEnv],
|
||||
secretRefs: DEV_DB_ENV_CONTRACT.secretRefs.map((item) => ({ ...item })),
|
||||
dns: { ...DEV_DB_ENV_CONTRACT.dns },
|
||||
readinessLayers: [...DEV_DB_ENV_CONTRACT.readinessLayers],
|
||||
forbiddenRuntimeHosts: [...DEV_DB_ENV_CONTRACT.forbidden.invalidRuntimeHosts],
|
||||
nonSecretDefaults: { ...DEV_DB_ENV_CONTRACT.nonSecretDefaults },
|
||||
fixtureEvidence: {
|
||||
liveDbEvidence: false,
|
||||
@@ -173,6 +186,7 @@ export function summarizeDbContract(db = buildDbHealthContract()) {
|
||||
),
|
||||
connection: summarizeConnection(db.connection),
|
||||
redaction: summarizeRedaction(db.redaction),
|
||||
readinessLayers: buildReadinessLayers(db.connection),
|
||||
blocker: db.blocker ?? null,
|
||||
fixtureEvidence: false
|
||||
};
|
||||
@@ -257,6 +271,14 @@ export function parseDbUrlContract(rawUrl) {
|
||||
errorCode: "MISSING_HOST"
|
||||
};
|
||||
}
|
||||
if (isForbiddenRuntimeHost(url.hostname)) {
|
||||
return {
|
||||
ok: false,
|
||||
result: "forbidden_runtime_host",
|
||||
classification: "db_url_forbidden_invalid_host",
|
||||
errorCode: "FORBIDDEN_INVALID_HOST"
|
||||
};
|
||||
}
|
||||
const port = url.port ? Number.parseInt(url.port, 10) : 5432;
|
||||
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
||||
return {
|
||||
@@ -382,6 +404,7 @@ function applyConnectionResult(base, connection) {
|
||||
liveDbEvidence: liveConnected,
|
||||
liveDbConnectedEvidence: liveConnected
|
||||
},
|
||||
readinessLayers: buildReadinessLayers(connection),
|
||||
blocker: liveConnected ? null : blockerForConnection(connection),
|
||||
evidence: liveConnected ? "live_db_tcp_connection_ready" : "live_db_tcp_connection_blocked"
|
||||
};
|
||||
@@ -391,6 +414,9 @@ function blockerForConnection(connection) {
|
||||
if (connection.result === "invalid_url") {
|
||||
return "DB URL is injected but cannot be parsed as a supported postgres URL";
|
||||
}
|
||||
if (connection.result === "forbidden_runtime_host") {
|
||||
return "DB URL is injected but points at a forbidden .invalid DEV runtime host";
|
||||
}
|
||||
if (connection.result === "unsupported_protocol") {
|
||||
return "DB URL is injected but does not use a supported postgres protocol";
|
||||
}
|
||||
@@ -421,6 +447,38 @@ function normalizeTimeoutMs(value) {
|
||||
return Math.min(parsed, 5000);
|
||||
}
|
||||
|
||||
function isForbiddenRuntimeHost(hostname) {
|
||||
const normalized = String(hostname ?? "").toLowerCase();
|
||||
return DEV_DB_ENV_CONTRACT.forbidden.invalidRuntimeHosts.some((host) =>
|
||||
host.startsWith(".") ? normalized.endsWith(host) : normalized === host
|
||||
);
|
||||
}
|
||||
|
||||
function buildReadinessLayers(connection = {}) {
|
||||
const result = connection?.result ?? "unknown";
|
||||
const connected = result === "connected";
|
||||
const dnsReady = connected || ["refused", "timeout", "network_unreachable", "error"].includes(result);
|
||||
const tcpReady = connected;
|
||||
return {
|
||||
dns: {
|
||||
status: dnsReady ? "pass" : "not_proven",
|
||||
result
|
||||
},
|
||||
tcp: {
|
||||
status: tcpReady ? "pass" : "not_proven",
|
||||
result
|
||||
},
|
||||
auth: {
|
||||
status: "not_proven",
|
||||
result: connected ? "tcp_connected_auth_not_checked" : result
|
||||
},
|
||||
schema: {
|
||||
status: "not_proven",
|
||||
result: connected ? "tcp_connected_schema_not_checked" : result
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeConnection(connection) {
|
||||
if (!connection) {
|
||||
return {
|
||||
|
||||
@@ -49,6 +49,8 @@ test("cloud api exposes /health, /health/live, and /live probes", async () => {
|
||||
assert.equal(healthPayload.codeAgent.secretRefs[0].secretName, "hwlab-code-agent-provider");
|
||||
assert.equal(healthPayload.codeAgent.secretRefs[0].secretKey, "openai-api-key");
|
||||
assert.equal(healthPayload.codeAgent.secretRefs[0].redacted, true);
|
||||
assert.equal(healthPayload.codeAgent.egress.directPublicOpenAi, false);
|
||||
assert.equal(healthPayload.codeAgent.safety.secretMaterialRead, false);
|
||||
assert.equal(JSON.stringify(healthPayload.codeAgent).includes("sk-"), false);
|
||||
assert.deepEqual(healthPayload.db.missingEnv, ["HWLAB_CLOUD_DB_URL", "HWLAB_CLOUD_DB_SSL_MODE"]);
|
||||
assert.equal(healthPayload.db.secretRefs[0].secretName, "hwlab-cloud-api-dev-db");
|
||||
@@ -152,7 +154,7 @@ test("cloud api health does not treat DB env presence-only as live readiness", a
|
||||
const originalProbeDisabled = process.env.HWLAB_CLOUD_DB_PROBE_DISABLED;
|
||||
process.env.HWLAB_CLOUD_DB_URL = "postgres://user:password@db.example.invalid:5432/hwlab";
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = "require";
|
||||
process.env.HWLAB_CLOUD_DB_PROBE_DISABLED = "1";
|
||||
delete process.env.HWLAB_CLOUD_DB_PROBE_DISABLED;
|
||||
|
||||
const server = createCloudApiServer();
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
@@ -168,9 +170,12 @@ test("cloud api health does not treat DB env presence-only as live readiness", a
|
||||
assert.equal(payload.db.liveDbEvidence, false);
|
||||
assert.equal(payload.db.safety.liveDbEvidence, false);
|
||||
assert.equal(payload.db.ready, false);
|
||||
assert.equal(payload.db.connectionAttempted, false);
|
||||
assert.equal(payload.db.connectionResult, "not_attempted");
|
||||
assert.equal(payload.db.evidence, "env_presence_only_probe_disabled");
|
||||
assert.equal(payload.db.connectionAttempted, true);
|
||||
assert.equal(payload.db.connectionResult, "forbidden_runtime_host");
|
||||
assert.equal(payload.db.evidence, "live_db_tcp_connection_blocked");
|
||||
assert.equal(payload.db.connection.networkAttempted, false);
|
||||
assert.equal(payload.db.connection.classification, "db_url_forbidden_invalid_host");
|
||||
assert.equal(payload.db.readinessLayers.dns.status, "not_proven");
|
||||
assert.equal(JSON.stringify(payload.db).includes("password"), false);
|
||||
assert.equal(JSON.stringify(payload.db).includes("db.example.invalid"), false);
|
||||
} finally {
|
||||
@@ -216,14 +221,17 @@ test("cloud api /v1 describes Code Agent provider blocker without leaking secret
|
||||
assert.equal(payload.codeAgent.backend, "hwlab-cloud-api/openai-responses");
|
||||
assert.equal(payload.codeAgent.mode, "openai");
|
||||
assert.equal(payload.codeAgent.status, "blocked");
|
||||
assert.equal(payload.codeAgent.blocker, "凭证缺口");
|
||||
assert.match(payload.codeAgent.blocker, /DEV Code Agent OpenAI base URL is missing/u);
|
||||
assert.equal(payload.codeAgent.reason, "provider_unavailable");
|
||||
assert.deepEqual(payload.codeAgent.missingEnv, ["OPENAI_API_KEY"]);
|
||||
assert.deepEqual(payload.codeAgent.missingEnv, ["OPENAI_API_KEY", "HWLAB_CODE_AGENT_OPENAI_BASE_URL"]);
|
||||
assert.equal(payload.codeAgent.secretRefs[0].env, "OPENAI_API_KEY");
|
||||
assert.equal(payload.codeAgent.secretRefs[0].secretName, "hwlab-code-agent-provider");
|
||||
assert.equal(payload.codeAgent.secretRefs[0].secretKey, "openai-api-key");
|
||||
assert.equal(payload.codeAgent.secretRefs[0].redacted, true);
|
||||
assert.equal(payload.codeAgent.ready, false);
|
||||
assert.equal(payload.codeAgent.egress.present, false);
|
||||
assert.equal(payload.codeAgent.egress.valueRedacted, true);
|
||||
assert.equal(payload.codeAgent.safety.secretMaterialRead, false);
|
||||
assert.match(payload.codeAgent.summary, /真实后端已接入/u);
|
||||
assert.match(payload.codeAgent.summary, /hwlab-code-agent-provider\/openai-api-key/u);
|
||||
assert.equal(JSON.stringify(payload).includes("sk-"), false);
|
||||
@@ -234,6 +242,34 @@ test("cloud api /v1 describes Code Agent provider blocker without leaking secret
|
||||
}
|
||||
});
|
||||
|
||||
test("cloud api /v1 describes Code Agent egress blocker without leaking API key", async () => {
|
||||
const server = createCloudApiServer({
|
||||
env: {
|
||||
OPENAI_API_KEY: "test-openai-key-material",
|
||||
HWLAB_CODE_AGENT_PROVIDER: "openai",
|
||||
HWLAB_CODE_AGENT_MODEL: "gpt-test",
|
||||
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "https://api.openai.com/v1/responses"
|
||||
}
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
|
||||
try {
|
||||
const { port } = server.address();
|
||||
const response = await fetch(`http://127.0.0.1:${port}/v1`);
|
||||
assert.equal(response.status, 200);
|
||||
const payload = await response.json();
|
||||
assert.equal(payload.codeAgent.status, "blocked");
|
||||
assert.equal(payload.codeAgent.egress.directPublicOpenAi, true);
|
||||
assert.match(payload.codeAgent.blocker, /public api\.openai\.com/u);
|
||||
assert.equal(payload.codeAgent.safety.secretMaterialRead, false);
|
||||
assert.equal(JSON.stringify(payload).includes("test-openai-key-material"), false);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("cloud api /v1/agent/chat returns structured completed Code Agent payload", async () => {
|
||||
const server = createCloudApiServer({
|
||||
callCodeAgentProvider: async ({ providerPlan, message, traceId }) => ({
|
||||
|
||||
Reference in New Issue
Block a user