@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/cost.ts
CHANGED
|
@@ -6,12 +6,15 @@ import { Hono } from 'hono';
|
|
|
6
6
|
import type { Cursor, TenantId, Timestamp } from '@kindgi/types';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
COST_AGGREGATE_DEFAULT_LIMIT,
|
|
10
|
+
COST_AGGREGATE_MAX_LIMIT,
|
|
9
11
|
COST_GROUP_DIMENSIONS,
|
|
10
12
|
type CostAggregateGroup,
|
|
11
13
|
type CostBinding,
|
|
12
14
|
type CostGroupDimension,
|
|
13
15
|
type CostRecord,
|
|
14
16
|
type CostRecordFilter,
|
|
17
|
+
type CostTokenTotals,
|
|
15
18
|
} from '../cost-binding.js';
|
|
16
19
|
import { statusFor, toWireError } from '../errors.js';
|
|
17
20
|
import type { AppEnv } from '../types.js';
|
|
@@ -52,6 +55,11 @@ export function costRouter(binding: CostBinding): Hono<AppEnv> {
|
|
|
52
55
|
c.status(statusFor('bad-input') as never);
|
|
53
56
|
return c.json(toWireError({ code: 'bad-input', message: filter.error }, requestId));
|
|
54
57
|
}
|
|
58
|
+
const include = parseInclude(c.req.query('include'));
|
|
59
|
+
if (include.kind === 'err') {
|
|
60
|
+
c.status(statusFor('bad-input') as never);
|
|
61
|
+
return c.json(toWireError({ code: 'bad-input', message: include.error }, requestId));
|
|
62
|
+
}
|
|
55
63
|
|
|
56
64
|
// Thread the ?scopeKind + ?scopeId + ?inherit
|
|
57
65
|
// triplet as a sibling of `filter` (matches the binding's shape:
|
|
@@ -71,6 +79,7 @@ export function costRouter(binding: CostBinding): Hono<AppEnv> {
|
|
|
71
79
|
filter: filter.value,
|
|
72
80
|
...(scopeParsed.scope !== undefined && { scope: scopeParsed.scope }),
|
|
73
81
|
...(scopeParsed.inherit !== undefined && { inherit: scopeParsed.inherit }),
|
|
82
|
+
...(include.rawUsage && { includeRawUsage: true }),
|
|
74
83
|
});
|
|
75
84
|
return c.json({
|
|
76
85
|
data: page.data.map(serializeRecord),
|
|
@@ -84,8 +93,17 @@ export function costRouter(binding: CostBinding): Hono<AppEnv> {
|
|
|
84
93
|
const requestId = c.get('requestId');
|
|
85
94
|
const tenantId = c.get('tenantId') as TenantId;
|
|
86
95
|
const recordId = c.req.param('recordId');
|
|
96
|
+
const include = parseInclude(c.req.query('include'));
|
|
97
|
+
if (include.kind === 'err') {
|
|
98
|
+
c.status(statusFor('bad-input') as never);
|
|
99
|
+
return c.json(toWireError({ code: 'bad-input', message: include.error }, requestId));
|
|
100
|
+
}
|
|
87
101
|
|
|
88
|
-
const record = await binding.getRecord({
|
|
102
|
+
const record = await binding.getRecord({
|
|
103
|
+
tenantId,
|
|
104
|
+
recordId,
|
|
105
|
+
...(include.rawUsage && { includeRawUsage: true }),
|
|
106
|
+
});
|
|
89
107
|
if (record === null) {
|
|
90
108
|
c.status(statusFor('cost-record-not-found') as never);
|
|
91
109
|
return c.json(
|
|
@@ -153,6 +171,21 @@ export function costRouter(binding: CostBinding): Hono<AppEnv> {
|
|
|
153
171
|
}
|
|
154
172
|
}
|
|
155
173
|
|
|
174
|
+
// limit — the most groups to return, the most expensive first.
|
|
175
|
+
const limit = parseAggregateLimit(query.limit);
|
|
176
|
+
if (limit === null) {
|
|
177
|
+
c.status(statusFor('bad-input') as never);
|
|
178
|
+
return c.json(
|
|
179
|
+
toWireError(
|
|
180
|
+
{
|
|
181
|
+
code: 'bad-input',
|
|
182
|
+
message: `\`limit\` must be an integer from 1 to ${COST_AGGREGATE_MAX_LIMIT} (default ${COST_AGGREGATE_DEFAULT_LIMIT})`,
|
|
183
|
+
},
|
|
184
|
+
requestId,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
156
189
|
// Time range — required. Default to last 30 days when both absent
|
|
157
190
|
// (documented in the response `timeRange`). If only one endpoint is
|
|
158
191
|
// supplied, reject — half-open defaults invite confusion.
|
|
@@ -224,12 +257,21 @@ export function costRouter(binding: CostBinding): Hono<AppEnv> {
|
|
|
224
257
|
...(Object.keys(filter.value).length > 0 && { filter: filter.value }),
|
|
225
258
|
...(scopeParsed.scope !== undefined && { scope: scopeParsed.scope }),
|
|
226
259
|
...(scopeParsed.inherit !== undefined && { inherit: scopeParsed.inherit }),
|
|
260
|
+
limit,
|
|
227
261
|
});
|
|
228
262
|
|
|
263
|
+
// The most expensive groups, capped here too: a binding may return
|
|
264
|
+
// every group. The window's totals stay over every record.
|
|
265
|
+
const groups = [...result.groups].sort((a, b) => compareGroups(a, b, groupBy)).slice(0, limit);
|
|
266
|
+
const totalGroups = Math.max(result.totalGroups ?? 0, result.groups.length);
|
|
267
|
+
|
|
229
268
|
return c.json({
|
|
230
|
-
groups:
|
|
269
|
+
groups: groups.map(serializeGroup),
|
|
270
|
+
totalGroups,
|
|
271
|
+
truncated: totalGroups > groups.length,
|
|
231
272
|
totalUsd: result.totalUsd,
|
|
232
273
|
totalRecords: result.totalRecords,
|
|
274
|
+
tokens: serializeTokens(result.tokens),
|
|
233
275
|
timeRange: {
|
|
234
276
|
from: result.timeRange.from,
|
|
235
277
|
to: result.timeRange.to,
|
|
@@ -248,27 +290,34 @@ function parseRecordFilter(
|
|
|
248
290
|
opts: { skipTime?: boolean } = {},
|
|
249
291
|
): { kind: 'ok'; value: CostRecordFilter } | { kind: 'err'; error: string } {
|
|
250
292
|
const out: {
|
|
251
|
-
|
|
252
|
-
agentId?: string;
|
|
253
|
-
conversationId?: string;
|
|
254
|
-
category?: string;
|
|
255
|
-
providerId?: string;
|
|
256
|
-
from?: Date;
|
|
257
|
-
to?: Date;
|
|
293
|
+
-readonly [K in keyof CostRecordFilter]: CostRecordFilter[K];
|
|
258
294
|
} = {};
|
|
259
|
-
const strKeys
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
295
|
+
const strKeys = [
|
|
296
|
+
'runId',
|
|
297
|
+
'agentId',
|
|
298
|
+
'conversationId',
|
|
299
|
+
'category',
|
|
300
|
+
'providerId',
|
|
301
|
+
'model',
|
|
302
|
+
'servedModel',
|
|
303
|
+
'rootRunId',
|
|
304
|
+
] as const;
|
|
305
|
+
for (const key of strKeys) {
|
|
306
|
+
const raw = query[key];
|
|
270
307
|
if (raw !== undefined && raw.length > 0) {
|
|
271
|
-
out[
|
|
308
|
+
out[key] = raw;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const descendants = query.includeDescendants;
|
|
312
|
+
if (descendants !== undefined && descendants.length > 0) {
|
|
313
|
+
if (descendants !== 'true' && descendants !== 'false') {
|
|
314
|
+
return { kind: 'err', error: '`includeDescendants` must be `true` or `false`' };
|
|
315
|
+
}
|
|
316
|
+
if (descendants === 'true') {
|
|
317
|
+
if (out.runId === undefined) {
|
|
318
|
+
return { kind: 'err', error: '`includeDescendants` needs a `runId`' };
|
|
319
|
+
}
|
|
320
|
+
out.includeDescendants = true;
|
|
272
321
|
}
|
|
273
322
|
}
|
|
274
323
|
if (opts.skipTime !== true) {
|
|
@@ -291,6 +340,22 @@ function parseRecordFilter(
|
|
|
291
340
|
return { kind: 'ok', value: out };
|
|
292
341
|
}
|
|
293
342
|
|
|
343
|
+
/** The `include` query: optional extra fields, comma-separated. Only `rawUsage` today. */
|
|
344
|
+
function parseInclude(
|
|
345
|
+
raw: string | undefined,
|
|
346
|
+
): { kind: 'ok'; rawUsage: boolean } | { kind: 'err'; error: string } {
|
|
347
|
+
const fields = (raw ?? '')
|
|
348
|
+
.split(',')
|
|
349
|
+
.map((f) => f.trim())
|
|
350
|
+
.filter((f) => f.length > 0);
|
|
351
|
+
for (const field of fields) {
|
|
352
|
+
if (field !== 'rawUsage') {
|
|
353
|
+
return { kind: 'err', error: `\`include\` value "${field}" is not known (one of: rawUsage)` };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return { kind: 'ok', rawUsage: fields.includes('rawUsage') };
|
|
357
|
+
}
|
|
358
|
+
|
|
294
359
|
function parseIsoDate(raw: string): Date | null {
|
|
295
360
|
const t = Date.parse(raw);
|
|
296
361
|
if (!Number.isFinite(t)) return null;
|
|
@@ -312,14 +377,87 @@ function serializeRecord(rec: CostRecord): Record<string, unknown> {
|
|
|
312
377
|
occurredAt: rec.occurredAt as unknown as string,
|
|
313
378
|
...(rec.metrics !== undefined && { metrics: rec.metrics }),
|
|
314
379
|
...(rec.attributes !== undefined && { attributes: rec.attributes }),
|
|
380
|
+
...pick(rec, MODEL_CALL_FIELDS),
|
|
315
381
|
};
|
|
316
382
|
}
|
|
317
383
|
|
|
384
|
+
/** A model call's fields on the wire, as the binding gives them (all optional). */
|
|
385
|
+
const MODEL_CALL_FIELDS = [
|
|
386
|
+
'callId',
|
|
387
|
+
'projectId',
|
|
388
|
+
'rootRunId',
|
|
389
|
+
'parentRunId',
|
|
390
|
+
'agentVersion',
|
|
391
|
+
'flowId',
|
|
392
|
+
'nodeId',
|
|
393
|
+
'step',
|
|
394
|
+
'purpose',
|
|
395
|
+
'model',
|
|
396
|
+
'servedModel',
|
|
397
|
+
'fallback',
|
|
398
|
+
'status',
|
|
399
|
+
'usage',
|
|
400
|
+
'durationMs',
|
|
401
|
+
'finishReason',
|
|
402
|
+
'providerRequestId',
|
|
403
|
+
'attempts',
|
|
404
|
+
'error',
|
|
405
|
+
'rawUsage',
|
|
406
|
+
] as const satisfies readonly (keyof CostRecord)[];
|
|
407
|
+
|
|
408
|
+
function pick(rec: CostRecord, keys: readonly (keyof CostRecord)[]): Record<string, unknown> {
|
|
409
|
+
const out: Record<string, unknown> = {};
|
|
410
|
+
for (const key of keys) {
|
|
411
|
+
if (rec[key] !== undefined) out[key] = rec[key];
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** `?limit=` on the aggregate: the default when absent, `null` when it isn't 1..max. */
|
|
417
|
+
function parseAggregateLimit(raw: string | undefined): number | null {
|
|
418
|
+
if (raw === undefined) return COST_AGGREGATE_DEFAULT_LIMIT;
|
|
419
|
+
if (!/^\d+$/.test(raw)) return null;
|
|
420
|
+
const n = Number(raw);
|
|
421
|
+
return n >= 1 && n <= COST_AGGREGATE_MAX_LIMIT ? n : null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* The aggregate's order: `totalUsd` descending, then the key, dimension
|
|
426
|
+
* by dimension in `groupBy` order (code-point order; `null` last).
|
|
427
|
+
*/
|
|
428
|
+
function compareGroups(
|
|
429
|
+
a: CostAggregateGroup,
|
|
430
|
+
b: CostAggregateGroup,
|
|
431
|
+
groupBy: readonly CostGroupDimension[],
|
|
432
|
+
): number {
|
|
433
|
+
if (a.totalUsd !== b.totalUsd) return b.totalUsd - a.totalUsd;
|
|
434
|
+
for (const dim of groupBy) {
|
|
435
|
+
const x = a.key[dim] ?? null;
|
|
436
|
+
const y = b.key[dim] ?? null;
|
|
437
|
+
if (x === y) continue;
|
|
438
|
+
if (x === null) return 1;
|
|
439
|
+
if (y === null) return -1;
|
|
440
|
+
return x < y ? -1 : 1;
|
|
441
|
+
}
|
|
442
|
+
return 0;
|
|
443
|
+
}
|
|
444
|
+
|
|
318
445
|
function serializeGroup(g: CostAggregateGroup): Record<string, unknown> {
|
|
319
446
|
return {
|
|
320
447
|
key: g.key,
|
|
321
448
|
count: g.count,
|
|
322
449
|
totalUsd: g.totalUsd,
|
|
450
|
+
tokens: serializeTokens(g.tokens),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function serializeTokens(t: CostTokenTotals): CostTokenTotals {
|
|
455
|
+
return {
|
|
456
|
+
prompt: t.prompt,
|
|
457
|
+
completion: t.completion,
|
|
458
|
+
cacheRead: t.cacheRead,
|
|
459
|
+
cacheWrite: t.cacheWrite,
|
|
460
|
+
reasoning: t.reasoning,
|
|
323
461
|
};
|
|
324
462
|
}
|
|
325
463
|
|
|
@@ -5,9 +5,10 @@ import { createHash } from 'node:crypto';
|
|
|
5
5
|
|
|
6
6
|
import { Hono } from 'hono';
|
|
7
7
|
|
|
8
|
-
import type { AgentId } from '@kindgi/agents';
|
|
8
|
+
import type { Agent, AgentId, AgentPins } from '@kindgi/agents';
|
|
9
9
|
import { tuplesForCreate } from '@kindgi/authz';
|
|
10
10
|
import { parsePublicKeyPem, verifyEd25519 } from '@kindgi/crypto';
|
|
11
|
+
import type { Flow, FlowPins } from '@kindgi/flow';
|
|
11
12
|
import { type Guardrail, validateGuardrailSpec } from '@kindgi/guardrails';
|
|
12
13
|
import type { ProjectBinding, Scope } from '@kindgi/platform';
|
|
13
14
|
import { validateToolManifest } from '@kindgi/tools';
|
|
@@ -16,13 +17,20 @@ import type {
|
|
|
16
17
|
FlowId,
|
|
17
18
|
GuardrailId,
|
|
18
19
|
ProjectId,
|
|
20
|
+
Semver,
|
|
19
21
|
SigningKeyId,
|
|
20
22
|
TenantId,
|
|
21
23
|
ToolId,
|
|
22
24
|
} from '@kindgi/types';
|
|
23
25
|
|
|
24
26
|
import type { AgentRegistryBinding } from '../agent-binding.js';
|
|
27
|
+
import { type UnpinnableRef, publishDeployedAgent, resolveAgentPins } from '../agent-pins.js';
|
|
28
|
+
import type { BlockRegistryBinding } from '../block-binding.js';
|
|
29
|
+
import type { DeployedVersionOutcome } from '../deploy-versions.js';
|
|
25
30
|
import type {
|
|
31
|
+
DeployedAgent,
|
|
32
|
+
DeployedFlow,
|
|
33
|
+
DeployedVersion,
|
|
26
34
|
Deployment,
|
|
27
35
|
DeploymentBinding,
|
|
28
36
|
DeploymentPrimitiveCounts,
|
|
@@ -30,6 +38,7 @@ import type {
|
|
|
30
38
|
} from '../deployment-binding.js';
|
|
31
39
|
import { statusFor, toWireError } from '../errors.js';
|
|
32
40
|
import type { FlowRegistryBinding } from '../flow-binding.js';
|
|
41
|
+
import { publishDeployedFlow, resolveFlowPins } from '../flow-pins.js';
|
|
33
42
|
import type { GuardrailRegistryBinding } from '../guardrail-binding.js';
|
|
34
43
|
import type { ImageRegistryBinding } from '../image-registry-binding.js';
|
|
35
44
|
import type { SecretBinding } from '../secrets-binding.js';
|
|
@@ -112,6 +121,8 @@ export interface DeploymentsRouterBindings {
|
|
|
112
121
|
readonly guardrailRegistry?: GuardrailRegistryBinding;
|
|
113
122
|
readonly agentRegistry?: AgentRegistryBinding;
|
|
114
123
|
readonly flowRegistry?: FlowRegistryBinding;
|
|
124
|
+
/** Data blocks: an agent's block references are pinned at deploy with its tools. */
|
|
125
|
+
readonly blockRegistry?: BlockRegistryBinding;
|
|
115
126
|
/**
|
|
116
127
|
* REQUIRED at runtime for `POST /v1/deployments/:deploymentId/secrets`.
|
|
117
128
|
* Optional at the type level so app compositions without a secrets
|
|
@@ -529,6 +540,10 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
529
540
|
}
|
|
530
541
|
|
|
531
542
|
const rolled: RollbackAction[] = [];
|
|
543
|
+
// The version each agent is registered under: its definition's, or
|
|
544
|
+
// the one a deploy registered in its place (`publishDeployedAgent`).
|
|
545
|
+
const deployedAgents: DeployedAgent[] = [];
|
|
546
|
+
const deployedFlows: DeployedFlow[] = [];
|
|
532
547
|
try {
|
|
533
548
|
if (bindings.toolRegistry !== undefined && defaultProjectId !== undefined) {
|
|
534
549
|
for (const tool of validated.tools) {
|
|
@@ -578,58 +593,50 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
578
593
|
}
|
|
579
594
|
}
|
|
580
595
|
if (bindings.agentRegistry !== undefined && defaultProjectId !== undefined) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
596
|
+
deployedAgents.push(
|
|
597
|
+
...(await registerAgents({
|
|
598
|
+
agents: bindings.agentRegistry,
|
|
599
|
+
tools: bindings.toolRegistry,
|
|
600
|
+
blocks: bindings.blockRegistry,
|
|
584
601
|
tenantId,
|
|
585
|
-
projectId:
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
kind: 'agent',
|
|
593
|
-
id: agentId as AgentId,
|
|
594
|
-
tenantId,
|
|
595
|
-
projectId: projectIdForAgent,
|
|
596
|
-
}),
|
|
597
|
-
});
|
|
598
|
-
if (outcome.kind === 'ok') {
|
|
599
|
-
const agentId = outcome.agentId;
|
|
600
|
-
const version = outcome.version;
|
|
601
|
-
rolled.push(async () => {
|
|
602
|
-
await bindings.agentRegistry?.unregister({ tenantId, agentId, version });
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
}
|
|
602
|
+
projectId: defaultProjectId,
|
|
603
|
+
defined: validated.agents,
|
|
604
|
+
rolled,
|
|
605
|
+
})),
|
|
606
|
+
);
|
|
607
|
+
} else {
|
|
608
|
+
deployedAgents.push(...validated.agents.map(deployedPrimitive));
|
|
606
609
|
}
|
|
607
610
|
if (bindings.flowRegistry !== undefined && defaultProjectId !== undefined) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
+
deployedFlows.push(
|
|
612
|
+
...(await registerFlows({
|
|
613
|
+
flows: bindings.flowRegistry,
|
|
614
|
+
tools: bindings.toolRegistry,
|
|
615
|
+
agents: bindings.agentRegistry,
|
|
611
616
|
tenantId,
|
|
612
|
-
projectId:
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
projectId: projectIdForGraph,
|
|
620
|
-
}),
|
|
621
|
-
});
|
|
622
|
-
if (outcome.kind === 'ok') {
|
|
623
|
-
const flowId = outcome.flowId;
|
|
624
|
-
const version = outcome.version;
|
|
625
|
-
rolled.push(async () => {
|
|
626
|
-
await bindings.flowRegistry?.unregister({ tenantId, flowId, version });
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
}
|
|
617
|
+
projectId: defaultProjectId,
|
|
618
|
+
defined: validated.flows,
|
|
619
|
+
rolled,
|
|
620
|
+
})),
|
|
621
|
+
);
|
|
622
|
+
} else {
|
|
623
|
+
deployedFlows.push(...validated.flows.map(deployedPrimitive));
|
|
630
624
|
}
|
|
631
625
|
} catch (cause) {
|
|
632
626
|
await rollback(rolled);
|
|
627
|
+
if (cause instanceof UnpinnableDeploy) {
|
|
628
|
+
c.status(statusFor('invalid-agent') as never);
|
|
629
|
+
return c.json(
|
|
630
|
+
toWireError(
|
|
631
|
+
{
|
|
632
|
+
code: 'validation-failed',
|
|
633
|
+
message: `The deployment's agents or flows use tool or agent versions that aren't published (${cause.issues.length} issue${cause.issues.length === 1 ? '' : 's'}); nothing was deployed`,
|
|
634
|
+
issues: cause.issues as unknown as Record<string, unknown>[],
|
|
635
|
+
},
|
|
636
|
+
requestId,
|
|
637
|
+
),
|
|
638
|
+
);
|
|
639
|
+
}
|
|
633
640
|
c.status(statusFor('internal-server-error') as never);
|
|
634
641
|
return c.json(
|
|
635
642
|
toWireError(
|
|
@@ -669,14 +676,8 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
669
676
|
version: t.version,
|
|
670
677
|
})),
|
|
671
678
|
guardrails: validated.guardrails.map((g) => ({ id: g.id as unknown as string })),
|
|
672
|
-
agents:
|
|
673
|
-
|
|
674
|
-
version: a.version as unknown as string,
|
|
675
|
-
})),
|
|
676
|
-
flows: validated.flows.map((f) => ({
|
|
677
|
-
id: f.id as unknown as string,
|
|
678
|
-
version: f.version as unknown as string,
|
|
679
|
-
})),
|
|
679
|
+
agents: deployedAgents,
|
|
680
|
+
flows: deployedFlows,
|
|
680
681
|
},
|
|
681
682
|
});
|
|
682
683
|
} catch (cause) {
|
|
@@ -1374,8 +1375,18 @@ function validateAgentIndexShape(
|
|
|
1374
1375
|
if (typeof spec.name !== 'string' || (spec.name as string).length === 0) {
|
|
1375
1376
|
out.push({ path: '/name', message: 'agent.name must be a non-empty string' });
|
|
1376
1377
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1378
|
+
const instructions = spec.instructions as { prompt?: unknown; version?: unknown } | string;
|
|
1379
|
+
const promptRef =
|
|
1380
|
+
typeof instructions === 'object' &&
|
|
1381
|
+
instructions !== null &&
|
|
1382
|
+
typeof instructions.prompt === 'string' &&
|
|
1383
|
+
typeof instructions.version === 'string';
|
|
1384
|
+
if (!promptRef && (typeof instructions !== 'string' || instructions.length === 0)) {
|
|
1385
|
+
out.push({
|
|
1386
|
+
path: '/instructions',
|
|
1387
|
+
message:
|
|
1388
|
+
'agent.instructions must be a non-empty string, or a prompt block { prompt, version }',
|
|
1389
|
+
});
|
|
1379
1390
|
}
|
|
1380
1391
|
if (!Array.isArray(spec.capabilities)) {
|
|
1381
1392
|
out.push({ path: '/capabilities', message: 'agent.capabilities must be an array' });
|
|
@@ -1411,6 +1422,205 @@ function validateFlowIndexShape(
|
|
|
1411
1422
|
|
|
1412
1423
|
type RollbackAction = () => Promise<void>;
|
|
1413
1424
|
|
|
1425
|
+
/** A deploy whose agents or flows use a tool or agent with no published version in range. */
|
|
1426
|
+
class UnpinnableDeploy extends Error {
|
|
1427
|
+
constructor(readonly issues: readonly UnpinnableRef[]) {
|
|
1428
|
+
super('the deployment uses tools or agents that are not published');
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/** A deployed agent or flow registered under its definition's version. */
|
|
1433
|
+
function deployedPrimitive(definition: { readonly id: string; readonly version: string }): {
|
|
1434
|
+
id: string;
|
|
1435
|
+
version: string;
|
|
1436
|
+
} {
|
|
1437
|
+
return { id: definition.id, version: definition.version };
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/** A deployed agent or flow, under the version the deploy rule registered it as. */
|
|
1441
|
+
function deployedVersion(
|
|
1442
|
+
definition: { readonly id: string; readonly version: string },
|
|
1443
|
+
outcome: DeployedVersionOutcome,
|
|
1444
|
+
): DeployedVersion {
|
|
1445
|
+
return outcome.kind === 'reused' || outcome.kind === 'renumbered'
|
|
1446
|
+
? {
|
|
1447
|
+
id: definition.id,
|
|
1448
|
+
version: outcome.version,
|
|
1449
|
+
authoredVersion: definition.version,
|
|
1450
|
+
reason: outcome.reason,
|
|
1451
|
+
newVersion: outcome.kind === 'renumbered',
|
|
1452
|
+
...(outcome.pinChanges !== undefined && { pinChanges: outcome.pinChanges }),
|
|
1453
|
+
}
|
|
1454
|
+
: deployedPrimitive(definition);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* Register a deployment's agents, each under the version it's registered
|
|
1459
|
+
* as (`DeployedAgent`), pushing a rollback for each version it writes.
|
|
1460
|
+
*
|
|
1461
|
+
* With a tool registry, every agent is pinned first (the pack's tools
|
|
1462
|
+
* are registered by now): one whose range matches no published version
|
|
1463
|
+
* throws `UnpinnableDeploy` before any agent is written, and the deploy
|
|
1464
|
+
* rolls back. Each is then registered by `publishDeployedAgent`, which
|
|
1465
|
+
* never keeps a version's old pins. Without one, agents register
|
|
1466
|
+
* unpinned, as before pins existed.
|
|
1467
|
+
*/
|
|
1468
|
+
async function registerAgents(input: {
|
|
1469
|
+
readonly agents: AgentRegistryBinding;
|
|
1470
|
+
readonly tools: ToolRegistryBinding | undefined;
|
|
1471
|
+
readonly blocks: BlockRegistryBinding | undefined;
|
|
1472
|
+
readonly tenantId: TenantId;
|
|
1473
|
+
readonly projectId: ProjectId;
|
|
1474
|
+
readonly defined: readonly Agent[];
|
|
1475
|
+
readonly rolled: RollbackAction[];
|
|
1476
|
+
}): Promise<DeployedAgent[]> {
|
|
1477
|
+
const { agents, tools, blocks, tenantId, projectId, rolled } = input;
|
|
1478
|
+
// Signed deploys have no per-request principal — pass parent-only
|
|
1479
|
+
// tuples (no owner grant). Tenant admins keep access via
|
|
1480
|
+
// `admin from parent` cascade.
|
|
1481
|
+
const enqueueTuples = (agentId: string) =>
|
|
1482
|
+
tuplesForCreate({ kind: 'agent', id: agentId as AgentId, tenantId, projectId });
|
|
1483
|
+
const written = (agentId: AgentId, version: Semver) =>
|
|
1484
|
+
rolled.push(async () => {
|
|
1485
|
+
await agents.unregister({ tenantId, agentId, version });
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
if (tools === undefined) {
|
|
1489
|
+
for (const agent of input.defined) {
|
|
1490
|
+
const outcome = await agents.publish({ tenantId, projectId, agent, enqueueTuples });
|
|
1491
|
+
if (outcome.kind === 'ok') written(outcome.agentId, outcome.version);
|
|
1492
|
+
}
|
|
1493
|
+
return input.defined.map(deployedPrimitive);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
const pinsOf = await pinAgents(tools, blocks, tenantId, input.defined);
|
|
1497
|
+
const deployed: DeployedAgent[] = [];
|
|
1498
|
+
for (const [i, agent] of input.defined.entries()) {
|
|
1499
|
+
const pins = pinsOf[i] as AgentPins;
|
|
1500
|
+
const outcome = await publishDeployedAgent({
|
|
1501
|
+
agents,
|
|
1502
|
+
tenantId,
|
|
1503
|
+
projectId,
|
|
1504
|
+
agent,
|
|
1505
|
+
pins,
|
|
1506
|
+
enqueueTuples,
|
|
1507
|
+
});
|
|
1508
|
+
if (outcome.kind === 'registered' || outcome.kind === 'renumbered') {
|
|
1509
|
+
written(agent.id, outcome.version as unknown as Semver);
|
|
1510
|
+
}
|
|
1511
|
+
deployed.push(deployedVersion(agent, outcome));
|
|
1512
|
+
}
|
|
1513
|
+
return deployed;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* Register a deployment's flows, each under the version it's registered
|
|
1518
|
+
* as, pushing a rollback for each version it writes. Runs after the
|
|
1519
|
+
* agents, so a flow's agent pins see the versions this deploy
|
|
1520
|
+
* registered: a tool change cascades through an agent into a flow
|
|
1521
|
+
* within the one deploy, each derived once.
|
|
1522
|
+
*
|
|
1523
|
+
* With the tool and agent registries, every flow is pinned first; one
|
|
1524
|
+
* that runs a tool or agent with no published version throws
|
|
1525
|
+
* `UnpinnableDeploy` before any flow is written, and the deploy rolls
|
|
1526
|
+
* back. Without them, flows register unpinned, as before pins existed.
|
|
1527
|
+
*/
|
|
1528
|
+
async function registerFlows(input: {
|
|
1529
|
+
readonly flows: FlowRegistryBinding;
|
|
1530
|
+
readonly tools: ToolRegistryBinding | undefined;
|
|
1531
|
+
readonly agents: AgentRegistryBinding | undefined;
|
|
1532
|
+
readonly tenantId: TenantId;
|
|
1533
|
+
readonly projectId: ProjectId;
|
|
1534
|
+
readonly defined: readonly Flow[];
|
|
1535
|
+
readonly rolled: RollbackAction[];
|
|
1536
|
+
}): Promise<DeployedFlow[]> {
|
|
1537
|
+
const { flows, tools, agents, tenantId, projectId, rolled } = input;
|
|
1538
|
+
const enqueueTuples = (flowId: string) =>
|
|
1539
|
+
tuplesForCreate({ kind: 'flow', id: flowId as FlowId, tenantId, projectId });
|
|
1540
|
+
const written = (flowId: FlowId, version: string) =>
|
|
1541
|
+
rolled.push(async () => {
|
|
1542
|
+
await flows.unregister({ tenantId, flowId, version: version as never });
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
if (tools === undefined || agents === undefined) {
|
|
1546
|
+
for (const flow of input.defined) {
|
|
1547
|
+
const outcome = await flows.publish({ tenantId, projectId, flow, enqueueTuples });
|
|
1548
|
+
if (outcome.kind === 'ok') written(outcome.flowId, outcome.version as unknown as string);
|
|
1549
|
+
}
|
|
1550
|
+
return input.defined.map(deployedPrimitive);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
const pinsOf = await pinFlows(tools, agents, tenantId, input.defined);
|
|
1554
|
+
const deployed: DeployedFlow[] = [];
|
|
1555
|
+
for (const [i, flow] of input.defined.entries()) {
|
|
1556
|
+
const outcome = await publishDeployedFlow({
|
|
1557
|
+
flows,
|
|
1558
|
+
tenantId,
|
|
1559
|
+
projectId,
|
|
1560
|
+
flow,
|
|
1561
|
+
pins: pinsOf[i] as FlowPins,
|
|
1562
|
+
enqueueTuples,
|
|
1563
|
+
});
|
|
1564
|
+
if (outcome.kind === 'registered' || outcome.kind === 'renumbered') {
|
|
1565
|
+
written(flow.id, outcome.version);
|
|
1566
|
+
}
|
|
1567
|
+
deployed.push(deployedVersion(flow, outcome));
|
|
1568
|
+
}
|
|
1569
|
+
return deployed;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/** Each flow's pins, in order; throws `UnpinnableDeploy` naming every tool or agent with no published version. */
|
|
1573
|
+
async function pinFlows(
|
|
1574
|
+
tools: ToolRegistryBinding,
|
|
1575
|
+
agents: AgentRegistryBinding,
|
|
1576
|
+
tenantId: TenantId,
|
|
1577
|
+
defined: readonly Flow[],
|
|
1578
|
+
): Promise<FlowPins[]> {
|
|
1579
|
+
const pins: FlowPins[] = [];
|
|
1580
|
+
const unpinnable: UnpinnableRef[] = [];
|
|
1581
|
+
for (const [i, flow] of defined.entries()) {
|
|
1582
|
+
const resolved = await resolveFlowPins(tools, agents, tenantId, flow);
|
|
1583
|
+
if (resolved.kind === 'ok') {
|
|
1584
|
+
pins.push(resolved.pins);
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
for (const issue of resolved.issues) {
|
|
1588
|
+
unpinnable.push({
|
|
1589
|
+
path: `/flows/${i}${issue.path}`,
|
|
1590
|
+
message: `flow "${flow.id as unknown as string}": ${issue.message}`,
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
if (unpinnable.length > 0) throw new UnpinnableDeploy(unpinnable);
|
|
1595
|
+
return pins;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/** Each agent's pins, in order; throws `UnpinnableDeploy` naming every range that matches nothing. */
|
|
1599
|
+
async function pinAgents(
|
|
1600
|
+
tools: ToolRegistryBinding,
|
|
1601
|
+
blocks: BlockRegistryBinding | undefined,
|
|
1602
|
+
tenantId: TenantId,
|
|
1603
|
+
defined: readonly Agent[],
|
|
1604
|
+
): Promise<AgentPins[]> {
|
|
1605
|
+
const pins: AgentPins[] = [];
|
|
1606
|
+
const unpinnable: UnpinnableRef[] = [];
|
|
1607
|
+
for (const [i, agent] of defined.entries()) {
|
|
1608
|
+
const resolved = await resolveAgentPins(tools, tenantId, agent, blocks);
|
|
1609
|
+
if (resolved.kind === 'ok') {
|
|
1610
|
+
pins.push(resolved.pins);
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
for (const issue of resolved.issues) {
|
|
1614
|
+
unpinnable.push({
|
|
1615
|
+
path: `/agents/${i}${issue.path}`,
|
|
1616
|
+
message: `agent "${agent.id as unknown as string}": ${issue.message}`,
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
if (unpinnable.length > 0) throw new UnpinnableDeploy(unpinnable);
|
|
1621
|
+
return pins;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1414
1624
|
async function rollback(actions: readonly RollbackAction[]): Promise<void> {
|
|
1415
1625
|
// Reverse order — last-in, first-out — so registrations are undone in
|
|
1416
1626
|
// the mirror sequence of their creation.
|