stitchkit 0.35.0 → 0.36.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/dist/cli.js CHANGED
@@ -4,10 +4,10 @@ import {
4
4
  emitResult,
5
5
  parseCliArgs,
6
6
  pollUntilDone
7
- } from "./index-bfabej0h.js";
7
+ } from "./index-82gncajj.js";
8
8
  import"./index-0ed3bx43.js";
9
9
  import"./index-x3fcszf8.js";
10
- import"./index-enc7t99e.js";
10
+ import"./index-17rdjw68.js";
11
11
  export {
12
12
  pollUntilDone,
13
13
  parseCliArgs,
@@ -192,4 +192,149 @@ function safeJsonParse(text) {
192
192
  return JSON.parse(text, (key, value) => isUnsafeKey(key) ? undefined : value);
193
193
  }
194
194
 
195
- export { __require, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, typedEntries, isRecord, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateHandlerOutput, isUnsafeKey, safeJsonParse };
195
+ // src/server/request.ts
196
+ function generateTraceId() {
197
+ return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
198
+ }
199
+ function resolveTraceId(req) {
200
+ const header = req.headers.get("x-request-id") ?? req.headers.get("x-trace-id");
201
+ const trimmed = header?.trim();
202
+ if (trimmed && trimmed.length <= 128 && /^[\w.-]+$/.test(trimmed)) {
203
+ return trimmed;
204
+ }
205
+ return generateTraceId();
206
+ }
207
+ function resolveSocketIp(req, server) {
208
+ if (typeof server === "object" && server !== null && "requestIP" in server && typeof server.requestIP === "function") {
209
+ const addr = server.requestIP(req);
210
+ if (isRecord(addr) && typeof addr.address === "string" && addr.address) {
211
+ return addr.address;
212
+ }
213
+ }
214
+ if ("ip" in req && typeof req.ip === "string" && req.ip)
215
+ return req.ip;
216
+ return;
217
+ }
218
+ function extractIp(req, options = {}) {
219
+ if (options.trustProxy) {
220
+ const forwarded = req.headers.get("x-forwarded-for");
221
+ if (forwarded)
222
+ return (forwarded.split(",")[0] ?? "").trim().replace(/^::ffff:/, "");
223
+ const realIp = req.headers.get("x-real-ip");
224
+ if (realIp)
225
+ return realIp.trim().replace(/^::ffff:/, "");
226
+ }
227
+ return (options.socketIp ?? "").replace(/^::ffff:/, "");
228
+ }
229
+ function getClientInfo(req, options = {}) {
230
+ return {
231
+ ipAddress: extractIp(req, options) || undefined,
232
+ userAgent: req.headers.get("user-agent") ?? undefined
233
+ };
234
+ }
235
+ function parseQueryParams(url) {
236
+ const query = {};
237
+ for (const key of new Set(url.searchParams.keys())) {
238
+ if (isUnsafeKey(key))
239
+ continue;
240
+ const values = url.searchParams.getAll(key);
241
+ const [first] = values;
242
+ query[key] = values.length === 1 && first !== undefined ? first : values;
243
+ }
244
+ return query;
245
+ }
246
+
247
+ // src/observability/trace.ts
248
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
249
+ function randomHex(bytes) {
250
+ const arr = new Uint8Array(bytes);
251
+ crypto.getRandomValues(arr);
252
+ let hex = "";
253
+ for (const byte of arr)
254
+ hex += byte.toString(16).padStart(2, "0");
255
+ return hex;
256
+ }
257
+ function createTraceContext() {
258
+ return { traceId: randomHex(16), spanId: randomHex(8) };
259
+ }
260
+ function parseTraceparent(header) {
261
+ if (!header)
262
+ return null;
263
+ const match = TRACEPARENT_RE.exec(header.trim());
264
+ if (!match?.[1] || !match[2])
265
+ return null;
266
+ const traceId = match[1].toLowerCase();
267
+ const parentSpanId = match[2].toLowerCase();
268
+ if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
269
+ return null;
270
+ return { traceId, spanId: randomHex(8), parentSpanId };
271
+ }
272
+ function formatTraceparent(ctx) {
273
+ return `00-${ctx.traceId}-${ctx.spanId}-01`;
274
+ }
275
+ function resolveTraceContext(req) {
276
+ return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
277
+ }
278
+ function childSpan(parent) {
279
+ return {
280
+ traceId: parent.traceId,
281
+ spanId: randomHex(8),
282
+ parentSpanId: parent.spanId
283
+ };
284
+ }
285
+
286
+ // src/observability/context.ts
287
+ import { AsyncLocalStorage } from "node:async_hooks";
288
+ var storage = new AsyncLocalStorage;
289
+ function runWithRequestContext(ctx, fn) {
290
+ return storage.run(ctx, fn);
291
+ }
292
+ function getRequestContext() {
293
+ return storage.getStore();
294
+ }
295
+ function getTraceId() {
296
+ return storage.getStore()?.trace.traceId;
297
+ }
298
+ function getUserId() {
299
+ return storage.getStore()?.userId;
300
+ }
301
+ function setRequestUser(userId) {
302
+ const ctx = storage.getStore();
303
+ if (ctx)
304
+ ctx.userId = userId;
305
+ }
306
+ function setRequestEndpoint(serviceName, action) {
307
+ const ctx = storage.getStore();
308
+ if (ctx) {
309
+ ctx.serviceName = serviceName;
310
+ ctx.action = action;
311
+ }
312
+ }
313
+ function setRequestDimensions(dimensions) {
314
+ const ctx = storage.getStore();
315
+ if (ctx)
316
+ ctx.dimensions = { ...ctx.dimensions, ...dimensions };
317
+ }
318
+ function setRequestError(error) {
319
+ const ctx = storage.getStore();
320
+ if (ctx)
321
+ ctx.error = error;
322
+ }
323
+ function wrapInRequestContext(handler, options = {}) {
324
+ return (req, server) => {
325
+ const ctx = {
326
+ trace: resolveTraceContext(req),
327
+ source: "http",
328
+ method: req.method,
329
+ path: new URL(req.url, "http://localhost").pathname,
330
+ startedAt: process.hrtime.bigint(),
331
+ ...getClientInfo(req, {
332
+ trustProxy: options.trustProxy,
333
+ socketIp: resolveSocketIp(req, server)
334
+ })
335
+ };
336
+ return runWithRequestContext(ctx, () => handler(req, server));
337
+ };
338
+ }
339
+
340
+ export { __require, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, typedEntries, isRecord, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateHandlerOutput, isUnsafeKey, safeJsonParse, generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
@@ -7,12 +7,14 @@ import {
7
7
  } from "./index-x3fcszf8.js";
8
8
  import {
9
9
  formatZodError,
10
+ getRequestContext,
10
11
  isRecord,
11
12
  isUnsafeKey,
12
13
  normalizeError,
14
+ runWithRequestContext,
13
15
  safeJsonParse,
14
16
  validateHandlerOutput
15
- } from "./index-enc7t99e.js";
17
+ } from "./index-17rdjw68.js";
16
18
 
17
19
  // src/tools/coerce.ts
18
20
  import { z } from "zod";
@@ -434,6 +436,25 @@ function toolResultFromError(err) {
434
436
  };
435
437
  }
