@oh-my-pi/pi-ai 16.3.14 → 16.4.0

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 (53) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +5 -2
  3. package/dist/types/auth-broker/remote-store.d.ts +1 -0
  4. package/dist/types/auth-broker/wire-schemas.d.ts +8 -0
  5. package/dist/types/auth-storage.d.ts +5 -0
  6. package/dist/types/providers/azure-openai-responses.d.ts +1 -1
  7. package/dist/types/providers/ollama.d.ts +1 -1
  8. package/dist/types/providers/openai-chat-server-schema.d.ts +1 -1
  9. package/dist/types/providers/openai-chat-wire.d.ts +1 -1
  10. package/dist/types/providers/openai-codex/request-transformer.d.ts +43 -5
  11. package/dist/types/providers/openai-codex-responses.d.ts +51 -8
  12. package/dist/types/providers/openai-completions.d.ts +1 -1
  13. package/dist/types/providers/openai-responses-wire.d.ts +16 -7
  14. package/dist/types/providers/openai-responses.d.ts +1 -1
  15. package/dist/types/providers/openai-shared.d.ts +38 -8
  16. package/dist/types/registry/novita.d.ts +6 -0
  17. package/dist/types/registry/oauth/device-code.d.ts +25 -0
  18. package/dist/types/registry/oauth/index.d.ts +1 -25
  19. package/dist/types/registry/oauth/xai-oauth.d.ts +9 -25
  20. package/dist/types/registry/registry.d.ts +4 -1
  21. package/dist/types/registry/xai-oauth.d.ts +0 -1
  22. package/dist/types/types.d.ts +26 -3
  23. package/package.json +4 -4
  24. package/src/auth-broker/remote-store.ts +60 -5
  25. package/src/auth-broker/server.ts +1 -0
  26. package/src/auth-broker/wire-schemas.ts +1 -0
  27. package/src/auth-gateway/server.ts +2 -2
  28. package/src/auth-storage.ts +198 -60
  29. package/src/error/flags.ts +4 -1
  30. package/src/error/rate-limit.ts +7 -1
  31. package/src/providers/amazon-bedrock.ts +1 -0
  32. package/src/providers/anthropic-messages-server.ts +18 -0
  33. package/src/providers/azure-openai-responses.ts +3 -3
  34. package/src/providers/ollama.ts +1 -1
  35. package/src/providers/openai-chat-server-schema.ts +1 -1
  36. package/src/providers/openai-chat-server.ts +8 -1
  37. package/src/providers/openai-chat-wire.ts +1 -1
  38. package/src/providers/openai-codex/request-transformer.ts +94 -41
  39. package/src/providers/openai-codex-responses.ts +475 -39
  40. package/src/providers/openai-completions.ts +38 -12
  41. package/src/providers/openai-responses-server.ts +8 -1
  42. package/src/providers/openai-responses-wire.ts +16 -7
  43. package/src/providers/openai-responses.ts +18 -5
  44. package/src/providers/openai-shared.ts +125 -13
  45. package/src/registry/novita.ts +22 -0
  46. package/src/registry/oauth/__tests__/xai-oauth.test.ts +191 -80
  47. package/src/registry/oauth/device-code.ts +92 -0
  48. package/src/registry/oauth/index.ts +1 -91
  49. package/src/registry/oauth/xai-oauth.ts +231 -191
  50. package/src/registry/registry.ts +2 -0
  51. package/src/registry/xai-oauth.ts +0 -1
  52. package/src/stream.ts +7 -1
  53. package/src/types.ts +29 -3
