openbot 0.5.4 → 0.5.5

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 (48) hide show
  1. package/CLAUDE.md +5 -4
  2. package/deploy/README.md +2 -4
  3. package/dist/app/cli.js +3 -1
  4. package/dist/app/cloud-mode.js +5 -16
  5. package/dist/app/ensure-default-stack.js +54 -0
  6. package/dist/app/openbot-plugin.js +4 -0
  7. package/dist/app/server.js +2 -3
  8. package/dist/harness/index.js +3 -0
  9. package/dist/plugins/openbot/index.js +6 -5
  10. package/dist/plugins/openbot/model.js +2 -41
  11. package/dist/plugins/openbot/runtime.js +9 -4
  12. package/dist/plugins/storage/index.js +3 -325
  13. package/dist/plugins/storage/service.js +18 -21
  14. package/dist/services/plugins/host.js +21 -0
  15. package/dist/services/plugins/model-registry.js +34 -9
  16. package/dist/services/plugins/registry.js +26 -42
  17. package/dist/services/todo/types.js +1 -0
  18. package/docs/templates/AGENT.example.md +8 -14
  19. package/package.json +2 -2
  20. package/src/app/cli.ts +3 -1
  21. package/src/app/cloud-mode.ts +7 -22
  22. package/src/app/ensure-default-stack.ts +63 -0
  23. package/src/app/openbot-plugin.ts +5 -0
  24. package/src/app/server.ts +2 -5
  25. package/src/app/types.ts +3 -8
  26. package/src/harness/index.ts +4 -0
  27. package/src/plugins/storage/index.ts +3 -368
  28. package/src/plugins/storage/service.ts +17 -22
  29. package/src/services/plugins/host.ts +36 -0
  30. package/src/services/plugins/model-registry.ts +41 -9
  31. package/src/services/plugins/registry.ts +28 -43
  32. package/src/services/plugins/service.ts +6 -11
  33. package/src/services/plugins/types.ts +36 -2
  34. package/src/services/todo/types.ts +12 -0
  35. package/src/plugins/approval/index.ts +0 -147
  36. package/src/plugins/bash/index.ts +0 -545
  37. package/src/plugins/delegation/index.ts +0 -153
  38. package/src/plugins/memory/index.ts +0 -182
  39. package/src/plugins/openbot/context.ts +0 -137
  40. package/src/plugins/openbot/history.ts +0 -158
  41. package/src/plugins/openbot/index.ts +0 -95
  42. package/src/plugins/openbot/model.ts +0 -76
  43. package/src/plugins/openbot/runtime.ts +0 -504
  44. package/src/plugins/openbot/system-prompt.ts +0 -57
  45. package/src/plugins/preview/index.ts +0 -323
  46. package/src/plugins/todo/index.ts +0 -166
  47. package/src/plugins/todo/service.ts +0 -123
  48. package/src/plugins/ui/index.ts +0 -130
