-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathcustom-provider-store.ts
More file actions
113 lines (102 loc) · 3.62 KB
/
Copy pathcustom-provider-store.ts
File metadata and controls
113 lines (102 loc) · 3.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* Durable, encrypted storage for custom model providers.
*
* Mirrors model-credential-store: specs live in a DurableMap, API keys
* are encrypted at rest with a key derived from the connector secret,
* and the store never hands the plaintext key to anything but the
* per-call resolver.
*/
import { decryptSecret, deriveConnectorKey, encryptSecret } from "../connectors/connector-client-store.ts";
import type { DurableMap } from "../persistence/durable-map.ts";
import { customModelInputModalities, validateCustomProviderSpec, type CustomProviderSpec } from "./custom-providers.ts";
export interface StoredCustomProvider extends CustomProviderSpec {
apiKeyEnc?: string;
disabled?: boolean;
updatedAt: number;
updatedBy: string;
}
interface CustomProviderStatus extends CustomProviderSpec {
disabled: boolean;
hasKey: boolean;
updatedAt: number;
updatedBy: string;
}
export interface CustomProviderStore {
/** Enabled specs only — what the runtime registry should serve. */
enabled(): Promise<CustomProviderSpec[]>;
/** Everything, for the admin surface (no secrets). */
statuses(): Promise<CustomProviderStatus[]>;
/** Plaintext key for one provider, or null when absent/disabled. */
resolveKey(id: string): Promise<string | null>;
upsert(spec: CustomProviderSpec, apiKey: string | undefined, updatedBy: string): Promise<void>;
delete(id: string, updatedBy: string): Promise<boolean>;
}
function strip(saved: StoredCustomProvider): CustomProviderSpec {
return {
id: saved.id,
name: saved.name,
protocol: saved.protocol,
baseUrl: saved.baseUrl,
models: saved.models.map((model) => {
const stored = model as typeof model & { modalities?: unknown };
return stored.modalities === undefined
? { ...model }
: { ...model, modalities: customModelInputModalities(stored) };
}),
};
}
export function createCustomProviderStore(input: {
backing: DurableMap<StoredCustomProvider>;
keyMaterial: string | Buffer;
}): CustomProviderStore {
const key = deriveConnectorKey(input.keyMaterial, "custom-model-providers");
return {
async enabled() {
const all = await input.backing.all();
return all.filter((p) => !p.disabled).map(strip);
},
async statuses() {
const all = await input.backing.all();
return all
.map((p) => ({
...strip(p),
disabled: p.disabled ?? false,
hasKey: Boolean(p.apiKeyEnc),
updatedAt: p.updatedAt,
updatedBy: p.updatedBy,
}))
.sort((a, b) => a.id.localeCompare(b.id));
},
async resolveKey(id) {
const saved = await input.backing.get(id);
if (!saved || saved.disabled || !saved.apiKeyEnc) return null;
return decryptSecret(saved.apiKeyEnc, key);
},
async upsert(spec, apiKey, updatedBy) {
validateCustomProviderSpec(spec);
const actor = updatedBy.trim();
if (!actor) throw new Error("updatedBy is required");
const existing = await input.backing.get(spec.id);
const trimmedKey = apiKey?.trim();
const apiKeyEnc = trimmedKey ? encryptSecret(trimmedKey, key) : existing?.apiKeyEnc;
await input.backing.put(spec.id, {
...spec,
...(apiKeyEnc ? { apiKeyEnc } : {}),
disabled: false,
updatedAt: Date.now(),
updatedBy: actor,
});
},
async delete(id, updatedBy) {
const existing = await input.backing.get(id);
if (!existing || existing.disabled) return false;
await input.backing.put(id, {
...existing,
disabled: true,
updatedAt: Date.now(),
updatedBy,
});
return true;
},
};
}