@oh-my-pi/pi-ai 18.2.0 → 18.2.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/types/auth-broker/remote-store.d.ts +17 -0
  3. package/dist/types/auth-gateway/index.d.ts +1 -0
  4. package/dist/types/auth-gateway/session-state.d.ts +65 -0
  5. package/dist/types/auth-storage.d.ts +16 -0
  6. package/dist/types/error/body-error.d.ts +15 -0
  7. package/dist/types/error/flags.d.ts +16 -0
  8. package/dist/types/error/index.d.ts +1 -0
  9. package/dist/types/oneshot-retry.d.ts +6 -0
  10. package/dist/types/providers/openai-codex/request-transformer.d.ts +27 -0
  11. package/dist/types/providers/openai-shared.d.ts +20 -3
  12. package/dist/types/registry/oauth/perplexity.d.ts +1 -7
  13. package/dist/types/registry/oauth/types.d.ts +8 -0
  14. package/dist/types/stream.d.ts +2 -0
  15. package/dist/types/types.d.ts +3 -1
  16. package/dist/types/usage.d.ts +8 -0
  17. package/dist/types/utils/block-symbols.d.ts +36 -0
  18. package/dist/types/utils/openai-http.d.ts +2 -0
  19. package/dist/types/utils/retry-after.d.ts +2 -0
  20. package/dist/types/utils/schema/wire.d.ts +4 -5
  21. package/dist/types/utils.d.ts +9 -0
  22. package/package.json +6 -6
  23. package/src/auth-broker/remote-store.ts +73 -8
  24. package/src/auth-broker/wire-schemas.ts +1 -0
  25. package/src/auth-gateway/index.ts +1 -0
  26. package/src/auth-gateway/server.ts +48 -11
  27. package/src/auth-gateway/session-state.ts +114 -0
  28. package/src/auth-storage.ts +144 -13
  29. package/src/error/body-error.ts +310 -0
  30. package/src/error/flags.ts +63 -13
  31. package/src/error/index.ts +1 -0
  32. package/src/error/retryable.ts +2 -0
  33. package/src/oneshot-retry.ts +13 -3
  34. package/src/providers/anthropic-messages-server.ts +24 -3
  35. package/src/providers/anthropic.ts +101 -15
  36. package/src/providers/cursor.ts +7 -1
  37. package/src/providers/devin.ts +82 -28
  38. package/src/providers/openai-chat-server.ts +4 -0
  39. package/src/providers/openai-codex/request-transformer.ts +36 -0
  40. package/src/providers/openai-codex-responses.ts +35 -12
  41. package/src/providers/openai-completions.ts +43 -12
  42. package/src/providers/openai-reasoning-fallback.ts +6 -6
  43. package/src/providers/openai-responses-server.ts +2 -1
  44. package/src/providers/openai-responses.ts +25 -4
  45. package/src/providers/openai-shared.ts +199 -51
  46. package/src/registry/oauth/perplexity.ts +94 -28
  47. package/src/registry/oauth/types.ts +9 -0
  48. package/src/stream.ts +23 -2
  49. package/src/types.ts +3 -0
  50. package/src/usage/claude.ts +33 -0
  51. package/src/usage/google-antigravity.ts +8 -2
  52. package/src/usage.ts +3 -0
  53. package/src/utils/block-symbols.ts +57 -0
  54. package/src/utils/openai-http.ts +39 -3
  55. package/src/utils/retry-after.ts +12 -0
  56. package/src/utils/schema/normalize.ts +3 -3
  57. package/src/utils/schema/stamps.ts +33 -45
  58. package/src/utils/schema/wire.ts +9 -7
  59. package/src/utils.ts +67 -22
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { Type } from "@oh-my-pi/omptype";
10
+ import { structuredCloneJSON } from "@oh-my-pi/pi-utils";
10
11
  import type { Tool, TSchema } from "../../types";
11
12
  import { upgradeJsonSchemaTo202012 } from "./draft";
12
13
  import { stamp } from "./stamps";
@@ -107,7 +108,7 @@ function arkJsonAstToWire(value: unknown): unknown {
107
108
  return {};
108
109
  }
109
110
 
