Files
pi_harness/packages/ai/test/google-shared-image-tool-result-routing.test.ts
T
Mario Zechner ba93da9a93 feat(ai): move API implementations to src/api with lazy wrappers (phase 2)
Stream implementations move from src/providers/ to src/api/, renamed by
API id (anthropic.ts -> anthropic-messages.ts, google.ts ->
google-generative-ai.ts, mistral.ts -> mistral-conversations.ts,
amazon-bedrock.ts -> bedrock-converse-stream.ts). Every module now
exports exactly stream/streamSimple; shared helpers move alongside.

New ProviderStreams dispatch contract in types.ts, lazyApi() wrapper in
api/lazy.ts, and one .lazy.ts wrapper per API. Bedrock's wrapper keeps
the node-only variable-specifier import and setBedrockProviderModule()
(now taking ProviderStreams).

providers/register-builtins.ts deleted; interim until the compat
entrypoint lands, builtin api-registry registration lives in stream.ts
and lazy wrappers are exported from the root barrel. Old per-API lazy
exports (streamAnthropic, ...) are gone; package.json subpaths retarget
to dist/api/.
2026-06-10 20:08:59 +02:00

103 lines
3.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { convertMessages } from "../src/api/google-shared.ts";
import type { Context, Model } from "../src/types.ts";
function makeModel<TApi extends "google-generative-ai">(
api: TApi,
provider: Model<TApi>["provider"],
id: string,
): Model<TApi> {
return {
id,
name: id,
api,
provider,
baseUrl: "https://example.com",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192,
};
}
function makeContext(model: { api: string; provider: string; id: string }): Context {
const now = Date.now();
return {
messages: [
{ role: "user", content: "read the files", timestamp: now },
{
role: "assistant",
content: [
{ type: "toolCall", id: "call_a", name: "read", arguments: { path: "a.txt" } },
{ type: "toolCall", id: "call_img", name: "read", arguments: { path: "image.png" } },
{ type: "toolCall", id: "call_b", name: "read", arguments: { path: "b.txt" } },
],
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: now,
},
{
role: "toolResult",
toolCallId: "call_a",
toolName: "read",
content: [{ type: "text", text: "alpha text" }],
isError: false,
timestamp: now,
},
{
role: "toolResult",
toolCallId: "call_img",
toolName: "read",
content: [{ type: "image", data: "abc", mimeType: "image/png" }],
isError: false,
timestamp: now,
},
{
role: "toolResult",
toolCallId: "call_b",
toolName: "read",
content: [{ type: "text", text: "beta text" }],
isError: false,
timestamp: now,
},
],
};
}
describe("google-shared image tool result routing", () => {
it("keeps separate synthetic image turn for Gemini 2.x Google API models", () => {
const model = makeModel("google-generative-ai", "google", "gemini-2.5-flash");
const contents = convertMessages(model, makeContext(model));
expect(contents).toHaveLength(5);
expect(contents[2].parts?.every((part) => part.functionResponse)).toBe(true);
expect(contents[3].parts?.[0]?.text).toBe("Tool result image:");
expect(contents[3].parts?.[1]?.inlineData).toBeTruthy();
expect(contents[4].parts?.[0]?.functionResponse).toBeTruthy();
});
it("nests image tool results for Gemini 3 Google API models", () => {
const model = makeModel("google-generative-ai", "google", "gemini-3-pro-preview");
const contents = convertMessages(model, makeContext(model));
expect(contents).toHaveLength(3);
const toolResultTurn = contents[2];
expect(toolResultTurn.parts).toHaveLength(3);
const imageResponse = toolResultTurn.parts?.[1]?.functionResponse;
expect(imageResponse).toBeTruthy();
expect(imageResponse?.parts).toHaveLength(1);
expect(imageResponse?.parts?.[0]?.inlineData).toBeTruthy();
});
});