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

@@ -57,6 +57,7 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => {
import { stream as streamBedrock } from "../src/api/bedrock-converse-stream.ts";
import type { Context, Message, Model } from "../src/types.ts";
import { normalizeContext } from "../src/utils/transcript.ts";
const baseModel: Model<"bedrock-converse-stream"> = {
id: "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
@@ -82,7 +83,7 @@ const novaModel: Model<"bedrock-converse-stream"> = {
async function capturePayload(context: Context, model = baseModel): Promise<unknown> {
let capturedPayload: unknown;
const s = streamBedrock(model, context, {
const s = streamBedrock(model, normalizeContext(context), {
cacheRetention: "none",
signal: AbortSignal.abort(),
onPayload: (payload) => {
@@ -151,7 +152,7 @@ describe("Bedrock tool arguments", () => {
try {
const message = await streamBedrock(
baseModel,
{ messages: [{ role: "user", content: "Use the tool", timestamp: Date.now() }] },
normalizeContext({ messages: [{ role: "user", content: "Use the tool", timestamp: Date.now() }] }),
{ cacheRetention: "none" },
).result();
@@ -324,6 +325,98 @@ describe("bedrock convertMessages skips unknown content types", () => {
expect(p.messages[0].content[0].toolResult.content).toEqual([{ text: "<empty>" }]);
});
// Regression for #10794: Claude rejects error tool results that contain images.
it("moves images of error tool results after the tool results", async () => {
const messages: Message[] = [
{
role: "assistant",
content: [
{ type: "toolCall", id: "call-1", name: "codemode", arguments: {} },
{ type: "toolCall", id: "call-2", name: "read", arguments: {} },
],
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
model: baseModel.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: Date.now(),
},
{
role: "toolResult",
toolCallId: "call-1",
toolName: "codemode",
content: [
{ type: "text", text: "Script failed" },
{ type: "image", data: "AAAA", mimeType: "image/png" },
{ type: "text", text: "Script error" },
],
isError: true,
timestamp: Date.now(),
},
{
role: "toolResult",
toolCallId: "call-2",
toolName: "read",
content: [{ type: "image", data: "BBBB", mimeType: "image/png" }],
isError: false,
timestamp: Date.now(),
},
];
const payload = (await capturePayload({ messages })) as {
messages: Array<{ role: string; content: Array<Record<string, any>> }>;
};
const content = payload.messages[1].content;
expect(content).toHaveLength(4);
expect(content[0].toolResult).toEqual({
toolUseId: "call-1",
content: [
{ text: "Script failed" },
{ text: "(image attached after the tool results)" },
{ text: "Script error" },
],
status: "error",
});
expect(content[1].toolResult.status).toBe("success");
expect(content[1].toolResult.content).toEqual([{ image: expect.objectContaining({ format: "png" }) }]);
expect(content[2]).toEqual({ text: "Images from the error result of tool call call-1 (codemode):" });
expect(content[3]).toEqual({ image: expect.objectContaining({ format: "png" }) });
});
it("keeps images in error tool results for non-Claude models", async () => {
const messages: Message[] = [
{
role: "toolResult",
toolCallId: "call-1",
toolName: "codemode",
content: [
{ type: "text", text: "Script failed" },
{ type: "image", data: "AAAA", mimeType: "image/png" },
],
isError: true,
timestamp: Date.now(),
},
];
const payload = (await capturePayload({ messages }, novaModel)) as {
messages: Array<{ role: string; content: Array<Record<string, any>> }>;
};
expect(payload.messages[0].content).toEqual([
{
toolResult: {
toolUseId: "call-1",
content: [{ text: "Script failed" }, { image: expect.objectContaining({ format: "png" }) }],
status: "error",
},
},
]);
});
it("skips assistant messages with only unknown content blocks", async () => {
const messages: Message[] = [
{
@@ -351,7 +444,7 @@ describe("bedrock convertMessages skips unknown content types", () => {
});
it("removes empty property names only from replayed Bedrock input", async () => {
const toolArguments = {
const toolArguments: { path: string; edits: Array<Record<string, string>> } = {
path: "/workspace/foobar/file.js",
edits: [
{ oldText: "first", newText: "updated first" },