mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-31 20:01:36 +00:00
fix: stabilize extensions surface test gate
This commit is contained in:
@@ -115,7 +115,13 @@ vi.mock("../../../src/agents/tools/gateway.js", () => gatewayMocks);
|
||||
const configMocks = vi.hoisted(() => ({
|
||||
loadConfig: vi.fn(() => ({ browser: {} })),
|
||||
}));
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", () => configMocks);
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: configMocks.loadConfig,
|
||||
};
|
||||
});
|
||||
|
||||
const sessionTabRegistryMocks = vi.hoisted(() => ({
|
||||
trackSessionBrowserTab: vi.fn(),
|
||||
|
||||
@@ -8,9 +8,13 @@ const { loadConfigMock, isNodeCommandAllowedMock, resolveNodeCommandAllowlistMoc
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", () => ({
|
||||
loadConfig: loadConfigMock,
|
||||
}));
|
||||
vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: loadConfigMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../../../src/gateway/node-command-policy.js", () => ({
|
||||
isNodeCommandAllowed: isNodeCommandAllowedMock,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleFixture,
|
||||
createFeishuTextMessageEvent,
|
||||
@@ -8,10 +11,7 @@ import {
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "../../../test/helpers/plugins/feishu-lifecycle.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
} from "./test-support/lifecycle.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleConfig,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
@@ -12,10 +15,7 @@ import {
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "../../../test/helpers/plugins/feishu-lifecycle.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
} from "./test-support/lifecycle.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createNonExitingRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { FeishuConfigSchema } from "./config-schema.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuTextMessageEvent,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
@@ -9,11 +13,7 @@ import {
|
||||
runFeishuLifecycleSequence,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "../../../test/helpers/plugins/feishu-lifecycle.js";
|
||||
import { createNonExitingRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { FeishuConfigSchema } from "./config-schema.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
} from "./test-support/lifecycle.js";
|
||||
import type { FeishuConfig, ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { resetProcessedFeishuCardActionTokensForTests } from "./card-action.js";
|
||||
import { createFeishuCardInteractionEnvelope } from "./card-interaction.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleConfig,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
@@ -12,12 +17,7 @@ import {
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "../../../test/helpers/plugins/feishu-lifecycle.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { resetProcessedFeishuCardActionTokensForTests } from "./card-action.js";
|
||||
import { createFeishuCardInteractionEnvelope } from "./card-interaction.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
} from "./test-support/lifecycle.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleConfig,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
@@ -13,10 +16,7 @@ import {
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "../../../test/helpers/plugins/feishu-lifecycle.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
} from "./test-support/lifecycle.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,39 +1,8 @@
|
||||
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/feishu";
|
||||
import { expect, vi } from "vitest";
|
||||
import { loadBundledPluginPublicSurfaceSync } from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
import { createPluginRuntimeMock } from "./plugin-runtime-mock.js";
|
||||
|
||||
type ResolvedFeishuAccount = {
|
||||
accountId: string;
|
||||
selectionSource: string;
|
||||
enabled: boolean;
|
||||
configured: boolean;
|
||||
name?: string;
|
||||
appId?: string;
|
||||
appSecret?: string;
|
||||
encryptKey?: string;
|
||||
verificationToken?: string;
|
||||
domain: string;
|
||||
config: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const { monitorSingleAccount } = loadBundledPluginPublicSurfaceSync<{
|
||||
monitorSingleAccount: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
account: ResolvedFeishuAccount;
|
||||
runtime: RuntimeEnv;
|
||||
botOpenIdSource: typeof FEISHU_PREFETCHED_BOT_OPEN_ID_SOURCE;
|
||||
}) => Promise<void>;
|
||||
}>({
|
||||
pluginId: "feishu",
|
||||
artifactBasename: "src/monitor.account.js",
|
||||
});
|
||||
const { setFeishuRuntime } = loadBundledPluginPublicSurfaceSync<{
|
||||
setFeishuRuntime: (runtime: PluginRuntime) => void;
|
||||
}>({
|
||||
pluginId: "feishu",
|
||||
artifactBasename: "src/runtime.js",
|
||||
});
|
||||
import { createPluginRuntimeMock } from "../../../../test/helpers/plugins/plugin-runtime-mock.js";
|
||||
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../../runtime-api.js";
|
||||
import { setFeishuRuntime } from "../runtime.js";
|
||||
import type { ResolvedFeishuAccount } from "../types.js";
|
||||
|
||||
type InboundDebouncerParams<T> = {
|
||||
onFlush?: (items: T[]) => Promise<void>;
|
||||
@@ -261,7 +230,7 @@ export function createResolvedFeishuLifecycleAccount(params: {
|
||||
}): ResolvedFeishuAccount {
|
||||
return {
|
||||
accountId: params.accountId,
|
||||
selectionSource: "explicit",
|
||||
selectionSource: "config",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
appId: params.appId,
|
||||
@@ -400,6 +369,11 @@ export function expectFeishuReplyDispatcherSentFinalReplyOnce(params: {
|
||||
expect(dispatcher.sendFinalReply).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
async function loadMonitorSingleAccount() {
|
||||
const module = await import("../monitor.account.js");
|
||||
return module.monitorSingleAccount;
|
||||
}
|
||||
|
||||
export async function setupFeishuLifecycleHandler<T extends RuntimeEnv>(params: {
|
||||
createEventDispatcherMock: {
|
||||
mockReturnValue: (value: unknown) => unknown;
|
||||
@@ -422,6 +396,7 @@ export async function setupFeishuLifecycleHandler<T extends RuntimeEnv>(params:
|
||||
params.createEventDispatcherMock.mockReturnValue({ register });
|
||||
}
|
||||
|
||||
const monitorSingleAccount = await loadMonitorSingleAccount();
|
||||
await monitorSingleAccount({
|
||||
cfg: params.cfg,
|
||||
account: params.account,
|
||||
@@ -2,10 +2,7 @@
|
||||
import type { PluginSdkFacadeTypeMap } from "../generated/plugin-sdk-facade-type-map.generated.js";
|
||||
type FacadeEntry = PluginSdkFacadeTypeMap["matrix-runtime-surface"];
|
||||
type FacadeModule = FacadeEntry["module"];
|
||||
import {
|
||||
createLazyFacadeObjectValue,
|
||||
loadBundledPluginPublicSurfaceModuleSync,
|
||||
} from "./facade-runtime.js";
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
function loadFacadeModule(): FacadeModule {
|
||||
return loadBundledPluginPublicSurfaceModuleSync<FacadeModule>({
|
||||
@@ -19,6 +16,5 @@ export const resolveMatrixAccountStringValues: FacadeModule["resolveMatrixAccoun
|
||||
loadFacadeModule()["resolveMatrixAccountStringValues"](
|
||||
...args,
|
||||
)) as FacadeModule["resolveMatrixAccountStringValues"];
|
||||
export const setMatrixRuntime: FacadeModule["setMatrixRuntime"] = createLazyFacadeObjectValue(
|
||||
() => loadFacadeModule()["setMatrixRuntime"] as object,
|
||||
) as FacadeModule["setMatrixRuntime"];
|
||||
export const setMatrixRuntime: FacadeModule["setMatrixRuntime"] = ((...args) =>
|
||||
loadFacadeModule()["setMatrixRuntime"](...args)) as FacadeModule["setMatrixRuntime"];
|
||||
|
||||
24
test/fixtures/test-parallel.behavior.json
vendored
24
test/fixtures/test-parallel.behavior.json
vendored
@@ -146,6 +146,10 @@
|
||||
"file": "extensions/duckduckgo/src/ddg-search-provider.test.ts",
|
||||
"reason": "This provider test hoists a client mock and imports the provider lazily; keep it in its own forked lane so shared extension workers do not reuse a previously cached module under test."
|
||||
},
|
||||
{
|
||||
"file": "extensions/browser/src/browser/chrome.test.ts",
|
||||
"reason": "This Chrome CDP helper suite opens mock WebSocket servers and stubs global fetch state; keep it isolated so shared extension workers do not inherit stale browser probe state from neighboring files."
|
||||
},
|
||||
{
|
||||
"file": "extensions/firecrawl/src/firecrawl-scrape-tool.test.ts",
|
||||
"reason": "This scrape-tool suite hoists client mocks and imports the tool lazily; keep it in its own forked lane so shared extension workers cannot bypass the mocked client via cached modules."
|
||||
@@ -158,6 +162,26 @@
|
||||
"file": "extensions/firecrawl/src/firecrawl-search-tool.test.ts",
|
||||
"reason": "This tool suite hoists the Firecrawl client mock and imports lazily; keep it isolated so shared extension workers cannot leak cached implementations across files."
|
||||
},
|
||||
{
|
||||
"file": "extensions/feishu/src/monitor.acp-init-failure.lifecycle.test.ts",
|
||||
"reason": "This Feishu websocket lifecycle suite can stall only in the shared extensions swarm when reconnect state bleeds across files; keep it isolated so ACP init-failure teardown stays deterministic."
|
||||
},
|
||||
{
|
||||
"file": "extensions/feishu/src/monitor.bot-menu.lifecycle.test.ts",
|
||||
"reason": "This Feishu websocket lifecycle suite can stall only in the shared extensions swarm when reconnect state bleeds across files; keep it isolated so bot-menu lifecycle teardown stays deterministic."
|
||||
},
|
||||
{
|
||||
"file": "extensions/feishu/src/monitor.broadcast.reply-once.lifecycle.test.ts",
|
||||
"reason": "This Feishu websocket lifecycle suite can stall only in the shared extensions swarm when reconnect state bleeds across files; keep it isolated so broadcast reply-once teardown stays deterministic."
|
||||
},
|
||||
{
|
||||
"file": "extensions/feishu/src/monitor.card-action.lifecycle.test.ts",
|
||||
"reason": "This Feishu websocket lifecycle suite shares the same reconnect helpers and module state; keep it isolated with the other Feishu lifecycle files so card-action runs do not inherit cross-file websocket state."
|
||||
},
|
||||
{
|
||||
"file": "extensions/feishu/src/monitor.reply-once.lifecycle.test.ts",
|
||||
"reason": "This Feishu websocket lifecycle suite can stall only in the shared extensions swarm when reconnect state bleeds across files; keep it isolated so reply-once teardown stays deterministic."
|
||||
},
|
||||
{
|
||||
"file": "extensions/line/src/download.test.ts",
|
||||
"reason": "This LINE media-download suite depends on hoisted SDK mocks; run it in its own forked lane so shared extension workers do not fall back to the real SDK client."
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { vi } from "vitest";
|
||||
import { resolveRelativeBundledPluginPublicModuleId } from "../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
|
||||
export type SearchImpl = () => Promise<unknown[]>;
|
||||
export type MemoryReadParams = { relPath: string; from?: number; lines?: number };
|
||||
@@ -39,16 +38,10 @@ const readAgentMemoryFileMock = vi.fn(
|
||||
async (params: MemoryReadParams) => await readFileImpl(params),
|
||||
);
|
||||
|
||||
const memoryIndexModuleId = resolveRelativeBundledPluginPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
pluginId: "memory-core",
|
||||
artifactBasename: "src/memory/index.js",
|
||||
});
|
||||
const memoryToolsRuntimeModuleId = resolveRelativeBundledPluginPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
pluginId: "memory-core",
|
||||
artifactBasename: "src/tools.runtime.js",
|
||||
});
|
||||
const { memoryIndexModuleId, memoryToolsRuntimeModuleId } = vi.hoisted(() => ({
|
||||
memoryIndexModuleId: "../../extensions/memory-core/src/memory/index.js",
|
||||
memoryToolsRuntimeModuleId: "../../extensions/memory-core/src/tools.runtime.js",
|
||||
}));
|
||||
|
||||
vi.mock(memoryIndexModuleId, () => ({
|
||||
getMemorySearchManager: getMemorySearchManagerMock,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { clearRuntimeAuthProfileStoreSnapshots } from "../../../src/agents/auth-
|
||||
import type { AuthProfileStore } from "../../../src/agents/auth-profiles/types.js";
|
||||
import { registerProviders, requireProvider } from "../../../src/plugins/contracts/testkit.js";
|
||||
import { createNonExitingRuntime } from "../../../src/runtime.js";
|
||||
import { loadBundledPluginPublicSurfaceSync } from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
import type {
|
||||
WizardMultiSelectParams,
|
||||
WizardPrompter,
|
||||
@@ -26,6 +25,13 @@ const loginOpenAICodexOAuthMock = vi.hoisted(() => vi.fn<LoginOpenAICodexOAuth>(
|
||||
const githubCopilotLoginCommandMock = vi.hoisted(() => vi.fn<GithubCopilotLoginCommand>());
|
||||
const ensureAuthProfileStoreMock = vi.hoisted(() => vi.fn<EnsureAuthProfileStore>());
|
||||
const listProfilesForProviderMock = vi.hoisted(() => vi.fn<ListProfilesForProvider>());
|
||||
const providerAuthContractModules = vi.hoisted(() => ({
|
||||
githubCopilotIndexModuleUrl: new URL(
|
||||
"../../../extensions/github-copilot/index.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
openAIIndexModuleUrl: new URL("../../../extensions/openai/index.ts", import.meta.url).href,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth-login", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/provider-auth-login")>();
|
||||
@@ -45,18 +51,9 @@ vi.mock("openclaw/plugin-sdk/provider-auth", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
const { default: githubCopilotPlugin } = loadBundledPluginPublicSurfaceSync<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({
|
||||
pluginId: "github-copilot",
|
||||
artifactBasename: "index.js",
|
||||
});
|
||||
const { default: openAIPlugin } = loadBundledPluginPublicSurfaceSync<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({
|
||||
pluginId: "openai",
|
||||
artifactBasename: "index.js",
|
||||
});
|
||||
async function importBundledProviderPlugin<T>(moduleUrl: string): Promise<T> {
|
||||
return (await import(`${moduleUrl}?t=${Date.now()}`)) as T;
|
||||
}
|
||||
|
||||
function buildPrompter(): WizardPrompter {
|
||||
const progress: WizardProgress = {
|
||||
@@ -166,6 +163,9 @@ export function describeOpenAICodexProviderAuthContract() {
|
||||
installSharedAuthProfileStoreHooks(state);
|
||||
|
||||
async function expectStableFallbackProfile(params: { access: string; profileId: string }) {
|
||||
const { default: openAIPlugin } = await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>(providerAuthContractModules.openAIIndexModuleUrl);
|
||||
const provider = requireProvider(registerProviders(openAIPlugin), "openai-codex");
|
||||
loginOpenAICodexOAuthMock.mockResolvedValueOnce({
|
||||
refresh: "refresh-token",
|
||||
@@ -183,12 +183,15 @@ export function describeOpenAICodexProviderAuthContract() {
|
||||
);
|
||||
}
|
||||
|
||||
function getProvider() {
|
||||
async function getProvider() {
|
||||
const { default: openAIPlugin } = await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>(providerAuthContractModules.openAIIndexModuleUrl);
|
||||
return requireProvider(registerProviders(openAIPlugin), "openai-codex");
|
||||
}
|
||||
|
||||
it("keeps OAuth auth results provider-owned", async () => {
|
||||
const provider = getProvider();
|
||||
const provider = await getProvider();
|
||||
loginOpenAICodexOAuthMock.mockResolvedValueOnce({
|
||||
email: "user@example.com",
|
||||
refresh: "refresh-token",
|
||||
@@ -210,7 +213,7 @@ export function describeOpenAICodexProviderAuthContract() {
|
||||
});
|
||||
|
||||
it("backfills OAuth email from the JWT profile claim", async () => {
|
||||
const provider = getProvider();
|
||||
const provider = await getProvider();
|
||||
const access = createJwt({
|
||||
"https://api.openai.com/profile": {
|
||||
email: "jwt-user@example.com",
|
||||
@@ -274,7 +277,7 @@ export function describeOpenAICodexProviderAuthContract() {
|
||||
});
|
||||
|
||||
it("falls back to the default profile when JWT parsing yields no identity", async () => {
|
||||
const provider = getProvider();
|
||||
const provider = await getProvider();
|
||||
loginOpenAICodexOAuthMock.mockResolvedValueOnce({
|
||||
refresh: "refresh-token",
|
||||
access: "not-a-jwt-token",
|
||||
@@ -294,7 +297,7 @@ export function describeOpenAICodexProviderAuthContract() {
|
||||
});
|
||||
|
||||
it("keeps OAuth failures non-fatal at the provider layer", async () => {
|
||||
const provider = getProvider();
|
||||
const provider = await getProvider();
|
||||
loginOpenAICodexOAuthMock.mockRejectedValueOnce(new Error("oauth failed"));
|
||||
|
||||
await expect(provider.auth[0]?.run(buildAuthContext() as never)).resolves.toEqual({
|
||||
@@ -312,12 +315,15 @@ export function describeGithubCopilotProviderAuthContract() {
|
||||
describe("github-copilot provider auth contract", () => {
|
||||
installSharedAuthProfileStoreHooks(state);
|
||||
|
||||
function getProvider() {
|
||||
async function getProvider() {
|
||||
const { default: githubCopilotPlugin } = await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>(providerAuthContractModules.githubCopilotIndexModuleUrl);
|
||||
return requireProvider(registerProviders(githubCopilotPlugin), "github-copilot");
|
||||
}
|
||||
|
||||
it("keeps device auth results provider-owned", async () => {
|
||||
const provider = getProvider();
|
||||
const provider = await getProvider();
|
||||
state.authStore.profiles["github-copilot:github"] = {
|
||||
type: "token",
|
||||
provider: "github-copilot",
|
||||
@@ -362,7 +368,7 @@ export function describeGithubCopilotProviderAuthContract() {
|
||||
});
|
||||
|
||||
it("keeps auth gated on interactive TTYs", async () => {
|
||||
const provider = getProvider();
|
||||
const provider = await getProvider();
|
||||
const stdin = process.stdin as NodeJS.ReadStream & { isTTY?: boolean };
|
||||
const hadOwnIsTTY = Object.prototype.hasOwnProperty.call(stdin, "isTTY");
|
||||
const previousIsTTYDescriptor = Object.getOwnPropertyDescriptor(stdin, "isTTY");
|
||||
|
||||
@@ -3,10 +3,6 @@ import type { AuthProfileStore } from "../../../src/agents/auth-profiles/types.j
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { ModelDefinitionConfig } from "../../../src/config/types.models.js";
|
||||
import { registerProviders, requireProvider } from "../../../src/plugins/contracts/testkit.js";
|
||||
import {
|
||||
loadBundledPluginPublicSurfaceSync,
|
||||
resolveRelativeBundledPluginPublicModuleId,
|
||||
} from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
|
||||
const resolveCopilotApiTokenMock = vi.hoisted(() => vi.fn());
|
||||
const buildOllamaProviderMock = vi.hoisted(() => vi.fn());
|
||||
@@ -14,6 +10,33 @@ const buildVllmProviderMock = vi.hoisted(() => vi.fn());
|
||||
const buildSglangProviderMock = vi.hoisted(() => vi.fn());
|
||||
const ensureAuthProfileStoreMock = vi.hoisted(() => vi.fn());
|
||||
const listProfilesForProviderMock = vi.hoisted(() => vi.fn());
|
||||
const bundledProviderModules = vi.hoisted(() => ({
|
||||
cloudflareAiGatewayIndexModuleUrl: new URL(
|
||||
"../../../extensions/cloudflare-ai-gateway/index.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
cloudflareAiGatewayIndexModuleId: new URL(
|
||||
"../../../extensions/cloudflare-ai-gateway/index.js",
|
||||
import.meta.url,
|
||||
).pathname,
|
||||
githubCopilotIndexModuleUrl: new URL(
|
||||
"../../../extensions/github-copilot/index.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
githubCopilotTokenModuleId: new URL(
|
||||
"../../../extensions/github-copilot/token.js",
|
||||
import.meta.url,
|
||||
).pathname,
|
||||
minimaxIndexModuleUrl: new URL("../../../extensions/minimax/index.ts", import.meta.url).href,
|
||||
modelStudioIndexModuleUrl: new URL("../../../extensions/modelstudio/index.ts", import.meta.url)
|
||||
.href,
|
||||
ollamaApiModuleId: new URL("../../../extensions/ollama/api.js", import.meta.url).pathname,
|
||||
ollamaIndexModuleUrl: new URL("../../../extensions/ollama/index.ts", import.meta.url).href,
|
||||
sglangApiModuleId: new URL("../../../extensions/sglang/api.js", import.meta.url).pathname,
|
||||
sglangIndexModuleUrl: new URL("../../../extensions/sglang/index.ts", import.meta.url).href,
|
||||
vllmApiModuleId: new URL("../../../extensions/vllm/api.js", import.meta.url).pathname,
|
||||
vllmIndexModuleUrl: new URL("../../../extensions/vllm/index.ts", import.meta.url).href,
|
||||
}));
|
||||
|
||||
type ProviderHandle = Awaited<ReturnType<typeof requireProvider>>;
|
||||
|
||||
@@ -108,28 +131,12 @@ function runCatalog(
|
||||
});
|
||||
}
|
||||
|
||||
async function importBundledProviderPlugin<T>(moduleUrl: string): Promise<T> {
|
||||
return (await import(`${moduleUrl}?t=${Date.now()}`)) as T;
|
||||
}
|
||||
|
||||
function installDiscoveryHooks(state: DiscoveryState) {
|
||||
beforeEach(async () => {
|
||||
const githubCopilotTokenModuleId = resolveRelativeBundledPluginPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
pluginId: "github-copilot",
|
||||
artifactBasename: "token.js",
|
||||
});
|
||||
const ollamaApiModuleId = resolveRelativeBundledPluginPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
pluginId: "ollama",
|
||||
artifactBasename: "api.js",
|
||||
});
|
||||
const vllmApiModuleId = resolveRelativeBundledPluginPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
pluginId: "vllm",
|
||||
artifactBasename: "api.js",
|
||||
});
|
||||
const sglangApiModuleId = resolveRelativeBundledPluginPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
pluginId: "sglang",
|
||||
artifactBasename: "api.js",
|
||||
});
|
||||
vi.resetModules();
|
||||
vi.doMock("openclaw/plugin-sdk/agent-runtime", async () => {
|
||||
const actual = await import("../../../src/plugin-sdk/agent-runtime.ts");
|
||||
@@ -147,29 +154,31 @@ function installDiscoveryHooks(state: DiscoveryState) {
|
||||
listProfilesForProvider: listProfilesForProviderMock,
|
||||
};
|
||||
});
|
||||
vi.doMock(githubCopilotTokenModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(githubCopilotTokenModuleId);
|
||||
vi.doMock(bundledProviderModules.githubCopilotTokenModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(
|
||||
bundledProviderModules.githubCopilotTokenModuleId,
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
resolveCopilotApiToken: resolveCopilotApiTokenMock,
|
||||
};
|
||||
});
|
||||
vi.doMock(ollamaApiModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(ollamaApiModuleId);
|
||||
vi.doMock(bundledProviderModules.ollamaApiModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(bundledProviderModules.ollamaApiModuleId);
|
||||
return {
|
||||
...actual,
|
||||
buildOllamaProvider: (...args: unknown[]) => buildOllamaProviderMock(...args),
|
||||
};
|
||||
});
|
||||
vi.doMock(vllmApiModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(vllmApiModuleId);
|
||||
vi.doMock(bundledProviderModules.vllmApiModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(bundledProviderModules.vllmApiModuleId);
|
||||
return {
|
||||
...actual,
|
||||
buildVllmProvider: (...args: unknown[]) => buildVllmProviderMock(...args),
|
||||
};
|
||||
});
|
||||
vi.doMock(sglangApiModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(sglangApiModuleId);
|
||||
vi.doMock(bundledProviderModules.sglangApiModuleId, async () => {
|
||||
const actual = await vi.importActual<object>(bundledProviderModules.sglangApiModuleId);
|
||||
return {
|
||||
...actual,
|
||||
buildSglangProvider: (...args: unknown[]) => buildSglangProviderMock(...args),
|
||||
@@ -186,27 +195,27 @@ function installDiscoveryHooks(state: DiscoveryState) {
|
||||
{ default: modelStudioPlugin },
|
||||
{ default: cloudflareAiGatewayPlugin },
|
||||
] = await Promise.all([
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({ pluginId: "github-copilot", artifactBasename: "index.js" }),
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
}>(bundledProviderModules.githubCopilotIndexModuleUrl),
|
||||
importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({ pluginId: "ollama", artifactBasename: "index.js" }),
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
}>(bundledProviderModules.ollamaIndexModuleUrl),
|
||||
importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({ pluginId: "vllm", artifactBasename: "index.js" }),
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
}>(bundledProviderModules.vllmIndexModuleUrl),
|
||||
importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({ pluginId: "sglang", artifactBasename: "index.js" }),
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
}>(bundledProviderModules.sglangIndexModuleUrl),
|
||||
importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({ pluginId: "minimax", artifactBasename: "index.js" }),
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
}>(bundledProviderModules.minimaxIndexModuleUrl),
|
||||
importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({ pluginId: "modelstudio", artifactBasename: "index.js" }),
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
}>(bundledProviderModules.modelStudioIndexModuleUrl),
|
||||
importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviders>[0];
|
||||
}>({ pluginId: "cloudflare-ai-gateway", artifactBasename: "index.js" }),
|
||||
}>(bundledProviderModules.cloudflareAiGatewayIndexModuleUrl),
|
||||
]);
|
||||
state.githubCopilotProvider = requireProvider(
|
||||
registerProviders(githubCopilotPlugin),
|
||||
|
||||
@@ -4,10 +4,6 @@ import path from "node:path";
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ProviderPlugin, ProviderRuntimeModel } from "../../../src/plugins/types.js";
|
||||
import {
|
||||
loadBundledPluginPublicSurfaceSync,
|
||||
resolveRelativeBundledPluginPublicModuleId,
|
||||
} from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
import {
|
||||
createProviderUsageFetch,
|
||||
makeResponse,
|
||||
@@ -24,6 +20,24 @@ const getOAuthProvidersMock = vi.hoisted(() =>
|
||||
{ id: "openai-codex", envApiKey: "OPENAI_API_KEY", oauthTokenEnv: "OPENAI_OAUTH_TOKEN" },
|
||||
]),
|
||||
);
|
||||
const providerRuntimeContractModules = vi.hoisted(() => ({
|
||||
anthropicIndexModuleUrl: new URL("../../../extensions/anthropic/index.ts", import.meta.url).href,
|
||||
githubCopilotIndexModuleUrl: new URL(
|
||||
"../../../extensions/github-copilot/index.ts",
|
||||
import.meta.url,
|
||||
).href,
|
||||
googleIndexModuleUrl: new URL("../../../extensions/google/index.ts", import.meta.url).href,
|
||||
openAIIndexModuleUrl: new URL("../../../extensions/openai/index.ts", import.meta.url).href,
|
||||
openAICodexProviderRuntimeModuleId: new URL(
|
||||
"../../../extensions/openai/openai-codex-provider.runtime.js",
|
||||
import.meta.url,
|
||||
).pathname,
|
||||
openRouterIndexModuleUrl: new URL("../../../extensions/openrouter/index.ts", import.meta.url)
|
||||
.href,
|
||||
veniceIndexModuleUrl: new URL("../../../extensions/venice/index.ts", import.meta.url).href,
|
||||
xAIIndexModuleUrl: new URL("../../../extensions/xai/index.ts", import.meta.url).href,
|
||||
zaiIndexModuleUrl: new URL("../../../extensions/zai/index.ts", import.meta.url).href,
|
||||
}));
|
||||
|
||||
vi.mock("@mariozechner/pi-ai/oauth", async () => {
|
||||
const actual = await vi.importActual<typeof import("@mariozechner/pi-ai/oauth")>(
|
||||
@@ -36,16 +50,14 @@ vi.mock("@mariozechner/pi-ai/oauth", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
const openAICodexProviderRuntimeModuleId = resolveRelativeBundledPluginPublicModuleId({
|
||||
fromModuleUrl: import.meta.url,
|
||||
pluginId: "openai",
|
||||
artifactBasename: "openai-codex-provider.runtime.js",
|
||||
});
|
||||
|
||||
vi.mock(openAICodexProviderRuntimeModuleId, () => ({
|
||||
vi.mock(providerRuntimeContractModules.openAICodexProviderRuntimeModuleId, () => ({
|
||||
refreshOpenAICodexToken: refreshOpenAICodexTokenMock,
|
||||
}));
|
||||
|
||||
async function importBundledProviderPlugin<T>(moduleUrl: string): Promise<T> {
|
||||
return (await import(`${moduleUrl}?t=${Date.now()}`)) as T;
|
||||
}
|
||||
|
||||
function createModel(overrides: Partial<ProviderRuntimeModel> & Pick<ProviderRuntimeModel, "id">) {
|
||||
return {
|
||||
id: overrides.id,
|
||||
@@ -74,96 +86,72 @@ const PROVIDER_RUNTIME_CONTRACT_FIXTURES: readonly ProviderRuntimeContractFixtur
|
||||
pluginId: "anthropic",
|
||||
name: "Anthropic",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "anthropic",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.anthropicIndexModuleUrl),
|
||||
},
|
||||
{
|
||||
providerIds: ["github-copilot"],
|
||||
pluginId: "github-copilot",
|
||||
name: "GitHub Copilot",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "github-copilot",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.githubCopilotIndexModuleUrl),
|
||||
},
|
||||
{
|
||||
providerIds: ["google", "google-gemini-cli"],
|
||||
pluginId: "google",
|
||||
name: "Google",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "google",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.googleIndexModuleUrl),
|
||||
},
|
||||
{
|
||||
providerIds: ["openai", "openai-codex"],
|
||||
pluginId: "openai",
|
||||
name: "OpenAI",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "openai",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.openAIIndexModuleUrl),
|
||||
},
|
||||
{
|
||||
providerIds: ["openrouter"],
|
||||
pluginId: "openrouter",
|
||||
name: "OpenRouter",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "openrouter",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.openRouterIndexModuleUrl),
|
||||
},
|
||||
{
|
||||
providerIds: ["venice"],
|
||||
pluginId: "venice",
|
||||
name: "Venice",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "venice",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.veniceIndexModuleUrl),
|
||||
},
|
||||
{
|
||||
providerIds: ["xai"],
|
||||
pluginId: "xai",
|
||||
name: "xAI",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "xai",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.xAIIndexModuleUrl),
|
||||
},
|
||||
{
|
||||
providerIds: ["zai"],
|
||||
pluginId: "zai",
|
||||
name: "Z.AI",
|
||||
load: async () =>
|
||||
loadBundledPluginPublicSurfaceSync<{
|
||||
await importBundledProviderPlugin<{
|
||||
default: Parameters<typeof registerProviderPlugin>[0]["plugin"];
|
||||
}>({
|
||||
pluginId: "zai",
|
||||
artifactBasename: "index.js",
|
||||
}),
|
||||
}>(providerRuntimeContractModules.zaiIndexModuleUrl),
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -736,6 +724,27 @@ export function describeXAIProviderRuntimeContract() {
|
||||
});
|
||||
});
|
||||
|
||||
it("owns downstream xai compat contributions for x-ai routed models", () => {
|
||||
const provider = requireProviderContractProvider("xai");
|
||||
|
||||
expect(
|
||||
provider.contributeResolvedModelCompat?.({
|
||||
provider: "openrouter",
|
||||
modelId: "x-ai/grok-4-1-fast",
|
||||
model: createModel({
|
||||
id: "x-ai/grok-4-1-fast",
|
||||
provider: "openrouter",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
}),
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
toolSchemaProfile: "xai",
|
||||
nativeWebSearchTool: true,
|
||||
toolCallArgumentsEncoding: "html-entities",
|
||||
});
|
||||
});
|
||||
|
||||
it("owns xai tool_stream defaults", () => {
|
||||
const provider = requireProviderContractProvider("xai");
|
||||
|
||||
@@ -804,25 +813,22 @@ export function describeOpenRouterProviderRuntimeContract() {
|
||||
describe("openrouter provider runtime contract", { timeout: CONTRACT_SETUP_TIMEOUT_MS }, () => {
|
||||
installRuntimeHooks();
|
||||
|
||||
it("owns xai downstream compat flags for x-ai routed models", () => {
|
||||
it("owns dynamic OpenRouter model defaults", () => {
|
||||
const provider = requireProviderContractProvider("openrouter");
|
||||
expect(
|
||||
provider.normalizeResolvedModel?.({
|
||||
provider: "openrouter",
|
||||
modelId: "x-ai/grok-4-1-fast",
|
||||
model: createModel({
|
||||
id: "x-ai/grok-4-1-fast",
|
||||
provider: "openrouter",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
}),
|
||||
}),
|
||||
).toMatchObject({
|
||||
compat: {
|
||||
toolSchemaProfile: "xai",
|
||||
nativeWebSearchTool: true,
|
||||
toolCallArgumentsEncoding: "html-entities",
|
||||
},
|
||||
const model = provider.resolveDynamicModel?.({
|
||||
provider: "openrouter",
|
||||
modelId: "x-ai/grok-4-1-fast",
|
||||
modelRegistry: {
|
||||
find: () => null,
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(model).toMatchObject({
|
||||
id: "x-ai/grok-4-1-fast",
|
||||
provider: "openrouter",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
maxTokens: 8192,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user