@kindgi/api 0.1.4-rc.1 → 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 +12 -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 +23 -4
- package/dist/app.js.map +1 -1
- package/dist/deploy-versions.d.ts +8 -5
- package/dist/deploy-versions.d.ts.map +1 -1
- package/dist/deploy-versions.js +3 -9
- package/dist/deploy-versions.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +25 -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 +14 -3
- package/dist/eval-run-binding.d.ts.map +1 -1
- package/dist/eval-run-binding.js.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/handler-binding.d.ts +14 -2
- package/dist/handler-binding.d.ts.map +1 -1
- package/dist/index.d.ts +10 -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 +16 -1
- package/dist/judged-dispatcher.d.ts.map +1 -1
- package/dist/judged-dispatcher.js +45 -6
- 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 +504 -22
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +37 -0
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +709 -5
- 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/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 +31 -3
- 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/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 +33 -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 +2 -1
- package/dist/routes/eval-comparison.d.ts.map +1 -1
- package/dist/routes/eval-comparison.js +22 -9
- package/dist/routes/eval-comparison.js.map +1 -1
- package/dist/routes/flows.d.ts +14 -7
- package/dist/routes/flows.d.ts.map +1 -1
- package/dist/routes/flows.js +10 -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/hierarchy-errors.d.ts +11 -0
- package/dist/routes/hierarchy-errors.d.ts.map +1 -1
- package/dist/routes/hierarchy-errors.js +13 -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 +47 -3
- 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 +22 -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 +42 -3
- package/dist/routes/teams.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/openapi.json +6995 -3625
- package/package.json +21 -21
- package/src/agent-binding.ts +12 -2
- package/src/agent-pins.ts +3 -1
- package/src/app.ts +37 -3
- package/src/deploy-versions.ts +11 -14
- package/src/errors.ts +25 -0
- package/src/eval-case-binding.ts +7 -0
- package/src/eval-run-binding.ts +24 -3
- package/src/flow-pins.ts +49 -9
- package/src/gate-policy-binding.ts +171 -0
- package/src/gate.ts +467 -0
- package/src/handler-binding.ts +14 -2
- package/src/index.ts +46 -1
- package/src/judged-dispatcher.ts +74 -6
- 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 +609 -22
- package/src/openapi/schemas.ts +788 -5
- package/src/publish-refused.ts +31 -0
- package/src/retention-binding.ts +30 -0
- package/src/routes/agent-releases.ts +581 -0
- package/src/routes/agents.ts +43 -2
- package/src/routes/approvals.ts +36 -8
- package/src/routes/audit.ts +10 -0
- package/src/routes/auth.ts +1 -1
- package/src/routes/conversations.ts +31 -3
- package/src/routes/deployments.ts +50 -5
- package/src/routes/env.ts +1 -0
- package/src/routes/eval-comparison.ts +24 -10
- package/src/routes/flows.ts +27 -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/hierarchy-errors.ts +14 -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 +54 -2
- package/src/routes/retention.ts +21 -2
- package/src/routes/reviewers.ts +23 -4
- package/src/routes/runs.ts +33 -20
- package/src/routes/secrets.ts +2 -0
- package/src/routes/segments.ts +75 -0
- package/src/routes/teams.ts +53 -3
- package/src/routes/uuid-param.ts +28 -0
package/src/routes/agents.ts
CHANGED
|
@@ -23,10 +23,18 @@ import {
|
|
|
23
23
|
nextFreeAgentVersion,
|
|
24
24
|
} from '../derive-agent-version.js';
|
|
25
25
|
import { statusFor, toWireError } from '../errors.js';
|
|
26
|
+
import type { AgentReleaseBindings } from '../live-version-binding.js';
|
|
26
27
|
import type { Authorizer } from '../middleware/authorize.js';
|
|
27
28
|
import { refuseWritesWhenReadOnly } from '../registry-read-only.js';
|
|
28
29
|
import type { ToolRegistryBinding } from '../tool-binding.js';
|
|
29
30
|
import type { AppEnv } from '../types.js';
|
|
31
|
+
import {
|
|
32
|
+
type AgentReleaseGateDeps,
|
|
33
|
+
isPromotionCheck,
|
|
34
|
+
isPromotionWrite,
|
|
35
|
+
mountAgentReleaseRoutes,
|
|
36
|
+
} from './agent-releases.js';
|
|
37
|
+
import { liveScopeToWire } from './live-scope-wire.js';
|
|
30
38
|
import { clampLimit } from './pagination.js';
|
|
31
39
|
import { parseScopeParams } from './scope-params.js';
|
|
32
40
|
|
|
@@ -52,6 +60,10 @@ export function agentsRouter(
|
|
|
52
60
|
authorizer?: Authorizer,
|
|
53
61
|
toolRegistry?: ToolRegistryBinding,
|
|
54
62
|
blockRegistry?: BlockRegistryBinding,
|
|
63
|
+
/** Live versions per scope and promotions (evals step 4); absent → those routes aren't mounted. */
|
|
64
|
+
releases?: AgentReleaseBindings,
|
|
65
|
+
/** What a promotion's gate reads besides the releases (evals step 4b). */
|
|
66
|
+
gateDeps?: AgentReleaseGateDeps,
|
|
55
67
|
): Hono<AppEnv> {
|
|
56
68
|
const r = new Hono<AppEnv>();
|
|
57
69
|
// A read-only registry (under `kindgi dev`, the pack's files) refuses
|
|
@@ -71,6 +83,9 @@ export function agentsRouter(
|
|
|
71
83
|
// POST /:agentId/versions → publish on the agent (derive)
|
|
72
84
|
// POST /:agentId/versions/:version/unregister → admin on the agent
|
|
73
85
|
// POST /:agentId/versions/:version/reinstate → admin on the agent
|
|
86
|
+
// POST /:agentId/promotions, /live/rollback, /live/unpin → promote on the agent
|
|
87
|
+
// POST /:agentId/promotions/check → read on the agent (changes nothing)
|
|
88
|
+
// GET /:agentId/live, /live-versions, /promotions[/…] → read on the agent
|
|
74
89
|
// GET / (list) → tenant-scoped fetch;
|
|
75
90
|
// with a project scope
|
|
76
91
|
// (?scopeKind=project&scopeId=),
|
|
@@ -108,9 +123,16 @@ export function agentsRouter(
|
|
|
108
123
|
});
|
|
109
124
|
r.use('/:agentId/*', async (c, next) => {
|
|
110
125
|
// Deriving a version (POST …/versions) is `publish` on the agent;
|
|
111
|
-
//
|
|
126
|
+
// changing what's live is its own permission (`promote`, granted to
|
|
127
|
+
// the agent's admins); unregister and reinstate are `admin`.
|
|
112
128
|
const action =
|
|
113
|
-
c.req.method === 'GET'
|
|
129
|
+
c.req.method === 'GET' || isPromotionCheck(c.req.method, c.req.path)
|
|
130
|
+
? 'read'
|
|
131
|
+
: isPromotionWrite(c.req.method, c.req.path)
|
|
132
|
+
? 'promote'
|
|
133
|
+
: c.req.path.endsWith('/versions')
|
|
134
|
+
? 'publish'
|
|
135
|
+
: 'admin';
|
|
114
136
|
const agentId = c.req.param('agentId') ?? '';
|
|
115
137
|
const mw = authorizer.authorize(action, () => ref('agent', agentId));
|
|
116
138
|
return mw(c, next);
|
|
@@ -434,6 +456,23 @@ export function agentsRouter(
|
|
|
434
456
|
const version = c.req.param('version') as Semver;
|
|
435
457
|
|
|
436
458
|
const outcome = await binding.unregister({ tenantId, agentId, version });
|
|
459
|
+
if (!outcome.unregistered && outcome.live !== undefined && outcome.live.length > 0) {
|
|
460
|
+
c.status(statusFor('agent-version-live') as never);
|
|
461
|
+
return c.json(
|
|
462
|
+
toWireError(
|
|
463
|
+
{
|
|
464
|
+
code: 'agent-version-live',
|
|
465
|
+
message: `${agentId as unknown as string} ${version as unknown as string} is live in ${
|
|
466
|
+
outcome.live.length === 1 ? 'a scope' : `${outcome.live.length} scopes`
|
|
467
|
+
}: roll back, unpin, or promote another version there first, then unregister it.`,
|
|
468
|
+
agentId: agentId as unknown as string,
|
|
469
|
+
version: version as unknown as string,
|
|
470
|
+
scopes: outcome.live.map(liveScopeToWire),
|
|
471
|
+
},
|
|
472
|
+
requestId,
|
|
473
|
+
),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
437
476
|
if (!outcome.unregistered) {
|
|
438
477
|
c.status(statusFor('agent-not-found') as never);
|
|
439
478
|
return c.json(
|
|
@@ -489,6 +528,8 @@ export function agentsRouter(
|
|
|
489
528
|
});
|
|
490
529
|
});
|
|
491
530
|
|
|
531
|
+
if (releases !== undefined) mountAgentReleaseRoutes(r, binding, releases, gateDeps);
|
|
532
|
+
|
|
492
533
|
return r;
|
|
493
534
|
}
|
|
494
535
|
|
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 ----------
|
|
@@ -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,11 @@ 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';
|
|
44
46
|
import { type RegistryReadOnly, refuseReadOnly } from '../registry-read-only.js';
|
|
45
47
|
import type { SecretBinding } from '../secrets-binding.js';
|
|
46
48
|
import type { SigningKeyBinding as SigningKeyRegistryBinding } from '../signing-key-binding.js';
|
|
@@ -122,6 +124,11 @@ export interface DeploymentsRouterBindings {
|
|
|
122
124
|
readonly guardrailRegistry?: GuardrailRegistryBinding;
|
|
123
125
|
readonly agentRegistry?: AgentRegistryBinding;
|
|
124
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;
|
|
125
132
|
/** Data blocks: an agent's block references are pinned at deploy with its tools. */
|
|
126
133
|
readonly blockRegistry?: BlockRegistryBinding;
|
|
127
134
|
/**
|
|
@@ -572,6 +579,8 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
572
579
|
rolled.push(async () => {
|
|
573
580
|
await bindings.toolRegistry?.unregister({ tenantId, toolId, version });
|
|
574
581
|
});
|
|
582
|
+
} else if (outcome.kind !== 'already-registered') {
|
|
583
|
+
throw new PublishRefused('tool', `${tool.id}@${tool.version}`, outcome.kind);
|
|
575
584
|
}
|
|
576
585
|
}
|
|
577
586
|
}
|
|
@@ -595,6 +604,8 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
595
604
|
rolled.push(async () => {
|
|
596
605
|
await bindings.guardrailRegistry?.unregister({ tenantId, guardrailId });
|
|
597
606
|
});
|
|
607
|
+
} else if (outcome.kind !== 'already-registered') {
|
|
608
|
+
throw new PublishRefused('guardrail', guardrail.id, outcome.kind);
|
|
598
609
|
}
|
|
599
610
|
}
|
|
600
611
|
}
|
|
@@ -619,6 +630,7 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
619
630
|
flows: bindings.flowRegistry,
|
|
620
631
|
tools: bindings.toolRegistry,
|
|
621
632
|
agents: bindings.agentRegistry,
|
|
633
|
+
...(bindings.liveVersions !== undefined && { live: bindings.liveVersions }),
|
|
622
634
|
tenantId,
|
|
623
635
|
projectId: defaultProjectId,
|
|
624
636
|
defined: validated.flows,
|
|
@@ -630,6 +642,22 @@ export function deploymentsRouter(bindings: DeploymentsRouterBindings): Hono<App
|
|
|
630
642
|
}
|
|
631
643
|
} catch (cause) {
|
|
632
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
|
+
}
|
|
633
661
|
if (cause instanceof UnpinnableDeploy) {
|
|
634
662
|
c.status(statusFor('invalid-agent') as never);
|
|
635
663
|
return c.json(
|
|
@@ -1495,6 +1523,9 @@ async function registerAgents(input: {
|
|
|
1495
1523
|
for (const agent of input.defined) {
|
|
1496
1524
|
const outcome = await agents.publish({ tenantId, projectId, agent, enqueueTuples });
|
|
1497
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
|
+
}
|
|
1498
1529
|
}
|
|
1499
1530
|
return input.defined.map(deployedPrimitive);
|
|
1500
1531
|
}
|
|
@@ -1524,7 +1555,10 @@ async function registerAgents(input: {
|
|
|
1524
1555
|
* as, pushing a rollback for each version it writes. Runs after the
|
|
1525
1556
|
* agents, so a flow's agent pins see the versions this deploy
|
|
1526
1557
|
* registered: a tool change cascades through an agent into a flow
|
|
1527
|
-
* 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).
|
|
1528
1562
|
*
|
|
1529
1563
|
* With the tool and agent registries, every flow is pinned first; one
|
|
1530
1564
|
* that runs a tool or agent with no published version throws
|
|
@@ -1535,12 +1569,13 @@ async function registerFlows(input: {
|
|
|
1535
1569
|
readonly flows: FlowRegistryBinding;
|
|
1536
1570
|
readonly tools: ToolRegistryBinding | undefined;
|
|
1537
1571
|
readonly agents: AgentRegistryBinding | undefined;
|
|
1572
|
+
readonly live?: LiveVersionBinding;
|
|
1538
1573
|
readonly tenantId: TenantId;
|
|
1539
1574
|
readonly projectId: ProjectId;
|
|
1540
1575
|
readonly defined: readonly Flow[];
|
|
1541
1576
|
readonly rolled: RollbackAction[];
|
|
1542
1577
|
}): Promise<DeployedFlow[]> {
|
|
1543
|
-
const { flows, tools, agents, tenantId, projectId, rolled } = input;
|
|
1578
|
+
const { flows, tools, agents, live, tenantId, projectId, rolled } = input;
|
|
1544
1579
|
const enqueueTuples = (flowId: string) =>
|
|
1545
1580
|
tuplesForCreate({ kind: 'flow', id: flowId as FlowId, tenantId, projectId });
|
|
1546
1581
|
const written = (flowId: FlowId, version: string) =>
|
|
@@ -1552,11 +1587,20 @@ async function registerFlows(input: {
|
|
|
1552
1587
|
for (const flow of input.defined) {
|
|
1553
1588
|
const outcome = await flows.publish({ tenantId, projectId, flow, enqueueTuples });
|
|
1554
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
|
+
}
|
|
1555
1593
|
}
|
|
1556
1594
|
return input.defined.map(deployedPrimitive);
|
|
1557
1595
|
}
|
|
1558
1596
|
|
|
1559
|
-
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
|
+
);
|
|
1560
1604
|
const deployed: DeployedFlow[] = [];
|
|
1561
1605
|
for (const [i, flow] of input.defined.entries()) {
|
|
1562
1606
|
const outcome = await publishDeployedFlow({
|
|
@@ -1581,11 +1625,12 @@ async function pinFlows(
|
|
|
1581
1625
|
agents: AgentRegistryBinding,
|
|
1582
1626
|
tenantId: TenantId,
|
|
1583
1627
|
defined: readonly Flow[],
|
|
1628
|
+
live: FlowPinsLive | undefined,
|
|
1584
1629
|
): Promise<FlowPins[]> {
|
|
1585
1630
|
const pins: FlowPins[] = [];
|
|
1586
1631
|
const unpinnable: UnpinnableRef[] = [];
|
|
1587
1632
|
for (const [i, flow] of defined.entries()) {
|
|
1588
|
-
const resolved = await resolveFlowPins(tools, agents, tenantId, flow);
|
|
1633
|
+
const resolved = await resolveFlowPins(tools, agents, tenantId, flow, live);
|
|
1589
1634
|
if (resolved.kind === 'ok') {
|
|
1590
1635
|
pins.push(resolved.pins);
|
|
1591
1636
|
continue;
|
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
|
});
|
|
@@ -7,6 +7,7 @@ import type { ProjectId, Semver } from '@kindgi/types';
|
|
|
7
7
|
|
|
8
8
|
import type { EvalBaseline, EvalComparison } from '../eval-run-binding.js';
|
|
9
9
|
import { DEFAULT_COMPARISON } from '../judged-dispatcher.js';
|
|
10
|
+
import { parseSegmentsBody } from './segments.js';
|
|
10
11
|
|
|
11
12
|
const MAX_REPETITIONS = 10;
|
|
12
13
|
const MAX_K = 100;
|
|
@@ -20,19 +21,18 @@ function obj(value: unknown): Record<string, unknown> | undefined {
|
|
|
20
21
|
function parseLive(raw: unknown): EvalBaseline | string {
|
|
21
22
|
const live = obj(raw);
|
|
22
23
|
if (live === undefined) return '`baseline.live` must be an object';
|
|
23
|
-
const segments = live.segments === undefined ? undefined : obj(live.segments);
|
|
24
|
-
const stringsOnly =
|
|
25
|
-
segments !== undefined && Object.values(segments).every((v) => typeof v === 'string');
|
|
26
|
-
if (live.segments !== undefined && !stringsOnly) {
|
|
27
|
-
return '`baseline.live.segments` must be an object of strings';
|
|
28
|
-
}
|
|
29
24
|
if (live.projectId !== undefined && typeof live.projectId !== 'string') {
|
|
30
25
|
return '`baseline.live.projectId` must be a string';
|
|
31
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
|
+
}
|
|
32
32
|
return {
|
|
33
33
|
live: {
|
|
34
34
|
...(typeof live.projectId === 'string' && { projectId: live.projectId as ProjectId }),
|
|
35
|
-
...(segments !== undefined && { segments: segments
|
|
35
|
+
...(path.segments !== undefined && { segments: path.segments }),
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
38
|
}
|
|
@@ -86,7 +86,8 @@ function integerIn(raw: unknown, name: string, max: number): number | string {
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* A comparison eval run's settings from the start body: `baseline`,
|
|
89
|
-
* `reads`, `repetitions`, `k` and a flow candidate's
|
|
89
|
+
* `reads`, `repetitions`, `k`, `classWeights` and a flow candidate's
|
|
90
|
+
* `versions`.
|
|
90
91
|
* `undefined` when none is given (the defaults apply); an error message
|
|
91
92
|
* for a bad one.
|
|
92
93
|
*/
|
|
@@ -95,7 +96,7 @@ export function parseComparison(
|
|
|
95
96
|
):
|
|
96
97
|
| { readonly kind: 'ok'; readonly value?: EvalComparison }
|
|
97
98
|
| { readonly kind: 'err'; readonly message: string } {
|
|
98
|
-
const { baseline, reads, repetitions, k } = b;
|
|
99
|
+
const { baseline, reads, repetitions, k, classWeights } = b;
|
|
99
100
|
const versions = b.versions === undefined ? undefined : parseVersions(b.versions);
|
|
100
101
|
if (typeof versions === 'string') return { kind: 'err', message: versions };
|
|
101
102
|
if (
|
|
@@ -103,10 +104,22 @@ export function parseComparison(
|
|
|
103
104
|
reads === undefined &&
|
|
104
105
|
repetitions === undefined &&
|
|
105
106
|
k === undefined &&
|
|
106
|
-
versions === undefined
|
|
107
|
+
versions === undefined &&
|
|
108
|
+
classWeights === undefined
|
|
107
109
|
) {
|
|
108
110
|
return { kind: 'ok' };
|
|
109
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
|
+
}
|
|
110
123
|
const parsedBaseline =
|
|
111
124
|
baseline === undefined ? DEFAULT_COMPARISON.baseline : parseBaseline(baseline);
|
|
112
125
|
if (typeof parsedBaseline === 'string' && parsedBaseline !== 'recorded') {
|
|
@@ -130,6 +143,7 @@ export function parseComparison(
|
|
|
130
143
|
repetitions: reps,
|
|
131
144
|
k: topK,
|
|
132
145
|
...(versions !== undefined && { versions }),
|
|
146
|
+
...(classWeights !== undefined && { classWeights }),
|
|
133
147
|
},
|
|
134
148
|
};
|
|
135
149
|
}
|
package/src/routes/flows.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { UnpinnableRef } from '../agent-pins.js';
|
|
|
12
12
|
import { statusFor, toWireError } from '../errors.js';
|
|
13
13
|
import type { FlowRegistryBinding, FlowVersionRecord } from '../flow-binding.js';
|
|
14
14
|
import { resolveFlowPins } from '../flow-pins.js';
|
|
15
|
+
import type { LiveVersionBinding } from '../live-version-binding.js';
|
|
15
16
|
import type { Authorizer } from '../middleware/authorize.js';
|
|
16
17
|
import { refuseWritesWhenReadOnly } from '../registry-read-only.js';
|
|
17
18
|
import type { ToolRegistryBinding } from '../tool-binding.js';
|
|
@@ -37,16 +38,18 @@ import { parseScopeParams } from './scope-params.js';
|
|
|
37
38
|
* `/v1/flows/:flowId/run` route is added here.
|
|
38
39
|
*
|
|
39
40
|
* With `pinning` (the tool and agent registries), a published version is
|
|
40
|
-
* pinned: each tool it runs
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* pins
|
|
41
|
+
* pinned: each tool it runs resolves once, at publish, to its latest
|
|
42
|
+
* version, and each agent it runs at no named version to what a run in
|
|
43
|
+
* the flow's project would get, its live version there with
|
|
44
|
+
* `pinning.live`, else its latest. Every run of that version uses those
|
|
45
|
+
* (`pins`, see `resolveFlowPins`); one with no published version refuses
|
|
46
|
+
* the publish. Without `pinning`, versions carry no pins and bind the
|
|
47
|
+
* latest versions per run.
|
|
45
48
|
*/
|
|
46
49
|
export function flowsRouter(
|
|
47
50
|
binding: FlowRegistryBinding,
|
|
48
51
|
authorizer?: Authorizer,
|
|
49
|
-
pinning?:
|
|
52
|
+
pinning?: FlowPinning,
|
|
50
53
|
): Hono<AppEnv> {
|
|
51
54
|
const r = new Hono<AppEnv>();
|
|
52
55
|
// A read-only registry (under `kindgi dev`, the pack's files) refuses
|
|
@@ -300,7 +303,7 @@ export function flowsRouter(
|
|
|
300
303
|
// Pin the version: every tool and unversioned agent resolves now,
|
|
301
304
|
// once, to the version its runs use. One with no published version
|
|
302
305
|
// refuses the publish rather than store a partly pinned version.
|
|
303
|
-
const pinned = await pinFlow(pinning, tenantId, loaded.value);
|
|
306
|
+
const pinned = await pinFlow(pinning, tenantId, projectId, loaded.value);
|
|
304
307
|
if (pinned.kind === 'unpinnable') {
|
|
305
308
|
c.status(statusFor('validation-failed') as never);
|
|
306
309
|
return c.json(toWireError(unpinnableFlow(loaded.value, pinned.issues), requestId));
|
|
@@ -415,19 +418,32 @@ export function flowsRouter(
|
|
|
415
418
|
return r;
|
|
416
419
|
}
|
|
417
420
|
|
|
421
|
+
/** The registries a published flow version is pinned against. */
|
|
422
|
+
export interface FlowPinning {
|
|
423
|
+
readonly tools: ToolRegistryBinding;
|
|
424
|
+
readonly agents: AgentRegistryBinding;
|
|
425
|
+
/** The agents' live versions: an agent step pins the one live for the flow's project. */
|
|
426
|
+
readonly live?: LiveVersionBinding;
|
|
427
|
+
}
|
|
428
|
+
|
|
418
429
|
/** The flow with its pins, or the references with no published version; as it is without `pinning`. */
|
|
419
430
|
async function pinFlow(
|
|
420
|
-
pinning:
|
|
421
|
-
| { readonly tools: ToolRegistryBinding; readonly agents: AgentRegistryBinding }
|
|
422
|
-
| undefined,
|
|
431
|
+
pinning: FlowPinning | undefined,
|
|
423
432
|
tenantId: TenantId,
|
|
433
|
+
projectId: ProjectId,
|
|
424
434
|
flow: Flow,
|
|
425
435
|
): Promise<
|
|
426
436
|
| { readonly kind: 'ok'; readonly flow: Flow }
|
|
427
437
|
| { readonly kind: 'unpinnable'; readonly issues: readonly UnpinnableRef[] }
|
|
428
438
|
> {
|
|
429
439
|
if (pinning === undefined) return { kind: 'ok', flow };
|
|
430
|
-
const resolved = await resolveFlowPins(
|
|
440
|
+
const resolved = await resolveFlowPins(
|
|
441
|
+
pinning.tools,
|
|
442
|
+
pinning.agents,
|
|
443
|
+
tenantId,
|
|
444
|
+
flow,
|
|
445
|
+
pinning.live !== undefined ? { binding: pinning.live, projectId } : undefined,
|
|
446
|
+
);
|
|
431
447
|
if (resolved.kind === 'unpinnable') return resolved;
|
|
432
448
|
return {
|
|
433
449
|
kind: 'ok',
|