@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +20 -0
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +13 -0
  5. package/dist/runtime/components/ForgeProvidersPanel.vue +461 -78
  6. package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +13 -0
  7. package/dist/runtime/server/harness-settings.d.ts +50 -12
  8. package/dist/runtime/server/harness-settings.js +93 -9
  9. package/dist/runtime/server/index.d.ts +2 -0
  10. package/dist/runtime/server/index.js +2 -0
  11. package/dist/runtime/server/provider-service.d.ts +26 -2
  12. package/dist/runtime/server/provider-service.js +16 -1
  13. package/dist/runtime/server/provider-write.d.ts +25 -9
  14. package/dist/runtime/server/provider-write.js +72 -4
  15. package/dist/runtime/shared/data-zone.d.ts +66 -0
  16. package/dist/runtime/shared/data-zone.js +79 -0
  17. package/dist/runtime/shared/harness-providers.d.ts +41 -2
  18. package/dist/runtime/shared/harness-providers.js +31 -8
  19. package/dist/runtime/shared/harnesses.d.ts +7 -0
  20. package/dist/runtime/shared/harnesses.js +18 -0
  21. package/dist/runtime/shared/provider-accounts.d.ts +215 -0
  22. package/dist/runtime/shared/provider-accounts.js +255 -0
  23. package/dist/runtime/shared/provider-registry.d.ts +38 -0
  24. package/dist/runtime/shared/provider-registry.js +16 -0
  25. package/dist/runtime/shared/settings-types.d.ts +7 -0
  26. package/dist/runtime/vue/index.d.ts +2 -0
  27. package/dist/runtime/vue/index.js +2 -0
  28. package/dist/runtime/vue/providers-panel-model.d.ts +98 -5
  29. package/dist/runtime/vue/providers-panel-model.js +13 -1
  30. package/dist/runtime/vue/useProvidersPanel.d.ts +66 -5
  31. package/dist/runtime/vue/useProvidersPanel.js +417 -29
  32. package/package.json +2 -2
  33. package/src/runtime/components/ForgeProvidersPanel.vue +477 -78
  34. package/src/runtime/server/harness-settings.ts +165 -21
  35. package/src/runtime/server/index.ts +2 -0
  36. package/src/runtime/server/provider-service.ts +53 -1
  37. package/src/runtime/server/provider-write.ts +129 -15
  38. package/src/runtime/shared/data-zone.ts +172 -0
  39. package/src/runtime/shared/harness-providers.ts +77 -6
  40. package/src/runtime/shared/harnesses.ts +25 -0
  41. package/src/runtime/shared/provider-accounts.ts +526 -0
  42. package/src/runtime/shared/provider-registry.ts +58 -0
  43. package/src/runtime/shared/settings-types.ts +7 -0
  44. package/src/runtime/vue/index.ts +2 -0
  45. package/src/runtime/vue/providers-panel-model.ts +123 -5
  46. package/src/runtime/vue/useProvidersPanel.ts +586 -37
