@oh-my-pi/pi-ai 17.2.0 → 17.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.
package/src/stream.ts CHANGED
@@ -24,6 +24,7 @@ import { isInvalidatedOAuthTokenError } from "./error/auth-classify";
24
24
  import { isUsageLimitOutcome } from "./error/rate-limit";
25
25
  import type { BedrockOptions } from "./providers/amazon-bedrock";
26
26
  import type { AnthropicOptions } from "./providers/anthropic";
27
+ import { coworkFetch } from "./providers/cowork-fetch";
27
28
  import type { CursorOptions } from "./providers/cursor";
28
29
  import type { DevinOptions } from "./providers/devin";
29
30
  import { isGitLabDuoModel, streamGitLabDuo } from "./providers/gitlab-duo";
@@ -81,6 +82,11 @@ import { wrapFetchForProxy } from "./utils/proxy";
81
82
  import { withRequestDebugFetch } from "./utils/request-debug";
82
83
  import { withGeminiThinkingLoopGuard } from "./utils/thinking-loop";
83
84
 
85
+ function defaultFetchForModel(model: Model<Api>): FetchImpl {
86
+ if (model.provider === "anthropic" && model.api === "anthropic-messages") return coworkFetch;
87
+ return globalThis.fetch;
88
+ }
89
+
84
90
  function isGoogleVertexAuthenticatedModel(model: Model<Api>): boolean {
85
91
  return (
86
92
  model.provider === "google-vertex" &&
@@ -769,11 +775,12 @@ function streamDispatch<TApi extends Api>(
769
775
  context: Context,
770
776
  options?: OptionsForApi<TApi>,
771
777
  ): AssistantMessageEventStream {
772
- const baseOptions = (options || {}) as StreamOptions;
778
+ const inputOptions = (options || {}) as StreamOptions;
779
+ const baseOptions = { ...inputOptions, fetch: inputOptions.fetch ?? defaultFetchForModel(model) };
773
780
  const debugOptions = withExtraCaFetch(withRequestDebugFetch(baseOptions));
774
781
  const requestOptions = {
775
782
  ...debugOptions,
776
- fetch: wrapFetchForProxy(debugOptions.fetch ?? (globalThis.fetch as FetchImpl), model.provider),
783
+ fetch: wrapFetchForProxy(debugOptions.fetch, model.provider),
777
784
  } as OptionsForApi<TApi>;
778
785
  assertExplicitOpenAIResponsesPromptCacheSupport(model, requestOptions);
779
786
 
@@ -1009,11 +1016,12 @@ export function streamSimple<TApi extends Api>(
1009
1016
  context: Context,
1010
1017
  options?: SimpleStreamOptions,
1011
1018
  ): AssistantMessageEventStream {
1012
- const baseOptions = (options || {}) as SimpleStreamOptions;
1019
+ const inputOptions = (options || {}) as SimpleStreamOptions;
1020
+ const baseOptions = { ...inputOptions, fetch: inputOptions.fetch ?? defaultFetchForModel(model) };
1013
1021
  const debugOptions = withExtraCaFetch(withRequestDebugFetch(baseOptions));
1014
1022
  const requestOptions = {
1015
1023
  ...debugOptions,
1016
- fetch: wrapFetchForProxy(debugOptions.fetch ?? (globalThis.fetch as FetchImpl), model.provider),
1024
+ fetch: wrapFetchForProxy(debugOptions.fetch, model.provider),
1017
1025
  } as SimpleStreamOptions;
1018
1026
 
1019
1027
  const apiKeyResolver = isApiKeyResolver(requestOptions?.apiKey) ? requestOptions.apiKey : undefined;
@@ -146,6 +146,33 @@ export async function listCodexResetCredits(auth: CodexResetAuth): Promise<Codex
146
146
  return { credits, availableCount };
147
147
  }
148
148
 
149
+ /**
150
+ * Pick the credit to spend: the available one that expires soonest.
151
+ *
152
+ * Credits are perishable, so spending in expiry order maximizes the bank's
153
+ * lifetime value. Available credits without a parseable `expiresAt` rank after
154
+ * dated ones; when nothing is available the first credit is returned unchanged
155
+ * (the consume then surfaces the backend's business outcome verbatim).
156
+ */
157
+ export function pickSoonestExpiringCredit(credits: readonly CodexResetCredit[]): CodexResetCredit | undefined {
158
+ let best: CodexResetCredit | undefined;
159
+ let bestExpiry = Number.POSITIVE_INFINITY;
160
+ let undated: CodexResetCredit | undefined;
161
+ for (const credit of credits) {
162
+ if ((credit.status ?? "available") !== "available") continue;
163
+ const expiry = credit.expiresAt ? Date.parse(credit.expiresAt) : Number.NaN;
164
+ if (Number.isNaN(expiry)) {
165
+ undated ??= credit;
166
+ continue;
167
+ }
168
+ if (expiry < bestExpiry) {
169
+ best = credit;
170
+ bestExpiry = expiry;
171
+ }
172
+ }
173
+ return best ?? undated ?? credits[0];
174
+ }
175
+
149
176
  /**
150
177
  * Spend one saved reset. `redeemRequestId` is the idempotency key; one is
151
178
  * generated when omitted, so retrying with the SAME id is safe and won't
@@ -177,6 +177,8 @@ export interface ConnectProxiedSocketOptions {
177
177
  signal?: AbortSignal;
178
178
  /** Maximum wall-clock time to establish the final TLS tunnel. Disabled when absent or non-positive. */
179
179
  timeoutMs?: number;
180
+ /** Target TLS profile. Cursor defaults to HTTP/2 when this is absent. */
181
+ tls?: tls.ConnectionOptions;
180
182
  }
181
183
 
182
184
  /**
@@ -261,10 +263,12 @@ export async function connectProxiedSocket(
261
263
  return;
262
264
  }
263
265
 
266
+ const tlsOptions = options?.tls;
264
267
  tunnelSocket = tls.connect({
268
+ ...tlsOptions,
265
269
  socket: rawSocket,
266
- servername: targetHost,
267
- ALPNProtocols: ["h2"],
270
+ servername: tlsOptions?.servername ?? targetHost,
271
+ ALPNProtocols: tlsOptions?.ALPNProtocols ?? ["h2"],
268
272
  });
269
273
  tunnelSocket.once("secureConnect", onTunnelReady);
270
274
  tunnelSocket.once("error", onTunnelError);