@skaile/forge-settings 0.0.0-stage → 1.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +94 -2
  4. package/dist/module.d.mts +17 -0
  5. package/dist/module.json +9 -0
  6. package/dist/module.mjs +23 -0
  7. package/dist/runtime/ai-sdk/index.d.ts +45 -0
  8. package/dist/runtime/ai-sdk/index.js +39 -0
  9. package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +21 -0
  10. package/dist/runtime/components/ForgeProvidersPanel.vue +435 -0
  11. package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +21 -0
  12. package/dist/runtime/editor/index.d.ts +58 -0
  13. package/dist/runtime/editor/index.js +83 -0
  14. package/dist/runtime/model/errors.d.ts +24 -0
  15. package/dist/runtime/model/errors.js +10 -0
  16. package/dist/runtime/server/credential-policy.d.ts +60 -0
  17. package/dist/runtime/server/credential-policy.js +64 -0
  18. package/dist/runtime/server/credential-store.d.ts +184 -0
  19. package/dist/runtime/server/credential-store.js +234 -0
  20. package/dist/runtime/server/env-layers.d.ts +62 -0
  21. package/dist/runtime/server/env-layers.js +185 -0
  22. package/dist/runtime/server/env.d.ts +30 -0
  23. package/dist/runtime/server/env.js +15 -0
  24. package/dist/runtime/server/harness-settings.d.ts +150 -0
  25. package/dist/runtime/server/harness-settings.js +166 -0
  26. package/dist/runtime/server/index.d.ts +23 -0
  27. package/dist/runtime/server/index.js +14 -0
  28. package/dist/runtime/server/provider-discovery.d.ts +73 -0
  29. package/dist/runtime/server/provider-discovery.js +77 -0
  30. package/dist/runtime/server/provider-layers.d.ts +27 -0
  31. package/dist/runtime/server/provider-layers.js +37 -0
  32. package/dist/runtime/server/provider-service.d.ts +126 -0
  33. package/dist/runtime/server/provider-service.js +87 -0
  34. package/dist/runtime/server/provider-settings.d.ts +78 -0
  35. package/dist/runtime/server/provider-settings.js +81 -0
  36. package/dist/runtime/server/provider-write.d.ts +154 -0
  37. package/dist/runtime/server/provider-write.js +223 -0
  38. package/dist/runtime/server/settings-defaults.d.ts +26 -0
  39. package/dist/runtime/server/settings-defaults.js +46 -0
  40. package/dist/runtime/shared/env-vars.d.ts +55 -0
  41. package/dist/runtime/shared/env-vars.js +22 -0
  42. package/dist/runtime/shared/harness-providers.d.ts +62 -0
  43. package/dist/runtime/shared/harness-providers.js +38 -0
  44. package/dist/runtime/shared/harnesses.d.ts +19 -0
  45. package/dist/runtime/shared/harnesses.js +15 -0
  46. package/dist/runtime/shared/provider-registry.d.ts +255 -0
  47. package/dist/runtime/shared/provider-registry.js +112 -0
  48. package/dist/runtime/shared/settings-types.d.ts +84 -0
  49. package/dist/runtime/shared/settings-types.js +0 -0
  50. package/dist/runtime/shims-vue.d.ts +8 -0
  51. package/dist/runtime/vue/index.d.ts +14 -0
  52. package/dist/runtime/vue/index.js +6 -0
  53. package/dist/runtime/vue/settings-modal-scope.d.ts +62 -0
  54. package/dist/runtime/vue/settings-modal-scope.js +2 -0
  55. package/dist/runtime/vue/useProvidersPanel.d.ts +232 -0
  56. package/dist/runtime/vue/useProvidersPanel.js +655 -0
  57. package/dist/types.d.mts +9 -0
  58. package/package.json +117 -4
  59. package/src/runtime/ai-sdk/index.ts +91 -0
  60. package/src/runtime/components/ForgeProvidersPanel.vue +449 -0
  61. package/src/runtime/editor/index.ts +157 -0
  62. package/src/runtime/model/errors.ts +31 -0
  63. package/src/runtime/server/credential-policy.ts +137 -0
  64. package/src/runtime/server/credential-store.ts +473 -0
  65. package/src/runtime/server/env-layers.ts +290 -0
  66. package/src/runtime/server/env.ts +46 -0
  67. package/src/runtime/server/harness-settings.ts +373 -0
  68. package/src/runtime/server/index.ts +24 -0
  69. package/src/runtime/server/provider-discovery.ts +164 -0
  70. package/src/runtime/server/provider-layers.ts +70 -0
  71. package/src/runtime/server/provider-service.ts +244 -0
  72. package/src/runtime/server/provider-settings.ts +192 -0
  73. package/src/runtime/server/provider-write.ts +438 -0
  74. package/src/runtime/server/settings-defaults.ts +87 -0
  75. package/src/runtime/shared/env-vars.ts +82 -0
  76. package/src/runtime/shared/harness-providers.ts +118 -0
  77. package/src/runtime/shared/harnesses.ts +34 -0
  78. package/src/runtime/shared/provider-registry.ts +391 -0
  79. package/src/runtime/shared/settings-types.ts +87 -0
  80. package/src/runtime/shims-vue.d.ts +8 -0
  81. package/src/runtime/vue/index.ts +15 -0
  82. package/src/runtime/vue/settings-modal-scope.ts +66 -0
  83. package/src/runtime/vue/useProvidersPanel.ts +1087 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * The AI provider registry vocabulary, as forge-settings speaks it.
