@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 +31 -4
- package/dist/src/compliance.d.ts +31 -0
- package/dist/src/compliance.js +89 -0
- package/dist/src/envelope.d.ts +46 -0
- package/dist/src/envelope.js +89 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/lafs.md +2 -3
- package/package.json +6 -2
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.
|
|
7
|
+
**Current version:** 1.4.0 | [📚 Documentation](https://codluv.gitbook.io/lafs-protocol/) | [Spec](lafs.md) | [Migration Guides](migrations/)
|
|
8
8
|
|
|
9
9
|
[](https://codluv.gitbook.io/lafs-protocol/)
|
|
10
10
|
[](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
|
|
43
|
-
if (!
|
|
44
|
-
console.error(
|
|
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
|
+
}
|
package/dist/src/index.d.ts
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/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.
|
|
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":
|
|
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
|
+
"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",
|