Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
11 KiB
JSON
1 line
11 KiB
JSON
{"repo": "abyssbugg/ant-multi-provider-auth", "files": {"src/providers/deepseek/adapter.ts": {"c9db2241b289738f10a9dc3be8de45c506290590": {"t": 1786067565.9395738, "n": 10402, "h": "1379fd35679458c9", "c": "import { authManager } from '../../auth';\nimport { authenticateWithBrowser } from '../../auth/browser/session-auth';\nimport { parseOpenAISSELine } from '../../transcoder/streaming';\nimport type {\n Account,\n AuthResult,\n ContentBlock,\n InternalRequest,\n InternalResponse,\n ModelDefinition,\n StreamChunk,\n UsageInfo,\n} from '../../types';\nimport { QuotaExceededError, RateLimitError } from '../../utils/errors';\nimport { logger } from '../../utils/logger';\nimport { BaseProvider } from '../base';\nimport { DEEPSEEK_MODELS } from './models';\n\nconst DEEPSEEK_API_BASE = 'https://api.deepseek.com/v1';\n\nexport class DeepSeekProvider extends BaseProvider {\n readonly id = 'deepseek' as const;\n readonly name = 'DeepSeek';\n\n async authenticate(): Promise<AuthResult> {\n console.log('\\n🔐 DeepSeek Authentication');\n console.log('='.repeat(50));\n console.log('\\nChoose authentication method:');\n console.log(' 1. Browser login (use your existing DeepSeek account/subscription)');\n console.log(' 2. API key (for API-only access)\\n');\n\n const choice = await this.promptForChoice();\n\n if (choice === '1' || choice === '') {\n // Browser-based authentication (primary)\n return this.authenticateWithBrowser();\n }\n // API key authentication (fallback)\n return this.authenticateWithApiKey();\n }\n\n private async authenticateWithBrowser(): Promise<AuthResult> {\n console.log('\\n🌐 Opening browser for DeepSeek login...');\n console.log('Please log in with your DeepSeek account.\\n');\n\n const result = await authenticateWithBrowser('deepseek');\n\n if (result.type === 'success' && result.account) {\n await authManager.saveAccount(result.account);\n }\n\n return result;\n }\n\n private async authenticateWithApiKey(): Promise<AuthResult> {\n console.log('\\nGet your API key from: https://platform.deepseek.com/api_keys\\n');\n\n const apiKey = await this.promptForApiKey();\n if (!apiKey) {\n return { type: 'failed', error: 'No API key provided' };\n }\n\n const isValid = await this.validateApiKey(apiKey);\n if (!isValid) {\n return { type: 'failed', error: 'Invalid API key' };\n }\n\n const accountId = `deepseek-${crypto.randomUUID().slice(0, 8)}`;\n const account: Account = {\n id: accountId,\n provider: 'deepseek',\n authType: 'api-key',\n accessToken: apiKey,\n expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,\n healthScore: 1.0,\n consecutiveErrors: 0,\n lastUsed: Date.now(),\n quotaInfo: {\n used: 0,\n limit: 0,\n resetsAt: 0,\n perModel: {},\n },\n createdAt: Date.now(),\n updatedAt: Date.now(),\n };\n\n await authManager.saveAccount(account);\n return { type: 'success', account };\n }\n\n private async promptForChoice(): Promise<string> {\n const rl = await import('node:readline');\n const readline = rl.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n readline.question('Enter choice (1 or 2) [1]: ', (answer) => {\n readline.close();\n resolve(answer.trim());\n });\n });\n }\n\n private async promptForApiKey(): Promise<string | null> {\n const rl = await import('node:readline');\n const readline = rl.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n readline.question('Enter your DeepSeek API key: ', (answer) => {\n readline.close();\n resolve(answer.trim() || null);\n });\n });\n }\n\n private async validateApiKey(apiKey: string): Promise<boolean> {\n try {\n const response = await fetch(`${DEEPSEEK_API_BASE}/models`, {\n headers: { Authorization: `Bearer ${apiKey}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n listModels(): ModelDefinition[] {\n return DEEPSEEK_MODELS;\n }\n\n protected async doSend(request: InternalRequest, account: Account): Promise<InternalResponse> {\n const response = await fetch(`${DEEPSEEK_API_BASE}/chat/completions`, {\n method: 'POST',\n headers: this.createRequestHeaders(account.accessToken || ''),\n body: JSON.stringify(this.transformRequest(request, false)),\n });\n\n await this.handleResponseErrors(response, account.id);\n\n const data = (await response.json()) as Record<string, unknown>;\n return this.transformResponse(data, request);\n }\n\n protected async *doStream(\n request: InternalRequest,\n account: Account\n ): AsyncGenerator<StreamChunk> {\n const response = await fetch(`${DEEPSEEK_API_BASE}/chat/completions`, {\n method: 'POST',\n headers: this.createRequestHeaders(account.accessToken || ''),\n body: JSON.stringify(this.transformRequest(request, true)),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.error('DeepSeek streaming error', { status: response.status, error: errorText });\n yield { type: 'error', error: `DeepSeek API error: ${response.status}` };\n return;\n }\n\n const body = response.body;\n if (!body) {\n yield { type: 'error', error: 'No response body' };\n return;\n }\n\n yield* this.readSSEStream(body);\n }\n\n private async *readSSEStream(body: ReadableStream<Uint8Array>): AsyncGenerator<StreamChunk> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n const blockIndex = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n const chunk = parseOpenAISSELine(line, blockIndex);\n if (chunk) {\n yield chunk;\n if (chunk.type === 'message_stop') return;\n }\n }\n }\n\n yield { type: 'message_stop', stopReason: 'end_turn' };\n } finally {\n reader.releaseLock();\n }\n }\n\n private createRequestHeaders(token: string): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n };\n }\n\n private transformRequest(request: InternalRequest, stream: boolean): Record<string, unknown> {\n const messages = request.messages.map((m) => ({\n role: m.role,\n content: this.contentBlocksToString(m.content),\n }));\n\n if (request.system) {\n messages.unshift({ role: 'system', content: request.system });\n }\n\n const body: Record<string, unknown> = {\n model: request.model,\n messages,\n stream,\n };\n\n if (request.config.maxTokens) body.max_tokens = request.config.maxTokens;\n if (request.config.temperature !== undefined) body.temperature = request.config.temperature;\n if (request.config.topP !== undefined) body.top_p = request.config.topP;\n if (request.config.stopSequences) body.stop = request.config.stopSequences;\n\n if (request.tools && request.tools.length > 0) {\n body.tools = request.tools.map((t) => ({\n type: 'function',\n function: {\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n },\n }));\n }\n\n return body;\n }\n\n private contentBlocksToString(blocks: ContentBlock[]): string {\n return blocks\n .filter((b): b is { type: 'text'; text: string } => b.type === 'text')\n .map((b) => b.text)\n .join('');\n }\n\n private async handleResponseErrors(response: Response, accountId: string): Promise<void> {\n if (response.ok) return;\n\n const errorText = await response.text();\n\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n const retryMs = retryAfter ? Number.parseInt(retryAfter) * 1000 : 60000;\n throw new RateLimitError('deepseek', retryMs);\n }\n\n if (response.status === 402 || errorText.includes('quota') || errorText.includes('balance')) {\n throw new QuotaExceededError('deepseek', accountId);\n }\n\n throw new Error(`DeepSeek API error: ${response.status} - ${errorText}`);\n }\n\n private transformResponse(\n data: Record<string, unknown>,\n request: InternalRequest\n ): InternalResponse {\n const choices = data.choices as\n | Array<{\n message?: { content?: string; tool_calls?: unknown[]; reasoning_content?: string };\n finish_reason?: string;\n }>\n | undefined;\n\n const rawUsage = data.usage as\n | { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number }\n | undefined;\n\n const message = choices?.[0]?.message;\n const content: ContentBlock[] = [];\n\n if (message?.reasoning_content) {\n content.push({ type: 'thinking', thinking: message.reasoning_content });\n }\n\n if (message?.content) {\n content.push({ type: 'text', text: message.content });\n }\n\n if (message?.tool_calls && Array.isArray(message.tool_calls)) {\n for (const tc of message.tool_calls) {\n const toolCall = tc as { id: string; function: { name: string; arguments: string } };\n content.push({\n type: 'tool_use',\n id: toolCall.id,\n name: toolCall.function.name,\n input: JSON.parse(toolCall.function.arguments || '{}'),\n });\n }\n }\n\n if (content.length === 0) {\n content.push({ type: 'text', text: '' });\n }\n\n const usage: UsageInfo = {\n inputTokens: rawUsage?.prompt_tokens || 0,\n outputTokens: rawUsage?.completion_tokens || 0,\n totalTokens: rawUsage?.total_tokens || 0,\n };\n\n const stopReason = this.mapStopReason(choices?.[0]?.finish_reason);\n\n return {\n id: (data.id as string) || crypto.randomUUID(),\n provider: 'deepseek',\n model: (data.model as string) || request.model,\n type: 'message',\n content,\n stopReason,\n usage,\n metadata: {\n provider: 'deepseek',\n model: request.model,\n latencyMs: 0,\n },\n };\n }\n\n private mapStopReason(reason?: string): InternalResponse['stopReason'] {\n switch (reason) {\n case 'stop':\n return 'end_turn';\n case 'length':\n return 'max_tokens';\n case 'tool_calls':\n return 'tool_use';\n case 'content_filter':\n return 'error';\n default:\n return 'end_turn';\n }\n }\n}\n"}}}} |