pi-requesty-discover 2.1.1 → 3.0.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/README.adoc +46 -35
- package/README.md +42 -19
- package/docs/DECISIONS.adoc +23 -29
- package/package.json +4 -2
- package/src/discovery.ts +222 -0
- package/src/env.ts +2 -22
- package/src/health-check.ts +63 -34
- package/src/index.ts +106 -261
- package/src/models-json.ts +32 -11
- package/src/settings.ts +47 -0
- package/src/ui/requesty-status-loader.ts +1 -1
- package/src/utils.ts +24 -0
package/src/models-json.ts
CHANGED
|
@@ -2,16 +2,21 @@ import type { ProviderConfig, ProviderModelConfig } from '@earendil-works/pi-cod
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { z } from 'zod'
|
|
5
|
-
import {
|
|
5
|
+
import { type Env, getEnv } from './env'
|
|
6
|
+
import type { DiscoverySettings } from './settings'
|
|
6
7
|
|
|
7
8
|
const DEFAULT_BASE_URL = 'https://router.requesty.ai/v1'
|
|
8
9
|
const DEFAULT_NAME = 'Requesty'
|
|
9
10
|
|
|
11
|
+
/** Resolves the API key for a provider through Pi's model registry. */
|
|
12
|
+
export type GetApiKey = (providerId: string) => Promise<string | undefined>
|
|
13
|
+
|
|
10
14
|
const ProviderSchema = z
|
|
11
15
|
.object({
|
|
12
16
|
name: z.string().optional(),
|
|
13
17
|
baseUrl: z.string().optional(),
|
|
14
18
|
apiKey: z.string().optional(),
|
|
19
|
+
api: z.string().optional(),
|
|
15
20
|
models: z.array(z.object({ id: z.string() }).catchall(z.unknown())).optional(),
|
|
16
21
|
})
|
|
17
22
|
.catchall(z.unknown())
|
|
@@ -22,7 +27,7 @@ const ModelsJsonSchema = z
|
|
|
22
27
|
})
|
|
23
28
|
.catchall(z.unknown())
|
|
24
29
|
|
|
25
|
-
type ModelsJson = z.infer<typeof ModelsJsonSchema>
|
|
30
|
+
export type ModelsJson = z.infer<typeof ModelsJsonSchema>
|
|
26
31
|
type ModelsJsonProvider = z.infer<typeof ProviderSchema>
|
|
27
32
|
|
|
28
33
|
export type RequestyProvider = ModelsJsonProvider & {
|
|
@@ -42,15 +47,22 @@ export type ModelsDiff = {
|
|
|
42
47
|
removed: string[]
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
export function getRequestyConfig(
|
|
50
|
+
export async function getRequestyConfig(
|
|
51
|
+
getApiKey: GetApiKey,
|
|
52
|
+
settings: DiscoverySettings,
|
|
53
|
+
envConfig: Env = getEnv(),
|
|
54
|
+
): Promise<RequestyConfig> {
|
|
46
55
|
const data = readModelsJson(envConfig)
|
|
47
|
-
const provider = data.providers[
|
|
56
|
+
const provider = data.providers[settings.providerId]
|
|
48
57
|
|
|
49
58
|
if (!provider) {
|
|
50
|
-
throw new Error(`${envConfig.models_json_path} does not define providers.${
|
|
59
|
+
throw new Error(`${envConfig.models_json_path} does not define providers.${settings.providerId}`)
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
const apiKey =
|
|
62
|
+
const apiKey = await getApiKey(settings.providerId)
|
|
63
|
+
if (!apiKey) {
|
|
64
|
+
throw new Error(`No API key found for provider ${settings.providerId}`)
|
|
65
|
+
}
|
|
54
66
|
return {
|
|
55
67
|
data,
|
|
56
68
|
existingModelIds: (provider.models ?? []).map(m => m.id),
|
|
@@ -81,11 +93,20 @@ export function formatModelsDiffSummary(diff: ModelsDiff): string {
|
|
|
81
93
|
].join('\n')
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
export function updateModelsJson(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
export function updateModelsJson(
|
|
97
|
+
data: ModelsJson,
|
|
98
|
+
models: ProviderModelConfig[],
|
|
99
|
+
settings: DiscoverySettings,
|
|
100
|
+
envConfig = getEnv(),
|
|
101
|
+
): void {
|
|
102
|
+
const provider = data.providers[settings.providerId]
|
|
103
|
+
const { name, baseUrl, api, apiKey, models: _existingModels, ...passthrough } = provider
|
|
104
|
+
data.providers[settings.providerId] = {
|
|
105
|
+
name,
|
|
106
|
+
baseUrl,
|
|
107
|
+
api,
|
|
108
|
+
apiKey,
|
|
109
|
+
...passthrough,
|
|
89
110
|
models: models.map(model => ({
|
|
90
111
|
id: model.id,
|
|
91
112
|
name: model.name,
|
package/src/settings.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { parse as JSON5Parse } from 'json5'
|
|
3
|
+
import { prettifyError, z } from 'zod'
|
|
4
|
+
import { type Env } from './env'
|
|
5
|
+
import { formatErrorMessage } from './utils'
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_PROVIDER_ID = 'requesty-export'
|
|
8
|
+
|
|
9
|
+
const HealthCheckModeSchema = z.enum(['off', 'basic', 'full']).default('full')
|
|
10
|
+
|
|
11
|
+
const DiscoverySettingsSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
bannedModels: z.array(z.string().min(1)).default([]),
|
|
14
|
+
healthCheckMode: HealthCheckModeSchema,
|
|
15
|
+
providerId: z.string().default(DEFAULT_PROVIDER_ID),
|
|
16
|
+
})
|
|
17
|
+
.strip()
|
|
18
|
+
|
|
19
|
+
export type DiscoverySettings = z.infer<typeof DiscoverySettingsSchema>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reads the discovery settings file (JSON5, so it supports comments).
|
|
23
|
+
* Returns defaults when the file does not exist; throws when it exists but is invalid.
|
|
24
|
+
*/
|
|
25
|
+
export function readDiscoverySettings(envConfig: Env): DiscoverySettings {
|
|
26
|
+
if (!fs.existsSync(envConfig.settings_path)) {
|
|
27
|
+
return {
|
|
28
|
+
bannedModels: [],
|
|
29
|
+
healthCheckMode: 'full',
|
|
30
|
+
providerId: DEFAULT_PROVIDER_ID,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const raw = fs.readFileSync(envConfig.settings_path, 'utf8')
|
|
35
|
+
let data: unknown
|
|
36
|
+
try {
|
|
37
|
+
data = JSON5Parse(raw)
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Failed to parse ${envConfig.settings_path}: ${formatErrorMessage(error)}`, { cause: error })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = DiscoverySettingsSchema.safeParse(data)
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
throw new Error(`${envConfig.settings_path} is invalid: ${prettifyError(result.error)}`)
|
|
45
|
+
}
|
|
46
|
+
return result.data
|
|
47
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DynamicBorder, Theme } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
import { Container, Loader, Spacer, TUI } from '@earendil-works/pi-tui'
|
|
2
|
+
import { Container, Loader, Spacer, type TUI } from '@earendil-works/pi-tui'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A loader that allows us to change the displayed message.
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Try<T> = { ok: true; value: T } | { ok: false; error: unknown }
|
|
2
|
+
|
|
3
|
+
export function runCatching<T>(fn: () => T): Try<T> {
|
|
4
|
+
try {
|
|
5
|
+
return { ok: true, value: fn() }
|
|
6
|
+
} catch (error) {
|
|
7
|
+
return { ok: false, error }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function runCatchingAsync<T>(fn: () => Promise<T>): Promise<Try<T>> {
|
|
12
|
+
try {
|
|
13
|
+
return { ok: true, value: await fn() }
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return { ok: false, error }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatErrorMessage(error: unknown): string {
|
|
20
|
+
if (error instanceof Error) {
|
|
21
|
+
return error.message
|
|
22
|
+
}
|
|
23
|
+
return String(error)
|
|
24
|
+
}
|