@ziggs-ai/api-client 0.1.3 → 0.1.4

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.
Files changed (49) hide show
  1. package/README.md +13 -7
  2. package/dist/ConnectionManager.d.ts +45 -0
  3. package/dist/ConnectionManager.js +118 -0
  4. package/dist/http/AgentSearchClient.d.ts +36 -0
  5. package/dist/http/AgentSearchClient.js +72 -0
  6. package/dist/http/AgreementClient.d.ts +153 -0
  7. package/dist/http/AgreementClient.js +457 -0
  8. package/dist/http/ArtifactsClient.d.ts +48 -0
  9. package/dist/http/ArtifactsClient.js +90 -0
  10. package/dist/http/ChatClient.d.ts +14 -0
  11. package/dist/http/ChatClient.js +69 -0
  12. package/dist/http/MarketplaceClient.d.ts +19 -0
  13. package/dist/http/MarketplaceClient.js +72 -0
  14. package/dist/http/MessagesClient.d.ts +22 -0
  15. package/dist/http/MessagesClient.js +41 -0
  16. package/dist/http/ScopeClient.d.ts +33 -0
  17. package/dist/http/ScopeClient.js +39 -0
  18. package/dist/http/TaskClient.d.ts +75 -0
  19. package/dist/http/TaskClient.js +343 -0
  20. package/dist/http/TelemetryClient.d.ts +11 -0
  21. package/dist/http/TelemetryClient.js +53 -0
  22. package/dist/http/index.d.ts +12 -0
  23. package/dist/http/index.js +9 -0
  24. package/dist/index.d.ts +9 -0
  25. package/{src → dist}/index.js +2 -12
  26. package/dist/shared/runtimeLog.d.ts +14 -0
  27. package/dist/shared/runtimeLog.js +64 -0
  28. package/dist/types.d.ts +120 -0
  29. package/dist/types.js +50 -0
  30. package/dist/utils/urlUtils.d.ts +2 -0
  31. package/dist/utils/urlUtils.js +8 -0
  32. package/dist/websocket/ControlSocket.d.ts +13 -0
  33. package/dist/websocket/ControlSocket.js +36 -0
  34. package/dist/websocket/WebSocketClient.d.ts +71 -0
  35. package/dist/websocket/WebSocketClient.js +217 -0
  36. package/dist/websocket/index.js +1 -0
  37. package/package.json +13 -6
  38. package/src/ConnectionManager.js +0 -179
  39. package/src/http/AgentSearchClient.js +0 -113
  40. package/src/http/ContextReader.js +0 -99
  41. package/src/http/ContextWriter.js +0 -98
  42. package/src/http/TaskClient.js +0 -612
  43. package/src/http/TelemetryClient.js +0 -43
  44. package/src/http/index.js +0 -6
  45. package/src/types.js +0 -28
  46. package/src/utils/urlUtils.js +0 -17
  47. package/src/websocket/ControlSocket.js +0 -55
  48. package/src/websocket/WebSocketClient.js +0 -318
  49. /package/{src/websocket/index.js → dist/websocket/index.d.ts} +0 -0
