stitchkit 0.41.0 → 0.43.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 (66) hide show
  1. package/README.md +22 -5
  2. package/dist/cli.js +2 -2
  3. package/dist/contract/errors-factory.d.ts +88 -15
  4. package/dist/contract/errors-factory.d.ts.map +1 -1
  5. package/dist/contract/errors.d.ts +4 -4
  6. package/dist/contract/errors.d.ts.map +1 -1
  7. package/dist/contract/factory.d.ts +29 -2
  8. package/dist/contract/factory.d.ts.map +1 -1
  9. package/dist/contract/index.d.ts +2 -2
  10. package/dist/contract/index.d.ts.map +1 -1
  11. package/dist/contract/index.js +2 -2
  12. package/dist/{index-dnkefke9.js → index-310bfer5.js} +1 -1
  13. package/dist/{index-4e1c0hsw.js → index-esqmem78.js} +101 -67
  14. package/dist/{index-sc67e454.js → index-gex6gxhe.js} +7 -4
  15. package/dist/{index-ncqqn1bc.js → index-h2wdcsby.js} +95 -17
  16. package/dist/{index-xax049k6.js → index-r6czv7yt.js} +58 -22
  17. package/dist/{index-809wc1tt.js → index-s4qsmgwe.js} +4 -1
  18. package/dist/index.js +2 -2
  19. package/dist/internal/typed.d.ts +8 -0
  20. package/dist/internal/typed.d.ts.map +1 -1
  21. package/dist/node.js +2 -2
  22. package/dist/observability/audit.d.ts +27 -15
  23. package/dist/observability/audit.d.ts.map +1 -1
  24. package/dist/observability/event.d.ts +2 -2
  25. package/dist/observability/index.d.ts +4 -4
  26. package/dist/observability/index.d.ts.map +1 -1
  27. package/dist/observability/index.js +70 -59
  28. package/dist/react.js +1 -1
  29. package/dist/server/create.d.ts.map +1 -1
  30. package/dist/server/error-hook.d.ts +8 -4
  31. package/dist/server/error-hook.d.ts.map +1 -1
  32. package/dist/server/index.js +7 -6
  33. package/dist/server/logger.d.ts +0 -1
  34. package/dist/server/logger.d.ts.map +1 -1
  35. package/dist/server/types.d.ts +8 -13
  36. package/dist/server/types.d.ts.map +1 -1
  37. package/dist/tools/agent.d.ts +1 -1
  38. package/dist/tools/agent.d.ts.map +1 -1
  39. package/dist/tools/flatten-join.d.ts.map +1 -1
  40. package/dist/tools/list-names.d.ts +8 -6
  41. package/dist/tools/list-names.d.ts.map +1 -1
  42. package/dist/tools/manifest.d.ts +13 -5
  43. package/dist/tools/manifest.d.ts.map +1 -1
  44. package/dist/tools/mcp-handler.d.ts +5 -4
  45. package/dist/tools/mcp-handler.d.ts.map +1 -1
  46. package/dist/tools/mcp-stdio.d.ts +3 -2
  47. package/dist/tools/mcp-stdio.d.ts.map +1 -1
  48. package/dist/tools/mcp.d.ts +49 -9
  49. package/dist/tools/mcp.d.ts.map +1 -1
  50. package/dist/tools/native-mcp.d.ts +4 -18
  51. package/dist/tools/native-mcp.d.ts.map +1 -1
  52. package/dist/tools/runtime-tool.d.ts +32 -1
  53. package/dist/tools/runtime-tool.d.ts.map +1 -1
  54. package/dist/tools/surface.d.ts +37 -0
  55. package/dist/tools/surface.d.ts.map +1 -0
  56. package/dist/tools/toolkit.d.ts +4 -4
  57. package/dist/tools/toolkit.d.ts.map +1 -1
  58. package/dist/tools/transports.d.ts +11 -9
  59. package/dist/tools/transports.d.ts.map +1 -1
  60. package/dist/tools/view-file.d.ts +3 -3
  61. package/dist/tools.d.ts +6 -6
  62. package/dist/tools.d.ts.map +1 -1
  63. package/dist/tools.js +330 -155
  64. package/llms-full.txt +524 -131
  65. package/llms.txt +1 -0
  66. package/package.json +2 -2
