@agentuity/core 1.0.59 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/deprecation.d.ts +20 -0
  2. package/dist/deprecation.d.ts.map +1 -0
  3. package/dist/deprecation.js +102 -0
  4. package/dist/deprecation.js.map +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/services/oauth/flow.d.ts +0 -31
  10. package/dist/services/oauth/flow.d.ts.map +1 -1
  11. package/dist/services/oauth/flow.js +13 -138
  12. package/dist/services/oauth/flow.js.map +1 -1
  13. package/dist/services/oauth/index.d.ts +0 -1
  14. package/dist/services/oauth/index.d.ts.map +1 -1
  15. package/dist/services/oauth/index.js +0 -1
  16. package/dist/services/oauth/index.js.map +1 -1
  17. package/dist/services/oauth/types.d.ts +0 -11
  18. package/dist/services/oauth/types.d.ts.map +1 -1
  19. package/dist/services/oauth/types.js +0 -19
  20. package/dist/services/oauth/types.js.map +1 -1
  21. package/dist/services/sandbox/execute.d.ts.map +1 -1
  22. package/dist/services/sandbox/execute.js +11 -22
  23. package/dist/services/sandbox/execute.js.map +1 -1
  24. package/dist/services/sandbox/run.d.ts.map +1 -1
  25. package/dist/services/sandbox/run.js +30 -83
  26. package/dist/services/sandbox/run.js.map +1 -1
  27. package/dist/services/sandbox/types.d.ts +0 -8
  28. package/dist/services/sandbox/types.d.ts.map +1 -1
  29. package/dist/services/sandbox/types.js +0 -14
  30. package/dist/services/sandbox/types.js.map +1 -1
  31. package/package.json +2 -2
  32. package/src/deprecation.ts +120 -0
  33. package/src/index.ts +3 -0
  34. package/src/services/oauth/flow.ts +15 -156
  35. package/src/services/oauth/index.ts +0 -1
  36. package/src/services/oauth/types.ts +0 -26
  37. package/src/services/sandbox/execute.ts +12 -26
  38. package/src/services/sandbox/run.ts +34 -129
  39. package/src/services/sandbox/types.ts +0 -14
  40. package/dist/services/oauth/token-storage.d.ts +0 -109
  41. package/dist/services/oauth/token-storage.d.ts.map +0 -1
  42. package/dist/services/oauth/token-storage.js +0 -140
  43. package/dist/services/oauth/token-storage.js.map +0 -1
  44. package/src/services/oauth/token-storage.ts +0 -220
