diff --git a/extensions/microsoft-foundry/onboard.ts b/extensions/microsoft-foundry/onboard.ts index bd9402dae49..6a519d7f0d3 100644 --- a/extensions/microsoft-foundry/onboard.ts +++ b/extensions/microsoft-foundry/onboard.ts @@ -1,5 +1,6 @@ import type { ProviderAuthContext } from "openclaw/plugin-sdk/core"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime"; import { normalizeOptionalString, normalizeStringifiedOptionalString, @@ -469,31 +470,36 @@ export async function testFoundryConnection(params: { modelNameHint: params.modelNameHint, api: params.api, }); - const signal = - typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(15_000) : undefined; - const res = await fetch(testRequest.url, { - method: "POST", - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", + const { response: res, release } = await fetchWithSsrFGuard({ + url: testRequest.url, + init: { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(testRequest.body), }, - body: JSON.stringify(testRequest.body), - ...(signal ? { signal } : {}), + timeoutMs: 15_000, }); - if (res.status === 400) { - const body = await res.text().catch(() => ""); - await params.ctx.prompter.note( - `Endpoint is reachable but returned 400 Bad Request - check your deployment name and API version.\n${body.slice(0, 200)}`, - "Connection Test", - ); - } else if (!res.ok) { - const body = await res.text().catch(() => ""); - await params.ctx.prompter.note( - `Warning: test request returned ${res.status}. ${body.slice(0, 200)}\nProceeding anyway - you can fix the endpoint later.`, - "Connection Test", - ); - } else { - await params.ctx.prompter.note("Connection test successful!", "✓"); + try { + if (res.status === 400) { + const body = await res.text().catch(() => ""); + await params.ctx.prompter.note( + `Endpoint is reachable but returned 400 Bad Request - check your deployment name and API version.\n${body.slice(0, 200)}`, + "Connection Test", + ); + } else if (!res.ok) { + const body = await res.text().catch(() => ""); + await params.ctx.prompter.note( + `Warning: test request returned ${res.status}. ${body.slice(0, 200)}\nProceeding anyway - you can fix the endpoint later.`, + "Connection Test", + ); + } else { + await params.ctx.prompter.note("Connection test successful!", "✓"); + } + } finally { + await release(); } } catch (err) { await params.ctx.prompter.note( diff --git a/src/agents/model-auth-label.ts b/src/agents/model-auth-label.ts index f28013c9825..77118c3e7bb 100644 --- a/src/agents/model-auth-label.ts +++ b/src/agents/model-auth-label.ts @@ -11,7 +11,7 @@ import { normalizeProviderId } from "./model-selection.js"; export function resolveModelAuthLabel(params: { provider?: string; cfg?: OpenClawConfig; - sessionEntry?: SessionEntry; + sessionEntry?: Partial>; agentDir?: string; }): string | undefined { const resolvedProvider = params.provider?.trim(); diff --git a/src/auto-reply/reply/commands-models.ts b/src/auto-reply/reply/commands-models.ts index 9c121cc8ede..2374a4466fe 100644 --- a/src/auto-reply/reply/commands-models.ts +++ b/src/auto-reply/reply/commands-models.ts @@ -21,6 +21,9 @@ import type { CommandHandler } from "./commands-types.js"; const PAGE_SIZE_DEFAULT = 20; const PAGE_SIZE_MAX = 100; +type ModelsCommandSessionEntry = Partial< + Pick +>; export type ModelsProviderData = { byProvider: Map>; @@ -196,7 +199,7 @@ function resolveProviderLabel(params: { provider: string; cfg: OpenClawConfig; agentDir?: string; - sessionEntry?: SessionEntry; + sessionEntry?: ModelsCommandSessionEntry; }): string { const authLabel = resolveModelAuthLabel({ provider: params.provider, @@ -215,7 +218,7 @@ export function formatModelsAvailableHeader(params: { total: number; cfg: OpenClawConfig; agentDir?: string; - sessionEntry?: SessionEntry; + sessionEntry?: ModelsCommandSessionEntry; }): string { const providerLabel = resolveProviderLabel({ provider: params.provider, @@ -233,7 +236,7 @@ export async function resolveModelsCommandReply(params: { currentModel?: string; agentId?: string; agentDir?: string; - sessionEntry?: SessionEntry; + sessionEntry?: ModelsCommandSessionEntry; }): Promise { const body = params.commandBodyNormalized.trim(); if (!body.startsWith("/models")) { diff --git a/src/auto-reply/reply/directive-handling.model.test.ts b/src/auto-reply/reply/directive-handling.model.test.ts index f4b95e6894f..f3f207d57c9 100644 --- a/src/auto-reply/reply/directive-handling.model.test.ts +++ b/src/auto-reply/reply/directive-handling.model.test.ts @@ -21,11 +21,11 @@ vi.mock("../../agents/auth-profiles.js", () => ({ resolveAuthStorePathForDisplay: () => "/tmp/auth-profiles.json", })); +import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js"; import { clearRuntimeAuthProfileStoreSnapshots, replaceRuntimeAuthProfileStoreSnapshots, } from "../../agents/auth-profiles.js"; -import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js"; import type { ModelAliasIndex } from "../../agents/model-selection.js"; import type { OpenClawConfig } from "../../config/config.js"; import type { SessionEntry } from "../../config/sessions.js"; @@ -49,9 +49,17 @@ const queueMocks = vi.hoisted(() => ({ vi.mock("../../agents/agent-scope.js", () => ({ resolveAgentConfig: vi.fn(() => ({})), resolveAgentDir: vi.fn(() => "/tmp/agent"), + resolveAgentEffectiveModelPrimary: vi.fn(() => undefined), resolveSessionAgentId: vi.fn(() => "main"), })); +vi.mock("../../agents/model-catalog.js", () => ({ + loadModelCatalog: vi.fn(async () => [ + { provider: "anthropic", id: "claude-opus-4-6", name: "Claude Opus" }, + { provider: "localai", id: "ultra-chat", name: "Ultra Chat" }, + ]), +})); + vi.mock("../../agents/sandbox.js", () => ({ resolveSandboxRuntimeStatus: vi.fn(() => ({ sandboxed: false })), }));