feat(frontend): add kiro api and model presets

This commit is contained in:
nianzs
2026-04-29 16:29:35 +08:00
parent 05bc424c9a
commit e8fc09869b
6 changed files with 369 additions and 4 deletions
+3
View File
@@ -17,6 +17,7 @@ import subscriptionsAPI from './subscriptions'
import usageAPI from './usage'
import geminiAPI from './gemini'
import antigravityAPI from './antigravity'
import kiroAPI from './kiro'
import userAttributesAPI from './userAttributes'
import opsAPI from './ops'
import errorPassthroughAPI from './errorPassthrough'
@@ -49,6 +50,7 @@ export const adminAPI = {
usage: usageAPI,
gemini: geminiAPI,
antigravity: antigravityAPI,
kiro: kiroAPI,
userAttributes: userAttributesAPI,
ops: opsAPI,
errorPassthrough: errorPassthroughAPI,
@@ -79,6 +81,7 @@ export {
usageAPI,
geminiAPI,
antigravityAPI,
kiroAPI,
userAttributesAPI,
opsAPI,
errorPassthroughAPI,
+89
View File
@@ -0,0 +1,89 @@
import { apiClient } from '../client'
export interface KiroAuthUrlResponse {
auth_url: string
session_id: string
state: string
}
export interface KiroIDCAuthUrlResponse extends KiroAuthUrlResponse {
client_id?: string
region?: string
start_url?: string
}
export interface KiroTokenInfo {
access_token?: string
refresh_token?: string
profile_arn?: string
expires_at?: string
auth_method?: string
provider?: string
client_id?: string
client_secret?: string
client_id_hash?: string
email?: string
start_url?: string
region?: string
[key: string]: unknown
}
export async function generateAuthUrl(payload: {
proxy_id?: number
provider?: string
}): Promise<KiroAuthUrlResponse> {
const { data } = await apiClient.post<KiroAuthUrlResponse>('/admin/kiro/oauth/auth-url', payload)
return data
}
export async function generateIDCAuthUrl(payload: {
proxy_id?: number
start_url?: string
region?: string
}): Promise<KiroIDCAuthUrlResponse> {
const { data } = await apiClient.post<KiroIDCAuthUrlResponse>('/admin/kiro/oauth/idc-auth-url', payload)
return data
}
export async function exchangeCode(payload: {
session_id: string
state: string
code: string
callback_path?: string
login_option?: string
proxy_id?: number
}): Promise<KiroTokenInfo> {
const { data } = await apiClient.post<KiroTokenInfo>('/admin/kiro/oauth/exchange-code', payload)
return data
}
export async function refreshToken(payload: {
refresh_token: string
auth_method?: string
provider?: string
client_id?: string
client_secret?: string
start_url?: string
region?: string
profile_arn?: string
proxy_id?: number
}): Promise<KiroTokenInfo> {
const { data } = await apiClient.post<KiroTokenInfo>('/admin/kiro/oauth/refresh-token', payload)
return data
}
export async function importToken(payload: {
token_json: string
device_registration_json?: string
}): Promise<KiroTokenInfo> {
const { data } = await apiClient.post<KiroTokenInfo>('/admin/kiro/oauth/import-token', payload)
return data
}
export default {
generateAuthUrl,
generateIDCAuthUrl,
exchangeCode,
refreshToken,
importToken
}
@@ -50,6 +50,15 @@ describe('useModelWhitelist', () => {
expect(models.indexOf('gemini-2.5-flash-image')).toBeLessThan(models.indexOf('gemini-2.5-flash-lite'))
})
it('kiro 模型列表不暴露旧的 -agentic / -chat 后缀', () => {
const models = getModelsByPlatform('kiro')
expect(models).toContain('claude-sonnet-4-6')
expect(models).toContain('claude-sonnet-4-6-thinking')
expect(models).not.toContain('claude-sonnet-4-6-chat')
expect(models.every((model) => !model.endsWith('-agentic') && !model.endsWith('-chat'))).toBe(true)
})
it('whitelist 模式会忽略通配符条目', () => {
const mapping = buildModelMappingObject('whitelist', ['claude-*', 'gemini-3.1-flash-image'], [])
expect(mapping).toEqual({
+191
View File
@@ -0,0 +1,191 @@
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
import type { KiroTokenInfo } from '@/api/admin/kiro'
export function useKiroOAuth() {
const appStore = useAppStore()
const { t } = useI18n()
const authUrl = ref('')
const sessionId = ref('')
const state = ref('')
const loading = ref(false)
const error = ref('')
const resetState = () => {
authUrl.value = ''
sessionId.value = ''
state.value = ''
loading.value = false
error.value = ''
}
const generateAuthUrl = async (
proxyId: number | null | undefined,
provider: 'Google' | 'Github' = 'Google'
): Promise<boolean> => {
loading.value = true
error.value = ''
authUrl.value = ''
sessionId.value = ''
state.value = ''
try {
const response = await adminAPI.kiro.generateAuthUrl({
proxy_id: proxyId || undefined,
provider
})
authUrl.value = response.auth_url
sessionId.value = response.session_id
state.value = response.state
return true
} catch (err: any) {
error.value = err.response?.data?.detail || t('admin.accounts.oauth.authFailed')
appStore.showError(error.value)
return false
} finally {
loading.value = false
}
}
const generateIDCAuthUrl = async (
params: { proxyId?: number | null; startUrl?: string; region?: string }
): Promise<boolean> => {
loading.value = true
error.value = ''
authUrl.value = ''
sessionId.value = ''
state.value = ''
try {
const response = await adminAPI.kiro.generateIDCAuthUrl({
proxy_id: params.proxyId || undefined,
start_url: params.startUrl,
region: params.region
})
authUrl.value = response.auth_url
sessionId.value = response.session_id
state.value = response.state
return true
} catch (err: any) {
error.value = err.response?.data?.detail || t('admin.accounts.oauth.authFailed')
appStore.showError(error.value)
return false
} finally {
loading.value = false
}
}
const exchangeAuthCode = async (params: {
code: string
sessionId: string
state: string
callbackPath?: string
loginOption?: string
proxyId?: number | null
}): Promise<KiroTokenInfo | null> => {
loading.value = true
error.value = ''
try {
return await adminAPI.kiro.exchangeCode({
session_id: params.sessionId,
state: params.state,
code: params.code.trim(),
callback_path: params.callbackPath,
login_option: params.loginOption,
proxy_id: params.proxyId || undefined
})
} catch (err: any) {
error.value = err.response?.data?.detail || t('admin.accounts.oauth.authFailed')
appStore.showError(error.value)
return null
} finally {
loading.value = false
}
}
const validateRefreshToken = async (payload: {
refreshToken: string
authMethod?: string
provider?: string
clientId?: string
clientSecret?: string
startUrl?: string
region?: string
profileArn?: string
proxyId?: number | null
}): Promise<KiroTokenInfo | null> => {
loading.value = true
error.value = ''
try {
return await adminAPI.kiro.refreshToken({
refresh_token: payload.refreshToken.trim(),
auth_method: payload.authMethod,
provider: payload.provider,
client_id: payload.clientId,
client_secret: payload.clientSecret,
start_url: payload.startUrl,
region: payload.region,
profile_arn: payload.profileArn,
proxy_id: payload.proxyId || undefined
})
} catch (err: any) {
error.value = err.response?.data?.detail || t('admin.accounts.oauth.authFailed')
return null
} finally {
loading.value = false
}
}
const importToken = async (
tokenJSON: string,
deviceRegistrationJSON?: string
): Promise<KiroTokenInfo | null> => {
loading.value = true
error.value = ''
try {
return await adminAPI.kiro.importToken({
token_json: tokenJSON,
device_registration_json: deviceRegistrationJSON
})
} catch (err: any) {
error.value = err.response?.data?.detail || t('admin.accounts.oauth.authFailed')
appStore.showError(error.value)
return null
} finally {
loading.value = false
}
}
const buildCredentials = (tokenInfo: KiroTokenInfo): Record<string, unknown> => ({
access_token: tokenInfo.access_token,
refresh_token: tokenInfo.refresh_token,
profile_arn: tokenInfo.profile_arn,
expires_at: tokenInfo.expires_at,
auth_method: tokenInfo.auth_method,
provider: tokenInfo.provider,
client_id: tokenInfo.client_id,
client_secret: tokenInfo.client_secret,
client_id_hash: tokenInfo.client_id_hash,
email: tokenInfo.email,
start_url: tokenInfo.start_url,
region: tokenInfo.region
})
return {
authUrl,
sessionId,
state,
loading,
error,
resetState,
generateAuthUrl,
generateIDCAuthUrl,
exchangeAuthCode,
validateRefreshToken,
importToken,
buildCredentials
}
}
+41 -2
View File
@@ -76,6 +76,19 @@ const antigravityModels = [
'tab_flash_lite_preview'
]
const kiroModels = [
'claude-opus-4-6',
'claude-opus-4-6-thinking',
'claude-sonnet-4-6',
'claude-sonnet-4-6-thinking',
'claude-opus-4-5-20251101',
'claude-opus-4-5-20251101-thinking',
'claude-sonnet-4-5-20250929',
'claude-sonnet-4-5-20250929-thinking',
'claude-haiku-4-5-20251001',
'claude-haiku-4-5-20251001-thinking'
]
// 智谱 GLM
const zhipuModels = [
'glm-4', 'glm-4v', 'glm-4-plus', 'glm-4-0520',
@@ -298,6 +311,19 @@ const antigravityPresetMappings = [
{ label: 'Opus 4.7', from: 'claude-opus-4-7', to: 'claude-opus-4-7', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }
]
const kiroPresetMappings = [
{ label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'claude-opus-4.6', color: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200 dark:bg-yellow-900/30 dark:text-yellow-300' },
{ label: 'Opus 4.6 Thinking', from: 'claude-opus-4-6-thinking', to: 'claude-opus-4.6', color: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200 dark:bg-yellow-900/30 dark:text-yellow-300' },
{ label: 'Sonnet 4.6', from: 'claude-sonnet-4-6', to: 'claude-sonnet-4.6', color: 'bg-orange-100 text-orange-700 hover:bg-orange-200 dark:bg-orange-900/30 dark:text-orange-300' },
{ label: 'Sonnet 4.6 Thinking', from: 'claude-sonnet-4-6-thinking', to: 'claude-sonnet-4.6', color: 'bg-orange-100 text-orange-700 hover:bg-orange-200 dark:bg-orange-900/30 dark:text-orange-300' },
{ label: 'Opus 4.5', from: 'claude-opus-4-5-20251101', to: 'claude-opus-4.5', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-300' },
{ label: 'Opus 4.5 Thinking', from: 'claude-opus-4-5-20251101-thinking', to: 'claude-opus-4.5', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-300' },
{ label: 'Sonnet 4.5', from: 'claude-sonnet-4-5-20250929', to: 'claude-sonnet-4.5', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-300' },
{ label: 'Sonnet 4.5 Thinking', from: 'claude-sonnet-4-5-20250929-thinking', to: 'claude-sonnet-4.5', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-300' },
{ label: 'Haiku 4.5', from: 'claude-haiku-4-5-20251001', to: 'claude-haiku-4.5', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-300' },
{ label: 'Haiku 4.5 Thinking', from: 'claude-haiku-4-5-20251001-thinking', to: 'claude-haiku-4.5', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-300' }
]
// Bedrock 预设映射(与后端 DefaultBedrockModelMapping 保持一致)
const bedrockPresetMappings = [
{ label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'us.anthropic.claude-opus-4-6-v1', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' },
@@ -308,15 +334,18 @@ const bedrockPresetMappings = [
{ label: 'Haiku 4.5', from: 'claude-haiku-4-5', to: 'us.anthropic.claude-haiku-4-5-20251001-v1:0', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400' },
]
const kiroDefaultMappings = kiroPresetMappings.map(({ from, to }) => ({ from, to }))
// Antigravity 默认映射(从后端 API 获取,与 constants.go 保持一致)
// 使用 fetchAntigravityDefaultMappings() 异步获取
import { getAntigravityDefaultModelMapping } from '@/api/admin/accounts'
let _antigravityDefaultMappingsCache: { from: string; to: string }[] | null = null
let _kiroDefaultMappingsCache: { from: string; to: string }[] | null = null
export async function fetchAntigravityDefaultMappings(): Promise<{ from: string; to: string }[]> {
if (_antigravityDefaultMappingsCache !== null) {
return _antigravityDefaultMappingsCache
return _antigravityDefaultMappingsCache.map(({ from, to }) => ({ from, to }))
}
try {
const mapping = await getAntigravityDefaultModelMapping()
@@ -325,7 +354,15 @@ export async function fetchAntigravityDefaultMappings(): Promise<{ from: string;
console.warn('[fetchAntigravityDefaultMappings] API failed, using empty fallback', e)
_antigravityDefaultMappingsCache = []
}
return _antigravityDefaultMappingsCache
return _antigravityDefaultMappingsCache.map(({ from, to }) => ({ from, to }))
}
export async function fetchKiroDefaultMappings(): Promise<{ from: string; to: string }[]> {
if (_kiroDefaultMappingsCache !== null) {
return _kiroDefaultMappingsCache.map(({ from, to }) => ({ from, to }))
}
_kiroDefaultMappingsCache = kiroDefaultMappings.map(({ from, to }) => ({ from, to }))
return _kiroDefaultMappingsCache.map(({ from, to }) => ({ from, to }))
}
// =====================
@@ -354,6 +391,7 @@ export function getModelsByPlatform(platform: string): string[] {
case 'claude': return claudeModels
case 'gemini': return geminiModels
case 'antigravity': return antigravityModels
case 'kiro': return kiroModels
case 'zhipu': return zhipuModels
case 'qwen': return qwenModels
case 'deepseek': return deepseekModels
@@ -378,6 +416,7 @@ export function getPresetMappingsByPlatform(platform: string) {
if (platform === 'openai') return openaiPresetMappings
if (platform === 'gemini') return geminiPresetMappings
if (platform === 'antigravity') return antigravityPresetMappings
if (platform === 'kiro') return kiroPresetMappings
if (platform === 'bedrock') return bedrockPresetMappings
return anthropicPresetMappings
}
+36 -2
View File
@@ -468,7 +468,7 @@ export interface PaginationConfig {
// ==================== API Key & Group Types ====================
export type GroupPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity'
export type GroupPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity' | 'kiro'
export type SubscriptionType = 'standard' | 'subscription'
@@ -642,7 +642,7 @@ export interface UpdateGroupRequest {
// ==================== Account & Proxy Types ====================
export type AccountPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity'
export type AccountPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity' | 'kiro'
export type AccountType = 'oauth' | 'setup-token' | 'apikey' | 'upstream' | 'bedrock' | 'service_account'
export type OAuthAddMethod = 'oauth' | 'setup-token'
export type ProxyProtocol = 'http' | 'https' | 'socks5' | 'socks5h'
@@ -801,6 +801,12 @@ export interface Account {
overload_until: string | null
temp_unschedulable_until: string | null
temp_unschedulable_reason: string | null
kiro_quota_state?: string | null
kiro_quota_reason?: string | null
kiro_quota_reset_at?: string | null
kiro_runtime_state?: string | null
kiro_runtime_reason?: string | null
kiro_runtime_reset_at?: string | null
// Session window fields (5-hour window)
session_window_start: string | null
@@ -885,6 +891,21 @@ export interface AntigravityModelQuota {
reset_time: string // 重置时间 ISO8601
}
export interface KiroCreditProgress {
current_usage: number
usage_limit: number
percentage_used: number
days_remaining?: number
expiry_date?: string | null
}
export interface KiroOverageInfo {
current_overages: number
overage_charges: number
currency_code?: string
currency_symbol?: string
}
export interface AccountUsageInfo {
source?: 'passive' | 'active'
updated_at: string | null
@@ -903,6 +924,19 @@ export interface AccountUsageInfo {
amount?: number
minimum_balance?: number
}> | null
kiro_subscription_name?: string | null
kiro_subscription_type?: string | null
kiro_reset_at?: string | null
kiro_overages_enabled?: boolean
kiro_credit?: KiroCreditProgress | null
kiro_bonus?: KiroCreditProgress | null
kiro_overage?: KiroOverageInfo | null
kiro_quota_state?: string | null
kiro_quota_reason?: string | null
kiro_quota_reset_at?: string | null
kiro_runtime_state?: string | null
kiro_runtime_reason?: string | null
kiro_runtime_reset_at?: string | null
// Antigravity 403 forbidden 状态
is_forbidden?: boolean
forbidden_reason?: string