@skaile/forge-settings 1.4.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/CHANGELOG.md +25 -0
- package/README.md +34 -11
- package/dist/module.json +1 -1
- package/dist/runtime/ai-sdk/index.d.ts +33 -26
- package/dist/runtime/ai-sdk/index.js +34 -16
- package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +2 -2
- package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +2 -2
- package/dist/runtime/editor/index.d.ts +55 -16
- package/dist/runtime/editor/index.js +82 -40
- package/dist/runtime/model/selector.d.ts +16 -0
- package/dist/runtime/model/selector.js +12 -0
- package/dist/runtime/server/credential-store.d.ts +16 -4
- package/dist/runtime/server/credential-store.js +3 -4
- package/dist/runtime/server/env-layers.d.ts +1 -1
- package/dist/runtime/server/env-layers.js +3 -3
- package/dist/runtime/server/index.d.ts +7 -8
- package/dist/runtime/server/index.js +1 -3
- package/dist/runtime/server/provider-discovery.d.ts +28 -8
- package/dist/runtime/server/provider-discovery.js +57 -33
- package/dist/runtime/server/provider-layers.d.ts +1 -1
- package/dist/runtime/server/provider-layers.js +3 -5
- package/dist/runtime/server/provider-service.d.ts +2 -2
- package/dist/runtime/server/provider-write.d.ts +1 -1
- package/dist/runtime/server/provider-write.js +3 -3
- package/dist/runtime/server/voice-provider.d.ts +61 -0
- package/dist/runtime/server/voice-provider.js +31 -0
- package/dist/runtime/shared/data-zone.d.ts +28 -26
- package/dist/runtime/shared/data-zone.js +4 -4
- package/dist/runtime/shared/harness-providers.d.ts +7 -7
- package/dist/runtime/shared/provider-accounts.d.ts +1 -1
- package/dist/runtime/shared/provider-registry.d.ts +19 -220
- package/dist/runtime/shared/provider-registry.js +25 -81
- package/dist/runtime/vue/providers-panel-model.d.ts +4 -4
- package/dist/runtime/{server → workspaces}/harness-settings.d.ts +3 -3
- package/dist/runtime/workspaces/index.d.ts +16 -0
- package/dist/runtime/workspaces/index.js +5 -0
- package/dist/runtime/workspaces/language-model.d.ts +45 -0
- package/dist/runtime/workspaces/language-model.js +26 -0
- package/dist/runtime/workspaces/model-endpoint.d.ts +38 -0
- package/dist/runtime/workspaces/model-endpoint.js +54 -0
- package/dist/runtime/workspaces/provider-settings.d.ts +35 -0
- package/dist/runtime/{server → workspaces}/provider-settings.js +0 -31
- package/dist/runtime/{server → workspaces}/settings-defaults.d.ts +1 -1
- package/dist/runtime/{shared → workspaces}/settings-types.d.ts +4 -4
- package/package.json +11 -2
- package/src/runtime/ai-sdk/index.ts +88 -47
- package/src/runtime/components/ForgeProvidersPanel.vue +2 -2
- package/src/runtime/editor/index.ts +158 -63
- package/src/runtime/model/selector.ts +31 -0
- package/src/runtime/server/credential-store.ts +20 -10
- package/src/runtime/server/env-layers.ts +4 -4
- package/src/runtime/server/index.ts +7 -8
- package/src/runtime/server/provider-discovery.ts +114 -48
- package/src/runtime/server/provider-layers.ts +6 -6
- package/src/runtime/server/provider-service.ts +2 -2
- package/src/runtime/server/provider-write.ts +3 -3
- package/src/runtime/server/voice-provider.ts +110 -0
- package/src/runtime/shared/data-zone.ts +40 -33
- package/src/runtime/shared/harness-providers.ts +7 -7
- package/src/runtime/shared/provider-accounts.ts +1 -1
- package/src/runtime/shared/provider-registry.ts +82 -314
- package/src/runtime/vue/providers-panel-model.ts +5 -5
- package/src/runtime/vue/useProvidersPanel.ts +5 -2
- package/src/runtime/{server → workspaces}/harness-settings.ts +3 -3
- package/src/runtime/workspaces/index.ts +17 -0
- package/src/runtime/workspaces/language-model.ts +82 -0
- package/src/runtime/workspaces/model-endpoint.ts +100 -0
- package/src/runtime/workspaces/provider-settings.ts +96 -0
- package/src/runtime/{server → workspaces}/settings-defaults.ts +1 -1
- package/src/runtime/{shared → workspaces}/settings-types.ts +4 -4
- package/dist/runtime/server/provider-settings.d.ts +0 -78
- package/src/runtime/server/provider-settings.ts +0 -192
- /package/dist/runtime/{server → workspaces}/harness-settings.js +0 -0
- /package/dist/runtime/{server → workspaces}/settings-defaults.js +0 -0
- /package/dist/runtime/{shared → workspaces}/settings-types.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- `@skaile/workspaces` dependency → optional peer, needed only by the new `@skaile/forge-settings/workspaces` entry. `/server`, `/vue`, `/nuxt`, `/ai-sdk` and `/editor` no longer import it at runtime or in their declarations; the registry schema, catalog, discovery and model constructor come from the new dependency `@skaile/provider-registry`.
|
|
8
|
+
- `SkaileSettings`, `DomainConfig`, `SETTINGS_DEFAULTS`, `migrateLegacyHarnessSettings`, `migrateLegacyProviderSettings`, `LegacySettingsMigration` and the harness settings rules (`HarnessSettings`, `LockFlags`, `LockFlagStatus`, `resolveLockFlags`, `enabledHarnessIds`, `HarnessRow`, `buildHarnessRows`, `HarnessSettingsPatch`, `validateHarnessSettingsPatch`, `HarnessLayer`, `applyHarnessLayers`) from `@skaile/forge-settings/server` → `@skaile/forge-settings/workspaces`. Voice selection (`resolveVoiceProvider`, `VoiceSettings`, `VoiceProviderSelection`) stays in `/server`; `VoiceSettings` is now its own interface with the same four fields instead of `Pick<SkaileSettings, …>`, so a `SkaileSettings` still passes.
|
|
9
|
+
- `resolveRegistryLanguageModel(selector, { projectDir, registry?, env, roles?, policy? })` → `resolveRegistryLanguageModel(selector, { registry, env, roles?, policy?, path?, key? })`: `registry` is required and nothing is read from disk. Project-oriented callers → `resolveWorkspaceLanguageModel(selector, { projectDir, registry?, env, roles?, policy?, path?, key? })` from `/workspaces`, which loads the project's registry once and keeps the keyring at `<skaile home>/forge-credentials.json`.
|
|
10
|
+
- Credential store: implicit `<skaile home>/forge-credentials.json` → explicit `options.path`, else `FORGE_CREDENTIAL_STORE` of the environment; with neither, a keyring operation throws `FORGE_CREDENTIAL_STORE or credential store path is required` (resolution reports it as the credential error). Workspaces hosts pass `workspaceCredentialStorePath(env)` (or pin it into `FORGE_CREDENTIAL_STORE`) to keep their file. Env-only and keyless providers need no store.
|
|
11
|
+
- `editorLanguageModel(registry, env, ref, options)` synchronous `LanguageModel | null` → `Promise<LanguageModel | null>`, built by the shared `@skaile/provider-registry/ai-sdk` constructor (adds `openai-responses` and `anthropic-messages` entries; the `<providerId>/<modelId>` profile selector is unchanged). A `<providerId>/…` selector whose provider is a registry entry now throws when disabled or refused instead of answering `null`.
|
|
12
|
+
- `createEditorModelProvider(…).listModels()` returns `[]` for a registry with no servable models (was `null`, which fell through to the editor's presets) and lists discovered models next to the declared ones.
|
|
13
|
+
- `shared/provider-registry` mirror types → re-exports of the canonical `@skaile/provider-registry` types (same names); `parseCredentialRef`, `formatCredentialRef` and `isEntryEnabled` are the canonical functions.
|
|
14
|
+
- 1.4.0 harness additions move with the harness rules to `/workspaces`: `HarnessRowProviders`, `buildHarnessRows(harnesses, settings, detections?, { candidates, dataZoneRule }?)` with `HarnessRow.roles` / `roleAssignments` / `blockedProviders`, `validateHarnessSettingsPatch`'s `harnessRoles` (and `ctx.runnableModels`), `applyHarnessLayers`' `roles`, and the `SkaileSettings.harnessRoles` field: `import { buildHarnessRows, type HarnessRowProviders, type SkaileSettings } from '@skaile/forge-settings/server'` → `… from '@skaile/forge-settings/workspaces'`. `HARNESS_ROLES`, `harnessRoleIds` and the browser-safe harness-provider rule (`resolveHarnessProviders`, `harnessProviderBlock`, `HarnessProviderBlock`, `HarnessProviderCandidate`, `HarnessProviderOptions`) stay in `/server` and `/vue`.
|
|
15
|
+
- 1.4.0 data-zone vocabulary mirror → the registry's own names, re-exported by `/server` and `/vue` from `@skaile/provider-registry` (values unchanged): `DATA_ZONES` → `AI_PROVIDER_DATA_ZONES`, `DATA_ZONE_RULES` → `AI_PROVIDER_DATA_ZONE_RULES`, type `DataZone` → `AIProviderDataZone`, type `DataZoneRule` → `AIProviderDataZoneRule`. Every signature that took or returned them (`entryDataZone`, `entryAllowedByDataZone`, `dataZoneAllows`, `dataZoneRefusal`, `filterRegistryByDataZone`, `presetDataZone`, `ProviderInfo.dataZone`, `HarnessProviderBlock.rule` / `.zone`, the `dataZoneRule` panel prop) now names the canonical types. The browser-safe helpers `PRESET_DATA_ZONES`, `DATA_ZONE_LABELS`, `DATA_ZONE_RULE_LABELS`, `isDataZone`, `isDataZoneRule`, `presetDataZone`, `entryDataZone`, `entryAllowedByDataZone`, `dataZoneAllows`, `dataZoneRefusal` and `filterRegistryByDataZone` (which keeps a merged registry merged) stay in `/server` and `/vue`; `PRESET_DATA_ZONES` is tested equal to `@skaile/provider-registry/catalog`.
|
|
16
|
+
- 1.4.0 `isModelAllowed` mirror → the canonical `isModelAllowed` of `@skaile/provider-registry`, still re-exported by `/server` and `/vue` under the same name and with the same rules; its entry parameter is `AIProviderModelAllowListFields` (`allowed_models?: string[]`), so pass a mutable array instead of a `readonly string[]`. New re-exports next to it: `allowedModelIds`, `AIProviderModelAllowListFields`.
|
|
17
|
+
- 1.4.0 entry fields `data_zone`, `eu_processing` and `allowed_models` are validated by `@skaile/provider-registry` (was: the `@skaile/workspaces` 9.1 schema), so `upsertProviderEntry` and the generic entries accept them with any peer or none. `/workspaces`' `loadWorkspaceProviderRegistry` / `resolveWorkspaceLanguageModel` load the project registry through the installed `@skaile/workspaces`, whose schema before 9.1 refuses those keys: a registry that uses them needs the peer at 9.1 or later.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `discoverProviderModels` asks any entry that speaks a models list (`discovery: { type: 'openai-models-list' | 'anthropic-models-list' }` or an OpenAI/Anthropic surface) at its own `base_url`, through `fetchEntryModels`: no catalog preset needed, `credential.kind: 'none'` queries without `Authorization`, static headers and the credential carrier match inference. Options `path`, `key`, `signal` (`signal` also settles a catalog-preset listing as cancelled).
|
|
22
|
+
- `CREDENTIAL_STORE_PATH_REQUIRED` (`/server`): the message a keyring operation without a store path throws.
|
|
23
|
+
- `createEditorModelProvider` / `editorLanguageModel` options `path` and `key` (local store); `listEditorModelProfiles(registry, env, options)`.
|
|
24
|
+
- `asMergedRegistry(view)` (browser-safe).
|
|
25
|
+
- `/workspaces`: `resolveWorkspaceLanguageModel`, `loadWorkspaceProviderRegistry(projectDir)`, `workspaceCredentialStorePath(env?)` (`FORGE_CREDENTIAL_STORE` of `env`, else `<skaile home>/forge-credentials.json`), `readWorkspaceModelEndpoint({ env, projectDir? })` (`SKAILE_MODEL_ENDPOINT_URL` + `SKAILE_MODEL_ENDPOINT_TOKEN`, else `<projectDir>/.skaile/model-endpoint.json`; `null` when absent, token-free configuration errors) and the env names `WORKSPACE_MODEL_ENDPOINT_URL_ENV` / `WORKSPACE_MODEL_ENDPOINT_TOKEN_ENV`.
|
|
26
|
+
- Accepts `@skaile/workspaces` ^7.8.0 through ^10.0.0 as the optional `/workspaces` peer (`^7.8.0 || ^8.0.0 || ^9.0.0 || ^10.0.0`; 1.4.0 required `^9.1.0` as a dependency). The `/workspaces` entry imports no 9.1 symbol: its imports (`loadMergedProviderRegistry`, `globalSkaileDir`, `SETTINGS_DEFAULTS`, `SkaileSettings`, the `HarnessInfo` / `HarnessDetection` types) exist in 7.8.0 through 10.0.0, and the entry typechecks against each of 7.8.0, 8.0.0, 9.0.0 and 10.0.0. 1.4.0's `^9.1.0` was for the registry schema of `data_zone` / `eu_processing` / `allowed_models`, which the generic entries now take from `@skaile/provider-registry`; only a project registry that uses those keys needs the peer at 9.1 or later (see Breaking). 10.0.0 moved the registry primitives to `@skaile/provider-registry`, which this package now takes from there.
|
|
27
|
+
|
|
3
28
|
## 1.4.0
|
|
4
29
|
|
|
5
30
|
- `<ForgeProvidersPanel>` adds a provider as a flow: a library of the catalog presets (plus Custom), then *How do you sign in?* listing only the kinds the preset accepts, the fields of that kind and the preset's routing parameters, then a done step that tests every created entry when a `client` is passed. New boolean prop `expert` (default `false`): adds the raw Reference form (`env:` / `keyring:` / `cmd:` / `platform:`) to the sign-in step and makes `api_version` editable. Detected-preset Add and Edit keep working; Edit skips the library.
|
package/README.md
CHANGED
|
@@ -4,13 +4,14 @@ Provider and settings operations for Skaile forge apps, without the app's databa
|
|
|
4
4
|
|
|
5
5
|
| Entry | Contents | Peers it needs |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| `/server` | registry layers and writes, credential store, `.env` layers, provider inventory and discovery,
|
|
7
|
+
| `/server` | registry layers and writes, credential store, `.env` layers, provider inventory and discovery, credential policy, model roles, voice selection (`resolveVoiceProvider`) | none |
|
|
8
8
|
| `/vue` | `useProvidersPanel`, `urlProvidersClient`, `SettingsModalScope`, model-role helpers, browser-safe registry/env/harness vocabulary | `vue` |
|
|
9
9
|
| `/nuxt` | Nuxt module registering `<ForgeProvidersPanel>` and `<ForgeModelRolesPanel>` | `nuxt`, `@nuxt/ui` |
|
|
10
10
|
| `/ai-sdk` | AI SDK `LanguageModel` of a registry selector | `ai` + the `@ai-sdk/*` provider the surface needs |
|
|
11
|
-
| `/editor` | the editor's `ModelProvider` over a registry | `@skaile/forge-editor`, `@ai-sdk
|
|
11
|
+
| `/editor` | the editor's `ModelProvider` over a registry | `@skaile/forge-editor`, `ai`, the `@ai-sdk/*` provider the surface needs |
|
|
12
|
+
| `/workspaces` | Skaile Workspaces integration: `SkaileSettings`, `SETTINGS_DEFAULTS`, legacy settings migrations, harness settings rules, `workspaceCredentialStorePath`, `resolveWorkspaceLanguageModel`, `readWorkspaceModelEndpoint` | `@skaile/workspaces` |
|
|
12
13
|
|
|
13
|
-
Importing `/server` loads no Vue, editor, AI SDK
|
|
14
|
+
The registry schema, catalog, discovery and model constructor come from `@skaile/provider-registry` (a dependency). Importing `/server` loads no Vue, editor, AI SDK, agent session code or Skaile Workspaces. Every peer is optional and used by one entry only: `/server`, `/vue`, `/nuxt`, `/ai-sdk` and `/editor` install, load and typecheck without `@skaile/workspaces`; only `/workspaces` needs it.
|
|
14
15
|
|
|
15
16
|
## What stays with the host
|
|
16
17
|
|
|
@@ -34,16 +35,20 @@ The policy is checked against `credential.ref` and every `${NAME}` token in `bas
|
|
|
34
35
|
```ts
|
|
35
36
|
const scope: ProviderWriteScope = { name: 'instance', file, below: [], env, lock, policy } // refuses writes naming a refused ref
|
|
36
37
|
discoverProviderModels('main', registry, { env, policy })
|
|
37
|
-
resolveRegistryLanguageModel('main/gpt-x', {
|
|
38
|
+
resolveRegistryLanguageModel('main/gpt-x', { registry, env, policy })
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
## Credential store
|
|
41
42
|
|
|
42
43
|
`keyring:<service>/<account>` references are stored AES-256-GCM encrypted in a JSON file; the reference is the additional authenticated data, so a blob cannot be replayed under another reference. The key is `FORGE_CREDENTIAL_KEY` (64 hex characters) or `options.key`. Without it only operations that need the store fail, with `CredentialKeyError` (`statusCode: 503`); `env:` references keep working. A blob that does not authenticate raises `CredentialDecryptError`.
|
|
43
44
|
|
|
45
|
+
There is no implicit store location. The file is `options.path`, else `FORGE_CREDENTIAL_STORE` of the environment the call is handed; with neither, a keyring operation throws `FORGE_CREDENTIAL_STORE or credential store path is required` (resolution reports it as the credential's error and sends nothing). Env-only and keyless providers need no store. A Skaile Workspaces host keeps its existing file by passing `workspaceCredentialStorePath()` from `/workspaces` (`<skaile home>/forge-credentials.json`) as `path`, or pinning it into `FORGE_CREDENTIAL_STORE`.
|
|
46
|
+
|
|
44
47
|
## Model discovery
|
|
45
48
|
|
|
46
|
-
`discoverProviderModels(id, registry, { env, policy?, redact? })` returns `live
|
|
49
|
+
`discoverProviderModels(id, registry, { env, policy?, redact?, path?, key?, signal? })` returns `live` (possibly empty), `static` (the entry's own preset catalog, with the scrubbed reason the live request failed — a labelled fallback, never an inference destination), `unsupported` (the entry speaks no models list and has no catalog: let the admin type a model id) or `error`. Error text never contains a remote response body, and every secret the call resolved (plus `redact`) is removed from it, also in URL-encoded and base64 form.
|
|
50
|
+
|
|
51
|
+
Any entry with `discovery: { type: 'openai-models-list' }` (or an OpenAI/Anthropic surface) is asked at its own `base_url`, with its static `headers` and its credential exactly as inference sends them — no catalog preset needed. `credential.kind: 'none'` queries without `Authorization`, so a local model server works keyless. The credential is resolved under the policy first: a refused or unresolvable reference sends no request.
|
|
47
52
|
|
|
48
53
|
## Models for the editor and the AI SDK
|
|
49
54
|
|
|
@@ -55,18 +60,36 @@ export const models = createEditorModelProvider({
|
|
|
55
60
|
registry: () => getProviderRegistry(home()),
|
|
56
61
|
env: () => effectiveEnv(),
|
|
57
62
|
policy, // refused entries are neither listed nor resolved
|
|
63
|
+
path: credentialStoreFile, // keyring: references; else FORGE_CREDENTIAL_STORE of env()
|
|
58
64
|
onFailure: (e) => createError({ statusCode: e.code === 'policy_refused' ? 403 : 400, statusMessage: e.message }),
|
|
59
65
|
})
|
|
60
66
|
```
|
|
61
67
|
|
|
68
|
+
`listModels` answers the declared models (`model_map` / `capabilities`) plus the models each entry's models list reports, one profile per selector (a declared identity wins over a discovered duplicate). Declared models work without a models list. The registry is authoritative: an empty one lists `[]`, and a selector whose provider id is a registry entry either resolves or throws (disabled, refused, unresolvable) — only a selector the registry does not own (or a browser `{ endpoint, model, apiKey }` reference) answers `null` and falls through to the editor's presets. `editorModelProfiles(registry, { policy? })` is the declared-only, network-free list; `await editorLanguageModel(registry, env, ref, options)` is the adapter's model lookup.
|
|
69
|
+
|
|
62
70
|
```ts
|
|
63
71
|
import { resolveRegistryLanguageModel } from '@skaile/forge-settings/ai-sdk'
|
|
64
|
-
const { model } = await resolveRegistryLanguageModel('assistant-default/some-model', {
|
|
72
|
+
const { model } = await resolveRegistryLanguageModel('assistant-default/some-model', { registry, env, policy, path })
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
Role-to-selector mapping belongs to the host's settings document; there is no global model locator. Failures are `ModelResolutionError` with a `code`; messages never carry a secret.
|
|
75
|
+
Resolves against the one `registry` snapshot passed (no disk read), with the same policy-checked credential resolver discovery uses, through `@skaile/provider-registry/ai-sdk` (`openai-completions`, `openai-responses`, `anthropic-messages`). A disabled or missing named provider and an unassigned `@role` fail; nothing is rerouted. Role-to-selector mapping belongs to the host's settings document; there is no global model locator. Failures are `ModelResolutionError` with a `code`; messages never carry a secret.
|
|
76
|
+
|
|
77
|
+
## Skaile Workspaces
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { readWorkspaceModelEndpoint, resolveWorkspaceLanguageModel } from '@skaile/forge-settings/workspaces'
|
|
81
|
+
|
|
82
|
+
// The project's merged registry (user + .skaile/providers.yml + catalog), read once;
|
|
83
|
+
// keyring: references read <skaile home>/forge-credentials.json unless path / FORGE_CREDENTIAL_STORE say otherwise.
|
|
84
|
+
const { model } = await resolveWorkspaceLanguageModel('@assistant', { projectDir, env, roles, policy })
|
|
85
|
+
// Or an already merged (e.g. transform-proxy-routed) registry: { projectDir, registry, env }.
|
|
86
|
+
|
|
87
|
+
// A running `skaile serve --model-endpoint`: SKAILE_MODEL_ENDPOINT_URL + _TOKEN from env (both, http(s)),
|
|
88
|
+
// else <projectDir>/.skaile/model-endpoint.json when projectDir is given; null when neither exists.
|
|
89
|
+
const endpoint = await readWorkspaceModelEndpoint({ env, projectDir }) // { url, token } | null
|
|
90
|
+
```
|
|
68
91
|
|
|
69
|
-
|
|
92
|
+
`readWorkspaceModelEndpoint` never climbs directories or reads the working directory, and its configuration errors (half an env pair, malformed or incomplete file) never contain the token. `workspaceCredentialStorePath(env?)` is the Workspaces keyring: `FORGE_CREDENTIAL_STORE` of `env`, else `<skaile home>/forge-credentials.json`. The settings document (`SkaileSettings`, `SETTINGS_DEFAULTS`, `migrateLegacyHarnessSettings`, `migrateLegacyProviderSettings`) and harness rules (`buildHarnessRows`, `validateHarnessSettingsPatch`, `applyHarnessLayers`, …) live here too; voice selection (`resolveVoiceProvider`) is generic and lives in `/server` — a `SkaileSettings` passes as its `VoiceSettings`.
|
|
70
93
|
|
|
71
94
|
## Providers panel
|
|
72
95
|
|
|
@@ -94,7 +117,7 @@ Without URLs, pass `:client` (a `ProvidersPanelClient`: `list`, `detected`, `pre
|
|
|
94
117
|
|
|
95
118
|
### Data zones and model filters
|
|
96
119
|
|
|
97
|
-
Three entry fields (`@skaile/workspaces` 9.1; 9.0's schema refuses them)
|
|
120
|
+
Three entry fields of the `@skaile/provider-registry` schema are writable through `upsertProviderEntry` (a Workspaces runtime reading the same file needs `@skaile/workspaces` 9.1 or later; 9.0's schema refuses them). A body that omits one keeps the stored value, and `null` removes it:
|
|
98
121
|
|
|
99
122
|
|Field|Accepts|Removed by `null` means|
|
|
100
123
|
|---|---|---|
|
|
@@ -104,7 +127,7 @@ Three entry fields (`@skaile/workspaces` 9.1; 9.0's schema refuses them) are wri
|
|
|
104
127
|
|
|
105
128
|
Any other value is a 400 naming the field. Provider views carry `dataZone` (effective), `euProcessing`, `allowedModels` and `modelIds`.
|
|
106
129
|
|
|
107
|
-
The zone rule is the host's: `filterRegistryByDataZone(registry, rule)` keeps what `rule` allows (`any` all; `eu_options` EU, local, and EU option with `eu_processing: true`; `eu` EU and local). `entryDataZone`, `entryAllowedByDataZone` and `dataZoneRefusal` answer per entry; `PRESET_DATA_ZONES` copies the
|
|
130
|
+
The zone rule is the host's: `filterRegistryByDataZone(registry, rule)` keeps what `rule` allows (`any` all; `eu_options` EU, local, and EU option with `eu_processing: true`; `eu` EU and local), and keeps a merged registry merged. `entryDataZone`, `entryAllowedByDataZone` and `dataZoneRefusal` answer per entry; `PRESET_DATA_ZONES` copies the `@skaile/provider-registry/catalog` zone per preset, so the panel needs no catalog in the browser. The vocabulary is the registry's: `AI_PROVIDER_DATA_ZONES`, `AI_PROVIDER_DATA_ZONE_RULES`, `AIProviderDataZone`, `AIProviderDataZoneRule`, and `isModelAllowed` / `allowedModelIds` for `allowed_models`, re-exported by `/server` and `/vue`. Pass the rule to `resolveHarnessProviders(..., { dataZoneRule })` and to `/workspaces`' `buildHarnessRows(..., { candidates, dataZoneRule })`: an entry the rule keeps out is reported as `blocked-by-rule` (`blocked`, `HarnessRow.blockedProviders`) with a message naming the rule, and is never eligible or chosen.
|
|
108
131
|
|
|
109
132
|
In the panel every row shows its zone chip (`provider-zone-<id>`) and, for `llm` / `embed` entries, a model chip (`provider-models-chip-<id>`: "All models" / "N models"). An own `eu_option` row offers "EU processing is on for this account" (`provider-eu-processing-<id>`). `:data-zone-rule="rule"` marks rows outside the rule with a muted note (`provider-blocked-<id>`). "Models allowed here" (`provider-models-<id>`) switches All models / Only these (`provider-models-mode-<id>`); Only these is a searchable checklist (`provider-models-search-<id>`, `provider-model-<id>-<modelId>`) of the entry's declared models or, for a discovery entry, of `client.models(id)`, and Save (`provider-models-save-<id>`) writes `allowed_models`.
|
|
110
133
|
|
|
@@ -128,7 +151,7 @@ const roles = [
|
|
|
128
151
|
]
|
|
129
152
|
validateModelRoleAssignments(roles, assignments, registry) // [{ role, code, message }]: one issue per role id, host turns them into 400s
|
|
130
153
|
resolveModelRole('assistant', { roles, assignments, registry }) // { kind: 'model', selector, entryId } | { kind: 'voice', entryId } | { kind: 'unassigned' }
|
|
131
|
-
resolveRegistryLanguageModel('@assistant', { roles: modelRoleAliases(roles, assignments) })
|
|
154
|
+
resolveRegistryLanguageModel('@assistant', { registry, env, roles: modelRoleAliases(roles, assignments) })
|
|
132
155
|
```
|
|
133
156
|
|
|
134
157
|
A dangling assignment throws `ModelResolutionError` (`unknown_selector`). `<ForgeModelRolesPanel :roles :assignments :client :read-only @update:assignments>` renders one row per enabled role (provider select, model select from `client.models`, free text when the provider cannot list models, Clear, a `status` slot per row). It never writes; the host saves the emitted assignments.
|
package/dist/module.json
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@skaile/forge-settings/ai-sdk` — the AI SDK language model of a registry
|
|
3
|
-
* selector,
|
|
4
|
-
* credential
|
|
3
|
+
* selector, built by `@skaile/provider-registry/ai-sdk` against one registry
|
|
4
|
+
* snapshot the host passes, with the credential resolved under the host's
|
|
5
|
+
* policy.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
* this module
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* The shared constructor owns endpoint, header and SDK wiring for every
|
|
8
|
+
* surface it supports; this module decides *whether* an entry may be resolved
|
|
9
|
+
* and supplies its credential — through the same policy-checked resolver model
|
|
10
|
+
* discovery uses, so a model that lists is a model that calls with the same
|
|
11
|
+
* key. It is not a service locator: the caller passes the selector
|
|
12
|
+
* (role-to-selector mapping belongs to the host's settings document), the
|
|
13
|
+
* registry and the environment explicitly. Nothing is read from disk here;
|
|
14
|
+
* a Skaile Workspaces host that resolves against a project's registry uses
|
|
15
|
+
* `resolveWorkspaceLanguageModel` from `@skaile/forge-settings/workspaces`.
|
|
11
16
|
*
|
|
12
17
|
* Provider SDKs (`@ai-sdk/anthropic`, `@ai-sdk/openai`,
|
|
13
|
-
* `@ai-sdk/openai-compatible`) are optional peers
|
|
14
|
-
*
|
|
18
|
+
* `@ai-sdk/openai-compatible`) are optional peers: install the one the
|
|
19
|
+
* selected surface needs.
|
|
15
20
|
*/
|
|
16
|
-
import { type MergedAIProviderRegistry } from "@skaile/
|
|
17
|
-
import { type ResolvedLanguageModel } from "@skaile/
|
|
21
|
+
import { type MergedAIProviderRegistry } from "@skaile/provider-registry";
|
|
22
|
+
import { type ResolvedLanguageModel } from "@skaile/provider-registry/ai-sdk";
|
|
18
23
|
import type { CredentialPolicy } from "../server/credential-policy.js";
|
|
19
|
-
export { ModelResolutionError } from "../model/errors.js";
|
|
20
24
|
export type { ModelResolutionCode } from "../model/errors.js";
|
|
25
|
+
export { ModelResolutionError } from "../model/errors.js";
|
|
21
26
|
export type { ResolvedLanguageModel };
|
|
22
27
|
/** Options for {@link resolveRegistryLanguageModel}. */
|
|
23
28
|
export interface ResolveRegistryLanguageModelOptions {
|
|
24
|
-
/**
|
|
25
|
-
|
|
29
|
+
/** The one merged registry snapshot the selector resolves against. */
|
|
30
|
+
registry: MergedAIProviderRegistry;
|
|
26
31
|
/**
|
|
27
32
|
* The environment `env:` credential references are read from. Required, so a
|
|
28
33
|
* host never reads the ambient process by accident.
|
|
@@ -35,21 +40,23 @@ export interface ResolveRegistryLanguageModelOptions {
|
|
|
35
40
|
* before anything is read; a refused one is a `policy_refused` failure.
|
|
36
41
|
*/
|
|
37
42
|
policy?: CredentialPolicy;
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*/
|
|
43
|
-
registry?: MergedAIProviderRegistry;
|
|
43
|
+
/** Local encrypted store for `keyring:` references; else `FORGE_CREDENTIAL_STORE` of `env`. */
|
|
44
|
+
path?: string;
|
|
45
|
+
/** 64-char hex key of the local store; else `FORGE_CREDENTIAL_KEY` of `env`. */
|
|
46
|
+
key?: string;
|
|
44
47
|
}
|
|
45
48
|
/**
|
|
46
49
|
* The AI SDK model for `selector` (`<providerId>/<modelId>`, a bare model id or
|
|
47
|
-
* `@role`), resolved
|
|
48
|
-
*
|
|
50
|
+
* `@role`), resolved against `options.registry` only.
|
|
51
|
+
*
|
|
52
|
+
* Strict: a selector naming a provider that is missing from the registry's
|
|
53
|
+
* enabled `llm` entries (and that no entry maps as a model id), disabled, or an
|
|
54
|
+
* unassigned `@role` fails — nothing is rerouted to another endpoint and
|
|
55
|
+
* nothing is sent. A bare model id without `/` resolves as the registry orders it.
|
|
49
56
|
*
|
|
50
|
-
* @throws {ModelResolutionError} `
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* stays on `cause`).
|
|
57
|
+
* @throws {ModelResolutionError} `unknown_selector` (no enabled entry serves the
|
|
58
|
+
* selector, or the named entry is disabled / not an `llm` entry) ·
|
|
59
|
+
* `policy_refused` · `unresolved` (credential, endpoint or provider SDK; the
|
|
60
|
+
* message is fixed, the underlying error stays on `cause`).
|
|
54
61
|
*/
|
|
55
62
|
export declare function resolveRegistryLanguageModel(selector: string, options: ResolveRegistryLanguageModelOptions): Promise<ResolvedLanguageModel>;
|
|
@@ -1,24 +1,50 @@
|
|
|
1
|
-
import { parseModelRef } from "@skaile/
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { parseModelRef } from "@skaile/provider-registry";
|
|
2
|
+
import {
|
|
3
|
+
resolveLanguageModel
|
|
4
|
+
} from "@skaile/provider-registry/ai-sdk";
|
|
5
|
+
import { assertEntryAllowed, ModelResolutionError } from "../model/errors.js";
|
|
6
|
+
import { credentialStoreOptions, selectorProviderId } from "../model/selector.js";
|
|
7
|
+
import { resolveEntryCredential } from "../server/credential-store.js";
|
|
5
8
|
export { ModelResolutionError } from "../model/errors.js";
|
|
9
|
+
function policyCredentialResolver(options) {
|
|
10
|
+
return (entry, providerId) => {
|
|
11
|
+
assertEntryAllowed(entry, providerId, options.policy);
|
|
12
|
+
const resolved = resolveEntryCredential(entry, credentialStoreOptions(options.env, options));
|
|
13
|
+
return resolved.error && !resolved.value ? { error: resolved.error } : { ...resolved.value !== void 0 ? { value: resolved.value } : {} };
|
|
14
|
+
};
|
|
15
|
+
}
|
|
6
16
|
export async function resolveRegistryLanguageModel(selector, options) {
|
|
7
|
-
const merged = options.registry
|
|
17
|
+
const merged = options.registry;
|
|
18
|
+
const head = selectorProviderId(selector);
|
|
19
|
+
const named = head === void 0 ? void 0 : merged.providers[head];
|
|
20
|
+
if (head !== void 0 && named !== void 0 && (named.kind !== "llm" || named.enabled === false)) {
|
|
21
|
+
throw new ModelResolutionError(
|
|
22
|
+
"unknown_selector",
|
|
23
|
+
`provider '${head}' is ${named.kind !== "llm" ? "not an llm entry" : "disabled"}`,
|
|
24
|
+
{ providerId: head }
|
|
25
|
+
);
|
|
26
|
+
}
|
|
8
27
|
const ref = parseModelRef(selector, merged, options.roles);
|
|
9
|
-
if (!ref) {
|
|
28
|
+
if (!ref || head !== void 0 && named === void 0 && ref.fallback === true) {
|
|
10
29
|
throw new ModelResolutionError("unknown_selector", `no provider serves '${selector}'`);
|
|
11
30
|
}
|
|
31
|
+
if (ref.role !== void 0 && !options.roles?.[ref.role]) {
|
|
32
|
+
throw new ModelResolutionError(
|
|
33
|
+
"unknown_selector",
|
|
34
|
+
`role '@${ref.role}' is not assigned to a model`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
12
37
|
const entry = merged.providers[ref.providerId];
|
|
13
38
|
if (entry) assertEntryAllowed(entry, ref.providerId, options.policy);
|
|
14
39
|
try {
|
|
15
40
|
return await resolveLanguageModel(selector, {
|
|
16
|
-
|
|
41
|
+
registry: merged,
|
|
17
42
|
env: options.env,
|
|
18
43
|
...options.roles ? { roles: options.roles } : {},
|
|
19
|
-
|
|
44
|
+
resolveCredential: policyCredentialResolver(options)
|
|
20
45
|
});
|
|
21
46
|
} catch (cause) {
|
|
47
|
+
if (cause instanceof ModelResolutionError) throw cause;
|
|
22
48
|
throw new ModelResolutionError(
|
|
23
49
|
"unresolved",
|
|
24
50
|
`provider '${ref.providerId}': credential, endpoint or provider SDK could not be resolved`,
|
|
@@ -26,11 +52,3 @@ export async function resolveRegistryLanguageModel(selector, options) {
|
|
|
26
52
|
);
|
|
27
53
|
}
|
|
28
54
|
}
|
|
29
|
-
function loadRegistry(projectDir) {
|
|
30
|
-
const { merged, diagnostics } = loadMergedProviderRegistry({ projectDir });
|
|
31
|
-
const errors = diagnostics.filter((d) => d.severity === "error").map((d) => d.path ? `${d.path}: ${d.message}` : d.message);
|
|
32
|
-
if (errors.length > 0) {
|
|
33
|
-
throw new ModelResolutionError("registry_invalid", `providers registry: ${errors.join("; ")}`);
|
|
34
|
-
}
|
|
35
|
-
return merged;
|
|
36
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AIProviderDataZoneRule } from "../shared/provider-registry.js";
|
|
2
2
|
import type { ProvidersPanelClient } from "../vue/providers-client.js";
|
|
3
3
|
import { type ProvidersPanelLayer } from "../vue/useProvidersPanel.js";
|
|
4
4
|
type __VLS_Props = {
|
|
@@ -33,7 +33,7 @@ type __VLS_Props = {
|
|
|
33
33
|
* The host's data-zone rule: rows outside it show a muted note naming the
|
|
34
34
|
* rule (`provider-blocked-<id>`). Absent or `any`: none.
|
|
35
35
|
*/
|
|
36
|
-
dataZoneRule?:
|
|
36
|
+
dataZoneRule?: AIProviderDataZoneRule;
|
|
37
37
|
};
|
|
38
38
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
39
39
|
changed: () => any;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AIProviderDataZoneRule } from "../shared/provider-registry.js";
|
|
2
2
|
import type { ProvidersPanelClient } from "../vue/providers-client.js";
|
|
3
3
|
import { type ProvidersPanelLayer } from "../vue/useProvidersPanel.js";
|
|
4
4
|
type __VLS_Props = {
|
|
@@ -33,7 +33,7 @@ type __VLS_Props = {
|
|
|
33
33
|
* The host's data-zone rule: rows outside it show a muted note naming the
|
|
34
34
|
* rule (`provider-blocked-<id>`). Absent or `any`: none.
|
|
35
35
|
*/
|
|
36
|
-
dataZoneRule?:
|
|
36
|
+
dataZoneRule?: AIProviderDataZoneRule;
|
|
37
37
|
};
|
|
38
38
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
39
39
|
changed: () => any;
|
|
@@ -4,11 +4,24 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `@skaile/forge-editor`'s AI routes ask the host's `ModelProvider`
|
|
6
6
|
* (`listModels` / `languageModel`) before their own runtime-config presets.
|
|
7
|
-
* This adapter answers from the host's registry
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* This adapter answers from the host's registry and is a thin layer over
|
|
8
|
+
* `@skaile/forge-settings/ai-sdk`: a profile's id is the registry selector
|
|
9
|
+
* `<providerId>/<modelId>`, which `languageModel` receives back as
|
|
10
|
+
* `{ preset: id }` and resolves through the same constructor, credential
|
|
11
|
+
* resolver and policy as every other registry model. Every `llm` entry whose
|
|
12
|
+
* surface the AI SDK constructor speaks (`openai-completions`,
|
|
13
|
+
* `openai-responses`, `anthropic-messages`) and that has an endpoint is
|
|
14
|
+
* offered.
|
|
15
|
+
*
|
|
16
|
+
* Listing combines what the registry declares (`model_map` / `capabilities`)
|
|
17
|
+
* with what an entry's own models list reports, for entries that declare
|
|
18
|
+
* `discovery` or declare no models at all. Declared models work without a
|
|
19
|
+
* models list; a declared identity wins over a discovered one.
|
|
20
|
+
*
|
|
21
|
+
* The registry is authoritative for the selectors it owns: an empty registry
|
|
22
|
+
* lists `[]`, and a `<providerId>/…` selector naming a registry entry that is
|
|
23
|
+
* disabled, refused or unresolvable throws instead of falling through to the
|
|
24
|
+
* editor's presets. Only a selector the registry does not own answers `null`.
|
|
12
25
|
*
|
|
13
26
|
* The registry and the credential environment are supplied by the host on every
|
|
14
27
|
* call, so provider edits apply without a restart and nothing reads the ambient
|
|
@@ -16,35 +29,61 @@
|
|
|
16
29
|
* them to HTTP in `onFailure`.
|
|
17
30
|
*
|
|
18
31
|
* `@skaile/forge-editor` is an optional peer of this entry only, for its
|
|
19
|
-
* `ModelProvider` contract (types).
|
|
32
|
+
* `ModelProvider` contract (types).
|
|
20
33
|
*/
|
|
21
34
|
import type { ModelProfile, ModelRef } from "@skaile/forge-editor/ai";
|
|
22
35
|
import type { ModelProvider } from "@skaile/forge-editor/server";
|
|
36
|
+
import { type LanguageModelObject } from "@skaile/provider-registry/ai-sdk";
|
|
23
37
|
import { ModelResolutionError } from "../model/errors.js";
|
|
24
38
|
import { type CredentialPolicy } from "../server/credential-policy.js";
|
|
25
39
|
import { type AIProviderRegistryView } from "../shared/provider-registry.js";
|
|
26
|
-
export { ModelResolutionError } from "../model/errors.js";
|
|
27
40
|
export type { ModelResolutionCode } from "../model/errors.js";
|
|
28
|
-
|
|
41
|
+
export { ModelResolutionError } from "../model/errors.js";
|
|
42
|
+
/** Policy and credential store for an editor model call; see `CredentialPolicy`. */
|
|
29
43
|
export interface EditorModelPolicyOptions {
|
|
30
44
|
/** Which credential references may be resolved; refused entries are not offered. */
|
|
31
45
|
policy?: CredentialPolicy;
|
|
46
|
+
/** Local encrypted store for `keyring:` references; else `FORGE_CREDENTIAL_STORE` of the env. */
|
|
47
|
+
path?: string;
|
|
48
|
+
/** 64-char hex key of the local store; else `FORGE_CREDENTIAL_KEY` of the env. */
|
|
49
|
+
key?: string;
|
|
32
50
|
}
|
|
33
51
|
/**
|
|
34
|
-
* The
|
|
35
|
-
* order. With a policy, entries it
|
|
52
|
+
* The models the registry declares (`model_map` / `capabilities`) as editor
|
|
53
|
+
* model profiles, in registry order — no network. With a policy, entries it
|
|
54
|
+
* refuses are left out. {@link createEditorModelProvider} lists these plus the
|
|
55
|
+
* models discovered live.
|
|
36
56
|
*/
|
|
37
57
|
export declare function editorModelProfiles(registry: AIProviderRegistryView, options?: EditorModelPolicyOptions): ModelProfile[];
|
|
58
|
+
/**
|
|
59
|
+
* Declared and discovered models as editor profiles, in registry order, one
|
|
60
|
+
* profile per selector.
|
|
61
|
+
*
|
|
62
|
+
* An entry's models list is asked when the entry declares `discovery` (and not
|
|
63
|
+
* `type: none`) or declares no models; it is asked with the credential the
|
|
64
|
+
* policy allows, exactly as inference resolves it. A failed or unsupported
|
|
65
|
+
* list leaves that entry with its declared models. A discovered id that a
|
|
66
|
+
* declared model already names (as selector or as mapped provider id) is
|
|
67
|
+
* dropped: the declared identity wins.
|
|
68
|
+
*/
|
|
69
|
+
export declare function listEditorModelProfiles(registry: AIProviderRegistryView, env: Record<string, string | undefined>, options?: EditorModelPolicyOptions): Promise<ModelProfile[]>;
|
|
38
70
|
/**
|
|
39
71
|
* The language model of selector `ref.preset`, or `null` when the registry does
|
|
40
|
-
* not
|
|
41
|
-
* editor's own
|
|
72
|
+
* not own it: a browser-defined `{ endpoint, model, apiKey }` reference, or a
|
|
73
|
+
* preset id whose leading segment names no registry entry, is the editor's own
|
|
74
|
+
* to resolve.
|
|
75
|
+
*
|
|
76
|
+
* Delegates to `resolveRegistryLanguageModel` (`@skaile/forge-settings/ai-sdk`),
|
|
77
|
+
* so the selector's model id may be declared or discovered: an id the entry's
|
|
78
|
+
* `model_map` maps is sent as the mapped id, any other is sent verbatim.
|
|
42
79
|
*
|
|
43
|
-
* @throws {ModelResolutionError}
|
|
44
|
-
* entry
|
|
45
|
-
*
|
|
80
|
+
* @throws {ModelResolutionError} for an owned selector: `unknown_selector` when
|
|
81
|
+
* the entry is disabled or not an `llm` entry, `policy_refused` when the
|
|
82
|
+
* policy refuses it (checked before its credential is read), `unresolved`
|
|
83
|
+
* when its credential, endpoint or SDK does not resolve. No message carries a
|
|
84
|
+
* secret.
|
|
46
85
|
*/
|
|
47
|
-
export declare function editorLanguageModel(registry: AIProviderRegistryView, env: Record<string, string | undefined>, ref: ModelRef, options?: EditorModelPolicyOptions):
|
|
86
|
+
export declare function editorLanguageModel(registry: AIProviderRegistryView, env: Record<string, string | undefined>, ref: ModelRef, options?: EditorModelPolicyOptions): Promise<LanguageModelObject | null>;
|
|
48
87
|
/** Options for {@link createEditorModelProvider}. */
|
|
49
88
|
export interface EditorModelProviderOptions extends EditorModelPolicyOptions {
|
|
50
89
|
/** The registry to serve, read on every call. */
|
|
@@ -1,63 +1,106 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolveEntryBaseUrl } from "@skaile/provider-registry";
|
|
2
|
+
import { AI_SDK_SURFACES } from "@skaile/provider-registry/ai-sdk";
|
|
3
|
+
import { resolveRegistryLanguageModel } from "../ai-sdk/index.js";
|
|
4
|
+
import { ModelResolutionError } from "../model/errors.js";
|
|
5
|
+
import { credentialStoreOptions, selectorProviderId } from "../model/selector.js";
|
|
3
6
|
import { entryPolicyRefusal } from "../server/credential-policy.js";
|
|
4
|
-
import {
|
|
7
|
+
import { discoverProviderModels } from "../server/provider-discovery.js";
|
|
5
8
|
import { listAIProviderModels } from "../server/provider-service.js";
|
|
6
9
|
import {
|
|
10
|
+
asMergedRegistry,
|
|
7
11
|
entriesOfKind
|
|
8
12
|
} from "../shared/provider-registry.js";
|
|
9
13
|
export { ModelResolutionError } from "../model/errors.js";
|
|
10
|
-
|
|
11
|
-
function editorEntries(registry) {
|
|
14
|
+
function editorEntries(registry, policy) {
|
|
12
15
|
const entries = /* @__PURE__ */ new Map();
|
|
13
16
|
for (const [id, entry] of entriesOfKind(registry, "llm")) {
|
|
14
|
-
|
|
17
|
+
const surface = entry.surface ?? "openai-completions";
|
|
18
|
+
if (!AI_SDK_SURFACES.includes(surface)) continue;
|
|
19
|
+
if (!resolveEntryBaseUrl(entry)) continue;
|
|
20
|
+
if (entryPolicyRefusal(entry, policy)) continue;
|
|
21
|
+
entries.set(id, entry);
|
|
15
22
|
}
|
|
16
23
|
return entries;
|
|
17
24
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const allowed = (providerId) => {
|
|
21
|
-
const entry = entries.get(providerId);
|
|
22
|
-
return entry !== void 0 && !entryPolicyRefusal(entry, options.policy);
|
|
23
|
-
};
|
|
24
|
-
return listAIProviderModels(registry).filter((model) => allowed(model.providerId)).map((model) => ({
|
|
25
|
+
function registryProfile(providerId, entry, model) {
|
|
26
|
+
return {
|
|
25
27
|
id: model.id,
|
|
26
|
-
label: `${
|
|
27
|
-
endpoint:
|
|
28
|
+
label: `${providerId} \xB7 ${model.name}`,
|
|
29
|
+
endpoint: resolveEntryBaseUrl(entry),
|
|
28
30
|
model: model.providerModelId,
|
|
29
31
|
source: "preset"
|
|
30
|
-
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function declaredProfiles(registry, entries) {
|
|
35
|
+
const byProvider = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const model of listAIProviderModels(registry)) {
|
|
37
|
+
const entry = entries.get(model.providerId);
|
|
38
|
+
if (!entry) continue;
|
|
39
|
+
const profile = registryProfile(model.providerId, entry, {
|
|
40
|
+
id: model.id,
|
|
41
|
+
name: model.modelId,
|
|
42
|
+
providerModelId: model.providerModelId
|
|
43
|
+
});
|
|
44
|
+
const own = byProvider.get(model.providerId);
|
|
45
|
+
if (own) own.push(profile);
|
|
46
|
+
else byProvider.set(model.providerId, [profile]);
|
|
47
|
+
}
|
|
48
|
+
return byProvider;
|
|
49
|
+
}
|
|
50
|
+
export function editorModelProfiles(registry, options = {}) {
|
|
51
|
+
return [...declaredProfiles(registry, editorEntries(registry, options.policy)).values()].flat();
|
|
31
52
|
}
|
|
32
|
-
export function
|
|
53
|
+
export async function listEditorModelProfiles(registry, env, options = {}) {
|
|
54
|
+
const entries = editorEntries(registry, options.policy);
|
|
55
|
+
const declared = declaredProfiles(registry, entries);
|
|
56
|
+
const listed = await Promise.all(
|
|
57
|
+
[...entries].map(async ([providerId, entry]) => {
|
|
58
|
+
const own = declared.get(providerId) ?? [];
|
|
59
|
+
const listable = entry.discovery !== void 0 ? entry.discovery.type !== "none" : own.length === 0;
|
|
60
|
+
if (!listable) return own;
|
|
61
|
+
const result = await discoverProviderModels(
|
|
62
|
+
providerId,
|
|
63
|
+
registry,
|
|
64
|
+
credentialStoreOptions(env, options)
|
|
65
|
+
);
|
|
66
|
+
if (result.status !== "live") return own;
|
|
67
|
+
const taken = new Set(
|
|
68
|
+
own.flatMap((profile) => [profile.id, `${providerId}/${profile.model}`])
|
|
69
|
+
);
|
|
70
|
+
const discovered = result.models.flatMap((model) => {
|
|
71
|
+
const id = `${providerId}/${model.id}`;
|
|
72
|
+
if (taken.has(id)) return [];
|
|
73
|
+
taken.add(id);
|
|
74
|
+
return [
|
|
75
|
+
registryProfile(providerId, entry, { id, name: model.name, providerModelId: model.id })
|
|
76
|
+
];
|
|
77
|
+
});
|
|
78
|
+
return [...own, ...discovered];
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
return listed.flat();
|
|
82
|
+
}
|
|
83
|
+
export async function editorLanguageModel(registry, env, ref, options = {}) {
|
|
33
84
|
if (!("preset" in ref)) return null;
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
env,
|
|
40
|
-
...options.policy ? { policy: options.policy } : {}
|
|
85
|
+
const merged = asMergedRegistry(registry);
|
|
86
|
+
const head = selectorProviderId(ref.preset);
|
|
87
|
+
if (head === void 0 || merged.providers[head] === void 0) return null;
|
|
88
|
+
const resolved = await resolveRegistryLanguageModel(ref.preset, {
|
|
89
|
+
registry: merged,
|
|
90
|
+
...credentialStoreOptions(env, options)
|
|
41
91
|
});
|
|
42
|
-
|
|
43
|
-
throw new ModelResolutionError(
|
|
44
|
-
"unresolved",
|
|
45
|
-
`provider '${model.providerId}': ${credential.error}`,
|
|
46
|
-
{ providerId: model.providerId }
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
return createOpenAICompatible({
|
|
50
|
-
name: model.providerId,
|
|
51
|
-
baseURL: entry.base_url,
|
|
52
|
-
...credential.value ? { apiKey: credential.value } : {},
|
|
53
|
-
...entry.headers ? { headers: entry.headers } : {}
|
|
54
|
-
}).chatModel(model.providerModelId);
|
|
92
|
+
return resolved.model;
|
|
55
93
|
}
|
|
56
94
|
export function createEditorModelProvider(options) {
|
|
57
95
|
return {
|
|
58
96
|
async languageModel(ref) {
|
|
59
97
|
try {
|
|
60
|
-
return
|
|
98
|
+
return await editorLanguageModel(
|
|
99
|
+
await options.registry(),
|
|
100
|
+
await options.env(),
|
|
101
|
+
ref,
|
|
102
|
+
options
|
|
103
|
+
);
|
|
61
104
|
} catch (error) {
|
|
62
105
|
if (error instanceof ModelResolutionError && options.onFailure)
|
|
63
106
|
throw options.onFailure(error);
|
|
@@ -65,8 +108,7 @@ export function createEditorModelProvider(options) {
|
|
|
65
108
|
}
|
|
66
109
|
},
|
|
67
110
|
async listModels() {
|
|
68
|
-
|
|
69
|
-
return profiles.length > 0 ? profiles : null;
|
|
111
|
+
return listEditorModelProfiles(await options.registry(), await options.env(), options);
|
|
70
112
|
}
|
|
71
113
|
};
|
|
72
114
|
}
|