@slates/provider-handler 1.0.0-rc.22 → 1.0.0-rc.26

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.cjs CHANGED
@@ -37,6 +37,22 @@ var import_error4 = require("@lowerdeck/error");
37
37
  var import_proto = require("@slates/proto");
38
38
  var import_provider2 = require("@slates/provider");
39
39
 
40
+ // src/pQueue.ts
41
+ var import_p_queue = __toESM(require("p-queue"), 1);
42
+ var resolveDefaultExport = (value) => {
43
+ let current = value;
44
+ for (let i = 0; i < 4; i++) {
45
+ if (typeof current === "function") return current;
46
+ if (current && typeof current === "object" && "default" in current) {
47
+ current = current.default;
48
+ continue;
49
+ }
50
+ break;
51
+ }
52
+ throw new TypeError("Module export is not a constructor");
53
+ };
54
+ var PQueue = resolveDefaultExport(import_p_queue.default);
55
+
40
56
  // src/spec.ts
41
57
  var import_error2 = require("@lowerdeck/error");
42
58
  var import_provider = require("@slates/provider");
@@ -94,8 +110,7 @@ var mapAuthMethod = (slate, m) => ({
94
110
  scopes: "scopes" in m ? m.scopes.map((s) => ({
95
111
  id: s.scope,
96
112
  title: s.title,
97
- description: s.description,
98
- defaultChecked: s.defaultChecked
113
+ description: s.description
99
114
  })) : void 0,
100
115
  inputSchema: toJsonSchema(m.inputSchema ?? import_zod.default.object({})),
101
116
  outputSchema: toJsonSchema(slate.spec.auth.outputSchema),
@@ -253,6 +268,36 @@ var serializeWebhookHttpResponse = async (response) => {
253
268
  };
254
269
 
255
270
  // src/index.ts
271
+ var DEFAULT_MAX_ATTACHMENT_SIZE_BYTES = 1e8;
272
+ var routeAttachmentsThroughDirectUpload = async (attachments, live) => {
273
+ if (!attachments || attachments.length === 0 || !live) return attachments;
274
+ let contentEntries = attachments.map((attachment, index) => ({ attachment, index })).filter((entry) => entry.attachment.content.type === "content");
275
+ if (contentEntries.length === 0) return attachments;
276
+ let queue = new PQueue({ concurrency: 10 });
277
+ let replacements = /* @__PURE__ */ new Map();
278
+ await Promise.all(
279
+ contentEntries.map(
280
+ (entry) => queue.add(async () => {
281
+ try {
282
+ let body = entry.attachment.content.type === "content" ? entry.attachment.content.encoding === "base64" ? Buffer.from(entry.attachment.content.content, "base64") : Buffer.from(entry.attachment.content.content, "utf-8") : Buffer.alloc(0);
283
+ replacements.set(
284
+ entry.index,
285
+ await (0, import_provider2.uploadAttachmentDirect)({
286
+ live,
287
+ mimeType: entry.attachment.mimeType,
288
+ body
289
+ })
290
+ );
291
+ } catch {
292
+ replacements.set(entry.index, null);
293
+ }
294
+ })
295
+ )
296
+ );
297
+ return attachments.map(
298
+ (attachment, index) => replacements.has(index) ? replacements.get(index) : attachment
299
+ ).filter((a) => a != null);
300
+ };
256
301
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
257
302
  var getObjectKeyCount = (value) => isRecord(value) ? Object.keys(value).length : void 0;
258
303
  var DOWNLOAD_ATTACHMENT_URL_KEYS = /* @__PURE__ */ new Set([
@@ -316,6 +361,19 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
316
361
  let auth = new State(null);
317
362
  let config = new State(null);
318
363
  let session = new State(null);
364
+ let hubCapabilities = new State(null);
365
+ let liveInvocation = new State(
366
+ null
367
+ );
368
+ let getLiveInvocation = () => {
369
+ let directUpload = hubCapabilities.get()?.attachments?.directUpload;
370
+ let live = liveInvocation.get();
371
+ if (!directUpload?.enabled || !live) return null;
372
+ return {
373
+ ...live,
374
+ maxAttachmentSizeBytes: directUpload.maxAttachmentSizeBytes ?? DEFAULT_MAX_ATTACHMENT_SIZE_BYTES
375
+ };
376
+ };
319
377
  let logger = new import_provider2.SlateLogger(listeners);
320
378
  let providerTrace = {
321
379
  providerId: slate.spec.key,
@@ -442,6 +500,15 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
442
500
  state: params.state
443
501
  });
444
502
  });
503
+ manager.onNotification("slates/hub.capabilities.set", async ({ params }) => {
504
+ hubCapabilities.set(params.capabilities);
505
+ });
506
+ manager.onNotification("slates/hub.live_invocation.set", async ({ params }) => {
507
+ liveInvocation.set({
508
+ token: params.token,
509
+ baseUrl: params.baseUrl
510
+ });
511
+ });
445
512
  manager.onRequest("slates/config.changed", async ({ params }) => {
446
513
  getContextBasic();
447
514
  let newConfig = validate(
@@ -906,19 +973,39 @@ var createProviderHandler = (slate, listeners) => (0, import_proto.createSlatesP
906
973
  },
907
974
  () => (0, import_provider2.runWithContext)(context, () => action.handleInvocation(context))
908
975
  );
976
+ let contextAttachments = await context._finalizeAttachments();
977
+ let merged = mergeAttachments(
978
+ [...res.attachments ?? [], ...contextAttachments],
979
+ res.output
980
+ );
981
+ let redacted = (0, import_provider2.redactUrlAttachmentSecrets)(
982
+ merged,
983
+ context._getAuthConfigForRedaction()
984
+ );
985
+ let finalAttachments = await routeAttachmentsThroughDirectUpload(
986
+ redacted,
987
+ getLiveInvocation()
988
+ );
909
989
  return withRequestTraces(context, {
910
990
  output: res.output,
911
991
  message: res.message,
912
- attachments: mergeAttachments(res.attachments, res.output)
992
+ attachments: finalAttachments
913
993
  });
914
994
  };
915
995
  if (action.isPublic) {
916
996
  getContextBasic();
917
- return invoke(new import_provider2.SlatePublicContext(input, slate.spec, logger));
997
+ return invoke(new import_provider2.SlatePublicContext(input, slate.spec, logger, getLiveInvocation()));
918
998
  }
919
999
  let ctx = getContextFull();
920
1000
  return invoke(
921
- new import_provider2.SlateContext(ctx.config, input, ctx.auth?.output, slate.spec, logger)
1001
+ new import_provider2.SlateContext(
1002
+ ctx.config,
1003
+ input,
1004
+ ctx.auth?.output,
1005
+ slate.spec,
1006
+ logger,
1007
+ getLiveInvocation()
1008
+ )
922
1009
  );
923
1010
  });
