chore(vendor): import pi 1.3.0

Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
alexion committed 2026-10-15 17:58:48 -04:00
1 parent 719370c8f7
commit 6081e3e325
1743 files changed
+205459 -131704

No files matched your search

+135 -20
View File
@@ -2,8 +2,9 @@ import type Anthropic from "@anthropic-ai/sdk";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
import { getModel } from "../src/compat.ts";
import type { Context, ToolCall } from "../src/types.ts";
import { transformMessages } from "../src/api/transform-messages.ts";
import { getModel, normalizeContext } from "../src/compat.ts";
import type { Api, Model, ToolCall } from "../src/types.ts";
function createSseResponse(events: Array<{ event: string; data: string }>): Response {
const body = events.map(({ event, data }) => `event: ${event}\ndata: ${data}\n`).join("\n");
@@ -80,7 +81,121 @@ function createFakeAnthropicClient(response: Response): Anthropic {
} as unknown as Anthropic;
}
type ResponseContentBlock = { type: "thinking"; thinking: string; signature: string } | { type: "text"; text: string };
function createResponseModelSseResponse(model: string, contentBlock: ResponseContentBlock): Response {
return createSseResponse([
{
event: "message_start",
data: JSON.stringify({
type: "message_start",
message: { id: "msg_response_model", model, usage: { input_tokens: 100, output_tokens: 0 } },
}),
},
{
event: "content_block_start",
data: JSON.stringify({ type: "content_block_start", index: 0, content_block: contentBlock }),
},
{ event: "content_block_stop", data: JSON.stringify({ type: "content_block_stop", index: 0 }) },
{
event: "message_delta",
data: JSON.stringify({
type: "message_delta",
delta: { stop_reason: "end_turn" },
usage: { input_tokens: 100, output_tokens: 20 },
}),
},
{ event: "message_stop", data: JSON.stringify({ type: "message_stop" }) },
]);
}
describe("Anthropic raw SSE parsing", () => {
it("forwards parsed provider stream events in order", async () => {
const model = getModel("anthropic", "claude-haiku-4-5");
const providerEvents: unknown[] = [];
const eventModels: Model<Api>[] = [];
const result = await streamAnthropic(
model,
normalizeContext({ messages: [{ role: "user", content: "Hello", timestamp: 1 }] }),
{
client: createFakeAnthropicClient(createSseResponse(minimalAnthropicEvents)),
onProviderStreamEvent: async (event, eventModel) => {
await Promise.resolve();
providerEvents.push(event);
eventModels.push(eventModel);
},
},
).result();
expect(result.stopReason).toBe("stop");
expect(providerEvents.map((event) => (event as { type: string }).type)).toEqual([
"message_start",
"content_block_start",
"content_block_delta",
"content_block_stop",
"message_delta",
"message_stop",
]);
expect(eventModels).toEqual([model, model, model, model, model, model]);
});
it("keeps signed thinking replayable when a proxy relabels the model", async () => {
// Regression test for earendil-works/pi#9188.
const model = getModel("anthropic", "claude-opus-5");
const responseModel = "kimi-for-coding";
const initialContext = normalizeContext({
messages: [{ role: "user", content: "Hello", timestamp: 1 }],
});
const first = await streamAnthropic(model, initialContext, {
client: createFakeAnthropicClient(
createResponseModelSseResponse(responseModel, {
type: "thinking",
thinking: "reasoning",
signature: "signature",
}),
),
}).result();
expect(first.model).toBe(model.id);
expect(first.responseModel).toBe(responseModel);
const transformed = transformMessages([...initialContext.messages, first], model);
const replayedAssistant = transformed.find((message) => message.role === "assistant");
expect(replayedAssistant?.content).toEqual([
{ type: "thinking", thinking: "reasoning", thinkingSignature: "signature" },
]);
});
it("uses a returned fallback model for cost attribution", async () => {
const fallbackModel = "fallback-model";
const model: Model<"anthropic-messages"> = {
...getModel("anthropic", "claude-opus-5"),
compat: {
allowedFallbackModels: [
{
provider: "anthropic",
model: fallbackModel,
cost: { input: 3, output: 5, cacheRead: 0, cacheWrite: 0 },
},
],
},
};
const result = await streamAnthropic(
model,
normalizeContext({ messages: [{ role: "user", content: "Hello", timestamp: 1 }] }),
{
client: createFakeAnthropicClient(
createResponseModelSseResponse(fallbackModel, { type: "text", text: "done" }),
),
},
).result();
expect(result.model).toBe(model.id);
expect(result.responseModel).toBe(fallbackModel);
expect(result.usage.cost.input).toBeCloseTo(0.0003, 10);
expect(result.usage.cost.output).toBeCloseTo(0.0001, 10);
});
it("fails safely when Anthropic falls back after output begins", async () => {
const model = getModel("anthropic", "claude-opus-5");
const response = createSseResponse([
@@ -120,7 +235,7 @@ describe("Anthropic raw SSE parsing", () => {
const result = await streamAnthropic(
model,
{ messages: [{ role: "user", content: "Hello", timestamp: 1 }] },
normalizeContext({ messages: [{ role: "user", content: "Hello", timestamp: 1 }] }),
{ client: createFakeAnthropicClient(response) },
).result();
@@ -143,7 +258,7 @@ describe("Anthropic raw SSE parsing", () => {
await streamAnthropic(
getModel("anthropic", "claude-fable-5-1"),
{ messages: [{ role: "user", content: "Hello", timestamp: 1 }] },
normalizeContext({ messages: [{ role: "user", content: "Hello", timestamp: 1 }] }),
{
client,
onPayload: (payload) => ({ ...(payload as Record<string, unknown>), stream: false }),
@@ -167,8 +282,8 @@ describe("Anthropic raw SSE parsing", () => {
} as unknown as Anthropic;
await streamAnthropic(
getModel("openrouter", "anthropic/claude-3-haiku"),
{ messages: [{ role: "user", content: "Hello", timestamp: 1 }] },
getModel("openrouter", "anthropic/claude-haiku-4.5"),
normalizeContext({ messages: [{ role: "user", content: "Hello", timestamp: 1 }] }),
{ client, thinkingEnabled: false },
).result();
@@ -190,7 +305,7 @@ describe("Anthropic raw SSE parsing", () => {
const result = await streamAnthropic(
getModel("anthropic", "claude-fable-5-1"),
{ messages: [{ role: "user", content: "Hello", timestamp: 1 }] },
normalizeContext({ messages: [{ role: "user", content: "Hello", timestamp: 1 }] }),
{ client },
).result();
@@ -220,7 +335,7 @@ describe("Anthropic raw SSE parsing", () => {
const result = await streamAnthropic(
getModel("anthropic", "claude-fable-5-1"),
{ messages: [{ role: "user", content: "Hello", timestamp: 1 }] },
normalizeContext({ messages: [{ role: "user", content: "Hello", timestamp: 1 }] }),
{ client: createFakeAnthropicClient(createSseResponse(events)) },
).result();
@@ -242,7 +357,7 @@ describe("Anthropic raw SSE parsing", () => {
});
it("repairs malformed SSE JSON and malformed streamed tool JSON", async () => {
const model = getModel("anthropic", "claude-haiku-4-5");
const context: Context = {
const context = normalizeContext({
messages: [{ role: "user", content: "Use the edit tool.", timestamp: Date.now() }],
tools: [
{
@@ -254,7 +369,7 @@ describe("Anthropic raw SSE parsing", () => {
}),
},
],
};
});
const malformedToolJsonDelta = String.raw`{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\"path\":\"A\H\",\"text\":\"col1 col2\"}"}}`;
@@ -329,9 +444,9 @@ describe("Anthropic raw SSE parsing", () => {
it("preserves content from content_block_start events", async () => {
const model = getModel("anthropic", "claude-haiku-4-5");
const context: Context = {
const context = normalizeContext({
messages: [{ role: "user", content: "Say hello.", timestamp: Date.now() }],
};
});
const response = createSseResponse([
{
event: "message_start",
@@ -427,9 +542,9 @@ describe("Anthropic raw SSE parsing", () => {
it("preserves refusal stop details from message_delta", async () => {
const model = getModel("anthropic", "claude-fable-5");
const context: Context = {
const context = normalizeContext({
messages: [{ role: "user", content: "blocked request", timestamp: Date.now() }],
};
});
const explanation =
"This request triggered restrictions on violative cyber content and was blocked under Anthropic's Usage Policy. To learn more, provide feedback, or request an exemption based on how you use Claude, visit our help center: https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude.";
const response = createSseResponse([
@@ -486,9 +601,9 @@ describe("Anthropic raw SSE parsing", () => {
it("preserves sensitive stop reasons with a descriptive error message", async () => {
const model = getModel("anthropic", "claude-haiku-4-5");
const context: Context = {
const context = normalizeContext({
messages: [{ role: "user", content: "blocked request", timestamp: Date.now() }],
};
});
const response = createSseResponse([
{
event: "message_start",
@@ -536,9 +651,9 @@ describe("Anthropic raw SSE parsing", () => {
it("treats message_delta without usage as a no-op for usage accumulation", async () => {
const model = getModel("anthropic", "claude-haiku-4-5");
const context: Context = {
const context = normalizeContext({
messages: [{ role: "user", content: "Say hello.", timestamp: Date.now() }],
};
});
const response = createSseResponse(
minimalAnthropicEvents.map((event) =>
event.event === "message_delta"
@@ -564,9 +679,9 @@ describe("Anthropic raw SSE parsing", () => {
it("ignores unknown SSE events after message_stop", async () => {
const model = getModel("anthropic", "claude-haiku-4-5");
const context: Context = {
const context = normalizeContext({
messages: [{ role: "user", content: "Say hello.", timestamp: Date.now() }],
};
});
const response = createSseResponse([
...minimalAnthropicEvents,
{ event: "done", data: "[DONE]" },