@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.
Files changed (33) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/ai-sdk/index.d.ts +3 -2
  4. package/dist/runtime/ai-sdk/index.js +3 -11
  5. package/dist/runtime/editor/index.js +4 -15
  6. package/dist/runtime/model/errors.d.ts +8 -0
  7. package/dist/runtime/model/errors.js +9 -0
  8. package/dist/runtime/server/credential-policy.d.ts +2 -1
  9. package/dist/runtime/server/credential-policy.js +1 -0
  10. package/dist/runtime/server/credential-store.js +1 -1
  11. package/dist/runtime/server/env-layers.js +1 -3
  12. package/dist/runtime/server/provider-discovery.js +1 -1
  13. package/dist/runtime/server/provider-layers.js +1 -3
  14. package/dist/runtime/server/provider-write.js +2 -4
  15. package/dist/runtime/shared/guards.d.ts +2 -0
  16. package/dist/runtime/shared/guards.js +3 -0
  17. package/dist/runtime/vue/providers-panel-model.d.ts +147 -0
  18. package/dist/runtime/vue/providers-panel-model.js +37 -0
  19. package/dist/runtime/vue/useProvidersPanel.d.ts +5 -122
  20. package/dist/runtime/vue/useProvidersPanel.js +15 -37
  21. package/package.json +1 -1
  22. package/src/runtime/ai-sdk/index.ts +9 -12
  23. package/src/runtime/editor/index.ts +4 -15
  24. package/src/runtime/model/errors.ts +21 -0
  25. package/src/runtime/server/credential-policy.ts +3 -1
  26. package/src/runtime/server/credential-store.ts +1 -1
  27. package/src/runtime/server/env-layers.ts +1 -4
  28. package/src/runtime/server/provider-discovery.ts +1 -1
  29. package/src/runtime/server/provider-layers.ts +1 -4
  30. package/src/runtime/server/provider-write.ts +2 -7
  31. package/src/runtime/shared/guards.ts +4 -0
  32. package/src/runtime/vue/providers-panel-model.ts +203 -0
  33. 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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@skaile/forge-settings",
3
3
  "configKey": "forgeSettings",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "3.6.1"
@@ -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 { type CredentialPolicy } from "../server/credential-policy.js";
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 { entryPolicyRefusal } from "../server/credential-policy.js";
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 && options.policy) {
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
- cause instanceof Error ? cause.message : String(cause),
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 && !(options.policy && entryPolicyRefusal(entry, options.policy));
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
- if (options.policy) {
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
- `AI provider "${model.providerId}": ${credential.error}`,
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: CredentialPolicy): string | undefined;
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 = options.policy ? entryPolicyRefusal(entry, options.policy) : void 0;
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 = options.policy ? entryPolicyRefusal(entry, options.policy) : void 0;
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 = scope.policy ? entryPolicyRefusal(entry, scope.policy) : void 0;
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,2 @@
1
+ /** A non-null, non-array object. */
2
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,3 @@
1
+ export function isPlainObject(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
@@ -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 EnvLayerId, type EnvSettingsDocument } from "../shared/env-vars.js";
18
- import { type AIProviderCredentialKind, type AIProviderKind, type AIProviderSurface } from "../shared/provider-registry.js";
19
- /** Registry document a provider entry is defined in. */
20
- export type ProvidersPanelLayer = "instance" | "workspace";
21
- /** Props both `ForgeProvidersPanel` variants declare. */
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
- export const CUSTOM_PRESET = "__custom__";
17
- export const CREDENTIAL_ENTER_KEY = "__enter_key__";
18
- export const CREDENTIAL_OTHER_REF = "__other_ref__";
19
- const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
20
- const DEFAULT_PRESETS_URL = "/api/providers/presets";
21
- const ENV_REF_PATTERN = /^env:(.+)$/;
22
- const FALLBACK_CREDENTIAL_KIND = "api_key_header";
23
- const ENV_SOURCE_LABEL = {
24
- instance: "instance .env",
25
- workspace: "workspace .env",
26
- process: "boot environment"
27
- };
28
- const OVERRIDES_INSTANCE_NOTICE = "Entering a key here stores it in this workspace's .env and overrides that value for this workspace.";
29
- function requestErrorMessage(err, fallback) {
30
- const e = err;
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([]);