myagent-ai 1.6.4 → 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/agents/base.py CHANGED
@@ -145,6 +145,7 @@ class BaseAgent(ABC):
145
145
  request_kwargs.update(kwargs)
146
146
 
147
147
  full_text = ""
148
+ full_reasoning = "" # Track reasoning tokens (for o1/o3/DeepSeek-R1 etc.)
148
149
  tool_calls_acc: Dict[int, Dict] = {} # index -> {id, name, arguments_str}
149
150
  finish_reason = ""
150
151
 
@@ -166,6 +167,12 @@ class BaseAgent(ABC):
166
167
  else:
167
168
  await _write_sse({"type": "text_delta", "content": delta_text})
168
169
 
170
+ async def _emit_reasoning_delta(delta_text: str):
171
+ """处理推理 token:发送 reasoning_delta SSE 事件"""
172
+ nonlocal full_reasoning
173
+ full_reasoning += delta_text
174
+ await _write_sse({"type": "reasoning_delta", "content": delta_text})
175
+
169
176
  try:
170
177
  if self.llm.provider in self.llm._OPENAI_COMPATIBLE_PROVIDERS or self.llm.provider == "zhipu":
171
178
  # 使用异步客户端流式
@@ -190,6 +197,11 @@ class BaseAgent(ABC):
190
197
  if delta.content:
191
198
  await _emit_text_delta(delta.content)
192
199
 
200
+ # Handle reasoning_content (OpenAI o1/o3, DeepSeek-R1, Qwen-QwQ etc.)
201
+ reasoning_content = getattr(delta, 'reasoning_content', None) or getattr(delta, 'reasoning', None)
202
+ if reasoning_content:
203
+ await _emit_reasoning_delta(reasoning_content)
204
+
193
205
  # Handle tool_call deltas (accumulate)
194
206
  if hasattr(delta, 'tool_calls') and delta.tool_calls:
195
207
  for tc_delta in delta.tool_calls:
@@ -252,8 +264,17 @@ class BaseAgent(ABC):
252
264
  if event is None:
253
265
  break
254
266
  if event.type == "content_block_delta":
255
- if hasattr(event.delta, "text"):
267
+ # Handle text content
268
+ if hasattr(event.delta, "text") and event.delta.text:
256
269
  await _emit_text_delta(event.delta.text)
270
+ # Handle extended thinking (Anthropic reasoning)
271
+ if hasattr(event.delta, "thinking") and event.delta.thinking:
272
+ await _emit_reasoning_delta(event.delta.thinking)
273
+ # Handle thinking delta via type
274
+ if hasattr(event.delta, "type") and event.delta.type == "thinking_delta":
275
+ thinking_text = getattr(event.delta, "thinking", "")
276
+ if thinking_text:
277
+ await _emit_reasoning_delta(thinking_text)
257
278
  elif event.type == "message_stop":
258
279
  finish_reason = "stop"
259
280
 
@@ -292,9 +313,14 @@ class BaseAgent(ABC):
292
313
  break
293
314
  try:
294
315
  data = json.loads(line.decode('utf-8') if isinstance(line, bytes) else line)
295
- content = data.get("message", {}).get("content", "")
316
+ message = data.get("message", {})
317
+ content = message.get("content", "")
296
318
  if content:
297
319
  await _emit_text_delta(content)
320
+ # Handle Ollama thinking/reasoning (e.g., DeepSeek-R1 via Ollama)
321
+ thinking = message.get("thinking", "")
322
+ if thinking:
323
+ await _emit_reasoning_delta(thinking)
298
324
  if data.get("done"):
299
325
  finish_reason = "stop"
300
326
  # Record usage from Ollama
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/ui/chat.html CHANGED
@@ -3063,7 +3063,8 @@ async function loadSessions() {
3063
3063
  } catch (e) {
3064
3064
  // Offline - try cache
3065
3065
  state.sessions = state.agentSessions[state.activeAgent] || [];
3066
- renderSessions();
3066
+ renderSessions();
3067
+ }
3067
3068
 
3068
3069
  // Auto-select most recent session if none selected
