@ziggs-ai/api-client 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +10 -0
  2. package/dist/ConnectionManager.d.ts +21 -57
  3. package/dist/ConnectionManager.js +34 -163
  4. package/dist/capabilities/agreements.js +2 -2
  5. package/dist/capabilities/artifacts.js +11 -10
  6. package/dist/capabilities/connections.js +1 -1
  7. package/dist/capabilities/context.js +29 -11
  8. package/dist/capabilities/grants.d.ts +7 -0
  9. package/dist/capabilities/grants.js +9 -2
  10. package/dist/capabilities/index.d.ts +1 -1
  11. package/dist/capabilities/index.js +1 -1
  12. package/dist/capabilities/links.d.ts +0 -8
  13. package/dist/capabilities/links.js +3 -25
  14. package/dist/capabilities/types.d.ts +1 -0
  15. package/dist/capabilities/types.js +7 -2
  16. package/dist/http/AgreementClient.d.ts +67 -16
  17. package/dist/http/AgreementClient.js +161 -29
  18. package/dist/http/ArtifactsClient.d.ts +5 -1
  19. package/dist/http/ArtifactsClient.js +17 -5
  20. package/dist/http/ChatClient.js +5 -2
  21. package/dist/http/ConnectionsClient.js +4 -4
  22. package/dist/http/ContextDiscoveryClient.js +2 -1
  23. package/dist/http/ContextReadClient.d.ts +30 -3
  24. package/dist/http/ContextReadClient.js +63 -17
  25. package/dist/http/GrantsClient.js +2 -1
  26. package/dist/http/InboxClient.d.ts +32 -50
  27. package/dist/http/InboxClient.js +0 -39
  28. package/dist/http/MarketplaceClient.d.ts +0 -1
  29. package/dist/http/MarketplaceClient.js +8 -3
  30. package/dist/http/MessagesClient.js +3 -5
  31. package/dist/http/OrgsClient.js +3 -2
  32. package/dist/http/PaymentsClient.js +3 -5
  33. package/dist/http/TaskClient.d.ts +8 -0
  34. package/dist/http/TaskClient.js +3 -0
  35. package/dist/http/agreementFlows.d.ts +13 -5
  36. package/dist/http/agreementFlows.js +18 -24
  37. package/dist/http/index.d.ts +3 -3
  38. package/dist/http/index.js +1 -1
  39. package/dist/http/operatorHeaders.d.ts +7 -1
  40. package/dist/http/operatorHeaders.js +8 -1
  41. package/dist/index.d.ts +5 -4
  42. package/dist/index.js +4 -3
  43. package/dist/shared/apiError.d.ts +22 -1
  44. package/dist/shared/apiError.js +60 -3
  45. package/dist/shared/rateLimit.d.ts +12 -22
  46. package/dist/shared/rateLimit.js +18 -51
  47. package/dist/types.d.ts +70 -1
  48. package/dist/types.js +39 -1
  49. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { createAgreement, claimAgreement, getAgreement, proposeBroadcast, proposeDirectTo, } from './AgreementClient.js';
2
- import { claimOffer, publishOffer } from './MarketplaceClient.js';
1
+ import { createAgreement, claimAgreement, proposeBroadcast, proposeDirectTo, } from './AgreementClient.js';
2
+ import { publishOffer } from './MarketplaceClient.js';
3
3
  import { isBroadcastTarget, } from '../types.js';
