@robota-sdk/agent-command 3.0.0-beta.64
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/LICENSE +21 -0
- package/dist/node/index.cjs +30 -0
- package/dist/node/index.d.ts +293 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +31 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +48 -0
- package/src/agent/__tests__/agent-command.test.ts +504 -0
- package/src/agent/agent-command-module.ts +82 -0
- package/src/agent/agent-command-parser.ts +180 -0
- package/src/agent/agent-command.ts +235 -0
- package/src/agent/index.ts +7 -0
- package/src/background/__tests__/background-command-module.test.ts +255 -0
- package/src/background/background-command-module.ts +53 -0
- package/src/background/background-command.ts +63 -0
- package/src/background/index.ts +6 -0
- package/src/compact/__tests__/compact-command-module.test.ts +162 -0
- package/src/compact/compact-command-module.ts +51 -0
- package/src/compact/compact-command.ts +21 -0
- package/src/compact/index.ts +6 -0
- package/src/context/__tests__/context-command-module.test.ts +294 -0
- package/src/context/context-command-module.ts +54 -0
- package/src/context/context-command.ts +298 -0
- package/src/context/index.ts +6 -0
- package/src/exit/__tests__/exit-command-module.test.ts +35 -0
- package/src/exit/exit-command-module.ts +48 -0
- package/src/exit/exit-command.ts +10 -0
- package/src/exit/index.ts +6 -0
- package/src/help/__tests__/help-command-module.test.ts +106 -0
- package/src/help/help-command-module.ts +48 -0
- package/src/help/help-command.ts +9 -0
- package/src/help/index.ts +6 -0
- package/src/index.ts +20 -0
- package/src/language/__tests__/language-command-module.test.ts +105 -0
- package/src/language/index.ts +6 -0
- package/src/language/language-command-module.ts +56 -0
- package/src/language/language-command.ts +22 -0
- package/src/memory/__tests__/memory-command-module.test.ts +272 -0
- package/src/memory/index.ts +6 -0
- package/src/memory/memory-command-module.ts +57 -0
- package/src/memory/memory-command.ts +234 -0
- package/src/mode/__tests__/mode-command-module.test.ts +143 -0
- package/src/mode/index.ts +6 -0
- package/src/mode/mode-command-module.ts +56 -0
- package/src/mode/mode-command.ts +34 -0
- package/src/model/__tests__/model-command-module.test.ts +273 -0
- package/src/model/index.ts +6 -0
- package/src/model/model-command-module.ts +68 -0
- package/src/model/model-command.ts +40 -0
- package/src/permissions/__tests__/permissions-command-module.test.ts +164 -0
- package/src/permissions/index.ts +6 -0
- package/src/permissions/permissions-command-module.ts +56 -0
- package/src/permissions/permissions-command.ts +45 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +214 -0
- package/src/plugin/index.ts +7 -0
- package/src/plugin/plugin-command-module.ts +81 -0
- package/src/plugin/plugin-command.ts +230 -0
- package/src/provider/__tests__/provider-command-module.test.ts +488 -0
- package/src/provider/__tests__/provider-setup-flow.test.ts +43 -0
- package/src/provider/index.ts +30 -0
- package/src/provider/provider-command-execution.ts +150 -0
- package/src/provider/provider-command-module.ts +65 -0
- package/src/provider/provider-command-profile-lifecycle.ts +211 -0
- package/src/provider/provider-command-profile-operations.ts +198 -0
- package/src/provider/provider-command-profile.ts +109 -0
- package/src/provider/provider-command-setup.ts +104 -0
- package/src/provider/provider-setup-flow.ts +309 -0
- package/src/reset/__tests__/reset-command-module.test.ts +63 -0
- package/src/reset/index.ts +2 -0
- package/src/reset/reset-command-module.ts +49 -0
- package/src/reset/reset-command.ts +10 -0
- package/src/rewind/__tests__/rewind-command-module.test.ts +215 -0
- package/src/rewind/index.ts +2 -0
- package/src/rewind/rewind-command-module.ts +57 -0
- package/src/rewind/rewind-command.ts +184 -0
- package/src/session/__tests__/session-command-module.test.ts +339 -0
- package/src/session/index.ts +17 -0
- package/src/session/session-command-module.ts +168 -0
- package/src/session/session-command.ts +74 -0
- package/src/settings/index.ts +7 -0
- package/src/settings/settings-command-module.ts +50 -0
- package/src/skills/__tests__/skills-command-module.test.ts +157 -0
- package/src/skills/index.ts +6 -0
- package/src/skills/skills-command-module.ts +62 -0
- package/src/skills/skills-command.ts +110 -0
- package/src/statusline/__tests__/statusline-command-module.test.ts +95 -0
- package/src/statusline/index.ts +6 -0
- package/src/statusline/statusline-command-module.ts +56 -0
- package/src/statusline/statusline-command.ts +79 -0
- package/src/user-local/__tests__/user-local-command.test.ts +145 -0
- package/src/user-local/index.ts +13 -0
- package/src/user-local/user-local-command-constants.ts +5 -0
- package/src/user-local/user-local-command-module.ts +67 -0
- package/src/user-local/user-local-command.ts +205 -0
- package/src/user-local/user-local-memory-command.ts +147 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { findProviderDefinition, formatSupportedProviderTypes } from '@robota-sdk/agent-core';
|
|
2
|
+
import type {
|
|
3
|
+
ICommandResult,
|
|
4
|
+
IProviderCommandModuleOptions,
|
|
5
|
+
IProviderProfileSettings,
|
|
6
|
+
} from '@robota-sdk/agent-framework';
|
|
7
|
+
import { testProviderProfileCommand } from '@robota-sdk/agent-framework';
|
|
8
|
+
import { formatProviderSetupChoiceLabel } from './provider-setup-flow.js';
|
|
9
|
+
import { createSetupFlow, createProviderSetupInteraction } from './provider-command-setup.js';
|
|
10
|
+
import { buildProviderSwitch } from './provider-command-profile-operations.js';
|
|
11
|
+
import { createProviderProfileSelectionInteraction } from './provider-command-profile.js';
|
|
12
|
+
|
|
13
|
+
export async function executeProviderCommand(
|
|
14
|
+
args: string,
|
|
15
|
+
options: IProviderCommandModuleOptions,
|
|
16
|
+
): Promise<ICommandResult> {
|
|
17
|
+
const settings = options.settings.readMergedSettings();
|
|
18
|
+
const trimmedArgs = args.trim();
|
|
19
|
+
if (trimmedArgs.length === 0) {
|
|
20
|
+
return buildProviderProfilePicker(settings.currentProvider, settings.providers, options);
|
|
21
|
+
}
|
|
22
|
+
const [subcommand = 'current', profileArg] = trimmedArgs.split(/\s+/);
|
|
23
|
+
|
|
24
|
+
if (subcommand === 'list') {
|
|
25
|
+
return buildProviderProfilePicker(settings.currentProvider, settings.providers, options);
|
|
26
|
+
}
|
|
27
|
+
if (subcommand === 'current' || subcommand === '') {
|
|
28
|
+
return {
|
|
29
|
+
message: formatCurrentProvider(settings.currentProvider, settings.providers),
|
|
30
|
+
success: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (subcommand === 'use') {
|
|
34
|
+
return buildProviderSwitch(settings.providers, profileArg, options);
|
|
35
|
+
}
|
|
36
|
+
if (subcommand === 'test') {
|
|
37
|
+
return await testProviderProfileCommand(
|
|
38
|
+
settings.currentProvider,
|
|
39
|
+
settings.providers,
|
|
40
|
+
profileArg,
|
|
41
|
+
options,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (subcommand === 'add') {
|
|
45
|
+
return buildProviderSetup(profileArg, options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
message: 'Usage: provider [current|list|use <profile>|add <type>|test [profile]]',
|
|
50
|
+
success: false,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildProviderProfilePicker(
|
|
55
|
+
currentProvider: string | undefined,
|
|
56
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
57
|
+
options: IProviderCommandModuleOptions,
|
|
58
|
+
): ICommandResult {
|
|
59
|
+
const message = formatProviderList(currentProvider, providers);
|
|
60
|
+
if (Object.keys(providers ?? {}).length === 0) {
|
|
61
|
+
return { message, success: true };
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
message,
|
|
65
|
+
success: true,
|
|
66
|
+
interaction: createProviderProfileSelectionInteraction(currentProvider, providers, options),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatProviderList(
|
|
71
|
+
currentProvider: string | undefined,
|
|
72
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
73
|
+
): string {
|
|
74
|
+
const entries = Object.entries(providers ?? {});
|
|
75
|
+
if (entries.length === 0) {
|
|
76
|
+
return 'No provider profiles configured.';
|
|
77
|
+
}
|
|
78
|
+
return entries
|
|
79
|
+
.map(([name, profile]) => {
|
|
80
|
+
const marker = name === currentProvider ? '*' : '-';
|
|
81
|
+
return `${marker} ${name}: ${profile.type ?? 'unknown'} ${profile.model ?? '(no model)'}`;
|
|
82
|
+
})
|
|
83
|
+
.join('\n');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function formatCurrentProvider(
|
|
87
|
+
currentProvider: string | undefined,
|
|
88
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
89
|
+
): string {
|
|
90
|
+
if (!currentProvider) {
|
|
91
|
+
return 'No current provider configured.';
|
|
92
|
+
}
|
|
93
|
+
const profile = providers?.[currentProvider];
|
|
94
|
+
if (!profile) {
|
|
95
|
+
return `Current provider "${currentProvider}" was not found in providers.`;
|
|
96
|
+
}
|
|
97
|
+
return [
|
|
98
|
+
`Current provider: ${currentProvider}`,
|
|
99
|
+
`Type: ${profile.type ?? 'unknown'}`,
|
|
100
|
+
`Model: ${profile.model ?? '(no model)'}`,
|
|
101
|
+
...(profile.baseURL ? [`Base URL: ${profile.baseURL}`] : []),
|
|
102
|
+
].join('\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildProviderSetup(
|
|
106
|
+
type: string | undefined,
|
|
107
|
+
options: IProviderCommandModuleOptions,
|
|
108
|
+
): ICommandResult {
|
|
109
|
+
if (type === undefined || type.length === 0) {
|
|
110
|
+
return {
|
|
111
|
+
message: 'Provider setup requested. Select a provider to continue.',
|
|
112
|
+
success: true,
|
|
113
|
+
interaction: createProviderSelectionInteraction(options),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (findProviderDefinition(options.providerDefinitions, type) === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
message: `Usage: provider add <type>. Supported: ${formatSupportedProviderTypes(options.providerDefinitions)}`,
|
|
119
|
+
success: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
message: `Provider setup requested: ${type}`,
|
|
124
|
+
success: true,
|
|
125
|
+
interaction: createProviderSetupInteraction(createSetupFlow(type, options), options),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function createProviderSelectionInteraction(options: IProviderCommandModuleOptions) {
|
|
130
|
+
return {
|
|
131
|
+
prompt: {
|
|
132
|
+
kind: 'choice' as const,
|
|
133
|
+
title: 'Select provider',
|
|
134
|
+
options: options.providerDefinitions.map((definition) => ({
|
|
135
|
+
value: definition.type,
|
|
136
|
+
label: formatProviderSetupChoiceLabel(definition),
|
|
137
|
+
})),
|
|
138
|
+
maxVisible: 6,
|
|
139
|
+
},
|
|
140
|
+
submit: (value: string) => {
|
|
141
|
+
const flow = createSetupFlow(value, options);
|
|
142
|
+
return {
|
|
143
|
+
message: `Provider setup requested: ${value}`,
|
|
144
|
+
success: true,
|
|
145
|
+
interaction: createProviderSetupInteraction(flow, options),
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
cancel: () => ({ message: 'Provider setup cancelled.', success: true }),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ICommand,
|
|
3
|
+
ICommandModule as TCommandModule,
|
|
4
|
+
ICommandSource,
|
|
5
|
+
IProviderCommandModuleOptions,
|
|
6
|
+
IProviderCommandSettingsAdapter,
|
|
7
|
+
ISystemCommand as TSystemCommand,
|
|
8
|
+
} from '@robota-sdk/agent-framework';
|
|
9
|
+
import { executeProviderCommand } from './provider-command-execution.js';
|
|
10
|
+
export type { IProviderCommandModuleOptions, IProviderCommandSettingsAdapter };
|
|
11
|
+
|
|
12
|
+
function buildProviderSubcommands(): ICommand[] {
|
|
13
|
+
return [
|
|
14
|
+
{ name: 'current', description: 'Show current provider', source: 'provider' },
|
|
15
|
+
{ name: 'list', description: 'List provider profiles', source: 'provider' },
|
|
16
|
+
{ name: 'use', description: 'Switch provider profile', source: 'provider' },
|
|
17
|
+
{ name: 'add', description: 'Configure a provider profile', source: 'provider' },
|
|
18
|
+
{ name: 'test', description: 'Test provider profile', source: 'provider' },
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createProviderCommandEntry(): ICommand {
|
|
23
|
+
return {
|
|
24
|
+
name: 'provider',
|
|
25
|
+
displayName: 'Provider Setup',
|
|
26
|
+
description: 'Manage provider profiles',
|
|
27
|
+
source: 'provider',
|
|
28
|
+
modelInvocable: false,
|
|
29
|
+
argumentHint: 'current | list | use <profile> | add [type] | test [profile]',
|
|
30
|
+
subcommands: buildProviderSubcommands(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ProviderCommandSource implements ICommandSource {
|
|
35
|
+
readonly name = 'provider';
|
|
36
|
+
|
|
37
|
+
getCommands(): ICommand[] {
|
|
38
|
+
return [createProviderCommandEntry()];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createProviderSystemCommand(options: IProviderCommandModuleOptions): TSystemCommand {
|
|
43
|
+
const entry = createProviderCommandEntry();
|
|
44
|
+
return {
|
|
45
|
+
name: entry.name,
|
|
46
|
+
displayName: entry.displayName,
|
|
47
|
+
description: entry.description,
|
|
48
|
+
requiresPermission: false,
|
|
49
|
+
userInvocable: true,
|
|
50
|
+
modelInvocable: false,
|
|
51
|
+
argumentHint: entry.argumentHint,
|
|
52
|
+
subcommands: entry.subcommands,
|
|
53
|
+
execute: async (_session, args) => executeProviderCommand(args, options),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createProviderCommandModule(
|
|
58
|
+
options: IProviderCommandModuleOptions,
|
|
59
|
+
): TCommandModule {
|
|
60
|
+
return {
|
|
61
|
+
name: 'agent-command-provider',
|
|
62
|
+
commandSources: [new ProviderCommandSource()],
|
|
63
|
+
systemCommands: [createProviderSystemCommand(options)],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ICommandInteraction,
|
|
3
|
+
ICommandResult,
|
|
4
|
+
IProviderCommandModuleOptions,
|
|
5
|
+
} from '@robota-sdk/agent-framework';
|
|
6
|
+
import {
|
|
7
|
+
deleteProviderProfile,
|
|
8
|
+
sanitizeProviderProfileName,
|
|
9
|
+
upsertProviderProfile,
|
|
10
|
+
} from '@robota-sdk/agent-framework';
|
|
11
|
+
import { formatProviderChoiceLabel } from './provider-command-profile-operations.js';
|
|
12
|
+
|
|
13
|
+
const YES = 'yes';
|
|
14
|
+
const MAX_DUPLICATE_PROFILE_SUFFIX = 1000;
|
|
15
|
+
const PROVIDER_RESTART_EFFECT = {
|
|
16
|
+
type: 'session-restart-requested',
|
|
17
|
+
reason: 'other',
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export function buildProviderDuplicate(
|
|
21
|
+
profileName: string,
|
|
22
|
+
options: IProviderCommandModuleOptions,
|
|
23
|
+
): ICommandResult {
|
|
24
|
+
const settings = options.settings.readMergedSettings();
|
|
25
|
+
if (!settings.providers?.[profileName]) {
|
|
26
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
27
|
+
}
|
|
28
|
+
const defaultName = suggestDuplicateProfileName(profileName, Object.keys(settings.providers));
|
|
29
|
+
return {
|
|
30
|
+
message: `Provider duplicate requested: ${profileName}`,
|
|
31
|
+
success: true,
|
|
32
|
+
interaction: createProviderDuplicateInteraction(profileName, defaultName, options),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createProviderDuplicateInteraction(
|
|
37
|
+
profileName: string,
|
|
38
|
+
defaultName: string,
|
|
39
|
+
options: IProviderCommandModuleOptions,
|
|
40
|
+
): ICommandInteraction {
|
|
41
|
+
return {
|
|
42
|
+
prompt: {
|
|
43
|
+
kind: 'text',
|
|
44
|
+
title: `Duplicate ${profileName} as`,
|
|
45
|
+
placeholder: defaultName,
|
|
46
|
+
allowEmpty: true,
|
|
47
|
+
validate: (value) => validateDuplicateProfileName(value, defaultName, options),
|
|
48
|
+
},
|
|
49
|
+
submit: (value) => completeProviderDuplicate(profileName, value, defaultName, options),
|
|
50
|
+
cancel: () => ({ message: 'Provider duplicate cancelled.', success: true }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateDuplicateProfileName(
|
|
55
|
+
value: string,
|
|
56
|
+
defaultName: string,
|
|
57
|
+
options: IProviderCommandModuleOptions,
|
|
58
|
+
): string | undefined {
|
|
59
|
+
const name = normalizeProfileName(value, defaultName);
|
|
60
|
+
if (name.length === 0) {
|
|
61
|
+
return 'Required';
|
|
62
|
+
}
|
|
63
|
+
if (options.settings.readMergedSettings().providers?.[name] !== undefined) {
|
|
64
|
+
return `Provider profile "${name}" already exists`;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function completeProviderDuplicate(
|
|
70
|
+
profileName: string,
|
|
71
|
+
value: string,
|
|
72
|
+
defaultName: string,
|
|
73
|
+
options: IProviderCommandModuleOptions,
|
|
74
|
+
): ICommandResult {
|
|
75
|
+
const settings = options.settings.readMergedSettings();
|
|
76
|
+
const sourceProfile = settings.providers?.[profileName];
|
|
77
|
+
if (!sourceProfile) {
|
|
78
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
79
|
+
}
|
|
80
|
+
const targetName = normalizeProfileName(value, defaultName);
|
|
81
|
+
const validationMessage = validateDuplicateProfileName(targetName, defaultName, options);
|
|
82
|
+
if (validationMessage !== undefined) {
|
|
83
|
+
return { message: validationMessage, success: false };
|
|
84
|
+
}
|
|
85
|
+
options.settings.writeTargetSettings(
|
|
86
|
+
upsertProviderProfile(options.settings.readTargetSettings(), targetName, { ...sourceProfile }),
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
message: `Provider profile duplicated: ${profileName} -> ${targetName}.`,
|
|
90
|
+
success: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function buildProviderDelete(
|
|
95
|
+
profileName: string,
|
|
96
|
+
options: IProviderCommandModuleOptions,
|
|
97
|
+
): ICommandResult {
|
|
98
|
+
const settings = options.settings.readMergedSettings();
|
|
99
|
+
const providers = settings.providers ?? {};
|
|
100
|
+
if (!providers[profileName]) {
|
|
101
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
102
|
+
}
|
|
103
|
+
if (Object.keys(providers).length <= 1) {
|
|
104
|
+
return { message: 'Cannot delete the only provider profile.', success: false };
|
|
105
|
+
}
|
|
106
|
+
if (options.settings.readTargetSettings().providers?.[profileName] === undefined) {
|
|
107
|
+
return {
|
|
108
|
+
message: `Provider profile "${profileName}" is not stored in the active write target; edit its source settings file or override it before deleting.`,
|
|
109
|
+
success: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
message: `Provider delete requested: ${profileName}`,
|
|
114
|
+
success: true,
|
|
115
|
+
interaction: createProviderDeleteConfirmationInteraction(profileName, options),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createProviderDeleteConfirmationInteraction(
|
|
120
|
+
profileName: string,
|
|
121
|
+
options: IProviderCommandModuleOptions,
|
|
122
|
+
): ICommandInteraction {
|
|
123
|
+
return {
|
|
124
|
+
prompt: {
|
|
125
|
+
kind: 'choice',
|
|
126
|
+
title: `Delete provider profile ${profileName}?`,
|
|
127
|
+
options: [
|
|
128
|
+
{ value: YES, label: 'Yes' },
|
|
129
|
+
{ value: 'no', label: 'No' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
submit: (value) => {
|
|
133
|
+
if (value !== YES) {
|
|
134
|
+
return { message: 'Provider delete cancelled.', success: true };
|
|
135
|
+
}
|
|
136
|
+
return confirmProviderDelete(profileName, options);
|
|
137
|
+
},
|
|
138
|
+
cancel: () => ({ message: 'Provider delete cancelled.', success: true }),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function confirmProviderDelete(
|
|
143
|
+
profileName: string,
|
|
144
|
+
options: IProviderCommandModuleOptions,
|
|
145
|
+
): ICommandResult {
|
|
146
|
+
const settings = options.settings.readMergedSettings();
|
|
147
|
+
if (settings.currentProvider !== profileName) {
|
|
148
|
+
options.settings.writeTargetSettings(
|
|
149
|
+
deleteProviderProfile(options.settings.readTargetSettings(), profileName),
|
|
150
|
+
);
|
|
151
|
+
return { message: `Provider profile deleted: ${profileName}.`, success: true };
|
|
152
|
+
}
|
|
153
|
+
const replacementOptions = Object.entries(settings.providers ?? {})
|
|
154
|
+
.filter(([name]) => name !== profileName)
|
|
155
|
+
.map(([name, profile]) => ({
|
|
156
|
+
value: name,
|
|
157
|
+
label: formatProviderChoiceLabel(name, profile, settings.currentProvider),
|
|
158
|
+
}));
|
|
159
|
+
return {
|
|
160
|
+
message: `Select a replacement provider before deleting ${profileName}.`,
|
|
161
|
+
success: true,
|
|
162
|
+
interaction: {
|
|
163
|
+
prompt: {
|
|
164
|
+
kind: 'choice',
|
|
165
|
+
title: `Replacement provider for ${profileName}`,
|
|
166
|
+
options: replacementOptions,
|
|
167
|
+
maxVisible: 8,
|
|
168
|
+
},
|
|
169
|
+
submit: (replacementName) =>
|
|
170
|
+
completeActiveProviderDelete(profileName, replacementName, options),
|
|
171
|
+
cancel: () => ({ message: 'Provider delete cancelled.', success: true }),
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function completeActiveProviderDelete(
|
|
177
|
+
profileName: string,
|
|
178
|
+
replacementName: string,
|
|
179
|
+
options: IProviderCommandModuleOptions,
|
|
180
|
+
): ICommandResult {
|
|
181
|
+
const settings = options.settings.readMergedSettings();
|
|
182
|
+
if (settings.providers?.[replacementName] === undefined || replacementName === profileName) {
|
|
183
|
+
return { message: `Provider profile "${replacementName}" was not found.`, success: false };
|
|
184
|
+
}
|
|
185
|
+
const target = deleteProviderProfile(options.settings.readTargetSettings(), profileName);
|
|
186
|
+
options.settings.writeTargetSettings({ ...target, currentProvider: replacementName });
|
|
187
|
+
return {
|
|
188
|
+
message: `Provider profile deleted: ${profileName}. Restarting with ${replacementName}...`,
|
|
189
|
+
success: true,
|
|
190
|
+
effects: [{ ...PROVIDER_RESTART_EFFECT, message: 'Provider delete restart' }],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function suggestDuplicateProfileName(profileName: string, existingProfileNames: readonly string[]) {
|
|
195
|
+
const base = sanitizeProviderProfileName(`${profileName}-copy`) ?? 'provider-copy';
|
|
196
|
+
if (!existingProfileNames.includes(base)) {
|
|
197
|
+
return base;
|
|
198
|
+
}
|
|
199
|
+
for (let index = 2; index < MAX_DUPLICATE_PROFILE_SUFFIX; index += 1) {
|
|
200
|
+
const candidate = `${base}-${index}`;
|
|
201
|
+
if (!existingProfileNames.includes(candidate)) {
|
|
202
|
+
return candidate;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return `${base}-${Date.now()}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeProfileName(value: string, defaultName: string): string {
|
|
209
|
+
const raw = value.trim() || defaultName;
|
|
210
|
+
return sanitizeProviderProfileName(raw) ?? '';
|
|
211
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ICommandInteraction,
|
|
3
|
+
ICommandResult,
|
|
4
|
+
IProviderCommandModuleOptions,
|
|
5
|
+
IProviderProfileSettings,
|
|
6
|
+
IProviderSetupInput,
|
|
7
|
+
} from '@robota-sdk/agent-framework';
|
|
8
|
+
import {
|
|
9
|
+
buildProviderSetupPatch,
|
|
10
|
+
setCurrentProvider,
|
|
11
|
+
upsertProviderProfile,
|
|
12
|
+
} from '@robota-sdk/agent-framework';
|
|
13
|
+
import { createProviderSetupFlow, submitProviderSetupValue } from './provider-setup-flow.js';
|
|
14
|
+
import type { IProviderSetupFlowState } from './provider-setup-flow.js';
|
|
15
|
+
import {
|
|
16
|
+
createProviderSetupInteraction,
|
|
17
|
+
toProviderSetupStepPrompt,
|
|
18
|
+
} from './provider-command-setup.js';
|
|
19
|
+
|
|
20
|
+
const YES = 'yes';
|
|
21
|
+
const PROVIDER_RESTART_EFFECT = {
|
|
22
|
+
type: 'session-restart-requested',
|
|
23
|
+
reason: 'other',
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export function formatProviderChoiceLabel(
|
|
27
|
+
name: string,
|
|
28
|
+
profile: IProviderProfileSettings,
|
|
29
|
+
currentProvider: string | undefined,
|
|
30
|
+
): string {
|
|
31
|
+
const marker = name === currentProvider ? '* ' : '';
|
|
32
|
+
return `${marker}${name}: ${profile.type ?? 'unknown'} ${profile.model ?? '(no model)'}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function buildProviderSwitch(
|
|
36
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
37
|
+
profileName: string | undefined,
|
|
38
|
+
options: IProviderCommandModuleOptions,
|
|
39
|
+
): ICommandResult {
|
|
40
|
+
if (!profileName) {
|
|
41
|
+
return { message: 'Usage: provider use <profile>', success: false };
|
|
42
|
+
}
|
|
43
|
+
if (!providers?.[profileName]) {
|
|
44
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
45
|
+
}
|
|
46
|
+
if (options.settings.readMergedSettings().currentProvider === profileName) {
|
|
47
|
+
return { message: `Provider profile "${profileName}" is already current.`, success: true };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
message: `Provider change requested: ${profileName}`,
|
|
51
|
+
success: true,
|
|
52
|
+
interaction: createProviderSwitchInteraction(profileName, options),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createProviderSwitchInteraction(
|
|
57
|
+
profileName: string,
|
|
58
|
+
options: IProviderCommandModuleOptions,
|
|
59
|
+
): ICommandInteraction {
|
|
60
|
+
return {
|
|
61
|
+
prompt: {
|
|
62
|
+
kind: 'choice',
|
|
63
|
+
title: `Change provider to ${profileName}? This will restart the session.`,
|
|
64
|
+
options: [
|
|
65
|
+
{ value: YES, label: 'Yes' },
|
|
66
|
+
{ value: 'no', label: 'No' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
submit: (value) => {
|
|
70
|
+
if (value !== YES) {
|
|
71
|
+
return { message: 'Provider change cancelled.', success: true };
|
|
72
|
+
}
|
|
73
|
+
const merged = options.settings.readMergedSettings();
|
|
74
|
+
const target = options.settings.readTargetSettings();
|
|
75
|
+
const next =
|
|
76
|
+
target.providers?.[profileName] !== undefined ||
|
|
77
|
+
merged.providers?.[profileName] !== undefined
|
|
78
|
+
? { ...target, currentProvider: profileName }
|
|
79
|
+
: setCurrentProvider(target, profileName);
|
|
80
|
+
options.settings.writeTargetSettings(next);
|
|
81
|
+
return {
|
|
82
|
+
message: `Provider changed to ${profileName}. Restarting...`,
|
|
83
|
+
success: true,
|
|
84
|
+
effects: [{ ...PROVIDER_RESTART_EFFECT, message: 'Provider change restart' }],
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
cancel: () => ({ message: 'Provider change cancelled.', success: true }),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function buildProviderEdit(
|
|
92
|
+
profileName: string,
|
|
93
|
+
options: IProviderCommandModuleOptions,
|
|
94
|
+
): ICommandResult {
|
|
95
|
+
const settings = options.settings.readMergedSettings();
|
|
96
|
+
const profile = settings.providers?.[profileName];
|
|
97
|
+
if (!profile) {
|
|
98
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
99
|
+
}
|
|
100
|
+
if (!profile.type) {
|
|
101
|
+
return { message: `Provider profile "${profileName}" is missing type.`, success: false };
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const flow = createProviderSetupFlow(profile.type, options.providerDefinitions, {
|
|
105
|
+
profileName,
|
|
106
|
+
setCurrent: false,
|
|
107
|
+
initialValues: getProviderProfileSetupValues(profile),
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
message: `Provider edit requested: ${profileName}`,
|
|
111
|
+
success: true,
|
|
112
|
+
interaction: createProviderEditInteraction(flow, profileName, options),
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return { message: error instanceof Error ? error.message : String(error), success: false };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getProviderProfileSetupValues(profile: IProviderProfileSettings): {
|
|
120
|
+
model?: string;
|
|
121
|
+
apiKey?: string;
|
|
122
|
+
baseURL?: string;
|
|
123
|
+
} {
|
|
124
|
+
return {
|
|
125
|
+
...(typeof profile.model === 'string' ? { model: profile.model } : {}),
|
|
126
|
+
...(typeof profile.apiKey === 'string' ? { apiKey: profile.apiKey } : {}),
|
|
127
|
+
...(typeof profile.baseURL === 'string' ? { baseURL: profile.baseURL } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createProviderEditInteraction(
|
|
132
|
+
flow: IProviderSetupFlowState,
|
|
133
|
+
profileName: string,
|
|
134
|
+
options: IProviderCommandModuleOptions,
|
|
135
|
+
): ICommandInteraction {
|
|
136
|
+
return {
|
|
137
|
+
prompt: toProviderSetupStepPrompt(flow),
|
|
138
|
+
submit: (value) => submitProviderEditInteractionValue(flow, profileName, value, options),
|
|
139
|
+
cancel: () => ({ message: 'Provider edit cancelled.', success: true }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function submitProviderEditInteractionValue(
|
|
144
|
+
flow: IProviderSetupFlowState,
|
|
145
|
+
profileName: string,
|
|
146
|
+
value: string,
|
|
147
|
+
options: IProviderCommandModuleOptions,
|
|
148
|
+
): ICommandResult {
|
|
149
|
+
const result = submitProviderSetupValue(flow, value);
|
|
150
|
+
if (result.status === 'error') {
|
|
151
|
+
return {
|
|
152
|
+
message: result.message,
|
|
153
|
+
success: false,
|
|
154
|
+
interaction: createProviderEditInteraction(flow, profileName, options),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (result.status === 'complete') {
|
|
158
|
+
return completeProviderEdit(result.input, profileName, options);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
message: '',
|
|
162
|
+
success: true,
|
|
163
|
+
interaction: createProviderEditInteraction(result.state, profileName, options),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function completeProviderEdit(
|
|
168
|
+
input: IProviderSetupInput,
|
|
169
|
+
profileName: string,
|
|
170
|
+
options: IProviderCommandModuleOptions,
|
|
171
|
+
): ICommandResult {
|
|
172
|
+
const merged = options.settings.readMergedSettings();
|
|
173
|
+
const currentProfile = merged.providers?.[profileName];
|
|
174
|
+
if (!currentProfile) {
|
|
175
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
176
|
+
}
|
|
177
|
+
const target = options.settings.readTargetSettings();
|
|
178
|
+
const patch = buildProviderSetupPatch(input, {
|
|
179
|
+
providerDefinitions: options.providerDefinitions,
|
|
180
|
+
});
|
|
181
|
+
const updatedProfile = patch.providers[profileName];
|
|
182
|
+
if (!updatedProfile) {
|
|
183
|
+
return { message: `Provider profile "${profileName}" was not updated.`, success: false };
|
|
184
|
+
}
|
|
185
|
+
options.settings.writeTargetSettings(
|
|
186
|
+
upsertProviderProfile(target, profileName, { ...currentProfile, ...updatedProfile }),
|
|
187
|
+
);
|
|
188
|
+
const isCurrent = merged.currentProvider === profileName;
|
|
189
|
+
return {
|
|
190
|
+
message: isCurrent
|
|
191
|
+
? `Provider ${profileName} updated. Restarting...`
|
|
192
|
+
: `Provider ${profileName} updated.`,
|
|
193
|
+
success: true,
|
|
194
|
+
...(isCurrent
|
|
195
|
+
? { effects: [{ ...PROVIDER_RESTART_EFFECT, message: 'Provider edit restart' }] }
|
|
196
|
+
: {}),
|
|
197
|
+
};
|
|
198
|
+
}
|