@ziggs-ai/api-client 0.13.0 → 0.14.1
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/capabilities/agreements.js +33 -3
- package/dist/capabilities/artifacts.d.ts +4 -3
- package/dist/capabilities/artifacts.js +39 -16
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/capabilities/index.js +1 -0
- package/dist/capabilities/introductions.d.ts +6 -0
- package/dist/capabilities/introductions.js +172 -0
- package/dist/capabilities/links.d.ts +1 -0
- package/dist/capabilities/links.js +1 -1
- package/dist/http/ArtifactsClient.d.ts +7 -6
- package/dist/http/ArtifactsClient.js +7 -6
- package/dist/http/ChatClient.d.ts +13 -2
- package/dist/http/ChatClient.js +14 -4
- package/dist/http/ContextGrantsClient.js +20 -24
- package/dist/http/IntroductionsClient.d.ts +71 -0
- package/dist/http/IntroductionsClient.js +54 -0
- package/dist/http/TaskClient.d.ts +25 -0
- package/dist/http/TaskClient.js +51 -2
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/types.d.ts +23 -0
- package/package.json +1 -1
- package/dist/relay/provisionRelayWorkers.d.ts +0 -119
- package/dist/relay/provisionRelayWorkers.js +0 -267
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { ContextGrantsClient, GrantsClient, claimOpenAgreement, delegateAgreement, getMyAgreements, pullOffers, } from '../http/index.js';
|
|
2
|
-
function providerAgentId(agreement) {
|
|
3
|
-
return agreement.parties?.provider?.actor ?? null;
|
|
4
|
-
}
|
|
5
|
-
function isActiveAgreement(agreement) {
|
|
6
|
-
return agreement.status === 'active';
|
|
7
|
-
}
|
|
8
|
-
export function findExistingWorkerDelegation(hireAgreementId, assigneeId, agreements) {
|
|
9
|
-
const matches = agreements.filter((a) => {
|
|
10
|
-
if (a.parentAgreementId !== hireAgreementId)
|
|
11
|
-
return false;
|
|
12
|
-
if (!isActiveAgreement(a))
|
|
13
|
-
return false;
|
|
14
|
-
return providerAgentId(a) === assigneeId;
|
|
15
|
-
});
|
|
16
|
-
matches.sort((a, b) => {
|
|
17
|
-
const ta = a.updatedAt ?? a.createdAt ?? '';
|
|
18
|
-
const tb = b.updatedAt ?? b.createdAt ?? '';
|
|
19
|
-
return tb.localeCompare(ta);
|
|
20
|
-
});
|
|
21
|
-
return matches[0] ?? null;
|
|
22
|
-
}
|
|
23
|
-
export function findOpenOfferForAgent(assigneeId, offers) {
|
|
24
|
-
return (offers.find((o) => {
|
|
25
|
-
if (o.status !== 'open' && o.status !== 'active')
|
|
26
|
-
return false;
|
|
27
|
-
return providerAgentId(o) === assigneeId;
|
|
28
|
-
}) ?? null);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* The default grant plumbing, built on the two clients that own these routes.
|
|
32
|
-
*
|
|
33
|
-
* A thin function seam rather than the clients themselves, so a test can drive
|
|
34
|
-
* provisioning without a backend — the same reason the agreement calls are
|
|
35
|
-
* injected.
|
|
36
|
-
*/
|
|
37
|
-
export function defaultRelayProvisionDeps() {
|
|
38
|
-
return {
|
|
39
|
-
getMyAgreements,
|
|
40
|
-
pullOffers,
|
|
41
|
-
claimOpenAgreement,
|
|
42
|
-
delegateAgreement,
|
|
43
|
-
listAgreementGrants: async ({ agreementId, role }, creds) => {
|
|
44
|
-
const client = new GrantsClient(creds.operatorKey, creds.agentId);
|
|
45
|
-
return client.listAllGrants({
|
|
46
|
-
scopeKind: 'agreement',
|
|
47
|
-
scopeId: agreementId,
|
|
48
|
-
role,
|
|
49
|
-
health: 'active',
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
|
-
delegateAgreementGrant: async ({ parentGrantId, holderId, agreementId }, creds) => {
|
|
53
|
-
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
54
|
-
return client.delegateGrant(parentGrantId, {
|
|
55
|
-
holderId,
|
|
56
|
-
holderKind: 'agent',
|
|
57
|
-
// `write` is exactly what `canActUnderAgreement` tests. Deliberately not
|
|
58
|
-
// `admit`: that would let a coordinator widen its own crew.
|
|
59
|
-
kind: 'write',
|
|
60
|
-
scope: { kind: 'agreement', id: agreementId },
|
|
61
|
-
// from-start, not from-now: the contract predates the grant, so a
|
|
62
|
-
// from-now watermark would exclude the very row the grant is about.
|
|
63
|
-
temporal: 'from-start',
|
|
64
|
-
});
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Hand the coordinator a pass on one worker contract.
|
|
70
|
-
*
|
|
71
|
-
* Membership in work is a grant, which is what lets a coordinator create work
|
|
72
|
-
* under a contract it is not a party to. Issuing that grant is the half that has
|
|
73
|
-
* to happen here: without it the recipe fails at the coordinator's first
|
|
74
|
-
* `task_create` with "Caller <coordinator> holds no grant on agreement <worker>,
|
|
75
|
-
* so it cannot create work citing it."
|
|
76
|
-
*
|
|
77
|
-
* Reused before minted, because provisioning is re-run: a second call after an
|
|
78
|
-
* agreement activates should not stack a second pass on a contract that already
|
|
79
|
-
* has one.
|
|
80
|
-
*/
|
|
81
|
-
async function grantCoordinatorAccess(args, deps) {
|
|
82
|
-
const { agreementId, coordinatorId, status, creds } = args;
|
|
83
|
-
// Participation grants are minted at ACTIVATION, so a contract still waiting
|
|
84
|
-
// on a signature has nothing to delegate from yet. Say so rather than
|
|
85
|
-
// reporting a failure the caller cannot act on.
|
|
86
|
-
if (status !== 'active') {
|
|
87
|
-
return {
|
|
88
|
-
status: 'deferred',
|
|
89
|
-
error: `${agreementId} is not active yet, so there is no participation grant to ` +
|
|
90
|
-
'delegate from — provision again once it activates',
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
const alreadyIssued = await deps.listAgreementGrants({ agreementId, role: 'issuer' }, creds);
|
|
95
|
-
const existing = alreadyIssued.find((g) => g.holderId === coordinatorId);
|
|
96
|
-
if (existing?.grantId)
|
|
97
|
-
return { status: 'reused', grantId: existing.grantId };
|
|
98
|
-
const held = await deps.listAgreementGrants({ agreementId, role: 'holder' }, creds);
|
|
99
|
-
// Only a `write` or `admit` parent can beget the `write` child the act rule
|
|
100
|
-
// tests; a `read` participation grant cannot be widened by delegating it.
|
|
101
|
-
// `access` is optional on GrantView because some rails do not record it —
|
|
102
|
-
// context grants always do, so an absent value here is a malformed row and
|
|
103
|
-
// must not be treated as strong enough.
|
|
104
|
-
const parent = held.find((g) => g.access === 'write' || g.access === 'admit');
|
|
105
|
-
if (!parent?.grantId) {
|
|
106
|
-
return {
|
|
107
|
-
status: 'failed',
|
|
108
|
-
error: `no write-or-stronger grant on ${agreementId} to delegate from (found ` +
|
|
109
|
-
`${held.length ? held.map((g) => g.access ?? 'unstated').join(', ') : 'none'}) — ` +
|
|
110
|
-
'the initiator must be a party to the worker contract it is provisioning',
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
const result = await deps.delegateAgreementGrant({ parentGrantId: parent.grantId, holderId: coordinatorId, agreementId }, creds);
|
|
114
|
-
if (result.status === 'pending_approval') {
|
|
115
|
-
return { status: 'pending_approval', approvalAgreementId: result.agreementId };
|
|
116
|
-
}
|
|
117
|
-
return { status: 'granted', grantId: result.grant.grantId };
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
return {
|
|
121
|
-
status: 'failed',
|
|
122
|
-
error: err instanceof Error ? err.message : String(err),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
export async function provisionRelayWorkers(input, deps = defaultRelayProvisionDeps()) {
|
|
127
|
-
const { creds, hireAgreementId, coordinatorId, chatId, steps, inputArtifactIds } = input;
|
|
128
|
-
if (!steps.length)
|
|
129
|
-
throw new Error('steps must not be empty');
|
|
130
|
-
if (!coordinatorId?.trim()) {
|
|
131
|
-
throw new Error('coordinatorId is required: provisioning a worker for a coordinator means both ' +
|
|
132
|
-
'the contract and an agreement-scope grant to that coordinator on it, and ' +
|
|
133
|
-
'without the grant every branch of the run is refused');
|
|
134
|
-
}
|
|
135
|
-
const myAgreements = (await deps.getMyAgreements({}, creds));
|
|
136
|
-
let offersCache = null;
|
|
137
|
-
// The contracts first, then the passes. Two passes rather than one because a
|
|
138
|
-
// grant can only be delegated from a participation grant, which exists only
|
|
139
|
-
// once the contract is active — so the grant decision needs the settled
|
|
140
|
-
// status of the row, not the intent that created it.
|
|
141
|
-
const contracted = [];
|
|
142
|
-
const pendingApprovals = [];
|
|
143
|
-
for (const step of steps) {
|
|
144
|
-
const existing = findExistingWorkerDelegation(hireAgreementId, step.assigneeId, myAgreements);
|
|
145
|
-
if (existing?.agreementId) {
|
|
146
|
-
contracted.push({
|
|
147
|
-
stepId: step.stepId,
|
|
148
|
-
order: step.order,
|
|
149
|
-
agreementId: existing.agreementId,
|
|
150
|
-
assigneeId: step.assigneeId,
|
|
151
|
-
description: step.description,
|
|
152
|
-
provisionMethod: 'existing',
|
|
153
|
-
status: 'active',
|
|
154
|
-
});
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (step.offerAgreementId) {
|
|
158
|
-
const { agreement: claimed } = await deps.claimOpenAgreement(step.offerAgreementId, creds);
|
|
159
|
-
const agreementId = claimed.agreementId;
|
|
160
|
-
const status = isActiveAgreement(claimed)
|
|
161
|
-
? 'active'
|
|
162
|
-
: 'pending_approval';
|
|
163
|
-
if (status === 'pending_approval')
|
|
164
|
-
pendingApprovals.push(agreementId);
|
|
165
|
-
contracted.push({
|
|
166
|
-
stepId: step.stepId,
|
|
167
|
-
order: step.order,
|
|
168
|
-
agreementId,
|
|
169
|
-
assigneeId: step.assigneeId,
|
|
170
|
-
description: step.description,
|
|
171
|
-
provisionMethod: 'claim',
|
|
172
|
-
status,
|
|
173
|
-
});
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
if (!offersCache) {
|
|
177
|
-
offersCache = await deps.pullOffers({ limit: 100 }, creds);
|
|
178
|
-
}
|
|
179
|
-
const openOffer = findOpenOfferForAgent(step.assigneeId, offersCache);
|
|
180
|
-
if (openOffer?.agreementId) {
|
|
181
|
-
const { agreement: claimed } = await deps.claimOpenAgreement(openOffer.agreementId, creds);
|
|
182
|
-
const agreementId = claimed.agreementId;
|
|
183
|
-
const status = isActiveAgreement(claimed)
|
|
184
|
-
? 'active'
|
|
185
|
-
: 'pending_approval';
|
|
186
|
-
if (status === 'pending_approval')
|
|
187
|
-
pendingApprovals.push(agreementId);
|
|
188
|
-
contracted.push({
|
|
189
|
-
stepId: step.stepId,
|
|
190
|
-
order: step.order,
|
|
191
|
-
agreementId,
|
|
192
|
-
assigneeId: step.assigneeId,
|
|
193
|
-
description: step.description,
|
|
194
|
-
provisionMethod: 'claim',
|
|
195
|
-
status,
|
|
196
|
-
});
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (!chatId?.trim()) {
|
|
200
|
-
throw new Error(`No standing offer found for ${step.assigneeId} and chatId is required to propose a delegation under the hire`);
|
|
201
|
-
}
|
|
202
|
-
const delegated = await deps.delegateAgreement({
|
|
203
|
-
description: step.description,
|
|
204
|
-
executorId: step.assigneeId,
|
|
205
|
-
chatId: chatId.trim(),
|
|
206
|
-
parentAgreementId: hireAgreementId,
|
|
207
|
-
agreementDescription: `Relay step ${step.stepId}: ${step.description}`,
|
|
208
|
-
lifecycle: 'count-bound',
|
|
209
|
-
maxExecutions: 50,
|
|
210
|
-
price: 0,
|
|
211
|
-
}, creds);
|
|
212
|
-
const agreementId = delegated.agreementId;
|
|
213
|
-
const status = isActiveAgreement(delegated)
|
|
214
|
-
? 'active'
|
|
215
|
-
: 'pending_approval';
|
|
216
|
-
if (status === 'pending_approval')
|
|
217
|
-
pendingApprovals.push(agreementId);
|
|
218
|
-
contracted.push({
|
|
219
|
-
stepId: step.stepId,
|
|
220
|
-
order: step.order,
|
|
221
|
-
agreementId,
|
|
222
|
-
assigneeId: step.assigneeId,
|
|
223
|
-
description: step.description,
|
|
224
|
-
provisionMethod: 'delegate',
|
|
225
|
-
status,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
contracted.sort((a, b) => a.order - b.order);
|
|
229
|
-
// ── the second act of provisioning: the coordinator's passes ─────────────
|
|
230
|
-
const provisioned = [];
|
|
231
|
-
for (const step of contracted) {
|
|
232
|
-
const grant = await grantCoordinatorAccess({
|
|
233
|
-
agreementId: step.agreementId,
|
|
234
|
-
coordinatorId,
|
|
235
|
-
status: step.status,
|
|
236
|
-
creds,
|
|
237
|
-
}, deps);
|
|
238
|
-
if (grant.status === 'pending_approval' && grant.approvalAgreementId) {
|
|
239
|
-
pendingApprovals.push(grant.approvalAgreementId);
|
|
240
|
-
}
|
|
241
|
-
provisioned.push({ ...step, grant });
|
|
242
|
-
}
|
|
243
|
-
const payload = {
|
|
244
|
-
inputArtifactIds,
|
|
245
|
-
steps: provisioned.map(({ stepId, order, agreementId, assigneeId, description }) => ({
|
|
246
|
-
stepId,
|
|
247
|
-
order,
|
|
248
|
-
agreementId,
|
|
249
|
-
assigneeId,
|
|
250
|
-
description,
|
|
251
|
-
})),
|
|
252
|
-
};
|
|
253
|
-
/**
|
|
254
|
-
* Ready means BOTH acts are done for every step. A run whose contracts are all
|
|
255
|
-
* active but whose coordinator holds no passes looks provisioned and has every
|
|
256
|
-
* branch refused, so the grant has to count here or this flag goes on lying.
|
|
257
|
-
*/
|
|
258
|
-
const readyForKickoff = pendingApprovals.length === 0 &&
|
|
259
|
-
provisioned.every((s) => s.status === 'active' &&
|
|
260
|
-
(s.grant.status === 'granted' || s.grant.status === 'reused'));
|
|
261
|
-
return {
|
|
262
|
-
payload,
|
|
263
|
-
steps: provisioned,
|
|
264
|
-
pendingApprovals,
|
|
265
|
-
readyForKickoff,
|
|
266
|
-
};
|
|
267
|
-
}
|