File size: 2,430 Bytes
ea7122c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Adapt the supplied Agent's events to the existing Pi terminal's display format.
// Native messages stay untouched in Agent.messages; these are display objects only.
let lastTimestamp = 0;
export function timestamp() {
  lastTimestamp = Math.max(Date.now(), lastTimestamp + 1);
  return lastTimestamp;
}

export function piUsage(usage = {}) {
  const input = usage.prompt_tokens ?? 0;
  const output = usage.completion_tokens ?? 0;
  return {
    input, output, cacheRead: usage.cached_tokens ?? 0, cacheWrite: 0,
    totalTokens: input + output,
    cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
  };
}

export function assistantMessage(native = {}, result = {}, at = timestamp()) {
  const content = [];
  if (native.reasoning_content) content.push({ type: 'thinking', thinking: native.reasoning_content });
  if (native.content) content.push({ type: 'text', text: native.content });
  for (const call of native.tool_calls ?? []) {
    content.push({ type: 'toolCall', id: call.id, name: call.function.name, arguments: call.function.arguments });
  }
  const reasons = { stop: 'stop', length: 'length', tool_calls: 'toolUse', cancelled: 'aborted' };
  return {
    role: 'assistant', content, timestamp: at, api: 'webgpu', provider: 'local', model: 'MiniCPM5-2B-GGUF',
    usage: piUsage(result.usage),
    ...(result.finish_reason ? { stopReason: reasons[result.finish_reason] ?? 'error' } : {}),
  };
}

export function toolMessage(event) {
  return {
    role: 'toolResult', toolCallId: event.call.id, toolName: event.call.function.name,
    content: [{ type: 'text', text: event.message.content ?? '' }],
    details: event.details, isError: event.is_error, timestamp: timestamp(),
  };
}

export function appendDelta(message, type, text) {
  const key = type === 'reasoning' ? 'thinking' : 'text';
  let block = message.content.find(block => block.type === key);
  if (!block) message.content.push(block = { type: key, [key]: '' });
  block[key] += text;
  return key;
}

export function loadProgress(progress) {
  // Tensor counts are not bytes, even though both have loaded/total fields.
  if (progress.status === 'weights' && progress.kind === 'bytes' && progress.total > 0) {
    return { type: 'load_progress', phase: 'download', loaded: progress.loaded, total: progress.total };
  }
  return { type: 'load_progress', phase: 'compile', status: progress.status, message: progress.message };
}