@meetopenbot/openbot 0.1.6 → 0.1.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/index.d.ts CHANGED
@@ -12,11 +12,7 @@ export declare const openbotPlugin: {
12
12
  configSchema: {
13
13
  type: "object";
14
14
  properties: {
15
- model: {
16
- type: "string";
17
- override: true;
18
- description: string;
19
- };
15
+ model: import("./model-registry.js").ModelConfigField;
20
16
  authMode?: {
21
17
  type: "string";
22
18
  description: string;
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defineOpenbotPlugin } from './types.js';
2
2
  import { isCloudMode } from './cloud-mode.js';
3
+ import { resolveModelConfigField } from './model-registry.js';
3
4
  import { openbotRuntime } from './runtime.js';
4
5
  import { bashPlugin } from './tools/bash.js';
5
6
  import { memoryPlugin } from './tools/memory.js';
@@ -10,6 +11,7 @@ import { uiPlugin } from './tools/ui.js';
10
11
  import { previewPlugin } from './tools/preview.js';
11
12
  import { storageToolPlugin } from './tools/storage.js';
12
13
  export const OPENBOT_PLUGIN_ID = '@meetopenbot/openbot';
14
+ const modelField = await resolveModelConfigField();
13
15
  /**
14
16
  * `@meetopenbot/openbot` — the standard, opinionated OpenBot agent runtime.
15
17
  *
@@ -33,11 +35,7 @@ export const openbotPlugin = defineOpenbotPlugin({
33
35
  },
34
36
  }
35
37
  : {}),
36
- model: {
37
- type: 'string',
38
- override: true,
39
- description: 'Model from the hosted marketplace registry.',
40
- },
38
+ model: modelField,
41
39
  },
42
40
  },
43
41
  toolDefinitions: {
@@ -0,0 +1,35 @@
1
+ export type ModelRegistryProvider = {
2
+ label: string;
3
+ models: Array<{
4
+ id: string;
5
+ label: string;
6
+ description: string;
7
+ }>;
8
+ };
9
+ export type ModelRegistry = {
10
+ providers?: Record<string, ModelRegistryProvider>;
11
+ };
12
+ export type ModelConfigField = {
13
+ type: 'string';
14
+ description: string;
15
+ default?: string;
16
+ override?: boolean;
17
+ enum?: string[];
18
+ options?: Array<{
19
+ label: string;
20
+ value: string;
21
+ description?: string;
22
+ }>;
23
+ };
24
+ export declare const PROVIDER_API_KEY_LINKS: Record<string, string>;
25
+ export declare function fetchModelRegistry(): Promise<ModelRegistry | null>;
26
+ export declare function listApiKeyProviders(registry: ModelRegistry | null): Array<{
27
+ id: string;
28
+ label: string;
29
+ }>;
30
+ export declare function getProviderModelOptions(registry: ModelRegistry | null, provider: string): Array<{
31
+ label: string;
32
+ value: string;
33
+ }> | undefined;
34
+ /** Registry-backed model field for plugin configSchema (`enum` + labeled `options`). */
35
+ export declare function resolveModelConfigField(): Promise<ModelConfigField>;
@@ -0,0 +1,91 @@
1
+ const DEFAULT_REGISTRY_URL = 'https://raw.githubusercontent.com/meetopenbot/openbot-registry/main/registry.json';
2
+ const API_KEY_PROVIDER_IDS = ['openai', 'anthropic', 'google'];
3
+ export const PROVIDER_API_KEY_LINKS = {
4
+ openai: 'https://platform.openai.com/api-keys',
5
+ anthropic: 'https://console.anthropic.com/settings/keys',
6
+ google: 'https://aistudio.google.com/app/apikey',
7
+ };
8
+ const FALLBACK_PROVIDERS = [
9
+ { id: 'openai', label: 'OpenAI' },
10
+ { id: 'anthropic', label: 'Anthropic' },
11
+ { id: 'google', label: 'Google' },
12
+ ];
13
+ let cachedRegistry;
14
+ export async function fetchModelRegistry() {
15
+ if (cachedRegistry !== undefined)
16
+ return cachedRegistry;
17
+ const url = process.env.OPENBOT_MODEL_REGISTRY_URL?.trim() || DEFAULT_REGISTRY_URL;
18
+ try {
19
+ const response = await fetch(url, {
20
+ headers: { Accept: 'application/json' },
21
+ signal: AbortSignal.timeout(15000),
22
+ });
23
+ if (!response.ok) {
24
+ cachedRegistry = null;
25
+ return null;
26
+ }
27
+ cachedRegistry = (await response.json());
28
+ return cachedRegistry;
29
+ }
30
+ catch {
31
+ cachedRegistry = null;
32
+ return null;
33
+ }
34
+ }
35
+ export function listApiKeyProviders(registry) {
36
+ if (!registry?.providers)
37
+ return FALLBACK_PROVIDERS;
38
+ const providers = API_KEY_PROVIDER_IDS.flatMap((id) => {
39
+ const provider = registry.providers?.[id];
40
+ return provider ? [{ id, label: provider.label }] : [];
41
+ });
42
+ return providers.length > 0 ? providers : FALLBACK_PROVIDERS;
43
+ }
44
+ export function getProviderModelOptions(registry, provider) {
45
+ const models = registry?.providers?.[provider]?.models;
46
+ if (!models?.length)
47
+ return undefined;
48
+ return models.map((m) => ({ label: m.label, value: m.id }));
49
+ }
50
+ function listAllModelOptions(registry) {
51
+ if (!registry?.providers)
52
+ return [];
53
+ const options = [];
54
+ for (const providerId of API_KEY_PROVIDER_IDS) {
55
+ const provider = registry.providers[providerId];
56
+ if (!provider)
57
+ continue;
58
+ for (const model of provider.models ?? []) {
59
+ options.push({
60
+ value: `${providerId}/${model.id}`,
61
+ label: `${provider.label} — ${model.label}`,
62
+ description: model.description,
63
+ });
64
+ }
65
+ }
66
+ return options;
67
+ }
68
+ const freeInputModelField = () => ({
69
+ type: 'string',
70
+ override: true,
71
+ description: 'Provider model in provider/model-id format (e.g. openai/gpt-4o-mini).',
72
+ default: 'openai/gpt-4o-mini',
73
+ });
74
+ /** Registry-backed model field for plugin configSchema (`enum` + labeled `options`). */
75
+ export async function resolveModelConfigField() {
76
+ const registry = await fetchModelRegistry();
77
+ const options = listAllModelOptions(registry);
78
+ if (options.length === 0)
79
+ return freeInputModelField();
80
+ const defaultModel = options.find((option) => option.value === 'openai/gpt-4o-mini')?.value ??
81
+ options.find((option) => option.value.startsWith('openai/'))?.value ??
82
+ options[0].value;
83
+ return {
84
+ type: 'string',
85
+ override: true,
86
+ description: 'Model from the OpenBot registry.',
87
+ default: defaultModel,
88
+ enum: options.map((option) => option.value),
89
+ options,
90
+ };
91
+ }
package/dist/runtime.js CHANGED
@@ -4,6 +4,7 @@ import { buildContext } from './context.js';
4
4
  import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
5
5
  import { isAuthErrorMessage, isCreditsErrorMessage } from './credits-auth.js';
6
6
  import { resolveModel } from './model.js';
7
+ import { fetchModelRegistry, getProviderModelOptions, listApiKeyProviders, PROVIDER_API_KEY_LINKS, } from './model-registry.js';
7
8
  async function buildSystemPrompt(state, storage) {
8
9
  const context = await buildContext(state, storage);
9
10
  const sections = [OPENBOT_SYSTEM_PROMPT, '', context];
@@ -187,8 +188,8 @@ export const openbotRuntime = (options) => (builder) => {
187
188
  }
188
189
  }
189
190
  if (isAuthErrorMessage(errorMessage) && !isCreditsCloudAgent(context.state.agentId)) {
190
- const registry = await host.resolveModelRegistry();
191
- const providerActions = host.listApiKeyProvidersFromRegistry(registry).map((provider) => ({
191
+ const registry = await fetchModelRegistry();
192
+ const providerActions = listApiKeyProviders(registry).map((provider) => ({
192
193
  id: provider.id,
193
194
  label: provider.label,
194
195
  variant: 'primary',
@@ -200,13 +201,7 @@ export const openbotRuntime = (options) => (builder) => {
200
201
  widgetId: `api_provider_selection_${Date.now()}`,
201
202
  title: `Setup AI Provider`,
202
203
  description: `Select a provider to continue.`,
203
- actions: providerActions.length > 0
204
- ? providerActions
205
- : [
206
- { id: 'openai', label: 'OpenAI', variant: 'primary' },
207
- { id: 'anthropic', label: 'Anthropic', variant: 'primary' },
208
- { id: 'google', label: 'Google', variant: 'primary' },
209
- ],
204
+ actions: providerActions,
210
205
  metadata: {
211
206
  type: 'api_provider_selection',
212
207
  },
@@ -259,31 +254,11 @@ export const openbotRuntime = (options) => (builder) => {
259
254
  const provider = actionId;
260
255
  const [_, ...rest] = currentModelString.split('/');
261
256
  const currentModelId = rest.join('/');
262
- const registry = await host.resolveModelRegistry();
263
- const providerData = registry.providers?.[provider];
264
- const providerLinks = {
265
- openai: 'https://platform.openai.com/api-keys',
266
- anthropic: 'https://console.anthropic.com/settings/keys',
267
- google: 'https://aistudio.google.com/app/apikey',
268
- };
257
+ const registry = await fetchModelRegistry();
258
+ const providerData = registry?.providers?.[provider];
269
259
  const label = providerData?.label || provider;
270
- const link = providerLinks[provider] || '';
271
- const modelOptions = providerData?.models.map((m) => ({
272
- label: m.label,
273
- value: m.id,
274
- }));
275
- if (!modelOptions || modelOptions.length === 0) {
276
- yield {
277
- type: 'agent:output',
278
- data: {
279
- content: `No models are listed for **${label}** in the marketplace registry.`,
280
- },
281
- meta: { agentId: context.state.agentId },
282
- };
283
- return;
284
- }
285
- const defaultModel = modelOptions[0].value;
286
- const defaultValue = modelOptions.find((m) => m.value === currentModelId)?.value || defaultModel;
260
+ const link = PROVIDER_API_KEY_LINKS[provider] || '';
261
+ const modelOptions = getProviderModelOptions(registry, provider);
287
262
  yield {
288
263
  type: 'client:ui:widget',
289
264
  data: {
@@ -298,21 +273,49 @@ export const openbotRuntime = (options) => (builder) => {
298
273
  },
299
274
  meta: { agentId: context.state.agentId, threadId },
300
275
  };
276
+ if (modelOptions && modelOptions.length > 0) {
277
+ const defaultModelId = modelOptions.find((m) => m.value === currentModelId)?.value || modelOptions[0].value;
278
+ yield {
279
+ type: 'client:ui:widget',
280
+ data: {
281
+ kind: 'choice',
282
+ widgetId: `api_model_selection_${Date.now()}`,
283
+ title: 'Select Model',
284
+ description: `Choose a ${label} model.`,
285
+ actions: modelOptions.map((option) => ({
286
+ id: option.value,
287
+ label: option.label,
288
+ variant: option.value === defaultModelId ? 'primary' : 'secondary',
289
+ })),
290
+ metadata: {
291
+ type: 'api_model_selection',
292
+ provider,
293
+ },
294
+ },
295
+ meta: { agentId: context.state.agentId, threadId },
296
+ };
297
+ return;
298
+ }
301
299
  yield {
302
300
  type: 'client:ui:widget',
303
301
  data: {
304
302
  kind: 'form',
305
303
  widgetId: `api_key_request_${Date.now()}`,
306
304
  title: `${label} Setup`,
307
- description: `Enter your API key and select a model.`,
305
+ description: `Enter your API key and model ID.`,
308
306
  fields: [
309
307
  {
310
308
  id: 'model',
311
309
  label: 'Model',
312
- type: 'select',
313
- options: modelOptions,
310
+ type: 'text',
311
+ description: 'Enter the model ID for this provider.',
312
+ placeholder: provider === 'openai'
313
+ ? 'gpt-4o-mini'
314
+ : provider === 'anthropic'
315
+ ? 'claude-3-5-sonnet-20241022'
316
+ : 'gemini-2.0-flash',
314
317
  required: true,
315
- defaultValue,
318
+ defaultValue: currentModelId || '',
316
319
  },
317
320
  {
318
321
  id: 'apiKey',
@@ -333,12 +336,64 @@ export const openbotRuntime = (options) => (builder) => {
333
336
  };
334
337
  return;
335
338
  }
339
+ if (metadata?.type === 'api_model_selection') {
340
+ const provider = String(metadata.provider);
341
+ const modelId = String(actionId).trim();
342
+ const registry = await fetchModelRegistry();
343
+ const providerData = registry?.providers?.[provider];
344
+ const label = providerData?.label || provider;
345
+ const modelLabel = providerData?.models.find((model) => model.id === modelId)?.label || modelId;
346
+ const link = PROVIDER_API_KEY_LINKS[provider] || '';
347
+ yield {
348
+ type: 'client:ui:widget',
349
+ data: {
350
+ widgetId: event.data.widgetId,
351
+ kind: 'message',
352
+ title: 'Model Selected',
353
+ body: `${modelLabel} was selected.`,
354
+ state: 'submitted',
355
+ display: 'collapsed',
356
+ disabled: true,
357
+ actions: [],
358
+ },
359
+ meta: { agentId: context.state.agentId, threadId },
360
+ };
361
+ yield {
362
+ type: 'client:ui:widget',
363
+ data: {
364
+ kind: 'form',
365
+ widgetId: `api_key_request_${Date.now()}`,
366
+ title: `${label} Setup`,
367
+ description: `Enter your API key to continue.`,
368
+ fields: [
369
+ {
370
+ id: 'apiKey',
371
+ label: 'API Key',
372
+ type: 'password',
373
+ description: `Get your key here: [${link}](${link})`,
374
+ placeholder: `sk-...`,
375
+ required: true,
376
+ },
377
+ ],
378
+ submitLabel: 'Save & Continue',
379
+ metadata: {
380
+ type: 'api_key_request',
381
+ provider,
382
+ model: modelId,
383
+ },
384
+ },
385
+ meta: { agentId: context.state.agentId, threadId },
386
+ };
387
+ return;
388
+ }
336
389
  if (metadata?.type !== 'api_key_request')
337
390
  return;
338
- if (!values?.apiKey || !values?.model)
391
+ if (!values?.apiKey)
339
392
  return;
340
393
  const provider = String(values.provider || metadata.provider);
341
- const modelId = String(values.model).trim();
394
+ const modelId = String(values.model || metadata.model || '').trim();
395
+ if (!modelId)
396
+ return;
342
397
  const apiKey = String(values.apiKey);
343
398
  if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'google') {
344
399
  yield {
package/dist/types.d.ts CHANGED
@@ -45,20 +45,6 @@ export interface PluginHost {
45
45
  isCloudSystemAgent: (agentId: string) => boolean;
46
46
  isCloudMode: () => boolean;
47
47
  parseOpenbotAuthMode: (value: unknown) => 'credits' | 'byok';
48
- resolveModelRegistry: () => Promise<{
49
- providers?: Record<string, {
50
- label: string;
51
- models: Array<{
52
- id: string;
53
- label: string;
54
- description: string;
55
- }>;
56
- }>;
57
- }>;
58
- listApiKeyProvidersFromRegistry: (registry: Awaited<ReturnType<PluginHost['resolveModelRegistry']>>) => Array<{
59
- id: string;
60
- label: string;
61
- }>;
62
48
  saveConfig: (patch: Record<string, unknown>) => void;
63
49
  getBaseDir: () => string;
64
50
  resolvePath: (p: string) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/openbot",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Monolithic OpenBot agent runtime with batteries-included tools.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",