mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(antigravity): 对齐 AM 客户端标识,前端 OAuth 增加 TOTP 支持
- User-Agent 改为 Electron 浏览器格式,新增 x-client-name/x-client-version/x-vscode-sessionid/x-goog-api-client header - 移除 MODEL_ALIAS_MAP 别名映射,改为仅剥离 -online 后缀 - thinking 自动注入关键字新增 gemini-3.1-pro - 新增定时 Antigravity UA 版本刷新任务(每 6 小时 + 启动时) - 前端 OAuth 设备授权新增 TOTP 验证码生成(otpauth),Region 改为按钮选择 - SSO OIDC 轮询日志 authorization_pending/slow_down 降级为 debug
This commit is contained in:
44
frontend/package-lock.json
generated
44
frontend/package-lock.json
generated
@@ -22,6 +22,7 @@
|
|||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"lucide-vue-next": "^0.544.0",
|
"lucide-vue-next": "^0.544.0",
|
||||||
"marked": "^16.0.0",
|
"marked": "^16.0.0",
|
||||||
|
"otpauth": "^9.5.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
@@ -262,7 +263,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -306,7 +306,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@@ -1142,6 +1141,18 @@
|
|||||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -1600,7 +1611,6 @@
|
|||||||
"integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==",
|
"integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.10.0"
|
"undici-types": "~7.10.0"
|
||||||
}
|
}
|
||||||
@@ -1679,7 +1689,6 @@
|
|||||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.49.0",
|
"@typescript-eslint/scope-manager": "8.49.0",
|
||||||
"@typescript-eslint/types": "8.49.0",
|
"@typescript-eslint/types": "8.49.0",
|
||||||
@@ -2008,7 +2017,6 @@
|
|||||||
"integrity": "sha512-oWtNM89Np+YsQO3ttT5i1Aer/0xbzQzp66NzuJn/U16bB7MnvSzdLKXgk1kkMLYyKSSzA2ajzqMkYheaE9opuQ==",
|
"integrity": "sha512-oWtNM89Np+YsQO3ttT5i1Aer/0xbzQzp66NzuJn/U16bB7MnvSzdLKXgk1kkMLYyKSSzA2ajzqMkYheaE9opuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/utils": "4.0.10",
|
"@vitest/utils": "4.0.10",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
@@ -2306,7 +2314,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2608,7 +2615,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.2",
|
"baseline-browser-mapping": "^2.8.2",
|
||||||
"caniuse-lite": "^1.0.30001741",
|
"caniuse-lite": "^1.0.30001741",
|
||||||
@@ -2725,7 +2731,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
||||||
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kurkle/color": "^0.3.0"
|
"@kurkle/color": "^0.3.0"
|
||||||
},
|
},
|
||||||
@@ -2948,7 +2953,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/kossnocorp"
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
@@ -3201,7 +3205,6 @@
|
|||||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -4101,7 +4104,6 @@
|
|||||||
"integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==",
|
"integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@acemir/cssom": "^0.9.23",
|
"@acemir/cssom": "^0.9.23",
|
||||||
"@asamuzakjp/dom-selector": "^6.7.4",
|
"@asamuzakjp/dom-selector": "^6.7.4",
|
||||||
@@ -4512,6 +4514,18 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/otpauth": {
|
||||||
|
"version": "9.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.5.0.tgz",
|
||||||
|
"integrity": "sha512-Ldhc6UYl4baR5toGr8nfKC+L/b8/RgHKoIixAebgoNGzUUCET02g04rMEZ2ZsPfeVQhMHcuaOgb28nwMr81zCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/hectorm/otpauth?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||||
@@ -4666,7 +4680,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -4734,7 +4747,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -5765,7 +5777,6 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -5859,7 +5870,6 @@
|
|||||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -5935,7 +5945,6 @@
|
|||||||
"integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==",
|
"integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "4.0.10",
|
"@vitest/expect": "4.0.10",
|
||||||
"@vitest/mocker": "4.0.10",
|
"@vitest/mocker": "4.0.10",
|
||||||
@@ -6020,7 +6029,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz",
|
||||||
"integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
|
"integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.21",
|
"@vue/compiler-dom": "3.5.21",
|
||||||
"@vue/compiler-sfc": "3.5.21",
|
"@vue/compiler-sfc": "3.5.21",
|
||||||
@@ -6053,6 +6061,7 @@
|
|||||||
"integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==",
|
"integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
"eslint-scope": "^8.2.0",
|
"eslint-scope": "^8.2.0",
|
||||||
@@ -6077,6 +6086,7 @@
|
|||||||
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"lucide-vue-next": "^0.544.0",
|
"lucide-vue-next": "^0.544.0",
|
||||||
"marked": "^16.0.0",
|
"marked": "^16.0.0",
|
||||||
|
"otpauth": "^9.5.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
|
|||||||
79
frontend/src/composables/useTotp.ts
Normal file
79
frontend/src/composables/useTotp.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { ref, computed, onBeforeUnmount } from 'vue'
|
||||||
|
import { TOTP, Secret } from 'otpauth'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TOTP composable: 给定 secret 持续生成验证码并显示剩余秒数。
|
||||||
|
*/
|
||||||
|
export function useTotp() {
|
||||||
|
const secret = ref('')
|
||||||
|
const code = ref('')
|
||||||
|
const remaining = ref(0)
|
||||||
|
const error = ref('')
|
||||||
|
|
||||||
|
let timer: ReturnType<typeof setInterval> | null = null
|
||||||
|
let totp: TOTP | null = null
|
||||||
|
|
||||||
|
function parseSecret(raw: string): Secret | null {
|
||||||
|
const cleaned = raw.replace(/[\s-]/g, '')
|
||||||
|
if (!cleaned) return null
|
||||||
|
try {
|
||||||
|
return Secret.fromBase32(cleaned.toUpperCase())
|
||||||
|
} catch {
|
||||||
|
// 尝试 hex
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Secret.fromHex(cleaned)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate(): void {
|
||||||
|
if (!totp) {
|
||||||
|
code.value = ''
|
||||||
|
remaining.value = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
code.value = totp.generate()
|
||||||
|
const epoch = Math.floor(Date.now() / 1000)
|
||||||
|
remaining.value = totp.period - (epoch % totp.period)
|
||||||
|
}
|
||||||
|
|
||||||
|
function start(rawSecret: string): void {
|
||||||
|
stop()
|
||||||
|
error.value = ''
|
||||||
|
secret.value = rawSecret
|
||||||
|
|
||||||
|
const parsed = parseSecret(rawSecret)
|
||||||
|
if (!parsed) {
|
||||||
|
error.value = rawSecret.trim() ? 'Secret 格式无效' : ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
totp = new TOTP({ secret: parsed, digits: 6, period: 30 })
|
||||||
|
generate()
|
||||||
|
timer = setInterval(generate, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop(): void {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
totp = null
|
||||||
|
code.value = ''
|
||||||
|
remaining.value = 0
|
||||||
|
error.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(stop)
|
||||||
|
|
||||||
|
return {
|
||||||
|
secret,
|
||||||
|
code: computed(() => code.value),
|
||||||
|
remaining: computed(() => remaining.value),
|
||||||
|
error: computed(() => error.value),
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,10 +144,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<label class="text-xs font-medium">Region</label>
|
<label class="text-xs font-medium">Region</label>
|
||||||
|
<div class="grid grid-cols-2 gap-1.5">
|
||||||
|
<button
|
||||||
|
v-for="r in (['eu-north-1', 'us-east-1'] as const)"
|
||||||
|
:key="r"
|
||||||
|
class="h-8 text-xs font-medium font-mono rounded-md border transition-colors"
|
||||||
|
:class="device.region === r
|
||||||
|
? 'border-primary bg-primary/5 text-foreground'
|
||||||
|
: 'border-border text-muted-foreground hover:text-foreground hover:border-foreground/20'"
|
||||||
|
@click="device.region = r"
|
||||||
|
>
|
||||||
|
{{ r }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<label class="text-xs font-medium text-muted-foreground">TOTP Secret (可选)</label>
|
||||||
<input
|
<input
|
||||||
v-model="device.region"
|
v-model="device.totp_secret"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="us-east-1"
|
placeholder="Base32 secret, 如 JBSWY3DPEHPK3PXP"
|
||||||
class="w-full h-8 px-2 text-xs rounded-md border border-border bg-background font-mono"
|
class="w-full h-8 px-2 text-xs rounded-md border border-border bg-background font-mono"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
>
|
>
|
||||||
@@ -205,6 +221,44 @@
|
|||||||
<span>剩余 {{ deviceCountdownFormatted }}</span>
|
<span>剩余 {{ deviceCountdownFormatted }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- TOTP 验证码 -->
|
||||||
|
<div
|
||||||
|
v-if="totp.code.value"
|
||||||
|
class="w-full rounded-lg border border-border bg-background p-3"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ShieldCheck class="w-3.5 h-3.5 text-primary" />
|
||||||
|
<span class="text-[10px] text-muted-foreground">MFA 验证码</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<span
|
||||||
|
class="text-lg font-mono font-bold tracking-[0.25em]"
|
||||||
|
>{{ totp.code.value }}</span>
|
||||||
|
<button
|
||||||
|
class="p-1 rounded hover:bg-muted transition-colors"
|
||||||
|
title="复制验证码"
|
||||||
|
@click="copyToClipboard(totp.code.value)"
|
||||||
|
>
|
||||||
|
<Copy class="w-3 h-3 text-muted-foreground" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 flex items-center gap-2">
|
||||||
|
<div class="flex-1 h-1 rounded-full bg-muted overflow-hidden">
|
||||||
|
<div
|
||||||
|
class="h-full rounded-full transition-all duration-1000 ease-linear"
|
||||||
|
:class="totp.remaining.value <= 5 ? 'bg-red-500' : 'bg-primary'"
|
||||||
|
:style="{ width: `${(totp.remaining.value / 30) * 100}%` }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="text-[10px] font-mono tabular-nums shrink-0"
|
||||||
|
:class="totp.remaining.value <= 5 ? 'text-red-500' : 'text-muted-foreground'"
|
||||||
|
>{{ totp.remaining.value }}s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="flex gap-2 w-full">
|
<div class="flex gap-2 w-full">
|
||||||
<Button
|
<Button
|
||||||
@@ -447,9 +501,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onBeforeUnmount } from 'vue'
|
import { ref, computed, watch, onBeforeUnmount } from 'vue'
|
||||||
import { Dialog, Button, Textarea, Popover, PopoverTrigger, PopoverContent } from '@/components/ui'
|
import { Dialog, Button, Textarea, Popover, PopoverTrigger, PopoverContent } from '@/components/ui'
|
||||||
import { UserPlus, Copy, ExternalLink, Upload, Globe, AlertCircle } from 'lucide-vue-next'
|
import { UserPlus, Copy, ExternalLink, Upload, Globe, AlertCircle, ShieldCheck } from 'lucide-vue-next'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
import { useClipboard } from '@/composables/useClipboard'
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
|
import { useTotp } from '@/composables/useTotp'
|
||||||
import { parseApiError } from '@/utils/errorParser'
|
import { parseApiError } from '@/utils/errorParser'
|
||||||
import {
|
import {
|
||||||
startProviderLevelOAuth,
|
startProviderLevelOAuth,
|
||||||
@@ -476,6 +531,7 @@ const emit = defineEmits<{
|
|||||||
const { success, error: showError } = useToast()
|
const { success, error: showError } = useToast()
|
||||||
const { copyToClipboard } = useClipboard()
|
const { copyToClipboard } = useClipboard()
|
||||||
const proxyNodesStore = useProxyNodesStore()
|
const proxyNodesStore = useProxyNodesStore()
|
||||||
|
const totp = useTotp()
|
||||||
|
|
||||||
// 代理节点选择
|
// 代理节点选择
|
||||||
const proxyPopoverOpen = ref(false)
|
const proxyPopoverOpen = ref(false)
|
||||||
@@ -524,6 +580,7 @@ interface DeviceAuthState {
|
|||||||
auth_type: DeviceAuthType
|
auth_type: DeviceAuthType
|
||||||
start_url: string
|
start_url: string
|
||||||
region: string
|
region: string
|
||||||
|
totp_secret: string
|
||||||
starting: boolean
|
starting: boolean
|
||||||
session_id: string
|
session_id: string
|
||||||
user_code: string
|
user_code: string
|
||||||
@@ -542,7 +599,8 @@ function createInitialDeviceState(): DeviceAuthState {
|
|||||||
return {
|
return {
|
||||||
auth_type: 'builder_id',
|
auth_type: 'builder_id',
|
||||||
start_url: '',
|
start_url: '',
|
||||||
region: 'us-east-1',
|
region: 'eu-north-1',
|
||||||
|
totp_secret: '',
|
||||||
starting: false,
|
starting: false,
|
||||||
session_id: '',
|
session_id: '',
|
||||||
user_code: '',
|
user_code: '',
|
||||||
@@ -608,16 +666,19 @@ function stopDevicePolling() {
|
|||||||
|
|
||||||
function resetDevice() {
|
function resetDevice() {
|
||||||
stopDevicePolling()
|
stopDevicePolling()
|
||||||
const { auth_type, start_url, region } = device.value
|
totp.stop()
|
||||||
|
const { auth_type, start_url, region, totp_secret } = device.value
|
||||||
device.value = createInitialDeviceState()
|
device.value = createInitialDeviceState()
|
||||||
device.value.auth_type = auth_type
|
device.value.auth_type = auth_type
|
||||||
device.value.start_url = start_url
|
device.value.start_url = start_url
|
||||||
device.value.region = region
|
device.value.region = region
|
||||||
|
device.value.totp_secret = totp_secret
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
oauth.value = createInitialOAuthState()
|
oauth.value = createInitialOAuthState()
|
||||||
stopDevicePolling()
|
stopDevicePolling()
|
||||||
|
totp.stop()
|
||||||
device.value = createInitialDeviceState()
|
device.value = createInitialDeviceState()
|
||||||
importText.value = ''
|
importText.value = ''
|
||||||
importFileName.value = ''
|
importFileName.value = ''
|
||||||
@@ -883,6 +944,10 @@ async function startDeviceAuth() {
|
|||||||
device.value.status = 'pending'
|
device.value.status = 'pending'
|
||||||
startCountdown()
|
startCountdown()
|
||||||
scheduleDevicePoll()
|
scheduleDevicePoll()
|
||||||
|
// 如果配置了 TOTP secret,启动验证码生成
|
||||||
|
if (device.value.totp_secret.trim()) {
|
||||||
|
totp.start(device.value.totp_secret.trim())
|
||||||
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const errorMessage = parseApiError(err, '发起设备授权失败')
|
const errorMessage = parseApiError(err, '发起设备授权失败')
|
||||||
showError(errorMessage, '错误')
|
showError(errorMessage, '错误')
|
||||||
@@ -909,6 +974,7 @@ async function pollDevice() {
|
|||||||
switch (result.status) {
|
switch (result.status) {
|
||||||
case 'authorized':
|
case 'authorized':
|
||||||
stopDevicePolling()
|
stopDevicePolling()
|
||||||
|
totp.stop()
|
||||||
device.value.status = 'authorized'
|
device.value.status = 'authorized'
|
||||||
success(result.email ? `授权成功: ${result.email}` : '授权成功,账号已添加')
|
success(result.email ? `授权成功: ${result.email}` : '授权成功,账号已添加')
|
||||||
emit('saved')
|
emit('saved')
|
||||||
|
|||||||
@@ -2037,6 +2037,15 @@ async def _sso_oidc_post(
|
|||||||
# 返回原始 JSON 让调用方处理错误码
|
# 返回原始 JSON 让调用方处理错误码
|
||||||
try:
|
try:
|
||||||
err_data = resp.json()
|
err_data = resp.json()
|
||||||
|
err_code = err_data.get("error", "")
|
||||||
|
if err_code in ("authorization_pending", "slow_down"):
|
||||||
|
logger.debug(
|
||||||
|
"SSO OIDC polling: {} returned {} ({})",
|
||||||
|
url,
|
||||||
|
resp.status_code,
|
||||||
|
err_code,
|
||||||
|
)
|
||||||
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"SSO OIDC request failed: {} returned {} | body={}",
|
"SSO OIDC request failed: {} returned {} | body={}",
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -371,9 +371,7 @@ class HandlerAdapterBase(ApiAdapter):
|
|||||||
elif is_antigravity:
|
elif is_antigravity:
|
||||||
from src.services.provider.adapters.antigravity.constants import (
|
from src.services.provider.adapters.antigravity.constants import (
|
||||||
V1INTERNAL_PATH_TEMPLATE,
|
V1INTERNAL_PATH_TEMPLATE,
|
||||||
)
|
get_v1internal_extra_headers,
|
||||||
from src.services.provider.adapters.antigravity.constants import (
|
|
||||||
get_http_user_agent as _get_antigravity_ua,
|
|
||||||
)
|
)
|
||||||
from src.services.provider.adapters.antigravity.url_availability import (
|
from src.services.provider.adapters.antigravity.url_availability import (
|
||||||
url_availability,
|
url_availability,
|
||||||
@@ -392,7 +390,7 @@ class HandlerAdapterBase(ApiAdapter):
|
|||||||
merged_extra.update(cli_extra)
|
merged_extra.update(cli_extra)
|
||||||
|
|
||||||
if is_antigravity:
|
if is_antigravity:
|
||||||
merged_extra["User-Agent"] = _get_antigravity_ua()
|
merged_extra.update(get_v1internal_extra_headers())
|
||||||
|
|
||||||
if is_kiro:
|
if is_kiro:
|
||||||
from src.services.provider.adapters.kiro.headers import (
|
from src.services.provider.adapters.kiro.headers import (
|
||||||
|
|||||||
@@ -297,9 +297,7 @@ class GeminiChatAdapter(ChatAdapterBase):
|
|||||||
if is_antigravity:
|
if is_antigravity:
|
||||||
from src.services.provider.adapters.antigravity.constants import (
|
from src.services.provider.adapters.antigravity.constants import (
|
||||||
V1INTERNAL_PATH_TEMPLATE,
|
V1INTERNAL_PATH_TEMPLATE,
|
||||||
)
|
get_v1internal_extra_headers,
|
||||||
from src.services.provider.adapters.antigravity.constants import (
|
|
||||||
get_http_user_agent as _get_antigravity_ua,
|
|
||||||
)
|
)
|
||||||
from src.services.provider.adapters.antigravity.url_availability import url_availability
|
from src.services.provider.adapters.antigravity.url_availability import url_availability
|
||||||
|
|
||||||
@@ -316,7 +314,7 @@ class GeminiChatAdapter(ChatAdapterBase):
|
|||||||
# Antigravity 需要特定的 User-Agent
|
# Antigravity 需要特定的 User-Agent
|
||||||
merged_extra = dict(extra_headers) if extra_headers else {}
|
merged_extra = dict(extra_headers) if extra_headers else {}
|
||||||
if is_antigravity:
|
if is_antigravity:
|
||||||
merged_extra["User-Agent"] = _get_antigravity_ua()
|
merged_extra.update(get_v1internal_extra_headers())
|
||||||
headers = cls.build_headers_with_extra(api_key, merged_extra if merged_extra else None)
|
headers = cls.build_headers_with_extra(api_key, merged_extra if merged_extra else None)
|
||||||
|
|
||||||
# OAuth 统一处理:替换端点默认认证头(x-goog-api-key)为 Authorization: Bearer
|
# OAuth 统一处理:替换端点默认认证头(x-goog-api-key)为 Authorization: Bearer
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from src.services.provider.adapters.antigravity.constants import (
|
|||||||
PROD_BASE_URL,
|
PROD_BASE_URL,
|
||||||
SANDBOX_BASE_URL,
|
SANDBOX_BASE_URL,
|
||||||
VERSION_FETCH_URL,
|
VERSION_FETCH_URL,
|
||||||
get_http_user_agent,
|
get_v1internal_extra_headers,
|
||||||
parse_version_string,
|
parse_version_string,
|
||||||
update_user_agent_version,
|
update_user_agent_version,
|
||||||
)
|
)
|
||||||
@@ -242,8 +242,8 @@ async def load_code_assist(
|
|||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {access_token}",
|
"Authorization": f"Bearer {access_token}",
|
||||||
"User-Agent": get_http_user_agent(),
|
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
**get_v1internal_extra_headers(),
|
||||||
}
|
}
|
||||||
body = {"metadata": _CODE_ASSIST_METADATA}
|
body = {"metadata": _CODE_ASSIST_METADATA}
|
||||||
|
|
||||||
@@ -339,8 +339,8 @@ async def onboard_user(
|
|||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {access_token}",
|
"Authorization": f"Bearer {access_token}",
|
||||||
"User-Agent": get_http_user_agent(),
|
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
**get_v1internal_extra_headers(),
|
||||||
}
|
}
|
||||||
body = {
|
body = {
|
||||||
"tierId": tier_id,
|
"tierId": tier_id,
|
||||||
@@ -447,9 +447,9 @@ async def fetch_available_models(
|
|||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {access_token}",
|
"Authorization": f"Bearer {access_token}",
|
||||||
"User-Agent": get_http_user_agent(),
|
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
**get_v1internal_extra_headers(),
|
||||||
}
|
}
|
||||||
body = {"project": project_id}
|
body = {"project": project_id}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
import uuid
|
||||||
|
|
||||||
# ============== API 端点 ==============
|
# ============== API 端点 ==============
|
||||||
# 唯一定义在 core 层,此处 re-export 保持向后兼容
|
# 唯一定义在 core 层,此处 re-export 保持向后兼容
|
||||||
@@ -19,25 +20,31 @@ SANDBOX_BASE_URL = "https://daily-cloudcode-pa.sandbox.googleapis.com"
|
|||||||
|
|
||||||
# ============== User-Agent ==============
|
# ============== User-Agent ==============
|
||||||
VERSION_FETCH_URL = "https://antigravity-auto-updater-974169037036.us-central1.run.app"
|
VERSION_FETCH_URL = "https://antigravity-auto-updater-974169037036.us-central1.run.app"
|
||||||
_FALLBACK_VERSION = "1.15.8"
|
_FALLBACK_VERSION = "1.18.4"
|
||||||
|
_FALLBACK_CHROME = "132.0.6834.160"
|
||||||
|
_FALLBACK_ELECTRON = "39.2.3"
|
||||||
_VERSION_RE = re.compile(r"\d+\.\d+\.\d+")
|
_VERSION_RE = re.compile(r"\d+\.\d+\.\d+")
|
||||||
|
|
||||||
|
|
||||||
def _detect_platform_tag() -> str:
|
def _detect_platform_info() -> str:
|
||||||
"""检测当前运行平台,格式与 Go runtime 保持一致。"""
|
"""检测当前运行平台,格式对齐 AM constants.rs 的 Electron UA。"""
|
||||||
os_name = platform.system().lower() # linux, darwin, windows
|
os_name = platform.system().lower()
|
||||||
arch = platform.machine().lower()
|
if os_name == "darwin":
|
||||||
if arch in ("x86_64", "amd64"):
|
return "Macintosh; Intel Mac OS X 10_15_7"
|
||||||
arch = "amd64"
|
elif os_name == "windows":
|
||||||
elif arch in ("aarch64", "arm64"):
|
return "Windows NT 10.0; Win64; x64"
|
||||||
arch = "arm64"
|
else:
|
||||||
return f"{os_name}/{arch}"
|
return "X11; Linux x86_64"
|
||||||
|
|
||||||
|
|
||||||
_PLATFORM_TAG = _detect_platform_tag()
|
_PLATFORM_INFO = _detect_platform_info()
|
||||||
|
|
||||||
# HTTP Header User-Agent(向后兼容:模块级常量保留,新代码应使用 get_http_user_agent())
|
# HTTP Header User-Agent(对齐 AM constants.rs: 完整 Electron 浏览器格式)
|
||||||
HTTP_USER_AGENT = f"antigravity/{_FALLBACK_VERSION} {_PLATFORM_TAG}"
|
HTTP_USER_AGENT = (
|
||||||
|
f"Mozilla/5.0 ({_PLATFORM_INFO}) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
f"Antigravity/{_FALLBACK_VERSION} Chrome/{_FALLBACK_CHROME} "
|
||||||
|
f"Electron/{_FALLBACK_ELECTRON} Safari/537.36"
|
||||||
|
)
|
||||||
|
|
||||||
# V1InternalRequest.userAgent 字段(固定值)
|
# V1InternalRequest.userAgent 字段(固定值)
|
||||||
REQUEST_USER_AGENT = "antigravity"
|
REQUEST_USER_AGENT = "antigravity"
|
||||||
@@ -48,9 +55,13 @@ _ua_version: str = _FALLBACK_VERSION
|
|||||||
|
|
||||||
|
|
||||||
def get_http_user_agent() -> str:
|
def get_http_user_agent() -> str:
|
||||||
"""返回当前 HTTP User-Agent 字符串(支持动态版本号更新)。"""
|
"""返回当前 HTTP User-Agent 字符串(对齐 AM Electron UA 格式)。"""
|
||||||
with _ua_lock:
|
with _ua_lock:
|
||||||
return f"antigravity/{_ua_version} {_PLATFORM_TAG}"
|
return (
|
||||||
|
f"Mozilla/5.0 ({_PLATFORM_INFO}) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
f"Antigravity/{_ua_version} Chrome/{_FALLBACK_CHROME} "
|
||||||
|
f"Electron/{_FALLBACK_ELECTRON} Safari/537.36"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_user_agent_version(version: str) -> None:
|
def update_user_agent_version(version: str) -> None:
|
||||||
@@ -61,8 +72,11 @@ def update_user_agent_version(version: str) -> None:
|
|||||||
return
|
return
|
||||||
with _ua_lock:
|
with _ua_lock:
|
||||||
_ua_version = version
|
_ua_version = version
|
||||||
# Backward compat: keep module-level constant in sync.
|
HTTP_USER_AGENT = (
|
||||||
HTTP_USER_AGENT = f"antigravity/{_ua_version} {_PLATFORM_TAG}"
|
f"Mozilla/5.0 ({_PLATFORM_INFO}) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
f"Antigravity/{_ua_version} Chrome/{_FALLBACK_CHROME} "
|
||||||
|
f"Electron/{_FALLBACK_ELECTRON} Safari/537.36"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_version_string(text: str) -> str | None:
|
def parse_version_string(text: str) -> str | None:
|
||||||
@@ -74,6 +88,25 @@ def parse_version_string(text: str) -> str | None:
|
|||||||
# ============== URL 可用性 ==============
|
# ============== URL 可用性 ==============
|
||||||
URL_UNAVAILABLE_TTL_SECONDS = 300 # 5 分钟
|
URL_UNAVAILABLE_TTL_SECONDS = 300 # 5 分钟
|
||||||
|
|
||||||
|
# ============== AM Client Identity Headers ==============
|
||||||
|
# 对齐 AM upstream/client.rs: 伪装为官方 Antigravity 客户端
|
||||||
|
# 缺少这些 header 会导致新模型(如 gemini-3.1-pro-preview)返回 404
|
||||||
|
_SESSION_ID = uuid.uuid4().hex # 每次进程启动生成一个固定 session ID
|
||||||
|
|
||||||
|
|
||||||
|
def get_v1internal_extra_headers() -> dict[str, str]:
|
||||||
|
"""构建 v1internal 请求需要的额外 header(对齐 AM upstream/client.rs)。"""
|
||||||
|
with _ua_lock:
|
||||||
|
version = _ua_version
|
||||||
|
return {
|
||||||
|
"User-Agent": get_http_user_agent(),
|
||||||
|
"x-client-name": "antigravity",
|
||||||
|
"x-client-version": version,
|
||||||
|
"x-vscode-sessionid": _SESSION_ID,
|
||||||
|
"x-goog-api-client": "gl-node/18.18.2 fire/0.8.6 grpc/1.10.x",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ============== Thinking Signature ==============
|
# ============== Thinking Signature ==============
|
||||||
# 统一从 core 层导入,避免多处定义
|
# 统一从 core 层导入,避免多处定义
|
||||||
from src.core.api_format.conversion.constants import DUMMY_THOUGHT_SIGNATURE # noqa: E402
|
from src.core.api_format.conversion.constants import DUMMY_THOUGHT_SIGNATURE # noqa: E402
|
||||||
@@ -88,15 +121,12 @@ OUTPUT_OVERHEAD_IMAGE = 2048
|
|||||||
# 模型最大输出限制(防止超限)
|
# 模型最大输出限制(防止超限)
|
||||||
MODEL_MAX_OUTPUT_LIMIT = 65536
|
MODEL_MAX_OUTPUT_LIMIT = 65536
|
||||||
# 包含这些关键字的模型会自动注入 thinkingConfig(如果缺失)
|
# 包含这些关键字的模型会自动注入 thinkingConfig(如果缺失)
|
||||||
THINKING_MODELS_AUTO_INJECT_KEYWORDS = ("thinking", "gemini-2.0-pro", "gemini-3-pro")
|
THINKING_MODELS_AUTO_INJECT_KEYWORDS = (
|
||||||
|
"thinking",
|
||||||
# ============== Model Alias Mapping ==============
|
"gemini-2.0-pro",
|
||||||
# 对齐 AM common_utils.rs: 将预览/别名映射回上游物理模型名
|
"gemini-3-pro",
|
||||||
MODEL_ALIAS_MAP: dict[str, str] = {
|
"gemini-3.1-pro",
|
||||||
"gemini-3-pro-preview": "gemini-3-pro-high",
|
)
|
||||||
"gemini-3-pro-image-preview": "gemini-3-pro-image",
|
|
||||||
"gemini-3-flash-preview": "gemini-3-flash",
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============== Google Search (Grounding) ==============
|
# ============== Google Search (Grounding) ==============
|
||||||
# 对齐 AM common_utils.rs: 仅 gemini-2.5-flash 支持 googleSearch tool
|
# 对齐 AM common_utils.rs: 仅 gemini-2.5-flash 支持 googleSearch tool
|
||||||
@@ -203,7 +233,6 @@ __all__ = [
|
|||||||
"IMAGE_ASPECT_RATIO_SUFFIXES",
|
"IMAGE_ASPECT_RATIO_SUFFIXES",
|
||||||
"IMAGE_GEN_UPSTREAM_MODEL",
|
"IMAGE_GEN_UPSTREAM_MODEL",
|
||||||
"MIN_SIGNATURE_LENGTH",
|
"MIN_SIGNATURE_LENGTH",
|
||||||
"MODEL_ALIAS_MAP",
|
|
||||||
"MODEL_MAX_OUTPUT_LIMIT",
|
"MODEL_MAX_OUTPUT_LIMIT",
|
||||||
"NETWORKING_TOOL_KEYWORDS",
|
"NETWORKING_TOOL_KEYWORDS",
|
||||||
"OUTPUT_OVERHEAD",
|
"OUTPUT_OVERHEAD",
|
||||||
@@ -225,6 +254,7 @@ __all__ = [
|
|||||||
"VERSION_FETCH_URL",
|
"VERSION_FETCH_URL",
|
||||||
"WEB_SEARCH_MODEL",
|
"WEB_SEARCH_MODEL",
|
||||||
"get_http_user_agent",
|
"get_http_user_agent",
|
||||||
|
"get_v1internal_extra_headers",
|
||||||
"parse_version_string",
|
"parse_version_string",
|
||||||
"update_user_agent_version",
|
"update_user_agent_version",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from src.services.provider.adapters.antigravity.constants import (
|
|||||||
ASPECT_RATIO_TABLE,
|
ASPECT_RATIO_TABLE,
|
||||||
IMAGE_ASPECT_RATIO_SUFFIXES,
|
IMAGE_ASPECT_RATIO_SUFFIXES,
|
||||||
IMAGE_GEN_UPSTREAM_MODEL,
|
IMAGE_GEN_UPSTREAM_MODEL,
|
||||||
MODEL_ALIAS_MAP,
|
|
||||||
MODEL_MAX_OUTPUT_LIMIT,
|
MODEL_MAX_OUTPUT_LIMIT,
|
||||||
NETWORKING_TOOL_KEYWORDS,
|
NETWORKING_TOOL_KEYWORDS,
|
||||||
OUTPUT_OVERHEAD,
|
OUTPUT_OVERHEAD,
|
||||||
@@ -45,7 +44,6 @@ from src.services.provider.adapters.antigravity.constants import (
|
|||||||
THINKING_BUDGET_DEFAULT_INJECT,
|
THINKING_BUDGET_DEFAULT_INJECT,
|
||||||
THINKING_MODELS_AUTO_INJECT_KEYWORDS,
|
THINKING_MODELS_AUTO_INJECT_KEYWORDS,
|
||||||
WEB_SEARCH_MODEL,
|
WEB_SEARCH_MODEL,
|
||||||
get_http_user_agent,
|
|
||||||
)
|
)
|
||||||
from src.services.provider.adapters.antigravity.url_availability import url_availability
|
from src.services.provider.adapters.antigravity.url_availability import url_availability
|
||||||
from src.services.provider.request_context import get_selected_base_url
|
from src.services.provider.request_context import get_selected_base_url
|
||||||
@@ -396,26 +394,6 @@ def _clean_json_schema(schema: dict[str, Any]) -> None:
|
|||||||
clean_gemini_schema(schema)
|
clean_gemini_schema(schema)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Model alias mapping(对齐 AM common_utils.rs)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_model_alias(model: str) -> str:
|
|
||||||
"""将预览/别名模型名映射回上游物理模型名。
|
|
||||||
|
|
||||||
对齐 AM common_utils.rs resolve_request_config:
|
|
||||||
- gemini-3-pro-preview → gemini-3-pro-high
|
|
||||||
- gemini-3-pro-image-preview → gemini-3-pro-image
|
|
||||||
- gemini-3-flash-preview → gemini-3-flash
|
|
||||||
- 同时剥离 -online 后缀
|
|
||||||
"""
|
|
||||||
resolved = model.rstrip()
|
|
||||||
# 剥离 -online 后缀(联网意图由 tools 检测,不依赖后缀传递到上游)
|
|
||||||
resolved = resolved.removesuffix("-online")
|
|
||||||
return MODEL_ALIAS_MAP.get(resolved, resolved)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Google Search (Grounding) 检测与注入(对齐 AM common_utils.rs)
|
# Google Search (Grounding) 检测与注入(对齐 AM common_utils.rs)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -895,9 +873,9 @@ def wrap_v1internal_request(
|
|||||||
# 1. 深度清理 [undefined]
|
# 1. 深度清理 [undefined]
|
||||||
_deep_clean_undefined(inner_request)
|
_deep_clean_undefined(inner_request)
|
||||||
|
|
||||||
# 2. 模型别名映射(对齐 AM common_utils.rs)
|
# 2. 剥离 -online 后缀(联网意图由 tools 检测,不依赖后缀传递到上游)
|
||||||
has_online_suffix = _detect_online_suffix(model)
|
has_online_suffix = _detect_online_suffix(model)
|
||||||
final_model = _resolve_model_alias(model)
|
final_model = model.rstrip().removesuffix("-online")
|
||||||
|
|
||||||
# 3. 联网检测(对齐 AM:-online 后缀或客户端声明了联网工具)
|
# 3. 联网检测(对齐 AM:-online 后缀或客户端声明了联网工具)
|
||||||
has_networking = has_online_suffix or _detect_networking_tools(inner_request)
|
has_networking = has_online_suffix or _detect_networking_tools(inner_request)
|
||||||
@@ -1044,7 +1022,11 @@ class AntigravityV1InternalEnvelope:
|
|||||||
name = "antigravity:v1internal"
|
name = "antigravity:v1internal"
|
||||||
|
|
||||||
def extra_headers(self) -> dict[str, str] | None:
|
def extra_headers(self) -> dict[str, str] | None:
|
||||||
return {"User-Agent": get_http_user_agent()}
|
from src.services.provider.adapters.antigravity.constants import (
|
||||||
|
get_v1internal_extra_headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
return get_v1internal_extra_headers()
|
||||||
|
|
||||||
def wrap_request(
|
def wrap_request(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -251,6 +251,14 @@ class MaintenanceScheduler:
|
|||||||
name="Gemini文件映射清理",
|
name="Gemini文件映射清理",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Antigravity User-Agent 版本刷新 - 每 6 小时
|
||||||
|
scheduler.add_interval_job(
|
||||||
|
self._scheduled_antigravity_ua_refresh,
|
||||||
|
hours=6,
|
||||||
|
job_id="antigravity_ua_refresh",
|
||||||
|
name="Antigravity UA版本刷新",
|
||||||
|
)
|
||||||
|
|
||||||
# Provider 签到任务 - 根据配置时间执行
|
# Provider 签到任务 - 根据配置时间执行
|
||||||
checkin_hour, checkin_minute = self._get_checkin_time()
|
checkin_hour, checkin_minute = self._get_checkin_time()
|
||||||
scheduler.add_cron_job(
|
scheduler.add_cron_job(
|
||||||
@@ -280,6 +288,14 @@ class MaintenanceScheduler:
|
|||||||
# 增加延迟时间避免与 UsageQueueConsumer 等后台任务竞争数据库连接
|
# 增加延迟时间避免与 UsageQueueConsumer 等后台任务竞争数据库连接
|
||||||
await asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
# 刷新 Antigravity User-Agent 版本号(不阻塞其他启动任务)
|
||||||
|
try:
|
||||||
|
from src.services.provider.adapters.antigravity.client import refresh_user_agent
|
||||||
|
|
||||||
|
await refresh_user_agent()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("启动时刷新 Antigravity UA 版本失败(不影响运行): {}", e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info("启动时执行首次清理任务...")
|
logger.info("启动时执行首次清理任务...")
|
||||||
await self._perform_cleanup()
|
await self._perform_cleanup()
|
||||||
@@ -338,6 +354,15 @@ class MaintenanceScheduler:
|
|||||||
"""Gemini 文件映射清理任务(定时调用)"""
|
"""Gemini 文件映射清理任务(定时调用)"""
|
||||||
await self._perform_gemini_file_mapping_cleanup()
|
await self._perform_gemini_file_mapping_cleanup()
|
||||||
|
|
||||||
|
async def _scheduled_antigravity_ua_refresh(self) -> None:
|
||||||
|
"""Antigravity User-Agent 版本刷新(定时调用)"""
|
||||||
|
try:
|
||||||
|
from src.services.provider.adapters.antigravity.client import refresh_user_agent
|
||||||
|
|
||||||
|
await refresh_user_agent()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("定时刷新 Antigravity UA 版本失败: {}", e)
|
||||||
|
|
||||||
async def _scheduled_provider_checkin(self) -> None:
|
async def _scheduled_provider_checkin(self) -> None:
|
||||||
"""Provider 签到任务(定时调用)"""
|
"""Provider 签到任务(定时调用)"""
|
||||||
await self._perform_provider_checkin()
|
await self._perform_provider_checkin()
|
||||||
|
|||||||
Reference in New Issue
Block a user