/** * 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 特有参数: * - 多模态图片:user 消息的 images[] → OpenAI content 数组 [{type:"text"}, {type:"image_url"}] * - 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); // === Agnes 多模态:将 images 转为 OpenAI content 数组 === // buildOpenAICompatibleMessages 不处理图片(各 Provider 自行处理) const nonSystemMsgs = request.messages.filter((m) => m.role !== 'system'); for (let i = 0; i < messages.length; i++) { // messages[0] 是 system,非 system 消息从 messages[1] 开始 if (i === 0) continue; const origMsg = nonSystemMsgs[i - 1]; if (!origMsg?.images?.length) continue; const contentParts: Array> = []; if (origMsg.content) { contentParts.push({ type: 'text', text: origMsg.content }); } for (const img of origMsg.images) { contentParts.push({ type: 'image_url', image_url: { url: img.url, detail: img.detail ?? 'auto' }, }); } messages[i].content = contentParts; } 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; } }