dsh-agy-cli 0.0.0-stage → 0.12.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 (91) hide show
  1. package/LICENSE +30 -0
  2. package/README.en.md +27 -0
  3. package/README.md +94 -2
  4. package/agents/dsh-agy-image-view.md +17 -0
  5. package/agents/dsh-agy-mcp-executor.md +32 -0
  6. package/agents/dsh-agy-read-only.md +20 -0
  7. package/agents/dsh-agy-tool-free.md +16 -0
  8. package/agents/dsh-agy-workspace-write.md +23 -0
  9. package/bin/dsh-agy-cli.js +9 -0
  10. package/bin/win32-x64/agy-launcher.exe +0 -0
  11. package/cordis.patch.yml +16 -0
  12. package/dist/client.js +4259 -0
  13. package/lib/agent-installer.d.ts +66 -0
  14. package/lib/agent-installer.js +147 -0
  15. package/lib/agent-presets.d.ts +42 -0
  16. package/lib/agent-presets.js +130 -0
  17. package/lib/agents-cli.d.ts +3 -0
  18. package/lib/agents-cli.js +110 -0
  19. package/lib/agy/diagnostics.d.ts +29 -0
  20. package/lib/agy/diagnostics.js +109 -0
  21. package/lib/agy/errors.d.ts +20 -0
  22. package/lib/agy/errors.js +77 -0
  23. package/lib/agy/experimental-transport.d.ts +79 -0
  24. package/lib/agy/experimental-transport.js +612 -0
  25. package/lib/agy/keepalive.d.ts +140 -0
  26. package/lib/agy/keepalive.js +424 -0
  27. package/lib/agy/limiter.d.ts +32 -0
  28. package/lib/agy/limiter.js +111 -0
  29. package/lib/agy/log.d.ts +214 -0
  30. package/lib/agy/log.js +503 -0
  31. package/lib/agy/models.d.ts +49 -0
  32. package/lib/agy/models.js +203 -0
  33. package/lib/agy/overrides.d.ts +17 -0
  34. package/lib/agy/overrides.js +73 -0
  35. package/lib/agy/parser.d.ts +102 -0
  36. package/lib/agy/parser.js +492 -0
  37. package/lib/agy/persistent-transport.d.ts +101 -0
  38. package/lib/agy/persistent-transport.js +753 -0
  39. package/lib/agy/process.d.ts +144 -0
  40. package/lib/agy/process.js +452 -0
  41. package/lib/agy/redact.d.ts +3 -0
  42. package/lib/agy/redact.js +24 -0
  43. package/lib/agy/stream-protocol.d.ts +50 -0
  44. package/lib/agy/stream-protocol.js +156 -0
  45. package/lib/agy/windows-launcher.d.ts +21 -0
  46. package/lib/agy/windows-launcher.js +76 -0
  47. package/lib/diagnostics.d.ts +78 -0
  48. package/lib/diagnostics.js +162 -0
  49. package/lib/doctor.d.ts +138 -0
  50. package/lib/doctor.js +923 -0
  51. package/lib/dsh/context.d.ts +133 -0
  52. package/lib/dsh/context.js +352 -0
  53. package/lib/dsh-home.d.ts +6 -0
  54. package/lib/dsh-home.js +11 -0
  55. package/lib/index.d.ts +43 -0
  56. package/lib/index.js +256 -0
  57. package/lib/mcp/agy-registration.d.ts +70 -0
  58. package/lib/mcp/agy-registration.js +245 -0
  59. package/lib/mcp/bridge.d.ts +73 -0
  60. package/lib/mcp/bridge.js +295 -0
  61. package/lib/mcp/endpoint.d.ts +41 -0
  62. package/lib/mcp/endpoint.js +142 -0
  63. package/lib/mcp/protocol.d.ts +107 -0
  64. package/lib/mcp/protocol.js +197 -0
  65. package/lib/mcp/tool-projection.d.ts +64 -0
  66. package/lib/mcp/tool-projection.js +168 -0
  67. package/lib/provider/agy.d.ts +153 -0
  68. package/lib/provider/agy.js +2159 -0
  69. package/lib/provider/config.d.ts +226 -0
  70. package/lib/provider/config.js +294 -0
  71. package/lib/provider/error-codes.d.ts +22 -0
  72. package/lib/provider/error-codes.js +29 -0
  73. package/lib/provider/image-bridge.d.ts +60 -0
  74. package/lib/provider/image-bridge.js +290 -0
  75. package/lib/provider/mock.d.ts +15 -0
  76. package/lib/provider/mock.js +78 -0
  77. package/lib/provider/prompt-budget.d.ts +45 -0
  78. package/lib/provider/prompt-budget.js +596 -0
  79. package/lib/provider/serialize.d.ts +15 -0
  80. package/lib/provider/serialize.js +82 -0
  81. package/lib/provider/tool-protocol.d.ts +80 -0
  82. package/lib/provider/tool-protocol.js +848 -0
  83. package/lib/provider/tool-schema-file.d.ts +11 -0
  84. package/lib/provider/tool-schema-file.js +27 -0
  85. package/lib/session/file-store.d.ts +56 -0
  86. package/lib/session/file-store.js +186 -0
  87. package/lib/session/store.d.ts +49 -0
  88. package/lib/session/store.js +62 -0
  89. package/lib/web/router.d.ts +19 -0
  90. package/lib/web/router.js +906 -0
  91. package/package.json +112 -4
