v0.1.2: 全项目代码审查修复 + 子代理编排器 + 上下文压缩 + 并行工具执行

后端修复: Ollama adapter 移除死代码/修复超时硬编码/iteration硬编码/pullModel无超时/流异常断开补发DONE; SSE解析器支持非字符串arguments; Sandbox fail-closed安全加固; IPC移除未使用变量

新增功能: TaskOrchestrator子任务编排器; DelegateTaskTool委派工具; Agent Loop并行工具执行; 上下文自动压缩; 记忆检索注入System Prompt

前端修复: useAgentStream text_delta thought累积bug; 工具调用状态正确流转; 修复重复stateChange事件; TraceStep.thought正确填充
This commit is contained in:
thzxx
2026-07-06 22:48:33 +08:00
parent 8cdb93f8bf
commit 6f08759f63
25 changed files with 1044 additions and 675 deletions
@@ -172,7 +172,7 @@ export class DeepSeekAdapter extends BaseAdapter {
} else if (request.params.thinkingEnabled) {
body.thinking = { type: 'enabled' };
const effortMap: Record<string, string> = {
low: 'high', medium: 'high', high: 'high', xhigh: 'max', max: 'max',
low: 'high', medium: 'high', high: 'high', max: 'max',
};
// DeepSeek API 仅支持 high / max 两档,low/medium 映射为 high
body.reasoning_effort = effortMap[request.params.thinkingEffort ?? 'high'] ?? 'high';
+22 -10
View File
@@ -56,7 +56,7 @@ export class OllamaAdapter extends BaseAdapter {
}
const data = await response.json() as Record<string, unknown>;
return this.toMetonaResponse(data, request.meta.requestId);
return this.toMetonaResponse(data, request.meta.requestId, request.meta.iteration);
}
async *chatStream(request: MetonaRequest): AsyncIterable<MetonaStreamEvent> {
@@ -66,7 +66,7 @@ export class OllamaAdapter extends BaseAdapter {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...nativeRequest, stream: true }),
signal: AbortSignal.timeout(300_000),
signal: AbortSignal.timeout(this.config.timeoutMs ?? 300_000),
});
if (!response.ok || !response.body) {
@@ -77,9 +77,7 @@ export class OllamaAdapter extends BaseAdapter {
const decoder = new TextDecoder();
let seq = 0;
let buffer = '';
// 工具调用缓冲区
const toolCallsBuffer = new Map<number, { name: string; argsBuffer: string }>();
let streamEndedNormally = false;
while (true) {
const { done, value } = await reader.read();
@@ -147,6 +145,7 @@ export class OllamaAdapter extends BaseAdapter {
// 流结束
if (chunk.done) {
streamEndedNormally = true;
// 发送 usage 信息
yield {
type: MetonaStreamEventType.USAGE,
@@ -177,6 +176,18 @@ export class OllamaAdapter extends BaseAdapter {
}
}
}
// 流未正常结束(连接断开等),补发 DONE 事件防止 Agent Loop 挂起
if (!streamEndedNormally) {
yield {
type: MetonaStreamEventType.DONE,
requestId: request.meta.requestId,
sessionId: request.meta.sessionId,
iteration: request.meta.iteration,
seq: seq++,
timestamp: Date.now(),
};
}
}
// ===== POST /api/generate =====
@@ -281,6 +292,7 @@ export class OllamaAdapter extends BaseAdapter {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, stream: true }),
signal: AbortSignal.timeout(600_000), // 模型下载可能较慢,10 分钟超时
});
if (!response.ok || !response.body) throw new Error(`Ollama pull error: ${response.status}`);
@@ -394,9 +406,9 @@ export class OllamaAdapter extends BaseAdapter {
options: {
temperature: request.params.temperature,
num_predict: request.params.maxTokens,
top_p: request.params.topP,
stop: request.params.stopSequences,
num_ctx: request.params.contextLength,
...(request.params.topP != null && { top_p: request.params.topP }),
...(request.params.stopSequences?.length && { stop: request.params.stopSequences }),
...(request.params.contextLength != null && { num_ctx: request.params.contextLength }),
},
};
@@ -421,7 +433,7 @@ export class OllamaAdapter extends BaseAdapter {
return body;
}
private toMetonaResponse(data: Record<string, unknown>, requestId: string): MetonaResponse {
private toMetonaResponse(data: Record<string, unknown>, requestId: string, iteration: number = 0): MetonaResponse {
const message = data.message as Record<string, unknown> | undefined;
const toolCalls = message?.tool_calls as Array<Record<string, unknown>> | undefined;
return {
@@ -455,7 +467,7 @@ export class OllamaAdapter extends BaseAdapter {
id: `tc_${Date.now()}_${i}`,
name: (fn?.name as string) ?? '',
args,
iteration: 0,
iteration,
timestamp: Date.now(),
};
}),
@@ -223,10 +223,15 @@ export function parseOpenAICompatibleResponse(
toolCalls: rawToolCalls?.map((tc) => {
const fn = tc.function as Record<string, unknown>;
let args: Record<string, unknown> = {};
try {
args = JSON.parse(fn.arguments as string);
} catch {
args = {};
const rawArgs = fn?.arguments;
if (typeof rawArgs === 'string') {
try {
args = JSON.parse(rawArgs);
} catch {
args = {};
}
} else if (rawArgs && typeof rawArgs === 'object') {
args = rawArgs as Record<string, unknown>;
}
return {
id: tc.id as string,