@kindgi/api 0.1.3 → 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 +20 -2
- 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 +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/index.d.ts +17 -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 +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/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 +461 -7
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +40 -0
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +843 -7
- 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/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/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/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 +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/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/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 +24 -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 +77 -73
- package/dist/routes/teams.js.map +1 -1
- package/openapi.json +13545 -10103
- package/package.json +21 -21
- package/src/agent-binding.ts +19 -4
- package/src/agent-pins.ts +147 -0
- package/src/app.ts +71 -2
- 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 +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/index.ts +85 -2
- package/src/judged-dispatcher.ts +507 -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 +523 -7
- package/src/openapi/schemas.ts +1009 -88
- package/src/provider-binding.ts +12 -7
- package/src/routes/agents.ts +243 -19
- package/src/routes/blocks.ts +362 -0
- package/src/routes/cost.ts +55 -1
- 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/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/providers.ts +5 -0
- package/src/routes/runs.ts +28 -3
- package/src/routes/teams.ts +96 -90
package/src/routes/orgs.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Cursor, OrgId, TenantId, UserId } from '@kindgi/types';
|
|
|
11
11
|
import { statusFor, toWireError } from '../errors.js';
|
|
12
12
|
import type { Authorizer } from '../middleware/authorize.js';
|
|
13
13
|
import type { AppEnv } from '../types.js';
|
|
14
|
+
import { orgDeleteSlugConflictError, slugConflictError } from './hierarchy-errors.js';
|
|
14
15
|
import { clampLimit } from './pagination.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -19,10 +20,13 @@ import { clampLimit } from './pagination.js';
|
|
|
19
20
|
* Five endpoints on `/v1/orgs`:
|
|
20
21
|
* - `GET /` — cursor-paginated list; optional
|
|
21
22
|
* `?limit=` / `?cursor=` / `?nameContains=`.
|
|
22
|
-
* - `POST /` — create; body `{ name, slug }`; 201 `{ id }
|
|
23
|
+
* - `POST /` — create; body `{ name, slug }`; 201 `{ id }`;
|
|
24
|
+
* 409 `slug-conflict` when the tenant has
|
|
25
|
+
* an org with that slug.
|
|
23
26
|
* - `GET /:orgId` — 200 `Org`; 404 `org-not-found`.
|
|
24
27
|
* - `PATCH /:orgId` — partial update via `OrgPatch`; 204 on
|
|
25
|
-
* success; 404 `org-not-found
|
|
28
|
+
* success; 404 `org-not-found`; 409
|
|
29
|
+
* `slug-conflict`.
|
|
26
30
|
* - `DELETE /:orgId` — 204 idempotent (unknown is 204 per the
|
|
27
31
|
* binding's delete-is-a-no-op contract).
|
|
28
32
|
*
|
|
@@ -142,17 +146,24 @@ export function orgsRouter(
|
|
|
142
146
|
: ('00000000-0000-0000-0000-000000000000' as UserId);
|
|
143
147
|
const result = await tenantHierarchy.createOrg({ tenantId, creatorUserId, spec });
|
|
144
148
|
if (result.kind === 'err') {
|
|
145
|
-
const
|
|
146
|
-
result.error.code === 'slug-conflict'
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
const error =
|
|
150
|
+
result.error.code === 'slug-conflict'
|
|
151
|
+
? slugConflictError('org', spec.slug)
|
|
152
|
+
: { code: 'internal-server-error', message: result.error.message };
|
|
153
|
+
c.status(statusFor(error.code) as never);
|
|
154
|
+
return c.json(toWireError(error, requestId));
|
|
149
155
|
}
|
|
150
156
|
c.status(201);
|
|
151
157
|
return c.json({ id: result.value.orgId as unknown as string });
|
|
152
158
|
}
|
|
153
|
-
const
|
|
159
|
+
const outcome = await binding.create(tenantId, spec);
|
|
160
|
+
if (outcome.kind === 'slug-conflict') {
|
|
161
|
+
const error = slugConflictError('org', outcome.slug);
|
|
162
|
+
c.status(statusFor(error.code) as never);
|
|
163
|
+
return c.json(toWireError(error, requestId));
|
|
164
|
+
}
|
|
154
165
|
c.status(201);
|
|
155
|
-
return c.json({ id:
|
|
166
|
+
return c.json({ id: outcome.orgId as unknown as string });
|
|
156
167
|
});
|
|
157
168
|
|
|
158
169
|
// ---------- GET /:orgId ----------
|
|
@@ -226,24 +237,24 @@ export function orgsRouter(
|
|
|
226
237
|
shaped.slug = b.slug;
|
|
227
238
|
}
|
|
228
239
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
240
|
+
const outcome = await binding.update(tenantId, orgId, shaped);
|
|
241
|
+
if (outcome.kind === 'org-not-found') {
|
|
242
|
+
c.status(statusFor('org-not-found') as never);
|
|
243
|
+
return c.json(
|
|
244
|
+
toWireError(
|
|
245
|
+
{
|
|
246
|
+
code: 'org-not-found',
|
|
247
|
+
message: `No org with id "${orgId as unknown as string}"`,
|
|
248
|
+
orgId: orgId as unknown as string,
|
|
249
|
+
},
|
|
250
|
+
requestId,
|
|
251
|
+
),
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (outcome.kind === 'slug-conflict') {
|
|
255
|
+
const error = slugConflictError('org', outcome.slug);
|
|
256
|
+
c.status(statusFor(error.code) as never);
|
|
257
|
+
return c.json(toWireError(error, requestId));
|
|
247
258
|
}
|
|
248
259
|
c.status(204);
|
|
249
260
|
return c.body(null);
|
|
@@ -251,9 +262,15 @@ export function orgsRouter(
|
|
|
251
262
|
|
|
252
263
|
// ---------- DELETE /:orgId ----------
|
|
253
264
|
r.delete('/:orgId', async (c) => {
|
|
265
|
+
const requestId = c.get('requestId');
|
|
254
266
|
const tenantId = c.get('tenantId') as TenantId;
|
|
255
267
|
const orgId = c.req.param('orgId') as OrgId;
|
|
256
|
-
await binding.delete(tenantId, orgId);
|
|
268
|
+
const outcome = await binding.delete(tenantId, orgId);
|
|
269
|
+
if (outcome?.kind === 'slug-conflict') {
|
|
270
|
+
const error = orgDeleteSlugConflictError(outcome.slugs);
|
|
271
|
+
c.status(statusFor(error.code) as never);
|
|
272
|
+
return c.json(toWireError(error, requestId));
|
|
273
|
+
}
|
|
257
274
|
c.status(204);
|
|
258
275
|
return c.body(null);
|
|
259
276
|
});
|
package/src/routes/policies.ts
CHANGED
|
@@ -6,10 +6,12 @@ import { Hono } from 'hono';
|
|
|
6
6
|
import type { Cursor, TenantId } from '@kindgi/types';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
APPLIED_POLICY_KINDS,
|
|
9
10
|
POLICY_KINDS,
|
|
10
11
|
type Policy,
|
|
11
12
|
type PolicyKind,
|
|
12
13
|
type PolicyRegistryBinding,
|
|
14
|
+
isAppliedPolicyKind,
|
|
13
15
|
validatePolicySpec,
|
|
14
16
|
} from '@kindgi/policy-contract';
|
|
15
17
|
import { statusFor, toWireError } from '../errors.js';
|
|
@@ -221,6 +223,23 @@ export function policiesRouter(binding: PolicyRegistryBinding): Hono<AppEnv> {
|
|
|
221
223
|
);
|
|
222
224
|
}
|
|
223
225
|
|
|
226
|
+
// A known kind no runtime consumer applies yet: publishing it would
|
|
227
|
+
// change nothing, silently.
|
|
228
|
+
if (!isAppliedPolicyKind(validation.value.kind)) {
|
|
229
|
+
c.status(statusFor('kind-not-applied') as never);
|
|
230
|
+
return c.json(
|
|
231
|
+
toWireError(
|
|
232
|
+
{
|
|
233
|
+
code: 'kind-not-applied',
|
|
234
|
+
message: `This runtime doesn't apply "${validation.value.kind}" policies yet, so publishing one would change nothing. Policy kinds it applies: ${APPLIED_POLICY_KINDS.join(', ')}.`,
|
|
235
|
+
kind: validation.value.kind,
|
|
236
|
+
appliedKinds: [...APPLIED_POLICY_KINDS],
|
|
237
|
+
},
|
|
238
|
+
requestId,
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
224
243
|
const outcome = await binding.publish({ tenantId, policy: validation.value });
|
|
225
244
|
if (outcome.kind === 'already-registered') {
|
|
226
245
|
c.status(statusFor('policy-already-registered') as never);
|
package/src/routes/projects.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { Cursor, OrgId, ProjectId, 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 { projectDefaultAlreadyExistsError, slugConflictError } from './hierarchy-errors.js';
|
|
22
23
|
import { clampLimit } from './pagination.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -30,12 +31,20 @@ import { clampLimit } from './pagination.js';
|
|
|
30
31
|
* `?nameContains=`.
|
|
31
32
|
* - `POST /` — create; body `{ name, slug,
|
|
32
33
|
* orgId?, description?, isDefault? }`;
|
|
33
|
-
* 201 `{ id }
|
|
34
|
+
* 201 `{ id }`; 409 `slug-conflict`
|
|
35
|
+
* when its org (or, without an org,
|
|
36
|
+
* the tenant's projects without one)
|
|
37
|
+
* has a project with that slug, or
|
|
38
|
+
* `project-default-already-exists`
|
|
39
|
+
* for a second Default.
|
|
34
40
|
* - `GET /default` — returns the tenant's Default
|
|
35
41
|
* project or 404 `project-not-found`.
|
|
36
42
|
* MUST be mounted BEFORE `/:projectId`.
|
|
37
43
|
* - `GET /:projectId` — get; 200 or 404 `project-not-found`.
|
|
38
|
-
* - `PATCH /:projectId` — partial update; 204
|
|
44
|
+
* - `PATCH /:projectId` — partial update; 204, 404
|
|
45
|
+
* `project-not-found` or 409
|
|
46
|
+
* `slug-conflict` (a new slug, or a
|
|
47
|
+
* move to an org that has it).
|
|
39
48
|
* - `DELETE /:projectId` — 204 idempotent.
|
|
40
49
|
* - `GET /:projectId/memberships` — list; cursor-paginated.
|
|
41
50
|
* - `POST /:projectId/memberships` — add member; body `{ userId, role }`;
|
|
@@ -235,23 +244,31 @@ export function projectsRouter(
|
|
|
235
244
|
: ('00000000-0000-0000-0000-000000000000' as UserId);
|
|
236
245
|
const result = await tenantHierarchy.createProject({ tenantId, creatorUserId, spec });
|
|
237
246
|
if (result.kind === 'err') {
|
|
238
|
-
const
|
|
247
|
+
const error =
|
|
239
248
|
result.error.code === 'slug-conflict'
|
|
240
|
-
? 'slug
|
|
249
|
+
? slugConflictError('project', spec.slug)
|
|
241
250
|
: result.error.code === 'default-conflict'
|
|
242
|
-
?
|
|
243
|
-
: 'internal-server-error';
|
|
244
|
-
c.status(statusFor(code) as never);
|
|
245
|
-
return c.json(toWireError(
|
|
251
|
+
? projectDefaultAlreadyExistsError()
|
|
252
|
+
: { code: 'internal-server-error', message: result.error.message };
|
|
253
|
+
c.status(statusFor(error.code) as never);
|
|
254
|
+
return c.json(toWireError(error, requestId));
|
|
246
255
|
}
|
|
247
256
|
c.status(201);
|
|
248
257
|
return c.json({ id: result.value.projectId as unknown as string });
|
|
249
258
|
}
|
|
250
259
|
// No authorizer configured — fall back to the raw binding create;
|
|
251
260
|
// no FGA tuples written.
|
|
252
|
-
const
|
|
261
|
+
const outcome = await binding.create(tenantId, spec);
|
|
262
|
+
if (outcome.kind !== 'ok') {
|
|
263
|
+
const error =
|
|
264
|
+
outcome.kind === 'slug-conflict'
|
|
265
|
+
? slugConflictError('project', outcome.slug)
|
|
266
|
+
: projectDefaultAlreadyExistsError();
|
|
267
|
+
c.status(statusFor(error.code) as never);
|
|
268
|
+
return c.json(toWireError(error, requestId));
|
|
269
|
+
}
|
|
253
270
|
c.status(201);
|
|
254
|
-
return c.json({ id:
|
|
271
|
+
return c.json({ id: outcome.projectId as unknown as string });
|
|
255
272
|
});
|
|
256
273
|
|
|
257
274
|
// ---------- GET /:projectId/memberships ----------
|
|
@@ -346,42 +363,41 @@ export function projectsRouter(
|
|
|
346
363
|
),
|
|
347
364
|
);
|
|
348
365
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
code: 'project-not-found',
|
|
377
|
-
message: `No project with id "${projectId as unknown as string}"`,
|
|
378
|
-
projectId: projectId as unknown as string,
|
|
379
|
-
},
|
|
380
|
-
requestId,
|
|
381
|
-
),
|
|
382
|
-
);
|
|
366
|
+
// Racy — the project can be deleted between the preliminary get and the add.
|
|
367
|
+
const projectGone = () => {
|
|
368
|
+
c.status(statusFor('project-not-found') as never);
|
|
369
|
+
return c.json(
|
|
370
|
+
toWireError(
|
|
371
|
+
{
|
|
372
|
+
code: 'project-not-found',
|
|
373
|
+
message: `No project with id "${projectId as unknown as string}"`,
|
|
374
|
+
projectId: projectId as unknown as string,
|
|
375
|
+
},
|
|
376
|
+
requestId,
|
|
377
|
+
),
|
|
378
|
+
);
|
|
379
|
+
};
|
|
380
|
+
// With an authorizer, add through the tenant-hierarchy binding — it
|
|
381
|
+
// writes the FGA `<role>@project` tuple atomically so the granted
|
|
382
|
+
// user's permissions land in FGA.
|
|
383
|
+
if (authorizer !== undefined) {
|
|
384
|
+
const res = await tenantHierarchy.addProjectMember({
|
|
385
|
+
tenantId,
|
|
386
|
+
projectId,
|
|
387
|
+
userId: b.userId as unknown as UserId,
|
|
388
|
+
role: b.role as ProjectRole,
|
|
389
|
+
});
|
|
390
|
+
if (res.kind === 'err') {
|
|
391
|
+
if (res.error.code === 'project-not-found') return projectGone();
|
|
392
|
+
throw new Error(res.error.message, { cause: res.error });
|
|
383
393
|
}
|
|
384
|
-
|
|
394
|
+
} else {
|
|
395
|
+
const outcome = await membershipBinding.add(tenantId, {
|
|
396
|
+
projectId,
|
|
397
|
+
userId: b.userId as unknown as UserId,
|
|
398
|
+
role: b.role,
|
|
399
|
+
});
|
|
400
|
+
if (outcome.kind === 'project-not-found') return projectGone();
|
|
385
401
|
}
|
|
386
402
|
c.status(201);
|
|
387
403
|
return c.json({
|
|
@@ -436,38 +452,33 @@ export function projectsRouter(
|
|
|
436
452
|
),
|
|
437
453
|
);
|
|
438
454
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
requestId,
|
|
467
|
-
),
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
throw cause;
|
|
455
|
+
const outcome = await membershipBinding.updateRole(tenantId, projectId, userId, b.role);
|
|
456
|
+
if (outcome.kind === 'project-membership-not-found') {
|
|
457
|
+
c.status(statusFor('project-membership-not-found') as never);
|
|
458
|
+
return c.json(
|
|
459
|
+
toWireError(
|
|
460
|
+
{
|
|
461
|
+
code: 'project-membership-not-found',
|
|
462
|
+
message: `No membership for project "${projectId as unknown as string}" + user "${userId as unknown as string}"`,
|
|
463
|
+
projectId: projectId as unknown as string,
|
|
464
|
+
userId: userId as unknown as string,
|
|
465
|
+
},
|
|
466
|
+
requestId,
|
|
467
|
+
),
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
if (outcome.kind === 'project-not-found') {
|
|
471
|
+
c.status(statusFor('project-not-found') as never);
|
|
472
|
+
return c.json(
|
|
473
|
+
toWireError(
|
|
474
|
+
{
|
|
475
|
+
code: 'project-not-found',
|
|
476
|
+
message: `No project with id "${projectId as unknown as string}"`,
|
|
477
|
+
projectId: projectId as unknown as string,
|
|
478
|
+
},
|
|
479
|
+
requestId,
|
|
480
|
+
),
|
|
481
|
+
);
|
|
471
482
|
}
|
|
472
483
|
c.status(204);
|
|
473
484
|
return c.body(null);
|
|
@@ -571,24 +582,24 @@ export function projectsRouter(
|
|
|
571
582
|
);
|
|
572
583
|
}
|
|
573
584
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
585
|
+
const outcome = await binding.update(tenantId, projectId, shaped);
|
|
586
|
+
if (outcome.kind === 'project-not-found') {
|
|
587
|
+
c.status(statusFor('project-not-found') as never);
|
|
588
|
+
return c.json(
|
|
589
|
+
toWireError(
|
|
590
|
+
{
|
|
591
|
+
code: 'project-not-found',
|
|
592
|
+
message: `No project with id "${projectId as unknown as string}"`,
|
|
593
|
+
projectId: projectId as unknown as string,
|
|
594
|
+
},
|
|
595
|
+
requestId,
|
|
596
|
+
),
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
if (outcome.kind === 'slug-conflict') {
|
|
600
|
+
const error = slugConflictError('project', outcome.slug);
|
|
601
|
+
c.status(statusFor(error.code) as never);
|
|
602
|
+
return c.json(toWireError(error, requestId));
|
|
592
603
|
}
|
|
593
604
|
c.status(204);
|
|
594
605
|
return c.body(null);
|
package/src/routes/providers.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type Feature,
|
|
10
10
|
type ModelInfo,
|
|
11
11
|
type ProviderMetadata,
|
|
12
|
+
validateProviderLabels,
|
|
12
13
|
} from '@kindgi/capabilities';
|
|
13
14
|
import type { Cursor, TenantId } from '@kindgi/types';
|
|
14
15
|
|
|
@@ -316,6 +317,7 @@ function serializeProvider(m: ProviderMetadata): Record<string, unknown> {
|
|
|
316
317
|
...(m.description !== undefined && { description: m.description }),
|
|
317
318
|
...(m.capabilityKind !== undefined && { capabilityKind: m.capabilityKind }),
|
|
318
319
|
...(m.fallback !== undefined && { fallback: m.fallback }),
|
|
320
|
+
...(m.labels !== undefined && { labels: m.labels }),
|
|
319
321
|
};
|
|
320
322
|
}
|
|
321
323
|
|
|
@@ -432,6 +434,8 @@ function validateProviderMetadata(
|
|
|
432
434
|
},
|
|
433
435
|
};
|
|
434
436
|
}
|
|
437
|
+
const badLabels = validateProviderLabels(b.id, b.labels);
|
|
438
|
+
if (badLabels !== undefined) return { kind: 'err', error: badLabels };
|
|
435
439
|
const value: ProviderMetadata = {
|
|
436
440
|
id: b.id,
|
|
437
441
|
region: b.region,
|
|
@@ -440,6 +444,7 @@ function validateProviderMetadata(
|
|
|
440
444
|
...(b.description !== undefined && { description: b.description }),
|
|
441
445
|
...(b.capabilityKind !== undefined && { capabilityKind: b.capabilityKind as string }),
|
|
442
446
|
...(b.fallback !== undefined && { fallback: b.fallback as boolean }),
|
|
447
|
+
...(b.labels !== undefined && { labels: b.labels as Readonly<Record<string, string>> }),
|
|
443
448
|
};
|
|
444
449
|
return { kind: 'ok', value };
|
|
445
450
|
}
|
package/src/routes/runs.ts
CHANGED
|
@@ -654,7 +654,8 @@ function invokeFromBody(
|
|
|
654
654
|
* Wire row for a run. `output` is the run's output once it completed;
|
|
655
655
|
* single-run responses carry it, lists only with `?include=output`
|
|
656
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
|
|
657
|
+
* an agent's turns, the agent, its version and the conversation; a
|
|
658
|
+
* replay run, the run it replays and its eval run.
|
|
658
659
|
*/
|
|
659
660
|
function serializeRun(
|
|
660
661
|
row: KernelRunRecord,
|
|
@@ -682,6 +683,8 @@ function serializeRun(
|
|
|
682
683
|
conversationId: row.agent.conversationId as unknown as string,
|
|
683
684
|
},
|
|
684
685
|
}),
|
|
686
|
+
...(row.replayOf != null && { replayOf: row.replayOf as unknown as string }),
|
|
687
|
+
...(row.evalRunId != null && { evalRunId: row.evalRunId }),
|
|
685
688
|
};
|
|
686
689
|
}
|
|
687
690
|
|
|
@@ -742,6 +745,8 @@ function listRunsInput(input: {
|
|
|
742
745
|
...(filter.parentRunId !== undefined && { parent: { runId: filter.parentRunId } }),
|
|
743
746
|
...(filter.topLevelOnly && { topLevelOnly: true }),
|
|
744
747
|
...(filter.agentId !== undefined && { agentId: filter.agentId }),
|
|
748
|
+
replays: filter.replays,
|
|
749
|
+
...(filter.evalRunId !== undefined && { evalRunId: filter.evalRunId }),
|
|
745
750
|
};
|
|
746
751
|
}
|
|
747
752
|
|
|
@@ -749,17 +754,21 @@ interface RunListFilter {
|
|
|
749
754
|
readonly parentRunId?: RunId;
|
|
750
755
|
readonly topLevelOnly: boolean;
|
|
751
756
|
readonly agentId?: string;
|
|
757
|
+
readonly replays: 'exclude' | 'include' | 'only';
|
|
758
|
+
readonly evalRunId?: string;
|
|
752
759
|
readonly includeOutput: boolean;
|
|
753
760
|
}
|
|
754
761
|
|
|
755
762
|
/**
|
|
756
763
|
* `?parentRunId=` (children of a run), `?topLevel=true`, `?agentId=` (an
|
|
757
|
-
* agent's turns), `?include
|
|
764
|
+
* agent's turns), `?replays=exclude|include|only` (default `exclude`),
|
|
765
|
+
* `?evalRunId=` (one eval run's replays; implies they are included),
|
|
766
|
+
* `?include=output`.
|
|
758
767
|
*/
|
|
759
768
|
function parseRunListFilter(
|
|
760
769
|
query: Readonly<Record<string, string>>,
|
|
761
770
|
): { kind: 'ok'; value: RunListFilter } | { kind: 'err'; message: string } {
|
|
762
|
-
const { parentRunId, topLevel, agentId, include } = query;
|
|
771
|
+
const { parentRunId, topLevel, agentId, replays, evalRunId, include } = query;
|
|
763
772
|
if (agentId !== undefined && agentId.trim() === '') {
|
|
764
773
|
return { kind: 'err', message: '`agentId` must not be empty' };
|
|
765
774
|
}
|
|
@@ -773,6 +782,20 @@ function parseRunListFilter(
|
|
|
773
782
|
if (parentRunId !== undefined && topLevelOnly) {
|
|
774
783
|
return { kind: 'err', message: '`parentRunId` and `topLevel=true` cannot be combined' };
|
|
775
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
|
+
}
|
|
776
799
|
const includes = include === undefined ? [] : include.split(',').map((i) => i.trim());
|
|
777
800
|
const unknown = includes.filter((i) => i !== 'output');
|
|
778
801
|
if (unknown.length > 0) {
|
|
@@ -784,6 +807,8 @@ function parseRunListFilter(
|
|
|
784
807
|
...(parentRunId !== undefined && { parentRunId: parentRunId as RunId }),
|
|
785
808
|
topLevelOnly,
|
|
786
809
|
...(agentId !== undefined && { agentId }),
|
|
810
|
+
replays: replays ?? (evalRunId !== undefined ? 'include' : 'exclude'),
|
|
811
|
+
...(evalRunId !== undefined && { evalRunId }),
|
|
787
812
|
includeOutput: includes.includes('output'),
|
|
788
813
|
},
|
|
789
814
|
};
|