@byok-sdk/keys 0.3.9 → 0.3.10
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 +312 -181
- package/dist/bin/pi-provider-launcher.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +188 -91
- 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-profile.d.ts +58 -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
package/README.md
CHANGED
|
@@ -125,6 +125,18 @@ authority selection, not a mirror, migration shim, cache, or dual-write path.
|
|
|
125
125
|
It stores the complete four-ID provider registry as one versioned deterministic
|
|
126
126
|
snapshot so delete and the one-enabled invariant share one CAS decision.
|
|
127
127
|
|
|
128
|
+
Each normalized profile also has an exact credential-free binding. The
|
|
129
|
+
registry exposes `profile_revision` (a strictly advancing decimal derived from
|
|
130
|
+
`updated_at`) and `profile_hash` (SHA-256 over the normalized runtime-relevant,
|
|
131
|
+
non-secret record). `exactProviderProfileBinding()` and
|
|
132
|
+
`assertExactProviderProfileBinding()` are the local authority used by Agent
|
|
133
|
+
admission. Base URLs and credential bytes never enter the wire binding.
|
|
134
|
+
|
|
135
|
+
For `byok-profile` launches, the Pi launcher receives the exact ref,
|
|
136
|
+
revision/hash, model, and required capabilities. A validation-only invocation
|
|
137
|
+
checks the read-only SQLite authority before task claim; the launch invocation
|
|
138
|
+
checks the same fields again before reading the OS credential or spawning Pi.
|
|
139
|
+
|
|
128
140
|
## Module inventory
|
|
129
141
|
|
|
130
142
|
Every module under `src/`, one line of responsibility each. The public surface is
|
|
@@ -134,7 +146,7 @@ whatever `index.ts` re-exports; nothing here is reachable by deep import.
|
|
|
134
146
|
| --- | --- |
|
|
135
147
|
| `index.ts` | The package barrel — the single public entry point, and the only supported import path |
|
|
136
148
|
| `errors.ts` | `ByokKeysError` (`code` + message) and `BYOK_KEYS_ERROR_CODES`, the code strings consumers branch on |
|
|
137
|
-
| `provider-profile.ts` | zod schema
|
|
149
|
+
| `provider-profile.ts` | zod schema plus exact credential-free revision/hash binding for the model provider profile |
|
|
138
150
|
| `headers.ts` | `providerHeaders()` and fail-closed `requiredProviderSecret()` |
|
|
139
151
|
| `url.ts` | `normalizeProviderUrl()` with the HTTPS / loopback / private-network guard |
|
|
140
152
|
| `http.ts` | Shared transport guards: injectable `fetch`, timeout, bounded JSON, HTTP error classification |
|
|
@@ -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,202 @@ function assertSecretNamespace(value) {
|
|
|
71
72
|
return normalized;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// src/url.ts
|
|
76
|
+
function normalizeProviderUrl(value) {
|
|
77
|
+
let url;
|
|
78
|
+
try {
|
|
79
|
+
url = new URL(value);
|
|
80
|
+
} catch {
|
|
81
|
+
throw new ByokKeysError(
|
|
82
|
+
"PROVIDER_URL_INVALID",
|
|
83
|
+
"Provider base URL must be absolute"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (url.username || url.password || url.hash || url.search || url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) {
|
|
87
|
+
throw new ByokKeysError(
|
|
88
|
+
"PROVIDER_URL_INVALID",
|
|
89
|
+
"Provider URL requires HTTPS; HTTP is allowed only for localhost"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (isPrivateNetworkLiteral(url.hostname) && !isLoopbackHost(url.hostname)) {
|
|
93
|
+
throw new ByokKeysError(
|
|
94
|
+
"PROVIDER_URL_INVALID",
|
|
95
|
+
"Private-network provider IPs are not allowed"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return url.toString().replace(/\/$/u, "");
|
|
99
|
+
}
|
|
100
|
+
function isLoopbackHost(hostname) {
|
|
101
|
+
const value = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
102
|
+
return value === "localhost" || value === "127.0.0.1" || value === "::1";
|
|
103
|
+
}
|
|
104
|
+
function isPrivateNetworkLiteral(hostname) {
|
|
105
|
+
const value = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
106
|
+
if (value.includes(":")) return true;
|
|
107
|
+
const parts = value.split(".").map(Number);
|
|
108
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
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;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/provider-profile.ts
|
|
115
|
+
var PROVIDER_PROFILE_REF_PATTERN = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/u;
|
|
116
|
+
var ProviderProfileRefSchema = z.string().min(1).max(64).regex(
|
|
117
|
+
PROVIDER_PROFILE_REF_PATTERN,
|
|
118
|
+
"provider profile refs must be lowercase portable identifiers"
|
|
119
|
+
);
|
|
120
|
+
var MODEL_PROVIDER_KINDS = [
|
|
121
|
+
"openai",
|
|
122
|
+
"deepseek",
|
|
123
|
+
"anthropic",
|
|
124
|
+
"custom"
|
|
125
|
+
];
|
|
126
|
+
var PROVIDER_MODEL_CAPABILITIES = ["image-input"];
|
|
127
|
+
var ProviderModelCapabilitySchema = z.enum(PROVIDER_MODEL_CAPABILITIES);
|
|
128
|
+
var PROVIDER_AUTH_MODES = ["bearer", "x_api_key", "none"];
|
|
129
|
+
var MODEL_PROVIDER_ADAPTERS = ["openai_compatible", "anthropic"];
|
|
130
|
+
function boundedString(field, maximumLength) {
|
|
131
|
+
return z.string().superRefine((value, ctx) => {
|
|
132
|
+
if (value.trim().length === 0 || value.length > maximumLength || /[\u0000\r\n]/u.test(value)) {
|
|
133
|
+
ctx.addIssue({
|
|
134
|
+
code: "custom",
|
|
135
|
+
message: `Provider ${field} is invalid`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}).transform((value) => value.trim());
|
|
139
|
+
}
|
|
140
|
+
function isoTimestamp(field) {
|
|
141
|
+
return boundedString(field, 64).superRefine((value, ctx) => {
|
|
142
|
+
if (!Number.isFinite(Date.parse(value))) {
|
|
143
|
+
ctx.addIssue({
|
|
144
|
+
code: "custom",
|
|
145
|
+
message: `Provider ${field} must be an ISO timestamp`
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
var providerBaseUrl = z.string().superRefine((value, ctx) => {
|
|
151
|
+
try {
|
|
152
|
+
normalizeProviderUrl(value);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
ctx.addIssue({
|
|
155
|
+
code: "custom",
|
|
156
|
+
message: error instanceof Error ? error.message : "Provider base URL is invalid",
|
|
157
|
+
params: {
|
|
158
|
+
byokCode: error instanceof ByokKeysError ? error.code : "PROVIDER_URL_INVALID"
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}).transform((value) => normalizeProviderUrl(value));
|
|
163
|
+
var ModelProviderProfileSchema = z.object({
|
|
164
|
+
adapter: z.enum(MODEL_PROVIDER_ADAPTERS),
|
|
165
|
+
auth_mode: z.enum(PROVIDER_AUTH_MODES),
|
|
166
|
+
base_url: providerBaseUrl,
|
|
167
|
+
capabilities: z.array(ProviderModelCapabilitySchema).max(8),
|
|
168
|
+
created_at: isoTimestamp("created_at"),
|
|
169
|
+
display_name: boundedString("display_name", 100),
|
|
170
|
+
enabled: z.boolean(),
|
|
171
|
+
kind: z.literal("model"),
|
|
172
|
+
model: boundedString("model", 160),
|
|
173
|
+
profile_ref: ProviderProfileRefSchema,
|
|
174
|
+
provider_kind: z.enum(MODEL_PROVIDER_KINDS),
|
|
175
|
+
updated_at: isoTimestamp("updated_at")
|
|
176
|
+
}).superRefine((profile, ctx) => {
|
|
177
|
+
if (new Set(profile.capabilities).size !== profile.capabilities.length) {
|
|
178
|
+
ctx.addIssue({
|
|
179
|
+
code: "custom",
|
|
180
|
+
message: "Provider capabilities cannot repeat",
|
|
181
|
+
path: ["capabilities"]
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (profile.adapter === "anthropic" && profile.auth_mode !== "x_api_key") {
|
|
185
|
+
ctx.addIssue({
|
|
186
|
+
code: "custom",
|
|
187
|
+
message: "Anthropic requires x_api_key authentication",
|
|
188
|
+
path: ["auth_mode"]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (profile.adapter === "openai_compatible" && profile.auth_mode === "x_api_key") {
|
|
192
|
+
ctx.addIssue({
|
|
193
|
+
code: "custom",
|
|
194
|
+
message: "OpenAI-compatible providers support bearer or no authentication",
|
|
195
|
+
path: ["auth_mode"]
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (Date.parse(profile.updated_at) < Date.parse(profile.created_at)) {
|
|
199
|
+
ctx.addIssue({
|
|
200
|
+
code: "custom",
|
|
201
|
+
message: "Provider updated_at cannot precede created_at",
|
|
202
|
+
path: ["updated_at"]
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
function exactProviderProfileBinding(profileInput, requiredCapabilities = profileInput.capabilities) {
|
|
207
|
+
const profile = parseModelProviderProfile(profileInput);
|
|
208
|
+
const revision = Date.parse(profile.updated_at);
|
|
209
|
+
if (!Number.isSafeInteger(revision) || revision < 0) {
|
|
210
|
+
throw new ByokKeysError(
|
|
211
|
+
"PROVIDER_PROFILE_INVALID",
|
|
212
|
+
"Provider updated_at cannot be represented as a canonical revision"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const normalizedCapabilities = [...profile.capabilities].sort();
|
|
216
|
+
const canonical = JSON.stringify({
|
|
217
|
+
adapter: profile.adapter,
|
|
218
|
+
auth_mode: profile.auth_mode,
|
|
219
|
+
base_url: profile.base_url,
|
|
220
|
+
capabilities: normalizedCapabilities,
|
|
221
|
+
kind: profile.kind,
|
|
222
|
+
model: profile.model,
|
|
223
|
+
profile_ref: profile.profile_ref,
|
|
224
|
+
provider_kind: profile.provider_kind
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
profileRef: profile.profile_ref,
|
|
228
|
+
profileRevision: String(revision),
|
|
229
|
+
profileHash: `sha256:${createHash("sha256").update(canonical).digest("hex")}`,
|
|
230
|
+
modelId: profile.model,
|
|
231
|
+
requiredCapabilities: [...requiredCapabilities]
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function assertExactProviderProfileBinding(profile, expected) {
|
|
235
|
+
if (new Set(expected.requiredCapabilities).size !== expected.requiredCapabilities.length) {
|
|
236
|
+
throw new Error("provider profile required capabilities must be unique");
|
|
237
|
+
}
|
|
238
|
+
const actual = exactProviderProfileBinding(profile, expected.requiredCapabilities);
|
|
239
|
+
if (actual.profileRef !== expected.profileRef) throw new Error("provider profile ref mismatch");
|
|
240
|
+
if (actual.profileRevision !== expected.profileRevision) throw new Error("provider profile revision mismatch");
|
|
241
|
+
if (actual.profileHash !== expected.profileHash) throw new Error("provider profile hash mismatch");
|
|
242
|
+
if (actual.modelId !== expected.modelId) throw new Error("provider profile model mismatch");
|
|
243
|
+
const supported = new Set(profile.capabilities);
|
|
244
|
+
const unsupported = expected.requiredCapabilities.find((capability) => !supported.has(capability));
|
|
245
|
+
if (unsupported !== void 0) throw new Error(`provider profile does not support required capability ${unsupported}`);
|
|
246
|
+
}
|
|
247
|
+
function parseModelProviderProfile(value) {
|
|
248
|
+
const result = ModelProviderProfileSchema.safeParse(value);
|
|
249
|
+
if (result.success) return result.data;
|
|
250
|
+
const issue = result.error.issues[0];
|
|
251
|
+
const params = issue?.params;
|
|
252
|
+
throw new ByokKeysError(
|
|
253
|
+
params?.byokCode ?? "PROVIDER_PROFILE_INVALID",
|
|
254
|
+
issue?.message ?? "Provider profile is invalid",
|
|
255
|
+
{ cause: result.error }
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
74
259
|
// src/secret-store.ts
|
|
75
260
|
var DEFAULT_SECRET_SERVICE_PREFIX = "com.byok.keys";
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
261
|
+
function modelProviderSecretName(profileRef) {
|
|
262
|
+
const parsed = ProviderProfileRefSchema.safeParse(profileRef);
|
|
263
|
+
if (!parsed.success) {
|
|
264
|
+
throw new ByokKeysError(
|
|
265
|
+
"PROVIDER_PROFILE_INVALID",
|
|
266
|
+
"Provider profile ref must be a lowercase portable identifier",
|
|
267
|
+
{ cause: parsed.error }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return assertSecretName(`model-${parsed.data}-api-key`);
|
|
84
271
|
}
|
|
85
272
|
function decodeStrictBase64Utf8(encoded) {
|
|
86
273
|
if (encoded.length % 4 !== 0) return void 0;
|
|
@@ -266,11 +453,11 @@ function assertKeychainPath(keychainPath) {
|
|
|
266
453
|
|
|
267
454
|
// src/pi-provider-projection.ts
|
|
268
455
|
var PI_PROJECTED_KEY_ENV = "PI_PROVIDER_API_KEY";
|
|
269
|
-
function piProjectionProviderId(
|
|
270
|
-
return `byok-sdk-${
|
|
456
|
+
function piProjectionProviderId(profileRef) {
|
|
457
|
+
return `byok-sdk-${profileRef}`;
|
|
271
458
|
}
|
|
272
459
|
function buildPiProviderProjection(profile) {
|
|
273
|
-
const projectedProviderId = piProjectionProviderId(profile.
|
|
460
|
+
const projectedProviderId = piProjectionProviderId(profile.profile_ref);
|
|
274
461
|
return {
|
|
275
462
|
providers: {
|
|
276
463
|
[projectedProviderId]: {
|
|
@@ -281,7 +468,11 @@ function buildPiProviderProjection(profile) {
|
|
|
281
468
|
models: [
|
|
282
469
|
{
|
|
283
470
|
id: profile.model,
|
|
284
|
-
name: profile.display_name
|
|
471
|
+
name: profile.display_name,
|
|
472
|
+
input: [
|
|
473
|
+
"text",
|
|
474
|
+
...profile.capabilities.includes("image-input") ? ["image"] : []
|
|
475
|
+
]
|
|
285
476
|
}
|
|
286
477
|
]
|
|
287
478
|
}
|
|
@@ -314,139 +505,12 @@ function buildPiProviderArgs(profile, delegatedArgs) {
|
|
|
314
505
|
return [
|
|
315
506
|
...delegatedArgs,
|
|
316
507
|
"--provider",
|
|
317
|
-
piProjectionProviderId(profile.
|
|
508
|
+
piProjectionProviderId(profile.profile_ref),
|
|
318
509
|
"--model",
|
|
319
510
|
profile.model
|
|
320
511
|
];
|
|
321
512
|
}
|
|
322
513
|
|
|
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
514
|
// src/pi-provider-launcher-core.ts
|
|
451
515
|
var PI_CHILD_BASE_ENV_NAMES = [
|
|
452
516
|
"PATH",
|
|
@@ -480,15 +544,17 @@ var PI_CHILD_WINDOWS_ENV_NAMES = [
|
|
|
480
544
|
];
|
|
481
545
|
function parsePiProviderLauncherOptions(args) {
|
|
482
546
|
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 --");
|
|
547
|
+
const ownArgs = separator < 0 ? args : args.slice(0, separator);
|
|
548
|
+
const piArgs = separator < 0 ? [] : args.slice(separator + 1);
|
|
487
549
|
const allowedFlags = /* @__PURE__ */ new Set([
|
|
488
550
|
"--pi-bin",
|
|
489
551
|
"--profile-db",
|
|
490
552
|
"--provider",
|
|
491
553
|
"--model",
|
|
554
|
+
"--profile-revision",
|
|
555
|
+
"--profile-hash",
|
|
556
|
+
"--required-capabilities",
|
|
557
|
+
"--validate-only",
|
|
492
558
|
"--session-dir",
|
|
493
559
|
"--secret-service-prefix",
|
|
494
560
|
"--macos-keychain-path"
|
|
@@ -512,9 +578,10 @@ function parsePiProviderLauncherOptions(args) {
|
|
|
512
578
|
if (value === void 0) throw new Error(`${flag} requires a value`);
|
|
513
579
|
return value;
|
|
514
580
|
};
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
581
|
+
const rawProfileRef = required("--provider");
|
|
582
|
+
const profileRef = ProviderProfileRefSchema.safeParse(rawProfileRef);
|
|
583
|
+
if (!profileRef.success) {
|
|
584
|
+
throw new Error(`provider profile ref ${rawProfileRef} is not a valid @byok-sdk/keys identifier`);
|
|
518
585
|
}
|
|
519
586
|
const modelId = required("--model");
|
|
520
587
|
if (modelId.length > 160) throw new Error("--model exceeds 160 characters");
|
|
@@ -528,11 +595,54 @@ function parsePiProviderLauncherOptions(args) {
|
|
|
528
595
|
if (macosKeychainPath !== void 0 && !path2.posix.isAbsolute(macosKeychainPath)) {
|
|
529
596
|
throw new Error("--macos-keychain-path must be an absolute path");
|
|
530
597
|
}
|
|
598
|
+
const validateOnly = values.get("--validate-only") === "true";
|
|
599
|
+
if (values.has("--validate-only") && !["true", "false"].includes(values.get("--validate-only"))) {
|
|
600
|
+
throw new Error("--validate-only must be true or false");
|
|
601
|
+
}
|
|
602
|
+
if (!validateOnly && piArgs.length === 0) {
|
|
603
|
+
throw new Error("launcher requires Pi arguments after --");
|
|
604
|
+
}
|
|
605
|
+
const exactValues = [
|
|
606
|
+
values.get("--profile-revision"),
|
|
607
|
+
values.get("--profile-hash"),
|
|
608
|
+
values.get("--required-capabilities")
|
|
609
|
+
];
|
|
610
|
+
if (exactValues.some((value) => value !== void 0) && exactValues.some((value) => value === void 0)) {
|
|
611
|
+
throw new Error("exact provider binding requires revision, hash, and required capabilities together");
|
|
612
|
+
}
|
|
613
|
+
let expectedBinding;
|
|
614
|
+
if (exactValues[0] !== void 0) {
|
|
615
|
+
if (!/^(?:0|[1-9][0-9]{0,19})$/u.test(exactValues[0])) {
|
|
616
|
+
throw new Error("--profile-revision must be canonical decimal");
|
|
617
|
+
}
|
|
618
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(exactValues[1])) {
|
|
619
|
+
throw new Error("--profile-hash must be lowercase sha256");
|
|
620
|
+
}
|
|
621
|
+
let capabilities;
|
|
622
|
+
try {
|
|
623
|
+
capabilities = JSON.parse(exactValues[2]);
|
|
624
|
+
} catch {
|
|
625
|
+
throw new Error("--required-capabilities must be a JSON array");
|
|
626
|
+
}
|
|
627
|
+
const parsedCapabilities = ProviderModelCapabilitySchema.array().max(8).safeParse(capabilities);
|
|
628
|
+
if (!parsedCapabilities.success || new Set(parsedCapabilities.data).size !== parsedCapabilities.data.length) {
|
|
629
|
+
throw new Error("--required-capabilities must contain unique supported capabilities");
|
|
630
|
+
}
|
|
631
|
+
expectedBinding = {
|
|
632
|
+
profileRef: profileRef.data,
|
|
633
|
+
profileRevision: exactValues[0],
|
|
634
|
+
profileHash: exactValues[1],
|
|
635
|
+
modelId,
|
|
636
|
+
requiredCapabilities: parsedCapabilities.data
|
|
637
|
+
};
|
|
638
|
+
}
|
|
531
639
|
return {
|
|
532
640
|
piBin: required("--pi-bin"),
|
|
533
641
|
profileDbPath,
|
|
534
|
-
|
|
642
|
+
profileRef: profileRef.data,
|
|
535
643
|
modelId,
|
|
644
|
+
...expectedBinding === void 0 ? {} : { expectedBinding },
|
|
645
|
+
validateOnly,
|
|
536
646
|
sessionDir,
|
|
537
647
|
...secretServicePrefix ? { secretServicePrefix } : {},
|
|
538
648
|
...macosKeychainPath !== void 0 ? { macosKeychainPath } : {},
|
|
@@ -548,11 +658,11 @@ async function resolvePiProviderSecret(profile, createStore) {
|
|
|
548
658
|
`${secrets.providerLabel} is unavailable`
|
|
549
659
|
);
|
|
550
660
|
}
|
|
551
|
-
const secret = await secrets.get(modelProviderSecretName(profile.
|
|
661
|
+
const secret = await secrets.get(modelProviderSecretName(profile.profile_ref));
|
|
552
662
|
if (!secret) {
|
|
553
663
|
throw new ByokKeysError(
|
|
554
664
|
"PROVIDER_SECRET_MISSING",
|
|
555
|
-
`${profile.
|
|
665
|
+
`${profile.profile_ref} provider profile requires a secret in ${secrets.providerLabel}`
|
|
556
666
|
);
|
|
557
667
|
}
|
|
558
668
|
return secret;
|
|
@@ -596,10 +706,10 @@ async function ensurePiSessionDirectory(sessionDir) {
|
|
|
596
706
|
}
|
|
597
707
|
|
|
598
708
|
// src/profile-store.ts
|
|
599
|
-
function providerNotConfigured(
|
|
709
|
+
function providerNotConfigured(profileRef) {
|
|
600
710
|
return new ByokKeysError(
|
|
601
711
|
"PROVIDER_NOT_CONFIGURED",
|
|
602
|
-
`${
|
|
712
|
+
`${profileRef} model provider is not configured`
|
|
603
713
|
);
|
|
604
714
|
}
|
|
605
715
|
function closeSqliteDatabaseAfterInitializationFailure(database, initializationError, message, close = (handle) => handle.close()) {
|
|
@@ -668,16 +778,18 @@ function secureSqliteFilePermissions(databasePath) {
|
|
|
668
778
|
// src/sqlite-profile-store.ts
|
|
669
779
|
var SCHEMA = `
|
|
670
780
|
CREATE TABLE IF NOT EXISTS provider_profile (
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
781
|
+
profile_ref TEXT PRIMARY KEY,
|
|
782
|
+
provider_kind TEXT NOT NULL CHECK (provider_kind IN ('openai', 'deepseek', 'anthropic', 'custom')),
|
|
783
|
+
kind TEXT NOT NULL CHECK (kind = 'model'),
|
|
784
|
+
adapter TEXT NOT NULL CHECK (adapter IN ('openai_compatible', 'anthropic')),
|
|
785
|
+
display_name TEXT NOT NULL,
|
|
786
|
+
base_url TEXT NOT NULL,
|
|
787
|
+
auth_mode TEXT NOT NULL CHECK (auth_mode IN ('bearer', 'x_api_key', 'none')),
|
|
788
|
+
model TEXT NOT NULL,
|
|
789
|
+
capabilities TEXT NOT NULL,
|
|
790
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
791
|
+
created_at TEXT NOT NULL,
|
|
792
|
+
updated_at TEXT NOT NULL
|
|
681
793
|
);
|
|
682
794
|
`;
|
|
683
795
|
var ENABLED_INDEX = `
|
|
@@ -717,12 +829,12 @@ var SqliteProviderProfileStore = class {
|
|
|
717
829
|
this.#closed = true;
|
|
718
830
|
this.#database.close();
|
|
719
831
|
}
|
|
720
|
-
async delete(
|
|
721
|
-
const result = this.#database.prepare("DELETE FROM provider_profile WHERE
|
|
832
|
+
async delete(profileRef) {
|
|
833
|
+
const result = this.#database.prepare("DELETE FROM provider_profile WHERE profile_ref = ?").run(profileRef);
|
|
722
834
|
return Number(result.changes) === 1;
|
|
723
835
|
}
|
|
724
|
-
async get(
|
|
725
|
-
const row = this.#database.prepare("SELECT * FROM provider_profile WHERE
|
|
836
|
+
async get(profileRef) {
|
|
837
|
+
const row = this.#database.prepare("SELECT * FROM provider_profile WHERE profile_ref = ?").get(profileRef);
|
|
726
838
|
return row === void 0 ? void 0 : parseRow(row);
|
|
727
839
|
}
|
|
728
840
|
async getEnabled() {
|
|
@@ -730,11 +842,11 @@ var SqliteProviderProfileStore = class {
|
|
|
730
842
|
return row === void 0 ? void 0 : parseRow(row);
|
|
731
843
|
}
|
|
732
844
|
async list() {
|
|
733
|
-
const rows = this.#database.prepare("SELECT * FROM provider_profile ORDER BY
|
|
845
|
+
const rows = this.#database.prepare("SELECT * FROM provider_profile ORDER BY profile_ref ASC").all();
|
|
734
846
|
return rows.map(parseRow);
|
|
735
847
|
}
|
|
736
848
|
async save(profile) {
|
|
737
|
-
const existing = await this.get(profile.
|
|
849
|
+
const existing = await this.get(profile.profile_ref);
|
|
738
850
|
const validated = parseModelProviderProfile({
|
|
739
851
|
...profile,
|
|
740
852
|
created_at: existing?.created_at ?? profile.created_at
|
|
@@ -742,40 +854,44 @@ var SqliteProviderProfileStore = class {
|
|
|
742
854
|
this.#transaction(() => {
|
|
743
855
|
if (validated.enabled) {
|
|
744
856
|
this.#database.prepare(
|
|
745
|
-
"UPDATE provider_profile SET enabled = 0 WHERE
|
|
746
|
-
).run(validated.
|
|
857
|
+
"UPDATE provider_profile SET enabled = 0 WHERE profile_ref <> ?"
|
|
858
|
+
).run(validated.profile_ref);
|
|
747
859
|
}
|
|
748
860
|
this.#database.prepare(
|
|
749
861
|
`INSERT INTO provider_profile (
|
|
750
|
-
|
|
751
|
-
auth_mode, model, enabled, created_at, updated_at
|
|
752
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
753
|
-
ON CONFLICT(
|
|
862
|
+
profile_ref, provider_kind, kind, adapter, display_name, base_url,
|
|
863
|
+
auth_mode, model, capabilities, enabled, created_at, updated_at
|
|
864
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
865
|
+
ON CONFLICT(profile_ref) DO UPDATE SET
|
|
866
|
+
provider_kind = excluded.provider_kind,
|
|
754
867
|
adapter = excluded.adapter,
|
|
755
868
|
display_name = excluded.display_name,
|
|
756
869
|
base_url = excluded.base_url,
|
|
757
870
|
auth_mode = excluded.auth_mode,
|
|
758
871
|
model = excluded.model,
|
|
872
|
+
capabilities = excluded.capabilities,
|
|
759
873
|
enabled = excluded.enabled,
|
|
760
874
|
updated_at = excluded.updated_at`
|
|
761
875
|
).run(
|
|
762
|
-
validated.
|
|
876
|
+
validated.profile_ref,
|
|
877
|
+
validated.provider_kind,
|
|
763
878
|
validated.kind,
|
|
764
879
|
validated.adapter,
|
|
765
880
|
validated.display_name,
|
|
766
881
|
validated.base_url,
|
|
767
882
|
validated.auth_mode,
|
|
768
883
|
validated.model,
|
|
884
|
+
JSON.stringify(validated.capabilities),
|
|
769
885
|
validated.enabled ? 1 : 0,
|
|
770
886
|
validated.created_at,
|
|
771
887
|
validated.updated_at
|
|
772
888
|
);
|
|
773
889
|
});
|
|
774
|
-
return await this.get(validated.
|
|
890
|
+
return await this.get(validated.profile_ref);
|
|
775
891
|
}
|
|
776
|
-
async setEnabled(
|
|
777
|
-
const existing = await this.get(
|
|
778
|
-
if (existing === void 0) throw providerNotConfigured(
|
|
892
|
+
async setEnabled(profileRef) {
|
|
893
|
+
const existing = await this.get(profileRef);
|
|
894
|
+
if (existing === void 0) throw providerNotConfigured(profileRef);
|
|
779
895
|
return this.save({ ...existing, enabled: true });
|
|
780
896
|
}
|
|
781
897
|
/** `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK`, per `providers.ts:1252-1263`. */
|
|
@@ -791,8 +907,19 @@ var SqliteProviderProfileStore = class {
|
|
|
791
907
|
}
|
|
792
908
|
};
|
|
793
909
|
function parseRow(row) {
|
|
910
|
+
let capabilities;
|
|
911
|
+
try {
|
|
912
|
+
capabilities = JSON.parse(row.capabilities);
|
|
913
|
+
} catch (cause) {
|
|
914
|
+
throw new ByokKeysError(
|
|
915
|
+
"PROVIDER_PROFILE_INVALID",
|
|
916
|
+
"Provider profile capabilities column is not valid JSON",
|
|
917
|
+
{ cause }
|
|
918
|
+
);
|
|
919
|
+
}
|
|
794
920
|
return parseModelProviderProfile({
|
|
795
921
|
...row,
|
|
922
|
+
capabilities,
|
|
796
923
|
enabled: row.enabled === 1
|
|
797
924
|
});
|
|
798
925
|
}
|
|
@@ -1100,15 +1227,19 @@ async function run(options) {
|
|
|
1100
1227
|
});
|
|
1101
1228
|
let projectionDir;
|
|
1102
1229
|
try {
|
|
1103
|
-
const profile = await profiles.get(options.
|
|
1230
|
+
const profile = await profiles.get(options.profileRef);
|
|
1104
1231
|
if (profile === void 0) {
|
|
1105
|
-
throw new Error(`provider ${options.
|
|
1232
|
+
throw new Error(`provider profile ${options.profileRef} is not configured`);
|
|
1106
1233
|
}
|
|
1107
1234
|
if (profile.model !== options.modelId) {
|
|
1108
1235
|
throw new Error(
|
|
1109
1236
|
`selected model ${options.modelId} does not match configured provider model ${profile.model}`
|
|
1110
1237
|
);
|
|
1111
1238
|
}
|
|
1239
|
+
if (options.expectedBinding !== void 0) {
|
|
1240
|
+
assertExactProviderProfileBinding(profile, options.expectedBinding);
|
|
1241
|
+
}
|
|
1242
|
+
if (options.validateOnly) return 0;
|
|
1112
1243
|
const secret = await resolvePiProviderSecret(
|
|
1113
1244
|
profile,
|
|
1114
1245
|
() => createSecretStore(
|