primitive-admin 1.2.0-alpha.7 → 1.2.0-alpha.9

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 (93) hide show
  1. package/README.md +8 -2
  2. package/assets/skill/skills/primitive-platform/SKILL.md +22 -8
  3. package/dist/src/commands/apps-children.d.ts +1 -1
  4. package/dist/src/commands/apps-children.js +55 -145
  5. package/dist/src/commands/apps-children.js.map +1 -1
  6. package/dist/src/commands/auth.js +107 -33
  7. package/dist/src/commands/auth.js.map +1 -1
  8. package/dist/src/commands/database-types.js +13 -313
  9. package/dist/src/commands/database-types.js.map +1 -1
  10. package/dist/src/commands/databases.js +13 -1
  11. package/dist/src/commands/databases.js.map +1 -1
  12. package/dist/src/commands/documents.js +70 -11
  13. package/dist/src/commands/documents.js.map +1 -1
  14. package/dist/src/commands/llm.js +9 -6
  15. package/dist/src/commands/llm.js.map +1 -1
  16. package/dist/src/commands/settings.d.ts +12 -9
  17. package/dist/src/commands/settings.js +31 -79
  18. package/dist/src/commands/settings.js.map +1 -1
  19. package/dist/src/commands/sync-app-settings.d.ts +12 -2
  20. package/dist/src/commands/sync-app-settings.js +35 -10
  21. package/dist/src/commands/sync-app-settings.js.map +1 -1
  22. package/dist/src/commands/sync.d.ts +1 -8
  23. package/dist/src/commands/sync.js +47 -45
  24. package/dist/src/commands/sync.js.map +1 -1
  25. package/dist/src/lib/api-client.d.ts +46 -6
  26. package/dist/src/lib/api-client.js +156 -64
  27. package/dist/src/lib/api-client.js.map +1 -1
  28. package/dist/src/lib/app-settings-descriptor.d.ts +38 -0
  29. package/dist/src/lib/app-settings-descriptor.js +96 -4
  30. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  31. package/dist/src/lib/child-apps-local.d.ts +0 -46
  32. package/dist/src/lib/child-apps-local.js +2 -72
  33. package/dist/src/lib/child-apps-local.js.map +1 -1
  34. package/dist/src/lib/credentials-home.d.ts +51 -0
  35. package/dist/src/lib/credentials-home.js +169 -0
  36. package/dist/src/lib/credentials-home.js.map +1 -0
  37. package/dist/src/lib/credentials-lock.d.ts +80 -0
  38. package/dist/src/lib/credentials-lock.js +222 -0
  39. package/dist/src/lib/credentials-lock.js.map +1 -0
  40. package/dist/src/lib/credentials-store.d.ts +56 -4
  41. package/dist/src/lib/credentials-store.js +236 -78
  42. package/dist/src/lib/credentials-store.js.map +1 -1
  43. package/dist/src/lib/env-resolver-core.d.ts +1 -1
  44. package/dist/src/lib/env-resolver-core.js.map +1 -1
  45. package/dist/src/lib/function-bundle.d.ts +38 -18
  46. package/dist/src/lib/function-bundle.js +60 -35
  47. package/dist/src/lib/function-bundle.js.map +1 -1
  48. package/dist/src/lib/function-document-types.d.ts +5 -3
  49. package/dist/src/lib/function-document-types.js +10 -14
  50. package/dist/src/lib/function-document-types.js.map +1 -1
  51. package/dist/src/lib/function-source-roots.d.ts +67 -0
  52. package/dist/src/lib/function-source-roots.js +148 -0
  53. package/dist/src/lib/function-source-roots.js.map +1 -0
  54. package/dist/src/lib/function-sync.d.ts +27 -0
  55. package/dist/src/lib/function-sync.js +16 -3
  56. package/dist/src/lib/function-sync.js.map +1 -1
  57. package/dist/src/lib/function-triggers.d.ts +15 -3
  58. package/dist/src/lib/function-triggers.js +72 -10
  59. package/dist/src/lib/function-triggers.js.map +1 -1
  60. package/dist/src/lib/function-typecheck.d.ts +37 -2
  61. package/dist/src/lib/function-typecheck.js +210 -28
  62. package/dist/src/lib/function-typecheck.js.map +1 -1
  63. package/dist/src/lib/generated-config-surfaces.d.ts +48 -11
  64. package/dist/src/lib/generated-config-surfaces.js +178 -49
  65. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  66. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  67. package/dist/src/lib/generated-sdk-types.js +1 -1
  68. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  69. package/dist/src/lib/generated-workflow-model-fields.d.ts +20 -0
  70. package/dist/src/lib/generated-workflow-model-fields.js +53 -0
  71. package/dist/src/lib/generated-workflow-model-fields.js.map +1 -0
  72. package/dist/src/lib/login-scope-lines.d.ts +1 -1
  73. package/dist/src/lib/login-scope-lines.js +1 -1
  74. package/dist/src/lib/login-scope-lines.js.map +1 -1
  75. package/dist/src/lib/project-config.d.ts +7 -0
  76. package/dist/src/lib/project-config.js +22 -1
  77. package/dist/src/lib/project-config.js.map +1 -1
  78. package/dist/src/lib/prompt-schema-codegen.d.ts +10 -3
  79. package/dist/src/lib/prompt-schema-codegen.js +52 -14
  80. package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
  81. package/dist/src/lib/root-import-decision.d.ts +9 -4
  82. package/dist/src/lib/root-import-decision.js +24 -3
  83. package/dist/src/lib/root-import-decision.js.map +1 -1
  84. package/dist/src/lib/scope-request.d.ts +8 -5
  85. package/dist/src/lib/scope-request.js +17 -8
  86. package/dist/src/lib/scope-request.js.map +1 -1
  87. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +6 -2
  88. package/dist/src/lib/swift-codegen/agentGenerator.js +56 -4
  89. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -1
  90. package/dist/src/lib/workflow-field-descriptor.d.ts +91 -0
  91. package/dist/src/lib/workflow-field-descriptor.js +153 -0
  92. package/dist/src/lib/workflow-field-descriptor.js.map +1 -0
  93. package/package.json +2 -2
