@linxiraos/pi-ai 1.1.26 → 1.2.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 (237) hide show
  1. package/CHANGELOG.md +9 -8
  2. package/README.md +15 -0
  3. package/dist/types/auth/affinity.d.ts +33 -6
  4. package/dist/types/auth/cascade.d.ts +44 -7
  5. package/dist/types/auth/oauth.d.ts +9 -6
  6. package/dist/types/auth/policy.d.ts +1 -0
  7. package/dist/types/auth/refresh.d.ts +1 -2
  8. package/dist/types/auth/select.d.ts +6 -4
  9. package/dist/types/auth/sqlite-credential-store.d.ts +21 -1
  10. package/dist/types/auth/store.d.ts +8 -2
  11. package/dist/types/auth/types.d.ts +99 -15
  12. package/dist/types/auth-broker/client.d.ts +3 -3
  13. package/dist/types/auth-broker/discover.d.ts +1 -1
  14. package/dist/types/auth-broker/refresher.d.ts +6 -1
  15. package/dist/types/auth-broker/remote-store.d.ts +6 -4
  16. package/dist/types/auth-broker/server.d.ts +2 -0
  17. package/dist/types/auth-broker/snapshot-cache.d.ts +12 -0
  18. package/dist/types/auth-gateway/dispatch.d.ts +13 -5
  19. package/dist/types/auth-gateway/http.d.ts +0 -2
  20. package/dist/types/auth-gateway/index.d.ts +1 -0
  21. package/dist/types/auth-gateway/routes/embeddings.d.ts +2 -2
  22. package/dist/types/auth-gateway/routes/images.d.ts +3 -3
  23. package/dist/types/auth-gateway/routes/rerank.d.ts +2 -2
  24. package/dist/types/auth-gateway/routes/speech.d.ts +2 -2
  25. package/dist/types/auth-gateway/routes/systemone.d.ts +2 -2
  26. package/dist/types/auth-gateway/routes/transcriptions.d.ts +2 -2
  27. package/dist/types/auth-gateway/routes/video.d.ts +4 -4
  28. package/dist/types/auth-gateway/server.d.ts +22 -5
  29. package/dist/types/auth-gateway/session-state.d.ts +6 -0
  30. package/dist/types/auth-gateway/stdio.d.ts +12 -0
  31. package/dist/types/auth-storage.d.ts +7 -6
  32. package/dist/types/dialect/terminator-wait.d.ts +19 -0
  33. package/dist/types/env-api-key.d.ts +21 -0
  34. package/dist/types/error/auth.d.ts +10 -0
  35. package/dist/types/error/flags.d.ts +38 -0
  36. package/dist/types/images/index.d.ts +2 -3
  37. package/dist/types/images/shared.d.ts +6 -0
  38. package/dist/types/index.d.ts +2 -1
  39. package/dist/types/judgment/index.d.ts +2 -1
  40. package/dist/types/judgment/native.d.ts +80 -0
  41. package/dist/types/judgment/openai-decisions.d.ts +3 -0
  42. package/dist/types/judgment/question-file.d.ts +52 -0
  43. package/dist/types/judgment/systemone.d.ts +7 -0
  44. package/dist/types/judgment/types.d.ts +2 -2
  45. package/dist/types/judgment/wire.d.ts +36 -0
  46. package/dist/types/provider-details.d.ts +1 -1
  47. package/dist/types/provider-session-state.d.ts +2 -2
  48. package/dist/types/providers/anthropic-client.d.ts +10 -0
  49. package/dist/types/providers/anthropic-state.d.ts +2 -0
  50. package/dist/types/providers/anthropic.d.ts +38 -3
  51. package/dist/types/providers/aws-credentials.d.ts +4 -8
  52. package/dist/types/providers/connect-error-detail.d.ts +9 -0
  53. package/dist/types/providers/connect-frame.d.ts +44 -0
  54. package/dist/types/providers/cursor/interaction-query.d.ts +0 -2
  55. package/dist/types/providers/cursor.d.ts +8 -8
  56. package/dist/types/providers/google-auth.d.ts +4 -1
  57. package/dist/types/providers/google-shared.d.ts +24 -4
  58. package/dist/types/providers/inference-headers.d.ts +6 -0
  59. package/dist/types/providers/mock.d.ts +3 -1
  60. package/dist/types/providers/openai-codex/request-transformer.d.ts +2 -0
  61. package/dist/types/providers/openai-codex-responses.d.ts +1 -1
  62. package/dist/types/providers/openai-configuration-update.d.ts +20 -11
  63. package/dist/types/providers/openai-responses-wire.d.ts +1 -55
  64. package/dist/types/providers/openai-responses.d.ts +40 -3
  65. package/dist/types/providers/openai-shared.d.ts +30 -5
  66. package/dist/types/providers/pi-native-client.d.ts +1 -1
  67. package/dist/types/providers/transform-messages.d.ts +25 -0
  68. package/dist/types/providers/vision-guard.d.ts +1 -1
  69. package/dist/types/registry/oauth/cloudflare-workers-ai.d.ts +3 -0
  70. package/dist/types/registry/oauth/cursor.d.ts +10 -2
  71. package/dist/types/registry/oauth/index.d.ts +2 -0
  72. package/dist/types/registry/oauth/snowflake.d.ts +3 -0
  73. package/dist/types/registry/snowflake.d.ts +4 -0
  74. package/dist/types/stream.d.ts +0 -21
  75. package/dist/types/transcription/types.d.ts +2 -1
  76. package/dist/types/types.d.ts +116 -14
  77. package/dist/types/usage.d.ts +2 -0
  78. package/dist/types/utils/anthropic-auth.d.ts +21 -7
  79. package/dist/types/utils/aws-profile.d.ts +5 -0
  80. package/dist/types/utils/block-symbols.d.ts +14 -0
  81. package/dist/types/utils/dsml-leak.d.ts +29 -0
  82. package/dist/types/utils/glyph-codec.d.ts +1 -1
  83. package/dist/types/utils/idle-iterator.d.ts +11 -0
  84. package/dist/types/utils/leaked-thinking-stream.d.ts +1 -1
  85. package/dist/types/utils/resolve-peer.d.ts +2 -0
  86. package/dist/types/utils/retry-wait.d.ts +9 -0
  87. package/dist/types/utils/schema/normalize.d.ts +8 -3
  88. package/dist/types/utils/stream-markup-healing.d.ts +4 -4
  89. package/dist/types/utils/tool-call-arguments.d.ts +8 -0
  90. package/dist/types/utils.d.ts +6 -0
  91. package/package.json +6 -6
  92. package/src/auth/affinity.ts +130 -37
  93. package/src/auth/blocks.ts +94 -15
  94. package/src/auth/cascade.ts +112 -17
  95. package/src/auth/health.ts +3 -2
  96. package/src/auth/oauth.ts +25 -10
  97. package/src/auth/policy.ts +10 -1
  98. package/src/auth/pool.ts +0 -5
  99. package/src/auth/refresh.ts +17 -12
  100. package/src/auth/resets.ts +1 -1
  101. package/src/auth/rotation.ts +8 -2
  102. package/src/auth/select.ts +102 -30
  103. package/src/auth/sqlite-credential-store.ts +119 -49
  104. package/src/auth/store.ts +8 -2
  105. package/src/auth/types.ts +108 -17
  106. package/src/auth/usage-cache.ts +16 -5
  107. package/src/auth/usage.ts +2 -2
  108. package/src/auth-broker/client.ts +7 -4
  109. package/src/auth-broker/discover.ts +6 -5
  110. package/src/auth-broker/refresher.ts +8 -1
  111. package/src/auth-broker/remote-store.ts +200 -62
  112. package/src/auth-broker/server.ts +316 -130
  113. package/src/auth-broker/snapshot-cache.ts +142 -2
  114. package/src/auth-gateway/dispatch.ts +15 -7
  115. package/src/auth-gateway/http.ts +0 -8
  116. package/src/auth-gateway/index.ts +1 -0
  117. package/src/auth-gateway/routes/embeddings.ts +2 -2
  118. package/src/auth-gateway/routes/images.ts +4 -4
  119. package/src/auth-gateway/routes/rerank.ts +2 -2
  120. package/src/auth-gateway/routes/speech.ts +2 -2
  121. package/src/auth-gateway/routes/systemone.ts +22 -14
  122. package/src/auth-gateway/routes/transcriptions.ts +2 -2
  123. package/src/auth-gateway/routes/video.ts +7 -7
  124. package/src/auth-gateway/server.ts +175 -134
  125. package/src/auth-gateway/session-state.ts +51 -9
  126. package/src/auth-gateway/stdio.ts +132 -0
  127. package/src/auth-storage.ts +23 -9
  128. package/src/dialect/deepseek.ts +67 -8
  129. package/src/dialect/gemini.ts +8 -2
  130. package/src/dialect/hermes.ts +8 -2
  131. package/src/dialect/kimi.ts +8 -2
  132. package/src/dialect/qwen-xml.ts +8 -2
  133. package/src/dialect/qwen3.ts +8 -2
  134. package/src/dialect/terminator-wait.ts +42 -0
  135. package/src/env-api-key.ts +78 -0
  136. package/src/error/auth.ts +20 -0
  137. package/src/error/flags.ts +143 -24
  138. package/src/error/rate-limit.ts +8 -6
  139. package/src/images/google-antigravity.ts +0 -1
  140. package/src/images/index.ts +3 -17
  141. package/src/images/openai-hosted.ts +13 -4
  142. package/src/images/openai-images.ts +19 -13
  143. package/src/images/shared.ts +10 -2
  144. package/src/index.ts +2 -1
  145. package/src/judgment/index.ts +2 -1
  146. package/src/judgment/native.ts +333 -0
  147. package/src/judgment/openai-decisions-predicate.md +9 -0
  148. package/src/judgment/openai-decisions.ts +129 -0
  149. package/src/judgment/question-file.ts +90 -0
  150. package/src/judgment/systemone.ts +65 -0
  151. package/src/judgment/text.ts +9 -1
  152. package/src/judgment/types.ts +2 -2
  153. package/src/judgment/wire.ts +42 -0
  154. package/src/provider-details.ts +1 -1
  155. package/src/provider-session-state.ts +2 -2
  156. package/src/providers/amazon-bedrock.ts +19 -8
  157. package/src/providers/anthropic-client.ts +16 -1
  158. package/src/providers/anthropic-state.ts +6 -0
  159. package/src/providers/anthropic.ts +962 -238
  160. package/src/providers/apple-foundation-models.ts +10 -4
  161. package/src/providers/aws-credentials.ts +65 -5
  162. package/src/providers/azure-openai-responses.ts +1 -1
  163. package/src/providers/connect-error-detail.ts +21 -0
  164. package/src/providers/connect-frame.ts +107 -0
  165. package/src/providers/cursor/interaction-query.ts +1 -9
  166. package/src/providers/cursor.ts +590 -133
  167. package/src/providers/devin.ts +68 -58
  168. package/src/providers/factory-droid/gemini.ts +38 -51
  169. package/src/providers/factory-droid.ts +2 -0
  170. package/src/providers/gitlab-duo-workflow.ts +5 -2
  171. package/src/providers/gitlab-duo.ts +1 -0
  172. package/src/providers/google-auth.ts +10 -3
  173. package/src/providers/google-gemini-cli.ts +4 -0
  174. package/src/providers/google-shared.ts +134 -105
  175. package/src/providers/google-vertex.ts +0 -1
  176. package/src/providers/google.ts +1 -1
  177. package/src/providers/inference-headers.ts +19 -0
  178. package/src/providers/mock.ts +4 -0
  179. package/src/providers/ollama.ts +1 -1
  180. package/src/providers/openai-anthropic-shim.ts +1 -0
  181. package/src/providers/openai-codex/request-transformer.ts +16 -11
  182. package/src/providers/openai-codex-responses.ts +201 -76
  183. package/src/providers/openai-completions.ts +12 -2
  184. package/src/providers/openai-configuration-update.ts +54 -15
  185. package/src/providers/openai-responses-wire.ts +1 -55
  186. package/src/providers/openai-responses.ts +437 -33
  187. package/src/providers/openai-shared.ts +267 -72
  188. package/src/providers/pi-native-client.ts +2 -2
  189. package/src/providers/pi-native-server.ts +1 -0
  190. package/src/providers/systemone-server.ts +1 -1
  191. package/src/providers/transcriptions-server.ts +1 -1
  192. package/src/providers/transform-messages.ts +240 -84
  193. package/src/providers/vision-guard.ts +1 -1
  194. package/src/registry/aws.ts +51 -14
  195. package/src/registry/cloudflare-ai-gateway.ts +26 -3
  196. package/src/registry/hooks/api-key.ts +1 -0
  197. package/src/registry/hooks/custom.ts +2 -0
  198. package/src/registry/hooks/env.ts +2 -5
  199. package/src/registry/oauth/callback-server.ts +6 -1
  200. package/src/registry/oauth/cloudflare-ai-gateway.ts +2 -2
  201. package/src/registry/oauth/cloudflare-workers-ai.ts +32 -0
  202. package/src/registry/oauth/cursor.ts +80 -17
  203. package/src/registry/oauth/index.ts +5 -0
  204. package/src/registry/oauth/snowflake.ts +183 -0
  205. package/src/registry/registry.ts +2 -0
  206. package/src/registry/snowflake.ts +149 -0
  207. package/src/stream.ts +85 -107
  208. package/src/transcription/types.ts +2 -1
  209. package/src/types.ts +142 -15
  210. package/src/usage/claude.ts +19 -12
  211. package/src/usage/commandcode.ts +1 -1
  212. package/src/usage/cursor.ts +14 -25
  213. package/src/usage/github-copilot.ts +1 -1
  214. package/src/usage/kimi.ts +1 -1
  215. package/src/usage/minimax-code.ts +8 -1
  216. package/src/usage.ts +8 -0
  217. package/src/utils/anthropic-auth.ts +34 -10
  218. package/src/utils/aws-profile.ts +10 -4
  219. package/src/utils/block-symbols.ts +17 -0
  220. package/src/utils/dsml-leak.ts +109 -0
  221. package/src/utils/empty-completion-retry.ts +2 -3
  222. package/src/utils/glyph-codec.ts +200 -7
  223. package/src/utils/idle-iterator.ts +80 -35
  224. package/src/utils/leaked-thinking-stream.ts +47 -25
  225. package/src/utils/resolve-peer.ts +7 -0
  226. package/src/utils/retry-wait.ts +19 -0
  227. package/src/utils/schema/CONSTRAINTS.md +9 -7
  228. package/src/utils/schema/fields.ts +20 -0
  229. package/src/utils/schema/normalize.ts +65 -23
  230. package/src/utils/schema/wire.ts +3 -3
  231. package/src/utils/stream-markup-healing.ts +4 -4
  232. package/src/utils/thinking-loop.ts +36 -13
  233. package/src/utils/tool-call-arguments.ts +16 -0
  234. package/src/utils/transport-fetch.ts +1 -1
  235. package/src/utils.ts +41 -10
  236. package/dist/types/judgment/typesafe.d.ts +0 -49
  237. package/src/judgment/typesafe.ts +0 -183
