@oh-my-pi/pi-ai 15.12.3 → 15.12.4

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 (52) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/types/auth-broker/remote-store.d.ts +1 -0
  3. package/dist/types/auth-broker/wire-schemas.d.ts +1 -1
  4. package/dist/types/auth-gateway/types.d.ts +7 -1
  5. package/dist/types/auth-storage.d.ts +19 -0
  6. package/dist/types/providers/anthropic-messages-server-schema.d.ts +1 -1
  7. package/dist/types/providers/anthropic-messages-server.d.ts +2 -2
  8. package/dist/types/providers/google-shared.d.ts +17 -0
  9. package/dist/types/providers/openai-chat-server-schema.d.ts +2 -2
  10. package/dist/types/providers/openai-chat-server.d.ts +2 -2
  11. package/dist/types/providers/openai-chat-wire.d.ts +644 -0
  12. package/dist/types/providers/openai-codex-responses.d.ts +1 -1
  13. package/dist/types/providers/openai-completions.d.ts +1 -1
  14. package/dist/types/providers/openai-responses-server-schema.d.ts +2 -2
  15. package/dist/types/providers/openai-responses-server.d.ts +2 -2
  16. package/dist/types/providers/openai-responses-shared.d.ts +5 -6
  17. package/dist/types/providers/openai-responses-wire.d.ts +6065 -0
  18. package/dist/types/providers/openai-responses.d.ts +3 -3
  19. package/dist/types/providers/pi-native-server.d.ts +2 -1
  20. package/dist/types/usage.d.ts +1 -1
  21. package/dist/types/utils/openai-http.d.ts +58 -0
  22. package/dist/types/utils.d.ts +1 -1
  23. package/package.json +4 -5
  24. package/src/auth-broker/remote-store.ts +9 -0
  25. package/src/auth-broker/wire-schemas.ts +1 -1
  26. package/src/auth-gateway/server.ts +16 -2
  27. package/src/auth-gateway/types.ts +8 -0
  28. package/src/auth-storage.ts +100 -8
  29. package/src/providers/anthropic-messages-server-schema.ts +1 -1
  30. package/src/providers/anthropic-messages-server.ts +31 -10
  31. package/src/providers/anthropic.ts +3 -2
  32. package/src/providers/azure-openai-responses.ts +63 -49
  33. package/src/providers/gitlab-duo.ts +3 -3
  34. package/src/providers/google-gemini-cli.ts +4 -7
  35. package/src/providers/google-shared.ts +98 -33
  36. package/src/providers/openai-anthropic-shim.ts +2 -2
  37. package/src/providers/openai-chat-server-schema.ts +3 -2
  38. package/src/providers/openai-chat-server.ts +30 -8
  39. package/src/providers/openai-chat-wire.ts +847 -0
  40. package/src/providers/openai-codex-responses.ts +10 -10
  41. package/src/providers/openai-completions.ts +141 -141
  42. package/src/providers/openai-responses-server-schema.ts +3 -2
  43. package/src/providers/openai-responses-server.ts +50 -43
  44. package/src/providers/openai-responses-shared.ts +25 -19
  45. package/src/providers/openai-responses-wire.ts +6391 -0
  46. package/src/providers/openai-responses.ts +60 -68
  47. package/src/providers/pi-native-server.ts +42 -15
  48. package/src/registry/oauth/gitlab-duo.ts +82 -12
  49. package/src/stream.ts +20 -7
  50. package/src/usage.ts +1 -1
  51. package/src/utils/openai-http.ts +153 -0
  52. package/src/utils.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.12.4] - 2026-06-13