@@ -0,0 +1,255 @@
1
+ import {
2
+ validateCredentialRef
3
+ } from "./provider-registry.js";
4
+ export function missingRequiredParams(preset, params) {
5
+ return (preset.required_params ?? []).filter((key) => {
6
+ if (preset.params?.[key] !== void 0) return false;
7
+ const value = params?.[key];
8
+ if (typeof value === "number" || typeof value === "boolean") return false;
9
+ return typeof value !== "string" || value.trim() === "";
10
+ });
11
+ }
12
+ export const PROVIDER_PARAM_LABELS = {
13
+ resource: "Resource",
14
+ deployment: "Deployment",
15
+ api_version: "API version",
16
+ region: "Region",
17
+ project: "Project",
18
+ location: "Location",
19
+ publisher: "Publisher"
20
+ };
21
+ export const EXPERT_PARAMS = ["api_version"];
22
+ export function presetParamKeys(preset, current = void 0) {
23
+ const keys = (preset?.required_params ?? []).filter((key) => preset?.params?.[key] === void 0);
24
+ for (const key of Object.keys(current ?? {})) if (!keys.includes(key)) keys.push(key);
25
+ return keys;
26
+ }
27
+ export function defaultParamValue(presetId, key) {
28
+ return presetId === AZURE_OPENAI_PRESET && key === "api_version" ? AZURE_OPENAI_DEFAULT_API_VERSION : "";
29
+ }
30
+ export const AZURE_TRANSPORTS = ["azure-openai", "foundry"];
31
+ export function isAzureTransport(transport) {
32
+ return transport !== void 0 && AZURE_TRANSPORTS.includes(transport);
33
+ }
34
+ function apiKeyOption(kind) {
35
+ return {
36
+ id: "api_key",
37
+ label: "API key",
38
+ description: "A key from the provider. It is stored in this scope's environment; the entry only names the variable.",
39
+ credentialKind: kind,
40
+ fields: "key"
41
+ };
42
+ }
43
+ const ENTRA_OPTION = {
44
+ id: "entra",
45
+ label: "Entra ID",
46
+ description: "Microsoft Entra ID: the server's Azure identity signs the requests (managed identity, az login or AZURE_CLIENT_ID / AZURE_CLIENT_SECRET / AZURE_TENANT_ID). No key is stored.",
47
+ credentialKind: "azure_entra",
48
+ fields: "none"
49
+ };
50
+ const AWS_OPTION = {
51
+ id: "aws",
52
+ label: "AWS credentials",
53
+ description: "The server's AWS default credential chain signs the requests. No key is stored.",
54
+ credentialKind: "sigv4",
55
+ fields: "none"
56
+ };
57
+ const GOOGLE_OPTION = {
58
+ id: "google",
59
+ label: "Google credentials",
60
+ description: "Google application default credentials on the server sign the requests. No key is stored.",
61
+ credentialKind: "google_adc",
62
+ fields: "none"
63
+ };
64
+ const OAUTH_OPTION = {
65
+ id: "oauth",
66
+ label: "OAuth",
67
+ description: "An OAuth token held behind a reference (keyring, command or control plane).",
68
+ credentialKind: "oauth",
69
+ fields: "reference"
70
+ };
71
+ const NONE_OPTION = {
72
+ id: "none",
73
+ label: "No sign-in",
74
+ description: "The endpoint takes no credential, such as a model server on this machine.",
75
+ credentialKind: "none",
76
+ fields: "none"
77
+ };
78
+ function referenceOption(kind) {
79
+ return {
80
+ id: "reference",
81
+ label: "Reference",
82
+ description: "Type the credential reference yourself: env:NAME, keyring:service/account, cmd:\u2026 or platform:\u2026.",
83
+ credentialKind: kind,
84
+ fields: "reference"
85
+ };
86
+ }
87
+ export function presetSignInOptions(preset, options = {}) {
88
+ const out = [];
89
+ const kind = preset?.credential.kind;
90
+ if (!preset) {
91
+ out.push(apiKeyOption("bearer"), NONE_OPTION);
92
+ } else if (kind === "none") {
93
+ out.push(NONE_OPTION);
94
+ } else if (kind === "sigv4") {
95
+ out.push(AWS_OPTION);
96
+ } else if (kind === "google_adc") {
97
+ out.push(GOOGLE_OPTION);
98
+ } else if (kind === "azure_entra") {
99
+ out.push(ENTRA_OPTION);
100
+ } else if (kind === "oauth") {
101
+ out.push(OAUTH_OPTION);
102
+ } else if (kind === "command") {
103
+ out.push(referenceOption("command"));
104
+ } else if (kind === "api_key_header" || kind === "bearer") {
105
+ if (isAzureTransport(preset.transport)) out.push(ENTRA_OPTION);
106
+ out.push(apiKeyOption(kind));
107
+ }
108
+ const referenceKind = kind ?? "bearer";
109
+ if (options.expert && referenceKind !== "none" && !out.some((o) => o.fields === "reference")) {
110
+ out.push(referenceOption(referenceKind));
111
+ }
112
+ return out;
113
+ }
114
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
115
+ export function signInCredential(option, input, context = {}) {
116
+ if (option.fields === "none") return { ok: true, credential: { kind: option.credentialKind } };
117
+ if (option.fields === "reference") {
118
+ const ref = input.ref?.trim() ?? "";
119
+ if (!ref) return { ok: false, error: "Enter a credential reference." };
120
+ const refusal = validateCredentialRef(ref);
121
+ if (refusal) return { ok: false, error: refusal };
122
+ return { ok: true, credential: { kind: option.credentialKind, ref } };
123
+ }
124
+ const name = input.envName?.trim() || context.defaultEnv || "";
125
+ if (!name) {
126
+ return context.requireCredential ? { ok: false, error: "Name the environment variable the key is stored in." } : { ok: true };
127
+ }
128
+ if (!ENV_NAME.test(name)) {
129
+ return { ok: false, error: `'${name}' is not a valid environment variable name.` };
130
+ }
131
+ return { ok: true, credential: { kind: option.credentialKind, ref: `env:${name}` } };
132
+ }
133
+ export function signInOptionOf(credential, options) {
134
+ if (!credential?.kind) return options[0];
135
+ const sameKind = options.filter((o) => o.credentialKind === credential.kind);
136
+ const ref = credential.ref;
137
+ const wanted = !ref ? sameKind.some((o) => o.fields === "none") ? "none" : "key" : ref.startsWith("env:") ? "key" : "reference";
138
+ return sameKind.find((o) => o.fields === wanted) ?? sameKind[0];
139
+ }
140
+ export const CREDENTIAL_KIND_LABELS = {
141
+ api_key_header: "API key",
142
+ bearer: "API key",
143
+ azure_entra: "Entra ID",
144
+ sigv4: "AWS credentials",
145
+ google_adc: "Google credentials",
146
+ oauth: "OAuth",
147
+ command: "Command",
148
+ none: "No sign-in"
149
+ };
150
+ export const AZURE_FAMILY_LABEL = "Microsoft Azure";
151
+ export function providerFamilyLabel(entry, presets) {
152
+ const preset = entry.preset ? presets.find((p) => p.id === entry.preset) : void 0;
153
+ if (isAzureTransport(entry.transport ?? preset?.transport)) return AZURE_FAMILY_LABEL;
154
+ return preset?.label ?? (entry.preset ? entry.preset : "Custom");
155
+ }
156
+ export function providerAccountLine(entry, presets) {
157
+ const preset = entry.preset ? presets.find((p) => p.id === entry.preset) : void 0;
158
+ const kind = entry.credentialKind ?? preset?.credential.kind;
159
+ const resource = entry.transportParams?.resource?.trim();
160
+ return [
161
+ providerFamilyLabel(entry, presets),
162
+ kind ? CREDENTIAL_KIND_LABELS[kind] : null,
163
+ resource || null
164
+ ].filter((part) => !!part).join(" \xB7 ");
165
+ }
166
+ const DAY_MS = 24 * 60 * 60 * 1e3;
167
+ export function credentialExpiry(expiresAt, now = Date.now()) {
168
+ if (!expiresAt) return null;
169
+ const at = Date.parse(expiresAt);
170
+ if (!Number.isFinite(at)) return null;
171
+ if (at <= now) return { days: Math.floor((at - now) / DAY_MS), expired: true, text: "Expired" };
172
+ const days = Math.floor((at - now) / DAY_MS);
173
+ const text = days === 0 ? "Expires today" : `Expires in ${days} day${days === 1 ? "" : "s"}`;
174
+ return { days, expired: false, text };
175
+ }
176
+ export const AZURE_OPENAI_PRESET = "azure-openai";
177
+ export const FOUNDRY_PRESET = "foundry";
178
+ export const AZURE_OPENAI_DEFAULT_API_VERSION = "2025-04-01-preview";
179
+ export function deploymentModelMap(rows) {
180
+ const modelMap = {};
181
+ for (const row of rows) {
182
+ const deployment = row.deployment.trim();
183
+ const model = row.model.trim();
184
+ if (!deployment && !model) continue;
185
+ if (!deployment) return { ok: false, error: `Name the deployment that serves '${model}'.` };
186
+ const id = model || deployment;
187
+ if (Object.hasOwn(modelMap, id)) return { ok: false, error: `Model '${id}' is listed twice.` };
188
+ modelMap[id] = deployment;
189
+ }
190
+ return { ok: true, modelMap };
191
+ }
192
+ const AZURE_RESOURCE = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
193
+ function freeId(base, resource, taken) {
194
+ if (!taken.has(base)) return base;
195
+ const slug = resource.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
196
+ const withResource = slug ? `${base}-${slug}` : base;
197
+ if (!taken.has(withResource)) return withResource;
198
+ for (let n = 2; ; n++) {
199
+ const candidate = `${withResource}-${n}`;
200
+ if (!taken.has(candidate)) return candidate;
201
+ }
202
+ }
203
+ export function azureAccountEntries(input) {
204
+ const resource = input.resource.trim();
205
+ if (!resource) return { ok: false, error: "Enter the Azure resource name." };
206
+ if (!AZURE_RESOURCE.test(resource)) {
207
+ return {
208
+ ok: false,
209
+ error: "An Azure resource name has letters, digits and hyphens, and starts and ends with a letter or digit."
210
+ };
211
+ }
212
+ if (!input.openai && !input.foundry) {
213
+ return { ok: false, error: "Choose Azure OpenAI, Claude on Foundry or both." };
214
+ }
215
+ const taken = new Set(input.takenIds ?? []);
216
+ const entries = [];
217
+ const credential = input.credential ? { credential: { ...input.credential } } : {};
218
+ if (input.openai) {
219
+ const map = deploymentModelMap(input.openai.deployments);
220
+ if (!map.ok) return map;
221
+ const first = Object.values(map.modelMap)[0];
222
+ if (!first) return { ok: false, error: "Add at least one Azure OpenAI deployment." };
223
+ const apiVersion = input.openai.apiVersion.trim();
224
+ if (!apiVersion) return { ok: false, error: "Enter the Azure OpenAI api_version." };
225
+ const id = freeId(AZURE_OPENAI_PRESET, resource, taken);
226
+ taken.add(id);
227
+ entries.push({
228
+ id,
229
+ body: {
230
+ preset: AZURE_OPENAI_PRESET,
231
+ label: "Azure OpenAI",
232
+ params: { resource, deployment: first, api_version: apiVersion },
233
+ model_map: map.modelMap,
234
+ ...credential
235
+ }
236
+ });
237
+ }
238
+ if (input.foundry) {
239
+ const map = deploymentModelMap(input.foundry.deployments);
240
+ if (!map.ok) return map;
241
+ const id = freeId(FOUNDRY_PRESET, resource, taken);
242
+ taken.add(id);
243
+ entries.push({
244
+ id,
245
+ body: {
246
+ preset: FOUNDRY_PRESET,
247
+ label: "Claude on Foundry",
248
+ params: { resource },
249
+ ...Object.keys(map.modelMap).length > 0 ? { model_map: map.modelMap } : {},
250
+ ...credential
251
+ }
252
+ });
253
+ }
254
+ return { ok: true, entries };
255
+ }
@@ -35,6 +35,23 @@ export type AIProviderTransport = (typeof AI_PROVIDER_TRANSPORTS)[number];
35
35
  export declare const AI_PROVIDER_CREDENTIAL_KINDS: readonly ["api_key_header", "bearer", "sigv4", "google_adc", "azure_entra", "oauth", "command", "none"];
