@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 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?.content && invokeEvent.data?.role) {
167
+ if (invokeEvent.data?.role) {
168
168
  const role = invokeEvent.data.role === 'agent' ? 'user' : invokeEvent.data.role;
169
- messages.push({
170
- role,
171
- content: invokeEvent.data.content,
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 candidates = isAutoModel(configuredModelString)
101
+ const hasImages = eventsHaveImages(events);
102
+ let candidates = isAutoModel(configuredModelString)
101
103
  ? listAutoModelCandidates({ authMode })
102
104
  : [configuredModelString];
103
105
  try {
104
- const missingKey = missingByokApiKeyMessage(configuredModelString, { authMode });
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: eventsToModelMessages(events, {
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.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.0"
27
+ "@meetopenbot/plugin-sdk": "^1.0.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^20.10.1",