@@ -209,7 +209,7 @@ export const openaiChatRequestSchema = type({
209
209
  "frequency_penalty?": "number",
210
210
  "logit_bias?": type({ "[string]": "number" }),
211
211
  "user?": "string",
212
- "reasoning_effort?": "'minimal' | 'low' | 'medium' | 'high' | 'xhigh'",
212
+ "reasoning_effort?": "'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'",
213
213
  "parallel_tool_calls?": "boolean",
214
214
  "service_tier?": "'auto' | 'default' | 'flex' | 'scale' | 'priority'",
215
215
  "metadata?": type({ "[string]": "unknown" }),
@@ -35,7 +35,14 @@ export type { ParsedRequest };
35
35
  type ReasoningEffort = NonNullable<ParsedRequest["options"]["reasoning"]>;
36
36
 
37
37
  function isReasoningEffort(value: unknown): value is ReasoningEffort {
38
- return value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh";
38
+ return (
39
+ value === "minimal" ||
40
+ value === "low" ||
41
+ value === "medium" ||
42
+ value === "high" ||
43
+ value === "xhigh" ||
44
+ value === "max"
45
+ );
39
46
  }
40
47
 
41
48
  function isServiceTier(value: unknown): value is ServiceTier {
@@ -116,7 +116,7 @@ export type Metadata = {
116
116
  };
117
117
 
118
118
  /** Constrains effort on reasoning for reasoning models. */
119
- export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | null;
119
+ export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | null;
120
120
 
121
121
  /** JSON object response format (older JSON mode). */
122
122
  export interface ResponseFormatJSONObject {
@@ -8,7 +8,7 @@ import { mapOpenAIReasoningEffort } from "../openai-shared";
8
8
  export type CodexReasoningContext = "auto" | "current_turn" | "all_turns";
9
9
 
10
10
  /** User-facing effort levels accepted by Codex request options. */
11
- type CodexCallerEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
11
+ type CodexCallerEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
12
12
 
13
13
  /** Caller literal → catalog `Effort` bridge (the enum is nominal). */
14
14
  const EFFORT_BY_NAME: Record<CodexCallerEffort, Effort> = {
@@ -17,23 +17,31 @@ const EFFORT_BY_NAME: Record<CodexCallerEffort, Effort> = {
17
17
  medium: Effort.Medium,
18
18
  high: Effort.High,
19
19
  xhigh: Effort.XHigh,
20
+ max: Effort.Max,
20
21
  };
21
22
 
22
23
  export interface ReasoningConfig {
23
24
  effort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
24
25
  summary?: "auto" | "concise" | "detailed";
25
26
  context?: CodexReasoningContext;
27
+ /** Pro reasoning serving mode (gpt-5.6+ catalog pro aliases). */
28
+ mode?: "pro";
26
29
  }
27
30
 
28
31
  export interface CodexRequestOptions {
29
- /** User-facing effort; the wire-only `max` tier is reached via the model's effort map. */
32
+ /** User-facing effort; maps 1:1 onto the wire tier of the same name. */
30
33
  reasoningEffort?: CodexCallerEffort | "none";
31
34
  reasoningSummary?: ReasoningConfig["summary"] | null;
32
35
  /** Explicit `reasoning.context` override; defaults to `all_turns` when unset. The `all_turns` value is gated to gpt-5.4+ Codex models — older ids reject it, so it is suppressed and `context` omitted. */
33
36
  reasoningContext?: CodexReasoningContext;
34
37
  textVerbosity?: "low" | "medium" | "high";
35
38
  include?: string[];
36
- /** Responses Lite transport contract: strips image detail and disables parallel tool calling, mirroring codex-rs. */
39
+ /**
40
+ * Responses Lite transport override; defaults to the model's
41
+ * `useResponsesLite`. Lite moves instructions/tools into input items,
42
+ * strips image detail, and disables parallel tool calling (codex-rs
43
+ * `use_responses_lite`).
44
+ */
37
45
  responsesLite?: boolean;
38
46
  }
39
47
 
@@ -46,6 +54,8 @@ export interface InputItem {
46
54
  name?: string;
47
55
  output?: unknown;
48
56
  arguments?: unknown;
57
+ /** `additional_tools` developer item payload (Responses Lite). */
58
+ tools?: unknown;
49
59
  }
50
60
 
51
61
  export interface RequestBody {
@@ -56,6 +66,8 @@ export interface RequestBody {
56
66
  input?: InputItem[];
57
67
  tools?: unknown;
58
68
  tool_choice?: unknown;
69
+ /** Concurrent reasoning-summary delivery (codex-rs `StreamOptions`). */
70
+ stream_options?: { reasoning_summary_delivery: "sequential_cutoff" };
59
71
  // Sampling controls (temperature/top_p/top_k/min_p/presence_penalty/
60
72
  // repetition_penalty/frequency_penalty/stop) are intentionally absent: the
61
73
  // Codex backend rejects every one with a 400 `Unsupported parameter`, so
@@ -74,29 +86,22 @@ export interface RequestBody {
74
86
  [key: string]: unknown;
75
87
  }
76
88
 
77
- function containsInputImage(value: unknown): boolean {
78
- if (!value || typeof value !== "object") return false;
79
- if ((value as { type?: unknown }).type === "input_image") return true;
80
- if (Array.isArray(value)) {
81
- for (const item of value) {
82
- if (containsInputImage(item)) return true;
83
- }
84
- return false;
85
- }
86
- for (const item of Object.values(value)) {
87
- if (containsInputImage(item)) return true;
88
- }
89
- return false;
90
- }
91
-
92
- /** Returns whether a Codex request can use the text-only Responses Lite transport. */
93
- export function shouldUseCodexResponsesLite(body: RequestBody, requested: boolean | undefined): boolean {
94
- return requested === true && !containsInputImage(body.input);
89
+ /**
90
+ * Resolve whether a Codex request uses the Responses Lite transport: an
91
+ * explicit option wins, otherwise the model's catalog flag (codex-rs
92
+ * `model_info.use_responses_lite`) decides.
93
+ */
94
+ export function resolveCodexResponsesLite(
95
+ model: Model<"openai-codex-responses">,
96
+ requested: boolean | undefined,
97
+ ): boolean {
98
+ return requested ?? model.useResponsesLite === true;
95
99
  }
96
100
 
97
101
  /**
98
102
  * Clamp a user-facing effort to the model's ladder, then remap to the wire
99
- * tier (e.g. GPT-5.6's shifted five-tier scale sends `max` for user `xhigh`).
103
+ * tier. User efforts map 1:1 onto wire tiers; the effort map only covers
104
+ * host quirks where a wire tier genuinely does not exist (e.g. `minimal→none`).
100
105
  * A mapped value outside the Codex wire vocabulary is a broken compat/model
101
106
  * effort map — fail loudly rather than silently sending a different tier.
102
107
  */
@@ -240,24 +245,62 @@ function repairToolCallPairs(input: InputItem[]): InputItem[] {
240
245
  * `detail` from every input image (message content and tool outputs) before
241
246
  * sending, letting the server choose.
242
247
  */
243
- function stripImageDetails(input: InputItem[]): void {
248
+ function stripImageDetails(input: unknown[]): void {
244
249
  for (const item of input) {
245
- for (const collection of [item.content, item.output]) {
250
+ if (!item || typeof item !== "object") continue;
251
+ const content = "content" in item ? item.content : undefined;
252
+ const output = "output" in item ? item.output : undefined;
253
+ for (const collection of [content, output]) {
246
254
  if (!Array.isArray(collection)) continue;
247
255
  for (const part of collection) {
248
- if (
249
- part &&
250
- typeof part === "object" &&
251
- (part as { type?: unknown }).type === "input_image" &&
252
- "detail" in part
253
- ) {
254
- part.detail = undefined;
255
- }
256
+ if (!part || typeof part !== "object") continue;
257
+ if (!("type" in part) || part.type !== "input_image") continue;
258
+ if ("detail" in part) part.detail = undefined;
256
259
  }
257
260
  }
258
261
  }
259
262
  }
260
263
 
264
+ /**
265
+ * Structural view of a Responses-style body mutated by the Lite rewrite.
266
+ * Loose (`unknown`) property types let the turn transformer (`RequestBody`)
267
+ * and the agent's remote-compaction payloads reuse one shaper.
268
+ */
269
+ export interface CodexLiteShapedBody {
270
+ instructions?: unknown;
271
+ tools?: unknown;
272
+ input?: unknown;
273
+ parallel_tool_calls?: unknown;
274
+ }
275
+
276
+ /**
277
+ * Applies the Responses Lite body contract in place (codex-rs
278
+ * `build_responses_request` with `use_responses_lite`): strips pinned image
279
+ * detail, forces parallel tool calling off, moves tools into a leading
280
+ * `additional_tools` developer item and the base instructions into a
281
+ * developer message, then omits top-level `instructions`/`tools`. Shared by
282
+ * normal turns and both remote-compaction paths — codex-rs routes
283
+ * `/responses/compact` through the same builder.
284
+ */
285
+ export function applyCodexResponsesLiteShape(body: CodexLiteShapedBody): void {
286
+ const input = Array.isArray(body.input) ? body.input : [];
287
+ stripImageDetails(input);
288
+ body.parallel_tool_calls = false;
289
+ const prefix: InputItem[] = [
290
+ { type: "additional_tools", role: "developer", tools: Array.isArray(body.tools) ? body.tools : [] },
291
+ ];
292
+ if (typeof body.instructions === "string" && body.instructions.length > 0) {
293
+ prefix.push({
294
+ type: "message",
295
+ role: "developer",
296
+ content: [{ type: "input_text", text: body.instructions }],
297
+ });
298
+ }
299
+ body.input = [...prefix, ...input];
300
+ delete body.instructions;
301
+ delete body.tools;
302
+ }
303
+
261
304
  export async function transformRequestBody(
262
305
  body: RequestBody,
263
306
  model: Model<"openai-codex-responses">,
@@ -331,16 +374,9 @@ export async function transformRequestBody(
331
374
  }
332
375
  }
333
376
 
334
- const responsesLite = shouldUseCodexResponsesLite(body, options.responsesLite);
377
+ const responsesLite = resolveCodexResponsesLite(model, options.responsesLite);
335
378
  if (responsesLite) {
336
- if (Array.isArray(body.input)) {
337
- stripImageDetails(body.input);
338
- }
339
- // Responses Lite does not support parallel tool calling; codex-rs forces
340
- // it off (`prompt.parallel_tool_calls && !use_responses_lite`).
341
- if (body.tools !== undefined) {
342
- body.parallel_tool_calls = false;
343
- }
379
+ applyCodexResponsesLiteShape(body);
344
380
  }
345
381
 
346
382
  if (options.reasoningEffort !== undefined) {
@@ -367,6 +403,23 @@ export async function transformRequestBody(
367
403
  } else {
368
404
  delete body.reasoning;
369
405
  }
406
+ // Catalog pro aliases (`gpt-5.6-*-pro`): applied after the effort branch so
407
+ // the mode is sent even when no effort is set (the branch above deletes
408
+ // `body.reasoning` in that case) — mode and effort are independent fields.
409
+ if (model.reasoningMode) {
410
+ body.reasoning = { ...body.reasoning, mode: model.reasoningMode };
411
+ }
412
+
413
+ // Concurrent reasoning summaries (codex-rs `concurrent_reasoning_summaries`
414
+ // feature): `sequential_cutoff` lets the server stream output without
415
+ // blocking on summary generation. Only meaningful when a summary is
416
+ // requested; codex-rs additionally gates on its OpenAI provider check,
417
+ // which is inherent here.
418
+ if (body.reasoning?.summary !== undefined) {
419
+ body.stream_options = { reasoning_summary_delivery: "sequential_cutoff" };
420
+ } else {
421
+ delete body.stream_options;
422
+ }
370
423
 
371
424
  body.text = {
372
425
  ...body.text,