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.
@@ -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 { getEnv, type Env } from './env'
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(envConfig: Env = getEnv()): RequestyConfig {
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[envConfig.provider_id]
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.${envConfig.provider_id}`)
59
+ throw new Error(`${envConfig.models_json_path} does not define providers.${settings.providerId}`)
51
60
  }
52
61
 
53
- const apiKey = envConfig.requesty_api_key
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(data: ModelsJson, models: ProviderModelConfig[], envConfig: Env = getEnv()): void {
85
- const provider = data.providers[envConfig.provider_id]
86
- data.providers[envConfig.provider_id] = {
87
- ...provider,
88
- apiKey: nonEmptyString(provider.apiKey) ?? '$REQUESTY_API_KEY',
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,
@@ -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
+ }