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

+118 -20
View File
@@ -3,8 +3,9 @@ import type { AddressInfo } from "node:net";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
import { getModel, streamSimple } from "../src/compat.ts";
import { getModel, normalizeContext, streamSimple } from "../src/compat.ts";
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
import { getSupportedThinkingLevels } from "../src/models.ts";
import type { Context, Model, Tool } from "../src/types.ts";
const originalFireworksApiKey = process.env.FIREWORKS_API_KEY;
@@ -18,8 +19,8 @@ afterEach(() => {
});
describe("Fireworks models", () => {
it("registers the default Kimi K2.6 model via Anthropic-compatible Messages API", () => {
const model = getModel("fireworks", "accounts/fireworks/models/kimi-k2p6");
it("registers non-GLM, non-Kimi-K3 models via Anthropic-compatible Messages API", () => {
const model = getModel("fireworks", "accounts/fireworks/models/deepseek-v4p1-flash");
expect(model).toBeDefined();
expect(model.api).toBe("anthropic-messages");
@@ -27,19 +28,11 @@ describe("Fireworks models", () => {
expect(model.baseUrl).toBe("https://api.fireworks.ai/inference");
expect(model.reasoning).toBe(true);
expect(model.input).toEqual(["text", "image"]);
expect(model.contextWindow).toBe(262000);
expect(model.maxTokens).toBe(262000);
expect(model.cost).toEqual({
input: 0.95,
output: 4,
cacheRead: 0.16,
cacheWrite: 0,
});
});
it("aligns GLM 5.2 Fast with GLM 5.2's OpenAI-compatible config", () => {
const base = getModel("fireworks", "accounts/fireworks/models/glm-5p2");
const fast = getModel("fireworks", "accounts/fireworks/routers/glm-5p2-fast");
it("aligns GLM 5.3 Fast with GLM 5.3's OpenAI-compatible config", () => {
const base = getModel("fireworks", "accounts/fireworks/models/glm-5p3");
const fast = getModel("fireworks", "accounts/fireworks/routers/glm-5p3-fast");
expect(fast.api).toBe(base.api);
expect(fast.baseUrl).toBe(base.baseUrl);
@@ -47,7 +40,7 @@ describe("Fireworks models", () => {
expect(fast.thinkingLevelMap).toEqual(base.thinkingLevelMap);
});
it.each(["accounts/fireworks/models/glm-5p2", "accounts/fireworks/routers/glm-5p2-fast"] as const)(
it.each(["accounts/fireworks/models/glm-5p3", "accounts/fireworks/routers/glm-5p3-fast"] as const)(
"omits unsupported long cache retention for %s",
async (modelId) => {
const model = getModel("fireworks", modelId);
@@ -78,9 +71,11 @@ describe("Fireworks models", () => {
const compat = {
supportsStore: false,
supportsDeveloperRole: false,
supportsStrictMode: true,
requiresReasoningContentOnAssistantMessages: true,
thinkingFormat: "openai",
deferredToolsMode: "kimi",
supportsMidConvoSystemMessages: true,
supportsMidConvoToolAdditions: true,
sendSessionAffinityHeaders: true,
supportsLongCacheRetention: false,
};
@@ -88,7 +83,7 @@ describe("Fireworks models", () => {
off: null,
minimal: null,
low: "low",
medium: "medium",
medium: null,
high: "high",
xhigh: null,
max: "max",
@@ -121,6 +116,69 @@ describe("Fireworks models", () => {
expect(payload?.reasoning_effort).toBe("max");
});
// Regression for #9323: native effort must reach Messages without budget-based fallback.
it.each([
["accounts/fireworks/models/deepseek-v4p1-flash", ["off", "low", "high", "max"]],
["accounts/fireworks/models/qwen3p8-max", ["off", "low", "medium", "xhigh"]],
["accounts/fireworks/models/qwen3p8-2p4t-a95b", ["off", "low", "medium", "xhigh"]],
] as const)("sends native Messages effort levels for %s", async (modelId, levels) => {
const model = getModel("fireworks", modelId);
expect(model.api).toBe("anthropic-messages");
expect(model.compat?.forceAdaptiveThinking).toBe(true);
expect(getSupportedThinkingLevels(model)).toEqual(levels);
for (const level of levels) {
let payload: Record<string, unknown> | undefined;
await streamSimple(
model,
{ messages: [{ role: "user", content: "test", timestamp: 0 }] },
{
apiKey: "test-fireworks-key",
reasoning: level === "off" ? undefined : level,
onPayload: (value) => {
payload = value as Record<string, unknown>;
throw new Error("payload captured");
},
},
).result();
expect(payload).toBeDefined();
expect(payload?.thinking).toEqual(
level === "off" ? { type: "disabled" } : { type: "adaptive", display: "summarized" },
);
expect(payload?.output_config).toEqual(level === "off" ? undefined : { effort: level });
}
});
// Regression for #9323: accepted aliases are not distinct native effort levels.
it.each([
["accounts/fireworks/models/glm-5p3", ["low", "high", "max"]],
["accounts/fireworks/routers/glm-5p3-fast", ["low", "high", "max"]],
["accounts/fireworks/models/kimi-k3", ["low", "high", "max"]],
["accounts/fireworks/routers/kimi-k3-fast", ["low", "high", "max"]],
] as const)("exposes distinct native effort levels for %s", (modelId, levels) => {
expect(getSupportedThinkingLevels(getModel("fireworks", modelId))).toEqual(levels);
});
it("keeps toggle-only Messages models without a verified fallback on budget-based thinking", async () => {
const model = getModel("fireworks", "accounts/fireworks/models/nemotron-3-ultra-nvfp4");
expect(model.compat?.forceAdaptiveThinking).toBeUndefined();
let payload: Record<string, unknown> | undefined;
await streamSimple(
model,
{ messages: [{ role: "user", content: "test", timestamp: 0 }] },
{
apiKey: "test-fireworks-key",
reasoning: "high",
onPayload: (value) => {
payload = value as Record<string, unknown>;
throw new Error("payload captured");
},
},
).result();
expect(payload?.thinking).toEqual({ type: "enabled", budget_tokens: 16384, display: "summarized" });
expect(payload?.output_config).toBeUndefined();
});
it("resolves FIREWORKS_API_KEY from the environment", () => {
process.env.FIREWORKS_API_KEY = "test-fireworks-key";
@@ -129,13 +187,14 @@ describe("Fireworks models", () => {
});
it("sets Fireworks-specific compat for session affinity and unsupported tool fields", () => {
const model = getModel("fireworks", "accounts/fireworks/models/kimi-k2p6");
const model = getModel("fireworks", "accounts/fireworks/models/nemotron-3-ultra-nvfp4");
expect(model.compat).toBeDefined();
expect(model.compat?.sendSessionAffinityHeaders).toBe(true);
expect(model.compat?.supportsEagerToolInputStreaming).toBe(false);
expect(model.compat?.supportsCacheControlOnTools).toBe(false);
expect(model.compat?.supportsLongCacheRetention).toBe(false);
expect(model.compat?.allowEmptySignature).toBe(true);
});
});
@@ -153,6 +212,7 @@ const tool: Tool = {
};
const FIREWORKS_ANTHROPIC_COMPAT = {
allowEmptySignature: true,
sendSessionAffinityHeaders: true,
supportsEagerToolInputStreaming: false,
supportsCacheControlOnTools: false,
@@ -192,6 +252,15 @@ function createAnthropicModel(): Model<"anthropic-messages"> {
};
}
function createOpenRouterModel(): Model<"anthropic-messages"> {
return {
...createAnthropicModel(),
id: "anthropic/claude-opus-4.8",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api",
};
}
function createContext(tools: Tool[] = [tool]): Context {
return {
messages: [{ role: "user", content: "Use the tool", timestamp: Date.now() }],
@@ -234,7 +303,7 @@ async function captureAnthropicRequest(
// Override the model's baseUrl to point to the local test server
const localModel = { ...model, baseUrl: `http://127.0.0.1:${address.port}` };
const stream = streamAnthropic(localModel, context, {
const stream = streamAnthropic(localModel, normalizeContext(context), {
apiKey: "test-key",
cacheRetention: (options?.cacheRetention as "none" | "short" | "long") ?? "short",
sessionId: options?.sessionId,
@@ -263,7 +332,7 @@ function getTools(body: Record<string, unknown>): Record<string, unknown>[] {
return tools as Record<string, unknown>[];
}
describe("Fireworks Anthropic session affinity and tool compat", () => {
describe("Anthropic-compatible session affinity and tool compat", () => {
it("sends x-session-affinity header for Fireworks models", async () => {
const model = createFireworksModel();
// Need a real port, capture will assign one
@@ -293,6 +362,35 @@ describe("Fireworks Anthropic session affinity and tool compat", () => {
expect(request.headers["x-session-affinity"]).toBeUndefined();
});
// Regression test for https://github.com/earendil-works/pi/issues/9102
it("sends only x-session-id for OpenRouter models", async () => {
const request = await captureAnthropicRequest(createOpenRouterModel(), createContext(), {
sessionId: "openrouter-session-1",
});
expect(request.headers["x-session-id"]).toBe("openrouter-session-1");
expect(request.headers["x-session-affinity"]).toBeUndefined();
});
it("omits OpenRouter session headers when cacheRetention is none", async () => {
const request = await captureAnthropicRequest(createOpenRouterModel(), createContext(), {
sessionId: "openrouter-session-2",
cacheRetention: "none",
});
expect(request.headers["x-session-id"]).toBeUndefined();
expect(request.headers["x-session-affinity"]).toBeUndefined();
});
it("allows OpenRouter session headers to be disabled", async () => {
const model = { ...createOpenRouterModel(), compat: { sendSessionAffinityHeaders: false } };
const request = await captureAnthropicRequest(model, createContext(), {
sessionId: "openrouter-session-3",
});
expect(request.headers["x-session-id"]).toBeUndefined();
});
it("omits cache_control on tools for Fireworks models", async () => {
const model = createFireworksModel();
const request = await captureAnthropicRequest(model, createContext());