@cleocode/lafs-protocol 1.3.2 → 1.4.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  LAFS defines a standard envelope format for structured responses from LLM-powered agents and tools. It complements transport protocols like [MCP](https://modelcontextprotocol.io/) and [A2A](https://github.com/google/A2A) by standardizing what comes back — not how it gets there.
6
6
 
7
- **Current version:** 1.3.1 | [📚 Documentation](https://codluv.gitbook.io/lafs-protocol/) | [Spec](lafs.md) | [Migration Guides](migrations/)
7
+ **Current version:** 1.4.0 | [📚 Documentation](https://codluv.gitbook.io/lafs-protocol/) | [Spec](lafs.md) | [Migration Guides](migrations/)
8
8
 
9
9
  [![GitBook](https://img.shields.io/badge/docs-gitbook-blue)](https://codluv.gitbook.io/lafs-protocol/)
10
10
  [![npm](https://img.shields.io/npm/v/@cleocode/lafs-protocol)](https://www.npmjs.com/package/@cleocode/lafs-protocol)
@@ -33,15 +33,35 @@ npm install @cleocode/lafs-protocol
33
33
 
34
34
  ```typescript
35
35
  import {
36
+ createEnvelope,
37
+ parseLafsResponse,
38
+ LafsError,
36
39
  validateEnvelope,
37
40
  runEnvelopeConformance,
38
41
  isRegisteredErrorCode,
39
42
  } from "@cleocode/lafs-protocol";
40
43
 
44
+ // Build envelope with defaults
45
+ const envelope = createEnvelope({
46
+ success: true,
47
+ result: { items: [] },
48
+ meta: { operation: "example.list", requestId: "req_1" },
49
+ });
50
+
41
51
  // Validate an envelope against the schema
42
- const result = validateEnvelope(envelope);
43
- if (!result.valid) {
44
- console.error(result.errors);
52
+ const validation = validateEnvelope(envelope);
53
+ if (!validation.valid) {
54
+ console.error(validation.errors);
55
+ }
56
+
57
+ // Parse envelope responses with one function
58
+ try {
59
+ const parsed = parseLafsResponse(envelope);
60
+ console.log(parsed);
61
+ } catch (error) {
62
+ if (error instanceof LafsError) {
63
+ console.error(error.code, error.message);
64
+ }
45
65
  }
46
66
 
47
67
  // Run full conformance suite (schema + invariants + error codes + strict mode + pagination)
@@ -49,6 +69,13 @@ const report = runEnvelopeConformance(envelope);
49
69
  console.log(report.ok); // true if all checks pass
50
70
  ```
51
71
 
72
+ ## LLM-agent implementation guides
73
+
74
+ - `docs/guides/llm-agent-guide.md` - parser, success/error handling, strict JSON policy
75
+ - `docs/guides/schema-extension.md` - operation-specific result validation on top of core schema
76
+ - `docs/guides/compliance-pipeline.md` - generation middleware with validate + conformance gates
77
+ - `docs/llms.txt` - LLM-oriented index and canonical sources
78
+
52
79
  ## CLI
53
80
 
54
81
  ```bash
@@ -0,0 +1,31 @@
1
+ import type { ConformanceReport, FlagInput, LAFSEnvelope } from "./types.js";
2
+ import { type EnvelopeValidationResult } from "./validateEnvelope.js";
3
+ export type ComplianceStage = "schema" | "envelope" | "flags" | "format";
4
+ export interface ComplianceIssue {
5
+ stage: ComplianceStage;
6
+ message: string;
7
+ detail?: string;
8
+ }
9
+ export interface EnforceComplianceOptions {
10
+ checkConformance?: boolean;
11
+ checkFlags?: boolean;
12
+ flags?: FlagInput;
13
+ requireJsonOutput?: boolean;
14
+ }
15
+ export interface ComplianceResult {
16
+ ok: boolean;
17
+ envelope?: LAFSEnvelope;
18
+ validation: EnvelopeValidationResult;
19
+ envelopeConformance?: ConformanceReport;
20
+ flagConformance?: ConformanceReport;
21
+ issues: ComplianceIssue[];
22
+ }
23
+ export declare class ComplianceError extends Error {
24
+ readonly issues: ComplianceIssue[];
25
+ constructor(issues: ComplianceIssue[]);
26
+ }
27
+ export declare function enforceCompliance(input: unknown, options?: EnforceComplianceOptions): ComplianceResult;
28
+ export declare function assertCompliance(input: unknown, options?: EnforceComplianceOptions): LAFSEnvelope;
29
+ export declare function withCompliance<TArgs extends unknown[], TResult extends LAFSEnvelope>(producer: (...args: TArgs) => TResult | Promise<TResult>, options?: EnforceComplianceOptions): (...args: TArgs) => Promise<LAFSEnvelope>;
30
+ export type ComplianceMiddleware = (envelope: LAFSEnvelope, next: () => LAFSEnvelope | Promise<LAFSEnvelope>) => Promise<LAFSEnvelope> | LAFSEnvelope;
31
+ export declare function createComplianceMiddleware(options?: EnforceComplianceOptions): ComplianceMiddleware;
@@ -0,0 +1,89 @@
1
+ import { runEnvelopeConformance, runFlagConformance } from "./conformance.js";
2
+ import { resolveOutputFormat } from "./flagSemantics.js";
3
+ import { assertEnvelope, validateEnvelope } from "./validateEnvelope.js";
4
+ export class ComplianceError extends Error {
5
+ issues;
6
+ constructor(issues) {
7
+ super(`LAFS compliance failed: ${issues.map((issue) => issue.message).join("; ")}`);
8
+ this.name = "ComplianceError";
9
+ this.issues = issues;
10
+ }
11
+ }
12
+ function conformanceIssues(report, stage) {
13
+ return report.checks
14
+ .filter((check) => !check.pass)
15
+ .map((check) => ({
16
+ stage,
17
+ message: `${check.name} failed`,
18
+ detail: check.detail,
19
+ }));
20
+ }
21
+ export function enforceCompliance(input, options = {}) {
22
+ const { checkConformance = true, checkFlags = false, flags, requireJsonOutput = false, } = options;
23
+ const issues = [];
24
+ const validation = validateEnvelope(input);
25
+ if (!validation.valid) {
26
+ issues.push(...validation.errors.map((error) => ({
27
+ stage: "schema",
28
+ message: "schema validation failed",
29
+ detail: error,
30
+ })));
31
+ return {
32
+ ok: false,
33
+ validation,
34
+ issues,
35
+ };
36
+ }
37
+ const envelope = assertEnvelope(input);
38
+ let envelopeConformance;
39
+ if (checkConformance) {
40
+ envelopeConformance = runEnvelopeConformance(envelope);
41
+ if (!envelopeConformance.ok) {
42
+ issues.push(...conformanceIssues(envelopeConformance, "envelope"));
43
+ }
44
+ }
45
+ let flagConformance;
46
+ if (checkFlags && flags) {
47
+ flagConformance = runFlagConformance(flags);
48
+ if (!flagConformance.ok) {
49
+ issues.push(...conformanceIssues(flagConformance, "flags"));
50
+ }
51
+ }
52
+ if (requireJsonOutput) {
53
+ const resolved = resolveOutputFormat(flags ?? {});
54
+ if (resolved.format !== "json") {
55
+ issues.push({
56
+ stage: "format",
57
+ message: "non-json output format resolved",
58
+ detail: `resolved format is ${resolved.format}`,
59
+ });
60
+ }
61
+ }
62
+ return {
63
+ ok: issues.length === 0,
64
+ envelope,
65
+ validation,
66
+ envelopeConformance,
67
+ flagConformance,
68
+ issues,
69
+ };
70
+ }
71
+ export function assertCompliance(input, options = {}) {
72
+ const result = enforceCompliance(input, options);
73
+ if (!result.ok || !result.envelope) {
74
+ throw new ComplianceError(result.issues);
75
+ }
76
+ return result.envelope;
77
+ }
78
+ export function withCompliance(producer, options = {}) {
79
+ return async (...args) => {
80
+ const envelope = await producer(...args);
81
+ return assertCompliance(envelope, options);
82
+ };
83
+ }
84
+ export function createComplianceMiddleware(options = {}) {
85
+ return async (_envelope, next) => {
86
+ const candidate = await next();
87
+ return assertCompliance(candidate, options);
88
+ };
89
+ }
@@ -0,0 +1,46 @@
1
+ import type { LAFSEnvelope, LAFSError, LAFSErrorCategory, LAFSMeta, LAFSTransport, MVILevel } from "./types.js";
2
+ export declare const LAFS_SCHEMA_URL: "https://lafs.dev/schemas/v1/envelope.schema.json";
3
+ export interface CreateEnvelopeMetaInput {
4
+ operation: string;
5
+ requestId: string;
6
+ transport?: LAFSTransport;
7
+ specVersion?: string;
8
+ schemaVersion?: string;
9
+ timestamp?: string;
10
+ strict?: boolean;
11
+ mvi?: MVILevel | boolean;
12
+ contextVersion?: number;
13
+ sessionId?: string;
14
+ warnings?: LAFSMeta["warnings"];
15
+ }
16
+ export interface CreateEnvelopeSuccessInput {
17
+ success: true;
18
+ result: LAFSEnvelope["result"];
19
+ page?: LAFSEnvelope["page"];
20
+ error?: null;
21
+ _extensions?: LAFSEnvelope["_extensions"];
22
+ meta: CreateEnvelopeMetaInput;
23
+ }
24
+ export interface CreateEnvelopeErrorInput {
25
+ success: false;
26
+ error: Partial<LAFSError> & Pick<LAFSError, "code" | "message">;
27
+ result?: null;
28
+ page?: LAFSEnvelope["page"];
29
+ _extensions?: LAFSEnvelope["_extensions"];
30
+ meta: CreateEnvelopeMetaInput;
31
+ }
32
+ export type CreateEnvelopeInput = CreateEnvelopeSuccessInput | CreateEnvelopeErrorInput;
33
+ export declare function createEnvelope(input: CreateEnvelopeInput): LAFSEnvelope;
34
+ export declare class LafsError extends Error implements LAFSError {
35
+ code: string;
36
+ category: LAFSErrorCategory;
37
+ retryable: boolean;
38
+ retryAfterMs: number | null;
39
+ details: Record<string, unknown>;
40
+ registered: boolean;
41
+ constructor(error: LAFSError);
42
+ }
43
+ export interface ParseLafsResponseOptions {
44
+ requireRegisteredErrorCode?: boolean;
45
+ }
46
+ export declare function parseLafsResponse<T = unknown>(input: unknown, options?: ParseLafsResponseOptions): T;
@@ -0,0 +1,89 @@
1
+ import { isRegisteredErrorCode } from "./errorRegistry.js";
2
+ import { assertEnvelope } from "./validateEnvelope.js";
3
+ export const LAFS_SCHEMA_URL = "https://lafs.dev/schemas/v1/envelope.schema.json";
4
+ function resolveMviLevel(input) {
5
+ if (typeof input === "boolean") {
6
+ return input ? "minimal" : "standard";
7
+ }
8
+ return input ?? "standard";
9
+ }
10
+ function createMeta(input) {
11
+ return {
12
+ specVersion: input.specVersion ?? "1.0.0",
13
+ schemaVersion: input.schemaVersion ?? "1.0.0",
14
+ timestamp: input.timestamp ?? new Date().toISOString(),
15
+ operation: input.operation,
16
+ requestId: input.requestId,
17
+ transport: input.transport ?? "sdk",
18
+ strict: input.strict ?? true,
19
+ mvi: resolveMviLevel(input.mvi),
20
+ contextVersion: input.contextVersion ?? 0,
21
+ ...(input.sessionId ? { sessionId: input.sessionId } : {}),
22
+ ...(input.warnings ? { warnings: input.warnings } : {}),
23
+ };
24
+ }
25
+ function normalizeError(error) {
26
+ return {
27
+ code: error.code,
28
+ message: error.message,
29
+ category: (error.category ?? "INTERNAL"),
30
+ retryable: error.retryable ?? false,
31
+ retryAfterMs: error.retryAfterMs ?? null,
32
+ details: error.details ?? {},
33
+ };
34
+ }
35
+ export function createEnvelope(input) {
36
+ const meta = createMeta(input.meta);
37
+ if (input.success) {
38
+ return {
39
+ $schema: LAFS_SCHEMA_URL,
40
+ _meta: meta,
41
+ success: true,
42
+ result: input.result,
43
+ ...(input.page !== undefined ? { page: input.page } : {}),
44
+ ...(input.error !== undefined ? { error: null } : {}),
45
+ ...(input._extensions !== undefined ? { _extensions: input._extensions } : {}),
46
+ };
47
+ }
48
+ return {
49
+ $schema: LAFS_SCHEMA_URL,
50
+ _meta: meta,
51
+ success: false,
52
+ result: null,
53
+ error: normalizeError(input.error),
54
+ ...(input.page !== undefined ? { page: input.page } : {}),
55
+ ...(input._extensions !== undefined ? { _extensions: input._extensions } : {}),
56
+ };
57
+ }
58
+ export class LafsError extends Error {
59
+ code;
60
+ category;
61
+ retryable;
62
+ retryAfterMs;
63
+ details;
64
+ registered;
65
+ constructor(error) {
66
+ super(error.message);
67
+ this.name = "LafsError";
68
+ this.code = error.code;
69
+ this.category = error.category;
70
+ this.retryable = error.retryable;
71
+ this.retryAfterMs = error.retryAfterMs;
72
+ this.details = error.details;
73
+ this.registered = isRegisteredErrorCode(error.code);
74
+ }
75
+ }
76
+ export function parseLafsResponse(input, options = {}) {
77
+ const envelope = assertEnvelope(input);
78
+ if (envelope.success) {
79
+ return envelope.result;
80
+ }
81
+ const error = envelope.error;
82
+ if (!error) {
83
+ throw new Error("Invalid LAFS envelope: success=false requires error object");
84
+ }
85
+ if (options.requireRegisteredErrorCode && !isRegisteredErrorCode(error.code)) {
86
+ throw new Error(`Unregistered LAFS error code: ${error.code}`);
87
+ }
88
+ throw new LafsError(error);
89
+ }
@@ -1,8 +1,10 @@
1
1
  export * from "./types.js";
2
2
  export * from "./errorRegistry.js";
3
3
  export * from "./validateEnvelope.js";
4
+ export * from "./envelope.js";
4
5
  export * from "./flagSemantics.js";
5
6
  export * from "./conformance.js";
7
+ export * from "./compliance.js";
6
8
  export * from "./tokenEstimator.js";
7
9
  export * from "./budgetEnforcement.js";
8
10
  export * from "./mcpAdapter.js";
package/dist/src/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  export * from "./types.js";
2
2
  export * from "./errorRegistry.js";
3
3
  export * from "./validateEnvelope.js";
4
+ export * from "./envelope.js";
4
5
  export * from "./flagSemantics.js";
5
6
  export * from "./conformance.js";
7
+ export * from "./compliance.js";
6
8
  export * from "./tokenEstimator.js";
7
9
  export * from "./budgetEnforcement.js";
8
10
  export * from "./mcpAdapter.js";
package/lafs.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # LAFS: LLM-Agent-First Specification
2
2
 
3
3
  > 📚 **Documentation:** https://codluv.gitbook.io/lafs-protocol/
4
- > **Version:** 1.3.1 | **Status:** Production Ready
4
+ > **Version:** 1.4.0 | **Status:** Production Ready
5
5
 
6
6
  ## 1. Scope
7
7
 
@@ -130,7 +130,7 @@ All responses MUST conform to `schemas/v1/envelope.schema.json`.
130
130
  "requestId": "req_123",
131
131
  "transport": "cli",
132
132
  "strict": true,
133
- "mvi": true,
133
+ "mvi": "standard",
134
134
  "contextVersion": 0
135
135
  },
136
136
  "success": true,
@@ -142,7 +142,6 @@ All responses MUST conform to `schemas/v1/envelope.schema.json`.
142
142
 
143
143
  ### 6.1 Envelope invariants
144
144
 
145
- - Exactly one of `result` or `error` MUST be non-null.
146
145
  - `success=true` implies `error=null` or error omitted.
147
146
  - `success=false` implies `result=null` and `error` MUST be present.
148
147
  - The `page` and `error` fields are optional when their value would be null. In strict mode, producers SHOULD omit these fields rather than set them to null.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/lafs-protocol",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "LLM-Agent-First Specification schemas and conformance tooling",
@@ -22,7 +22,11 @@
22
22
  "./a2a/bindings": {
23
23
  "import": "./dist/src/a2a/bindings/index.js",
24
24
  "types": "./dist/src/a2a/bindings/index.d.ts"
25
- }
25
+ },
26
+ "./schemas/v1/envelope.schema.json": "./schemas/v1/envelope.schema.json",
27
+ "./schemas/v1/error-registry.json": "./schemas/v1/error-registry.json",
28
+ "./schemas/v1/context-ledger.schema.json": "./schemas/v1/context-ledger.schema.json",
29
+ "./schemas/v1/discovery.schema.json": "./schemas/v1/discovery.schema.json"
26
30
  },
27
31
  "files": [
28
32
  "dist",