@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.
- package/README.md +10 -0
- package/dist/ConnectionManager.d.ts +21 -57
- package/dist/ConnectionManager.js +34 -163
- package/dist/capabilities/agreements.js +2 -2
- package/dist/capabilities/artifacts.js +11 -10
- package/dist/capabilities/connections.js +1 -1
- package/dist/capabilities/context.js +29 -11
- package/dist/capabilities/grants.d.ts +7 -0
- package/dist/capabilities/grants.js +9 -2
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/capabilities/index.js +1 -1
- package/dist/capabilities/links.d.ts +0 -8
- package/dist/capabilities/links.js +3 -25
- package/dist/capabilities/types.d.ts +1 -0
- package/dist/capabilities/types.js +7 -2
- package/dist/http/AgreementClient.d.ts +67 -16
- package/dist/http/AgreementClient.js +161 -29
- package/dist/http/ArtifactsClient.d.ts +5 -1
- package/dist/http/ArtifactsClient.js +17 -5
- package/dist/http/ChatClient.js +5 -2
- package/dist/http/ConnectionsClient.js +4 -4
- package/dist/http/ContextDiscoveryClient.js +2 -1
- package/dist/http/ContextReadClient.d.ts +30 -3
- package/dist/http/ContextReadClient.js +63 -17
- package/dist/http/GrantsClient.js +2 -1
- package/dist/http/InboxClient.d.ts +32 -50
- package/dist/http/InboxClient.js +0 -39
- package/dist/http/MarketplaceClient.d.ts +0 -1
- package/dist/http/MarketplaceClient.js +8 -3
- package/dist/http/MessagesClient.js +3 -5
- package/dist/http/OrgsClient.js +3 -2
- package/dist/http/PaymentsClient.js +3 -5
- package/dist/http/TaskClient.d.ts +8 -0
- package/dist/http/TaskClient.js +3 -0
- package/dist/http/agreementFlows.d.ts +13 -5
- package/dist/http/agreementFlows.js +18 -24
- package/dist/http/index.d.ts +3 -3
- package/dist/http/index.js +1 -1
- package/dist/http/operatorHeaders.d.ts +7 -1
- package/dist/http/operatorHeaders.js +8 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/shared/apiError.d.ts +22 -1
- package/dist/shared/apiError.js +60 -3
- package/dist/shared/rateLimit.d.ts +12 -22
- package/dist/shared/rateLimit.js +18 -51
- package/dist/types.d.ts +70 -1
- package/dist/types.js +39 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createAgreement, claimAgreement,
|
|
2
|
-
import {
|
|
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
|
|
60
|
-
*
|
|
61
|
-
*
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
}
|
package/dist/http/index.d.ts
CHANGED
|
@@ -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,
|
|
29
|
+
export type { InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, } from './InboxClient.js';
|
package/dist/http/index.js
CHANGED
|
@@ -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
|
|
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
|
|
6
|
-
export {
|
|
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
|
|
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:
|
|
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
|
-
/**
|
|
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;
|
package/dist/shared/apiError.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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):
|
|
36
|
+
}, body: string): ApiError;
|
package/dist/shared/rateLimit.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
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
|
|
65
|
-
*
|
|
66
|
-
*
|
|
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}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|