@oh-my-pi/pi-ai 18.2.0 → 18.2.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 (75) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +2 -0
  3. package/dist/types/auth/sqlite-credential-store.d.ts +2 -1
  4. package/dist/types/auth-broker/remote-store.d.ts +17 -0
  5. package/dist/types/auth-gateway/index.d.ts +1 -0
  6. package/dist/types/auth-gateway/session-state.d.ts +118 -0
  7. package/dist/types/auth-storage.d.ts +17 -0
  8. package/dist/types/error/body-error.d.ts +15 -0
  9. package/dist/types/error/flags.d.ts +16 -0
  10. package/dist/types/error/index.d.ts +1 -0
  11. package/dist/types/index.d.ts +1 -0
  12. package/dist/types/oneshot-retry.d.ts +6 -0
  13. package/dist/types/provider-session-state.d.ts +46 -0
  14. package/dist/types/providers/amazon-bedrock.d.ts +3 -0
  15. package/dist/types/providers/aws-sigv4.d.ts +12 -0
  16. package/dist/types/providers/openai-codex/request-transformer.d.ts +27 -0
  17. package/dist/types/providers/openai-responses.d.ts +15 -0
  18. package/dist/types/providers/openai-shared.d.ts +20 -3
  19. package/dist/types/registry/oauth/perplexity.d.ts +1 -7
  20. package/dist/types/registry/oauth/types.d.ts +8 -0
  21. package/dist/types/stream.d.ts +2 -0
  22. package/dist/types/types.d.ts +3 -1
  23. package/dist/types/usage/openai-codex.d.ts +3 -1
  24. package/dist/types/usage.d.ts +11 -1
  25. package/dist/types/utils/block-symbols.d.ts +36 -0
  26. package/dist/types/utils/openai-http.d.ts +2 -0
  27. package/dist/types/utils/retry-after.d.ts +2 -0
  28. package/dist/types/utils/schema/wire.d.ts +4 -5
  29. package/dist/types/utils.d.ts +9 -0
  30. package/package.json +6 -6
  31. package/src/auth/sqlite-credential-store.ts +8 -33
  32. package/src/auth-broker/remote-store.ts +73 -8
  33. package/src/auth-broker/wire-schemas.ts +1 -0
  34. package/src/auth-gateway/index.ts +1 -0
  35. package/src/auth-gateway/server.ts +186 -74
  36. package/src/auth-gateway/session-state.ts +312 -0
  37. package/src/auth-storage.ts +146 -15
  38. package/src/error/body-error.ts +310 -0
  39. package/src/error/flags.ts +63 -13
  40. package/src/error/index.ts +1 -0
  41. package/src/error/retryable.ts +2 -0
  42. package/src/index.ts +1 -0
  43. package/src/oneshot-retry.ts +13 -3
  44. package/src/provider-session-state.ts +56 -0
  45. package/src/providers/amazon-bedrock.ts +20 -3
  46. package/src/providers/anthropic-messages-server.ts +104 -23
  47. package/src/providers/anthropic-signature.ts +5 -2
  48. package/src/providers/anthropic.ts +101 -15
  49. package/src/providers/aws-sigv4.ts +16 -5
  50. package/src/providers/cursor.ts +60 -10
  51. package/src/providers/devin.ts +82 -28
  52. package/src/providers/openai-chat-server.ts +4 -0
  53. package/src/providers/openai-codex/request-transformer.ts +36 -0
  54. package/src/providers/openai-codex-responses.ts +35 -12
  55. package/src/providers/openai-completions.ts +49 -12
  56. package/src/providers/openai-reasoning-fallback.ts +6 -6
  57. package/src/providers/openai-responses-server.ts +2 -1
  58. package/src/providers/openai-responses.ts +52 -4
  59. package/src/providers/openai-shared.ts +199 -51
  60. package/src/registry/oauth/perplexity.ts +94 -28
  61. package/src/registry/oauth/types.ts +9 -0
  62. package/src/stream.ts +23 -2
  63. package/src/types.ts +3 -0
  64. package/src/usage/claude.ts +33 -0
  65. package/src/usage/google-antigravity.ts +8 -2
  66. package/src/usage/openai-codex.ts +94 -11
  67. package/src/usage.ts +8 -1
  68. package/src/utils/block-symbols.ts +57 -0
  69. package/src/utils/http-inspector.ts +20 -0
  70. package/src/utils/openai-http.ts +39 -3
  71. package/src/utils/retry-after.ts +12 -0
  72. package/src/utils/schema/normalize.ts +3 -3
  73. package/src/utils/schema/stamps.ts +33 -45
  74. package/src/utils/schema/wire.ts +9 -7
  75. package/src/utils.ts +67 -22
