@skaile/forge-settings 1.3.2 → 1.4.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 +19 -0
- package/README.md +20 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +13 -0
- package/dist/runtime/components/ForgeProvidersPanel.vue +461 -78
- package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +13 -0
- package/dist/runtime/server/harness-settings.d.ts +50 -12
- package/dist/runtime/server/harness-settings.js +93 -9
- package/dist/runtime/server/index.d.ts +2 -0
- package/dist/runtime/server/index.js +2 -0
- package/dist/runtime/server/provider-service.d.ts +26 -2
- package/dist/runtime/server/provider-service.js +16 -1
- package/dist/runtime/server/provider-write.d.ts +25 -9
- package/dist/runtime/server/provider-write.js +72 -4
- package/dist/runtime/shared/data-zone.d.ts +66 -0
- package/dist/runtime/shared/data-zone.js +79 -0
- package/dist/runtime/shared/harness-providers.d.ts +41 -2
- package/dist/runtime/shared/harness-providers.js +31 -8
- package/dist/runtime/shared/harnesses.d.ts +7 -0
- package/dist/runtime/shared/harnesses.js +18 -0
- package/dist/runtime/shared/provider-accounts.d.ts +215 -0
- package/dist/runtime/shared/provider-accounts.js +255 -0
- package/dist/runtime/shared/provider-registry.d.ts +38 -0
- package/dist/runtime/shared/provider-registry.js +16 -0
- package/dist/runtime/shared/settings-types.d.ts +7 -0
- package/dist/runtime/vue/index.d.ts +2 -0
- package/dist/runtime/vue/index.js +2 -0
- package/dist/runtime/vue/providers-panel-model.d.ts +98 -5
- package/dist/runtime/vue/providers-panel-model.js +13 -1
- package/dist/runtime/vue/useProvidersPanel.d.ts +66 -5
- package/dist/runtime/vue/useProvidersPanel.js +417 -29
- package/package.json +2 -2
- package/src/runtime/components/ForgeProvidersPanel.vue +477 -78
- package/src/runtime/server/harness-settings.ts +165 -21
- package/src/runtime/server/index.ts +2 -0
- package/src/runtime/server/provider-service.ts +53 -1
- package/src/runtime/server/provider-write.ts +129 -15
- package/src/runtime/shared/data-zone.ts +172 -0
- package/src/runtime/shared/harness-providers.ts +77 -6
- package/src/runtime/shared/harnesses.ts +25 -0
- package/src/runtime/shared/provider-accounts.ts +526 -0
- package/src/runtime/shared/provider-registry.ts +58 -0
- package/src/runtime/shared/settings-types.ts +7 -0
- package/src/runtime/vue/index.ts +2 -0
- package/src/runtime/vue/providers-panel-model.ts +123 -5
- package/src/runtime/vue/useProvidersPanel.ts +586 -37
|
@@ -5,9 +5,27 @@ import {
|
|
|
5
5
|
ref,
|
|
6
6
|
watch
|
|
7
7
|
} from "vue";
|
|
8
|
+
import { DATA_ZONE_LABELS, dataZoneRefusal, entryDataZone } from "../shared/data-zone.js";
|
|
8
9
|
import {
|
|
9
10
|
validateEnvVarName
|
|
10
11
|
} from "../shared/env-vars.js";
|
|
12
|
+
import { isPlainObject } from "../shared/guards.js";
|
|
13
|
+
import {
|
|
14
|
+
AZURE_OPENAI_DEFAULT_API_VERSION,
|
|
15
|
+
AZURE_OPENAI_PRESET,
|
|
16
|
+
azureAccountEntries,
|
|
17
|
+
credentialExpiry,
|
|
18
|
+
defaultParamValue,
|
|
19
|
+
EXPERT_PARAMS,
|
|
20
|
+
FOUNDRY_PRESET,
|
|
21
|
+
missingRequiredParams,
|
|
22
|
+
PROVIDER_PARAM_LABELS,
|
|
23
|
+
presetParamKeys,
|
|
24
|
+
presetSignInOptions,
|
|
25
|
+
providerAccountLine,
|
|
26
|
+
signInCredential,
|
|
27
|
+
signInOptionOf
|
|
28
|
+
} from "../shared/provider-accounts.js";
|
|
11
29
|
import {
|
|
12
30
|
AI_PROVIDER_SURFACES,
|
|
13
31
|
validateCredentialRef
|
|
@@ -16,6 +34,7 @@ import {
|
|
|
16
34
|
urlProvidersClient
|
|
17
35
|
} from "./providers-client.js";
|
|
18
36
|
import {
|
|
37
|
+
AZURE_ACCOUNT,
|
|
19
38
|
CREDENTIAL_ENTER_KEY,
|
|
20
39
|
CREDENTIAL_OTHER_REF,
|
|
21
40
|
CUSTOM_PRESET,
|
|
@@ -26,9 +45,18 @@ import {
|
|
|
26
45
|
nonEnvSourceLabel,
|
|
27
46
|
OVERRIDES_INSTANCE_NOTICE,
|
|
28
47
|
PROVIDER_ID_PATTERN,
|
|
48
|
+
providerModelChoices,
|
|
49
|
+
providerModelsChip,
|
|
29
50
|
requestErrorMessage
|
|
30
51
|
} from "./providers-panel-model.js";
|
|
31
|
-
export {
|
|
52
|
+
export {
|
|
53
|
+
AZURE_ACCOUNT,
|
|
54
|
+
CREDENTIAL_ENTER_KEY,
|
|
55
|
+
CREDENTIAL_OTHER_REF,
|
|
56
|
+
CUSTOM_PRESET,
|
|
57
|
+
providerModelChoices,
|
|
58
|
+
providerModelsChip
|
|
59
|
+
} from "./providers-panel-model.js";
|
|
32
60
|
export function useProvidersPanel(props, emit) {
|
|
33
61
|
const providers = ref([]);
|
|
34
62
|
const presets = ref([]);
|
|
@@ -173,11 +201,18 @@ export function useProvidersPanel(props, emit) {
|
|
|
173
201
|
if (entry.locked) notices.push("Locked by the control plane.");
|
|
174
202
|
return notices;
|
|
175
203
|
}
|
|
204
|
+
function blockedNoteOf(zone, euProcessing) {
|
|
205
|
+
const rule = props.dataZoneRule;
|
|
206
|
+
if (!rule || rule === "any") return null;
|
|
207
|
+
return dataZoneRefusal(rule, zone, euProcessing) ?? null;
|
|
208
|
+
}
|
|
176
209
|
function buildRow(entry) {
|
|
177
210
|
const state = stateOf(entry);
|
|
178
211
|
const writable = !props.readOnly && !entry.locked;
|
|
179
212
|
const needsCredential = entry.credentialKind !== "none";
|
|
180
213
|
const envName = envNameOfRef(entry.credentialRef);
|
|
214
|
+
const zone = entry.dataZone ?? entryDataZone({ preset: entry.preset });
|
|
215
|
+
const isModelEntry = entry.kind === "llm" || entry.kind === "embed";
|
|
181
216
|
return {
|
|
182
217
|
...entry,
|
|
183
218
|
state,
|
|
@@ -192,25 +227,52 @@ export function useProvidersPanel(props, emit) {
|
|
|
192
227
|
credentialOptions: needsCredential ? credentialOptionsOf(entry) : [],
|
|
193
228
|
origin: entry.preset ?? entry.surface ?? entry.kind,
|
|
194
229
|
status: statusOf(entry, needsCredential),
|
|
195
|
-
overrideNotices: overrideNoticesOf(entry)
|
|
230
|
+
overrideNotices: overrideNoticesOf(entry),
|
|
231
|
+
accountLine: providerAccountLine(entry, presets.value),
|
|
232
|
+
expiry: needsCredential ? credentialExpiry(entry.credentialExpiresAt) : null,
|
|
233
|
+
canRenew: needsCredential && writable,
|
|
234
|
+
zone,
|
|
235
|
+
zoneLabel: DATA_ZONE_LABELS[zone],
|
|
236
|
+
canSetEuProcessing: zone === "eu_option" && writable && state === "own",
|
|
237
|
+
blockedNote: blockedNoteOf(zone, entry.euProcessing),
|
|
238
|
+
modelsChip: isModelEntry ? providerModelsChip(entry.allowedModels) : null,
|
|
239
|
+
canFilterModels: isModelEntry && writable && state === "own"
|
|
196
240
|
};
|
|
197
241
|
}
|
|
198
242
|
const rows = computed(() => providers.value.map(buildRow));
|
|
199
243
|
const dialogVisible = ref(false);
|
|
200
244
|
const editing = ref(null);
|
|
201
|
-
const form = reactive({ ...EMPTY_FORM });
|
|
245
|
+
const form = reactive({ ...EMPTY_FORM, params: {} });
|
|
202
246
|
const dialogError = ref(null);
|
|
203
247
|
const saving = ref(false);
|
|
248
|
+
const dialogStep = ref("account");
|
|
249
|
+
const createdIds = ref([]);
|
|
250
|
+
const azureForm = reactive(emptyAzureForm());
|
|
251
|
+
function emptyAzureForm() {
|
|
252
|
+
return {
|
|
253
|
+
resource: "",
|
|
254
|
+
useOpenAI: true,
|
|
255
|
+
useFoundry: false,
|
|
256
|
+
deployments: [{ deployment: "", model: "" }],
|
|
257
|
+
apiVersion: AZURE_OPENAI_DEFAULT_API_VERSION
|
|
258
|
+
};
|
|
259
|
+
}
|
|
204
260
|
const presetOptions = computed(() => [
|
|
205
261
|
...presets.value.map((preset) => ({ label: preset.label, value: preset.id })),
|
|
206
262
|
{ label: "Custom", value: CUSTOM_PRESET }
|
|
207
263
|
]);
|
|
264
|
+
const libraryPresets = computed(
|
|
265
|
+
() => presets.value.filter(
|
|
266
|
+
(preset) => preset.id !== AZURE_OPENAI_PRESET && preset.id !== FOUNDRY_PRESET
|
|
267
|
+
)
|
|
268
|
+
);
|
|
208
269
|
const surfaceOptions = AI_PROVIDER_SURFACES.map((surface) => ({
|
|
209
270
|
label: surface,
|
|
210
271
|
value: surface
|
|
211
272
|
}));
|
|
212
273
|
const selectedPreset = computed(() => presets.value.find((preset) => preset.id === form.preset));
|
|
213
274
|
const isCustom = computed(() => form.preset === CUSTOM_PRESET);
|
|
275
|
+
const isAzureAccount = computed(() => form.preset === AZURE_ACCOUNT);
|
|
214
276
|
const customKind = computed(() => editing.value?.kind ?? "llm");
|
|
215
277
|
const showSurface = computed(
|
|
216
278
|
() => isCustom.value && (customKind.value === "llm" || customKind.value === "embed")
|
|
@@ -219,38 +281,135 @@ export function useProvidersPanel(props, emit) {
|
|
|
219
281
|
() => selectedPreset.value?.base_url ?? "https://api.example.com/v1"
|
|
220
282
|
);
|
|
221
283
|
const canSetValue = computed(() => environmentOn.value);
|
|
222
|
-
const
|
|
284
|
+
const azureSignInPreset = computed(() => {
|
|
285
|
+
const openai = presets.value.find((p) => p.id === AZURE_OPENAI_PRESET);
|
|
286
|
+
const foundry = presets.value.find((p) => p.id === FOUNDRY_PRESET);
|
|
287
|
+
const chosen = !azureForm.useOpenAI && azureForm.useFoundry ? foundry : openai;
|
|
288
|
+
return chosen ?? openai ?? foundry ?? {
|
|
289
|
+
credential: { kind: "api_key_header", env: "AZURE_OPENAI_API_KEY" },
|
|
290
|
+
transport: "azure-openai"
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
const signInOptions = computed(() => {
|
|
294
|
+
const expert = props.expert === true;
|
|
295
|
+
if (isAzureAccount.value) return presetSignInOptions(azureSignInPreset.value, { expert });
|
|
296
|
+
if (!form.preset) return [];
|
|
297
|
+
return presetSignInOptions(isCustom.value ? null : selectedPreset.value ?? null, { expert });
|
|
298
|
+
});
|
|
299
|
+
const signInOption = computed(() => signInOptions.value.find((o) => o.id === form.signIn));
|
|
300
|
+
const storedSignIn = computed(() => {
|
|
301
|
+
const row = editing.value;
|
|
302
|
+
if (!row) return void 0;
|
|
303
|
+
const kind = row.credentialKind ?? selectedPreset.value?.credential.kind;
|
|
304
|
+
return signInOptionOf({ kind, ref: row.credentialRef }, signInOptions.value);
|
|
305
|
+
});
|
|
306
|
+
const showSignIn = computed(() => {
|
|
307
|
+
const row = editing.value;
|
|
308
|
+
if (!row) return signInOptions.value.length > 0;
|
|
309
|
+
const option = storedSignIn.value;
|
|
310
|
+
const kind = row.credentialKind ?? selectedPreset.value?.credential.kind;
|
|
311
|
+
if (!option || kind && option.credentialKind !== kind) return false;
|
|
312
|
+
const ref2 = row.credentialRef;
|
|
313
|
+
if (!ref2) return option.fields !== "reference";
|
|
314
|
+
return option.fields === (envNameOfRef(ref2) ? "key" : "reference");
|
|
315
|
+
});
|
|
316
|
+
const showCredentialFields = computed(
|
|
317
|
+
() => showSignIn.value && signInOption.value?.fields === "key"
|
|
318
|
+
);
|
|
319
|
+
const showReferenceField = computed(
|
|
320
|
+
() => showSignIn.value && signInOption.value?.fields === "reference"
|
|
321
|
+
);
|
|
322
|
+
const defaultEnvName = computed(
|
|
323
|
+
() => isAzureAccount.value ? azureSignInPreset.value.credential.env : selectedPreset.value?.credential.env
|
|
324
|
+
);
|
|
325
|
+
const paramFields = computed(() => {
|
|
326
|
+
if (isCustom.value || isAzureAccount.value) return [];
|
|
327
|
+
const preset = selectedPreset.value;
|
|
328
|
+
const kept = editing.value && editing.value.preset === form.preset ? editing.value : null;
|
|
329
|
+
const required = new Set(preset?.required_params ?? []);
|
|
330
|
+
return presetParamKeys(preset, kept?.transportParams).map((key) => ({
|
|
331
|
+
key,
|
|
332
|
+
label: PROVIDER_PARAM_LABELS[key] ?? key,
|
|
333
|
+
required: required.has(key),
|
|
334
|
+
editable: props.expert === true || !EXPERT_PARAMS.includes(key)
|
|
335
|
+
}));
|
|
336
|
+
});
|
|
223
337
|
const idError = computed(() => {
|
|
224
338
|
const id = form.id.trim();
|
|
225
339
|
return id && !PROVIDER_ID_PATTERN.test(id) ? "Use lower-case letters, digits, '-' and '_', starting with a letter or digit." : null;
|
|
226
340
|
});
|
|
227
|
-
const dialogTitle = computed(
|
|
228
|
-
(
|
|
229
|
-
|
|
341
|
+
const dialogTitle = computed(() => {
|
|
342
|
+
if (editing.value) return `Edit provider \u2014 ${editing.value.id}`;
|
|
343
|
+
if (dialogStep.value === "done") return "Provider added";
|
|
344
|
+
if (dialogStep.value === "account" && isAzureAccount.value) return "Add Microsoft Azure";
|
|
345
|
+
return "Add provider";
|
|
346
|
+
});
|
|
230
347
|
function resetDialog(values, row) {
|
|
231
348
|
Object.assign(form, EMPTY_FORM, values);
|
|
349
|
+
form.params = { ...values.params ?? {} };
|
|
350
|
+
Object.assign(azureForm, emptyAzureForm());
|
|
232
351
|
editing.value = row;
|
|
233
352
|
dialogError.value = null;
|
|
353
|
+
createdIds.value = [];
|
|
354
|
+
dialogStep.value = "account";
|
|
234
355
|
dialogVisible.value = true;
|
|
235
356
|
}
|
|
357
|
+
function settleSignIn() {
|
|
358
|
+
if (!signInOptions.value.some((o) => o.id === form.signIn)) {
|
|
359
|
+
form.signIn = signInOptions.value[0]?.id ?? "";
|
|
360
|
+
}
|
|
361
|
+
}
|
|
236
362
|
function openAdd(presetId) {
|
|
237
363
|
if (props.readOnly) return;
|
|
238
364
|
resetDialog({}, null);
|
|
239
|
-
if (presetId)
|
|
365
|
+
if (presetId) chooseLibrary(presetId);
|
|
366
|
+
else dialogStep.value = "library";
|
|
367
|
+
}
|
|
368
|
+
function chooseLibrary(value) {
|
|
369
|
+
if (props.readOnly || editing.value) return;
|
|
370
|
+
dialogError.value = null;
|
|
371
|
+
if (value === AZURE_ACCOUNT) {
|
|
372
|
+
form.preset = AZURE_ACCOUNT;
|
|
373
|
+
form.id = "";
|
|
374
|
+
form.envName = "";
|
|
375
|
+
form.params = {};
|
|
376
|
+
} else {
|
|
377
|
+
if (form.preset === AZURE_ACCOUNT) form.preset = "";
|
|
378
|
+
selectPreset(value);
|
|
379
|
+
}
|
|
380
|
+
settleSignIn();
|
|
381
|
+
dialogStep.value = "account";
|
|
382
|
+
}
|
|
383
|
+
function backToLibrary() {
|
|
384
|
+
if (editing.value) return;
|
|
385
|
+
dialogError.value = null;
|
|
386
|
+
dialogStep.value = "library";
|
|
387
|
+
}
|
|
388
|
+
function selectSignIn(id) {
|
|
389
|
+
if (signInOptions.value.some((o) => o.id === id)) form.signIn = id;
|
|
240
390
|
}
|
|
241
391
|
function openEdit(row) {
|
|
242
392
|
if (!isEditable(row)) return;
|
|
243
393
|
const preset = presets.value.find((p) => p.id === row.preset);
|
|
394
|
+
const ref2 = row.credentialRef;
|
|
395
|
+
const params = {};
|
|
396
|
+
for (const key of presetParamKeys(preset, row.transportParams)) {
|
|
397
|
+
params[key] = row.transportParams?.[key] ?? defaultParamValue(preset?.id, key);
|
|
398
|
+
}
|
|
244
399
|
resetDialog(
|
|
245
400
|
{
|
|
246
401
|
preset: preset ? preset.id : CUSTOM_PRESET,
|
|
247
402
|
id: row.id,
|
|
248
403
|
surface: row.surface ?? "",
|
|
249
404
|
// The preset's own endpoint shows as the placeholder, not as a pinned value.
|
|
250
|
-
baseUrl: row.baseUrl && row.baseUrl !== preset?.base_url ? row.baseUrl : ""
|
|
405
|
+
baseUrl: row.baseUrl && row.baseUrl !== preset?.base_url ? row.baseUrl : "",
|
|
406
|
+
envName: envNameOfRef(ref2) ?? "",
|
|
407
|
+
credentialRef: ref2 && !envNameOfRef(ref2) ? ref2 : "",
|
|
408
|
+
params
|
|
251
409
|
},
|
|
252
410
|
row
|
|
253
411
|
);
|
|
412
|
+
form.signIn = storedSignIn.value?.id ?? signInOptions.value[0]?.id ?? "";
|
|
254
413
|
}
|
|
255
414
|
function selectPreset(value) {
|
|
256
415
|
const previous = selectedPreset.value;
|
|
@@ -260,6 +419,41 @@ export function useProvidersPanel(props, emit) {
|
|
|
260
419
|
if (form.envName === "" || form.envName === previous?.credential.env)
|
|
261
420
|
form.envName = next?.credential.env ?? "";
|
|
262
421
|
if (value === CUSTOM_PRESET && form.surface === "") form.surface = previous?.surface ?? "";
|
|
422
|
+
const params = {};
|
|
423
|
+
for (const key of presetParamKeys(next)) {
|
|
424
|
+
const typed = form.params[key]?.trim();
|
|
425
|
+
params[key] = typed && typed !== defaultParamValue(previous?.id, key) ? typed : defaultParamValue(next?.id, key);
|
|
426
|
+
}
|
|
427
|
+
form.params = params;
|
|
428
|
+
settleSignIn();
|
|
429
|
+
}
|
|
430
|
+
function addDeploymentRow() {
|
|
431
|
+
azureForm.deployments.push({ deployment: "", model: "" });
|
|
432
|
+
}
|
|
433
|
+
function removeDeploymentRow(index) {
|
|
434
|
+
azureForm.deployments.splice(index, 1);
|
|
435
|
+
if (azureForm.deployments.length === 0) addDeploymentRow();
|
|
436
|
+
}
|
|
437
|
+
function dialogCredential() {
|
|
438
|
+
const option = signInOption.value;
|
|
439
|
+
if (!option) return { ok: false, error: "Choose how you sign in." };
|
|
440
|
+
return signInCredential(
|
|
441
|
+
option,
|
|
442
|
+
{ envName: form.envName, ref: form.credentialRef },
|
|
443
|
+
{ defaultEnv: defaultEnvName.value }
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
function keyFieldsRefusal() {
|
|
447
|
+
if (!showCredentialFields.value) return null;
|
|
448
|
+
const envName = form.envName.trim();
|
|
449
|
+
if (envName) {
|
|
450
|
+
const refusal = validateEnvVarName(envName);
|
|
451
|
+
if (refusal) return refusal;
|
|
452
|
+
}
|
|
453
|
+
if (form.secret && !envName && !defaultEnvName.value) {
|
|
454
|
+
return "Name the environment variable the value is stored in.";
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
263
457
|
}
|
|
264
458
|
function formRefusal() {
|
|
265
459
|
if (!form.preset) return "Choose a preset or Custom.";
|
|
@@ -267,13 +461,17 @@ export function useProvidersPanel(props, emit) {
|
|
|
267
461
|
if (idError.value) return idError.value;
|
|
268
462
|
if (showSurface.value && !form.surface) return "Choose the surface a custom provider speaks.";
|
|
269
463
|
if (isCustom.value && !form.baseUrl.trim()) return "A custom provider needs a base URL.";
|
|
270
|
-
if (
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
if (refusal) return refusal;
|
|
464
|
+
if (!isCustom.value && selectedPreset.value) {
|
|
465
|
+
const missing = missingRequiredParams(selectedPreset.value, form.params)[0];
|
|
466
|
+
if (missing) {
|
|
467
|
+
return `Enter the ${(PROVIDER_PARAM_LABELS[missing] ?? missing).toLowerCase()} (${missing}).`;
|
|
275
468
|
}
|
|
276
|
-
|
|
469
|
+
}
|
|
470
|
+
if (showSignIn.value) {
|
|
471
|
+
const keyRefusal = keyFieldsRefusal();
|
|
472
|
+
if (keyRefusal) return keyRefusal;
|
|
473
|
+
const credential = dialogCredential();
|
|
474
|
+
if (!credential.ok) return credential.error;
|
|
277
475
|
}
|
|
278
476
|
return null;
|
|
279
477
|
}
|
|
@@ -291,41 +489,120 @@ export function useProvidersPanel(props, emit) {
|
|
|
291
489
|
}
|
|
292
490
|
const baseUrl = form.baseUrl.trim();
|
|
293
491
|
if (baseUrl) body.base_url = baseUrl;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
492
|
+
const fields = paramFields.value;
|
|
493
|
+
if (fields.length > 0) {
|
|
494
|
+
const params = kept && isPlainObject(body.params) ? { ...body.params } : {};
|
|
495
|
+
for (const { key } of fields) {
|
|
496
|
+
const value = form.params[key]?.trim();
|
|
497
|
+
if (value) params[key] = value;
|
|
498
|
+
else delete params[key];
|
|
499
|
+
}
|
|
500
|
+
body.params = params;
|
|
501
|
+
} else if (!kept) {
|
|
502
|
+
delete body.params;
|
|
503
|
+
}
|
|
504
|
+
if (showSignIn.value) {
|
|
505
|
+
const credential = dialogCredential();
|
|
506
|
+
if (credential.ok && credential.credential) body.credential = credential.credential;
|
|
507
|
+
} else if (row?.credentialRef) {
|
|
508
|
+
const credentialKind = kept?.credentialKind ?? (isCustom.value ? "bearer" : preset?.credential.kind ?? "bearer");
|
|
509
|
+
body.credential = { kind: credentialKind, ref: row.credentialRef };
|
|
510
|
+
} else if (kept?.credentialKind) {
|
|
511
|
+
body.credential = { kind: kept.credentialKind };
|
|
512
|
+
}
|
|
298
513
|
return body;
|
|
299
514
|
}
|
|
515
|
+
async function writeDialogSecret() {
|
|
516
|
+
const name = form.envName.trim() || defaultEnvName.value || "";
|
|
517
|
+
if (!showCredentialFields.value || !form.secret || !environmentOn.value || !name) return;
|
|
518
|
+
const update = { set: { [name]: form.secret }, unset: [] };
|
|
519
|
+
await client().updateEnvironment(update);
|
|
520
|
+
form.secret = "";
|
|
521
|
+
}
|
|
522
|
+
async function finishAdd(ids) {
|
|
523
|
+
createdIds.value = ids;
|
|
524
|
+
await afterWrite();
|
|
525
|
+
if (!canTest.value) {
|
|
526
|
+
dialogVisible.value = false;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
dialogStep.value = "done";
|
|
530
|
+
for (const id of ids) await testProvider({ id });
|
|
531
|
+
}
|
|
532
|
+
async function saveAzureAccount() {
|
|
533
|
+
const keyRefusal = keyFieldsRefusal();
|
|
534
|
+
const credential = dialogCredential();
|
|
535
|
+
const drafts = keyRefusal ? { ok: false, error: keyRefusal } : !credential.ok ? credential : azureAccountEntries({
|
|
536
|
+
resource: azureForm.resource,
|
|
537
|
+
credential: credential.credential,
|
|
538
|
+
openai: azureForm.useOpenAI ? { deployments: azureForm.deployments, apiVersion: azureForm.apiVersion } : void 0,
|
|
539
|
+
foundry: azureForm.useFoundry ? { deployments: [] } : void 0,
|
|
540
|
+
takenIds: providers.value.map((entry) => entry.id)
|
|
541
|
+
});
|
|
542
|
+
if (!drafts.ok) {
|
|
543
|
+
dialogError.value = drafts.error;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
saving.value = true;
|
|
547
|
+
dialogError.value = null;
|
|
548
|
+
const created = [];
|
|
549
|
+
try {
|
|
550
|
+
await writeDialogSecret();
|
|
551
|
+
for (const draft of drafts.entries) {
|
|
552
|
+
await client().upsert(draft.id, draft.body);
|
|
553
|
+
created.push(draft.id);
|
|
554
|
+
}
|
|
555
|
+
await finishAdd(created);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
const message = requestErrorMessage(err, "Could not add the Azure account.");
|
|
558
|
+
dialogError.value = created.length > 0 ? `${message} (created: ${created.join(", ")})` : message;
|
|
559
|
+
if (created.length > 0) await afterWrite();
|
|
560
|
+
} finally {
|
|
561
|
+
saving.value = false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
300
564
|
async function saveProvider() {
|
|
301
565
|
if (props.readOnly) return;
|
|
566
|
+
if (isAzureAccount.value && !editing.value) {
|
|
567
|
+
await saveAzureAccount();
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
302
570
|
const refusal = formRefusal();
|
|
303
571
|
if (refusal) {
|
|
304
572
|
dialogError.value = refusal;
|
|
305
573
|
return;
|
|
306
574
|
}
|
|
307
575
|
const id = form.id.trim();
|
|
576
|
+
const adding = editing.value === null;
|
|
308
577
|
saving.value = true;
|
|
309
578
|
dialogError.value = null;
|
|
310
579
|
try {
|
|
311
|
-
|
|
312
|
-
const update = {
|
|
313
|
-
set: { [form.envName.trim()]: form.secret },
|
|
314
|
-
unset: []
|
|
315
|
-
};
|
|
316
|
-
await client().updateEnvironment(update);
|
|
317
|
-
form.secret = "";
|
|
318
|
-
}
|
|
580
|
+
await writeDialogSecret();
|
|
319
581
|
await client().upsert(id, buildBody());
|
|
320
|
-
dialogVisible.value = false;
|
|
321
582
|
editing.value = null;
|
|
322
|
-
|
|
583
|
+
if (adding) {
|
|
584
|
+
await finishAdd([id]);
|
|
585
|
+
} else {
|
|
586
|
+
dialogVisible.value = false;
|
|
587
|
+
await afterWrite();
|
|
588
|
+
}
|
|
323
589
|
} catch (err) {
|
|
324
590
|
dialogError.value = requestErrorMessage(err, "Could not save the provider.");
|
|
325
591
|
} finally {
|
|
326
592
|
saving.value = false;
|
|
327
593
|
}
|
|
328
594
|
}
|
|
595
|
+
function finishDialog() {
|
|
596
|
+
dialogVisible.value = false;
|
|
597
|
+
createdIds.value = [];
|
|
598
|
+
}
|
|
599
|
+
function openSignIn(row) {
|
|
600
|
+
if (!row.canRenew) return;
|
|
601
|
+
if (isEditable(row)) openEdit(row);
|
|
602
|
+
else if (canEnterKey.value && (!row.credentialRef || envNameOfRef(row.credentialRef))) {
|
|
603
|
+
openKeyEditor(row);
|
|
604
|
+
} else openRefEditor(row);
|
|
605
|
+
}
|
|
329
606
|
const deleteTarget = ref(null);
|
|
330
607
|
const deleteError = ref(null);
|
|
331
608
|
const deleting = ref(false);
|
|
@@ -448,6 +725,84 @@ export function useProvidersPanel(props, emit) {
|
|
|
448
725
|
`Could not reset ${row.id}.`
|
|
449
726
|
);
|
|
450
727
|
}
|
|
728
|
+
async function setEuProcessing(row, on) {
|
|
729
|
+
if (!row.canSetEuProcessing || row.euProcessing === on) return;
|
|
730
|
+
await runRowWrite(
|
|
731
|
+
row.id,
|
|
732
|
+
() => client().upsert(row.id, { ...ownBody(row), eu_processing: on }),
|
|
733
|
+
`Could not change EU processing of ${row.id}.`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const modelsEditing = ref(null);
|
|
737
|
+
const modelsForm = reactive({ mode: "all", selected: [], query: "" });
|
|
738
|
+
const modelsKnown = ref([]);
|
|
739
|
+
const modelsLoading = ref(false);
|
|
740
|
+
const modelsListMessage = ref(null);
|
|
741
|
+
const modelsError = ref(null);
|
|
742
|
+
const modelChoices = computed(
|
|
743
|
+
() => providerModelChoices(modelsKnown.value, modelsForm.selected, modelsForm.query)
|
|
744
|
+
);
|
|
745
|
+
async function openModelsEditor(row) {
|
|
746
|
+
if (!row.canFilterModels) return;
|
|
747
|
+
modelsEditing.value = row.id;
|
|
748
|
+
modelsForm.mode = row.allowedModels === void 0 ? "all" : "only";
|
|
749
|
+
modelsForm.selected = [...row.allowedModels ?? []];
|
|
750
|
+
modelsForm.query = "";
|
|
751
|
+
modelsError.value = null;
|
|
752
|
+
modelsListMessage.value = null;
|
|
753
|
+
if (row.modelIds && row.modelIds.length > 0) {
|
|
754
|
+
modelsKnown.value = [...row.modelIds];
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
modelsKnown.value = [];
|
|
758
|
+
modelsLoading.value = true;
|
|
759
|
+
try {
|
|
760
|
+
const listed = await client().models(row.id);
|
|
761
|
+
if (modelsEditing.value !== row.id) return;
|
|
762
|
+
modelsKnown.value = listed.models.map((model) => model.id);
|
|
763
|
+
if (listed.models.length === 0) {
|
|
764
|
+
modelsListMessage.value = listed.message ?? "This provider lists no models.";
|
|
765
|
+
}
|
|
766
|
+
} catch (err) {
|
|
767
|
+
if (modelsEditing.value === row.id) {
|
|
768
|
+
modelsListMessage.value = requestErrorMessage(
|
|
769
|
+
err,
|
|
770
|
+
`Could not list the models of ${row.id}.`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
} finally {
|
|
774
|
+
modelsLoading.value = false;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function setModelsMode(mode) {
|
|
778
|
+
modelsForm.mode = mode;
|
|
779
|
+
}
|
|
780
|
+
function toggleModel(id, on) {
|
|
781
|
+
const has = modelsForm.selected.includes(id);
|
|
782
|
+
if (on && !has) modelsForm.selected = [...modelsForm.selected, id];
|
|
783
|
+
if (!on && has) modelsForm.selected = modelsForm.selected.filter((model) => model !== id);
|
|
784
|
+
}
|
|
785
|
+
function cancelModelsEditor() {
|
|
786
|
+
modelsEditing.value = null;
|
|
787
|
+
modelsError.value = null;
|
|
788
|
+
}
|
|
789
|
+
async function saveModelsEditor() {
|
|
790
|
+
const row = rows.value.find((r) => r.id === modelsEditing.value);
|
|
791
|
+
if (!row?.canFilterModels) return;
|
|
792
|
+
const order = providerModelChoices(modelsKnown.value, modelsForm.selected);
|
|
793
|
+
const allowed = modelsForm.mode === "all" ? null : order.filter((id) => modelsForm.selected.includes(id));
|
|
794
|
+
rowBusy.value = row.id;
|
|
795
|
+
modelsError.value = null;
|
|
796
|
+
try {
|
|
797
|
+
await client().upsert(row.id, { ...ownBody(row), allowed_models: allowed });
|
|
798
|
+
modelsEditing.value = null;
|
|
799
|
+
await afterWrite();
|
|
800
|
+
} catch (err) {
|
|
801
|
+
modelsError.value = requestErrorMessage(err, `Could not save the models of ${row.id}.`);
|
|
802
|
+
} finally {
|
|
803
|
+
rowBusy.value = null;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
451
806
|
const canTest = computed(() => Boolean(props.client) && !props.readOnly);
|
|
452
807
|
const testBusy = ref(null);
|
|
453
808
|
const testResults = reactive({});
|
|
@@ -608,6 +963,25 @@ export function useProvidersPanel(props, emit) {
|
|
|
608
963
|
openEdit,
|
|
609
964
|
selectPreset,
|
|
610
965
|
saveProvider,
|
|
966
|
+
// add flow
|
|
967
|
+
dialogStep,
|
|
968
|
+
libraryPresets,
|
|
969
|
+
chooseLibrary,
|
|
970
|
+
backToLibrary,
|
|
971
|
+
isAzureAccount,
|
|
972
|
+
signInOptions,
|
|
973
|
+
signInOption,
|
|
974
|
+
selectSignIn,
|
|
975
|
+
showSignIn,
|
|
976
|
+
showReferenceField,
|
|
977
|
+
paramFields,
|
|
978
|
+
azureForm,
|
|
979
|
+
addDeploymentRow,
|
|
980
|
+
removeDeploymentRow,
|
|
981
|
+
createdIds,
|
|
982
|
+
finishDialog,
|
|
983
|
+
defaultEnvName,
|
|
984
|
+
openSignIn,
|
|
611
985
|
// delete
|
|
612
986
|
deleteTarget,
|
|
613
987
|
deleteOpen,
|
|
@@ -625,6 +999,7 @@ export function useProvidersPanel(props, emit) {
|
|
|
625
999
|
setCredentialEnv,
|
|
626
1000
|
setEnabled,
|
|
627
1001
|
resetPatch,
|
|
1002
|
+
setEuProcessing,
|
|
628
1003
|
rowBusy,
|
|
629
1004
|
rowError,
|
|
630
1005
|
canTest,
|
|
@@ -646,6 +1021,19 @@ export function useProvidersPanel(props, emit) {
|
|
|
646
1021
|
openRefEditor,
|
|
647
1022
|
cancelRefEditor,
|
|
648
1023
|
saveRefEditor,
|
|
1024
|
+
// models allowed here
|
|
1025
|
+
modelsEditing,
|
|
1026
|
+
modelsForm,
|
|
1027
|
+
modelsKnown,
|
|
1028
|
+
modelsLoading,
|
|
1029
|
+
modelsListMessage,
|
|
1030
|
+
modelChoices,
|
|
1031
|
+
modelsError,
|
|
1032
|
+
openModelsEditor,
|
|
1033
|
+
setModelsMode,
|
|
1034
|
+
toggleModel,
|
|
1035
|
+
cancelModelsEditor,
|
|
1036
|
+
saveModelsEditor,
|
|
649
1037
|
// environment
|
|
650
1038
|
envDocument,
|
|
651
1039
|
envEditable,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skaile/forge-settings",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Provider and settings operations for Skaile forge apps: provider registry layers, credential store, env layers, model discovery, the providers panel, and editor / AI SDK model adapters.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.9",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@nuxt/kit": "^4.0.0",
|
|
58
|
-
"@skaile/workspaces": "^
|
|
58
|
+
"@skaile/workspaces": "^9.1.0",
|
|
59
59
|
"ofetch": "^1.4.0",
|
|
60
60
|
"yaml": "^2.9.0"
|
|
61
61
|
},
|