@@ -1,109 +0,0 @@
1
- import type { KeyValueStorage } from '../keyvalue/service.ts';
2
- import type { OAuthFlowConfig, OAuthTokenResponse, StoredToken } from './types.ts';
3
- /**
4
- * Check whether a stored token's access token has expired.
5
- *
6
- * @param token - The stored token to check
7
- * @returns true if the token has an expires_at timestamp that is in the past
8
- *
9
- * @example
10
- * ```typescript
11
- * const token = await storage.get('user:123');
12
- * if (token && isTokenExpired(token)) {
13
- * // Token is expired and auto-refresh wasn't available
14
- * }
15
- * ```
16
- */
17
- export declare function isTokenExpired(token: StoredToken): boolean;
18
- /**
19
- * Options for configuring a TokenStorage instance.
20
- */
21
- export interface TokenStorageOptions {
22
- /**
23
- * OAuth configuration for auto-refresh and token revocation.
24
- * If not provided, auto-refresh on get() and server-side revocation on invalidate() are disabled.
25
- */
26
- config?: OAuthFlowConfig;
27
- /**
28
- * KV namespace for storing tokens. Defaults to 'oauth-tokens'.
29
- */
30
- namespace?: string;
31
- /**
32
- * Key prefix prepended to all storage keys.
33
- * Useful for scoping tokens by application or tenant.
34
- */
35
- prefix?: string;
36
- }
37
- /**
38
- * Interface for storing, retrieving, and invalidating OAuth tokens.
39
- *
40
- * Implementations handle persistence and may support automatic token refresh
41
- * on retrieval and server-side revocation on invalidation.
42
- */
43
- export interface TokenStorage {
44
- /**
45
- * Retrieve a stored token by key.
46
- *
47
- * If the token is expired and a refresh_token is available (and config is provided),
48
- * the token is automatically refreshed, stored, and the new token is returned.
49
- * If auto-refresh fails, the expired token is returned so the caller can decide
50
- * how to handle it (check with {@link isTokenExpired}).
51
- *
52
- * @param key - The storage key (e.g. a user ID or session ID)
53
- * @returns The stored token, or null if no token exists for the key
54
- */
55
- get(key: string): Promise<StoredToken | null>;
56
- /**
57
- * Store a token response from a token exchange or refresh.
58
- *
59
- * Automatically computes `expires_at` from `expires_in` if present.
60
- *
61
- * @param key - The storage key (e.g. a user ID or session ID)
62
- * @param token - The OAuth token response to store
63
- */
64
- set(key: string, token: OAuthTokenResponse): Promise<void>;
65
- /**
66
- * Invalidate a stored token: revoke it server-side and remove from storage.
67
- *
68
- * If config is provided, the refresh token (or access token as fallback)
69
- * is revoked via the token revocation endpoint. Revocation is best-effort —
70
- * the token is removed from storage regardless of whether revocation succeeds.
71
- *
72
- * @param key - The storage key to invalidate
73
- * @returns The token that was removed, or null if no token existed
74
- */
75
- invalidate(key: string): Promise<StoredToken | null>;
76
- }
77
- /**
78
- * Token storage backed by Agentuity's Key-Value storage service.
79
- *
80
- * Stores tokens as JSON in a KV namespace. Supports automatic token refresh
81
- * on retrieval when tokens expire (if OAuth config is provided).
82
- *
83
- * @example
84
- * ```typescript
85
- * import { KeyValueTokenStorage } from '@agentuity/core/oauth';
86
- *
87
- * // Create storage with auto-refresh enabled
88
- * const storage = new KeyValueTokenStorage(ctx.kv, {
89
- * config: { issuer: 'https://auth.example.com' },
90
- * });
91
- *
92
- * // Store a token after initial exchange
93
- * await storage.set('user:123', tokenResponse);
94
- *
95
- * // Retrieve — auto-refreshes if expired
96
- * const token = await storage.get('user:123');
97
- *
98
- * // Logout — revokes server-side and removes from storage
99
- * await storage.invalidate('user:123');
100
- * ```
101
- */
102
- export declare class KeyValueTokenStorage implements TokenStorage {
103
- #private;
104
- constructor(kv: KeyValueStorage, options?: TokenStorageOptions);
105
- get(key: string): Promise<StoredToken | null>;
106
- set(key: string, token: OAuthTokenResponse): Promise<void>;
107
- invalidate(key: string): Promise<StoredToken | null>;
108
- }
109
- //# sourceMappingURL=token-storage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"token-storage.d.ts","sourceRoot":"","sources":["../../../src/services/oauth/token-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAMnF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAG1D;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IAEzB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC5B;;;;;;;;;;OAUG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE9C;;;;;;;OAOG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;;;;;;;;OASG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACrD;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,oBAAqB,YAAW,YAAY;;gBAM5C,EAAE,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,mBAAmB;IAOxD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAyB7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;CA8C1D"}
@@ -1,140 +0,0 @@
1
- import { StoredTokenSchema } from "./types.js";
2
- import { refreshToken, logout } from "./flow.js";
3
- const DEFAULT_NAMESPACE = 'oauth-tokens';
4
- /**
5
- * Check whether a stored token's access token has expired.
6
- *
7
- * @param token - The stored token to check
8
- * @returns true if the token has an expires_at timestamp that is in the past
9
- *
10
- * @example
11
- * ```typescript
12
- * const token = await storage.get('user:123');
13
- * if (token && isTokenExpired(token)) {
14
- * // Token is expired and auto-refresh wasn't available
15
- * }
16
- * ```
17
- */
18
- export function isTokenExpired(token) {
19
- if (!token.expires_at)
20
- return false;
21
- return Math.floor(Date.now() / 1000) >= token.expires_at;
22
- }
23
- /**
24
- * Convert an OAuth token response to a StoredToken with computed expires_at.
25
- */
26
- function toStoredToken(token) {
27
- return {
28
- access_token: token.access_token,
29
- token_type: token.token_type,
30
- refresh_token: token.refresh_token,
31
- scope: token.scope,
32
- id_token: token.id_token,
33
- expires_at: token.expires_in ? Math.floor(Date.now() / 1000) + token.expires_in : undefined,
34
- };
35
- }
36
- /**
37
- * Token storage backed by Agentuity's Key-Value storage service.
38
- *
39
- * Stores tokens as JSON in a KV namespace. Supports automatic token refresh
40
- * on retrieval when tokens expire (if OAuth config is provided).
41
- *
42
- * @example
43
- * ```typescript
44
- * import { KeyValueTokenStorage } from '@agentuity/core/oauth';
45
- *
46
- * // Create storage with auto-refresh enabled
47
- * const storage = new KeyValueTokenStorage(ctx.kv, {
48
- * config: { issuer: 'https://auth.example.com' },
49
- * });
50
- *
51
- * // Store a token after initial exchange
52
- * await storage.set('user:123', tokenResponse);
53
- *
54
- * // Retrieve — auto-refreshes if expired
55
- * const token = await storage.get('user:123');
56
- *
57
- * // Logout — revokes server-side and removes from storage
58
- * await storage.invalidate('user:123');
59
- * ```
60
- */
61
- export class KeyValueTokenStorage {
62
- #kv;
63
- #namespace;
64
- #prefix;
65
- #config;
66
- constructor(kv, options) {
67
- this.#kv = kv;
68
- this.#namespace = options?.namespace ?? DEFAULT_NAMESPACE;
69
- this.#prefix = options?.prefix ?? '';
70
- this.#config = options?.config;
71
- }
72
- async get(key) {
73
- const result = await this.#kv.get(this.#namespace, this.#resolveKey(key));
74
- if (!result.exists)
75
- return null;
76
- const parsed = StoredTokenSchema.safeParse(result.data);
77
- if (!parsed.success)
78
- return null;
79
- const token = parsed.data;
80
- // Auto-refresh if expired and refresh_token + config are available
81
- if (isTokenExpired(token) && token.refresh_token && this.#config) {
82
- try {
83
- const newTokenResponse = await refreshToken(token.refresh_token, this.#config);
84
- const newStored = toStoredToken(newTokenResponse);
85
- await this.#store(key, newStored);
86
- return newStored;
87
- }
88
- catch {
89
- // Refresh failed — return the expired token, caller can check isTokenExpired()
90
- return token;
91
- }
92
- }
93
- return token;
94
- }
95
- async set(key, token) {
96
- const stored = toStoredToken(token);
97
- await this.#store(key, stored);
98
- }
99
- async invalidate(key) {
100
- const resolvedKey = this.#resolveKey(key);
101
- const result = await this.#kv.get(this.#namespace, resolvedKey);
102
- if (!result.exists)
103
- return null;
104
- const parsed = StoredTokenSchema.safeParse(result.data);
105
- const token = parsed.success ? parsed.data : null;
106
- // Revoke server-side (best effort)
107
- if (token && this.#config) {
108
- const tokenToRevoke = token.refresh_token ?? token.access_token;
109
- try {
110
- await logout(tokenToRevoke, this.#config);
111
- }
112
- catch {
113
- // Best effort — continue with storage cleanup
114
- }
115
- }
116
- // Remove from storage regardless of revocation result
117
- await this.#kv.delete(this.#namespace, resolvedKey);
118
- return token;
119
- }
120
- async #store(key, token) {
121
- // Only set explicit TTL for tokens without a refresh_token.
122
- // Tokens with refresh capability persist until explicitly invalidated
123
- // (auto-refresh on get() will keep them fresh).
124
- let ttl;
125
- if (!token.refresh_token && token.expires_at) {
126
- const remaining = token.expires_at - Math.floor(Date.now() / 1000);
127
- if (remaining > 0) {
128
- ttl = Math.max(remaining, 60); // KV minimum is 60 seconds
129
- }
130
- }
131
- await this.#kv.set(this.#namespace, this.#resolveKey(key), token, {
132
- ttl,
133
- contentType: 'application/json',
134
- });
135
- }
136
- #resolveKey(key) {
137
- return this.#prefix ? `${this.#prefix}${key}` : key;
138
- }
139
- }
140
- //# sourceMappingURL=token-storage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"token-storage.js","sourceRoot":"","sources":["../../../src/services/oauth/token-storage.ts"],"names":[],"mappings":"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"}
@@ -1,220 +0,0 @@
1
- import type { KeyValueStorage } from '../keyvalue/service.ts';
2
- import type { OAuthFlowConfig, OAuthTokenResponse, StoredToken } from './types.ts';
3
- import { StoredTokenSchema } from './types.ts';
4
- import { refreshToken, logout } from './flow.ts';
5
-
6
- const DEFAULT_NAMESPACE = 'oauth-tokens';
7
-
8
- /**
9
- * Check whether a stored token's access token has expired.
10
- *
11
- * @param token - The stored token to check
12
- * @returns true if the token has an expires_at timestamp that is in the past
13
- *
14
- * @example
15
- * ```typescript
16
- * const token = await storage.get('user:123');
17
- * if (token && isTokenExpired(token)) {
18
- * // Token is expired and auto-refresh wasn't available
19
- * }
20
- * ```
21
- */
22
- export function isTokenExpired(token: StoredToken): boolean {
23
- if (!token.expires_at) return false;
24
- return Math.floor(Date.now() / 1000) >= token.expires_at;
25
- }
26
-
27
- /**
28
- * Options for configuring a TokenStorage instance.
29
- */
30
- export interface TokenStorageOptions {
31
- /**
32
- * OAuth configuration for auto-refresh and token revocation.
33
- * If not provided, auto-refresh on get() and server-side revocation on invalidate() are disabled.
34
- */
35
- config?: OAuthFlowConfig;
36
-
37
- /**
38
- * KV namespace for storing tokens. Defaults to 'oauth-tokens'.
39
- */
40
- namespace?: string;
41
-
42
- /**
43
- * Key prefix prepended to all storage keys.
44
- * Useful for scoping tokens by application or tenant.
45
- */
46
- prefix?: string;
47
- }
48
-
49
- /**
50
- * Interface for storing, retrieving, and invalidating OAuth tokens.
51
- *
52
- * Implementations handle persistence and may support automatic token refresh
53
- * on retrieval and server-side revocation on invalidation.
54
- */
55
- export interface TokenStorage {
56
- /**
57
- * Retrieve a stored token by key.
58
- *
59
- * If the token is expired and a refresh_token is available (and config is provided),
60
- * the token is automatically refreshed, stored, and the new token is returned.
61
- * If auto-refresh fails, the expired token is returned so the caller can decide
62
- * how to handle it (check with {@link isTokenExpired}).
63
- *
64
- * @param key - The storage key (e.g. a user ID or session ID)
65
- * @returns The stored token, or null if no token exists for the key
66
- */
67
- get(key: string): Promise<StoredToken | null>;
68
-
69
- /**
70
- * Store a token response from a token exchange or refresh.
71
- *
72
- * Automatically computes `expires_at` from `expires_in` if present.
73
- *
74
- * @param key - The storage key (e.g. a user ID or session ID)
75
- * @param token - The OAuth token response to store
76
- */
77
- set(key: string, token: OAuthTokenResponse): Promise<void>;
78
-
79
- /**
80
- * Invalidate a stored token: revoke it server-side and remove from storage.
81
- *
82
- * If config is provided, the refresh token (or access token as fallback)
83
- * is revoked via the token revocation endpoint. Revocation is best-effort —
84
- * the token is removed from storage regardless of whether revocation succeeds.
85
- *
86
- * @param key - The storage key to invalidate
87
- * @returns The token that was removed, or null if no token existed
88
- */
89
- invalidate(key: string): Promise<StoredToken | null>;
90
- }
91
-
92
- /**
93
- * Convert an OAuth token response to a StoredToken with computed expires_at.
94
- */
95
- function toStoredToken(token: OAuthTokenResponse): StoredToken {
96
- return {
97
- access_token: token.access_token,
98
- token_type: token.token_type,
99
- refresh_token: token.refresh_token,
100
- scope: token.scope,
101
- id_token: token.id_token,
102
- expires_at: token.expires_in ? Math.floor(Date.now() / 1000) + token.expires_in : undefined,
103
- };
104
- }
105
-
106
- /**
107
- * Token storage backed by Agentuity's Key-Value storage service.
108
- *
109
- * Stores tokens as JSON in a KV namespace. Supports automatic token refresh
110
- * on retrieval when tokens expire (if OAuth config is provided).
111
- *
112
- * @example
113
- * ```typescript
114
- * import { KeyValueTokenStorage } from '@agentuity/core/oauth';
115
- *
116
- * // Create storage with auto-refresh enabled
117
- * const storage = new KeyValueTokenStorage(ctx.kv, {
118
- * config: { issuer: 'https://auth.example.com' },
119
- * });
120
- *
121
- * // Store a token after initial exchange
122
- * await storage.set('user:123', tokenResponse);
123
- *
124
- * // Retrieve — auto-refreshes if expired
125
- * const token = await storage.get('user:123');
126
- *
127
- * // Logout — revokes server-side and removes from storage
128
- * await storage.invalidate('user:123');
129
- * ```
130
- */
131
- export class KeyValueTokenStorage implements TokenStorage {
132
- #kv: KeyValueStorage;
133
- #namespace: string;
134
- #prefix: string;
135
- #config?: OAuthFlowConfig;
136
-
137
- constructor(kv: KeyValueStorage, options?: TokenStorageOptions) {
138
- this.#kv = kv;
139
- this.#namespace = options?.namespace ?? DEFAULT_NAMESPACE;
140
- this.#prefix = options?.prefix ?? '';
141
- this.#config = options?.config;
142
- }
143
-
144
- async get(key: string): Promise<StoredToken | null> {
145
- const result = await this.#kv.get<StoredToken>(this.#namespace, this.#resolveKey(key));
146
- if (!result.exists) return null;
147
-
148
- const parsed = StoredTokenSchema.safeParse(result.data);
149
- if (!parsed.success) return null;
150
-
151
- const token = parsed.data;
152
-
153
- // Auto-refresh if expired and refresh_token + config are available
154
- if (isTokenExpired(token) && token.refresh_token && this.#config) {
155
- try {
156
- const newTokenResponse = await refreshToken(token.refresh_token, this.#config);
157
- const newStored = toStoredToken(newTokenResponse);
158
- await this.#store(key, newStored);
159
- return newStored;
160
- } catch {
161
- // Refresh failed — return the expired token, caller can check isTokenExpired()
162
- return token;
163
- }
164
- }
165
-
166
- return token;
167
- }
168
-
169
- async set(key: string, token: OAuthTokenResponse): Promise<void> {
170
- const stored = toStoredToken(token);
171
- await this.#store(key, stored);
172
- }
173
-
174
- async invalidate(key: string): Promise<StoredToken | null> {
175
- const resolvedKey = this.#resolveKey(key);
176
- const result = await this.#kv.get<StoredToken>(this.#namespace, resolvedKey);
177
-
178
- if (!result.exists) return null;
179
-
180
- const parsed = StoredTokenSchema.safeParse(result.data);
181
- const token = parsed.success ? parsed.data : null;
182
-
183
- // Revoke server-side (best effort)
184
- if (token && this.#config) {
185
- const tokenToRevoke = token.refresh_token ?? token.access_token;
186
- try {
187
- await logout(tokenToRevoke, this.#config);
188
- } catch {
189
- // Best effort — continue with storage cleanup
190
- }
191
- }
192
-
193
- // Remove from storage regardless of revocation result
194
- await this.#kv.delete(this.#namespace, resolvedKey);
195
-
196
- return token;
197
- }
198
-
199
- async #store(key: string, token: StoredToken): Promise<void> {
200
- // Only set explicit TTL for tokens without a refresh_token.
201
- // Tokens with refresh capability persist until explicitly invalidated
202
- // (auto-refresh on get() will keep them fresh).
203
- let ttl: number | undefined;
204
- if (!token.refresh_token && token.expires_at) {
205
- const remaining = token.expires_at - Math.floor(Date.now() / 1000);
206
- if (remaining > 0) {
207
- ttl = Math.max(remaining, 60); // KV minimum is 60 seconds
208
- }
209
- }
210
-
211
- await this.#kv.set(this.#namespace, this.#resolveKey(key), token, {
212
- ttl,
213
- contentType: 'application/json',
214
- });
215
- }
216
-
217
- #resolveKey(key: string): string {
218
- return this.#prefix ? `${this.#prefix}${key}` : key;
219
- }
220
- }