@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
package/src/routes/runs.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright (C) 2026 Kindgi Inc.
|
|
3
3
|
|
|
4
4
|
import { Hono } from 'hono';
|
|
5
|
+
import type { MiddlewareHandler } from 'hono';
|
|
5
6
|
|
|
6
7
|
import type { AgentId } from '@kindgi/agents';
|
|
7
8
|
import type { KernelRunRecord, ListRunsInput, RunBinding } from '@kindgi/runtime';
|
|
8
|
-
import type { FlowId,
|
|
9
|
+
import type { FlowId, ListScope, ProjectId, RunId, Semver, TenantId } from '@kindgi/types';
|
|
9
10
|
|
|
10
11
|
import { ref } from '@kindgi/authz';
|
|
11
12
|
|
|
@@ -21,7 +22,7 @@ import type { MintPublicRunTokenResult } from '../public-run-token.js';
|
|
|
21
22
|
import type { AppEnv } from '../types.js';
|
|
22
23
|
import type { DecodedCursor } from './pagination.js';
|
|
23
24
|
import { clampLimit, decodeCursor } from './pagination.js';
|
|
24
|
-
import {
|
|
25
|
+
import { parseListScope } from './scope-params.js';
|
|
25
26
|
import {
|
|
26
27
|
formatSseFrame,
|
|
27
28
|
isTerminalWireKind,
|
|
@@ -60,6 +61,24 @@ const MAX_RUN_ANCESTRY = 16;
|
|
|
60
61
|
* `POST /:runId/cancel`, `POST /:runId/resume`, `GET /:runId/stream`
|
|
61
62
|
* (SSE), `GET /:runId/journal`.
|
|
62
63
|
*/
|
|
64
|
+
/** A run id: a UUID. */
|
|
65
|
+
const RUN_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A `:runId` that isn't a run id is a 400, before it reaches a query (where
|
|
69
|
+
* Postgres's uuid cast would fail it as a 500).
|
|
70
|
+
*/
|
|
71
|
+
const refuseMalformedRunId: MiddlewareHandler<AppEnv> = async (c, next) => {
|
|
72
|
+
if (RUN_ID_RE.test(c.req.param('runId') ?? '')) return next();
|
|
73
|
+
c.status(statusFor('bad-input') as never);
|
|
74
|
+
return c.json(
|
|
75
|
+
toWireError(
|
|
76
|
+
{ code: 'bad-input', message: '`runId` must be a run id (a UUID)' },
|
|
77
|
+
c.get('requestId'),
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
63
82
|
export function runsRouter(
|
|
64
83
|
binding: RunHandlerBinding,
|
|
65
84
|
runBinding: RunBinding,
|
|
@@ -179,7 +198,7 @@ export function runsRouter(
|
|
|
179
198
|
});
|
|
180
199
|
|
|
181
200
|
// ---------- GET /:runId ----------
|
|
182
|
-
r.get('/:runId', async (c) => {
|
|
201
|
+
r.get('/:runId', refuseMalformedRunId, async (c) => {
|
|
183
202
|
const requestId = c.get('requestId');
|
|
184
203
|
const runId = c.req.param('runId') as RunId;
|
|
185
204
|
|
|
@@ -194,7 +213,7 @@ export function runsRouter(
|
|
|
194
213
|
});
|
|
195
214
|
|
|
196
215
|
// ---------- GET /:runId/progress ----------
|
|
197
|
-
r.get('/:runId/progress', async (c) => {
|
|
216
|
+
r.get('/:runId/progress', refuseMalformedRunId, async (c) => {
|
|
198
217
|
const requestId = c.get('requestId');
|
|
199
218
|
const runId = c.req.param('runId') as RunId;
|
|
200
219
|
const loaded = await readableRun(runBinding, c, runId);
|
|
@@ -209,7 +228,8 @@ export function runsRouter(
|
|
|
209
228
|
|
|
210
229
|
// ---------- GET / (list, cursor-paginated) ----------
|
|
211
230
|
//
|
|
212
|
-
// Content-scoped list; scope filter threaded via `
|
|
231
|
+
// Content-scoped list; scope filter threaded via `parseListScope` (a
|
|
232
|
+
// `scopeId` that isn't a UUID is a 400 here, not a failed query).
|
|
213
233
|
// Every run belongs to exactly one project; `scope.kind === 'project'`
|
|
214
234
|
// narrows to that project, `scope.kind === 'org'` to the org's
|
|
215
235
|
// projects, and tenant / undefined behave as documented in
|
|
@@ -232,7 +252,7 @@ export function runsRouter(
|
|
|
232
252
|
cursorFilter = decoded;
|
|
233
253
|
}
|
|
234
254
|
|
|
235
|
-
const scopeParsed =
|
|
255
|
+
const scopeParsed = parseListScope(c.req.query(), { tenantId });
|
|
236
256
|
if (scopeParsed.kind === 'err') {
|
|
237
257
|
c.status(statusFor('scope-invalid') as never);
|
|
238
258
|
return c.json(
|
|
@@ -263,7 +283,7 @@ export function runsRouter(
|
|
|
263
283
|
});
|
|
264
284
|
|
|
265
285
|
// ---------- POST /:runId/cancel ----------
|
|
266
|
-
r.post('/:runId/cancel', async (c) => {
|
|
286
|
+
r.post('/:runId/cancel', refuseMalformedRunId, async (c) => {
|
|
267
287
|
const requestId = c.get('requestId');
|
|
268
288
|
const tenantId = c.get('tenantId') as TenantId;
|
|
269
289
|
const runId = c.req.param('runId') as RunId;
|
|
@@ -562,11 +582,11 @@ export function runsRouter(
|
|
|
562
582
|
},
|
|
563
583
|
});
|
|
564
584
|
};
|
|
565
|
-
r.get('/:runId/stream', (c) => streamRun(c, 'full'));
|
|
566
|
-
r.get('/:runId/progress/stream', (c) => streamRun(c, 'progress'));
|
|
585
|
+
r.get('/:runId/stream', refuseMalformedRunId, (c) => streamRun(c, 'full'));
|
|
586
|
+
r.get('/:runId/progress/stream', refuseMalformedRunId, (c) => streamRun(c, 'progress'));
|
|
567
587
|
|
|
568
588
|
// ---------- GET /:runId/journal ----------
|
|
569
|
-
r.get('/:runId/journal', async (c) => {
|
|
589
|
+
r.get('/:runId/journal', refuseMalformedRunId, async (c) => {
|
|
570
590
|
const requestId = c.get('requestId');
|
|
571
591
|
const tenantId = c.get('tenantId') as TenantId;
|
|
572
592
|
const runId = c.req.param('runId') as RunId;
|
|
@@ -633,7 +653,9 @@ function invokeFromBody(
|
|
|
633
653
|
/**
|
|
634
654
|
* Wire row for a run. `output` is the run's output once it completed;
|
|
635
655
|
* single-run responses carry it, lists only with `?include=output`
|
|
636
|
-
* (outputs can be large). Child runs carry their parent's run + node
|
|
656
|
+
* (outputs can be large). Child runs carry their parent's run + node;
|
|
657
|
+
* an agent's turns, the agent, its version and the conversation; a
|
|
658
|
+
* replay run, the run it replays and its eval run.
|
|
637
659
|
*/
|
|
638
660
|
function serializeRun(
|
|
639
661
|
row: KernelRunRecord,
|
|
@@ -654,6 +676,15 @@ function serializeRun(
|
|
|
654
676
|
...(opts.output && row.output !== undefined && { output: row.output }),
|
|
655
677
|
...(row.parentRunId != null && { parentRunId: row.parentRunId as unknown as string }),
|
|
656
678
|
...(row.parentNodeId != null && { parentNodeId: row.parentNodeId as unknown as string }),
|
|
679
|
+
...(row.agent !== undefined && {
|
|
680
|
+
agent: {
|
|
681
|
+
id: row.agent.id,
|
|
682
|
+
version: row.agent.version,
|
|
683
|
+
conversationId: row.agent.conversationId as unknown as string,
|
|
684
|
+
},
|
|
685
|
+
}),
|
|
686
|
+
...(row.replayOf != null && { replayOf: row.replayOf as unknown as string }),
|
|
687
|
+
...(row.evalRunId != null && { evalRunId: row.evalRunId }),
|
|
657
688
|
};
|
|
658
689
|
}
|
|
659
690
|
|
|
@@ -698,7 +729,7 @@ async function readableRun(
|
|
|
698
729
|
/** The binding query for `GET /v1/runs`: scope, page, and the parent filters. */
|
|
699
730
|
function listRunsInput(input: {
|
|
700
731
|
readonly tenantId: TenantId;
|
|
701
|
-
readonly scope:
|
|
732
|
+
readonly scope: ListScope | undefined;
|
|
702
733
|
readonly limit: number;
|
|
703
734
|
readonly cursor: DecodedCursor | null;
|
|
704
735
|
readonly filter: RunListFilter;
|
|
@@ -706,42 +737,65 @@ function listRunsInput(input: {
|
|
|
706
737
|
const { tenantId, scope, limit, cursor, filter } = input;
|
|
707
738
|
return {
|
|
708
739
|
tenantId,
|
|
709
|
-
...(scope
|
|
710
|
-
scope: { kind: 'project' as const, projectId: scope.projectId as ProjectId },
|
|
711
|
-
}),
|
|
712
|
-
...(scope?.kind === 'org' && {
|
|
713
|
-
scope: { kind: 'org' as const, orgId: scope.orgId as unknown as OrgId },
|
|
714
|
-
}),
|
|
740
|
+
...(scope !== undefined && { scope }),
|
|
715
741
|
limit,
|
|
716
742
|
...(cursor !== null && {
|
|
717
743
|
cursor: { createdAt: cursor.createdAt as never, id: cursor.id as RunId },
|
|
718
744
|
}),
|
|
719
745
|
...(filter.parentRunId !== undefined && { parent: { runId: filter.parentRunId } }),
|
|
720
746
|
...(filter.topLevelOnly && { topLevelOnly: true }),
|
|
747
|
+
...(filter.agentId !== undefined && { agentId: filter.agentId }),
|
|
748
|
+
replays: filter.replays,
|
|
749
|
+
...(filter.evalRunId !== undefined && { evalRunId: filter.evalRunId }),
|
|
721
750
|
};
|
|
722
751
|
}
|
|
723
752
|
|
|
724
753
|
interface RunListFilter {
|
|
725
754
|
readonly parentRunId?: RunId;
|
|
726
755
|
readonly topLevelOnly: boolean;
|
|
756
|
+
readonly agentId?: string;
|
|
757
|
+
readonly replays: 'exclude' | 'include' | 'only';
|
|
758
|
+
readonly evalRunId?: string;
|
|
727
759
|
readonly includeOutput: boolean;
|
|
728
760
|
}
|
|
729
761
|
|
|
730
|
-
/**
|
|
762
|
+
/**
|
|
763
|
+
* `?parentRunId=` (children of a run), `?topLevel=true`, `?agentId=` (an
|
|
764
|
+
* agent's turns), `?replays=exclude|include|only` (default `exclude`),
|
|
765
|
+
* `?evalRunId=` (one eval run's replays; implies they are included),
|
|
766
|
+
* `?include=output`.
|
|
767
|
+
*/
|
|
731
768
|
function parseRunListFilter(
|
|
732
769
|
query: Readonly<Record<string, string>>,
|
|
733
770
|
): { kind: 'ok'; value: RunListFilter } | { kind: 'err'; message: string } {
|
|
734
|
-
const { parentRunId, topLevel, include } = query;
|
|
771
|
+
const { parentRunId, topLevel, agentId, replays, evalRunId, include } = query;
|
|
772
|
+
if (agentId !== undefined && agentId.trim() === '') {
|
|
773
|
+
return { kind: 'err', message: '`agentId` must not be empty' };
|
|
774
|
+
}
|
|
735
775
|
if (topLevel !== undefined && topLevel !== 'true' && topLevel !== 'false') {
|
|
736
776
|
return { kind: 'err', message: '`topLevel` must be `true` or `false`' };
|
|
737
777
|
}
|
|
738
778
|
const topLevelOnly = topLevel === 'true';
|
|
739
|
-
if (parentRunId !== undefined && parentRunId
|
|
740
|
-
return { kind: 'err', message: '`parentRunId` must be a run id' };
|
|
779
|
+
if (parentRunId !== undefined && !RUN_ID_RE.test(parentRunId)) {
|
|
780
|
+
return { kind: 'err', message: '`parentRunId` must be a run id (a UUID)' };
|
|
741
781
|
}
|
|
742
782
|
if (parentRunId !== undefined && topLevelOnly) {
|
|
743
783
|
return { kind: 'err', message: '`parentRunId` and `topLevel=true` cannot be combined' };
|
|
744
784
|
}
|
|
785
|
+
if (
|
|
786
|
+
replays !== undefined &&
|
|
787
|
+
replays !== 'exclude' &&
|
|
788
|
+
replays !== 'include' &&
|
|
789
|
+
replays !== 'only'
|
|
790
|
+
) {
|
|
791
|
+
return { kind: 'err', message: '`replays` must be `exclude`, `include` or `only`' };
|
|
792
|
+
}
|
|
793
|
+
if (evalRunId !== undefined && evalRunId.trim() === '') {
|
|
794
|
+
return { kind: 'err', message: '`evalRunId` must not be empty' };
|
|
795
|
+
}
|
|
796
|
+
if (evalRunId !== undefined && replays === 'exclude') {
|
|
797
|
+
return { kind: 'err', message: '`evalRunId` and `replays=exclude` cannot be combined' };
|
|
798
|
+
}
|
|
745
799
|
const includes = include === undefined ? [] : include.split(',').map((i) => i.trim());
|
|
746
800
|
const unknown = includes.filter((i) => i !== 'output');
|
|
747
801
|
if (unknown.length > 0) {
|
|
@@ -752,6 +806,9 @@ function parseRunListFilter(
|
|
|
752
806
|
value: {
|
|
753
807
|
...(parentRunId !== undefined && { parentRunId: parentRunId as RunId }),
|
|
754
808
|
topLevelOnly,
|
|
809
|
+
...(agentId !== undefined && { agentId }),
|
|
810
|
+
replays: replays ?? (evalRunId !== undefined ? 'include' : 'exclude'),
|
|
811
|
+
...(evalRunId !== undefined && { evalRunId }),
|
|
755
812
|
includeOutput: includes.includes('output'),
|
|
756
813
|
},
|
|
757
814
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { type ResourceRef, ref } from '@kindgi/authz';
|
|
5
5
|
import type { Scope } from '@kindgi/platform';
|
|
6
|
-
import type { OrgId, ProjectId, TenantId } from '@kindgi/types';
|
|
6
|
+
import type { ListScope, OrgId, ProjectId, TenantId } from '@kindgi/types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The authorization resource for a request scope: the project or org it
|
|
@@ -131,3 +131,37 @@ export function parseScopeParams(
|
|
|
131
131
|
|
|
132
132
|
return { kind: 'ok', scope, ...(inherit !== undefined && { inherit }) };
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
const SCOPE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A content list's `?scopeKind` + `?scopeId` as a `ListScope`: a project
|
|
139
|
+
* or an org narrows the list; no scope, or `tenant`, is the whole tenant.
|
|
140
|
+
* Project and org ids are UUIDs, so a malformed `scopeId` is an error
|
|
141
|
+
* here (the route's 400), not a failed query.
|
|
142
|
+
*/
|
|
143
|
+
export function parseListScope(
|
|
144
|
+
query: QuerySource,
|
|
145
|
+
session: { readonly tenantId: TenantId },
|
|
146
|
+
):
|
|
147
|
+
| { readonly kind: 'ok'; readonly scope?: ListScope }
|
|
148
|
+
| { readonly kind: 'err'; readonly message: string } {
|
|
149
|
+
const parsed = parseScopeParams(query, session);
|
|
150
|
+
if (parsed.kind === 'err') return parsed;
|
|
151
|
+
const scope = parsed.scope;
|
|
152
|
+
if (scope === undefined || scope.kind === 'tenant') return { kind: 'ok' };
|
|
153
|
+
const id = (scope.kind === 'project' ? scope.projectId : scope.orgId) as unknown as string;
|
|
154
|
+
if (!SCOPE_ID_RE.test(id)) {
|
|
155
|
+
return {
|
|
156
|
+
kind: 'err',
|
|
157
|
+
message: `scope query parameters malformed: scopeId must be ${scope.kind === 'project' ? 'a project' : 'an org'} id (a UUID), got "${id}"`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
kind: 'ok',
|
|
162
|
+
scope:
|
|
163
|
+
scope.kind === 'project'
|
|
164
|
+
? { kind: 'project', projectId: scope.projectId }
|
|
165
|
+
: { kind: 'org', orgId: scope.orgId },
|
|
166
|
+
};
|
|
167
|
+
}
|
package/src/routes/teams.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { Cursor, OrgId, TeamId, TenantId, UserId } from '@kindgi/types';
|
|
|
19
19
|
import { statusFor, toWireError } from '../errors.js';
|
|
20
20
|
import type { Authorizer } from '../middleware/authorize.js';
|
|
21
21
|
import type { AppEnv } from '../types.js';
|
|
22
|
+
import { slugConflictError } from './hierarchy-errors.js';
|
|
22
23
|
import { clampLimit } from './pagination.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -29,9 +30,13 @@ import { clampLimit } from './pagination.js';
|
|
|
29
30
|
* `?limit=` / `?cursor=` / `?orgId=` /
|
|
30
31
|
* `?nameContains=`.
|
|
31
32
|
* - `POST /` — create; body `{ name, slug,
|
|
32
|
-
* description?, orgId? }`; 201 `{ id }
|
|
33
|
+
* description?, orgId? }`; 201 `{ id }`;
|
|
34
|
+
* 409 `slug-conflict` when the tenant
|
|
35
|
+
* has a team with that slug.
|
|
33
36
|
* - `GET /:teamId` — get; 200 or 404 `team-not-found`.
|
|
34
|
-
* - `PATCH /:teamId` — partial update; 204
|
|
37
|
+
* - `PATCH /:teamId` — partial update; 204, 404
|
|
38
|
+
* `team-not-found` or 409
|
|
39
|
+
* `slug-conflict`.
|
|
35
40
|
* - `DELETE /:teamId` — 204 idempotent.
|
|
36
41
|
* - `GET /:teamId/memberships` — list; cursor-paginated.
|
|
37
42
|
* - `POST /:teamId/memberships` — add member; body `{ userId, role }`;
|
|
@@ -193,17 +198,24 @@ export function teamsRouter(
|
|
|
193
198
|
: ('00000000-0000-0000-0000-000000000000' as UserId);
|
|
194
199
|
const result = await tenantHierarchy.createTeam({ tenantId, creatorUserId, spec });
|
|
195
200
|
if (result.kind === 'err') {
|
|
196
|
-
const
|
|
197
|
-
result.error.code === 'slug-conflict'
|
|
198
|
-
|
|
199
|
-
|
|
201
|
+
const error =
|
|
202
|
+
result.error.code === 'slug-conflict'
|
|
203
|
+
? slugConflictError('team', spec.slug)
|
|
204
|
+
: { code: 'internal-server-error', message: result.error.message };
|
|
205
|
+
c.status(statusFor(error.code) as never);
|
|
206
|
+
return c.json(toWireError(error, requestId));
|
|
200
207
|
}
|
|
201
208
|
c.status(201);
|
|
202
209
|
return c.json({ id: result.value.teamId as unknown as string });
|
|
203
210
|
}
|
|
204
|
-
const
|
|
211
|
+
const outcome = await binding.create(tenantId, spec);
|
|
212
|
+
if (outcome.kind === 'slug-conflict') {
|
|
213
|
+
const error = slugConflictError('team', outcome.slug);
|
|
214
|
+
c.status(statusFor(error.code) as never);
|
|
215
|
+
return c.json(toWireError(error, requestId));
|
|
216
|
+
}
|
|
205
217
|
c.status(201);
|
|
206
|
-
return c.json({ id:
|
|
218
|
+
return c.json({ id: outcome.teamId as unknown as string });
|
|
207
219
|
});
|
|
208
220
|
|
|
209
221
|
// ---------- GET /:teamId/memberships ----------
|
|
@@ -300,39 +312,38 @@ export function teamsRouter(
|
|
|
300
312
|
),
|
|
301
313
|
);
|
|
302
314
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
code: 'team-not-found',
|
|
328
|
-
message: `No team with id "${teamId as unknown as string}"`,
|
|
329
|
-
teamId: teamId as unknown as string,
|
|
330
|
-
},
|
|
331
|
-
requestId,
|
|
332
|
-
),
|
|
333
|
-
);
|
|
315
|
+
// Racy — the team can be deleted between the preliminary get and the add.
|
|
316
|
+
const teamGone = () => {
|
|
317
|
+
c.status(statusFor('team-not-found') as never);
|
|
318
|
+
return c.json(
|
|
319
|
+
toWireError(
|
|
320
|
+
{
|
|
321
|
+
code: 'team-not-found',
|
|
322
|
+
message: `No team with id "${teamId as unknown as string}"`,
|
|
323
|
+
teamId: teamId as unknown as string,
|
|
324
|
+
},
|
|
325
|
+
requestId,
|
|
326
|
+
),
|
|
327
|
+
);
|
|
328
|
+
};
|
|
329
|
+
if (authorizer !== undefined) {
|
|
330
|
+
const res = await tenantHierarchy.addTeamMember({
|
|
331
|
+
tenantId,
|
|
332
|
+
teamId,
|
|
333
|
+
userId: b.userId as unknown as UserId,
|
|
334
|
+
role: b.role as TeamRole,
|
|
335
|
+
});
|
|
336
|
+
if (res.kind === 'err') {
|
|
337
|
+
if (res.error.code === 'team-not-found') return teamGone();
|
|
338
|
+
throw new Error(res.error.message, { cause: res.error });
|
|
334
339
|
}
|
|
335
|
-
|
|
340
|
+
} else {
|
|
341
|
+
const outcome = await membershipBinding.add(tenantId, {
|
|
342
|
+
teamId,
|
|
343
|
+
userId: b.userId as unknown as UserId,
|
|
344
|
+
role: b.role,
|
|
345
|
+
});
|
|
346
|
+
if (outcome.kind === 'team-not-found') return teamGone();
|
|
336
347
|
}
|
|
337
348
|
c.status(201);
|
|
338
349
|
return c.json({
|
|
@@ -387,38 +398,33 @@ export function teamsRouter(
|
|
|
387
398
|
),
|
|
388
399
|
);
|
|
389
400
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
requestId,
|
|
418
|
-
),
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
throw cause;
|
|
401
|
+
const outcome = await membershipBinding.updateRole(tenantId, teamId, userId, b.role);
|
|
402
|
+
if (outcome.kind === 'team-membership-not-found') {
|
|
403
|
+
c.status(statusFor('team-membership-not-found') as never);
|
|
404
|
+
return c.json(
|
|
405
|
+
toWireError(
|
|
406
|
+
{
|
|
407
|
+
code: 'team-membership-not-found',
|
|
408
|
+
message: `No membership for team "${teamId as unknown as string}" + user "${userId as unknown as string}"`,
|
|
409
|
+
teamId: teamId as unknown as string,
|
|
410
|
+
userId: userId as unknown as string,
|
|
411
|
+
},
|
|
412
|
+
requestId,
|
|
413
|
+
),
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (outcome.kind === 'team-not-found') {
|
|
417
|
+
c.status(statusFor('team-not-found') as never);
|
|
418
|
+
return c.json(
|
|
419
|
+
toWireError(
|
|
420
|
+
{
|
|
421
|
+
code: 'team-not-found',
|
|
422
|
+
message: `No team with id "${teamId as unknown as string}"`,
|
|
423
|
+
teamId: teamId as unknown as string,
|
|
424
|
+
},
|
|
425
|
+
requestId,
|
|
426
|
+
),
|
|
427
|
+
);
|
|
422
428
|
}
|
|
423
429
|
c.status(204);
|
|
424
430
|
return c.body(null);
|
|
@@ -522,24 +528,24 @@ export function teamsRouter(
|
|
|
522
528
|
);
|
|
523
529
|
}
|
|
524
530
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
531
|
+
const outcome = await binding.update(tenantId, teamId, shaped);
|
|
532
|
+
if (outcome.kind === 'team-not-found') {
|
|
533
|
+
c.status(statusFor('team-not-found') as never);
|
|
534
|
+
return c.json(
|
|
535
|
+
toWireError(
|
|
536
|
+
{
|
|
537
|
+
code: 'team-not-found',
|
|
538
|
+
message: `No team with id "${teamId as unknown as string}"`,
|
|
539
|
+
teamId: teamId as unknown as string,
|
|
540
|
+
},
|
|
541
|
+
requestId,
|
|
542
|
+
),
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
if (outcome.kind === 'slug-conflict') {
|
|
546
|
+
const error = slugConflictError('team', outcome.slug);
|
|
547
|
+
c.status(statusFor(error.code) as never);
|
|
548
|
+
return c.json(toWireError(error, requestId));
|
|
543
549
|
}
|
|
544
550
|
c.status(204);
|
|
545
551
|
return c.body(null);
|
package/src/types.ts
CHANGED
|
@@ -15,9 +15,10 @@ export interface AppEnv {
|
|
|
15
15
|
tenantId: TenantId;
|
|
16
16
|
/**
|
|
17
17
|
* Set by `bearerAuthMiddleware` when the token's `TokenResolution`
|
|
18
|
-
* carried a `reviewerRole
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* carried a `reviewerRole`, or by the approvals routes (and `whoami`)
|
|
19
|
+
* from the reviewer roster for a token whose user is a registered
|
|
20
|
+
* reviewer (`ReviewerBinding.resolveReviewerRole`). Approvals routes
|
|
21
|
+
* gate visibility + decisions on this: `standard < senior < admin`.
|
|
21
22
|
*/
|
|
22
23
|
reviewerRole?: ReviewerRole;
|
|
23
24
|
/**
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
WebhookEndpointId,
|
|
13
13
|
WebhookEventId,
|
|
14
14
|
} from '@kindgi/types';
|
|
15
|
+
import type { CostTokenTotals } from './cost-binding.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Outbound webhook endpoints: URLs the platform sends signed events to,
|
|
@@ -175,6 +176,16 @@ export interface FinishedRun {
|
|
|
175
176
|
readonly failureMessage: string | null;
|
|
176
177
|
readonly createdAt: Timestamp;
|
|
177
178
|
readonly completedAt: Timestamp;
|
|
179
|
+
/**
|
|
180
|
+
* The model calls of the whole run tree (this run and every run it
|
|
181
|
+
* started), from the cost ledger, summed when the run ended. Absent
|
|
182
|
+
* when the runtime records no usage.
|
|
183
|
+
*/
|
|
184
|
+
readonly usage?: {
|
|
185
|
+
readonly calls: number;
|
|
186
|
+
readonly costUsd: number;
|
|
187
|
+
readonly tokens: CostTokenTotals;
|
|
188
|
+
};
|
|
178
189
|
}
|
|
179
190
|
|
|
180
191
|
export interface RunFinishedEvent {
|