6
+
7
+ ### Added
8
+
9
+ - Added `GITLAB_CLIENT_ID` and `GITLAB_REDIRECT_URI` env-var overrides for the GitLab Duo OAuth login flow so users running with their own GitLab OAuth application can replace the bundled credentials when GitLab rejects the bundled `client_id`'s redirect URI. Setting `GITLAB_REDIRECT_URI` also disables the random-port fallback (strict OAuth providers reject mismatched URIs anyway). ([#2424](https://github.com/can1357/oh-my-pi/issues/2424))
10
+ - Added `AuthStorage.listStoredCredentials()` and `AuthStorage.removeCredential()` for per-account credential management.
11
+
12
+ ### Changed
13
+
14
+ - Replaced the OpenAI SDK client usage in `openai-completions`, `openai-responses`, `azure-openai-responses`, and `openai-codex-responses` with the new internal `postOpenAIStream` OpenAI-wire JSON/SSE transport
15
+
16
+ ### Fixed
17
+
18
+ - Fixed streaming providers to cancel upstream model requests when the client closes the response body, so interrupted SSE sessions stop instead of continuing in the background
19
+ - Fixed: provider request builders treat unknown `model.maxTokens` (`null`) as "no model cap" instead of coercing to `0` via `Math.min`; Anthropic falls back to the 64k Claude-Code cap for its required `max_tokens`.
20
+ - Fixed transient stream failures on OpenAI-compatible providers by retrying HTTP 408/429/5xx responses and transient network errors with Retry-After/quota-hint aware backoff
21
+ - Fixed SSE stream handling for OpenAI-compatible responses by parsing wire-level JSON frames directly and honoring `[DONE]` termination
22
+ - Fixed stream error handling for OpenAI-compatible providers by preserving structured HTTP status/headers and response body details from failed requests for retry and strict-tool fallback logic
23
+ - Fixed OpenAI-compat streams ending with a bare `finish_reason: "error"` (gateways like OpenRouter reporting upstream failures, e.g. Gemini `MALFORMED_FUNCTION_CALL`) surfacing as a non-retryable `Provider finish_reason: error`. The reason is now mapped to `Provider returned error finish_reason`, which the session retry classifier recognizes as transient, so the turn auto-retries instead of stopping with a pinned error banner.
24
+ - Fixed `SqliteAuthCredentialStore.open()` crashing with `SQLITE_BUSY_RECOVERY` (errno 261) when several `omp --session` panes restore concurrently after an unclean shutdown: `PRAGMA busy_timeout = 5000` now runs as a standalone statement BEFORE `PRAGMA journal_mode=WAL` (the first lock-taking statement during WAL recovery), and `open()` retries the BUSY family — `SQLITE_BUSY`, `SQLITE_BUSY_RECOVERY`, `SQLITE_BUSY_SNAPSHOT`, `SQLITE_BUSY_TIMEOUT` — with bounded exponential backoff. The exhausted-retry error message includes the DB path. Exported `isSqliteBusyError(err)` for callers that need the same classifier ([#2421](https://github.com/can1357/oh-my-pi/issues/2421)).
25
+ - Fixed MiniMax-M3 OpenAI-compatible streams rendering reasoning twice when the same chunk carried both `<think>…</think>` content and structured `reasoning_content`; structured reasoning now wins and cumulative MiniMax reasoning snapshots are collapsed to deltas. ([#2433](https://github.com/can1357/oh-my-pi/issues/2433))
26
+ - Fixed Gemini turns silently halting the agent when the model returned `finishReason: STOP` with only an empty (or whitespace-only) text part and no tool call — the well-known "empty response" failure. All Google surfaces (public Generative Language `streamGoogle`, Vertex `streamGoogleVertex`, and Cloud Code Assist `google-gemini-cli`/`google-antigravity`) now classify such a turn as empty via the shared `hasMeaningfulGoogleContent` check and retry it up to `MAX_EMPTY_STREAM_RETRIES` times before surfacing an error. The Cloud Code Assist path previously had an empty-stream retry that never fired for this case (its `hasContent` flag counted an empty-string text part as content), and the public/Vertex path had no retry at all; the retry now emits a single `start` event so no duplicate partial message leaks downstream.
27
+
5
28
  ## [15.12.1] - 2026-06-12
6
29
 
7
30
  ### Added
@@ -37,6 +37,7 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
37
37
  */
38
38
  updateAuthCredential(id: number, credential: AuthCredential): void;
39
39
  deleteAuthCredential(id: number, disabledCause: string): void;
40
+ deleteAuthCredentialRemote(id: number, disabledCause: string): Promise<boolean>;
40
41
  tryDisableAuthCredentialIfMatches(id: number, _expectedData: string, disabledCause: string): boolean;
41
42
  waitForFreshSnapshot(maxWaitMs: number, opts?: {
42
43
  signal?: AbortSignal;
@@ -10,7 +10,7 @@
10
10
  * keys are rejected — the previous implementation used a hand-rolled
11
11
  * `hasOnlyFields` allowlist for the same effect.
12
12
  */
13
- import * as z from "zod/v4";
13
+ import { z } from "zod/v4";
14
14
  /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
15
15
  export declare const oauthCredentialSchema: z.ZodObject<{
16
16
  type: z.ZodLiteral<"oauth">;
@@ -90,10 +90,16 @@ export interface AuthGatewayParsedRequest {
90
90
  stream: boolean;
91
91
  options: AuthGatewayParsedRequestOptions;
92
92
  }
93
+ export interface AuthGatewayStreamControl {
94
+ /** Gateway request signal. Encoders stop producing frames when it aborts. */
95
+ signal?: AbortSignal;
96
+ /** Called when the HTTP response body is cancelled by the client. */
97
+ onCancel?: (reason?: unknown) => void;
98
+ }
93
99
  export interface AuthGatewayFormatModule {
94
100
  parseRequest(body: unknown, headers?: Headers): AuthGatewayParsedRequest;
95
101
  encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown>;
96
- encodeStream(events: AssistantMessageEventStream, requestedModelId: string, options?: AuthGatewayParsedRequestOptions): ReadableStream<Uint8Array>;
102
+ encodeStream(events: AssistantMessageEventStream, requestedModelId: string, options?: AuthGatewayParsedRequestOptions, control?: AuthGatewayStreamControl): ReadableStream<Uint8Array>;
97
103
  /**
98
104
  * Emit a protocol-specific error envelope. OpenAI returns
99
105
  * `{ error: { message, type } }`; Anthropic returns
@@ -308,6 +308,11 @@ export interface AuthCredentialStore {
308
308
  * `replaceAuthCredentialsForProvider`.
309
309
  */
310
310
  replaceAuthCredentialsRemote?(provider: string, credentials: AuthCredential[]): Promise<StoredAuthCredential[]>;
311
+ /**
312
+ * Optional async write hook for disabling one stored credential. Remote stores
313
+ * use it to await broker persistence before AuthStorage updates its snapshot.
314
+ */
315
+ deleteAuthCredentialRemote?(id: number, disabledCause: string): Promise<boolean>;
311
316
  /**
312
317
  * Optional async write hook for clearing every credential for a provider
313
318
  * (logout). When present, `AuthStorage.remove` routes through this instead
@@ -585,10 +590,18 @@ export declare class AuthStorage {
585
590
  * Set credential for a provider.
586
591
  */
587
592
  set(provider: string, credential: AuthCredentialEntry): Promise<void>;
593
+ /**
594
+ * List stored credential rows, optionally filtered by provider.
595
+ */
596
+ listStoredCredentials(provider?: string): StoredAuthCredential[];
588
597
  /**
589
598
  * Remove credential for a provider.
590
599
  */
591
600
  remove(provider: string): Promise<void>;
601
+ /**
602
+ * Remove one stored credential for a provider.
603
+ */
604
+ removeCredential(provider: string, credentialId: number): Promise<boolean>;
592
605
  /**
593
606
  * List all providers with credentials.
594
607
  */
@@ -890,6 +903,12 @@ export declare class AuthStorage {
890
903
  */
891
904
  describeCredentialSource(provider: string, sessionId?: string): string | undefined;
892
905
  }
906
+ /**
907
+ * SQLite's busy result code family — base `SQLITE_BUSY` plus the extended
908
+ * variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
909
+ * and `SQLITE_BUSY_TIMEOUT`. All warrant the same backoff-and-retry treatment.
910
+ */
911
+ export declare function isSqliteBusyError(err: unknown): boolean;
893
912
  /**
894
913
  * Default SQLite-backed implementation of {@link AuthCredentialStore}.
895
914
  *
@@ -7,7 +7,7 @@
7
7
  * Used by `anthropic-messages.ts:parseRequest` to validate the inbound JSON
8
8
  * before walking it into pi-ai's canonical `Context`.
9
9
  */
10
- import * as z from "zod/v4";
10
+ import { z } from "zod/v4";
11
11
  import type { ContentBlockParam, ImageBlockParam, MessageCreateParams, MessageParam, TextBlockParam, Tool, ToolChoice } from "./anthropic-wire";
12
12
  export declare const cacheControlSchema: z.ZodObject<{
13
13
  type: z.ZodLiteral<"ephemeral">;
@@ -4,11 +4,11 @@ import type { AssistantMessage, AssistantMessageEventStream } from "../types";
4
4
  * gateway translation. Inbound: foreign HTTP body → omp Context. Outbound:
5
5
  * omp AssistantMessage[Stream] → Anthropic-shaped JSON / SSE.
6
6
  */
7
- import type { AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
7
+ import type { AuthGatewayStreamControl, AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
8
8
  export type { ParsedRequest };
9
9
  export declare function parseRequest(body: unknown, headers?: Headers): ParsedRequest;
10
10
  export declare function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown>;
11
- export declare function encodeStream(events: AssistantMessageEventStream, requestedModelId: string): ReadableStream<Uint8Array>;
11
+ export declare function encodeStream(events: AssistantMessageEventStream, requestedModelId: string, _options?: ParsedRequest["options"], control?: AuthGatewayStreamControl): ReadableStream<Uint8Array>;
12
12
  /**
13
13
  * Anthropic error envelope: `{ type: "error", error: { type, message } }`.
14
14
  * See https://docs.anthropic.com/en/api/errors. Returned as a `Response` so
@@ -97,6 +97,23 @@ export declare function mapStopReason(reason: FinishReason): StopReason;
97
97
  * Map string finish reason to our StopReason (for raw API responses).
98
98
  */
99
99
  export declare function mapStopReasonString(reason: string): StopReason;
100
+ /**
101
+ * Bounded retries for the well-known Gemini "empty response" failure: a benign
102
+ * `finishReason: STOP` carrying only an empty/whitespace text part and no tool call.
103
+ * Shared by the public/Vertex `streamGoogleGenAI` path and the Cloud Code Assist
104
+ * (`google-gemini-cli`/`google-antigravity`) provider so both apply the same policy.
105
+ */
106
+ export declare const MAX_EMPTY_STREAM_RETRIES = 2;
107
+ export declare const EMPTY_STREAM_BASE_DELAY_MS = 500;
108
+ /**
109
+ * Whether a completed Google assistant message carries content worth delivering.
110
+ *
111
+ * A tool call or any non-whitespace text counts as meaningful. An empty/whitespace-only
112
+ * text part — or thinking that never produced an answer — is the "empty response" failure:
113
+ * delivered as-is the agent loop has nothing to act on and silently halts, so the request
114
+ * must be retried instead of surfaced.
115
+ */
116
+ export declare function hasMeaningfulGoogleContent(output: AssistantMessage): boolean;
100
117
  export declare function nextToolCallId(name: string): string;
101
118
  /**
102
119
  * Push the appropriate `text_end` / `thinking_end` event for the given block.
@@ -7,8 +7,8 @@
7
7
  * non-strict defaults (e.g. `stream_options.include_obfuscation`) — does not
8
8
  * trip 400s on shapes we simply ignore.
9
9
  */
10
- import type { ChatCompletionContentPart, ChatCompletionCreateParams, ChatCompletionMessageParam, ChatCompletionMessageToolCall, ChatCompletionTool, ChatCompletionToolChoiceOption } from "openai/resources/chat/completions";
11
- import * as z from "zod/v4";
10
+ import { z } from "zod/v4";
11
+ import type { ChatCompletionContentPart, ChatCompletionCreateParams, ChatCompletionMessageParam, ChatCompletionMessageToolCall, ChatCompletionTool, ChatCompletionToolChoiceOption } from "./openai-chat-wire";
12
12
  export declare const textPartSchema: z.ZodObject<{
13
13
  type: z.ZodLiteral<"text">;
14
14
  text: z.ZodString;
@@ -2,12 +2,12 @@
2
2
  * Parsed inbound OpenAI chat-completions request, ready to feed into pi-ai
3
3
  * `stream(model, context, options)`.
4
4
  */
5
- import type { AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
5
+ import type { AuthGatewayStreamControl, AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
6
6
  import type { AssistantMessage, AssistantMessageEventStream } from "../types";
7
7
  export type { ParsedRequest };
8
8
  export declare function parseRequest(body: unknown, headers?: Headers): ParsedRequest;
9
9
  export declare function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown>;
10
- export declare function encodeStream(events: AssistantMessageEventStream, requestedModelId: string, options?: ParsedRequest["options"]): ReadableStream<Uint8Array>;
10
+ export declare function encodeStream(events: AssistantMessageEventStream, requestedModelId: string, options?: ParsedRequest["options"], control?: AuthGatewayStreamControl): ReadableStream<Uint8Array>;
11
11
  /**
12
12
  * OpenAI chat-completions error envelope:
13
13
  * `{ error: { message, type } }`