@aragon-agent/cli 0.6.11 → 0.6.13
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 +10 -1
- package/README.md +97 -20
- package/dist/agent/controller.d.ts +38 -3
- package/dist/agent/controller.js +151 -60
- package/dist/agent/model-profile-settings.d.ts +31 -0
- package/dist/agent/model-profile-settings.js +199 -0
- package/dist/agent/queued-messages.d.ts +20 -0
- package/dist/agent/queued-messages.js +46 -0
- package/dist/agent/reducer.d.ts +21 -2
- package/dist/agent/reducer.js +65 -37
- package/dist/cli.js +15 -1
- package/dist/commands/builtins.js +83 -42
- package/dist/commands/queue-command.d.ts +3 -0
- package/dist/commands/queue-command.js +5 -0
- package/dist/commands/registry.d.ts +3 -0
- package/dist/compaction/child.d.ts +4 -2
- package/dist/compaction/child.js +4 -1
- package/dist/compaction/compactor.d.ts +7 -2
- package/dist/compaction/compactor.js +13 -11
- package/dist/compaction/context-command.js +5 -0
- package/dist/compaction/wiring.d.ts +4 -3
- package/dist/compaction/wiring.js +6 -1
- package/dist/config/cli-commands.d.ts +2 -0
- package/dist/config/cli-commands.js +16 -4
- package/dist/config/load.d.ts +2 -1
- package/dist/config/load.js +15 -8
- package/dist/config/model-profile-resolution.d.ts +20 -0
- package/dist/config/model-profile-resolution.js +81 -0
- package/dist/config/model-profile-store.d.ts +67 -0
- package/dist/config/model-profile-store.js +153 -0
- package/dist/config/model-profiles.d.ts +44 -0
- package/dist/config/model-profiles.js +121 -0
- package/dist/config/schema.d.ts +9 -0
- package/dist/config/store.d.ts +11 -1
- package/dist/config/store.js +43 -27
- package/dist/fast/resolve.d.ts +2 -1
- package/dist/fast/resolve.js +6 -4
- package/dist/fast/wiring.d.ts +5 -3
- package/dist/fast/wiring.js +8 -4
- package/dist/input/enter-sequences.d.ts +11 -10
- package/dist/input/enter-sequences.js +29 -27
- package/dist/input/stdin-filter.js +93 -28
- package/dist/logging/install.d.ts +3 -0
- package/dist/logging/install.js +4 -2
- package/dist/logging/secret-registry.d.ts +2 -0
- package/dist/logging/secret-registry.js +18 -0
- package/dist/session/persist.d.ts +6 -7
- package/dist/session/persist.js +6 -9
- package/dist/session/store.d.ts +4 -1
- package/dist/session/validate-session.d.ts +3 -0
- package/dist/session/validate-session.js +207 -0
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/subagent.d.ts +3 -1
- package/dist/team/subagent.js +3 -1
- package/dist/ui/ActivityLine.d.ts +6 -70
- package/dist/ui/ActivityLine.js +12 -34
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.js +161 -226
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +18 -84
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/PromptInput.d.ts +6 -2
- package/dist/ui/PromptInput.js +91 -105
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +24 -0
- package/dist/ui/QueueStatusRow.js +51 -0
- package/dist/ui/StatusBar.d.ts +15 -149
- package/dist/ui/StatusBar.js +27 -112
- package/dist/ui/clipboard-task.d.ts +29 -0
- package/dist/ui/clipboard-task.js +66 -0
- package/dist/ui/clipboard.d.ts +29 -50
- package/dist/ui/clipboard.js +141 -75
- package/dist/ui/composer-input.d.ts +28 -0
- package/dist/ui/composer-input.js +46 -0
- package/dist/ui/composer-limits.d.ts +3 -4
- package/dist/ui/composer-limits.js +4 -9
- package/dist/ui/composer-rows.d.ts +1 -1
- package/dist/ui/composer-rows.js +24 -137
- package/dist/ui/editor-navigation.d.ts +45 -0
- package/dist/ui/editor-navigation.js +85 -0
- package/dist/ui/editor-reducer.d.ts +9 -0
- package/dist/ui/editor-reducer.js +31 -15
- package/dist/ui/enter-frames.d.ts +15 -31
- package/dist/ui/enter-frames.js +14 -33
- package/dist/ui/interaction-copy.d.ts +77 -0
- package/dist/ui/interaction-copy.js +40 -0
- package/dist/ui/interaction-hints.d.ts +18 -0
- package/dist/ui/interaction-hints.js +92 -0
- package/dist/ui/layout/budget.js +1 -1
- package/dist/ui/layout/queue-layout.d.ts +19 -0
- package/dist/ui/layout/queue-layout.js +26 -0
- package/dist/ui/layout/status-layout.d.ts +55 -0
- package/dist/ui/layout/status-layout.js +109 -0
- package/dist/ui/layout/todo-layout.d.ts +1 -0
- package/dist/ui/layout/todo-layout.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +11 -3
- package/dist/ui/overlays/ModelProfileEditor.d.ts +12 -0
- package/dist/ui/overlays/ModelProfileEditor.js +103 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +35 -0
- package/dist/ui/overlays/ModelProfilePicker.js +167 -0
- package/dist/ui/overlays/QueueOverlay.d.ts +22 -0
- package/dist/ui/overlays/QueueOverlay.js +79 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +9 -2
- package/dist/ui/overlays/SettingsScreen.js +347 -3
- package/dist/ui/overlays/model-profile-state.d.ts +74 -0
- package/dist/ui/overlays/model-profile-state.js +119 -0
- package/dist/ui/run-status-row.d.ts +7 -19
- package/dist/ui/run-status-row.js +2 -18
- package/dist/ui/selection/selection-controller.d.ts +2 -2
- package/dist/ui/selection/selection-controller.js +58 -20
- package/dist/ui/transcript-text.js +20 -12
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
* /resume /copy /terminal-setup /exit (/quit), plus the /team, /fast and
|
|
5
5
|
* /todo subsystems.
|
|
6
6
|
*/
|
|
7
|
+
import { modelSettingsResultMessage } from '../agent/model-profile-settings.js';
|
|
7
8
|
import { existsSync, statSync } from 'node:fs';
|
|
8
9
|
import { isAbsolute, resolve } from 'node:path';
|
|
9
10
|
import { resolveOutputTokens } from '@aragon-agent/core';
|
|
10
11
|
import { clampThinkingLevel, clampTheme, isAdapterProvider, isThemeName, parseMaxTokensInput, ADAPTER_PROVIDERS, DEFAULT_MAX_TOKENS, HARD_MAX_RETRIES, LEGACY_DARK_THEME, THEME_NAMES, THINKING_LEVELS, } from '../config/schema.js';
|
|
11
12
|
import { computeCost, formatCost, formatDuration, formatTokens } from '../agent/usage.js';
|
|
13
|
+
import { mergePendingEntries } from '../agent/queued-messages.js';
|
|
12
14
|
import { TODO_FOLLOW_LIMITS } from '../todo/limits.js';
|
|
13
15
|
import { buildContinuationMessage } from '../todo/follow-through.js';
|
|
14
16
|
import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
15
|
-
import { loadConfig } from '../config/load.js';
|
|
16
17
|
import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
|
|
17
18
|
import { getLogger } from '../logging/logger.js';
|
|
18
|
-
import {
|
|
19
|
+
import { registerQueueCommand } from './queue-command.js';
|
|
19
20
|
import { perfCommand } from './perf.js';
|
|
20
21
|
import { runCompactCommand } from '../compaction/command.js';
|
|
21
22
|
import { runContextCommand } from '../compaction/context-command.js';
|
|
@@ -370,8 +371,16 @@ const COMMANDS = [
|
|
|
370
371
|
return;
|
|
371
372
|
}
|
|
372
373
|
const persist = (patch) => {
|
|
373
|
-
|
|
374
|
-
|
|
374
|
+
const connection = ['provider', 'model', 'baseUrl'].some((key) => key in patch);
|
|
375
|
+
if (!connection) {
|
|
376
|
+
controller.setFastConfig(patch);
|
|
377
|
+
ctx.persistConfig({ fast: patch });
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
const result = controller.saveModelSettingsPatch({ fast: patch });
|
|
381
|
+
if (!result.ok)
|
|
382
|
+
ctx.notify('warn', modelSettingsResultMessage(result));
|
|
383
|
+
return result.ok;
|
|
375
384
|
};
|
|
376
385
|
if (!verb || verb === 'status') {
|
|
377
386
|
ctx.notify('info', formatFastStatus(controller));
|
|
@@ -379,7 +388,8 @@ const COMMANDS = [
|
|
|
379
388
|
}
|
|
380
389
|
if (verb === 'on' || verb === 'off') {
|
|
381
390
|
const enabled = verb === 'on';
|
|
382
|
-
persist({ enabled })
|
|
391
|
+
if (!persist({ enabled }))
|
|
392
|
+
return;
|
|
383
393
|
if (!controller.isFastRegistered()) {
|
|
384
394
|
// The honest branch, and the whole point of splitting by what was
|
|
385
395
|
// decided at construction (§3.3). It is one sentence, and it keeps
|
|
@@ -413,11 +423,13 @@ const COMMANDS = [
|
|
|
413
423
|
ctx.notify('warn', `Unknown provider "${provider}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
414
424
|
return;
|
|
415
425
|
}
|
|
416
|
-
persist({ provider, model })
|
|
426
|
+
if (!persist({ provider, model }))
|
|
427
|
+
return;
|
|
417
428
|
ctx.toast('success', `Fast model set to ${provider}:${model}.`);
|
|
418
429
|
return;
|
|
419
430
|
}
|
|
420
|
-
persist({ model: raw })
|
|
431
|
+
if (!persist({ model: raw }))
|
|
432
|
+
return;
|
|
421
433
|
ctx.toast('success', `Fast model set to ${raw}.`);
|
|
422
434
|
return;
|
|
423
435
|
}
|
|
@@ -431,7 +443,8 @@ const COMMANDS = [
|
|
|
431
443
|
ctx.notify('warn', `Unknown provider "${raw}". Choose: ${ADAPTER_PROVIDERS.join(', ')}.`);
|
|
432
444
|
return;
|
|
433
445
|
}
|
|
434
|
-
persist({ provider: raw })
|
|
446
|
+
if (!persist({ provider: raw }))
|
|
447
|
+
return;
|
|
435
448
|
ctx.toast('success', `Fast provider set to ${raw}.`);
|
|
436
449
|
return;
|
|
437
450
|
}
|
|
@@ -440,18 +453,20 @@ const COMMANDS = [
|
|
|
440
453
|
// (R-13): the user is allowed to run both tiers on one model, and the
|
|
441
454
|
// review still costs what it costs.
|
|
442
455
|
const cfg = controller.getConfig();
|
|
443
|
-
persist({
|
|
456
|
+
if (!persist({
|
|
444
457
|
provider: cfg.provider,
|
|
445
458
|
model: cfg.model,
|
|
446
459
|
baseUrl: cfg.baseUrl ?? '',
|
|
447
|
-
})
|
|
460
|
+
}))
|
|
461
|
+
return;
|
|
448
462
|
ctx.toast('success', `Fast tier set to ${cfg.provider}:${cfg.model} (same as main).`);
|
|
449
463
|
return;
|
|
450
464
|
}
|
|
451
465
|
if (verb === 'review') {
|
|
452
466
|
const raw = (value ?? '').trim().toLowerCase();
|
|
453
467
|
if (raw === 'off') {
|
|
454
|
-
persist({ review: false })
|
|
468
|
+
if (!persist({ review: false }))
|
|
469
|
+
return;
|
|
455
470
|
ctx.toast('success', 'Fast review off.');
|
|
456
471
|
return;
|
|
457
472
|
}
|
|
@@ -507,7 +522,8 @@ const COMMANDS = [
|
|
|
507
522
|
ctx.notify('warn', 'Usage: /fast delegate <on|off>');
|
|
508
523
|
return;
|
|
509
524
|
}
|
|
510
|
-
persist({ delegate: raw === 'on' })
|
|
525
|
+
if (!persist({ delegate: raw === 'on' }))
|
|
526
|
+
return;
|
|
511
527
|
ctx.toast('success', `Fast delegation ${raw}.`);
|
|
512
528
|
return;
|
|
513
529
|
}
|
|
@@ -882,10 +898,16 @@ const COMMANDS = [
|
|
|
882
898
|
name: 'reset',
|
|
883
899
|
description: 'Start a new conversation',
|
|
884
900
|
run: (ctx) => {
|
|
901
|
+
if (ctx.controller.isRunning()) {
|
|
902
|
+
ctx.notify('warn', 'Cannot reset while the agent is running. Wait for it to stop.');
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
885
905
|
ctx.controller.clearMessages();
|
|
886
906
|
ctx.controller.clearAllQueues();
|
|
887
907
|
ctx.dispatch({ type: 'resetConversation' });
|
|
888
908
|
ctx.toast('info', 'Started a new conversation.');
|
|
909
|
+
if (ctx.state.pendingSteering.length)
|
|
910
|
+
ctx.notify('warn', `\u5df2\u53d6\u6d88 ${ctx.state.pendingSteering.length} \u6761\u672a\u63a5\u6536\u6d88\u606f`);
|
|
889
911
|
},
|
|
890
912
|
},
|
|
891
913
|
{
|
|
@@ -920,7 +942,7 @@ const COMMANDS = [
|
|
|
920
942
|
...(cfg.baseUrl ? { baseUrl: cfg.baseUrl } : {}),
|
|
921
943
|
},
|
|
922
944
|
messages: ctx.controller.getMessages(),
|
|
923
|
-
entries: ctx.state.entries,
|
|
945
|
+
entries: mergePendingEntries(ctx.state.entries, ctx.state.pendingSteering),
|
|
924
946
|
// `/resume` restores `messages`, so the belief that justifies the
|
|
925
947
|
// panel comes back — and the panel has to come back with it (§3.13).
|
|
926
948
|
todos: ctx.controller.getTodoSnapshot()?.items ?? [],
|
|
@@ -936,22 +958,30 @@ const COMMANDS = [
|
|
|
936
958
|
name: 'resume',
|
|
937
959
|
description: 'Load a saved session',
|
|
938
960
|
run: (ctx) => {
|
|
961
|
+
if (ctx.controller.isRunning() || ctx.controller.isModelSettingsBusy?.()) {
|
|
962
|
+
ctx.notify('warn', 'Cannot resume while running work, team, review, or compaction is busy.');
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
939
965
|
try {
|
|
940
966
|
const path = resolveSessionPath(ctx.args, ctx.controller.getCwd());
|
|
941
967
|
const session = loadSession(path);
|
|
968
|
+
if (ctx.controller.isRunning() || ctx.controller.isModelSettingsBusy?.()) {
|
|
969
|
+
ctx.notify('warn', 'Cannot resume while running work, team, review, or compaction is busy.');
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
// Loading has validated and normalized every external value. Commit
|
|
973
|
+
// synchronously so no new run can start between clearing and restoring.
|
|
974
|
+
ctx.controller.clearAllQueues();
|
|
942
975
|
ctx.controller.replaceMessages(session.messages);
|
|
943
|
-
//
|
|
944
|
-
// file fails the whole resume rather than half of it — and AN ABSENT
|
|
945
|
-
// `todos` CLEARS RATHER THAN SKIPPING (P0-2 / D-22). `replaceMessages`
|
|
946
|
-
// has just discarded the conversation the current list belonged to, so
|
|
947
|
-
// leaving the rail up produces a panel that disagrees with the model,
|
|
948
|
-
// which §1.2 ranks below having no panel at all.
|
|
976
|
+
// Absence clears the previous conversation's list, including old files.
|
|
949
977
|
ctx.controller.restoreTodos(session.todos ?? []);
|
|
950
978
|
ctx.dispatch({ type: 'restoreEntries', entries: session.entries });
|
|
951
979
|
if (session.model?.providerId && session.model?.modelId) {
|
|
952
980
|
ctx.controller.setModel(session.model.providerId, session.model.modelId, session.model.baseUrl);
|
|
953
981
|
}
|
|
954
982
|
ctx.notify('info', `Resumed session from ${path}`);
|
|
983
|
+
if (ctx.state.pendingSteering.length)
|
|
984
|
+
ctx.notify('warn', `\u5df2\u53d6\u6d88 ${ctx.state.pendingSteering.length} \u6761\u672a\u63a5\u6536\u6d88\u606f`);
|
|
955
985
|
}
|
|
956
986
|
catch (err) {
|
|
957
987
|
ctx.notify('error', `Resume failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -961,7 +991,7 @@ const COMMANDS = [
|
|
|
961
991
|
{
|
|
962
992
|
name: 'copy',
|
|
963
993
|
description: 'Copy the last answer to the clipboard',
|
|
964
|
-
run: (ctx) => {
|
|
994
|
+
run: async (ctx) => {
|
|
965
995
|
const last = [...ctx.state.entries].reverse().find((e) => e.kind === 'assistant');
|
|
966
996
|
if (!last || last.kind !== 'assistant' || last.text.trim().length === 0) {
|
|
967
997
|
ctx.notify('warn', 'No assistant message to copy.');
|
|
@@ -973,15 +1003,11 @@ const COMMANDS = [
|
|
|
973
1003
|
// which the local `pbcopy` never could.
|
|
974
1004
|
//
|
|
975
1005
|
// `ctx.writeForeign` and not `stdout.write` — see the field's comment.
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
ctx.toast('warn', 'Clipboard not available.');
|
|
1006
|
+
if (!ctx.requestCopy) {
|
|
1007
|
+
ctx.notify('warn', 'Clipboard coordinator unavailable.');
|
|
979
1008
|
return;
|
|
980
1009
|
}
|
|
981
|
-
|
|
982
|
-
// detectable, and tmux without `set -g set-clipboard on` swallows OSC 52
|
|
983
|
-
// silently (R-7).
|
|
984
|
-
ctx.toast('success', via === 'osc52' ? 'Sent to the terminal clipboard.' : 'Sent to the system clipboard.');
|
|
1010
|
+
await ctx.requestCopy({ text: last.text, lines: last.text.split('\n').length });
|
|
985
1011
|
},
|
|
986
1012
|
},
|
|
987
1013
|
{
|
|
@@ -1042,17 +1068,24 @@ const COMMANDS = [
|
|
|
1042
1068
|
const lines = [
|
|
1043
1069
|
'Shift+Enter newline - terminal setup',
|
|
1044
1070
|
'',
|
|
1045
|
-
'Ctrl+J
|
|
1046
|
-
'
|
|
1071
|
+
'Ctrl+J inserts a newline when delivered; Alt+Enter may be intercepted.',
|
|
1072
|
+
'To get a dedicated Shift+Enter key, make the terminal send',
|
|
1047
1073
|
'the CSI-u sequence \\u001b[13;2u for it:',
|
|
1048
1074
|
'',
|
|
1049
|
-
' Windows Terminal
|
|
1050
|
-
'
|
|
1051
|
-
'
|
|
1075
|
+
' Windows Terminal settings.json, merge root-level arrays:',
|
|
1076
|
+
' "copyOnSelect": false,',
|
|
1077
|
+
' "actions": [{ "id": "User.AragonNewline", "command":',
|
|
1078
|
+
' { "action": "sendInput", "input": "\\u001b[13;2u" } }],',
|
|
1079
|
+
' "keybindings": [{ "keys": "shift+enter", "id": "User.AragonNewline" }]',
|
|
1080
|
+
' PowerShell runs inside the terminal; configure this binding in',
|
|
1081
|
+
' Windows Terminal, not in the PowerShell profile. If both keys',
|
|
1082
|
+
' send CR, the application cannot distinguish Shift+Enter from Enter.',
|
|
1052
1083
|
' VS Code terminal keybindings.json:',
|
|
1053
1084
|
' { "key": "shift+enter",',
|
|
1054
1085
|
' "command": "workbench.action.terminal.sendSequence",',
|
|
1086
|
+
' "when": "terminalFocus",',
|
|
1055
1087
|
' "args": { "text": "\\u001b[13;2u" } }',
|
|
1088
|
+
' User settings: "terminal.integrated.copyOnSelection": false',
|
|
1056
1089
|
' iTerm2 Settings > Profiles > Keys > Key Mappings:',
|
|
1057
1090
|
' Shift+Enter -> Send Escape Sequence: [13;2u',
|
|
1058
1091
|
' kitty/WezTerm/foot native once keyboard enhancement is on',
|
|
@@ -1063,6 +1096,13 @@ const COMMANDS = [
|
|
|
1063
1096
|
' or Alt+Enter',
|
|
1064
1097
|
'',
|
|
1065
1098
|
'This command only prints advice; it changes no terminal modes.',
|
|
1099
|
+
'Host selections (Shift+drag or /mouse off) belong to the terminal.',
|
|
1100
|
+
'App selections wait for Ctrl+C; only a native tool exit confirms copying.',
|
|
1101
|
+
'SSH uses OSC 52: the terminal may deny it; sent does not mean confirmed.',
|
|
1102
|
+
'Without the stdin filter, Delete falls back to Backspace.',
|
|
1103
|
+
'Without paste support, multiline paste cannot be guaranteed safe.',
|
|
1104
|
+
'IME confirmation has no composition signal in PTY bytes. If it emits CR,',
|
|
1105
|
+
'edit externally and use bracketed paste; this host combination needs validation.',
|
|
1066
1106
|
];
|
|
1067
1107
|
ctx.notify('info', lines.join('\n'));
|
|
1068
1108
|
},
|
|
@@ -1090,12 +1130,12 @@ const COMMANDS = [
|
|
|
1090
1130
|
ctx.notify('warn', 'Cannot reload the config while a run is in progress.');
|
|
1091
1131
|
return;
|
|
1092
1132
|
}
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1133
|
+
const result = ctx.controller.reloadModelSettings();
|
|
1134
|
+
if (!result.ok) {
|
|
1135
|
+
ctx.notify('warn', modelSettingsResultMessage(result));
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
const config = ctx.controller.getConfig();
|
|
1099
1139
|
getLogger().info('config', 'reload', { provider: config.provider, model: config.model });
|
|
1100
1140
|
ctx.toast('success', `Reloaded config: ${config.provider}:${config.model}.`);
|
|
1101
1141
|
},
|
|
@@ -1205,6 +1245,7 @@ const DEFAULT_BG_LOG_ROWS = 40;
|
|
|
1205
1245
|
export function registerBuiltinCommands(registry) {
|
|
1206
1246
|
for (const command of COMMANDS)
|
|
1207
1247
|
registry.register(command);
|
|
1248
|
+
registerQueueCommand(registry);
|
|
1208
1249
|
}
|
|
1209
1250
|
/**
|
|
1210
1251
|
* Every built-in command name INCLUDING aliases (`quit`), for documentation and
|
|
@@ -1214,10 +1255,10 @@ export function registerBuiltinCommands(registry) {
|
|
|
1214
1255
|
* `registry.get(name)` instead, because that is the only check that stays
|
|
1215
1256
|
* correct when commands are registered dynamically (§7.1 / P1-2).
|
|
1216
1257
|
*/
|
|
1217
|
-
export const BUILTIN_COMMAND_NAMES = COMMANDS.flatMap((c) => [
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
]).sort();
|
|
1258
|
+
export const BUILTIN_COMMAND_NAMES = ['queue', ...COMMANDS.flatMap((c) => [
|
|
1259
|
+
c.name,
|
|
1260
|
+
...(c.aliases ?? []),
|
|
1261
|
+
])].sort();
|
|
1221
1262
|
// The `copyToClipboard` helper that used to live here moved into
|
|
1222
1263
|
// `ui/clipboard.ts` (tui-selection-and-scroll-follow §4.4.5), unchanged, so that
|
|
1223
1264
|
// `/copy` and the drag-select release share ONE clipboard path instead of two
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Read-only access to the live receipt-backed queue through the standard overlay port. */
|
|
2
|
+
export function registerQueueCommand(registry) {
|
|
3
|
+
registry.register({ name: 'queue', description: '\u67e5\u770b\u5f85\u5904\u7406\u6d88\u606f\u5168\u6587',
|
|
4
|
+
run: (ctx) => { ctx.setOverlay('queue'); } });
|
|
5
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* input is routed here instead of to the agent; `//` is the literal-`/` escape.
|
|
4
4
|
*/
|
|
5
5
|
import type { SubmitMessageOptions } from '../agent/prompt-options.js';
|
|
6
|
+
import type { CopyRequest, CopyRequestResult } from '../ui/clipboard-task.js';
|
|
6
7
|
import type { AgentController } from '../agent/controller.js';
|
|
7
8
|
import type { AgentMode } from '../agent/agent-mode.js';
|
|
8
9
|
import type { NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
|
|
@@ -10,6 +11,8 @@ import type { PersistedConfig } from '../config/schema.js';
|
|
|
10
11
|
import type { FollowThroughBudget } from '../todo/follow-through.js';
|
|
11
12
|
import type { UpdateCommandPort } from '../update/types.js';
|
|
12
13
|
export interface CommandContext {
|
|
14
|
+
/** App-owned shared lock and single feedback path for command and selection copies. */
|
|
15
|
+
requestCopy?: (request: CopyRequest) => Promise<CopyRequestResult>;
|
|
13
16
|
/** Raw argument string after the command word (may be empty). */
|
|
14
17
|
args: string;
|
|
15
18
|
controller: AgentController;
|
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
* lead's own history had been compacted. The team panel row and the dispatch
|
|
29
29
|
* report are where a child's internals belong.
|
|
30
30
|
*/
|
|
31
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
31
32
|
import type { AssistantMessage, ContextManager, LLMRequest, Message, ModelInfo, ModelRef, TokenUsage } from '@aragon-agent/core';
|
|
32
33
|
import type { CliConfig } from '../config/schema.js';
|
|
33
34
|
export interface ChildCompactionDeps {
|
|
34
35
|
/** Read LIVE on every call - see `childConfig` below. */
|
|
35
36
|
getConfig: () => CliConfig;
|
|
36
|
-
hasKey: (providerId: string) => boolean;
|
|
37
|
-
getApiKey: (providerId: string) => string | undefined;
|
|
37
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
38
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
38
39
|
getModelInfoFor: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => ModelInfo;
|
|
39
40
|
isPricedModel: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => boolean;
|
|
40
41
|
/** The lead wiring's own transport, reused (D-10 stays true). */
|
|
@@ -50,6 +51,7 @@ export interface ChildCompactionDeps {
|
|
|
50
51
|
* exactly on the cap.
|
|
51
52
|
*/
|
|
52
53
|
export interface ChildContextManagerRequest {
|
|
54
|
+
role?: ModelRole;
|
|
53
55
|
label: string;
|
|
54
56
|
/**
|
|
55
57
|
* The model the child actually runs on.
|
package/dist/compaction/child.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* lead's own history had been compacted. The team panel row and the dispatch
|
|
29
29
|
* report are where a child's internals belong.
|
|
30
30
|
*/
|
|
31
|
+
import { resolveFastTier } from '../fast/resolve.js';
|
|
31
32
|
import { getLogger } from '../logging/logger.js';
|
|
32
33
|
import { Compactor } from './compactor.js';
|
|
33
34
|
import { COMPACTION_LIMITS } from './limits.js';
|
|
@@ -56,7 +57,7 @@ export function createChildContextManager(req, deps) {
|
|
|
56
57
|
...base,
|
|
57
58
|
provider: req.model.providerId,
|
|
58
59
|
model: req.model.modelId,
|
|
59
|
-
|
|
60
|
+
baseUrl: req.model.baseUrl,
|
|
60
61
|
compaction: {
|
|
61
62
|
...base.compaction,
|
|
62
63
|
keepRecentTurns: COMPACTION_LIMITS.childKeepRecentTurns,
|
|
@@ -71,6 +72,8 @@ export function createChildContextManager(req, deps) {
|
|
|
71
72
|
};
|
|
72
73
|
const compactor = new Compactor({
|
|
73
74
|
getConfig: childConfig,
|
|
75
|
+
mainRole: req.role ?? 'main',
|
|
76
|
+
resolveFastCandidate: () => resolveFastTier(deps.getConfig(), deps.hasKey),
|
|
74
77
|
hasKey: deps.hasKey,
|
|
75
78
|
getApiKey: deps.getApiKey,
|
|
76
79
|
getModelInfoFor: deps.getModelInfoFor,
|
|
@@ -9,16 +9,20 @@
|
|
|
9
9
|
* keep, which model summarizes, what the prompt asks for, what happens when the
|
|
10
10
|
* call fails, and when to stop spending.
|
|
11
11
|
*/
|
|
12
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
12
13
|
import { type Message, type ModelInfo, type ModelRef, type TokenUsage, type AssistantMessage, type CompactionContext, type CompactionOutcome, type CompactionProbe, type ContextManager, type LLMRequest } from '@aragon-agent/core';
|
|
13
14
|
import type { CliConfig } from '../config/schema.js';
|
|
14
15
|
import type { NoticeLevel } from '../agent/reducer.js';
|
|
16
|
+
import { resolveFastTier } from '../fast/resolve.js';
|
|
15
17
|
import { ContextMeter } from './meter.js';
|
|
16
18
|
import type { CompactionEvent, Pressure } from './types.js';
|
|
17
19
|
export interface CompactorDeps {
|
|
20
|
+
mainRole?: ModelRole;
|
|
21
|
+
resolveFastCandidate?: () => ReturnType<typeof resolveFastTier>;
|
|
18
22
|
/** Read LIVE on every call — the controller replaces the object on mutation. */
|
|
19
23
|
getConfig: () => CliConfig;
|
|
20
|
-
hasKey: (providerId: string) => boolean;
|
|
21
|
-
getApiKey: (providerId: string) => string | undefined;
|
|
24
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
25
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
22
26
|
/**
|
|
23
27
|
* The engine's live history and system prompt.
|
|
24
28
|
*
|
|
@@ -87,6 +91,7 @@ export interface CompactorOptions {
|
|
|
87
91
|
}
|
|
88
92
|
/** The summarizer the next compaction would use. */
|
|
89
93
|
export interface SummarizerChoice {
|
|
94
|
+
role: ModelRole;
|
|
90
95
|
ref: ModelRef;
|
|
91
96
|
/** Whether the fast tier supplied it, for `/compact status` and the card. */
|
|
92
97
|
fromFastTier: boolean;
|
|
@@ -486,20 +486,22 @@ export class Compactor {
|
|
|
486
486
|
const { summarizer } = args;
|
|
487
487
|
if (!summarizer)
|
|
488
488
|
return { kind: 'failed', reason: 'no_summarizer_model' };
|
|
489
|
-
const first = await this.callOnce(summarizer
|
|
489
|
+
const first = await this.callOnce(summarizer, args);
|
|
490
490
|
if (first.kind === 'ok' || first.kind === 'aborted')
|
|
491
491
|
return first;
|
|
492
492
|
// Rung 2 — retry, on the MAIN model when rung 1 was the fast tier.
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
493
|
+
const retryChoice = summarizer.fromFastTier
|
|
494
|
+
? { ref: this.mainRef(), role: this.deps.mainRole ?? 'main', fromFastTier: false }
|
|
495
|
+
: summarizer;
|
|
496
|
+
this.log.warn('summarize_retry', { reason: first.reason, model: retryChoice.ref.modelId });
|
|
497
|
+
const second = await this.callOnce(retryChoice, args);
|
|
497
498
|
if (second.kind === 'ok' || second.kind === 'aborted')
|
|
498
499
|
return second;
|
|
499
500
|
return { kind: 'failed', reason: second.reason };
|
|
500
501
|
}
|
|
501
|
-
async callOnce(
|
|
502
|
-
const
|
|
502
|
+
async callOnce(choice, args) {
|
|
503
|
+
const { ref, role } = choice;
|
|
504
|
+
const apiKey = this.deps.getApiKey(ref.providerId, role);
|
|
503
505
|
if (!apiKey)
|
|
504
506
|
return { kind: 'failed', reason: 'no_api_key' };
|
|
505
507
|
if (args.ctx.signal.aborted)
|
|
@@ -764,14 +766,14 @@ export class Compactor {
|
|
|
764
766
|
resolveSummarizer() {
|
|
765
767
|
const config = this.deps.getConfig();
|
|
766
768
|
if (config.compaction.useFastTier) {
|
|
767
|
-
const tier = resolveFastTier(config, this.deps.hasKey);
|
|
769
|
+
const tier = this.deps.resolveFastCandidate?.() ?? resolveFastTier(config, this.deps.hasKey);
|
|
768
770
|
if (tier.ok)
|
|
769
|
-
return { ref: tier.ref, fromFastTier: true };
|
|
771
|
+
return { ref: tier.ref, role: 'fast', fromFastTier: true };
|
|
770
772
|
}
|
|
771
773
|
const ref = this.mainRef();
|
|
772
|
-
if (!this.deps.hasKey(ref.providerId))
|
|
774
|
+
if (!this.deps.hasKey(ref.providerId, this.deps.mainRole ?? 'main'))
|
|
773
775
|
return null;
|
|
774
|
-
return { ref, fromFastTier: false };
|
|
776
|
+
return { ref, role: this.deps.mainRole ?? 'main', fromFastTier: false };
|
|
775
777
|
}
|
|
776
778
|
/**
|
|
777
779
|
* The meter this compactor reads, injected or private (R-1).
|
|
@@ -45,6 +45,11 @@ export function formatContextReport(ctx) {
|
|
|
45
45
|
// one line and answers the same question.
|
|
46
46
|
lines.push(' includes subagent, fast-tier and compaction spend,');
|
|
47
47
|
lines.push(' not just this conversation');
|
|
48
|
+
lines.push(' Scope lead occupancy is used / limit in tokens; session input includes caches.');
|
|
49
|
+
lines.push(' Estimate USD session cost, including restored usage; missing prices count as 0.');
|
|
50
|
+
lines.push(' Zero estimated cost does not guarantee free usage or match the bill.');
|
|
51
|
+
lines.push(' Run average booked output-token delta / elapsed time, not instantaneous generation.');
|
|
52
|
+
lines.push(' May include team, fast-tier and compaction output booked during this run.');
|
|
48
53
|
return lines.join('\n');
|
|
49
54
|
}
|
|
50
55
|
/**
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
* guard is now unreachable for declines and stays for the abort race it was
|
|
36
36
|
* written for.
|
|
37
37
|
*/
|
|
38
|
+
import type { ModelRole } from '../config/model-profiles.js';
|
|
38
39
|
import { type AgentEvent, type ContextManager, type Message, type ModelInfo, type ModelRef, type ProviderRegistry, type ProviderRegistryOptions, type TokenUsage } from '@aragon-agent/core';
|
|
39
40
|
import type { CliConfig } from '../config/schema.js';
|
|
40
41
|
import type { NoticeLevel } from '../agent/reducer.js';
|
|
@@ -44,8 +45,8 @@ import type { CompactionEventListener, CompactionSnapshot } from './types.js';
|
|
|
44
45
|
export interface CompactionWiringDeps {
|
|
45
46
|
/** Read LIVE on every call - the controller replaces the object on mutation. */
|
|
46
47
|
getConfig: () => CliConfig;
|
|
47
|
-
hasKey: (providerId: string) => boolean;
|
|
48
|
-
getApiKey: (providerId: string) => string | undefined;
|
|
48
|
+
hasKey: (providerId: string, role?: ModelRole) => boolean;
|
|
49
|
+
getApiKey: (providerId: string, role?: ModelRole) => string | undefined;
|
|
49
50
|
getModelInfoFor: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => ModelInfo;
|
|
50
51
|
isPricedModel: (ref: Pick<ModelRef, 'providerId' | 'modelId'>) => boolean;
|
|
51
52
|
/**
|
|
@@ -158,7 +159,7 @@ export declare class CompactionWiring {
|
|
|
158
159
|
isEnabled(): boolean;
|
|
159
160
|
setEnabled(enabled: boolean): void;
|
|
160
161
|
/** Re-emit the snapshot after any config mutation, so the chip and the gauge move. */
|
|
161
|
-
onConfigChanged(): void;
|
|
162
|
+
onConfigChanged(enabled?: boolean): void;
|
|
162
163
|
abort(): void;
|
|
163
164
|
/**
|
|
164
165
|
* The host replaced or cleared the history (hardening §3.2.3, sites 3 and 4).
|
|
@@ -209,7 +209,12 @@ export class CompactionWiring {
|
|
|
209
209
|
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
210
210
|
}
|
|
211
211
|
/** Re-emit the snapshot after any config mutation, so the chip and the gauge move. */
|
|
212
|
-
onConfigChanged() {
|
|
212
|
+
onConfigChanged(enabled) {
|
|
213
|
+
if (enabled !== undefined && enabled !== this.enabled) {
|
|
214
|
+
this.enabled = enabled;
|
|
215
|
+
if (enabled)
|
|
216
|
+
this.compactor.clearSelfDisable();
|
|
217
|
+
}
|
|
213
218
|
this.emit({ type: 'snapshot', snapshot: this.snapshot() });
|
|
214
219
|
}
|
|
215
220
|
abort() {
|
|
@@ -168,6 +168,8 @@ export declare function applyCompactionConfigSet(key: string, value: string): Pa
|
|
|
168
168
|
* later pastes a log into an issue.
|
|
169
169
|
*/
|
|
170
170
|
export declare function warnIfRedactionDisabled(key: string, value: string): void;
|
|
171
|
+
/** Every secret is masked wherever it is printed, `--json` included (R-12). */
|
|
172
|
+
export declare function maskConfig(config: PersistedConfig): Record<string, unknown>;
|
|
171
173
|
export declare function runConfigGet(key: string): void;
|
|
172
174
|
export declare function runConfigList(opts?: {
|
|
173
175
|
json?: boolean;
|
|
@@ -13,6 +13,7 @@ import process from 'node:process';
|
|
|
13
13
|
import { getConfigBackupPath, getConfigPath, getHomeRoot, getLogsDir, getPromptHistoryPath, getUiStatePath, isHomeOverridden, } from './app-paths.js';
|
|
14
14
|
import { getHomeMigrationBreadcrumbPath } from './migrate-home.js';
|
|
15
15
|
import { loadPersistedConfig, updatePersistedConfig } from './store.js';
|
|
16
|
+
import { normalizeModelProfiles, validateModelProfiles } from './model-profiles.js';
|
|
16
17
|
import { clampFastConfig, clampRetryConfig, clampCompactionConfig, parseThresholdInput, clampUpdateConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, DEFAULT_COMPACTION_CONFIG, DEFAULT_UPDATE_CONFIG, } from './schema.js';
|
|
17
18
|
import { clampLogLevel } from '../logging/levels.js';
|
|
18
19
|
/** Config keys under `log.` that `aragon config set` accepts. */
|
|
@@ -386,12 +387,23 @@ export function warnIfRedactionDisabled(key, value) {
|
|
|
386
387
|
// get / list
|
|
387
388
|
// ---------------------------------------------------------------------------
|
|
388
389
|
/** Every secret is masked wherever it is printed, `--json` included (R-12). */
|
|
389
|
-
function maskConfig(config) {
|
|
390
|
+
export function maskConfig(config) {
|
|
390
391
|
const apiKeys = {};
|
|
391
392
|
for (const [provider, key] of Object.entries(config.apiKeys)) {
|
|
392
393
|
apiKeys[provider] = maskSecret(key);
|
|
393
394
|
}
|
|
394
|
-
|
|
395
|
+
const masked = { ...config, apiKeys };
|
|
396
|
+
if (config.modelProfiles !== undefined) {
|
|
397
|
+
if (validateModelProfiles(config.modelProfiles).length)
|
|
398
|
+
masked.modelProfiles = { invalid: true };
|
|
399
|
+
else {
|
|
400
|
+
const profiles = normalizeModelProfiles(config.modelProfiles);
|
|
401
|
+
masked.modelProfiles = { ...profiles, entries: profiles.entries.map((entry) => ({
|
|
402
|
+
...entry, apiKey: entry.apiKey ? 'Configured' : 'Missing',
|
|
403
|
+
})) };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return masked;
|
|
395
407
|
}
|
|
396
408
|
/**
|
|
397
409
|
* The human-readable label for a value whose raw form would mislead, or
|
|
@@ -503,8 +515,8 @@ export async function runConfigEdit() {
|
|
|
503
515
|
JSON.parse(readFileSync(file, 'utf-8'));
|
|
504
516
|
process.stdout.write(`${file} saved.\n`);
|
|
505
517
|
}
|
|
506
|
-
catch
|
|
507
|
-
process.stderr.write(`${file} is not valid JSON
|
|
518
|
+
catch {
|
|
519
|
+
process.stderr.write(`${file} is not valid JSON.\n` +
|
|
508
520
|
`The previous version is still at ${backup}.\n`);
|
|
509
521
|
process.exitCode = 1;
|
|
510
522
|
}
|
package/dist/config/load.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* legitimately long single tool run can never be watchdog-killed.
|
|
7
7
|
*/
|
|
8
8
|
import { type CliConfig } from './schema.js';
|
|
9
|
+
import type { ModelRole } from './model-profiles.js';
|
|
9
10
|
export interface CliFlags {
|
|
10
11
|
provider?: string;
|
|
11
12
|
model?: string;
|
|
@@ -245,4 +246,4 @@ export declare function loadConfig(flags?: CliFlags): CliConfig;
|
|
|
245
246
|
* Build the `getApiKey(providerId)` resolver the core `Agent` consumes.
|
|
246
247
|
* The one-shot `--api-key` override wins, but only for the active provider.
|
|
247
248
|
*/
|
|
248
|
-
export declare function makeGetApiKey(config: CliConfig): (providerId: string) => string | undefined;
|
|
249
|
+
export declare function makeGetApiKey(config: CliConfig, role?: ModelRole): (providerId: string) => string | undefined;
|
package/dist/config/load.js
CHANGED
|
@@ -9,6 +9,7 @@ import process from 'node:process';
|
|
|
9
9
|
import { DEFAULT_CONFIG, IDLE_TIMEOUT_MARGIN_MS, clampAskRounds, clampDensity, clampHumanTimeout, clampCompactionConfig, clampLogConfig, clampFastConfig, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampBashConfig, clampUpdateConfig, clampTheme, clampThinkingLevel, clampContextWindow, clampMaxTokens, clampMaxRenderInterval, clampScrollResumeMs, clampTranscriptRetain, clampTranscriptWindow, coercePositiveInt, isAutoToken, isSkillsToolPolicyMode, parseThresholdInput, } from './schema.js';
|
|
10
10
|
import { loadDotenv, readEnvConfig, readEnvDisabledSkills } from './env.js';
|
|
11
11
|
import { readConfigFile } from './store.js';
|
|
12
|
+
import { projectModelProfiles, resolveModelProfileState, resolveModelRoleKey } from './model-profile-resolution.js';
|
|
12
13
|
import { getConfigPath } from './app-paths.js';
|
|
13
14
|
import { setHistoryEnabled } from './prompt-history.js';
|
|
14
15
|
import { setEntryRetain } from '../agent/entry-limits.js';
|
|
@@ -440,11 +441,17 @@ export function loadConfig(flags = {}) {
|
|
|
440
441
|
// `.env` must be loaded before reading env overrides.
|
|
441
442
|
loadDotenv(cwd);
|
|
442
443
|
const read = readConfigFile();
|
|
443
|
-
const
|
|
444
|
+
const projection = projectModelProfiles(read.config ?? {});
|
|
445
|
+
const file = projection.file;
|
|
444
446
|
const env = readEnvConfig();
|
|
445
447
|
if (read.parseError) {
|
|
446
448
|
getLogger().error('config', 'config_parse_failed', { error: read.parseError });
|
|
447
449
|
}
|
|
450
|
+
if (projection.issues.length) {
|
|
451
|
+
getLogger().warn('config', 'model_profiles_invalid', {
|
|
452
|
+
error: 'Invalid model profiles; using custom connections. Repair with aragon config edit.',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
448
455
|
const provider = pick(flags.provider, env.partial.provider, file.provider) ?? DEFAULT_CONFIG.provider;
|
|
449
456
|
const model = pick(flags.model, env.partial.model, file.model) ?? DEFAULT_CONFIG.model;
|
|
450
457
|
const baseUrl = pick(flags.baseUrl, env.partial.baseUrl ?? undefined, file.baseUrl ?? undefined);
|
|
@@ -543,6 +550,7 @@ export function loadConfig(flags = {}) {
|
|
|
543
550
|
// it through the same module-singleton channel `setHistoryEnabled` uses.
|
|
544
551
|
setEntryRetain(transcriptRetain);
|
|
545
552
|
const config = {
|
|
553
|
+
modelProfiles: projection.issues.length ? undefined : file.modelProfiles,
|
|
546
554
|
provider,
|
|
547
555
|
model,
|
|
548
556
|
baseUrl,
|
|
@@ -605,6 +613,10 @@ export function loadConfig(flags = {}) {
|
|
|
605
613
|
unicode: caps.unicode,
|
|
606
614
|
apiKeyOverride: flags.apiKey && flags.apiKey.trim().length > 0 ? flags.apiKey.trim() : undefined,
|
|
607
615
|
};
|
|
616
|
+
config.modelProfileState = resolveModelProfileState(config, config.modelProfiles, projection.issues.length > 0);
|
|
617
|
+
if (config.apiKeyOverride && (config.modelProfiles?.mainId || config.modelProfiles?.fastId)) {
|
|
618
|
+
config.apiKeyOverrideTarget = { provider, baseUrl: baseUrl?.trim() || null };
|
|
619
|
+
}
|
|
608
620
|
recordResolvedConfig(config);
|
|
609
621
|
return config;
|
|
610
622
|
}
|
|
@@ -646,11 +658,6 @@ function recordResolvedConfig(config) {
|
|
|
646
658
|
* Build the `getApiKey(providerId)` resolver the core `Agent` consumes.
|
|
647
659
|
* The one-shot `--api-key` override wins, but only for the active provider.
|
|
648
660
|
*/
|
|
649
|
-
export function makeGetApiKey(config) {
|
|
650
|
-
return (providerId) => {
|
|
651
|
-
if (config.apiKeyOverride && providerId === config.provider) {
|
|
652
|
-
return config.apiKeyOverride;
|
|
653
|
-
}
|
|
654
|
-
return config.apiKeys[providerId];
|
|
655
|
-
};
|
|
661
|
+
export function makeGetApiKey(config, role = 'main') {
|
|
662
|
+
return (providerId) => resolveModelRoleKey({ config, role, providerId });
|
|
656
663
|
}
|