agenthooksprotocol 0.0.0-bootstrap.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 (151) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +29 -0
  3. package/dist/src/client/auth.d.ts +147 -0
  4. package/dist/src/client/auth.d.ts.map +1 -0
  5. package/dist/src/client/auth.js +629 -0
  6. package/dist/src/client/auth.js.map +1 -0
  7. package/dist/src/client/composition.d.ts +42 -0
  8. package/dist/src/client/composition.d.ts.map +1 -0
  9. package/dist/src/client/composition.js +523 -0
  10. package/dist/src/client/composition.js.map +1 -0
  11. package/dist/src/client/content.d.ts +73 -0
  12. package/dist/src/client/content.d.ts.map +1 -0
  13. package/dist/src/client/content.js +404 -0
  14. package/dist/src/client/content.js.map +1 -0
  15. package/dist/src/client/hooks.d.ts +62 -0
  16. package/dist/src/client/hooks.d.ts.map +1 -0
  17. package/dist/src/client/hooks.js +997 -0
  18. package/dist/src/client/hooks.js.map +1 -0
  19. package/dist/src/client/index.d.ts +11 -0
  20. package/dist/src/client/index.d.ts.map +1 -0
  21. package/dist/src/client/index.js +9 -0
  22. package/dist/src/client/index.js.map +1 -0
  23. package/dist/src/client/transport.d.ts +12 -0
  24. package/dist/src/client/transport.d.ts.map +1 -0
  25. package/dist/src/client/transport.js +347 -0
  26. package/dist/src/client/transport.js.map +1 -0
  27. package/dist/src/client/types.d.ts +117 -0
  28. package/dist/src/client/types.d.ts.map +1 -0
  29. package/dist/src/client/types.js +10 -0
  30. package/dist/src/client/types.js.map +1 -0
  31. package/dist/src/client/validation.d.ts +3 -0
  32. package/dist/src/client/validation.d.ts.map +1 -0
  33. package/dist/src/client/validation.js +25 -0
  34. package/dist/src/client/validation.js.map +1 -0
  35. package/dist/src/compaction.d.ts +27 -0
  36. package/dist/src/compaction.d.ts.map +1 -0
  37. package/dist/src/compaction.js +155 -0
  38. package/dist/src/compaction.js.map +1 -0
  39. package/dist/src/content-upload.d.ts +60 -0
  40. package/dist/src/content-upload.d.ts.map +1 -0
  41. package/dist/src/content-upload.js +129 -0
  42. package/dist/src/content-upload.js.map +1 -0
  43. package/dist/src/draft/generated.d.ts +3085 -0
  44. package/dist/src/draft/generated.d.ts.map +1 -0
  45. package/dist/src/draft/generated.js +15372 -0
  46. package/dist/src/draft/generated.js.map +1 -0
  47. package/dist/src/draft/index.d.ts +31 -0
  48. package/dist/src/draft/index.d.ts.map +1 -0
  49. package/dist/src/draft/index.js +129 -0
  50. package/dist/src/draft/index.js.map +1 -0
  51. package/dist/src/draft/schemas.d.ts +8848 -0
  52. package/dist/src/draft/schemas.d.ts.map +1 -0
  53. package/dist/src/draft/schemas.js +9219 -0
  54. package/dist/src/draft/schemas.js.map +1 -0
  55. package/dist/src/draft-atomic.d.ts +18 -0
  56. package/dist/src/draft-atomic.d.ts.map +1 -0
  57. package/dist/src/draft-atomic.js +102 -0
  58. package/dist/src/draft-atomic.js.map +1 -0
  59. package/dist/src/draft-runtime.d.ts +89 -0
  60. package/dist/src/draft-runtime.d.ts.map +1 -0
  61. package/dist/src/draft-runtime.js +242 -0
  62. package/dist/src/draft-runtime.js.map +1 -0
  63. package/dist/src/elicitation.d.ts +18 -0
  64. package/dist/src/elicitation.d.ts.map +1 -0
  65. package/dist/src/elicitation.js +191 -0
  66. package/dist/src/elicitation.js.map +1 -0
  67. package/dist/src/errors.d.ts +7 -0
  68. package/dist/src/errors.d.ts.map +1 -0
  69. package/dist/src/errors.js +15 -0
  70. package/dist/src/errors.js.map +1 -0
  71. package/dist/src/framing.d.ts +7 -0
  72. package/dist/src/framing.d.ts.map +1 -0
  73. package/dist/src/framing.js +39 -0
  74. package/dist/src/framing.js.map +1 -0
  75. package/dist/src/generated.d.ts +304 -0
  76. package/dist/src/generated.d.ts.map +1 -0
  77. package/dist/src/generated.js +1885 -0
  78. package/dist/src/generated.js.map +1 -0
  79. package/dist/src/hook-runner.d.ts +11 -0
  80. package/dist/src/hook-runner.d.ts.map +1 -0
  81. package/dist/src/hook-runner.js +223 -0
  82. package/dist/src/hook-runner.js.map +1 -0
  83. package/dist/src/index.d.ts +8 -0
  84. package/dist/src/index.d.ts.map +1 -0
  85. package/dist/src/index.js +8 -0
  86. package/dist/src/index.js.map +1 -0
  87. package/dist/src/observation.d.ts +26 -0
  88. package/dist/src/observation.d.ts.map +1 -0
  89. package/dist/src/observation.js +28 -0
  90. package/dist/src/observation.js.map +1 -0
  91. package/dist/src/server/attachments.d.ts +29 -0
  92. package/dist/src/server/attachments.d.ts.map +1 -0
  93. package/dist/src/server/attachments.js +112 -0
  94. package/dist/src/server/attachments.js.map +1 -0
  95. package/dist/src/server/hooks.d.ts +20 -0
  96. package/dist/src/server/hooks.d.ts.map +1 -0
  97. package/dist/src/server/hooks.js +129 -0
  98. package/dist/src/server/hooks.js.map +1 -0
  99. package/dist/src/server/index.d.ts +7 -0
  100. package/dist/src/server/index.d.ts.map +1 -0
  101. package/dist/src/server/index.js +4 -0
  102. package/dist/src/server/index.js.map +1 -0
  103. package/dist/src/server/stdio.d.ts +15 -0
  104. package/dist/src/server/stdio.d.ts.map +1 -0
  105. package/dist/src/server/stdio.js +69 -0
  106. package/dist/src/server/stdio.js.map +1 -0
  107. package/dist/src/transport.d.ts +7 -0
  108. package/dist/src/transport.d.ts.map +1 -0
  109. package/dist/src/transport.js +195 -0
  110. package/dist/src/transport.js.map +1 -0
  111. package/dist/src/types.d.ts +116 -0
  112. package/dist/src/types.d.ts.map +1 -0
  113. package/dist/src/types.js +14 -0
  114. package/dist/src/types.js.map +1 -0
  115. package/dist/src/validation.d.ts +7 -0
  116. package/dist/src/validation.d.ts.map +1 -0
  117. package/dist/src/validation.js +209 -0
  118. package/dist/src/validation.js.map +1 -0
  119. package/package.json +64 -0
  120. package/src/client/auth.ts +1076 -0
  121. package/src/client/composition.ts +708 -0
  122. package/src/client/content.ts +510 -0
  123. package/src/client/hooks.ts +1368 -0
  124. package/src/client/index.ts +28 -0
  125. package/src/client/transport.ts +408 -0
  126. package/src/client/types.ts +173 -0
  127. package/src/client/validation.ts +24 -0
  128. package/src/compaction.ts +196 -0
  129. package/src/content-upload.ts +205 -0
  130. package/src/draft/README.md +16 -0
  131. package/src/draft/ahp-codegen.lock.json +123 -0
  132. package/src/draft/generated.ts +19143 -0
  133. package/src/draft/index.ts +246 -0
  134. package/src/draft/schemas.ts +9413 -0
  135. package/src/draft-atomic.ts +119 -0
  136. package/src/draft-runtime.ts +365 -0
  137. package/src/elicitation.ts +254 -0
  138. package/src/errors.ts +30 -0
  139. package/src/framing.ts +56 -0
  140. package/src/generated.ts +2408 -0
  141. package/src/hook-runner.ts +310 -0
  142. package/src/index.ts +8 -0
  143. package/src/observation.ts +65 -0
  144. package/src/server/attachments.ts +128 -0
  145. package/src/server/hooks.ts +205 -0
  146. package/src/server/index.ts +18 -0
  147. package/src/server/node-crypto.d.ts +7 -0
  148. package/src/server/stdio.ts +81 -0
  149. package/src/transport.ts +273 -0
  150. package/src/types.ts +124 -0
  151. package/src/validation.ts +372 -0
