@skaile/forge-settings 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -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 +46 -0
- package/dist/runtime/ai-sdk/index.js +31 -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 +72 -0
- package/dist/runtime/model/errors.d.ts +32 -0
- package/dist/runtime/model/errors.js +19 -0
- package/dist/runtime/server/credential-policy.d.ts +61 -0
- package/dist/runtime/server/credential-policy.js +65 -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 +183 -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 +35 -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 +221 -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/guards.d.ts +2 -0
- package/dist/runtime/shared/guards.js +3 -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/providers-panel-model.d.ts +147 -0
- package/dist/runtime/vue/providers-panel-model.js +37 -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 +115 -0
- package/dist/runtime/vue/useProvidersPanel.js +633 -0
- package/dist/types.d.mts +9 -0
- package/package.json +117 -4
- package/src/runtime/ai-sdk/index.ts +88 -0
- package/src/runtime/components/ForgeProvidersPanel.vue +449 -0
- package/src/runtime/editor/index.ts +146 -0
- package/src/runtime/model/errors.ts +52 -0
- package/src/runtime/server/credential-policy.ts +139 -0
- package/src/runtime/server/credential-store.ts +473 -0
- package/src/runtime/server/env-layers.ts +287 -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 +67 -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 +433 -0
- package/src/runtime/server/settings-defaults.ts +87 -0
- package/src/runtime/shared/env-vars.ts +82 -0
- package/src/runtime/shared/guards.ts +4 -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/providers-panel-model.ts +203 -0
- package/src/runtime/vue/settings-modal-scope.ts +66 -0
- package/src/runtime/vue/useProvidersPanel.ts +930 -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,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
|
+
};
|