@hexabot-ai/api 3.2.2-alpha.16 → 3.2.2-alpha.18

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.
@@ -5,8 +5,16 @@
5
5
  */
6
6
 
7
7
  import { validateWorkflow } from '@hexabot-ai/agentic';
8
- import { Action } from '@hexabot-ai/types';
9
- import { Injectable, NotFoundException } from '@nestjs/common';
8
+ import {
9
+ Action,
10
+ type WorkflowVersion,
11
+ type WorkflowVersionFull,
12
+ } from '@hexabot-ai/types';
13
+ import {
14
+ BadRequestException,
15
+ Injectable,
16
+ NotFoundException,
17
+ } from '@nestjs/common';
10
18
  import { Tool, ToolGuards } from '@rekog/mcp-nest';
11
19
  import { z } from 'zod';
12
20
 
@@ -67,11 +75,14 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
67
75
  request?: HexabotMcpRequest,
68
76
  ) {
69
77
  const actorId = this.getActorId(request);
70
-
71
- return await this.workflowHelper.commitWorkflowDefinition({
78
+ const version = await this.workflowHelper.commitWorkflowDefinition({
72
79
  ...args,
73
80
  createdBy: actorId,
74
81
  });
82
+
83
+ return this.workflowHelper.summarizeWorkflowVersion(version, {
84
+ includeDefinitionYmlByteLength: true,
85
+ });
75
86
  }
76
87
 
77
88
  @McpPermission('workflow', Action.READ)
@@ -108,7 +119,8 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
108
119
  @ToolGuards([McpPermissionGuard])
109
120
  @Tool({
110
121
  name: 'hexabot_workflow_version_search',
111
- description: 'List workflow definition YAML versions for a workflow.',
122
+ description:
123
+ 'List compact workflow definition version metadata for a workflow, excluding YAML bodies.',
112
124
  parameters: z.object({
113
125
  workflowId: uuidSchema,
114
126
  ...paginationSchema,
@@ -118,18 +130,28 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
118
130
  })
119
131
  async searchWorkflowVersions(args: { workflowId: string } & PaginationArgs) {
120
132
  const where = { workflow: { id: args.workflowId } } as any;
121
-
122
- return await this.listWithCount(
133
+ const result = await this.listWithCount(
123
134
  this.workflowVersionService,
124
135
  this.findOptions<WorkflowVersionOrmEntity>(args, where),
125
136
  );
137
+
138
+ return {
139
+ ...result,
140
+ items: result.items.map((version) =>
141
+ this.workflowHelper.summarizeWorkflowVersion(
142
+ version as WorkflowVersion,
143
+ { includeDefinitionYmlByteLength: true },
144
+ ),
145
+ ),
146
+ };
126
147
  }
127
148
 
128
149
  @McpPermission('workflowversion', Action.READ)
129
150
  @ToolGuards([McpPermissionGuard])
130
151
  @Tool({
131
152
  name: 'hexabot_workflow_version_get',
132
- description: 'Read one workflow definition YAML version.',
153
+ description:
154
+ 'Read compact workflow definition version metadata, excluding the YAML body.',
133
155
  parameters: z.object({
134
156
  id: uuidSchema,
135
157
  workflowId: uuidSchema.optional(),
@@ -147,7 +169,57 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
147
169
  throw new NotFoundException(`Workflow version ${args.id} not found`);
148
170
  }
149
171
 
150
- return version;
172
+ return this.workflowHelper.summarizeWorkflowVersion(version, {
173
+ includeDefinitionYmlByteLength: true,
174
+ });
175
+ }
176
+
177
+ @McpPermission('workflowversion', Action.READ)
178
+ @ToolGuards([McpPermissionGuard])
179
+ @Tool({
180
+ name: 'hexabot_workflow_yaml_get',
181
+ description:
182
+ 'Read workflow definition YAML by workflowId/current version or versionId, with checksum, byte length, and UTF-8 byte offset/limit chunking.',
183
+ parameters: z.object({
184
+ workflowId: uuidSchema.optional(),
185
+ versionId: uuidSchema.optional(),
186
+ offset: z.number().int().min(0).default(0),
187
+ limit: z.number().int().min(1).max(64000).default(16000),
188
+ }),
189
+ })
190
+ async getWorkflowYaml(args: {
191
+ workflowId?: string;
192
+ versionId?: string;
193
+ offset?: number;
194
+ limit?: number;
195
+ }) {
196
+ const version = await this.requireWorkflowVersionForYaml(args);
197
+ const definitionYml = version.definitionYml;
198
+ const offset = args.offset ?? 0;
199
+ const limit = args.limit ?? 16000;
200
+ const { chunk, chunkByteLength, endOffset, totalByteLength } =
201
+ this.sliceUtf8Chunk(definitionYml, offset, limit);
202
+ const workflowId = this.resolveRelationId(version.workflow);
203
+
204
+ return {
205
+ workflowId,
206
+ versionId: version.id,
207
+ version: version.version,
208
+ checksum: version.checksum,
209
+ definitionYmlByteLength: totalByteLength,
210
+ definitionYmlLength: definitionYml.length,
211
+ chunk: {
212
+ offset,
213
+ limit,
214
+ unit: 'utf8Bytes',
215
+ endOffset,
216
+ length: chunk.length,
217
+ byteLength: chunkByteLength,
218
+ hasMore: endOffset < totalByteLength,
219
+ nextOffset: endOffset < totalByteLength ? endOffset : null,
220
+ },
221
+ definitionYml: chunk,
222
+ };
151
223
  }
152
224
 
153
225
  @McpPermission('workflowversion', Action.UPDATE)
@@ -162,9 +234,13 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
162
234
  }),
163
235
  })
164
236
  async updateWorkflowVersion(args: { id: string; message?: string | null }) {
165
- return await this.workflowVersionService.updateOne(args.id, {
237
+ const version = await this.workflowVersionService.updateOne(args.id, {
166
238
  message: args.message ?? undefined,
167
239
  });
240
+
241
+ return this.workflowHelper.summarizeWorkflowVersion(version, {
242
+ includeDefinitionYmlByteLength: true,
243
+ });
168
244
  }
169
245
 
170
246
  @McpPermission('workflowversion', Action.CREATE)
@@ -184,10 +260,14 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
184
260
  _context: unknown,
185
261
  request?: HexabotMcpRequest,
186
262
  ) {
187
- return await this.workflowHelper.restoreWorkflowVersionSnapshot(
263
+ const version = await this.workflowHelper.restoreWorkflowVersionSnapshot(
188
264
  args,
189
265
  this.getActorId(request),
190
266
  );
267
+
268
+ return this.workflowHelper.summarizeWorkflowVersion(version, {
269
+ includeDefinitionYmlByteLength: true,
270
+ });
191
271
  }
192
272
 
193
273
  @McpPermission('workflowversion', Action.CREATE)
@@ -207,10 +287,14 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
207
287
  _context: unknown,
208
288
  request?: HexabotMcpRequest,
209
289
  ) {
210
- return await this.workflowHelper.restoreWorkflowVersionSnapshot(
290
+ const version = await this.workflowHelper.restoreWorkflowVersionSnapshot(
211
291
  args,
212
292
  this.getActorId(request),
213
293
  );
294
+
295
+ return this.workflowHelper.summarizeWorkflowVersion(version, {
296
+ includeDefinitionYmlByteLength: true,
297
+ });
214
298
  }
215
299
 
216
300
  private getWorkflowValidationActions() {
@@ -223,4 +307,153 @@ export class HexabotWorkflowVersionMcpTools extends HexabotMcpToolBase {
223
307
  ),
224
308
  );
225
309
  }
310
+
311
+ private async requireWorkflowVersionForYaml(args: {
312
+ workflowId?: string;
313
+ versionId?: string;
314
+ }): Promise<WorkflowVersion | WorkflowVersionFull> {
315
+ if (!args.workflowId && !args.versionId) {
316
+ throw new BadRequestException(
317
+ 'workflowId or versionId is required to read workflow YAML',
318
+ );
319
+ }
320
+
321
+ if (args.versionId) {
322
+ const version = await this.workflowVersionService.findOneAndPopulate({
323
+ where: {
324
+ id: args.versionId,
325
+ ...(args.workflowId ? { workflow: { id: args.workflowId } } : {}),
326
+ } as any,
327
+ });
328
+
329
+ if (!version) {
330
+ throw new NotFoundException(
331
+ `Workflow version ${args.versionId} not found`,
332
+ );
333
+ }
334
+
335
+ return version;
336
+ }
337
+
338
+ const workflowId = args.workflowId;
339
+ if (!workflowId) {
340
+ throw new BadRequestException(
341
+ 'workflowId is required when versionId is not provided',
342
+ );
343
+ }
344
+ const workflow = await this.workflowHelper.requireWorkflow(workflowId);
345
+ const currentVersionId = this.resolveRelationId(workflow.currentVersion);
346
+ if (!currentVersionId) {
347
+ throw new NotFoundException(
348
+ `Workflow ${workflowId} has no current version`,
349
+ );
350
+ }
351
+
352
+ const version = await this.workflowVersionService.findOneAndPopulate({
353
+ where: {
354
+ id: currentVersionId,
355
+ workflow: { id: workflowId },
356
+ } as any,
357
+ });
358
+
359
+ if (!version) {
360
+ throw new NotFoundException(
361
+ `Workflow version ${currentVersionId} not found`,
362
+ );
363
+ }
364
+
365
+ return version;
366
+ }
367
+
368
+ private sliceUtf8Chunk(
369
+ value: string,
370
+ offset: number,
371
+ limit: number,
372
+ ): {
373
+ chunk: string;
374
+ chunkByteLength: number;
375
+ endOffset: number;
376
+ totalByteLength: number;
377
+ } {
378
+ const totalByteLength = Buffer.byteLength(value, 'utf8');
379
+ if (offset > totalByteLength) {
380
+ throw new BadRequestException(
381
+ `Offset ${offset} exceeds workflow YAML byte length ${totalByteLength}`,
382
+ );
383
+ }
384
+
385
+ const startIndex = this.findUtf8IndexForOffset(value, offset);
386
+ let endIndex = value.length;
387
+ let byteOffset = offset;
388
+
389
+ for (let index = startIndex; index < value.length; ) {
390
+ const char = this.readCodePoint(value, index);
391
+ const nextByteOffset = byteOffset + Buffer.byteLength(char, 'utf8');
392
+
393
+ if (nextByteOffset > offset + limit) {
394
+ if (index === startIndex) {
395
+ throw new BadRequestException(
396
+ `Limit ${limit} is too small to include the next UTF-8 character at offset ${offset}`,
397
+ );
398
+ }
399
+ endIndex = index;
400
+ break;
401
+ }
402
+
403
+ byteOffset = nextByteOffset;
404
+ index += char.length;
405
+ }
406
+
407
+ const chunk = value.slice(startIndex, endIndex);
408
+ const chunkByteLength = Buffer.byteLength(chunk, 'utf8');
409
+
410
+ return {
411
+ chunk,
412
+ chunkByteLength,
413
+ endOffset: offset + chunkByteLength,
414
+ totalByteLength,
415
+ };
416
+ }
417
+
418
+ private findUtf8IndexForOffset(value: string, offset: number): number {
419
+ if (offset === 0) {
420
+ return 0;
421
+ }
422
+
423
+ let byteOffset = 0;
424
+ for (let index = 0; index < value.length; ) {
425
+ const char = this.readCodePoint(value, index);
426
+ const nextByteOffset = byteOffset + Buffer.byteLength(char, 'utf8');
427
+
428
+ if (offset === nextByteOffset) {
429
+ return index + char.length;
430
+ }
431
+
432
+ if (offset < nextByteOffset) {
433
+ throw new BadRequestException(
434
+ `Offset ${offset} is not aligned to a UTF-8 character boundary`,
435
+ );
436
+ }
437
+
438
+ byteOffset = nextByteOffset;
439
+ index += char.length;
440
+ }
441
+
442
+ if (offset === byteOffset) {
443
+ return value.length;
444
+ }
445
+
446
+ throw new BadRequestException(
447
+ `Offset ${offset} exceeds workflow YAML byte length ${byteOffset}`,
448
+ );
449
+ }
450
+
451
+ private readCodePoint(value: string, index: number): string {
452
+ const codePoint = value.codePointAt(index);
453
+ if (codePoint === undefined) {
454
+ return '';
455
+ }
456
+
457
+ return String.fromCodePoint(codePoint);
458
+ }
226
459
  }