36
36
  /** Union of {@link AI_PROVIDER_CREDENTIAL_KINDS}. */
37
37
  export type AIProviderCredentialKind = (typeof AI_PROVIDER_CREDENTIAL_KINDS)[number];
38
+ /**
39
+ * Where an endpoint processes data: `eu` inside the EU, `eu_option` only once
40
+ * the account's EU-processing setting is on, `transfer` outside the EU,
41
+ * `local` on the operator's own machine or network. Upstream
42
+ * `AI_PROVIDER_DATA_ZONES` (`@skaile/workspaces` 9.1).
43
+ */
44
+ export declare const DATA_ZONES: readonly ["eu", "eu_option", "transfer", "local"];
45
+ /** Union of {@link DATA_ZONES}. */
46
+ export type DataZone = (typeof DATA_ZONES)[number];
47
+ /**
48
+ * Which data zones a host allows: `any` all, `eu_options` EU, local and
49
+ * `eu_option` entries with `eu_processing: true`, `eu` only EU and local.
50
+ * Upstream `AI_PROVIDER_DATA_ZONE_RULES` (`@skaile/workspaces` 9.1).
51
+ */
52
+ export declare const DATA_ZONE_RULES: readonly ["any", "eu_options", "eu"];
53
+ /** Union of {@link DATA_ZONE_RULES}. */
54
+ export type DataZoneRule = (typeof DATA_ZONE_RULES)[number];
38
55
  /** `transport: bedrock` routing parameters. */