3069
3070
  if (!state.activeSessionId && state.sessions.length > 0) {
@@ -3350,6 +3351,17 @@ function renderMessages() {
3350
3351
  <div class="thought-content">${renderMarkdown(msg.thought)}</div>
3351
3352
  </details>`;
3352
3353
  })() : '';
3354
+ const reasoningHtml = msg.reasoning ? (() => {
3355
+ const isStreaming = !!msg.streaming;
3356
+ return `<details class="thought-block ${isStreaming ? 'streaming' : ''}" ${isStreaming ? 'open' : ''}>
3357
+ <summary>
3358
+ <span class="thought-icon">💡</span>
3359
+ <span class="thought-label">模型推理过程</span>
3360
+ ${isStreaming ? '<span class="thought-badge">推理中...</span>' : '<span class="thought-badge">已完成</span>'}
3361
+ </summary>
3362
+ <div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
3363
+ </details>`;
3364
+ })() : '';
3353
3365
  const actionBtns = (!isUser && msg.content) ? `
3354
3366
  <div class="msg-actions">
3355
3367
  <button class="msg-action-btn" onclick="copyMessage(${i})" title="复制">
@@ -3375,6 +3387,7 @@ function renderMessages() {
3375
3387
  <div class="message-row ${msg.role}">
3376
3388
  <div class="message-avatar">${avatar}</div>
3377
3389
  <div style="flex:1;min-width:0">
3390
+ ${reasoningHtml}
3378
3391
  ${thoughtHtml}
3379
3392
  ${content || streamingIndicator ? `<div class="message-bubble">${content}${ttsIndicator}</div>` : ''}
3380
3393
  ${streamingIndicator}
@@ -3392,6 +3405,165 @@ function renderMessages() {
3392
3405
  scrollToBottom();
3393
3406
  }
3394
3407
 
3408
+ // ── Incremental Streaming Update (avoid full DOM rebuild on every token) ──
3409
+ function updateStreamingMessage(msgIdx) {
3410
+ // Only update the last streaming message bubble without rebuilding the entire DOM
3411
+ const msg = state.messages[msgIdx];
3412
+ if (!msg || msg.role !== 'assistant') return;
3413
+
3414
+ const container = document.getElementById('messagesInner');
3415
+ if (!container) return;
3416
+
3417
+ // Find or create the streaming message row
3418
+ const rows = container.querySelectorAll('.message-row.assistant');
3419
+ let targetRow = null;
3420
+ // Count assistant rows to find the right one
3421
+ let assistantCount = 0;
3422
+ for (const row of rows) {
3423
+ assistantCount++;
3424
+ if (assistantCount === msgIdx + 1) {
3425
+ targetRow = row;
3426
+ break;
3427
+ }
3428
+ }
3429
+
3430
+ // Fallback: if we can't find by count, use last assistant row
3431
+ if (!targetRow && rows.length > 0) {
3432
+ targetRow = rows[rows.length - 1];
3433
+ }
3434
+
3435
+ if (!targetRow) {
3436
+ // Message row doesn't exist yet, fall back to full render
3437
+ renderMessages();
3438
+ return;
3439
+ }
3440
+
3441
+ const contentArea = targetRow.querySelector(':scope > div');
3442
+ if (!contentArea) return;
3443
+
3444
+ // Update reasoning block (model inference/reasoning - e.g. o1, DeepSeek-R1)
3445
+ let reasoningBlock = contentArea.querySelector('.thought-block .thought-label');
3446
+ // Find reasoning block by looking for "模型推理过程" label
3447
+ let reasoningDetails = null;
3448
+ const allThoughtBlocks = contentArea.querySelectorAll('.thought-block');
3449
+ for (const tb of allThoughtBlocks) {
3450
+ const label = tb.querySelector('.thought-label');
3451
+ if (label && label.textContent.includes('模型推理过程')) {
3452
+ reasoningDetails = tb;
3453
+ break;
3454
+ }
3455
+ }
3456
+ if (msg.reasoning) {
3457
+ const reasoningHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
3458
+ <summary>
3459
+ <span class="thought-icon">💡</span>
3460
+ <span class="thought-label">模型推理过程</span>
3461
+ ${msg.streaming ? '<span class="thought-badge">推理中...</span>' : '<span class="thought-badge">已完成</span>'}
3462
+ </summary>
3463
+ <div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
3464
+ </details>`;
3465
+ if (reasoningDetails) {
3466
+ reasoningDetails.outerHTML = reasoningHtml;
3467
+ } else {
3468
+ contentArea.insertAdjacentHTML('afterbegin', reasoningHtml);
3469
+ }
3470
+ }
3471
+
3472
+ // Update thought block (agent thinking)
3473
+ let thoughtBlock = null;
3474
+ const updatedBlocks = contentArea.querySelectorAll('.thought-block');
3475
+ for (const tb of updatedBlocks) {
3476
+ const label = tb.querySelector('.thought-label');
3477
+ if (label && label.textContent.includes('Agent 思考过程')) {
3478
+ thoughtBlock = tb;
3479
+ break;
3480
+ }
3481
+ }
3482
+ if (msg.thought) {
3483
+ const thoughtHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
3484
+ <summary>
3485
+ <span class="thought-icon">🧠</span>
3486
+ <span class="thought-label">Agent 思考过程</span>
3487
+ ${msg.streaming ? '<span class="thought-badge">思考中...</span>' : '<span class="thought-badge">已完成</span>'}
3488
+ </summary>
3489
+ <div class="thought-content">${renderMarkdown(msg.thought)}</div>
3490
+ </details>`;
3491
+ if (thoughtBlock) {
3492
+ thoughtBlock.outerHTML = thoughtHtml;
3493
+ } else {
3494
+ contentArea.insertAdjacentHTML('afterbegin', thoughtHtml);
3495
+ }
3496
+ } else if (thoughtBlock && !msg.thought) {
3497
+ thoughtBlock.remove();
3498
+ }
3499
+
3500
+ // Update message bubble content
3501
+ const bubble = contentArea.querySelector('.message-bubble');
3502
+ const content = msg.content ? renderMarkdown(msg.content) : '';
3503
+ const streamingIndicator = msg.streaming && !msg.content && !msg.thought ? `
3504
+ <div class="streaming-indicator">
3505
+ <div class="spinner"></div>
3506
+ <div class="streaming-dots">
3507
+ <span class="dot"></span><span class="dot"></span><span class="dot"></span>
3508
+ </div>
3509
+ <span style="font-weight:500">Agent 正在思考...</span>
3510
+ </div>` : '';
3511
+
3512
+ if (bubble) {
3513
+ if (content) {
3514
+ bubble.innerHTML = content;
3515
+ bubble.style.display = '';
3516
+ } else {
3517
+ bubble.style.display = 'none';
3518
+ }
3519
+ } else if (content) {
3520
+ contentArea.insertAdjacentHTML('afterbegin', `<div class="message-bubble">${content}</div>`);
3521
+ }
3522
+
3523
+ // Update streaming indicator
3524
+ let indicator = contentArea.querySelector('.streaming-indicator');
3525
+ if (streamingIndicator) {
3526
+ if (!indicator) {
3527
+ contentArea.insertAdjacentHTML('beforeend', streamingIndicator);
3528
+ }
3529
+ } else if (indicator) {
3530
+ indicator.remove();
3531
+ }
3532
+
3533
+ // Update exec events
3534
+ const existingPanel = contentArea.querySelector('.exec-events-panel');
3535
+ if (msg.exec_events && msg.exec_events.length > 0) {
3536
+ const execHtml = renderExecEvents(msg.exec_events, msgIdx);
3537
+ if (existingPanel) {
3538
+ existingPanel.outerHTML = execHtml;
3539
+ } else {
3540
+ contentArea.insertAdjacentHTML('beforeend', execHtml);
3541
+ }
3542
+ }
3543
+
3544
+ // Auto-scroll
3545
+ scrollToBottom();
3546
+ }
3547
+
3548
+ // Throttle streaming updates (max ~20fps to avoid excessive reflows)
3549
+ const _streamThrottle = { last: 0, pending: false, idx: -1 };
3550
+ function throttledStreamUpdate(msgIdx) {
3551
+ _streamThrottle.idx = msgIdx;
3552
+ if (_streamThrottle.pending) return;
3553
+ const now = performance.now();
3554
+ if (now - _streamThrottle.last < 50) {
3555
+ _streamThrottle.pending = true;
3556
+ requestAnimationFrame(() => {
3557
+ _streamThrottle.pending = false;
3558
+ _streamThrottle.last = performance.now();
3559
+ updateStreamingMessage(_streamThrottle.idx);
3560
+ });
3561
+ return;
3562
+ }
3563
+ _streamThrottle.last = now;
3564
+ updateStreamingMessage(msgIdx);
3565
+ }
3566
+
3395
3567
  // ── Execution Events Rendering ──
3396
3568
  function renderExecEvents(events, msgIndex) {
3397
3569
  // 过滤出有意义的事件(仅展示调用/结果对,跳过中间状态)
@@ -3788,6 +3960,8 @@ async function sendMessage() {
3788
3960
 
3789
3961
  if (evt.type === 'session') {
3790
3962
  sessionIdReceived = evt.session_id;
3963
+ // Sync the actual session ID (backend may prefix with agent_path)
3964
+ state.activeSessionId = evt.session_id;
3791
3965
  } else if (evt.type === 'text') {
3792
3966
  fullResponse = evt.content;
3793
3967
  state.messages[msgIdx].content = evt.content;
@@ -3796,12 +3970,12 @@ async function sendMessage() {
3796
3970
  // Incremental streaming token
3797
3971
  fullResponse += evt.content;
3798
3972
  state.messages[msgIdx].content = fullResponse;
3799
- renderMessages();
3973
+ throttledStreamUpdate(msgIdx);
3800
3974
  } else if (evt.type === 'thought_delta') {
3801
3975
  // Agent 思考过程增量文本(流式推送,单独显示)
3802
3976
  fullThought += evt.content;
3803
3977
  state.messages[msgIdx].thought = fullThought;
3804
- renderMessages();
3978
+ throttledStreamUpdate(msgIdx);
3805
3979
  } else if (evt.type === 'thought') {
3806
3980
  // Agent 思考过程文本
3807
3981
  if (fullThought.trim() === '') {
@@ -3810,7 +3984,7 @@ async function sendMessage() {
3810
3984
  fullThought += '\n\n' + evt.content;
3811
3985
  }
3812
3986
  state.messages[msgIdx].thought = fullThought;
3813
- renderMessages();
3987
+ throttledStreamUpdate(msgIdx);
3814
3988
  } else if (evt.type === 'queue_start') {
3815
3989
  // New message starting from queue
3816
3990
  if (state.messages[msgIdx]) {
@@ -3828,13 +4002,13 @@ async function sendMessage() {
3828
4002
  // Clear intermediate text from previous agent loop iterations
3829
4003
  fullResponse = '';
3830
4004
  state.messages[msgIdx].content = '';
3831
- renderMessages();
4005
+ throttledStreamUpdate(msgIdx);
3832
4006
  } else if (evt.type === 'exec_event') {
3833
4007
  // Real-time execution event (tool call, code exec, skill result, etc.)
3834
4008
  execEventsReceived.push(evt.data);
3835
4009
  // 立即更新消息的 exec_events 并渲染
3836
4010
  state.messages[msgIdx].exec_events = [...execEventsReceived];
3837
- renderMessages();
4011
+ throttledStreamUpdate(msgIdx);
3838
4012
  } else if (evt.type === 'task_plan_updated') {
3839
4013
  // 任务计划已更新,刷新侧边栏任务列表
3840
4014
  loadTaskPlan();
@@ -3844,6 +4018,15 @@ async function sendMessage() {
3844
4018
  execEventsReceived = evt.exec_events;
3845
4019
  state.messages[msgIdx].exec_events = [...execEventsReceived];
3846
4020
  }
4021
+ } else if (evt.type === 'reasoning_delta') {
4022
+ // 模型推理过程增量文本(OpenAI o1/o3/DeepSeek-R1 等推理模型)
4023
+ if (!state.messages[msgIdx].reasoning) state.messages[msgIdx].reasoning = '';
4024
+ state.messages[msgIdx].reasoning += evt.content;
4025
+ throttledStreamUpdate(msgIdx);
4026
+ } else if (evt.type === 'reasoning') {
4027
+ // 模型推理完整文本
4028
+ state.messages[msgIdx].reasoning = evt.content;
4029
+ throttledStreamUpdate(msgIdx);
3847
4030
  } else if (evt.type === 'error') {
3848
4031
  fullResponse = '❌ ' + evt.error;
3849
4032
  state.messages[msgIdx].content = fullResponse;
@@ -4208,7 +4391,6 @@ async function callChatInject(text, choice) {
4208
4391
  toast(`注入失败: ${e.message}`, 'error');
4209
4392
  }
4210
4393
  }
4211
- }
4212
4394
 
4213
4395
  async function insertMessageToRunningSession(text) {
4214
4396
  // 立即将消息插入到当前正在进行的会话中