@oh-my-pi/pi-ai 17.4.0 → 17.4.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/CHANGELOG.md +30 -0
- package/LICENSE +23 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/auth-retry.d.ts +8 -3
- package/dist/types/auth-storage.d.ts +9 -0
- package/dist/types/dialect/qwen-xml.d.ts +6 -0
- package/dist/types/error/auth-classify.d.ts +8 -8
- package/dist/types/error/oauth.d.ts +2 -1
- package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
- package/dist/types/providers/cursor-pi-args.d.ts +21 -0
- package/dist/types/providers/cursor.d.ts +23 -3
- package/dist/types/providers/google-types.d.ts +6 -0
- package/dist/types/providers/mock.d.ts +3 -1
- package/dist/types/providers/openai-shared.d.ts +6 -1
- package/dist/types/registry/oauth/google-antigravity.d.ts +2 -6
- package/dist/types/registry/oauth/index.d.ts +2 -3
- package/dist/types/registry/oauth/types.d.ts +2 -1
- package/dist/types/registry/oauth/xai-oauth.d.ts +2 -2
- package/dist/types/registry/registry.d.ts +1 -1
- package/dist/types/registry/types.d.ts +2 -1
- package/dist/types/registry/xai-oauth.d.ts +1 -1
- package/dist/types/types.d.ts +21 -0
- package/dist/types/utils/stream-markup-healing.d.ts +1 -1
- package/package.json +8 -6
- package/src/auth-retry.ts +41 -3
- package/src/auth-storage.ts +229 -75
- package/src/dialect/qwen-xml.ts +82 -0
- package/src/error/auth-classify.ts +10 -8
- package/src/error/oauth.ts +2 -1
- package/src/providers/amazon-bedrock.ts +12 -19
- package/src/providers/anthropic.ts +29 -35
- package/src/providers/cursor/exec-modern.ts +2 -0
- package/src/providers/cursor-pi-args.ts +38 -2
- package/src/providers/cursor.ts +357 -32
- package/src/providers/google-shared.ts +11 -12
- package/src/providers/google-types.ts +7 -0
- package/src/providers/mock.ts +4 -1
- package/src/providers/openai-codex-responses.ts +34 -8
- package/src/providers/openai-completions.ts +2 -2
- package/src/providers/openai-shared.ts +72 -14
- package/src/registry/oauth/google-antigravity.ts +247 -162
- package/src/registry/oauth/index.ts +3 -3
- package/src/registry/oauth/types.ts +2 -1
- package/src/registry/oauth/xai-oauth.ts +14 -7
- package/src/registry/types.ts +2 -1
- package/src/registry/xai-oauth.ts +2 -2
- package/src/stream.ts +8 -4
- package/src/types.ts +22 -0
- package/src/utils/schema/CONSTRAINTS.md +1 -0
- package/src/utils/schema/fields.ts +20 -4
- package/src/utils/stream-markup-healing.ts +9 -2
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
* Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
3
3
|
* Uses different OAuth credentials than google-gemini-cli for access to additional models.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { type } from "@oh-my-pi/omptype";
|
|
6
|
+
import { getAntigravityUserAgent } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
|
|
6
7
|
import * as AIError from "../../error";
|
|
8
|
+
import { raceWithSignal } from "../../utils/abort";
|
|
7
9
|
import { oauthFetch, runGoogleOAuthLogin, throwIfLoginCancelled } from "./google-oauth-shared";
|
|
8
10
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
9
11
|
|
|
10
|
-
const
|
|
11
|
-
const CLIENT_ID = decode(
|
|
12
|
+
const CLIENT_ID = atob(
|
|
12
13
|
"MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==",
|
|
13
14
|
);
|
|
14
|
-
const CLIENT_SECRET =
|
|
15
|
+
const CLIENT_SECRET = atob("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
|
|
15
16
|
const CALLBACK_PORT = 51121;
|
|
16
17
|
const CALLBACK_PATH = "/oauth-callback";
|
|
17
18
|
|
|
@@ -25,127 +26,250 @@ const SCOPES = [
|
|
|
25
26
|
|
|
26
27
|
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
27
28
|
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
const CLOUD_CODE_ASSIST_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
30
|
+
const LOAD_CODE_ASSIST_URL = `${CLOUD_CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`;
|
|
31
|
+
const ONBOARD_USER_URL = `${CLOUD_CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`;
|
|
32
|
+
const OPERATIONS_URL = `${CLOUD_CODE_ASSIST_ENDPOINT}/v1internal`;
|
|
33
|
+
const FREE_TIER_ID = "free-tier";
|
|
34
|
+
const ONBOARD_TIMEOUT_MS = 30_000;
|
|
35
|
+
const ONBOARD_POLL_INTERVAL_MS = 1_000;
|
|
36
|
+
const PROVIDER = "google-antigravity";
|
|
37
|
+
|
|
38
|
+
/** Cloud Code Assist metadata sent by native Antigravity control-plane requests. */
|
|
39
|
+
export const ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA = Object.freeze({
|
|
40
|
+
ideType: "ANTIGRAVITY",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
interface CloudCodeContext {
|
|
44
|
+
headers: Record<string, string>;
|
|
45
|
+
signal?: AbortSignal;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
interface
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
interface CloudCodeRequest extends CloudCodeContext {
|
|
49
|
+
label: string;
|
|
50
|
+
url: string;
|
|
51
|
+
method: "GET" | "POST";
|
|
52
|
+
body?: string;
|
|
53
|
+
timeoutMs?: number;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
const userTierSchema = type({
|
|
57
|
+
"id?": "string",
|
|
50
58
|
});
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
const ineligibleTierSchema = type({
|
|
61
|
+
"tierId?": "string",
|
|
62
|
+
"reasonMessage?": "string",
|
|
63
|
+
"validationUrl?": "string",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const loadCodeAssistResponseSchema = type({
|
|
67
|
+
"currentTier?": userTierSchema.or("null"),
|
|
68
|
+
"paidTier?": userTierSchema.or("null"),
|
|
69
|
+
"allowedTiers?": userTierSchema.array(),
|
|
70
|
+
"ineligibleTiers?": ineligibleTierSchema.array(),
|
|
71
|
+
"cloudaicompanionProject?": "string",
|
|
72
|
+
});
|
|
73
|
+
type LoadCodeAssistResponse = typeof loadCodeAssistResponseSchema.infer;
|
|
74
|
+
|
|
75
|
+
const operationErrorSchema = type({
|
|
76
|
+
"code?": "number",
|
|
77
|
+
"message?": "string",
|
|
78
|
+
});
|
|
79
|
+
type OperationError = typeof operationErrorSchema.infer;
|
|
80
|
+
|
|
81
|
+
const onboardUserResponseSchema = type({
|
|
82
|
+
"@type": "string",
|
|
83
|
+
"cloudaicompanionProject?": "string",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const onboardOperationSchema = type({
|
|
87
|
+
"name?": "string",
|
|
88
|
+
"done?": "boolean",
|
|
89
|
+
"error?": operationErrorSchema.or("null"),
|
|
90
|
+
"response?": onboardUserResponseSchema.or("null"),
|
|
91
|
+
});
|
|
92
|
+
type OnboardOperation = typeof onboardOperationSchema.infer;
|
|
93
|
+
|
|
94
|
+
function parseLoadCodeAssistResponse(payload: unknown): LoadCodeAssistResponse {
|
|
95
|
+
const result = loadCodeAssistResponseSchema(payload);
|
|
96
|
+
if (result instanceof type.errors) {
|
|
97
|
+
throw new AIError.OAuthError(`failed to unmarshal LoadCodeAssistResponse: ${result.summary}`, {
|
|
98
|
+
kind: "provisioning",
|
|
99
|
+
provider: PROVIDER,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseOnboardOperation(payload: unknown): OnboardOperation {
|
|
106
|
+
const result = onboardOperationSchema(payload);
|
|
107
|
+
if (result instanceof type.errors) {
|
|
108
|
+
throw new AIError.OAuthError(`failed to unmarshal OnboardUser operation: ${result.summary}`, {
|
|
109
|
+
kind: "provisioning",
|
|
110
|
+
provider: PROVIDER,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function extractProjectId(payload: LoadCodeAssistResponse): string | undefined {
|
|
117
|
+
const projectId = payload.cloudaicompanionProject;
|
|
118
|
+
return projectId && projectId.length > 0 ? projectId : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function hasMessageField(payload: LoadCodeAssistResponse, field: "currentTier" | "paidTier"): boolean {
|
|
122
|
+
return payload[field] !== undefined && payload[field] !== null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isFreeTierAllowed(payload: LoadCodeAssistResponse): boolean {
|
|
126
|
+
return payload.allowedTiers?.some(tier => tier.id === FREE_TIER_ID) === true;
|
|
56
127
|
}
|
|
57
128
|
|
|
58
|
-
|
|
129
|
+
function getFreeTierIneligibility(
|
|
130
|
+
payload: LoadCodeAssistResponse,
|
|
131
|
+
): { reasonMessage: string; validationUrl: string | undefined } | undefined {
|
|
132
|
+
const tier = payload.ineligibleTiers?.find(candidate => candidate.tierId === FREE_TIER_ID);
|
|
133
|
+
if (!tier?.reasonMessage) return undefined;
|
|
59
134
|
return {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
ide_name: "antigravity",
|
|
135
|
+
reasonMessage: tier.reasonMessage,
|
|
136
|
+
validationUrl: tier.validationUrl && tier.validationUrl.length > 0 ? tier.validationUrl : undefined,
|
|
63
137
|
};
|
|
64
138
|
}
|
|
65
139
|
|
|
66
|
-
function
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
140
|
+
function assertFreeTierEligible(payload: LoadCodeAssistResponse): void {
|
|
141
|
+
if (isFreeTierAllowed(payload)) return;
|
|
142
|
+
const ineligibility = getFreeTierIneligibility(payload);
|
|
143
|
+
if (!ineligibility) return;
|
|
144
|
+
const validation = ineligibility.validationUrl ? `\n${ineligibility.validationUrl}` : "";
|
|
145
|
+
throw new AIError.OAuthError(`${ineligibility.reasonMessage}${validation}`, {
|
|
146
|
+
kind: "provisioning",
|
|
147
|
+
provider: PROVIDER,
|
|
148
|
+
});
|
|
75
149
|
}
|
|
76
150
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
151
|
+
async function requestCloudCodeAssist({
|
|
152
|
+
label,
|
|
153
|
+
url,
|
|
154
|
+
method,
|
|
155
|
+
headers,
|
|
156
|
+
body,
|
|
157
|
+
signal,
|
|
158
|
+
timeoutMs,
|
|
159
|
+
}: CloudCodeRequest): Promise<unknown> {
|
|
160
|
+
throwIfLoginCancelled(signal);
|
|
161
|
+
const init: RequestInit = body === undefined ? { method, headers } : { method, headers, body };
|
|
162
|
+
const response = await oauthFetch(url, init, { provider: PROVIDER, signal, timeoutMs });
|
|
163
|
+
if (response.status !== 200) {
|
|
164
|
+
const errorText = await response.text();
|
|
165
|
+
throw new AIError.OAuthError(`${label} failed: ${response.status} ${response.statusText}: ${errorText}`, {
|
|
166
|
+
kind: "provisioning",
|
|
167
|
+
provider: PROVIDER,
|
|
168
|
+
status: response.status,
|
|
169
|
+
});
|
|
83
170
|
}
|
|
84
|
-
return
|
|
171
|
+
return response.json();
|
|
85
172
|
}
|
|
86
173
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
):
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
174
|
+
async function postLoadCodeAssist(
|
|
175
|
+
context: CloudCodeContext,
|
|
176
|
+
body: Record<string, unknown>,
|
|
177
|
+
): Promise<LoadCodeAssistResponse> {
|
|
178
|
+
const payload = await requestCloudCodeAssist({
|
|
179
|
+
...context,
|
|
180
|
+
label: "loadCodeAssist",
|
|
181
|
+
url: LOAD_CODE_ASSIST_URL,
|
|
182
|
+
method: "POST",
|
|
183
|
+
body: JSON.stringify(body),
|
|
184
|
+
});
|
|
185
|
+
return parseLoadCodeAssistResponse(payload);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function loadCodeAssist(context: CloudCodeContext): Promise<LoadCodeAssistResponse> {
|
|
189
|
+
let payload = await postLoadCodeAssist(context, {
|
|
190
|
+
metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
191
|
+
});
|
|
192
|
+
const projectId = extractProjectId(payload);
|
|
193
|
+
if (!hasMessageField(payload, "paidTier") && projectId) {
|
|
194
|
+
payload = await postLoadCodeAssist(context, {
|
|
195
|
+
cloudaicompanionProject: projectId,
|
|
196
|
+
metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
197
|
+
});
|
|
101
198
|
}
|
|
102
|
-
return
|
|
199
|
+
return payload;
|
|
103
200
|
}
|
|
104
201
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
if (attempt > 1) {
|
|
114
|
-
onProgress?.(`Waiting for project provisioning (attempt ${attempt}/${PROJECT_ONBOARD_MAX_ATTEMPTS})...`);
|
|
115
|
-
throwIfLoginCancelled(signal);
|
|
116
|
-
await Bun.sleep(PROJECT_ONBOARD_INTERVAL_MS);
|
|
117
|
-
}
|
|
202
|
+
function remainingOnboardTime(deadline: number): number {
|
|
203
|
+
const remaining = deadline - Date.now();
|
|
204
|
+
if (remaining > 0) return remaining;
|
|
205
|
+
throw new AIError.OAuthError(`onboardUser timed out after ${ONBOARD_TIMEOUT_MS}ms`, {
|
|
206
|
+
kind: "timeout",
|
|
207
|
+
provider: PROVIDER,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
118
210
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
211
|
+
function describeOperationError(error: OperationError): string {
|
|
212
|
+
if (error.message) {
|
|
213
|
+
return typeof error.code === "number" ? `${error.code}: ${error.message}` : error.message;
|
|
214
|
+
}
|
|
215
|
+
return JSON.stringify(error) ?? String(error);
|
|
216
|
+
}
|
|
125
217
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
218
|
+
async function onboardUser(context: CloudCodeContext): Promise<void> {
|
|
219
|
+
const deadline = Date.now() + ONBOARD_TIMEOUT_MS;
|
|
220
|
+
let operation = parseOnboardOperation(
|
|
221
|
+
await requestCloudCodeAssist({
|
|
222
|
+
...context,
|
|
223
|
+
label: "onboardUser",
|
|
224
|
+
url: ONBOARD_USER_URL,
|
|
225
|
+
method: "POST",
|
|
226
|
+
body: JSON.stringify({
|
|
227
|
+
tierId: FREE_TIER_ID,
|
|
228
|
+
metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
229
|
+
}),
|
|
230
|
+
timeoutMs: remainingOnboardTime(deadline),
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
133
233
|
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
234
|
+
while (true) {
|
|
235
|
+
if (operation.done === true) {
|
|
236
|
+
if (operation.error !== undefined && operation.error !== null) {
|
|
237
|
+
throw new AIError.OAuthError(`OnboardUser operation failed: ${describeOperationError(operation.error)}`, {
|
|
238
|
+
kind: "provisioning",
|
|
239
|
+
provider: PROVIDER,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (operation.response === undefined || operation.response === null) {
|
|
243
|
+
throw new AIError.OAuthError("failed to unmarshal OnboardUserResponse", {
|
|
244
|
+
kind: "provisioning",
|
|
245
|
+
provider: PROVIDER,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
137
249
|
}
|
|
138
250
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
251
|
+
await raceWithSignal(
|
|
252
|
+
Bun.sleep(Math.min(ONBOARD_POLL_INTERVAL_MS, remainingOnboardTime(deadline))),
|
|
253
|
+
context.signal,
|
|
254
|
+
);
|
|
255
|
+
throwIfLoginCancelled(context.signal);
|
|
256
|
+
const operationName = operation.name ?? "";
|
|
257
|
+
if (operationName.length === 0) {
|
|
258
|
+
throw new AIError.OAuthError("onboardUser returned an operation without a name", {
|
|
259
|
+
kind: "provisioning",
|
|
260
|
+
provider: PROVIDER,
|
|
261
|
+
});
|
|
142
262
|
}
|
|
263
|
+
operation = parseOnboardOperation(
|
|
264
|
+
await requestCloudCodeAssist({
|
|
265
|
+
...context,
|
|
266
|
+
label: "onboardUser operation",
|
|
267
|
+
url: `${OPERATIONS_URL}/${operationName}`,
|
|
268
|
+
method: "GET",
|
|
269
|
+
timeoutMs: remainingOnboardTime(deadline),
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
143
272
|
}
|
|
144
|
-
|
|
145
|
-
throw new AIError.OAuthError(
|
|
146
|
-
`onboardUser did not return a provisioned project id after ${PROJECT_ONBOARD_MAX_ATTEMPTS} attempts`,
|
|
147
|
-
{ kind: "provisioning", provider: "google-antigravity" },
|
|
148
|
-
);
|
|
149
273
|
}
|
|
150
274
|
|
|
151
275
|
async function discoverProject(
|
|
@@ -153,84 +277,45 @@ async function discoverProject(
|
|
|
153
277
|
onProgress?: (message: string) => void,
|
|
154
278
|
signal?: AbortSignal,
|
|
155
279
|
): Promise<string> {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
280
|
+
const context: CloudCodeContext = {
|
|
281
|
+
headers: {
|
|
282
|
+
Authorization: `Bearer ${accessToken}`,
|
|
283
|
+
"Content-Type": "application/json",
|
|
284
|
+
"User-Agent": getAntigravityUserAgent(),
|
|
285
|
+
},
|
|
286
|
+
signal,
|
|
160
287
|
};
|
|
161
288
|
|
|
162
|
-
onProgress?.("Checking
|
|
289
|
+
onProgress?.("Checking Cloud Code Assist account status...");
|
|
163
290
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
for (const endpoint of [DAILY_CLOUD_CODE_ENDPOINT, CLOUD_CODE_ENDPOINT]) {
|
|
170
|
-
throwIfLoginCancelled(signal);
|
|
171
|
-
const loadResponse = await oauthFetch(
|
|
172
|
-
`${endpoint}/v1internal:loadCodeAssist`,
|
|
173
|
-
{
|
|
174
|
-
method: "POST",
|
|
175
|
-
headers,
|
|
176
|
-
body: JSON.stringify({
|
|
177
|
-
metadata: ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA,
|
|
178
|
-
}),
|
|
179
|
-
},
|
|
180
|
-
{ provider: "google-antigravity", signal },
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
if (!loadResponse.ok) {
|
|
184
|
-
lastStatus = loadResponse.status;
|
|
185
|
-
lastErrorText = await loadResponse.text();
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
loadedSuccessfully = true;
|
|
190
|
-
const loadPayload = (await loadResponse.json()) as LoadCodeAssistPayload;
|
|
191
|
-
const existingProject = extractProjectId(loadPayload);
|
|
192
|
-
if (existingProject) {
|
|
193
|
-
return existingProject;
|
|
194
|
-
}
|
|
195
|
-
fallbackTierId = getDefaultTierId(loadPayload.allowedTiers, loadPayload.currentTier);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (!loadedSuccessfully && lastStatus !== undefined) {
|
|
199
|
-
throw new AIError.OAuthError(`loadCodeAssist failed: ${lastStatus}: ${lastErrorText || "unknown error"}`, {
|
|
200
|
-
kind: "discovery",
|
|
201
|
-
status: lastStatus,
|
|
202
|
-
});
|
|
291
|
+
const initial = await loadCodeAssist(context);
|
|
292
|
+
assertFreeTierEligible(initial);
|
|
293
|
+
if (!hasMessageField(initial, "currentTier")) {
|
|
294
|
+
onProgress?.("Provisioning the Antigravity free tier...");
|
|
295
|
+
await onboardUser(context);
|
|
203
296
|
}
|
|
204
297
|
|
|
205
|
-
onProgress?.("
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"X-Goog-Api-Client": GOOG_API_CLIENT_HEADER,
|
|
214
|
-
};
|
|
215
|
-
const provisionedProject = await onboardProjectWithRetries(
|
|
216
|
-
DAILY_CLOUD_CODE_ENDPOINT,
|
|
217
|
-
onboardHeaders,
|
|
218
|
-
onboardBody,
|
|
219
|
-
signal,
|
|
220
|
-
onProgress,
|
|
221
|
-
);
|
|
222
|
-
return provisionedProject;
|
|
298
|
+
onProgress?.("Refreshing Cloud Code Assist project...");
|
|
299
|
+
const refreshed = await loadCodeAssist(context);
|
|
300
|
+
const projectId = extractProjectId(refreshed);
|
|
301
|
+
if (projectId) return projectId;
|
|
302
|
+
throw new AIError.OAuthError("loadCodeAssist did not return a cloudaicompanionProject", {
|
|
303
|
+
kind: "provisioning",
|
|
304
|
+
provider: PROVIDER,
|
|
305
|
+
});
|
|
223
306
|
} catch (error) {
|
|
307
|
+
throwIfLoginCancelled(signal);
|
|
224
308
|
if (error instanceof AIError.LoginCancelledError || error instanceof AIError.OAuthError) {
|
|
225
309
|
throw error;
|
|
226
310
|
}
|
|
227
311
|
throw new AIError.OAuthError(
|
|
228
|
-
`Could not discover
|
|
229
|
-
{ kind: "discovery", provider:
|
|
312
|
+
`Could not discover an Antigravity project. ${error instanceof Error ? error.message : String(error)}`,
|
|
313
|
+
{ kind: "discovery", provider: PROVIDER, cause: error },
|
|
230
314
|
);
|
|
231
315
|
}
|
|
232
316
|
}
|
|
233
317
|
|
|
318
|
+
/** Authenticate an Antigravity account and resolve its Cloud Code Assist project. */
|
|
234
319
|
export async function loginAntigravity(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
235
320
|
return runGoogleOAuthLogin(ctrl, {
|
|
236
321
|
provider: "google-antigravity",
|
|
@@ -60,12 +60,12 @@ export function unregisterOAuthProviders(sourceId: string): void {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Refresh
|
|
64
|
-
* Saves the new credentials and returns the new access token.
|
|
63
|
+
* Refresh a built-in OAuth grant, cancelling provider work when refresh ownership ends.
|
|
65
64
|
*/
|
|
66
65
|
export async function refreshOAuthToken(
|
|
67
66
|
provider: OAuthProvider,
|
|
68
67
|
credentials: OAuthCredentials,
|
|
68
|
+
signal?: AbortSignal,
|
|
69
69
|
): Promise<OAuthCredentials> {
|
|
70
70
|
if (!credentials) {
|
|
71
71
|
throw new AIError.OAuthError(`No OAuth credentials found for ${provider}`, {
|
|
@@ -82,7 +82,7 @@ export async function refreshOAuthToken(
|
|
|
82
82
|
}
|
|
83
83
|
// Providers without a real refresher (static bearer tokens / API keys that
|
|
84
84
|
// don't expire) return the credentials unchanged.
|
|
85
|
-
return def.refreshToken ? def.refreshToken(credentials) : credentials;
|
|
85
|
+
return def.refreshToken ? def.refreshToken(credentials, signal) : credentials;
|
|
86
86
|
}
|
|
87
87
|
function getPerplexityJwtExpiryMs(token: string): number | undefined {
|
|
88
88
|
const parts = token.split(".");
|
|
@@ -89,7 +89,8 @@ export interface OAuthProviderInterface {
|
|
|
89
89
|
readonly name: string;
|
|
90
90
|
readonly sourceId?: string;
|
|
91
91
|
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
|
|
92
|
-
|
|
92
|
+
/** Refresh a stored grant; the signal bounds provider network work to refresh ownership. */
|
|
93
|
+
refreshToken?(credentials: OAuthCredentials, signal?: AbortSignal): Promise<OAuthCredentials>;
|
|
93
94
|
getApiKey?(credentials: OAuthCredentials): string;
|
|
94
95
|
/** Store resulting OAuth credentials under a different provider id. */
|
|
95
96
|
readonly storeCredentialsAs?: string;
|
|
@@ -106,6 +106,7 @@ export function validateXAIBillingEndpoint(url: string, field: string = "billing
|
|
|
106
106
|
async function xaiOAuthDiscovery(
|
|
107
107
|
timeoutMs: number = DISCOVERY_TIMEOUT_MS,
|
|
108
108
|
fetchOverride?: FetchImpl,
|
|
109
|
+
signal?: AbortSignal,
|
|
109
110
|
): Promise<XAIOAuthDiscovery> {
|
|
110
111
|
const fetchImpl = fetchOverride ?? fetch;
|
|
111
112
|
let response: Response;
|
|
@@ -113,7 +114,7 @@ async function xaiOAuthDiscovery(
|
|
|
113
114
|
response = await fetchImpl(XAI_OAUTH_DISCOVERY_URL, {
|
|
114
115
|
method: "GET",
|
|
115
116
|
headers: { Accept: "application/json" },
|
|
116
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
117
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
|
|
117
118
|
});
|
|
118
119
|
} catch (error) {
|
|
119
120
|
throw new AIError.OAuthError(
|
|
@@ -482,7 +483,7 @@ async function pollXAIDeviceToken(
|
|
|
482
483
|
/** Log in to xAI Grok with the RFC 8628 device authorization grant. */
|
|
483
484
|
export async function loginXAIOAuth(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
484
485
|
const fetchImpl = ctrl.fetch ?? fetch;
|
|
485
|
-
const discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS, fetchImpl);
|
|
486
|
+
const discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS, fetchImpl, ctrl.signal);
|
|
486
487
|
const device = await requestXAIDeviceAuthorization(fetchImpl, ctrl.signal);
|
|
487
488
|
ctrl.onAuth?.({
|
|
488
489
|
url: device.verificationUriComplete,
|
|
@@ -503,15 +504,19 @@ export async function loginXAIOAuth(ctrl: OAuthController): Promise<OAuthCredent
|
|
|
503
504
|
* Refresh an xAI OAuth access token using a stored refresh_token.
|
|
504
505
|
*
|
|
505
506
|
* Re-runs OIDC discovery and re-validates the token endpoint before sending
|
|
506
|
-
* the stored refresh token.
|
|
507
|
+
* the stored refresh token. Caller cancellation aborts every network request.
|
|
507
508
|
*/
|
|
508
|
-
export async function refreshXAIOAuthToken(
|
|
509
|
+
export async function refreshXAIOAuthToken(
|
|
510
|
+
refreshToken: string,
|
|
511
|
+
fetchOverride?: FetchImpl,
|
|
512
|
+
signal?: AbortSignal,
|
|
513
|
+
): Promise<OAuthCredentials> {
|
|
509
514
|
const fetchImpl = fetchOverride ?? fetch;
|
|
510
515
|
if (typeof refreshToken !== "string" || !refreshToken.trim()) {
|
|
511
516
|
throw new AIError.OAuthError("missing refresh_token", { kind: "validation", provider: "xai" });
|
|
512
517
|
}
|
|
513
518
|
|
|
514
|
-
const discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS, fetchImpl);
|
|
519
|
+
const discovery = await xaiOAuthDiscovery(DISCOVERY_TIMEOUT_MS, fetchImpl, signal);
|
|
515
520
|
const tokenEndpoint = validateXAIEndpoint(discovery.token_endpoint, "token_endpoint");
|
|
516
521
|
|
|
517
522
|
const body = new URLSearchParams({
|
|
@@ -528,7 +533,9 @@ export async function refreshXAIOAuthToken(refreshToken: string, fetchOverride?:
|
|
|
528
533
|
},
|
|
529
534
|
body,
|
|
530
535
|
redirect: "error",
|
|
531
|
-
signal:
|
|
536
|
+
signal: signal
|
|
537
|
+
? AbortSignal.any([signal, AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)])
|
|
538
|
+
: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
532
539
|
});
|
|
533
540
|
|
|
534
541
|
if (!response.ok) {
|
|
@@ -555,5 +562,5 @@ export async function refreshXAIOAuthToken(refreshToken: string, fetchOverride?:
|
|
|
555
562
|
);
|
|
556
563
|
}
|
|
557
564
|
const credentials = parseXAITokenResponse(payload, "xAI token refresh response", refreshToken);
|
|
558
|
-
return withXAIOAuthIdentity(credentials, fetchImpl);
|
|
565
|
+
return withXAIOAuthIdentity(credentials, fetchImpl, signal);
|
|
559
566
|
}
|
package/src/registry/types.ts
CHANGED
|
@@ -74,7 +74,8 @@ export interface ProviderDefinition {
|
|
|
74
74
|
readonly prepareModelDiscovery?: ProviderModelDiscoveryPreparer;
|
|
75
75
|
// --- interactive login (OAuthProviderInterface-compatible) ---
|
|
76
76
|
readonly login?: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials | string>;
|
|
77
|
-
|
|
77
|
+
/** Refresh a stored grant; the signal bounds provider network work to refresh ownership. */
|
|
78
|
+
readonly refreshToken?: (credentials: OAuthCredentials, signal?: AbortSignal) => Promise<OAuthCredentials>;
|
|
78
79
|
readonly getApiKey?: (credentials: OAuthCredentials) => string;
|
|
79
80
|
/** Store OAuth credentials under a different provider id (e.g. `openai-codex-device` ⇒ `openai-codex`). */
|
|
80
81
|
readonly storeCredentialsAs?: string;
|
|
@@ -9,9 +9,9 @@ export const xaiOauthProvider = {
|
|
|
9
9
|
const { loginXAIOAuth } = await import("./oauth/xai-oauth");
|
|
10
10
|
return loginXAIOAuth(cb);
|
|
11
11
|
},
|
|
12
|
-
refreshToken: async (credentials: OAuthCredentials) => {
|
|
12
|
+
refreshToken: async (credentials: OAuthCredentials, signal?: AbortSignal) => {
|
|
13
13
|
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
|
|
14
14
|
const { refreshXAIOAuthToken } = await import("./oauth/xai-oauth");
|
|
15
|
-
return refreshXAIOAuthToken(credentials.refresh);
|
|
15
|
+
return refreshXAIOAuthToken(credentials.refresh, undefined, signal);
|
|
16
16
|
},
|
|
17
17
|
} as const satisfies ProviderDefinition;
|
package/src/stream.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { getCustomApi } from "./api-registry";
|
|
|
20
20
|
import { createAuthRetryKeyState, isApiKeyResolver, resolveNextAuthRetryKey } from "./auth-retry";
|
|
21
21
|
import * as AIError from "./error";
|
|
22
22
|
import { ProviderHttpError } from "./error";
|
|
23
|
-
import { isInvalidatedOAuthTokenError } from "./error/auth-classify";
|
|
24
23
|
import { isConcurrencyCapExclusion, isUsageLimitOutcome } from "./error/rate-limit";
|
|
25
24
|
import type { BedrockOptions } from "./providers/amazon-bedrock";
|
|
26
25
|
import type { AnthropicOptions } from "./providers/anthropic";
|
|
@@ -1091,6 +1090,7 @@ function isRetryableUpstreamError(
|
|
|
1091
1090
|
status: number | undefined,
|
|
1092
1091
|
message: string | undefined,
|
|
1093
1092
|
): boolean {
|
|
1093
|
+
if (AIError.isAuthRetryableError(error)) return true;
|
|
1094
1094
|
// 401 means the credential is bad; 403 is its valid-token twin (access
|
|
1095
1095
|
// denied by plan, model policy, or org restriction — a sibling account may
|
|
1096
1096
|
// not share it). Explicit account-scoped policy errors such as Codex
|
|
@@ -1107,9 +1107,6 @@ function isRetryableUpstreamError(
|
|
|
1107
1107
|
// classify as RATE_LIMIT_EXCEEDED in `parseRateLimitReason` and stay in the
|
|
1108
1108
|
// provider's own backoff layer instead of burning siblings.
|
|
1109
1109
|
if (AIError.isCodexChatGPTAccountPolicyError(error, model.provider, model.id)) return true;
|
|
1110
|
-
if (AIError.isAccountPolicyError(error)) return true;
|
|
1111
|
-
if (AIError.isUsageLimit(error)) return true;
|
|
1112
|
-
if (isInvalidatedOAuthTokenError(error)) return true;
|
|
1113
1110
|
if (status === 401 || (status === 403 && !isConcurrencyCapExclusion(status, message))) return true;
|
|
1114
1111
|
return isUsageLimitOutcome(status, message);
|
|
1115
1112
|
}
|
|
@@ -1926,6 +1923,7 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
1926
1923
|
codexSseMaxAttempts: options?.codexSseMaxAttempts,
|
|
1927
1924
|
providerSessionState: options?.providerSessionState,
|
|
1928
1925
|
maxInFlightRequests: options?.maxInFlightRequests,
|
|
1926
|
+
toolNamespacesInfo: options?.toolNamespacesInfo,
|
|
1929
1927
|
onPayload: options?.onPayload,
|
|
1930
1928
|
onResponse: options?.onResponse,
|
|
1931
1929
|
onSseEvent: options?.onSseEvent,
|
|
@@ -2311,10 +2309,16 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
2311
2309
|
case "cursor-agent": {
|
|
2312
2310
|
const execHandlers = options?.cursorExecHandlers ?? options?.execHandlers;
|
|
2313
2311
|
const onToolResult = options?.cursorOnToolResult ?? execHandlers?.onToolResult;
|
|
2312
|
+
const cursorModel = model as Model<"cursor-agent">;
|
|
2313
|
+
const effort =
|
|
2314
|
+
options?.reasoning && !options.disableReasoning && !options.forceReasoningOff && cursorModel.reasoning
|
|
2315
|
+
? requireSupportedEffort(cursorModel, options.reasoning)
|
|
2316
|
+
: undefined;
|
|
2314
2317
|
return castApi<"cursor-agent">({
|
|
2315
2318
|
...base,
|
|
2316
2319
|
execHandlers,
|
|
2317
2320
|
onToolResult,
|
|
2321
|
+
wireModelId: resolveWireModelId(cursorModel, effort),
|
|
2318
2322
|
});
|
|
2319
2323
|
}
|
|
2320
2324
|
|