@fractaal/pi-ai 0.84.9 → 0.85.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,8 +15,10 @@ import { formatProviderError, normalizeProviderError } from "../utils/error-body
15
15
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
16
16
  import { headersToRecord } from "../utils/headers.js";
17
17
  import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
18
+ import { isRetryableAssistantError, retryAssistantCall } from "../utils/retry.js";
18
19
  import { uuidv7 } from "../utils/uuid.js";
19
20
  import { createGrammarToolInputProperties } from "./constrained-sampling.js";
21
+ import { retainCodexCompactionInput, trimCodexCompactionToolOutputs } from "./openai-codex-compaction-history.js";
20
22
  import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
21
23
  import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
22
24
  import { buildBaseOptions } from "./simple-options.js";
@@ -44,6 +46,7 @@ const CODEX_RESPONSE_STATUSES = new Set([
44
46
  "queued",
45
47
  "in_progress",
46
48
  ]);
49
+ export { estimateOpenAINativeCompactionTokens } from "./openai-codex-compaction-history.js";
47
50
  export function isOpenAICodexProvider(provider) {
48
51
  if (!provider || provider.id !== "openai-codex")
49
52
  return false;
@@ -432,12 +435,25 @@ function buildSimpleCodexOptions(model, context, options) {
432
435
  export const streamSimple = (model, context, options) => stream(model, context, buildSimpleCodexOptions(model, context, options));
433
436
  export async function compactOpenAICodexResponses(model, context, options) {
434
437
  const items = [];
435
- const result = await stream(model, context, {
436
- ...buildSimpleCodexOptions(model, context, options),
437
- transport: "sse",
438
- nativeCompaction: true,
439
- onNativeCompactionItem: (item) => items.push(item),
440
- }).result();
438
+ const base = buildSimpleCodexOptions(model, context, options);
439
+ let transport = base.transport;
440
+ const produce = () => {
441
+ items.length = 0;
442
+ return stream(model, context, {
443
+ ...base,
444
+ transport,
445
+ // Own one bounded stream retry budget; do not multiply HTTP retries.
446
+ maxRetries: 0,
447
+ nativeCompaction: true,
448
+ onNativeCompactionItem: (item) => items.push(item),
449
+ }).result();
450
+ };
451
+ const policy = { enabled: true, maxRetries: 2, baseDelayMs: BASE_DELAY_MS };
452
+ let result = await retryAssistantCall(produce, policy, options?.signal);
453
+ if (transport !== "sse" && base.authMode !== "transport" && isRetryableAssistantError(result)) {
454
+ transport = "sse";
455
+ result = await retryAssistantCall(produce, policy, options?.signal);
456
+ }
441
457
  if (result.stopReason !== "stop") {
442
458
  throw new OpenAICodexNativeCompactionError(result.errorMessage || `OpenAI native compaction did not complete (${result.stopReason})`, getOpenAICodexResponseFailure(result));
443
459
  }
@@ -448,13 +464,12 @@ export async function compactOpenAICodexResponses(model, context, options) {
448
464
  if (!isOpenAINativeCompactionItem(item)) {
449
465
  throw new Error("OpenAI native compaction returned a malformed checkpoint item");
450
466
  }
451
- closeOpenAICodexWebSocketSessions(options?.sessionId);
452
467
  return {
453
- item: {
454
- type: "compaction",
455
- encrypted_content: item.encrypted_content,
456
- id: typeof item.id === "string" ? item.id : undefined,
457
- },
468
+ item: { ...item },
469
+ retainedInput: retainCodexCompactionInput(buildRequestBody(model, {
470
+ ...context,
471
+ messages: (options?.nativeCompactionRetainedMessages ?? context.messages).filter((message) => message.role === "user"),
472
+ }, base, undefined).input ?? []),
458
473
  tokensBefore: result.usage.input + result.usage.cacheRead,
459
474
  usage: result.usage,
460
475
  };
@@ -466,7 +481,12 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
466
481
  const supportsStrictMode = model.compat?.supportsStrictMode ?? true;
467
482
  const supportsOpenAIGrammarTools = model.compat?.supportsOpenAIGrammarTools ?? false;
468
483
  const toolPlacement = splitDeferredTools(context, model.compat?.supportsToolSearch ?? false);
469
- const messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {
484
+ const checkpoint = options?.nativeCompactionCheckpoint;
485
+ const checkpointInput = checkpoint ? [...(checkpoint.retainedInput ?? []), checkpoint.item] : [];
486
+ const requestContext = options?.nativeCompaction
487
+ ? trimCodexCompactionToolOutputs(context, checkpointInput, model.contextWindow)
488
+ : context;
489
+ const messages = convertResponsesMessages(model, requestContext, CODEX_TOOL_CALL_PROVIDERS, {
470
490
  includeSystemPrompt: false,
471
491
  grammarToolInputProperties,
472
492
  deferredTools: toolPlacement.deferred,
@@ -476,15 +496,16 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
476
496
  supportsOpenAIGrammarTools,
477
497
  },
478
498
  });
479
- const checkpoint = options?.nativeCompactionCheckpoint;
480
499
  if (checkpoint) {
481
- if (checkpoint.provider !== model.provider || checkpoint.modelId !== model.id) {
482
- throw new Error(`OpenAI native compaction checkpoint requires ${checkpoint.provider}/${checkpoint.modelId}; current model is ${model.provider}/${model.id}`);
500
+ if (checkpoint.provider !== model.provider || model.api !== "openai-codex-responses") {
501
+ throw new Error(`OpenAI native compaction checkpoint requires the openai-codex Responses route; current model is ${model.provider}/${model.id}`);
483
502
  }
484
503
  if (!isOpenAINativeCompactionItem(checkpoint.item)) {
485
504
  throw new Error("Stored OpenAI native compaction checkpoint is malformed");
486
505
  }
487
- messages.unshift({ ...checkpoint.item });
506
+ messages.unshift(...(checkpoint.retainedInput ?? []), {
507
+ ...checkpoint.item,
508
+ });
488
509
  }
489
510
  if (options?.nativeCompaction) {
490
511
  messages.push({ type: "compaction_trigger" });
@@ -1271,7 +1292,7 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
1271
1292
  }
1272
1293
  try {
1273
1294
  socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
1274
- await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)), onStart), output, stream, model, {
1295
+ await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs), options?.onNativeCompactionItem), onStart), output, stream, model, {
1275
1296
  serviceTier: options?.serviceTier,
1276
1297
  grammarToolInputProperties,
1277
1298
  resolveServiceTier: resolveCodexServiceTier,
@@ -1280,6 +1301,10 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
1280
1301
  if (options?.signal?.aborted) {
1281
1302
  keepConnection = false;
1282
1303
  }
1304
+ else if (options?.nativeCompaction && entry) {
1305
+ // The rewritten history must be sent in full, but the connection stays reusable.
1306
+ entry.continuation = undefined;
1307
+ }
1283
1308
  else if (useCachedContext && entry && output.responseId) {
1284
1309
  const responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {
1285
1310
  includeSystemPrompt: false,