@meetopenbot/pi 0.0.1 → 0.0.2

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/dist/format.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const isAssistantMessage = (message) => message.role === 'assistant';
2
2
  export const createStreamState = () => ({
3
3
  assistantText: '',
4
- lastYieldedText: '',
5
4
  });
6
5
  const strArg = (args, ...keys) => {
7
6
  if (!args || typeof args !== 'object')
@@ -76,36 +75,47 @@ export const formatPiEvent = (event, state) => {
76
75
  if (assistantEvent.type !== 'text_delta')
77
76
  return undefined;
78
77
  state.assistantText += assistantEvent.delta;
79
- if (state.assistantText === state.lastYieldedText)
80
- return undefined;
81
- state.lastYieldedText = state.assistantText;
82
- return state.assistantText;
78
+ return undefined;
83
79
  }
84
80
  case 'tool_execution_start': {
85
- state.statusLine = formatToolStart(event.toolName, event.args);
86
- return state.statusLine;
81
+ const statusLine = formatToolStart(event.toolName, event.args);
82
+ return {
83
+ type: 'tool_start',
84
+ toolCallId: event.toolCallId,
85
+ toolName: event.toolName,
86
+ args: event.args,
87
+ statusLine,
88
+ };
87
89
  }
88
90
  case 'tool_execution_end': {
89
- if (!event.isError)
90
- return undefined;
91
- state.statusLine = `Tool **${event.toolName}** failed.`;
92
- return state.statusLine;
91
+ let statusLine;
92
+ if (event.isError) {
93
+ statusLine = `Tool **${event.toolName}** failed.`;
94
+ }
95
+ return {
96
+ type: 'tool_end',
97
+ toolCallId: event.toolCallId,
98
+ toolName: event.toolName,
99
+ result: event.result,
100
+ isError: event.isError,
101
+ statusLine,
102
+ };
93
103
  }
94
104
  case 'auto_retry_start': {
95
- state.statusLine = `Retrying (${event.attempt}/${event.maxAttempts})…`;
96
- return state.statusLine;
105
+ const statusLine = `Retrying (${event.attempt}/${event.maxAttempts})…`;
106
+ return { type: 'status', statusLine };
97
107
  }
98
108
  case 'compaction_start': {
99
- state.statusLine = 'Compacting conversation context…';
100
- return state.statusLine;
109
+ const statusLine = 'Compacting conversation context…';
110
+ return { type: 'status', statusLine };
101
111
  }
102
112
  case 'agent_end': {
103
113
  const lastAssistant = [...event.messages].reverse().find(isAssistantMessage);
104
114
  if (lastAssistant?.stopReason === 'error' && lastAssistant.errorMessage?.trim()) {
105
- return `**Pi error:** ${lastAssistant.errorMessage.trim()}`;
115
+ return { type: 'text', content: `**Pi error:** ${lastAssistant.errorMessage.trim()}` };
106
116
  }
107
117
  if (state.assistantText.trim()) {
108
- return state.assistantText;
118
+ return { type: 'text', content: state.assistantText };
109
119
  }
110
120
  return undefined;
111
121
  }
@@ -118,3 +128,21 @@ export const formatPiError = (error) => {
118
128
  return error.message;
119
129
  return String(error);
120
130
  };
131
+ export const formatToolResult = (result) => {
132
+ if (!result)
133
+ return '';
134
+ if (typeof result === 'string')
135
+ return result;
136
+ if (result && typeof result === 'object' && Array.isArray(result.content)) {
137
+ return result.content
138
+ .map((c) => {
139
+ if (c.type === 'text')
140
+ return c.text;
141
+ if (c.type === 'image')
142
+ return `[Image: ${c.source?.data?.slice(0, 20)}...]`;
143
+ return `[${c.type}]`;
144
+ })
145
+ .join('\n');
146
+ }
147
+ return JSON.stringify(result, null, 2);
148
+ };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { agentOutput, definePlugin, shouldHandleInvoke, } from '@meetopenbot/plugin-sdk';
1
+ import { agentOutput, definePlugin, shouldHandleInvoke, uiWidget, } from '@meetopenbot/plugin-sdk';
2
2
  import { resolveConfig } from './config.js';
3
- import { formatPiError } from './format.js';
3
+ import { formatPiError, formatToolResult } from './format.js';
4
4
  import { getOrCreatePiSession } from './session.js';
5
5
  import { streamPiPrompt } from './stream.js';
6
6
  export default definePlugin({
@@ -73,12 +73,76 @@ export default definePlugin({
73
73
  state: handlerCtx.state,
74
74
  storage: context.storage,
75
75
  });
76
+ let fullTextContent = '';
77
+ const toolInfoMap = new Map();
76
78
  for await (const chunk of streamPiPrompt(session, prompt, {
77
79
  streaming: session.isStreaming,
78
80
  })) {
81
+ switch (chunk.type) {
82
+ case 'tool_start':
83
+ toolInfoMap.set(chunk.toolCallId, {
84
+ statusLine: chunk.statusLine,
85
+ args: chunk.args,
86
+ });
87
+ yield uiWidget({
88
+ agentId: context.agentId,
89
+ threadId,
90
+ widget: {
91
+ kind: 'message',
92
+ widgetId: chunk.toolCallId,
93
+ title: chunk.statusLine,
94
+ body: `**Input**\n\`\`\`json\n${JSON.stringify(chunk.args, null, 2)}\n\`\`\``,
95
+ variant: 'basic',
96
+ display: "collapsed",
97
+ },
98
+ });
99
+ break;
100
+ case 'tool_end': {
101
+ const info = toolInfoMap.get(chunk.toolCallId);
102
+ const inputMd = info
103
+ ? `**Input**\n\`\`\`json\n${JSON.stringify(info.args, null, 2)}\n\`\`\`\n\n`
104
+ : '';
105
+ const outputMd = `**Output**\n${formatToolResult(chunk.result)}`;
106
+ yield uiWidget({
107
+ agentId: context.agentId,
108
+ threadId,
109
+ widget: {
110
+ kind: 'message',
111
+ widgetId: chunk.toolCallId,
112
+ title: chunk.statusLine || info?.statusLine || `Tool ${chunk.toolName} finished`,
113
+ body: inputMd + outputMd,
114
+ variant: 'basic',
115
+ display: "collapsed",
116
+ state: chunk.isError ? 'error' : 'submitted',
117
+ },
118
+ });
119
+ break;
120
+ }
121
+ case 'status':
122
+ yield uiWidget({
123
+ agentId: context.agentId,
124
+ threadId,
125
+ widget: {
126
+ kind: 'message',
127
+ widgetId: `status-${Date.now()}`,
128
+ title: "Status",
129
+ body: chunk.statusLine,
130
+ variant: 'basic',
131
+ display: "collapsed",
132
+ },
133
+ });
134
+ break;
135
+ case 'text':
136
+ if (fullTextContent)
137
+ fullTextContent += '\n\n';
138
+ fullTextContent += chunk.content;
139
+ break;
140
+ }
141
+ }
142
+ if (fullTextContent.trim()) {
79
143
  yield agentOutput({
80
144
  agentId: context.agentId,
81
- content: chunk,
145
+ content: fullTextContent.trim(),
82
146
  threadId,
83
147
  });
84
148
  }
package/dist/stream.js CHANGED
@@ -11,9 +11,9 @@ export async function* streamPiPrompt(session, prompt, options) {
11
11
  wake = undefined;
12
12
  };
13
13
  const unsubscribe = session.subscribe((event) => {
14
- const chunk = formatPiEvent(event, state);
15
- if (chunk) {
16
- pending.push(chunk);
14
+ const result = formatPiEvent(event, state);
15
+ if (result) {
16
+ pending.push(result);
17
17
  wakeUp();
18
18
  }
19
19
  if (event.type === 'agent_end') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/pi",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "OpenBot agent plugin powered by the Pi coding agent SDK.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@earendil-works/pi-ai": "^0.79.1",
23
23
  "@earendil-works/pi-coding-agent": "^0.79.1",
24
- "@meetopenbot/plugin-sdk": "^0.1.2"
24
+ "@meetopenbot/plugin-sdk": "^0.1.4"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^25.9.2",