@robota-sdk/agent-command 3.0.0-beta.100
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/CHANGELOG.md +1351 -0
- package/LICENSE +661 -0
- package/README.md +100 -0
- package/dist/node/index.cjs +121 -0
- package/dist/node/index.d.cts +1053 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1053 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +122 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +83 -0
- package/src/advisor/advisor-command-module.ts +47 -0
- package/src/advisor/advisor-command.test.ts +102 -0
- package/src/advisor/advisor-command.ts +55 -0
- package/src/advisor/advisor-cost-functional.test.ts +92 -0
- package/src/advisor/index.ts +6 -0
- package/src/agent/__tests__/agent-command.test.ts +597 -0
- package/src/agent/agent-command-module.ts +89 -0
- package/src/agent/agent-command-parser.ts +180 -0
- package/src/agent/agent-command.ts +255 -0
- package/src/agent/index.ts +7 -0
- package/src/background/__tests__/background-command-module.test.ts +261 -0
- package/src/background/background-command-module.ts +52 -0
- package/src/background/background-command.ts +65 -0
- package/src/background/index.ts +6 -0
- package/src/command-module-utils.test.ts +37 -0
- package/src/command-module-utils.ts +35 -0
- package/src/compact/__tests__/compact-command-module.test.ts +181 -0
- package/src/compact/compact-command-module.ts +59 -0
- package/src/compact/compact-command.ts +33 -0
- package/src/compact/index.ts +6 -0
- package/src/context/__tests__/context-command-module.test.ts +502 -0
- package/src/context/auto-compact-format.ts +25 -0
- package/src/context/context-breakdown.ts +295 -0
- package/src/context/context-command-module.ts +82 -0
- package/src/context/context-command.ts +291 -0
- package/src/context/index.ts +6 -0
- package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +121 -0
- package/src/default/__tests__/default-command-modules.test.ts +192 -0
- package/src/default/__tests__/model-exposure.test.ts +269 -0
- package/src/default/__tests__/org-policy-forwarding.test.ts +106 -0
- package/src/default/__tests__/provider-environment-forwarding.test.ts +98 -0
- package/src/default/default-command-modules.ts +247 -0
- package/src/default/index.ts +5 -0
- package/src/devices/__tests__/devices-command-module.test.ts +299 -0
- package/src/devices/devices-command-module.ts +308 -0
- package/src/devices/devices-command-port.ts +143 -0
- package/src/devices/index.ts +14 -0
- package/src/doctor/__tests__/doctor-command-module.test.ts +145 -0
- package/src/doctor/__tests__/doctor-display-vocabulary.test.ts +38 -0
- package/src/doctor/__tests__/doctor-fixture.ts +146 -0
- package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +484 -0
- package/src/doctor/doctor-command-module.ts +132 -0
- package/src/doctor/doctor-extensions-probe.ts +323 -0
- package/src/doctor/doctor-node-deps.ts +88 -0
- package/src/doctor/doctor-redaction.ts +99 -0
- package/src/doctor/doctor-render.ts +70 -0
- package/src/doctor/doctor-repair.ts +136 -0
- package/src/doctor/doctor-runner.ts +122 -0
- package/src/doctor/doctor-settings-probe.ts +239 -0
- package/src/doctor/doctor-storage-probe.ts +187 -0
- package/src/doctor/doctor-types.ts +109 -0
- package/src/doctor/index.ts +32 -0
- package/src/editor/__tests__/editor-command-functional.test.ts +124 -0
- package/src/editor/editor-command-module.ts +53 -0
- package/src/editor/editor-command.ts +58 -0
- package/src/editor/index.ts +7 -0
- package/src/editor/resolve-editor.ts +21 -0
- package/src/effort/effort-command-module.ts +51 -0
- package/src/effort/effort-command.test.ts +203 -0
- package/src/effort/effort-command.ts +136 -0
- package/src/effort/index.ts +6 -0
- package/src/events/__tests__/events-command.test.ts +73 -0
- package/src/events/events-command-module.ts +49 -0
- package/src/events/events-command.ts +69 -0
- package/src/events/index.ts +2 -0
- package/src/exit/__tests__/exit-command-module.test.ts +61 -0
- package/src/exit/exit-command-module.ts +47 -0
- package/src/exit/exit-command.ts +26 -0
- package/src/exit/index.ts +6 -0
- package/src/fork/__tests__/fork-command.test.ts +184 -0
- package/src/fork/fork-command-module.ts +61 -0
- package/src/fork/fork-command.ts +162 -0
- package/src/fork/index.ts +8 -0
- package/src/git/__tests__/fake-git-port.ts +35 -0
- package/src/git/__tests__/git-command-module.test.ts +111 -0
- package/src/git/__tests__/git-commit.test.ts +185 -0
- package/src/git/git-command-module.ts +138 -0
- package/src/git/git-commit.ts +254 -0
- package/src/git/index.ts +20 -0
- package/src/goal/__tests__/goal-command.test.ts +80 -0
- package/src/goal/goal-command-module.ts +49 -0
- package/src/goal/goal-command.ts +69 -0
- package/src/goal/index.ts +6 -0
- package/src/handoff/__tests__/handoff-command-module.test.ts +25 -0
- package/src/handoff/__tests__/handoff-command.test.ts +218 -0
- package/src/handoff/handoff-command-module.ts +49 -0
- package/src/handoff/handoff-command.ts +187 -0
- package/src/handoff/index.ts +6 -0
- package/src/help/__tests__/help-command-module.test.ts +134 -0
- package/src/help/__tests__/help-command.test.ts +130 -0
- package/src/help/help-command-module.ts +47 -0
- package/src/help/help-command.ts +11 -0
- package/src/help/index.ts +6 -0
- package/src/index.ts +163 -0
- package/src/keybindings/__tests__/keybindings-command-module.test.ts +57 -0
- package/src/keybindings/index.ts +6 -0
- package/src/keybindings/keybindings-command-module.ts +88 -0
- package/src/language/__tests__/language-command-module.test.ts +126 -0
- package/src/language/index.ts +6 -0
- package/src/language/language-command-module.ts +55 -0
- package/src/language/language-command.ts +52 -0
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +895 -0
- package/src/mcp-activation/__tests__/mcp-model-view.test.ts +242 -0
- package/src/mcp-activation/__tests__/mcp-skill-command.test.ts +141 -0
- package/src/mcp-activation/index.ts +12 -0
- package/src/mcp-activation/mcp-activation-command-module.ts +123 -0
- package/src/mcp-activation/mcp-activation-command.ts +662 -0
- package/src/mcp-activation/mcp-model-notice.ts +99 -0
- package/src/mcp-activation/mcp-model-status.ts +104 -0
- package/src/memory/__tests__/memory-command-module.test.ts +476 -0
- package/src/memory/__tests__/memory-command-unavailable.test.ts +63 -0
- package/src/memory/index.ts +6 -0
- package/src/memory/memory-command-module.ts +77 -0
- package/src/memory/memory-command.ts +326 -0
- package/src/mode/__tests__/mode-command-module.test.ts +230 -0
- package/src/mode/index.ts +6 -0
- package/src/mode/mode-command-module.ts +55 -0
- package/src/mode/mode-command.ts +67 -0
- package/src/model/__tests__/model-command-module.test.ts +80 -0
- package/src/model/__tests__/model-command.test.ts +265 -0
- package/src/model/index.ts +6 -0
- package/src/model/model-command-module.ts +56 -0
- package/src/model/model-command.ts +249 -0
- package/src/output-style/__tests__/output-style-command.test.ts +96 -0
- package/src/output-style/index.ts +6 -0
- package/src/output-style/output-style-command-module.ts +143 -0
- package/src/peers/__tests__/peers-command-module.test.ts +24 -0
- package/src/peers/__tests__/peers-command.test.ts +399 -0
- package/src/peers/index.ts +9 -0
- package/src/peers/peers-command-module.ts +49 -0
- package/src/peers/peers-command.ts +220 -0
- package/src/permissions/__tests__/permissions-command-module.test.ts +276 -0
- package/src/permissions/index.ts +6 -0
- package/src/permissions/permissions-command-module.ts +55 -0
- package/src/permissions/permissions-command.ts +89 -0
- package/src/plan/__tests__/plan-command.test.ts +129 -0
- package/src/plan/index.ts +9 -0
- package/src/plan/plan-command-module.ts +49 -0
- package/src/plan/plan-command.ts +92 -0
- package/src/plugin/__tests__/plugin-active-turn.test.ts +100 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +300 -0
- package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
- package/src/plugin/index.ts +7 -0
- package/src/plugin/plugin-command-module.ts +85 -0
- package/src/plugin/plugin-command.ts +271 -0
- package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
- package/src/preset/__tests__/preset-command-module.test.ts +220 -0
- package/src/preset/index.ts +6 -0
- package/src/preset/preset-command-module.ts +80 -0
- package/src/preset/preset-command.ts +148 -0
- package/src/provider/__tests__/optional-provider-credentials.test.ts +130 -0
- package/src/provider/__tests__/org-policy.test.ts +174 -0
- package/src/provider/__tests__/provider-command-module.test.ts +884 -0
- package/src/provider/__tests__/provider-connection-setup.test.ts +567 -0
- package/src/provider/__tests__/provider-reference-switch.test.ts +113 -0
- package/src/provider/__tests__/provider-setup-flow.test.ts +244 -0
- package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
- package/src/provider/__tests__/scripted-interaction.ts +40 -0
- package/src/provider/index.ts +32 -0
- package/src/provider/provider-command-execution.ts +235 -0
- package/src/provider/provider-command-module.ts +82 -0
- package/src/provider/provider-command-profile-lifecycle.ts +239 -0
- package/src/provider/provider-command-profile-operations.ts +218 -0
- package/src/provider/provider-command-profile.ts +116 -0
- package/src/provider/provider-command-setup.ts +79 -0
- package/src/provider/provider-connection-setup.ts +287 -0
- package/src/provider/provider-connection-validation.ts +38 -0
- package/src/provider/provider-onboarding.ts +73 -0
- package/src/provider/provider-prompt-interaction.ts +28 -0
- package/src/provider/provider-setup-ask.ts +67 -0
- package/src/provider/provider-setup-flow.ts +326 -0
- package/src/provider/provider-startup.ts +225 -0
- package/src/remote-control/__tests__/remote-control-command-module.test.ts +36 -0
- package/src/remote-control/__tests__/remote-control-command.test.ts +216 -0
- package/src/remote-control/index.ts +8 -0
- package/src/remote-control/remote-control-command-module.ts +61 -0
- package/src/remote-control/remote-control-command.ts +138 -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 +47 -0
- package/src/reset/reset-command.ts +10 -0
- package/src/rewind/__tests__/rewind-command-module.test.ts +287 -0
- package/src/rewind/index.ts +2 -0
- package/src/rewind/rewind-command-module.ts +56 -0
- package/src/rewind/rewind-command.ts +279 -0
- package/src/sandbox/__tests__/sandbox-command.test.ts +80 -0
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/sandbox-command-module.ts +56 -0
- package/src/sandbox/sandbox-command.ts +94 -0
- package/src/schedule/__tests__/loop-command-functional.test.ts +399 -0
- package/src/schedule/__tests__/loop-command.test.ts +594 -0
- package/src/schedule/__tests__/loop-jitter.test.ts +25 -0
- package/src/schedule/__tests__/monitor-model-permission-functional.test.ts +126 -0
- package/src/schedule/__tests__/schedule-command.test.ts +247 -0
- package/src/schedule/__tests__/schedule-redos.test.ts +114 -0
- package/src/schedule/index.ts +12 -0
- package/src/schedule/loop-command.ts +427 -0
- package/src/schedule/loop-jitter.ts +41 -0
- package/src/schedule/schedule-command-module.ts +184 -0
- package/src/schedule/schedule-command.ts +161 -0
- package/src/schedule/schedule-spec-parser.ts +70 -0
- package/src/session/__tests__/model-pricing.test.ts +66 -0
- package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
- package/src/session/__tests__/session-command-module.test.ts +589 -0
- package/src/session/index.ts +17 -0
- package/src/session/model-pricing.ts +26 -0
- package/src/session/session-command-module.ts +197 -0
- package/src/session/session-command.ts +272 -0
- package/src/settings/index.ts +7 -0
- package/src/settings/settings-command-module.ts +47 -0
- package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
- package/src/shell/__tests__/shell-command-functional.test.ts +118 -0
- package/src/shell/index.ts +8 -0
- package/src/shell/resolve-shell.ts +25 -0
- package/src/shell/shell-command-module.ts +53 -0
- package/src/shell/shell-command.ts +48 -0
- package/src/shell/spawn-inherited.ts +32 -0
- package/src/skills/__tests__/skill-read-command.test.ts +64 -0
- package/src/skills/__tests__/skills-command-module.test.ts +200 -0
- package/src/skills/index.ts +7 -0
- package/src/skills/skill-read-command.ts +60 -0
- package/src/skills/skills-command-module.ts +76 -0
- package/src/skills/skills-command.ts +108 -0
- package/src/statusline/__tests__/statusline-command-module.test.ts +96 -0
- package/src/statusline/index.ts +6 -0
- package/src/statusline/statusline-command-module.ts +55 -0
- package/src/statusline/statusline-command.ts +80 -0
- package/src/terminal-client/__tests__/terminal-client-commands.test.ts +216 -0
- package/src/terminal-client/index.ts +5 -0
- package/src/terminal-client/terminal-client-commands.ts +109 -0
- package/src/theme/__tests__/theme-command.test.ts +204 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-command-module.ts +221 -0
- package/src/user-local/__tests__/user-local-command.test.ts +180 -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 +65 -0
- package/src/user-local/user-local-command.ts +214 -0
- package/src/user-local/user-local-memory-command.ts +160 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { IProviderDefinition } from '@robota-sdk/agent-core';
|
|
3
|
+
import {
|
|
4
|
+
createProviderSetupFlow,
|
|
5
|
+
formatProviderSetupHelpLinks,
|
|
6
|
+
formatProviderSetupPromptLabel,
|
|
7
|
+
formatProviderSetupSelectionPrompt,
|
|
8
|
+
getProviderSetupStep,
|
|
9
|
+
resolveProviderSetupSelection,
|
|
10
|
+
runProviderSetupPromptFlow,
|
|
11
|
+
submitProviderSetupValue,
|
|
12
|
+
} from '../provider-setup-flow.js';
|
|
13
|
+
|
|
14
|
+
const openaiDefaults = {
|
|
15
|
+
apiKey: '$ENV:OPENAI_API_KEY',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const providerDefinitions: readonly IProviderDefinition[] = [
|
|
19
|
+
{
|
|
20
|
+
type: 'openai',
|
|
21
|
+
displayName: 'OpenAI',
|
|
22
|
+
description: 'Use OpenAI Responses API',
|
|
23
|
+
defaults: openaiDefaults,
|
|
24
|
+
setupHelpLinks: [
|
|
25
|
+
{
|
|
26
|
+
kind: 'api-key',
|
|
27
|
+
label: 'OpenAI API keys',
|
|
28
|
+
url: 'https://platform.openai.com/api-keys',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
setupSteps: [
|
|
32
|
+
{
|
|
33
|
+
key: 'model',
|
|
34
|
+
title: 'OpenAI model',
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'apiKey',
|
|
39
|
+
title: 'OpenAI API key',
|
|
40
|
+
defaultValue: openaiDefaults.apiKey,
|
|
41
|
+
masked: true,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
requiresApiKey: true,
|
|
45
|
+
createProvider: () => {
|
|
46
|
+
throw new Error('not used');
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'anthropic',
|
|
51
|
+
displayName: 'Anthropic',
|
|
52
|
+
description: 'Use Claude models through Anthropic',
|
|
53
|
+
defaults: { model: 'claude-sonnet-4-6' },
|
|
54
|
+
setupSteps: [
|
|
55
|
+
{ key: 'apiKey', title: 'Anthropic API key', required: true, masked: true },
|
|
56
|
+
{ key: 'model', title: 'Anthropic model', defaultValue: 'claude-sonnet-4-6' },
|
|
57
|
+
],
|
|
58
|
+
requiresApiKey: true,
|
|
59
|
+
createProvider: () => {
|
|
60
|
+
throw new Error('not used');
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
describe('provider setup flow', () => {
|
|
66
|
+
it('carries provider-owned setup help links into generic prompt labels', () => {
|
|
67
|
+
const state = createProviderSetupFlow('openai', providerDefinitions);
|
|
68
|
+
|
|
69
|
+
expect(state.setupHelpLinks).toEqual(providerDefinitions[0]?.setupHelpLinks);
|
|
70
|
+
expect(formatProviderSetupHelpLinks(state.setupHelpLinks)).toBe(
|
|
71
|
+
' Setup help: API key: OpenAI API keys - https://platform.openai.com/api-keys',
|
|
72
|
+
);
|
|
73
|
+
expect(formatProviderSetupPromptLabel(getProviderSetupStep(state), state.setupHelpLinks)).toBe(
|
|
74
|
+
[
|
|
75
|
+
' Setup help: API key: OpenAI API keys - https://platform.openai.com/api-keys',
|
|
76
|
+
' OpenAI model: ',
|
|
77
|
+
].join('\n'),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('formats provider selection prompts from injected provider definitions', () => {
|
|
82
|
+
expect(formatProviderSetupSelectionPrompt(providerDefinitions)).toBe(
|
|
83
|
+
[
|
|
84
|
+
' Select provider:',
|
|
85
|
+
' 1. OpenAI (openai) — Use OpenAI Responses API',
|
|
86
|
+
' 2. Anthropic (anthropic) — Use Claude models through Anthropic',
|
|
87
|
+
' Provider [1-2] (default: 1): ',
|
|
88
|
+
].join('\n'),
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('resolves provider selection by index or type without provider-specific branches', () => {
|
|
93
|
+
expect(resolveProviderSetupSelection('2', providerDefinitions)).toBe('anthropic');
|
|
94
|
+
expect(resolveProviderSetupSelection('openai', providerDefinitions)).toBe('openai');
|
|
95
|
+
expect(resolveProviderSetupSelection('', providerDefinitions)).toBe('openai');
|
|
96
|
+
expect(() => resolveProviderSetupSelection('gemma', providerDefinitions)).toThrow(
|
|
97
|
+
'Unknown provider: gemma',
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('builds OpenAI setup input from model and default API key submissions', () => {
|
|
102
|
+
let state = createProviderSetupFlow('openai', providerDefinitions);
|
|
103
|
+
expect(state.setupHelpLinks).toEqual([
|
|
104
|
+
{
|
|
105
|
+
kind: 'api-key',
|
|
106
|
+
label: 'OpenAI API keys',
|
|
107
|
+
url: 'https://platform.openai.com/api-keys',
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
expect(getProviderSetupStep(state)).toMatchObject({
|
|
111
|
+
key: 'model',
|
|
112
|
+
required: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const emptyModelResult = submitProviderSetupValue(state, '');
|
|
116
|
+
expect(emptyModelResult.status).toBe('error');
|
|
117
|
+
|
|
118
|
+
const modelResult = submitProviderSetupValue(state, 'gpt-4o');
|
|
119
|
+
expect(modelResult.status).toBe('next');
|
|
120
|
+
if (modelResult.status !== 'next') throw new Error('expected next');
|
|
121
|
+
state = modelResult.state;
|
|
122
|
+
|
|
123
|
+
const apiKeyResult = submitProviderSetupValue(state, '');
|
|
124
|
+
expect(apiKeyResult).toEqual({
|
|
125
|
+
status: 'complete',
|
|
126
|
+
input: {
|
|
127
|
+
profile: 'openai',
|
|
128
|
+
type: 'openai',
|
|
129
|
+
model: 'gpt-4o',
|
|
130
|
+
apiKey: openaiDefaults.apiKey,
|
|
131
|
+
setCurrent: true,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('keeps Anthropic API key validation outside the TUI component', () => {
|
|
137
|
+
const state = createProviderSetupFlow('anthropic', providerDefinitions);
|
|
138
|
+
|
|
139
|
+
expect(submitProviderSetupValue(state, '')).toEqual({
|
|
140
|
+
status: 'error',
|
|
141
|
+
state,
|
|
142
|
+
message: 'Required',
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('suggests unique profile names from type and disambiguates duplicates', () => {
|
|
147
|
+
let state = createProviderSetupFlow('anthropic', providerDefinitions, {
|
|
148
|
+
existingProfileNames: ['anthropic'],
|
|
149
|
+
});
|
|
150
|
+
const apiKeyResult = submitProviderSetupValue(state, 'sk-ant-test');
|
|
151
|
+
expect(apiKeyResult.status).toBe('next');
|
|
152
|
+
if (apiKeyResult.status !== 'next') throw new Error('expected next');
|
|
153
|
+
state = apiKeyResult.state;
|
|
154
|
+
|
|
155
|
+
const modelResult = submitProviderSetupValue(state, '');
|
|
156
|
+
|
|
157
|
+
expect(modelResult).toEqual({
|
|
158
|
+
status: 'complete',
|
|
159
|
+
input: {
|
|
160
|
+
profile: 'anthropic-2',
|
|
161
|
+
type: 'anthropic',
|
|
162
|
+
model: 'claude-sonnet-4-6',
|
|
163
|
+
apiKey: 'sk-ant-test',
|
|
164
|
+
setCurrent: true,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('builds edit input for a fixed profile from current profile values', () => {
|
|
170
|
+
let state = createProviderSetupFlow('anthropic', providerDefinitions, {
|
|
171
|
+
profileName: 'work-claude',
|
|
172
|
+
setCurrent: false,
|
|
173
|
+
initialValues: {
|
|
174
|
+
apiKey: '$ENV:ANTHROPIC_API_KEY',
|
|
175
|
+
model: 'claude-sonnet-4-6',
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(getProviderSetupStep(state)).toMatchObject({
|
|
180
|
+
key: 'apiKey',
|
|
181
|
+
defaultValue: '$ENV:ANTHROPIC_API_KEY',
|
|
182
|
+
required: false,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const apiKeyResult = submitProviderSetupValue(state, '');
|
|
186
|
+
expect(apiKeyResult.status).toBe('next');
|
|
187
|
+
if (apiKeyResult.status !== 'next') throw new Error('expected next');
|
|
188
|
+
state = apiKeyResult.state;
|
|
189
|
+
|
|
190
|
+
const modelResult = submitProviderSetupValue(state, 'claude-opus-4-5');
|
|
191
|
+
|
|
192
|
+
expect(modelResult).toEqual({
|
|
193
|
+
status: 'complete',
|
|
194
|
+
input: {
|
|
195
|
+
profile: 'work-claude',
|
|
196
|
+
type: 'anthropic',
|
|
197
|
+
model: 'claude-opus-4-5',
|
|
198
|
+
apiKey: '$ENV:ANTHROPIC_API_KEY',
|
|
199
|
+
setCurrent: false,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('runs prompt input with masked API key steps', async () => {
|
|
205
|
+
const promptInput = vi.fn(async (label: string) => (label.includes('model') ? 'gpt-4o' : ''));
|
|
206
|
+
|
|
207
|
+
const input = await runProviderSetupPromptFlow('openai', promptInput, providerDefinitions);
|
|
208
|
+
|
|
209
|
+
expect(input.model).toBe('gpt-4o');
|
|
210
|
+
expect(input.profile).toBe('openai');
|
|
211
|
+
expect(input.apiKey).toBe(openaiDefaults.apiKey);
|
|
212
|
+
expect(promptInput).toHaveBeenNthCalledWith(
|
|
213
|
+
1,
|
|
214
|
+
formatProviderSetupPromptLabel(
|
|
215
|
+
{
|
|
216
|
+
key: 'model',
|
|
217
|
+
title: 'OpenAI model',
|
|
218
|
+
required: true,
|
|
219
|
+
},
|
|
220
|
+
providerDefinitions[0]?.setupHelpLinks,
|
|
221
|
+
),
|
|
222
|
+
false,
|
|
223
|
+
);
|
|
224
|
+
expect(promptInput).toHaveBeenNthCalledWith(
|
|
225
|
+
2,
|
|
226
|
+
formatProviderSetupPromptLabel(
|
|
227
|
+
{
|
|
228
|
+
key: 'apiKey',
|
|
229
|
+
title: 'OpenAI API key',
|
|
230
|
+
defaultValue: openaiDefaults.apiKey,
|
|
231
|
+
masked: true,
|
|
232
|
+
},
|
|
233
|
+
providerDefinitions[0]?.setupHelpLinks,
|
|
234
|
+
),
|
|
235
|
+
true,
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('formats provider setup help links for generic setup descriptions', () => {
|
|
240
|
+
expect(formatProviderSetupHelpLinks(providerDefinitions[0]?.setupHelpLinks)).toBe(
|
|
241
|
+
' Setup help: API key: OpenAI API keys - https://platform.openai.com/api-keys',
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ensureProviderConfig } from '../provider-startup.js';
|
|
4
|
+
|
|
5
|
+
it('requires host settings access before provider startup', async () => {
|
|
6
|
+
await expect(
|
|
7
|
+
ensureProviderConfig(
|
|
8
|
+
process.cwd(),
|
|
9
|
+
{} as never,
|
|
10
|
+
async () => '',
|
|
11
|
+
{ writeLine: () => undefined, writeError: () => undefined } as never,
|
|
12
|
+
[],
|
|
13
|
+
{ formatError: () => 'Provider is not configured.' },
|
|
14
|
+
),
|
|
15
|
+
).rejects.toThrow('Provider startup requires host settings sources and stores.');
|
|
16
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
2
|
+
|
|
3
|
+
import type { IActionRequest, IUserInteraction, TActionResponse } from '@robota-sdk/agent-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build a command host context whose `getUserInteraction().ask` replays a scripted sequence of
|
|
7
|
+
* responses (CMD-004 test double). Each `ask` records the request it received (so tests can assert on
|
|
8
|
+
* the rendered action shape) and returns the next scripted answer; once the script is exhausted it
|
|
9
|
+
* returns `{ type: 'cancelled' }`, mirroring a user dismissing the prompt.
|
|
10
|
+
*/
|
|
11
|
+
export function scriptedContext(
|
|
12
|
+
answers: readonly TActionResponse[],
|
|
13
|
+
/**
|
|
14
|
+
* #3282 §3: whether THIS session is running with no provider configured yet — `/provider add`
|
|
15
|
+
* reads it (never settings) to decide hot-swap vs. restart. Defaults to `false`, an ordinary
|
|
16
|
+
* session, matching every test written before setup mode existed.
|
|
17
|
+
*/
|
|
18
|
+
isSetupRequired = false,
|
|
19
|
+
): {
|
|
20
|
+
context: ReturnType<typeof createTestCommandHost>;
|
|
21
|
+
requests: IActionRequest[];
|
|
22
|
+
} {
|
|
23
|
+
const requests: IActionRequest[] = [];
|
|
24
|
+
let index = 0;
|
|
25
|
+
const ui: IUserInteraction = {
|
|
26
|
+
ask: (request) => {
|
|
27
|
+
requests.push(request);
|
|
28
|
+
const answer = answers[index];
|
|
29
|
+
index += 1;
|
|
30
|
+
return Promise.resolve(answer ?? { type: 'cancelled' });
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
// ARCH-029: the double answers "no capability of that kind" for everything else, so this fixture
|
|
34
|
+
// states exactly the one capability it scripts. The double cast it replaced —
|
|
35
|
+
// `as Partial<…> as ICommandHostContext` — asserted conformance twice over one member.
|
|
36
|
+
const context = createTestCommandHost({
|
|
37
|
+
overrides: { getUserInteraction: () => ui, isSetupRequired: () => isSetupRequired },
|
|
38
|
+
});
|
|
39
|
+
return { context, requests };
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ProviderCommandSource,
|
|
3
|
+
createProviderCommandEntry,
|
|
4
|
+
createProviderCommandModule,
|
|
5
|
+
} from './provider-command-module.js';
|
|
6
|
+
export { executeProviderCommand } from './provider-command-execution.js';
|
|
7
|
+
export {
|
|
8
|
+
createProviderSetupFlow,
|
|
9
|
+
formatProviderSetupChoiceLabel,
|
|
10
|
+
formatProviderSetupHelpLinks,
|
|
11
|
+
formatProviderSetupPromptLabel,
|
|
12
|
+
formatProviderSetupSelectionPrompt,
|
|
13
|
+
getProviderSetupStep,
|
|
14
|
+
resolveProviderSetupSelection,
|
|
15
|
+
runProviderSetupPromptFlow,
|
|
16
|
+
submitProviderSetupValue,
|
|
17
|
+
validateProviderSetupValue,
|
|
18
|
+
} from './provider-setup-flow.js';
|
|
19
|
+
export type {
|
|
20
|
+
IProviderSetupFlowOptions,
|
|
21
|
+
IProviderSetupFlowState,
|
|
22
|
+
IProviderSetupPromptStep,
|
|
23
|
+
TProviderSetupFlowSubmitResult,
|
|
24
|
+
TProviderSetupType,
|
|
25
|
+
TPromptInput,
|
|
26
|
+
} from './provider-setup-flow.js';
|
|
27
|
+
export type {
|
|
28
|
+
IProviderCommandModuleOptions,
|
|
29
|
+
IProviderCommandSettingsAdapter,
|
|
30
|
+
} from '@robota-sdk/agent-framework';
|
|
31
|
+
export { ensureProviderConfig, runProviderStartupSetup } from './provider-startup.js';
|
|
32
|
+
export type { IProviderStartupContext, IEnsureProviderConfigOptions } from './provider-startup.js';
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
confirmAction,
|
|
3
|
+
findProviderDefinition,
|
|
4
|
+
formatSupportedProviderTypes,
|
|
5
|
+
isConfirmed,
|
|
6
|
+
selectAction,
|
|
7
|
+
} from '@robota-sdk/agent-core';
|
|
8
|
+
import { testProviderProfileCommand } from '@robota-sdk/agent-framework';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
buildProviderDuplicate,
|
|
12
|
+
buildProviderProfileDelete,
|
|
13
|
+
} from './provider-command-profile-lifecycle.js';
|
|
14
|
+
import {
|
|
15
|
+
buildProviderEdit,
|
|
16
|
+
buildProviderReconnect,
|
|
17
|
+
buildProviderSwitch,
|
|
18
|
+
} from './provider-command-profile-operations.js';
|
|
19
|
+
import { askProviderProfileSelection } from './provider-command-profile.js';
|
|
20
|
+
import { createSetupFlow, runProviderAddSetup } from './provider-command-setup.js';
|
|
21
|
+
import { formatProviderSetupChoiceLabel } from './provider-setup-flow.js';
|
|
22
|
+
|
|
23
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
24
|
+
import type {
|
|
25
|
+
ICommandHostSetupState,
|
|
26
|
+
ICommandHostUserInteraction,
|
|
27
|
+
IProviderCommandModuleOptions,
|
|
28
|
+
IProviderProfileSettings,
|
|
29
|
+
} from '@robota-sdk/agent-framework';
|
|
30
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
31
|
+
|
|
32
|
+
export async function executeProviderCommand(
|
|
33
|
+
context: ICommandHostUserInteraction & ICommandHostSetupState,
|
|
34
|
+
args: string,
|
|
35
|
+
options: IProviderCommandModuleOptions,
|
|
36
|
+
): Promise<ICommandResult> {
|
|
37
|
+
const ui = context.getUserInteraction();
|
|
38
|
+
// #3282 §3: the session's own live state, never inferred from settings — an ordinary env-default
|
|
39
|
+
// session also has no persisted `currentProvider`, and is not in setup mode either.
|
|
40
|
+
const isSetupRequired = context.isSetupRequired?.() === true;
|
|
41
|
+
const settings = options.settings.readMergedSettings();
|
|
42
|
+
const trimmedArgs = args.trim();
|
|
43
|
+
if (trimmedArgs.length === 0) {
|
|
44
|
+
return buildProviderProfilePicker(ui, settings.currentProvider, settings.providers, options);
|
|
45
|
+
}
|
|
46
|
+
const argTokens = trimmedArgs.split(/\s+/);
|
|
47
|
+
const [subcommand = 'current', profileArg] = argTokens;
|
|
48
|
+
|
|
49
|
+
if (subcommand === 'list') {
|
|
50
|
+
return buildProviderProfilePicker(ui, settings.currentProvider, settings.providers, options);
|
|
51
|
+
}
|
|
52
|
+
if (subcommand === 'current' || subcommand === '') {
|
|
53
|
+
return {
|
|
54
|
+
message: formatCurrentProvider(settings.currentProvider, settings.providers),
|
|
55
|
+
success: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (subcommand === 'switch') {
|
|
59
|
+
return buildProviderSwitch(settings.providers, profileArg, options);
|
|
60
|
+
}
|
|
61
|
+
if (subcommand === 'test') {
|
|
62
|
+
return testProviderProfileCommand(
|
|
63
|
+
settings.currentProvider,
|
|
64
|
+
settings.providers,
|
|
65
|
+
profileArg,
|
|
66
|
+
options,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
// #3282 §4b: direct, non-interactive forms of what the profile-action menu otherwise reaches
|
|
70
|
+
// through two asks (`askProviderProfileSelection` then `askProviderProfileAction`) — for a caller
|
|
71
|
+
// (the Settings screen, or a typed command) that already names the profile and does not want the
|
|
72
|
+
// "which profile" ask repeated. `edit`/`duplicate` still ask their own follow-up questions (the
|
|
73
|
+
// new name, the changed fields) exactly as the menu's Edit/Duplicate do.
|
|
74
|
+
if (subcommand === 'edit') {
|
|
75
|
+
if (!ui) return { message: 'Provider edit requires an interactive session.', success: false };
|
|
76
|
+
if (!profileArg) return { message: 'Usage: provider edit <profile>', success: false };
|
|
77
|
+
return buildProviderEdit(ui, profileArg, options);
|
|
78
|
+
}
|
|
79
|
+
if (subcommand === 'reconnect') {
|
|
80
|
+
if (!ui)
|
|
81
|
+
return { message: 'Provider reconnect requires an interactive session.', success: false };
|
|
82
|
+
if (!profileArg) return { message: 'Usage: /provider reconnect <profile>', success: false };
|
|
83
|
+
return buildProviderReconnect(ui, profileArg, options);
|
|
84
|
+
}
|
|
85
|
+
if (subcommand === 'duplicate') {
|
|
86
|
+
if (!ui)
|
|
87
|
+
return { message: 'Provider duplicate requires an interactive session.', success: false };
|
|
88
|
+
if (!profileArg) return { message: 'Usage: provider duplicate <profile>', success: false };
|
|
89
|
+
return buildProviderDuplicate(ui, profileArg, options);
|
|
90
|
+
}
|
|
91
|
+
if (subcommand === 'delete') {
|
|
92
|
+
// #3282 §4b: `--confirmed` is set ONLY by the Settings screen's own write (`settings-reporter.ts`)
|
|
93
|
+
// — its "Delete…" button already confirmed through its own ConfirmDialog before sending this, and
|
|
94
|
+
// a modal write has nowhere to ask a follow-up question. A plainly-typed `delete <profile>` never
|
|
95
|
+
// carries it, so it confirms here first when a human can answer (matching `/clear`'s "confirm only
|
|
96
|
+
// when interactive; with no human the explicit form proceeds") — deleting a profile also deletes
|
|
97
|
+
// its stored key, unlike `switch`, which this direct-form family otherwise mirrors.
|
|
98
|
+
if (!profileArg) return { message: 'Usage: provider delete <profile>', success: false };
|
|
99
|
+
const preConfirmed = argTokens.includes('--confirmed');
|
|
100
|
+
if (!preConfirmed && ui) {
|
|
101
|
+
const response = await ui.ask(
|
|
102
|
+
confirmAction('provider-delete', `Delete profile "${profileArg}"?`),
|
|
103
|
+
);
|
|
104
|
+
if (!isConfirmed(response)) {
|
|
105
|
+
return { message: 'Provider delete cancelled.', success: true };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return buildProviderProfileDelete(
|
|
109
|
+
settings.providers,
|
|
110
|
+
settings.currentProvider,
|
|
111
|
+
profileArg,
|
|
112
|
+
options,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (subcommand === 'add') {
|
|
116
|
+
return buildProviderSetup(ui, profileArg, options, isSetupRequired);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
message:
|
|
121
|
+
'Usage: provider [current|list|switch <profile>|edit <profile>|reconnect <profile>|duplicate <profile>|delete <profile>|add <type>|test [profile]]',
|
|
122
|
+
success: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* List the provider profiles. With an interactive renderer attached, drive the inline profile picker
|
|
128
|
+
* (CMD-004); without one (headless/automation) or with no profiles, return the formatted list as text.
|
|
129
|
+
*/
|
|
130
|
+
function buildProviderProfilePicker(
|
|
131
|
+
ui: IUserInteraction | undefined,
|
|
132
|
+
currentProvider: string | undefined,
|
|
133
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
134
|
+
options: IProviderCommandModuleOptions,
|
|
135
|
+
): Promise<ICommandResult> | ICommandResult {
|
|
136
|
+
if (!ui || Object.keys(providers ?? {}).length === 0) {
|
|
137
|
+
return { message: formatProviderList(currentProvider, providers), success: true };
|
|
138
|
+
}
|
|
139
|
+
return askProviderProfileSelection(ui, currentProvider, providers, options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatProviderList(
|
|
143
|
+
currentProvider: string | undefined,
|
|
144
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
145
|
+
): string {
|
|
146
|
+
const entries = Object.entries(providers ?? {});
|
|
147
|
+
if (entries.length === 0) {
|
|
148
|
+
return 'No provider profiles configured.';
|
|
149
|
+
}
|
|
150
|
+
return entries
|
|
151
|
+
.map(([name, profile]) => {
|
|
152
|
+
const marker = name === currentProvider ? '*' : '-';
|
|
153
|
+
return `${marker} ${name}: ${profile.type ?? 'unknown'} ${profile.model ?? '(no model)'}`;
|
|
154
|
+
})
|
|
155
|
+
.join('\n');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function formatCurrentProvider(
|
|
159
|
+
currentProvider: string | undefined,
|
|
160
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
161
|
+
): string {
|
|
162
|
+
if (!currentProvider) {
|
|
163
|
+
return 'No current provider configured.';
|
|
164
|
+
}
|
|
165
|
+
const profile = providers?.[currentProvider];
|
|
166
|
+
if (!profile) {
|
|
167
|
+
return `Current provider "${currentProvider}" was not found in providers.`;
|
|
168
|
+
}
|
|
169
|
+
return [
|
|
170
|
+
`Current provider: ${currentProvider}`,
|
|
171
|
+
`Type: ${profile.type ?? 'unknown'}`,
|
|
172
|
+
`Model: ${profile.model ?? '(no model)'}`,
|
|
173
|
+
...(profile.baseURL ? [`Base URL: ${profile.baseURL}`] : []),
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Configure a provider profile. With an explicit `type`, run the setup wizard directly; without one,
|
|
179
|
+
* ask the user to pick a provider type first (CMD-004). Without an interactive renderer, setup cannot
|
|
180
|
+
* proceed — return usage text instead of a silent guess.
|
|
181
|
+
*/
|
|
182
|
+
function buildProviderSetup(
|
|
183
|
+
ui: IUserInteraction | undefined,
|
|
184
|
+
type: string | undefined,
|
|
185
|
+
options: IProviderCommandModuleOptions,
|
|
186
|
+
isSetupRequired: boolean,
|
|
187
|
+
): Promise<ICommandResult> | ICommandResult {
|
|
188
|
+
if (type === undefined || type.length === 0) {
|
|
189
|
+
if (!ui) {
|
|
190
|
+
return {
|
|
191
|
+
message: `Usage: provider add <type>. Supported: ${formatSupportedProviderTypes(options.providerDefinitions)}`,
|
|
192
|
+
success: false,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return askProviderSetupType(ui, options, isSetupRequired);
|
|
196
|
+
}
|
|
197
|
+
if (findProviderDefinition(options.providerDefinitions, type) === undefined) {
|
|
198
|
+
return {
|
|
199
|
+
message: `Usage: provider add <type>. Supported: ${formatSupportedProviderTypes(options.providerDefinitions)}`,
|
|
200
|
+
success: false,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (!ui) {
|
|
204
|
+
return {
|
|
205
|
+
message: `Provider setup for "${type}" requires an interactive session.`,
|
|
206
|
+
success: false,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return runProviderAddSetup(ui, createSetupFlow(type, options), options, isSetupRequired);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function askProviderSetupType(
|
|
213
|
+
ui: IUserInteraction,
|
|
214
|
+
options: IProviderCommandModuleOptions,
|
|
215
|
+
isSetupRequired: boolean,
|
|
216
|
+
): Promise<ICommandResult> {
|
|
217
|
+
const typeOptions = options.providerDefinitions.map((definition) => ({
|
|
218
|
+
value: definition.type,
|
|
219
|
+
label: formatProviderSetupChoiceLabel(definition),
|
|
220
|
+
}));
|
|
221
|
+
const response = await ui.ask(
|
|
222
|
+
selectAction('provider-type', 'Select provider', typeOptions, { maxVisible: 6 }),
|
|
223
|
+
);
|
|
224
|
+
if (response.type !== 'answer' || response.values[0] === undefined) {
|
|
225
|
+
return { message: 'Provider setup cancelled.', success: true };
|
|
226
|
+
}
|
|
227
|
+
const type = response.values[0];
|
|
228
|
+
if (findProviderDefinition(options.providerDefinitions, type) === undefined) {
|
|
229
|
+
return {
|
|
230
|
+
message: `Usage: provider add <type>. Supported: ${formatSupportedProviderTypes(options.providerDefinitions)}`,
|
|
231
|
+
success: false,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return runProviderAddSetup(ui, createSetupFlow(type, options), options, isSetupRequired);
|
|
235
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { executeProviderCommand } from './provider-command-execution.js';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ICommandModule as TCommandModule,
|
|
5
|
+
IProviderCommandModuleOptions,
|
|
6
|
+
IProviderCommandSettingsAdapter,
|
|
7
|
+
ISystemCommand as TSystemCommand,
|
|
8
|
+
} from '@robota-sdk/agent-framework';
|
|
9
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-command';
|
|
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: 'switch', description: 'Hot-swap to another provider profile', source: 'provider' },
|
|
17
|
+
{ name: 'add', description: 'Configure a provider profile', source: 'provider' },
|
|
18
|
+
{
|
|
19
|
+
name: 'edit',
|
|
20
|
+
description: 'Edit a profile, including optional server credentials',
|
|
21
|
+
source: 'provider',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'reconnect',
|
|
25
|
+
description: 'Replace a named service connection through browser approval or API key entry',
|
|
26
|
+
source: 'provider',
|
|
27
|
+
modelInvocable: false,
|
|
28
|
+
},
|
|
29
|
+
{ name: 'test', description: 'Test provider profile', source: 'provider' },
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createProviderCommandEntry(): ICommand {
|
|
34
|
+
return {
|
|
35
|
+
name: 'provider',
|
|
36
|
+
displayName: 'Provider Setup',
|
|
37
|
+
description:
|
|
38
|
+
'Show, configure or switch provider profiles. Suggest /provider reconnect <profile> when a saved service connection needs new credentials, or /provider edit <profile> for its model or server settings; returns profile details or the result of the change.',
|
|
39
|
+
source: 'provider',
|
|
40
|
+
// User-only: provider profiles hold account credentials; a credential action.
|
|
41
|
+
modelInvocable: false,
|
|
42
|
+
argumentHint:
|
|
43
|
+
'current | list | switch <profile> | add [type] | edit <profile> | reconnect <profile> | test [profile]',
|
|
44
|
+
subcommands: buildProviderSubcommands(),
|
|
45
|
+
example: '/provider switch production',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class ProviderCommandSource implements ICommandSource {
|
|
50
|
+
readonly name = 'provider';
|
|
51
|
+
|
|
52
|
+
getCommands(): ICommand[] {
|
|
53
|
+
return [createProviderCommandEntry()];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createProviderSystemCommand(options: IProviderCommandModuleOptions): TSystemCommand {
|
|
58
|
+
const entry = createProviderCommandEntry();
|
|
59
|
+
return {
|
|
60
|
+
name: entry.name,
|
|
61
|
+
displayName: entry.displayName,
|
|
62
|
+
description: entry.description,
|
|
63
|
+
example: entry.example,
|
|
64
|
+
requiresPermission: false,
|
|
65
|
+
userInvocable: true,
|
|
66
|
+
modelInvocable: false,
|
|
67
|
+
argumentHint: entry.argumentHint,
|
|
68
|
+
subcommands: entry.subcommands,
|
|
69
|
+
lifecycle: 'inline',
|
|
70
|
+
execute: (context, args) => executeProviderCommand(context, args, options),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createProviderCommandModule(
|
|
75
|
+
options: IProviderCommandModuleOptions,
|
|
76
|
+
): TCommandModule {
|
|
77
|
+
return {
|
|
78
|
+
name: 'agent-command-provider',
|
|
79
|
+
commandSources: [new ProviderCommandSource()],
|
|
80
|
+
systemCommands: [createProviderSystemCommand(options)],
|
|
81
|
+
};
|
|
82
|
+
}
|