@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/provider-binding.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { CapabilityDescriptor } from './capability-binding.js';
|
|
|
18
18
|
* endpoints, credentials) cross the HTTP boundary. Secrets stay inside
|
|
19
19
|
* the binding implementation; the wire surface returns only routing-
|
|
20
20
|
* relevant metadata: provider-level `id` / `region` / `attributes` /
|
|
21
|
-
* `capabilityKind` + `models[]` (each entry carries `name`,
|
|
21
|
+
* `capabilityKind` / `labels` + `models[]` (each entry carries `name`,
|
|
22
22
|
* `contextWindow`, `features`, `cost`, optional `p95LatencyMs`,
|
|
23
23
|
* `maxOutputTokens`, `description`).
|
|
24
24
|
*
|
|
@@ -38,8 +38,8 @@ export interface ProviderRegistryBinding {
|
|
|
38
38
|
*/
|
|
39
39
|
list(input: ProviderListInput): Promise<ProviderPage>;
|
|
40
40
|
/**
|
|
41
|
-
* Fetch a provider by id, or `null` when unknown. The
|
|
42
|
-
* `null` as `404 provider-not-found`.
|
|
41
|
+
* Fetch a provider by id, or `null` when unknown or unregistered. The
|
|
42
|
+
* route surfaces `null` as `404 provider-not-found`.
|
|
43
43
|
*/
|
|
44
44
|
get(input: ProviderGetInput): Promise<ProviderMetadata | null>;
|
|
45
45
|
/**
|
|
@@ -47,13 +47,18 @@ export interface ProviderRegistryBinding {
|
|
|
47
47
|
* the wire shape via the `@kindgi/capabilities` runtime rules
|
|
48
48
|
* before calling — the binding receives well-formed metadata.
|
|
49
49
|
* Bindings MAY reject with `already-registered` when the same
|
|
50
|
-
* `providerId` is re-registered; the route maps that to `409`.
|
|
50
|
+
* `providerId` is re-registered; the route maps that to `409`. An
|
|
51
|
+
* unregistered id is free: registering it again makes a new provider.
|
|
51
52
|
*/
|
|
52
53
|
register(input: ProviderRegisterInput): Promise<ProviderRegisterOutcome>;
|
|
53
54
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
55
|
+
* Unregister a provider: a tombstone, not an erase. From then on the
|
|
56
|
+
* provider is gone from `list`, `get`, `capabilitiesFor` and
|
|
57
|
+
* `resolveForRuntime`, and the router never picks it; a retention
|
|
58
|
+
* policy on the `provider` domain purges the row. Returns
|
|
59
|
+
* `{ unregistered: true }` on success; `{ unregistered: false }` when
|
|
60
|
+
* the id was unknown or already unregistered — the route flips the
|
|
61
|
+
* latter to `404`.
|
|
57
62
|
*/
|
|
58
63
|
unregister(input: ProviderUnregisterInput): Promise<ProviderUnregisterOutcome>;
|
|
59
64
|
/**
|
package/src/routes/agents.ts
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (C) 2026 Kindgi Inc.
|
|
3
3
|
|
|
4
|
-
import { Hono } from 'hono';
|
|
4
|
+
import { type Context, Hono } from 'hono';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type Agent,
|
|
8
|
+
type AgentId,
|
|
9
|
+
type DefineAgentSpec,
|
|
10
|
+
defineAgent,
|
|
11
|
+
pinsDigest,
|
|
12
|
+
} from '@kindgi/agents';
|
|
7
13
|
import { type Principal, ref, tuplesForCreate } from '@kindgi/authz';
|
|
8
14
|
import type { Cursor, ProjectId, Semver, TenantId, UserId } from '@kindgi/types';
|
|
9
15
|
|
|
10
|
-
import type { AgentRegistryBinding } from '../agent-binding.js';
|
|
16
|
+
import type { AgentRegistryBinding, AgentVersionRecord } from '../agent-binding.js';
|
|
17
|
+
import { resolveAgentPins } from '../agent-pins.js';
|
|
18
|
+
import type { BlockRegistryBinding } from '../block-binding.js';
|
|
19
|
+
import {
|
|
20
|
+
type DeriveAgentVersionOutcome,
|
|
21
|
+
type PinSwaps,
|
|
22
|
+
deriveAgentVersion,
|
|
23
|
+
nextFreeAgentVersion,
|
|
24
|
+
} from '../derive-agent-version.js';
|
|
11
25
|
import { statusFor, toWireError } from '../errors.js';
|
|
12
26
|
import type { Authorizer } from '../middleware/authorize.js';
|
|
27
|
+
import type { ToolRegistryBinding } from '../tool-binding.js';
|
|
13
28
|
import type { AppEnv } from '../types.js';
|
|
14
29
|
import { clampLimit } from './pagination.js';
|
|
15
30
|
import { parseScopeParams } from './scope-params.js';
|
|
@@ -24,8 +39,19 @@ import { parseScopeParams } from './scope-params.js';
|
|
|
24
39
|
*
|
|
25
40
|
* Publish is data-as-code: the body is a full `DefineAgentSpec` (the
|
|
26
41
|
* same value `defineAgent(...)` accepts).
|
|
42
|
+
*
|
|
43
|
+
* With `toolRegistry`, a published version is pinned: each tool range
|
|
44
|
+
* resolves once, at publish, to the exact version every run of that
|
|
45
|
+
* version uses (`pins`, see `resolveAgentPins`), and a range that
|
|
46
|
+
* matches no published version refuses the publish. Without it,
|
|
47
|
+
* versions carry no pins and resolve their ranges per run.
|
|
27
48
|
*/
|
|
28
|
-
export function agentsRouter(
|
|
49
|
+
export function agentsRouter(
|
|
50
|
+
binding: AgentRegistryBinding,
|
|
51
|
+
authorizer?: Authorizer,
|
|
52
|
+
toolRegistry?: ToolRegistryBinding,
|
|
53
|
+
blockRegistry?: BlockRegistryBinding,
|
|
54
|
+
): Hono<AppEnv> {
|
|
29
55
|
const r = new Hono<AppEnv>();
|
|
30
56
|
|
|
31
57
|
// Authorization — check the resource directly (ref('agent', businessId));
|
|
@@ -35,6 +61,7 @@ export function agentsRouter(binding: AgentRegistryBinding, authorizer?: Authori
|
|
|
35
61
|
//
|
|
36
62
|
// POST / → admin on body.projectId
|
|
37
63
|
// GET /:agentId, GET /:agentId/versions[/…] → read on the agent
|
|
64
|
+
// POST /:agentId/versions → publish on the agent (derive)
|
|
38
65
|
// POST /:agentId/versions/:version/unregister → admin on the agent
|
|
39
66
|
// POST /:agentId/versions/:version/reinstate → admin on the agent
|
|
40
67
|
// GET / (list) → tenant-scoped fetch;
|
|
@@ -73,7 +100,10 @@ export function agentsRouter(binding: AgentRegistryBinding, authorizer?: Authori
|
|
|
73
100
|
return mw(c, next);
|
|
74
101
|
});
|
|
75
102
|
r.use('/:agentId/*', async (c, next) => {
|
|
76
|
-
|
|
103
|
+
// Deriving a version (POST …/versions) is `publish` on the agent;
|
|
104
|
+
// unregister and reinstate are `admin`.
|
|
105
|
+
const action =
|
|
106
|
+
c.req.method === 'GET' ? 'read' : c.req.path.endsWith('/versions') ? 'publish' : 'admin';
|
|
77
107
|
const agentId = c.req.param('agentId') ?? '';
|
|
78
108
|
const mw = authorizer.authorize(action, () => ref('agent', agentId));
|
|
79
109
|
return mw(c, next);
|
|
@@ -281,6 +311,28 @@ export function agentsRouter(binding: AgentRegistryBinding, authorizer?: Authori
|
|
|
281
311
|
);
|
|
282
312
|
}
|
|
283
313
|
|
|
314
|
+
// Pin the version: every tool range resolves now, once, to the
|
|
315
|
+
// version its runs use. A range nothing satisfies refuses the
|
|
316
|
+
// publish rather than store a partly pinned version.
|
|
317
|
+
let agent: Agent = defined.value;
|
|
318
|
+
if (toolRegistry !== undefined) {
|
|
319
|
+
const resolved = await resolveAgentPins(toolRegistry, tenantId, defined.value, blockRegistry);
|
|
320
|
+
if (resolved.kind === 'unpinnable') {
|
|
321
|
+
c.status(statusFor('invalid-agent') as never);
|
|
322
|
+
return c.json(
|
|
323
|
+
toWireError(
|
|
324
|
+
{
|
|
325
|
+
code: 'validation-failed',
|
|
326
|
+
message: `Agent "${defined.value.id as unknown as string}" uses tool versions that aren't published (${resolved.issues.length} issue${resolved.issues.length === 1 ? '' : 's'})`,
|
|
327
|
+
issues: resolved.issues as unknown as Record<string, unknown>[],
|
|
328
|
+
},
|
|
329
|
+
requestId,
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
agent = { ...agent, pins: resolved.pins, pinsDigest: pinsDigest(resolved.pins) };
|
|
334
|
+
}
|
|
335
|
+
|
|
284
336
|
const principal = c.get('principal') as Principal | undefined;
|
|
285
337
|
const creatorUserId =
|
|
286
338
|
principal?.actor.kind === 'user'
|
|
@@ -289,7 +341,7 @@ export function agentsRouter(binding: AgentRegistryBinding, authorizer?: Authori
|
|
|
289
341
|
const outcome = await binding.publish({
|
|
290
342
|
tenantId,
|
|
291
343
|
projectId,
|
|
292
|
-
agent
|
|
344
|
+
agent,
|
|
293
345
|
// Write the authorization tuples in the same transaction as the
|
|
294
346
|
// registry row. The binding calls this with the business
|
|
295
347
|
// `agentId`.
|
|
@@ -300,18 +352,7 @@ export function agentsRouter(binding: AgentRegistryBinding, authorizer?: Authori
|
|
|
300
352
|
),
|
|
301
353
|
});
|
|
302
354
|
if (outcome.kind === 'already-registered') {
|
|
303
|
-
c.
|
|
304
|
-
return c.json(
|
|
305
|
-
toWireError(
|
|
306
|
-
{
|
|
307
|
-
code: 'agent-already-registered',
|
|
308
|
-
message: `Agent "${outcome.agentId as unknown as string}" version "${outcome.version as unknown as string}" is already registered`,
|
|
309
|
-
agentId: outcome.agentId as unknown as string,
|
|
310
|
-
version: outcome.version as unknown as string,
|
|
311
|
-
},
|
|
312
|
-
requestId,
|
|
313
|
-
),
|
|
314
|
-
);
|
|
355
|
+
return alreadyRegistered(c, binding, tenantId, outcome.agentId, outcome.version);
|
|
315
356
|
}
|
|
316
357
|
if (outcome.kind === 'project-not-found') {
|
|
317
358
|
// Caller supplied a `projectId` that does not resolve within
|
|
@@ -337,6 +378,47 @@ export function agentsRouter(binding: AgentRegistryBinding, authorizer?: Authori
|
|
|
337
378
|
});
|
|
338
379
|
});
|
|
339
380
|
|
|
381
|
+
// ---------- POST /:agentId/versions (derive a version) ----------
|
|
382
|
+
r.post('/:agentId/versions', async (c) => {
|
|
383
|
+
const requestId = c.get('requestId');
|
|
384
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
385
|
+
const agentId = c.req.param('agentId') as AgentId;
|
|
386
|
+
const body = await jsonObject(c);
|
|
387
|
+
if (body === undefined) {
|
|
388
|
+
c.status(statusFor('bad-input') as never);
|
|
389
|
+
return c.json(
|
|
390
|
+
toWireError(
|
|
391
|
+
{ code: 'bad-input', message: 'Request body must be a JSON object' },
|
|
392
|
+
requestId,
|
|
393
|
+
),
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const parsed = deriveBody(body);
|
|
397
|
+
if (typeof parsed === 'string') {
|
|
398
|
+
c.status(statusFor('bad-input') as never);
|
|
399
|
+
return c.json(toWireError({ code: 'bad-input', message: parsed }, requestId));
|
|
400
|
+
}
|
|
401
|
+
const principal = c.get('principal') as Principal | undefined;
|
|
402
|
+
const userId = principal?.actor.kind === 'user' ? (principal.actor.id as UserId) : undefined;
|
|
403
|
+
const outcome = await deriveAgentVersion({
|
|
404
|
+
agents: binding,
|
|
405
|
+
blocks: blockRegistry,
|
|
406
|
+
tenantId,
|
|
407
|
+
agentId,
|
|
408
|
+
from: parsed.from,
|
|
409
|
+
swaps: parsed.pins,
|
|
410
|
+
...(parsed.label !== undefined && { label: parsed.label }),
|
|
411
|
+
...(userId !== undefined && { by: `user:${userId as unknown as string}` }),
|
|
412
|
+
...(parsed.projectId !== undefined && { projectId: parsed.projectId }),
|
|
413
|
+
tuplesFor: (projectId) => (id) =>
|
|
414
|
+
tuplesForCreate(
|
|
415
|
+
{ kind: 'agent', id: id as AgentId, tenantId, projectId },
|
|
416
|
+
userId ?? ('00000000-0000-0000-0000-000000000000' as UserId),
|
|
417
|
+
),
|
|
418
|
+
});
|
|
419
|
+
return derived(c, agentId, parsed.from, outcome);
|
|
420
|
+
});
|
|
421
|
+
|
|
340
422
|
// ---------- POST /:agentId/versions/:version/unregister ----------
|
|
341
423
|
r.post('/:agentId/versions/:version/unregister', async (c) => {
|
|
342
424
|
const requestId = c.get('requestId');
|
|
@@ -403,7 +485,143 @@ export function agentsRouter(binding: AgentRegistryBinding, authorizer?: Authori
|
|
|
403
485
|
return r;
|
|
404
486
|
}
|
|
405
487
|
|
|
406
|
-
|
|
488
|
+
/**
|
|
489
|
+
* The 409 for a publish whose number is taken. Versions never change, so
|
|
490
|
+
* it names the next free one (an expert's derived version may hold it).
|
|
491
|
+
*/
|
|
492
|
+
async function alreadyRegistered(
|
|
493
|
+
c: Context<AppEnv>,
|
|
494
|
+
binding: AgentRegistryBinding,
|
|
495
|
+
tenantId: TenantId,
|
|
496
|
+
agentId: AgentId,
|
|
497
|
+
version: Semver,
|
|
498
|
+
) {
|
|
499
|
+
const next = await nextFreeAgentVersion(binding, tenantId, agentId, version as unknown as string);
|
|
500
|
+
const suggestion = next === undefined ? '' : `; publish it as ${next}, the next free version`;
|
|
501
|
+
c.status(statusFor('agent-already-registered') as never);
|
|
502
|
+
return c.json(
|
|
503
|
+
toWireError(
|
|
504
|
+
{
|
|
505
|
+
code: 'agent-already-registered',
|
|
506
|
+
message: `Agent "${agentId as unknown as string}" version "${version as unknown as string}" is already registered, and versions never change${suggestion}`,
|
|
507
|
+
agentId: agentId as unknown as string,
|
|
508
|
+
version: version as unknown as string,
|
|
509
|
+
...(next !== undefined && { nextFreeVersion: next }),
|
|
510
|
+
},
|
|
511
|
+
c.get('requestId'),
|
|
512
|
+
),
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** The request body as an object; undefined when it isn't JSON or isn't an object. */
|
|
517
|
+
async function jsonObject(c: Context<AppEnv>): Promise<Record<string, unknown> | undefined> {
|
|
518
|
+
try {
|
|
519
|
+
const body = await c.req.json();
|
|
520
|
+
return body !== null && typeof body === 'object' && !Array.isArray(body)
|
|
521
|
+
? (body as Record<string, unknown>)
|
|
522
|
+
: undefined;
|
|
523
|
+
} catch {
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
interface DeriveBody {
|
|
529
|
+
readonly from: string;
|
|
530
|
+
readonly pins: PinSwaps;
|
|
531
|
+
readonly label?: string;
|
|
532
|
+
readonly projectId?: ProjectId;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** `{ from, pins: { prompts?, settings? }, label?, projectId? }`, or what's wrong with it. */
|
|
536
|
+
function deriveBody(b: Record<string, unknown>): DeriveBody | string {
|
|
537
|
+
if (typeof b.from !== 'string' || b.from.length === 0) {
|
|
538
|
+
return '`from` (the version to derive from) is required';
|
|
539
|
+
}
|
|
540
|
+
const pins = pinSwapsOf(b.pins);
|
|
541
|
+
if (typeof pins === 'string') return pins;
|
|
542
|
+
for (const key of ['label', 'projectId'] as const) {
|
|
543
|
+
if (b[key] !== undefined && typeof b[key] !== 'string') return `\`${key}\` must be a string`;
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
from: b.from,
|
|
547
|
+
pins,
|
|
548
|
+
...(typeof b.label === 'string' && { label: b.label }),
|
|
549
|
+
...(typeof b.projectId === 'string' && { projectId: b.projectId as ProjectId }),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** `pins`: prompt and settings pins only, each block id → exact version; or what's wrong with it. */
|
|
554
|
+
function pinSwapsOf(pins: unknown): PinSwaps | string {
|
|
555
|
+
if (pins === null || typeof pins !== 'object' || Array.isArray(pins)) {
|
|
556
|
+
return '`pins` must be { prompts?, settings? }';
|
|
557
|
+
}
|
|
558
|
+
for (const [kind, map] of Object.entries(pins)) {
|
|
559
|
+
if (kind !== 'prompts' && kind !== 'settings') {
|
|
560
|
+
return `\`pins.${kind}\` can't be swapped: only prompt and settings pins (tool pins come from code)`;
|
|
561
|
+
}
|
|
562
|
+
if (!isStringMap(map)) return `\`pins.${kind}\` must map block ids to exact versions`;
|
|
563
|
+
}
|
|
564
|
+
return pins as PinSwaps;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function isStringMap(value: unknown): boolean {
|
|
568
|
+
return (
|
|
569
|
+
value !== null &&
|
|
570
|
+
typeof value === 'object' &&
|
|
571
|
+
!Array.isArray(value) &&
|
|
572
|
+
Object.values(value).every((v) => typeof v === 'string')
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** The response to a derive. */
|
|
577
|
+
function derived(
|
|
578
|
+
c: Context<AppEnv>,
|
|
579
|
+
agentId: AgentId,
|
|
580
|
+
from: string,
|
|
581
|
+
outcome: DeriveAgentVersionOutcome,
|
|
582
|
+
) {
|
|
583
|
+
const requestId = c.get('requestId');
|
|
584
|
+
const fail = (
|
|
585
|
+
status: string,
|
|
586
|
+
error: { code: string; message: string } & Record<string, unknown>,
|
|
587
|
+
) => {
|
|
588
|
+
c.status(statusFor(status) as never);
|
|
589
|
+
return c.json(toWireError(error, requestId));
|
|
590
|
+
};
|
|
591
|
+
switch (outcome.kind) {
|
|
592
|
+
case 'ok':
|
|
593
|
+
c.status(201);
|
|
594
|
+
return c.json(serializeAgent(outcome.agent));
|
|
595
|
+
case 'not-found':
|
|
596
|
+
return fail('agent-not-found', {
|
|
597
|
+
code: 'agent-not-found',
|
|
598
|
+
message: `No agent "${agentId as unknown as string}" at version "${from}"`,
|
|
599
|
+
});
|
|
600
|
+
case 'unpinned':
|
|
601
|
+
return fail('validation-failed', {
|
|
602
|
+
code: 'validation-failed',
|
|
603
|
+
message: `Agent "${agentId as unknown as string}" version ${from} has no pins (it was published before pins); publish it again to pin it, then derive`,
|
|
604
|
+
});
|
|
605
|
+
case 'invalid':
|
|
606
|
+
return fail('validation-failed', {
|
|
607
|
+
code: 'validation-failed',
|
|
608
|
+
message: `Can't derive from version ${from} (${outcome.issues.length} issue${outcome.issues.length === 1 ? '' : 's'})`,
|
|
609
|
+
issues: outcome.issues as unknown as Record<string, unknown>[],
|
|
610
|
+
});
|
|
611
|
+
case 'no-project':
|
|
612
|
+
return fail('bad-input', {
|
|
613
|
+
code: 'bad-input',
|
|
614
|
+
message: "`projectId` is required: this runtime doesn't record the version's project",
|
|
615
|
+
});
|
|
616
|
+
case 'project-not-found':
|
|
617
|
+
return fail('bad-input', {
|
|
618
|
+
code: 'bad-input',
|
|
619
|
+
message: `\`projectId\` "${outcome.projectId as unknown as string}" does not resolve to a project in this tenant`,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function serializeAgent(a: AgentVersionRecord): Record<string, unknown> {
|
|
407
625
|
return {
|
|
408
626
|
id: a.id as unknown as string,
|
|
409
627
|
version: a.version as unknown as string,
|
|
@@ -411,6 +629,8 @@ function serializeAgent(a: Agent): Record<string, unknown> {
|
|
|
411
629
|
...(a.description !== undefined && { description: a.description }),
|
|
412
630
|
instructions: a.instructions,
|
|
413
631
|
...(a.parameters !== undefined && { parameters: a.parameters }),
|
|
632
|
+
...(a.settings !== undefined && { settings: a.settings }),
|
|
633
|
+
...(a.modelSettings !== undefined && { modelSettings: a.modelSettings }),
|
|
414
634
|
capabilities: a.capabilities,
|
|
415
635
|
tools: a.tools,
|
|
416
636
|
retrieval: a.retrieval,
|
|
@@ -420,5 +640,9 @@ function serializeAgent(a: Agent): Record<string, unknown> {
|
|
|
420
640
|
...(a.tags !== undefined && { tags: a.tags }),
|
|
421
641
|
...(a.preferredProvider !== undefined && { preferredProvider: a.preferredProvider }),
|
|
422
642
|
...(a.preferredModel !== undefined && { preferredModel: a.preferredModel }),
|
|
643
|
+
...(a.pins !== undefined && { pins: a.pins }),
|
|
644
|
+
...(a.pinsDigest !== undefined && { pinsDigest: a.pinsDigest }),
|
|
645
|
+
...(a.derivedFrom !== undefined && { derivedFrom: a.derivedFrom }),
|
|
646
|
+
...(a.unregisteredAt !== undefined && { unregisteredAt: a.unregisteredAt }),
|
|
423
647
|
};
|
|
424
648
|
}
|