@ziggs-ai/api-client 0.33.0 → 0.34.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.
@@ -171,7 +171,7 @@ export const agreementBuyCapability = {
171
171
  chatId: CHAT_PARAM,
172
172
  ...TERMS_PARAMS,
173
173
  mandateAgreementId: MANDATE_PARAM,
174
- parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
174
+ parentAgreementId: { type: 'string', description: 'The job this work is part of; ending that job ends this work. Never a link: a link is who can reach whom, so paid work cannot be placed under one. This placement does not grant spending consent.' },
175
175
  },
176
176
  needsAgentId: true,
177
177
  handler: async (args, env) => {
@@ -203,7 +203,7 @@ export const agreementBidCapability = {
203
203
  chatId: CHAT_PARAM,
204
204
  ...TERMS_PARAMS,
205
205
  mandateAgreementId: MANDATE_PARAM,
206
- parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
206
+ parentAgreementId: { type: 'string', description: 'The job this work is part of; ending that job ends this work. Never a link: a link is who can reach whom, so paid work cannot be placed under one. This placement does not grant spending consent.' },
207
207
  },
208
208
  needsAgentId: true,
209
209
  handler: async (args, env) => {
@@ -243,7 +243,7 @@ export const agreementBrokerCapability = {
243
243
  chatId: CHAT_PARAM,
244
244
  ...TERMS_PARAMS,
245
245
  mandateAgreementId: MANDATE_PARAM,
246
- parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
246
+ parentAgreementId: { type: 'string', description: 'The job this work is part of; ending that job ends this work. Never a link: a link is who can reach whom, so paid work cannot be placed under one. This placement does not grant spending consent.' },
247
247
  },
248
248
  needsAgentId: true,
249
249
  handler: async (args, env) => {
@@ -274,7 +274,7 @@ export const agreementRequestCapability = {
274
274
  ...TERMS_PARAMS,
275
275
  match: MATCH_PARAM,
276
276
  mandateAgreementId: MANDATE_PARAM,
277
- parentAgreementId: { type: 'string', description: 'Governing agreement, including a link or negotiation agreement. This placement does not grant spending consent.' },
277
+ parentAgreementId: { type: 'string', description: 'The job this work is part of; ending that job ends this work. Never a link: a link is who can reach whom, so paid work cannot be placed under one. This placement does not grant spending consent.' },
278
278
  },
279
279
  needsAgentId: true,
280
280
  handler: async (args, env) => {
@@ -105,6 +105,8 @@ export declare class ContextReadClient {
105
105
  * intended. Send it on every read made while acting on a wake.
106
106
  */
107
107
  constructor(operatorKey: string, agentId?: string, baseUrl?: string, laneId?: string);
108
+ /** The same reader acting on one wake's lane. */
109
+ withLane(laneId: string | undefined): ContextReadClient;
108
110
  read<T = unknown>(type: ContextReadType, query: ContextReadQuery): Promise<ContextReadEnvelope<T>>;
109
111
  /**
110
112
  * Aggregated chat snapshot — `GET /context/snapshot?via=chat:<id>`. The
@@ -74,6 +74,12 @@ export class ContextReadClient {
74
74
  this.baseUrl = baseUrl || getBackendUrl();
75
75
  this.laneId = laneId;
76
76
  }
77
+ /** The same reader acting on one wake's lane. */
78
+ withLane(laneId) {
79
+ if (!laneId || laneId === this.laneId)
80
+ return this;
81
+ return new ContextReadClient(this.operatorKey, this.agentId, this.baseUrl, laneId);
82
+ }
77
83
  async read(type, query) {
78
84
  if (!query.via?.trim()) {
79
85
  throw new Error('ContextReadClient.read: via is required');
@@ -31,9 +31,11 @@ export declare function parseErrorCode(responseBody: string): string | null;
31
31
  export declare function parseRetryAfterMs(headers: {
32
32
  get(name: string): string | null;
33
33
  }): number | null;
34
+ export declare function isCoverageRefusal(code: string | null | undefined): boolean;
34
35
  /**
35
36
  * Throw the parsed failure as an {@link ApiError} (or {@link RateLimitedError}
36
- * for 429), preserving status, body, machine code, and Retry-After.
37
+ * for a 429 that is not a coverage refusal), preserving status, body, machine
38
+ * code, and Retry-After.
37
39
  */
38
40
  export declare function throwApiError(response: {
39
41
  status: number;
@@ -100,14 +100,34 @@ function clampRetryMs(ms) {
100
100
  return 1_000;
101
101
  return Math.min(MAX_RETRY_AFTER_MS, Math.max(1_000, Math.round(ms)));
102
102
  }
103
+ /**
104
+ * Coverage refusals name themselves. A 429 with one of these codes is not a
105
+ * wait-and-retry: the writer has to hire, or someone has to set the agreement
106
+ * up. The backend answers them as 403; this keeps a stale 429 from becoming a
107
+ * rate limit too.
108
+ */
109
+ const COVERAGE_REFUSALS = new Set([
110
+ 'CONVERSATION_ALLOWANCE_EXHAUSTED',
111
+ 'CONTACT_POLICY_REFUSED',
112
+ 'LISTING_PAUSED',
113
+ 'CONVERSATION_COVERAGE_REQUIRED',
114
+ 'CONVERSATION_COVERAGE_ENDED',
115
+ 'CONVERSATION_NOT_FUNDED',
116
+ 'CONVERSATION_SPONSORSHIP_REQUIRED',
117
+ 'TASK_ONLY_AGENT',
118
+ ]);
119
+ export function isCoverageRefusal(code) {
120
+ return !!code && COVERAGE_REFUSALS.has(code);
121
+ }
103
122
  /**
104
123
  * Throw the parsed failure as an {@link ApiError} (or {@link RateLimitedError}
105
- * for 429), preserving status, body, machine code, and Retry-After.
124
+ * for a 429 that is not a coverage refusal), preserving status, body, machine
125
+ * code, and Retry-After.
106
126
  */
107
127
  export function throwApiError(response, responseBody, defaultMessage) {
108
128
  const message = parseErrorMessage(responseBody, defaultMessage);
109
129
  const code = parseErrorCode(responseBody);
110
- if (response.status === 429) {
130
+ if (response.status === 429 && !isCoverageRefusal(code)) {
111
131
  throw new RateLimitedError(message, response.headers ? parseRetryAfterMs(response.headers) : null, responseBody, code);
112
132
  }
113
133
  throw new ApiError(message, response.status, responseBody, code);
@@ -1,5 +1,5 @@
1
1
  import { ApiError, RateLimitedError } from '../types.js';
2
- import { parseErrorCode, parseErrorMessage, parseRetryAfterMs, } from './apiError.js';
2
+ import { isCoverageRefusal, parseErrorCode, parseErrorMessage, parseRetryAfterMs, } from './apiError.js';
3
3
  /**
4
4
  * a 429 is not a generic failure, it is an instruction with a
5
5
  * deadline attached.
@@ -33,7 +33,7 @@ export function isRateLimited(err) {
33
33
  export function pollSurfaceError(label, res, body) {
34
34
  const message = parseErrorMessage(body, `${label} failed: ${res.status}`);
35
35
  const code = parseErrorCode(body);
36
- if (res.status === 429) {
36
+ if (res.status === 429 && !isCoverageRefusal(code)) {
37
37
  return new RateLimitedError(message, parseRetryAfterMs(res.headers), body, code);
38
38
  }
39
39
  return new ApiError(message, res.status, body, code);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",