@huydao/karrot 0.1.7 → 0.1.8

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.
@@ -6,6 +6,7 @@ export type RunAgUiPostMessageOptions = {
6
6
  processTimeoutMs?: number;
7
7
  injectMessage?: boolean;
8
8
  injectRunMetadata?: boolean;
9
+ textEvents?: AgUiPostTextEventConfig[];
9
10
  run?: {
10
11
  url: string;
11
12
  headers?: Record<string, string>;
@@ -52,4 +53,13 @@ export type RunAgUiPostMessageOptions = {
52
53
  timeoutMs?: number;
53
54
  };
54
55
  };
56
+ export type AgUiPostTextEventConfig = {
57
+ type: string;
58
+ name?: string;
59
+ role?: string;
60
+ textPath?: string;
61
+ contentPath?: string;
62
+ deltaPath?: string;
63
+ mode?: 'content' | 'delta';
64
+ };
55
65
  export declare function runAgUiPostMessage(options: RunAgUiPostMessageOptions): Promise<MessageRunResult>;
@@ -56,6 +56,13 @@ function getStringAtPath(payload, pathExpression) {
56
56
  const rawValue = getValueByPath(payload, pathExpression);
57
57
  return typeof rawValue === 'string' ? rawValue.trim() : '';
58
58
  }
59
+ function getRawStringAtPath(payload, pathExpression) {
60
+ if (!pathExpression) {
61
+ return '';
62
+ }
63
+ const rawValue = getValueByPath(payload, pathExpression);
64
+ return typeof rawValue === 'string' ? rawValue : '';
65
+ }
59
66
  function getArrayAtPath(payload, pathExpression) {
60
67
  const rawValue = getValueByPath(payload, pathExpression);
61
68
  return Array.isArray(rawValue) ? rawValue : [];
@@ -278,15 +285,80 @@ function roundSeconds(startTimeMs, endTimeMs) {
278
285
  }
279
286
  return Number(((endTimeMs - startTimeMs) / 1000).toFixed(1));
280
287
  }
281
- function isAssistantTextEvent(event) {
288
+ function isStandardAssistantTextEvent(event) {
282
289
  return (event.type === 'TEXT_MESSAGE_CONTENT' ||
283
- event.type === 'TEXT_MESSAGE_CHUNK' ||
284
- (event.type === 'CUSTOM' && event.name === 'super-testing-agent.model_stream_chunk'));
290
+ event.type === 'TEXT_MESSAGE_CHUNK');
285
291
  }
286
292
  function isToolStartEvent(event) {
287
293
  return (event.type === 'TOOL_CALL_START' ||
288
294
  (event.type === 'CUSTOM' && event.name === 'super-testing-agent.tool_started'));
289
295
  }
296
+ function matchesConfiguredTextEvent(event, config) {
297
+ if (event.type !== config.type) {
298
+ return false;
299
+ }
300
+ if (config.name != null && event.name !== config.name) {
301
+ return false;
302
+ }
303
+ if (config.role != null && event.role !== config.role) {
304
+ return false;
305
+ }
306
+ return true;
307
+ }
308
+ function getConfiguredTextEventValue(event, textEvents) {
309
+ for (const config of textEvents ?? []) {
310
+ if (!matchesConfiguredTextEvent(event, config)) {
311
+ continue;
312
+ }
313
+ const content = getRawStringAtPath(event, config.contentPath);
314
+ if (content.trim()) {
315
+ return { content: content.trim() };
316
+ }
317
+ const delta = getRawStringAtPath(event, config.deltaPath);
318
+ if (delta) {
319
+ return { delta };
320
+ }
321
+ const text = getRawStringAtPath(event, config.textPath);
322
+ if (!text) {
323
+ return {};
324
+ }
325
+ return config.mode === 'content' ? { content: text.trim() } : { delta: text };
326
+ }
327
+ return {};
328
+ }
329
+ function isAssistantTextEvent(event, textEvents) {
330
+ return (isStandardAssistantTextEvent(event) ||
331
+ Boolean((textEvents ?? []).find((config) => matchesConfiguredTextEvent(event, config))));
332
+ }
333
+ function getAssistantContent(event, textEvents) {
334
+ const configured = getConfiguredTextEventValue(event, textEvents);
335
+ if (configured.content) {
336
+ return configured.content;
337
+ }
338
+ if (typeof event.content === 'string' && event.content.trim()) {
339
+ return event.content.trim();
340
+ }
341
+ if (typeof event.value?.content === 'string' && event.value.content.trim()) {
342
+ return event.value.content.trim();
343
+ }
344
+ if (typeof event.value?.output === 'string' && event.value.output.trim()) {
345
+ return event.value.output.trim();
346
+ }
347
+ return undefined;
348
+ }
349
+ function getAssistantDelta(event, textEvents) {
350
+ const configured = getConfiguredTextEventValue(event, textEvents);
351
+ if (configured.delta) {
352
+ return configured.delta;
353
+ }
354
+ if (typeof event.delta === 'string' && event.delta) {
355
+ return event.delta;
356
+ }
357
+ if (typeof event.value?.delta === 'string' && event.value.delta) {
358
+ return event.value.delta;
359
+ }
360
+ return undefined;
361
+ }
290
362
  function createConnectCollector(options) {
291
363
  const assistantFragments = [];
292
364
  const toolCalls = [];
@@ -335,13 +407,15 @@ function createConnectCollector(options) {
335
407
  });
336
408
  }
337
409
  const isAssistantMessage = parsed.role === 'assistant' || typeof parsed.role !== 'string';
