@ophirai/sdk 0.1.0

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.
Files changed (60) hide show
  1. package/README.md +139 -0
  2. package/dist/__tests__/buyer.test.d.ts +1 -0
  3. package/dist/__tests__/buyer.test.js +664 -0
  4. package/dist/__tests__/discovery.test.d.ts +1 -0
  5. package/dist/__tests__/discovery.test.js +188 -0
  6. package/dist/__tests__/escrow.test.d.ts +1 -0
  7. package/dist/__tests__/escrow.test.js +385 -0
  8. package/dist/__tests__/identity.test.d.ts +1 -0
  9. package/dist/__tests__/identity.test.js +222 -0
  10. package/dist/__tests__/integration.test.d.ts +1 -0
  11. package/dist/__tests__/integration.test.js +681 -0
  12. package/dist/__tests__/lockstep.test.d.ts +1 -0
  13. package/dist/__tests__/lockstep.test.js +320 -0
  14. package/dist/__tests__/messages.test.d.ts +1 -0
  15. package/dist/__tests__/messages.test.js +976 -0
  16. package/dist/__tests__/negotiation.test.d.ts +1 -0
  17. package/dist/__tests__/negotiation.test.js +667 -0
  18. package/dist/__tests__/seller.test.d.ts +1 -0
  19. package/dist/__tests__/seller.test.js +767 -0
  20. package/dist/__tests__/server.test.d.ts +1 -0
  21. package/dist/__tests__/server.test.js +239 -0
  22. package/dist/__tests__/signing.test.d.ts +1 -0
  23. package/dist/__tests__/signing.test.js +713 -0
  24. package/dist/__tests__/sla.test.d.ts +1 -0
  25. package/dist/__tests__/sla.test.js +342 -0
  26. package/dist/__tests__/transport.test.d.ts +1 -0
  27. package/dist/__tests__/transport.test.js +197 -0
  28. package/dist/__tests__/x402.test.d.ts +1 -0
  29. package/dist/__tests__/x402.test.js +141 -0
  30. package/dist/buyer.d.ts +190 -0
  31. package/dist/buyer.js +555 -0
  32. package/dist/discovery.d.ts +47 -0
  33. package/dist/discovery.js +51 -0
  34. package/dist/escrow.d.ts +177 -0
  35. package/dist/escrow.js +434 -0
  36. package/dist/identity.d.ts +60 -0
  37. package/dist/identity.js +108 -0
  38. package/dist/index.d.ts +122 -0
  39. package/dist/index.js +43 -0
  40. package/dist/lockstep.d.ts +94 -0
  41. package/dist/lockstep.js +127 -0
  42. package/dist/messages.d.ts +172 -0
  43. package/dist/messages.js +262 -0
  44. package/dist/negotiation.d.ts +113 -0
  45. package/dist/negotiation.js +214 -0
  46. package/dist/seller.d.ts +127 -0
  47. package/dist/seller.js +395 -0
  48. package/dist/server.d.ts +52 -0
  49. package/dist/server.js +149 -0
  50. package/dist/signing.d.ts +98 -0
  51. package/dist/signing.js +165 -0
  52. package/dist/sla.d.ts +95 -0
  53. package/dist/sla.js +187 -0
  54. package/dist/transport.d.ts +41 -0
  55. package/dist/transport.js +127 -0
  56. package/dist/types.d.ts +86 -0
  57. package/dist/types.js +1 -0
  58. package/dist/x402.d.ts +25 -0
  59. package/dist/x402.js +54 -0
  60. package/package.json +40 -0
