@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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/node/index.cjs +30 -0
  3. package/dist/node/index.d.ts +293 -0
  4. package/dist/node/index.d.ts.map +1 -0
  5. package/dist/node/index.js +31 -0
  6. package/dist/node/index.js.map +1 -0
  7. package/package.json +48 -0
  8. package/src/agent/__tests__/agent-command.test.ts +504 -0
  9. package/src/agent/agent-command-module.ts +82 -0
  10. package/src/agent/agent-command-parser.ts +180 -0
  11. package/src/agent/agent-command.ts +235 -0
  12. package/src/agent/index.ts +7 -0
  13. package/src/background/__tests__/background-command-module.test.ts +255 -0
  14. package/src/background/background-command-module.ts +53 -0
  15. package/src/background/background-command.ts +63 -0
  16. package/src/background/index.ts +6 -0
  17. package/src/compact/__tests__/compact-command-module.test.ts +162 -0
  18. package/src/compact/compact-command-module.ts +51 -0
  19. package/src/compact/compact-command.ts +21 -0
  20. package/src/compact/index.ts +6 -0
  21. package/src/context/__tests__/context-command-module.test.ts +294 -0
  22. package/src/context/context-command-module.ts +54 -0
  23. package/src/context/context-command.ts +298 -0
  24. package/src/context/index.ts +6 -0
  25. package/src/exit/__tests__/exit-command-module.test.ts +35 -0
  26. package/src/exit/exit-command-module.ts +48 -0
  27. package/src/exit/exit-command.ts +10 -0
  28. package/src/exit/index.ts +6 -0
  29. package/src/help/__tests__/help-command-module.test.ts +106 -0
  30. package/src/help/help-command-module.ts +48 -0
  31. package/src/help/help-command.ts +9 -0
  32. package/src/help/index.ts +6 -0
  33. package/src/index.ts +20 -0
  34. package/src/language/__tests__/language-command-module.test.ts +105 -0
  35. package/src/language/index.ts +6 -0
  36. package/src/language/language-command-module.ts +56 -0
  37. package/src/language/language-command.ts +22 -0
  38. package/src/memory/__tests__/memory-command-module.test.ts +272 -0
  39. package/src/memory/index.ts +6 -0
  40. package/src/memory/memory-command-module.ts +57 -0
  41. package/src/memory/memory-command.ts +234 -0
  42. package/src/mode/__tests__/mode-command-module.test.ts +143 -0
  43. package/src/mode/index.ts +6 -0
  44. package/src/mode/mode-command-module.ts +56 -0
  45. package/src/mode/mode-command.ts +34 -0
  46. package/src/model/__tests__/model-command-module.test.ts +273 -0
  47. package/src/model/index.ts +6 -0
  48. package/src/model/model-command-module.ts +68 -0
  49. package/src/model/model-command.ts +40 -0
  50. package/src/permissions/__tests__/permissions-command-module.test.ts +164 -0
  51. package/src/permissions/index.ts +6 -0
  52. package/src/permissions/permissions-command-module.ts +56 -0
  53. package/src/permissions/permissions-command.ts +45 -0
  54. package/src/plugin/__tests__/plugin-command-module.test.ts +214 -0
  55. package/src/plugin/index.ts +7 -0
  56. package/src/plugin/plugin-command-module.ts +81 -0
  57. package/src/plugin/plugin-command.ts +230 -0
  58. package/src/provider/__tests__/provider-command-module.test.ts +488 -0
  59. package/src/provider/__tests__/provider-setup-flow.test.ts +43 -0
  60. package/src/provider/index.ts +30 -0
  61. package/src/provider/provider-command-execution.ts +150 -0
  62. package/src/provider/provider-command-module.ts +65 -0
  63. package/src/provider/provider-command-profile-lifecycle.ts +211 -0
  64. package/src/provider/provider-command-profile-operations.ts +198 -0
  65. package/src/provider/provider-command-profile.ts +109 -0
  66. package/src/provider/provider-command-setup.ts +104 -0
  67. package/src/provider/provider-setup-flow.ts +309 -0
  68. package/src/reset/__tests__/reset-command-module.test.ts +63 -0
  69. package/src/reset/index.ts +2 -0
  70. package/src/reset/reset-command-module.ts +49 -0
  71. package/src/reset/reset-command.ts +10 -0
  72. package/src/rewind/__tests__/rewind-command-module.test.ts +215 -0
  73. package/src/rewind/index.ts +2 -0
  74. package/src/rewind/rewind-command-module.ts +57 -0
  75. package/src/rewind/rewind-command.ts +184 -0
  76. package/src/session/__tests__/session-command-module.test.ts +339 -0
  77. package/src/session/index.ts +17 -0
  78. package/src/session/session-command-module.ts +168 -0
  79. package/src/session/session-command.ts +74 -0
  80. package/src/settings/index.ts +7 -0
  81. package/src/settings/settings-command-module.ts +50 -0
  82. package/src/skills/__tests__/skills-command-module.test.ts +157 -0
  83. package/src/skills/index.ts +6 -0
  84. package/src/skills/skills-command-module.ts +62 -0
  85. package/src/skills/skills-command.ts +110 -0
  86. package/src/statusline/__tests__/statusline-command-module.test.ts +95 -0
  87. package/src/statusline/index.ts +6 -0
  88. package/src/statusline/statusline-command-module.ts +56 -0
  89. package/src/statusline/statusline-command.ts +79 -0
  90. package/src/user-local/__tests__/user-local-command.test.ts +145 -0
  91. package/src/user-local/index.ts +13 -0
  92. package/src/user-local/user-local-command-constants.ts +5 -0
  93. package/src/user-local/user-local-command-module.ts +67 -0
  94. package/src/user-local/user-local-command.ts +205 -0
  95. package/src/user-local/user-local-memory-command.ts +147 -0
