@oh-my-pi/pi-ai 17.2.3 → 17.2.5

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 (35) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/types/auth-storage.d.ts +8 -0
  3. package/dist/types/dialect/examples.d.ts +10 -2
  4. package/dist/types/dialect/inventory.d.ts +6 -9
  5. package/dist/types/dialect/rendering.d.ts +9 -0
  6. package/dist/types/dialect/types.d.ts +0 -1
  7. package/dist/types/error/flags.d.ts +5 -0
  8. package/dist/types/providers/openai-codex-responses.d.ts +15 -1
  9. package/dist/types/providers/openai-shared.d.ts +24 -6
  10. package/dist/types/providers/transform-messages.d.ts +1 -1
  11. package/dist/types/types.d.ts +6 -4
  12. package/dist/types/utils/harmony-leak.d.ts +9 -0
  13. package/dist/types/utils/schema/typescript.d.ts +8 -2
  14. package/package.json +4 -4
  15. package/src/auth-broker/discover.ts +2 -1
  16. package/src/auth-storage.ts +149 -15
  17. package/src/dialect/examples.ts +24 -12
  18. package/src/dialect/gemini.ts +17 -31
  19. package/src/dialect/harmony.ts +1 -2
  20. package/src/dialect/inventory.ts +20 -63
  21. package/src/dialect/rendering.ts +54 -0
  22. package/src/dialect/types.ts +0 -1
  23. package/src/error/flags.ts +8 -0
  24. package/src/providers/anthropic.ts +78 -33
  25. package/src/providers/cursor.ts +6 -3
  26. package/src/providers/openai-codex-responses.ts +191 -36
  27. package/src/providers/openai-completions.ts +7 -1
  28. package/src/providers/openai-responses.ts +5 -6
  29. package/src/providers/openai-shared.ts +114 -40
  30. package/src/providers/transform-messages.ts +1 -1
  31. package/src/stream.ts +2 -2
  32. package/src/types.ts +9 -8
  33. package/src/utils/harmony-leak.ts +12 -0
  34. package/src/utils/schema/typescript.ts +21 -7
  35. package/src/utils.ts +1 -1
@@ -300,7 +300,7 @@ function normalizeAnthropicTargetToolCallId<TApi extends Api>(
300
300
  * credential redaction is enabled. Exported so hosts can route the same shapes
301
301
  * through reversible obfuscation (keyed placeholders restored before local tool
302
302
  * execution) instead of the irreversible `[*_token_redacted]` rewrite below —
303
- * an irreversible placeholder echoed back in edit-tool `old_text` can never
303
+ * an irreversible placeholder echoed back in edit-tool `old_string` can never
304
304
  * match the real bytes on disk.
305
305
  */
306
306
  export const SENSITIVE_TOKEN_RE =
package/src/stream.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  } from "@oh-my-pi/pi-catalog/model-thinking";
16
16
  import { CATALOG_PROVIDERS, type ProviderCatalogEntry } from "@oh-my-pi/pi-catalog/provider-models";
17
17
  import { CODEX_BASE_URL } from "@oh-my-pi/pi-catalog/wire/codex";
18
- import { $env, $pickenv, getConfigRootDir, isEnoent, logger, withExtraCaFetch } from "@oh-my-pi/pi-utils";
18
+ import { $env, $pickenv, getProviderInFlightRoot, isEnoent, logger, withExtraCaFetch } from "@oh-my-pi/pi-utils";
19
19
  import { getCustomApi } from "./api-registry";
20
20
  import { createAuthRetryKeyState, isApiKeyResolver, resolveNextAuthRetryKey } from "./auth-retry";
21
21
  import * as AIError from "./error";
