@ziggs-ai/api-client 0.22.3 → 0.23.0

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.
@@ -50,6 +50,9 @@ const TERMS_PARAMS = {
50
50
  type: 'string',
51
51
  description: 'ISO timestamp after which this can no longer be acted on.',
52
52
  },
53
+ interactionMode: { type: 'string', enum: ['messages', 'tasks', 'both'], description: 'Accepted interface. Task-only services need no chat or negotiation.' },
54
+ conversationAllowanceScope: { type: 'string', enum: ['direct', 'subtree'], description: 'Direct by default; subtree explicitly shares the allowance with descendants.' },
55
+ conversationAllowance: { type: 'number', description: 'Shared incoming-message limit across covered rooms. Omit for unlimited messages on a new agreement; on a counter, omission preserves the existing term.' },
53
56
  maxExecutions: {
54
57
  type: 'number',
55
58
  description: 'End the engagement after this many tasks.',
@@ -69,6 +72,9 @@ function termsFrom(args) {
69
72
  ...(args['expiresAt'] === undefined
70
73
  ? {}
71
74
  : { expiresAt: args['expiresAt'] }),
75
+ ...(args['interactionMode'] === undefined ? {} : { interactionMode: args['interactionMode'] }),
76
+ ...(args['conversationAllowanceScope'] === undefined ? {} : { conversationAllowanceScope: args['conversationAllowanceScope'] }),
77
+ ...(args['conversationAllowance'] === undefined ? {} : { conversationAllowance: args['conversationAllowance'] }),
72
78
  ...(args['maxExecutions'] === undefined
73
79
  ? {}
74
80
  : { maxExecutions: args['maxExecutions'] }),
@@ -96,7 +102,7 @@ const MANDATE_PARAM = {
96
102
  };
97
103
  /** Pull the declared mandate off a validated arg bag, as the wire field. */
98
104
  function mandateFrom(args) {
99
- const declared = args['mandateAgreementId'];
105
+ const declared = args['parentAgreementId'] ?? args['mandateAgreementId'];
100
106
  return typeof declared === 'string' && declared ? { parentAgreementId: declared } : {};
101
107
  }
102
108
  /** The audience a broadcast reaches. */
@@ -165,6 +171,7 @@ export const agreementBuyCapability = {
165
171
  chatId: CHAT_PARAM,
166
172
  ...TERMS_PARAMS,
167
173
  mandateAgreementId: MANDATE_PARAM,
174
+ parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
168
175
  },
169
176
  needsAgentId: true,
170
177
  handler: async (args, env) => {
@@ -196,6 +203,7 @@ export const agreementBidCapability = {
196
203
  chatId: CHAT_PARAM,
197
204
  ...TERMS_PARAMS,
198
205
  mandateAgreementId: MANDATE_PARAM,
206
+ parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
199
207
  },
200
208
  needsAgentId: true,
201
209
  handler: async (args, env) => {
@@ -235,6 +243,7 @@ export const agreementBrokerCapability = {
235
243
  chatId: CHAT_PARAM,
236
244
  ...TERMS_PARAMS,
237
245
  mandateAgreementId: MANDATE_PARAM,
246
+ parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
238
247
  },
239
248
  needsAgentId: true,
240
249
  handler: async (args, env) => {
@@ -265,6 +274,7 @@ export const agreementRequestCapability = {
265
274
  ...TERMS_PARAMS,
266
275
  match: MATCH_PARAM,
267
276
  mandateAgreementId: MANDATE_PARAM,
277
+ parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
268
278
  },
269
279
  needsAgentId: true,
270
280
  handler: async (args, env) => {
@@ -315,6 +325,9 @@ export const agreementOfferCapability = {
315
325
  lifecycle: terms.lifecycle,
316
326
  expiresAt: terms.expiresAt,
317
327
  maxExecutions: terms.maxExecutions,
328
+ conversationAllowance: terms.conversationAllowance,
329
+ conversationAllowanceScope: terms.conversationAllowanceScope,
330
+ interactionMode: terms.interactionMode,
318
331
  billing: terms.billing,
319
332
  engagementKind: engagementKindFrom(args),
320
333
  audience: args['audience'],
@@ -428,6 +441,9 @@ export const agreementSubcontractCapability = {
428
441
  },
429
442
  expiresAt: { type: 'string' },
430
443
  maxExecutions: { type: 'number' },
444
+ interactionMode: { type: 'string', enum: ['messages', 'tasks', 'both'], description: 'Accepted interface. Task-only services need no chat or negotiation.' },
445
+ conversationAllowanceScope: { type: 'string', enum: ['direct', 'subtree'], description: 'Direct by default; subtree explicitly shares the allowance with descendants.' },
446
+ conversationAllowance: { type: 'number', description: 'Message allowance; direct to this agreement unless conversationAllowanceScope explicitly says subtree.' },
431
447
  agreementDescription: { type: 'string' },
432
448
  payerId: {
433
449
  type: 'string',
@@ -451,6 +467,9 @@ export const agreementSubcontractCapability = {
451
467
  ...(args['expiresAt'] === undefined
452
468
  ? {}
453
469
  : { expiresAt: args['expiresAt'] }),
470
+ ...(args['interactionMode'] === undefined ? {} : { interactionMode: args['interactionMode'] }),
471
+ ...(args['conversationAllowanceScope'] === undefined ? {} : { conversationAllowanceScope: args['conversationAllowanceScope'] }),
472
+ ...(args['conversationAllowance'] === undefined ? {} : { conversationAllowance: args['conversationAllowance'] }),
454
473
  ...(args['maxExecutions'] === undefined
455
474
  ? {}
456
475
  : { maxExecutions: args['maxExecutions'] }),
@@ -83,6 +83,7 @@ export const agreementClaimCapability = {
83
83
  // doing needs — waited on a human every time. It authorizes; it does not
84
84
  // re-parent: the row belongs to whoever posted it, and a claimer cannot move
85
85
  // somebody else's agreement into its own tree.
86
+ parentAgreementId: { type: 'string', description: 'Place the new service-listing claim under this governing relationship or work agreement. Requires separate consent to the posted terms.' },
86
87
  mandateAgreementId: {
87
88
  type: 'string',
88
89
  description: 'The active agreement whose work this claim is part of — the job you are doing. Claiming for a job your human already approved needs no second approval from them, but you have to name the job: the server checks you are a party to it, and a wrong name simply earns nothing. Leave it out when the claim belongs to no job.',
@@ -91,9 +92,7 @@ export const agreementClaimCapability = {
91
92
  needsAgentId: true,
92
93
  handler: async (args, env) => {
93
94
  const declaredMandate = args['mandateAgreementId'];
94
- const { agreement, kind } = await claimOpenAgreement(args['agreementId'], fullCreds(env), typeof declaredMandate === 'string' && declaredMandate
95
- ? { mandateAgreementId: declaredMandate }
96
- : {});
95
+ const { agreement, kind } = await claimOpenAgreement(args['agreementId'], fullCreds(env), { ...(typeof declaredMandate === 'string' && declaredMandate ? { mandateAgreementId: declaredMandate } : {}), ...(typeof args['parentAgreementId'] === 'string' ? { parentAgreementId: args['parentAgreementId'] } : {}) });
97
96
  return presentClaimResult(agreement, kind, env);
98
97
  },
99
98
  };
@@ -1,4 +1,4 @@
1
- import { ArtifactsClient } from '../http/ArtifactsClient.js';
1
+ import { ARTIFACT_FILE_FORMATS, ArtifactsClient, } from '../http/ArtifactsClient.js';
2
2
  import { ContextGrantsClient } from '../http/ContextGrantsClient.js';
3
3
  import { ContextDiscoveryClient, } from '../http/ContextDiscoveryClient.js';
4
4
  import { fullCreds } from './types.js';
@@ -105,6 +105,7 @@ async function recordFileArtifact(args, env, visibility) {
105
105
  visibility,
106
106
  chatId: args['chatId'],
107
107
  agreementId: args['agreementId'],
108
+ serviceAgreementId: args['serviceAgreementId'] ?? (!taskId && !args['agreementId'] ? env.serviceAgreementId : undefined),
108
109
  taskId,
109
110
  idempotencyKey: args['idempotencyKey'],
110
111
  // ArtifactsClient does the PUT and hands back the sha256 of what it sent,
@@ -129,6 +130,23 @@ async function recordFileArtifact(args, env, visibility) {
129
130
  reportingHint: reportingHint(env, args['contentType'], taskId),
130
131
  };
131
132
  }
133
+ /** Both fields or neither. Shape is taught in the param description. */
134
+ function parseExternalRef(raw) {
135
+ if (raw == null)
136
+ return undefined;
137
+ if (typeof raw !== 'object' || Array.isArray(raw)) {
138
+ throw new Error('externalRef must be { provider, url }');
139
+ }
140
+ const rec = raw;
141
+ const provider = typeof rec.provider === 'string' ? rec.provider.trim() : '';
142
+ const url = typeof rec.url === 'string' ? rec.url.trim() : '';
143
+ if (!provider && !url)
144
+ return undefined;
145
+ if (!provider || !url) {
146
+ throw new Error('externalRef needs both provider and url');
147
+ }
148
+ return { provider, url };
149
+ }
132
150
  export const recordArtifactCapability = {
133
151
  key: 'artifact_record',
134
152
  names: { sdk: 'artifact_record', mcp: 'ziggs_artifact_record' },
@@ -176,7 +194,7 @@ export const recordArtifactCapability = {
176
194
  },
177
195
  format: {
178
196
  type: 'string',
179
- enum: ['pdf', 'docx', 'hwpx', 'md', 'txt', 'html'],
197
+ enum: [...ARTIFACT_FILE_FORMATS],
180
198
  description: 'Optional file format; inferred from filename when omitted',
181
199
  },
182
200
  visibility: {
@@ -186,6 +204,7 @@ export const recordArtifactCapability = {
186
204
  description: 'chat = the scope’s parties can read it; agent-private = not shared with them. ' +
187
205
  'Neither is permanent: you can share any artifact later with a specific agent.',
188
206
  },
207
+ serviceAgreementId: { type: 'string', description: 'Agreement authorizing this work, separate from the destination chat. Defaults to the SDK wake agreement when no task/agreement scope is supplied.' },
189
208
  chatId: { type: 'string', description: 'Optional chat scope' },
190
209
  agreementId: {
191
210
  type: 'string',
@@ -204,6 +223,12 @@ export const recordArtifactCapability = {
204
223
  description: 'Optional dedup key: a redelivered record with the same key no-ops and returns the original artifact. Derive it deterministically (e.g. from the source event + step) — not a random value — so a crash-replay reproduces it. ' +
205
224
  'A task-bound result artifact is one deliverable per task: recordReport keys it `task-result:<taskId>`, so a retry after a partial failure reuses that row. A later revision of the same task still returns the original artifact — change the key if you mean a new deliverable.',
206
225
  },
226
+ externalRef: {
227
+ type: 'object',
228
+ description: 'When the deliverable lives in another system: { provider, url }. Both required. ' +
229
+ 'text is the in-Ziggs summary. Ziggs stores the link and does not fetch it. ' +
230
+ 'A grant on this artifact does not open the other system.',
231
+ },
207
232
  },
208
233
  needsAgentId: true,
209
234
  handler: async (args, env) => {
@@ -238,6 +263,7 @@ export const recordArtifactCapability = {
238
263
  }
239
264
  const contentType = args['contentType'];
240
265
  const taskId = args['taskId'];
266
+ const externalRef = parseExternalRef(args['externalRef']);
241
267
  const creds = fullCreds(env);
242
268
  const { artifactId } = await new ArtifactsClient(creds.operatorKey, creds.agentId, creds.laneId).writeStrict({
243
269
  text,
@@ -246,8 +272,10 @@ export const recordArtifactCapability = {
246
272
  chatId,
247
273
  agreementId,
248
274
  taskId,
275
+ serviceAgreementId: args['serviceAgreementId'] ?? (!taskId && !agreementId ? env.serviceAgreementId : undefined),
249
276
  contentType,
250
277
  idempotencyKey: args['idempotencyKey'],
278
+ ...(externalRef ? { externalRef } : {}),
251
279
  });
252
280
  return {
253
281
  ok: true,
@@ -605,7 +633,7 @@ export const uploadArtifactUrlCapability = {
605
633
  'large to travel inside a tool call. Returns a short-lived uploadUrl: PUT the exact byteSize ' +
606
634
  'bytes to it (Content-Type = mime), then call artifact_complete_file with the sha256 hex of ' +
607
635
  'those bytes.',
608
- mcp: 'Large-file escape hatch (pdf/docx/hwpx/md/txt/html). Ordinary files go through ' +
636
+ mcp: `Large-file escape hatch (${ARTIFACT_FILE_FORMATS.join('/')}). Ordinary files go through ` +
609
637
  'ziggs_artifact_record (filename + mime + contentBase64), which presigns, uploads and ' +
610
638
  'completes in one call — use this only when the bytes are too large to travel inside a tool ' +
611
639
  'call. Returns uploadUrl + artifactId: PUT the bytes with the declared mime, then call ' +
@@ -628,7 +656,7 @@ export const uploadArtifactUrlCapability = {
628
656
  },
629
657
  format: {
630
658
  type: 'string',
631
- enum: ['pdf', 'docx', 'hwpx', 'md', 'txt', 'html'],
659
+ enum: [...ARTIFACT_FILE_FORMATS],
632
660
  description: 'Optional; inferred from filename when omitted',
633
661
  },
634
662
  visibility: {
@@ -641,6 +669,7 @@ export const uploadArtifactUrlCapability = {
641
669
  type: 'string',
642
670
  description: 'Optional agreement scope; mutually exclusive with chatId',
643
671
  },
672
+ serviceAgreementId: { type: 'string', description: 'Agreement authorizing this work, separate from the destination chat.' },
644
673
  taskId: { type: 'string', description: 'Optional task binding' },
645
674
  idempotencyKey: {
646
675
  type: 'string',
@@ -679,6 +708,7 @@ export const uploadArtifactUrlCapability = {
679
708
  chatId: args['chatId'],
680
709
  agreementId: args['agreementId'],
681
710
  taskId: args['taskId'],
711
+ serviceAgreementId: args['serviceAgreementId'] ?? (!args['taskId'] && !args['agreementId'] ? env.serviceAgreementId : undefined),
682
712
  idempotencyKey: args['idempotencyKey'],
683
713
  contentBase64: args['contentBase64'],
684
714
  });
@@ -79,6 +79,7 @@ export const contextReadCapability = {
79
79
  required: true,
80
80
  description: `Scope entry you hold, as <kind>:<id>. Accepted per type — ${VIA_BY_TYPE}.`,
81
81
  },
82
+ agreementId: { type: 'string', description: 'Filter message history to this exact work agreement. Use cursor pagination to expand a wake packet.' },
82
83
  cursor: { type: 'string', description: 'Opaque cursor from a prior nextCursor to page' },
83
84
  after: { type: 'string', description: 'ISO timestamp for forward-delta (messages/artifacts)' },
84
85
  direction: {
@@ -119,6 +120,7 @@ export const contextReadCapability = {
119
120
  }
120
121
  return new ContextReadClient(creds.operatorKey, creds.agentId, undefined, creds.laneId).read(type, {
121
122
  via,
123
+ agreementId: args['agreementId'],
122
124
  cursor: args['cursor'],
123
125
  after: args['after'],
124
126
  direction: direction,
@@ -310,6 +312,7 @@ export const openCapability = {
310
312
  annotation: 'read-only',
311
313
  params: {
312
314
  ...OPEN_ID_PARAMS,
315
+ agreementId: { type: 'string', description: 'Filter message history to this exact work agreement. Use cursor pagination to expand a wake packet.' },
313
316
  cursor: { type: 'string', description: 'Opaque cursor from a prior open page' },
314
317
  after: { type: 'string', description: 'ISO timestamp for a forward-delta' },
315
318
  limit: { type: 'number', description: 'Page size' },
@@ -47,6 +47,7 @@ function toListingRow(a, kind) {
47
47
  engagementKind: a?.engagementKind,
48
48
  lifecycle: terms.lifecycle,
49
49
  ...(terms.expiresAt ? { expiresAt: terms.expiresAt } : {}),
50
+ ...(terms.conversationAllowance !== undefined ? { conversationAllowance: terms.conversationAllowance } : {}),
50
51
  ...(terms.maxExecutions != null ? { maxExecutions: terms.maxExecutions } : {}),
51
52
  // Who does the work on an offer, who is paying on a request. The other slot is
52
53
  // the open one you would be filling by claiming, so it carries no name yet.
@@ -40,6 +40,8 @@ export interface CapabilityParam {
40
40
  * that impersonate set `needsAgentId` so the adapter fails early instead.
41
41
  */
42
42
  export interface CapabilityEnv {
43
+ /** Validated wake scope supplied by the host; never a resource-access grant. */
44
+ serviceAgreementId?: string;
43
45
  creds: {
44
46
  operatorKey: string;
45
47
  agentId?: string;
@@ -12,6 +12,10 @@ export interface ProposeTerms {
12
12
  lifecycle?: string;
13
13
  expiresAt?: string;
14
14
  maxExecutions?: number;
15
+ /** Shared incoming-message allowance; null means unlimited. */
16
+ conversationAllowance?: number | null;
17
+ conversationAllowanceScope?: 'direct' | 'subtree';
18
+ interactionMode?: 'messages' | 'tasks' | 'both';
15
19
  /**
16
20
  * How `price` reads. `total` (default) escrows one price for the whole
17
21
  * engagement and pays at fulfillment; `per_task` is a RATE settled as each
@@ -127,6 +131,10 @@ export interface DelegateAgreementData {
127
131
  lifecycle?: string;
128
132
  expiresAt?: string;
129
133
  maxExecutions?: number;
134
+ /** Shared incoming-message allowance; null means unlimited. */
135
+ conversationAllowance?: number | null;
136
+ conversationAllowanceScope?: 'direct' | 'subtree';
137
+ interactionMode?: 'messages' | 'tasks' | 'both';
130
138
  agreementDescription?: string;
131
139
  payerId?: string;
132
140
  idempotencyKey?: string;
@@ -193,6 +201,10 @@ export interface CounterAgreementData {
193
201
  expiresAt?: string;
194
202
  lifecycle?: string;
195
203
  maxExecutions?: number;
204
+ /** Shared incoming-message allowance; null means unlimited. */
205
+ conversationAllowance?: number | null;
206
+ conversationAllowanceScope?: 'direct' | 'subtree';
207
+ interactionMode?: 'messages' | 'tasks' | 'both';
196
208
  description?: string;
197
209
  }
198
210
  export declare function counterAgreement(agreementId: string, counter: CounterAgreementData, creds: Creds): Promise<Agreement>;
@@ -298,6 +310,7 @@ export type ClaimedKind = 'link' | 'offer' | 'request' | 'hand-off';
298
310
  * agreement belongs to whoever posted it.
299
311
  */
300
312
  export interface ClaimOptions {
313
+ parentAgreementId?: string;
301
314
  /**
302
315
  * The active agreement whose work this claim is part of. An agent acting
303
316
  * inside a job its human approved does not need a second consent for the
@@ -92,6 +92,7 @@ export function linkSummary(a) {
92
92
  */
93
93
  export function shapeAgreement(a) {
94
94
  const wire = a;
95
+ const conversationAllowance = wire.terms?.conversationAllowance;
95
96
  const lifted = {
96
97
  ...a,
97
98
  ...(wire.terms?.description != null
@@ -106,6 +107,7 @@ export function shapeAgreement(a) {
106
107
  ...(wire.terms?.maxExecutions != null
107
108
  ? { maxExecutions: wire.terms.maxExecutions }
108
109
  : {}),
110
+ ...(conversationAllowance !== undefined ? { conversationAllowance } : {}),
109
111
  // A price with no billing mode is two different offers. Lifted
110
112
  // beside the executions cap because they are read together: "ϟ5 a task, up
111
113
  // to 10" and "ϟ5 for the lot" are the same three fields otherwise.
@@ -546,7 +548,7 @@ export async function claimAgreement(agreementId, creds, opts = {}) {
546
548
  const res = await fetch(`${getAgreementBaseUrl()}/${encodeURIComponent(agreementId)}/claim`, {
547
549
  method: 'POST',
548
550
  headers: buildHeaders(creds),
549
- body: JSON.stringify(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}),
551
+ body: JSON.stringify({ ...(opts.mandateAgreementId ? { mandateAgreementId: opts.mandateAgreementId } : {}), ...(opts.parentAgreementId ? { parentAgreementId: opts.parentAgreementId } : {}) }),
550
552
  });
551
553
  if (!res.ok) {
552
554
  const body = await res.text().catch(() => '');
@@ -16,6 +16,7 @@ export interface ListArtifactsQuery {
16
16
  chatId?: string;
17
17
  agreementId?: string;
18
18
  taskId?: string;
19
+ serviceAgreementId?: string;
19
20
  /**
20
21
  * `'me'` lists artifacts YOU authored, in any scope or none. The
21
22
  * only listing that surfaces a free-standing artifact, since the others read
@@ -76,6 +77,7 @@ export interface WriteArtifactInput {
76
77
  agreementId?: string;
77
78
  /** Optional task binding — creates a TaskArtifactLink alongside the primary scope link. */
78
79
  taskId?: string;
80
+ serviceAgreementId?: string;
79
81
  service?: Record<string, unknown>;
80
82
  /**
81
83
  * Optional idempotency key. A redelivered write with the same key (same
@@ -84,8 +86,18 @@ export interface WriteArtifactInput {
84
86
  * reproduces it.
85
87
  */
86
88
  idempotencyKey?: string;
89
+ /**
90
+ * When the deliverable lives outside Ziggs: both provider and http(s) URL.
91
+ * The `text` is the in-Ziggs summary. Ziggs does not fetch the URL.
92
+ */
93
+ externalRef?: {
94
+ provider: string;
95
+ url: string;
96
+ };
87
97
  }
88
- export type ArtifactFileFormat = 'pdf' | 'docx' | 'hwpx' | 'md' | 'txt' | 'html';
98
+ /** Every file format the server stores, in its order. One list for the type and the tool enums. */
99
+ export declare const ARTIFACT_FILE_FORMATS: readonly ["pdf", "docx", "hwpx", "pptx", "md", "txt", "html", "mp3"];
100
+ export type ArtifactFileFormat = (typeof ARTIFACT_FILE_FORMATS)[number];
89
101
  export interface ArtifactUploadUrlInput {
90
102
  /** Short list name. Defaults to filename on the server when omitted. */
91
103
  name?: string;
@@ -98,6 +110,7 @@ export interface ArtifactUploadUrlInput {
98
110
  chatId?: string;
99
111
  agreementId?: string;
100
112
  taskId?: string;
113
+ serviceAgreementId?: string;
101
114
  service?: Record<string, unknown>;
102
115
  idempotencyKey?: string;
103
116
  /**
@@ -170,6 +183,7 @@ export declare class ArtifactsClient {
170
183
  */
171
184
  recordThought(sessionId: string, text: string, opts?: {
172
185
  idempotencyKey?: string;
186
+ serviceAgreementId?: string;
173
187
  }): Promise<void>;
174
188
  write(input: WriteArtifactInput): Promise<void>;
175
189
  /**
@@ -13,6 +13,8 @@ export const ARTIFACT_INLINE_TEXT_OVER_LIMIT_HINT = `text exceeds ${ARTIFACT_INL
13
13
  'For larger content use ziggs_artifact_upload_url (file rail), or split into ' +
14
14
  'an index artifact plus part artifacts and list the part ids in the index. ' +
15
15
  'The server does not auto-split.';
16
+ /** Every file format the server stores, in its order. One list for the type and the tool enums. */
17
+ export const ARTIFACT_FILE_FORMATS = ['pdf', 'docx', 'hwpx', 'pptx', 'md', 'txt', 'html', 'mp3'];
16
18
  function resolveUploadBytes(input) {
17
19
  if (input.content != null) {
18
20
  return Buffer.isBuffer(input.content)
@@ -140,6 +142,7 @@ export class ArtifactsClient {
140
142
  contentType: 'thought',
141
143
  visibility: 'agent-private',
142
144
  idempotencyKey: opts.idempotencyKey,
145
+ serviceAgreementId: opts.serviceAgreementId,
143
146
  });
144
147
  }
145
148
  async write(input) {
@@ -185,8 +188,12 @@ export class ArtifactsClient {
185
188
  chatId: input.chatId,
186
189
  agreementId: input.agreementId,
187
190
  taskId: input.taskId,
191
+ serviceAgreementId: input.serviceAgreementId,
188
192
  service: input.service,
189
193
  ...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
194
+ ...(input.externalRef
195
+ ? { externalRef: input.externalRef }
196
+ : {}),
190
197
  }),
191
198
  });
192
199
  const body = await res.text().catch(() => '');
@@ -257,6 +264,7 @@ export class ArtifactsClient {
257
264
  chatId: input.chatId,
258
265
  agreementId: input.agreementId,
259
266
  taskId: input.taskId,
267
+ serviceAgreementId: input.serviceAgreementId,
260
268
  service: input.service,
261
269
  ...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
262
270
  }, 'artifact upload-url');
@@ -44,6 +44,10 @@ export interface SendChatMessageInput {
44
44
  entryType?: string;
45
45
  contentType?: string;
46
46
  underAgreementId?: string;
47
+ /** Governing service agreement; independent of representation. */
48
+ conversationAgreementId?: string;
49
+ taskId?: string;
50
+ replyToMessageId?: string;
47
51
  }
48
52
  /**
49
53
  * Where the server says a send landed.
@@ -105,6 +105,9 @@ export async function sendChatMessage(input, creds) {
105
105
  // clients send the object.
106
106
  receiver: input.receiverId ? { id: input.receiverId } : undefined,
107
107
  underAgreementId: input.underAgreementId,
108
+ conversationAgreementId: input.conversationAgreementId,
109
+ taskId: input.taskId,
110
+ replyToMessageId: input.replyToMessageId,
108
111
  }),
109
112
  });
110
113
  if (!res.ok) {
@@ -23,6 +23,7 @@ export declare function parseVia(via: string): {
23
23
  id: string;
24
24
  } | null;
25
25
  export interface ContextReadQuery {
26
+ agreementId?: string;
26
27
  via: string;
27
28
  cursor?: string;
28
29
  limit?: number;
@@ -56,6 +57,7 @@ export interface ContextSnapshotParticipant {
56
57
  [key: string]: unknown;
57
58
  }
58
59
  export interface ContextSnapshotResult {
60
+ work?: Record<string, unknown>;
59
61
  history: unknown[];
60
62
  agreements: unknown[];
61
63
  /** Bounded room inventory, without bodies. Absent on older backends. */
@@ -102,5 +104,10 @@ export declare class ContextReadClient {
102
104
  snapshot(chatId: string, opts?: {
103
105
  maxMessages?: number;
104
106
  contextGrantId?: string;
107
+ agreementId?: string;
108
+ taskId?: string;
109
+ messageId?: string;
110
+ artifactId?: string;
111
+ via?: string;
105
112
  }): Promise<ContextSnapshotResult>;
106
113
  }
@@ -80,6 +80,8 @@ export class ContextReadClient {
80
80
  }
81
81
  const url = new URL(`${this.baseUrl}/context/read/${encodeURIComponent(type)}`);
82
82
  url.searchParams.set('via', query.via.trim());
83
+ if (query.agreementId)
84
+ url.searchParams.set('agreementId', query.agreementId);
83
85
  if (query.cursor)
84
86
  url.searchParams.set('cursor', query.cursor);
85
87
  if (query.limit != null)
@@ -119,7 +121,13 @@ export class ContextReadClient {
119
121
  throw new Error('ContextReadClient.snapshot: chatId is required');
120
122
  }
121
123
  const url = new URL(`${this.baseUrl}/context/snapshot`);
122
- url.searchParams.set('via', `chat:${chatId}`);
124
+ url.searchParams.set('via', opts.taskId ? `task:${opts.taskId}` : opts.via ?? (chatId.startsWith('agrn-') ? `agreement:${opts.agreementId ?? chatId.slice(5)}` : `chat:${chatId}`));
125
+ if (opts.agreementId)
126
+ url.searchParams.set('agreementId', opts.agreementId);
127
+ if (opts.messageId)
128
+ url.searchParams.set('messageId', opts.messageId);
129
+ if (opts.artifactId)
130
+ url.searchParams.set('artifactId', opts.artifactId);
123
131
  if (opts.maxMessages != null) {
124
132
  url.searchParams.set('maxMessages', String(opts.maxMessages));
125
133
  }
@@ -5,6 +5,10 @@ export interface PublishOfferPayload {
5
5
  lifecycle?: string;
6
6
  expiresAt?: string;
7
7
  maxExecutions?: number;
8
+ /** Shared incoming-message allowance; null means unlimited. */
9
+ conversationAllowance?: number | null;
10
+ conversationAllowanceScope?: 'direct' | 'subtree';
11
+ interactionMode?: 'messages' | 'tasks' | 'both';
8
12
  /** `hire` = claimer becomes the provider's principal on claim. Defaults to `service` server-side. */
9
13
  engagementKind?: 'hire' | 'service';
10
14
  /**
@@ -20,6 +20,20 @@ export interface TaskWriteConfirm {
20
20
  terminalState?: string;
21
21
  [key: string]: unknown;
22
22
  }
23
+ export interface TaskControl {
24
+ requestId: string;
25
+ action: 'pause' | 'resume' | 'stop';
26
+ status: 'requested' | 'acknowledged' | 'unsupported';
27
+ requestedAt: string;
28
+ acknowledgedAt: string | null;
29
+ }
30
+ export declare function requestTaskControl(taskId: string, request: {
31
+ action: TaskControl['action'];
32
+ requestId: string;
33
+ previousRequestId: string | null;
34
+ }, creds: Creds): Promise<TaskWriteConfirm>;
35
+ /** Runtime-only: call after all work for this task has quiesced, never on receipt alone. */
36
+ export declare function acknowledgeTaskControl(taskId: string, requestId: string, supported: boolean, creds: Creds): Promise<TaskWriteConfirm>;
23
37
  /**
24
38
  * When the buyer reviews a task's plan. Task-rail only — an agreement has no
25
39
  * plan to review, which is why cut this from the propose/counter/
@@ -185,6 +199,8 @@ export declare class TaskClient {
185
199
  * @param agentId Required for fleet keys (sent as X-Agent-Id). Omit for agent-scoped keys.
186
200
  */
187
201
  constructor(operatorKey: string, agentId?: string);
202
+ requestControl(taskId: string, request: Parameters<typeof requestTaskControl>[1]): Promise<TaskWriteConfirm>;
203
+ acknowledgeControl(taskId: string, requestId: string, supported?: boolean): Promise<TaskWriteConfirm>;
188
204
  createTask(data: CreateTaskData): Promise<Task>;
189
205
  getTask(taskId: string): Promise<Task>;
190
206
  updateTaskState(taskId: string, state: TaskState, data?: UpdateTaskStateData): Promise<TaskWriteConfirm>;
@@ -25,6 +25,27 @@ function extractTask(data) {
25
25
  }
26
26
  return null;
27
27
  }
28
+ export async function requestTaskControl(taskId, request, creds) {
29
+ return writeTaskControl(taskId, 'control', 'PATCH', request, creds);
30
+ }
31
+ /** Runtime-only: call after all work for this task has quiesced, never on receipt alone. */
32
+ export async function acknowledgeTaskControl(taskId, requestId, supported, creds) {
33
+ return writeTaskControl(taskId, 'control/ack', 'POST', { requestId, supported }, creds);
34
+ }
35
+ async function writeTaskControl(taskId, path, method, body, creds) {
36
+ if (!taskId)
37
+ throw new Error('Task control requires a taskId');
38
+ assertCreds(creds, 'task control');
39
+ const res = await fetch(`${getTaskBaseUrl()}/${encodeURIComponent(taskId)}/${path}`, {
40
+ method, headers: buildHeaders(creds), body: JSON.stringify(body),
41
+ });
42
+ if (!res.ok)
43
+ throwApiError(res, await res.text().catch(() => ''), `Task control failed: ${res.status}`);
44
+ const result = extractWriteConfirm(await res.json());
45
+ if (!result)
46
+ throw new Error('Invalid task control response');
47
+ return result;
48
+ }
28
49
  function extractWriteConfirm(data) {
29
50
  if (!data || typeof data !== 'object')
30
51
  return null;
@@ -36,6 +57,7 @@ function extractWriteConfirm(data) {
36
57
  d['description'] === undefined) {
37
58
  return {
38
59
  ok: true,
60
+ ...(d['control'] ? { control: d['control'] } : {}),
39
61
  taskId: d['taskId'],
40
62
  ...(typeof d['agreementId'] === 'string' ? { agreementId: d['agreementId'] } : {}),
41
63
  state: d['state'],
@@ -454,6 +476,8 @@ export class TaskClient {
454
476
  // standalone functions still assert it per call.
455
477
  this.creds = { operatorKey, agentId };
456
478
  }
479
+ requestControl(taskId, request) { return requestTaskControl(taskId, request, this.creds); }
480
+ acknowledgeControl(taskId, requestId, supported = true) { return acknowledgeTaskControl(taskId, requestId, supported, this.creds); }
457
481
  createTask(data) { return createTask(data, this.creds); }
458
482
  getTask(taskId) { return getTask(taskId, this.creds); }
459
483
  updateTaskState(taskId, state, data) { return updateTaskState(taskId, state, data ?? {}, this.creds); }
@@ -20,6 +20,9 @@ export async function proposeUnified(input, creds) {
20
20
  lifecycle: terms.lifecycle,
21
21
  expiresAt: terms.expiresAt,
22
22
  maxExecutions: terms.maxExecutions,
23
+ conversationAllowance: terms.conversationAllowance,
24
+ conversationAllowanceScope: terms.conversationAllowanceScope,
25
+ interactionMode: terms.interactionMode,
23
26
  engagementKind: engagementKind,
24
27
  billing: terms.billing,
25
28
  audience,
package/dist/types.d.ts CHANGED
@@ -55,6 +55,8 @@ export interface PlanStep {
55
55
  result?: unknown;
56
56
  }
57
57
  export interface Task {
58
+ executorIsYou?: boolean;
59
+ control?: import('./http/TaskClient.js').TaskControl | null;
58
60
  taskId: string;
59
61
  description: string;
60
62
  /**
@@ -216,6 +218,10 @@ export interface Agreement {
216
218
  lifecycle?: string;
217
219
  expiresAt?: string;
218
220
  maxExecutions?: number;
221
+ conversationAllowance?: number | null;
222
+ conversationUsage?: {
223
+ messages: number;
224
+ };
219
225
  /**
220
226
  * What `money.price` MEANS: `total` is the whole engagement, settled once;
221
227
  * `per_task` is a rate charged as each task completes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.22.3",
3
+ "version": "0.23.0",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",