pi-advisor-flow 0.7.0 → 0.8.1-dev.1
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 +27 -18
- package/README.md +9 -8
- package/dist/index.js +1968 -1081
- package/extensions/index.ts +2 -1
- package/package.json +48 -35
- package/src/attachments.ts +11 -10
- package/src/child-session.ts +15 -0
- package/src/commands/activation-preparation.ts +1 -0
- package/src/commands/activation.ts +12 -3
- package/src/commands/lifecycle.ts +7 -1
- package/src/commands/manual-command.ts +7 -1
- package/src/commands/manual-consultation.ts +102 -92
- package/src/commands/manual-progress.ts +7 -5
- package/src/commands/model-commands.ts +1 -1
- package/src/commands/model-options.ts +14 -3
- package/src/commands/model-picker.ts +1 -0
- package/src/commands/registration.ts +1 -0
- package/src/commands/renderers.ts +67 -55
- package/src/commands/runtime.ts +16 -14
- package/src/commands/settings-commands.ts +10 -4
- package/src/commands/settings-persistence.ts +16 -2
- package/src/commands/types.ts +1 -0
- package/src/commands.ts +0 -1
- package/src/config/args.ts +1 -1
- package/src/config/defaults.ts +8 -1
- package/src/config/schema.ts +42 -17
- package/src/config/state.ts +12 -3
- package/src/config/storage.ts +34 -23
- package/src/config/types.ts +16 -3
- package/src/config/validation.ts +33 -21
- package/src/config.ts +3 -0
- package/src/content-utils.ts +24 -4
- package/src/conversation.ts +16 -15
- package/src/git.ts +10 -3
- package/src/herdr-block.ts +83 -0
- package/src/herdr-shared.ts +108 -0
- package/src/herdr.ts +30 -192
- package/src/jev/client.ts +75 -85
- package/src/jev/failure.ts +19 -0
- package/src/jev/key-store.ts +26 -11
- package/src/jev/ledger.ts +58 -64
- package/src/jev/questions.ts +26 -23
- package/src/jev/state.ts +15 -12
- package/src/jev/transport.ts +8 -3
- package/src/model-stream.ts +35 -37
- package/src/outcomes.ts +15 -11
- package/src/pi-settings.ts +4 -9
- package/src/preferences.ts +4 -2
- package/src/redaction.ts +10 -12
- package/src/scout-context.ts +59 -60
- package/src/scout-curation.ts +4 -13
- package/src/scout-groups.ts +178 -175
- package/src/scout-protocol.ts +74 -70
- package/src/scout-reconstruct.ts +1 -1
- package/src/scout.ts +128 -77
- package/src/session-state.ts +64 -49
- package/src/tool-result-cap.ts +18 -17
- package/src/tools/consult-context.ts +7 -15
- package/src/tools/consultation.ts +14 -4
- package/src/tools/gate-policy.ts +1 -0
- package/src/tools/gate-protocol.ts +36 -23
- package/src/tools/jev-filter.ts +43 -41
- package/src/tools/jev-turn-gate.ts +29 -9
- package/src/tools/loop-gate.ts +31 -20
- package/src/tools/model-access.ts +46 -0
- package/src/tools/prompts.ts +20 -25
- package/src/tools/register-ask-advisor.ts +36 -16
- package/src/tools/register-lifecycle.ts +22 -0
- package/src/tools/register-outcome.ts +2 -0
- package/src/tools/register-renderers.ts +146 -128
- package/src/tools/registration.ts +12 -14
- package/src/tools/render-advisor-result.ts +35 -22
- package/src/tools/render-common.ts +16 -19
- package/src/tools/scout-status.ts +42 -27
- package/src/tools/types.ts +1 -0
- package/src/tools.ts +0 -1
- package/src/ui/jev-setup-submenu.ts +65 -59
- package/src/ui/manual-dialog-render.ts +1 -0
- package/src/ui/manual-dialog.ts +39 -43
- package/src/ui/masked-input.ts +5 -11
- package/src/ui/model-multi-selector.ts +9 -0
- package/src/ui/model-selector-adapter.ts +31 -0
- package/src/ui/model-selector.ts +14 -161
- package/src/ui/searchable-model-list.ts +230 -0
- package/src/ui/settings-formatting.ts +24 -14
- package/src/ui/settings-items.ts +38 -1
- package/src/ui/settings-list-adapter.ts +15 -12
- package/src/ui/settings-mutations.ts +157 -70
- package/src/ui/settings-selector.ts +18 -9
- package/src/ui/text-setting-submenu.ts +3 -6
- package/src/ui/types.ts +14 -0
- package/src/ui.ts +0 -1
- package/src/usage.ts +26 -19
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type SettingsList,
|
|
1
|
+
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
2
|
+
import type {
|
|
3
|
+
Component,
|
|
4
|
+
Focusable,
|
|
5
|
+
Input,
|
|
6
|
+
SettingItem,
|
|
7
|
+
SettingsList,
|
|
9
8
|
} from "@earendil-works/pi-tui";
|
|
10
9
|
|
|
11
10
|
interface SettingsListPrivateFields {
|
|
@@ -15,6 +14,8 @@ interface SettingsListPrivateFields {
|
|
|
15
14
|
submenuComponent?: Component | null;
|
|
16
15
|
}
|
|
17
16
|
|
|
17
|
+
type SettingsListWithPrivateFields = SettingsList & SettingsListPrivateFields;
|
|
18
|
+
|
|
18
19
|
/**
|
|
19
20
|
* Contains the narrow compatibility access needed for pi-tui's SettingsList.
|
|
20
21
|
* No other UI module should depend on its private fields.
|
|
@@ -45,6 +46,7 @@ export class SettingsListAdapter {
|
|
|
45
46
|
|
|
46
47
|
setFocused(value: boolean): void {
|
|
47
48
|
const fields = this.privateFields();
|
|
49
|
+
// SAFETY: a SettingsList submenu component is registered only when focusable.
|
|
48
50
|
const submenu = fields.submenuComponent as Focusable | undefined;
|
|
49
51
|
if (submenu) {
|
|
50
52
|
submenu.focused = value;
|
|
@@ -53,7 +55,7 @@ export class SettingsListAdapter {
|
|
|
53
55
|
|
|
54
56
|
setSelectedId(items: SettingItem[], selectedId: string): void {
|
|
55
57
|
const selectedIndex = items.findIndex((item) => item.id === selectedId);
|
|
56
|
-
if (selectedIndex
|
|
58
|
+
if (selectedIndex !== -1) {
|
|
57
59
|
this.privateFields().selectedIndex = selectedIndex;
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -63,9 +65,9 @@ export class SettingsListAdapter {
|
|
|
63
65
|
onChange: (id: string, value: string) => void
|
|
64
66
|
): boolean {
|
|
65
67
|
let direction = 0;
|
|
66
|
-
if (matchesKey(keyData, Key.left) || keyData === "\
|
|
68
|
+
if (matchesKey(keyData, Key.left) || keyData === "\u001B[D") {
|
|
67
69
|
direction = -1;
|
|
68
|
-
} else if (matchesKey(keyData, Key.right) || keyData === "\
|
|
70
|
+
} else if (matchesKey(keyData, Key.right) || keyData === "\u001B[C") {
|
|
69
71
|
direction = 1;
|
|
70
72
|
}
|
|
71
73
|
if (direction === 0) {
|
|
@@ -98,6 +100,7 @@ export class SettingsListAdapter {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
private privateFields(): SettingsListPrivateFields {
|
|
101
|
-
|
|
103
|
+
// SAFETY: compatibility-only read of SettingsList's private layout fields.
|
|
104
|
+
return this.list as SettingsListWithPrivateFields;
|
|
102
105
|
}
|
|
103
106
|
}
|
|
@@ -1,115 +1,202 @@
|
|
|
1
1
|
import { DEFAULT_JEV_MODEL } from "../config/types.ts";
|
|
2
|
-
import type { AdvisorSettings, ContextPreset
|
|
2
|
+
import type { AdvisorSettings, ContextPreset } from "./types.ts";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
"scoutEnabled",
|
|
6
|
-
"showUsageDetails",
|
|
7
|
-
"showUsageFooter",
|
|
8
|
-
"planGate",
|
|
9
|
-
"failureGate",
|
|
10
|
-
"completionGate",
|
|
11
|
-
"collapseResponses",
|
|
12
|
-
"blockOnBlocked",
|
|
4
|
+
const BOOLEAN_SETTING_FIELDS = [
|
|
13
5
|
"autoLoopGate",
|
|
14
|
-
"
|
|
6
|
+
"blockOnBlocked",
|
|
7
|
+
"collapseResponses",
|
|
8
|
+
"completionGate",
|
|
9
|
+
"failureGate",
|
|
15
10
|
"herdrIntegration",
|
|
11
|
+
"outcomeLogging",
|
|
12
|
+
"planGate",
|
|
16
13
|
"redactSecrets",
|
|
14
|
+
"scoutEnabled",
|
|
15
|
+
"sessionSummary",
|
|
16
|
+
"showUsageDetails",
|
|
17
|
+
"showUsageFooter",
|
|
17
18
|
"trackedFileContent",
|
|
18
19
|
"untrackedContent",
|
|
19
|
-
|
|
20
|
-
]);
|
|
20
|
+
] as const;
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const BOOLEAN_SETTING_IDS = new Set<string>(BOOLEAN_SETTING_FIELDS);
|
|
23
|
+
|
|
24
|
+
type BooleanSettingField = (typeof BOOLEAN_SETTING_FIELDS)[number];
|
|
25
|
+
|
|
26
|
+
const parseModelWhitelist = (value: string) => [
|
|
27
|
+
...new Set(
|
|
28
|
+
value
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((model) => model.trim())
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const applyCoreMutation = (
|
|
23
36
|
settings: AdvisorSettings,
|
|
24
37
|
id: string,
|
|
25
38
|
value: string,
|
|
26
39
|
presets: ContextPreset[]
|
|
27
|
-
):
|
|
40
|
+
): boolean => {
|
|
28
41
|
switch (id) {
|
|
29
|
-
case "context":
|
|
42
|
+
case "context": {
|
|
30
43
|
settings.contextMaxChars =
|
|
31
44
|
presets.find((preset) => preset.label === value)?.value ??
|
|
32
45
|
settings.contextMaxChars;
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
case "simpleMode": {
|
|
35
49
|
settings.simpleMode = value === "On";
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
case "alwaysOn": {
|
|
38
53
|
settings.alwaysOn = value === "On";
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
case "effort": {
|
|
41
57
|
settings.effort = value;
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
case "customRule": {
|
|
44
61
|
settings.customRule = value.trim() || undefined;
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
case "toolPolicies": {
|
|
65
|
+
// SAFETY: the value originates from the settings submenu's previously validated JSON editor.
|
|
47
66
|
settings.toolPolicies = JSON.parse(
|
|
48
67
|
value
|
|
49
68
|
) as AdvisorSettings["toolPolicies"];
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
case "loopThreshold": {
|
|
52
72
|
settings.loopThreshold = Number(
|
|
53
73
|
value.replace("After ", "").replace(" repeats", "")
|
|
54
74
|
);
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
case "maxCallsPerSession": {
|
|
57
78
|
settings.maxCallsPerSession = value === "∞" ? undefined : Number(value);
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
case "modelWhitelist": {
|
|
82
|
+
settings.modelWhitelist = parseModelWhitelist(value);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
case "failureMode": {
|
|
86
|
+
// SAFETY: value comes from the fixed failureMode options of the settings list.
|
|
60
87
|
settings.failureMode = value as AdvisorSettings["failureMode"];
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
case "gitContext": {
|
|
91
|
+
// SAFETY: value comes from the fixed GitContextLevel options of the settings list.
|
|
63
92
|
settings.gitContext = value as AdvisorSettings["gitContext"];
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
default: {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const applyNumericMutation = (
|
|
102
|
+
settings: AdvisorSettings,
|
|
103
|
+
id: string,
|
|
104
|
+
value: string
|
|
105
|
+
): boolean => {
|
|
106
|
+
switch (id) {
|
|
107
|
+
case "toolResultMaxLines": {
|
|
66
108
|
settings.toolResultMaxLines = Number(value);
|
|
67
|
-
|
|
68
|
-
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
case "toolResultMaxBytes": {
|
|
69
112
|
settings.toolResultMaxBytes = Number(value);
|
|
70
|
-
|
|
71
|
-
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
case "gitContextMaxChars": {
|
|
72
116
|
settings.gitContextMaxChars = Number(value);
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
default: {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const applyJevMutation = (
|
|
126
|
+
settings: AdvisorSettings,
|
|
127
|
+
id: string,
|
|
128
|
+
value: string
|
|
129
|
+
): boolean => {
|
|
130
|
+
switch (id) {
|
|
131
|
+
case "jevFilter": {
|
|
75
132
|
settings.jevFilterEnabled = value === "On";
|
|
76
|
-
|
|
77
|
-
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
case "jevFilterSkipConfidence": {
|
|
78
136
|
settings.jevFilterSkipConfidence = Number(value);
|
|
79
|
-
|
|
80
|
-
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
case "jevFilterNoulMargin": {
|
|
81
140
|
settings.jevFilterNoulMargin = Number(value);
|
|
82
|
-
|
|
83
|
-
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
case "jevFilterOverrideWindow": {
|
|
84
144
|
settings.jevFilterOverrideWindow = Number(value.replace(" turns", ""));
|
|
85
|
-
|
|
86
|
-
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
case "jevTurnGateEveryTurns": {
|
|
87
148
|
settings.jevTurnGateEveryTurns =
|
|
88
|
-
value === "Off" ? 0 : Number(value.
|
|
89
|
-
|
|
90
|
-
|
|
149
|
+
value === "Off" ? 0 : Number(value.replaceAll(/[^0-9]/gu, ""));
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
case "jevTurnGateNoulThreshold": {
|
|
91
153
|
settings.jevTurnGateNoulThreshold = Number(value);
|
|
92
|
-
|
|
93
|
-
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
case "jevModel": {
|
|
94
157
|
settings.jevModel = value.trim() || DEFAULT_JEV_MODEL;
|
|
95
|
-
|
|
96
|
-
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
case "jevTimeoutMs": {
|
|
97
161
|
settings.jevTimeoutMs = Number(value);
|
|
98
|
-
|
|
99
|
-
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
case "jevDigestMaxChars": {
|
|
100
165
|
settings.jevDigestMaxChars = Number(value);
|
|
101
|
-
|
|
102
|
-
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
case "jevPricePerMtok": {
|
|
103
169
|
settings.jevPricePerMtok = Number(value);
|
|
104
|
-
|
|
105
|
-
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
case "jevTransport": {
|
|
173
|
+
// SAFETY: value comes from the fixed jevTransport options of the settings list.
|
|
106
174
|
settings.jevTransport = value as AdvisorSettings["jevTransport"];
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
default: {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const mutateAdvisorSettings = (
|
|
184
|
+
settings: AdvisorSettings,
|
|
185
|
+
id: string,
|
|
186
|
+
value: string,
|
|
187
|
+
presets: ContextPreset[]
|
|
188
|
+
): void => {
|
|
189
|
+
if (applyCoreMutation(settings, id, value, presets)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (applyNumericMutation(settings, id, value)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (applyJevMutation(settings, id, value)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (BOOLEAN_SETTING_IDS.has(id)) {
|
|
199
|
+
// SAFETY: BOOLEAN_SETTING_IDS only contains AdvisorSettings boolean fields.
|
|
200
|
+
settings[id as BooleanSettingField] = value === "On";
|
|
114
201
|
}
|
|
115
202
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
SettingsList,
|
|
6
|
-
truncateToWidth,
|
|
7
|
-
} from "@earendil-works/pi-tui";
|
|
2
|
+
import { SettingsList, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { Component, Focusable } from "@earendil-works/pi-tui";
|
|
4
|
+
|
|
8
5
|
import {
|
|
9
6
|
rainbowGradient,
|
|
10
7
|
SIMPLE_MODE_GRADIENT_INTERVAL_MS,
|
|
11
8
|
} from "./settings-formatting.ts";
|
|
12
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
advisorModelWhitelistItem,
|
|
11
|
+
createSettingsItems,
|
|
12
|
+
} from "./settings-items.ts";
|
|
13
13
|
import { SettingsListAdapter } from "./settings-list-adapter.ts";
|
|
14
14
|
import { mutateAdvisorSettings } from "./settings-mutations.ts";
|
|
15
15
|
import type {
|
|
@@ -51,7 +51,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
51
51
|
label: String(configuredContext),
|
|
52
52
|
value: configuredContext,
|
|
53
53
|
},
|
|
54
|
-
].
|
|
54
|
+
].toSorted((a, b) => a.value - b.value);
|
|
55
55
|
if (this.settings.simpleMode) {
|
|
56
56
|
this.startSimpleModeGradient();
|
|
57
57
|
}
|
|
@@ -96,8 +96,16 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
96
96
|
}
|
|
97
97
|
return defaultLabel(text, selected);
|
|
98
98
|
};
|
|
99
|
+
const modelWhitelist = advisorModelWhitelistItem(
|
|
100
|
+
this.settings,
|
|
101
|
+
this.options.modelRefs,
|
|
102
|
+
this.options.keybindings,
|
|
103
|
+
this.options.theme,
|
|
104
|
+
this.options.tui
|
|
105
|
+
);
|
|
99
106
|
const items = createSettingsItems({
|
|
100
107
|
effortLevels: this.options.effortLevels,
|
|
108
|
+
modelWhitelist,
|
|
101
109
|
presets: this.presets,
|
|
102
110
|
settings: this.settings,
|
|
103
111
|
theme: this.options.theme,
|
|
@@ -147,12 +155,13 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
147
155
|
(this.options.onChange ?? this.options.onSave)?.({
|
|
148
156
|
...this.settings,
|
|
149
157
|
showUsageDetails: this.settings.showUsageDetails ?? true,
|
|
150
|
-
toolPolicies: { ...
|
|
158
|
+
toolPolicies: { ...this.settings.toolPolicies },
|
|
151
159
|
});
|
|
152
160
|
if (
|
|
153
161
|
id === "context" ||
|
|
154
162
|
id === "simpleMode" ||
|
|
155
163
|
id === "customRule" ||
|
|
164
|
+
id === "modelWhitelist" ||
|
|
156
165
|
id === "toolPolicies"
|
|
157
166
|
) {
|
|
158
167
|
this.settingsList = this.createSettingsList(id);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Input,
|
|
5
|
-
truncateToWidth,
|
|
6
|
-
} from "@earendil-works/pi-tui";
|
|
1
|
+
import { Input, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { Component, Focusable } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
7
4
|
import type { TextSettingSubmenuOptions } from "./types.ts";
|
|
8
5
|
|
|
9
6
|
/** Small inline editor used by SettingsList for the two free-form settings. */
|
package/src/ui/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { KeybindingsManager, TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
3
4
|
import type { GateFailureMode } from "../config/types.ts";
|
|
4
5
|
import type { GitContextLevel } from "../git.ts";
|
|
5
6
|
|
|
@@ -11,6 +12,7 @@ export interface SearchableModelSelectorOptions {
|
|
|
11
12
|
allOptions: string[];
|
|
12
13
|
currentOption?: string;
|
|
13
14
|
keybindings: KeybindingsManager;
|
|
15
|
+
multiSelect?: false;
|
|
14
16
|
onCancel: () => void;
|
|
15
17
|
onSelect: (value: string) => void;
|
|
16
18
|
theme: Theme;
|
|
@@ -18,6 +20,15 @@ export interface SearchableModelSelectorOptions {
|
|
|
18
20
|
tui: RenderRequester;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
export interface SearchableModelMultiSelectorOptions extends Omit<
|
|
24
|
+
SearchableModelSelectorOptions,
|
|
25
|
+
"currentOption" | "multiSelect" | "onSelect"
|
|
26
|
+
> {
|
|
27
|
+
currentOptions: string[];
|
|
28
|
+
multiSelect: true;
|
|
29
|
+
onSelect: (values: string[]) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export interface ManualAdvisorRequest {
|
|
22
33
|
/** The repository disclosure level selected for this consultation. */
|
|
23
34
|
gitContext: GitContextLevel;
|
|
@@ -82,6 +93,7 @@ export interface AdvisorSettings {
|
|
|
82
93
|
jevTurnGateNoulThreshold?: number;
|
|
83
94
|
loopThreshold?: number;
|
|
84
95
|
maxCallsPerSession?: number;
|
|
96
|
+
modelWhitelist?: string[];
|
|
85
97
|
outcomeLogging?: boolean;
|
|
86
98
|
planGate: boolean;
|
|
87
99
|
redactSecrets?: boolean;
|
|
@@ -100,6 +112,8 @@ export interface AdvisorSettings {
|
|
|
100
112
|
export interface AdvisorSettingsSelectorOptions {
|
|
101
113
|
effortLevels: string[];
|
|
102
114
|
initial: AdvisorSettings;
|
|
115
|
+
keybindings?: KeybindingsManager;
|
|
116
|
+
modelRefs?: string[];
|
|
103
117
|
onCancel: () => void;
|
|
104
118
|
onChange?: (settings: AdvisorSettings) => void;
|
|
105
119
|
/** @deprecated Use onChange; retained for extensions embedding this component. */
|
package/src/ui.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// biome-ignore lint/performance/noBarrelFile: this facade intentionally preserves the public UI module contract.
|
|
2
1
|
export { ManualAdvisorDialog } from "./ui/manual-dialog.ts";
|
|
3
2
|
export { SearchableModelSelector } from "./ui/model-selector.ts";
|
|
4
3
|
export { AdvisorSettingsSelector } from "./ui/settings-selector.ts";
|
package/src/usage.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Usage } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
|
|
3
|
+
import { isNumber, isRecordOf } from "./content-utils.ts";
|
|
4
|
+
import type { JsonValue } from "./content-utils.ts";
|
|
5
|
+
|
|
3
6
|
/** Normalized usage returned by an Advisor or Scout provider response. */
|
|
4
7
|
export interface AdvisorUsageSnapshot {
|
|
5
8
|
cacheRead?: number;
|
|
@@ -22,26 +25,22 @@ export interface AdvisorUsageTotals {
|
|
|
22
25
|
totalTokens?: number;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
const finite = (value:
|
|
26
|
-
|
|
27
|
-
? value
|
|
28
|
-
: undefined;
|
|
28
|
+
const finite = (value: JsonValue | undefined): number | undefined =>
|
|
29
|
+
isNumber(value) && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
29
30
|
|
|
30
31
|
const add = (left: number | undefined, right: number | undefined) =>
|
|
31
32
|
left === undefined || right === undefined ? (left ?? right) : left + right;
|
|
32
33
|
|
|
33
|
-
import { isRecord } from "./content-utils.ts";
|
|
34
|
-
|
|
35
34
|
const costFields = ["input", "output", "cacheRead", "cacheWrite", "total"];
|
|
36
35
|
|
|
37
36
|
/** Extracts provider-agnostic usage fields without trusting provider metadata. */
|
|
38
|
-
export const snapshotAdvisorUsage = (
|
|
39
|
-
usage:
|
|
37
|
+
export const snapshotAdvisorUsage = <Input>(
|
|
38
|
+
usage: Input
|
|
40
39
|
): AdvisorUsageSnapshot | undefined => {
|
|
41
|
-
if (!
|
|
40
|
+
if (!isRecordOf(usage)) {
|
|
42
41
|
return undefined;
|
|
43
42
|
}
|
|
44
|
-
const cost =
|
|
43
|
+
const cost = isRecordOf(usage.cost) ? usage.cost : undefined;
|
|
45
44
|
const snapshot = {
|
|
46
45
|
cacheRead: finite(usage.cacheRead),
|
|
47
46
|
cacheWrite: finite(usage.cacheWrite),
|
|
@@ -60,7 +59,7 @@ export const snapshotAdvisorUsage = (
|
|
|
60
59
|
};
|
|
61
60
|
|
|
62
61
|
/** Returns the reported provider cost, when the response includes one. */
|
|
63
|
-
export const advisorUsageCost = (usage:
|
|
62
|
+
export const advisorUsageCost = <Input>(usage: Input): number | undefined =>
|
|
64
63
|
snapshotAdvisorUsage(usage)?.cost;
|
|
65
64
|
|
|
66
65
|
/**
|
|
@@ -68,23 +67,21 @@ export const advisorUsageCost = (usage: unknown): number | undefined =>
|
|
|
68
67
|
* Missing fields become zero only at this Pi API boundary; absent usage remains
|
|
69
68
|
* undefined so an unavailable request is never presented as a zero-cost call.
|
|
70
69
|
*/
|
|
71
|
-
export const advisorUsageForPi = (usage:
|
|
70
|
+
export const advisorUsageForPi = <Input>(usage: Input): Usage | undefined => {
|
|
72
71
|
const snapshot = snapshotAdvisorUsage(usage);
|
|
73
|
-
if (!(snapshot &&
|
|
72
|
+
if (!(snapshot && isRecordOf(usage))) {
|
|
74
73
|
return undefined;
|
|
75
74
|
}
|
|
76
|
-
const cost =
|
|
75
|
+
const cost = isRecordOf(usage.cost) ? usage.cost : undefined;
|
|
77
76
|
const input = snapshot.input ?? 0;
|
|
78
77
|
const output = snapshot.output ?? 0;
|
|
79
78
|
const cacheRead = snapshot.cacheRead ?? 0;
|
|
80
79
|
const cacheWrite = snapshot.cacheWrite ?? 0;
|
|
81
80
|
const cacheWrite1h = finite(usage.cacheWrite1h);
|
|
82
81
|
const reasoning = finite(usage.reasoning);
|
|
83
|
-
|
|
82
|
+
const piUsage: Usage = {
|
|
84
83
|
cacheRead,
|
|
85
84
|
cacheWrite,
|
|
86
|
-
...(cacheWrite1h === undefined ? {} : { cacheWrite1h }),
|
|
87
|
-
...(reasoning === undefined ? {} : { reasoning }),
|
|
88
85
|
cost: {
|
|
89
86
|
cacheRead: finite(cost?.cacheRead) ?? 0,
|
|
90
87
|
cacheWrite: finite(cost?.cacheWrite) ?? 0,
|
|
@@ -97,6 +94,13 @@ export const advisorUsageForPi = (usage: unknown): Usage | undefined => {
|
|
|
97
94
|
totalTokens:
|
|
98
95
|
snapshot.totalTokens ?? input + output + cacheRead + cacheWrite,
|
|
99
96
|
};
|
|
97
|
+
if (cacheWrite1h !== undefined) {
|
|
98
|
+
piUsage.cacheWrite1h = cacheWrite1h;
|
|
99
|
+
}
|
|
100
|
+
if (reasoning !== undefined) {
|
|
101
|
+
piUsage.reasoning = reasoning;
|
|
102
|
+
}
|
|
103
|
+
return piUsage;
|
|
100
104
|
};
|
|
101
105
|
|
|
102
106
|
/** Creates empty totals without treating absent usage as zero usage. */
|
|
@@ -107,7 +111,10 @@ export const emptyAdvisorUsageTotals = (): AdvisorUsageTotals => ({
|
|
|
107
111
|
});
|
|
108
112
|
|
|
109
113
|
/** Adds one direct Advisor response to session-local usage totals. */
|
|
110
|
-
export const addAdvisorUsage = (
|
|
114
|
+
export const addAdvisorUsage = <Input>(
|
|
115
|
+
totals: AdvisorUsageTotals,
|
|
116
|
+
usage: Input
|
|
117
|
+
) => {
|
|
111
118
|
totals.calls += 1;
|
|
112
119
|
const snapshot = snapshotAdvisorUsage(usage);
|
|
113
120
|
if (!snapshot) {
|
|
@@ -165,7 +172,7 @@ const formatUsageFields = (usage: AdvisorUsageSnapshot): string | undefined => {
|
|
|
165
172
|
return tokens.join(" · ") || undefined;
|
|
166
173
|
};
|
|
167
174
|
|
|
168
|
-
export const formatAdvisorUsage = (usage:
|
|
175
|
+
export const formatAdvisorUsage = <Input>(usage: Input): string | undefined => {
|
|
169
176
|
const snapshot = snapshotAdvisorUsage(usage);
|
|
170
177
|
return snapshot ? formatUsageFields(snapshot) : undefined;
|
|
171
178
|
};
|