📄 models.ts  •  6021 bytes
/**
 * CmdCode V0.5 - 模型提供商注册表
 * 
 * 内置免费/低成本大模型API列表,启动时交互式选择
 * 支持用户自定义接入任意 OpenAI 兼容 API
 */

export interface ModelProvider {
  id: string           // 模型ID(用于API调用)
  name: string         // 显示名称
  vendor: string       // 厂商
  url: string          // API Base URL
  free: boolean        // 是否免费
  description: string  // 简短描述
  apiKeyHint: string   // API Key获取提示
}

/** 内置模型提供商列表 */
export const BUILTIN_PROVIDERS: ModelProvider[] = [
  // ═══════════════════════════════════════
  // 付费模型(按量计费 / 赠送额度)
  // ═══════════════════════════════════════
  {
    id: 'deepseek-v4-flash',
    name: 'DeepSeek V4 Flash',
    vendor: 'DeepSeek',
    url: 'https://api.deepseek.com/v1',
    free: false,
    description: 'DeepSeek V4 Flash,快速响应,新用户赠送额度',
    apiKeyHint: 'platform.deepseek.com/api_keys → API Key',
  },
  {
    id: 'deepseek-v4-pro',
    name: 'DeepSeek V4 Pro',
    vendor: 'DeepSeek',
    url: 'https://api.deepseek.com/v1',
    free: false,
    description: 'DeepSeek V4 Pro,深度推理,适合复杂任务',
    apiKeyHint: 'platform.deepseek.com/api_keys → API Key',
  },
  {
    id: 'minimax-m2.7-m1',
    name: 'MiniMax-M2.7 (m1)',
    vendor: 'MiniMax',
    url: 'https://api.minimaxi.com/v1',
    free: false,
    description: 'MiniMax M2.7 (m1) 119元套餐',
    apiKeyHint: 'platform.minimaxi.com → API Key',
  },
  {
    id: 'minimax-m2.7-m2',
    name: 'MiniMax-M2.7 (m2)',
    vendor: 'MiniMax',
    url: 'https://api.minimaxi.com/v1',
    free: false,
    description: 'MiniMax M2.7 (m2) 119元套餐',
    apiKeyHint: 'platform.minimaxi.com → API Key',
  },
  {
    id: 'minimax-m2.7-m3',
    name: 'MiniMax-M2.7 (m3)',
    vendor: 'MiniMax',
    url: 'https://api.minimaxi.com/v1',
    free: false,
    description: 'MiniMax M2.7 (m3) 199极速套餐',
    apiKeyHint: 'platform.minimaxi.com → API Key',
  },
  {
    id: 'glm-5.1',
    name: 'GLM-5.1 (火山引擎)',
    vendor: '字节跳动',
    url: 'https://ark.cn-beijing.volces.com/api/coding/v3',
    free: false,
    description: '火山引擎 Ark GLM-5.1 编码模型',
    apiKeyHint: 'console.volcengine.com/ark → API Key',
  },
  {
    id: 'glm-5',
    name: 'GLM-5 (腾讯云)',
    vendor: '腾讯云',
    url: 'https://api.lkeap.cloud.tencent.com/coding/v3',
    free: false,
    description: '腾讯云 GLM-5 编码模型',
    apiKeyHint: 'console.cloud.tencent.com/lkeap → API Key',
  },
  // ═══════════════════════════════════════
  // 小米 MiMo(Token Plan 计费,双认证头 api-key + Bearer)
  // ═══════════════════════════════════════
  {
    id: 'mimo-v2.5-pro',
    name: 'MiMo V2.5 Pro',
    vendor: '小米',
    url: 'https://api.xiaomimimo.com/v1',
    free: false,
    description: '小米MiMo V2.5 Pro,深度推理,1M上下文,128K输出',
    apiKeyHint: 'platform.xiaomimimo.com → API Key',
  },
  {
    id: 'mimo-v2.5',
    name: 'MiMo V2.5',
    vendor: '小米',
    url: 'https://api.xiaomimimo.com/v1',
    free: false,
    description: '小米MiMo V2.5,1M上下文,128K输出',
    apiKeyHint: 'platform.xiaomimimo.com → API Key',
  },
]


/** 自定义模型配置(用户手动输入) */
export interface CustomModelConfig {
  id: string
  name: string
  vendor: string
  url: string
  apiKey: string
}

/** 连接测试结果 */
export interface ConnectionTestResult {
  success: boolean
  latencyMs: number
  error?: string
}

/**
 * 测试API连通性(5秒超时,不会卡死)
 */
export async function testConnection(
  url: string,
  apiKey: string,
  model: string,
): Promise<ConnectionTestResult> {
  const start = Date.now()
  try {
    const controller = new AbortController()
    const timer = setTimeout(() => controller.abort(), 5_000)

    // 尝试调用 models 端点或发送最小请求
    const testUrl = url.replace(/\/+$/, '') + '/chat/completions'
    const response = await fetch(testUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model,
        messages: [{ role: 'user', content: 'hi' }],
        max_tokens: 1,
        stream: false,
      }),
      signal: controller.signal,
    })

    clearTimeout(timer)
    const latencyMs = Date.now() - start

    // 200/401/403/429 都说明网络通了,只是认证/限流问题
    if (response.ok || response.status === 429 || response.status === 401 || response.status === 403) {
      // 尝试读取错误信息
      if (!response.ok) {
        let errMsg = `HTTP ${response.status}`
        try {
          const body = await response.json()
          if (body.error?.message) errMsg = body.error.message
        } catch { /* P5: 响应体解析失败 */ }
        if (response.status === 401) {
          return { success: false, latencyMs, error: `API Key 无效: ${errMsg}` }
        }
        if (response.status === 429) {
          return { success: true, latencyMs } // 429说明Key有效,只是限流
        }
        return { success: false, latencyMs, error: errMsg }
      }

      return { success: true, latencyMs }
    }

    return { success: false, latencyMs, error: `HTTP ${response.status}` }
  } catch (e: any) {
    const latencyMs = Date.now() - start
    if (e.name === 'AbortError') {
      return { success: false, latencyMs, error: '连接超时(5秒),请检查网络或API地址' }
    }
    return { success: false, latencyMs, error: e.message || '网络连接失败' }
  }
}