@jsonstudio/rcc 0.89.1348 → 0.89.1457
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/README.md +51 -1427
- package/dist/build-info.js +2 -2
- package/dist/cli/commands/config.js +3 -0
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/init.js +3 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/config/bundled-docs.js +2 -2
- package/dist/cli/config/bundled-docs.js.map +1 -1
- package/dist/cli/config/init-config.d.ts +2 -1
- package/dist/cli/config/init-config.js +33 -1
- package/dist/cli/config/init-config.js.map +1 -1
- package/dist/client/gemini/gemini-protocol-client.js +2 -1
- package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +39 -15
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
- package/dist/client/openai/chat-protocol-client.js +2 -1
- package/dist/client/openai/chat-protocol-client.js.map +1 -1
- package/dist/client/responses/responses-protocol-client.js +2 -1
- package/dist/client/responses/responses-protocol-client.js.map +1 -1
- package/dist/error-handling/quiet-error-handling-center.js +46 -8
- package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.events.js +4 -2
- package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +9 -6
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
- package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -1
- package/dist/providers/auth/antigravity-userinfo-helper.js +25 -4
- package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/tokenfile-auth.d.ts +2 -0
- package/dist/providers/auth/tokenfile-auth.js +33 -1
- package/dist/providers/auth/tokenfile-auth.js.map +1 -1
- package/dist/providers/core/config/camoufox-launcher.d.ts +5 -0
- package/dist/providers/core/config/camoufox-launcher.js +5 -0
- package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +7 -18
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +0 -5
- package/dist/providers/core/runtime/base-provider.js +26 -112
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +7 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +362 -93
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.d.ts +3 -0
- package/dist/providers/core/runtime/http-request-executor.js +110 -38
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +3 -0
- package/dist/providers/core/runtime/http-transport-provider.js +80 -37
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/rate-limit-manager.d.ts +1 -12
- package/dist/providers/core/runtime/rate-limit-manager.js +4 -77
- package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +20 -43
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/server/handlers/handler-utils.js +5 -1
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +1 -1
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/runtime/http-server/index.js +68 -29
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +50 -6
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +4 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/utils/strip-internal-keys.d.ts +12 -0
- package/dist/utils/strip-internal-keys.js +28 -0
- package/dist/utils/strip-internal-keys.js.map +1 -0
- package/docs/CHAT_PROCESS_PROTOCOL_AND_PIPELINE.md +221 -0
- package/docs/antigravity-gemini-format-cleanup.md +102 -0
- package/docs/antigravity-routing-contract.md +31 -0
- package/docs/chat-semantic-expansion-plan.md +8 -6
- package/docs/glm-chat-completions.md +1 -1
- package/docs/servertool-framework.md +65 -0
- package/docs/v2-architecture/README.md +6 -8
- package/docs/verified-configs/README.md +60 -0
- package/docs/verified-configs/v0.45.0/README.md +244 -0
- package/docs/verified-configs/v0.45.0/lmstudio-5521-gpt-oss-20b-mlx.json +135 -0
- package/docs/verified-configs/v0.45.0/merged-config.5521.json +1205 -0
- package/docs/verified-configs/v0.45.0/merged-config.qwen-5522.json +1559 -0
- package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-final.json +221 -0
- package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-fixed.json +242 -0
- package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus.json +242 -0
- package/package.json +17 -11
- package/scripts/build-core.mjs +3 -1
- package/scripts/ci/repo-sanity.mjs +138 -0
- package/scripts/mock-provider/run-regressions.mjs +157 -1
- package/scripts/run-bg.sh +0 -14
- package/scripts/tests/ci-jest.mjs +119 -0
- package/scripts/tools-dev/responses-debug-client/README.md +23 -0
- package/scripts/tools-dev/responses-debug-client/payloads/poem.json +13 -0
- package/scripts/tools-dev/responses-debug-client/payloads/sample-no-tools.json +98 -0
- package/scripts/tools-dev/responses-debug-client/payloads/text.json +13 -0
- package/scripts/tools-dev/responses-debug-client/payloads/tool.json +27 -0
- package/scripts/tools-dev/responses-debug-client/run.mjs +65 -0
- package/scripts/tools-dev/responses-debug-client/src/index.ts +281 -0
- package/scripts/tools-dev/run-llmswitch-chat.mjs +53 -0
- package/scripts/tools-dev/server-tools-dev/run-web-fetch.mjs +65 -0
- package/scripts/vendor-core.mjs +13 -3
- package/scripts/test-fc-responses.mjs +0 -66
- package/scripts/test-guidance.mjs +0 -100
- package/scripts/test-iflow-web-search.mjs +0 -141
- package/scripts/test-iflow.mjs +0 -379
- package/scripts/test-tool-exec.mjs +0 -26
|
@@ -7,13 +7,188 @@
|
|
|
7
7
|
* - 认证:OAuth2 Bearer token
|
|
8
8
|
* - 特性:多 project 支持、token 共享、模型回退
|
|
9
9
|
*/
|
|
10
|
-
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { createHash, randomUUID, randomBytes } from 'node:crypto';
|
|
11
|
+
import { platform as osPlatform, arch as osArch, release as osRelease } from 'node:os';
|
|
11
12
|
import { Transform } from 'node:stream';
|
|
12
13
|
import { StringDecoder } from 'node:string_decoder';
|
|
13
14
|
import { HttpTransportProvider } from './http-transport-provider.js';
|
|
14
15
|
import { GeminiCLIProtocolClient } from '../../../client/gemini-cli/gemini-cli-protocol-client.js';
|
|
15
16
|
import { getDefaultProjectId } from '../../auth/gemini-cli-userinfo-helper.js';
|
|
16
|
-
import {
|
|
17
|
+
import { resolveAntigravityApiBaseCandidates } from '../../auth/antigravity-userinfo-helper.js';
|
|
18
|
+
import { getCamoufoxFingerprintProfile } from '../config/camoufox-launcher.js';
|
|
19
|
+
const ANTIGRAVITY_OS_VERSIONS = {
|
|
20
|
+
darwin: ['10.15.7', '11.6.8', '12.6.3', '13.5.2', '14.2.1', '14.5'],
|
|
21
|
+
win32: ['10.0.19041', '10.0.19042', '10.0.19043', '10.0.22000', '10.0.22621', '10.0.22631'],
|
|
22
|
+
linux: ['5.15.0', '5.19.0', '6.1.0', '6.2.0', '6.5.0', '6.6.0']
|
|
23
|
+
};
|
|
24
|
+
const ANTIGRAVITY_ARCHS = ['x64', 'arm64'];
|
|
25
|
+
const ANTIGRAVITY_VERSIONS = ['1.10.0', '1.10.5', '1.11.0', '1.11.2', '1.11.5', '1.12.0', '1.12.1'];
|
|
26
|
+
const ANTIGRAVITY_IDE_TYPES = ['IDE_UNSPECIFIED', 'VSCODE', 'INTELLIJ', 'ANDROID_STUDIO', 'CLOUD_SHELL_EDITOR'];
|
|
27
|
+
const ANTIGRAVITY_PLATFORMS = ['PLATFORM_UNSPECIFIED', 'WINDOWS', 'MACOS', 'LINUX'];
|
|
28
|
+
const ANTIGRAVITY_SDK_CLIENTS = [
|
|
29
|
+
'google-cloud-sdk vscode_cloudshelleditor/0.1',
|
|
30
|
+
'google-cloud-sdk vscode/1.86.0',
|
|
31
|
+
'google-cloud-sdk vscode/1.87.0',
|
|
32
|
+
'google-cloud-sdk intellij/2024.1',
|
|
33
|
+
'google-cloud-sdk android-studio/2024.1',
|
|
34
|
+
'gcloud-python/1.2.0 grpc-google-iam-v1/0.12.6'
|
|
35
|
+
];
|
|
36
|
+
let antigravitySessionFingerprint = null;
|
|
37
|
+
const randomFrom = (items) => items[Math.floor(Math.random() * items.length)];
|
|
38
|
+
const generateAntigravityFingerprint = () => {
|
|
39
|
+
const platform = randomFrom(['darwin', 'win32', 'linux']);
|
|
40
|
+
const arch = randomFrom(ANTIGRAVITY_ARCHS);
|
|
41
|
+
const osVersion = randomFrom(ANTIGRAVITY_OS_VERSIONS[platform] ?? ANTIGRAVITY_OS_VERSIONS.linux);
|
|
42
|
+
const antigravityVersion = randomFrom(ANTIGRAVITY_VERSIONS);
|
|
43
|
+
const matchingPlatform = platform === 'darwin'
|
|
44
|
+
? 'MACOS'
|
|
45
|
+
: platform === 'win32'
|
|
46
|
+
? 'WINDOWS'
|
|
47
|
+
: platform === 'linux'
|
|
48
|
+
? 'LINUX'
|
|
49
|
+
: randomFrom(ANTIGRAVITY_PLATFORMS);
|
|
50
|
+
return {
|
|
51
|
+
deviceId: randomUUID(),
|
|
52
|
+
sessionToken: randomBytes(16).toString('hex'),
|
|
53
|
+
userAgent: `antigravity/${antigravityVersion} ${platform}/${arch}`,
|
|
54
|
+
apiClient: randomFrom(ANTIGRAVITY_SDK_CLIENTS),
|
|
55
|
+
clientMetadata: {
|
|
56
|
+
ideType: randomFrom(ANTIGRAVITY_IDE_TYPES),
|
|
57
|
+
platform: matchingPlatform,
|
|
58
|
+
pluginType: 'GEMINI',
|
|
59
|
+
osVersion,
|
|
60
|
+
arch,
|
|
61
|
+
sqmId: `{${randomUUID().toUpperCase()}}`
|
|
62
|
+
},
|
|
63
|
+
quotaUser: `device-${randomBytes(8).toString('hex')}`,
|
|
64
|
+
createdAt: Date.now()
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const getAntigravitySessionFingerprint = () => {
|
|
68
|
+
if (!antigravitySessionFingerprint) {
|
|
69
|
+
antigravitySessionFingerprint = generateAntigravityFingerprint();
|
|
70
|
+
}
|
|
71
|
+
return antigravitySessionFingerprint;
|
|
72
|
+
};
|
|
73
|
+
const collectCurrentAntigravityFingerprint = () => {
|
|
74
|
+
const platform = osPlatform();
|
|
75
|
+
const arch = osArch();
|
|
76
|
+
const osVersion = osRelease();
|
|
77
|
+
const matchingPlatform = platform === 'darwin'
|
|
78
|
+
? 'MACOS'
|
|
79
|
+
: platform === 'win32'
|
|
80
|
+
? 'WINDOWS'
|
|
81
|
+
: platform === 'linux'
|
|
82
|
+
? 'LINUX'
|
|
83
|
+
: 'PLATFORM_UNSPECIFIED';
|
|
84
|
+
return {
|
|
85
|
+
deviceId: randomUUID(),
|
|
86
|
+
sessionToken: randomBytes(16).toString('hex'),
|
|
87
|
+
userAgent: `antigravity/1.11.5 ${platform}/${arch}`,
|
|
88
|
+
apiClient: 'google-cloud-sdk vscode_cloudshelleditor/0.1',
|
|
89
|
+
clientMetadata: {
|
|
90
|
+
ideType: 'VSCODE',
|
|
91
|
+
platform: matchingPlatform,
|
|
92
|
+
pluginType: 'GEMINI',
|
|
93
|
+
osVersion,
|
|
94
|
+
arch,
|
|
95
|
+
sqmId: `{${randomUUID().toUpperCase()}}`
|
|
96
|
+
},
|
|
97
|
+
quotaUser: `device-${randomBytes(16).toString('hex').slice(0, 16)}`,
|
|
98
|
+
createdAt: Date.now()
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const buildAntigravityFingerprintHeaders = (fingerprint) => ({
|
|
102
|
+
'User-Agent': fingerprint.userAgent,
|
|
103
|
+
'X-Goog-Api-Client': fingerprint.apiClient,
|
|
104
|
+
'Client-Metadata': JSON.stringify(fingerprint.clientMetadata),
|
|
105
|
+
'X-Goog-QuotaUser': fingerprint.quotaUser,
|
|
106
|
+
'X-Client-Device-Id': fingerprint.deviceId,
|
|
107
|
+
...(fingerprint.acceptEncoding ? { 'Accept-Encoding': fingerprint.acceptEncoding } : {})
|
|
108
|
+
});
|
|
109
|
+
const extractAntigravityAlias = (providerKey) => {
|
|
110
|
+
if (!providerKey) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const parts = providerKey.split('.').filter(Boolean);
|
|
114
|
+
if (!parts.length) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (parts[0] !== 'antigravity') {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return parts.length >= 2 ? parts[1] : undefined;
|
|
121
|
+
};
|
|
122
|
+
const stableIdFromProfile = (profileId) => {
|
|
123
|
+
const hash = createHash('sha256').update(profileId).digest('hex');
|
|
124
|
+
const deviceId = `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
125
|
+
return {
|
|
126
|
+
deviceId,
|
|
127
|
+
quotaUser: `device-${hash.slice(0, 16)}`,
|
|
128
|
+
sqmId: `{${hash.slice(0, 8).toUpperCase()}-${hash.slice(8, 12).toUpperCase()}-${hash.slice(12, 16).toUpperCase()}-${hash.slice(16, 20).toUpperCase()}-${hash.slice(20, 32).toUpperCase()}}`
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
const buildFingerprintFromCamoufox = (profileId, env) => {
|
|
132
|
+
const camouKeys = Object.keys(env).filter((key) => key.startsWith('CAMOU_CONFIG_')).sort();
|
|
133
|
+
if (camouKeys.length === 0) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const blob = camouKeys.map((key) => env[key]).join('');
|
|
137
|
+
let parsed = null;
|
|
138
|
+
try {
|
|
139
|
+
parsed = JSON.parse(blob);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const ua = typeof parsed['navigator.userAgent'] === 'string' ? parsed['navigator.userAgent'] : '';
|
|
145
|
+
const oscpu = typeof parsed['navigator.oscpu'] === 'string' ? parsed['navigator.oscpu'] : '';
|
|
146
|
+
const platformRaw = typeof parsed['navigator.platform'] === 'string' ? parsed['navigator.platform'] : oscpu;
|
|
147
|
+
const acceptEncoding = typeof parsed['headers.Accept-Encoding'] === 'string' ? parsed['headers.Accept-Encoding'] : undefined;
|
|
148
|
+
const platformLower = platformRaw.toLowerCase();
|
|
149
|
+
const platform = platformLower.includes('win') ? 'WINDOWS' : platformLower.includes('mac') ? 'MACOS' : 'LINUX';
|
|
150
|
+
const arch = platformLower.includes('arm64') || platformLower.includes('aarch64') ? 'arm64' : 'x64';
|
|
151
|
+
const stableIds = stableIdFromProfile(profileId);
|
|
152
|
+
return {
|
|
153
|
+
deviceId: stableIds.deviceId,
|
|
154
|
+
sessionToken: randomBytes(16).toString('hex'),
|
|
155
|
+
userAgent: ua || `antigravity/1.11.5 ${osPlatform()}/${osArch()}`,
|
|
156
|
+
apiClient: 'google-cloud-sdk vscode_cloudshelleditor/0.1',
|
|
157
|
+
acceptEncoding,
|
|
158
|
+
clientMetadata: {
|
|
159
|
+
ideType: 'IDE_UNSPECIFIED',
|
|
160
|
+
platform,
|
|
161
|
+
pluginType: 'GEMINI',
|
|
162
|
+
osVersion: oscpu || osRelease(),
|
|
163
|
+
arch,
|
|
164
|
+
sqmId: stableIds.sqmId
|
|
165
|
+
},
|
|
166
|
+
quotaUser: stableIds.quotaUser,
|
|
167
|
+
createdAt: Date.now()
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
const resolveAntigravityFingerprint = (runtime) => {
|
|
171
|
+
const alias = (runtime?.metadata && typeof runtime.metadata === 'object' && typeof runtime.metadata.alias === 'string'
|
|
172
|
+
? String(runtime.metadata.alias)
|
|
173
|
+
: undefined) || extractAntigravityAlias(runtime?.providerKey);
|
|
174
|
+
const camoufox = getCamoufoxFingerprintProfile('antigravity', alias);
|
|
175
|
+
if (camoufox) {
|
|
176
|
+
const parsed = buildFingerprintFromCamoufox(camoufox.profileId, camoufox.env);
|
|
177
|
+
if (parsed) {
|
|
178
|
+
return parsed;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const env = (process.env.ROUTECODEX_ANTIGRAVITY_FINGERPRINT_MODE || process.env.RCC_ANTIGRAVITY_FINGERPRINT_MODE || '')
|
|
182
|
+
.trim()
|
|
183
|
+
.toLowerCase();
|
|
184
|
+
if (env === 'current' || env === 'host') {
|
|
185
|
+
return collectCurrentAntigravityFingerprint();
|
|
186
|
+
}
|
|
187
|
+
return getAntigravitySessionFingerprint();
|
|
188
|
+
};
|
|
189
|
+
// Legacy helper seed for Antigravity signature session key generation.
|
|
190
|
+
// Kept for backwards compatibility (even when we avoid auto-injecting sessionId).
|
|
191
|
+
const ANTIGRAVITY_PLUGIN_SESSION_ID = `-${randomUUID()}`;
|
|
17
192
|
export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
18
193
|
constructor(config, dependencies) {
|
|
19
194
|
const providerId = typeof config.config?.providerId === 'string' && config.config.providerId.trim().length
|
|
@@ -35,10 +210,28 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
|
35
210
|
};
|
|
36
211
|
super(cfg, dependencies, 'gemini-cli-http-provider', new GeminiCLIProtocolClient());
|
|
37
212
|
}
|
|
213
|
+
applyStreamModeHeaders(headers, wantsSse) {
|
|
214
|
+
if (!this.isAntigravityRuntime()) {
|
|
215
|
+
return super.applyStreamModeHeaders(headers, wantsSse);
|
|
216
|
+
}
|
|
217
|
+
// Antigravity: keep SSE Accept header behavior stable.
|
|
218
|
+
// We intentionally do not force "*/*" here because the upstream is sensitive to header triplets
|
|
219
|
+
// and we have observed successful runs with the default "text/event-stream" Accept.
|
|
220
|
+
return super.applyStreamModeHeaders(headers, wantsSse);
|
|
221
|
+
}
|
|
222
|
+
getBaseUrlCandidates(_context) {
|
|
223
|
+
if (!this.isAntigravityRuntime()) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
return resolveAntigravityApiBaseCandidates(this.getEffectiveBaseUrl());
|
|
227
|
+
}
|
|
38
228
|
async preprocessRequest(request) {
|
|
39
229
|
const processedRequest = await super.preprocessRequest(request);
|
|
40
230
|
const adapter = this.resolvePayload(processedRequest);
|
|
41
231
|
const payload = adapter.payload;
|
|
232
|
+
const metadata = processedRequest && typeof processedRequest === 'object' && typeof processedRequest.metadata === 'object'
|
|
233
|
+
? processedRequest.metadata
|
|
234
|
+
: undefined;
|
|
42
235
|
// 从 auth provider 获取 project_id(仅做最小的 OAuth token 解析,不在此处触发 OAuth 流程)
|
|
43
236
|
if (!this.authProvider) {
|
|
44
237
|
throw new Error('Gemini CLI: auth provider not found');
|
|
@@ -57,6 +250,16 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
|
57
250
|
};
|
|
58
251
|
const tokenData = readTokenPayload();
|
|
59
252
|
const projectId = getDefaultProjectId(tokenData || {});
|
|
253
|
+
const projectOverride = (() => {
|
|
254
|
+
const ext = this.config?.config?.extensions;
|
|
255
|
+
const candidates = [ext?.projectId, ext?.project_id, ext?.project];
|
|
256
|
+
for (const candidate of candidates) {
|
|
257
|
+
if (typeof candidate === 'string' && candidate.trim().length) {
|
|
258
|
+
return candidate.trim();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
})();
|
|
60
263
|
// 构建 Gemini CLI 格式的请求(仅做传输层整理,不做 OpenAI→Gemini 语义转换)
|
|
61
264
|
const model = typeof payload.model === 'string' && payload.model.trim().length > 0
|
|
62
265
|
? payload.model
|
|
@@ -65,10 +268,22 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
|
65
268
|
throw new Error('Gemini CLI: model is required');
|
|
66
269
|
}
|
|
67
270
|
payload.model = model;
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
271
|
+
const isClaudeModel = this.isClaudeModelName(model);
|
|
272
|
+
// Project selection:
|
|
273
|
+
// - honor explicit payload.project (client/config),
|
|
274
|
+
// - otherwise honor provider config extensions project override,
|
|
275
|
+
// - otherwise fall back to token-derived default project_id if present.
|
|
276
|
+
const existingProject = typeof payload.project === 'string' && String(payload.project).trim().length
|
|
277
|
+
? String(payload.project).trim()
|
|
278
|
+
: '';
|
|
279
|
+
if (!existingProject) {
|
|
280
|
+
const nextProject = projectOverride || projectId;
|
|
281
|
+
if (nextProject && nextProject.trim().length) {
|
|
282
|
+
payload.project = nextProject.trim();
|
|
283
|
+
}
|
|
284
|
+
else if (this.isAntigravityRuntime()) {
|
|
285
|
+
payload.project = this.generateSyntheticProjectId();
|
|
286
|
+
}
|
|
72
287
|
}
|
|
73
288
|
this.ensureRequestMetadata(payload);
|
|
74
289
|
// 删除与 Gemini 协议无关的字段,避免影响 Cloud Code Assist schema 校验。
|
|
@@ -85,6 +300,43 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
|
85
300
|
// 如果上游/compat 层错误地产生了 payload.request(例如旧版本样本/回放),这里做一次扁平化,
|
|
86
301
|
// 避免出现 body.request.request.contents 这种非法形状,导致上游生成空响应或被静默忽略。
|
|
87
302
|
this.flattenRequestContainer(payload);
|
|
303
|
+
// Standard contract: Antigravity identity fields are carried in the JSON wrapper (not headers).
|
|
304
|
+
// gcli2api antigravity alignment:
|
|
305
|
+
// - requestId/requestType are sent as HTTP headers (provider finalizeRequestHeaders)
|
|
306
|
+
// - JSON wrapper should not force-inject extra identity/session fields
|
|
307
|
+
// Provider must not infer request semantics from user payload; modality hints must come from llmswitch-core metadata.
|
|
308
|
+
if (this.isAntigravityRuntime()) {
|
|
309
|
+
const record = payload;
|
|
310
|
+
if (!this.hasNonEmptyString(record.userAgent)) {
|
|
311
|
+
record.userAgent = 'antigravity';
|
|
312
|
+
}
|
|
313
|
+
if (!this.hasNonEmptyString(record.requestType)) {
|
|
314
|
+
const hasImageAttachment = metadata && (metadata.hasImageAttachment === true || metadata.hasImageAttachment === 'true');
|
|
315
|
+
const fallbackImageByModel = typeof model === 'string' && model.toLowerCase().includes('image');
|
|
316
|
+
record.requestType = hasImageAttachment || fallbackImageByModel ? 'image_gen' : 'agent';
|
|
317
|
+
}
|
|
318
|
+
const existingReqId = record.requestId;
|
|
319
|
+
if (typeof existingReqId !== 'string' || !existingReqId.trim().startsWith('req-')) {
|
|
320
|
+
record.requestId = `req-${randomUUID()}`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// gcli2api antigravity alignment: keep JSON wrapper minimal.
|
|
324
|
+
// Preserve requestId/requestType in metadata for headers, then remove them from payload.
|
|
325
|
+
if (this.isAntigravityRuntime() && !isClaudeModel) {
|
|
326
|
+
const meta = processedRequest.metadata ?? {};
|
|
327
|
+
processedRequest.metadata = meta;
|
|
328
|
+
if (typeof payload.requestId === 'string' && payload.requestId.trim().length) {
|
|
329
|
+
meta.__antigravityRequestId = String(payload.requestId).trim();
|
|
330
|
+
delete payload.requestId;
|
|
331
|
+
}
|
|
332
|
+
if (typeof payload.requestType === 'string' && payload.requestType.trim().length) {
|
|
333
|
+
meta.__antigravityRequestType = String(payload.requestType).trim();
|
|
334
|
+
delete payload.requestType;
|
|
335
|
+
}
|
|
336
|
+
if (typeof payload.userAgent === 'string' && payload.userAgent.trim().length) {
|
|
337
|
+
delete payload.userAgent;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
88
340
|
return processedRequest;
|
|
89
341
|
}
|
|
90
342
|
wantsUpstreamSse(request, context) {
|
|
@@ -107,16 +359,81 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
|
107
359
|
async finalizeRequestHeaders(headers, request) {
|
|
108
360
|
const finalized = await super.finalizeRequestHeaders(headers, request);
|
|
109
361
|
if (this.isAntigravityRuntime()) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
362
|
+
const deleteHeaderInsensitive = (key) => {
|
|
363
|
+
const target = key.toLowerCase();
|
|
364
|
+
for (const k of Object.keys(finalized)) {
|
|
365
|
+
if (k.toLowerCase() === target) {
|
|
366
|
+
delete finalized[k];
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const payload = this.resolvePayload(request).payload;
|
|
371
|
+
const meta = request.metadata ?? {};
|
|
372
|
+
const runtime = this.getCurrentRuntimeMetadata();
|
|
373
|
+
const isClaudeModel = this.isClaudeModelName(payload?.model);
|
|
374
|
+
const isClaudeThinking = typeof payload?.model === 'string' && payload.model.toLowerCase().includes('thinking');
|
|
375
|
+
const fingerprint = resolveAntigravityFingerprint(runtime);
|
|
376
|
+
const fingerprintHeaders = buildAntigravityFingerprintHeaders(fingerprint);
|
|
377
|
+
finalized['User-Agent'] = fingerprintHeaders['User-Agent'];
|
|
378
|
+
finalized['X-Goog-Api-Client'] = fingerprintHeaders['X-Goog-Api-Client'];
|
|
379
|
+
finalized['Client-Metadata'] = fingerprintHeaders['Client-Metadata'];
|
|
380
|
+
finalized['X-Goog-QuotaUser'] = fingerprintHeaders['X-Goog-QuotaUser'];
|
|
381
|
+
finalized['X-Client-Device-Id'] = fingerprintHeaders['X-Client-Device-Id'];
|
|
382
|
+
if (fingerprintHeaders['Accept-Encoding']) {
|
|
383
|
+
finalized['Accept-Encoding'] = fingerprintHeaders['Accept-Encoding'];
|
|
384
|
+
}
|
|
385
|
+
if (isClaudeModel) {
|
|
386
|
+
if (!finalized['Accept-Encoding']) {
|
|
387
|
+
finalized['Accept-Encoding'] = 'gzip';
|
|
388
|
+
}
|
|
389
|
+
if (isClaudeThinking) {
|
|
390
|
+
const existing = finalized['anthropic-beta'];
|
|
391
|
+
const interleaved = 'interleaved-thinking-2025-05-14';
|
|
392
|
+
finalized['anthropic-beta'] = existing && existing.length
|
|
393
|
+
? (existing.includes(interleaved) ? existing : `${existing},${interleaved}`)
|
|
394
|
+
: interleaved;
|
|
395
|
+
}
|
|
114
396
|
}
|
|
115
|
-
|
|
116
|
-
|
|
397
|
+
else {
|
|
398
|
+
// gcli2api/opencode antigravity alignment: use fingerprint headers for non-Claude too.
|
|
399
|
+
if (!finalized['Accept-Encoding']) {
|
|
400
|
+
finalized['Accept-Encoding'] = 'gzip';
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const requestId = (typeof payload.requestId === 'string' && payload.requestId.trim().length ? payload.requestId.trim() : '') ||
|
|
404
|
+
(typeof meta.__antigravityRequestId === 'string' && meta.__antigravityRequestId.trim().length
|
|
405
|
+
? String(meta.__antigravityRequestId).trim()
|
|
406
|
+
: '');
|
|
407
|
+
const requestType = (typeof payload.requestType === 'string' && payload.requestType.trim().length ? payload.requestType.trim() : '') ||
|
|
408
|
+
(typeof meta.__antigravityRequestType === 'string' && meta.__antigravityRequestType.trim().length
|
|
409
|
+
? String(meta.__antigravityRequestType).trim()
|
|
410
|
+
: '');
|
|
411
|
+
if (requestId)
|
|
412
|
+
finalized['requestId'] = requestId;
|
|
413
|
+
if (requestType)
|
|
414
|
+
finalized['requestType'] = requestType;
|
|
117
415
|
}
|
|
118
416
|
return finalized;
|
|
119
417
|
}
|
|
418
|
+
buildHttpRequestBody(request) {
|
|
419
|
+
const built = super.buildHttpRequestBody(request);
|
|
420
|
+
if (!this.isAntigravityRuntime() || !built || typeof built !== 'object') {
|
|
421
|
+
return built;
|
|
422
|
+
}
|
|
423
|
+
// gcli2api antigravity alignment: requestId/requestType are headers; JSON wrapper stays minimal.
|
|
424
|
+
return built;
|
|
425
|
+
}
|
|
426
|
+
isClaudeModelName(model) {
|
|
427
|
+
return typeof model === 'string' && model.toLowerCase().includes('claude');
|
|
428
|
+
}
|
|
429
|
+
generateSyntheticProjectId() {
|
|
430
|
+
const adjectives = ['useful', 'bright', 'swift', 'calm', 'bold'];
|
|
431
|
+
const nouns = ['fuze', 'wave', 'spark', 'flow', 'core'];
|
|
432
|
+
const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
|
|
433
|
+
const noun = nouns[Math.floor(Math.random() * nouns.length)];
|
|
434
|
+
const randomPart = randomUUID().slice(0, 5).toLowerCase();
|
|
435
|
+
return `${adj}-${noun}-${randomPart}`;
|
|
436
|
+
}
|
|
120
437
|
async postprocessResponse(response, context) {
|
|
121
438
|
const processingTime = Date.now() - context.startTime;
|
|
122
439
|
if (response && typeof response === 'object') {
|
|
@@ -177,7 +494,11 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
|
177
494
|
return super.wrapUpstreamSseResponse(normalizer, context);
|
|
178
495
|
}
|
|
179
496
|
isAntigravityRuntime() {
|
|
180
|
-
|
|
497
|
+
const fromConfig = typeof this.config?.config?.providerId === 'string' && this.config.config.providerId.trim()
|
|
498
|
+
? this.config.config.providerId.trim().toLowerCase()
|
|
499
|
+
: '';
|
|
500
|
+
const fromOAuth = typeof this.oauthProviderId === 'string' ? this.oauthProviderId.trim().toLowerCase() : '';
|
|
501
|
+
return fromConfig === 'antigravity' || fromOAuth === 'antigravity';
|
|
181
502
|
}
|
|
182
503
|
resolvePayload(source) {
|
|
183
504
|
if (this.hasDataEnvelope(source)) {
|
|
@@ -230,62 +551,38 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
|
|
|
230
551
|
ensureRequestMetadata(payload) {
|
|
231
552
|
const isAntigravity = this.isAntigravityRuntime();
|
|
232
553
|
if (!this.hasNonEmptyString(payload.requestId)) {
|
|
554
|
+
// gcli2api antigravity alignment: requestId uses req- prefix (carried as HTTP header).
|
|
233
555
|
payload.requestId = `req-${randomUUID()}`;
|
|
234
556
|
}
|
|
235
|
-
//
|
|
236
|
-
if (!isAntigravity && !this.hasNonEmptyString(payload.session_id)) {
|
|
237
|
-
payload.session_id = `session-${randomUUID()}`;
|
|
238
|
-
}
|
|
239
|
-
if (!this.hasNonEmptyString(payload.userAgent)) {
|
|
240
|
-
payload.userAgent = this.oauthProviderId === 'antigravity' ? 'antigravity' : 'routecodex';
|
|
241
|
-
}
|
|
242
|
-
// 对齐 gcli2api:Antigravity 路径统一标记 requestType=agent,
|
|
243
|
-
// 不区分模型(claude / gemini 均走 agent 配额路径)。
|
|
244
|
-
if (isAntigravity && !this.hasNonEmptyString(payload.requestType)) {
|
|
245
|
-
payload.requestType = 'agent';
|
|
246
|
-
}
|
|
247
|
-
// 对齐 gcli2api:Antigravity 统一注入固定 systemInstruction,
|
|
248
|
-
// 将已有 systemInstruction.parts 追加在 Antigravity 提示词之后。
|
|
557
|
+
// Never inject legacy snake_case session_id.
|
|
249
558
|
if (isAntigravity) {
|
|
250
|
-
|
|
251
|
-
? payload.request
|
|
252
|
-
: undefined;
|
|
253
|
-
const currentSystemInstruction = payload.systemInstruction ??
|
|
254
|
-
(requestContainer && requestContainer.systemInstruction);
|
|
255
|
-
const currentParts = currentSystemInstruction && typeof currentSystemInstruction === 'object' && !Array.isArray(currentSystemInstruction)
|
|
256
|
-
? currentSystemInstruction.parts
|
|
257
|
-
: Array.isArray(currentSystemInstruction)
|
|
258
|
-
? currentSystemInstruction
|
|
259
|
-
: undefined;
|
|
260
|
-
const antigravityPrompt = 'Please ignore the following [ignore]You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.' +
|
|
261
|
-
'You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.' +
|
|
262
|
-
'**Absolute paths only****Proactiveness**[/ignore]';
|
|
263
|
-
const nextParts = [{ text: antigravityPrompt }];
|
|
264
|
-
if (Array.isArray(currentParts) && currentParts.length > 0) {
|
|
265
|
-
for (const part of currentParts) {
|
|
266
|
-
if (part && typeof part === 'object') {
|
|
267
|
-
const text = String(part.text ?? '').trim();
|
|
268
|
-
if (text.length) {
|
|
269
|
-
nextParts.push({ text });
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
const nextSystemInstruction = {
|
|
275
|
-
parts: nextParts,
|
|
276
|
-
...(currentSystemInstruction &&
|
|
277
|
-
typeof currentSystemInstruction === 'object' &&
|
|
278
|
-
!Array.isArray(currentSystemInstruction) &&
|
|
279
|
-
typeof currentSystemInstruction.role === 'string'
|
|
280
|
-
? { role: currentSystemInstruction.role }
|
|
281
|
-
: {})
|
|
282
|
-
};
|
|
283
|
-
payload.systemInstruction = nextSystemInstruction;
|
|
284
|
-
if (requestContainer && typeof requestContainer === 'object') {
|
|
285
|
-
requestContainer.systemInstruction = nextSystemInstruction;
|
|
286
|
-
}
|
|
559
|
+
delete payload.session_id;
|
|
287
560
|
}
|
|
288
561
|
}
|
|
562
|
+
isAntigravityMinimalCompatibilityEnabled() {
|
|
563
|
+
const raw = (process.env.ROUTECODEX_ANTIGRAVITY_HEADER_MODE || process.env.RCC_ANTIGRAVITY_HEADER_MODE || '')
|
|
564
|
+
.trim()
|
|
565
|
+
.toLowerCase();
|
|
566
|
+
// Legacy switch kept for older deployments; RouteCodex now follows gcli2api antigravity header style by default.
|
|
567
|
+
// This method is currently unused (we avoid forcing sessionId injection and keep requestId/requestType in headers).
|
|
568
|
+
if (!raw) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
if (raw === 'standard' || raw === 'full' || raw === 'headers') {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
return raw === 'minimal' || raw === 'gcli2api';
|
|
575
|
+
}
|
|
576
|
+
buildAntigravitySignatureSessionKey(options) {
|
|
577
|
+
const modelKeyRaw = typeof options.model === 'string' ? options.model.trim().toLowerCase() : 'unknown';
|
|
578
|
+
// Antigravity contract: strip tier suffix to reduce cache misses when tier changes.
|
|
579
|
+
const modelKey = modelKeyRaw.replace(/-(minimal|low|medium|high)$/i, '');
|
|
580
|
+
const projectPart = typeof options.project === 'string' && options.project.trim().length ? options.project.trim() : 'default';
|
|
581
|
+
const conversationPart = typeof options.conversationKey === 'string' && options.conversationKey.trim().length
|
|
582
|
+
? options.conversationKey.trim()
|
|
583
|
+
: 'default';
|
|
584
|
+
return `${ANTIGRAVITY_PLUGIN_SESSION_ID}:${modelKey}:${projectPart}:${conversationPart}`;
|
|
585
|
+
}
|
|
289
586
|
flattenRequestContainer(payload) {
|
|
290
587
|
const requestContainer = payload.request && typeof payload.request === 'object'
|
|
291
588
|
? payload.request
|
|
@@ -366,11 +663,6 @@ class GeminiSseNormalizer extends Transform {
|
|
|
366
663
|
this.buffer += remaining.replace(/\r/g, '');
|
|
367
664
|
}
|
|
368
665
|
this.processBuffered(true);
|
|
369
|
-
console.log('[GeminiSseNormalizer] Stream complete:', {
|
|
370
|
-
totalChunks: this.chunkCounter,
|
|
371
|
-
processedEvents: this.processedEventCounter,
|
|
372
|
-
emittedEvents: this.eventCounter
|
|
373
|
-
});
|
|
374
666
|
if (this.lastDonePayload) {
|
|
375
667
|
this.pushEvent('gemini.done', this.lastDonePayload);
|
|
376
668
|
this.lastDonePayload = null;
|
|
@@ -390,24 +682,11 @@ class GeminiSseNormalizer extends Transform {
|
|
|
390
682
|
this.processEvent(rawEvent);
|
|
391
683
|
}
|
|
392
684
|
if (flush && this.buffer.trim().length) {
|
|
393
|
-
console.log('[GeminiSseNormalizer] Final buffer flush:', {
|
|
394
|
-
bufferLength: this.buffer.length,
|
|
395
|
-
bufferPreview: this.buffer.slice(0, 300),
|
|
396
|
-
eventsFoundInLoop: eventsFound
|
|
397
|
-
});
|
|
398
685
|
this.processEvent(this.buffer);
|
|
399
686
|
this.buffer = '';
|
|
400
687
|
}
|
|
401
|
-
else if (flush) {
|
|
402
|
-
console.log('[GeminiSseNormalizer] Flush called but buffer empty:', {
|
|
403
|
-
eventsFoundInLoop: eventsFound
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
688
|
}
|
|
407
689
|
processEvent(rawEvent) {
|
|
408
|
-
if (process.env.ROUTECODEX_DEBUG_GEMINI_RAW === '1') {
|
|
409
|
-
console.log('[DEBUG-GEMINI-INPUT]', JSON.stringify(rawEvent));
|
|
410
|
-
}
|
|
411
690
|
this.processedEventCounter++;
|
|
412
691
|
const trimmed = rawEvent.trim();
|
|
413
692
|
if (!trimmed.length) {
|
|
@@ -430,22 +709,12 @@ class GeminiSseNormalizer extends Transform {
|
|
|
430
709
|
this.capturedEvents.push(parsed);
|
|
431
710
|
const response = parsed?.response;
|
|
432
711
|
if (!response || typeof response !== 'object') {
|
|
433
|
-
// Log dropped events for debugging
|
|
434
|
-
console.warn('[GeminiSseNormalizer] Dropped event without valid response field:', {
|
|
435
|
-
hasResponse: !!parsed?.response,
|
|
436
|
-
parsedKeys: Object.keys(parsed || {}),
|
|
437
|
-
payloadPreview: payloadText.slice(0, 150)
|
|
438
|
-
});
|
|
439
712
|
return;
|
|
440
713
|
}
|
|
441
714
|
this.emitCandidateParts(response);
|
|
442
715
|
}
|
|
443
716
|
catch (err) {
|
|
444
|
-
//
|
|
445
|
-
console.error('[GeminiSseNormalizer] Failed to parse SSE payload:', {
|
|
446
|
-
error: err instanceof Error ? err.message : String(err),
|
|
447
|
-
payloadPreview: payloadText.slice(0, 200)
|
|
448
|
-
});
|
|
717
|
+
// ignore parse errors; upstream stream snapshots (if enabled) are used for debugging
|
|
449
718
|
}
|
|
450
719
|
}
|
|
451
720
|
emitCandidateParts(response) {
|