924
1011
  manager.onRequest("slates/action.trigger.map_event", async ({ params }) => {
@@ -5,12 +5,30 @@ import {
5
5
  SLATES_PROTOCOL_VERSION
6
6
  } from "@slates/proto";
7
7
  import {
8
+ redactUrlAttachmentSecrets,
8
9
  runWithContext,
9
10
  SlateContext,
10
11
  SlateLogger,
11
- SlatePublicContext
12
+ SlatePublicContext,
13
+ uploadAttachmentDirect
12
14
  } from "@slates/provider";
13
15
 
16
+ // src/pQueue.ts
17
+ import PQueueImport from "p-queue";
18
+ var resolveDefaultExport = (value) => {
19
+ let current = value;
20
+ for (let i = 0; i < 4; i++) {
21
+ if (typeof current === "function") return current;
22
+ if (current && typeof current === "object" && "default" in current) {
23
+ current = current.default;
24
+ continue;
25
+ }
26
+ break;
27
+ }
28
+ throw new TypeError("Module export is not a constructor");
29
+ };
30
+ var PQueue = resolveDefaultExport(PQueueImport);
31
+
14
32
  // src/spec.ts
15
33
  import { badRequestError, notFoundError, ServiceError as ServiceError2 } from "@lowerdeck/error";
16
34
  import {
@@ -70,8 +88,7 @@ var mapAuthMethod = (slate, m) => ({
70
88
  scopes: "scopes" in m ? m.scopes.map((s) => ({
71
89
  id: s.scope,
72
90
  title: s.title,
73
- description: s.description,
74
- defaultChecked: s.defaultChecked
91
+ description: s.description
75
92
  })) : void 0,
76
93
  inputSchema: toJsonSchema(m.inputSchema ?? z.object({})),
77
94
  outputSchema: toJsonSchema(slate.spec.auth.outputSchema),
@@ -229,6 +246,36 @@ var serializeWebhookHttpResponse = async (response) => {
229
246
  };
230
247
 
231
248
  // src/index.ts
249
+ var DEFAULT_MAX_ATTACHMENT_SIZE_BYTES = 1e8;
250
+ var routeAttachmentsThroughDirectUpload = async (attachments, live) => {
251
+ if (!attachments || attachments.length === 0 || !live) return attachments;
252
+ let contentEntries = attachments.map((attachment, index) => ({ attachment, index })).filter((entry) => entry.attachment.content.type === "content");
253
+ if (contentEntries.length === 0) return attachments;
254
+ let queue = new PQueue({ concurrency: 10 });
255
+ let replacements = /* @__PURE__ */ new Map();
256
+ await Promise.all(
257
+ contentEntries.map(
258
+ (entry) => queue.add(async () => {
259
+ try {
260
+ let body = entry.attachment.content.type === "content" ? entry.attachment.content.encoding === "base64" ? Buffer.from(entry.attachment.content.content, "base64") : Buffer.from(entry.attachment.content.content, "utf-8") : Buffer.alloc(0);
261
+ replacements.set(
262
+ entry.index,
263
+ await uploadAttachmentDirect({
264
+ live,
265
+ mimeType: entry.attachment.mimeType,
266
+ body
267
+ })
268
+ );
269
+ } catch {
270
+ replacements.set(entry.index, null);
271
+ }
272
+ })
273
+ )
274
+ );
275
+ return attachments.map(
276
+ (attachment, index) => replacements.has(index) ? replacements.get(index) : attachment
277
+ ).filter((a) => a != null);
278
+ };
232
279
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
233
280
  var getObjectKeyCount = (value) => isRecord(value) ? Object.keys(value).length : void 0;
234
281
  var DOWNLOAD_ATTACHMENT_URL_KEYS = /* @__PURE__ */ new Set([
@@ -292,6 +339,19 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
292
339
  let auth = new State(null);
293
340
  let config = new State(null);
294
341
  let session = new State(null);
342
+ let hubCapabilities = new State(null);
343
+ let liveInvocation = new State(
344
+ null
345
+ );
346
+ let getLiveInvocation = () => {
347
+ let directUpload = hubCapabilities.get()?.attachments?.directUpload;
348
+ let live = liveInvocation.get();
349
+ if (!directUpload?.enabled || !live) return null;
350
+ return {
351
+ ...live,
352
+ maxAttachmentSizeBytes: directUpload.maxAttachmentSizeBytes ?? DEFAULT_MAX_ATTACHMENT_SIZE_BYTES
353
+ };
354
+ };
295
355
  let logger = new SlateLogger(listeners);
296
356
  let providerTrace = {
297
357
  providerId: slate.spec.key,
@@ -418,6 +478,15 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
418
478
  state: params.state
419
479
  });
420
480
  });
481
+ manager.onNotification("slates/hub.capabilities.set", async ({ params }) => {
482
+ hubCapabilities.set(params.capabilities);
483
+ });
484
+ manager.onNotification("slates/hub.live_invocation.set", async ({ params }) => {
485
+ liveInvocation.set({
486
+ token: params.token,
487
+ baseUrl: params.baseUrl
488
+ });
489
+ });
421
490
  manager.onRequest("slates/config.changed", async ({ params }) => {
422
491
  getContextBasic();
423
492
  let newConfig = validate(
@@ -882,19 +951,39 @@ var createProviderHandler = (slate, listeners) => createSlatesProviderProtoHandl
882
951
  },
883
952
  () => runWithContext(context, () => action.handleInvocation(context))
884
953
  );
954
+ let contextAttachments = await context._finalizeAttachments();
955
+ let merged = mergeAttachments(
956
+ [...res.attachments ?? [], ...contextAttachments],
957
+ res.output
958
+ );
959
+ let redacted = redactUrlAttachmentSecrets(
960
+ merged,
961
+ context._getAuthConfigForRedaction()
962
+ );
963
+ let finalAttachments = await routeAttachmentsThroughDirectUpload(
964
+ redacted,
965
+ getLiveInvocation()
966
+ );
885
967
  return withRequestTraces(context, {
886
968
  output: res.output,
887
969
  message: res.message,
888
- attachments: mergeAttachments(res.attachments, res.output)
970
+ attachments: finalAttachments
889
971
  });
890
972
  };
891
973
  if (action.isPublic) {
892
974
  getContextBasic();
893
- return invoke(new SlatePublicContext(input, slate.spec, logger));
975
+ return invoke(new SlatePublicContext(input, slate.spec, logger, getLiveInvocation()));
894
976
  }
895
977
  let ctx = getContextFull();
896
978
  return invoke(
897
- new SlateContext(ctx.config, input, ctx.auth?.output, slate.spec, logger)
979
+ new SlateContext(
980
+ ctx.config,
981
+ input,
982
+ ctx.auth?.output,
983
+ slate.spec,
984
+ logger,
985
+ getLiveInvocation()
986
+ )
898
987
  );
899
988
  });
900
989
  manager.onRequest("slates/action.trigger.map_event", async ({ params }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slates/provider-handler",
3
- "version": "1.0.0-rc.22",
3
+ "version": "1.0.0-rc.26",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -32,8 +32,9 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@lowerdeck/error": "^1.1.0",
35
- "@slates/proto": "1.0.0-rc.15",
36
- "@slates/provider": "1.0.0-rc.18",
35
+ "@slates/proto": "1.0.0-rc.16",
36
+ "@slates/provider": "1.0.0-rc.20",
37
+ "p-queue": "^6.6.2",
37
38
  "zod": "^4.2.1"
38
39
  },
39
40
  "devDependencies": {
@@ -0,0 +1,171 @@
1
+ import { SLATES_PROTOCOL_VERSION, SlatesProviderProtoHandlerManager } from '@slates/proto';
2
+ import {
3
+ Slate,
4
+ SlateAuth,
5
+ SlateConfig,
6
+ SlatePublicTool,
7
+ SlateSpecification
8
+ } from '@slates/provider';
9
+ import { afterEach, describe, expect, it, vi } from 'vitest';
10
+ import { z } from 'zod';
11
+ import { createProviderHandler } from './index';
12
+
13
+ let createManager = async () => {
14
+ let spec = SlateSpecification.create({
15
+ key: 'attachments-test',
16
+ name: 'Attachments Test',
17
+ config: SlateConfig.create(z.object({})),
18
+ auth: SlateAuth.create().output(z.object({}))
19
+ });
20
+ let tool = SlatePublicTool.create(spec, {
21
+ key: 'attach',
22
+ name: 'Attach'
23
+ })
24
+ .input(z.object({}))
25
+ .output(z.object({}))
26
+ .handleInvocation(async ctx => {
27
+ await ctx.addAttachment({
28
+ type: 'content',
29
+ content: new Response('hello', {
30
+ headers: { 'content-type': 'text/plain' }
31
+ }),
32
+ filename: 'hello.txt'
33
+ });
34
+
35
+ return { output: {}, message: 'ok' };
36
+ })
37
+ .build();
38
+ let slate = Slate.create({ spec, triggers: [], tools: [tool] });
39
+ let manager = await createProviderHandler(slate, []).run();
40
+
41
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
42
+ jsonrpc: '2.0',
43
+ method: 'slates/hello',
44
+ params: { protocol: SLATES_PROTOCOL_VERSION }
45
+ });
46
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
47
+ jsonrpc: '2.0',
48
+ method: 'slates/participant.set',
49
+ params: {
50
+ participants: [
51
+ { type: 'consumer', id: 'consumer', name: 'Consumer' },
52
+ { type: 'hub', id: 'hub', name: 'Hub' }
53
+ ]
54
+ }
55
+ });
56
+
57
+ return manager;
58
+ };
59
+
60
+ let invoke = async (manager: SlatesProviderProtoHandlerManager) =>
61
+ SlatesProviderProtoHandlerManager.handleInput(manager, {
62
+ jsonrpc: '2.0',
63
+ id: 'request',
64
+ method: 'slates/action.tool.invoke',
65
+ params: { actionId: 'attach', input: {} }
66
+ });
67
+
68
+ let expectInlineAttachment = (response: Awaited<ReturnType<typeof invoke>>) => {
69
+ expect(response).toMatchObject({
70
+ result: {
71
+ attachments: [
72
+ {
73
+ mimeType: 'text/plain',
74
+ content: {
75
+ type: 'content',
76
+ encoding: 'base64',
77
+ content: Buffer.from('hello').toString('base64')
78
+ }
79
+ }
80
+ ]
81
+ }
82
+ });
83
+ };
84
+
85
+ describe('addAttachment fallback', () => {
86
+ afterEach(() => {
87
+ vi.restoreAllMocks();
88
+ });
89
+
90
+ it('returns a standard inline attachment when capabilities and a live token are absent', async () => {
91
+ let fetchSpy = vi.spyOn(globalThis, 'fetch');
92
+ let response = await invoke(await createManager());
93
+
94
+ expectInlineAttachment(response);
95
+ expect(fetchSpy).not.toHaveBeenCalled();
96
+ });
97
+
98
+ it('returns a standard inline attachment when direct upload is enabled without a live token', async () => {
99
+ let manager = await createManager();
100
+ let fetchSpy = vi.spyOn(globalThis, 'fetch');
101
+
102
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
103
+ jsonrpc: '2.0',
104
+ method: 'slates/hub.capabilities.set',
105
+ params: { capabilities: { attachments: { directUpload: { enabled: true } } } }
106
+ });
107
+
108
+ expectInlineAttachment(await invoke(manager));
109
+ expect(fetchSpy).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it('returns a standard inline attachment when a live token exists without the capability', async () => {
113
+ let manager = await createManager();
114
+ let fetchSpy = vi.spyOn(globalThis, 'fetch');
115
+
116
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
117
+ jsonrpc: '2.0',
118
+ method: 'slates/hub.live_invocation.set',
119
+ params: { token: 'token', baseUrl: 'https://hub.example' }
120
+ });
121
+
122
+ expectInlineAttachment(await invoke(manager));
123
+ expect(fetchSpy).not.toHaveBeenCalled();
124
+ });
125
+
126
+ it('uses direct upload only when both the capability and live token exist', async () => {
127
+ let manager = await createManager();
128
+ let fetchSpy = vi
129
+ .spyOn(globalThis, 'fetch')
130
+ .mockResolvedValueOnce(
131
+ new Response(
132
+ JSON.stringify({
133
+ attachments: [
134
+ {
135
+ referenceId: 'attachment-reference',
136
+ uploadUrl: 'https://uploads.example/attachment'
137
+ }
138
+ ]
139
+ }),
140
+ { status: 200, headers: { 'content-type': 'application/json' } }
141
+ )
142
+ )
143
+ .mockResolvedValueOnce(new Response(null, { status: 200 }));
144
+
145
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
146
+ jsonrpc: '2.0',
147
+ method: 'slates/hub.capabilities.set',
148
+ params: { capabilities: { attachments: { directUpload: { enabled: true } } } }
149
+ });
150
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
151
+ jsonrpc: '2.0',
152
+ method: 'slates/hub.live_invocation.set',
153
+ params: { token: 'token', baseUrl: 'https://hub.example' }
154
+ });
155
+
156
+ expect(await invoke(manager)).toMatchObject({
157
+ result: {
158
+ attachments: [
159
+ {
160
+ mimeType: 'text/plain',
161
+ content: {
162
+ type: 'upload_reference',
163
+ referenceId: 'attachment-reference'
164
+ }
165
+ }
166
+ ]
167
+ }
168
+ });
169
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
170
+ });
171
+ });
package/src/index.ts CHANGED
@@ -5,14 +5,18 @@ import {
5
5
  type SlatesParticipant
6
6
  } from '@slates/proto';
