openbot 0.5.8 → 0.5.9
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/CLAUDE.md +1 -1
- package/dist/app/cli.js +1 -1
- package/dist/harness/index.js +7 -1
- package/dist/harness/merge-plugin-config.js +26 -0
- package/dist/plugins/storage/service.js +4 -5
- package/dist/services/plugins/enrich-openbot-plugin.js +38 -0
- package/dist/services/plugins/host.js +0 -3
- package/dist/services/plugins/model-registry.js +1 -1
- package/package.json +2 -2
- package/src/app/cli.ts +1 -1
- package/src/app/types.ts +2 -0
- package/src/harness/index.ts +9 -1
- package/src/harness/merge-plugin-config.ts +35 -0
- package/src/plugins/storage/service.ts +4 -10
- package/src/services/plugins/enrich-openbot-plugin.ts +41 -0
- package/src/services/plugins/host.ts +0 -6
- package/src/services/plugins/types.ts +0 -6
- package/src/services/plugins/model-registry.ts +0 -178
package/CLAUDE.md
CHANGED
|
@@ -31,7 +31,7 @@ OpenBot is a local-first, event-driven harness for running AI agents, built on t
|
|
|
31
31
|
|
|
32
32
|
Core built-ins: `storage` (infra handlers for `/api/state`), `plugin-manager` (marketplace + npm plugin install). The default agent runtime is **`@meetopenbot/openbot`**, a community npm package installed to `~/.openbot/plugins/@meetopenbot/openbot/` on first boot (`ensureDefaultStack`). Disk-installed plugins take precedence over built-ins in `resolvePlugin`.
|
|
33
33
|
|
|
34
|
-
A `Plugin`'s `factory(context)` returns a `MelonyPlugin`; `PluginContext` carries `agentId`, `agentDetails`, per-agent `config` (from `AGENT.md` frontmatter), the shared `storage` service, the merged `tools` map, `publicBaseUrl`, the run's `abortSignal`, and `host` (platform hooks: `runAgent`, cloud auth,
|
|
34
|
+
A `Plugin`'s `factory(context)` returns a `MelonyPlugin`; `PluginContext` carries `agentId`, `agentDetails`, per-agent `config` (from `AGENT.md` frontmatter), the shared `storage` service, the merged `tools` map, `publicBaseUrl`, the run's `abortSignal`, and `host` (platform hooks: `runAgent`, cloud auth, config).
|
|
35
35
|
|
|
36
36
|
### Agents
|
|
37
37
|
|
package/dist/app/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ function checkNodeVersion() {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
checkNodeVersion();
|
|
21
|
-
program.name('openbot').description('OpenBot CLI').version('0.5.
|
|
21
|
+
program.name('openbot').description('OpenBot CLI').version('0.5.9');
|
|
22
22
|
program
|
|
23
23
|
.command('start')
|
|
24
24
|
.description('Start the OpenBot harness')
|
package/dist/harness/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { abortRegistry, abortKey } from '../services/abort.js';
|
|
|
7
7
|
import { loadConfig } from '../app/config.js';
|
|
8
8
|
import { getPublicBaseUrl } from '../plugins/storage/files.js';
|
|
9
9
|
import { createPluginHost } from '../services/plugins/host.js';
|
|
10
|
+
import { getInvokeConfigOverrides, mergePluginConfig } from './merge-plugin-config.js';
|
|
10
11
|
export { STATE_AGENT_ID, ORCHESTRATOR_AGENT_ID };
|
|
11
12
|
async function emitEvent(chunk, state, { persistEvents, channelId, threadId, onEvent, parentAgentId, parentToolCallId, }) {
|
|
12
13
|
ensureEventId(chunk);
|
|
@@ -69,6 +70,11 @@ export async function runAgent(options) {
|
|
|
69
70
|
}
|
|
70
71
|
const builder = melony().initialState(state);
|
|
71
72
|
const pluginHost = createPluginHost(runAgent);
|
|
73
|
+
const invokeConfigOverrides = getInvokeConfigOverrides(event);
|
|
74
|
+
const pluginConfigs = Object.fromEntries(pluginRefs.map((ref) => [
|
|
75
|
+
ref.id,
|
|
76
|
+
mergePluginConfig(ref.config, invokeConfigOverrides, ref.id),
|
|
77
|
+
]));
|
|
72
78
|
for (const ref of pluginRefs) {
|
|
73
79
|
const plugin = await resolvePlugin(ref.id);
|
|
74
80
|
if (!plugin)
|
|
@@ -76,7 +82,7 @@ export async function runAgent(options) {
|
|
|
76
82
|
builder.use(plugin.factory({
|
|
77
83
|
agentId,
|
|
78
84
|
agentDetails,
|
|
79
|
-
config: ref.
|
|
85
|
+
config: pluginConfigs[ref.id],
|
|
80
86
|
storage: storageService,
|
|
81
87
|
tools,
|
|
82
88
|
publicBaseUrl,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function isPluginKeyedConfig(config) {
|
|
5
|
+
return Object.values(config).every(isPlainObject);
|
|
6
|
+
}
|
|
7
|
+
export function getInvokeConfigOverrides(event) {
|
|
8
|
+
if (event.type !== 'agent:invoke')
|
|
9
|
+
return undefined;
|
|
10
|
+
const config = event.data?.config;
|
|
11
|
+
if (!isPlainObject(config) || Object.keys(config).length === 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
return config;
|
|
14
|
+
}
|
|
15
|
+
export function mergePluginConfig(base, overrides, pluginId) {
|
|
16
|
+
const baseConfig = base ?? {};
|
|
17
|
+
if (!overrides)
|
|
18
|
+
return baseConfig;
|
|
19
|
+
if (isPluginKeyedConfig(overrides)) {
|
|
20
|
+
const pluginOverride = overrides[pluginId];
|
|
21
|
+
if (!isPlainObject(pluginOverride))
|
|
22
|
+
return baseConfig;
|
|
23
|
+
return { ...baseConfig, ...pluginOverride };
|
|
24
|
+
}
|
|
25
|
+
return { ...baseConfig, ...overrides };
|
|
26
|
+
}
|
|
@@ -9,7 +9,7 @@ import crypto from 'node:crypto';
|
|
|
9
9
|
import matter from 'gray-matter';
|
|
10
10
|
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
11
11
|
import { listBuiltInPlugins, parsePluginModule } from '../../services/plugins/registry.js';
|
|
12
|
-
import { enrichOpenbotPluginDescriptor
|
|
12
|
+
import { enrichOpenbotPluginDescriptor } from '../../services/plugins/enrich-openbot-plugin.js';
|
|
13
13
|
import { processService } from '../../services/process.js';
|
|
14
14
|
import { memoryService } from '../memory/service.js';
|
|
15
15
|
import { guessMimeType, resolveChannelFile, statChannelFile } from './files.js';
|
|
@@ -837,13 +837,12 @@ export const storageService = {
|
|
|
837
837
|
return Array.from(deduped.values()).filter((agent) => !agent.hidden);
|
|
838
838
|
},
|
|
839
839
|
getPlugins: async () => {
|
|
840
|
-
const [builtIn, fromDisk
|
|
840
|
+
const [builtIn, fromDisk] = await Promise.all([
|
|
841
841
|
listBuiltInPluginDescriptors(),
|
|
842
842
|
listPluginsFromDisk(),
|
|
843
|
-
listRegistryModelOptions(),
|
|
844
843
|
]);
|
|
845
|
-
const
|
|
846
|
-
const merged = [...
|
|
844
|
+
const enrichPlugin = (plugin) => enrichOpenbotPluginDescriptor(plugin);
|
|
845
|
+
const merged = [...builtIn.map(enrichPlugin), ...fromDisk.map(enrichPlugin)];
|
|
847
846
|
const deduped = new Map();
|
|
848
847
|
for (const plugin of merged) {
|
|
849
848
|
if (!deduped.has(plugin.id)) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
2
|
+
import { DEFAULT_CLOUD_OPENBOT_AUTH_MODE, isCloudMode } from '../../app/cloud-mode.js';
|
|
3
|
+
/** Injects cloud platform fields into the openbot plugin descriptor for settings UI. */
|
|
4
|
+
export function enrichOpenbotPluginDescriptor(descriptor) {
|
|
5
|
+
if (descriptor.id !== OPENBOT_PLUGIN_ID || !descriptor.configSchema)
|
|
6
|
+
return descriptor;
|
|
7
|
+
if (!isCloudMode())
|
|
8
|
+
return descriptor;
|
|
9
|
+
const { authMode: _authMode, ...otherProperties } = descriptor.configSchema.properties;
|
|
10
|
+
const cloudAuthModeProperty = {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'Credits uses OpenBot platform billing. BYOK uses your own API keys.',
|
|
13
|
+
enum: ['credits', 'byok'],
|
|
14
|
+
default: DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
15
|
+
options: [
|
|
16
|
+
{
|
|
17
|
+
label: 'Credits',
|
|
18
|
+
value: 'credits',
|
|
19
|
+
description: 'Use OpenBot platform credits.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
label: 'Bring your own key',
|
|
23
|
+
value: 'byok',
|
|
24
|
+
description: 'Use your own provider API keys.',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
...descriptor,
|
|
30
|
+
configSchema: {
|
|
31
|
+
...descriptor.configSchema,
|
|
32
|
+
properties: {
|
|
33
|
+
authMode: cloudAuthModeProperty,
|
|
34
|
+
...otherProperties,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -2,15 +2,12 @@ import { DEFAULT_CLOUD_OPENBOT_AUTH_MODE, isCloudMode, isCloudSystemAgent, parse
|
|
|
2
2
|
import { ORCHESTRATOR_AGENT_ID } from '../../app/agent-ids.js';
|
|
3
3
|
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
4
4
|
import { getBaseDir, resolvePath, saveConfig } from '../../app/config.js';
|
|
5
|
-
import { listApiKeyProvidersFromRegistry, resolveModelRegistry, } from './model-registry.js';
|
|
6
5
|
export function createPluginHost(runAgent) {
|
|
7
6
|
return {
|
|
8
7
|
runAgent,
|
|
9
8
|
isCloudSystemAgent,
|
|
10
9
|
isCloudMode,
|
|
11
10
|
parseOpenbotAuthMode,
|
|
12
|
-
resolveModelRegistry,
|
|
13
|
-
listApiKeyProvidersFromRegistry,
|
|
14
11
|
saveConfig,
|
|
15
12
|
getBaseDir,
|
|
16
13
|
resolvePath,
|
|
@@ -74,7 +74,7 @@ export function enrichOpenbotPluginDescriptor(descriptor, modelOptions) {
|
|
|
74
74
|
: pickDefaultModelValue(modelOptions) ?? staticDefault;
|
|
75
75
|
const nextModelProperty = {
|
|
76
76
|
...modelProperty,
|
|
77
|
-
description: 'Model from the hosted marketplace registry.',
|
|
77
|
+
description: modelProperty.description ?? 'Model from the hosted marketplace registry.',
|
|
78
78
|
};
|
|
79
79
|
if (values.length > 0) {
|
|
80
80
|
nextModelProperty.enum = values;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"build": "tsc && mkdir -p dist/assets && cp src/assets/icon.svg dist/assets/icon.svg",
|
|
12
12
|
"start": "node dist/app/cli.js start",
|
|
13
13
|
"format": "prettier --write .",
|
|
14
|
-
"push": "fly deploy --build-only --push --config deploy/fly.toml -a openbot-runtime --image-label v0.5.
|
|
14
|
+
"push": "fly deploy --build-only --push --config deploy/fly.toml -a openbot-runtime --image-label v0.5.9"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"openbot": "./dist/app/cli.js"
|
package/src/app/cli.ts
CHANGED
package/src/app/types.ts
CHANGED
|
@@ -42,6 +42,8 @@ export type AgentInvokeEvent = BaseEvent & {
|
|
|
42
42
|
role?: 'user' | 'assistant' | 'system';
|
|
43
43
|
content: string;
|
|
44
44
|
agentId?: string;
|
|
45
|
+
/** Per-run config overrides: flat fields (e.g. model) or nested by plugin id. */
|
|
46
|
+
config?: Record<string, unknown>;
|
|
45
47
|
};
|
|
46
48
|
};
|
|
47
49
|
|
package/src/harness/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { abortRegistry, abortKey } from '../services/abort.js';
|
|
|
9
9
|
import { loadConfig } from '../app/config.js';
|
|
10
10
|
import { getPublicBaseUrl } from '../plugins/storage/files.js';
|
|
11
11
|
import { createPluginHost } from '../services/plugins/host.js';
|
|
12
|
+
import { getInvokeConfigOverrides, mergePluginConfig } from './merge-plugin-config.js';
|
|
12
13
|
|
|
13
14
|
export { STATE_AGENT_ID, ORCHESTRATOR_AGENT_ID };
|
|
14
15
|
|
|
@@ -121,6 +122,13 @@ export async function runAgent(options: RunAgentOptions): Promise<void> {
|
|
|
121
122
|
const builder = melony<OpenBotState, OpenBotEvent>().initialState(state);
|
|
122
123
|
|
|
123
124
|
const pluginHost = createPluginHost(runAgent);
|
|
125
|
+
const invokeConfigOverrides = getInvokeConfigOverrides(event);
|
|
126
|
+
const pluginConfigs = Object.fromEntries(
|
|
127
|
+
pluginRefs.map((ref) => [
|
|
128
|
+
ref.id,
|
|
129
|
+
mergePluginConfig(ref.config, invokeConfigOverrides, ref.id),
|
|
130
|
+
]),
|
|
131
|
+
);
|
|
124
132
|
|
|
125
133
|
for (const ref of pluginRefs) {
|
|
126
134
|
const plugin = await resolvePlugin(ref.id);
|
|
@@ -130,7 +138,7 @@ export async function runAgent(options: RunAgentOptions): Promise<void> {
|
|
|
130
138
|
plugin.factory({
|
|
131
139
|
agentId,
|
|
132
140
|
agentDetails,
|
|
133
|
-
config: ref.
|
|
141
|
+
config: pluginConfigs[ref.id],
|
|
134
142
|
storage: storageService,
|
|
135
143
|
tools,
|
|
136
144
|
publicBaseUrl,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { OpenBotEvent } from '../app/types.js';
|
|
2
|
+
|
|
3
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isPluginKeyedConfig(config: Record<string, unknown>): boolean {
|
|
8
|
+
return Object.values(config).every(isPlainObject);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getInvokeConfigOverrides(event: OpenBotEvent): Record<string, unknown> | undefined {
|
|
12
|
+
if (event.type !== 'agent:invoke') return undefined;
|
|
13
|
+
|
|
14
|
+
const config = event.data?.config;
|
|
15
|
+
if (!isPlainObject(config) || Object.keys(config).length === 0) return undefined;
|
|
16
|
+
|
|
17
|
+
return config;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function mergePluginConfig(
|
|
21
|
+
base: Record<string, unknown> | undefined,
|
|
22
|
+
overrides: Record<string, unknown> | undefined,
|
|
23
|
+
pluginId: string,
|
|
24
|
+
): Record<string, unknown> {
|
|
25
|
+
const baseConfig = base ?? {};
|
|
26
|
+
if (!overrides) return baseConfig;
|
|
27
|
+
|
|
28
|
+
if (isPluginKeyedConfig(overrides)) {
|
|
29
|
+
const pluginOverride = overrides[pluginId];
|
|
30
|
+
if (!isPlainObject(pluginOverride)) return baseConfig;
|
|
31
|
+
return { ...baseConfig, ...pluginOverride };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { ...baseConfig, ...overrides };
|
|
35
|
+
}
|
|
@@ -33,10 +33,7 @@ import {
|
|
|
33
33
|
import type { PluginRef } from '../../services/plugins/types.js';
|
|
34
34
|
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
35
35
|
import { listBuiltInPlugins, parsePluginModule } from '../../services/plugins/registry.js';
|
|
36
|
-
import {
|
|
37
|
-
enrichOpenbotPluginDescriptor,
|
|
38
|
-
listRegistryModelOptions,
|
|
39
|
-
} from '../../services/plugins/model-registry.js';
|
|
36
|
+
import { enrichOpenbotPluginDescriptor } from '../../services/plugins/enrich-openbot-plugin.js';
|
|
40
37
|
import { OpenBotEvent, OpenBotState } from '../../app/types.js';
|
|
41
38
|
import { processService } from '../../services/process.js';
|
|
42
39
|
import { memoryService } from '../memory/service.js';
|
|
@@ -1056,17 +1053,14 @@ export const storageService = {
|
|
|
1056
1053
|
return Array.from(deduped.values()).filter((agent) => !agent.hidden);
|
|
1057
1054
|
},
|
|
1058
1055
|
getPlugins: async (): Promise<PluginDescriptor[]> => {
|
|
1059
|
-
const [builtIn, fromDisk
|
|
1056
|
+
const [builtIn, fromDisk] = await Promise.all([
|
|
1060
1057
|
listBuiltInPluginDescriptors(),
|
|
1061
1058
|
listPluginsFromDisk(),
|
|
1062
|
-
listRegistryModelOptions(),
|
|
1063
1059
|
]);
|
|
1064
1060
|
|
|
1065
|
-
const
|
|
1066
|
-
enrichOpenbotPluginDescriptor(plugin, modelOptions),
|
|
1067
|
-
);
|
|
1061
|
+
const enrichPlugin = (plugin: PluginDescriptor) => enrichOpenbotPluginDescriptor(plugin);
|
|
1068
1062
|
|
|
1069
|
-
const merged = [...
|
|
1063
|
+
const merged = [...builtIn.map(enrichPlugin), ...fromDisk.map(enrichPlugin)];
|
|
1070
1064
|
const deduped = new Map<string, PluginDescriptor>();
|
|
1071
1065
|
for (const plugin of merged) {
|
|
1072
1066
|
if (!deduped.has(plugin.id)) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
2
|
+
import { DEFAULT_CLOUD_OPENBOT_AUTH_MODE, isCloudMode } from '../../app/cloud-mode.js';
|
|
3
|
+
import type { ConfigSchema, PluginDescriptor } from './domain.js';
|
|
4
|
+
|
|
5
|
+
/** Injects cloud platform fields into the openbot plugin descriptor for settings UI. */
|
|
6
|
+
export function enrichOpenbotPluginDescriptor(descriptor: PluginDescriptor): PluginDescriptor {
|
|
7
|
+
if (descriptor.id !== OPENBOT_PLUGIN_ID || !descriptor.configSchema) return descriptor;
|
|
8
|
+
if (!isCloudMode()) return descriptor;
|
|
9
|
+
|
|
10
|
+
const { authMode: _authMode, ...otherProperties } = descriptor.configSchema.properties;
|
|
11
|
+
|
|
12
|
+
const cloudAuthModeProperty: ConfigSchema['properties'][string] = {
|
|
13
|
+
type: 'string',
|
|
14
|
+
description: 'Credits uses OpenBot platform billing. BYOK uses your own API keys.',
|
|
15
|
+
enum: ['credits', 'byok'],
|
|
16
|
+
default: DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
17
|
+
options: [
|
|
18
|
+
{
|
|
19
|
+
label: 'Credits',
|
|
20
|
+
value: 'credits',
|
|
21
|
+
description: 'Use OpenBot platform credits.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Bring your own key',
|
|
25
|
+
value: 'byok',
|
|
26
|
+
description: 'Use your own provider API keys.',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
...descriptor,
|
|
33
|
+
configSchema: {
|
|
34
|
+
...descriptor.configSchema,
|
|
35
|
+
properties: {
|
|
36
|
+
authMode: cloudAuthModeProperty,
|
|
37
|
+
...otherProperties,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -8,10 +8,6 @@ import {
|
|
|
8
8
|
import { ORCHESTRATOR_AGENT_ID } from '../../app/agent-ids.js';
|
|
9
9
|
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
10
10
|
import { getBaseDir, resolvePath, saveConfig } from '../../app/config.js';
|
|
11
|
-
import {
|
|
12
|
-
listApiKeyProvidersFromRegistry,
|
|
13
|
-
resolveModelRegistry,
|
|
14
|
-
} from './model-registry.js';
|
|
15
11
|
import type { PluginHost, RunAgentHostOptions } from './types.js';
|
|
16
12
|
|
|
17
13
|
export function createPluginHost(
|
|
@@ -22,8 +18,6 @@ export function createPluginHost(
|
|
|
22
18
|
isCloudSystemAgent,
|
|
23
19
|
isCloudMode,
|
|
24
20
|
parseOpenbotAuthMode,
|
|
25
|
-
resolveModelRegistry,
|
|
26
|
-
listApiKeyProvidersFromRegistry,
|
|
27
21
|
saveConfig,
|
|
28
22
|
getBaseDir,
|
|
29
23
|
resolvePath,
|
|
@@ -38,12 +38,6 @@ export interface PluginHost {
|
|
|
38
38
|
isCloudSystemAgent: (agentId: string) => boolean;
|
|
39
39
|
isCloudMode: () => boolean;
|
|
40
40
|
parseOpenbotAuthMode: (value: unknown) => 'credits' | 'byok';
|
|
41
|
-
resolveModelRegistry: () => ReturnType<
|
|
42
|
-
typeof import('./model-registry.js').resolveModelRegistry
|
|
43
|
-
>;
|
|
44
|
-
listApiKeyProvidersFromRegistry: (
|
|
45
|
-
registry: Awaited<ReturnType<PluginHost['resolveModelRegistry']>>,
|
|
46
|
-
) => Array<{ id: string; label: string }>;
|
|
47
41
|
saveConfig: (patch: Record<string, unknown>) => void;
|
|
48
42
|
getBaseDir: () => string;
|
|
49
43
|
resolvePath: (p: string) => string;
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_MARKETPLACE_REGISTRY_URL,
|
|
3
|
-
loadConfig,
|
|
4
|
-
} from '../../app/config.js';
|
|
5
|
-
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
8
|
-
isCloudMode,
|
|
9
|
-
} from '../../app/cloud-mode.js';
|
|
10
|
-
import type { ConfigSchema, PluginDescriptor } from './domain.js';
|
|
11
|
-
|
|
12
|
-
export type RegistryProviderCatalog = Record<
|
|
13
|
-
string,
|
|
14
|
-
{
|
|
15
|
-
label: string;
|
|
16
|
-
models: Array<{ id: string; label: string; description: string }>;
|
|
17
|
-
}
|
|
18
|
-
>;
|
|
19
|
-
|
|
20
|
-
export type ModelRegistry = {
|
|
21
|
-
providers?: RegistryProviderCatalog;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type RegistryModelOption = {
|
|
25
|
-
value: string;
|
|
26
|
-
label: string;
|
|
27
|
-
description?: string;
|
|
28
|
-
provider: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
let cachedRegistry: ModelRegistry | null = null;
|
|
32
|
-
let cacheUrl: string | null = null;
|
|
33
|
-
|
|
34
|
-
function getRegistryUrl(): string {
|
|
35
|
-
const { marketplaceRegistryUrl } = loadConfig();
|
|
36
|
-
return marketplaceRegistryUrl?.trim() || DEFAULT_MARKETPLACE_REGISTRY_URL;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function resolveModelRegistry(): Promise<ModelRegistry> {
|
|
40
|
-
const url = getRegistryUrl();
|
|
41
|
-
if (cachedRegistry && cacheUrl === url) return cachedRegistry;
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const res = await fetch(url, {
|
|
45
|
-
headers: { Accept: 'application/json' },
|
|
46
|
-
signal: AbortSignal.timeout(15_000),
|
|
47
|
-
});
|
|
48
|
-
if (!res.ok) {
|
|
49
|
-
throw new Error(`Registry HTTP ${res.status} ${res.statusText}`);
|
|
50
|
-
}
|
|
51
|
-
cachedRegistry = (await res.json()) as ModelRegistry;
|
|
52
|
-
cacheUrl = url;
|
|
53
|
-
return cachedRegistry;
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.warn(
|
|
56
|
-
'[model-registry] fetch failed:',
|
|
57
|
-
err instanceof Error ? err.message : err,
|
|
58
|
-
);
|
|
59
|
-
return { providers: {} };
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function listModelOptionsFromRegistry(registry: ModelRegistry): RegistryModelOption[] {
|
|
64
|
-
const providers = registry.providers ?? {};
|
|
65
|
-
const out: RegistryModelOption[] = [];
|
|
66
|
-
|
|
67
|
-
for (const [providerId, provider] of Object.entries(providers)) {
|
|
68
|
-
for (const model of provider.models ?? []) {
|
|
69
|
-
out.push({
|
|
70
|
-
value: `${providerId}/${model.id}`,
|
|
71
|
-
label: `${provider.label} — ${model.label}`,
|
|
72
|
-
description: model.description,
|
|
73
|
-
provider: providerId,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return out;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function listRegistryModelOptions(): Promise<RegistryModelOption[]> {
|
|
82
|
-
const registry = await resolveModelRegistry();
|
|
83
|
-
return listModelOptionsFromRegistry(registry);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function listApiKeyProvidersFromRegistry(
|
|
87
|
-
registry: ModelRegistry,
|
|
88
|
-
): Array<{ id: string; label: string }> {
|
|
89
|
-
const providers = registry.providers ?? {};
|
|
90
|
-
return Object.entries(providers).map(([id, provider]) => ({
|
|
91
|
-
id,
|
|
92
|
-
label: provider.label,
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function pickDefaultModelValue(options: RegistryModelOption[]): string | undefined {
|
|
97
|
-
if (options.length === 0) return undefined;
|
|
98
|
-
const values = options.map((option) => option.value);
|
|
99
|
-
const preferred = values.find((value) => value.startsWith('openai/'));
|
|
100
|
-
return preferred ?? values[0];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function enrichOpenbotPluginDescriptor(
|
|
104
|
-
descriptor: PluginDescriptor,
|
|
105
|
-
modelOptions: RegistryModelOption[],
|
|
106
|
-
): PluginDescriptor {
|
|
107
|
-
if (descriptor.id !== OPENBOT_PLUGIN_ID || !descriptor.configSchema) return descriptor;
|
|
108
|
-
|
|
109
|
-
const modelProperty = descriptor.configSchema.properties.model;
|
|
110
|
-
if (!modelProperty) return descriptor;
|
|
111
|
-
|
|
112
|
-
const values = modelOptions.map((option) => option.value);
|
|
113
|
-
const staticDefault =
|
|
114
|
-
typeof modelProperty.default === 'string' ? modelProperty.default : undefined;
|
|
115
|
-
const defaultModel =
|
|
116
|
-
staticDefault && values.includes(staticDefault)
|
|
117
|
-
? staticDefault
|
|
118
|
-
: pickDefaultModelValue(modelOptions) ?? staticDefault;
|
|
119
|
-
|
|
120
|
-
const nextModelProperty: ConfigSchema['properties'][string] = {
|
|
121
|
-
...modelProperty,
|
|
122
|
-
description: 'Model from the hosted marketplace registry.',
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
if (values.length > 0) {
|
|
126
|
-
nextModelProperty.enum = values;
|
|
127
|
-
nextModelProperty.options = modelOptions.map((option) => ({
|
|
128
|
-
label: option.label,
|
|
129
|
-
value: option.value,
|
|
130
|
-
description: option.description,
|
|
131
|
-
}));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (defaultModel) {
|
|
135
|
-
nextModelProperty.default = defaultModel;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const { model: _model, authMode: _authMode, ...otherProperties } =
|
|
139
|
-
descriptor.configSchema.properties;
|
|
140
|
-
|
|
141
|
-
const cloudAuthModeProperty: ConfigSchema['properties'][string] = {
|
|
142
|
-
type: 'string',
|
|
143
|
-
description: 'Credits uses OpenBot platform billing. BYOK uses your own API keys.',
|
|
144
|
-
enum: ['credits', 'byok'],
|
|
145
|
-
default: DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
146
|
-
options: [
|
|
147
|
-
{
|
|
148
|
-
label: 'Credits',
|
|
149
|
-
value: 'credits',
|
|
150
|
-
description: 'Use OpenBot platform credits.',
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
label: 'Bring your own key',
|
|
154
|
-
value: 'byok',
|
|
155
|
-
description: 'Use your own provider API keys.',
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const nextProperties: ConfigSchema['properties'] = isCloudMode()
|
|
161
|
-
? {
|
|
162
|
-
authMode: cloudAuthModeProperty,
|
|
163
|
-
model: nextModelProperty,
|
|
164
|
-
...otherProperties,
|
|
165
|
-
}
|
|
166
|
-
: {
|
|
167
|
-
model: nextModelProperty,
|
|
168
|
-
...otherProperties,
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
...descriptor,
|
|
173
|
-
configSchema: {
|
|
174
|
-
...descriptor.configSchema,
|
|
175
|
-
properties: nextProperties,
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
}
|