pi-requesty-discover 1.1.0 → 2.0.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/README.adoc +14 -5
- package/README.md +14 -5
- package/package.json +1 -1
- package/src/health-check.ts +9 -1
- package/src/index.ts +188 -84
- package/src/models-json.ts +26 -0
package/README.adoc
CHANGED
|
@@ -16,7 +16,8 @@ This is a fork of https://github.com/requestyai/pi-requesty[pi-requesty] with ad
|
|
|
16
16
|
|
|
17
17
|
* Use `REQUESTY_API_KEY` from the environment
|
|
18
18
|
* Health-check available models and write a log about them
|
|
19
|
-
*
|
|
19
|
+
* Brief information about what was found, including added/removed models, health summary
|
|
20
|
+
* Confirm before writing `models.json`, with a dry-run mode that only previews
|
|
20
21
|
|
|
21
22
|
== When and why use it
|
|
22
23
|
|
|
@@ -25,7 +26,7 @@ Use this extension if
|
|
|
25
26
|
- you are on Requesty.ai
|
|
26
27
|
- you are limited to certain models by a company policy (for instance, for data residency/protection reasons)
|
|
27
28
|
- you don't want to handcraft your own curated list of models constantly
|
|
28
|
-
- you want to health
|
|
29
|
+
- you want to health-check models and preview the diff before writing
|
|
29
30
|
|
|
30
31
|
== Installation
|
|
31
32
|
|
|
@@ -124,12 +125,20 @@ Inside Pi:
|
|
|
124
125
|
/requesty-discover
|
|
125
126
|
----
|
|
126
127
|
|
|
127
|
-
The command
|
|
128
|
-
|
|
128
|
+
The command:
|
|
129
|
+
|
|
130
|
+
- discovers Requesty models (and optional health checks)
|
|
131
|
+
- shows a summary notification (added/removed IDs, health summary)
|
|
132
|
+
- asks for confirmation before writing `~/.pi/agent/models.json`
|
|
133
|
+
- reports whether the file was updated
|
|
134
|
+
|
|
135
|
+
If there are no model ID changes, confirmation still offers a metadata refresh write, which might have changed as well.
|
|
136
|
+
|
|
137
|
+
After a successful write, run `/reload`.
|
|
129
138
|
|
|
130
139
|
[source,text]
|
|
131
140
|
----
|
|
132
141
|
/requesty-discover --dry-run
|
|
133
142
|
----
|
|
134
143
|
|
|
135
|
-
|
|
144
|
+
Same discovery and health checks, but never prompts to write and leaves `models.json` unchanged.
|
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ This is a fork of [pi-requesty](https://github.com/requestyai/pi-requesty) with
|
|
|
8
8
|
|
|
9
9
|
* Use `REQUESTY_API_KEY` from the environment
|
|
10
10
|
* Health-check available models and write a log about them
|
|
11
|
-
*
|
|
11
|
+
* Brief information about what was found, including added/removed models, health summary
|
|
12
|
+
* Confirm before writing `models.json`, with a dry-run mode that only previews
|
|
12
13
|
|
|
13
14
|
## When and why use it
|
|
14
15
|
|
|
@@ -17,7 +18,7 @@ Use this extension if
|
|
|
17
18
|
* you are on Requesty.ai
|
|
18
19
|
* you are limited to certain models by a company policy (for instance, for data residency/protection reasons)
|
|
19
20
|
* you don’t want to handcraft your own curated list of models constantly
|
|
20
|
-
* you want to health
|
|
21
|
+
* you want to health-check models and preview the diff before writing
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
@@ -91,11 +92,19 @@ Inside Pi:
|
|
|
91
92
|
/requesty-discover
|
|
92
93
|
```
|
|
93
94
|
|
|
94
|
-
The command
|
|
95
|
-
|
|
95
|
+
The command:
|
|
96
|
+
|
|
97
|
+
* discovers Requesty models (and optional health checks)
|
|
98
|
+
* shows a summary notification (added/removed IDs, health summary)
|
|
99
|
+
* asks for confirmation before writing `~/.pi/agent/models.json`
|
|
100
|
+
* reports whether the file was updated
|
|
101
|
+
|
|
102
|
+
If there are no model ID changes, confirmation still offers a metadata refresh write, which might have changed as well.
|
|
103
|
+
|
|
104
|
+
After a successful write, run `/reload`.
|
|
96
105
|
|
|
97
106
|
```text
|
|
98
107
|
/requesty-discover --dry-run
|
|
99
108
|
```
|
|
100
109
|
|
|
101
|
-
|
|
110
|
+
Same discovery and health checks, but never prompts to write and leaves `models.json` unchanged.
|
package/package.json
CHANGED
package/src/health-check.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ProviderModelConfig } from '@earendil-works/pi-coding-agent'
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { getEnv, type Env } from './env'
|
|
5
|
+
import { formatModelsDiffSummary, type ModelsDiff } from './models-json'
|
|
5
6
|
|
|
6
7
|
const HEALTH_CHECK_CONCURRENCY = 10
|
|
7
8
|
const HEALTH_CHECK_TIMEOUT_MS = 15_000
|
|
@@ -98,7 +99,12 @@ export function formatHealthSummary(results: HealthCheckResult[]): string {
|
|
|
98
99
|
return `Health check: ${passed.length} OK, ${failed.length} failed:\n${failedModels}\n`
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
export function writeHealthCheckLog(
|
|
102
|
+
export function writeHealthCheckLog(
|
|
103
|
+
provider: Provider,
|
|
104
|
+
results: HealthCheckResult[],
|
|
105
|
+
diff: ModelsDiff,
|
|
106
|
+
envConfig: Env = getEnv(),
|
|
107
|
+
): void {
|
|
102
108
|
const passed = results.filter(r => r.ok)
|
|
103
109
|
const failed = results.filter(r => !r.ok)
|
|
104
110
|
const lines = [
|
|
@@ -110,6 +116,8 @@ export function writeHealthCheckLog(provider: Provider, results: HealthCheckResu
|
|
|
110
116
|
`Passed: ${passed.length}`,
|
|
111
117
|
`Failed: ${failed.length}`,
|
|
112
118
|
'',
|
|
119
|
+
formatModelsDiffSummary(diff),
|
|
120
|
+
'',
|
|
113
121
|
]
|
|
114
122
|
|
|
115
123
|
if (failed.length === 0) {
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { ExtensionAPI, ExtensionCommandContext, ProviderModelConfig } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
import { getEnv } from './env'
|
|
3
|
-
import {
|
|
2
|
+
import { type Env, getEnv } from './env'
|
|
3
|
+
import {
|
|
4
|
+
diffModels,
|
|
5
|
+
formatModelsDiffSummary,
|
|
6
|
+
getRequestyConfig,
|
|
7
|
+
type ModelsDiff,
|
|
8
|
+
updateModelsJson,
|
|
9
|
+
} from './models-json'
|
|
4
10
|
import { discoverModels } from './requesty-api'
|
|
5
11
|
import { checkModels, formatHealthSummary, writeHealthCheckLog } from './health-check'
|
|
6
12
|
import { RequestyStatusLoader } from './ui/requesty-status-loader.ts'
|
|
@@ -20,102 +26,214 @@ export type Notifier = {
|
|
|
20
26
|
notify(message: string, level: NotificationLevel): void
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
export type Confirmer = {
|
|
30
|
+
confirm(title: string, message: string): Promise<boolean>
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
export type StatusReporter = {
|
|
24
34
|
set(message: string): void
|
|
25
|
-
clear(): void
|
|
26
35
|
}
|
|
27
36
|
|
|
37
|
+
type ModelsJsonData = ReturnType<typeof getRequestyConfig>['data']
|
|
38
|
+
|
|
39
|
+
type DiscoveryEvaluation = {
|
|
40
|
+
dryRun: boolean
|
|
41
|
+
modelCount: number
|
|
42
|
+
failedCount: number
|
|
43
|
+
passing: ProviderModelConfig[]
|
|
44
|
+
diff: ModelsDiff
|
|
45
|
+
healthCheckSummary: string
|
|
46
|
+
logNote: string
|
|
47
|
+
data: ModelsJsonData
|
|
48
|
+
env: Env
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type EvaluationResult =
|
|
52
|
+
| {
|
|
53
|
+
ok: true
|
|
54
|
+
value: DiscoveryEvaluation
|
|
55
|
+
}
|
|
56
|
+
| { ok: false; error: unknown }
|
|
57
|
+
|
|
28
58
|
// noinspection JSUnusedGlobalSymbols
|
|
29
59
|
export default function (pi: ExtensionAPI) {
|
|
30
60
|
pi.registerCommand(COMMAND_NAME, {
|
|
31
61
|
description: 'Dynamically discover Requesty models, run health checks, and update the local models.json.',
|
|
32
62
|
getArgumentCompletions,
|
|
33
63
|
handler: async (args, ctx) => {
|
|
34
|
-
await
|
|
64
|
+
await runDiscoveryWorkflow(ctx, args)
|
|
35
65
|
},
|
|
36
66
|
})
|
|
37
67
|
}
|
|
38
68
|
|
|
69
|
+
export async function runDiscoveryWorkflow(ctx: ExtensionCommandContext, args: string) {
|
|
70
|
+
// Interactive TUI command; no print/json/rpc path.
|
|
71
|
+
if (ctx.mode !== 'tui') {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const notifier = createUiNotifier(ctx)
|
|
76
|
+
const confirmer = createUiConfirmer(ctx)
|
|
77
|
+
|
|
78
|
+
// Phase A: progress UI only. Loader must close before confirm (Phase B).
|
|
79
|
+
// Errors are wrapped because ctx.ui.custom resolves via done() and does not reject.
|
|
80
|
+
const evaluationResult: EvaluationResult = await runWithStatusUi(ctx, 'Discovering models...', async status => {
|
|
81
|
+
try {
|
|
82
|
+
const evaluation = await evaluateDiscovery(args, status)
|
|
83
|
+
return { ok: true, value: evaluation }
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return { ok: false, error }
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if (!evaluationResult.ok) {
|
|
90
|
+
notifier.notify(formatDiscoveryFailure(evaluationResult.error), 'error')
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Phases B + C: decide, optional write, final notify — outside the loader.
|
|
95
|
+
await finalizeDiscovery(evaluationResult.value, confirmer, notifier)
|
|
96
|
+
}
|
|
97
|
+
|
|
39
98
|
async function runWithStatusUi<T>(
|
|
40
99
|
ctx: ExtensionCommandContext,
|
|
41
100
|
initialMessage: string,
|
|
42
|
-
fn: (status: StatusReporter
|
|
101
|
+
fn: (status: StatusReporter) => Promise<T>,
|
|
43
102
|
): Promise<T> {
|
|
44
|
-
if (ctx.mode !== 'tui') {
|
|
45
|
-
return fn(createNoopStatusReporter(), createNoopNotifier())
|
|
46
|
-
}
|
|
47
|
-
|
|
48
103
|
return ctx.ui.custom<T>((_tui, theme, _kb, done) => {
|
|
49
104
|
const loader = new RequestyStatusLoader(_tui, theme, initialMessage)
|
|
50
|
-
const notifier = createUiNotifier(ctx)
|
|
51
105
|
const status = createLoaderStatusReporter(loader)
|
|
52
106
|
void Promise.resolve()
|
|
53
|
-
.then(() => fn(status
|
|
107
|
+
.then(() => fn(status))
|
|
54
108
|
.then(done)
|
|
55
109
|
.catch(done)
|
|
56
110
|
return loader
|
|
57
111
|
})
|
|
58
112
|
}
|
|
59
113
|
|
|
60
|
-
|
|
114
|
+
function formatDiscoveryFailure(error: unknown): string {
|
|
115
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
116
|
+
return `Discovery failed: ${detail}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function evaluateDiscovery(args: string, status: StatusReporter): Promise<DiscoveryEvaluation> {
|
|
61
120
|
status.set('Discovering Requesty models...')
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
121
|
+
const dryRun = args.split(' ').includes(DRY_RUN_ARG)
|
|
122
|
+
|
|
123
|
+
const env = getEnv()
|
|
124
|
+
const { data, provider, existingModelIds } = getRequestyConfig(env)
|
|
125
|
+
const models = await discoverModels(provider)
|
|
126
|
+
const modelsMap = new Map(models.map(m => [m.id, m]))
|
|
127
|
+
|
|
128
|
+
let diff: ModelsDiff
|
|
129
|
+
let failedCount = 0
|
|
130
|
+
let passing: ProviderModelConfig[]
|
|
131
|
+
let logNote = ''
|
|
132
|
+
let healthCheckSummary = ''
|
|
133
|
+
|
|
134
|
+
if (env.health_check_mode !== 'off') {
|
|
135
|
+
status.set(`Checking models 0/${models.length}...`)
|
|
136
|
+
const healthResults = await checkModels(provider, models, env.health_check_mode === 'full', {
|
|
137
|
+
onProgress: ({ completed, total }) => {
|
|
138
|
+
status.set(`Checking models ${completed}/${total}...`)
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
const sortedResults = healthResults.toSorted((a, b) => a.modelId.localeCompare(b.modelId))
|
|
142
|
+
failedCount = sortedResults.filter(r => !r.ok).length
|
|
143
|
+
passing = sortedResults.flatMap(r => {
|
|
144
|
+
const model = modelsMap.get(r.modelId)
|
|
145
|
+
return r.ok && model ? [model] : []
|
|
146
|
+
})
|
|
147
|
+
diff = diffModels(existingModelIds, passing)
|
|
148
|
+
healthCheckSummary = formatHealthSummary(sortedResults)
|
|
149
|
+
writeHealthCheckLog(provider, sortedResults, diff, env)
|
|
150
|
+
logNote = `Full health check log: ${env.health_check_log_path}\n`
|
|
151
|
+
} else {
|
|
152
|
+
passing = models
|
|
153
|
+
diff = diffModels(existingModelIds, passing)
|
|
66
154
|
}
|
|
67
155
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
status.set(`Checking models 0/${models.length}...`)
|
|
81
|
-
const healthResults = await checkModels(provider, models, env.health_check_mode === 'full', {
|
|
82
|
-
onProgress: ({ completed, total }) => {
|
|
83
|
-
status.set(`Checking models ${completed}/${total}...`)
|
|
84
|
-
},
|
|
85
|
-
})
|
|
86
|
-
const sortedResults = healthResults.toSorted((a, b) => a.modelId.localeCompare(b.modelId))
|
|
87
|
-
failed = sortedResults.filter(r => !r.ok)
|
|
88
|
-
passing = sortedResults.flatMap(r => {
|
|
89
|
-
const model = modelsMap.get(r.modelId)
|
|
90
|
-
return r.ok && model ? [model] : []
|
|
91
|
-
})
|
|
92
|
-
healthCheckSummary = formatHealthSummary(sortedResults)
|
|
93
|
-
writeHealthCheckLog(provider, sortedResults, env)
|
|
94
|
-
logNote = `Full health check log: ${env.health_check_log_path}\n`
|
|
95
|
-
} else {
|
|
96
|
-
passing = models
|
|
97
|
-
}
|
|
156
|
+
return {
|
|
157
|
+
dryRun,
|
|
158
|
+
modelCount: models.length,
|
|
159
|
+
failedCount,
|
|
160
|
+
passing,
|
|
161
|
+
diff,
|
|
162
|
+
healthCheckSummary,
|
|
163
|
+
logNote,
|
|
164
|
+
data,
|
|
165
|
+
env,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
98
168
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
169
|
+
async function finalizeDiscovery(
|
|
170
|
+
evaluation: DiscoveryEvaluation,
|
|
171
|
+
confirmer: Confirmer,
|
|
172
|
+
notifier: Notifier,
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
const level = notificationLevel(evaluation)
|
|
175
|
+
const summary = buildDiscoverySummary(evaluation)
|
|
176
|
+
|
|
177
|
+
// Always surface the discovery result first (toast styling), then decide.
|
|
178
|
+
if (evaluation.dryRun) {
|
|
179
|
+
notifier.notify(
|
|
180
|
+
`${summary}
|
|
181
|
+
Dry run: left models.json unchanged.`,
|
|
182
|
+
level,
|
|
183
|
+
)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (evaluation.passing.length === 0) {
|
|
188
|
+
notifier.notify(
|
|
189
|
+
`${summary}
|
|
190
|
+
Left models.json unchanged.`,
|
|
191
|
+
level,
|
|
192
|
+
)
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
notifier.notify(summary, level)
|
|
197
|
+
const { title, message } = buildConfirmPrompt(evaluation)
|
|
198
|
+
const shouldUpdate = await confirmer.confirm(title, message)
|
|
199
|
+
if (shouldUpdate) {
|
|
200
|
+
updateModelsJson(evaluation.data, evaluation.passing, evaluation.env)
|
|
201
|
+
notifier.notify('Updated models.json. Run /reload to use the changes.', 'info')
|
|
202
|
+
return
|
|
203
|
+
}
|
|
103
204
|
|
|
104
|
-
|
|
105
|
-
|
|
205
|
+
notifier.notify('Left models.json unchanged.', 'info')
|
|
206
|
+
}
|
|
106
207
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
208
|
+
function buildDiscoverySummary(evaluation: DiscoveryEvaluation): string {
|
|
209
|
+
return [
|
|
210
|
+
`Discovered ${evaluation.modelCount} Requesty model(s).`,
|
|
211
|
+
evaluation.healthCheckSummary.trimEnd(),
|
|
212
|
+
formatModelsDiffSummary(evaluation.diff),
|
|
213
|
+
evaluation.logNote.trimEnd(),
|
|
214
|
+
]
|
|
215
|
+
.filter(part => part.length > 0)
|
|
216
|
+
.join('\n')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildConfirmPrompt(evaluation: DiscoveryEvaluation): { title: string; message: string } {
|
|
220
|
+
const hasIdChanges = evaluation.diff.added.length > 0 || evaluation.diff.removed.length > 0
|
|
221
|
+
if (hasIdChanges) {
|
|
222
|
+
return {
|
|
223
|
+
title: `Write ${evaluation.passing.length} model(s)?`,
|
|
224
|
+
message: 'Update models.json with the discovery result above.',
|
|
113
225
|
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
notifier.notify(`Discovery failed: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
|
116
|
-
} finally {
|
|
117
|
-
status.clear()
|
|
118
226
|
}
|
|
227
|
+
return {
|
|
228
|
+
title: 'Refresh models.json?',
|
|
229
|
+
message: 'No model ID changes. Rewrite the file to refresh metadata anyway?',
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function notificationLevel(evaluation: DiscoveryEvaluation): NotificationLevel {
|
|
234
|
+
if (evaluation.failedCount === 0) return 'info'
|
|
235
|
+
if (evaluation.failedCount < evaluation.modelCount) return 'warning'
|
|
236
|
+
return 'error'
|
|
119
237
|
}
|
|
120
238
|
|
|
121
239
|
function getArgumentCompletions(prefix: string): AutocompleteItem[] {
|
|
@@ -123,37 +241,26 @@ function getArgumentCompletions(prefix: string): AutocompleteItem[] {
|
|
|
123
241
|
{
|
|
124
242
|
value: DRY_RUN_ARG,
|
|
125
243
|
label: DRY_RUN_ARG,
|
|
126
|
-
description: 'Preview without
|
|
244
|
+
description: 'Preview discovery without offering to write models.json',
|
|
127
245
|
},
|
|
128
246
|
]
|
|
129
247
|
if (!prefix) return options
|
|
130
248
|
return options.filter(o => o.value.toLowerCase().startsWith(prefix.toLowerCase()))
|
|
131
249
|
}
|
|
132
250
|
|
|
133
|
-
function
|
|
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
|
-
},
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function createNoopNotifier(): Notifier {
|
|
251
|
+
function createUiNotifier(ctx: ExtensionCommandContext): Notifier {
|
|
145
252
|
return {
|
|
146
|
-
notify() {
|
|
147
|
-
|
|
253
|
+
notify(message: string, level?: NotificationLevel) {
|
|
254
|
+
const prefixedMessage = `${COMMAND_NAME}: ${message}`
|
|
255
|
+
ctx.ui.notify(prefixedMessage, level)
|
|
148
256
|
},
|
|
149
257
|
}
|
|
150
258
|
}
|
|
151
259
|
|
|
152
|
-
function
|
|
260
|
+
function createUiConfirmer(ctx: ExtensionCommandContext): Confirmer {
|
|
153
261
|
return {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
ctx.ui.notify(prefixedMessage, level)
|
|
262
|
+
confirm(title, message) {
|
|
263
|
+
return ctx.ui.confirm(title, message)
|
|
157
264
|
},
|
|
158
265
|
}
|
|
159
266
|
}
|
|
@@ -163,8 +270,5 @@ function createLoaderStatusReporter(loader: RequestyStatusLoader): StatusReporte
|
|
|
163
270
|
set(message: string) {
|
|
164
271
|
loader.setMessage(message)
|
|
165
272
|
},
|
|
166
|
-
clear() {
|
|
167
|
-
// Nothing to clear: the loader closes when ctx.ui.custom() resolves.
|
|
168
|
-
},
|
|
169
273
|
}
|
|
170
274
|
}
|
package/src/models-json.ts
CHANGED
|
@@ -12,6 +12,7 @@ const ProviderSchema = z
|
|
|
12
12
|
name: z.string().optional(),
|
|
13
13
|
baseUrl: z.string().optional(),
|
|
14
14
|
apiKey: z.string().optional(),
|
|
15
|
+
models: z.array(z.object({ id: z.string() }).catchall(z.unknown())).optional(),
|
|
15
16
|
})
|
|
16
17
|
.catchall(z.unknown())
|
|
17
18
|
|
|
@@ -33,6 +34,12 @@ type RequestyProvider = ModelsJsonProvider & {
|
|
|
33
34
|
type RequestyConfig = {
|
|
34
35
|
data: ModelsJson
|
|
35
36
|
provider: RequestyProvider
|
|
37
|
+
existingModelIds: string[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ModelsDiff = {
|
|
41
|
+
added: string[]
|
|
42
|
+
removed: string[]
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
export function getRequestyConfig(envConfig: Env = getEnv()): RequestyConfig {
|
|
@@ -46,6 +53,7 @@ export function getRequestyConfig(envConfig: Env = getEnv()): RequestyConfig {
|
|
|
46
53
|
const apiKey = envConfig.requesty_api_key
|
|
47
54
|
return {
|
|
48
55
|
data,
|
|
56
|
+
existingModelIds: (provider.models ?? []).map(m => m.id),
|
|
49
57
|
provider: {
|
|
50
58
|
...provider,
|
|
51
59
|
name: nonEmptyString(provider.name) ?? DEFAULT_NAME,
|
|
@@ -55,6 +63,24 @@ export function getRequestyConfig(envConfig: Env = getEnv()): RequestyConfig {
|
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
export function diffModels(previousIds: string[], nextModels: ProviderModelConfig[]): ModelsDiff {
|
|
67
|
+
const previous = new Set(previousIds)
|
|
68
|
+
const next = new Set(nextModels.map(m => m.id))
|
|
69
|
+
return {
|
|
70
|
+
added: [...next].filter(id => !previous.has(id)).toSorted(),
|
|
71
|
+
removed: [...previous].filter(id => !next.has(id)).toSorted(),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function formatModelsDiffSummary(diff: ModelsDiff): string {
|
|
76
|
+
return [
|
|
77
|
+
diff.added.length === 0 ? 'No added models.' : 'Added models:',
|
|
78
|
+
...diff.added.map(id => `- ${id}`),
|
|
79
|
+
diff.removed.length === 0 ? 'No removed models.' : 'Removed models:',
|
|
80
|
+
...diff.removed.map(id => `- ${id}`),
|
|
81
|
+
].join('\n')
|
|
82
|
+
}
|
|
83
|
+
|
|
58
84
|
export function updateModelsJson(data: ModelsJson, models: ProviderModelConfig[], envConfig: Env = getEnv()): void {
|
|
59
85
|
const provider = data.providers[envConfig.provider_id]
|
|
60
86
|
data.providers[envConfig.provider_id] = {
|