@kindgi/api 0.1.2 → 0.1.4-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-binding.d.ts +18 -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 +22 -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 +100 -5
- package/dist/cost-binding.d.ts.map +1 -1
- package/dist/cost-binding.js +10 -0
- package/dist/cost-binding.js.map +1 -1
- package/dist/deploy-versions.d.ts +58 -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 +69 -0
- package/dist/derive-agent-version.d.ts.map +1 -0
- package/dist/derive-agent-version.js +139 -0
- package/dist/derive-agent-version.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -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 +30 -0
- package/dist/eval-run-binding.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.d.ts +38 -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 +10 -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/hitl-binding.d.ts +20 -5
- package/dist/hitl-binding.d.ts.map +1 -1
- package/dist/index.d.ts +19 -8
- 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 +134 -0
- package/dist/judged-dispatcher.d.ts.map +1 -0
- package/dist/judged-dispatcher.js +297 -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/middleware/auth.d.ts +3 -2
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +5 -1
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +8 -1
- package/dist/middleware/idempotency.js.map +1 -1
- 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 +543 -24
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +58 -0
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +1058 -27
- package/dist/openapi/schemas.js.map +1 -1
- package/dist/provenance-binding.d.ts +27 -1
- package/dist/provenance-binding.d.ts.map +1 -1
- package/dist/provenance-binding.js.map +1 -1
- package/dist/provider-binding.d.ts +12 -7
- package/dist/provider-binding.d.ts.map +1 -1
- package/dist/reviewer-binding.d.ts +10 -2
- package/dist/reviewer-binding.d.ts.map +1 -1
- package/dist/reviewer-role.d.ts +13 -0
- package/dist/reviewer-role.d.ts.map +1 -0
- package/dist/reviewer-role.js +27 -0
- package/dist/reviewer-role.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 +175 -11
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/approvals.d.ts +5 -4
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +52 -16
- package/dist/routes/approvals.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 +281 -0
- package/dist/routes/blocks.js.map +1 -0
- package/dist/routes/conversations.d.ts +7 -1
- package/dist/routes/conversations.d.ts.map +1 -1
- package/dist/routes/conversations.js +41 -1
- package/dist/routes/conversations.js.map +1 -1
- package/dist/routes/cost.d.ts.map +1 -1
- package/dist/routes/cost.js +142 -11
- 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 +208 -55
- package/dist/routes/deployments.js.map +1 -1
- package/dist/routes/eval-comparison.d.ts +14 -0
- package/dist/routes/eval-comparison.d.ts.map +1 -0
- package/dist/routes/eval-comparison.js +87 -0
- package/dist/routes/eval-comparison.js.map +1 -0
- package/dist/routes/eval-runs.d.ts.map +1 -1
- package/dist/routes/eval-runs.js +8 -0
- package/dist/routes/eval-runs.js.map +1 -1
- package/dist/routes/flows.d.ts +13 -1
- package/dist/routes/flows.d.ts.map +1 -1
- package/dist/routes/flows.js +44 -3
- package/dist/routes/flows.js.map +1 -1
- package/dist/routes/hierarchy-errors.d.ts +35 -0
- package/dist/routes/hierarchy-errors.d.ts.map +1 -0
- package/dist/routes/hierarchy-errors.js +39 -0
- package/dist/routes/hierarchy-errors.js.map +1 -0
- package/dist/routes/identity.d.ts +7 -0
- package/dist/routes/identity.d.ts.map +1 -1
- package/dist/routes/identity.js +3 -2
- package/dist/routes/identity.js.map +1 -1
- 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 +87 -79
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/provenance.d.ts.map +1 -1
- package/dist/routes/provenance.js +32 -1
- package/dist/routes/provenance.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.d.ts +0 -7
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +65 -20
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/scope-params.d.ts +16 -1
- package/dist/routes/scope-params.d.ts.map +1 -1
- package/dist/routes/scope-params.js +28 -0
- package/dist/routes/scope-params.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 +77 -73
- package/dist/routes/teams.js.map +1 -1
- package/dist/types.d.ts +4 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook-endpoint-binding.d.ts +11 -0
- package/dist/webhook-endpoint-binding.d.ts.map +1 -1
- package/dist/webhook-endpoint-binding.js.map +1 -1
- package/openapi.json +13316 -9299
- package/package.json +21 -21
- package/src/agent-binding.ts +19 -4
- package/src/agent-pins.ts +147 -0
- package/src/app.ts +76 -3
- package/src/block-binding.ts +137 -0
- package/src/block-pins.ts +148 -0
- package/src/cost-binding.ts +116 -5
- package/src/deploy-versions.ts +157 -0
- package/src/deployment-binding.ts +27 -3
- package/src/derive-agent-version.ts +206 -0
- package/src/errors.ts +17 -0
- package/src/eval-case-binding.ts +71 -0
- package/src/eval-run-binding.ts +33 -0
- package/src/eval-run-dispatcher.ts +57 -16
- package/src/eval-suite-binding.ts +2 -0
- package/src/flow-binding.ts +11 -4
- package/src/flow-pins.ts +113 -0
- package/src/hitl-binding.ts +20 -4
- package/src/index.ts +88 -2
- package/src/judged-dispatcher.ts +507 -0
- package/src/judged-items.ts +263 -0
- package/src/judgment-binding.ts +349 -0
- package/src/middleware/auth.ts +3 -2
- package/src/middleware/idempotency.ts +7 -1
- package/src/openapi/generate.ts +7 -1
- package/src/openapi/operations.ts +615 -24
- package/src/openapi/schemas.ts +1157 -22
- package/src/provenance-binding.ts +42 -1
- package/src/provider-binding.ts +12 -7
- package/src/reviewer-binding.ts +10 -2
- package/src/reviewer-role.ts +35 -0
- package/src/routes/agents.ts +243 -19
- package/src/routes/approvals.ts +70 -19
- package/src/routes/blocks.ts +362 -0
- package/src/routes/conversations.ts +57 -1
- package/src/routes/cost.ts +159 -21
- package/src/routes/deployments.ts +266 -56
- package/src/routes/eval-comparison.ts +101 -0
- package/src/routes/eval-runs.ts +11 -0
- package/src/routes/flows.ts +63 -5
- package/src/routes/hierarchy-errors.ts +51 -0
- package/src/routes/identity.ts +10 -2
- 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 +106 -95
- package/src/routes/provenance.ts +48 -1
- package/src/routes/providers.ts +5 -0
- package/src/routes/runs.ts +79 -22
- package/src/routes/scope-params.ts +35 -1
- package/src/routes/teams.ts +96 -90
- package/src/types.ts +4 -3
- package/src/webhook-endpoint-binding.ts +11 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { AgentId } from '@kindgi/agents';
|
|
5
|
+
import type { ProjectId, Semver } from '@kindgi/types';
|
|
6
|
+
|
|
7
|
+
import type { EvalBaseline, EvalComparison } from '../eval-run-binding.js';
|
|
8
|
+
import { DEFAULT_COMPARISON } from '../judged-dispatcher.js';
|
|
9
|
+
|
|
10
|
+
const MAX_REPETITIONS = 10;
|
|
11
|
+
const MAX_K = 100;
|
|
12
|
+
|
|
13
|
+
function obj(value: unknown): Record<string, unknown> | undefined {
|
|
14
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
15
|
+
? (value as Record<string, unknown>)
|
|
16
|
+
: undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseLive(raw: unknown): EvalBaseline | string {
|
|
20
|
+
const live = obj(raw);
|
|
21
|
+
if (live === undefined) return '`baseline.live` must be an object';
|
|
22
|
+
const segments = live.segments === undefined ? undefined : obj(live.segments);
|
|
23
|
+
const stringsOnly =
|
|
24
|
+
segments !== undefined && Object.values(segments).every((v) => typeof v === 'string');
|
|
25
|
+
if (live.segments !== undefined && !stringsOnly) {
|
|
26
|
+
return '`baseline.live.segments` must be an object of strings';
|
|
27
|
+
}
|
|
28
|
+
if (live.projectId !== undefined && typeof live.projectId !== 'string') {
|
|
29
|
+
return '`baseline.live.projectId` must be a string';
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
live: {
|
|
33
|
+
...(typeof live.projectId === 'string' && { projectId: live.projectId as ProjectId }),
|
|
34
|
+
...(segments !== undefined && { segments: segments as Record<string, string> }),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseBaseline(raw: unknown): EvalBaseline | string {
|
|
40
|
+
if (raw === 'recorded') return 'recorded';
|
|
41
|
+
const o = obj(raw);
|
|
42
|
+
if (o === undefined) {
|
|
43
|
+
return "`baseline` must be 'recorded', { agentId, version } or { live: { projectId?, segments? } }";
|
|
44
|
+
}
|
|
45
|
+
if (o.live !== undefined) return parseLive(o.live);
|
|
46
|
+
if (typeof o.agentId !== 'string' || o.agentId === '' || typeof o.version !== 'string') {
|
|
47
|
+
return '`baseline` as a version needs `agentId` and `version`';
|
|
48
|
+
}
|
|
49
|
+
return { agentId: o.agentId as AgentId, version: o.version as Semver };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function integerIn(raw: unknown, name: string, max: number): number | string {
|
|
53
|
+
return typeof raw === 'number' && Number.isInteger(raw) && raw >= 1 && raw <= max
|
|
54
|
+
? raw
|
|
55
|
+
: `\`${name}\` must be an integer from 1 to ${max}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A comparison eval run's settings from the start body: `baseline`,
|
|
60
|
+
* `reads`, `repetitions` and `k`. `undefined` when none is given (the
|
|
61
|
+
* defaults apply); an error message for a bad one.
|
|
62
|
+
*/
|
|
63
|
+
export function parseComparison(
|
|
64
|
+
b: Readonly<Record<string, unknown>>,
|
|
65
|
+
):
|
|
66
|
+
| { readonly kind: 'ok'; readonly value?: EvalComparison }
|
|
67
|
+
| { readonly kind: 'err'; readonly message: string } {
|
|
68
|
+
const { baseline, reads, repetitions, k } = b;
|
|
69
|
+
if (
|
|
70
|
+
baseline === undefined &&
|
|
71
|
+
reads === undefined &&
|
|
72
|
+
repetitions === undefined &&
|
|
73
|
+
k === undefined
|
|
74
|
+
) {
|
|
75
|
+
return { kind: 'ok' };
|
|
76
|
+
}
|
|
77
|
+
const parsedBaseline =
|
|
78
|
+
baseline === undefined ? DEFAULT_COMPARISON.baseline : parseBaseline(baseline);
|
|
79
|
+
if (typeof parsedBaseline === 'string' && parsedBaseline !== 'recorded') {
|
|
80
|
+
return { kind: 'err', message: parsedBaseline };
|
|
81
|
+
}
|
|
82
|
+
if (reads !== undefined && reads !== 'recorded' && reads !== 'live') {
|
|
83
|
+
return { kind: 'err', message: "`reads` must be 'recorded' or 'live'" };
|
|
84
|
+
}
|
|
85
|
+
const reps =
|
|
86
|
+
repetitions === undefined
|
|
87
|
+
? DEFAULT_COMPARISON.repetitions
|
|
88
|
+
: integerIn(repetitions, 'repetitions', MAX_REPETITIONS);
|
|
89
|
+
if (typeof reps === 'string') return { kind: 'err', message: reps };
|
|
90
|
+
const topK = k === undefined ? DEFAULT_COMPARISON.k : integerIn(k, 'k', MAX_K);
|
|
91
|
+
if (typeof topK === 'string') return { kind: 'err', message: topK };
|
|
92
|
+
return {
|
|
93
|
+
kind: 'ok',
|
|
94
|
+
value: {
|
|
95
|
+
baseline: parsedBaseline,
|
|
96
|
+
reads: reads ?? DEFAULT_COMPARISON.reads,
|
|
97
|
+
repetitions: reps,
|
|
98
|
+
k: topK,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
package/src/routes/eval-runs.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { statusFor, toWireError } from '../errors.js';
|
|
|
10
10
|
import {
|
|
11
11
|
type AgentRef,
|
|
12
12
|
EVAL_RUN_STATUSES,
|
|
13
|
+
type EvalComparison,
|
|
13
14
|
type EvalRun,
|
|
14
15
|
type EvalRunBinding,
|
|
15
16
|
type EvalRunFilter,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
type FlowRef,
|
|
18
19
|
} from '../eval-run-binding.js';
|
|
19
20
|
import type { AppEnv } from '../types.js';
|
|
21
|
+
import { parseComparison } from './eval-comparison.js';
|
|
20
22
|
import { clampLimit } from './pagination.js';
|
|
21
23
|
import { parseScopeParams } from './scope-params.js';
|
|
22
24
|
import { formatSseFrame } from './sse.js';
|
|
@@ -93,6 +95,7 @@ function startRouter(binding: EvalRunBinding): Hono<AppEnv> {
|
|
|
93
95
|
...(parsed.value.correlationId !== undefined && {
|
|
94
96
|
correlationId: parsed.value.correlationId,
|
|
95
97
|
}),
|
|
98
|
+
...(parsed.value.comparison !== undefined && { comparison: parsed.value.comparison }),
|
|
96
99
|
});
|
|
97
100
|
if (outcome.kind === 'suite-not-found') {
|
|
98
101
|
c.status(statusFor('eval-suite-not-found') as never);
|
|
@@ -454,6 +457,7 @@ function serializeEvalRun(run: EvalRun): Record<string, unknown> {
|
|
|
454
457
|
...(run.result !== undefined && { result: run.result }),
|
|
455
458
|
...(run.error !== undefined && { error: run.error }),
|
|
456
459
|
...(run.correlationId !== undefined && { correlationId: run.correlationId }),
|
|
460
|
+
...(run.comparison !== undefined && { comparison: run.comparison }),
|
|
457
461
|
};
|
|
458
462
|
}
|
|
459
463
|
|
|
@@ -486,6 +490,7 @@ interface ParsedStartBody {
|
|
|
486
490
|
readonly flowRef?: FlowRef;
|
|
487
491
|
readonly dryRun?: boolean;
|
|
488
492
|
readonly correlationId?: string;
|
|
493
|
+
readonly comparison?: EvalComparison;
|
|
489
494
|
}
|
|
490
495
|
|
|
491
496
|
function parseStartBody(
|
|
@@ -618,6 +623,11 @@ function parseStartBody(
|
|
|
618
623
|
};
|
|
619
624
|
}
|
|
620
625
|
|
|
626
|
+
const comparison = parseComparison(b);
|
|
627
|
+
if (comparison.kind === 'err') {
|
|
628
|
+
return { kind: 'err', error: { code: 'bad-input', message: comparison.message } };
|
|
629
|
+
}
|
|
630
|
+
|
|
621
631
|
return {
|
|
622
632
|
kind: 'ok',
|
|
623
633
|
value: {
|
|
@@ -626,6 +636,7 @@ function parseStartBody(
|
|
|
626
636
|
...(flowRef !== undefined && { flowRef }),
|
|
627
637
|
...(dryRun !== undefined && { dryRun }),
|
|
628
638
|
...(correlationId !== undefined && { correlationId: correlationId as string }),
|
|
639
|
+
...(comparison.value !== undefined && { comparison: comparison.value }),
|
|
629
640
|
},
|
|
630
641
|
};
|
|
631
642
|
}
|
package/src/routes/flows.ts
CHANGED
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
import { Hono } from 'hono';
|
|
5
5
|
|
|
6
6
|
import { type Principal, ref, tuplesForCreate } from '@kindgi/authz';
|
|
7
|
-
import { type Flow, type FlowError, loadFlow } from '@kindgi/flow';
|
|
7
|
+
import { type Flow, type FlowError, flowPinsDigest, loadFlow } from '@kindgi/flow';
|
|
8
8
|
import type { Cursor, FlowId, ProjectId, TenantId, UserId } from '@kindgi/types';
|
|
9
9
|
|
|
10
|
+
import type { AgentRegistryBinding } from '../agent-binding.js';
|
|
11
|
+
import type { UnpinnableRef } from '../agent-pins.js';
|
|
10
12
|
import { statusFor, toWireError } from '../errors.js';
|
|
11
|
-
import type { FlowRegistryBinding } from '../flow-binding.js';
|
|
13
|
+
import type { FlowRegistryBinding, FlowVersionRecord } from '../flow-binding.js';
|
|
14
|
+
import { resolveFlowPins } from '../flow-pins.js';
|
|
12
15
|
import type { Authorizer } from '../middleware/authorize.js';
|
|
16
|
+
import type { ToolRegistryBinding } from '../tool-binding.js';
|
|
13
17
|
import type { AppEnv } from '../types.js';
|
|
14
18
|
import { clampLimit } from './pagination.js';
|
|
15
19
|
import { parseScopeParams } from './scope-params.js';
|
|
@@ -30,8 +34,19 @@ import { parseScopeParams } from './scope-params.js';
|
|
|
30
34
|
* Run initiation stays at `POST /v1/runs` (one primitive,
|
|
31
35
|
* discriminated subject). No
|
|
32
36
|
* `/v1/flows/:flowId/run` route is added here.
|
|
37
|
+
*
|
|
38
|
+
* With `pinning` (the tool and agent registries), a published version is
|
|
39
|
+
* pinned: each tool it runs, and each agent it runs at no named version,
|
|
40
|
+
* resolves once, at publish, to the latest version, which every run of
|
|
41
|
+
* that version uses (`pins`, see `resolveFlowPins`); one with no
|
|
42
|
+
* published version refuses the publish. Without it, versions carry no
|
|
43
|
+
* pins and bind the latest versions per run.
|
|
33
44
|
*/
|
|
34
|
-
export function flowsRouter(
|
|
45
|
+
export function flowsRouter(
|
|
46
|
+
binding: FlowRegistryBinding,
|
|
47
|
+
authorizer?: Authorizer,
|
|
48
|
+
pinning?: { readonly tools: ToolRegistryBinding; readonly agents: AgentRegistryBinding },
|
|
49
|
+
): Hono<AppEnv> {
|
|
35
50
|
const r = new Hono<AppEnv>();
|
|
36
51
|
|
|
37
52
|
// Authorization — mirrors agents 1:1. Check the resource directly;
|
|
@@ -275,13 +290,23 @@ export function flowsRouter(binding: FlowRegistryBinding, authorizer?: Authorize
|
|
|
275
290
|
);
|
|
276
291
|
}
|
|
277
292
|
|
|
293
|
+
// Pin the version: every tool and unversioned agent resolves now,
|
|
294
|
+
// once, to the version its runs use. One with no published version
|
|
295
|
+
// refuses the publish rather than store a partly pinned version.
|
|
296
|
+
const pinned = await pinFlow(pinning, tenantId, loaded.value);
|
|
297
|
+
if (pinned.kind === 'unpinnable') {
|
|
298
|
+
c.status(statusFor('validation-failed') as never);
|
|
299
|
+
return c.json(toWireError(unpinnableFlow(loaded.value, pinned.issues), requestId));
|
|
300
|
+
}
|
|
301
|
+
const flow = pinned.flow;
|
|
302
|
+
|
|
278
303
|
const principal = c.get('principal') as Principal | undefined;
|
|
279
304
|
const creatorUserId =
|
|
280
305
|
principal?.actor.kind === 'user' ? (principal.actor.id as UserId) : undefined;
|
|
281
306
|
const outcome = await binding.publish({
|
|
282
307
|
tenantId,
|
|
283
308
|
projectId,
|
|
284
|
-
flow
|
|
309
|
+
flow,
|
|
285
310
|
enqueueTuples: (flowId) =>
|
|
286
311
|
tuplesForCreate({ kind: 'flow', id: flowId as FlowId, tenantId, projectId }, creatorUserId),
|
|
287
312
|
});
|
|
@@ -383,7 +408,36 @@ export function flowsRouter(binding: FlowRegistryBinding, authorizer?: Authorize
|
|
|
383
408
|
return r;
|
|
384
409
|
}
|
|
385
410
|
|
|
386
|
-
|
|
411
|
+
/** The flow with its pins, or the references with no published version; as it is without `pinning`. */
|
|
412
|
+
async function pinFlow(
|
|
413
|
+
pinning:
|
|
414
|
+
| { readonly tools: ToolRegistryBinding; readonly agents: AgentRegistryBinding }
|
|
415
|
+
| undefined,
|
|
416
|
+
tenantId: TenantId,
|
|
417
|
+
flow: Flow,
|
|
418
|
+
): Promise<
|
|
419
|
+
| { readonly kind: 'ok'; readonly flow: Flow }
|
|
420
|
+
| { readonly kind: 'unpinnable'; readonly issues: readonly UnpinnableRef[] }
|
|
421
|
+
> {
|
|
422
|
+
if (pinning === undefined) return { kind: 'ok', flow };
|
|
423
|
+
const resolved = await resolveFlowPins(pinning.tools, pinning.agents, tenantId, flow);
|
|
424
|
+
if (resolved.kind === 'unpinnable') return resolved;
|
|
425
|
+
return {
|
|
426
|
+
kind: 'ok',
|
|
427
|
+
flow: { ...flow, pins: resolved.pins, pinsDigest: flowPinsDigest(resolved.pins) },
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** The `validation-failed` error naming each tool or agent a flow runs that isn't published. */
|
|
432
|
+
function unpinnableFlow(flow: Flow, issues: readonly UnpinnableRef[]) {
|
|
433
|
+
return {
|
|
434
|
+
code: 'validation-failed' as const,
|
|
435
|
+
message: `Flow "${flow.id as unknown as string}" runs tools or agents that aren't published (${issues.length} issue${issues.length === 1 ? '' : 's'})`,
|
|
436
|
+
issues: issues as unknown as Record<string, unknown>[],
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function serializeGraph(g: FlowVersionRecord): Record<string, unknown> {
|
|
387
441
|
return {
|
|
388
442
|
id: g.id as unknown as string,
|
|
389
443
|
version: g.version,
|
|
@@ -393,6 +447,10 @@ function serializeGraph(g: Flow): Record<string, unknown> {
|
|
|
393
447
|
edges: g.edges,
|
|
394
448
|
...(g.maxParallelism !== undefined && { maxParallelism: g.maxParallelism }),
|
|
395
449
|
...(g.metadata !== undefined && { metadata: g.metadata }),
|
|
450
|
+
...(g.pins !== undefined && { pins: g.pins }),
|
|
451
|
+
...(g.pinsDigest !== undefined && { pinsDigest: g.pinsDigest }),
|
|
452
|
+
...(g.derivedFrom !== undefined && { derivedFrom: g.derivedFrom }),
|
|
453
|
+
...(g.unregisteredAt !== undefined && { unregisteredAt: g.unregisteredAt }),
|
|
396
454
|
};
|
|
397
455
|
}
|
|
398
456
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wire errors the tenant-hierarchy routes (orgs, teams, projects) share,
|
|
6
|
+
* so a conflict reads the same whichever route or write path (with or
|
|
7
|
+
* without an authorizer) produced it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** A hierarchy resource, as its wire errors name it. */
|
|
11
|
+
export type HierarchyResource = 'org' | 'team' | 'project';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `409 slug-conflict`: another `resource` has `slug` where it must be
|
|
15
|
+
* unique. An org's and a team's slug are unique in the tenant; a
|
|
16
|
+
* project's in its org, and a project without an org's among the
|
|
17
|
+
* projects without one.
|
|
18
|
+
*/
|
|
19
|
+
export function slugConflictError(resource: HierarchyResource, slug: string) {
|
|
20
|
+
return {
|
|
21
|
+
code: 'slug-conflict',
|
|
22
|
+
message:
|
|
23
|
+
resource === 'project'
|
|
24
|
+
? `Another project in its org has the slug "${slug}" (for a project without an org: another project without one)`
|
|
25
|
+
: `Another ${resource} in the tenant has the slug "${slug}"`,
|
|
26
|
+
resource,
|
|
27
|
+
slug,
|
|
28
|
+
} as const;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `409 slug-conflict` on deleting an org: its projects would leave it
|
|
33
|
+
* with `slugs` that projects without an org already have.
|
|
34
|
+
*/
|
|
35
|
+
export function orgDeleteSlugConflictError(slugs: readonly string[]) {
|
|
36
|
+
const named = slugs.map((s) => `"${s}"`).join(', ');
|
|
37
|
+
return {
|
|
38
|
+
code: 'slug-conflict',
|
|
39
|
+
message: `Deleting the org would leave its projects ${named} without an org, where other projects already have those slugs. Rename or move them first.`,
|
|
40
|
+
resource: 'project',
|
|
41
|
+
slugs,
|
|
42
|
+
} as const;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** `409 project-default-already-exists`: the tenant has a Default project. */
|
|
46
|
+
export function projectDefaultAlreadyExistsError() {
|
|
47
|
+
return {
|
|
48
|
+
code: 'project-default-already-exists',
|
|
49
|
+
message: 'The tenant already has a Default project',
|
|
50
|
+
} as const;
|
|
51
|
+
}
|
package/src/routes/identity.ts
CHANGED
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
SessionSummary,
|
|
12
12
|
UserRecord,
|
|
13
13
|
} from '../identity-directory-binding.js';
|
|
14
|
+
import type { ReviewerBinding } from '../reviewer-binding.js';
|
|
15
|
+
import { callerReviewerRole } from '../reviewer-role.js';
|
|
14
16
|
import type { SessionStoreBinding } from '../session-store-binding.js';
|
|
15
17
|
import type { AppEnv } from '../types.js';
|
|
16
18
|
import { clampLimit } from './pagination.js';
|
|
@@ -47,10 +49,16 @@ export interface IdentityRouterOptions {
|
|
|
47
49
|
* deployments that don't wire an OAuth surface.
|
|
48
50
|
*/
|
|
49
51
|
readonly sessionStore?: SessionStoreBinding;
|
|
52
|
+
/**
|
|
53
|
+
* Optional. When present, `whoami` reports the reviewer role of a
|
|
54
|
+
* caller whose token carries none from the roster, as the approvals
|
|
55
|
+
* routes resolve it.
|
|
56
|
+
*/
|
|
57
|
+
readonly reviewerBinding?: ReviewerBinding;
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
export function identityRouter(options: IdentityRouterOptions = {}): Hono<AppEnv> {
|
|
53
|
-
const { directory, sessionStore } = options;
|
|
61
|
+
const { directory, sessionStore, reviewerBinding } = options;
|
|
54
62
|
const r = new Hono<AppEnv>();
|
|
55
63
|
|
|
56
64
|
// ---------- GET / whoami ----------
|
|
@@ -62,7 +70,7 @@ export function identityRouter(options: IdentityRouterOptions = {}): Hono<AppEnv
|
|
|
62
70
|
const userId = c.get('userId') as UserId | undefined;
|
|
63
71
|
const providerId = c.get('providerId') as string | undefined;
|
|
64
72
|
const scopes = (c.get('scopes') as readonly string[] | undefined) ?? [];
|
|
65
|
-
const reviewerRole = c
|
|
73
|
+
const reviewerRole = await callerReviewerRole(c, reviewerBinding);
|
|
66
74
|
|
|
67
75
|
const body: Record<string, unknown> = { tenantId };
|
|
68
76
|
if (sessionId !== undefined) body.sessionId = sessionId;
|