@meetopenbot/openbot 0.2.5 → 0.2.7
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/auto-model.js +4 -8
- package/dist/history.js +81 -5
- package/dist/model.js +29 -17
- package/dist/runtime.js +26 -7
- package/package.json +8 -6
package/dist/auto-model.js
CHANGED
|
@@ -7,14 +7,10 @@ export const AUTO_MODEL_OPTION = {
|
|
|
7
7
|
description: 'Fast and cheap. OpenBot picks the model.',
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Auto currently always uses this model. Restore a cheap pool (preference
|
|
11
|
+
* order, failover, intent routing) without changing the `openbot/auto` alias.
|
|
12
12
|
*/
|
|
13
|
-
export const AUTO_MODEL_POOL = [
|
|
14
|
-
'openai/gpt-5.6-luna',
|
|
15
|
-
'google/gemini-3.5-flash',
|
|
16
|
-
'openai/gpt-5.4-nano',
|
|
17
|
-
];
|
|
13
|
+
export const AUTO_MODEL_POOL = ['deepseek/deepseek-v4-flash'];
|
|
18
14
|
const PROVIDER_BYOK_ENV = {
|
|
19
15
|
openai: 'OPENAI_API_KEY',
|
|
20
16
|
anthropic: 'ANTHROPIC_API_KEY',
|
|
@@ -43,7 +39,7 @@ export function listAutoModelCandidates(ctx) {
|
|
|
43
39
|
const available = pool.filter((id) => providerHasByokKey(providerOf(id), env));
|
|
44
40
|
return available.length > 0 ? available : [pool[0]];
|
|
45
41
|
}
|
|
46
|
-
/**
|
|
42
|
+
/** First available pool member. Later: classify, then pick. */
|
|
47
43
|
export function pickAutoModel(ctx) {
|
|
48
44
|
return listAutoModelCandidates(ctx)[0] ?? AUTO_MODEL_POOL[0];
|
|
49
45
|
}
|
package/dist/history.js
CHANGED
|
@@ -1,3 +1,76 @@
|
|
|
1
|
+
function asReasoningPart(part) {
|
|
2
|
+
const text = typeof part.text === 'string' ? part.text : '';
|
|
3
|
+
if (!text.trim() && !part.providerOptions)
|
|
4
|
+
return undefined;
|
|
5
|
+
return {
|
|
6
|
+
type: 'reasoning',
|
|
7
|
+
text,
|
|
8
|
+
...(part.providerOptions
|
|
9
|
+
? { providerOptions: part.providerOptions }
|
|
10
|
+
: {}),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function outputReasoningParts(event, includeReasoning) {
|
|
14
|
+
if (!includeReasoning)
|
|
15
|
+
return [];
|
|
16
|
+
const stored = event.data.reasoningParts;
|
|
17
|
+
if (Array.isArray(stored) && stored.length > 0) {
|
|
18
|
+
return stored
|
|
19
|
+
.map(asReasoningPart)
|
|
20
|
+
.filter((part) => Boolean(part));
|
|
21
|
+
}
|
|
22
|
+
const fallback = event.data.reasoning;
|
|
23
|
+
if (typeof fallback === 'string' && fallback.trim()) {
|
|
24
|
+
return [{ type: 'reasoning', text: fallback }];
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
function firstToolCallIndex(parts) {
|
|
29
|
+
return parts.findIndex((part) => part.type === 'tool-call');
|
|
30
|
+
}
|
|
31
|
+
function insertBeforeToolCalls(parts, extra) {
|
|
32
|
+
if (extra.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
const toolIndex = firstToolCallIndex(parts);
|
|
35
|
+
if (toolIndex === -1)
|
|
36
|
+
parts.push(...extra);
|
|
37
|
+
else
|
|
38
|
+
parts.splice(toolIndex, 0, ...extra);
|
|
39
|
+
}
|
|
40
|
+
function makeAssistantMessage(text, reasoningParts) {
|
|
41
|
+
if (reasoningParts.length === 0) {
|
|
42
|
+
return { role: 'assistant', content: text };
|
|
43
|
+
}
|
|
44
|
+
const content = [...reasoningParts];
|
|
45
|
+
if (text)
|
|
46
|
+
content.push({ type: 'text', text });
|
|
47
|
+
return { role: 'assistant', content };
|
|
48
|
+
}
|
|
49
|
+
function appendAssistantOutput(message, text, reasoningParts) {
|
|
50
|
+
if (typeof message.content === 'string') {
|
|
51
|
+
if (reasoningParts.length === 0) {
|
|
52
|
+
message.content += text;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const content = [...reasoningParts];
|
|
56
|
+
const combined = message.content + text;
|
|
57
|
+
if (combined)
|
|
58
|
+
content.push({ type: 'text', text: combined });
|
|
59
|
+
message.content = content;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(message.content))
|
|
63
|
+
return;
|
|
64
|
+
const parts = message.content;
|
|
65
|
+
insertBeforeToolCalls(parts, reasoningParts);
|
|
66
|
+
if (text) {
|
|
67
|
+
const lastText = [...parts].reverse().find((part) => part.type === 'text');
|
|
68
|
+
if (lastText && lastText.type === 'text')
|
|
69
|
+
lastText.text += text;
|
|
70
|
+
else
|
|
71
|
+
insertBeforeToolCalls(parts, [{ type: 'text', text }]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
1
74
|
/**
|
|
2
75
|
* Ensures every tool-call has a matching tool-result before calling the LLM.
|
|
3
76
|
* Orphaned calls (interrupted run, missing :result event, etc.) get an empty
|
|
@@ -66,7 +139,8 @@ function toolResultText(data) {
|
|
|
66
139
|
* This is a basic implementation that maps events to messages and filters out
|
|
67
140
|
* events from sub-processes (delegation) to avoid duplication in history.
|
|
68
141
|
*/
|
|
69
|
-
export function eventsToModelMessages(events) {
|
|
142
|
+
export function eventsToModelMessages(events, options) {
|
|
143
|
+
const includeReasoning = options?.includeReasoning !== false;
|
|
70
144
|
const messages = [];
|
|
71
145
|
for (const event of events) {
|
|
72
146
|
// Skip events that belong to a sub-process (like delegation)
|
|
@@ -76,13 +150,15 @@ export function eventsToModelMessages(events) {
|
|
|
76
150
|
}
|
|
77
151
|
switch (event.type) {
|
|
78
152
|
case 'agent:output': {
|
|
79
|
-
const
|
|
153
|
+
const output = event;
|
|
154
|
+
const content = output.data.content ?? '';
|
|
155
|
+
const reasoningParts = outputReasoningParts(output, includeReasoning);
|
|
80
156
|
const last = messages[messages.length - 1];
|
|
81
|
-
if (last && last.role === 'assistant'
|
|
82
|
-
last
|
|
157
|
+
if (last && last.role === 'assistant') {
|
|
158
|
+
appendAssistantOutput(last, content, reasoningParts);
|
|
83
159
|
}
|
|
84
160
|
else {
|
|
85
|
-
messages.push(
|
|
161
|
+
messages.push(makeAssistantMessage(content, reasoningParts));
|
|
86
162
|
}
|
|
87
163
|
break;
|
|
88
164
|
}
|
package/dist/model.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { createOpenAI, openai as defaultOpenai } from '@ai-sdk/openai';
|
|
2
2
|
import { createAnthropic, anthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { createDeepSeek, deepSeek } from '@ai-sdk/deepseek';
|
|
3
4
|
import { expandModelString } from './auto-model.js';
|
|
4
5
|
import { CREDITS_API_KEY_PLACEHOLDER, INTEGRATIONS_TOKEN_HEADER, creditsProviderBaseUrl, resolveCreditsAuthConfig, shouldUseCreditsAuth, } from '@meetopenbot/plugin-sdk';
|
|
5
|
-
function deepseekChat(modelId) {
|
|
6
|
-
return createOpenAI({
|
|
7
|
-
baseURL: 'https://api.deepseek.com',
|
|
8
|
-
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
9
|
-
}).chat(modelId);
|
|
10
|
-
}
|
|
11
6
|
function googleChat(modelId) {
|
|
12
7
|
return createOpenAI({
|
|
13
8
|
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
@@ -15,14 +10,14 @@ function googleChat(modelId) {
|
|
|
15
10
|
}).chat(modelId);
|
|
16
11
|
}
|
|
17
12
|
/**
|
|
18
|
-
* OpenAI-compatible Chat Completions. Gemini
|
|
13
|
+
* OpenAI-compatible Chat Completions. Gemini's gateway only speaks this.
|
|
19
14
|
* Do not use for OpenAI itself — gpt-5.6-luna rejects function tools on
|
|
20
15
|
* `/v1/chat/completions` unless `reasoning_effort` is `none`.
|
|
21
16
|
*/
|
|
22
17
|
function openAiChatModel(options, modelId) {
|
|
23
18
|
return createOpenAI(options).chat(modelId);
|
|
24
19
|
}
|
|
25
|
-
/** AI SDK
|
|
20
|
+
/** AI SDK default: OpenAI Responses API (`/v1/responses`), required for tools on Luna. */
|
|
26
21
|
function openAiResponsesModel(options, modelId) {
|
|
27
22
|
return createOpenAI(options)(modelId);
|
|
28
23
|
}
|
|
@@ -31,19 +26,20 @@ function resolveCreditsProvider(provider, modelId) {
|
|
|
31
26
|
if (!config) {
|
|
32
27
|
throw new Error('OpenBot Credits is not configured. The cloud host must set OPENBOT_INTEGRATIONS_BASE_URL and OPENBOT_INTEGRATIONS_TOKEN.');
|
|
33
28
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
const options = {
|
|
30
|
+
baseURL: creditsProviderBaseUrl(config, provider),
|
|
31
|
+
headers: { [INTEGRATIONS_TOKEN_HEADER]: config.token },
|
|
32
|
+
apiKey: config.token || CREDITS_API_KEY_PLACEHOLDER,
|
|
33
|
+
};
|
|
38
34
|
switch (provider) {
|
|
39
35
|
case 'openai':
|
|
40
|
-
return openAiResponsesModel(
|
|
36
|
+
return openAiResponsesModel(options, modelId);
|
|
41
37
|
case 'anthropic':
|
|
42
|
-
return createAnthropic(
|
|
38
|
+
return createAnthropic(options)(modelId);
|
|
43
39
|
case 'google':
|
|
44
|
-
return openAiChatModel(
|
|
40
|
+
return openAiChatModel(options, modelId);
|
|
45
41
|
case 'deepseek':
|
|
46
|
-
return
|
|
42
|
+
return createDeepSeek(options)(modelId);
|
|
47
43
|
}
|
|
48
44
|
}
|
|
49
45
|
export function resolveModel(modelString, options) {
|
|
@@ -62,8 +58,24 @@ export function resolveModel(modelString, options) {
|
|
|
62
58
|
case 'google':
|
|
63
59
|
return useCredits ? resolveCreditsProvider('google', modelId) : googleChat(modelId);
|
|
64
60
|
case 'deepseek':
|
|
65
|
-
return useCredits ? resolveCreditsProvider('deepseek', modelId) :
|
|
61
|
+
return useCredits ? resolveCreditsProvider('deepseek', modelId) : deepSeek(modelId);
|
|
66
62
|
default:
|
|
67
63
|
throw new Error(`Unsupported AI provider: "${provider}"`);
|
|
68
64
|
}
|
|
69
65
|
}
|
|
66
|
+
function providerOf(modelString) {
|
|
67
|
+
return expandModelString(modelString).split('/')[0] ?? '';
|
|
68
|
+
}
|
|
69
|
+
/** Low thinking for DeepSeek (Auto). Google Chat Completions cannot round-trip thinking. */
|
|
70
|
+
export function reasoningForModel(modelString) {
|
|
71
|
+
const provider = providerOf(modelString);
|
|
72
|
+
if (provider === 'deepseek')
|
|
73
|
+
return 'low';
|
|
74
|
+
if (provider === 'google')
|
|
75
|
+
return 'none';
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
/** Gemini's OpenAI Chat path drops thinking; omit parts so tool turns stay valid. */
|
|
79
|
+
export function includeReasoningInHistory(modelString) {
|
|
80
|
+
return providerOf(modelString) !== 'google';
|
|
81
|
+
}
|
package/dist/runtime.js
CHANGED
|
@@ -4,7 +4,7 @@ import { buildContext } from './context.js';
|
|
|
4
4
|
import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
|
|
5
5
|
import { isAuthErrorMessage, isCreditsErrorMessage } from '@meetopenbot/plugin-sdk';
|
|
6
6
|
import { AUTO_MODEL_ID, isAutoModel, isRetryableModelError, listAutoModelCandidates, } from './auto-model.js';
|
|
7
|
-
import { resolveModel } from './model.js';
|
|
7
|
+
import { includeReasoningInHistory, reasoningForModel, resolveModel } from './model.js';
|
|
8
8
|
import { fetchModelRegistry, getProviderModelOptions, listApiKeyProviders, PROVIDER_API_KEY_LINKS, } from '@meetopenbot/plugin-sdk';
|
|
9
9
|
async function buildSystemPrompt(state, storage) {
|
|
10
10
|
const context = await buildContext(state, storage);
|
|
@@ -89,7 +89,6 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
89
89
|
channelId: context.state.channelId,
|
|
90
90
|
threadId: context.state.threadId,
|
|
91
91
|
});
|
|
92
|
-
const messages = eventsToModelMessages(events);
|
|
93
92
|
const candidates = isAutoModel(configuredModelString)
|
|
94
93
|
? listAutoModelCandidates({ authMode })
|
|
95
94
|
: [configuredModelString];
|
|
@@ -98,15 +97,18 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
98
97
|
const generate = async () => {
|
|
99
98
|
let lastError;
|
|
100
99
|
for (let i = 0; i < candidates.length; i++) {
|
|
100
|
+
const candidate = candidates[i];
|
|
101
101
|
try {
|
|
102
102
|
return await generateText({
|
|
103
|
-
model: resolveModel(
|
|
103
|
+
model: resolveModel(candidate, { authMode }),
|
|
104
104
|
system: systemPrompt,
|
|
105
|
-
messages,
|
|
105
|
+
messages: eventsToModelMessages(events, {
|
|
106
|
+
includeReasoning: includeReasoningInHistory(candidate),
|
|
107
|
+
}),
|
|
106
108
|
tools: toolDefinitions,
|
|
107
|
-
stopWhen: ({ steps }) => steps.length === 1,
|
|
108
109
|
allowSystemInMessages: true,
|
|
109
110
|
abortSignal,
|
|
111
|
+
reasoning: reasoningForModel(candidate),
|
|
110
112
|
});
|
|
111
113
|
}
|
|
112
114
|
catch (error) {
|
|
@@ -154,10 +156,27 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
154
156
|
parentToolCallId,
|
|
155
157
|
};
|
|
156
158
|
// Text before actions so history/UI show the model's intent first.
|
|
157
|
-
|
|
159
|
+
const reasoningParts = result.reasoning.flatMap((part) => {
|
|
160
|
+
if (part.type !== 'reasoning')
|
|
161
|
+
return [];
|
|
162
|
+
if (!part.text.trim() && !part.providerMetadata)
|
|
163
|
+
return [];
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
text: part.text,
|
|
167
|
+
...(part.providerMetadata ? { providerOptions: part.providerMetadata } : {}),
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
});
|
|
171
|
+
const reasoning = result.reasoningText?.trim() || undefined;
|
|
172
|
+
if (result.text || reasoning || reasoningParts.length > 0) {
|
|
158
173
|
yield {
|
|
159
174
|
type: 'agent:output',
|
|
160
|
-
data: {
|
|
175
|
+
data: {
|
|
176
|
+
content: result.text ?? '',
|
|
177
|
+
...(reasoning ? { reasoning } : {}),
|
|
178
|
+
...(reasoningParts.length > 0 ? { reasoningParts } : {}),
|
|
179
|
+
},
|
|
161
180
|
meta: outputMeta,
|
|
162
181
|
};
|
|
163
182
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetopenbot/openbot",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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",
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@ai-sdk/anthropic": "^
|
|
23
|
-
"@ai-sdk/
|
|
24
|
-
"ai": "^
|
|
22
|
+
"@ai-sdk/anthropic": "^4.0.42",
|
|
23
|
+
"@ai-sdk/deepseek": "^3.0.32",
|
|
24
|
+
"@ai-sdk/openai": "^4.0.46",
|
|
25
|
+
"ai": "^7.0.77",
|
|
25
26
|
"zod": "^4.3.5",
|
|
26
|
-
"@meetopenbot/plugin-sdk": "^0.5.
|
|
27
|
+
"@meetopenbot/plugin-sdk": "^0.5.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "^20.10.1",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "tsc && node ../../scripts/write-plugin-declaration.mjs",
|
|
34
35
|
"dev": "tsc --watch --preserveWatchOutput",
|
|
35
|
-
"typecheck": "tsc --noEmit"
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "node --experimental-strip-types --test src/*.test.ts"
|
|
36
38
|
}
|
|
37
39
|
}
|