@kadi.build/core 0.0.1-alpha.10 → 0.0.1-alpha.12
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 +269 -1311
- package/dist/abilities/AbilityLoader.d.ts +26 -0
- package/dist/abilities/AbilityLoader.d.ts.map +1 -1
- package/dist/abilities/AbilityLoader.js +141 -18
- package/dist/abilities/AbilityLoader.js.map +1 -1
- package/dist/abilities/AbilityProxy.d.ts +33 -0
- package/dist/abilities/AbilityProxy.d.ts.map +1 -1
- package/dist/abilities/AbilityProxy.js +40 -0
- package/dist/abilities/AbilityProxy.js.map +1 -1
- package/dist/abilities/index.d.ts +1 -1
- package/dist/abilities/index.d.ts.map +1 -1
- package/dist/abilities/types.d.ts +67 -0
- package/dist/abilities/types.d.ts.map +1 -1
- package/dist/broker/BrokerProtocol.js +11 -11
- package/dist/broker/BrokerProtocol.js.map +1 -1
- package/dist/client/KadiClient.d.ts +191 -2
- package/dist/client/KadiClient.d.ts.map +1 -1
- package/dist/client/KadiClient.js +412 -2
- package/dist/client/KadiClient.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/messages/index.d.ts +1 -1
- package/dist/messages/index.js +1 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/zod-helpers.d.ts +129 -0
- package/dist/schemas/zod-helpers.d.ts.map +1 -0
- package/dist/schemas/zod-helpers.js +225 -0
- package/dist/schemas/zod-helpers.js.map +1 -0
- package/dist/schemas/zod-to-json-schema.d.ts +159 -0
- package/dist/schemas/zod-to-json-schema.d.ts.map +1 -0
- package/dist/schemas/zod-to-json-schema.js +154 -0
- package/dist/schemas/zod-to-json-schema.js.map +1 -0
- package/dist/transports/NativeTransport.d.ts +29 -0
- package/dist/transports/NativeTransport.d.ts.map +1 -1
- package/dist/transports/NativeTransport.js +98 -3
- package/dist/transports/NativeTransport.js.map +1 -1
- package/dist/transports/StdioTransport.d.ts +141 -63
- package/dist/transports/StdioTransport.d.ts.map +1 -1
- package/dist/transports/StdioTransport.js +309 -232
- package/dist/transports/StdioTransport.js.map +1 -1
- package/dist/types/broker.d.ts +0 -22
- package/dist/types/broker.d.ts.map +1 -1
- package/dist/types/broker.js +0 -27
- package/dist/types/broker.js.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/zod-tools.d.ts +198 -0
- package/dist/types/zod-tools.d.ts.map +1 -0
- package/dist/types/zod-tools.js +14 -0
- package/dist/types/zod-tools.js.map +1 -0
- package/dist/utils/LockfileResolver.d.ts +108 -0
- package/dist/utils/LockfileResolver.d.ts.map +1 -0
- package/dist/utils/LockfileResolver.js +230 -0
- package/dist/utils/LockfileResolver.js.map +1 -0
- package/dist/utils/StdioMessageReader.d.ts +122 -0
- package/dist/utils/StdioMessageReader.d.ts.map +1 -0
- package/dist/utils/StdioMessageReader.js +209 -0
- package/dist/utils/StdioMessageReader.js.map +1 -0
- package/dist/utils/StdioMessageWriter.d.ts +104 -0
- package/dist/utils/StdioMessageWriter.d.ts.map +1 -0
- package/dist/utils/StdioMessageWriter.js +162 -0
- package/dist/utils/StdioMessageWriter.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lockfile Resolver
|
|
3
|
+
*
|
|
4
|
+
* Utilities for resolving ability paths from agent-lock.json
|
|
5
|
+
* Integrates kadi-core with kadi-install's global cache architecture
|
|
6
|
+
*
|
|
7
|
+
* @module utils/LockfileResolver
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { KadiError, ErrorCode } from '../errors/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Find project root by looking for agent.json
|
|
14
|
+
*
|
|
15
|
+
* Walks up directory tree from cwd until finding agent.json
|
|
16
|
+
*
|
|
17
|
+
* @param startDir - Directory to start search from (defaults to cwd)
|
|
18
|
+
* @returns Absolute path to project root
|
|
19
|
+
* @throws {KadiError} If no project root found
|
|
20
|
+
*/
|
|
21
|
+
export function findProjectRoot(startDir = process.cwd()) {
|
|
22
|
+
let current = path.resolve(startDir);
|
|
23
|
+
const root = path.parse(current).root;
|
|
24
|
+
while (current !== root) {
|
|
25
|
+
const agentJsonPath = path.join(current, 'agent.json');
|
|
26
|
+
if (fs.existsSync(agentJsonPath)) {
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
// Move up one directory
|
|
30
|
+
current = path.dirname(current);
|
|
31
|
+
}
|
|
32
|
+
throw new KadiError('No KADI project found - could not locate agent.json', ErrorCode.INVALID_CONFIG, 404, {
|
|
33
|
+
searchedFrom: startDir,
|
|
34
|
+
reason: 'No agent.json found in current directory or parent directories',
|
|
35
|
+
suggestion: 'Make sure you are running from within a KADI project',
|
|
36
|
+
alternatives: [
|
|
37
|
+
'Run from a directory containing agent.json',
|
|
38
|
+
'Initialize a new KADI project: kadi init',
|
|
39
|
+
'Provide explicit path when loading ability'
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read agent-lock.json from project root
|
|
45
|
+
*
|
|
46
|
+
* @param projectRoot - Project root directory
|
|
47
|
+
* @returns Parsed lockfile
|
|
48
|
+
* @throws {KadiError} If lockfile not found or invalid
|
|
49
|
+
*/
|
|
50
|
+
export function readLockfile(projectRoot) {
|
|
51
|
+
const lockfilePath = path.join(projectRoot, 'agent-lock.json');
|
|
52
|
+
// Check if lockfile exists
|
|
53
|
+
if (!fs.existsSync(lockfilePath)) {
|
|
54
|
+
throw new KadiError('No agent-lock.json found - cannot auto-resolve ability paths', ErrorCode.INVALID_CONFIG, 404, {
|
|
55
|
+
projectRoot,
|
|
56
|
+
lockfilePath,
|
|
57
|
+
reason: 'agent-lock.json not found in project root',
|
|
58
|
+
suggestion: 'Run `kadi install` to generate lockfile',
|
|
59
|
+
alternatives: [
|
|
60
|
+
'Run: kadi install (to install all abilities from agent.json)',
|
|
61
|
+
'Run: kadi install <ability-name> (to install specific ability)',
|
|
62
|
+
'Provide explicit path: client.load("ability", "native", { path: "./abilities/..." })'
|
|
63
|
+
],
|
|
64
|
+
hint: 'agent-lock.json is created by running `kadi install`'
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Read and parse lockfile
|
|
68
|
+
try {
|
|
69
|
+
const content = fs.readFileSync(lockfilePath, 'utf-8');
|
|
70
|
+
const lockfile = JSON.parse(content);
|
|
71
|
+
// Basic validation
|
|
72
|
+
if (!lockfile.abilities || typeof lockfile.abilities !== 'object') {
|
|
73
|
+
throw new Error('Invalid lockfile format: missing or invalid "abilities" field');
|
|
74
|
+
}
|
|
75
|
+
return lockfile;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof SyntaxError) {
|
|
79
|
+
throw new KadiError('Invalid agent-lock.json - malformed JSON', ErrorCode.INVALID_CONFIG, 400, {
|
|
80
|
+
lockfilePath,
|
|
81
|
+
parseError: error.message,
|
|
82
|
+
suggestion: 'Delete agent-lock.json and run `kadi install` to regenerate'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
throw new KadiError('Failed to read agent-lock.json', ErrorCode.INTERNAL_ERROR, 500, {
|
|
86
|
+
lockfilePath,
|
|
87
|
+
error: error instanceof Error ? error.message : String(error)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve ability path from lockfile
|
|
93
|
+
*
|
|
94
|
+
* @param abilityName - Name of ability to resolve
|
|
95
|
+
* @param projectRoot - Project root directory
|
|
96
|
+
* @returns Resolved ability entry from lockfile
|
|
97
|
+
* @throws {KadiError} If ability not found in lockfile or path doesn't exist
|
|
98
|
+
*/
|
|
99
|
+
export function resolveAbilityPath(abilityName, projectRoot) {
|
|
100
|
+
const lockfile = readLockfile(projectRoot);
|
|
101
|
+
// Check if ability exists in lockfile
|
|
102
|
+
const abilityEntry = lockfile.abilities[abilityName];
|
|
103
|
+
if (!abilityEntry) {
|
|
104
|
+
// Ability not in lockfile - show helpful error
|
|
105
|
+
const installedAbilities = Object.keys(lockfile.abilities);
|
|
106
|
+
throw new KadiError(`Ability '${abilityName}' not found in agent-lock.json`, ErrorCode.ABILITY_NOT_FOUND, 404, {
|
|
107
|
+
abilityName,
|
|
108
|
+
projectRoot,
|
|
109
|
+
installedAbilities,
|
|
110
|
+
installedCount: installedAbilities.length,
|
|
111
|
+
reason: `'${abilityName}' is not installed in this project`,
|
|
112
|
+
suggestion: `Run: kadi install ${abilityName}`,
|
|
113
|
+
alternatives: [
|
|
114
|
+
`Install ability: kadi install ${abilityName}`,
|
|
115
|
+
'View installed abilities in agent-lock.json',
|
|
116
|
+
'Provide explicit path when loading ability'
|
|
117
|
+
],
|
|
118
|
+
hint: installedAbilities.length === 0
|
|
119
|
+
? 'No abilities installed yet. Run `kadi install` to install from agent.json'
|
|
120
|
+
: `Currently installed: ${installedAbilities.slice(0, 5).join(', ')}${installedAbilities.length > 5 ? '...' : ''}`
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Resolve path (may be relative to project root)
|
|
124
|
+
const absolutePath = path.isAbsolute(abilityEntry.resolved)
|
|
125
|
+
? abilityEntry.resolved
|
|
126
|
+
: path.resolve(projectRoot, abilityEntry.resolved);
|
|
127
|
+
// Verify the resolved path exists
|
|
128
|
+
if (!fs.existsSync(absolutePath)) {
|
|
129
|
+
throw new KadiError(`Ability '${abilityName}@${abilityEntry.version}' is in lockfile but directory not found`, ErrorCode.ABILITY_NOT_FOUND, 404, {
|
|
130
|
+
abilityName,
|
|
131
|
+
version: abilityEntry.version,
|
|
132
|
+
expectedPath: absolutePath,
|
|
133
|
+
reason: 'Directory listed in lockfile does not exist',
|
|
134
|
+
possibleCauses: [
|
|
135
|
+
'Ability was manually deleted',
|
|
136
|
+
'Project was moved without abilities/ folder',
|
|
137
|
+
'File system error or permissions issue'
|
|
138
|
+
],
|
|
139
|
+
suggestion: `Run: kadi install --force ${abilityName}`,
|
|
140
|
+
alternatives: [
|
|
141
|
+
`Reinstall ability: kadi install --force ${abilityName}`,
|
|
142
|
+
'Check if abilities/ folder exists',
|
|
143
|
+
'Run `kadi install` to restore all abilities'
|
|
144
|
+
]
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// Return entry with absolute path
|
|
148
|
+
return {
|
|
149
|
+
...abilityEntry,
|
|
150
|
+
resolved: absolutePath
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Read ability's agent.json manifest
|
|
155
|
+
*
|
|
156
|
+
* @param abilityPath - Path to ability directory
|
|
157
|
+
* @returns Parsed ability manifest
|
|
158
|
+
* @throws {KadiError} If manifest not found or invalid
|
|
159
|
+
*/
|
|
160
|
+
export function readAbilityManifest(abilityPath) {
|
|
161
|
+
const manifestPath = path.join(abilityPath, 'agent.json');
|
|
162
|
+
if (!fs.existsSync(manifestPath)) {
|
|
163
|
+
throw new KadiError('Ability agent.json not found', ErrorCode.INVALID_CONFIG, 404, {
|
|
164
|
+
abilityPath,
|
|
165
|
+
manifestPath,
|
|
166
|
+
reason: 'agent.json not found in ability directory',
|
|
167
|
+
suggestion: 'Reinstall the ability: kadi install --force <ability-name>'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const content = fs.readFileSync(manifestPath, 'utf-8');
|
|
172
|
+
const manifest = JSON.parse(content);
|
|
173
|
+
// Basic validation
|
|
174
|
+
if (!manifest.name || !manifest.version) {
|
|
175
|
+
throw new Error('Invalid manifest: missing name or version');
|
|
176
|
+
}
|
|
177
|
+
return manifest;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error instanceof SyntaxError) {
|
|
181
|
+
throw new KadiError('Invalid ability agent.json - malformed JSON', ErrorCode.INVALID_CONFIG, 400, {
|
|
182
|
+
manifestPath,
|
|
183
|
+
parseError: error.message
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
throw new KadiError('Failed to read ability agent.json', ErrorCode.INTERNAL_ERROR, 500, {
|
|
187
|
+
manifestPath,
|
|
188
|
+
error: error instanceof Error ? error.message : String(error)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Auto-resolve ability configuration for loading
|
|
194
|
+
*
|
|
195
|
+
* Combines lockfile lookup with manifest reading to provide
|
|
196
|
+
* all information needed to load an ability.
|
|
197
|
+
*
|
|
198
|
+
* @param abilityName - Name of ability
|
|
199
|
+
* @param projectRoot - Project root directory (optional, will search if not provided)
|
|
200
|
+
* @returns Resolved ability configuration
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const config = resolveAbilityConfig('math-lib');
|
|
205
|
+
* // Returns:
|
|
206
|
+
* // {
|
|
207
|
+
* // path: '/path/to/project/abilities/math-lib@1.0.0',
|
|
208
|
+
* // version: '1.0.0',
|
|
209
|
+
* // entry: 'service.js',
|
|
210
|
+
* // manifest: { ... }
|
|
211
|
+
* // }
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export function resolveAbilityConfig(abilityName, projectRoot) {
|
|
215
|
+
// Find project root if not provided
|
|
216
|
+
const root = projectRoot || findProjectRoot();
|
|
217
|
+
// Resolve ability from lockfile
|
|
218
|
+
const entry = resolveAbilityPath(abilityName, root);
|
|
219
|
+
// Read ability manifest
|
|
220
|
+
const manifest = readAbilityManifest(entry.resolved);
|
|
221
|
+
// Get entry point (default to 'index.js' if not specified)
|
|
222
|
+
const entryPoint = manifest.entry || 'index.js';
|
|
223
|
+
return {
|
|
224
|
+
path: entry.resolved,
|
|
225
|
+
version: entry.version,
|
|
226
|
+
entry: entryPoint,
|
|
227
|
+
manifest
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=LockfileResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LockfileResolver.js","sourceRoot":"","sources":["../../src/utils/LockfileResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAyC1D;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,WAAmB,OAAO,CAAC,GAAG,EAAE;IAC9D,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IAEtC,OAAO,OAAO,KAAK,IAAI,EAAE,CAAC;QACxB,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAEvD,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACjC,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,wBAAwB;QACxB,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,IAAI,SAAS,CACjB,qDAAqD,EACrD,SAAS,CAAC,cAAc,EACxB,GAAG,EACH;QACE,YAAY,EAAE,QAAQ;QACtB,MAAM,EAAE,gEAAgE;QACxE,UAAU,EAAE,sDAAsD;QAClE,YAAY,EAAE;YACZ,4CAA4C;YAC5C,0CAA0C;YAC1C,4CAA4C;SAC7C;KACF,CACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IAE/D,2BAA2B;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CACjB,8DAA8D,EAC9D,SAAS,CAAC,cAAc,EACxB,GAAG,EACH;YACE,WAAW;YACX,YAAY;YACZ,MAAM,EAAE,2CAA2C;YACnD,UAAU,EAAE,yCAAyC;YACrD,YAAY,EAAE;gBACZ,8DAA8D;gBAC9D,gEAAgE;gBAChE,sFAAsF;aACvF;YACD,IAAI,EAAE,sDAAsD;SAC7D,CACF,CAAC;IACJ,CAAC;IAED,0BAA0B;IAC1B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAkB,CAAC;QAEtD,mBAAmB;QACnB,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,OAAO,QAAQ,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,MAAM,IAAI,SAAS,CACjB,0CAA0C,EAC1C,SAAS,CAAC,cAAc,EACxB,GAAG,EACH;gBACE,YAAY;gBACZ,UAAU,EAAE,KAAK,CAAC,OAAO;gBACzB,UAAU,EAAE,6DAA6D;aAC1E,CACF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,SAAS,CACjB,gCAAgC,EAChC,SAAS,CAAC,cAAc,EACxB,GAAG,EACH;YACE,YAAY;YACZ,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,WAAmB;IAEnB,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IAE3C,sCAAsC;IACtC,MAAM,YAAY,GAAG,QAAQ,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAErD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,+CAA+C;QAC/C,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAE3D,MAAM,IAAI,SAAS,CACjB,YAAY,WAAW,gCAAgC,EACvD,SAAS,CAAC,iBAAiB,EAC3B,GAAG,EACH;YACE,WAAW;YACX,WAAW;YACX,kBAAkB;YAClB,cAAc,EAAE,kBAAkB,CAAC,MAAM;YACzC,MAAM,EAAE,IAAI,WAAW,oCAAoC;YAC3D,UAAU,EAAE,qBAAqB,WAAW,EAAE;YAC9C,YAAY,EAAE;gBACZ,iCAAiC,WAAW,EAAE;gBAC9C,6CAA6C;gBAC7C,4CAA4C;aAC7C;YACD,IAAI,EAAE,kBAAkB,CAAC,MAAM,KAAK,CAAC;gBACnC,CAAC,CAAC,2EAA2E;gBAC7E,CAAC,CAAC,wBAAwB,kBAAkB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;SACrH,CACF,CAAC;IACJ,CAAC;IAED,iDAAiD;IACjD,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC;QACzD,CAAC,CAAC,YAAY,CAAC,QAAQ;QACvB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAErD,kCAAkC;IAClC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CACjB,YAAY,WAAW,IAAI,YAAY,CAAC,OAAO,0CAA0C,EACzF,SAAS,CAAC,iBAAiB,EAC3B,GAAG,EACH;YACE,WAAW;YACX,OAAO,EAAE,YAAY,CAAC,OAAO;YAC7B,YAAY,EAAE,YAAY;YAC1B,MAAM,EAAE,6CAA6C;YACrD,cAAc,EAAE;gBACd,8BAA8B;gBAC9B,6CAA6C;gBAC7C,wCAAwC;aACzC;YACD,UAAU,EAAE,6BAA6B,WAAW,EAAE;YACtD,YAAY,EAAE;gBACZ,2CAA2C,WAAW,EAAE;gBACxD,mCAAmC;gBACnC,6CAA6C;aAC9C;SACF,CACF,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,OAAO;QACL,GAAG,YAAY;QACf,QAAQ,EAAE,YAAY;KACvB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAE1D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CACjB,8BAA8B,EAC9B,SAAS,CAAC,cAAc,EACxB,GAAG,EACH;YACE,WAAW;YACX,YAAY;YACZ,MAAM,EAAE,2CAA2C;YACnD,UAAU,EAAE,4DAA4D;SACzE,CACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAoB,CAAC;QAExD,mBAAmB;QACnB,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC/D,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,MAAM,IAAI,SAAS,CACjB,6CAA6C,EAC7C,SAAS,CAAC,cAAc,EACxB,GAAG,EACH;gBACE,YAAY;gBACZ,UAAU,EAAE,KAAK,CAAC,OAAO;aAC1B,CACF,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,SAAS,CACjB,mCAAmC,EACnC,SAAS,CAAC,cAAc,EACxB,GAAG,EACH;YACE,YAAY;YACZ,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAmB,EACnB,WAAoB;IAOpB,oCAAoC;IACpC,MAAM,IAAI,GAAG,WAAW,IAAI,eAAe,EAAE,CAAC;IAE9C,gCAAgC;IAChC,MAAM,KAAK,GAAG,kBAAkB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAEpD,wBAAwB;IACxB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAErD,2DAA2D;IAC3D,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,IAAI,UAAU,CAAC;IAEhD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,QAAQ;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,UAAU;QACjB,QAAQ;KACT,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio Message Reader
|
|
3
|
+
*
|
|
4
|
+
* Reads Content-Length framed messages from a stream (LSP-style).
|
|
5
|
+
* Handles partial messages across multiple chunks and ignores non-framed output.
|
|
6
|
+
*
|
|
7
|
+
* Message format:
|
|
8
|
+
* ```
|
|
9
|
+
* Content-Length: 67\r\n
|
|
10
|
+
* \r\n
|
|
11
|
+
* {"jsonrpc":"2.0","id":1,"method":"invoke","params":{...}}
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module utils/StdioMessageReader
|
|
15
|
+
*/
|
|
16
|
+
import { EventEmitter } from 'events';
|
|
17
|
+
/**
|
|
18
|
+
* Message reader for Content-Length framed messages
|
|
19
|
+
*
|
|
20
|
+
* Implements the LSP (Language Server Protocol) message framing format.
|
|
21
|
+
* This allows reliable JSON-RPC communication over stdio even when the
|
|
22
|
+
* ability writes logs or debug output to stdout.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const reader = new StdioMessageReader();
|
|
27
|
+
*
|
|
28
|
+
* reader.on('message', (message) => {
|
|
29
|
+
* console.log('Received:', message);
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* reader.on('error', (error) => {
|
|
33
|
+
* console.error('Parse error:', error);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Feed data chunks
|
|
37
|
+
* process.stdout.on('data', (chunk) => {
|
|
38
|
+
* reader.onData(chunk);
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare class StdioMessageReader extends EventEmitter {
|
|
43
|
+
/**
|
|
44
|
+
* Buffer for accumulating incoming data
|
|
45
|
+
*/
|
|
46
|
+
private buffer;
|
|
47
|
+
/**
|
|
48
|
+
* Whether the reader has been destroyed
|
|
49
|
+
*/
|
|
50
|
+
private destroyed;
|
|
51
|
+
/**
|
|
52
|
+
* Header end marker
|
|
53
|
+
*/
|
|
54
|
+
private static readonly HEADER_END;
|
|
55
|
+
/**
|
|
56
|
+
* Content-Length header regex
|
|
57
|
+
* Note: No multiline flag - header must be clean (no garbage before it)
|
|
58
|
+
*/
|
|
59
|
+
private static readonly CONTENT_LENGTH_PATTERN;
|
|
60
|
+
/**
|
|
61
|
+
* Process incoming data chunk
|
|
62
|
+
*
|
|
63
|
+
* Appends chunk to buffer and attempts to parse complete messages.
|
|
64
|
+
* May emit multiple 'message' events if buffer contains multiple messages.
|
|
65
|
+
*
|
|
66
|
+
* @param chunk - Incoming data chunk
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* stream.on('data', (chunk) => {
|
|
71
|
+
* reader.onData(chunk);
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
onData(chunk: Buffer): void;
|
|
76
|
+
/**
|
|
77
|
+
* Attempt to read and emit complete messages from buffer
|
|
78
|
+
*
|
|
79
|
+
* Continues reading until no complete message can be extracted.
|
|
80
|
+
* Handles multiple messages in a single buffer.
|
|
81
|
+
*/
|
|
82
|
+
private tryReadMessages;
|
|
83
|
+
/**
|
|
84
|
+
* Attempt to read a single complete message
|
|
85
|
+
*
|
|
86
|
+
* @returns Parsed message object, or null if no complete message available
|
|
87
|
+
* @throws {Error} If header is malformed or JSON is invalid
|
|
88
|
+
*/
|
|
89
|
+
private tryReadSingleMessage;
|
|
90
|
+
/**
|
|
91
|
+
* Skip to next potential header in buffer
|
|
92
|
+
*
|
|
93
|
+
* Used for error recovery when a malformed message is encountered.
|
|
94
|
+
* Searches for the next "Content-Length:" pattern.
|
|
95
|
+
*/
|
|
96
|
+
private skipToNextHeader;
|
|
97
|
+
/**
|
|
98
|
+
* Get current buffer size (for debugging/monitoring)
|
|
99
|
+
*
|
|
100
|
+
* @returns Number of bytes in buffer
|
|
101
|
+
*/
|
|
102
|
+
getBufferSize(): number;
|
|
103
|
+
/**
|
|
104
|
+
* Clear the internal buffer
|
|
105
|
+
*
|
|
106
|
+
* Useful for resetting state after errors or for cleanup.
|
|
107
|
+
*/
|
|
108
|
+
clearBuffer(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Destroy the reader
|
|
111
|
+
*
|
|
112
|
+
* Cleans up resources and prevents further processing.
|
|
113
|
+
*/
|
|
114
|
+
destroy(): void;
|
|
115
|
+
/**
|
|
116
|
+
* Check if reader is destroyed
|
|
117
|
+
*
|
|
118
|
+
* @returns true if destroyed
|
|
119
|
+
*/
|
|
120
|
+
isDestroyed(): boolean;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=StdioMessageReader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StdioMessageReader.d.ts","sourceRoot":"","sources":["../../src/utils/StdioMessageReader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAClD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAmB;IAEjC;;OAEG;IACH,OAAO,CAAC,SAAS,CAAS;IAE1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAc;IAEhD;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAmC;IAEjF;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAY3B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAqBvB;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAmD5B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;;;OAIG;IACH,aAAa,IAAI,MAAM;IAIvB;;;;OAIG;IACH,WAAW,IAAI,IAAI;IAInB;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAMf;;;;OAIG;IACH,WAAW,IAAI,OAAO;CAGvB"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio Message Reader
|
|
3
|
+
*
|
|
4
|
+
* Reads Content-Length framed messages from a stream (LSP-style).
|
|
5
|
+
* Handles partial messages across multiple chunks and ignores non-framed output.
|
|
6
|
+
*
|
|
7
|
+
* Message format:
|
|
8
|
+
* ```
|
|
9
|
+
* Content-Length: 67\r\n
|
|
10
|
+
* \r\n
|
|
11
|
+
* {"jsonrpc":"2.0","id":1,"method":"invoke","params":{...}}
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module utils/StdioMessageReader
|
|
15
|
+
*/
|
|
16
|
+
import { EventEmitter } from 'events';
|
|
17
|
+
/**
|
|
18
|
+
* Message reader for Content-Length framed messages
|
|
19
|
+
*
|
|
20
|
+
* Implements the LSP (Language Server Protocol) message framing format.
|
|
21
|
+
* This allows reliable JSON-RPC communication over stdio even when the
|
|
22
|
+
* ability writes logs or debug output to stdout.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const reader = new StdioMessageReader();
|
|
27
|
+
*
|
|
28
|
+
* reader.on('message', (message) => {
|
|
29
|
+
* console.log('Received:', message);
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* reader.on('error', (error) => {
|
|
33
|
+
* console.error('Parse error:', error);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Feed data chunks
|
|
37
|
+
* process.stdout.on('data', (chunk) => {
|
|
38
|
+
* reader.onData(chunk);
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export class StdioMessageReader extends EventEmitter {
|
|
43
|
+
/**
|
|
44
|
+
* Buffer for accumulating incoming data
|
|
45
|
+
*/
|
|
46
|
+
buffer = Buffer.alloc(0);
|
|
47
|
+
/**
|
|
48
|
+
* Whether the reader has been destroyed
|
|
49
|
+
*/
|
|
50
|
+
destroyed = false;
|
|
51
|
+
/**
|
|
52
|
+
* Header end marker
|
|
53
|
+
*/
|
|
54
|
+
static HEADER_END = '\r\n\r\n';
|
|
55
|
+
/**
|
|
56
|
+
* Content-Length header regex
|
|
57
|
+
* Note: No multiline flag - header must be clean (no garbage before it)
|
|
58
|
+
*/
|
|
59
|
+
static CONTENT_LENGTH_PATTERN = /^Content-Length:\s*(\d+)\s*$/i;
|
|
60
|
+
/**
|
|
61
|
+
* Process incoming data chunk
|
|
62
|
+
*
|
|
63
|
+
* Appends chunk to buffer and attempts to parse complete messages.
|
|
64
|
+
* May emit multiple 'message' events if buffer contains multiple messages.
|
|
65
|
+
*
|
|
66
|
+
* @param chunk - Incoming data chunk
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* stream.on('data', (chunk) => {
|
|
71
|
+
* reader.onData(chunk);
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
onData(chunk) {
|
|
76
|
+
if (this.destroyed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Append to buffer
|
|
80
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
81
|
+
// Try to extract messages
|
|
82
|
+
this.tryReadMessages();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Attempt to read and emit complete messages from buffer
|
|
86
|
+
*
|
|
87
|
+
* Continues reading until no complete message can be extracted.
|
|
88
|
+
* Handles multiple messages in a single buffer.
|
|
89
|
+
*/
|
|
90
|
+
tryReadMessages() {
|
|
91
|
+
while (true) {
|
|
92
|
+
try {
|
|
93
|
+
const message = this.tryReadSingleMessage();
|
|
94
|
+
if (!message) {
|
|
95
|
+
// No complete message available
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
// Emit the message
|
|
99
|
+
this.emit('message', message);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
// Emit error but continue processing
|
|
103
|
+
this.emit('error', error);
|
|
104
|
+
// Try to recover by skipping to next potential header
|
|
105
|
+
this.skipToNextHeader();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Attempt to read a single complete message
|
|
111
|
+
*
|
|
112
|
+
* @returns Parsed message object, or null if no complete message available
|
|
113
|
+
* @throws {Error} If header is malformed or JSON is invalid
|
|
114
|
+
*/
|
|
115
|
+
tryReadSingleMessage() {
|
|
116
|
+
// Look for header end marker
|
|
117
|
+
const headerEndIndex = this.buffer.indexOf(StdioMessageReader.HEADER_END);
|
|
118
|
+
if (headerEndIndex === -1) {
|
|
119
|
+
// No complete header yet
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
// Extract header
|
|
123
|
+
const headerBytes = this.buffer.slice(0, headerEndIndex);
|
|
124
|
+
const header = headerBytes.toString('utf8');
|
|
125
|
+
// Parse Content-Length
|
|
126
|
+
const match = header.match(StdioMessageReader.CONTENT_LENGTH_PATTERN);
|
|
127
|
+
if (!match || !match[1]) {
|
|
128
|
+
throw new Error(`Invalid message header: missing or malformed Content-Length\nHeader: ${header}`);
|
|
129
|
+
}
|
|
130
|
+
const contentLength = parseInt(match[1], 10);
|
|
131
|
+
if (isNaN(contentLength) || contentLength < 0) {
|
|
132
|
+
throw new Error(`Invalid Content-Length value: ${match[1]}`);
|
|
133
|
+
}
|
|
134
|
+
// Check if we have the complete message
|
|
135
|
+
const messageStart = headerEndIndex + StdioMessageReader.HEADER_END.length;
|
|
136
|
+
const messageEnd = messageStart + contentLength;
|
|
137
|
+
if (this.buffer.length < messageEnd) {
|
|
138
|
+
// Need more data - message is incomplete
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
// Extract message bytes
|
|
142
|
+
const messageBytes = this.buffer.slice(messageStart, messageEnd);
|
|
143
|
+
const messageJson = messageBytes.toString('utf8');
|
|
144
|
+
// Parse JSON
|
|
145
|
+
let message;
|
|
146
|
+
try {
|
|
147
|
+
message = JSON.parse(messageJson);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
throw new Error(`Invalid JSON in message body: ${error instanceof Error ? error.message : String(error)}\nJSON: ${messageJson.substring(0, 200)}`);
|
|
151
|
+
}
|
|
152
|
+
// Remove processed data from buffer
|
|
153
|
+
this.buffer = this.buffer.slice(messageEnd);
|
|
154
|
+
return message;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Skip to next potential header in buffer
|
|
158
|
+
*
|
|
159
|
+
* Used for error recovery when a malformed message is encountered.
|
|
160
|
+
* Searches for the next "Content-Length:" pattern.
|
|
161
|
+
*/
|
|
162
|
+
skipToNextHeader() {
|
|
163
|
+
const searchString = this.buffer.toString('utf8');
|
|
164
|
+
const nextHeaderIndex = searchString.indexOf('Content-Length:', 1);
|
|
165
|
+
if (nextHeaderIndex === -1) {
|
|
166
|
+
// No next header found - clear buffer
|
|
167
|
+
this.buffer = Buffer.alloc(0);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Skip to next header
|
|
171
|
+
this.buffer = this.buffer.slice(nextHeaderIndex);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get current buffer size (for debugging/monitoring)
|
|
176
|
+
*
|
|
177
|
+
* @returns Number of bytes in buffer
|
|
178
|
+
*/
|
|
179
|
+
getBufferSize() {
|
|
180
|
+
return this.buffer.length;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Clear the internal buffer
|
|
184
|
+
*
|
|
185
|
+
* Useful for resetting state after errors or for cleanup.
|
|
186
|
+
*/
|
|
187
|
+
clearBuffer() {
|
|
188
|
+
this.buffer = Buffer.alloc(0);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Destroy the reader
|
|
192
|
+
*
|
|
193
|
+
* Cleans up resources and prevents further processing.
|
|
194
|
+
*/
|
|
195
|
+
destroy() {
|
|
196
|
+
this.destroyed = true;
|
|
197
|
+
this.buffer = Buffer.alloc(0);
|
|
198
|
+
this.removeAllListeners();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Check if reader is destroyed
|
|
202
|
+
*
|
|
203
|
+
* @returns true if destroyed
|
|
204
|
+
*/
|
|
205
|
+
isDestroyed() {
|
|
206
|
+
return this.destroyed;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=StdioMessageReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StdioMessageReader.js","sourceRoot":"","sources":["../../src/utils/StdioMessageReader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdio Message Writer
|
|
3
|
+
*
|
|
4
|
+
* Writes Content-Length framed messages to a stream (LSP-style).
|
|
5
|
+
* Ensures reliable message delivery even when other output is written to the stream.
|
|
6
|
+
*
|
|
7
|
+
* Message format:
|
|
8
|
+
* ```
|
|
9
|
+
* Content-Length: 67\r\n
|
|
10
|
+
* \r\n
|
|
11
|
+
* {"jsonrpc":"2.0","id":1,"result":{"value":42}}
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module utils/StdioMessageWriter
|
|
15
|
+
*/
|
|
16
|
+
import type { Writable } from 'stream';
|
|
17
|
+
/**
|
|
18
|
+
* Message writer for Content-Length framed messages
|
|
19
|
+
*
|
|
20
|
+
* Implements the LSP (Language Server Protocol) message framing format.
|
|
21
|
+
* Automatically calculates content length and formats headers.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const writer = new StdioMessageWriter(process.stdout);
|
|
26
|
+
*
|
|
27
|
+
* // Write a JSON-RPC message
|
|
28
|
+
* await writer.write({
|
|
29
|
+
* jsonrpc: '2.0',
|
|
30
|
+
* id: 1,
|
|
31
|
+
* result: { value: 42 }
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare class StdioMessageWriter {
|
|
36
|
+
/**
|
|
37
|
+
* Output stream
|
|
38
|
+
*/
|
|
39
|
+
private readonly stream;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the writer has been destroyed
|
|
42
|
+
*/
|
|
43
|
+
private destroyed;
|
|
44
|
+
/**
|
|
45
|
+
* Header separator
|
|
46
|
+
*/
|
|
47
|
+
private static readonly HEADER_SEPARATOR;
|
|
48
|
+
/**
|
|
49
|
+
* Create a new message writer
|
|
50
|
+
*
|
|
51
|
+
* @param stream - Output stream (typically stdout)
|
|
52
|
+
*/
|
|
53
|
+
constructor(stream: Writable);
|
|
54
|
+
/**
|
|
55
|
+
* Write a framed message to the stream
|
|
56
|
+
*
|
|
57
|
+
* Serializes the message to JSON, calculates byte length, formats
|
|
58
|
+
* Content-Length header, and writes everything to the stream.
|
|
59
|
+
*
|
|
60
|
+
* @param message - Message object to write (will be JSON-stringified)
|
|
61
|
+
* @returns Promise that resolves when message is fully written
|
|
62
|
+
*
|
|
63
|
+
* @throws {Error} If writer is destroyed or write fails
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* await writer.write({
|
|
68
|
+
* jsonrpc: '2.0',
|
|
69
|
+
* id: 1,
|
|
70
|
+
* method: 'invoke',
|
|
71
|
+
* params: { toolName: 'add', toolInput: { a: 5, b: 3 } }
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
write(message: Record<string, unknown>): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Flush the stream
|
|
78
|
+
*
|
|
79
|
+
* Ensures all buffered data is written to the underlying stream.
|
|
80
|
+
*
|
|
81
|
+
* @returns Promise that resolves when flush is complete
|
|
82
|
+
*/
|
|
83
|
+
flush(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Destroy the writer
|
|
86
|
+
*
|
|
87
|
+
* Cleans up resources. After calling destroy(), the writer cannot be used.
|
|
88
|
+
* Note: This does NOT close the underlying stream.
|
|
89
|
+
*/
|
|
90
|
+
destroy(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Check if writer is destroyed
|
|
93
|
+
*
|
|
94
|
+
* @returns true if destroyed
|
|
95
|
+
*/
|
|
96
|
+
isDestroyed(): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Get the underlying stream
|
|
99
|
+
*
|
|
100
|
+
* @returns The writable stream
|
|
101
|
+
*/
|
|
102
|
+
getStream(): Writable;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=StdioMessageWriter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StdioMessageWriter.d.ts","sourceRoot":"","sources":["../../src/utils/StdioMessageWriter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAEvC;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,kBAAkB;IAC7B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAElC;;OAEG;IACH,OAAO,CAAC,SAAS,CAAS;IAE1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAElD;;;;OAIG;gBACS,MAAM,EAAE,QAAQ;IAI5B;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC5D;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB5B;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAIf;;;;OAIG;IACH,WAAW,IAAI,OAAO;IAItB;;;;OAIG;IACH,SAAS,IAAI,QAAQ;CAGtB"}
|