7
7
  import {
8
+ redactUrlAttachmentSecrets,
8
9
  runWithContext,
9
10
  type Slate,
10
11
  type SlateAttachment,
11
12
  SlateContext,
13
+ type SlateLiveInvocationInfo,
12
14
  SlateLogger,
13
15
  type SlateLogListener,
14
- SlatePublicContext
16
+ SlatePublicContext,
17
+ uploadAttachmentDirect
15
18
  } from '@slates/provider';
19
+ import { PQueue } from './pQueue';
16
20
  import {
17
21
  getAction,
18
22
  getActionWithType,
@@ -26,6 +30,57 @@ import { State } from './state';
26
30
  import { toJsonSchema, validate } from './validation';
27
31
  import { serializeWebhookHttpResponse } from './webhook';
28
32
 
33
+ let DEFAULT_MAX_ATTACHMENT_SIZE_BYTES = 100_000_000;
34
+
35
+ let routeAttachmentsThroughDirectUpload = async (
36
+ attachments: SlateAttachment[] | undefined,
37
+ live: SlateLiveInvocationInfo | null
38
+ ): Promise<SlateAttachment[] | undefined> => {
39
+ if (!attachments || attachments.length === 0 || !live) return attachments;
40
+
41
+ let contentEntries = attachments
42
+ .map((attachment, index) => ({ attachment, index }))
43
+ .filter(entry => entry.attachment.content.type === 'content');
44
+ if (contentEntries.length === 0) return attachments;
45
+
46
+ let queue = new PQueue({ concurrency: 10 });
47
+ let replacements = new Map<number, SlateAttachment | null>();
48
+
49
+ await Promise.all(
50
+ contentEntries.map(entry =>
51
+ queue.add(async () => {
52
+ try {
53
+ let body =
54
+ entry.attachment.content.type === 'content'
55
+ ? entry.attachment.content.encoding === 'base64'
56
+ ? Buffer.from(entry.attachment.content.content, 'base64')
57
+ : Buffer.from(entry.attachment.content.content, 'utf-8')
58
+ : Buffer.alloc(0);
59
+
60
+ replacements.set(
61
+ entry.index,
62
+ await uploadAttachmentDirect({
63
+ live,
64
+ mimeType: entry.attachment.mimeType,
65
+ body
66
+ })
67
+ );
68
+ } catch {
69
+ // Upload failed or was interrupted -- we simply don't have this attachment, not a
70
+ // failed tool call.
71
+ replacements.set(entry.index, null);
72
+ }
73
+ })
74
+ )
75
+ );
76
+
77
+ return attachments
78
+ .map((attachment, index) =>
79
+ replacements.has(index) ? replacements.get(index) : attachment
80
+ )
81
+ .filter((a): a is SlateAttachment => a != null);
82
+ };
83
+
29
84
  let isRecord = (value: unknown): value is Record<string, unknown> =>
30
85
  typeof value === 'object' && value !== null && !Array.isArray(value);
31
86
 
@@ -128,6 +183,26 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
128
183
  let config = new State<{ value: ConfigType } | null>(null);
129
184
  let session = new State<{ id: string; state: any } | null>(null);
130
185
 
186
+ let hubCapabilities = new State<{
187
+ attachments?: { directUpload?: { enabled: boolean; maxAttachmentSizeBytes?: number } };
188
+ } | null>(null);
189
+ let liveInvocation = new State<Pick<SlateLiveInvocationInfo, 'token' | 'baseUrl'> | null>(
190
+ null
191
+ );
192
+
193
+ let getLiveInvocation = (): SlateLiveInvocationInfo | null => {
194
+ let directUpload = hubCapabilities.get()?.attachments?.directUpload;
195
+ let live = liveInvocation.get();
196
+
197
+ if (!directUpload?.enabled || !live) return null;
198
+
199
+ return {
200
+ ...live,
201
+ maxAttachmentSizeBytes:
202
+ directUpload.maxAttachmentSizeBytes ?? DEFAULT_MAX_ATTACHMENT_SIZE_BYTES
203
+ };
204
+ };
205
+
131
206
  let logger = new SlateLogger(listeners);
132
207
  let providerTrace = {
133
208
  providerId: slate.spec.key,
@@ -301,6 +376,17 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
301
376
  });