436
438
  async function executeToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson = false, onOutputStrip) {
439
+ return inToolCallContext({ source: context.source, toolName, method }, () => runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip));
440
+ }
441
+ function inToolCallContext(call, body) {
442
+ const parent = getRequestContext();
443
+ if (!parent)
444
+ return body();
445
+ return runWithRequestContext({
446
+ ...parent,
447
+ trace: parent.trace,
448
+ dimensions: parent.dimensions ? { ...parent.dimensions } : undefined,
449
+ error: undefined,
450
+ source: call.source,
451
+ method: "TOOL",
452
+ path: `/${call.source}/${call.toolName}`,
453
+ serviceName: call.method.serviceName,
454
+ action: call.method.key
455
+ }, body);
456
+ }
457
+ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip) {
437
458
  const startedAt = Date.now();
438
459
  const finish = async (result, thrown) => {
439
460
  await hooks?.afterToolCall?.(toolName, rawArgs, result, Date.now() - startedAt, context, method, thrown);
@@ -627,14 +648,15 @@ function collectTools(service, transport, config = {}) {
627
648
  }
628
649
  function createToolRunner(config) {
629
650
  const extendKeys = config.extend ? new Set(Object.keys(config.extend.schema)) : null;
630
- return async (tool, rawArgs) => {
651
+ return async (tool, rawArgs) => inToolCallContext({ source: config.source, toolName: tool.name, method: tool.method }, () => runOneToolCall(tool, rawArgs));
652
+ async function runOneToolCall(tool, rawArgs) {
631
653
  let extraContext = {};
632
654
  if (tool.shouldExtend && config.extend) {
633
655
  extraContext = await config.extend.resolve(rawArgs);
634
656
  }
635
657
  const cleanArgs = tool.shouldExtend && extendKeys ? Object.fromEntries(Object.entries(rawArgs).filter(([key]) => !extendKeys.has(key))) : rawArgs;
636
658
  return executeToolMethod(tool.method, tool.name, cleanArgs, { ...config.context, ...extraContext, source: config.source }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(tool.name, paths) : undefined);
637
- };
659
+ }
638
660
  }
639
661
  function formatToolError(result, toolName, errorHint) {
640
662
  const err = { error: result.code };
@@ -6,7 +6,7 @@ import {
6
6
  isUnsafeKey,
7
7
  safeJsonParse,
8
8
  unauthorized
9
- } from "./index-enc7t99e.js";
9
+ } from "./index-17rdjw68.js";
10
10
 
11
11
  // src/server/middleware/cookies.ts
12
12
  function parseCookies(header) {
@@ -6,30 +6,28 @@ import {
6
6
  import {
7
7
  isWithinDir
8
8
  } from "./index-x3fcszf8.js";
9
- import {
10
- extractIp,
11
- getClientInfo,
12
- getRequestContext,
13
- parseQueryParams,
14
- resolveSocketIp,
15
- resolveTraceId,
16
- setRequestEndpoint,
17
- setRequestError
18
- } from "./index-wya8bkme.js";
19
9
  import {
20
10
  AppError,
21
11
  __require,
22
12
  badRequest,
23
13
  errorCode,
14
+ extractIp,
15
+ getClientInfo,
16
+ getRequestContext,
24
17
  isRecord,
25
18
  isUnsafeKey,
26
19
  mergeMeta,
27
20
  normalizeError,
21
+ parseQueryParams,
28
22
  recordedErrorMessage,
23
+ resolveSocketIp,
24
+ resolveTraceId,
29
25
  safeJsonParse,
26
+ setRequestEndpoint,
27
+ setRequestError,
30
28
  typedEntries,
31
29
  validateHandlerOutput
32
- } from "./index-enc7t99e.js";
30
+ } from "./index-17rdjw68.js";
33
31
 
34
32
  // src/server/multipart.ts
35
33
  var DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
package/dist/node.js CHANGED
@@ -3,10 +3,9 @@ import {
3
3
  createImplement,
4
4
  createSocketIOServer,
5
5
  implement
6
- } from "./index-k7ysesv0.js";
6
+ } from "./index-dvrn81q4.js";
7
7
  import"./index-czmqks7r.js";
8
8
  import"./index-x3fcszf8.js";
9
- import"./index-wya8bkme.js";
10
9
  import {
11
10
  AppError,
12
11
  appError,
@@ -16,7 +15,7 @@ import {
16
15
  notFound,
17
16
  rateLimited,
18
17
  unauthorized
19
- } from "./index-enc7t99e.js";
18
+ } from "./index-17rdjw68.js";
20
19
  // src/server/node.ts
21
20
  import { serve } from "srvx";
22
21
  async function serveNode(config) {
@@ -5,7 +5,10 @@ import {
5
5
  getRequestContext,
6
6
  getTraceId,
7
7
  getUserId,
8
+ isRecord,
9
+ isUnsafeKey,
8
10
  parseTraceparent,
11
+ recordedErrorMessage,
9
12
  resolveTraceContext,
10
13
  runWithRequestContext,
11
14
  setRequestDimensions,
@@ -13,12 +16,7 @@ import {
13
16
  setRequestError,
14
17
  setRequestUser,
15
18
  wrapInRequestContext
16
- } from "../index-wya8bkme.js";
17
- import {
18
- isRecord,
19
- isUnsafeKey,
20
- recordedErrorMessage
21
- } from "../index-enc7t99e.js";
19
+ } from "../index-17rdjw68.js";
22
20
 
23
21
  // src/observability/sanitize.ts
24
22
  var DEFAULT_SENSITIVE_KEYS = /(password|passwd|pwd|secret|token|apikey|api[-_ ]?key|auth|authorization|bearer|session|cookie|init[-_ ]?data|credential|private[-_ ]?key)/i;
@@ -10,7 +10,7 @@ import {
10
10
  socketIoLane,
11
11
  staticRoute,
12
12
  webSocketLane
13
- } from "../index-k7ysesv0.js";
13
+ } from "../index-dvrn81q4.js";
14
14
  import {
15
15
  createAuthHook,
16
16
  createBearerResolver,
@@ -23,7 +23,7 @@ import {
23
23
  signJwt,
24
24
  verifyJwt,
25
25
  verifyPkce
26
- } from "../index-nmq59nae.js";
26
+ } from "../index-bx49hskg.js";
27
27
  import {
28
28
  DEFAULT_CORS_ALLOW_HEADERS,
29
29
  DEFAULT_CORS_EXPOSE_HEADERS,
@@ -37,14 +37,6 @@ import {
37
37
  import {
38
38
  isWithinDir
39
39
  } from "../index-x3fcszf8.js";
40
- import {
41
- extractIp,
42
- generateTraceId,
43
- getClientInfo,
44
- getTraceId,
45
- resolveSocketIp,
46
- resolveTraceId
47
- } from "../index-wya8bkme.js";
48
40
  import {
49
41
  AppError,
50
42
  STITCH_ERROR_STATUS,
@@ -52,16 +44,22 @@ import {
52
44
  badRequest,
53
45
  conflict,
54
46
  errorCode,
47
+ extractIp,
55
48
  forbidden,
56
49
  formatZodError,
50
+ generateTraceId,
51
+ getClientInfo,
52
+ getTraceId,
57
53
  isRecord,
58
54
  isStitchErrorCode,
59
55
  normalizeError,
60
56
  notFound,
61
57
  rateLimited,
58
+ resolveSocketIp,
59
+ resolveTraceId,
62
60
  unauthorized,
63
61
  zodIssues
64
- } from "../index-enc7t99e.js";
62
+ } from "../index-17rdjw68.js";
65
63
  // src/server/swept-map.ts
66
64
  function createSweptMap(options) {
67
65
  const store = new Map;
@@ -88,4 +88,19 @@ export declare function toolResultFromError(err: unknown): Extract<ToolResult, {
88
88
  ok: false;
89
89
  }>;
90
90
  export declare function executeToolMethod(method: MethodDef<unknown, unknown, unknown>, toolName: string, rawArgs: Record<string, unknown>, context: ToolCallContext, hooks?: ToolCallHooks, lifecycle?: ToolLifecycle, coerceJson?: boolean, onOutputStrip?: (paths: string[]) => void): Promise<ToolResult>;
91
+ /**
92
+ * Run `body` in a request context forked for one tool call — the shared rule,
93
+ * so every entry point isolates the same way. A mount uses it around
94
+ * `ToolExtend.resolve` as well, which runs before the executor and is a
95
+ * documented per-call resolution point. → ADR 0045.
96
+ *
97
+ * No fork where there is no ambient context: nothing is shared there, and
98
+ * inventing a root would stamp every stdio / CLI row with a `parentSpanId`
99
+ * pointing at a span no row emits.
100
+ */
101
+ export declare function inToolCallContext<T>(call: {
102
+ source: TransportSource;
103
+ toolName: string;
104
+ method: MethodDef<unknown, unknown, unknown>;
105
+ }, body: () => Promise<T>): Promise<T>;
91
106
  //# sourceMappingURL=execute.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,CACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,EACnB,KAAK,CAAC,EAAE,OAAO,KACZ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAAW,CAAC,EAAE,CACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAEjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,0DAA0D;IAC1D,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,SAAS,KAChB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,CAQpF;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAC5C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,aAAa,EACrB,SAAS,CAAC,EAAE,aAAa,EACzB,UAAU,UAAQ,EAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACxC,OAAO,CAAC,UAAU,CAAC,CAwIrB"}
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/tools/mount.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/tools/mount.ts"],"names":[],"mappings":"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"}
package/dist/tools.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  inputIsQuery,
3
3
  signJwt,
4
4
  verifyPkce
5
- } from "./index-nmq59nae.js";
5
+ } from "./index-bx49hskg.js";
6
6
  import {
7
7
  DEFAULT_CORS_ALLOW_HEADERS
8
8
  } from "./index-czmqks7r.js";
@@ -21,22 +21,20 @@ import {
21
21
  readCapped,
22
22
  toolResultFromError,
23
23
  writeDownload
24
- } from "./index-bfabej0h.js";
24
+ } from "./index-82gncajj.js";
25
25
  import {
26
26
  toJsonSchema
27
27
  } from "./index-0ed3bx43.js";
28
28
  import {
29
29
  isWithinDir
30
30
  } from "./index-x3fcszf8.js";
31
- import {
32
- getTraceId
33
- } from "./index-wya8bkme.js";
34
31
  import {
35
32
  AppError,
33
+ getTraceId,
36
34
  isRecord,
37
35
  mergeMeta,
38
36
  typedEntries
39
- } from "./index-enc7t99e.js";
37
+ } from "./index-17rdjw68.js";
40
38
 
41
39
  // src/tools/agent.ts
42
40
  import { tool, zodSchema } from "ai";
package/llms-full.txt CHANGED
@@ -2912,6 +2912,8 @@ import {
2912
2912
  } from 'stitchkit/observability'
2913
2913
 
2914
2914
  createAuthHook({ /* … */ inject: (ctx, user) => user && setRequestUser(user.id) })
2915
+ // ↑ on the HTTP path. Inside a tool call this writes to that call's own context
2916
+ // (→ ADR 0045); a tool row takes its identity from the mount's `context`.
2915
2917
  // only to override what the framework already recorded — see below:
2916
2918
  setRequestError({ code: err.code, message: err.message, details: err.issues })
2917
2919
  ```
@@ -2940,6 +2942,12 @@ meaning to (→ ADR 0021). Resolve it cheaply from `ctx.params` / headers in
2940
2942
  `ctx.req` are available there) and it lands on `event.dimensions` for the request
2941
2943
  either way, so your sink reads it as a column instead of re-parsing the path:
2942
2944
 
2945
+ > **On the tool path it lands on that call's own event, not the request's.** The
2946
+ > same hooks object is assignable to `ToolLifecycle`, so this recipe is the one
2947
+ > people apply to tools — and since ADR 0045 each tool call runs in its own
2948
+ > context. Read the value off the tool row (`event.toolName != null`); both rows
2949
+ > carry the same `traceId`.
2950
+
2943
2951
  ```ts
2944
2952
  // beforeHandle (success) and onError (failure) alike:
2945
2953
  const projectId = ctx.req?.headers.get('x-project') ?? String(ctx.params?.projectId ?? '')
@@ -2957,6 +2965,9 @@ createHandler({ /* … */ traceId: getTraceId })
2957
2965
  falls back to its own resolver — a trusted inbound `x-request-id` / `x-trace-id`,
2958
2966
  else a fresh id — so the line never carries the string `"undefined"`.
2959
2967
 
2968
+ Inside a **tool call** the context is that call's own (→ ADR 0045), so the
2969
+ enrichment a request log picks up describes the request, not the call.
2970
+
2960
2971
  `getRequestContext()` / `getTraceId()` then return the active values from
2961
2972
  anywhere in the call — stamp `getTraceId()` onto every line your logger writes.
2962
2973
  The **request log picks the context up on its own**: with a context active, each
@@ -3136,10 +3147,10 @@ object must stay assignable to `ToolLifecycle`.)
3136
3147
  which `createAuditHook`'s **tool** row does not read: a tool event takes
