mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix: guard plugin HTTP calls in CI
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const log = createSubsystemLogger("kilocode-models");
|
||||
@@ -135,49 +136,56 @@ export async function discoverKilocodeModels(): Promise<ModelDefinitionConfig[]>
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(KILOCODE_MODELS_URL, {
|
||||
headers: { Accept: "application/json" },
|
||||
signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: KILOCODE_MODELS_URL,
|
||||
init: {
|
||||
headers: { Accept: "application/json" },
|
||||
},
|
||||
timeoutMs: DISCOVERY_TIMEOUT_MS,
|
||||
auditContext: "kilocode.model_discovery",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
log.warn(`Failed to discover models: HTTP ${response.status}, using static catalog`);
|
||||
return buildStaticCatalog();
|
||||
}
|
||||
|
||||
const data = (await response.json()) as GatewayModelsResponse;
|
||||
if (!Array.isArray(data.data) || data.data.length === 0) {
|
||||
log.warn("No models found from gateway API, using static catalog");
|
||||
return buildStaticCatalog();
|
||||
}
|
||||
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
const discoveredIds = new Set<string>();
|
||||
|
||||
for (const entry of data.data) {
|
||||
if (!entry || typeof entry !== "object") {
|
||||
continue;
|
||||
try {
|
||||
if (!response.ok) {
|
||||
log.warn(`Failed to discover models: HTTP ${response.status}, using static catalog`);
|
||||
return buildStaticCatalog();
|
||||
}
|
||||
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
||||
if (!id || discoveredIds.has(id)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
models.push(toModelDefinition(entry));
|
||||
discoveredIds.add(id);
|
||||
} catch (e) {
|
||||
log.warn(`Skipping malformed model entry "${id}": ${String(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const staticModels = buildStaticCatalog();
|
||||
for (const staticModel of staticModels) {
|
||||
if (!discoveredIds.has(staticModel.id)) {
|
||||
models.unshift(staticModel);
|
||||
const data = (await response.json()) as GatewayModelsResponse;
|
||||
if (!Array.isArray(data.data) || data.data.length === 0) {
|
||||
log.warn("No models found from gateway API, using static catalog");
|
||||
return buildStaticCatalog();
|
||||
}
|
||||
}
|
||||
|
||||
return models.length > 0 ? models : buildStaticCatalog();
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
const discoveredIds = new Set<string>();
|
||||
|
||||
for (const entry of data.data) {
|
||||
if (!entry || typeof entry !== "object") {
|
||||
continue;
|
||||
}
|
||||
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
||||
if (!id || discoveredIds.has(id)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
models.push(toModelDefinition(entry));
|
||||
discoveredIds.add(id);
|
||||
} catch (e) {
|
||||
log.warn(`Skipping malformed model entry "${id}": ${String(e)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const staticModels = buildStaticCatalog();
|
||||
for (const staticModel of staticModels) {
|
||||
if (!discoveredIds.has(staticModel.id)) {
|
||||
models.unshift(staticModel);
|
||||
}
|
||||
}
|
||||
|
||||
return models.length > 0 ? models : buildStaticCatalog();
|
||||
} finally {
|
||||
await release();
|
||||
}
|
||||
} catch (error) {
|
||||
log.warn(`Discovery failed: ${String(error)}, using static catalog`);
|
||||
return buildStaticCatalog();
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
|
||||
type ParsedTwilioApiError = {
|
||||
code?: number;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
const TWILIO_API_TIMEOUT_MS = 30_000;
|
||||
|
||||
function parseTwilioApiError(text: string): ParsedTwilioApiError {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(text);
|
||||
@@ -57,23 +61,31 @@ export async function twilioApiRequest<T = unknown>(params: {
|
||||
return acc;
|
||||
}, new URLSearchParams());
|
||||
|
||||
const response = await fetch(`${params.baseUrl}${params.endpoint}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${params.accountSid}:${params.authToken}`).toString("base64")}`,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: `${params.baseUrl}${params.endpoint}`,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${params.accountSid}:${params.authToken}`).toString("base64")}`,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: bodyParams,
|
||||
},
|
||||
body: bodyParams,
|
||||
timeoutMs: TWILIO_API_TIMEOUT_MS,
|
||||
auditContext: "voice-call.twilio_api",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (params.allowNotFound && response.status === 404) {
|
||||
return undefined as T;
|
||||
try {
|
||||
if (!response.ok) {
|
||||
if (params.allowNotFound && response.status === 404) {
|
||||
return undefined as T;
|
||||
}
|
||||
const errorText = await response.text();
|
||||
throw new TwilioApiError(response.status, errorText);
|
||||
}
|
||||
const errorText = await response.text();
|
||||
throw new TwilioApiError(response.status, errorText);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
return text ? (JSON.parse(text) as T) : (undefined as T);
|
||||
const text = await response.text();
|
||||
return text ? (JSON.parse(text) as T) : (undefined as T);
|
||||
} finally {
|
||||
await release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import path from "node:path";
|
||||
import { Command } from "commander";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { SUPERVISOR_HINT_ENV_VARS } from "../../infra/supervisor-markers.js";
|
||||
import { withEnvAsync } from "../../test-utils/env.js";
|
||||
import { withTempSecretFiles } from "../../test-utils/secret-file-fixture.js";
|
||||
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
|
||||
|
||||
@@ -42,6 +44,9 @@ const writeDiagnosticStabilityBundleForFailureSync = vi.fn((_reason: string, _er
|
||||
const controlUiState = vi.hoisted(() => ({
|
||||
root: "/tmp/openclaw-control-ui" as string | null,
|
||||
}));
|
||||
const withoutSupervisorEnv = Object.fromEntries(
|
||||
SUPERVISOR_HINT_ENV_VARS.map((key) => [key, undefined]),
|
||||
) as Record<string, string | undefined>;
|
||||
|
||||
const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
|
||||
|
||||
@@ -317,9 +322,11 @@ describe("gateway run option collisions", () => {
|
||||
});
|
||||
startGatewayServer.mockRejectedValueOnce(err);
|
||||
|
||||
await expect(runGatewayCli(["gateway", "run", "--allow-unconfigured"])).rejects.toThrow(
|
||||
"__exit__:0",
|
||||
);
|
||||
await withEnvAsync(withoutSupervisorEnv, async () => {
|
||||
await expect(runGatewayCli(["gateway", "run", "--allow-unconfigured"])).rejects.toThrow(
|
||||
"__exit__:0",
|
||||
);
|
||||
});
|
||||
|
||||
expect(writeDiagnosticStabilityBundleForFailureSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user