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

Viewing files at commit 4bfaf3cca6a5Commit detailsView latest

packages/sidecar/src/index.ts 56 lines · 1.9 KB · typescript
1
import type { AgentExtension } from '@a2a-js/sdk';
2
import {
3
  DEFAULT_EXTENSION_URI,
4
  type InteractionEvent,
5
  type TrustEnvelope,
6
} from '../../protocol/src/index.js';
7
import { TrustEngine, type PolicyContext } from '../../core/src/index.js';
8
9
export type A2AMessageLike = {
10
  role: 'ROLE_USER' | 'ROLE_AGENT' | 'user' | 'agent';
11
  parts: unknown[];
12
  extensions?: string[];
13
  metadata?: Record<string, unknown>;
14
};
15
16
export function openClaspA2AExtension(uri = DEFAULT_EXTENSION_URI): AgentExtension {
17
  return {
18
    uri,
19
    description: 'OpenClasp trust, contract, and behavioural-assurance envelope',
20
    required: false,
21
    params: {},
22
  };
23
}
24
25
export class OpenClaspSidecar {
26
  constructor(
27
    readonly engine: TrustEngine,
28
    readonly extensionUri = DEFAULT_EXTENSION_URI,
29
  ) {}
30
31
  async forward<T>(input: {
32
    message: A2AMessageLike;
33
    envelope: TrustEnvelope;
34
    policy: Omit<PolicyContext, 'envelope'>;
35
    structuredEvents?: InteractionEvent[];
36
    send: (message: A2AMessageLike) => Promise<T>;
37
  }): Promise<{
38
    forwarded: boolean;
39
    decision: ReturnType<TrustEngine['assess']>;
40
    response?: T;
41
    networkPayloads: Record<string, unknown>[];
42
  }> {
43
    const embedded = input.message.metadata?.[this.extensionUri];
44
    if (!embedded) throw new Error('Missing OpenClasp A2A extension metadata');
45
    const decision = this.engine.assess({ envelope: input.envelope, ...input.policy });
46
    if (decision.decision === 'DENY') return { forwarded: false, decision, networkPayloads: [] };
47
    const networkPayloads = (input.structuredEvents ?? [])
48
      .map((event) => {
49
        this.engine.recordEvent(event);
50
        return this.engine.networkContribution(event);
51
      })
52
      .filter((value): value is Record<string, unknown> => value !== null);
53
    const response = await input.send(input.message);
54
    return { forwarded: true, decision, response, networkPayloads };
55
  }
56
}