@skaile/forge-settings 1.3.3 → 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 +15 -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,66 @@
1
+ /**
2
+ * Data zones: where a provider entry processes data, and which entries a
3
+ * host's data-zone rule ("Where may AI process data?") keeps.
4
+ *
5
+ * `@skaile/workspaces` 9.1 owns this (`entryDataZone`,
6
+ * `entryAllowedByDataZone`, `filterRegistryByDataZone` in
7
+ * `bridge/src/provider-catalog.ts`, the `data_zone` of each catalog preset).
8
+ * forge-settings mirrors it here so the settings panel and a host on an older
9
+ * SDK apply the same rule in the browser and on the server. Semantics match
10
+ * upstream exactly; {@link PRESET_DATA_ZONES} is a copy of the catalog's
11
+ * column, and the SDK catalog is its source.
12
+ *
13
+ * Browser-safe on purpose — no `node:` imports.
14
+ */
15
+ import { type AIProviderRegistry, type AIProviderRegistryView, type DataZone, type DataZoneRule, type MergedAIProviderRegistry } from "./provider-registry.js";
16
+ /**
17
+ * The `data_zone` of each catalog preset, by preset id. Copied from
18
+ * `@skaile/workspaces/bridge`'s `AI_PROVIDER_PRESETS` (9.1); change it there
19
+ * first. A preset this table does not name is `transfer`.
20
+ */
21
+ export declare const PRESET_DATA_ZONES: Readonly<Record<string, DataZone>>;
22
+ /** What a provider row's zone chip says. */
23
+ export declare const DATA_ZONE_LABELS: Readonly<Record<DataZone, string>>;
24
+ /** What a host calls each rule when it names the rule that keeps an entry out. */
25
+ export declare const DATA_ZONE_RULE_LABELS: Readonly<Record<DataZoneRule, string>>;
26
+ /** Whether `value` is one of {@link DATA_ZONES}. */
27
+ export declare function isDataZone(value: unknown): value is DataZone;
28
+ /** Whether `value` is one of {@link DATA_ZONE_RULES}. */
29
+ export declare function isDataZoneRule(value: unknown): value is DataZoneRule;
30
+ /** The zone of catalog preset `presetId`, or `undefined` for an id the catalog does not name. */
31
+ export declare function presetDataZone(presetId: string | undefined): DataZone | undefined;
32
+ /**
33
+ * Where an entry processes data: its own `data_zone`, else the zone of the
34
+ * catalog preset it names, else `transfer` (an unknown endpoint is assumed to
35
+ * leave the EU).
36
+ */
37
+ export declare function entryDataZone(entry: {
38
+ data_zone?: DataZone;
39
+ preset?: string;
40
+ }): DataZone;
41
+ /**
42
+ * Whether `rule` allows an entry in `zone`: `any` allows all; `eu` allows
43
+ * `eu` and `local`; `eu_options` additionally allows `eu_option` when
44
+ * `euProcessing` is `true`.
45
+ */
46
+ export declare function dataZoneAllows(rule: DataZoneRule, zone: DataZone, euProcessing: boolean | undefined): boolean;
47
+ /** Whether `rule` allows `entry` ({@link dataZoneAllows} over {@link entryDataZone}). */
48
+ export declare function entryAllowedByDataZone(entry: {
49
+ data_zone?: DataZone;
50
+ preset?: string;
51
+ eu_processing?: boolean;
52
+ }, rule: DataZoneRule): boolean;
53
+ /**
54
+ * Why `rule` keeps an entry in `zone` out, naming the rule, or `undefined`
55
+ * when the rule allows it. Secret-free, safe to render.
56
+ */
57
+ export declare function dataZoneRefusal(rule: DataZoneRule, zone: DataZone, euProcessing: boolean | undefined): string | undefined;
58
+ /**
59
+ * The registry with only the entries `rule` allows, every kind alike
60
+ * (`llm`, `embed`, `stt`, `tts`). Order is kept; the input is not mutated. A
61
+ * merged registry comes back merged, its `order`, `origin` and `locked`
62
+ * narrowed to the kept entries.
63
+ */
64
+ export declare function filterRegistryByDataZone(registry: AIProviderRegistry, rule: DataZoneRule): AIProviderRegistry;
65
+ export declare function filterRegistryByDataZone(registry: MergedAIProviderRegistry, rule: DataZoneRule): MergedAIProviderRegistry;
66
+ export declare function filterRegistryByDataZone(registry: AIProviderRegistryView, rule: DataZoneRule): AIProviderRegistryView;
@@ -0,0 +1,79 @@
1
+ import {
2
+ DATA_ZONE_RULES,
3
+ DATA_ZONES,
4
+ isMergedRegistry
5
+ } from "./provider-registry.js";
6
+ export const PRESET_DATA_ZONES = {
7
+ anthropic: "transfer",
8
+ openai: "transfer",
9
+ google: "transfer",
10
+ groq: "transfer",
11
+ deepseek: "transfer",
12
+ openrouter: "transfer",
13
+ mistral: "eu",
14
+ xai: "transfer",
15
+ together: "transfer",
16
+ fireworks: "transfer",
17
+ bedrock: "eu_option",
18
+ vertex: "eu_option",
19
+ "azure-openai": "eu_option",
20
+ foundry: "eu_option",
21
+ ollama: "local",
22
+ lmstudio: "local",
23
+ vllm: "local",
24
+ litellm: "transfer",
25
+ deepgram: "transfer",
26
+ elevenlabs: "transfer"
27
+ };
28
+ export const DATA_ZONE_LABELS = {
29
+ eu: "EU",
30
+ eu_option: "EU option",
31
+ transfer: "Transfer",
32
+ local: "Local"
33
+ };
34
+ export const DATA_ZONE_RULE_LABELS = {
35
+ any: "Anywhere",
36
+ eu_options: "EU, or EU option with EU processing on",
37
+ eu: "EU only"
38
+ };
39
+ export function isDataZone(value) {
40
+ return typeof value === "string" && DATA_ZONES.includes(value);
41
+ }
42
+ export function isDataZoneRule(value) {
43
+ return typeof value === "string" && DATA_ZONE_RULES.includes(value);
44
+ }
45
+ export function presetDataZone(presetId) {
46
+ return presetId !== void 0 && Object.hasOwn(PRESET_DATA_ZONES, presetId) ? PRESET_DATA_ZONES[presetId] : void 0;
47
+ }
48
+ export function entryDataZone(entry) {
49
+ return entry.data_zone ?? presetDataZone(entry.preset) ?? "transfer";
50
+ }
51
+ export function dataZoneAllows(rule, zone, euProcessing) {
52
+ if (rule === "any") return true;
53
+ if (zone === "eu" || zone === "local") return true;
54
+ return rule === "eu_options" && zone === "eu_option" && euProcessing === true;
55
+ }
56
+ export function entryAllowedByDataZone(entry, rule) {
57
+ return dataZoneAllows(rule, entryDataZone(entry), entry.eu_processing);
58
+ }
59
+ export function dataZoneRefusal(rule, zone, euProcessing) {
60
+ if (dataZoneAllows(rule, zone, euProcessing)) return void 0;
61
+ const why = zone === "eu_option" && rule === "eu_options" ? "EU processing is not on for this account" : `this provider is ${DATA_ZONE_LABELS[zone]}`;
62
+ return `Blocked by the data-zone rule "${DATA_ZONE_RULE_LABELS[rule]}": ${why}.`;
63
+ }
64
+ export function filterRegistryByDataZone(registry, rule) {
65
+ const entries = isMergedRegistry(registry) ? registry.providers : registry;
66
+ const providers = {};
67
+ for (const [id, entry] of Object.entries(entries)) {
68
+ if (entryAllowedByDataZone(entry, rule)) providers[id] = entry;
69
+ }
70
+ if (!isMergedRegistry(registry)) return providers;
71
+ const kept = (id) => Object.hasOwn(providers, id);
72
+ return {
73
+ ...registry,
74
+ providers,
75
+ order: registry.order.filter(kept),
76
+ origin: Object.fromEntries(Object.entries(registry.origin).filter(([id]) => kept(id))),
77
+ locked: registry.locked.filter(kept)
78
+ };
79
+ }
@@ -8,9 +8,12 @@
8
8
  * Eligibility is by wire surface, never by vendor name: an entry is eligible