110
- /** Symbol-stamped caches keyed by schema object identity. */
111
+ /** `stamp` cache keys; the entries themselves live in a weak side table keyed by schema identity. */
111
112
  const kJsonWireSchema = Symbol("pi.schema.json.wire");
112
113
  const kArkWireSchema = Symbol("pi.schema.ark.wire");
113
114
  const kStrippedSchema = Symbol("pi.schema.descriptions.stripped");
@@ -602,7 +603,9 @@ export function toolWireSchema(tool: Tool): Record<string, unknown> {
602
603
  const params: TSchema = tool.parameters;
603
604
  if (isArkSchema(params)) return arkToWireSchema(params);
604
605
  return stamp(params as Record<string, unknown>, kJsonWireSchema, p => {
605
- const raw = isArkJsonAst(p) ? arkJsonAstToWire(p) : p;
606
+ // Legacy schemas may carry non-cloneable metadata (helper functions); the JSON
607
+ // fallback drops it the same way the wire serializer always has.
608
+ const raw = isArkJsonAst(p) ? arkJsonAstToWire(p) : structuredCloneJSON(p);
606
609
  const upgraded = upgradeJsonSchemaTo202012(raw) as Record<string, unknown>;
607
610
  return postProcessJsonSchema(upgraded);
608
611
  });
@@ -663,10 +666,9 @@ function stripSchemaDescriptionsInPlace(node: unknown): void {
663
666
 
664
667
  /**
665
668
  * Return a deep clone of `schema` with every `description` annotation removed.
666
- * The result is memoized on the input via a non-enumerable symbol (`stamp`) so
667
- * repeated provider requests reuse the same stripped object; the input is never
668
- * mutated, so the stamped `toolWireSchema` cache stays intact for
669
- * system-prompt/UI rendering.
669
+ * The result is memoized against the input (`stamp`) so repeated provider
670
+ * requests reuse the same stripped object; the input is never mutated, so the
671
+ * memoized `toolWireSchema` result stays intact for system-prompt/UI rendering.
670
672
  */
671
673
  export function stripSchemaDescriptions(schema: Record<string, unknown>): Record<string, unknown> {
672
674
  return stamp(schema, kStrippedSchema, source => {
@@ -682,7 +684,7 @@ export function stripSchemaDescriptions(schema: Record<string, unknown>): Record
682
684
  * Used when the full tool catalog is rendered into the system prompt instead, so
683
685
  * the descriptions ride the wire once (in the prompt) rather than duplicated on
684
686
  * every tool definition. Parameters are resolved to wire JSON Schema and cloned,
685
- * leaving the original tool objects and the stamped schema cache untouched.
687
+ * leaving the original tool objects and the memoized schema cache untouched.
686
688
  */
687
689
  export function stripToolDescriptions(tools: readonly Tool[]): Tool[] {
688
690
  return tools.map(tool => ({
package/src/utils.ts CHANGED
@@ -45,8 +45,10 @@ export function normalizeResponsesToolCallId(
45
45
  id: string,
46
46
  itemPrefix: ResponsesToolItemIdPrefix = "fc",
47
47
  ): { callId: string; itemId: string } {
48
- const [callId, itemId] = id.split("|");
49
- if (callId && itemId) {
48
+ const sep = id.search(/[\n|]/);
49
+ if (sep > 0) {
50
+ const callId = id.slice(0, sep);
51
+ const itemId = id.slice(sep + 1);
50
52
  return { callId, itemId: normalizeResponsesItemId(itemId, itemPrefix) };
51
53
  }
52
54
  const hash = Bun.hash(id).toString(36);
@@ -324,6 +326,15 @@ export function stripUnpairedOpenAIResponsesComputerReasoningIdsForReplay(items:
324
326
  * Returns `undefined` for hidden-empty turns that only contain reasoning and an
325
327
  * empty assistant message, allowing callers to rebuild visible transcript
326
328
  * history instead of replaying stale native state.
329
+ *
330
+ * When the turn does carry replayable output, whitespace-only assistant
331
+ * messages are dropped rather than replayed. gpt-5.6 Codex ends a turn whose
332
+ * text all landed in the `commentary` phase with an empty `final_answer`
333
+ * message; replaying that item verbatim seeds the next turn with an empty slot
334
+ * the model then fills - `""` → `"\n\n"` → stray words → non-Latin residue -
335
+ * and each contaminated item is replayed in turn, so the drift compounds until
336
+ * the visible answer collapses. Codex CLI replays the empty item too
337
+ * (openai/codex#32389); this is the layer where omp can refuse to.
327
338
  */
328
339
  export function sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(
329
340
  items: Array<Record<string, unknown>>,
@@ -331,34 +342,68 @@ export function sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(
331
342
  ): ResponseInput | undefined {
332
343
  const sanitized = sanitizeOpenAIResponsesHistoryItemsForReplay(items, options);
333
344
  let hasReplayableAssistantOutput = false;
345
+ let hasEmptyAssistantMessage = false;
334
346
 
335
347
  for (const item of sanitized) {
336
348
  if (item.type === "reasoning") continue;
337
- if (item.type !== "message" || item.role !== "assistant") {
338
- hasReplayableAssistantOutput = true;
339
- break;
349
+ if (isEmptyAssistantMessage(item)) {
350
+ hasEmptyAssistantMessage = true;
351
+ continue;
340
352
  }
341
- if (typeof item.content === "string") {
342
- if (NON_WHITESPACE_RE.test(item.content)) {
343
- hasReplayableAssistantOutput = true;
344
- break;
345
- }
353
+ hasReplayableAssistantOutput = true;
354
+ }
355
+
356
+ if (!hasReplayableAssistantOutput) return undefined;
357
+ if (!hasEmptyAssistantMessage) return sanitized;
358
+ return dropEmptyAssistantMessagesForReplay(sanitized);
359
+ }
360
+
361
+ function isEmptyAssistantMessage(item: OpenAIResponsesReplayItem): boolean {
362
+ if (item.type !== "message" || item.role !== "assistant") return false;
363
+ if (typeof item.content === "string") return !NON_WHITESPACE_RE.test(item.content);
364
+ for (const part of item.content) {
365
+ if (part.type === "output_text" && NON_WHITESPACE_RE.test(part.text)) return false;
366
+ if (part.type === "refusal" && NON_WHITESPACE_RE.test(part.refusal)) return false;
367
+ }
368
+ return true;
369
+ }
370
+
371
+ /**
372
+ * Remove whitespace-only assistant messages plus any reasoning item left with
373
+ * no output to introduce: a reasoning item is only meaningful ahead of the
374
+ * output it produced, and a dangling one is rejected by the Responses API when
375
+ * it carries an id.
376
+ */
377
+ function dropEmptyAssistantMessagesForReplay(items: ResponseInput): ResponseInput {
378
+ const kept: ResponseInput = [];
379
+ let pendingReasoning: ResponseInput = [];
380
+ let removedAssistantMessage = false;
381
+ const flushReasoning = (): void => {
382
+ kept.push(...pendingReasoning);
383
+ pendingReasoning = [];
384
+ };
385
+ for (const item of items) {
386
+ if (isOpenAIResponsesClientInputBoundary(item as unknown as Record<string, unknown>)) {
387
+ pendingReasoning = [];
388
+ removedAssistantMessage = false;
389
+ kept.push(item);
346
390
  continue;
347
391
  }
348
- for (const part of item.content) {
349
- if (part.type === "output_text" && NON_WHITESPACE_RE.test(part.text)) {
350
- hasReplayableAssistantOutput = true;
351
- break;
352
- }
353
- if (part.type === "refusal" && NON_WHITESPACE_RE.test(part.refusal)) {
354
- hasReplayableAssistantOutput = true;
355
- break;
356
- }
392
+ if (item.type === "reasoning") {
393
+ if (removedAssistantMessage) pendingReasoning = [];
394
+ removedAssistantMessage = false;
395
+ pendingReasoning.push(item);
396
+ continue;
357
397
  }
358
- if (hasReplayableAssistantOutput) break;
398
+ if (isEmptyAssistantMessage(item)) {
399
+ removedAssistantMessage = true;
400
+ continue;
401
+ }
402
+ flushReasoning();
403
+ removedAssistantMessage = false;
404
+ kept.push(item);
359
405
  }
360
-
361
- return hasReplayableAssistantOutput ? sanitized : undefined;
406
+ return kept;
362
407
  }
363
408
 
364
409
  /**