mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:00:43 +00:00
fix(gateway): scope startup provider discovery
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Providers/Cloudflare AI Gateway: strip assistant prefill turns from Anthropic Messages payloads when thinking is enabled, so Claude requests through Cloudflare AI Gateway no longer fail Anthropic conversation-ending validation. Fixes #72905; carries forward #73005. Thanks @AaronFaby and @sahilsatralkar.
|
||||
- Gateway/startup: scope primary-model provider discovery during channel prewarm to the configured provider owner and add split startup trace timings, so boot avoids staging unrelated bundled provider dependencies while setup discovery remains broad. Fixes #73002. Thanks @Schnup03.
|
||||
- Channels/sessions: prevent guarded inbound session recording from creating route-only phantom sessions while still allowing last-route updates for sessions that already exist. Carries forward #73009. Thanks @jzakirov.
|
||||
- Cron: accept `delivery.threadId` in Gateway cron add/update schemas so scheduled announce delivery can target Telegram forum topics and other threaded channel destinations through the documented delivery path. Fixes #73017. Thanks @coachsootz.
|
||||
- Plugins/runtime deps: stage bundled plugin dependencies imported by mirrored root dist chunks, so packaged memory and status commands do not miss `chokidar` or similar root-chunk dependencies after update. Fixes #72882 and #72970; carries forward #72992. Thanks @shrimpy8, @colin-chang, and @Schnup03.
|
||||
|
||||
@@ -125,6 +125,34 @@ describe("models-config", () => {
|
||||
expect(observedWorkspaceDir).toBe("/tmp/openclaw-workspace");
|
||||
});
|
||||
|
||||
it("threads startup provider discovery scope into implicit provider discovery", async () => {
|
||||
let observedProviderIds: readonly string[] | undefined;
|
||||
let observedTimeoutMs: number | undefined;
|
||||
|
||||
await resolveProvidersForModelsJsonWithDeps(
|
||||
{
|
||||
cfg: { models: { providers: {} } },
|
||||
agentDir: "/tmp/openclaw-models-config-env-vars-test",
|
||||
env: {},
|
||||
providerDiscoveryProviderIds: ["openai"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
},
|
||||
{
|
||||
resolveImplicitProviders: async ({
|
||||
providerDiscoveryProviderIds,
|
||||
providerDiscoveryTimeoutMs,
|
||||
}) => {
|
||||
observedProviderIds = providerDiscoveryProviderIds;
|
||||
observedTimeoutMs = providerDiscoveryTimeoutMs;
|
||||
return {};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(observedProviderIds).toEqual(["openai"]);
|
||||
expect(observedTimeoutMs).toBe(5000);
|
||||
});
|
||||
|
||||
it("threads plugin metadata snapshots through models.json planning", async () => {
|
||||
const pluginMetadataSnapshot = {
|
||||
index: { plugins: [] },
|
||||
|
||||
@@ -22,6 +22,8 @@ export type ResolveImplicitProvidersForModelsJson = (params: {
|
||||
workspaceDir?: string;
|
||||
explicitProviders: Record<string, ProviderConfig>;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
providerDiscoveryProviderIds?: readonly string[];
|
||||
providerDiscoveryTimeoutMs?: number;
|
||||
}) => Promise<Record<string, ProviderConfig>>;
|
||||
|
||||
export type ModelsJsonPlan =
|
||||
@@ -43,6 +45,8 @@ export async function resolveProvidersForModelsJsonWithDeps(
|
||||
env: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
providerDiscoveryProviderIds?: readonly string[];
|
||||
providerDiscoveryTimeoutMs?: number;
|
||||
},
|
||||
deps?: {
|
||||
resolveImplicitProviders?: ResolveImplicitProvidersForModelsJson;
|
||||
@@ -60,6 +64,12 @@ export async function resolveProvidersForModelsJsonWithDeps(
|
||||
...(params.pluginMetadataSnapshot
|
||||
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
|
||||
: {}),
|
||||
...(params.providerDiscoveryProviderIds
|
||||
? { providerDiscoveryProviderIds: params.providerDiscoveryProviderIds }
|
||||
: {}),
|
||||
...(params.providerDiscoveryTimeoutMs !== undefined
|
||||
? { providerDiscoveryTimeoutMs: params.providerDiscoveryTimeoutMs }
|
||||
: {}),
|
||||
});
|
||||
return mergeProviders({
|
||||
implicit: implicitProviders,
|
||||
@@ -101,6 +111,8 @@ export async function planOpenClawModelsJsonWithDeps(
|
||||
existingRaw: string;
|
||||
existingParsed: unknown;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
providerDiscoveryProviderIds?: readonly string[];
|
||||
providerDiscoveryTimeoutMs?: number;
|
||||
},
|
||||
deps?: {
|
||||
resolveImplicitProviders?: ResolveImplicitProvidersForModelsJson;
|
||||
@@ -116,6 +128,12 @@ export async function planOpenClawModelsJsonWithDeps(
|
||||
...(params.pluginMetadataSnapshot
|
||||
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
|
||||
: {}),
|
||||
...(params.providerDiscoveryProviderIds
|
||||
? { providerDiscoveryProviderIds: params.providerDiscoveryProviderIds }
|
||||
: {}),
|
||||
...(params.providerDiscoveryTimeoutMs !== undefined
|
||||
? { providerDiscoveryTimeoutMs: params.providerDiscoveryTimeoutMs }
|
||||
: {}),
|
||||
},
|
||||
deps,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { PluginMetadataSnapshotOwnerMaps } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import type { ProviderPlugin } from "../plugins/types.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
resolveRuntimePluginDiscoveryProviders: vi.fn(),
|
||||
runProviderCatalog: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/provider-discovery.js", () => ({
|
||||
resolveRuntimePluginDiscoveryProviders: mocks.resolveRuntimePluginDiscoveryProviders,
|
||||
runProviderCatalog: mocks.runProviderCatalog,
|
||||
groupPluginDiscoveryProvidersByOrder: (providers: ProviderPlugin[]) => ({
|
||||
simple: providers,
|
||||
profile: [],
|
||||
paired: [],
|
||||
late: [],
|
||||
}),
|
||||
normalizePluginDiscoveryResult: ({
|
||||
provider,
|
||||
result,
|
||||
}: {
|
||||
provider: ProviderPlugin;
|
||||
result?: { provider?: unknown; providers?: Record<string, unknown> } | null;
|
||||
}) => result?.providers ?? (result?.provider ? { [provider.id]: result.provider } : {}),
|
||||
}));
|
||||
|
||||
import { resolveImplicitProviders } from "./models-config.providers.implicit.js";
|
||||
|
||||
function metadataOwners(
|
||||
overrides: Partial<PluginMetadataSnapshotOwnerMaps>,
|
||||
): PluginMetadataSnapshotOwnerMaps {
|
||||
return {
|
||||
channels: new Map(),
|
||||
channelConfigs: new Map(),
|
||||
providers: new Map(),
|
||||
modelCatalogProviders: new Map(),
|
||||
cliBackends: new Map(),
|
||||
setupProviders: new Map(),
|
||||
commandAliases: new Map(),
|
||||
contracts: new Map(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createProvider(id: string): ProviderPlugin {
|
||||
return {
|
||||
id,
|
||||
label: id,
|
||||
auth: [],
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async () => null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("resolveImplicitProviders startup discovery scope", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.resolveRuntimePluginDiscoveryProviders.mockResolvedValue([createProvider("openai")]);
|
||||
mocks.runProviderCatalog.mockResolvedValue({
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
api: "openai-responses",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("passes startup provider scopes as plugin owner filters", async () => {
|
||||
await resolveImplicitProviders({
|
||||
agentDir: "/tmp/openclaw-agent",
|
||||
config: {},
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
explicitProviders: {},
|
||||
pluginMetadataSnapshot: {
|
||||
index: { plugins: [] } as never,
|
||||
manifestRegistry: { plugins: [], diagnostics: [] },
|
||||
owners: metadataOwners({
|
||||
providers: new Map([["openai", ["openai"]]]),
|
||||
}),
|
||||
},
|
||||
providerDiscoveryProviderIds: ["openai"],
|
||||
providerDiscoveryTimeoutMs: 1234,
|
||||
});
|
||||
|
||||
expect(mocks.resolveRuntimePluginDiscoveryProviders).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onlyPluginIds: ["openai"],
|
||||
}),
|
||||
);
|
||||
expect(mocks.runProviderCatalog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
timeoutMs: 1234,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -45,6 +45,8 @@ type ImplicitProviderParams = {
|
||||
workspaceDir?: string;
|
||||
explicitProviders?: Record<string, ProviderConfig> | null;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
providerDiscoveryProviderIds?: readonly string[];
|
||||
providerDiscoveryTimeoutMs?: number;
|
||||
};
|
||||
|
||||
type ImplicitProviderContext = ImplicitProviderParams & {
|
||||
@@ -73,6 +75,7 @@ function resolveProviderDiscoveryFilter(params: {
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
resolveOwners?: (provider: string) => readonly string[] | undefined;
|
||||
providerIds?: readonly string[];
|
||||
}): string[] | undefined {
|
||||
const { config, workspaceDir, env } = params;
|
||||
const testRaw = env.OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS?.trim();
|
||||
@@ -83,6 +86,18 @@ function resolveProviderDiscoveryFilter(params: {
|
||||
.filter(Boolean);
|
||||
return ids.length > 0 ? [...new Set(ids)] : undefined;
|
||||
}
|
||||
const scopedProviderIds = params.providerIds
|
||||
?.map((value) => value.trim())
|
||||
.filter((value) => value.length > 0);
|
||||
if (scopedProviderIds) {
|
||||
return resolveProviderPluginScopeFromProviderIds({
|
||||
providerIds: scopedProviderIds,
|
||||
config,
|
||||
workspaceDir,
|
||||
env,
|
||||
resolveOwners: params.resolveOwners,
|
||||
});
|
||||
}
|
||||
const live =
|
||||
env.OPENCLAW_LIVE_TEST === "1" || env.OPENCLAW_LIVE_GATEWAY === "1" || env.LIVE === "1";
|
||||
if (!live) {
|
||||
@@ -102,15 +117,31 @@ function resolveProviderDiscoveryFilter(params: {
|
||||
if (ids.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveProviderPluginScopeFromProviderIds({
|
||||
providerIds: ids,
|
||||
config,
|
||||
workspaceDir,
|
||||
env,
|
||||
resolveOwners: params.resolveOwners,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveProviderPluginScopeFromProviderIds(params: {
|
||||
providerIds: readonly string[];
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
resolveOwners?: (provider: string) => readonly string[] | undefined;
|
||||
}): string[] {
|
||||
const pluginIds = new Set<string>();
|
||||
for (const id of ids) {
|
||||
for (const id of params.providerIds) {
|
||||
const owners =
|
||||
params.resolveOwners?.(id) ??
|
||||
resolveOwningPluginIdsForProvider({
|
||||
provider: id,
|
||||
config,
|
||||
workspaceDir,
|
||||
env,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
}) ??
|
||||
[];
|
||||
if (owners.length > 0) {
|
||||
@@ -121,9 +152,7 @@ function resolveProviderDiscoveryFilter(params: {
|
||||
}
|
||||
pluginIds.add(id);
|
||||
}
|
||||
return pluginIds.size > 0
|
||||
? [...pluginIds].toSorted((left, right) => left.localeCompare(right))
|
||||
: undefined;
|
||||
return [...pluginIds].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolvePluginMetadataProviderOwners(
|
||||
@@ -140,13 +169,25 @@ function resolvePluginMetadataProviderOwners(
|
||||
const owners = new Set<string>();
|
||||
appendNormalizedPluginMetadataOwners(
|
||||
owners,
|
||||
pluginMetadataSnapshot.owners.providers,
|
||||
pluginMetadataSnapshot.owners.providers ?? new Map(),
|
||||
provider,
|
||||
normalizedProvider,
|
||||
);
|
||||
appendNormalizedPluginMetadataOwners(
|
||||
owners,
|
||||
pluginMetadataSnapshot.owners.cliBackends,
|
||||
pluginMetadataSnapshot.owners.modelCatalogProviders ?? new Map(),
|
||||
provider,
|
||||
normalizedProvider,
|
||||
);
|
||||
appendNormalizedPluginMetadataOwners(
|
||||
owners,
|
||||
pluginMetadataSnapshot.owners.setupProviders ?? new Map(),
|
||||
provider,
|
||||
normalizedProvider,
|
||||
);
|
||||
appendNormalizedPluginMetadataOwners(
|
||||
owners,
|
||||
pluginMetadataSnapshot.owners.cliBackends ?? new Map(),
|
||||
provider,
|
||||
normalizedProvider,
|
||||
);
|
||||
@@ -187,6 +228,7 @@ export function resolveProviderDiscoveryFilterForTest(params: {
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
resolveOwners?: (provider: string) => readonly string[] | undefined;
|
||||
providerIds?: readonly string[];
|
||||
}): string[] | undefined {
|
||||
return resolveProviderDiscoveryFilter(params);
|
||||
}
|
||||
@@ -321,7 +363,7 @@ async function resolvePluginImplicitProviders(
|
||||
resolveProviderApiKey: resolveCatalogProviderApiKey,
|
||||
resolveProviderAuth: (providerId, options) =>
|
||||
ctx.resolveProviderAuth(providerId?.trim() || provider.id, options),
|
||||
timeoutMs: resolveLiveProviderCatalogTimeoutMs(ctx.env),
|
||||
timeoutMs: ctx.providerDiscoveryTimeoutMs ?? resolveLiveProviderCatalogTimeoutMs(ctx.env),
|
||||
});
|
||||
if (!result) {
|
||||
continue;
|
||||
@@ -435,6 +477,7 @@ export async function resolveImplicitProviders(
|
||||
resolveOwners: params.pluginMetadataSnapshot
|
||||
? (provider) => resolvePluginMetadataProviderOwners(params.pluginMetadataSnapshot, provider)
|
||||
: undefined,
|
||||
providerIds: params.providerDiscoveryProviderIds,
|
||||
}),
|
||||
...(params.pluginMetadataSnapshot
|
||||
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
|
||||
|
||||
@@ -132,4 +132,39 @@ describe("resolveProviderDiscoveryFilterForTest", () => {
|
||||
}),
|
||||
).toEqual(["volcengine"]);
|
||||
});
|
||||
|
||||
it("scopes normal startup discovery to requested provider owners", () => {
|
||||
const snapshot = {
|
||||
owners: metadataOwners({
|
||||
providers: new Map([
|
||||
["openai", ["openai"]],
|
||||
["anthropic", ["anthropic"]],
|
||||
]),
|
||||
}),
|
||||
};
|
||||
|
||||
expect(
|
||||
resolveProviderDiscoveryFilterForTest({
|
||||
env: liveFilterEnv({}),
|
||||
providerIds: ["openai"],
|
||||
resolveOwners: (provider) => resolvePluginMetadataProviderOwnersForTest(snapshot, provider),
|
||||
}),
|
||||
).toEqual(["openai"]);
|
||||
});
|
||||
|
||||
it("maps scoped startup provider aliases through model catalog owners", () => {
|
||||
const snapshot = {
|
||||
owners: metadataOwners({
|
||||
modelCatalogProviders: new Map([["openai-codex", ["codex"]]]),
|
||||
}),
|
||||
};
|
||||
|
||||
expect(
|
||||
resolveProviderDiscoveryFilterForTest({
|
||||
env: liveFilterEnv({}),
|
||||
providerIds: ["OpenAI-Codex"],
|
||||
resolveOwners: (provider) => resolvePluginMetadataProviderOwnersForTest(snapshot, provider),
|
||||
}),
|
||||
).toEqual(["codex"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,8 @@ async function buildModelsJsonFingerprint(params: {
|
||||
agentDir: string;
|
||||
workspaceDir?: string;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index">;
|
||||
providerDiscoveryProviderIds?: readonly string[];
|
||||
providerDiscoveryTimeoutMs?: number;
|
||||
}): Promise<string> {
|
||||
const authProfilesMtimeMs = await readFileMtimeMs(
|
||||
path.join(params.agentDir, "auth-profiles.json"),
|
||||
@@ -64,6 +66,8 @@ async function buildModelsJsonFingerprint(params: {
|
||||
modelsFileMtimeMs,
|
||||
workspaceDir: params.workspaceDir,
|
||||
pluginMetadataSnapshotIndexFingerprint,
|
||||
providerDiscoveryProviderIds: params.providerDiscoveryProviderIds,
|
||||
providerDiscoveryTimeoutMs: params.providerDiscoveryTimeoutMs,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,6 +156,8 @@ export async function ensureOpenClawModelsJson(
|
||||
options: {
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
workspaceDir?: string;
|
||||
providerDiscoveryProviderIds?: readonly string[];
|
||||
providerDiscoveryTimeoutMs?: number;
|
||||
} = {},
|
||||
): Promise<{ agentDir: string; wrote: boolean }> {
|
||||
const resolved = resolveModelsConfigInput(config);
|
||||
@@ -175,6 +181,12 @@ export async function ensureOpenClawModelsJson(
|
||||
agentDir,
|
||||
...(workspaceDir ? { workspaceDir } : {}),
|
||||
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
|
||||
...(options.providerDiscoveryProviderIds
|
||||
? { providerDiscoveryProviderIds: options.providerDiscoveryProviderIds }
|
||||
: {}),
|
||||
...(options.providerDiscoveryTimeoutMs !== undefined
|
||||
? { providerDiscoveryTimeoutMs: options.providerDiscoveryTimeoutMs }
|
||||
: {}),
|
||||
});
|
||||
const cached = MODELS_JSON_STATE.readyCache.get(targetPath);
|
||||
if (cached) {
|
||||
@@ -199,6 +211,12 @@ export async function ensureOpenClawModelsJson(
|
||||
existingRaw: existingModelsFile.raw,
|
||||
existingParsed: existingModelsFile.parsed,
|
||||
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
|
||||
...(options.providerDiscoveryProviderIds
|
||||
? { providerDiscoveryProviderIds: options.providerDiscoveryProviderIds }
|
||||
: {}),
|
||||
...(options.providerDiscoveryTimeoutMs !== undefined
|
||||
? { providerDiscoveryTimeoutMs: options.providerDiscoveryTimeoutMs }
|
||||
: {}),
|
||||
});
|
||||
|
||||
if (plan.action === "skip") {
|
||||
|
||||
@@ -113,6 +113,29 @@ describe("models-config write serialization", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not reuse scoped startup discovery cache for a different provider scope", async () => {
|
||||
await withModelsTempHome(async (home) => {
|
||||
planOpenClawModelsJsonMock.mockImplementation(async () => ({ action: "skip" }));
|
||||
const agentDir = path.join(home, "agent");
|
||||
await ensureOpenClawModelsJson({}, agentDir, {
|
||||
providerDiscoveryProviderIds: ["openai"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
});
|
||||
await ensureOpenClawModelsJson({}, agentDir, {
|
||||
providerDiscoveryProviderIds: ["anthropic"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
});
|
||||
|
||||
expect(planOpenClawModelsJsonMock).toHaveBeenCalledTimes(2);
|
||||
expect(planOpenClawModelsJsonMock).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
providerDiscoveryProviderIds: ["anthropic"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("serializes concurrent models.json writes to avoid overlap", async () => {
|
||||
await withModelsTempHome(async () => {
|
||||
const first = structuredClone(CUSTOM_PROXY_MODELS_CONFIG);
|
||||
|
||||
@@ -22,6 +22,7 @@ const SESSION_LOCK_STALE_MS = 30 * 60 * 1000;
|
||||
const ACP_BACKEND_READY_TIMEOUT_MS = 5_000;
|
||||
const ACP_BACKEND_READY_POLL_MS = 50;
|
||||
const PRIMARY_MODEL_PREWARM_TIMEOUT_MS = 5_000;
|
||||
const STARTUP_PROVIDER_DISCOVERY_TIMEOUT_MS = 5_000;
|
||||
|
||||
type Awaitable<T> = T | Promise<T>;
|
||||
|
||||
@@ -146,7 +147,10 @@ async function prewarmConfiguredPrimaryModel(params: {
|
||||
}
|
||||
const agentDir = resolveOpenClawAgentDir();
|
||||
try {
|
||||
await ensureOpenClawModelsJson(params.cfg, agentDir);
|
||||
await ensureOpenClawModelsJson(params.cfg, agentDir, {
|
||||
providerDiscoveryProviderIds: [provider],
|
||||
providerDiscoveryTimeoutMs: STARTUP_PROVIDER_DISCOVERY_TIMEOUT_MS,
|
||||
});
|
||||
const resolved = resolveModel(provider, model, agentDir, params.cfg, {
|
||||
skipProviderRuntimeHooks: true,
|
||||
});
|
||||
@@ -318,11 +322,15 @@ export async function startGatewaySidecars(params: {
|
||||
await measureStartup(params.startupTrace, "sidecars.channels", async () => {
|
||||
if (!skipChannels) {
|
||||
try {
|
||||
await prewarmConfiguredPrimaryModelWithTimeout({
|
||||
cfg: params.cfg,
|
||||
log: params.log,
|
||||
});
|
||||
await params.startChannels();
|
||||
await measureStartup(params.startupTrace, "sidecars.model-prewarm", () =>
|
||||
prewarmConfiguredPrimaryModelWithTimeout({
|
||||
cfg: params.cfg,
|
||||
log: params.log,
|
||||
}),
|
||||
);
|
||||
await measureStartup(params.startupTrace, "sidecars.channel-start", () =>
|
||||
params.startChannels(),
|
||||
);
|
||||
} catch (err) {
|
||||
params.logChannels.error(`channel startup failed: ${String(err)}`);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
||||
const ensureOpenClawModelsJsonMock = vi.fn<
|
||||
(config: unknown, agentDir: unknown) => Promise<{ agentDir: string; wrote: boolean }>
|
||||
(
|
||||
config: unknown,
|
||||
agentDir: unknown,
|
||||
options?: unknown,
|
||||
) => Promise<{ agentDir: string; wrote: boolean }>
|
||||
>(async () => ({ agentDir: "/tmp/agent", wrote: false }));
|
||||
const resolveModelMock = vi.fn<
|
||||
(
|
||||
@@ -41,8 +45,8 @@ vi.mock("../agents/agent-paths.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../agents/models-config.js", () => ({
|
||||
ensureOpenClawModelsJson: (config: unknown, agentDir: unknown) =>
|
||||
ensureOpenClawModelsJsonMock(config, agentDir),
|
||||
ensureOpenClawModelsJson: (config: unknown, agentDir: unknown, options?: unknown) =>
|
||||
ensureOpenClawModelsJsonMock(config, agentDir, options),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/harness/selection.js", () => ({
|
||||
@@ -100,7 +104,14 @@ describe("gateway startup primary model warmup", () => {
|
||||
log: { warn: vi.fn() },
|
||||
});
|
||||
|
||||
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledWith(cfg, "/tmp/agent");
|
||||
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledWith(
|
||||
cfg,
|
||||
"/tmp/agent",
|
||||
expect.objectContaining({
|
||||
providerDiscoveryProviderIds: ["openai-codex"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
}),
|
||||
);
|
||||
expect(resolveModelMock).toHaveBeenCalledWith("openai-codex", "gpt-5.4", "/tmp/agent", cfg, {
|
||||
skipProviderRuntimeHooks: true,
|
||||
});
|
||||
@@ -208,7 +219,14 @@ describe("gateway startup primary model warmup", () => {
|
||||
modelId: "gpt-5.4",
|
||||
config: cfg,
|
||||
});
|
||||
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledWith(cfg, "/tmp/agent");
|
||||
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledWith(
|
||||
cfg,
|
||||
"/tmp/agent",
|
||||
expect.objectContaining({
|
||||
providerDiscoveryProviderIds: ["openai-codex"],
|
||||
providerDiscoveryTimeoutMs: 5000,
|
||||
}),
|
||||
);
|
||||
expect(resolveModelMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user