9
9
  * for a harness when it is an enabled `llm` entry whose `surface` the harness
10
10
  * declares. A harness that declares no surfaces accepts every `llm` entry.
11
+ * A host's data-zone rule then keeps out the entries outside it: such an
12
+ * entry is reported as `blocked-by-rule`, never eligible and never chosen.
11
13
  *
12
14
  * Browser-safe on purpose — no `node:` imports.
13
15
  */
16
+ import type { DataZone, DataZoneRule } from "./provider-registry.js";
14
17
  /** How many registry entries a harness can run at once (`@skaile/workspaces` `HarnessInfo.providerPlurality`). */
15
18
  export type HarnessProviderPlurality = "registry" | "credentials-n" | "single";
16
19
  /** The slice of a harness the rule reads. `GET /api/agents` rows satisfy it. */
@@ -30,6 +33,32 @@ export interface HarnessProviderCandidate {
30
33
  surface?: string;
31
34
  /** Absent means enabled. */
32
35
  enabled?: boolean;
36
+ /** Catalog preset; gives the zone when {@link dataZone} is absent. */
37
+ preset?: string;
38
+ /** Effective data zone (`GET /api/providers` rows carry it). Absent: the preset's, else `transfer`. */
39
+ dataZone?: DataZone;
40
+ /** The account's EU-processing setting is on. */
41
+ euProcessing?: boolean;
42
+ }
43
+ /** Why an entry on a surface the harness speaks still cannot be used. */
44
+ export type HarnessProviderBlockReason = "blocked-by-rule";
45
+ /** One entry the harness could speak to that the host's rule keeps out. */
46
+ export interface HarnessProviderBlock {
47
+ id: string;
48
+ reason: HarnessProviderBlockReason;
49
+ /** The rule in force. */
50
+ rule: DataZoneRule;
51
+ /** The rule's label ("EU only"). */
52
+ ruleLabel: string;
53
+ /** The entry's effective zone. */
54
+ zone: DataZone;
55
+ /** What a settings table shows on the box it cannot tick. Secret-free. */
56
+ message: string;
57
+ }
58
+ /** What {@link resolveHarnessProviders} applies beyond the surface rule. */
59
+ export interface HarnessProviderOptions {
60
+ /** The host's data-zone rule. Absent: `any`. */
61
+ dataZoneRule?: DataZoneRule;
33
62
  }