4
4
  export async function proposeUnified(input, creds) {
5
5
  const { proposedTo, chatId, engagementKind, providerId, ...terms } = input;
@@ -56,30 +56,24 @@ export async function proposeUnified(input, creds) {
56
56
  return { agreement, shape: 'direct' };
57
57
  }
58
58
  /**
59
- * ZIG-1021 — one claim verb for any open broadcast. Fetches the agreement to
60
- * route: link invites and quests claim through POST /agreements/:id/claim;
61
- * standing offers (open payer side) through POST /marketplace/offers/claim.
59
+ * ZIG-1021 — one claim verb for any open broadcast: link invite, quest,
60
+ * hand-off, or standing offer.
61
+ *
62
+ * One request. This used to read the agreement first to decide which endpoint to
63
+ * post to, and `GET /agreements/:id` is party-scoped — a claimer is by definition
64
+ * not yet a party to the broadcast it is claiming, so the routing read 404'd and
65
+ * every standing offer in the store failed with "Agreement not found" before
66
+ * either claim endpoint was called (ZIG-1155). The backend routes it now, where
67
+ * the row is readable without being a party to it.
68
+ *
69
+ * `kind` arrives on the claim response — the route that did the routing reports
70
+ * which broadcast kind this turned out to be.
62
71
  */
63
72
  export async function claimOpenAgreement(agreementId, creds) {
64
73
  if (!agreementId)
65
74
  throw new Error('agreementId is required');
66
- const existing = await getAgreement(agreementId, creds);
67
- if (!existing)
68
- throw new Error(`Agreement not found: ${agreementId}`);
69
- if (existing.engagementKind === 'link') {
70
- const { agreement } = await claimAgreement(agreementId, creds);
71
- return { agreement, kind: 'link' };
72
- }
73
- if (isBroadcastTarget(existing.parties?.payer)) {
74
- // Seller-broadcast standing offer: the open side is the payer — you buy.
75
- const agreement = await claimOffer(agreementId, creds);
76
- return { agreement, kind: 'offer' };
77
- }
78
- const { agreement } = await claimAgreement(agreementId, creds);
79
- // ZIG-1059: a pinned provider inverts the quest reading — the publisher's
80
- // hired agent does the work and the claimer is who it is done FOR.
81
- return {
82
- agreement,
83
- kind: existing.providerPinned === true ? 'hand-off' : 'quest',
84
- };
75
+ const { agreement, kind } = await claimAgreement(agreementId, creds);
76
+ // A server that has not shipped the `kind` field yet still claims correctly;
77
+ // 'quest' is the shape the route has always handled.
78
+ return { agreement, kind: kind ?? 'quest' };
85
79
  }
@@ -7,8 +7,8 @@ export { MessagesClient } from './MessagesClient.js';
7
7
  export type { ListMessagesOptions, ListMessagesResult } from './MessagesClient.js';
8
8
  export { ArtifactsClient, artifactScopeForSession, AGREEMENT_LANE_PREFIX, } from './ArtifactsClient.js';
9
9
  export type { ArtifactVisibility, ListArtifactsOptions, ListArtifactsQuery, ListArtifactsResult, WriteArtifactInput, } from './ArtifactsClient.js';
10
- export { ContextReadClient } from './ContextReadClient.js';
11
- export type { ContextReadType, ContextReadQuery, ContextReadEnvelope, ContextSnapshotResult, } from './ContextReadClient.js';
10
+ export { ContextReadClient, CONTEXT_READ_TYPES, CONTEXT_READ_VIA, VIA_KINDS, parseVia, viaHint, } from './ContextReadClient.js';
11
+ export type { ContextReadType, ContextReadQuery, ContextReadEnvelope, ContextSnapshotResult, ViaKind, } from './ContextReadClient.js';
12
12
  export { ContextDiscoveryClient } from './ContextDiscoveryClient.js';
13
13
  export type { DiscoverableItem } from './ContextDiscoveryClient.js';
14
14
  export { GrantsClient } from './GrantsClient.js';
@@ -26,4 +26,4 @@ export type { MyOrg, OrgResolution } from './OrgsClient.js';
26
26
  export { AgentSearchClient } from './AgentSearchClient.js';
27
27
  export { TelemetryClient } from './TelemetryClient.js';
28
28
  export { InboxClient } from './InboxClient.js';
29
- export type { InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxOperatorAgentEntry, InboxOperatorEnvelope, } from './InboxClient.js';
29
+ export type { InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, } from './InboxClient.js';
@@ -7,7 +7,7 @@ export { MessagesClient } from './MessagesClient.js';
7
7
  export { ArtifactsClient,
8
8
  // ZIG-1032: agreement lanes are not chats — callers scope artifact writes with this.
9
9
  artifactScopeForSession, AGREEMENT_LANE_PREFIX, } from './ArtifactsClient.js';
10
- export { ContextReadClient } from './ContextReadClient.js';
10
+ export { ContextReadClient, CONTEXT_READ_TYPES, CONTEXT_READ_VIA, VIA_KINDS, parseVia, viaHint, } from './ContextReadClient.js';
11
11
  export { ContextDiscoveryClient } from './ContextDiscoveryClient.js';
12
12
  export { GrantsClient } from './GrantsClient.js';
13
13
  export { ContextGrantsClient } from './ContextGrantsClient.js';
@@ -3,4 +3,10 @@
3
3
  * an agentId is present — agent-scoped keys identify the agent themselves;
4
4
  * fleet keys must pass one (ZIG-642).
5
5
  */
6
- export declare function buildOperatorHeaders(operatorKey: string, agentId?: string, extra?: Record<string, string>): Record<string, string>;
6
+ export declare function buildOperatorHeaders(operatorKey: string, agentId?: string, extra?: Record<string, string>,
7
+ /**
8
+ * ZIG-1092 — the lane this call belongs to, sent as `X-Ziggs-Lane`. The
9
+ * backend narrows the wake's reach to the engagement's orgs; omitting it
10
+ * narrows to the agent's own org, so it can never widen reach.
11
+ */
12
+ laneId?: string): Record<string, string>;
@@ -3,10 +3,17 @@
3
3
  * an agentId is present — agent-scoped keys identify the agent themselves;
4
4
  * fleet keys must pass one (ZIG-642).
5
5
  */
6
- export function buildOperatorHeaders(operatorKey, agentId, extra) {
6
+ export function buildOperatorHeaders(operatorKey, agentId, extra,
7
+ /**
8
+ * ZIG-1092 — the lane this call belongs to, sent as `X-Ziggs-Lane`. The
9
+ * backend narrows the wake's reach to the engagement's orgs; omitting it
10
+ * narrows to the agent's own org, so it can never widen reach.
11
+ */
12
+ laneId) {
7
13
  return {
8
14
  Authorization: `Bearer ${operatorKey}`,
9
15
  ...(agentId ? { 'X-Agent-Id': agentId } : {}),
16
+ ...(laneId ? { 'X-Ziggs-Lane': laneId } : {}),
10
17
  ...extra,
11
18
  };
12
19
  }
package/dist/index.d.ts CHANGED
@@ -2,11 +2,12 @@ export * from './http/index.js';
2
2
  export * from './capabilities/index.js';
3
3
  export * from './relay/provisionRelayWorkers.js';
4
4
  export { ConnectionManager } from './ConnectionManager.js';
5
- export type { StartAgentOptions } from './ConnectionManager.js';
6
- export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, AGREEMENT_ENGAGEMENT_KIND, isValidContentType } from './types.js';
5
+ export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, isPersonaRef, isRoomPresentationRef, isOpaquePresentationRef, AGREEMENT_ENGAGEMENT_KIND, isValidContentType, } from './types.js';
6
+ export type { PrincipalPresentation } from './types.js';
7
7
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
8
8
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
9
9
  export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