package/lib/agy/log.js ADDED
@@ -0,0 +1,503 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { appendFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { isAgyReasoningEffort, } from './process.js';
6
+ import { redactText } from './redact.js';
7
+ import { isAgyToolEventKind, } from './parser.js';
8
+ import { isModelDiscoveryErrorCode, } from '../provider/error-codes.js';
9
+ const PROTOCOL_ARGUMENT_ISSUES = [
10
+ 'missing-required',
11
+ 'unexpected-property',
12
+ 'type',
13
+ 'enum',
14
+ 'constraint',
15
+ 'combinator',
16
+ 'unknown',
17
+ ];
18
+ const PROTOCOL_COMPATIBILITIES = [
19
+ 'pwsh-description-default',
20
+ 'json-control-character-escape',
21
+ 'agy-call-envelope',
22
+ 'agy-command-envelope',
23
+ 'agy-thought-call-envelope',
24
+ 'agy-bare-call-envelope',
25
+ ];
26
+ function isAgyProtocolArgumentIssue(value) {
27
+ return typeof value === 'string'
28
+ && PROTOCOL_ARGUMENT_ISSUES.includes(value);
29
+ }
30
+ function isToolProtocolCompatibility(value) {
31
+ return typeof value === 'string'
32
+ && PROTOCOL_COMPATIBILITIES.includes(value);
33
+ }
34
+ function safeProtocolKeyList(value) {
35
+ if (!Array.isArray(value))
36
+ return undefined;
37
+ return value.slice(0, 16).map(key => typeof key === 'string' && /^[A-Za-z0-9_.:-]{1,128}$/.test(key)
38
+ ? key
39
+ : '[redacted]');
40
+ }
41
+ const BRIDGE_OUTCOMES = [
42
+ 'text-only',
43
+ 'mcp-tools',
44
+ 'dsh-pending',
45
+ 'dsh-message',
46
+ 'dsh-tool-call',
47
+ 'agy-owned',
48
+ 'context-rejected',
49
+ 'schema-rejected',
50
+ 'protocol-rejected',
51
+ 'agy-internal-tool',
52
+ 'permission-required',
53
+ 'failed',
54
+ ];
55
+ function isAgyBridgeOutcome(value) {
56
+ return typeof value === 'string' && BRIDGE_OUTCOMES.includes(value);
57
+ }
58
+ function isAgyPermissionPreset(value) {
59
+ return value === 'read-only' || value === 'workspace-write' || value === 'danger-full-access';
60
+ }
61
+ function isAgyApprovalPolicy(value) {
62
+ return value === 'ask' || value === 'never';
63
+ }
64
+ const MODEL_DISCOVERY_LOG_SOURCES = [
65
+ 'static',
66
+ 'discovered',
67
+ 'merged',
68
+ 'cache',
69
+ 'fallback',
70
+ ];
71
+ function isModelDiscoveryLogSource(value) {
72
+ return typeof value === 'string'
73
+ && MODEL_DISCOVERY_LOG_SOURCES.includes(value);
74
+ }
75
+ /**
76
+ * Temporary test-observability sink. The caller must pass an already
77
+ * sanitized record; this file is intentionally outside the DSH session store
78
+ * because that store currently drops Error.cause and nested diagnostics.
79
+ */
80
+ export const AGY_DIAGNOSTIC_LOG_PATH = join(tmpdir(), 'dsh-agy-cli-diagnostic.jsonl');
81
+ export function appendAgyDiagnosticRecord(record) {
82
+ try {
83
+ appendFileSync(AGY_DIAGNOSTIC_LOG_PATH, `${JSON.stringify(record)}\n`, 'utf8');
84
+ }
85
+ catch {
86
+ // Diagnostic persistence must never affect a model request.
87
+ }
88
+ }
89
+ function fingerprint(value) {
90
+ return createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 16);
91
+ }
92
+ function baseRecord(telemetry) {
93
+ return {
94
+ event: 'agy.request.started',
95
+ requestId: telemetry.requestId,
96
+ provider: telemetry.provider,
97
+ model: telemetry.model,
98
+ agent: telemetry.agent,
99
+ toolPolicy: telemetry.toolPolicy,
100
+ toolSchemaCount: telemetry.toolSchemaCount,
101
+ toolCallCount: telemetry.toolCallCount,
102
+ bridgeOutcome: telemetry.bridgeOutcome,
103
+ ...(telemetry.permissionPreset === undefined ? {} : { permissionPreset: telemetry.permissionPreset }),
104
+ ...(telemetry.sandboxMode === undefined ? {} : { sandboxMode: telemetry.sandboxMode }),
105
+ ...(telemetry.approvalPolicy === undefined ? {} : { approvalPolicy: telemetry.approvalPolicy }),
106
+ ...(telemetry.reasoningEffort === undefined ? {} : { reasoningEffort: telemetry.reasoningEffort }),
107
+ ...(telemetry.purpose === undefined ? {} : { purpose: telemetry.purpose }),
108
+ ...(telemetry.modelDiscoverySource === undefined ? {} : { modelDiscoverySource: telemetry.modelDiscoverySource }),
109
+ ...(telemetry.modelDiscoveryWarningCode === undefined ? {} : { modelDiscoveryWarningCode: telemetry.modelDiscoveryWarningCode }),
110
+ attempt: telemetry.attempt,
111
+ processAttemptCount: telemetry.processAttemptCount,
112
+ retryMaxRetries: telemetry.retryMaxRetries,
113
+ ...(telemetry.inputFrameBytes === undefined ? {} : { inputFrameBytes: telemetry.inputFrameBytes }),
114
+ ...(telemetry.inputFrameLimitBytes === undefined ? {} : { inputFrameLimitBytes: telemetry.inputFrameLimitBytes }),
115
+ ...(telemetry.promptBytes === undefined ? {} : { promptBytes: telemetry.promptBytes }),
116
+ ...(telemetry.promptLimitBytes === undefined ? {} : { promptLimitBytes: telemetry.promptLimitBytes }),
117
+ ...(telemetry.toolResultCount === undefined ? {} : { toolResultCount: telemetry.toolResultCount }),
118
+ ...(telemetry.largestToolResultBytes === undefined ? {} : { largestToolResultBytes: telemetry.largestToolResultBytes }),
119
+ ...(telemetry.truncatedToolResultCount === undefined ? {} : { truncatedToolResultCount: telemetry.truncatedToolResultCount }),
120
+ ...(telemetry.historyCompacted ? { historyCompacted: true } : {}),
121
+ ...(telemetry.omittedMessageCount === 0 ? {} : { omittedMessageCount: telemetry.omittedMessageCount }),
122
+ ...(telemetry.toolEventKind === undefined ? {} : { toolEventKind: telemetry.toolEventKind }),
123
+ ...(telemetry.toolEventStreamIndex === undefined ? {} : { toolEventStreamIndex: telemetry.toolEventStreamIndex }),
124
+ ...(telemetry.toolEventDiagnostic === undefined ? {} : { toolEventDiagnostic: telemetry.toolEventDiagnostic }),
125
+ ...(telemetry.carrierAbortExpected ? { carrierAbortExpected: true } : {}),
126
+ ...(telemetry.carrierValidation === undefined ? {} : { carrierValidation: telemetry.carrierValidation }),
127
+ protocolRepairAttempts: telemetry.protocolRepairAttempts,
128
+ ...(telemetry.protocolRepairReason === undefined ? {} : { protocolRepairReason: telemetry.protocolRepairReason }),
129
+ ...(telemetry.protocolFailureDetail === undefined ? {} : { protocolFailureDetail: telemetry.protocolFailureDetail }),
130
+ ...(telemetry.protocolResponseShape === undefined ? {} : { protocolResponseShape: telemetry.protocolResponseShape }),
131
+ ...(telemetry.protocolResponseBytes === undefined ? {} : { protocolResponseBytes: telemetry.protocolResponseBytes }),
132
+ ...(telemetry.protocolToolName === undefined ? {} : { protocolToolName: telemetry.protocolToolName }),
133
+ ...(telemetry.protocolArgumentIssue === undefined ? {} : { protocolArgumentIssue: telemetry.protocolArgumentIssue }),
134
+ ...(telemetry.protocolMissingRequiredKeys === undefined ? {} : { protocolMissingRequiredKeys: telemetry.protocolMissingRequiredKeys }),
135
+ ...(telemetry.protocolReceivedArgumentKeys === undefined ? {} : { protocolReceivedArgumentKeys: telemetry.protocolReceivedArgumentKeys }),
136
+ ...(telemetry.protocolCompatibilityApplied === undefined ? {} : { protocolCompatibilityApplied: telemetry.protocolCompatibilityApplied }),
137
+ eventCount: telemetry.eventCount,
138
+ toolEventCount: telemetry.toolEventCount,
139
+ permissionEventCount: telemetry.permissionEventCount,
140
+ eventCategoryCounts: { ...telemetry.eventCategoryCounts },
141
+ ...(telemetry.dispatch === undefined ? {} : { dispatch: telemetry.dispatch }),
142
+ };
143
+ }
144
+ /** Build a whitelist-only record; prompt, stderr, env and executable are absent by design. */
145
+ export function buildAgyLogRecord(telemetry, event, errorCode) {
146
+ const process = telemetry.process;
147
+ const record = {
148
+ ...baseRecord(telemetry),
149
+ event,
150
+ ...(telemetry.queueWaitMs === undefined ? {} : { queueWaitMs: telemetry.queueWaitMs }),
151
+ ...(telemetry.firstEventMs === undefined ? {} : { firstEventMs: telemetry.firstEventMs }),
152
+ ...(telemetry.firstTextMs === undefined ? {} : { firstTextMs: telemetry.firstTextMs }),
153
+ ...(telemetry.finalStatus === undefined ? {} : { finalStatus: telemetry.finalStatus }),
154
+ ...(telemetry.finalResultRaw === undefined ? {} : { finalResultRaw: telemetry.finalResultRaw }),
155
+ ...(telemetry.usage === undefined ? {} : { usage: telemetry.usage }),
156
+ ...(telemetry.usageSource === undefined ? {} : { usageSource: telemetry.usageSource }),
157
+ ...(telemetry.cumulativeUsage === undefined ? {} : { cumulativeUsage: telemetry.cumulativeUsage }),
158
+ ...(telemetry.cacheHit === undefined ? {} : { cacheHit: telemetry.cacheHit }),
159
+ ...(telemetry.cacheTokenShare === undefined ? {} : { cacheTokenShare: telemetry.cacheTokenShare }),
160
+ ...(telemetry.requestMode === undefined ? {} : { requestMode: telemetry.requestMode }),
161
+ ...(telemetry.conversationReused === undefined ? {} : { conversationReused: telemetry.conversationReused }),
162
+ ...(telemetry.conversationMismatch === undefined ? {} : { conversationMismatch: telemetry.conversationMismatch }),
163
+ ...(telemetry.partialAnswerDelivered === undefined ? {} : { partialAnswerDelivered: telemetry.partialAnswerDelivered }),
164
+ ...(telemetry.stablePrefixBytes === undefined ? {} : { stablePrefixBytes: telemetry.stablePrefixBytes }),
165
+ ...(telemetry.stablePrefixHash === undefined ? {} : { stablePrefixHash: telemetry.stablePrefixHash }),
166
+ ...(telemetry.toolSchemaHash === undefined ? {} : { toolSchemaHash: telemetry.toolSchemaHash }),
167
+ ...(telemetry.sessionId === undefined ? {} : { sessionFingerprint: fingerprint(telemetry.sessionId) }),
168
+ ...(telemetry.conversationId === undefined ? {} : { conversationFingerprint: fingerprint(telemetry.conversationId) }),
169
+ ...(telemetry.processDiagnostic === undefined ? {} : { processDiagnostic: telemetry.processDiagnostic }),
170
+ ...(process === undefined ? {} : {
171
+ exitCode: process.exitCode,
172
+ termination: process.termination,
173
+ }),
174
+ ...(telemetry.durationMs === undefined && process === undefined
175
+ ? {}
176
+ : { durationMs: telemetry.durationMs ?? process?.durationMs ?? 0 }),
177
+ ...(errorCode === undefined ? {} : { errorCode }),
178
+ };
179
+ return record;
180
+ }
181
+ const PROCESS_DIAGNOSTIC_STAGES = [
182
+ 'prepare',
183
+ 'spawn',
184
+ 'stdin',
185
+ 'stdout-handler',
186
+ ];
187
+ const TOOL_EVENT_CARRIER_SHAPES = [
188
+ 'not-send-message',
189
+ 'missing-recipient',
190
+ 'missing-message',
191
+ 'unreadable-recipient',
192
+ 'unreadable-message',
193
+ 'complete',
194
+ ];
195
+ const TOOL_EVENT_RECIPIENT_CLASSES = [
196
+ 'not-applicable',
197
+ 'missing',
198
+ 'default-api',
199
+ 'dsh-recipient',
200
+ 'self-conversation',
201
+ 'other-conversation',
202
+ 'other',
203
+ ];
204
+ const CARRIER_VALIDATIONS = [
205
+ 'recipient-rejected',
206
+ 'valid-message',
207
+ 'valid-tool-call',
208
+ 'invalid-envelope',
209
+ 'unknown-tool',
210
+ 'arguments-invalid',
211
+ 'response-limit',
212
+ ];
213
+ const TOOL_EVENT_STRUCTURAL_KEYS = new Set([
214
+ 'event',
215
+ 'init',
216
+ 'step_update',
217
+ 'checkpoint',
218
+ 'agent_response',
219
+ 'result',
220
+ 'tool_call',
221
+ 'tool_result',
222
+ 'permission_request',
223
+ 'error_message',
224
+ 'error',
225
+ 'step_type',
226
+ 'tool_name',
227
+ 'tool_input',
228
+ 'state',
229
+ 'name',
230
+ 'recipient',
231
+ 'message',
232
+ 'arguments',
233
+ 'parameters',
234
+ 'input',
235
+ 'args',
236
+ ]);
237
+ function sanitizeStructuralKeys(value) {
238
+ if (!Array.isArray(value))
239
+ return [];
240
+ const safe = value.filter((key) => (typeof key === 'string' && TOOL_EVENT_STRUCTURAL_KEYS.has(key)));
241
+ return [...new Set(safe)].slice(0, 24);
242
+ }
243
+ function sanitizeToolEventDiagnostic(value) {
244
+ if (typeof value !== 'object' || value === null)
245
+ return undefined;
246
+ const candidate = value;
247
+ const kind = candidate.kind;
248
+ const carrierShape = candidate.carrierShape;
249
+ const recipientClass = candidate.recipientClass;
250
+ if (!isAgyToolEventKind(kind)
251
+ || !TOOL_EVENT_CARRIER_SHAPES.includes(carrierShape)
252
+ || !TOOL_EVENT_RECIPIENT_CLASSES.includes(recipientClass)) {
253
+ return undefined;
254
+ }
255
+ const safeText = (key) => {
256
+ const text = candidate[key];
257
+ return typeof text === 'string' && text.length > 0 ? redactText(text, 128) : undefined;
258
+ };
259
+ const eventName = safeText('eventName');
260
+ if (eventName === undefined)
261
+ return undefined;
262
+ const stepType = safeText('stepType');
263
+ const toolName = safeText('toolName');
264
+ return {
265
+ eventName,
266
+ kind,
267
+ ...(stepType === undefined ? {} : { stepType }),
268
+ ...(toolName === undefined ? {} : { toolName }),
269
+ carrierShape: carrierShape,
270
+ recipientClass: recipientClass,
271
+ topLevelKeys: sanitizeStructuralKeys(candidate.topLevelKeys),
272
+ stepKeys: sanitizeStructuralKeys(candidate.stepKeys),
273
+ toolInputKeys: sanitizeStructuralKeys(candidate.toolInputKeys),
274
+ };
275
+ }
276
+ function sanitizeProcessDiagnostic(value) {
277
+ if (typeof value !== 'object' || value === null)
278
+ return undefined;
279
+ const candidate = value;
280
+ const stage = candidate.stage;
281
+ if (!PROCESS_DIAGNOSTIC_STAGES.includes(stage))
282
+ return undefined;
283
+ const safeStage = stage;
284
+ const safeNumber = (key) => {
285
+ const number = candidate[key];
286
+ return typeof number === 'number' && Number.isSafeInteger(number) && number >= 0 ? number : undefined;
287
+ };
288
+ const safeText = (key, maxLength) => {
289
+ const text = candidate[key];
290
+ return typeof text === 'string' && text.length > 0 ? redactText(text, maxLength) : undefined;
291
+ };
292
+ const lineHash = candidate.lineHash;
293
+ const errorName = safeText('errorName', 128);
294
+ const errorCode = safeText('errorCode', 128);
295
+ const lineNumber = safeNumber('lineNumber');
296
+ const lineLength = safeNumber('lineLength');
297
+ const stdoutLineCount = safeNumber('stdoutLineCount');
298
+ const stdoutBytes = safeNumber('stdoutBytes');
299
+ const stderrBytes = safeNumber('stderrBytes');
300
+ const stderrSummary = safeText('stderrSummary', 200);
301
+ return {
302
+ stage: safeStage,
303
+ ...(errorName === undefined ? {} : { errorName }),
304
+ ...(errorCode === undefined ? {} : { errorCode }),
305
+ ...(lineNumber === undefined ? {} : { lineNumber }),
306
+ ...(lineLength === undefined ? {} : { lineLength }),
307
+ ...(typeof lineHash === 'string' && /^[0-9a-f]{16}$/i.test(lineHash)
308
+ ? { lineHash: lineHash.toLowerCase() }
309
+ : {}),
310
+ ...(stdoutLineCount === undefined ? {} : { stdoutLineCount }),
311
+ ...(stdoutBytes === undefined ? {} : { stdoutBytes }),
312
+ ...(stderrBytes === undefined ? {} : { stderrBytes }),
313
+ ...(stderrSummary === undefined ? {} : { stderrSummary }),
314
+ };
315
+ }
316
+ /** Apply a final whitelist-only safety pass before the host logger receives metadata. */
317
+ export function sanitizeAgyLogRecord(record) {
318
+ const safeUsage = sanitizeTokenUsage(record.usage);
319
+ const safeCumulativeUsage = sanitizeTokenUsage(record.cumulativeUsage);
320
+ const processDiagnostic = sanitizeProcessDiagnostic(record.processDiagnostic);
321
+ const toolEventDiagnostic = sanitizeToolEventDiagnostic(record.toolEventDiagnostic);
322
+ const protocolMissingRequiredKeys = safeProtocolKeyList(record.protocolMissingRequiredKeys);
323
+ const protocolReceivedArgumentKeys = safeProtocolKeyList(record.protocolReceivedArgumentKeys);
324
+ const sessionFingerprint = record.sessionFingerprint
325
+ ?? (record.sessionId === undefined ? undefined : fingerprint(record.sessionId));
326
+ const conversationFingerprint = record.conversationFingerprint
327
+ ?? (record.conversationId === undefined ? undefined : fingerprint(record.conversationId));
328
+ return {
329
+ event: record.event,
330
+ requestId: redactText(record.requestId, 256),
331
+ provider: redactText(record.provider, 256),
332
+ model: redactText(record.model, 256),
333
+ agent: redactText(record.agent, 256),
334
+ toolPolicy: record.toolPolicy === 'agy-owned'
335
+ ? 'agy-owned'
336
+ : record.toolPolicy === 'dsh-owned' ? 'dsh-owned' : 'reject',
337
+ toolSchemaCount: record.toolSchemaCount,
338
+ toolCallCount: safeNonNegativeNumber(record.toolCallCount),
339
+ bridgeOutcome: isAgyBridgeOutcome(record.bridgeOutcome) ? record.bridgeOutcome : 'failed',
340
+ ...(isAgyPermissionPreset(record.permissionPreset) ? { permissionPreset: record.permissionPreset } : {}),
341
+ ...(isAgyPermissionPreset(record.sandboxMode) ? { sandboxMode: record.sandboxMode } : {}),
342
+ ...(isAgyApprovalPolicy(record.approvalPolicy) ? { approvalPolicy: record.approvalPolicy } : {}),
343
+ ...(isAgyReasoningEffort(record.reasoningEffort) ? { reasoningEffort: record.reasoningEffort } : {}),
344
+ ...(record.purpose === 'compaction' || record.purpose === 'session-title' ? { purpose: record.purpose } : {}),
345
+ ...(isModelDiscoveryLogSource(record.modelDiscoverySource)
346
+ ? { modelDiscoverySource: record.modelDiscoverySource }
347
+ : {}),
348
+ ...(isModelDiscoveryErrorCode(record.modelDiscoveryWarningCode)
349
+ ? { modelDiscoveryWarningCode: record.modelDiscoveryWarningCode }
350
+ : {}),
351
+ attempt: record.attempt,
352
+ processAttemptCount: safeNonNegativeNumber(record.processAttemptCount ?? record.attempt),
353
+ retryMaxRetries: safeNonNegativeNumber(record.retryMaxRetries ?? 0),
354
+ ...(record.inputFrameBytes === undefined ? {} : { inputFrameBytes: safeNonNegativeNumber(record.inputFrameBytes) }),
355
+ ...(record.inputFrameLimitBytes === undefined ? {} : { inputFrameLimitBytes: safeNonNegativeNumber(record.inputFrameLimitBytes) }),
356
+ ...(record.promptBytes === undefined ? {} : { promptBytes: safeNonNegativeNumber(record.promptBytes) }),
357
+ ...(record.promptLimitBytes === undefined ? {} : { promptLimitBytes: safeNonNegativeNumber(record.promptLimitBytes) }),
358
+ ...(record.toolResultCount === undefined ? {} : { toolResultCount: safeNonNegativeNumber(record.toolResultCount) }),
359
+ ...(record.largestToolResultBytes === undefined ? {} : { largestToolResultBytes: safeNonNegativeNumber(record.largestToolResultBytes) }),
360
+ ...(record.truncatedToolResultCount === undefined ? {} : { truncatedToolResultCount: safeNonNegativeNumber(record.truncatedToolResultCount) }),
361
+ ...(record.historyCompacted === true ? { historyCompacted: true } : {}),
362
+ ...(record.omittedMessageCount === undefined ? {} : { omittedMessageCount: safeNonNegativeNumber(record.omittedMessageCount) }),
363
+ ...(isAgyToolEventKind(record.toolEventKind) ? { toolEventKind: record.toolEventKind } : {}),
364
+ ...(record.toolEventStreamIndex === undefined
365
+ ? {}
366
+ : { toolEventStreamIndex: safeNonNegativeNumber(record.toolEventStreamIndex) }),
367
+ ...(toolEventDiagnostic === undefined ? {} : { toolEventDiagnostic }),
368
+ ...(record.carrierAbortExpected === true ? { carrierAbortExpected: true } : {}),
369
+ ...(CARRIER_VALIDATIONS.includes(record.carrierValidation)
370
+ ? { carrierValidation: record.carrierValidation }
371
+ : {}),
372
+ protocolRepairAttempts: safeNonNegativeNumber(record.protocolRepairAttempts ?? 0),
373
+ ...(record.protocolRepairReason === 'not-json'
374
+ || record.protocolRepairReason === 'arguments-invalid'
375
+ || record.protocolRepairReason === 'internal-tool-event'
376
+ ? { protocolRepairReason: record.protocolRepairReason }
377
+ : {}),
378
+ ...(record.protocolFailureDetail === 'not-json'
379
+ || record.protocolFailureDetail === 'envelope'
380
+ || record.protocolFailureDetail === 'message-shape'
381
+ || record.protocolFailureDetail === 'tool-call-shape'
382
+ || record.protocolFailureDetail === 'response-limit'
383
+ || record.protocolFailureDetail === 'validation'
384
+ || record.protocolFailureDetail === 'unknown'
385
+ ? { protocolFailureDetail: record.protocolFailureDetail }
386
+ : {}),
387
+ ...(record.protocolResponseShape === 'empty'
388
+ || record.protocolResponseShape === 'object-like'
389
+ || record.protocolResponseShape === 'array-like'
390
+ || record.protocolResponseShape === 'fenced'
391
+ || record.protocolResponseShape === 'plain-text'
392
+ || record.protocolResponseShape === 'non-string'
393
+ ? { protocolResponseShape: record.protocolResponseShape }
394
+ : {}),
395
+ ...(record.protocolResponseBytes === undefined
396
+ ? {}
397
+ : { protocolResponseBytes: safeNonNegativeNumber(record.protocolResponseBytes) }),
398
+ ...(typeof record.protocolToolName === 'string'
399
+ ? { protocolToolName: redactText(record.protocolToolName, 128) }
400
+ : {}),
401
+ ...(isAgyProtocolArgumentIssue(record.protocolArgumentIssue)
402
+ ? { protocolArgumentIssue: record.protocolArgumentIssue }
403
+ : {}),
404
+ ...(protocolMissingRequiredKeys === undefined
405
+ ? {}
406
+ : { protocolMissingRequiredKeys }),
407
+ ...(protocolReceivedArgumentKeys === undefined
408
+ ? {}
409
+ : { protocolReceivedArgumentKeys }),
410
+ ...(isToolProtocolCompatibility(record.protocolCompatibilityApplied)
411
+ ? { protocolCompatibilityApplied: record.protocolCompatibilityApplied }
412
+ : {}),
413
+ eventCount: record.eventCount,
414
+ toolEventCount: record.toolEventCount,
415
+ permissionEventCount: record.permissionEventCount,
416
+ eventCategoryCounts: {
417
+ init: record.eventCategoryCounts.init,
418
+ step_update: record.eventCategoryCounts.step_update,
419
+ checkpoint: record.eventCategoryCounts.checkpoint,
420
+ agent_response: record.eventCategoryCounts.agent_response,
421
+ result: record.eventCategoryCounts.result,
422
+ tool: record.eventCategoryCounts.tool,
423
+ permission: record.eventCategoryCounts.permission,
424
+ error: record.eventCategoryCounts.error,
425
+ unknown: record.eventCategoryCounts.unknown,
426
+ },
427
+ ...(record.durationMs === undefined ? {} : { durationMs: record.durationMs }),
428
+ ...(record.dispatch === 'cold' || record.dispatch === 'persistent' ? { dispatch: record.dispatch } : {}),
429
+ ...(record.firstEventMs === undefined
430
+ ? {}
431
+ : { firstEventMs: safeNonNegativeNumber(record.firstEventMs) }),
432
+ ...(record.firstTextMs === undefined
433
+ ? {}
434
+ : { firstTextMs: safeNonNegativeNumber(record.firstTextMs) }),
435
+ ...(record.exitCode === undefined ? {} : { exitCode: record.exitCode }),
436
+ ...(record.termination === undefined ? {} : { termination: record.termination }),
437
+ ...(record.queueWaitMs === undefined ? {} : { queueWaitMs: record.queueWaitMs }),
438
+ ...(safeUsage === undefined ? {} : { usage: safeUsage }),
439
+ ...(processDiagnostic === undefined ? {} : { processDiagnostic }),
440
+ ...(record.errorCode === undefined ? {} : { errorCode: redactText(record.errorCode, 128) }),
441
+ ...(record.finalStatus === undefined ? {} : { finalStatus: redactText(record.finalStatus, 128) }),
442
+ ...(record.finalResultRaw === undefined ? {} : { finalResultRaw: redactText(record.finalResultRaw, 1_024) }),
443
+ ...(record.usageSource === 'step' || record.usageSource === 'result-fallback'
444
+ ? { usageSource: record.usageSource }
445
+ : {}),
446
+ ...(safeCumulativeUsage === undefined ? {} : { cumulativeUsage: safeCumulativeUsage }),
447
+ ...(record.cacheHit === undefined ? {} : { cacheHit: record.cacheHit === true }),
448
+ ...(record.cacheTokenShare === undefined
449
+ ? {}
450
+ : { cacheTokenShare: Math.min(1, Math.max(0, safeNonNegativeNumber(record.cacheTokenShare))) }),
451
+ ...(record.requestMode === 'full'
452
+ || record.requestMode === 'bootstrap'
453
+ || record.requestMode === 'delta'
454
+ || record.requestMode === 'full-fallback'
455
+ ? { requestMode: record.requestMode }
456
+ : {}),
457
+ ...(record.conversationReused === undefined ? {} : { conversationReused: record.conversationReused === true }),
458
+ ...(record.conversationMismatch === undefined ? {} : { conversationMismatch: record.conversationMismatch === true }),
459
+ ...(record.partialAnswerDelivered === undefined ? {} : { partialAnswerDelivered: record.partialAnswerDelivered === true }),
460
+ ...(record.stablePrefixBytes === undefined
461
+ ? {}
462
+ : { stablePrefixBytes: safeNonNegativeNumber(record.stablePrefixBytes) }),
463
+ ...(typeof record.stablePrefixHash === 'string' && /^[0-9a-f]{16,64}$/i.test(record.stablePrefixHash)
464
+ ? { stablePrefixHash: record.stablePrefixHash.toLowerCase() }
465
+ : {}),
466
+ ...(typeof record.toolSchemaHash === 'string' && /^[0-9a-f]{16,64}$/i.test(record.toolSchemaHash)
467
+ ? { toolSchemaHash: record.toolSchemaHash.toLowerCase() }
468
+ : {}),
469
+ ...(sessionFingerprint === undefined ? {} : { sessionFingerprint: redactText(sessionFingerprint, 64) }),
470
+ ...(conversationFingerprint === undefined ? {} : { conversationFingerprint: redactText(conversationFingerprint, 64) }),
471
+ };
472
+ }
473
+ function sanitizeTokenUsage(usage) {
474
+ if (usage === undefined)
475
+ return undefined;
476
+ return {
477
+ inputTokens: safeNonNegativeNumber(usage.inputTokens),
478
+ outputTokens: safeNonNegativeNumber(usage.outputTokens),
479
+ ...(usage.cacheReadTokens === undefined
480
+ ? {}
481
+ : { cacheReadTokens: safeNonNegativeNumber(usage.cacheReadTokens) }),
482
+ ...(usage.cacheWriteTokens === undefined
483
+ ? {}
484
+ : { cacheWriteTokens: safeNonNegativeNumber(usage.cacheWriteTokens) }),
485
+ ...(usage.reasoningTokens === undefined
486
+ ? {}
487
+ : { reasoningTokens: safeNonNegativeNumber(usage.reasoningTokens) }),
488
+ };
489
+ }
490
+ function safeNonNegativeNumber(value) {
491
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0;
492
+ }
493
+ export function emitAgyLog(logger, record) {
494
+ if (logger === undefined)
495
+ return;
496
+ try {
497
+ logger(sanitizeAgyLogRecord(record));
498
+ }
499
+ catch {
500
+ // Observability must never break a model request.
501
+ }
502
+ }
503
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1,49 @@
1
+ import { type ProcessRequest, type ProcessResult } from './process.js';
2
+ import type { ModelConfig } from '../provider/config.js';
3
+ import { type ModelDiscoveryErrorCode } from '../provider/error-codes.js';
4
+ export type { ModelDiscoveryErrorCode } from '../provider/error-codes.js';
5
+ export declare const DEFAULT_MODEL_DISCOVERY_TTL_MS: number;
6
+ export declare const DEFAULT_MODEL_DISCOVERY_TIMEOUT_MS = 10000;
7
+ export declare const MAX_MODEL_DISCOVERY_OUTPUT_BYTES: number;
8
+ export type ModelDiscoverySource = 'discovered' | 'merged' | 'cache' | 'fallback';
9
+ export interface AgyModelDiscoveryResult {
10
+ models: readonly ModelConfig[];
11
+ source: ModelDiscoverySource;
12
+ stale: boolean;
13
+ warning?: string;
14
+ warningCode?: ModelDiscoveryErrorCode;
15
+ }
16
+ export type AgyModelDiscoveryCommand = (request: ProcessRequest) => Promise<ProcessResult>;
17
+ export interface AgyModelDiscoveryOptions {
18
+ executable?: string;
19
+ ttlMs?: number;
20
+ timeoutMs?: number;
21
+ maxOutputBytes?: number;
22
+ runCommand?: AgyModelDiscoveryCommand;
23
+ now?: () => number;
24
+ }
25
+ /** Merge explicit configuration first, then add AGY-discovered models. Normalizes base ids and dedupes. */
26
+ export declare function mergeModelCatalog(configured: readonly ModelConfig[], discovered: readonly ModelConfig[]): readonly ModelConfig[];
27
+ /** @deprecated Use normalizeModelId/extractModelEffort for effort-split handling. */
28
+ export declare const MODEL_EFFORT_SUFFIX_RE: RegExp;
29
+ /** Parse the tab-separated, non-interactive output of `agy models`. Normalizes -high/-medium/-low suffix to base id and dedupes base. */
30
+ export declare function parseAgyModels(output: string): ModelConfig[];
31
+ /**
32
+ * Discover AGY models without invoking a model request. Results are kept in
33
+ * memory only. A refresh is single-flight and all failures fail open to the
34
+ * last successful catalog or the caller's static catalog.
35
+ */
36
+ export declare class AgyModelDiscovery {
37
+ private readonly executable;
38
+ private readonly ttlMs;
39
+ private readonly timeoutMs;
40
+ private readonly maxOutputBytes;
41
+ private readonly runCommand;
42
+ private readonly now;
43
+ private cache;
44
+ private inFlight;
45
+ constructor(options?: AgyModelDiscoveryOptions);
46
+ discover(configured: readonly ModelConfig[]): Promise<AgyModelDiscoveryResult>;
47
+ private refresh;
48
+ }
49
+ //# sourceMappingURL=models.d.ts.map