@oh-my-pi/pi-ai 18.1.14 → 18.1.16
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.1.16] - 2026-09-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Codex SSE streams that end without a terminal completion event now retry when replay-safe and remain transient errors when partial output prevents replay ([#11349](https://github.com/can1357/oh-my-pi/issues/11349)).
|
|
10
|
+
|
|
11
|
+
## [18.1.15] - 2026-09-08
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- GitHub Copilot sign-in keeps the GitHub-owned Copilot CLI OAuth app on Enterprise domains: private instances run their own OAuth registry and reject the github.com-registered OpenCode client, which is now used only for public github.com sign-ins ([#11285](https://github.com/can1357/oh-my-pi/pull/11285) by [@H4vC](https://github.com/H4vC))
|
|
16
|
+
- GitHub Copilot sign-in uses the minimal-grant OpenCode OAuth app again (`read:user` only): GitHub renders each app's existing per-user grant on the consent page, so Enterprise organizations that block the Copilot CLI app's broad historic grant can log in as on 18.1.4. API request identity still mimics the Copilot CLI, and tokens minted by either app keep working ([#11280](https://github.com/can1357/oh-my-pi/pull/11280) by [@H4vC](https://github.com/H4vC)).
|
|
17
|
+
- GitHub Copilot plan/model-policy 403s no longer count as credential failures for credential-lifetime decisions: the token is valid, so stored credentials are preserved instead of wiped ([#11280](https://github.com/can1357/oh-my-pi/pull/11280) by [@H4vC](https://github.com/H4vC)).
|
|
18
|
+
- Fixed custom `google-generative-ai` providers failing mid-turn model fallback when Gemini 3 tool calls are replayed without their original thought signature ([#11270](https://github.com/can1357/oh-my-pi/issues/11270)).
|
|
19
|
+
- Fixed Linux `zcode://` (Z.AI coding-plan) OAuth sign-in hanging at the paste prompt after a successful browser authorization: native scheme registration now runs `update-desktop-database`, so `xdg-desktop-portal-gtk` resolves the handler on the first attempt instead of reporting "No Apps available" and no longer routes the callback to a stale transaction directory ([#11279](https://github.com/can1357/oh-my-pi/issues/11279)).
|
|
20
|
+
|
|
5
21
|
## [18.1.14] - 2026-09-07
|
|
6
22
|
|
|
7
23
|
### Fixed
|
|
@@ -87,6 +87,15 @@ export declare function isFastModeUnsupported(error: unknown): boolean;
|
|
|
87
87
|
* fail identically) nor surface as an auth failure.
|
|
88
88
|
*/
|
|
89
89
|
export declare function isClinePassSurfaceGateMessage(errorMessage: string | undefined): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* GitHub Copilot 403s are plan/model-policy/org denials against a valid token —
|
|
92
|
+
* never a revoked credential (those arrive as 401). Wiping stored credentials
|
|
93
|
+
* on them hides the whole provider from `/model` and forces a pointless
|
|
94
|
+
* re-login, so credential-lifetime decisions must exempt them even though
|
|
95
|
+
* they still classify as `Flag.AuthFailed` (issue #11275). Mirrors the 403
|
|
96
|
+
* contract in `rewriteCopilotError` (`utils/http-inspector.ts`).
|
|
97
|
+
*/
|
|
98
|
+
export declare function isGitHubCopilotPolicyDenial(provider: string | undefined, status: number | undefined, errorMessage: string | undefined): boolean;
|
|
90
99
|
export declare function classifyMessage(message: {
|
|
91
100
|
api?: Api;
|
|
92
101
|
provider?: string;
|
|
@@ -15,7 +15,7 @@ type GitHubCopilotLoginOptions = {
|
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
17
|
* Refresh GitHub Copilot token.
|
|
18
|
-
* GitHub OAuth tokens from both the
|
|
18
|
+
* GitHub OAuth tokens from both the OpenCode app and the Copilot CLI app
|
|
19
19
|
* remain directly usable, so existing logins need no token exchange or migration.
|
|
20
20
|
*/
|
|
21
21
|
export declare function refreshGitHubCopilotToken(refreshToken: string, enterpriseDomain?: string, apiEndpoint?: string): OAuthCredentials;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.1.
|
|
3
|
+
"version": "18.1.16",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -124,11 +124,11 @@
|
|
|
124
124
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
125
125
|
},
|
|
126
126
|
"dependencies": {
|
|
127
|
-
"@oh-my-pi/omptype": "18.1.
|
|
128
|
-
"@oh-my-pi/pi-catalog": "18.1.
|
|
129
|
-
"@oh-my-pi/pi-natives": "18.1.
|
|
130
|
-
"@oh-my-pi/pi-utils": "18.1.
|
|
131
|
-
"@oh-my-pi/pi-wire": "18.1.
|
|
127
|
+
"@oh-my-pi/omptype": "18.1.16",
|
|
128
|
+
"@oh-my-pi/pi-catalog": "18.1.16",
|
|
129
|
+
"@oh-my-pi/pi-natives": "18.1.16",
|
|
130
|
+
"@oh-my-pi/pi-utils": "18.1.16",
|
|
131
|
+
"@oh-my-pi/pi-wire": "18.1.16"
|
|
132
132
|
},
|
|
133
133
|
"devDependencies": {
|
|
134
134
|
"@types/bun": "^1.3.14"
|
package/src/error/flags.ts
CHANGED
|
@@ -694,6 +694,26 @@ export function isClinePassSurfaceGateMessage(errorMessage: string | undefined):
|
|
|
694
694
|
return errorMessage !== undefined && CLINE_PASS_SURFACE_GATE_PATTERN.test(errorMessage);
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
+
const GITHUB_COPILOT_POLICY_DENIAL_PATTERN = /GitHub Copilot access denied \(HTTP 403\)/;
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* GitHub Copilot 403s are plan/model-policy/org denials against a valid token —
|
|
701
|
+
* never a revoked credential (those arrive as 401). Wiping stored credentials
|
|
702
|
+
* on them hides the whole provider from `/model` and forces a pointless
|
|
703
|
+
* re-login, so credential-lifetime decisions must exempt them even though
|
|
704
|
+
* they still classify as `Flag.AuthFailed` (issue #11275). Mirrors the 403
|
|
705
|
+
* contract in `rewriteCopilotError` (`utils/http-inspector.ts`).
|
|
706
|
+
*/
|
|
707
|
+
export function isGitHubCopilotPolicyDenial(
|
|
708
|
+
provider: string | undefined,
|
|
709
|
+
status: number | undefined,
|
|
710
|
+
errorMessage: string | undefined,
|
|
711
|
+
): boolean {
|
|
712
|
+
if (provider !== "github-copilot") return false;
|
|
713
|
+
if (status === 403) return true;
|
|
714
|
+
return errorMessage !== undefined && GITHUB_COPILOT_POLICY_DENIAL_PATTERN.test(errorMessage);
|
|
715
|
+
}
|
|
716
|
+
|
|
697
717
|
export function classifyMessage(message: {
|
|
698
718
|
api?: Api;
|
|
699
719
|
provider?: string;
|
|
@@ -2147,6 +2147,17 @@ class CodexStreamProcessor {
|
|
|
2147
2147
|
firstTokenTime = this.#handleStreamEvent(rawEvent, firstTokenTime);
|
|
2148
2148
|
if (this.runtime.sawTerminalEvent) break;
|
|
2149
2149
|
}
|
|
2150
|
+
if (!this.runtime.sawTerminalEvent) {
|
|
2151
|
+
CODEX_DEBUG &&
|
|
2152
|
+
logger.debug("[codex] codex stream ended unexpectedly", {
|
|
2153
|
+
transport: this.runtime.transport,
|
|
2154
|
+
terminalEventSeen: false,
|
|
2155
|
+
unexpectedStreamEnd: true,
|
|
2156
|
+
sentTurnStateHeader: Boolean(this.requestContext.turnState.value),
|
|
2157
|
+
sentModelsEtagHeader: Boolean(this.requestContext.websocketState?.modelsEtag),
|
|
2158
|
+
});
|
|
2159
|
+
throw new CodexProviderStreamError("Codex stream ended before terminal completion event", true);
|
|
2160
|
+
}
|
|
2150
2161
|
return { firstTokenTime };
|
|
2151
2162
|
} catch (error) {
|
|
2152
2163
|
const recovered = await this.#recoverStreamError(error);
|
|
@@ -2952,21 +2963,6 @@ class CodexStreamProcessor {
|
|
|
2952
2963
|
if (this.options?.signal?.aborted) {
|
|
2953
2964
|
throw new AIError.AbortError();
|
|
2954
2965
|
}
|
|
2955
|
-
if (!this.runtime.sawTerminalEvent) {
|
|
2956
|
-
if (this.requestContext.websocketState) {
|
|
2957
|
-
resetCodexWebSocketAppendState(this.requestContext.websocketState);
|
|
2958
|
-
this.requestContext.websocketState.modelsEtag = undefined;
|
|
2959
|
-
}
|
|
2960
|
-
CODEX_DEBUG &&
|
|
2961
|
-
logger.debug("[codex] codex stream ended unexpectedly", {
|
|
2962
|
-
transport: this.runtime.transport,
|
|
2963
|
-
terminalEventSeen: this.runtime.sawTerminalEvent,
|
|
2964
|
-
unexpectedStreamEnd: true,
|
|
2965
|
-
sentTurnStateHeader: Boolean(this.requestContext.turnState.value),
|
|
2966
|
-
sentModelsEtagHeader: Boolean(this.requestContext.websocketState?.modelsEtag),
|
|
2967
|
-
});
|
|
2968
|
-
throw new CodexProviderStreamError("Codex stream ended before terminal completion event", false);
|
|
2969
|
-
}
|
|
2970
2966
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
2971
2967
|
throw new CodexProviderStreamError("Codex response failed", false);
|
|
2972
2968
|
}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GitHub Copilot OAuth flow
|
|
2
|
+
* GitHub Copilot OAuth flow (OpenCode OAuth app, minimal read:user grant).
|
|
3
|
+
*
|
|
4
|
+
* The device flow intentionally uses the OpenCode app identity rather than
|
|
5
|
+
* the official Copilot CLI app: GitHub renders each app's existing per-user
|
|
6
|
+
* grant (Existing access) on the consent page, and Enterprise orgs that
|
|
7
|
+
* restrict OAuth apps block the CLI app's broad historic grant
|
|
8
|
+
* (repo/gist/codespace) no matter what scope this request asks for
|
|
9
|
+
* (issue #11275). API request identity still mimics the Copilot CLI via
|
|
10
|
+
* COPILOT_API_HEADERS. Enterprise domains keep the GitHub-owned Copilot CLI
|
|
11
|
+
* client: private instances run their own OAuth registry and reject the
|
|
12
|
+
* github.com-registered OpenCode client.
|
|
3
13
|
*/
|
|
4
14
|
import { scheduler } from "node:timers/promises";
|
|
5
15
|
import { getBundledModels } from "@oh-my-pi/pi-catalog/models";
|
|
@@ -15,7 +25,18 @@ import * as AIError from "../../error";
|
|
|
15
25
|
import type { FetchImpl } from "../../types";
|
|
16
26
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
17
27
|
|
|
18
|
-
const
|
|
28
|
+
const OPENCODE_CLIENT_ID = "Ov23li8tweQw6odWQebz";
|
|
29
|
+
const COPILOT_CLI_CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* OAuth client for the device flow. Public github.com uses the minimal-grant
|
|
33
|
+
* OpenCode app (narrow consent, issue #11275); private GitHub Enterprise
|
|
34
|
+
* instances run their own OAuth registry, which does not know that github.com
|
|
35
|
+
* registration, so they keep the GitHub-owned Copilot CLI client.
|
|
36
|
+
*/
|
|
37
|
+
function resolveOAuthClientId(domain: string): string {
|
|
38
|
+
return isPublicGitHubHost(domain) ? OPENCODE_CLIENT_ID : COPILOT_CLI_CLIENT_ID;
|
|
39
|
+
}
|
|
19
40
|
const OAUTH_SCOPE = "read:user";
|
|
20
41
|
const OAUTH_HEADERS = {
|
|
21
42
|
Accept: "application/json",
|
|
@@ -82,7 +103,7 @@ async function startDeviceFlow(domain: string, fetchImpl: FetchImpl): Promise<De
|
|
|
82
103
|
method: "POST",
|
|
83
104
|
headers: OAUTH_HEADERS,
|
|
84
105
|
body: new URLSearchParams({
|
|
85
|
-
client_id:
|
|
106
|
+
client_id: resolveOAuthClientId(domain),
|
|
86
107
|
scope: OAUTH_SCOPE,
|
|
87
108
|
}),
|
|
88
109
|
},
|
|
@@ -156,7 +177,7 @@ async function pollForGitHubAccessToken(
|
|
|
156
177
|
method: "POST",
|
|
157
178
|
headers: OAUTH_HEADERS,
|
|
158
179
|
body: new URLSearchParams({
|
|
159
|
-
client_id:
|
|
180
|
+
client_id: resolveOAuthClientId(domain),
|
|
160
181
|
device_code: deviceCode,
|
|
161
182
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
162
183
|
}),
|
|
@@ -207,7 +228,7 @@ const FAR_FUTURE_MS = Date.now() + 10 * 365.25 * 24 * 60 * 60 * 1000;
|
|
|
207
228
|
|
|
208
229
|
/**
|
|
209
230
|
* Refresh GitHub Copilot token.
|
|
210
|
-
* GitHub OAuth tokens from both the
|
|
231
|
+
* GitHub OAuth tokens from both the OpenCode app and the Copilot CLI app
|
|
211
232
|
* remain directly usable, so existing logins need no token exchange or migration.
|
|
212
233
|
*/
|
|
213
234
|
export function refreshGitHubCopilotToken(
|
|
@@ -335,8 +356,8 @@ export async function loginGitHubCopilot(options: GitHubCopilotLoginOptions): Pr
|
|
|
335
356
|
|
|
336
357
|
const apiEndpoint = await discoverGitHubCopilotApiEndpoint(githubAccessToken, fetchImpl);
|
|
337
358
|
|
|
338
|
-
// Keep storing the GitHub token directly so credentials minted by the
|
|
339
|
-
//
|
|
359
|
+
// Keep storing the GitHub token directly so credentials minted by the
|
|
360
|
+
// Copilot CLI OAuth app remain valid alongside new OpenCode app logins.
|
|
340
361
|
const credentials: OAuthCredentials = {
|
|
341
362
|
refresh: githubAccessToken,
|
|
342
363
|
access: githubAccessToken,
|