@@ -0,0 +1,488 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
3
+ import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
4
+ import type {
5
+ ICommandHostContext,
6
+ IProviderCommandSettingsAdapter,
7
+ TProviderSettingsDocument,
8
+ } from '@robota-sdk/agent-framework';
9
+ import { createProviderCommandModule } from '../provider-command-module.js';
10
+
11
+ const providerDefinitions: readonly IProviderDefinition[] = [
12
+ {
13
+ type: 'openai',
14
+ displayName: 'OpenAI Compatible',
15
+ description: 'Use OpenAI or an OpenAI-compatible endpoint',
16
+ defaults: {
17
+ model: 'supergemma4-26b-uncensored-v2',
18
+ apiKey: 'lm-studio',
19
+ baseURL: 'http://localhost:1234/v1',
20
+ },
21
+ setupHelpLinks: [
22
+ {
23
+ kind: 'official',
24
+ label: 'OpenAI-compatible local server docs',
25
+ url: 'https://lmstudio.ai/docs/developer',
26
+ },
27
+ ],
28
+ setupSteps: [
29
+ {
30
+ key: 'baseURL',
31
+ title: 'OpenAI-compatible base URL',
32
+ defaultValue: 'http://localhost:1234/v1',
33
+ },
34
+ {
35
+ key: 'model',
36
+ title: 'OpenAI-compatible model',
37
+ defaultValue: 'supergemma4-26b-uncensored-v2',
38
+ },
39
+ {
40
+ key: 'apiKey',
41
+ title: 'OpenAI-compatible API key',
42
+ defaultValue: 'lm-studio',
43
+ masked: true,
44
+ },
45
+ ],
46
+ requiresApiKey: true,
47
+ createProvider: () => {
48
+ throw new Error('not used');
49
+ },
50
+ },
51
+ {
52
+ type: 'anthropic',
53
+ defaults: {
54
+ model: 'claude-sonnet-4-6',
55
+ apiKey: '$ENV:ANTHROPIC_API_KEY',
56
+ },
57
+ setupSteps: [
58
+ { key: 'apiKey', title: 'anthropic API key', masked: true },
59
+ { key: 'model', title: 'anthropic model', defaultValue: 'claude-sonnet-4-6' },
60
+ ],
61
+ requiresApiKey: true,
62
+ createProvider: () => {
63
+ throw new Error('not used');
64
+ },
65
+ },
66
+ ];
67
+
68
+ function createSettingsAdapter(
69
+ merged: TProviderSettingsDocument,
70
+ target: TProviderSettingsDocument = {},
71
+ ): {
72
+ adapter: IProviderCommandSettingsAdapter;
73
+ readTarget: () => TProviderSettingsDocument;
74
+ } {
75
+ let writable = target;
76
+ return {
77
+ adapter: {
78
+ readMergedSettings: () => merged,
79
+ readTargetSettings: () => writable,
80
+ writeTargetSettings: (next) => {
81
+ writable = next;
82
+ },
83
+ },
84
+ readTarget: () => writable,
85
+ };
86
+ }
87
+
88
+ function createExecutor(adapter: IProviderCommandSettingsAdapter): SystemCommandExecutor {
89
+ const module = createProviderCommandModule({
90
+ providerDefinitions,
91
+ settings: adapter,
92
+ });
93
+ return new SystemCommandExecutor([...(module.systemCommands ?? [])]);
94
+ }
95
+
96
+ const session = {} as ICommandHostContext;
97
+
98
+ describe('createProviderCommandModule', () => {
99
+ it('contributes /provider metadata and executable command from the provider package', () => {
100
+ const { adapter } = createSettingsAdapter({});
101
+ const module = createProviderCommandModule({ providerDefinitions, settings: adapter });
102
+ const commands = module.commandSources?.flatMap((source) => source.getCommands()) ?? [];
103
+
104
+ expect(module.name).toBe('agent-command-provider');
105
+ expect(commands.map((command) => command.name)).toEqual(['provider']);
106
+ expect(commands[0]?.subcommands?.map((command) => command.name)).toContain('use');
107
+ expect(module.systemCommands?.map((command) => command.name)).toEqual(['provider']);
108
+ });
109
+
110
+ it('lists provider profiles from injected merged settings', async () => {
111
+ const { adapter } = createSettingsAdapter({
112
+ currentProvider: 'openai',
113
+ providers: {
114
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
115
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
116
+ },
117
+ });
118
+
119
+ const result = await createExecutor(adapter).execute('provider', session, 'list');
120
+
121
+ expect(result?.success).toBe(true);
122
+ expect(result?.message).toContain('* openai');
123
+ expect(result?.message).toContain('anthropic');
124
+ expect(result?.interaction?.prompt).toMatchObject({
125
+ kind: 'choice',
126
+ title: 'Select provider profile',
127
+ });
128
+ });
129
+
130
+ it('opens a provider profile picker from /provider without CLI-owned routing', async () => {
131
+ const { adapter } = createSettingsAdapter({
132
+ currentProvider: 'openai',
133
+ providers: {
134
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
135
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
136
+ },
137
+ });
138
+
139
+ const result = await createExecutor(adapter).execute('provider', session, '');
140
+ const selected = await result?.interaction?.submit('anthropic');
141
+
142
+ expect(result?.message).toContain('* openai');
143
+ expect(selected?.interaction?.prompt).toMatchObject({
144
+ kind: 'choice',
145
+ title: 'Provider profile: anthropic',
146
+ });
147
+ });
148
+
149
+ it('confirms provider switch through a generic command interaction', async () => {
150
+ const { adapter, readTarget } = createSettingsAdapter(
151
+ {
152
+ currentProvider: 'anthropic',
153
+ providers: {
154
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
155
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
156
+ },
157
+ },
158
+ {},
159
+ );
160
+
161
+ const result = await createExecutor(adapter).execute('provider', session, 'use openai');
162
+ const submitted = await result?.interaction?.submit('yes');
163
+
164
+ expect(result?.interaction?.prompt).toMatchObject({
165
+ kind: 'choice',
166
+ title: 'Change provider to openai? This will restart the session.',
167
+ });
168
+ expect(readTarget().currentProvider).toBe('openai');
169
+ expect(submitted?.effects).toEqual([
170
+ {
171
+ type: 'session-restart-requested',
172
+ reason: 'other',
173
+ message: 'Provider change restart',
174
+ },
175
+ ]);
176
+ });
177
+
178
+ it('switches from the provider profile action menu through the same restart interaction', async () => {
179
+ const { adapter, readTarget } = createSettingsAdapter(
180
+ {
181
+ currentProvider: 'anthropic',
182
+ providers: {
183
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
184
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
185
+ },
186
+ },
187
+ {},
188
+ );
189
+
190
+ const listed = await createExecutor(adapter).execute('provider', session, 'list');
191
+ const selected = await listed?.interaction?.submit('openai');
192
+ const switchRequested = await selected?.interaction?.submit('switch');
193
+ const submitted = await switchRequested?.interaction?.submit('yes');
194
+
195
+ expect(switchRequested?.message).toBe('Provider change requested: openai');
196
+ expect(readTarget().currentProvider).toBe('openai');
197
+ expect(submitted?.effects).toEqual([
198
+ {
199
+ type: 'session-restart-requested',
200
+ reason: 'other',
201
+ message: 'Provider change restart',
202
+ },
203
+ ]);
204
+ });
205
+
206
+ it('edits a provider profile through provider-owned setup metadata without exposing secrets', async () => {
207
+ const { adapter, readTarget } = createSettingsAdapter(
208
+ {
209
+ currentProvider: 'anthropic',
210
+ providers: {
211
+ anthropic: {
212
+ type: 'anthropic',
213
+ model: 'claude-sonnet-4-6',
214
+ apiKey: 'sk-ant-secret',
215
+ },
216
+ },
217
+ },
218
+ {},
219
+ );
220
+
221
+ const listed = await createExecutor(adapter).execute('provider', session, 'list');
222
+ const selected = await listed?.interaction?.submit('anthropic');
223
+ const editRequested = await selected?.interaction?.submit('edit');
224
+
225
+ expect(editRequested?.interaction?.prompt).toMatchObject({
226
+ kind: 'text',
227
+ title: 'anthropic API key',
228
+ placeholder: '(unchanged)',
229
+ masked: true,
230
+ });
231
+
232
+ const modelPrompt = await editRequested?.interaction?.submit('');
233
+ const completed = await modelPrompt?.interaction?.submit('claude-opus-4-5');
234
+
235
+ expect(readTarget()).toMatchObject({
236
+ providers: {
237
+ anthropic: {
238
+ type: 'anthropic',
239
+ model: 'claude-opus-4-5',
240
+ apiKey: 'sk-ant-secret',
241
+ },
242
+ },
243
+ });
244
+ expect(completed?.message).toBe('Provider anthropic updated. Restarting...');
245
+ expect(completed?.effects).toEqual([
246
+ {
247
+ type: 'session-restart-requested',
248
+ reason: 'other',
249
+ message: 'Provider edit restart',
250
+ },
251
+ ]);
252
+ });
253
+
254
+ it('duplicates a provider profile from the action menu without switching sessions', async () => {
255
+ const { adapter, readTarget } = createSettingsAdapter({
256
+ currentProvider: 'openai',
257
+ providers: {
258
+ openai: {
259
+ type: 'openai',
260
+ model: 'supergemma4-26b-uncensored-v2',
261
+ apiKey: 'lm-studio',
262
+ },
263
+ },
264
+ });
265
+
266
+ const listed = await createExecutor(adapter).execute('provider', session, 'list');
267
+ const selected = await listed?.interaction?.submit('openai');
268
+ const duplicateRequested = await selected?.interaction?.submit('duplicate');
269
+ const completed = await duplicateRequested?.interaction?.submit('');
270
+
271
+ expect(duplicateRequested?.interaction?.prompt).toMatchObject({
272
+ kind: 'text',
273
+ title: 'Duplicate openai as',
274
+ placeholder: 'openai-copy',
275
+ });
276
+ expect(readTarget()).toMatchObject({
277
+ providers: {
278
+ 'openai-copy': {
279
+ type: 'openai',
280
+ model: 'supergemma4-26b-uncensored-v2',
281
+ apiKey: 'lm-studio',
282
+ },
283
+ },
284
+ });
285
+ expect(completed?.effects).toBeUndefined();
286
+ });
287
+
288
+ it('deletes inactive provider profiles after confirmation', async () => {
289
+ const { adapter, readTarget } = createSettingsAdapter(
290
+ {
291
+ currentProvider: 'openai',
292
+ providers: {
293
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
294
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
295
+ },
296
+ },
297
+ {
298
+ currentProvider: 'openai',
299
+ providers: {
300
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
301
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
302
+ },
303
+ },
304
+ );
305
+
306
+ const listed = await createExecutor(adapter).execute('provider', session, 'list');
307
+ const selected = await listed?.interaction?.submit('anthropic');
308
+ const deleteRequested = await selected?.interaction?.submit('delete');
309
+ const completed = await deleteRequested?.interaction?.submit('yes');
310
+
311
+ expect(completed?.message).toBe('Provider profile deleted: anthropic.');
312
+ expect(readTarget()).toEqual({
313
+ currentProvider: 'openai',
314
+ providers: {
315
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
316
+ },
317
+ });
318
+ });
319
+
320
+ it('requires a replacement before deleting the active provider profile', async () => {
321
+ const { adapter, readTarget } = createSettingsAdapter(
322
+ {
323
+ currentProvider: 'anthropic',
324
+ providers: {
325
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
326
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
327
+ },
328
+ },
329
+ {
330
+ currentProvider: 'anthropic',
331
+ providers: {
332
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
333
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
334
+ },
335
+ },
336
+ );
337
+
338
+ const listed = await createExecutor(adapter).execute('provider', session, 'list');
339
+ const selected = await listed?.interaction?.submit('anthropic');
340
+ const deleteRequested = await selected?.interaction?.submit('delete');
341
+ const replacementPrompt = await deleteRequested?.interaction?.submit('yes');
342
+ const completed = await replacementPrompt?.interaction?.submit('openai');
343
+
344
+ expect(replacementPrompt?.interaction?.prompt).toMatchObject({
345
+ kind: 'choice',
346
+ title: 'Replacement provider for anthropic',
347
+ });
348
+ expect(readTarget()).toEqual({
349
+ currentProvider: 'openai',
350
+ providers: {
351
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
352
+ },
353
+ });
354
+ expect(completed?.effects).toEqual([
355
+ {
356
+ type: 'session-restart-requested',
357
+ reason: 'other',
358
+ message: 'Provider delete restart',
359
+ },
360
+ ]);
361
+ });
362
+
363
+ it('blocks deletion of inherited provider profiles instead of pretending to remove them', async () => {
364
+ const { adapter } = createSettingsAdapter(
365
+ {
366
+ currentProvider: 'openai',
367
+ providers: {
368
+ openai: { type: 'openai', model: 'supergemma4-26b-uncensored-v2' },
369
+ anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
370
+ },
371
+ },
372
+ {},
373
+ );
374
+
375
+ const listed = await createExecutor(adapter).execute('provider', session, 'list');
376
+ const selected = await listed?.interaction?.submit('anthropic');
377
+ const deleteRequested = await selected?.interaction?.submit('delete');
378
+
379
+ expect(deleteRequested?.success).toBe(false);
380
+ expect(deleteRequested?.message).toContain('not stored in the active write target');
381
+ });
382
+
383
+ it('owns provider setup flow and writes settings after generic prompt submissions', async () => {
384
+ const { adapter, readTarget } = createSettingsAdapter({}, {});
385
+ const first = await createExecutor(adapter).execute('provider', session, 'add openai');
386
+
387
+ expect(first?.interaction?.prompt).toMatchObject({
388
+ kind: 'text',
389
+ title: 'OpenAI-compatible base URL',
390
+ description:
391
+ ' Setup help: Official: OpenAI-compatible local server docs - https://lmstudio.ai/docs/developer',
392
+ placeholder: 'http://localhost:1234/v1',
393
+ allowEmpty: true,
394
+ });
395
+
396
+ const second = await first?.interaction?.submit('');
397
+ const third = await second?.interaction?.submit('');
398
+ const completed = await third?.interaction?.submit('');
399
+
400
+ expect(readTarget()).toMatchObject({
401
+ currentProvider: 'openai',
402
+ providers: {
403
+ openai: {
404
+ type: 'openai',
405
+ baseURL: 'http://localhost:1234/v1',
406
+ model: 'supergemma4-26b-uncensored-v2',
407
+ apiKey: 'lm-studio',
408
+ },
409
+ },
410
+ });
411
+ expect(completed?.effects).toEqual([
412
+ {
413
+ type: 'session-restart-requested',
414
+ reason: 'other',
415
+ message: 'Provider setup restart',
416
+ },
417
+ ]);
418
+ });
419
+
420
+ it('creates another profile when provider type already exists', async () => {
421
+ const { adapter, readTarget } = createSettingsAdapter(
422
+ {
423
+ currentProvider: 'openai',
424
+ providers: {
425
+ openai: {
426
+ type: 'openai',
427
+ baseURL: 'http://localhost:1234/v1',
428
+ model: 'supergemma4-26b-uncensored-v2',
429
+ apiKey: 'lm-studio',
430
+ },
431
+ },
432
+ },
433
+ {},
434
+ );
435
+ const first = await createExecutor(adapter).execute('provider', session, 'add openai');
436
+ const second = await first?.interaction?.submit('');
437
+ const third = await second?.interaction?.submit('');
438
+
439
+ await third?.interaction?.submit('');
440
+
441
+ expect(readTarget()).toMatchObject({
442
+ currentProvider: 'openai-2',
443
+ providers: {
444
+ 'openai-2': {
445
+ type: 'openai',
446
+ baseURL: 'http://localhost:1234/v1',
447
+ model: 'supergemma4-26b-uncensored-v2',
448
+ apiKey: 'lm-studio',
449
+ },
450
+ },
451
+ });
452
+ });
453
+
454
+ it('validates provider profile before probe without blocking manual configuration', async () => {
455
+ const probe = vi.fn().mockResolvedValue({ ok: false, message: 'Connection failed' });
456
+ const { adapter } = createSettingsAdapter({
457
+ currentProvider: 'openai',
458
+ providers: {
459
+ openai: {
460
+ type: 'openai',
461
+ model: 'supergemma4-26b-uncensored-v2',
462
+ baseURL: 'http://localhost:1234/v1',
463
+ },
464
+ },
465
+ });
466
+
467
+ const module = createProviderCommandModule({
468
+ providerDefinitions: [
469
+ {
470
+ ...providerDefinitions[0]!,
471
+ requiresApiKey: false,
472
+ probeProfile: probe,
473
+ },
474
+ ],
475
+ settings: adapter,
476
+ });
477
+ const result = await new SystemCommandExecutor([...(module.systemCommands ?? [])]).execute(
478
+ 'provider',
479
+ session,
480
+ 'test openai',
481
+ );
482
+
483
+ expect(result?.success).toBe(true);
484
+ expect(result?.message).toContain('Connection failed');
485
+ expect(result?.message).toContain('manual configuration can continue');
486
+ expect(probe).toHaveBeenCalled();
487
+ });
488
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
3
+ import {
4
+ createProviderSetupFlow,
5
+ formatProviderSetupHelpLinks,
6
+ formatProviderSetupPromptLabel,
7
+ getProviderSetupStep,
8
+ } from '../provider-setup-flow.js';
9
+
10
+ const providerDefinitions: readonly IProviderDefinition[] = [
11
+ {
12
+ type: 'openai',
13
+ setupHelpLinks: [
14
+ {
15
+ kind: 'api-key',
16
+ label: 'OpenAI API keys',
17
+ url: 'https://platform.openai.com/api-keys',
18
+ },
19
+ ],
20
+ setupSteps: [{ key: 'apiKey', title: 'OpenAI API key', masked: true }],
21
+ requiresApiKey: true,
22
+ createProvider: () => {
23
+ throw new Error('not used');
24
+ },
25
+ },
26
+ ];
27
+
28
+ describe('provider setup flow', () => {
29
+ it('carries provider-owned setup help links into generic prompt labels', () => {
30
+ const state = createProviderSetupFlow('openai', providerDefinitions);
31
+
32
+ expect(state.setupHelpLinks).toEqual(providerDefinitions[0]?.setupHelpLinks);
33
+ expect(formatProviderSetupHelpLinks(state.setupHelpLinks)).toBe(
34
+ ' Setup help: API key: OpenAI API keys - https://platform.openai.com/api-keys',
35
+ );
36
+ expect(formatProviderSetupPromptLabel(getProviderSetupStep(state), state.setupHelpLinks)).toBe(
37
+ [
38
+ ' Setup help: API key: OpenAI API keys - https://platform.openai.com/api-keys',
39
+ ' OpenAI API key: ',
40
+ ].join('\n'),
41
+ );
42
+ });
43
+ });
@@ -0,0 +1,30 @@
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';