10
- export { parseErrorMessage, throwApiError } from './shared/apiError.js';
11
- export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, ApiError, } from './types.js';
10
+ export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
11
+ export { ApiError } from './types.js';
12
+ export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, } from './types.js';
12
13
  export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, } from './http/AgreementClient.js';
package/dist/index.js CHANGED
@@ -2,12 +2,13 @@ export * from './http/index.js';
2
2
  export * from './capabilities/index.js';
3
3
  export * from './relay/provisionRelayWorkers.js';
4
4
  export { ConnectionManager } from './ConnectionManager.js';
5
- export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, AGREEMENT_ENGAGEMENT_KIND, isValidContentType } from './types.js';
5
+ export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, isPersonaRef, isRoomPresentationRef, isOpaquePresentationRef, AGREEMENT_ENGAGEMENT_KIND, isValidContentType, } from './types.js';
6
6
  export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
7
7
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
8
- // ZIG-1019: retry loops need the server's own wait, not a guess.
8
+ // ZIG-1019 / ZIG-1124: one ApiError shape; 429s carry the server's wait.
9
9
  export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
10
10
  // One reader for the one error shape the API answers in — exported so nothing has
11
11
  // to re-implement the field precedence (it was copy-pasted into six clients, and
12
12
  // half of them had drifted).
