- Electron + React + TypeScript 架构 - 三栏布局: Sidebar | ChatPanel | DetailPanel - 9 个内置工具 (文件系统/网络/记忆/命令) - SQLite 持久化 (better-sqlite3) - MUI 暗色/亮色主题系统 - Agent Loop ReAct 状态机引擎 - DeepSeek / Agnes AI / Ollama Provider 适配器 - MCP 协议集成 - 系统托盘 + 全局快捷键 - Tailwind CSS v4 + Tailwind Merge - 修复: Sidebar 缺失 TextField 导入导致黑屏
71 lines
2.4 KiB
TypeScript
71 lines
2.4 KiB
TypeScript
/**
|
||
* Agnes AI Provider Adapter
|
||
*
|
||
* OpenAI 兼容 API,免费使用。
|
||
* 支持 Tool Calling、Thinking 模式、512K 上下文、多模态(图片 base64)。
|
||
*
|
||
* 差异于 DeepSeek:
|
||
* - Thinking 模式使用 chat_template_kwargs(非 thinking 字段)
|
||
* - 图片使用 base64 格式(与 Ollama 一致,通过 images 字段传递)
|
||
* - 512K 上下文,65.5K 最大输出
|
||
*
|
||
* @see apis/agnes-ai-api-docs-20260625.html
|
||
*/
|
||
|
||
import { DeepSeekAdapter } from './deepseek.adapter';
|
||
import type { MetonaRequest } from '../types';
|
||
|
||
export class AgnesAdapter extends DeepSeekAdapter {
|
||
override readonly provider: string = 'agnes';
|
||
readonly supportedModels = ['agnes-2.0-flash'];
|
||
readonly supportsToolCalling = true;
|
||
readonly supportsThinking = true;
|
||
|
||
/**
|
||
* 覆盖原生请求构建
|
||
*
|
||
* 差异:
|
||
* 1. Thinking 模式使用 chat_template_kwargs
|
||
* 2. 图片使用 OpenAI 多模态 content 数组格式
|
||
*/
|
||
protected override toNativeRequest(request: MetonaRequest): Record<string, unknown> {
|
||
const base = super.toNativeRequest(request) as Record<string, unknown>;
|
||
|
||
// Agnes Thinking 模式:使用 chat_template_kwargs
|
||
if (request.params?.thinkingEnabled) {
|
||
delete base.thinking;
|
||
delete base.reasoning_effort;
|
||
base.chat_template_kwargs = { enable_thinking: true };
|
||
}
|
||
|
||
// Agnes 默认 max_tokens 更大
|
||
if (!request.params.maxTokens) {
|
||
base.max_tokens = 65536;
|
||
}
|
||
|
||
// 图片处理:将 images 转为 OpenAI 多模态 content 数组格式
|
||
const messages = base.messages as Array<Record<string, unknown>>;
|
||
for (let i = 0; i < messages.length; i++) {
|
||
const msg = messages[i];
|
||
// 找到对应的原始消息(跳过 system 消息)
|
||
const origMsg = request.messages.filter((m) => m.role !== 'system')[i];
|
||
if (!origMsg?.images || origMsg.images.length === 0) continue;
|
||
|
||
// 将 content 转为数组格式:[{type: "text", text: ...}, {type: "image_url", ...}]
|
||
const contentParts: Array<Record<string, unknown>> = [];
|
||
if (msg.content && typeof msg.content === 'string') {
|
||
contentParts.push({ type: 'text', text: msg.content });
|
||
}
|
||
for (const img of origMsg.images) {
|
||
contentParts.push({
|
||
type: 'image_url',
|
||
image_url: { url: img.url, detail: img.detail ?? 'auto' },
|
||
});
|
||
}
|
||
msg.content = contentParts;
|
||
}
|
||
|
||
return base;
|
||
}
|
||
}
|