34
63
  /**
35
64
  * - `chosen`: the stored pick, narrowed to what is still eligible, is used.
@@ -48,15 +77,25 @@ export interface HarnessProviderResolution<C extends HarnessProviderCandidate =
48
77
  state: HarnessProviderState;
49
78
  /** The harness can run several providers at once. */
50
79
  multiple: boolean;
51
- /** Stored pick ids that are no longer eligible, deduplicated, in stored order. */
80
+ /** Stored pick ids that are no longer eligible (blocked ones included), deduplicated, in stored order. */
52
81
  stale: string[];
82
+ /** Candidates on a surface the harness speaks that the data-zone rule keeps out, in registry order. */
83
+ blocked: HarnessProviderBlock[];
53
84
  }
54
85
  /** Whether `candidate` is an enabled `llm` entry on a surface `harness` speaks. */
55
86
  export declare function isProviderEligibleForHarness(harness: HarnessProviderInfo, candidate: HarnessProviderCandidate): boolean;
87
+ /**
88
+ * Why the data-zone rule keeps `candidate` out of `harness`, or `undefined`
89
+ * when it does not: the candidate must be otherwise eligible
90
+ * ({@link isProviderEligibleForHarness}) and outside `rule`.
91
+ */
92
+ export declare function harnessProviderBlock(harness: HarnessProviderInfo, candidate: HarnessProviderCandidate, rule: DataZoneRule | undefined): HarnessProviderBlock | undefined;
56
93
  /**
57
94
  * Resolve the providers a harness runs against.
58
95
  *
59
96
  * @param candidates Registry entries in registry order.
60
97
  * @param selection The stored per-harness pick (`SkaileSettings.harnessProviders[harness.id]`).
98
+ * @param options.dataZoneRule The host's data-zone rule: entries outside it are
99
+ * `blocked`, never eligible, never auto-chosen; a stored pick of one is `stale`.
61
100
  */
