refactor(providers): share anthropic stream helper

This commit is contained in:
Vincent Koc 2026-04-04 20:35:14 +09:00
parent 3f457cabf7
commit 83c10350c6
5 changed files with 55 additions and 25 deletions

View File

@ -6,4 +6,5 @@ export {
resolveAnthropicBetas,
resolveAnthropicFastMode,
resolveAnthropicServiceTier,
wrapAnthropicProviderStream,
} from "./stream-wrappers.js";

View File

@ -5,4 +5,5 @@ export {
resolveAnthropicBetas,
resolveAnthropicFastMode,
resolveAnthropicServiceTier,
wrapAnthropicProviderStream,
} from "./stream-wrappers.js";

View File

@ -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<v
buildReplayPolicy: (ctx) => 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) ||

View File

@ -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<string, string>; payload?: Record<string, unknown> } = {};
const base: StreamFn = (model, _context, options) => {
captured.headers = options?.headers;
const payload = {} as Record<string, unknown>;
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" });
});
});

View File

@ -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 };