@meetopenbot/openbot 0.2.6 → 1.0.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.
@@ -49,6 +49,19 @@ export function expandModelString(modelString, ctx) {
49
49
  return pickAutoModel(ctx);
50
50
  return modelString.trim();
51
51
  }
52
+ /** BYOK message when the resolved provider has no API key in env. */
53
+ export function missingByokApiKeyMessage(modelString, ctx) {
54
+ if (shouldUseCreditsAuth(ctx))
55
+ return undefined;
56
+ const env = ctx?.env ?? process.env;
57
+ const provider = providerOf(expandModelString(modelString, ctx));
58
+ if (providerHasByokKey(provider, env))
59
+ return undefined;
60
+ const envVar = PROVIDER_BYOK_ENV[provider];
61
+ if (!envVar)
62
+ return undefined;
63
+ return `${provider} API key is missing`;
64
+ }
52
65
  export function isRetryableModelError(error) {
53
66
  const message = error instanceof Error ? error.message : String(error);
54
67
  const lower = message.toLowerCase();
@@ -0,0 +1,26 @@
1
+ import { memoryTools } from "./tools/memory.js";
2
+ import { todoTools } from "./tools/todo.js";
3
+ import { storageTools } from "./tools/storage.js";
4
+ import { askAgentTools } from "./tools/ask-agent.js";
5
+ import { startWorkTools } from "./tools/start-work.js";
6
+ import { threadStatusTools } from "./tools/thread-status.js";
7
+ import { threadTitleTools } from "./tools/thread-title.js";
8
+ import { wrapToolsWithApproval } from "./tools/approval.js";
9
+ /**
10
+ * Merge first-party OpenBot coordinator tools and wrap configured actions
11
+ * with approval.
12
+ */
13
+ export function buildOpenbotTools(args) {
14
+ const tools = {
15
+ ...memoryTools,
16
+ ...todoTools,
17
+ ...storageTools,
18
+ ...askAgentTools,
19
+ ...startWorkTools,
20
+ ...threadStatusTools,
21
+ ...threadTitleTools,
22
+ ...args.context.tools,
23
+ };
24
+ const approvalConfig = args.context.config?.approval ?? { actions: [] };
25
+ return wrapToolsWithApproval(tools, approvalConfig.actions ?? [], args.context);
26
+ }
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 content = event.data.content;
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' && typeof last.content === 'string') {
82
- last.content += content;
157
+ if (last && last.role === 'assistant') {
158
+ appendAssistantOutput(last, content, reasoningParts);
83
159
  }
84
160
  else {
85
- messages.push({ role: 'assistant', content });
161
+ messages.push(makeAssistantMessage(content, reasoningParts));
86
162
  }
87
163
  break;
88
164
  }
package/dist/index.js CHANGED
@@ -1,90 +1,89 @@
1
- import { defineOpenbotPlugin } from "./types.js";
2
- import { cloudAuthModeProperties, modelConfigField, } from "@meetopenbot/plugin-sdk";
3
- import { AUTO_MODEL_ID, AUTO_MODEL_OPTION } from "./auto-model.js";
4
- import { openbotRuntime } from "./runtime.js";
5
- import { bashPlugin } from "./tools/bash.js";
6
- import { memoryPlugin } from "./tools/memory.js";
7
- import { todoPlugin } from "./tools/todo.js";
8
- import { approvalPlugin } from "./tools/approval.js";
9
- import { askAgentPlugin } from "./tools/ask-agent.js";
10
- import { startWorkPlugin } from "./tools/start-work.js";
11
- import { threadStatusPlugin } from "./tools/thread-status.js";
12
- import { uiPlugin } from "./tools/ui.js";
13
- import { previewPlugin } from "./tools/preview.js";
14
- import { storageToolPlugin } from "./tools/storage.js";
15
- export const OPENBOT_PLUGIN_ID = "@meetopenbot/openbot";
1
+ import { defineOpenbotPlugin } from './types.js';
2
+ import { cloudAuthModeProperties, modelConfigField, } from '@meetopenbot/plugin-sdk';
3
+ import { AUTO_MODEL_ID, AUTO_MODEL_OPTION } from './auto-model.js';
4
+ import { handleOpenbotWidgetResponse, runOpenbotTurn } from './runtime.js';
5
+ import { memoryTools } from './tools/memory.js';
6
+ import { todoTools } from './tools/todo.js';
7
+ import { storageTools } from './tools/storage.js';
8
+ import { askAgentTools } from './tools/ask-agent.js';
9
+ import { startWorkTools } from './tools/start-work.js';
10
+ import { threadStatusTools } from './tools/thread-status.js';
11
+ import { threadTitleTools } from './tools/thread-title.js';
12
+ import { buildOpenbotTools } from './build-tools.js';
13
+ export const OPENBOT_PLUGIN_ID = '@meetopenbot/openbot';
16
14
  const modelField = await modelConfigField({
17
- providers: ["openai", "anthropic", "google", "deepseek"],
15
+ providers: ['openai', 'anthropic', 'google', 'deepseek'],
18
16
  extraOptions: [AUTO_MODEL_OPTION],
19
17
  defaultValue: AUTO_MODEL_ID,
20
- description: "Auto, or a model from the OpenBot registry.",
21
- fallbackDescription: "Auto, or a provider model in provider/model-id format (e.g. openai/gpt-4o-mini).",
18
+ description: 'Auto, or a model from the OpenBot registry.',
19
+ fallbackDescription: 'Auto, or a provider model in provider/model-id format (e.g. openai/gpt-4o-mini).',
22
20
  });
