dsh-agy-cli 0.0.0-stage → 0.12.1
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/LICENSE +31 -0
- package/README.en.md +48 -0
- package/README.md +67 -2
- package/agents/dsh-agy-image-view.md +17 -0
- package/agents/dsh-agy-mcp-executor.md +32 -0
- package/agents/dsh-agy-read-only.md +20 -0
- package/agents/dsh-agy-tool-free.md +16 -0
- package/agents/dsh-agy-workspace-write.md +23 -0
- package/bin/dsh-agy-cli.js +9 -0
- package/bin/win32-x64/agy-launcher.exe +0 -0
- package/cordis.patch.yml +16 -0
- package/dist/client.js +4259 -0
- package/lib/agent-installer.d.ts +66 -0
- package/lib/agent-installer.js +147 -0
- package/lib/agent-presets.d.ts +42 -0
- package/lib/agent-presets.js +130 -0
- package/lib/agents-cli.d.ts +3 -0
- package/lib/agents-cli.js +110 -0
- package/lib/agy/diagnostics.d.ts +29 -0
- package/lib/agy/diagnostics.js +109 -0
- package/lib/agy/errors.d.ts +20 -0
- package/lib/agy/errors.js +77 -0
- package/lib/agy/experimental-transport.d.ts +79 -0
- package/lib/agy/experimental-transport.js +612 -0
- package/lib/agy/keepalive.d.ts +140 -0
- package/lib/agy/keepalive.js +445 -0
- package/lib/agy/limiter.d.ts +32 -0
- package/lib/agy/limiter.js +111 -0
- package/lib/agy/log.d.ts +214 -0
- package/lib/agy/log.js +503 -0
- package/lib/agy/models.d.ts +49 -0
- package/lib/agy/models.js +203 -0
- package/lib/agy/overrides.d.ts +17 -0
- package/lib/agy/overrides.js +73 -0
- package/lib/agy/parser.d.ts +102 -0
- package/lib/agy/parser.js +492 -0
- package/lib/agy/persistent-transport.d.ts +101 -0
- package/lib/agy/persistent-transport.js +753 -0
- package/lib/agy/process.d.ts +144 -0
- package/lib/agy/process.js +452 -0
- package/lib/agy/redact.d.ts +3 -0
- package/lib/agy/redact.js +24 -0
- package/lib/agy/stream-protocol.d.ts +50 -0
- package/lib/agy/stream-protocol.js +156 -0
- package/lib/agy/windows-launcher.d.ts +21 -0
- package/lib/agy/windows-launcher.js +76 -0
- package/lib/diagnostics.d.ts +78 -0
- package/lib/diagnostics.js +162 -0
- package/lib/doctor.d.ts +138 -0
- package/lib/doctor.js +940 -0
- package/lib/dsh/context.d.ts +133 -0
- package/lib/dsh/context.js +352 -0
- package/lib/dsh-home.d.ts +6 -0
- package/lib/dsh-home.js +11 -0
- package/lib/index.d.ts +54 -0
- package/lib/index.js +267 -0
- package/lib/mcp/agy-registration.d.ts +70 -0
- package/lib/mcp/agy-registration.js +245 -0
- package/lib/mcp/bridge.d.ts +73 -0
- package/lib/mcp/bridge.js +295 -0
- package/lib/mcp/endpoint.d.ts +41 -0
- package/lib/mcp/endpoint.js +142 -0
- package/lib/mcp/protocol.d.ts +107 -0
- package/lib/mcp/protocol.js +197 -0
- package/lib/mcp/tool-projection.d.ts +64 -0
- package/lib/mcp/tool-projection.js +168 -0
- package/lib/provider/agy.d.ts +153 -0
- package/lib/provider/agy.js +2159 -0
- package/lib/provider/config.d.ts +226 -0
- package/lib/provider/config.js +294 -0
- package/lib/provider/error-codes.d.ts +22 -0
- package/lib/provider/error-codes.js +29 -0
- package/lib/provider/image-bridge.d.ts +60 -0
- package/lib/provider/image-bridge.js +290 -0
- package/lib/provider/mock.d.ts +15 -0
- package/lib/provider/mock.js +78 -0
- package/lib/provider/prompt-budget.d.ts +45 -0
- package/lib/provider/prompt-budget.js +596 -0
- package/lib/provider/serialize.d.ts +15 -0
- package/lib/provider/serialize.js +82 -0
- package/lib/provider/tool-protocol.d.ts +80 -0
- package/lib/provider/tool-protocol.js +848 -0
- package/lib/provider/tool-schema-file.d.ts +11 -0
- package/lib/provider/tool-schema-file.js +27 -0
- package/lib/session/file-store.d.ts +56 -0
- package/lib/session/file-store.js +186 -0
- package/lib/session/store.d.ts +49 -0
- package/lib/session/store.js +62 -0
- package/lib/web/router.d.ts +19 -0
- package/lib/web/router.js +994 -0
- package/package.json +115 -4
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
export const DEFAULT_MAX_LINE_LENGTH = 1_048_576;
|
|
2
|
+
export class AgyParserError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
lineNumber;
|
|
5
|
+
rawLine;
|
|
6
|
+
constructor(message, code, lineNumber, rawLine, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.lineNumber = lineNumber;
|
|
10
|
+
this.rawLine = rawLine;
|
|
11
|
+
this.name = 'AgyParserError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const AGY_TOOL_EVENT_KINDS = [
|
|
15
|
+
'event-tool-call',
|
|
16
|
+
'event-tool-result',
|
|
17
|
+
'step-type-tool',
|
|
18
|
+
'explicit-tool-name',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Stable AGY orchestration recipients used to carry a DSH-owned tool call.
|
|
22
|
+
* Keep this exact-name allowlist narrow; arbitrary recipients remain rejected.
|
|
23
|
+
*/
|
|
24
|
+
const DSH_CARRIER_RECIPIENTS = ['dsh', 'dsh-session', 'dsh-runner'];
|
|
25
|
+
export function isDshCarrierRecipient(value) {
|
|
26
|
+
return DSH_CARRIER_RECIPIENTS.includes(value);
|
|
27
|
+
}
|
|
28
|
+
export function isAgyToolEventKind(value) {
|
|
29
|
+
return typeof value === 'string'
|
|
30
|
+
&& AGY_TOOL_EVENT_KINDS.includes(value);
|
|
31
|
+
}
|
|
32
|
+
export function emptyAgyEventCategoryCounts() {
|
|
33
|
+
return {
|
|
34
|
+
init: 0,
|
|
35
|
+
step_update: 0,
|
|
36
|
+
checkpoint: 0,
|
|
37
|
+
agent_response: 0,
|
|
38
|
+
result: 0,
|
|
39
|
+
tool: 0,
|
|
40
|
+
permission: 0,
|
|
41
|
+
error: 0,
|
|
42
|
+
unknown: 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function isRecord(value) {
|
|
46
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
47
|
+
}
|
|
48
|
+
function diagnosticLine(value) {
|
|
49
|
+
return value.length <= 4_096 ? value : `${value.slice(0, 4_096)}…`;
|
|
50
|
+
}
|
|
51
|
+
/** Parse one complete NDJSON line and require only the stable event envelope. */
|
|
52
|
+
export function parseAgyJsonLine(rawLine, lineNumber = 1, maxLineLength = DEFAULT_MAX_LINE_LENGTH) {
|
|
53
|
+
// The instance-configured limit travels with the call: an AgyStreamParser
|
|
54
|
+
// constructed with a larger maxLineLength must not be silently clamped back
|
|
55
|
+
// to the module default by this second gate.
|
|
56
|
+
if (rawLine.length > maxLineLength) {
|
|
57
|
+
throw new AgyParserError(`AGY event line ${lineNumber} exceeds the maximum length`, 'LINE_TOO_LONG', lineNumber, diagnosticLine(rawLine));
|
|
58
|
+
}
|
|
59
|
+
let value;
|
|
60
|
+
try {
|
|
61
|
+
value = JSON.parse(rawLine);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw new AgyParserError(`Invalid AGY JSON at line ${lineNumber}`, 'INVALID_JSON_LINE', lineNumber, diagnosticLine(rawLine), { cause: error });
|
|
65
|
+
}
|
|
66
|
+
if (!isRecord(value) || typeof value.event !== 'string' || value.event.length === 0) {
|
|
67
|
+
throw new AgyParserError(`AGY event at line ${lineNumber} must be an object with a non-empty event field`, 'INVALID_EVENT', lineNumber, diagnosticLine(rawLine));
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
/** Incremental parser for arbitrary stdout chunks, including CRLF streams. */
|
|
72
|
+
export class AgyStreamParser {
|
|
73
|
+
buffer = '';
|
|
74
|
+
lineNumber = 0;
|
|
75
|
+
maxLineLength;
|
|
76
|
+
constructor(options = {}) {
|
|
77
|
+
this.maxLineLength = options.maxLineLength ?? DEFAULT_MAX_LINE_LENGTH;
|
|
78
|
+
if (!Number.isSafeInteger(this.maxLineLength) || this.maxLineLength < 1) {
|
|
79
|
+
throw new RangeError('maxLineLength must be a positive safe integer');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
push(chunk) {
|
|
83
|
+
this.buffer += chunk;
|
|
84
|
+
if (this.buffer.length > this.maxLineLength && !this.buffer.includes('\n')) {
|
|
85
|
+
this.lineNumber += 1;
|
|
86
|
+
const rawLine = diagnosticLine(this.buffer);
|
|
87
|
+
this.buffer = '';
|
|
88
|
+
throw new AgyParserError(`AGY event line ${this.lineNumber} exceeds the maximum length`, 'LINE_TOO_LONG', this.lineNumber, rawLine);
|
|
89
|
+
}
|
|
90
|
+
const lines = this.buffer.split('\n');
|
|
91
|
+
this.buffer = lines.pop() ?? '';
|
|
92
|
+
return lines.flatMap(line => this.parseLine(line.replace(/\r$/, '')));
|
|
93
|
+
}
|
|
94
|
+
end() {
|
|
95
|
+
if (this.buffer.length === 0)
|
|
96
|
+
return [];
|
|
97
|
+
const line = this.buffer.replace(/\r$/, '');
|
|
98
|
+
this.buffer = '';
|
|
99
|
+
return this.parseLine(line);
|
|
100
|
+
}
|
|
101
|
+
parseLine(line) {
|
|
102
|
+
this.lineNumber += 1;
|
|
103
|
+
if (line.length > this.maxLineLength) {
|
|
104
|
+
throw new AgyParserError(`AGY event line ${this.lineNumber} exceeds the maximum length`, 'LINE_TOO_LONG', this.lineNumber, diagnosticLine(line));
|
|
105
|
+
}
|
|
106
|
+
if (line.trim().length === 0)
|
|
107
|
+
return [];
|
|
108
|
+
return [parseAgyJsonLine(line, this.lineNumber, this.maxLineLength)];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Parse an async sequence of stdout chunks without buffering the full stream. */
|
|
112
|
+
export async function* parseAgyChunks(chunks) {
|
|
113
|
+
const parser = new AgyStreamParser();
|
|
114
|
+
for await (const chunk of chunks) {
|
|
115
|
+
yield* parser.push(chunk);
|
|
116
|
+
}
|
|
117
|
+
yield* parser.end();
|
|
118
|
+
}
|
|
119
|
+
function nestedRecord(event, key) {
|
|
120
|
+
const value = event[key];
|
|
121
|
+
return isRecord(value) ? value : undefined;
|
|
122
|
+
}
|
|
123
|
+
function normalizedKey(key) {
|
|
124
|
+
return key.toLowerCase().replace(/[-_]/g, '');
|
|
125
|
+
}
|
|
126
|
+
const STRUCTURAL_KEY_NAMES = new Map([
|
|
127
|
+
['event', 'event'],
|
|
128
|
+
['init', 'init'],
|
|
129
|
+
['stepupdate', 'step_update'],
|
|
130
|
+
['checkpoint', 'checkpoint'],
|
|
131
|
+
['agentresponse', 'agent_response'],
|
|
132
|
+
['result', 'result'],
|
|
133
|
+
['toolcall', 'tool_call'],
|
|
134
|
+
['toolresult', 'tool_result'],
|
|
135
|
+
['permissionrequest', 'permission_request'],
|
|
136
|
+
['errormessage', 'error_message'],
|
|
137
|
+
['error', 'error'],
|
|
138
|
+
['steptype', 'step_type'],
|
|
139
|
+
['toolname', 'tool_name'],
|
|
140
|
+
['toolinput', 'tool_input'],
|
|
141
|
+
['state', 'state'],
|
|
142
|
+
['name', 'name'],
|
|
143
|
+
['recipient', 'recipient'],
|
|
144
|
+
['message', 'message'],
|
|
145
|
+
['arguments', 'arguments'],
|
|
146
|
+
['parameters', 'parameters'],
|
|
147
|
+
['input', 'input'],
|
|
148
|
+
['args', 'args'],
|
|
149
|
+
]);
|
|
150
|
+
function structuralKeys(value) {
|
|
151
|
+
if (!isRecord(value))
|
|
152
|
+
return [];
|
|
153
|
+
const keys = new Set();
|
|
154
|
+
for (const key of Object.keys(value)) {
|
|
155
|
+
const canonical = STRUCTURAL_KEY_NAMES.get(normalizedKey(key));
|
|
156
|
+
if (canonical !== undefined)
|
|
157
|
+
keys.add(canonical);
|
|
158
|
+
}
|
|
159
|
+
return [...keys].sort();
|
|
160
|
+
}
|
|
161
|
+
function valueForKey(value, key) {
|
|
162
|
+
if (!isRecord(value))
|
|
163
|
+
return undefined;
|
|
164
|
+
const target = normalizedKey(key);
|
|
165
|
+
for (const [candidate, child] of Object.entries(value)) {
|
|
166
|
+
if (normalizedKey(candidate) === target)
|
|
167
|
+
return child;
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
function decodeRecord(value) {
|
|
172
|
+
if (isRecord(value))
|
|
173
|
+
return value;
|
|
174
|
+
if (typeof value !== 'string')
|
|
175
|
+
return undefined;
|
|
176
|
+
const trimmed = value.trim();
|
|
177
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
|
|
178
|
+
return undefined;
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(trimmed);
|
|
181
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** Extract the AGY conversation identity emitted by an init event. */
|
|
188
|
+
export function conversationIdOf(event) {
|
|
189
|
+
if (event.event !== 'init')
|
|
190
|
+
return undefined;
|
|
191
|
+
if (typeof event.conversation_id === 'string' && event.conversation_id.length > 0) {
|
|
192
|
+
return event.conversation_id;
|
|
193
|
+
}
|
|
194
|
+
const init = nestedRecord(event, 'init');
|
|
195
|
+
return typeof init?.conversation_id === 'string' && init.conversation_id.length > 0
|
|
196
|
+
? init.conversation_id
|
|
197
|
+
: undefined;
|
|
198
|
+
}
|
|
199
|
+
function stepUpdateOf(event) {
|
|
200
|
+
return event.event === 'step_update' ? nestedRecord(event, 'step_update') : undefined;
|
|
201
|
+
}
|
|
202
|
+
/** Return AGY's step type without constraining future event payloads. */
|
|
203
|
+
export function stepTypeOf(event) {
|
|
204
|
+
const update = stepUpdateOf(event);
|
|
205
|
+
return typeof update?.step_type === 'string' ? update.step_type : undefined;
|
|
206
|
+
}
|
|
207
|
+
/** Extract an explicit AGY tool name from a step update, when present. */
|
|
208
|
+
export function toolNameOf(event) {
|
|
209
|
+
const update = stepUpdateOf(event);
|
|
210
|
+
if (typeof update?.tool_name === 'string')
|
|
211
|
+
return update.tool_name;
|
|
212
|
+
if (typeof event.tool_name === 'string')
|
|
213
|
+
return event.tool_name;
|
|
214
|
+
for (const key of ['tool_call', 'tool_result']) {
|
|
215
|
+
const nested = nestedRecord(event, key);
|
|
216
|
+
if (typeof nested?.tool_name === 'string')
|
|
217
|
+
return nested.tool_name;
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
function decodeNestedString(value) {
|
|
222
|
+
let current = value.trim();
|
|
223
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
224
|
+
if (!(current.startsWith('"') && current.endsWith('"')))
|
|
225
|
+
break;
|
|
226
|
+
try {
|
|
227
|
+
const decoded = JSON.parse(current);
|
|
228
|
+
if (typeof decoded !== 'string')
|
|
229
|
+
break;
|
|
230
|
+
current = decoded.trim();
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return current;
|
|
237
|
+
}
|
|
238
|
+
function findEncodedFieldCandidates(value, field, candidates = [], depth = 0) {
|
|
239
|
+
if (depth > 8 || value === null || typeof value !== 'object')
|
|
240
|
+
return candidates;
|
|
241
|
+
if (Array.isArray(value)) {
|
|
242
|
+
for (const child of value)
|
|
243
|
+
findEncodedFieldCandidates(child, field, candidates, depth + 1);
|
|
244
|
+
return candidates;
|
|
245
|
+
}
|
|
246
|
+
for (const [key, child] of Object.entries(value)) {
|
|
247
|
+
if (normalizedKey(key) === normalizedKey(field) && typeof child === 'string') {
|
|
248
|
+
candidates.push(decodeNestedString(child));
|
|
249
|
+
}
|
|
250
|
+
findEncodedFieldCandidates(child, field, candidates, depth + 1);
|
|
251
|
+
}
|
|
252
|
+
return candidates;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Return one unambiguous encoded field. AGY may wrap tool arguments in several
|
|
256
|
+
* layers; equal duplicates are harmless, but conflicting values must not be
|
|
257
|
+
* resolved by whichever branch happens to be visited first.
|
|
258
|
+
*/
|
|
259
|
+
function findEncodedField(value, field) {
|
|
260
|
+
const candidates = [...new Set(findEncodedFieldCandidates(value, field))];
|
|
261
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
262
|
+
}
|
|
263
|
+
function hasField(value, field, depth = 0) {
|
|
264
|
+
if (depth > 8 || value === null || typeof value !== 'object')
|
|
265
|
+
return false;
|
|
266
|
+
if (Array.isArray(value))
|
|
267
|
+
return value.some(child => hasField(child, field, depth + 1));
|
|
268
|
+
const target = normalizedKey(field);
|
|
269
|
+
for (const [key, child] of Object.entries(value)) {
|
|
270
|
+
if (normalizedKey(key) === target)
|
|
271
|
+
return true;
|
|
272
|
+
if (hasField(child, field, depth + 1))
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
/** Extract the narrowly-scoped AGY send_message carrier used by DSH-owned mode. */
|
|
278
|
+
export function sendMessageOf(event) {
|
|
279
|
+
if (toolNameOf(event) !== 'send_message')
|
|
280
|
+
return undefined;
|
|
281
|
+
const recipient = findEncodedField(event, 'recipient');
|
|
282
|
+
const message = findEncodedField(event, 'message');
|
|
283
|
+
if (recipient === undefined || message === undefined)
|
|
284
|
+
return undefined;
|
|
285
|
+
return { recipient, message };
|
|
286
|
+
}
|
|
287
|
+
function stepNameOf(event) {
|
|
288
|
+
const update = stepUpdateOf(event);
|
|
289
|
+
return typeof update?.name === 'string' ? update.name : undefined;
|
|
290
|
+
}
|
|
291
|
+
/** Classify only explicit AGY tool lifecycle signals. */
|
|
292
|
+
export function toolEventKindOf(event) {
|
|
293
|
+
if (event.event === 'tool_call')
|
|
294
|
+
return 'event-tool-call';
|
|
295
|
+
if (event.event === 'tool_result')
|
|
296
|
+
return 'event-tool-result';
|
|
297
|
+
if (stepTypeOf(event) === 'tool')
|
|
298
|
+
return 'step-type-tool';
|
|
299
|
+
if (toolNameOf(event) !== undefined)
|
|
300
|
+
return 'explicit-tool-name';
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
/** Recognize internal AGY tool lifecycle events without mapping them to DSH tools. */
|
|
304
|
+
export function isToolEvent(event) {
|
|
305
|
+
return toolEventKindOf(event) !== undefined;
|
|
306
|
+
}
|
|
307
|
+
/** Classify a tool event's carrier shape without retaining any payload values. */
|
|
308
|
+
export function toolEventDiagnosticOf(event, activeConversationId) {
|
|
309
|
+
const kind = toolEventKindOf(event);
|
|
310
|
+
if (kind === undefined)
|
|
311
|
+
return undefined;
|
|
312
|
+
const step = stepUpdateOf(event);
|
|
313
|
+
const stepType = stepTypeOf(event);
|
|
314
|
+
const toolName = toolNameOf(event);
|
|
315
|
+
const isSendMessage = toolName === 'send_message';
|
|
316
|
+
const hasRecipient = isSendMessage && hasField(event, 'recipient');
|
|
317
|
+
const hasMessage = isSendMessage && hasField(event, 'message');
|
|
318
|
+
const recipient = isSendMessage ? findEncodedField(event, 'recipient') : undefined;
|
|
319
|
+
const message = isSendMessage ? findEncodedField(event, 'message') : undefined;
|
|
320
|
+
let carrierShape = 'not-send-message';
|
|
321
|
+
if (isSendMessage) {
|
|
322
|
+
if (!hasRecipient)
|
|
323
|
+
carrierShape = 'missing-recipient';
|
|
324
|
+
else if (!hasMessage)
|
|
325
|
+
carrierShape = 'missing-message';
|
|
326
|
+
else if (recipient === undefined)
|
|
327
|
+
carrierShape = 'unreadable-recipient';
|
|
328
|
+
else if (message === undefined)
|
|
329
|
+
carrierShape = 'unreadable-message';
|
|
330
|
+
else
|
|
331
|
+
carrierShape = 'complete';
|
|
332
|
+
}
|
|
333
|
+
const recipientClass = !isSendMessage
|
|
334
|
+
? 'not-applicable'
|
|
335
|
+
: recipient === undefined
|
|
336
|
+
? 'missing'
|
|
337
|
+
: recipient === 'default_api'
|
|
338
|
+
? 'default-api'
|
|
339
|
+
: isDshCarrierRecipient(recipient)
|
|
340
|
+
? 'dsh-recipient'
|
|
341
|
+
: activeConversationId !== undefined && recipient === activeConversationId
|
|
342
|
+
? 'self-conversation'
|
|
343
|
+
: activeConversationId !== undefined
|
|
344
|
+
? 'other-conversation'
|
|
345
|
+
: 'other';
|
|
346
|
+
const toolInput = decodeRecord(valueForKey(step, 'tool_input'))
|
|
347
|
+
?? decodeRecord(valueForKey(event, 'tool_input'))
|
|
348
|
+
?? decodeRecord(valueForKey(event, 'arguments'))
|
|
349
|
+
?? decodeRecord(valueForKey(event, 'input'));
|
|
350
|
+
return {
|
|
351
|
+
eventName: event.event,
|
|
352
|
+
kind,
|
|
353
|
+
...(stepType === undefined ? {} : { stepType }),
|
|
354
|
+
...(toolName === undefined ? {} : { toolName }),
|
|
355
|
+
carrierShape,
|
|
356
|
+
recipientClass,
|
|
357
|
+
topLevelKeys: structuralKeys(event),
|
|
358
|
+
stepKeys: structuralKeys(step),
|
|
359
|
+
toolInputKeys: structuralKeys(toolInput),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/** Recognize permission checkpoints that cannot be answered by a headless Provider. */
|
|
363
|
+
export function isPermissionEvent(event) {
|
|
364
|
+
const stepType = stepTypeOf(event);
|
|
365
|
+
return event.event === 'permission_request'
|
|
366
|
+
|| event.event === 'ask_permission'
|
|
367
|
+
|| stepType === 'permission'
|
|
368
|
+
|| stepType === 'permission_request'
|
|
369
|
+
|| stepType === 'ask_permission'
|
|
370
|
+
|| toolNameOf(event) === 'ask_permission'
|
|
371
|
+
|| stepNameOf(event) === 'ask_permission';
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Classify only stable event categories; unknown event names remain forward-compatible.
|
|
375
|
+
*
|
|
376
|
+
* A step_update carries its own step_type, so a nested `agent_response` update is
|
|
377
|
+
* counted as `agent_response` rather than being swallowed by the outer `step_update`
|
|
378
|
+
* case. Counts must reflect what the model actually emitted: reading `agent_response`
|
|
379
|
+
* as `step_update` made telemetry report zero streamed text on every turn, even
|
|
380
|
+
* though AGY streams text deltas natively.
|
|
381
|
+
*/
|
|
382
|
+
export function eventCategoryOf(event) {
|
|
383
|
+
if (isPermissionEvent(event))
|
|
384
|
+
return 'permission';
|
|
385
|
+
if (isToolEvent(event))
|
|
386
|
+
return 'tool';
|
|
387
|
+
switch (event.event) {
|
|
388
|
+
case 'init':
|
|
389
|
+
return 'init';
|
|
390
|
+
case 'step_update': {
|
|
391
|
+
if (stepTypeOf(event) === 'agent_response')
|
|
392
|
+
return 'agent_response';
|
|
393
|
+
return 'step_update';
|
|
394
|
+
}
|
|
395
|
+
case 'checkpoint':
|
|
396
|
+
return 'checkpoint';
|
|
397
|
+
case 'agent_response':
|
|
398
|
+
return 'agent_response';
|
|
399
|
+
case 'result':
|
|
400
|
+
return 'result';
|
|
401
|
+
case 'error':
|
|
402
|
+
case 'error_message':
|
|
403
|
+
return 'error';
|
|
404
|
+
default:
|
|
405
|
+
return 'unknown';
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Bounded JSON of AGY's own `result` frame.
|
|
410
|
+
*
|
|
411
|
+
* `status: "ERROR"` is the only signal the adapter gets on the long-context
|
|
412
|
+
* failures this provider sees, and AGY omits `error` on those frames, so the
|
|
413
|
+
* `response` text and the remaining keys are the sole surviving diagnosis.
|
|
414
|
+
* Keys that carry bulk are truncated rather than dropped: a missing key and a
|
|
415
|
+
* truncated key look identical in telemetry and would be indistinguishable from
|
|
416
|
+
* "AGY said nothing".
|
|
417
|
+
*/
|
|
418
|
+
export function finalResultRawOf(event) {
|
|
419
|
+
if (event.event !== 'result')
|
|
420
|
+
return undefined;
|
|
421
|
+
const result = isRecord(event.result) ? event.result : undefined;
|
|
422
|
+
if (result === undefined)
|
|
423
|
+
return '{}';
|
|
424
|
+
const bounded = {};
|
|
425
|
+
for (const [key, value] of Object.entries(result)) {
|
|
426
|
+
if (typeof value === 'string')
|
|
427
|
+
bounded[key] = value.length > 512 ? `${value.slice(0, 512)}…(${value.length} chars)` : value;
|
|
428
|
+
else if (isRecord(value))
|
|
429
|
+
bounded[key] = '<object>';
|
|
430
|
+
else
|
|
431
|
+
bounded[key] = value;
|
|
432
|
+
}
|
|
433
|
+
return JSON.stringify(bounded).slice(0, 1_024);
|
|
434
|
+
}
|
|
435
|
+
/** Extract bounded classification detail from AGY error events without exposing the payload. */
|
|
436
|
+
export function errorDetailOf(event) {
|
|
437
|
+
if (event.event !== 'error' && event.event !== 'error_message')
|
|
438
|
+
return undefined;
|
|
439
|
+
const values = [];
|
|
440
|
+
const direct = event[event.event];
|
|
441
|
+
if (typeof direct === 'string' && direct.trim().length > 0)
|
|
442
|
+
values.push(direct.trim());
|
|
443
|
+
const containers = [event];
|
|
444
|
+
if (isRecord(direct))
|
|
445
|
+
containers.push(direct);
|
|
446
|
+
const error = nestedRecord(event, 'error');
|
|
447
|
+
if (error !== undefined)
|
|
448
|
+
containers.push(error);
|
|
449
|
+
const errorMessage = nestedRecord(event, 'error_message');
|
|
450
|
+
if (errorMessage !== undefined)
|
|
451
|
+
containers.push(errorMessage);
|
|
452
|
+
for (const container of containers) {
|
|
453
|
+
for (const key of ['code', 'type', 'message', 'detail', 'reason', 'text', 'error']) {
|
|
454
|
+
const value = container[key];
|
|
455
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
456
|
+
values.push(value.trim());
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const unique = [...new Set(values)];
|
|
460
|
+
return unique.length === 0 ? undefined : unique.join(' ').slice(0, 2_048);
|
|
461
|
+
}
|
|
462
|
+
/** Extract a text delta from the observed AGY step_update envelope. */
|
|
463
|
+
export function textDeltaOf(event) {
|
|
464
|
+
if (event.event !== 'step_update')
|
|
465
|
+
return undefined;
|
|
466
|
+
const update = nestedRecord(event, 'step_update');
|
|
467
|
+
return typeof update?.text_delta === 'string' ? update.text_delta : undefined;
|
|
468
|
+
}
|
|
469
|
+
/** Extract the final response text from a result event. */
|
|
470
|
+
export function responseOf(event) {
|
|
471
|
+
if (event.event !== 'result')
|
|
472
|
+
return undefined;
|
|
473
|
+
const result = nestedRecord(event, 'result');
|
|
474
|
+
return typeof result?.response === 'string' ? result.response : undefined;
|
|
475
|
+
}
|
|
476
|
+
/** Extract the final AGY result status without constraining future statuses. */
|
|
477
|
+
export function statusOf(event) {
|
|
478
|
+
if (event.event !== 'result')
|
|
479
|
+
return undefined;
|
|
480
|
+
const result = nestedRecord(event, 'result');
|
|
481
|
+
return typeof result?.status === 'string' ? result.status : undefined;
|
|
482
|
+
}
|
|
483
|
+
/** Extract usage as an opaque object; AGY may add fields across versions. */
|
|
484
|
+
export function usageOf(event) {
|
|
485
|
+
const container = event.event === 'result'
|
|
486
|
+
? nestedRecord(event, 'result')
|
|
487
|
+
: event.event === 'step_update'
|
|
488
|
+
? nestedRecord(event, 'step_update')
|
|
489
|
+
: undefined;
|
|
490
|
+
return nestedRecord(container ?? {}, 'usage');
|
|
491
|
+
}
|
|
492
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type AgyReasoningEffort } from './process.js';
|
|
2
|
+
export type PersistentTransportErrorCode = 'ABORTED' | 'DISPOSED' | 'EVENT_HANDLER_FAILED' | 'FRAME_TOO_LARGE' | 'PROTOCOL_ERROR' | 'TIMEOUT' | 'WORKER_BUSY' | 'WORKER_CRASHED' | 'WORKER_ERROR' | 'WORKER_LIMIT' | 'WORKER_START_FAILED' | 'WORKER_STOPPED' | 'OUTPUT_LIMIT' | 'CONVERSATION_MISMATCH';
|
|
3
|
+
export declare class PersistentTransportError extends Error {
|
|
4
|
+
readonly code: PersistentTransportErrorCode;
|
|
5
|
+
constructor(message: string, code: PersistentTransportErrorCode, options?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
export type PersistentWorkerState = 'starting' | 'idle' | 'busy' | 'stopping' | 'stopped';
|
|
8
|
+
export interface PersistentRequestFrame {
|
|
9
|
+
readonly kind: 'request';
|
|
10
|
+
readonly requestId: string;
|
|
11
|
+
readonly sessionId: string;
|
|
12
|
+
readonly text: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PersistentShutdownFrame {
|
|
15
|
+
readonly kind: 'shutdown';
|
|
16
|
+
}
|
|
17
|
+
export type PersistentOutgoingFrame = PersistentRequestFrame | PersistentShutdownFrame;
|
|
18
|
+
export interface PersistentIncomingFrame {
|
|
19
|
+
readonly kind: 'init' | 'step_update' | 'result';
|
|
20
|
+
readonly conversationId?: string;
|
|
21
|
+
readonly raw?: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface PersistentTransportRequest {
|
|
24
|
+
readonly sessionId: string;
|
|
25
|
+
readonly text: string;
|
|
26
|
+
readonly signal?: AbortSignal;
|
|
27
|
+
readonly timeoutMs?: number;
|
|
28
|
+
/** Inactivity budget re-armed on every received frame; omitted uses timeoutMs. */
|
|
29
|
+
readonly idleTimeoutMs?: number;
|
|
30
|
+
readonly maxOutputBytes?: number;
|
|
31
|
+
readonly onEvent?: (payload: unknown) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Route for a *newly spawned* worker. AGY takes --agent/--model/--effort at
|
|
34
|
+
* process start, so a worker cannot answer a turn for a different model than
|
|
35
|
+
* the one it was launched with. These values therefore also key the worker
|
|
36
|
+
* pool: a request whose route differs from the live worker's gets its own
|
|
37
|
+
* process instead of silently answering with the wrong model.
|
|
38
|
+
*/
|
|
39
|
+
readonly agent?: string;
|
|
40
|
+
readonly model?: string;
|
|
41
|
+
readonly reasoningEffort?: AgyReasoningEffort;
|
|
42
|
+
/**
|
|
43
|
+
* Existing AGY conversation this worker must continue. A freshly spawned
|
|
44
|
+
* worker is launched with `--conversation <id>`; if AGY answers with a
|
|
45
|
+
* different conversation_id the request fails with CONVERSATION_MISMATCH
|
|
46
|
+
* before any delta is emitted, so the caller can fall back to full history
|
|
47
|
+
* instead of silently answering without context.
|
|
48
|
+
*/
|
|
49
|
+
readonly conversation?: string;
|
|
50
|
+
/** Extra argv appended after the route args (mode, slash-command switches). */
|
|
51
|
+
readonly extraArgs?: readonly string[];
|
|
52
|
+
}
|
|
53
|
+
export interface PersistentTransportResult {
|
|
54
|
+
readonly requestId: string;
|
|
55
|
+
readonly sessionId: string;
|
|
56
|
+
readonly events: readonly unknown[];
|
|
57
|
+
readonly durationMs: number;
|
|
58
|
+
readonly firstEventMs?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface ExperimentalAgyTransportOptions {
|
|
61
|
+
readonly executable: string;
|
|
62
|
+
readonly args?: readonly string[];
|
|
63
|
+
readonly cwd?: string;
|
|
64
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
65
|
+
readonly maxWorkers?: number;
|
|
66
|
+
readonly maxFrameBytes?: number;
|
|
67
|
+
readonly maxOutputBytes?: number;
|
|
68
|
+
readonly maxStderrBytes?: number;
|
|
69
|
+
readonly idleTtlMs?: number;
|
|
70
|
+
readonly readyTimeoutMs?: number;
|
|
71
|
+
readonly shutdownTimeoutMs?: number;
|
|
72
|
+
readonly defaultRequestTimeoutMs?: number;
|
|
73
|
+
}
|
|
74
|
+
export interface PersistentTransportStats {
|
|
75
|
+
readonly totalWorkers: number;
|
|
76
|
+
readonly startingWorkers: number;
|
|
77
|
+
readonly idleWorkers: number;
|
|
78
|
+
readonly busyWorkers: number;
|
|
79
|
+
readonly stoppingWorkers: number;
|
|
80
|
+
readonly maxWorkers: number;
|
|
81
|
+
readonly disposed: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** Encode one bounded NDJSON frame, including its trailing newline. */
|
|
84
|
+
export declare function encodePersistentFrame(frame: PersistentOutgoingFrame, maxFrameBytes: number): string;
|
|
85
|
+
/** Parse and validate the small protocol envelope; payload remains opaque. */
|
|
86
|
+
export declare function parsePersistentFrame(line: string): PersistentIncomingFrame;
|
|
87
|
+
/**
|
|
88
|
+
* Worker-per-route persistent transport. One AGY process per (session, route);
|
|
89
|
+
* the worker holds a single active turn and is keyed by the route because AGY
|
|
90
|
+
* fixes --agent/--model/--effort at process start.
|
|
91
|
+
*/
|
|
92
|
+
export declare class ExperimentalAgyTransport {
|
|
93
|
+
private readonly options;
|
|
94
|
+
private readonly workers;
|
|
95
|
+
private disposed;
|
|
96
|
+
constructor(options: ExperimentalAgyTransportOptions);
|
|
97
|
+
request(input: PersistentTransportRequest): Promise<PersistentTransportResult>;
|
|
98
|
+
dispose(): Promise<void>;
|
|
99
|
+
getStats(): PersistentTransportStats;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=persistent-transport.d.ts.map
|