@jsonstudio/rcc 0.89.1348 → 0.89.1488

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +51 -1427
  2. package/configsamples/config.json +12 -4
  3. package/dist/build-info.js +2 -2
  4. package/dist/cli/commands/config.js +3 -0
  5. package/dist/cli/commands/config.js.map +1 -1
  6. package/dist/cli/commands/init.js +3 -0
  7. package/dist/cli/commands/init.js.map +1 -1
  8. package/dist/cli/config/bundled-docs.js +2 -2
  9. package/dist/cli/config/bundled-docs.js.map +1 -1
  10. package/dist/cli/config/init-config.d.ts +2 -1
  11. package/dist/cli/config/init-config.js +33 -1
  12. package/dist/cli/config/init-config.js.map +1 -1
  13. package/dist/client/gemini/gemini-protocol-client.js +2 -1
  14. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  15. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +67 -16
  16. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  17. package/dist/client/openai/chat-protocol-client.js +2 -1
  18. package/dist/client/openai/chat-protocol-client.js.map +1 -1
  19. package/dist/client/responses/responses-protocol-client.js +2 -1
  20. package/dist/client/responses/responses-protocol-client.js.map +1 -1
  21. package/dist/error-handling/quiet-error-handling-center.js +46 -8
  22. package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
  23. package/dist/manager/modules/quota/antigravity-quota-manager.d.ts +4 -0
  24. package/dist/manager/modules/quota/antigravity-quota-manager.js +130 -2
  25. package/dist/manager/modules/quota/antigravity-quota-manager.js.map +1 -1
  26. package/dist/manager/modules/quota/provider-quota-daemon.events.js +67 -4
  27. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  28. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +9 -6
  29. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  30. package/dist/modules/llmswitch/bridge.js +17 -4
  31. package/dist/modules/llmswitch/bridge.js.map +1 -1
  32. package/dist/modules/llmswitch/core-loader.d.ts +1 -1
  33. package/dist/modules/llmswitch/core-loader.js +15 -3
  34. package/dist/modules/llmswitch/core-loader.js.map +1 -1
  35. package/dist/providers/auth/antigravity-userinfo-helper.d.ts +5 -2
  36. package/dist/providers/auth/antigravity-userinfo-helper.js +63 -8
  37. package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
  38. package/dist/providers/auth/gemini-cli-userinfo-helper.js +66 -4
  39. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  40. package/dist/providers/auth/oauth-lifecycle.js +112 -1
  41. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  42. package/dist/providers/auth/tokenfile-auth.d.ts +14 -0
  43. package/dist/providers/auth/tokenfile-auth.js +125 -2
  44. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  45. package/dist/providers/core/config/camoufox-launcher.d.ts +5 -0
  46. package/dist/providers/core/config/camoufox-launcher.js +5 -0
  47. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  48. package/dist/providers/core/config/service-profiles.js +7 -18
  49. package/dist/providers/core/config/service-profiles.js.map +1 -1
  50. package/dist/providers/core/runtime/base-provider.d.ts +0 -5
  51. package/dist/providers/core/runtime/base-provider.js +26 -112
  52. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  53. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +6 -0
  54. package/dist/providers/core/runtime/gemini-cli-http-provider.js +409 -100
  55. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  56. package/dist/providers/core/runtime/http-request-executor.d.ts +3 -0
  57. package/dist/providers/core/runtime/http-request-executor.js +110 -38
  58. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  59. package/dist/providers/core/runtime/http-transport-provider.d.ts +3 -0
  60. package/dist/providers/core/runtime/http-transport-provider.js +89 -39
  61. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  62. package/dist/providers/core/runtime/rate-limit-manager.d.ts +1 -12
  63. package/dist/providers/core/runtime/rate-limit-manager.js +4 -77
  64. package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
  65. package/dist/providers/core/utils/http-client.js +20 -43
  66. package/dist/providers/core/utils/http-client.js.map +1 -1
  67. package/dist/runtime/wasm-runtime/wasm-config.d.ts +73 -0
  68. package/dist/runtime/wasm-runtime/wasm-config.js +124 -0
  69. package/dist/runtime/wasm-runtime/wasm-config.js.map +1 -0
  70. package/dist/runtime/wasm-runtime/wasm-loader.d.ts +40 -0
  71. package/dist/runtime/wasm-runtime/wasm-loader.js +62 -0
  72. package/dist/runtime/wasm-runtime/wasm-loader.js.map +1 -0
  73. package/dist/server/handlers/handler-utils.js +5 -1
  74. package/dist/server/handlers/handler-utils.js.map +1 -1
  75. package/dist/server/handlers/responses-handler.js +1 -1
  76. package/dist/server/handlers/responses-handler.js.map +1 -1
  77. package/dist/server/runtime/http-server/index.js +121 -30
  78. package/dist/server/runtime/http-server/index.js.map +1 -1
  79. package/dist/server/runtime/http-server/request-executor.js +50 -6
  80. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  81. package/dist/server/runtime/http-server/routes.js +4 -1
  82. package/dist/server/runtime/http-server/routes.js.map +1 -1
  83. package/dist/utils/strip-internal-keys.d.ts +12 -0
  84. package/dist/utils/strip-internal-keys.js +28 -0
  85. package/dist/utils/strip-internal-keys.js.map +1 -0
  86. package/docs/CHAT_PROCESS_PROTOCOL_AND_PIPELINE.md +221 -0
  87. package/docs/antigravity-gemini-format-cleanup.md +143 -0
  88. package/docs/antigravity-routing-contract.md +31 -0
  89. package/docs/chat-semantic-expansion-plan.md +8 -6
  90. package/docs/glm-chat-completions.md +1 -1
  91. package/docs/llms-wasm-migration.md +331 -0
  92. package/docs/llms-wasm-module-boundaries.md +588 -0
  93. package/docs/llms-wasm-replay-baseline.md +171 -0
  94. package/docs/plans/llms-wasm-migration-plan.md +401 -0
  95. package/docs/servertool-framework.md +65 -0
  96. package/docs/v2-architecture/README.md +6 -8
  97. package/docs/verified-configs/README.md +60 -0
  98. package/docs/verified-configs/v0.45.0/README.md +244 -0
  99. package/docs/verified-configs/v0.45.0/lmstudio-5521-gpt-oss-20b-mlx.json +135 -0
  100. package/docs/verified-configs/v0.45.0/merged-config.5521.json +1205 -0
  101. package/docs/verified-configs/v0.45.0/merged-config.qwen-5522.json +1559 -0
  102. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-final.json +221 -0
  103. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-fixed.json +242 -0
  104. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus.json +242 -0
  105. package/package.json +17 -11
  106. package/scripts/antigravity-token-bridge.mjs +283 -0
  107. package/scripts/build-core.mjs +3 -1
  108. package/scripts/ci/repo-sanity.mjs +138 -0
  109. package/scripts/mock-provider/run-regressions.mjs +157 -1
  110. package/scripts/run-bg.sh +0 -14
  111. package/scripts/tests/ci-jest.mjs +119 -0
  112. package/scripts/tools-dev/responses-debug-client/README.md +23 -0
  113. package/scripts/tools-dev/responses-debug-client/payloads/poem.json +13 -0
  114. package/scripts/tools-dev/responses-debug-client/payloads/sample-no-tools.json +98 -0
  115. package/scripts/tools-dev/responses-debug-client/payloads/text.json +13 -0
  116. package/scripts/tools-dev/responses-debug-client/payloads/tool.json +27 -0
  117. package/scripts/tools-dev/responses-debug-client/run.mjs +65 -0
  118. package/scripts/tools-dev/responses-debug-client/src/index.ts +281 -0
  119. package/scripts/tools-dev/run-llmswitch-chat.mjs +53 -0
  120. package/scripts/tools-dev/server-tools-dev/run-web-fetch.mjs +65 -0
  121. package/scripts/vendor-core.mjs +13 -3
  122. package/scripts/test-fc-responses.mjs +0 -66
  123. package/scripts/test-guidance.mjs +0 -100
  124. package/scripts/test-iflow-web-search.mjs +0 -141
  125. package/scripts/test-iflow.mjs +0 -379
  126. package/scripts/test-tool-exec.mjs +0 -26
