@robota-sdk/agent-command 3.0.0-beta.67 → 3.0.0-beta.69
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/README.md +0 -1
- package/dist/node/index.cjs +57 -28
- package/dist/node/index.d.ts +3 -16
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +57 -28
- package/dist/node/index.js.map +1 -1
- package/package.json +3 -3
- package/src/compact/__tests__/compact-command-module.test.ts +13 -4
- package/src/compact/compact-command-module.ts +2 -0
- package/src/compact/compact-command.ts +11 -4
- package/src/context/__tests__/context-command-module.test.ts +42 -0
- package/src/default/default-command-modules.ts +0 -5
- package/src/help/__tests__/help-command.test.ts +119 -0
- package/src/index.ts +0 -1
- package/src/provider/__tests__/org-policy.test.ts +165 -0
- package/src/provider/__tests__/provider-command-module.test.ts +18 -29
- package/src/provider/__tests__/provider-setup-flow.test.ts +2 -2
- package/src/provider/provider-command-execution.ts +2 -2
- package/src/provider/provider-command-module.ts +4 -2
- package/src/provider/provider-command-profile-operations.ts +37 -49
- package/src/provider/provider-onboarding.ts +64 -0
- package/src/provider/provider-setup-flow.ts +11 -2
- package/src/provider/provider-startup.ts +15 -3
- package/src/session/__tests__/model-pricing.test.ts +64 -0
- package/src/session/__tests__/session-command-module.test.ts +51 -7
- package/src/session/model-pricing.ts +79 -0
- package/src/session/session-command.ts +124 -8
- package/src/model/__tests__/model-command-module.test.ts +0 -273
- package/src/model/index.ts +0 -6
- package/src/model/model-command-module.ts +0 -70
- package/src/model/model-command.ts +0 -41
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import type {
|
|
3
|
-
ICommandHostContext,
|
|
4
|
-
IEditCheckpointRestoreResult,
|
|
5
|
-
IModelCommandModuleOptions,
|
|
6
|
-
TProviderSettingsDocument,
|
|
7
|
-
} from '@robota-sdk/agent-framework';
|
|
8
|
-
import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
|
|
9
|
-
import { createModelCommandModule } from '../model-command-module.js';
|
|
10
|
-
|
|
11
|
-
type TTestProviderDefinition = NonNullable<
|
|
12
|
-
IModelCommandModuleOptions['providerDefinitions']
|
|
13
|
-
>[number];
|
|
14
|
-
|
|
15
|
-
function createCheckpointResult(): IEditCheckpointRestoreResult {
|
|
16
|
-
return {
|
|
17
|
-
target: {
|
|
18
|
-
id: 'checkpoint_1',
|
|
19
|
-
sessionId: 'session_1',
|
|
20
|
-
sequence: 1,
|
|
21
|
-
prompt: 'edit files',
|
|
22
|
-
createdAt: '2026-05-03T00:00:00.000Z',
|
|
23
|
-
fileCount: 1,
|
|
24
|
-
},
|
|
25
|
-
restoredCheckpointCount: 0,
|
|
26
|
-
restoredFileCount: 0,
|
|
27
|
-
removedCheckpointCount: 0,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const commandHostContext: ICommandHostContext = {
|
|
32
|
-
getSession: () => {
|
|
33
|
-
throw new Error('model command should not read session runtime');
|
|
34
|
-
},
|
|
35
|
-
getContextState: () => ({
|
|
36
|
-
usedTokens: 0,
|
|
37
|
-
maxTokens: 1,
|
|
38
|
-
usedPercentage: 0,
|
|
39
|
-
remainingPercentage: 100,
|
|
40
|
-
}),
|
|
41
|
-
getAutoCompactThreshold: () => 0.835,
|
|
42
|
-
compactContext: async () => undefined,
|
|
43
|
-
getCwd: () => '/workspace',
|
|
44
|
-
listEditCheckpoints: () => [],
|
|
45
|
-
restoreEditCheckpoint: async () => createCheckpointResult(),
|
|
46
|
-
rollbackEditCheckpoint: async () => createCheckpointResult(),
|
|
47
|
-
getUsedMemoryReferences: () => [],
|
|
48
|
-
recordMemoryEvent: () => undefined,
|
|
49
|
-
listBackgroundTasks: () => [],
|
|
50
|
-
readBackgroundTaskLog: async () => ({ taskId: 'task_1', lines: [] }),
|
|
51
|
-
cancelBackgroundTask: async () => undefined,
|
|
52
|
-
closeBackgroundTask: async () => undefined,
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const providerDefinitions: readonly TTestProviderDefinition[] = [
|
|
56
|
-
{
|
|
57
|
-
type: 'anthropic',
|
|
58
|
-
defaults: { model: 'claude-sonnet-4-6' },
|
|
59
|
-
modelCatalog: {
|
|
60
|
-
status: 'fallback',
|
|
61
|
-
lastVerifiedAt: '2026-05-04',
|
|
62
|
-
sourceUrl: 'https://platform.claude.com/docs/en/api/models/list',
|
|
63
|
-
entries: [
|
|
64
|
-
{
|
|
65
|
-
id: 'claude-sonnet-4-6',
|
|
66
|
-
displayName: 'Claude Sonnet 4.6',
|
|
67
|
-
contextWindow: 1_000_000,
|
|
68
|
-
lifecycle: 'active',
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
createProvider: () => {
|
|
73
|
-
throw new Error('not used');
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
type: 'qwen',
|
|
78
|
-
defaults: { model: 'qwen-plus' },
|
|
79
|
-
modelCatalog: {
|
|
80
|
-
status: 'fallback',
|
|
81
|
-
lastVerifiedAt: '2026-05-04',
|
|
82
|
-
sourceUrl:
|
|
83
|
-
'https://www.alibabacloud.com/help/en/model-studio/compatibility-of-openai-with-dashscope',
|
|
84
|
-
entries: [
|
|
85
|
-
{
|
|
86
|
-
id: 'qwen-plus',
|
|
87
|
-
displayName: 'Qwen Plus',
|
|
88
|
-
lifecycle: 'active',
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
id: 'qwen-max',
|
|
92
|
-
displayName: 'Qwen Max',
|
|
93
|
-
lifecycle: 'active',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
|
-
createProvider: () => {
|
|
98
|
-
throw new Error('not used');
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
type: 'openai',
|
|
103
|
-
modelCatalog: {
|
|
104
|
-
status: 'unavailable',
|
|
105
|
-
sourceUrl: 'https://platform.openai.com/docs/api-reference/models/list',
|
|
106
|
-
message: 'OpenAI models should be discovered live.',
|
|
107
|
-
},
|
|
108
|
-
createProvider: () => {
|
|
109
|
-
throw new Error('not used');
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
const refreshableProviderDefinitions: readonly TTestProviderDefinition[] = [
|
|
115
|
-
{
|
|
116
|
-
type: 'openai',
|
|
117
|
-
modelCatalog: {
|
|
118
|
-
status: 'unavailable',
|
|
119
|
-
sourceUrl: 'https://platform.openai.com/docs/api-reference/models/list',
|
|
120
|
-
message: 'OpenAI models should be discovered live.',
|
|
121
|
-
},
|
|
122
|
-
refreshModelCatalog: async () => ({
|
|
123
|
-
status: 'live',
|
|
124
|
-
sourceUrl: 'https://platform.openai.com/docs/api-reference/models/list',
|
|
125
|
-
lastVerifiedAt: '2026-05-05T00:00:00.000Z',
|
|
126
|
-
entries: [{ id: 'gpt-5.1', displayName: 'gpt-5.1', lifecycle: 'active' }],
|
|
127
|
-
}),
|
|
128
|
-
createProvider: () => {
|
|
129
|
-
throw new Error('not used');
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
];
|
|
133
|
-
|
|
134
|
-
function createModelModuleForSettings(settings: TProviderSettingsDocument) {
|
|
135
|
-
return createModelCommandModule({
|
|
136
|
-
providerDefinitions,
|
|
137
|
-
settings: {
|
|
138
|
-
readMergedSettings: () => settings,
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
describe('createModelCommandModule', () => {
|
|
144
|
-
it('provides model metadata and executable command from one module owner', () => {
|
|
145
|
-
const module = createModelCommandModule();
|
|
146
|
-
const command = module.systemCommands?.[0];
|
|
147
|
-
const entry = module.commandSources?.[0]?.getCommands()[0];
|
|
148
|
-
|
|
149
|
-
expect(module.name).toBe('agent-command-model');
|
|
150
|
-
expect(entry).toEqual(
|
|
151
|
-
expect.objectContaining({
|
|
152
|
-
name: 'model',
|
|
153
|
-
description: 'Change AI model',
|
|
154
|
-
argumentHint: '<model-id>',
|
|
155
|
-
source: 'model',
|
|
156
|
-
}),
|
|
157
|
-
);
|
|
158
|
-
expect(entry?.subcommands?.map((subcommand) => subcommand.name)).toContain('claude-sonnet-4-6');
|
|
159
|
-
expect(command).toEqual(
|
|
160
|
-
expect.objectContaining({
|
|
161
|
-
name: 'model',
|
|
162
|
-
description: 'Change AI model',
|
|
163
|
-
argumentHint: '<model-id>',
|
|
164
|
-
lifecycle: 'inline',
|
|
165
|
-
}),
|
|
166
|
-
);
|
|
167
|
-
expect(command?.subcommands).toEqual(entry?.subcommands);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('deduplicates date-suffixed model variants in descriptor subcommands', () => {
|
|
171
|
-
const entry = createModelCommandModule().commandSources?.[0]?.getCommands()[0];
|
|
172
|
-
const names = entry?.subcommands?.map((subcommand) => subcommand.name) ?? [];
|
|
173
|
-
|
|
174
|
-
expect(names).toContain('claude-opus-4-6');
|
|
175
|
-
expect(names).toContain('claude-sonnet-4-6');
|
|
176
|
-
expect(names).toContain('claude-haiku-4-5');
|
|
177
|
-
expect(names).not.toContain('claude-haiku-4-5-20251001');
|
|
178
|
-
expect(names).not.toContain('claude-sonnet-4-5-20250929');
|
|
179
|
-
expect(names).not.toContain('claude-opus-4-5-20251101');
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('formats subcommand descriptions with human-readable names and context windows', () => {
|
|
183
|
-
const entry = createModelCommandModule().commandSources?.[0]?.getCommands()[0];
|
|
184
|
-
const subcommands = entry?.subcommands ?? [];
|
|
185
|
-
|
|
186
|
-
expect(
|
|
187
|
-
subcommands.find((subcommand) => subcommand.name === 'claude-opus-4-6')?.description,
|
|
188
|
-
).toBe('Claude Opus 4.6 (1M)');
|
|
189
|
-
expect(
|
|
190
|
-
subcommands.find((subcommand) => subcommand.name === 'claude-haiku-4-5')?.description,
|
|
191
|
-
).toBe('Claude Haiku 4.5 (200K)');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('lists models for the effective active provider instead of Claude defaults', () => {
|
|
195
|
-
const entry = createModelModuleForSettings({
|
|
196
|
-
currentProvider: 'qwen',
|
|
197
|
-
providers: {
|
|
198
|
-
qwen: { type: 'qwen', model: 'qwen-plus' },
|
|
199
|
-
anthropic: { type: 'anthropic', model: 'claude-sonnet-4-6' },
|
|
200
|
-
},
|
|
201
|
-
}).commandSources?.[0]?.getCommands()[0];
|
|
202
|
-
|
|
203
|
-
const names = entry?.subcommands?.map((subcommand) => subcommand.name) ?? [];
|
|
204
|
-
expect(names).toEqual(['qwen-plus', 'qwen-max']);
|
|
205
|
-
expect(names).not.toContain('claude-sonnet-4-6');
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('keeps manual model input available when the active provider has no catalog', async () => {
|
|
209
|
-
const executor = new SystemCommandExecutor([
|
|
210
|
-
...(createModelModuleForSettings({
|
|
211
|
-
currentProvider: 'openai',
|
|
212
|
-
providers: {
|
|
213
|
-
openai: { type: 'openai', model: 'gpt-5.1' },
|
|
214
|
-
},
|
|
215
|
-
}).systemCommands ?? []),
|
|
216
|
-
]);
|
|
217
|
-
|
|
218
|
-
const usage = await executor.execute('model', commandHostContext, '');
|
|
219
|
-
const manual = await executor.execute('model', commandHostContext, 'gpt-5.1');
|
|
220
|
-
|
|
221
|
-
expect(usage?.success).toBe(false);
|
|
222
|
-
expect(usage?.message).toContain('No model catalog available for provider openai');
|
|
223
|
-
expect(manual?.success).toBe(true);
|
|
224
|
-
expect(manual?.effects).toEqual([{ type: 'model-change-requested', modelId: 'gpt-5.1' }]);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('surfaces refreshed catalog freshness when usage is requested', async () => {
|
|
228
|
-
const executor = new SystemCommandExecutor([
|
|
229
|
-
...(createModelCommandModule({
|
|
230
|
-
providerDefinitions: refreshableProviderDefinitions,
|
|
231
|
-
settings: {
|
|
232
|
-
readMergedSettings: () => ({
|
|
233
|
-
currentProvider: 'openai',
|
|
234
|
-
providers: {
|
|
235
|
-
openai: { type: 'openai', model: 'gpt-5.1', apiKey: 'sk-test' },
|
|
236
|
-
},
|
|
237
|
-
}),
|
|
238
|
-
},
|
|
239
|
-
}).systemCommands ?? []),
|
|
240
|
-
]);
|
|
241
|
-
|
|
242
|
-
const usage = await executor.execute('model', commandHostContext, '');
|
|
243
|
-
|
|
244
|
-
expect(usage?.success).toBe(false);
|
|
245
|
-
expect(usage?.message).toContain('Catalog: live; 1 model(s)');
|
|
246
|
-
expect(usage?.message).toContain('verified 2026-05-05T00:00:00.000Z');
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('requests model changes through a typed command effect', async () => {
|
|
250
|
-
const executor = new SystemCommandExecutor([
|
|
251
|
-
...(createModelCommandModule().systemCommands ?? []),
|
|
252
|
-
]);
|
|
253
|
-
|
|
254
|
-
const result = await executor.execute('model', commandHostContext, 'claude-sonnet-4-6');
|
|
255
|
-
|
|
256
|
-
expect(result?.success).toBe(true);
|
|
257
|
-
expect(result?.data?.modelId).toBe('claude-sonnet-4-6');
|
|
258
|
-
expect(result?.effects).toEqual([
|
|
259
|
-
{ type: 'model-change-requested', modelId: 'claude-sonnet-4-6' },
|
|
260
|
-
]);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('shows usage when no model id is provided', async () => {
|
|
264
|
-
const executor = new SystemCommandExecutor([
|
|
265
|
-
...(createModelCommandModule().systemCommands ?? []),
|
|
266
|
-
]);
|
|
267
|
-
|
|
268
|
-
const result = await executor.execute('model', commandHostContext, '');
|
|
269
|
-
|
|
270
|
-
expect(result?.success).toBe(false);
|
|
271
|
-
expect(result?.message).toBe('Usage: model <model-id>');
|
|
272
|
-
});
|
|
273
|
-
});
|
package/src/model/index.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildModelCommandSubcommands,
|
|
3
|
-
MODEL_COMMAND_ARGUMENT_HINT,
|
|
4
|
-
MODEL_COMMAND_DESCRIPTION,
|
|
5
|
-
} from '@robota-sdk/agent-framework';
|
|
6
|
-
|
|
7
|
-
import { executeModelCommand } from './model-command.js';
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
ICommand,
|
|
11
|
-
ICommandModule,
|
|
12
|
-
ICommandSource,
|
|
13
|
-
IModelCommandModuleOptions,
|
|
14
|
-
ISystemCommand,
|
|
15
|
-
} from '@robota-sdk/agent-framework';
|
|
16
|
-
|
|
17
|
-
export function createModelCommandEntry(options?: IModelCommandModuleOptions): ICommand {
|
|
18
|
-
return {
|
|
19
|
-
name: 'model',
|
|
20
|
-
displayName: 'Change Model',
|
|
21
|
-
description: MODEL_COMMAND_DESCRIPTION,
|
|
22
|
-
source: 'model',
|
|
23
|
-
argumentHint: MODEL_COMMAND_ARGUMENT_HINT,
|
|
24
|
-
subcommands: buildModelSubcommands(options),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function createModelSystemCommand(options?: IModelCommandModuleOptions): ISystemCommand {
|
|
29
|
-
const entry = createModelCommandEntry(options);
|
|
30
|
-
return {
|
|
31
|
-
name: entry.name,
|
|
32
|
-
displayName: entry.displayName,
|
|
33
|
-
description: entry.description,
|
|
34
|
-
requiresPermission: true,
|
|
35
|
-
userInvocable: true,
|
|
36
|
-
argumentHint: entry.argumentHint,
|
|
37
|
-
subcommands: entry.subcommands,
|
|
38
|
-
lifecycle: 'inline',
|
|
39
|
-
execute: (context, args) => executeModelCommand(context, args, options),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class ModelCommandSource implements ICommandSource {
|
|
44
|
-
readonly name = 'model';
|
|
45
|
-
|
|
46
|
-
constructor(private readonly options?: IModelCommandModuleOptions) {}
|
|
47
|
-
|
|
48
|
-
getCommands(): ICommand[] {
|
|
49
|
-
return [createModelCommandEntry(this.options)];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function createModelCommandModule(options?: IModelCommandModuleOptions): ICommandModule {
|
|
54
|
-
return {
|
|
55
|
-
name: 'agent-command-model',
|
|
56
|
-
commandSources: [new ModelCommandSource(options)],
|
|
57
|
-
systemCommands: [createModelSystemCommand(options)],
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function buildModelSubcommands(options?: IModelCommandModuleOptions): ICommand[] {
|
|
62
|
-
if (options === undefined) {
|
|
63
|
-
return buildModelCommandSubcommands('model');
|
|
64
|
-
}
|
|
65
|
-
return buildModelCommandSubcommands({
|
|
66
|
-
source: 'model',
|
|
67
|
-
providerDefinitions: options.providerDefinitions,
|
|
68
|
-
settings: options.settings.readMergedSettings(),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { formatModelCommandUsageMessageAsync } from '@robota-sdk/agent-framework';
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
ICommandHostContext,
|
|
5
|
-
ICommandResult,
|
|
6
|
-
IModelCommandModuleOptions,
|
|
7
|
-
} from '@robota-sdk/agent-framework';
|
|
8
|
-
|
|
9
|
-
function parseModelId(args: string): string | undefined {
|
|
10
|
-
const modelId = args.trim().split(/\s+/)[0];
|
|
11
|
-
return modelId !== undefined && modelId.length > 0 ? modelId : undefined;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function executeModelCommand(
|
|
15
|
-
_context: ICommandHostContext,
|
|
16
|
-
args: string,
|
|
17
|
-
options?: IModelCommandModuleOptions,
|
|
18
|
-
): Promise<ICommandResult> {
|
|
19
|
-
const modelId = parseModelId(args);
|
|
20
|
-
if (modelId === undefined) {
|
|
21
|
-
return {
|
|
22
|
-
message: await formatModelCommandUsageMessageAsync({
|
|
23
|
-
...(options?.settings !== undefined
|
|
24
|
-
? { settings: options.settings.readMergedSettings() }
|
|
25
|
-
: {}),
|
|
26
|
-
...(options?.providerDefinitions !== undefined
|
|
27
|
-
? { providerDefinitions: options.providerDefinitions }
|
|
28
|
-
: {}),
|
|
29
|
-
refresh: true,
|
|
30
|
-
}),
|
|
31
|
-
success: false,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
message: `Model change requested: ${modelId}`,
|
|
37
|
-
success: true,
|
|
38
|
-
data: { modelId },
|
|
39
|
-
effects: [{ type: 'model-change-requested', modelId }],
|
|
40
|
-
};
|
|
41
|
-
}
|