13
- export { parseErrorMessage, throwApiError } from './shared/apiError.js';
13
+ export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
14
+ export { ApiError } from './types.js';
@@ -16,7 +16,28 @@
16
16
  * so the same failure read differently depending on which client you called.
17
17
  */
18
18
  export declare function parseErrorMessage(responseBody: string, defaultMessage: string): string;
19
- /** Throw the parsed failure as an {@link ApiError}, preserving status and body. */
19
+ /** Machine code from `{ error, code }` when the filter (or thrower) supplied one. */
20
+ export declare function parseErrorCode(responseBody: string): string | null;
21
+ /**
22
+ * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
23
+ * one) and is accepted in both forms — delta-seconds or an HTTP date;
24
+ * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
25
+ * bad header cannot park a loop for an hour, floored at a second so a `0` does
26
+ * not reproduce the hot-retry it is meant to stop.
27
+ *
28
+ * Lives next to {@link throwApiError} so every client path (not only the poll
29
+ * surface) can honour the server's number (ZIG-1124).
30
+ */
31
+ export declare function parseRetryAfterMs(headers: {
32
+ get(name: string): string | null;
33
+ }): number | null;
34
+ /**
35
+ * Throw the parsed failure as an {@link ApiError} (or {@link RateLimitedError}
36
+ * for 429), preserving status, body, machine code, and Retry-After.
37
+ */
20
38
  export declare function throwApiError(response: {
21
39
  status: number;
40
+ headers?: {
41
+ get(name: string): string | null;
42
+ };
22
43
  }, responseBody: string, defaultMessage: string): never;
