@uipath/packager-tool-workflowcompiler-browser 1.202.0-preview.140 → 1.202.0-preview.142

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.
@@ -1,783 +0,0 @@
1
- import {
2
- type ConnectionInfo,
3
- type IFileSystem,
4
- type IProjectCleanupOptions,
5
- type IProjectPackOptions,
6
- type IProjectToolFactory,
7
- type IProjectValidateOptions,
8
- type IToolLogger,
9
- PackJobTargetKind,
10
- type ProjectOperationContext,
11
- ToolErrorCodes,
12
- ToolResult,
13
- toRelativeUrl,
14
- translate,
15
- } from "@uipath/solutionpackager-tool-core";
16
- import { toProjectCleanupInfo } from "./cleanup-result.js";
17
- import { PackResultProcessor } from "./pack-result-processor.js";
18
- import {
19
- CompletedStatus,
20
- type PackAgentResult,
21
- PackagerHubConnection,
22
- } from "./packager-hub-connection.js";
23
-
24
- type ResolveCallback = (result: ToolResult) => void;
25
-
26
- export enum BundleOperation {
27
- Pack = "Pack",
28
- Validate = "Validate",
29
- Cleanup = "Cleanup",
30
- }
31
-
32
- interface PackEntry {
33
- operation: BundleOperation.Pack;
34
- options: IProjectPackOptions;
35
- resolve: ResolveCallback;
36
- }
37
-
38
- interface ValidateEntry {
39
- operation: BundleOperation.Validate;
40
- options: IProjectValidateOptions;
41
- resolve: ResolveCallback;
42
- }
43
-
44
- interface CleanupEntry {
45
- operation: BundleOperation.Cleanup;
46
- options: IProjectCleanupOptions;
47
- resolve: ResolveCallback;
48
- }
49
-
50
- type ProjectEntry = PackEntry | ValidateEntry | CleanupEntry;
51
-
52
- interface BundleSession {
53
- operation: BundleOperation;
54
- expectedCount: number;
55
- projects: Map<string, ProjectEntry>;
56
- timeout?: ReturnType<typeof setTimeout>;
57
- }
58
-
59
- export interface IProjectBundleExecutor {
60
- packAsync(
61
- options: IProjectPackOptions,
62
- context: ProjectOperationContext,
63
- ): Promise<ToolResult>;
64
-
65
- validateAsync(
66
- options: IProjectValidateOptions,
67
- context: ProjectOperationContext,
68
- ): Promise<ToolResult>;
69
-
70
- cleanupAsync(
71
- options: IProjectCleanupOptions,
72
- context: ProjectOperationContext,
73
- ): Promise<ToolResult>;
74
- }
75
-
76
- /**
77
- * Coordinates pack or validate of multiple projects within a single operation context.
78
- * Waits until all compatible projects have submitted their options,
79
- * then executes a single bundled remote-agent operation and resolves all pending promises.
80
- */
81
- /**
82
- * Shape of the `POST /odata/Attachments` response. Orchestrator returns
83
- * PascalCase (`Id`, `BlobFileAccess.Uri`); the camelCase fallbacks mirror the
84
- * defensive reads in `@uipath/orchestrator-sdk`'s `uploadJobAttachment`.
85
- */
86
- interface AttachmentResponse {
87
- Id?: string;
88
- id?: string;
89
- BlobFileAccess?: { Uri: string };
90
- blobFileAccess?: { Uri: string };
91
- }
92
-
93
- export class ProjectBundleExecutor implements IProjectBundleExecutor {
94
- static readonly SESSION_TIMEOUT_MS = 1_800_000;
95
-
96
- /**
97
- * Maximum length (in characters) of the inline `inputArguments` payload
98
- * accepted by the serverless jobs endpoint. Beyond this the arguments must
99
- * be uploaded as a file attachment and referenced via `inputFile`.
100
- */
101
- static readonly MAX_INLINE_INPUT_ARGUMENTS_LENGTH = 10_000;
102
-
103
- private readonly sessions = new Map<string, BundleSession>();
104
- private readonly pendingSessionCreations = new Map<
105
- string,
106
- Promise<BundleSession>
107
- >();
108
-
109
- constructor(
110
- private readonly toolFactory: IProjectToolFactory,
111
- private readonly fileSystem: IFileSystem,
112
- ) {}
113
-
114
- async packAsync(
115
- options: IProjectPackOptions,
116
- context: ProjectOperationContext,
117
- ): Promise<ToolResult> {
118
- return this.enqueueAsync(context, {
119
- operation: BundleOperation.Pack,
120
- options,
121
- resolve: () => {},
122
- });
123
- }
124
-
125
- async validateAsync(
126
- options: IProjectValidateOptions,
127
- context: ProjectOperationContext,
128
- ): Promise<ToolResult> {
129
- return this.enqueueAsync(context, {
130
- operation: BundleOperation.Validate,
131
- options,
132
- resolve: () => {},
133
- });
134
- }
135
-
136
- async cleanupAsync(
137
- options: IProjectCleanupOptions,
138
- context: ProjectOperationContext,
139
- ): Promise<ToolResult> {
140
- if (
141
- context.downloadUrl &&
142
- !options.dryRun &&
143
- !context.solutionId &&
144
- !context.projectId
145
- ) {
146
- return ToolResult.error(
147
- ToolErrorCodes.InternalError,
148
- "Cleanup save-back requires a solutionId or projectId for downloaded workspaces.",
149
- );
150
- }
151
-
152
- return this.enqueueAsync(context, {
153
- operation: BundleOperation.Cleanup,
154
- options,
155
- resolve: () => {},
156
- });
157
- }
158
-
159
- private async enqueueAsync(
160
- context: ProjectOperationContext,
161
- entry: ProjectEntry,
162
- ): Promise<ToolResult> {
163
- const projectPath = entry.options.projectPath;
164
- context.logger?.info(
165
- `[BundleExecutor] ${entry.operation}Async called for project: ${projectPath}`,
166
- );
167
- const session = await this.getSessionAsync(context, entry.operation);
168
-
169
- if (session.operation !== entry.operation) {
170
- return ToolResult.error(
171
- "BundleOperationMismatch",
172
- translate.t(
173
- "toolWorkflowCompilerBrowser.errors.bundleOperationMismatch",
174
- {
175
- sessionOperation: session.operation,
176
- entryOperation: entry.operation,
177
- },
178
- ),
179
- );
180
- }
181
-
182
- const promise = new Promise<ToolResult>((resolve) => {
183
- entry.resolve = resolve;
184
- });
185
- session.projects.set(projectPath, entry);
186
- const allReceived = session.projects.size === session.expectedCount;
187
- context.logger?.info(
188
- `[BundleExecutor] session (${session.operation}): ${session.projects.size}/${session.expectedCount} projects received. allReceived=${allReceived}`,
189
- );
190
-
191
- if (allReceived) {
192
- clearTimeout(session.timeout);
193
- await this.runAgentAsync(session, context);
194
- this.sessions.delete(context.id);
195
- }
196
-
197
- return promise;
198
- }
199
-
200
- private getSessionAsync(
201
- context: ProjectOperationContext,
202
- operation: BundleOperation,
203
- ): Promise<BundleSession> {
204
- const existing = this.sessions.get(context.id);
205
- if (existing) {
206
- return Promise.resolve(existing);
207
- }
208
-
209
- let pending = this.pendingSessionCreations.get(context.id);
210
- if (!pending) {
211
- pending = this.createSessionAsync(context, operation);
212
- this.pendingSessionCreations.set(context.id, pending);
213
- }
214
- return pending;
215
- }
216
-
217
- private async createSessionAsync(
218
- context: ProjectOperationContext,
219
- operation: BundleOperation,
220
- ): Promise<BundleSession> {
221
- const expectedCount = this.countCompatibleProjects(context);
222
- const session: BundleSession = {
223
- operation,
224
- expectedCount,
225
- projects: new Map(),
226
- };
227
- session.timeout = setTimeout(() => {
228
- if (session.projects.size < session.expectedCount) {
229
- context.logger?.error(
230
- `[BundleExecutor] Session timed out after ${ProjectBundleExecutor.SESSION_TIMEOUT_MS}ms: ` +
231
- `received ${session.projects.size}/${session.expectedCount} projects`,
232
- );
233
- this.resolveAll(
234
- session,
235
- ToolResult.error(
236
- "SessionTimeoutError",
237
- translate.t(
238
- "toolWorkflowCompilerBrowser.errors.sessionTimeout",
239
- {
240
- received: session.projects.size,
241
- expected: session.expectedCount,
242
- },
243
- ),
244
- ),
245
- );
246
- this.sessions.delete(context.id);
247
- }
248
- }, ProjectBundleExecutor.SESSION_TIMEOUT_MS);
249
- this.sessions.set(context.id, session);
250
- this.pendingSessionCreations.delete(context.id);
251
- return session;
252
- }
253
-
254
- private async runAgentAsync(
255
- session: BundleSession,
256
- context: ProjectOperationContext,
257
- ): Promise<void> {
258
- const projectPaths = [...session.projects.keys()];
259
- context.logger?.info(
260
- translate.t(
261
- "toolWorkflowCompilerBrowser.info.startingRemoteAgent",
262
- {
263
- operation: session.operation,
264
- count: session.expectedCount,
265
- paths: projectPaths.join(", "),
266
- },
267
- ),
268
- );
269
-
270
- const connection = context.connection;
271
- context.logger?.info(
272
- `[BundleExecutor] connection info: cloudUrl=${connection?.cloudUrl ?? "MISSING"}, hasToken=${!!connection?.accessToken}, folderId=${connection?.folderId ?? "MISSING"}`,
273
- );
274
- if (
275
- !connection?.cloudUrl ||
276
- !connection.accessToken ||
277
- !connection.folderId
278
- ) {
279
- this.resolveAll(
280
- session,
281
- ToolResult.error(
282
- "ConnectionError",
283
- translate.t(
284
- "toolWorkflowCompilerBrowser.errors.missingConnectionInfo",
285
- ),
286
- ),
287
- );
288
- return;
289
- }
290
-
291
- const sessionId = crypto.randomUUID();
292
- const hubConnection = await this.connectToHubAsync(
293
- connection,
294
- sessionId,
295
- context.logger,
296
- );
297
- if (!hubConnection) {
298
- this.resolveAll(
299
- session,
300
- ToolResult.error(
301
- "SignalRConnectionError",
302
- translate.t(
303
- "toolWorkflowCompilerBrowser.errors.signalRConnectionFailed",
304
- ),
305
- ),
306
- );
307
- return;
308
- }
309
-
310
- try {
311
- context.logger?.info(`[BundleExecutor] Sending BeginSession...`);
312
- await this.sendBeginSessionAsync(
313
- session,
314
- context,
315
- connection,
316
- sessionId,
317
- );
318
- context.logger?.info(
319
- `[BundleExecutor] BeginSession succeeded. Waiting for completion...`,
320
- );
321
- const result = await hubConnection.waitForCompletionAsync();
322
- context.logger?.info(
323
- `[BundleExecutor] Got result: errorCode=${result.errorCode}, status=${result.status}, message=${result.message}, bucketId=${result.bucketId}`,
324
- );
325
- await this.handleAgentResultAsync(result, session, context);
326
- } catch (error) {
327
- const message =
328
- error instanceof Error ? error.message : String(error);
329
- context.logger?.error(
330
- `[BundleExecutor] runAgentAsync caught error: ${message}`,
331
- );
332
- this.resolveAll(
333
- session,
334
- ToolResult.error(`${session.operation}AgentError`, message),
335
- );
336
- } finally {
337
- await hubConnection.disconnectAsync();
338
- }
339
- }
340
-
341
- private async connectToHubAsync(
342
- connection: ConnectionInfo,
343
- sessionId: string,
344
- logger?: IToolLogger,
345
- ): Promise<PackagerHubConnection | null> {
346
- const relativeBase = toRelativeUrl(connection.cloudUrl!);
347
- const hubUrl = `${relativeBase}/orchestrator_/signalr/robotdebug?sessionId=${sessionId}`;
348
- logger?.info(
349
- translate.t("toolWorkflowCompilerBrowser.info.connectingToHub", {
350
- url: hubUrl,
351
- }),
352
- );
353
-
354
- const hubConnection = new PackagerHubConnection(logger);
355
- try {
356
- await hubConnection.connectAsync(hubUrl, connection.accessToken!);
357
- return hubConnection;
358
- } catch (error) {
359
- const message =
360
- error instanceof Error ? error.message : String(error);
361
- logger?.error(
362
- translate.t(
363
- "toolWorkflowCompilerBrowser.errors.hubConnectionFailed",
364
- { url: hubUrl, message },
365
- ),
366
- );
367
- return null;
368
- }
369
- }
370
-
371
- private async handleAgentResultAsync(
372
- result: PackAgentResult,
373
- session: BundleSession,
374
- context: ProjectOperationContext,
375
- ): Promise<void> {
376
- if (
377
- result.errorCode !== 0 ||
378
- result.status !== CompletedStatus.Succeeded
379
- ) {
380
- this.resolveAll(
381
- session,
382
- ToolResult.error(
383
- `${session.operation}AgentError`,
384
- result.message ??
385
- translate.t(
386
- "toolWorkflowCompilerBrowser.errors.agentFailed",
387
- {
388
- operation: session.operation,
389
- status: result.status,
390
- errorCode: result.errorCode,
391
- },
392
- ),
393
- ),
394
- );
395
- return;
396
- }
397
-
398
- if (session.operation === BundleOperation.Pack) {
399
- await this.handlePackBucketAsync(result, session, context);
400
- return;
401
- }
402
-
403
- if (session.operation === BundleOperation.Cleanup) {
404
- this.resolveCleanup(result, session, context);
405
- return;
406
- }
407
-
408
- this.resolveAll(session, ToolResult.success());
409
- }
410
-
411
- /**
412
- * The agent removed unused items in-place and (for cloud solutions) saved the
413
- * solution back to StudioWeb; there is no bucket to download. Resolve each
414
- * project with its own result carrying the changed files (the full map, which
415
- * solution-cleanup-service unions) and that project's cleanup payload mapped
416
- * to the generic {@link toProjectCleanupInfo} shape.
417
- */
418
- private resolveCleanup(
419
- result: PackAgentResult,
420
- session: BundleSession,
421
- context: ProjectOperationContext,
422
- ): void {
423
- for (const [projectPath, entry] of session.projects) {
424
- const details: Record<string, unknown> = {};
425
- if (result.changedFilesByProject) {
426
- details.changedFilesByProject = result.changedFilesByProject;
427
- }
428
-
429
- const designId = context.projects.find(
430
- (p) => p.ProjectPath === projectPath,
431
- )?.Id;
432
- const raw = designId
433
- ? result.unusedDependenciesByProject?.[designId]
434
- : undefined;
435
- if (raw) {
436
- details.projectCleanup = toProjectCleanupInfo(raw);
437
- }
438
-
439
- const projectResult = ToolResult.success();
440
- if (Object.keys(details).length > 0) {
441
- projectResult.details = details;
442
- }
443
- entry.resolve(projectResult);
444
- }
445
- }
446
-
447
- private async handlePackBucketAsync(
448
- result: PackAgentResult,
449
- session: BundleSession,
450
- context: ProjectOperationContext,
451
- ): Promise<void> {
452
- if (result.bucketId == null) {
453
- this.resolveAll(
454
- session,
455
- ToolResult.error(
456
- "PackAgentError",
457
- translate.t(
458
- "toolWorkflowCompilerBrowser.errors.packSucceededNoBucket",
459
- ),
460
- ),
461
- );
462
- return;
463
- }
464
-
465
- const packEntries = new Map<string, { options: IProjectPackOptions }>();
466
- for (const [projectPath, entry] of session.projects) {
467
- if (entry.operation === BundleOperation.Pack) {
468
- packEntries.set(projectPath, { options: entry.options });
469
- }
470
- }
471
-
472
- const resultProcessor = new PackResultProcessor(
473
- this.fileSystem,
474
- context.logger,
475
- );
476
- const perProjectResults = await resultProcessor.processAsync(
477
- result.bucketId,
478
- packEntries,
479
- context,
480
- );
481
- for (const [projectPath, entry] of session.projects) {
482
- const projectResult = perProjectResults.get(projectPath);
483
- entry.resolve(
484
- projectResult ??
485
- ToolResult.error(
486
- "PackResultError",
487
- translate.t(
488
- "toolWorkflowCompilerBrowser.errors.noResultForProject",
489
- ),
490
- ),
491
- );
492
- }
493
- }
494
-
495
- private async sendBeginSessionAsync(
496
- session: BundleSession,
497
- context: ProjectOperationContext,
498
- connection: ConnectionInfo,
499
- sessionId: string,
500
- ): Promise<void> {
501
- const relativeBase = toRelativeUrl(connection.cloudUrl!);
502
- const onLocalRobot =
503
- context.jobTarget?.kind === PackJobTargetKind.LocalRobot;
504
- // Two different job-creation APIs, not one endpoint with a flag: the serverless
505
- // endpoint forces RuntimeType=Serverless and takes no machine targeting, while
506
- // BeginSession takes the runtime/machine fields but no jobType. The agent behaves
507
- // the same either way — it dispatches on Command in the input arguments.
508
- const url = onLocalRobot
509
- ? `${relativeBase}/orchestrator_/api/RobotDebug/BeginSession`
510
- : `${relativeBase}/orchestrator_/api/serverless/jobs`;
511
-
512
- context.logger?.info(
513
- `[BundleExecutor] BeginSession URL: ${url} (target=${context.jobTarget?.kind ?? PackJobTargetKind.Serverless})`,
514
- );
515
-
516
- const firstEntry = session.projects.values().next().value!;
517
- const opts = firstEntry.options;
518
- const skipAnalyze = "skipAnalyze" in opts ? opts.skipAnalyze : false;
519
- const skipValidate = "skipValidate" in opts ? opts.skipValidate : false;
520
-
521
- const rpaProjects = [...session.projects.entries()].map(
522
- ([projectPath, entry]) => {
523
- const project = context.projects.find(
524
- (p) => p.ProjectPath === projectPath,
525
- );
526
- return {
527
- RelativePath: project?.ProjectRelativePath ?? "",
528
- DesignId: project?.Id ?? "",
529
- ProjectStorageId:
530
- entry.operation === BundleOperation.Pack
531
- ? (entry.options.projectStorageId ?? "")
532
- : "",
533
- PackageId:
534
- entry.operation === BundleOperation.Pack
535
- ? entry.options.package.id
536
- : "",
537
- };
538
- },
539
- );
540
-
541
- const executorInfo: Record<string, unknown> = {
542
- Command: session.operation,
543
- SupportedProtocolVersions: ["2.0"],
544
- DownloadUrl: context.downloadUrl ?? "",
545
- SolutionId: context.solutionId ?? "",
546
- ProjectId: context.projectId ?? "",
547
- SessionId: sessionId,
548
- ProfileKey: "StudioWeb",
549
- LicenseType: "Development",
550
- LogLevel: opts.logLevel ?? "Warning",
551
- Culture: "en-US",
552
- DownloadTimeoutInSeconds: 300,
553
- SkipAnalyze: String(skipAnalyze),
554
- SkipValidate: String(skipValidate),
555
- TargetFramework: context.targetFramework,
556
- RpaProjects: rpaProjects,
557
- WorkflowCompilerVersion: context.workflowCompilerVersion ?? "",
558
- };
559
-
560
- if (firstEntry.operation === BundleOperation.Pack) {
561
- executorInfo.Configuration = context.configuration;
562
- const pkg = firstEntry.options.package;
563
- executorInfo.Package = {
564
- Name: pkg.id,
565
- Version: pkg.version,
566
- Author: pkg.author ?? "",
567
- Description: pkg.description ?? "",
568
- ReleaseNotes: pkg.releaseNotes ?? "",
569
- Tags: pkg.tags ?? "",
570
- IconUrl: pkg.iconUrl ?? "",
571
- ProjectUrl: pkg.projectUrl ?? "",
572
- RepositoryType: pkg.repositoryType ?? "",
573
- RepositoryUrl: pkg.repositoryUrl ?? "",
574
- RepositoryBranch: pkg.repositoryBranch ?? "",
575
- RepositoryCommit: pkg.repositoryCommit ?? "",
576
- };
577
- // Flat fields retained for backward compatibility with agents that
578
- // predate the nested Package structure.
579
- executorInfo.Name = pkg.id;
580
- executorInfo.Version = pkg.version;
581
- executorInfo.Author = pkg.author ?? "";
582
- executorInfo.Description = pkg.description ?? "";
583
- executorInfo.ReleaseNotes = pkg.releaseNotes ?? "";
584
-
585
- // Lets the agent pack the sources already on its disk instead of
586
- // downloading the snapshot. Only sent when the job is pinned to
587
- // specific machine sessions of a local robot: on any other machine
588
- // the path does not exist and the agent fails setup rather than
589
- // falling back to the download, so an unpinned job must not carry it.
590
- if (context.localSourcePath) {
591
- if (
592
- onLocalRobot &&
593
- context.jobTarget?.machineSessionIds?.length
594
- ) {
595
- executorInfo.LocalSourcePath = context.localSourcePath;
596
- } else {
597
- context.logger?.warn(
598
- "[BundleExecutor] Dropping localSourcePath: the pack job is not pinned to a local robot machine session.",
599
- );
600
- }
601
- }
602
- }
603
-
604
- if (firstEntry.operation === BundleOperation.Cleanup) {
605
- executorInfo.DryRun = firstEntry.options.dryRun;
606
- executorInfo.SkipImports = firstEntry.options.skipImports;
607
- // Forward the StudioWeb editing session's solution lock key so the agent's
608
- // save-back overwrite is accepted while the solution is locked by that session.
609
- if (context.lockKey) {
610
- executorInfo.LockKey = context.lockKey;
611
- }
612
- }
613
-
614
- const inputArguments = JSON.stringify(executorInfo);
615
-
616
- const body: Record<string, unknown> = onLocalRobot
617
- ? {
618
- source: "StudioWeb",
619
- targetFramework: context.targetFramework,
620
- // Development runtime + runAsMe is what confines the job to this
621
- // user's own machines; without both, Orchestrator is free to
622
- // schedule it anywhere. Mirrors StudioWeb debug's payload.
623
- runtimeType: "Development",
624
- runAsMe: true,
625
- jobsCount: 1,
626
- strategy: "ModernJobsCount",
627
- ...(context.jobTarget?.machineSessionIds?.length
628
- ? {
629
- machineSessionIds:
630
- context.jobTarget.machineSessionIds,
631
- }
632
- : {}),
633
- }
634
- : {
635
- source: "StudioWeb",
636
- targetFramework: context.targetFramework,
637
- jobType: "PublishStudioProject",
638
- };
639
-
640
- // Both job endpoints cap inline inputArguments at
641
- // MAX_INLINE_INPUT_ARGUMENTS_LENGTH characters. For larger payloads we
642
- // upload the arguments as a file attachment and reference it via
643
- // inputFile instead (the two are mutually exclusive on the server).
644
- if (
645
- inputArguments.length >
646
- ProjectBundleExecutor.MAX_INLINE_INPUT_ARGUMENTS_LENGTH
647
- ) {
648
- context.logger?.info(
649
- translate.t(
650
- "toolWorkflowCompilerBrowser.info.offloadingInputArguments",
651
- { length: inputArguments.length },
652
- ),
653
- );
654
- body.inputFile = await this.uploadInputArgumentsFileAsync(
655
- inputArguments,
656
- connection,
657
- context.logger,
658
- );
659
- } else {
660
- body.inputArguments = inputArguments;
661
- }
662
-
663
- const headers: Record<string, string> = {
664
- "Content-Type": "application/json",
665
- Authorization: `Bearer ${connection.accessToken}`,
666
- "X-UIPATH-OrganizationUnitId": connection.folderId ?? "",
667
- };
668
-
669
- if (connection.tenantId) {
670
- headers["x-uipath-tenantid"] = connection.tenantId;
671
- }
672
-
673
- const response = await fetch(url, {
674
- method: "POST",
675
- headers,
676
- body: JSON.stringify(body),
677
- });
678
-
679
- if (!response.ok) {
680
- const errorText = await response.text();
681
- throw new Error(
682
- translate.t(
683
- "toolWorkflowCompilerBrowser.errors.beginSessionFailed",
684
- { status: response.status, errorText },
685
- ),
686
- );
687
- }
688
- }
689
-
690
- /**
691
- * Uploads the input arguments JSON as a file attachment and returns the
692
- * attachment id, to be passed as `inputFile` on the serverless job request.
693
- * Mirrors the two-step flow used by the Orchestrator UI: create the
694
- * attachment (which returns a signed blob write URI) then PUT the contents
695
- * to that URI using the storage-provided headers.
696
- */
697
- private async uploadInputArgumentsFileAsync(
698
- inputArguments: string,
699
- connection: ConnectionInfo,
700
- logger?: IToolLogger,
701
- ): Promise<string> {
702
- const baseUrl = toRelativeUrl(connection.cloudUrl!);
703
- const attachmentsUrl = `${baseUrl}/orchestrator_/odata/Attachments`;
704
-
705
- const headers: Record<string, string> = {
706
- "Content-Type": "application/json",
707
- Authorization: `Bearer ${connection.accessToken}`,
708
- "X-UIPATH-OrganizationUnitId": connection.folderId ?? "",
709
- };
710
- if (connection.tenantId) {
711
- headers["x-uipath-tenantid"] = connection.tenantId;
712
- }
713
-
714
- const createResponse = await fetch(attachmentsUrl, {
715
- method: "POST",
716
- headers,
717
- body: JSON.stringify({ name: "input.json" }),
718
- });
719
- if (!createResponse.ok) {
720
- const errorText = await createResponse.text();
721
- throw new Error(
722
- translate.t(
723
- "toolWorkflowCompilerBrowser.errors.createAttachmentFailed",
724
- { status: createResponse.status, errorText },
725
- ),
726
- );
727
- }
728
-
729
- const attachment = (await createResponse.json()) as AttachmentResponse;
730
- const attachmentId = attachment.Id ?? attachment.id ?? "";
731
- const writeUri =
732
- attachment.BlobFileAccess?.Uri ?? attachment.blobFileAccess?.Uri;
733
- if (!attachmentId || !writeUri) {
734
- throw new Error(
735
- translate.t(
736
- "toolWorkflowCompilerBrowser.errors.attachmentResponseMalformed",
737
- ),
738
- );
739
- }
740
-
741
- // PUT the raw bytes to the SAS blob URI. The Azure blob endpoint
742
- // requires the BlockBlob type header; matches the proven contract in
743
- // `@uipath/orchestrator-sdk`'s uploadJobAttachment.
744
- const uploadResponse = await fetch(writeUri, {
745
- method: "PUT",
746
- headers: {
747
- "Content-Type": "application/octet-stream",
748
- "x-ms-blob-type": "BlockBlob",
749
- },
750
- body: inputArguments,
751
- });
752
- if (!uploadResponse.ok) {
753
- throw new Error(
754
- translate.t(
755
- "toolWorkflowCompilerBrowser.errors.uploadInputArgumentsFailed",
756
- {
757
- status: uploadResponse.status,
758
- statusText: uploadResponse.statusText,
759
- },
760
- ),
761
- );
762
- }
763
-
764
- logger?.info(
765
- `[BundleExecutor] Uploaded input arguments as attachment ${attachmentId}`,
766
- );
767
- return attachmentId;
768
- }
769
-
770
- private resolveAll(session: BundleSession, result: ToolResult): void {
771
- for (const [, entry] of session.projects) {
772
- entry.resolve(result);
773
- }
774
- }
775
-
776
- private countCompatibleProjects(context: ProjectOperationContext): number {
777
- return context.projects.filter(
778
- (p) =>
779
- p.ProjectPath != null &&
780
- this.toolFactory.supportedTypes.includes(p.Type),
781
- ).length;
782
- }
783
- }