@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
@@ -914,7 +914,15 @@ async function resizeAnthropicManyImageContent(
914
914
  let changed = false;
915
915
  const next = await Promise.all(
916
916
  content.map(async block => {
917
- if (block.type !== "image") return block;
917
+ // Remotely referenced blocks never put base64 on the wire, so their size
918
+ // cannot violate the many-image request budget — and resizing would
919
+ // desync fallback bytes from the advertised remote image.
920
+ if (
921
+ block.type !== "image" ||
922
+ block.url ||
923
+ (block.providerFile?.provider === "anthropic" && block.providerFile.id)
924
+ )
925
+ return block;
918
926
  let resized = anthropicManyImageResizeCache.get(block);
919
927
  if (resized === undefined) {
920
928
  resized = await limit(() => resizeAnthropicManyImageBlock(block));
@@ -971,19 +979,14 @@ async function prepareAnthropicManyImageContext(context: Context, supportsImages
971
979
  return { ...context, messages };
972
980
  }
973
981
 
982
+ type AnthropicImageSource =
983
+ | { type: "base64"; media_type: AnthropicImageMediaType; data: string }
984
+ | { type: "url"; url: string }
985
+ | { type: "file"; file_id: string };
986
+
974
987
  type AnthropicToolResultContent =
975
988
  | string
976
- | Array<
977
- | { type: "text"; text: string }
978
- | {
979
- type: "image";
980
- source: {
981
- type: "base64";
982
- media_type: AnthropicImageMediaType;
983
- data: string;
984
- };
985
- }
986
- >;
989
+ | Array<{ type: "text"; text: string } | { type: "image"; source: AnthropicImageSource }>;
987
990
 
988
991
  /**
989
992
  * Convert content blocks to Anthropic API format
@@ -992,17 +995,7 @@ function convertContentBlocks(
992
995
  content: (TextContent | ImageContent)[],
993
996
  supportsImages = true,
994
997
  ): AnthropicToolResultContent {
995
- const blocks: Array<
996
- | { type: "text"; text: string }
997
- | {
998
- type: "image";
999
- source: {
1000
- type: "base64";
1001
- media_type: AnthropicImageMediaType;
1002
- data: string;
1003
- };
1004
- }
1005
- > = [];
998
+ const blocks: Array<{ type: "text"; text: string } | { type: "image"; source: AnthropicImageSource }> = [];
1006
999
  let sawText = false;
1007
1000
  let sawImage = false;
1008
1001
 
@@ -1020,21 +1013,22 @@ function convertContentBlocks(
1020
1013
  continue;
1021
1014
  }
1022
1015
 
1023
- const mediaType = normalizeAnthropicImageMediaType(block.mimeType);
1024
- if (!mediaType) {
1025
- blocks.push({ type: "text", text: `[unsupported image: ${block.mimeType}]` });
1026
- continue;
1016
+ let source: AnthropicImageSource;
1017
+ if (block.providerFile?.provider === "anthropic" && block.providerFile.id) {
1018
+ source = { type: "file", file_id: block.providerFile.id };
1019
+ } else if (block.url) {
1020
+ source = { type: "url", url: block.url };
1021
+ } else {
1022
+ const mediaType = normalizeAnthropicImageMediaType(block.mimeType);
1023
+ if (!mediaType) {
1024
+ blocks.push({ type: "text", text: `[unsupported image: ${block.mimeType}]` });
1025
+ continue;
1026
+ }
1027
+ source = { type: "base64", media_type: mediaType, data: block.data };
1027
1028
  }
1028
1029
 
1029
1030
  sawImage = true;
1030
- blocks.push({
1031
- type: "image",
1032
- source: {
1033
- type: "base64",
1034
- media_type: mediaType,
1035
- data: block.data,
1036
- },
1037
- });
1031
+ blocks.push({ type: "image", source });
1038
1032
  }
1039
1033
 
1040
1034
  if (!supportsImages) {
@@ -74,6 +74,8 @@ import type { ToolResultMessage } from "../../types";
74
74
  * and their translation are consumed together.
75
75
  */
76
76
  export {
77
+ cursorEditOwnedReadPath,
78
+ cursorRawReadPath,
77
79
  omitUndefinedArgs,
78
80
  piEscapeRegexLiteral,
79
81
  piGrepSkip,
@@ -43,8 +43,9 @@ export function piReadPath(readPath: string, offset?: number, limit?: number): s
43
43
  const start = offset !== undefined ? Math.max(1, Math.floor(offset)) : undefined;
44
44
  const count = limit !== undefined ? Math.floor(limit) : undefined;
45
45
  if (start === undefined && count === undefined) return readPath;
46
- if (start === undefined) return `${readPath}:raw:1+${count}`;
47
- return count === undefined ? `${readPath}:raw:${start}-` : `${readPath}:raw:${start}+${count}`;
46
+ const base = readPath.split(":").some(chunk => chunk.toLowerCase() === "raw") ? readPath : `${readPath}:raw`;
47
+ if (start === undefined) return `${base}:1+${count}`;
48
+ return count === undefined ? `${base}:${start}-` : `${base}:${start}+${count}`;
48
49
  }
49
50
 
50
51
  const READ_RANGE_CHUNK_RE = /^L?(\d+)(?:(\.\.|[-+])L?(\d+)?)?$/i;
@@ -77,6 +78,41 @@ export function piReadPathHasRange(readPath: string): boolean {
77
78
  return preceding !== undefined && isReadRangeList(preceding);
78
79
  }
79
80
 
81
+ /**
82
+ * Force a Cursor exec read onto `read`'s verbatim `:raw` selector.
83
+ *
84
+ * Native `editToolCall` (StrReplace) materializes via `readArgs` then
85
+ * `writeArgs`. The server treats the read result as file bytes and writes
86
+ * them back. A hashline-formatted native read (`[path#TAG]` + `LINE:`
87
+ * prefixes) poisons that cycle: the write would persist the markup.
88
+ * `:raw` is the existing selector that drops both. A path that already
89
+ * carries `raw` is left alone; a range-only selector gets `raw` inserted
90
+ * so the range still applies without the gutter.
91
+ */
92
+ export function cursorRawReadPath(readPath: string): string {
93
+ const chunks = readPath.split(":");
94
+ if (chunks.some(chunk => chunk.toLowerCase() === "raw")) return readPath;
95
+ if (piReadPathHasRange(readPath)) {
96
+ const last = chunks.pop()!;
97
+ return `${chunks.join(":")}:raw:${last}`;
98
+ }
99
+ return `${readPath}:raw`;
100
+ }
101
+
102
+ /**
103
+ * Path the edit-owned materialization read should execute.
104
+ *
105
+ * Range is composed first (`piReadPath` already uses `:raw` for a range),
106
+ * then a whole-file path is forced onto `:raw`. The caller must drop
107
+ * `offset`/`limit` after this so the bridge's `piReadPath` cannot append a
108
+ * second `:raw` onto the already-composed selector.
109
+ */
110
+ export function cursorEditOwnedReadPath(readPath: string, offset?: number, limit?: number): string | null {
111
+ const ranged = piReadPath(readPath, offset, limit);
112
+ if (ranged === null) return null;
113
+ return cursorRawReadPath(ranged);
114
+ }
115
+
80
116
  /**
81
117
  * The same range as {@link piReadPath}, rendered for a transcript block rather
82
118
  * than for execution.