The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 77f5465edca6Commit detailsView latest

connectors/botpress/src/finalization.ts 98 lines · 3.9 KB · typescript
1
export type StructuredFinalization = Record<string, any>;
2
3
const boundedStrings = (value: unknown, limit: number, maxLength: number) =>
4
  (Array.isArray(value) ? value : [])
5
    .filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
6
    .slice(0, limit)
7
    .map((item) => item.trim().slice(0, maxLength));
8
9
export function extractFinalizationJson(value: string): unknown {
10
  const marker = value.indexOf('OPENCLASP_FINALIZATION');
11
  const source = marker >= 0 ? value.slice(marker + 'OPENCLASP_FINALIZATION'.length) : value;
12
  const start = source.indexOf('{');
13
  if (start < 0) throw new Error('Finalization JSON object is missing');
14
  let depth = 0;
15
  let quoted = false;
16
  let escaped = false;
17
  for (let index = start; index < source.length; index += 1) {
18
    const character = source[index]!;
19
    if (quoted) {
20
      if (escaped) escaped = false;
21
      else if (character === '\\') escaped = true;
22
      else if (character === '"') quoted = false;
23
      continue;
24
    }
25
    if (character === '"') quoted = true;
26
    else if (character === '{') depth += 1;
27
    else if (character === '}' && --depth === 0) return JSON.parse(source.slice(start, index + 1));
28
  }
29
  throw new Error('Finalization JSON object is incomplete');
30
}
31
32
export function parseFinalizationAssessment(
33
  text: string,
34
  interactionId: string,
35
): StructuredFinalization {
36
  const value = extractFinalizationJson(text) as StructuredFinalization;
37
  const outcomes = ['success', 'partial', 'failure', 'cancelled'];
38
  const workAgain = ['yes', 'no', 'unsure'];
39
  const statuses = ['met', 'partially_met', 'missed', 'unknown'];
40
  const dimensions = [
41
    'overall_satisfaction',
42
    'outcome_satisfaction',
43
    'communication',
44
    'timeliness',
45
    'scope_adherence',
46
    'evidence_quality',
47
    'correction_handling',
48
    'reliability',
49
  ];
50
  if (!outcomes.includes(value.outcome) || typeof value.summary !== 'string')
51
    throw new Error('Finalization outcome or summary is invalid');
52
  if (!workAgain.includes(value.wouldWorkAgain))
53
    throw new Error('Finalization wouldWorkAgain is invalid');
54
  if (typeof value.confidence !== 'number' || value.confidence < 0 || value.confidence > 1)
55
    throw new Error('Finalization confidence is invalid');
56
  if (!value.ratings || typeof value.ratings !== 'object')
57
    throw new Error('Finalization ratings are missing');
58
  const ratings = Object.fromEntries(
59
    dimensions.map((dimension) => {
60
      const rating = value.ratings[dimension];
61
      if (typeof rating !== 'number' || rating < 0 || rating > 1)
62
        throw new Error(`Finalization rating ${dimension} is invalid`);
63
      return [dimension, rating];
64
    }),
65
  );
66
  const criteria = (Array.isArray(value.criteria) ? value.criteria : [])
67
    .slice(0, 100)
68
    .map((item) => {
69
      if (!item || typeof item !== 'object' || typeof item.criterion !== 'string')
70
        throw new Error('Finalization criterion is invalid');
71
      return {
72
        criterion: item.criterion.slice(0, 1000),
73
        status: statuses.includes(item.status) ? item.status : 'unknown',
74
        ...(typeof item.explanation === 'string'
75
          ? { explanation: item.explanation.slice(0, 1000) }
76
          : {}),
77
        evidenceReferences: boundedStrings(item.evidenceReferences, 50, 2048),
78
      };
79
    });
80
  return {
81
    interactionId,
82
    outcome: value.outcome,
83
    summary: value.summary.slice(0, 2000),
84
    criteria,
85
    deliverables: boundedStrings(value.deliverables, 100, 1000),
86
    actionsTaken: boundedStrings(value.actionsTaken, 100, 1000),
87
    blockers: boundedStrings(value.blockers, 100, 1000),
88
    corrections: boundedStrings(value.corrections, 100, 1000),
89
    evidenceReferences: boundedStrings(value.evidenceReferences, 100, 2048),
90
    confidence: value.confidence,
91
    ratings,
92
    wouldWorkAgain: value.wouldWorkAgain,
93
    reasonCodes: boundedStrings(value.reasonCodes, 32, 128),
94
    ...(typeof value.privateComment === 'string'
95
      ? { privateComment: value.privateComment.slice(0, 1000) }
96
      : {}),
97
  };
98
}