@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
|
@@ -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
|
|
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
|
|
10
|
-
|
|
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
|
+
};
|