@meetopenbot/openbot 0.1.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.
@@ -0,0 +1,5 @@
1
+ export type OpenbotAuthMode = 'credits' | 'byok';
2
+ /** True when this runtime is a platform-managed cloud deployment. */
3
+ export declare const isCloudMode: () => boolean;
4
+ /** Default auth mode: Credits on cloud, BYOK locally. */
5
+ export declare const defaultAuthMode: () => OpenbotAuthMode;
@@ -0,0 +1,4 @@
1
+ /** True when this runtime is a platform-managed cloud deployment. */
2
+ export const isCloudMode = () => process.env.OPENBOT_CLOUD_MODE === '1';
3
+ /** Default auth mode: Credits on cloud, BYOK locally. */
4
+ export const defaultAuthMode = () => (isCloudMode() ? 'credits' : 'byok');
@@ -0,0 +1,11 @@
1
+ import { OpenBotState } from './types.js';
2
+ export declare const DEFAULT_CONTEXT_BUDGET = 8000;
3
+ export declare const MAX_CONTEXT_FILES = 50;
4
+ /**
5
+ * Returns the known context window budget (in tokens) for a given model string.
6
+ */
7
+ export declare const getContextBudgetForModel: (modelString: string) => number;
8
+ /**
9
+ * Simplified context builder for MVP.
10
+ */
11
+ export declare function buildContext(state: OpenBotState, storage?: Record<string, any>): Promise<string>;
@@ -0,0 +1,120 @@
1
+ import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
2
+ import { todoService } from './tools/todo-service.js';
3
+ export const DEFAULT_CONTEXT_BUDGET = 8000;
4
+ export const MAX_CONTEXT_FILES = 50;
5
+ /**
6
+ * Returns the known context window budget (in tokens) for a given model string.
7
+ */
8
+ export const getContextBudgetForModel = (modelString) => {
9
+ const budgets = {
10
+ 'openai/gpt-4o': 128000,
11
+ 'openai/gpt-4o-mini': 128000,
12
+ 'openai/o1-preview': 128000,
13
+ 'openai/o1-mini': 128000,
14
+ 'anthropic/claude-3-5-sonnet-20240620': 200000,
15
+ 'anthropic/claude-3-5-sonnet-latest': 200000,
16
+ 'anthropic/claude-3-opus-20240229': 200000,
17
+ 'anthropic/claude-3-sonnet-20240229': 200000,
18
+ 'anthropic/claude-3-haiku-20240307': 200000,
19
+ };
20
+ return budgets[modelString] || DEFAULT_CONTEXT_BUDGET;
21
+ };
22
+ /**
23
+ * Simplified context builder for MVP.
24
+ */
25
+ export async function buildContext(state, storage) {
26
+ const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
27
+ const sections = [];
28
+ // Fetch agents once if storage is available
29
+ const allAgents = storage?.getAgents ? await storage.getAgents().catch(() => []) : [];
30
+ // 1. User
31
+ if (state.currentUser?.userName) {
32
+ sections.push(`## HUMAN\n- Name: ${state.currentUser.userName}`);
33
+ }
34
+ // 2. Environment
35
+ let env = '## ENVIRONMENT\n';
36
+ const channelName = channelDetails?.name || channelId;
37
+ env += `- Mode: Channel (#${channelName})\n`;
38
+ if (channelDetails?.cwd) {
39
+ env += `- Workspace: ${channelDetails.cwd}\n`;
40
+ }
41
+ if (threadId) {
42
+ env += `- Thread: ${threadDetails?.name || threadId}\n`;
43
+ }
44
+ sections.push(env);
45
+ // 2.5 Thread todos
46
+ if (channelId && threadId) {
47
+ try {
48
+ const list = await todoService.getTodos({ channelId, threadId });
49
+ if (list.items.length > 0) {
50
+ const formatted = list.items
51
+ .map((t) => `- [${t.status}] (${t.id}) ${t.content}`)
52
+ .join('\n');
53
+ sections.push(`## TODOS\n${formatted}`);
54
+ }
55
+ }
56
+ catch (error) {
57
+ console.warn('[context] Failed to fetch todos:', error);
58
+ }
59
+ }
60
+ // 2.6 Installed Agents
61
+ if (allAgents.length > 0) {
62
+ const formatted = allAgents
63
+ .map((a) => `- ${a.id}: ${a.name}${a.description ? ` - ${a.description}` : ''}`)
64
+ .join('\n');
65
+ sections.push(`## INSTALLED AGENTS\n${formatted}`);
66
+ }
67
+ // 3. Channel Spec
68
+ const spec = channelDetails?.spec?.trim();
69
+ if (spec) {
70
+ sections.push(`## CHANNEL SPECIFICATION\n${spec}`);
71
+ }
72
+ // 4. Files
73
+ if (storage?.listFiles && channelId && channelDetails?.cwd) {
74
+ try {
75
+ const files = await storage.listFiles({ channelId });
76
+ if (files.length > 0) {
77
+ const limited = files.slice(0, MAX_CONTEXT_FILES);
78
+ const formatted = limited
79
+ .map((f) => `- ${f.name}${f.isDirectory ? '/' : ''}`)
80
+ .join('\n');
81
+ let fileSection = `## FILES\n${formatted}`;
82
+ if (files.length > MAX_CONTEXT_FILES) {
83
+ fileSection += `\n- ... and ${files.length - MAX_CONTEXT_FILES} more files`;
84
+ }
85
+ sections.push(fileSection);
86
+ }
87
+ else {
88
+ sections.push('## FILES\n- (No files in workspace)');
89
+ }
90
+ }
91
+ catch (error) {
92
+ console.warn('[context] Failed to fetch files:', error);
93
+ }
94
+ }
95
+ // 5. Agent Instructions
96
+ const rawInstructions = agentDetails?.instructions?.trim();
97
+ if (rawInstructions &&
98
+ rawInstructions !== OPENBOT_SYSTEM_PROMPT.trim()) {
99
+ sections.push(`## Instructions\n${rawInstructions}`);
100
+ }
101
+ // 6. Memories
102
+ if (storage?.listMemories) {
103
+ try {
104
+ const scopes = ['global', `agent:${agentId}`];
105
+ if (channelId)
106
+ scopes.push(`channel:${channelId}`);
107
+ const records = await storage.listMemories({ scopes, limit: 20 });
108
+ if (records.length > 0) {
109
+ const formatted = records
110
+ .map((r) => `- (${r.scope}) ${r.content}`)
111
+ .join('\n');
112
+ sections.push(`## MEMORIES\n${formatted}`);
113
+ }
114
+ }
115
+ catch (error) {
116
+ console.warn('[context] Failed to fetch memories:', error);
117
+ }
118
+ }
119
+ return sections.join('\n\n');
120
+ }
@@ -0,0 +1,9 @@
1
+ import { OpenBotEvent } from './types.js';
2
+ import { type ModelMessage } from 'ai';
3
+ /**
4
+ * Converts a raw event log into a valid chain of ModelMessages for the AI SDK.
5
+ *
6
+ * This is a basic implementation that maps events to messages and filters out
7
+ * events from sub-processes (delegation) to avoid duplication in history.
8
+ */
9
+ export declare function eventsToModelMessages(events: OpenBotEvent[]): ModelMessage[];
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Ensures every tool-call has a matching tool-result before calling the LLM.
3
+ * Orphaned calls (interrupted run, missing :result event, etc.) get an empty
4
+ * result so the conversation can resume instead of failing validation.
5
+ */
6
+ function fillMissingToolResults(messages) {
7
+ const filled = [];
8
+ const pending = new Map();
9
+ const flushPending = () => {
10
+ if (pending.size === 0)
11
+ return;
12
+ filled.push({
13
+ role: 'tool',
14
+ content: [...pending.entries()].map(([toolCallId, toolName]) => ({
15
+ type: 'tool-result',
16
+ toolCallId,
17
+ toolName,
18
+ output: { type: 'text', value: '' },
19
+ })),
20
+ });
21
+ pending.clear();
22
+ };
23
+ for (const message of messages) {
24
+ if (message.role === 'tool' && Array.isArray(message.content)) {
25
+ filled.push(message);
26
+ for (const part of message.content) {
27
+ if (part.type === 'tool-result') {
28
+ pending.delete(part.toolCallId);
29
+ }
30
+ }
31
+ continue;
32
+ }
33
+ flushPending();
34
+ filled.push(message);
35
+ if (message.role === 'assistant' && Array.isArray(message.content)) {
36
+ for (const part of message.content) {
37
+ if (part.type === 'tool-call') {
38
+ const toolCall = part;
39
+ pending.set(toolCall.toolCallId, toolCall.toolName);
40
+ }
41
+ }
42
+ }
43
+ }
44
+ flushPending();
45
+ return filled;
46
+ }
47
+ /**
48
+ * Converts a raw event log into a valid chain of ModelMessages for the AI SDK.
49
+ *
50
+ * This is a basic implementation that maps events to messages and filters out
51
+ * events from sub-processes (delegation) to avoid duplication in history.
52
+ */
53
+ export function eventsToModelMessages(events) {
54
+ const messages = [];
55
+ for (const event of events) {
56
+ // Skip events that belong to a sub-process (like delegation)
57
+ // so they don't pollute the main conversation history.
58
+ if (event.meta?.parentToolCallId) {
59
+ continue;
60
+ }
61
+ switch (event.type) {
62
+ case 'agent:output': {
63
+ const content = event.data.content;
64
+ const last = messages[messages.length - 1];
65
+ if (last && last.role === 'assistant' && typeof last.content === 'string') {
66
+ last.content += content;
67
+ }
68
+ else {
69
+ messages.push({ role: 'assistant', content });
70
+ }
71
+ break;
72
+ }
73
+ case 'agent:invoke': {
74
+ const invokeEvent = event;
75
+ if (invokeEvent.data?.content && invokeEvent.data?.role) {
76
+ messages.push({
77
+ role: invokeEvent.data.role,
78
+ content: invokeEvent.data.content
79
+ });
80
+ }
81
+ break;
82
+ }
83
+ default:
84
+ // Handle tool calls (action:*)
85
+ if (event.type.startsWith('action:') && !event.type.endsWith(':result')) {
86
+ const toolName = event.type.slice(7);
87
+ const toolCallId = event.meta?.toolCallId;
88
+ if (typeof toolCallId !== 'string')
89
+ break;
90
+ const toolCall = {
91
+ type: 'tool-call',
92
+ toolCallId,
93
+ toolName,
94
+ input: event.data,
95
+ };
96
+ const last = messages[messages.length - 1];
97
+ if (last && last.role === 'assistant') {
98
+ if (typeof last.content === 'string') {
99
+ last.content = [
100
+ { type: 'text', text: last.content },
101
+ toolCall,
102
+ ];
103
+ }
104
+ else if (Array.isArray(last.content)) {
105
+ last.content.push(toolCall);
106
+ }
107
+ }
108
+ else {
109
+ messages.push({
110
+ role: 'assistant',
111
+ content: [toolCall],
112
+ });
113
+ }
114
+ }
115
+ // Handle tool results (action:*:result)
116
+ else if (event.type.startsWith('action:') && event.type.endsWith(':result')) {
117
+ const toolName = event.type.slice(7, -7);
118
+ const toolCallId = event.meta?.toolCallId;
119
+ if (typeof toolCallId !== 'string')
120
+ break;
121
+ const toolResult = {
122
+ type: 'tool-result',
123
+ toolCallId,
124
+ toolName,
125
+ output: {
126
+ type: 'text',
127
+ value: event?.data?.output || "No output", // ?.output is from delegation result
128
+ },
129
+ };
130
+ const last = messages[messages.length - 1];
131
+ if (last && last.role === 'tool' && Array.isArray(last.content)) {
132
+ last.content.push(toolResult);
133
+ }
134
+ else {
135
+ messages.push({
136
+ role: 'tool',
137
+ content: [toolResult],
138
+ });
139
+ }
140
+ }
141
+ break;
142
+ }
143
+ }
144
+ return fillMissingToolResults(messages);
145
+ }
@@ -0,0 +1,80 @@
1
+ export declare const OPENBOT_PLUGIN_ID = "@meetopenbot/openbot";
2
+ /**
3
+ * `@meetopenbot/openbot` — the standard, opinionated OpenBot agent runtime.
4
+ *
5
+ * Batteries-included: shell, memory, todo, storage tools, delegation, approval,
6
+ * preview, and the LLM loop.
7
+ */
8
+ export declare const openbotPlugin: {
9
+ id: string;
10
+ name: string;
11
+ description: string;
12
+ configSchema: {
13
+ type: "object";
14
+ properties: {
15
+ model: {
16
+ type: "string";
17
+ description: string;
18
+ };
19
+ authMode?: {
20
+ type: "string";
21
+ description: string;
22
+ enum: string[];
23
+ default: string;
24
+ } | undefined;
25
+ };
26
+ };
27
+ toolDefinitions: {
28
+ create_channel: {
29
+ description: string;
30
+ inputSchema: import("zod").ZodObject<{
31
+ channelId: import("zod").ZodString;
32
+ spec: import("zod").ZodOptional<import("zod").ZodString>;
33
+ initialState: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
34
+ cwd: import("zod").ZodOptional<import("zod").ZodString>;
35
+ }, import("zod/v4/core").$strip>;
36
+ };
37
+ patch_channel_details: {
38
+ description: string;
39
+ inputSchema: import("zod").ZodObject<{
40
+ state: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
41
+ spec: import("zod").ZodOptional<import("zod").ZodString>;
42
+ cwd: import("zod").ZodOptional<import("zod").ZodString>;
43
+ }, import("zod/v4/core").$strip>;
44
+ };
45
+ patch_thread_details: {
46
+ description: string;
47
+ inputSchema: import("zod").ZodObject<{
48
+ state: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
49
+ }, import("zod/v4/core").$strip>;
50
+ };
51
+ create_variable: {
52
+ description: string;
53
+ inputSchema: import("zod").ZodObject<{
54
+ key: import("zod").ZodString;
55
+ value: import("zod").ZodString;
56
+ secret: import("zod").ZodOptional<import("zod").ZodBoolean>;
57
+ }, import("zod/v4/core").$strip>;
58
+ };
59
+ delete_variable: {
60
+ description: string;
61
+ inputSchema: import("zod").ZodObject<{
62
+ key: import("zod").ZodString;
63
+ }, import("zod/v4/core").$strip>;
64
+ };
65
+ delete_channel: {
66
+ description: string;
67
+ inputSchema: import("zod").ZodObject<{
68
+ channelId: import("zod").ZodString;
69
+ }, import("zod/v4/core").$strip>;
70
+ };
71
+ get_workspace_file_url: {
72
+ description: string;
73
+ inputSchema: import("zod").ZodObject<{
74
+ path: import("zod").ZodString;
75
+ }, import("zod/v4/core").$strip>;
76
+ };
77
+ };
78
+ factory: (context: import("./types.js").PluginContext) => (builder: import("melony").MelonyBuilder<import("@meetopenbot/plugin-sdk").OpenBotState, import("./types.js").OpenBotEvent>) => void;
79
+ };
80
+ export default openbotPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ import { defineOpenbotPlugin } from './types.js';
2
+ import { isCloudMode } from './cloud-mode.js';
3
+ import { openbotRuntime } from './runtime.js';
4
+ import { bashPlugin } from './tools/bash.js';
5
+ import { memoryPlugin } from './tools/memory.js';
6
+ import { todoPlugin } from './tools/todo.js';
7
+ import { approvalPlugin } from './tools/approval.js';
8
+ import { delegationPlugin } from './tools/delegation.js';
9
+ import { uiPlugin } from './tools/ui.js';
10
+ import { previewPlugin } from './tools/preview.js';
11
+ import { storageToolPlugin } from './tools/storage.js';
12
+ export const OPENBOT_PLUGIN_ID = '@meetopenbot/openbot';
13
+ /**
14
+ * `@meetopenbot/openbot` — the standard, opinionated OpenBot agent runtime.
15
+ *
16
+ * Batteries-included: shell, memory, todo, storage tools, delegation, approval,
17
+ * preview, and the LLM loop.
18
+ */
19
+ export const openbotPlugin = defineOpenbotPlugin({
20
+ id: OPENBOT_PLUGIN_ID,
21
+ name: 'OpenBot Agent',
22
+ description: 'The standard OpenBot agent runtime with inbuilt tools (shell, memory, todo, storage, delegation, and approval).',
23
+ configSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ ...(isCloudMode()
27
+ ? {
28
+ authMode: {
29
+ type: 'string',
30
+ description: 'Credits — use your workspace credit balance via OpenBot. BYOK — bring your own API key.',
31
+ enum: ['credits', 'byok'],
32
+ default: 'credits',
33
+ },
34
+ }
35
+ : {}),
36
+ model: {
37
+ type: 'string',
38
+ description: 'Model from the hosted marketplace registry.',
39
+ },
40
+ },
41
+ },
42
+ toolDefinitions: {
43
+ ...bashPlugin.toolDefinitions,
44
+ ...memoryPlugin.toolDefinitions,
45
+ ...todoPlugin.toolDefinitions,
46
+ ...storageToolPlugin.toolDefinitions,
47
+ ...delegationPlugin.toolDefinitions,
48
+ ...previewPlugin.toolDefinitions,
49
+ },
50
+ factory: (context) => (builder) => {
51
+ const { agentId, config, storage, tools, abortSignal, host } = context;
52
+ bashPlugin.factory(context)(builder);
53
+ memoryPlugin.factory(context)(builder);
54
+ todoPlugin.factory(context)(builder);
55
+ storageToolPlugin.register(context)(builder);
56
+ delegationPlugin.factory(context)(builder);
57
+ uiPlugin.factory(context)(builder);
58
+ previewPlugin.factory(context)(builder);
59
+ const approvalConfig = config?.approval ?? {
60
+ actions: [],
61
+ };
62
+ approvalPlugin.factory({ ...context, config: approvalConfig })(builder);
63
+ const authMode = host.isCloudSystemAgent(agentId)
64
+ ? host.parseOpenbotAuthMode(config?.authMode)
65
+ : 'byok';
66
+ return openbotRuntime({
67
+ model: config?.model,
68
+ authMode,
69
+ agentId,
70
+ storage,
71
+ toolDefinitions: tools,
72
+ abortSignal,
73
+ host,
74
+ })(builder);
75
+ },
76
+ });
77
+ export default openbotPlugin;
@@ -0,0 +1,2 @@
1
+ import type { LanguageModel } from 'ai';
2
+ export declare function resolveModel(modelString: string, _agentId?: string): LanguageModel;
package/dist/model.js ADDED
@@ -0,0 +1,20 @@
1
+ import { openai as defaultOpenai } from '@ai-sdk/openai';
2
+ import { anthropic } from '@ai-sdk/anthropic';
3
+ import { google } from '@ai-sdk/google';
4
+ export function resolveModel(modelString, _agentId) {
5
+ const [provider, ...rest] = modelString.split('/');
6
+ const modelId = rest.join('/');
7
+ if (!modelId) {
8
+ throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
9
+ }
10
+ switch (provider) {
11
+ case 'openai':
12
+ return defaultOpenai(modelId);
13
+ case 'anthropic':
14
+ return anthropic(modelId);
15
+ case 'google':
16
+ return google(modelId);
17
+ default:
18
+ throw new Error(`Unsupported AI provider: "${provider}"`);
19
+ }
20
+ }
@@ -0,0 +1,25 @@
1
+ import { PluginHost, PluginFactory } from './types.js';
2
+ export interface OpenBotRuntimeOptions {
3
+ /** Provider model string (e.g. `openai/gpt-4o-mini`, `anthropic/claude-3-5-sonnet-20240620`). */
4
+ model?: string;
5
+ /** Cloud system agent auth mode; self-hosted agents always use BYOK. */
6
+ authMode?: 'credits' | 'byok';
7
+ agentId?: string;
8
+ storage?: Record<string, any>;
9
+ /** Tool definitions merged from all tool plugins attached to this agent. */
10
+ toolDefinitions?: Record<string, {
11
+ description: string;
12
+ inputSchema: unknown;
13
+ }>;
14
+ /** Fires when the run is stopped; cancels the in-flight LLM call. */
15
+ abortSignal?: AbortSignal;
16
+ host: PluginHost;
17
+ }
18
+ /**
19
+ * OpenBot agent runtime.
20
+ *
21
+ * - One `generateText` call per `runLLM` (tools have no `execute`; SDK stops at 1 step).
22
+ * - Tool calls become `action:*` events; plugins emit `:result` when done.
23
+ * - When a full batch of results is in, `runLLM` runs again with updated history.
24
+ */
25
+ export declare const openbotRuntime: (options: OpenBotRuntimeOptions) => PluginFactory;