@cleocode/lafs 1.8.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/schemas/v1/conformance-profiles.json +39 -0
  4. package/dist/schemas/v1/envelope.schema.json +306 -0
  5. package/dist/schemas/v1/error-registry.json +162 -0
  6. package/dist/src/a2a/bindings/grpc.d.ts +67 -0
  7. package/dist/src/a2a/bindings/grpc.js +148 -0
  8. package/dist/src/a2a/bindings/http.d.ts +102 -0
  9. package/dist/src/a2a/bindings/http.js +120 -0
  10. package/dist/src/a2a/bindings/index.d.ts +35 -0
  11. package/dist/src/a2a/bindings/index.js +79 -0
  12. package/dist/src/a2a/bindings/jsonrpc.d.ts +77 -0
  13. package/dist/src/a2a/bindings/jsonrpc.js +114 -0
  14. package/dist/src/a2a/bridge.d.ts +175 -0
  15. package/dist/src/a2a/bridge.js +286 -0
  16. package/dist/src/a2a/extensions.d.ts +121 -0
  17. package/dist/src/a2a/extensions.js +205 -0
  18. package/dist/src/a2a/index.d.ts +40 -0
  19. package/dist/src/a2a/index.js +76 -0
  20. package/dist/src/a2a/streaming.d.ts +74 -0
  21. package/dist/src/a2a/streaming.js +265 -0
  22. package/dist/src/a2a/task-lifecycle.d.ts +109 -0
  23. package/dist/src/a2a/task-lifecycle.js +313 -0
  24. package/dist/src/budgetEnforcement.d.ts +84 -0
  25. package/dist/src/budgetEnforcement.js +328 -0
  26. package/dist/src/circuit-breaker/index.d.ts +121 -0
  27. package/dist/src/circuit-breaker/index.js +249 -0
  28. package/dist/src/cli.d.ts +16 -0
  29. package/dist/src/cli.js +63 -0
  30. package/dist/src/compliance.d.ts +31 -0
  31. package/dist/src/compliance.js +89 -0
  32. package/dist/src/conformance.d.ts +7 -0
  33. package/dist/src/conformance.js +248 -0
  34. package/dist/src/conformanceProfiles.d.ts +11 -0
  35. package/dist/src/conformanceProfiles.js +34 -0
  36. package/dist/src/deprecationRegistry.d.ts +13 -0
  37. package/dist/src/deprecationRegistry.js +39 -0
  38. package/dist/src/discovery.d.ts +286 -0
  39. package/dist/src/discovery.js +350 -0
  40. package/dist/src/envelope.d.ts +60 -0
  41. package/dist/src/envelope.js +136 -0
  42. package/dist/src/errorRegistry.d.ts +28 -0
  43. package/dist/src/errorRegistry.js +36 -0
  44. package/dist/src/fieldExtraction.d.ts +67 -0
  45. package/dist/src/fieldExtraction.js +133 -0
  46. package/dist/src/flagResolver.d.ts +46 -0
  47. package/dist/src/flagResolver.js +47 -0
  48. package/dist/src/flagSemantics.d.ts +16 -0
  49. package/dist/src/flagSemantics.js +45 -0
  50. package/dist/src/health/index.d.ts +105 -0
  51. package/dist/src/health/index.js +220 -0
  52. package/dist/src/index.d.ts +24 -0
  53. package/dist/src/index.js +34 -0
  54. package/dist/src/mcpAdapter.d.ts +28 -0
  55. package/dist/src/mcpAdapter.js +281 -0
  56. package/dist/src/mviProjection.d.ts +19 -0
  57. package/dist/src/mviProjection.js +116 -0
  58. package/dist/src/problemDetails.d.ts +34 -0
  59. package/dist/src/problemDetails.js +45 -0
  60. package/dist/src/shutdown/index.d.ts +69 -0
  61. package/dist/src/shutdown/index.js +160 -0
  62. package/dist/src/tokenEstimator.d.ts +87 -0
  63. package/dist/src/tokenEstimator.js +238 -0
  64. package/dist/src/types.d.ts +135 -0
  65. package/dist/src/types.js +12 -0
  66. package/dist/src/validateEnvelope.d.ts +15 -0
  67. package/dist/src/validateEnvelope.js +31 -0
  68. package/lafs.md +819 -0
  69. package/package.json +88 -0
  70. package/schemas/v1/agent-card.schema.json +230 -0
  71. package/schemas/v1/conformance-profiles.json +39 -0
  72. package/schemas/v1/context-ledger.schema.json +70 -0
  73. package/schemas/v1/discovery.schema.json +132 -0
  74. package/schemas/v1/envelope.schema.json +306 -0
  75. package/schemas/v1/error-registry.json +162 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * LAFS Token Estimator
