@oh-my-pi/pi-ai 17.4.0 → 17.4.2

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +23 -0
  3. package/THIRD-PARTY-NOTICES.txt +22909 -0
  4. package/dist/types/auth-retry.d.ts +8 -3
  5. package/dist/types/auth-storage.d.ts +9 -0
  6. package/dist/types/dialect/qwen-xml.d.ts +6 -0
  7. package/dist/types/error/auth-classify.d.ts +8 -8
  8. package/dist/types/error/oauth.d.ts +2 -1
  9. package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
  10. package/dist/types/providers/cursor-pi-args.d.ts +21 -0
  11. package/dist/types/providers/cursor.d.ts +23 -3
  12. package/dist/types/providers/google-types.d.ts +6 -0
  13. package/dist/types/providers/mock.d.ts +3 -1
  14. package/dist/types/providers/openai-shared.d.ts +6 -1
  15. package/dist/types/registry/oauth/google-antigravity.d.ts +2 -6
  16. package/dist/types/registry/oauth/index.d.ts +2 -3
  17. package/dist/types/registry/oauth/types.d.ts +2 -1
  18. package/dist/types/registry/oauth/xai-oauth.d.ts +2 -2
  19. package/dist/types/registry/registry.d.ts +1 -1
  20. package/dist/types/registry/types.d.ts +2 -1
  21. package/dist/types/registry/xai-oauth.d.ts +1 -1
  22. package/dist/types/types.d.ts +21 -0
  23. package/dist/types/utils/stream-markup-healing.d.ts +1 -1
  24. package/package.json +8 -6
  25. package/src/auth-retry.ts +41 -3
  26. package/src/auth-storage.ts +229 -75
  27. package/src/dialect/qwen-xml.ts +82 -0
  28. package/src/error/auth-classify.ts +10 -8
  29. package/src/error/oauth.ts +2 -1
  30. package/src/providers/amazon-bedrock.ts +12 -19
  31. package/src/providers/anthropic.ts +29 -35
  32. package/src/providers/cursor/exec-modern.ts +2 -0
  33. package/src/providers/cursor-pi-args.ts +38 -2
  34. package/src/providers/cursor.ts +357 -32
  35. package/src/providers/google-shared.ts +11 -12
  36. package/src/providers/google-types.ts +7 -0
  37. package/src/providers/mock.ts +4 -1
  38. package/src/providers/openai-codex-responses.ts +34 -8
  39. package/src/providers/openai-completions.ts +2 -2
  40. package/src/providers/openai-shared.ts +72 -14
  41. package/src/registry/oauth/google-antigravity.ts +247 -162
  42. package/src/registry/oauth/index.ts +3 -3
  43. package/src/registry/oauth/types.ts +2 -1
  44. package/src/registry/oauth/xai-oauth.ts +14 -7
  45. package/src/registry/types.ts +2 -1
  46. package/src/registry/xai-oauth.ts +2 -2
  47. package/src/stream.ts +8 -4
  48. package/src/types.ts +22 -0
  49. package/src/utils/schema/CONSTRAINTS.md +1 -0
  50. package/src/utils/schema/fields.ts +20 -4
  51. package/src/utils/stream-markup-healing.ts +9 -2
@@ -2,6 +2,7 @@ import { scheduler } from "node:timers/promises";
2
2
  import { type } from "@oh-my-pi/omptype";
3
3
  import { calculateCost } from "@oh-my-pi/pi-catalog/models";
