@meetopenbot/openbot 1.0.0 → 1.0.1
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/history.js +9 -5
- package/dist/images.js +64 -0
- package/dist/runtime.js +32 -5
- package/package.json +2 -2
package/dist/history.js
CHANGED
|
@@ -164,12 +164,16 @@ export function eventsToModelMessages(events, options) {
|
|
|
164
164
|
}
|
|
165
165
|
case 'agent:invoke': {
|
|
166
166
|
const invokeEvent = event;
|
|
167
|
-
if (invokeEvent.data?.
|
|
167
|
+
if (invokeEvent.data?.role) {
|
|
168
168
|
const role = invokeEvent.data.role === 'agent' ? 'user' : invokeEvent.data.role;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
const content = invokeEvent.data.content ?? '';
|
|
170
|
+
const hasFiles = Boolean(invokeEvent.data.attachments?.length);
|
|
171
|
+
if (content || hasFiles) {
|
|
172
|
+
messages.push({
|
|
173
|
+
role,
|
|
174
|
+
content,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
173
177
|
}
|
|
174
178
|
break;
|
|
175
179
|
}
|
package/dist/images.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { shouldUseCreditsAuth } from '@meetopenbot/plugin-sdk';
|
|
2
|
+
export function isImageAttachment(file) {
|
|
3
|
+
if (file.mimeType?.startsWith('image/'))
|
|
4
|
+
return true;
|
|
5
|
+
return /\.(png|jpe?g|gif|webp)$/i.test(file.name);
|
|
6
|
+
}
|
|
7
|
+
export function eventsHaveImages(events) {
|
|
8
|
+
return events.some((event) => {
|
|
9
|
+
if (event.meta?.parentToolCallId)
|
|
10
|
+
return false;
|
|
11
|
+
if (event.type !== 'agent:invoke')
|
|
12
|
+
return false;
|
|
13
|
+
return Boolean(event.data?.attachments?.some(isImageAttachment));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** Auto uses DeepSeek, which cannot see pictures. Pick a cheap vision model. */
|
|
17
|
+
export function pickVisionModel(authMode, env = process.env) {
|
|
18
|
+
if (shouldUseCreditsAuth({ authMode }))
|
|
19
|
+
return 'google/gemini-3.5-flash';
|
|
20
|
+
if (env.OPENAI_API_KEY?.trim())
|
|
21
|
+
return 'openai/gpt-4o-mini';
|
|
22
|
+
if (env.GOOGLE_GENERATIVE_AI_API_KEY?.trim())
|
|
23
|
+
return 'google/gemini-3.5-flash';
|
|
24
|
+
if (env.ANTHROPIC_API_KEY?.trim())
|
|
25
|
+
return 'anthropic/claude-haiku-4-5';
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Put image files on matching user messages as AI SDK `file` parts. */
|
|
29
|
+
export async function addImageParts(messages, events, read) {
|
|
30
|
+
const result = [...messages];
|
|
31
|
+
let userIndex = 0;
|
|
32
|
+
for (const event of events) {
|
|
33
|
+
if (event.meta?.parentToolCallId)
|
|
34
|
+
continue;
|
|
35
|
+
if (event.type !== 'agent:invoke' || !event.data?.role)
|
|
36
|
+
continue;
|
|
37
|
+
const files = (event.data.attachments ?? []).filter(isImageAttachment);
|
|
38
|
+
const text = event.data.content ?? '';
|
|
39
|
+
if (!text && files.length === 0)
|
|
40
|
+
continue;
|
|
41
|
+
const current = result[userIndex];
|
|
42
|
+
userIndex += 1;
|
|
43
|
+
if (!current || files.length === 0)
|
|
44
|
+
continue;
|
|
45
|
+
const parts = [];
|
|
46
|
+
if (typeof current.content === 'string' && current.content) {
|
|
47
|
+
parts.push({ type: 'text', text: current.content });
|
|
48
|
+
}
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const loaded = await read(file.name);
|
|
51
|
+
if (!loaded?.data.byteLength)
|
|
52
|
+
continue;
|
|
53
|
+
parts.push({
|
|
54
|
+
type: 'file',
|
|
55
|
+
data: loaded.data,
|
|
56
|
+
mediaType: loaded.mediaType || 'image/png',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (parts.some((part) => part.type === 'file')) {
|
|
60
|
+
result[userIndex - 1] = { role: current.role, content: parts };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
package/dist/runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { streamText, stepCountIs } from 'ai';
|
|
2
2
|
import { eventsToModelMessages } from './history.js';
|
|
3
|
+
import { addImageParts, eventsHaveImages, pickVisionModel } from './images.js';
|
|
3
4
|
import { buildContext } from './context.js';
|
|
4
5
|
import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
|
|
5
6
|
import { isAuthErrorMessage, isCreditsErrorMessage } from '@meetopenbot/plugin-sdk';
|
|
@@ -97,11 +98,19 @@ export async function* runOpenbotTurn(args, options) {
|
|
|
97
98
|
channelId: state.channelId ?? '',
|
|
98
99
|
threadId,
|
|
99
100
|
});
|
|
100
|
-
const
|
|
101
|
+
const hasImages = eventsHaveImages(events);
|
|
102
|
+
let candidates = isAutoModel(configuredModelString)
|
|
101
103
|
? listAutoModelCandidates({ authMode })
|
|
102
104
|
: [configuredModelString];
|
|
103
105
|
try {
|
|
104
|
-
|
|
106
|
+
if (hasImages && isAutoModel(configuredModelString)) {
|
|
107
|
+
const vision = pickVisionModel(authMode);
|
|
108
|
+
if (!vision) {
|
|
109
|
+
throw new Error('Looking at images needs an OpenAI, Google, or Anthropic API key. Auto\'s usual model cannot see pictures.');
|
|
110
|
+
}
|
|
111
|
+
candidates = [vision];
|
|
112
|
+
}
|
|
113
|
+
const missingKey = missingByokApiKeyMessage(candidates[0], { authMode });
|
|
105
114
|
if (missingKey)
|
|
106
115
|
throw new Error(missingKey);
|
|
107
116
|
let lastError;
|
|
@@ -116,12 +125,30 @@ export async function* runOpenbotTurn(args, options) {
|
|
|
116
125
|
};
|
|
117
126
|
const tools = toAiTools(toolDefinitions, args, flushPendingOutput);
|
|
118
127
|
try {
|
|
128
|
+
let messages = eventsToModelMessages(events, {
|
|
129
|
+
includeReasoning: includeReasoningInHistory(candidate),
|
|
130
|
+
});
|
|
131
|
+
if (hasImages) {
|
|
132
|
+
messages = await addImageParts(messages, events, async (name) => {
|
|
133
|
+
try {
|
|
134
|
+
const file = await storage.readThreadAttachment({
|
|
135
|
+
channelId: state.channelId,
|
|
136
|
+
threadId,
|
|
137
|
+
name,
|
|
138
|
+
});
|
|
139
|
+
return { data: new Uint8Array(file.body), mediaType: file.mimeType };
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.warn('[openbot] could not read attachment', name, error);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
console.info('[openbot] attached images for', candidate);
|
|
147
|
+
}
|
|
119
148
|
const result = streamText({
|
|
120
149
|
model: resolveModel(candidate, { authMode }),
|
|
121
150
|
system: systemPrompt,
|
|
122
|
-
messages
|
|
123
|
-
includeReasoning: includeReasoningInHistory(candidate),
|
|
124
|
-
}),
|
|
151
|
+
messages,
|
|
125
152
|
tools,
|
|
126
153
|
abortSignal,
|
|
127
154
|
reasoning: reasoningForModel(candidate),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetopenbot/openbot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@ai-sdk/openai": "^4.0.46",
|
|
25
25
|
"ai": "^7.0.77",
|
|
26
26
|
"zod": "^4.3.5",
|
|
27
|
-
"@meetopenbot/plugin-sdk": "^1.0.
|
|
27
|
+
"@meetopenbot/plugin-sdk": "^1.0.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^20.10.1",
|