pi-advisor-flow 0.11.2 → 0.12.0
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 +12 -0
- package/README.md +4 -2
- package/dist/index.js +863 -238
- package/package.json +10 -10
- package/src/commands/settings-commands.ts +43 -0
- package/src/commands/settings-persistence.ts +9 -2
- package/src/config/types.ts +6 -1
- package/src/jev/client.ts +88 -22
- package/src/jev/decisions-client.ts +124 -0
- package/src/jev/decisions-response.ts +198 -0
- package/src/jev/key-store.ts +1 -1
- package/src/jev/ledger.ts +28 -6
- package/src/jev/openai-key-store.ts +159 -0
- package/src/jev/transport.ts +103 -25
- package/src/tools/jev-filter.ts +2 -2
- package/src/tools/jev-turn-gate.ts +2 -2
- package/src/ui/jev-setup-submenu-view.ts +96 -0
- package/src/ui/jev-setup-submenu.ts +253 -235
- package/src/ui/jev-setup-support.ts +173 -0
- package/src/ui/settings-items.ts +28 -20
- package/src/ui/settings-mutations.ts +0 -5
- package/src/ui/settings-selector.ts +29 -0
- package/src/ui/types.ts +32 -2
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { noul } from "@typesafe-ai/sdk";
|
|
3
|
+
|
|
4
|
+
import type { JevTransport } from "../config/types.ts";
|
|
5
|
+
import { jevClientFromCredentials } from "../jev/client.ts";
|
|
6
|
+
import { OPENAI_DECISIONS_MODEL } from "../jev/decisions-client.ts";
|
|
7
|
+
import {
|
|
8
|
+
clearKeyTypeSafeKey,
|
|
9
|
+
removeTypeSafeKeyFromAdvisorJson,
|
|
10
|
+
writeKeyTypeSafeKey,
|
|
11
|
+
} from "../jev/key-store.ts";
|
|
12
|
+
import {
|
|
13
|
+
clearOpenAIDecisionsKey,
|
|
14
|
+
writeOpenAIDecisionsKey,
|
|
15
|
+
} from "../jev/openai-key-store.ts";
|
|
16
|
+
import {
|
|
17
|
+
resolveJevTransport,
|
|
18
|
+
resolveJevTransportFor,
|
|
19
|
+
} from "../jev/transport.ts";
|
|
20
|
+
import type { JevCredentials, JevTransportKind } from "../jev/transport.ts";
|
|
21
|
+
import { redactSecrets } from "../redaction.ts";
|
|
22
|
+
import type {
|
|
23
|
+
JevSetupDeps,
|
|
24
|
+
JevSetupSelection,
|
|
25
|
+
RenderRequester,
|
|
26
|
+
} from "./types.ts";
|
|
27
|
+
|
|
28
|
+
export interface JevSetupSubmenuOptions {
|
|
29
|
+
afterSelection?: () => void;
|
|
30
|
+
currentTransport: JevTransport;
|
|
31
|
+
currentValue: string;
|
|
32
|
+
done: (selectedValue?: string) => void;
|
|
33
|
+
onSelection?: (selection: JevSetupSelection) => boolean;
|
|
34
|
+
setupDeps?: JevSetupDeps;
|
|
35
|
+
theme: Theme;
|
|
36
|
+
tui: RenderRequester;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type JevSetupOverrides = JevSetupDeps;
|
|
40
|
+
export type SetupMode =
|
|
41
|
+
| "menu"
|
|
42
|
+
| "key-entry"
|
|
43
|
+
| "resolving"
|
|
44
|
+
| "verifying"
|
|
45
|
+
| "clearing";
|
|
46
|
+
export type SetupAction =
|
|
47
|
+
| JevTransportKind
|
|
48
|
+
| "clear-stored-key"
|
|
49
|
+
| "disable"
|
|
50
|
+
| "done"
|
|
51
|
+
| "enter-key";
|
|
52
|
+
|
|
53
|
+
export interface JevSetupViewState {
|
|
54
|
+
canEnterKey: boolean;
|
|
55
|
+
credentials: JevCredentials | undefined;
|
|
56
|
+
filterEnabled: boolean;
|
|
57
|
+
mode: SetupMode;
|
|
58
|
+
notice: string | undefined;
|
|
59
|
+
selectedIndex: number;
|
|
60
|
+
selectedTransport: JevTransportKind | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const fireAndForget = async (
|
|
64
|
+
action: Promise<unknown>,
|
|
65
|
+
onError: (message: string) => void
|
|
66
|
+
): Promise<void> => {
|
|
67
|
+
try {
|
|
68
|
+
await action;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
onError(redactSecrets(message));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const providerName = (transport: JevTransportKind): string => {
|
|
76
|
+
if (transport === "typesafe") {
|
|
77
|
+
return "TypeSafe Jev";
|
|
78
|
+
}
|
|
79
|
+
if (transport === "openrouter") {
|
|
80
|
+
return "OpenRouter (Pi login)";
|
|
81
|
+
}
|
|
82
|
+
return `OpenAI Decisions (${OPENAI_DECISIONS_MODEL})`;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const transportLabel = (credentials: JevCredentials): string => {
|
|
86
|
+
if (credentials.transport === "openrouter") {
|
|
87
|
+
return "OpenRouter (reusing Pi login)";
|
|
88
|
+
}
|
|
89
|
+
if (credentials.transport === "openai-decisions") {
|
|
90
|
+
let source = "verified API key";
|
|
91
|
+
switch (credentials.source) {
|
|
92
|
+
case "openai-bun-secrets": {
|
|
93
|
+
source = "Bun.secrets";
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "openai-env": {
|
|
97
|
+
source = "OPENAI_API_KEY";
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "openai-file": {
|
|
101
|
+
source = "stored file, mode 0600";
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "openai-provider-credential": {
|
|
105
|
+
source = "Pi stored API key";
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
default: {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return `OpenAI Decisions (${OPENAI_DECISIONS_MODEL}; key: ${source})`;
|
|
113
|
+
}
|
|
114
|
+
switch (credentials.source) {
|
|
115
|
+
case "advisor-json": {
|
|
116
|
+
return "TypeSafe (key: advisor.json — plaintext, not recommended)";
|
|
117
|
+
}
|
|
118
|
+
case "bun-secrets": {
|
|
119
|
+
return "TypeSafe (key: Bun.secrets)";
|
|
120
|
+
}
|
|
121
|
+
case "file": {
|
|
122
|
+
return "TypeSafe (key: stored file, mode 0600)";
|
|
123
|
+
}
|
|
124
|
+
default: {
|
|
125
|
+
return "TypeSafe (key: TYPESAFE_API_KEY)";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const canClearCredential = (credentials: JevCredentials) =>
|
|
131
|
+
credentials.source === "bun-secrets" ||
|
|
132
|
+
credentials.source === "file" ||
|
|
133
|
+
credentials.source === "openai-bun-secrets" ||
|
|
134
|
+
credentials.source === "openai-file";
|
|
135
|
+
|
|
136
|
+
export const canReplaceWithEnteredKey = (credentials: JevCredentials) =>
|
|
137
|
+
credentials.transport === "typesafe" ||
|
|
138
|
+
(credentials.transport === "openai-decisions" &&
|
|
139
|
+
(credentials.source === "openai-bun-secrets" ||
|
|
140
|
+
credentials.source === "openai-file"));
|
|
141
|
+
|
|
142
|
+
export const defaultVerify = async (credentials: JevCredentials) => {
|
|
143
|
+
const client = jevClientFromCredentials(credentials);
|
|
144
|
+
try {
|
|
145
|
+
await client.ask(
|
|
146
|
+
{ purpose: "pi-advisor setup verification" },
|
|
147
|
+
{ verified: noul("Answer yes.") }
|
|
148
|
+
);
|
|
149
|
+
return { ok: true };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
message: error instanceof Error ? error.message : String(error),
|
|
153
|
+
ok: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const defaultSetupDeps: Required<JevSetupDeps> = {
|
|
159
|
+
clearStoredKey: (transport) =>
|
|
160
|
+
transport === "openai-decisions"
|
|
161
|
+
? clearOpenAIDecisionsKey()
|
|
162
|
+
: clearKeyTypeSafeKey(),
|
|
163
|
+
removePlaintextKey: removeTypeSafeKeyFromAdvisorJson,
|
|
164
|
+
resolveTransport: (transport) =>
|
|
165
|
+
transport ? resolveJevTransportFor(transport) : resolveJevTransport(),
|
|
166
|
+
verify: defaultVerify,
|
|
167
|
+
writeKey: (key, transport) =>
|
|
168
|
+
transport === "openai-decisions"
|
|
169
|
+
? writeOpenAIDecisionsKey(key)
|
|
170
|
+
: writeKeyTypeSafeKey(key),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export { consumePlaintextKeyWarning as consumeSetupPlaintextWarning } from "../jev/key-store.ts";
|
package/src/ui/settings-items.ts
CHANGED
|
@@ -33,6 +33,8 @@ import { FALLBACK_ADVISOR_MODEL_DISABLED } from "./types.ts";
|
|
|
33
33
|
import type {
|
|
34
34
|
AdvisorSettings,
|
|
35
35
|
ContextPreset,
|
|
36
|
+
JevSetupDeps,
|
|
37
|
+
JevSetupSelection,
|
|
36
38
|
RenderRequester,
|
|
37
39
|
} from "./types.ts";
|
|
38
40
|
|
|
@@ -42,6 +44,9 @@ export interface SettingsItemsOptions {
|
|
|
42
44
|
modelWhitelist: SettingItem;
|
|
43
45
|
presets: ContextPreset[];
|
|
44
46
|
settings: AdvisorSettings;
|
|
47
|
+
afterJevSetup?: () => void;
|
|
48
|
+
jevSetupDeps?: JevSetupDeps;
|
|
49
|
+
onJevSetup?: (selection: JevSetupSelection) => boolean;
|
|
45
50
|
theme: Theme;
|
|
46
51
|
tui: RenderRequester;
|
|
47
52
|
}
|
|
@@ -78,16 +83,28 @@ const scoutTimeoutItem = (settings: AdvisorSettings): SettingItem => {
|
|
|
78
83
|
const jevItems = (
|
|
79
84
|
settings: AdvisorSettings,
|
|
80
85
|
theme: Theme,
|
|
81
|
-
tui: RenderRequester
|
|
86
|
+
tui: RenderRequester,
|
|
87
|
+
jevSetupDeps?: JevSetupDeps,
|
|
88
|
+
onJevSetup?: (selection: JevSetupSelection) => boolean,
|
|
89
|
+
afterJevSetup?: () => void
|
|
82
90
|
): SettingItem[] => [
|
|
83
91
|
{
|
|
84
92
|
currentValue: settingValue(settings.jevFilterEnabled, false),
|
|
85
93
|
description:
|
|
86
|
-
"Screen low-stakes ask_advisor consultations with Jev; guided setup verifies credentials.",
|
|
94
|
+
"Screen low-stakes ask_advisor consultations with Jev/Decisions; guided setup verifies credentials.",
|
|
87
95
|
id: "jevFilter",
|
|
88
|
-
label: "Jev consultation filter",
|
|
96
|
+
label: "Jev/Decisions consultation filter",
|
|
89
97
|
submenu: (currentValue, done) =>
|
|
90
|
-
new JevSetupSubmenu({
|
|
98
|
+
new JevSetupSubmenu({
|
|
99
|
+
afterSelection: afterJevSetup,
|
|
100
|
+
currentTransport: settings.jevTransport ?? DEFAULT_JEV_TRANSPORT,
|
|
101
|
+
currentValue,
|
|
102
|
+
done,
|
|
103
|
+
onSelection: onJevSetup,
|
|
104
|
+
setupDeps: jevSetupDeps,
|
|
105
|
+
theme,
|
|
106
|
+
tui,
|
|
107
|
+
}),
|
|
91
108
|
},
|
|
92
109
|
{
|
|
93
110
|
currentValue: String(
|
|
@@ -170,7 +187,7 @@ const jevItems = (
|
|
|
170
187
|
settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK
|
|
171
188
|
),
|
|
172
189
|
description:
|
|
173
|
-
"
|
|
190
|
+
"TypeSafe/OpenRouter estimated input price per million tokens; ignored for OpenAI Decisions, whose cost is unavailable.",
|
|
174
191
|
id: "jevPricePerMtok",
|
|
175
192
|
label: "Jev price/Mtok",
|
|
176
193
|
values: numericValues(
|
|
@@ -187,7 +204,7 @@ const jevItems = (
|
|
|
187
204
|
description:
|
|
188
205
|
"Proactively consult the Advisor every N turns without a consultation (0 = off).",
|
|
189
206
|
id: "jevTurnGateEveryTurns",
|
|
190
|
-
label: "Jev turn gate",
|
|
207
|
+
label: "Jev/Decisions turn gate",
|
|
191
208
|
values: [
|
|
192
209
|
"Off",
|
|
193
210
|
"every 3 turns",
|
|
@@ -203,24 +220,12 @@ const jevItems = (
|
|
|
203
220
|
description:
|
|
204
221
|
"Jev confidence required before the turn gate interrupts with advice.",
|
|
205
222
|
id: "jevTurnGateNoulThreshold",
|
|
206
|
-
label: "Jev turn-gate threshold",
|
|
223
|
+
label: "Jev/Decisions turn-gate threshold",
|
|
207
224
|
values: numericValues(
|
|
208
225
|
settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
|
|
209
226
|
[0.6, 0.7, 0.8, 0.85, 0.9, 0.95]
|
|
210
227
|
),
|
|
211
228
|
},
|
|
212
|
-
{
|
|
213
|
-
currentValue: settings.jevTransport ?? DEFAULT_JEV_TRANSPORT,
|
|
214
|
-
description:
|
|
215
|
-
"How Jev calls travel: auto reuses an OpenRouter login when no TypeSafe key is set.",
|
|
216
|
-
id: "jevTransport",
|
|
217
|
-
label: "Jev transport",
|
|
218
|
-
values: withCurrentValue(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT, [
|
|
219
|
-
"auto",
|
|
220
|
-
"typesafe",
|
|
221
|
-
"openrouter",
|
|
222
|
-
]),
|
|
223
|
-
},
|
|
224
229
|
];
|
|
225
230
|
|
|
226
231
|
export const advisorFallbackModelItem = (
|
|
@@ -290,6 +295,9 @@ export const createSettingsItems = ({
|
|
|
290
295
|
modelWhitelist,
|
|
291
296
|
presets,
|
|
292
297
|
settings,
|
|
298
|
+
afterJevSetup,
|
|
299
|
+
jevSetupDeps,
|
|
300
|
+
onJevSetup,
|
|
293
301
|
theme,
|
|
294
302
|
tui,
|
|
295
303
|
}: SettingsItemsOptions): SettingItem[] => {
|
|
@@ -591,7 +599,7 @@ export const createSettingsItems = ({
|
|
|
591
599
|
settings.outcomeLogging,
|
|
592
600
|
false
|
|
593
601
|
),
|
|
594
|
-
...jevItems(settings, theme, tui)
|
|
602
|
+
...jevItems(settings, theme, tui, jevSetupDeps, onJevSetup, afterJevSetup)
|
|
595
603
|
);
|
|
596
604
|
return items;
|
|
597
605
|
};
|
|
@@ -183,11 +183,6 @@ const applyJevMutation = (
|
|
|
183
183
|
settings.jevPricePerMtok = Number(value);
|
|
184
184
|
return true;
|
|
185
185
|
}
|
|
186
|
-
case "jevTransport": {
|
|
187
|
-
// SAFETY: value comes from the fixed jevTransport options of the settings list.
|
|
188
|
-
settings.jevTransport = value as AdvisorSettings["jevTransport"];
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
186
|
default: {
|
|
192
187
|
return false;
|
|
193
188
|
}
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
AdvisorSettings,
|
|
18
18
|
AdvisorSettingsSelectorOptions,
|
|
19
19
|
ContextPreset,
|
|
20
|
+
JevSetupSelection,
|
|
20
21
|
} from "./types.ts";
|
|
21
22
|
|
|
22
23
|
export class AdvisorSettingsSelector implements Component, Focusable {
|
|
@@ -112,9 +113,15 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
112
113
|
this.options.tui
|
|
113
114
|
);
|
|
114
115
|
const items = createSettingsItems({
|
|
116
|
+
afterJevSetup: () => {
|
|
117
|
+
this.settingsList = this.createSettingsList("jevFilter");
|
|
118
|
+
this.options.tui.requestRender();
|
|
119
|
+
},
|
|
115
120
|
effortLevels: this.options.effortLevels,
|
|
116
121
|
fallbackModel,
|
|
122
|
+
jevSetupDeps: this.options.jevSetupDeps,
|
|
117
123
|
modelWhitelist,
|
|
124
|
+
onJevSetup: (selection) => this.applyJevSetup(selection),
|
|
118
125
|
presets: this.presets,
|
|
119
126
|
settings: this.settings,
|
|
120
127
|
theme: this.options.theme,
|
|
@@ -135,6 +142,28 @@ export class AdvisorSettingsSelector implements Component, Focusable {
|
|
|
135
142
|
return adapter;
|
|
136
143
|
}
|
|
137
144
|
|
|
145
|
+
private applyJevSetup(selection: JevSetupSelection): boolean {
|
|
146
|
+
const updated: AdvisorSettings = {
|
|
147
|
+
...this.settings,
|
|
148
|
+
jevFilterEnabled: selection.enabled,
|
|
149
|
+
jevTransport: selection.transport,
|
|
150
|
+
showUsageDetails: this.settings.showUsageDetails ?? true,
|
|
151
|
+
toolPolicies: { ...this.settings.toolPolicies },
|
|
152
|
+
};
|
|
153
|
+
try {
|
|
154
|
+
const result = this.options.onJevSetup
|
|
155
|
+
? this.options.onJevSetup(selection, updated)
|
|
156
|
+
: (this.options.onChange ?? this.options.onSave)?.(updated);
|
|
157
|
+
if (result === false) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
Object.assign(this.settings, updated);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
138
167
|
private startSimpleModeGradient(): void {
|
|
139
168
|
this.stopSimpleModeGradient();
|
|
140
169
|
this.simpleModeGradientStartedAt = Date.now();
|
package/src/ui/types.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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
|
+
import type { GateFailureMode, JevTransport } from "../config/types.ts";
|
|
5
5
|
import type { GitContextLevel } from "../git.ts";
|
|
6
|
+
import type { JevKeyStoreResult } from "../jev/key-store.ts";
|
|
7
|
+
import type { JevCredentials, JevTransportKind } from "../jev/transport.ts";
|
|
6
8
|
|
|
7
9
|
export interface RenderRequester {
|
|
8
10
|
requestRender: () => void;
|
|
@@ -93,7 +95,7 @@ export interface AdvisorSettings {
|
|
|
93
95
|
jevModel?: string;
|
|
94
96
|
jevPricePerMtok?: number;
|
|
95
97
|
jevTimeoutMs?: number;
|
|
96
|
-
jevTransport?: "auto" | "typesafe" | "openrouter";
|
|
98
|
+
jevTransport?: "auto" | "typesafe" | "openrouter" | "openai-decisions";
|
|
97
99
|
jevTurnGateEveryTurns?: number;
|
|
98
100
|
jevTurnGateNoulThreshold?: number;
|
|
99
101
|
loopThreshold?: number;
|
|
@@ -115,6 +117,29 @@ export interface AdvisorSettings {
|
|
|
115
117
|
untrackedContent?: boolean;
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
export interface JevSetupSelection {
|
|
121
|
+
enabled: boolean;
|
|
122
|
+
transport: JevTransport;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface JevSetupResult {
|
|
126
|
+
message?: string;
|
|
127
|
+
ok: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface JevSetupDeps {
|
|
131
|
+
clearStoredKey?: (transport: JevTransportKind) => Promise<JevKeyStoreResult>;
|
|
132
|
+
removePlaintextKey?: () => JevKeyStoreResult;
|
|
133
|
+
resolveTransport?: (
|
|
134
|
+
transport?: JevTransportKind
|
|
135
|
+
) => Promise<JevCredentials | undefined>;
|
|
136
|
+
verify?: (credentials: JevCredentials) => Promise<JevSetupResult>;
|
|
137
|
+
writeKey?: (
|
|
138
|
+
key: string,
|
|
139
|
+
transport: JevTransportKind
|
|
140
|
+
) => Promise<JevKeyStoreResult>;
|
|
141
|
+
}
|
|
142
|
+
|
|
118
143
|
export interface AdvisorSettingsSelectorOptions {
|
|
119
144
|
effortLevels: string[];
|
|
120
145
|
initial: AdvisorSettings;
|
|
@@ -125,6 +150,11 @@ export interface AdvisorSettingsSelectorOptions {
|
|
|
125
150
|
/** @deprecated Use onChange; retained for extensions embedding this component. */
|
|
126
151
|
onSave?: (settings: AdvisorSettings) => void;
|
|
127
152
|
presets: ContextPreset[];
|
|
153
|
+
jevSetupDeps?: JevSetupDeps;
|
|
154
|
+
onJevSetup?: (
|
|
155
|
+
selection: JevSetupSelection,
|
|
156
|
+
settings: AdvisorSettings
|
|
157
|
+
) => boolean;
|
|
128
158
|
theme: Theme;
|
|
129
159
|
tui: RenderRequester;
|
|
130
160
|
}
|