@agentuity/core 2.0.0-beta.0 → 2.0.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/deprecation.d.ts +20 -0
- package/dist/deprecation.d.ts.map +1 -0
- package/dist/deprecation.js +102 -0
- package/dist/deprecation.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/services/api.d.ts.map +1 -1
- package/dist/services/api.js +13 -5
- package/dist/services/api.js.map +1 -1
- package/dist/services/oauth/flow.d.ts +82 -0
- package/dist/services/oauth/flow.d.ts.map +1 -0
- package/dist/services/oauth/flow.js +308 -0
- package/dist/services/oauth/flow.js.map +1 -0
- package/dist/services/oauth/index.d.ts +2 -0
- package/dist/services/oauth/index.d.ts.map +1 -1
- package/dist/services/oauth/index.js +2 -0
- package/dist/services/oauth/index.js.map +1 -1
- package/dist/services/oauth/token-storage.d.ts +109 -0
- package/dist/services/oauth/token-storage.d.ts.map +1 -0
- package/dist/services/oauth/token-storage.js +140 -0
- package/dist/services/oauth/token-storage.js.map +1 -0
- package/dist/services/oauth/types.d.ts +63 -0
- package/dist/services/oauth/types.d.ts.map +1 -1
- package/dist/services/oauth/types.js +74 -0
- package/dist/services/oauth/types.js.map +1 -1
- package/dist/services/project/get.d.ts +12 -0
- package/dist/services/project/get.d.ts.map +1 -1
- package/dist/services/project/get.js +9 -0
- package/dist/services/project/get.js.map +1 -1
- package/dist/services/sandbox/client.d.ts +201 -1
- package/dist/services/sandbox/client.d.ts.map +1 -1
- package/dist/services/sandbox/client.js +276 -15
- package/dist/services/sandbox/client.js.map +1 -1
- package/dist/services/sandbox/create.d.ts +5 -0
- package/dist/services/sandbox/create.d.ts.map +1 -1
- package/dist/services/sandbox/create.js +11 -0
- package/dist/services/sandbox/create.js.map +1 -1
- package/dist/services/sandbox/execute.d.ts.map +1 -1
- package/dist/services/sandbox/execute.js +22 -11
- package/dist/services/sandbox/execute.js.map +1 -1
- package/dist/services/sandbox/execution.d.ts +1 -0
- package/dist/services/sandbox/execution.d.ts.map +1 -1
- package/dist/services/sandbox/execution.js +4 -2
- package/dist/services/sandbox/execution.js.map +1 -1
- package/dist/services/sandbox/files.js +1 -1
- package/dist/services/sandbox/files.js.map +1 -1
- package/dist/services/sandbox/index.d.ts +3 -1
- package/dist/services/sandbox/index.d.ts.map +1 -1
- package/dist/services/sandbox/index.js +1 -0
- package/dist/services/sandbox/index.js.map +1 -1
- package/dist/services/sandbox/job.d.ts +227 -0
- package/dist/services/sandbox/job.d.ts.map +1 -0
- package/dist/services/sandbox/job.js +109 -0
- package/dist/services/sandbox/job.js.map +1 -0
- package/dist/services/sandbox/run.d.ts +1 -0
- package/dist/services/sandbox/run.d.ts.map +1 -1
- package/dist/services/sandbox/run.js +83 -30
- package/dist/services/sandbox/run.js.map +1 -1
- package/dist/services/sandbox/types.d.ts +45 -0
- package/dist/services/sandbox/types.d.ts.map +1 -1
- package/dist/services/sandbox/types.js +42 -0
- package/dist/services/sandbox/types.js.map +1 -1
- package/dist/services/sandbox/util.d.ts +1 -0
- package/dist/services/sandbox/util.d.ts.map +1 -1
- package/dist/services/sandbox/util.js +1 -0
- package/dist/services/sandbox/util.js.map +1 -1
- package/dist/services/schedule/service.d.ts +5 -0
- package/dist/services/schedule/service.d.ts.map +1 -1
- package/dist/services/schedule/service.js +16 -0
- package/dist/services/schedule/service.js.map +1 -1
- package/dist/services/schedule/types.d.ts +1 -0
- package/dist/services/schedule/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/deprecation.ts +120 -0
- package/src/index.ts +3 -0
- package/src/services/api.ts +15 -5
- package/src/services/oauth/flow.ts +356 -0
- package/src/services/oauth/index.ts +2 -0
- package/src/services/oauth/token-storage.ts +220 -0
- package/src/services/oauth/types.ts +95 -0
- package/src/services/project/get.ts +9 -0
- package/src/services/sandbox/client.ts +446 -16
- package/src/services/sandbox/create.ts +13 -0
- package/src/services/sandbox/execute.ts +26 -12
- package/src/services/sandbox/execution.ts +5 -2
- package/src/services/sandbox/files.ts +1 -1
- package/src/services/sandbox/index.ts +20 -0
- package/src/services/sandbox/job.ts +161 -0
- package/src/services/sandbox/run.ts +129 -34
- package/src/services/sandbox/types.ts +50 -0
- package/src/services/sandbox/util.ts +1 -0
- package/src/services/schedule/service.ts +20 -0
|
@@ -0,0 +1,220 @@
|
|
|
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
|
+
}
|
|
@@ -19,6 +19,7 @@ export const OAuthClientSchema = z.object({
|
|
|
19
19
|
refresh_token_lifetime_seconds: z.number().optional(),
|
|
20
20
|
id_token_lifetime_seconds: z.number().optional(),
|
|
21
21
|
allowed_user_ids: z.array(z.string()),
|
|
22
|
+
internal: z.boolean().optional().default(false),
|
|
22
23
|
created_at: z.string(),
|
|
23
24
|
updated_at: z.string(),
|
|
24
25
|
});
|
|
@@ -154,6 +155,8 @@ export const OAuthPermissionLevelSchema = z.object({
|
|
|
154
155
|
label: z.string(),
|
|
155
156
|
value: z.string(),
|
|
156
157
|
scopes: z.array(z.string()),
|
|
158
|
+
warning: z.boolean().optional(),
|
|
159
|
+
warningTitle: z.string().optional(),
|
|
157
160
|
});
|
|
158
161
|
|
|
159
162
|
export type OAuthPermissionLevel = z.infer<typeof OAuthPermissionLevelSchema>;
|
|
@@ -238,3 +241,95 @@ export type OAuthUserConsentRevokeResponse = z.infer<typeof OAuthUserConsentRevo
|
|
|
238
241
|
export type OAuthScopesResponse = z.infer<typeof OAuthScopesResponseSchema>;
|
|
239
242
|
export type OAuthOrgMembersResponse = z.infer<typeof OAuthOrgMembersResponseSchema>;
|
|
240
243
|
export type OAuthKeysRotateResponse = z.infer<typeof OAuthKeysRotateResponseSchema>;
|
|
244
|
+
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// OAuth 2.0 Authorization Code Flow Types
|
|
247
|
+
// ============================================================================
|
|
248
|
+
|
|
249
|
+
export const OAuthFlowConfigSchema = z.object({
|
|
250
|
+
clientId: z.string().optional().describe('OAuth client ID. Defaults to OAUTH_CLIENT_ID env var'),
|
|
251
|
+
clientSecret: z
|
|
252
|
+
.string()
|
|
253
|
+
.optional()
|
|
254
|
+
.describe('OAuth client secret. Defaults to OAUTH_CLIENT_SECRET env var'),
|
|
255
|
+
issuer: z
|
|
256
|
+
.string()
|
|
257
|
+
.optional()
|
|
258
|
+
.describe(
|
|
259
|
+
'OIDC issuer base URL. Defaults to OAUTH_ISSUER env var. Used to derive authorize/token/userinfo URLs'
|
|
260
|
+
),
|
|
261
|
+
authorizeUrl: z
|
|
262
|
+
.string()
|
|
263
|
+
.optional()
|
|
264
|
+
.describe('Authorization endpoint. Defaults to OAUTH_AUTHORIZE_URL or {issuer}/authorize'),
|
|
265
|
+
tokenUrl: z
|
|
266
|
+
.string()
|
|
267
|
+
.optional()
|
|
268
|
+
.describe('Token endpoint. Defaults to OAUTH_TOKEN_URL or {issuer}/oauth/token'),
|
|
269
|
+
userinfoUrl: z
|
|
270
|
+
.string()
|
|
271
|
+
.optional()
|
|
272
|
+
.describe('UserInfo endpoint. Defaults to OAUTH_USERINFO_URL or {issuer}/userinfo'),
|
|
273
|
+
revokeUrl: z
|
|
274
|
+
.string()
|
|
275
|
+
.optional()
|
|
276
|
+
.describe(
|
|
277
|
+
'Token revocation endpoint (RFC 7009). Defaults to OAUTH_REVOKE_URL or {issuer}/revoke'
|
|
278
|
+
),
|
|
279
|
+
endSessionUrl: z
|
|
280
|
+
.string()
|
|
281
|
+
.optional()
|
|
282
|
+
.describe(
|
|
283
|
+
'OIDC end session endpoint. Defaults to OAUTH_END_SESSION_URL or {issuer}/end_session'
|
|
284
|
+
),
|
|
285
|
+
scopes: z
|
|
286
|
+
.string()
|
|
287
|
+
.optional()
|
|
288
|
+
.describe('Space-separated scopes. Defaults to OAUTH_SCOPES or "openid profile email"'),
|
|
289
|
+
prompt: z
|
|
290
|
+
.enum(['none', 'login', 'consent', 'select_account'])
|
|
291
|
+
.optional()
|
|
292
|
+
.describe(
|
|
293
|
+
'OIDC prompt parameter. Controls authentication UX: "login" forces re-auth, "consent" forces consent screen, "none" fails if not authenticated, "select_account" lets user pick an account'
|
|
294
|
+
),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
export type OAuthFlowConfig = z.infer<typeof OAuthFlowConfigSchema>;
|
|
298
|
+
|
|
299
|
+
export const OAuthTokenResponseSchema = z.object({
|
|
300
|
+
access_token: z.string(),
|
|
301
|
+
token_type: z.string().optional(),
|
|
302
|
+
expires_in: z.number().optional(),
|
|
303
|
+
refresh_token: z.string().optional(),
|
|
304
|
+
scope: z.string().optional(),
|
|
305
|
+
id_token: z.string().optional(),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
export type OAuthTokenResponse = z.infer<typeof OAuthTokenResponseSchema>;
|
|
309
|
+
|
|
310
|
+
export const OAuthUserInfoSchema = z
|
|
311
|
+
.object({
|
|
312
|
+
sub: z.string(),
|
|
313
|
+
name: z.string().optional(),
|
|
314
|
+
given_name: z.string().optional(),
|
|
315
|
+
family_name: z.string().optional(),
|
|
316
|
+
email: z.string().optional(),
|
|
317
|
+
email_verified: z.boolean().optional(),
|
|
318
|
+
})
|
|
319
|
+
.catchall(z.unknown());
|
|
320
|
+
|
|
321
|
+
export type OAuthUserInfo = z.infer<typeof OAuthUserInfoSchema>;
|
|
322
|
+
|
|
323
|
+
export const StoredTokenSchema = z.object({
|
|
324
|
+
access_token: z.string(),
|
|
325
|
+
token_type: z.string().optional(),
|
|
326
|
+
refresh_token: z.string().optional(),
|
|
327
|
+
scope: z.string().optional(),
|
|
328
|
+
id_token: z.string().optional(),
|
|
329
|
+
expires_at: z
|
|
330
|
+
.number()
|
|
331
|
+
.optional()
|
|
332
|
+
.describe('Unix timestamp (seconds) when the access token expires'),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
export type StoredToken = z.infer<typeof StoredTokenSchema>;
|
|
@@ -16,9 +16,18 @@ export const ProjectSchema = z.object({
|
|
|
16
16
|
orgId: z.string().describe('the organization id'),
|
|
17
17
|
cloudRegion: z.string().nullable().optional().describe('the cloud region'),
|
|
18
18
|
vanityHostname: z.string().nullable().optional().describe('the vanity hostname'),
|
|
19
|
+
domains: z.array(z.string()).nullable().optional().describe('custom domains for the project'),
|
|
19
20
|
api_key: z.string().optional().describe('the SDK api key for the project'),
|
|
20
21
|
env: z.record(z.string(), z.string()).optional().describe('the environment key/values'),
|
|
21
22
|
secrets: z.record(z.string(), z.string()).optional().describe('the secrets key/values'),
|
|
23
|
+
urls: z
|
|
24
|
+
.object({
|
|
25
|
+
dashboard: z.string().describe('the dashboard URL for the project'),
|
|
26
|
+
app: z.string().describe('the public URL for the latest deployment'),
|
|
27
|
+
custom: z.array(z.string()).describe('custom domain URLs'),
|
|
28
|
+
})
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('project URLs'),
|
|
22
31
|
});
|
|
23
32
|
|
|
24
33
|
export const ProjectGetResponseSchema = APIResponseSchema(ProjectSchema);
|