/** * Agnes AI Provider Adapter * * OpenAI 兼容 API。支持 Tool Calling、Thinking 模式、多模态(图片)。 * * 独立继承 BaseAdapter,通过 shared/openai-format 和 shared/sse-stream 复用 * OpenAI 兼容格式构建和 SSE 流式解析逻辑。不与其他 Provider Adapter 耦合。 * * 与 DeepSeek 的差异: * - Thinking 模式使用 chat_template_kwargs(非 thinking 字段) * - 默认 max_tokens 更大(65536 vs 8192) * * @see apis/agnes-ai-api-docs-20260625.html */ import { BaseAdapter } from './base-adapter'; import type { MetonaRequest, MetonaResponse, MetonaStreamEvent } from '../types'; import { MetonaFinishReason } from '../types'; import { buildOpenAICompatibleMessages, buildOpenAICompatibleTools } from './shared/openai-format'; import { parseSSEStream, parseOpenAICompatibleResponse } from './shared/sse-stream'; export class AgnesAdapter extends BaseAdapter { override readonly provider: string = 'agnes'; readonly supportedModels = ['agnes-2.0-flash']; readonly supportsToolCalling = true; readonly supportsThinking = true; // ===== POST /chat/completions (非流式) ===== async chat(request: MetonaRequest): Promise { const body = this.toNativeRequest(request, false); const response = await fetch(`${this.config.baseURL}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.config.apiKey}`, ...this.config.headers, }, body: JSON.stringify(body), signal: AbortSignal.timeout(this.config.timeoutMs ?? 300_000), }); if (!response.ok) { const errorBody = await response.text().catch(() => ''); throw new Error(`Agnes AI API error: ${response.status} ${response.statusText} - ${errorBody}`); } const data = await response.json() as Record; const parsed = parseOpenAICompatibleResponse(data, request.meta.requestId, this.provider, this.config.defaultModel); return { meta: { requestId: request.meta.requestId, provider: this.provider, model: (data.model as string) ?? this.config.defaultModel, latencyMs: 0, timestamp: Date.now(), }, content: parsed.content, reasoningContent: parsed.reasoningContent, toolCalls: parsed.toolCalls, usage: parsed.usage, finishReason: parsed.finishReason as MetonaFinishReason, }; } // ===== POST /chat/completions (流式) ===== async *chatStream(request: MetonaRequest): AsyncIterable { const body = this.toNativeRequest(request, true); const response = await fetch(`${this.config.baseURL}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.config.apiKey}`, ...this.config.headers, }, body: JSON.stringify(body), }); if (!response.ok || !response.body) { throw new Error(`Agnes AI stream error: ${response.status}`); } yield* parseSSEStream( response.body, request.meta.requestId, request.meta.sessionId, request.meta.iteration, ); } // ========== 私有方法 ========== /** * 构建 Agnes AI 原生请求体 * * Agnes AI 特有参数: * - chat_template_kwargs: { enable_thinking: true } — 启用思考模式(非 thinking 字段) * - 默认 max_tokens: 65536(512K 上下文) */ private toNativeRequest(request: MetonaRequest, stream: boolean): Record { const messages = buildOpenAICompatibleMessages(request); const tools = buildOpenAICompatibleTools(request.tools); const body: Record = { model: this.config.defaultModel, messages, temperature: request.params.temperature ?? 0, max_tokens: request.params.maxTokens ?? 65536, stream, }; if (stream) { body.stream_options = { include_usage: true }; } if (tools) { body.tools = tools; } // Thinking 模式:Agnes 使用 chat_template_kwargs 而非 thinking if (request.params.thinkingEnabled) { body.chat_template_kwargs = { enable_thinking: true }; } // 停止序列 if (request.params.stopSequences?.length) { body.stop = request.params.stopSequences; } return body; } }