@@ -1,4 +1,4 @@
1
- import { ApiError } from '../types.js';
1
+ import { ApiError, RateLimitedError } from '../types.js';
2
2
  /**
3
3
  * Read the message out of a Ziggs error response.
4
4
  *
@@ -51,7 +51,64 @@ export function parseErrorMessage(responseBody, defaultMessage) {
51
51
  (preferMessage ? message : (error ?? message)) ??
52
52
  defaultMessage);
53
53
  }
54
- /** Throw the parsed failure as an {@link ApiError}, preserving status and body. */
54
+ /** Machine code from `{ error, code }` when the filter (or thrower) supplied one. */
55
+ export function parseErrorCode(responseBody) {
56
+ if (!responseBody)
57
+ return null;
58
+ try {
59
+ const parsed = JSON.parse(responseBody);
60
+ if (typeof parsed !== 'object' || parsed === null)
61
+ return null;
62
+ const code = parsed['code'];
63
+ return typeof code === 'string' && code ? code : null;
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ const MAX_RETRY_AFTER_MS = 120_000;
70
+ /**
71
+ * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
72
+ * one) and is accepted in both forms — delta-seconds or an HTTP date;
73
+ * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
74
+ * bad header cannot park a loop for an hour, floored at a second so a `0` does
75
+ * not reproduce the hot-retry it is meant to stop.
76
+ *
77
+ * Lives next to {@link throwApiError} so every client path (not only the poll
78
+ * surface) can honour the server's number (ZIG-1124).
79
+ */
80
+ export function parseRetryAfterMs(headers) {
81
+ const explicit = headers.get('retry-after');
82
+ if (explicit) {
83
+ const seconds = Number(explicit);
84
+ if (Number.isFinite(seconds))
85
+ return clampRetryMs(seconds * 1000);
86
+ const at = Date.parse(explicit);
87
+ if (!Number.isNaN(at))
88
+ return clampRetryMs(at - Date.now());
89
+ }
90
+ const reset = headers.get('ratelimit-reset');
91
+ if (reset) {
92
+ const seconds = Number(reset);
93
+ if (Number.isFinite(seconds))
94
+ return clampRetryMs(seconds * 1000);
95
+ }
96
+ return null;
97
+ }
98
+ function clampRetryMs(ms) {
99
+ if (!Number.isFinite(ms))
100
+ return 1_000;
101
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(1_000, Math.round(ms)));
102
+ }
103
+ /**
104
+ * Throw the parsed failure as an {@link ApiError} (or {@link RateLimitedError}
105
+ * for 429), preserving status, body, machine code, and Retry-After.
106
+ */
55
107
  export function throwApiError(response, responseBody, defaultMessage) {
56
- throw new ApiError(parseErrorMessage(responseBody, defaultMessage), response.status, responseBody);
108
+ const message = parseErrorMessage(responseBody, defaultMessage);
109
+ const code = parseErrorCode(responseBody);
110
+ if (response.status === 429) {
111
+ throw new RateLimitedError(message, response.headers ? parseRetryAfterMs(response.headers) : null, responseBody, code);
112
+ }
113
+ throw new ApiError(message, response.status, responseBody, code);
57
114
  }
@@ -1,3 +1,5 @@
1
+ import { ApiError, RateLimitedError } from '../types.js';
2
+ import { parseRetryAfterMs } from './apiError.js';
1
3
  /**
2
4
  * ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
3
5
  * deadline attached.
@@ -11,36 +13,24 @@
11
13
  * The server already says exactly how long to wait — `Retry-After`, or
12
14
  * `RateLimit-Reset` from the standard headers. These helpers carry that number
13
15
  * to whoever is doing the backing off.
16
+ *
17
+ * ZIG-1124 — {@link RateLimitedError} extends {@link ApiError}; non-429 poll
18
+ * failures are ApiError too (same shape as {@link throwApiError}).
14
19
  */
15
- /** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
16
- export declare class RateLimitedError extends Error {
17
- readonly status = 429;
18
- /** How long the server said to wait. Null when it said nothing. */
19
- readonly retryAfterMs: number | null;
20
- constructor(message: string, retryAfterMs: number | null);
21
- }
22
- /** True for the error above — survives structured clones and re-wraps. */
20
+ export { RateLimitedError, parseRetryAfterMs };
21
+ /** True for a 429 that carries a wait field — survives structured clones. */
23
22
  export declare function isRateLimited(err: unknown): err is {
24
23
  retryAfterMs: number | null;
24
+ status: number;
25
25
  };
26
26
  /**
27
- * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
28
- * one) and is accepted in both forms — delta-seconds or an HTTP date;
29
- * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
30
- * bad header cannot park a loop for an hour, floored at a second so a `0` does
31
- * not reproduce the hot-retry it is meant to stop.
32
- */
33
- export declare function parseRetryAfterMs(headers: {
34
- get(name: string): string | null;
35
- }): number | null;
36
- /**
37
- * Build the error for a failed poll-surface response: 429s carry the server's
38
- * wait, everything else stays an ordinary Error so existing handling is
39
- * unchanged.
27
+ * Build the error for a failed poll-surface response. Same shape as
28
+ * {@link throwApiError}: ApiError with status/body/code, or RateLimitedError
29
+ * when the server said 429.
40
30
  */
