From 9a9db879527f1be6aad797694aeae9e5b5bc032e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 18 Mar 2026 17:09:42 -0700 Subject: [PATCH] fix(release): isolate config doc surfaces and sdk exports --- extensions/device-pair/api.ts | 2 +- extensions/diagnostics-otel/api.ts | 2 +- extensions/diffs/api.ts | 2 +- extensions/line/api.ts | 2 +- extensions/llm-task/api.ts | 2 +- extensions/memory-lancedb/api.ts | 2 +- extensions/minimax/index.ts | 14 +- extensions/minimax/oauth.ts | 2 +- extensions/nostr/api.ts | 2 +- extensions/signal/src/accounts.ts | 2 +- extensions/synology-chat/api.ts | 2 +- extensions/talk-voice/api.ts | 2 +- extensions/thread-ownership/api.ts | 2 +- extensions/tlon/api.ts | 2 +- extensions/twitch/api.ts | 2 +- extensions/voice-call/api.ts | 2 +- package.json | 72 ++++ scripts/lib/plugin-sdk-entrypoints.json | 18 + scripts/load-channel-config-surface.ts | 183 ++++++++++- .../load-channel-config-surface.test.ts | 89 +++++ src/plugins/provider-model-definitions.ts | 309 ++++++++++++++---- 21 files changed, 623 insertions(+), 92 deletions(-) create mode 100644 src/config/load-channel-config-surface.test.ts diff --git a/extensions/device-pair/api.ts b/extensions/device-pair/api.ts index 137cd4b89ba..299ad90f05d 100644 --- a/extensions/device-pair/api.ts +++ b/extensions/device-pair/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/device-pair.js"; +export * from "openclaw/plugin-sdk/device-pair"; diff --git a/extensions/diagnostics-otel/api.ts b/extensions/diagnostics-otel/api.ts index 077ad45965f..01d7aed8989 100644 --- a/extensions/diagnostics-otel/api.ts +++ b/extensions/diagnostics-otel/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/diagnostics-otel.js"; +export * from "openclaw/plugin-sdk/diagnostics-otel"; diff --git a/extensions/diffs/api.ts b/extensions/diffs/api.ts index a200daea1fd..e6fbaf9022a 100644 --- a/extensions/diffs/api.ts +++ b/extensions/diffs/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/diffs.js"; +export * from "openclaw/plugin-sdk/diffs"; diff --git a/extensions/line/api.ts b/extensions/line/api.ts index 4c0731ecc1a..5fdc62bdfb4 100644 --- a/extensions/line/api.ts +++ b/extensions/line/api.ts @@ -1,2 +1,2 @@ -export * from "../../src/plugin-sdk/line.js"; +export * from "openclaw/plugin-sdk/line"; export * from "./setup-api.js"; diff --git a/extensions/llm-task/api.ts b/extensions/llm-task/api.ts index 25e5e13d5ca..8eebdd06e0b 100644 --- a/extensions/llm-task/api.ts +++ b/extensions/llm-task/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/llm-task.js"; +export * from "openclaw/plugin-sdk/llm-task"; diff --git a/extensions/memory-lancedb/api.ts b/extensions/memory-lancedb/api.ts index ce6e02cf02f..c1bd12dd4b7 100644 --- a/extensions/memory-lancedb/api.ts +++ b/extensions/memory-lancedb/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/memory-lancedb.js"; +export * from "openclaw/plugin-sdk/memory-lancedb"; diff --git a/extensions/minimax/index.ts b/extensions/minimax/index.ts index ff54a2730b0..e219ceec6a0 100644 --- a/extensions/minimax/index.ts +++ b/extensions/minimax/index.ts @@ -1,3 +1,10 @@ +import { + buildOauthProviderAuthResult, + definePluginEntry, + type ProviderAuthContext, + type ProviderAuthResult, + type ProviderCatalogContext, +} from "openclaw/plugin-sdk/minimax-portal-auth"; import { MINIMAX_OAUTH_MARKER, createProviderApiKeyAuthMethod, @@ -5,13 +12,6 @@ import { listProfilesForProvider, } from "openclaw/plugin-sdk/provider-auth"; import { fetchMinimaxUsage } from "openclaw/plugin-sdk/provider-usage"; -import { - buildOauthProviderAuthResult, - definePluginEntry, - type ProviderAuthContext, - type ProviderAuthResult, - type ProviderCatalogContext, -} from "../../src/plugin-sdk/minimax-portal-auth.js"; import { minimaxMediaUnderstandingProvider, minimaxPortalMediaUnderstandingProvider, diff --git a/extensions/minimax/oauth.ts b/extensions/minimax/oauth.ts index 394a083630a..fb405cd5559 100644 --- a/extensions/minimax/oauth.ts +++ b/extensions/minimax/oauth.ts @@ -2,7 +2,7 @@ import { randomBytes, randomUUID } from "node:crypto"; import { generatePkceVerifierChallenge, toFormUrlEncoded, -} from "../../src/plugin-sdk/minimax-portal-auth.js"; +} from "openclaw/plugin-sdk/minimax-portal-auth"; export type MiniMaxRegion = "cn" | "global"; diff --git a/extensions/nostr/api.ts b/extensions/nostr/api.ts index 3fbe8cf14d6..3f3d64cc3bf 100644 --- a/extensions/nostr/api.ts +++ b/extensions/nostr/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/nostr.js"; +export * from "openclaw/plugin-sdk/nostr"; diff --git a/extensions/signal/src/accounts.ts b/extensions/signal/src/accounts.ts index 76f245425b0..272b4612dc1 100644 --- a/extensions/signal/src/accounts.ts +++ b/extensions/signal/src/accounts.ts @@ -4,7 +4,7 @@ import { resolveAccountEntry, type OpenClawConfig, } from "openclaw/plugin-sdk/account-resolution"; -import type { SignalAccountConfig } from "../../../src/plugin-sdk/signal-core.js"; +import type { SignalAccountConfig } from "openclaw/plugin-sdk/signal-core"; export type ResolvedSignalAccount = { accountId: string; diff --git a/extensions/synology-chat/api.ts b/extensions/synology-chat/api.ts index dded68ce44c..4ff5241bd49 100644 --- a/extensions/synology-chat/api.ts +++ b/extensions/synology-chat/api.ts @@ -1,2 +1,2 @@ -export * from "../../src/plugin-sdk/synology-chat.js"; +export * from "openclaw/plugin-sdk/synology-chat"; export * from "./setup-api.js"; diff --git a/extensions/talk-voice/api.ts b/extensions/talk-voice/api.ts index 5f50f1a5247..a5ae821e944 100644 --- a/extensions/talk-voice/api.ts +++ b/extensions/talk-voice/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/talk-voice.js"; +export * from "openclaw/plugin-sdk/talk-voice"; diff --git a/extensions/thread-ownership/api.ts b/extensions/thread-ownership/api.ts index 16e4afef70a..d94a5fd68e1 100644 --- a/extensions/thread-ownership/api.ts +++ b/extensions/thread-ownership/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/thread-ownership.js"; +export * from "openclaw/plugin-sdk/thread-ownership"; diff --git a/extensions/tlon/api.ts b/extensions/tlon/api.ts index 2d50ee84bd8..5364c68f07d 100644 --- a/extensions/tlon/api.ts +++ b/extensions/tlon/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/tlon.js"; +export * from "openclaw/plugin-sdk/tlon"; diff --git a/extensions/twitch/api.ts b/extensions/twitch/api.ts index dfe3fbff0cd..68033283423 100644 --- a/extensions/twitch/api.ts +++ b/extensions/twitch/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/twitch.js"; +export * from "openclaw/plugin-sdk/twitch"; diff --git a/extensions/voice-call/api.ts b/extensions/voice-call/api.ts index d0f69774b5e..ef9f7d7a3c0 100644 --- a/extensions/voice-call/api.ts +++ b/extensions/voice-call/api.ts @@ -1 +1 @@ -export * from "../../src/plugin-sdk/voice-call.js"; +export * from "openclaw/plugin-sdk/voice-call"; diff --git a/package.json b/package.json index 3879931c535..6516cb56e58 100644 --- a/package.json +++ b/package.json @@ -182,6 +182,10 @@ "types": "./dist/plugin-sdk/discord-core.d.ts", "default": "./dist/plugin-sdk/discord-core.js" }, + "./plugin-sdk/feishu": { + "types": "./dist/plugin-sdk/feishu.d.ts", + "default": "./dist/plugin-sdk/feishu.js" + }, "./plugin-sdk/slack": { "types": "./dist/plugin-sdk/slack.d.ts", "default": "./dist/plugin-sdk/slack.js" @@ -250,6 +254,18 @@ "types": "./dist/plugin-sdk/boolean-param.d.ts", "default": "./dist/plugin-sdk/boolean-param.js" }, + "./plugin-sdk/device-pair": { + "types": "./dist/plugin-sdk/device-pair.d.ts", + "default": "./dist/plugin-sdk/device-pair.js" + }, + "./plugin-sdk/diagnostics-otel": { + "types": "./dist/plugin-sdk/diagnostics-otel.d.ts", + "default": "./dist/plugin-sdk/diagnostics-otel.js" + }, + "./plugin-sdk/diffs": { + "types": "./dist/plugin-sdk/diffs.d.ts", + "default": "./dist/plugin-sdk/diffs.js" + }, "./plugin-sdk/channel-config-helpers": { "types": "./dist/plugin-sdk/channel-config-helpers.d.ts", "default": "./dist/plugin-sdk/channel-config-helpers.js" @@ -290,6 +306,22 @@ "types": "./dist/plugin-sdk/keyed-async-queue.d.ts", "default": "./dist/plugin-sdk/keyed-async-queue.js" }, + "./plugin-sdk/line": { + "types": "./dist/plugin-sdk/line.d.ts", + "default": "./dist/plugin-sdk/line.js" + }, + "./plugin-sdk/llm-task": { + "types": "./dist/plugin-sdk/llm-task.d.ts", + "default": "./dist/plugin-sdk/llm-task.js" + }, + "./plugin-sdk/memory-lancedb": { + "types": "./dist/plugin-sdk/memory-lancedb.d.ts", + "default": "./dist/plugin-sdk/memory-lancedb.js" + }, + "./plugin-sdk/minimax-portal-auth": { + "types": "./dist/plugin-sdk/minimax-portal-auth.d.ts", + "default": "./dist/plugin-sdk/minimax-portal-auth.js" + }, "./plugin-sdk/provider-auth": { "types": "./dist/plugin-sdk/provider-auth.d.ts", "default": "./dist/plugin-sdk/provider-auth.js" @@ -334,6 +366,10 @@ "types": "./dist/plugin-sdk/image-generation.d.ts", "default": "./dist/plugin-sdk/image-generation.js" }, + "./plugin-sdk/nostr": { + "types": "./dist/plugin-sdk/nostr.d.ts", + "default": "./dist/plugin-sdk/nostr.js" + }, "./plugin-sdk/reply-history": { "types": "./dist/plugin-sdk/reply-history.d.ts", "default": "./dist/plugin-sdk/reply-history.js" @@ -342,6 +378,14 @@ "types": "./dist/plugin-sdk/media-understanding.d.ts", "default": "./dist/plugin-sdk/media-understanding.js" }, + "./plugin-sdk/secret-input-runtime": { + "types": "./dist/plugin-sdk/secret-input-runtime.d.ts", + "default": "./dist/plugin-sdk/secret-input-runtime.js" + }, + "./plugin-sdk/secret-input-schema": { + "types": "./dist/plugin-sdk/secret-input-schema.d.ts", + "default": "./dist/plugin-sdk/secret-input-schema.js" + }, "./plugin-sdk/request-url": { "types": "./dist/plugin-sdk/request-url.d.ts", "default": "./dist/plugin-sdk/request-url.js" @@ -362,6 +406,34 @@ "types": "./dist/plugin-sdk/secret-input.d.ts", "default": "./dist/plugin-sdk/secret-input.js" }, + "./plugin-sdk/signal-core": { + "types": "./dist/plugin-sdk/signal-core.d.ts", + "default": "./dist/plugin-sdk/signal-core.js" + }, + "./plugin-sdk/synology-chat": { + "types": "./dist/plugin-sdk/synology-chat.d.ts", + "default": "./dist/plugin-sdk/synology-chat.js" + }, + "./plugin-sdk/talk-voice": { + "types": "./dist/plugin-sdk/talk-voice.d.ts", + "default": "./dist/plugin-sdk/talk-voice.js" + }, + "./plugin-sdk/thread-ownership": { + "types": "./dist/plugin-sdk/thread-ownership.d.ts", + "default": "./dist/plugin-sdk/thread-ownership.js" + }, + "./plugin-sdk/tlon": { + "types": "./dist/plugin-sdk/tlon.d.ts", + "default": "./dist/plugin-sdk/tlon.js" + }, + "./plugin-sdk/twitch": { + "types": "./dist/plugin-sdk/twitch.d.ts", + "default": "./dist/plugin-sdk/twitch.js" + }, + "./plugin-sdk/voice-call": { + "types": "./dist/plugin-sdk/voice-call.d.ts", + "default": "./dist/plugin-sdk/voice-call.js" + }, "./plugin-sdk/web-media": { "types": "./dist/plugin-sdk/web-media.d.ts", "default": "./dist/plugin-sdk/web-media.js" diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 282052b23f5..1f78aaaf735 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -35,6 +35,7 @@ "telegram-core", "discord", "discord-core", + "feishu", "slack", "slack-core", "imessage", @@ -52,6 +53,9 @@ "allowlist-resolution", "allowlist-config-edit", "boolean-param", + "device-pair", + "diagnostics-otel", + "diffs", "channel-config-helpers", "channel-config-schema", "channel-lifecycle", @@ -62,6 +66,10 @@ "directory-runtime", "json-store", "keyed-async-queue", + "line", + "llm-task", + "memory-lancedb", + "minimax-portal-auth", "provider-auth", "provider-auth-api-key", "provider-auth-login", @@ -73,13 +81,23 @@ "provider-usage", "provider-web-search", "image-generation", + "nostr", "reply-history", "media-understanding", + "secret-input-runtime", + "secret-input-schema", "request-url", "webhook-ingress", "webhook-path", "runtime-store", "secret-input", + "signal-core", + "synology-chat", + "talk-voice", + "thread-ownership", + "tlon", + "twitch", + "voice-call", "web-media", "speech", "state-paths", diff --git a/scripts/load-channel-config-surface.ts b/scripts/load-channel-config-surface.ts index 2dfb3e60d83..3852711851b 100644 --- a/scripts/load-channel-config-surface.ts +++ b/scripts/load-channel-config-surface.ts @@ -1,4 +1,6 @@ -import { pathToFileURL } from "node:url"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { buildChannelConfigSchema } from "../src/channels/plugins/config-schema.js"; function isBuiltChannelConfigSchema( @@ -41,16 +43,177 @@ function resolveConfigSchemaExport( return null; } -const modulePath = process.argv[2]?.trim(); -if (!modulePath) { - process.exit(2); +function resolveRepoRoot(): string { + return path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); } -const imported = (await import(pathToFileURL(modulePath).href)) as Record; -const resolved = resolveConfigSchemaExport(imported); -if (!resolved) { - process.exit(3); +function resolvePackageRoot(modulePath: string): string { + let cursor = path.dirname(path.resolve(modulePath)); + while (true) { + if (fs.existsSync(path.join(cursor, "package.json"))) { + return cursor; + } + const parent = path.dirname(cursor); + if (parent === cursor) { + throw new Error(`package root not found for ${modulePath}`); + } + cursor = parent; + } } -process.stdout.write(JSON.stringify(resolved)); -process.exit(0); +function shouldRetryViaIsolatedCopy(error: unknown): boolean { + if (!error || typeof error !== "object") { + return false; + } + const code = "code" in error ? error.code : undefined; + const message = "message" in error && typeof error.message === "string" ? error.message : ""; + return code === "ERR_MODULE_NOT_FOUND" && message.includes(`${path.sep}node_modules${path.sep}`); +} + +const SOURCE_FILE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]; + +function resolveImportCandidates(basePath: string): string[] { + const extension = path.extname(basePath); + const candidates = new Set([basePath]); + if (extension) { + const stem = basePath.slice(0, -extension.length); + for (const sourceExtension of SOURCE_FILE_EXTENSIONS) { + candidates.add(`${stem}${sourceExtension}`); + } + } else { + for (const sourceExtension of SOURCE_FILE_EXTENSIONS) { + candidates.add(`${basePath}${sourceExtension}`); + candidates.add(path.join(basePath, `index${sourceExtension}`)); + } + } + return Array.from(candidates); +} + +function resolveRelativeImportPath(fromFile: string, specifier: string): string | null { + for (const candidate of resolveImportCandidates( + path.resolve(path.dirname(fromFile), specifier), + )) { + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) { + return candidate; + } + } + return null; +} + +function collectRelativeImportGraph(entryPath: string): Set { + const discovered = new Set(); + const queue = [path.resolve(entryPath)]; + const importPattern = + /(?:import|export)\s+(?:[^"'`]*?\s+from\s+)?["'`]([^"'`]+)["'`]|import\(\s*["'`]([^"'`]+)["'`]\s*\)/g; + + while (queue.length > 0) { + const currentPath = queue.pop(); + if (!currentPath || discovered.has(currentPath)) { + continue; + } + discovered.add(currentPath); + + const source = fs.readFileSync(currentPath, "utf8"); + for (const match of source.matchAll(importPattern)) { + const specifier = match[1] ?? match[2]; + if (!specifier?.startsWith(".")) { + continue; + } + const resolved = resolveRelativeImportPath(currentPath, specifier); + if (resolved) { + queue.push(resolved); + } + } + } + + return discovered; +} + +function resolveCommonAncestor(paths: Iterable): string { + const resolvedPaths = Array.from(paths, (entry) => path.resolve(entry)); + const [first, ...rest] = resolvedPaths; + if (!first) { + throw new Error("cannot resolve common ancestor for empty path set"); + } + let ancestor = first; + for (const candidate of rest) { + while (path.relative(ancestor, candidate).startsWith(`..${path.sep}`)) { + const parent = path.dirname(ancestor); + if (parent === ancestor) { + return ancestor; + } + ancestor = parent; + } + } + return ancestor; +} + +function copyModuleImportGraphWithoutNodeModules(params: { + modulePath: string; + repoRoot: string; +}): { + copiedModulePath: string; + cleanup: () => void; +} { + const packageRoot = resolvePackageRoot(params.modulePath); + const relativeFiles = collectRelativeImportGraph(params.modulePath); + const copyRoot = resolveCommonAncestor([packageRoot, ...relativeFiles]); + const relativeModulePath = path.relative(copyRoot, params.modulePath); + const tempParent = path.join(params.repoRoot, ".openclaw-config-doc-cache"); + fs.mkdirSync(tempParent, { recursive: true }); + const isolatedRoot = fs.mkdtempSync(path.join(tempParent, `${path.basename(packageRoot)}-`)); + + for (const sourcePath of relativeFiles) { + const relativePath = path.relative(copyRoot, sourcePath); + const targetPath = path.join(isolatedRoot, relativePath); + fs.mkdirSync(path.dirname(targetPath), { recursive: true }); + fs.copyFileSync(sourcePath, targetPath); + } + return { + copiedModulePath: path.join(isolatedRoot, relativeModulePath), + cleanup: () => { + fs.rmSync(isolatedRoot, { recursive: true, force: true }); + }, + }; +} + +export async function loadChannelConfigSurfaceModule( + modulePath: string, + options?: { repoRoot?: string }, +): Promise<{ schema: Record; uiHints?: Record } | null> { + const repoRoot = options?.repoRoot ?? resolveRepoRoot(); + + try { + const imported = (await import(pathToFileURL(modulePath).href)) as Record; + return resolveConfigSchemaExport(imported); + } catch (error) { + if (!shouldRetryViaIsolatedCopy(error)) { + throw error; + } + + const isolatedCopy = copyModuleImportGraphWithoutNodeModules({ modulePath, repoRoot }); + try { + const imported = (await import( + `${pathToFileURL(isolatedCopy.copiedModulePath).href}?isolated=${Date.now()}` + )) as Record; + return resolveConfigSchemaExport(imported); + } finally { + isolatedCopy.cleanup(); + } + } +} + +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { + const modulePath = process.argv[2]?.trim(); + if (!modulePath) { + process.exit(2); + } + + const resolved = await loadChannelConfigSurfaceModule(modulePath); + if (!resolved) { + process.exit(3); + } + + process.stdout.write(JSON.stringify(resolved)); + process.exit(0); +} diff --git a/src/config/load-channel-config-surface.test.ts b/src/config/load-channel-config-surface.test.ts new file mode 100644 index 00000000000..f001304fbd0 --- /dev/null +++ b/src/config/load-channel-config-surface.test.ts @@ -0,0 +1,89 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { loadChannelConfigSurfaceModule } from "../../scripts/load-channel-config-surface.ts"; + +const tempDirs: string[] = []; + +function makeTempRoot(prefix: string): string { + const root = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + tempDirs.push(root); + return root; +} + +afterEach(() => { + for (const dir of tempDirs.splice(0, tempDirs.length)) { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +describe("loadChannelConfigSurfaceModule", () => { + it("retries from an isolated package copy when extension-local node_modules is broken", async () => { + const repoRoot = makeTempRoot("openclaw-config-surface-"); + const packageRoot = path.join(repoRoot, "extensions", "demo"); + const modulePath = path.join(packageRoot, "src", "config-schema.js"); + + fs.mkdirSync(path.join(packageRoot, "src"), { recursive: true }); + fs.writeFileSync( + path.join(packageRoot, "package.json"), + JSON.stringify({ name: "@openclaw/demo", type: "module" }, null, 2), + "utf8", + ); + fs.writeFileSync( + modulePath, + [ + "import { z } from 'zod';", + "export const DemoChannelConfigSchema = {", + " schema: {", + " type: 'object',", + " properties: { ok: { type: z.object({}).shape ? 'string' : 'string' } },", + " },", + "};", + "", + ].join("\n"), + "utf8", + ); + + fs.mkdirSync(path.join(repoRoot, "node_modules", "zod"), { recursive: true }); + fs.writeFileSync( + path.join(repoRoot, "node_modules", "zod", "package.json"), + JSON.stringify({ + name: "zod", + type: "module", + exports: { ".": "./index.js" }, + }), + "utf8", + ); + fs.writeFileSync( + path.join(repoRoot, "node_modules", "zod", "index.js"), + "export const z = { object: () => ({ shape: {} }) };\n", + "utf8", + ); + + const poisonedStorePackage = path.join( + repoRoot, + "node_modules", + ".pnpm", + "zod@0.0.0", + "node_modules", + "zod", + ); + fs.mkdirSync(poisonedStorePackage, { recursive: true }); + fs.mkdirSync(path.join(packageRoot, "node_modules"), { recursive: true }); + fs.symlinkSync( + "../../../node_modules/.pnpm/zod@0.0.0/node_modules/zod", + path.join(packageRoot, "node_modules", "zod"), + "dir", + ); + + await expect(loadChannelConfigSurfaceModule(modulePath, { repoRoot })).resolves.toMatchObject({ + schema: { + type: "object", + properties: { + ok: { type: "string" }, + }, + }, + }); + }); +}); diff --git a/src/plugins/provider-model-definitions.ts b/src/plugins/provider-model-definitions.ts index 8691c6aa7f3..58271bf219d 100644 --- a/src/plugins/provider-model-definitions.ts +++ b/src/plugins/provider-model-definitions.ts @@ -1,62 +1,3 @@ -import { - KIMI_CODING_BASE_URL, - KIMI_CODING_DEFAULT_MODEL_ID as KIMI_CODING_MODEL_ID, -} from "../../extensions/kimi-coding/provider-catalog.js"; -import { - DEFAULT_MINIMAX_BASE_URL, - MINIMAX_API_BASE_URL, - MINIMAX_API_COST, - MINIMAX_CN_API_BASE_URL, - MINIMAX_HOSTED_COST, - MINIMAX_HOSTED_MODEL_ID, - MINIMAX_HOSTED_MODEL_REF, - MINIMAX_LM_STUDIO_COST, - buildMinimaxApiModelDefinition, - buildMinimaxModelDefinition, -} from "../../extensions/minimax/model-definitions.js"; -import { - MISTRAL_BASE_URL, - MISTRAL_DEFAULT_COST, - MISTRAL_DEFAULT_MODEL_ID, - MISTRAL_DEFAULT_MODEL_REF, - buildMistralModelDefinition, -} from "../../extensions/mistral/model-definitions.js"; -import { - MODELSTUDIO_CN_BASE_URL, - MODELSTUDIO_DEFAULT_COST, - MODELSTUDIO_DEFAULT_MODEL_ID, - MODELSTUDIO_DEFAULT_MODEL_REF, - MODELSTUDIO_GLOBAL_BASE_URL, - buildModelStudioDefaultModelDefinition, - buildModelStudioModelDefinition, -} from "../../extensions/modelstudio/model-definitions.js"; -import { MOONSHOT_CN_BASE_URL } from "../../extensions/moonshot/onboard.js"; -import { - MOONSHOT_BASE_URL, - MOONSHOT_DEFAULT_MODEL_ID, - buildMoonshotProvider, -} from "../../extensions/moonshot/provider-catalog.js"; -import { - QIANFAN_BASE_URL, - QIANFAN_DEFAULT_MODEL_ID, -} from "../../extensions/qianfan/provider-catalog.js"; -import { - XAI_BASE_URL, - XAI_DEFAULT_COST, - XAI_DEFAULT_MODEL_ID, - XAI_DEFAULT_MODEL_REF, - buildXaiModelDefinition, -} from "../../extensions/xai/model-definitions.js"; -import { - ZAI_CN_BASE_URL, - ZAI_CODING_CN_BASE_URL, - ZAI_CODING_GLOBAL_BASE_URL, - ZAI_DEFAULT_COST, - ZAI_DEFAULT_MODEL_ID, - ZAI_GLOBAL_BASE_URL, - buildZaiModelDefinition, - resolveZaiBaseUrl, -} from "../../extensions/zai/model-definitions.js"; import type { ModelDefinitionConfig } from "../config/types.models.js"; import { KILOCODE_DEFAULT_CONTEXT_WINDOW, @@ -66,10 +7,258 @@ import { KILOCODE_DEFAULT_MODEL_NAME, } from "../providers/kilocode-shared.js"; +const KIMI_CODING_BASE_URL = "https://api.kimi.com/coding/"; +const KIMI_CODING_MODEL_ID = "kimi-code"; const KIMI_CODING_MODEL_REF = `kimi/${KIMI_CODING_MODEL_ID}`; + +const DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1"; +const MINIMAX_API_BASE_URL = "https://api.minimax.io/anthropic"; +const MINIMAX_CN_API_BASE_URL = "https://api.minimaxi.com/anthropic"; +const MINIMAX_HOSTED_MODEL_ID = "MiniMax-M2.7"; +const MINIMAX_HOSTED_MODEL_REF = `minimax/${MINIMAX_HOSTED_MODEL_ID}`; +const DEFAULT_MINIMAX_CONTEXT_WINDOW = 200000; +const DEFAULT_MINIMAX_MAX_TOKENS = 8192; +const MINIMAX_API_COST = { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0.12 }; +const MINIMAX_HOSTED_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; +const MINIMAX_LM_STUDIO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; +const MINIMAX_MODEL_CATALOG = { + "MiniMax-M2.7": { name: "MiniMax M2.7", reasoning: true }, + "MiniMax-M2.7-highspeed": { name: "MiniMax M2.7 Highspeed", reasoning: true }, + "MiniMax-M2.5": { name: "MiniMax M2.5", reasoning: true }, + "MiniMax-M2.5-highspeed": { name: "MiniMax M2.5 Highspeed", reasoning: true }, +} as const; + +const MISTRAL_BASE_URL = "https://api.mistral.ai/v1"; +const MISTRAL_DEFAULT_MODEL_ID = "mistral-large-latest"; +const MISTRAL_DEFAULT_MODEL_REF = `mistral/${MISTRAL_DEFAULT_MODEL_ID}`; +const MISTRAL_DEFAULT_CONTEXT_WINDOW = 262144; +const MISTRAL_DEFAULT_MAX_TOKENS = 262144; +const MISTRAL_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; + +const MODELSTUDIO_CN_BASE_URL = "https://coding.dashscope.aliyuncs.com/v1"; +const MODELSTUDIO_GLOBAL_BASE_URL = "https://coding-intl.dashscope.aliyuncs.com/v1"; +const MODELSTUDIO_DEFAULT_MODEL_ID = "qwen3.5-plus"; +const MODELSTUDIO_DEFAULT_MODEL_REF = `modelstudio/${MODELSTUDIO_DEFAULT_MODEL_ID}`; +const MODELSTUDIO_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; +const MODELSTUDIO_MODEL_CATALOG = { + "qwen3.5-plus": { + name: "qwen3.5-plus", + reasoning: false, + input: ["text", "image"], + contextWindow: 1000000, + maxTokens: 65536, + }, + "qwen3-max-2026-01-23": { + name: "qwen3-max-2026-01-23", + reasoning: false, + input: ["text"], + contextWindow: 262144, + maxTokens: 65536, + }, + "qwen3-coder-next": { + name: "qwen3-coder-next", + reasoning: false, + input: ["text"], + contextWindow: 262144, + maxTokens: 65536, + }, + "qwen3-coder-plus": { + name: "qwen3-coder-plus", + reasoning: false, + input: ["text"], + contextWindow: 1000000, + maxTokens: 65536, + }, + "MiniMax-M2.5": { + name: "MiniMax-M2.5", + reasoning: false, + input: ["text"], + contextWindow: 1000000, + maxTokens: 65536, + }, + "glm-5": { + name: "glm-5", + reasoning: false, + input: ["text"], + contextWindow: 202752, + maxTokens: 16384, + }, + "glm-4.7": { + name: "glm-4.7", + reasoning: false, + input: ["text"], + contextWindow: 202752, + maxTokens: 16384, + }, + "kimi-k2.5": { + name: "kimi-k2.5", + reasoning: false, + input: ["text", "image"], + contextWindow: 262144, + maxTokens: 32768, + }, +} as const; + +const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; +const MOONSHOT_CN_BASE_URL = "https://api.moonshot.cn/v1"; +const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5"; const MOONSHOT_DEFAULT_MODEL_REF = `moonshot/${MOONSHOT_DEFAULT_MODEL_ID}`; +const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 256000; +const MOONSHOT_DEFAULT_MAX_TOKENS = 8192; +const MOONSHOT_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; + +const QIANFAN_BASE_URL = "https://qianfan.baidubce.com/v2"; +const QIANFAN_DEFAULT_MODEL_ID = "deepseek-v3.2"; const QIANFAN_DEFAULT_MODEL_REF = `qianfan/${QIANFAN_DEFAULT_MODEL_ID}`; +const XAI_BASE_URL = "https://api.x.ai/v1"; +const XAI_DEFAULT_MODEL_ID = "grok-4"; +const XAI_DEFAULT_MODEL_REF = `xai/${XAI_DEFAULT_MODEL_ID}`; +const XAI_DEFAULT_CONTEXT_WINDOW = 131072; +const XAI_DEFAULT_MAX_TOKENS = 8192; +const XAI_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; + +const ZAI_CODING_GLOBAL_BASE_URL = "https://api.z.ai/api/coding/paas/v4"; +const ZAI_CODING_CN_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4"; +const ZAI_GLOBAL_BASE_URL = "https://api.z.ai/api/paas/v4"; +const ZAI_CN_BASE_URL = "https://open.bigmodel.cn/api/paas/v4"; +const ZAI_DEFAULT_MODEL_ID = "glm-5"; +const ZAI_DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; +const ZAI_MODEL_CATALOG = { + "glm-5": { name: "GLM-5", reasoning: true }, + "glm-5-turbo": { name: "GLM-5 Turbo", reasoning: true }, + "glm-4.7": { name: "GLM-4.7", reasoning: true }, + "glm-4.7-flash": { name: "GLM-4.7 Flash", reasoning: true }, + "glm-4.7-flashx": { name: "GLM-4.7 FlashX", reasoning: true }, +} as const; + +function buildMinimaxModelDefinition(params: { + id: string; + name?: string; + reasoning?: boolean; + cost: ModelDefinitionConfig["cost"]; + contextWindow: number; + maxTokens: number; +}): ModelDefinitionConfig { + const catalog = MINIMAX_MODEL_CATALOG[params.id as keyof typeof MINIMAX_MODEL_CATALOG]; + return { + id: params.id, + name: params.name ?? catalog?.name ?? `MiniMax ${params.id}`, + reasoning: params.reasoning ?? catalog?.reasoning ?? false, + input: ["text"], + cost: params.cost, + contextWindow: params.contextWindow, + maxTokens: params.maxTokens, + }; +} + +function buildMinimaxApiModelDefinition(modelId: string): ModelDefinitionConfig { + return buildMinimaxModelDefinition({ + id: modelId, + cost: MINIMAX_API_COST, + contextWindow: DEFAULT_MINIMAX_CONTEXT_WINDOW, + maxTokens: DEFAULT_MINIMAX_MAX_TOKENS, + }); +} + +function buildMistralModelDefinition(): ModelDefinitionConfig { + return { + id: MISTRAL_DEFAULT_MODEL_ID, + name: "Mistral Large", + reasoning: false, + input: ["text", "image"], + cost: MISTRAL_DEFAULT_COST, + contextWindow: MISTRAL_DEFAULT_CONTEXT_WINDOW, + maxTokens: MISTRAL_DEFAULT_MAX_TOKENS, + }; +} + +function buildModelStudioModelDefinition(params: { + id: string; + name?: string; + reasoning?: boolean; + input?: string[]; + cost?: ModelDefinitionConfig["cost"]; + contextWindow?: number; + maxTokens?: number; +}): ModelDefinitionConfig { + const catalog = MODELSTUDIO_MODEL_CATALOG[params.id as keyof typeof MODELSTUDIO_MODEL_CATALOG]; + return { + id: params.id, + name: params.name ?? catalog?.name ?? params.id, + reasoning: params.reasoning ?? catalog?.reasoning ?? false, + input: + (params.input as ("text" | "image")[]) ?? + ([...(catalog?.input ?? ["text"])] as ("text" | "image")[]), + cost: params.cost ?? MODELSTUDIO_DEFAULT_COST, + contextWindow: params.contextWindow ?? catalog?.contextWindow ?? 262144, + maxTokens: params.maxTokens ?? catalog?.maxTokens ?? 65536, + }; +} + +function buildModelStudioDefaultModelDefinition(): ModelDefinitionConfig { + return buildModelStudioModelDefinition({ id: MODELSTUDIO_DEFAULT_MODEL_ID }); +} + +function createMoonshotModelDefinition(): ModelDefinitionConfig { + return { + id: MOONSHOT_DEFAULT_MODEL_ID, + name: "Kimi K2.5", + reasoning: false, + input: ["text", "image"], + cost: MOONSHOT_DEFAULT_COST, + contextWindow: MOONSHOT_DEFAULT_CONTEXT_WINDOW, + maxTokens: MOONSHOT_DEFAULT_MAX_TOKENS, + }; +} + +function buildXaiModelDefinition(): ModelDefinitionConfig { + return { + id: XAI_DEFAULT_MODEL_ID, + name: "Grok 4", + reasoning: false, + input: ["text"], + cost: XAI_DEFAULT_COST, + contextWindow: XAI_DEFAULT_CONTEXT_WINDOW, + maxTokens: XAI_DEFAULT_MAX_TOKENS, + }; +} + +function resolveZaiBaseUrl(endpoint?: string): string { + switch (endpoint) { + case "coding-cn": + return ZAI_CODING_CN_BASE_URL; + case "global": + return ZAI_GLOBAL_BASE_URL; + case "cn": + return ZAI_CN_BASE_URL; + case "coding-global": + return ZAI_CODING_GLOBAL_BASE_URL; + default: + return ZAI_GLOBAL_BASE_URL; + } +} + +function buildZaiModelDefinition(params: { + id: string; + name?: string; + reasoning?: boolean; + cost?: ModelDefinitionConfig["cost"]; + contextWindow?: number; + maxTokens?: number; +}): ModelDefinitionConfig { + const catalog = ZAI_MODEL_CATALOG[params.id as keyof typeof ZAI_MODEL_CATALOG]; + return { + id: params.id, + name: params.name ?? catalog?.name ?? `GLM ${params.id}`, + reasoning: params.reasoning ?? catalog?.reasoning ?? true, + input: ["text"], + cost: params.cost ?? ZAI_DEFAULT_COST, + contextWindow: params.contextWindow ?? 204800, + maxTokens: params.maxTokens ?? 131072, + }; +} + export { DEFAULT_MINIMAX_BASE_URL, MINIMAX_API_BASE_URL, @@ -123,7 +312,7 @@ export { }; export function buildMoonshotModelDefinition(): ModelDefinitionConfig { - return buildMoonshotProvider().models[0]; + return createMoonshotModelDefinition(); } export function buildKilocodeModelDefinition(): ModelDefinitionConfig {