nexus-ai-pro 2.2.0 → 2.3.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/API_STABILITY.md +39 -0
- package/CHANGELOG.md +142 -1
- package/README.md +10 -5
- package/dist/agent/capabilities.d.ts +39 -0
- package/dist/agent/capabilities.js +36 -0
- package/dist/agent/create-agent.d.ts +61 -11
- package/dist/agent/create-agent.js +136 -22
- package/dist/agent/index.d.ts +3 -2
- package/dist/agent/index.js +1 -1
- package/dist/agent/middleware/approval.d.ts +26 -0
- package/dist/agent/middleware/approval.js +23 -0
- package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist/agent/middleware/context-editor.d.ts +52 -0
- package/dist/agent/middleware/context-editor.js +66 -0
- package/dist/agent/middleware/filesystem.d.ts +44 -0
- package/dist/agent/middleware/filesystem.js +83 -0
- package/dist/agent/middleware/index.d.ts +19 -0
- package/dist/agent/middleware/index.js +9 -0
- package/dist/agent/middleware/limits.d.ts +42 -0
- package/dist/agent/middleware/limits.js +52 -0
- package/dist/agent/middleware/models.d.ts +48 -0
- package/dist/agent/middleware/models.js +44 -0
- package/dist/agent/middleware/pii.d.ts +67 -0
- package/dist/agent/middleware/pii.js +173 -0
- package/dist/agent/middleware/retry.d.ts +66 -0
- package/dist/agent/middleware/retry.js +59 -0
- package/dist/agent/middleware/shared.d.ts +41 -0
- package/dist/agent/middleware/shared.js +73 -0
- package/dist/agent/middleware/tool-selector.d.ts +64 -0
- package/dist/agent/middleware/tool-selector.js +106 -0
- package/dist/agent/permissions.d.ts +113 -0
- package/dist/agent/permissions.js +180 -0
- package/dist/agent/sandbox.d.ts +151 -0
- package/dist/agent/sandbox.js +257 -0
- package/dist/agent/tool.d.ts +8 -3
- package/dist/agent/tool.js +14 -3
- package/dist/deep-agent/index.d.ts +109 -0
- package/dist/deep-agent/index.js +216 -0
- package/dist/graph/functional.d.ts +9 -0
- package/dist/graph/functional.js +20 -3
- package/dist/graph/graph.js +25 -4
- package/dist/graph/lint.d.ts +4 -1
- package/dist/graph/lint.js +35 -0
- package/dist/postgres/fulltext.d.ts +67 -0
- package/dist/postgres/fulltext.js +91 -0
- package/dist/rag/elasticsearch.d.ts +71 -0
- package/dist/rag/elasticsearch.js +130 -0
- package/dist/rag/pipeline.d.ts +140 -0
- package/dist/rag/pipeline.js +187 -0
- package/dist/rag/rerankers.d.ts +92 -0
- package/dist/rag/rerankers.js +97 -0
- package/dist/rag/retrievers.d.ts +12 -1
- package/dist/server/assistant.d.ts +5 -1
- package/dist/server/assistant.js +4 -2
- package/dist/server/auth.d.ts +250 -0
- package/dist/server/auth.js +392 -0
- package/dist/store/memory.d.ts +10 -0
- package/dist/store/memory.js +10 -0
- package/dist/types/graph.d.ts +38 -0
- package/dist/types/messages.d.ts +36 -2
- package/dist/types/principal.d.ts +21 -0
- package/dist/types/principal.js +1 -0
- package/dist/types/server.d.ts +2 -9
- package/dist/utils/tenant.d.ts +11 -0
- package/dist/utils/tenant.js +14 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/agent/capabilities.d.ts +39 -0
- package/dist-cjs/agent/capabilities.js +41 -0
- package/dist-cjs/agent/create-agent.d.ts +61 -11
- package/dist-cjs/agent/create-agent.js +136 -22
- package/dist-cjs/agent/index.d.ts +3 -2
- package/dist-cjs/agent/index.js +4 -4
- package/dist-cjs/agent/middleware/approval.d.ts +26 -0
- package/dist-cjs/agent/middleware/approval.js +26 -0
- package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
- package/dist-cjs/agent/middleware/context-editor.js +70 -0
- package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
- package/dist-cjs/agent/middleware/filesystem.js +86 -0
- package/dist-cjs/agent/middleware/index.d.ts +19 -0
- package/dist-cjs/agent/middleware/index.js +28 -0
- package/dist-cjs/agent/middleware/limits.d.ts +42 -0
- package/dist-cjs/agent/middleware/limits.js +57 -0
- package/dist-cjs/agent/middleware/models.d.ts +48 -0
- package/dist-cjs/agent/middleware/models.js +48 -0
- package/dist-cjs/agent/middleware/pii.d.ts +67 -0
- package/dist-cjs/agent/middleware/pii.js +178 -0
- package/dist-cjs/agent/middleware/retry.d.ts +66 -0
- package/dist-cjs/agent/middleware/retry.js +63 -0
- package/dist-cjs/agent/middleware/shared.d.ts +41 -0
- package/dist-cjs/agent/middleware/shared.js +83 -0
- package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
- package/dist-cjs/agent/middleware/tool-selector.js +109 -0
- package/dist-cjs/agent/permissions.d.ts +113 -0
- package/dist-cjs/agent/permissions.js +189 -0
- package/dist-cjs/agent/sandbox.d.ts +151 -0
- package/dist-cjs/agent/sandbox.js +297 -0
- package/dist-cjs/agent/tool.d.ts +8 -3
- package/dist-cjs/agent/tool.js +14 -3
- package/dist-cjs/deep-agent/index.d.ts +109 -0
- package/dist-cjs/deep-agent/index.js +219 -0
- package/dist-cjs/graph/functional.d.ts +9 -0
- package/dist-cjs/graph/functional.js +20 -3
- package/dist-cjs/graph/graph.js +25 -4
- package/dist-cjs/graph/lint.d.ts +4 -1
- package/dist-cjs/graph/lint.js +35 -0
- package/dist-cjs/postgres/fulltext.d.ts +67 -0
- package/dist-cjs/postgres/fulltext.js +129 -0
- package/dist-cjs/rag/elasticsearch.d.ts +71 -0
- package/dist-cjs/rag/elasticsearch.js +135 -0
- package/dist-cjs/rag/pipeline.d.ts +140 -0
- package/dist-cjs/rag/pipeline.js +191 -0
- package/dist-cjs/rag/rerankers.d.ts +92 -0
- package/dist-cjs/rag/rerankers.js +107 -0
- package/dist-cjs/rag/retrievers.d.ts +12 -1
- package/dist-cjs/server/assistant.d.ts +5 -1
- package/dist-cjs/server/assistant.js +4 -2
- package/dist-cjs/server/auth.d.ts +250 -0
- package/dist-cjs/server/auth.js +402 -0
- package/dist-cjs/store/memory.d.ts +10 -0
- package/dist-cjs/store/memory.js +10 -0
- package/dist-cjs/types/graph.d.ts +38 -0
- package/dist-cjs/types/messages.d.ts +36 -2
- package/dist-cjs/types/principal.d.ts +21 -0
- package/dist-cjs/types/principal.js +2 -0
- package/dist-cjs/types/server.d.ts +2 -9
- package/dist-cjs/utils/tenant.d.ts +11 -0
- package/dist-cjs/utils/tenant.js +15 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +4 -3
- package/package.json +118 -1
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JwtError = void 0;
|
|
4
|
+
exports.createJwtVerifier = createJwtVerifier;
|
|
5
|
+
exports.jwtAuth = jwtAuth;
|
|
6
|
+
exports.hashApiKey = hashApiKey;
|
|
7
|
+
exports.apiKeyAuth = apiKeyAuth;
|
|
8
|
+
exports.trustedProxyAuth = trustedProxyAuth;
|
|
9
|
+
exports.anyAuth = anyAuth;
|
|
10
|
+
const tenant_js_1 = require("../utils/tenant.js");
|
|
11
|
+
class JwtError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code, message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = 'JwtError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.JwtError = JwtError;
|
|
20
|
+
const HMAC = ['HS256', 'HS384', 'HS512'];
|
|
21
|
+
const ASYMMETRIC = [
|
|
22
|
+
'RS256',
|
|
23
|
+
'RS384',
|
|
24
|
+
'RS512',
|
|
25
|
+
'PS256',
|
|
26
|
+
'PS384',
|
|
27
|
+
'PS512',
|
|
28
|
+
'ES256',
|
|
29
|
+
'ES384',
|
|
30
|
+
'ES512',
|
|
31
|
+
'EdDSA',
|
|
32
|
+
];
|
|
33
|
+
function cryptoParams(alg) {
|
|
34
|
+
const bits = alg.slice(2);
|
|
35
|
+
const hash = `SHA-${bits === 'DSA' ? '512' : bits}`;
|
|
36
|
+
switch (alg.slice(0, 2)) {
|
|
37
|
+
case 'HS':
|
|
38
|
+
return { importAs: { name: 'HMAC', hash }, verifyAs: { name: 'HMAC' } };
|
|
39
|
+
case 'RS':
|
|
40
|
+
return { importAs: { name: 'RSASSA-PKCS1-v1_5', hash }, verifyAs: { name: 'RSASSA-PKCS1-v1_5' } };
|
|
41
|
+
case 'PS':
|
|
42
|
+
return { importAs: { name: 'RSA-PSS', hash }, verifyAs: { name: 'RSA-PSS', saltLength: Number(bits) / 8 } };
|
|
43
|
+
case 'ES': {
|
|
44
|
+
const namedCurve = bits === '512' ? 'P-521' : `P-${bits}`;
|
|
45
|
+
return { importAs: { name: 'ECDSA', namedCurve }, verifyAs: { name: 'ECDSA', hash } };
|
|
46
|
+
}
|
|
47
|
+
default:
|
|
48
|
+
return { importAs: { name: 'Ed25519' }, verifyAs: { name: 'Ed25519' } };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function jwkFits(jwk, alg) {
|
|
52
|
+
if (jwk.alg && jwk.alg !== alg && !(alg === 'EdDSA' && jwk.alg === 'Ed25519'))
|
|
53
|
+
return false;
|
|
54
|
+
if (jwk.use && jwk.use !== 'sig')
|
|
55
|
+
return false;
|
|
56
|
+
if (jwk.key_ops && !jwk.key_ops.includes('verify'))
|
|
57
|
+
return false;
|
|
58
|
+
if (alg.startsWith('RS') || alg.startsWith('PS'))
|
|
59
|
+
return jwk.kty === 'RSA';
|
|
60
|
+
if (alg.startsWith('ES')) {
|
|
61
|
+
const curve = alg === 'ES512' ? 'P-521' : `P-${alg.slice(2)}`;
|
|
62
|
+
return jwk.kty === 'EC' && jwk.crv === curve;
|
|
63
|
+
}
|
|
64
|
+
if (alg === 'EdDSA')
|
|
65
|
+
return jwk.kty === 'OKP' && jwk.crv === 'Ed25519';
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
function createJwtVerifier(options) {
|
|
69
|
+
const sources = [options.secret, options.publicKey, options.jwks].filter((source) => source !== undefined);
|
|
70
|
+
if (sources.length !== 1) {
|
|
71
|
+
throw new TypeError('createJwtVerifier() needs exactly one of secret, publicKey, or jwks');
|
|
72
|
+
}
|
|
73
|
+
if (options.jwks !== undefined && options.audience === undefined) {
|
|
74
|
+
throw new TypeError('A key set signs tokens for every application its provider serves: set audience, or audience: false to accept any');
|
|
75
|
+
}
|
|
76
|
+
const allowed = new Set((options.algorithms ?? (options.secret !== undefined ? HMAC : ASYMMETRIC)).filter((alg) => options.secret !== undefined ? HMAC.includes(alg) : ASYMMETRIC.includes(alg)));
|
|
77
|
+
if (allowed.size === 0)
|
|
78
|
+
throw new TypeError('None of the algorithms given can be used with this kind of key');
|
|
79
|
+
const now = options.now ?? Date.now;
|
|
80
|
+
const tolerance = options.clockToleranceSec ?? 60;
|
|
81
|
+
const subtle = globalThis.crypto.subtle;
|
|
82
|
+
const imported = new Map();
|
|
83
|
+
const importOnce = (cacheKey, load) => {
|
|
84
|
+
let key = imported.get(cacheKey);
|
|
85
|
+
if (!key) {
|
|
86
|
+
key = load();
|
|
87
|
+
key.catch(() => imported.delete(cacheKey));
|
|
88
|
+
imported.set(cacheKey, key);
|
|
89
|
+
}
|
|
90
|
+
return key;
|
|
91
|
+
};
|
|
92
|
+
let keySet = options.jwks !== undefined && typeof options.jwks === 'object' && !(options.jwks instanceof URL)
|
|
93
|
+
? { keys: options.jwks.keys, fetchedAt: Number.POSITIVE_INFINITY }
|
|
94
|
+
: undefined;
|
|
95
|
+
let lastUnknownFetch = Number.NEGATIVE_INFINITY;
|
|
96
|
+
let inFlight;
|
|
97
|
+
const refresh = () => {
|
|
98
|
+
if (inFlight)
|
|
99
|
+
return inFlight;
|
|
100
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
101
|
+
inFlight = (async () => {
|
|
102
|
+
const response = await fetcher(String(options.jwks), { headers: { accept: 'application/json' } });
|
|
103
|
+
if (!response.ok)
|
|
104
|
+
throw new JwtError('JWT_KEY', `The key set answered ${response.status}`);
|
|
105
|
+
const body = (await response.json());
|
|
106
|
+
if (!Array.isArray(body.keys))
|
|
107
|
+
throw new JwtError('JWT_KEY', 'The key set has no keys');
|
|
108
|
+
keySet = { keys: body.keys, fetchedAt: now() };
|
|
109
|
+
imported.clear();
|
|
110
|
+
})().finally(() => {
|
|
111
|
+
inFlight = undefined;
|
|
112
|
+
});
|
|
113
|
+
return inFlight;
|
|
114
|
+
};
|
|
115
|
+
const keyFor = async (header, alg) => {
|
|
116
|
+
const { importAs } = cryptoParams(alg);
|
|
117
|
+
if (options.secret !== undefined) {
|
|
118
|
+
const raw = typeof options.secret === 'string' ? new TextEncoder().encode(options.secret) : options.secret;
|
|
119
|
+
return importOnce(`secret:${alg}`, () => subtle.importKey('raw', raw, importAs, false, ['verify']));
|
|
120
|
+
}
|
|
121
|
+
if (options.publicKey !== undefined) {
|
|
122
|
+
const key = options.publicKey;
|
|
123
|
+
if (typeof key === 'object' && 'type' in key && 'algorithm' in key)
|
|
124
|
+
return key;
|
|
125
|
+
if (typeof key === 'string') {
|
|
126
|
+
return importOnce(`pem:${alg}`, () => subtle.importKey('spki', pemToDer(key), importAs, false, ['verify']));
|
|
127
|
+
}
|
|
128
|
+
if (!jwkFits(key, alg))
|
|
129
|
+
throw new JwtError('JWT_KEY', `The key cannot verify ${alg}`);
|
|
130
|
+
return importOnce(`jwk:${alg}`, () => subtle.importKey('jwk', key, importAs, false, ['verify']));
|
|
131
|
+
}
|
|
132
|
+
const remote = typeof options.jwks === 'string' || options.jwks instanceof URL;
|
|
133
|
+
const cacheMs = options.jwksCacheMs ?? 10 * 60_000;
|
|
134
|
+
if (remote && (!keySet || now() - keySet.fetchedAt > cacheMs))
|
|
135
|
+
await refresh();
|
|
136
|
+
const find = () => keySet?.keys.filter((jwk) => (header.kid === undefined || jwk.kid === header.kid) && jwkFits(jwk, alg)) ?? [];
|
|
137
|
+
let matches = find();
|
|
138
|
+
if (matches.length === 0 && remote && now() - lastUnknownFetch >= (options.jwksCooldownMs ?? 30_000)) {
|
|
139
|
+
lastUnknownFetch = now();
|
|
140
|
+
await refresh();
|
|
141
|
+
matches = find();
|
|
142
|
+
}
|
|
143
|
+
const [jwk] = matches;
|
|
144
|
+
if (!jwk || (header.kid === undefined && matches.length > 1)) {
|
|
145
|
+
throw new JwtError('JWT_KEY', header.kid ? `No key "${header.kid}" for ${alg}` : `No single key for ${alg}`);
|
|
146
|
+
}
|
|
147
|
+
return importOnce(`jwks:${jwk.kid ?? ''}:${alg}`, () => subtle.importKey('jwk', jwk, importAs, false, ['verify']));
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
async verify(token) {
|
|
151
|
+
const parts = token.split('.');
|
|
152
|
+
if (parts.length !== 3 || parts.some((part) => !/^[A-Za-z0-9_-]*$/.test(part)) || !parts[2]) {
|
|
153
|
+
throw new JwtError('JWT_MALFORMED', 'The token is not a signed JWT');
|
|
154
|
+
}
|
|
155
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
156
|
+
const header = decodeJson(encodedHeader, 'header');
|
|
157
|
+
const payload = decodeJson(encodedPayload, 'payload');
|
|
158
|
+
if (typeof header.alg !== 'string' || !allowed.has(header.alg)) {
|
|
159
|
+
throw new JwtError('JWT_ALGORITHM', `The algorithm "${String(header.alg)}" is not accepted`);
|
|
160
|
+
}
|
|
161
|
+
if (header.crit !== undefined)
|
|
162
|
+
throw new JwtError('JWT_MALFORMED', 'The token has critical headers this verifier does not understand');
|
|
163
|
+
const alg = header.alg;
|
|
164
|
+
const key = await keyFor(header, alg).catch((error) => {
|
|
165
|
+
throw error instanceof JwtError ? error : new JwtError('JWT_KEY', `No usable key for ${alg}`);
|
|
166
|
+
});
|
|
167
|
+
const valid = await subtle
|
|
168
|
+
.verify(cryptoParams(alg).verifyAs, key, base64UrlBytes(encodedSignature), new TextEncoder().encode(`${encodedHeader}.${encodedPayload}`))
|
|
169
|
+
.catch(() => false);
|
|
170
|
+
if (!valid)
|
|
171
|
+
throw new JwtError('JWT_SIGNATURE', 'The signature does not match');
|
|
172
|
+
checkClaims(payload, options, now() / 1000, tolerance);
|
|
173
|
+
return { header, payload };
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function checkClaims(payload, options, seconds, tolerance) {
|
|
178
|
+
const numeric = (name) => {
|
|
179
|
+
const value = payload[name];
|
|
180
|
+
if (value !== undefined && typeof value !== 'number')
|
|
181
|
+
throw new JwtError('JWT_CLAIM', `"${name}" is not a number`);
|
|
182
|
+
return value;
|
|
183
|
+
};
|
|
184
|
+
const exp = numeric('exp');
|
|
185
|
+
const nbf = numeric('nbf');
|
|
186
|
+
const iat = numeric('iat');
|
|
187
|
+
if (exp === undefined && options.requireExpiry !== false)
|
|
188
|
+
throw new JwtError('JWT_CLAIM', 'The token has no expiry');
|
|
189
|
+
if (exp !== undefined && seconds >= exp + tolerance)
|
|
190
|
+
throw new JwtError('JWT_EXPIRED', 'The token has expired');
|
|
191
|
+
if (nbf !== undefined && seconds + tolerance < nbf)
|
|
192
|
+
throw new JwtError('JWT_NOT_YET_VALID', 'The token is not valid yet');
|
|
193
|
+
if (options.maxAgeSec !== undefined) {
|
|
194
|
+
if (iat === undefined)
|
|
195
|
+
throw new JwtError('JWT_CLAIM', 'The token has no issue time');
|
|
196
|
+
if (seconds - iat > options.maxAgeSec + tolerance)
|
|
197
|
+
throw new JwtError('JWT_TOO_OLD', 'The token is too old');
|
|
198
|
+
}
|
|
199
|
+
if (options.issuer !== undefined) {
|
|
200
|
+
const issuers = typeof options.issuer === 'string' ? [options.issuer] : options.issuer;
|
|
201
|
+
if (typeof payload.iss !== 'string' || !issuers.includes(payload.iss)) {
|
|
202
|
+
throw new JwtError('JWT_ISSUER', 'The token is from another issuer');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (options.audience !== undefined && options.audience !== false) {
|
|
206
|
+
const wanted = typeof options.audience === 'string' ? [options.audience] : options.audience;
|
|
207
|
+
const audiences = typeof payload.aud === 'string' ? [payload.aud] : Array.isArray(payload.aud) ? payload.aud : [];
|
|
208
|
+
if (!audiences.some((audience) => wanted.includes(audience))) {
|
|
209
|
+
throw new JwtError('JWT_AUDIENCE', 'The token is for another audience');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
for (const claim of options.requiredClaims ?? []) {
|
|
213
|
+
if (payload[claim] === undefined)
|
|
214
|
+
throw new JwtError('JWT_CLAIM', `The token has no "${claim}" claim`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function jwtAuth(options) {
|
|
218
|
+
const verifier = createJwtVerifier(options);
|
|
219
|
+
const read = options.token ?? bearerToken;
|
|
220
|
+
return async (request) => {
|
|
221
|
+
const token = read(request);
|
|
222
|
+
if (token?.split('.').length !== 3)
|
|
223
|
+
return undefined;
|
|
224
|
+
let payload;
|
|
225
|
+
try {
|
|
226
|
+
payload = (await verifier.verify(token)).payload;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (!(error instanceof JwtError))
|
|
230
|
+
throw error;
|
|
231
|
+
const description = error.message.replace(/["\\]/g, "'");
|
|
232
|
+
return refuse(401, error.message, `Bearer error="invalid_token", error_description="${description}"`);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const principal = options.principal
|
|
236
|
+
? await options.principal(payload, request)
|
|
237
|
+
: principalFromClaims(payload, options.claims);
|
|
238
|
+
if (!principal)
|
|
239
|
+
return refuse(403, 'The token is valid, but not for this server');
|
|
240
|
+
return { method: 'jwt', claims: payload, ...principal };
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return refuse(403, error instanceof Error ? error.message : String(error));
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function principalFromClaims(payload, names = {}) {
|
|
248
|
+
const subject = claimAt(payload, names.subject ?? 'sub');
|
|
249
|
+
const tenant = claimAt(payload, names.tenant ?? 'tenant_id');
|
|
250
|
+
const roles = listOf(claimAt(payload, names.roles ?? 'roles'));
|
|
251
|
+
const scopes = listOf(names.scopes ? claimAt(payload, names.scopes) : (payload.scope ?? payload.scp));
|
|
252
|
+
return {
|
|
253
|
+
...(typeof subject === 'string' ? { userId: subject } : {}),
|
|
254
|
+
...(typeof tenant === 'string' ? { tenantId: (0, tenant_js_1.assertTenantId)(tenant) } : {}),
|
|
255
|
+
...(roles ? { roles } : {}),
|
|
256
|
+
...(scopes ? { scopes } : {}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async function hashApiKey(key) {
|
|
260
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(key));
|
|
261
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
262
|
+
}
|
|
263
|
+
function apiKeyAuth(options) {
|
|
264
|
+
if (!options.keys && !options.lookup)
|
|
265
|
+
throw new TypeError('apiKeyAuth() needs keys, a lookup, or both');
|
|
266
|
+
const header = options.header?.toLowerCase();
|
|
267
|
+
return async (request) => {
|
|
268
|
+
const key = header
|
|
269
|
+
? (request.headers.get(header) ?? undefined)
|
|
270
|
+
: (request.headers.get('x-api-key') ?? bearerToken(request));
|
|
271
|
+
if (!key)
|
|
272
|
+
return undefined;
|
|
273
|
+
if (options.prefix ? !key.startsWith(options.prefix) : /^[\w-]+\.[\w-]+\.[\w-]+$/.test(key))
|
|
274
|
+
return undefined;
|
|
275
|
+
const hash = await hashApiKey(key);
|
|
276
|
+
const known = Object.hasOwn(options.keys ?? {}, hash) ? options.keys?.[hash] : undefined;
|
|
277
|
+
const principal = known ?? (await options.lookup?.(hash, request));
|
|
278
|
+
if (!principal)
|
|
279
|
+
return refuse(401, 'The API key is not valid', 'Bearer error="invalid_token"');
|
|
280
|
+
if (principal.tenantId !== undefined)
|
|
281
|
+
(0, tenant_js_1.assertTenantId)(principal.tenantId);
|
|
282
|
+
return { method: 'api-key', ...principal };
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function trustedProxyAuth(options) {
|
|
286
|
+
if (!options.secret && !options.trust) {
|
|
287
|
+
throw new TypeError('trustedProxyAuth() needs a secret or a trust check: otherwise anyone who sets the headers is trusted');
|
|
288
|
+
}
|
|
289
|
+
const names = {
|
|
290
|
+
user: 'x-forwarded-user',
|
|
291
|
+
tenant: 'x-forwarded-tenant',
|
|
292
|
+
roles: 'x-forwarded-roles',
|
|
293
|
+
scopes: 'x-forwarded-scopes',
|
|
294
|
+
...options.headers,
|
|
295
|
+
};
|
|
296
|
+
const separator = options.separator ?? ',';
|
|
297
|
+
const list = (value) => value
|
|
298
|
+
?.split(separator)
|
|
299
|
+
.map((item) => item.trim())
|
|
300
|
+
.filter(Boolean);
|
|
301
|
+
return async (request) => {
|
|
302
|
+
if (options.secret && !sameText(request.headers.get(options.secret.header) ?? '', options.secret.value)) {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
if (options.trust && !(await options.trust(request)))
|
|
306
|
+
return undefined;
|
|
307
|
+
const user = request.headers.get(names.user);
|
|
308
|
+
if (!user)
|
|
309
|
+
return undefined;
|
|
310
|
+
const tenant = request.headers.get(names.tenant);
|
|
311
|
+
if (tenant && !/^[A-Za-z0-9_-]{1,128}$/.test(tenant))
|
|
312
|
+
return refuse(403, `"${tenant}" is not a valid tenant id`);
|
|
313
|
+
const roles = list(request.headers.get(names.roles));
|
|
314
|
+
const scopes = list(request.headers.get(names.scopes));
|
|
315
|
+
return {
|
|
316
|
+
method: 'proxy',
|
|
317
|
+
userId: user,
|
|
318
|
+
...(tenant ? { tenantId: tenant } : {}),
|
|
319
|
+
...(roles?.length ? { roles } : {}),
|
|
320
|
+
...(scopes?.length ? { scopes } : {}),
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function anyAuth(...hooks) {
|
|
325
|
+
return async (request) => {
|
|
326
|
+
let refusal;
|
|
327
|
+
for (const hook of hooks) {
|
|
328
|
+
const result = await hook(request);
|
|
329
|
+
if (result instanceof Response)
|
|
330
|
+
refusal ??= result;
|
|
331
|
+
else if (result)
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
return refusal;
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function bearerToken(request) {
|
|
338
|
+
const match = /^Bearer\s+(\S+)\s*$/i.exec(request.headers.get('authorization') ?? '');
|
|
339
|
+
return match?.[1];
|
|
340
|
+
}
|
|
341
|
+
function refuse(status, message, challenge) {
|
|
342
|
+
return new Response(JSON.stringify({ error: { code: status === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN', message } }), {
|
|
343
|
+
status,
|
|
344
|
+
headers: {
|
|
345
|
+
'content-type': 'application/json; charset=utf-8',
|
|
346
|
+
...(challenge ? { 'www-authenticate': challenge } : {}),
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function sameText(left, right) {
|
|
351
|
+
let difference = left.length ^ right.length;
|
|
352
|
+
const length = Math.max(left.length, right.length);
|
|
353
|
+
for (let index = 0; index < length; index += 1) {
|
|
354
|
+
difference |= (left.charCodeAt(index) || 0) ^ (right.charCodeAt(index) || 0);
|
|
355
|
+
}
|
|
356
|
+
return difference === 0;
|
|
357
|
+
}
|
|
358
|
+
function base64UrlBytes(text) {
|
|
359
|
+
const base64 = text
|
|
360
|
+
.replace(/-/g, '+')
|
|
361
|
+
.replace(/_/g, '/')
|
|
362
|
+
.padEnd(Math.ceil(text.length / 4) * 4, '=');
|
|
363
|
+
const binary = atob(base64);
|
|
364
|
+
const bytes = new Uint8Array(binary.length);
|
|
365
|
+
for (let index = 0; index < binary.length; index += 1)
|
|
366
|
+
bytes[index] = binary.charCodeAt(index);
|
|
367
|
+
return bytes;
|
|
368
|
+
}
|
|
369
|
+
function decodeJson(part, what) {
|
|
370
|
+
try {
|
|
371
|
+
const value = JSON.parse(new TextDecoder().decode(base64UrlBytes(part)));
|
|
372
|
+
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
373
|
+
return value;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
}
|
|
377
|
+
throw new JwtError('JWT_MALFORMED', `The token's ${what} is not a JSON object`);
|
|
378
|
+
}
|
|
379
|
+
function pemToDer(pem) {
|
|
380
|
+
const body = pem.replace(/-----(?:BEGIN|END) PUBLIC KEY-----/g, '').replace(/\s+/g, '');
|
|
381
|
+
if (!body || pem.includes('PRIVATE KEY'))
|
|
382
|
+
throw new JwtError('JWT_KEY', 'The public key is not a PEM public key');
|
|
383
|
+
return base64UrlBytes(body.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')).buffer;
|
|
384
|
+
}
|
|
385
|
+
function claimAt(payload, path) {
|
|
386
|
+
if (path in payload)
|
|
387
|
+
return payload[path];
|
|
388
|
+
let current = payload;
|
|
389
|
+
for (const segment of path.split('.')) {
|
|
390
|
+
if (!current || typeof current !== 'object')
|
|
391
|
+
return undefined;
|
|
392
|
+
current = current[segment];
|
|
393
|
+
}
|
|
394
|
+
return current;
|
|
395
|
+
}
|
|
396
|
+
function listOf(value) {
|
|
397
|
+
if (typeof value === 'string')
|
|
398
|
+
return value.split(/\s+/).filter(Boolean);
|
|
399
|
+
if (Array.isArray(value))
|
|
400
|
+
return value.filter((item) => typeof item === 'string');
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
@@ -20,6 +20,10 @@ export declare class MemoryStore implements Store {
|
|
|
20
20
|
private readonly options;
|
|
21
21
|
private readonly items;
|
|
22
22
|
private readonly maxItems;
|
|
23
|
+
/** Whether any item was ever given a time to live; until one is, there is nothing to sweep. */
|
|
24
|
+
private expiring;
|
|
25
|
+
/** When expired items were last swept, in milliseconds. */
|
|
26
|
+
private sweptAt;
|
|
23
27
|
private readonly now;
|
|
24
28
|
constructor(options?: MemoryStoreOptions);
|
|
25
29
|
/** Stores an item, keeping its original `createdAt` when it replaces one. */
|
|
@@ -42,5 +46,11 @@ export declare class MemoryStore implements Store {
|
|
|
42
46
|
size(): number;
|
|
43
47
|
private embed;
|
|
44
48
|
private expired;
|
|
49
|
+
/**
|
|
50
|
+
* Drops expired items. Reads already skip them, so this only reclaims memory: it runs once any item
|
|
51
|
+
* has a time to live, and at most once a second, so a write stays constant-time however many items
|
|
52
|
+
* there are. A store that is full sweeps before it evicts, so a live item never makes way for an
|
|
53
|
+
* expired one.
|
|
54
|
+
*/
|
|
45
55
|
private sweep;
|
|
46
56
|
}
|
package/dist-cjs/store/memory.js
CHANGED
|
@@ -11,6 +11,8 @@ class MemoryStore {
|
|
|
11
11
|
options;
|
|
12
12
|
items = new Map();
|
|
13
13
|
maxItems;
|
|
14
|
+
expiring = false;
|
|
15
|
+
sweptAt = Number.NEGATIVE_INFINITY;
|
|
14
16
|
now;
|
|
15
17
|
constructor(options = {}) {
|
|
16
18
|
this.options = options;
|
|
@@ -25,6 +27,8 @@ class MemoryStore {
|
|
|
25
27
|
const timestamp = this.now().toISOString();
|
|
26
28
|
const existing = this.items.get(id);
|
|
27
29
|
const vector = await this.embed(value, options.index);
|
|
30
|
+
if (options.ttlMs !== undefined)
|
|
31
|
+
this.expiring = true;
|
|
28
32
|
this.items.delete(id);
|
|
29
33
|
this.items.set(id, {
|
|
30
34
|
namespace: [...namespace],
|
|
@@ -106,6 +110,12 @@ class MemoryStore {
|
|
|
106
110
|
return item.expiresAt !== undefined && Date.parse(item.expiresAt) <= this.now().getTime();
|
|
107
111
|
}
|
|
108
112
|
sweep() {
|
|
113
|
+
if (!this.expiring)
|
|
114
|
+
return;
|
|
115
|
+
const now = this.now().getTime();
|
|
116
|
+
if (now - this.sweptAt < 1_000 && this.items.size <= this.maxItems)
|
|
117
|
+
return;
|
|
118
|
+
this.sweptAt = now;
|
|
109
119
|
for (const [id, item] of this.items)
|
|
110
120
|
if (this.expired(item))
|
|
111
121
|
this.items.delete(id);
|
|
@@ -8,6 +8,7 @@ import type { OperationLifecycleLike } from './lifecycle.js';
|
|
|
8
8
|
* is, so a different process can pick it up.
|
|
9
9
|
*/
|
|
10
10
|
import type { CacheAdapter } from '../cache/adapters.js';
|
|
11
|
+
import type { Principal } from './principal.js';
|
|
11
12
|
import type { Store } from './store.js';
|
|
12
13
|
/** Entry sentinel. An edge from `START` names the first node. */
|
|
13
14
|
export declare const START = "__start__";
|
|
@@ -243,6 +244,14 @@ export interface NodeOptions<S extends ChannelSchema = ChannelSchema> {
|
|
|
243
244
|
* that are not.
|
|
244
245
|
*/
|
|
245
246
|
idempotent?: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* What the node does to the world, as capabilities: `payments:charge`, `network:api.stripe.com`,
|
|
249
|
+
* `filesystem:write`. Declared for the linter, which reports a node with effects that also
|
|
250
|
+
* interrupts, since the node runs again from the top when the answer comes.
|
|
251
|
+
*/
|
|
252
|
+
effects?: readonly string[];
|
|
253
|
+
/** True when the node may call `context.interrupt()`. Declared for the linter. */
|
|
254
|
+
interrupts?: boolean;
|
|
246
255
|
/**
|
|
247
256
|
* Nodes this one may reach through `Send` or a `Command`. Declaring them keeps compile-time
|
|
248
257
|
* reachability checks and diagrams exact, so a node reached only that way is not reported as
|
|
@@ -374,6 +383,8 @@ export interface NodeContext<S extends ChannelSchema> {
|
|
|
374
383
|
readonly threadId: string;
|
|
375
384
|
/** The tenant the run is for, when it has one. */
|
|
376
385
|
readonly tenantId?: string;
|
|
386
|
+
/** Who the run is for, when it was started with a principal: the caller the server authenticated. */
|
|
387
|
+
readonly principal?: Readonly<Principal>;
|
|
377
388
|
/** Identifies this task. Equal to `node` unless the task came from a `Send`. */
|
|
378
389
|
readonly taskId: string;
|
|
379
390
|
/** Input carried by a `Send`. Undefined for a node reached through an ordinary edge. */
|
|
@@ -577,6 +588,14 @@ export interface GraphRunOptions {
|
|
|
577
588
|
* `tenantId` is refused before anything runs. A run without one carries on the thread's tenant.
|
|
578
589
|
*/
|
|
579
590
|
tenantId?: string;
|
|
591
|
+
/**
|
|
592
|
+
* Who the run is for, such as the caller the server authenticated. Nodes read it as
|
|
593
|
+
* `context.principal`, and an agent hands it to every tool and middleware. Its `tenantId` is the
|
|
594
|
+
* run's tenant when `tenantId` is not given, and a run whose two tenants differ is refused. Its
|
|
595
|
+
* `userId` is recorded on every checkpoint as `metadata.userId`; the rest is never stored, so a
|
|
596
|
+
* resumed run carries the principal of whoever resumed it.
|
|
597
|
+
*/
|
|
598
|
+
principal?: Principal;
|
|
580
599
|
/** Overrides the compiled `maxConcurrency` for this run. */
|
|
581
600
|
maxConcurrency?: number;
|
|
582
601
|
/** Breakpoints for this run only, replacing the compiled ones. */
|
|
@@ -853,6 +872,10 @@ export interface GraphDescription {
|
|
|
853
872
|
idempotent?: boolean;
|
|
854
873
|
/** True when the node reuses results through `cache`. */
|
|
855
874
|
cache?: boolean;
|
|
875
|
+
/** The capabilities its work uses, when it declares them. */
|
|
876
|
+
effects?: string[];
|
|
877
|
+
/** True when it declares that it may interrupt. */
|
|
878
|
+
interrupts?: boolean;
|
|
856
879
|
/** The graph this node runs, when it is a compiled graph used through `asNode()`. */
|
|
857
880
|
subgraph?: GraphDescription;
|
|
858
881
|
}>;
|
|
@@ -876,6 +899,19 @@ export interface GraphDescription {
|
|
|
876
899
|
checkpointer?: 'memory' | 'custom' | 'none';
|
|
877
900
|
/** Tasks a superstep runs at once. */
|
|
878
901
|
maxConcurrency?: number;
|
|
902
|
+
/** The tools the graph's nodes call, when it was compiled with them, as an agent is. */
|
|
903
|
+
tools?: GraphToolDescription[];
|
|
904
|
+
}
|
|
905
|
+
/** A tool a graph calls, as the linter reads it. */
|
|
906
|
+
export interface GraphToolDescription {
|
|
907
|
+
/** The tool's name. */
|
|
908
|
+
name: string;
|
|
909
|
+
/** Its capabilities, when it declares a fixed list. */
|
|
910
|
+
capabilities?: string[];
|
|
911
|
+
/** True when it computes its capabilities from each call's arguments. */
|
|
912
|
+
dynamic?: boolean;
|
|
913
|
+
/** How its calls are approved: an interrupt for each, a permission policy, or not at all. */
|
|
914
|
+
approval: 'interrupt' | 'policy' | 'none';
|
|
879
915
|
}
|
|
880
916
|
/**
|
|
881
917
|
* Options applied when a graph is compiled: stores, breakpoints, concurrency, retries, and failure
|
|
@@ -898,6 +934,8 @@ export interface CompileOptions {
|
|
|
898
934
|
maxConcurrency?: number;
|
|
899
935
|
/** Default retry policy for every node. A node's own policy wins. */
|
|
900
936
|
retry?: RetryPolicy;
|
|
937
|
+
/** The tools the graph's nodes call, for `describe()` and the linter. `createAgent()` fills it. */
|
|
938
|
+
tools?: GraphToolDescription[];
|
|
901
939
|
/**
|
|
902
940
|
* Defaults for every node: a retry policy, timeouts, and an `onError`. A node's own options win,
|
|
903
941
|
* field by field. `retry` here is the same as `compile({ retry })`, and wins over it.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CacheTtl, ReasoningEffort } from './providers.js';
|
|
2
2
|
import type { AssetInput } from './images.js';
|
|
3
|
+
import type { Principal } from './principal.js';
|
|
4
|
+
import type { Store } from './store.js';
|
|
3
5
|
declare global {
|
|
4
6
|
namespace NodeJS {
|
|
5
7
|
/**
|
|
@@ -128,8 +130,19 @@ export interface ToolDefinition {
|
|
|
128
130
|
description: string;
|
|
129
131
|
/** JSON Schema for the arguments. */
|
|
130
132
|
parameters: Record<string, unknown>;
|
|
131
|
-
/**
|
|
132
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Runs the tool, for the agent loop and executors that call tools automatically. An agent passes
|
|
135
|
+
* a `ToolContext` too: who the run is for, its thread and tenant, its signal, and its store.
|
|
136
|
+
*/
|
|
137
|
+
execute?: (args: Record<string, unknown>, context?: ToolContext) => Promise<unknown>;
|
|
138
|
+
/**
|
|
139
|
+
* What the tool does to the world, as capabilities a permission policy decides on:
|
|
140
|
+
* `filesystem:read` or `filesystem:write` with an optional path, `network` with an optional host,
|
|
141
|
+
* `shell` with an optional command, `code`, or a name of the application's own such as `payments`.
|
|
142
|
+
* A function computes them from each call's arguments, so a policy sees the path, host, or command
|
|
143
|
+
* the call actually uses. Never sent to a model.
|
|
144
|
+
*/
|
|
145
|
+
capabilities?: readonly string[] | ((args: Record<string, unknown>) => readonly string[]);
|
|
133
146
|
/**
|
|
134
147
|
* Ends a cacheable prefix after this tool. Only honored when the request sets
|
|
135
148
|
* `cache.mode: 'explicit'` and the provider accepts caller-placed breakpoints.
|
|
@@ -145,6 +158,27 @@ export interface ToolDefinition {
|
|
|
145
158
|
export type ToolChoice = 'auto' | 'none' | 'required' | {
|
|
146
159
|
name: string;
|
|
147
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* What a tool receives besides its arguments when an agent runs it. Every field is optional: a tool
|
|
163
|
+
* run outside an agent, or by an executor given nothing, sees none of them.
|
|
164
|
+
*/
|
|
165
|
+
export interface ToolContext {
|
|
166
|
+
/** The model's id for this call. */
|
|
167
|
+
toolCallId?: string;
|
|
168
|
+
/** The thread the run belongs to. */
|
|
169
|
+
threadId?: string;
|
|
170
|
+
/** The tenant the run is for. */
|
|
171
|
+
tenantId?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Who the run is for: the caller the server authenticated, with their roles and scopes, so a
|
|
174
|
+
* tool can act as that user and nobody else.
|
|
175
|
+
*/
|
|
176
|
+
principal?: Readonly<Principal>;
|
|
177
|
+
/** Aborted when the run is cancelled or the step times out. A tool that waits should stop with it. */
|
|
178
|
+
signal?: AbortSignal;
|
|
179
|
+
/** Long-term memory. In a tenant's run, the tenant's view of it. */
|
|
180
|
+
store?: Store;
|
|
181
|
+
}
|
|
148
182
|
/** What a tool returned, correlated with the call that asked for it. */
|
|
149
183
|
export interface ToolCallResult {
|
|
150
184
|
/** The id of the call this answers. */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who a request, a run, or a tool call is for.
|
|
3
|
+
*
|
|
4
|
+
* One shape everywhere: the server's authentication hook returns it, a run carries it, graph nodes
|
|
5
|
+
* and workflow steps read it, tools and middleware receive it, and a permission policy can decide on
|
|
6
|
+
* it. Its tenant scopes threads, runs, budgets, and the store.
|
|
7
|
+
*/
|
|
8
|
+
export interface Principal {
|
|
9
|
+
/** Isolates threads, runs, and cron jobs. Requests only ever see their own tenant's resources. */
|
|
10
|
+
tenantId?: string;
|
|
11
|
+
/** Who the caller is: a token's subject, a key's owner. Recorded on what they create. */
|
|
12
|
+
userId?: string;
|
|
13
|
+
/** What the caller may do. A route that names a scope refuses a principal without it. */
|
|
14
|
+
scopes?: readonly string[];
|
|
15
|
+
/** The caller's roles, for policies and tools that decide by role rather than by scope. */
|
|
16
|
+
roles?: readonly string[];
|
|
17
|
+
/** How the caller was authenticated: `jwt`, `api-key`, `proxy`, or a name of your own. */
|
|
18
|
+
method?: string;
|
|
19
|
+
/** The verified claims or attributes the principal was built from, for anything the fields above leave out. */
|
|
20
|
+
claims?: Readonly<Record<string, unknown>>;
|
|
21
|
+
}
|
|
@@ -6,15 +6,8 @@
|
|
|
6
6
|
* graph thread, so state, interrupts, and history come from the checkpointer; and an assistant is
|
|
7
7
|
* anything that can stream events for an input, which a compiled graph already does.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
export
|
|
11
|
-
/** Isolates threads, runs, and cron jobs. Requests only ever see their own tenant's resources. */
|
|
12
|
-
tenantId?: string;
|
|
13
|
-
/** Who the caller is, recorded on what they create. */
|
|
14
|
-
userId?: string;
|
|
15
|
-
/** What the caller may do. A route that names a scope refuses a principal without it. */
|
|
16
|
-
scopes?: readonly string[];
|
|
17
|
-
}
|
|
9
|
+
import type { Principal } from './principal.js';
|
|
10
|
+
export type { Principal };
|
|
18
11
|
/** Where a run stands, as the server reports it. */
|
|
19
12
|
export type RunStatus = 'queued' | 'running' | 'awaiting_input' | 'succeeded' | 'failed' | 'cancelled' | 'expired';
|
|
20
13
|
/** What happens when a run is requested for a thread that is already running one. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Principal } from '../types/principal.js';
|
|
1
2
|
/**
|
|
2
3
|
* The tenant id rule every scope shares. An id is letters, digits, `_`, and `-`, so it can never hold
|
|
3
4
|
* the `/` a scoped name or key is built with: no tenant's prefix is a prefix of another's.
|
|
@@ -12,3 +13,13 @@ export declare class TenantIdError extends RangeError {
|
|
|
12
13
|
export declare function assertTenantId(tenantId: string): string;
|
|
13
14
|
/** @internal The prefix a tenant's names and keys carry in a shared store. */
|
|
14
15
|
export declare function tenantPrefix(tenantId: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Run options with the principal's tenant applied: the principal's `tenantId` becomes the run's when
|
|
18
|
+
* it has none, a run whose two tenants differ is refused, and the principal's `userId` is recorded
|
|
19
|
+
* in the run's metadata.
|
|
20
|
+
*/
|
|
21
|
+
export declare function withPrincipal<O extends {
|
|
22
|
+
tenantId?: string;
|
|
23
|
+
principal?: Principal;
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
}>(runOptions: O): O;
|