4
4
  import {
5
+ applyCodexResidencyHeader,
5
6
  CODEX_BASE_URL,
6
7
  CODEX_CLIENT_VERSION,
7
8
  getCodexAccountId,
@@ -495,12 +496,15 @@ interface CodexCompatibilityIdentity {
495
496
  sessionId: string;
496
497
  threadId: string;
497
498
  windowId: string;
498
- turnMetadataJson?: string;
499
+ /** Header projection: identity fields only, never the Code Mode snapshot. */
500
+ turnMetadataHeaderJson?: string;
499
501
  }
500
502
 
501
503
  interface CodexRequestMetadata extends CodexCompatibilityIdentity {
502
504
  turnId: string;
505
+ /** Canonical body projection, including `tool_namespaces_info` when present. */
503
506
  turnMetadataJson: string;
507
+ turnMetadataHeaderJson: string;
504
508
  clientMetadata: Record<string, string>;
505
509
  }
506
510
 
@@ -517,10 +521,11 @@ const CODEX_RESERVED_METADATA_KEYS: Record<string, true> = {
517
521
  [OPENAI_HEADERS.SUBAGENT]: true,
518
522
  request_kind: true,
519
523
  compaction: true,
520
- // codex-rs reserves `code_mode_tool_names` for its Responses Lite Code Mode
521
- // mapping (#35271); OMP never emits it, but callers must not smuggle it in
522
- // as an extra either.
524
+ // codex-rs retired `code_mode_tool_names` in favor of `tool_namespaces_info`
525
+ // for its Responses Lite Code Mode mapping; both stay reserved so callers
526
+ // cannot smuggle them in as extras.
523
527
  code_mode_tool_names: true,
528
+ tool_namespaces_info: true,
524
529
  turn_started_at_unix_ms: true,
525
530
  forked_from_thread_id: true,
526
531
  parent_thread_id: true,
@@ -598,6 +603,7 @@ function createCodexRequestMetadata(
598
603
  clientMetadata?: Readonly<Record<string, string>>;
599
604
  parentTurnId?: string;
600
605
  compaction?: CodexCompactionRequestContext;
606
+ toolNamespacesInfo?: unknown;
601
607
  },
602
608
  ): CodexRequestMetadata {
603
609
  if (options.startNewTurn || !session.turnId) {
@@ -637,7 +643,17 @@ function createCodexRequestMetadata(
637
643
  turnMetadata.turn_started_at_unix_ms = session.turnStartedAtUnixMs;
638
644
  }
639
645
  for (const key in extra) turnMetadata[key] = extra[key];
640
- const turnMetadataJson = toAsciiJsonString(turnMetadata);
646
+ // The `x-codex-turn-metadata` header is capped at 100KB by the Codex
647
+ // backend, while `tool_namespaces_info` grows with the session's tool count
648
+ // (409 tools ~ 100KB on its own). The body's `client_metadata` is the
649
+ // canonical envelope, so the snapshot rides there only and the header keeps
650
+ // the fixed-size identity projection.
651
+ const turnMetadataHeaderJson = toAsciiJsonString(turnMetadata);
652
+ let turnMetadataJson = turnMetadataHeaderJson;
653
+ if (options.toolNamespacesInfo !== undefined) {
654
+ turnMetadata.tool_namespaces_info = options.toolNamespacesInfo;
655
+ turnMetadataJson = toAsciiJsonString(turnMetadata);
656
+ }
641
657
  const clientMetadata: Record<string, string> = {
642
658
  [OPENAI_HEADERS.INSTALLATION_ID]: identity.installationId,
643
659
  session_id: identity.sessionId,
@@ -653,6 +669,7 @@ function createCodexRequestMetadata(
653
669
  ...identity,
654
670
  turnId: session.turnId,
655
671
  turnMetadataJson,
672
+ turnMetadataHeaderJson,
656
673
  clientMetadata,
657
674
  };
658
675
  }
@@ -661,8 +678,8 @@ function applyCodexCompatibilityHeaders(headers: Headers, metadata: CodexCompati
661
678
  headers.set(OPENAI_HEADERS.SCOPED_SESSION_ID, metadata.sessionId);
662
679
  headers.set(OPENAI_HEADERS.THREAD_ID, metadata.threadId);
663
680
  headers.set(OPENAI_HEADERS.WINDOW_ID, metadata.windowId);
664
- if (metadata.turnMetadataJson) {
665
- headers.set(OPENAI_HEADERS.TURN_METADATA, metadata.turnMetadataJson);
681
+ if (metadata.turnMetadataHeaderJson) {
682
+ headers.set(OPENAI_HEADERS.TURN_METADATA, metadata.turnMetadataHeaderJson);
666
683
  } else {
667
684
  headers.delete(OPENAI_HEADERS.TURN_METADATA);
668
685
  }
@@ -1452,6 +1469,7 @@ function createCodexRequestContext(
1452
1469
  clientMetadata: transformedBody.client_metadata,
1453
1470
  parentTurnId: options?.parentTurnId,
1454
1471
  compaction,
1472
+ toolNamespacesInfo: options?.toolNamespacesInfo,
1455
1473
  });
1456
1474
  transformedBody.client_metadata = requestMetadata.clientMetadata;
1457
1475
  return {
@@ -4238,6 +4256,12 @@ function createCodexHeaders(
4238
4256
  headers.delete("x-api-key");
4239
4257
  headers.set("Authorization", `Bearer ${accessToken}`);
4240
4258
  if (accountId) headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId);
4259
+ // Region-pinned enterprise workspaces answer 401 `Workspace is not authorized
4260
+ // in this region.` when the request's egress region does not match them and
4261
+ // the client did not declare the workspace's residency. The access token
4262
+ // already carries it, so this needs no configuration. A caller-supplied
4263
+ // header still wins: a proxy fronting Codex may want a different value.
4264
+ applyCodexResidencyHeader(headers, accessToken);
4241
4265
  if (attestation) {
4242
4266
  headers.set(OPENAI_HEADERS.ATTESTATION, attestation);
4243
4267
  } else {
@@ -4479,7 +4503,9 @@ function convertMessages(model: Model<"openai-codex-responses">, context: Contex
4479
4503
 
4480
4504
  function normalizeInputMessageContent(
4481
4505
  model: Model<"openai-codex-responses">,
4482
- content: string | Array<{ type: "text"; text: string } | { type: "image"; mimeType: string; data: string }>,
4506
+ content:
4507
+ | string
4508
+ | Array<{ type: "text"; text: string } | { type: "image"; mimeType: string; data: string; url?: string }>,
4483
4509
  ): ResponseInputContent[] {
4484
4510
  // gpt-5.x codex rejects reserved Harmony control-token spellings in input
4485
4511
  // data; escape the transport copy of untrusted user text so ordinary docs or
@@ -1950,7 +1950,7 @@ export function convertMessages(
1950
1950
  content.push({
1951
1951
  type: "image_url",
1952
1952
  image_url: {
1953
- url: `data:${item.mimeType};base64,${item.data}`,
1953
+ url: item.url ?? `data:${item.mimeType};base64,${item.data}`,
1954
1954
  // Chat Completions has no "original"; omit it (provider default).
1955
1955
  ...(item.detail && item.detail !== "original" ? { detail: item.detail } : {}),
1956
1956
  },
@@ -2257,7 +2257,7 @@ export function convertMessages(
2257
2257
  imageBlocks.push({
2258
2258
  type: "image_url",
2259
2259
  image_url: {
2260
- url: `data:${block.mimeType};base64,${block.data}`,
2260
+ url: block.url ?? `data:${block.mimeType};base64,${block.data}`,
2261
2261
  },
2262
2262
  });
2263
2263
  }
@@ -1630,6 +1630,18 @@ function clampResponsesImageDetail(
1630
1630
  return resolved === "original" && !supportsImageDetailOriginal ? "auto" : resolved;
1631
1631
  }
1632
1632
 
1633
+ function convertResponsesInputImage(image: ImageContent, supportsImageDetailOriginal: boolean): ResponseInputImage {
1634
+ const detail = clampResponsesImageDetail(image.detail, supportsImageDetailOriginal);
1635
+ if (image.providerFile?.provider === "openai" && image.providerFile.id) {
1636
+ return { type: "input_image", detail, file_id: image.providerFile.id };
1637
+ }
1638
+ return {
1639
+ type: "input_image",
1640
+ detail,
1641
+ image_url: image.url ?? `data:${image.mimeType};base64,${image.data}`,
1642
+ };
1643
+ }
1644
+
1633
1645
  export function convertResponsesInputContent(
1634
1646
  content: string | Array<TextContent | ImageContent>,
1635
1647
  supportsImages: boolean,
@@ -1659,11 +1671,7 @@ export function convertResponsesInputContent(
1659
1671
  } satisfies ResponseInputText);
1660
1672
  }
1661
1673
  for (const item of imageBlocks) {
1662
- normalizedContent.push({
1663
- type: "input_image",
1664
- detail: clampResponsesImageDetail(item.detail, supportsImageDetailOriginal),
1665
- image_url: `data:${item.mimeType};base64,${item.data}`,
1666
- } satisfies ResponseInputImage);
1674
+ normalizedContent.push(convertResponsesInputImage(item, supportsImageDetailOriginal));
1667
1675
  }
1668
1676
  if (omittedImages) {
1669
1677
  normalizedContent.push({
@@ -2338,11 +2346,7 @@ export function appendResponsesToolResultMessages<TApi extends Api>(
2338
2346
  ];
2339
2347
  for (const block of toolResult.content) {
2340
2348
  if (block.type === "image") {
2341
- contentParts.push({
2342
- type: "input_image",
2343
- detail: clampResponsesImageDetail(block.detail, supportsImageDetailOriginal),
2344
- image_url: `data:${block.mimeType};base64,${block.data}`,
2345
- } satisfies ResponseInputImage);
2349
+ contentParts.push(convertResponsesInputImage(block, supportsImageDetailOriginal));
2346
2350
  }
2347
2351
  }
2348
2352
  const imageMessage = { role: "user", content: contentParts } satisfies ResponseInput[number];
@@ -2363,6 +2367,17 @@ export function appendResponsesToolResultMessages<TApi extends Api>(
2363
2367
  */
2364
2368
  type ResponsesToolCallBlock = ToolCall & { [kStreamingPartialJson]: string; [kStreamingLastParseLen]?: number };
2365
2369
 
2370
+ function ensureReasoningSummaryPart(
2371
+ item: ResponseReasoningItem,
2372
+ summaryIndex: number,
2373
+ ): ResponseReasoningItem["summary"][number] {
2374
+ item.summary = item.summary || [];
2375
+ while (item.summary.length <= summaryIndex) {
2376
+ item.summary.push({ type: "summary_text", text: "" });
2377
+ }
2378
+ return item.summary[summaryIndex]!;
2379
+ }
2380
+
2366
2381
  export function appendReasoningSummaryPart(
2367
2382
  item: ResponseReasoningItem,
2368
2383
  part: ResponseReasoningItem["summary"][number],
@@ -2450,15 +2465,44 @@ export function appendReasoningSummaryTextDelta(
2450
2465
  stream: AssistantMessageEventStream,
2451
2466
  output: AssistantMessage,
2452
2467
  contentIndex: number,
2468
+ summaryIndex = 0,
2453
2469
  ): void {
2454
- item.summary = item.summary || [];
2455
- const lastPart = item.summary[item.summary.length - 1];
2456
- if (!lastPart) return;
2470
+ const part = ensureReasoningSummaryPart(item, summaryIndex);
2457
2471
  block.thinking += delta;
2458
- lastPart.text += delta;
2472
+ part.text += delta;
2459
2473
  stream.push({ type: "thinking_delta", contentIndex, delta, partial: output });
2460
2474
  }
2461
2475
 
2476
+ /**
2477
+ * Applies a completed reasoning-summary snapshot, including providers that omit
2478
+ * the preceding summary-part event.
2479
+ */
2480
+ export function applyReasoningSummaryTextDone(
2481
+ item: ResponseReasoningItem,
2482
+ block: ThinkingContent,
2483
+ text: string,
2484
+ summaryIndex: number,
2485
+ stream: AssistantMessageEventStream,
2486
+ output: AssistantMessage,
2487
+ contentIndex: number,
2488
+ ): void {
2489
+ const part = ensureReasoningSummaryPart(item, summaryIndex);
2490
+ const previous = part.text;
2491
+ part.text = text;
2492
+ if (!text || text === previous) return;
2493
+ if (!block.thinking) {
2494
+ block.thinking = text;
2495
+ stream.push({ type: "thinking_delta", contentIndex, delta: text, partial: output });
2496
+ return;
2497
+ }
2498
+ if (text.startsWith(block.thinking)) {
2499
+ const delta = text.slice(block.thinking.length);
2500
+ if (!delta) return;
2501
+ block.thinking += delta;
2502
+ stream.push({ type: "thinking_delta", contentIndex, delta, partial: output });
2503
+ }
2504
+ }
2505
+
2462
2506
  export function appendReasoningSummaryPartDone(
2463
2507
  item: ResponseReasoningItem,
2464
2508
  block: ThinkingContent,
@@ -2994,6 +3038,20 @@ export async function processResponsesStream<TApi extends Api>(
2994
3038
  stream,
2995
3039
  output,
2996
3040
  contentIndexOf(entry.block),
3041
+ event.summary_index,
3042
+ );
3043
+ }
3044
+ } else if (event.type === "response.reasoning_summary_text.done") {
3045
+ const entry = lookupOpenItem(event);
3046
+ if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
3047
+ applyReasoningSummaryTextDone(
3048
+ entry.item,
3049
+ entry.block,
3050
+ event.text,
3051
+ event.summary_index,
3052
+ stream,
3053
+ output,
3054
+ contentIndexOf(entry.block),
2997
3055
  );
2998
3056
  }
2999
3057
  } else if (event.type === "response.reasoning_summary_part.done") {