feat(qwen): add qwen provider and video generation

This commit is contained in:
Peter Steinberger
2026-04-04 17:43:15 +01:00
parent 759373e887
commit e3ac0f43df
104 changed files with 2477 additions and 483 deletions

View File

@@ -24,6 +24,7 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi
registerRealtimeVoiceProvider() {},
registerMediaUnderstandingProvider() {},
registerImageGenerationProvider() {},
registerVideoGenerationProvider() {},
registerWebFetchProvider() {},
registerWebSearchProvider() {},
registerInteractiveHandler() {},

View File

@@ -4,6 +4,7 @@ import {
mediaUnderstandingProviderContractRegistry,
pluginRegistrationContractRegistry,
speechProviderContractRegistry,
videoGenerationProviderContractRegistry,
} from "../../../src/plugins/contracts/registry.js";
import { loadPluginManifestRegistry } from "../../../src/plugins/manifest-registry.js";
@@ -17,11 +18,13 @@ type PluginRegistrationContractParams = {
realtimeVoiceProviderIds?: string[];
mediaUnderstandingProviderIds?: string[];
imageGenerationProviderIds?: string[];
videoGenerationProviderIds?: string[];
cliBackendIds?: string[];
toolNames?: string[];
requireSpeechVoices?: boolean;
requireDescribeImages?: boolean;
requireGenerateImage?: boolean;
requireGenerateVideo?: boolean;
manifestAuthChoice?: {
pluginId: string;
choiceId: string;
@@ -91,6 +94,23 @@ function findImageGenerationProvider(pluginId: string) {
return entry.provider;
}
function findVideoGenerationProviderIds(pluginId: string) {
return videoGenerationProviderContractRegistry
.filter((entry) => entry.pluginId === pluginId)
.map((entry) => entry.provider.id)
.toSorted((left, right) => left.localeCompare(right));
}
function findVideoGenerationProvider(pluginId: string) {
const entry = videoGenerationProviderContractRegistry.find(
(candidate) => candidate.pluginId === pluginId,
);
if (!entry) {
throw new Error(`video-generation provider contract missing for ${pluginId}`);
}
return entry.provider;
}
export function describePluginRegistrationContract(params: PluginRegistrationContractParams) {
describe(`${params.pluginId} plugin registration contract`, () => {
if (params.providerIds) {
@@ -162,6 +182,17 @@ export function describePluginRegistrationContract(params: PluginRegistrationCon
});
}
if (params.videoGenerationProviderIds) {
it("keeps bundled video-generation ownership explicit", () => {
expect(findRegistration(params.pluginId).videoGenerationProviderIds).toEqual(
params.videoGenerationProviderIds,
);
expect(findVideoGenerationProviderIds(params.pluginId)).toEqual(
params.videoGenerationProviderIds,
);
});
}
if (params.cliBackendIds) {
it("keeps bundled CLI backend ownership explicit", () => {
expect(findRegistration(params.pluginId).cliBackendIds).toEqual(params.cliBackendIds);
@@ -196,6 +227,14 @@ export function describePluginRegistrationContract(params: PluginRegistrationCon
});
}
if (params.requireGenerateVideo) {
it("keeps bundled video-generation support explicit", () => {
expect(findVideoGenerationProvider(params.pluginId).generateVideo).toEqual(
expect.any(Function),
);
});
}
const manifestAuthChoice = params.manifestAuthChoice;
if (manifestAuthChoice) {
it("keeps onboarding auth grouping explicit", () => {

View File

@@ -162,6 +162,10 @@ export function createPluginRuntimeMock(overrides: DeepPartial<PluginRuntime> =
generate: vi.fn() as unknown as PluginRuntime["imageGeneration"]["generate"],
listProviders: vi.fn() as unknown as PluginRuntime["imageGeneration"]["listProviders"],
},
videoGeneration: {
generate: vi.fn() as unknown as PluginRuntime["videoGeneration"]["generate"],
listProviders: vi.fn() as unknown as PluginRuntime["videoGeneration"]["listProviders"],
},
webSearch: {
listProviders: vi.fn() as unknown as PluginRuntime["webSearch"]["listProviders"],
search: vi.fn() as unknown as PluginRuntime["webSearch"]["search"],

View File

@@ -28,8 +28,7 @@ const bundledProviderModules = vi.hoisted(() => ({
import.meta.url,
).pathname,
minimaxIndexModuleUrl: new URL("../../../extensions/minimax/index.ts", import.meta.url).href,
modelStudioIndexModuleUrl: new URL("../../../extensions/modelstudio/index.ts", import.meta.url)
.href,
modelStudioIndexModuleUrl: new URL("../../../extensions/qwen/index.ts", import.meta.url).href,
ollamaApiModuleId: new URL("../../../extensions/ollama/api.js", import.meta.url).pathname,
ollamaIndexModuleUrl: new URL("../../../extensions/ollama/index.ts", import.meta.url).href,
sglangApiModuleId: new URL("../../../extensions/sglang/api.js", import.meta.url).pathname,
@@ -230,7 +229,7 @@ function installDiscoveryHooks(state: DiscoveryState) {
state.sglangProvider = requireProvider(sglangProviders, "sglang");
state.minimaxProvider = requireProvider(minimaxProviders, "minimax");
state.minimaxPortalProvider = requireProvider(minimaxProviders, "minimax-portal");
state.modelStudioProvider = requireProvider(modelStudioProviders, "modelstudio");
state.modelStudioProvider = requireProvider(modelStudioProviders, "qwen");
state.cloudflareAiGatewayProvider = requireProvider(
cloudflareAiGatewayProviders,
"cloudflare-ai-gateway",

View File

@@ -129,6 +129,7 @@ function createTestRegistryForSetup(
realtimeVoiceProviders: [],
mediaUnderstandingProviders: [],
imageGenerationProviders: [],
videoGenerationProviders: [],
webFetchProviders: [],
webSearchProviders: [],
gatewayHandlers: {},