338
- if (isAssistantTextEvent(parsed) && isAssistantMessage) {
410
+ if (isAssistantTextEvent(parsed, options.textEvents) && isAssistantMessage) {
339
411
  firstTextAt ??= eventTime(parsed);
340
- if (typeof parsed.content === 'string' && parsed.content.trim()) {
341
- latestAssistantContent = parsed.content.trim();
412
+ const content = getAssistantContent(parsed, options.textEvents);
413
+ const delta = getAssistantDelta(parsed, options.textEvents);
414
+ if (content) {
415
+ latestAssistantContent = content;
342
416
  }
343
- else if (typeof parsed.delta === 'string' && parsed.delta) {
344
- assistantFragments.push(parsed.delta);
417
+ else if (delta) {
418
+ assistantFragments.push(delta);
345
419
  }
346
420
  }
347
421
  if (isToolStartEvent(parsed)) {
@@ -469,11 +543,12 @@ function startConnectStream(options) {
469
543
  const collector = createConnectCollector({
470
544
  targetRunId: options.targetRunId,
471
545
  fallbackThreadId: options.fallbackThreadId,
546
+ textEvents: options.textEvents,
472
547
  });
473
548
  if (!response.body) {
474
549
  const rawContent = await response.text();
475
550
  await promises_1.default.writeFile(options.outputPath, rawContent, 'utf8');
476
- const parsed = extractResultFromSse(rawContent, options.fallbackThreadId);
551
+ const parsed = extractResultFromSse(rawContent, options.fallbackThreadId, {}, options.textEvents);
477
552
  return {
478
553
  output: parsed.output,
479
554
  toolCalls: parsed.toolCalls,
@@ -546,7 +621,7 @@ function startConnectStream(options) {
546
621
  result,
547
622
  };
548
623
  }
549
- function extractResultFromSse(rawContent, fallbackThreadId, fallbackMetrics = {}) {
624
+ function extractResultFromSse(rawContent, fallbackThreadId, fallbackMetrics = {}, textEvents) {
550
625
  const fragments = [];
551
626
  let latestContent;
552
627
  let resolvedThreadId = fallbackThreadId;
@@ -573,13 +648,15 @@ function extractResultFromSse(rawContent, fallbackThreadId, fallbackMetrics = {}
573
648
  if (parsed.type === 'RUN_STARTED' && typeof runStartedAt !== 'number') {
574
649
  runStartedAt = normalizeEventTimestamp(parsed.timestamp);
575
650
  }
576
- if (isAssistantTextEvent(parsed)) {
651
+ if (isAssistantTextEvent(parsed, textEvents)) {
577
652
  firstTextAt ??= normalizeEventTimestamp(parsed.timestamp);
578
- if (typeof parsed.content === 'string' && parsed.content.trim()) {
579
- latestContent = parsed.content.trim();
653
+ const content = getAssistantContent(parsed, textEvents);
654
+ const delta = getAssistantDelta(parsed, textEvents);
655
+ if (content) {
656
+ latestContent = content;
580
657
  }
581
- else if (typeof parsed.delta === 'string' && parsed.delta) {
582
- fragments.push(parsed.delta);
658
+ else if (delta) {
659
+ fragments.push(delta);
583
660
  }
584
661
  }
585
662
  if (isToolStartEvent(parsed)) {
@@ -663,6 +740,7 @@ async function runAgUiPostMessage(options) {
663
740
  targetRunId: runId,
664
741
  fallbackThreadId: resolvedThreadId,
665
742
  processTimeoutMs: options.connect.processTimeoutMs ?? options.processTimeoutMs,
743
+ textEvents: options.textEvents,
666
744
  });
667
745
  await connectStream.ready;
668
746
  const runResponse = await postAndCaptureResponse({
@@ -717,7 +795,7 @@ async function runAgUiPostMessage(options) {
717
795
  const parsed = extractResultFromSse(runResponse.rawContent, resolvedThreadId, {
718
796
  startTimeMs: runResponse.startedAtMs,
719
797
  finishedTimeMs: runResponse.finishedAtMs,
720
- });
798
+ }, options.textEvents);
721
799
  if (options.observe) {
722
800
  await promises_1.default.writeFile(observePath, '', 'utf8');
723
801
  const observed = await waitForObservedCompletion({
@@ -48,6 +48,7 @@ function createAgUiPostRunner(config) {
48
48
  processTimeoutMs: processTimeoutMs ?? transport.processTimeoutMs,
49
49
  injectMessage: transport.injectMessage,
50
50
  injectRunMetadata: transport.injectRunMetadata,
51
+ textEvents: transport.textEvents,
51
52
  run: transport.run ?? transport.request,
52
53
  connect: transport.connect,
53
54
  observe: transport.observe,
@@ -22,6 +22,15 @@ export type KarrotConfig = {
22
22
  type: 'ag-ui-post';
23
23
  injectMessage?: boolean;
24
24
  injectRunMetadata?: boolean;
25
+ textEvents?: Array<{
26
+ type: string;
27
+ name?: string;
28
+ role?: string;
29
+ textPath?: string;
30
+ contentPath?: string;
31
+ deltaPath?: string;
32
+ mode?: 'content' | 'delta';
33
+ }>;
25
34
  run?: {
26
35
  url: string;
27
36
  headers?: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huydao/karrot",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Reusable AI scenario execution, assertion, evaluation, and reporting toolkit",
5
5
  "license": "ISC",
6
6
  "type": "commonjs",