@robota-sdk/agent-command 3.0.0-beta.74 → 3.0.0-beta.76

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-command",
3
- "version": "3.0.0-beta.74",
3
+ "version": "3.0.0-beta.76",
4
4
  "description": "Consolidated command module implementations for Robota SDK CLI",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
@@ -24,9 +24,10 @@
24
24
  "src"
25
25
  ],
26
26
  "dependencies": {
27
- "@robota-sdk/agent-core": "3.0.0-beta.74",
28
- "@robota-sdk/agent-framework": "3.0.0-beta.74",
29
- "@robota-sdk/agent-interface-transport": "3.0.0-beta.74"
27
+ "@robota-sdk/agent-core": "3.0.0-beta.76",
28
+ "@robota-sdk/agent-framework": "3.0.0-beta.76",
29
+ "@robota-sdk/agent-interface-transport": "3.0.0-beta.76",
30
+ "@robota-sdk/agent-preset": "3.0.0-beta.76"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/node": "^22.19.21",
@@ -1,3 +1,4 @@
1
+ import { CONTEXT_ESTIMATE_CHARS_PER_TOKEN } from '@robota-sdk/agent-core';
1
2
  import {
2
3
  addCommandContextReference,
3
4
  clearCommandContextReferences,
@@ -282,12 +283,11 @@ function formatContextReferenceSummary(references: readonly IContextReferenceIte
282
283
  return `References: ${active} active, ${observed} observed`;
283
284
  }
284
285
 
285
- // 1 token ≈ 4 chars — same approximation used across the codebase (limits-helpers.ts)
286
- const CHARS_PER_TOKEN = 4;
286
+ // 1 token ≈ 4 chars — sourced from the agent-core estimation SSOT.
287
287
  const TOOL_ARG_MAX_LEN = 60;
288
288
 
289
289
  function estimateTokens(charLength: number): number {
290
- return Math.ceil(charLength / CHARS_PER_TOKEN);
290
+ return Math.ceil(charLength / CONTEXT_ESTIMATE_CHARS_PER_TOKEN);
291
291
  }
292
292
 
293
293
  function formatContextReferenceLine(reference: IContextReferenceItem): string {
@@ -349,7 +349,7 @@ function computeMessageTokensByRole(rawMessages: TUniversalMessage[]): IMessageT
349
349
  let toolCallCount = 0;
350
350
 
351
351
  for (const msg of rawMessages) {
352
- const t = Math.ceil(JSON.stringify(msg).length / CHARS_PER_TOKEN);
352
+ const t = Math.ceil(JSON.stringify(msg).length / CONTEXT_ESTIMATE_CHARS_PER_TOKEN);
353
353
  if (msg.role === 'system') {
354
354
  systemTokens += t;
355
355
  } else if (msg.role === 'user') {
@@ -0,0 +1,97 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { IProviderDefinition } from '@robota-sdk/agent-core';
4
+ import type {
5
+ IProviderCommandSettingsAdapter,
6
+ TProviderSettingsDocument,
7
+ } from '@robota-sdk/agent-framework';
8
+
9
+ import { createDefaultCommandModules } from '../default-command-modules.js';
10
+
11
+ const providerDefinitions: readonly IProviderDefinition[] = [
12
+ {
13
+ type: 'anthropic',
14
+ defaults: { model: 'claude-sonnet-4-6', apiKey: '$ENV:ANTHROPIC_API_KEY' },
15
+ setupSteps: [{ key: 'apiKey', title: 'anthropic API key', masked: true }],
16
+ requiresApiKey: true,
17
+ createProvider: () => {
18
+ throw new Error('not used');
19
+ },
20
+ },
21
+ ];
22
+
23
+ const providerSettingsAdapter: IProviderCommandSettingsAdapter = {
24
+ readMergedSettings: () => ({}) as TProviderSettingsDocument,
25
+ readTargetSettings: () => ({}) as TProviderSettingsDocument,
26
+ writeTargetSettings: () => undefined,
27
+ };
28
+
29
+ const baseOptions = { cwd: '/tmp', providerDefinitions, providerSettingsAdapter } as const;
30
+
31
+ function moduleNames(opts: Parameters<typeof createDefaultCommandModules>[0]): string[] {
32
+ return createDefaultCommandModules(opts).map((module) => module.name);
33
+ }
34
+
35
+ describe('createDefaultCommandModules — PRESET-004 module-selection delta', () => {
36
+ // Module `name`s are the stable ICommandModule ids (the `agent-command-*` form).
37
+ const HELP = 'agent-command-help';
38
+ const AGENT = 'agent-command-agent';
39
+ const BACKGROUND = 'agent-command-background';
40
+
41
+ it('TC-04: neither enabled nor disabled given → full default set unchanged (no-regression)', () => {
42
+ const names = moduleNames(baseOptions);
43
+ // No-regression: the default set length is the documented 21 modules.
44
+ expect(names).toHaveLength(21);
45
+ expect(names).toEqual([
46
+ 'agent-command-skills',
47
+ 'agent-command-help',
48
+ 'agent-command-agent',
49
+ 'agent-command-permissions',
50
+ 'agent-command-mode',
51
+ 'agent-command-preset',
52
+ 'agent-command-language',
53
+ 'agent-command-background',
54
+ 'agent-command-memory',
55
+ 'agent-command-user-local',
56
+ 'agent-command-compact',
57
+ 'agent-command-context',
58
+ 'agent-command-exit',
59
+ 'agent-command-session',
60
+ 'agent-command-reset',
61
+ 'agent-command-rewind',
62
+ 'agent-command-schedule',
63
+ 'agent-command-statusline',
64
+ 'agent-command-plugin',
65
+ 'agent-command-settings',
66
+ 'agent-command-provider',
67
+ ]);
68
+ });
69
+
70
+ it('TC-01: enabledCommandModules whitelist keeps exactly the listed module names', () => {
71
+ const names = moduleNames({ ...baseOptions, enabledCommandModules: [HELP, AGENT] });
72
+ expect(new Set(names)).toEqual(new Set([HELP, AGENT]));
73
+ expect(names).toHaveLength(2);
74
+ });
75
+
76
+ it('TC-02: disabledCommandModules blacklist removes the named module', () => {
77
+ const full = moduleNames(baseOptions);
78
+ const names = moduleNames({ ...baseOptions, disabledCommandModules: [BACKGROUND] });
79
+ expect(names).not.toContain(BACKGROUND);
80
+ expect(names).toHaveLength(full.length - 1);
81
+ });
82
+
83
+ it('TC-03: a name in both enabled and disabled is excluded (deny > allow)', () => {
84
+ const names = moduleNames({
85
+ ...baseOptions,
86
+ enabledCommandModules: [HELP, AGENT],
87
+ disabledCommandModules: [AGENT],
88
+ });
89
+ expect(new Set(names)).toEqual(new Set([HELP]));
90
+ expect(names).not.toContain(AGENT);
91
+ });
92
+
93
+ it('whitelist with an unknown name simply yields no module for it', () => {
94
+ const names = moduleNames({ ...baseOptions, enabledCommandModules: [HELP, 'does-not-exist'] });
95
+ expect(names).toEqual([HELP]);
96
+ });
97
+ });
@@ -9,6 +9,7 @@ import { createMemoryCommandModule } from '../memory/index.js';
9
9
  import { createModeCommandModule } from '../mode/index.js';
10
10
  import { createPermissionsCommandModule } from '../permissions/index.js';
11
11
  import { createPluginCommandModule } from '../plugin/index.js';
12
+ import { createPresetCommandModule } from '../preset/index.js';
12
13
  import { createProviderCommandModule } from '../provider/index.js';
13
14
  import { createResetCommandModule } from '../reset/index.js';
14
15
  import { createRewindCommandModule } from '../rewind/index.js';
@@ -26,19 +27,56 @@ export interface IDefaultCommandModulesOptions {
26
27
  cwd: string;
27
28
  providerDefinitions: readonly IProviderDefinition[];
28
29
  providerSettingsAdapter: IProviderCommandSettingsAdapter;
30
+ /**
31
+ * Whitelist of module `name`s to keep. When provided, only modules whose `name`
32
+ * appears here survive. Omitted → all modules kept (no-regression).
33
+ */
34
+ enabledCommandModules?: readonly string[];
35
+ /**
36
+ * Blacklist of module `name`s to remove. Applied after the whitelist, so a name
37
+ * present in both is removed (deny > allow). Omitted → no modules removed.
38
+ */
39
+ disabledCommandModules?: readonly string[];
40
+ }
41
+
42
+ /**
43
+ * Apply the preset module-selection delta to the default module set.
44
+ *
45
+ * Rules: if `enabled` is provided, keep only modules whose `name` is in it; then
46
+ * remove any module whose `name` is in `disabled` (deny > allow). Neither given →
47
+ * the full default set is returned unchanged (no-regression).
48
+ */
49
+ function applyModuleSelection(
50
+ modules: readonly ICommandModule[],
51
+ enabled: readonly string[] | undefined,
52
+ disabled: readonly string[] | undefined,
53
+ ): readonly ICommandModule[] {
54
+ let selected = modules;
55
+ if (enabled !== undefined) {
56
+ const allow = new Set(enabled);
57
+ selected = selected.filter((module) => allow.has(module.name));
58
+ }
59
+ if (disabled !== undefined) {
60
+ const deny = new Set(disabled);
61
+ selected = selected.filter((module) => !deny.has(module.name));
62
+ }
63
+ return selected;
29
64
  }
30
65
 
31
66
  export function createDefaultCommandModules({
32
67
  cwd,
33
68
  providerDefinitions,
34
69
  providerSettingsAdapter,
70
+ enabledCommandModules,
71
+ disabledCommandModules,
35
72
  }: IDefaultCommandModulesOptions): readonly ICommandModule[] {
36
- return [
73
+ const modules: readonly ICommandModule[] = [
37
74
  createSkillsCommandModule({ cwd }),
38
75
  createHelpCommandModule(),
39
76
  createAgentCommandModule(),
40
77
  createPermissionsCommandModule(),
41
78
  createModeCommandModule(),
79
+ createPresetCommandModule(),
42
80
  createLanguageCommandModule(),
43
81
  createBackgroundCommandModule(),
44
82
  createMemoryCommandModule(),
@@ -58,4 +96,5 @@ export function createDefaultCommandModules({
58
96
  settings: providerSettingsAdapter,
59
97
  }),
60
98
  ];
99
+ return applyModuleSelection(modules, enabledCommandModules, disabledCommandModules);
61
100
  }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from './memory/index.js';
10
10
  export * from './mode/index.js';
11
11
  export * from './permissions/index.js';
12
12
  export * from './plugin/index.js';
13
+ export * from './preset/index.js';
13
14
  export * from './provider/index.js';
14
15
  export * from './reset/index.js';
15
16
  export * from './rewind/index.js';
@@ -0,0 +1,138 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { listPresets } from '@robota-sdk/agent-preset';
3
+ import type { ICommandHostContext, ICommandSessionRuntime } from '@robota-sdk/agent-framework';
4
+ import { createPresetCommandModule, executePresetCommand } from '../index.js';
5
+
6
+ function createSessionRuntime(overrides?: Partial<ICommandSessionRuntime>): ICommandSessionRuntime {
7
+ return {
8
+ clearHistory: () => undefined,
9
+ compact: async () => undefined,
10
+ getContextState: () => ({
11
+ maxTokens: 100,
12
+ usedTokens: 10,
13
+ usedPercentage: 10,
14
+ remainingPercentage: 90,
15
+ }),
16
+ getPermissionMode: () => 'default',
17
+ setPermissionMode: () => undefined,
18
+ getSessionId: () => 'session_1',
19
+ getMessageCount: () => 0,
20
+ getSessionAllowedTools: () => [],
21
+ getAutoCompactThreshold: () => false,
22
+ getFullHistory: () => [],
23
+ getHistory: () => [],
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ function createCommandHostContext(
29
+ runtime: ICommandSessionRuntime,
30
+ overrides?: Partial<ICommandHostContext>,
31
+ ): ICommandHostContext {
32
+ return {
33
+ getSession: () => runtime,
34
+ getContextState: () => ({
35
+ maxTokens: 100,
36
+ usedTokens: 10,
37
+ usedPercentage: 10,
38
+ remainingPercentage: 90,
39
+ }),
40
+ getAutoCompactThreshold: () => 0.8,
41
+ compactContext: async () => undefined,
42
+ getCwd: () => '/workspace',
43
+ listEditCheckpoints: () => [],
44
+ restoreEditCheckpoint: async () => ({
45
+ target: {
46
+ id: 'checkpoint_1',
47
+ sessionId: 'session_1',
48
+ sequence: 1,
49
+ prompt: 'edit',
50
+ createdAt: '2026-05-03T00:00:00.000Z',
51
+ fileCount: 0,
52
+ },
53
+ restoredCheckpointCount: 1,
54
+ restoredFileCount: 0,
55
+ removedCheckpointCount: 0,
56
+ }),
57
+ rollbackEditCheckpoint: async () => ({
58
+ target: {
59
+ id: 'checkpoint_1',
60
+ sessionId: 'session_1',
61
+ sequence: 1,
62
+ prompt: 'edit',
63
+ createdAt: '2026-05-03T00:00:00.000Z',
64
+ fileCount: 0,
65
+ },
66
+ restoredCheckpointCount: 1,
67
+ restoredFileCount: 0,
68
+ removedCheckpointCount: 0,
69
+ }),
70
+ getUsedMemoryReferences: () => [],
71
+ recordMemoryEvent: () => undefined,
72
+ listBackgroundTasks: () => [],
73
+ readBackgroundTaskLog: async (taskId) => ({ taskId, lines: [] }),
74
+ cancelBackgroundTask: async () => undefined,
75
+ closeBackgroundTask: async () => undefined,
76
+ ...overrides,
77
+ };
78
+ }
79
+
80
+ describe('preset command module', () => {
81
+ it('exposes a single preset system command (structure)', async () => {
82
+ const module = createPresetCommandModule();
83
+ expect(module.systemCommands?.map((command) => command.name)).toContain('preset');
84
+ });
85
+
86
+ it('TC-01: lists every preset and marks the active one', async () => {
87
+ const runtime = createSessionRuntime({ getActivePresetId: () => 'autonomous-builder' });
88
+ const context = createCommandHostContext(runtime);
89
+
90
+ const result = await executePresetCommand(context, '');
91
+
92
+ expect(result.success).toBe(true);
93
+ for (const preset of listPresets()) {
94
+ expect(result.message).toContain(preset.id);
95
+ }
96
+ // The active preset is marked with the `* ` prefix.
97
+ expect(result.message).toContain('* autonomous-builder');
98
+ expect(result.message).not.toContain('* default');
99
+ expect(result.data?.active).toBe('autonomous-builder');
100
+ });
101
+
102
+ it('TC-02: switches to a valid preset and drives the live re-apply seams', async () => {
103
+ const setActivePresetId = vi.fn();
104
+ const setPermissionMode = vi.fn();
105
+ const applyModelOptions = vi.fn();
106
+ const runtime = createSessionRuntime({
107
+ setActivePresetId,
108
+ setPermissionMode,
109
+ applyModelOptions,
110
+ });
111
+ const context = createCommandHostContext(runtime);
112
+
113
+ const result = await executePresetCommand(context, 'careful-reviewer');
114
+
115
+ expect(result.success).toBe(true);
116
+ expect(result.message).toBe('Switched to preset: careful-reviewer');
117
+ expect(result.data?.preset).toBe('careful-reviewer');
118
+ expect(setActivePresetId).toHaveBeenCalledWith('careful-reviewer');
119
+ // careful-reviewer resolves autonomy ask-first → default permission posture (PRESET-012)
120
+ // and effort high → applyModelOptions (PRESET-013).
121
+ expect(setPermissionMode).toHaveBeenCalledWith('default');
122
+ expect(applyModelOptions).toHaveBeenCalledWith(expect.objectContaining({ effort: 'high' }));
123
+ });
124
+
125
+ it('TC-04: rejects an unknown preset id without switching', async () => {
126
+ const setActivePresetId = vi.fn();
127
+ const runtime = createSessionRuntime({ setActivePresetId });
128
+ const context = createCommandHostContext(runtime);
129
+
130
+ const result = await executePresetCommand(context, '__nope__');
131
+
132
+ expect(result.success).toBe(false);
133
+ for (const preset of listPresets()) {
134
+ expect(result.message).toContain(preset.id);
135
+ }
136
+ expect(setActivePresetId).not.toHaveBeenCalled();
137
+ });
138
+ });
@@ -0,0 +1,6 @@
1
+ export {
2
+ createPresetCommandEntry,
3
+ createPresetCommandModule,
4
+ PresetCommandSource,
5
+ } from './preset-command-module.js';
6
+ export { executePresetCommand } from './preset-command.js';
@@ -0,0 +1,79 @@
1
+ import { listPresets } from '@robota-sdk/agent-preset';
2
+
3
+ import { executePresetCommand } from './preset-command.js';
4
+
5
+ import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
6
+ import type {
7
+ ICommand,
8
+ ICommandInteractionHint,
9
+ ICommandSource,
10
+ } from '@robota-sdk/agent-interface-transport';
11
+
12
+ const PRESET_COMMAND_DESCRIPTION = 'List presets or switch the active preset';
13
+ const PRESET_ARGUMENT_HINT = 'list | <preset-id>';
14
+
15
+ /** Build one subcommand per registered preset id (mirrors the permission-mode subcommands). */
16
+ function buildPresetSubcommands(source = 'preset'): ICommand[] {
17
+ return listPresets().map((preset) => ({
18
+ name: preset.id,
19
+ description: preset.description,
20
+ source,
21
+ }));
22
+ }
23
+
24
+ export function createPresetCommandEntry(): ICommand {
25
+ return {
26
+ name: 'preset',
27
+ displayName: 'Agent Preset',
28
+ description: PRESET_COMMAND_DESCRIPTION,
29
+ source: 'preset',
30
+ argumentHint: PRESET_ARGUMENT_HINT,
31
+ subcommands: buildPresetSubcommands('preset'),
32
+ modelInvocable: false,
33
+ };
34
+ }
35
+
36
+ function createPresetSystemCommand(): ISystemCommand {
37
+ const entry = createPresetCommandEntry();
38
+ return {
39
+ name: entry.name,
40
+ displayName: entry.displayName,
41
+ description: entry.description,
42
+ requiresPermission: false,
43
+ userInvocable: true,
44
+ modelInvocable: false,
45
+ argumentHint: entry.argumentHint,
46
+ subcommands: entry.subcommands,
47
+ lifecycle: 'inline',
48
+ execute: executePresetCommand,
49
+ };
50
+ }
51
+
52
+ export class PresetCommandSource implements ICommandSource {
53
+ readonly name = 'preset';
54
+
55
+ getCommands(): ICommand[] {
56
+ return [createPresetCommandEntry()];
57
+ }
58
+ }
59
+
60
+ const PRESET_INTERACTION_HINTS: Record<string, ICommandInteractionHint> = {
61
+ preset: {
62
+ type: 'pick',
63
+ getItems: () =>
64
+ buildPresetSubcommands().map((sub) => ({
65
+ label: sub.name,
66
+ value: sub.name,
67
+ description: sub.description,
68
+ })),
69
+ },
70
+ };
71
+
72
+ export function createPresetCommandModule(): ICommandModule {
73
+ return {
74
+ name: 'agent-command-preset',
75
+ commandSources: [new PresetCommandSource()],
76
+ systemCommands: [createPresetSystemCommand()],
77
+ interactionHints: PRESET_INTERACTION_HINTS,
78
+ };
79
+ }
@@ -0,0 +1,61 @@
1
+ import { applyPresetToSession } from '@robota-sdk/agent-framework';
2
+ import { getPreset, listPresets, resolvePreset } from '@robota-sdk/agent-preset';
3
+
4
+ import type { ICommandHostContext } from '@robota-sdk/agent-framework';
5
+ import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
6
+
7
+ /** Default active preset id reported when the runtime has no recorded active preset. */
8
+ const DEFAULT_ACTIVE_PRESET_ID = 'default';
9
+
10
+ /** Read the active preset id from the session, defaulting when the optional seam is absent. */
11
+ function readActivePresetId(context: ICommandHostContext): string {
12
+ return context.getSession().getActivePresetId?.() ?? DEFAULT_ACTIVE_PRESET_ID;
13
+ }
14
+
15
+ /** Build the `/preset` listing: one line per preset, marking the active one with a `*` prefix. */
16
+ function formatPresetList(active: string): string {
17
+ const lines = listPresets().map((preset) => {
18
+ const marker = preset.id === active ? '* ' : ' ';
19
+ return `${marker}${preset.id} — ${preset.title}: ${preset.description}`;
20
+ });
21
+ return ['Available presets:', ...lines].join('\n');
22
+ }
23
+
24
+ /** Build the rejection message for an unknown preset id, listing the valid ids. */
25
+ function formatUnknownPresetMessage(id: string): string {
26
+ const ids = listPresets()
27
+ .map((preset) => preset.id)
28
+ .join(', ');
29
+ return `Unknown preset: ${id}. Available: ${ids}`;
30
+ }
31
+
32
+ export async function executePresetCommand(
33
+ context: ICommandHostContext,
34
+ args: string,
35
+ ): Promise<ICommandResult> {
36
+ const id = args.trim().split(/\s+/)[0];
37
+
38
+ if (id === undefined || id.length === 0 || id === 'list') {
39
+ const active = readActivePresetId(context);
40
+ return {
41
+ message: formatPresetList(active),
42
+ success: true,
43
+ data: { presets: listPresets(), active },
44
+ };
45
+ }
46
+
47
+ if (getPreset(id) === undefined) {
48
+ return {
49
+ message: formatUnknownPresetMessage(id),
50
+ success: false,
51
+ };
52
+ }
53
+
54
+ const resolved = resolvePreset(id);
55
+ await applyPresetToSession(context, id, resolved);
56
+ return {
57
+ message: `Switched to preset: ${id}`,
58
+ success: true,
59
+ data: { preset: id },
60
+ };
61
+ }
@@ -1,69 +1,16 @@
1
- /** USD prices per 1,000,000 tokens (as of May 2026 — update when providers change rates). */
2
- interface IModelPrice {
3
- inputPerMillion: number;
4
- outputPerMillion: number;
5
- }
6
-
7
- const MODEL_PRICES: Record<string, IModelPrice> = {
8
- // Anthropic Claude 4
9
- 'claude-opus-4-7': { inputPerMillion: 15, outputPerMillion: 75 },
10
- 'claude-opus-4-5': { inputPerMillion: 15, outputPerMillion: 75 },
11
- 'claude-sonnet-4-6': { inputPerMillion: 3, outputPerMillion: 15 },
12
- 'claude-sonnet-4-5': { inputPerMillion: 3, outputPerMillion: 15 },
13
- 'claude-haiku-4-5': { inputPerMillion: 0.8, outputPerMillion: 4 },
14
- // Anthropic Claude 3
15
- 'claude-3-5-sonnet-20241022': { inputPerMillion: 3, outputPerMillion: 15 },
16
- 'claude-3-5-haiku-20241022': { inputPerMillion: 0.8, outputPerMillion: 4 },
17
- 'claude-3-opus-20240229': { inputPerMillion: 15, outputPerMillion: 75 },
18
- // OpenAI
19
- 'gpt-4o': { inputPerMillion: 2.5, outputPerMillion: 10 },
20
- 'gpt-4o-mini': { inputPerMillion: 0.15, outputPerMillion: 0.6 },
21
- o1: { inputPerMillion: 15, outputPerMillion: 60 },
22
- 'o1-mini': { inputPerMillion: 3, outputPerMillion: 12 },
23
- o3: { inputPerMillion: 10, outputPerMillion: 40 },
24
- 'o3-mini': { inputPerMillion: 1.1, outputPerMillion: 4.4 },
25
- // DeepSeek
26
- 'deepseek-chat': { inputPerMillion: 0.14, outputPerMillion: 0.28 },
27
- 'deepseek-reasoner': { inputPerMillion: 0.55, outputPerMillion: 2.19 },
28
- // Google Gemini
29
- 'gemini-2.0-flash': { inputPerMillion: 0.1, outputPerMillion: 0.4 },
30
- 'gemini-2.0-flash-thinking': { inputPerMillion: 0.35, outputPerMillion: 3.5 },
31
- 'gemini-1.5-pro': { inputPerMillion: 1.25, outputPerMillion: 5 },
32
- 'gemini-1.5-flash': { inputPerMillion: 0.075, outputPerMillion: 0.3 },
33
- };
34
-
35
- const PATTERN_PRICES: Array<{ pattern: RegExp; price: IModelPrice }> = [
36
- { pattern: /claude-opus/i, price: { inputPerMillion: 15, outputPerMillion: 75 } },
37
- { pattern: /claude-sonnet/i, price: { inputPerMillion: 3, outputPerMillion: 15 } },
38
- { pattern: /claude-haiku/i, price: { inputPerMillion: 0.8, outputPerMillion: 4 } },
39
- { pattern: /gpt-4o-mini/i, price: { inputPerMillion: 0.15, outputPerMillion: 0.6 } },
40
- { pattern: /gpt-4/i, price: { inputPerMillion: 2.5, outputPerMillion: 10 } },
41
- { pattern: /deepseek/i, price: { inputPerMillion: 0.14, outputPerMillion: 0.28 } },
42
- { pattern: /gemini-2/i, price: { inputPerMillion: 0.1, outputPerMillion: 0.4 } },
43
- { pattern: /gemini-1/i, price: { inputPerMillion: 1.25, outputPerMillion: 5 } },
44
- ];
45
-
46
- function lookupPrice(modelId: string): IModelPrice | undefined {
47
- const exact = MODEL_PRICES[modelId];
48
- if (exact) return exact;
49
- for (const { pattern, price } of PATTERN_PRICES) {
50
- if (pattern.test(modelId)) return price;
51
- }
52
- return undefined;
53
- }
1
+ import { calculateModelCost } from '@robota-sdk/agent-core';
54
2
 
55
- /** Returns USD cost, or undefined if the model is not in the pricing table. */
3
+ /**
4
+ * Returns USD cost, or undefined if the model is not in the pricing table.
5
+ * Delegates to the agent-core pricing SSOT (`calculateModelCost`); this package owns only the
6
+ * command-facing display helpers below.
7
+ */
56
8
  export function calculateCost(
57
9
  modelId: string,
58
10
  inputTokens: number,
59
11
  outputTokens: number,
60
12
  ): number | undefined {
61
- const price = lookupPrice(modelId);
62
- if (!price) return undefined;
63
- return (
64
- (inputTokens / 1_000_000) * price.inputPerMillion +
65
- (outputTokens / 1_000_000) * price.outputPerMillion
66
- );
13
+ return calculateModelCost(modelId, inputTokens, outputTokens);
67
14
  }
68
15
 
69
16
  /** Format a USD amount for display (e.g. "$0.0043"). */