39
56
  export interface AIProviderBedrockParams {
40
57
  /** AWS region, e.g. `us-east-1`. */
@@ -119,6 +136,10 @@ export interface AIProviderEntryCommon {
119
136
  enabled?: boolean;
120
137
  /** Set by the control plane: lower layers cannot override this entry. */
121
138
  locked?: boolean;
139
+ /** Where the endpoint processes data; overrides the preset's zone. Read it through `entryDataZone`. */
140
+ data_zone?: DataZone;
141
+ /** The account's EU-processing setting is on; only an `eu_option` entry reads it. */
142
+ eu_processing?: boolean;
122
143
  }
123
144
  /** An `llm`/`embed` entry — the full record. */
124
145
  export type AIProviderLlmEntry = AIProviderEntryCommon & AIProviderTransportSlice & {
@@ -132,6 +153,12 @@ export type AIProviderLlmEntry = AIProviderEntryCommon & AIProviderTransportSlic
132
153
  /** Keyed by the logical id, i.e. the `model_map` key. */
133
154
  capabilities?: Record<string, AIProviderCapabilities>;
134
155
  discovery?: AIProviderDiscovery;
156
+ /**
157
+ * The models this entry may be used with; absent = every model, `[]` =
158
+ * none. Logical ids of `model_map`, or provider-side ids (what a
159
+ * `discovery` entry lists). Read it through {@link isModelAllowed}.
160
+ */
161
+ allowed_models?: string[];
135
162
  };
136
163
  /**
137
164
  * An `stt`/`tts` entry — the reduced record. `surface`, `transport`,
@@ -184,6 +211,17 @@ export declare function registryEntries(registry: AIProviderRegistryView): Array
184
211
  export declare function entriesOfKind<K extends AIProviderKind>(registry: AIProviderRegistryView, kind: K): Array<[string, K extends AIProviderVoiceKind ? AIProviderVoiceEntry : AIProviderLlmEntry]>;
185
212
  /** Whether an entry uses the reduced voice record. Narrows `params` into view. */
186
213
  export declare function isVoiceEntry(entry: AIProviderEntry): entry is AIProviderVoiceEntry;
214
+ /**
215
+ * Whether an entry's `allowed_models` lets it be used with `modelId`. Absent
216
+ * allows every model, `[]` none. `modelId` and the list may each hold the
217
+ * logical id (a `model_map` key) or the provider-side id; a trailing
218
+ * thinking-level suffix (`:high`) is ignored. Mirrors `isModelAllowed` of
219
+ * `@skaile/workspaces/core` 9.1, which enforces it at model resolution.
220
+ */
221
+ export declare function isModelAllowed(entry: {
222
+ allowed_models?: readonly string[];
223
+ model_map?: Record<string, string>;
224
+ }, modelId: string): boolean;
187
225
  /**
188
226
  * The one `params` key a settings override replaces, per voice kind.
189
227
  *
@@ -27,6 +27,8 @@ export const AI_PROVIDER_CREDENTIAL_KINDS = [
27
27
  "command",
28
28
  "none"
29
29
  ];
30
+ export const DATA_ZONES = ["eu", "eu_option", "transfer", "local"];
31
+ export const DATA_ZONE_RULES = ["any", "eu_options", "eu"];
30
32
  export function isMergedRegistry(registry) {
31
33
  return Array.isArray(registry.order);
32
34
  }
@@ -47,6 +49,20 @@ export function entriesOfKind(registry, kind) {
47
49
  export function isVoiceEntry(entry) {
48
50
  return entry.kind === "stt" || entry.kind === "tts";
49
51
  }
52
+ const THINKING_SUFFIX = /:(off|minimal|low|medium|high|xhigh)$/i;
53
+ export function isModelAllowed(entry, modelId) {
54
+ const list = entry.allowed_models;
55
+ if (list === void 0) return true;
56
+ const matches = (id) => {
57
+ if (list.includes(id)) return true;
58
+ const mapped = entry.model_map?.[id];
59
+ if (mapped !== void 0 && list.includes(mapped)) return true;
60
+ return list.some((allowed) => entry.model_map?.[allowed] === id);
61
+ };
62
+ if (matches(modelId)) return true;
63
+ const base = modelId.replace(THINKING_SUFFIX, "");
64
+ return base !== modelId && matches(base);
65
+ }
50
66
  export const VOICE_OVERRIDE_PARAM = { stt: "language", tts: "voice_id" };
51
67
  export const VOICE_OVERRIDE_SETTING = { stt: "sttLanguage", tts: "ttsVoice" };
52
68
  export function selectVoiceEntryId(ids, wanted) {
@@ -55,6 +55,13 @@ export interface SkaileSettings extends Omit<CoreSettings, "apiKeys"> {
55
55
  * harness, all of them) is used automatically.
56
56
  */
57
57
  harnessProviders?: Record<string, string[]>;
58
+ /**
59
+ * Per-harness model per task ("roles"): role id → model selector
60
+ * (`"<providerId>/<modelId>"`), keyed by harness id. Only harnesses with
61
+ * `HARNESS_ROLES` take roles (omp, Continue); hosts pass the session
62
+ * harness's map to `skaile serve --roles`. Frozen by `lockModel`.
63
+ */
64
+ harnessRoles?: Record<string, Record<string, string>>;
58
65
  /**
59
66
  * Instance allow-list of harness ids the harness picker offers. Absent means
60
67
  * `DEFAULT_ENABLED_HARNESSES`.
@@ -6,10 +6,12 @@
6
6
  * `ForgeProvidersPanel` itself is registered as a component by
7
7
  * `@skaile/forge-settings/nuxt`.
8
8
  */
9
+ export * from "../shared/data-zone.js";
9
10
  export * from "../shared/env-vars.js";
10
11
  export * from "../shared/harness-providers.js";
11
12
  export * from "../shared/harnesses.js";
12
13
  export * from "../shared/model-roles.js";
14
+ export * from "../shared/provider-accounts.js";
13
15
  export * from "../shared/provider-registry.js";
14
16
  export * from "./model-roles-panel.js";
15
17
  export * from "./providers-client.js";
@@ -1,7 +1,9 @@
1
+ export * from "../shared/data-zone.js";
1
2
  export * from "../shared/env-vars.js";
2
3
  export * from "../shared/harness-providers.js";
3
4
  export * from "../shared/harnesses.js";
4
5
  export * from "../shared/model-roles.js";
6
+ export * from "../shared/provider-accounts.js";
5
7
  export * from "../shared/provider-registry.js";
6
8
  export * from "./model-roles-panel.js";
7
9
  export * from "./providers-client.js";
@@ -5,7 +5,8 @@
5
5
  * are re-exported by `useProvidersPanel`; the rest is internal to the package.
6
6
  */
7
7
  import type { EnvLayerId } from "../shared/env-vars.js";
8
- import type { AIProviderCredentialKind, AIProviderKind, AIProviderSurface } from "../shared/provider-registry.js";
8
+ import type { CredentialExpiry, ProviderDeploymentRow, ProviderSignInId } from "../shared/provider-accounts.js";
9
+ import type { AIProviderCredentialKind, AIProviderKind, AIProviderSurface, AIProviderTransport, DataZone, DataZoneRule } from "../shared/provider-registry.js";
9
10
  import type { ProvidersPanelClient } from "./providers-client.js";
10
11
  /** Registry document a provider entry is defined in. */
11
12
  export type ProvidersPanelLayer = "instance" | "workspace";
@@ -43,6 +44,17 @@ export interface ProvidersPanelProps {
43
44
  * `layer`; a string replaces it; `false` hides it (Add provider stays).
44
45
  */
45
46
  note?: string | false;
47
+ /**
48
+ * Expert mode: the sign-in step also offers the raw reference form
49
+ * (`env:` / `keyring:` / `cmd:` / `platform:`) and expert-only parameters
50
+ * (`api_version`) become editable. Default `false`.
51
+ */
52
+ expert?: boolean;
53
+ /**
54
+ * The host's data-zone rule ("Where may AI process data?"). Rows outside it
55
+ * show a muted note naming the rule. Absent or `any`: no row is marked.
56
+ */
57
+ dataZoneRule?: DataZoneRule;
46
58
  }
47
59
  /** Emits both variants declare. */
48
60
  export type ProvidersPanelEmit = (event: "changed") => void;
@@ -63,12 +75,29 @@ export interface ProvidersPanelEntry {
63
75
  baseUrl?: string;
64
76
  preset?: string;
65
77
  surface?: AIProviderSurface;
78
+ /** How requests reach the surface, when not `direct`. */
79
+ transport?: AIProviderTransport;
80
+ /** Routing parameters (`resource`, `deployment`, `api_version`, …) of an `llm`/`embed` entry. */
81
+ transportParams?: Record<string, string>;
82
+ /**
83
+ * When the credential expires (ISO timestamp), for a credential that
84
+ * reports one. The row then shows "Expires in N days · Renew".
85
+ */
86
+ credentialExpiresAt?: string | null;
66
87
  enabled: boolean;
67
88
  locked: boolean;
68
89
  /** Registry documents holding the id, lowest first: `["instance"]`, `["workspace"]` or both. */
69
90
  layers: ProvidersPanelLayer[];
70
91
  /** The scope's own document's values for the editable fields the dialog does not show. */
71
92
  layerFields?: Record<string, unknown>;
93
+ /** Where the entry processes data. Absent (an older host): the preset's zone, else `transfer`. */
94
+ dataZone?: DataZone;
95
+ /** The entry's `eu_processing` switch. */
96
+ euProcessing?: boolean;
97
+ /** The entry's `allowed_models`; absent = every model, `[]` = none. */
98
+ allowedModels?: string[];
99
+ /** Logical model ids the entry declares (`model_map`); absent: its models come from discovery. */
100
+ modelIds?: string[];
72
101
  }
73
102
  /** One catalog preset, as `GET /api/providers/presets` and `.../detected` list it. */
74
103
  export interface ProvidersPanelPreset {
@@ -76,12 +105,18 @@ export interface ProvidersPanelPreset {
76
105
  label: string;
77
106
  kind: AIProviderKind;
78
107
  surface?: AIProviderSurface;
108
+ /** How requests reach the surface; Azure transports offer Entra ID sign-in. */
109
+ transport?: AIProviderTransport;
79
110
  base_url?: string;
80
111
  base_url_env?: string;
81
112
  credential: {
82
113
  kind: AIProviderCredentialKind;
83
114
  env?: string;
84
115
  };
116
+ /** Default parameters. */
117
+ params?: Record<string, string>;
118
+ /** `params` keys an entry naming this preset must supply. */
119
+ required_params?: string[];
85
120
  }
86
121
  /** Where a row's entry lives relative to the scope's own document. */
87
122
  export type ProvidersPanelRowState = "own" | "inherited" | "patched";
@@ -121,8 +156,25 @@ export interface ProvidersPanelRow extends ProvidersPanelEntry {
121
156
  origin: string;
122
157
  /** `configured · <source>`, `missing <VAR>`, the resolver's reason, or `No credential needed.`. */
123
158
  status: string;
124
- /** Which other layer defines or overrides the credential; see the panel contract. */
125
159
  overrideNotices: string[];
160
+ /** "Microsoft Azure · Entra ID · contoso-ai": family · sign-in kind · resource. */
161
+ accountLine: string;
162
+ /** The credential's expiry, when it reports one. */
163
+ expiry: CredentialExpiry | null;
164
+ /** "Renew" is offered next to the expiry: the credential can be changed here. */
165
+ canRenew: boolean;
166
+ /** The entry's effective data zone. */
167
+ zone: DataZone;
168
+ /** The zone chip: EU / EU option / Transfer / Local. */
169
+ zoneLabel: string;
170
+ /** "EU processing is on for this account" is offered: an `eu_option` entry the panel may write. */
171
+ canSetEuProcessing: boolean;
172
+ /** Why the host's data-zone rule keeps the entry out, naming the rule; `null` when it does not. */
173
+ blockedNote: string | null;
174
+ /** The model chip: "All models" | "N models". `null` for a voice entry. */
175
+ modelsChip: string | null;
176
+ /** "Models allowed here" can be changed: an `llm`/`embed` entry the panel may write. */
177
+ canFilterModels: boolean;
126
178
  }
127
179
  /** The add/edit dialog's fields. */
128
180
  export interface ProvidersPanelForm {
@@ -133,15 +185,48 @@ export interface ProvidersPanelForm {
133
185
  surface: string;
134
186
  /** Empty: the preset's endpoint. */
135
187
  baseUrl: string;
136
- /** Adding only: the `env:` variable the credential references. */
188
+ /** Key sign-in: the `env:` variable the credential references. */
137
189
  envName: string;
138
- /** Adding only, "Set value now": written to the scope's `.env`, never to the provider route. */
190
+ /** Key sign-in, "Set value now": written to the scope's `.env`, never to the provider route. */
139
191
  secret: string;
192
+ /** The chosen "How do you sign in?" option. */
193
+ signIn: ProviderSignInId | "";
194
+ /** `reference` sign-in: the typed reference. */
195
+ credentialRef: string;
196
+ /** Routing parameters by key (`resource`, `deployment`, `api_version`, …). */
197
+ params: Record<string, string>;
198
+ }
199
+ /** The Microsoft Azure card's fields. */
200
+ export interface ProvidersPanelAzureForm {
201
+ /** Azure resource name, shared by both entries. */
202
+ resource: string;
203
+ /** Write an Azure OpenAI entry. */
204
+ useOpenAI: boolean;
205
+ /** Write a Claude on Foundry entry. */
206
+ useFoundry: boolean;
207
+ /** Azure OpenAI deployments: deployment name → logical model id (`model_map`). */
208
+ deployments: ProviderDeploymentRow[];
209
+ /** Azure OpenAI `api_version`; prefilled, editable in expert mode. */
210
+ apiVersion: string;
211
+ }
212
+ /** The add dialog's step: preset library, then the account fields, then the test of what was created. */
213
+ export type ProvidersPanelDialogStep = "library" | "account" | "done";
214
+ /** "Models allowed here": every model, or only the listed ones. */
215
+ export type ProvidersPanelModelsMode = "all" | "only";
216
+ /** The "Models allowed here" editor's fields. */
217
+ export interface ProvidersPanelModelsForm {
218
+ mode: ProvidersPanelModelsMode;
219
+ /** The ticked model ids. */
220
+ selected: string[];
221
+ /** The checklist's search text. */
222
+ query: string;
140
223
  }
141
224
  /** The preset select's value for an entry that names no preset. */
142
225
  export declare const CUSTOM_PRESET = "__custom__";
143
226
  /** Credential select value: open the inline key editor. */
144
227
  export declare const CREDENTIAL_ENTER_KEY = "__enter_key__";
228
+ /** The preset select's value for the Microsoft Azure account (Azure OpenAI and/or Claude on Foundry). */
229
+ export declare const AZURE_ACCOUNT = "__azure__";
145
230
  /** Credential select value: open the inline reference editor. */
146
231
  export declare const CREDENTIAL_OTHER_REF = "__other_ref__";
147
232
  /** Registry ids the write routes accept (`isSafeProviderId` upstream). */
@@ -163,6 +248,14 @@ export declare function providersPanelRowMeta(row: {
163
248
  export declare const ENV_REF_PATTERN: RegExp;
164
249
  /** The credential kind a written reference gets when the row states none. */
165
250
  export declare const FALLBACK_CREDENTIAL_KIND: AIProviderCredentialKind;
251
+ /** A row's model chip: "All models" without an allow-list, else its size. */
252
+ export declare function providerModelsChip(allowedModels: readonly string[] | undefined): string;
253
+ /**
254
+ * The checklist's ids: the known models (declared or discovered) in their
255
+ * order, then any allowed id neither lists (so it can still be unticked),
256
+ * narrowed by a case-insensitive `query`.
257
+ */
258
+ export declare function providerModelChoices(known: readonly string[], allowed: readonly string[], query?: string): string[];
166
259
  export declare const ENV_SOURCE_LABEL: Record<EnvLayerId, string>;
167
260
  export declare const OVERRIDES_INSTANCE_NOTICE = "Entering a key here stores it in this workspace's .env and overrides that value for this workspace.";
168
261
  /**
@@ -174,4 +267,4 @@ export declare function requestErrorMessage(err: unknown, fallback: string): str
174
267
  export declare function envNameOfRef(ref: string | undefined): string | undefined;
175
268
  /** `configured · …` for a reference that is not an env var. */
176
269
  export declare function nonEnvSourceLabel(ref: string): string | undefined;
177
- export declare const EMPTY_FORM: Readonly<ProvidersPanelForm>;
270
+ export declare const EMPTY_FORM: Readonly<Omit<ProvidersPanelForm, "params">>;
@@ -1,5 +1,6 @@
1
1
  export const CUSTOM_PRESET = "__custom__";
2
2
  export const CREDENTIAL_ENTER_KEY = "__enter_key__";
3
+ export const AZURE_ACCOUNT = "__azure__";
3
4
  export const CREDENTIAL_OTHER_REF = "__other_ref__";
4
5
  export const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
5
6
  export const DEFAULT_PRESETS_URL = "/api/providers/presets";
@@ -14,6 +15,15 @@ export function providersPanelRowMeta(row) {
14
15
  }
15
16
  export const ENV_REF_PATTERN = /^env:(.+)$/;
16
17
  export const FALLBACK_CREDENTIAL_KIND = "api_key_header";
18
+ export function providerModelsChip(allowedModels) {
19
+ if (allowedModels === void 0) return "All models";
20
+ return allowedModels.length === 1 ? "1 model" : `${allowedModels.length} models`;
21
+ }
22
+ export function providerModelChoices(known, allowed, query = "") {
23
+ const ids = [.../* @__PURE__ */ new Set([...known, ...allowed])];
24
+ const needle = query.trim().toLowerCase();
25
+ return needle ? ids.filter((id) => id.toLowerCase().includes(needle)) : ids;
26
+ }
17
27
  export const ENV_SOURCE_LABEL = {
18
28
  instance: "instance .env",
19
29
  workspace: "workspace .env",
@@ -42,5 +52,7 @@ export const EMPTY_FORM = {
42
52
  surface: "",
43
53
  baseUrl: "",
44
54
  envName: "",
45
- secret: ""
55
+ secret: "",
56
+ signIn: "",
57
+ credentialRef: ""
46
58
  };