@ziggs-ai/api-client 0.15.1 → 0.17.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/capabilities/agreementVerbs.d.ts +9 -0
- package/dist/capabilities/agreementVerbs.js +10 -1
- package/dist/capabilities/agreements.js +2 -1
- package/dist/capabilities/artifacts.js +16 -6
- package/dist/capabilities/connections.js +5 -5
- package/dist/capabilities/grants.js +8 -2
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/capabilities/index.js +1 -0
- package/dist/capabilities/links.js +3 -3
- package/dist/capabilities/listedFields.d.ts +17 -0
- package/dist/capabilities/listedFields.js +45 -0
- package/dist/capabilities/marketplace.js +7 -2
- package/dist/capabilities/payments.js +7 -2
- package/dist/capabilities/tasks.js +9 -3
- package/dist/http/AgreementClient.js +3 -10
- package/dist/http/ArtifactsClient.d.ts +5 -16
- package/dist/http/ArtifactsClient.js +8 -5
- package/dist/http/ChatClient.d.ts +2 -6
- package/dist/http/ChatClient.js +3 -10
- package/dist/http/ConnectionsClient.d.ts +6 -4
- package/dist/http/ConnectionsClient.js +7 -5
- package/dist/http/ContextReadClient.js +3 -14
- package/dist/http/GrantsClient.d.ts +10 -1
- package/dist/http/GrantsClient.js +12 -2
- package/dist/http/IntroductionsClient.d.ts +11 -4
- package/dist/http/MarketplaceClient.js +3 -10
- package/dist/http/OrgsClient.d.ts +0 -14
- package/dist/http/OrgsClient.js +2 -17
- package/dist/http/PaymentsClient.d.ts +27 -3
- package/dist/http/PaymentsClient.js +18 -3
- package/dist/http/TaskClient.js +4 -11
- package/dist/http/index.d.ts +2 -1
- package/dist/http/index.js +5 -1
- package/dist/http/operatorHeaders.d.ts +22 -3
- package/dist/http/operatorHeaders.js +28 -4
- package/dist/http/paymentGrantSelection.d.ts +5 -0
- package/dist/http/paymentGrantSelection.js +26 -16
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/shared/operatorKey.d.ts +47 -0
- package/dist/shared/operatorKey.js +64 -0
- package/dist/types.d.ts +6 -26
- package/dist/types.js +1 -19
- package/dist/utils/appUrls.d.ts +7 -0
- package/dist/utils/appUrls.js +38 -0
- package/package.json +3 -2
|
@@ -56,7 +56,16 @@ export declare class GrantsClient {
|
|
|
56
56
|
private readonly operatorKey;
|
|
57
57
|
private readonly agentId?;
|
|
58
58
|
private readonly baseUrl;
|
|
59
|
-
|
|
59
|
+
private readonly laneId?;
|
|
60
|
+
/**
|
|
61
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
62
|
+
* X-Ziggs-Lane. The server reads off it who the agent is acting for, and
|
|
63
|
+
* answers the spendable rails with only the grants this wake may spend —
|
|
64
|
+
* so an agent serving several customers is never handed a list it has to
|
|
65
|
+
* choose between. Without it the list is everything the agent holds, and
|
|
66
|
+
* the grant it picks may then be refused with nothing saying why.
|
|
67
|
+
*/
|
|
68
|
+
constructor(operatorKey: string, agentId?: string, baseUrl?: string, laneId?: string);
|
|
60
69
|
listGrants(query?: ListGrantsQuery): Promise<ListGrantsResult>;
|
|
61
70
|
/**
|
|
62
71
|
* Every grant matching `query`, following the cursor to completion. Use when a
|
|
@@ -12,12 +12,22 @@ export class GrantsClient {
|
|
|
12
12
|
operatorKey;
|
|
13
13
|
agentId;
|
|
14
14
|
baseUrl;
|
|
15
|
-
|
|
15
|
+
laneId;
|
|
16
|
+
/**
|
|
17
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
18
|
+
* X-Ziggs-Lane. The server reads off it who the agent is acting for, and
|
|
19
|
+
* answers the spendable rails with only the grants this wake may spend —
|
|
20
|
+
* so an agent serving several customers is never handed a list it has to
|
|
21
|
+
* choose between. Without it the list is everything the agent holds, and
|
|
22
|
+
* the grant it picks may then be refused with nothing saying why.
|
|
23
|
+
*/
|
|
24
|
+
constructor(operatorKey, agentId, baseUrl, laneId) {
|
|
16
25
|
if (!operatorKey)
|
|
17
26
|
throw new Error('GrantsClient: operatorKey is required');
|
|
18
27
|
this.operatorKey = operatorKey;
|
|
19
28
|
this.agentId = agentId;
|
|
20
29
|
this.baseUrl = baseUrl || getBackendUrl();
|
|
30
|
+
this.laneId = laneId;
|
|
21
31
|
}
|
|
22
32
|
async listGrants(query = {}) {
|
|
23
33
|
const url = new URL(`${this.baseUrl}/grants`);
|
|
@@ -41,7 +51,7 @@ export class GrantsClient {
|
|
|
41
51
|
if (query.limit != null)
|
|
42
52
|
url.searchParams.set('limit', String(query.limit));
|
|
43
53
|
const res = await fetch(url.toString(), {
|
|
44
|
-
headers: buildOperatorHeaders(this.operatorKey, this.agentId),
|
|
54
|
+
headers: buildOperatorHeaders(this.operatorKey, this.agentId, undefined, this.laneId),
|
|
45
55
|
});
|
|
46
56
|
const body = await res.text().catch(() => '');
|
|
47
57
|
if (!res.ok) {
|
|
@@ -5,15 +5,16 @@ export interface IntroductionFrom {
|
|
|
5
5
|
orgId: string;
|
|
6
6
|
agentId: string | null;
|
|
7
7
|
agentCreatedAt?: string | null;
|
|
8
|
-
claimedLabel
|
|
8
|
+
claimedLabel: {
|
|
9
9
|
text: string;
|
|
10
10
|
selfChosen: boolean;
|
|
11
11
|
verified: boolean;
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
|
-
* Same text as claimedLabel.text. Not identity
|
|
14
|
+
* Same text as claimedLabel.text. Not identity. Remove after 2026-10-07;
|
|
15
|
+
* readers must use claimedLabel.
|
|
15
16
|
*/
|
|
16
|
-
label
|
|
17
|
+
label?: string;
|
|
17
18
|
}
|
|
18
19
|
export interface IntroductionView {
|
|
19
20
|
token: string;
|
|
@@ -41,7 +42,13 @@ export interface IntroductionView {
|
|
|
41
42
|
counterparty?: {
|
|
42
43
|
principal: string;
|
|
43
44
|
agentId: string | null;
|
|
44
|
-
|
|
45
|
+
claimedLabel: {
|
|
46
|
+
text: string;
|
|
47
|
+
selfChosen: boolean;
|
|
48
|
+
verified: boolean;
|
|
49
|
+
};
|
|
50
|
+
/** Same text as claimedLabel.text. Remove after 2026-10-07. */
|
|
51
|
+
label?: string;
|
|
45
52
|
nextStep: string;
|
|
46
53
|
};
|
|
47
54
|
}
|
|
@@ -2,17 +2,10 @@
|
|
|
2
2
|
import { shapeAgreement } from './AgreementClient.js';
|
|
3
3
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
4
4
|
import { throwApiError } from '../shared/apiError.js';
|
|
5
|
+
import { buildCredsHeaders } from './operatorHeaders.js';
|
|
5
6
|
function getMarketplaceBaseUrl() { return `${getBackendUrl()}/marketplace`; }
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'content-type': 'application/json',
|
|
9
|
-
Authorization: `Bearer ${creds.operatorKey}`,
|
|
10
|
-
'X-Agent-Id': creds.agentId,
|
|
11
|
-
// the wake's lane, so the backend can fence this call to the
|
|
12
|
-
// engagement it belongs to rather than the agent's whole authority.
|
|
13
|
-
...(creds.laneId ? { 'X-Ziggs-Lane': creds.laneId } : {}),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
7
|
+
// Auth, agent and the wake's lane, from the one helper — see operatorHeaders.
|
|
8
|
+
const buildHeaders = buildCredsHeaders;
|
|
16
9
|
function assertCreds(creds, op) {
|
|
17
10
|
if (!creds?.operatorKey)
|
|
18
11
|
throw new Error(`operatorKey is required for ${op}`);
|
|
@@ -27,20 +27,6 @@ export type OrgResolution = {
|
|
|
27
27
|
* match. Ambiguous names return the candidates rather than guessing.
|
|
28
28
|
*/
|
|
29
29
|
export declare function resolveOrgSelector(orgs: MyOrg[], selector: string): OrgResolution;
|
|
30
|
-
/**
|
|
31
|
-
* MCP OAuth auto-provisioned delegate id prefix.
|
|
32
|
-
*
|
|
33
|
-
* The backend stopped classifying delegates by their id and now reads a stamp
|
|
34
|
-
* on the agent row, which is what freed the prefix to drop the vendor name. A
|
|
35
|
-
* client holds credentials, not rows, so this is still a shape test — but it is
|
|
36
|
-
* a shape test against a NAME, and the only thing it decides is which access
|
|
37
|
-
* endpoint to read. It said `delegate--` until the ids were renamed;
|
|
38
|
-
* left alone, every delegate session would have fallen through to the hosted
|
|
39
|
-
* reader and reported the wrong org and connection state.
|
|
40
|
-
*/
|
|
41
|
-
export declare const MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX = "delegate--";
|
|
42
|
-
/** True when `agentId` is an inbound MCP OAuth auto-provisioned delegate. */
|
|
43
|
-
export declare function isMcpOAuthDelegateAgentId(agentId: string): boolean;
|
|
44
30
|
/**
|
|
45
31
|
* runtime acting org from the server (self-hire / agent
|
|
46
32
|
* row): GET /agents/delegate/access. Moved here from ziggs-mcp's inline
|
package/dist/http/OrgsClient.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isMcpOAuthDelegateSession } from '../shared/operatorKey.js';
|
|
1
2
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
2
3
|
import { throwApiError } from '../shared/apiError.js';
|
|
3
4
|
import { buildOperatorHeaders } from './operatorHeaders.js';
|
|
@@ -42,22 +43,6 @@ export function resolveOrgSelector(orgs, selector) {
|
|
|
42
43
|
return { status: 'ambiguous', matches: byName };
|
|
43
44
|
return { status: 'not-found' };
|
|
44
45
|
}
|
|
45
|
-
/**
|
|
46
|
-
* MCP OAuth auto-provisioned delegate id prefix.
|
|
47
|
-
*
|
|
48
|
-
* The backend stopped classifying delegates by their id and now reads a stamp
|
|
49
|
-
* on the agent row, which is what freed the prefix to drop the vendor name. A
|
|
50
|
-
* client holds credentials, not rows, so this is still a shape test — but it is
|
|
51
|
-
* a shape test against a NAME, and the only thing it decides is which access
|
|
52
|
-
* endpoint to read. It said `delegate--` until the ids were renamed;
|
|
53
|
-
* left alone, every delegate session would have fallen through to the hosted
|
|
54
|
-
* reader and reported the wrong org and connection state.
|
|
55
|
-
*/
|
|
56
|
-
export const MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX = 'delegate--';
|
|
57
|
-
/** True when `agentId` is an inbound MCP OAuth auto-provisioned delegate. */
|
|
58
|
-
export function isMcpOAuthDelegateAgentId(agentId) {
|
|
59
|
-
return !!agentId && agentId.startsWith(MCP_OAUTH_DELEGATE_AGENT_ID_PREFIX);
|
|
60
|
-
}
|
|
61
46
|
/**
|
|
62
47
|
* runtime acting org from the server (self-hire / agent
|
|
63
48
|
* row): GET /agents/delegate/access. Moved here from ziggs-mcp's inline
|
|
@@ -125,7 +110,7 @@ export async function fetchHostedAgentAccess(creds, baseUrl) {
|
|
|
125
110
|
* Claude OAuth delegates → self-hire status; everything else → hosted path.
|
|
126
111
|
*/
|
|
127
112
|
export async function fetchSessionAccess(creds, baseUrl) {
|
|
128
|
-
if (
|
|
113
|
+
if (isMcpOAuthDelegateSession(creds)) {
|
|
129
114
|
return fetchDelegateAccess(creds, baseUrl);
|
|
130
115
|
}
|
|
131
116
|
return fetchHostedAgentAccess(creds, baseUrl);
|
|
@@ -87,7 +87,21 @@ export declare class PaymentsClient {
|
|
|
87
87
|
private readonly operatorKey;
|
|
88
88
|
private readonly agentId?;
|
|
89
89
|
private readonly baseUrl;
|
|
90
|
-
|
|
90
|
+
private readonly laneId?;
|
|
91
|
+
/**
|
|
92
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
93
|
+
* X-Ziggs-Lane. It tells the backend which hire this wake is in, and the
|
|
94
|
+
* backend reads off it WHO the spend is for. An agent that serves several
|
|
95
|
+
* hirers holds all of their grants at once and the holder is the same agent
|
|
96
|
+
* in every one, so without a lane, spend authority one customer gave is
|
|
97
|
+
* spendable while the agent works for another. ConnectionsClient and
|
|
98
|
+
* ArtifactsClient send the same header for the same reason.
|
|
99
|
+
*
|
|
100
|
+
* Omitting it cannot widen anything — a stated lane can only cause a
|
|
101
|
+
* refusal — but it does leave the spend unfenced, so a caller that knows
|
|
102
|
+
* its lane should pass it.
|
|
103
|
+
*/
|
|
104
|
+
constructor(operatorKey: string, agentId?: string, baseUrl?: string, laneId?: string);
|
|
91
105
|
balance(): Promise<WalletBalance>;
|
|
92
106
|
resolve({ userId, agentId }?: {
|
|
93
107
|
userId?: string;
|
|
@@ -99,7 +113,12 @@ export declare class PaymentsClient {
|
|
|
99
113
|
idempotencyKey?: string;
|
|
100
114
|
description?: string;
|
|
101
115
|
paymentGrantId?: string;
|
|
102
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
118
|
+
* hire's own grant over a standing one — a preference about which budget is
|
|
119
|
+
* billed, never about which grants may be spent. That answer is the
|
|
120
|
+
* server's, and it turns on who gave the grant.
|
|
121
|
+
*/
|
|
103
122
|
agreementId?: string;
|
|
104
123
|
}): Promise<TransferResult>;
|
|
105
124
|
hold({ amount, idempotencyKey, description, paymentGrantId, agreementId, }: {
|
|
@@ -107,7 +126,12 @@ export declare class PaymentsClient {
|
|
|
107
126
|
idempotencyKey?: string;
|
|
108
127
|
description?: string;
|
|
109
128
|
paymentGrantId?: string;
|
|
110
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* The hire this spend belongs to. Used for attribution, and to prefer that
|
|
131
|
+
* hire's own grant over a standing one — a preference about which budget is
|
|
132
|
+
* billed, never about which grants may be spent. That answer is the
|
|
133
|
+
* server's, and it turns on who gave the grant.
|
|
134
|
+
*/
|
|
111
135
|
agreementId?: string;
|
|
112
136
|
}): Promise<HoldResult>;
|
|
113
137
|
/**
|
|
@@ -18,12 +18,27 @@ export class PaymentsClient {
|
|
|
18
18
|
operatorKey;
|
|
19
19
|
agentId;
|
|
20
20
|
baseUrl;
|
|
21
|
-
|
|
21
|
+
laneId;
|
|
22
|
+
/**
|
|
23
|
+
* @param laneId the wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
24
|
+
* X-Ziggs-Lane. It tells the backend which hire this wake is in, and the
|
|
25
|
+
* backend reads off it WHO the spend is for. An agent that serves several
|
|
26
|
+
* hirers holds all of their grants at once and the holder is the same agent
|
|
27
|
+
* in every one, so without a lane, spend authority one customer gave is
|
|
28
|
+
* spendable while the agent works for another. ConnectionsClient and
|
|
29
|
+
* ArtifactsClient send the same header for the same reason.
|
|
30
|
+
*
|
|
31
|
+
* Omitting it cannot widen anything — a stated lane can only cause a
|
|
32
|
+
* refusal — but it does leave the spend unfenced, so a caller that knows
|
|
33
|
+
* its lane should pass it.
|
|
34
|
+
*/
|
|
35
|
+
constructor(operatorKey, agentId, baseUrl, laneId) {
|
|
22
36
|
if (!operatorKey)
|
|
23
37
|
throw new Error('PaymentsClient: operatorKey is required');
|
|
24
38
|
this.operatorKey = operatorKey;
|
|
25
39
|
this.agentId = agentId;
|
|
26
40
|
this.baseUrl = baseUrl || getBackendUrl();
|
|
41
|
+
this.laneId = laneId;
|
|
27
42
|
}
|
|
28
43
|
async balance() {
|
|
29
44
|
const w = (await this._get('/payments/wallet'));
|
|
@@ -180,7 +195,7 @@ export class PaymentsClient {
|
|
|
180
195
|
* (retired GET /payments/grants), following the cursor to completion.
|
|
181
196
|
*/
|
|
182
197
|
async listGrants() {
|
|
183
|
-
const grants = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl);
|
|
198
|
+
const grants = new GrantsClient(this.operatorKey, this.agentId, this.baseUrl, this.laneId);
|
|
184
199
|
return grants.listAllGrants({ scopeKind: 'wallet', health: 'active' });
|
|
185
200
|
}
|
|
186
201
|
async createTopUpIntent({ amount, description, currency, } = {}) {
|
|
@@ -261,7 +276,7 @@ export class PaymentsClient {
|
|
|
261
276
|
async _request(method, path, body) {
|
|
262
277
|
const init = {
|
|
263
278
|
method,
|
|
264
|
-
headers: buildOperatorHeaders(this.operatorKey, this.agentId, body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
279
|
+
headers: buildOperatorHeaders(this.operatorKey, this.agentId, body !== undefined ? { 'content-type': 'application/json' } : {}, this.laneId),
|
|
265
280
|
};
|
|
266
281
|
if (body !== undefined)
|
|
267
282
|
init.body = JSON.stringify(body);
|
package/dist/http/TaskClient.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
2
2
|
import { partyActorIds } from '../types.js';
|
|
3
3
|
import { throwApiError } from '../shared/apiError.js';
|
|
4
|
+
import { buildCredsHeaders } from './operatorHeaders.js';
|
|
4
5
|
function getTaskBaseUrl() { return `${getBackendUrl()}/tasks`; }
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'content-type': 'application/json',
|
|
8
|
-
Authorization: `Bearer ${creds.operatorKey}`,
|
|
9
|
-
'X-Agent-Id': creds.agentId,
|
|
10
|
-
// the wake's lane, so the backend can fence this call to the
|
|
11
|
-
// engagement it belongs to rather than the agent's whole authority.
|
|
12
|
-
...(creds.laneId ? { 'X-Ziggs-Lane': creds.laneId } : {}),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
6
|
+
// Auth, agent and the wake's lane, from the one helper — see operatorHeaders.
|
|
7
|
+
const buildHeaders = buildCredsHeaders;
|
|
15
8
|
function assertCreds(creds, op) {
|
|
16
9
|
if (!creds?.operatorKey)
|
|
17
10
|
throw new Error(`operatorKey is required for ${op}`);
|
|
@@ -165,7 +158,7 @@ export async function getActiveTasksForAgent(agentId, creds) {
|
|
|
165
158
|
* assignee's own work list hands over a task nobody woke it for. Every caller of
|
|
166
159
|
* this function is asking "what can I act on now", and for a withheld row the
|
|
167
160
|
* answer is nothing — `updateTaskState` refuses it with `dependencies_unmet`.
|
|
168
|
-
* Two of
|
|
161
|
+
* Two of Agent's three callers do worse than waste a wake: the needs gate
|
|
169
162
|
* and the unmet-access sweep both FAIL the tasks they find, so a withheld node
|
|
170
163
|
* was one missing grant away from being failed before its turn came.
|
|
171
164
|
*
|
package/dist/http/index.d.ts
CHANGED
|
@@ -23,8 +23,9 @@ export { PaymentsClient } from './PaymentsClient.js';
|
|
|
23
23
|
export type { PaymentsError, WalletBalance, WalletRef, PaymentTransactionView, TransferResult, HoldResult, ReleaseResult, PaymentGrantView, PaymentGrantEnvelope, RevokeGrantResult, PaymentApproval, WaitForApprovalResult, } from './PaymentsClient.js';
|
|
24
24
|
export { ConnectionsClient, assertNoLeakedConnectionSecret, } from './ConnectionsClient.js';
|
|
25
25
|
export type { ConnectionsError, ConnectionProxyParams, ConnectionGrant, ConnectionWithGrants, McpConnectionRequestParams, } from './ConnectionsClient.js';
|
|
26
|
-
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess,
|
|
26
|
+
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess, } from './OrgsClient.js';
|
|
27
27
|
export type { MyOrg, OrgResolution } from './OrgsClient.js';
|
|
28
28
|
export { AgentSearchClient } from './AgentSearchClient.js';
|
|
29
29
|
export { TelemetryClient } from './TelemetryClient.js';
|
|
30
30
|
export { InboxClient } from './InboxClient.js';
|
|
31
|
+
export { WAKE_LANE_HEADER, laneHeader, buildOperatorHeaders, buildCredsHeaders, } from './operatorHeaders.js';
|
package/dist/http/index.js
CHANGED
|
@@ -15,7 +15,11 @@ export { ContextGrantsClient } from './ContextGrantsClient.js';
|
|
|
15
15
|
export { grantCaveat, CONTEXT_GRANT_SCOPE_KINDS, GRANT_SCOPE_KINDS, } from './grants.js';
|
|
16
16
|
export { PaymentsClient } from './PaymentsClient.js';
|
|
17
17
|
export { ConnectionsClient, assertNoLeakedConnectionSecret, } from './ConnectionsClient.js';
|
|
18
|
-
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess,
|
|
18
|
+
export { fetchMyOrgs, resolveOrgSelector, fetchDelegateAccess, fetchHostedAgentAccess, fetchSessionAccess, } from './OrgsClient.js';
|
|
19
19
|
export { AgentSearchClient } from './AgentSearchClient.js';
|
|
20
20
|
export { TelemetryClient } from './TelemetryClient.js';
|
|
21
21
|
export { InboxClient } from './InboxClient.js';
|
|
22
|
+
// The lane header, built in one place — see operatorHeaders.ts. Exported
|
|
23
|
+
// because the MCP transports in agent-sdk and ziggs-mcp send it too, and a
|
|
24
|
+
// copy of a header nobody notices missing goes stale unseen.
|
|
25
|
+
export { WAKE_LANE_HEADER, laneHeader, buildOperatorHeaders, buildCredsHeaders, } from './operatorHeaders.js';
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth headers for operator-key HTTP clients.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Auth headers for operator-key HTTP clients.
|
|
3
|
+
*
|
|
4
|
+
* ONE place builds them, because the lane is the header a caller cannot be
|
|
5
|
+
* trusted to remember. Omitting `X-Ziggs-Lane` never fails: the server fences
|
|
6
|
+
* the wake to the agent's own org instead, so a door that forgets it answers
|
|
7
|
+
* short lists and refuses grants the agent's customer gave it — silently, and
|
|
8
|
+
* only for the agents that serve someone other than their owner. A header
|
|
9
|
+
* whose absence is indistinguishable from a legitimate answer has to be built
|
|
10
|
+
* in one place, not copied into each caller.
|
|
5
11
|
*/
|
|
12
|
+
/** The header the backend's wake fence reads (`access/wake-fence.ts`). */
|
|
13
|
+
export declare const WAKE_LANE_HEADER = "X-Ziggs-Lane";
|
|
14
|
+
/**
|
|
15
|
+
* The lane header, or nothing. `laneId` is a chat id, or `agrn-<agreementId>`
|
|
16
|
+
* for a task with no origin chat.
|
|
17
|
+
*/
|
|
18
|
+
export declare function laneHeader(laneId?: string): Record<string, string>;
|
|
6
19
|
export declare function buildOperatorHeaders(operatorKey: string, agentId?: string, extra?: Record<string, string>,
|
|
7
20
|
/**
|
|
8
21
|
* the lane this call belongs to, sent as `X-Ziggs-Lane`. The
|
|
@@ -10,3 +23,9 @@ export declare function buildOperatorHeaders(operatorKey: string, agentId?: stri
|
|
|
10
23
|
* narrows to the agent's own org, so it can never widen reach.
|
|
11
24
|
*/
|
|
12
25
|
laneId?: string): Record<string, string>;
|
|
26
|
+
/** What a `Creds`-shaped client sends on a JSON call: auth, agent, lane. */
|
|
27
|
+
export declare function buildCredsHeaders(creds: {
|
|
28
|
+
operatorKey: string;
|
|
29
|
+
agentId: string;
|
|
30
|
+
laneId?: string;
|
|
31
|
+
}): Record<string, string>;
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth headers for operator-key HTTP clients.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Auth headers for operator-key HTTP clients.
|
|
3
|
+
*
|
|
4
|
+
* ONE place builds them, because the lane is the header a caller cannot be
|
|
5
|
+
* trusted to remember. Omitting `X-Ziggs-Lane` never fails: the server fences
|
|
6
|
+
* the wake to the agent's own org instead, so a door that forgets it answers
|
|
7
|
+
* short lists and refuses grants the agent's customer gave it — silently, and
|
|
8
|
+
* only for the agents that serve someone other than their owner. A header
|
|
9
|
+
* whose absence is indistinguishable from a legitimate answer has to be built
|
|
10
|
+
* in one place, not copied into each caller.
|
|
5
11
|
*/
|
|
12
|
+
/** The header the backend's wake fence reads (`access/wake-fence.ts`). */
|
|
13
|
+
export const WAKE_LANE_HEADER = 'X-Ziggs-Lane';
|
|
14
|
+
/**
|
|
15
|
+
* The lane header, or nothing. `laneId` is a chat id, or `agrn-<agreementId>`
|
|
16
|
+
* for a task with no origin chat.
|
|
17
|
+
*/
|
|
18
|
+
export function laneHeader(laneId) {
|
|
19
|
+
return laneId ? { [WAKE_LANE_HEADER]: laneId } : {};
|
|
20
|
+
}
|
|
6
21
|
export function buildOperatorHeaders(operatorKey, agentId, extra,
|
|
7
22
|
/**
|
|
8
23
|
* the lane this call belongs to, sent as `X-Ziggs-Lane`. The
|
|
@@ -12,8 +27,17 @@ export function buildOperatorHeaders(operatorKey, agentId, extra,
|
|
|
12
27
|
laneId) {
|
|
13
28
|
return {
|
|
14
29
|
Authorization: `Bearer ${operatorKey}`,
|
|
30
|
+
// `X-Agent-Id` is only sent when an agentId is present — agent-scoped keys
|
|
31
|
+
// identify the agent themselves; fleet keys must pass one.
|
|
15
32
|
...(agentId ? { 'X-Agent-Id': agentId } : {}),
|
|
16
|
-
...(laneId
|
|
33
|
+
...laneHeader(laneId),
|
|
17
34
|
...extra,
|
|
18
35
|
};
|
|
19
36
|
}
|
|
37
|
+
/** What a `Creds`-shaped client sends on a JSON call: auth, agent, lane. */
|
|
38
|
+
export function buildCredsHeaders(creds) {
|
|
39
|
+
return {
|
|
40
|
+
'content-type': 'application/json',
|
|
41
|
+
...buildOperatorHeaders(creds.operatorKey, creds.agentId, undefined, creds.laneId),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -39,6 +39,11 @@ export interface GrantSelection {
|
|
|
39
39
|
* spend names one, then the standing grant. A standing grant is the buyer
|
|
40
40
|
* bootstrap and the right default; reaching for it while an agreement grant
|
|
41
41
|
* covers the same spend would bill the wrong budget.
|
|
42
|
+
*
|
|
43
|
+
* This is a preference, never a fence. Every grant here is already one the
|
|
44
|
+
* server said this wake may spend, so picking a different one cannot reach
|
|
45
|
+
* another party's authority — it would only bill the wrong budget of the same
|
|
46
|
+
* party. That is why the mismatch is no longer a rejection above.
|
|
42
47
|
*/
|
|
43
48
|
export declare function selectPaymentGrant(grants: readonly GrantView[], spend: SpendShape): GrantSelection;
|
|
44
49
|
/**
|
|
@@ -52,18 +52,18 @@ function whyNot(grant, spend) {
|
|
|
52
52
|
if (allowed && spend.toWalletId && !allowed.includes(spend.toWalletId)) {
|
|
53
53
|
return `may only pay ${allowed.join(', ')}, and this one pays ${spend.toWalletId}`;
|
|
54
54
|
}
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
// A grant's agreement is NOT checked here, deliberately.
|
|
56
|
+
//
|
|
57
|
+
// It used to be refused when it did not match the spend's agreement, which
|
|
58
|
+
// made a customer's own grant unusable inside their second hire of the same
|
|
59
|
+
// agent, and made a standing grant unusable on any spend that named a deal.
|
|
60
|
+
// Whether a grant may be spent at all is the server's answer, and it turns on
|
|
61
|
+
// who GAVE the grant against who the agent is acting for — not on which of
|
|
62
|
+
// that party's deals is running. A list the server answered for this wake
|
|
63
|
+
// holds only grants it may spend, so there is nothing left to refuse here.
|
|
64
|
+
//
|
|
65
|
+
// The agreement still decides the ORDER below, which is a budget preference
|
|
66
|
+
// rather than a question of authority.
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
@@ -73,6 +73,11 @@ function whyNot(grant, spend) {
|
|
|
73
73
|
* spend names one, then the standing grant. A standing grant is the buyer
|
|
74
74
|
* bootstrap and the right default; reaching for it while an agreement grant
|
|
75
75
|
* covers the same spend would bill the wrong budget.
|
|
76
|
+
*
|
|
77
|
+
* This is a preference, never a fence. Every grant here is already one the
|
|
78
|
+
* server said this wake may spend, so picking a different one cannot reach
|
|
79
|
+
* another party's authority — it would only bill the wrong budget of the same
|
|
80
|
+
* party. That is why the mismatch is no longer a rejection above.
|
|
76
81
|
*/
|
|
77
82
|
export function selectPaymentGrant(grants, spend) {
|
|
78
83
|
const rejected = [];
|
|
@@ -86,10 +91,15 @@ export function selectPaymentGrant(grants, spend) {
|
|
|
86
91
|
}
|
|
87
92
|
if (fitting.length === 0)
|
|
88
93
|
return { grant: null, rejected };
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
// Explicit both ways, because the deal is no longer a fence: until it was
|
|
95
|
+
// removed, a deal-bound grant simply never reached this list on a spend that
|
|
96
|
+
// named nothing, so "prefer the standing grant" happened by accident of the
|
|
97
|
+
// rejection. Now it has to be said.
|
|
98
|
+
const standing = fitting.find((g) => !g.agreementId);
|
|
99
|
+
const preferred = spend.agreementId
|
|
100
|
+
? (fitting.find((g) => g.agreementId === spend.agreementId) ?? standing)
|
|
101
|
+
: standing;
|
|
102
|
+
return { grant: preferred ?? fitting[0], rejected };
|
|
93
103
|
}
|
|
94
104
|
/**
|
|
95
105
|
* The sentence an agent gets when nothing fits.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,3 +12,6 @@ export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiE
|
|
|
12
12
|
export { ApiError } from './types.js';
|
|
13
13
|
export type { Creds, Task, TaskState, PlanStep, PlanStepStatus, Agreement, AgreementParties, AgreementPartySide, EngagementKind, BroadcastAudience, EntryType, ContentType, MessageMetadata, MessageHandler, InboxDeliveryKind, InboxDeliveryRef, InboxChatNews, InboxProposalRef, InboxTaskRef, InboxRequestRef, InboxConnectionRequestRef, InboxHumanAttention, InboxEnvelope, InboxAckResult, InboxReadOptions, } from './types.js';
|
|
14
14
|
export type { ProposeTerms, ProposeDirectInput, ProposeBroadcastInput, ProposeAgreementData, ClaimOptions, } from './http/AgreementClient.js';
|
|
15
|
+
export { agreementLaneId, agreementIdFromLane, isAgreementLaneId, isAgreementId } from '@ziggs-ai/contracts';
|
|
16
|
+
export * from './shared/operatorKey.js';
|
|
17
|
+
export * from './utils/appUrls.js';
|
package/dist/index.js
CHANGED
|
@@ -13,3 +13,6 @@ export { RateLimitedError, isRateLimited, parseRetryAfterMs, pollSurfaceError, }
|
|
|
13
13
|
// half of them had drifted).
|
|
14
14
|
export { parseErrorMessage, parseErrorCode, throwApiError, } from './shared/apiError.js';
|
|
15
15
|
export { ApiError } from './types.js';
|
|
16
|
+
export { agreementLaneId, agreementIdFromLane, isAgreementLaneId, isAgreementId } from '@ziggs-ai/contracts';
|
|
17
|
+
export * from './shared/operatorKey.js';
|
|
18
|
+
export * from './utils/appUrls.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** JWT payload fields we read client-side (signature not verified — identity hint only). */
|
|
2
|
+
export interface OperatorKeyClaims {
|
|
3
|
+
type?: string;
|
|
4
|
+
keyId?: string;
|
|
5
|
+
ownerId?: string;
|
|
6
|
+
boundAgentId?: string | null;
|
|
7
|
+
/**
|
|
8
|
+
* How the key was authorized. MCP consent uses `mcp_oauth` or `device_code`;
|
|
9
|
+
* provisioning and other mint paths carry their own provenance.
|
|
10
|
+
*/
|
|
11
|
+
issuedVia?: string;
|
|
12
|
+
exp?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Decode operator JWT payload without verifying signature (boundAgentId). */
|
|
15
|
+
export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
|
|
16
|
+
export declare function isOperatorKeyExpired(claims: OperatorKeyClaims | null): boolean;
|
|
17
|
+
/** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
|
|
18
|
+
export declare const ISSUED_VIA_MCP_OAUTH = "mcp_oauth";
|
|
19
|
+
/** The stamp the MCP OAuth device-code flow puts on its tokens. */
|
|
20
|
+
export declare const ISSUED_VIA_DEVICE_CODE = "device_code";
|
|
21
|
+
/**
|
|
22
|
+
* Did a person board this credential through the connector directory?
|
|
23
|
+
*
|
|
24
|
+
* That is the question the tool surface turns on: a connected assistant
|
|
25
|
+
* (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
|
|
26
|
+
* anyone who configured this server themselves with an operator key gets the
|
|
27
|
+
* whole thing.
|
|
28
|
+
*
|
|
29
|
+
* Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
|
|
30
|
+
* board an assistant through that door — the authorization-code flow and the
|
|
31
|
+
* device-code flow — and both want the same narrowed surface, but the server
|
|
32
|
+
* records them as the different acts they are. An equality test against one
|
|
33
|
+
* value serves the whole catalogue to every credential from the other one.
|
|
34
|
+
*
|
|
35
|
+
* Read off the unverified payload. On the remote endpoint the backend verified
|
|
36
|
+
* the signature before this ran; on stdio the key is the caller's own. Either
|
|
37
|
+
* way the payload is the one the backend signed. And what hangs on the answer
|
|
38
|
+
* is which tools are registered, not what a call may do: every call is still
|
|
39
|
+
* authorized by the backend. Provenance may decide what is OFFERED, never what
|
|
40
|
+
* is PERMITTED.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isDirectoryBoarded(claims: OperatorKeyClaims | null): boolean;
|
|
43
|
+
/** Routing hint only. Every request is still authenticated by the backend. */
|
|
44
|
+
export declare function isMcpOAuthDelegateSession(creds: {
|
|
45
|
+
operatorKey: string;
|
|
46
|
+
agentId: string;
|
|
47
|
+
}): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Decode operator JWT payload without verifying signature (boundAgentId). */
|
|
2
|
+
export function decodeOperatorKeyClaims(token) {
|
|
3
|
+
const trimmed = token.trim();
|
|
4
|
+
const parts = trimmed.split('.');
|
|
5
|
+
if (parts.length !== 3)
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
const encoded = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
9
|
+
const json = new TextDecoder().decode(Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0)));
|
|
10
|
+
const payload = JSON.parse(json);
|
|
11
|
+
return {
|
|
12
|
+
type: payload.type,
|
|
13
|
+
keyId: payload.keyId,
|
|
14
|
+
ownerId: payload.ownerId,
|
|
15
|
+
boundAgentId: typeof payload.boundAgentId === 'string' ? payload.boundAgentId : null,
|
|
16
|
+
issuedVia: typeof payload.issuedVia === 'string' ? payload.issuedVia : undefined,
|
|
17
|
+
exp: payload.exp,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function isOperatorKeyExpired(claims) {
|
|
25
|
+
if (!claims?.exp)
|
|
26
|
+
return false;
|
|
27
|
+
return claims.exp * 1000 <= Date.now();
|
|
28
|
+
}
|
|
29
|
+
/** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
|
|
30
|
+
export const ISSUED_VIA_MCP_OAUTH = 'mcp_oauth';
|
|
31
|
+
/** The stamp the MCP OAuth device-code flow puts on its tokens. */
|
|
32
|
+
export const ISSUED_VIA_DEVICE_CODE = 'device_code';
|
|
33
|
+
/**
|
|
34
|
+
* Did a person board this credential through the connector directory?
|
|
35
|
+
*
|
|
36
|
+
* That is the question the tool surface turns on: a connected assistant
|
|
37
|
+
* (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
|
|
38
|
+
* anyone who configured this server themselves with an operator key gets the
|
|
39
|
+
* whole thing.
|
|
40
|
+
*
|
|
41
|
+
* Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
|
|
42
|
+
* board an assistant through that door — the authorization-code flow and the
|
|
43
|
+
* device-code flow — and both want the same narrowed surface, but the server
|
|
44
|
+
* records them as the different acts they are. An equality test against one
|
|
45
|
+
* value serves the whole catalogue to every credential from the other one.
|
|
46
|
+
*
|
|
47
|
+
* Read off the unverified payload. On the remote endpoint the backend verified
|
|
48
|
+
* the signature before this ran; on stdio the key is the caller's own. Either
|
|
49
|
+
* way the payload is the one the backend signed. And what hangs on the answer
|
|
50
|
+
* is which tools are registered, not what a call may do: every call is still
|
|
51
|
+
* authorized by the backend. Provenance may decide what is OFFERED, never what
|
|
52
|
+
* is PERMITTED.
|
|
53
|
+
*/
|
|
54
|
+
export function isDirectoryBoarded(claims) {
|
|
55
|
+
return (claims?.issuedVia === ISSUED_VIA_MCP_OAUTH ||
|
|
56
|
+
claims?.issuedVia === ISSUED_VIA_DEVICE_CODE);
|
|
57
|
+
}
|
|
58
|
+
/** Routing hint only. Every request is still authenticated by the backend. */
|
|
59
|
+
export function isMcpOAuthDelegateSession(creds) {
|
|
60
|
+
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
61
|
+
return (!!creds.agentId &&
|
|
62
|
+
claims?.boundAgentId === creds.agentId &&
|
|
63
|
+
isDirectoryBoarded(claims));
|
|
64
|
+
}
|