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

@@ -1,14 +1,7 @@
import { describe, expect, it } from "vitest";
import { convertMessages } from "../src/api/openai-completions.ts";
import { getModel } from "../src/compat.ts";
import type {
AssistantMessage,
Context,
Model,
OpenAICompletionsCompat,
ToolResultMessage,
Usage,
} from "../src/types.ts";
import { getModel, normalizeContext } from "../src/compat.ts";
import type { AssistantMessage, Model, OpenAICompletionsCompat, ToolResultMessage, Usage } from "../src/types.ts";
const emptyUsage: Usage = {
input: 0,
@@ -19,11 +12,7 @@ const emptyUsage: Usage = {
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
const compat: Omit<
Required<OpenAICompletionsCompat>,
"deferredToolsMode" | "thinkingTokenBudgetField" | "vllmPriority"
> & {
deferredToolsMode?: OpenAICompletionsCompat["deferredToolsMode"];
const compat: Omit<Required<OpenAICompletionsCompat>, "thinkingTokenBudgetField" | "vllmPriority"> & {
thinkingTokenBudgetField?: OpenAICompletionsCompat["thinkingTokenBudgetField"];
} = {
supportsStore: true,
@@ -46,6 +35,8 @@ const compat: Omit<
thinkingTokenBudgetField: undefined,
supportsStrictMode: true,
supportsOpenAIGrammarTools: false,
supportsMidConvoSystemMessages: false,
supportsMidConvoToolAdditions: false,
cacheControlFormat: "anthropic",
sendSessionAffinityHeaders: false,
sessionAffinityFormat: "openai",
@@ -78,6 +69,35 @@ function buildEmptyToolResult(toolCallId: string, timestamp: number): ToolResult
}
describe("openai-completions convertMessages", () => {
// Regression test for https://github.com/earendil-works/pi/issues/9797
it("omits empty text parts from user messages with images", () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini");
const model: Model<"openai-completions"> = {
...baseModel,
api: "openai-completions",
input: ["text", "image"],
};
const context = normalizeContext({
messages: [
{
role: "user",
content: [
{ type: "text", text: "" },
{ type: "image", data: "ZmFrZQ==", mimeType: "image/png" },
],
timestamp: Date.now(),
},
],
});
expect(convertMessages(model, context, compat)).toEqual([
{
role: "user",
content: [{ type: "image_url", image_url: { url: "data:image/png;base64,ZmFrZQ==" } }],
},
]);
});
it("batches tool-result images after consecutive tool results", () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini");
const model: Model<"openai-completions"> = {
@@ -101,14 +121,14 @@ describe("openai-completions convertMessages", () => {
timestamp: now,
};
const context: Context = {
const context = normalizeContext({
messages: [
{ role: "user", content: "Read the images", timestamp: now - 2 },
assistantMessage,
buildToolResult("tool-1", now + 1),
buildToolResult("tool-2", now + 2),
],
};
});
const messages = convertMessages(model, context, compat);
const roles = messages.map((message) => message.role);
@@ -144,13 +164,13 @@ describe("openai-completions convertMessages", () => {
timestamp: now,
};
const context: Context = {
const context = normalizeContext({
messages: [
{ role: "user", content: "Run the command", timestamp: now - 1 },
assistantMessage,
buildEmptyToolResult("tool-1", now + 1),
],
};
});
const messages = convertMessages(model, context, compat);
const toolMessage = messages.find((m) => m.role === "tool") as { role: "tool"; content: string } | undefined;