@skaile/forge-settings 1.0.0 → 1.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/CHANGELOG.md +7 -0
- package/dist/module.json +1 -1
- package/dist/runtime/ai-sdk/index.d.ts +3 -2
- package/dist/runtime/ai-sdk/index.js +3 -11
- package/dist/runtime/editor/index.js +4 -15
- package/dist/runtime/model/errors.d.ts +8 -0
- package/dist/runtime/model/errors.js +9 -0
- package/dist/runtime/server/credential-policy.d.ts +2 -1
- package/dist/runtime/server/credential-policy.js +1 -0
- package/dist/runtime/server/credential-store.js +1 -1
- package/dist/runtime/server/env-layers.js +1 -3
- package/dist/runtime/server/provider-discovery.js +1 -1
- package/dist/runtime/server/provider-layers.js +1 -3
- package/dist/runtime/server/provider-write.js +2 -4
- package/dist/runtime/shared/guards.d.ts +2 -0
- package/dist/runtime/shared/guards.js +3 -0
- package/dist/runtime/vue/providers-panel-model.d.ts +147 -0
- package/dist/runtime/vue/providers-panel-model.js +37 -0
- package/dist/runtime/vue/useProvidersPanel.d.ts +5 -122
- package/dist/runtime/vue/useProvidersPanel.js +15 -37
- package/package.json +1 -1
- package/src/runtime/ai-sdk/index.ts +9 -12
- package/src/runtime/editor/index.ts +4 -15
- package/src/runtime/model/errors.ts +21 -0
- package/src/runtime/server/credential-policy.ts +3 -1
- package/src/runtime/server/credential-store.ts +1 -1
- package/src/runtime/server/env-layers.ts +1 -4
- package/src/runtime/server/provider-discovery.ts +1 -1
- package/src/runtime/server/provider-layers.ts +1 -4
- package/src/runtime/server/provider-write.ts +2 -7
- package/src/runtime/shared/guards.ts +4 -0
- package/src/runtime/vue/providers-panel-model.ts +203 -0
- package/src/runtime/vue/useProvidersPanel.ts +35 -192
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
- `@skaile/forge-settings/ai-sdk`: an `unresolved` `ModelResolutionError` no longer copies Workspaces' error text into `message` (it could echo a resolved credential or a key in an endpoint query). The message is now fixed (`provider '<id>': credential, endpoint or provider SDK could not be resolved`); the original error stays on `cause`.
|
|
6
|
+
- Refusal wording is one format across adapters: `provider '<id>': <reason>`. `./editor` previously said `AI provider "<id>": <reason>`, for both `policy_refused` and `unresolved`.
|
|
7
|
+
- `entryPolicyRefusal(entry, policy?)` accepts an absent policy (refuses nothing), so call sites no longer branch on it.
|
|
8
|
+
- Internal: one `isPlainObject`, one `assertEntryAllowed`, and the providers panel's types and pure reference helpers moved to `vue/providers-panel-model.ts`. No public export changes.
|
|
9
|
+
|
|
3
10
|
## 1.0.0
|
|
4
11
|
|
|
5
12
|
First release. Extracted from `@skaile/forge-common` (provider/settings half) and skaile-forge's editor model adapter; `@skaile/forge-common` is unchanged and stays installable for consumers that have not migrated.
|
package/dist/module.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* one the selected surface needs.
|
|
15
15
|
*/
|
|
16
16
|
import { type ResolvedLanguageModel } from "@skaile/workspaces/ai-sdk";
|
|
17
|
-
import {
|
|
17
|
+
import type { CredentialPolicy } from "../server/credential-policy.js";
|
|
18
18
|
export { ModelResolutionError } from "../model/errors.js";
|
|
19
19
|
export type { ModelResolutionCode } from "../model/errors.js";
|
|
20
20
|
export type { ResolvedLanguageModel };
|
|
@@ -40,6 +40,7 @@ export interface ResolveRegistryLanguageModelOptions {
|
|
|
40
40
|
* `@role`), resolved by Workspaces against the registry on disk.
|
|
41
41
|
*
|
|
42
42
|
* @throws {ModelResolutionError} `registry_invalid` · `unknown_selector` ·
|
|
43
|
-
* `policy_refused` · `unresolved` (credential, endpoint or provider SDK
|
|
43
|
+
* `policy_refused` · `unresolved` (credential, endpoint or provider SDK; the
|
|
44
|
+
* message is fixed and never carries Workspaces' text, which stays on `cause`).
|
|
44
45
|
*/
|
|
45
46
|
export declare function resolveRegistryLanguageModel(selector: string, options: ResolveRegistryLanguageModelOptions): Promise<ResolvedLanguageModel>;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { parseModelRef } from "@skaile/workspaces/core";
|
|
2
2
|
import { resolveLanguageModel } from "@skaile/workspaces/ai-sdk";
|
|
3
3
|
import { loadMergedProviderRegistry } from "@skaile/workspaces/sdk/bridge";
|
|
4
|
-
import {
|
|
5
|
-
import { ModelResolutionError } from "../model/errors.js";
|
|
4
|
+
import { ModelResolutionError, assertEntryAllowed } from "../model/errors.js";
|
|
6
5
|
export { ModelResolutionError } from "../model/errors.js";
|
|
7
6
|
export async function resolveRegistryLanguageModel(selector, options) {
|
|
8
7
|
const { merged, diagnostics } = loadMergedProviderRegistry({ projectDir: options.projectDir });
|
|
@@ -15,14 +14,7 @@ export async function resolveRegistryLanguageModel(selector, options) {
|
|
|
15
14
|
throw new ModelResolutionError("unknown_selector", `no provider serves '${selector}'`);
|
|
16
15
|
}
|
|
17
16
|
const entry = merged.providers[ref.providerId];
|
|
18
|
-
if (entry
|
|
19
|
-
const refusal = entryPolicyRefusal(entry, options.policy);
|
|
20
|
-
if (refusal) {
|
|
21
|
-
throw new ModelResolutionError("policy_refused", `provider '${ref.providerId}': ${refusal}`, {
|
|
22
|
-
providerId: ref.providerId
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
17
|
+
if (entry) assertEntryAllowed(entry, ref.providerId, options.policy);
|
|
26
18
|
try {
|
|
27
19
|
return await resolveLanguageModel(selector, {
|
|
28
20
|
projectDir: options.projectDir,
|
|
@@ -32,7 +24,7 @@ export async function resolveRegistryLanguageModel(selector, options) {
|
|
|
32
24
|
} catch (cause) {
|
|
33
25
|
throw new ModelResolutionError(
|
|
34
26
|
"unresolved",
|
|
35
|
-
|
|
27
|
+
`provider '${ref.providerId}': credential, endpoint or provider SDK could not be resolved`,
|
|
36
28
|
{ providerId: ref.providerId, cause }
|
|
37
29
|
);
|
|
38
30
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
2
|
-
import { ModelResolutionError } from "../model/errors.js";
|
|
2
|
+
import { ModelResolutionError, assertEntryAllowed } from "../model/errors.js";
|
|
3
3
|
import { entryPolicyRefusal } from "../server/credential-policy.js";
|
|
4
4
|
import { resolveEntryCredential } from "../server/credential-store.js";
|
|
5
5
|
import { listAIProviderModels } from "../server/provider-service.js";
|
|
@@ -19,7 +19,7 @@ export function editorModelProfiles(registry, options = {}) {
|
|
|
19
19
|
const entries = editorEntries(registry);
|
|
20
20
|
const allowed = (providerId) => {
|
|
21
21
|
const entry = entries.get(providerId);
|
|
22
|
-
return entry !== void 0 && !
|
|
22
|
+
return entry !== void 0 && !entryPolicyRefusal(entry, options.policy);
|
|
23
23
|
};
|
|
24
24
|
return listAIProviderModels(registry).filter((model) => allowed(model.providerId)).map((model) => ({
|
|
25
25
|
id: model.id,
|
|
@@ -34,18 +34,7 @@ export function editorLanguageModel(registry, env, ref, options = {}) {
|
|
|
34
34
|
const model = listAIProviderModels(registry).find((candidate) => candidate.id === ref.preset);
|
|
35
35
|
const entry = model && editorEntries(registry).get(model.providerId);
|
|
36
36
|
if (!model || !entry) return null;
|
|
37
|
-
|
|
38
|
-
const refusal = entryPolicyRefusal(entry, options.policy);
|
|
39
|
-
if (refusal) {
|
|
40
|
-
throw new ModelResolutionError(
|
|
41
|
-
"policy_refused",
|
|
42
|
-
`AI provider "${model.providerId}": ${refusal}`,
|
|
43
|
-
{
|
|
44
|
-
providerId: model.providerId
|
|
45
|
-
}
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
37
|
+
assertEntryAllowed(entry, model.providerId, options.policy);
|
|
49
38
|
const credential = resolveEntryCredential(entry, {
|
|
50
39
|
env,
|
|
51
40
|
...options.policy ? { policy: options.policy } : {}
|
|
@@ -53,7 +42,7 @@ export function editorLanguageModel(registry, env, ref, options = {}) {
|
|
|
53
42
|
if (credential.error && !credential.value) {
|
|
54
43
|
throw new ModelResolutionError(
|
|
55
44
|
"unresolved",
|
|
56
|
-
`
|
|
45
|
+
`provider '${model.providerId}': ${credential.error}`,
|
|
57
46
|
{ providerId: model.providerId }
|
|
58
47
|
);
|
|
59
48
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type CredentialPolicy } from "../server/credential-policy.js";
|
|
2
|
+
import type { AIProviderEntry } from "../shared/provider-registry.js";
|
|
1
3
|
/**
|
|
2
4
|
* A model could not be resolved. Framework-free: the host decides which HTTP
|
|
3
5
|
* status (or tool error) each `code` becomes.
|
|
@@ -22,3 +24,9 @@ export declare class ModelResolutionError extends Error {
|
|
|
22
24
|
cause?: unknown;
|
|
23
25
|
});
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Throw `policy_refused` when `policy` refuses `entry`; no policy refuses
|
|
29
|
+
* nothing. The one place a model adapter turns a policy refusal into an error,
|
|
30
|
+
* so every adapter words it the same way. Nothing is resolved.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertEntryAllowed(entry: AIProviderEntry, providerId: string, policy?: CredentialPolicy): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { entryPolicyRefusal } from "../server/credential-policy.js";
|
|
1
2
|
export class ModelResolutionError extends Error {
|
|
2
3
|
code;
|
|
3
4
|
providerId;
|
|
@@ -8,3 +9,11 @@ export class ModelResolutionError extends Error {
|
|
|
8
9
|
if (options.providerId !== void 0) this.providerId = options.providerId;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
12
|
+
export function assertEntryAllowed(entry, providerId, policy) {
|
|
13
|
+
const refusal = entryPolicyRefusal(entry, policy);
|
|
14
|
+
if (refusal) {
|
|
15
|
+
throw new ModelResolutionError("policy_refused", `provider '${providerId}': ${refusal}`, {
|
|
16
|
+
providerId
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -48,8 +48,9 @@ export declare function credentialRefRefusal(ref: string, policy: CredentialPoli
|
|
|
48
48
|
/**
|
|
49
49
|
* The reason an entry is refused, or `undefined`: its credential reference and
|
|
50
50
|
* every environment name it interpolates are checked, nothing is resolved.
|
|
51
|
+
* Without a policy nothing is refused.
|
|
51
52
|
*/
|
|
52
|
-
export declare function entryPolicyRefusal(entry: AIProviderEntry, policy
|
|
53
|
+
export declare function entryPolicyRefusal(entry: AIProviderEntry, policy?: CredentialPolicy): string | undefined;
|
|
53
54
|
/**
|
|
54
55
|
* The registry without the entries the policy refuses, as a plain entry map in
|
|
55
56
|
* registry order, plus what was refused. Nothing is resolved.
|
|
@@ -30,6 +30,7 @@ function interpolationRefusal(field, value, policy) {
|
|
|
30
30
|
return void 0;
|
|
31
31
|
}
|
|
32
32
|
export function entryPolicyRefusal(entry, policy) {
|
|
33
|
+
if (!policy) return void 0;
|
|
33
34
|
const ref = entry.credential?.ref;
|
|
34
35
|
if (ref !== void 0) {
|
|
35
36
|
const refusal = credentialRefRefusal(ref, policy);
|
|
@@ -186,7 +186,7 @@ export function resolveCredentialRef(ref, options = {}) {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
export function resolveEntryCredential(entry, options = {}) {
|
|
189
|
-
const refusal =
|
|
189
|
+
const refusal = entryPolicyRefusal(entry, options.policy);
|
|
190
190
|
if (refusal) return { source: "none", error: refusal };
|
|
191
191
|
const credential = entry.credential;
|
|
192
192
|
if (!credential || credential.kind === "none") return { source: "none" };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { AI_PROVIDER_PRESETS } from "@skaile/workspaces/sdk/bridge";
|
|
4
|
+
import { isPlainObject } from "../shared/guards.js";
|
|
4
5
|
import { parseCredentialRef, registryEntries } from "../shared/provider-registry.js";
|
|
5
6
|
import {
|
|
6
7
|
ENV_LAYER_ORDER,
|
|
@@ -153,9 +154,6 @@ export function buildEnvSettingsDocument(input) {
|
|
|
153
154
|
}
|
|
154
155
|
return { scope: input.scope, editable: input.editable, layer: input.scope, vars, missing };
|
|
155
156
|
}
|
|
156
|
-
function isPlainObject(value) {
|
|
157
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
158
|
-
}
|
|
159
157
|
export function validateEnvSettingsUpdate(body) {
|
|
160
158
|
if (!isPlainObject(body)) return "Body must be an object with `set` and/or `unset`";
|
|
161
159
|
const set = {};
|
|
@@ -27,7 +27,7 @@ export async function discoverProviderModels(providerId, registry, options) {
|
|
|
27
27
|
return { status: "unsupported", providerId, reason: "only llm entries list models" };
|
|
28
28
|
}
|
|
29
29
|
if (!isEntryEnabled(entry)) return { status: "error", providerId, error: "entry is disabled" };
|
|
30
|
-
const refusal =
|
|
30
|
+
const refusal = entryPolicyRefusal(entry, options.policy);
|
|
31
31
|
if (refusal) return { status: "error", providerId, error: refusal };
|
|
32
32
|
const credential = resolveEntryCredential(entry, {
|
|
33
33
|
env: options.env,
|
|
@@ -5,10 +5,8 @@ import {
|
|
|
5
5
|
} from "@skaile/workspaces/core";
|
|
6
6
|
import { AI_PROVIDER_PRESETS } from "@skaile/workspaces/sdk/bridge";
|
|
7
7
|
import { parse as parseYaml } from "yaml";
|
|
8
|
+
import { isPlainObject } from "../shared/guards.js";
|
|
8
9
|
const PROVIDERS_FILENAME_ALIAS = "providers.yaml";
|
|
9
|
-
function isPlainObject(value) {
|
|
10
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
-
}
|
|
12
10
|
export function providerLayerDocument(path) {
|
|
13
11
|
if (existsSync(path)) return path;
|
|
14
12
|
const alias = `${path.slice(0, -PROVIDERS_FILENAME.length)}${PROVIDERS_FILENAME_ALIAS}`;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
AI_PROVIDER_PRESETS,
|
|
11
11
|
expandPreset
|
|
12
12
|
} from "@skaile/workspaces/sdk/bridge";
|
|
13
|
+
import { isPlainObject } from "../shared/guards.js";
|
|
13
14
|
import { entryPolicyRefusal } from "./credential-policy.js";
|
|
14
15
|
import { catalogPreset, readProviderLayer, writeProviderLayer } from "./provider-layers.js";
|
|
15
16
|
export class ProviderWriteError extends Error {
|
|
@@ -46,9 +47,6 @@ export function providerScopeWritable(scope, caller) {
|
|
|
46
47
|
if (caller.isAdmin) return true;
|
|
47
48
|
return scope.name !== "instance" && !scope.lock;
|
|
48
49
|
}
|
|
49
|
-
function isPlainObject(value) {
|
|
50
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
-
}
|
|
52
50
|
function mergeLayerOf(scope) {
|
|
53
51
|
return scope.name === "instance" ? "user" : "workspace";
|
|
54
52
|
}
|
|
@@ -154,7 +152,7 @@ export function upsertProviderEntry(scope, id, body) {
|
|
|
154
152
|
entry = { ...kept, ...fields };
|
|
155
153
|
}
|
|
156
154
|
const next = { ...providers, [id]: entry };
|
|
157
|
-
const refusal =
|
|
155
|
+
const refusal = entryPolicyRefusal(entry, scope.policy);
|
|
158
156
|
if (refusal) throw createError({ statusCode: 400, statusMessage: refusal });
|
|
159
157
|
const diagnostics = entryErrors(scope, next, id);
|
|
160
158
|
if (diagnostics.length > 0) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The providers panel's vocabulary: the types the panel and its composable
|
|
3
|
+
* share, the constants of its form, and the pure reference / error helpers.
|
|
4
|
+
* No Vue, no requests — testable on their own. The public types and constants
|
|
5
|
+
* are re-exported by `useProvidersPanel`; the rest is internal to the package.
|
|
6
|
+
*/
|
|
7
|
+
import type { EnvLayerId } from "../shared/env-vars.js";
|
|
8
|
+
import type { AIProviderCredentialKind, AIProviderKind, AIProviderSurface } from "../shared/provider-registry.js";
|
|
9
|
+
/** Registry document a provider entry is defined in. */
|
|
10
|
+
export type ProvidersPanelLayer = "instance" | "workspace";
|
|
11
|
+
/** Props both `ForgeProvidersPanel` variants declare. */
|
|
12
|
+
export interface ProvidersPanelProps {
|
|
13
|
+
/** `GET` merged view for this scope (`/api/providers` | `/api/workspaces/:id/providers`). */
|
|
14
|
+
providersUrl: string;
|
|
15
|
+
/** `PUT|DELETE ${providersWriteUrl}/:id`; `GET ${providersWriteUrl}/detected`. */
|
|
16
|
+
providersWriteUrl: string;
|
|
17
|
+
/** Catalog preset summaries. Default `/api/providers/presets`. */
|
|
18
|
+
presetsUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The scope's `.env` route. `GET` on load and after every write; keys typed
|
|
21
|
+
* into the panel write through it. Omitted, no key can be entered.
|
|
22
|
+
*/
|
|
23
|
+
environmentUrl?: string;
|
|
24
|
+
/** The scope's own registry document. */
|
|
25
|
+
layer: ProvidersPanelLayer;
|
|
26
|
+
/** Disables every write (non-admin at instance scope; provider lock). */
|
|
27
|
+
readOnly?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Emits both variants declare. */
|
|
30
|
+
export type ProvidersPanelEmit = (event: "changed") => void;
|
|
31
|
+
/** One entry of `GET ${providersUrl}?all=1`. */
|
|
32
|
+
export interface ProvidersPanelEntry {
|
|
33
|
+
id: string;
|
|
34
|
+
/** The entry's label, else its id. */
|
|
35
|
+
name: string;
|
|
36
|
+
kind: AIProviderKind;
|
|
37
|
+
/** Whether the credential reference resolved on the server. */
|
|
38
|
+
configured: boolean;
|
|
39
|
+
/** Why it did not resolve. Secret-free. */
|
|
40
|
+
error?: string;
|
|
41
|
+
credentialRef?: string;
|
|
42
|
+
credentialKind?: AIProviderCredentialKind;
|
|
43
|
+
hasEnvKey: boolean;
|
|
44
|
+
envKeyName?: string;
|
|
45
|
+
baseUrl?: string;
|
|
46
|
+
preset?: string;
|
|
47
|
+
surface?: AIProviderSurface;
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
locked: boolean;
|
|
50
|
+
/** Registry documents holding the id, lowest first: `["instance"]`, `["workspace"]` or both. */
|
|
51
|
+
layers: ProvidersPanelLayer[];
|
|
52
|
+
/** The scope's own document's values for the editable fields the dialog does not show. */
|
|
53
|
+
layerFields?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/** One catalog preset, as `GET /api/providers/presets` and `.../detected` list it. */
|
|
56
|
+
export interface ProvidersPanelPreset {
|
|
57
|
+
id: string;
|
|
58
|
+
label: string;
|
|
59
|
+
kind: AIProviderKind;
|
|
60
|
+
surface?: AIProviderSurface;
|
|
61
|
+
base_url?: string;
|
|
62
|
+
base_url_env?: string;
|
|
63
|
+
credential: {
|
|
64
|
+
kind: AIProviderCredentialKind;
|
|
65
|
+
env?: string;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Where a row's entry lives relative to the scope's own document. */
|
|
69
|
+
export type ProvidersPanelRowState = "own" | "inherited" | "patched";
|
|
70
|
+
/** One entry of a row's credential select. */
|
|
71
|
+
export interface ProvidersPanelCredentialOption {
|
|
72
|
+
/** An env var NAME, a literal non-env ref, {@link CREDENTIAL_ENTER_KEY} or {@link CREDENTIAL_OTHER_REF}. */
|
|
73
|
+
value: string;
|
|
74
|
+
/** `NAME ••••hint` | `NAME (not set)` | the literal ref | `Enter a key…` | `Other reference…`. */
|
|
75
|
+
label: string;
|
|
76
|
+
defined?: boolean;
|
|
77
|
+
hint?: string;
|
|
78
|
+
source?: EnvLayerId;
|
|
79
|
+
action?: "enter-key" | "other-ref";
|
|
80
|
+
}
|
|
81
|
+
/** A table row: the entry plus what the panel derives for it. */
|
|
82
|
+
export interface ProvidersPanelRow extends ProvidersPanelEntry {
|
|
83
|
+
/** Own entry, an inherited one, or an inherited one this scope patched. */
|
|
84
|
+
state: ProvidersPanelRowState;
|
|
85
|
+
/** `instance` | `workspace` | `instance · overridden here`. */
|
|
86
|
+
badge: string;
|
|
87
|
+
/** Set on inherited rows. */
|
|
88
|
+
inheritedNote?: string;
|
|
89
|
+
/** Edit/Delete offered: the scope's document defines it and the panel is writable. */
|
|
90
|
+
editable: boolean;
|
|
91
|
+
/** Reset offered: a patched row of a writable panel. */
|
|
92
|
+
canReset: boolean;
|
|
93
|
+
/** The enabled switch is live. */
|
|
94
|
+
canToggle: boolean;
|
|
95
|
+
/** The entry takes a credential (its kind is not `none`). */
|
|
96
|
+
needsCredential: boolean;
|
|
97
|
+
/** The credential picker is live. */
|
|
98
|
+
canEditCredential: boolean;
|
|
99
|
+
/** The select's model: the env var NAME of an `env:` ref, else the literal ref, else `""`. */
|
|
100
|
+
credentialValue: string;
|
|
101
|
+
credentialOptions: ProvidersPanelCredentialOption[];
|
|
102
|
+
/** The preset it inherits, else its surface, else its kind. */
|
|
103
|
+
origin: string;
|
|
104
|
+
/** `configured · <source>`, `missing <VAR>`, the resolver's reason, or `No credential needed.`. */
|
|
105
|
+
status: string;
|
|
106
|
+
/** Which other layer defines or overrides the credential; see the panel contract. */
|
|
107
|
+
overrideNotices: string[];
|
|
108
|
+
}
|
|
109
|
+
/** The add/edit dialog's fields. */
|
|
110
|
+
export interface ProvidersPanelForm {
|
|
111
|
+
/** A preset id, or {@link CUSTOM_PRESET}. */
|
|
112
|
+
preset: string;
|
|
113
|
+
id: string;
|
|
114
|
+
/** Custom entries only. */
|
|
115
|
+
surface: string;
|
|
116
|
+
/** Empty: the preset's endpoint. */
|
|
117
|
+
baseUrl: string;
|
|
118
|
+
/** Adding only: the `env:` variable the credential references. */
|
|
119
|
+
envName: string;
|
|
120
|
+
/** Adding only, "Set value now": written to the scope's `.env`, never to the provider route. */
|
|
121
|
+
secret: string;
|
|
122
|
+
}
|
|
123
|
+
/** The preset select's value for an entry that names no preset. */
|
|
124
|
+
export declare const CUSTOM_PRESET = "__custom__";
|
|
125
|
+
/** Credential select value: open the inline key editor. */
|
|
126
|
+
export declare const CREDENTIAL_ENTER_KEY = "__enter_key__";
|
|
127
|
+
/** Credential select value: open the inline reference editor. */
|
|
128
|
+
export declare const CREDENTIAL_OTHER_REF = "__other_ref__";
|
|
129
|
+
/** Registry ids the write routes accept (`isSafeProviderId` upstream). */
|
|
130
|
+
export declare const PROVIDER_ID_PATTERN: RegExp;
|
|
131
|
+
export declare const DEFAULT_PRESETS_URL = "/api/providers/presets";
|
|
132
|
+
/** An `env:NAME` credential reference; group 1 is the variable name. */
|
|
133
|
+
export declare const ENV_REF_PATTERN: RegExp;
|
|
134
|
+
/** The credential kind a written reference gets when the row states none. */
|
|
135
|
+
export declare const FALLBACK_CREDENTIAL_KIND: AIProviderCredentialKind;
|
|
136
|
+
export declare const ENV_SOURCE_LABEL: Record<EnvLayerId, string>;
|
|
137
|
+
export declare const OVERRIDES_INSTANCE_NOTICE = "Entering a key here stores it in this workspace's .env and overrides that value for this workspace.";
|
|
138
|
+
/**
|
|
139
|
+
* The server's refusal for a failed request, else `fallback`. A registry
|
|
140
|
+
* rejection (`400` with `data.diagnostics`) reads as its diagnostics' messages.
|
|
141
|
+
*/
|
|
142
|
+
export declare function requestErrorMessage(err: unknown, fallback: string): string;
|
|
143
|
+
/** The variable name of an `env:NAME` reference. */
|
|
144
|
+
export declare function envNameOfRef(ref: string | undefined): string | undefined;
|
|
145
|
+
/** `configured · …` for a reference that is not an env var. */
|
|
146
|
+
export declare function nonEnvSourceLabel(ref: string): string | undefined;
|
|
147
|
+
export declare const EMPTY_FORM: Readonly<ProvidersPanelForm>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const CUSTOM_PRESET = "__custom__";
|
|
2
|
+
export const CREDENTIAL_ENTER_KEY = "__enter_key__";
|
|
3
|
+
export const CREDENTIAL_OTHER_REF = "__other_ref__";
|
|
4
|
+
export const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
5
|
+
export const DEFAULT_PRESETS_URL = "/api/providers/presets";
|
|
6
|
+
export const ENV_REF_PATTERN = /^env:(.+)$/;
|
|
7
|
+
export const FALLBACK_CREDENTIAL_KIND = "api_key_header";
|
|
8
|
+
export const ENV_SOURCE_LABEL = {
|
|
9
|
+
instance: "instance .env",
|
|
10
|
+
workspace: "workspace .env",
|
|
11
|
+
process: "boot environment"
|
|
12
|
+
};
|
|
13
|
+
export const OVERRIDES_INSTANCE_NOTICE = "Entering a key here stores it in this workspace's .env and overrides that value for this workspace.";
|
|
14
|
+
export function requestErrorMessage(err, fallback) {
|
|
15
|
+
const e = err;
|
|
16
|
+
const details = (e?.data?.data?.diagnostics ?? []).map((d) => d.message).filter((m) => typeof m === "string" && m !== "");
|
|
17
|
+
if (details.length > 0) return details.join("; ");
|
|
18
|
+
const message = e?.data?.statusMessage ?? e?.statusMessage;
|
|
19
|
+
return typeof message === "string" && message ? message : fallback;
|
|
20
|
+
}
|
|
21
|
+
export function envNameOfRef(ref) {
|
|
22
|
+
return ref ? ENV_REF_PATTERN.exec(ref)?.[1] : void 0;
|
|
23
|
+
}
|
|
24
|
+
export function nonEnvSourceLabel(ref) {
|
|
25
|
+
if (ref.startsWith("keyring:")) return "keyring";
|
|
26
|
+
if (ref.startsWith("cmd:")) return "runtime-owned (cmd)";
|
|
27
|
+
if (ref.startsWith("platform:")) return "control-plane-owned (platform)";
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
export const EMPTY_FORM = {
|
|
31
|
+
preset: "",
|
|
32
|
+
id: "",
|
|
33
|
+
surface: "",
|
|
34
|
+
baseUrl: "",
|
|
35
|
+
envName: "",
|
|
36
|
+
secret: ""
|
|
37
|
+
};
|
|
@@ -14,128 +14,11 @@
|
|
|
14
14
|
* value.
|
|
15
15
|
*/
|
|
16
16
|
import { type ComputedRef, type Ref, type WritableComputedRef } from "vue";
|
|
17
|
-
import { type
|
|
18
|
-
import { type
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
export interface ProvidersPanelProps {
|
|
23
|
-
/** `GET` merged view for this scope (`/api/providers` | `/api/workspaces/:id/providers`). */
|
|
24
|
-
providersUrl: string;
|
|
25
|
-
/** `PUT|DELETE ${providersWriteUrl}/:id`; `GET ${providersWriteUrl}/detected`. */
|
|
26
|
-
providersWriteUrl: string;
|
|
27
|
-
/** Catalog preset summaries. Default `/api/providers/presets`. */
|
|
28
|
-
presetsUrl?: string;
|
|
29
|
-
/**
|
|
30
|
-
* The scope's `.env` route. `GET` on load and after every write; keys typed
|
|
31
|
-
* into the panel write through it. Omitted, no key can be entered.
|
|
32
|
-
*/
|
|
33
|
-
environmentUrl?: string;
|
|
34
|
-
/** The scope's own registry document. */
|
|
35
|
-
layer: ProvidersPanelLayer;
|
|
36
|
-
/** Disables every write (non-admin at instance scope; provider lock). */
|
|
37
|
-
readOnly?: boolean;
|
|
38
|
-
}
|
|
39
|
-
/** Emits both variants declare. */
|
|
40
|
-
export type ProvidersPanelEmit = (event: "changed") => void;
|
|
41
|
-
/** One entry of `GET ${providersUrl}?all=1`. */
|
|
42
|
-
export interface ProvidersPanelEntry {
|
|
43
|
-
id: string;
|
|
44
|
-
/** The entry's label, else its id. */
|
|
45
|
-
name: string;
|
|
46
|
-
kind: AIProviderKind;
|
|
47
|
-
/** Whether the credential reference resolved on the server. */
|
|
48
|
-
configured: boolean;
|
|
49
|
-
/** Why it did not resolve. Secret-free. */
|
|
50
|
-
error?: string;
|
|
51
|
-
credentialRef?: string;
|
|
52
|
-
credentialKind?: AIProviderCredentialKind;
|
|
53
|
-
hasEnvKey: boolean;
|
|
54
|
-
envKeyName?: string;
|
|
55
|
-
baseUrl?: string;
|
|
56
|
-
preset?: string;
|
|
57
|
-
surface?: AIProviderSurface;
|
|
58
|
-
enabled: boolean;
|
|
59
|
-
locked: boolean;
|
|
60
|
-
/** Registry documents holding the id, lowest first: `["instance"]`, `["workspace"]` or both. */
|
|
61
|
-
layers: ProvidersPanelLayer[];
|
|
62
|
-
/** The scope's own document's values for the editable fields the dialog does not show. */
|
|
63
|
-
layerFields?: Record<string, unknown>;
|
|
64
|
-
}
|
|
65
|
-
/** One catalog preset, as `GET /api/providers/presets` and `.../detected` list it. */
|
|
66
|
-
export interface ProvidersPanelPreset {
|
|
67
|
-
id: string;
|
|
68
|
-
label: string;
|
|
69
|
-
kind: AIProviderKind;
|
|
70
|
-
surface?: AIProviderSurface;
|
|
71
|
-
base_url?: string;
|
|
72
|
-
base_url_env?: string;
|
|
73
|
-
credential: {
|
|
74
|
-
kind: AIProviderCredentialKind;
|
|
75
|
-
env?: string;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/** Where a row's entry lives relative to the scope's own document. */
|
|
79
|
-
export type ProvidersPanelRowState = "own" | "inherited" | "patched";
|
|
80
|
-
/** One entry of a row's credential select. */
|
|
81
|
-
export interface ProvidersPanelCredentialOption {
|
|
82
|
-
/** An env var NAME, a literal non-env ref, {@link CREDENTIAL_ENTER_KEY} or {@link CREDENTIAL_OTHER_REF}. */
|
|
83
|
-
value: string;
|
|
84
|
-
/** `NAME ••••hint` | `NAME (not set)` | the literal ref | `Enter a key…` | `Other reference…`. */
|
|
85
|
-
label: string;
|
|
86
|
-
defined?: boolean;
|
|
87
|
-
hint?: string;
|
|
88
|
-
source?: EnvLayerId;
|
|
89
|
-
action?: "enter-key" | "other-ref";
|
|
90
|
-
}
|
|
91
|
-
/** A table row: the entry plus what the panel derives for it. */
|
|
92
|
-
export interface ProvidersPanelRow extends ProvidersPanelEntry {
|
|
93
|
-
/** Own entry, an inherited one, or an inherited one this scope patched. */
|
|
94
|
-
state: ProvidersPanelRowState;
|
|
95
|
-
/** `instance` | `workspace` | `instance · overridden here`. */
|
|
96
|
-
badge: string;
|
|
97
|
-
/** Set on inherited rows. */
|
|
98
|
-
inheritedNote?: string;
|
|
99
|
-
/** Edit/Delete offered: the scope's document defines it and the panel is writable. */
|
|
100
|
-
editable: boolean;
|
|
101
|
-
/** Reset offered: a patched row of a writable panel. */
|
|
102
|
-
canReset: boolean;
|
|
103
|
-
/** The enabled switch is live. */
|
|
104
|
-
canToggle: boolean;
|
|
105
|
-
/** The entry takes a credential (its kind is not `none`). */
|
|
106
|
-
needsCredential: boolean;
|
|
107
|
-
/** The credential picker is live. */
|
|
108
|
-
canEditCredential: boolean;
|
|
109
|
-
/** The select's model: the env var NAME of an `env:` ref, else the literal ref, else `""`. */
|
|
110
|
-
credentialValue: string;
|
|
111
|
-
credentialOptions: ProvidersPanelCredentialOption[];
|
|
112
|
-
/** The preset it inherits, else its surface, else its kind. */
|
|
113
|
-
origin: string;
|
|
114
|
-
/** `configured · <source>`, `missing <VAR>`, the resolver's reason, or `No credential needed.`. */
|
|
115
|
-
status: string;
|
|
116
|
-
/** Which other layer defines or overrides the credential; see the panel contract. */
|
|
117
|
-
overrideNotices: string[];
|
|
118
|
-
}
|
|
119
|
-
/** The add/edit dialog's fields. */
|
|
120
|
-
export interface ProvidersPanelForm {
|
|
121
|
-
/** A preset id, or {@link CUSTOM_PRESET}. */
|
|
122
|
-
preset: string;
|
|
123
|
-
id: string;
|
|
124
|
-
/** Custom entries only. */
|
|
125
|
-
surface: string;
|
|
126
|
-
/** Empty: the preset's endpoint. */
|
|
127
|
-
baseUrl: string;
|
|
128
|
-
/** Adding only: the `env:` variable the credential references. */
|
|
129
|
-
envName: string;
|
|
130
|
-
/** Adding only, "Set value now": written to the scope's `.env`, never to the provider route. */
|
|
131
|
-
secret: string;
|
|
132
|
-
}
|
|
133
|
-
/** The preset select's value for an entry that names no preset. */
|
|
134
|
-
export declare const CUSTOM_PRESET = "__custom__";
|
|
135
|
-
/** Credential select value: open the inline key editor. */
|
|
136
|
-
export declare const CREDENTIAL_ENTER_KEY = "__enter_key__";
|
|
137
|
-
/** Credential select value: open the inline reference editor. */
|
|
138
|
-
export declare const CREDENTIAL_OTHER_REF = "__other_ref__";
|
|
17
|
+
import { type EnvSettingsDocument } from "../shared/env-vars.js";
|
|
18
|
+
import { type AIProviderSurface } from "../shared/provider-registry.js";
|
|
19
|
+
import { type ProvidersPanelEntry, type ProvidersPanelEmit, type ProvidersPanelForm, type ProvidersPanelPreset, type ProvidersPanelProps, type ProvidersPanelRow } from "./providers-panel-model.js";
|
|
20
|
+
export { CREDENTIAL_ENTER_KEY, CREDENTIAL_OTHER_REF, CUSTOM_PRESET } from "./providers-panel-model.js";
|
|
21
|
+
export type { ProvidersPanelCredentialOption, ProvidersPanelEmit, ProvidersPanelEntry, ProvidersPanelForm, ProvidersPanelLayer, ProvidersPanelPreset, ProvidersPanelProps, ProvidersPanelRow, ProvidersPanelRowState, } from "./providers-panel-model.js";
|
|
139
22
|
/** What both `ForgeProvidersPanel` variants bind to. */
|
|
140
23
|
export interface ProvidersPanelState {
|
|
141
24
|
/** The scope's entries, disabled ones included, in registry order. */
|
|
@@ -13,43 +13,21 @@ import {
|
|
|
13
13
|
AI_PROVIDER_SURFACES,
|
|
14
14
|
validateCredentialRef
|
|
15
15
|
} from "../shared/provider-registry.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const details = (e?.data?.data?.diagnostics ?? []).map((d) => d.message).filter((m) => typeof m === "string" && m !== "");
|
|
32
|
-
if (details.length > 0) return details.join("; ");
|
|
33
|
-
const message = e?.data?.statusMessage ?? e?.statusMessage;
|
|
34
|
-
return typeof message === "string" && message ? message : fallback;
|
|
35
|
-
}
|
|
36
|
-
function envNameOfRef(ref2) {
|
|
37
|
-
return ref2 ? ENV_REF_PATTERN.exec(ref2)?.[1] : void 0;
|
|
38
|
-
}
|
|
39
|
-
function nonEnvSourceLabel(ref2) {
|
|
40
|
-
if (ref2.startsWith("keyring:")) return "keyring";
|
|
41
|
-
if (ref2.startsWith("cmd:")) return "runtime-owned (cmd)";
|
|
42
|
-
if (ref2.startsWith("platform:")) return "control-plane-owned (platform)";
|
|
43
|
-
return void 0;
|
|
44
|
-
}
|
|
45
|
-
const EMPTY_FORM = {
|
|
46
|
-
preset: "",
|
|
47
|
-
id: "",
|
|
48
|
-
surface: "",
|
|
49
|
-
baseUrl: "",
|
|
50
|
-
envName: "",
|
|
51
|
-
secret: ""
|
|
52
|
-
};
|
|
16
|
+
import {
|
|
17
|
+
CREDENTIAL_ENTER_KEY,
|
|
18
|
+
CREDENTIAL_OTHER_REF,
|
|
19
|
+
CUSTOM_PRESET,
|
|
20
|
+
DEFAULT_PRESETS_URL,
|
|
21
|
+
EMPTY_FORM,
|
|
22
|
+
ENV_SOURCE_LABEL,
|
|
23
|
+
envNameOfRef,
|
|
24
|
+
FALLBACK_CREDENTIAL_KIND,
|
|
25
|
+
nonEnvSourceLabel,
|
|
26
|
+
OVERRIDES_INSTANCE_NOTICE,
|
|
27
|
+
PROVIDER_ID_PATTERN,
|
|
28
|
+
requestErrorMessage
|
|
29
|
+
} from "./providers-panel-model.js";
|
|
30
|
+
export { CREDENTIAL_ENTER_KEY, CREDENTIAL_OTHER_REF, CUSTOM_PRESET } from "./providers-panel-model.js";
|
|
53
31
|
export function useProvidersPanel(props, emit) {
|
|
54
32
|
const providers = ref([]);
|
|
55
33
|
const presets = ref([]);
|