@@ -0,0 +1,108 @@
1
+ import nacl from 'tweetnacl';
2
+ import bs58 from 'bs58';
3
+ import { OphirError, OphirErrorCode } from '@ophirai/protocol';
4
+ /** Multicodec prefix for Ed25519 public key (varint-encoded 0xed) */
5
+ const ED25519_MULTICODEC_PREFIX = new Uint8Array([0xed, 0x01]);
6
+ const DID_KEY_PREFIX = 'did:key:z';
7
+ /**
8
+ * Generate an Ed25519 keypair for agent identity.
9
+ *
10
+ * @throws {OphirError} if the generated keypair has unexpected key lengths.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const { publicKey, secretKey } = generateKeyPair();
15
+ * // publicKey: Uint8Array(32), secretKey: Uint8Array(64)
16
+ * ```
17
+ */
18
+ export function generateKeyPair() {
19
+ const kp = nacl.sign.keyPair();
20
+ if (kp.publicKey.length !== 32 || kp.secretKey.length !== 64) {
21
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Key generation produced invalid lengths: publicKey=${kp.publicKey.length}, secretKey=${kp.secretKey.length}`);
22
+ }
23
+ return { publicKey: kp.publicKey, secretKey: kp.secretKey };
24
+ }
25
+ /**
26
+ * Convert Ed25519 public key to did:key:z6Mk... format.
27
+ * Prepends multicodec prefix (0xed01) then base58-btc encodes with 'z' prefix.
28
+ *
29
+ * @throws {OphirError} if publicKey is not exactly 32 bytes.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const did = publicKeyToDid(keypair.publicKey);
34
+ * // 'did:key:z6Mk...'
35
+ * ```
36
+ */
37
+ export function publicKeyToDid(publicKey) {
38
+ if (publicKey.length !== 32) {
39
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid public key length: expected 32, got ${publicKey.length}`);
40
+ }
41
+ const prefixed = new Uint8Array(ED25519_MULTICODEC_PREFIX.length + publicKey.length);
42
+ prefixed.set(ED25519_MULTICODEC_PREFIX);
43
+ prefixed.set(publicKey, ED25519_MULTICODEC_PREFIX.length);
44
+ return `did:key:z${bs58.encode(prefixed)}`;
45
+ }
46
+ /**
47
+ * Extract Ed25519 public key from did:key string.
48
+ *
49
+ * @throws {OphirError} if input is empty, DID format is invalid, or extracted key is not 32 bytes.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const publicKey = didToPublicKey('did:key:z6Mk...');
54
+ * // Uint8Array(32)
55
+ * ```
56
+ */
57
+ export function didToPublicKey(did) {
58
+ if (!did || typeof did !== 'string') {
59
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, 'DID must be a non-empty string');
60
+ }
61
+ if (!did.startsWith(DID_KEY_PREFIX)) {
62
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid did:key format: ${did}. Expected format: did:key:z<base58-encoded-ed25519-public-key>`);
63
+ }
64
+ const encoded = did.slice(DID_KEY_PREFIX.length);
65
+ const decoded = bs58.decode(encoded);
66
+ // Strip the 2-byte multicodec prefix
67
+ if (decoded.length < 2 ||
68
+ decoded[0] !== ED25519_MULTICODEC_PREFIX[0] ||
69
+ decoded[1] !== ED25519_MULTICODEC_PREFIX[1]) {
70
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, 'Invalid multicodec prefix — expected Ed25519 (0xed01)');
71
+ }
72
+ const publicKey = decoded.slice(2);
73
+ if (publicKey.length !== 32) {
74
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid public key length after DID decoding: expected 32, got ${publicKey.length}`);
75
+ }
76
+ return publicKey;
77
+ }
78
+ /**
79
+ * Generate a complete agent identity bundle.
80
+ *
81
+ * @param endpoint - The HTTPS endpoint URL for the agent.
82
+ * @throws {OphirError} if endpoint is not a valid URL with http or https protocol.
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const identity = generateAgentIdentity('https://agent.example.com');
87
+ * // { agentId: 'did:key:z6Mk...', keypair: { publicKey, secretKey }, endpoint: 'https://...' }
88
+ * ```
89
+ */
90
+ export function generateAgentIdentity(endpoint) {
91
+ if (!endpoint || typeof endpoint !== 'string') {
92
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, 'endpoint must be a non-empty string');
93
+ }
94
+ try {
95
+ const url = new URL(endpoint);
96
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
97
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `endpoint must use http or https protocol, got: ${url.protocol}`);
98
+ }
99
+ }
100
+ catch (e) {
101
+ if (e instanceof OphirError)
102
+ throw e;
103
+ throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `endpoint is not a valid URL: ${endpoint}`);
104
+ }
105
+ const keypair = generateKeyPair();
106
+ const agentId = publicKeyToDid(keypair.publicKey);
107
+ return { agentId, keypair, endpoint };
108
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @module @ophirai/sdk
3
+ *
4
+ * SDK for the Ophir Agent Negotiation Protocol.
5
+ * Provides BuyerAgent, SellerAgent, Ed25519 signing, JSON-RPC transport,
6
+ * Solana escrow management, and integration utilities.
7
+ */
8
+ export { canonicalize, sign, verify, agreementHash, signMessage, verifyMessage, } from './signing.js';
9
+ export { generateKeyPair, publicKeyToDid, didToPublicKey, generateAgentIdentity, } from './identity.js';
10
+ /** Shared types for escrow configuration, service offerings, pricing, and agreements. */
11
+ export type {
12
+ /** Configuration for Solana escrow operations (program ID, RPC endpoint). */
13
+ EscrowConfig,
14
+ /** A service a seller offers (category, description, base price, currency, unit). */
15
+ ServiceOffering,
16
+ /** Pricing strategy for quote generation (fixed, competitive, dynamic). */
17
+ PricingStrategy,
18
+ /** Custom comparator function for ranking quotes. */
19
+ RankingFunction,
20
+ /** Seller identity and service catalog, parsed from an Agent Card. */
21
+ SellerInfo,
22
+ /** Dual-signed agreement between buyer and seller with final terms and agreement hash. */
23
+ Agreement,
24
+ /** Result of filing an SLA dispute (dispute ID and outcome status). */
25
+ DisputeResult,
26
+ /** Result of a job execution (success flag, data, and optional error). */
27
+ JobResult, } from './types.js';
28
+ /** JSON-RPC 2.0 client for sending negotiation messages over HTTPS. */
29
+ export { JsonRpcClient } from './transport.js';
30
+ /** Configuration options for the JSON-RPC client (timeout, headers). */
31
+ export type { JsonRpcClientConfig } from './transport.js';
32
+ /** Tracks the state of a single negotiation session, enforcing valid transitions. */
33
+ export { NegotiationSession } from './negotiation.js';
34
+ /** Express-based JSON-RPC 2.0 server that dispatches to registered method handlers. */
35
+ export { NegotiationServer } from './server.js';
36
+ export { buildRFQ, buildQuote, buildCounter, buildAccept, buildReject, buildDispute, } from './messages.js';
37
+ /** Pre-built SLA templates and comparison/conversion utilities. */
38
+ export { SLA_TEMPLATES, compareSLAs, meetsSLARequirements, slaToLockstepSpec, } from './sla.js';
39
+ export type {
40
+ /** Result of comparing two SLA specifications metric-by-metric. */
41
+ SLAComparisonResult,
42
+ /** Per-metric comparison detail (name, offered vs required, met flag). */
43
+ SLAComparisonDetail,
44
+ /** Result of checking if an SLA meets requirements (meets flag, gaps). */
45
+ SLAMeetsResult,
46
+ /** A gap between a required SLA target and an offered value. */
47
+ SLAGap,
48
+ /** A single behavioral check in a Lockstep verification spec. */
49
+ LockstepBehavioralCheck,
50
+ /** Full Lockstep verification spec converted from SLA terms. */
51
+ LockstepVerificationSpec, } from './sla.js';
52
+ /** Sell-side agent that receives RFQs, generates quotes, and manages agreements. */
53
+ export { SellerAgent } from './seller.js';
54
+ /** Configuration for creating a SellerAgent (keypair, endpoint, services, pricing). */
55
+ export type { SellerAgentConfig } from './seller.js';
56
+ /** Buy-side agent that sends RFQs, collects quotes, ranks, and accepts offers. */
57
+ export { BuyerAgent } from './buyer.js';
58
+ /** Configuration for creating a BuyerAgent (keypair, endpoint, escrow config). */
59
+ export type { BuyerAgentConfig } from './buyer.js';
60
+ /** Manages Solana PDA escrow operations (create, release, dispute, cancel). */
61
+ export { EscrowManager } from './escrow.js';
62
+ /** On-chain escrow status enum (Active, Released, Disputed, Cancelled). */
63
+ export type { EscrowStatus, EscrowAccountData } from './escrow.js';
64
+ /** Discover agents via A2A Agent Cards at /.well-known/agent.json endpoints. */
65
+ export { discoverAgents, parseAgentCard } from './discovery.js';
66
+ /** A2A-compatible Agent Card describing an agent's identity and capabilities. */
67
+ export type { AgentCard, NegotiationCapability } from './discovery.js';
68
+ /** Convert agreements to Lockstep specs and monitor SLA compliance. */
69
+ export { agreementToLockstepSpec, LockstepMonitor } from './lockstep.js';
70
+ export type {
71
+ /** Lockstep behavioral verification specification derived from SLA terms. */
72
+ LockstepSpec,
73
+ /** A single behavioral requirement in a Lockstep verification spec. */
74
+ LockstepBehavioralRequirement,
75
+ /** Configuration for the LockstepMonitor (verification endpoint URL). */
76
+ LockstepMonitorConfig,
77
+ /** Result of an SLA compliance check (compliant flag and violation details). */
78
+ ComplianceResult, } from './lockstep.js';
79
+ /** Convert agreements to x402 HTTP payment headers and parse responses. */
80
+ export { agreementToX402Headers, parseX402Response } from './x402.js';
81
+ /** All protocol-level types re-exported from @ophirai/protocol for convenience. */
82
+ export type {
83
+ /** Agent identity with did:key identifier and HTTP endpoint. */
84
+ AgentIdentity,
85
+ /** Service category and requirements specified by the buyer. */
86
+ ServiceRequirement,
87
+ /** Budget constraints (max price, currency, unit, total budget). */
88
+ BudgetConstraint,
89
+ /** SLA requirements with metrics and dispute resolution terms. */
90
+ SLARequirement,
91
+ /** Single SLA metric (name, target, comparison operator). */
92
+ SLAMetric,
93
+ /** Accepted payment method (network and token). */
94
+ PaymentMethod,
95
+ /** Seller's pricing offer (price, currency, unit, model, volume discounts). */
96
+ PricingOffer,
97
+ /** Execution metadata for a running service. */
98
+ ExecutionInfo,
99
+ /** Escrow deposit requirements from a seller's quote. */
100
+ EscrowRequirement,
101
+ /** Agreed-upon final terms (price, currency, unit, SLA, escrow). */
102
+ FinalTerms,
103
+ /** Evidence of an SLA violation for dispute filing. */
104
+ ViolationEvidence,
105
+ /** Union type of all 11 negotiation states. */
106
+ NegotiationState,
107
+ /** Parameters for a negotiate/rfq message. */
108
+ RFQParams,
109
+ /** Parameters for a negotiate/quote message. */
110
+ QuoteParams,
111
+ /** Parameters for a negotiate/counter message. */
112
+ CounterParams,
113
+ /** Parameters for a negotiate/accept message (dual-signed). */
114
+ AcceptParams,
115
+ /** Parameters for a negotiate/reject message. */
116
+ RejectParams,
117
+ /** Parameters for a negotiate/dispute message. */
118
+ DisputeParams,
119
+ /** JSON-RPC 2.0 request envelope with typed params. */
120
+ JsonRpcRequest,
121
+ /** JSON-RPC 2.0 response envelope with typed result. */
122
+ JsonRpcResponse, } from '@ophirai/protocol';
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @module @ophirai/sdk
3
+ *
4
+ * SDK for the Ophir Agent Negotiation Protocol.
5
+ * Provides BuyerAgent, SellerAgent, Ed25519 signing, JSON-RPC transport,
6
+ * Solana escrow management, and integration utilities.
7
+ */
8
+ // ── Cryptographic signing ────────────────────────────────────────────
9
+ export { canonicalize, sign, verify, agreementHash, signMessage, verifyMessage, } from './signing.js';
10
+ // ── Agent identity (did:key) ─────────────────────────────────────────
11
+ export { generateKeyPair, publicKeyToDid, didToPublicKey, generateAgentIdentity, } from './identity.js';
12
+ // ── JSON-RPC transport ──────────────────────────────────────────────
13
+ /** JSON-RPC 2.0 client for sending negotiation messages over HTTPS. */
14
+ export { JsonRpcClient } from './transport.js';
15
+ // ── Negotiation state machine ────────────────────────────────────────
16
+ /** Tracks the state of a single negotiation session, enforcing valid transitions. */
17
+ export { NegotiationSession } from './negotiation.js';
18
+ // ── JSON-RPC server ─────────────────────────────────────────────────
19
+ /** Express-based JSON-RPC 2.0 server that dispatches to registered method handlers. */
20
+ export { NegotiationServer } from './server.js';
21
+ // ── Message builders ────────────────────────────────────────────────
22
+ export { buildRFQ, buildQuote, buildCounter, buildAccept, buildReject, buildDispute, } from './messages.js';
23
+ // ── SLA utilities ───────────────────────────────────────────────────
24
+ /** Pre-built SLA templates and comparison/conversion utilities. */
25
+ export { SLA_TEMPLATES, compareSLAs, meetsSLARequirements, slaToLockstepSpec, } from './sla.js';
26
+ // ── Seller agent ────────────────────────────────────────────────────
27
+ /** Sell-side agent that receives RFQs, generates quotes, and manages agreements. */
28
+ export { SellerAgent } from './seller.js';
29
+ // ── Buyer agent ─────────────────────────────────────────────────────
30
+ /** Buy-side agent that sends RFQs, collects quotes, ranks, and accepts offers. */
31
+ export { BuyerAgent } from './buyer.js';
32
+ // ── Solana escrow ───────────────────────────────────────────────────
33
+ /** Manages Solana PDA escrow operations (create, release, dispute, cancel). */
34
+ export { EscrowManager } from './escrow.js';
35
+ // ── Agent discovery (A2A) ───────────────────────────────────────────
36
+ /** Discover agents via A2A Agent Cards at /.well-known/agent.json endpoints. */
37
+ export { discoverAgents, parseAgentCard } from './discovery.js';
38
+ // ── Lockstep verification ───────────────────────────────────────────
39
+ /** Convert agreements to Lockstep specs and monitor SLA compliance. */
40
+ export { agreementToLockstepSpec, LockstepMonitor } from './lockstep.js';
41
+ // ── x402 payment headers ───────────────────────────────────────────
42
+ /** Convert agreements to x402 HTTP payment headers and parse responses. */
43
+ export { agreementToX402Headers, parseX402Response } from './x402.js';
@@ -0,0 +1,94 @@
1
+ import type { Agreement, DisputeResult } from './types.js';
2
+ /** A single behavioral requirement in a Lockstep verification spec. */
3
+ export interface LockstepBehavioralRequirement {
4
+ metric: string;
5
+ operator: string;
6
+ threshold: number;
7
+ measurement_method: string;
8
+ measurement_window: string;
9
+ }
10
+ /** Lockstep behavioral verification specification derived from SLA terms. */
11
+ export interface LockstepSpec {
12
+ spec_version: string;
13
+ agent_id: string;
14
+ behavioral_requirements: LockstepBehavioralRequirement[];
15
+ verification_mode: string;
16
+ on_violation: {
17
+ action: string;
18
+ escrow_address?: string;
19
+ penalty_rate?: number;
20
+ };
21
+ }
22
+ /** Convert agreed SLA terms into a Lockstep behavioral verification specification.
23
+ * @param agreement - The finalized agreement containing SLA metrics and escrow details
24
+ * @returns A Lockstep spec with behavioral requirements derived from the agreement's SLA metrics
25
+ * @example
26
+ * ```typescript
27
+ * const spec = agreementToLockstepSpec(agreement);
28
+ * console.log(spec.behavioral_requirements); // [{ metric: 'latency', operator: '<=', ... }]
29
+ * ```
30
+ */
31
+ export declare function agreementToLockstepSpec(agreement: Agreement): LockstepSpec;
32
+ /** Configuration for the LockstepMonitor. */
33
+ export interface LockstepMonitorConfig {
34
+ /** Lockstep verification API endpoint. Defaults to the Lockstep public API. */
35
+ verificationEndpoint?: string;
36
+ }
37
+ /** Result of an SLA compliance check against the Lockstep verification service. */
38
+ export interface ComplianceResult {
39
+ compliant: boolean;
40
+ violations: {
41
+ metric: string;
42
+ threshold: number;
43
+ observed: number;
44
+ timestamp: string;
45
+ }[];
46
+ }
47
+ /**
48
+ * Monitor agent compliance with agreed SLA terms via Lockstep verification.
49
+ */
50
+ export declare class LockstepMonitor {
51
+ private verificationEndpoint;
52
+ private specs;
53
+ constructor(config?: LockstepMonitorConfig);
54
+ /** Register an agreement for continuous SLA monitoring. Operates locally if the endpoint is unavailable.
55
+ * @param agreement - The finalized agreement to monitor for SLA compliance
56
+ * @returns An object containing the assigned monitoringId
57
+ * @example
58
+ * ```typescript
59
+ * const monitor = new LockstepMonitor();
60
+ * const { monitoringId } = await monitor.startMonitoring(agreement);
61
+ * ```
62
+ */
63
+ startMonitoring(agreement: Agreement): Promise<{
64
+ monitoringId: string;
65
+ }>;
66
+ /** Check SLA compliance for a monitored agreement.
67
+ * Returns unknown compliance status if the verification endpoint is unavailable,
68
+ * so callers can distinguish between verified compliance and missing data.
69
+ * @param monitoringId - The monitoring ID returned by startMonitoring
70
+ * @returns A compliance result with violation details; `compliant` is false with an empty violations array when the endpoint is unreachable (unknown state)
71
+ * @example
72
+ * ```typescript
73
+ * const result = await monitor.checkCompliance(monitoringId);
74
+ * if (!result.compliant) console.log('Violations:', result.violations);
75
+ * ```
76
+ */
77
+ checkCompliance(monitoringId: string): Promise<ComplianceResult>;
78
+ /** Trigger an SLA violation dispute via the Lockstep service. Returns a pending result if the endpoint is unavailable.
79
+ * @param monitoringId - The monitoring ID for the agreement in violation
80
+ * @param violation - Details of the observed SLA violation
81
+ * @returns The dispute result with outcome status ("pending" when the endpoint is unavailable)
82
+ * @example
83
+ * ```typescript
84
+ * const dispute = await monitor.triggerDispute(monitoringId, {
85
+ * metric: 'latency', threshold: 200, observed: 450,
86
+ * });
87
+ * ```
88
+ */
89
+ triggerDispute(monitoringId: string, violation: {
90
+ metric: string;
91
+ threshold: number;
92
+ observed: number;
93
+ }): Promise<DisputeResult>;
94
+ }
@@ -0,0 +1,127 @@
1
+ const DEFAULT_LOCKSTEP_ENDPOINT = 'https://api.lockstep.dev/v1';
2
+ /** Convert agreed SLA terms into a Lockstep behavioral verification specification.
3
+ * @param agreement - The finalized agreement containing SLA metrics and escrow details
4
+ * @returns A Lockstep spec with behavioral requirements derived from the agreement's SLA metrics
5
+ * @example
6
+ * ```typescript
7
+ * const spec = agreementToLockstepSpec(agreement);
8
+ * console.log(spec.behavioral_requirements); // [{ metric: 'latency', operator: '<=', ... }]
9
+ * ```
10
+ */
11
+ export function agreementToLockstepSpec(agreement) {
12
+ const sla = agreement.final_terms.sla;
13
+ const metrics = sla?.metrics ?? [];
14
+ return {
15
+ spec_version: '1.0',
16
+ agent_id: agreement.agreement_id,
17
+ behavioral_requirements: metrics.map((m) => ({
18
+ metric: m.name === 'custom' && m.custom_name ? m.custom_name : m.name,
19
+ operator: m.comparison,
20
+ threshold: m.target,
21
+ measurement_method: m.measurement_method ?? 'rolling_average',
22
+ measurement_window: m.measurement_window ?? '1h',
23
+ })),
24
+ verification_mode: 'continuous',
25
+ on_violation: {
26
+ action: 'trigger_dispute',
27
+ escrow_address: agreement.escrow?.address,
28
+ penalty_rate: metrics.length > 0 && metrics[0].penalty_per_violation
29
+ ? parseFloat(metrics[0].penalty_per_violation.amount) || undefined
30
+ : undefined,
31
+ },
32
+ };
33
+ }
34
+ /**
35
+ * Monitor agent compliance with agreed SLA terms via Lockstep verification.
36
+ */
37
+ export class LockstepMonitor {
38
+ verificationEndpoint;
39
+ specs = new Map();
40
+ constructor(config = {}) {
41
+ this.verificationEndpoint =
42
+ config.verificationEndpoint ?? DEFAULT_LOCKSTEP_ENDPOINT;
43
+ }
44
+ /** Register an agreement for continuous SLA monitoring. Operates locally if the endpoint is unavailable.
45
+ * @param agreement - The finalized agreement to monitor for SLA compliance
46
+ * @returns An object containing the assigned monitoringId
47
+ * @example
48
+ * ```typescript
49
+ * const monitor = new LockstepMonitor();
50
+ * const { monitoringId } = await monitor.startMonitoring(agreement);
51
+ * ```
52
+ */
53
+ async startMonitoring(agreement) {
54
+ const spec = agreementToLockstepSpec(agreement);
55
+ const monitoringId = `mon_${agreement.agreement_id}`;
56
+ this.specs.set(monitoringId, spec);
57
+ try {
58
+ await fetch(`${this.verificationEndpoint}/monitors`, {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({ monitoring_id: monitoringId, spec }),
62
+ });
63
+ // Spec is already stored locally; remote registration is best-effort
64
+ }
65
+ catch (_err) {
66
+ // Lockstep endpoint not available — operate in local mode.
67
+ // This is best-effort: the spec is stored locally regardless.
68
+ }
69
+ return { monitoringId };
70
+ }
71
+ /** Check SLA compliance for a monitored agreement.
72
+ * Returns unknown compliance status if the verification endpoint is unavailable,
73
+ * so callers can distinguish between verified compliance and missing data.
74
+ * @param monitoringId - The monitoring ID returned by startMonitoring
75
+ * @returns A compliance result with violation details; `compliant` is false with an empty violations array when the endpoint is unreachable (unknown state)
76
+ * @example
77
+ * ```typescript
78
+ * const result = await monitor.checkCompliance(monitoringId);
79
+ * if (!result.compliant) console.log('Violations:', result.violations);
80
+ * ```
81
+ */
82
+ async checkCompliance(monitoringId) {
83
+ try {
84
+ const res = await fetch(`${this.verificationEndpoint}/monitors/${monitoringId}/compliance`);
85
+ if (res.ok) {
86
+ return (await res.json());
87
+ }
88
+ }
89
+ catch (_err) {
90
+ // Lockstep endpoint unavailable — cannot verify compliance
91
+ }
92
+ // Cannot verify compliance — report as non-compliant with no specific violations
93
+ // so callers don't mistakenly treat unverified state as verified compliance.
94
+ return { compliant: false, violations: [] };
95
+ }
96
+ /** Trigger an SLA violation dispute via the Lockstep service. Returns a pending result if the endpoint is unavailable.
97
+ * @param monitoringId - The monitoring ID for the agreement in violation
98
+ * @param violation - Details of the observed SLA violation
99
+ * @returns The dispute result with outcome status ("pending" when the endpoint is unavailable)
100
+ * @example
101
+ * ```typescript
102
+ * const dispute = await monitor.triggerDispute(monitoringId, {
103
+ * metric: 'latency', threshold: 200, observed: 450,
104
+ * });
105
+ * ```
106
+ */
107
+ async triggerDispute(monitoringId, violation) {
108
+ const spec = this.specs.get(monitoringId);
109
+ try {
110
+ const res = await fetch(`${this.verificationEndpoint}/monitors/${monitoringId}/dispute`, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({ violation, spec }),
114
+ });
115
+ if (res.ok) {
116
+ return (await res.json());
117
+ }
118
+ }
119
+ catch (_err) {
120
+ // Lockstep endpoint unavailable — return pending result
121
+ }
122
+ return {
123
+ dispute_id: `dispute_${monitoringId}_${Date.now()}`,
124
+ outcome: 'pending',
125
+ };
126
+ }
127
+ }
@@ -0,0 +1,172 @@
1
+ import type { AgentIdentity, ServiceRequirement, BudgetConstraint, SLARequirement, PaymentMethod, PricingOffer, ExecutionInfo, EscrowRequirement, FinalTerms, ViolationEvidence, JsonRpcRequest, RFQParams, QuoteParams, CounterParams, AcceptParams, RejectParams, DisputeParams } from '@ophirai/protocol';
2
+ /**
3
+ * Build a signed RFQ (Request for Quote) message. Signs the RFQ with the buyer's Ed25519 key
4
+ * so that sellers can verify the buyer authorized this request.
5
+ *
6
+ * @throws {OphirError} if buyer identity fields are missing.
7
+ * @throws {OphirError} if secretKey is not 64 bytes.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const rfq = buildRFQ({
12
+ * buyer: { agent_id: 'did:key:z6Mk...', endpoint: 'https://buyer.example.com' },
13
+ * service: { category: 'inference' },
14
+ * budget: { max_price_per_unit: '0.01', currency: 'USDC', unit: 'request' },
15
+ * secretKey: keypair.secretKey,
16
+ * });
17
+ * ```
18
+ */
19
+ export declare function buildRFQ(params: {
20
+ buyer: AgentIdentity;
21
+ service: ServiceRequirement;
22
+ budget: BudgetConstraint;
23
+ sla?: SLARequirement;
24
+ sellers?: string[];
25
+ maxRounds?: number;
26
+ ttlMs?: number;
27
+ acceptedPayments?: PaymentMethod[];
28
+ secretKey: Uint8Array;
29
+ }): JsonRpcRequest<RFQParams>;
30
+ /**
31
+ * Build a signed Quote response message. Signs the quote with the seller's Ed25519 key.
32
+ *
33
+ * @throws {OphirError} if rfqId or seller identity fields are missing.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const quote = buildQuote({
38
+ * rfqId: 'a1b2c3d4-...',
39
+ * seller: { agent_id: 'did:key:z6Mk...', endpoint: 'https://seller.example.com' },
40
+ * pricing: { price_per_unit: '0.005', currency: 'USDC', unit: 'request', pricing_model: 'fixed' },
41
+ * secretKey: keypair.secretKey,
42
+ * });
43
+ * ```
44
+ */
45
+ export declare function buildQuote(params: {
46
+ rfqId: string;
47
+ seller: AgentIdentity;
48
+ pricing: PricingOffer;
49
+ sla?: SLARequirement;
50
+ execution?: ExecutionInfo;
51
+ escrow?: EscrowRequirement;
52
+ ttlMs?: number;
53
+ secretKey: Uint8Array;
54
+ }): JsonRpcRequest<QuoteParams>;
55
+ /**
56
+ * Build a signed counter-offer message. Signs with the sender's Ed25519 key.
57
+ *
58
+ * @throws {OphirError} if rfqId, inResponseTo, or from.agent_id are missing.
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const counter = buildCounter({
63
+ * rfqId: 'a1b2c3d4-...',
64
+ * inResponseTo: 'e5f6g7h8-...',
65
+ * round: 1,
66
+ * from: { agent_id: 'did:key:z6Mk...', role: 'buyer' },
67
+ * modifications: { price_per_unit: '0.008' },
68
+ * secretKey: keypair.secretKey,
69
+ * });
70
+ * ```
71
+ */
72
+ export declare function buildCounter(params: {
73
+ rfqId: string;
74
+ inResponseTo: string;
75
+ round: number;
76
+ from: {
77
+ agent_id: string;
78
+ role: 'buyer' | 'seller';
79
+ };
80
+ modifications: Record<string, unknown>;
81
+ justification?: string;
82
+ ttlMs?: number;
83
+ secretKey: Uint8Array;
84
+ }): JsonRpcRequest<CounterParams>;
85
+ /**
86
+ * Build an Accept message with agreement hash and buyer signature.
87
+ *
88
+ * Validates that finalTerms contains the required fields: price_per_unit, currency, and unit.
89
+ *
90
+ * @throws {OphirError} if rfqId, acceptingMessageId, or required finalTerms fields are missing.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const accept = buildAccept({
95
+ * rfqId: 'a1b2c3d4-...',
96
+ * acceptingMessageId: 'e5f6g7h8-...',
97
+ * finalTerms: { price_per_unit: '0.01', currency: 'USDC', unit: 'request' },
98
+ * buyerSecretKey: keypair.secretKey,
99
+ * });
100
+ * ```
101
+ */
102
+ export declare function buildAccept(params: {
103
+ rfqId: string;
104
+ acceptingMessageId: string;
105
+ finalTerms: FinalTerms;
106
+ buyerSecretKey: Uint8Array;
107
+ sellerSignature?: string;
108
+ }): JsonRpcRequest<AcceptParams>;
109
+ /**
110
+ * Build a signed Reject message to decline a negotiation. Signs with the rejecting
111
+ * agent's Ed25519 key so that receivers can verify the rejection is authorized.
112
+ *
113
+ * @throws {OphirError} if rfqId, rejectingMessageId, reason, or agentId are empty.
114
+ * @throws {OphirError} if secretKey is not 64 bytes.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const reject = buildReject({
119
+ * rfqId: 'a1b2c3d4-...',
120
+ * rejectingMessageId: 'e5f6g7h8-...',
121
+ * reason: 'Price too high',
122
+ * agentId: 'did:key:z6Mk...',
123
+ * secretKey: keypair.secretKey,
124
+ * });
125
+ * ```
126
+ */
127
+ export declare function buildReject(params: {
128
+ rfqId: string;
129
+ rejectingMessageId: string;
130
+ reason: string;
131
+ agentId: string;
132
+ secretKey: Uint8Array;
133
+ }): JsonRpcRequest<RejectParams>;
134
+ /**
135
+ * Build a signed Dispute message with violation evidence.
136
+ *
137
+ * @throws {OphirError} if agreementId, filedBy.agent_id, requestedRemedy, or escrowAction are empty.
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * const dispute = buildDispute({
142
+ * agreementId: 'a1b2c3d4-...',
143
+ * filedBy: { agent_id: 'did:key:z6Mk...', role: 'buyer' },
144
+ * violation: {
145
+ * sla_metric: 'uptime_pct',
146
+ * agreed_value: 99.9,
147
+ * observed_value: 95.0,
148
+ * measurement_window: '24h',
149
+ * evidence_hash: 'abc123',
150
+ * },
151
+ * requestedRemedy: 'Full refund',
152
+ * escrowAction: 'release_to_buyer',
153
+ * secretKey: keypair.secretKey,
154
+ * });
155
+ * ```
156
+ */
157
+ export declare function buildDispute(params: {
158
+ agreementId: string;
159
+ filedBy: {
160
+ agent_id: string;
161
+ role: 'buyer' | 'seller';
162
+ };
163
+ violation: ViolationEvidence;
164
+ requestedRemedy: string;
165
+ escrowAction: string;
166
+ lockstepReport?: {
167
+ verification_id: string;
168
+ result: 'PASS' | 'FAIL';
169
+ deviations: string[];
170
+ };
171
+ secretKey: Uint8Array;
172
+ }): JsonRpcRequest<DisputeParams>;