@hasna/switcher 0.1.2 → 0.1.4
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 +20 -8
- package/dist/cli/index.js +2725 -621
- package/dist/cli.d.ts +2 -0
- package/dist/codex-model-policy.d.ts +65 -0
- package/dist/credentials.d.ts +71 -8
- package/dist/direct-launch.d.ts +2 -2
- package/dist/domain.d.ts +259 -2
- package/dist/gemini-model-policy.d.ts +56 -0
- package/dist/generated/api.d.ts +192 -0
- package/dist/harness-types.d.ts +9 -0
- package/dist/hermes-model-policy.d.ts +30 -0
- package/dist/index.js +138 -48
- package/dist/inference-gateway.d.ts +24 -0
- package/dist/launcher.d.ts +8 -3
- package/dist/mcp/index.js +153 -62
- package/dist/model-policy-schema.d.ts +137 -0
- package/dist/model-policy.d.ts +31 -0
- package/dist/native-model-policy.d.ts +23 -0
- package/dist/opencode-model-policy.d.ts +33 -0
- package/dist/ori-model-policy.d.ts +8 -0
- package/dist/sdk.d.ts +248 -6
- package/dist/sdk.js +138 -48
- package/dist/serve/index.js +1044 -88
- package/docs/MODEL-POLICY.md +82 -0
- package/openapi.json +836 -1
- package/package.json +3 -2
package/dist/sdk.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { components } from "./generated/api";
|
|
2
|
+
import { type CredentialChainOptions } from "@hasna/contracts/client";
|
|
2
3
|
export { providerFromPreset } from "./presets";
|
|
3
4
|
export type ProviderPreset = components["schemas"]["ProviderPreset"];
|
|
4
5
|
export type ProviderInput = components["schemas"]["ProviderInput"];
|
|
5
6
|
export type Provider = components["schemas"]["Provider"];
|
|
6
7
|
export type ProfileInput = components["schemas"]["ProfileInput"];
|
|
7
8
|
export type Profile = components["schemas"]["Profile"];
|
|
9
|
+
export type ModelPolicy = components["schemas"]["ModelPolicy"];
|
|
10
|
+
export type RoutingEvent = components["schemas"]["RoutingEvent"];
|
|
8
11
|
export type Model = components["schemas"]["Model"];
|
|
9
12
|
export type Catalog = components["schemas"]["Catalog"];
|
|
10
13
|
export type LaunchPlan = components["schemas"]["LaunchPlan"];
|
|
@@ -27,10 +30,18 @@ export type ClientOptions = {
|
|
|
27
30
|
fetch?: typeof fetch;
|
|
28
31
|
timeoutMs?: number;
|
|
29
32
|
};
|
|
33
|
+
export type EnvironmentClientOptions = {
|
|
34
|
+
credentials?: CredentialChainOptions;
|
|
35
|
+
fetch?: typeof fetch;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
};
|
|
30
38
|
export declare class SwitcherClient {
|
|
31
39
|
private readonly options;
|
|
40
|
+
private transport?;
|
|
32
41
|
readonly baseUrl: string;
|
|
33
42
|
constructor(options: ClientOptions);
|
|
43
|
+
/** Use Contracts' paired authority/credential resolution on every request. */
|
|
44
|
+
static fromEnvironment(env?: Record<string, string | undefined>, options?: EnvironmentClientOptions): SwitcherClient;
|
|
34
45
|
request<T>(method: string, path: string, body?: unknown, options?: {
|
|
35
46
|
version?: number;
|
|
36
47
|
idempotencyKey?: string;
|
|
@@ -226,6 +237,26 @@ export declare class SwitcherClient {
|
|
|
226
237
|
providerId: string;
|
|
227
238
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
228
239
|
model: string;
|
|
240
|
+
modelPolicy?: {
|
|
241
|
+
version: 1;
|
|
242
|
+
roles?: {
|
|
243
|
+
subagent?: string;
|
|
244
|
+
fast?: string;
|
|
245
|
+
planning?: string;
|
|
246
|
+
review?: string;
|
|
247
|
+
summary?: string;
|
|
248
|
+
compaction?: string;
|
|
249
|
+
weak?: string;
|
|
250
|
+
editor?: string;
|
|
251
|
+
};
|
|
252
|
+
allowedModels?: string[];
|
|
253
|
+
aliases?: {
|
|
254
|
+
[key: string]: string;
|
|
255
|
+
};
|
|
256
|
+
fallbacks?: {
|
|
257
|
+
[key: string]: string[];
|
|
258
|
+
};
|
|
259
|
+
};
|
|
229
260
|
version: number;
|
|
230
261
|
updatedAt: string;
|
|
231
262
|
}>>;
|
|
@@ -235,6 +266,26 @@ export declare class SwitcherClient {
|
|
|
235
266
|
providerId: string;
|
|
236
267
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
237
268
|
model: string;
|
|
269
|
+
modelPolicy?: {
|
|
270
|
+
version: 1;
|
|
271
|
+
roles?: {
|
|
272
|
+
subagent?: string;
|
|
273
|
+
fast?: string;
|
|
274
|
+
planning?: string;
|
|
275
|
+
review?: string;
|
|
276
|
+
summary?: string;
|
|
277
|
+
compaction?: string;
|
|
278
|
+
weak?: string;
|
|
279
|
+
editor?: string;
|
|
280
|
+
};
|
|
281
|
+
allowedModels?: string[];
|
|
282
|
+
aliases?: {
|
|
283
|
+
[key: string]: string;
|
|
284
|
+
};
|
|
285
|
+
fallbacks?: {
|
|
286
|
+
[key: string]: string[];
|
|
287
|
+
};
|
|
288
|
+
};
|
|
238
289
|
version: number;
|
|
239
290
|
updatedAt: string;
|
|
240
291
|
}>;
|
|
@@ -244,6 +295,26 @@ export declare class SwitcherClient {
|
|
|
244
295
|
providerId: string;
|
|
245
296
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
246
297
|
model: string;
|
|
298
|
+
modelPolicy?: {
|
|
299
|
+
version: 1;
|
|
300
|
+
roles?: {
|
|
301
|
+
subagent?: string;
|
|
302
|
+
fast?: string;
|
|
303
|
+
planning?: string;
|
|
304
|
+
review?: string;
|
|
305
|
+
summary?: string;
|
|
306
|
+
compaction?: string;
|
|
307
|
+
weak?: string;
|
|
308
|
+
editor?: string;
|
|
309
|
+
};
|
|
310
|
+
allowedModels?: string[];
|
|
311
|
+
aliases?: {
|
|
312
|
+
[key: string]: string;
|
|
313
|
+
};
|
|
314
|
+
fallbacks?: {
|
|
315
|
+
[key: string]: string[];
|
|
316
|
+
};
|
|
317
|
+
};
|
|
247
318
|
version: number;
|
|
248
319
|
updatedAt: string;
|
|
249
320
|
}>;
|
|
@@ -253,6 +324,26 @@ export declare class SwitcherClient {
|
|
|
253
324
|
providerId: string;
|
|
254
325
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
255
326
|
model: string;
|
|
327
|
+
modelPolicy?: {
|
|
328
|
+
version: 1;
|
|
329
|
+
roles?: {
|
|
330
|
+
subagent?: string;
|
|
331
|
+
fast?: string;
|
|
332
|
+
planning?: string;
|
|
333
|
+
review?: string;
|
|
334
|
+
summary?: string;
|
|
335
|
+
compaction?: string;
|
|
336
|
+
weak?: string;
|
|
337
|
+
editor?: string;
|
|
338
|
+
};
|
|
339
|
+
allowedModels?: string[];
|
|
340
|
+
aliases?: {
|
|
341
|
+
[key: string]: string;
|
|
342
|
+
};
|
|
343
|
+
fallbacks?: {
|
|
344
|
+
[key: string]: string[];
|
|
345
|
+
};
|
|
346
|
+
};
|
|
256
347
|
version: number;
|
|
257
348
|
updatedAt: string;
|
|
258
349
|
}>;
|
|
@@ -294,6 +385,26 @@ export declare class SwitcherClient {
|
|
|
294
385
|
providerId: string;
|
|
295
386
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
296
387
|
model: string;
|
|
388
|
+
modelPolicy?: {
|
|
389
|
+
version: 1;
|
|
390
|
+
roles?: {
|
|
391
|
+
subagent?: string;
|
|
392
|
+
fast?: string;
|
|
393
|
+
planning?: string;
|
|
394
|
+
review?: string;
|
|
395
|
+
summary?: string;
|
|
396
|
+
compaction?: string;
|
|
397
|
+
weak?: string;
|
|
398
|
+
editor?: string;
|
|
399
|
+
};
|
|
400
|
+
allowedModels?: string[];
|
|
401
|
+
aliases?: {
|
|
402
|
+
[key: string]: string;
|
|
403
|
+
};
|
|
404
|
+
fallbacks?: {
|
|
405
|
+
[key: string]: string[];
|
|
406
|
+
};
|
|
407
|
+
};
|
|
297
408
|
version: number;
|
|
298
409
|
updatedAt: string;
|
|
299
410
|
};
|
|
@@ -317,9 +428,30 @@ export declare class SwitcherClient {
|
|
|
317
428
|
warnings: string[];
|
|
318
429
|
}>;
|
|
319
430
|
listRuns(options?: {}): Promise<Page<{
|
|
431
|
+
modelPolicyVersion?: 1;
|
|
320
432
|
profileId: string;
|
|
321
433
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
322
434
|
model: string;
|
|
435
|
+
modelPolicy?: {
|
|
436
|
+
version: 1;
|
|
437
|
+
roles?: {
|
|
438
|
+
subagent?: string;
|
|
439
|
+
fast?: string;
|
|
440
|
+
planning?: string;
|
|
441
|
+
review?: string;
|
|
442
|
+
summary?: string;
|
|
443
|
+
compaction?: string;
|
|
444
|
+
weak?: string;
|
|
445
|
+
editor?: string;
|
|
446
|
+
};
|
|
447
|
+
allowedModels?: string[];
|
|
448
|
+
aliases?: {
|
|
449
|
+
[key: string]: string;
|
|
450
|
+
};
|
|
451
|
+
fallbacks?: {
|
|
452
|
+
[key: string]: string[];
|
|
453
|
+
};
|
|
454
|
+
};
|
|
323
455
|
planToken: string;
|
|
324
456
|
version: number;
|
|
325
457
|
updatedAt: string;
|
|
@@ -331,11 +463,44 @@ export declare class SwitcherClient {
|
|
|
331
463
|
startedAt: string;
|
|
332
464
|
endedAt?: string;
|
|
333
465
|
exitCode?: number;
|
|
466
|
+
routingEvents?: {
|
|
467
|
+
at: string;
|
|
468
|
+
requestId: string;
|
|
469
|
+
requestedModel: string;
|
|
470
|
+
resolvedModel?: string;
|
|
471
|
+
reportedModel?: string;
|
|
472
|
+
decision: "allow" | "alias" | "reject" | "fallback";
|
|
473
|
+
role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor";
|
|
474
|
+
reason?: string;
|
|
475
|
+
upstreamStatus?: number;
|
|
476
|
+
}[];
|
|
477
|
+
routingEventsDropped?: number;
|
|
334
478
|
}>>;
|
|
335
479
|
getRun(id: string): Promise<{
|
|
480
|
+
modelPolicyVersion?: 1;
|
|
336
481
|
profileId: string;
|
|
337
482
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
338
483
|
model: string;
|
|
484
|
+
modelPolicy?: {
|
|
485
|
+
version: 1;
|
|
486
|
+
roles?: {
|
|
487
|
+
subagent?: string;
|
|
488
|
+
fast?: string;
|
|
489
|
+
planning?: string;
|
|
490
|
+
review?: string;
|
|
491
|
+
summary?: string;
|
|
492
|
+
compaction?: string;
|
|
493
|
+
weak?: string;
|
|
494
|
+
editor?: string;
|
|
495
|
+
};
|
|
496
|
+
allowedModels?: string[];
|
|
497
|
+
aliases?: {
|
|
498
|
+
[key: string]: string;
|
|
499
|
+
};
|
|
500
|
+
fallbacks?: {
|
|
501
|
+
[key: string]: string[];
|
|
502
|
+
};
|
|
503
|
+
};
|
|
339
504
|
planToken: string;
|
|
340
505
|
version: number;
|
|
341
506
|
updatedAt: string;
|
|
@@ -347,11 +512,46 @@ export declare class SwitcherClient {
|
|
|
347
512
|
startedAt: string;
|
|
348
513
|
endedAt?: string;
|
|
349
514
|
exitCode?: number;
|
|
515
|
+
routingEvents?: {
|
|
516
|
+
at: string;
|
|
517
|
+
requestId: string;
|
|
518
|
+
requestedModel: string;
|
|
519
|
+
resolvedModel?: string;
|
|
520
|
+
reportedModel?: string;
|
|
521
|
+
decision: "allow" | "alias" | "reject" | "fallback";
|
|
522
|
+
role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor";
|
|
523
|
+
reason?: string;
|
|
524
|
+
upstreamStatus?: number;
|
|
525
|
+
}[];
|
|
526
|
+
routingEventsDropped?: number;
|
|
350
527
|
}>;
|
|
351
|
-
createRun(input: components["schemas"]["RunInput"],
|
|
528
|
+
createRun(input: Omit<components["schemas"]["RunInput"], "modelPolicyVersion"> & {
|
|
529
|
+
modelPolicyVersion?: 1;
|
|
530
|
+
}, idempotencyKey?: string): Promise<{
|
|
531
|
+
modelPolicyVersion?: 1;
|
|
352
532
|
profileId: string;
|
|
353
533
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
354
534
|
model: string;
|
|
535
|
+
modelPolicy?: {
|
|
536
|
+
version: 1;
|
|
537
|
+
roles?: {
|
|
538
|
+
subagent?: string;
|
|
539
|
+
fast?: string;
|
|
540
|
+
planning?: string;
|
|
541
|
+
review?: string;
|
|
542
|
+
summary?: string;
|
|
543
|
+
compaction?: string;
|
|
544
|
+
weak?: string;
|
|
545
|
+
editor?: string;
|
|
546
|
+
};
|
|
547
|
+
allowedModels?: string[];
|
|
548
|
+
aliases?: {
|
|
549
|
+
[key: string]: string;
|
|
550
|
+
};
|
|
551
|
+
fallbacks?: {
|
|
552
|
+
[key: string]: string[];
|
|
553
|
+
};
|
|
554
|
+
};
|
|
355
555
|
planToken: string;
|
|
356
556
|
version: number;
|
|
357
557
|
updatedAt: string;
|
|
@@ -363,14 +563,44 @@ export declare class SwitcherClient {
|
|
|
363
563
|
startedAt: string;
|
|
364
564
|
endedAt?: string;
|
|
365
565
|
exitCode?: number;
|
|
566
|
+
routingEvents?: {
|
|
567
|
+
at: string;
|
|
568
|
+
requestId: string;
|
|
569
|
+
requestedModel: string;
|
|
570
|
+
resolvedModel?: string;
|
|
571
|
+
reportedModel?: string;
|
|
572
|
+
decision: "allow" | "alias" | "reject" | "fallback";
|
|
573
|
+
role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor";
|
|
574
|
+
reason?: string;
|
|
575
|
+
upstreamStatus?: number;
|
|
576
|
+
}[];
|
|
577
|
+
routingEventsDropped?: number;
|
|
366
578
|
}>;
|
|
367
|
-
finishRun(id: string, version: number, input: {
|
|
368
|
-
|
|
369
|
-
exitCode: number;
|
|
370
|
-
}, idempotencyKey?: string): Promise<{
|
|
579
|
+
finishRun(id: string, version: number, input: components["schemas"]["RunUpdate"], idempotencyKey?: string): Promise<{
|
|
580
|
+
modelPolicyVersion?: 1;
|
|
371
581
|
profileId: string;
|
|
372
582
|
harness: "claude" | "codex" | "grok" | "opencode" | "opencode2" | "pi" | "omp" | "dsh" | "cline" | "hermes" | "prime-agent" | "gemini" | "aider" | "kilo";
|
|
373
583
|
model: string;
|
|
584
|
+
modelPolicy?: {
|
|
585
|
+
version: 1;
|
|
586
|
+
roles?: {
|
|
587
|
+
subagent?: string;
|
|
588
|
+
fast?: string;
|
|
589
|
+
planning?: string;
|
|
590
|
+
review?: string;
|
|
591
|
+
summary?: string;
|
|
592
|
+
compaction?: string;
|
|
593
|
+
weak?: string;
|
|
594
|
+
editor?: string;
|
|
595
|
+
};
|
|
596
|
+
allowedModels?: string[];
|
|
597
|
+
aliases?: {
|
|
598
|
+
[key: string]: string;
|
|
599
|
+
};
|
|
600
|
+
fallbacks?: {
|
|
601
|
+
[key: string]: string[];
|
|
602
|
+
};
|
|
603
|
+
};
|
|
374
604
|
planToken: string;
|
|
375
605
|
version: number;
|
|
376
606
|
updatedAt: string;
|
|
@@ -382,6 +612,18 @@ export declare class SwitcherClient {
|
|
|
382
612
|
startedAt: string;
|
|
383
613
|
endedAt?: string;
|
|
384
614
|
exitCode?: number;
|
|
615
|
+
routingEvents?: {
|
|
616
|
+
at: string;
|
|
617
|
+
requestId: string;
|
|
618
|
+
requestedModel: string;
|
|
619
|
+
resolvedModel?: string;
|
|
620
|
+
reportedModel?: string;
|
|
621
|
+
decision: "allow" | "alias" | "reject" | "fallback";
|
|
622
|
+
role?: "main" | "subagent" | "fast" | "planning" | "review" | "summary" | "compaction" | "weak" | "editor";
|
|
623
|
+
reason?: string;
|
|
624
|
+
upstreamStatus?: number;
|
|
625
|
+
}[];
|
|
626
|
+
routingEventsDropped?: number;
|
|
385
627
|
}>;
|
|
386
628
|
}
|
|
387
|
-
export declare function clientFromEnv(env?: Record<string, string | undefined
|
|
629
|
+
export declare function clientFromEnv(env?: Record<string, string | undefined>, options?: EnvironmentClientOptions): SwitcherClient;
|
package/dist/sdk.js
CHANGED
|
@@ -1,10 +1,56 @@
|
|
|
1
1
|
// src/domain.ts
|
|
2
|
+
import { z as z2 } from "zod";
|
|
3
|
+
|
|
4
|
+
// src/model-policy-schema.ts
|
|
2
5
|
import { z } from "zod";
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
6
|
+
var policyModelIdSchema = z.string().min(1).max(300).regex(/^[^\u0000-\u001f\u007f]+$/);
|
|
7
|
+
var modelPolicyRoleSchema = z.enum(["subagent", "fast", "planning", "review", "summary", "compaction", "weak", "editor"]);
|
|
8
|
+
var boundedModelList = z.array(policyModelIdSchema).max(500);
|
|
9
|
+
var aliasSchema = z.string().regex(/^[A-Za-z0-9._/-]{1,120}$/).refine((v) => !["__proto__", "prototype", "constructor"].includes(v));
|
|
10
|
+
var boundedModelMap = z.record(aliasSchema, policyModelIdSchema).superRefine((value, ctx) => {
|
|
11
|
+
if (Object.keys(value).length > 200)
|
|
12
|
+
ctx.addIssue({ code: "custom", message: "Model policy maps may contain at most 200 entries." });
|
|
13
|
+
});
|
|
14
|
+
var fallbacksSchema = z.record(policyModelIdSchema, z.array(policyModelIdSchema).max(20)).superRefine((value, ctx) => {
|
|
15
|
+
if (Object.keys(value).length > 200)
|
|
16
|
+
ctx.addIssue({ code: "custom", message: "Model policy fallback maps may contain at most 200 entries." });
|
|
17
|
+
});
|
|
18
|
+
var modelPolicySchema = z.object({
|
|
19
|
+
version: z.literal(1).default(1),
|
|
20
|
+
roles: z.object({
|
|
21
|
+
subagent: policyModelIdSchema.optional(),
|
|
22
|
+
fast: policyModelIdSchema.optional(),
|
|
23
|
+
planning: policyModelIdSchema.optional(),
|
|
24
|
+
review: policyModelIdSchema.optional(),
|
|
25
|
+
summary: policyModelIdSchema.optional(),
|
|
26
|
+
compaction: policyModelIdSchema.optional(),
|
|
27
|
+
weak: policyModelIdSchema.optional(),
|
|
28
|
+
editor: policyModelIdSchema.optional()
|
|
29
|
+
}).strict().optional(),
|
|
30
|
+
allowedModels: boundedModelList.optional(),
|
|
31
|
+
aliases: boundedModelMap.optional(),
|
|
32
|
+
fallbacks: fallbacksSchema.optional()
|
|
33
|
+
}).strict();
|
|
34
|
+
var routingDecisionSchema = z.enum(["allow", "alias", "reject", "fallback"]);
|
|
35
|
+
var routingEventRoleSchema = z.enum(["main", ...modelPolicyRoleSchema.options]);
|
|
36
|
+
var routingEventSchema = z.object({
|
|
37
|
+
at: z.string().datetime({ offset: true }),
|
|
38
|
+
requestId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/),
|
|
39
|
+
requestedModel: policyModelIdSchema,
|
|
40
|
+
resolvedModel: policyModelIdSchema.optional(),
|
|
41
|
+
reportedModel: policyModelIdSchema.optional(),
|
|
42
|
+
decision: routingDecisionSchema,
|
|
43
|
+
role: routingEventRoleSchema.optional(),
|
|
44
|
+
reason: z.string().regex(/^[a-z][a-z0-9_]{0,63}$/).optional(),
|
|
45
|
+
upstreamStatus: z.number().int().min(100).max(599).optional()
|
|
46
|
+
}).strict();
|
|
47
|
+
var routingEventsSchema = z.array(routingEventSchema).max(1000);
|
|
48
|
+
// src/domain.ts
|
|
49
|
+
var harnessSchema = z2.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
|
|
50
|
+
var protocolSchema = z2.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
|
|
51
|
+
var idSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
|
|
52
|
+
var label = z2.string().min(1).max(200);
|
|
53
|
+
var envRef = z2.string().regex(/^SWITCHER_PROVIDER_[A-Z0-9_]+$/);
|
|
8
54
|
function endpoint(value) {
|
|
9
55
|
let url;
|
|
10
56
|
try {
|
|
@@ -17,74 +63,79 @@ function endpoint(value) {
|
|
|
17
63
|
throw new Fault(400, "invalid_url", "URL must use HTTPS, contain no credentials/query/fragment, or use HTTP on loopback.");
|
|
18
64
|
return url.href.replace(/\/+$/, "");
|
|
19
65
|
}
|
|
20
|
-
var urlSchema =
|
|
66
|
+
var urlSchema = z2.string().max(2000).superRefine((v, ctx) => {
|
|
21
67
|
try {
|
|
22
68
|
endpoint(v);
|
|
23
69
|
} catch {
|
|
24
70
|
ctx.addIssue({ code: "custom", message: "Invalid endpoint URL" });
|
|
25
71
|
}
|
|
26
72
|
}).transform(endpoint);
|
|
27
|
-
var modelSchema =
|
|
28
|
-
id:
|
|
73
|
+
var modelSchema = z2.object({
|
|
74
|
+
id: z2.string().min(1).max(300),
|
|
29
75
|
name: label,
|
|
30
|
-
description:
|
|
31
|
-
available:
|
|
32
|
-
contextWindow:
|
|
33
|
-
maxOutputTokens:
|
|
34
|
-
inputModalities:
|
|
35
|
-
outputModalities:
|
|
36
|
-
supportedParameters:
|
|
37
|
-
supportedGenerationMethods:
|
|
76
|
+
description: z2.string().max(8000).optional(),
|
|
77
|
+
available: z2.boolean().optional(),
|
|
78
|
+
contextWindow: z2.number().int().positive().optional(),
|
|
79
|
+
maxOutputTokens: z2.number().int().positive().optional(),
|
|
80
|
+
inputModalities: z2.array(z2.string().max(50)).max(20).optional(),
|
|
81
|
+
outputModalities: z2.array(z2.string().max(50)).max(20).optional(),
|
|
82
|
+
supportedParameters: z2.array(z2.string().max(100)).max(100).optional(),
|
|
83
|
+
supportedGenerationMethods: z2.array(z2.string().min(1).max(100)).max(100).optional()
|
|
38
84
|
}).strict();
|
|
39
|
-
var providerInputSchema =
|
|
85
|
+
var providerInputSchema = z2.object({
|
|
40
86
|
id: idSchema,
|
|
41
87
|
name: label,
|
|
42
88
|
baseUrl: urlSchema,
|
|
43
89
|
protocol: protocolSchema,
|
|
44
90
|
credentialEnv: envRef.optional(),
|
|
45
|
-
authStyle:
|
|
91
|
+
authStyle: z2.enum(["bearer", "x-api-key", "api-key"]).default("bearer"),
|
|
46
92
|
catalogBaseUrl: urlSchema.optional(),
|
|
47
|
-
catalogFormat:
|
|
48
|
-
catalogAuthStyle:
|
|
93
|
+
catalogFormat: z2.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]).optional(),
|
|
94
|
+
catalogAuthStyle: z2.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
49
95
|
catalogCredentialEnv: envRef.optional(),
|
|
50
|
-
catalogAccountId:
|
|
51
|
-
modelsPath:
|
|
52
|
-
manualModels:
|
|
96
|
+
catalogAccountId: z2.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/).optional(),
|
|
97
|
+
modelsPath: z2.string().regex(/^[a-zA-Z0-9_/-]+$/).max(200).default("models"),
|
|
98
|
+
manualModels: z2.array(modelSchema).max(1e4).default([])
|
|
53
99
|
}).strict().refine((p) => !p.modelsPath.split("/").includes("..") && !p.modelsPath.startsWith("/"), "modelsPath must be relative");
|
|
54
|
-
var providerPresetSchema =
|
|
100
|
+
var providerPresetSchema = z2.object({
|
|
55
101
|
id: idSchema,
|
|
56
102
|
name: label,
|
|
57
103
|
credentialEnv: envRef.optional(),
|
|
58
|
-
credentialAliases:
|
|
59
|
-
protocols:
|
|
104
|
+
credentialAliases: z2.array(z2.string().regex(/^[A-Z][A-Z0-9_]+$/)),
|
|
105
|
+
protocols: z2.array(z2.object({
|
|
60
106
|
protocol: protocolSchema,
|
|
61
107
|
baseUrl: urlSchema.optional(),
|
|
62
|
-
authStyle:
|
|
108
|
+
authStyle: z2.enum(["bearer", "x-api-key", "api-key"]),
|
|
63
109
|
catalogBaseUrl: urlSchema.optional(),
|
|
64
|
-
catalogFormat:
|
|
65
|
-
catalogAuthStyle:
|
|
66
|
-
modelsPath:
|
|
67
|
-
notes:
|
|
110
|
+
catalogFormat: z2.enum(["openai", "ollama", "mistral", "together", "fireworks", "dashscope", "gemini", "none"]),
|
|
111
|
+
catalogAuthStyle: z2.enum(["bearer", "x-api-key", "api-key", "none"]).optional(),
|
|
112
|
+
modelsPath: z2.string(),
|
|
113
|
+
notes: z2.array(z2.string())
|
|
68
114
|
}).strict()).min(1),
|
|
69
|
-
sources:
|
|
70
|
-
verification:
|
|
115
|
+
sources: z2.array(z2.string().url()),
|
|
116
|
+
verification: z2.literal("documented")
|
|
71
117
|
}).strict();
|
|
72
|
-
var profileInputSchema =
|
|
118
|
+
var profileInputSchema = z2.object({
|
|
73
119
|
id: idSchema,
|
|
74
120
|
name: label,
|
|
75
121
|
providerId: idSchema,
|
|
76
122
|
harness: harnessSchema,
|
|
77
|
-
model:
|
|
123
|
+
model: z2.string().min(1).max(300),
|
|
124
|
+
modelPolicy: modelPolicySchema.optional()
|
|
78
125
|
}).strict();
|
|
79
|
-
var runInputSchema =
|
|
126
|
+
var runInputSchema = z2.object({
|
|
127
|
+
modelPolicyVersion: z2.literal(1),
|
|
80
128
|
profileId: idSchema,
|
|
81
129
|
harness: harnessSchema,
|
|
82
|
-
model:
|
|
83
|
-
|
|
130
|
+
model: z2.string().min(1).max(300),
|
|
131
|
+
modelPolicy: modelPolicySchema.optional(),
|
|
132
|
+
planToken: z2.string().regex(/^[a-f0-9]{64}$/)
|
|
84
133
|
}).strict();
|
|
85
|
-
var runUpdateSchema =
|
|
86
|
-
status:
|
|
87
|
-
exitCode:
|
|
134
|
+
var runUpdateSchema = z2.object({
|
|
135
|
+
status: z2.enum(["exited", "failed", "interrupted"]),
|
|
136
|
+
exitCode: z2.number().int().min(0).max(255),
|
|
137
|
+
routingEvents: routingEventsSchema.optional(),
|
|
138
|
+
routingEventsDropped: z2.number().int().min(0).max(1e6).optional()
|
|
88
139
|
}).strict();
|
|
89
140
|
|
|
90
141
|
class Fault extends Error {
|
|
@@ -149,7 +200,7 @@ async function boundedJson(response, maxBytes = MAX_BYTES) {
|
|
|
149
200
|
}
|
|
150
201
|
|
|
151
202
|
// src/sdk.ts
|
|
152
|
-
import {
|
|
203
|
+
import { createClientTransport } from "@hasna/contracts/client";
|
|
153
204
|
|
|
154
205
|
// src/presets.ts
|
|
155
206
|
var route = (protocol, baseUrl, options = {}) => ({
|
|
@@ -310,6 +361,7 @@ function apiError(status, data, apiKey) {
|
|
|
310
361
|
|
|
311
362
|
class SwitcherClient {
|
|
312
363
|
options;
|
|
364
|
+
transport;
|
|
313
365
|
baseUrl;
|
|
314
366
|
constructor(options) {
|
|
315
367
|
this.baseUrl = endpoint(options.baseUrl).replace(/\/v1$/, "");
|
|
@@ -317,9 +369,50 @@ class SwitcherClient {
|
|
|
317
369
|
throw new Error("Switcher API key is required.");
|
|
318
370
|
this.options = { ...options };
|
|
319
371
|
}
|
|
372
|
+
static fromEnvironment(env = process.env, options = {}) {
|
|
373
|
+
const shared = createClientTransport("switcher", env, {
|
|
374
|
+
credentials: options.credentials,
|
|
375
|
+
timeoutMs: options.timeoutMs ?? 120000,
|
|
376
|
+
retry: false,
|
|
377
|
+
fetchImpl: async (input, init) => {
|
|
378
|
+
const address = String(input).replace(/\/v1\/(health|ready|version)$/, "/$1");
|
|
379
|
+
const key = new Headers(init?.headers).get("x-api-key") ?? "";
|
|
380
|
+
let response;
|
|
381
|
+
try {
|
|
382
|
+
response = await (options.fetch ?? fetch)(address, init);
|
|
383
|
+
} catch {
|
|
384
|
+
throw new SwitcherError(0, "connection_failed", "Switcher API request failed; check endpoint and service availability.");
|
|
385
|
+
}
|
|
386
|
+
if (response.status === 401 || response.status === 403) {
|
|
387
|
+
await response.body?.cancel().catch(() => {});
|
|
388
|
+
throw new SwitcherError(response.status, "api_error", `Switcher API returned HTTP ${response.status}. Check the configured credential; no alternate identity was selected.`);
|
|
389
|
+
}
|
|
390
|
+
let data;
|
|
391
|
+
try {
|
|
392
|
+
data = await boundedJson(response);
|
|
393
|
+
} catch {
|
|
394
|
+
throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
|
|
395
|
+
}
|
|
396
|
+
if (!response.ok)
|
|
397
|
+
throw apiError(response.status, data, key);
|
|
398
|
+
return Response.json(data);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
const client = new SwitcherClient({ baseUrl: shared.resolution.baseUrl, apiKey: () => {
|
|
402
|
+
throw new Error("Shared credential transport was not invoked.");
|
|
403
|
+
} });
|
|
404
|
+
client.transport = shared.client;
|
|
405
|
+
return client;
|
|
406
|
+
}
|
|
320
407
|
async request(method, path, body, options = {}) {
|
|
321
408
|
if (!/^\/v1\/[a-zA-Z0-9/?&=._%+-]+$/.test(path) && !["/health", "/ready", "/version"].includes(path) || path.includes(".."))
|
|
322
409
|
throw new Error("Invalid API path.");
|
|
410
|
+
if (this.transport)
|
|
411
|
+
return this.transport.request(method, path.startsWith("/v1/") ? path.slice(3) : path, body, {
|
|
412
|
+
idempotencyKey: method === "GET" ? undefined : options.idempotencyKey ?? crypto.randomUUID(),
|
|
413
|
+
headers: options.version === undefined ? undefined : { "if-match": String(options.version) },
|
|
414
|
+
retry: false
|
|
415
|
+
});
|
|
323
416
|
const apiKey = typeof this.options.apiKey === "function" ? this.options.apiKey() : this.options.apiKey;
|
|
324
417
|
if (!apiKey || /[\r\n]/.test(apiKey))
|
|
325
418
|
throw new Error("Switcher API key is required.");
|
|
@@ -410,17 +503,14 @@ class SwitcherClient {
|
|
|
410
503
|
return this.request("GET", `/v1/runs/${encodeURIComponent(id)}`);
|
|
411
504
|
}
|
|
412
505
|
createRun(input, idempotencyKey) {
|
|
413
|
-
return this.request("POST", "/v1/runs", input, { idempotencyKey });
|
|
506
|
+
return this.request("POST", "/v1/runs", { ...input, modelPolicyVersion: 1 }, { idempotencyKey });
|
|
414
507
|
}
|
|
415
508
|
finishRun(id, version, input, idempotencyKey) {
|
|
416
509
|
return this.request("PATCH", `/v1/runs/${encodeURIComponent(id)}`, input, { version, idempotencyKey });
|
|
417
510
|
}
|
|
418
511
|
}
|
|
419
|
-
function clientFromEnv(env = process.env) {
|
|
420
|
-
|
|
421
|
-
if (!env.HASNA_SWITCHER_API_URL || !credential())
|
|
422
|
-
throw new Error("Set HASNA_SWITCHER_API_URL and HASNA_SWITCHER_API_KEY; no local database fallback is available.");
|
|
423
|
-
return new SwitcherClient({ baseUrl: env.HASNA_SWITCHER_API_URL, apiKey: credential });
|
|
512
|
+
function clientFromEnv(env = process.env, options = {}) {
|
|
513
|
+
return SwitcherClient.fromEnvironment(env, options);
|
|
424
514
|
}
|
|
425
515
|
export {
|
|
426
516
|
providerFromPreset,
|