From 4ca07559abe41bec180a2dc1d51e059bc6f2c527 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 27 Mar 2026 23:24:36 +0000 Subject: [PATCH] refactor: move provider seams behind plugin sdk surfaces --- docs/.generated/plugin-sdk-api-baseline.json | 25 +- docs/.generated/plugin-sdk-api-baseline.jsonl | 17 +- extensions/amazon-bedrock/api.ts | 7 + .../amazon-bedrock/discovery.ts | 67 +++- extensions/anthropic-vertex/api.ts | 42 ++- extensions/anthropic-vertex/region.ts | 118 +++++++ extensions/anthropic/index.ts | 2 +- extensions/byteplus/index.ts | 2 +- extensions/chutes/index.ts | 2 +- extensions/discord/api.ts | 1 + .../discord/src/monitor/provider.test.ts | 1 - extensions/discord/src/monitor/provider.ts | 2 + .../discord/src/send.creates-thread.test.ts | 14 +- extensions/discord/src/voice-message.ts | 14 +- extensions/fal/image-generation-provider.ts | 2 +- extensions/fal/index.ts | 2 +- extensions/google/api.ts | 123 +++++-- extensions/google/index.ts | 7 +- extensions/google/model-id.ts | 27 ++ extensions/huggingface/index.ts | 2 +- extensions/imessage/api.ts | 1 + extensions/imessage/runtime-api.ts | 1 + .../imessage/src/monitor/monitor-provider.ts | 2 + extensions/kimi-coding/index.ts | 2 +- .../minimax/image-generation-provider.ts | 2 +- extensions/minimax/index.ts | 2 +- extensions/mistral/api.ts | 10 +- extensions/modelstudio/api.ts | 2 + extensions/modelstudio/models.ts | 61 +++- extensions/moonshot/api.ts | 5 +- extensions/moonshot/onboard.ts | 3 +- extensions/moonshot/provider-catalog.ts | 50 +++ .../openai/image-generation-provider.ts | 2 +- extensions/openai/openai-provider.ts | 2 +- extensions/opencode-go/index.ts | 2 +- extensions/opencode/index.ts | 2 +- extensions/sglang/api.ts | 1 + extensions/sglang/index.ts | 3 +- extensions/sglang/models.ts | 23 ++ extensions/slack/api.ts | 1 + extensions/slack/src/monitor/provider.ts | 2 + extensions/telegram/runtime-api.ts | 1 + extensions/telegram/src/thread-bindings.ts | 20 +- extensions/vllm/api.ts | 1 + extensions/vllm/index.ts | 3 +- extensions/vllm/models.ts | 23 ++ extensions/volcengine/index.ts | 2 +- extensions/whatsapp/src/accounts.ts | 4 +- extensions/whatsapp/src/auth-store.ts | 19 +- extensions/whatsapp/src/creds-files.ts | 19 ++ extensions/xai/api.ts | 24 +- extensions/xai/model-id.ts | 21 ++ package.json | 20 ++ scripts/lib/plugin-sdk-entrypoints.json | 5 + scripts/lib/plugin-sdk-facades.mjs | 82 ++++- src/agents/anthropic-vertex-provider.ts | 124 ------- src/agents/anthropic-vertex-stream.test.ts | 2 +- src/agents/anthropic-vertex-stream.ts | 2 +- src/agents/bedrock-discovery.test.ts | 45 ++- src/agents/cloudflare-ai-gateway.ts | 8 - src/agents/google-generative-ai.test.ts | 2 +- src/agents/google-generative-ai.ts | 46 --- src/agents/huggingface-models.test.ts | 4 +- src/agents/huggingface-models.ts | 9 - src/agents/model-id-normalization.test.ts | 2 +- src/agents/model-id-normalization.ts | 45 --- ...ig.providers.cloudflare-ai-gateway.test.ts | 2 +- .../models-config.providers.discovery.ts | 117 ------- ...onfig.providers.google-antigravity.test.ts | 8 +- .../models-config.providers.kilocode.test.ts | 2 +- ...odels-config.providers.kimi-coding.test.ts | 2 +- ...odels-config.providers.modelstudio.test.ts | 6 +- .../models-config.providers.nvidia.test.ts | 2 +- .../models-config.providers.ollama.test.ts | 2 +- src/agents/models-config.providers.static.ts | 36 +- src/agents/models-config.providers.ts | 316 ++++++------------ ...config.providers.vercel-ai-gateway.test.ts | 2 +- src/agents/pi-embedded-helpers/google.ts | 2 +- src/agents/pi-embedded-runner/model.ts | 2 +- src/agents/provider-attribution.ts | 2 +- src/agents/provider-capabilities.ts | 2 +- src/agents/sglang-defaults.ts | 7 - src/agents/tools/pdf-native-providers.ts | 2 +- src/agents/vercel-ai-gateway.ts | 12 - src/agents/vllm-defaults.ts | 7 - src/auto-reply/reply/commands-approve.ts | 8 +- .../gateway-models.profiles.live.test.ts | 2 +- src/gateway/model-pricing-cache.ts | 3 +- src/plugin-sdk/account-core.ts | 62 ++++ src/plugin-sdk/amazon-bedrock.ts | 8 + src/plugin-sdk/anthropic-vertex.ts | 8 + src/plugin-sdk/core.ts | 1 + src/plugin-sdk/discord-surface.ts | 5 + src/plugin-sdk/feishu-conversation.ts | 2 + src/plugin-sdk/google-model-id.ts | 4 + src/plugin-sdk/google.ts | 10 +- src/plugin-sdk/image-generation-core.ts | 2 +- src/plugin-sdk/imessage-policy.ts | 1 + src/plugin-sdk/imessage-runtime.ts | 1 + src/plugin-sdk/imessage.ts | 1 + src/plugin-sdk/matrix-surface.ts | 1 + src/plugin-sdk/minimax.ts | 2 + src/plugin-sdk/mistral.ts | 2 + src/plugin-sdk/modelstudio.ts | 2 + src/plugin-sdk/moonshot.ts | 10 +- src/plugin-sdk/provider-auth-api-key.ts | 2 +- src/plugin-sdk/provider-auth-runtime.ts | 8 + src/plugin-sdk/provider-auth.ts | 1 - src/plugin-sdk/provider-models.ts | 4 +- src/plugin-sdk/provider-setup.ts | 7 +- src/plugin-sdk/runtime-api-guardrails.test.ts | 3 +- src/plugin-sdk/self-hosted-provider-setup.ts | 7 +- src/plugin-sdk/sglang.ts | 1 + src/plugin-sdk/signal-surface.ts | 6 +- src/plugin-sdk/slack-surface.ts | 7 +- src/plugin-sdk/telegram-runtime-surface.ts | 2 + src/plugin-sdk/telegram-surface.ts | 2 + src/plugin-sdk/vllm.ts | 1 + src/plugin-sdk/whatsapp-auth-presence.ts | 1 + src/plugin-sdk/whatsapp-surface.ts | 1 + src/plugin-sdk/xai-model-id.ts | 1 + src/plugin-sdk/zalo-setup.ts | 7 +- src/plugin-sdk/zalo.ts | 1 + src/plugins/provider-self-hosted-setup.ts | 65 ++++ 124 files changed, 1170 insertions(+), 812 deletions(-) create mode 100644 extensions/amazon-bedrock/api.ts rename src/agents/bedrock-discovery.ts => extensions/amazon-bedrock/discovery.ts (77%) create mode 100644 extensions/google/model-id.ts create mode 100644 extensions/sglang/models.ts create mode 100644 extensions/vllm/models.ts create mode 100644 extensions/whatsapp/src/creds-files.ts create mode 100644 extensions/xai/model-id.ts delete mode 100644 src/agents/anthropic-vertex-provider.ts delete mode 100644 src/agents/cloudflare-ai-gateway.ts delete mode 100644 src/agents/google-generative-ai.ts delete mode 100644 src/agents/huggingface-models.ts delete mode 100644 src/agents/model-id-normalization.ts delete mode 100644 src/agents/models-config.providers.discovery.ts delete mode 100644 src/agents/sglang-defaults.ts delete mode 100644 src/agents/vercel-ai-gateway.ts delete mode 100644 src/agents/vllm-defaults.ts create mode 100644 src/plugin-sdk/account-core.ts create mode 100644 src/plugin-sdk/amazon-bedrock.ts create mode 100644 src/plugin-sdk/google-model-id.ts create mode 100644 src/plugin-sdk/mistral.ts create mode 100644 src/plugin-sdk/provider-auth-runtime.ts create mode 100644 src/plugin-sdk/whatsapp-auth-presence.ts create mode 100644 src/plugin-sdk/xai-model-id.ts diff --git a/docs/.generated/plugin-sdk-api-baseline.json b/docs/.generated/plugin-sdk-api-baseline.json index 719ffdbd54d..d1e9a907f13 100644 --- a/docs/.generated/plugin-sdk-api-baseline.json +++ b/docs/.generated/plugin-sdk-api-baseline.json @@ -3144,7 +3144,7 @@ "exportName": "buildChannelOutboundSessionRoute", "kind": "function", "source": { - "line": 181, + "line": 182, "path": "src/plugin-sdk/core.ts" } }, @@ -3189,7 +3189,7 @@ "exportName": "createChannelPluginBase", "kind": "function", "source": { - "line": 527, + "line": 528, "path": "src/plugin-sdk/core.ts" } }, @@ -3198,16 +3198,25 @@ "exportName": "createChatChannelPlugin", "kind": "function", "source": { - "line": 504, + "line": 505, "path": "src/plugin-sdk/core.ts" } }, + { + "declaration": "export function createSubsystemLogger(subsystem: string): SubsystemLogger;", + "exportName": "createSubsystemLogger", + "kind": "function", + "source": { + "line": 308, + "path": "src/logging/subsystem.ts" + } + }, { "declaration": "export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;", "exportName": "defineChannelPluginEntry", "kind": "function", "source": { - "line": 274, + "line": 275, "path": "src/plugin-sdk/core.ts" } }, @@ -3225,7 +3234,7 @@ "exportName": "defineSetupPluginEntry", "kind": "function", "source": { - "line": 311, + "line": 312, "path": "src/plugin-sdk/core.ts" } }, @@ -3423,7 +3432,7 @@ "exportName": "stripChannelTargetPrefix", "kind": "function", "source": { - "line": 161, + "line": 162, "path": "src/plugin-sdk/core.ts" } }, @@ -3432,7 +3441,7 @@ "exportName": "stripTargetKindPrefix", "kind": "function", "source": { - "line": 173, + "line": 174, "path": "src/plugin-sdk/core.ts" } }, @@ -3513,7 +3522,7 @@ "exportName": "ChannelOutboundSessionRouteParams", "kind": "type", "source": { - "line": 156, + "line": 157, "path": "src/plugin-sdk/core.ts" } }, diff --git a/docs/.generated/plugin-sdk-api-baseline.jsonl b/docs/.generated/plugin-sdk-api-baseline.jsonl index 8eb2931787c..423483e616d 100644 --- a/docs/.generated/plugin-sdk-api-baseline.jsonl +++ b/docs/.generated/plugin-sdk-api-baseline.jsonl @@ -345,16 +345,17 @@ {"declaration":"export function applyAccountNameToChannelSection(params: { cfg: OpenClawConfig; channelKey: string; accountId: string; name?: string | undefined; alwaysUseAccounts?: boolean | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"applyAccountNameToChannelSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":34,"sourcePath":"src/channels/plugins/setup-helpers.ts"} {"declaration":"export function buildAgentSessionKey(params: { agentId: string; channel: string; accountId?: string | null | undefined; peer?: RoutePeer | null | undefined; dmScope?: \"main\" | \"per-peer\" | \"per-channel-peer\" | \"per-account-channel-peer\" | undefined; identityLinks?: Record<...> | undefined; }): string;","entrypoint":"core","exportName":"buildAgentSessionKey","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":91,"sourcePath":"src/routing/resolve-route.ts"} {"declaration":"export function buildChannelConfigSchema(schema: ZodType>, options?: BuildChannelConfigSchemaOptions | undefined): ChannelConfigSchema;","entrypoint":"core","exportName":"buildChannelConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":75,"sourcePath":"src/channels/plugins/config-schema.ts"} -{"declaration":"export function buildChannelOutboundSessionRoute(params: { cfg: OpenClawConfig; agentId: string; channel: string; accountId?: string | null | undefined; peer: { kind: \"direct\" | \"group\" | \"channel\"; id: string; }; chatType: \"direct\" | \"group\" | \"channel\"; from: string; to: string; threadId?: string | ... 1 more ... | undefined; }): ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"buildChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":181,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function buildChannelOutboundSessionRoute(params: { cfg: OpenClawConfig; agentId: string; channel: string; accountId?: string | null | undefined; peer: { kind: \"direct\" | \"group\" | \"channel\"; id: string; }; chatType: \"direct\" | \"group\" | \"channel\"; from: string; to: string; threadId?: string | ... 1 more ... | undefined; }): ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"buildChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":182,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function buildPluginConfigSchema(schema: ZodType>, options?: BuildPluginConfigSchemaOptions | undefined): OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"buildPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":78,"sourcePath":"src/plugins/config-schema.ts"} {"declaration":"export function channelTargetSchema(options?: { description?: string | undefined; } | undefined): TString;","entrypoint":"core","exportName":"channelTargetSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/agents/schema/typebox.ts"} {"declaration":"export function channelTargetsSchema(options?: { description?: string | undefined; } | undefined): TArray;","entrypoint":"core","exportName":"channelTargetsSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":44,"sourcePath":"src/agents/schema/typebox.ts"} {"declaration":"export function clearAccountEntryFields(params: { accounts?: Record | undefined; accountId: string; fields: string[]; isValueSet?: ((value: unknown) => boolean) | undefined; markClearedOnFieldPresence?: boolean | undefined; }): { ...; };","entrypoint":"core","exportName":"clearAccountEntryFields","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":122,"sourcePath":"src/channels/plugins/config-helpers.ts"} -{"declaration":"export function createChannelPluginBase(params: CreateChannelPluginBaseOptions): CreatedChannelPluginBase;","entrypoint":"core","exportName":"createChannelPluginBase","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":527,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions<...> | undefined; pairing?: ChannelPairingAdapter | ... 1 more ... | undefined; threading?: ChannelThreadingAdapter | ... 1 more ... | undefined; outbound?: ChannelOutboundAdapter | ... 1 more ... | undefined; }): ChannelPlugin<...>;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":504,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":274,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function createChannelPluginBase(params: CreateChannelPluginBaseOptions): CreatedChannelPluginBase;","entrypoint":"core","exportName":"createChannelPluginBase","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":528,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function createChatChannelPlugin(params: { base: ChatChannelPluginBase; security?: ChannelSecurityAdapter | ChatChannelSecurityOptions<...> | undefined; pairing?: ChannelPairingAdapter | ... 1 more ... | undefined; threading?: ChannelThreadingAdapter | ... 1 more ... | undefined; outbound?: ChannelOutboundAdapter | ... 1 more ... | undefined; }): ChannelPlugin<...>;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":505,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function createSubsystemLogger(subsystem: string): SubsystemLogger;","entrypoint":"core","exportName":"createSubsystemLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":308,"sourcePath":"src/logging/subsystem.ts"} +{"declaration":"export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":275,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":129,"sourcePath":"src/plugin-sdk/plugin-entry.ts"} -{"declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":311,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function defineSetupPluginEntry(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":312,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function delegateCompactionToRuntime(params: { sessionId: string; sessionKey?: string | undefined; sessionFile: string; tokenBudget?: number | undefined; force?: boolean | undefined; currentTokenCount?: number | undefined; compactionTarget?: \"budget\" | ... 1 more ... | undefined; customInstructions?: string | undefined; runtimeContext?: ContextEngineRuntimeContext | undefined; }): Promise<...>;","entrypoint":"core","exportName":"delegateCompactionToRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/context-engine/delegate.ts"} {"declaration":"export function deleteAccountFromConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; clearBaseFields?: string[] | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"deleteAccountFromConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/config-helpers.ts"} {"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":108,"sourcePath":"src/plugins/config-schema.ts"} @@ -376,8 +377,8 @@ {"declaration":"export function resolveThreadSessionKeys(params: { baseSessionKey: string; threadId?: string | null | undefined; parentSessionKey?: string | undefined; useSuffix?: boolean | undefined; normalizeThreadId?: ((threadId: string) => string) | undefined; }): { ...; };","entrypoint":"core","exportName":"resolveThreadSessionKeys","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":234,"sourcePath":"src/routing/session-key.ts"} {"declaration":"export function setAccountEnabledInConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; enabled: boolean; allowTopLevel?: boolean | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"setAccountEnabledInConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/channels/plugins/config-helpers.ts"} {"declaration":"export function stringEnum(values: T, options?: StringEnumOptions): TUnsafe;","entrypoint":"core","exportName":"stringEnum","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":15,"sourcePath":"src/agents/schema/typebox.ts"} -{"declaration":"export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string;","entrypoint":"core","exportName":"stripChannelTargetPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":161,"sourcePath":"src/plugin-sdk/core.ts"} -{"declaration":"export function stripTargetKindPrefix(raw: string): string;","entrypoint":"core","exportName":"stripTargetKindPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":173,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string;","entrypoint":"core","exportName":"stripChannelTargetPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":162,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export function stripTargetKindPrefix(raw: string): string;","entrypoint":"core","exportName":"stripTargetKindPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":174,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export function tryReadSecretFileSync(filePath: string | undefined, label: string, options?: SecretFileReadOptions): string | undefined;","entrypoint":"core","exportName":"tryReadSecretFileSync","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":130,"sourcePath":"src/infra/secret-file.ts"} {"declaration":"export const DEFAULT_ACCOUNT_ID: \"default\";","entrypoint":"core","exportName":"DEFAULT_ACCOUNT_ID","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export","sourceLine":3,"sourcePath":"src/routing/account-id.ts"} {"declaration":"export const DEFAULT_SECRET_FILE_MAX_BYTES: number;","entrypoint":"core","exportName":"DEFAULT_SECRET_FILE_MAX_BYTES","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export","sourceLine":5,"sourcePath":"src/infra/secret-file.ts"} @@ -386,7 +387,7 @@ {"declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"core","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":482,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelMessagingAdapter = ChannelMessagingAdapter;","entrypoint":"core","exportName":"ChannelMessagingAdapter","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":395,"sourcePath":"src/channels/plugins/types.core.ts"} {"declaration":"export type ChannelOutboundSessionRoute = ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"ChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":309,"sourcePath":"src/channels/plugins/types.core.ts"} -{"declaration":"export type ChannelOutboundSessionRouteParams = { cfg: OpenClawConfig; agentId: string; accountId?: string | null; target: string; resolvedTarget?: { to: string; kind: import(\"src/channels/plugins/types.core\").ChannelDirectoryEntryKind | \"channel\"; display?: string; source: \"normalized\" | \"directory\"; }; replyToId?: string | null; threadId?: string | number | null;};","entrypoint":"core","exportName":"ChannelOutboundSessionRouteParams","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":156,"sourcePath":"src/plugin-sdk/core.ts"} +{"declaration":"export type ChannelOutboundSessionRouteParams = { cfg: OpenClawConfig; agentId: string; accountId?: string | null; target: string; resolvedTarget?: { to: string; kind: import(\"src/channels/plugins/types.core\").ChannelDirectoryEntryKind | \"channel\"; display?: string; source: \"normalized\" | \"directory\"; }; replyToId?: string | null; threadId?: string | number | null;};","entrypoint":"core","exportName":"ChannelOutboundSessionRouteParams","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":157,"sourcePath":"src/plugin-sdk/core.ts"} {"declaration":"export type ChannelPlugin = ChannelPlugin;","entrypoint":"core","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/channels/plugins/types.plugin.ts"} {"declaration":"export type GatewayBindUrlResult = GatewayBindUrlResult;","entrypoint":"core","exportName":"GatewayBindUrlResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/shared/gateway-bind-url.ts"} {"declaration":"export type GatewayRequestHandlerOptions = GatewayRequestHandlerOptions;","entrypoint":"core","exportName":"GatewayRequestHandlerOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":115,"sourcePath":"src/gateway/server-methods/types.ts"} diff --git a/extensions/amazon-bedrock/api.ts b/extensions/amazon-bedrock/api.ts new file mode 100644 index 00000000000..880380f970c --- /dev/null +++ b/extensions/amazon-bedrock/api.ts @@ -0,0 +1,7 @@ +export { + discoverBedrockModels, + mergeImplicitBedrockProvider, + resetBedrockDiscoveryCacheForTest, + resolveBedrockConfigApiKey, + resolveImplicitBedrockProvider, +} from "./discovery.js"; diff --git a/src/agents/bedrock-discovery.ts b/extensions/amazon-bedrock/discovery.ts similarity index 77% rename from src/agents/bedrock-discovery.ts rename to extensions/amazon-bedrock/discovery.ts index 85de0457475..a0285e66d10 100644 --- a/src/agents/bedrock-discovery.ts +++ b/extensions/amazon-bedrock/discovery.ts @@ -3,8 +3,13 @@ import { ListFoundationModelsCommand, type ListFoundationModelsCommandOutput, } from "@aws-sdk/client-bedrock"; -import type { BedrockDiscoveryConfig, ModelDefinitionConfig } from "../config/types.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; +import { createSubsystemLogger } from "openclaw/plugin-sdk/core"; +import { resolveAwsSdkEnvVarName } from "openclaw/plugin-sdk/provider-auth-runtime"; +import type { + BedrockDiscoveryConfig, + ModelDefinitionConfig, + ModelProviderConfig, +} from "openclaw/plugin-sdk/provider-models"; const log = createSubsystemLogger("bedrock-discovery"); @@ -145,6 +150,10 @@ export function resetBedrockDiscoveryCacheForTest(): void { hasLoggedBedrockError = false; } +export function resolveBedrockConfigApiKey(env: NodeJS.ProcessEnv = process.env): string { + return resolveAwsSdkEnvVarName(env) ?? "AWS_PROFILE"; +} + export async function discoverBedrockModels(params: { region: string; config?: BedrockDiscoveryConfig; @@ -219,8 +228,60 @@ export async function discoverBedrockModels(params: { } if (!hasLoggedBedrockError) { hasLoggedBedrockError = true; - log.warn(`Failed to list models: ${String(error)}`); + log.warn("Failed to discover Bedrock models", { + error: error instanceof Error ? error.message : String(error), + }); } return []; } } + +export async function resolveImplicitBedrockProvider(params: { + config?: { models?: { bedrockDiscovery?: BedrockDiscoveryConfig } }; + env?: NodeJS.ProcessEnv; +}): Promise { + const env = params.env ?? process.env; + const discoveryConfig = params.config?.models?.bedrockDiscovery; + const enabled = discoveryConfig?.enabled; + const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined; + if (enabled === false) { + return null; + } + if (enabled !== true && !hasAwsCreds) { + return null; + } + + const region = discoveryConfig?.region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1"; + const models = await discoverBedrockModels({ + region, + config: discoveryConfig, + }); + if (models.length === 0) { + return null; + } + + return { + baseUrl: `https://bedrock-runtime.${region}.amazonaws.com`, + api: "bedrock-converse-stream", + auth: "aws-sdk", + models, + }; +} + +export function mergeImplicitBedrockProvider(params: { + existing: ModelProviderConfig | undefined; + implicit: ModelProviderConfig; +}): ModelProviderConfig { + const { existing, implicit } = params; + if (!existing) { + return implicit; + } + return { + ...implicit, + ...existing, + models: + Array.isArray(existing.models) && existing.models.length > 0 + ? existing.models + : implicit.models, + }; +} diff --git a/extensions/anthropic-vertex/api.ts b/extensions/anthropic-vertex/api.ts index fd711809c02..fea76564159 100644 --- a/extensions/anthropic-vertex/api.ts +++ b/extensions/anthropic-vertex/api.ts @@ -1,5 +1,45 @@ +import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-models"; export { ANTHROPIC_VERTEX_DEFAULT_MODEL_ID, buildAnthropicVertexProvider, } from "./provider-catalog.js"; -export { resolveAnthropicVertexRegion } from "./region.js"; +export { + hasAnthropicVertexAvailableAuth, + hasAnthropicVertexCredentials, + resolveAnthropicVertexClientRegion, + resolveAnthropicVertexConfigApiKey, + resolveAnthropicVertexProjectId, + resolveAnthropicVertexRegion, + resolveAnthropicVertexRegionFromBaseUrl, +} from "./region.js"; +import { buildAnthropicVertexProvider } from "./provider-catalog.js"; +import { hasAnthropicVertexAvailableAuth } from "./region.js"; + +export function mergeImplicitAnthropicVertexProvider(params: { + existing: ModelProviderConfig | undefined; + implicit: ModelProviderConfig; +}): ModelProviderConfig { + const { existing, implicit } = params; + if (!existing) { + return implicit; + } + return { + ...implicit, + ...existing, + models: + Array.isArray(existing.models) && existing.models.length > 0 + ? existing.models + : implicit.models, + }; +} + +export function resolveImplicitAnthropicVertexProvider(params?: { + env?: NodeJS.ProcessEnv; +}): ModelProviderConfig | null { + const env = params?.env ?? process.env; + if (!hasAnthropicVertexAvailableAuth(env)) { + return null; + } + + return buildAnthropicVertexProvider({ env }); +} diff --git a/extensions/anthropic-vertex/region.ts b/extensions/anthropic-vertex/region.ts index b01be31a6de..de169dd8ee9 100644 --- a/extensions/anthropic-vertex/region.ts +++ b/extensions/anthropic-vertex/region.ts @@ -1,5 +1,21 @@ +import { existsSync, readFileSync } from "node:fs"; +import { homedir, platform } from "node:os"; +import { join } from "node:path"; + const ANTHROPIC_VERTEX_DEFAULT_REGION = "global"; const ANTHROPIC_VERTEX_REGION_RE = /^[a-z0-9-]+$/; +const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials"; +const GCLOUD_DEFAULT_ADC_PATH = join( + homedir(), + ".config", + "gcloud", + "application_default_credentials.json", +); + +type AdcProjectFile = { + project_id?: unknown; + quota_project_id?: unknown; +}; function normalizeOptionalSecretInput(value: unknown): string | undefined { if (typeof value !== "string") { @@ -18,3 +34,105 @@ export function resolveAnthropicVertexRegion(env: NodeJS.ProcessEnv = process.en ? region : ANTHROPIC_VERTEX_DEFAULT_REGION; } + +export function resolveAnthropicVertexProjectId( + env: NodeJS.ProcessEnv = process.env, +): string | undefined { + return ( + normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_PROJECT_ID) || + normalizeOptionalSecretInput(env.GOOGLE_CLOUD_PROJECT) || + normalizeOptionalSecretInput(env.GOOGLE_CLOUD_PROJECT_ID) || + resolveAnthropicVertexProjectIdFromAdc(env) + ); +} + +export function resolveAnthropicVertexRegionFromBaseUrl(baseUrl?: string): string | undefined { + const trimmed = baseUrl?.trim(); + if (!trimmed) { + return undefined; + } + + try { + const host = new URL(trimmed).hostname.toLowerCase(); + if (host === "aiplatform.googleapis.com") { + return "global"; + } + const match = /^([a-z0-9-]+)-aiplatform\.googleapis\.com$/.exec(host); + return match?.[1]; + } catch { + return undefined; + } +} + +export function resolveAnthropicVertexClientRegion(params?: { + baseUrl?: string; + env?: NodeJS.ProcessEnv; +}): string { + return ( + resolveAnthropicVertexRegionFromBaseUrl(params?.baseUrl) || + resolveAnthropicVertexRegion(params?.env) + ); +} + +function hasAnthropicVertexMetadataServerAdc(env: NodeJS.ProcessEnv = process.env): boolean { + const explicitMetadataOptIn = normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_USE_GCP_METADATA); + return explicitMetadataOptIn === "1" || explicitMetadataOptIn?.toLowerCase() === "true"; +} + +function resolveAnthropicVertexDefaultAdcPath(env: NodeJS.ProcessEnv = process.env): string { + return platform() === "win32" + ? join( + env.APPDATA ?? join(homedir(), "AppData", "Roaming"), + "gcloud", + "application_default_credentials.json", + ) + : GCLOUD_DEFAULT_ADC_PATH; +} + +function resolveAnthropicVertexAdcCredentialsPath( + env: NodeJS.ProcessEnv = process.env, +): string | undefined { + const explicitCredentialsPath = normalizeOptionalSecretInput(env.GOOGLE_APPLICATION_CREDENTIALS); + if (explicitCredentialsPath) { + return existsSync(explicitCredentialsPath) ? explicitCredentialsPath : undefined; + } + + const defaultAdcPath = resolveAnthropicVertexDefaultAdcPath(env); + return existsSync(defaultAdcPath) ? defaultAdcPath : undefined; +} + +function resolveAnthropicVertexProjectIdFromAdc( + env: NodeJS.ProcessEnv = process.env, +): string | undefined { + const credentialsPath = resolveAnthropicVertexAdcCredentialsPath(env); + if (!credentialsPath) { + return undefined; + } + + try { + const parsed = JSON.parse(readFileSync(credentialsPath, "utf8")) as AdcProjectFile; + return ( + normalizeOptionalSecretInput(parsed.project_id) || + normalizeOptionalSecretInput(parsed.quota_project_id) + ); + } catch { + return undefined; + } +} + +export function hasAnthropicVertexCredentials(env: NodeJS.ProcessEnv = process.env): boolean { + return ( + hasAnthropicVertexMetadataServerAdc(env) || + resolveAnthropicVertexAdcCredentialsPath(env) !== undefined + ); +} + +export function hasAnthropicVertexAvailableAuth(env: NodeJS.ProcessEnv = process.env): boolean { + return hasAnthropicVertexCredentials(env); +} + +export function resolveAnthropicVertexConfigApiKey( + env: NodeJS.ProcessEnv = process.env, +): string | undefined { + return hasAnthropicVertexAvailableAuth(env) ? GCP_VERTEX_CREDENTIALS_MARKER : undefined; +} diff --git a/extensions/anthropic/index.ts b/extensions/anthropic/index.ts index 6fa44c30cd3..18adeb7c02e 100644 --- a/extensions/anthropic/index.ts +++ b/extensions/anthropic/index.ts @@ -10,7 +10,6 @@ import { CLAUDE_CLI_PROFILE_ID, applyAuthProfileConfig, buildTokenProfileId, - createProviderApiKeyAuthMethod, ensureApiKeyFromOptionEnvOrPrompt, listProfilesForProvider, normalizeApiKeyInput, @@ -25,6 +24,7 @@ import { validateAnthropicSetupToken, validateApiKeyInput, } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-models"; import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage"; import { buildAnthropicCliBackend } from "./cli-backend.js"; diff --git a/extensions/byteplus/index.ts b/extensions/byteplus/index.ts index 17bf790c616..4c4bca8c0ff 100644 --- a/extensions/byteplus/index.ts +++ b/extensions/byteplus/index.ts @@ -1,5 +1,5 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard"; import { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-catalog.js"; diff --git a/extensions/chutes/index.ts b/extensions/chutes/index.ts index de70c603e23..0dd599ef6d6 100644 --- a/extensions/chutes/index.ts +++ b/extensions/chutes/index.ts @@ -1,11 +1,11 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { - createProviderApiKeyAuthMethod, resolveOAuthApiKeyMarker, type ProviderAuthContext, type ProviderAuthResult, } from "openclaw/plugin-sdk/provider-auth"; import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { loginChutes } from "openclaw/plugin-sdk/provider-auth-login"; import { CHUTES_DEFAULT_MODEL_REF, diff --git a/extensions/discord/api.ts b/extensions/discord/api.ts index daf4ed43ebd..7eb10e69f85 100644 --- a/extensions/discord/api.ts +++ b/extensions/discord/api.ts @@ -12,6 +12,7 @@ export * from "./src/probe.js"; export * from "./src/session-key-normalization.js"; export * from "./src/status-issues.js"; export * from "./src/targets.js"; +export { resolveDiscordRuntimeGroupPolicy } from "./src/monitor/provider.js"; export { DISCORD_DEFAULT_INBOUND_WORKER_TIMEOUT_MS, DISCORD_DEFAULT_LISTENER_TIMEOUT_MS, diff --git a/extensions/discord/src/monitor/provider.test.ts b/extensions/discord/src/monitor/provider.test.ts index 74d28cf7039..7af1f95f3bf 100644 --- a/extensions/discord/src/monitor/provider.test.ts +++ b/extensions/discord/src/monitor/provider.test.ts @@ -653,7 +653,6 @@ describe("monitorDiscordProvider", () => { retry_after: 193.632, global: false, }, - request, ); rateLimitError.discordCode = 30034; clientHandleDeployRequestMock.mockRejectedValueOnce(rateLimitError); diff --git a/extensions/discord/src/monitor/provider.ts b/extensions/discord/src/monitor/provider.ts index 92cc3000ff1..11108541a0d 100644 --- a/extensions/discord/src/monitor/provider.ts +++ b/extensions/discord/src/monitor/provider.ts @@ -1183,3 +1183,5 @@ export const __testing = { shouldLogVerboseForTesting = mock; }, }; + +export const resolveDiscordRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy; diff --git a/extensions/discord/src/send.creates-thread.test.ts b/extensions/discord/src/send.creates-thread.test.ts index aaf07fdec7b..d869f9fd836 100644 --- a/extensions/discord/src/send.creates-thread.test.ts +++ b/extensions/discord/src/send.creates-thread.test.ts @@ -436,15 +436,11 @@ function createMockRateLimitError(retryAfter = 0.001): RateLimitError { "X-RateLimit-Bucket": "test-bucket", }, }); - return createCompatRateLimitError( - response, - { - message: "You are being rate limited.", - retry_after: retryAfter, - global: false, - }, - request, - ); + return createCompatRateLimitError(response, { + message: "You are being rate limited.", + retry_after: retryAfter, + global: false, + }); } describe("retry rate limits", () => { diff --git a/extensions/discord/src/voice-message.ts b/extensions/discord/src/voice-message.ts index 22fe268862c..9d8cf72d25a 100644 --- a/extensions/discord/src/voice-message.ts +++ b/extensions/discord/src/voice-message.ts @@ -296,15 +296,11 @@ export async function sendDiscordVoiceMessage( retry_after?: number; global?: boolean; }; - throw createRateLimitError( - res, - { - message: retryData.message ?? "You are being rate limited.", - retry_after: retryData.retry_after ?? 1, - global: retryData.global ?? false, - }, - uploadUrlRequest, - ); + throw createRateLimitError(res, { + message: retryData.message ?? "You are being rate limited.", + retry_after: retryData.retry_after ?? 1, + global: retryData.global ?? false, + }); } const errorBody = (await res.json().catch(() => null)) as { code?: number; diff --git a/extensions/fal/image-generation-provider.ts b/extensions/fal/image-generation-provider.ts index d5d81124c60..3f5f368d1ae 100644 --- a/extensions/fal/image-generation-provider.ts +++ b/extensions/fal/image-generation-provider.ts @@ -8,7 +8,7 @@ import { type SsrFPolicy, ssrfPolicyFromAllowPrivateNetwork, } from "openclaw/plugin-sdk/infra-runtime"; -import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth"; +import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; const DEFAULT_FAL_BASE_URL = "https://fal.run"; const DEFAULT_FAL_IMAGE_MODEL = "fal-ai/flux/dev"; diff --git a/extensions/fal/index.ts b/extensions/fal/index.ts index 435c8dd0678..77088ee9ff0 100644 --- a/extensions/fal/index.ts +++ b/extensions/fal/index.ts @@ -1,5 +1,5 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { buildFalImageGenerationProvider } from "./image-generation-provider.js"; import { applyFalConfig, FAL_DEFAULT_IMAGE_MODEL_REF } from "./onboard.js"; diff --git a/extensions/google/api.ts b/extensions/google/api.ts index 4cc8a043f4c..d81fd3653cc 100644 --- a/extensions/google/api.ts +++ b/extensions/google/api.ts @@ -1,32 +1,18 @@ +import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-models"; import { applyAgentDefaultModelPrimary, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { - createGoogleThinkingPayloadWrapper, - sanitizeGoogleThinkingPayload, -} from "openclaw/plugin-sdk/provider-stream"; +import { normalizeAntigravityModelId, normalizeGoogleModelId } from "./model-id.js"; +export { normalizeAntigravityModelId, normalizeGoogleModelId }; -export { createGoogleThinkingPayloadWrapper, sanitizeGoogleThinkingPayload }; +type GoogleApiCarrier = { + api?: string | null; +}; -export function normalizeGoogleModelId(id: string): string { - if (id === "gemini-3-pro") { - return "gemini-3-pro-preview"; - } - if (id === "gemini-3-flash") { - return "gemini-3-flash-preview"; - } - if (id === "gemini-3.1-pro") { - return "gemini-3.1-pro-preview"; - } - if (id === "gemini-3.1-flash-lite") { - return "gemini-3.1-flash-lite-preview"; - } - if (id === "gemini-3.1-flash" || id === "gemini-3.1-flash-preview") { - return "gemini-3-flash-preview"; - } - return id; -} +type GoogleProviderConfigLike = GoogleApiCarrier & { + models?: ReadonlyArray | null; +}; const DEFAULT_GOOGLE_API_HOST = "generativelanguage.googleapis.com"; @@ -57,6 +43,97 @@ export function normalizeGoogleApiBaseUrl(baseUrl?: string): string { } } +export function isGoogleGenerativeAiApi(api?: string | null): boolean { + return api === "google-generative-ai"; +} + +export function normalizeGoogleGenerativeAiBaseUrl(baseUrl?: string): string | undefined { + return baseUrl ? normalizeGoogleApiBaseUrl(baseUrl) : baseUrl; +} + +export function resolveGoogleGenerativeAiTransport(params: { + api: TApi; + baseUrl?: string; +}): { api: TApi; baseUrl?: string } { + return { + api: params.api, + baseUrl: isGoogleGenerativeAiApi(params.api) + ? normalizeGoogleGenerativeAiBaseUrl(params.baseUrl) + : params.baseUrl, + }; +} + +export function resolveGoogleGenerativeAiApiOrigin(baseUrl?: string): string { + return normalizeGoogleApiBaseUrl(baseUrl).replace(/\/v1beta$/i, ""); +} + +export function shouldNormalizeGoogleGenerativeAiProviderConfig( + providerKey: string, + provider: GoogleProviderConfigLike, +): boolean { + if (providerKey === "google" || providerKey === "google-vertex") { + return true; + } + if (isGoogleGenerativeAiApi(provider.api)) { + return true; + } + return provider.models?.some((model) => isGoogleGenerativeAiApi(model?.api)) ?? false; +} + +export function shouldNormalizeGoogleProviderConfig( + providerKey: string, + provider: GoogleProviderConfigLike, +): boolean { + return ( + providerKey === "google-antigravity" || + shouldNormalizeGoogleGenerativeAiProviderConfig(providerKey, provider) + ); +} + +function normalizeProviderModels( + provider: ModelProviderConfig, + normalizeId: (id: string) => string, +): ModelProviderConfig { + const models = provider.models; + if (!Array.isArray(models) || models.length === 0) { + return provider; + } + + let mutated = false; + const nextModels = models.map((model) => { + const nextId = normalizeId(model.id); + if (nextId === model.id) { + return model; + } + mutated = true; + return { ...model, id: nextId }; + }); + + return mutated ? { ...provider, models: nextModels } : provider; +} + +export function normalizeGoogleProviderConfig( + providerKey: string, + provider: ModelProviderConfig, +): ModelProviderConfig { + let nextProvider = provider; + + if (shouldNormalizeGoogleGenerativeAiProviderConfig(providerKey, nextProvider)) { + const modelNormalized = normalizeProviderModels(nextProvider, normalizeGoogleModelId); + const normalizedBaseUrl = normalizeGoogleGenerativeAiBaseUrl(modelNormalized.baseUrl); + nextProvider = + normalizedBaseUrl !== modelNormalized.baseUrl + ? { ...modelNormalized, baseUrl: normalizedBaseUrl ?? modelNormalized.baseUrl } + : modelNormalized; + } + + if (providerKey === "google-antigravity") { + nextProvider = normalizeProviderModels(nextProvider, normalizeAntigravityModelId); + } + + return nextProvider; +} + export function parseGeminiAuth(apiKey: string): { headers: Record } { if (apiKey.startsWith("{")) { try { diff --git a/extensions/google/index.ts b/extensions/google/index.ts index 5b585f49410..1e7d00def05 100644 --- a/extensions/google/index.ts +++ b/extensions/google/index.ts @@ -8,11 +8,8 @@ import { } from "openclaw/plugin-sdk/plugin-entry"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-models"; -import { - GOOGLE_GEMINI_DEFAULT_MODEL, - applyGoogleGeminiModelDefault, - createGoogleThinkingPayloadWrapper, -} from "./api.js"; +import { createGoogleThinkingPayloadWrapper } from "openclaw/plugin-sdk/provider-stream"; +import { GOOGLE_GEMINI_DEFAULT_MODEL, applyGoogleGeminiModelDefault } from "./api.js"; import { buildGoogleGeminiCliBackend } from "./cli-backend.js"; import { isModernGoogleModel, resolveGoogle31ForwardCompatModel } from "./provider-models.js"; import { createGeminiWebSearchProvider } from "./src/gemini-web-search-provider.js"; diff --git a/extensions/google/model-id.ts b/extensions/google/model-id.ts new file mode 100644 index 00000000000..076fa1c9059 --- /dev/null +++ b/extensions/google/model-id.ts @@ -0,0 +1,27 @@ +const ANTIGRAVITY_BARE_PRO_IDS = new Set(["gemini-3-pro", "gemini-3.1-pro", "gemini-3-1-pro"]); + +export function normalizeGoogleModelId(id: string): string { + if (id === "gemini-3-pro") { + return "gemini-3-pro-preview"; + } + if (id === "gemini-3-flash") { + return "gemini-3-flash-preview"; + } + if (id === "gemini-3.1-pro") { + return "gemini-3.1-pro-preview"; + } + if (id === "gemini-3.1-flash-lite") { + return "gemini-3.1-flash-lite-preview"; + } + if (id === "gemini-3.1-flash" || id === "gemini-3.1-flash-preview") { + return "gemini-3-flash-preview"; + } + return id; +} + +export function normalizeAntigravityModelId(id: string): string { + if (ANTIGRAVITY_BARE_PRO_IDS.has(id)) { + return `${id}-low`; + } + return id; +} diff --git a/extensions/huggingface/index.ts b/extensions/huggingface/index.ts index 311041825d4..5ef46c834a1 100644 --- a/extensions/huggingface/index.ts +++ b/extensions/huggingface/index.ts @@ -1,5 +1,5 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { applyHuggingfaceConfig, HUGGINGFACE_DEFAULT_MODEL_REF } from "./onboard.js"; import { buildHuggingfaceProvider } from "./provider-catalog.js"; diff --git a/extensions/imessage/api.ts b/extensions/imessage/api.ts index d91c47bac54..af6c2474be2 100644 --- a/extensions/imessage/api.ts +++ b/extensions/imessage/api.ts @@ -3,3 +3,4 @@ export * from "./src/group-policy.js"; export * from "./src/probe.js"; export * from "./src/target-parsing-helpers.js"; export * from "./src/targets.js"; +export { resolveIMessageRuntimeGroupPolicy } from "./src/monitor/monitor-provider.js"; diff --git a/extensions/imessage/runtime-api.ts b/extensions/imessage/runtime-api.ts index be3b48ec58b..39857341bcf 100644 --- a/extensions/imessage/runtime-api.ts +++ b/extensions/imessage/runtime-api.ts @@ -23,4 +23,5 @@ export { export { monitorIMessageProvider } from "./src/monitor.js"; export type { MonitorIMessageOpts } from "./src/monitor.js"; export { probeIMessage } from "./src/probe.js"; +export type { IMessageProbe } from "./src/probe.js"; export { sendMessageIMessage } from "./src/send.js"; diff --git a/extensions/imessage/src/monitor/monitor-provider.ts b/extensions/imessage/src/monitor/monitor-provider.ts index f5524a12f85..77e3db3498f 100644 --- a/extensions/imessage/src/monitor/monitor-provider.ts +++ b/extensions/imessage/src/monitor/monitor-provider.ts @@ -536,3 +536,5 @@ export const __testing = { resolveIMessageRuntimeGroupPolicy: resolveOpenProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, }; + +export const resolveIMessageRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy; diff --git a/extensions/kimi-coding/index.ts b/extensions/kimi-coding/index.ts index f5d58b14df0..2f6bb9450be 100644 --- a/extensions/kimi-coding/index.ts +++ b/extensions/kimi-coding/index.ts @@ -1,5 +1,5 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { isRecord } from "openclaw/plugin-sdk/text-runtime"; import { applyKimiCodeConfig, KIMI_CODING_MODEL_REF } from "./onboard.js"; import { buildKimiCodingProvider } from "./provider-catalog.js"; diff --git a/extensions/minimax/image-generation-provider.ts b/extensions/minimax/image-generation-provider.ts index 143efc0759f..d42d3088863 100644 --- a/extensions/minimax/image-generation-provider.ts +++ b/extensions/minimax/image-generation-provider.ts @@ -1,5 +1,5 @@ import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation"; -import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth"; +import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; const DEFAULT_MINIMAX_IMAGE_BASE_URL = "https://api.minimax.io"; const DEFAULT_MODEL = "image-01"; diff --git a/extensions/minimax/index.ts b/extensions/minimax/index.ts index 549061e36d9..61729c23eb8 100644 --- a/extensions/minimax/index.ts +++ b/extensions/minimax/index.ts @@ -6,11 +6,11 @@ import { } from "openclaw/plugin-sdk/plugin-entry"; import { MINIMAX_OAUTH_MARKER, - createProviderApiKeyAuthMethod, ensureAuthProfileStore, listProfilesForProvider, } from "openclaw/plugin-sdk/provider-auth"; import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { fetchMinimaxUsage } from "openclaw/plugin-sdk/provider-usage"; import { isMiniMaxModernModelId, MINIMAX_DEFAULT_MODEL_ID } from "./api.js"; import { diff --git a/extensions/mistral/api.ts b/extensions/mistral/api.ts index 440f4601ad7..dfaa2f5b087 100644 --- a/extensions/mistral/api.ts +++ b/extensions/mistral/api.ts @@ -1,9 +1 @@ -export { applyMistralConfig, applyMistralProviderConfig } from "./onboard.js"; -export { - buildMistralCatalogModels, - buildMistralModelDefinition, - MISTRAL_BASE_URL, - MISTRAL_DEFAULT_COST, - MISTRAL_DEFAULT_MODEL_ID, - MISTRAL_DEFAULT_MODEL_REF, -} from "./model-definitions.js"; +export { buildMistralProvider } from "./provider-catalog.js"; diff --git a/extensions/modelstudio/api.ts b/extensions/modelstudio/api.ts index 7c90078e7e6..0ae9ad13acf 100644 --- a/extensions/modelstudio/api.ts +++ b/extensions/modelstudio/api.ts @@ -1,6 +1,8 @@ export { + applyModelStudioNativeStreamingUsageCompat, buildModelStudioDefaultModelDefinition, buildModelStudioModelDefinition, + isNativeModelStudioBaseUrl, MODELSTUDIO_BASE_URL, MODELSTUDIO_CN_BASE_URL, MODELSTUDIO_DEFAULT_COST, diff --git a/extensions/modelstudio/models.ts b/extensions/modelstudio/models.ts index ba44c9599e8..00c0ae5f5ec 100644 --- a/extensions/modelstudio/models.ts +++ b/extensions/modelstudio/models.ts @@ -1,4 +1,7 @@ -import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-models"; +import type { + ModelDefinitionConfig, + ModelProviderConfig, +} from "openclaw/plugin-sdk/provider-models"; export const MODELSTUDIO_BASE_URL = "https://coding-intl.dashscope.aliyuncs.com/v1"; export const MODELSTUDIO_GLOBAL_BASE_URL = MODELSTUDIO_BASE_URL; @@ -91,6 +94,62 @@ export const MODELSTUDIO_MODEL_CATALOG: ReadonlyArray = [ }, ]; +function normalizeModelStudioBaseUrl(baseUrl: string | undefined): string { + const trimmed = baseUrl?.trim(); + if (!trimmed) { + return ""; + } + try { + const url = new URL(trimmed); + url.hash = ""; + url.search = ""; + return url.toString().replace(/\/+$/, "").toLowerCase(); + } catch { + return trimmed.replace(/\/+$/, "").toLowerCase(); + } +} + +export function isNativeModelStudioBaseUrl(baseUrl: string | undefined): boolean { + const normalized = normalizeModelStudioBaseUrl(baseUrl); + return ( + normalized === MODELSTUDIO_BASE_URL || + normalized === MODELSTUDIO_CN_BASE_URL || + normalized === MODELSTUDIO_STANDARD_CN_BASE_URL || + normalized === MODELSTUDIO_STANDARD_GLOBAL_BASE_URL + ); +} + +function withStreamingUsageCompat(provider: ModelProviderConfig): ModelProviderConfig { + if (!Array.isArray(provider.models) || provider.models.length === 0) { + return provider; + } + + let changed = false; + const models = provider.models.map((model) => { + if (model.compat?.supportsUsageInStreaming !== undefined) { + return model; + } + changed = true; + return { + ...model, + compat: { + ...model.compat, + supportsUsageInStreaming: true, + }, + }; + }); + + return changed ? { ...provider, models } : provider; +} + +export function applyModelStudioNativeStreamingUsageCompat( + provider: ModelProviderConfig, +): ModelProviderConfig { + return isNativeModelStudioBaseUrl(provider.baseUrl) + ? withStreamingUsageCompat(provider) + : provider; +} + export function buildModelStudioModelDefinition(params: { id: string; name?: string; diff --git a/extensions/moonshot/api.ts b/extensions/moonshot/api.ts index 355343763d4..ef700cd6825 100644 --- a/extensions/moonshot/api.ts +++ b/extensions/moonshot/api.ts @@ -1,6 +1,9 @@ export { + applyMoonshotNativeStreamingUsageCompat, buildMoonshotProvider, + isNativeMoonshotBaseUrl, MOONSHOT_BASE_URL, + MOONSHOT_CN_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, } from "./provider-catalog.js"; -export { MOONSHOT_CN_BASE_URL, MOONSHOT_DEFAULT_MODEL_REF } from "./onboard.js"; +export { MOONSHOT_DEFAULT_MODEL_REF } from "./onboard.js"; diff --git a/extensions/moonshot/onboard.ts b/extensions/moonshot/onboard.ts index 28397b90e14..029c2c61839 100644 --- a/extensions/moonshot/onboard.ts +++ b/extensions/moonshot/onboard.ts @@ -5,10 +5,9 @@ import { import { buildMoonshotProvider, MOONSHOT_BASE_URL, + MOONSHOT_CN_BASE_URL, MOONSHOT_DEFAULT_MODEL_ID, } from "./provider-catalog.js"; - -export const MOONSHOT_CN_BASE_URL = "https://api.moonshot.cn/v1"; export const MOONSHOT_DEFAULT_MODEL_REF = `moonshot/${MOONSHOT_DEFAULT_MODEL_ID}`; const moonshotPresetAppliers = createDefaultModelPresetAppliers<[string]>({ diff --git a/extensions/moonshot/provider-catalog.ts b/extensions/moonshot/provider-catalog.ts index c3684f2e7c3..12718503e1a 100644 --- a/extensions/moonshot/provider-catalog.ts +++ b/extensions/moonshot/provider-catalog.ts @@ -1,6 +1,7 @@ import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-models"; export const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1"; +export const MOONSHOT_CN_BASE_URL = "https://api.moonshot.cn/v1"; export const MOONSHOT_DEFAULT_MODEL_ID = "kimi-k2.5"; const MOONSHOT_DEFAULT_CONTEXT_WINDOW = 262144; const MOONSHOT_DEFAULT_MAX_TOKENS = 262144; @@ -50,6 +51,55 @@ const MOONSHOT_MODEL_CATALOG = [ }, ] as const; +function normalizeMoonshotBaseUrl(baseUrl: string | undefined): string { + const trimmed = baseUrl?.trim(); + if (!trimmed) { + return ""; + } + try { + const url = new URL(trimmed); + url.hash = ""; + url.search = ""; + return url.toString().replace(/\/+$/, "").toLowerCase(); + } catch { + return trimmed.replace(/\/+$/, "").toLowerCase(); + } +} + +export function isNativeMoonshotBaseUrl(baseUrl: string | undefined): boolean { + const normalized = normalizeMoonshotBaseUrl(baseUrl); + return normalized === MOONSHOT_BASE_URL || normalized === MOONSHOT_CN_BASE_URL; +} + +function withStreamingUsageCompat(provider: ModelProviderConfig): ModelProviderConfig { + if (!Array.isArray(provider.models) || provider.models.length === 0) { + return provider; + } + + let changed = false; + const models = provider.models.map((model) => { + if (model.compat?.supportsUsageInStreaming !== undefined) { + return model; + } + changed = true; + return { + ...model, + compat: { + ...model.compat, + supportsUsageInStreaming: true, + }, + }; + }); + + return changed ? { ...provider, models } : provider; +} + +export function applyMoonshotNativeStreamingUsageCompat( + provider: ModelProviderConfig, +): ModelProviderConfig { + return isNativeMoonshotBaseUrl(provider.baseUrl) ? withStreamingUsageCompat(provider) : provider; +} + export function buildMoonshotProvider(): ModelProviderConfig { return { baseUrl: MOONSHOT_BASE_URL, diff --git a/extensions/openai/image-generation-provider.ts b/extensions/openai/image-generation-provider.ts index 2eb74972b93..c7338a1be4f 100644 --- a/extensions/openai/image-generation-provider.ts +++ b/extensions/openai/image-generation-provider.ts @@ -1,5 +1,5 @@ import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation"; -import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth"; +import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; import { OPENAI_DEFAULT_IMAGE_MODEL as DEFAULT_OPENAI_IMAGE_MODEL } from "./default-models.js"; const DEFAULT_OPENAI_IMAGE_BASE_URL = "https://api.openai.com/v1"; diff --git a/extensions/openai/openai-provider.ts b/extensions/openai/openai-provider.ts index 54c074ed90a..b2ed4e85e77 100644 --- a/extensions/openai/openai-provider.ts +++ b/extensions/openai/openai-provider.ts @@ -2,7 +2,7 @@ import { type ProviderResolveDynamicModelContext, type ProviderRuntimeModel, } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { DEFAULT_CONTEXT_TOKENS, normalizeModelCompat, diff --git a/extensions/opencode-go/index.ts b/extensions/opencode-go/index.ts index 17186612e19..3732fb15349 100644 --- a/extensions/opencode-go/index.ts +++ b/extensions/opencode-go/index.ts @@ -1,5 +1,5 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { applyOpencodeGoConfig, OPENCODE_GO_DEFAULT_MODEL_REF } from "./api.js"; const PROVIDER_ID = "opencode-go"; diff --git a/extensions/opencode/index.ts b/extensions/opencode/index.ts index 4aafd2714ec..6be8e688c9a 100644 --- a/extensions/opencode/index.ts +++ b/extensions/opencode/index.ts @@ -1,6 +1,6 @@ import { isMiniMaxModernModelId } from "openclaw/plugin-sdk/minimax"; import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { applyOpencodeZenConfig, OPENCODE_ZEN_DEFAULT_MODEL } from "./api.js"; const PROVIDER_ID = "opencode"; diff --git a/extensions/sglang/api.ts b/extensions/sglang/api.ts index aaad53efe7e..81f0bd62750 100644 --- a/extensions/sglang/api.ts +++ b/extensions/sglang/api.ts @@ -4,3 +4,4 @@ export { SGLANG_MODEL_PLACEHOLDER, SGLANG_PROVIDER_LABEL, } from "./defaults.js"; +export { buildSglangProvider } from "./models.js"; diff --git a/extensions/sglang/index.ts b/extensions/sglang/index.ts index 94011d6f5d8..d00ff40ce10 100644 --- a/extensions/sglang/index.ts +++ b/extensions/sglang/index.ts @@ -8,6 +8,7 @@ import { SGLANG_DEFAULT_BASE_URL, SGLANG_MODEL_PLACEHOLDER, SGLANG_PROVIDER_LABEL, + buildSglangProvider, } from "./api.js"; const PROVIDER_ID = "sglang"; @@ -64,7 +65,7 @@ export default definePluginEntry({ return await providerSetup.discoverOpenAICompatibleSelfHostedProvider({ ctx, providerId: PROVIDER_ID, - buildProvider: providerSetup.buildSglangProvider, + buildProvider: buildSglangProvider, }); }, }, diff --git a/extensions/sglang/models.ts b/extensions/sglang/models.ts new file mode 100644 index 00000000000..97c80f81da1 --- /dev/null +++ b/extensions/sglang/models.ts @@ -0,0 +1,23 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { discoverOpenAICompatibleLocalModels } from "openclaw/plugin-sdk/provider-setup"; +import { SGLANG_DEFAULT_BASE_URL, SGLANG_PROVIDER_LABEL } from "./defaults.js"; + +type ModelsConfig = NonNullable; +type ProviderConfig = NonNullable[string]; + +export async function buildSglangProvider(params?: { + baseUrl?: string; + apiKey?: string; +}): Promise { + const baseUrl = (params?.baseUrl?.trim() || SGLANG_DEFAULT_BASE_URL).replace(/\/+$/, ""); + const models = await discoverOpenAICompatibleLocalModels({ + baseUrl, + apiKey: params?.apiKey, + label: SGLANG_PROVIDER_LABEL, + }); + return { + baseUrl, + api: "openai-completions", + models, + }; +} diff --git a/extensions/slack/api.ts b/extensions/slack/api.ts index 782854bf11a..9c995c289f1 100644 --- a/extensions/slack/api.ts +++ b/extensions/slack/api.ts @@ -16,3 +16,4 @@ export * from "./src/probe.js"; export * from "./src/sent-thread-cache.js"; export * from "./src/targets.js"; export * from "./src/threading-tool-context.js"; +export { resolveSlackRuntimeGroupPolicy } from "./src/monitor/provider.js"; diff --git a/extensions/slack/src/monitor/provider.ts b/extensions/slack/src/monitor/provider.ts index 133fb0a03f0..43332a202c4 100644 --- a/extensions/slack/src/monitor/provider.ts +++ b/extensions/slack/src/monitor/provider.ts @@ -584,6 +584,8 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { export { isNonRecoverableSlackAuthError } from "./reconnect-policy.js"; +export const resolveSlackRuntimeGroupPolicy = resolveOpenProviderRuntimeGroupPolicy; + export const __testing = { publishSlackConnectedStatus, publishSlackDisconnectedStatus, diff --git a/extensions/telegram/runtime-api.ts b/extensions/telegram/runtime-api.ts index 674d428aa76..b744a2804a4 100644 --- a/extensions/telegram/runtime-api.ts +++ b/extensions/telegram/runtime-api.ts @@ -85,6 +85,7 @@ export { export { createTelegramThreadBindingManager, getTelegramThreadBindingManager, + resetTelegramThreadBindingsForTests, setTelegramThreadBindingIdleTimeoutBySessionKey, setTelegramThreadBindingMaxAgeBySessionKey, } from "./src/thread-bindings.js"; diff --git a/extensions/telegram/src/thread-bindings.ts b/extensions/telegram/src/thread-bindings.ts index e145679c0cb..9f0ca03868f 100644 --- a/extensions/telegram/src/thread-bindings.ts +++ b/extensions/telegram/src/thread-bindings.ts @@ -808,14 +808,16 @@ export function setTelegramThreadBindingMaxAgeBySessionKey(params: { }); } +export async function resetTelegramThreadBindingsForTests() { + for (const manager of getThreadBindingsState().managersByAccountId.values()) { + manager.stop(); + } + await Promise.allSettled(getThreadBindingsState().persistQueueByAccountId.values()); + getThreadBindingsState().persistQueueByAccountId.clear(); + getThreadBindingsState().managersByAccountId.clear(); + getThreadBindingsState().bindingsByAccountConversation.clear(); +} + export const __testing = { - async resetTelegramThreadBindingsForTests() { - for (const manager of getThreadBindingsState().managersByAccountId.values()) { - manager.stop(); - } - await Promise.allSettled(getThreadBindingsState().persistQueueByAccountId.values()); - getThreadBindingsState().persistQueueByAccountId.clear(); - getThreadBindingsState().managersByAccountId.clear(); - getThreadBindingsState().bindingsByAccountConversation.clear(); - }, + resetTelegramThreadBindingsForTests, }; diff --git a/extensions/vllm/api.ts b/extensions/vllm/api.ts index bdb6d4a334c..b01c8e166bc 100644 --- a/extensions/vllm/api.ts +++ b/extensions/vllm/api.ts @@ -4,3 +4,4 @@ export { VLLM_MODEL_PLACEHOLDER, VLLM_PROVIDER_LABEL, } from "./defaults.js"; +export { buildVllmProvider } from "./models.js"; diff --git a/extensions/vllm/index.ts b/extensions/vllm/index.ts index 0614feb56b0..96d78cfeb64 100644 --- a/extensions/vllm/index.ts +++ b/extensions/vllm/index.ts @@ -8,6 +8,7 @@ import { VLLM_DEFAULT_BASE_URL, VLLM_MODEL_PLACEHOLDER, VLLM_PROVIDER_LABEL, + buildVllmProvider, } from "./api.js"; const PROVIDER_ID = "vllm"; @@ -64,7 +65,7 @@ export default definePluginEntry({ return await providerSetup.discoverOpenAICompatibleSelfHostedProvider({ ctx, providerId: PROVIDER_ID, - buildProvider: providerSetup.buildVllmProvider, + buildProvider: buildVllmProvider, }); }, }, diff --git a/extensions/vllm/models.ts b/extensions/vllm/models.ts new file mode 100644 index 00000000000..1b1c886e15f --- /dev/null +++ b/extensions/vllm/models.ts @@ -0,0 +1,23 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { discoverOpenAICompatibleLocalModels } from "openclaw/plugin-sdk/provider-setup"; +import { VLLM_DEFAULT_BASE_URL, VLLM_PROVIDER_LABEL } from "./defaults.js"; + +type ModelsConfig = NonNullable; +type ProviderConfig = NonNullable[string]; + +export async function buildVllmProvider(params?: { + baseUrl?: string; + apiKey?: string; +}): Promise { + const baseUrl = (params?.baseUrl?.trim() || VLLM_DEFAULT_BASE_URL).replace(/\/+$/, ""); + const models = await discoverOpenAICompatibleLocalModels({ + baseUrl, + apiKey: params?.apiKey, + label: VLLM_PROVIDER_LABEL, + }); + return { + baseUrl, + api: "openai-completions", + models, + }; +} diff --git a/extensions/volcengine/index.ts b/extensions/volcengine/index.ts index 9533b9a2f6f..d353534cd10 100644 --- a/extensions/volcengine/index.ts +++ b/extensions/volcengine/index.ts @@ -1,5 +1,5 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard"; import { buildDoubaoCodingProvider, buildDoubaoProvider } from "./provider-catalog.js"; diff --git a/extensions/whatsapp/src/accounts.ts b/extensions/whatsapp/src/accounts.ts index bf9b9243099..9de02f09ea2 100644 --- a/extensions/whatsapp/src/accounts.ts +++ b/extensions/whatsapp/src/accounts.ts @@ -8,9 +8,9 @@ import { resolveMergedAccountConfig, resolveUserPath, type OpenClawConfig, -} from "openclaw/plugin-sdk/account-resolution"; +} from "openclaw/plugin-sdk/account-core"; import { resolveOAuthDir } from "openclaw/plugin-sdk/state-paths"; -import { hasWebCredsSync } from "./auth-store.js"; +import { hasWebCredsSync } from "./creds-files.js"; import type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "./runtime-api.js"; export type ResolvedWhatsAppAccount = { diff --git a/extensions/whatsapp/src/auth-store.ts b/extensions/whatsapp/src/auth-store.ts index 9d782a9b0c0..b58620755a0 100644 --- a/extensions/whatsapp/src/auth-store.ts +++ b/extensions/whatsapp/src/auth-store.ts @@ -9,7 +9,9 @@ import { defaultRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env import { resolveOAuthDir } from "openclaw/plugin-sdk/state-paths"; import type { WebChannel } from "openclaw/plugin-sdk/text-runtime"; import { resolveUserPath } from "openclaw/plugin-sdk/text-runtime"; +import { hasWebCredsSync, resolveWebCredsBackupPath, resolveWebCredsPath } from "./creds-files.js"; import { resolveComparableIdentity, type WhatsAppSelfIdentity } from "./identity.js"; +export { hasWebCredsSync, resolveWebCredsBackupPath, resolveWebCredsPath }; export function resolveDefaultWebAuthDir(): string { return path.join(resolveOAuthDir(), "whatsapp", DEFAULT_ACCOUNT_ID); @@ -17,23 +19,6 @@ export function resolveDefaultWebAuthDir(): string { export const WA_WEB_AUTH_DIR = resolveDefaultWebAuthDir(); -export function resolveWebCredsPath(authDir: string): string { - return path.join(authDir, "creds.json"); -} - -export function resolveWebCredsBackupPath(authDir: string): string { - return path.join(authDir, "creds.json.bak"); -} - -export function hasWebCredsSync(authDir: string): boolean { - try { - const stats = fsSync.statSync(resolveWebCredsPath(authDir)); - return stats.isFile() && stats.size > 1; - } catch { - return false; - } -} - export function readCredsJsonRaw(filePath: string): string | null { try { if (!fsSync.existsSync(filePath)) { diff --git a/extensions/whatsapp/src/creds-files.ts b/extensions/whatsapp/src/creds-files.ts new file mode 100644 index 00000000000..163ed839159 --- /dev/null +++ b/extensions/whatsapp/src/creds-files.ts @@ -0,0 +1,19 @@ +import fsSync from "node:fs"; +import path from "node:path"; + +export function resolveWebCredsPath(authDir: string): string { + return path.join(authDir, "creds.json"); +} + +export function resolveWebCredsBackupPath(authDir: string): string { + return path.join(authDir, "creds.json.bak"); +} + +export function hasWebCredsSync(authDir: string): boolean { + try { + const stats = fsSync.statSync(resolveWebCredsPath(authDir)); + return stats.isFile() && stats.size > 1; + } catch { + return false; + } +} diff --git a/extensions/xai/api.ts b/extensions/xai/api.ts index cd79f82df38..319b345f6e0 100644 --- a/extensions/xai/api.ts +++ b/extensions/xai/api.ts @@ -10,6 +10,8 @@ export { XAI_DEFAULT_MAX_TOKENS, } from "./model-definitions.js"; export { isModernXaiModel, resolveXaiForwardCompatModel } from "./provider-models.js"; +import { normalizeXaiModelId } from "./model-id.js"; +export { normalizeXaiModelId }; export const XAI_TOOL_SCHEMA_PROFILE = "xai"; export const HTML_ENTITY_TOOL_CALL_ARGUMENTS_ENCODING = "html-entities"; @@ -35,25 +37,3 @@ export function applyXaiModelCompat(model: T): T } as T extends { compat?: infer TCompat } ? TCompat : never, } as T; } - -export function normalizeXaiModelId(id: string): string { - if (id === "grok-4-fast-reasoning") { - return "grok-4-fast"; - } - if (id === "grok-4-1-fast-reasoning") { - return "grok-4-1-fast"; - } - if (id === "grok-4.20-experimental-beta-0304-reasoning") { - return "grok-4.20-beta-latest-reasoning"; - } - if (id === "grok-4.20-experimental-beta-0304-non-reasoning") { - return "grok-4.20-beta-latest-non-reasoning"; - } - if (id === "grok-4.20-reasoning") { - return "grok-4.20-beta-latest-reasoning"; - } - if (id === "grok-4.20-non-reasoning") { - return "grok-4.20-beta-latest-non-reasoning"; - } - return id; -} diff --git a/extensions/xai/model-id.ts b/extensions/xai/model-id.ts new file mode 100644 index 00000000000..9bf95e28a32 --- /dev/null +++ b/extensions/xai/model-id.ts @@ -0,0 +1,21 @@ +export function normalizeXaiModelId(id: string): string { + if (id === "grok-4-fast-reasoning") { + return "grok-4-fast"; + } + if (id === "grok-4-1-fast-reasoning") { + return "grok-4-1-fast"; + } + if (id === "grok-4.20-experimental-beta-0304-reasoning") { + return "grok-4.20-beta-latest-reasoning"; + } + if (id === "grok-4.20-experimental-beta-0304-non-reasoning") { + return "grok-4.20-beta-latest-non-reasoning"; + } + if (id === "grok-4.20-reasoning") { + return "grok-4.20-beta-latest-reasoning"; + } + if (id === "grok-4.20-non-reasoning") { + return "grok-4.20-beta-latest-non-reasoning"; + } + return id; +} diff --git a/package.json b/package.json index d5eed19334d..cef86b49d65 100644 --- a/package.json +++ b/package.json @@ -224,6 +224,10 @@ "types": "./dist/plugin-sdk/account-helpers.d.ts", "default": "./dist/plugin-sdk/account-helpers.js" }, + "./plugin-sdk/account-core": { + "types": "./dist/plugin-sdk/account-core.d.ts", + "default": "./dist/plugin-sdk/account-core.js" + }, "./plugin-sdk/account-id": { "types": "./dist/plugin-sdk/account-id.d.ts", "default": "./dist/plugin-sdk/account-id.js" @@ -236,6 +240,10 @@ "types": "./dist/plugin-sdk/agent-config-primitives.d.ts", "default": "./dist/plugin-sdk/agent-config-primitives.js" }, + "./plugin-sdk/amazon-bedrock": { + "types": "./dist/plugin-sdk/amazon-bedrock.d.ts", + "default": "./dist/plugin-sdk/amazon-bedrock.js" + }, "./plugin-sdk/allow-from": { "types": "./dist/plugin-sdk/allow-from.d.ts", "default": "./dist/plugin-sdk/allow-from.js" @@ -612,6 +620,10 @@ "types": "./dist/plugin-sdk/moonshot.d.ts", "default": "./dist/plugin-sdk/moonshot.js" }, + "./plugin-sdk/mistral": { + "types": "./dist/plugin-sdk/mistral.d.ts", + "default": "./dist/plugin-sdk/mistral.js" + }, "./plugin-sdk/msteams": { "types": "./dist/plugin-sdk/msteams.d.ts", "default": "./dist/plugin-sdk/msteams.js" @@ -656,6 +668,10 @@ "types": "./dist/plugin-sdk/provider-auth.d.ts", "default": "./dist/plugin-sdk/provider-auth.js" }, + "./plugin-sdk/provider-auth-runtime": { + "types": "./dist/plugin-sdk/provider-auth-runtime.d.ts", + "default": "./dist/plugin-sdk/provider-auth-runtime.js" + }, "./plugin-sdk/provider-auth-api-key": { "types": "./dist/plugin-sdk/provider-auth-api-key.d.ts", "default": "./dist/plugin-sdk/provider-auth-api-key.js" @@ -868,6 +884,10 @@ "types": "./dist/plugin-sdk/volcengine.d.ts", "default": "./dist/plugin-sdk/volcengine.js" }, + "./plugin-sdk/whatsapp-auth-presence": { + "types": "./dist/plugin-sdk/whatsapp-auth-presence.d.ts", + "default": "./dist/plugin-sdk/whatsapp-auth-presence.js" + }, "./plugin-sdk/whatsapp-core": { "types": "./dist/plugin-sdk/whatsapp-core.d.ts", "default": "./dist/plugin-sdk/whatsapp-core.js" diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 2ce76fe098a..0d2e018201f 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -46,9 +46,11 @@ "testing", "temp-path", "account-helpers", + "account-core", "account-id", "account-resolution", "agent-config-primitives", + "amazon-bedrock", "allow-from", "allowlist-config-edit", "anthropic-vertex", @@ -143,6 +145,7 @@ "modelstudio", "modelstudio-definitions", "moonshot", + "mistral", "msteams", "nextcloud-talk", "nvidia", @@ -154,6 +157,7 @@ "opencode-go", "qianfan", "provider-auth", + "provider-auth-runtime", "provider-auth-api-key", "provider-auth-result", "provider-auth-login", @@ -207,6 +211,7 @@ "web-media", "voice-call", "volcengine", + "whatsapp-auth-presence", "whatsapp-core", "whatsapp-shared", "whatsapp-surface", diff --git a/scripts/lib/plugin-sdk-facades.mjs b/scripts/lib/plugin-sdk-facades.mjs index 4e758dbce63..d117640b4bd 100644 --- a/scripts/lib/plugin-sdk-facades.mjs +++ b/scripts/lib/plugin-sdk-facades.mjs @@ -3,13 +3,32 @@ import path from "node:path"; import ts from "typescript"; export const GENERATED_PLUGIN_SDK_FACADES = [ + { + subpath: "amazon-bedrock", + source: "../../extensions/amazon-bedrock/api.js", + exports: [ + "discoverBedrockModels", + "mergeImplicitBedrockProvider", + "resetBedrockDiscoveryCacheForTest", + "resolveBedrockConfigApiKey", + "resolveImplicitBedrockProvider", + ], + }, { subpath: "anthropic-vertex", source: "../../extensions/anthropic-vertex/api.js", exports: [ "ANTHROPIC_VERTEX_DEFAULT_MODEL_ID", "buildAnthropicVertexProvider", + "hasAnthropicVertexAvailableAuth", + "hasAnthropicVertexCredentials", + "mergeImplicitAnthropicVertexProvider", + "resolveAnthropicVertexClientRegion", + "resolveAnthropicVertexConfigApiKey", + "resolveImplicitAnthropicVertexProvider", + "resolveAnthropicVertexProjectId", "resolveAnthropicVertexRegion", + "resolveAnthropicVertexRegionFromBaseUrl", ], }, { @@ -99,6 +118,8 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "DiscordSendResult", "handleDiscordMessageAction", "inspectDiscordAccount", + "isDiscordExecApprovalApprover", + "isDiscordExecApprovalClientEnabled", "InspectedDiscordAccount", "listDiscordAccountIds", "listDiscordDirectoryGroupsFromConfig", @@ -111,14 +132,17 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "resolveDefaultDiscordAccountId", "resolveDiscordAccount", "resolveDiscordChannelId", + "resolveDiscordRuntimeGroupPolicy", "resolveDiscordGroupRequireMention", "resolveDiscordGroupToolPolicy", ], typeExports: [ "DiscordComponentMessageSpec", + "DiscordProbe", "DiscordSendComponents", "DiscordSendEmbeds", "DiscordSendResult", + "DiscordTokenResolution", "InspectedDiscordAccount", "ResolvedDiscordAccount", ], @@ -239,6 +263,8 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ exports: [ "buildFeishuConversationId", "createFeishuThreadBindingManager", + "feishuSessionBindingAdapterChannels", + "feishuThreadBindingTesting", "parseFeishuDirectConversationId", "parseFeishuConversationId", "parseFeishuTargetId", @@ -249,13 +275,19 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ source: "../../extensions/google/api.js", exports: [ "applyGoogleGeminiModelDefault", - "createGoogleThinkingPayloadWrapper", "DEFAULT_GOOGLE_API_BASE_URL", "GOOGLE_GEMINI_DEFAULT_MODEL", + "isGoogleGenerativeAiApi", + "normalizeAntigravityModelId", "normalizeGoogleApiBaseUrl", + "normalizeGoogleGenerativeAiBaseUrl", "normalizeGoogleModelId", + "normalizeGoogleProviderConfig", "parseGeminiAuth", - "sanitizeGoogleThinkingPayload", + "resolveGoogleGenerativeAiApiOrigin", + "resolveGoogleGenerativeAiTransport", + "shouldNormalizeGoogleProviderConfig", + "shouldNormalizeGoogleGenerativeAiProviderConfig", ], }, { @@ -329,6 +361,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ source: "../../extensions/imessage/api.js", exports: [ "normalizeIMessageHandle", + "resolveIMessageRuntimeGroupPolicy", "resolveIMessageGroupRequireMention", "resolveIMessageGroupToolPolicy", ], @@ -337,6 +370,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ subpath: "imessage-runtime", source: "../../extensions/imessage/runtime-api.js", exports: ["monitorIMessageProvider", "probeIMessage", "sendMessageIMessage"], + typeExports: ["IMessageProbe"], }, { subpath: "irc-surface", @@ -465,7 +499,11 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ { subpath: "matrix-surface", source: "../../extensions/matrix/api.js", - exports: ["createMatrixThreadBindingManager", "resetMatrixThreadBindingsForTests"], + exports: [ + "createMatrixThreadBindingManager", + "matrixSessionBindingAdapterChannels", + "resetMatrixThreadBindingsForTests", + ], }, { subpath: "matrix-thread-bindings", @@ -487,6 +525,8 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "buildMinimaxPortalProvider", "buildMinimaxProvider", "isMiniMaxModernModelId", + "MINIMAX_API_BASE_URL", + "MINIMAX_CN_API_BASE_URL", "MINIMAX_DEFAULT_MODEL_ID", "MINIMAX_DEFAULT_MODEL_REF", "MINIMAX_TEXT_MODEL_CATALOG", @@ -498,6 +538,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ subpath: "modelstudio", source: "../../extensions/modelstudio/api.js", exports: [ + "applyModelStudioNativeStreamingUsageCompat", "buildModelStudioDefaultModelDefinition", "buildModelStudioModelDefinition", "MODELSTUDIO_BASE_URL", @@ -509,6 +550,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "MODELSTUDIO_STANDARD_CN_BASE_URL", "MODELSTUDIO_STANDARD_GLOBAL_BASE_URL", "MODELSTUDIO_MODEL_CATALOG", + "isNativeModelStudioBaseUrl", "buildModelStudioProvider", ], }, @@ -530,7 +572,20 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ { subpath: "moonshot", source: "../../extensions/moonshot/api.js", - exports: ["buildMoonshotProvider"], + exports: [ + "applyMoonshotNativeStreamingUsageCompat", + "buildMoonshotProvider", + "isNativeMoonshotBaseUrl", + "MOONSHOT_BASE_URL", + "MOONSHOT_CN_BASE_URL", + "MOONSHOT_DEFAULT_MODEL_ID", + "MOONSHOT_DEFAULT_MODEL_REF", + ], + }, + { + subpath: "mistral", + source: "../../extensions/mistral/api.js", + exports: ["buildMistralProvider"], }, { subpath: "nvidia", @@ -633,7 +688,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "signalMessageActions", "SignalSender", ], - typeExports: ["ResolvedSignalAccount", "SignalSender"], + typeExports: ["ResolvedSignalAccount", "SignalProbe", "SignalSender"], }, { subpath: "provider-reasoning", @@ -649,6 +704,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ subpath: "sglang", source: "../../extensions/sglang/api.js", exports: [ + "buildSglangProvider", "SGLANG_DEFAULT_API_KEY_ENV_VAR", "SGLANG_DEFAULT_BASE_URL", "SGLANG_MODEL_PLACEHOLDER", @@ -722,6 +778,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "resolveDefaultSlackAccountId", "resolveSlackAutoThreadId", "resolveSlackGroupRequireMention", + "resolveSlackRuntimeGroupPolicy", "resolveSlackGroupToolPolicy", "resolveSlackReplyToMode", "ResolvedSlackAccount", @@ -733,7 +790,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "removeSlackReaction", "unpinSlackMessage", ], - typeExports: ["InspectedSlackAccount", "ResolvedSlackAccount"], + typeExports: ["InspectedSlackAccount", "ResolvedSlackAccount", "SlackProbe"], }, { subpath: "together", @@ -788,6 +845,8 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "probeTelegram", "reactMessageTelegram", "renameForumTopicTelegram", + "resetTelegramThreadBindingsForTests", + "resolveTelegramRuntimeGroupPolicy", "resolveTelegramToken", "sendMessageTelegram", "sendPollTelegram", @@ -851,6 +910,8 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "StickerMetadata", "TelegramButtonStyle", "TelegramInlineButtons", + "TelegramProbe", + "TelegramTokenResolution", ], }, { @@ -886,6 +947,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ subpath: "vllm", source: "../../extensions/vllm/api.js", exports: [ + "buildVllmProvider", "VLLM_DEFAULT_API_KEY_ENV_VAR", "VLLM_DEFAULT_BASE_URL", "VLLM_MODEL_PLACEHOLDER", @@ -955,6 +1017,7 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ "resolveWhatsAppGroupRequireMention", "resolveWhatsAppGroupToolPolicy", "resolveWhatsAppOutboundTarget", + "whatsappAccessControlTesting", ], typeExports: [ "WebChannelStatus", @@ -966,7 +1029,12 @@ export const GENERATED_PLUGIN_SDK_FACADES = [ { subpath: "zalo-setup", source: "../../extensions/zalo/api.js", - exports: ["zaloSetupAdapter", "zaloSetupWizard"], + exports: [ + "evaluateZaloGroupAccess", + "resolveZaloRuntimeGroupPolicy", + "zaloSetupAdapter", + "zaloSetupWizard", + ], }, ]; diff --git a/src/agents/anthropic-vertex-provider.ts b/src/agents/anthropic-vertex-provider.ts deleted file mode 100644 index 17df481f1e5..00000000000 --- a/src/agents/anthropic-vertex-provider.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { existsSync, readFileSync } from "node:fs"; -import { homedir, platform } from "node:os"; -import { join } from "node:path"; -import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; - -const ANTHROPIC_VERTEX_DEFAULT_REGION = "global"; -const ANTHROPIC_VERTEX_REGION_RE = /^[a-z0-9-]+$/; -const GCLOUD_DEFAULT_ADC_PATH = join( - homedir(), - ".config", - "gcloud", - "application_default_credentials.json", -); - -type AdcProjectFile = { - project_id?: unknown; - quota_project_id?: unknown; -}; - -export function resolveAnthropicVertexProjectId( - env: NodeJS.ProcessEnv = process.env, -): string | undefined { - return ( - normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_PROJECT_ID) || - normalizeOptionalSecretInput(env.GOOGLE_CLOUD_PROJECT) || - normalizeOptionalSecretInput(env.GOOGLE_CLOUD_PROJECT_ID) || - resolveAnthropicVertexProjectIdFromAdc(env) - ); -} - -export function resolveAnthropicVertexRegion(env: NodeJS.ProcessEnv = process.env): string { - const region = - normalizeOptionalSecretInput(env.GOOGLE_CLOUD_LOCATION) || - normalizeOptionalSecretInput(env.CLOUD_ML_REGION); - - return region && ANTHROPIC_VERTEX_REGION_RE.test(region) - ? region - : ANTHROPIC_VERTEX_DEFAULT_REGION; -} - -export function resolveAnthropicVertexRegionFromBaseUrl(baseUrl?: string): string | undefined { - const trimmed = baseUrl?.trim(); - if (!trimmed) { - return undefined; - } - - try { - const host = new URL(trimmed).hostname.toLowerCase(); - if (host === "aiplatform.googleapis.com") { - return "global"; - } - const match = /^([a-z0-9-]+)-aiplatform\.googleapis\.com$/.exec(host); - return match?.[1]; - } catch { - return undefined; - } -} - -export function resolveAnthropicVertexClientRegion(params?: { - baseUrl?: string; - env?: NodeJS.ProcessEnv; -}): string { - return ( - resolveAnthropicVertexRegionFromBaseUrl(params?.baseUrl) || - resolveAnthropicVertexRegion(params?.env) - ); -} - -function hasAnthropicVertexMetadataServerAdc(env: NodeJS.ProcessEnv = process.env): boolean { - const explicitMetadataOptIn = normalizeOptionalSecretInput(env.ANTHROPIC_VERTEX_USE_GCP_METADATA); - return explicitMetadataOptIn === "1" || explicitMetadataOptIn?.toLowerCase() === "true"; -} - -function resolveAnthropicVertexDefaultAdcPath(env: NodeJS.ProcessEnv = process.env): string { - return platform() === "win32" - ? join( - env.APPDATA ?? join(homedir(), "AppData", "Roaming"), - "gcloud", - "application_default_credentials.json", - ) - : GCLOUD_DEFAULT_ADC_PATH; -} - -function resolveAnthropicVertexAdcCredentialsPath( - env: NodeJS.ProcessEnv = process.env, -): string | undefined { - const explicitCredentialsPath = normalizeOptionalSecretInput(env.GOOGLE_APPLICATION_CREDENTIALS); - if (explicitCredentialsPath) { - return existsSync(explicitCredentialsPath) ? explicitCredentialsPath : undefined; - } - - const defaultAdcPath = resolveAnthropicVertexDefaultAdcPath(env); - return existsSync(defaultAdcPath) ? defaultAdcPath : undefined; -} - -function resolveAnthropicVertexProjectIdFromAdc( - env: NodeJS.ProcessEnv = process.env, -): string | undefined { - const credentialsPath = resolveAnthropicVertexAdcCredentialsPath(env); - if (!credentialsPath) { - return undefined; - } - - try { - const parsed = JSON.parse(readFileSync(credentialsPath, "utf8")) as AdcProjectFile; - return ( - normalizeOptionalSecretInput(parsed.project_id) || - normalizeOptionalSecretInput(parsed.quota_project_id) - ); - } catch { - return undefined; - } -} - -export function hasAnthropicVertexCredentials(env: NodeJS.ProcessEnv = process.env): boolean { - return ( - hasAnthropicVertexMetadataServerAdc(env) || - resolveAnthropicVertexAdcCredentialsPath(env) !== undefined - ); -} - -export function hasAnthropicVertexAvailableAuth(env: NodeJS.ProcessEnv = process.env): boolean { - return hasAnthropicVertexCredentials(env); -} diff --git a/src/agents/anthropic-vertex-stream.test.ts b/src/agents/anthropic-vertex-stream.test.ts index 77a3ad9c57e..17de7d8d75f 100644 --- a/src/agents/anthropic-vertex-stream.test.ts +++ b/src/agents/anthropic-vertex-stream.test.ts @@ -32,7 +32,7 @@ vi.mock("@anthropic-ai/vertex-sdk", () => ({ import { resolveAnthropicVertexRegion, resolveAnthropicVertexRegionFromBaseUrl, -} from "./anthropic-vertex-provider.js"; +} from "../plugin-sdk/anthropic-vertex.js"; let createAnthropicVertexStreamFn: typeof import("./anthropic-vertex-stream.js").createAnthropicVertexStreamFn; let createAnthropicVertexStreamFnForModel: typeof import("./anthropic-vertex-stream.js").createAnthropicVertexStreamFnForModel; diff --git a/src/agents/anthropic-vertex-stream.ts b/src/agents/anthropic-vertex-stream.ts index de808f5cdd6..e7b2e16c859 100644 --- a/src/agents/anthropic-vertex-stream.ts +++ b/src/agents/anthropic-vertex-stream.ts @@ -4,7 +4,7 @@ import { streamAnthropic, type AnthropicOptions, type Model } from "@mariozechne import { resolveAnthropicVertexClientRegion, resolveAnthropicVertexProjectId, -} from "./anthropic-vertex-provider.js"; +} from "../plugin-sdk/anthropic-vertex.js"; type AnthropicVertexEffort = NonNullable; diff --git a/src/agents/bedrock-discovery.test.ts b/src/agents/bedrock-discovery.test.ts index a4d51276cf6..4f914a30c28 100644 --- a/src/agents/bedrock-discovery.test.ts +++ b/src/agents/bedrock-discovery.test.ts @@ -15,7 +15,7 @@ const baseActiveAnthropicSummary = { }; async function loadDiscovery() { - const mod = await import("./bedrock-discovery.js"); + const mod = await import("../plugin-sdk/amazon-bedrock.js"); mod.resetBedrockDiscoveryCacheForTest(); return mod; } @@ -139,4 +139,47 @@ describe("bedrock discovery", () => { }); expect(sendMock).toHaveBeenCalledTimes(2); }); + + it("resolves the Bedrock config apiKey from AWS auth env vars", async () => { + const { resolveBedrockConfigApiKey } = await loadDiscovery(); + + expect( + resolveBedrockConfigApiKey({ + AWS_BEARER_TOKEN_BEDROCK: "bearer", // pragma: allowlist secret + AWS_PROFILE: "default", + }), + ).toBe("AWS_BEARER_TOKEN_BEDROCK"); + + expect(resolveBedrockConfigApiKey({} as NodeJS.ProcessEnv)).toBe("AWS_PROFILE"); + }); + + it("merges implicit Bedrock models into explicit provider overrides", async () => { + const { mergeImplicitBedrockProvider } = await loadDiscovery(); + + expect( + mergeImplicitBedrockProvider({ + existing: { + baseUrl: "https://override.example.com", + headers: { "x-test-header": "1" }, + models: [], + }, + implicit: { + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + api: "bedrock-converse-stream", + auth: "aws-sdk", + models: [ + { + id: "amazon.nova-micro-v1:0", + name: "Nova", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1, + maxTokens: 1, + }, + ], + }, + }).models?.map((model) => model.id), + ).toEqual(["amazon.nova-micro-v1:0"]); + }); }); diff --git a/src/agents/cloudflare-ai-gateway.ts b/src/agents/cloudflare-ai-gateway.ts deleted file mode 100644 index d3fe68f7845..00000000000 --- a/src/agents/cloudflare-ai-gateway.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Deprecated compat shim. Prefer openclaw/plugin-sdk/cloudflare-ai-gateway. -export { - buildCloudflareAiGatewayModelDefinition, - CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_ID, - CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, - CLOUDFLARE_AI_GATEWAY_PROVIDER_ID, - resolveCloudflareAiGatewayBaseUrl, -} from "../plugin-sdk/cloudflare-ai-gateway.js"; diff --git a/src/agents/google-generative-ai.test.ts b/src/agents/google-generative-ai.test.ts index 42af87e58a4..0aa06442b9b 100644 --- a/src/agents/google-generative-ai.test.ts +++ b/src/agents/google-generative-ai.test.ts @@ -5,7 +5,7 @@ import { resolveGoogleGenerativeAiApiOrigin, resolveGoogleGenerativeAiTransport, shouldNormalizeGoogleGenerativeAiProviderConfig, -} from "./google-generative-ai.js"; +} from "../plugin-sdk/google.js"; describe("google-generative-ai helpers", () => { it("detects the Google Generative AI transport id", () => { diff --git a/src/agents/google-generative-ai.ts b/src/agents/google-generative-ai.ts deleted file mode 100644 index 9b3260e137a..00000000000 --- a/src/agents/google-generative-ai.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { normalizeGoogleApiBaseUrl } from "../infra/google-api-base-url.js"; - -type GoogleApiCarrier = { - api?: string | null; -}; - -type GoogleProviderConfigLike = GoogleApiCarrier & { - models?: ReadonlyArray | null; -}; - -export function isGoogleGenerativeAiApi(api?: string | null): boolean { - return api === "google-generative-ai"; -} - -export function normalizeGoogleGenerativeAiBaseUrl(baseUrl?: string): string | undefined { - return baseUrl ? normalizeGoogleApiBaseUrl(baseUrl) : baseUrl; -} - -export function resolveGoogleGenerativeAiTransport(params: { - api: TApi; - baseUrl?: string; -}): { api: TApi; baseUrl?: string } { - return { - api: params.api, - baseUrl: isGoogleGenerativeAiApi(params.api) - ? normalizeGoogleGenerativeAiBaseUrl(params.baseUrl) - : params.baseUrl, - }; -} - -export function resolveGoogleGenerativeAiApiOrigin(baseUrl?: string): string { - return normalizeGoogleApiBaseUrl(baseUrl).replace(/\/v1beta$/i, ""); -} - -export function shouldNormalizeGoogleGenerativeAiProviderConfig( - providerKey: string, - provider: GoogleProviderConfigLike, -): boolean { - if (providerKey === "google" || providerKey === "google-vertex") { - return true; - } - if (isGoogleGenerativeAiApi(provider.api)) { - return true; - } - return provider.models?.some((model) => isGoogleGenerativeAiApi(model?.api)) ?? false; -} diff --git a/src/agents/huggingface-models.test.ts b/src/agents/huggingface-models.test.ts index 86ec0b47873..b0c34838b84 100644 --- a/src/agents/huggingface-models.test.ts +++ b/src/agents/huggingface-models.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "vitest"; import { + buildHuggingfaceModelDefinition, discoverHuggingfaceModels, HUGGINGFACE_MODEL_CATALOG, - buildHuggingfaceModelDefinition, isHuggingfacePolicyLocked, -} from "./huggingface-models.js"; +} from "../plugin-sdk/huggingface.js"; describe("huggingface-models", () => { it("buildHuggingfaceModelDefinition returns config with required fields", () => { diff --git a/src/agents/huggingface-models.ts b/src/agents/huggingface-models.ts deleted file mode 100644 index f20d0f4cd4b..00000000000 --- a/src/agents/huggingface-models.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Deprecated compat shim. Prefer openclaw/plugin-sdk/huggingface. -export { - buildHuggingfaceModelDefinition, - discoverHuggingfaceModels, - HUGGINGFACE_BASE_URL, - HUGGINGFACE_MODEL_CATALOG, - HUGGINGFACE_POLICY_SUFFIXES, - isHuggingfacePolicyLocked, -} from "../plugin-sdk/huggingface.js"; diff --git a/src/agents/model-id-normalization.test.ts b/src/agents/model-id-normalization.test.ts index ea3ccf6b4ef..8b75023effd 100644 --- a/src/agents/model-id-normalization.test.ts +++ b/src/agents/model-id-normalization.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { normalizeXaiModelId } from "./model-id-normalization.js"; +import { normalizeXaiModelId } from "../plugin-sdk/xai.js"; describe("normalizeXaiModelId", () => { it("maps deprecated grok 4.20 beta ids to GA ids", () => { diff --git a/src/agents/model-id-normalization.ts b/src/agents/model-id-normalization.ts deleted file mode 100644 index d68afa198db..00000000000 --- a/src/agents/model-id-normalization.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Keep model ID normalization dependency-free so config parsing and other -// startup-only paths do not pull in provider discovery or plugin loading. -export function normalizeGoogleModelId(id: string): string { - if (id === "gemini-3-pro") { - return "gemini-3-pro-preview"; - } - if (id === "gemini-3-flash") { - return "gemini-3-flash-preview"; - } - if (id === "gemini-3.1-pro") { - return "gemini-3.1-pro-preview"; - } - if (id === "gemini-3.1-flash-lite") { - return "gemini-3.1-flash-lite-preview"; - } - // Preserve compatibility with earlier OpenClaw docs/config that pointed at a - // non-existent Gemini Flash preview ID. Google's current Flash text model is - // `gemini-3-flash-preview`. - if (id === "gemini-3.1-flash" || id === "gemini-3.1-flash-preview") { - return "gemini-3-flash-preview"; - } - return id; -} - -export function normalizeXaiModelId(id: string): string { - if (id === "grok-4-fast-reasoning") { - return "grok-4-fast"; - } - if (id === "grok-4-1-fast-reasoning") { - return "grok-4-1-fast"; - } - if (id === "grok-4.20-experimental-beta-0304-reasoning") { - return "grok-4.20-beta-latest-reasoning"; - } - if (id === "grok-4.20-experimental-beta-0304-non-reasoning") { - return "grok-4.20-beta-latest-non-reasoning"; - } - if (id === "grok-4.20-reasoning") { - return "grok-4.20-beta-latest-reasoning"; - } - if (id === "grok-4.20-non-reasoning") { - return "grok-4.20-beta-latest-non-reasoning"; - } - return id; -} diff --git a/src/agents/models-config.providers.cloudflare-ai-gateway.test.ts b/src/agents/models-config.providers.cloudflare-ai-gateway.test.ts index c6de651e811..ce7672e5679 100644 --- a/src/agents/models-config.providers.cloudflare-ai-gateway.test.ts +++ b/src/agents/models-config.providers.cloudflare-ai-gateway.test.ts @@ -3,8 +3,8 @@ import { writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; +import { resolveCloudflareAiGatewayBaseUrl } from "../plugin-sdk/cloudflare-ai-gateway.js"; import { captureEnv } from "../test-utils/env.js"; -import { resolveCloudflareAiGatewayBaseUrl } from "./cloudflare-ai-gateway.js"; import { NON_ENV_SECRETREF_MARKER } from "./model-auth-markers.js"; import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js"; diff --git a/src/agents/models-config.providers.discovery.ts b/src/agents/models-config.providers.discovery.ts deleted file mode 100644 index e16fb67a2d9..00000000000 --- a/src/agents/models-config.providers.discovery.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { OpenClawConfig } from "../config/config.js"; -import type { ModelDefinitionConfig } from "../config/types.models.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; -import { resolveOllamaApiBase } from "../plugin-sdk/provider-models.js"; -import { isReasoningModelHeuristic } from "../plugin-sdk/provider-reasoning.js"; -import { SGLANG_DEFAULT_BASE_URL, SGLANG_PROVIDER_LABEL } from "../plugin-sdk/sglang.js"; -import { VLLM_DEFAULT_BASE_URL, VLLM_PROVIDER_LABEL } from "../plugin-sdk/vllm.js"; -import { - SELF_HOSTED_DEFAULT_CONTEXT_WINDOW, - SELF_HOSTED_DEFAULT_COST, - SELF_HOSTED_DEFAULT_MAX_TOKENS, -} from "./self-hosted-provider-defaults.js"; -export { - buildHuggingfaceProvider, - buildKilocodeProviderWithDiscovery, - buildVeniceProvider, - buildVercelAiGatewayProvider, -} from "../plugin-sdk/provider-catalog.js"; - -export { resolveOllamaApiBase } from "../plugin-sdk/provider-models.js"; - -type ModelsConfig = NonNullable; -type ProviderConfig = NonNullable[string]; - -const log = createSubsystemLogger("agents/model-providers"); - -type OpenAICompatModelsResponse = { - data?: Array<{ - id?: string; - }>; -}; - -async function discoverOpenAICompatibleLocalModels(params: { - baseUrl: string; - apiKey?: string; - label: string; - contextWindow?: number; - maxTokens?: number; -}): Promise { - if (process.env.VITEST || process.env.NODE_ENV === "test") { - return []; - } - - const trimmedBaseUrl = params.baseUrl.trim().replace(/\/+$/, ""); - const url = `${trimmedBaseUrl}/models`; - - try { - const trimmedApiKey = params.apiKey?.trim(); - const response = await fetch(url, { - headers: trimmedApiKey ? { Authorization: `Bearer ${trimmedApiKey}` } : undefined, - signal: AbortSignal.timeout(5000), - }); - if (!response.ok) { - log.warn(`Failed to discover ${params.label} models: ${response.status}`); - return []; - } - const data = (await response.json()) as OpenAICompatModelsResponse; - const models = data.data ?? []; - if (models.length === 0) { - log.warn(`No ${params.label} models found on local instance`); - return []; - } - - return models - .map((model) => ({ id: typeof model.id === "string" ? model.id.trim() : "" })) - .filter((model) => Boolean(model.id)) - .map((model) => { - const modelId = model.id; - return { - id: modelId, - name: modelId, - reasoning: isReasoningModelHeuristic(modelId), - input: ["text"], - cost: SELF_HOSTED_DEFAULT_COST, - contextWindow: params.contextWindow ?? SELF_HOSTED_DEFAULT_CONTEXT_WINDOW, - maxTokens: params.maxTokens ?? SELF_HOSTED_DEFAULT_MAX_TOKENS, - } satisfies ModelDefinitionConfig; - }); - } catch (error) { - log.warn(`Failed to discover ${params.label} models: ${String(error)}`); - return []; - } -} - -export async function buildVllmProvider(params?: { - baseUrl?: string; - apiKey?: string; -}): Promise { - const baseUrl = (params?.baseUrl?.trim() || VLLM_DEFAULT_BASE_URL).replace(/\/+$/, ""); - const models = await discoverOpenAICompatibleLocalModels({ - baseUrl, - apiKey: params?.apiKey, - label: VLLM_PROVIDER_LABEL, - }); - return { - baseUrl, - api: "openai-completions", - models, - }; -} - -export async function buildSglangProvider(params?: { - baseUrl?: string; - apiKey?: string; -}): Promise { - const baseUrl = (params?.baseUrl?.trim() || SGLANG_DEFAULT_BASE_URL).replace(/\/+$/, ""); - const models = await discoverOpenAICompatibleLocalModels({ - baseUrl, - apiKey: params?.apiKey, - label: SGLANG_PROVIDER_LABEL, - }); - return { - baseUrl, - api: "openai-completions", - models, - }; -} diff --git a/src/agents/models-config.providers.google-antigravity.test.ts b/src/agents/models-config.providers.google-antigravity.test.ts index f14cab01493..05acc2a235d 100644 --- a/src/agents/models-config.providers.google-antigravity.test.ts +++ b/src/agents/models-config.providers.google-antigravity.test.ts @@ -2,12 +2,8 @@ import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { normalizeGoogleModelId } from "./model-id-normalization.js"; -import { - normalizeAntigravityModelId, - normalizeProviders, - type ProviderConfig, -} from "./models-config.providers.js"; +import { normalizeAntigravityModelId, normalizeGoogleModelId } from "../plugin-sdk/google.js"; +import { normalizeProviders, type ProviderConfig } from "./models-config.providers.js"; function buildModel(id: string): NonNullable[number] { return { diff --git a/src/agents/models-config.providers.kilocode.test.ts b/src/agents/models-config.providers.kilocode.test.ts index 18edb78b2a6..cb6c01dbdf2 100644 --- a/src/agents/models-config.providers.kilocode.test.ts +++ b/src/agents/models-config.providers.kilocode.test.ts @@ -2,9 +2,9 @@ import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; +import { buildKilocodeProvider } from "../plugin-sdk/kilocode.js"; import { captureEnv } from "../test-utils/env.js"; import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js"; -import { buildKilocodeProvider } from "./models-config.providers.js"; const KILOCODE_MODEL_IDS = ["kilo/auto"]; diff --git a/src/agents/models-config.providers.kimi-coding.test.ts b/src/agents/models-config.providers.kimi-coding.test.ts index 3da4986961a..dea77c7aedd 100644 --- a/src/agents/models-config.providers.kimi-coding.test.ts +++ b/src/agents/models-config.providers.kimi-coding.test.ts @@ -2,9 +2,9 @@ import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; +import { buildKimiCodingProvider } from "../plugin-sdk/kimi-coding.js"; import { captureEnv } from "../test-utils/env.js"; import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js"; -import { buildKimiCodingProvider } from "./models-config.providers.js"; describe("Kimi implicit provider (#22409)", () => { it("should include Kimi when KIMI_API_KEY is configured", async () => { diff --git a/src/agents/models-config.providers.modelstudio.test.ts b/src/agents/models-config.providers.modelstudio.test.ts index 619146d635c..9a5ad45c0b1 100644 --- a/src/agents/models-config.providers.modelstudio.test.ts +++ b/src/agents/models-config.providers.modelstudio.test.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from "vitest"; -import { - applyNativeStreamingUsageCompat, - buildModelStudioProvider, -} from "./models-config.providers.js"; +import { buildModelStudioProvider } from "../plugin-sdk/modelstudio.js"; +import { applyNativeStreamingUsageCompat } from "./models-config.providers.js"; describe("Model Studio implicit provider", () => { it("should opt native Model Studio baseUrls into streaming usage", () => { diff --git a/src/agents/models-config.providers.nvidia.test.ts b/src/agents/models-config.providers.nvidia.test.ts index bfa70c5bb41..c4b7306ddbe 100644 --- a/src/agents/models-config.providers.nvidia.test.ts +++ b/src/agents/models-config.providers.nvidia.test.ts @@ -3,10 +3,10 @@ import { writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; +import { buildNvidiaProvider } from "../plugin-sdk/nvidia.js"; import { withEnvAsync } from "../test-utils/env.js"; import { resolveApiKeyForProvider } from "./model-auth.js"; import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js"; -import { buildNvidiaProvider } from "./models-config.providers.js"; describe("NVIDIA provider", () => { it("should include nvidia when NVIDIA_API_KEY is configured", async () => { diff --git a/src/agents/models-config.providers.ollama.test.ts b/src/agents/models-config.providers.ollama.test.ts index 49e4deae551..aa019492ce3 100644 --- a/src/agents/models-config.providers.ollama.test.ts +++ b/src/agents/models-config.providers.ollama.test.ts @@ -3,8 +3,8 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { ModelDefinitionConfig } from "../config/types.models.js"; +import { resolveOllamaApiBase } from "../plugin-sdk/ollama-surface.js"; import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js"; -import { resolveOllamaApiBase } from "./models-config.providers.js"; afterEach(() => { vi.unstubAllEnvs(); diff --git a/src/agents/models-config.providers.static.ts b/src/agents/models-config.providers.static.ts index 0a1bc1e9bda..88375d0ed96 100644 --- a/src/agents/models-config.providers.static.ts +++ b/src/agents/models-config.providers.static.ts @@ -1,29 +1,27 @@ export { ANTHROPIC_VERTEX_DEFAULT_MODEL_ID, buildAnthropicVertexProvider, - buildBytePlusCodingProvider, - buildBytePlusProvider, - buildDeepSeekProvider, -} from "../plugin-sdk/provider-catalog.js"; +} from "../plugin-sdk/anthropic-vertex.js"; +export { buildBytePlusCodingProvider, buildBytePlusProvider } from "../plugin-sdk/byteplus.js"; +export { buildDeepSeekProvider } from "../plugin-sdk/deepseek.js"; +export { buildKimiCodingProvider } from "../plugin-sdk/kimi-coding.js"; +export { buildKilocodeProvider } from "../plugin-sdk/kilocode.js"; +export { buildMinimaxPortalProvider, buildMinimaxProvider } from "../plugin-sdk/minimax.js"; export { - buildKimiCodingProvider, - buildKilocodeProvider, - buildMinimaxPortalProvider, - buildMinimaxProvider, MODELSTUDIO_BASE_URL, MODELSTUDIO_DEFAULT_MODEL_ID, buildModelStudioProvider, - buildMoonshotProvider, - buildNvidiaProvider, - buildOpenAICodexProvider, - buildOpenrouterProvider, +} from "../plugin-sdk/modelstudio.js"; +export { buildMoonshotProvider } from "../plugin-sdk/moonshot.js"; +export { buildNvidiaProvider } from "../plugin-sdk/nvidia.js"; +export { buildOpenAICodexProvider } from "../plugin-sdk/openai.js"; +export { buildOpenrouterProvider } from "../plugin-sdk/openrouter.js"; +export { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID, buildQianfanProvider, - buildSyntheticProvider, - buildTogetherProvider, - buildDoubaoCodingProvider, - buildDoubaoProvider, - XIAOMI_DEFAULT_MODEL_ID, - buildXiaomiProvider, -} from "../plugin-sdk/provider-catalog.js"; +} from "../plugin-sdk/qianfan.js"; +export { buildSyntheticProvider } from "../plugin-sdk/synthetic.js"; +export { buildTogetherProvider } from "../plugin-sdk/together.js"; +export { buildDoubaoCodingProvider, buildDoubaoProvider } from "../plugin-sdk/volcengine.js"; +export { XIAOMI_DEFAULT_MODEL_ID, buildXiaomiProvider } from "../plugin-sdk/xiaomi.js"; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index dcb06a5c734..bcdae29cb7f 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -2,43 +2,55 @@ import type { OpenClawConfig } from "../config/config.js"; import { coerceSecretRef, resolveSecretInputRef } from "../config/types.secrets.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { - buildAnthropicVertexProvider, - buildKimiCodingProvider, - buildKilocodeProvider, + mergeImplicitBedrockProvider, + resolveBedrockConfigApiKey, + resolveImplicitBedrockProvider, +} from "../plugin-sdk/amazon-bedrock.js"; +import { + mergeImplicitAnthropicVertexProvider, + resolveImplicitAnthropicVertexProvider, + resolveAnthropicVertexConfigApiKey, +} from "../plugin-sdk/anthropic-vertex.js"; +import { + normalizeGoogleModelId, + normalizeGoogleProviderConfig, + shouldNormalizeGoogleProviderConfig, +} from "../plugin-sdk/google.js"; +import { buildKilocodeProvider } from "../plugin-sdk/kilocode.js"; +import { buildKimiCodingProvider } from "../plugin-sdk/kimi-coding.js"; +import { + MODELSTUDIO_BASE_URL, + MODELSTUDIO_DEFAULT_MODEL_ID, + applyModelStudioNativeStreamingUsageCompat, buildModelStudioProvider, - buildNvidiaProvider, +} from "../plugin-sdk/modelstudio.js"; +import { applyMoonshotNativeStreamingUsageCompat } from "../plugin-sdk/moonshot.js"; +import { buildNvidiaProvider } from "../plugin-sdk/nvidia.js"; +import { resolveOllamaApiBase } from "../plugin-sdk/ollama-surface.js"; +import { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID, buildQianfanProvider, - MODELSTUDIO_BASE_URL, - MODELSTUDIO_DEFAULT_MODEL_ID, - XIAOMI_DEFAULT_MODEL_ID, - buildXiaomiProvider, -} from "../plugin-sdk/provider-catalog.js"; +} from "../plugin-sdk/qianfan.js"; +import { normalizeXaiModelId } from "../plugin-sdk/xai.js"; +import { XIAOMI_DEFAULT_MODEL_ID, buildXiaomiProvider } from "../plugin-sdk/xiaomi.js"; import { isRecord } from "../utils.js"; import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; -import { hasAnthropicVertexAvailableAuth } from "./anthropic-vertex-provider.js"; import { ensureAuthProfileStore, listProfilesForProvider } from "./auth-profiles.js"; -import { discoverBedrockModels } from "./bedrock-discovery.js"; -import { - normalizeGoogleGenerativeAiBaseUrl, - shouldNormalizeGoogleGenerativeAiProviderConfig, -} from "./google-generative-ai.js"; -import { normalizeGoogleModelId, normalizeXaiModelId } from "./model-id-normalization.js"; -import { resolveOllamaApiBase } from "./models-config.providers.discovery.js"; +export { buildKilocodeProvider } from "../plugin-sdk/kilocode.js"; +export { buildKimiCodingProvider } from "../plugin-sdk/kimi-coding.js"; export { - buildKimiCodingProvider, - buildKilocodeProvider, MODELSTUDIO_BASE_URL, MODELSTUDIO_DEFAULT_MODEL_ID, buildModelStudioProvider, - buildNvidiaProvider, +} from "../plugin-sdk/modelstudio.js"; +export { buildNvidiaProvider } from "../plugin-sdk/nvidia.js"; +export { QIANFAN_BASE_URL, QIANFAN_DEFAULT_MODEL_ID, buildQianfanProvider, - XIAOMI_DEFAULT_MODEL_ID, - buildXiaomiProvider, -} from "../plugin-sdk/provider-catalog.js"; +} from "../plugin-sdk/qianfan.js"; +export { XIAOMI_DEFAULT_MODEL_ID, buildXiaomiProvider } from "../plugin-sdk/xiaomi.js"; import { groupPluginDiscoveryProvidersByOrder, normalizePluginDiscoveryResult, @@ -52,8 +64,9 @@ import { resolveEnvSecretRefHeaderValueMarker, } from "./model-auth-markers.js"; import { resolveAwsSdkEnvVarName, resolveEnvApiKey } from "./model-auth.js"; -export { resolveOllamaApiBase } from "./models-config.providers.discovery.js"; -export { normalizeGoogleModelId, normalizeXaiModelId }; +export { resolveOllamaApiBase } from "../plugin-sdk/ollama-surface.js"; +export { normalizeGoogleModelId } from "../plugin-sdk/google.js"; +export { normalizeXaiModelId } from "../plugin-sdk/xai.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; @@ -63,20 +76,51 @@ type SecretDefaults = { exec?: string; }; -const MOONSHOT_NATIVE_BASE_URLS = new Set([ - "https://api.moonshot.ai/v1", - "https://api.moonshot.cn/v1", -]); -const MODELSTUDIO_NATIVE_BASE_URLS = new Set([ - "https://coding-intl.dashscope.aliyuncs.com/v1", - "https://coding.dashscope.aliyuncs.com/v1", - "https://dashscope.aliyuncs.com/compatible-mode/v1", - "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", -]); const log = createSubsystemLogger("agents/model-providers"); const ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/; +const NATIVE_STREAMING_USAGE_COMPAT: Record ProviderConfig> = + { + moonshot: applyMoonshotNativeStreamingUsageCompat, + modelstudio: applyModelStudioNativeStreamingUsageCompat, + }; + +const PROVIDER_CONFIG_API_KEY_RESOLVERS: Partial< + Record string | undefined> +> = { + "amazon-bedrock": resolveBedrockConfigApiKey, + "anthropic-vertex": resolveAnthropicVertexConfigApiKey, +}; + +const PROVIDER_IMPLICIT_MERGERS: Partial< + Record< + string, + (params: { existing: ProviderConfig | undefined; implicit: ProviderConfig }) => ProviderConfig + > +> = { + "amazon-bedrock": mergeImplicitBedrockProvider, + "anthropic-vertex": mergeImplicitAnthropicVertexProvider, +}; + +const CORE_IMPLICIT_PROVIDER_RESOLVERS = [ + { + id: "amazon-bedrock", + resolve: (params: { config?: OpenClawConfig; env: NodeJS.ProcessEnv }) => + resolveImplicitBedrockProvider({ + config: params.config, + env: params.env, + }), + }, + { + id: "anthropic-vertex", + resolve: (params: { config?: OpenClawConfig; env: NodeJS.ProcessEnv }) => + resolveImplicitAnthropicVertexProvider({ + env: params.env, + }), + }, +] as const; + function resolveLiveProviderCatalogTimeoutMs(env: NodeJS.ProcessEnv): number | null { const live = env.OPENCLAW_LIVE_TEST === "1" || env.OPENCLAW_LIVE_GATEWAY === "1" || env.LIVE === "1"; @@ -114,44 +158,6 @@ function normalizeApiKeyConfig(value: string): string { return match?.[1] ?? trimmed; } -function normalizeProviderBaseUrl(baseUrl: string | undefined): string { - const trimmed = baseUrl?.trim(); - if (!trimmed) { - return ""; - } - try { - const url = new URL(trimmed); - url.hash = ""; - url.search = ""; - return url.toString().replace(/\/+$/, "").toLowerCase(); - } catch { - return trimmed.replace(/\/+$/, "").toLowerCase(); - } -} - -function withStreamingUsageCompat(provider: ProviderConfig): ProviderConfig { - if (!Array.isArray(provider.models) || provider.models.length === 0) { - return provider; - } - - let changed = false; - const models = provider.models.map((model) => { - if (model.compat?.supportsUsageInStreaming !== undefined) { - return model; - } - changed = true; - return { - ...model, - compat: { - ...model.compat, - supportsUsageInStreaming: true, - }, - }; - }); - - return changed ? { ...provider, models } : provider; -} - export function applyNativeStreamingUsageCompat( providers: Record, ): Record { @@ -159,13 +165,7 @@ export function applyNativeStreamingUsageCompat( const nextProviders: Record = {}; for (const [providerKey, provider] of Object.entries(providers)) { - const normalizedBaseUrl = normalizeProviderBaseUrl(provider.baseUrl); - const isNativeMoonshot = - providerKey === "moonshot" && MOONSHOT_NATIVE_BASE_URLS.has(normalizedBaseUrl); - const isNativeModelStudio = - providerKey === "modelstudio" && MODELSTUDIO_NATIVE_BASE_URLS.has(normalizedBaseUrl); - const nextProvider = - isNativeMoonshot || isNativeModelStudio ? withStreamingUsageCompat(provider) : provider; + const nextProvider = NATIVE_STREAMING_USAGE_COMPAT[providerKey]?.(provider) ?? provider; nextProviders[providerKey] = nextProvider; changed ||= nextProvider !== provider; } @@ -309,44 +309,6 @@ function resolveApiKeyFromProfiles(params: { return undefined; } -const ANTIGRAVITY_BARE_PRO_IDS = new Set(["gemini-3-pro", "gemini-3.1-pro", "gemini-3-1-pro"]); - -export function normalizeAntigravityModelId(id: string): string { - if (ANTIGRAVITY_BARE_PRO_IDS.has(id)) { - return `${id}-low`; - } - return id; -} - -function normalizeProviderModels( - provider: ProviderConfig, - normalizeId: (id: string) => string, -): ProviderConfig { - let mutated = false; - const models = provider.models.map((model) => { - const nextId = normalizeId(model.id); - if (nextId === model.id) { - return model; - } - mutated = true; - return { ...model, id: nextId }; - }); - return mutated ? { ...provider, models } : provider; -} - -function normalizeGoogleProvider(provider: ProviderConfig): ProviderConfig { - const modelNormalized = normalizeProviderModels(provider, normalizeGoogleModelId); - const normalizedBaseUrl = normalizeGoogleGenerativeAiBaseUrl(modelNormalized.baseUrl); - if (normalizedBaseUrl !== modelNormalized.baseUrl) { - return { ...modelNormalized, baseUrl: normalizedBaseUrl ?? modelNormalized.baseUrl }; - } - return modelNormalized; -} - -function normalizeAntigravityProvider(provider: ProviderConfig): ProviderConfig { - return normalizeProviderModels(provider, normalizeAntigravityModelId); -} - function normalizeSourceProviderLookup( providers: ModelsConfig["providers"] | undefined, ): Record { @@ -595,17 +557,18 @@ export function normalizeProviders(params: { const normalizedApiKey = normalizeOptionalSecretInput(normalizedProvider.apiKey); const hasConfiguredApiKey = Boolean(normalizedApiKey || normalizedProvider.apiKey); if (hasModels && !hasConfiguredApiKey) { - const authMode = - normalizedProvider.auth ?? (normalizedKey === "amazon-bedrock" ? "aws-sdk" : undefined); - if (authMode === "aws-sdk") { + const authMode = normalizedProvider.auth; + const providerApiKeyResolver = PROVIDER_CONFIG_API_KEY_RESOLVERS[normalizedKey]; + if (providerApiKeyResolver && (!authMode || authMode === "aws-sdk")) { + const apiKey = providerApiKeyResolver(env); + mutated = true; + normalizedProvider = { ...normalizedProvider, apiKey }; + } else if (authMode === "aws-sdk") { const apiKey = resolveAwsSdkApiKeyVarName(env); mutated = true; normalizedProvider = { ...normalizedProvider, apiKey }; } else { - const fromEnv = - normalizedKey === "anthropic-vertex" - ? resolveEnvApiKey(normalizedKey, env)?.apiKey - : resolveEnvApiKeyVarName(normalizedKey, env); + const fromEnv = resolveEnvApiKeyVarName(normalizedKey, env); const apiKey = fromEnv ?? profileApiKey?.apiKey; if (apiKey?.trim()) { if (profileApiKey && profileApiKey.source !== "plaintext") { @@ -617,20 +580,12 @@ export function normalizeProviders(params: { } } - if (shouldNormalizeGoogleGenerativeAiProviderConfig(normalizedKey, normalizedProvider)) { - const googleNormalized = normalizeGoogleProvider(normalizedProvider); + if (shouldNormalizeGoogleProviderConfig(normalizedKey, normalizedProvider)) { + const googleNormalized = normalizeGoogleProviderConfig(normalizedKey, normalizedProvider); if (googleNormalized !== normalizedProvider) { mutated = true; + normalizedProvider = googleNormalized; } - normalizedProvider = googleNormalized; - } - - if (normalizedKey === "google-antigravity") { - const antigravityNormalized = normalizeAntigravityProvider(normalizedProvider); - if (antigravityNormalized !== normalizedProvider) { - mutated = true; - } - normalizedProvider = antigravityNormalized; } const existing = next[normalizedKey]; @@ -877,82 +832,21 @@ export async function resolveImplicitProviders( mergeImplicitProviderSet(providers, await resolvePluginImplicitProviders(context, "paired")); mergeImplicitProviderSet(providers, await resolvePluginImplicitProviders(context, "late")); - const implicitBedrock = await resolveImplicitBedrockProvider({ - agentDir: params.agentDir, - config: params.config, - env, - }); - if (implicitBedrock) { - const existing = providers["amazon-bedrock"]; - providers["amazon-bedrock"] = existing - ? { - ...implicitBedrock, - ...existing, - models: - Array.isArray(existing.models) && existing.models.length > 0 - ? existing.models - : implicitBedrock.models, - } - : implicitBedrock; - } - - const implicitAnthropicVertex = resolveImplicitAnthropicVertexProvider({ env }); - if (implicitAnthropicVertex) { - const existing = providers["anthropic-vertex"]; - providers["anthropic-vertex"] = existing - ? { - ...implicitAnthropicVertex, - ...existing, - models: - Array.isArray(existing.models) && existing.models.length > 0 - ? existing.models - : implicitAnthropicVertex.models, - } - : implicitAnthropicVertex; + for (const provider of CORE_IMPLICIT_PROVIDER_RESOLVERS) { + const implicit = await provider.resolve({ config: params.config, env }); + if (!implicit) { + continue; + } + const merge = PROVIDER_IMPLICIT_MERGERS[provider.id]; + if (!merge) { + providers[provider.id] = implicit; + continue; + } + providers[provider.id] = merge({ + existing: providers[provider.id], + implicit, + }); } return providers; } - -export function resolveImplicitAnthropicVertexProvider(params: { - env?: NodeJS.ProcessEnv; -}): ProviderConfig | null { - const env = params.env ?? process.env; - if (!hasAnthropicVertexAvailableAuth(env)) { - return null; - } - - return buildAnthropicVertexProvider({ env }); -} -export async function resolveImplicitBedrockProvider(params: { - agentDir: string; - config?: OpenClawConfig; - env?: NodeJS.ProcessEnv; -}): Promise { - const env = params.env ?? process.env; - const discoveryConfig = params.config?.models?.bedrockDiscovery; - const enabled = discoveryConfig?.enabled; - const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined; - if (enabled === false) { - return null; - } - if (enabled !== true && !hasAwsCreds) { - return null; - } - - const region = discoveryConfig?.region ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1"; - const models = await discoverBedrockModels({ - region, - config: discoveryConfig, - }); - if (models.length === 0) { - return null; - } - - return { - baseUrl: `https://bedrock-runtime.${region}.amazonaws.com`, - api: "bedrock-converse-stream", - auth: "aws-sdk", - models, - } satisfies ProviderConfig; -} diff --git a/src/agents/models-config.providers.vercel-ai-gateway.test.ts b/src/agents/models-config.providers.vercel-ai-gateway.test.ts index d53e2f85435..96ff046c1f3 100644 --- a/src/agents/models-config.providers.vercel-ai-gateway.test.ts +++ b/src/agents/models-config.providers.vercel-ai-gateway.test.ts @@ -3,10 +3,10 @@ import { writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; +import { VERCEL_AI_GATEWAY_BASE_URL } from "../plugin-sdk/vercel-ai-gateway.js"; import { captureEnv } from "../test-utils/env.js"; import { NON_ENV_SECRETREF_MARKER } from "./model-auth-markers.js"; import { resolveImplicitProvidersForTest } from "./models-config.e2e-harness.js"; -import { VERCEL_AI_GATEWAY_BASE_URL } from "./vercel-ai-gateway.js"; describe("vercel-ai-gateway provider resolution", () => { it("adds the provider with GPT-5.4 models when AI_GATEWAY_API_KEY is present", async () => { diff --git a/src/agents/pi-embedded-helpers/google.ts b/src/agents/pi-embedded-helpers/google.ts index fc7bb2d4b2b..4c82034795c 100644 --- a/src/agents/pi-embedded-helpers/google.ts +++ b/src/agents/pi-embedded-helpers/google.ts @@ -1,4 +1,4 @@ -import { isGoogleGenerativeAiApi } from "../google-generative-ai.js"; +import { isGoogleGenerativeAiApi } from "../../plugin-sdk/google.js"; import { sanitizeGoogleTurnOrdering } from "./bootstrap.js"; export function isGoogleModelApi(api?: string | null): boolean { diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index 8bc22caebac..44f454c321b 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -2,6 +2,7 @@ import type { Api, Model } from "@mariozechner/pi-ai"; import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent"; import type { OpenClawConfig } from "../../config/config.js"; import type { ModelDefinitionConfig } from "../../config/types.js"; +import { resolveGoogleGenerativeAiTransport } from "../../plugin-sdk/google.js"; import { buildProviderUnknownModelHintWithPlugin, clearProviderRuntimeHookCache, @@ -11,7 +12,6 @@ import { } from "../../plugins/provider-runtime.js"; import { resolveOpenClawAgentDir } from "../agent-paths.js"; import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; -import { resolveGoogleGenerativeAiTransport } from "../google-generative-ai.js"; import { buildModelAliasLines } from "../model-alias-lines.js"; import { isSecretRefHeaderValueMarker } from "../model-auth-markers.js"; import { normalizeModelCompat } from "../model-compat.js"; diff --git a/src/agents/provider-attribution.ts b/src/agents/provider-attribution.ts index 6b9a55a4bfa..528bafebf1f 100644 --- a/src/agents/provider-attribution.ts +++ b/src/agents/provider-attribution.ts @@ -1,6 +1,6 @@ import type { RuntimeVersionEnv } from "../version.js"; import { resolveRuntimeServiceVersion } from "../version.js"; -import { normalizeProviderId } from "./model-selection.js"; +import { normalizeProviderId } from "./provider-id.js"; export type ProviderAttributionVerification = | "vendor-documented" diff --git a/src/agents/provider-capabilities.ts b/src/agents/provider-capabilities.ts index 05f7b9ada8b..c3424956ecf 100644 --- a/src/agents/provider-capabilities.ts +++ b/src/agents/provider-capabilities.ts @@ -1,6 +1,6 @@ import type { OpenClawConfig } from "../config/config.js"; import { resolveProviderCapabilitiesWithPlugin as resolveProviderCapabilitiesWithPluginRuntime } from "../plugins/provider-runtime.js"; -import { normalizeProviderId } from "./model-selection.js"; +import { normalizeProviderId } from "./provider-id.js"; export type ProviderCapabilities = { anthropicToolSchemaMode: "native" | "openai-functions"; diff --git a/src/agents/sglang-defaults.ts b/src/agents/sglang-defaults.ts deleted file mode 100644 index 3c03bb3a80c..00000000000 --- a/src/agents/sglang-defaults.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Deprecated compat shim. Prefer openclaw/plugin-sdk/sglang. -export { - SGLANG_DEFAULT_API_KEY_ENV_VAR, - SGLANG_DEFAULT_BASE_URL, - SGLANG_MODEL_PLACEHOLDER, - SGLANG_PROVIDER_LABEL, -} from "../plugin-sdk/sglang.js"; diff --git a/src/agents/tools/pdf-native-providers.ts b/src/agents/tools/pdf-native-providers.ts index 24449e8a92a..9a81d6fb5de 100644 --- a/src/agents/tools/pdf-native-providers.ts +++ b/src/agents/tools/pdf-native-providers.ts @@ -3,9 +3,9 @@ * This bypasses pi-ai's content type system which does not have a "document" type. */ +import { resolveGoogleGenerativeAiApiOrigin } from "../../plugin-sdk/google.js"; import { isRecord } from "../../utils.js"; import { normalizeSecretInput } from "../../utils/normalize-secret-input.js"; -import { resolveGoogleGenerativeAiApiOrigin } from "../google-generative-ai.js"; type PdfInput = { base64: string; diff --git a/src/agents/vercel-ai-gateway.ts b/src/agents/vercel-ai-gateway.ts deleted file mode 100644 index 914b1b44e1e..00000000000 --- a/src/agents/vercel-ai-gateway.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Deprecated compat shim. Prefer openclaw/plugin-sdk/vercel-ai-gateway. -export { - discoverVercelAiGatewayModels, - getStaticVercelAiGatewayModelCatalog, - VERCEL_AI_GATEWAY_BASE_URL, - VERCEL_AI_GATEWAY_DEFAULT_CONTEXT_WINDOW, - VERCEL_AI_GATEWAY_DEFAULT_COST, - VERCEL_AI_GATEWAY_DEFAULT_MAX_TOKENS, - VERCEL_AI_GATEWAY_DEFAULT_MODEL_ID, - VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, - VERCEL_AI_GATEWAY_PROVIDER_ID, -} from "../plugin-sdk/vercel-ai-gateway.js"; diff --git a/src/agents/vllm-defaults.ts b/src/agents/vllm-defaults.ts deleted file mode 100644 index 5b0c2793a75..00000000000 --- a/src/agents/vllm-defaults.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Deprecated compat shim. Prefer openclaw/plugin-sdk/vllm. -export { - VLLM_DEFAULT_API_KEY_ENV_VAR, - VLLM_DEFAULT_BASE_URL, - VLLM_MODEL_PLACEHOLDER, - VLLM_PROVIDER_LABEL, -} from "../plugin-sdk/vllm.js"; diff --git a/src/auto-reply/reply/commands-approve.ts b/src/auto-reply/reply/commands-approve.ts index 126a713c59e..ceefabc2780 100644 --- a/src/auto-reply/reply/commands-approve.ts +++ b/src/auto-reply/reply/commands-approve.ts @@ -1,10 +1,10 @@ -import { - isDiscordExecApprovalApprover, - isDiscordExecApprovalClientEnabled, -} from "../../../extensions/discord/api.js"; import { callGateway } from "../../gateway/call.js"; import { ErrorCodes } from "../../gateway/protocol/index.js"; import { logVerbose } from "../../globals.js"; +import { + isDiscordExecApprovalApprover, + isDiscordExecApprovalClientEnabled, +} from "../../plugin-sdk/discord-surface.js"; import { isTelegramExecApprovalApprover, isTelegramExecApprovalClientEnabled, diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 9a4ba7f3dc2..3bc1a04adfc 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -21,7 +21,6 @@ import { isModelNotFoundErrorMessage } from "../agents/live-model-errors.js"; import { isHighSignalLiveModelRef } from "../agents/live-model-filter.js"; import { isLiveProfileKeyModeEnabled, isLiveTestEnabled } from "../agents/live-test-helpers.js"; import { getApiKeyForModel } from "../agents/model-auth.js"; -import { normalizeGoogleModelId } from "../agents/model-id-normalization.js"; import { shouldSuppressBuiltInModel } from "../agents/model-suppression.js"; import { ensureOpenClawModelsJson } from "../agents/models-config.js"; import { isRateLimitErrorMessage } from "../agents/pi-embedded-helpers/errors.js"; @@ -29,6 +28,7 @@ import { discoverAuthStorage, discoverModels } from "../agents/pi-model-discover import { clearRuntimeConfigSnapshot, loadConfig } from "../config/config.js"; import type { ModelsConfig, OpenClawConfig, ModelProviderConfig } from "../config/types.js"; import { isTruthyEnvValue } from "../infra/env.js"; +import { normalizeGoogleModelId } from "../plugin-sdk/google.js"; import { DEFAULT_AGENT_ID } from "../routing/session-key.js"; import { stripAssistantInternalScaffolding } from "../shared/text/assistant-visible-text.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; diff --git a/src/gateway/model-pricing-cache.ts b/src/gateway/model-pricing-cache.ts index 447268fd923..eb3f63b3000 100644 --- a/src/gateway/model-pricing-cache.ts +++ b/src/gateway/model-pricing-cache.ts @@ -7,9 +7,10 @@ import { resolveModelRefFromString, type ModelRef, } from "../agents/model-selection.js"; -import { normalizeGoogleModelId, normalizeXaiModelId } from "../agents/models-config.providers.js"; import type { OpenClawConfig } from "../config/config.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { normalizeGoogleModelId } from "../plugin-sdk/google.js"; +import { normalizeXaiModelId } from "../plugin-sdk/xai.js"; export type CachedModelPricing = { input: number; diff --git a/src/plugin-sdk/account-core.ts b/src/plugin-sdk/account-core.ts new file mode 100644 index 00000000000..e40508b6cfb --- /dev/null +++ b/src/plugin-sdk/account-core.ts @@ -0,0 +1,62 @@ +export type { OpenClawConfig } from "../config/config.js"; + +export { createAccountActionGate } from "../channels/plugins/account-action-gate.js"; +export { + createAccountListHelpers, + describeAccountSnapshot, + listCombinedAccountIds, + mergeAccountConfig, + resolveMergedAccountConfig, +} from "../channels/plugins/account-helpers.js"; +export { normalizeChatType } from "../channels/chat-type.js"; +export { resolveAccountEntry, resolveNormalizedAccountEntry } from "../routing/account-lookup.js"; +export { + DEFAULT_ACCOUNT_ID, + normalizeAccountId, + normalizeOptionalAccountId, +} from "../routing/session-key.js"; +export { normalizeE164, pathExists, resolveUserPath } from "../utils.js"; + +/** Resolve an account by id, then fall back to the default account when the primary lacks credentials. */ +export function resolveAccountWithDefaultFallback(params: { + accountId?: string | null; + normalizeAccountId: (accountId?: string | null) => string; + resolvePrimary: (accountId: string) => TAccount; + hasCredential: (account: TAccount) => boolean; + resolveDefaultAccountId: () => string; +}): TAccount { + const hasExplicitAccountId = Boolean(params.accountId?.trim()); + const normalizedAccountId = params.normalizeAccountId(params.accountId); + const primary = params.resolvePrimary(normalizedAccountId); + if (hasExplicitAccountId || params.hasCredential(primary)) { + return primary; + } + + const fallbackId = params.resolveDefaultAccountId(); + if (fallbackId === normalizedAccountId) { + return primary; + } + const fallback = params.resolvePrimary(fallbackId); + if (!params.hasCredential(fallback)) { + return primary; + } + return fallback; +} + +/** List normalized configured account ids from a raw channel account record map. */ +export function listConfiguredAccountIds(params: { + accounts: Record | undefined; + normalizeAccountId: (accountId: string) => string; +}): string[] { + if (!params.accounts) { + return []; + } + const ids = new Set(); + for (const key of Object.keys(params.accounts)) { + if (!key) { + continue; + } + ids.add(params.normalizeAccountId(key)); + } + return [...ids]; +} diff --git a/src/plugin-sdk/amazon-bedrock.ts b/src/plugin-sdk/amazon-bedrock.ts new file mode 100644 index 00000000000..019dae56428 --- /dev/null +++ b/src/plugin-sdk/amazon-bedrock.ts @@ -0,0 +1,8 @@ +// Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. +export { + discoverBedrockModels, + mergeImplicitBedrockProvider, + resetBedrockDiscoveryCacheForTest, + resolveBedrockConfigApiKey, + resolveImplicitBedrockProvider, +} from "../../extensions/amazon-bedrock/api.js"; diff --git a/src/plugin-sdk/anthropic-vertex.ts b/src/plugin-sdk/anthropic-vertex.ts index 262d5428521..9ac0752f75d 100644 --- a/src/plugin-sdk/anthropic-vertex.ts +++ b/src/plugin-sdk/anthropic-vertex.ts @@ -2,5 +2,13 @@ export { ANTHROPIC_VERTEX_DEFAULT_MODEL_ID, buildAnthropicVertexProvider, + hasAnthropicVertexAvailableAuth, + hasAnthropicVertexCredentials, + mergeImplicitAnthropicVertexProvider, + resolveAnthropicVertexClientRegion, + resolveAnthropicVertexConfigApiKey, + resolveImplicitAnthropicVertexProvider, + resolveAnthropicVertexProjectId, resolveAnthropicVertexRegion, + resolveAnthropicVertexRegionFromBaseUrl, } from "../../extensions/anthropic-vertex/api.js"; diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 4af0b800080..e2cd7bdafe3 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -139,6 +139,7 @@ export type { SecretFileReadOptions, SecretFileReadResult } from "../infra/secre export { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js"; export type { GatewayBindUrlResult } from "../shared/gateway-bind-url.js"; export { resolveGatewayPort } from "../config/paths.js"; +export { createSubsystemLogger } from "../logging/subsystem.js"; export { normalizeAtHashSlug, normalizeHyphenSlug } from "../shared/string-normalization.js"; export { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js"; diff --git a/src/plugin-sdk/discord-surface.ts b/src/plugin-sdk/discord-surface.ts index 839bb4a89e7..a386400c1f8 100644 --- a/src/plugin-sdk/discord-surface.ts +++ b/src/plugin-sdk/discord-surface.ts @@ -5,6 +5,8 @@ export { createDiscordActionGate, handleDiscordMessageAction, inspectDiscordAccount, + isDiscordExecApprovalApprover, + isDiscordExecApprovalClientEnabled, listDiscordAccountIds, listDiscordDirectoryGroupsFromConfig, listDiscordDirectoryPeersFromConfig, @@ -15,14 +17,17 @@ export { resolveDefaultDiscordAccountId, resolveDiscordAccount, resolveDiscordChannelId, + resolveDiscordRuntimeGroupPolicy, resolveDiscordGroupRequireMention, resolveDiscordGroupToolPolicy, } from "../../extensions/discord/api.js"; export type { DiscordComponentMessageSpec, + DiscordProbe, DiscordSendComponents, DiscordSendEmbeds, DiscordSendResult, + DiscordTokenResolution, InspectedDiscordAccount, ResolvedDiscordAccount, } from "../../extensions/discord/api.js"; diff --git a/src/plugin-sdk/feishu-conversation.ts b/src/plugin-sdk/feishu-conversation.ts index 894e20081c4..cd046ce3477 100644 --- a/src/plugin-sdk/feishu-conversation.ts +++ b/src/plugin-sdk/feishu-conversation.ts @@ -2,6 +2,8 @@ export { buildFeishuConversationId, createFeishuThreadBindingManager, + feishuSessionBindingAdapterChannels, + feishuThreadBindingTesting, parseFeishuDirectConversationId, parseFeishuConversationId, parseFeishuTargetId, diff --git a/src/plugin-sdk/google-model-id.ts b/src/plugin-sdk/google-model-id.ts new file mode 100644 index 00000000000..c8af23d33fe --- /dev/null +++ b/src/plugin-sdk/google-model-id.ts @@ -0,0 +1,4 @@ +export { + normalizeAntigravityModelId, + normalizeGoogleModelId, +} from "../../extensions/google/model-id.js"; diff --git a/src/plugin-sdk/google.ts b/src/plugin-sdk/google.ts index 9348cb0c769..87629f2a84d 100644 --- a/src/plugin-sdk/google.ts +++ b/src/plugin-sdk/google.ts @@ -1,11 +1,17 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. export { applyGoogleGeminiModelDefault, - createGoogleThinkingPayloadWrapper, DEFAULT_GOOGLE_API_BASE_URL, GOOGLE_GEMINI_DEFAULT_MODEL, + isGoogleGenerativeAiApi, + normalizeAntigravityModelId, normalizeGoogleApiBaseUrl, + normalizeGoogleGenerativeAiBaseUrl, normalizeGoogleModelId, + normalizeGoogleProviderConfig, parseGeminiAuth, - sanitizeGoogleThinkingPayload, + resolveGoogleGenerativeAiApiOrigin, + resolveGoogleGenerativeAiTransport, + shouldNormalizeGoogleProviderConfig, + shouldNormalizeGoogleGenerativeAiProviderConfig, } from "../../extensions/google/api.js"; diff --git a/src/plugin-sdk/image-generation-core.ts b/src/plugin-sdk/image-generation-core.ts index 21cca18ec40..94f2dbf7252 100644 --- a/src/plugin-sdk/image-generation-core.ts +++ b/src/plugin-sdk/image-generation-core.ts @@ -15,7 +15,6 @@ export type { OpenClawConfig } from "../config/config.js"; export { describeFailoverError, isFailoverError } from "../agents/failover-error.js"; export { resolveApiKeyForProvider } from "../agents/model-auth.js"; -export { normalizeGoogleModelId } from "../agents/model-id-normalization.js"; export { resolveAgentModelFallbackValues, resolveAgentModelPrimaryValue, @@ -27,5 +26,6 @@ export { } from "../image-generation/provider-registry.js"; export { parseImageGenerationModelRef } from "../image-generation/model-ref.js"; export { createSubsystemLogger } from "../logging/subsystem.js"; +export { normalizeGoogleModelId } from "./google.js"; export { OPENAI_DEFAULT_IMAGE_MODEL } from "./openai.js"; export { getProviderEnvVars } from "../secrets/provider-env-vars.js"; diff --git a/src/plugin-sdk/imessage-policy.ts b/src/plugin-sdk/imessage-policy.ts index 48649a6f6d9..7bc5b8c7e1b 100644 --- a/src/plugin-sdk/imessage-policy.ts +++ b/src/plugin-sdk/imessage-policy.ts @@ -1,6 +1,7 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. export { normalizeIMessageHandle, + resolveIMessageRuntimeGroupPolicy, resolveIMessageGroupRequireMention, resolveIMessageGroupToolPolicy, } from "../../extensions/imessage/api.js"; diff --git a/src/plugin-sdk/imessage-runtime.ts b/src/plugin-sdk/imessage-runtime.ts index acd66b0c414..9e66147ae13 100644 --- a/src/plugin-sdk/imessage-runtime.ts +++ b/src/plugin-sdk/imessage-runtime.ts @@ -4,3 +4,4 @@ export { probeIMessage, sendMessageIMessage, } from "../../extensions/imessage/runtime-api.js"; +export type { IMessageProbe } from "../../extensions/imessage/runtime-api.js"; diff --git a/src/plugin-sdk/imessage.ts b/src/plugin-sdk/imessage.ts index a98506efb2d..f075628b623 100644 --- a/src/plugin-sdk/imessage.ts +++ b/src/plugin-sdk/imessage.ts @@ -1,4 +1,5 @@ export type { IMessageAccountConfig } from "../config/types.js"; +export type { IMessageProbe } from "./imessage-runtime.js"; export type { OpenClawConfig } from "../config/config.js"; export type { ChannelMessageActionContext, diff --git a/src/plugin-sdk/matrix-surface.ts b/src/plugin-sdk/matrix-surface.ts index 96d8f47582c..d63120398b3 100644 --- a/src/plugin-sdk/matrix-surface.ts +++ b/src/plugin-sdk/matrix-surface.ts @@ -1,5 +1,6 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. export { createMatrixThreadBindingManager, + matrixSessionBindingAdapterChannels, resetMatrixThreadBindingsForTests, } from "../../extensions/matrix/api.js"; diff --git a/src/plugin-sdk/minimax.ts b/src/plugin-sdk/minimax.ts index c0166508243..7947ff83291 100644 --- a/src/plugin-sdk/minimax.ts +++ b/src/plugin-sdk/minimax.ts @@ -3,6 +3,8 @@ export { buildMinimaxPortalProvider, buildMinimaxProvider, isMiniMaxModernModelId, + MINIMAX_API_BASE_URL, + MINIMAX_CN_API_BASE_URL, MINIMAX_DEFAULT_MODEL_ID, MINIMAX_DEFAULT_MODEL_REF, MINIMAX_TEXT_MODEL_CATALOG, diff --git a/src/plugin-sdk/mistral.ts b/src/plugin-sdk/mistral.ts new file mode 100644 index 00000000000..e33cf766d05 --- /dev/null +++ b/src/plugin-sdk/mistral.ts @@ -0,0 +1,2 @@ +// Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. +export { buildMistralProvider } from "../../extensions/mistral/api.js"; diff --git a/src/plugin-sdk/modelstudio.ts b/src/plugin-sdk/modelstudio.ts index 30dbf7c64de..15a47ce0897 100644 --- a/src/plugin-sdk/modelstudio.ts +++ b/src/plugin-sdk/modelstudio.ts @@ -1,5 +1,6 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. export { + applyModelStudioNativeStreamingUsageCompat, buildModelStudioDefaultModelDefinition, buildModelStudioModelDefinition, MODELSTUDIO_BASE_URL, @@ -11,5 +12,6 @@ export { MODELSTUDIO_STANDARD_CN_BASE_URL, MODELSTUDIO_STANDARD_GLOBAL_BASE_URL, MODELSTUDIO_MODEL_CATALOG, + isNativeModelStudioBaseUrl, buildModelStudioProvider, } from "../../extensions/modelstudio/api.js"; diff --git a/src/plugin-sdk/moonshot.ts b/src/plugin-sdk/moonshot.ts index b9c8dfe3b4f..6794eb71376 100644 --- a/src/plugin-sdk/moonshot.ts +++ b/src/plugin-sdk/moonshot.ts @@ -1,2 +1,10 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. -export { buildMoonshotProvider } from "../../extensions/moonshot/api.js"; +export { + applyMoonshotNativeStreamingUsageCompat, + buildMoonshotProvider, + isNativeMoonshotBaseUrl, + MOONSHOT_BASE_URL, + MOONSHOT_CN_BASE_URL, + MOONSHOT_DEFAULT_MODEL_ID, + MOONSHOT_DEFAULT_MODEL_REF, +} from "../../extensions/moonshot/api.js"; diff --git a/src/plugin-sdk/provider-auth-api-key.ts b/src/plugin-sdk/provider-auth-api-key.ts index b083d8e27cb..1564f541ce2 100644 --- a/src/plugin-sdk/provider-auth-api-key.ts +++ b/src/plugin-sdk/provider-auth-api-key.ts @@ -3,7 +3,7 @@ export type { OpenClawConfig } from "../config/config.js"; export type { SecretInput } from "../config/types.secrets.js"; -export { upsertAuthProfile } from "../agents/auth-profiles.js"; +export { upsertAuthProfile } from "../agents/auth-profiles/profiles.js"; export { formatApiKeyPreview, normalizeApiKeyInput, diff --git a/src/plugin-sdk/provider-auth-runtime.ts b/src/plugin-sdk/provider-auth-runtime.ts new file mode 100644 index 00000000000..2fa5310946c --- /dev/null +++ b/src/plugin-sdk/provider-auth-runtime.ts @@ -0,0 +1,8 @@ +// Public runtime auth helpers for provider plugins. + +export { resolveEnvApiKey } from "../agents/model-auth-env.js"; +export { + requireApiKey, + resolveApiKeyForProvider, + resolveAwsSdkEnvVarName, +} from "../agents/model-auth.js"; diff --git a/src/plugin-sdk/provider-auth.ts b/src/plugin-sdk/provider-auth.ts index 3378e7b71a6..b6b59e21f6d 100644 --- a/src/plugin-sdk/provider-auth.ts +++ b/src/plugin-sdk/provider-auth.ts @@ -16,7 +16,6 @@ export { resolveOAuthApiKeyMarker, resolveNonEnvSecretRefApiKeyMarker, } from "../agents/model-auth-markers.js"; -export { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js"; export { formatApiKeyPreview, normalizeApiKeyInput, diff --git a/src/plugin-sdk/provider-models.ts b/src/plugin-sdk/provider-models.ts index 0233fe334ff..742fcc7d32e 100644 --- a/src/plugin-sdk/provider-models.ts +++ b/src/plugin-sdk/provider-models.ts @@ -1,9 +1,9 @@ // Public model/catalog helpers for provider plugins. -import type { ModelDefinitionConfig } from "../config/types.models.js"; +import type { BedrockDiscoveryConfig, ModelDefinitionConfig } from "../config/types.models.js"; export type { ModelApi, ModelProviderConfig } from "../config/types.models.js"; -export type { ModelDefinitionConfig } from "../config/types.models.js"; +export type { BedrockDiscoveryConfig, ModelDefinitionConfig } from "../config/types.models.js"; export type { ProviderPlugin } from "../plugins/types.js"; export type { KilocodeModelCatalogEntry } from "../plugins/provider-model-kilocode.js"; diff --git a/src/plugin-sdk/provider-setup.ts b/src/plugin-sdk/provider-setup.ts index 3c50bfd6dd1..e574f7f8423 100644 --- a/src/plugin-sdk/provider-setup.ts +++ b/src/plugin-sdk/provider-setup.ts @@ -10,6 +10,7 @@ export type { export { applyProviderDefaultModel, configureOpenAICompatibleSelfHostedProviderNonInteractive, + discoverOpenAICompatibleLocalModels, discoverOpenAICompatibleSelfHostedProvider, promptAndConfigureOpenAICompatibleSelfHostedProvider, promptAndConfigureOpenAICompatibleSelfHostedProviderAuth, @@ -31,7 +32,5 @@ export { VLLM_DEFAULT_MAX_TOKENS, promptAndConfigureVllm, } from "../plugins/provider-vllm-setup.js"; -export { - buildSglangProvider, - buildVllmProvider, -} from "../agents/models-config.providers.discovery.js"; +export { buildVllmProvider } from "./vllm.js"; +export { buildSglangProvider } from "./sglang.js"; diff --git a/src/plugin-sdk/runtime-api-guardrails.test.ts b/src/plugin-sdk/runtime-api-guardrails.test.ts index 5a08f47f9e8..c8363b1e72f 100644 --- a/src/plugin-sdk/runtime-api-guardrails.test.ts +++ b/src/plugin-sdk/runtime-api-guardrails.test.ts @@ -33,6 +33,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { monitorIMessageProvider } from "./src/monitor.js";', 'export type { MonitorIMessageOpts } from "./src/monitor.js";', 'export { probeIMessage } from "./src/probe.js";', + 'export type { IMessageProbe } from "./src/probe.js";', 'export { sendMessageIMessage } from "./src/send.js";', ], "extensions/googlechat/runtime-api.ts": ['export * from "openclaw/plugin-sdk/googlechat";'], @@ -78,7 +79,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record = { 'export { resolveTelegramFetch, resolveTelegramTransport, shouldRetryTelegramTransportFallback } from "./src/fetch.js";', 'export { makeProxyFetch } from "./src/proxy.js";', 'export { createForumTopicTelegram, deleteMessageTelegram, editForumTopicTelegram, editMessageReplyMarkupTelegram, editMessageTelegram, pinMessageTelegram, reactMessageTelegram, renameForumTopicTelegram, sendMessageTelegram, sendPollTelegram, sendStickerTelegram, sendTypingTelegram, unpinMessageTelegram } from "./src/send.js";', - 'export { createTelegramThreadBindingManager, getTelegramThreadBindingManager, setTelegramThreadBindingIdleTimeoutBySessionKey, setTelegramThreadBindingMaxAgeBySessionKey } from "./src/thread-bindings.js";', + 'export { createTelegramThreadBindingManager, getTelegramThreadBindingManager, resetTelegramThreadBindingsForTests, setTelegramThreadBindingIdleTimeoutBySessionKey, setTelegramThreadBindingMaxAgeBySessionKey } from "./src/thread-bindings.js";', 'export { resolveTelegramToken } from "./src/token.js";', ], "extensions/whatsapp/runtime-api.ts": [ diff --git a/src/plugin-sdk/self-hosted-provider-setup.ts b/src/plugin-sdk/self-hosted-provider-setup.ts index 871aa4fb566..cae33e74580 100644 --- a/src/plugin-sdk/self-hosted-provider-setup.ts +++ b/src/plugin-sdk/self-hosted-provider-setup.ts @@ -10,6 +10,7 @@ export type { export { applyProviderDefaultModel, configureOpenAICompatibleSelfHostedProviderNonInteractive, + discoverOpenAICompatibleLocalModels, discoverOpenAICompatibleSelfHostedProvider, promptAndConfigureOpenAICompatibleSelfHostedProvider, promptAndConfigureOpenAICompatibleSelfHostedProviderAuth, @@ -18,7 +19,5 @@ export { SELF_HOSTED_DEFAULT_MAX_TOKENS, } from "../plugins/provider-self-hosted-setup.js"; -export { - buildSglangProvider, - buildVllmProvider, -} from "../agents/models-config.providers.discovery.js"; +export { buildVllmProvider } from "./vllm.js"; +export { buildSglangProvider } from "./sglang.js"; diff --git a/src/plugin-sdk/sglang.ts b/src/plugin-sdk/sglang.ts index 6bd87bcc196..53ec7fa2c99 100644 --- a/src/plugin-sdk/sglang.ts +++ b/src/plugin-sdk/sglang.ts @@ -1,5 +1,6 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. export { + buildSglangProvider, SGLANG_DEFAULT_API_KEY_ENV_VAR, SGLANG_DEFAULT_BASE_URL, SGLANG_MODEL_PLACEHOLDER, diff --git a/src/plugin-sdk/signal-surface.ts b/src/plugin-sdk/signal-surface.ts index b34cdf77bff..cdf3d65f107 100644 --- a/src/plugin-sdk/signal-surface.ts +++ b/src/plugin-sdk/signal-surface.ts @@ -12,4 +12,8 @@ export { sendReactionSignal, signalMessageActions, } from "../../extensions/signal/api.js"; -export type { ResolvedSignalAccount, SignalSender } from "../../extensions/signal/api.js"; +export type { + ResolvedSignalAccount, + SignalProbe, + SignalSender, +} from "../../extensions/signal/api.js"; diff --git a/src/plugin-sdk/slack-surface.ts b/src/plugin-sdk/slack-surface.ts index 5f61b82a725..82ba31a3db2 100644 --- a/src/plugin-sdk/slack-surface.ts +++ b/src/plugin-sdk/slack-surface.ts @@ -24,6 +24,7 @@ export { resolveDefaultSlackAccountId, resolveSlackAutoThreadId, resolveSlackGroupRequireMention, + resolveSlackRuntimeGroupPolicy, resolveSlackGroupToolPolicy, resolveSlackReplyToMode, sendSlackMessage, @@ -34,4 +35,8 @@ export { removeSlackReaction, unpinSlackMessage, } from "../../extensions/slack/api.js"; -export type { InspectedSlackAccount, ResolvedSlackAccount } from "../../extensions/slack/api.js"; +export type { + InspectedSlackAccount, + ResolvedSlackAccount, + SlackProbe, +} from "../../extensions/slack/api.js"; diff --git a/src/plugin-sdk/telegram-runtime-surface.ts b/src/plugin-sdk/telegram-runtime-surface.ts index 76347d447ad..8fe0e31d45c 100644 --- a/src/plugin-sdk/telegram-runtime-surface.ts +++ b/src/plugin-sdk/telegram-runtime-surface.ts @@ -14,6 +14,8 @@ export { probeTelegram, reactMessageTelegram, renameForumTopicTelegram, + resetTelegramThreadBindingsForTests, + resolveTelegramRuntimeGroupPolicy, resolveTelegramToken, sendMessageTelegram, sendPollTelegram, diff --git a/src/plugin-sdk/telegram-surface.ts b/src/plugin-sdk/telegram-surface.ts index 0bc4a041257..9b4f05c1557 100644 --- a/src/plugin-sdk/telegram-surface.ts +++ b/src/plugin-sdk/telegram-surface.ts @@ -38,4 +38,6 @@ export type { StickerMetadata, TelegramButtonStyle, TelegramInlineButtons, + TelegramProbe, + TelegramTokenResolution, } from "../../extensions/telegram/api.js"; diff --git a/src/plugin-sdk/vllm.ts b/src/plugin-sdk/vllm.ts index 2d8b32849e4..4d21740bc93 100644 --- a/src/plugin-sdk/vllm.ts +++ b/src/plugin-sdk/vllm.ts @@ -1,5 +1,6 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. export { + buildVllmProvider, VLLM_DEFAULT_API_KEY_ENV_VAR, VLLM_DEFAULT_BASE_URL, VLLM_MODEL_PLACEHOLDER, diff --git a/src/plugin-sdk/whatsapp-auth-presence.ts b/src/plugin-sdk/whatsapp-auth-presence.ts new file mode 100644 index 00000000000..e3f20c0ab8a --- /dev/null +++ b/src/plugin-sdk/whatsapp-auth-presence.ts @@ -0,0 +1 @@ +export { hasAnyWhatsAppAuth } from "../../extensions/whatsapp/auth-presence.js"; diff --git a/src/plugin-sdk/whatsapp-surface.ts b/src/plugin-sdk/whatsapp-surface.ts index af65bb8be63..6f024052898 100644 --- a/src/plugin-sdk/whatsapp-surface.ts +++ b/src/plugin-sdk/whatsapp-surface.ts @@ -9,6 +9,7 @@ export { resolveWhatsAppGroupRequireMention, resolveWhatsAppGroupToolPolicy, resolveWhatsAppOutboundTarget, + whatsappAccessControlTesting, } from "../../extensions/whatsapp/api.js"; export type { WebChannelStatus, diff --git a/src/plugin-sdk/xai-model-id.ts b/src/plugin-sdk/xai-model-id.ts new file mode 100644 index 00000000000..463481c66f3 --- /dev/null +++ b/src/plugin-sdk/xai-model-id.ts @@ -0,0 +1 @@ +export { normalizeXaiModelId } from "../../extensions/xai/model-id.js"; diff --git a/src/plugin-sdk/zalo-setup.ts b/src/plugin-sdk/zalo-setup.ts index a4ba9c5e6e3..ec2f5813338 100644 --- a/src/plugin-sdk/zalo-setup.ts +++ b/src/plugin-sdk/zalo-setup.ts @@ -1,2 +1,7 @@ // Generated by scripts/generate-plugin-sdk-facades.mjs. Do not edit manually. -export { zaloSetupAdapter, zaloSetupWizard } from "../../extensions/zalo/api.js"; +export { + evaluateZaloGroupAccess, + resolveZaloRuntimeGroupPolicy, + zaloSetupAdapter, + zaloSetupWizard, +} from "../../extensions/zalo/api.js"; diff --git a/src/plugin-sdk/zalo.ts b/src/plugin-sdk/zalo.ts index 2a2eba66bf4..a1526fbc215 100644 --- a/src/plugin-sdk/zalo.ts +++ b/src/plugin-sdk/zalo.ts @@ -63,6 +63,7 @@ export type { WizardPrompter } from "../wizard/prompts.js"; export { formatAllowFromLowercase, isNormalizedSenderAllowed } from "./allow-from.js"; export { zaloSetupAdapter } from "./zalo-setup.js"; export { zaloSetupWizard } from "./zalo-setup.js"; +export { evaluateZaloGroupAccess, resolveZaloRuntimeGroupPolicy } from "./zalo-setup.js"; export { resolveDirectDmAuthorizationOutcome, resolveSenderCommandAuthorizationWithRuntime, diff --git a/src/plugins/provider-self-hosted-setup.ts b/src/plugins/provider-self-hosted-setup.ts index db7223ed987..1140103dfa5 100644 --- a/src/plugins/provider-self-hosted-setup.ts +++ b/src/plugins/provider-self-hosted-setup.ts @@ -6,6 +6,9 @@ import { SELF_HOSTED_DEFAULT_MAX_TOKENS, } from "../agents/self-hosted-provider-defaults.js"; import type { OpenClawConfig } from "../config/config.js"; +import type { ModelDefinitionConfig } from "../config/types.models.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import { isReasoningModelHeuristic } from "../plugin-sdk/provider-reasoning.js"; import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js"; import type { WizardPrompter } from "../wizard/prompts.js"; import { applyAuthProfileConfig } from "./provider-auth-helpers.js"; @@ -22,6 +25,68 @@ export { SELF_HOSTED_DEFAULT_MAX_TOKENS, } from "../agents/self-hosted-provider-defaults.js"; +const log = createSubsystemLogger("plugins/self-hosted-provider-setup"); + +type OpenAICompatModelsResponse = { + data?: Array<{ + id?: string; + }>; +}; + +export async function discoverOpenAICompatibleLocalModels(params: { + baseUrl: string; + apiKey?: string; + label: string; + contextWindow?: number; + maxTokens?: number; + env?: NodeJS.ProcessEnv; +}): Promise { + const env = params.env ?? process.env; + if (env.VITEST || env.NODE_ENV === "test") { + return []; + } + + const trimmedBaseUrl = params.baseUrl.trim().replace(/\/+$/, ""); + const url = `${trimmedBaseUrl}/models`; + + try { + const trimmedApiKey = params.apiKey?.trim(); + const response = await fetch(url, { + headers: trimmedApiKey ? { Authorization: `Bearer ${trimmedApiKey}` } : undefined, + signal: AbortSignal.timeout(5000), + }); + if (!response.ok) { + log.warn(`Failed to discover ${params.label} models: ${response.status}`); + return []; + } + const data = (await response.json()) as OpenAICompatModelsResponse; + const models = data.data ?? []; + if (models.length === 0) { + log.warn(`No ${params.label} models found on local instance`); + return []; + } + + return models + .map((model) => ({ id: typeof model.id === "string" ? model.id.trim() : "" })) + .filter((model) => Boolean(model.id)) + .map((model) => { + const modelId = model.id; + return { + id: modelId, + name: modelId, + reasoning: isReasoningModelHeuristic(modelId), + input: ["text"], + cost: SELF_HOSTED_DEFAULT_COST, + contextWindow: params.contextWindow ?? SELF_HOSTED_DEFAULT_CONTEXT_WINDOW, + maxTokens: params.maxTokens ?? SELF_HOSTED_DEFAULT_MAX_TOKENS, + } satisfies ModelDefinitionConfig; + }); + } catch (error) { + log.warn(`Failed to discover ${params.label} models: ${String(error)}`); + return []; + } +} + export function applyProviderDefaultModel(cfg: OpenClawConfig, modelRef: string): OpenClawConfig { const existingModel = cfg.agents?.defaults?.model; const fallbacks =