@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,220 @@
1
+ /**
2
+ * LAFS Health Check Module
3
+ *
4
+ * Provides health check endpoints for monitoring and orchestration
5
+ */
6
+ import { createRequire } from 'node:module';
7
+ const require = createRequire(import.meta.url);
8
+ let pkg;
9
+ try {
10
+ pkg = require('../../package.json');
11
+ }
12
+ catch {
13
+ pkg = require('../../../package.json');
14
+ }
15
+ /**
16
+ * Health check middleware for Express applications
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import express from 'express';
21
+ * import { healthCheck } from '@cleocode/lafs/health';
22
+ *
23
+ * const app = express();
24
+ *
25
+ * // Basic health check
26
+ * app.use('/health', healthCheck());
27
+ *
28
+ * // Custom health checks
29
+ * app.use('/health', healthCheck({
30
+ * checks: [
31
+ * async () => ({
32
+ * name: 'database',
33
+ * status: await checkDatabase() ? 'ok' : 'error'
34
+ * })
35
+ * ]
36
+ * }));
37
+ * ```
38
+ */
39
+ export function healthCheck(config = {}) {
40
+ const { path = '/health', checks = [] } = config;
41
+ const startTime = Date.now();
42
+ return async (req, res) => {
43
+ const timestamp = new Date().toISOString();
44
+ const checkResults = [];
45
+ // Run all health checks
46
+ for (const check of checks) {
47
+ const start = Date.now();
48
+ try {
49
+ const result = await check();
50
+ result.duration = Date.now() - start;
51
+ checkResults.push(result);
52
+ }
53
+ catch (error) {
54
+ checkResults.push({
55
+ name: 'unknown',
56
+ status: 'error',
57
+ message: error instanceof Error ? error.message : 'Check failed',
58
+ duration: Date.now() - start
59
+ });
60
+ }
61
+ }
62
+ // Add default checks
63
+ checkResults.push({
64
+ name: 'envelopeValidation',
65
+ status: 'ok'
66
+ });
67
+ checkResults.push({
68
+ name: 'tokenBudgets',
69
+ status: 'ok'
70
+ });
71
+ // Determine overall status
72
+ const hasErrors = checkResults.some(c => c.status === 'error');
73
+ const hasWarnings = checkResults.some(c => c.status === 'warning');
74
+ const status = hasErrors
75
+ ? 'unhealthy'
76
+ : hasWarnings
77
+ ? 'degraded'
78
+ : 'healthy';
79
+ const health = {
80
+ status,
81
+ timestamp,
82
+ version: pkg.version,
83
+ uptime: Math.floor((Date.now() - startTime) / 1000),
84
+ checks: checkResults
85
+ };
86
+ const statusCode = status === 'healthy' ? 200 : status === 'degraded' ? 200 : 503;
87
+ res.status(statusCode).json(health);
88
+ };
89
+ }
90
+ /**
91
+ * Create a database health check
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * const dbCheck = createDatabaseHealthCheck({
96
+ * checkConnection: async () => await db.ping()
97
+ * });
98
+ *
99
+ * app.use('/health', healthCheck({
100
+ * checks: [dbCheck]
101
+ * }));
102
+ * ```
103
+ */
104
+ export function createDatabaseHealthCheck(config) {
105
+ return async () => {
106
+ try {
107
+ const isConnected = await config.checkConnection();
108
+ return {
109
+ name: config.name || 'database',
110
+ status: isConnected ? 'ok' : 'error',
111
+ message: isConnected ? 'Connected' : 'Connection failed'
112
+ };
113
+ }
114
+ catch (error) {
115
+ return {
116
+ name: config.name || 'database',
117
+ status: 'error',
118
+ message: error instanceof Error ? error.message : 'Database check failed'
119
+ };
120
+ }
121
+ };
122
+ }
123
+ /**
124
+ * Create an external service health check
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const apiCheck = createExternalServiceHealthCheck({
129
+ * name: 'payment-api',
130
+ * url: 'https://api.payment.com/health',
131
+ * timeout: 5000
132
+ * });
133
+ * ```
134
+ */
135
+ export function createExternalServiceHealthCheck(config) {
136
+ return async () => {
137
+ const start = Date.now();
138
+ try {
139
+ const controller = new AbortController();
140
+ const timeout = setTimeout(() => controller.abort(), config.timeout || 5000);
141
+ const response = await fetch(config.url, {
142
+ signal: controller.signal
143
+ });
144
+ clearTimeout(timeout);
145
+ return {
146
+ name: config.name,
147
+ status: response.ok ? 'ok' : 'error',
148
+ message: response.ok ? 'Service healthy' : `HTTP ${response.status}`,
149
+ duration: Date.now() - start
150
+ };
151
+ }
152
+ catch (error) {
153
+ return {
154
+ name: config.name,
155
+ status: 'error',
156
+ message: error instanceof Error ? error.message : 'Service unreachable',
157
+ duration: Date.now() - start
158
+ };
159
+ }
160
+ };
161
+ }
162
+ /**
163
+ * Liveness probe - basic check that service is running
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * app.get('/health/live', livenessProbe());
168
+ * ```
169
+ */
170
+ export function livenessProbe() {
171
+ return (req, res) => {
172
+ res.status(200).json({
173
+ status: 'alive',
174
+ timestamp: new Date().toISOString()
175
+ });
176
+ };
177
+ }
178
+ /**
179
+ * Readiness probe - check that service is ready to accept traffic
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * app.get('/health/ready', readinessProbe({
184
+ * checks: [dbCheck, cacheCheck]
185
+ * }));
186
+ * ```
187
+ */
188
+ export function readinessProbe(config = {}) {
189
+ return async (req, res) => {
190
+ const checkResults = [];
191
+ if (config.checks) {
192
+ for (const check of config.checks) {
193
+ try {
194
+ const result = await check();
195
+ checkResults.push(result);
196
+ }
197
+ catch (error) {
198
+ checkResults.push({
199
+ name: 'unknown',
200
+ status: 'error',
201
+ message: error instanceof Error ? error.message : 'Check failed'
202
+ });
203
+ }
204
+ }
205
+ }
206
+ const hasErrors = checkResults.some(c => c.status === 'error');
207
+ if (hasErrors) {
208
+ res.status(503).json({
209
+ status: 'not ready',
210
+ checks: checkResults
211
+ });
212
+ }
213
+ else {
214
+ res.status(200).json({
215
+ status: 'ready',
216
+ checks: checkResults
217
+ });
218
+ }
219
+ };
220
+ }
@@ -0,0 +1,24 @@
1
+ export * from "./types.js";
2
+ export * from "./errorRegistry.js";
3
+ export * from "./deprecationRegistry.js";
4
+ export * from "./validateEnvelope.js";
5
+ export * from "./envelope.js";
6
+ export * from "./flagSemantics.js";
7
+ export * from "./fieldExtraction.js";
8
+ export * from "./flagResolver.js";
9
+ export * from "./mviProjection.js";
10
+ export * from "./conformance.js";
11
+ export * from "./conformanceProfiles.js";
12
+ export * from "./compliance.js";
13
+ export * from "./tokenEstimator.js";
14
+ export * from "./budgetEnforcement.js";
15
+ export * from "./mcpAdapter.js";
16
+ export * from "./discovery.js";
17
+ export { lafsErrorToProblemDetails, PROBLEM_DETAILS_CONTENT_TYPE } from './problemDetails.js';
18
+ export type { LafsProblemDetails } from './problemDetails.js';
19
+ export * from "./health/index.js";
20
+ export * from "./shutdown/index.js";
21
+ export * from "./circuit-breaker/index.js";
22
+ export { LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware, TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskRefinementError, TaskManager, attachLafsEnvelope, TaskEventBus, PushNotificationConfigStore, PushNotificationDispatcher, TaskArtifactAssembler, streamTaskEvents, } from "./a2a/index.js";
23
+ export type { LafsA2AConfig, LafsSendMessageParams, LafsExtensionParams, ExtensionNegotiationResult, BuildLafsExtensionOptions, ExtensionNegotiationMiddlewareOptions, CreateTaskOptions, ListTasksOptions, ListTasksResult, TaskStreamEvent, StreamIteratorOptions, PushNotificationDeliveryResult, PushTransport, } from "./a2a/index.js";
24
+ export type { Task, TaskState, TaskStatus, Artifact, Part, Message, PushNotificationConfig, MessageSendConfiguration, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, SendMessageResponse, SendMessageSuccessResponse, JSONRPCErrorResponse, TextPart, DataPart, FilePart, } from "./a2a/index.js";
@@ -0,0 +1,34 @@
1
+ export * from "./types.js";
2
+ export * from "./errorRegistry.js";
3
+ export * from "./deprecationRegistry.js";
4
+ export * from "./validateEnvelope.js";
5
+ export * from "./envelope.js";
6
+ export * from "./flagSemantics.js";
7
+ export * from "./fieldExtraction.js";
8
+ export * from "./flagResolver.js";
9
+ export * from "./mviProjection.js";
10
+ export * from "./conformance.js";
11
+ export * from "./conformanceProfiles.js";
12
+ export * from "./compliance.js";
13
+ export * from "./tokenEstimator.js";
14
+ export * from "./budgetEnforcement.js";
15
+ export * from "./mcpAdapter.js";
16
+ export * from "./discovery.js";
17
+ export { lafsErrorToProblemDetails, PROBLEM_DETAILS_CONTENT_TYPE } from './problemDetails.js';
18
+ // Operations & Reliability
19
+ export * from "./health/index.js";
20
+ export * from "./shutdown/index.js";
21
+ export * from "./circuit-breaker/index.js";
22
+ // A2A Integration
23
+ // Explicitly re-export to avoid naming conflicts with discovery types
24
+ // (AgentCard, AgentSkill, AgentCapabilities, AgentExtension).
25
+ // For full A2A types, import from '@cleocode/lafs/a2a'.
26
+ export {
27
+ // Bridge
28
+ LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER,
29
+ // Extensions (T098)
30
+ LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware,
31
+ // Task Lifecycle (T099)
32
+ TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskRefinementError, TaskManager, attachLafsEnvelope,
33
+ // Streaming and Async (T101)
34
+ TaskEventBus, PushNotificationConfigStore, PushNotificationDispatcher, TaskArtifactAssembler, streamTaskEvents, } from "./a2a/index.js";
@@ -0,0 +1,28 @@
1
+ import type { CallToolResult, TextContent } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { LAFSEnvelope, LAFSError } from "./types.js";
3
+ /**
4
+ * Wrap MCP tool result in LAFS envelope
5
+ *
6
+ * @param mcpResult - The raw MCP CallToolResult
7
+ * @param operation - The operation name (tool name)
8
+ * @param budget - Optional token budget for response
9
+ * @returns LAFS-compliant envelope
10
+ */
11
+ export declare function wrapMCPResult(mcpResult: CallToolResult, operation: string, budget?: number): LAFSEnvelope;
12
+ /**
13
+ * Create a LAFS error envelope for MCP adapter errors
14
+ *
15
+ * @param message - Error message
16
+ * @param operation - The operation being performed
17
+ * @param category - Error category
18
+ * @returns LAFS error envelope
19
+ */
20
+ export declare function createAdapterErrorEnvelope(message: string, operation: string, category?: LAFSError["category"]): LAFSEnvelope;
21
+ /**
22
+ * Type guard to check if content is TextContent
23
+ */
24
+ export declare function isTextContent(content: unknown): content is TextContent;
25
+ /**
26
+ * Parse MCP text content as JSON if possible
27
+ */
28
+ export declare function parseMCPTextContent(content: TextContent): unknown;
@@ -0,0 +1,281 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * Extract result from MCP content array
4
+ * Attempts to parse JSON content, falls back to text representation
5
+ */
6
+ function extractResultFromContent(content) {
7
+ if (!content || content.length === 0) {
8
+ return null;
9
+ }
10
+ // Combine all text content
11
+ const textParts = [];
12
+ const otherContent = [];
13
+ for (const item of content) {
14
+ if (item.type === "text" && item.text) {
15
+ textParts.push(item.text);
16
+ }
17
+ else {
18
+ otherContent.push(item);
19
+ }
20
+ }
21
+ // Try to parse as JSON if single text item looks like JSON
22
+ if (textParts.length === 1) {
23
+ const text = textParts[0]?.trim() ?? "";
24
+ if (text.startsWith("{") || text.startsWith("[")) {
25
+ try {
26
+ const parsed = JSON.parse(text);
27
+ if (typeof parsed === "object" && parsed !== null) {
28
+ return parsed;
29
+ }
30
+ }
31
+ catch {
32
+ // Not valid JSON, treat as text
33
+ }
34
+ }
35
+ }
36
+ // Combine into result object
37
+ const result = {};
38
+ if (textParts.length > 0) {
39
+ result.text = textParts.length === 1 ? textParts[0] : textParts.join("\n");
40
+ }
41
+ if (otherContent.length > 0) {
42
+ result.content = otherContent;
43
+ }
44
+ return result;
45
+ }
46
+ /**
47
+ * Estimate token count from content
48
+ * Rough estimation: ~4 characters per token
49
+ */
50
+ function estimateTokens(content) {
51
+ if (!content)
52
+ return 0;
53
+ const jsonString = JSON.stringify(content);
54
+ return Math.ceil(jsonString.length / 4);
55
+ }
56
+ /**
57
+ * Truncate content to fit within budget
58
+ */
59
+ function truncateToBudget(content, budget) {
60
+ if (!content)
61
+ return { result: null, truncated: false, originalEstimate: 0 };
62
+ const originalEstimate = estimateTokens(content);
63
+ if (originalEstimate <= budget) {
64
+ return { result: content, truncated: false, originalEstimate };
65
+ }
66
+ // Calculate truncation ratio
67
+ const ratio = budget / originalEstimate;
68
+ const jsonString = JSON.stringify(content);
69
+ const targetLength = Math.floor(jsonString.length * ratio);
70
+ // Truncate the string and try to make it valid JSON
71
+ let truncated = jsonString.slice(0, targetLength);
72
+ // Close any open structures
73
+ const openBraces = (truncated.match(/\{/g) || []).length - (truncated.match(/\}/g) || []).length;
74
+ const openBrackets = (truncated.match(/\[/g) || []).length - (truncated.match(/\]/g) || []).length;
75
+ truncated += "}".repeat(Math.max(0, openBraces));
76
+ truncated += "]".repeat(Math.max(0, openBrackets));
77
+ try {
78
+ const parsed = JSON.parse(truncated);
79
+ // Add truncation notice
80
+ if (typeof parsed === "object" && parsed !== null) {
81
+ parsed["_truncated"] = true;
82
+ parsed["_originalTokens"] = originalEstimate;
83
+ parsed["_budget"] = budget;
84
+ }
85
+ return { result: parsed, truncated: true, originalEstimate };
86
+ }
87
+ catch {
88
+ // If parsing fails, return minimal result
89
+ return {
90
+ result: {
91
+ _truncated: true,
92
+ _error: "Content truncated due to budget constraints",
93
+ _originalTokens: originalEstimate,
94
+ _budget: budget,
95
+ },
96
+ truncated: true,
97
+ originalEstimate,
98
+ };
99
+ }
100
+ }
101
+ /**
102
+ * Convert MCP error to LAFS error format
103
+ */
104
+ function convertMCPErrorToLAFS(mcpResult, operation) {
105
+ const content = mcpResult.content;
106
+ const errorText = content
107
+ .filter((item) => item.type === "text" && typeof item.text === "string")
108
+ .map((item) => item.text)
109
+ .join("\n");
110
+ // Determine error category based on error text
111
+ let category = "INTERNAL";
112
+ let code = "E_MCP_INTERNAL_ERROR";
113
+ let retryable = false;
114
+ let retryAfterMs = null;
115
+ const errorLower = errorText.toLowerCase();
116
+ if (errorLower.includes("not found") || errorLower.includes("doesn't exist") || errorLower.includes("does not exist")) {
117
+ category = "NOT_FOUND";
118
+ code = "E_MCP_NOT_FOUND";
119
+ }
120
+ else if (errorLower.includes("rate limit") || errorLower.includes("too many requests")) {
121
+ category = "RATE_LIMIT";
122
+ code = "E_MCP_RATE_LIMIT";
123
+ retryable = true;
124
+ retryAfterMs = 60000; // 1 minute default
125
+ }
126
+ else if (errorLower.includes("auth") || errorLower.includes("unauthorized") || errorLower.includes("forbidden")) {
127
+ category = "AUTH";
128
+ code = "E_MCP_AUTH_ERROR";
129
+ }
130
+ else if (errorLower.includes("permission") || errorLower.includes("access denied")) {
131
+ category = "PERMISSION";
132
+ code = "E_MCP_PERMISSION_DENIED";
133
+ }
134
+ else if (errorLower.includes("validation") || errorLower.includes("invalid")) {
135
+ category = "VALIDATION";
136
+ code = "E_MCP_VALIDATION_ERROR";
137
+ }
138
+ else if (errorLower.includes("timeout") || errorLower.includes("transient")) {
139
+ category = "TRANSIENT";
140
+ code = "E_MCP_TRANSIENT_ERROR";
141
+ retryable = true;
142
+ retryAfterMs = 5000; // 5 seconds
143
+ }
144
+ return {
145
+ code,
146
+ message: errorText || "MCP tool execution failed",
147
+ category,
148
+ retryable,
149
+ retryAfterMs,
150
+ details: {
151
+ operation,
152
+ mcpError: true,
153
+ contentTypes: content.map((c) => c.type),
154
+ },
155
+ };
156
+ }
157
+ /**
158
+ * Wrap MCP tool result in LAFS envelope
159
+ *
160
+ * @param mcpResult - The raw MCP CallToolResult
161
+ * @param operation - The operation name (tool name)
162
+ * @param budget - Optional token budget for response
163
+ * @returns LAFS-compliant envelope
164
+ */
165
+ export function wrapMCPResult(mcpResult, operation, budget) {
166
+ const requestId = randomUUID();
167
+ const timestamp = new Date().toISOString();
168
+ // Build base meta
169
+ const meta = {
170
+ specVersion: "1.0.0",
171
+ schemaVersion: "1.0.0",
172
+ timestamp,
173
+ operation,
174
+ requestId,
175
+ transport: "sdk",
176
+ strict: true,
177
+ mvi: "standard",
178
+ contextVersion: 1,
179
+ };
180
+ // Handle MCP error
181
+ if (mcpResult.isError) {
182
+ const error = convertMCPErrorToLAFS(mcpResult, operation);
183
+ return {
184
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
185
+ _meta: meta,
186
+ success: false,
187
+ result: null,
188
+ error,
189
+ };
190
+ }
191
+ // Extract result from MCP content
192
+ let result = extractResultFromContent(mcpResult.content);
193
+ let truncated = false;
194
+ let originalEstimate = 0;
195
+ let extensions;
196
+ // Apply budget enforcement if specified
197
+ if (budget !== undefined && budget > 0) {
198
+ const budgetResult = truncateToBudget(result, budget);
199
+ result = budgetResult.result;
200
+ truncated = budgetResult.truncated;
201
+ originalEstimate = budgetResult.originalEstimate;
202
+ // Put token estimate in extensions to comply with strict schema
203
+ extensions = {
204
+ "x-mcp-token-estimate": {
205
+ estimated: truncated ? budget : originalEstimate,
206
+ truncated,
207
+ originalEstimate: truncated ? originalEstimate : undefined,
208
+ },
209
+ };
210
+ }
211
+ return {
212
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
213
+ _meta: meta,
214
+ success: true,
215
+ result,
216
+ error: null,
217
+ _extensions: extensions,
218
+ };
219
+ }
220
+ /**
221
+ * Create a LAFS error envelope for MCP adapter errors
222
+ *
223
+ * @param message - Error message
224
+ * @param operation - The operation being performed
225
+ * @param category - Error category
226
+ * @returns LAFS error envelope
227
+ */
228
+ export function createAdapterErrorEnvelope(message, operation, category = "INTERNAL") {
229
+ const requestId = randomUUID();
230
+ const timestamp = new Date().toISOString();
231
+ const error = {
232
+ code: "E_MCP_ADAPTER_ERROR",
233
+ message,
234
+ category,
235
+ retryable: category === "TRANSIENT" || category === "RATE_LIMIT",
236
+ retryAfterMs: category === "RATE_LIMIT" ? 60000 : category === "TRANSIENT" ? 5000 : null,
237
+ details: {
238
+ operation,
239
+ adapterError: true,
240
+ },
241
+ };
242
+ return {
243
+ $schema: "https://lafs.dev/schemas/v1/envelope.schema.json",
244
+ _meta: {
245
+ specVersion: "1.0.0",
246
+ schemaVersion: "1.0.0",
247
+ timestamp,
248
+ operation,
249
+ requestId,
250
+ transport: "sdk",
251
+ strict: true,
252
+ mvi: "standard",
253
+ contextVersion: 1,
254
+ },
255
+ success: false,
256
+ result: null,
257
+ error,
258
+ };
259
+ }
260
+ /**
261
+ * Type guard to check if content is TextContent
262
+ */
263
+ export function isTextContent(content) {
264
+ return (typeof content === "object" &&
265
+ content !== null &&
266
+ "type" in content &&
267
+ content.type === "text" &&
268
+ "text" in content &&
269
+ typeof content.text === "string");
270
+ }
271
+ /**
272
+ * Parse MCP text content as JSON if possible
273
+ */
274
+ export function parseMCPTextContent(content) {
275
+ try {
276
+ return JSON.parse(content.text);
277
+ }
278
+ catch {
279
+ return content.text;
280
+ }
281
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * MVI-aware envelope projection.
3
+ * Strips fields based on declared MVI level to reduce token cost.
4
+ * At 'minimal': ~38 tokens per error (vs ~162 at 'full').
5
+ */
6
+ import type { LAFSEnvelope, MVILevel } from './types.js';
7
+ /**
8
+ * Project an envelope to the declared MVI verbosity level.
9
+ * - 'minimal': Only fields required for agent control flow
10
+ * - 'standard': All commonly useful fields (current default behavior)
11
+ * - 'full': Complete echo-back including request parameters
12
+ * - 'custom': No projection (controlled by _fields)
13
+ */
14
+ export declare function projectEnvelope(envelope: LAFSEnvelope, mviLevel?: MVILevel): Record<string, unknown>;
15
+ /**
16
+ * Estimate token count for a projected envelope.
17
+ * Uses simple heuristic: 1 token per ~4 characters of JSON.
18
+ */
19
+ export declare function estimateProjectedTokens(projected: Record<string, unknown>): number;