Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
205
scripts/e2e/lib/openai-chat-tools/client.mjs
Normal file
205
scripts/e2e/lib/openai-chat-tools/client.mjs
Normal file
@@ -0,0 +1,205 @@
|
||||
// Gateway client for OpenAI chat tools E2E scenarios.
|
||||
import { readPositiveIntEnv, readTcpPortEnv } from "../env-limits.mjs";
|
||||
|
||||
const portText = process.env.PORT;
|
||||
const token = process.env.OPENCLAW_GATEWAY_TOKEN;
|
||||
const backendModel = process.env.MODEL_REF || "openai/gpt-5.4-mini";
|
||||
|
||||
const timeoutSeconds = readPositiveIntEnv("OPENCLAW_OPENAI_CHAT_TOOLS_TIMEOUT_SECONDS", 180);
|
||||
const maxBodyBytes = readPositiveIntEnv("OPENCLAW_OPENAI_CHAT_TOOLS_MAX_BODY_BYTES", 1048576);
|
||||
|
||||
if (!portText || !token) {
|
||||
throw new Error("missing PORT/OPENCLAW_GATEWAY_TOKEN");
|
||||
}
|
||||
const port = readTcpPortEnv("PORT", portText);
|
||||
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
||||
throw new Error(`invalid OPENCLAW_OPENAI_CHAT_TOOLS_TIMEOUT_SECONDS: ${timeoutSeconds}`);
|
||||
}
|
||||
if (!Number.isFinite(maxBodyBytes) || maxBodyBytes <= 0) {
|
||||
throw new Error(`invalid OPENCLAW_OPENAI_CHAT_TOOLS_MAX_BODY_BYTES: ${maxBodyBytes}`);
|
||||
}
|
||||
|
||||
function cancelReaderSoon(reader) {
|
||||
void Promise.resolve()
|
||||
.then(() => reader.cancel())
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
async function readResponseChunk(reader, timeoutPromise, markCanceled) {
|
||||
const readPromise = reader.read();
|
||||
if (!timeoutPromise) {
|
||||
return await readPromise;
|
||||
}
|
||||
|
||||
let waitingForRead = true;
|
||||
const timeoutReadPromise = timeoutPromise.catch((error) => {
|
||||
if (waitingForRead) {
|
||||
markCanceled();
|
||||
cancelReaderSoon(reader);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
try {
|
||||
return await Promise.race([readPromise, timeoutReadPromise]);
|
||||
} finally {
|
||||
waitingForRead = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function readBoundedResponseText(response, byteLimit, timeoutPromise) {
|
||||
const contentLength = response.headers?.get?.("content-length");
|
||||
if (contentLength && /^\d+$/u.test(contentLength)) {
|
||||
const parsedContentLength = Number(contentLength);
|
||||
if (!Number.isSafeInteger(parsedContentLength) || parsedContentLength > byteLimit) {
|
||||
await response.body?.cancel().catch(() => undefined);
|
||||
throw new Error(`chat completions response body exceeded ${byteLimit} bytes`);
|
||||
}
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
return "";
|
||||
}
|
||||
const chunks = [];
|
||||
let totalBytes = 0;
|
||||
let canceled = false;
|
||||
try {
|
||||
for (;;) {
|
||||
const { done, value } = await readResponseChunk(reader, timeoutPromise, () => {
|
||||
canceled = true;
|
||||
});
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
totalBytes += value.byteLength;
|
||||
if (totalBytes > byteLimit) {
|
||||
canceled = true;
|
||||
await reader.cancel();
|
||||
throw new Error(`chat completions response body exceeded ${byteLimit} bytes`);
|
||||
}
|
||||
chunks.push(Buffer.from(value));
|
||||
}
|
||||
} finally {
|
||||
if (!canceled) {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
return Buffer.concat(chunks, totalBytes).toString("utf8");
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutError = new Error(`chat completions request timed out after ${timeoutSeconds}s`);
|
||||
let timeout;
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
timeout = setTimeout(() => {
|
||||
controller.abort(timeoutError);
|
||||
reject(timeoutError);
|
||||
}, timeoutSeconds * 1000);
|
||||
timeout.unref?.();
|
||||
});
|
||||
const started = Date.now();
|
||||
let response;
|
||||
let text;
|
||||
try {
|
||||
response = await Promise.race([
|
||||
fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
"content-type": "application/json",
|
||||
"x-openclaw-model": backendModel,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "openclaw",
|
||||
stream: false,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"Use the get_weather tool exactly once for Paris, France. Return the tool call only.",
|
||||
},
|
||||
],
|
||||
tool_choice: "auto",
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_weather",
|
||||
description: "Return weather for a city.",
|
||||
strict: true,
|
||||
parameters: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
city: { type: "string", description: "City and country." },
|
||||
},
|
||||
required: ["city"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
signal: controller.signal,
|
||||
}),
|
||||
timeoutPromise,
|
||||
]);
|
||||
text = await readBoundedResponseText(response, maxBodyBytes, timeoutPromise);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = text ? JSON.parse(text) : {};
|
||||
} catch {
|
||||
throw new Error(`non-JSON response ${response.status}: ${text}`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`chat completions request failed ${response.status}: ${JSON.stringify(body)}`);
|
||||
}
|
||||
|
||||
const choice = body.choices?.[0];
|
||||
const toolCalls = choice?.message?.tool_calls;
|
||||
if (choice?.finish_reason !== "tool_calls") {
|
||||
throw new Error(`expected finish_reason tool_calls: ${JSON.stringify(body)}`);
|
||||
}
|
||||
const messageContent = choice?.message?.content;
|
||||
const hasVisibleContent =
|
||||
(typeof messageContent === "string" && messageContent.trim().length > 0) ||
|
||||
(Array.isArray(messageContent) && messageContent.length > 0) ||
|
||||
(messageContent !== undefined &&
|
||||
messageContent !== null &&
|
||||
typeof messageContent !== "string" &&
|
||||
!Array.isArray(messageContent));
|
||||
if (hasVisibleContent) {
|
||||
throw new Error(`expected tool call only response: ${JSON.stringify(choice.message)}`);
|
||||
}
|
||||
if (!Array.isArray(toolCalls) || toolCalls.length !== 1) {
|
||||
throw new Error(`expected exactly one tool call: ${JSON.stringify(body)}`);
|
||||
}
|
||||
const [toolCall] = toolCalls;
|
||||
if (toolCall?.type !== "function" || toolCall?.function?.name !== "get_weather") {
|
||||
throw new Error(`unexpected tool call: ${JSON.stringify(toolCall)}`);
|
||||
}
|
||||
|
||||
let args;
|
||||
try {
|
||||
args = JSON.parse(toolCall.function.arguments || "{}");
|
||||
} catch {
|
||||
throw new Error(`tool arguments were not valid JSON: ${toolCall.function.arguments}`);
|
||||
}
|
||||
if (typeof args.city !== "string" || !/paris/i.test(args.city)) {
|
||||
throw new Error(`expected Paris city argument: ${JSON.stringify(args)}`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
ok: true,
|
||||
elapsedMs: Date.now() - started,
|
||||
finishReason: choice.finish_reason,
|
||||
toolName: toolCall.function.name,
|
||||
args,
|
||||
}),
|
||||
);
|
||||
86
scripts/e2e/lib/openai-chat-tools/scenario.sh
Normal file
86
scripts/e2e/lib/openai-chat-tools/scenario.sh
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
export OPENCLAW_SKIP_CHANNELS=1
|
||||
export OPENCLAW_SKIP_GMAIL_WATCHER=1
|
||||
export OPENCLAW_SKIP_CRON=1
|
||||
export OPENCLAW_SKIP_CANVAS_HOST=1
|
||||
export OPENCLAW_SKIP_BROWSER_CONTROL_SERVER=1
|
||||
export OPENCLAW_SKIP_ACPX_RUNTIME=1
|
||||
export OPENCLAW_SKIP_ACPX_RUNTIME_PROBE=1
|
||||
export OPENCLAW_AGENT_HARNESS_FALLBACK=none
|
||||
|
||||
for profile_path in "$HOME/.profile" /home/appuser/.profile; do
|
||||
if [ -f "$profile_path" ] && [ -r "$profile_path" ]; then
|
||||
set +e +u
|
||||
# shellcheck disable=SC1090
|
||||
source "$profile_path"
|
||||
set -euo pipefail
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "${OPENAI_API_KEY:-}" ]; then
|
||||
echo "ERROR: OPENAI_API_KEY was not available after sourcing ~/.profile." >&2
|
||||
exit 1
|
||||
fi
|
||||
export OPENAI_API_KEY
|
||||
if [ -n "${OPENAI_BASE_URL:-}" ]; then
|
||||
export OPENAI_BASE_URL
|
||||
fi
|
||||
|
||||
PORT="${PORT:?missing PORT}"
|
||||
TOKEN="${OPENCLAW_GATEWAY_TOKEN:?missing OPENCLAW_GATEWAY_TOKEN}"
|
||||
MODEL_REF="${OPENCLAW_OPENAI_CHAT_TOOLS_MODEL:?missing OPENCLAW_OPENAI_CHAT_TOOLS_MODEL}"
|
||||
GATEWAY_LOG="/tmp/openclaw-openai-chat-tools-gateway.log"
|
||||
CLIENT_LOG="/tmp/openclaw-openai-chat-tools-client.log"
|
||||
gateway_pid=""
|
||||
|
||||
cleanup() {
|
||||
openclaw_e2e_stop_process "$gateway_pid"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
dump_debug_logs() {
|
||||
local status="$1"
|
||||
echo "OpenAI Chat Completions tools Docker E2E failed with exit code $status" >&2
|
||||
openclaw_e2e_dump_logs "$GATEWAY_LOG" "$CLIENT_LOG"
|
||||
if [ -f "$OPENCLAW_CONFIG_PATH" ]; then
|
||||
echo "--- $OPENCLAW_CONFIG_PATH keys ---" >&2
|
||||
node -e "const fs=require('fs'); const cfg=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); console.error(JSON.stringify({model:cfg.agents?.defaults?.model, tools:cfg.tools, provider:cfg.models?.providers?.openai && {api:cfg.models.providers.openai.api, baseUrl:cfg.models.providers.openai.baseUrl, agentRuntime:cfg.models.providers.openai.agentRuntime}}, null, 2));" "$OPENCLAW_CONFIG_PATH" || true
|
||||
fi
|
||||
}
|
||||
trap 'status=$?; dump_debug_logs "$status"; exit "$status"' ERR
|
||||
|
||||
entry="$(openclaw_e2e_resolve_entrypoint)"
|
||||
mkdir -p "$OPENCLAW_STATE_DIR" "$OPENCLAW_TEST_WORKSPACE_DIR"
|
||||
|
||||
node scripts/e2e/lib/openai-chat-tools/write-config.mjs
|
||||
|
||||
gateway_pid="$(openclaw_e2e_start_gateway "$entry" "$PORT" "$GATEWAY_LOG")"
|
||||
for _ in $(seq 1 360); do
|
||||
if ! kill -0 "$gateway_pid" 2>/dev/null; then
|
||||
echo "gateway exited before listening" >&2
|
||||
exit 1
|
||||
fi
|
||||
if node "$entry" gateway health \
|
||||
--url "ws://127.0.0.1:$PORT" \
|
||||
--token "$TOKEN" \
|
||||
--timeout 120000 \
|
||||
--json >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
node "$entry" gateway health \
|
||||
--url "ws://127.0.0.1:$PORT" \
|
||||
--token "$TOKEN" \
|
||||
--timeout 120000 \
|
||||
--json >/dev/null
|
||||
|
||||
PORT="$PORT" OPENCLAW_GATEWAY_TOKEN="$TOKEN" MODEL_REF="$MODEL_REF" \
|
||||
node scripts/e2e/lib/openai-chat-tools/client.mjs >"$CLIENT_LOG" 2>&1
|
||||
|
||||
openclaw_e2e_print_log "$CLIENT_LOG"
|
||||
echo "OpenAI Chat Completions tools Docker E2E passed"
|
||||
90
scripts/e2e/lib/openai-chat-tools/write-config.mjs
Normal file
90
scripts/e2e/lib/openai-chat-tools/write-config.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Config writer for OpenAI chat tools E2E scenarios.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { readPositiveIntEnv, readTcpPortEnv } from "../env-limits.mjs";
|
||||
|
||||
function requireEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`missing ${name}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const configPath = requireEnv("OPENCLAW_CONFIG_PATH");
|
||||
const stateDir = requireEnv("OPENCLAW_STATE_DIR");
|
||||
const workspaceDir = requireEnv("OPENCLAW_TEST_WORKSPACE_DIR");
|
||||
const modelRef = requireEnv("OPENCLAW_OPENAI_CHAT_TOOLS_MODEL");
|
||||
const token = requireEnv("OPENCLAW_GATEWAY_TOKEN");
|
||||
const timeoutSeconds = readPositiveIntEnv("OPENCLAW_OPENAI_CHAT_TOOLS_TIMEOUT_SECONDS", 180);
|
||||
const gatewayPort = readTcpPortEnv("PORT", 18789);
|
||||
const [providerId, modelId] = modelRef.split("/");
|
||||
if (providerId !== "openai" || !modelId) {
|
||||
throw new Error(`OPENCLAW_OPENAI_CHAT_TOOLS_MODEL must be openai/*, got ${modelRef}`);
|
||||
}
|
||||
|
||||
const config = {
|
||||
gateway: {
|
||||
port: gatewayPort,
|
||||
bind: "loopback",
|
||||
auth: { mode: "token", token },
|
||||
controlUi: { enabled: false },
|
||||
http: {
|
||||
endpoints: {
|
||||
chatCompletions: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
openai: {
|
||||
api: "openai-responses",
|
||||
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
|
||||
baseUrl: (process.env.OPENAI_BASE_URL || "https://api.openai.com/v1").trim(),
|
||||
agentRuntime: { id: "openclaw" },
|
||||
timeoutSeconds,
|
||||
models: [
|
||||
{
|
||||
id: modelId,
|
||||
name: modelId,
|
||||
api: "openai-responses",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
contextTokens: 64000,
|
||||
maxTokens: 512,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: modelRef, fallbacks: [] },
|
||||
models: {
|
||||
[modelRef]: {
|
||||
agentRuntime: { id: "openclaw" },
|
||||
params: { transport: "sse", openaiWsWarmup: false },
|
||||
},
|
||||
},
|
||||
workspace: workspaceDir,
|
||||
skipBootstrap: true,
|
||||
timeoutSeconds,
|
||||
contextTokens: 64000,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
enabled: true,
|
||||
allow: ["openai"],
|
||||
entries: { openai: { enabled: true } },
|
||||
},
|
||||
skills: { allowBundled: [] },
|
||||
tools: { allow: ["get_weather"] },
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
||||
fs.mkdirSync(workspaceDir, { recursive: true });
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
||||
fs.mkdirSync(path.join(stateDir, "logs"), { recursive: true });
|
||||
Reference in New Issue
Block a user