@kindgi/api 0.1.3 → 0.1.4-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-binding.d.ts +26 -4
- package/dist/agent-binding.d.ts.map +1 -1
- package/dist/agent-pins.d.ts +48 -0
- package/dist/agent-pins.d.ts.map +1 -0
- package/dist/agent-pins.js +102 -0
- package/dist/agent-pins.js.map +1 -0
- package/dist/app.d.ts +22 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +27 -3
- package/dist/app.js.map +1 -1
- package/dist/block-binding.d.ts +132 -0
- package/dist/block-binding.d.ts.map +1 -0
- package/dist/block-binding.js +4 -0
- package/dist/block-binding.js.map +1 -0
- package/dist/block-pins.d.ts +22 -0
- package/dist/block-pins.d.ts.map +1 -0
- package/dist/block-pins.js +112 -0
- package/dist/block-pins.js.map +1 -0
- package/dist/cost-binding.d.ts +20 -1
- package/dist/cost-binding.d.ts.map +1 -1
- package/dist/cost-binding.js +4 -0
- package/dist/cost-binding.js.map +1 -1
- package/dist/deploy-versions.d.ts +60 -0
- package/dist/deploy-versions.d.ts.map +1 -0
- package/dist/deploy-versions.js +91 -0
- package/dist/deploy-versions.js.map +1 -0
- package/dist/deployment-binding.d.ts +24 -3
- package/dist/deployment-binding.d.ts.map +1 -1
- package/dist/derive-agent-version.d.ts +77 -0
- package/dist/derive-agent-version.d.ts.map +1 -0
- package/dist/derive-agent-version.js +149 -0
- package/dist/derive-agent-version.js.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -1
- package/dist/eval-case-binding.d.ts +65 -0
- package/dist/eval-case-binding.d.ts.map +1 -0
- package/dist/eval-case-binding.js +4 -0
- package/dist/eval-case-binding.js.map +1 -0
- package/dist/eval-run-binding.d.ts +37 -0
- package/dist/eval-run-binding.d.ts.map +1 -1
- package/dist/eval-run-binding.js.map +1 -1
- package/dist/eval-run-dispatcher.d.ts +41 -3
- package/dist/eval-run-dispatcher.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.js +21 -15
- package/dist/eval-run-dispatcher.js.map +1 -1
- package/dist/eval-suite-binding.d.ts +1 -1
- package/dist/eval-suite-binding.d.ts.map +1 -1
- package/dist/eval-suite-binding.js +2 -0
- package/dist/eval-suite-binding.js.map +1 -1
- package/dist/flow-binding.d.ts +18 -4
- package/dist/flow-binding.d.ts.map +1 -1
- package/dist/flow-pins.d.ts +36 -0
- package/dist/flow-pins.d.ts.map +1 -0
- package/dist/flow-pins.js +81 -0
- package/dist/flow-pins.js.map +1 -0
- package/dist/guardrail-binding.d.ts +8 -0
- package/dist/guardrail-binding.d.ts.map +1 -1
- package/dist/handler-binding.d.ts +3 -0
- package/dist/handler-binding.d.ts.map +1 -1
- package/dist/index.d.ts +18 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/judged-dispatcher.d.ts +138 -0
- package/dist/judged-dispatcher.d.ts.map +1 -0
- package/dist/judged-dispatcher.js +308 -0
- package/dist/judged-dispatcher.js.map +1 -0
- package/dist/judged-items.d.ts +86 -0
- package/dist/judged-items.d.ts.map +1 -0
- package/dist/judged-items.js +184 -0
- package/dist/judged-items.js.map +1 -0
- package/dist/judgment-binding.d.ts +316 -0
- package/dist/judgment-binding.d.ts.map +1 -0
- package/dist/judgment-binding.js +19 -0
- package/dist/judgment-binding.js.map +1 -0
- package/dist/openapi/generate.d.ts.map +1 -1
- package/dist/openapi/generate.js +4 -1
- package/dist/openapi/generate.js.map +1 -1
- package/dist/openapi/operations.d.ts.map +1 -1
- package/dist/openapi/operations.js +482 -13
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +46 -0
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +1350 -175
- package/dist/openapi/schemas.js.map +1 -1
- package/dist/provider-binding.d.ts +12 -7
- package/dist/provider-binding.d.ts.map +1 -1
- package/dist/registry-read-only.d.ts +32 -0
- package/dist/registry-read-only.d.ts.map +1 -0
- package/dist/registry-read-only.js +22 -0
- package/dist/registry-read-only.js.map +1 -0
- package/dist/routes/agents.d.ts +9 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +181 -11
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/blocks.d.ts +19 -0
- package/dist/routes/blocks.d.ts.map +1 -0
- package/dist/routes/blocks.js +306 -0
- package/dist/routes/blocks.js.map +1 -0
- package/dist/routes/cost.d.ts.map +1 -1
- package/dist/routes/cost.js +47 -2
- package/dist/routes/cost.js.map +1 -1
- package/dist/routes/deployments.d.ts +3 -0
- package/dist/routes/deployments.d.ts.map +1 -1
- package/dist/routes/deployments.js +224 -55
- package/dist/routes/deployments.js.map +1 -1
- package/dist/routes/eval-comparison.d.ts +15 -0
- package/dist/routes/eval-comparison.d.ts.map +1 -0
- package/dist/routes/eval-comparison.js +123 -0
- package/dist/routes/eval-comparison.js.map +1 -0
- package/dist/routes/eval-runs.d.ts +7 -1
- package/dist/routes/eval-runs.d.ts.map +1 -1
- package/dist/routes/eval-runs.js +42 -3
- package/dist/routes/eval-runs.js.map +1 -1
- package/dist/routes/eval-versions.d.ts +25 -0
- package/dist/routes/eval-versions.d.ts.map +1 -0
- package/dist/routes/eval-versions.js +66 -0
- package/dist/routes/eval-versions.js.map +1 -0
- package/dist/routes/flows.d.ts +13 -1
- package/dist/routes/flows.d.ts.map +1 -1
- package/dist/routes/flows.js +48 -3
- package/dist/routes/flows.js.map +1 -1
- package/dist/routes/guardrails.d.ts.map +1 -1
- package/dist/routes/guardrails.js +4 -0
- package/dist/routes/guardrails.js.map +1 -1
- package/dist/routes/hierarchy-errors.d.ts +45 -0
- package/dist/routes/hierarchy-errors.d.ts.map +1 -0
- package/dist/routes/hierarchy-errors.js +47 -0
- package/dist/routes/hierarchy-errors.js.map +1 -0
- package/dist/routes/judged-suites.d.ts +20 -0
- package/dist/routes/judged-suites.d.ts.map +1 -0
- package/dist/routes/judged-suites.js +272 -0
- package/dist/routes/judged-suites.js.map +1 -0
- package/dist/routes/judgment-context.d.ts +22 -0
- package/dist/routes/judgment-context.d.ts.map +1 -0
- package/dist/routes/judgment-context.js +88 -0
- package/dist/routes/judgment-context.js.map +1 -0
- package/dist/routes/judgment-flow-context.d.ts +32 -0
- package/dist/routes/judgment-flow-context.d.ts.map +1 -0
- package/dist/routes/judgment-flow-context.js +195 -0
- package/dist/routes/judgment-flow-context.js.map +1 -0
- package/dist/routes/judgments.d.ts +41 -0
- package/dist/routes/judgments.d.ts.map +1 -0
- package/dist/routes/judgments.js +566 -0
- package/dist/routes/judgments.js.map +1 -0
- package/dist/routes/orgs.d.ts +5 -2
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +38 -22
- package/dist/routes/orgs.js.map +1 -1
- package/dist/routes/policies.d.ts.map +1 -1
- package/dist/routes/policies.js +12 -1
- package/dist/routes/policies.js.map +1 -1
- package/dist/routes/projects.d.ts +10 -2
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +94 -80
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/providers.d.ts.map +1 -1
- package/dist/routes/providers.js +6 -1
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/runs.js +25 -3
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/teams.d.ts +6 -2
- package/dist/routes/teams.d.ts.map +1 -1
- package/dist/routes/teams.js +83 -73
- package/dist/routes/teams.js.map +1 -1
- package/dist/routes/tools.d.ts.map +1 -1
- package/dist/routes/tools.js +4 -0
- package/dist/routes/tools.js.map +1 -1
- package/dist/tool-binding.d.ts +8 -0
- package/dist/tool-binding.d.ts.map +1 -1
- package/openapi.json +14253 -10099
- package/package.json +21 -21
- package/src/agent-binding.ts +28 -4
- package/src/agent-pins.ts +147 -0
- package/src/app.ts +81 -3
- package/src/block-binding.ts +137 -0
- package/src/block-pins.ts +148 -0
- package/src/cost-binding.ts +21 -1
- package/src/deploy-versions.ts +157 -0
- package/src/deployment-binding.ts +27 -3
- package/src/derive-agent-version.ts +217 -0
- package/src/errors.ts +18 -0
- package/src/eval-case-binding.ts +71 -0
- package/src/eval-run-binding.ts +40 -0
- package/src/eval-run-dispatcher.ts +60 -16
- package/src/eval-suite-binding.ts +2 -0
- package/src/flow-binding.ts +20 -4
- package/src/flow-pins.ts +113 -0
- package/src/guardrail-binding.ts +9 -0
- package/src/handler-binding.ts +3 -0
- package/src/index.ts +86 -2
- package/src/judged-dispatcher.ts +530 -0
- package/src/judged-items.ts +263 -0
- package/src/judgment-binding.ts +349 -0
- package/src/openapi/generate.ts +7 -1
- package/src/openapi/operations.ts +579 -13
- package/src/openapi/schemas.ts +1408 -128
- package/src/provider-binding.ts +12 -7
- package/src/registry-read-only.ts +43 -0
- package/src/routes/agents.ts +252 -19
- package/src/routes/blocks.ts +387 -0
- package/src/routes/cost.ts +55 -1
- package/src/routes/deployments.ts +291 -56
- package/src/routes/eval-comparison.ts +135 -0
- package/src/routes/eval-runs.ts +57 -3
- package/src/routes/eval-versions.ts +110 -0
- package/src/routes/flows.ts +70 -5
- package/src/routes/guardrails.ts +7 -0
- package/src/routes/hierarchy-errors.ts +60 -0
- package/src/routes/judged-suites.ts +363 -0
- package/src/routes/judgment-context.ts +128 -0
- package/src/routes/judgment-flow-context.ts +245 -0
- package/src/routes/judgments.ts +743 -0
- package/src/routes/orgs.ts +44 -27
- package/src/routes/policies.ts +19 -0
- package/src/routes/projects.ts +118 -96
- package/src/routes/providers.ts +5 -0
- package/src/routes/runs.ts +29 -3
- package/src/routes/teams.ts +104 -90
- package/src/routes/tools.ts +7 -0
- package/src/tool-binding.ts +9 -0
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,11 @@ 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 {
|
|
23
|
+
orgNotFoundError,
|
|
24
|
+
projectDefaultAlreadyExistsError,
|
|
25
|
+
slugConflictError,
|
|
26
|
+
} from './hierarchy-errors.js';
|
|
22
27
|
import { clampLimit } from './pagination.js';
|
|
23
28
|
|
|
24
29
|
/**
|
|
@@ -30,12 +35,20 @@ import { clampLimit } from './pagination.js';
|
|
|
30
35
|
* `?nameContains=`.
|
|
31
36
|
* - `POST /` — create; body `{ name, slug,
|
|
32
37
|
* orgId?, description?, isDefault? }`;
|
|
33
|
-
* 201 `{ id }
|
|
38
|
+
* 201 `{ id }`; 409 `slug-conflict`
|
|
39
|
+
* when its org (or, without an org,
|
|
40
|
+
* the tenant's projects without one)
|
|
41
|
+
* has a project with that slug, or
|
|
42
|
+
* `project-default-already-exists`
|
|
43
|
+
* for a second Default.
|
|
34
44
|
* - `GET /default` — returns the tenant's Default
|
|
35
45
|
* project or 404 `project-not-found`.
|
|
36
46
|
* MUST be mounted BEFORE `/:projectId`.
|
|
37
47
|
* - `GET /:projectId` — get; 200 or 404 `project-not-found`.
|
|
38
|
-
* - `PATCH /:projectId` — partial update; 204
|
|
48
|
+
* - `PATCH /:projectId` — partial update; 204, 404
|
|
49
|
+
* `project-not-found` or 409
|
|
50
|
+
* `slug-conflict` (a new slug, or a
|
|
51
|
+
* move to an org that has it).
|
|
39
52
|
* - `DELETE /:projectId` — 204 idempotent.
|
|
40
53
|
* - `GET /:projectId/memberships` — list; cursor-paginated.
|
|
41
54
|
* - `POST /:projectId/memberships` — add member; body `{ userId, role }`;
|
|
@@ -235,23 +248,35 @@ export function projectsRouter(
|
|
|
235
248
|
: ('00000000-0000-0000-0000-000000000000' as UserId);
|
|
236
249
|
const result = await tenantHierarchy.createProject({ tenantId, creatorUserId, spec });
|
|
237
250
|
if (result.kind === 'err') {
|
|
238
|
-
const
|
|
251
|
+
const error =
|
|
239
252
|
result.error.code === 'slug-conflict'
|
|
240
|
-
? 'slug
|
|
241
|
-
: result.error.code === '
|
|
242
|
-
?
|
|
243
|
-
: '
|
|
244
|
-
|
|
245
|
-
|
|
253
|
+
? slugConflictError('project', spec.slug)
|
|
254
|
+
: result.error.code === 'org-not-found'
|
|
255
|
+
? orgNotFoundError(result.error.orgId as unknown as string)
|
|
256
|
+
: result.error.code === 'default-conflict'
|
|
257
|
+
? projectDefaultAlreadyExistsError()
|
|
258
|
+
: { code: 'internal-server-error', message: result.error.message };
|
|
259
|
+
c.status(statusFor(error.code) as never);
|
|
260
|
+
return c.json(toWireError(error, requestId));
|
|
246
261
|
}
|
|
247
262
|
c.status(201);
|
|
248
263
|
return c.json({ id: result.value.projectId as unknown as string });
|
|
249
264
|
}
|
|
250
265
|
// No authorizer configured — fall back to the raw binding create;
|
|
251
266
|
// no FGA tuples written.
|
|
252
|
-
const
|
|
267
|
+
const outcome = await binding.create(tenantId, spec);
|
|
268
|
+
if (outcome.kind !== 'ok') {
|
|
269
|
+
const error =
|
|
270
|
+
outcome.kind === 'slug-conflict'
|
|
271
|
+
? slugConflictError('project', outcome.slug)
|
|
272
|
+
: outcome.kind === 'org-not-found'
|
|
273
|
+
? orgNotFoundError(outcome.orgId as unknown as string)
|
|
274
|
+
: projectDefaultAlreadyExistsError();
|
|
275
|
+
c.status(statusFor(error.code) as never);
|
|
276
|
+
return c.json(toWireError(error, requestId));
|
|
277
|
+
}
|
|
253
278
|
c.status(201);
|
|
254
|
-
return c.json({ id:
|
|
279
|
+
return c.json({ id: outcome.projectId as unknown as string });
|
|
255
280
|
});
|
|
256
281
|
|
|
257
282
|
// ---------- GET /:projectId/memberships ----------
|
|
@@ -346,42 +371,41 @@ export function projectsRouter(
|
|
|
346
371
|
),
|
|
347
372
|
);
|
|
348
373
|
}
|
|
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
|
-
);
|
|
374
|
+
// Racy — the project can be deleted between the preliminary get and the add.
|
|
375
|
+
const projectGone = () => {
|
|
376
|
+
c.status(statusFor('project-not-found') as never);
|
|
377
|
+
return c.json(
|
|
378
|
+
toWireError(
|
|
379
|
+
{
|
|
380
|
+
code: 'project-not-found',
|
|
381
|
+
message: `No project with id "${projectId as unknown as string}"`,
|
|
382
|
+
projectId: projectId as unknown as string,
|
|
383
|
+
},
|
|
384
|
+
requestId,
|
|
385
|
+
),
|
|
386
|
+
);
|
|
387
|
+
};
|
|
388
|
+
// With an authorizer, add through the tenant-hierarchy binding — it
|
|
389
|
+
// writes the FGA `<role>@project` tuple atomically so the granted
|
|
390
|
+
// user's permissions land in FGA.
|
|
391
|
+
if (authorizer !== undefined) {
|
|
392
|
+
const res = await tenantHierarchy.addProjectMember({
|
|
393
|
+
tenantId,
|
|
394
|
+
projectId,
|
|
395
|
+
userId: b.userId as unknown as UserId,
|
|
396
|
+
role: b.role as ProjectRole,
|
|
397
|
+
});
|
|
398
|
+
if (res.kind === 'err') {
|
|
399
|
+
if (res.error.code === 'project-not-found') return projectGone();
|
|
400
|
+
throw new Error(res.error.message, { cause: res.error });
|
|
383
401
|
}
|
|
384
|
-
|
|
402
|
+
} else {
|
|
403
|
+
const outcome = await membershipBinding.add(tenantId, {
|
|
404
|
+
projectId,
|
|
405
|
+
userId: b.userId as unknown as UserId,
|
|
406
|
+
role: b.role,
|
|
407
|
+
});
|
|
408
|
+
if (outcome.kind === 'project-not-found') return projectGone();
|
|
385
409
|
}
|
|
386
410
|
c.status(201);
|
|
387
411
|
return c.json({
|
|
@@ -436,38 +460,33 @@ export function projectsRouter(
|
|
|
436
460
|
),
|
|
437
461
|
);
|
|
438
462
|
}
|
|
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;
|
|
463
|
+
const outcome = await membershipBinding.updateRole(tenantId, projectId, userId, b.role);
|
|
464
|
+
if (outcome.kind === 'project-membership-not-found') {
|
|
465
|
+
c.status(statusFor('project-membership-not-found') as never);
|
|
466
|
+
return c.json(
|
|
467
|
+
toWireError(
|
|
468
|
+
{
|
|
469
|
+
code: 'project-membership-not-found',
|
|
470
|
+
message: `No membership for project "${projectId as unknown as string}" + user "${userId as unknown as string}"`,
|
|
471
|
+
projectId: projectId as unknown as string,
|
|
472
|
+
userId: userId as unknown as string,
|
|
473
|
+
},
|
|
474
|
+
requestId,
|
|
475
|
+
),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
if (outcome.kind === 'project-not-found') {
|
|
479
|
+
c.status(statusFor('project-not-found') as never);
|
|
480
|
+
return c.json(
|
|
481
|
+
toWireError(
|
|
482
|
+
{
|
|
483
|
+
code: 'project-not-found',
|
|
484
|
+
message: `No project with id "${projectId as unknown as string}"`,
|
|
485
|
+
projectId: projectId as unknown as string,
|
|
486
|
+
},
|
|
487
|
+
requestId,
|
|
488
|
+
),
|
|
489
|
+
);
|
|
471
490
|
}
|
|
472
491
|
c.status(204);
|
|
473
492
|
return c.body(null);
|
|
@@ -571,24 +590,27 @@ export function projectsRouter(
|
|
|
571
590
|
);
|
|
572
591
|
}
|
|
573
592
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
593
|
+
const outcome = await binding.update(tenantId, projectId, shaped);
|
|
594
|
+
if (outcome.kind === 'project-not-found') {
|
|
595
|
+
c.status(statusFor('project-not-found') as never);
|
|
596
|
+
return c.json(
|
|
597
|
+
toWireError(
|
|
598
|
+
{
|
|
599
|
+
code: 'project-not-found',
|
|
600
|
+
message: `No project with id "${projectId as unknown as string}"`,
|
|
601
|
+
projectId: projectId as unknown as string,
|
|
602
|
+
},
|
|
603
|
+
requestId,
|
|
604
|
+
),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
if (outcome.kind === 'slug-conflict' || outcome.kind === 'org-not-found') {
|
|
608
|
+
const error =
|
|
609
|
+
outcome.kind === 'slug-conflict'
|
|
610
|
+
? slugConflictError('project', outcome.slug)
|
|
611
|
+
: orgNotFoundError(outcome.orgId as unknown as string);
|
|
612
|
+
c.status(statusFor(error.code) as never);
|
|
613
|
+
return c.json(toWireError(error, requestId));
|
|
592
614
|
}
|
|
593
615
|
c.status(204);
|
|
594
616
|
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,9 @@ 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 }),
|
|
688
|
+
...(row.versions != null && { versions: row.versions }),
|
|
685
689
|
};
|
|
686
690
|
}
|
|
687
691
|
|
|
@@ -742,6 +746,8 @@ function listRunsInput(input: {
|
|
|
742
746
|
...(filter.parentRunId !== undefined && { parent: { runId: filter.parentRunId } }),
|
|
743
747
|
...(filter.topLevelOnly && { topLevelOnly: true }),
|
|
744
748
|
...(filter.agentId !== undefined && { agentId: filter.agentId }),
|
|
749
|
+
replays: filter.replays,
|
|
750
|
+
...(filter.evalRunId !== undefined && { evalRunId: filter.evalRunId }),
|
|
745
751
|
};
|
|
746
752
|
}
|
|
747
753
|
|
|
@@ -749,17 +755,21 @@ interface RunListFilter {
|
|
|
749
755
|
readonly parentRunId?: RunId;
|
|
750
756
|
readonly topLevelOnly: boolean;
|
|
751
757
|
readonly agentId?: string;
|
|
758
|
+
readonly replays: 'exclude' | 'include' | 'only';
|
|
759
|
+
readonly evalRunId?: string;
|
|
752
760
|
readonly includeOutput: boolean;
|
|
753
761
|
}
|
|
754
762
|
|
|
755
763
|
/**
|
|
756
764
|
* `?parentRunId=` (children of a run), `?topLevel=true`, `?agentId=` (an
|
|
757
|
-
* agent's turns), `?include
|
|
765
|
+
* agent's turns), `?replays=exclude|include|only` (default `exclude`),
|
|
766
|
+
* `?evalRunId=` (one eval run's replays; implies they are included),
|
|
767
|
+
* `?include=output`.
|
|
758
768
|
*/
|
|
759
769
|
function parseRunListFilter(
|
|
760
770
|
query: Readonly<Record<string, string>>,
|
|
761
771
|
): { kind: 'ok'; value: RunListFilter } | { kind: 'err'; message: string } {
|
|
762
|
-
const { parentRunId, topLevel, agentId, include } = query;
|
|
772
|
+
const { parentRunId, topLevel, agentId, replays, evalRunId, include } = query;
|
|
763
773
|
if (agentId !== undefined && agentId.trim() === '') {
|
|
764
774
|
return { kind: 'err', message: '`agentId` must not be empty' };
|
|
765
775
|
}
|
|
@@ -773,6 +783,20 @@ function parseRunListFilter(
|
|
|
773
783
|
if (parentRunId !== undefined && topLevelOnly) {
|
|
774
784
|
return { kind: 'err', message: '`parentRunId` and `topLevel=true` cannot be combined' };
|
|
775
785
|
}
|
|
786
|
+
if (
|
|
787
|
+
replays !== undefined &&
|
|
788
|
+
replays !== 'exclude' &&
|
|
789
|
+
replays !== 'include' &&
|
|
790
|
+
replays !== 'only'
|
|
791
|
+
) {
|
|
792
|
+
return { kind: 'err', message: '`replays` must be `exclude`, `include` or `only`' };
|
|
793
|
+
}
|
|
794
|
+
if (evalRunId !== undefined && evalRunId.trim() === '') {
|
|
795
|
+
return { kind: 'err', message: '`evalRunId` must not be empty' };
|
|
796
|
+
}
|
|
797
|
+
if (evalRunId !== undefined && replays === 'exclude') {
|
|
798
|
+
return { kind: 'err', message: '`evalRunId` and `replays=exclude` cannot be combined' };
|
|
799
|
+
}
|
|
776
800
|
const includes = include === undefined ? [] : include.split(',').map((i) => i.trim());
|
|
777
801
|
const unknown = includes.filter((i) => i !== 'output');
|
|
778
802
|
if (unknown.length > 0) {
|
|
@@ -784,6 +808,8 @@ function parseRunListFilter(
|
|
|
784
808
|
...(parentRunId !== undefined && { parentRunId: parentRunId as RunId }),
|
|
785
809
|
topLevelOnly,
|
|
786
810
|
...(agentId !== undefined && { agentId }),
|
|
811
|
+
replays: replays ?? (evalRunId !== undefined ? 'include' : 'exclude'),
|
|
812
|
+
...(evalRunId !== undefined && { evalRunId }),
|
|
787
813
|
includeOutput: includes.includes('output'),
|
|
788
814
|
},
|
|
789
815
|
};
|