3
+ *
4
+ * Provides character-based token estimation for LAFS envelopes and JSON payloads.
5
+ * Uses the approximation: 1 token ≈ 4 characters.
6
+ * Properly handles nested objects, arrays, Unicode graphemes, and circular references.
7
+ */
8
+ /**
9
+ * Counts Unicode graphemes in a string using Intl.Segmenter when available.
10
+ * Falls back to character counting for environments without Intl.Segmenter.
11
+ */
12
+ function countGraphemes(str) {
13
+ // Use Intl.Segmenter for proper grapheme counting (Node.js 16+, modern browsers)
14
+ if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {
15
+ // @ts-ignore - Intl.Segmenter may not be in all TypeScript lib versions
16
+ const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
17
+ // @ts-ignore
18
+ return Array.from(segmenter.segment(str)).length;
19
+ }
20
+ // Fallback: count code points using spread operator (handles surrogate pairs)
21
+ return [...str].length;
22
+ }
23
+ /**
24
+ * Default options for token estimation
25
+ */
26
+ const DEFAULT_OPTIONS = {
27
+ charsPerToken: 4,
28
+ maxDepth: 100,
29
+ maxStringLength: 100000,
30
+ };
31
+ /**
32
+ * TokenEstimator provides character-based token counting for JSON payloads.
33
+ *
34
+ * Algorithm:
35
+ * 1. Serialize value to JSON (handling circular refs)
36
+ * 2. Count Unicode graphemes (not bytes)
37
+ * 3. Divide by charsPerToken ratio (default 4)
38
+ * 4. Add overhead for structural characters
39
+ */
40
+ export class TokenEstimator {
41
+ options;
42
+ constructor(options = {}) {
43
+ this.options = { ...DEFAULT_OPTIONS, ...options };
44
+ }
45
+ /**
46
+ * Estimate tokens for any JavaScript value.
47
+ * Handles circular references, nested objects, arrays, and Unicode.
48
+ *
49
+ * @param value - Any value to estimate
50
+ * @returns Estimated token count
51
+ */
52
+ estimate(value) {
53
+ return this.estimateWithTracking(value, new WeakSet(), 0);
54
+ }
55
+ /**
56
+ * Estimate tokens from a JSON string.
57
+ * More efficient if you already have the JSON string.
58
+ *
59
+ * @param json - JSON string to estimate
60
+ * @returns Estimated token count
61
+ */
62
+ estimateJSON(json) {
63
+ // Count graphemes in the JSON string
64
+ const graphemes = countGraphemes(json);
65
+ // Add overhead for JSON structure (brackets, quotes, colons, etc.)
66
+ const structuralOverhead = Math.ceil(graphemes * 0.1);
67
+ return Math.ceil((graphemes + structuralOverhead) / this.options.charsPerToken);
68
+ }
69
+ /**
70
+ * Internal recursive estimation with circular reference tracking.
71
+ */
72
+ estimateWithTracking(value, seen, depth) {
73
+ // Prevent infinite recursion
74
+ if (depth > this.options.maxDepth) {
75
+ return 1; // Minimal cost for max depth exceeded
76
+ }
77
+ // Handle null
78
+ if (value === null) {
79
+ return 1; // "null" = 4 chars / 4 = 1 token
80
+ }
81
+ // Handle undefined
82
+ if (value === undefined) {
83
+ return 1;
84
+ }
85
+ // Handle primitives
86
+ const type = typeof value;
87
+ if (type === 'boolean') {
88
+ return value ? 1 : 1; // "true" or "false" ≈ 1 token
89
+ }
90
+ if (type === 'number') {
91
+ const str = String(value);
92
+ return Math.ceil(countGraphemes(str) / this.options.charsPerToken);
93
+ }
94
+ if (type === 'string') {
95
+ const str = value;
96
+ // Limit string length to prevent performance issues
97
+ const truncated = str.length > this.options.maxStringLength
98
+ ? str.slice(0, this.options.maxStringLength) + '…'
99
+ : str;
100
+ const graphemes = countGraphemes(truncated);
101
+ // Add 2 for quotes
102
+ return Math.ceil((graphemes + 2) / this.options.charsPerToken);
103
+ }
104
+ // Handle objects and arrays
105
+ if (type === 'object') {
106
+ const obj = value;
107
+ // Check for circular reference
108
+ if (seen.has(obj)) {
109
+ return 1; // Minimal cost for circular ref placeholder
110
+ }
111
+ seen.add(obj);
112
+ try {
113
+ if (Array.isArray(obj)) {
114
+ return this.estimateArray(obj, seen, depth);
115
+ }
116
+ return this.estimateObject(obj, seen, depth);
117
+ }
118
+ finally {
119
+ seen.delete(obj);
120
+ }
121
+ }
122
+ // Handle symbols, functions, etc.
123
+ return 1;
124
+ }
125
+ /**
126
+ * Estimate tokens for an array.
127
+ */
128
+ estimateArray(arr, seen, depth) {
129
+ let tokens = 1; // Opening bracket [ (already counted as structural)
130
+ for (let i = 0; i < arr.length; i++) {
131
+ tokens += this.estimateWithTracking(arr[i], seen, depth + 1);
132
+ // Add comma separator (except for last element)
133
+ if (i < arr.length - 1) {
134
+ tokens += 1; // comma + space ≈ 2 chars / 4 = 0.5, round up to 1
135
+ }
136
+ }
137
+ tokens += 1; // Closing bracket ]
138
+ return tokens;
139
+ }
140
+ /**
141
+ * Estimate tokens for a plain object.
142
+ */
143
+ estimateObject(obj, seen, depth) {
144
+ let tokens = 1; // Opening brace {
145
+ const keys = Object.keys(obj);
146
+ for (let i = 0; i < keys.length; i++) {
147
+ const key = keys[i];
148
+ const value = obj[key];
149
+ // Estimate key (with quotes)
150
+ tokens += Math.ceil((countGraphemes(key) + 2) / this.options.charsPerToken);
151
+ // Colon separator
152
+ tokens += 1; // " : " ≈ 3 chars / 4 = 0.75, round up to 1
153
+ // Estimate value
154
+ tokens += this.estimateWithTracking(value, seen, depth + 1);
155
+ // Comma separator (except for last property)
156
+ if (i < keys.length - 1) {
157
+ tokens += 1; // comma + space ≈ 2 chars / 4 = 0.5, round up to 1
158
+ }
159
+ }
160
+ tokens += 1; // Closing brace }
161
+ return tokens;
162
+ }
163
+ /**
164
+ * Check if a value can be safely serialized (no circular refs).
165
+ */
166
+ canSerialize(value) {
167
+ try {
168
+ JSON.stringify(value);
169
+ return true;
170
+ }
171
+ catch {
172
+ return false;
173
+ }
174
+ }
175
+ /**
176
+ * Serialize value to JSON with circular reference handling.
177
+ * Circular refs are replaced with "[Circular]".
178
+ */
179
+ safeStringify(value) {
180
+ const seen = new WeakSet();
181
+ return JSON.stringify(value, (key, val) => {
182
+ if (typeof val === 'object' && val !== null) {
183
+ if (seen.has(val)) {
184
+ return '[Circular]';
185
+ }
186
+ seen.add(val);
187
+ }
188
+ return val;
189
+ });
190
+ }
191
+ /**
192
+ * Create a safe copy of a value with circular refs removed.
193
+ */
194
+ safeCopy(value) {
195
+ const seen = new WeakSet();
196
+ function clone(val) {
197
+ if (val === null || typeof val !== 'object') {
198
+ return val;
199
+ }
200
+ if (seen.has(val)) {
201
+ return '[Circular]';
202
+ }
203
+ seen.add(val);
204
+ try {
205
+ if (Array.isArray(val)) {
206
+ return val.map(clone);
207
+ }
208
+ const result = {};
209
+ for (const [k, v] of Object.entries(val)) {
210
+ result[k] = clone(v);
211
+ }
212
+ return result;
213
+ }
214
+ finally {
215
+ seen.delete(val);
216
+ }
217
+ }
218
+ return clone(value);
219
+ }
220
+ }
221
+ /**
222
+ * Global token estimator instance with default settings.
223
+ */
224
+ export const defaultEstimator = new TokenEstimator();
225
+ /**
226
+ * Convenience function to estimate tokens for a value.
227
+ */
228
+ export function estimateTokens(value, options) {
229
+ const estimator = options ? new TokenEstimator(options) : defaultEstimator;
230
+ return estimator.estimate(value);
231
+ }
232
+ /**
233
+ * Convenience function to estimate tokens from a JSON string.
234
+ */
235
+ export function estimateTokensJSON(json, options) {
236
+ const estimator = options ? new TokenEstimator(options) : defaultEstimator;
237
+ return estimator.estimateJSON(json);
238
+ }
@@ -0,0 +1,135 @@
1
+ export type LAFSTransport = "cli" | "http" | "grpc" | "sdk";
2
+ export type LAFSErrorCategory = "VALIDATION" | "AUTH" | "PERMISSION" | "NOT_FOUND" | "CONFLICT" | "RATE_LIMIT" | "TRANSIENT" | "INTERNAL" | "CONTRACT" | "MIGRATION";
3
+ export interface Warning {
4
+ code: string;
5
+ message: string;
6
+ deprecated?: string;
7
+ replacement?: string;
8
+ removeBy?: string;
9
+ }
10
+ export type MVILevel = 'minimal' | 'standard' | 'full' | 'custom';
11
+ export declare const MVI_LEVELS: ReadonlySet<MVILevel>;
12
+ export declare function isMVILevel(value: unknown): value is MVILevel;
13
+ export interface LAFSMeta {
14
+ specVersion: string;
15
+ schemaVersion: string;
16
+ timestamp: string;
17
+ operation: string;
18
+ requestId: string;
19
+ transport: LAFSTransport;
20
+ strict: boolean;
21
+ mvi: MVILevel;
22
+ contextVersion: number;
23
+ /** Session identifier for correlating multi-step agent workflows */
24
+ sessionId?: string;
25
+ warnings?: Warning[];
26
+ }
27
+ export type LAFSAgentAction = 'retry' | 'retry_modified' | 'escalate' | 'stop' | 'wait' | 'refresh_context' | 'authenticate';
28
+ export declare const AGENT_ACTIONS: ReadonlySet<LAFSAgentAction>;
29
+ export declare function isAgentAction(value: unknown): value is LAFSAgentAction;
30
+ export interface LAFSError {
31
+ code: string;
32
+ message: string;
33
+ category: LAFSErrorCategory;
34
+ retryable: boolean;
35
+ retryAfterMs: number | null;
36
+ details: Record<string, unknown>;
37
+ agentAction?: LAFSAgentAction;
38
+ escalationRequired?: boolean;
39
+ suggestedAction?: string;
40
+ docUrl?: string;
41
+ }
42
+ export interface LAFSPageCursor {
43
+ mode: "cursor";
44
+ nextCursor: string | null;
45
+ hasMore: boolean;
46
+ limit?: number;
47
+ total?: number | null;
48
+ }
49
+ export interface LAFSPageOffset {
50
+ mode: "offset";
51
+ limit: number;
52
+ offset: number;
53
+ hasMore: boolean;
54
+ total?: number | null;
55
+ }
56
+ export interface LAFSPageNone {
57
+ mode: "none";
58
+ }
59
+ export type LAFSPage = LAFSPageCursor | LAFSPageOffset | LAFSPageNone;
60
+ export interface ContextLedgerEntry {
61
+ entryId: string;
62
+ timestamp: string;
63
+ operation: string;
64
+ contextDelta: Record<string, unknown>;
65
+ requestId?: string;
66
+ }
67
+ export interface ContextLedger {
68
+ ledgerId: string;
69
+ version: number;
70
+ createdAt: string;
71
+ updatedAt: string;
72
+ entries: ContextLedgerEntry[];
73
+ checksum: string;
74
+ maxEntries: number;
75
+ }
76
+ export interface LAFSEnvelope {
77
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json";
78
+ _meta: LAFSMeta;
79
+ success: boolean;
80
+ result: Record<string, unknown> | Record<string, unknown>[] | null;
81
+ error?: LAFSError | null;
82
+ page?: LAFSPage | null;
83
+ _extensions?: Record<string, unknown>;
84
+ }
85
+ export interface FlagInput {
86
+ requestedFormat?: "json" | "human";
87
+ jsonFlag?: boolean;
88
+ humanFlag?: boolean;
89
+ projectDefault?: "json" | "human";
90
+ userDefault?: "json" | "human";
91
+ /**
92
+ * When true, indicates the output is connected to an interactive terminal.
93
+ * If no explicit format flag or project/user default is set, TTY terminals
94
+ * default to `"human"` format while non-TTY (piped, CI, agents) defaults
95
+ * to `"json"` per the LAFS protocol.
96
+ *
97
+ * CLI tools should pass `process.stdout.isTTY ?? false` here.
98
+ */
99
+ tty?: boolean;
100
+ /** Suppress non-essential output for scripting. When true, only essential data is returned. */
101
+ quiet?: boolean;
102
+ }
103
+ export interface ConformanceReport {
104
+ ok: boolean;
105
+ checks: Array<{
106
+ name: string;
107
+ pass: boolean;
108
+ detail?: string;
109
+ }>;
110
+ }
111
+ export type BudgetEnforcementOptions = {
112
+ truncateOnExceed?: boolean;
113
+ onBudgetExceeded?: (estimated: number, budget: number) => void;
114
+ };
115
+ export interface TokenEstimate {
116
+ estimated: number;
117
+ truncated?: boolean;
118
+ originalEstimate?: number;
119
+ }
120
+ export interface LAFSMetaWithBudget extends LAFSMeta {
121
+ _tokenEstimate?: TokenEstimate;
122
+ }
123
+ export interface LAFSEnvelopeWithBudget extends Omit<LAFSEnvelope, '_meta'> {
124
+ _meta: LAFSMetaWithBudget;
125
+ }
126
+ export type MiddlewareFunction = (envelope: LAFSEnvelope) => LAFSEnvelope | Promise<LAFSEnvelope>;
127
+ export type NextFunction = () => LAFSEnvelope | Promise<LAFSEnvelope>;
128
+ export type BudgetMiddleware = (envelope: LAFSEnvelope, next: NextFunction) => Promise<LAFSEnvelope> | LAFSEnvelope;
129
+ export interface BudgetEnforcementResult {
130
+ envelope: LAFSEnvelope;
131
+ withinBudget: boolean;
132
+ estimatedTokens: number;
133
+ budget: number;
134
+ truncated: boolean;
135
+ }
@@ -0,0 +1,12 @@
1
+ export const MVI_LEVELS = new Set([
2
+ 'minimal', 'standard', 'full', 'custom',
3
+ ]);
4
+ export function isMVILevel(value) {
5
+ return typeof value === 'string' && MVI_LEVELS.has(value);
6
+ }
7
+ export const AGENT_ACTIONS = new Set([
8
+ 'retry', 'retry_modified', 'escalate', 'stop', 'wait', 'refresh_context', 'authenticate',
9
+ ]);
10
+ export function isAgentAction(value) {
11
+ return typeof value === 'string' && AGENT_ACTIONS.has(value);
12
+ }
@@ -0,0 +1,15 @@
1
+ import type { LAFSEnvelope } from "./types.js";
2
+ /** Structured representation of a single validation error from AJV */
3
+ export interface StructuredValidationError {
4
+ path: string;
5
+ keyword: string;
6
+ message: string;
7
+ params: Record<string, unknown>;
8
+ }
9
+ export interface EnvelopeValidationResult {
10
+ valid: boolean;
11
+ errors: string[];
12
+ structuredErrors: StructuredValidationError[];
13
+ }
14
+ export declare function validateEnvelope(input: unknown): EnvelopeValidationResult;
15
+ export declare function assertEnvelope(input: unknown): LAFSEnvelope;
@@ -0,0 +1,31 @@
1
+ import { createRequire } from "node:module";
2
+ import envelopeSchema from "../schemas/v1/envelope.schema.json" with { type: "json" };
3
+ const require = createRequire(import.meta.url);
4
+ const AjvModule = require("ajv");
5
+ const AddFormatsModule = require("ajv-formats");
6
+ const AjvCtor = (typeof AjvModule === "function" ? AjvModule : AjvModule.default);
7
+ const addFormats = (typeof AddFormatsModule === "function" ? AddFormatsModule : AddFormatsModule.default);
8
+ const ajv = new AjvCtor({ allErrors: true, strict: true, allowUnionTypes: true });
9
+ addFormats(ajv);
10
+ const validate = ajv.compile(envelopeSchema);
11
+ export function validateEnvelope(input) {
12
+ const valid = validate(input);
13
+ if (valid) {
14
+ return { valid: true, errors: [], structuredErrors: [] };
15
+ }
16
+ const structuredErrors = (validate.errors ?? []).map((error) => ({
17
+ path: error.instancePath || "/",
18
+ keyword: error.keyword ?? "unknown",
19
+ message: error.message ?? "validation error",
20
+ params: error.params ?? {},
21
+ }));
22
+ const errors = structuredErrors.map((se) => `${se.path} ${se.message}`.trim());
23
+ return { valid: false, errors, structuredErrors };
24
+ }
25
+ export function assertEnvelope(input) {
26
+ const result = validateEnvelope(input);
27
+ if (!result.valid) {
28
+ throw new Error(`Invalid LAFS envelope: ${result.errors.join("; ")}`);
29
+ }
30
+ return input;
31
+ }