fix(plugin-sdk): share canonical replay hook families

This commit is contained in:
Vincent Koc
2026-04-14 16:33:55 +01:00
parent 20bfa3cce3
commit 4c15f1310b
12 changed files with 53 additions and 44 deletions

View File

@@ -4,7 +4,7 @@ import {
readConfiguredProviderCatalogEntries,
type ProviderCatalogContext,
} from "openclaw/plugin-sdk/provider-catalog-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { OPENAI_COMPATIBLE_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import type { OpenClawConfig } from "openclaw/plugin-sdk/provider-onboard";
import {
applyArceeConfig,
@@ -20,9 +20,6 @@ import {
} from "./provider-catalog.js";
const PROVIDER_ID = "arcee";
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "openai-compatible",
});
const ARCEE_WIZARD_GROUP = {
groupId: "arcee",
groupLabel: "Arcee AI",

View File

@@ -1,10 +1,10 @@
import type { ProviderResolveDynamicModelContext } from "openclaw/plugin-sdk/plugin-entry";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import {
buildProviderReplayFamilyHooks,
cloneFirstTemplateModel,
DEFAULT_CONTEXT_TOKENS,
normalizeModelCompat,
OPENAI_COMPATIBLE_REPLAY_HOOKS,
} from "openclaw/plugin-sdk/provider-model-shared";
import { isFireworksKimiModelId } from "./model-id.js";
import { applyFireworksConfig, FIREWORKS_DEFAULT_MODEL_REF } from "./onboard.js";
@@ -18,10 +18,6 @@ import {
import { wrapFireworksProviderStream } from "./stream.js";
const PROVIDER_ID = "fireworks";
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "openai-compatible",
});
function resolveFireworksDynamicModel(ctx: ProviderResolveDynamicModelContext) {
const modelId = ctx.modelId.trim();
if (!modelId) {

View File

@@ -1,14 +1,11 @@
import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { PASSTHROUGH_GEMINI_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { applyKilocodeConfig, KILOCODE_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildKilocodeProviderWithDiscovery } from "./provider-catalog.js";
const PROVIDER_ID = "kilocode";
const PASSTHROUGH_GEMINI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "passthrough-gemini",
});
const KILOCODE_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("kilocode-thinking");
export default defineSingleProviderPluginEntry({

View File

@@ -1,5 +1,5 @@
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { OPENAI_COMPATIBLE_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { applyMoonshotNativeStreamingUsageCompat } from "./api.js";
import { moonshotMediaUnderstandingProvider } from "./media-understanding-provider.js";
@@ -12,9 +12,6 @@ import { buildMoonshotProvider } from "./provider-catalog.js";
import { createKimiWebSearchProvider } from "./src/kimi-web-search-provider.js";
const PROVIDER_ID = "moonshot";
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "openai-compatible",
});
const MOONSHOT_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("moonshot-thinking");
export default defineSingleProviderPluginEntry({

View File

@@ -7,7 +7,7 @@ import {
type ProviderDiscoveryContext,
} from "openclaw/plugin-sdk/plugin-entry";
import {
buildProviderReplayFamilyHooks,
OPENAI_COMPATIBLE_REPLAY_HOOKS,
type ModelProviderConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/text-runtime";
@@ -33,9 +33,6 @@ import { createOllamaWebSearchProvider } from "./src/web-search-provider.js";
const PROVIDER_ID = "ollama";
const DEFAULT_API_KEY = "ollama-local";
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "openai-compatible",
});
type OllamaPluginConfig = {
discovery?: {

View File

@@ -1,13 +1,9 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { PASSTHROUGH_GEMINI_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { applyOpencodeGoConfig, OPENCODE_GO_DEFAULT_MODEL_REF } from "./api.js";
const PROVIDER_ID = "opencode-go";
const PASSTHROUGH_GEMINI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "passthrough-gemini",
});
export default definePluginEntry({
id: PROVIDER_ID,
name: "OpenCode Go Provider",

View File

@@ -1,17 +1,14 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import {
buildProviderReplayFamilyHooks,
matchesExactOrPrefix,
PASSTHROUGH_GEMINI_REPLAY_HOOKS,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import { applyOpencodeZenConfig, OPENCODE_ZEN_DEFAULT_MODEL } from "./api.js";
const PROVIDER_ID = "opencode";
const MINIMAX_MODERN_MODEL_MATCHERS = ["minimax-m2.7"] as const;
const PASSTHROUGH_GEMINI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "passthrough-gemini",
});
function isModernOpencodeModel(modelId: string): boolean {
const lower = normalizeLowercaseStringOrEmpty(modelId);

View File

@@ -5,8 +5,8 @@ import {
} from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import {
buildProviderReplayFamilyHooks,
DEFAULT_CONTEXT_TOKENS,
PASSTHROUGH_GEMINI_REPLAY_HOOKS,
} from "openclaw/plugin-sdk/provider-model-shared";
import {
getOpenRouterModelCapabilities,
@@ -26,9 +26,6 @@ const OPENROUTER_CACHE_TTL_MODEL_PREFIXES = [
"moonshotai/",
"zai/",
] as const;
const PASSTHROUGH_GEMINI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "passthrough-gemini",
});
export default definePluginEntry({
id: "openrouter",

View File

@@ -1,6 +1,6 @@
import { Type } from "@sinclair/typebox";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { OPENAI_COMPATIBLE_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { defaultToolStreamExtraParams } from "openclaw/plugin-sdk/provider-stream-shared";
import { jsonResult, readProviderEnvValue } from "openclaw/plugin-sdk/provider-web-search";
import {
@@ -24,10 +24,6 @@ import {
} from "./x-search-tool-shared.js";
const PROVIDER_ID = "xai";
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "openai-compatible",
});
function hasResolvableXaiApiKey(config: unknown): boolean {
return Boolean(
resolveFallbackXaiAuth(config as never)?.apiKey || readProviderEnvValue(["XAI_API_KEY"]),

View File

@@ -17,8 +17,8 @@ import {
validateApiKeyInput,
} from "openclaw/plugin-sdk/provider-auth-api-key";
import {
buildProviderReplayFamilyHooks,
normalizeModelCompat,
OPENAI_COMPATIBLE_REPLAY_HOOKS,
} from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { defaultToolStreamExtraParams } from "openclaw/plugin-sdk/provider-stream-shared";
@@ -32,9 +32,6 @@ import { applyZaiConfig, applyZaiProviderConfig, ZAI_DEFAULT_MODEL_REF } from ".
const PROVIDER_ID = "zai";
const GLM5_TEMPLATE_MODEL_ID = "glm-4.7";
const PROFILE_ID = "zai:default";
const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "openai-compatible",
});
const ZAI_TOOL_STREAM_HOOKS = buildProviderStreamFamilyHooks("tool-stream-default-on");
function resolveGlm5ForwardCompatModel(

View File

@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { buildProviderReplayFamilyHooks } from "./provider-model-shared.js";
import {
buildProviderReplayFamilyHooks,
OPENAI_COMPATIBLE_REPLAY_HOOKS,
PASSTHROUGH_GEMINI_REPLAY_HOOKS,
} from "./provider-model-shared.js";
describe("buildProviderReplayFamilyHooks", () => {
it("covers the replay family matrix", async () => {
@@ -171,4 +175,34 @@ describe("buildProviderReplayFamilyHooks", () => {
} as never),
).not.toHaveProperty("dropThinkingBlocks");
});
it("exposes canonical replay hooks for reused provider families", () => {
expect(
OPENAI_COMPATIBLE_REPLAY_HOOKS.buildReplayPolicy?.({
provider: "xai",
modelApi: "openai-completions",
modelId: "grok-4",
} as never),
).toMatchObject({
sanitizeToolCallIds: true,
applyAssistantFirstOrderingFix: true,
validateGeminiTurns: true,
});
expect(
PASSTHROUGH_GEMINI_REPLAY_HOOKS.buildReplayPolicy?.({
provider: "openrouter",
modelApi: "openai-completions",
modelId: "gemini-2.5-pro",
} as never),
).toMatchObject({
applyAssistantFirstOrderingFix: false,
validateGeminiTurns: false,
validateAnthropicTurns: false,
sanitizeThoughtSignatures: {
allowBase64Only: true,
includeCamelCase: true,
},
});
});
});

View File

@@ -160,3 +160,11 @@ export function buildProviderReplayFamilyHooks(
}
throw new Error("Unsupported provider replay family");
}
export const OPENAI_COMPATIBLE_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "openai-compatible",
});
export const PASSTHROUGH_GEMINI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
family: "passthrough-gemini",
});