@@ -7,13 +7,180 @@
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 { ANTIGRAVITY_HELPER_DEFAULTS } from '../../auth/antigravity-userinfo-helper.js';
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.9 ${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 extractAntigravityAlias = (providerKey) => {
102
+ if (!providerKey) {
103
+ return undefined;
104
+ }
105
+ const parts = providerKey.split('.').filter(Boolean);
106
+ if (!parts.length) {
107
+ return undefined;
108
+ }
109
+ if (parts[0] !== 'antigravity') {
110
+ return undefined;
111
+ }
112
+ return parts.length >= 2 ? parts[1] : undefined;
113
+ };
114
+ const stableIdFromProfile = (profileId) => {
115
+ const hash = createHash('sha256').update(profileId).digest('hex');
116
+ const deviceId = `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
117
+ return {
118
+ deviceId,
119
+ quotaUser: `device-${hash.slice(0, 16)}`,
120
+ 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()}}`
121
+ };
122
+ };
123
+ const buildFingerprintFromCamoufox = (profileId, env) => {
124
+ const camouKeys = Object.keys(env).filter((key) => key.startsWith('CAMOU_CONFIG_')).sort();
125
+ if (camouKeys.length === 0) {
126
+ return null;
127
+ }
128
+ const blob = camouKeys.map((key) => env[key]).join('');
129
+ let parsed = null;
130
+ try {
131
+ parsed = JSON.parse(blob);
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ const ua = typeof parsed['navigator.userAgent'] === 'string' ? parsed['navigator.userAgent'] : '';
137
+ const oscpu = typeof parsed['navigator.oscpu'] === 'string' ? parsed['navigator.oscpu'] : '';
138
+ const platformRaw = typeof parsed['navigator.platform'] === 'string' ? parsed['navigator.platform'] : oscpu;
139
+ const acceptEncoding = typeof parsed['headers.Accept-Encoding'] === 'string' ? parsed['headers.Accept-Encoding'] : undefined;
140
+ const platformLower = platformRaw.toLowerCase();
141
+ const platform = platformLower.includes('win') ? 'WINDOWS' : platformLower.includes('mac') ? 'MACOS' : 'LINUX';
142
+ const arch = platformLower.includes('arm64') || platformLower.includes('aarch64') ? 'arm64' : 'x64';
143
+ const stableIds = stableIdFromProfile(profileId);
144
+ return {
145
+ deviceId: stableIds.deviceId,
146
+ sessionToken: randomBytes(16).toString('hex'),
147
+ userAgent: ua || `antigravity/1.11.9 ${osPlatform()}/${osArch()}`,
148
+ apiClient: 'google-cloud-sdk vscode_cloudshelleditor/0.1',
149
+ acceptEncoding,
150
+ clientMetadata: {
151
+ ideType: 'IDE_UNSPECIFIED',
152
+ platform,
153
+ pluginType: 'GEMINI',
154
+ osVersion: oscpu || osRelease(),
155
+ arch,
156
+ sqmId: stableIds.sqmId
157
+ },
158
+ quotaUser: stableIds.quotaUser,
159
+ createdAt: Date.now()
160
+ };
161
+ };
162
+ const resolveAntigravityFingerprint = (runtime) => {
163
+ const alias = (runtime?.metadata && typeof runtime.metadata === 'object' && typeof runtime.metadata.alias === 'string'
164
+ ? String(runtime.metadata.alias)
165
+ : undefined) || extractAntigravityAlias(runtime?.providerKey);
166
+ const camoufox = getCamoufoxFingerprintProfile('antigravity', alias);
167
+ if (camoufox) {
168
+ const parsed = buildFingerprintFromCamoufox(camoufox.profileId, camoufox.env);
169
+ if (parsed) {
170
+ return parsed;
171
+ }
172
+ }
173
+ const env = (process.env.ROUTECODEX_ANTIGRAVITY_FINGERPRINT_MODE || process.env.RCC_ANTIGRAVITY_FINGERPRINT_MODE || '')
174
+ .trim()
175
+ .toLowerCase();
176
+ if (env === 'current' || env === 'host') {
177
+ return collectCurrentAntigravityFingerprint();
178
+ }
179
+ return getAntigravitySessionFingerprint();
180
+ };
181
+ // Legacy helper seed for Antigravity signature session key generation.
182
+ // Kept for backwards compatibility (even when we avoid auto-injecting sessionId).
183
+ const ANTIGRAVITY_PLUGIN_SESSION_ID = `-${randomUUID()}`;
17
184
  export class GeminiCLIHttpProvider extends HttpTransportProvider {
18
185
  constructor(config, dependencies) {
19
186
  const providerId = typeof config.config?.providerId === 'string' && config.config.providerId.trim().length
@@ -35,10 +202,28 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
35
202
  };
36
203
  super(cfg, dependencies, 'gemini-cli-http-provider', new GeminiCLIProtocolClient());
37
204
  }
205
+ applyStreamModeHeaders(headers, wantsSse) {
206
+ if (!this.isAntigravityRuntime()) {
207
+ return super.applyStreamModeHeaders(headers, wantsSse);
208
+ }
209
+ // Antigravity: keep SSE Accept header behavior stable.
210
+ // We intentionally do not force "*/*" here because the upstream is sensitive to header triplets
211
+ // and we have observed successful runs with the default "text/event-stream" Accept.
212
+ return super.applyStreamModeHeaders(headers, wantsSse);
213
+ }
214
+ getBaseUrlCandidates(_context) {
215
+ if (!this.isAntigravityRuntime()) {
216
+ return undefined;
217
+ }
218
+ return resolveAntigravityApiBaseCandidates(this.getEffectiveBaseUrl());
219
+ }
38
220
  async preprocessRequest(request) {
39
221
  const processedRequest = await super.preprocessRequest(request);
40
222
  const adapter = this.resolvePayload(processedRequest);
41
223
  const payload = adapter.payload;
224
+ const metadata = processedRequest && typeof processedRequest === 'object' && typeof processedRequest.metadata === 'object'
225
+ ? processedRequest.metadata
226
+ : undefined;
42
227
  // 从 auth provider 获取 project_id(仅做最小的 OAuth token 解析,不在此处触发 OAuth 流程)
43
228
  if (!this.authProvider) {
44
229
  throw new Error('Gemini CLI: auth provider not found');
@@ -57,6 +242,16 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
57
242
  };
58
243
  const tokenData = readTokenPayload();
59
244
  const projectId = getDefaultProjectId(tokenData || {});
245
+ const projectOverride = (() => {
246
+ const ext = this.config?.config?.extensions;
247
+ const candidates = [ext?.projectId, ext?.project_id, ext?.project];
248
+ for (const candidate of candidates) {
249
+ if (typeof candidate === 'string' && candidate.trim().length) {
250
+ return candidate.trim();
251
+ }
252
+ }
253
+ return undefined;
254
+ })();
60
255
  // 构建 Gemini CLI 格式的请求(仅做传输层整理,不做 OpenAI→Gemini 语义转换)
61
256
  const model = typeof payload.model === 'string' && payload.model.trim().length > 0
62
257
  ? payload.model
@@ -65,10 +260,22 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
65
260
  throw new Error('Gemini CLI: model is required');
66
261
  }
67
262
  payload.model = model;
68
- // 若当前 token 中已有 project 元数据,则补充到请求中;否则让上游决定后续行为。
69
- // 注意:Antigravity 运行时保持与早期成功快照一致,始终显式发送 project。
70
- if (projectId) {
71
- payload.project = projectId;
263
+ const isClaudeModel = this.isClaudeModelName(model);
264
+ // Project selection:
265
+ // - honor explicit payload.project (client/config),
266
+ // - otherwise honor provider config extensions project override,
267
+ // - otherwise fall back to token-derived default project_id if present.
268
+ const existingProject = typeof payload.project === 'string' && String(payload.project).trim().length
269
+ ? String(payload.project).trim()
270
+ : '';
271
+ if (!existingProject) {
272
+ const nextProject = projectOverride || projectId;
273
+ if (nextProject && nextProject.trim().length) {
274
+ payload.project = nextProject.trim();
275
+ }
276
+ else {
277
+ throw new Error('Gemini CLI: project_id missing in token; re-auth or configure projectId');
278
+ }
72
279
  }
73
280
  this.ensureRequestMetadata(payload);
74
281
  // 删除与 Gemini 协议无关的字段,避免影响 Cloud Code Assist schema 校验。
@@ -85,6 +292,57 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
85
292
  // 如果上游/compat 层错误地产生了 payload.request(例如旧版本样本/回放),这里做一次扁平化,
86
293
  // 避免出现 body.request.request.contents 这种非法形状,导致上游生成空响应或被静默忽略。
87
294
  this.flattenRequestContainer(payload);
295
+ const sourceRequest = processedRequest && typeof processedRequest === 'object' && typeof processedRequest.request === 'object'
296
+ ? processedRequest.request
297
+ : undefined;
298
+ if (sourceRequest) {
299
+ const keys = [
300
+ 'contents',
301
+ 'systemInstruction',
302
+ 'tools',
303
+ 'toolConfig',
304
+ 'generationConfig',
305
+ 'safetySettings'
306
+ ];
307
+ for (const key of keys) {
308
+ if (payload[key] === undefined && sourceRequest[key] !== undefined) {
309
+ payload[key] = sourceRequest[key];
310
+ }
311
+ }
312
+ }
313
+ // Standard contract: Antigravity identity fields are carried in the JSON wrapper (not headers).
314
+ // gcli2api antigravity alignment:
315
+ // - requestId/requestType are sent as HTTP headers (provider finalizeRequestHeaders)
316
+ // - JSON wrapper should not force-inject extra identity/session fields
317
+ // Provider must not infer request semantics from user payload; modality hints must come from llmswitch-core metadata.
318
+ if (this.isAntigravityRuntime()) {
319
+ const record = payload;
320
+ if (!this.hasNonEmptyString(record.userAgent)) {
321
+ record.userAgent = 'antigravity';
322
+ }
323
+ if (!this.hasNonEmptyString(record.requestType)) {
324
+ const hasImageAttachment = metadata && (metadata.hasImageAttachment === true || metadata.hasImageAttachment === 'true');
325
+ const fallbackImageByModel = typeof model === 'string' && model.toLowerCase().includes('image');
326
+ record.requestType = hasImageAttachment || fallbackImageByModel ? 'image_gen' : 'agent';
327
+ }
328
+ const existingReqId = record.requestId;
329
+ const normalizedReqId = typeof existingReqId === 'string' ? existingReqId.trim() : '';
330
+ if (!normalizedReqId || !(normalizedReqId.startsWith('agent-') || normalizedReqId.startsWith('req-'))) {
331
+ record.requestId = `agent-${randomUUID()}`;
332
+ }
333
+ }
334
+ // opencode-antigravity-auth alignment: keep requestId/requestType/userAgent in JSON wrapper,
335
+ // but also mirror requestId/requestType into metadata for header propagation.
336
+ if (this.isAntigravityRuntime()) {
337
+ const meta = processedRequest.metadata ?? {};
338
+ processedRequest.metadata = meta;
339
+ if (typeof payload.requestId === 'string' && payload.requestId.trim().length) {
340
+ meta.__antigravityRequestId = String(payload.requestId).trim();
341
+ }
342
+ if (typeof payload.requestType === 'string' && payload.requestType.trim().length) {
343
+ meta.__antigravityRequestType = String(payload.requestType).trim();
344
+ }
345
+ }
88
346
  return processedRequest;
89
347
  }
90
348
  wantsUpstreamSse(request, context) {
@@ -107,16 +365,106 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
107
365
  async finalizeRequestHeaders(headers, request) {
108
366
  const finalized = await super.finalizeRequestHeaders(headers, request);
109
367
  if (this.isAntigravityRuntime()) {
110
- finalized['User-Agent'] = ANTIGRAVITY_HELPER_DEFAULTS.userAgent;
111
- if (!this.hasNonEmptyString(finalized['Accept-Encoding'])) {
112
- // 对齐旧版成功快照:使用 gzip, deflate, br
113
- finalized['Accept-Encoding'] = 'gzip, deflate, br';
368
+ const payload = this.resolvePayload(request).payload;
369
+ const meta = request.metadata ?? {};
370
+ const runtime = this.getCurrentRuntimeMetadata();
371
+ const isClaudeModel = this.isClaudeModelName(payload?.model);
372
+ const isClaudeThinking = typeof payload?.model === 'string' && payload.model.toLowerCase().includes('thinking');
373
+ const requestNode = payload && typeof payload.request === 'object' && payload.request !== null
374
+ ? payload.request
375
+ : payload;
376
+ const hasTools = Array.isArray(requestNode.tools) &&
377
+ (requestNode.tools?.length ?? 0) > 0;
378
+ const fingerprint = resolveAntigravityFingerprint(runtime);
379
+ finalized['User-Agent'] = fingerprint.userAgent;
380
+ if (isClaudeModel) {
381
+ if (isClaudeThinking && hasTools) {
382
+ const existing = finalized['anthropic-beta'];
383
+ const interleaved = 'interleaved-thinking-2025-05-14';
384
+ finalized['anthropic-beta'] = existing && existing.length
385
+ ? (existing.includes(interleaved) ? existing : `${existing},${interleaved}`)
386
+ : interleaved;
387
+ }
114
388
  }
115
- // 保留 X-Goog-Api-Client / Client-Metadata,由上游决定是否使用;
116
- // 不再强行删除 Accept 头,维持默认的 text/event-stream。
117
389
  }
118
390
  return finalized;
119
391
  }
392
+ buildHttpRequestBody(request) {
393
+ const built = super.buildHttpRequestBody(request);
394
+ if (!this.isAntigravityRuntime() || !built || typeof built !== 'object') {
395
+ return built;
396
+ }
397
+ const record = built;
398
+ const payload = this.resolvePayload(request).payload;
399
+ const payloadRequest = payload.request && typeof payload.request === 'object' && !Array.isArray(payload.request)
400
+ ? payload.request
401
+ : undefined;
402
+ const rootRecord = request;
403
+ const rootRequestCandidate = rootRecord.request ??
404
+ (rootRecord.data && typeof rootRecord.data === 'object' ? rootRecord.data.request : undefined);
405
+ const rootRequest = rootRequestCandidate && typeof rootRequestCandidate === 'object' && !Array.isArray(rootRequestCandidate)
406
+ ? rootRequestCandidate
407
+ : undefined;
408
+ const mergeRequestFields = (target) => {
409
+ const source = payloadRequest ?? payload;
410
+ const keys = [
411
+ 'contents',
412
+ 'systemInstruction',
413
+ 'tools',
414
+ 'toolConfig',
415
+ 'generationConfig',
416
+ 'safetySettings'
417
+ ];
418
+ for (const key of keys) {
419
+ if (target[key] === undefined && source[key] !== undefined) {
420
+ target[key] = source[key];
421
+ }
422
+ }
423
+ };
424
+ if (record.request && typeof record.request === 'object' && !Array.isArray(record.request)) {
425
+ const target = record.request;
426
+ mergeRequestFields(target);
427
+ if (rootRequest) {
428
+ for (const [key, value] of Object.entries(rootRequest)) {
429
+ if (target[key] === undefined) {
430
+ target[key] = value;
431
+ }
432
+ }
433
+ }
434
+ delete target.metadata;
435
+ return built;
436
+ }
437
+ const candidate = payloadRequest || rootRequest;
438
+ if (candidate) {
439
+ const nextRequest = { ...candidate };
440
+ mergeRequestFields(nextRequest);
441
+ delete nextRequest.metadata;
442
+ record.request = nextRequest;
443
+ return built;
444
+ }
445
+ const fallback = {};
446
+ const candidates = [
447
+ 'contents',
448
+ 'systemInstruction',
449
+ 'tools',
450
+ 'toolConfig',
451
+ 'generationConfig',
452
+ 'safetySettings',
453
+ 'metadata'
454
+ ];
455
+ for (const key of candidates) {
456
+ if (payload[key] !== undefined) {
457
+ fallback[key] = payload[key];
458
+ }
459
+ }
460
+ if (Object.keys(fallback).length > 0) {
461
+ record.request = fallback;
462
+ }
463
+ return built;
464
+ }
465
+ isClaudeModelName(model) {
466
+ return typeof model === 'string' && model.toLowerCase().includes('claude');
467
+ }
120
468
  async postprocessResponse(response, context) {
121
469
  const processingTime = Date.now() - context.startTime;
122
470
  if (response && typeof response === 'object') {
@@ -177,7 +525,11 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
177
525
  return super.wrapUpstreamSseResponse(normalizer, context);
178
526
  }
179
527
  isAntigravityRuntime() {
180
- return (this.oauthProviderId || '').toLowerCase() === 'antigravity';
528
+ const fromConfig = typeof this.config?.config?.providerId === 'string' && this.config.config.providerId.trim()
529
+ ? this.config.config.providerId.trim().toLowerCase()
530
+ : '';
531
+ const fromOAuth = typeof this.oauthProviderId === 'string' ? this.oauthProviderId.trim().toLowerCase() : '';
532
+ return fromConfig === 'antigravity' || fromOAuth === 'antigravity';
181
533
  }
182
534
  resolvePayload(source) {
183
535
  if (this.hasDataEnvelope(source)) {
@@ -188,6 +540,17 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
188
540
  if (!envelope.data || typeof envelope.data !== 'object') {
189
541
  envelope.data = dataRecord;
190
542
  }
543
+ if (source && typeof source === 'object') {
544
+ const sourceRecord = source;
545
+ for (const [key, value] of Object.entries(sourceRecord)) {
546
+ if (key === 'data' || key === 'metadata') {
547
+ continue;
548
+ }
549
+ if (dataRecord[key] === undefined) {
550
+ dataRecord[key] = value;
551
+ }
552
+ }
553
+ }
191
554
  return {
192
555
  payload: dataRecord,
193
556
  assign: (updated) => {
@@ -209,12 +572,9 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
209
572
  * 参考 CLIProxyAPI 的 cliPreviewFallbackOrder
210
573
  */
211
574
  getFallbackModels(model) {
212
- const fallbackMap = {
213
- 'gemini-2.5-pro': ['gemini-2.5-pro-preview-06-05'],
214
- 'gemini-2.5-flash': [],
215
- 'gemini-2.5-flash-lite': []
216
- };
217
- return fallbackMap[model] || [];
575
+ // 禁用模型回退:路由层负责标准映射,Provider 不做降级/回退。
576
+ void model;
577
+ return [];
218
578
  }
219
579
  applyStreamAction(target, wantsStream) {
220
580
  const adapter = this.resolvePayload(target);
@@ -230,62 +590,39 @@ export class GeminiCLIHttpProvider extends HttpTransportProvider {
230
590
  ensureRequestMetadata(payload) {
231
591
  const isAntigravity = this.isAntigravityRuntime();
232
592
  if (!this.hasNonEmptyString(payload.requestId)) {
233
- payload.requestId = `req-${randomUUID()}`;
234
- }
235
- // 对齐 gcli2api:Antigravity 运行时不发送 session_id,其它运行时保持原有行为。
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';
593
+ // opencode-antigravity-auth alignment: antigravity uses agent- prefix.
594
+ const prefix = isAntigravity ? 'agent' : 'req';
595
+ payload.requestId = `${prefix}-${randomUUID()}`;
241
596
  }
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 提示词之后。
597
+ // Never inject legacy snake_case session_id.
249
598
  if (isAntigravity) {
250
- const requestContainer = payload.request && typeof payload.request === 'object'
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
- }
599
+ delete payload.session_id;
287
600
  }
288
601
  }
602
+ isAntigravityMinimalCompatibilityEnabled() {
603
+ const raw = (process.env.ROUTECODEX_ANTIGRAVITY_HEADER_MODE || process.env.RCC_ANTIGRAVITY_HEADER_MODE || '')
604
+ .trim()
605
+ .toLowerCase();
606
+ // Legacy switch kept for older deployments; RouteCodex now follows gcli2api antigravity header style by default.
607
+ // This method is currently unused (we avoid forcing sessionId injection and keep requestId/requestType in headers).
608
+ if (!raw) {
609
+ return false;
610
+ }
611
+ if (raw === 'standard' || raw === 'full' || raw === 'headers') {
612
+ return false;
613
+ }
614
+ return raw === 'minimal' || raw === 'gcli2api';
615
+ }
616
+ buildAntigravitySignatureSessionKey(options) {
617
+ const modelKeyRaw = typeof options.model === 'string' ? options.model.trim().toLowerCase() : 'unknown';
618
+ // Antigravity contract: strip tier suffix to reduce cache misses when tier changes.
619
+ const modelKey = modelKeyRaw.replace(/-(minimal|low|medium|high)$/i, '');
620
+ const projectPart = typeof options.project === 'string' && options.project.trim().length ? options.project.trim() : 'default';
621
+ const conversationPart = typeof options.conversationKey === 'string' && options.conversationKey.trim().length
622
+ ? options.conversationKey.trim()
623
+ : 'default';
624
+ return `${ANTIGRAVITY_PLUGIN_SESSION_ID}:${modelKey}:${projectPart}:${conversationPart}`;
625
+ }
289
626
  flattenRequestContainer(payload) {
290
627
  const requestContainer = payload.request && typeof payload.request === 'object'
291
628
  ? payload.request
@@ -366,11 +703,6 @@ class GeminiSseNormalizer extends Transform {
366
703
  this.buffer += remaining.replace(/\r/g, '');
367
704
  }
368
705
  this.processBuffered(true);
369
- console.log('[GeminiSseNormalizer] Stream complete:', {
370
- totalChunks: this.chunkCounter,
371
- processedEvents: this.processedEventCounter,
372
- emittedEvents: this.eventCounter
373
- });
374
706
  if (this.lastDonePayload) {
375
707
  this.pushEvent('gemini.done', this.lastDonePayload);
376
708
  this.lastDonePayload = null;
@@ -390,24 +722,11 @@ class GeminiSseNormalizer extends Transform {
390
722
  this.processEvent(rawEvent);
391
723
  }
392
724
  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
725
  this.processEvent(this.buffer);
399
726
  this.buffer = '';
400
727
  }
401
- else if (flush) {
402
- console.log('[GeminiSseNormalizer] Flush called but buffer empty:', {
403
- eventsFoundInLoop: eventsFound
404
- });
405
- }
406
728
  }
407
729
  processEvent(rawEvent) {
408
- if (process.env.ROUTECODEX_DEBUG_GEMINI_RAW === '1') {
409
- console.log('[DEBUG-GEMINI-INPUT]', JSON.stringify(rawEvent));
410
- }
411
730
  this.processedEventCounter++;
412
731
  const trimmed = rawEvent.trim();
413
732
  if (!trimmed.length) {
@@ -430,22 +749,12 @@ class GeminiSseNormalizer extends Transform {
430
749
  this.capturedEvents.push(parsed);
431
750
  const response = parsed?.response;
432
751
  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
752
  return;
440
753
  }
441
754
  this.emitCandidateParts(response);
442
755
  }
443
756
  catch (err) {
444
- // Log parse failures for debugging
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
- });
757
+ // ignore parse errors; upstream stream snapshots (if enabled) are used for debugging
449
758
  }
450
759
  }
451
760
  emitCandidateParts(response) {