@amigo-ai/platform-sdk 0.11.0 → 0.11.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.
package/dist/index.mjs CHANGED
@@ -3115,9 +3115,10 @@ async function requestDeviceCode(baseUrl, params, fetchFn) {
3115
3115
  }
3116
3116
  return await res.json();
3117
3117
  }
3118
- async function pollDeviceCode(baseUrl, deviceCode, scope, fetchFn) {
3118
+ async function pollDeviceCode(baseUrl, deviceCode, scope, workspaceId2, fetchFn) {
3119
3119
  const body = new URLSearchParams({ grant_type: "device_code", device_code: deviceCode });
3120
3120
  if (scope) body.set("scope", scope);
3121
+ if (workspaceId2) body.set("workspace_id", workspaceId2);
3121
3122
  const res = await identityPost(baseUrl, "/token", body, fetchFn);
3122
3123
  if (res.status === 300)
3123
3124
  return { type: "multi_workspace", data: await res.json() };
@@ -3193,6 +3194,64 @@ function toAuthResult(token, workspaceIdOverride) {
3193
3194
  scope: token.scope
3194
3195
  };
3195
3196
  }
3197
+ async function fetchWorkspaces(baseUrl, accessToken, fetchFn) {
3198
+ for (const path of ["/self/profile", "/self"]) {
3199
+ const res = await fetchFn(`${baseUrl}${path}`, {
3200
+ headers: { Authorization: `Bearer ${accessToken}` }
3201
+ });
3202
+ if (res.ok) {
3203
+ const data = await res.json();
3204
+ return (data.workspaces ?? []).map((ws) => ({
3205
+ workspace_id: ws.workspace_id,
3206
+ role: ws.role,
3207
+ name: ws.name
3208
+ }));
3209
+ }
3210
+ if (res.status !== 404) {
3211
+ throw new AmigoError(`Failed to fetch workspaces (${res.status})`, { statusCode: res.status });
3212
+ }
3213
+ }
3214
+ return [];
3215
+ }
3216
+ async function resolveWorkspaceFromBootstrap(baseUrl, token, options, fetchFn) {
3217
+ if (!token.refresh_token) {
3218
+ throw new AmigoError("Bootstrap token missing refresh_token", { errorCode: "server_error" });
3219
+ }
3220
+ const workspaces = await fetchWorkspaces(baseUrl, token.access_token, fetchFn);
3221
+ if (workspaces.length === 0) {
3222
+ throw new AmigoError(
3223
+ "No workspace memberships found. Create a workspace or request an invitation.",
3224
+ { errorCode: "no_workspaces" }
3225
+ );
3226
+ }
3227
+ if (workspaces.length === 1 && workspaces[0]) {
3228
+ const scoped2 = await doRefreshToken(
3229
+ baseUrl,
3230
+ { refreshToken: token.refresh_token, workspaceId: workspaces[0].workspace_id, scope: options.scope },
3231
+ fetchFn
3232
+ );
3233
+ return toAuthResult(scoped2, workspaces[0].workspace_id);
3234
+ }
3235
+ const workspaceId2 = await options.onWorkspaceRequired(workspaces);
3236
+ const scoped = await doRefreshToken(
3237
+ baseUrl,
3238
+ { refreshToken: token.refresh_token, workspaceId: workspaceId2, scope: options.scope },
3239
+ fetchFn
3240
+ );
3241
+ return toAuthResult(scoped, workspaceId2);
3242
+ }
3243
+ async function resolveWorkspaceFromMulti(baseUrl, multi, options, fetchFn) {
3244
+ if (!multi.refresh_token) {
3245
+ throw new AmigoError("Multi-workspace response missing refresh_token", { errorCode: "server_error" });
3246
+ }
3247
+ const workspaceId2 = await options.onWorkspaceRequired(multi.workspaces);
3248
+ const scoped = await doRefreshToken(
3249
+ baseUrl,
3250
+ { refreshToken: multi.refresh_token, workspaceId: workspaceId2, scope: options.scope },
3251
+ fetchFn
3252
+ );
3253
+ return toAuthResult(scoped, workspaceId2);
3254
+ }
3196
3255
  async function loginWithDeviceCode(options) {
3197
3256
  const baseUrl = (options.identityBaseUrl ?? DEFAULT_IDENTITY_URL).replace(/\/+$/, "");
3198
3257
  const fetchFn = options.fetch ?? globalThis.fetch;
@@ -3210,7 +3269,7 @@ async function loginWithDeviceCode(options) {
3210
3269
  if (options.signal?.aborted) throw new LoginCancelledError();
3211
3270
  options.onStatus?.("polling");
3212
3271
  try {
3213
- const result = await pollDeviceCode(baseUrl, issuance.device_code, options.scope, fetchFn);
3272
+ const result = await pollDeviceCode(baseUrl, issuance.device_code, options.scope, options.workspaceId, fetchFn);
3214
3273
  if (result.type === "pending") {
3215
3274
  options.onStatus?.("authorization_pending");
3216
3275
  continue;
@@ -3222,20 +3281,22 @@ async function loginWithDeviceCode(options) {
3222
3281
  }
3223
3282
  options.onStatus?.("approved");
3224
3283
  if (result.type === "token") {
3284
+ const claims = decodeJwtPayload(result.data.access_token);
3285
+ const isBootstrap = claims?.workspace_bootstrap || !claims?.workspace_id;
3286
+ if (isBootstrap && result.data.refresh_token) {
3287
+ if (options.workspaceId) {
3288
+ const scoped = await doRefreshToken(
3289
+ baseUrl,
3290
+ { refreshToken: result.data.refresh_token, workspaceId: options.workspaceId, scope: options.scope },
3291
+ fetchFn
3292
+ );
3293
+ return toAuthResult(scoped, options.workspaceId);
3294
+ }
3295
+ return await resolveWorkspaceFromBootstrap(baseUrl, result.data, options, fetchFn);
3296
+ }
3225
3297
  return toAuthResult(result.data);
3226
3298
  }
3227
- const workspaceId2 = await options.onWorkspaceRequired(result.data.workspaces);
3228
- if (!result.data.refresh_token) {
3229
- throw new AmigoError("Multi-workspace response missing refresh_token", {
3230
- errorCode: "server_error"
3231
- });
3232
- }
3233
- const tokenResponse = await doRefreshToken(
3234
- baseUrl,
3235
- { refreshToken: result.data.refresh_token, workspaceId: workspaceId2, scope: options.scope },
3236
- fetchFn
3237
- );
3238
- return toAuthResult(tokenResponse, workspaceId2);
3299
+ return await resolveWorkspaceFromMulti(baseUrl, result.data, options, fetchFn);
3239
3300
  } catch (err) {
3240
3301
  if (err instanceof AmigoError && err.errorCode === "expired_token") {
3241
3302
  options.onStatus?.("expired");