@@ -0,0 +1,372 @@
1
+ import { HookOperationalError } from "./errors.js";
2
+ import {
3
+ INTERCEPT_METHOD,
4
+ PROTOCOL_VERSION,
5
+ TOOL_KINDS,
6
+ type InterceptRequest,
7
+ type InterceptResult,
8
+ type JsonObject,
9
+ type JsonValue,
10
+ } from "./types.js";
11
+
12
+ type RecordValue = Record<string, unknown>;
13
+
14
+ function record(value: unknown, label: string): RecordValue {
15
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
16
+ throw new HookOperationalError(
17
+ "MALFORMED_JSON_RPC",
18
+ `${label} must be an object`,
19
+ );
20
+ }
21
+ return value as RecordValue;
22
+ }
23
+
24
+ function nonEmptyString(value: unknown, label: string): string {
25
+ if (typeof value !== "string" || value.trim().length === 0) {
26
+ throw new HookOperationalError(
27
+ "MALFORMED_JSON_RPC",
28
+ `${label} must be a non-empty string`,
29
+ );
30
+ }
31
+ return value;
32
+ }
33
+
34
+ function optionalString(value: RecordValue, key: string, label: string): void {
35
+ if (key in value) nonEmptyString(value[key], `${label}.${key}`);
36
+ }
37
+
38
+ function assertUri(value: unknown, label: string): void {
39
+ const text = nonEmptyString(value, label);
40
+ try {
41
+ new URL(text);
42
+ } catch (error) {
43
+ throw new HookOperationalError(
44
+ "MALFORMED_JSON_RPC",
45
+ `${label} must be an absolute URI`,
46
+ { cause: error },
47
+ );
48
+ }
49
+ }
50
+
51
+ export function assertJsonValue(
52
+ value: unknown,
53
+ label = "value",
54
+ ): asserts value is JsonValue {
55
+ assertJsonValueInner(value, label, new WeakSet<object>());
56
+ }
57
+
58
+ export function assertJsonObject(
59
+ value: unknown,
60
+ label = "value",
61
+ ): asserts value is JsonObject {
62
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
63
+ throw new HookOperationalError(
64
+ "MALFORMED_JSON_RPC",
65
+ `${label} must be a JSON object`,
66
+ );
67
+ }
68
+ assertJsonValue(value, label);
69
+ }
70
+
71
+ function assertJsonValueInner(
72
+ value: unknown,
73
+ label: string,
74
+ seen: WeakSet<object>,
75
+ ): void {
76
+ if (value === null || typeof value === "string" || typeof value === "boolean")
77
+ return;
78
+ if (typeof value === "number" && Number.isFinite(value)) return;
79
+ if (typeof value !== "object")
80
+ throw new HookOperationalError(
81
+ "MALFORMED_JSON_RPC",
82
+ `${label} is not a JSON value`,
83
+ );
84
+ if (seen.has(value))
85
+ throw new HookOperationalError(
86
+ "MALFORMED_JSON_RPC",
87
+ `${label} contains a cycle`,
88
+ );
89
+ seen.add(value);
90
+ if (Array.isArray(value)) {
91
+ const keys = Reflect.ownKeys(value).filter((key) => key !== "length");
92
+ if (
93
+ keys.length !== value.length ||
94
+ keys.some((key, index) => key !== String(index))
95
+ ) {
96
+ throw new HookOperationalError(
97
+ "MALFORMED_JSON_RPC",
98
+ `${label} must be a dense JSON array`,
99
+ );
100
+ }
101
+ for (let index = 0; index < value.length; index += 1)
102
+ assertJsonValueInner(value[index], `${label}[${index}]`, seen);
103
+ } else {
104
+ const prototype = Object.getPrototypeOf(value) as unknown;
105
+ if (prototype !== Object.prototype && prototype !== null)
106
+ throw new HookOperationalError(
107
+ "MALFORMED_JSON_RPC",
108
+ `${label} must be a plain JSON object`,
109
+ );
110
+ for (const key of Reflect.ownKeys(value)) {
111
+ if (typeof key !== "string")
112
+ throw new HookOperationalError(
113
+ "MALFORMED_JSON_RPC",
114
+ `${label} cannot have symbol keys`,
115
+ );
116
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
117
+ if (
118
+ descriptor === undefined ||
119
+ !descriptor.enumerable ||
120
+ !("value" in descriptor)
121
+ ) {
122
+ throw new HookOperationalError(
123
+ "MALFORMED_JSON_RPC",
124
+ `${label}.${key} must be an enumerable data property`,
125
+ );
126
+ }
127
+ assertJsonValueInner(descriptor.value, `${label}.${key}`, seen);
128
+ }
129
+ }
130
+ seen.delete(value);
131
+ }
132
+
133
+ function assertRfc3339(value: unknown, label: string): void {
134
+ const text = nonEmptyString(value, label);
135
+ if (
136
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(
137
+ text,
138
+ ) ||
139
+ !Number.isFinite(Date.parse(text))
140
+ ) {
141
+ throw new HookOperationalError(
142
+ "MALFORMED_JSON_RPC",
143
+ `${label} must be an RFC 3339 timestamp`,
144
+ );
145
+ }
146
+ }
147
+
148
+ function isReverseDns(value: string): boolean {
149
+ return /^(?:[A-Za-z][A-Za-z0-9-]*\.)+[A-Za-z][A-Za-z0-9_.-]*$/.test(value);
150
+ }
151
+
152
+ function assertExtensions(
153
+ value: unknown,
154
+ label: string,
155
+ ): asserts value is JsonObject {
156
+ assertJsonObject(value, label);
157
+ for (const key of Object.keys(value)) {
158
+ if (!isReverseDns(key))
159
+ throw new HookOperationalError(
160
+ "MALFORMED_JSON_RPC",
161
+ `${label} keys must use reverse-DNS names`,
162
+ );
163
+ }
164
+ }
165
+
166
+ export function parseJson(line: string): unknown {
167
+ try {
168
+ return JSON.parse(line) as unknown;
169
+ } catch (error) {
170
+ throw new HookOperationalError(
171
+ "MALFORMED_JSON",
172
+ "Message is not valid JSON",
173
+ { cause: error },
174
+ );
175
+ }
176
+ }
177
+
178
+ export function parseInterceptRequest(line: string): InterceptRequest {
179
+ const parsed = parseJson(line);
180
+ const root = record(parsed, "request");
181
+ if (root.jsonrpc !== "2.0" || root.method !== INTERCEPT_METHOD) {
182
+ throw new HookOperationalError(
183
+ "MALFORMED_JSON_RPC",
184
+ "Expected JSON-RPC 2.0 hooks/intercept request",
185
+ );
186
+ }
187
+ const requestId = nonEmptyString(root.id, "request.id");
188
+ const params = record(root.params, "request.params");
189
+ if (params.protocolVersion !== PROTOCOL_VERSION)
190
+ throw new HookOperationalError(
191
+ "INCOMPATIBLE_VERSION",
192
+ "Expected protocolVersion draft",
193
+ );
194
+ const capabilities = record(
195
+ params.capabilities,
196
+ "request.params.capabilities",
197
+ );
198
+ if (
199
+ !Array.isArray(capabilities.effects) ||
200
+ capabilities.effects.length !== 1 ||
201
+ capabilities.effects[0] !== "deny"
202
+ ) {
203
+ throw new HookOperationalError(
204
+ "UNSUPPORTED_EFFECT",
205
+ 'capabilities.effects must be exactly ["deny"]',
206
+ );
207
+ }
208
+ const event = record(params.event, "request.params.event");
209
+ const eventId = nonEmptyString(event.id, "event.id");
210
+ if (requestId !== eventId)
211
+ throw new HookOperationalError(
212
+ "ID_MISMATCH",
213
+ "JSON-RPC request ID must equal event.id",
214
+ );
215
+ assertUri(event.source, "event.source");
216
+ if (event.type !== "tool.before")
217
+ throw new HookOperationalError(
218
+ "UNSUPPORTED_EVENT",
219
+ "event.type must be tool.before",
220
+ );
221
+ assertRfc3339(event.time, "event.time");
222
+ const session = record(event.session, "event.session");
223
+ nonEmptyString(session.id, "event.session.id");
224
+ optionalString(session, "cwd", "event.session");
225
+ optionalString(session, "model", "event.session");
226
+ if ("agent" in session) {
227
+ const agent = record(session.agent, "event.session.agent");
228
+ nonEmptyString(agent.id, "event.session.agent.id");
229
+ optionalString(agent, "type", "event.session.agent");
230
+ }
231
+ if (
232
+ "workspaceRoots" in session &&
233
+ (!Array.isArray(session.workspaceRoots) ||
234
+ !session.workspaceRoots.every(
235
+ (root) => typeof root === "string" && root.length > 0,
236
+ ))
237
+ ) {
238
+ throw new HookOperationalError(
239
+ "MALFORMED_JSON_RPC",
240
+ "event.session.workspaceRoots must be an array of non-empty strings",
241
+ );
242
+ }
243
+ const tool = record(event.tool, "event.tool");
244
+ nonEmptyString(tool.callId, "event.tool.callId");
245
+ nonEmptyString(tool.name, "event.tool.name");
246
+ if (
247
+ typeof tool.kind !== "string" ||
248
+ !(TOOL_KINDS as readonly string[]).includes(tool.kind)
249
+ ) {
250
+ throw new HookOperationalError(
251
+ "MALFORMED_JSON_RPC",
252
+ `Unknown event.tool.kind: ${String(tool.kind)}`,
253
+ );
254
+ }
255
+ assertJsonObject(tool.input, "event.tool.input");
256
+ if ("mcp" in tool) assertJsonObject(tool.mcp, "event.tool.mcp");
257
+ if ("native" in event) {
258
+ const native = record(event.native, "event.native");
259
+ nonEmptyString(native.provider, "event.native.provider");
260
+ nonEmptyString(native.eventName, "event.native.eventName");
261
+ assertJsonObject(native.payload, "event.native.payload");
262
+ }
263
+ if ("extensions" in event)
264
+ assertExtensions(event.extensions, "event.extensions");
265
+ return parsed as InterceptRequest;
266
+ }
267
+
268
+ export function parseInterceptResponse(
269
+ line: string,
270
+ expectedId: string,
271
+ ): InterceptResult {
272
+ const root = record(parseJson(line), "response");
273
+ if (root.jsonrpc !== "2.0")
274
+ throw new HookOperationalError(
275
+ "MALFORMED_JSON_RPC",
276
+ "response.jsonrpc must be 2.0",
277
+ );
278
+ if (root.id !== expectedId)
279
+ throw new HookOperationalError(
280
+ "ID_MISMATCH",
281
+ "Response ID does not match request ID",
282
+ );
283
+ if ("error" in root && "result" in root)
284
+ throw new HookOperationalError(
285
+ "MALFORMED_JSON_RPC",
286
+ "Response cannot contain both result and error",
287
+ );
288
+ if ("error" in root) {
289
+ const rpcError = record(root.error, "response.error");
290
+ if (
291
+ !Number.isInteger(rpcError.code) ||
292
+ typeof rpcError.message !== "string"
293
+ ) {
294
+ throw new HookOperationalError(
295
+ "MALFORMED_JSON_RPC",
296
+ "Malformed JSON-RPC error object",
297
+ );
298
+ }
299
+ if ("data" in rpcError)
300
+ assertJsonValue(rpcError.data, "response.error.data");
301
+ throw new HookOperationalError(
302
+ "JSON_RPC_ERROR",
303
+ `Backend JSON-RPC error ${String(rpcError.code)}: ${rpcError.message}`,
304
+ );
305
+ }
306
+ if (!("result" in root))
307
+ throw new HookOperationalError(
308
+ "MALFORMED_JSON_RPC",
309
+ "Response must contain result or error",
310
+ );
311
+ const result = record(root.result, "response.result");
312
+ if (result.protocolVersion !== PROTOCOL_VERSION)
313
+ throw new HookOperationalError(
314
+ "INCOMPATIBLE_VERSION",
315
+ "Backend protocolVersion is not draft",
316
+ );
317
+ if (!Array.isArray(result.effects))
318
+ throw new HookOperationalError(
319
+ "MALFORMED_JSON_RPC",
320
+ "response.result.effects must be an array",
321
+ );
322
+ if (result.effects.length > 1)
323
+ throw new HookOperationalError(
324
+ "MULTIPLE_EFFECTS",
325
+ "tool.before permits at most one effect",
326
+ );
327
+ if (result.effects.length === 0)
328
+ return { protocolVersion: PROTOCOL_VERSION, effects: [] };
329
+ const effect = record(result.effects[0], "response.result.effects[0]");
330
+ if (effect.type !== "deny")
331
+ throw new HookOperationalError(
332
+ "UNSUPPORTED_EFFECT",
333
+ `Unsupported tool.before effect: ${String(effect.type)}`,
334
+ );
335
+ if (
336
+ Object.keys(effect).some(
337
+ (key) => !["type", "reason", "code", "extensions"].includes(key),
338
+ )
339
+ ) {
340
+ throw new HookOperationalError(
341
+ "UNSUPPORTED_EFFECT",
342
+ "Unknown deny effect field",
343
+ );
344
+ }
345
+ const reason = nonEmptyString(
346
+ effect.reason,
347
+ "response.result.effects[0].reason",
348
+ );
349
+ if (
350
+ "code" in effect &&
351
+ (typeof effect.code !== "string" || !isReverseDns(effect.code))
352
+ ) {
353
+ throw new HookOperationalError(
354
+ "MALFORMED_JSON_RPC",
355
+ "Deny effect code must use reverse-DNS notation",
356
+ );
357
+ }
358
+ if ("extensions" in effect)
359
+ assertExtensions(
360
+ effect.extensions,
361
+ "response.result.effects[0].extensions",
362
+ );
363
+ const deny = {
364
+ type: "deny" as const,
365
+ reason,
366
+ ...(effect.code === undefined ? {} : { code: effect.code as string }),
367
+ ...(effect.extensions === undefined
368
+ ? {}
369
+ : { extensions: effect.extensions as JsonObject }),
370
+ };
371
+ return { protocolVersion: PROTOCOL_VERSION, effects: [deny] };
372
+ }