@@ -121,6 +121,9 @@ function mergeMeta(contractMeta, endpointMeta) {
121
121
  return { ...contractMeta };
122
122
  return { ...contractMeta, ...endpointMeta };
123
123
  }
124
+ // src/contract/errors-factory.ts
125
+ import { z as z2 } from "zod";
126
+
124
127
  // src/internal/typed.ts
125
128
  function typedEntries(value) {
126
129
  return Object.entries(value);
@@ -129,7 +132,7 @@ function isRecord(value) {
129
132
  return typeof value === "object" && value !== null && !Array.isArray(value);
130
133
  }
131
134
  // src/contract/pagination.ts
132
- import { z as z2 } from "zod";
135
+ import { z as z3 } from "zod";
133
136
 
134
137
  // src/internal/base64url.ts
135
138
  function bytesToBase64Url(bytes) {
@@ -147,7 +150,7 @@ function base64UrlToBytes(segment) {
147
150
  return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
148
151
  }
149
152
  // src/internal/errors.ts
150
- import { z as z3 } from "zod";
153
+ import { z as z4 } from "zod";
151
154
  function issuePath(path) {
152
155
  return path.length > 0 ? path.map(String).join(".") : "(root)";
153
156
  }
@@ -170,7 +173,7 @@ var MAX_DETAIL_ISSUES = 20;
170
173
  function errorCode(err) {
171
174
  if (AppError.is(err))
172
175
  return err.code;
173
- if (err instanceof z3.ZodError)
176
+ if (err instanceof z4.ZodError)
174
177
  return "VALIDATION_ERROR";
175
178
  return;
176
179
  }
@@ -186,7 +189,7 @@ function recordedErrorMessage(code, envelopeMessage, thrown) {
186
189
  function normalizeError(err) {
187
190
  if (AppError.is(err))
188
191
  return err;
189
- if (err instanceof z3.ZodError) {
192
+ if (err instanceof z4.ZodError) {
190
193
  return new AppError("VALIDATION_ERROR", formatZodError(err), 400, {
191
194
  issues: zodIssues(err).slice(0, MAX_DETAIL_ISSUES)
192
195
  });
@@ -17,7 +17,7 @@ import {
17
17
  runWithRequestContext,
18
18
  safeJsonParse,
19
19
  validateDeclaredOutput
20
- } from "./index-sc67e454.js";
20
+ } from "./index-gex6gxhe.js";
21
21
 
22
22
  // src/tools/coerce.ts
23
23
  import { z } from "zod";
@@ -165,22 +165,97 @@ function stringValues(schema) {
165
165
  const values = schema.enum.filter((value) => typeof value === "string");
166
166
  return values.length === schema.enum.length ? values : null;
167
167
  }
168
+ var JSON_SCHEMA_TYPE_ORDER = [
169
+ "string",
170
+ "number",
171
+ "integer",
172
+ "boolean",
173
+ "object",
174
+ "array",
175
+ "null"
176
+ ];
177
+ function jsonSchemaType(value) {
178
+ switch (value) {
179
+ case "array":
180
+ case "boolean":
181
+ case "integer":
182
+ case "null":
183
+ case "number":
184
+ case "object":
185
+ case "string":
186
+ return value;
187
+ default:
188
+ return null;
189
+ }
190
+ }
191
+ function valueType(value) {
192
+ if (value === null)
193
+ return "null";
194
+ if (Array.isArray(value))
195
+ return "array";
196
+ if (isRecord(value))
197
+ return "object";
198
+ if (typeof value === "string")
199
+ return "string";
200
+ if (typeof value === "number")
201
+ return "number";
202
+ if (typeof value === "boolean")
203
+ return "boolean";
204
+ return null;
205
+ }
206
+ function normalizeTypes(types) {
207
+ const normalized = new Set(types);
208
+ if (normalized.has("number"))
209
+ normalized.delete("integer");
210
+ return JSON_SCHEMA_TYPE_ORDER.filter((type) => normalized.has(type));
211
+ }
212
+ function combinedBranchTypes(schema) {
213
+ for (const keyword of ["oneOf", "anyOf"]) {
214
+ const value = schema[keyword];
215
+ if (!Array.isArray(value) || value.length === 0)
216
+ continue;
217
+ const collected = [];
218
+ for (const branch of value) {
219
+ if (!isRecord(branch))
220
+ return null;
221
+ const types = typeNames(branch);
222
+ if (!types)
223
+ return null;
224
+ collected.push(...types);
225
+ }
226
+ return normalizeTypes(collected);
227
+ }
228
+ return null;
229
+ }
168
230
  function typeNames(schema) {
169
- if (typeof schema.type === "string")
170
- return [schema.type];
231
+ const direct = jsonSchemaType(schema.type);
232
+ if (direct)
233
+ return [direct];
171
234
  if (Array.isArray(schema.type)) {
172
- const values2 = schema.type.filter((value) => typeof value === "string");
173
- if (values2.length === schema.type.length)
174
- return values2;
175
- }
176
- const values = stringValues(schema);
177
- if (values)
178
- return ["string"];
179
- if (typeof schema.const === "number")
180
- return ["number"];
181
- if (typeof schema.const === "boolean")
182
- return ["boolean"];
183
- return null;
235
+ const values = [];
236
+ for (const value of schema.type) {
237
+ const type = jsonSchemaType(value);
238
+ if (!type)
239
+ return null;
240
+ values.push(type);
241
+ }
242
+ return values.length > 0 ? normalizeTypes(values) : null;
243
+ }
244
+ if (schema.const !== undefined) {
245
+ const type = valueType(schema.const);
246
+ return type ? [type] : null;
247
+ }
248
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
249
+ const values = [];
250
+ for (const value of schema.enum) {
251
+ const type = valueType(value);
252
+ if (!type)
253
+ return null;
254
+ values.push(type);
255
+ }
256
+ return normalizeTypes(values);
257
+ }
258
+ return combinedBranchTypes(schema);
184
259
  }
185
260
  function typeSchema(type, nullable) {
186
261
  const schema = { type: nullable ? [type, "null"] : type };
@@ -206,6 +281,8 @@ function commonBaseType(schemas) {
206
281
  bases.add(name);
207
282
  }
208
283
  }
284
+ if (bases.size === 0)
285
+ return nullable ? { type: "null" } : {};
209
286
  if (bases.size === 1) {
210
287
  const [only] = bases;
211
288
  return only ? typeSchema(only, nullable) : {};
@@ -213,7 +290,8 @@ function commonBaseType(schemas) {
213
290
  if ([...bases].every((name) => name === "integer" || name === "number")) {
214
291
  return typeSchema("number", nullable);
215
292
  }
216
- return {};
293
+ const types = normalizeTypes(nullable ? [...bases, "null"] : bases);
294
+ return types.length > 0 ? { type: types } : {};
217
295
  }
218
296
  function mergePropertySchemas(properties) {
219
297
  const schemas = properties.map((property) => nullableProjection(property.schema));
@@ -717,7 +795,7 @@ function toToolName(serviceName, methodName) {
717
795
  }
718
796
  function assertUniqueToolName(name, taken, surface) {
719
797
  if (taken) {
720
- throw new Error(`Duplicate ${surface} "${name}" across mounted services`);
798
+ throw new Error(`Duplicate ${surface} "${name}" across mounted operations`);
721
799
  }
722
800
  }
723
801
 
@@ -1,6 +1,6 @@
1
1
  import {
2
- mapObject
3
- } from "./index-809wc1tt.js";
2
+ mapObjectTypeBoundary
3
+ } from "./index-s4qsmgwe.js";
4
4
 
5
5
  // src/contract/define.ts
6
6
  import { z } from "zod";
@@ -237,32 +237,68 @@ function appError(code, message, details) {
237
237
  throw new AppError(code, message, isStitchErrorCode(code) ? STITCH_ERROR_STATUS[code] : 500, details);
238
238
  }
239
239
  // src/contract/errors-factory.ts
240
- function defineErrors(defs) {
241
- const errors = mapObject(defs, (code, status) => (message, details, hint) => {
242
- throw new AppError(String(code), message, status, details, hint);
240
+ import { z as z2 } from "zod";
241
+ function isErrorDetailsSchema(value) {
242
+ return value instanceof z2.ZodObject || value instanceof z2.ZodOptional && value.unwrap() instanceof z2.ZodObject;
243
+ }
244
+ function validateDefinition(code, definition) {
245
+ if (!Number.isInteger(definition.status) || definition.status < 400 || definition.status > 599) {
246
+ throw new Error(`[stitchkit] Error "${code}" must declare an integer HTTP status from 400 to 599`);
247
+ }
248
+ if (definition.details !== undefined && !isErrorDetailsSchema(definition.details)) {
249
+ throw new Error(`[stitchkit] Error "${code}" details must be a Zod object or optional Zod object`);
250
+ }
251
+ }
252
+ function defineErrors(source) {
253
+ const definitions = mapObjectTypeBoundary(source, (code, definition) => {
254
+ const name = String(code);
255
+ validateDefinition(name, definition);
256
+ return Object.freeze({ ...definition });
257
+ });
258
+ Object.freeze(definitions);
259
+ const errors = mapObjectTypeBoundary(definitions, (code, definition) => {
260
+ const name = String(code);
261
+ return (options = {}) => {
262
+ if (definition.details === undefined) {
263
+ if ("details" in options) {
264
+ throw new Error(`[stitchkit] Error "${name}" does not declare details`);
265
+ }
266
+ return new AppError(name, options.message, definition.status, undefined, options.hint);
267
+ }
268
+ const details = definition.details.parse(options.details);
269
+ return new AppError(name, options.message, definition.status, details, options.hint);
270
+ };
243
271
  });
244
- const codes = mapObject(defs, (code) => code);
245
- const known = new Set(Object.keys(defs));
246
- return {
272
+ Object.freeze(errors);
273
+ const codes = mapObjectTypeBoundary(definitions, (code) => String(code));
274
+ Object.freeze(codes);
275
+ const known = new Set(Object.keys(definitions));
276
+ return Object.freeze({
247
277
  errors,
248
278
  codes,
279
+ definitions,
249
280
  isCode: (code) => known.has(code)
250
- };
281
+ });
251
282
  }
252
283
  // src/contract/factory.ts
253
- function createContractFactory() {
254
- return {
255
- defineContract: (meta, endpoints) => {
256
- const validated = defineContract({ prefix: meta.prefix }, endpoints);
257
- return {
258
- endpoints: validated.endpoints,
259
- meta: { ...meta, prefix: meta.prefix, scope: meta.scope }
260
- };
261
- }
284
+ function createContractFactory(config) {
285
+ const scoped = (meta, endpoints) => {
286
+ const validated = defineContract({ prefix: meta.prefix }, endpoints);
287
+ return {
288
+ endpoints: validated.endpoints,
289
+ meta: { ...meta, prefix: meta.prefix, scope: meta.scope }
290
+ };
262
291
  };
292
+ if (config?.toolExposure !== "explicit")
293
+ return { defineContract: scoped };
294
+ const explicit = (meta, endpoints) => {
295
+ const materialized = mapObjectTypeBoundary(endpoints, (_key, endpoint) => endpoint.expose ? endpoint : { ...endpoint, expose: ["HTTP"] });
296
+ return scoped(meta, materialized);
297
+ };
298
+ return { defineContract: explicit };
263
299
  }
264
300
  // src/contract/pagination.ts
265
- import { z as z2 } from "zod";
301
+ import { z as z3 } from "zod";
266
302
 
267
303
  // src/internal/base64url.ts
268
304
  function bytesToBase64Url(bytes) {
@@ -282,9 +318,9 @@ function base64UrlToBytes(segment) {
282
318
 
283
319
  // src/contract/pagination.ts
284
320
  function paginatedSchema(itemSchema) {
285
- return z2.object({
286
- items: z2.array(itemSchema),
287
- nextCursor: z2.string().nullable()
321
+ return z3.object({
322
+ items: z3.array(itemSchema),
323
+ nextCursor: z3.string().nullable()
288
324
  });
289
325
  }
290
326
  function toBase64Url(str) {
@@ -14,5 +14,8 @@ function mapObject(source, mapper) {
14
14
  }
15
15
  return result;
16
16
  }
17
+ function mapObjectTypeBoundary(source, mapper) {
18
+ return mapObject(source, (key, value) => mapper(key, value));
19
+ }
17
20
 
18
- export { typedEntries, isRecord, mapObject };
21
+ export { typedEntries, isRecord, mapObject, mapObjectTypeBoundary };
package/dist/index.js CHANGED
@@ -17,12 +17,12 @@ import {
17
17
  parseTrailingWildcard,
18
18
  rateLimited,
19
19
  unauthorized
20
- } from "./index-xax049k6.js";
20
+ } from "./index-r6czv7yt.js";
21
21
  import {
22
22
  isRecord,
23
23
  mapObject,
24
24
  typedEntries
25
- } from "./index-809wc1tt.js";
25
+ } from "./index-s4qsmgwe.js";
26
26
 
27
27
  // src/browser/client-multipart.ts
28
28
  function isFileDescriptor(value) {
@@ -6,4 +6,12 @@ export declare function isRecord(value: unknown): value is Record<string, unknow
6
6
  export declare function mapObject<TSource extends object, TResult extends {
7
7
  [K in keyof TSource]?: unknown;
8
8
  }>(source: TSource, mapper: <K extends keyof TSource>(key: K, value: TSource[K]) => TResult[K] | undefined): TResult;
9
+ /**
10
+ * Dynamic key-wise factory bridge. Use when runtime construction preserves a
11
+ * mapped type's key/value relation but TypeScript cannot express the dependent
12
+ * callback return. The assertion is intentionally isolated at this boundary.
13
+ */
14
+ export declare function mapObjectTypeBoundary<TSource extends object, TResult extends {
15
+ [K in keyof TSource]?: unknown;
16
+ }>(source: TSource, mapper: (key: keyof TSource, value: TSource[keyof TSource]) => unknown): TResult;
9
17
  //# sourceMappingURL=typed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"typed.d.ts","sourceRoot":"","sources":["../../src/internal/typed.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAC3C,KAAK,EAAE,CAAC,GACP,KAAK,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAE/C;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,SAAS,CACvB,OAAO,SAAS,MAAM,EACtB,OAAO,SAAS;KAAG,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,EAAE,OAAO;CAAE,EAElD,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,GACrF,OAAO,CAOT"}
1
+ {"version":3,"file":"typed.d.ts","sourceRoot":"","sources":["../../src/internal/typed.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAC3C,KAAK,EAAE,CAAC,GACP,KAAK,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAE/C;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,SAAS,CACvB,OAAO,SAAS,MAAM,EACtB,OAAO,SAAS;KAAG,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,EAAE,OAAO;CAAE,EAElD,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,GACrF,OAAO,CAOT;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,SAAS,MAAM,EACtB,OAAO,SAAS;KAAG,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,EAAE,OAAO;CAAE,EAElD,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,OAAO,CAAC,KAAK,OAAO,GACrE,OAAO,CAKT"}
package/dist/node.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createImplement,
4
4
  createSocketIOServer,
5
5
  implement
6
- } from "./index-4e1c0hsw.js";
6
+ } from "./index-esqmem78.js";
7
7
  import"./index-6jypn22c.js";
8
8
  import"./index-x3fcszf8.js";
9
9
  import {
@@ -15,7 +15,7 @@ import {
15
15
  notFound,
16
16
  rateLimited,
17
17
  unauthorized
18
- } from "./index-sc67e454.js";
18
+ } from "./index-gex6gxhe.js";
19
19
  // src/server/node.ts
20
20
  import { serve } from "srvx";
21
21
  async function serveNode(config) {
@@ -1,8 +1,9 @@
1
1
  import type { ToolCallHooks } from '../tools/execute';
2
+ import { type RequestContext } from './context';
2
3
  import type { RequestEvent } from './event';
3
4
  import { type SanitizeOptions } from './sanitize';
4
- /** Config for `createAuditHook`. */
5
- export interface AuditConfig {
5
+ /** One isolated RequestEvent sink and its sanitisation policy. */
6
+ export interface RequestEventSinkConfig {
6
7
  /**
7
8
  * The sink — persists one audit event. It runs fire-and-forget and its own
8
9
  * errors are swallowed, so a slow or failing write never blocks or breaks the
@@ -14,19 +15,30 @@ export interface AuditConfig {
14
15
  /** Payload sanitisation tuning — passed through to `sanitizePayload`. */
15
16
  sanitize?: SanitizeOptions;
16
17
  }
17
- /** What `createAuditHook` returns one wiring point per surface. */
18
- export interface AuditHook {
19
- /**
20
- * Wrap the HTTP fetch handler — audits every request from its final
21
- * response (success and error alike). Compose it INSIDE `wrapInRequestContext`
22
- * (it reads that context for trace ids, timing and identity).
23
- */
24
- http: <S>(handler: (req: Request, server: S) => Promise<Response>) => (req: Request, server: S) => Promise<Response>;
25
- /**
26
- * Tool-call hooks — audits every MCP / agent tool call. Pass as `hooks` to
27
- * `createMcpHandler`, `createStdioMcpServer` or `mountAgent`.
28
- */
18
+ export interface RequestObservabilityConfig extends RequestEventSinkConfig {
19
+ /** Clone and record JSON request bodies. Default false. */
20
+ includePayload?: boolean;
21
+ }
22
+ export interface ObservabilityConfig {
23
+ request?: RequestObservabilityConfig;
24
+ tools?: RequestEventSinkConfig;
25
+ }
26
+ /** The one HTTP completion snapshot produced by the handler. */
27
+ export interface HttpRequestCompletion {
28
+ context: RequestContext;
29
+ statusCode: number;
30
+ durationMs: number;
31
+ payload?: Promise<unknown>;
32
+ }
33
+ /** Server-facing request projection returned by `createObservability`. */
34
+ export interface HttpRequestObserver {
35
+ includePayload: boolean;
36
+ complete(completion: HttpRequestCompletion): void;
37
+ }
38
+ /** What `createObservability` returns — one wiring point per enabled surface. */
39
+ export interface Observability {
40
+ request?: HttpRequestObserver;
29
41
  toolCall: ToolCallHooks;
30
42
  }
31
- export declare function createAuditHook(config: AuditConfig): AuditHook;
43
+ export declare function createObservability(config: ObservabilityConfig): Observability;
32
44
  //# sourceMappingURL=audit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/observability/audit.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAGhF,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAC1C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,IAAI,EAAE,CAAC,CAAC,EACN,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,KACpD,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpD;;;OAGG;IACH,QAAQ,EAAE,aAAa,CAAC;CACzB;AA8BD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CA6G9D"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/observability/audit.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAGhF,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAC1C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA2B,SAAQ,sBAAsB;IACxE,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED,gEAAgE;AAChE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5B;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACnD;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,aAAa,CAAC;CACzB;AAsCD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CA0G9E"}
@@ -2,8 +2,8 @@ import type { HttpMethod, TransportSource } from '../contract';
2
2
  import type { JsonValue } from './sanitize';
3
3
  /**
4
4
  * A normalised audit event — one shape for a completed call on any surface
5
- * (an HTTP request, an MCP tool call, an agent tool call). `createAuditHook`
6
- * produces it and hands it to the project's `write` sink. The project maps it
5
+ * (an HTTP request, an MCP tool call, an agent tool call). `createObservability`
6
+ * produces it and hands it to the configured surface sink. The project maps it
7
7
  * onto its own audit table; stitchkit owns the normalisation.
8
8
  */
9
9
  export interface RequestEvent {
@@ -2,11 +2,11 @@
2
2
  * `stitchkit/observability` — the audit layer one level above the raw hooks.
3
3
  *
4
4
  * W3C trace context, an `AsyncLocalStorage` request context, payload
5
- * sanitisation, a normalised `RequestEvent`, and `createAuditHook` to wire it
6
- * all into one sink. A project's logging becomes a table plus a `write`
7
- * function — nothing else.
5
+ * sanitisation, a normalised `RequestEvent`, and `createObservability` to wire
6
+ * framework-owned HTTP completion plus canonical tool hooks into independent
7
+ * sinks.
8
8
  */
9
- export { type AuditConfig, type AuditHook, createAuditHook } from './audit';
9
+ export { createObservability, type HttpRequestCompletion, type HttpRequestObserver, type Observability, type ObservabilityConfig, type RequestEventSinkConfig, type RequestObservabilityConfig, } from './audit';
10
10
  export { getRequestContext, getTraceId, getUserId, type RequestContext, runWithRequestContext, setRequestDimensions, setRequestEndpoint, setRequestError, setRequestUser, type WrapRequestContextOptions, wrapInRequestContext, } from './context';
11
11
  export type { RequestEvent } from './event';
12
12
  export { type JsonValue, measureSize, redact, type SanitizeOptions, type SizeMeasure, sanitizePayload, truncatePreview, } from './sanitize';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,KAAK,cAAc,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,KAAK,yBAAyB,EAC9B,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACL,KAAK,SAAS,EACd,WAAW,EACX,MAAM,EACN,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,GAChC,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,KAAK,cAAc,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,KAAK,yBAAyB,EAC9B,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACL,KAAK,SAAS,EACd,WAAW,EACX,MAAM,EACN,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
@@ -16,7 +16,7 @@ import {
16
16
  setRequestError,
17
17
  setRequestUser,
18
18
  wrapInRequestContext
19
- } from "../index-sc67e454.js";
19
+ } from "../index-gex6gxhe.js";
20
20
 
21
21
  // src/observability/sanitize.ts
22
22
  var DEFAULT_SENSITIVE_KEYS = /(password|passwd|pwd|secret|token|apikey|api[-_ ]?key|auth|authorization|bearer|session|cookie|init[-_ ]?data|credential|private[-_ ]?key)/i;
@@ -100,7 +100,6 @@ function measureSize(value) {
100
100
  }
101
101
 
102
102
  // src/observability/audit.ts
103
- var BODY_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
104
103
  function toolErrorMessage(result) {
105
104
  const { details, hint } = result;
106
105
  if (isRecord(details) && typeof details.message === "string") {
@@ -114,64 +113,71 @@ function auditErrorMessage(result, thrown) {
114
113
  function readString(value) {
115
114
  return typeof value === "string" ? value : undefined;
116
115
  }
117
- function createAuditHook(config) {
118
- const { write, filter, sanitize } = config;
119
- const emit = async (event) => {
116
+ function createEmitter(config) {
117
+ return async (event) => {
120
118
  try {
121
- if (filter && !filter(event))
119
+ if (config.filter && !config.filter(event))
122
120
  return;
123
- await write(event);
121
+ await config.write(event);
124
122
  } catch {}
125
123
  };
126
- const http = (handler) => {
127
- return async (req, server) => {
128
- const bodyClone = BODY_METHODS.has(req.method) ? req.clone() : null;
129
- const res = await handler(req, server);
130
- const ctx = getRequestContext();
131
- if (ctx) {
132
- const durationMs = Math.round(Number(process.hrtime.bigint() - ctx.startedAt) / 1e6);
133
- (async () => {
134
- let body;
135
- if (bodyClone) {
136
- try {
137
- body = await bodyClone.json();
138
- } catch {
139
- body = undefined;
140
- }
124
+ }
125
+ function createObservability(config) {
126
+ const request = config.request ? {
127
+ includePayload: config.request.includePayload ?? false,
128
+ complete: ({ context, statusCode, durationMs, payload }) => {
129
+ const requestConfig = config.request;
130
+ if (!requestConfig)
131
+ return;
132
+ const emit = createEmitter(requestConfig);
133
+ (async () => {
134
+ let body;
135
+ if (payload) {
136
+ try {
137
+ body = await payload;
138
+ } catch {
139
+ body = undefined;
141
140
  }
142
- await emit({
143
- source: ctx.source,
144
- method: ctx.method,
145
- path: ctx.path,
146
- ...ctx.serviceName !== undefined && { serviceName: ctx.serviceName },
147
- ...ctx.action !== undefined && { action: ctx.action },
148
- ...ctx.dimensions !== undefined && { dimensions: ctx.dimensions },
149
- traceId: ctx.trace.traceId,
150
- spanId: ctx.trace.spanId,
151
- parentSpanId: ctx.trace.parentSpanId,
152
- ok: res.status < 400,
153
- statusCode: res.status,
154
- durationMs,
155
- errorCode: ctx.error?.code,
156
- errorMessage: ctx.error?.message,
157
- ...ctx.error?.details !== undefined && {
158
- errorDetail: sanitizePayload(ctx.error.details, sanitize)
159
- },
160
- payload: sanitizePayload(body, sanitize),
161
- resultSize: null,
162
- responseBytes: 0,
163
- userId: ctx.userId,
164
- ipAddress: ctx.ipAddress,
165
- userAgent: ctx.userAgent,
166
- startedAt: new Date(Date.now() - durationMs)
167
- });
168
- })();
169
- }
170
- return res;
171
- };
172
- };
173
- const toolCall = {
141
+ }
142
+ await emit({
143
+ source: context.source,
144
+ method: context.method,
145
+ path: context.path,
146
+ ...context.serviceName !== undefined && {
147
+ serviceName: context.serviceName
148
+ },
149
+ ...context.action !== undefined && { action: context.action },
150
+ ...context.dimensions !== undefined && {
151
+ dimensions: context.dimensions
152
+ },
153
+ traceId: context.trace.traceId,
154
+ spanId: context.trace.spanId,
155
+ parentSpanId: context.trace.parentSpanId,
156
+ ok: statusCode < 400,
157
+ statusCode,
158
+ durationMs,
159
+ errorCode: context.error?.code,
160
+ errorMessage: context.error?.message,
161
+ ...context.error?.details !== undefined && {
162
+ errorDetail: sanitizePayload(context.error.details, requestConfig.sanitize)
163
+ },
164
+ payload: sanitizePayload(body, requestConfig.sanitize),
165
+ resultSize: null,
166
+ responseBytes: 0,
167
+ userId: context.userId,
168
+ ipAddress: context.ipAddress,
169
+ userAgent: context.userAgent,
170
+ startedAt: new Date(Date.now() - durationMs)
171
+ });
172
+ })();
173
+ }
174
+ } : undefined;
175
+ const toolCall = config.tools ? {
174
176
  afterToolCall: ({ toolName, args, result, durationMs, context, endpoint, error }) => {
177
+ const toolConfig = config.tools;
178
+ if (!toolConfig)
179
+ return;
180
+ const emit = createEmitter(toolConfig);
175
181
  const requestCtx = getRequestContext();
176
182
  const span = requestCtx ? childSpan(requestCtx.trace) : createTraceContext();
177
183
  const measure = result.ok ? measureSize(result.data) : { resultSize: null, responseBytes: 0 };
@@ -182,7 +188,9 @@ function createAuditHook(config) {
182
188
  path: `/${context.source}/${toolName}`,
183
189
  serviceName: endpoint.serviceName,
184
190
  action: endpoint.key,
185
- ...requestCtx?.dimensions !== undefined && { dimensions: requestCtx.dimensions },
191
+ ...requestCtx?.dimensions !== undefined && {
192
+ dimensions: requestCtx.dimensions
193
+ },
186
194
  toolName,
187
195
  traceId: span.traceId,
188
196
  spanId: span.spanId,
@@ -193,9 +201,9 @@ function createAuditHook(config) {
193
201
  errorCode: result.ok ? undefined : result.code,
194
202
  errorMessage: result.ok ? undefined : auditErrorMessage(result, error),
195
203
  ...!result.ok && result.details !== undefined && {
196
- errorDetail: sanitizePayload(result.details, sanitize)
204
+ errorDetail: sanitizePayload(result.details, toolConfig.sanitize)
197
205
  },
198
- payload: sanitizePayload(args, sanitize),
206
+ payload: sanitizePayload(args, toolConfig.sanitize),
199
207
  resultSize: measure.resultSize,
200
208
  responseBytes: measure.responseBytes,
201
209
  userId: readString(context.userId),
@@ -206,8 +214,11 @@ function createAuditHook(config) {
206
214
  startedAt: new Date(Date.now() - durationMs)
207
215
  });
208
216
  }
217
+ } : undefined;
218
+ return {
219
+ ...request && { request },
220
+ toolCall: toolCall ?? {}
209
221
  };
210
- return { http, toolCall };
211
222
  }
212
223
  export {
213
224
  wrapInRequestContext,
@@ -227,6 +238,6 @@ export {
227
238
  getRequestContext,
228
239
  formatTraceparent,
229
240
  createTraceContext,
230
- createAuditHook,
241
+ createObservability,
231
242
  childSpan
232
243
  };