@omnicross/core 0.1.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.
- package/LICENSE +21 -0
- package/NOTICE +57 -0
- package/README.md +15 -0
- package/dist/ApiKeyPoolService-BmMkau07.d.cts +170 -0
- package/dist/ApiKeyPoolService-BmMkau07.d.ts +170 -0
- package/dist/ProviderProxy-f_8ziIhW.d.cts +120 -0
- package/dist/ProviderProxy-vjt8sQQk.d.ts +120 -0
- package/dist/SubscriptionAuthSource-Cr4fVEYY.d.cts +264 -0
- package/dist/SubscriptionAuthSource-D89zmiSS.d.ts +264 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +218 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.d.cts +68 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.d.ts +68 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.js +189 -0
- package/dist/completion/ApiKeyPoolService.cjs +331 -0
- package/dist/completion/ApiKeyPoolService.d.cts +2 -0
- package/dist/completion/ApiKeyPoolService.d.ts +2 -0
- package/dist/completion/ApiKeyPoolService.js +306 -0
- package/dist/completion.cjs +4027 -0
- package/dist/completion.d.cts +17 -0
- package/dist/completion.d.ts +17 -0
- package/dist/completion.js +3983 -0
- package/dist/index-BTSmc9Sm.d.ts +645 -0
- package/dist/index-DXazdTzZ.d.cts +645 -0
- package/dist/index.cjs +10428 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +10339 -0
- package/dist/outbound-api/subscriptionRegistryPort.cjs +38 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +73 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +73 -0
- package/dist/outbound-api/subscriptionRegistryPort.js +12 -0
- package/dist/outbound-api.cjs +5264 -0
- package/dist/outbound-api.d.cts +320 -0
- package/dist/outbound-api.d.ts +320 -0
- package/dist/outbound-api.js +5218 -0
- package/dist/pipeline/SubscriptionAuthSource.cjs +131 -0
- package/dist/pipeline/SubscriptionAuthSource.d.cts +3 -0
- package/dist/pipeline/SubscriptionAuthSource.d.ts +3 -0
- package/dist/pipeline/SubscriptionAuthSource.js +103 -0
- package/dist/pipeline/SubscriptionAuthStrategy.cjs +18 -0
- package/dist/pipeline/SubscriptionAuthStrategy.d.cts +61 -0
- package/dist/pipeline/SubscriptionAuthStrategy.d.ts +61 -0
- package/dist/pipeline/SubscriptionAuthStrategy.js +0 -0
- package/dist/ports/gemini-code-assist-resolver.cjs +38 -0
- package/dist/ports/gemini-code-assist-resolver.d.cts +26 -0
- package/dist/ports/gemini-code-assist-resolver.d.ts +26 -0
- package/dist/ports/gemini-code-assist-resolver.js +12 -0
- package/dist/ports.cjs +18 -0
- package/dist/ports.d.cts +15 -0
- package/dist/ports.d.ts +15 -0
- package/dist/ports.js +0 -0
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +2958 -0
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +77 -0
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +77 -0
- package/dist/provider-proxy/ingress/providerProxyShared.js +2925 -0
- package/dist/provider-proxy/matchText.cjs +73 -0
- package/dist/provider-proxy/matchText.d.cts +47 -0
- package/dist/provider-proxy/matchText.d.ts +47 -0
- package/dist/provider-proxy/matchText.js +45 -0
- package/dist/provider-proxy/types.cjs +18 -0
- package/dist/provider-proxy/types.d.cts +12 -0
- package/dist/provider-proxy/types.d.ts +12 -0
- package/dist/provider-proxy/types.js +0 -0
- package/dist/provider-proxy.cjs +4667 -0
- package/dist/provider-proxy.d.cts +69 -0
- package/dist/provider-proxy.d.ts +69 -0
- package/dist/provider-proxy.js +4636 -0
- package/dist/serializeError.cjs +82 -0
- package/dist/serializeError.d.cts +24 -0
- package/dist/serializeError.d.ts +24 -0
- package/dist/serializeError.js +57 -0
- package/dist/sse-parser.cjs +456 -0
- package/dist/sse-parser.d.cts +143 -0
- package/dist/sse-parser.d.ts +143 -0
- package/dist/sse-parser.js +430 -0
- package/dist/transformer/TransformerChainExecutor.cjs +321 -0
- package/dist/transformer/TransformerChainExecutor.d.cts +104 -0
- package/dist/transformer/TransformerChainExecutor.d.ts +104 -0
- package/dist/transformer/TransformerChainExecutor.js +294 -0
- package/dist/transformer/TransformerService.cjs +290 -0
- package/dist/transformer/TransformerService.d.cts +138 -0
- package/dist/transformer/TransformerService.d.ts +138 -0
- package/dist/transformer/TransformerService.js +265 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +1115 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.cts +102 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.ts +102 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +1085 -0
- package/dist/transformer/transformers/GeminiTransformer.cjs +1013 -0
- package/dist/transformer/transformers/GeminiTransformer.d.cts +70 -0
- package/dist/transformer/transformers/GeminiTransformer.d.ts +70 -0
- package/dist/transformer/transformers/GeminiTransformer.js +986 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +538 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +53 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +53 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +513 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +73 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +51 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +51 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +48 -0
- package/dist/transformer/types.cjs +18 -0
- package/dist/transformer/types.d.cts +405 -0
- package/dist/transformer/types.d.ts +405 -0
- package/dist/transformer/types.js +0 -0
- package/dist/transformer.cjs +3736 -0
- package/dist/transformer.d.cts +33 -0
- package/dist/transformer.d.ts +33 -0
- package/dist/transformer.js +3712 -0
- package/dist/types-CGGrKqC_.d.cts +142 -0
- package/dist/types-CbCN2NQP.d.ts +142 -0
- package/dist/types-DCzHkhJt.d.ts +467 -0
- package/dist/types-DZIQbgp0.d.cts +467 -0
- package/dist/usage-event-sink-BX7FE1NL.d.cts +59 -0
- package/dist/usage-event-sink-BX7FE1NL.d.ts +59 -0
- package/package.json +62 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/auth/GeminiCodeAssistProjectResolver.ts
|
|
21
|
+
var GeminiCodeAssistProjectResolver_exports = {};
|
|
22
|
+
__export(GeminiCodeAssistProjectResolver_exports, {
|
|
23
|
+
GeminiCodeAssistHandshakeError: () => GeminiCodeAssistHandshakeError,
|
|
24
|
+
GeminiCodeAssistProjectResolver: () => GeminiCodeAssistProjectResolver,
|
|
25
|
+
getGeminiCodeAssistProjectResolver: () => getGeminiCodeAssistProjectResolver
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(GeminiCodeAssistProjectResolver_exports);
|
|
28
|
+
|
|
29
|
+
// src/transformer/transformers/GeminiCodeAssistTransformer.ts
|
|
30
|
+
var DEFAULT_CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
31
|
+
var DEFAULT_CODE_ASSIST_API_VERSION = "v1internal";
|
|
32
|
+
function resolveCodeAssistEndpoint() {
|
|
33
|
+
return (process.env.CODE_ASSIST_ENDPOINT || DEFAULT_CODE_ASSIST_ENDPOINT).replace(/\/+$/, "");
|
|
34
|
+
}
|
|
35
|
+
function resolveCodeAssistApiVersion() {
|
|
36
|
+
return process.env.CODE_ASSIST_API_VERSION || DEFAULT_CODE_ASSIST_API_VERSION;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/auth/GeminiCodeAssistProjectResolver.ts
|
|
40
|
+
var FREE_TIER_ID = "free-tier";
|
|
41
|
+
var LEGACY_TIER_ID = "legacy-tier";
|
|
42
|
+
var ONBOARD_POLL_INTERVAL_MS = 5e3;
|
|
43
|
+
var ONBOARD_MAX_POLLS = 24;
|
|
44
|
+
var GeminiCodeAssistHandshakeError = class extends Error {
|
|
45
|
+
constructor(message, status, code) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.status = status;
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.name = "GeminiCodeAssistHandshakeError";
|
|
50
|
+
}
|
|
51
|
+
status;
|
|
52
|
+
code;
|
|
53
|
+
};
|
|
54
|
+
var GeminiCodeAssistProjectResolver = class {
|
|
55
|
+
constructor(fetchImpl = (url, init) => fetch(url, init)) {
|
|
56
|
+
this.fetchImpl = fetchImpl;
|
|
57
|
+
}
|
|
58
|
+
fetchImpl;
|
|
59
|
+
/** account access token → resolved project id (undefined = free-tier, no project). */
|
|
60
|
+
cache = /* @__PURE__ */ new Map();
|
|
61
|
+
/** In-flight handshakes so concurrent callers share one round-trip. */
|
|
62
|
+
inflight = /* @__PURE__ */ new Map();
|
|
63
|
+
/** Test/diagnostic helper — clear the cached resolution for an account. */
|
|
64
|
+
clearCache() {
|
|
65
|
+
this.cache.clear();
|
|
66
|
+
this.inflight.clear();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve (and cache) the Code Assist project for the given access token.
|
|
70
|
+
* Runs the handshake at most once per token. Returns `undefined` for a
|
|
71
|
+
* brand-new free-tier account (valid — the envelope sends no project).
|
|
72
|
+
*/
|
|
73
|
+
async resolveProject(accessToken) {
|
|
74
|
+
if (this.cache.has(accessToken)) {
|
|
75
|
+
return this.cache.get(accessToken);
|
|
76
|
+
}
|
|
77
|
+
const existing = this.inflight.get(accessToken);
|
|
78
|
+
if (existing) return existing;
|
|
79
|
+
const run = this.runHandshake(accessToken).then((project) => {
|
|
80
|
+
this.cache.set(accessToken, project);
|
|
81
|
+
return project;
|
|
82
|
+
}).finally(() => {
|
|
83
|
+
this.inflight.delete(accessToken);
|
|
84
|
+
});
|
|
85
|
+
this.inflight.set(accessToken, run);
|
|
86
|
+
return run;
|
|
87
|
+
}
|
|
88
|
+
codeAssistUrl(method) {
|
|
89
|
+
return `${resolveCodeAssistEndpoint()}/${resolveCodeAssistApiVersion()}:${method}`;
|
|
90
|
+
}
|
|
91
|
+
/** Seed project id from env; reject a purely-numeric value (project NUMBER). */
|
|
92
|
+
seedProjectId() {
|
|
93
|
+
const seed = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID || "";
|
|
94
|
+
if (seed && /^\d+$/.test(seed.trim())) return "";
|
|
95
|
+
return seed.trim();
|
|
96
|
+
}
|
|
97
|
+
async runHandshake(accessToken) {
|
|
98
|
+
const seededProject = this.seedProjectId();
|
|
99
|
+
const load = await this.postCodeAssist("loadCodeAssist", accessToken, {
|
|
100
|
+
cloudaicompanionProject: seededProject || void 0,
|
|
101
|
+
metadata: {
|
|
102
|
+
ideType: "IDE_UNSPECIFIED",
|
|
103
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
104
|
+
pluginType: "GEMINI",
|
|
105
|
+
duetProject: seededProject || void 0
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
if (load.currentTier?.id) {
|
|
109
|
+
return load.cloudaicompanionProject || void 0;
|
|
110
|
+
}
|
|
111
|
+
const defaultTier = load.allowedTiers?.find((t) => t.isDefault);
|
|
112
|
+
const tierId = defaultTier?.id ?? LEGACY_TIER_ID;
|
|
113
|
+
const isFreeish = tierId === FREE_TIER_ID || tierId === LEGACY_TIER_ID;
|
|
114
|
+
const onboardProject = isFreeish ? void 0 : seededProject || void 0;
|
|
115
|
+
let lro = await this.postCodeAssist("onboardUser", accessToken, {
|
|
116
|
+
tierId,
|
|
117
|
+
cloudaicompanionProject: onboardProject,
|
|
118
|
+
metadata: {
|
|
119
|
+
ideType: "IDE_UNSPECIFIED",
|
|
120
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
121
|
+
pluginType: "GEMINI",
|
|
122
|
+
duetProject: onboardProject
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
let polls = 0;
|
|
126
|
+
while (!lro.done && polls < ONBOARD_MAX_POLLS) {
|
|
127
|
+
await delay(ONBOARD_POLL_INTERVAL_MS);
|
|
128
|
+
polls++;
|
|
129
|
+
lro = await this.getOperation(accessToken, lro.name);
|
|
130
|
+
}
|
|
131
|
+
if (lro.error) {
|
|
132
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
133
|
+
`Code Assist onboardUser failed: ${lro.error.message ?? "unknown"}`,
|
|
134
|
+
lro.error.code ?? 500
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return lro.response?.cloudaicompanionProject?.id || void 0;
|
|
138
|
+
}
|
|
139
|
+
/** POST a Code Assist method, surfacing the documented hard failures clearly. */
|
|
140
|
+
async postCodeAssist(method, accessToken, body) {
|
|
141
|
+
const url = this.codeAssistUrl(method);
|
|
142
|
+
const res = await this.fetchImpl(url, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${accessToken}`,
|
|
146
|
+
"Content-Type": "application/json"
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify(body)
|
|
149
|
+
});
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
await this.throwForStatus(res, method);
|
|
152
|
+
}
|
|
153
|
+
return await res.json();
|
|
154
|
+
}
|
|
155
|
+
async getOperation(accessToken, name) {
|
|
156
|
+
const url = this.codeAssistUrl("getOperation");
|
|
157
|
+
const res = await this.fetchImpl(url, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: {
|
|
160
|
+
Authorization: `Bearer ${accessToken}`,
|
|
161
|
+
"Content-Type": "application/json"
|
|
162
|
+
},
|
|
163
|
+
body: JSON.stringify({ name })
|
|
164
|
+
});
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
await this.throwForStatus(res, "getOperation");
|
|
167
|
+
}
|
|
168
|
+
return await res.json();
|
|
169
|
+
}
|
|
170
|
+
/** Map a non-2xx Code Assist response to a clear, surfaced error. */
|
|
171
|
+
async throwForStatus(res, method) {
|
|
172
|
+
let detailCode;
|
|
173
|
+
let detailMessage = "";
|
|
174
|
+
try {
|
|
175
|
+
const errBody = await res.json();
|
|
176
|
+
detailCode = errBody.error?.status;
|
|
177
|
+
detailMessage = errBody.error?.message ?? "";
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
if (res.status === 403) {
|
|
181
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
182
|
+
`Code Assist ${method} denied (403 ${detailCode ?? "PERMISSION_DENIED"}): ${detailMessage || "enable the Cloud AI Companion API and grant the required role"}`,
|
|
183
|
+
403,
|
|
184
|
+
detailCode
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (res.status === 429) {
|
|
188
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
189
|
+
`Code Assist ${method} rate-limited (429): ${detailMessage || "quota exceeded, retry later"}`,
|
|
190
|
+
429,
|
|
191
|
+
detailCode
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
195
|
+
`Code Assist ${method} failed (${res.status}): ${detailMessage || res.statusText}`,
|
|
196
|
+
res.status,
|
|
197
|
+
detailCode
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
function delay(ms) {
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
setTimeout(resolve, ms);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
var _resolverSingleton = null;
|
|
207
|
+
function getGeminiCodeAssistProjectResolver() {
|
|
208
|
+
if (!_resolverSingleton) {
|
|
209
|
+
_resolverSingleton = new GeminiCodeAssistProjectResolver();
|
|
210
|
+
}
|
|
211
|
+
return _resolverSingleton;
|
|
212
|
+
}
|
|
213
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
214
|
+
0 && (module.exports = {
|
|
215
|
+
GeminiCodeAssistHandshakeError,
|
|
216
|
+
GeminiCodeAssistProjectResolver,
|
|
217
|
+
getGeminiCodeAssistProjectResolver
|
|
218
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeminiCodeAssistProjectResolver — runs the Google Code Assist project
|
|
3
|
+
* handshake ONCE per account and caches the resolved Cloud AI Companion
|
|
4
|
+
* project id.
|
|
5
|
+
*
|
|
6
|
+
* The gemini SUBSCRIPTION upstream (`cloudcode-pa.googleapis.com`) requires a
|
|
7
|
+
* project id in its generateContent envelope for paid tiers. Brand-new
|
|
8
|
+
* free-tier accounts have NO project (and sending one → Precondition Failed),
|
|
9
|
+
* so `undefined` is a VALID resolved value. The resolution dance mirrors
|
|
10
|
+
* gemini-cli `packages/core/src/code_assist/setup.ts`:
|
|
11
|
+
*
|
|
12
|
+
* 1. seed `projectId = GOOGLE_CLOUD_PROJECT || GOOGLE_CLOUD_PROJECT_ID`
|
|
13
|
+
* (may be empty; a purely-numeric value is rejected — Code Assist wants
|
|
14
|
+
* the human-readable project id, not the number).
|
|
15
|
+
* 2. POST `:loadCodeAssist` with `{ cloudaicompanionProject, metadata }`.
|
|
16
|
+
* - If the response carries `currentTier` → already onboarded; use the
|
|
17
|
+
* response's `cloudaicompanionProject` (may be undefined for free-tier).
|
|
18
|
+
* - Else pick the onboarding tier (first `allowedTiers` with `isDefault`,
|
|
19
|
+
* default `legacy-tier`) and POST `:onboardUser`. For free-tier /
|
|
20
|
+
* legacy-tier the `cloudaicompanionProject` MUST be undefined; for
|
|
21
|
+
* standard-tier include it. `:onboardUser` returns an LRO — poll
|
|
22
|
+
* `:getOperation` until `done`, then read
|
|
23
|
+
* `response.cloudaicompanionProject.id`.
|
|
24
|
+
*
|
|
25
|
+
* Resolution result is cached in-memory keyed by access token (1:1 with the
|
|
26
|
+
* account) so the handshake runs at most once per token. On a documented hard
|
|
27
|
+
* failure (403 SERVICE_DISABLED / PERMISSION_DENIED, 429) we throw a clear
|
|
28
|
+
* error WITHOUT caching, so a transient/permission issue can be retried after
|
|
29
|
+
* the user fixes it.
|
|
30
|
+
*
|
|
31
|
+
* @module @omnicross/core/auth/GeminiCodeAssistProjectResolver
|
|
32
|
+
*/
|
|
33
|
+
/** A clear, surfaced error for the documented hard-failure modes. */
|
|
34
|
+
declare class GeminiCodeAssistHandshakeError extends Error {
|
|
35
|
+
readonly status: number;
|
|
36
|
+
readonly code?: string | undefined;
|
|
37
|
+
constructor(message: string, status: number, code?: string | undefined);
|
|
38
|
+
}
|
|
39
|
+
/** Injectable fetch so tests can mock the network without a live endpoint. */
|
|
40
|
+
type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
|
|
41
|
+
declare class GeminiCodeAssistProjectResolver {
|
|
42
|
+
private readonly fetchImpl;
|
|
43
|
+
/** account access token → resolved project id (undefined = free-tier, no project). */
|
|
44
|
+
private readonly cache;
|
|
45
|
+
/** In-flight handshakes so concurrent callers share one round-trip. */
|
|
46
|
+
private readonly inflight;
|
|
47
|
+
constructor(fetchImpl?: FetchLike);
|
|
48
|
+
/** Test/diagnostic helper — clear the cached resolution for an account. */
|
|
49
|
+
clearCache(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve (and cache) the Code Assist project for the given access token.
|
|
52
|
+
* Runs the handshake at most once per token. Returns `undefined` for a
|
|
53
|
+
* brand-new free-tier account (valid — the envelope sends no project).
|
|
54
|
+
*/
|
|
55
|
+
resolveProject(accessToken: string): Promise<string | undefined>;
|
|
56
|
+
private codeAssistUrl;
|
|
57
|
+
/** Seed project id from env; reject a purely-numeric value (project NUMBER). */
|
|
58
|
+
private seedProjectId;
|
|
59
|
+
private runHandshake;
|
|
60
|
+
/** POST a Code Assist method, surfacing the documented hard failures clearly. */
|
|
61
|
+
private postCodeAssist;
|
|
62
|
+
private getOperation;
|
|
63
|
+
/** Map a non-2xx Code Assist response to a clear, surfaced error. */
|
|
64
|
+
private throwForStatus;
|
|
65
|
+
}
|
|
66
|
+
declare function getGeminiCodeAssistProjectResolver(): GeminiCodeAssistProjectResolver;
|
|
67
|
+
|
|
68
|
+
export { type FetchLike, GeminiCodeAssistHandshakeError, GeminiCodeAssistProjectResolver, getGeminiCodeAssistProjectResolver };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeminiCodeAssistProjectResolver — runs the Google Code Assist project
|
|
3
|
+
* handshake ONCE per account and caches the resolved Cloud AI Companion
|
|
4
|
+
* project id.
|
|
5
|
+
*
|
|
6
|
+
* The gemini SUBSCRIPTION upstream (`cloudcode-pa.googleapis.com`) requires a
|
|
7
|
+
* project id in its generateContent envelope for paid tiers. Brand-new
|
|
8
|
+
* free-tier accounts have NO project (and sending one → Precondition Failed),
|
|
9
|
+
* so `undefined` is a VALID resolved value. The resolution dance mirrors
|
|
10
|
+
* gemini-cli `packages/core/src/code_assist/setup.ts`:
|
|
11
|
+
*
|
|
12
|
+
* 1. seed `projectId = GOOGLE_CLOUD_PROJECT || GOOGLE_CLOUD_PROJECT_ID`
|
|
13
|
+
* (may be empty; a purely-numeric value is rejected — Code Assist wants
|
|
14
|
+
* the human-readable project id, not the number).
|
|
15
|
+
* 2. POST `:loadCodeAssist` with `{ cloudaicompanionProject, metadata }`.
|
|
16
|
+
* - If the response carries `currentTier` → already onboarded; use the
|
|
17
|
+
* response's `cloudaicompanionProject` (may be undefined for free-tier).
|
|
18
|
+
* - Else pick the onboarding tier (first `allowedTiers` with `isDefault`,
|
|
19
|
+
* default `legacy-tier`) and POST `:onboardUser`. For free-tier /
|
|
20
|
+
* legacy-tier the `cloudaicompanionProject` MUST be undefined; for
|
|
21
|
+
* standard-tier include it. `:onboardUser` returns an LRO — poll
|
|
22
|
+
* `:getOperation` until `done`, then read
|
|
23
|
+
* `response.cloudaicompanionProject.id`.
|
|
24
|
+
*
|
|
25
|
+
* Resolution result is cached in-memory keyed by access token (1:1 with the
|
|
26
|
+
* account) so the handshake runs at most once per token. On a documented hard
|
|
27
|
+
* failure (403 SERVICE_DISABLED / PERMISSION_DENIED, 429) we throw a clear
|
|
28
|
+
* error WITHOUT caching, so a transient/permission issue can be retried after
|
|
29
|
+
* the user fixes it.
|
|
30
|
+
*
|
|
31
|
+
* @module @omnicross/core/auth/GeminiCodeAssistProjectResolver
|
|
32
|
+
*/
|
|
33
|
+
/** A clear, surfaced error for the documented hard-failure modes. */
|
|
34
|
+
declare class GeminiCodeAssistHandshakeError extends Error {
|
|
35
|
+
readonly status: number;
|
|
36
|
+
readonly code?: string | undefined;
|
|
37
|
+
constructor(message: string, status: number, code?: string | undefined);
|
|
38
|
+
}
|
|
39
|
+
/** Injectable fetch so tests can mock the network without a live endpoint. */
|
|
40
|
+
type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
|
|
41
|
+
declare class GeminiCodeAssistProjectResolver {
|
|
42
|
+
private readonly fetchImpl;
|
|
43
|
+
/** account access token → resolved project id (undefined = free-tier, no project). */
|
|
44
|
+
private readonly cache;
|
|
45
|
+
/** In-flight handshakes so concurrent callers share one round-trip. */
|
|
46
|
+
private readonly inflight;
|
|
47
|
+
constructor(fetchImpl?: FetchLike);
|
|
48
|
+
/** Test/diagnostic helper — clear the cached resolution for an account. */
|
|
49
|
+
clearCache(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve (and cache) the Code Assist project for the given access token.
|
|
52
|
+
* Runs the handshake at most once per token. Returns `undefined` for a
|
|
53
|
+
* brand-new free-tier account (valid — the envelope sends no project).
|
|
54
|
+
*/
|
|
55
|
+
resolveProject(accessToken: string): Promise<string | undefined>;
|
|
56
|
+
private codeAssistUrl;
|
|
57
|
+
/** Seed project id from env; reject a purely-numeric value (project NUMBER). */
|
|
58
|
+
private seedProjectId;
|
|
59
|
+
private runHandshake;
|
|
60
|
+
/** POST a Code Assist method, surfacing the documented hard failures clearly. */
|
|
61
|
+
private postCodeAssist;
|
|
62
|
+
private getOperation;
|
|
63
|
+
/** Map a non-2xx Code Assist response to a clear, surfaced error. */
|
|
64
|
+
private throwForStatus;
|
|
65
|
+
}
|
|
66
|
+
declare function getGeminiCodeAssistProjectResolver(): GeminiCodeAssistProjectResolver;
|
|
67
|
+
|
|
68
|
+
export { type FetchLike, GeminiCodeAssistHandshakeError, GeminiCodeAssistProjectResolver, getGeminiCodeAssistProjectResolver };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/transformer/transformers/GeminiCodeAssistTransformer.ts
|
|
2
|
+
var DEFAULT_CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
3
|
+
var DEFAULT_CODE_ASSIST_API_VERSION = "v1internal";
|
|
4
|
+
function resolveCodeAssistEndpoint() {
|
|
5
|
+
return (process.env.CODE_ASSIST_ENDPOINT || DEFAULT_CODE_ASSIST_ENDPOINT).replace(/\/+$/, "");
|
|
6
|
+
}
|
|
7
|
+
function resolveCodeAssistApiVersion() {
|
|
8
|
+
return process.env.CODE_ASSIST_API_VERSION || DEFAULT_CODE_ASSIST_API_VERSION;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/auth/GeminiCodeAssistProjectResolver.ts
|
|
12
|
+
var FREE_TIER_ID = "free-tier";
|
|
13
|
+
var LEGACY_TIER_ID = "legacy-tier";
|
|
14
|
+
var ONBOARD_POLL_INTERVAL_MS = 5e3;
|
|
15
|
+
var ONBOARD_MAX_POLLS = 24;
|
|
16
|
+
var GeminiCodeAssistHandshakeError = class extends Error {
|
|
17
|
+
constructor(message, status, code) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.name = "GeminiCodeAssistHandshakeError";
|
|
22
|
+
}
|
|
23
|
+
status;
|
|
24
|
+
code;
|
|
25
|
+
};
|
|
26
|
+
var GeminiCodeAssistProjectResolver = class {
|
|
27
|
+
constructor(fetchImpl = (url, init) => fetch(url, init)) {
|
|
28
|
+
this.fetchImpl = fetchImpl;
|
|
29
|
+
}
|
|
30
|
+
fetchImpl;
|
|
31
|
+
/** account access token → resolved project id (undefined = free-tier, no project). */
|
|
32
|
+
cache = /* @__PURE__ */ new Map();
|
|
33
|
+
/** In-flight handshakes so concurrent callers share one round-trip. */
|
|
34
|
+
inflight = /* @__PURE__ */ new Map();
|
|
35
|
+
/** Test/diagnostic helper — clear the cached resolution for an account. */
|
|
36
|
+
clearCache() {
|
|
37
|
+
this.cache.clear();
|
|
38
|
+
this.inflight.clear();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve (and cache) the Code Assist project for the given access token.
|
|
42
|
+
* Runs the handshake at most once per token. Returns `undefined` for a
|
|
43
|
+
* brand-new free-tier account (valid — the envelope sends no project).
|
|
44
|
+
*/
|
|
45
|
+
async resolveProject(accessToken) {
|
|
46
|
+
if (this.cache.has(accessToken)) {
|
|
47
|
+
return this.cache.get(accessToken);
|
|
48
|
+
}
|
|
49
|
+
const existing = this.inflight.get(accessToken);
|
|
50
|
+
if (existing) return existing;
|
|
51
|
+
const run = this.runHandshake(accessToken).then((project) => {
|
|
52
|
+
this.cache.set(accessToken, project);
|
|
53
|
+
return project;
|
|
54
|
+
}).finally(() => {
|
|
55
|
+
this.inflight.delete(accessToken);
|
|
56
|
+
});
|
|
57
|
+
this.inflight.set(accessToken, run);
|
|
58
|
+
return run;
|
|
59
|
+
}
|
|
60
|
+
codeAssistUrl(method) {
|
|
61
|
+
return `${resolveCodeAssistEndpoint()}/${resolveCodeAssistApiVersion()}:${method}`;
|
|
62
|
+
}
|
|
63
|
+
/** Seed project id from env; reject a purely-numeric value (project NUMBER). */
|
|
64
|
+
seedProjectId() {
|
|
65
|
+
const seed = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID || "";
|
|
66
|
+
if (seed && /^\d+$/.test(seed.trim())) return "";
|
|
67
|
+
return seed.trim();
|
|
68
|
+
}
|
|
69
|
+
async runHandshake(accessToken) {
|
|
70
|
+
const seededProject = this.seedProjectId();
|
|
71
|
+
const load = await this.postCodeAssist("loadCodeAssist", accessToken, {
|
|
72
|
+
cloudaicompanionProject: seededProject || void 0,
|
|
73
|
+
metadata: {
|
|
74
|
+
ideType: "IDE_UNSPECIFIED",
|
|
75
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
76
|
+
pluginType: "GEMINI",
|
|
77
|
+
duetProject: seededProject || void 0
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
if (load.currentTier?.id) {
|
|
81
|
+
return load.cloudaicompanionProject || void 0;
|
|
82
|
+
}
|
|
83
|
+
const defaultTier = load.allowedTiers?.find((t) => t.isDefault);
|
|
84
|
+
const tierId = defaultTier?.id ?? LEGACY_TIER_ID;
|
|
85
|
+
const isFreeish = tierId === FREE_TIER_ID || tierId === LEGACY_TIER_ID;
|
|
86
|
+
const onboardProject = isFreeish ? void 0 : seededProject || void 0;
|
|
87
|
+
let lro = await this.postCodeAssist("onboardUser", accessToken, {
|
|
88
|
+
tierId,
|
|
89
|
+
cloudaicompanionProject: onboardProject,
|
|
90
|
+
metadata: {
|
|
91
|
+
ideType: "IDE_UNSPECIFIED",
|
|
92
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
93
|
+
pluginType: "GEMINI",
|
|
94
|
+
duetProject: onboardProject
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
let polls = 0;
|
|
98
|
+
while (!lro.done && polls < ONBOARD_MAX_POLLS) {
|
|
99
|
+
await delay(ONBOARD_POLL_INTERVAL_MS);
|
|
100
|
+
polls++;
|
|
101
|
+
lro = await this.getOperation(accessToken, lro.name);
|
|
102
|
+
}
|
|
103
|
+
if (lro.error) {
|
|
104
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
105
|
+
`Code Assist onboardUser failed: ${lro.error.message ?? "unknown"}`,
|
|
106
|
+
lro.error.code ?? 500
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return lro.response?.cloudaicompanionProject?.id || void 0;
|
|
110
|
+
}
|
|
111
|
+
/** POST a Code Assist method, surfacing the documented hard failures clearly. */
|
|
112
|
+
async postCodeAssist(method, accessToken, body) {
|
|
113
|
+
const url = this.codeAssistUrl(method);
|
|
114
|
+
const res = await this.fetchImpl(url, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
Authorization: `Bearer ${accessToken}`,
|
|
118
|
+
"Content-Type": "application/json"
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify(body)
|
|
121
|
+
});
|
|
122
|
+
if (!res.ok) {
|
|
123
|
+
await this.throwForStatus(res, method);
|
|
124
|
+
}
|
|
125
|
+
return await res.json();
|
|
126
|
+
}
|
|
127
|
+
async getOperation(accessToken, name) {
|
|
128
|
+
const url = this.codeAssistUrl("getOperation");
|
|
129
|
+
const res = await this.fetchImpl(url, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: {
|
|
132
|
+
Authorization: `Bearer ${accessToken}`,
|
|
133
|
+
"Content-Type": "application/json"
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({ name })
|
|
136
|
+
});
|
|
137
|
+
if (!res.ok) {
|
|
138
|
+
await this.throwForStatus(res, "getOperation");
|
|
139
|
+
}
|
|
140
|
+
return await res.json();
|
|
141
|
+
}
|
|
142
|
+
/** Map a non-2xx Code Assist response to a clear, surfaced error. */
|
|
143
|
+
async throwForStatus(res, method) {
|
|
144
|
+
let detailCode;
|
|
145
|
+
let detailMessage = "";
|
|
146
|
+
try {
|
|
147
|
+
const errBody = await res.json();
|
|
148
|
+
detailCode = errBody.error?.status;
|
|
149
|
+
detailMessage = errBody.error?.message ?? "";
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
if (res.status === 403) {
|
|
153
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
154
|
+
`Code Assist ${method} denied (403 ${detailCode ?? "PERMISSION_DENIED"}): ${detailMessage || "enable the Cloud AI Companion API and grant the required role"}`,
|
|
155
|
+
403,
|
|
156
|
+
detailCode
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (res.status === 429) {
|
|
160
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
161
|
+
`Code Assist ${method} rate-limited (429): ${detailMessage || "quota exceeded, retry later"}`,
|
|
162
|
+
429,
|
|
163
|
+
detailCode
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
throw new GeminiCodeAssistHandshakeError(
|
|
167
|
+
`Code Assist ${method} failed (${res.status}): ${detailMessage || res.statusText}`,
|
|
168
|
+
res.status,
|
|
169
|
+
detailCode
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
function delay(ms) {
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
setTimeout(resolve, ms);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
var _resolverSingleton = null;
|
|
179
|
+
function getGeminiCodeAssistProjectResolver() {
|
|
180
|
+
if (!_resolverSingleton) {
|
|
181
|
+
_resolverSingleton = new GeminiCodeAssistProjectResolver();
|
|
182
|
+
}
|
|
183
|
+
return _resolverSingleton;
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
GeminiCodeAssistHandshakeError,
|
|
187
|
+
GeminiCodeAssistProjectResolver,
|
|
188
|
+
getGeminiCodeAssistProjectResolver
|
|
189
|
+
};
|