pi-requesty-discover 1.0.2 → 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/README.adoc CHANGED
@@ -8,6 +8,8 @@ endif::[]
8
8
 
9
9
  = pi-requesty-discover
10
10
 
11
+ image:https://img.shields.io/npm/v/pi-requesty-discover.svg[npm version, link=https://www.npmjs.com/package/pi-requesty-discover]
12
+
11
13
  This extension helps you discover currently available models in requesty.ai for your Pi Coding Agent.
12
14
 
13
15
  This is a fork of https://github.com/requestyai/pi-requesty[pi-requesty] with additional features:
@@ -25,10 +27,13 @@ Use this extension if
25
27
  - you don't want to handcraft your own curated list of models constantly
26
28
  - you want to health check the list to complain about non-working models via a dry-run
27
29
 
28
- == Recommended Installation from GitHub repo
30
+ == Installation
29
31
 
30
32
  [source,bash]
31
33
  ----
34
+ # NPM
35
+ pi install npm:pi-requesty-discover
36
+ # GitHub
32
37
  pi install git:github.com/enolive/pi-requesty-discover
33
38
  ----
34
39
 
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # pi-requesty-discover
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/pi-requesty-discover.svg)](https://www.npmjs.com/package/pi-requesty-discover)
4
+
3
5
  This extension helps you discover currently available models in requesty.ai for your Pi Coding Agent.
4
6
 
5
7
  This is a fork of [pi-requesty](https://github.com/requestyai/pi-requesty) with additional features:
@@ -17,9 +19,12 @@ Use this extension if
17
19
  * you don’t want to handcraft your own curated list of models constantly
18
20
  * you want to health check the list to complain about non-working models via a dry-run
19
21
 
20
- ## Recommended Installation from GitHub repo
22
+ ## Installation
21
23
 
22
24
  ```bash
25
+ # NPM
26
+ pi install npm:pi-requesty-discover
27
+ # GitHub
23
28
  pi install git:github.com/enolive/pi-requesty-discover
24
29
  ```
25
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-requesty-discover",
3
- "version": "1.0.2",
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 withLoader(ctx, 'Discovering models...', () => runCommand(args, ctx))
34
+ await runWithStatusUi(ctx, 'Discovering models...', (status, notifier) => runCommand(args, status, notifier))
30
35
  },
31
36
  })
32
37
  }
33
38
 
34
- async function withLoader<T>(ctx: ExtensionCommandContext, message: string, fn: () => Promise<T>): Promise<T> {
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 BorderedLoader(_tui, theme, message, { cancellable: false })
37
- fn().then(done).catch(done)
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 getArgumentCompletions(prefix: string): AutocompleteItem[] {
43
- const options = [
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(ctx, 'running in dry mode, no changes will be done')
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
- ctx.ui.setStatus(COMMAND_NAME, `Checking models 0/${models.length}...`)
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
- ctx.ui.setStatus(COMMAND_NAME, `Checking models ${completed}/${total}...`)
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(ctx, message, 'info')
108
+ notifier.notify(message, 'info')
103
109
  } else if (failed.length < models.length) {
104
- notify(ctx, message, 'warning')
110
+ notifier.notify(message, 'warning')
105
111
  } else {
106
- notify(ctx, message, 'error')
112
+ notifier.notify(message, 'error')
107
113
  }
108
114
  } catch (error) {
109
- notify(ctx, `Discovery failed: ${error instanceof Error ? error.message : String(error)}`, 'error')
115
+ notifier.notify(`Discovery failed: ${error instanceof Error ? error.message : String(error)}`, 'error')
110
116
  } finally {
111
- ctx.ui.setStatus(COMMAND_NAME, undefined)
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 notify(ctx: ExtensionCommandContext, message: string, level?: NotificationLevel): void {
116
- const prefixedMessage = `${COMMAND_NAME}: ${message}`
117
- ctx.ui.notify(prefixedMessage, level)
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
+ }