3137
3148
  `errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
3138
3149
  identity and `dimensions` from the context. Calling it in `onToolError` would
3139
- leave the tool row exactly as scrubbed as before and for MCP over HTTP it would
3140
- also write the cause into the log line of the enclosing `/mcp` request, turning
3141
- one incident into two records. It is right for the **HTTP** path, where the
3142
- request *is* the record.
3150
+ leave the tool row exactly as scrubbed as before. It is right for the **HTTP**
3151
+ path, where the request *is* the record. (Since ADR 0045 a tool call runs in its
3152
+ own context, so it can no longer write into the enclosing `/mcp` request's row
3153
+ either the call is simply not where that helper belongs.)
3143
3154
 
3144
3155
  ### One row that names the cause
3145
3156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.35.0",
3
+ "version": "0.36.1",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",
@@ -1,151 +0,0 @@
1
- import {
2
- isRecord,
3
- isUnsafeKey
4
- } from "./index-enc7t99e.js";
5
-
6
- // src/server/request.ts
7
- function generateTraceId() {
8
- return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
9
- }
10
- function resolveTraceId(req) {
11
- const header = req.headers.get("x-request-id") ?? req.headers.get("x-trace-id");
12
- const trimmed = header?.trim();
13
- if (trimmed && trimmed.length <= 128 && /^[\w.-]+$/.test(trimmed)) {
14
- return trimmed;
15
- }
16
- return generateTraceId();
17
- }
18
- function resolveSocketIp(req, server) {
19
- if (typeof server === "object" && server !== null && "requestIP" in server && typeof server.requestIP === "function") {
20
- const addr = server.requestIP(req);
21
- if (isRecord(addr) && typeof addr.address === "string" && addr.address) {
22
- return addr.address;
23
- }
24
- }
25
- if ("ip" in req && typeof req.ip === "string" && req.ip)
26
- return req.ip;
27
- return;
28
- }
29
- function extractIp(req, options = {}) {
30
- if (options.trustProxy) {
31
- const forwarded = req.headers.get("x-forwarded-for");
32
- if (forwarded)
33
- return (forwarded.split(",")[0] ?? "").trim().replace(/^::ffff:/, "");
34
- const realIp = req.headers.get("x-real-ip");
35
- if (realIp)
36
- return realIp.trim().replace(/^::ffff:/, "");
37
- }
38
- return (options.socketIp ?? "").replace(/^::ffff:/, "");
39
- }
40
- function getClientInfo(req, options = {}) {
41
- return {
42
- ipAddress: extractIp(req, options) || undefined,
43
- userAgent: req.headers.get("user-agent") ?? undefined
44
- };
45
- }
46
- function parseQueryParams(url) {
47
- const query = {};
48
- for (const key of new Set(url.searchParams.keys())) {
49
- if (isUnsafeKey(key))
50
- continue;
51
- const values = url.searchParams.getAll(key);
52
- const [first] = values;
53
- query[key] = values.length === 1 && first !== undefined ? first : values;
54
- }
55
- return query;
56
- }
57
-
58
- // src/observability/trace.ts
59
- var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
60
- function randomHex(bytes) {
61
- const arr = new Uint8Array(bytes);
62
- crypto.getRandomValues(arr);
63
- let hex = "";
64
- for (const byte of arr)
65
- hex += byte.toString(16).padStart(2, "0");
66
- return hex;
67
- }
68
- function createTraceContext() {
69
- return { traceId: randomHex(16), spanId: randomHex(8) };
70
- }
71
- function parseTraceparent(header) {
72
- if (!header)
73
- return null;
74
- const match = TRACEPARENT_RE.exec(header.trim());
75
- if (!match?.[1] || !match[2])
76
- return null;
77
- const traceId = match[1].toLowerCase();
78
- const parentSpanId = match[2].toLowerCase();
79
- if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
80
- return null;
81
- return { traceId, spanId: randomHex(8), parentSpanId };
82
- }
83
- function formatTraceparent(ctx) {
84
- return `00-${ctx.traceId}-${ctx.spanId}-01`;
85
- }
86
- function resolveTraceContext(req) {
87
- return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
88
- }
89
- function childSpan(parent) {
90
- return {
91
- traceId: parent.traceId,
92
- spanId: randomHex(8),
93
- parentSpanId: parent.spanId
94
- };
95
- }
96
-
97
- // src/observability/context.ts
98
- import { AsyncLocalStorage } from "node:async_hooks";
99
- var storage = new AsyncLocalStorage;
100
- function runWithRequestContext(ctx, fn) {
101
- return storage.run(ctx, fn);
102
- }
103
- function getRequestContext() {
104
- return storage.getStore();
105
- }
106
- function getTraceId() {
107
- return storage.getStore()?.trace.traceId;
108
- }
109
- function getUserId() {
110
- return storage.getStore()?.userId;
111
- }
112
- function setRequestUser(userId) {
113
- const ctx = storage.getStore();
114
- if (ctx)
115
- ctx.userId = userId;
116
- }
117
- function setRequestEndpoint(serviceName, action) {
118
- const ctx = storage.getStore();
119
- if (ctx) {
120
- ctx.serviceName = serviceName;
121
- ctx.action = action;
122
- }
123
- }
124
- function setRequestDimensions(dimensions) {
125
- const ctx = storage.getStore();
126
- if (ctx)
127
- ctx.dimensions = { ...ctx.dimensions, ...dimensions };
128
- }
129
- function setRequestError(error) {
130
- const ctx = storage.getStore();
131
- if (ctx)
132
- ctx.error = error;
133
- }
134
- function wrapInRequestContext(handler, options = {}) {
135
- return (req, server) => {
136
- const ctx = {
137
- trace: resolveTraceContext(req),
138
- source: "http",
139
- method: req.method,
140
- path: new URL(req.url, "http://localhost").pathname,
141
- startedAt: process.hrtime.bigint(),
142
- ...getClientInfo(req, {
143
- trustProxy: options.trustProxy,
144
- socketIp: resolveSocketIp(req, server)
145
- })
146
- };
147
- return runWithRequestContext(ctx, () => handler(req, server));
148
- };
149
- }
150
-
151
- export { generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };