mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-21 05:32:53 +00:00
refactor: move bundled replay policy ownership into plugins (#60452)
* refactor: move bundled replay policy ownership into plugins * test: preserve replay fallback until providers adopt hooks * test: cover response replay branches for ollama and zai --------- Co-authored-by: Shakker <shakkerdroid@gmail.com>
This commit is contained in:
@@ -36,6 +36,43 @@ describe("minimax provider hooks", () => {
|
||||
).toBe("native");
|
||||
});
|
||||
|
||||
it("owns replay policy for Anthropic and OpenAI-compatible MiniMax transports", () => {
|
||||
const { providers } = registerProviderPlugin({
|
||||
plugin: minimaxPlugin,
|
||||
id: "minimax",
|
||||
name: "MiniMax Provider",
|
||||
});
|
||||
const apiProvider = requireRegisteredProvider(providers, "minimax");
|
||||
const portalProvider = requireRegisteredProvider(providers, "minimax-portal");
|
||||
|
||||
expect(
|
||||
apiProvider.buildReplayPolicy?.({
|
||||
provider: "minimax",
|
||||
modelApi: "anthropic-messages",
|
||||
modelId: "MiniMax-M2.7",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeMode: "full",
|
||||
sanitizeToolCallIds: true,
|
||||
preserveSignatures: true,
|
||||
validateAnthropicTurns: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
portalProvider.buildReplayPolicy?.({
|
||||
provider: "minimax-portal",
|
||||
modelApi: "openai-completions",
|
||||
modelId: "MiniMax-M2.7",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("owns fast-mode stream wrapping for MiniMax transports", () => {
|
||||
const { providers } = registerProviderPlugin({
|
||||
plugin: minimaxPlugin,
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthResult,
|
||||
type ProviderCatalogContext,
|
||||
type ProviderReplayPolicy,
|
||||
type ProviderReplayPolicyContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
MINIMAX_OAUTH_MARKER,
|
||||
@@ -39,6 +41,36 @@ function resolveMinimaxReasoningOutputMode(): "native" {
|
||||
return "native";
|
||||
}
|
||||
|
||||
function buildMinimaxReplayPolicy(
|
||||
ctx: ProviderReplayPolicyContext,
|
||||
): ProviderReplayPolicy | undefined {
|
||||
if (ctx.modelApi === "anthropic-messages" || ctx.modelApi === "bedrock-converse-stream") {
|
||||
const modelId = ctx.modelId?.toLowerCase() ?? "";
|
||||
return {
|
||||
sanitizeMode: "full",
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
preserveSignatures: true,
|
||||
repairToolUseResultPairing: true,
|
||||
validateAnthropicTurns: true,
|
||||
allowSyntheticToolResults: true,
|
||||
...(modelId.includes("claude") ? { dropThinkingBlocks: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (ctx.modelApi === "openai-completions") {
|
||||
return {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDefaultBaseUrl(region: MiniMaxRegion): string {
|
||||
return region === "cn" ? DEFAULT_BASE_URL_CN : DEFAULT_BASE_URL_GLOBAL;
|
||||
}
|
||||
@@ -235,6 +267,7 @@ export default definePluginEntry({
|
||||
});
|
||||
return apiKey ? { token: apiKey } : null;
|
||||
},
|
||||
buildReplayPolicy: (ctx) => buildMinimaxReplayPolicy(ctx),
|
||||
wrapStreamFn: (ctx) =>
|
||||
createMinimaxFastModeWrapper(ctx.streamFn, ctx.extraParams?.fastMode === true),
|
||||
resolveReasoningOutputMode: () => resolveMinimaxReasoningOutputMode(),
|
||||
@@ -287,6 +320,7 @@ export default definePluginEntry({
|
||||
run: createOAuthHandler("cn"),
|
||||
},
|
||||
],
|
||||
buildReplayPolicy: (ctx) => buildMinimaxReplayPolicy(ctx),
|
||||
wrapStreamFn: (ctx) =>
|
||||
createMinimaxFastModeWrapper(ctx.streamFn, ctx.extraParams?.fastMode === true),
|
||||
resolveReasoningOutputMode: () => resolveMinimaxReasoningOutputMode(),
|
||||
|
||||
23
extensions/moonshot/index.test.ts
Normal file
23
extensions/moonshot/index.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("moonshot provider plugin", () => {
|
||||
it("owns replay policy for OpenAI-compatible Moonshot transports", () => {
|
||||
const provider = registerSingleProviderPlugin(plugin);
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "moonshot",
|
||||
modelApi: "openai-completions",
|
||||
modelId: "kimi-k2.5",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,7 @@
|
||||
import type {
|
||||
ProviderReplayPolicy,
|
||||
ProviderReplayPolicyContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
|
||||
import {
|
||||
createMoonshotThinkingWrapper,
|
||||
@@ -15,6 +19,22 @@ import { createKimiWebSearchProvider } from "./src/kimi-web-search-provider.js";
|
||||
|
||||
const PROVIDER_ID = "moonshot";
|
||||
|
||||
function buildMoonshotReplayPolicy(
|
||||
ctx: ProviderReplayPolicyContext,
|
||||
): ProviderReplayPolicy | undefined {
|
||||
if (ctx.modelApi !== "openai-completions") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
};
|
||||
}
|
||||
|
||||
export default defineSingleProviderPluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Moonshot Provider",
|
||||
@@ -58,6 +78,7 @@ export default defineSingleProviderPluginEntry({
|
||||
},
|
||||
applyNativeStreamingUsageCompat: ({ providerConfig }) =>
|
||||
applyMoonshotNativeStreamingUsageCompat(providerConfig),
|
||||
buildReplayPolicy: (ctx) => buildMoonshotReplayPolicy(ctx),
|
||||
wrapStreamFn: (ctx) => {
|
||||
const thinkingType = resolveMoonshotThinkingType({
|
||||
configuredThinking: ctx.extraParams?.thinking,
|
||||
|
||||
@@ -142,6 +142,46 @@ describe("ollama plugin", () => {
|
||||
expect((payloadSeen?.options as Record<string, unknown> | undefined)?.num_ctx).toBe(202752);
|
||||
});
|
||||
|
||||
it("owns replay policy for OpenAI-compatible Ollama routes only", () => {
|
||||
const provider = registerProvider();
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "ollama",
|
||||
modelApi: "openai-completions",
|
||||
modelId: "qwen3:32b",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "ollama",
|
||||
modelApi: "openai-responses",
|
||||
modelId: "qwen3:32b",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "ollama",
|
||||
modelApi: "ollama",
|
||||
modelId: "qwen3.5:9b",
|
||||
} as never),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("wraps native Ollama payloads with top-level think=false when thinking is off", () => {
|
||||
const provider = registerProvider();
|
||||
let payloadSeen: Record<string, unknown> | undefined;
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
type ProviderAuthResult,
|
||||
type ProviderDiscoveryContext,
|
||||
type ProviderReplayPolicy,
|
||||
type ProviderReplayPolicyContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
buildOllamaProvider,
|
||||
@@ -26,6 +28,35 @@ import {
|
||||
const PROVIDER_ID = "ollama";
|
||||
const DEFAULT_API_KEY = "ollama-local";
|
||||
|
||||
function buildOllamaReplayPolicy(
|
||||
ctx: ProviderReplayPolicyContext,
|
||||
): ProviderReplayPolicy | undefined {
|
||||
if (
|
||||
ctx.modelApi !== "openai-completions" &&
|
||||
ctx.modelApi !== "openai-responses" &&
|
||||
ctx.modelApi !== "openai-codex-responses" &&
|
||||
ctx.modelApi !== "azure-openai-responses"
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
...(ctx.modelApi === "openai-completions"
|
||||
? {
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
}
|
||||
: {
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function shouldSkipAmbientOllamaDiscovery(env: NodeJS.ProcessEnv): boolean {
|
||||
return Boolean(env.VITEST) || env.NODE_ENV === "test";
|
||||
}
|
||||
@@ -149,6 +180,7 @@ export default definePluginEntry({
|
||||
providerBaseUrl: config?.models?.providers?.ollama?.baseUrl,
|
||||
});
|
||||
},
|
||||
buildReplayPolicy: (ctx) => buildOllamaReplayPolicy(ctx),
|
||||
wrapStreamFn: (ctx) => {
|
||||
return createConfiguredOllamaCompatStreamWrapper(ctx);
|
||||
},
|
||||
|
||||
37
extensions/xai/index.test.ts
Normal file
37
extensions/xai/index.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("xai provider plugin", () => {
|
||||
it("owns replay policy for xAI OpenAI-compatible transports", () => {
|
||||
const provider = registerSingleProviderPlugin(plugin);
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "xai",
|
||||
modelApi: "openai-completions",
|
||||
modelId: "grok-3",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "xai",
|
||||
modelApi: "openai-responses",
|
||||
modelId: "grok-4-fast",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type {
|
||||
ProviderReplayPolicy,
|
||||
ProviderReplayPolicyContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
coerceSecretRef,
|
||||
resolveNonEnvSecretRefApiKeyMarker,
|
||||
@@ -31,6 +35,33 @@ import { createXaiWebSearchProvider } from "./web-search.js";
|
||||
|
||||
const PROVIDER_ID = "xai";
|
||||
|
||||
function buildXaiReplayPolicy(ctx: ProviderReplayPolicyContext): ProviderReplayPolicy | undefined {
|
||||
if (
|
||||
ctx.modelApi !== "openai-completions" &&
|
||||
ctx.modelApi !== "openai-responses" &&
|
||||
ctx.modelApi !== "openai-codex-responses" &&
|
||||
ctx.modelApi !== "azure-openai-responses"
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
...(ctx.modelApi === "openai-completions"
|
||||
? {
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
}
|
||||
: {
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function readConfiguredOrManagedApiKey(value: unknown): string | undefined {
|
||||
const literal = normalizeSecretInputString(value);
|
||||
if (literal) {
|
||||
@@ -250,6 +281,7 @@ export default defineSingleProviderPluginEntry({
|
||||
catalog: {
|
||||
buildProvider: buildXaiProvider,
|
||||
},
|
||||
buildReplayPolicy: (ctx) => buildXaiReplayPolicy(ctx),
|
||||
prepareExtraParams: (ctx) => {
|
||||
if (ctx.extraParams?.tool_stream !== undefined) {
|
||||
return ctx.extraParams;
|
||||
|
||||
@@ -3,6 +3,38 @@ import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("zai provider plugin", () => {
|
||||
it("owns replay policy for OpenAI-compatible Z.ai transports", () => {
|
||||
const provider = registerSingleProviderPlugin(plugin);
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "zai",
|
||||
modelApi: "openai-completions",
|
||||
modelId: "glm-5.1",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "zai",
|
||||
modelApi: "openai-responses",
|
||||
modelId: "glm-5.1",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves persisted GLM-5 family models with provider-owned metadata", () => {
|
||||
const provider = registerSingleProviderPlugin(plugin);
|
||||
const template = {
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthMethod,
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
type ProviderReplayPolicy,
|
||||
type ProviderReplayPolicyContext,
|
||||
type ProviderResolveDynamicModelContext,
|
||||
type ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
@@ -28,6 +30,33 @@ const PROVIDER_ID = "zai";
|
||||
const GLM5_TEMPLATE_MODEL_ID = "glm-4.7";
|
||||
const PROFILE_ID = "zai:default";
|
||||
|
||||
function buildZaiReplayPolicy(ctx: ProviderReplayPolicyContext): ProviderReplayPolicy | undefined {
|
||||
if (
|
||||
ctx.modelApi !== "openai-completions" &&
|
||||
ctx.modelApi !== "openai-responses" &&
|
||||
ctx.modelApi !== "openai-codex-responses" &&
|
||||
ctx.modelApi !== "azure-openai-responses"
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
...(ctx.modelApi === "openai-completions"
|
||||
? {
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
}
|
||||
: {
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveGlm5ForwardCompatModel(
|
||||
ctx: ProviderResolveDynamicModelContext,
|
||||
): ProviderRuntimeModel | undefined {
|
||||
@@ -264,6 +293,7 @@ export default definePluginEntry({
|
||||
}),
|
||||
],
|
||||
resolveDynamicModel: (ctx) => resolveGlm5ForwardCompatModel(ctx),
|
||||
buildReplayPolicy: (ctx) => buildZaiReplayPolicy(ctx),
|
||||
prepareExtraParams: (ctx) => {
|
||||
if (ctx.extraParams?.tool_stream !== undefined) {
|
||||
return ctx.extraParams;
|
||||
|
||||
@@ -11,16 +11,27 @@ vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
"kilocode",
|
||||
"kimi",
|
||||
"kimi-code",
|
||||
"minimax",
|
||||
"minimax-portal",
|
||||
"mistral",
|
||||
"moonshot",
|
||||
"openai",
|
||||
"openai-codex",
|
||||
"opencode",
|
||||
"opencode-go",
|
||||
"ollama",
|
||||
"openrouter",
|
||||
"sglang",
|
||||
"vllm",
|
||||
"xai",
|
||||
"zai",
|
||||
].includes(provider)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
if (provider === "sglang" || provider === "vllm") {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
buildReplayPolicy: (context?: { modelId?: string; modelApi?: string }) => {
|
||||
const modelId = context?.modelId?.toLowerCase() ?? "";
|
||||
@@ -37,6 +48,38 @@ vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
allowSyntheticToolResults: true,
|
||||
...(modelId.includes("claude") ? { dropThinkingBlocks: true } : {}),
|
||||
};
|
||||
case "minimax":
|
||||
case "minimax-portal":
|
||||
return context?.modelApi === "openai-completions"
|
||||
? {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
}
|
||||
: {
|
||||
sanitizeMode: "full",
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
preserveSignatures: true,
|
||||
repairToolUseResultPairing: true,
|
||||
validateAnthropicTurns: true,
|
||||
allowSyntheticToolResults: true,
|
||||
...(modelId.includes("claude") ? { dropThinkingBlocks: true } : {}),
|
||||
};
|
||||
case "moonshot":
|
||||
case "ollama":
|
||||
case "zai":
|
||||
return context?.modelApi === "openai-completions"
|
||||
? {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
}
|
||||
: undefined;
|
||||
case "google":
|
||||
return {
|
||||
sanitizeMode: "full",
|
||||
@@ -88,6 +131,28 @@ vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
case "xai":
|
||||
if (
|
||||
context?.modelApi === "openai-completions" ||
|
||||
context?.modelApi === "openai-responses"
|
||||
) {
|
||||
return {
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
...(context.modelApi === "openai-completions"
|
||||
? {
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
}
|
||||
: {
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
case "kilocode":
|
||||
return modelId.includes("gemini")
|
||||
? {
|
||||
@@ -183,10 +248,10 @@ describe("resolveTranscriptPolicy", () => {
|
||||
expect(policy.validateAnthropicTurns).toBe(true);
|
||||
});
|
||||
|
||||
it("falls back to transport defaults when a plugin replay hook returns undefined", () => {
|
||||
it("falls back to unowned transport defaults when no owning plugin exists", () => {
|
||||
const policy = resolveTranscriptPolicy({
|
||||
provider: "kilocode",
|
||||
modelId: "kilocode-default",
|
||||
provider: "custom-openai-proxy",
|
||||
modelId: "demo-model",
|
||||
modelApi: "openai-completions",
|
||||
});
|
||||
|
||||
@@ -197,6 +262,49 @@ describe("resolveTranscriptPolicy", () => {
|
||||
expect(policy.validateAnthropicTurns).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves transport defaults when a runtime plugin has not adopted replay hooks", () => {
|
||||
const policy = resolveTranscriptPolicy({
|
||||
provider: "vllm",
|
||||
modelId: "demo-model",
|
||||
modelApi: "openai-completions",
|
||||
});
|
||||
|
||||
expect(policy.sanitizeToolCallIds).toBe(true);
|
||||
expect(policy.toolCallIdMode).toBe("strict");
|
||||
expect(policy.applyGoogleTurnOrdering).toBe(true);
|
||||
expect(policy.validateGeminiTurns).toBe(true);
|
||||
expect(policy.validateAnthropicTurns).toBe(true);
|
||||
});
|
||||
|
||||
it("uses provider-owned Anthropic replay policy for MiniMax transports", () => {
|
||||
const policy = resolveTranscriptPolicy({
|
||||
provider: "minimax",
|
||||
modelId: "MiniMax-M2.7",
|
||||
modelApi: "anthropic-messages",
|
||||
});
|
||||
|
||||
expect(policy.sanitizeMode).toBe("full");
|
||||
expect(policy.sanitizeToolCallIds).toBe(true);
|
||||
expect(policy.preserveSignatures).toBe(true);
|
||||
expect(policy.validateAnthropicTurns).toBe(true);
|
||||
});
|
||||
|
||||
it("uses provider-owned OpenAI-compatible replay policy for MiniMax portal completions", () => {
|
||||
const policy = resolveTranscriptPolicy({
|
||||
provider: "minimax-portal",
|
||||
modelId: "MiniMax-M2.7",
|
||||
modelApi: "openai-completions",
|
||||
});
|
||||
|
||||
expect(policy.sanitizeMode).toBe("images-only");
|
||||
expect(policy.sanitizeToolCallIds).toBe(true);
|
||||
expect(policy.toolCallIdMode).toBe("strict");
|
||||
expect(policy.preserveSignatures).toBe(false);
|
||||
expect(policy.applyGoogleTurnOrdering).toBe(true);
|
||||
expect(policy.validateGeminiTurns).toBe(true);
|
||||
expect(policy.validateAnthropicTurns).toBe(true);
|
||||
});
|
||||
|
||||
it("enables Anthropic-compatible policies for Bedrock provider", () => {
|
||||
const policy = resolveTranscriptPolicy({
|
||||
provider: "amazon-bedrock",
|
||||
|
||||
@@ -44,7 +44,14 @@ function isAnthropicApi(modelApi?: string | null): boolean {
|
||||
return modelApi === "anthropic-messages" || modelApi === "bedrock-converse-stream";
|
||||
}
|
||||
|
||||
function buildTransportReplayFallback(params: {
|
||||
/**
|
||||
* Provides a narrow replay-policy fallback for providers that do not have an
|
||||
* owning runtime plugin.
|
||||
*
|
||||
* This exists to preserve generic custom-provider behavior. Bundled providers
|
||||
* should express replay ownership through `buildReplayPolicy` instead.
|
||||
*/
|
||||
function buildUnownedProviderTransportReplayFallback(params: {
|
||||
modelApi?: string | null;
|
||||
modelId?: string | null;
|
||||
}): ProviderReplayPolicy | undefined {
|
||||
@@ -162,13 +169,16 @@ export function resolveTranscriptPolicy(params: {
|
||||
model: params.model,
|
||||
};
|
||||
|
||||
const pluginPolicy = runtimePlugin?.buildReplayPolicy?.(context);
|
||||
if (pluginPolicy != null) {
|
||||
return mergeTranscriptPolicy(pluginPolicy);
|
||||
// Once a provider adopts the replay-policy hook, replay policy should come
|
||||
// from the plugin, not from transport-family defaults in core.
|
||||
const buildReplayPolicy = runtimePlugin?.buildReplayPolicy;
|
||||
if (buildReplayPolicy) {
|
||||
const pluginPolicy = buildReplayPolicy(context);
|
||||
return mergeTranscriptPolicy(pluginPolicy ?? undefined);
|
||||
}
|
||||
|
||||
return mergeTranscriptPolicy(
|
||||
buildTransportReplayFallback({
|
||||
buildUnownedProviderTransportReplayFallback({
|
||||
modelApi: params.modelApi,
|
||||
modelId: params.modelId,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user