package/src/stream.ts CHANGED
@@ -17,12 +17,12 @@ import {
17
17
  requireSupportedEffort,
18
18
  resolveWireModelId,
19
19
  } from "@linxiraos/pi-catalog/model-thinking";
20
- import { providerEntries } from "@linxiraos/pi-catalog/compat/providers";
21
20
  import { CODEX_BASE_URL } from "@linxiraos/pi-catalog/wire/codex";
22
- import { $env, $pickenv, getProviderInFlightRoot, isEnoent, logger, untilAborted } from "@linxiraos/pi-utils";
21
+ import { $env, getProviderInFlightRoot, isEnoent, logger, untilAborted } from "@linxiraos/pi-utils";
23
22
  import { getCustomApi } from "./api-registry";
24
23
  import { createAuthRetryKeyState, isApiKeyResolver, resolvedApiKeyBearer, resolveNextAuthRetryKey } from "./auth-retry";
25
24
  import type { OAuthRequestIdentity } from "./auth/types";
25
+ import { getEnvApiKey } from "./env-api-key";
26
26
  import * as AIError from "./error";
27
27
  import { ProviderHttpError } from "./error";
28
28
  import { isConcurrencyCapExclusion, isUsageLimitOutcome } from "./error/rate-limit";
@@ -58,7 +58,7 @@ import {
58
58
  streamOpenAICompletions,
59
59
  streamOpenAIResponses,
60
60
  } from "./providers/register-builtins";
