support google gemini
This commit is contained in:
parent
d1a4b506ce
commit
1b85207b7f
@ -1,5 +1,9 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [main] 0.1.4
|
||||||
|
- 支持 Google Gemini 模型。
|
||||||
|
- 支持 Grok3 的 tool call 流式传输。
|
||||||
|
|
||||||
## [main] 0.1.3
|
## [main] 0.1.3
|
||||||
- 解决 issue#21 点击按钮后的发送文本后不会清空当前的输入框。
|
- 解决 issue#21 点击按钮后的发送文本后不会清空当前的输入框。
|
||||||
- 修复暂停按键在多轮对话后消失的问题。
|
- 修复暂停按键在多轮对话后消失的问题。
|
||||||
|
7
package-lock.json
generated
7
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "openmcp",
|
"name": "openmcp",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "openmcp",
|
"name": "openmcp",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"service",
|
"service",
|
||||||
"renderer",
|
"renderer",
|
||||||
@ -15,7 +15,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.9.0",
|
||||||
"bson": "^6.8.0",
|
"bson": "^6.8.0",
|
||||||
"openai": "^5.0.1",
|
"openai": "^5.0.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
@ -11830,6 +11830,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"openai": "^5.0.1",
|
"openai": "^5.0.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
|
@ -235,7 +235,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.9.0",
|
||||||
"bson": "^6.8.0",
|
"bson": "^6.8.0",
|
||||||
"openai": "^5.0.1",
|
"openai": "^5.0.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"openai": "^5.0.1",
|
"openai": "^5.0.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
|
200
service/src/hook/axios-fetch.ts
Normal file
200
service/src/hook/axios-fetch.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
interface FetchOptions {
|
||||||
|
method?: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
body?: string | Buffer | FormData | URLSearchParams | object;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FetchResponse {
|
||||||
|
ok: boolean;
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
headers: Headers;
|
||||||
|
url: string;
|
||||||
|
redirected: boolean;
|
||||||
|
type: string;
|
||||||
|
body: any;
|
||||||
|
|
||||||
|
json(): Promise<any>;
|
||||||
|
text(): Promise<string>;
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer>;
|
||||||
|
getReader(): ReadableStreamDefaultReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReadableStreamDefaultReader {
|
||||||
|
read(): Promise<{done: boolean, value?: any}>;
|
||||||
|
cancel(): Promise<void>;
|
||||||
|
releaseLock(): void;
|
||||||
|
get closed(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 axios 配置转换为 fetch 风格的配置
|
||||||
|
*/
|
||||||
|
function adaptRequestOptions(url: string, options: FetchOptions = {}): any {
|
||||||
|
const axiosConfig: any = {
|
||||||
|
url,
|
||||||
|
method: options.method || 'GET',
|
||||||
|
headers: options.headers,
|
||||||
|
responseType: 'stream'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理 body/data 转换
|
||||||
|
if (options.body) {
|
||||||
|
if (typeof options.body === 'string' || Buffer.isBuffer(options.body)) {
|
||||||
|
axiosConfig.data = options.body;
|
||||||
|
} else if (typeof options.body === 'object') {
|
||||||
|
// 如果是 FormData、URLSearchParams 等特殊类型需要特殊处理
|
||||||
|
if (options.body instanceof FormData) {
|
||||||
|
axiosConfig.data = options.body;
|
||||||
|
axiosConfig.headers = {
|
||||||
|
...axiosConfig.headers,
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
};
|
||||||
|
} else if (options.body instanceof URLSearchParams) {
|
||||||
|
axiosConfig.data = options.body.toString();
|
||||||
|
axiosConfig.headers = {
|
||||||
|
...axiosConfig.headers,
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 普通 JSON 对象
|
||||||
|
axiosConfig.data = JSON.stringify(options.body);
|
||||||
|
axiosConfig.headers = {
|
||||||
|
...axiosConfig.headers,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return axiosConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 axios 响应转换为 fetch 风格的 Response 对象
|
||||||
|
*/
|
||||||
|
function adaptResponse(axiosResponse: FetchOptions): FetchResponse {
|
||||||
|
// 创建 Headers 对象
|
||||||
|
const headers = new Headers();
|
||||||
|
Object.entries(axiosResponse.headers || {}).forEach(([key, value]) => {
|
||||||
|
headers.append(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建符合 Fetch API 的 Response 对象
|
||||||
|
const fetchResponse = {
|
||||||
|
ok: axiosResponse.status >= 200 && axiosResponse.status < 300,
|
||||||
|
status: axiosResponse.status,
|
||||||
|
statusText: axiosResponse.statusText,
|
||||||
|
headers: headers,
|
||||||
|
url: axiosResponse.config.url,
|
||||||
|
redirected: false, // axios 不直接提供此信息
|
||||||
|
type: 'basic', // 简单类型
|
||||||
|
body: null,
|
||||||
|
|
||||||
|
// 标准方法
|
||||||
|
json: async () => {
|
||||||
|
if (typeof axiosResponse.data === 'object') {
|
||||||
|
return axiosResponse.data;
|
||||||
|
}
|
||||||
|
throw new Error('Response is not JSON');
|
||||||
|
},
|
||||||
|
text: async () => {
|
||||||
|
if (typeof axiosResponse.data === 'string') {
|
||||||
|
return axiosResponse.data;
|
||||||
|
}
|
||||||
|
return JSON.stringify(axiosResponse.data);
|
||||||
|
},
|
||||||
|
arrayBuffer: async () => {
|
||||||
|
throw new Error('arrayBuffer not implemented for streaming');
|
||||||
|
},
|
||||||
|
|
||||||
|
// 流式支持
|
||||||
|
getReader: () => {
|
||||||
|
if (!axiosResponse.data.on || typeof axiosResponse.data.on !== 'function') {
|
||||||
|
throw new Error('Not a stream response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 Node.js 流转换为 Web Streams 的 ReadableStream
|
||||||
|
const nodeStream = axiosResponse.data;
|
||||||
|
let isCancelled = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
read: () => {
|
||||||
|
if (isCancelled) {
|
||||||
|
return Promise.resolve({ done: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const onData = (chunk: any) => {
|
||||||
|
cleanup();
|
||||||
|
resolve({ done: false, value: chunk });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEnd = () => {
|
||||||
|
cleanup();
|
||||||
|
resolve({ done: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = (err: Error) => {
|
||||||
|
cleanup();
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
nodeStream.off('data', onData);
|
||||||
|
nodeStream.off('end', onEnd);
|
||||||
|
nodeStream.off('error', onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeStream.once('data', onData);
|
||||||
|
nodeStream.once('end', onEnd);
|
||||||
|
nodeStream.once('error', onError);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel: () => {
|
||||||
|
isCancelled = true;
|
||||||
|
nodeStream.destroy();
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
releaseLock: () => {
|
||||||
|
// TODO: 实现 releaseLock 方法
|
||||||
|
},
|
||||||
|
|
||||||
|
get closed() {
|
||||||
|
return isCancelled;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} as FetchResponse;
|
||||||
|
|
||||||
|
// 设置 body 为可读流
|
||||||
|
if (axiosResponse.data.on && typeof axiosResponse.data.on === 'function') {
|
||||||
|
fetchResponse.body = {
|
||||||
|
getReader: fetchResponse.getReader
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 主函数 - 用 axios 实现 fetch
|
||||||
|
*/
|
||||||
|
export async function axiosFetch(url: any, options: any): Promise<any> {
|
||||||
|
const axiosConfig = adaptRequestOptions(url, options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios(axiosConfig) as FetchOptions;
|
||||||
|
return adaptResponse(response);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.response) {
|
||||||
|
return adaptResponse(error.response);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
@ -108,3 +108,5 @@ export const llms = [
|
|||||||
userModel: 'moonshot-v1-8k'
|
userModel: 'moonshot-v1-8k'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,3 +7,20 @@ export type MyMessageType = OpenAI.Chat.ChatCompletionMessageParam & {
|
|||||||
export type MyToolMessageType = OpenAI.Chat.ChatCompletionToolMessageParam & {
|
export type MyToolMessageType = OpenAI.Chat.ChatCompletionToolMessageParam & {
|
||||||
extraInfo?: any;
|
extraInfo?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OpenMcpChatOption {
|
||||||
|
baseURL: string;
|
||||||
|
apiKey: string;
|
||||||
|
model: string;
|
||||||
|
messages: any[];
|
||||||
|
temperature?: number;
|
||||||
|
tools?: any[];
|
||||||
|
parallelToolCalls?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MyStream<T> extends AsyncIterable<T> {
|
||||||
|
[Symbol.asyncIterator](): AsyncIterator<T>;
|
||||||
|
controller: {
|
||||||
|
abort(): void;
|
||||||
|
};
|
||||||
|
}
|
@ -5,6 +5,7 @@ import { RestfulResponse } from "../common/index.dto";
|
|||||||
import { ocrDB } from "../hook/db";
|
import { ocrDB } from "../hook/db";
|
||||||
import type { ToolCallContent } from "../mcp/client.dto";
|
import type { ToolCallContent } from "../mcp/client.dto";
|
||||||
import { ocrWorkerStorage } from "../mcp/ocr.service";
|
import { ocrWorkerStorage } from "../mcp/ocr.service";
|
||||||
|
import { axiosFetch } from "../hook/axios-fetch";
|
||||||
|
|
||||||
export let currentStream: AsyncIterable<any> | null = null;
|
export let currentStream: AsyncIterable<any> | null = null;
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ export async function streamingChatCompletion(
|
|||||||
data: any,
|
data: any,
|
||||||
webview: PostMessageble
|
webview: PostMessageble
|
||||||
) {
|
) {
|
||||||
let {
|
const {
|
||||||
baseURL,
|
baseURL,
|
||||||
apiKey,
|
apiKey,
|
||||||
model,
|
model,
|
||||||
@ -24,12 +25,33 @@ export async function streamingChatCompletion(
|
|||||||
|
|
||||||
const client = new OpenAI({
|
const client = new OpenAI({
|
||||||
baseURL,
|
baseURL,
|
||||||
apiKey
|
apiKey,
|
||||||
|
fetch: async (input: string | URL | Request, init?: RequestInit) => {
|
||||||
|
|
||||||
|
console.log('openai fetch begin');
|
||||||
|
|
||||||
|
if (model.startsWith('gemini')) {
|
||||||
|
// 该死的 google
|
||||||
|
if (init) {
|
||||||
|
init.headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${apiKey}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('input:', input);
|
||||||
|
console.log('init:', init);
|
||||||
|
|
||||||
|
return await axiosFetch(input, init);
|
||||||
|
} else {
|
||||||
|
return await fetch(input, init);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tools.length === 0) {
|
const seriableTools = (tools.length === 0) ? undefined: tools;
|
||||||
tools = undefined;
|
const seriableParallelToolCalls = (tools.length === 0)?
|
||||||
}
|
undefined: model.startsWith('gemini') ? undefined : parallelToolCalls;
|
||||||
|
|
||||||
await postProcessMessages(messages);
|
await postProcessMessages(messages);
|
||||||
|
|
||||||
@ -37,8 +59,8 @@ export async function streamingChatCompletion(
|
|||||||
model,
|
model,
|
||||||
messages,
|
messages,
|
||||||
temperature,
|
temperature,
|
||||||
tools,
|
tools: seriableTools,
|
||||||
parallel_tool_calls: parallelToolCalls,
|
parallel_tool_calls: seriableParallelToolCalls,
|
||||||
stream: true
|
stream: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { VSCodeWebViewLike } from './hook/adapter';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import { setRunningCWD } from './hook/setting';
|
import { setRunningCWD } from './hook/setting';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
export interface VSCodeMessage {
|
export interface VSCodeMessage {
|
||||||
command: string;
|
command: string;
|
||||||
|
@ -79,7 +79,6 @@ export class PanelController {
|
|||||||
|
|
||||||
@Controller('system-prompts/load')
|
@Controller('system-prompts/load')
|
||||||
async loadSystemPrompts(data: RequestData, webview: PostMessageble) {
|
async loadSystemPrompts(data: RequestData, webview: PostMessageble) {
|
||||||
const client = getClient(data.clientId);
|
|
||||||
const queryPrompts = await systemPromptDB.findAll();
|
const queryPrompts = await systemPromptDB.findAll();
|
||||||
const prompts = [];
|
const prompts = [];
|
||||||
for (const prompt of queryPrompts) {
|
for (const prompt of queryPrompts) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user