pi-requesty-discover 1.0.3 → 1.0.4
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 +4 -2
- package/src/index.ts +88 -36
- package/src/ui/requesty-status-loader.ts +38 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-requesty-discover",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Discover available Requesty models for the Pi Coding Agent",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
40
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
41
|
+
"@earendil-works/pi-tui": "*"
|
|
41
42
|
},
|
|
42
43
|
"pi": {
|
|
43
44
|
"extensions": [
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
]
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
49
|
+
"@earendil-works/pi-tui": "^0.80.3",
|
|
48
50
|
"@eslint/js": "^10.0.1",
|
|
49
51
|
"@types/node": "^26.1.0",
|
|
50
52
|
"@vitest/coverage-v8": "^4.1.9",
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BorderedLoader,
|
|
3
|
-
ExtensionAPI,
|
|
4
|
-
ExtensionCommandContext,
|
|
5
|
-
ProviderModelConfig,
|
|
6
|
-
} from '@earendil-works/pi-coding-agent'
|
|
1
|
+
import { ExtensionAPI, ExtensionCommandContext, ProviderModelConfig } from '@earendil-works/pi-coding-agent'
|
|
7
2
|
import { getEnv } from './env'
|
|
8
3
|
import { getRequestyConfig, updateModelsJson } from './models-json'
|
|
9
4
|
import { discoverModels } from './requesty-api'
|
|
10
5
|
import { checkModels, formatHealthSummary, writeHealthCheckLog } from './health-check'
|
|
6
|
+
import { RequestyStatusLoader } from './ui/requesty-status-loader.ts'
|
|
11
7
|
|
|
12
8
|
const COMMAND_NAME = 'requesty-discover'
|
|
13
9
|
const DRY_RUN_ARG = '--dry-run'
|
|
@@ -18,7 +14,16 @@ interface AutocompleteItem {
|
|
|
18
14
|
description: string
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
type NotificationLevel = 'info' | 'warning' | 'error'
|
|
17
|
+
export type NotificationLevel = 'info' | 'warning' | 'error'
|
|
18
|
+
|
|
19
|
+
export type Notifier = {
|
|
20
|
+
notify(message: string, level?: NotificationLevel): void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type StatusReporter = {
|
|
24
|
+
set(message: string): void
|
|
25
|
+
clear(): void
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
// noinspection JSUnusedGlobalSymbols
|
|
24
29
|
export default function (pi: ExtensionAPI) {
|
|
@@ -26,37 +31,38 @@ export default function (pi: ExtensionAPI) {
|
|
|
26
31
|
description: 'Dynamically discover Requesty models, run health checks, and update the local models.json.',
|
|
27
32
|
getArgumentCompletions,
|
|
28
33
|
handler: async (args, ctx) => {
|
|
29
|
-
await
|
|
34
|
+
await runWithStatusUi(ctx, 'Discovering models...', (status, notifier) => runCommand(args, status, notifier))
|
|
30
35
|
},
|
|
31
36
|
})
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
async function
|
|
39
|
+
async function runWithStatusUi<T>(
|
|
40
|
+
ctx: ExtensionCommandContext,
|
|
41
|
+
initialMessage: string,
|
|
42
|
+
fn: (status: StatusReporter, notifier: Notifier) => Promise<T>,
|
|
43
|
+
): Promise<T> {
|
|
44
|
+
if (ctx.mode !== 'tui') {
|
|
45
|
+
return fn(createNoopStatusReporter(), createNoopNotifier())
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
return ctx.ui.custom<T>((_tui, theme, _kb, done) => {
|
|
36
|
-
const loader = new
|
|
37
|
-
|
|
49
|
+
const loader = new RequestyStatusLoader(_tui, theme, initialMessage)
|
|
50
|
+
const notifier = createUiNotifier(ctx)
|
|
51
|
+
const status = createLoaderStatusReporter(loader)
|
|
52
|
+
void Promise.resolve()
|
|
53
|
+
.then(() => fn(status, notifier))
|
|
54
|
+
.then(done)
|
|
55
|
+
.catch(done)
|
|
38
56
|
return loader
|
|
39
57
|
})
|
|
40
58
|
}
|
|
41
59
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
value: DRY_RUN_ARG,
|
|
46
|
-
label: DRY_RUN_ARG,
|
|
47
|
-
description: 'Preview without writing into the new model.json file',
|
|
48
|
-
},
|
|
49
|
-
]
|
|
50
|
-
if (!prefix) return options
|
|
51
|
-
return options.filter(o => o.value.toLowerCase().startsWith(prefix.toLowerCase()))
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function runCommand(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
55
|
-
ctx.ui.setStatus(COMMAND_NAME, 'Discovering Requesty models...')
|
|
60
|
+
export async function runCommand(args: string, status: StatusReporter, notifier: Notifier): Promise<void> {
|
|
61
|
+
status.set('Discovering Requesty models...')
|
|
56
62
|
const parts = args.split(' ')
|
|
57
63
|
const dryRun = parts.includes(DRY_RUN_ARG)
|
|
58
64
|
if (dryRun) {
|
|
59
|
-
notify(
|
|
65
|
+
notifier.notify('running in dry mode, no changes will be done', undefined)
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
try {
|
|
@@ -71,10 +77,10 @@ async function runCommand(args: string, ctx: ExtensionCommandContext): Promise<v
|
|
|
71
77
|
let healthCheckSummary = ''
|
|
72
78
|
|
|
73
79
|
if (env.health_check_mode !== 'off') {
|
|
74
|
-
|
|
80
|
+
status.set(`Checking models 0/${models.length}...`)
|
|
75
81
|
const healthResults = await checkModels(provider, models, env.health_check_mode === 'full', {
|
|
76
82
|
onProgress: ({ completed, total }) => {
|
|
77
|
-
|
|
83
|
+
status.set(`Checking models ${completed}/${total}...`)
|
|
78
84
|
},
|
|
79
85
|
})
|
|
80
86
|
const sortedResults = healthResults.toSorted((a, b) => a.modelId.localeCompare(b.modelId))
|
|
@@ -99,20 +105,66 @@ async function runCommand(args: string, ctx: ExtensionCommandContext): Promise<v
|
|
|
99
105
|
const message = `Discovered ${models.length} Requesty model(s).\n${healthCheckSummary}${logNote}${writeNote}`
|
|
100
106
|
|
|
101
107
|
if (failed.length === 0) {
|
|
102
|
-
notify(
|
|
108
|
+
notifier.notify(message, 'info')
|
|
103
109
|
} else if (failed.length < models.length) {
|
|
104
|
-
notify(
|
|
110
|
+
notifier.notify(message, 'warning')
|
|
105
111
|
} else {
|
|
106
|
-
notify(
|
|
112
|
+
notifier.notify(message, 'error')
|
|
107
113
|
}
|
|
108
114
|
} catch (error) {
|
|
109
|
-
notify(
|
|
115
|
+
notifier.notify(`Discovery failed: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
|
110
116
|
} finally {
|
|
111
|
-
|
|
117
|
+
status.clear()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getArgumentCompletions(prefix: string): AutocompleteItem[] {
|
|
122
|
+
const options = [
|
|
123
|
+
{
|
|
124
|
+
value: DRY_RUN_ARG,
|
|
125
|
+
label: DRY_RUN_ARG,
|
|
126
|
+
description: 'Preview without writing into the new model.json file',
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
if (!prefix) return options
|
|
130
|
+
return options.filter(o => o.value.toLowerCase().startsWith(prefix.toLowerCase()))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function createNoopStatusReporter(): StatusReporter {
|
|
134
|
+
return {
|
|
135
|
+
set() {
|
|
136
|
+
// Status output is only rendered in TUI mode.
|
|
137
|
+
},
|
|
138
|
+
clear() {
|
|
139
|
+
// Status output is only rendered in TUI mode.
|
|
140
|
+
},
|
|
112
141
|
}
|
|
113
142
|
}
|
|
114
143
|
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
144
|
+
function createNoopNotifier(): Notifier {
|
|
145
|
+
return {
|
|
146
|
+
notify() {
|
|
147
|
+
// Notifications are only rendered in TUI mode.
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function createUiNotifier(ctx: ExtensionCommandContext): Notifier {
|
|
153
|
+
return {
|
|
154
|
+
notify(message: string, level?: NotificationLevel) {
|
|
155
|
+
const prefixedMessage = `${COMMAND_NAME}: ${message}`
|
|
156
|
+
ctx.ui.notify(prefixedMessage, level)
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createLoaderStatusReporter(loader: RequestyStatusLoader): StatusReporter {
|
|
162
|
+
return {
|
|
163
|
+
set(message: string) {
|
|
164
|
+
loader.setMessage(message)
|
|
165
|
+
},
|
|
166
|
+
clear() {
|
|
167
|
+
// Nothing to clear: the loader closes when ctx.ui.custom() resolves.
|
|
168
|
+
},
|
|
169
|
+
}
|
|
118
170
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DynamicBorder, Theme } from '@earendil-works/pi-coding-agent'
|
|
2
|
+
import { Container, Loader, Spacer, TUI } from '@earendil-works/pi-tui'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A loader that allows us to change the displayed message.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* this is essentially a reimplementation of the default BorderedLoader from pi-tui
|
|
9
|
+
* which does not offer a public api for changing its message.
|
|
10
|
+
*/
|
|
11
|
+
export class RequestyStatusLoader extends Container {
|
|
12
|
+
private readonly loader: Loader
|
|
13
|
+
|
|
14
|
+
constructor(tui: TUI, theme: Theme, message: string) {
|
|
15
|
+
super()
|
|
16
|
+
|
|
17
|
+
const borderColor = (s: string) => theme.fg('border', s)
|
|
18
|
+
this.loader = new Loader(
|
|
19
|
+
tui,
|
|
20
|
+
s => theme.fg('accent', s),
|
|
21
|
+
s => theme.fg('muted', s),
|
|
22
|
+
message,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
this.addChild(new DynamicBorder(borderColor))
|
|
26
|
+
this.addChild(this.loader)
|
|
27
|
+
this.addChild(new Spacer(1))
|
|
28
|
+
this.addChild(new DynamicBorder(borderColor))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setMessage(message: string): void {
|
|
32
|
+
this.loader.setMessage(message)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
dispose(): void {
|
|
36
|
+
this.loader.stop()
|
|
37
|
+
}
|
|
38
|
+
}
|