@@ -51,6 +51,8 @@ export interface AppChildSummary {
51
51
  expiresAt: string | null;
52
52
  /** Exact origins the child's CORS and email-redirect checks accept beside its authored lists. */
53
53
  previewOrigins?: string[];
54
+ /** The child's protection level: always `internal` (#4564); absent from an older server. */
55
+ protection?: string;
54
56
  }
55
57
  /** A child's preview origins after a write to them. */
56
58
  export interface ChildPreviewOrigins {
@@ -499,9 +501,18 @@ export declare class ApiClient {
499
501
  request<T = any>(path: string, options?: RequestInit): Promise<T>;
500
502
  /**
501
503
  * Whether a 403 should be retried with the stored step-up: it names
502
- * `admin` among the missing scopes, and an unexpired step-up is stored.
504
+ * `admin` among the missing scopes, or it is a protected app's refusal of
505
+ * a token that is no step-up, and an unexpired step-up is stored.
503
506
  */
504
507
  private stepUpApplies;
508
+ /**
509
+ * #4564 — a request outside `request` (an upload, a function invocation)
510
+ * sent with the CLI's own credentials, retried once with the stored step-up
511
+ * exactly as `request` retries. `init` builds the request for a bearer. The
512
+ * answer is the retry's, unless the retry was refused 401: the step-up is
513
+ * dropped and the original refusal comes back, still readable.
514
+ */
515
+ private fetchWithStepUp;
505
516
  /** Forget a step-up the server no longer accepts. */
506
517
  private dropStepUp;
507
518
  /** The typed error a failed response throws. */
@@ -2104,10 +2115,14 @@ export declare class ApiClient {
2104
2115
  * through the caller's document permission (reader+) or the app-admin arm.
2105
2116
  * Returns the unified `{ items, hasMore, nextCursor? }` envelope. The cursor
2106
2117
  * is an opaque token — feed `nextCursor` back as `cursor` for the next page.
2118
+ * `limit` is 100 records when omitted and at most 10000; it is forwarded
2119
+ * unparsed (a string from the command line) so the server validates it.
2107
2120
  */
2108
2121
  queryDocumentRecords(appId: string, documentId: string, modelName: string, queryOptions?: {
2109
2122
  filter?: Record<string, any>;
2110
- limit?: number;
2123
+ /** `{ field: 1 | -1 }`, key order as precedence; the route validates it. */
2124
+ sort?: unknown;
2125
+ limit?: number | string;
2111
2126
  cursor?: string;
2112
2127
  /** #3764 — the format the caller believes this document has. */
2113
2128
  documentFormat?: 1 | 3;
@@ -2127,16 +2142,20 @@ export declare class ApiClient {
2127
2142
  * with `{ options: { operations, groupBy?, filter? } }`, answering `{ result }`
2128
2143
  * in the same shape as the database endpoint.
2129
2144
  *
2130
- * `groupBy` takes plain field names only: StringSet facet grouping and
2131
- * `{ field, contains }` membership grouping are database-only, which is why
2132
- * this signature is narrower than the databases one.
2145
+ * `groupBy` takes plain field names and period levels `{ field, period }`
2146
+ * (keyed `<field>_<period>` in a row's `group`): StringSet facet grouping
2147
+ * and `{ field, contains }` membership grouping are database-only, which is
2148
+ * why this signature is narrower than the databases one.
2133
2149
  */
2134
2150
  aggregateDocumentRecords(appId: string, documentId: string, modelName: string, options: {
2135
2151
  operations: {
2136
2152
  type: string;
2137
2153
  field?: string;
2138
2154
  }[];
2139
- groupBy?: string[];
2155
+ groupBy?: Array<string | {
2156
+ field: string;
2157
+ period: "month" | "quarter" | "year";
2158
+ }>;
2140
2159
  filter?: Record<string, any>;
2141
2160
  }, extra?: {
2142
2161
  documentFormat?: 1 | 3;
@@ -2354,6 +2373,27 @@ export declare class ApiClient {
2354
2373
  contentType: string;
2355
2374
  sha256: string;
2356
2375
  }): Promise<any>;
2376
+ /**
2377
+ * Upload a file to an agent session by `sessionId` (#4585): the document
2378
+ * blob upload's headers and body, on the session's attachment route.
2379
+ */
2380
+ uploadAgentSessionAttachment(appId: string, sessionId: string, blobId: string, data: Buffer, meta: {
2381
+ filename: string;
2382
+ contentType: string;
2383
+ sha256: string;
2384
+ }): Promise<any>;
2385
+ /** Download an agent session's file by `sessionId` (#4585). */
2386
+ downloadAgentSessionAttachment(appId: string, sessionId: string, blobId: string): Promise<Buffer>;
2387
+ /**
2388
+ * An agent session file's download headers (lower-cased names) by
2389
+ * `sessionId` (#4585). A refusal has no body, so it fails with the status
2390
+ * alone and no code.
2391
+ */
2392
+ headAgentSessionAttachment(appId: string, sessionId: string, blobId: string): Promise<{
2393
+ status: number;
2394
+ headers: Record<string, string>;
2395
+ }>;
2396
+ private agentSessionAttachmentRead;
2357
2397
  listDocumentAliases(appId: string, documentId: string): Promise<any[]>;
2358
2398
  /**
2359
2399
  * The document a user's alias points at, or null when the user holds no
@@ -243,9 +243,10 @@ export class ApiClient {
243
243
  };
244
244
  let { response, text } = await send(credentials.accessToken);
245
245
  // #3889 — a 403 naming `admin` is retried once with a stored, unexpired
246
- // step-up. A retry refused 401 means the step-up is gone (revoked or
247
- // expired early): drop it and show the original refusal. The retry's own
248
- // refusal (say, an unpinned step-up on an app route) is shown as it is.
246
+ // step-up, and (#4564) so is a write a protected app refused for want of
247
+ // one. A retry refused 401 means the step-up is gone (revoked or expired
248
+ // early): drop it and show the original refusal. The retry's own refusal
249
+ // (say, a step-up without the scope this command needs) is shown as it is.
249
250
  if (response.status === 403 && this.stepUpApplies(response, text, path)) {
250
251
  const retried = await send(credentials.stepUp.accessToken);
251
252
  if (retried.response.ok) {
@@ -265,7 +266,8 @@ export class ApiClient {
265
266
  }
266
267
  /**
267
268
  * Whether a 403 should be retried with the stored step-up: it names
268
- * `admin` among the missing scopes, and an unexpired step-up is stored.
269
+ * `admin` among the missing scopes, or it is a protected app's refusal of
270
+ * a token that is no step-up, and an unexpired step-up is stored.
269
271
  */
270
272
  stepUpApplies(response, text, path) {
271
273
  const stepUp = this.credentials?.stepUp;
@@ -273,10 +275,41 @@ export class ApiClient {
273
275
  return false;
274
276
  if (new Date(stepUp.expiresAt).getTime() <= Date.now())
275
277
  return false;
276
- const details = parseErrorResponse(response, text, path).details;
278
+ const parsed = parseErrorResponse(response, text, path);
279
+ if (parsed.code === "PROTECTED_APP_STEP_UP_REQUIRED")
280
+ return true;
281
+ const details = parsed.details;
277
282
  const missing = details && !Array.isArray(details) ? details.missing : undefined;
278
283
  return Array.isArray(missing) && missing.includes("admin");
279
284
  }
285
+ /**
286
+ * #4564 — a request outside `request` (an upload, a function invocation)
287
+ * sent with the CLI's own credentials, retried once with the stored step-up
288
+ * exactly as `request` retries. `init` builds the request for a bearer. The
289
+ * answer is the retry's, unless the retry was refused 401: the step-up is
290
+ * dropped and the original refusal comes back, still readable.
291
+ */
292
+ async fetchWithStepUp(url, path, accessToken, init) {
293
+ const response = await fetchWithTLS(url, init(accessToken));
294
+ if (response.status !== 403)
295
+ return response;
296
+ // Read once to decide; hand the caller a response it can still read.
297
+ const text = await response.text();
298
+ const original = new Response(text, {
299
+ status: response.status,
300
+ statusText: response.statusText,
301
+ headers: response.headers,
302
+ });
303
+ if (!this.stepUpApplies(response, text, path))
304
+ return original;
305
+ const retried = await fetchWithTLS(url, init(this.credentials.stepUp.accessToken));
306
+ if (retried.status === 401) {
307
+ await retried.text().catch(() => "");
308
+ this.dropStepUp();
309
+ return original;
310
+ }
311
+ return retried;
312
+ }
280
313
  /** Forget a step-up the server no longer accepts. */
281
314
  dropStepUp() {
282
315
  if (!this.credentials?.stepUp)
@@ -944,21 +977,25 @@ export class ApiClient {
944
977
  : options?.runtime === "task"
945
978
  ? `${base}/start`
946
979
  : base;
947
- const headers = {
948
- Authorization: `Bearer ${options?.bearer ?? credentials.accessToken}`,
949
- "Content-Type": "application/json",
980
+ const init = (bearer) => {
981
+ const headers = {
982
+ Authorization: `Bearer ${bearer}`,
983
+ "Content-Type": "application/json",
984
+ };
985
+ // The CLI's own credentials reach an app-scoped route through the
986
+ // global-admin header; a minted user token does not need it and must
987
+ // not carry it.
988
+ if (!options?.bearer && credentials.globalAdminAppId) {
989
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
990
+ }
991
+ return { method: "POST", headers, body: JSON.stringify(body) };
950
992
  };
951
- // The CLI's own credentials reach an app-scoped route through the
952
- // global-admin header; a minted user token does not need it and must not
953
- // carry it.
954
- if (!options?.bearer && credentials.globalAdminAppId) {
955
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
956
- }
957
- const response = await fetchWithTLS(`${credentials.serverUrl}${path}`, {
958
- method: "POST",
959
- headers,
960
- body: JSON.stringify(body),
961
- });
993
+ const url = `${credentials.serverUrl}${path}`;
994
+ // A minted user token is sent as it is; the CLI's own credentials get the
995
+ // step-up retry a protected app's refusal asks for (#4564).
996
+ const response = options?.bearer
997
+ ? await fetchWithTLS(url, init(options.bearer))
998
+ : await this.fetchWithStepUp(url, path, credentials.accessToken, init);
962
999
  const text = await response.text();
963
1000
  let parsed = null;
964
1001
  try {
@@ -1495,17 +1532,15 @@ export class ApiClient {
1495
1532
  const credentials = await this.ensureAuthenticated();
1496
1533
  const encodedFilename = encodeURIComponent(filename);
1497
1534
  const url = `${credentials.serverUrl}/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases/${testCaseId}/attachments/${encodedFilename}`;
1498
- const headers = {
1499
- Authorization: `Bearer ${credentials.accessToken}`,
1500
- "Content-Type": contentType,
1501
- };
1502
- if (credentials.globalAdminAppId) {
1503
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
1504
- }
1505
- const response = await fetchWithTLS(url, {
1506
- method: "PUT",
1507
- headers,
1508
- body: data,
1535
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
1536
+ const headers = {
1537
+ Authorization: `Bearer ${bearer}`,
1538
+ "Content-Type": contentType,
1539
+ };
1540
+ if (credentials.globalAdminAppId) {
1541
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
1542
+ }
1543
+ return { method: "PUT", headers, body: data };
1509
1544
  });
1510
1545
  const text = await response.text();
1511
1546
  if (!response.ok) {
@@ -2460,11 +2495,15 @@ export class ApiClient {
2460
2495
  * through the caller's document permission (reader+) or the app-admin arm.
2461
2496
  * Returns the unified `{ items, hasMore, nextCursor? }` envelope. The cursor
2462
2497
  * is an opaque token — feed `nextCursor` back as `cursor` for the next page.
2498
+ * `limit` is 100 records when omitted and at most 10000; it is forwarded
2499
+ * unparsed (a string from the command line) so the server validates it.
2463
2500
  */
2464
2501
  async queryDocumentRecords(appId, documentId, modelName, queryOptions) {
2465
2502
  const query = {};
2466
2503
  if (queryOptions?.filter)
2467
2504
  query.filter = JSON.stringify(queryOptions.filter);
2505
+ if (queryOptions?.sort !== undefined)
2506
+ query.sort = JSON.stringify(queryOptions.sort);
2468
2507
  if (queryOptions?.documentFormat !== undefined) {
2469
2508
  query.documentFormat = queryOptions.documentFormat;
2470
2509
  }
@@ -2496,9 +2535,10 @@ export class ApiClient {
2496
2535
  * with `{ options: { operations, groupBy?, filter? } }`, answering `{ result }`
2497
2536
  * in the same shape as the database endpoint.
2498
2537
  *
2499
- * `groupBy` takes plain field names only: StringSet facet grouping and
2500
- * `{ field, contains }` membership grouping are database-only, which is why
2501
- * this signature is narrower than the databases one.
2538
+ * `groupBy` takes plain field names and period levels `{ field, period }`
2539
+ * (keyed `<field>_<period>` in a row's `group`): StringSet facet grouping
2540
+ * and `{ field, contains }` membership grouping are database-only, which is
2541
+ * why this signature is narrower than the databases one.
2502
2542
  */
2503
2543
  async aggregateDocumentRecords(appId, documentId, modelName, options, extra) {
2504
2544
  return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/aggregate` +
@@ -2589,18 +2629,16 @@ export class ApiClient {
2589
2629
  form.set("firstId", chunk.firstId);
2590
2630
  form.set("lastId", chunk.lastId);
2591
2631
  form.set("chunk", new Blob([chunk.body]), `${chunk.index}.ndjson.gz`);
2592
- const headers = {
2593
- Authorization: `Bearer ${credentials.accessToken}`,
2594
- };
2595
- if (credentials.globalAdminAppId) {
2596
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2597
- }
2598
2632
  const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
2599
2633
  `/ingest/${encodeURIComponent(sessionId)}/chunks`;
2600
- const response = await fetchWithTLS(url, {
2601
- method: "POST",
2602
- headers,
2603
- body: form,
2634
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
2635
+ const headers = {
2636
+ Authorization: `Bearer ${bearer}`,
2637
+ };
2638
+ if (credentials.globalAdminAppId) {
2639
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2640
+ }
2641
+ return { method: "POST", headers, body: form };
2604
2642
  });
2605
2643
  const text = await response.text();
2606
2644
  let result = {};
@@ -2758,6 +2796,31 @@ export class ApiClient {
2758
2796
  async uploadBlob(appId, documentId, blobId, data, meta) {
2759
2797
  const credentials = await this.ensureAuthenticated();
2760
2798
  const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}/blobs/${blobId}`;
2799
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
2800
+ const headers = {
2801
+ Authorization: `Bearer ${bearer}`,
2802
+ "Content-Type": meta.contentType,
2803
+ "X-Blob-Filename": encodeURIComponent(meta.filename),
2804
+ "X-Blob-Size": String(data.length),
2805
+ "X-Blob-Sha256": meta.sha256,
2806
+ };
2807
+ if (credentials.globalAdminAppId) {
2808
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2809
+ }
2810
+ return { method: "PUT", headers, body: data };
2811
+ });
2812
+ if (!response.ok) {
2813
+ throw await blobFailure(response, "Failed to upload blob: ");
2814
+ }
2815
+ return response.json();
2816
+ }
2817
+ /**
2818
+ * Upload a file to an agent session by `sessionId` (#4585): the document
2819
+ * blob upload's headers and body, on the session's attachment route.
2820
+ */
2821
+ async uploadAgentSessionAttachment(appId, sessionId, blobId, data, meta) {
2822
+ const credentials = await this.ensureAuthenticated();
2823
+ const url = `${credentials.serverUrl}/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(blobId)}`;
2761
2824
  const headers = {
2762
2825
  Authorization: `Bearer ${credentials.accessToken}`,
2763
2826
  "Content-Type": meta.contentType,
@@ -2768,16 +2831,47 @@ export class ApiClient {
2768
2831
  if (credentials.globalAdminAppId) {
2769
2832
  headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2770
2833
  }
2771
- const response = await fetchWithTLS(url, {
2772
- method: "PUT",
2773
- headers,
2774
- body: data,
2775
- });
2834
+ const response = await fetchWithTLS(url, { method: "PUT", headers, body: data });
2776
2835
  if (!response.ok) {
2777
- throw await blobFailure(response, "Failed to upload blob: ");
2836
+ throw await blobFailure(response, "Failed to upload attachment: ");
2778
2837
  }
2779
2838
  return response.json();
2780
2839
  }
2840
+ /** Download an agent session's file by `sessionId` (#4585). */
2841
+ async downloadAgentSessionAttachment(appId, sessionId, blobId) {
2842
+ const response = await this.agentSessionAttachmentRead("GET", appId, sessionId, blobId);
2843
+ if (!response.ok) {
2844
+ throw await blobFailure(response, "Failed to download attachment: ");
2845
+ }
2846
+ return Buffer.from(await response.arrayBuffer());
2847
+ }
2848
+ /**
2849
+ * An agent session file's download headers (lower-cased names) by
2850
+ * `sessionId` (#4585). A refusal has no body, so it fails with the status
2851
+ * alone and no code.
2852
+ */
2853
+ async headAgentSessionAttachment(appId, sessionId, blobId) {
2854
+ const response = await this.agentSessionAttachmentRead("HEAD", appId, sessionId, blobId);
2855
+ if (!response.ok && response.status !== 304) {
2856
+ throw new ApiError(`Failed to read attachment headers: HTTP ${response.status}`, response.status);
2857
+ }
2858
+ const headers = {};
2859
+ response.headers.forEach((value, name) => {
2860
+ headers[name.toLowerCase()] = value;
2861
+ });
2862
+ return { status: response.status, headers };
2863
+ }
2864
+ async agentSessionAttachmentRead(method, appId, sessionId, blobId) {
2865
+ const credentials = await this.ensureAuthenticated();
2866
+ const url = `${credentials.serverUrl}/app/${appId}/api/agent-sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(blobId)}/download`;
2867
+ const headers = {
2868
+ Authorization: `Bearer ${credentials.accessToken}`,
2869
+ };
2870
+ if (credentials.globalAdminAppId) {
2871
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2872
+ }
2873
+ return fetchWithTLS(url, { method, headers });
2874
+ }
2781
2875
  async listDocumentAliases(appId, documentId) {
2782
2876
  return this.get(`/app/${appId}/api/documents/${documentId}/aliases`);
2783
2877
  }
@@ -2939,21 +3033,19 @@ export class ApiClient {
2939
3033
  async uploadBucketBlob(appId, bucketIdOrKey, data, meta) {
2940
3034
  const credentials = await this.ensureAuthenticated();
2941
3035
  const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`;
2942
- const headers = {
2943
- Authorization: `Bearer ${credentials.accessToken}`,
2944
- "Content-Type": meta.contentType,
2945
- "X-Blob-Filename": encodeURIComponent(meta.filename),
2946
- };
2947
- if (meta.tags && meta.tags.length > 0) {
2948
- headers["X-Blob-Tags"] = JSON.stringify(meta.tags);
2949
- }
2950
- if (credentials.globalAdminAppId) {
2951
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2952
- }
2953
- const response = await fetchWithTLS(url, {
2954
- method: "POST",
2955
- headers,
2956
- body: data,
3036
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
3037
+ const headers = {
3038
+ Authorization: `Bearer ${bearer}`,
3039
+ "Content-Type": meta.contentType,
3040
+ "X-Blob-Filename": encodeURIComponent(meta.filename),
3041
+ };
3042
+ if (meta.tags && meta.tags.length > 0) {
3043
+ headers["X-Blob-Tags"] = JSON.stringify(meta.tags);
3044
+ }
3045
+ if (credentials.globalAdminAppId) {
3046
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
3047
+ }
3048
+ return { method: "POST", headers, body: data };
2957
3049
  });
2958
3050
  if (!response.ok) {
2959
3051
  throw await blobFailure(response, "Failed to upload blob: ");