mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 10:40:20 +00:00
Media: extract runtime provider registry
This commit is contained in:
47
src/extension-host/media-runtime-registry.test.ts
Normal file
47
src/extension-host/media-runtime-registry.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildExtensionHostMediaUnderstandingRegistry,
|
||||
getExtensionHostMediaUnderstandingProvider,
|
||||
normalizeExtensionHostMediaProviderId,
|
||||
} from "./media-runtime-registry.js";
|
||||
|
||||
describe("extension host media runtime registry", () => {
|
||||
it("registers built-in providers", () => {
|
||||
const registry = buildExtensionHostMediaUnderstandingRegistry();
|
||||
const provider = getExtensionHostMediaUnderstandingProvider("mistral", registry);
|
||||
|
||||
expect(provider?.id).toBe("mistral");
|
||||
expect(provider?.capabilities).toEqual(["audio"]);
|
||||
});
|
||||
|
||||
it("keeps media-specific provider normalization", () => {
|
||||
expect(normalizeExtensionHostMediaProviderId("gemini")).toBe("google");
|
||||
});
|
||||
|
||||
it("merges overrides onto built-in providers", () => {
|
||||
const registry = buildExtensionHostMediaUnderstandingRegistry({
|
||||
openai: {
|
||||
id: "openai",
|
||||
capabilities: ["image"],
|
||||
},
|
||||
});
|
||||
|
||||
const provider = getExtensionHostMediaUnderstandingProvider("openai", registry);
|
||||
expect(provider?.id).toBe("openai");
|
||||
expect(provider?.capabilities).toEqual(["image"]);
|
||||
expect(provider?.describeImage).toBeTypeOf("function");
|
||||
});
|
||||
|
||||
it("adds brand new providers", () => {
|
||||
const registry = buildExtensionHostMediaUnderstandingRegistry({
|
||||
custom: {
|
||||
id: "custom",
|
||||
capabilities: ["audio"],
|
||||
},
|
||||
});
|
||||
|
||||
const provider = getExtensionHostMediaUnderstandingProvider("custom", registry);
|
||||
expect(provider?.id).toBe("custom");
|
||||
expect(provider?.capabilities).toEqual(["audio"]);
|
||||
});
|
||||
});
|
||||
73
src/extension-host/media-runtime-registry.ts
Normal file
73
src/extension-host/media-runtime-registry.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import { anthropicProvider } from "../media-understanding/providers/anthropic/index.js";
|
||||
import { deepgramProvider } from "../media-understanding/providers/deepgram/index.js";
|
||||
import { googleProvider } from "../media-understanding/providers/google/index.js";
|
||||
import { groqProvider } from "../media-understanding/providers/groq/index.js";
|
||||
import {
|
||||
minimaxPortalProvider,
|
||||
minimaxProvider,
|
||||
} from "../media-understanding/providers/minimax/index.js";
|
||||
import { mistralProvider } from "../media-understanding/providers/mistral/index.js";
|
||||
import { moonshotProvider } from "../media-understanding/providers/moonshot/index.js";
|
||||
import { openaiProvider } from "../media-understanding/providers/openai/index.js";
|
||||
import { zaiProvider } from "../media-understanding/providers/zai/index.js";
|
||||
import type { MediaUnderstandingProvider } from "../media-understanding/types.js";
|
||||
|
||||
const EXTENSION_HOST_MEDIA_PROVIDERS: readonly MediaUnderstandingProvider[] = [
|
||||
groqProvider,
|
||||
openaiProvider,
|
||||
googleProvider,
|
||||
anthropicProvider,
|
||||
minimaxProvider,
|
||||
minimaxPortalProvider,
|
||||
moonshotProvider,
|
||||
mistralProvider,
|
||||
zaiProvider,
|
||||
deepgramProvider,
|
||||
];
|
||||
|
||||
export type ExtensionHostMediaUnderstandingProviderRegistry = Map<
|
||||
string,
|
||||
MediaUnderstandingProvider
|
||||
>;
|
||||
|
||||
export function normalizeExtensionHostMediaProviderId(id: string): string {
|
||||
const normalized = normalizeProviderId(id);
|
||||
if (normalized === "gemini") {
|
||||
return "google";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function buildExtensionHostMediaUnderstandingRegistry(
|
||||
overrides?: Record<string, MediaUnderstandingProvider>,
|
||||
): ExtensionHostMediaUnderstandingProviderRegistry {
|
||||
const registry: ExtensionHostMediaUnderstandingProviderRegistry = new Map();
|
||||
for (const provider of EXTENSION_HOST_MEDIA_PROVIDERS) {
|
||||
registry.set(normalizeExtensionHostMediaProviderId(provider.id), provider);
|
||||
}
|
||||
if (!overrides) {
|
||||
return registry;
|
||||
}
|
||||
|
||||
for (const [key, provider] of Object.entries(overrides)) {
|
||||
const normalizedKey = normalizeExtensionHostMediaProviderId(key);
|
||||
const existing = registry.get(normalizedKey);
|
||||
const merged = existing
|
||||
? {
|
||||
...existing,
|
||||
...provider,
|
||||
capabilities: provider.capabilities ?? existing.capabilities,
|
||||
}
|
||||
: provider;
|
||||
registry.set(normalizedKey, merged);
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
||||
export function getExtensionHostMediaUnderstandingProvider(
|
||||
id: string,
|
||||
registry: ExtensionHostMediaUnderstandingProviderRegistry,
|
||||
): MediaUnderstandingProvider | undefined {
|
||||
return registry.get(normalizeExtensionHostMediaProviderId(id));
|
||||
}
|
||||
@@ -1,63 +1,6 @@
|
||||
import { normalizeProviderId } from "../../agents/model-selection.js";
|
||||
import type { MediaUnderstandingProvider } from "../types.js";
|
||||
import { anthropicProvider } from "./anthropic/index.js";
|
||||
import { deepgramProvider } from "./deepgram/index.js";
|
||||
import { googleProvider } from "./google/index.js";
|
||||
import { groqProvider } from "./groq/index.js";
|
||||
import { minimaxPortalProvider, minimaxProvider } from "./minimax/index.js";
|
||||
import { mistralProvider } from "./mistral/index.js";
|
||||
import { moonshotProvider } from "./moonshot/index.js";
|
||||
import { openaiProvider } from "./openai/index.js";
|
||||
import { zaiProvider } from "./zai/index.js";
|
||||
|
||||
const PROVIDERS: MediaUnderstandingProvider[] = [
|
||||
groqProvider,
|
||||
openaiProvider,
|
||||
googleProvider,
|
||||
anthropicProvider,
|
||||
minimaxProvider,
|
||||
minimaxPortalProvider,
|
||||
moonshotProvider,
|
||||
mistralProvider,
|
||||
zaiProvider,
|
||||
deepgramProvider,
|
||||
];
|
||||
|
||||
export function normalizeMediaProviderId(id: string): string {
|
||||
const normalized = normalizeProviderId(id);
|
||||
if (normalized === "gemini") {
|
||||
return "google";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function buildMediaUnderstandingRegistry(
|
||||
overrides?: Record<string, MediaUnderstandingProvider>,
|
||||
): Map<string, MediaUnderstandingProvider> {
|
||||
const registry = new Map<string, MediaUnderstandingProvider>();
|
||||
for (const provider of PROVIDERS) {
|
||||
registry.set(normalizeMediaProviderId(provider.id), provider);
|
||||
}
|
||||
if (overrides) {
|
||||
for (const [key, provider] of Object.entries(overrides)) {
|
||||
const normalizedKey = normalizeMediaProviderId(key);
|
||||
const existing = registry.get(normalizedKey);
|
||||
const merged = existing
|
||||
? {
|
||||
...existing,
|
||||
...provider,
|
||||
capabilities: provider.capabilities ?? existing.capabilities,
|
||||
}
|
||||
: provider;
|
||||
registry.set(normalizedKey, merged);
|
||||
}
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
||||
export function getMediaUnderstandingProvider(
|
||||
id: string,
|
||||
registry: Map<string, MediaUnderstandingProvider>,
|
||||
): MediaUnderstandingProvider | undefined {
|
||||
return registry.get(normalizeMediaProviderId(id));
|
||||
}
|
||||
export {
|
||||
buildExtensionHostMediaUnderstandingRegistry as buildMediaUnderstandingRegistry,
|
||||
getExtensionHostMediaUnderstandingProvider as getMediaUnderstandingProvider,
|
||||
normalizeExtensionHostMediaProviderId as normalizeMediaProviderId,
|
||||
type ExtensionHostMediaUnderstandingProviderRegistry as MediaUnderstandingProviderRegistry,
|
||||
} from "../../extension-host/media-runtime-registry.js";
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
MediaUnderstandingModelConfig,
|
||||
MediaUnderstandingScopeConfig,
|
||||
} from "../config/types.tools.js";
|
||||
import { normalizeExtensionHostMediaProviderId } from "../extension-host/media-runtime-registry.js";
|
||||
import { logVerbose, shouldLogVerbose } from "../globals.js";
|
||||
import {
|
||||
DEFAULT_MAX_BYTES,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
DEFAULT_MEDIA_CONCURRENCY,
|
||||
DEFAULT_PROMPT,
|
||||
} from "./defaults.js";
|
||||
import { normalizeMediaProviderId } from "./providers/index.js";
|
||||
import { normalizeMediaUnderstandingChatType, resolveMediaUnderstandingScope } from "./scope.js";
|
||||
import type { MediaUnderstandingCapability } from "./types.js";
|
||||
|
||||
@@ -91,7 +91,7 @@ function resolveEntryCapabilities(params: {
|
||||
if (entryType === "cli") {
|
||||
return undefined;
|
||||
}
|
||||
const providerId = normalizeMediaProviderId(params.entry.provider ?? "");
|
||||
const providerId = normalizeExtensionHostMediaProviderId(params.entry.provider ?? "");
|
||||
if (!providerId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export function resolveEntriesWithActiveFallback(params: {
|
||||
if (!activeProviderRaw) {
|
||||
return entries;
|
||||
}
|
||||
const activeProvider = normalizeMediaProviderId(activeProviderRaw);
|
||||
const activeProvider = normalizeExtensionHostMediaProviderId(activeProviderRaw);
|
||||
if (!activeProvider) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ import type {
|
||||
MediaUnderstandingConfig,
|
||||
MediaUnderstandingModelConfig,
|
||||
} from "../config/types.tools.js";
|
||||
import {
|
||||
getExtensionHostMediaUnderstandingProvider,
|
||||
normalizeExtensionHostMediaProviderId,
|
||||
} from "../extension-host/media-runtime-registry.js";
|
||||
import { logVerbose, shouldLogVerbose } from "../globals.js";
|
||||
import { resolveProxyFetchFromEnv } from "../infra/net/proxy-fetch.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
|
||||
@@ -27,7 +31,6 @@ import { MediaUnderstandingSkipError } from "./errors.js";
|
||||
import { fileExists } from "./fs.js";
|
||||
import { extractGeminiResponse } from "./output-extract.js";
|
||||
import { describeImageWithModel } from "./providers/image.js";
|
||||
import { getMediaUnderstandingProvider, normalizeMediaProviderId } from "./providers/index.js";
|
||||
import { resolveMaxBytes, resolveMaxChars, resolvePrompt, resolveTimeoutMs } from "./resolve.js";
|
||||
import type {
|
||||
MediaUnderstandingCapability,
|
||||
@@ -302,7 +305,9 @@ export function buildModelDecision(params: {
|
||||
};
|
||||
}
|
||||
const providerIdRaw = params.entry.provider?.trim();
|
||||
const providerId = providerIdRaw ? normalizeMediaProviderId(providerIdRaw) : undefined;
|
||||
const providerId = providerIdRaw
|
||||
? normalizeExtensionHostMediaProviderId(providerIdRaw)
|
||||
: undefined;
|
||||
return {
|
||||
type: "provider",
|
||||
provider: providerId ?? providerIdRaw,
|
||||
@@ -429,7 +434,7 @@ export async function runProviderEntry(params: {
|
||||
if (!providerIdRaw) {
|
||||
throw new Error(`Provider entry missing provider for ${capability}`);
|
||||
}
|
||||
const providerId = normalizeMediaProviderId(providerIdRaw);
|
||||
const providerId = normalizeExtensionHostMediaProviderId(providerIdRaw);
|
||||
const { maxBytes, maxChars, timeoutMs, prompt } = resolveEntryRunOptions({
|
||||
capability,
|
||||
entry,
|
||||
@@ -450,7 +455,10 @@ export async function runProviderEntry(params: {
|
||||
maxBytes,
|
||||
timeoutMs,
|
||||
});
|
||||
const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry);
|
||||
const provider = getExtensionHostMediaUnderstandingProvider(
|
||||
providerId,
|
||||
params.providerRegistry,
|
||||
);
|
||||
const imageInput = {
|
||||
buffer: media.buffer,
|
||||
fileName: media.fileName,
|
||||
@@ -475,7 +483,7 @@ export async function runProviderEntry(params: {
|
||||
};
|
||||
}
|
||||
|
||||
const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry);
|
||||
const provider = getExtensionHostMediaUnderstandingProvider(providerId, params.providerRegistry);
|
||||
if (!provider) {
|
||||
throw new Error(`Media provider not available: ${providerId}`);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ import type {
|
||||
MediaUnderstandingConfig,
|
||||
MediaUnderstandingModelConfig,
|
||||
} from "../config/types.tools.js";
|
||||
import {
|
||||
buildExtensionHostMediaUnderstandingRegistry,
|
||||
getExtensionHostMediaUnderstandingProvider,
|
||||
normalizeExtensionHostMediaProviderId,
|
||||
} from "../extension-host/media-runtime-registry.js";
|
||||
import { logVerbose, shouldLogVerbose } from "../globals.js";
|
||||
import {
|
||||
mergeInboundPathRoots,
|
||||
@@ -40,11 +45,6 @@ import {
|
||||
import { isMediaUnderstandingSkipError } from "./errors.js";
|
||||
import { fileExists } from "./fs.js";
|
||||
import { extractGeminiResponse } from "./output-extract.js";
|
||||
import {
|
||||
buildMediaUnderstandingRegistry,
|
||||
getMediaUnderstandingProvider,
|
||||
normalizeMediaProviderId,
|
||||
} from "./providers/index.js";
|
||||
import { resolveModelEntries, resolveScopeDecision } from "./resolve.js";
|
||||
import {
|
||||
buildModelDecision,
|
||||
@@ -76,7 +76,7 @@ export type RunCapabilityResult = {
|
||||
export function buildProviderRegistry(
|
||||
overrides?: Record<string, MediaUnderstandingProvider>,
|
||||
): ProviderRegistry {
|
||||
return buildMediaUnderstandingRegistry(overrides);
|
||||
return buildExtensionHostMediaUnderstandingRegistry(overrides);
|
||||
}
|
||||
|
||||
export function normalizeMediaAttachments(ctx: MsgContext): MediaAttachment[] {
|
||||
@@ -349,7 +349,7 @@ async function resolveKeyEntry(params: {
|
||||
providerId: string,
|
||||
model?: string,
|
||||
): Promise<MediaUnderstandingModelConfig | null> => {
|
||||
const provider = getMediaUnderstandingProvider(providerId, providerRegistry);
|
||||
const provider = getExtensionHostMediaUnderstandingProvider(providerId, providerRegistry);
|
||||
if (!provider) {
|
||||
return null;
|
||||
}
|
||||
@@ -536,11 +536,11 @@ async function resolveActiveModelEntry(params: {
|
||||
if (!activeProviderRaw) {
|
||||
return null;
|
||||
}
|
||||
const providerId = normalizeMediaProviderId(activeProviderRaw);
|
||||
const providerId = normalizeExtensionHostMediaProviderId(activeProviderRaw);
|
||||
if (!providerId) {
|
||||
return null;
|
||||
}
|
||||
const provider = getMediaUnderstandingProvider(providerId, params.providerRegistry);
|
||||
const provider = getExtensionHostMediaUnderstandingProvider(providerId, params.providerRegistry);
|
||||
if (!provider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user