23
- const SPECIALIST_TOOL_NAMES = new Set([
24
- ...Object.keys(bashPlugin.toolDefinitions ?? {}),
25
- ...Object.keys(previewPlugin.toolDefinitions ?? {}),
26
- ]);
27
21
  /**
28
22
  * `@meetopenbot/openbot` — the standard, opinionated OpenBot agent runtime.
29
23
  *
30
24
  * The orchestrator (`system`) is a coordinator: ask agents, start work in
31
- * Spaces, memory, todos, storage. Specialist tools (shell, preview) stay on
32
- * non-orchestrator agents that opt into this runtime.
25
+ * Spaces, memory, todos, storage. Coding and preview work belongs to
26
+ * specialist harnesses, not this plugin.
33
27
  */
34
28
  export const openbotPlugin = defineOpenbotPlugin({
35
- id: OPENBOT_PLUGIN_ID,
36
- name: "OpenBot Agent",
37
- description: "OpenBot coordinator runtime: ask specialists, route work into Spaces, and track todos.",
29
+ name: 'OpenBot Agent',
30
+ description: 'OpenBot coordinator runtime: ask specialists, route work into Spaces, and track todos.',
38
31
  configSchema: {
39
- type: "object",
32
+ type: 'object',
40
33
  properties: {
41
34
  ...cloudAuthModeProperties(),
42
35
  model: modelField,
43
36
  },
44
37
  },
45
- toolDefinitions: {
46
- ...bashPlugin.toolDefinitions,
47
- ...memoryPlugin.toolDefinitions,
48
- ...todoPlugin.toolDefinitions,
49
- ...storageToolPlugin.toolDefinitions,
50
- ...askAgentPlugin.toolDefinitions,
51
- ...startWorkPlugin.toolDefinitions,
52
- ...threadStatusPlugin.toolDefinitions,
53
- ...previewPlugin.toolDefinitions,
38
+ tools: {
39
+ ...memoryTools,
40
+ ...todoTools,
41
+ ...storageTools,
42
+ ...askAgentTools,
43
+ ...startWorkTools,
44
+ ...threadStatusTools,
45
+ ...threadTitleTools,
54
46
  },
55
- factory: (context) => (builder) => {
56
- const { agentId, config, storage, tools, abortSignal, host } = context;
57
- const isOrchestrator = agentId === host.orchestratorAgentId;
58
- memoryPlugin.factory(context)(builder);
59
- todoPlugin.factory(context)(builder);
60
- storageToolPlugin.register(context)(builder);
61
- askAgentPlugin.factory(context)(builder);
62
- startWorkPlugin.factory(context)(builder);
63
- threadStatusPlugin.factory(context)(builder);
64
- uiPlugin.factory(context)(builder);
65
- if (!isOrchestrator) {
66
- bashPlugin.factory(context)(builder);
67
- previewPlugin.factory(context)(builder);
68
- }
69
- const approvalConfig = config?.approval ?? {
70
- actions: [],
47
+ async *run(args) {
48
+ const { context } = args;
49
+ const authMode = context.host.isCloudSystemAgent(context.agentId)
50
+ ? context.host.parseOpenbotAuthMode(context.config?.authMode)
51
+ : 'byok';
52
+ const toolDefinitions = buildOpenbotTools({ context });
53
+ yield* runOpenbotTurn(args, {
54
+ model: context.config?.model || AUTO_MODEL_ID,
55
+ authMode,
56
+ agentId: context.agentId,
57
+ storage: context.storage,
58
+ toolDefinitions,
59
+ abortSignal: context.abortSignal,
60
+ host: context.host,
61
+ });
62
+ },
63
+ async *onWidgetResponse(args) {
64
+ const { context } = args;
65
+ const authMode = context.host.isCloudSystemAgent(context.agentId)
66
+ ? context.host.parseOpenbotAuthMode(context.config?.authMode)
67
+ : 'byok';
68
+ const toolDefinitions = buildOpenbotTools({ context });
69
+ const turnArgs = {
70
+ prompt: '',
71
+ threadId: args.state.threadId,
72
+ event: args.state.triggerEvent,
73
+ handlerCtx: args.handlerCtx,
74
+ context,
75
+ state: args.state,
76
+ emit: args.emit,
71
77
  };
72
- approvalPlugin.factory({ ...context, config: approvalConfig })(builder);
73
- const authMode = host.isCloudSystemAgent(agentId)
74
- ? host.parseOpenbotAuthMode(config?.authMode)
75
- : "byok";
76
- const toolDefinitions = isOrchestrator
77
- ? Object.fromEntries(Object.entries(tools).filter(([name]) => !SPECIALIST_TOOL_NAMES.has(name)))
78
- : tools;
79
- return openbotRuntime({
80
- model: config?.model || AUTO_MODEL_ID,
78
+ yield* handleOpenbotWidgetResponse(turnArgs, {
79
+ model: context.config?.model || AUTO_MODEL_ID,
81
80
  authMode,
82
- agentId,
83
- storage,
81
+ agentId: context.agentId,
82
+ storage: context.storage,
84
83
  toolDefinitions,
85
- abortSignal,
86
- host,
87
- })(builder);
84
+ abortSignal: context.abortSignal,
85
+ host: context.host,
86
+ });
88
87
  },
89
88
  });
90
89
  export default openbotPlugin;
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/DeepSeek gateways only speak this.
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 5+ default: OpenAI Responses API (`/v1/responses`), required for tools on Luna. */
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 baseURL = creditsProviderBaseUrl(config, provider);
35
- const headers = { [INTEGRATIONS_TOKEN_HEADER]: config.token };
36
- // Any non-empty key satisfies the SDK; the integrations gateway authenticates via header.
37
- const apiKey = config.token || CREDITS_API_KEY_PLACEHOLDER;
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({ baseURL, apiKey, headers }, modelId);
36
+ return openAiResponsesModel(options, modelId);
41
37
  case 'anthropic':
42
- return createAnthropic({ baseURL, apiKey, headers })(modelId);
38
+ return createAnthropic(options)(modelId);
43
39
  case 'google':
44
- return openAiChatModel({ baseURL, apiKey, headers }, modelId);
40
+ return openAiChatModel(options, modelId);
45
41
  case 'deepseek':
46
- return openAiChatModel({ baseURL, apiKey, headers }, modelId);
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) : deepseekChat(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
+ }
@@ -0,0 +1,31 @@
1
+ /** Persist one `agent:output` per model step so jsonl stays thought → tools → reply. */
2
+ export function createAgentOutputBuffer(meta) {
3
+ let text = '';
4
+ let reasoning = '';
5
+ return {
6
+ addText(delta) {
7
+ text += delta;
8
+ },
9
+ addReasoning(delta) {
10
+ reasoning += delta;
11
+ },
12
+ take() {
13
+ if (!text && !reasoning)
14
+ return null;
15
+ const event = {
16
+ type: 'agent:output',
17
+ data: {
18
+ content: text,
19
+ ...(reasoning ? { reasoning } : {}),
20
+ },
21
+ meta,
22
+ };
23
+ text = '';
24
+ reasoning = '';
25
+ return event;
26
+ },
27
+ };
28
+ }
29
+ export function isOutputBoundaryPart(type) {
30
+ return type === 'tool-call' || type === 'finish-step' || type === 'step-finish' || type === 'finish';
31
+ }