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:
2026-10-15 17:58:48 -04:00
parent 719370c8f7
commit 6081e3e325
1743 changed files with 205460 additions and 131705 deletions

View File

@@ -11,17 +11,16 @@ import { registerSessionResourceCleanup } from "../session-resources.ts";
import type {
Api,
AssistantMessage,
Context,
Model,
ProviderEnv,
ProviderHeaders,
SimpleStreamOptions,
StreamFunction,
StreamOptions,
TranscriptContext,
Usage,
} from "../types.ts";
import { combineAbortSignals } from "../utils/abort-signals.ts";
import { splitDeferredTools } from "../utils/deferred-tools.ts";
import {
appendAssistantMessageDiagnostic,
createAssistantMessageDiagnostic,
@@ -32,6 +31,14 @@ import { AssistantMessageEventStream } from "../utils/event-stream.ts";
import { headersToRecord } from "../utils/headers.ts";
import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.ts";
import { getPiUserAgent } from "../utils/pi-user-agent.ts";
import { getSystemMessageText } from "../utils/text.ts";
import {
getDeclaredTools,
getInitialSystemMessage,
normalizeContext,
resolveTranscript,
resolveTranscriptTools,
} from "../utils/transcript.ts";
import { uuidv7 } from "../utils/uuid.ts";
import { createGrammarToolInputProperties } from "./constrained-sampling.ts";
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts";
@@ -208,7 +215,7 @@ function loadNodeZlib(): typeof NodeZlib | null {
// Returns the zstd-compressed body bytes, or null when compression is
// unavailable (browser/Vite builds). Callers fall back to sending the
// uncompressed JSON when this returns null.
function compressRequestBodyZstd(bodyJson: string): Uint8Array | null {
function compressRequestBodyZstd(bodyJson: string): Uint8Array<ArrayBuffer> | null {
const zlib = loadNodeZlib();
if (!zlib || typeof zlib.zstdCompressSync !== "function") {
return null;
@@ -217,7 +224,8 @@ function compressRequestBodyZstd(bodyJson: string): Uint8Array | null {
const compressed = zlib.zstdCompressSync(bodyJson, {
params: { [zlib.constants.ZSTD_c_compressionLevel]: REQUEST_COMPRESSION_ZSTD_LEVEL },
});
return new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength);
// A copy owns a plain ArrayBuffer, which fetch accepts as a body under every lib.
return new Uint8Array(compressed);
} catch {
return null;
}
@@ -229,10 +237,11 @@ function compressRequestBodyZstd(bodyJson: string): Uint8Array | null {
export const stream: StreamFunction<"openai-codex-responses", OpenAICodexResponsesOptions> = (
model: Model<"openai-codex-responses">,
context: Context,
context: TranscriptContext,
options?: OpenAICodexResponsesOptions,
): AssistantMessageEventStream => {
const stream = new AssistantMessageEventStream();
const normalizedContext = resolveTranscript(context, model.compat?.supportsMidConvoSystemMessages);
(async () => {
const output: AssistantMessage = {
@@ -261,12 +270,12 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
const accountId = extractAccountId(apiKey);
const grammarToolInputProperties = createGrammarToolInputProperties(
context.tools,
getDeclaredTools(normalizedContext.messages),
model.compat?.supportsOpenAIGrammarTools ?? false,
);
const cacheSessionId = options?.cacheRetention === "none" ? undefined : options?.sessionId;
const codexSessionId = clampOpenAIPromptCacheKey(cacheSessionId);
let body = buildRequestBody(model, context, options, codexSessionId, grammarToolInputProperties);
let body = buildRequestBody(model, normalizedContext, options, codexSessionId, grammarToolInputProperties);
const nextBody = await options?.onPayload?.(body, model);
if (nextBody !== undefined) {
body = nextBody as RequestBody;
@@ -349,7 +358,7 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
output,
createAssistantMessageDiagnostic("provider_transport_failure", error, {
configuredTransport: transport,
fallbackTransport: websocketStarted ? undefined : "sse",
...(websocketStarted ? {} : { fallbackTransport: "sse" }),
eventsEmitted: websocketStarted,
phase: websocketStarted ? "after_message_stream_start" : "before_message_stream_start",
requestBytes: new TextEncoder().encode(bodyJson).byteLength,
@@ -372,7 +381,7 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
if (compressedBody) {
sseHeaders.set("content-encoding", "zstd");
}
const sseBody: Uint8Array | string = compressedBody ?? bodyJson;
const sseBody: Uint8Array<ArrayBuffer> | string = compressedBody ?? bodyJson;
// Fetch with retry logic for rate limits and transient errors
let response: Response | undefined;
@@ -491,7 +500,7 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
export const streamSimple: StreamFunction<"openai-codex-responses", SimpleStreamOptions> = (
model: Model<"openai-codex-responses">,
context: Context,
context: TranscriptContext,
options?: SimpleStreamOptions,
): AssistantMessageEventStream => {
const apiKey = options?.apiKey;
@@ -518,39 +527,35 @@ export const streamSimple: StreamFunction<"openai-codex-responses", SimpleStream
function buildRequestBody(
model: Model<"openai-codex-responses">,
context: Context,
context: TranscriptContext,
options: OpenAICodexResponsesOptions | undefined,
cacheSessionId: string | undefined,
grammarToolInputProperties: ReadonlyMap<string, string> = createGrammarToolInputProperties(
context.tools,
getDeclaredTools(context.messages),
model.compat?.supportsOpenAIGrammarTools ?? false,
),
): RequestBody {
const supportsStrictMode = model.compat?.supportsStrictMode ?? true;
const supportsOpenAIGrammarTools = model.compat?.supportsOpenAIGrammarTools ?? false;
const deferredToolsMode = model.compat?.supportsAdditionalTools
? "additional-tools"
: model.compat?.supportsToolSearch
? "tool-search"
: undefined;
const toolPlacement = splitDeferredTools(context, deferredToolsMode !== undefined);
const supportsAdditionalTools = model.compat?.supportsAdditionalTools ?? false;
const supportsToolSearch = model.compat?.supportsToolSearch ?? false;
const transcriptTools = resolveTranscriptTools(context.messages, supportsAdditionalTools || supportsToolSearch);
const messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {
includeSystemPrompt: false,
grammarToolInputProperties,
deferredTools: toolPlacement.deferred,
deferredToolsMode,
toolOptions: {
strict: null,
supportsStrictMode,
supportsOpenAIGrammarTools,
},
supportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,
supportsAdditionalTools,
supportsToolSearch,
toolOptions: { strict: null, supportsStrictMode, supportsOpenAIGrammarTools },
});
const initialSystemMessage = getInitialSystemMessage(context.messages);
const instructions = initialSystemMessage ? getSystemMessageText(initialSystemMessage) : "";
const body: RequestBody = {
model: model.id,
store: false,
stream: true,
instructions: context.systemPrompt || "You are a helpful assistant.",
instructions: instructions || "You are a helpful assistant.",
input: messages,
text: { verbosity: options?.textVerbosity || "low" },
include: ["reasoning.encrypted_content"],
@@ -567,8 +572,8 @@ function buildRequestBody(
body.service_tier = options.serviceTier;
}
if (toolPlacement.immediate.length > 0) {
body.tools = convertResponsesTools(toolPlacement.immediate, {
if (transcriptTools.requestTools.length > 0) {
body.tools = convertResponsesTools(transcriptTools.requestTools, {
strict: null,
supportsStrictMode,
supportsOpenAIGrammarTools,
@@ -578,7 +583,9 @@ function buildRequestBody(
if (options?.reasoningEffort !== undefined) {
const effort =
options.reasoningEffort === "none"
? (model.thinkingLevelMap?.off ?? "none")
? model.thinkingLevelMap?.off === undefined
? "none"
: model.thinkingLevelMap.off
: (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort);
if (effort !== null) {
body.reasoning = {
@@ -586,6 +593,8 @@ function buildRequestBody(
summary: options.reasoningSummary ?? "auto",
};
}
} else if (model.reasoning && model.thinkingLevelMap?.off !== null) {
body.reasoning = { effort: model.thinkingLevelMap?.off ?? "none" };
}
return body;
@@ -657,12 +666,18 @@ async function processStream(
grammarToolInputProperties: ReadonlyMap<string, string>,
options?: OpenAICodexResponsesOptions,
): Promise<void> {
await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal), output), output, stream, model, {
serviceTier: options?.serviceTier,
grammarToolInputProperties,
resolveServiceTier: resolveCodexServiceTier,
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
});
await processResponsesStream(
mapCodexEvents(parseSSE(response, options?.signal), output, model, options?.onProviderStreamEvent),
output,
stream,
model,
{
serviceTier: options?.serviceTier,
grammarToolInputProperties,
resolveServiceTier: resolveCodexServiceTier,
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
},
);
}
class CodexApiError extends Error {
@@ -689,8 +704,20 @@ class CodexProtocolError extends Error {
}
}
class ProviderStreamEventCallbackError extends Error {
constructor(cause: unknown) {
super(formatThrownValue(cause));
this.name = "ProviderStreamEventCallbackError";
this.cause = cause;
}
}
function isCodexNonTransportError(error: unknown): boolean {
return error instanceof CodexApiError || error instanceof CodexProtocolError;
return (
error instanceof CodexApiError ||
error instanceof CodexProtocolError ||
error instanceof ProviderStreamEventCallbackError
);
}
function isWebSocketConnectionLimitReachedError(error: unknown): boolean {
@@ -717,8 +744,16 @@ function extractCodexEventError(event: Record<string, unknown>): { code?: string
async function* mapCodexEvents(
events: AsyncIterable<Record<string, unknown>>,
output: AssistantMessage,
model: Model<"openai-codex-responses">,
onProviderStreamEvent: StreamOptions["onProviderStreamEvent"] | undefined,
): AsyncGenerator<ResponseStreamEvent> {
for await (const event of events) {
try {
await onProviderStreamEvent?.(event, model);
} catch (error) {
// Keep callback failures out of Codex's WebSocket retry and SSE fallback path.
throw new ProviderStreamEventCallbackError(error);
}
const type = typeof event.type === "string" ? event.type : undefined;
if (!type) continue;
@@ -1508,7 +1543,12 @@ async function processWebSocketStream(
socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
await processResponsesStream(
startWebSocketOutputOnFirstEvent(
mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs), output),
mapCodexEvents(
parseWebSocket(socket, options?.signal, idleTimeoutMs),
output,
model,
options?.onProviderStreamEvent,
),
onStart,
),
output,
@@ -1524,10 +1564,15 @@ async function processWebSocketStream(
if (options?.signal?.aborted) {
keepConnection = false;
} else if (useCachedContext && entry && output.responseId) {
const responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {
includeSystemPrompt: false,
grammarToolInputProperties,
}).filter((item) => item.type !== "function_call_output" && item.type !== "custom_tool_call_output");
const responseItems = convertResponsesMessages(
model,
normalizeContext({ messages: [output] }),
CODEX_TOOL_CALL_PROVIDERS,
{
includeSystemPrompt: false,
grammarToolInputProperties,
},
).filter((item) => item.type !== "function_call_output" && item.type !== "custom_tool_call_output");
entry.continuation = {
lastRequestBody: fullBody,
lastResponseId: output.responseId,
@@ -1599,7 +1644,11 @@ function buildBaseCodexHeaders(
accountId: string,
token: string,
): Headers {
const headers = new Headers(initHeaders);
// Defaults first so model and caller headers can override them, matching the other providers.
const headers = new Headers({ originator: "pi", "User-Agent": getPiUserAgent() });
for (const [key, value] of Object.entries(initHeaders || {})) {
headers.set(key, value);
}
for (const [key, value] of Object.entries(additionalHeaders || {})) {
if (value === null) {
headers.delete(key);
@@ -1609,8 +1658,6 @@ function buildBaseCodexHeaders(
}
headers.set("Authorization", `Bearer ${token}`);
headers.set("chatgpt-account-id", accountId);
headers.set("originator", "pi");
headers.set("User-Agent", getPiUserAgent());
return headers;
}