@byok-sdk/keys 0.3.9 → 0.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/README.md +13 -1
- package/dist/bin/pi-provider-launcher.js +430 -181
- package/dist/bin/pi-provider-launcher.js.map +1 -1
- package/dist/errors.d.ts +4 -2
- package/dist/index.d.ts +5 -3
- package/dist/index.js +310 -94
- package/dist/index.js.map +1 -1
- package/dist/pi-provider-launcher-core.d.ts +5 -2
- package/dist/pi-provider-projection.d.ts +11 -2
- package/dist/profile-store.d.ts +12 -12
- package/dist/provider-catalog.d.ts +73 -0
- package/dist/provider-profile.d.ts +86 -5
- package/dist/registry.d.ts +22 -9
- package/dist/secret-store.d.ts +17 -14
- package/dist/sqlite-profile-store.d.ts +6 -6
- package/dist/truth-profile-store.d.ts +14 -4
- package/package.json +2 -2
|
@@ -4,6 +4,7 @@ import { promises, mkdirSync, existsSync, chmodSync } from 'fs';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import path2, { dirname } from 'path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
7
8
|
import { createRequire } from 'module';
|
|
8
9
|
|
|
9
10
|
// src/errors.ts
|
|
@@ -71,16 +72,291 @@ function assertSecretNamespace(value) {
|
|
|
71
72
|
return normalized;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// src/provider-catalog.ts
|
|
76
|
+
function openAiCompatible(display_name, base_url, api_key_env) {
|
|
77
|
+
return {
|
|
78
|
+
display_name,
|
|
79
|
+
base_url,
|
|
80
|
+
adapter: "openai_compatible",
|
|
81
|
+
auth_mode: "bearer",
|
|
82
|
+
api_key_env
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function anthropicMessages(display_name, base_url, api_key_env) {
|
|
86
|
+
return {
|
|
87
|
+
display_name,
|
|
88
|
+
base_url,
|
|
89
|
+
adapter: "anthropic",
|
|
90
|
+
auth_mode: "x_api_key",
|
|
91
|
+
api_key_env
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
var MODEL_PROVIDER_VENDORS = {
|
|
95
|
+
"ant-ling": openAiCompatible("Ant Ling", "https://api.ant-ling.com/v1", "ANT_LING_API_KEY"),
|
|
96
|
+
baseten: openAiCompatible("Baseten", "https://inference.baseten.co/v1", "BASETEN_API_KEY"),
|
|
97
|
+
cerebras: openAiCompatible("Cerebras", "https://api.cerebras.ai/v1", "CEREBRAS_API_KEY"),
|
|
98
|
+
deepseek: openAiCompatible("DeepSeek", "https://api.deepseek.com", "DEEPSEEK_API_KEY"),
|
|
99
|
+
groq: openAiCompatible("Groq", "https://api.groq.com/openai/v1", "GROQ_API_KEY"),
|
|
100
|
+
huggingface: openAiCompatible("Hugging Face", "https://router.huggingface.co/v1", "HF_TOKEN"),
|
|
101
|
+
moonshotai: openAiCompatible("Moonshot AI", "https://api.moonshot.ai/v1", "MOONSHOT_API_KEY"),
|
|
102
|
+
"moonshotai-cn": openAiCompatible("Moonshot AI CN", "https://api.moonshot.cn/v1", "MOONSHOT_API_KEY"),
|
|
103
|
+
nvidia: openAiCompatible("NVIDIA", "https://integrate.api.nvidia.com/v1", "NVIDIA_API_KEY"),
|
|
104
|
+
openai: openAiCompatible("OpenAI", "https://api.openai.com/v1", "OPENAI_API_KEY"),
|
|
105
|
+
openrouter: openAiCompatible("OpenRouter", "https://openrouter.ai/api/v1", "OPENROUTER_API_KEY"),
|
|
106
|
+
"qwen-token-plan": openAiCompatible(
|
|
107
|
+
"Qwen Token Plan",
|
|
108
|
+
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
|
|
109
|
+
"QWEN_TOKEN_PLAN_API_KEY"
|
|
110
|
+
),
|
|
111
|
+
"qwen-token-plan-cn": openAiCompatible(
|
|
112
|
+
"Qwen Token Plan CN",
|
|
113
|
+
"https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
|
|
114
|
+
"QWEN_TOKEN_PLAN_CN_API_KEY"
|
|
115
|
+
),
|
|
116
|
+
together: openAiCompatible("Together", "https://api.together.ai/v1", "TOGETHER_API_KEY"),
|
|
117
|
+
xai: openAiCompatible("xAI", "https://api.x.ai/v1", "XAI_API_KEY"),
|
|
118
|
+
xiaomi: openAiCompatible("Xiaomi", "https://api.xiaomimimo.com/v1", "XIAOMI_API_KEY"),
|
|
119
|
+
"xiaomi-token-plan-ams": openAiCompatible(
|
|
120
|
+
"Xiaomi Token Plan AMS",
|
|
121
|
+
"https://token-plan-ams.xiaomimimo.com/v1",
|
|
122
|
+
"XIAOMI_TOKEN_PLAN_AMS_API_KEY"
|
|
123
|
+
),
|
|
124
|
+
"xiaomi-token-plan-cn": openAiCompatible(
|
|
125
|
+
"Xiaomi Token Plan CN",
|
|
126
|
+
"https://token-plan-cn.xiaomimimo.com/v1",
|
|
127
|
+
"XIAOMI_TOKEN_PLAN_CN_API_KEY"
|
|
128
|
+
),
|
|
129
|
+
"xiaomi-token-plan-sgp": openAiCompatible(
|
|
130
|
+
"Xiaomi Token Plan SGP",
|
|
131
|
+
"https://token-plan-sgp.xiaomimimo.com/v1",
|
|
132
|
+
"XIAOMI_TOKEN_PLAN_SGP_API_KEY"
|
|
133
|
+
),
|
|
134
|
+
zai: openAiCompatible("Z.AI", "https://api.z.ai/api/coding/paas/v4", "ZAI_API_KEY"),
|
|
135
|
+
"zai-coding-cn": openAiCompatible(
|
|
136
|
+
"Z.AI Coding CN",
|
|
137
|
+
"https://open.bigmodel.cn/api/coding/paas/v4",
|
|
138
|
+
"ZAI_CODING_CN_API_KEY"
|
|
139
|
+
),
|
|
140
|
+
anthropic: anthropicMessages("Anthropic", "https://api.anthropic.com/v1", "ANTHROPIC_API_KEY"),
|
|
141
|
+
fireworks: anthropicMessages("Fireworks", "https://api.fireworks.ai/inference/v1", "FIREWORKS_API_KEY"),
|
|
142
|
+
"kimi-coding": anthropicMessages("Kimi For Coding", "https://api.kimi.com/coding/v1", "KIMI_API_KEY"),
|
|
143
|
+
minimax: anthropicMessages("MiniMax", "https://api.minimax.io/anthropic/v1", "MINIMAX_API_KEY"),
|
|
144
|
+
"minimax-cn": anthropicMessages("MiniMax CN", "https://api.minimaxi.com/anthropic/v1", "MINIMAX_CN_API_KEY"),
|
|
145
|
+
"vercel-ai-gateway": anthropicMessages(
|
|
146
|
+
"Vercel AI Gateway",
|
|
147
|
+
"https://ai-gateway.vercel.sh/v1",
|
|
148
|
+
"AI_GATEWAY_API_KEY"
|
|
149
|
+
)
|
|
150
|
+
};
|
|
151
|
+
var MODEL_PROVIDER_VENDOR_IDS = Object.keys(
|
|
152
|
+
MODEL_PROVIDER_VENDORS
|
|
153
|
+
);
|
|
154
|
+
function modelProviderVendor(kind) {
|
|
155
|
+
return Object.hasOwn(MODEL_PROVIDER_VENDORS, kind) ? MODEL_PROVIDER_VENDORS[kind] : void 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/url.ts
|
|
159
|
+
function normalizeProviderUrl(value) {
|
|
160
|
+
let url;
|
|
161
|
+
try {
|
|
162
|
+
url = new URL(value);
|
|
163
|
+
} catch {
|
|
164
|
+
throw new ByokKeysError(
|
|
165
|
+
"PROVIDER_URL_INVALID",
|
|
166
|
+
"Provider base URL must be absolute"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (url.username || url.password || url.hash || url.search || url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) {
|
|
170
|
+
throw new ByokKeysError(
|
|
171
|
+
"PROVIDER_URL_INVALID",
|
|
172
|
+
"Provider URL requires HTTPS; HTTP is allowed only for localhost"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (isPrivateNetworkLiteral(url.hostname) && !isLoopbackHost(url.hostname)) {
|
|
176
|
+
throw new ByokKeysError(
|
|
177
|
+
"PROVIDER_URL_INVALID",
|
|
178
|
+
"Private-network provider IPs are not allowed"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return url.toString().replace(/\/$/u, "");
|
|
182
|
+
}
|
|
183
|
+
function isLoopbackHost(hostname) {
|
|
184
|
+
const value = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
185
|
+
return value === "localhost" || value === "127.0.0.1" || value === "::1";
|
|
186
|
+
}
|
|
187
|
+
function isPrivateNetworkLiteral(hostname) {
|
|
188
|
+
const value = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
189
|
+
if (value.includes(":")) return true;
|
|
190
|
+
const parts = value.split(".").map(Number);
|
|
191
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return parts[0] === 10 || parts[0] === 127 || parts[0] === 169 && parts[1] === 254 || parts[0] === 172 && (parts[1] ?? 0) >= 16 && (parts[1] ?? 0) <= 31 || parts[0] === 192 && parts[1] === 168;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/provider-profile.ts
|
|
198
|
+
var PROVIDER_PROFILE_REF_PATTERN = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/u;
|
|
199
|
+
var ProviderProfileRefSchema = z.string().min(1).max(64).regex(
|
|
200
|
+
PROVIDER_PROFILE_REF_PATTERN,
|
|
201
|
+
"provider profile refs must be lowercase portable identifiers"
|
|
202
|
+
);
|
|
203
|
+
var MODEL_PROVIDER_KINDS = [
|
|
204
|
+
...MODEL_PROVIDER_VENDOR_IDS,
|
|
205
|
+
"custom"
|
|
206
|
+
];
|
|
207
|
+
var PROVIDER_MODEL_CAPABILITIES = ["image-input"];
|
|
208
|
+
var ProviderModelCapabilitySchema = z.enum(PROVIDER_MODEL_CAPABILITIES);
|
|
209
|
+
var PROVIDER_AUTH_MODES = ["bearer", "x_api_key", "none"];
|
|
210
|
+
var MODEL_PROVIDER_ADAPTERS = ["openai_compatible", "anthropic"];
|
|
211
|
+
function boundedString(field, maximumLength) {
|
|
212
|
+
return z.string().superRefine((value, ctx) => {
|
|
213
|
+
if (value.trim().length === 0 || value.length > maximumLength || /[\u0000\r\n]/u.test(value)) {
|
|
214
|
+
ctx.addIssue({
|
|
215
|
+
code: "custom",
|
|
216
|
+
message: `Provider ${field} is invalid`
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}).transform((value) => value.trim());
|
|
220
|
+
}
|
|
221
|
+
function isoTimestamp(field) {
|
|
222
|
+
return boundedString(field, 64).superRefine((value, ctx) => {
|
|
223
|
+
if (!Number.isFinite(Date.parse(value))) {
|
|
224
|
+
ctx.addIssue({
|
|
225
|
+
code: "custom",
|
|
226
|
+
message: `Provider ${field} must be an ISO timestamp`
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
var providerBaseUrl = z.string().superRefine((value, ctx) => {
|
|
232
|
+
try {
|
|
233
|
+
normalizeProviderUrl(value);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
ctx.addIssue({
|
|
236
|
+
code: "custom",
|
|
237
|
+
message: error instanceof Error ? error.message : "Provider base URL is invalid",
|
|
238
|
+
params: {
|
|
239
|
+
byokCode: error instanceof ByokKeysError ? error.code : "PROVIDER_URL_INVALID"
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}).transform((value) => normalizeProviderUrl(value));
|
|
244
|
+
var ModelProviderProfileSchema = z.object({
|
|
245
|
+
adapter: z.enum(MODEL_PROVIDER_ADAPTERS),
|
|
246
|
+
auth_mode: z.enum(PROVIDER_AUTH_MODES),
|
|
247
|
+
base_url: providerBaseUrl,
|
|
248
|
+
capabilities: z.array(ProviderModelCapabilitySchema).max(8),
|
|
249
|
+
created_at: isoTimestamp("created_at"),
|
|
250
|
+
display_name: boundedString("display_name", 100),
|
|
251
|
+
enabled: z.boolean(),
|
|
252
|
+
kind: z.literal("model"),
|
|
253
|
+
model: boundedString("model", 160),
|
|
254
|
+
profile_ref: ProviderProfileRefSchema,
|
|
255
|
+
provider_kind: z.enum(MODEL_PROVIDER_KINDS),
|
|
256
|
+
updated_at: isoTimestamp("updated_at")
|
|
257
|
+
}).superRefine((profile, ctx) => {
|
|
258
|
+
if (new Set(profile.capabilities).size !== profile.capabilities.length) {
|
|
259
|
+
ctx.addIssue({
|
|
260
|
+
code: "custom",
|
|
261
|
+
message: "Provider capabilities cannot repeat",
|
|
262
|
+
path: ["capabilities"]
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (profile.adapter === "anthropic" && profile.auth_mode !== "x_api_key") {
|
|
266
|
+
ctx.addIssue({
|
|
267
|
+
code: "custom",
|
|
268
|
+
message: "Anthropic requires x_api_key authentication",
|
|
269
|
+
path: ["auth_mode"]
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (profile.adapter === "openai_compatible" && profile.auth_mode === "x_api_key") {
|
|
273
|
+
ctx.addIssue({
|
|
274
|
+
code: "custom",
|
|
275
|
+
message: "OpenAI-compatible providers support bearer or no authentication",
|
|
276
|
+
path: ["auth_mode"]
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const vendor = modelProviderVendor(profile.provider_kind);
|
|
280
|
+
if (vendor !== void 0 && vendor.adapter !== profile.adapter) {
|
|
281
|
+
ctx.addIssue({
|
|
282
|
+
code: "custom",
|
|
283
|
+
message: `Provider kind ${profile.provider_kind} speaks the ${vendor.adapter} adapter`,
|
|
284
|
+
path: ["adapter"]
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (Date.parse(profile.updated_at) < Date.parse(profile.created_at)) {
|
|
288
|
+
ctx.addIssue({
|
|
289
|
+
code: "custom",
|
|
290
|
+
message: "Provider updated_at cannot precede created_at",
|
|
291
|
+
path: ["updated_at"]
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
function exactProviderProfileBinding(profileInput, requiredCapabilities = profileInput.capabilities) {
|
|
296
|
+
const profile = parseModelProviderProfile(profileInput);
|
|
297
|
+
const revision = Date.parse(profile.updated_at);
|
|
298
|
+
if (!Number.isSafeInteger(revision) || revision < 0) {
|
|
299
|
+
throw new ByokKeysError(
|
|
300
|
+
"PROVIDER_PROFILE_INVALID",
|
|
301
|
+
"Provider updated_at cannot be represented as a canonical revision"
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const normalizedCapabilities = [...profile.capabilities].sort();
|
|
305
|
+
const canonical = JSON.stringify({
|
|
306
|
+
adapter: profile.adapter,
|
|
307
|
+
auth_mode: profile.auth_mode,
|
|
308
|
+
base_url: profile.base_url,
|
|
309
|
+
capabilities: normalizedCapabilities,
|
|
310
|
+
kind: profile.kind,
|
|
311
|
+
model: profile.model,
|
|
312
|
+
profile_ref: profile.profile_ref,
|
|
313
|
+
provider_kind: profile.provider_kind
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
profileRef: profile.profile_ref,
|
|
317
|
+
profileRevision: String(revision),
|
|
318
|
+
profileHash: `sha256:${createHash("sha256").update(canonical).digest("hex")}`,
|
|
319
|
+
modelId: profile.model,
|
|
320
|
+
requiredCapabilities: [...requiredCapabilities]
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function assertExactProviderProfileBinding(profile, expected) {
|
|
324
|
+
if (new Set(expected.requiredCapabilities).size !== expected.requiredCapabilities.length) {
|
|
325
|
+
throw new Error("provider profile required capabilities must be unique");
|
|
326
|
+
}
|
|
327
|
+
const actual = exactProviderProfileBinding(profile, expected.requiredCapabilities);
|
|
328
|
+
if (actual.profileRef !== expected.profileRef) throw new Error("provider profile ref mismatch");
|
|
329
|
+
if (actual.profileRevision !== expected.profileRevision) throw new Error("provider profile revision mismatch");
|
|
330
|
+
if (actual.profileHash !== expected.profileHash) throw new Error("provider profile hash mismatch");
|
|
331
|
+
if (actual.modelId !== expected.modelId) throw new Error("provider profile model mismatch");
|
|
332
|
+
const supported = new Set(profile.capabilities);
|
|
333
|
+
const unsupported = expected.requiredCapabilities.find((capability) => !supported.has(capability));
|
|
334
|
+
if (unsupported !== void 0) throw new Error(`provider profile does not support required capability ${unsupported}`);
|
|
335
|
+
}
|
|
336
|
+
function parseModelProviderProfile(value) {
|
|
337
|
+
const result = ModelProviderProfileSchema.safeParse(value);
|
|
338
|
+
if (result.success) return result.data;
|
|
339
|
+
const issue = result.error.issues[0];
|
|
340
|
+
const params = issue?.params;
|
|
341
|
+
throw new ByokKeysError(
|
|
342
|
+
params?.byokCode ?? "PROVIDER_PROFILE_INVALID",
|
|
343
|
+
issue?.message ?? "Provider profile is invalid",
|
|
344
|
+
{ cause: result.error }
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
74
348
|
// src/secret-store.ts
|
|
75
349
|
var DEFAULT_SECRET_SERVICE_PREFIX = "com.byok.keys";
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
350
|
+
function modelProviderSecretName(profileRef) {
|
|
351
|
+
const parsed = ProviderProfileRefSchema.safeParse(profileRef);
|
|
352
|
+
if (!parsed.success) {
|
|
353
|
+
throw new ByokKeysError(
|
|
354
|
+
"PROVIDER_PROFILE_INVALID",
|
|
355
|
+
"Provider profile ref must be a lowercase portable identifier",
|
|
356
|
+
{ cause: parsed.error }
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return assertSecretName(`model-${parsed.data}-api-key`);
|
|
84
360
|
}
|
|
85
361
|
function decodeStrictBase64Utf8(encoded) {
|
|
86
362
|
if (encoded.length % 4 !== 0) return void 0;
|
|
@@ -266,11 +542,11 @@ function assertKeychainPath(keychainPath) {
|
|
|
266
542
|
|
|
267
543
|
// src/pi-provider-projection.ts
|
|
268
544
|
var PI_PROJECTED_KEY_ENV = "PI_PROVIDER_API_KEY";
|
|
269
|
-
function piProjectionProviderId(
|
|
270
|
-
return `byok-sdk-${
|
|
545
|
+
function piProjectionProviderId(profileRef) {
|
|
546
|
+
return `byok-sdk-${profileRef}`;
|
|
271
547
|
}
|
|
272
548
|
function buildPiProviderProjection(profile) {
|
|
273
|
-
const projectedProviderId = piProjectionProviderId(profile.
|
|
549
|
+
const projectedProviderId = piProjectionProviderId(profile.profile_ref);
|
|
274
550
|
return {
|
|
275
551
|
providers: {
|
|
276
552
|
[projectedProviderId]: {
|
|
@@ -281,7 +557,11 @@ function buildPiProviderProjection(profile) {
|
|
|
281
557
|
models: [
|
|
282
558
|
{
|
|
283
559
|
id: profile.model,
|
|
284
|
-
name: profile.display_name
|
|
560
|
+
name: profile.display_name,
|
|
561
|
+
input: [
|
|
562
|
+
"text",
|
|
563
|
+
...profile.capabilities.includes("image-input") ? ["image"] : []
|
|
564
|
+
]
|
|
285
565
|
}
|
|
286
566
|
]
|
|
287
567
|
}
|
|
@@ -314,139 +594,12 @@ function buildPiProviderArgs(profile, delegatedArgs) {
|
|
|
314
594
|
return [
|
|
315
595
|
...delegatedArgs,
|
|
316
596
|
"--provider",
|
|
317
|
-
piProjectionProviderId(profile.
|
|
597
|
+
piProjectionProviderId(profile.profile_ref),
|
|
318
598
|
"--model",
|
|
319
599
|
profile.model
|
|
320
600
|
];
|
|
321
601
|
}
|
|
322
602
|
|
|
323
|
-
// src/url.ts
|
|
324
|
-
function normalizeProviderUrl(value) {
|
|
325
|
-
let url;
|
|
326
|
-
try {
|
|
327
|
-
url = new URL(value);
|
|
328
|
-
} catch {
|
|
329
|
-
throw new ByokKeysError(
|
|
330
|
-
"PROVIDER_URL_INVALID",
|
|
331
|
-
"Provider base URL must be absolute"
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
if (url.username || url.password || url.hash || url.search || url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) {
|
|
335
|
-
throw new ByokKeysError(
|
|
336
|
-
"PROVIDER_URL_INVALID",
|
|
337
|
-
"Provider URL requires HTTPS; HTTP is allowed only for localhost"
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
if (isPrivateNetworkLiteral(url.hostname) && !isLoopbackHost(url.hostname)) {
|
|
341
|
-
throw new ByokKeysError(
|
|
342
|
-
"PROVIDER_URL_INVALID",
|
|
343
|
-
"Private-network provider IPs are not allowed"
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
return url.toString().replace(/\/$/u, "");
|
|
347
|
-
}
|
|
348
|
-
function isLoopbackHost(hostname) {
|
|
349
|
-
const value = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
350
|
-
return value === "localhost" || value === "127.0.0.1" || value === "::1";
|
|
351
|
-
}
|
|
352
|
-
function isPrivateNetworkLiteral(hostname) {
|
|
353
|
-
const value = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
354
|
-
if (value.includes(":")) return true;
|
|
355
|
-
const parts = value.split(".").map(Number);
|
|
356
|
-
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
return parts[0] === 10 || parts[0] === 127 || parts[0] === 169 && parts[1] === 254 || parts[0] === 172 && (parts[1] ?? 0) >= 16 && (parts[1] ?? 0) <= 31 || parts[0] === 192 && parts[1] === 168;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// src/provider-profile.ts
|
|
363
|
-
var MODEL_PROVIDER_IDS = [
|
|
364
|
-
"openai",
|
|
365
|
-
"deepseek",
|
|
366
|
-
"anthropic",
|
|
367
|
-
"custom"
|
|
368
|
-
];
|
|
369
|
-
var PROVIDER_AUTH_MODES = ["bearer", "x_api_key", "none"];
|
|
370
|
-
var MODEL_PROVIDER_ADAPTERS = ["openai_compatible", "anthropic"];
|
|
371
|
-
function boundedString(field, maximumLength) {
|
|
372
|
-
return z.string().superRefine((value, ctx) => {
|
|
373
|
-
if (value.trim().length === 0 || value.length > maximumLength || /[\u0000\r\n]/u.test(value)) {
|
|
374
|
-
ctx.addIssue({
|
|
375
|
-
code: "custom",
|
|
376
|
-
message: `Provider ${field} is invalid`
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
}).transform((value) => value.trim());
|
|
380
|
-
}
|
|
381
|
-
function isoTimestamp(field) {
|
|
382
|
-
return boundedString(field, 64).superRefine((value, ctx) => {
|
|
383
|
-
if (!Number.isFinite(Date.parse(value))) {
|
|
384
|
-
ctx.addIssue({
|
|
385
|
-
code: "custom",
|
|
386
|
-
message: `Provider ${field} must be an ISO timestamp`
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
var providerBaseUrl = z.string().superRefine((value, ctx) => {
|
|
392
|
-
try {
|
|
393
|
-
normalizeProviderUrl(value);
|
|
394
|
-
} catch (error) {
|
|
395
|
-
ctx.addIssue({
|
|
396
|
-
code: "custom",
|
|
397
|
-
message: error instanceof Error ? error.message : "Provider base URL is invalid",
|
|
398
|
-
params: {
|
|
399
|
-
byokCode: error instanceof ByokKeysError ? error.code : "PROVIDER_URL_INVALID"
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
}).transform((value) => normalizeProviderUrl(value));
|
|
404
|
-
var ModelProviderProfileSchema = z.object({
|
|
405
|
-
adapter: z.enum(MODEL_PROVIDER_ADAPTERS),
|
|
406
|
-
auth_mode: z.enum(PROVIDER_AUTH_MODES),
|
|
407
|
-
base_url: providerBaseUrl,
|
|
408
|
-
created_at: isoTimestamp("created_at"),
|
|
409
|
-
display_name: boundedString("display_name", 100),
|
|
410
|
-
enabled: z.boolean(),
|
|
411
|
-
kind: z.literal("model"),
|
|
412
|
-
model: boundedString("model", 160),
|
|
413
|
-
provider_id: z.enum(MODEL_PROVIDER_IDS),
|
|
414
|
-
updated_at: isoTimestamp("updated_at")
|
|
415
|
-
}).superRefine((profile, ctx) => {
|
|
416
|
-
if (profile.adapter === "anthropic" && profile.auth_mode !== "x_api_key") {
|
|
417
|
-
ctx.addIssue({
|
|
418
|
-
code: "custom",
|
|
419
|
-
message: "Anthropic requires x_api_key authentication",
|
|
420
|
-
path: ["auth_mode"]
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
if (profile.adapter === "openai_compatible" && profile.auth_mode === "x_api_key") {
|
|
424
|
-
ctx.addIssue({
|
|
425
|
-
code: "custom",
|
|
426
|
-
message: "OpenAI-compatible providers support bearer or no authentication",
|
|
427
|
-
path: ["auth_mode"]
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
if (Date.parse(profile.updated_at) < Date.parse(profile.created_at)) {
|
|
431
|
-
ctx.addIssue({
|
|
432
|
-
code: "custom",
|
|
433
|
-
message: "Provider updated_at cannot precede created_at",
|
|
434
|
-
path: ["updated_at"]
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
function parseModelProviderProfile(value) {
|
|
439
|
-
const result = ModelProviderProfileSchema.safeParse(value);
|
|
440
|
-
if (result.success) return result.data;
|
|
441
|
-
const issue = result.error.issues[0];
|
|
442
|
-
const params = issue?.params;
|
|
443
|
-
throw new ByokKeysError(
|
|
444
|
-
params?.byokCode ?? "PROVIDER_PROFILE_INVALID",
|
|
445
|
-
issue?.message ?? "Provider profile is invalid",
|
|
446
|
-
{ cause: result.error }
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
603
|
// src/pi-provider-launcher-core.ts
|
|
451
604
|
var PI_CHILD_BASE_ENV_NAMES = [
|
|
452
605
|
"PATH",
|
|
@@ -480,15 +633,17 @@ var PI_CHILD_WINDOWS_ENV_NAMES = [
|
|
|
480
633
|
];
|
|
481
634
|
function parsePiProviderLauncherOptions(args) {
|
|
482
635
|
const separator = args.indexOf("--");
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
const piArgs = args.slice(separator + 1);
|
|
486
|
-
if (piArgs.length === 0) throw new Error("launcher requires Pi arguments after --");
|
|
636
|
+
const ownArgs = separator < 0 ? args : args.slice(0, separator);
|
|
637
|
+
const piArgs = separator < 0 ? [] : args.slice(separator + 1);
|
|
487
638
|
const allowedFlags = /* @__PURE__ */ new Set([
|
|
488
639
|
"--pi-bin",
|
|
489
640
|
"--profile-db",
|
|
490
641
|
"--provider",
|
|
491
642
|
"--model",
|
|
643
|
+
"--profile-revision",
|
|
644
|
+
"--profile-hash",
|
|
645
|
+
"--required-capabilities",
|
|
646
|
+
"--validate-only",
|
|
492
647
|
"--session-dir",
|
|
493
648
|
"--secret-service-prefix",
|
|
494
649
|
"--macos-keychain-path"
|
|
@@ -512,9 +667,10 @@ function parsePiProviderLauncherOptions(args) {
|
|
|
512
667
|
if (value === void 0) throw new Error(`${flag} requires a value`);
|
|
513
668
|
return value;
|
|
514
669
|
};
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
670
|
+
const rawProfileRef = required("--provider");
|
|
671
|
+
const profileRef = ProviderProfileRefSchema.safeParse(rawProfileRef);
|
|
672
|
+
if (!profileRef.success) {
|
|
673
|
+
throw new Error(`provider profile ref ${rawProfileRef} is not a valid @byok-sdk/keys identifier`);
|
|
518
674
|
}
|
|
519
675
|
const modelId = required("--model");
|
|
520
676
|
if (modelId.length > 160) throw new Error("--model exceeds 160 characters");
|
|
@@ -528,11 +684,54 @@ function parsePiProviderLauncherOptions(args) {
|
|
|
528
684
|
if (macosKeychainPath !== void 0 && !path2.posix.isAbsolute(macosKeychainPath)) {
|
|
529
685
|
throw new Error("--macos-keychain-path must be an absolute path");
|
|
530
686
|
}
|
|
687
|
+
const validateOnly = values.get("--validate-only") === "true";
|
|
688
|
+
if (values.has("--validate-only") && !["true", "false"].includes(values.get("--validate-only"))) {
|
|
689
|
+
throw new Error("--validate-only must be true or false");
|
|
690
|
+
}
|
|
691
|
+
if (!validateOnly && piArgs.length === 0) {
|
|
692
|
+
throw new Error("launcher requires Pi arguments after --");
|
|
693
|
+
}
|
|
694
|
+
const exactValues = [
|
|
695
|
+
values.get("--profile-revision"),
|
|
696
|
+
values.get("--profile-hash"),
|
|
697
|
+
values.get("--required-capabilities")
|
|
698
|
+
];
|
|
699
|
+
if (exactValues.some((value) => value !== void 0) && exactValues.some((value) => value === void 0)) {
|
|
700
|
+
throw new Error("exact provider binding requires revision, hash, and required capabilities together");
|
|
701
|
+
}
|
|
702
|
+
let expectedBinding;
|
|
703
|
+
if (exactValues[0] !== void 0) {
|
|
704
|
+
if (!/^(?:0|[1-9][0-9]{0,19})$/u.test(exactValues[0])) {
|
|
705
|
+
throw new Error("--profile-revision must be canonical decimal");
|
|
706
|
+
}
|
|
707
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(exactValues[1])) {
|
|
708
|
+
throw new Error("--profile-hash must be lowercase sha256");
|
|
709
|
+
}
|
|
710
|
+
let capabilities;
|
|
711
|
+
try {
|
|
712
|
+
capabilities = JSON.parse(exactValues[2]);
|
|
713
|
+
} catch {
|
|
714
|
+
throw new Error("--required-capabilities must be a JSON array");
|
|
715
|
+
}
|
|
716
|
+
const parsedCapabilities = ProviderModelCapabilitySchema.array().max(8).safeParse(capabilities);
|
|
717
|
+
if (!parsedCapabilities.success || new Set(parsedCapabilities.data).size !== parsedCapabilities.data.length) {
|
|
718
|
+
throw new Error("--required-capabilities must contain unique supported capabilities");
|
|
719
|
+
}
|
|
720
|
+
expectedBinding = {
|
|
721
|
+
profileRef: profileRef.data,
|
|
722
|
+
profileRevision: exactValues[0],
|
|
723
|
+
profileHash: exactValues[1],
|
|
724
|
+
modelId,
|
|
725
|
+
requiredCapabilities: parsedCapabilities.data
|
|
726
|
+
};
|
|
727
|
+
}
|
|
531
728
|
return {
|
|
532
729
|
piBin: required("--pi-bin"),
|
|
533
730
|
profileDbPath,
|
|
534
|
-
|
|
731
|
+
profileRef: profileRef.data,
|
|
535
732
|
modelId,
|
|
733
|
+
...expectedBinding === void 0 ? {} : { expectedBinding },
|
|
734
|
+
validateOnly,
|
|
536
735
|
sessionDir,
|
|
537
736
|
...secretServicePrefix ? { secretServicePrefix } : {},
|
|
538
737
|
...macosKeychainPath !== void 0 ? { macosKeychainPath } : {},
|
|
@@ -548,11 +747,11 @@ async function resolvePiProviderSecret(profile, createStore) {
|
|
|
548
747
|
`${secrets.providerLabel} is unavailable`
|
|
549
748
|
);
|
|
550
749
|
}
|
|
551
|
-
const secret = await secrets.get(modelProviderSecretName(profile.
|
|
750
|
+
const secret = await secrets.get(modelProviderSecretName(profile.profile_ref));
|
|
552
751
|
if (!secret) {
|
|
553
752
|
throw new ByokKeysError(
|
|
554
753
|
"PROVIDER_SECRET_MISSING",
|
|
555
|
-
`${profile.
|
|
754
|
+
`${profile.profile_ref} provider profile requires a secret in ${secrets.providerLabel}`
|
|
556
755
|
);
|
|
557
756
|
}
|
|
558
757
|
return secret;
|
|
@@ -596,10 +795,10 @@ async function ensurePiSessionDirectory(sessionDir) {
|
|
|
596
795
|
}
|
|
597
796
|
|
|
598
797
|
// src/profile-store.ts
|
|
599
|
-
function providerNotConfigured(
|
|
798
|
+
function providerNotConfigured(profileRef) {
|
|
600
799
|
return new ByokKeysError(
|
|
601
800
|
"PROVIDER_NOT_CONFIGURED",
|
|
602
|
-
`${
|
|
801
|
+
`${profileRef} model provider is not configured`
|
|
603
802
|
);
|
|
604
803
|
}
|
|
605
804
|
function closeSqliteDatabaseAfterInitializationFailure(database, initializationError, message, close = (handle) => handle.close()) {
|
|
@@ -666,20 +865,40 @@ function secureSqliteFilePermissions(databasePath) {
|
|
|
666
865
|
}
|
|
667
866
|
|
|
668
867
|
// src/sqlite-profile-store.ts
|
|
868
|
+
function sqlList(values) {
|
|
869
|
+
return values.map((value) => `'${value}'`).join(", ");
|
|
870
|
+
}
|
|
669
871
|
var SCHEMA = `
|
|
670
872
|
CREATE TABLE IF NOT EXISTS provider_profile (
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
873
|
+
profile_ref TEXT PRIMARY KEY,
|
|
874
|
+
provider_kind TEXT NOT NULL CHECK (provider_kind IN (${sqlList(MODEL_PROVIDER_KINDS)})),
|
|
875
|
+
kind TEXT NOT NULL CHECK (kind = 'model'),
|
|
876
|
+
adapter TEXT NOT NULL CHECK (adapter IN (${sqlList(MODEL_PROVIDER_ADAPTERS)})),
|
|
877
|
+
display_name TEXT NOT NULL,
|
|
878
|
+
base_url TEXT NOT NULL,
|
|
879
|
+
auth_mode TEXT NOT NULL CHECK (auth_mode IN (${sqlList(PROVIDER_AUTH_MODES)})),
|
|
880
|
+
model TEXT NOT NULL,
|
|
881
|
+
capabilities TEXT NOT NULL,
|
|
882
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
883
|
+
created_at TEXT NOT NULL,
|
|
884
|
+
updated_at TEXT NOT NULL
|
|
681
885
|
);
|
|
682
886
|
`;
|
|
887
|
+
function normalizeTableDdl(sql) {
|
|
888
|
+
return sql.replace(/\bIF\s+NOT\s+EXISTS\b/giu, "").replace(/\s+/gu, " ").trim().replace(/;$/u, "").trim();
|
|
889
|
+
}
|
|
890
|
+
function assertProviderProfileSchemaIsCurrent(database, path4) {
|
|
891
|
+
const row = database.prepare(
|
|
892
|
+
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'provider_profile'"
|
|
893
|
+
).get();
|
|
894
|
+
const stored = row?.sql;
|
|
895
|
+
if (typeof stored !== "string") return;
|
|
896
|
+
if (normalizeTableDdl(stored) === normalizeTableDdl(SCHEMA)) return;
|
|
897
|
+
throw new ByokKeysError(
|
|
898
|
+
"PROVIDER_STORE_SCHEMA_STALE",
|
|
899
|
+
`Provider profile store at ${path4} was created by a different @byok-sdk/keys schema; recreate the store file to continue`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
683
902
|
var ENABLED_INDEX = `
|
|
684
903
|
CREATE UNIQUE INDEX IF NOT EXISTS provider_profile_one_enabled
|
|
685
904
|
ON provider_profile(kind)
|
|
@@ -696,6 +915,7 @@ var SqliteProviderProfileStore = class {
|
|
|
696
915
|
try {
|
|
697
916
|
this.#database.exec(SCHEMA);
|
|
698
917
|
this.#database.exec(ENABLED_INDEX);
|
|
918
|
+
assertProviderProfileSchemaIsCurrent(this.#database, options.path);
|
|
699
919
|
secureSqliteFilePermissions(options.path);
|
|
700
920
|
} catch (error) {
|
|
701
921
|
closeSqliteDatabaseAfterInitializationFailure(
|
|
@@ -704,6 +924,16 @@ var SqliteProviderProfileStore = class {
|
|
|
704
924
|
"SqliteProviderProfileStore initialization failed and its native handle could not be closed"
|
|
705
925
|
);
|
|
706
926
|
}
|
|
927
|
+
} else {
|
|
928
|
+
try {
|
|
929
|
+
assertProviderProfileSchemaIsCurrent(this.#database, options.path);
|
|
930
|
+
} catch (error) {
|
|
931
|
+
closeSqliteDatabaseAfterInitializationFailure(
|
|
932
|
+
this.#database,
|
|
933
|
+
error,
|
|
934
|
+
"SqliteProviderProfileStore read-only schema check failed and its native handle could not be closed"
|
|
935
|
+
);
|
|
936
|
+
}
|
|
707
937
|
}
|
|
708
938
|
}
|
|
709
939
|
/**
|
|
@@ -717,12 +947,12 @@ var SqliteProviderProfileStore = class {
|
|
|
717
947
|
this.#closed = true;
|
|
718
948
|
this.#database.close();
|
|
719
949
|
}
|
|
720
|
-
async delete(
|
|
721
|
-
const result = this.#database.prepare("DELETE FROM provider_profile WHERE
|
|
950
|
+
async delete(profileRef) {
|
|
951
|
+
const result = this.#database.prepare("DELETE FROM provider_profile WHERE profile_ref = ?").run(profileRef);
|
|
722
952
|
return Number(result.changes) === 1;
|
|
723
953
|
}
|
|
724
|
-
async get(
|
|
725
|
-
const row = this.#database.prepare("SELECT * FROM provider_profile WHERE
|
|
954
|
+
async get(profileRef) {
|
|
955
|
+
const row = this.#database.prepare("SELECT * FROM provider_profile WHERE profile_ref = ?").get(profileRef);
|
|
726
956
|
return row === void 0 ? void 0 : parseRow(row);
|
|
727
957
|
}
|
|
728
958
|
async getEnabled() {
|
|
@@ -730,11 +960,11 @@ var SqliteProviderProfileStore = class {
|
|
|
730
960
|
return row === void 0 ? void 0 : parseRow(row);
|
|
731
961
|
}
|
|
732
962
|
async list() {
|
|
733
|
-
const rows = this.#database.prepare("SELECT * FROM provider_profile ORDER BY
|
|
963
|
+
const rows = this.#database.prepare("SELECT * FROM provider_profile ORDER BY profile_ref ASC").all();
|
|
734
964
|
return rows.map(parseRow);
|
|
735
965
|
}
|
|
736
966
|
async save(profile) {
|
|
737
|
-
const existing = await this.get(profile.
|
|
967
|
+
const existing = await this.get(profile.profile_ref);
|
|
738
968
|
const validated = parseModelProviderProfile({
|
|
739
969
|
...profile,
|
|
740
970
|
created_at: existing?.created_at ?? profile.created_at
|
|
@@ -742,40 +972,44 @@ var SqliteProviderProfileStore = class {
|
|
|
742
972
|
this.#transaction(() => {
|
|
743
973
|
if (validated.enabled) {
|
|
744
974
|
this.#database.prepare(
|
|
745
|
-
"UPDATE provider_profile SET enabled = 0 WHERE
|
|
746
|
-
).run(validated.
|
|
975
|
+
"UPDATE provider_profile SET enabled = 0 WHERE profile_ref <> ?"
|
|
976
|
+
).run(validated.profile_ref);
|
|
747
977
|
}
|
|
748
978
|
this.#database.prepare(
|
|
749
979
|
`INSERT INTO provider_profile (
|
|
750
|
-
|
|
751
|
-
auth_mode, model, enabled, created_at, updated_at
|
|
752
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
753
|
-
ON CONFLICT(
|
|
980
|
+
profile_ref, provider_kind, kind, adapter, display_name, base_url,
|
|
981
|
+
auth_mode, model, capabilities, enabled, created_at, updated_at
|
|
982
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
983
|
+
ON CONFLICT(profile_ref) DO UPDATE SET
|
|
984
|
+
provider_kind = excluded.provider_kind,
|
|
754
985
|
adapter = excluded.adapter,
|
|
755
986
|
display_name = excluded.display_name,
|
|
756
987
|
base_url = excluded.base_url,
|
|
757
988
|
auth_mode = excluded.auth_mode,
|
|
758
989
|
model = excluded.model,
|
|
990
|
+
capabilities = excluded.capabilities,
|
|
759
991
|
enabled = excluded.enabled,
|
|
760
992
|
updated_at = excluded.updated_at`
|
|
761
993
|
).run(
|
|
762
|
-
validated.
|
|
994
|
+
validated.profile_ref,
|
|
995
|
+
validated.provider_kind,
|
|
763
996
|
validated.kind,
|
|
764
997
|
validated.adapter,
|
|
765
998
|
validated.display_name,
|
|
766
999
|
validated.base_url,
|
|
767
1000
|
validated.auth_mode,
|
|
768
1001
|
validated.model,
|
|
1002
|
+
JSON.stringify(validated.capabilities),
|
|
769
1003
|
validated.enabled ? 1 : 0,
|
|
770
1004
|
validated.created_at,
|
|
771
1005
|
validated.updated_at
|
|
772
1006
|
);
|
|
773
1007
|
});
|
|
774
|
-
return await this.get(validated.
|
|
1008
|
+
return await this.get(validated.profile_ref);
|
|
775
1009
|
}
|
|
776
|
-
async setEnabled(
|
|
777
|
-
const existing = await this.get(
|
|
778
|
-
if (existing === void 0) throw providerNotConfigured(
|
|
1010
|
+
async setEnabled(profileRef) {
|
|
1011
|
+
const existing = await this.get(profileRef);
|
|
1012
|
+
if (existing === void 0) throw providerNotConfigured(profileRef);
|
|
779
1013
|
return this.save({ ...existing, enabled: true });
|
|
780
1014
|
}
|
|
781
1015
|
/** `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK`, per `providers.ts:1252-1263`. */
|
|
@@ -791,8 +1025,19 @@ var SqliteProviderProfileStore = class {
|
|
|
791
1025
|
}
|
|
792
1026
|
};
|
|
793
1027
|
function parseRow(row) {
|
|
1028
|
+
let capabilities;
|
|
1029
|
+
try {
|
|
1030
|
+
capabilities = JSON.parse(row.capabilities);
|
|
1031
|
+
} catch (cause) {
|
|
1032
|
+
throw new ByokKeysError(
|
|
1033
|
+
"PROVIDER_PROFILE_INVALID",
|
|
1034
|
+
"Provider profile capabilities column is not valid JSON",
|
|
1035
|
+
{ cause }
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
794
1038
|
return parseModelProviderProfile({
|
|
795
1039
|
...row,
|
|
1040
|
+
capabilities,
|
|
796
1041
|
enabled: row.enabled === 1
|
|
797
1042
|
});
|
|
798
1043
|
}
|
|
@@ -1100,15 +1345,19 @@ async function run(options) {
|
|
|
1100
1345
|
});
|
|
1101
1346
|
let projectionDir;
|
|
1102
1347
|
try {
|
|
1103
|
-
const profile = await profiles.get(options.
|
|
1348
|
+
const profile = await profiles.get(options.profileRef);
|
|
1104
1349
|
if (profile === void 0) {
|
|
1105
|
-
throw new Error(`provider ${options.
|
|
1350
|
+
throw new Error(`provider profile ${options.profileRef} is not configured`);
|
|
1106
1351
|
}
|
|
1107
1352
|
if (profile.model !== options.modelId) {
|
|
1108
1353
|
throw new Error(
|
|
1109
1354
|
`selected model ${options.modelId} does not match configured provider model ${profile.model}`
|
|
1110
1355
|
);
|
|
1111
1356
|
}
|
|
1357
|
+
if (options.expectedBinding !== void 0) {
|
|
1358
|
+
assertExactProviderProfileBinding(profile, options.expectedBinding);
|
|
1359
|
+
}
|
|
1360
|
+
if (options.validateOnly) return 0;
|
|
1112
1361
|
const secret = await resolvePiProviderSecret(
|
|
1113
1362
|
profile,
|
|
1114
1363
|
() => createSecretStore(
|