@robota-sdk/agent-command 3.0.0-beta.92 → 3.0.0-beta.94
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 +24 -0
- package/dist/node/index.cjs +78 -75
- package/dist/node/index.d.cts +13 -4
- package/dist/node/index.d.cts.map +1 -1
- package/dist/node/index.d.ts +13 -4
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +83 -80
- package/dist/node/index.js.map +1 -1
- package/package.json +7 -7
- package/src/default/default-command-modules.ts +15 -1
- package/src/model/model-command.ts +41 -16
- 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/provider-command-execution.ts +23 -5
- package/src/provider/provider-command-module.ts +8 -2
- package/src/provider/provider-command-profile-operations.ts +60 -12
- package/src/provider/provider-command-profile.ts +8 -0
- package/src/provider/provider-command-setup.ts +11 -60
- package/src/provider/provider-connection-setup.ts +287 -0
- package/src/provider/provider-connection-validation.ts +38 -0
- package/src/provider/provider-onboarding.ts +11 -2
- 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 +5 -1
- package/src/provider/provider-startup.ts +63 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TPromptInput } from './provider-setup-flow.js';
|
|
2
2
|
import type { ITerminalOutput } from '@robota-sdk/agent-core';
|
|
3
3
|
|
|
4
|
-
export type TOnboardingPath = 'has-key' | 'free-key' | 'local';
|
|
4
|
+
export type TOnboardingPath = 'has-key' | 'free-key' | 'local' | 'connect';
|
|
5
5
|
|
|
6
6
|
export interface IOnboardingResult {
|
|
7
7
|
path: TOnboardingPath;
|
|
@@ -44,10 +44,19 @@ const LOCAL_MODEL_GUIDE = `
|
|
|
44
44
|
export async function runOnboardingBranch(
|
|
45
45
|
promptInput: TPromptInput,
|
|
46
46
|
terminal: ITerminalOutput,
|
|
47
|
+
offerOpenRouter = false,
|
|
47
48
|
): Promise<IOnboardingResult> {
|
|
48
|
-
const
|
|
49
|
+
const prompt = offerOpenRouter
|
|
50
|
+
? ONBOARDING_PROMPT.replace(
|
|
51
|
+
' Choose [1-3]',
|
|
52
|
+
' 4. Connect with OpenRouter (Browser or API key)\n\n Choose [1-4]',
|
|
53
|
+
)
|
|
54
|
+
: ONBOARDING_PROMPT;
|
|
55
|
+
const raw = await promptInput(prompt);
|
|
49
56
|
const choice = raw.trim() || '1';
|
|
50
57
|
|
|
58
|
+
if (offerOpenRouter && choice === '4') return { path: 'connect', preselectedType: 'openrouter' };
|
|
59
|
+
|
|
51
60
|
if (choice === '2') {
|
|
52
61
|
terminal.writeLine(GEMINI_GUIDE);
|
|
53
62
|
await promptInput(' Press Enter when you have your API key: ');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
2
|
+
import type { TPromptInput } from './provider-setup-flow.js';
|
|
3
|
+
|
|
4
|
+
/** Adapt existing terminal key/model prompts; browser waiting uses the host's abortable interaction. */
|
|
5
|
+
export function createProviderPromptInteraction(promptInput: TPromptInput): IUserInteraction {
|
|
6
|
+
return {
|
|
7
|
+
async ask(request, options) {
|
|
8
|
+
const choices = request.options ?? [];
|
|
9
|
+
const labels = [
|
|
10
|
+
request.description,
|
|
11
|
+
` ${request.title}`,
|
|
12
|
+
...choices.map((choice, index) => ` ${index + 1}. ${choice.label}`),
|
|
13
|
+
choices.length > 0 ? ' Choose: ' : ` ${request.placeholder ?? ''}: `,
|
|
14
|
+
]
|
|
15
|
+
.filter((value) => value !== undefined)
|
|
16
|
+
.join('\n');
|
|
17
|
+
const answer = await promptInput(labels, request.masked, options);
|
|
18
|
+
if (options?.signal?.aborted) return { type: 'cancelled' };
|
|
19
|
+
if (choices.length === 0) return { type: 'answer', values: [], text: answer };
|
|
20
|
+
const selected =
|
|
21
|
+
choices[Number(answer.trim() || '1') - 1] ??
|
|
22
|
+
choices.find((choice) => choice.value === answer.trim());
|
|
23
|
+
return selected === undefined
|
|
24
|
+
? { type: 'cancelled' }
|
|
25
|
+
: { type: 'answer', values: [selected.value] };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { textAction } from '@robota-sdk/agent-core';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
formatProviderSetupHelpLinks,
|
|
5
|
+
getProviderSetupStep,
|
|
6
|
+
submitProviderSetupValue,
|
|
7
|
+
} from './provider-setup-flow.js';
|
|
8
|
+
|
|
9
|
+
import type { IActionRequest, IUserInteraction } from '@robota-sdk/agent-core';
|
|
10
|
+
import type { IProviderSetupInput } from '@robota-sdk/agent-framework';
|
|
11
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
12
|
+
import type { IProviderSetupFlowState } from './provider-setup-flow.js';
|
|
13
|
+
|
|
14
|
+
/** Build the per-step `IActionRequest` for the current setup step (CMD-004 inline ask). */
|
|
15
|
+
function toProviderSetupStepRequest(
|
|
16
|
+
flow: IProviderSetupFlowState,
|
|
17
|
+
errorMessage?: string,
|
|
18
|
+
): IActionRequest {
|
|
19
|
+
const step = getProviderSetupStep(flow);
|
|
20
|
+
const placeholder =
|
|
21
|
+
step.masked === true && step.defaultValue !== undefined ? '(unchanged)' : step.defaultValue;
|
|
22
|
+
const helpLinks = formatProviderSetupHelpLinks(flow.setupHelpLinks);
|
|
23
|
+
const description =
|
|
24
|
+
[errorMessage, helpLinks.length > 0 ? helpLinks : undefined]
|
|
25
|
+
.filter((part): part is string => part !== undefined && part.length > 0)
|
|
26
|
+
.join('\n') || undefined;
|
|
27
|
+
return textAction(`provider-setup-${step.key}`, step.title, {
|
|
28
|
+
description,
|
|
29
|
+
placeholder,
|
|
30
|
+
allowEmpty:
|
|
31
|
+
step.defaultValue !== undefined || (step.editOnly === true && step.required !== true),
|
|
32
|
+
masked: step.masked,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Drive the setup-step engine through inline `ui.ask` calls (CMD-004), re-asking the same step when a
|
|
38
|
+
* step fails validation (the error is surfaced in the request description). The `complete` sink differs
|
|
39
|
+
* between the add and edit paths.
|
|
40
|
+
*/
|
|
41
|
+
export async function runProviderSetupAsk(
|
|
42
|
+
ui: IUserInteraction,
|
|
43
|
+
flow: IProviderSetupFlowState,
|
|
44
|
+
complete: (input: IProviderSetupInput) => ICommandResult | Promise<ICommandResult>,
|
|
45
|
+
cancelMessage: string,
|
|
46
|
+
signal?: AbortSignal,
|
|
47
|
+
): Promise<ICommandResult> {
|
|
48
|
+
let state = flow;
|
|
49
|
+
let errorMessage: string | undefined;
|
|
50
|
+
for (;;) {
|
|
51
|
+
if (signal?.aborted) return { message: cancelMessage, success: true };
|
|
52
|
+
const response = await ui.ask(toProviderSetupStepRequest(state, errorMessage), { signal });
|
|
53
|
+
if (response.type === 'cancelled') {
|
|
54
|
+
return { message: cancelMessage, success: true };
|
|
55
|
+
}
|
|
56
|
+
const result = submitProviderSetupValue(state, response.text ?? '');
|
|
57
|
+
if (result.status === 'error') {
|
|
58
|
+
errorMessage = result.message;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (result.status === 'complete') {
|
|
62
|
+
return complete(result.input);
|
|
63
|
+
}
|
|
64
|
+
state = result.state;
|
|
65
|
+
errorMessage = undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -10,7 +10,11 @@ import type {
|
|
|
10
10
|
import type { IProviderSetupInput } from '@robota-sdk/agent-framework';
|
|
11
11
|
|
|
12
12
|
export type TProviderSetupType = string;
|
|
13
|
-
export type TPromptInput = (
|
|
13
|
+
export type TPromptInput = (
|
|
14
|
+
label: string,
|
|
15
|
+
masked?: boolean,
|
|
16
|
+
options?: { signal?: AbortSignal },
|
|
17
|
+
) => Promise<string>;
|
|
14
18
|
|
|
15
19
|
export interface IProviderSetupPromptStep extends IProviderSetupStepDefinition {
|
|
16
20
|
key: TProviderSetupField;
|
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
} from '@robota-sdk/agent-framework';
|
|
10
10
|
|
|
11
11
|
import { runOnboardingBranch } from './provider-onboarding.js';
|
|
12
|
+
import { createProviderSetupFlow } from './provider-setup-flow.js';
|
|
13
|
+
import {
|
|
14
|
+
hasProviderConnectionMethods,
|
|
15
|
+
runProviderConnectionSetup,
|
|
16
|
+
} from './provider-connection-setup.js';
|
|
17
|
+
import { createProviderPromptInteraction } from './provider-prompt-interaction.js';
|
|
12
18
|
import {
|
|
13
19
|
formatProviderSetupSelectionPrompt,
|
|
14
20
|
resolveProviderSetupSelection,
|
|
@@ -16,12 +22,15 @@ import {
|
|
|
16
22
|
type TPromptInput,
|
|
17
23
|
} from './provider-setup-flow.js';
|
|
18
24
|
|
|
19
|
-
import type { IProviderDefinition } from '@robota-sdk/agent-core';
|
|
25
|
+
import type { IProviderDefinition, IUserInteraction } from '@robota-sdk/agent-core';
|
|
20
26
|
import type { ITerminalOutput } from '@robota-sdk/agent-core';
|
|
21
27
|
import type {
|
|
22
28
|
ISettingsDocumentStore,
|
|
23
29
|
TSettingsScope,
|
|
24
30
|
TSettingsSource,
|
|
31
|
+
IProviderConnectionHost,
|
|
32
|
+
IOrgPolicy,
|
|
33
|
+
TProviderSettingsDocument,
|
|
25
34
|
} from '@robota-sdk/agent-framework';
|
|
26
35
|
|
|
27
36
|
export interface IProviderStartupContext {
|
|
@@ -31,6 +40,9 @@ export interface IProviderStartupContext {
|
|
|
31
40
|
settingsStores: readonly ISettingsDocumentStore[];
|
|
32
41
|
/** Selected host snapshot for configure-time credential reference checks. */
|
|
33
42
|
env?: Record<string, string | undefined>;
|
|
43
|
+
connectionHost?: IProviderConnectionHost;
|
|
44
|
+
orgPolicy?: IOrgPolicy;
|
|
45
|
+
interaction?: IUserInteraction;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
export interface IEnsureProviderConfigOptions {
|
|
@@ -47,7 +59,11 @@ export async function runProviderStartupSetup(
|
|
|
47
59
|
terminal: ITerminalOutput,
|
|
48
60
|
providerDefinitions: readonly IProviderDefinition[],
|
|
49
61
|
): Promise<void> {
|
|
50
|
-
const onboarding = await runOnboardingBranch(
|
|
62
|
+
const onboarding = await runOnboardingBranch(
|
|
63
|
+
promptInput,
|
|
64
|
+
terminal,
|
|
65
|
+
providerDefinitions.some((definition) => definition.type === 'openrouter'),
|
|
66
|
+
);
|
|
51
67
|
const access = resolveSettingsAccess(ctx);
|
|
52
68
|
const existingProfileNames = Object.keys(
|
|
53
69
|
readMergedProviderSettings(access.sources).providers ?? {},
|
|
@@ -64,10 +80,45 @@ export async function runProviderStartupSetup(
|
|
|
64
80
|
type = resolveProviderSetupSelection(providerChoice, providerDefinitions);
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
const setupOptions = {
|
|
84
|
+
providerDefinitions,
|
|
85
|
+
env: ctx.env,
|
|
86
|
+
connectionHost: ctx.connectionHost,
|
|
87
|
+
orgPolicy: ctx.orgPolicy,
|
|
88
|
+
settings: {
|
|
89
|
+
readMergedSettings: () => readMergedProviderSettings(access.sources),
|
|
90
|
+
readTargetSettings: () => settingsStore.read() as TProviderSettingsDocument,
|
|
91
|
+
writeTargetSettings: (settings: TProviderSettingsDocument) => settingsStore.write(settings),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
if (hasProviderConnectionMethods(type, setupOptions)) {
|
|
95
|
+
const definitions =
|
|
96
|
+
ctx.interaction === undefined
|
|
97
|
+
? providerDefinitions.map((definition) => ({
|
|
98
|
+
...definition,
|
|
99
|
+
connectionMethods: definition.connectionMethods?.filter(
|
|
100
|
+
(method) => method !== 'browser',
|
|
101
|
+
),
|
|
102
|
+
}))
|
|
103
|
+
: providerDefinitions;
|
|
104
|
+
if (ctx.interaction === undefined)
|
|
105
|
+
terminal.writeLine(
|
|
106
|
+
'Browser connection is unavailable here. API key setup remains available.',
|
|
107
|
+
);
|
|
108
|
+
const result = await runProviderConnectionSetup(
|
|
109
|
+
ctx.interaction ?? createProviderPromptInteraction(promptInput),
|
|
110
|
+
createProviderSetupFlow(type, definitions, { existingProfileNames }),
|
|
111
|
+
{ ...setupOptions, providerDefinitions: definitions },
|
|
112
|
+
false,
|
|
113
|
+
);
|
|
114
|
+
if (!result.success) throw new ProviderConfigError(result.message);
|
|
115
|
+
if (result.hostActions === undefined) return;
|
|
116
|
+
} else {
|
|
117
|
+
const input = await runProviderSetupPromptFlow(type, promptInput, providerDefinitions, {
|
|
118
|
+
existingProfileNames,
|
|
119
|
+
});
|
|
120
|
+
applyProviderConfiguration(settingsStore, input, { providerDefinitions, env: ctx.env });
|
|
121
|
+
}
|
|
71
122
|
const language = await promptInput(' Response language (ko/en/ja/zh, default: en): ');
|
|
72
123
|
if (language) {
|
|
73
124
|
const settings = settingsStore.read();
|
|
@@ -89,7 +140,9 @@ export async function ensureProviderConfig(
|
|
|
89
140
|
const merged = readMergedProviderSettings(access.sources);
|
|
90
141
|
const selectedSettings =
|
|
91
142
|
ctx.provider !== undefined ? { ...merged, currentProvider: ctx.provider } : merged;
|
|
92
|
-
if (
|
|
143
|
+
if (
|
|
144
|
+
checkSettingsDocument(selectedSettings, providerDefinitions, ctx.env ?? options.env) === 'valid'
|
|
145
|
+
) {
|
|
93
146
|
return;
|
|
94
147
|
}
|
|
95
148
|
// Zero-config startup: a recognized provider env key with complete definition defaults
|
|
@@ -114,7 +167,9 @@ export async function ensureProviderConfig(
|
|
|
114
167
|
const updated = readMergedProviderSettings(access.sources);
|
|
115
168
|
const updatedSettings =
|
|
116
169
|
ctx.provider !== undefined ? { ...updated, currentProvider: ctx.provider } : updated;
|
|
117
|
-
if (
|
|
170
|
+
if (
|
|
171
|
+
checkSettingsDocument(updatedSettings, providerDefinitions, ctx.env ?? options.env) !== 'valid'
|
|
172
|
+
) {
|
|
118
173
|
throw new ProviderConfigError(options.formatError(providerDefinitions));
|
|
119
174
|
}
|
|
120
175
|
}
|