@@ -187,7 +187,7 @@ function collectMessageParts(error: unknown, captured: CapturedHttpErrorResponse
187
187
  * the same rejection with `Supported values are: …`, so value lists count too.
188
188
  */
189
189
  const REASONING_EFFORT_FIELD_PATTERN =
190
- /reasoning[_. ]effort|reasoning value|(?:valid|supported|allowed) (?:levels?|values?)/i;
190
+ /reasoning[_. ]?effort|reasoning value|(?:valid|supported|allowed) (?:levels?|values?)/i;
191
191
 
192
192
  function mentionsReasoningEffort(error: unknown, captured: CapturedHttpErrorResponse | undefined): boolean {
193
193
  const param = capturedStringField(captured, "param");
@@ -224,17 +224,17 @@ interface EffortRejectionSignal {
224
224
  rejectedMatches: boolean;
225
225
  }
226
226
 
227
- const EFFORT_FIELD_PATTERN = /reasoning[_. ]effort|reasoning value/i;
227
+ const EFFORT_FIELD_PATTERN = /reasoning[_. ]?effort|reasoning value/i;
228
228
  const ALLOWED_LEVELS_PATTERN = /(?:valid|supported|allowed) levels?/i;
229
229
 
230
230
  /** Fielded rejection verdicts in any word order: verdict-first, field-first, or bare mention plus verdict. */
231
231
  function messageCarriesEffortVerdict(message: string): boolean {
232
232
  return (
233
- /invalid[^\n]*(?:reasoning[_. ]effort|reasoning value)/i.test(message) ||
234
- /(?:reasoning[_. ]effort|reasoning value)[^\n]*(?:invalid|unsupported|not supported|not permitted|must be|expected|unknown|unexpected|unrecognized)/i.test(
233
+ /invalid[^\n]*(?:reasoning[_. ]?effort|reasoning value)/i.test(message) ||
234
+ /(?:reasoning[_. ]?effort|reasoning value)[^\n]*(?:invalid|unsupported|not supported|not permitted|must be|expected|unknown|unexpected|unrecognized)/i.test(
235
235
  message,
236
236
  ) ||
237
- /(?:unsupported|not supported|not permitted|unknown|unexpected|unrecognized|extra)[^\n]*(?:reasoning[_. ]effort|reasoning value)/i.test(
237
+ /(?:unsupported|not supported|not permitted|unknown|unexpected|unrecognized|extra)[^\n]*(?:reasoning[_. ]?effort|reasoning value)/i.test(
238
238
  message,
239
239
  )
240
240
  );
@@ -367,7 +367,7 @@ function nearestEnabledReasoningFallback(currentEffort: string, allowed: Set<str
367
367
  * supported`).
368
368
  */
369
369
  const TEMPLATE_KWARG_EFFORT_PATTERN =
370
- /chat_template_kwargs[^\n]{0,120}reasoning[_. ]effort|reasoning[_. ]effort[^\n]{0,120}chat_template_kwargs/i;
370
+ /chat_template_kwargs[^\n]{0,120}reasoning[_. ]?effort|reasoning[_. ]?effort[^\n]{0,120}chat_template_kwargs/i;
371
371
  const FIELD_REJECTION_PATTERN =
372
372
  /invalid|unsupported|not supported|not permitted|unknown|unexpected|unrecognized|rejected|extra input/i;
373
373
 
@@ -43,7 +43,7 @@ import {
43
43
  type OpenAIResponsesTool,
44
44
  openaiResponsesRequestSchema,
45
45
  } from "./openai-responses-server-schema";
46
- import { encodeTextSignatureV1, parseTextSignature } from "./openai-shared";
46
+ import { coerceNullMessageContentInPlace, encodeTextSignatureV1, parseTextSignature } from "./openai-shared";
47
47
 
48
48
  export type { ParsedRequest };
49
49
 
@@ -365,6 +365,7 @@ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
365
365
  // `resolvePromptCacheKey` call further down.
366
366
 
367
367
  rejectUnsupportedExplicitPromptCacheFields(body);
368
+ coerceNullMessageContentInPlace(isObj(body) ? body.input : undefined);
368
369
  const data = openaiResponsesRequestSchema(body);
369
370
  if (data instanceof type.errors) {
370
371
  throw new AIError.ValidationError(`openai-responses: ${data.summary}`);
@@ -295,6 +295,33 @@ function resetOpenAIResponsesChainState(state: OpenAIResponsesChainState): void
295
295
  state.lastPromptCacheBreakpointPolicy = undefined;
296
296
  }
297
297
 
298
+ /**
299
+ * Drop the account-bound half of every retained `openai-responses` record in
300
+ * `states`: the stateful `previous_response_id` chain baselines.
301
+ *
302
+ * Chaining stores the turn server-side under the account that created it, so a
303
+ * baseline minted by one credential is dead weight the moment the session is
304
+ * switched to a sibling account — the next delta request answers
305
+ * `Previous response not found` and burns a turn re-learning that. Everything
306
+ * else this record holds describes the *deployment*, not the account
307
+ * (strict-tools demotion, reasoning-effort fallback, native-history-replay
308
+ * warmup, the chaining circuit breaker), and is deliberately preserved:
309
+ * re-learning an endpoint's limits on every credential switch is the cost this
310
+ * state exists to avoid.
311
+ */
312
+ export function resetOpenAIResponsesAccountScopedState(states: Map<string, ProviderSessionState>): void {
313
+ for (const [key, value] of states) {
314
+ if (!key.startsWith(OPENAI_RESPONSES_PROVIDER_SESSION_STATE_PREFIX)) continue;
315
+ const state = value as OpenAIResponsesProviderSessionState;
316
+ for (const chain of state.chains.values()) {
317
+ resetOpenAIResponsesChainState(chain);
318
+ // The stale-failure counter tallies the previous account's 404s; a
319
+ // fresh account must not inherit a tripped circuit breaker.
320
+ chain.staleFailures = 0;
321
+ }
322
+ }
323
+ }
324
+
298
325
  interface OpenAIResponsesChainedParams {
299
326
  params: OpenAIResponsesSamplingParams;
300
327
  /** Set iff the params carry previous_response_id (delta request). */
@@ -422,6 +449,7 @@ const streamOpenAIResponsesOnce = (
422
449
  let rawRequestDump: RawHttpRequestDump | undefined;
423
450
  let chainState: OpenAIResponsesChainState | undefined;
424
451
  let sentPreviousResponseId: string | undefined;
452
+ let lastSubmittedRequestWasFullReplay: boolean | undefined;
425
453
  const abortTracker = createAbortSourceTracker(options?.signal);
426
454
  const firstEventTimeoutAbortError = new AIError.StreamTimeoutError(OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE);
427
455
  const { requestAbortController, requestSignal } = abortTracker;
@@ -552,6 +580,7 @@ const streamOpenAIResponsesOnce = (
552
580
  typeof requestParams.model === "string" ? requestParams.model : model.id,
553
581
  );
554
582
  activeRequestParams = requestParams;
583
+ lastSubmittedRequestWasFullReplay = requestParams.previous_response_id === undefined;
555
584
  let requestTimeout: NodeJS.Timeout | undefined;
556
585
  if (requestTimeoutMs !== undefined) {
557
586
  requestTimeout = setTimeout(
@@ -576,6 +605,9 @@ const streamOpenAIResponsesOnce = (
576
605
  copilotCacheKey,
577
606
  copilotCacheSnapshot,
578
607
  ),
608
+ shouldRetryResponse: (response, bodyText) =>
609
+ !AIError.isRequestBodyReadTimeout(response.status, bodyText) ||
610
+ lastSubmittedRequestWasFullReplay !== true,
579
611
  // Transient 408/429/5xx get Retry-After-aware transport
580
612
  // retries; the first-event watchdog aborts `requestSignal`,
581
613
  // so retries cannot extend the caller's deadline.
@@ -878,7 +910,7 @@ const streamOpenAIResponsesOnce = (
878
910
  : activeParams,
879
911
  );
880
912
  chainState.lastPromptCacheBreakpointPolicy = promptCacheBreakpointPolicy;
881
- if (output.responseId) {
913
+ if (output.responseId && replayableResponseItems.length === nativeOutputItems.length) {
882
914
  chainState.lastResponseId = output.responseId;
883
915
  chainState.lastResponseItems = replayableResponseItems;
884
916
  chainState.canAppend = true;
@@ -886,8 +918,12 @@ const streamOpenAIResponsesOnce = (
886
918
  // full-context success must not mask categorical rejection.
887
919
  if (sentPreviousResponseId) chainState.staleFailures = 0;
888
920
  } else {
889
- // Without a response id the append baseline cannot be trusted.
921
+ // No response id, or replay sanitization dropped an item the server
922
+ // still holds. Sanitization is 1:1-or-fewer, so either case makes the
923
+ // append baseline untrustworthy; next turn must replay in full.
890
924
  chainState.canAppend = false;
925
+ chainState.lastResponseId = undefined;
926
+ chainState.lastResponseItems = undefined;
891
927
  }
892
928
  }
893
929
  } else if (chainState) {
@@ -926,6 +962,9 @@ const streamOpenAIResponsesOnce = (
926
962
  output.errorStatus = result.status;
927
963
  output.errorId = result.id;
928
964
  output.errorMessage = result.message;
965
+ if (AIError.isRequestBodyReadTimeout(result.status, result.message) && lastSubmittedRequestWasFullReplay) {
966
+ output.requestBodyReadTimeoutFullReplay = true;
967
+ }
929
968
  // Some providers via OpenRouter include extra details here.
930
969
  const rawMetadata = (error as { error?: { metadata?: { raw?: string } } })?.error?.metadata?.raw;
931
970
  if (rawMetadata) output.errorMessage += `\n${rawMetadata}`;
@@ -1153,6 +1192,11 @@ export function buildParams(
1153
1192
  });
1154
1193
  const strictResponsesPairing = policy.tools.strictResponsesPairing;
1155
1194
  const shouldReplayNativeHistory = providerSessionState?.nativeHistoryReplayWarmed ?? true;
1195
+ // Filtering native reasoning must not be undone by reconstruction when the
1196
+ // target also rejects synthetic items (Muse on OpenRouter). Unfiltered targets
1197
+ // retain required text/placeholder replay, including DeepSeek's #10690 fallback.
1198
+ const canReconstructReasoningReplay =
1199
+ !policy.reasoning.filterReasoningHistory || policy.reasoning.allowsSyntheticReasoningContentForToolCalls;
1156
1200
  const messages = buildResponsesInput({
1157
1201
  model,
1158
1202
  context,
@@ -1164,9 +1208,13 @@ export function buildParams(
1164
1208
  },
1165
1209
  includeThinkingSignatures: shouldReplayNativeHistory && !policy.reasoning.filterReasoningHistory,
1166
1210
  requiresReasoningReplayForAllTurns:
1167
- policy.reasoning.enabled && policy.reasoning.requiresReasoningContentForAllAssistantTurns,
1211
+ policy.reasoning.enabled &&
1212
+ policy.reasoning.requiresReasoningContentForAllAssistantTurns &&
1213
+ canReconstructReasoningReplay,
1168
1214
  requiresReasoningReplayForToolCalls:
1169
- policy.reasoning.enabled && policy.reasoning.requiresReasoningContentForToolCalls,
1215
+ policy.reasoning.enabled &&
1216
+ policy.reasoning.requiresReasoningContentForToolCalls &&
1217
+ canReconstructReasoningReplay,
1170
1218
  repairOrphanOutputs: true,
1171
1219
  });
1172
1220