mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
refactor(deadcode): tighten extension root modeling (#108538)
This commit is contained in:
+29
-5
@@ -113,9 +113,9 @@ const rootBundledPluginRuntimeDependencies = [
|
||||
"tokenjuice",
|
||||
] as const;
|
||||
|
||||
function strictBundledPluginWorkspace() {
|
||||
function strictBundledPluginWorkspace(extraEntries: readonly string[] = []) {
|
||||
return {
|
||||
entry: strictBundledPluginEntries,
|
||||
entry: [...strictBundledPluginEntries, ...extraEntries],
|
||||
project: ["*.ts!", "src/**/*.{js,mjs,ts}!"],
|
||||
ignoreDependencies: bundledPluginIgnoredRuntimeDependencies,
|
||||
} as const;
|
||||
@@ -373,19 +373,41 @@ const config = {
|
||||
project: ["index.js!", "scripts/**/*.js!"],
|
||||
},
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/amazon-bedrock-mantle`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/amazon-bedrock`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/acpx`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/azure-speech`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/browser`]: strictBundledPluginWorkspace([
|
||||
// Core and plugin-SDK facades resolve these shipped Browser surfaces by basename.
|
||||
"browser-control-auth.ts!",
|
||||
"browser-config.ts!",
|
||||
"browser-doctor.ts!",
|
||||
"browser-host-inspection.ts!",
|
||||
"browser-maintenance.ts!",
|
||||
"browser-profiles.ts!",
|
||||
]),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/cloudflare-ai-gateway`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/chutes`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/clawrouter`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/cohere`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/comfy`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/copilot`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepgram`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/deepinfra`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/discord`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/elevenlabs`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/featherless`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/fal`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/fireworks`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/google`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/huggingface`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/kilocode`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/kimi-coding`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/microsoft`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/memory-core`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/memory-lancedb`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/microsoft-foundry`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/migrate-claude`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/migrate-hermes`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/minimax`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/mistral`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/moonshot`]: strictBundledPluginWorkspace(),
|
||||
@@ -393,15 +415,17 @@ const config = {
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/pixverse`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/qianfan`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/qwen`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/qa-lab`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/senseaudio`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/tavily`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/tencent`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/vllm`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/voyage`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/xiaomi`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/xai`]: strictBundledPluginWorkspace(),
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/llama-cpp`]: {
|
||||
entry: bundledPluginEntries,
|
||||
project: ["index.ts!", "src/**/*.{js,mjs,ts}!"],
|
||||
project: ["*.ts!", "src/**/*.{js,mjs,ts}!"],
|
||||
ignoreDependencies: [
|
||||
// The provider resolves node-llama-cpp from its own package at runtime
|
||||
// so local embeddings use the plugin-owned native dependency.
|
||||
@@ -416,14 +440,14 @@ const config = {
|
||||
// public surface, so its exports are intentional even where the channel
|
||||
// consumes only a subset.
|
||||
entry: [...bundledPluginEntries, "protocol/index.ts!", "protocol/node.ts!"],
|
||||
project: ["index.ts!", "src/**/*.{js,mjs,ts}!", "protocol/**/*.ts!"],
|
||||
project: ["*.ts!", "src/**/*.{js,mjs,ts}!", "protocol/**/*.ts!"],
|
||||
ignoreDependencies: bundledPluginIgnoredRuntimeDependencies,
|
||||
},
|
||||
[`${BUNDLED_PLUGIN_ROOT_DIR}/*`]: {
|
||||
// Bundled plugins often load their public surface via string specifiers in
|
||||
// `index.ts` contracts, so Knip needs these convention-based entry files.
|
||||
entry: bundledPluginEntries,
|
||||
project: ["index.ts!", "src/**/*.{js,mjs,ts}!"],
|
||||
project: ["*.ts!", "src/**/*.{js,mjs,ts}!"],
|
||||
ignoreDependencies: bundledPluginIgnoredRuntimeDependencies,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Amazon Bedrock tests cover embedding provider plugin behavior.
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { testing, hasAwsCredentials } from "./embedding-provider.js";
|
||||
import { hasAwsCredentials } from "./embedding-provider.js";
|
||||
import { embeddingTesting as testing } from "./test-support.js";
|
||||
|
||||
describe("hasAwsCredentials", () => {
|
||||
it("accepts static AWS key credentials without loading the credential chain", async () => {
|
||||
|
||||
@@ -288,12 +288,16 @@ function parseCohereBatch(family: Family, raw: string): number[][] {
|
||||
return asNumberArrayBatch(embeddings);
|
||||
}
|
||||
|
||||
export const testing = {
|
||||
const testing = {
|
||||
parseCohereBatch,
|
||||
parseSingle,
|
||||
stripInferenceProfilePrefix,
|
||||
};
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
Reflect.set(globalThis, Symbol.for("openclaw.amazonBedrockEmbeddingTestApi"), testing);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -449,4 +453,3 @@ export async function hasAwsCredentials(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export { testing as __testing };
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import { BedrockRuntimeClient, ConversationRole } from "@aws-sdk/client-bedrock-runtime";
|
||||
import { onLlmRequestActivity } from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { streamBedrock, streamSimpleBedrock, testing } from "./stream.runtime.js";
|
||||
import { streamBedrock, streamSimpleBedrock } from "./stream.runtime.js";
|
||||
import { streamTesting as testing } from "./test-support.js";
|
||||
|
||||
function bedrockModel(overrides: Record<string, unknown>) {
|
||||
return {
|
||||
|
||||
@@ -1173,7 +1173,7 @@ function createImageBlock(mimeType: string, data: string) {
|
||||
}
|
||||
|
||||
/** Test-only hooks for Bedrock runtime conversion and endpoint policy. */
|
||||
export const testing = {
|
||||
const testing = {
|
||||
buildAdditionalModelRequestFields,
|
||||
convertMessages,
|
||||
getConfiguredBedrockRegion,
|
||||
@@ -1182,4 +1182,8 @@ export const testing = {
|
||||
resolveSimpleBedrockOptions,
|
||||
shouldUseExplicitBedrockEndpoint,
|
||||
};
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
Reflect.set(globalThis, Symbol.for("openclaw.amazonBedrockStreamTestApi"), testing);
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
type BedrockEmbeddingTestApi = {
|
||||
parseCohereBatch: (family: string, raw: string) => number[][];
|
||||
parseSingle: (family: string, raw: string) => number[];
|
||||
stripInferenceProfilePrefix: (modelId: string) => string;
|
||||
};
|
||||
|
||||
type BedrockStreamTestApi = {
|
||||
buildAdditionalModelRequestFields: (
|
||||
model: unknown,
|
||||
options: Record<string, unknown>,
|
||||
) => Record<string, unknown> | undefined;
|
||||
convertMessages: (...args: unknown[]) => Array<{ content?: unknown; role?: unknown }>;
|
||||
getConfiguredBedrockRegion: (params: unknown) => string | undefined;
|
||||
hasConfiguredBedrockProfile: (params: unknown) => boolean;
|
||||
mapThinkingLevelToEffort: (model: unknown, level: unknown) => string;
|
||||
resolveSimpleBedrockOptions: (
|
||||
model: unknown,
|
||||
options: Record<string, unknown>,
|
||||
) => Record<string, unknown>;
|
||||
shouldUseExplicitBedrockEndpoint: (...args: unknown[]) => boolean;
|
||||
};
|
||||
|
||||
function requireTestApi(key: string): object {
|
||||
const api = Reflect.get(globalThis, Symbol.for(key));
|
||||
if (!api) {
|
||||
throw new Error(`${key} is unavailable`);
|
||||
}
|
||||
return api as object;
|
||||
}
|
||||
|
||||
function lazyTestApi(key: string): object {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_target, property) => Reflect.get(requireTestApi(key), property),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const embeddingTesting = lazyTestApi(
|
||||
"openclaw.amazonBedrockEmbeddingTestApi",
|
||||
) as BedrockEmbeddingTestApi;
|
||||
export const streamTesting = lazyTestApi(
|
||||
"openclaw.amazonBedrockStreamTestApi",
|
||||
) as BedrockStreamTestApi;
|
||||
@@ -1,10 +1,10 @@
|
||||
// Chutes tests cover models plugin behavior.
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildChutesModelDefinition,
|
||||
CHUTES_MODEL_CATALOG,
|
||||
clearChutesModelCacheForTests,
|
||||
discoverChutesModels,
|
||||
} from "./models.js";
|
||||
|
||||
@@ -86,7 +86,7 @@ function requireChutesModel(
|
||||
|
||||
describe("chutes-models", () => {
|
||||
beforeEach(() => {
|
||||
clearChutesModelCacheForTests();
|
||||
clearLiveCatalogCacheForTests();
|
||||
});
|
||||
|
||||
it("buildChutesModelDefinition returns config with required fields", () => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Chutes model catalog, static model definitions, and dynamic model discovery.
|
||||
*/
|
||||
import {
|
||||
clearLiveCatalogCacheForTests,
|
||||
getCachedLiveProviderModelRows,
|
||||
LiveModelCatalogHttpError,
|
||||
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
@@ -489,11 +488,6 @@ interface ChutesModelEntry {
|
||||
|
||||
const CACHE_TTL = 5 * 60 * 1000;
|
||||
|
||||
/** Clears the dynamic Chutes model discovery cache for tests. */
|
||||
export function clearChutesModelCacheForTests(): void {
|
||||
clearLiveCatalogCacheForTests();
|
||||
}
|
||||
|
||||
async function fetchChutesModelRows(accessToken?: string): Promise<readonly unknown[]> {
|
||||
return await getCachedLiveProviderModelRows({
|
||||
providerId: "chutes",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createServer, type Server } from "node:http";
|
||||
import { connect, type AddressInfo } from "node:net";
|
||||
import type { Duplex } from "node:stream";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi, type MockedFunction } from "vitest";
|
||||
import { fetchClawRouterUsage, type ClawRouterUsageFetchGuard } from "./usage.js";
|
||||
import { fetchClawRouterUsage } from "./usage.js";
|
||||
|
||||
const runningServers: Server[] = [];
|
||||
const runningSockets = new Set<Duplex>();
|
||||
@@ -107,6 +107,10 @@ afterEach(async () => {
|
||||
savedProxyEnv.clear();
|
||||
});
|
||||
|
||||
type ClawRouterUsageFetchGuard = NonNullable<
|
||||
Parameters<typeof fetchClawRouterUsage>[0]["fetchGuard"]
|
||||
>;
|
||||
|
||||
function mockFetchGuard(response: Response): MockedFunction<ClawRouterUsageFetchGuard> {
|
||||
return vi.fn(async ({ url }) => ({
|
||||
response,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { normalizeClawRouterRootUrl } from "./provider-catalog.js";
|
||||
|
||||
const CLAWROUTER_USAGE_RESPONSE_MAX_BYTES = 1024 * 1024;
|
||||
|
||||
export type ClawRouterUsageFetchGuard = typeof fetchWithSsrFGuard;
|
||||
type ClawRouterUsageFetchGuard = typeof fetchWithSsrFGuard;
|
||||
|
||||
type ClawRouterBudget = {
|
||||
configured?: unknown;
|
||||
|
||||
@@ -6,7 +6,8 @@ import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
||||
import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import plugin from "./index.js";
|
||||
import { getComfyConfig, isComfyCapabilityConfigured } from "./workflow-runtime.js";
|
||||
import { getComfyConfigForTesting } from "./test-support.js";
|
||||
import { isComfyCapabilityConfigured } from "./workflow-runtime.js";
|
||||
|
||||
const LIVE =
|
||||
isLiveTestEnabled(["COMFY_LIVE_TEST"]) && (process.env.COMFY_LIVE_TEST ?? "").trim() === "1";
|
||||
@@ -123,7 +124,7 @@ describeLive("comfy live", () => {
|
||||
);
|
||||
|
||||
it("documents the effective comfy config shape for live debugging", () => {
|
||||
const comfyConfig = getComfyConfig(cfg as never);
|
||||
const comfyConfig = getComfyConfigForTesting(cfg as never);
|
||||
expect(typeof comfyConfig).toBe("object");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,7 @@ import type { LookupAddress } from "node:dns";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
setComfyFetchGuardForTesting,
|
||||
buildComfyImageGenerationProvider,
|
||||
} from "./image-generation-provider.js";
|
||||
import { buildComfyImageGenerationProvider } from "./image-generation-provider.js";
|
||||
import {
|
||||
buildComfyConfig,
|
||||
buildLegacyComfyConfig,
|
||||
@@ -14,6 +11,7 @@ import {
|
||||
mockComfyProviderApiKey,
|
||||
parseComfyJsonBody,
|
||||
} from "./test-helpers.js";
|
||||
import { setComfyFetchGuardForTesting } from "./test-support.js";
|
||||
|
||||
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
|
||||
@@ -5,13 +5,10 @@ import type {
|
||||
} from "openclaw/plugin-sdk/image-generation";
|
||||
import {
|
||||
DEFAULT_COMFY_MODEL,
|
||||
setComfyFetchGuardForTesting,
|
||||
isComfyCapabilityConfigured,
|
||||
runComfyWorkflow,
|
||||
} from "./workflow-runtime.js";
|
||||
|
||||
export { setComfyFetchGuardForTesting };
|
||||
|
||||
export function buildComfyImageGenerationProvider(): ImageGenerationProvider {
|
||||
return {
|
||||
id: "comfy",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { expectExplicitMusicGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { buildComfyMusicGenerationProvider } from "./music-generation-provider.js";
|
||||
import { setComfyFetchGuardForTesting } from "./workflow-runtime.js";
|
||||
import { setComfyFetchGuardForTesting } from "./test-support.js";
|
||||
|
||||
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
|
||||
type ComfyTestApi = {
|
||||
getConfig: (cfg?: unknown) => Record<string, unknown>;
|
||||
setFetchGuard: (impl: typeof fetchWithSsrFGuard | null) => void;
|
||||
};
|
||||
|
||||
function getComfyTestApi(): ComfyTestApi {
|
||||
const api = Reflect.get(globalThis, Symbol.for("openclaw.comfyTestApi"));
|
||||
if (!api) {
|
||||
throw new Error("Comfy test API is unavailable");
|
||||
}
|
||||
return api as ComfyTestApi;
|
||||
}
|
||||
|
||||
export function setComfyFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
getComfyTestApi().setFetchGuard(impl);
|
||||
}
|
||||
|
||||
export function getComfyConfigForTesting(cfg?: unknown): Record<string, unknown> {
|
||||
return getComfyTestApi().getConfig(cfg);
|
||||
}
|
||||
@@ -7,10 +7,8 @@ import {
|
||||
mockComfyProviderApiKey,
|
||||
parseComfyJsonBody,
|
||||
} from "./test-helpers.js";
|
||||
import {
|
||||
setComfyFetchGuardForTesting,
|
||||
buildComfyVideoGenerationProvider,
|
||||
} from "./video-generation-provider.js";
|
||||
import { setComfyFetchGuardForTesting } from "./test-support.js";
|
||||
import { buildComfyVideoGenerationProvider } from "./video-generation-provider.js";
|
||||
|
||||
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
|
||||
@@ -6,13 +6,10 @@ import type {
|
||||
} from "openclaw/plugin-sdk/video-generation";
|
||||
import {
|
||||
DEFAULT_COMFY_MODEL,
|
||||
setComfyFetchGuardForTesting,
|
||||
isComfyCapabilityConfigured,
|
||||
runComfyWorkflow,
|
||||
} from "./workflow-runtime.js";
|
||||
|
||||
export { setComfyFetchGuardForTesting };
|
||||
|
||||
function toComfyInputImage(inputImage?: VideoGenerationSourceAsset) {
|
||||
if (!inputImage) {
|
||||
return undefined;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Comfy tests cover workflow-runtime bounded-read delegation.
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { readJsonResponseForTest, setComfyFetchGuardForTesting } from "./workflow-runtime.js";
|
||||
import { setComfyFetchGuardForTesting } from "./test-support.js";
|
||||
import { readJsonResponseForTest } from "./workflow-runtime.js";
|
||||
|
||||
describe("readJsonResponse bounded read (readProviderJsonResponse delegation)", () => {
|
||||
const fetchMock = vi.fn();
|
||||
|
||||
@@ -114,10 +114,17 @@ type ComfyWorkflowResult = {
|
||||
|
||||
let comfyFetchGuard = fetchWithSsrFGuard;
|
||||
|
||||
export function setComfyFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
function setComfyFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
comfyFetchGuard = impl ?? fetchWithSsrFGuard;
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
Reflect.set(globalThis, Symbol.for("openclaw.comfyTestApi"), {
|
||||
getConfig: getComfyConfig,
|
||||
setFetchGuard: setComfyFetchGuardForTesting,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveComfyGeneratedOutputMaxBytes(params: {
|
||||
cfg: OpenClawConfig;
|
||||
capability: ComfyCapability;
|
||||
@@ -140,7 +147,7 @@ function readConfigInteger(config: ComfyProviderConfig, key: string): number | u
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
export function getComfyConfig(cfg?: OpenClawConfig): ComfyProviderConfig {
|
||||
function getComfyConfig(cfg?: OpenClawConfig): ComfyProviderConfig {
|
||||
const pluginConfig = cfg?.plugins?.entries?.comfy?.config;
|
||||
if (isRecord(pluginConfig)) {
|
||||
return pluginConfig;
|
||||
|
||||
@@ -12,10 +12,9 @@ import {
|
||||
} from "openclaw/plugin-sdk/hook-runtime";
|
||||
import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CopilotClientPool } from "./harness.js";
|
||||
import { createCopilotAgentHarness, type CopilotSessionBinding } from "./harness.js";
|
||||
import type { resolvePoolAcquire } from "./src/attempt.js";
|
||||
import type { PoolKey } from "./src/runtime.js";
|
||||
import type { CopilotClientPool, PoolKey } from "./src/runtime.js";
|
||||
|
||||
const COPILOT_BYOK_PROVIDER_ERROR =
|
||||
"[copilot-attempt] BYOK requires an OpenAI-compatible or Anthropic model api and a non-empty baseUrl";
|
||||
|
||||
@@ -31,8 +31,6 @@ import type {
|
||||
PoolKey,
|
||||
} from "./src/runtime.js";
|
||||
|
||||
export type { CopilotClientPool, CopilotClientPoolOptions };
|
||||
|
||||
const COPILOT_PROVIDER_IDS: ReadonlySet<string> = new Set(["github-copilot"]);
|
||||
|
||||
interface CreateCopilotAgentHarnessOptions {
|
||||
|
||||
@@ -6,7 +6,8 @@ import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
||||
import type { AgentHarnessAttemptParams } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-env";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createCopilotAgentHarness, type CopilotClientPool } from "../harness.js";
|
||||
import { createCopilotAgentHarness } from "../harness.js";
|
||||
import type { CopilotClientPool } from "./runtime.js";
|
||||
|
||||
const liveToolState = vi.hoisted(() => ({
|
||||
calls: [] as string[],
|
||||
|
||||
@@ -3,14 +3,14 @@ import {
|
||||
createCapturedPluginRegistration,
|
||||
registerSingleProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import type { ProviderCatalogContext } from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import deepinfraPlugin from "./index.js";
|
||||
import {
|
||||
DEEPINFRA_MODEL_CATALOG,
|
||||
DEEPINFRA_MODELS_URL,
|
||||
resetDeepInfraModelCacheForTest,
|
||||
} from "./provider-models.js";
|
||||
import { DEEPINFRA_MODEL_CATALOG } from "./provider-models.js";
|
||||
|
||||
const DEEPINFRA_MODELS_URL =
|
||||
"https://api.deepinfra.com/v1/openai/models?sort_by=openclaw&filter=with_meta";
|
||||
|
||||
function buildSyntheticDeepInfraEntries(count: number) {
|
||||
return Array.from({ length: count }, (_unused, index) => ({
|
||||
@@ -83,7 +83,7 @@ async function withLiveDiscoveryTestEnv(
|
||||
|
||||
describe("deepinfra augmentModelCatalog", () => {
|
||||
it("returns the discovered (static under VITEST) catalog when nothing is configured", async () => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
const provider = await registerSingleProviderPlugin(deepinfraPlugin);
|
||||
|
||||
const entries = (await provider.augmentModelCatalog?.({ entries: [] } as never)) ?? [];
|
||||
@@ -97,7 +97,7 @@ describe("deepinfra augmentModelCatalog", () => {
|
||||
});
|
||||
|
||||
it("preserves configured entries and appends discovered entries that are not already configured", async () => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
const provider = await registerSingleProviderPlugin(deepinfraPlugin);
|
||||
|
||||
const entries =
|
||||
@@ -129,7 +129,7 @@ describe("deepinfra augmentModelCatalog", () => {
|
||||
});
|
||||
|
||||
it("uses config-backed API keys to enable live model catalog augmentation", async () => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
const mockFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue(jsonResponse({ data: [makeAgentModelEntry("config/live-model")] }));
|
||||
@@ -157,7 +157,7 @@ describe("deepinfra augmentModelCatalog", () => {
|
||||
});
|
||||
|
||||
it("still runs live discovery when ctx.entries includes custom DeepInfra rows", async () => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
const mockFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue(jsonResponse({ data: [makeAgentModelEntry("custom/live-model")] }));
|
||||
@@ -205,7 +205,7 @@ describe("deepinfra augmentModelCatalog", () => {
|
||||
});
|
||||
|
||||
it("still fetches when ctx.entries has exactly the static catalog length (static-fallback case)", async () => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
const provider = await registerSingleProviderPlugin(deepinfraPlugin);
|
||||
|
||||
const entries =
|
||||
@@ -235,7 +235,7 @@ describe("deepinfra capability registration", () => {
|
||||
});
|
||||
|
||||
it("uses profile-resolved API keys for live text catalog discovery", async () => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
const mockFetch = vi.fn().mockResolvedValue(jsonResponse({ data: [makeAgentModelEntry()] }));
|
||||
const captured = createCapturedPluginRegistration();
|
||||
deepinfraPlugin.register(captured.api);
|
||||
|
||||
@@ -4,10 +4,7 @@ import {
|
||||
describeImageWithModel,
|
||||
} from "openclaw/plugin-sdk/media-understanding";
|
||||
import { afterAll, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
deepinfraMediaUnderstandingProvider,
|
||||
transcribeDeepInfraAudio,
|
||||
} from "./media-understanding-provider.js";
|
||||
import { deepinfraMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
|
||||
const { transcribeOpenAiCompatibleAudioMock } = vi.hoisted(() => ({
|
||||
transcribeOpenAiCompatibleAudioMock: vi.fn(async () => ({ text: "hello", model: "whisper" })),
|
||||
@@ -41,7 +38,7 @@ describe("deepinfra media understanding provider", () => {
|
||||
image: 45,
|
||||
audio: 45,
|
||||
},
|
||||
transcribeAudio: transcribeDeepInfraAudio,
|
||||
transcribeAudio: expect.any(Function),
|
||||
describeImage: describeImageWithModel,
|
||||
describeImages: describeImagesWithModel,
|
||||
});
|
||||
@@ -49,7 +46,7 @@ describe("deepinfra media understanding provider", () => {
|
||||
|
||||
it("routes audio transcription through the OpenAI-compatible DeepInfra endpoint", async () => {
|
||||
const buffer = Buffer.from("audio");
|
||||
const result = await transcribeDeepInfraAudio({
|
||||
const result = await deepinfraMediaUnderstandingProvider.transcribeAudio!({
|
||||
buffer,
|
||||
fileName: "clip.mp3",
|
||||
apiKey: "deepinfra-key",
|
||||
|
||||
@@ -21,7 +21,7 @@ function resolveDefault(
|
||||
return first ?? fallback[0] ?? "";
|
||||
}
|
||||
|
||||
export async function transcribeDeepInfraAudio(params: AudioTranscriptionRequest) {
|
||||
async function transcribeDeepInfraAudio(params: AudioTranscriptionRequest) {
|
||||
return await transcribeOpenAiCompatibleAudio({
|
||||
...params,
|
||||
provider: "deepinfra",
|
||||
|
||||
@@ -9,12 +9,8 @@ import {
|
||||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { captureEnv } from "openclaw/plugin-sdk/test-env";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
applyDeepInfraConfig,
|
||||
DEEPINFRA_BASE_URL,
|
||||
DEEPINFRA_DEFAULT_MODEL_REF,
|
||||
} from "./onboard.js";
|
||||
import { DEEPINFRA_DEFAULT_MODEL_ID } from "./provider-models.js";
|
||||
import { applyDeepInfraConfig } from "./onboard.js";
|
||||
import { DEEPINFRA_BASE_URL, DEEPINFRA_DEFAULT_MODEL_REF } from "./provider-models.js";
|
||||
|
||||
const { resolveEnvApiKey } = providerAuth;
|
||||
|
||||
@@ -29,10 +25,6 @@ describe("DeepInfra provider config", () => {
|
||||
it("DEEPINFRA_DEFAULT_MODEL_REF includes provider prefix", () => {
|
||||
expect(DEEPINFRA_DEFAULT_MODEL_REF).toBe("deepinfra/deepseek-ai/DeepSeek-V4-Flash");
|
||||
});
|
||||
|
||||
it("DEEPINFRA_DEFAULT_MODEL_ID is deepseek-ai/DeepSeek-V4-Flash", () => {
|
||||
expect(DEEPINFRA_DEFAULT_MODEL_ID).toBe("deepseek-ai/DeepSeek-V4-Flash");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyDeepInfraConfig", () => {
|
||||
|
||||
@@ -3,9 +3,7 @@ import {
|
||||
applyAgentDefaultModelPrimary,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { DEEPINFRA_BASE_URL, DEEPINFRA_DEFAULT_MODEL_REF } from "./provider-models.js";
|
||||
|
||||
export { DEEPINFRA_BASE_URL, DEEPINFRA_DEFAULT_MODEL_REF };
|
||||
import { DEEPINFRA_DEFAULT_MODEL_REF } from "./provider-models.js";
|
||||
|
||||
export function applyDeepInfraConfig(
|
||||
cfg: OpenClawConfig,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
// Deepinfra tests cover provider models plugin behavior.
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
@@ -13,17 +14,18 @@ vi.mock("openclaw/plugin-sdk/provider-auth", async () => {
|
||||
});
|
||||
|
||||
import {
|
||||
DEEPINFRA_MODELS_URL,
|
||||
DEEPINFRA_DEFAULT_MODEL_REF,
|
||||
DEEPINFRA_MODEL_CATALOG,
|
||||
discoverDeepInfraModels,
|
||||
discoverDeepInfraSurfaces,
|
||||
hasDeepInfraApiKey,
|
||||
resetDeepInfraModelCacheForTest,
|
||||
} from "./provider-models.js";
|
||||
|
||||
const DEEPINFRA_MODELS_URL =
|
||||
"https://api.deepinfra.com/v1/openai/models?sort_by=openclaw&filter=with_meta";
|
||||
|
||||
beforeEach(() => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
isProviderApiKeyConfiguredMock.mockReset();
|
||||
isProviderApiKeyConfiguredMock.mockReturnValue(false);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Deepinfra provider module implements model/runtime integration.
|
||||
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
|
||||
import {
|
||||
clearLiveCatalogCacheForTests,
|
||||
getCachedLiveProviderModelRows,
|
||||
LiveModelCatalogHttpError,
|
||||
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
@@ -20,9 +19,9 @@ const DEEPINFRA_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
|
||||
});
|
||||
|
||||
export const DEEPINFRA_BASE_URL = DEEPINFRA_MANIFEST_PROVIDER.baseUrl;
|
||||
export const DEEPINFRA_MODELS_URL = `${DEEPINFRA_BASE_URL}/models?sort_by=openclaw&filter=with_meta`;
|
||||
const DEEPINFRA_MODELS_URL = `${DEEPINFRA_BASE_URL}/models?sort_by=openclaw&filter=with_meta`;
|
||||
|
||||
export const DEEPINFRA_DEFAULT_MODEL_ID = "deepseek-ai/DeepSeek-V4-Flash";
|
||||
const DEEPINFRA_DEFAULT_MODEL_ID = "deepseek-ai/DeepSeek-V4-Flash";
|
||||
export const DEEPINFRA_DEFAULT_MODEL_REF = `deepinfra/${DEEPINFRA_DEFAULT_MODEL_ID}`;
|
||||
|
||||
const DEEPINFRA_DEFAULT_CONTEXT_WINDOW = 128000;
|
||||
@@ -97,10 +96,6 @@ interface DeepInfraDiscoveredCatalog {
|
||||
live: boolean;
|
||||
}
|
||||
|
||||
export function resetDeepInfraModelCacheForTest(): void {
|
||||
clearLiveCatalogCacheForTests();
|
||||
}
|
||||
|
||||
const SURFACE_FOR_TAG: Record<string, DeepInfraSurface> = {
|
||||
chat: "chat",
|
||||
vlm: "vlm",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
// Deepinfra tests cover surface model catalogs plugin behavior.
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resetDeepInfraModelCacheForTest } from "./provider-models.js";
|
||||
import {
|
||||
listDeepInfraImageGenCatalog,
|
||||
listDeepInfraVideoGenCatalog,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "./surface-model-catalogs.js";
|
||||
|
||||
beforeEach(() => {
|
||||
resetDeepInfraModelCacheForTest();
|
||||
clearLiveCatalogCacheForTests();
|
||||
});
|
||||
|
||||
function makeCtx(overrides: Partial<Parameters<typeof listDeepInfraImageGenCatalog>[0]> = {}) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Discord plugin module implements runtime api.threads behavior.
|
||||
export {
|
||||
testing as __testing,
|
||||
testing,
|
||||
autoBindSpawnedDiscordSubagent,
|
||||
createNoopThreadBindingManager,
|
||||
|
||||
@@ -6,10 +6,8 @@ const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
}));
|
||||
|
||||
import {
|
||||
setFalFetchGuardForTesting,
|
||||
buildFalImageGenerationProvider,
|
||||
} from "./image-generation-provider.js";
|
||||
import { buildFalImageGenerationProvider } from "./image-generation-provider.js";
|
||||
import { setFalFetchGuardForTesting } from "./test-support.js";
|
||||
|
||||
function expectFalJsonPost(params: { call: number; url: string; body: Record<string, unknown> }) {
|
||||
const request = fetchWithSsrFGuardMock.mock.calls[params.call - 1]?.[0];
|
||||
|
||||
@@ -155,10 +155,16 @@ type FalNetworkPolicy = {
|
||||
|
||||
let falFetchGuard = fetchWithSsrFGuard;
|
||||
|
||||
export function setFalFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
function setFalFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
falFetchGuard = impl ?? fetchWithSsrFGuard;
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
const key = Symbol.for("openclaw.falTestApi");
|
||||
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
|
||||
Reflect.set(globalThis, key, { ...api, setImageFetchGuard: setFalFetchGuardForTesting });
|
||||
}
|
||||
|
||||
function matchesTrustedHostSuffix(hostname: string, trustedSuffix: string): boolean {
|
||||
const normalizedHost = normalizeLowercaseStringOrEmpty(hostname);
|
||||
const normalizedSuffix = normalizeLowercaseStringOrEmpty(trustedSuffix);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
|
||||
type FalTestApi = {
|
||||
setImageFetchGuard: (impl: typeof fetchWithSsrFGuard | null) => void;
|
||||
setVideoFetchGuard: (impl: typeof fetchWithSsrFGuard | null) => void;
|
||||
};
|
||||
|
||||
function getFalTestApi(): FalTestApi {
|
||||
const api = Reflect.get(globalThis, Symbol.for("openclaw.falTestApi"));
|
||||
if (!api) {
|
||||
throw new Error("Fal test API is unavailable");
|
||||
}
|
||||
return api as FalTestApi;
|
||||
}
|
||||
|
||||
export function setFalFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
getFalTestApi().setImageFetchGuard(impl);
|
||||
}
|
||||
|
||||
export function setFalVideoFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
getFalTestApi().setVideoFetchGuard(impl);
|
||||
}
|
||||
@@ -4,10 +4,8 @@ import * as providerAuth from "openclaw/plugin-sdk/provider-auth-runtime";
|
||||
import * as providerHttp from "openclaw/plugin-sdk/provider-http";
|
||||
import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
setFalVideoFetchGuardForTesting,
|
||||
buildFalVideoGenerationProvider,
|
||||
} from "./video-generation-provider.js";
|
||||
import { setFalVideoFetchGuardForTesting } from "./test-support.js";
|
||||
import { buildFalVideoGenerationProvider } from "./video-generation-provider.js";
|
||||
|
||||
function createMockRequestConfig() {
|
||||
return {} as ReturnType<typeof providerHttp.resolveProviderHttpRequestConfig>["requestConfig"];
|
||||
|
||||
@@ -99,10 +99,16 @@ type FalQueueResponse = {
|
||||
|
||||
let falFetchGuard = fetchWithSsrFGuard;
|
||||
|
||||
export function setFalVideoFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
function setFalVideoFetchGuardForTesting(impl: typeof fetchWithSsrFGuard | null): void {
|
||||
falFetchGuard = impl ?? fetchWithSsrFGuard;
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
const key = Symbol.for("openclaw.falTestApi");
|
||||
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
|
||||
Reflect.set(globalThis, key, { ...api, setVideoFetchGuard: setFalVideoFetchGuardForTesting });
|
||||
}
|
||||
|
||||
function normalizeFalVideoUrl(value: unknown): string | undefined {
|
||||
const normalized = normalizeOptionalString(value);
|
||||
if (!normalized && value !== undefined && value !== null) {
|
||||
|
||||
@@ -35,7 +35,7 @@ import memoryPlugin, {
|
||||
shouldCapture,
|
||||
testing,
|
||||
} from "./index.js";
|
||||
import { createLanceDbRuntimeLoader } from "./lancedb-runtime.js";
|
||||
import { createLanceDbRuntimeLoader } from "./lancedb-runtime.test-support.js";
|
||||
import { installTmpDirHarness } from "./test-helpers.js";
|
||||
|
||||
const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "test-key";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
type LanceDbModule = typeof import("@lancedb/lancedb");
|
||||
|
||||
type LanceDbRuntimeTestApi = {
|
||||
createRuntimeLoader: (overrides?: {
|
||||
platform?: NodeJS.Platform;
|
||||
arch?: NodeJS.Architecture;
|
||||
importBundled?: () => Promise<LanceDbModule>;
|
||||
}) => { load: () => Promise<LanceDbModule> };
|
||||
};
|
||||
|
||||
const api = Reflect.get(globalThis, Symbol.for("openclaw.memoryLanceDbRuntimeTestApi"));
|
||||
if (!api) {
|
||||
throw new Error("Memory LanceDB runtime test API is unavailable");
|
||||
}
|
||||
|
||||
export const createLanceDbRuntimeLoader = (api as LanceDbRuntimeTestApi).createRuntimeLoader;
|
||||
@@ -1,7 +1,7 @@
|
||||
// Memory Lancedb plugin module implements lancedb runtime behavior.
|
||||
type LanceDbModule = typeof import("@lancedb/lancedb");
|
||||
|
||||
export type LanceDbRuntimeLogger = {
|
||||
type LanceDbRuntimeLogger = {
|
||||
info?: (message: string) => void;
|
||||
warn?: (message: string) => void;
|
||||
};
|
||||
@@ -38,7 +38,7 @@ function buildUnsupportedNativePlatformMessage(params: {
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function createLanceDbRuntimeLoader(overrides: Partial<LanceDbRuntimeLoaderDeps> = {}): {
|
||||
function createLanceDbRuntimeLoader(overrides: Partial<LanceDbRuntimeLoaderDeps> = {}): {
|
||||
load: (loggerInstance?: LanceDbRuntimeLogger) => Promise<LanceDbModule>;
|
||||
} {
|
||||
const deps: LanceDbRuntimeLoaderDeps = {
|
||||
@@ -71,6 +71,12 @@ export function createLanceDbRuntimeLoader(overrides: Partial<LanceDbRuntimeLoad
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
Reflect.set(globalThis, Symbol.for("openclaw.memoryLanceDbRuntimeTestApi"), {
|
||||
createRuntimeLoader: createLanceDbRuntimeLoader,
|
||||
});
|
||||
}
|
||||
|
||||
const defaultLoader = createLanceDbRuntimeLoader();
|
||||
|
||||
export async function loadLanceDbModule(logger?: LanceDbRuntimeLogger): Promise<LanceDbModule> {
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
resolveFoundryApi,
|
||||
} from "./shared.js";
|
||||
|
||||
export function shouldTestFoundryTextConnection(params: {
|
||||
function shouldTestFoundryTextConnection(params: {
|
||||
modelId: string;
|
||||
modelNameHint?: string | null;
|
||||
}): boolean {
|
||||
@@ -44,6 +44,12 @@ export function shouldTestFoundryTextConnection(params: {
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
const key = Symbol.for("openclaw.microsoftFoundryTestApi");
|
||||
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
|
||||
Reflect.set(globalThis, key, { ...api, shouldTestFoundryTextConnection });
|
||||
}
|
||||
|
||||
export const entraIdAuthMethod: ProviderAuthMethod = {
|
||||
id: "entra-id",
|
||||
label: "Entra ID (az login)",
|
||||
|
||||
@@ -3,33 +3,37 @@ import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { shouldTestFoundryTextConnection } from "./auth.js";
|
||||
import { getAccessTokenResultAsync } from "./cli.js";
|
||||
import plugin from "./index.js";
|
||||
import {
|
||||
buildFoundryConnectionTest,
|
||||
isValidTenantIdentifier,
|
||||
promptApiKeyEndpointAndModel,
|
||||
promptEndpointAndModelManually,
|
||||
selectFoundryDeployment,
|
||||
} from "./onboard.js";
|
||||
import { resetFoundryRuntimeAuthCaches } from "./runtime.js";
|
||||
import {
|
||||
COGNITIVE_SERVICES_RESOURCE,
|
||||
FOUNDRY_ANTHROPIC_SCOPE,
|
||||
buildFoundryAuthResult,
|
||||
extractFoundryEndpoint,
|
||||
formatFoundryApiLabel,
|
||||
isAnthropicFoundryDeployment,
|
||||
isFoundryMaiImageModel,
|
||||
normalizeFoundryEndpoint,
|
||||
requiresFoundryMaxCompletionTokens,
|
||||
requiresFoundryEntraIdClaudeAuth,
|
||||
supportsFoundryReasoningContent,
|
||||
supportsFoundryReasoningEffort,
|
||||
supportsFoundryImageInput,
|
||||
usesFoundryResponsesByDefault,
|
||||
} from "./shared.js";
|
||||
import { microsoftFoundryTesting } from "./test-support.js";
|
||||
|
||||
const {
|
||||
buildFoundryConnectionTest,
|
||||
isAnthropicFoundryDeployment,
|
||||
isValidTenantIdentifier,
|
||||
resetFoundryRuntimeAuthCaches,
|
||||
shouldTestFoundryTextConnection,
|
||||
supportsFoundryImageInput,
|
||||
supportsFoundryReasoningContent,
|
||||
supportsFoundryReasoningEffort,
|
||||
} = microsoftFoundryTesting;
|
||||
|
||||
const execFileMock = vi.hoisted(() => vi.fn());
|
||||
const execFileSyncMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
@@ -426,7 +426,7 @@ export async function promptApiKeyEndpointAndModel(
|
||||
});
|
||||
}
|
||||
|
||||
export function buildFoundryConnectionTest(params: {
|
||||
function buildFoundryConnectionTest(params: {
|
||||
endpoint: string;
|
||||
modelId: string;
|
||||
modelNameHint?: string | null;
|
||||
@@ -489,7 +489,7 @@ function extractTenantSuggestions(rawMessage: string): Array<{ id: string; label
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
export function isValidTenantIdentifier(value: string): boolean {
|
||||
function isValidTenantIdentifier(value: string): boolean {
|
||||
const trimmed = normalizeOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
@@ -503,6 +503,12 @@ export function isValidTenantIdentifier(value: string): boolean {
|
||||
return isTenantUuid || isTenantDomain;
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
const key = Symbol.for("openclaw.microsoftFoundryTestApi");
|
||||
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
|
||||
Reflect.set(globalThis, key, { ...api, buildFoundryConnectionTest, isValidTenantIdentifier });
|
||||
}
|
||||
|
||||
export async function promptTenantId(
|
||||
ctx: ProviderAuthContext,
|
||||
params?: {
|
||||
|
||||
@@ -28,11 +28,17 @@ const cachedTokens = new Map<string, CachedTokenEntry>();
|
||||
const refreshPromises = new Map<string, Promise<{ apiKey: string; expiresAt: number }>>();
|
||||
const FOUNDRY_TOKEN_FALLBACK_LIFETIME_MS = 55 * 60 * 1000;
|
||||
|
||||
export function resetFoundryRuntimeAuthCaches(): void {
|
||||
function resetFoundryRuntimeAuthCaches(): void {
|
||||
cachedTokens.clear();
|
||||
refreshPromises.clear();
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
const key = Symbol.for("openclaw.microsoftFoundryTestApi");
|
||||
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
|
||||
Reflect.set(globalThis, key, { ...api, resetFoundryRuntimeAuthCaches });
|
||||
}
|
||||
|
||||
async function refreshEntraToken(params?: {
|
||||
scope?: string;
|
||||
subscriptionId?: string;
|
||||
|
||||
@@ -144,7 +144,7 @@ function normalizeFoundryModelName(value?: string | null): string | undefined {
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function isAnthropicFoundryDeployment(modelName?: string | null): boolean {
|
||||
function isAnthropicFoundryDeployment(modelName?: string | null): boolean {
|
||||
const normalized = normalizeFoundryModelName(modelName);
|
||||
return normalized ? normalized.startsWith("claude") : false;
|
||||
}
|
||||
@@ -182,12 +182,12 @@ export function isFoundryMaiImageModel(value?: string | null): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function supportsFoundryReasoningContent(value?: string | null): boolean {
|
||||
function supportsFoundryReasoningContent(value?: string | null): boolean {
|
||||
const normalized = normalizeFoundryModelName(value);
|
||||
return normalized === "mai-ds-r1" || normalized === "mai-thinking-1";
|
||||
}
|
||||
|
||||
export function supportsFoundryImageInput(value?: string | null): boolean {
|
||||
function supportsFoundryImageInput(value?: string | null): boolean {
|
||||
const normalized = normalizeFoundryModelName(value);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
@@ -267,7 +267,7 @@ export function requiresFoundryMaxCompletionTokens(value?: string | null): boole
|
||||
);
|
||||
}
|
||||
|
||||
export function supportsFoundryReasoningEffort(value?: string | null): boolean {
|
||||
function supportsFoundryReasoningEffort(value?: string | null): boolean {
|
||||
const normalized = normalizeFoundryModelName(value);
|
||||
if (
|
||||
!normalized ||
|
||||
@@ -284,6 +284,18 @@ export function supportsFoundryReasoningEffort(value?: string | null): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
const key = Symbol.for("openclaw.microsoftFoundryTestApi");
|
||||
const api = (Reflect.get(globalThis, key) as Record<string, unknown> | undefined) ?? {};
|
||||
Reflect.set(globalThis, key, {
|
||||
...api,
|
||||
isAnthropicFoundryDeployment,
|
||||
supportsFoundryImageInput,
|
||||
supportsFoundryReasoningContent,
|
||||
supportsFoundryReasoningEffort,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveFoundryReasoningEfforts(value?: string | null): string[] | undefined {
|
||||
const normalized = normalizeFoundryModelName(value);
|
||||
if (!normalized || !supportsFoundryReasoningEffort(normalized)) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { FoundryProviderApi } from "./shared.js";
|
||||
|
||||
type MicrosoftFoundryTestApi = {
|
||||
buildFoundryConnectionTest: (params: {
|
||||
endpoint: string;
|
||||
modelId: string;
|
||||
modelNameHint?: string | null;
|
||||
api: FoundryProviderApi;
|
||||
}) => { url: string; body: Record<string, unknown> };
|
||||
isAnthropicFoundryDeployment: (value?: string | null) => boolean;
|
||||
isValidTenantIdentifier: (value: string) => boolean;
|
||||
resetFoundryRuntimeAuthCaches: () => void;
|
||||
shouldTestFoundryTextConnection: (params: {
|
||||
modelId: string;
|
||||
modelNameHint?: string | null;
|
||||
}) => boolean;
|
||||
supportsFoundryImageInput: (value?: string | null) => boolean;
|
||||
supportsFoundryReasoningContent: (value?: string | null) => boolean;
|
||||
supportsFoundryReasoningEffort: (value?: string | null) => boolean;
|
||||
};
|
||||
|
||||
const api = Reflect.get(globalThis, Symbol.for("openclaw.microsoftFoundryTestApi"));
|
||||
if (!api) {
|
||||
throw new Error("Microsoft Foundry test API is unavailable");
|
||||
}
|
||||
|
||||
export const microsoftFoundryTesting = api as MicrosoftFoundryTestApi;
|
||||
@@ -12,7 +12,7 @@ type ClaudeArchivePath = {
|
||||
relativePath: string;
|
||||
};
|
||||
|
||||
export type ClaudeAutoMemorySource = {
|
||||
type ClaudeAutoMemorySource = {
|
||||
id: string;
|
||||
label: string;
|
||||
path: string;
|
||||
|
||||
@@ -10,7 +10,7 @@ export type HermesAuthProfileConfig = {
|
||||
displayName?: string;
|
||||
};
|
||||
|
||||
export type HermesAuthConfigApplyResult = "configured" | "conflict" | "unavailable";
|
||||
type HermesAuthConfigApplyResult = "configured" | "conflict" | "unavailable";
|
||||
|
||||
class HermesAuthConfigConflict extends Error {}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { childRecord, isRecord, readString, sanitizeName } from "./helpers.js";
|
||||
import { normalizeHermesCustomProviderId, resolveHermesConfiguredProviderId } from "./model.js";
|
||||
|
||||
export type HermesProviderSecretBinding = {
|
||||
type HermesProviderSecretBinding = {
|
||||
envVar: string;
|
||||
provider: string;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import { MIGRATION_REASON_TARGET_EXISTS } from "openclaw/plugin-sdk/migration";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { buildAuthItems } from "./auth.js";
|
||||
import { buildHermesMigrationProvider } from "./provider.js";
|
||||
import { discoverHermesSource, resolveImplicitHermesRoot } from "./source.js";
|
||||
import { discoverHermesSource } from "./source.js";
|
||||
import { resolveTargets } from "./targets.js";
|
||||
import { cleanupTempRoots, makeContext, makeTempRoot, writeFile } from "./test/provider-helpers.js";
|
||||
|
||||
@@ -95,14 +95,23 @@ describe("Hermes migration file and skill items", () => {
|
||||
await writeFile(path.join(defaultRoot, "active_profile"), "coder\n");
|
||||
await writeFile(path.join(profileRoot, "config.yaml"), "model: openai/gpt-5.6\n");
|
||||
|
||||
expect(await resolveImplicitHermesRoot({ HERMES_HOME: defaultRoot }, "darwin")).toBe(
|
||||
defaultRoot,
|
||||
);
|
||||
expect(await resolveImplicitHermesRoot({ HOME: home }, "darwin")).toBe(profileRoot);
|
||||
expect(
|
||||
(
|
||||
await discoverHermesSource(undefined, {
|
||||
env: { HERMES_HOME: defaultRoot },
|
||||
platform: "darwin",
|
||||
})
|
||||
).root,
|
||||
).toBe(defaultRoot);
|
||||
expect(
|
||||
(await discoverHermesSource(undefined, { env: { HOME: home }, platform: "darwin" })).root,
|
||||
).toBe(profileRoot);
|
||||
expect((await discoverHermesSource(profileRoot)).root).toBe(profileRoot);
|
||||
|
||||
await writeFile(path.join(defaultRoot, "active_profile"), "../escape\n");
|
||||
expect(await resolveImplicitHermesRoot({ HOME: home }, "darwin")).toBe(defaultRoot);
|
||||
expect(
|
||||
(await discoverHermesSource(undefined, { env: { HOME: home }, platform: "darwin" })).root,
|
||||
).toBe(defaultRoot);
|
||||
|
||||
const windowsHome = path.join(root, "windows-home");
|
||||
const localAppData = path.join(windowsHome, "AppData", "Local");
|
||||
@@ -110,17 +119,21 @@ describe("Hermes migration file and skill items", () => {
|
||||
const legacyWindowsRoot = path.join(windowsHome, ".hermes");
|
||||
await writeFile(path.join(legacyWindowsRoot, "config.yaml"), "model: legacy\n");
|
||||
expect(
|
||||
await resolveImplicitHermesRoot(
|
||||
{ LOCALAPPDATA: localAppData, USERPROFILE: windowsHome },
|
||||
"win32",
|
||||
),
|
||||
(
|
||||
await discoverHermesSource(undefined, {
|
||||
env: { LOCALAPPDATA: localAppData, USERPROFILE: windowsHome },
|
||||
platform: "win32",
|
||||
})
|
||||
).root,
|
||||
).toBe(legacyWindowsRoot);
|
||||
await writeFile(path.join(nativeWindowsRoot, "config.yaml"), "model: current\n");
|
||||
expect(
|
||||
await resolveImplicitHermesRoot(
|
||||
{ LOCALAPPDATA: localAppData, USERPROFILE: windowsHome },
|
||||
"win32",
|
||||
),
|
||||
(
|
||||
await discoverHermesSource(undefined, {
|
||||
env: { LOCALAPPDATA: localAppData, USERPROFILE: windowsHome },
|
||||
platform: "win32",
|
||||
})
|
||||
).root,
|
||||
).toBe(nativeWindowsRoot);
|
||||
|
||||
const personaHome = path.join(root, "persona-home");
|
||||
@@ -128,10 +141,12 @@ describe("Hermes migration file and skill items", () => {
|
||||
const personaLegacyRoot = path.join(personaHome, ".hermes");
|
||||
await writeFile(path.join(personaLegacyRoot, "SOUL.md"), "Legacy persona\n");
|
||||
expect(
|
||||
await resolveImplicitHermesRoot(
|
||||
{ LOCALAPPDATA: personaLocalAppData, USERPROFILE: personaHome },
|
||||
"win32",
|
||||
),
|
||||
(
|
||||
await discoverHermesSource(undefined, {
|
||||
env: { LOCALAPPDATA: personaLocalAppData, USERPROFILE: personaHome },
|
||||
platform: "win32",
|
||||
})
|
||||
).root,
|
||||
).toBe(personaLegacyRoot);
|
||||
});
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ export async function discoverHermesSource(
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveImplicitHermesRoot(
|
||||
async function resolveImplicitHermesRoot(
|
||||
env: NodeJS.ProcessEnv,
|
||||
platform: NodeJS.Platform,
|
||||
): Promise<string> {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
|
||||
export type { QaProviderMode, QaProviderModeInput } from "./providers/index.js";
|
||||
|
||||
export type QaModelSelection = {
|
||||
type QaModelSelection = {
|
||||
primaryModel: string;
|
||||
alternateModel: string;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Voyage batch tests cover bounded status/error response reads.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runVoyageEmbeddingBatches, testing } from "./embedding-batch.js";
|
||||
import { runVoyageEmbeddingBatches } from "./embedding-batch.js";
|
||||
import type { VoyageEmbeddingClient } from "./embedding-provider.js";
|
||||
import { voyageEmbeddingBatchTesting as testing } from "./test-support.js";
|
||||
|
||||
const { fetchVoyageBatchStatus, readVoyageBatchError, VOYAGE_BATCH_RESPONSE_MAX_BYTES } = testing;
|
||||
|
||||
|
||||
@@ -339,8 +339,12 @@ export async function runVoyageEmbeddingBatches(
|
||||
});
|
||||
}
|
||||
|
||||
export const testing = {
|
||||
const testing = {
|
||||
fetchVoyageBatchStatus,
|
||||
readVoyageBatchError,
|
||||
VOYAGE_BATCH_RESPONSE_MAX_BYTES,
|
||||
} as const;
|
||||
|
||||
if (process.env.VITEST === "true") {
|
||||
Reflect.set(globalThis, Symbol.for("openclaw.voyageEmbeddingBatchTestApi"), testing);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { VoyageEmbeddingClient } from "./embedding-provider.js";
|
||||
|
||||
type VoyageBatchTestParams = {
|
||||
client: VoyageEmbeddingClient;
|
||||
deps: Record<string, unknown>;
|
||||
maxResponseBytes?: number;
|
||||
};
|
||||
|
||||
type VoyageEmbeddingBatchTestApi = {
|
||||
fetchVoyageBatchStatus: (params: VoyageBatchTestParams & { batchId: string }) => Promise<unknown>;
|
||||
readVoyageBatchError: (
|
||||
params: VoyageBatchTestParams & { errorFileId: string },
|
||||
) => Promise<string | undefined>;
|
||||
VOYAGE_BATCH_RESPONSE_MAX_BYTES: number;
|
||||
};
|
||||
|
||||
const api = Reflect.get(globalThis, Symbol.for("openclaw.voyageEmbeddingBatchTestApi"));
|
||||
if (!api) {
|
||||
throw new Error("Voyage embedding batch test API is unavailable");
|
||||
}
|
||||
|
||||
export const voyageEmbeddingBatchTesting = api as VoyageEmbeddingBatchTestApi;
|
||||
@@ -40,56 +40,46 @@ describe("check-deadcode-exports", () => {
|
||||
expect(knipConfig.workspaces["."].entry).toContain("src/mcp/openclaw-tools-serve.ts!");
|
||||
});
|
||||
|
||||
it.each([
|
||||
"acpx",
|
||||
"amazon-bedrock-mantle",
|
||||
"azure-speech",
|
||||
"cloudflare-ai-gateway",
|
||||
"cohere",
|
||||
"deepgram",
|
||||
"elevenlabs",
|
||||
"featherless",
|
||||
"fireworks",
|
||||
"google",
|
||||
"huggingface",
|
||||
"kilocode",
|
||||
"kimi-coding",
|
||||
"lmstudio",
|
||||
"microsoft",
|
||||
"minimax",
|
||||
"mistral",
|
||||
"moonshot",
|
||||
"nvidia",
|
||||
"pixverse",
|
||||
"qianfan",
|
||||
"qwen",
|
||||
"senseaudio",
|
||||
"tavily",
|
||||
"tencent",
|
||||
"vllm",
|
||||
"xiaomi",
|
||||
"xai",
|
||||
])("removes the bundled-plugin root catch-all from migrated %s workspace", (pluginId) => {
|
||||
const workspace = (
|
||||
knipConfig.workspaces as Record<string, { readonly entry: readonly string[] }>
|
||||
)[`extensions/${pluginId}`];
|
||||
if (!workspace) {
|
||||
throw new Error(`missing Knip workspace for ${pluginId}`);
|
||||
it("keeps every migrated bundled-plugin workspace strict", () => {
|
||||
const extensionWorkspaces = Object.entries(knipConfig.workspaces).filter(
|
||||
([workspace]) =>
|
||||
workspace.startsWith("extensions/") &&
|
||||
!["extensions/*", "extensions/llama-cpp", "extensions/reef"].includes(workspace),
|
||||
);
|
||||
expect(extensionWorkspaces.length).toBeGreaterThan(1);
|
||||
for (const [workspace, settings] of extensionWorkspaces) {
|
||||
expect(settings.entry, workspace).not.toContain("*.ts!");
|
||||
expect(settings.project, workspace).toContain("*.ts!");
|
||||
expect(settings.entry, workspace).toEqual(
|
||||
expect.arrayContaining([
|
||||
"index.ts!",
|
||||
"setup-entry.ts!",
|
||||
"*-api.ts!",
|
||||
"cli-metadata.ts!",
|
||||
"channel-entry.ts!",
|
||||
"provider-discovery.ts!",
|
||||
"{web-search,web-fetch}-provider.ts!",
|
||||
]),
|
||||
);
|
||||
}
|
||||
const entries = workspace.entry;
|
||||
expect(entries).not.toContain("*.ts!");
|
||||
expect(entries).toEqual(
|
||||
expect(knipConfig.workspaces["extensions/*"].entry).toContain("*.ts!");
|
||||
expect(knipConfig.workspaces["extensions/*"].project).toContain("*.ts!");
|
||||
expect(knipConfig.workspaces["extensions/llama-cpp"].entry).toContain("*.ts!");
|
||||
expect(knipConfig.workspaces["extensions/reef"].entry).toContain("*.ts!");
|
||||
});
|
||||
|
||||
it("models the Browser facades loaded by basename", () => {
|
||||
const workspace = knipConfig.workspaces["extensions/browser"];
|
||||
expect(workspace.entry).toEqual(
|
||||
expect.arrayContaining([
|
||||
"index.ts!",
|
||||
"setup-entry.ts!",
|
||||
"*-api.ts!",
|
||||
"cli-metadata.ts!",
|
||||
"channel-entry.ts!",
|
||||
"provider-discovery.ts!",
|
||||
"{web-search,web-fetch}-provider.ts!",
|
||||
"browser-control-auth.ts!",
|
||||
"browser-config.ts!",
|
||||
"browser-doctor.ts!",
|
||||
"browser-host-inspection.ts!",
|
||||
"browser-maintenance.ts!",
|
||||
"browser-profiles.ts!",
|
||||
]),
|
||||
);
|
||||
expect(knipConfig.workspaces["extensions/*"].entry).toContain("*.ts!");
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
Reference in New Issue
Block a user