61
- import { getProviderDefinition, PROVIDER_REGISTRY } from "./registry";
61
+ import { getProviderDefinition } from "./registry";
62
62
  import type {
63
63
  Api,
64
64
  AssistantMessage,
@@ -715,14 +715,14 @@ export const __providerInFlightForTesting = {
715
715
  function withProviderInFlightLimit<TOptions extends Pick<StreamOptions, "signal" | "maxInFlightRequests">>(
716
716
  model: Model<Api>,
717
717
  options: TOptions | undefined,
718
- dispatch: () => AssistantMessageEventStream,
718
+ dispatch: (limited: boolean) => AssistantMessageEventStream,
719
719
  ): AssistantMessageEventStream {
720
720
  // Leaked-thinking healing folds in here — the one shared provider-dispatch
721
721
  // chokepoint — so the loop guard (which wraps this) sees healed events and all
722
722
  // provider exits are covered by one wrap. Official first-party providers are
723
723
  // exempt (see `healLeakedThinking`); healing is otherwise idempotent.
724
724
  const limit = resolveProviderInFlightLimit(model.provider, options);
725
- if (limit === undefined) return healLeakedThinking(model, dispatch());
725
+ if (limit === undefined) return healLeakedThinking(model, dispatch(false));
726
726
 
727
727
  const outer = new AssistantMessageEventStream();
728
728
  void (async () => {
@@ -757,7 +757,7 @@ function withProviderInFlightLimit<TOptions extends Pick<StreamOptions, "signal"
757
757
  if (options?.signal?.aborted) {
758
758
  throw options.signal.reason ?? new AIError.AbortError("Provider request aborted before dispatch");
759
759
  }
760
- const inner = healLeakedThinking(model, dispatch());
760
+ const inner = healLeakedThinking(model, dispatch(true));
761
761
  let terminalEvent: AssistantMessageEvent | undefined;
762
762
  for await (const event of inner) {
763
763
  if (event.type === "done" || event.type === "error") {
@@ -869,75 +869,6 @@ function resolveVertexRequest(input: string | URL | Request): string | URL | Req
869
869
  return rewriteUrl(input);
870
870
  }
871
871
 
872
- type KeyResolver = string | (() => string | undefined);
873
-
874
- const LEGACY_ENV_KEYS: Record<string, KeyResolver> = {
875
- // Non-provider / search-tool keys and API-name keys not modeled as registry provider defs.
876
- "azure-openai-responses": "AZURE_OPENAI_API_KEY",
877
- jina: "JINA_API_KEY",
878
- brave: "BRAVE_API_KEY",
879
- tinyfish: "TINYFISH_API_KEY",
880
- firecrawl: "FIRECRAWL_API_KEY",
881
- };
882
-
883
- /**
884
- * Env fallbacks derived from the catalog provider entries (`env` in
885
- * `providers/<id>.kdl`) — the single source for plain provider env-var names.
886
- * Registry defs override with computed resolvers (Foundry/ADC/Bedrock
887
- * probes); legacy non-provider keys merge last.
888
- */
889
- const CATALOG_ENTRY_ENV_KEYS = Object.values(providerEntries()).flatMap(provider => {
890
- const envVars = provider.envVars;
891
- if (!envVars || envVars.length === 0) return [];
892
- const resolver: KeyResolver = envVars.length === 1 ? envVars[0] : () => $pickenv(...envVars);
893
- return [[provider.id, resolver] as [string, KeyResolver]];
894
- });
895
-
896
- const serviceProviderMap: Record<string, KeyResolver> = {
897
- ...Object.fromEntries(CATALOG_ENTRY_ENV_KEYS),
898
- ...Object.fromEntries(
899
- PROVIDER_REGISTRY.flatMap(provider =>
900
- provider.envKeys != null ? [[provider.id, provider.envKeys] as [string, KeyResolver]] : [],
901
- ),
902
- ),
903
- ...LEGACY_ENV_KEYS,
904
- };
905
-
906
- /**
907
- * Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.
908
- *
909
- * Will not return API keys for providers that require OAuth tokens.
910
- * Checks Bun.env, then cwd/.env, then ~/.env.
911
- */
912
- export function getEnvApiKey(provider: string): string | undefined {
913
- const resolver = serviceProviderMap[provider];
914
- if (typeof resolver === "string") {
915
- return $env[resolver];
916
- }
917
- return resolver?.();
918
- }
919
-
920
- /**
921
- * Name of the environment variable that backs `getEnvApiKey` for a provider,
922
- * when that provider maps to a single named variable (e.g. `github-copilot` →
923
- * `COPILOT_GITHUB_TOKEN`). Returns undefined for providers whose env fallback
924
- * is computed (multi-var pickers, Vertex ADC / Bedrock probes, …) since no
925
- * single variable name describes the source.
926
- */
927
- export function getEnvApiKeyName(provider: string): string | undefined {
928
- const resolver = serviceProviderMap[provider];
929
- return typeof resolver === "string" ? resolver : undefined;
930
- }
931
-
932
- /**
933
- * Enumerate every provider that has an env-var fallback for `getEnvApiKey`.
934
- * Used by `zeta-c auth-broker migrate --include-env` to discover env-sourced keys
935
- * that should be uploaded to the broker.
936
- */
937
- export function listProvidersWithEnvKey(): string[] {
938
- return Object.keys(serviceProviderMap);
939
- }
940
-
941
872
  function withResolvedModelHeaders<TApi extends Api>(
942
873
  model: Model<TApi>,
943
874
  signal: AbortSignal | undefined,
@@ -974,7 +905,7 @@ export function stream<TApi extends Api>(
974
905
  }
975
906
  if (!model.requiresGlyphTokenization) {
976
907
  return withThinkingLoopGuard(model, options, opts =>
977
- withProviderInFlightLimit(model, opts, () => streamDispatch(model, context, opts)),
908
+ withProviderInFlightLimit(model, opts, limited => streamDispatch(model, context, opts, limited)),
978
909
  );
979
910
  }
980
911
  const codec = applyGlyphCodec(context);
@@ -983,7 +914,7 @@ export function stream<TApi extends Api>(
983
914
  execHandlers === undefined ? options : { ...options, execHandlers: codec.wrapCursorExecHandlers(execHandlers) };
984
915
  return codec.wrap(
985
916
  withThinkingLoopGuard(model, wireOptions, opts =>
986
- withProviderInFlightLimit(model, opts, () => streamDispatch(model, codec.context, opts)),
917
+ withProviderInFlightLimit(model, opts, limited => streamDispatch(model, codec.context, opts, limited)),
987
918
  ),
988
919
  );
989
920
  }
@@ -991,15 +922,19 @@ export function stream<TApi extends Api>(
991
922
  function streamDispatch<TApi extends Api>(
992
923
  model: Model<TApi>,
993
924
  context: Context,
994
- options?: OptionsForApi<TApi>,
925
+ options: OptionsForApi<TApi> | undefined,
926
+ limited: boolean,
995
927
  ): AssistantMessageEventStream {
996
- const requestOptions = withTransportFetch(model, (options || {}) as StreamOptions) as OptionsForApi<TApi>;
928
+ const requestOptions = withSupportedSamplingParams(
929
+ model,
930
+ withTransportFetch(model, (options || {}) as StreamOptions),
931
+ ) as OptionsForApi<TApi>;
997
932
  assertExplicitOpenAIResponsesPromptCacheSupport(model, requestOptions);
998
933
 
999
934
  // Check custom API registry first (extension-provided APIs like "vertex-claude-api")
1000
935
  const customApiProvider = getCustomApi(model.api);
1001
936
  if (customApiProvider) {
1002
- return customApiProvider.stream(model, context, requestOptions as StreamOptions);
937
+ return customApiProvider.stream(model, context, { ...requestOptions, waitForTerminalDrain: limited });
1003
938
  }
1004
939
 
1005
940
  if (model.provider === "gitlab-duo") {
@@ -1010,6 +945,7 @@ function streamDispatch<TApi extends Api>(
1010
945
  return streamGitLabDuo(model, context, {
1011
946
  ...(requestOptions as SimpleStreamOptions),
1012
947
  apiKey,
948
+ waitForTerminalDrain: limited,
1013
949
  });
1014
950
  }
1015
951
 
@@ -1032,7 +968,10 @@ function streamDispatch<TApi extends Api>(
1032
968
  return streamBedrock(model as Model<"bedrock-converse-stream">, context, requestOptions as BedrockOptions);
1033
969
  }
1034
970
  if (model.api === "factory-droid-agent") {
1035
- return streamFactoryDroid(model as Model<"factory-droid-agent">, context, requestOptions as FactoryDroidOptions);
971
+ return streamFactoryDroid(model as Model<"factory-droid-agent">, context, {
972
+ ...(requestOptions as FactoryDroidOptions),
973
+ waitForTerminalDrain: limited,
974
+ });
1036
975
  }
1037
976
 
1038
977
  const providerDefinition = getProviderDefinition(model.provider);
@@ -1053,6 +992,13 @@ function streamDispatch<TApi extends Api>(
1053
992
  : { ...preparedOptions, apiKey };
1054
993
 
1055
994
  const api: Api = providerModel.api;
995
+ if (api === "openrouter" && $env.PI_OPENROUTER_RESPONSES !== "0") {
996
+ return streamOpenAIResponses(
997
+ providerModel as Model<"openai-responses">,
998
+ context,
999
+ providerOptions as OptionsForApi<"openai-responses">,
1000
+ );
1001
+ }
1056
1002
  switch (api) {
1057
1003
  case "anthropic-messages": {
1058
1004
  const anthropicOptions = providerOptions as AnthropicOptions;
@@ -1062,28 +1008,12 @@ function streamDispatch<TApi extends Api>(
1062
1008
  });
1063
1009
  }
1064
1010
 
1065
- case "openrouter": {
1066
- const useResponses = $env.PI_OPENROUTER_RESPONSES !== "0";
1067
- if (useResponses) {
1068
- return streamOpenAIResponses(
1069
- providerModel as Model<"openai-responses">,
1070
- context,
1071
- providerOptions as OptionsForApi<"openai-responses">,
1072
- );
1073
- }
1074
- return streamOpenAICompletions(
1075
- providerModel as Model<"openai-completions">,
1076
- context,
1077
- providerOptions as OptionsForApi<"openai-completions">,
1078
- );
1079
- }
1080
-
1011
+ case "openrouter":
1081
1012
  case "openai-completions":
1082
- return streamOpenAICompletions(
1083
- providerModel as Model<"openai-completions">,
1084
- context,
1085
- providerOptions as OptionsForApi<"openai-completions">,
1086
- );
1013
+ return streamOpenAICompletions(providerModel as Model<"openai-completions">, context, {
1014
+ ...(providerOptions as OpenAICompletionsOptions),
1015
+ waitForTerminalDrain: limited,
1016
+ });
1087
1017
 
1088
1018
  case "openai-responses":
1089
1019
  return streamOpenAIResponses(
@@ -1268,6 +1198,43 @@ function withInferenceSessionId(options?: SimpleStreamOptions): SimpleStreamOpti
1268
1198
  return { ...options, sessionId: crypto.randomUUID() };
1269
1199
  }
1270
1200
 
1201
+ type SamplingOptions = Pick<
1202
+ StreamOptions,
1203
+ "temperature" | "topP" | "topK" | "minP" | "presencePenalty" | "repetitionPenalty" | "frequencyPenalty"
1204
+ >;
1205
+
1206
+ /**
1207
+ * Drop explicit sampling parameters before any provider builds its payload
1208
+ * when the model's resolved `compat.supportsSamplingParams` is `false`. The
1209
+ * catalog's class rules assign that per model lineage on every compat record,
1210
+ * and an explicit compat override still wins, so this one check covers every
1211
+ * provider.
1212
+ */
1213
+ function withSupportedSamplingParams<T extends SamplingOptions>(model: Model<Api>, options: T): T {
1214
+ if (
1215
+ options.temperature === undefined &&
1216
+ options.topP === undefined &&
1217
+ options.topK === undefined &&
1218
+ options.minP === undefined &&
1219
+ options.presencePenalty === undefined &&
1220
+ options.repetitionPenalty === undefined &&
1221
+ options.frequencyPenalty === undefined
1222
+ ) {
1223
+ return options;
1224
+ }
1225
+ const compat = model.compat;
1226
+ if (!compat || !("supportsSamplingParams" in compat) || compat.supportsSamplingParams !== false) return options;
1227
+ const supported = { ...options };
1228
+ delete supported.temperature;
1229
+ delete supported.topP;
1230
+ delete supported.topK;
1231
+ delete supported.minP;
1232
+ delete supported.presencePenalty;
1233
+ delete supported.repetitionPenalty;
1234
+ delete supported.frequencyPenalty;
1235
+ return supported;
1236
+ }
1237
+
1271
1238
  export function streamSimple<TApi extends Api>(
1272
1239
  model: Model<TApi>,
1273
1240
  context: Context,
@@ -1316,7 +1283,10 @@ function streamSimpleRequest<TApi extends Api>(
1316
1283
  context: Context,
1317
1284
  options?: SimpleStreamOptions,
1318
1285
  ): AssistantMessageEventStream {
1319
- const requestOptions = withTransportFetch(model, (options || {}) as SimpleStreamOptions);
1286
+ const requestOptions = withSupportedSamplingParams(
1287
+ model,
1288
+ withTransportFetch(model, (options || {}) as SimpleStreamOptions),
1289
+ );
1320
1290
 
1321
1291
  const apiKeyResolver = isApiKeyResolver(requestOptions?.apiKey) ? requestOptions.apiKey : undefined;
1322
1292
  if (apiKeyResolver) {
@@ -1504,7 +1474,9 @@ function streamSimpleRequest<TApi extends Api>(
1504
1474
  const customApiProvider = getCustomApi(model.api);
1505
1475
  if (customApiProvider) {
1506
1476
  return withThinkingLoopGuard(model, requestOptions, opts =>
1507
- withProviderInFlightLimit(model, opts, () => customApiProvider.streamSimple(model, context, opts)),
1477
+ withProviderInFlightLimit(model, opts, limited =>
1478
+ customApiProvider.streamSimple(model, context, { ...opts, waitForTerminalDrain: limited }),
1479
+ ),
1508
1480
  );
1509
1481
  }
1510
1482
 
@@ -1536,10 +1508,11 @@ function streamSimpleRequest<TApi extends Api>(
1536
1508
  // GitLab Duo - wraps Anthropic/OpenAI behind GitLab AI Gateway direct access tokens
1537
1509
  if (model.provider === "gitlab-duo") {
1538
1510
  return withThinkingLoopGuard(model, requestOptions, opts =>
1539
- withProviderInFlightLimit(model, opts, () =>
1511
+ withProviderInFlightLimit(model, opts, limited =>
1540
1512
  streamGitLabDuo(model, context, {
1541
1513
  ...opts,
1542
1514
  apiKey,
1515
+ waitForTerminalDrain: limited,
1543
1516
  }),
1544
1517
  ),
1545
1518
  );
@@ -1568,11 +1541,12 @@ function streamSimpleRequest<TApi extends Api>(
1568
1541
  // (mirrors the mapOptionsForApi path every other provider takes).
1569
1542
  const kimiOptions = normalizeMandatoryReasoningOptions(model, requestOptions);
1570
1543
  return withThinkingLoopGuard(model, kimiOptions, opts =>
1571
- withProviderInFlightLimit(model, opts, () =>
1544
+ withProviderInFlightLimit(model, opts, limited =>
1572
1545
  streamKimi(model as Model<"openai-completions">, context, {
1573
1546
  ...opts,
1574
1547
  apiKey,
1575
1548
  format: opts?.kimiApiFormat,
1549
+ waitForTerminalDrain: limited,
1576
1550
  }),
1577
1551
  ),
1578
1552
  );
@@ -1582,11 +1556,12 @@ function streamSimpleRequest<TApi extends Api>(
1582
1556
  if (model.provider === "synthetic") {
1583
1557
  // Pass raw SimpleStreamOptions - streamSynthetic handles mapping internally.
1584
1558
  return withThinkingLoopGuard(model, requestOptions, opts =>
1585
- withProviderInFlightLimit(model, opts, () =>
1559
+ withProviderInFlightLimit(model, opts, limited =>
1586
1560
  streamSynthetic(model as Model<"openai-completions">, context, {
1587
1561
  ...opts,
1588
1562
  apiKey,
1589
1563
  format: opts?.syntheticApiFormat ?? "openai",
1564
+ waitForTerminalDrain: limited,
1590
1565
  }),
1591
1566
  ),
1592
1567
  );
@@ -1871,6 +1846,8 @@ function mapOptionsForApi<TApi extends Api>(
1871
1846
  streamIdleTimeoutMs: options?.streamIdleTimeoutMs,
1872
1847
  codexSseMaxAttempts: options?.codexSseMaxAttempts,
1873
1848
  providerSessionState: options?.providerSessionState,
1849
+ parentSessionId: options?.parentSessionId,
1850
+ minimizeEffort: options?.minimizeEffort,
1874
1851
  liveSteering: options?.liveSteering,
1875
1852
  maxInFlightRequests: options?.maxInFlightRequests,
1876
1853
  toolNamespacesInfo: options?.toolNamespacesInfo,
@@ -2123,6 +2100,7 @@ function mapOptionsForApi<TApi extends Api>(
2123
2100
  textVerbosity: options?.textVerbosity,
2124
2101
  promptCache: options?.promptCache,
2125
2102
  statefulResponses: options?.statefulResponses,
2103
+ storeResponses: options?.storeResponses,
2126
2104
  });
2127
2105
 
2128
2106
  case "azure-openai-responses":
@@ -4,7 +4,8 @@ export type TranscriptionResponseFormat = "json" | "verbose_json";
4
4
  export type TranscriptionTimestampGranularity = "word" | "segment";
5
5
 
6
6
  export interface TranscriptionRequest {
7
- audio: Uint8Array;
7
+ /** Audio bytes. A `Blob`/`File` (e.g. a parsed multipart upload) is sent without copying. */
8
+ audio: Uint8Array | Blob;
8
9
  mimeType: string;
9
10
  fileName?: string;
10
11
  language?: string;
package/src/types.ts CHANGED
@@ -299,23 +299,40 @@ export function realizesPriorityServiceTier(
299
299
  }
300
300
 
301
301
  /**
302
- * Premium-request weight contributed by a priority request to a provider that
303
- * realizes it and bills extra. Mirrors GitHub Copilot's `premiumRequests`
304
- * accounting so the "premium requests" stat aggregates priority traffic across
305
- * the OpenAI family, direct Anthropic fast mode, and Google priority.
302
+ * Premium-request weight contributed by a request a provider bills above
303
+ * standard. Priority (Fast mode) counts 1 on every provider that realizes it;
304
+ * `ultrafast` counts 1 on the OpenAI family, where it is a premium serving tier
305
+ * (its cost premium is recorded separately in `usage.cost`). Mirrors GitHub
306
+ * Copilot's `premiumRequests` accounting so the "premium requests" stat
307
+ * aggregates premium traffic across the OpenAI family, direct Anthropic fast
308
+ * mode, and Google priority.
306
309
  *
307
- * Returns 1 only when priority is actually realized on the wire for `model`
308
- * (see {@link realizesPriorityServiceTier}) and the provider bills it as a
309
- * premium request. OpenRouter is excluded — it bills per its own pricing, not
310
- * Copilot-premium semantics — as are Bedrock/Vertex Claude, where priority is
311
- * silently dropped.
310
+ * Returns 1 only when the tier is actually realized on the wire for `model`
311
+ * (see {@link realizesPriorityServiceTier} and {@link shouldSendServiceTier})
312
+ * and the provider bills it as a premium request. OpenRouter is excluded — it
313
+ * bills per its own pricing, not Copilot-premium semantics — as are
314
+ * Bedrock/Vertex Claude, where priority is silently dropped.
315
+ *
316
+ * Pass `served: true` when the tier is the one the provider reported serving
317
+ * (an assistant message's {@link AssistantMessage.serviceTier}) rather than a
318
+ * requested setting: realization is then already proven, so the wire gate is
319
+ * skipped and a model without discovery metadata (a stats-backfill row) still
320
+ * counts.
312
321
  */
313
- export function getPriorityPremiumRequests(
322
+ export function getPremiumServiceTierRequests(
314
323
  serviceTier: ServiceTier | null | undefined,
315
324
  model: ServiceTierModel,
325
+ options?: { served?: boolean },
316
326
  ): number {
317
- if (!realizesPriorityServiceTier(serviceTier, model)) return 0;
318
327
  const provider = model.provider;
328
+ if (serviceTier === "ultrafast") {
329
+ if (provider !== "openai" && provider !== "openai-codex") return 0;
330
+ return options?.served === true || shouldSendServiceTier("ultrafast", model) ? 1 : 0;
331
+ }
332
+ if (serviceTier !== "priority") return 0;
333
+ // A served tier is proof it reached the wire, so the realization gate only
334
+ // applies to requested-tier inference.
335
+ if (!options?.served && !realizesPriorityServiceTier(serviceTier, model)) return 0;
319
336
  return provider === "openai" ||
320
337
  provider === "openai-codex" ||
321
338
  provider === "anthropic" ||
@@ -325,6 +342,21 @@ export function getPriorityPremiumRequests(
325
342
  : 0;
326
343
  }
327
344
 
345
+ /** Parse a provider-reported `service_tier` echo into a known tier, or `undefined` for anything else. */
346
+ export function parseServiceTier(value: unknown): ServiceTier | undefined {
347
+ switch (value) {
348
+ case "auto":
349
+ case "default":
350
+ case "flex":
351
+ case "scale":
352
+ case "priority":
353
+ case "ultrafast":
354
+ return value;
355
+ default:
356
+ return undefined;
357
+ }
358
+ }
359
+
328
360
  /**
329
361
  * Coerce a persisted service-tier value to a {@link ServiceTierByFamily}. Newer
330
362
  * sessions store the family map directly; legacy sessions stored a single
@@ -373,6 +405,8 @@ export function coerceServiceTierByFamily(value: unknown): ServiceTierByFamily |
373
405
 
374
406
  export interface ProviderSessionState {
375
407
  close(): void;
408
+ /** Release one routing session after its final request, preserving shared provider fallbacks. */
409
+ releaseSession?(sessionId: string): void;
376
410
  }
377
411
 
378
412
  export interface ProviderResponseMetadata {
@@ -416,6 +450,11 @@ export interface CodexCompactionRequestContext extends CodexCompactionMetadata {
416
450
  export interface AnthropicCompactionRequest {
417
451
  /** Custom summarization prompt; replaces the API default entirely when set. */
418
452
  instructions?: string;
453
+ /**
454
+ * Replayed summaries' file metadata due before this time (ms) ends the request;
455
+ * later metadata replays with the retained tail, which the new summary carries.
456
+ */
457
+ filesDueBefore?: number;
419
458
  }
420
459
 
421
460
  /** OpenAI's GPT-5.6+ explicit prompt-cache controls. */
@@ -544,6 +583,16 @@ export interface StreamOptions {
544
583
  * `false` so `previous_response_id` cannot explain a result.
545
584
  */
546
585
  statefulResponses?: boolean;
586
+ /**
587
+ * Store this request's result server-side on hosts that support it
588
+ * (`compat.storeResponses`, e.g. Muse Code), so a stream that drops
589
+ * mid-turn resumes from `GET /responses/{id}` instead of re-running the
590
+ * turn. Privacy: stored runs retain prompts and outputs on the provider.
591
+ * Unset falls back to `PI_MUSE_STORE_RESPONSES`, then the host's
592
+ * `configureProviderStoreResponses` default, else off. Ignored on hosts
593
+ * without the capability.
594
+ */
595
+ storeResponses?: boolean;
547
596
  /**
548
597
  * Disable native reasoning when the caller supplies an external scratchpad.
549
598
  * OpenAI Responses emits `reasoning: { effort: "none" }`; Anthropic and
@@ -555,6 +604,21 @@ export interface StreamOptions {
555
604
  * Providers can use this to persist transport/session state between turns.
556
605
  */
557
606
  providerSessionState?: Map<string, ProviderSessionState>;
607
+ /**
608
+ * Session a side request branches from, so it can read the parent's cached
609
+ * prefix. Providers that keep per-conversation request controls (OpenAI
610
+ * `configuration_update` effort baselines) plan the request against a copy
611
+ * of that session's controls, never changing the parent's state; the Codex
612
+ * backend also routes it with the parent's cache affinity (session headers).
613
+ */
614
+ parentSessionId?: string;
615
+ /**
616
+ * Run at the model's lowest effort where the transport carries the change as
617
+ * a per-message control that keeps the cached prefix (Anthropic per-message
618
+ * effort, OpenAI `configuration_update`). Elsewhere the requested effort
619
+ * applies unchanged: changing request-level reasoning forfeits the cache.
620
+ */
621
+ minimizeEffort?: boolean;
558
622
  /**
559
623
  * Source of user steering a provider may deliver into the response it is
560
624
  * streaming (OpenAI Responses `response.steer` over the Codex WebSocket).
@@ -574,6 +638,8 @@ export interface StreamOptions {
574
638
  * are not covered.
575
639
  */
576
640
  maxInFlightRequests?: Record<string, number>;
641
+ /** @internal Keep the in-flight permit until a provider's bounded terminal drain finishes. */
642
+ waitForTerminalDrain?: boolean;
577
643
  /**
578
644
  * Optional callback for inspecting or replacing provider payloads before sending.
579
645
  * Return undefined to keep the payload unchanged.
@@ -816,6 +882,17 @@ export interface ThinkingContent {
816
882
  thinking: string;
817
883
  thinkingSignature?: string; // e.g., for OpenAI responses, the reasoning item ID
818
884
  itemId?: string; // item.id from output_item.added, used to match output_item.done
885
+ /**
886
+ * `true`: `thinking` is a provider-written summary of the model's reasoning
887
+ * (OpenAI Responses `summary_text`, Gemini thought summaries). `false`: the
888
+ * wire delivered it as the reasoning itself, not marked as a summary
889
+ * (chat-completions reasoning fields, Responses `reasoning_text`, Devin
890
+ * `thinking`); whether that is the full trace is vouched for per family by
891
+ * the catalog's `portable-reasoning`. Unset when provenance is unknown,
892
+ * including turns recorded before parsers set it. Only `false` blocks
893
+ * replay natively on another host.
894
+ */
895
+ summary?: boolean;
819
896
  }
820
897
 
821
898
  export interface RedactedThinkingContent {
@@ -1033,14 +1110,55 @@ export interface AnthropicCompactionPayload {
1033
1110
  encryptedContent?: string;
1034
1111
  /**
1035
1112
  * Harness-appended file metadata (`<files>` section) kept out of the
1036
- * byte-identical block. Replayed as a user message after the native block:
1037
- * the converter replaces the summary message with the block and skips its
1038
- * text, so without this the metadata would be invisible to this provider.
1113
+ * byte-identical block. The converter replaces the summary message with the
1114
+ * block and skips its text, so it replays this as a user message after the
1115
+ * block's retained tail: before the first message created after the summary.
1039
1116
  */
1040
1117
  filesText?: string;
1118
+ /**
1119
+ * File metadata of earlier summaries whose replay position lies inside this
1120
+ * summary's retained tail. Retained messages must reach the API unchanged,
1121
+ * so each keeps replaying where it did: before the first message created
1122
+ * after `after` (the earlier summary's commit time).
1123
+ */
1124
+ retainedFiles?: AnthropicCompactionFiles[];
1125
+ /**
1126
+ * Set on summaries whose retained tail replays unchanged: file metadata
1127
+ * after the tail, earlier metadata at `retainedFiles`. Summaries persisted
1128
+ * without it keep their original layout (metadata after the first retained
1129
+ * turn), since later thinking was signed against those bytes.
1130
+ */
1131
+ exactTail?: true;
1041
1132
  }
1042
1133
 
1043
- export type ProviderPayload = OpenAIResponsesHistoryPayload | AnthropicMessagePayload | AnthropicCompactionPayload;
1134
+ /** File metadata replayed at a fixed point of a natively compacted conversation. */
1135
+ export interface AnthropicCompactionFiles {
1136
+ text: string;
1137
+ /** Replays before the first message created after this time (ms). */
1138
+ after: number;
1139
+ }
1140
+
1141
+ /**
1142
+ * Records Cursor's server wrote into the client's conversation store for one
1143
+ * assistant turn: the `rootPromptMessagesJson` entries after the user's message,
1144
+ * reasoning signatures and redacted reasoning included. Written and read by the
1145
+ * Cursor provider, which sends them back unchanged to the wire model that produced them.
1146
+ */
1147
+ export interface CursorHistoryPayload {
1148
+ type: "cursorHistory";
1149
+ /** Wire route the records were written under, as sent (requested model, details id, max mode, parameters). */
1150
+ wireRoute: string;
1151
+ /** Digest of the turn's opening user message, content and paired tool results when recorded; a rewritten turn rebuilds instead. */
1152
+ digest: string;
1153
+ /** Record bytes (UTF-8 JSON) in conversation order; each blob id is the SHA-256 of its bytes. */
1154
+ records: string[];
1155
+ }
1156
+
1157
+ export type ProviderPayload =
1158
+ | OpenAIResponsesHistoryPayload
1159
+ | AnthropicMessagePayload
1160
+ | AnthropicCompactionPayload
1161
+ | CursorHistoryPayload;
1044
1162
 
1045
1163
  /** Provider-reported rewrite applied to request content before inference. */
1046
1164
  export interface ProviderInputTransformation {
@@ -1159,6 +1277,15 @@ export interface AssistantMessage {
1159
1277
  * other than what was requested.
1160
1278
  */
1161
1279
  upstreamModel?: string;
1280
+ /**
1281
+ * Service tier the provider reported serving this turn, when the API echoes
1282
+ * one (`response.service_tier`), falling back to the tier the request carried
1283
+ * when the response omits the echo. Absent when the provider reports no tier
1284
+ * or the echo cannot be trusted (proxies). This is the tier the turn actually
1285
+ * ran on, which is what cost, premium-request, and speed accounting key on —
1286
+ * the session's live setting may already have changed.
1287
+ */
1288
+ serviceTier?: ServiceTier;
1162
1289
  usage: Usage;
1163
1290
  stopReason: StopReason;
1164
1291
  stopDetails?: StopDetails | null;