3
+ *
4
+ * `@skaile/workspaces` owns this schema (`core/src/provider-registry.ts`): the
5
+ * validator, the layer merge, the catalog presets and the model-reference
6
+ * resolver live there and are deliberately NOT reimplemented here. What follows
7
+ * is a *structural* mirror of the record so browser bundles do not import the
8
+ * workspaces core. Field names, union members and optionality match the
9
+ * upstream record exactly, so an entry produced by the runtime is assignable to
10
+ * these types and vice versa.
11
+ *
12
+ * Browser-safe on purpose — no `node:` imports — so the settings modal renders
13
+ * registry entries with the same types the server resolves them with.
14
+ *
15
+ * Vocabulary: these are **AI provider** records. They are unrelated to the
16
+ * platform's connector `ProviderType`/`ProviderLink` (git, SharePoint, Drive).
17
+ */
18
+ /** What an entry produces. `stt`/`tts` use the reduced voice record. */
19
+ export declare const AI_PROVIDER_KINDS: readonly ["llm", "stt", "tts", "embed"];
20
+ /** Union of {@link AI_PROVIDER_KINDS}. */
21
+ export type AIProviderKind = (typeof AI_PROVIDER_KINDS)[number];
22
+ /** Kinds that use the voice subset of the record. */
23
+ export declare const AI_PROVIDER_VOICE_KINDS: readonly ["stt", "tts"];
24
+ /** Union of {@link AI_PROVIDER_VOICE_KINDS}. */
25
+ export type AIProviderVoiceKind = (typeof AI_PROVIDER_VOICE_KINDS)[number];
26
+ /** Wire protocol of an `llm`/`embed` entry — omp's `api` values verbatim. */
27
+ export declare const AI_PROVIDER_SURFACES: readonly ["anthropic-messages", "openai-completions", "openai-responses", "openai-codex-responses", "azure-openai-responses", "bedrock-converse-stream", "google-generative-ai", "google-vertex"];
28
+ /** Union of {@link AI_PROVIDER_SURFACES}. */
29
+ export type AIProviderSurface = (typeof AI_PROVIDER_SURFACES)[number];
30
+ /** How the request reaches the surface — selects the `params` shape. */
31
+ export declare const AI_PROVIDER_TRANSPORTS: readonly ["direct", "bedrock", "vertex", "foundry", "azure-openai"];
32
+ /** Union of {@link AI_PROVIDER_TRANSPORTS}. */
33
+ export type AIProviderTransport = (typeof AI_PROVIDER_TRANSPORTS)[number];
34
+ /** How the credential is presented. */
35
+ export declare const AI_PROVIDER_CREDENTIAL_KINDS: readonly ["api_key_header", "bearer", "sigv4", "google_adc", "azure_entra", "oauth", "command", "none"];
36
+ /** Union of {@link AI_PROVIDER_CREDENTIAL_KINDS}. */
37
+ export type AIProviderCredentialKind = (typeof AI_PROVIDER_CREDENTIAL_KINDS)[number];
38
+ /** `transport: bedrock` routing parameters. */
39
+ export interface AIProviderBedrockParams {
40
+ /** AWS region, e.g. `us-east-1`. */
41
+ region: string;
42
+ }
43
+ /** `transport: vertex` routing parameters. */
44
+ export interface AIProviderVertexParams {
45
+ /** GCP project id. */
46
+ project: string;
47
+ /** Vertex location, e.g. `europe-west4`. */
48
+ location: string;
49
+ /** Model publisher; defaults to `google`. */
50
+ publisher?: string;
51
+ }
52
+ /** `transport: foundry` routing parameters. */
53
+ export interface AIProviderFoundryParams {
54
+ /** Azure AI Foundry resource name. */
55
+ resource: string;
56
+ }
57
+ /** `transport: azure-openai` routing parameters. */
58
+ export interface AIProviderAzureOpenAIParams {
59
+ /** Azure OpenAI resource name. */
60
+ resource: string;
61
+ /** Deployment the request is routed to. */
62
+ deployment: string;
63
+ /** `api-version` query value. */
64
+ api_version: string;
65
+ }
66
+ /** Voice parameters, typed per preset: Deepgram `{model, language}`, ElevenLabs `{voice_id, output_format}`. */
67
+ export type AIProviderVoiceParams = Record<string, string | number | boolean>;
68
+ /** The `transport` → `params` discriminated union. */
69
+ export type AIProviderTransportSlice = {
70
+ transport?: "direct";
71
+ params?: Record<string, never>;
72
+ } | {
73
+ transport: "bedrock";
74
+ params: AIProviderBedrockParams;
75
+ } | {
76
+ transport: "vertex";
77
+ params: AIProviderVertexParams;
78
+ } | {
79
+ transport: "foundry";
80
+ params: AIProviderFoundryParams;
81
+ } | {
82
+ transport: "azure-openai";
83
+ params: AIProviderAzureOpenAIParams;
84
+ };
85
+ /** Reference to a secret — never the secret itself. See {@link parseCredentialRef}. */
86
+ export interface AIProviderCredential {
87
+ kind: AIProviderCredentialKind;
88
+ /**
89
+ * `env:NAME` · `cmd:<shell>` · `platform:<configId>` · `keyring:<service>/<account>`.
90
+ * A literal secret here is a validation error, not a warning.
91
+ */
92
+ ref?: string;
93
+ }
94
+ /** Per-model capability facts, keyed by the logical (`model_map`) id. */
95
+ export interface AIProviderCapabilities {
96
+ context_window?: number;
97
+ tools?: boolean;
98
+ images?: boolean;
99
+ thinking_max?: number;
100
+ }
101
+ /** omp-compatible discovery descriptor. */
102
+ export interface AIProviderDiscovery {
103
+ type: string;
104
+ /** Path appended to `base_url` when the surface default does not apply. */
105
+ path?: string;
106
+ }
107
+ /** Fields every entry carries, whatever its `kind`. */
108
+ export interface AIProviderEntryCommon {
109
+ /** Display name. Never load-bearing. */
110
+ label?: string;
111
+ /** Catalog preset this entry inherits from. */
112
+ preset?: string;
113
+ /** Literal endpoint; wins over the preset value and the transport derivation. */
114
+ base_url?: string;
115
+ /** Static, non-secret request headers. */
116
+ headers?: Record<string, string>;
117
+ credential?: AIProviderCredential;
118
+ /** Default `true`; read it through {@link isEntryEnabled}. */
119
+ enabled?: boolean;
120
+ /** Set by the control plane: lower layers cannot override this entry. */
121
+ locked?: boolean;
122
+ }
123
+ /** An `llm`/`embed` entry — the full record. */
124
+ export type AIProviderLlmEntry = AIProviderEntryCommon & AIProviderTransportSlice & {
125
+ kind: "llm" | "embed";
126
+ /** Required unless a `preset` supplies it. */
127
+ surface?: AIProviderSurface;
128
+ /** Azure's `api-version` lives here. */
129
+ query_params?: Record<string, string>;
130
+ /** Logical id → provider-side id. */
131
+ model_map?: Record<string, string>;
132
+ /** Keyed by the logical id, i.e. the `model_map` key. */
133
+ capabilities?: Record<string, AIProviderCapabilities>;
134
+ discovery?: AIProviderDiscovery;
135
+ };
136
+ /**
137
+ * An `stt`/`tts` entry — the reduced record. `surface`, `transport`,
138
+ * `model_map`, `capabilities` and `discovery` are `llm`-only: a TTS voice is
139
+ * not a wire protocol. The voice defaults forge apps used to hardcode at their
140
+ * call sites (Deepgram model/language, ElevenLabs voice id/output format) live
141
+ * in `params`.
142
+ */
143
+ export type AIProviderVoiceEntry = AIProviderEntryCommon & {
144
+ kind: AIProviderVoiceKind;
145
+ params?: AIProviderVoiceParams;
146
+ };
147
+ /** One registry entry. */
148
+ export type AIProviderEntry = AIProviderLlmEntry | AIProviderVoiceEntry;
149
+ /** Entry id → entry. Iteration order is registry order. */
150
+ export type AIProviderRegistry = Record<string, AIProviderEntry>;
151
+ /** Configuration layers, lowest precedence first. */
152
+ export type AIProviderLayerId = "catalog" | "user" | "workspace" | "runtime" | "session";
153
+ /** The merged, layered view the runtime hands to a session. */
154
+ export interface MergedAIProviderRegistry {
155
+ version: 1;
156
+ providers: AIProviderRegistry;
157
+ /** Registry order: highest-precedence layer first, document order within a layer. */
158
+ order: string[];
159
+ /** Highest-precedence layer that contributed to each entry. */
160
+ origin: Record<string, AIProviderLayerId>;
161
+ /** Entries frozen by `locked: true`. */
162
+ locked: string[];
163
+ }
164
+ /** Either shape a consumer may be handed. */
165
+ export type AIProviderRegistryView = AIProviderRegistry | MergedAIProviderRegistry;
166
+ /**
167
+ * Whether a view is the layered shape rather than a bare entry map.
168
+ *
169
+ * Exported because anything that *rebuilds* a registry (rather than just
170
+ * reading it) has to put the entries back in the shape it was handed.
171
+ */
172
+ export declare function isMergedRegistry(registry: AIProviderRegistryView): registry is MergedAIProviderRegistry;
173
+ /** `enabled` defaults to `true`. */
174
+ export declare function isEntryEnabled(entry: AIProviderEntry): boolean;
175
+ /** Entries in registry order, as `[id, entry]` pairs. Both registry shapes accepted. */
176
+ export declare function registryEntries(registry: AIProviderRegistryView): Array<[string, AIProviderEntry]>;
177
+ /**
178
+ * Enabled entries of one `kind`, in registry order.
179
+ *
180
+ * This is the replacement for every hardcoded provider-id list in the forge
181
+ * apps: which providers exist, and whether one does speech or chat, is a fact
182
+ * of the registry record, not of an app-local table.
183
+ */
184
+ export declare function entriesOfKind<K extends AIProviderKind>(registry: AIProviderRegistryView, kind: K): Array<[string, K extends AIProviderVoiceKind ? AIProviderVoiceEntry : AIProviderLlmEntry]>;
185
+ /** Whether an entry uses the reduced voice record. Narrows `params` into view. */
186
+ export declare function isVoiceEntry(entry: AIProviderEntry): entry is AIProviderVoiceEntry;
187
+ /**
188
+ * The one `params` key a settings override replaces, per voice kind.
189
+ *
190
+ * Shared so the server's `resolveVoiceProvider` and a settings UI cannot
191
+ * disagree about which parameter the user is allowed to override — the rest of
192
+ * an entry's `params` are registry facts.
193
+ */
194
+ export declare const VOICE_OVERRIDE_PARAM: {
195
+ readonly stt: "language";
196
+ readonly tts: "voice_id";
197
+ };
198
+ /** The settings field carrying each kind's {@link VOICE_OVERRIDE_PARAM} override. */
199
+ export declare const VOICE_OVERRIDE_SETTING: {
200
+ readonly stt: "sttLanguage";
201
+ readonly tts: "ttsVoice";
202
+ };
203
+ /**
204
+ * Pick which voice entry serves a kind, given the ids on offer in registry
205
+ * order and the id the settings name.
206
+ *
207
+ * A setting naming an entry the registry no longer has falls back to the first
208
+ * one rather than silencing the app. Both the server resolver and the settings
209
+ * modal go through this, so what the modal shows is what the request will use.
210
+ *
211
+ * @returns The chosen id, or `undefined` when no entry of that kind exists.
212
+ */
213
+ export declare function selectVoiceEntryId(ids: readonly string[], wanted?: string): string | undefined;
214
+ /** A parsed `credential.ref`. */
215
+ export type AIProviderCredentialRef = {
216
+ form: "env";
217
+ name: string;
218
+ } | {
219
+ form: "cmd";
220
+ command: string;
221
+ } | {
222
+ form: "platform";
223
+ configId: string;
224
+ } | {
225
+ form: "keyring";
226
+ service: string;
227
+ account: string;
228
+ };
229
+ /**
230
+ * Parse a `credential.ref`.
231
+ *
232
+ * Returns `undefined` for anything that is not one of the four reference forms
233
+ * — which, in a registry entry or a credential editor, means someone typed a
234
+ * literal secret.
235
+ */
236
+ export declare function parseCredentialRef(ref: string): AIProviderCredentialRef | undefined;
237
+ /** Render a parsed reference back to its `<form>:<body>` string. */
238
+ export declare function formatCredentialRef(ref: AIProviderCredentialRef): string;
239
+ /** The reference forms, spelled the way a credential editor offers them. */
240
+ export declare const CREDENTIAL_REF_FORMS: readonly ["env:NAME", "keyring:service/account", "cmd:<shell command>", "platform:<configId>"];
241
+ /** What a credential editor tells a user who typed something that is not a reference. */
242
+ export declare const CREDENTIAL_REF_GUIDANCE: string;
243
+ /**
244
+ * Check what a credential editor holds.
245
+ *
246
+ * An empty field is valid: it means "no override, use the entry's own
247
+ * reference". Anything else must parse as one of the four forms — a pasted
248
+ * secret does not, and that is the case this exists for.
249
+ *
250
+ * The message never echoes the value: a user who pasted a live key must not
251
+ * see it rendered back into the page (or into a screenshot of it).
252
+ *
253
+ * @returns The refusal to show, or `undefined` when the field is acceptable.
254
+ */
255
+ export declare function validateCredentialRef(value: string): string | undefined;
@@ -0,0 +1,112 @@
1
+ export const AI_PROVIDER_KINDS = ["llm", "stt", "tts", "embed"];
2
+ export const AI_PROVIDER_VOICE_KINDS = ["stt", "tts"];
3
+ export const AI_PROVIDER_SURFACES = [
4
+ "anthropic-messages",
5
+ "openai-completions",
6
+ "openai-responses",
7
+ "openai-codex-responses",
8
+ "azure-openai-responses",
9
+ "bedrock-converse-stream",
10
+ "google-generative-ai",
11
+ "google-vertex"
12
+ ];
13
+ export const AI_PROVIDER_TRANSPORTS = [
14
+ "direct",
15
+ "bedrock",
16
+ "vertex",
17
+ "foundry",
18
+ "azure-openai"
19
+ ];
20
+ export const AI_PROVIDER_CREDENTIAL_KINDS = [
21
+ "api_key_header",
22
+ "bearer",
23
+ "sigv4",
24
+ "google_adc",
25
+ "azure_entra",
26
+ "oauth",
27
+ "command",
28
+ "none"
29
+ ];
30
+ export function isMergedRegistry(registry) {
31
+ return Array.isArray(registry.order);
32
+ }
33
+ export function isEntryEnabled(entry) {
34
+ return entry.enabled !== false;
35
+ }
36
+ export function registryEntries(registry) {
37
+ if (isMergedRegistry(registry)) {
38
+ return registry.order.filter((id) => registry.providers[id] !== void 0).map((id) => [id, registry.providers[id]]);
39
+ }
40
+ return Object.entries(registry);
41
+ }
42
+ export function entriesOfKind(registry, kind) {
43
+ return registryEntries(registry).filter(
44
+ ([, entry]) => entry.kind === kind && isEntryEnabled(entry)
45
+ );
46
+ }
47
+ export function isVoiceEntry(entry) {
48
+ return entry.kind === "stt" || entry.kind === "tts";
49
+ }
50
+ export const VOICE_OVERRIDE_PARAM = { stt: "language", tts: "voice_id" };
51
+ export const VOICE_OVERRIDE_SETTING = { stt: "sttLanguage", tts: "ttsVoice" };
52
+ export function selectVoiceEntryId(ids, wanted) {
53
+ const trimmed = wanted?.trim();
54
+ if (trimmed && ids.includes(trimmed)) return trimmed;
55
+ return ids[0];
56
+ }
57
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
58
+ const CREDENTIAL_REF = /^(env|cmd|platform|keyring):([\s\S]+)$/;
59
+ export function parseCredentialRef(ref) {
60
+ const match = CREDENTIAL_REF.exec(ref.trim());
61
+ if (!match) return void 0;
62
+ const body = match[2].trim();
63
+ if (body === "") return void 0;
64
+ switch (match[1]) {
65
+ case "env":
66
+ return ENV_NAME.test(body) ? { form: "env", name: body } : void 0;
67
+ case "cmd":
68
+ return { form: "cmd", command: body };
69
+ case "platform":
70
+ return { form: "platform", configId: body };
71
+ case "keyring": {
72
+ const slash = body.indexOf("/");
73
+ if (slash <= 0 || slash === body.length - 1) return void 0;
74
+ return { form: "keyring", service: body.slice(0, slash), account: body.slice(slash + 1) };
75
+ }
76
+ default:
77
+ return void 0;
78
+ }
79
+ }
80
+ export function formatCredentialRef(ref) {
81
+ switch (ref.form) {
82
+ case "env":
83
+ return `env:${ref.name}`;
84
+ case "cmd":
85
+ return `cmd:${ref.command}`;
86
+ case "platform":
87
+ return `platform:${ref.configId}`;
88
+ case "keyring":
89
+ return `keyring:${ref.service}/${ref.account}`;
90
+ }
91
+ }
92
+ export const CREDENTIAL_REF_FORMS = [
93
+ "env:NAME",
94
+ "keyring:service/account",
95
+ "cmd:<shell command>",
96
+ "platform:<configId>"
97
+ ];
98
+ export const CREDENTIAL_REF_GUIDANCE = `Enter a reference, not a key \u2014 ${CREDENTIAL_REF_FORMS.join(
99
+ " \xB7 "
100
+ )}.`;
101
+ export function validateCredentialRef(value) {
102
+ const trimmed = value.trim();
103
+ if (trimmed === "" || parseCredentialRef(trimmed)) return void 0;
104
+ switch (CREDENTIAL_REF.exec(trimmed)?.[1]) {
105
+ case "env":
106
+ return "An env: reference takes a variable name such as env:ANTHROPIC_API_KEY.";
107
+ case "keyring":
108
+ return "A keyring: reference takes <service>/<account>, such as keyring:forge/anthropic.";
109
+ default:
110
+ return `A credential is stored as a reference, never as a key. ${CREDENTIAL_REF_GUIDANCE}`;
111
+ }
112
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The settings document type forge apps share.
3
+ *
4
+ * A leaf module: it imports types only, so the harness and provider settings
5
+ * modules read `SkaileSettings` from here instead of from the broad server
6
+ * entry. Browser-safe.
7
+ */
8
+ import type { SkaileSettings as CoreSettings } from "@skaile/workspaces/sdk/core";
9
+ /** Skill domain enablement configuration for a forge app installation. */
10
+ export interface DomainConfig {
11
+ /** Domain identifiers that are active for this installation. */
12
+ enabled: string[];
13
+ /** Domain shown by default for users with multi-domain access. */
14
+ default: string;
15
+ }
16
+ /**
17
+ * Full settings type for forge apps.
18
+ *
19
+ * Extends the core personal settings with forge-app-specific fields. The core
20
+ * type's legacy `apiKeys` field is deliberately omitted: a provider is selected
21
+ * as part of `model`, and a credential is a reference held by the registry
22
+ * entry, never a settings value.
23
+ */
24
+ export interface SkaileSettings extends Omit<CoreSettings, "apiKeys"> {
25
+ /**
26
+ * Model selection as `"<providerId>/<modelId>"`, where `providerId` is a
27
+ * registry entry id. A bare model id and an `@role` alias also resolve — the
28
+ * runtime does that against the session's registry.
29
+ */
30
+ model?: string;
31
+ /** Registry id of the `kind: stt` entry that transcribes speech. */
32
+ sttProvider?: string;
33
+ /** Registry id of the `kind: tts` entry that synthesizes speech. */
34
+ ttsProvider?: string;
35
+ /**
36
+ * Per-request override of the STT entry's `params.language`. Absent means the
37
+ * entry's own parameter wins — the endpoint's defaults are registry facts, not
38
+ * app settings.
39
+ */
40
+ sttLanguage?: string;
41
+ /** Per-request override of the TTS entry's `params.voice_id`. */
42
+ ttsVoice?: string;
43
+ /**
44
+ * Per-harness default model selector (`"<providerId>/<modelId>"`), keyed by
45
+ * harness id. Picked when a session starts on that harness without an
46
+ * explicit model. The default harness itself is the workspaces settings key
47
+ * `harness`, which selects a harness by id.
48
+ */
49
+ harnessModels?: Record<string, string>;
50
+ /**
51
+ * Per-harness provider pick: registry entry ids, keyed by harness id. Narrowed
52
+ * to the entries still eligible for that harness (enabled `llm` entries on a
53
+ * surface the harness speaks) by `resolveHarnessProviders`; absent or empty
54
+ * means "no pick" — the only eligible entry (or, for a multi-provider
55
+ * harness, all of them) is used automatically.
56
+ */
57
+ harnessProviders?: Record<string, string[]>;
58
+ /**
59
+ * Instance allow-list of harness ids the harness picker offers. Absent means
60
+ * `DEFAULT_ENABLED_HARNESSES`.
61
+ */
62
+ enabledHarnesses?: string[];
63
+ /**
64
+ * Admin locks, instance `settings.json` only (there is no environment
65
+ * override): when true, the instance's harness / provider pick / model /
66
+ * audio choice applies to every workspace and user. Read through
67
+ * `resolveLockFlags`.
68
+ */
69
+ lockHarness?: boolean;
70
+ lockProvider?: boolean;
71
+ lockModel?: boolean;
72
+ lockAudio?: boolean;
73
+ /** Active skill domain configuration. */
74
+ domains?: DomainConfig;
75
+ /** Optional display name for the agent shown in the UI. */
76
+ agentName?: string;
77
+ /** Optional user-supplied addendum materialized into the agent's system prompt. */
78
+ systemPromptExtension?: string;
79
+ /**
80
+ * When true the onboarding wizard has been completed (or skipped).
81
+ * Absent / false on a fresh install — triggers the first-run wizard.
82
+ */
83
+ hasCompletedOnboarding?: boolean;
84
+ }
File without changes
@@ -0,0 +1,8 @@
1
+ // Ambient module declaration so `tsc --noEmit` resolves `*.vue` imports.
2
+ // Runtime resolution is handled by Vite / @vitejs/plugin-vue at build time.
3
+ declare module "*.vue" {
4
+ import type { DefineComponent } from "vue";
5
+ // biome-ignore lint/suspicious/noExplicitAny: catch-all for SFC components
6
+ const component: DefineComponent<object, object, any>;
7
+ export default component;
8
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@skaile/forge-settings/vue` — browser-safe settings UI logic: the
3
+ * providers-panel composable, the modal scope contract and the registry /
4
+ * harness vocabulary the UI renders. No `node:` imports.
5
+ *
6
+ * `ForgeProvidersPanel` itself is registered as a component by
7
+ * `@skaile/forge-settings/nuxt`.
8
+ */
9
+ export * from "../shared/env-vars.js";
10
+ export * from "../shared/harness-providers.js";
11
+ export * from "../shared/harnesses.js";
12
+ export * from "../shared/provider-registry.js";
13
+ export * from "./settings-modal-scope.js";
14
+ export * from "./useProvidersPanel.js";
@@ -0,0 +1,6 @@
1
+ export * from "../shared/env-vars.js";
2
+ export * from "../shared/harness-providers.js";
3
+ export * from "../shared/harnesses.js";
4
+ export * from "../shared/provider-registry.js";
5
+ export * from "./settings-modal-scope.js";
6
+ export * from "./useProvidersPanel.js";
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The tier a settings modal edits, and the URLs of its routes.
3
+ *
4
+ * The URL protocol is the host's: every field names a route the host serves,
5
+ * and no component builds a URL from an id or a slug. `ForgeProvidersPanel`
6
+ * takes the `providers*` / `environmentUrl` / `providersLayer` fields of this
7
+ * record as its props; the rest belong to the host's own modal tabs (harness,
8
+ * voice, workspace), which stay in the host.
9
+ */
10
+ /** Which settings tier a modal edits. */
11
+ export type SettingsModalSettingsScope = "instance" | "workspace";
12
+ /**
13
+ * The tier the modal edits: the instance (app-wide defaults) or one
14
+ * workspace. The caller owns every route: the modal never builds a URL from an
15
+ * id or slug.
16
+ */
17
+ export interface SettingsModalScope {
18
+ /** Dialog header text, e.g. the workspace name. */
19
+ label: string;
20
+ /**
21
+ * `"instance"`: the Harness tab edits the instance defaults
22
+ * (`PUT /api/settings?scope=instance`); `"workspace"`: it edits this
23
+ * workspace's overrides through {@link settingsUrl}.
24
+ */
25
+ settingsScope: SettingsModalSettingsScope;
26
+ /**
27
+ * `GET`/`PUT` endpoint of this scope's settings: `/api/settings` for the
28
+ * instance, the workspace's override row (`{ harness, model }`) otherwise.
29
+ */
30
+ settingsUrl: string;
31
+ /** Base URL for the Workspace + Connectors tabs' config API calls. */
32
+ workspaceApiBaseUrl: string;
33
+ /** `GET` model catalog of this scope's registry, queried with `?harness=<id>`. */
34
+ modelsUrl: string;
35
+ /** Provider registry write base: `PUT|DELETE <url>/:id`, `GET <url>/detected`. */
36
+ providersWriteUrl: string;
37
+ /**
38
+ * `GET` endpoint of this scope's `.env` layer, which the Providers tab reads and
39
+ * writes. Defaults to the instance layer, `/api/settings/env`.
40
+ */
41
+ environmentUrl?: string;
42
+ /**
43
+ * `GET` endpoint listing registry entries with their credential status
44
+ * resolved against this scope's environment. Defaults to `/api/providers`.
45
+ */
46
+ providersUrl?: string;
47
+ /**
48
+ * The registry document the Providers tab edits, when it differs from
49
+ * {@link settingsScope}. A single-workspace app keeps instance settings but
50
+ * writes the workspace registry over a user-level one: `"workspace"` there
51
+ * makes the lower document's entries inherited and patchable.
52
+ */
53
+ providersLayer?: SettingsModalSettingsScope;
54
+ /** Workspace name for workspace-scope copy (the harness-change confirm); defaults to {@link label}. */
55
+ workspaceName?: string;
56
+ /** The caller may read but not write this scope (e.g. a `readonly` user, another owner's workspace). */
57
+ readOnly?: boolean;
58
+ }
59
+ /** Default `GET` route of the merged provider list. */
60
+ export declare const DEFAULT_PROVIDERS_URL = "/api/providers";
61
+ /** Default `GET`/`PUT` route of the instance `.env` layer. */
62
+ export declare const DEFAULT_ENVIRONMENT_URL = "/api/settings/env";
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_PROVIDERS_URL = "/api/providers";
2
+ export const DEFAULT_ENVIRONMENT_URL = "/api/settings/env";