@kindgi/api 0.1.3 → 0.1.4-rc.1
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/dist/agent-binding.d.ts +26 -4
- package/dist/agent-binding.d.ts.map +1 -1
- package/dist/agent-pins.d.ts +48 -0
- package/dist/agent-pins.d.ts.map +1 -0
- package/dist/agent-pins.js +102 -0
- package/dist/agent-pins.js.map +1 -0
- package/dist/app.d.ts +22 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +27 -3
- package/dist/app.js.map +1 -1
- package/dist/block-binding.d.ts +132 -0
- package/dist/block-binding.d.ts.map +1 -0
- package/dist/block-binding.js +4 -0
- package/dist/block-binding.js.map +1 -0
- package/dist/block-pins.d.ts +22 -0
- package/dist/block-pins.d.ts.map +1 -0
- package/dist/block-pins.js +112 -0
- package/dist/block-pins.js.map +1 -0
- package/dist/cost-binding.d.ts +20 -1
- package/dist/cost-binding.d.ts.map +1 -1
- package/dist/cost-binding.js +4 -0
- package/dist/cost-binding.js.map +1 -1
- package/dist/deploy-versions.d.ts +60 -0
- package/dist/deploy-versions.d.ts.map +1 -0
- package/dist/deploy-versions.js +91 -0
- package/dist/deploy-versions.js.map +1 -0
- package/dist/deployment-binding.d.ts +24 -3
- package/dist/deployment-binding.d.ts.map +1 -1
- package/dist/derive-agent-version.d.ts +77 -0
- package/dist/derive-agent-version.d.ts.map +1 -0
- package/dist/derive-agent-version.js +149 -0
- package/dist/derive-agent-version.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -1
- package/dist/eval-case-binding.d.ts +65 -0
- package/dist/eval-case-binding.d.ts.map +1 -0
- package/dist/eval-case-binding.js +4 -0
- package/dist/eval-case-binding.js.map +1 -0
- package/dist/eval-run-binding.d.ts +37 -0
- package/dist/eval-run-binding.d.ts.map +1 -1
- package/dist/eval-run-binding.js.map +1 -1
- package/dist/eval-run-dispatcher.d.ts +41 -3
- package/dist/eval-run-dispatcher.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.js +21 -15
- package/dist/eval-run-dispatcher.js.map +1 -1
- package/dist/eval-suite-binding.d.ts +1 -1
- package/dist/eval-suite-binding.d.ts.map +1 -1
- package/dist/eval-suite-binding.js +2 -0
- package/dist/eval-suite-binding.js.map +1 -1
- package/dist/flow-binding.d.ts +18 -4
- package/dist/flow-binding.d.ts.map +1 -1
- package/dist/flow-pins.d.ts +36 -0
- package/dist/flow-pins.d.ts.map +1 -0
- package/dist/flow-pins.js +81 -0
- package/dist/flow-pins.js.map +1 -0
- package/dist/guardrail-binding.d.ts +8 -0
- package/dist/guardrail-binding.d.ts.map +1 -1
- package/dist/handler-binding.d.ts +3 -0
- package/dist/handler-binding.d.ts.map +1 -1
- package/dist/index.d.ts +18 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/judged-dispatcher.d.ts +138 -0
- package/dist/judged-dispatcher.d.ts.map +1 -0
- package/dist/judged-dispatcher.js +308 -0
- package/dist/judged-dispatcher.js.map +1 -0
- package/dist/judged-items.d.ts +86 -0
- package/dist/judged-items.d.ts.map +1 -0
- package/dist/judged-items.js +184 -0
- package/dist/judged-items.js.map +1 -0
- package/dist/judgment-binding.d.ts +316 -0
- package/dist/judgment-binding.d.ts.map +1 -0
- package/dist/judgment-binding.js +19 -0
- package/dist/judgment-binding.js.map +1 -0
- package/dist/openapi/generate.d.ts.map +1 -1
- package/dist/openapi/generate.js +4 -1
- package/dist/openapi/generate.js.map +1 -1
- package/dist/openapi/operations.d.ts.map +1 -1
- package/dist/openapi/operations.js +482 -13
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +46 -0
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +1350 -175
- package/dist/openapi/schemas.js.map +1 -1
- package/dist/provider-binding.d.ts +12 -7
- package/dist/provider-binding.d.ts.map +1 -1
- package/dist/registry-read-only.d.ts +32 -0
- package/dist/registry-read-only.d.ts.map +1 -0
- package/dist/registry-read-only.js +22 -0
- package/dist/registry-read-only.js.map +1 -0
- package/dist/routes/agents.d.ts +9 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +181 -11
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/blocks.d.ts +19 -0
- package/dist/routes/blocks.d.ts.map +1 -0
- package/dist/routes/blocks.js +306 -0
- package/dist/routes/blocks.js.map +1 -0
- package/dist/routes/cost.d.ts.map +1 -1
- package/dist/routes/cost.js +47 -2
- package/dist/routes/cost.js.map +1 -1
- package/dist/routes/deployments.d.ts +3 -0
- package/dist/routes/deployments.d.ts.map +1 -1
- package/dist/routes/deployments.js +224 -55
- package/dist/routes/deployments.js.map +1 -1
- package/dist/routes/eval-comparison.d.ts +15 -0
- package/dist/routes/eval-comparison.d.ts.map +1 -0
- package/dist/routes/eval-comparison.js +123 -0
- package/dist/routes/eval-comparison.js.map +1 -0
- package/dist/routes/eval-runs.d.ts +7 -1
- package/dist/routes/eval-runs.d.ts.map +1 -1
- package/dist/routes/eval-runs.js +42 -3
- package/dist/routes/eval-runs.js.map +1 -1
- package/dist/routes/eval-versions.d.ts +25 -0
- package/dist/routes/eval-versions.d.ts.map +1 -0
- package/dist/routes/eval-versions.js +66 -0
- package/dist/routes/eval-versions.js.map +1 -0
- package/dist/routes/flows.d.ts +13 -1
- package/dist/routes/flows.d.ts.map +1 -1
- package/dist/routes/flows.js +48 -3
- package/dist/routes/flows.js.map +1 -1
- package/dist/routes/guardrails.d.ts.map +1 -1
- package/dist/routes/guardrails.js +4 -0
- package/dist/routes/guardrails.js.map +1 -1
- package/dist/routes/hierarchy-errors.d.ts +45 -0
- package/dist/routes/hierarchy-errors.d.ts.map +1 -0
- package/dist/routes/hierarchy-errors.js +47 -0
- package/dist/routes/hierarchy-errors.js.map +1 -0
- package/dist/routes/judged-suites.d.ts +20 -0
- package/dist/routes/judged-suites.d.ts.map +1 -0
- package/dist/routes/judged-suites.js +272 -0
- package/dist/routes/judged-suites.js.map +1 -0
- package/dist/routes/judgment-context.d.ts +22 -0
- package/dist/routes/judgment-context.d.ts.map +1 -0
- package/dist/routes/judgment-context.js +88 -0
- package/dist/routes/judgment-context.js.map +1 -0
- package/dist/routes/judgment-flow-context.d.ts +32 -0
- package/dist/routes/judgment-flow-context.d.ts.map +1 -0
- package/dist/routes/judgment-flow-context.js +195 -0
- package/dist/routes/judgment-flow-context.js.map +1 -0
- package/dist/routes/judgments.d.ts +41 -0
- package/dist/routes/judgments.d.ts.map +1 -0
- package/dist/routes/judgments.js +566 -0
- package/dist/routes/judgments.js.map +1 -0
- package/dist/routes/orgs.d.ts +5 -2
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +38 -22
- package/dist/routes/orgs.js.map +1 -1
- package/dist/routes/policies.d.ts.map +1 -1
- package/dist/routes/policies.js +12 -1
- package/dist/routes/policies.js.map +1 -1
- package/dist/routes/projects.d.ts +10 -2
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +94 -80
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/providers.d.ts.map +1 -1
- package/dist/routes/providers.js +6 -1
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/runs.js +25 -3
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/teams.d.ts +6 -2
- package/dist/routes/teams.d.ts.map +1 -1
- package/dist/routes/teams.js +83 -73
- package/dist/routes/teams.js.map +1 -1
- package/dist/routes/tools.d.ts.map +1 -1
- package/dist/routes/tools.js +4 -0
- package/dist/routes/tools.js.map +1 -1
- package/dist/tool-binding.d.ts +8 -0
- package/dist/tool-binding.d.ts.map +1 -1
- package/openapi.json +14253 -10099
- package/package.json +21 -21
- package/src/agent-binding.ts +28 -4
- package/src/agent-pins.ts +147 -0
- package/src/app.ts +81 -3
- package/src/block-binding.ts +137 -0
- package/src/block-pins.ts +148 -0
- package/src/cost-binding.ts +21 -1
- package/src/deploy-versions.ts +157 -0
- package/src/deployment-binding.ts +27 -3
- package/src/derive-agent-version.ts +217 -0
- package/src/errors.ts +18 -0
- package/src/eval-case-binding.ts +71 -0
- package/src/eval-run-binding.ts +40 -0
- package/src/eval-run-dispatcher.ts +60 -16
- package/src/eval-suite-binding.ts +2 -0
- package/src/flow-binding.ts +20 -4
- package/src/flow-pins.ts +113 -0
- package/src/guardrail-binding.ts +9 -0
- package/src/handler-binding.ts +3 -0
- package/src/index.ts +86 -2
- package/src/judged-dispatcher.ts +530 -0
- package/src/judged-items.ts +263 -0
- package/src/judgment-binding.ts +349 -0
- package/src/openapi/generate.ts +7 -1
- package/src/openapi/operations.ts +579 -13
- package/src/openapi/schemas.ts +1408 -128
- package/src/provider-binding.ts +12 -7
- package/src/registry-read-only.ts +43 -0
- package/src/routes/agents.ts +252 -19
- package/src/routes/blocks.ts +387 -0
- package/src/routes/cost.ts +55 -1
- package/src/routes/deployments.ts +291 -56
- package/src/routes/eval-comparison.ts +135 -0
- package/src/routes/eval-runs.ts +57 -3
- package/src/routes/eval-versions.ts +110 -0
- package/src/routes/flows.ts +70 -5
- package/src/routes/guardrails.ts +7 -0
- package/src/routes/hierarchy-errors.ts +60 -0
- package/src/routes/judged-suites.ts +363 -0
- package/src/routes/judgment-context.ts +128 -0
- package/src/routes/judgment-flow-context.ts +245 -0
- package/src/routes/judgments.ts +743 -0
- package/src/routes/orgs.ts +44 -27
- package/src/routes/policies.ts +19 -0
- package/src/routes/projects.ts +118 -96
- package/src/routes/providers.ts +5 -0
- package/src/routes/runs.ts +29 -3
- package/src/routes/teams.ts +104 -90
- package/src/routes/tools.ts +7 -0
- package/src/tool-binding.ts +9 -0
|
@@ -5,9 +5,10 @@ import { createHash } from 'node:crypto';
|
|
|
5
5
|
|
|
6
6
|
import { Hono } from 'hono';
|
|
7
7
|
|
|
8
|
-
import type { AgentId } from '@kindgi/agents';
|
|
8
|
+
import type { Agent, AgentId, AgentPins } from '@kindgi/agents';
|
|
9
9
|
import { tuplesForCreate } from '@kindgi/authz';
|
|
10
10
|
import { parsePublicKeyPem, verifyEd25519 } from '@kindgi/crypto';
|
|
11
|
+
import type { Flow, FlowPins } from '@kindgi/flow';
|
|
11
12
|
import { type Guardrail, validateGuardrailSpec } from '@kindgi/guardrails';
|
|
12
13
|
import type { ProjectBinding, Scope } from '@kindgi/platform';
|
|
13
14
|
import { validateToolManifest } from '@kindgi/tools';
|
|
@@ -16,13 +17,20 @@ import type {
|
|
|
16
17
|
FlowId,
|
|
17
18
|
GuardrailId,
|
|
18
19
|
ProjectId,
|
|
20
|
+
Semver,
|
|
19
21
|
SigningKeyId,
|
|
20
22
|
TenantId,
|
|
21
23
|
ToolId,
|
|
22
24
|
} from '@kindgi/types';
|
|
23
25
|
|
|
24
26
|
import type { AgentRegistryBinding } from '../agent-binding.js';
|
|
27
|
+
import { type UnpinnableRef, publishDeployedAgent, resolveAgentPins } from '../agent-pins.js';
|
|
28
|
+
import type { BlockRegistryBinding } from '../block-binding.js';
|
|
29
|
+
import type { DeployedVersionOutcome } from '../deploy-versions.js';
|
|
25
30
|
import type {
|
|
31
|
+
DeployedAgent,
|
|
32
|
+
DeployedFlow,
|
|
33
|
+
DeployedVersion,
|
|
26
34
|
Deployment,
|
|
27
35
|
DeploymentBinding,
|
|
28
36
|
DeploymentPrimitiveCounts,
|
|
@@ -30,8 +38,10 @@ import type {
|
|
|
30
38
|
} from '../deployment-binding.js';
|
|
31
39
|
import { statusFor, toWireError } from '../errors.js';
|
|
32
40
|
import type { FlowRegistryBinding } from '../flow-binding.js';
|
|
41
|
+
import { publishDeployedFlow, resolveFlowPins } from '../flow-pins.js';
|
|
33
42
|
import type { GuardrailRegistryBinding } from '../guardrail-binding.js';
|
|
34
43
|
import type { ImageRegistryBinding } from '../image-registry-binding.js';
|
|
44
|
+
import { type RegistryReadOnly, refuseReadOnly } from '../registry-read-only.js';
|
|
35
45
|
import type { SecretBinding } from '../secrets-binding.js';
|
|
36
46
|
import type { SigningKeyBinding as SigningKeyRegistryBinding } from '../signing-key-binding.js';
|
|
37
47
|
import type { ToolRegistryBinding } from '../tool-binding.js';
|
|
@@ -112,6 +122,8 @@ export interface DeploymentsRouterBindings {
|
|
|
112
122
|
readonly guardrailRegistry?: GuardrailRegistryBinding;
|
|
113
123
|
readonly agentRegistry?: AgentRegistryBinding;
|
|
114
124
|
readonly flowRegistry?: FlowRegistryBinding;
|
|
125
|
+
/** Data blocks: an agent's block references are pinned at deploy with its tools. */
|
|
126
|
+
readonly blockRegistry?: BlockRegistryBinding;
|
|
115
127
|
/**
|
|
116
128
|
* REQUIRED at runtime for `POST /v1/deployments/:deploymentId/secrets`.
|
|
117
129
|
* Optional at the type level so app compositions without a secrets
|
|
@@ -476,6 +488,11 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
476
488
|
}
|
|
477
489
|
const validated = validation.value;
|
|
478
490
|
|
|
491
|
+
// A read-only registry (under `kindgi dev`, the pack's files) takes
|
|
492
|
+
// nothing a deployment brings: refuse before any write.
|
|
493
|
+
const readOnly = readOnlyTarget(validated, bindings);
|
|
494
|
+
if (readOnly !== undefined) return refuseReadOnly(c, readOnly);
|
|
495
|
+
|
|
479
496
|
// ---- Registry upserts (with rollback tracking) ----
|
|
480
497
|
//
|
|
481
498
|
// Agent-registry, flow-registry, tool-registry, and
|
|
@@ -529,6 +546,10 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
529
546
|
}
|
|
530
547
|
|
|
531
548
|
const rolled: RollbackAction[] = [];
|
|
549
|
+
// The version each agent is registered under: its definition's, or
|
|
550
|
+
// the one a deploy registered in its place (`publishDeployedAgent`).
|
|
551
|
+
const deployedAgents: DeployedAgent[] = [];
|
|
552
|
+
const deployedFlows: DeployedFlow[] = [];
|
|
532
553
|
try {
|
|
533
554
|
if (bindings.toolRegistry !== undefined && defaultProjectId !== undefined) {
|
|
534
555
|
for (const tool of validated.tools) {
|
|
@@ -578,58 +599,50 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
578
599
|
}
|
|
579
600
|
}
|
|
580
601
|
if (bindings.agentRegistry !== undefined && defaultProjectId !== undefined) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
602
|
+
deployedAgents.push(
|
|
603
|
+
...(await registerAgents({
|
|
604
|
+
agents: bindings.agentRegistry,
|
|
605
|
+
tools: bindings.toolRegistry,
|
|
606
|
+
blocks: bindings.blockRegistry,
|
|
584
607
|
tenantId,
|
|
585
|
-
projectId:
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
kind: 'agent',
|
|
593
|
-
id: agentId as AgentId,
|
|
594
|
-
tenantId,
|
|
595
|
-
projectId: projectIdForAgent,
|
|
596
|
-
}),
|
|
597
|
-
});
|
|
598
|
-
if (outcome.kind === 'ok') {
|
|
599
|
-
const agentId = outcome.agentId;
|
|
600
|
-
const version = outcome.version;
|
|
601
|
-
rolled.push(async () => {
|
|
602
|
-
await bindings.agentRegistry?.unregister({ tenantId, agentId, version });
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
}
|
|
608
|
+
projectId: defaultProjectId,
|
|
609
|
+
defined: validated.agents,
|
|
610
|
+
rolled,
|
|
611
|
+
})),
|
|
612
|
+
);
|
|
613
|
+
} else {
|
|
614
|
+
deployedAgents.push(...validated.agents.map(deployedPrimitive));
|
|
606
615
|
}
|
|
607
616
|
if (bindings.flowRegistry !== undefined && defaultProjectId !== undefined) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
617
|
+
deployedFlows.push(
|
|
618
|
+
...(await registerFlows({
|
|
619
|
+
flows: bindings.flowRegistry,
|
|
620
|
+
tools: bindings.toolRegistry,
|
|
621
|
+
agents: bindings.agentRegistry,
|
|
611
622
|
tenantId,
|
|
612
|
-
projectId:
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
projectId: projectIdForGraph,
|
|
620
|
-
}),
|
|
621
|
-
});
|
|
622
|
-
if (outcome.kind === 'ok') {
|
|
623
|
-
const flowId = outcome.flowId;
|
|
624
|
-
const version = outcome.version;
|
|
625
|
-
rolled.push(async () => {
|
|
626
|
-
await bindings.flowRegistry?.unregister({ tenantId, flowId, version });
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
}
|
|
623
|
+
projectId: defaultProjectId,
|
|
624
|
+
defined: validated.flows,
|
|
625
|
+
rolled,
|
|
626
|
+
})),
|
|
627
|
+
);
|
|
628
|
+
} else {
|
|
629
|
+
deployedFlows.push(...validated.flows.map(deployedPrimitive));
|
|
630
630
|
}
|
|
631
631
|
} catch (cause) {
|
|
632
632
|
await rollback(rolled);
|
|
633
|
+
if (cause instanceof UnpinnableDeploy) {
|
|
634
|
+
c.status(statusFor('invalid-agent') as never);
|
|
635
|
+
return c.json(
|
|
636
|
+
toWireError(
|
|
637
|
+
{
|
|
638
|
+
code: 'validation-failed',
|
|
639
|
+
message: `The deployment's agents or flows use tool or agent versions that aren't published (${cause.issues.length} issue${cause.issues.length === 1 ? '' : 's'}); nothing was deployed`,
|
|
640
|
+
issues: cause.issues as unknown as Record<string, unknown>[],
|
|
641
|
+
},
|
|
642
|
+
requestId,
|
|
643
|
+
),
|
|
644
|
+
);
|
|
645
|
+
}
|
|
633
646
|
c.status(statusFor('internal-server-error') as never);
|
|
634
647
|
return c.json(
|
|
635
648
|
toWireError(
|
|
@@ -669,14 +682,8 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
669
682
|
version: t.version,
|
|
670
683
|
})),
|
|
671
684
|
guardrails: validated.guardrails.map((g) => ({ id: g.id as unknown as string })),
|
|
672
|
-
agents:
|
|
673
|
-
|
|
674
|
-
version: a.version as unknown as string,
|
|
675
|
-
})),
|
|
676
|
-
flows: validated.flows.map((f) => ({
|
|
677
|
-
id: f.id as unknown as string,
|
|
678
|
-
version: f.version as unknown as string,
|
|
679
|
-
})),
|
|
685
|
+
agents: deployedAgents,
|
|
686
|
+
flows: deployedFlows,
|
|
680
687
|
},
|
|
681
688
|
});
|
|
682
689
|
} catch (cause) {
|
|
@@ -1374,8 +1381,18 @@ function validateAgentIndexShape(
|
|
|
1374
1381
|
if (typeof spec.name !== 'string' || (spec.name as string).length === 0) {
|
|
1375
1382
|
out.push({ path: '/name', message: 'agent.name must be a non-empty string' });
|
|
1376
1383
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1384
|
+
const instructions = spec.instructions as { prompt?: unknown; version?: unknown } | string;
|
|
1385
|
+
const promptRef =
|
|
1386
|
+
typeof instructions === 'object' &&
|
|
1387
|
+
instructions !== null &&
|
|
1388
|
+
typeof instructions.prompt === 'string' &&
|
|
1389
|
+
typeof instructions.version === 'string';
|
|
1390
|
+
if (!promptRef && (typeof instructions !== 'string' || instructions.length === 0)) {
|
|
1391
|
+
out.push({
|
|
1392
|
+
path: '/instructions',
|
|
1393
|
+
message:
|
|
1394
|
+
'agent.instructions must be a non-empty string, or a prompt block { prompt, version }',
|
|
1395
|
+
});
|
|
1379
1396
|
}
|
|
1380
1397
|
if (!Array.isArray(spec.capabilities)) {
|
|
1381
1398
|
out.push({ path: '/capabilities', message: 'agent.capabilities must be an array' });
|
|
@@ -1411,6 +1428,205 @@ function validateFlowIndexShape(
|
|
|
1411
1428
|
|
|
1412
1429
|
type RollbackAction = () => Promise<void>;
|
|
1413
1430
|
|
|
1431
|
+
/** A deploy whose agents or flows use a tool or agent with no published version in range. */
|
|
1432
|
+
class UnpinnableDeploy extends Error {
|
|
1433
|
+
constructor(readonly issues: readonly UnpinnableRef[]) {
|
|
1434
|
+
super('the deployment uses tools or agents that are not published');
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/** A deployed agent or flow registered under its definition's version. */
|
|
1439
|
+
function deployedPrimitive(definition: { readonly id: string; readonly version: string }): {
|
|
1440
|
+
id: string;
|
|
1441
|
+
version: string;
|
|
1442
|
+
} {
|
|
1443
|
+
return { id: definition.id, version: definition.version };
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/** A deployed agent or flow, under the version the deploy rule registered it as. */
|
|
1447
|
+
function deployedVersion(
|
|
1448
|
+
definition: { readonly id: string; readonly version: string },
|
|
1449
|
+
outcome: DeployedVersionOutcome,
|
|
1450
|
+
): DeployedVersion {
|
|
1451
|
+
return outcome.kind === 'reused' || outcome.kind === 'renumbered'
|
|
1452
|
+
? {
|
|
1453
|
+
id: definition.id,
|
|
1454
|
+
version: outcome.version,
|
|
1455
|
+
authoredVersion: definition.version,
|
|
1456
|
+
reason: outcome.reason,
|
|
1457
|
+
newVersion: outcome.kind === 'renumbered',
|
|
1458
|
+
...(outcome.pinChanges !== undefined && { pinChanges: outcome.pinChanges }),
|
|
1459
|
+
}
|
|
1460
|
+
: deployedPrimitive(definition);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* Register a deployment's agents, each under the version it's registered
|
|
1465
|
+
* as (`DeployedAgent`), pushing a rollback for each version it writes.
|
|
1466
|
+
*
|
|
1467
|
+
* With a tool registry, every agent is pinned first (the pack's tools
|
|
1468
|
+
* are registered by now): one whose range matches no published version
|
|
1469
|
+
* throws `UnpinnableDeploy` before any agent is written, and the deploy
|
|
1470
|
+
* rolls back. Each is then registered by `publishDeployedAgent`, which
|
|
1471
|
+
* never keeps a version's old pins. Without one, agents register
|
|
1472
|
+
* unpinned, as before pins existed.
|
|
1473
|
+
*/
|
|
1474
|
+
async function registerAgents(input: {
|
|
1475
|
+
readonly agents: AgentRegistryBinding;
|
|
1476
|
+
readonly tools: ToolRegistryBinding | undefined;
|
|
1477
|
+
readonly blocks: BlockRegistryBinding | undefined;
|
|
1478
|
+
readonly tenantId: TenantId;
|
|
1479
|
+
readonly projectId: ProjectId;
|
|
1480
|
+
readonly defined: readonly Agent[];
|
|
1481
|
+
readonly rolled: RollbackAction[];
|
|
1482
|
+
}): Promise<DeployedAgent[]> {
|
|
1483
|
+
const { agents, tools, blocks, tenantId, projectId, rolled } = input;
|
|
1484
|
+
// Signed deploys have no per-request principal — pass parent-only
|
|
1485
|
+
// tuples (no owner grant). Tenant admins keep access via
|
|
1486
|
+
// `admin from parent` cascade.
|
|
1487
|
+
const enqueueTuples = (agentId: string) =>
|
|
1488
|
+
tuplesForCreate({ kind: 'agent', id: agentId as AgentId, tenantId, projectId });
|
|
1489
|
+
const written = (agentId: AgentId, version: Semver) =>
|
|
1490
|
+
rolled.push(async () => {
|
|
1491
|
+
await agents.unregister({ tenantId, agentId, version });
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
if (tools === undefined) {
|
|
1495
|
+
for (const agent of input.defined) {
|
|
1496
|
+
const outcome = await agents.publish({ tenantId, projectId, agent, enqueueTuples });
|
|
1497
|
+
if (outcome.kind === 'ok') written(outcome.agentId, outcome.version);
|
|
1498
|
+
}
|
|
1499
|
+
return input.defined.map(deployedPrimitive);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const pinsOf = await pinAgents(tools, blocks, tenantId, input.defined);
|
|
1503
|
+
const deployed: DeployedAgent[] = [];
|
|
1504
|
+
for (const [i, agent] of input.defined.entries()) {
|
|
1505
|
+
const pins = pinsOf[i] as AgentPins;
|
|
1506
|
+
const outcome = await publishDeployedAgent({
|
|
1507
|
+
agents,
|
|
1508
|
+
tenantId,
|
|
1509
|
+
projectId,
|
|
1510
|
+
agent,
|
|
1511
|
+
pins,
|
|
1512
|
+
enqueueTuples,
|
|
1513
|
+
});
|
|
1514
|
+
if (outcome.kind === 'registered' || outcome.kind === 'renumbered') {
|
|
1515
|
+
written(agent.id, outcome.version as unknown as Semver);
|
|
1516
|
+
}
|
|
1517
|
+
deployed.push(deployedVersion(agent, outcome));
|
|
1518
|
+
}
|
|
1519
|
+
return deployed;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
/**
|
|
1523
|
+
* Register a deployment's flows, each under the version it's registered
|
|
1524
|
+
* as, pushing a rollback for each version it writes. Runs after the
|
|
1525
|
+
* agents, so a flow's agent pins see the versions this deploy
|
|
1526
|
+
* registered: a tool change cascades through an agent into a flow
|
|
1527
|
+
* within the one deploy, each derived once.
|
|
1528
|
+
*
|
|
1529
|
+
* With the tool and agent registries, every flow is pinned first; one
|
|
1530
|
+
* that runs a tool or agent with no published version throws
|
|
1531
|
+
* `UnpinnableDeploy` before any flow is written, and the deploy rolls
|
|
1532
|
+
* back. Without them, flows register unpinned, as before pins existed.
|
|
1533
|
+
*/
|
|
1534
|
+
async function registerFlows(input: {
|
|
1535
|
+
readonly flows: FlowRegistryBinding;
|
|
1536
|
+
readonly tools: ToolRegistryBinding | undefined;
|
|
1537
|
+
readonly agents: AgentRegistryBinding | undefined;
|
|
1538
|
+
readonly tenantId: TenantId;
|
|
1539
|
+
readonly projectId: ProjectId;
|
|
1540
|
+
readonly defined: readonly Flow[];
|
|
1541
|
+
readonly rolled: RollbackAction[];
|
|
1542
|
+
}): Promise<DeployedFlow[]> {
|
|
1543
|
+
const { flows, tools, agents, tenantId, projectId, rolled } = input;
|
|
1544
|
+
const enqueueTuples = (flowId: string) =>
|
|
1545
|
+
tuplesForCreate({ kind: 'flow', id: flowId as FlowId, tenantId, projectId });
|
|
1546
|
+
const written = (flowId: FlowId, version: string) =>
|
|
1547
|
+
rolled.push(async () => {
|
|
1548
|
+
await flows.unregister({ tenantId, flowId, version: version as never });
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
if (tools === undefined || agents === undefined) {
|
|
1552
|
+
for (const flow of input.defined) {
|
|
1553
|
+
const outcome = await flows.publish({ tenantId, projectId, flow, enqueueTuples });
|
|
1554
|
+
if (outcome.kind === 'ok') written(outcome.flowId, outcome.version as unknown as string);
|
|
1555
|
+
}
|
|
1556
|
+
return input.defined.map(deployedPrimitive);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
const pinsOf = await pinFlows(tools, agents, tenantId, input.defined);
|
|
1560
|
+
const deployed: DeployedFlow[] = [];
|
|
1561
|
+
for (const [i, flow] of input.defined.entries()) {
|
|
1562
|
+
const outcome = await publishDeployedFlow({
|
|
1563
|
+
flows,
|
|
1564
|
+
tenantId,
|
|
1565
|
+
projectId,
|
|
1566
|
+
flow,
|
|
1567
|
+
pins: pinsOf[i] as FlowPins,
|
|
1568
|
+
enqueueTuples,
|
|
1569
|
+
});
|
|
1570
|
+
if (outcome.kind === 'registered' || outcome.kind === 'renumbered') {
|
|
1571
|
+
written(flow.id, outcome.version);
|
|
1572
|
+
}
|
|
1573
|
+
deployed.push(deployedVersion(flow, outcome));
|
|
1574
|
+
}
|
|
1575
|
+
return deployed;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/** Each flow's pins, in order; throws `UnpinnableDeploy` naming every tool or agent with no published version. */
|
|
1579
|
+
async function pinFlows(
|
|
1580
|
+
tools: ToolRegistryBinding,
|
|
1581
|
+
agents: AgentRegistryBinding,
|
|
1582
|
+
tenantId: TenantId,
|
|
1583
|
+
defined: readonly Flow[],
|
|
1584
|
+
): Promise<FlowPins[]> {
|
|
1585
|
+
const pins: FlowPins[] = [];
|
|
1586
|
+
const unpinnable: UnpinnableRef[] = [];
|
|
1587
|
+
for (const [i, flow] of defined.entries()) {
|
|
1588
|
+
const resolved = await resolveFlowPins(tools, agents, tenantId, flow);
|
|
1589
|
+
if (resolved.kind === 'ok') {
|
|
1590
|
+
pins.push(resolved.pins);
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
for (const issue of resolved.issues) {
|
|
1594
|
+
unpinnable.push({
|
|
1595
|
+
path: `/flows/${i}${issue.path}`,
|
|
1596
|
+
message: `flow "${flow.id as unknown as string}": ${issue.message}`,
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (unpinnable.length > 0) throw new UnpinnableDeploy(unpinnable);
|
|
1601
|
+
return pins;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
/** Each agent's pins, in order; throws `UnpinnableDeploy` naming every range that matches nothing. */
|
|
1605
|
+
async function pinAgents(
|
|
1606
|
+
tools: ToolRegistryBinding,
|
|
1607
|
+
blocks: BlockRegistryBinding | undefined,
|
|
1608
|
+
tenantId: TenantId,
|
|
1609
|
+
defined: readonly Agent[],
|
|
1610
|
+
): Promise<AgentPins[]> {
|
|
1611
|
+
const pins: AgentPins[] = [];
|
|
1612
|
+
const unpinnable: UnpinnableRef[] = [];
|
|
1613
|
+
for (const [i, agent] of defined.entries()) {
|
|
1614
|
+
const resolved = await resolveAgentPins(tools, tenantId, agent, blocks);
|
|
1615
|
+
if (resolved.kind === 'ok') {
|
|
1616
|
+
pins.push(resolved.pins);
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
for (const issue of resolved.issues) {
|
|
1620
|
+
unpinnable.push({
|
|
1621
|
+
path: `/agents/${i}${issue.path}`,
|
|
1622
|
+
message: `agent "${agent.id as unknown as string}": ${issue.message}`,
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
if (unpinnable.length > 0) throw new UnpinnableDeploy(unpinnable);
|
|
1627
|
+
return pins;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1414
1630
|
async function rollback(actions: readonly RollbackAction[]): Promise<void> {
|
|
1415
1631
|
// Reverse order — last-in, first-out — so registrations are undone in
|
|
1416
1632
|
// the mirror sequence of their creation.
|
|
@@ -1491,3 +1707,22 @@ export function sha256HexPrefixed(bytes: Uint8Array): string {
|
|
|
1491
1707
|
const hex = createHash('sha256').update(bytes).digest('hex');
|
|
1492
1708
|
return `sha256:${hex}`;
|
|
1493
1709
|
}
|
|
1710
|
+
|
|
1711
|
+
/** The first read-only registry a deployment would publish into, if any. */
|
|
1712
|
+
function readOnlyTarget(
|
|
1713
|
+
validated: {
|
|
1714
|
+
readonly tools: readonly unknown[];
|
|
1715
|
+
readonly agents: readonly unknown[];
|
|
1716
|
+
readonly flows: readonly unknown[];
|
|
1717
|
+
readonly guardrails: readonly unknown[];
|
|
1718
|
+
},
|
|
1719
|
+
bindings: DeploymentsRouterBindings,
|
|
1720
|
+
): RegistryReadOnly | undefined {
|
|
1721
|
+
const targets = [
|
|
1722
|
+
[validated.tools, bindings.toolRegistry?.readOnly],
|
|
1723
|
+
[validated.agents, bindings.agentRegistry?.readOnly],
|
|
1724
|
+
[validated.flows, bindings.flowRegistry?.readOnly],
|
|
1725
|
+
[validated.guardrails, bindings.guardrailRegistry?.readOnly],
|
|
1726
|
+
] as const;
|
|
1727
|
+
return targets.find(([brought, readOnly]) => brought.length > 0 && readOnly !== undefined)?.[1];
|
|
1728
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { AgentId } from '@kindgi/agents';
|
|
5
|
+
import type { FlowVersionOverrides } from '@kindgi/flow';
|
|
6
|
+
import type { ProjectId, Semver } from '@kindgi/types';
|
|
7
|
+
|
|
8
|
+
import type { EvalBaseline, EvalComparison } from '../eval-run-binding.js';
|
|
9
|
+
import { DEFAULT_COMPARISON } from '../judged-dispatcher.js';
|
|
10
|
+
|
|
11
|
+
const MAX_REPETITIONS = 10;
|
|
12
|
+
const MAX_K = 100;
|
|
13
|
+
|
|
14
|
+
function obj(value: unknown): Record<string, unknown> | undefined {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
16
|
+
? (value as Record<string, unknown>)
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseLive(raw: unknown): EvalBaseline | string {
|
|
21
|
+
const live = obj(raw);
|
|
22
|
+
if (live === undefined) return '`baseline.live` must be an object';
|
|
23
|
+
const segments = live.segments === undefined ? undefined : obj(live.segments);
|
|
24
|
+
const stringsOnly =
|
|
25
|
+
segments !== undefined && Object.values(segments).every((v) => typeof v === 'string');
|
|
26
|
+
if (live.segments !== undefined && !stringsOnly) {
|
|
27
|
+
return '`baseline.live.segments` must be an object of strings';
|
|
28
|
+
}
|
|
29
|
+
if (live.projectId !== undefined && typeof live.projectId !== 'string') {
|
|
30
|
+
return '`baseline.live.projectId` must be a string';
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
live: {
|
|
34
|
+
...(typeof live.projectId === 'string' && { projectId: live.projectId as ProjectId }),
|
|
35
|
+
...(segments !== undefined && { segments: segments as Record<string, string> }),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseBaseline(raw: unknown): EvalBaseline | string {
|
|
41
|
+
if (raw === 'recorded') return 'recorded';
|
|
42
|
+
const o = obj(raw);
|
|
43
|
+
if (o === undefined) {
|
|
44
|
+
return "`baseline` must be 'recorded', { agentId, version } or { live: { projectId?, segments? } }";
|
|
45
|
+
}
|
|
46
|
+
if (o.live !== undefined) return parseLive(o.live);
|
|
47
|
+
if (typeof o.agentId !== 'string' || o.agentId === '' || typeof o.version !== 'string') {
|
|
48
|
+
return '`baseline` as a version needs `agentId` and `version`';
|
|
49
|
+
}
|
|
50
|
+
return { agentId: o.agentId as AgentId, version: o.version as Semver };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** `{ id: version }` with non-empty strings; an error message otherwise. */
|
|
54
|
+
function versionMap(raw: unknown, name: string): Readonly<Record<string, string>> | string {
|
|
55
|
+
const o = obj(raw);
|
|
56
|
+
const valid =
|
|
57
|
+
o !== undefined &&
|
|
58
|
+
Object.entries(o).every(([id, v]) => id !== '' && typeof v === 'string' && v !== '');
|
|
59
|
+
return valid
|
|
60
|
+
? (o as Record<string, string>)
|
|
61
|
+
: `\`${name}\` must be an object of { id: version } with non-empty strings`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `versions: { agents?, tools? }`; `undefined` when it names nothing. */
|
|
65
|
+
function parseVersions(raw: unknown): FlowVersionOverrides | undefined | string {
|
|
66
|
+
const o = obj(raw);
|
|
67
|
+
if (o === undefined) return '`versions` must be an object: { agents?, tools? }';
|
|
68
|
+
const extra = Object.keys(o).filter((key) => key !== 'agents' && key !== 'tools');
|
|
69
|
+
if (extra.length > 0) return `\`versions\` takes \`agents\` and \`tools\`, not \`${extra[0]}\``;
|
|
70
|
+
const agents = o.agents === undefined ? {} : versionMap(o.agents, 'versions.agents');
|
|
71
|
+
if (typeof agents === 'string') return agents;
|
|
72
|
+
const tools = o.tools === undefined ? {} : versionMap(o.tools, 'versions.tools');
|
|
73
|
+
if (typeof tools === 'string') return tools;
|
|
74
|
+
if (Object.keys(agents).length === 0 && Object.keys(tools).length === 0) return undefined;
|
|
75
|
+
return {
|
|
76
|
+
...(Object.keys(agents).length > 0 && { agents }),
|
|
77
|
+
...(Object.keys(tools).length > 0 && { tools }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function integerIn(raw: unknown, name: string, max: number): number | string {
|
|
82
|
+
return typeof raw === 'number' && Number.isInteger(raw) && raw >= 1 && raw <= max
|
|
83
|
+
? raw
|
|
84
|
+
: `\`${name}\` must be an integer from 1 to ${max}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A comparison eval run's settings from the start body: `baseline`,
|
|
89
|
+
* `reads`, `repetitions`, `k` and a flow candidate's `versions`.
|
|
90
|
+
* `undefined` when none is given (the defaults apply); an error message
|
|
91
|
+
* for a bad one.
|
|
92
|
+
*/
|
|
93
|
+
export function parseComparison(
|
|
94
|
+
b: Readonly<Record<string, unknown>>,
|
|
95
|
+
):
|
|
96
|
+
| { readonly kind: 'ok'; readonly value?: EvalComparison }
|
|
97
|
+
| { readonly kind: 'err'; readonly message: string } {
|
|
98
|
+
const { baseline, reads, repetitions, k } = b;
|
|
99
|
+
const versions = b.versions === undefined ? undefined : parseVersions(b.versions);
|
|
100
|
+
if (typeof versions === 'string') return { kind: 'err', message: versions };
|
|
101
|
+
if (
|
|
102
|
+
baseline === undefined &&
|
|
103
|
+
reads === undefined &&
|
|
104
|
+
repetitions === undefined &&
|
|
105
|
+
k === undefined &&
|
|
106
|
+
versions === undefined
|
|
107
|
+
) {
|
|
108
|
+
return { kind: 'ok' };
|
|
109
|
+
}
|
|
110
|
+
const parsedBaseline =
|
|
111
|
+
baseline === undefined ? DEFAULT_COMPARISON.baseline : parseBaseline(baseline);
|
|
112
|
+
if (typeof parsedBaseline === 'string' && parsedBaseline !== 'recorded') {
|
|
113
|
+
return { kind: 'err', message: parsedBaseline };
|
|
114
|
+
}
|
|
115
|
+
if (reads !== undefined && reads !== 'recorded' && reads !== 'live') {
|
|
116
|
+
return { kind: 'err', message: "`reads` must be 'recorded' or 'live'" };
|
|
117
|
+
}
|
|
118
|
+
const reps =
|
|
119
|
+
repetitions === undefined
|
|
120
|
+
? DEFAULT_COMPARISON.repetitions
|
|
121
|
+
: integerIn(repetitions, 'repetitions', MAX_REPETITIONS);
|
|
122
|
+
if (typeof reps === 'string') return { kind: 'err', message: reps };
|
|
123
|
+
const topK = k === undefined ? DEFAULT_COMPARISON.k : integerIn(k, 'k', MAX_K);
|
|
124
|
+
if (typeof topK === 'string') return { kind: 'err', message: topK };
|
|
125
|
+
return {
|
|
126
|
+
kind: 'ok',
|
|
127
|
+
value: {
|
|
128
|
+
baseline: parsedBaseline,
|
|
129
|
+
reads: reads ?? DEFAULT_COMPARISON.reads,
|
|
130
|
+
repetitions: reps,
|
|
131
|
+
k: topK,
|
|
132
|
+
...(versions !== undefined && { versions }),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|