41
31
  export declare function pollSurfaceError(label: string, res: {
42
32
  status: number;
43
33
  headers: {
44
34
  get(name: string): string | null;
45
35
  };
46
- }, body: string): Error;
36
+ }, body: string): ApiError;
@@ -1,3 +1,5 @@
1
+ import { ApiError, RateLimitedError } from '../types.js';
2
+ import { parseErrorCode, parseErrorMessage, parseRetryAfterMs, } from './apiError.js';
1
3
  /**
2
4
  * ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
3
5
  * deadline attached.
@@ -11,63 +13,28 @@
11
13
  * The server already says exactly how long to wait — `Retry-After`, or
12
14
  * `RateLimit-Reset` from the standard headers. These helpers carry that number
13
15
  * to whoever is doing the backing off.
16
+ *
17
+ * ZIG-1124 — {@link RateLimitedError} extends {@link ApiError}; non-429 poll
18
+ * failures are ApiError too (same shape as {@link throwApiError}).
14
19
  */
15
- /** Thrown for HTTP 429 so a retry loop can wait the server's number, not its own. */
16
- export class RateLimitedError extends Error {
17
- status = 429;
18
- /** How long the server said to wait. Null when it said nothing. */
19
- retryAfterMs;
20
- constructor(message, retryAfterMs) {
21
- super(message);
22
- this.name = 'RateLimitedError';
23
- this.retryAfterMs = retryAfterMs;
24
- }
25
- }
26
- /** True for the error above — survives structured clones and re-wraps. */
20
+ export { RateLimitedError, parseRetryAfterMs };
21
+ /** True for a 429 that carries a wait field — survives structured clones. */
27
22
  export function isRateLimited(err) {
28
23
  return (!!err &&
29
24
  typeof err === 'object' &&
30
- err.status === 429);
31
- }
32
- const MAX_RETRY_AFTER_MS = 120_000;
33
- /**
34
- * Read the wait out of a 429 response. `Retry-After` wins (it is the explicit
35
- * one) and is accepted in both forms — delta-seconds or an HTTP date;
36
- * `RateLimit-Reset` is the standard-headers fallback, in seconds. Capped so a
37
- * bad header cannot park a loop for an hour, floored at a second so a `0` does
38
- * not reproduce the hot-retry it is meant to stop.
39
- */
40
- export function parseRetryAfterMs(headers) {
41
- const explicit = headers.get('retry-after');
42
- if (explicit) {
43
- const seconds = Number(explicit);
44
- if (Number.isFinite(seconds))
45
- return clampRetryMs(seconds * 1000);
46
- const at = Date.parse(explicit);
47
- if (!Number.isNaN(at))
48
- return clampRetryMs(at - Date.now());
49
- }
50
- const reset = headers.get('ratelimit-reset');
51
- if (reset) {
52
- const seconds = Number(reset);
53
- if (Number.isFinite(seconds))
54
- return clampRetryMs(seconds * 1000);
55
- }
56
- return null;
57
- }
58
- function clampRetryMs(ms) {
59
- if (!Number.isFinite(ms))
60
- return 1_000;
61
- return Math.min(MAX_RETRY_AFTER_MS, Math.max(1_000, Math.round(ms)));
25
+ err.status === 429 &&
26
+ 'retryAfterMs' in err);
62
27
  }
63
28
  /**
64
- * Build the error for a failed poll-surface response: 429s carry the server's
65
- * wait, everything else stays an ordinary Error so existing handling is
66
- * unchanged.
29
+ * Build the error for a failed poll-surface response. Same shape as
30
+ * {@link throwApiError}: ApiError with status/body/code, or RateLimitedError
31
+ * when the server said 429.
67
32
  */
68
33
  export function pollSurfaceError(label, res, body) {
69
- const message = `${label} ${res.status} ${body.slice(0, 200)}`;
70
- if (res.status !== 429)
71
- return new Error(message);
72
- return new RateLimitedError(message, parseRetryAfterMs(res.headers));
34
+ const message = parseErrorMessage(body, `${label} failed: ${res.status}`);
35
+ const code = parseErrorCode(body);
36
+ if (res.status === 429) {
37
+ return new RateLimitedError(message, parseRetryAfterMs(res.headers), body, code);
38
+ }
39
+ return new ApiError(message, res.status, body, code);
73
40
  }
