Files
openclaw/scripts/e2e/lib/openai-web-search-minimal/client.mjs
Peter Steinberger 964c8c84c1 refactor: consolidate coercion ownership (#122299)
* refactor: consolidate coercion ownership

Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.

The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.

* fix: guard integer option ownership

Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.

* fix: keep integer helpers on numeric facade

Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.

* fix: point numeric coercion to number runtime

Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
2026-08-11 17:14:53 -07:00

187 lines
5.3 KiB
JavaScript

// Client script for minimal OpenAI web-search E2E scenarios.
import { readdirSync } from "node:fs";
import { pathToFileURL } from "node:url";
import { readTcpPortEnv } from "../env-limits.mjs";
async function loadCallGateway() {
const candidates = readdirSync("/app/dist")
.filter((name) => /^call(?:\.runtime)?-[A-Za-z0-9_-]+\.js$/.test(name))
.toSorted();
for (const name of candidates) {
const mod = await import(pathToFileURL(`/app/dist/${name}`).href);
if (typeof mod.callGateway === "function") {
return mod.callGateway;
}
}
throw new Error(`unable to find callGateway export in /app/dist (${candidates.join(", ")})`);
}
const DEFAULT_RAW_SCHEMA_ERROR =
"400 The following tools cannot be used with reasoning.effort 'minimal': web_search.";
const DEFAULT_GATEWAY_SCHEMA_ERROR = "provider rejected the request schema or tool payload";
const SUCCESS_MARKER = "OPENCLAW_SCHEMA_E2E_OK";
function readExpectedRawSchemaError() {
return process.env.RAW_SCHEMA_ERROR?.trim() || DEFAULT_RAW_SCHEMA_ERROR;
}
function resolveGatewayPort(env = process.env) {
const portText = env.PORT;
if (!portText) {
throw new Error("missing PORT");
}
return readTcpPortEnv("PORT", portText, env);
}
async function gatewayAgent(params) {
const token = process.env.OPENCLAW_GATEWAY_TOKEN;
if (!token) {
throw new Error("missing PORT/OPENCLAW_GATEWAY_TOKEN");
}
const port = resolveGatewayPort();
try {
const callGateway = await loadCallGateway();
return {
ok: true,
value: await callGateway({
url: `ws://127.0.0.1:${port}`,
token,
method: "agent",
params,
expectFinal: true,
timeoutMs: 240_000,
clientName: "gateway-client",
mode: "backend",
scopes: ["operator.write"],
deviceIdentity: null,
}),
};
} catch (error) {
const combined = String(error);
return { ok: false, error: new Error(combined) };
}
}
function stringifyError(value) {
return value instanceof Error ? value.message || String(value) : String(value);
}
function validateRejectResult(result, expectedRawSchemaError = readExpectedRawSchemaError()) {
if (result.ok) {
throw new Error(`reject mode unexpectedly completed: ${JSON.stringify(result.value)}`);
}
const errorText = stringifyError(result.error);
if (
!errorText.includes(expectedRawSchemaError) &&
!errorText.includes(DEFAULT_GATEWAY_SCHEMA_ERROR)
) {
throw new Error(
`reject mode failed for an unexpected reason; expected ${JSON.stringify(
expectedRawSchemaError,
)} or ${JSON.stringify(DEFAULT_GATEWAY_SCHEMA_ERROR)} in ${JSON.stringify(errorText)}`,
);
}
return errorText;
}
function pushStringText(texts, value) {
if (typeof value === "string" && value.trim().length > 0) {
texts.push(value);
}
}
function pushContentText(texts, content) {
if (typeof content === "string") {
pushStringText(texts, content);
return;
}
if (!Array.isArray(content)) {
return;
}
for (const item of content) {
if (typeof item === "string") {
pushStringText(texts, item);
} else if (item && typeof item === "object") {
pushStringText(texts, item.text);
}
}
}
function extractSuccessReplyTexts(value) {
const texts = [];
pushSuccessReplyTexts(texts, value);
pushSuccessReplyTexts(texts, value?.result);
return texts;
}
function pushSuccessReplyTexts(texts, value) {
pushStringText(texts, value?.finalAssistantVisibleText);
pushStringText(texts, value?.meta?.finalAssistantVisibleText);
pushContentText(texts, value?.message?.content);
for (const payload of Array.isArray(value?.payloads) ? value.payloads : []) {
if (payload?.isError === true) {
continue;
}
pushStringText(texts, payload?.text);
pushContentText(texts, payload?.content);
}
}
function validateSuccessResult(result, marker = SUCCESS_MARKER) {
if (result.value?.status !== "ok") {
throw new Error(`agent run did not complete successfully: ${JSON.stringify(result.value)}`);
}
const replyTexts = extractSuccessReplyTexts(result.value);
if (!replyTexts.some((text) => text.includes(marker))) {
throw new Error(
`agent run completed without success marker ${JSON.stringify(marker)} in final reply: ${JSON.stringify(
result.value,
)}`,
);
}
}
async function main() {
const mode = process.argv[2];
const sessionKey = `agent:main:openai-web-search-minimal:${mode}`;
const message = mode === "reject" ? "FORCE_SCHEMA_REJECT" : `Return exactly ${SUCCESS_MARKER}.`;
const id = mode === "reject" ? "schema-reject" : "schema-success";
const result = await gatewayAgent({
sessionKey,
message,
thinking: "minimal",
deliver: false,
timeout: 180,
idempotencyKey: id,
});
if (mode === "reject") {
console.error(validateRejectResult(result));
return;
}
if (!result.ok) {
throw /** @type {Error} */ (result.error);
}
validateSuccessResult(result);
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
try {
await main();
} catch (error) {
console.error(error instanceof Error ? error.stack || error.message : String(error));
process.exit(1);
}
}
export const testing = {
DEFAULT_GATEWAY_SCHEMA_ERROR,
DEFAULT_RAW_SCHEMA_ERROR,
SUCCESS_MARKER,
resolveGatewayPort,
validateSuccessResult,
validateRejectResult,
};