@meetopenbot/openbot 0.1.14 → 0.2.0
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 +67 -0
- package/dist/context.js +119 -37
- package/dist/history.js +2 -1
- package/dist/index.js +47 -29
- package/dist/model-registry.js +12 -11
- package/dist/model.js +14 -4
- package/dist/runtime.js +80 -45
- package/dist/space-id.js +22 -0
- package/dist/system-prompt.js +54 -55
- package/dist/tools/ask-agent.js +123 -0
- package/dist/tools/start-work.js +262 -0
- package/dist/tools/storage.js +135 -98
- package/dist/tools/thread-status.js +91 -0
- package/package.json +8 -9
- package/dist/tools/delegation.js +0 -137
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { shouldUseCreditsAuth } from './credits-auth.js';
|
|
2
|
+
/** Product alias shown in the picker. Never sent to a provider or the credits proxy. */
|
|
3
|
+
export const AUTO_MODEL_ID = 'openbot/auto';
|
|
4
|
+
export const AUTO_MODEL_OPTION = {
|
|
5
|
+
value: AUTO_MODEL_ID,
|
|
6
|
+
label: 'Auto',
|
|
7
|
+
description: 'Fast and cheap. OpenBot picks the model.',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Cheap pool for Auto. Order is preference; failover walks this list.
|
|
11
|
+
* Intent routing can later pick a member without changing the alias.
|
|
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
|
+
];
|
|
18
|
+
const PROVIDER_BYOK_ENV = {
|
|
19
|
+
openai: 'OPENAI_API_KEY',
|
|
20
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
21
|
+
google: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
22
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
23
|
+
};
|
|
24
|
+
export function isAutoModel(modelString) {
|
|
25
|
+
if (!modelString)
|
|
26
|
+
return true;
|
|
27
|
+
const trimmed = modelString.trim();
|
|
28
|
+
return trimmed === '' || trimmed === AUTO_MODEL_ID || trimmed === 'auto';
|
|
29
|
+
}
|
|
30
|
+
function providerOf(modelString) {
|
|
31
|
+
return modelString.split('/')[0] ?? '';
|
|
32
|
+
}
|
|
33
|
+
function providerHasByokKey(provider, env) {
|
|
34
|
+
const key = PROVIDER_BYOK_ENV[provider];
|
|
35
|
+
return Boolean(key && env[key]?.trim());
|
|
36
|
+
}
|
|
37
|
+
/** Concrete models Auto may call, filtered by credits vs BYOK keys. */
|
|
38
|
+
export function listAutoModelCandidates(ctx) {
|
|
39
|
+
const pool = [...AUTO_MODEL_POOL];
|
|
40
|
+
if (shouldUseCreditsAuth(ctx))
|
|
41
|
+
return pool;
|
|
42
|
+
const env = ctx?.env ?? process.env;
|
|
43
|
+
const available = pool.filter((id) => providerHasByokKey(providerOf(id), env));
|
|
44
|
+
return available.length > 0 ? available : [pool[0]];
|
|
45
|
+
}
|
|
46
|
+
/** MVP picker: first available pool member. Later: classify, then pick. */
|
|
47
|
+
export function pickAutoModel(ctx) {
|
|
48
|
+
return listAutoModelCandidates(ctx)[0] ?? AUTO_MODEL_POOL[0];
|
|
49
|
+
}
|
|
50
|
+
/** Expand Auto to a concrete provider/id. Pass-through for pinned models. */
|
|
51
|
+
export function expandModelString(modelString, ctx) {
|
|
52
|
+
if (isAutoModel(modelString))
|
|
53
|
+
return pickAutoModel(ctx);
|
|
54
|
+
return modelString.trim();
|
|
55
|
+
}
|
|
56
|
+
export function isRetryableModelError(error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
const lower = message.toLowerCase();
|
|
59
|
+
return (/\b(429|500|502|503|529)\b/.test(message) ||
|
|
60
|
+
lower.includes('not supported') ||
|
|
61
|
+
lower.includes('rate limit') ||
|
|
62
|
+
lower.includes('overloaded') ||
|
|
63
|
+
lower.includes('temporarily unavailable') ||
|
|
64
|
+
lower.includes('timeout') ||
|
|
65
|
+
lower.includes('econnreset') ||
|
|
66
|
+
lower.includes('fetch failed'));
|
|
67
|
+
}
|
package/dist/context.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { GENERAL_SPACE_ID } from "./space-id.js";
|
|
2
|
+
import { OPENBOT_SYSTEM_PROMPT } from "./system-prompt.js";
|
|
3
|
+
import { todoService } from "./tools/todo-service.js";
|
|
3
4
|
export const DEFAULT_CONTEXT_BUDGET = 8000;
|
|
4
5
|
export const MAX_CONTEXT_FILES = 50;
|
|
5
6
|
/**
|
|
@@ -7,17 +8,17 @@ export const MAX_CONTEXT_FILES = 50;
|
|
|
7
8
|
*/
|
|
8
9
|
export const getContextBudgetForModel = (modelString) => {
|
|
9
10
|
const budgets = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
"openai/gpt-4o": 128000,
|
|
12
|
+
"openai/gpt-4o-mini": 128000,
|
|
13
|
+
"openai/o1-preview": 128000,
|
|
14
|
+
"openai/o1-mini": 128000,
|
|
15
|
+
"anthropic/claude-3-5-sonnet-20240620": 200000,
|
|
16
|
+
"anthropic/claude-3-5-sonnet-latest": 200000,
|
|
17
|
+
"anthropic/claude-3-opus-20240229": 200000,
|
|
18
|
+
"anthropic/claude-3-sonnet-20240229": 200000,
|
|
19
|
+
"anthropic/claude-3-haiku-20240307": 200000,
|
|
20
|
+
"deepseek/deepseek-chat": 64000,
|
|
21
|
+
"deepseek/deepseek-reasoner": 64000,
|
|
21
22
|
};
|
|
22
23
|
return budgets[modelString] || DEFAULT_CONTEXT_BUDGET;
|
|
23
24
|
};
|
|
@@ -25,18 +26,24 @@ export const getContextBudgetForModel = (modelString) => {
|
|
|
25
26
|
* Simplified context builder for MVP.
|
|
26
27
|
*/
|
|
27
28
|
export async function buildContext(state, storage) {
|
|
28
|
-
const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
|
|
29
|
+
const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails, } = state;
|
|
29
30
|
const sections = [];
|
|
30
31
|
// Fetch agents once if storage is available
|
|
31
|
-
const allAgents = storage?.getAgents
|
|
32
|
+
const allAgents = storage?.getAgents
|
|
33
|
+
? await storage.getAgents().catch(() => [])
|
|
34
|
+
: [];
|
|
32
35
|
// 1. User
|
|
33
36
|
if (state.currentUser?.userName) {
|
|
34
37
|
sections.push(`## HUMAN\n- Name: ${state.currentUser.userName}`);
|
|
35
38
|
}
|
|
36
39
|
// 2. Environment
|
|
37
|
-
let env =
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
+
let env = "## ENVIRONMENT\n";
|
|
41
|
+
const spaceName = channelDetails?.name || channelId;
|
|
42
|
+
const isGeneral = channelId === GENERAL_SPACE_ID;
|
|
43
|
+
env += `- Space: #${channelId || "unknown"} (${spaceName || channelId})\n`;
|
|
44
|
+
env += isGeneral
|
|
45
|
+
? "- Role: inbox — answer questions, listings, and setup here. start_work only for real project work into an existing Space; propose new Spaces before creating them\n"
|
|
46
|
+
: "- Role: dedicated Space — do the work here; do not start_work unless the human asks to move\n";
|
|
40
47
|
if (channelDetails?.cwd) {
|
|
41
48
|
env += `- Workspace: ${channelDetails.cwd}\n`;
|
|
42
49
|
}
|
|
@@ -44,6 +51,45 @@ export async function buildContext(state, storage) {
|
|
|
44
51
|
env += `- Thread: ${threadDetails?.name || threadId}\n`;
|
|
45
52
|
}
|
|
46
53
|
sections.push(env);
|
|
54
|
+
if (threadId) {
|
|
55
|
+
const threadState = threadDetails?.state && typeof threadDetails.state === "object"
|
|
56
|
+
? threadDetails.state
|
|
57
|
+
: undefined;
|
|
58
|
+
const threadStatus = typeof threadState?.status === "string" && threadState.status.trim()
|
|
59
|
+
? threadState.status.trim()
|
|
60
|
+
: "completed";
|
|
61
|
+
const threadStatusReason = typeof threadState?.statusReason === "string" && threadState.statusReason.trim()
|
|
62
|
+
? threadState.statusReason.trim()
|
|
63
|
+
: undefined;
|
|
64
|
+
let statusSection = `## THREAD STATUS\n- Status: ${threadStatus}`;
|
|
65
|
+
if (threadStatusReason) {
|
|
66
|
+
statusSection += `\n- Reason: ${threadStatusReason}`;
|
|
67
|
+
}
|
|
68
|
+
statusSection +=
|
|
69
|
+
"\nUse `set_thread_status` to update this. needs_input when blocked on the human; ready_for_review when you believe the job is done. Never set completed or archived.";
|
|
70
|
+
sections.push(statusSection);
|
|
71
|
+
}
|
|
72
|
+
const invokeData = state.triggerEvent?.type === "agent:invoke"
|
|
73
|
+
? state.triggerEvent.data
|
|
74
|
+
: undefined;
|
|
75
|
+
const mentionedAgentIds = Array.isArray(invokeData?.mentionedAgentIds)
|
|
76
|
+
? invokeData.mentionedAgentIds.filter((id) => typeof id === "string" && id.length > 0)
|
|
77
|
+
: [];
|
|
78
|
+
const forwardedFromSpaceId = typeof invokeData?.forwardedFromSpaceId === "string"
|
|
79
|
+
? invokeData.forwardedFromSpaceId
|
|
80
|
+
: undefined;
|
|
81
|
+
if (mentionedAgentIds.length > 0) {
|
|
82
|
+
const formatted = mentionedAgentIds
|
|
83
|
+
.map((id) => {
|
|
84
|
+
const agent = allAgents.find((a) => a.id === id);
|
|
85
|
+
return `- ${id}${agent?.name && agent.name !== id ? ` (${agent.name})` : ""} — you MUST ask this agent`;
|
|
86
|
+
})
|
|
87
|
+
.join("\n");
|
|
88
|
+
sections.push(`## MENTIONED AGENTS\n${formatted}`);
|
|
89
|
+
}
|
|
90
|
+
if (forwardedFromSpaceId) {
|
|
91
|
+
sections.push(`## FORWARDED WORK\nThis thread was started from #${forwardedFromSpaceId}. Stay in this Space. Do not call start_work again.`);
|
|
92
|
+
}
|
|
47
93
|
// 2.5 Thread todos
|
|
48
94
|
if (channelId && threadId) {
|
|
49
95
|
try {
|
|
@@ -51,35 +97,72 @@ export async function buildContext(state, storage) {
|
|
|
51
97
|
if (list.items.length > 0) {
|
|
52
98
|
const formatted = list.items
|
|
53
99
|
.map((t) => `- [${t.status}] (${t.id}) ${t.content}`)
|
|
54
|
-
.join(
|
|
100
|
+
.join("\n");
|
|
55
101
|
sections.push(`## TODOS\n${formatted}`);
|
|
56
102
|
}
|
|
57
103
|
}
|
|
58
104
|
catch (error) {
|
|
59
|
-
console.warn(
|
|
105
|
+
console.warn("[context] Failed to fetch todos:", error);
|
|
60
106
|
}
|
|
61
107
|
}
|
|
62
108
|
// 2.6 Installed Agents
|
|
63
109
|
if (allAgents.length > 0) {
|
|
64
110
|
const formatted = allAgents
|
|
65
|
-
.map((a) => `- ${a.id}: ${a.name}${a.description ? ` - ${a.description}` :
|
|
66
|
-
.join(
|
|
67
|
-
sections.push(`## INSTALLED AGENTS\n${formatted}
|
|
111
|
+
.map((a) => `- ${a.id}: ${a.name}${a.description ? ` - ${a.description}` : ""}`)
|
|
112
|
+
.join("\n");
|
|
113
|
+
sections.push(`## INSTALLED AGENTS\n${formatted}\n\nAnswer questions about people from this list. Do not create a Space to inspect agents.`);
|
|
114
|
+
}
|
|
115
|
+
if (storage?.getChannels) {
|
|
116
|
+
try {
|
|
117
|
+
const spaces = (await storage.getChannels());
|
|
118
|
+
if (spaces.length > 0) {
|
|
119
|
+
const formatted = (await Promise.all(spaces.map(async (space) => {
|
|
120
|
+
let specHint = "";
|
|
121
|
+
if (storage.getChannelDetails) {
|
|
122
|
+
try {
|
|
123
|
+
const details = await storage.getChannelDetails({
|
|
124
|
+
channelId: space.id,
|
|
125
|
+
});
|
|
126
|
+
const spec = typeof details?.spec === "string"
|
|
127
|
+
? details.spec.trim()
|
|
128
|
+
: "";
|
|
129
|
+
const excerpt = spec
|
|
130
|
+
.split("\n")
|
|
131
|
+
.map((line) => line.trim())
|
|
132
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
133
|
+
.join(" ")
|
|
134
|
+
.slice(0, 140);
|
|
135
|
+
if (excerpt)
|
|
136
|
+
specHint = ` — ${excerpt}`;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// ignore spec lookup failures
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const inbox = space.id === GENERAL_SPACE_ID ? " [inbox]" : "";
|
|
143
|
+
return `- ${space.id}: ${space.name || space.id}${inbox}${specHint}`;
|
|
144
|
+
}))).join("\n");
|
|
145
|
+
sections.push(`## SPACES\n${formatted}\n\nAnswer questions about available Spaces from this list. Do not create a Space to look them up.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.warn("[context] Failed to fetch spaces:", error);
|
|
150
|
+
}
|
|
68
151
|
}
|
|
69
|
-
// 3.
|
|
152
|
+
// 3. Space spec
|
|
70
153
|
const spec = channelDetails?.spec?.trim();
|
|
71
154
|
if (spec) {
|
|
72
|
-
sections.push(`##
|
|
155
|
+
sections.push(`## SPACE SPEC\n${spec}`);
|
|
73
156
|
}
|
|
74
|
-
// 4. Files
|
|
75
|
-
if (storage?.listFiles && channelId && channelDetails?.cwd) {
|
|
157
|
+
// 4. Files (dedicated Spaces only — general is an inbox)
|
|
158
|
+
if (!isGeneral && storage?.listFiles && channelId && channelDetails?.cwd) {
|
|
76
159
|
try {
|
|
77
160
|
const files = await storage.listFiles({ channelId });
|
|
78
161
|
if (files.length > 0) {
|
|
79
162
|
const limited = files.slice(0, MAX_CONTEXT_FILES);
|
|
80
163
|
const formatted = limited
|
|
81
|
-
.map((f) => `- ${f.name}${f.isDirectory ?
|
|
82
|
-
.join(
|
|
164
|
+
.map((f) => `- ${f.name}${f.isDirectory ? "/" : ""}`)
|
|
165
|
+
.join("\n");
|
|
83
166
|
let fileSection = `## FILES\n${formatted}`;
|
|
84
167
|
if (files.length > MAX_CONTEXT_FILES) {
|
|
85
168
|
fileSection += `\n- ... and ${files.length - MAX_CONTEXT_FILES} more files`;
|
|
@@ -87,36 +170,35 @@ export async function buildContext(state, storage) {
|
|
|
87
170
|
sections.push(fileSection);
|
|
88
171
|
}
|
|
89
172
|
else {
|
|
90
|
-
sections.push(
|
|
173
|
+
sections.push("## FILES\n- (No files in workspace)");
|
|
91
174
|
}
|
|
92
175
|
}
|
|
93
176
|
catch (error) {
|
|
94
|
-
console.warn(
|
|
177
|
+
console.warn("[context] Failed to fetch files:", error);
|
|
95
178
|
}
|
|
96
179
|
}
|
|
97
180
|
// 5. Agent Instructions
|
|
98
181
|
const rawInstructions = agentDetails?.instructions?.trim();
|
|
99
|
-
if (rawInstructions &&
|
|
100
|
-
rawInstructions !== OPENBOT_SYSTEM_PROMPT.trim()) {
|
|
182
|
+
if (rawInstructions && rawInstructions !== OPENBOT_SYSTEM_PROMPT.trim()) {
|
|
101
183
|
sections.push(`## Instructions\n${rawInstructions}`);
|
|
102
184
|
}
|
|
103
185
|
// 6. Memories
|
|
104
186
|
if (storage?.listMemories) {
|
|
105
187
|
try {
|
|
106
|
-
const scopes = [
|
|
188
|
+
const scopes = ["global", `agent:${agentId}`];
|
|
107
189
|
if (channelId)
|
|
108
190
|
scopes.push(`channel:${channelId}`);
|
|
109
191
|
const records = await storage.listMemories({ scopes, limit: 20 });
|
|
110
192
|
if (records.length > 0) {
|
|
111
193
|
const formatted = records
|
|
112
194
|
.map((r) => `- (${r.scope}) ${r.content}`)
|
|
113
|
-
.join(
|
|
195
|
+
.join("\n");
|
|
114
196
|
sections.push(`## MEMORIES\n${formatted}`);
|
|
115
197
|
}
|
|
116
198
|
}
|
|
117
199
|
catch (error) {
|
|
118
|
-
console.warn(
|
|
200
|
+
console.warn("[context] Failed to fetch memories:", error);
|
|
119
201
|
}
|
|
120
202
|
}
|
|
121
|
-
return sections.join(
|
|
203
|
+
return sections.join("\n\n");
|
|
122
204
|
}
|
package/dist/history.js
CHANGED
|
@@ -89,8 +89,9 @@ export function eventsToModelMessages(events) {
|
|
|
89
89
|
case 'agent:invoke': {
|
|
90
90
|
const invokeEvent = event;
|
|
91
91
|
if (invokeEvent.data?.content && invokeEvent.data?.role) {
|
|
92
|
+
const role = invokeEvent.data.role === 'agent' ? 'user' : invokeEvent.data.role;
|
|
92
93
|
messages.push({
|
|
93
|
-
role
|
|
94
|
+
role,
|
|
94
95
|
content: invokeEvent.data.content,
|
|
95
96
|
});
|
|
96
97
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
|
-
import { defineOpenbotPlugin } from
|
|
2
|
-
import { isCloudMode } from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
1
|
+
import { defineOpenbotPlugin } from "./types.js";
|
|
2
|
+
import { isCloudMode } from "./cloud-mode.js";
|
|
3
|
+
import { AUTO_MODEL_ID } from "./auto-model.js";
|
|
4
|
+
import { resolveModelConfigField } from "./model-registry.js";
|
|
5
|
+
import { openbotRuntime } from "./runtime.js";
|
|
6
|
+
import { bashPlugin } from "./tools/bash.js";
|
|
7
|
+
import { memoryPlugin } from "./tools/memory.js";
|
|
8
|
+
import { todoPlugin } from "./tools/todo.js";
|
|
9
|
+
import { approvalPlugin } from "./tools/approval.js";
|
|
10
|
+
import { askAgentPlugin } from "./tools/ask-agent.js";
|
|
11
|
+
import { startWorkPlugin } from "./tools/start-work.js";
|
|
12
|
+
import { threadStatusPlugin } from "./tools/thread-status.js";
|
|
13
|
+
import { uiPlugin } from "./tools/ui.js";
|
|
14
|
+
import { previewPlugin } from "./tools/preview.js";
|
|
15
|
+
import { storageToolPlugin } from "./tools/storage.js";
|
|
16
|
+
export const OPENBOT_PLUGIN_ID = "@meetopenbot/openbot";
|
|
14
17
|
const modelField = await resolveModelConfigField();
|
|
18
|
+
const SPECIALIST_TOOL_NAMES = new Set([
|
|
19
|
+
...Object.keys(bashPlugin.toolDefinitions ?? {}),
|
|
20
|
+
...Object.keys(previewPlugin.toolDefinitions ?? {}),
|
|
21
|
+
]);
|
|
15
22
|
/**
|
|
16
23
|
* `@meetopenbot/openbot` — the standard, opinionated OpenBot agent runtime.
|
|
17
24
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
25
|
+
* The orchestrator (`system`) is a coordinator: ask agents, start work in
|
|
26
|
+
* Spaces, memory, todos, storage. Specialist tools (shell, preview) stay on
|
|
27
|
+
* non-orchestrator agents that opt into this runtime.
|
|
20
28
|
*/
|
|
21
29
|
export const openbotPlugin = defineOpenbotPlugin({
|
|
22
30
|
id: OPENBOT_PLUGIN_ID,
|
|
23
|
-
name:
|
|
24
|
-
description:
|
|
31
|
+
name: "OpenBot Agent",
|
|
32
|
+
description: "OpenBot coordinator runtime: ask specialists, route work into Spaces, and track todos.",
|
|
25
33
|
configSchema: {
|
|
26
|
-
type:
|
|
34
|
+
type: "object",
|
|
27
35
|
properties: {
|
|
28
36
|
...(isCloudMode()
|
|
29
37
|
? {
|
|
30
38
|
authMode: {
|
|
31
|
-
type:
|
|
32
|
-
description:
|
|
33
|
-
enum: [
|
|
34
|
-
default:
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Credits — use your workspace credit balance via OpenBot. BYOK — bring your own API key.",
|
|
41
|
+
enum: ["credits", "byok"],
|
|
42
|
+
default: "credits",
|
|
35
43
|
},
|
|
36
44
|
}
|
|
37
45
|
: {}),
|
|
@@ -43,31 +51,41 @@ export const openbotPlugin = defineOpenbotPlugin({
|
|
|
43
51
|
...memoryPlugin.toolDefinitions,
|
|
44
52
|
...todoPlugin.toolDefinitions,
|
|
45
53
|
...storageToolPlugin.toolDefinitions,
|
|
46
|
-
...
|
|
54
|
+
...askAgentPlugin.toolDefinitions,
|
|
55
|
+
...startWorkPlugin.toolDefinitions,
|
|
56
|
+
...threadStatusPlugin.toolDefinitions,
|
|
47
57
|
...previewPlugin.toolDefinitions,
|
|
48
58
|
},
|
|
49
59
|
factory: (context) => (builder) => {
|
|
50
60
|
const { agentId, config, storage, tools, abortSignal, host } = context;
|
|
51
|
-
|
|
61
|
+
const isOrchestrator = agentId === host.orchestratorAgentId;
|
|
52
62
|
memoryPlugin.factory(context)(builder);
|
|
53
63
|
todoPlugin.factory(context)(builder);
|
|
54
64
|
storageToolPlugin.register(context)(builder);
|
|
55
|
-
|
|
65
|
+
askAgentPlugin.factory(context)(builder);
|
|
66
|
+
startWorkPlugin.factory(context)(builder);
|
|
67
|
+
threadStatusPlugin.factory(context)(builder);
|
|
56
68
|
uiPlugin.factory(context)(builder);
|
|
57
|
-
|
|
69
|
+
if (!isOrchestrator) {
|
|
70
|
+
bashPlugin.factory(context)(builder);
|
|
71
|
+
previewPlugin.factory(context)(builder);
|
|
72
|
+
}
|
|
58
73
|
const approvalConfig = config?.approval ?? {
|
|
59
74
|
actions: [],
|
|
60
75
|
};
|
|
61
76
|
approvalPlugin.factory({ ...context, config: approvalConfig })(builder);
|
|
62
77
|
const authMode = host.isCloudSystemAgent(agentId)
|
|
63
78
|
? host.parseOpenbotAuthMode(config?.authMode)
|
|
64
|
-
:
|
|
79
|
+
: "byok";
|
|
80
|
+
const toolDefinitions = isOrchestrator
|
|
81
|
+
? Object.fromEntries(Object.entries(tools).filter(([name]) => !SPECIALIST_TOOL_NAMES.has(name)))
|
|
82
|
+
: tools;
|
|
65
83
|
return openbotRuntime({
|
|
66
|
-
model: config?.model,
|
|
84
|
+
model: config?.model || AUTO_MODEL_ID,
|
|
67
85
|
authMode,
|
|
68
86
|
agentId,
|
|
69
87
|
storage,
|
|
70
|
-
toolDefinitions
|
|
88
|
+
toolDefinitions,
|
|
71
89
|
abortSignal,
|
|
72
90
|
host,
|
|
73
91
|
})(builder);
|
package/dist/model-registry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AUTO_MODEL_ID, AUTO_MODEL_OPTION } from './auto-model.js';
|
|
1
2
|
const DEFAULT_REGISTRY_URL = 'https://raw.githubusercontent.com/meetopenbot/openbot-registry/main/registry.json';
|
|
2
3
|
const API_KEY_PROVIDER_IDS = ['openai', 'anthropic', 'google', 'deepseek'];
|
|
3
4
|
export const PROVIDER_API_KEY_LINKS = {
|
|
@@ -67,26 +68,26 @@ function listAllModelOptions(registry) {
|
|
|
67
68
|
}
|
|
68
69
|
return options;
|
|
69
70
|
}
|
|
71
|
+
function withAutoOption(options) {
|
|
72
|
+
if (options.some((option) => option.value === AUTO_MODEL_ID))
|
|
73
|
+
return options;
|
|
74
|
+
return [AUTO_MODEL_OPTION, ...options];
|
|
75
|
+
}
|
|
70
76
|
const freeInputModelField = () => ({
|
|
71
77
|
type: 'string',
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
default: 'openai/gpt-4o-mini',
|
|
78
|
+
description: 'Auto, or a provider model in provider/model-id format (e.g. openai/gpt-4o-mini).',
|
|
79
|
+
default: AUTO_MODEL_ID,
|
|
75
80
|
});
|
|
76
81
|
/** Registry-backed model field for plugin configSchema (`enum` + labeled `options`). */
|
|
77
82
|
export async function resolveModelConfigField() {
|
|
78
83
|
const registry = await fetchModelRegistry();
|
|
79
|
-
const options = listAllModelOptions(registry);
|
|
80
|
-
if (options.length ===
|
|
84
|
+
const options = withAutoOption(listAllModelOptions(registry));
|
|
85
|
+
if (options.length === 1)
|
|
81
86
|
return freeInputModelField();
|
|
82
|
-
const defaultModel = options.find((option) => option.value === 'openai/gpt-4o-mini')?.value ??
|
|
83
|
-
options.find((option) => option.value.startsWith('openai/'))?.value ??
|
|
84
|
-
options[0].value;
|
|
85
87
|
return {
|
|
86
88
|
type: 'string',
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
default: defaultModel,
|
|
89
|
+
description: 'Auto, or a model from the OpenBot registry.',
|
|
90
|
+
default: AUTO_MODEL_ID,
|
|
90
91
|
enum: options.map((option) => option.value),
|
|
91
92
|
options,
|
|
92
93
|
};
|
package/dist/model.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createOpenAI, openai as defaultOpenai } from '@ai-sdk/openai';
|
|
2
2
|
import { createAnthropic, anthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { expandModelString } from './auto-model.js';
|
|
3
4
|
import { CREDITS_API_KEY_PLACEHOLDER, INTEGRATIONS_TOKEN_HEADER, creditsProviderBaseUrl, resolveCreditsAuthConfig, shouldUseCreditsAuth, } from './credits-auth.js';
|
|
4
5
|
const defaultDeepseek = createOpenAI({
|
|
5
6
|
baseURL: 'https://api.deepseek.com',
|
|
@@ -9,10 +10,18 @@ const defaultGoogle = createOpenAI({
|
|
|
9
10
|
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
10
11
|
apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
|
|
11
12
|
});
|
|
12
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* OpenAI-compatible Chat Completions. Gemini/DeepSeek gateways only speak this.
|
|
15
|
+
* Do not use for OpenAI itself — gpt-5.6-luna rejects function tools on
|
|
16
|
+
* `/v1/chat/completions` unless `reasoning_effort` is `none`.
|
|
17
|
+
*/
|
|
13
18
|
function openAiChatModel(options, modelId) {
|
|
14
19
|
return createOpenAI(options).chat(modelId);
|
|
15
20
|
}
|
|
21
|
+
/** AI SDK 5+ default: OpenAI Responses API (`/v1/responses`), required for tools on Luna. */
|
|
22
|
+
function openAiResponsesModel(options, modelId) {
|
|
23
|
+
return createOpenAI(options)(modelId);
|
|
24
|
+
}
|
|
16
25
|
function resolveCreditsProvider(provider, modelId) {
|
|
17
26
|
const config = resolveCreditsAuthConfig();
|
|
18
27
|
if (!config) {
|
|
@@ -24,7 +33,7 @@ function resolveCreditsProvider(provider, modelId) {
|
|
|
24
33
|
const apiKey = config.token || CREDITS_API_KEY_PLACEHOLDER;
|
|
25
34
|
switch (provider) {
|
|
26
35
|
case 'openai':
|
|
27
|
-
return
|
|
36
|
+
return openAiResponsesModel({ baseURL, apiKey, headers }, modelId);
|
|
28
37
|
case 'anthropic':
|
|
29
38
|
return createAnthropic({ baseURL, apiKey, headers })(modelId);
|
|
30
39
|
case 'google':
|
|
@@ -34,7 +43,8 @@ function resolveCreditsProvider(provider, modelId) {
|
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
export function resolveModel(modelString, options) {
|
|
37
|
-
const
|
|
46
|
+
const concrete = expandModelString(modelString, options);
|
|
47
|
+
const [provider, ...rest] = concrete.split('/');
|
|
38
48
|
const modelId = rest.join('/');
|
|
39
49
|
if (!modelId) {
|
|
40
50
|
throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
|
|
@@ -42,7 +52,7 @@ export function resolveModel(modelString, options) {
|
|
|
42
52
|
const useCredits = shouldUseCreditsAuth(options);
|
|
43
53
|
switch (provider) {
|
|
44
54
|
case 'openai':
|
|
45
|
-
return useCredits ? resolveCreditsProvider('openai', modelId) : defaultOpenai
|
|
55
|
+
return useCredits ? resolveCreditsProvider('openai', modelId) : defaultOpenai(modelId);
|
|
46
56
|
case 'anthropic':
|
|
47
57
|
return useCredits ? resolveCreditsProvider('anthropic', modelId) : anthropic(modelId);
|
|
48
58
|
case 'google':
|