@ziggs-ai/api-client 0.1.3 → 0.1.5
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/README.md +13 -7
- package/dist/ConnectionManager.d.ts +46 -0
- package/dist/ConnectionManager.js +132 -0
- package/dist/http/AgentSearchClient.d.ts +36 -0
- package/dist/http/AgentSearchClient.js +72 -0
- package/dist/http/AgreementClient.d.ts +153 -0
- package/dist/http/AgreementClient.js +477 -0
- package/dist/http/ArtifactsClient.d.ts +48 -0
- package/dist/http/ArtifactsClient.js +90 -0
- package/dist/http/ChatClient.d.ts +34 -0
- package/dist/http/ChatClient.js +104 -0
- package/dist/http/ContextDiscoveryClient.d.ts +23 -0
- package/dist/http/ContextDiscoveryClient.js +35 -0
- package/dist/http/ContextReadClient.d.ts +33 -0
- package/dist/http/ContextReadClient.js +54 -0
- package/dist/http/MarketplaceClient.d.ts +19 -0
- package/dist/http/MarketplaceClient.js +72 -0
- package/dist/http/MessagesClient.d.ts +26 -0
- package/dist/http/MessagesClient.js +49 -0
- package/dist/http/ScopeClient.d.ts +33 -0
- package/dist/http/ScopeClient.js +39 -0
- package/dist/http/TaskClient.d.ts +75 -0
- package/dist/http/TaskClient.js +352 -0
- package/dist/http/TelemetryClient.d.ts +11 -0
- package/dist/http/TelemetryClient.js +53 -0
- package/dist/http/index.d.ts +16 -0
- package/dist/http/index.js +11 -0
- package/dist/index.d.ts +9 -0
- package/{src → dist}/index.js +2 -12
- package/dist/shared/runtimeLog.d.ts +14 -0
- package/dist/shared/runtimeLog.js +64 -0
- package/dist/types.d.ts +130 -0
- package/dist/types.js +50 -0
- package/dist/utils/urlUtils.d.ts +2 -0
- package/dist/utils/urlUtils.js +8 -0
- package/dist/websocket/ControlSocket.d.ts +13 -0
- package/dist/websocket/ControlSocket.js +37 -0
- package/dist/websocket/WebSocketClient.d.ts +71 -0
- package/dist/websocket/WebSocketClient.js +233 -0
- package/dist/websocket/index.js +1 -0
- package/package.json +20 -7
- package/src/ConnectionManager.ts +172 -0
- package/src/http/AgentSearchClient.ts +115 -0
- package/src/http/AgreementClient.ts +721 -0
- package/src/http/ArtifactsClient.ts +133 -0
- package/src/http/ChatClient.ts +147 -0
- package/src/http/ContextDiscoveryClient.ts +52 -0
- package/src/http/ContextReadClient.ts +83 -0
- package/src/http/MarketplaceClient.ts +94 -0
- package/src/http/MessagesClient.ts +71 -0
- package/src/http/ScopeClient.ts +64 -0
- package/src/http/TaskClient.ts +450 -0
- package/src/http/{TelemetryClient.js → TelemetryClient.ts} +21 -7
- package/src/http/index.ts +26 -0
- package/src/index.ts +27 -0
- package/src/shared/runtimeLog.ts +68 -0
- package/src/types.ts +158 -0
- package/src/utils/urlUtils.ts +9 -0
- package/src/websocket/ControlSocket.ts +51 -0
- package/src/websocket/WebSocketClient.ts +315 -0
- package/src/websocket/index.ts +1 -0
- package/src/ConnectionManager.js +0 -179
- package/src/http/AgentSearchClient.js +0 -113
- package/src/http/ContextReader.js +0 -99
- package/src/http/ContextWriter.js +0 -98
- package/src/http/TaskClient.js +0 -612
- package/src/http/index.js +0 -6
- package/src/types.js +0 -28
- package/src/utils/urlUtils.js +0 -17
- package/src/websocket/ControlSocket.js +0 -55
- package/src/websocket/WebSocketClient.js +0 -318
- /package/{src/websocket/index.js → dist/websocket/index.d.ts} +0 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
3
|
+
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
4
|
+
import { OPEN_AGREEMENT_TARGET, ApiError } from '../types.js';
|
|
5
|
+
import { publishToLedger } from './TaskClient.js';
|
|
6
|
+
// Lazy: read at call time so dotenv loaded after this module is imported
|
|
7
|
+
// still takes effect. Baking it at module-load time would freeze the URL
|
|
8
|
+
// before the caller's dotenv.config() runs.
|
|
9
|
+
function getAgreementBaseUrl() {
|
|
10
|
+
return `${getBackendUrl()}/agreements`;
|
|
11
|
+
}
|
|
12
|
+
function buildHeaders(creds) {
|
|
13
|
+
return {
|
|
14
|
+
'content-type': 'application/json',
|
|
15
|
+
Authorization: `Bearer ${creds.operatorKey}`,
|
|
16
|
+
'X-Agent-Id': creds.agentId,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function assertCreds(creds, op) {
|
|
20
|
+
if (!creds?.operatorKey)
|
|
21
|
+
throw new Error(`operatorKey is required for ${op}`);
|
|
22
|
+
if (!creds?.agentId)
|
|
23
|
+
throw new Error(`agentId is required for ${op}`);
|
|
24
|
+
}
|
|
25
|
+
function parseErrorMessage(responseBody, defaultMessage) {
|
|
26
|
+
if (!responseBody)
|
|
27
|
+
return defaultMessage;
|
|
28
|
+
try {
|
|
29
|
+
const d = JSON.parse(responseBody);
|
|
30
|
+
return d['details'] || d['error'] || d['message'] || defaultMessage;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return responseBody || defaultMessage;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function throwApiError(response, responseBody, defaultMessage) {
|
|
37
|
+
throw new ApiError(parseErrorMessage(responseBody, defaultMessage), response.status, responseBody);
|
|
38
|
+
}
|
|
39
|
+
export async function proposeAgreement(proposalData, creds) {
|
|
40
|
+
if (!proposalData)
|
|
41
|
+
throw new Error('Proposal data is required for proposal creation');
|
|
42
|
+
assertCreds(creds, 'proposal creation');
|
|
43
|
+
const { idempotencyKey, ...bodyData } = proposalData;
|
|
44
|
+
const headers = buildHeaders(creds);
|
|
45
|
+
if (idempotencyKey)
|
|
46
|
+
headers['Idempotency-Key'] = idempotencyKey;
|
|
47
|
+
// ZIG-207: canonical REST path. Backend keeps /propose serving the
|
|
48
|
+
// identical handler with a Deprecation/Sunset header until PR-F removes it.
|
|
49
|
+
const res = await fetch(`${getAgreementBaseUrl()}/proposals`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers,
|
|
52
|
+
body: JSON.stringify(bodyData),
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
const body = await res.text().catch(() => '');
|
|
56
|
+
throwApiError(res, body, `Proposal creation failed: ${res.status} ${res.statusText}`);
|
|
57
|
+
}
|
|
58
|
+
const data = await res.json().catch(() => null);
|
|
59
|
+
if (!data?.['agreement']) {
|
|
60
|
+
throw new Error('Invalid response: expected { agreement } from POST /agreements/proposals');
|
|
61
|
+
}
|
|
62
|
+
return data['agreement'];
|
|
63
|
+
}
|
|
64
|
+
/** Propose a contract to one party (user or agent). Server defaults `engagementKind` to `service`. */
|
|
65
|
+
export async function proposeDirectTo(input, creds) {
|
|
66
|
+
return proposeAgreement(input, creds);
|
|
67
|
+
}
|
|
68
|
+
/** Propose an open quest (`proposedTo: "everyone"`). Server defaults `engagementKind` to `service`. */
|
|
69
|
+
export async function proposeBroadcast(input, creds) {
|
|
70
|
+
return proposeAgreement({ ...input, proposedTo: OPEN_AGREEMENT_TARGET }, creds);
|
|
71
|
+
}
|
|
72
|
+
export async function delegateAgreement(proposalData, creds) {
|
|
73
|
+
if (!proposalData)
|
|
74
|
+
throw new Error('Proposal data is required for delegation');
|
|
75
|
+
if (!proposalData.parentAgreementId)
|
|
76
|
+
throw new Error('parentAgreementId is required for delegation');
|
|
77
|
+
assertCreds(creds, 'proposal creation');
|
|
78
|
+
const { idempotencyKey, parentAgreementId } = proposalData;
|
|
79
|
+
const headers = buildHeaders(creds);
|
|
80
|
+
if (idempotencyKey)
|
|
81
|
+
headers['Idempotency-Key'] = idempotencyKey;
|
|
82
|
+
// ZIG-207 / ZIG-270: parentAgreementId in path AND body. Backend's
|
|
83
|
+
// `DelegateTaskDto` validates `parentAgreementId` as a required string;
|
|
84
|
+
// controller (`POST /agreements/:parentAgreementId/delegations`) merges
|
|
85
|
+
// path over body, so duplicating the value is harmless. Stripping it
|
|
86
|
+
// from the body (as ZIG-207 #72 did) trips DTO validation and returns
|
|
87
|
+
// `400: parentAgreementId must be a string` → infinite retry loop in
|
|
88
|
+
// `agreement_subcontract`. Send both, let the path win.
|
|
89
|
+
const bodyWithParent = { ...proposalData };
|
|
90
|
+
delete bodyWithParent.idempotencyKey;
|
|
91
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(parentAgreementId)}/delegations`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers,
|
|
94
|
+
body: JSON.stringify(bodyWithParent),
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
const body = await res.text().catch(() => '');
|
|
98
|
+
throwApiError(res, body, `Delegation failed: ${res.status} ${res.statusText}`);
|
|
99
|
+
}
|
|
100
|
+
const data = await res.json().catch(() => null);
|
|
101
|
+
if (!data?.['agreement']) {
|
|
102
|
+
throw new Error('Invalid response: expected { agreement } from POST /agreements/:parentAgreementId/delegations');
|
|
103
|
+
}
|
|
104
|
+
return data['agreement'];
|
|
105
|
+
}
|
|
106
|
+
export async function respondToAgreement(agreementId, action, creds) {
|
|
107
|
+
if (!agreementId || !action)
|
|
108
|
+
throw new Error('Agreement ID and action are required for proposal response');
|
|
109
|
+
assertCreds(creds, 'proposal response');
|
|
110
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/respond`, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: buildHeaders(creds),
|
|
113
|
+
body: JSON.stringify({ action }),
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
const body = await res.text().catch(() => '');
|
|
117
|
+
throwApiError(res, body, `Proposal response failed: ${res.status} ${res.statusText}`);
|
|
118
|
+
}
|
|
119
|
+
const data = await res.json().catch(() => null);
|
|
120
|
+
if (!data?.['agreement']) {
|
|
121
|
+
throw new Error('Invalid response: expected { agreement } from /agreements/:id/respond');
|
|
122
|
+
}
|
|
123
|
+
return data['agreement'];
|
|
124
|
+
}
|
|
125
|
+
export async function counterAgreement(agreementId, counter, creds) {
|
|
126
|
+
if (!agreementId)
|
|
127
|
+
throw new Error('agreementId is required for counter-offer');
|
|
128
|
+
assertCreds(creds, 'counter proposal');
|
|
129
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/counter`, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: buildHeaders(creds),
|
|
132
|
+
body: JSON.stringify(counter || {}),
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
const body = await res.text().catch(() => '');
|
|
136
|
+
throwApiError(res, body, `Counter proposal failed: ${res.status} ${res.statusText}`);
|
|
137
|
+
}
|
|
138
|
+
const data = await res.json().catch(() => null);
|
|
139
|
+
if (!data?.['agreement']) {
|
|
140
|
+
throw new Error('Invalid response: expected { agreement } from /agreements/:id/counter');
|
|
141
|
+
}
|
|
142
|
+
return data['agreement'];
|
|
143
|
+
}
|
|
144
|
+
export async function getAgreementStatus(agreementId, creds) {
|
|
145
|
+
if (!agreementId)
|
|
146
|
+
return null;
|
|
147
|
+
assertCreds(creds, 'proposal status read');
|
|
148
|
+
try {
|
|
149
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/status`, {
|
|
150
|
+
method: 'GET',
|
|
151
|
+
headers: buildHeaders(creds),
|
|
152
|
+
});
|
|
153
|
+
if (res.status === 404)
|
|
154
|
+
return null;
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
const body = await res.text().catch(() => '');
|
|
157
|
+
runtimeLog.warn('AgreementClient', `⚠️ Proposal status read failed: ${res.status} ${res.statusText} ${body}`);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return await res.json().catch(() => null);
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
runtimeLog.warn('AgreementClient', `⚠️ Proposal status read failed: ${e.message}`);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export async function listAgreements(filters = {}, creds) {
|
|
168
|
+
assertCreds(creds, 'list agreements');
|
|
169
|
+
try {
|
|
170
|
+
// Canonical query path: GET /agreements?scope=&status=&engagementKind=&...
|
|
171
|
+
const url = new URL(getAgreementBaseUrl());
|
|
172
|
+
if (filters.status)
|
|
173
|
+
url.searchParams.set('status', filters.status);
|
|
174
|
+
if (filters.engagementKind)
|
|
175
|
+
url.searchParams.set('engagementKind', filters.engagementKind);
|
|
176
|
+
if (filters.proposalStatus)
|
|
177
|
+
url.searchParams.set('proposalStatus', filters.proposalStatus);
|
|
178
|
+
if (filters.hasTask != null)
|
|
179
|
+
url.searchParams.set('hasTask', String(filters.hasTask));
|
|
180
|
+
const res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
|
|
181
|
+
if (!res.ok) {
|
|
182
|
+
const body = await res.text().catch(() => '');
|
|
183
|
+
runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
const data = await res.json().catch(() => null);
|
|
187
|
+
return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${e.message}`);
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export async function getMyAgreements(filters = {}, creds) {
|
|
195
|
+
assertCreds(creds, 'get my agreements');
|
|
196
|
+
try {
|
|
197
|
+
// Canonical query path: GET /agreements?scope=mine returns the enriched
|
|
198
|
+
// shape (tasks + originChatId + isYou flags).
|
|
199
|
+
const url = new URL(getAgreementBaseUrl());
|
|
200
|
+
url.searchParams.set('scope', 'mine');
|
|
201
|
+
if (filters.engagementKind)
|
|
202
|
+
url.searchParams.set('engagementKind', filters.engagementKind);
|
|
203
|
+
if (filters.proposalStatus)
|
|
204
|
+
url.searchParams.set('proposalStatus', filters.proposalStatus);
|
|
205
|
+
if (filters.hasTask != null)
|
|
206
|
+
url.searchParams.set('hasTask', String(filters.hasTask));
|
|
207
|
+
const res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
const body = await res.text().catch(() => '');
|
|
210
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const data = await res.json().catch(() => null);
|
|
214
|
+
return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${e.message}`);
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export async function getAgreement(agreementId, creds) {
|
|
222
|
+
if (!agreementId)
|
|
223
|
+
return null;
|
|
224
|
+
assertCreds(creds, 'get agreement');
|
|
225
|
+
try {
|
|
226
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}`, {
|
|
227
|
+
method: 'GET',
|
|
228
|
+
headers: buildHeaders(creds),
|
|
229
|
+
});
|
|
230
|
+
if (res.status === 404)
|
|
231
|
+
return null;
|
|
232
|
+
if (!res.ok) {
|
|
233
|
+
const body = await res.text().catch(() => '');
|
|
234
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const data = await res.json().catch(() => null);
|
|
238
|
+
return data?.['agreement'] ?? null;
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${e.message}`);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export async function createAgreement(body, creds) {
|
|
246
|
+
if (!body)
|
|
247
|
+
throw new Error('Body is required for agreement creation');
|
|
248
|
+
assertCreds(creds, 'agreement creation');
|
|
249
|
+
// Canonical REST create: POST /agreements.
|
|
250
|
+
const res = await fetch(getAgreementBaseUrl(), {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: buildHeaders(creds),
|
|
253
|
+
body: JSON.stringify(body),
|
|
254
|
+
});
|
|
255
|
+
if (!res.ok) {
|
|
256
|
+
const responseBody = await res.text().catch(() => '');
|
|
257
|
+
throwApiError(res, responseBody, `Agreement creation failed: ${res.status} ${res.statusText}`);
|
|
258
|
+
}
|
|
259
|
+
const data = await res.json().catch(() => null);
|
|
260
|
+
if (!data?.['agreement']) {
|
|
261
|
+
throw new Error('Invalid response: expected { ok, agreement } from POST /agreements');
|
|
262
|
+
}
|
|
263
|
+
return data;
|
|
264
|
+
}
|
|
265
|
+
export async function revokeAgreement(agreementId, creds) {
|
|
266
|
+
if (!agreementId)
|
|
267
|
+
throw new Error('agreementId is required for revocation');
|
|
268
|
+
assertCreds(creds, 'agreement revocation');
|
|
269
|
+
// ZIG-207: canonical REST verb. Legacy POST /agreements/:id/revoke stays
|
|
270
|
+
// serving the identical handler (with Deprecation headers) until PR-F.
|
|
271
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}`, {
|
|
272
|
+
method: 'DELETE',
|
|
273
|
+
headers: buildHeaders(creds),
|
|
274
|
+
});
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
const body = await res.text().catch(() => '');
|
|
277
|
+
throwApiError(res, body, `Agreement revocation failed: ${res.status} ${res.statusText}`);
|
|
278
|
+
}
|
|
279
|
+
const data = await res.json().catch(() => null);
|
|
280
|
+
if (!data?.['agreement']) {
|
|
281
|
+
throw new Error('Invalid response: expected { ok, agreement } from DELETE /agreements/:id');
|
|
282
|
+
}
|
|
283
|
+
return data;
|
|
284
|
+
}
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Chat links
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
export async function getAgreementsByChat(chatId, creds) {
|
|
289
|
+
if (!chatId)
|
|
290
|
+
return [];
|
|
291
|
+
assertCreds(creds, 'get agreements by chat');
|
|
292
|
+
try {
|
|
293
|
+
const res = await fetch(`${getAgreementBaseUrl()}/by-chat/${encodeURIComponent(chatId)}`, {
|
|
294
|
+
method: 'GET',
|
|
295
|
+
headers: buildHeaders(creds),
|
|
296
|
+
});
|
|
297
|
+
if (!res.ok) {
|
|
298
|
+
const body = await res.text().catch(() => '');
|
|
299
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get agreements by chat failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
const data = await res.json().catch(() => null);
|
|
303
|
+
const links = data?.['links'] ?? data?.['agreements'];
|
|
304
|
+
return Array.isArray(links) ? links : [];
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get agreements by chat failed: ${e.message}`);
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
export async function linkAgreementToChat(agreementId, chatId, linkType = 'mention', creds) {
|
|
312
|
+
if (!agreementId || !chatId)
|
|
313
|
+
return null;
|
|
314
|
+
assertCreds(creds, 'link agreement to chat');
|
|
315
|
+
try {
|
|
316
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/link`, {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: buildHeaders(creds),
|
|
319
|
+
body: JSON.stringify({ chatId, linkType }),
|
|
320
|
+
});
|
|
321
|
+
if (!res.ok) {
|
|
322
|
+
const body = await res.text().catch(() => '');
|
|
323
|
+
runtimeLog.warn('AgreementClient', `⚠️ Link agreement to chat failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return await res.json().catch(() => null);
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
runtimeLog.warn('AgreementClient', `⚠️ Link agreement to chat failed: ${e.message}`);
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
export async function getChatsForAgreement(agreementId, creds) {
|
|
334
|
+
if (!agreementId)
|
|
335
|
+
return [];
|
|
336
|
+
assertCreds(creds, 'get chats for agreement');
|
|
337
|
+
try {
|
|
338
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/chats`, {
|
|
339
|
+
method: 'GET',
|
|
340
|
+
headers: buildHeaders(creds),
|
|
341
|
+
});
|
|
342
|
+
if (!res.ok) {
|
|
343
|
+
const body = await res.text().catch(() => '');
|
|
344
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get chats for agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
const data = await res.json().catch(() => null);
|
|
348
|
+
return Array.isArray(data?.['links']) ? data['links'] : [];
|
|
349
|
+
}
|
|
350
|
+
catch (e) {
|
|
351
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get chats for agreement failed: ${e.message}`);
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
export async function joinAgreement(agreementId, creds) {
|
|
356
|
+
if (!agreementId)
|
|
357
|
+
throw new Error('agreementId is required for join');
|
|
358
|
+
assertCreds(creds, 'join agreement');
|
|
359
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/join`, {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: buildHeaders(creds),
|
|
362
|
+
});
|
|
363
|
+
if (!res.ok) {
|
|
364
|
+
const body = await res.text().catch(() => '');
|
|
365
|
+
throwApiError(res, body, `Join agreement failed: ${res.status} ${res.statusText}`);
|
|
366
|
+
}
|
|
367
|
+
const data = await res.json().catch(() => null);
|
|
368
|
+
if (!data?.['chatId']) {
|
|
369
|
+
throw new Error('Invalid response: expected { chatId } from /agreements/:id/join');
|
|
370
|
+
}
|
|
371
|
+
return data;
|
|
372
|
+
}
|
|
373
|
+
export async function linkArtifactToAgreement(agreementId, artifactId, linkType = 'produced', creds) {
|
|
374
|
+
if (!agreementId || !artifactId)
|
|
375
|
+
return null;
|
|
376
|
+
assertCreds(creds, 'link artifact to agreement');
|
|
377
|
+
try {
|
|
378
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/artifacts`, {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
headers: buildHeaders(creds),
|
|
381
|
+
body: JSON.stringify({ artifactId, linkType }),
|
|
382
|
+
});
|
|
383
|
+
if (!res.ok) {
|
|
384
|
+
const body = await res.text().catch(() => '');
|
|
385
|
+
runtimeLog.warn('AgreementClient', `⚠️ Link artifact failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
return await res.json().catch(() => null);
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
runtimeLog.warn('AgreementClient', `⚠️ Link artifact failed: ${e.message}`);
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
export async function getArtifactsForAgreement(agreementId, creds) {
|
|
396
|
+
if (!agreementId)
|
|
397
|
+
return [];
|
|
398
|
+
assertCreds(creds, 'get artifacts for agreement');
|
|
399
|
+
try {
|
|
400
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/artifacts`, {
|
|
401
|
+
method: 'GET',
|
|
402
|
+
headers: buildHeaders(creds),
|
|
403
|
+
});
|
|
404
|
+
if (!res.ok) {
|
|
405
|
+
const body = await res.text().catch(() => '');
|
|
406
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get artifacts failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
const data = await res.json().catch(() => null);
|
|
410
|
+
return Array.isArray(data?.['artifacts']) ? data['artifacts'] : [];
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get artifacts failed: ${e.message}`);
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
export async function linkUserToAgreement(agreementId, userId, role, creds) {
|
|
418
|
+
if (!agreementId || !userId || !role)
|
|
419
|
+
return null;
|
|
420
|
+
assertCreds(creds, 'link user to agreement');
|
|
421
|
+
try {
|
|
422
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: buildHeaders(creds),
|
|
425
|
+
body: JSON.stringify({ userId, role }),
|
|
426
|
+
});
|
|
427
|
+
if (!res.ok) {
|
|
428
|
+
const body = await res.text().catch(() => '');
|
|
429
|
+
runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
return await res.json().catch(() => null);
|
|
433
|
+
}
|
|
434
|
+
catch (e) {
|
|
435
|
+
runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${e.message}`);
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
export async function getUsersForAgreement(agreementId, creds) {
|
|
440
|
+
if (!agreementId)
|
|
441
|
+
return [];
|
|
442
|
+
assertCreds(creds, 'get users for agreement');
|
|
443
|
+
try {
|
|
444
|
+
const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
|
|
445
|
+
method: 'GET',
|
|
446
|
+
headers: buildHeaders(creds),
|
|
447
|
+
});
|
|
448
|
+
if (!res.ok) {
|
|
449
|
+
const body = await res.text().catch(() => '');
|
|
450
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
|
|
451
|
+
return [];
|
|
452
|
+
}
|
|
453
|
+
const data = await res.json().catch(() => null);
|
|
454
|
+
return Array.isArray(data?.['users']) ? data['users'] : [];
|
|
455
|
+
}
|
|
456
|
+
catch (e) {
|
|
457
|
+
runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${e.message}`);
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* @deprecated Prefer {@link proposeDirectTo}, {@link proposeBroadcast},
|
|
463
|
+
* {@link delegateAgreement}, or {@link publishOpenQuest}.
|
|
464
|
+
*/
|
|
465
|
+
export async function createContract(input, creds) {
|
|
466
|
+
assertCreds(creds, 'contract creation');
|
|
467
|
+
const { kind, ...rest } = input;
|
|
468
|
+
if (kind === 'direct')
|
|
469
|
+
return proposeDirectTo(rest, creds);
|
|
470
|
+
if (kind === 'delegate')
|
|
471
|
+
return delegateAgreement(rest, creds);
|
|
472
|
+
return publishOpenQuest(rest, creds);
|
|
473
|
+
}
|
|
474
|
+
/** Publish a buyer-broadcast quest to the marketplace ledger (Store "Wanted"). */
|
|
475
|
+
export async function publishOpenQuest(payload, creds) {
|
|
476
|
+
return publishToLedger(payload, creds);
|
|
477
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
export type ArtifactVisibility = 'chat' | 'agent-private';
|
|
3
|
+
export interface ListArtifactsOptions {
|
|
4
|
+
after?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ListArtifactsQuery {
|
|
8
|
+
chatId?: string;
|
|
9
|
+
agreementId?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ListArtifactsResult {
|
|
12
|
+
artifacts: unknown[];
|
|
13
|
+
latestSequence: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface WriteArtifactInput {
|
|
16
|
+
text: string;
|
|
17
|
+
content_type?: string;
|
|
18
|
+
visibility?: ArtifactVisibility;
|
|
19
|
+
/** When the artifact is associated with a chat. */
|
|
20
|
+
chatId?: string;
|
|
21
|
+
/** When the artifact is associated with an agreement (and optionally a task). */
|
|
22
|
+
agreementId?: string;
|
|
23
|
+
taskId?: string;
|
|
24
|
+
service?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Replaces `ContextReader`'s artifact reads + `ContextWriter`'s breadcrumb
|
|
28
|
+
* writes. `visibility: 'agent-private'` is the new home for agent thoughts —
|
|
29
|
+
* they persist, are searchable, but are not visible to other chat parties.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ArtifactsClient {
|
|
32
|
+
private readonly operatorKey;
|
|
33
|
+
private readonly agentId;
|
|
34
|
+
constructor(operatorKey: string, agentId: string);
|
|
35
|
+
list(q: ListArtifactsQuery, opts?: ListArtifactsOptions): Promise<ListArtifactsResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Record an agent thought as an `agent-private` artifact. Replaces the
|
|
38
|
+
* `ContextWriter.recordEvent({ kind: 'thought' })` hack.
|
|
39
|
+
*
|
|
40
|
+
* Note: the artifact write endpoint lives in the existing chat/agreement
|
|
41
|
+
* write paths; this helper is the SDK contract. Until the backend write
|
|
42
|
+
* endpoint is updated to accept `visibility`, this falls back to logging
|
|
43
|
+
* locally (operators can still wire their own sink).
|
|
44
|
+
*/
|
|
45
|
+
recordThought(chatId: string, text: string): Promise<void>;
|
|
46
|
+
write(input: WriteArtifactInput): Promise<void>;
|
|
47
|
+
private _headers;
|
|
48
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
3
|
+
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Replaces `ContextReader`'s artifact reads + `ContextWriter`'s breadcrumb
|
|
6
|
+
* writes. `visibility: 'agent-private'` is the new home for agent thoughts —
|
|
7
|
+
* they persist, are searchable, but are not visible to other chat parties.
|
|
8
|
+
*/
|
|
9
|
+
export class ArtifactsClient {
|
|
10
|
+
operatorKey;
|
|
11
|
+
agentId;
|
|
12
|
+
constructor(operatorKey, agentId) {
|
|
13
|
+
if (!operatorKey)
|
|
14
|
+
throw new Error('ArtifactsClient: operatorKey is required');
|
|
15
|
+
if (!agentId)
|
|
16
|
+
throw new Error('ArtifactsClient: agentId is required (operator-token impersonation)');
|
|
17
|
+
this.operatorKey = operatorKey;
|
|
18
|
+
this.agentId = agentId;
|
|
19
|
+
}
|
|
20
|
+
async list(q, opts = {}) {
|
|
21
|
+
if ((q.chatId && q.agreementId) || (!q.chatId && !q.agreementId)) {
|
|
22
|
+
throw new Error('ArtifactsClient.list: pass exactly one of chatId or agreementId');
|
|
23
|
+
}
|
|
24
|
+
const url = new URL(`${getBackendUrl()}/artifacts`);
|
|
25
|
+
if (q.chatId)
|
|
26
|
+
url.searchParams.set('chatId', q.chatId);
|
|
27
|
+
if (q.agreementId)
|
|
28
|
+
url.searchParams.set('agreementId', q.agreementId);
|
|
29
|
+
if (opts.after)
|
|
30
|
+
url.searchParams.set('after', opts.after);
|
|
31
|
+
if (opts.limit != null)
|
|
32
|
+
url.searchParams.set('limit', String(opts.limit));
|
|
33
|
+
const res = await fetch(url.toString(), { headers: this._headers() });
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const body = await res.text().catch(() => '');
|
|
36
|
+
throw new Error(`ArtifactsClient.list ${res.status} ${res.statusText} ${body.slice(0, 200)}`);
|
|
37
|
+
}
|
|
38
|
+
return (await res.json());
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Record an agent thought as an `agent-private` artifact. Replaces the
|
|
42
|
+
* `ContextWriter.recordEvent({ kind: 'thought' })` hack.
|
|
43
|
+
*
|
|
44
|
+
* Note: the artifact write endpoint lives in the existing chat/agreement
|
|
45
|
+
* write paths; this helper is the SDK contract. Until the backend write
|
|
46
|
+
* endpoint is updated to accept `visibility`, this falls back to logging
|
|
47
|
+
* locally (operators can still wire their own sink).
|
|
48
|
+
*/
|
|
49
|
+
async recordThought(chatId, text) {
|
|
50
|
+
return this.write({
|
|
51
|
+
chatId,
|
|
52
|
+
text,
|
|
53
|
+
content_type: 'thought',
|
|
54
|
+
visibility: 'agent-private',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async write(input) {
|
|
58
|
+
if (!input.text || !input.text.trim())
|
|
59
|
+
return;
|
|
60
|
+
if ((input.chatId && input.agreementId) || (!input.chatId && !input.agreementId)) {
|
|
61
|
+
throw new Error('ArtifactsClient.write: pass exactly one of chatId or agreementId');
|
|
62
|
+
}
|
|
63
|
+
const url = `${getBackendUrl()}/artifacts`;
|
|
64
|
+
const res = await fetch(url, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: this._headers(),
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
text: input.text.trim(),
|
|
69
|
+
content_type: input.content_type ?? 'text',
|
|
70
|
+
visibility: input.visibility ?? 'chat',
|
|
71
|
+
chatId: input.chatId,
|
|
72
|
+
agreementId: input.agreementId,
|
|
73
|
+
taskId: input.taskId,
|
|
74
|
+
service: input.service,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
const body = await res.text().catch(() => '');
|
|
79
|
+
// Soft-fail: artifact writes are breadcrumbs, not load-bearing.
|
|
80
|
+
runtimeLog.warn('ArtifactsClient', `⚠️ write failed ${res.status} ${res.statusText} ${body.slice(0, 200)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
_headers() {
|
|
84
|
+
return {
|
|
85
|
+
'content-type': 'application/json',
|
|
86
|
+
Authorization: `Bearer ${this.operatorKey}`,
|
|
87
|
+
'X-Agent-Id': this.agentId,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { type Creds } from '../types.js';
|
|
3
|
+
export interface ChatSummary {
|
|
4
|
+
chatId: string;
|
|
5
|
+
userIds?: string[];
|
|
6
|
+
agentIds?: string[];
|
|
7
|
+
createdAt?: string;
|
|
8
|
+
updatedAt?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare function openConversation(participantId: string, creds: Creds): Promise<{
|
|
12
|
+
chatId: string;
|
|
13
|
+
}>;
|
|
14
|
+
export interface SendChatMessageInput {
|
|
15
|
+
chatId: string;
|
|
16
|
+
receiverId: string;
|
|
17
|
+
text: string;
|
|
18
|
+
messageId: string;
|
|
19
|
+
entryType?: string;
|
|
20
|
+
contentType?: string;
|
|
21
|
+
underAgreementId?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SendChatMessageResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
message: string;
|
|
26
|
+
messageId: string;
|
|
27
|
+
chatId: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* POST /chats/:chatId/messages as an impersonated delegate agent.
|
|
31
|
+
* Requires operator token + X-Agent-Id (ZIG-222).
|
|
32
|
+
*/
|
|
33
|
+
export declare function sendChatMessage(input: SendChatMessageInput, creds: Creds): Promise<SendChatMessageResult>;
|
|
34
|
+
export declare function listMyChats(creds: Creds): Promise<ChatSummary[]>;
|