mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-21 05:32:53 +00:00
fix: apply Mistral compat across proxy transports
This commit is contained in:
@@ -29,6 +29,7 @@ let applyProviderNativeStreamingUsageCompatWithPlugin: typeof import("./provider
|
||||
let formatProviderAuthProfileApiKeyWithPlugin: typeof import("./provider-runtime.js").formatProviderAuthProfileApiKeyWithPlugin;
|
||||
let normalizeProviderConfigWithPlugin: typeof import("./provider-runtime.js").normalizeProviderConfigWithPlugin;
|
||||
let normalizeProviderModelIdWithPlugin: typeof import("./provider-runtime.js").normalizeProviderModelIdWithPlugin;
|
||||
let applyProviderResolvedModelCompatWithPlugins: typeof import("./provider-runtime.js").applyProviderResolvedModelCompatWithPlugins;
|
||||
let normalizeProviderTransportWithPlugin: typeof import("./provider-runtime.js").normalizeProviderTransportWithPlugin;
|
||||
let prepareProviderExtraParams: typeof import("./provider-runtime.js").prepareProviderExtraParams;
|
||||
let resolveProviderConfigApiKeyWithPlugin: typeof import("./provider-runtime.js").resolveProviderConfigApiKeyWithPlugin;
|
||||
@@ -211,6 +212,7 @@ describe("provider-runtime", () => {
|
||||
buildProviderMissingAuthMessageWithPlugin,
|
||||
buildProviderUnknownModelHintWithPlugin,
|
||||
applyProviderNativeStreamingUsageCompatWithPlugin,
|
||||
applyProviderResolvedModelCompatWithPlugins,
|
||||
formatProviderAuthProfileApiKeyWithPlugin,
|
||||
normalizeProviderConfigWithPlugin,
|
||||
normalizeProviderModelIdWithPlugin,
|
||||
@@ -727,6 +729,13 @@ describe("provider-runtime", () => {
|
||||
api: "openai-codex-responses",
|
||||
});
|
||||
|
||||
expect(
|
||||
applyProviderResolvedModelCompatWithPlugins({
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
context: createDemoResolvedModelContext({}),
|
||||
}),
|
||||
).toBeUndefined();
|
||||
|
||||
expect(
|
||||
formatProviderAuthProfileApiKeyWithPlugin({
|
||||
provider: DEMO_PROVIDER_ID,
|
||||
@@ -854,6 +863,53 @@ describe("provider-runtime", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("merges compat contributions from owner and foreign provider plugins", () => {
|
||||
resolveOwningPluginIdsForProviderMock.mockReturnValue(["openrouter"]);
|
||||
resolvePluginProvidersMock.mockImplementation((params) => {
|
||||
const onlyPluginIds = params.onlyPluginIds ?? [];
|
||||
const plugins: ProviderPlugin[] = [
|
||||
{
|
||||
id: "openrouter",
|
||||
label: "OpenRouter",
|
||||
auth: [],
|
||||
contributeResolvedModelCompat: () => ({ supportsStrictMode: true }),
|
||||
},
|
||||
{
|
||||
id: "mistral",
|
||||
label: "Mistral",
|
||||
auth: [],
|
||||
contributeResolvedModelCompat: ({ modelId }) =>
|
||||
modelId.startsWith("mistralai/") ? { supportsStore: false } : undefined,
|
||||
},
|
||||
];
|
||||
return onlyPluginIds.length > 0
|
||||
? plugins.filter((plugin) => onlyPluginIds.includes(plugin.id))
|
||||
: plugins;
|
||||
});
|
||||
|
||||
expect(
|
||||
applyProviderResolvedModelCompatWithPlugins({
|
||||
provider: "openrouter",
|
||||
context: createDemoResolvedModelContext({
|
||||
provider: "openrouter",
|
||||
modelId: "mistralai/mistral-small-3.2-24b-instruct",
|
||||
model: {
|
||||
...MODEL,
|
||||
provider: "openrouter",
|
||||
id: "mistralai/mistral-small-3.2-24b-instruct",
|
||||
compat: { supportsDeveloperRole: false },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
).toMatchObject({
|
||||
compat: {
|
||||
supportsDeveloperRole: false,
|
||||
supportsStrictMode: true,
|
||||
supportsStore: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves bundled catalog hooks through provider plugins", async () => {
|
||||
resolveCatalogHookProviderPluginIdsMock.mockReturnValue(["openai"]);
|
||||
resolvePluginProvidersMock.mockImplementation((params?: { onlyPluginIds?: string[] }) => {
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
ProviderFetchUsageSnapshotContext,
|
||||
ProviderNormalizeConfigContext,
|
||||
ProviderNormalizeModelIdContext,
|
||||
ProviderNormalizeResolvedModelContext,
|
||||
ProviderNormalizeTransportContext,
|
||||
ProviderModernModelPolicyContext,
|
||||
ProviderPrepareExtraParamsContext,
|
||||
@@ -224,6 +225,79 @@ export function normalizeProviderResolvedModelWithPlugin(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function resolveProviderCompatHookPlugins(params: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): ProviderPlugin[] {
|
||||
const candidates = resolveProviderPluginsForHooks(params);
|
||||
const owner = resolveProviderRuntimePlugin(params);
|
||||
if (!owner) {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
const ordered = [owner, ...candidates];
|
||||
const seen = new Set<string>();
|
||||
return ordered.filter((candidate) => {
|
||||
const key = `${candidate.pluginId ?? ""}:${candidate.id}`;
|
||||
if (seen.has(key)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function applyCompatPatchToModel(
|
||||
model: ProviderRuntimeModel,
|
||||
patch: Record<string, unknown>,
|
||||
): ProviderRuntimeModel {
|
||||
const compat =
|
||||
model.compat && typeof model.compat === "object"
|
||||
? (model.compat as Record<string, unknown>)
|
||||
: undefined;
|
||||
if (Object.entries(patch).every(([key, value]) => compat?.[key] === value)) {
|
||||
return model;
|
||||
}
|
||||
return {
|
||||
...model,
|
||||
compat: {
|
||||
...compat,
|
||||
...patch,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyProviderResolvedModelCompatWithPlugins(params: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
context: ProviderNormalizeResolvedModelContext;
|
||||
}): ProviderRuntimeModel | undefined {
|
||||
let nextModel = params.context.model;
|
||||
let changed = false;
|
||||
|
||||
for (const plugin of resolveProviderCompatHookPlugins(params)) {
|
||||
const patch = plugin.contributeResolvedModelCompat?.({
|
||||
...params.context,
|
||||
model: nextModel,
|
||||
});
|
||||
if (!patch || typeof patch !== "object") {
|
||||
continue;
|
||||
}
|
||||
const patchedModel = applyCompatPatchToModel(nextModel, patch as Record<string, unknown>);
|
||||
if (patchedModel === nextModel) {
|
||||
continue;
|
||||
}
|
||||
nextModel = patchedModel;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed ? nextModel : undefined;
|
||||
}
|
||||
|
||||
function resolveProviderHookPlugin(params: {
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
ModelProviderAuthMode,
|
||||
ModelProviderConfig,
|
||||
} from "../config/types.js";
|
||||
import type { ModelCompatConfig } from "../config/types.models.js";
|
||||
import type { OperatorScope } from "../gateway/method-scopes.js";
|
||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import type { InternalHookHandler } from "../hooks/internal-hooks.js";
|
||||
@@ -885,6 +886,17 @@ export type ProviderPlugin = {
|
||||
normalizeResolvedModel?: (
|
||||
ctx: ProviderNormalizeResolvedModelContext,
|
||||
) => ProviderRuntimeModel | null | undefined;
|
||||
/**
|
||||
* Provider-owned compat contribution for resolved models outside direct
|
||||
* provider ownership.
|
||||
*
|
||||
* Use this when a plugin can recognize its vendor's models behind another
|
||||
* OpenAI-compatible transport (for example OpenRouter or a custom base URL)
|
||||
* and needs to contribute compat flags without taking over the provider.
|
||||
*/
|
||||
contributeResolvedModelCompat?: (
|
||||
ctx: ProviderNormalizeResolvedModelContext,
|
||||
) => Partial<ModelCompatConfig> | null | undefined;
|
||||
/**
|
||||
* Provider-owned model-id normalization.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user