@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/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/provenance.ts
CHANGED
|
@@ -12,12 +12,15 @@ import type { ConversationId, RunId, SigningKeyId, TenantId, Timestamp } from '@
|
|
|
12
12
|
import { statusFor, toWireError } from '../errors.js';
|
|
13
13
|
|
|
14
14
|
import type {
|
|
15
|
+
CallUsageByCallId,
|
|
15
16
|
ProvenanceBinding,
|
|
17
|
+
ProvenanceBindingError,
|
|
16
18
|
ProvenanceListCursor,
|
|
17
19
|
ProvenanceRecordSummary,
|
|
18
20
|
} from '../provenance-binding.js';
|
|
19
21
|
import type { AppEnv } from '../types.js';
|
|
20
22
|
import { clampLimit, decodeCursor, encodeCursor } from './pagination.js';
|
|
23
|
+
import { parseListScope } from './scope-params.js';
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Provenance resource routes.
|
|
@@ -48,7 +51,8 @@ export interface ProvenanceRouterOptions {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/** Bundle schema version — bump when the wire shape of `bundle.body` changes. */
|
|
51
|
-
|
|
54
|
+
/** 1.1.0 adds `callUsage`: the model calls' usage from the cost ledger, when the run has any. */
|
|
55
|
+
const BUNDLE_SCHEMA_VERSION = '1.1.0';
|
|
52
56
|
|
|
53
57
|
export function provenanceRouter(
|
|
54
58
|
binding: ProvenanceBinding,
|
|
@@ -76,6 +80,14 @@ export function provenanceRouter(
|
|
|
76
80
|
cursorFilter = { createdAt: decoded.createdAt, id: decoded.id };
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
const scopeParsed = parseListScope(c.req.query(), { tenantId });
|
|
84
|
+
if (scopeParsed.kind === 'err') {
|
|
85
|
+
c.status(statusFor('scope-invalid') as never);
|
|
86
|
+
return c.json(
|
|
87
|
+
toWireError({ code: 'scope-invalid', message: scopeParsed.message }, requestId),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
79
91
|
const runIdFilter = c.req.query('runId');
|
|
80
92
|
const agentIdFilter = c.req.query('agentId');
|
|
81
93
|
const createdAfterRaw = c.req.query('createdAfter');
|
|
@@ -98,6 +110,7 @@ export function provenanceRouter(
|
|
|
98
110
|
const result = await binding.listRecords({
|
|
99
111
|
tenantId,
|
|
100
112
|
limit,
|
|
113
|
+
...(scopeParsed.scope !== undefined && { scope: scopeParsed.scope }),
|
|
101
114
|
...(runIdFilter !== undefined && runIdFilter.length > 0 && { runId: runIdFilter as RunId }),
|
|
102
115
|
...(agentIdFilter !== undefined && agentIdFilter.length > 0 && { agentId: agentIdFilter }),
|
|
103
116
|
...(createdAfter !== undefined && { createdAfter }),
|
|
@@ -148,6 +161,13 @@ export function provenanceRouter(
|
|
|
148
161
|
);
|
|
149
162
|
}
|
|
150
163
|
const provenance = result.value;
|
|
164
|
+
const callUsage = await readCallUsage(binding, tenantId, runId);
|
|
165
|
+
if (callUsage.kind === 'err') {
|
|
166
|
+
c.status(statusFor(callUsage.error.code) as never);
|
|
167
|
+
return c.json(
|
|
168
|
+
toWireError({ code: callUsage.error.code, message: callUsage.error.message }, requestId),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
151
171
|
return c.json({
|
|
152
172
|
id: provenance.id as unknown as string,
|
|
153
173
|
runId: provenance.runId as unknown as string,
|
|
@@ -165,6 +185,7 @@ export function provenanceRouter(
|
|
|
165
185
|
edges: provenance.edges,
|
|
166
186
|
},
|
|
167
187
|
...(provenance.signature !== undefined && { signature: provenance.signature }),
|
|
188
|
+
...(callUsage.value !== undefined && { callUsage: callUsage.value }),
|
|
168
189
|
});
|
|
169
190
|
});
|
|
170
191
|
|
|
@@ -232,6 +253,14 @@ export function provenanceRouter(
|
|
|
232
253
|
);
|
|
233
254
|
}
|
|
234
255
|
const provenance = loaded.value;
|
|
256
|
+
// The calls' usage as it stands now: the signature covers it.
|
|
257
|
+
const callUsage = await readCallUsage(binding, tenantId, runId);
|
|
258
|
+
if (callUsage.kind === 'err') {
|
|
259
|
+
c.status(statusFor(callUsage.error.code) as never);
|
|
260
|
+
return c.json(
|
|
261
|
+
toWireError({ code: callUsage.error.code, message: callUsage.error.message }, requestId),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
235
264
|
|
|
236
265
|
// 2. Optionally hydrate conversation messages. Agent turns use the
|
|
237
266
|
// conversation id as the run id; for flow-only runs no
|
|
@@ -306,6 +335,7 @@ export function provenanceRouter(
|
|
|
306
335
|
edges: provenance.edges,
|
|
307
336
|
},
|
|
308
337
|
...(messages !== undefined && { messages }),
|
|
338
|
+
...(callUsage.value !== undefined && { callUsage: callUsage.value }),
|
|
309
339
|
};
|
|
310
340
|
const canonicalBundleBytes = new TextEncoder().encode(canonicalize(bundleBody));
|
|
311
341
|
|
|
@@ -355,6 +385,7 @@ interface RecordMetadata {
|
|
|
355
385
|
readonly createdAt: string;
|
|
356
386
|
readonly flowRef?: { readonly id: string; readonly version: string };
|
|
357
387
|
readonly signed: boolean;
|
|
388
|
+
readonly projectId?: string;
|
|
358
389
|
}
|
|
359
390
|
|
|
360
391
|
function serializeRecordMetadata(row: ProvenanceRecordSummary): RecordMetadata {
|
|
@@ -371,6 +402,7 @@ function serializeRecordMetadata(row: ProvenanceRecordSummary): RecordMetadata {
|
|
|
371
402
|
},
|
|
372
403
|
}),
|
|
373
404
|
signed: row.signed,
|
|
405
|
+
...(row.projectId !== undefined && { projectId: row.projectId as unknown as string }),
|
|
374
406
|
};
|
|
375
407
|
}
|
|
376
408
|
|
|
@@ -404,3 +436,18 @@ function parseExportBody(body: Record<string, unknown>): ParseResult<ValidatedEx
|
|
|
404
436
|
},
|
|
405
437
|
};
|
|
406
438
|
}
|
|
439
|
+
|
|
440
|
+
/** The run's call usage, when the binding has a cost ledger and the run made calls. */
|
|
441
|
+
async function readCallUsage(
|
|
442
|
+
binding: ProvenanceBinding,
|
|
443
|
+
tenantId: TenantId,
|
|
444
|
+
runId: RunId,
|
|
445
|
+
): Promise<
|
|
446
|
+
| { readonly kind: 'ok'; readonly value: CallUsageByCallId | undefined }
|
|
447
|
+
| { readonly kind: 'err'; readonly error: ProvenanceBindingError }
|
|
448
|
+
> {
|
|
449
|
+
if (binding.getCallUsage === undefined) return { kind: 'ok', value: undefined };
|
|
450
|
+
const read = await binding.getCallUsage(tenantId, runId);
|
|
451
|
+
if (read.kind === 'err') return read;
|
|
452
|
+
return { kind: 'ok', value: Object.keys(read.value).length > 0 ? read.value : undefined };
|
|
453
|
+
}
|
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
|
}
|