package/dist/types.d.ts CHANGED
@@ -1,11 +1,40 @@
1
+ /**
2
+ * One failure shape for every HTTP client path (ZIG-1124).
3
+ *
4
+ * `status` and optional machine `code` come from the response; `body` is the
5
+ * raw text so callers can re-parse without scraping concatenated messages.
6
+ * A 429 is {@link RateLimitedError}, which adds the server's wait.
7
+ */
1
8
  export declare class ApiError extends Error {
2
9
  readonly status: number;
3
10
  readonly body?: string | undefined;
4
- constructor(message: string, status: number, body?: string | undefined);
11
+ readonly code?: string | null | undefined;
12
+ constructor(message: string, status: number, body?: string | undefined, code?: string | null | undefined);
13
+ }
14
+ /** HTTP 429 — same fields as {@link ApiError}, plus the server's Retry-After. */
15
+ export declare class RateLimitedError extends ApiError {
16
+ /** How long the server said to wait. Null when it said nothing. */
17
+ readonly retryAfterMs: number | null;
18
+ constructor(message: string, retryAfterMs: number | null, body?: string, code?: string | null);
5
19
  }
6
20
  export interface Creds {
7
21
  operatorKey: string;
8
22
  agentId: string;
23
+ /**
24
+ * ZIG-1092 — the lane (chat id, or `agrn-<agreementId>`) this call is being
25
+ * made from. Sent as `X-Ziggs-Lane` so the backend can fence the wake to the
26
+ * engagement the agent is actually acting inside.
27
+ *
28
+ * The operator key says WHO is calling; this says ON WHOSE BEHALF, RIGHT NOW.
29
+ * Without it an agent serving several customers carries its full authority
30
+ * into every call, and one tool call reaches another customer's work.
31
+ *
32
+ * Optional, and omitting it can only narrow what comes back (the backend
33
+ * falls back to the agent's own org) — never widen it. Nothing here is
34
+ * trusted: the server re-derives the party orgs itself and refuses a lane the
35
+ * agent is not in.
36
+ */
37
+ laneId?: string;
9
38
  }
10
39
  export type TaskState = 'active' | 'proposal' | 'completed' | 'failed' | 'cancelled' | 'ledger_open';
11
40
  export type PlanStepStatus = 'pending' | 'in_progress' | 'completed' | 'skipped';
@@ -159,6 +188,36 @@ export type BroadcastAudience = typeof OPEN_AGREEMENT_TARGET | typeof ORG_AGREEM
159
188
  export declare const BROADCAST_TARGETS: readonly ["everyone", "org"];
160
189
  /** True when `id` is a broadcast sentinel ('everyone' | 'org') rather than a concrete principal id. */
161
190
  export declare function isBroadcastTarget(id: string | null | undefined): boolean;
