mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:50:45 +00:00
test(e2e): fix kitchen sink crabbox coverage (#76287)
* test(e2e): fix kitchen sink crabbox coverage * test(e2e): update kitchen sink expected diagnostics * fix(plugins): harden registry and package gates * fix(plugins): load lazy tool middleware snapshots * fix(ci): satisfy crabbox branch gates * fix(plugins): await guarded fetch cleanup
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
fetchWithSsrFGuard,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
@@ -533,77 +537,94 @@ export async function discoverChutesModels(accessToken?: string): Promise<ModelD
|
||||
}
|
||||
|
||||
try {
|
||||
let response = await fetch(`${CHUTES_BASE_URL}/models`, {
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
headers,
|
||||
let guardedFetch = await fetchWithSsrFGuard({
|
||||
url: `${CHUTES_BASE_URL}/models`,
|
||||
init: {
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
headers,
|
||||
},
|
||||
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(CHUTES_BASE_URL),
|
||||
auditContext: "chutes-model-discovery",
|
||||
});
|
||||
let response = guardedFetch.response;
|
||||
|
||||
if (response.status === 401 && trimmedKey) {
|
||||
await guardedFetch.release();
|
||||
effectiveKey = "";
|
||||
response = await fetch(`${CHUTES_BASE_URL}/models`, {
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
guardedFetch = await fetchWithSsrFGuard({
|
||||
url: `${CHUTES_BASE_URL}/models`,
|
||||
init: {
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
},
|
||||
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(CHUTES_BASE_URL),
|
||||
auditContext: "chutes-model-discovery",
|
||||
});
|
||||
response = guardedFetch.response;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status !== 401 && response.status !== 503) {
|
||||
log.warn(`GET /v1/models failed: HTTP ${response.status}, using static catalog`);
|
||||
try {
|
||||
if (!response.ok) {
|
||||
if (response.status !== 401 && response.status !== 503) {
|
||||
log.warn(`GET /v1/models failed: HTTP ${response.status}, using static catalog`);
|
||||
}
|
||||
return staticCatalog();
|
||||
}
|
||||
return staticCatalog();
|
||||
}
|
||||
|
||||
const body = (await response.json()) as OpenAIListModelsResponse;
|
||||
const data = body?.data;
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
log.warn("No models in response, using static catalog");
|
||||
return staticCatalog();
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
|
||||
for (const entry of data) {
|
||||
const id = normalizeOptionalString(entry?.id) ?? "";
|
||||
if (!id || seen.has(id)) {
|
||||
continue;
|
||||
const body = (await response.json()) as OpenAIListModelsResponse;
|
||||
const data = body?.data;
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
log.warn("No models in response, using static catalog");
|
||||
return staticCatalog();
|
||||
}
|
||||
seen.add(id);
|
||||
|
||||
const lowerId = normalizeLowercaseStringOrEmpty(id);
|
||||
const isReasoning =
|
||||
entry.supported_features?.includes("reasoning") ||
|
||||
lowerId.includes("r1") ||
|
||||
lowerId.includes("thinking") ||
|
||||
lowerId.includes("reason") ||
|
||||
lowerId.includes("tee");
|
||||
const seen = new Set<string>();
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
|
||||
const input: Array<"text" | "image"> = (entry.input_modalities || ["text"]).filter(
|
||||
(i): i is "text" | "image" => i === "text" || i === "image",
|
||||
for (const entry of data) {
|
||||
const id = normalizeOptionalString(entry?.id) ?? "";
|
||||
if (!id || seen.has(id)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
|
||||
const lowerId = normalizeLowercaseStringOrEmpty(id);
|
||||
const isReasoning =
|
||||
entry.supported_features?.includes("reasoning") ||
|
||||
lowerId.includes("r1") ||
|
||||
lowerId.includes("thinking") ||
|
||||
lowerId.includes("reason") ||
|
||||
lowerId.includes("tee");
|
||||
|
||||
const input: Array<"text" | "image"> = (entry.input_modalities || ["text"]).filter(
|
||||
(i): i is "text" | "image" => i === "text" || i === "image",
|
||||
);
|
||||
|
||||
models.push({
|
||||
id,
|
||||
name: id,
|
||||
reasoning: isReasoning,
|
||||
input,
|
||||
cost: {
|
||||
input: entry.pricing?.prompt || 0,
|
||||
output: entry.pricing?.completion || 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: entry.context_length || CHUTES_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: entry.max_output_length || CHUTES_DEFAULT_MAX_TOKENS,
|
||||
compat: {
|
||||
supportsUsageInStreaming: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return cacheAndReturn(
|
||||
effectiveKey,
|
||||
models.length > 0 ? models : CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition),
|
||||
);
|
||||
|
||||
models.push({
|
||||
id,
|
||||
name: id,
|
||||
reasoning: isReasoning,
|
||||
input,
|
||||
cost: {
|
||||
input: entry.pricing?.prompt || 0,
|
||||
output: entry.pricing?.completion || 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: entry.context_length || CHUTES_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: entry.max_output_length || CHUTES_DEFAULT_MAX_TOKENS,
|
||||
compat: {
|
||||
supportsUsageInStreaming: false,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
await guardedFetch.release();
|
||||
}
|
||||
|
||||
return cacheAndReturn(
|
||||
effectiveKey,
|
||||
models.length > 0 ? models : CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition),
|
||||
);
|
||||
} catch (error) {
|
||||
log.warn(`Discovery failed: ${String(error)}, using static catalog`);
|
||||
return staticCatalog();
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
|
||||
import {
|
||||
fetchWithSsrFGuard,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { isHuggingfaceModelDiscoveryTestEnvironment } from "./model-discovery-env.js";
|
||||
|
||||
@@ -140,65 +144,74 @@ export async function discoverHuggingfaceModels(
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${HUGGINGFACE_BASE_URL}/models`, {
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
headers: {
|
||||
Authorization: `Bearer ${trimmedKey}`,
|
||||
"Content-Type": "application/json",
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: `${HUGGINGFACE_BASE_URL}/models`,
|
||||
init: {
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
headers: {
|
||||
Authorization: `Bearer ${trimmedKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(HUGGINGFACE_BASE_URL),
|
||||
auditContext: "huggingface-model-discovery",
|
||||
});
|
||||
if (!response.ok) {
|
||||
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
}
|
||||
|
||||
const body = (await response.json()) as OpenAIListModelsResponse;
|
||||
const data = body?.data;
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
}
|
||||
|
||||
const catalogById = new Map(
|
||||
HUGGINGFACE_MODEL_CATALOG.map((model) => [model.id, model] as const),
|
||||
);
|
||||
const seen = new Set<string>();
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
|
||||
for (const entry of data) {
|
||||
const id = typeof entry?.id === "string" ? entry.id.trim() : "";
|
||||
if (!id || seen.has(id)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
|
||||
const catalogEntry = catalogById.get(id);
|
||||
if (catalogEntry) {
|
||||
models.push(buildHuggingfaceModelDefinition(catalogEntry));
|
||||
continue;
|
||||
try {
|
||||
if (!response.ok) {
|
||||
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
}
|
||||
|
||||
const inferred = inferredMetaFromModelId(id);
|
||||
const name = displayNameFromApiEntry(entry, inferred.name);
|
||||
const modalities = entry.architecture?.input_modalities;
|
||||
const input: Array<"text" | "image"> =
|
||||
Array.isArray(modalities) && modalities.includes("image") ? ["text", "image"] : ["text"];
|
||||
const providers = Array.isArray(entry.providers) ? entry.providers : [];
|
||||
const providerWithContext = providers.find(
|
||||
(provider) => typeof provider?.context_length === "number" && provider.context_length > 0,
|
||||
const body = (await response.json()) as OpenAIListModelsResponse;
|
||||
const data = body?.data;
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
}
|
||||
|
||||
const catalogById = new Map(
|
||||
HUGGINGFACE_MODEL_CATALOG.map((model) => [model.id, model] as const),
|
||||
);
|
||||
models.push({
|
||||
id,
|
||||
name,
|
||||
reasoning: inferred.reasoning,
|
||||
input,
|
||||
cost: HUGGINGFACE_DEFAULT_COST,
|
||||
contextWindow: providerWithContext?.context_length ?? HUGGINGFACE_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: HUGGINGFACE_DEFAULT_MAX_TOKENS,
|
||||
});
|
||||
}
|
||||
const seen = new Set<string>();
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
|
||||
return models.length > 0
|
||||
? models
|
||||
: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
for (const entry of data) {
|
||||
const id = typeof entry?.id === "string" ? entry.id.trim() : "";
|
||||
if (!id || seen.has(id)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
|
||||
const catalogEntry = catalogById.get(id);
|
||||
if (catalogEntry) {
|
||||
models.push(buildHuggingfaceModelDefinition(catalogEntry));
|
||||
continue;
|
||||
}
|
||||
|
||||
const inferred = inferredMetaFromModelId(id);
|
||||
const name = displayNameFromApiEntry(entry, inferred.name);
|
||||
const modalities = entry.architecture?.input_modalities;
|
||||
const input: Array<"text" | "image"> =
|
||||
Array.isArray(modalities) && modalities.includes("image") ? ["text", "image"] : ["text"];
|
||||
const providers = Array.isArray(entry.providers) ? entry.providers : [];
|
||||
const providerWithContext = providers.find(
|
||||
(provider) => typeof provider?.context_length === "number" && provider.context_length > 0,
|
||||
);
|
||||
models.push({
|
||||
id,
|
||||
name,
|
||||
reasoning: inferred.reasoning,
|
||||
input,
|
||||
cost: HUGGINGFACE_DEFAULT_COST,
|
||||
contextWindow: providerWithContext?.context_length ?? HUGGINGFACE_DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: HUGGINGFACE_DEFAULT_MAX_TOKENS,
|
||||
});
|
||||
}
|
||||
|
||||
return models.length > 0
|
||||
? models
|
||||
: HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
} finally {
|
||||
await release();
|
||||
}
|
||||
} catch {
|
||||
return HUGGINGFACE_MODEL_CATALOG.map(buildHuggingfaceModelDefinition);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user