@ziggs-ai/api-client 0.4.0 → 0.6.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.
- package/dist/ConnectionManager.d.ts +13 -1
- package/dist/ConnectionManager.js +63 -9
- package/dist/capabilities/agreements.d.ts +8 -0
- package/dist/capabilities/agreements.js +45 -0
- package/dist/capabilities/artifacts.js +19 -11
- package/dist/capabilities/chat.d.ts +3 -3
- package/dist/capabilities/chat.js +8 -8
- package/dist/capabilities/connections.js +8 -8
- package/dist/capabilities/context.js +11 -11
- package/dist/capabilities/discovery.js +4 -4
- package/dist/capabilities/grants.js +3 -3
- package/dist/capabilities/index.d.ts +3 -1
- package/dist/capabilities/index.js +3 -1
- package/dist/capabilities/links.d.ts +0 -3
- package/dist/capabilities/links.js +64 -102
- package/dist/capabilities/marketplace.d.ts +8 -0
- package/dist/capabilities/marketplace.js +55 -0
- package/dist/capabilities/payments.js +2 -2
- package/dist/http/AgreementClient.d.ts +8 -0
- package/dist/http/AgreementClient.js +6 -12
- package/dist/http/ArtifactsClient.d.ts +2 -2
- package/dist/http/ArtifactsClient.js +2 -2
- package/dist/http/ChatClient.js +1 -1
- package/dist/http/ContextReadClient.js +16 -1
- package/dist/http/InboxClient.d.ts +95 -65
- package/dist/http/InboxClient.js +46 -17
- package/dist/http/MarketplaceClient.d.ts +8 -0
- package/dist/http/agreementFlows.d.ts +34 -0
- package/dist/http/agreementFlows.js +80 -0
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/shared/rateLimit.d.ts +46 -0
- package/dist/shared/rateLimit.js +73 -0
- package/dist/types.d.ts +12 -1
- package/package.json +1 -1
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
|
-
export type InboxScopeKind = 'chat' | 'agreement' | 'org';
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* One thing addressed to this agent. A reference, never content — following it
|
|
4
|
+
* (a chat read, a task read) is where this agent's grants are enforced.
|
|
6
5
|
*/
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
export interface InboxDeliveryRef {
|
|
7
|
+
/** 'message' | 'artifact' | 'task-state' | 'agreement'. */
|
|
8
|
+
kind: string;
|
|
9
|
+
resourceId: string;
|
|
10
|
+
chatId: string | null;
|
|
11
|
+
agreementId: string | null;
|
|
12
|
+
taskId: string | null;
|
|
13
|
+
/** Who wrote it. Never this agent — you are not woken by your own writes. */
|
|
14
|
+
actorId: string | null;
|
|
15
|
+
ts: string;
|
|
12
16
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
newMessages: number;
|
|
19
|
-
newArtifacts: number;
|
|
20
|
-
latestAt: string | null;
|
|
21
|
-
since: string;
|
|
22
|
-
/** Per-chat breakdown for org / agreement scopes (ZIG-543). Absent for a chat scope. */
|
|
23
|
-
chats?: InboxScopeChat[];
|
|
24
|
-
/** Chats with news beyond the per-scope cap, not listed in `chats`. */
|
|
25
|
-
truncatedChats?: number;
|
|
17
|
+
/** Message/artifact deliveries folded by chat, so you can open chats directly. */
|
|
18
|
+
export interface InboxChatNews {
|
|
19
|
+
chatId: string;
|
|
20
|
+
count: number;
|
|
21
|
+
latestAt: string;
|
|
26
22
|
}
|
|
27
23
|
export interface InboxProposalRef {
|
|
28
24
|
agreementId: string;
|
|
@@ -35,54 +31,59 @@ export interface InboxConnectionRequestRef {
|
|
|
35
31
|
message: string | null;
|
|
36
32
|
requestedAt: string | null;
|
|
37
33
|
}
|
|
38
|
-
/** Agent asking its principal to connect an MCP server + grant tools (ZIG-686). */
|
|
39
|
-
export interface InboxMcpServerRequestRef {
|
|
40
|
-
requestId: string;
|
|
41
|
-
requesterAgentId: string;
|
|
42
|
-
serverUrl: string;
|
|
43
|
-
tools: string[];
|
|
44
|
-
reason: string | null;
|
|
45
|
-
requestedAt: string | null;
|
|
46
|
-
}
|
|
47
34
|
/** Pull-only MCP: prompt the human when proposals need a decision (ZIG-482 / ZIG-481). */
|
|
48
35
|
export interface InboxHumanAttention {
|
|
49
36
|
required: true;
|
|
50
|
-
reason: 'proposals_awaiting_me' | 'connection_requests_awaiting_me' | '
|
|
37
|
+
reason: 'proposals_awaiting_me' | 'connection_requests_awaiting_me' | 'multiple';
|
|
51
38
|
proposalCount: number;
|
|
52
39
|
truncatedProposals: number;
|
|
53
40
|
connectionRequestCount: number;
|
|
54
41
|
truncatedConnectionRequests: number;
|
|
55
|
-
mcpServerRequestCount: number;
|
|
56
|
-
truncatedMcpServerRequests: number;
|
|
57
42
|
promptUser: string;
|
|
58
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* An open task assigned to this agent (ZIG-973). References only — read the
|
|
46
|
+
* task for its description/plan/inputs.
|
|
47
|
+
*
|
|
48
|
+
* Tasks ride their own channel because assignment IS their delivery: before
|
|
49
|
+
* this, a task wake was a synthetic chat row, so work under a chat-less
|
|
50
|
+
* agreement (or self-assigned) reached nobody.
|
|
51
|
+
*/
|
|
52
|
+
export interface InboxTaskRef {
|
|
53
|
+
taskId: string;
|
|
54
|
+
agreementId: string | null;
|
|
55
|
+
title: string;
|
|
56
|
+
state: string;
|
|
57
|
+
updatedAt: string | null;
|
|
58
|
+
}
|
|
59
59
|
export interface InboxEnvelope {
|
|
60
60
|
asOf: string;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Unacked deliveries addressed to this agent, newest first. This IS the
|
|
63
|
+
* inbox — read straight out of the delivery log, not derived from grants.
|
|
64
|
+
*/
|
|
65
|
+
deliveries: InboxDeliveryRef[];
|
|
66
|
+
/** True when there was more than one envelope's worth; the rest stay unacked. */
|
|
67
|
+
deliveriesCapped: boolean;
|
|
68
|
+
/** The chat-bearing deliveries above, folded by chat. */
|
|
69
|
+
chats: InboxChatNews[];
|
|
70
|
+
/**
|
|
71
|
+
* Pass to `ack()` after acting. Null when there is nothing to ack. Ack after
|
|
72
|
+
* acting, not after reading: a crash in between redelivers.
|
|
73
|
+
*/
|
|
74
|
+
ackTo: string | null;
|
|
75
|
+
/** Open tasks assigned to this agent — the work channel (ZIG-973). */
|
|
76
|
+
tasksAwaitingMe: InboxTaskRef[];
|
|
77
|
+
truncatedTasks: number;
|
|
64
78
|
proposalsAwaitingMe: InboxProposalRef[];
|
|
65
79
|
truncatedProposals: number;
|
|
66
80
|
connectionRequestsAwaitingMe: InboxConnectionRequestRef[];
|
|
67
81
|
truncatedConnectionRequests: number;
|
|
68
|
-
/** Agent requests to connect an MCP server awaiting the user (ZIG-686). */
|
|
69
|
-
mcpServerRequestsAwaitingMe: InboxMcpServerRequestRef[];
|
|
70
|
-
truncatedMcpServerRequests: number;
|
|
71
82
|
humanAttention?: InboxHumanAttention;
|
|
72
83
|
}
|
|
73
|
-
export interface InboxAck {
|
|
74
|
-
kind: InboxScopeKind;
|
|
75
|
-
id: string;
|
|
76
|
-
upTo: string;
|
|
77
|
-
}
|
|
78
84
|
export interface InboxAckResult {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
kind: InboxScopeKind;
|
|
82
|
-
id: string;
|
|
83
|
-
};
|
|
84
|
-
ackedUpTo: string;
|
|
85
|
-
}>;
|
|
85
|
+
/** Where the watermark now sits. Monotonic — a rewind is a no-op, not an error. */
|
|
86
|
+
ackedUpTo: string | null;
|
|
86
87
|
}
|
|
87
88
|
/**
|
|
88
89
|
* Long-poll option shared by the inbox reads. The server holds the request up
|
|
@@ -93,27 +94,50 @@ export interface InboxAckResult {
|
|
|
93
94
|
*/
|
|
94
95
|
export interface InboxReadOptions {
|
|
95
96
|
waitSeconds?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Operator read only (ZIG-965): restrict the sweep to this roster. The
|
|
99
|
+
* server intersects it with the agents the key owner runs — it narrows,
|
|
100
|
+
* never widens. A launcher hosting a subset should always pass the agents
|
|
101
|
+
* it actually registered, or it pays for a sweep of the owner's whole
|
|
102
|
+
* seeded fleet.
|
|
103
|
+
*/
|
|
104
|
+
agents?: string[];
|
|
96
105
|
}
|
|
97
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* One agent's line in the operator sweep: enough to decide whether to start a
|
|
108
|
+
* host, and nothing more.
|
|
109
|
+
*
|
|
110
|
+
* Deliberately NOT that agent's envelope. The launcher only chooses who to
|
|
111
|
+
* run; the host it starts reads its own inbox on its own credentials a moment
|
|
112
|
+
* later, so building N envelopes here was work thrown away.
|
|
113
|
+
*/
|
|
98
114
|
export interface InboxOperatorAgentEntry {
|
|
99
115
|
agentId: string;
|
|
100
|
-
/**
|
|
101
|
-
|
|
116
|
+
/** Newest delivery addressed to this agent, or null if it never had one. */
|
|
117
|
+
deliveredUpTo: string | null;
|
|
118
|
+
/** How far it has acked. Mail exists when `deliveredUpTo > ackedUpTo`. */
|
|
119
|
+
ackedUpTo: string | null;
|
|
120
|
+
/**
|
|
121
|
+
* True when this agent holds open assigned work. Independent of the
|
|
122
|
+
* watermark: a host that died mid-task already acked past the task's
|
|
123
|
+
* delivery, and the open task is the only durable trace it needs restarting.
|
|
124
|
+
*/
|
|
125
|
+
hasOpenTasks: boolean;
|
|
102
126
|
}
|
|
103
|
-
/** GET /inbox/operator:
|
|
127
|
+
/** GET /inbox/operator: which of the key owner's agents have mail or open work. */
|
|
104
128
|
export interface InboxOperatorEnvelope {
|
|
105
129
|
asOf: string;
|
|
106
|
-
/** Agents with
|
|
130
|
+
/** Agents with mail or open work. */
|
|
107
131
|
agents: InboxOperatorAgentEntry[];
|
|
108
|
-
/** Agents examined
|
|
132
|
+
/** Agents examined that had neither. */
|
|
109
133
|
idleAgents: number;
|
|
110
134
|
/** Owned agents beyond the server's per-pass cap — not examined. */
|
|
111
135
|
truncatedAgents: number;
|
|
112
136
|
}
|
|
113
137
|
/**
|
|
114
|
-
* The doorbell, not the door (ZIG-434): references
|
|
115
|
-
*
|
|
116
|
-
* Wraps `GET /inbox` and `POST /inbox/ack`.
|
|
138
|
+
* The doorbell, not the door (ZIG-434): references addressed to this agent
|
|
139
|
+
* since its last ack — never content. Flow: inbox → read → act → ack
|
|
140
|
+
* (ZIG-446). Wraps `GET /inbox` and `POST /inbox/ack`.
|
|
117
141
|
*/
|
|
118
142
|
export declare class InboxClient {
|
|
119
143
|
private readonly operatorKey;
|
|
@@ -128,11 +152,17 @@ export declare class InboxClient {
|
|
|
128
152
|
private inboxUrl;
|
|
129
153
|
getInbox(opts?: InboxReadOptions): Promise<InboxEnvelope>;
|
|
130
154
|
/**
|
|
131
|
-
* Operator-level multiplexed read:
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
155
|
+
* Operator-level multiplexed read: which of this key owner's agents have
|
|
156
|
+
* mail or open work (an agent-scoped key collapses to its one agent).
|
|
157
|
+
*
|
|
158
|
+
* Returns who to start, never what they were sent — the host you start
|
|
159
|
+
* reads its own inbox on its own identity. Ack stays per agent.
|
|
135
160
|
*/
|
|
136
161
|
getOperatorInbox(opts?: InboxReadOptions): Promise<InboxOperatorEnvelope>;
|
|
137
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Advance this agent's watermark — pass the envelope's `ackTo`. Monotonic
|
|
164
|
+
* server-side: an older value is a no-op, so a replayed ack can never
|
|
165
|
+
* redeliver handled work.
|
|
166
|
+
*/
|
|
167
|
+
ack(upTo: string): Promise<InboxAckResult>;
|
|
138
168
|
}
|
package/dist/http/InboxClient.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
+
import { pollSurfaceError } from '../shared/rateLimit.js';
|
|
3
4
|
/**
|
|
4
|
-
* The doorbell, not the door (ZIG-434): references
|
|
5
|
-
*
|
|
6
|
-
* Wraps `GET /inbox` and `POST /inbox/ack`.
|
|
5
|
+
* The doorbell, not the door (ZIG-434): references addressed to this agent
|
|
6
|
+
* since its last ack — never content. Flow: inbox → read → act → ack
|
|
7
|
+
* (ZIG-446). Wraps `GET /inbox` and `POST /inbox/ack`.
|
|
7
8
|
*/
|
|
8
9
|
export class InboxClient {
|
|
9
10
|
operatorKey;
|
|
@@ -34,6 +35,9 @@ export class InboxClient {
|
|
|
34
35
|
if (opts.waitSeconds != null && opts.waitSeconds > 0) {
|
|
35
36
|
url.searchParams.set('wait', String(opts.waitSeconds));
|
|
36
37
|
}
|
|
38
|
+
if (opts.agents?.length) {
|
|
39
|
+
url.searchParams.set('agents', opts.agents.join(','));
|
|
40
|
+
}
|
|
37
41
|
return url.toString();
|
|
38
42
|
}
|
|
39
43
|
async getInbox(opts = {}) {
|
|
@@ -42,37 +46,62 @@ export class InboxClient {
|
|
|
42
46
|
});
|
|
43
47
|
const body = await res.text().catch(() => '');
|
|
44
48
|
if (!res.ok) {
|
|
45
|
-
throw
|
|
49
|
+
throw pollSurfaceError('InboxClient.getInbox', res, body);
|
|
46
50
|
}
|
|
47
51
|
return JSON.parse(body);
|
|
48
52
|
}
|
|
49
53
|
/**
|
|
50
|
-
* Operator-level multiplexed read:
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
+
* Operator-level multiplexed read: which of this key owner's agents have
|
|
55
|
+
* mail or open work (an agent-scoped key collapses to its one agent).
|
|
56
|
+
*
|
|
57
|
+
* Returns who to start, never what they were sent — the host you start
|
|
58
|
+
* reads its own inbox on its own identity. Ack stays per agent.
|
|
54
59
|
*/
|
|
55
60
|
async getOperatorInbox(opts = {}) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
// Bounded wait (ZIG-965): the server holds at most `waitSeconds` (+ sweep
|
|
62
|
+
// time); a poll that outlives that by a wide margin is a dead sweep, and
|
|
63
|
+
// without a timeout it blocked the launcher's whole poll loop — lazy wake
|
|
64
|
+
// simply stopped. Abort and let the caller's retry loop take over.
|
|
65
|
+
const timeoutMs = ((opts.waitSeconds ?? 0) + 30) * 1000;
|
|
66
|
+
const ac = new AbortController();
|
|
67
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
68
|
+
let res;
|
|
69
|
+
try {
|
|
70
|
+
res = await fetch(this.inboxUrl('/inbox/operator', opts), {
|
|
71
|
+
headers: this.headers(),
|
|
72
|
+
signal: ac.signal,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
throw ac.signal.aborted
|
|
77
|
+
? new Error(`InboxClient.getOperatorInbox timed out after ${timeoutMs}ms`)
|
|
78
|
+
: err;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
}
|
|
59
83
|
const body = await res.text().catch(() => '');
|
|
60
84
|
if (!res.ok) {
|
|
61
|
-
throw
|
|
85
|
+
throw pollSurfaceError('InboxClient.getOperatorInbox', res, body);
|
|
62
86
|
}
|
|
63
87
|
return JSON.parse(body);
|
|
64
88
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Advance this agent's watermark — pass the envelope's `ackTo`. Monotonic
|
|
91
|
+
* server-side: an older value is a no-op, so a replayed ack can never
|
|
92
|
+
* redeliver handled work.
|
|
93
|
+
*/
|
|
94
|
+
async ack(upTo) {
|
|
95
|
+
if (!upTo)
|
|
96
|
+
throw new Error('InboxClient.ack: upTo is required');
|
|
68
97
|
const res = await fetch(`${this.baseUrl}/inbox/ack`, {
|
|
69
98
|
method: 'POST',
|
|
70
99
|
headers: this.headers(),
|
|
71
|
-
body: JSON.stringify({
|
|
100
|
+
body: JSON.stringify({ upTo }),
|
|
72
101
|
});
|
|
73
102
|
const body = await res.text().catch(() => '');
|
|
74
103
|
if (!res.ok) {
|
|
75
|
-
throw
|
|
104
|
+
throw pollSurfaceError('InboxClient.ack', res, body);
|
|
76
105
|
}
|
|
77
106
|
return JSON.parse(body);
|
|
78
107
|
}
|
|
@@ -8,6 +8,12 @@ export interface PublishOfferPayload {
|
|
|
8
8
|
maxExecutions?: number;
|
|
9
9
|
/** `hire` = claimer becomes the provider's principal on claim. Defaults to `service` server-side. */
|
|
10
10
|
engagementKind?: 'hire' | 'service';
|
|
11
|
+
/**
|
|
12
|
+
* How `price` reads. `total` (default) is one price for the whole engagement,
|
|
13
|
+
* escrowed on claim and paid at fulfillment; `per_task` is a RATE settled as
|
|
14
|
+
* each task completes (open/standing only; the default for a hire).
|
|
15
|
+
*/
|
|
16
|
+
billing?: 'total' | 'per_task';
|
|
11
17
|
/** Broadcast audience: 'everyone' (default, fully public) or 'org' (members of your active org only). */
|
|
12
18
|
audience?: BroadcastAudience;
|
|
13
19
|
metadata?: Record<string, unknown>;
|
|
@@ -20,6 +26,8 @@ export interface PullOffersOptions {
|
|
|
20
26
|
export declare function pullOffers(options: PullOffersOptions | undefined, creds: Creds): Promise<Agreement[]>;
|
|
21
27
|
export declare function claimOffer(agreementId: string, creds: Creds): Promise<Agreement>;
|
|
22
28
|
export interface PublishQuestPayload {
|
|
29
|
+
/** See PublishOfferPayload.billing. */
|
|
30
|
+
billing?: 'total' | 'per_task';
|
|
23
31
|
description: string;
|
|
24
32
|
chatId?: string;
|
|
25
33
|
payerId?: string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ProposeTerms } from './AgreementClient.js';
|
|
2
|
+
import { type Agreement, type Creds, type EngagementKind } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* ZIG-1022 — one propose grammar. Direct, broadcast (quest and standing
|
|
5
|
+
* offer), and link proposals all flow through here; the surfaces expose a
|
|
6
|
+
* single propose tool instead of dedicated publish/request tools.
|
|
7
|
+
*
|
|
8
|
+
* Routing:
|
|
9
|
+
* - engagementKind 'link' → POST /agreements (link proposal / open invite)
|
|
10
|
+
* - proposedTo 'everyone' | 'org', providerId = self → seller-broadcast standing offer
|
|
11
|
+
* - proposedTo 'everyone' | 'org', no providerId → buyer-broadcast quest
|
|
12
|
+
* - anything else → direct proposal
|
|
13
|
+
*/
|
|
14
|
+
export interface UnifiedProposeInput extends ProposeTerms {
|
|
15
|
+
proposedTo: string;
|
|
16
|
+
/** Required on direct proposals; optional on broadcasts/links. */
|
|
17
|
+
chatId?: string;
|
|
18
|
+
engagementKind?: EngagementKind;
|
|
19
|
+
}
|
|
20
|
+
export type ProposeShape = 'direct' | 'quest' | 'offer' | 'link';
|
|
21
|
+
export declare function proposeUnified(input: UnifiedProposeInput, creds: Creds): Promise<{
|
|
22
|
+
agreement: Agreement;
|
|
23
|
+
shape: ProposeShape;
|
|
24
|
+
}>;
|
|
25
|
+
export type ClaimedKind = 'link' | 'offer' | 'quest';
|
|
26
|
+
/**
|
|
27
|
+
* ZIG-1021 — one claim verb for any open broadcast. Fetches the agreement to
|
|
28
|
+
* route: link invites and quests claim through POST /agreements/:id/claim;
|
|
29
|
+
* standing offers (open payer side) through POST /marketplace/offers/claim.
|
|
30
|
+
*/
|
|
31
|
+
export declare function claimOpenAgreement(agreementId: string, creds: Creds): Promise<{
|
|
32
|
+
agreement: Agreement;
|
|
33
|
+
kind: ClaimedKind;
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createAgreement, claimAgreement, getAgreement, proposeBroadcast, proposeDirectTo, } from './AgreementClient.js';
|
|
2
|
+
import { claimOffer, publishOffer } from './MarketplaceClient.js';
|
|
3
|
+
import { isBroadcastTarget, } from '../types.js';
|
|
4
|
+
export async function proposeUnified(input, creds) {
|
|
5
|
+
const { proposedTo, chatId, engagementKind, providerId, ...terms } = input;
|
|
6
|
+
if (!proposedTo)
|
|
7
|
+
throw new Error('proposedTo is required (a user/agent id, or "everyone"/"org" to broadcast)');
|
|
8
|
+
if (engagementKind === 'link') {
|
|
9
|
+
// A link is an agreement, proposed to one agent (providerId) or opened as
|
|
10
|
+
// an invite (proposedTo 'everyone'); no chat, no money.
|
|
11
|
+
const target = isBroadcastTarget(proposedTo) ? undefined : proposedTo;
|
|
12
|
+
const { agreement } = await createAgreement({
|
|
13
|
+
engagementKind: 'link',
|
|
14
|
+
...(target ? { providerId: target } : {}),
|
|
15
|
+
...(terms.description ? { description: terms.description } : {}),
|
|
16
|
+
}, creds);
|
|
17
|
+
return { agreement, shape: 'link' };
|
|
18
|
+
}
|
|
19
|
+
if (isBroadcastTarget(proposedTo)) {
|
|
20
|
+
const audience = proposedTo;
|
|
21
|
+
if (providerId && creds.agentId && providerId === creds.agentId) {
|
|
22
|
+
// Seller-broadcast: you work, the claimer pays — a standing offer.
|
|
23
|
+
const agreement = await publishOffer({
|
|
24
|
+
description: terms.description ?? '',
|
|
25
|
+
price: terms.price,
|
|
26
|
+
lifecycle: terms.lifecycle,
|
|
27
|
+
expiresAt: terms.expiresAt,
|
|
28
|
+
maxExecutions: terms.maxExecutions,
|
|
29
|
+
engagementKind: engagementKind,
|
|
30
|
+
billing: terms.billing,
|
|
31
|
+
audience,
|
|
32
|
+
}, creds);
|
|
33
|
+
return { agreement, shape: 'offer' };
|
|
34
|
+
}
|
|
35
|
+
if (providerId) {
|
|
36
|
+
throw new Error('On a broadcast, providerId must be your own agent id (a standing offer: you work, the claimer pays) or omitted (a quest: the claimer works, you pay). A third-party providerId is not broadcastable.');
|
|
37
|
+
}
|
|
38
|
+
// Buyer-broadcast: the claimer works, your side pays — an open quest.
|
|
39
|
+
const agreement = await proposeBroadcast({
|
|
40
|
+
...terms,
|
|
41
|
+
chatId: chatId ?? '',
|
|
42
|
+
engagementKind: engagementKind ?? 'service',
|
|
43
|
+
audience,
|
|
44
|
+
}, creds);
|
|
45
|
+
return { agreement, shape: 'quest' };
|
|
46
|
+
}
|
|
47
|
+
if (!chatId)
|
|
48
|
+
throw new Error('chatId is required on a direct proposal');
|
|
49
|
+
const agreement = await proposeDirectTo({
|
|
50
|
+
...terms,
|
|
51
|
+
proposedTo,
|
|
52
|
+
chatId,
|
|
53
|
+
providerId: providerId?.trim() || proposedTo,
|
|
54
|
+
engagementKind: engagementKind ?? 'service',
|
|
55
|
+
}, creds);
|
|
56
|
+
return { agreement, shape: 'direct' };
|
|
57
|
+
}
|
|
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.
|
|
62
|
+
*/
|
|
63
|
+
export async function claimOpenAgreement(agreementId, creds) {
|
|
64
|
+
if (!agreementId)
|
|
65
|
+
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
|
+
return { agreement, kind: 'quest' };
|
|
80
|
+
}
|
package/dist/http/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './TaskClient.js';
|
|
2
2
|
export * from './AgreementClient.js';
|
|
3
3
|
export * from './MarketplaceClient.js';
|
|
4
|
+
export * from './agreementFlows.js';
|
|
4
5
|
export * from './ChatClient.js';
|
|
5
6
|
export { MessagesClient } from './MessagesClient.js';
|
|
6
7
|
export type { ListMessagesOptions, ListMessagesResult } from './MessagesClient.js';
|
|
@@ -25,4 +26,4 @@ export type { MyOrg, OrgResolution } from './OrgsClient.js';
|
|
|
25
26
|
export { AgentSearchClient } from './AgentSearchClient.js';
|
|
26
27
|
export { TelemetryClient } from './TelemetryClient.js';
|
|
27
28
|
export { InboxClient } from './InboxClient.js';
|
|
28
|
-
export type {
|
|
29
|
+
export type { InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxOperatorAgentEntry, InboxOperatorEnvelope, } from './InboxClient.js';
|
package/dist/http/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './TaskClient.js';
|
|
2
2
|
export * from './AgreementClient.js';
|
|
3
3
|
export * from './MarketplaceClient.js';
|
|
4
|
+
export * from './agreementFlows.js';
|
|
4
5
|
export * from './ChatClient.js';
|
|
5
6
|
export { MessagesClient } from './MessagesClient.js';
|
|
6
7
|
export { ArtifactsClient } from './ArtifactsClient.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,10 @@ 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';
|
|
5
6
|
export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, AGREEMENT_ENGAGEMENT_KIND, isValidContentType } from './types.js';
|
|
6
7
|
export { getBackendUrl, getWebSocketUrl } from './utils/urlUtils.js';
|
|
7
8
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
9
|
+
export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
|
|
8
10
|
export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, ApiError, } from './types.js';
|
|
9
11
|
export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, } from './http/AgreementClient.js';
|
package/dist/index.js
CHANGED
|
@@ -5,3 +5,5 @@ export { ConnectionManager } from './ConnectionManager.js';
|
|
|
5
5
|
export { EntryTypes, ContentTypes, OPEN_AGREEMENT_TARGET, ORG_AGREEMENT_TARGET, BROADCAST_TARGETS, isBroadcastTarget, 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.
|
|
9
|
+
export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, } from './shared/rateLimit.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
|
|
3
|
+
* deadline attached.
|
|
4
|
+
*
|
|
5
|
+
* The poll surface (`/inbox`, `/context/read`) is capped per actor, and every
|
|
6
|
+
* refused call still counts against that cap. A caller that retries on the
|
|
7
|
+
* usual exponential ladder (1s, 2s, 4s…) therefore spends its way deeper into
|
|
8
|
+
* the hole: observed live as an agent that could not read the conversation it
|
|
9
|
+
* had just been woken for, because its own retries kept the bucket empty.
|
|
10
|
+
*
|
|
11
|
+
* The server already says exactly how long to wait — `Retry-After`, or
|
|
12
|
+
* `RateLimit-Reset` from the standard headers. These helpers carry that number
|
|
13
|
+
* to whoever is doing the backing off.
|
|
14
|
+
*/
|
|
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. */
|
|
23
|
+
export declare function isRateLimited(err: unknown): err is {
|
|
24
|
+
retryAfterMs: number | null;
|
|
25
|
+
};
|
|
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.
|
|
40
|
+
*/
|
|
41
|
+
export declare function pollSurfaceError(label: string, res: {
|
|
42
|
+
status: number;
|
|
43
|
+
headers: {
|
|
44
|
+
get(name: string): string | null;
|
|
45
|
+
};
|
|
46
|
+
}, body: string): Error;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-1019 — a 429 is not a generic failure, it is an instruction with a
|
|
3
|
+
* deadline attached.
|
|
4
|
+
*
|
|
5
|
+
* The poll surface (`/inbox`, `/context/read`) is capped per actor, and every
|
|
6
|
+
* refused call still counts against that cap. A caller that retries on the
|
|
7
|
+
* usual exponential ladder (1s, 2s, 4s…) therefore spends its way deeper into
|
|
8
|
+
* the hole: observed live as an agent that could not read the conversation it
|
|
9
|
+
* had just been woken for, because its own retries kept the bucket empty.
|
|
10
|
+
*
|
|
11
|
+
* The server already says exactly how long to wait — `Retry-After`, or
|
|
12
|
+
* `RateLimit-Reset` from the standard headers. These helpers carry that number
|
|
13
|
+
* to whoever is doing the backing off.
|
|
14
|
+
*/
|
|
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. */
|
|
27
|
+
export function isRateLimited(err) {
|
|
28
|
+
return (!!err &&
|
|
29
|
+
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)));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
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.
|
|
67
|
+
*/
|
|
68
|
+
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));
|
|
73
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -102,6 +102,17 @@ export interface Agreement {
|
|
|
102
102
|
lifecycle?: string;
|
|
103
103
|
expiresAt?: string;
|
|
104
104
|
maxExecutions?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Seat bookkeeping when this agreement is an open link invite. Each claim
|
|
107
|
+
* takes a seat and mints its own child link, so one shared link produces N
|
|
108
|
+
* separate connections rather than a group.
|
|
109
|
+
*/
|
|
110
|
+
linkInvite?: {
|
|
111
|
+
maxClaims?: number;
|
|
112
|
+
claimsUsed?: number;
|
|
113
|
+
} | null;
|
|
114
|
+
/** Set on a link that was formed by claiming the invite with this id. */
|
|
115
|
+
linkInviteTemplateId?: string | null;
|
|
105
116
|
metadata?: Record<string, unknown>;
|
|
106
117
|
createdAt?: string;
|
|
107
118
|
updatedAt?: string;
|
|
@@ -165,7 +176,7 @@ export interface MessageMetadata {
|
|
|
165
176
|
* `task.notify.chat` / `agreement.notify.direct` traffic. The agent SDK's
|
|
166
177
|
* `normalizeIncomingEvent` reads `metadata.task` to upgrade structured
|
|
167
178
|
* task notifications into `task_result` events (so executors see
|
|
168
|
-
* `task-assigned` outcomes when the orchestrator calls `
|
|
179
|
+
* `task-assigned` outcomes when the orchestrator calls `task_create`),
|
|
169
180
|
* and maps explicit wire `operation` + `agreementId` lifecycle fields
|
|
170
181
|
* into `agreement_lifecycle` events for proposal approve/reject.
|
|
171
182
|
*/
|