@@ -189,7 +189,7 @@ function resolveProviderInFlightLimit(
189
189
 
190
190
  function providerInFlightRoot(): string {
191
191
  if (providerInFlightRootOverride) return providerInFlightRootOverride;
192
- return path.join(getConfigRootDir(), "run", "provider-inflight");
192
+ return getProviderInFlightRoot();
193
193
  }
194
194
 
195
195
  function providerInFlightSegment(provider: string): string {
package/src/types.ts CHANGED
@@ -214,10 +214,10 @@ export function resolveModelServiceTier(
214
214
 
215
215
  /**
216
216
  * True when the tier should be sent on the wire as the provider's service-tier
217
- * request field. OpenAI / OpenAI-Codex accept `flex`/`scale`/`priority`; Google
218
- * (Gemini API + Vertex) and OpenRouter accept `flex`/`priority`; Fireworks
219
- * Serverless realizes only its Priority serving path. Anthropic is absent — it
220
- * realizes `priority` via `speed: "fast"`, not a service-tier field.
217
+ * request field. OpenAI / OpenAI-Codex accept every {@link ServiceTier};
218
+ * Google (Gemini API + Vertex) and OpenRouter accept `flex`/`priority`;
219
+ * Fireworks Serverless realizes only its Priority serving path. Anthropic is
220
+ * absent because it realizes `priority` via `speed: "fast"`.
221
221
  */
222
222
  export function shouldSendServiceTier(
223
223
  serviceTier: ServiceTier | null | undefined,
@@ -225,12 +225,11 @@ export function shouldSendServiceTier(
225
225
  ): boolean {
226
226
  if (!serviceTier) return false;
227
227
  const provider = typeof target === "string" ? target : target?.provider;
228
- if (provider === "openai" || provider === "openai-codex" || provider === "openrouter") {
229
- return serviceTier === "flex" || serviceTier === "scale" || serviceTier === "priority";
230
- }
231
- if (typeof target !== "string" && target && isOpenAIServiceTierModel(target)) {
228
+ if (provider === "openai" || provider === "openai-codex") return true;
229
+ if (provider === "openrouter") {
232
230
  return serviceTier === "flex" || serviceTier === "scale" || serviceTier === "priority";
233
231
  }
232
+ if (typeof target !== "string" && target && isOpenAIServiceTierModel(target)) return true;
234
233
  if (provider === "google") {
235
234
  return serviceTier === "flex" || serviceTier === "priority";
236
235
  }
@@ -844,6 +843,8 @@ export interface AssistantRetryRecovery {
844
843
  export interface ContextSnapshot {
845
844
  promptTokens: number; // authoritative provider prompt/input tokens
846
845
  nonMessageTokens: number; // estimated non-message total at send time
846
+ /** Estimated prompt tokens removed by local history rewrites after this provider snapshot was recorded. */
847
+ historyRewriteTokensRemoved?: number;
847
848
  lastMessageTimestamp?: number;
848
849
  }
849
850
 
@@ -35,6 +35,18 @@ export function escapeHarmonyControlTokens(text: string): string {
35
35
  return text.replace(HARMONY_CONTROL_TOKEN_ESCAPE_RE, "<\\|$1\\|>");
36
36
  }
37
37
 
38
+ /**
39
+ * Escape reserved Harmony control tokens inside a JSON document string (e.g.
40
+ * `function_call.arguments`). Doubles the backslash so the document remains
41
+ * valid JSON whose *decoded* strings carry the inert `<\|token\|>` spelling.
42
+ * `<|` cannot occur outside a string literal in valid JSON, so the blanket
43
+ * replace never corrupts structure; malformed documents are escaped
44
+ * best-effort.
45
+ */
46
+ export function escapeHarmonyControlTokensInJson(text: string): string {
47
+ return text.replace(HARMONY_CONTROL_TOKEN_ESCAPE_RE, "<\\\\|$1\\\\|>");
48
+ }
49
+
38
50
  /**
39
51
  * Whether requests to `model` are served by a Harmony-dialect backend
40
52
  * (gpt-5.x / gpt-oss), which rejects reserved control-token spellings appearing
@@ -12,15 +12,22 @@
12
12
  import { isJsonObject } from "./types";
13
13
 
14
14
  export interface JsonSchemaToTsOptions {
15
- /** Indentation unit for nested object bodies. Default two spaces. */
15
+ /** Indentation unit for nested object bodies. Default two spaces (none in `harmony` style). */
16
16
  readonly indent?: string;
17
- /** Emit `description` keywords as JSDoc comments on object properties. Default true. */
17
+ /** Emit `description` keywords as comments on object properties. Default true. */
18
18
  readonly comments?: boolean;
19
+ /**
20
+ * Output flavor. `default` renders JSDoc comments, `;` delimiters, and
21
+ * indented bodies; `harmony` renders the flat OpenAI-Harmony convention —
22
+ * `//` line comments, `,` delimiters, no indentation.
23
+ */
24
+ readonly style?: "default" | "harmony";
19
25
  }
20
26
 
21
27
  interface Ctx {
22
28
  readonly indent: string;
23
29
  readonly comments: boolean;
30
+ readonly harmony: boolean;
24
31
  readonly defs: Record<string, unknown> | undefined;
25
32
  readonly seen: Set<unknown>;
26
33
  }
@@ -48,7 +55,11 @@ function joinUnion(parts: readonly string[]): string {
48
55
  return unique.length > 0 ? unique.join(" | ") : "never";
49
56
  }
50
57
 
51
- function emitJsDoc(lines: string[], description: string, pad: string): void {
58
+ function emitDescription(lines: string[], description: string, ctx: Ctx, pad: string): void {
59
+ if (ctx.harmony) {
60
+ for (const line of description.split("\n")) lines.push(`${pad}// ${line}`.trimEnd());
61
+ return;
62
+ }
52
63
  // `* /` keeps a stray closing token inside the description from ending the comment.
53
64
  const safe = description.replace(/\*\//g, "* /");
54
65
  if (!safe.includes("\n")) {
@@ -85,6 +96,7 @@ function convertObject(node: Record<string, unknown>, ctx: Ctx, pad: string): st
85
96
  const required = new Set(
86
97
  Array.isArray(node.required) ? node.required.filter((key): key is string => typeof key === "string") : [],
87
98
  );
99
+ const delimiter = ctx.harmony ? "," : ";";
88
100
  for (const key in properties) {
89
101
  const value = properties[key];
90
102
  if (
@@ -93,11 +105,11 @@ function convertObject(node: Record<string, unknown>, ctx: Ctx, pad: string): st
93
105
  typeof value.description === "string" &&
94
106
  value.description.length > 0
95
107
  ) {
96
- emitJsDoc(body, value.description, childPad);
108
+ emitDescription(body, value.description, ctx, childPad);
97
109
  }
98
110
  const optional = required.has(key) ? "" : "?";
99
111
  const name = SAFE_KEY.test(key) ? key : JSON.stringify(key);
100
- body.push(`${childPad}${name}${optional}: ${convert(value, ctx, childPad)};`);
112
+ body.push(`${childPad}${name}${optional}: ${convert(value, ctx, childPad)}${delimiter}`);
101
113
  }
102
114
  }
103
115
 
@@ -110,7 +122,7 @@ function convertObject(node: Record<string, unknown>, ctx: Ctx, pad: string): st
110
122
 
111
123
  // Named properties alongside a free-form value schema → index signature.
112
124
  if (isJsonObject(additional)) {
113
- body.push(`${childPad}[key: string]: ${convert(additional, ctx, childPad)};`);
125
+ body.push(`${childPad}[key: string]: ${convert(additional, ctx, childPad)}${ctx.harmony ? "," : ";"}`);
114
126
  }
115
127
  return `{\n${body.join("\n")}\n${pad}}`;
116
128
  }
@@ -188,9 +200,11 @@ export function jsonSchemaToTypeScript(schema: unknown, options?: JsonSchemaToTs
188
200
  }
189
201
  }
190
202
  }
203
+ const harmony = options?.style === "harmony";
191
204
  const ctx: Ctx = {
192
- indent: options?.indent ?? " ",
205
+ indent: options?.indent ?? (harmony ? "" : " "),
193
206
  comments: options?.comments ?? true,
207
+ harmony,
194
208
  defs,
195
209
  seen: new Set(),
196
210
  };
package/src/utils.ts CHANGED
@@ -403,7 +403,7 @@ function sanitizeOpenAIResponsesReasoningItemForReplay(
403
403
  function sanitizeOpenAIResponsesImageGenerationCallForReplay(
404
404
  item: Record<string, unknown>,
405
405
  ): ResponseInputItem.ImageGenerationCall | undefined {
406
- if (typeof item.id !== "string" || item.status !== "completed" || typeof item.result !== "string") {
406
+ if (typeof item.id !== "string" || typeof item.result !== "string" || item.result.length === 0) {
407
407
  return undefined;
408
408
  }
409
409
  return {