diff --git a/extensions/anthropic/api.ts b/extensions/anthropic/api.ts index f45884dc9d7..6fcd8f8e147 100644 --- a/extensions/anthropic/api.ts +++ b/extensions/anthropic/api.ts @@ -6,4 +6,5 @@ export { resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier, + wrapAnthropicProviderStream, } from "./stream-wrappers.js"; diff --git a/extensions/anthropic/contract-api.ts b/extensions/anthropic/contract-api.ts index 4878de5c363..34f998720bf 100644 --- a/extensions/anthropic/contract-api.ts +++ b/extensions/anthropic/contract-api.ts @@ -5,4 +5,5 @@ export { resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier, + wrapAnthropicProviderStream, } from "./stream-wrappers.js"; diff --git a/extensions/anthropic/register.runtime.ts b/extensions/anthropic/register.runtime.ts index 3f560bf9ff8..d0be287114b 100644 --- a/extensions/anthropic/register.runtime.ts +++ b/extensions/anthropic/register.runtime.ts @@ -18,7 +18,6 @@ import { } from "openclaw/plugin-sdk/provider-auth"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shared"; -import { composeProviderStreamWrappers } from "openclaw/plugin-sdk/provider-stream"; import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage"; import { buildAnthropicCliBackend } from "./cli-backend.js"; import { buildAnthropicCliMigrationResult, hasClaudeCliAuth } from "./cli-migration.js"; @@ -29,12 +28,7 @@ import { import { anthropicMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { buildAnthropicReplayPolicy } from "./replay-policy.js"; import { - createAnthropicBetaHeadersWrapper, - createAnthropicFastModeWrapper, - createAnthropicServiceTierWrapper, - resolveAnthropicBetas, - resolveAnthropicFastMode, - resolveAnthropicServiceTier, + wrapAnthropicProviderStream, } from "./stream-wrappers.js"; const PROVIDER_ID = "anthropic"; @@ -281,23 +275,7 @@ export async function registerAnthropicPlugin(api: OpenClawPluginApi): Promise buildAnthropicReplayPolicy(ctx), isModernModelRef: ({ modelId }) => matchesAnthropicModernModel(modelId), resolveReasoningOutputMode: () => "native", - wrapStreamFn: (ctx) => { - const anthropicBetas = resolveAnthropicBetas(ctx.extraParams, ctx.modelId); - const serviceTier = resolveAnthropicServiceTier(ctx.extraParams); - const fastMode = resolveAnthropicFastMode(ctx.extraParams); - return composeProviderStreamWrappers( - ctx.streamFn, - anthropicBetas?.length - ? (streamFn) => createAnthropicBetaHeadersWrapper(streamFn, anthropicBetas) - : undefined, - serviceTier - ? (streamFn) => createAnthropicServiceTierWrapper(streamFn, serviceTier) - : undefined, - fastMode !== undefined - ? (streamFn) => createAnthropicFastModeWrapper(streamFn, fastMode) - : undefined, - ); - }, + wrapStreamFn: (ctx) => wrapAnthropicProviderStream(ctx), resolveDefaultThinkingLevel: ({ modelId }) => matchesAnthropicModernModel(modelId) && (modelId.toLowerCase().startsWith(ANTHROPIC_OPUS_46_MODEL_ID) || diff --git a/extensions/anthropic/stream-wrappers.test.ts b/extensions/anthropic/stream-wrappers.test.ts index 4cde2e2bd23..3ea20c14ce6 100644 --- a/extensions/anthropic/stream-wrappers.test.ts +++ b/extensions/anthropic/stream-wrappers.test.ts @@ -1,6 +1,10 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { __testing, createAnthropicBetaHeadersWrapper } from "./stream-wrappers.js"; +import { + __testing, + createAnthropicBetaHeadersWrapper, + wrapAnthropicProviderStream, +} from "./stream-wrappers.js"; const CONTEXT_1M_BETA = "context-1m-2025-08-07"; const OAUTH_BETA = "oauth-2025-04-20"; @@ -41,4 +45,30 @@ describe("anthropic stream wrappers", () => { expect(headers?.["anthropic-beta"]).toContain(CONTEXT_1M_BETA); expect(warn).not.toHaveBeenCalled(); }); + + it("composes the anthropic provider stream chain from extra params", () => { + const captured: { headers?: Record; payload?: Record } = {}; + const base: StreamFn = (model, _context, options) => { + captured.headers = options?.headers; + const payload = {} as Record; + options?.onPayload?.(payload as never, model as never); + captured.payload = payload; + return {} as never; + }; + + const wrapped = wrapAnthropicProviderStream({ + streamFn: base, + modelId: "claude-sonnet-4-6", + extraParams: { context1m: true, serviceTier: "auto" }, + } as never); + + wrapped?.( + { provider: "anthropic", api: "anthropic-messages", id: "claude-sonnet-4-6" } as never, + {} as never, + { apiKey: "sk-ant-api-123" } as never, + ); + + expect(captured.headers?.["anthropic-beta"]).toContain(CONTEXT_1M_BETA); + expect(captured.payload).toMatchObject({ service_tier: "auto" }); + }); }); diff --git a/extensions/anthropic/stream-wrappers.ts b/extensions/anthropic/stream-wrappers.ts index 9e929986577..b70451b4e6d 100644 --- a/extensions/anthropic/stream-wrappers.ts +++ b/extensions/anthropic/stream-wrappers.ts @@ -1,7 +1,9 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import { streamSimple } from "@mariozechner/pi-ai"; +import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry"; import { applyAnthropicPayloadPolicyToParams, + composeProviderStreamWrappers, resolveAnthropicPayloadPolicy, streamWithPayloadPatch, } from "openclaw/plugin-sdk/provider-stream"; @@ -208,4 +210,22 @@ export function resolveAnthropicServiceTier( return normalized; } +export function wrapAnthropicProviderStream(ctx: ProviderWrapStreamFnContext): StreamFn | undefined { + const anthropicBetas = resolveAnthropicBetas(ctx.extraParams, ctx.modelId); + const serviceTier = resolveAnthropicServiceTier(ctx.extraParams); + const fastMode = resolveAnthropicFastMode(ctx.extraParams); + return composeProviderStreamWrappers( + ctx.streamFn, + anthropicBetas?.length + ? (streamFn) => createAnthropicBetaHeadersWrapper(streamFn, anthropicBetas) + : undefined, + serviceTier + ? (streamFn) => createAnthropicServiceTierWrapper(streamFn, serviceTier) + : undefined, + fastMode !== undefined + ? (streamFn) => createAnthropicFastModeWrapper(streamFn, fastMode) + : undefined, + ); +} + export const __testing = { log };