302
377
  });
303
378
 
379
+ manager.onNotification('slates/hub.capabilities.set', async ({ params }) => {
380
+ hubCapabilities.set(params.capabilities);
381
+ });
382
+
383
+ manager.onNotification('slates/hub.live_invocation.set', async ({ params }) => {
384
+ liveInvocation.set({
385
+ token: params.token,
386
+ baseUrl: params.baseUrl
387
+ });
388
+ });
389
+
304
390
  manager.onRequest('slates/config.changed', async ({ params }) => {
305
391
  getContextBasic();
306
392
 
@@ -828,21 +914,43 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
828
914
  () => runWithContext(context, () => action.handleInvocation(context as any))
829
915
  );
830
916
 
917
+ let contextAttachments = await context._finalizeAttachments();
918
+ let merged = mergeAttachments(
919
+ [...(res.attachments ?? []), ...contextAttachments],
920
+ res.output
921
+ );
922
+
923
+ let redacted = redactUrlAttachmentSecrets(
924
+ merged,
925
+ context._getAuthConfigForRedaction()
926
+ );
927
+ let finalAttachments = await routeAttachmentsThroughDirectUpload(
928
+ redacted,
929
+ getLiveInvocation()
930
+ );
931
+
831
932
  return withRequestTraces(context, {
832
933
  output: res.output,
833
934
  message: res.message,
834
- attachments: mergeAttachments(res.attachments, res.output)
935
+ attachments: finalAttachments
835
936
  });
836
937
  };
