@meetopenbot/openbot 1.2.1 → 1.2.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.
Files changed (2) hide show
  1. package/dist/history.js +62 -38
  2. package/package.json +2 -2
package/dist/history.js CHANGED
@@ -71,51 +71,75 @@ function appendAssistantOutput(message, text, reasoningParts) {
71
71
  insertBeforeToolCalls(parts, [{ type: 'text', text }]);
72
72
  }
73
73
  }
74
- /**
75
- * Ensures every tool-call has a matching tool-result before calling the LLM.
76
- * Orphaned calls (interrupted run, missing :result event, etc.) get an empty
77
- * result so the conversation can resume instead of failing validation.
78
- */
79
- function fillMissingToolResults(messages) {
80
- const filled = [];
81
- const pending = new Map();
82
- const flushPending = () => {
83
- if (pending.size === 0)
84
- return;
85
- filled.push({
86
- role: 'tool',
87
- content: [...pending.entries()].map(([toolCallId, toolName]) => ({
74
+ function isToolResultPart(part) {
75
+ return Boolean(part && typeof part === 'object' && part.type === 'tool-result');
76
+ }
77
+ function assistantToolCalls(message) {
78
+ if (message.role !== 'assistant' || !Array.isArray(message.content))
79
+ return [];
80
+ const calls = [];
81
+ for (const part of message.content) {
82
+ if (part.type !== 'tool-call')
83
+ continue;
84
+ const toolCall = part;
85
+ if (typeof toolCall.toolCallId !== 'string')
86
+ continue;
87
+ calls.push({
88
+ toolCallId: toolCall.toolCallId,
89
+ toolName: typeof toolCall.toolName === 'string' ? toolCall.toolName : 'tool',
90
+ });
91
+ }
92
+ return calls;
93
+ }
94
+ function collectToolResults(messages) {
95
+ const results = new Map();
96
+ for (const message of messages) {
97
+ if (message.role !== 'tool' || !Array.isArray(message.content))
98
+ continue;
99
+ for (const part of message.content) {
100
+ if (!isToolResultPart(part) || results.has(part.toolCallId))
101
+ continue;
102
+ results.set(part.toolCallId, part);
103
+ }
104
+ }
105
+ return results;
106
+ }
107
+ function toolMessageForCalls(calls, results) {
108
+ return {
109
+ role: 'tool',
110
+ content: calls.map(({ toolCallId, toolName }) => {
111
+ const existing = results.get(toolCallId);
112
+ if (existing) {
113
+ results.delete(toolCallId);
114
+ return existing;
115
+ }
116
+ return {
88
117
  type: 'tool-result',
89
118
  toolCallId,
90
119
  toolName,
91
120
  output: { type: 'text', value: '' },
92
- })),
93
- });
94
- pending.clear();
121
+ };
122
+ }),
95
123
  };
124
+ }
125
+ /**
126
+ * Rebuild tool messages so every `tool-call` is followed immediately by matching
127
+ * results. Late results (user invoke between call and `:result`) are moved next
128
+ * to their call; unmatched results are dropped; interrupted calls get an empty
129
+ * result so the next LLM turn does not fail provider validation.
130
+ */
131
+ function sanitizeToolPairing(messages) {
132
+ const results = collectToolResults(messages);
133
+ const sanitized = [];
96
134
  for (const message of messages) {
97
- if (message.role === 'tool' && Array.isArray(message.content)) {
98
- filled.push(message);
99
- for (const part of message.content) {
100
- if (part.type === 'tool-result') {
101
- pending.delete(part.toolCallId);
102
- }
103
- }
135
+ if (message.role === 'tool')
104
136
  continue;
105
- }
106
- flushPending();
107
- filled.push(message);
108
- if (message.role === 'assistant' && Array.isArray(message.content)) {
109
- for (const part of message.content) {
110
- if (part.type === 'tool-call') {
111
- const toolCall = part;
112
- pending.set(toolCall.toolCallId, toolCall.toolName);
113
- }
114
- }
115
- }
137
+ sanitized.push(message);
138
+ const calls = assistantToolCalls(message);
139
+ if (calls.length > 0)
140
+ sanitized.push(toolMessageForCalls(calls, results));
116
141
  }
117
- flushPending();
118
- return filled;
142
+ return sanitized;
119
143
  }
120
144
  function toolResultText(data) {
121
145
  if (data && typeof data === 'object') {
@@ -235,5 +259,5 @@ export function eventsToModelMessages(events, options) {
235
259
  break;
236
260
  }
237
261
  }
238
- return fillMissingToolResults(messages);
262
+ return sanitizeToolPairing(messages);
239
263
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/openbot",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "OpenBot coordinator runtime: ask specialists, route work into Spaces, and track todos.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "@ai-sdk/openai": "^4.0.46",
26
26
  "ai": "^7.0.77",
27
27
  "zod": "^4.3.5",
28
- "@meetopenbot/plugin-sdk": "^1.1.2"
28
+ "@meetopenbot/plugin-sdk": "^1.1.3"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.10.1",