@oh-my-pi/pi-ai 16.0.2 → 16.0.3

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.0.3] - 2026-06-16
6
+
7
+ ### Added
8
+
9
+ - Exported `renderDelimitedThinking` from the `@oh-my-pi/pi-ai/dialect` barrel so consumers can reuse the dialect's `<thinking>` envelope unwrap-and-rewrap logic (the only `./dialect/rendering` primitive re-exported; the rest stay dialect-internal).
10
+
11
+ ### Fixed
12
+
13
+ - Fixed OpenAI Responses/Codex tool schema normalization stripping provider-rejected regex lookaround patterns from MCP tool parameter schemas. ([#2784](https://github.com/can1357/oh-my-pi/issues/2784))
14
+ - Fixed OpenAI Responses parallel tool-call routing so late keyed argument deltas for a closed call are dropped instead of being appended to another open call.
15
+
5
16
  ## [16.0.2] - 2026-06-16
6
17
 
7
18
  ### Added
@@ -5,4 +5,5 @@ export * from "./factory";
5
5
  export * from "./history";
6
6
  export * from "./inventory";
7
7
  export * from "./owned-stream";
8
+ export { renderDelimitedThinking } from "./rendering";
8
9
  export * from "./types";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "16.0.2",
4
+ "version": "16.0.3",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,8 +38,8 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "@oh-my-pi/pi-catalog": "16.0.2",
42
- "@oh-my-pi/pi-utils": "16.0.2",
41
+ "@oh-my-pi/pi-catalog": "16.0.3",
42
+ "@oh-my-pi/pi-utils": "16.0.3",
43
43
  "partial-json": "^0.1.7",
44
44
  "zod": "^4"
45
45
  },
@@ -5,4 +5,9 @@ export * from "./factory";
5
5
  export * from "./history";
6
6
  export * from "./inventory";
7
7
  export * from "./owned-stream";
8
+ // `./rendering` is a dialect-internal primitives module deliberately excluded
9
+ // from the barrel. `renderDelimitedThinking` is the one helper an external
10
+ // consumer needs (the legacy markdown `/dump` reuses its `<thinking>` envelope
11
+ // unwrap), so re-export only that symbol rather than `export *`-ing the rest.
12
+ export { renderDelimitedThinking } from "./rendering";
8
13
  export * from "./types";
@@ -525,6 +525,7 @@ export async function processResponsesStream<TApi extends Api>(
525
525
  lastOpenItem = entry;
526
526
  };
527
527
  const lookupOpenItem = (event: { output_index?: number; item_id?: string }): StreamingItem | undefined => {
528
+ const hasKey = typeof event.output_index === "number" || event.item_id !== undefined;
528
529
  if (typeof event.output_index === "number") {
529
530
  const found = openItemsByOutputIndex.get(event.output_index);
530
531
  if (found) return found;
@@ -533,8 +534,10 @@ export async function processResponsesStream<TApi extends Api>(
533
534
  const found = openItemsByItemId.get(event.item_id);
534
535
  if (found) return found;
535
536
  }
536
- // Fallback for tests / mock providers that omit identifiers on stream events.
537
- return lastOpenItem ?? undefined;
537
+ // Keyed events whose item already closed are stale; drop them instead of
538
+ // routing to a sibling. Only fully identifierless mock/proxy events use the
539
+ // legacy singleton fallback.
540
+ return hasKey ? undefined : (lastOpenItem ?? undefined);
538
541
  };
539
542
  const hasOpenItemKey = (event: { output_index?: number; item_id?: string }): boolean =>
540
543
  typeof event.output_index === "number" || event.item_id !== undefined;
@@ -936,8 +936,25 @@ export function sanitizeSchemaForOpenAIResponses(schema: JsonObject): JsonObject
936
936
  * `normalizeSchemaFor*` dispatcher naming used elsewhere in this module.
937
937
  */
938
938
  export const normalizeSchemaForOpenAIResponses: (schema: JsonObject) => JsonObject = sanitizeSchemaForOpenAIResponses;
939
+ const OPENAI_UNSUPPORTED_REGEX_LOOKAROUNDS = new Set(["=", "!", "<=", "<!"]);
940
+ const OPENAI_RESPONSES_PATTERN_PROPERTIES_FALLBACK = ".*";
939
941
 
940
- function normalizeOpenAIResponsesSchemaNode(value: unknown, cache: WeakMap<JsonObject, JsonObject>): unknown {
942
+ function hasOpenAIUnsupportedRegexLookaround(pattern: string): boolean {
943
+ let groupStart = pattern.indexOf("(?");
944
+ while (groupStart !== -1) {
945
+ let escapes = 0;
946
+ for (let i = groupStart - 1; i >= 0 && pattern[i] === "\\"; i--) escapes++;
947
+ if (escapes % 2 === 0) {
948
+ const operator =
949
+ pattern[groupStart + 2] === "<" ? pattern.slice(groupStart + 2, groupStart + 4) : pattern[groupStart + 2];
950
+ if (OPENAI_UNSUPPORTED_REGEX_LOOKAROUNDS.has(operator)) return true;
951
+ }
952
+ groupStart = pattern.indexOf("(?", groupStart + 2);
953
+ }
954
+ return false;
955
+ }
956
+
957
+ function normalizeOpenAIResponsesSchemaNode(value: unknown, cache: WeakMap<JsonObject, unknown>): unknown {
941
958
  if (!isJsonObject(value)) return value;
942
959
 
943
960
  // `{}` (empty JSON Schema) ≡ `true` (JSON Schema draft 2020-12 §4.3.1).
@@ -973,11 +990,21 @@ function normalizeOpenAIResponsesSchemaNode(value: unknown, cache: WeakMap<JsonO
973
990
  changed = true;
974
991
  continue;
975
992
  }
993
+ if (
994
+ key === "pattern" &&
995
+ typeof value.pattern === "string" &&
996
+ hasOpenAIUnsupportedRegexLookaround(value.pattern)
997
+ ) {
998
+ changed = true;
999
+ continue;
1000
+ }
976
1001
 
977
1002
  const child = value[key];
978
1003
  let next: unknown = child;
979
- if (OPENAI_RESPONSES_SCHEMA_MAP_KEYS.has(key) && isJsonObject(child)) {
980
- next = normalizeOpenAIResponsesSchemaMap(child, cache);
1004
+ if (key === "patternProperties" && isJsonObject(child)) {
1005
+ next = normalizeOpenAIResponsesSchemaMap(child, cache, true);
1006
+ } else if (OPENAI_RESPONSES_SCHEMA_MAP_KEYS.has(key) && isJsonObject(child)) {
1007
+ next = normalizeOpenAIResponsesSchemaMap(child, cache, false);
981
1008
  } else if (OPENAI_RESPONSES_SCHEMA_ARRAY_KEYS.has(key) && Array.isArray(child)) {
982
1009
  next = normalizeOpenAIResponsesSchemaArray(child, cache);
983
1010
  } else if (OPENAI_RESPONSES_SCHEMA_VALUE_KEYS.has(key) && isJsonObject(child)) {
@@ -1008,7 +1035,7 @@ function normalizeOpenAIResponsesSchemaNode(value: unknown, cache: WeakMap<JsonO
1008
1035
  // the seeded partial and set `changed = true` for that node, so a node
1009
1036
  // that finishes with `changed === false` is provably non-cyclic and
1010
1037
  // referentially equal to its input.
1011
- const result = changed ? output : value;
1038
+ const result = changed ? (isJsonObjectEmpty(output) ? true : output) : value;
1012
1039
  cache.set(value, result);
1013
1040
  return result;
1014
1041
  }
@@ -1022,7 +1049,7 @@ function declaresObjectType(type: unknown): boolean {
1022
1049
  return false;
1023
1050
  }
1024
1051
 
1025
- function normalizeOpenAIResponsesSchemaArray(value: unknown[], cache: WeakMap<JsonObject, JsonObject>): unknown[] {
1052
+ function normalizeOpenAIResponsesSchemaArray(value: unknown[], cache: WeakMap<JsonObject, unknown>): unknown[] {
1026
1053
  let changed = false;
1027
1054
  const output = value.map(item => {
1028
1055
  const next = normalizeOpenAIResponsesSchemaNode(item, cache);
@@ -1032,7 +1059,11 @@ function normalizeOpenAIResponsesSchemaArray(value: unknown[], cache: WeakMap<Js
1032
1059
  return changed ? output : value;
1033
1060
  }
1034
1061
 
1035
- function normalizeOpenAIResponsesSchemaMap(schemaMap: JsonObject, cache: WeakMap<JsonObject, JsonObject>): JsonObject {
1062
+ function normalizeOpenAIResponsesSchemaMap(
1063
+ schemaMap: JsonObject,
1064
+ cache: WeakMap<JsonObject, unknown>,
1065
+ stripUnsupportedRegexKeys: boolean,
1066
+ ): JsonObject {
1036
1067
  let changed = false;
1037
1068
  const output: JsonObject = {};
1038
1069
  for (const key in schemaMap) {
@@ -1040,11 +1071,29 @@ function normalizeOpenAIResponsesSchemaMap(schemaMap: JsonObject, cache: WeakMap
1040
1071
  const child = schemaMap[key];
1041
1072
  const next = normalizeOpenAIResponsesSchemaNode(child, cache);
1042
1073
  if (next !== child) changed = true;
1074
+ if (stripUnsupportedRegexKeys && hasOpenAIUnsupportedRegexLookaround(key)) {
1075
+ changed = true;
1076
+ appendOpenAIResponsesFallbackPatternProperty(output, next);
1077
+ continue;
1078
+ }
1043
1079
  output[key] = next;
1044
1080
  }
1045
1081
  return changed ? output : schemaMap;
1046
1082
  }
1047
1083
 
1084
+ function appendOpenAIResponsesFallbackPatternProperty(output: JsonObject, schema: unknown): void {
1085
+ const existing = output[OPENAI_RESPONSES_PATTERN_PROPERTIES_FALLBACK];
1086
+ if (existing === undefined) {
1087
+ output[OPENAI_RESPONSES_PATTERN_PROPERTIES_FALLBACK] = schema;
1088
+ return;
1089
+ }
1090
+ if (isJsonObject(existing) && Array.isArray(existing.anyOf) && Object.keys(existing).length === 1) {
1091
+ existing.anyOf = [...existing.anyOf, schema];
1092
+ return;
1093
+ }
1094
+ output[OPENAI_RESPONSES_PATTERN_PROPERTIES_FALLBACK] = { anyOf: [existing, schema] };
1095
+ }
1096
+
1048
1097
  // ---------------------------------------------------------------------------
1049
1098
  // OpenAI strict mode — sanitize + enforce
1050
1099
  // ---------------------------------------------------------------------------