@slashfi/agents-sdk 0.8.0 → 0.9.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/agent-definitions/auth.d.ts +17 -0
- package/dist/agent-definitions/auth.d.ts.map +1 -1
- package/dist/agent-definitions/auth.js +135 -1
- package/dist/agent-definitions/auth.js.map +1 -1
- package/dist/agent-definitions/integrations.d.ts +19 -0
- package/dist/agent-definitions/integrations.d.ts.map +1 -1
- package/dist/agent-definitions/integrations.js +218 -5
- package/dist/agent-definitions/integrations.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/integration-interface.d.ts +37 -0
- package/dist/integration-interface.d.ts.map +1 -0
- package/dist/integration-interface.js +94 -0
- package/dist/integration-interface.js.map +1 -0
- package/dist/integrations-store.d.ts +33 -0
- package/dist/integrations-store.d.ts.map +1 -0
- package/dist/integrations-store.js +50 -0
- package/dist/integrations-store.js.map +1 -0
- package/dist/jwt.d.ts +86 -17
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +140 -17
- package/dist/jwt.js.map +1 -1
- package/dist/registry.d.ts +7 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +8 -21
- package/dist/registry.js.map +1 -1
- package/dist/secret-collection.d.ts +37 -0
- package/dist/secret-collection.d.ts.map +1 -0
- package/dist/secret-collection.js +37 -0
- package/dist/secret-collection.js.map +1 -0
- package/dist/server.d.ts +41 -44
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +232 -592
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/agent-definitions/auth.ts +187 -1
- package/src/agent-definitions/integrations.ts +260 -5
- package/src/index.ts +18 -4
- package/src/integration-interface.ts +118 -0
- package/src/integrations-store.ts +84 -0
- package/src/jwt.ts +233 -65
- package/src/registry.ts +17 -2
- package/src/secret-collection.ts +66 -0
- package/src/server.ts +268 -681
- package/src/types.ts +8 -1
- package/dist/slack-oauth.d.ts +0 -27
- package/dist/slack-oauth.d.ts.map +0 -1
- package/dist/slack-oauth.js +0 -48
- package/dist/slack-oauth.js.map +0 -1
- package/dist/web-pages.d.ts +0 -8
- package/dist/web-pages.d.ts.map +0 -1
- package/dist/web-pages.js +0 -169
- package/dist/web-pages.js.map +0 -1
- package/src/slack-oauth.ts +0 -66
- package/src/web-pages.ts +0 -178
package/src/jwt.ts
CHANGED
|
@@ -1,9 +1,223 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JWT utilities for auth tokens.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Supports two modes:
|
|
5
|
+
* - ES256 (asymmetric) — for production / cross-registry trust
|
|
6
|
+
* - HS256 (HMAC) — for backward compat / simple single-server setups
|
|
7
|
+
*
|
|
8
|
+
* Uses `jose` library for all crypto operations.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
SignJWT,
|
|
13
|
+
jwtVerify,
|
|
14
|
+
generateKeyPair,
|
|
15
|
+
exportJWK,
|
|
16
|
+
importJWK,
|
|
17
|
+
createRemoteJWKSet,
|
|
18
|
+
type JWTPayload,
|
|
19
|
+
|
|
20
|
+
type JWK,
|
|
21
|
+
} from "jose";
|
|
22
|
+
|
|
23
|
+
// ============================================
|
|
24
|
+
// Types
|
|
25
|
+
// ============================================
|
|
26
|
+
|
|
27
|
+
/** JWT payload for auth tokens */
|
|
28
|
+
export interface AgentJwtPayload {
|
|
29
|
+
/** Subject - the client ID or user ID */
|
|
30
|
+
sub: string;
|
|
31
|
+
/** Client/user name */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Issuer URL */
|
|
34
|
+
iss?: string;
|
|
35
|
+
/** Tenant ID */
|
|
36
|
+
tenantId?: string;
|
|
37
|
+
/** User ID (when acting on behalf of a user) */
|
|
38
|
+
userId?: string;
|
|
39
|
+
/** Scopes */
|
|
40
|
+
scopes: string[];
|
|
41
|
+
/** Identities (for cross-registry provisioning) */
|
|
42
|
+
identities?: Array<{ provider: string; id: string; [key: string]: unknown }>;
|
|
43
|
+
/** Issued at (unix seconds) */
|
|
44
|
+
iat: number;
|
|
45
|
+
/** Expires at (unix seconds) */
|
|
46
|
+
exp: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A signing key with metadata */
|
|
50
|
+
export interface SigningKey {
|
|
51
|
+
/** Unique key ID */
|
|
52
|
+
kid: string;
|
|
53
|
+
/** Private key (for signing) */
|
|
54
|
+
privateKey: CryptoKey;
|
|
55
|
+
/** Public key (for verification + JWKS) */
|
|
56
|
+
publicKey: CryptoKey;
|
|
57
|
+
/** Algorithm */
|
|
58
|
+
alg: string;
|
|
59
|
+
/** Status */
|
|
60
|
+
status: "active" | "deprecated" | "revoked";
|
|
61
|
+
/** When this key was created */
|
|
62
|
+
createdAt: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Exported key pair for storage */
|
|
66
|
+
export interface ExportedKeyPair {
|
|
67
|
+
kid: string;
|
|
68
|
+
alg: string;
|
|
69
|
+
privateKeyJwk: JWK;
|
|
70
|
+
publicKeyJwk: JWK;
|
|
71
|
+
status: "active" | "deprecated" | "revoked";
|
|
72
|
+
createdAt: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================
|
|
76
|
+
// Key Generation
|
|
77
|
+
// ============================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate a new ES256 signing key pair.
|
|
81
|
+
*/
|
|
82
|
+
export async function generateSigningKey(kid?: string): Promise<SigningKey> {
|
|
83
|
+
const { privateKey, publicKey } = await generateKeyPair("ES256");
|
|
84
|
+
return {
|
|
85
|
+
kid: kid ?? `key-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
86
|
+
privateKey,
|
|
87
|
+
publicKey,
|
|
88
|
+
alg: "ES256",
|
|
89
|
+
status: "active",
|
|
90
|
+
createdAt: Date.now(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Export a signing key to JWK format (for storage).
|
|
96
|
+
*/
|
|
97
|
+
export async function exportSigningKey(key: SigningKey): Promise<ExportedKeyPair> {
|
|
98
|
+
const privateKeyJwk = await exportJWK(key.privateKey);
|
|
99
|
+
const publicKeyJwk = await exportJWK(key.publicKey);
|
|
100
|
+
return {
|
|
101
|
+
kid: key.kid,
|
|
102
|
+
alg: key.alg,
|
|
103
|
+
privateKeyJwk,
|
|
104
|
+
publicKeyJwk,
|
|
105
|
+
status: key.status,
|
|
106
|
+
createdAt: key.createdAt,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Import a signing key from stored JWK format.
|
|
112
|
+
*/
|
|
113
|
+
export async function importSigningKey(exported: ExportedKeyPair): Promise<SigningKey> {
|
|
114
|
+
const privateKey = await importJWK(exported.privateKeyJwk, exported.alg) as CryptoKey;
|
|
115
|
+
const publicKey = await importJWK(exported.publicKeyJwk, exported.alg) as CryptoKey;
|
|
116
|
+
return {
|
|
117
|
+
kid: exported.kid,
|
|
118
|
+
privateKey,
|
|
119
|
+
publicKey,
|
|
120
|
+
alg: exported.alg,
|
|
121
|
+
status: exported.status,
|
|
122
|
+
createdAt: exported.createdAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Build a JWKS (JSON Web Key Set) from signing keys.
|
|
128
|
+
* Only includes public keys.
|
|
129
|
+
*/
|
|
130
|
+
export async function buildJwks(keys: SigningKey[]): Promise<{ keys: JWK[] }> {
|
|
131
|
+
const jwks: JWK[] = [];
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
if (key.status === "revoked") continue;
|
|
134
|
+
const jwk = await exportJWK(key.publicKey);
|
|
135
|
+
jwk.kid = key.kid;
|
|
136
|
+
jwk.alg = key.alg;
|
|
137
|
+
jwk.use = "sig";
|
|
138
|
+
jwks.push(jwk);
|
|
139
|
+
}
|
|
140
|
+
return { keys: jwks };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============================================
|
|
144
|
+
// Signing (ES256)
|
|
145
|
+
// ============================================
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Sign a JWT with ES256 using the server's private key.
|
|
6
149
|
*/
|
|
150
|
+
export async function signJwtES256(
|
|
151
|
+
payload: Omit<AgentJwtPayload, "iat" | "exp"> & { iat?: number; exp?: number },
|
|
152
|
+
privateKey: CryptoKey,
|
|
153
|
+
kid: string,
|
|
154
|
+
issuer?: string,
|
|
155
|
+
expiresIn?: string,
|
|
156
|
+
): Promise<string> {
|
|
157
|
+
let builder = new SignJWT(payload as unknown as JWTPayload)
|
|
158
|
+
.setProtectedHeader({ alg: "ES256", kid })
|
|
159
|
+
.setIssuedAt();
|
|
160
|
+
|
|
161
|
+
if (issuer) builder = builder.setIssuer(issuer);
|
|
162
|
+
if (payload.sub) builder = builder.setSubject(payload.sub);
|
|
163
|
+
if (expiresIn) {
|
|
164
|
+
builder = builder.setExpirationTime(expiresIn);
|
|
165
|
+
} else if (payload.exp) {
|
|
166
|
+
builder = builder.setExpirationTime(payload.exp);
|
|
167
|
+
} else {
|
|
168
|
+
builder = builder.setExpirationTime("1h");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return builder.sign(privateKey);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ============================================
|
|
175
|
+
// Verification
|
|
176
|
+
// ============================================
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Verify a JWT against a local public key.
|
|
180
|
+
*/
|
|
181
|
+
export async function verifyJwtLocal(
|
|
182
|
+
token: string,
|
|
183
|
+
publicKey: CryptoKey,
|
|
184
|
+
): Promise<AgentJwtPayload | null> {
|
|
185
|
+
try {
|
|
186
|
+
const { payload } = await jwtVerify(token, publicKey);
|
|
187
|
+
return payload as unknown as AgentJwtPayload;
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** JWKS cache for remote issuers */
|
|
194
|
+
const jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Verify a JWT against a remote issuer's JWKS.
|
|
198
|
+
* Fetches and caches the JWKS from the issuer's /.well-known/jwks.json
|
|
199
|
+
*/
|
|
200
|
+
export async function verifyJwtFromIssuer(
|
|
201
|
+
token: string,
|
|
202
|
+
issuerUrl: string,
|
|
203
|
+
): Promise<AgentJwtPayload | null> {
|
|
204
|
+
try {
|
|
205
|
+
const jwksUrl = issuerUrl.replace(/\/$/, "") + "/.well-known/jwks.json";
|
|
206
|
+
let jwks = jwksCache.get(jwksUrl);
|
|
207
|
+
if (!jwks) {
|
|
208
|
+
jwks = createRemoteJWKSet(new URL(jwksUrl));
|
|
209
|
+
jwksCache.set(jwksUrl, jwks);
|
|
210
|
+
}
|
|
211
|
+
const { payload } = await jwtVerify(token, jwks);
|
|
212
|
+
return payload as unknown as AgentJwtPayload;
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ============================================
|
|
219
|
+
// Legacy HMAC (backward compat)
|
|
220
|
+
// ============================================
|
|
7
221
|
|
|
8
222
|
const encoder = new TextEncoder();
|
|
9
223
|
|
|
@@ -20,106 +234,60 @@ function base64UrlDecode(str: string): Uint8Array {
|
|
|
20
234
|
|
|
21
235
|
async function hmacSign(data: string, secret: string): Promise<Uint8Array> {
|
|
22
236
|
const key = await crypto.subtle.importKey(
|
|
23
|
-
"raw",
|
|
24
|
-
|
|
25
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
26
|
-
false,
|
|
27
|
-
["sign"],
|
|
237
|
+
"raw", encoder.encode(secret),
|
|
238
|
+
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"],
|
|
28
239
|
);
|
|
29
240
|
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
30
241
|
return new Uint8Array(sig);
|
|
31
242
|
}
|
|
32
243
|
|
|
33
|
-
async function hmacVerify(
|
|
34
|
-
data: string,
|
|
35
|
-
signature: Uint8Array,
|
|
36
|
-
secret: string,
|
|
37
|
-
): Promise<boolean> {
|
|
244
|
+
async function hmacVerify(data: string, signature: Uint8Array, secret: string): Promise<boolean> {
|
|
38
245
|
const key = await crypto.subtle.importKey(
|
|
39
|
-
"raw",
|
|
40
|
-
|
|
41
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
42
|
-
false,
|
|
43
|
-
["verify"],
|
|
44
|
-
);
|
|
45
|
-
return crypto.subtle.verify(
|
|
46
|
-
"HMAC",
|
|
47
|
-
key,
|
|
48
|
-
signature.buffer as ArrayBuffer,
|
|
49
|
-
encoder.encode(data),
|
|
246
|
+
"raw", encoder.encode(secret),
|
|
247
|
+
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"],
|
|
50
248
|
);
|
|
249
|
+
return crypto.subtle.verify("HMAC", key, signature.buffer as ArrayBuffer, encoder.encode(data));
|
|
51
250
|
}
|
|
52
251
|
|
|
53
|
-
/**
|
|
54
|
-
export
|
|
55
|
-
/** Subject - the client ID */
|
|
56
|
-
sub: string;
|
|
57
|
-
/** Client name */
|
|
58
|
-
name: string;
|
|
59
|
-
/** Tenant ID */
|
|
60
|
-
tenantId?: string;
|
|
61
|
-
/** Scopes */
|
|
62
|
-
scopes: string[];
|
|
63
|
-
/** Issued at (unix seconds) */
|
|
64
|
-
iat: number;
|
|
65
|
-
/** Expires at (unix seconds) */
|
|
66
|
-
exp: number;
|
|
67
|
-
}
|
|
252
|
+
/** @deprecated Use AgentJwtPayload instead */
|
|
253
|
+
export type JwtPayload = AgentJwtPayload;
|
|
68
254
|
|
|
69
255
|
/**
|
|
70
|
-
* Sign a JWT with HMAC-SHA256.
|
|
71
|
-
*
|
|
72
|
-
* @param payload - JWT payload (client_id, scopes, etc.)
|
|
73
|
-
* @param secret - Signing secret (the client's secret hash)
|
|
74
|
-
* @returns Signed JWT string
|
|
256
|
+
* Sign a JWT with HMAC-SHA256 (legacy).
|
|
257
|
+
* @deprecated Use signJwtES256 for new code.
|
|
75
258
|
*/
|
|
76
259
|
export async function signJwt(
|
|
77
|
-
payload:
|
|
260
|
+
payload: AgentJwtPayload,
|
|
78
261
|
secret: string,
|
|
79
262
|
): Promise<string> {
|
|
80
263
|
const header = { alg: "HS256", typ: "JWT" };
|
|
81
|
-
|
|
82
264
|
const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));
|
|
83
265
|
const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(payload)));
|
|
84
266
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
85
|
-
|
|
86
267
|
const signature = await hmacSign(signingInput, secret);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return `${signingInput}.${signatureB64}`;
|
|
268
|
+
return `${signingInput}.${base64UrlEncode(signature)}`;
|
|
90
269
|
}
|
|
91
270
|
|
|
92
271
|
/**
|
|
93
|
-
* Verify and decode a JWT.
|
|
94
|
-
*
|
|
95
|
-
* @param token - JWT string
|
|
96
|
-
* @param secret - Signing secret to verify against
|
|
97
|
-
* @returns Decoded payload, or null if invalid/expired
|
|
272
|
+
* Verify and decode a JWT (HMAC-SHA256, legacy).
|
|
273
|
+
* @deprecated Use verifyJwtLocal or verifyJwtFromIssuer for new code.
|
|
98
274
|
*/
|
|
99
275
|
export async function verifyJwt(
|
|
100
276
|
token: string,
|
|
101
277
|
secret: string,
|
|
102
|
-
): Promise<
|
|
278
|
+
): Promise<AgentJwtPayload | null> {
|
|
103
279
|
const parts = token.split(".");
|
|
104
280
|
if (parts.length !== 3) return null;
|
|
105
|
-
|
|
106
281
|
const [headerB64, payloadB64, signatureB64] = parts;
|
|
107
282
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
108
|
-
|
|
109
283
|
try {
|
|
110
284
|
const signature = base64UrlDecode(signatureB64);
|
|
111
285
|
const valid = await hmacVerify(signingInput, signature, secret);
|
|
112
286
|
if (!valid) return null;
|
|
113
|
-
|
|
114
287
|
const payload = JSON.parse(
|
|
115
288
|
new TextDecoder().decode(base64UrlDecode(payloadB64)),
|
|
116
|
-
) as
|
|
117
|
-
|
|
118
|
-
// Check expiration
|
|
119
|
-
if (payload.exp && payload.exp < Date.now() / 1000) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
|
|
289
|
+
) as AgentJwtPayload;
|
|
290
|
+
if (payload.exp && payload.exp < Date.now() / 1000) return null;
|
|
123
291
|
return payload;
|
|
124
292
|
} catch {
|
|
125
293
|
return null;
|
package/src/registry.ts
CHANGED
|
@@ -37,6 +37,8 @@ const DEFAULT_SUPPORTED_ACTIONS: AgentAction[] = [
|
|
|
37
37
|
export interface AgentRegistryOptions {
|
|
38
38
|
/** Default visibility for agents without explicit visibility */
|
|
39
39
|
defaultVisibility?: Visibility;
|
|
40
|
+
/** Factory to enrich ToolContext with application-specific data */
|
|
41
|
+
contextFactory?: ContextFactory;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -82,6 +84,12 @@ export interface AgentRegistry {
|
|
|
82
84
|
* });
|
|
83
85
|
* ```
|
|
84
86
|
*/
|
|
87
|
+
/**
|
|
88
|
+
* Factory function that enriches the base ToolContext with application-specific data.
|
|
89
|
+
* Called before every tool execution.
|
|
90
|
+
*/
|
|
91
|
+
export type ContextFactory = (baseCtx: import("./types.js").ToolContext) => import("./types.js").ToolContext;
|
|
92
|
+
|
|
85
93
|
export function createAgentRegistry(
|
|
86
94
|
options: AgentRegistryOptions = {},
|
|
87
95
|
): AgentRegistry {
|
|
@@ -165,6 +173,8 @@ export function createAgentRegistry(
|
|
|
165
173
|
return (
|
|
166
174
|
callerType === "agent" || (callerType != null && callerId != null)
|
|
167
175
|
);
|
|
176
|
+
case "authenticated":
|
|
177
|
+
return callerId != null && callerId !== "anonymous";
|
|
168
178
|
case "private":
|
|
169
179
|
return callerId === agent.path;
|
|
170
180
|
default:
|
|
@@ -276,12 +286,12 @@ export function createAgentRegistry(
|
|
|
276
286
|
) {
|
|
277
287
|
return {
|
|
278
288
|
success: false,
|
|
279
|
-
error: `Access denied to tool: ${request.tool}`,
|
|
289
|
+
error: `Access denied to tool: ${request.tool} (visibility=${tool.visibility}, callerId=${request.callerId}, callerType=${request.callerType})`,
|
|
280
290
|
code: "ACCESS_DENIED",
|
|
281
291
|
} as CallAgentErrorResponse;
|
|
282
292
|
}
|
|
283
293
|
|
|
284
|
-
|
|
294
|
+
let ctx: ToolContext = {
|
|
285
295
|
tenantId: "default",
|
|
286
296
|
agentPath: agent.path,
|
|
287
297
|
callerId: request.callerId ?? "unknown",
|
|
@@ -289,6 +299,11 @@ export function createAgentRegistry(
|
|
|
289
299
|
metadata: request.metadata,
|
|
290
300
|
};
|
|
291
301
|
|
|
302
|
+
// Apply contextFactory if provided
|
|
303
|
+
if (options.contextFactory) {
|
|
304
|
+
ctx = options.contextFactory(ctx);
|
|
305
|
+
}
|
|
306
|
+
|
|
292
307
|
try {
|
|
293
308
|
if (!tool.execute) {
|
|
294
309
|
return {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Collection
|
|
3
|
+
*
|
|
4
|
+
* Manages pending secret collection forms (one-time tokens).
|
|
5
|
+
* Used by @integrations collect_secrets and the server's /secrets/collect endpoint.
|
|
6
|
+
*
|
|
7
|
+
* Exported for use in custom server implementations.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ============================================
|
|
11
|
+
// Types
|
|
12
|
+
// ============================================
|
|
13
|
+
|
|
14
|
+
export interface PendingCollectionField {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
required: boolean;
|
|
18
|
+
secret: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PendingCollection {
|
|
22
|
+
agent: string;
|
|
23
|
+
tool: string;
|
|
24
|
+
params: Record<string, unknown>;
|
|
25
|
+
fields: PendingCollectionField[];
|
|
26
|
+
auth?: {
|
|
27
|
+
callerId: string;
|
|
28
|
+
callerType: string;
|
|
29
|
+
scopes?: string[];
|
|
30
|
+
isRoot?: boolean;
|
|
31
|
+
};
|
|
32
|
+
createdAt: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// Storage (with TTL cleanup)
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
/** Default TTL for pending collections: 15 minutes */
|
|
40
|
+
const COLLECTION_TTL_MS = 15 * 60 * 1000;
|
|
41
|
+
|
|
42
|
+
/** Pending secret collection forms, keyed by one-time token */
|
|
43
|
+
export const pendingCollections = new Map<string, PendingCollection>();
|
|
44
|
+
|
|
45
|
+
/** Generate a random one-time token for secret collection */
|
|
46
|
+
export function generateCollectionToken(): string {
|
|
47
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
48
|
+
let token = "";
|
|
49
|
+
for (let i = 0; i < 48; i++) {
|
|
50
|
+
token += chars[Math.floor(Math.random() * chars.length)];
|
|
51
|
+
}
|
|
52
|
+
return token;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Clean up expired pending collections.
|
|
57
|
+
* Call this periodically or before lookups.
|
|
58
|
+
*/
|
|
59
|
+
export function cleanupExpiredCollections(): void {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
for (const [token, pending] of pendingCollections) {
|
|
62
|
+
if (now - pending.createdAt > COLLECTION_TTL_MS) {
|
|
63
|
+
pendingCollections.delete(token);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|