@@ -1,182 +0,0 @@
1
- import z from 'zod';
2
- import type { Plugin } from '../../services/plugins/types.js';
3
- import { OpenBotEvent, MemoryScopeAlias } from '../../app/types.js';
4
-
5
- /**
6
- * Resolve a scope alias to a concrete scope string. Aliases let tools accept
7
- * `agent`/`channel`/`global` without knowing the active ids; the bus rewrites
8
- * them using `context.state`.
9
- */
10
- function resolveMemoryScope(
11
- alias: MemoryScopeAlias | undefined,
12
- state: any,
13
- ): string {
14
- switch (alias) {
15
- case 'agent':
16
- return `agent:${state.agentId}`;
17
- case 'channel':
18
- return `channel:${state.channelId}`;
19
- case 'global':
20
- case undefined:
21
- return 'global';
22
- default:
23
- return 'global';
24
- }
25
- }
26
-
27
- function resolveMemoryScopeFilter(
28
- alias: MemoryScopeAlias | 'all' | undefined,
29
- state: any,
30
- ): string[] | undefined {
31
- if (alias === 'all' || alias === undefined) {
32
- return ['global', `agent:${state.agentId}`, `channel:${state.channelId}`];
33
- }
34
- return [resolveMemoryScope(alias, state)];
35
- }
36
-
37
- /**
38
- * `memory` — exposes the global memory store as agent tools and provides
39
- * platform-level memory handlers.
40
- */
41
- const memoryToolDefinitions = {
42
- remember: {
43
- description:
44
- 'Persist a durable fact, preference, or note to long-term memory so it can be recalled in future turns and runs. Use for stable information (user preferences, project conventions, contact details, decisions); avoid using it for transient chatter or per-step scratch state — that belongs in thread state. Keep entries short and self-contained.',
45
- inputSchema: z.object({
46
- content: z
47
- .string()
48
- .min(1)
49
- .describe(
50
- 'The fact to remember, written so it makes sense out of context (e.g. "User prefers TypeScript over JavaScript.").',
51
- ),
52
- scope: z
53
- .enum(['global', 'agent', 'channel'])
54
- .optional()
55
- .describe(
56
- 'Visibility: `global` (default, all agents everywhere), `agent` (only this agent), `channel` (only this channel).',
57
- ),
58
- tags: z
59
- .array(z.string())
60
- .optional()
61
- .describe('Optional tags for filtering with `recall`.'),
62
- }),
63
- },
64
- recall: {
65
- description:
66
- 'Search long-term memory for facts you previously stored with `remember`. Returns up to `limit` matching records with their ids so you can `forget` stale ones.',
67
- inputSchema: z.object({
68
- query: z
69
- .string()
70
- .optional()
71
- .describe('Case-insensitive substring filter against memory content.'),
72
- tag: z.string().optional().describe('Only return memories that include this tag.'),
73
- scope: z
74
- .enum(['global', 'agent', 'channel', 'all'])
75
- .optional()
76
- .describe(
77
- 'Restrict the search to a single scope. Default `all` returns global + this agent + this channel.',
78
- ),
79
- limit: z
80
- .number()
81
- .int()
82
- .positive()
83
- .max(50)
84
- .optional()
85
- .describe('Maximum records to return (default 20, max 50).'),
86
- }),
87
- },
88
- forget: {
89
- description:
90
- 'Delete a memory by id. Use after the user asks to forget something or when a previously remembered fact is now wrong. Get ids from `recall`.',
91
- inputSchema: z.object({
92
- id: z.string().describe('The memory record id (returned by `recall`/`remember`).'),
93
- }),
94
- },
95
- };
96
-
97
- export const memoryPlugin: Plugin = {
98
- id: 'memory',
99
- name: 'Memory',
100
- description:
101
- 'Global long-term memory: remember/recall/forget facts across runs and agents.',
102
- toolDefinitions: memoryToolDefinitions,
103
- factory: ({ storage }) => (builder) => {
104
- builder.on('action:remember', async function* (event, context) {
105
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
106
- try {
107
- const { content, scope, tags } = event.data;
108
- const record = await storage.appendMemory({
109
- scope: resolveMemoryScope(scope, context.state),
110
- content,
111
- tags,
112
- });
113
- yield {
114
- type: 'action:remember:result',
115
- data: { success: true, record },
116
- meta: resultMeta,
117
- } as OpenBotEvent;
118
- } catch (error) {
119
- yield {
120
- type: 'action:remember:result',
121
- data: {
122
- success: false,
123
- error: error instanceof Error ? error.message : 'Unknown error',
124
- },
125
- meta: resultMeta,
126
- } as OpenBotEvent;
127
- }
128
- });
129
-
130
- builder.on('action:recall', async function* (event, context) {
131
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
132
- try {
133
- const { query, tag, scope, limit } = event.data;
134
- const records = await storage.listMemories({
135
- scopes: resolveMemoryScopeFilter(scope, context.state),
136
- query,
137
- tag,
138
- limit,
139
- });
140
- yield {
141
- type: 'action:recall:result',
142
- data: { success: true, records },
143
- meta: resultMeta,
144
- } as OpenBotEvent;
145
- } catch (error) {
146
- yield {
147
- type: 'action:recall:result',
148
- data: {
149
- success: false,
150
- records: [],
151
- error: error instanceof Error ? error.message : 'Unknown error',
152
- },
153
- meta: resultMeta,
154
- } as OpenBotEvent;
155
- }
156
- });
157
-
158
- builder.on('action:forget', async function* (event, context) {
159
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
160
- try {
161
- const deleted = await storage.deleteMemory({ id: event.data.id });
162
- yield {
163
- type: 'action:forget:result',
164
- data: { success: true, deleted },
165
- meta: resultMeta,
166
- } as OpenBotEvent;
167
- } catch (error) {
168
- yield {
169
- type: 'action:forget:result',
170
- data: {
171
- success: false,
172
- deleted: false,
173
- error: error instanceof Error ? error.message : 'Unknown error',
174
- },
175
- meta: resultMeta,
176
- } as OpenBotEvent;
177
- }
178
- });
179
- },
180
- };
181
-
182
- export default memoryPlugin;
@@ -1,137 +0,0 @@
1
- import { OpenBotState } from '../../app/types.js';
2
- import { Storage } from '../../services/plugins/domain.js';
3
- import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
4
- import { todoService } from '../todo/service.js';
5
-
6
- export const DEFAULT_CONTEXT_BUDGET = 8000;
7
- export const MAX_CONTEXT_FILES = 50;
8
-
9
- /**
10
- * Returns the known context window budget (in tokens) for a given model string.
11
- */
12
- export const getContextBudgetForModel = (modelString: string): number => {
13
- const budgets: Record<string, number> = {
14
- 'openai/gpt-4o': 128000,
15
- 'openai/gpt-4o-mini': 128000,
16
- 'openai/o1-preview': 128000,
17
- 'openai/o1-mini': 128000,
18
- 'anthropic/claude-3-5-sonnet-20240620': 200000,
19
- 'anthropic/claude-3-5-sonnet-latest': 200000,
20
- 'anthropic/claude-3-opus-20240229': 200000,
21
- 'anthropic/claude-3-sonnet-20240229': 200000,
22
- 'anthropic/claude-3-haiku-20240307': 200000,
23
- };
24
-
25
- return budgets[modelString] || DEFAULT_CONTEXT_BUDGET;
26
- };
27
-
28
- /** Built-in orchestrator agent id. */
29
- export const ORCHESTRATOR_AGENT_ID = 'system';
30
-
31
- /**
32
- * Simplified context builder for MVP.
33
- */
34
- export async function buildContext(state: OpenBotState, storage?: Storage): Promise<string> {
35
- const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
36
-
37
- const sections: string[] = [];
38
-
39
- // Fetch agents once if storage is available
40
- const allAgents = storage?.getAgents ? await storage.getAgents().catch(() => []) : [];
41
-
42
- // 1. User
43
- if (state.currentUser?.userName) {
44
- sections.push(`## HUMAN\n- Name: ${state.currentUser.userName}`);
45
- }
46
-
47
- // 2. Environment
48
- let env = '## ENVIRONMENT\n';
49
- const channelName = channelDetails?.name || channelId;
50
- env += `- Mode: Channel (#${channelName})\n`;
51
- if (channelDetails?.cwd) {
52
- env += `- Workspace: ${channelDetails.cwd}\n`;
53
- }
54
- if (threadId) {
55
- env += `- Thread: ${threadDetails?.name || threadId}\n`;
56
- }
57
- sections.push(env);
58
-
59
- // 2.5 Thread todos
60
- if (channelId && threadId) {
61
- try {
62
- const list = await todoService.getTodos({ channelId, threadId });
63
- if (list.items.length > 0) {
64
- const formatted = list.items
65
- .map((t) => `- [${t.status}] (${t.id}) ${t.content}`)
66
- .join('\n');
67
- sections.push(`## TODOS\n${formatted}`);
68
- }
69
- } catch (error) {
70
- console.warn('[context] Failed to fetch todos:', error);
71
- }
72
- }
73
-
74
- // 2.6 Installed Agents
75
- if (allAgents.length > 0) {
76
- const formatted = allAgents
77
- .map((a) => `- ${a.id}: ${a.name}${a.description ? ` - ${a.description}` : ''}`)
78
- .join('\n');
79
- sections.push(`## INSTALLED AGENTS\n${formatted}`);
80
- }
81
-
82
- // 3. Channel Spec
83
- const spec = channelDetails?.spec?.trim();
84
- if (spec) {
85
- sections.push(`## CHANNEL SPECIFICATION\n${spec}`);
86
- }
87
-
88
- // 4. Files
89
- if (storage?.listFiles && channelId && channelDetails?.cwd) {
90
- try {
91
- const files = await storage.listFiles({ channelId });
92
- if (files.length > 0) {
93
- const limited = files.slice(0, MAX_CONTEXT_FILES);
94
- const formatted = limited
95
- .map((f) => `- ${f.name}${f.isDirectory ? '/' : ''}`)
96
- .join('\n');
97
- let fileSection = `## FILES\n${formatted}`;
98
- if (files.length > MAX_CONTEXT_FILES) {
99
- fileSection += `\n- ... and ${files.length - MAX_CONTEXT_FILES} more files`;
100
- }
101
- sections.push(fileSection);
102
- } else {
103
- sections.push('## FILES\n- (No files in workspace)');
104
- }
105
- } catch (error) {
106
- console.warn('[context] Failed to fetch files:', error);
107
- }
108
- }
109
-
110
- // 5. Agent Instructions
111
- const rawInstructions = agentDetails?.instructions?.trim();
112
- if (
113
- rawInstructions &&
114
- rawInstructions !== OPENBOT_SYSTEM_PROMPT.trim()
115
- ) {
116
- sections.push(`## Instructions\n${rawInstructions}`);
117
- }
118
-
119
- // 6. Memories
120
- if (storage?.listMemories) {
121
- try {
122
- const scopes = ['global', `agent:${agentId}`];
123
- if (channelId) scopes.push(`channel:${channelId}`);
124
- const records = await storage.listMemories({ scopes, limit: 20 });
125
- if (records.length > 0) {
126
- const formatted = records
127
- .map((r: any) => `- (${r.scope}) ${r.content}`)
128
- .join('\n');
129
- sections.push(`## MEMORIES\n${formatted}`);
130
- }
131
- } catch (error) {
132
- console.warn('[context] Failed to fetch memories:', error);
133
- }
134
- }
135
-
136
- return sections.join('\n\n');
137
- }
@@ -1,158 +0,0 @@
1
- import { OpenBotEvent } from '../../app/types.js';
2
- import { ToolResultPart, type ModelMessage } from 'ai';
3
-
4
- /**
5
- * Ensures every tool-call has a matching tool-result before calling the LLM.
6
- * Orphaned calls (interrupted run, missing :result event, etc.) get an empty
7
- * result so the conversation can resume instead of failing validation.
8
- */
9
- function fillMissingToolResults(messages: ModelMessage[]): ModelMessage[] {
10
- const filled: ModelMessage[] = [];
11
- const pending = new Map<string, string>();
12
-
13
- const flushPending = () => {
14
- if (pending.size === 0) return;
15
- filled.push({
16
- role: 'tool',
17
- content: [...pending.entries()].map(([toolCallId, toolName]) => ({
18
- type: 'tool-result' as const,
19
- toolCallId,
20
- toolName,
21
- output: { type: 'text' as const, value: '' },
22
- })),
23
- });
24
- pending.clear();
25
- };
26
-
27
- for (const message of messages) {
28
- if (message.role === 'tool' && Array.isArray(message.content)) {
29
- filled.push(message);
30
- for (const part of message.content) {
31
- if ((part as ToolResultPart).type === 'tool-result') {
32
- pending.delete((part as ToolResultPart).toolCallId);
33
- }
34
- }
35
- continue;
36
- }
37
-
38
- flushPending();
39
- filled.push(message);
40
-
41
- if (message.role === 'assistant' && Array.isArray(message.content)) {
42
- for (const part of message.content) {
43
- if ((part as { type?: string; toolCallId?: string; toolName?: string }).type === 'tool-call') {
44
- const toolCall = part as { toolCallId: string; toolName: string };
45
- pending.set(toolCall.toolCallId, toolCall.toolName);
46
- }
47
- }
48
- }
49
- }
50
-
51
- flushPending();
52
- return filled;
53
- }
54
-
55
- /**
56
- * Converts a raw event log into a valid chain of ModelMessages for the AI SDK.
57
- *
58
- * This is a basic implementation that maps events to messages and filters out
59
- * events from sub-processes (delegation) to avoid duplication in history.
60
- */
61
- export function eventsToModelMessages(events: OpenBotEvent[]): ModelMessage[] {
62
- const messages: ModelMessage[] = [];
63
-
64
- for (const event of events) {
65
- // Skip events that belong to a sub-process (like delegation)
66
- // so they don't pollute the main conversation history.
67
- if (event.meta?.parentToolCallId) {
68
- continue;
69
- }
70
-
71
- switch (event.type) {
72
- case 'agent:output': {
73
- const last = messages[messages.length - 1];
74
- if (last && last.role === 'assistant' && typeof last.content === 'string') {
75
- last.content += event.data.content;
76
- } else {
77
- messages.push({ role: 'assistant', content: event.data.content });
78
- }
79
- break;
80
- }
81
-
82
- case 'agent:invoke': {
83
- const invokeEvent = event as any;
84
- if (invokeEvent.data?.content && invokeEvent.data?.role) {
85
- messages.push({
86
- role: invokeEvent.data.role,
87
- content: invokeEvent.data.content
88
- } as ModelMessage);
89
- }
90
- break;
91
- }
92
-
93
- default:
94
- // Handle tool calls (action:*)
95
- if (event.type.startsWith('action:') && !event.type.endsWith(':result')) {
96
-
97
-
98
- const toolName = event.type.slice(7);
99
- const toolCallId = event.meta?.toolCallId;
100
- if (!toolCallId) break;
101
-
102
- const toolCall = {
103
- type: 'tool-call' as const,
104
- toolCallId,
105
- toolName,
106
- input: (event as any).data,
107
- };
108
-
109
- const last = messages[messages.length - 1];
110
-
111
- if (last && last.role === 'assistant') {
112
- if (typeof last.content === 'string') {
113
- last.content = [
114
- { type: 'text', text: last.content },
115
- toolCall,
116
- ];
117
- } else if (Array.isArray(last.content)) {
118
- (last.content as any[]).push(toolCall);
119
- }
120
- } else {
121
- messages.push({
122
- role: 'assistant',
123
- content: [toolCall],
124
- });
125
- }
126
- }
127
- // Handle tool results (action:*:result)
128
- else if (event.type.startsWith('action:') && event.type.endsWith(':result')) {
129
- const toolName = event.type.slice(7, -7);
130
- const toolCallId = event.meta?.toolCallId;
131
- if (!toolCallId) break;
132
-
133
- const toolResult: ToolResultPart = {
134
- type: 'tool-result' as const,
135
- toolCallId,
136
- toolName,
137
- output: {
138
- type: 'text',
139
- value: (event as any)?.data?.output || "No output", // ?.output is from delegation result
140
- },
141
- };
142
-
143
- const last = messages[messages.length - 1];
144
- if (last && last.role === 'tool' && Array.isArray(last.content)) {
145
- (last.content as any[]).push(toolResult);
146
- } else {
147
- messages.push({
148
- role: 'tool',
149
- content: [toolResult],
150
- });
151
- }
152
- }
153
- break;
154
- }
155
- }
156
-
157
- return fillMissingToolResults(messages);
158
- }
@@ -1,95 +0,0 @@
1
- import type { Plugin } from '../../services/plugins/types.js';
2
- import { isCloudSystemAgent, resolveCloudSystemModel } from '../../app/cloud-mode.js';
3
- import { openbotRuntime } from './runtime.js';
4
- import { bashPlugin } from '../bash/index.js';
5
- import { memoryPlugin } from '../memory/index.js';
6
- import { todoPlugin } from '../todo/index.js';
7
- import { approvalPlugin } from '../approval/index.js';
8
- import { storagePlugin } from '../storage/index.js';
9
- import { delegationPlugin } from '../delegation/index.js';
10
- import { uiPlugin } from '../ui/index.js';
11
- import { previewPlugin } from '../preview/index.js';
12
-
13
- /**
14
- * `openbot` — the standard, opinionated OpenBot agent runtime.
15
- *
16
- * This is the canonical execution loop for OpenBot agents. It handles
17
- * `agent:invoke`, manages short-term memory, assembles context, and
18
- * orchestrates tool calls.
19
- *
20
- * It comes with a "batteries-included" set of inbuilt tools: shell, memory,
21
- * todo, storage, delegation, and approval.
22
- */
23
- export const openbotPlugin: Plugin = {
24
- id: 'openbot',
25
- name: 'OpenBot Agent',
26
- description:
27
- 'The standard OpenBot agent runtime with inbuilt tools (shell, memory, todo, storage, delegation, and approval).',
28
- configSchema: {
29
- type: 'object',
30
- properties: {
31
- model: {
32
- type: 'string',
33
- description: 'Model from the hosted marketplace registry.',
34
- },
35
- approval: {
36
- type: 'object',
37
- description: 'Configuration for the inbuilt approval plugin.',
38
- properties: {
39
- actions: {
40
- type: 'array',
41
- items: { type: 'string' },
42
- description: 'List of actions that require manual approval.',
43
- },
44
- },
45
- },
46
- },
47
- },
48
- toolDefinitions: {
49
- ...bashPlugin.toolDefinitions,
50
- ...memoryPlugin.toolDefinitions,
51
- ...todoPlugin.toolDefinitions,
52
- ...storagePlugin.toolDefinitions,
53
- ...delegationPlugin.toolDefinitions,
54
- ...previewPlugin.toolDefinitions,
55
- // this is the capability to render UI widgets to the user. We dont need it for now.
56
- // ...uiPlugin.toolDefinitions,
57
- },
58
- factory: (context) => (builder) => {
59
- const { agentId, config, storage, tools, abortSignal } = context;
60
-
61
- // Register inbuilt plugins
62
- bashPlugin.factory(context)(builder);
63
- memoryPlugin.factory(context)(builder);
64
- todoPlugin.factory(context)(builder);
65
- storagePlugin.factory(context)(builder);
66
- delegationPlugin.factory(context)(builder);
67
- uiPlugin.factory(context)(builder);
68
- previewPlugin.factory(context)(builder);
69
-
70
- // Approval plugin configuration
71
- const approvalConfig = (config?.approval as any) || {
72
- actions: [
73
- 'action:shell_exec',
74
- 'action:expose_port',
75
- 'action:create_channel',
76
- 'action:delete_channel',
77
- ],
78
- };
79
- approvalPlugin.factory({ ...context, config: approvalConfig })(builder);
80
-
81
- const model = isCloudSystemAgent(agentId)
82
- ? resolveCloudSystemModel(config?.model as string | undefined)
83
- : (config?.model as string);
84
-
85
- return openbotRuntime({
86
- model,
87
- agentId,
88
- storage,
89
- toolDefinitions: tools,
90
- abortSignal,
91
- })(builder);
92
- },
93
- };
94
-
95
- export default openbotPlugin;
@@ -1,76 +0,0 @@
1
- import { createOpenAI, openai as defaultOpenai } from '@ai-sdk/openai';
2
- import { anthropic } from '@ai-sdk/anthropic';
3
- import { google } from '@ai-sdk/google';
4
- import type { LanguageModel } from 'ai';
5
- import {
6
- getCloudIntegrationsConfig,
7
- isCloudSystemAgent,
8
- } from '../../app/cloud-mode.js';
9
-
10
- let cloudOpenAIProvider: ReturnType<typeof createOpenAI> | null | undefined;
11
-
12
- /** Strip credentials — the integrations gateway injects the provider master key. */
13
- const integrationsFetch: typeof fetch = async (input, init) => {
14
- const headers = new Headers(init?.headers);
15
- headers.delete('Authorization');
16
- headers.delete('x-api-key');
17
- return globalThis.fetch(input, { ...init, headers });
18
- };
19
-
20
- function getCloudOpenAIProvider(): ReturnType<typeof createOpenAI> | null {
21
- if (cloudOpenAIProvider !== undefined) return cloudOpenAIProvider;
22
-
23
- const config = getCloudIntegrationsConfig();
24
- if (!config) {
25
- cloudOpenAIProvider = null;
26
- return null;
27
- }
28
-
29
- cloudOpenAIProvider = createOpenAI({
30
- baseURL: `${config.baseUrl}/openai/v1`,
31
- apiKey: 'unused',
32
- headers: {
33
- 'x-openbot-integrations-token': config.token,
34
- },
35
- fetch: integrationsFetch,
36
- });
37
- return cloudOpenAIProvider;
38
- }
39
-
40
- function resolveOpenbotModel(modelId: string, agentId?: string): LanguageModel {
41
- if (!agentId || !isCloudSystemAgent(agentId)) {
42
- throw new Error(
43
- `OpenBot models are only available for the cloud system agent. Use openai/..., anthropic/..., etc.`,
44
- );
45
- }
46
-
47
- const cloudOpenai = getCloudOpenAIProvider();
48
- if (!cloudOpenai) {
49
- throw new Error(
50
- 'OpenBot cloud model requires OPENBOT_INTEGRATIONS_BASE_URL and OPENBOT_INTEGRATIONS_TOKEN.',
51
- );
52
- }
53
-
54
- return cloudOpenai.chat(modelId);
55
- }
56
-
57
- export function resolveModel(modelString: string, agentId?: string): LanguageModel {
58
- const [provider, ...rest] = modelString.split('/');
59
- const modelId = rest.join('/');
60
- if (!modelId) {
61
- throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
62
- }
63
-
64
- switch (provider) {
65
- case 'openbot':
66
- return resolveOpenbotModel(modelId, agentId);
67
- case 'openai':
68
- return defaultOpenai(modelId);
69
- case 'anthropic':
70
- return anthropic(modelId);
71
- case 'google':
72
- return google(modelId);
73
- default:
74
- throw new Error(`Unsupported AI provider: "${provider}"`);
75
- }
76
- }