62
- export declare function resolveHarnessProviders<C extends HarnessProviderCandidate>(harness: HarnessProviderInfo, candidates: readonly C[], selection: readonly string[] | undefined): HarnessProviderResolution<C>;
101
+ export declare function resolveHarnessProviders<C extends HarnessProviderCandidate>(harness: HarnessProviderInfo, candidates: readonly C[], selection: readonly string[] | undefined, options?: HarnessProviderOptions): HarnessProviderResolution<C>;
@@ -1,14 +1,35 @@
1
+ import { DATA_ZONE_RULE_LABELS, dataZoneRefusal, presetDataZone } from "./data-zone.js";
1
2
  export function isProviderEligibleForHarness(harness, candidate) {
2
3
  if ((candidate.kind ?? "llm") !== "llm") return false;
3
4
  if (candidate.enabled === false) return false;
4
5
  if (!harness.surfaces) return true;
5
6
  return candidate.surface !== void 0 && harness.surfaces.includes(candidate.surface);
6
7
  }
7
- export function resolveHarnessProviders(harness, candidates, selection) {
8
+ export function harnessProviderBlock(harness, candidate, rule) {
9
+ if (!rule || rule === "any" || !isProviderEligibleForHarness(harness, candidate)) {
10
+ return void 0;
11
+ }
12
+ const zone = candidate.dataZone ?? presetDataZone(candidate.preset) ?? "transfer";
13
+ const message = dataZoneRefusal(rule, zone, candidate.euProcessing);
14
+ if (!message) return void 0;
15
+ return {
16
+ id: candidate.id,
17
+ reason: "blocked-by-rule",
18
+ rule,
19
+ ruleLabel: DATA_ZONE_RULE_LABELS[rule],
20
+ zone,
21
+ message
22
+ };
23
+ }
24
+ export function resolveHarnessProviders(harness, candidates, selection, options = {}) {
8
25
  const multiple = harness.providerPlurality === "registry" || harness.providerPlurality === "credentials-n";
9
- const eligible = candidates.filter(
10
- (candidate) => isProviderEligibleForHarness(harness, candidate)
11
- );
26
+ const blocked = [];
27
+ const eligible = candidates.filter((candidate) => {
28
+ if (!isProviderEligibleForHarness(harness, candidate)) return false;
29
+ const block = harnessProviderBlock(harness, candidate, options.dataZoneRule);
30
+ if (block) blocked.push(block);
31
+ return !block;
32
+ });
12
33
  const eligibleIds = new Set(eligible.map((candidate) => candidate.id));
13
34
  const picked = new Set(selection ?? []);
14
35
  const stale = [...picked].filter((id) => !eligibleIds.has(id));
@@ -19,11 +40,12 @@ export function resolveHarnessProviders(harness, candidates, selection) {
19
40
  selected: multiple ? usable : usable.slice(0, 1),
20
41
  state: "chosen",
21
42
  multiple,
22
- stale
43
+ stale,
44
+ blocked
23
45
  };
24
46
  }
25
47
  if (eligible.length === 0) {
26
- return { eligible, selected: [], state: "none-eligible", multiple, stale };
48
+ return { eligible, selected: [], state: "none-eligible", multiple, stale, blocked };
27
49
  }
28
50
  if (eligible.length === 1 || multiple) {
29
51
  return {
@@ -31,8 +53,9 @@ export function resolveHarnessProviders(harness, candidates, selection) {
31
53
  selected: eligible.map((candidate) => candidate.id),
32
54
  state: "auto",
33
55
  multiple,
34
- stale
56
+ stale,
57
+ blocked
35
58
  };
36
59
  }
37
- return { eligible, selected: [], state: "needs-choice", multiple, stale };
60
+ return { eligible, selected: [], state: "needs-choice", multiple, stale, blocked };
38
61
  }
@@ -17,3 +17,10 @@ export declare const HARNESS_LABELS: Record<string, string>;
17
17
  export declare const DEFAULT_ENABLED_HARNESSES: readonly ["omp", "claude-sdk", "codex"];
18
18
  /** Whether `id` is outside {@link DEFAULT_ENABLED_HARNESSES}, i.e. opt-in only. */
19
19
  export declare function isExperimentalHarness(id: string): boolean;
20
+ /**
21
+ * The model roles ("models per task") each multi-model harness renders, keyed by
22
+ * harness id. A harness absent here runs one model and takes no roles.
23
+ */
24
+ export declare const HARNESS_ROLES: Readonly<Record<string, readonly string[]>>;
25
+ /** The role ids `harnessId` supports ({@link HARNESS_ROLES}); empty for a one-model harness. */
26
+ export declare function harnessRoleIds(harnessId: string): string[];
@@ -13,3 +13,21 @@ export const DEFAULT_ENABLED_HARNESSES = ["omp", "claude-sdk", "codex"];
13
13
  export function isExperimentalHarness(id) {
14
14
  return !DEFAULT_ENABLED_HARNESSES.includes(id);
15
15
  }
16
+ export const HARNESS_ROLES = {
17
+ omp: [
18
+ "default",
19
+ "smol",
20
+ "slow",
21
+ "vision",
22
+ "plan",
23
+ "designer",
24
+ "commit",
25
+ "tiny",
26
+ "task",
27
+ "advisor"
28
+ ],
29
+ continue: ["chat", "edit", "apply", "autocomplete", "embed", "rerank", "summarize"]
30
+ };
31
+ export function harnessRoleIds(harnessId) {
32
+ return [...HARNESS_ROLES[harnessId] ?? []];
33
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Provider accounts: how one signs in to a catalog preset, what a provider
3
+ * row says about its account, and the Microsoft Azure account that writes up
4
+ * to two registry entries (Azure OpenAI and Claude on Foundry) sharing one
5
+ * resource and one credential.
6
+ *
7
+ * Pure and browser-safe: the settings panel and the server's write gate read
8
+ * the same rules. The facts come from the catalog preset
9
+ * (`@skaile/workspaces`' `AIProviderPreset`): its `credential.kind`, its
10
+ * `transport`, its default `params` and its `required_params`. The catalog
11
+ * names no list of accepted sign-in kinds, so they are derived here from those
12
+ * facts.
13
+ */
14
+ import { type AIProviderCredential, type AIProviderCredentialKind, type AIProviderTransport } from "./provider-registry.js";
15
+ /** The preset facts the account helpers read; a catalog preset and its summary both fit. */
16
+ export interface ProviderAccountPreset {
17
+ id: string;
18
+ label: string;
19
+ transport?: AIProviderTransport;
20
+ credential: {
21
+ kind: AIProviderCredentialKind;
22
+ env?: string;
23
+ };
24
+ /** Default routing parameters. */
25
+ params?: Record<string, string>;
26
+ /** `params` keys the operator must supply. */
27
+ required_params?: string[];
28
+ }
29
+ /**
30
+ * The `params` keys a preset requires that `params` leaves empty: the
31
+ * preset's `required_params` minus the ones its own `params` default.
32
+ * A value that is not a non-blank string, number or boolean counts as missing.
33
+ */
34
+ export declare function missingRequiredParams(preset: Pick<ProviderAccountPreset, "required_params" | "params">, params: Record<string, unknown> | undefined): string[];
35
+ /** What the add/edit dialog calls each routing parameter. */
36
+ export declare const PROVIDER_PARAM_LABELS: Record<string, string>;
37
+ /**
38
+ * Parameters the operator sets only in expert mode: the dialog prefills them
39
+ * and shows them read-only otherwise.
40
+ */
41
+ export declare const EXPERT_PARAMS: readonly string[];
42
+ /**
43
+ * The parameter fields an entry of `preset` needs: its required params the
44
+ * preset gives no default for, then any further key `current` already holds.
45
+ */
46
+ export declare function presetParamKeys(preset: Pick<ProviderAccountPreset, "required_params" | "params"> | null | undefined, current?: Record<string, unknown> | undefined): string[];
47
+ /** The value the dialog prefills for parameter `key` of `presetId`, else `""`. */
48
+ export declare function defaultParamValue(presetId: string | undefined, key: string): string;
49
+ /** One way to sign in to a provider, as the add flow's "How do you sign in?" step lists it. */
50
+ export type ProviderSignInId = "api_key" | "entra" | "aws" | "google" | "oauth" | "none" | "reference";
51
+ /** What the sign-in step offers for one {@link ProviderSignInId}. */
52
+ export interface ProviderSignInOption {
53
+ id: ProviderSignInId;
54
+ label: string;
55
+ description: string;
56
+ /** The `credential.kind` the entry gets. */
57
+ credentialKind: AIProviderCredentialKind;
58
+ /**
59
+ * The fields the next step shows: `key` = environment variable name plus
60
+ * an optional value for the scope's `.env`; `reference` = a typed
61
+ * `env:` / `keyring:` / `cmd:` / `platform:` reference; `none` = nothing
62
+ * (no credential, or the server's ambient cloud chain signs).
63
+ */
64
+ fields: "key" | "reference" | "none";
65
+ }
66
+ /** Transports that run on a Microsoft Azure resource. */
67
+ export declare const AZURE_TRANSPORTS: readonly AIProviderTransport[];
68
+ /** Whether a transport runs on a Microsoft Azure resource (and so accepts Entra ID). */
69
+ export declare function isAzureTransport(transport: AIProviderTransport | undefined): boolean;
70
+ /**
71
+ * The sign-in kinds a preset accepts, in the order the sign-in step lists
72
+ * them. `null` is a custom entry (no preset).
73
+ *
74
+ * - `none` presets (local model servers): No sign-in only.
75
+ * - `api_key_header` / `bearer` presets: API key; Azure transports
76
+ * (`azure-openai`, `foundry`) list Entra ID first, then API key.
77
+ * - `oauth` / `command` presets: a reference (the token lives behind one).
78
+ * - `expert`: the raw Reference form is added for every preset that takes a
79
+ * credential.
80
+ */
81
+ export declare function presetSignInOptions(preset: Pick<ProviderAccountPreset, "credential" | "transport"> | null, options?: {
82
+ expert?: boolean;
83
+ }): ProviderSignInOption[];
84
+ /** What the sign-in fields hold. */
85
+ export interface ProviderSignInInput {
86
+ /** `key` fields: the environment variable the key lives in. */
87
+ envName?: string;
88
+ /** `reference` fields: the typed reference. */
89
+ ref?: string;
90
+ }
91
+ /** The credential a sign-in choice writes, or why it cannot be written. */
92
+ export type ProviderSignInCredential = {
93
+ ok: true;
94
+ credential?: AIProviderCredential;
95
+ } | {
96
+ ok: false;
97
+ error: string;
98
+ };
99
+ /**
100
+ * The `credential` of an entry signed in with `option`.
101
+ *
102
+ * `key`: `{ kind, ref: "env:<NAME>" }`; a blank name falls back to
103
+ * `defaultEnv` (the preset's conventional variable) and, without one, writes
104
+ * no credential so the preset's own applies — unless `requireCredential`.
105
+ * `reference`: `{ kind, ref }` after the reference check. `none` fields: a
106
+ * cloud chain writes `{ kind }` without a reference, No sign-in writes
107
+ * `{ kind: "none" }`.
108
+ */
109
+ export declare function signInCredential(option: ProviderSignInOption, input: ProviderSignInInput, context?: {
110
+ defaultEnv?: string;
111
+ requireCredential?: boolean;
112
+ }): ProviderSignInCredential;
113
+ /** The sign-in option an existing credential corresponds to, among `options`. */
114
+ export declare function signInOptionOf(credential: {
115
+ kind?: AIProviderCredentialKind;
116
+ ref?: string;
117
+ } | undefined, options: readonly ProviderSignInOption[]): ProviderSignInOption | undefined;
118
+ /** What a row says for each credential kind. */
119
+ export declare const CREDENTIAL_KIND_LABELS: Record<AIProviderCredentialKind, string>;
120
+ /** The family an Azure-transport entry belongs to. */
121
+ export declare const AZURE_FAMILY_LABEL = "Microsoft Azure";
122
+ /**
123
+ * The provider family a row belongs to: "Microsoft Azure" for an
124
+ * Azure transport, else its preset's label, else "Custom".
125
+ */
126
+ export declare function providerFamilyLabel(entry: {
127
+ preset?: string;
128
+ transport?: AIProviderTransport;
129
+ }, presets: readonly Pick<ProviderAccountPreset, "id" | "label" | "transport">[]): string;
130
+ /**
131
+ * The account line of a provider row: family · sign-in kind · resource, each
132
+ * part only when known — "Microsoft Azure · Entra ID · contoso-ai".
133
+ */
134
+ export declare function providerAccountLine(entry: {
135
+ preset?: string;
136
+ transport?: AIProviderTransport;
137
+ credentialKind?: AIProviderCredentialKind;
138
+ transportParams?: Record<string, string>;
139
+ }, presets: readonly Pick<ProviderAccountPreset, "id" | "label" | "transport" | "credential">[]): string;
140
+ /** A credential's expiry as a row shows it. */
141
+ export interface CredentialExpiry {
142
+ /** Whole days left, rounded down; `0` = expires within a day, negative = expired. */
143
+ days: number;
144
+ expired: boolean;
145
+ /** "Expires in 12 days" · "Expires in 1 day" · "Expires today" · "Expired". */
146
+ text: string;
147
+ }
148
+ /** The expiry line for an ISO timestamp, or `null` when there is none or it does not parse. */
149
+ export declare function credentialExpiry(expiresAt: string | null | undefined, now?: number): CredentialExpiry | null;
150
+ /** Catalog preset of the Azure OpenAI entry. */
151
+ export declare const AZURE_OPENAI_PRESET = "azure-openai";
152
+ /** Catalog preset of the Claude on Foundry entry. */
153
+ export declare const FOUNDRY_PRESET = "foundry";
154
+ /**
155
+ * The `api_version` the Azure account prefills when the preset names none
156
+ * (the catalog's `azure-openai` preset requires it but has no default).
157
+ */
158
+ export declare const AZURE_OPENAI_DEFAULT_API_VERSION = "2025-04-01-preview";
159
+ /** One deployment row: the Azure deployment name and the logical model id it serves. */
160
+ export interface ProviderDeploymentRow {
161
+ deployment: string;
162
+ /** Logical model id (the `model_map` key). Blank: the deployment name. */
163
+ model: string;
164
+ }
165
+ /**
166
+ * `model_map` (logical id → deployment name) from deployment rows. Rows with
167
+ * both fields blank are skipped; a model without a deployment, or two rows
168
+ * naming the same logical id, is refused.
169
+ */
170
+ export declare function deploymentModelMap(rows: readonly ProviderDeploymentRow[]): {
171
+ ok: true;
172
+ modelMap: Record<string, string>;
173
+ } | {
174
+ ok: false;
175
+ error: string;
176
+ };
177
+ /** What the Microsoft Azure card collects. */
178
+ export interface AzureAccountInput {
179
+ /** Azure resource name: the `{resource}` of `<resource>.openai.azure.com` / `<resource>.services.ai.azure.com`. */
180
+ resource: string;
181
+ /** The one credential both entries get (from {@link signInCredential}). */
182
+ credential?: AIProviderCredential;
183
+ /** Set: write an Azure OpenAI entry. At least one deployment. */
184
+ openai?: {
185
+ deployments: readonly ProviderDeploymentRow[];
186
+ apiVersion: string;
187
+ };
188
+ /** Set: write a Claude on Foundry entry; deployments are optional. */
189
+ foundry?: {
190
+ deployments: readonly ProviderDeploymentRow[];
191
+ };
192
+ /** Entry ids the scope already uses; new ids avoid them. */
193
+ takenIds?: readonly string[];
194
+ }
195
+ /** One entry to `PUT`: its id and body. */
196
+ export interface ProviderEntryDraft {
197
+ id: string;
198
+ body: Record<string, unknown>;
199
+ }
200
+ /**
201
+ * The registry entries one Microsoft Azure account writes: an Azure OpenAI
202
+ * entry (preset `azure-openai`, `params` `{ resource, deployment, api_version }`
203
+ * with the first deployment as the routed one, `model_map` from the
204
+ * deployment rows) and/or a Claude on Foundry entry (preset `foundry`,
205
+ * `params.resource`, optional `model_map`). Both carry the same resource and
206
+ * the same credential. Ids are the preset ids, else `<preset>-<resource>`,
207
+ * else numbered, never one of `takenIds`.
208
+ */
209
+ export declare function azureAccountEntries(input: AzureAccountInput): {
210
+ ok: true;
211
+ entries: ProviderEntryDraft[];
212
+ } | {
213
+ ok: false;
214
+ error: string;
215
+ };