Files
openclaw/extensions/anthropic-vertex/api.test.ts
Ian Alloway dbcf7abda1 fix(anthropic-vertex): keep ADC fetch provider-local (#108350)
* fix(anthropic-vertex): shim native fetch so Google auth avoids gaxios's node-fetch import

@anthropic-ai/vertex-sdk's bundled gaxios only uses native fetch when a
global `window.fetch` exists; otherwise it dynamically imports
`node-fetch`, which can fail to resolve depending on how the plugin's
dependencies are installed. That failure surfaces deep inside gaxios's
token-exchange path as "Cannot convert undefined or null to object",
breaking every Vertex auth request (#107341). The same gaxios root
cause hit a different provider in #41380, where a global window.fetch
shim was the confirmed workaround.

Fixes #107341

* fix(anthropic-vertex): compare window directly against undefined

oxlint's unicorn/no-typeof-undefined flagged the typeof check; a
direct comparison is safe here since target.window is a property
access, not a possibly-undeclared identifier.

* fix(anthropic-vertex): keep ADC fetch provider-local

Co-authored-by: Ian Alloway <ian@allowayllc.com>

* test(anthropic-vertex): inject auth in API fixtures

Co-authored-by: Ian Alloway <ian@allowayllc.com>

* refactor(anthropic-vertex): clarify auth transport contract

Co-authored-by: Ian Alloway <ian@allowayllc.com>

* fix(anthropic-vertex): preserve ADC proxy routing

* chore: leave changelog to release workflow

---------

Co-authored-by: Ian Alloway <ian@allowayllc.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 03:47:40 -07:00

82 lines
2.9 KiB
TypeScript

// Anthropic Vertex tests cover api plugin behavior.
import { createAssistantMessageEventStream, type Model } from "openclaw/plugin-sdk/llm";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { AnthropicVertexStreamDeps } from "./stream-runtime.js";
function createStreamDeps(): {
deps: AnthropicVertexStreamDeps;
streamAnthropicMock: ReturnType<typeof vi.fn>;
anthropicVertexCtorMock: ReturnType<typeof vi.fn>;
} {
const streamAnthropicMock = vi.fn(
(..._args: Parameters<AnthropicVertexStreamDeps["streamAnthropic"]>) =>
createAssistantMessageEventStream(),
);
const anthropicVertexCtorMock = vi.fn();
const MockAnthropicVertex = function MockAnthropicVertex(options: unknown) {
anthropicVertexCtorMock(options);
} as unknown as AnthropicVertexStreamDeps["AnthropicVertex"];
const MockGoogleAuth =
function MockGoogleAuth() {} as unknown as AnthropicVertexStreamDeps["GoogleAuth"];
return {
deps: {
AnthropicVertex: MockAnthropicVertex,
GoogleAuth: MockGoogleAuth,
streamAnthropic: streamAnthropicMock,
},
streamAnthropicMock,
anthropicVertexCtorMock,
};
}
let createAnthropicVertexStreamFn: typeof import("./api.js").createAnthropicVertexStreamFn;
let createAnthropicVertexStreamFnForModel: typeof import("./api.js").createAnthropicVertexStreamFnForModel;
function makeModel(): Model<"anthropic-messages"> {
return {
id: "claude-sonnet-4-6",
api: "anthropic-messages",
provider: "anthropic-vertex",
maxTokens: 128000,
} as Model<"anthropic-messages">;
}
describe("Anthropic Vertex API stream factories", () => {
beforeAll(async () => {
({ createAnthropicVertexStreamFn, createAnthropicVertexStreamFnForModel } =
await import("./api.js"));
});
it("reuses the runtime stream factory across direct stream calls", async () => {
const { deps, streamAnthropicMock, anthropicVertexCtorMock } = createStreamDeps();
const streamFn = createAnthropicVertexStreamFn("vertex-project", "us-east5", undefined, deps);
const model = makeModel();
await streamFn(model, { messages: [] }, {});
await streamFn(model, { messages: [] }, {});
expect(anthropicVertexCtorMock).toHaveBeenCalledTimes(1);
expect(streamAnthropicMock).toHaveBeenCalledTimes(2);
});
it("reuses the runtime stream factory across model-derived stream calls", async () => {
const { deps, streamAnthropicMock, anthropicVertexCtorMock } = createStreamDeps();
const streamFn = createAnthropicVertexStreamFnForModel(
makeModel(),
{
ANTHROPIC_VERTEX_PROJECT_ID: "vertex-project",
GOOGLE_CLOUD_LOCATION: "us-east5",
} as NodeJS.ProcessEnv,
deps,
);
const model = makeModel();
await streamFn(model, { messages: [] }, {});
await streamFn(model, { messages: [] }, {});
expect(anthropicVertexCtorMock).toHaveBeenCalledTimes(1);
expect(streamAnthropicMock).toHaveBeenCalledTimes(2);
});
});