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:
1 parent
719370c8f7
commit
6081e3e325
1743 files changed
+205459
-131704
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { Type } from "typebox";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { convertMessages } from "../src/api/openai-completions.ts";
|
||||
import { getModel, stream, streamSimple } from "../src/compat.ts";
|
||||
import { getModel, normalizeContext, stream, streamSimple } from "../src/compat.ts";
|
||||
import type { AssistantMessage, Model, SimpleStreamOptions, Tool, ToolResultMessage } from "../src/types.ts";
|
||||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
@@ -219,6 +219,82 @@ describe("openai-completions tool_choice", () => {
|
||||
expect("strict" in (tool ?? {})).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults unknown OpenAI-compatible endpoints to non-strict tools", async () => {
|
||||
// Regression test for #9816.
|
||||
const model = {
|
||||
...localOpenAICompletionsModel,
|
||||
id: "local-model",
|
||||
name: "Local Model",
|
||||
} satisfies Model<"openai-completions">;
|
||||
const tool: Tool = {
|
||||
name: "ping",
|
||||
description: "Ping tool",
|
||||
parameters: Type.Object({
|
||||
required: Type.String(),
|
||||
optional: Type.Optional(Type.String()),
|
||||
}),
|
||||
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
||||
};
|
||||
let payload: unknown;
|
||||
|
||||
await streamSimple(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Call ping", timestamp: Date.now() }],
|
||||
tools: [tool],
|
||||
},
|
||||
{
|
||||
apiKey: "test",
|
||||
onPayload: (params: unknown) => {
|
||||
payload = params;
|
||||
},
|
||||
},
|
||||
).result();
|
||||
|
||||
const params = (payload ?? mockState.lastParams) as {
|
||||
tools?: Array<{ function?: { strict?: boolean; parameters?: { required?: string[] } } }>;
|
||||
};
|
||||
const functionTool = params.tools?.[0]?.function;
|
||||
expect(functionTool).not.toHaveProperty("strict");
|
||||
expect(functionTool?.parameters?.required).toEqual(["required"]);
|
||||
});
|
||||
|
||||
it("preserves strict tools for capable built-in Chat Completions models", async () => {
|
||||
const model = getModel("groq", "openai/gpt-oss-20b")!;
|
||||
expect(model.compat?.supportsStrictMode).toBe(true);
|
||||
const tool: Tool = {
|
||||
name: "ping",
|
||||
description: "Ping tool",
|
||||
parameters: Type.Object({
|
||||
required: Type.String(),
|
||||
optional: Type.Optional(Type.String()),
|
||||
}),
|
||||
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
||||
};
|
||||
let payload: unknown;
|
||||
|
||||
await streamSimple(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Call ping", timestamp: Date.now() }],
|
||||
tools: [tool],
|
||||
},
|
||||
{
|
||||
apiKey: "test",
|
||||
onPayload: (params: unknown) => {
|
||||
payload = params;
|
||||
},
|
||||
},
|
||||
).result();
|
||||
|
||||
const params = (payload ?? mockState.lastParams) as {
|
||||
tools?: Array<{ function?: { strict?: boolean; parameters?: { required?: string[] } } }>;
|
||||
};
|
||||
const functionTool = params.tools?.[0]?.function;
|
||||
expect(functionTool?.strict).toBe(true);
|
||||
expect(functionTool?.parameters?.required).toEqual(["required", "optional"]);
|
||||
});
|
||||
|
||||
it("maps Groq Qwen reasoning levels to default reasoning_effort", async () => {
|
||||
const model = getModel("groq", "qwen/qwen3.6-27b")!;
|
||||
let payload: unknown;
|
||||
@@ -320,21 +396,21 @@ describe("openai-completions tool_choice", () => {
|
||||
});
|
||||
|
||||
it("stores z.ai effort metadata", () => {
|
||||
for (const provider of ["zai", "zai-coding-cn"] as const) {
|
||||
for (const modelId of ["glm-5.2", "glm-5.2-highspeed"] as const) {
|
||||
const model = getModel(provider, modelId)!;
|
||||
expect(model.compat?.supportsReasoningEffort).toBe(true);
|
||||
expect(model.thinkingLevelMap).toEqual({
|
||||
off: "none",
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: null,
|
||||
high: "high",
|
||||
xhigh: null,
|
||||
max: "max",
|
||||
});
|
||||
}
|
||||
for (const modelId of ["glm-5.2", "glm-5.2-highspeed"] as const) {
|
||||
const model = getModel("zai", modelId)!;
|
||||
expect(model.compat?.supportsReasoningEffort).toBe(true);
|
||||
expect(model.thinkingLevelMap).toEqual({
|
||||
off: "none",
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: null,
|
||||
high: "high",
|
||||
xhigh: null,
|
||||
max: "max",
|
||||
});
|
||||
}
|
||||
|
||||
for (const provider of ["zai", "zai-coding-cn"] as const) {
|
||||
const glm53 = getModel(provider, "glm-5.3")!;
|
||||
expect(glm53.compat?.supportsReasoningEffort).toBe(true);
|
||||
expect(glm53.thinkingLevelMap).toEqual({
|
||||
@@ -1030,12 +1106,12 @@ describe("openai-completions tool_choice", () => {
|
||||
]);
|
||||
|
||||
expect(response.content).toHaveLength(6);
|
||||
expect(response.content[0]).toEqual({ type: "text", text: "answer 1 answer 2\n" });
|
||||
expect(response.content[1]).toEqual({
|
||||
expect(response.content[0]).toEqual({
|
||||
type: "thinking",
|
||||
thinking: "think 1 think 2",
|
||||
thinkingSignature: "reasoning_content",
|
||||
});
|
||||
expect(response.content[1]).toEqual({ type: "text", text: "answer 1 answer 2\n" });
|
||||
const readCall = response.content[2];
|
||||
const grepCall = response.content[3];
|
||||
const listCall = response.content[4];
|
||||
@@ -1233,7 +1309,7 @@ describe("openai-completions tool_choice", () => {
|
||||
const replayedAssistant = params.messages?.find((message) => message.role === "assistant");
|
||||
expect(replayedAssistant).toMatchObject({ role: "assistant", reasoning_content: "" });
|
||||
expect(params.thinking).toEqual({ type: "enabled" });
|
||||
expect(params.reasoning_effort).toBe("high");
|
||||
expect(params.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it("normalizes OpenCode Go reasoning deltas to reasoning_content for replay", async () => {
|
||||
@@ -1244,7 +1320,7 @@ describe("openai-completions tool_choice", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { compat: _compat, ...baseModel } = getModel("opencode-go", "kimi-k2.6")!;
|
||||
const { compat: _compat, ...baseModel } = getModel("opencode-go", "kimi-k3")!;
|
||||
const model = { ...baseModel, api: "openai-completions" } as const;
|
||||
const response = await streamSimple(
|
||||
model,
|
||||
@@ -1291,17 +1367,17 @@ describe("openai-completions tool_choice", () => {
|
||||
});
|
||||
|
||||
it("replays OpenCode Go reasoning thinking blocks as reasoning_content", () => {
|
||||
const { compat: _compat, ...baseModel } = getModel("opencode-go", "kimi-k2.6")!;
|
||||
const { compat: _compat, ...baseModel } = getModel("opencode-go", "kimi-k3")!;
|
||||
const model = { ...baseModel, api: "openai-completions" } as Model<"openai-completions">;
|
||||
const messages = convertMessages(
|
||||
model,
|
||||
{
|
||||
normalizeContext({
|
||||
messages: [
|
||||
{
|
||||
role: "assistant",
|
||||
api: "openai-completions",
|
||||
provider: "opencode-go",
|
||||
model: "kimi-k2.6",
|
||||
model: "kimi-k3",
|
||||
content: [
|
||||
{ type: "thinking", thinking: "think", thinkingSignature: "reasoning" },
|
||||
{ type: "toolCall", id: "call_1", name: "read", arguments: { path: "README.md" } },
|
||||
@@ -1318,7 +1394,7 @@ describe("openai-completions tool_choice", () => {
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
...model.compat,
|
||||
supportsStore: false,
|
||||
@@ -1349,8 +1425,8 @@ describe("openai-completions tool_choice", () => {
|
||||
expect(messages[0]).not.toHaveProperty("reasoning");
|
||||
});
|
||||
|
||||
it("sends thinking disabled for OpenCode Go Kimi K2.6 when thinking is off", async () => {
|
||||
const model = getModel("opencode-go", "kimi-k2.6")!;
|
||||
it("sends thinking disabled for OpenCode Kimi K2.6 when thinking is off", async () => {
|
||||
const model = getModel("opencode", "kimi-k2.6")!;
|
||||
let payload: unknown;
|
||||
|
||||
await streamSimple(
|
||||
@@ -1371,8 +1447,8 @@ describe("openai-completions tool_choice", () => {
|
||||
expect(params.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it("sends thinking enabled for OpenCode Go Kimi K2.6 when thinking is enabled", async () => {
|
||||
const model = getModel("opencode-go", "kimi-k2.6")!;
|
||||
it("sends thinking enabled for OpenCode Kimi K2.6 when thinking is enabled", async () => {
|
||||
const model = getModel("opencode", "kimi-k2.6")!;
|
||||
let payload: unknown;
|
||||
|
||||
await streamSimple(
|
||||
@@ -1443,7 +1519,7 @@ describe("openai-completions tool_choice", () => {
|
||||
});
|
||||
|
||||
it("sends max_tokens for OpenCode completions models", async () => {
|
||||
const cases = [getModel("opencode-go", "kimi-k2.6")!, getModel("opencode", "kimi-k2.6")!] as const;
|
||||
const cases = [getModel("opencode-go", "kimi-k3")!, getModel("opencode", "kimi-k2.6")!] as const;
|
||||
|
||||
for (const model of cases) {
|
||||
let payload: unknown;
|
||||
@@ -1483,10 +1559,7 @@ describe("openai-completions tool_choice", () => {
|
||||
name: "Custom Uppercase DeepSeek Model",
|
||||
baseUrl: "https://API.DeepSeek.COM",
|
||||
} satisfies Model<"openai-completions">;
|
||||
const nativeModels = [
|
||||
getModel("deepseek", "deepseek-v4-flash")!,
|
||||
getModel("deepseek", "deepseek-v4-pro")!,
|
||||
] as const;
|
||||
const nativeModels = [getModel("deepseek", "deepseek-flash")!, getModel("deepseek", "deepseek-v4-pro")!] as const;
|
||||
const cases = [...nativeModels, customModel, customUppercaseModel] as const;
|
||||
|
||||
for (const model of nativeModels) {
|
||||
@@ -1802,7 +1875,7 @@ describe("openai-completions tool_choice", () => {
|
||||
thinkingFormat: "ant-ling",
|
||||
supportsLongCacheRetention: false,
|
||||
});
|
||||
expect(model.compat?.supportsStrictMode).toBeUndefined();
|
||||
expect(model.compat?.supportsStrictMode).toBe(true);
|
||||
expect(model.compat?.requiresReasoningContentOnAssistantMessages).toBeUndefined();
|
||||
|
||||
await streamSimple(
|
||||
|
||||
Reference in new issue
Block a user