@@ -0,0 +1,457 @@
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
+ const res = await fetch(`${getAgreementBaseUrl()}/propose`, {
48
+ method: 'POST',
49
+ headers,
50
+ body: JSON.stringify(bodyData),
51
+ });
52
+ if (!res.ok) {
53
+ const body = await res.text().catch(() => '');
54
+ throwApiError(res, body, `Proposal creation failed: ${res.status} ${res.statusText}`);
55
+ }
56
+ const data = await res.json().catch(() => null);
57
+ if (!data?.['agreement']) {
58
+ throw new Error('Invalid response: expected { agreement } from /agreements/propose');
59
+ }
60
+ return data['agreement'];
61
+ }
62
+ /** Propose a contract to one party (user or agent). Server defaults `engagementKind` to `service`. */
63
+ export async function proposeDirectTo(input, creds) {
64
+ return proposeAgreement(input, creds);
65
+ }
66
+ /** Propose an open quest (`proposedTo: "everyone"`). Server defaults `engagementKind` to `service`. */
67
+ export async function proposeBroadcast(input, creds) {
68
+ return proposeAgreement({ ...input, proposedTo: OPEN_AGREEMENT_TARGET }, creds);
69
+ }
70
+ export async function delegateAgreement(proposalData, creds) {
71
+ if (!proposalData)
72
+ throw new Error('Proposal data is required for delegation');
73
+ assertCreds(creds, 'proposal creation');
74
+ const { idempotencyKey, ...bodyData } = proposalData;
75
+ const headers = buildHeaders(creds);
76
+ if (idempotencyKey)
77
+ headers['Idempotency-Key'] = idempotencyKey;
78
+ const res = await fetch(`${getAgreementBaseUrl()}/delegate`, {
79
+ method: 'POST',
80
+ headers,
81
+ body: JSON.stringify(bodyData),
82
+ });
83
+ if (!res.ok) {
84
+ const body = await res.text().catch(() => '');
85
+ throwApiError(res, body, `Delegation failed: ${res.status} ${res.statusText}`);
86
+ }
87
+ const data = await res.json().catch(() => null);
88
+ if (!data?.['agreement']) {
89
+ throw new Error('Invalid response: expected { agreement } from /agreements/delegate');
90
+ }
91
+ return data['agreement'];
92
+ }
93
+ export async function respondToAgreement(agreementId, action, creds) {
94
+ if (!agreementId || !action)
95
+ throw new Error('Agreement ID and action are required for proposal response');
96
+ assertCreds(creds, 'proposal response');
97
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/respond`, {
98
+ method: 'POST',
99
+ headers: buildHeaders(creds),
100
+ body: JSON.stringify({ action }),
101
+ });
102
+ if (!res.ok) {
103
+ const body = await res.text().catch(() => '');
104
+ throwApiError(res, body, `Proposal response failed: ${res.status} ${res.statusText}`);
105
+ }
106
+ const data = await res.json().catch(() => null);
107
+ if (!data?.['agreement']) {
108
+ throw new Error('Invalid response: expected { agreement } from /agreements/:id/respond');
109
+ }
110
+ return data['agreement'];
111
+ }
112
+ export async function counterAgreement(agreementId, counter, creds) {
113
+ if (!agreementId)
114
+ throw new Error('agreementId is required for counter-offer');
115
+ assertCreds(creds, 'counter proposal');
116
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/counter`, {
117
+ method: 'POST',
118
+ headers: buildHeaders(creds),
119
+ body: JSON.stringify(counter || {}),
120
+ });
121
+ if (!res.ok) {
122
+ const body = await res.text().catch(() => '');
123
+ throwApiError(res, body, `Counter proposal failed: ${res.status} ${res.statusText}`);
124
+ }
125
+ const data = await res.json().catch(() => null);
126
+ if (!data?.['agreement']) {
127
+ throw new Error('Invalid response: expected { agreement } from /agreements/:id/counter');
128
+ }
129
+ return data['agreement'];
130
+ }
131
+ export async function getAgreementStatus(agreementId, creds) {
132
+ if (!agreementId)
133
+ return null;
134
+ assertCreds(creds, 'proposal status read');
135
+ try {
136
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/status`, {
137
+ method: 'GET',
138
+ headers: buildHeaders(creds),
139
+ });
140
+ if (res.status === 404)
141
+ return null;
142
+ if (!res.ok) {
143
+ const body = await res.text().catch(() => '');
144
+ runtimeLog.warn('AgreementClient', `⚠️ Proposal status read failed: ${res.status} ${res.statusText} ${body}`);
145
+ return null;
146
+ }
147
+ return await res.json().catch(() => null);
148
+ }
149
+ catch (e) {
150
+ runtimeLog.warn('AgreementClient', `⚠️ Proposal status read failed: ${e.message}`);
151
+ return null;
152
+ }
153
+ }
154
+ export async function listAgreements(filters = {}, creds) {
155
+ assertCreds(creds, 'list agreements');
156
+ try {
157
+ const url = new URL(`${getAgreementBaseUrl()}/list`);
158
+ if (filters.status)
159
+ url.searchParams.set('status', filters.status);
160
+ if (filters.engagementKind)
161
+ url.searchParams.set('engagementKind', filters.engagementKind);
162
+ if (filters.proposalStatus)
163
+ url.searchParams.set('proposalStatus', filters.proposalStatus);
164
+ if (filters.hasTask != null)
165
+ url.searchParams.set('hasTask', String(filters.hasTask));
166
+ const res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
167
+ if (!res.ok) {
168
+ const body = await res.text().catch(() => '');
169
+ runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
170
+ return [];
171
+ }
172
+ const data = await res.json().catch(() => null);
173
+ return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
174
+ }
175
+ catch (e) {
176
+ runtimeLog.warn('AgreementClient', `⚠️ List agreements failed: ${e.message}`);
177
+ return [];
178
+ }
179
+ }
180
+ export async function getMyAgreements(filters = {}, creds) {
181
+ assertCreds(creds, 'get my agreements');
182
+ try {
183
+ const url = new URL(`${getAgreementBaseUrl()}/mine`);
184
+ if (filters.engagementKind)
185
+ url.searchParams.set('engagementKind', filters.engagementKind);
186
+ if (filters.proposalStatus)
187
+ url.searchParams.set('proposalStatus', filters.proposalStatus);
188
+ if (filters.hasTask != null)
189
+ url.searchParams.set('hasTask', String(filters.hasTask));
190
+ const res = await fetch(url.toString(), { method: 'GET', headers: buildHeaders(creds) });
191
+ if (!res.ok) {
192
+ const body = await res.text().catch(() => '');
193
+ runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
194
+ return [];
195
+ }
196
+ const data = await res.json().catch(() => null);
197
+ return Array.isArray(data?.['agreements']) ? data['agreements'] : [];
198
+ }
199
+ catch (e) {
200
+ runtimeLog.warn('AgreementClient', `⚠️ Get my agreements failed: ${e.message}`);
201
+ return [];
202
+ }
203
+ }
204
+ export async function getAgreement(agreementId, creds) {
205
+ if (!agreementId)
206
+ return null;
207
+ assertCreds(creds, 'get agreement');
208
+ try {
209
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}`, {
210
+ method: 'GET',
211
+ headers: buildHeaders(creds),
212
+ });
213
+ if (res.status === 404)
214
+ return null;
215
+ if (!res.ok) {
216
+ const body = await res.text().catch(() => '');
217
+ runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
218
+ return null;
219
+ }
220
+ const data = await res.json().catch(() => null);
221
+ return data?.['agreement'] ?? null;
222
+ }
223
+ catch (e) {
224
+ runtimeLog.warn('AgreementClient', `⚠️ Get agreement failed: ${e.message}`);
225
+ return null;
226
+ }
227
+ }
228
+ export async function createAgreement(body, creds) {
229
+ if (!body)
230
+ throw new Error('Body is required for agreement creation');
231
+ assertCreds(creds, 'agreement creation');
232
+ const res = await fetch(`${getAgreementBaseUrl()}/create`, {
233
+ method: 'POST',
234
+ headers: buildHeaders(creds),
235
+ body: JSON.stringify(body),
236
+ });
237
+ if (!res.ok) {
238
+ const responseBody = await res.text().catch(() => '');
239
+ throwApiError(res, responseBody, `Agreement creation failed: ${res.status} ${res.statusText}`);
240
+ }
241
+ const data = await res.json().catch(() => null);
242
+ if (!data?.['agreement']) {
243
+ throw new Error('Invalid response: expected { ok, agreement } from /agreements/create');
244
+ }
245
+ return data;
246
+ }
247
+ export async function revokeAgreement(agreementId, creds) {
248
+ if (!agreementId)
249
+ throw new Error('agreementId is required for revocation');
250
+ assertCreds(creds, 'agreement revocation');
251
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/revoke`, {
252
+ method: 'POST',
253
+ headers: buildHeaders(creds),
254
+ });
255
+ if (!res.ok) {
256
+ const body = await res.text().catch(() => '');
257
+ throwApiError(res, body, `Agreement revocation 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 /agreements/:id/revoke');
262
+ }
263
+ return data;
264
+ }
265
+ // ---------------------------------------------------------------------------
266
+ // Chat links
267
+ // ---------------------------------------------------------------------------
268
+ export async function getAgreementsByChat(chatId, creds) {
269
+ if (!chatId)
270
+ return [];
271
+ assertCreds(creds, 'get agreements by chat');
272
+ try {
273
+ const res = await fetch(`${getAgreementBaseUrl()}/by-chat/${encodeURIComponent(chatId)}`, {
274
+ method: 'GET',
275
+ headers: buildHeaders(creds),
276
+ });
277
+ if (!res.ok) {
278
+ const body = await res.text().catch(() => '');
279
+ runtimeLog.warn('AgreementClient', `⚠️ Get agreements by chat failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
280
+ return [];
281
+ }
282
+ const data = await res.json().catch(() => null);
283
+ const links = data?.['links'] ?? data?.['agreements'];
284
+ return Array.isArray(links) ? links : [];
285
+ }
286
+ catch (e) {
287
+ runtimeLog.warn('AgreementClient', `⚠️ Get agreements by chat failed: ${e.message}`);
288
+ return [];
289
+ }
290
+ }
291
+ export async function linkAgreementToChat(agreementId, chatId, linkType = 'mention', creds) {
292
+ if (!agreementId || !chatId)
293
+ return null;
294
+ assertCreds(creds, 'link agreement to chat');
295
+ try {
296
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/link`, {
297
+ method: 'POST',
298
+ headers: buildHeaders(creds),
299
+ body: JSON.stringify({ chatId, linkType }),
300
+ });
301
+ if (!res.ok) {
302
+ const body = await res.text().catch(() => '');
303
+ runtimeLog.warn('AgreementClient', `⚠️ Link agreement to chat failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
304
+ return null;
305
+ }
306
+ return await res.json().catch(() => null);
307
+ }
308
+ catch (e) {
309
+ runtimeLog.warn('AgreementClient', `⚠️ Link agreement to chat failed: ${e.message}`);
310
+ return null;
311
+ }
312
+ }
313
+ export async function getChatsForAgreement(agreementId, creds) {
314
+ if (!agreementId)
315
+ return [];
316
+ assertCreds(creds, 'get chats for agreement');
317
+ try {
318
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/chats`, {
319
+ method: 'GET',
320
+ headers: buildHeaders(creds),
321
+ });
322
+ if (!res.ok) {
323
+ const body = await res.text().catch(() => '');
324
+ runtimeLog.warn('AgreementClient', `⚠️ Get chats for agreement failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
325
+ return [];
326
+ }
327
+ const data = await res.json().catch(() => null);
328
+ return Array.isArray(data?.['links']) ? data['links'] : [];
329
+ }
330
+ catch (e) {
331
+ runtimeLog.warn('AgreementClient', `⚠️ Get chats for agreement failed: ${e.message}`);
332
+ return [];
333
+ }
334
+ }
335
+ export async function joinAgreement(agreementId, creds) {
336
+ if (!agreementId)
337
+ throw new Error('agreementId is required for join');
338
+ assertCreds(creds, 'join agreement');
339
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/join`, {
340
+ method: 'POST',
341
+ headers: buildHeaders(creds),
342
+ });
343
+ if (!res.ok) {
344
+ const body = await res.text().catch(() => '');
345
+ throwApiError(res, body, `Join agreement failed: ${res.status} ${res.statusText}`);
346
+ }
347
+ const data = await res.json().catch(() => null);
348
+ if (!data?.['chatId']) {
349
+ throw new Error('Invalid response: expected { chatId } from /agreements/:id/join');
350
+ }
351
+ return data;
352
+ }
353
+ export async function linkArtifactToAgreement(agreementId, artifactId, linkType = 'produced', creds) {
354
+ if (!agreementId || !artifactId)
355
+ return null;
356
+ assertCreds(creds, 'link artifact to agreement');
357
+ try {
358
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/artifacts`, {
359
+ method: 'POST',
360
+ headers: buildHeaders(creds),
361
+ body: JSON.stringify({ artifactId, linkType }),
362
+ });
363
+ if (!res.ok) {
364
+ const body = await res.text().catch(() => '');
365
+ runtimeLog.warn('AgreementClient', `⚠️ Link artifact failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
366
+ return null;
367
+ }
368
+ return await res.json().catch(() => null);
369
+ }
370
+ catch (e) {
371
+ runtimeLog.warn('AgreementClient', `⚠️ Link artifact failed: ${e.message}`);
372
+ return null;
373
+ }
374
+ }
375
+ export async function getArtifactsForAgreement(agreementId, creds) {
376
+ if (!agreementId)
377
+ return [];
378
+ assertCreds(creds, 'get artifacts for agreement');
379
+ try {
380
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/artifacts`, {
381
+ method: 'GET',
382
+ headers: buildHeaders(creds),
383
+ });
384
+ if (!res.ok) {
385
+ const body = await res.text().catch(() => '');
386
+ runtimeLog.warn('AgreementClient', `⚠️ Get artifacts failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
387
+ return [];
388
+ }
389
+ const data = await res.json().catch(() => null);
390
+ return Array.isArray(data?.['artifacts']) ? data['artifacts'] : [];
391
+ }
392
+ catch (e) {
393
+ runtimeLog.warn('AgreementClient', `⚠️ Get artifacts failed: ${e.message}`);
394
+ return [];
395
+ }
396
+ }
397
+ export async function linkUserToAgreement(agreementId, userId, role, creds) {
398
+ if (!agreementId || !userId || !role)
399
+ return null;
400
+ assertCreds(creds, 'link user to agreement');
401
+ try {
402
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
403
+ method: 'POST',
404
+ headers: buildHeaders(creds),
405
+ body: JSON.stringify({ userId, role }),
406
+ });
407
+ if (!res.ok) {
408
+ const body = await res.text().catch(() => '');
409
+ runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
410
+ return null;
411
+ }
412
+ return await res.json().catch(() => null);
413
+ }
414
+ catch (e) {
415
+ runtimeLog.warn('AgreementClient', `⚠️ Link user failed: ${e.message}`);
416
+ return null;
417
+ }
418
+ }
419
+ export async function getUsersForAgreement(agreementId, creds) {
420
+ if (!agreementId)
421
+ return [];
422
+ assertCreds(creds, 'get users for agreement');
423
+ try {
424
+ const res = await fetch(`${getAgreementBaseUrl()}/${agreementId}/users`, {
425
+ method: 'GET',
426
+ headers: buildHeaders(creds),
427
+ });
428
+ if (!res.ok) {
429
+ const body = await res.text().catch(() => '');
430
+ runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
431
+ return [];
432
+ }
433
+ const data = await res.json().catch(() => null);
434
+ return Array.isArray(data?.['users']) ? data['users'] : [];
435
+ }
436
+ catch (e) {
437
+ runtimeLog.warn('AgreementClient', `⚠️ Get users failed: ${e.message}`);
438
+ return [];
439
+ }
440
+ }
441
+ /**
442
+ * @deprecated Prefer {@link proposeDirectTo}, {@link proposeBroadcast},
443
+ * {@link delegateAgreement}, or {@link publishOpenQuest}.
444
+ */
445
+ export async function createContract(input, creds) {
446
+ assertCreds(creds, 'contract creation');
447
+ const { kind, ...rest } = input;
448
+ if (kind === 'direct')
449
+ return proposeDirectTo(rest, creds);
450
+ if (kind === 'delegate')
451
+ return delegateAgreement(rest, creds);
452
+ return publishOpenQuest(rest, creds);
453
+ }
454
+ /** Publish a buyer-broadcast quest to the marketplace ledger (Store "Wanted"). */
455
+ export async function publishOpenQuest(payload, creds) {
456
+ return publishToLedger(payload, creds);
457
+ }
@@ -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,14 @@
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 declare function listMyChats(creds: Creds): Promise<ChatSummary[]>;
@@ -0,0 +1,69 @@
1
+ import 'dotenv/config';
2
+ import { runtimeLog } from '../shared/runtimeLog.js';
3
+ import { getBackendUrl } from '../utils/urlUtils.js';
4
+ import { ApiError } from '../types.js';
5
+ function buildHeaders(creds) {
6
+ return {
7
+ 'content-type': 'application/json',
8
+ Authorization: `Bearer ${creds.operatorKey}`,
9
+ 'X-Agent-Id': creds.agentId,
10
+ };
11
+ }
12
+ function assertCreds(creds, op) {
13
+ if (!creds?.operatorKey)
14
+ throw new Error(`operatorKey is required for ${op}`);
15
+ if (!creds?.agentId)
16
+ throw new Error(`agentId is required for ${op}`);
17
+ }
18
+ function throwApiError(response, responseBody, defaultMessage) {
19
+ let message = defaultMessage;
20
+ if (responseBody) {
21
+ try {
22
+ const d = JSON.parse(responseBody);
23
+ message = d['details'] || d['error'] || d['message'] || defaultMessage;
24
+ }
25
+ catch {
26
+ message = responseBody || defaultMessage;
27
+ }
28
+ }
29
+ throw new ApiError(message, response.status, responseBody);
30
+ }
31
+ export async function openConversation(participantId, creds) {
32
+ if (!participantId)
33
+ throw new Error('participantId is required for openConversation');
34
+ assertCreds(creds, 'open conversation');
35
+ const res = await fetch(`${getBackendUrl()}/chat/openConversation`, {
36
+ method: 'POST',
37
+ headers: buildHeaders(creds),
38
+ body: JSON.stringify({ participantId }),
39
+ });
40
+ if (!res.ok) {
41
+ const body = await res.text().catch(() => '');
42
+ throwApiError(res, body, `openConversation failed: ${res.status} ${res.statusText}`);
43
+ }
44
+ const data = await res.json().catch(() => null);
45
+ if (!data?.['chatId'] || typeof data['chatId'] !== 'string') {
46
+ throw new Error('Invalid response: expected { chatId } from /chat/openConversation');
47
+ }
48
+ return { chatId: data['chatId'] };
49
+ }
50
+ export async function listMyChats(creds) {
51
+ assertCreds(creds, 'list my chats');
52
+ try {
53
+ const res = await fetch(`${getBackendUrl()}/chat/listMyChats`, {
54
+ method: 'POST',
55
+ headers: buildHeaders(creds),
56
+ });
57
+ if (!res.ok) {
58
+ const body = await res.text().catch(() => '');
59
+ runtimeLog.warn('ChatClient', `⚠️ listMyChats failed: ${res.status} ${res.statusText} ${body?.slice(0, 200)}`);
60
+ return [];
61
+ }
62
+ const data = await res.json().catch(() => null);
63
+ return Array.isArray(data?.['chats']) ? data['chats'] : [];
64
+ }
65
+ catch (e) {
66
+ runtimeLog.warn('ChatClient', `⚠️ listMyChats failed: ${e.message}`);
67
+ return [];
68
+ }
69
+ }