@kindgi/api 0.1.1 → 0.1.3
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/app.d.ts.map +1 -1
- package/dist/app.js +2 -1
- package/dist/app.js.map +1 -1
- package/dist/cost-binding.d.ts +81 -5
- package/dist/cost-binding.d.ts.map +1 -1
- package/dist/cost-binding.js +6 -0
- package/dist/cost-binding.js.map +1 -1
- package/dist/hitl-binding.d.ts +20 -5
- package/dist/hitl-binding.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- 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/operations.d.ts.map +1 -1
- package/dist/openapi/operations.js +83 -18
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +18 -0
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +202 -7
- 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/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/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/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 +95 -9
- package/dist/routes/cost.js.map +1 -1
- 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/provenance.d.ts.map +1 -1
- package/dist/routes/provenance.js +32 -1
- package/dist/routes/provenance.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 +44 -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/sse.d.ts +2 -2
- package/dist/routes/sse.js +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 +599 -24
- package/package.json +24 -24
- package/src/app.ts +5 -1
- package/src/cost-binding.ts +96 -5
- package/src/hitl-binding.ts +20 -4
- package/src/index.ts +3 -0
- package/src/middleware/auth.ts +3 -2
- package/src/middleware/idempotency.ts +7 -1
- package/src/openapi/operations.ts +93 -18
- package/src/openapi/schemas.ts +221 -7
- package/src/provenance-binding.ts +42 -1
- package/src/reviewer-binding.ts +10 -2
- package/src/reviewer-role.ts +35 -0
- package/src/routes/approvals.ts +70 -19
- package/src/routes/conversations.ts +57 -1
- package/src/routes/cost.ts +104 -20
- package/src/routes/identity.ts +10 -2
- package/src/routes/provenance.ts +48 -1
- package/src/routes/runs.ts +54 -22
- package/src/routes/scope-params.ts +35 -1
- package/src/routes/sse.ts +2 -2
- 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,8 @@ 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.
|
|
637
658
|
*/
|
|
638
659
|
function serializeRun(
|
|
639
660
|
row: KernelRunRecord,
|
|
@@ -654,6 +675,13 @@ function serializeRun(
|
|
|
654
675
|
...(opts.output && row.output !== undefined && { output: row.output }),
|
|
655
676
|
...(row.parentRunId != null && { parentRunId: row.parentRunId as unknown as string }),
|
|
656
677
|
...(row.parentNodeId != null && { parentNodeId: row.parentNodeId as unknown as string }),
|
|
678
|
+
...(row.agent !== undefined && {
|
|
679
|
+
agent: {
|
|
680
|
+
id: row.agent.id,
|
|
681
|
+
version: row.agent.version,
|
|
682
|
+
conversationId: row.agent.conversationId as unknown as string,
|
|
683
|
+
},
|
|
684
|
+
}),
|
|
657
685
|
};
|
|
658
686
|
}
|
|
659
687
|
|
|
@@ -698,7 +726,7 @@ async function readableRun(
|
|
|
698
726
|
/** The binding query for `GET /v1/runs`: scope, page, and the parent filters. */
|
|
699
727
|
function listRunsInput(input: {
|
|
700
728
|
readonly tenantId: TenantId;
|
|
701
|
-
readonly scope:
|
|
729
|
+
readonly scope: ListScope | undefined;
|
|
702
730
|
readonly limit: number;
|
|
703
731
|
readonly cursor: DecodedCursor | null;
|
|
704
732
|
readonly filter: RunListFilter;
|
|
@@ -706,38 +734,41 @@ function listRunsInput(input: {
|
|
|
706
734
|
const { tenantId, scope, limit, cursor, filter } = input;
|
|
707
735
|
return {
|
|
708
736
|
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
|
-
}),
|
|
737
|
+
...(scope !== undefined && { scope }),
|
|
715
738
|
limit,
|
|
716
739
|
...(cursor !== null && {
|
|
717
740
|
cursor: { createdAt: cursor.createdAt as never, id: cursor.id as RunId },
|
|
718
741
|
}),
|
|
719
742
|
...(filter.parentRunId !== undefined && { parent: { runId: filter.parentRunId } }),
|
|
720
743
|
...(filter.topLevelOnly && { topLevelOnly: true }),
|
|
744
|
+
...(filter.agentId !== undefined && { agentId: filter.agentId }),
|
|
721
745
|
};
|
|
722
746
|
}
|
|
723
747
|
|
|
724
748
|
interface RunListFilter {
|
|
725
749
|
readonly parentRunId?: RunId;
|
|
726
750
|
readonly topLevelOnly: boolean;
|
|
751
|
+
readonly agentId?: string;
|
|
727
752
|
readonly includeOutput: boolean;
|
|
728
753
|
}
|
|
729
754
|
|
|
730
|
-
/**
|
|
755
|
+
/**
|
|
756
|
+
* `?parentRunId=` (children of a run), `?topLevel=true`, `?agentId=` (an
|
|
757
|
+
* agent's turns), `?include=output`.
|
|
758
|
+
*/
|
|
731
759
|
function parseRunListFilter(
|
|
732
760
|
query: Readonly<Record<string, string>>,
|
|
733
761
|
): { kind: 'ok'; value: RunListFilter } | { kind: 'err'; message: string } {
|
|
734
|
-
const { parentRunId, topLevel, include } = query;
|
|
762
|
+
const { parentRunId, topLevel, agentId, include } = query;
|
|
763
|
+
if (agentId !== undefined && agentId.trim() === '') {
|
|
764
|
+
return { kind: 'err', message: '`agentId` must not be empty' };
|
|
765
|
+
}
|
|
735
766
|
if (topLevel !== undefined && topLevel !== 'true' && topLevel !== 'false') {
|
|
736
767
|
return { kind: 'err', message: '`topLevel` must be `true` or `false`' };
|
|
737
768
|
}
|
|
738
769
|
const topLevelOnly = topLevel === 'true';
|
|
739
|
-
if (parentRunId !== undefined && parentRunId
|
|
740
|
-
return { kind: 'err', message: '`parentRunId` must be a run id' };
|
|
770
|
+
if (parentRunId !== undefined && !RUN_ID_RE.test(parentRunId)) {
|
|
771
|
+
return { kind: 'err', message: '`parentRunId` must be a run id (a UUID)' };
|
|
741
772
|
}
|
|
742
773
|
if (parentRunId !== undefined && topLevelOnly) {
|
|
743
774
|
return { kind: 'err', message: '`parentRunId` and `topLevel=true` cannot be combined' };
|
|
@@ -752,6 +783,7 @@ function parseRunListFilter(
|
|
|
752
783
|
value: {
|
|
753
784
|
...(parentRunId !== undefined && { parentRunId: parentRunId as RunId }),
|
|
754
785
|
topLevelOnly,
|
|
786
|
+
...(agentId !== undefined && { agentId }),
|
|
755
787
|
includeOutput: includes.includes('output'),
|
|
756
788
|
},
|
|
757
789
|
};
|
|
@@ -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/sse.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { RunId, TenantId } from '@kindgi/types';
|
|
|
12
12
|
* Source of truth: `@kindgi/specs/run-event.schema.json`. Names are dotted
|
|
13
13
|
* `run.<xxx>-<yyy>` (e.g. `run.step-completed`) — matches the
|
|
14
14
|
* conventions doc §6 example. Internal-only kernel journal kinds
|
|
15
|
-
* (e.g. `
|
|
15
|
+
* (e.g. `value.recorded`) and kinds absent from the wire enum
|
|
16
16
|
* (`edge.evaluated`) are dropped by the mapper — the journal endpoint
|
|
17
17
|
* still exposes them.
|
|
18
18
|
*/
|
|
@@ -54,7 +54,7 @@ const TERMINAL_KINDS: ReadonlySet<RunEventKind> = new Set([
|
|
|
54
54
|
/**
|
|
55
55
|
* Project a kernel `JournalEntry` into the wire `RunEvent` shape
|
|
56
56
|
* consumed over SSE. Returns `null` for kernel kinds not exposed on
|
|
57
|
-
* the wire (`edge.evaluated`, `
|
|
57
|
+
* the wire (`edge.evaluated`, `value.recorded`).
|
|
58
58
|
*/
|
|
59
59
|
export function projectJournalEntry(
|
|
60
60
|
entry: JournalEntry,
|
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 {
|