837
938
 
838
939
  if (action.isPublic) {
839
940
  getContextBasic();
840
- return invoke(new SlatePublicContext(input, slate.spec, logger));
941
+ return invoke(new SlatePublicContext(input, slate.spec, logger, getLiveInvocation()));
841
942
  }
842
943
 
843
944
  let ctx = getContextFull();
844
945
  return invoke(
845
- new SlateContext(ctx.config, input, ctx.auth?.output!, slate.spec, logger)
946
+ new SlateContext(
947
+ ctx.config,
948
+ input,
949
+ ctx.auth?.output!,
950
+ slate.spec,
951
+ logger,
952
+ getLiveInvocation()
953
+ )
846
954
  );
847
955
  });
848
956
 
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveDefaultExport } from './pQueue';
3
+
4
+ class Example {
5
+ value: number;
6
+ constructor(value: number) {
7
+ this.value = value;
8
+ }
9
+ }
10
+
11
+ describe('resolveDefaultExport', () => {
12
+ it('unwraps nested CJS default exports from bundlers', () => {
13
+ expect(resolveDefaultExport<typeof Example>({ default: { default: Example } })).toBe(
14
+ Example
15
+ );
16
+ expect(new (resolveDefaultExport<typeof Example>({ default: Example }))(3).value).toBe(3);
17
+ });
18
+ });
package/src/pQueue.ts ADDED
@@ -0,0 +1,18 @@
1
+ import PQueueImport from 'p-queue';
2
+
3
+ export let resolveDefaultExport = <T>(value: unknown): T => {
4
+ let current = value;
5
+
6
+ for (let i = 0; i < 4; i++) {
7
+ if (typeof current === 'function') return current as T;
8
+ if (current && typeof current === 'object' && 'default' in current) {
9
+ current = (current as { default: unknown }).default;
10
+ continue;
11
+ }
12
+ break;
13
+ }
14
+
15
+ throw new TypeError('Module export is not a constructor');
16
+ };
17
+
18
+ export let PQueue = resolveDefaultExport<typeof PQueueImport>(PQueueImport);
package/src/spec.ts CHANGED
@@ -41,8 +41,7 @@ export let mapAuthMethod = <ConfigType extends {}, AuthType extends {}>(
41
41
  ? m.scopes.map(s => ({
42
42
  id: s.scope,
43
43
  title: s.title,
44
- description: s.description,
45
- defaultChecked: s.defaultChecked
44
+ description: s.description
46
45
  }))
47
46
  : undefined,
48
47