@kindgi/api 0.1.4-rc.0 → 0.1.4-rc.2
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 +20 -2
- package/dist/agent-binding.d.ts.map +1 -1
- package/dist/agent-pins.d.ts.map +1 -1
- package/dist/agent-pins.js +4 -1
- package/dist/agent-pins.js.map +1 -1
- package/dist/app.d.ts +8 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +30 -5
- package/dist/app.js.map +1 -1
- package/dist/deploy-versions.d.ts +10 -5
- package/dist/deploy-versions.d.ts.map +1 -1
- package/dist/deploy-versions.js +4 -10
- package/dist/deploy-versions.js.map +1 -1
- package/dist/derive-agent-version.d.ts +9 -1
- package/dist/derive-agent-version.d.ts.map +1 -1
- package/dist/derive-agent-version.js +11 -1
- package/dist/derive-agent-version.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -1
- package/dist/eval-case-binding.d.ts +10 -0
- package/dist/eval-case-binding.d.ts.map +1 -1
- package/dist/eval-run-binding.d.ts +21 -3
- package/dist/eval-run-binding.d.ts.map +1 -1
- package/dist/eval-run-binding.js.map +1 -1
- package/dist/eval-run-dispatcher.d.ts +3 -0
- package/dist/eval-run-dispatcher.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.js.map +1 -1
- package/dist/flow-binding.d.ts +8 -0
- package/dist/flow-binding.d.ts.map +1 -1
- package/dist/flow-pins.d.ts +20 -7
- package/dist/flow-pins.d.ts.map +1 -1
- package/dist/flow-pins.js +36 -11
- package/dist/flow-pins.js.map +1 -1
- package/dist/gate-policy-binding.d.ts +164 -0
- package/dist/gate-policy-binding.d.ts.map +1 -0
- package/dist/gate-policy-binding.js +12 -0
- package/dist/gate-policy-binding.js.map +1 -0
- package/dist/gate.d.ts +56 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +359 -0
- package/dist/gate.js.map +1 -0
- package/dist/guardrail-binding.d.ts +8 -0
- package/dist/guardrail-binding.d.ts.map +1 -1
- package/dist/handler-binding.d.ts +17 -2
- package/dist/handler-binding.d.ts.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/judged-dispatcher.d.ts +20 -1
- package/dist/judged-dispatcher.d.ts.map +1 -1
- package/dist/judged-dispatcher.js +57 -7
- package/dist/judged-dispatcher.js.map +1 -1
- package/dist/judgment-binding.d.ts +38 -0
- package/dist/judgment-binding.d.ts.map +1 -1
- package/dist/judgment-binding.js +19 -0
- package/dist/judgment-binding.js.map +1 -1
- package/dist/live-version-binding.d.ts +206 -0
- package/dist/live-version-binding.d.ts.map +1 -0
- package/dist/live-version-binding.js +4 -0
- package/dist/live-version-binding.js.map +1 -0
- package/dist/middleware/authorize.d.ts.map +1 -1
- package/dist/middleware/authorize.js +13 -2
- package/dist/middleware/authorize.js.map +1 -1
- package/dist/middleware/project-ref.d.ts +25 -0
- package/dist/middleware/project-ref.d.ts.map +1 -0
- package/dist/middleware/project-ref.js +72 -0
- package/dist/middleware/project-ref.js.map +1 -0
- package/dist/openapi/generate.d.ts.map +1 -1
- package/dist/openapi/generate.js +4 -0
- package/dist/openapi/generate.js.map +1 -1
- package/dist/openapi/operations.d.ts +6 -0
- package/dist/openapi/operations.d.ts.map +1 -1
- package/dist/openapi/operations.js +524 -27
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +43 -0
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +1050 -7
- package/dist/openapi/schemas.js.map +1 -1
- package/dist/publish-refused.d.ts +18 -0
- package/dist/publish-refused.d.ts.map +1 -0
- package/dist/publish-refused.js +22 -0
- package/dist/publish-refused.js.map +1 -0
- package/dist/registry-read-only.d.ts +32 -0
- package/dist/registry-read-only.d.ts.map +1 -0
- package/dist/registry-read-only.js +22 -0
- package/dist/registry-read-only.js.map +1 -0
- package/dist/retention-binding.d.ts +29 -0
- package/dist/retention-binding.d.ts.map +1 -1
- package/dist/routes/agent-releases.d.ts +40 -0
- package/dist/routes/agent-releases.d.ts.map +1 -0
- package/dist/routes/agent-releases.js +462 -0
- package/dist/routes/agent-releases.js.map +1 -0
- package/dist/routes/agents.d.ts +7 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +38 -4
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +27 -9
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/audit.d.ts.map +1 -1
- package/dist/routes/audit.js +7 -0
- package/dist/routes/audit.js.map +1 -1
- package/dist/routes/auth.js +1 -1
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/blocks.d.ts.map +1 -1
- package/dist/routes/blocks.js +37 -12
- package/dist/routes/blocks.js.map +1 -1
- package/dist/routes/conversations.d.ts +0 -7
- package/dist/routes/conversations.d.ts.map +1 -1
- package/dist/routes/conversations.js +19 -3
- package/dist/routes/conversations.js.map +1 -1
- package/dist/routes/deployments.d.ts +6 -0
- package/dist/routes/deployments.d.ts.map +1 -1
- package/dist/routes/deployments.js +49 -5
- package/dist/routes/deployments.js.map +1 -1
- package/dist/routes/env.d.ts.map +1 -1
- package/dist/routes/env.js +1 -0
- package/dist/routes/env.js.map +1 -1
- package/dist/routes/eval-comparison.d.ts +4 -2
- package/dist/routes/eval-comparison.d.ts.map +1 -1
- package/dist/routes/eval-comparison.js +59 -10
- package/dist/routes/eval-comparison.js.map +1 -1
- package/dist/routes/eval-runs.d.ts +7 -1
- package/dist/routes/eval-runs.d.ts.map +1 -1
- package/dist/routes/eval-runs.js +34 -3
- package/dist/routes/eval-runs.js.map +1 -1
- package/dist/routes/eval-versions.d.ts +25 -0
- package/dist/routes/eval-versions.d.ts.map +1 -0
- package/dist/routes/eval-versions.js +66 -0
- package/dist/routes/eval-versions.js.map +1 -0
- package/dist/routes/flows.d.ts +14 -7
- package/dist/routes/flows.d.ts.map +1 -1
- package/dist/routes/flows.js +14 -8
- package/dist/routes/flows.js.map +1 -1
- package/dist/routes/gate-policies.d.ts +19 -0
- package/dist/routes/gate-policies.d.ts.map +1 -0
- package/dist/routes/gate-policies.js +191 -0
- package/dist/routes/gate-policies.js.map +1 -0
- package/dist/routes/gate-policy-spec.d.ts +17 -0
- package/dist/routes/gate-policy-spec.d.ts.map +1 -0
- package/dist/routes/gate-policy-spec.js +192 -0
- package/dist/routes/gate-policy-spec.js.map +1 -0
- package/dist/routes/gate-policy-wire.d.ts +3 -0
- package/dist/routes/gate-policy-wire.d.ts.map +1 -0
- package/dist/routes/gate-policy-wire.js +18 -0
- package/dist/routes/gate-policy-wire.js.map +1 -0
- package/dist/routes/guardrails.d.ts.map +1 -1
- package/dist/routes/guardrails.js +4 -0
- package/dist/routes/guardrails.js.map +1 -1
- package/dist/routes/hierarchy-errors.d.ts +21 -0
- package/dist/routes/hierarchy-errors.d.ts.map +1 -1
- package/dist/routes/hierarchy-errors.js +21 -0
- package/dist/routes/hierarchy-errors.js.map +1 -1
- package/dist/routes/judged-suites.js +7 -0
- package/dist/routes/judged-suites.js.map +1 -1
- package/dist/routes/judgments.d.ts +4 -1
- package/dist/routes/judgments.d.ts.map +1 -1
- package/dist/routes/judgments.js +92 -19
- package/dist/routes/judgments.js.map +1 -1
- package/dist/routes/live-scope-wire.d.ts +32 -0
- package/dist/routes/live-scope-wire.d.ts.map +1 -0
- package/dist/routes/live-scope-wire.js +71 -0
- package/dist/routes/live-scope-wire.js.map +1 -0
- package/dist/routes/policies.d.ts +6 -3
- package/dist/routes/policies.d.ts.map +1 -1
- package/dist/routes/policies.js +55 -3
- package/dist/routes/policies.js.map +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +59 -9
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/retention.d.ts.map +1 -1
- package/dist/routes/retention.js +18 -2
- package/dist/routes/retention.js.map +1 -1
- package/dist/routes/reviewers.d.ts +8 -1
- package/dist/routes/reviewers.d.ts.map +1 -1
- package/dist/routes/reviewers.js +20 -3
- package/dist/routes/reviewers.js.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +23 -13
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/secrets.d.ts.map +1 -1
- package/dist/routes/secrets.js +2 -0
- package/dist/routes/secrets.js.map +1 -1
- package/dist/routes/segments.d.ts +17 -0
- package/dist/routes/segments.d.ts.map +1 -0
- package/dist/routes/segments.js +68 -0
- package/dist/routes/segments.js.map +1 -0
- package/dist/routes/teams.d.ts.map +1 -1
- package/dist/routes/teams.js +53 -8
- package/dist/routes/teams.js.map +1 -1
- package/dist/routes/tools.d.ts.map +1 -1
- package/dist/routes/tools.js +4 -0
- package/dist/routes/tools.js.map +1 -1
- package/dist/routes/uuid-param.d.ts +11 -0
- package/dist/routes/uuid-param.d.ts.map +1 -0
- package/dist/routes/uuid-param.js +19 -0
- package/dist/routes/uuid-param.js.map +1 -0
- package/dist/tool-binding.d.ts +8 -0
- package/dist/tool-binding.d.ts.map +1 -1
- package/openapi.json +6484 -2402
- package/package.json +21 -21
- package/src/agent-binding.ts +21 -2
- package/src/agent-pins.ts +3 -1
- package/src/app.ts +47 -4
- package/src/deploy-versions.ts +12 -15
- package/src/derive-agent-version.ts +12 -1
- package/src/errors.ts +26 -0
- package/src/eval-case-binding.ts +7 -0
- package/src/eval-run-binding.ts +31 -3
- package/src/eval-run-dispatcher.ts +3 -0
- package/src/flow-binding.ts +9 -0
- package/src/flow-pins.ts +49 -9
- package/src/gate-policy-binding.ts +171 -0
- package/src/gate.ts +467 -0
- package/src/guardrail-binding.ts +9 -0
- package/src/handler-binding.ts +17 -2
- package/src/index.ts +47 -1
- package/src/judged-dispatcher.ts +100 -9
- package/src/judgment-binding.ts +62 -0
- package/src/live-version-binding.ts +237 -0
- package/src/middleware/authorize.ts +18 -2
- package/src/middleware/project-ref.ts +88 -0
- package/src/openapi/generate.ts +5 -0
- package/src/openapi/operations.ts +664 -27
- package/src/openapi/schemas.ts +1175 -33
- package/src/publish-refused.ts +31 -0
- package/src/registry-read-only.ts +43 -0
- package/src/retention-binding.ts +30 -0
- package/src/routes/agent-releases.ts +581 -0
- package/src/routes/agents.ts +53 -3
- package/src/routes/approvals.ts +36 -8
- package/src/routes/audit.ts +10 -0
- package/src/routes/auth.ts +1 -1
- package/src/routes/blocks.ts +39 -14
- package/src/routes/conversations.ts +31 -3
- package/src/routes/deployments.ts +75 -5
- package/src/routes/env.ts +1 -0
- package/src/routes/eval-comparison.ts +59 -11
- package/src/routes/eval-runs.ts +46 -3
- package/src/routes/eval-versions.ts +110 -0
- package/src/routes/flows.ts +34 -11
- package/src/routes/gate-policies.ts +226 -0
- package/src/routes/gate-policy-spec.ts +229 -0
- package/src/routes/gate-policy-wire.ts +20 -0
- package/src/routes/guardrails.ts +7 -0
- package/src/routes/hierarchy-errors.ts +23 -0
- package/src/routes/judged-suites.ts +5 -0
- package/src/routes/judgments.ts +107 -23
- package/src/routes/live-scope-wire.ts +90 -0
- package/src/routes/policies.ts +61 -3
- package/src/routes/projects.ts +72 -9
- package/src/routes/retention.ts +21 -2
- package/src/routes/reviewers.ts +23 -4
- package/src/routes/runs.ts +34 -20
- package/src/routes/secrets.ts +2 -0
- package/src/routes/segments.ts +75 -0
- package/src/routes/teams.ts +66 -8
- package/src/routes/tools.ts +7 -0
- package/src/routes/uuid-param.ts +28 -0
- package/src/tool-binding.ts +9 -0
package/src/routes/approvals.ts
CHANGED
|
@@ -406,6 +406,7 @@ export function approvalsRouter(
|
|
|
406
406
|
// override it, when the resume payload must carry more than the
|
|
407
407
|
// decision (an agent gate refuses one, above).
|
|
408
408
|
let waitpointResolved = false;
|
|
409
|
+
let resume: ResumeReport | undefined;
|
|
409
410
|
if (
|
|
410
411
|
approval.waitTokenId !== undefined &&
|
|
411
412
|
approval.provenanceRef?.runId !== undefined &&
|
|
@@ -440,15 +441,12 @@ export function approvalsRouter(
|
|
|
440
441
|
// journaled `wait.resumed`. The approval-complete surface reports
|
|
441
442
|
// the decision as successful; a run that stayed suspended because
|
|
442
443
|
// the inline resume failed can be resumed later.
|
|
444
|
+
//
|
|
445
|
+
// The response says how the resume went (`resume`), so a decision
|
|
446
|
+
// whose run couldn't go on (a tool version it started with is gone,
|
|
447
|
+
// say) isn't reported as plain success.
|
|
443
448
|
if (runHandler !== undefined) {
|
|
444
|
-
|
|
445
|
-
await runHandler.resumeRun({
|
|
446
|
-
tenantId,
|
|
447
|
-
runId: approval.provenanceRef.runId as RunId,
|
|
448
|
-
});
|
|
449
|
-
} catch {
|
|
450
|
-
// Soft-fail — the decision is durable; the run can be resumed later.
|
|
451
|
-
}
|
|
449
|
+
resume = await resumeInline(runHandler, tenantId, approval.provenanceRef.runId as RunId);
|
|
452
450
|
}
|
|
453
451
|
}
|
|
454
452
|
|
|
@@ -461,6 +459,7 @@ export function approvalsRouter(
|
|
|
461
459
|
nextApproval: serializeApproval(result.nextApproval),
|
|
462
460
|
}),
|
|
463
461
|
waitpointResolved,
|
|
462
|
+
...(resume !== undefined && { resume }),
|
|
464
463
|
});
|
|
465
464
|
});
|
|
466
465
|
|
|
@@ -808,3 +807,32 @@ function parseAuditBundleBody(
|
|
|
808
807
|
},
|
|
809
808
|
};
|
|
810
809
|
}
|
|
810
|
+
|
|
811
|
+
/** How the inline resume after a decision went. */
|
|
812
|
+
type ResumeReport =
|
|
813
|
+
| { readonly kind: 'ok' }
|
|
814
|
+
| { readonly kind: 'failed'; readonly code: string; readonly message: string };
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Resume the run a decision released, in the same request. A resume that
|
|
818
|
+
* fails is reported, not raised: the decision is durable either way, and
|
|
819
|
+
* the runtime ends a run that can't go on, or resumes it later.
|
|
820
|
+
*/
|
|
821
|
+
async function resumeInline(
|
|
822
|
+
runHandler: RunHandlerBinding,
|
|
823
|
+
tenantId: TenantId,
|
|
824
|
+
runId: RunId,
|
|
825
|
+
): Promise<ResumeReport> {
|
|
826
|
+
try {
|
|
827
|
+
const outcome = await runHandler.resumeRun({ tenantId, runId });
|
|
828
|
+
return outcome.kind === 'ok'
|
|
829
|
+
? { kind: 'ok' }
|
|
830
|
+
: { kind: 'failed', code: outcome.error.code, message: outcome.error.message };
|
|
831
|
+
} catch (cause) {
|
|
832
|
+
return {
|
|
833
|
+
kind: 'failed',
|
|
834
|
+
code: 'resume-failed',
|
|
835
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
package/src/routes/audit.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// ?from=<iso>&to=<iso>
|
|
22
22
|
// ?runId=<id>
|
|
23
23
|
// ?limit=<n>&cursor=<opaque>
|
|
24
|
+
// ?order=asc|desc (asc, oldest first, by default)
|
|
24
25
|
//
|
|
25
26
|
|
|
26
27
|
import { Hono } from 'hono';
|
|
@@ -83,6 +84,14 @@ export function auditRouter(binding: AuditEventBinding, authorizer?: Authorizer)
|
|
|
83
84
|
const from = c.req.query('from');
|
|
84
85
|
const to = c.req.query('to');
|
|
85
86
|
const runId = c.req.query('runId');
|
|
87
|
+
const orderRaw = c.req.query('order');
|
|
88
|
+
|
|
89
|
+
if (orderRaw !== undefined && orderRaw !== 'asc' && orderRaw !== 'desc') {
|
|
90
|
+
c.status(statusFor('bad-input') as never);
|
|
91
|
+
return c.json(
|
|
92
|
+
toWireError({ code: 'bad-input', message: '`order` must be "asc" or "desc"' }, requestId),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
86
95
|
|
|
87
96
|
let outcome: 'allowed' | 'denied' | undefined;
|
|
88
97
|
if (outcomeRaw === 'allowed' || outcomeRaw === 'denied') outcome = outcomeRaw;
|
|
@@ -130,6 +139,7 @@ export function auditRouter(binding: AuditEventBinding, authorizer?: Authorizer)
|
|
|
130
139
|
...(to !== undefined && to.length > 0 && { to }),
|
|
131
140
|
},
|
|
132
141
|
...(cursor !== undefined && cursor.length > 0 && { cursor }),
|
|
142
|
+
...(orderRaw !== undefined && { order: orderRaw }),
|
|
133
143
|
limit,
|
|
134
144
|
});
|
|
135
145
|
|
package/src/routes/auth.ts
CHANGED
|
@@ -76,7 +76,7 @@ export function authRouters(options: AuthRouterOptions): {
|
|
|
76
76
|
authed.get('/providers', async (c) => {
|
|
77
77
|
const tenantId = c.get('tenantId') as TenantId;
|
|
78
78
|
const page = await identityProvider.list({ tenantId });
|
|
79
|
-
return c.json({ data: page.data.map(serializeProviderConfig) });
|
|
79
|
+
return c.json({ data: page.data.map(serializeProviderConfig), hasMore: false });
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
// ---------- POST /providers ----------
|
package/src/routes/blocks.ts
CHANGED
|
@@ -184,14 +184,20 @@ export function blocksRouter(binding: BlockRegistryBinding, authorizer?: Authori
|
|
|
184
184
|
return validationFailed(c, validated.error.message, validated.error.issues);
|
|
185
185
|
const block = validated.value;
|
|
186
186
|
|
|
187
|
-
// A block keeps its kind
|
|
187
|
+
// A block keeps its kind. A settings version without a schema keeps
|
|
188
|
+
// the latest version's (stored on it, so the check carries forward to
|
|
189
|
+
// every later version); one that gives a schema replaces it.
|
|
188
190
|
const latest = await binding.get({ tenantId, blockId: block.id });
|
|
189
191
|
const continuity = continuityIssues(latest, block);
|
|
190
192
|
if (continuity.length > 0) {
|
|
191
193
|
return validationFailed(c, `Block "${block.id}" can't take this version`, continuity);
|
|
192
194
|
}
|
|
193
195
|
|
|
194
|
-
const outcome = await binding.publish({
|
|
196
|
+
const outcome = await binding.publish({
|
|
197
|
+
tenantId,
|
|
198
|
+
projectId: projectId as ProjectId,
|
|
199
|
+
block: withCarriedSchema(latest, block),
|
|
200
|
+
});
|
|
195
201
|
return published(c, outcome);
|
|
196
202
|
});
|
|
197
203
|
|
|
@@ -243,7 +249,11 @@ function isBlockKind(value: string): value is BlockKind {
|
|
|
243
249
|
return (BLOCK_KINDS as readonly string[]).includes(value);
|
|
244
250
|
}
|
|
245
251
|
|
|
246
|
-
/**
|
|
252
|
+
/**
|
|
253
|
+
* A new version against the block's latest: the same kind, and a
|
|
254
|
+
* settings version without a schema of its own satisfies the schema it
|
|
255
|
+
* keeps (`carriedSchema`).
|
|
256
|
+
*/
|
|
247
257
|
function continuityIssues(
|
|
248
258
|
latest: BlockRecord | null,
|
|
249
259
|
block: BlockDefinition,
|
|
@@ -257,17 +267,32 @@ function continuityIssues(
|
|
|
257
267
|
},
|
|
258
268
|
];
|
|
259
269
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
const carried = carriedSchema(latest, block);
|
|
271
|
+
if (block.kind !== 'settings' || carried === undefined) return [];
|
|
272
|
+
return settingsSchemaIssues(block.content.values, carried).map((i) => ({
|
|
273
|
+
...i,
|
|
274
|
+
message: `${i.message} (the schema of version ${latest.version})`,
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The schema a settings version that gives none keeps: the latest
|
|
280
|
+
* version's. A version that gives a schema replaces it (`{}` drops the
|
|
281
|
+
* check on purpose).
|
|
282
|
+
*/
|
|
283
|
+
function carriedSchema(
|
|
284
|
+
latest: BlockRecord | null,
|
|
285
|
+
block: BlockDefinition,
|
|
286
|
+
): Readonly<Record<string, unknown>> | undefined {
|
|
287
|
+
if (latest?.kind !== 'settings' || block.kind !== 'settings') return undefined;
|
|
288
|
+
return block.content.schema === undefined ? latest.content.schema : undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** The version as stored: with the schema it keeps, if any. */
|
|
292
|
+
function withCarriedSchema(latest: BlockRecord | null, block: BlockDefinition): BlockDefinition {
|
|
293
|
+
const schema = carriedSchema(latest, block);
|
|
294
|
+
if (schema === undefined || block.kind !== 'settings') return block;
|
|
295
|
+
return { ...block, content: { ...block.content, schema } };
|
|
271
296
|
}
|
|
272
297
|
|
|
273
298
|
/** The response to a publish outcome. */
|
|
@@ -18,6 +18,7 @@ import { statusFor, toWireError } from '../errors.js';
|
|
|
18
18
|
import type { AppEnv } from '../types.js';
|
|
19
19
|
import { clampLimit, decodeCursor, encodeCursor } from './pagination.js';
|
|
20
20
|
import { parseListScope } from './scope-params.js';
|
|
21
|
+
import { refuseMalformedUuidParam } from './uuid-param.js';
|
|
21
22
|
|
|
22
23
|
const PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23
24
|
|
|
@@ -28,6 +29,15 @@ const PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]
|
|
|
28
29
|
* order); conversation list sort is `openedAt DESC, id DESC`
|
|
29
30
|
* (most-recently-opened first).
|
|
30
31
|
*/
|
|
32
|
+
/**
|
|
33
|
+
* A `:conversationId` that isn't a conversation id is a 400
|
|
34
|
+
* (`uuid-param.ts`), not a 500 from the uuid cast (as `runs` does).
|
|
35
|
+
*/
|
|
36
|
+
const refuseMalformedConversationId = refuseMalformedUuidParam(
|
|
37
|
+
'conversationId',
|
|
38
|
+
'a conversation id',
|
|
39
|
+
);
|
|
40
|
+
|
|
31
41
|
export function conversationsRouter(
|
|
32
42
|
conversationBinding: ConversationBinding,
|
|
33
43
|
runBinding: RunBinding,
|
|
@@ -62,6 +72,23 @@ export function conversationsRouter(
|
|
|
62
72
|
|
|
63
73
|
const agentIdRaw = c.req.query('agentId');
|
|
64
74
|
|
|
75
|
+
// Replay conversations (a comparison's replays) are left out unless asked for.
|
|
76
|
+
const replaysRaw = c.req.query('replays');
|
|
77
|
+
if (
|
|
78
|
+
replaysRaw !== undefined &&
|
|
79
|
+
replaysRaw !== 'exclude' &&
|
|
80
|
+
replaysRaw !== 'include' &&
|
|
81
|
+
replaysRaw !== 'only'
|
|
82
|
+
) {
|
|
83
|
+
c.status(statusFor('bad-input') as never);
|
|
84
|
+
return c.json(
|
|
85
|
+
toWireError(
|
|
86
|
+
{ code: 'bad-input', message: '`replays` must be `exclude`, `include` or `only`' },
|
|
87
|
+
requestId,
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
65
92
|
const scopeParsed = parseListScope(c.req.query(), { tenantId });
|
|
66
93
|
if (scopeParsed.kind === 'err') {
|
|
67
94
|
c.status(statusFor('scope-invalid') as never);
|
|
@@ -88,6 +115,7 @@ export function conversationsRouter(
|
|
|
88
115
|
...(scopeParsed.scope !== undefined && { scope: scopeParsed.scope }),
|
|
89
116
|
...(agentIdRaw !== undefined && agentIdRaw.length > 0 && { agentId: agentIdRaw as AgentId }),
|
|
90
117
|
...(statusFilter !== undefined && { status: statusFilter }),
|
|
118
|
+
replays: replaysRaw ?? 'exclude',
|
|
91
119
|
...(before !== undefined && { before }),
|
|
92
120
|
limit,
|
|
93
121
|
});
|
|
@@ -112,7 +140,7 @@ export function conversationsRouter(
|
|
|
112
140
|
});
|
|
113
141
|
|
|
114
142
|
// ---------- GET /:conversationId ----------
|
|
115
|
-
r.get('/:conversationId', async (c) => {
|
|
143
|
+
r.get('/:conversationId', refuseMalformedConversationId, async (c) => {
|
|
116
144
|
const requestId = c.get('requestId');
|
|
117
145
|
const tenantId = c.get('tenantId') as TenantId;
|
|
118
146
|
const conversationId = c.req.param('conversationId') as ConversationId;
|
|
@@ -186,7 +214,7 @@ export function conversationsRouter(
|
|
|
186
214
|
});
|
|
187
215
|
|
|
188
216
|
// ---------- POST /:conversationId/close (idempotent) ----------
|
|
189
|
-
r.post('/:conversationId/close', async (c) => {
|
|
217
|
+
r.post('/:conversationId/close', refuseMalformedConversationId, async (c) => {
|
|
190
218
|
const requestId = c.get('requestId');
|
|
191
219
|
const tenantId = c.get('tenantId') as TenantId;
|
|
192
220
|
const conversationId = c.req.param('conversationId') as ConversationId;
|
|
@@ -214,7 +242,7 @@ export function conversationsRouter(
|
|
|
214
242
|
});
|
|
215
243
|
|
|
216
244
|
// ---------- GET /:conversationId/messages (cursor-paginated, sequence asc) ----------
|
|
217
|
-
r.get('/:conversationId/messages', async (c) => {
|
|
245
|
+
r.get('/:conversationId/messages', refuseMalformedConversationId, async (c) => {
|
|
218
246
|
const requestId = c.get('requestId');
|
|
219
247
|
const tenantId = c.get('tenantId') as TenantId;
|
|
220
248
|
const conversationId = c.req.param('conversationId') as ConversationId;
|
|
@@ -38,9 +38,12 @@ import type {
|
|
|
38
38
|
} from '../deployment-binding.js';
|
|
39
39
|
import { statusFor, toWireError } from '../errors.js';
|
|
40
40
|
import type { FlowRegistryBinding } from '../flow-binding.js';
|
|
41
|
-
import { publishDeployedFlow, resolveFlowPins } from '../flow-pins.js';
|
|
41
|
+
import { type FlowPinsLive, publishDeployedFlow, resolveFlowPins } from '../flow-pins.js';
|
|
42
42
|
import type { GuardrailRegistryBinding } from '../guardrail-binding.js';
|
|
43
43
|
import type { ImageRegistryBinding } from '../image-registry-binding.js';
|
|
44
|
+
import type { LiveVersionBinding } from '../live-version-binding.js';
|
|
45
|
+
import { PublishRefused } from '../publish-refused.js';
|
|
46
|
+
import { type RegistryReadOnly, refuseReadOnly } from '../registry-read-only.js';
|
|
44
47
|
import type { SecretBinding } from '../secrets-binding.js';
|
|
45
48
|
import type { SigningKeyBinding as SigningKeyRegistryBinding } from '../signing-key-binding.js';
|
|
46
49
|
import type { ToolRegistryBinding } from '../tool-binding.js';
|
|
@@ -121,6 +124,11 @@ export interface DeploymentsRouterBindings {
|
|
|
121
124
|
readonly guardrailRegistry?: GuardrailRegistryBinding;
|
|
122
125
|
readonly agentRegistry?: AgentRegistryBinding;
|
|
123
126
|
readonly flowRegistry?: FlowRegistryBinding;
|
|
127
|
+
/**
|
|
128
|
+
* The agents' live versions: a deployed flow's agent step that names no
|
|
129
|
+
* version pins the one live for the deploy's project, else the latest.
|
|
130
|
+
*/
|
|
131
|
+
readonly liveVersions?: LiveVersionBinding;
|
|
124
132
|
/** Data blocks: an agent's block references are pinned at deploy with its tools. */
|
|
125
133
|
readonly blockRegistry?: BlockRegistryBinding;
|
|
126
134
|
/**
|
|
@@ -487,6 +495,11 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
487
495
|
}
|
|
488
496
|
const validated = validation.value;
|
|
489
497
|
|
|
498
|
+
// A read-only registry (under `kindgi dev`, the pack's files) takes
|
|
499
|
+
// nothing a deployment brings: refuse before any write.
|
|
500
|
+
const readOnly = readOnlyTarget(validated, bindings);
|
|
501
|
+
if (readOnly !== undefined) return refuseReadOnly(c, readOnly);
|
|
502
|
+
|
|
490
503
|
// ---- Registry upserts (with rollback tracking) ----
|
|
491
504
|
//
|
|
492
505
|
// Agent-registry, flow-registry, tool-registry, and
|
|
@@ -566,6 +579,8 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
566
579
|
rolled.push(async () => {
|
|
567
580
|
await bindings.toolRegistry?.unregister({ tenantId, toolId, version });
|
|
568
581
|
});
|
|
582
|
+
} else if (outcome.kind !== 'already-registered') {
|
|
583
|
+
throw new PublishRefused('tool', `${tool.id}@${tool.version}`, outcome.kind);
|
|
569
584
|
}
|
|
570
585
|
}
|
|
571
586
|
}
|
|
@@ -589,6 +604,8 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
589
604
|
rolled.push(async () => {
|
|
590
605
|
await bindings.guardrailRegistry?.unregister({ tenantId, guardrailId });
|
|
591
606
|
});
|
|
607
|
+
} else if (outcome.kind !== 'already-registered') {
|
|
608
|
+
throw new PublishRefused('guardrail', guardrail.id, outcome.kind);
|
|
592
609
|
}
|
|
593
610
|
}
|
|
594
611
|
}
|
|
@@ -613,6 +630,7 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
613
630
|
flows: bindings.flowRegistry,
|
|
614
631
|
tools: bindings.toolRegistry,
|
|
615
632
|
agents: bindings.agentRegistry,
|
|
633
|
+
...(bindings.liveVersions !== undefined && { live: bindings.liveVersions }),
|
|
616
634
|
tenantId,
|
|
617
635
|
projectId: defaultProjectId,
|
|
618
636
|
defined: validated.flows,
|
|
@@ -624,6 +642,22 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
624
642
|
}
|
|
625
643
|
} catch (cause) {
|
|
626
644
|
await rollback(rolled);
|
|
645
|
+
// A primitive refused with a typed outcome is the caller's to fix:
|
|
646
|
+
// that outcome's own status and code. Anything thrown is a 500.
|
|
647
|
+
if (cause instanceof PublishRefused) {
|
|
648
|
+
c.status(statusFor(cause.code) as never);
|
|
649
|
+
return c.json(
|
|
650
|
+
toWireError(
|
|
651
|
+
{
|
|
652
|
+
code: cause.code,
|
|
653
|
+
message: `${cause.message}; nothing was deployed`,
|
|
654
|
+
primitive: cause.primitive,
|
|
655
|
+
id: cause.id,
|
|
656
|
+
},
|
|
657
|
+
requestId,
|
|
658
|
+
),
|
|
659
|
+
);
|
|
660
|
+
}
|
|
627
661
|
if (cause instanceof UnpinnableDeploy) {
|
|
628
662
|
c.status(statusFor('invalid-agent') as never);
|
|
629
663
|
return c.json(
|
|
@@ -1489,6 +1523,9 @@ async function registerAgents(input: {
|
|
|
1489
1523
|
for (const agent of input.defined) {
|
|
1490
1524
|
const outcome = await agents.publish({ tenantId, projectId, agent, enqueueTuples });
|
|
1491
1525
|
if (outcome.kind === 'ok') written(outcome.agentId, outcome.version);
|
|
1526
|
+
else if (outcome.kind !== 'already-registered') {
|
|
1527
|
+
throw new PublishRefused('agent', `${agent.id}@${agent.version}`, outcome.kind);
|
|
1528
|
+
}
|
|
1492
1529
|
}
|
|
1493
1530
|
return input.defined.map(deployedPrimitive);
|
|
1494
1531
|
}
|
|
@@ -1518,7 +1555,10 @@ async function registerAgents(input: {
|
|
|
1518
1555
|
* as, pushing a rollback for each version it writes. Runs after the
|
|
1519
1556
|
* agents, so a flow's agent pins see the versions this deploy
|
|
1520
1557
|
* registered: a tool change cascades through an agent into a flow
|
|
1521
|
-
* within the one deploy, each derived once.
|
|
1558
|
+
* within the one deploy, each derived once. An agent step that names no
|
|
1559
|
+
* version pins what a run in the project gets: the agent's live version
|
|
1560
|
+
* there, with `live`, else the latest (this deploy's, when it registered
|
|
1561
|
+
* one).
|
|
1522
1562
|
*
|
|
1523
1563
|
* With the tool and agent registries, every flow is pinned first; one
|
|
1524
1564
|
* that runs a tool or agent with no published version throws
|
|
@@ -1529,12 +1569,13 @@ async function registerFlows(input: {
|
|
|
1529
1569
|
readonly flows: FlowRegistryBinding;
|
|
1530
1570
|
readonly tools: ToolRegistryBinding | undefined;
|
|
1531
1571
|
readonly agents: AgentRegistryBinding | undefined;
|
|
1572
|
+
readonly live?: LiveVersionBinding;
|
|
1532
1573
|
readonly tenantId: TenantId;
|
|
1533
1574
|
readonly projectId: ProjectId;
|
|
1534
1575
|
readonly defined: readonly Flow[];
|
|
1535
1576
|
readonly rolled: RollbackAction[];
|
|
1536
1577
|
}): Promise<DeployedFlow[]> {
|
|
1537
|
-
const { flows, tools, agents, tenantId, projectId, rolled } = input;
|
|
1578
|
+
const { flows, tools, agents, live, tenantId, projectId, rolled } = input;
|
|
1538
1579
|
const enqueueTuples = (flowId: string) =>
|
|
1539
1580
|
tuplesForCreate({ kind: 'flow', id: flowId as FlowId, tenantId, projectId });
|
|
1540
1581
|
const written = (flowId: FlowId, version: string) =>
|
|
@@ -1546,11 +1587,20 @@ async function registerFlows(input: {
|
|
|
1546
1587
|
for (const flow of input.defined) {
|
|
1547
1588
|
const outcome = await flows.publish({ tenantId, projectId, flow, enqueueTuples });
|
|
1548
1589
|
if (outcome.kind === 'ok') written(outcome.flowId, outcome.version as unknown as string);
|
|
1590
|
+
else if (outcome.kind !== 'already-registered') {
|
|
1591
|
+
throw new PublishRefused('flow', `${flow.id}@${flow.version}`, outcome.kind);
|
|
1592
|
+
}
|
|
1549
1593
|
}
|
|
1550
1594
|
return input.defined.map(deployedPrimitive);
|
|
1551
1595
|
}
|
|
1552
1596
|
|
|
1553
|
-
const pinsOf = await pinFlows(
|
|
1597
|
+
const pinsOf = await pinFlows(
|
|
1598
|
+
tools,
|
|
1599
|
+
agents,
|
|
1600
|
+
tenantId,
|
|
1601
|
+
input.defined,
|
|
1602
|
+
live !== undefined ? { binding: live, projectId } : undefined,
|
|
1603
|
+
);
|
|
1554
1604
|
const deployed: DeployedFlow[] = [];
|
|
1555
1605
|
for (const [i, flow] of input.defined.entries()) {
|
|
1556
1606
|
const outcome = await publishDeployedFlow({
|
|
@@ -1575,11 +1625,12 @@ async function pinFlows(
|
|
|
1575
1625
|
agents: AgentRegistryBinding,
|
|
1576
1626
|
tenantId: TenantId,
|
|
1577
1627
|
defined: readonly Flow[],
|
|
1628
|
+
live: FlowPinsLive | undefined,
|
|
1578
1629
|
): Promise<FlowPins[]> {
|
|
1579
1630
|
const pins: FlowPins[] = [];
|
|
1580
1631
|
const unpinnable: UnpinnableRef[] = [];
|
|
1581
1632
|
for (const [i, flow] of defined.entries()) {
|
|
1582
|
-
const resolved = await resolveFlowPins(tools, agents, tenantId, flow);
|
|
1633
|
+
const resolved = await resolveFlowPins(tools, agents, tenantId, flow, live);
|
|
1583
1634
|
if (resolved.kind === 'ok') {
|
|
1584
1635
|
pins.push(resolved.pins);
|
|
1585
1636
|
continue;
|
|
@@ -1701,3 +1752,22 @@ export function sha256HexPrefixed(bytes: Uint8Array): string {
|
|
|
1701
1752
|
const hex = createHash('sha256').update(bytes).digest('hex');
|
|
1702
1753
|
return `sha256:${hex}`;
|
|
1703
1754
|
}
|
|
1755
|
+
|
|
1756
|
+
/** The first read-only registry a deployment would publish into, if any. */
|
|
1757
|
+
function readOnlyTarget(
|
|
1758
|
+
validated: {
|
|
1759
|
+
readonly tools: readonly unknown[];
|
|
1760
|
+
readonly agents: readonly unknown[];
|
|
1761
|
+
readonly flows: readonly unknown[];
|
|
1762
|
+
readonly guardrails: readonly unknown[];
|
|
1763
|
+
},
|
|
1764
|
+
bindings: DeploymentsRouterBindings,
|
|
1765
|
+
): RegistryReadOnly | undefined {
|
|
1766
|
+
const targets = [
|
|
1767
|
+
[validated.tools, bindings.toolRegistry?.readOnly],
|
|
1768
|
+
[validated.agents, bindings.agentRegistry?.readOnly],
|
|
1769
|
+
[validated.flows, bindings.flowRegistry?.readOnly],
|
|
1770
|
+
[validated.guardrails, bindings.guardrailRegistry?.readOnly],
|
|
1771
|
+
] as const;
|
|
1772
|
+
return targets.find(([brought, readOnly]) => brought.length > 0 && readOnly !== undefined)?.[1];
|
|
1773
|
+
}
|
package/src/routes/env.ts
CHANGED
|
@@ -87,6 +87,7 @@ export function envRouter(envBinding: EnvBinding, authorizer?: Authorizer): Hono
|
|
|
87
87
|
});
|
|
88
88
|
return c.json({
|
|
89
89
|
data: page.data.map(serializeEnvRecord),
|
|
90
|
+
hasMore: page.nextCursor !== undefined,
|
|
90
91
|
...(page.nextCursor !== undefined && { nextCursor: page.nextCursor as unknown as string }),
|
|
91
92
|
});
|
|
92
93
|
});
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
// Copyright (C) 2026 Kindgi Inc.
|
|
3
3
|
|
|
4
4
|
import type { AgentId } from '@kindgi/agents';
|
|
5
|
+
import type { FlowVersionOverrides } from '@kindgi/flow';
|
|
5
6
|
import type { ProjectId, Semver } from '@kindgi/types';
|
|
6
7
|
|
|
7
8
|
import type { EvalBaseline, EvalComparison } from '../eval-run-binding.js';
|
|
8
9
|
import { DEFAULT_COMPARISON } from '../judged-dispatcher.js';
|
|
10
|
+
import { parseSegmentsBody } from './segments.js';
|
|
9
11
|
|
|
10
12
|
const MAX_REPETITIONS = 10;
|
|
11
13
|
const MAX_K = 100;
|
|
@@ -19,19 +21,18 @@ function obj(value: unknown): Record<string, unknown> | undefined {
|
|
|
19
21
|
function parseLive(raw: unknown): EvalBaseline | string {
|
|
20
22
|
const live = obj(raw);
|
|
21
23
|
if (live === undefined) return '`baseline.live` must be an object';
|
|
22
|
-
const segments = live.segments === undefined ? undefined : obj(live.segments);
|
|
23
|
-
const stringsOnly =
|
|
24
|
-
segments !== undefined && Object.values(segments).every((v) => typeof v === 'string');
|
|
25
|
-
if (live.segments !== undefined && !stringsOnly) {
|
|
26
|
-
return '`baseline.live.segments` must be an object of strings';
|
|
27
|
-
}
|
|
28
24
|
if (live.projectId !== undefined && typeof live.projectId !== 'string') {
|
|
29
25
|
return '`baseline.live.projectId` must be a string';
|
|
30
26
|
}
|
|
27
|
+
const path = parseSegmentsBody(live.segments, 'baseline.live.segments');
|
|
28
|
+
if (path.kind === 'err') return path.message;
|
|
29
|
+
if (path.segments !== undefined && live.projectId === undefined) {
|
|
30
|
+
return '`baseline.live.segments` is a segment path in a project: it needs `baseline.live.projectId`';
|
|
31
|
+
}
|
|
31
32
|
return {
|
|
32
33
|
live: {
|
|
33
34
|
...(typeof live.projectId === 'string' && { projectId: live.projectId as ProjectId }),
|
|
34
|
-
...(segments !== undefined && { segments: segments
|
|
35
|
+
...(path.segments !== undefined && { segments: path.segments }),
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
}
|
|
@@ -49,6 +50,34 @@ function parseBaseline(raw: unknown): EvalBaseline | string {
|
|
|
49
50
|
return { agentId: o.agentId as AgentId, version: o.version as Semver };
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/** `{ id: version }` with non-empty strings; an error message otherwise. */
|
|
54
|
+
function versionMap(raw: unknown, name: string): Readonly<Record<string, string>> | string {
|
|
55
|
+
const o = obj(raw);
|
|
56
|
+
const valid =
|
|
57
|
+
o !== undefined &&
|
|
58
|
+
Object.entries(o).every(([id, v]) => id !== '' && typeof v === 'string' && v !== '');
|
|
59
|
+
return valid
|
|
60
|
+
? (o as Record<string, string>)
|
|
61
|
+
: `\`${name}\` must be an object of { id: version } with non-empty strings`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `versions: { agents?, tools? }`; `undefined` when it names nothing. */
|
|
65
|
+
function parseVersions(raw: unknown): FlowVersionOverrides | undefined | string {
|
|
66
|
+
const o = obj(raw);
|
|
67
|
+
if (o === undefined) return '`versions` must be an object: { agents?, tools? }';
|
|
68
|
+
const extra = Object.keys(o).filter((key) => key !== 'agents' && key !== 'tools');
|
|
69
|
+
if (extra.length > 0) return `\`versions\` takes \`agents\` and \`tools\`, not \`${extra[0]}\``;
|
|
70
|
+
const agents = o.agents === undefined ? {} : versionMap(o.agents, 'versions.agents');
|
|
71
|
+
if (typeof agents === 'string') return agents;
|
|
72
|
+
const tools = o.tools === undefined ? {} : versionMap(o.tools, 'versions.tools');
|
|
73
|
+
if (typeof tools === 'string') return tools;
|
|
74
|
+
if (Object.keys(agents).length === 0 && Object.keys(tools).length === 0) return undefined;
|
|
75
|
+
return {
|
|
76
|
+
...(Object.keys(agents).length > 0 && { agents }),
|
|
77
|
+
...(Object.keys(tools).length > 0 && { tools }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
52
81
|
function integerIn(raw: unknown, name: string, max: number): number | string {
|
|
53
82
|
return typeof raw === 'number' && Number.isInteger(raw) && raw >= 1 && raw <= max
|
|
54
83
|
? raw
|
|
@@ -57,23 +86,40 @@ function integerIn(raw: unknown, name: string, max: number): number | string {
|
|
|
57
86
|
|
|
58
87
|
/**
|
|
59
88
|
* A comparison eval run's settings from the start body: `baseline`,
|
|
60
|
-
* `reads`, `repetitions
|
|
61
|
-
*
|
|
89
|
+
* `reads`, `repetitions`, `k`, `classWeights` and a flow candidate's
|
|
90
|
+
* `versions`.
|
|
91
|
+
* `undefined` when none is given (the defaults apply); an error message
|
|
92
|
+
* for a bad one.
|
|
62
93
|
*/
|
|
63
94
|
export function parseComparison(
|
|
64
95
|
b: Readonly<Record<string, unknown>>,
|
|
65
96
|
):
|
|
66
97
|
| { readonly kind: 'ok'; readonly value?: EvalComparison }
|
|
67
98
|
| { readonly kind: 'err'; readonly message: string } {
|
|
68
|
-
const { baseline, reads, repetitions, k } = b;
|
|
99
|
+
const { baseline, reads, repetitions, k, classWeights } = b;
|
|
100
|
+
const versions = b.versions === undefined ? undefined : parseVersions(b.versions);
|
|
101
|
+
if (typeof versions === 'string') return { kind: 'err', message: versions };
|
|
69
102
|
if (
|
|
70
103
|
baseline === undefined &&
|
|
71
104
|
reads === undefined &&
|
|
72
105
|
repetitions === undefined &&
|
|
73
|
-
k === undefined
|
|
106
|
+
k === undefined &&
|
|
107
|
+
versions === undefined &&
|
|
108
|
+
classWeights === undefined
|
|
74
109
|
) {
|
|
75
110
|
return { kind: 'ok' };
|
|
76
111
|
}
|
|
112
|
+
if (
|
|
113
|
+
classWeights !== undefined &&
|
|
114
|
+
classWeights !== 'as-recorded' &&
|
|
115
|
+
classWeights !== 'restricted-only'
|
|
116
|
+
) {
|
|
117
|
+
return {
|
|
118
|
+
kind: 'err',
|
|
119
|
+
message:
|
|
120
|
+
"`classWeights` must be 'as-recorded' (every judgment at its class's weight) or 'restricted-only' (only judgments recorded under a restricted class)",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
77
123
|
const parsedBaseline =
|
|
78
124
|
baseline === undefined ? DEFAULT_COMPARISON.baseline : parseBaseline(baseline);
|
|
79
125
|
if (typeof parsedBaseline === 'string' && parsedBaseline !== 'recorded') {
|
|
@@ -96,6 +142,8 @@ export function parseComparison(
|
|
|
96
142
|
reads: reads ?? DEFAULT_COMPARISON.reads,
|
|
97
143
|
repetitions: reps,
|
|
98
144
|
k: topK,
|
|
145
|
+
...(versions !== undefined && { versions }),
|
|
146
|
+
...(classWeights !== undefined && { classWeights }),
|
|
99
147
|
},
|
|
100
148
|
};
|
|
101
149
|
}
|
package/src/routes/eval-runs.ts
CHANGED
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
type EvalRunStatus,
|
|
18
18
|
type FlowRef,
|
|
19
19
|
} from '../eval-run-binding.js';
|
|
20
|
+
import { VERSIONS_NEED_A_FLOW } from '../judged-dispatcher.js';
|
|
20
21
|
import type { AppEnv } from '../types.js';
|
|
21
22
|
import { parseComparison } from './eval-comparison.js';
|
|
23
|
+
import { type FlowVersionsCheck, checkFlowVersions } from './eval-versions.js';
|
|
22
24
|
import { clampLimit } from './pagination.js';
|
|
23
25
|
import { parseScopeParams } from './scope-params.js';
|
|
24
26
|
import { formatSseFrame } from './sse.js';
|
|
@@ -47,16 +49,49 @@ export interface EvalRunsRouters {
|
|
|
47
49
|
readonly readback: Hono<AppEnv>;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
/**
|
|
53
|
+
* `versionsCheck`: where a flow candidate's `versions` are checked
|
|
54
|
+
* against the flow version at start (without it, they're applied as
|
|
55
|
+
* given).
|
|
56
|
+
*/
|
|
57
|
+
export function evalRunsRouters(
|
|
58
|
+
binding: EvalRunBinding,
|
|
59
|
+
versionsCheck?: FlowVersionsCheck,
|
|
60
|
+
): EvalRunsRouters {
|
|
51
61
|
return {
|
|
52
|
-
start: startRouter(binding),
|
|
62
|
+
start: startRouter(binding, versionsCheck),
|
|
53
63
|
readback: readbackRouter(binding),
|
|
54
64
|
};
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
// ---------- POST /v1/eval-suites/:suiteId/runs ----------
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
/** The `validation-failed` error for a flow candidate's `versions` that don't fit the flow; `undefined` when they do. */
|
|
70
|
+
async function versionsRefusal(
|
|
71
|
+
check: FlowVersionsCheck | undefined,
|
|
72
|
+
tenantId: TenantId,
|
|
73
|
+
start: ParsedStartBody,
|
|
74
|
+
) {
|
|
75
|
+
const versions = start.comparison?.versions;
|
|
76
|
+
const flowRef = start.flowRef;
|
|
77
|
+
if (check === undefined || versions === undefined || flowRef?.version === undefined) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const issues = await checkFlowVersions(
|
|
81
|
+
check,
|
|
82
|
+
tenantId,
|
|
83
|
+
{ flowId: flowRef.flowId as unknown as string, version: flowRef.version },
|
|
84
|
+
versions,
|
|
85
|
+
);
|
|
86
|
+
if (issues.length === 0) return undefined;
|
|
87
|
+
return {
|
|
88
|
+
code: 'validation-failed' as const,
|
|
89
|
+
message: `The versions don't fit flow ${flowRef.flowId as unknown as string} ${flowRef.version} (${issues.length} issue${issues.length === 1 ? '' : 's'})`,
|
|
90
|
+
issues: issues as unknown as Record<string, unknown>[],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function startRouter(binding: EvalRunBinding, versionsCheck?: FlowVersionsCheck): Hono<AppEnv> {
|
|
60
95
|
const r = new Hono<AppEnv>();
|
|
61
96
|
|
|
62
97
|
r.post('/:suiteId/runs', async (c) => {
|
|
@@ -84,6 +119,11 @@ function startRouter(binding: EvalRunBinding): Hono<AppEnv> {
|
|
|
84
119
|
c.status(statusFor(parsed.error.code) as never);
|
|
85
120
|
return c.json(toWireError(parsed.error, requestId));
|
|
86
121
|
}
|
|
122
|
+
const refusal = await versionsRefusal(versionsCheck, tenantId, parsed.value);
|
|
123
|
+
if (refusal !== undefined) {
|
|
124
|
+
c.status(statusFor(refusal.code) as never);
|
|
125
|
+
return c.json(toWireError(refusal, requestId));
|
|
126
|
+
}
|
|
87
127
|
|
|
88
128
|
const outcome = await binding.start({
|
|
89
129
|
tenantId,
|
|
@@ -627,6 +667,9 @@ function parseStartBody(
|
|
|
627
667
|
if (comparison.kind === 'err') {
|
|
628
668
|
return { kind: 'err', error: { code: 'bad-input', message: comparison.message } };
|
|
629
669
|
}
|
|
670
|
+
if (comparison.value?.versions !== undefined && agentRef !== undefined) {
|
|
671
|
+
return { kind: 'err', error: { code: 'bad-input', message: VERSIONS_NEED_A_FLOW } };
|
|
672
|
+
}
|
|
630
673
|
|
|
631
674
|
return {
|
|
632
675
|
kind: 'ok',
|