refactor(providers): flatten shared stream hooks

This commit is contained in:
Vincent Koc 2026-04-04 21:51:48 +09:00
parent 8f473023e4
commit a6707c2e1f
4 changed files with 58 additions and 16 deletions

View File

@ -317,7 +317,7 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin {
transport: "auto",
};
},
wrapStreamFn: (ctx) => OPENAI_RESPONSES_STREAM_HOOKS.wrapStreamFn?.(ctx),
...OPENAI_RESPONSES_STREAM_HOOKS,
resolveTransportTurnState: (ctx) => resolveOpenAITransportTurnState(ctx),
resolveWebSocketSessionPolicy: (ctx) => resolveOpenAIWebSocketSessionPolicy(ctx),
resolveReasoningOutputMode: () => "native",

View File

@ -256,7 +256,7 @@ export function buildOpenAIProvider(): ProviderPlugin {
...(hasExplicitWarmup ? {} : { openaiWsWarmup: true }),
};
},
wrapStreamFn: (ctx) => OPENAI_RESPONSES_STREAM_HOOKS.wrapStreamFn?.(ctx),
...OPENAI_RESPONSES_STREAM_HOOKS,
matchesContextOverflowError: ({ errorMessage }) =>
/content_filter.*(?:prompt|input).*(?:too long|exceed)/i.test(errorMessage),
resolveTransportTurnState: (ctx) => resolveOpenAITransportTurnState(ctx),

View File

@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
import openrouterPlugin from "./index.js";
@ -14,4 +14,41 @@ describe("openrouter provider hooks", () => {
} as never),
).toBe("native");
});
it("injects provider routing into compat before applying stream wrappers", async () => {
const provider = await registerSingleProviderPlugin(openrouterPlugin);
const baseStreamFn = vi.fn(() => ({ async *[Symbol.asyncIterator]() {} }) as never);
const wrapped = provider.wrapStreamFn?.({
provider: "openrouter",
modelId: "openai/gpt-5.4",
extraParams: {
provider: {
order: ["moonshot"],
},
},
streamFn: baseStreamFn,
thinkingLevel: "high",
} as never);
wrapped?.(
{
provider: "openrouter",
api: "openai-completions",
id: "openai/gpt-5.4",
compat: {},
} as never,
{ messages: [] } as never,
{},
);
expect(baseStreamFn).toHaveBeenCalledOnce();
expect(baseStreamFn.mock.calls[0]?.[0]).toMatchObject({
compat: {
openRouterRouting: {
order: ["moonshot"],
},
},
});
});
});

View File

@ -3,6 +3,7 @@ import {
definePluginEntry,
type ProviderResolveDynamicModelContext,
type ProviderRuntimeModel,
type ProviderWrapStreamFnContext,
} from "openclaw/plugin-sdk/plugin-entry";
const PROVIDER_ID = "openrouter";
@ -80,6 +81,22 @@ export default definePluginEntry({
);
}
function wrapOpenRouterProviderStream(
ctx: ProviderWrapStreamFnContext,
): StreamFn | undefined {
const providerRouting =
ctx.extraParams?.provider != null && typeof ctx.extraParams.provider === "object"
? (ctx.extraParams.provider as Record<string, unknown>)
: undefined;
const routedStreamFn = providerRouting
? injectOpenRouterRouting(ctx.streamFn, providerRouting)
: ctx.streamFn;
return OPENROUTER_THINKING_STREAM_HOOKS.wrapStreamFn?.({
...ctx,
streamFn: routedStreamFn,
});
}
function isOpenRouterCacheTtlModel(modelId: string): boolean {
return OPENROUTER_CACHE_TTL_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
}
@ -133,19 +150,7 @@ export default definePluginEntry({
...PASSTHROUGH_GEMINI_REPLAY_HOOKS,
resolveReasoningOutputMode: () => "native",
isModernModelRef: () => true,
wrapStreamFn: (ctx) => {
const providerRouting =
ctx.extraParams?.provider != null && typeof ctx.extraParams.provider === "object"
? (ctx.extraParams.provider as Record<string, unknown>)
: undefined;
const routedStreamFn = providerRouting
? injectOpenRouterRouting(ctx.streamFn, providerRouting)
: ctx.streamFn;
return OPENROUTER_THINKING_STREAM_HOOKS.wrapStreamFn?.({
...ctx,
streamFn: routedStreamFn,
});
},
wrapStreamFn: wrapOpenRouterProviderStream,
isCacheTtlEligible: (ctx) => isOpenRouterCacheTtlModel(ctx.modelId),
});
api.registerMediaUnderstandingProvider(openrouterMediaUnderstandingProvider);