@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.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +94 -2
- package/dist/module.d.mts +17 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +23 -0
- package/dist/runtime/ai-sdk/index.d.ts +45 -0
- package/dist/runtime/ai-sdk/index.js +39 -0
- package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +21 -0
- package/dist/runtime/components/ForgeProvidersPanel.vue +435 -0
- package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +21 -0
- package/dist/runtime/editor/index.d.ts +58 -0
- package/dist/runtime/editor/index.js +83 -0
- package/dist/runtime/model/errors.d.ts +24 -0
- package/dist/runtime/model/errors.js +10 -0
- package/dist/runtime/server/credential-policy.d.ts +60 -0
- package/dist/runtime/server/credential-policy.js +64 -0
- package/dist/runtime/server/credential-store.d.ts +184 -0
- package/dist/runtime/server/credential-store.js +234 -0
- package/dist/runtime/server/env-layers.d.ts +62 -0
- package/dist/runtime/server/env-layers.js +185 -0
- package/dist/runtime/server/env.d.ts +30 -0
- package/dist/runtime/server/env.js +15 -0
- package/dist/runtime/server/harness-settings.d.ts +150 -0
- package/dist/runtime/server/harness-settings.js +166 -0
- package/dist/runtime/server/index.d.ts +23 -0
- package/dist/runtime/server/index.js +14 -0
- package/dist/runtime/server/provider-discovery.d.ts +73 -0
- package/dist/runtime/server/provider-discovery.js +77 -0
- package/dist/runtime/server/provider-layers.d.ts +27 -0
- package/dist/runtime/server/provider-layers.js +37 -0
- package/dist/runtime/server/provider-service.d.ts +126 -0
- package/dist/runtime/server/provider-service.js +87 -0
- package/dist/runtime/server/provider-settings.d.ts +78 -0
- package/dist/runtime/server/provider-settings.js +81 -0
- package/dist/runtime/server/provider-write.d.ts +154 -0
- package/dist/runtime/server/provider-write.js +223 -0
- package/dist/runtime/server/settings-defaults.d.ts +26 -0
- package/dist/runtime/server/settings-defaults.js +46 -0
- package/dist/runtime/shared/env-vars.d.ts +55 -0
- package/dist/runtime/shared/env-vars.js +22 -0
- package/dist/runtime/shared/harness-providers.d.ts +62 -0
- package/dist/runtime/shared/harness-providers.js +38 -0
- package/dist/runtime/shared/harnesses.d.ts +19 -0
- package/dist/runtime/shared/harnesses.js +15 -0
- package/dist/runtime/shared/provider-registry.d.ts +255 -0
- package/dist/runtime/shared/provider-registry.js +112 -0
- package/dist/runtime/shared/settings-types.d.ts +84 -0
- package/dist/runtime/shared/settings-types.js +0 -0
- package/dist/runtime/shims-vue.d.ts +8 -0
- package/dist/runtime/vue/index.d.ts +14 -0
- package/dist/runtime/vue/index.js +6 -0
- package/dist/runtime/vue/settings-modal-scope.d.ts +62 -0
- package/dist/runtime/vue/settings-modal-scope.js +2 -0
- package/dist/runtime/vue/useProvidersPanel.d.ts +232 -0
- package/dist/runtime/vue/useProvidersPanel.js +655 -0
- package/dist/types.d.mts +9 -0
- package/package.json +117 -4
- package/src/runtime/ai-sdk/index.ts +91 -0
- package/src/runtime/components/ForgeProvidersPanel.vue +449 -0
- package/src/runtime/editor/index.ts +157 -0
- package/src/runtime/model/errors.ts +31 -0
- package/src/runtime/server/credential-policy.ts +137 -0
- package/src/runtime/server/credential-store.ts +473 -0
- package/src/runtime/server/env-layers.ts +290 -0
- package/src/runtime/server/env.ts +46 -0
- package/src/runtime/server/harness-settings.ts +373 -0
- package/src/runtime/server/index.ts +24 -0
- package/src/runtime/server/provider-discovery.ts +164 -0
- package/src/runtime/server/provider-layers.ts +70 -0
- package/src/runtime/server/provider-service.ts +244 -0
- package/src/runtime/server/provider-settings.ts +192 -0
- package/src/runtime/server/provider-write.ts +438 -0
- package/src/runtime/server/settings-defaults.ts +87 -0
- package/src/runtime/shared/env-vars.ts +82 -0
- package/src/runtime/shared/harness-providers.ts +118 -0
- package/src/runtime/shared/harnesses.ts +34 -0
- package/src/runtime/shared/provider-registry.ts +391 -0
- package/src/runtime/shared/settings-types.ts +87 -0
- package/src/runtime/shims-vue.d.ts +8 -0
- package/src/runtime/vue/index.ts +15 -0
- package/src/runtime/vue/settings-modal-scope.ts +66 -0
- 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,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";
|