191
+ /**
192
+ * Persona face id (`psn_*`). Non-addressable — never use for agent lookup,
193
+ * wake, or payment parties (ZIG-1137).
194
+ */
195
+ export declare function isPersonaRef(id: string | null | undefined): boolean;
196
+ /**
197
+ * Room presentation binding id (`rpb_*`). Opaque to account lookup / wake /
198
+ * pay. Chat sends may echo it as `receiverId` — the backend resolves it in-room
199
+ * (ZIG-1137).
200
+ */
201
+ export declare function isRoomPresentationRef(id: string | null | undefined): boolean;
202
+ /** Either opaque presentation ref (`psn_*` | `rpb_*`). */
203
+ export declare function isOpaquePresentationRef(id: string | null | undefined): boolean;
204
+ /** Display + entitlement face for a principal on the message/roster wire. */
205
+ export interface PrincipalPresentation {
206
+ /** Public, non-addressable reference (`psn_*` or `rpb_*`). */
207
+ ref?: string;
208
+ persona: {
209
+ id: string;
210
+ name: string;
211
+ image?: string | null;
212
+ revision?: number;
213
+ };
214
+ mode: 'persona' | 'chain';
215
+ /** Present only when the viewer is entitled to resolve the subject. */
216
+ subject?: {
217
+ id: string;
218
+ type: 'user' | 'agent';
219
+ };
220
+ }
162
221
  export interface MessageMetadata {
163
222
  chatId: string;
164
223
  /** Stable id for dedup across push + inbox catch-up (ZIG-454). */
@@ -167,13 +226,23 @@ export interface MessageMetadata {
167
226
  sender: {
168
227
  id: string;
169
228
  type?: string;
229
+ presentation?: PrincipalPresentation | null;
230
+ /** Absent when the viewer is masked (persona layer). */
231
+ underAgreementId?: string | null;
232
+ presentedAs?: string | null;
170
233
  };
171
234
  senderId: string;
172
235
  senderType?: string;
173
236
  receiver?: {
174
237
  id: string;
238
+ presentation?: PrincipalPresentation | null;
175
239
  } | null;
176
240
  receiverId?: string | null;
241
+ /**
242
+ * Message-level presentation stamp when the backend attaches one
243
+ * (same shape as sender.presentation). Prefer sender.presentation.
244
+ */
245
+ presentation?: PrincipalPresentation | null;
177
246
  entryType?: string;
178
247
  content_type?: string;
179
248
  taskId?: string | null;
package/dist/types.js CHANGED
@@ -1,13 +1,32 @@
1
+ /**
2
+ * One failure shape for every HTTP client path (ZIG-1124).
3
+ *
4
+ * `status` and optional machine `code` come from the response; `body` is the
5
+ * raw text so callers can re-parse without scraping concatenated messages.
6
+ * A 429 is {@link RateLimitedError}, which adds the server's wait.
7
+ */
1
8
  export class ApiError extends Error {
2
9
  status;
3
10
  body;
4
- constructor(message, status, body) {
11
+ code;
12
+ constructor(message, status, body, code) {
5
13
  super(message);
6
14
  this.status = status;
7
15
  this.body = body;
16
+ this.code = code;
8
17
  this.name = 'ApiError';
9
18
  }
10
19
  }
20
+ /** HTTP 429 — same fields as {@link ApiError}, plus the server's Retry-After. */
21
+ export class RateLimitedError extends ApiError {
22
+ /** How long the server said to wait. Null when it said nothing. */
23
+ retryAfterMs;
24
+ constructor(message, retryAfterMs, body, code) {
25
+ super(message, 429, body, code);
26
+ this.name = 'RateLimitedError';
27
+ this.retryAfterMs = retryAfterMs;
28
+ }
29
+ }
11
30
  /** ⚠️ SYNC: backend src/agreements/agreements.constants.ts AGREEMENT_ENGAGEMENT_KIND */
12
31
  export const AGREEMENT_ENGAGEMENT_KIND = {
13
32
  HIRE: 'hire',
@@ -58,3 +77,22 @@ export const BROADCAST_TARGETS = [OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET];
58
77
  export function isBroadcastTarget(id) {
59
78
  return id === OPEN_AGREEMENT_TARGET || id === ORG_AGREEMENT_TARGET;
60
79
  }
80
+ /**
81
+ * Persona face id (`psn_*`). Non-addressable — never use for agent lookup,
82
+ * wake, or payment parties (ZIG-1137).
83
+ */
84
+ export function isPersonaRef(id) {
85
+ return typeof id === 'string' && id.startsWith('psn_');
86
+ }
87
+ /**
88
+ * Room presentation binding id (`rpb_*`). Opaque to account lookup / wake /
89
+ * pay. Chat sends may echo it as `receiverId` — the backend resolves it in-room
90
+ * (ZIG-1137).
91
+ */
92
+ export function isRoomPresentationRef(id) {
93
+ return typeof id === 'string' && id.startsWith('rpb_');
94
+ }
95
+ /** Either opaque presentation ref (`psn_*` | `rpb_*`). */
96
+ export function isOpaquePresentationRef(id) {
97
+ return isPersonaRef(id) || isRoomPresentationRef(id);
98
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/api-client",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "HTTP and WebSocket client for the Ziggs backend API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",