@elench/testkit 0.1.81 → 0.1.83

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 (55) hide show
  1. package/README.md +64 -27
  2. package/lib/cli/agents/index.mjs +64 -0
  3. package/lib/cli/agents/investigate.mjs +75 -0
  4. package/lib/cli/agents/investigation-context.mjs +102 -0
  5. package/lib/cli/agents/investigation-context.test.mjs +144 -0
  6. package/lib/cli/agents/prompt-builder.mjs +25 -0
  7. package/lib/cli/agents/providers/claude.mjs +74 -0
  8. package/lib/cli/agents/providers/claude.test.mjs +95 -0
  9. package/lib/cli/agents/providers/codex.mjs +83 -0
  10. package/lib/cli/agents/providers/codex.test.mjs +93 -0
  11. package/lib/cli/agents/providers/shared.mjs +134 -0
  12. package/lib/cli/command-helpers.mjs +53 -25
  13. package/lib/cli/command-helpers.test.mjs +122 -0
  14. package/lib/cli/commands/investigate.mjs +87 -0
  15. package/lib/cli/commands/investigate.test.mjs +83 -0
  16. package/lib/cli/entrypoint.mjs +3 -0
  17. package/lib/cli/presentation/colors.mjs +12 -0
  18. package/lib/cli/presentation/events-reporter.mjs +135 -0
  19. package/lib/cli/presentation/events-reporter.test.mjs +73 -0
  20. package/lib/cli/presentation/summary-box.mjs +11 -11
  21. package/lib/cli/presentation/summary-box.test.mjs +17 -0
  22. package/lib/cli/presentation/tree-reporter.mjs +159 -0
  23. package/lib/cli/presentation/tree-reporter.test.mjs +166 -0
  24. package/lib/cli/tui/run-app.mjs +1 -0
  25. package/lib/cli/tui/run-session-app.mjs +370 -0
  26. package/lib/cli/tui/run-session-app.test.mjs +50 -0
  27. package/lib/cli/tui/run-session-state.mjs +481 -0
  28. package/lib/cli/tui/run-tree-state.mjs +1 -0
  29. package/lib/cli/tui/run-tree-state.test.mjs +324 -0
  30. package/lib/config-api/auth-fixtures.mjs +767 -0
  31. package/lib/config-api/index.d.ts +92 -108
  32. package/lib/config-api/index.mjs +22 -12
  33. package/lib/config-api/index.test.mjs +103 -210
  34. package/lib/discovery/index.mjs +1 -1
  35. package/lib/index.d.ts +34 -10
  36. package/lib/runner/orchestrator.mjs +1 -0
  37. package/lib/runtime/index.d.ts +177 -27
  38. package/lib/runtime/index.mjs +68 -3
  39. package/lib/runtime-src/k6/http-assertions.js +31 -1
  40. package/lib/runtime-src/k6/http-checks.js +120 -0
  41. package/lib/runtime-src/k6/http-checks.test.mjs +120 -0
  42. package/lib/runtime-src/k6/http-suite-runtime.js +151 -0
  43. package/lib/runtime-src/k6/http.js +285 -56
  44. package/lib/runtime-src/k6/http.test.mjs +205 -0
  45. package/lib/runtime-src/k6/scenario-suite.js +13 -110
  46. package/lib/runtime-src/k6/suite.js +13 -107
  47. package/lib/runtime-src/shared/error-body.mjs +42 -0
  48. package/lib/runtime-src/shared/http-parsing.mjs +68 -0
  49. package/lib/runtime-src/shared/http-parsing.test.mjs +69 -0
  50. package/node_modules/@elench/next-analysis/package.json +1 -1
  51. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  52. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  53. package/node_modules/@elench/ts-analysis/package.json +1 -1
  54. package/package.json +5 -5
  55. package/lib/config-api/profiles.mjs +0 -640
@@ -0,0 +1,151 @@
1
+ import { createHttpClient, getEnv } from "./http.js";
2
+ import { resolveHttpProfile } from "../../config-api/runtime.mjs";
3
+
4
+ export function normalizeSuiteArgs(configOrRun, maybeRun) {
5
+ if (typeof configOrRun === "function") {
6
+ return { config: {}, run: configOrRun };
7
+ }
8
+ if (typeof maybeRun !== "function") {
9
+ throw new Error("suite factory requires a run callback");
10
+ }
11
+ return { config: configOrRun || {}, run: maybeRun };
12
+ }
13
+
14
+ export function mergeProfileConfig(config) {
15
+ if (!config?.profile) return config || {};
16
+
17
+ const profile = resolveHttpProfile(config.profile) || {};
18
+ return {
19
+ ...profile,
20
+ ...config,
21
+ auth: config.auth ?? profile.auth ?? null,
22
+ headers: config.headers ?? profile.headers,
23
+ rawHeaders: config.rawHeaders ?? profile.rawHeaders,
24
+ options: config.options ?? profile.options,
25
+ env: config.env ?? profile.env,
26
+ __testkitAuthProfile: config.__testkitAuthProfile ?? profile.__testkitAuthProfile ?? null,
27
+ };
28
+ }
29
+
30
+ export function resolveRuntimeConfig(config, sessionBundle = null) {
31
+ const resolvedConfig = mergeProfileConfig(config);
32
+ const env = {
33
+ ...(resolvedConfig.env || getEnv()),
34
+ rawEnv: __ENV,
35
+ };
36
+ const auth = resolvedConfig.auth || null;
37
+ const profileMeta = resolvedConfig.__testkitAuthProfile || null;
38
+ const client = createHttpClient({
39
+ baseUrl: env.BASE,
40
+ defaultActor: profileMeta?.primaryActor || sessionBundle?.primaryActor || null,
41
+ routeHeaders: env.routeParams,
42
+ sessionBundle,
43
+ getHeaders(context) {
44
+ return {
45
+ ...callHeaders(auth?.headers, sessionBundle, env, context.actorName),
46
+ ...callHeaders(resolvedConfig.headers, sessionBundle, env, context.actorName),
47
+ };
48
+ },
49
+ getRawHeaders(context) {
50
+ return callHeaders(resolvedConfig.rawHeaders, sessionBundle, env, context.actorName);
51
+ },
52
+ });
53
+
54
+ return {
55
+ auth,
56
+ client,
57
+ env,
58
+ profileMeta,
59
+ resolvedConfig,
60
+ };
61
+ }
62
+
63
+ export function createSuiteActors(sessionBundle, client, profileMeta) {
64
+ const actorNames = profileMeta?.actorNames || Object.keys(sessionBundle?.actors || {});
65
+ const entries = actorNames.map((actorName) => {
66
+ const actorRecord = sessionBundle?.actors?.[actorName] || null;
67
+ const actorClient = client.as(actorName);
68
+ return {
69
+ email: actorRecord?.email || null,
70
+ headers: actorClient.headers(),
71
+ index: Number(actorRecord?.actorIndex ?? 0),
72
+ key: actorName,
73
+ name: actorRecord?.name || null,
74
+ organizationKey: actorRecord?.organizationKey || null,
75
+ organizationName: actorRecord?.organizationName || null,
76
+ rawHeaders: actorClient.rawHeaders(),
77
+ rawReq: actorClient.rawReq,
78
+ req: actorClient,
79
+ session: actorRecord?.session || null,
80
+ };
81
+ });
82
+ const byName = new Map(entries.map((entry, index) => [actorNames[index], entry]));
83
+ const primaryName = profileMeta?.primaryActor || sessionBundle?.primaryActor || null;
84
+ const primary = primaryName ? byName.get(primaryName) || null : null;
85
+
86
+ return {
87
+ actor: primary,
88
+ actors: {
89
+ names: actorNames,
90
+ primary,
91
+ get(name) {
92
+ const actor = byName.get(name);
93
+ if (!actor) {
94
+ throw new Error(`Unknown testkit actor "${name}"`);
95
+ }
96
+ return actor;
97
+ },
98
+ has(name) {
99
+ return byName.has(name);
100
+ },
101
+ list() {
102
+ return entries.slice();
103
+ },
104
+ },
105
+ };
106
+ }
107
+
108
+ export function formatFatalSuiteError(phase, error) {
109
+ if (error instanceof Error) {
110
+ return `Uncaught testkit suite error during ${phase}: ${error.message}`;
111
+ }
112
+ return `Uncaught testkit suite error during ${phase}: ${String(error)}`;
113
+ }
114
+
115
+ export function buildRuntimeExceptionDetail(phase, error) {
116
+ const message =
117
+ error instanceof Error ? error.message : String(error);
118
+ const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
119
+ const location = extractLocationFromStack(stack);
120
+ return {
121
+ kind: "runtime-exception",
122
+ key: location ? `${location.path}:${location.line}:${location.column}` : `runtime-exception:${phase}:${message}`,
123
+ title: "Uncaught runtime exception",
124
+ message: `Uncaught testkit suite error during ${phase}: ${message}`,
125
+ location,
126
+ stack,
127
+ };
128
+ }
129
+
130
+ function callHeaders(builder, sessionBundle, env, actorName) {
131
+ if (typeof builder !== "function") return {};
132
+ return builder(sessionBundle, { actor: actorName || null, env }) || {};
133
+ }
134
+
135
+ function extractLocationFromStack(stack) {
136
+ if (!stack) return null;
137
+ const matches = [...String(stack).matchAll(/(file:\/\/[^\s)]+|\/[^\s):]+):(\d+):(\d+)/g)].map((match) => ({
138
+ path: normalizeStackPath(match[1]),
139
+ line: Number(match[2]),
140
+ column: Number(match[3]),
141
+ }));
142
+ return matches[0] || null;
143
+ }
144
+
145
+ function normalizeStackPath(rawPath) {
146
+ if (typeof rawPath !== "string") return rawPath;
147
+ if (rawPath.startsWith("file://")) {
148
+ return rawPath.slice("file://".length);
149
+ }
150
+ return rawPath;
151
+ }
@@ -24,8 +24,10 @@ export function getEnv() {
24
24
  export function createHttpClient(config) {
25
25
  const {
26
26
  baseUrl,
27
+ defaultActor = null,
27
28
  routeHeaders = {},
28
29
  defaultHeaders = { "Content-Type": "application/json" },
30
+ sessionBundle = null,
29
31
  getHeaders = null,
30
32
  getRawHeaders = null,
31
33
  } = config;
@@ -34,81 +36,280 @@ export function createHttpClient(config) {
34
36
  throw new Error("baseUrl is required");
35
37
  }
36
38
 
37
- function buildHeaders(builder, setupData, extraHeaders = {}) {
38
- return {
39
- ...defaultHeaders,
40
- ...safeHeaders(builder, setupData),
39
+ function buildHeaders(builder, context, extraHeaders = {}, options = {}) {
40
+ const { includeDefaultHeaders = true, contentTypeJson = true } = options;
41
+ const resolvedHeaders = {
42
+ ...(includeDefaultHeaders ? defaultHeaders : {}),
43
+ ...safeHeaders(builder, context),
41
44
  ...routeHeaders,
42
45
  ...extraHeaders,
43
46
  };
47
+
48
+ if (contentTypeJson === false) {
49
+ delete resolvedHeaders["Content-Type"];
50
+ delete resolvedHeaders["content-type"];
51
+ }
52
+
53
+ return resolvedHeaders;
54
+ }
55
+
56
+ function buildHeaderContext(actorName = null) {
57
+ return {
58
+ actor: actorName && sessionBundle?.actors ? sessionBundle.actors[actorName] || null : null,
59
+ actorName,
60
+ sessionBundle,
61
+ };
62
+ }
63
+
64
+ function ensureKnownActor(actorName) {
65
+ if (!actorName || !sessionBundle?.actors) return;
66
+ if (!sessionBundle.actors[actorName]) {
67
+ throw new Error(`Unknown testkit actor "${actorName}"`);
68
+ }
44
69
  }
45
70
 
46
- function request(method, path, setupData, body, extraHeaders = {}) {
71
+ function resolvedHeadersFor(actorName, extraHeaders = {}) {
72
+ ensureKnownActor(actorName);
73
+ const headerContext = buildHeaderContext(actorName);
74
+ return buildHeaders(getHeaders, headerContext, extraHeaders);
75
+ }
76
+
77
+ function resolvedRawHeadersFor(actorName = null, extraHeaders = {}) {
78
+ ensureKnownActor(actorName);
79
+ const headerContext = buildHeaderContext(actorName);
80
+ return buildHeaders(getRawHeaders, headerContext, extraHeaders);
81
+ }
82
+
83
+ function requestAs(actorName, method, path, body, extraHeaders = {}, options = {}) {
84
+ ensureKnownActor(actorName);
47
85
  const url = `${baseUrl}${path}`;
48
- const headers = buildHeaders(getHeaders, setupData, extraHeaders);
49
- return runHttpRequest(method, path, url, body, headers);
86
+ const headerContext = buildHeaderContext(actorName);
87
+ const headers = buildHeaders(getHeaders, headerContext, extraHeaders, {
88
+ contentTypeJson: options.contentTypeJson !== false,
89
+ includeDefaultHeaders: options.includeDefaultHeaders !== false,
90
+ });
91
+ return runHttpRequest(method, path, url, body, headers, headerContext.actor, options);
50
92
  }
51
93
 
52
- function raw(method, path, body, extraHeaders = {}) {
94
+ function rawAs(actorName, method, path, body, extraHeaders = {}, options = {}) {
95
+ ensureKnownActor(actorName);
53
96
  const url = `${baseUrl}${path}`;
54
- const headers = buildHeaders(getRawHeaders, null, extraHeaders);
55
- return runHttpRequest(method, path, url, body, headers);
97
+ const headerContext = buildHeaderContext(actorName);
98
+ const headers = buildHeaders(getRawHeaders, headerContext, extraHeaders, {
99
+ contentTypeJson: options.contentTypeJson !== false,
100
+ includeDefaultHeaders: options.includeDefaultHeaders !== false,
101
+ });
102
+ return runHttpRequest(method, path, url, body, headers, headerContext.actor, options);
56
103
  }
57
104
 
58
- function getWithHeaders(path, setupData, extraHeaders = {}) {
59
- return runHttpRequest(
60
- "GET",
61
- path,
62
- `${baseUrl}${path}`,
63
- null,
64
- buildHeaders(getHeaders, setupData, extraHeaders)
65
- );
105
+ function raw(method, path, body, extraHeaders = {}) {
106
+ return rawAs(null, method, path, body, extraHeaders);
66
107
  }
67
108
 
109
+ function requestMultipart(actorName, method, path, payload, extraHeaders = {}, options = {}) {
110
+ const body = buildMultipartBody(payload);
111
+ return requestAs(actorName, method, path, body, extraHeaders, {
112
+ ...options,
113
+ contentTypeJson: false,
114
+ includeDefaultHeaders: false,
115
+ serialize: "raw",
116
+ });
117
+ }
118
+
119
+ function rawMultipart(actorName, method, path, payload, extraHeaders = {}, options = {}) {
120
+ const body = buildMultipartBody(payload);
121
+ return rawAs(actorName, method, path, body, extraHeaders, {
122
+ ...options,
123
+ contentTypeJson: false,
124
+ includeDefaultHeaders: false,
125
+ serialize: "raw",
126
+ });
127
+ }
128
+
129
+ function createActorClient(actorName) {
130
+ const actorRawReq = createRawInvoker(actorName);
131
+
132
+ return {
133
+ headers(extraHeaders = {}) {
134
+ return resolvedHeadersFor(actorName, extraHeaders);
135
+ },
136
+ rawHeaders(extraHeaders = {}) {
137
+ return resolvedRawHeadersFor(actorName, extraHeaders);
138
+ },
139
+ rawReq: actorRawReq,
140
+ delete(path, extraHeaders = {}) {
141
+ return requestAs(actorName, "DELETE", path, null, extraHeaders);
142
+ },
143
+ get(path, extraHeaders = {}) {
144
+ return requestAs(actorName, "GET", path, null, extraHeaders);
145
+ },
146
+ patch(path, body, extraHeaders = {}) {
147
+ return requestAs(actorName, "PATCH", path, body, extraHeaders);
148
+ },
149
+ post(path, body, extraHeaders = {}) {
150
+ return requestAs(actorName, "POST", path, body, extraHeaders);
151
+ },
152
+ put(path, body, extraHeaders = {}) {
153
+ return requestAs(actorName, "PUT", path, body, extraHeaders);
154
+ },
155
+ request(method, path, body, extraHeaders = {}) {
156
+ return requestAs(actorName, method, path, body, extraHeaders);
157
+ },
158
+ raw(method, path, body, extraHeaders = {}) {
159
+ return actorRawReq(method, path, body, extraHeaders);
160
+ },
161
+ rawDelete(path, extraHeaders = {}) {
162
+ return actorRawReq.delete(path, extraHeaders);
163
+ },
164
+ rawGet(path, extraHeaders = {}) {
165
+ return actorRawReq.get(path, extraHeaders);
166
+ },
167
+ rawPatch(path, body, extraHeaders = {}) {
168
+ return actorRawReq.patch(path, body, extraHeaders);
169
+ },
170
+ rawPost(path, body, extraHeaders = {}) {
171
+ return actorRawReq.post(path, body, extraHeaders);
172
+ },
173
+ rawPut(path, body, extraHeaders = {}) {
174
+ return actorRawReq.put(path, body, extraHeaders);
175
+ },
176
+ multipart: {
177
+ post(path, payload, extraHeaders = {}) {
178
+ return requestMultipart(actorName, "POST", path, payload, extraHeaders);
179
+ },
180
+ put(path, payload, extraHeaders = {}) {
181
+ return requestMultipart(actorName, "PUT", path, payload, extraHeaders);
182
+ },
183
+ patch(path, payload, extraHeaders = {}) {
184
+ return requestMultipart(actorName, "PATCH", path, payload, extraHeaders);
185
+ },
186
+ },
187
+ rawMultipart: {
188
+ post(path, payload, extraHeaders = {}) {
189
+ return actorRawReq.multipart.post(path, payload, extraHeaders);
190
+ },
191
+ put(path, payload, extraHeaders = {}) {
192
+ return actorRawReq.multipart.put(path, payload, extraHeaders);
193
+ },
194
+ patch(path, payload, extraHeaders = {}) {
195
+ return actorRawReq.multipart.patch(path, payload, extraHeaders);
196
+ },
197
+ },
198
+ };
199
+ }
200
+
201
+ function createRawInvoker(defaultRawActor = null) {
202
+ const invoker = (method, path, body, extraHeaders = {}) =>
203
+ rawAs(defaultRawActor, method, path, body, extraHeaders);
204
+
205
+ invoker.request = (method, path, body, extraHeaders = {}) =>
206
+ rawAs(defaultRawActor, method, path, body, extraHeaders);
207
+ invoker.get = (path, extraHeaders = {}) => rawAs(defaultRawActor, "GET", path, null, extraHeaders);
208
+ invoker.post = (path, body, extraHeaders = {}) => rawAs(defaultRawActor, "POST", path, body, extraHeaders);
209
+ invoker.put = (path, body, extraHeaders = {}) => rawAs(defaultRawActor, "PUT", path, body, extraHeaders);
210
+ invoker.patch = (path, body, extraHeaders = {}) => rawAs(defaultRawActor, "PATCH", path, body, extraHeaders);
211
+ invoker.delete = (path, extraHeaders = {}) =>
212
+ rawAs(defaultRawActor, "DELETE", path, null, extraHeaders);
213
+ invoker.headers = (extraHeaders = {}) => resolvedRawHeadersFor(defaultRawActor, extraHeaders);
214
+ invoker.as = (actorName) => {
215
+ ensureKnownActor(actorName);
216
+ return createRawInvoker(actorName);
217
+ };
218
+ invoker.multipart = {
219
+ post(path, payload, extraHeaders = {}) {
220
+ return rawMultipart(defaultRawActor, "POST", path, payload, extraHeaders);
221
+ },
222
+ put(path, payload, extraHeaders = {}) {
223
+ return rawMultipart(defaultRawActor, "PUT", path, payload, extraHeaders);
224
+ },
225
+ patch(path, payload, extraHeaders = {}) {
226
+ return rawMultipart(defaultRawActor, "PATCH", path, payload, extraHeaders);
227
+ },
228
+ };
229
+
230
+ return invoker;
231
+ }
232
+
233
+ const defaultClient = createActorClient(defaultActor);
234
+ const rawClient = createRawInvoker(null);
235
+
68
236
  return {
69
237
  rawHttp: http,
70
- request,
71
- raw,
72
- get(path, setupData, extraHeaders = {}) {
73
- return request("GET", path, setupData, null, extraHeaders);
238
+ headers(extraHeaders = {}) {
239
+ return resolvedHeadersFor(defaultActor, extraHeaders);
240
+ },
241
+ rawHeaders(actorName = null, extraHeaders = {}) {
242
+ return resolvedRawHeadersFor(actorName, extraHeaders);
243
+ },
244
+ as(actorName) {
245
+ ensureKnownActor(actorName);
246
+ return createActorClient(actorName);
74
247
  },
75
- put(path, setupData, body, extraHeaders = {}) {
76
- return request("PUT", path, setupData, body, extraHeaders);
248
+ request: defaultClient.request,
249
+ raw: rawClient,
250
+ get(path, extraHeaders = {}) {
251
+ return defaultClient.get(path, extraHeaders);
77
252
  },
78
- post(path, setupData, body, extraHeaders = {}) {
79
- return request("POST", path, setupData, body, extraHeaders);
253
+ put(path, body, extraHeaders = {}) {
254
+ return defaultClient.put(path, body, extraHeaders);
80
255
  },
81
- patch(path, setupData, body, extraHeaders = {}) {
82
- return request("PATCH", path, setupData, body, extraHeaders);
256
+ post(path, body, extraHeaders = {}) {
257
+ return defaultClient.post(path, body, extraHeaders);
83
258
  },
84
- delete(path, setupData, extraHeaders = {}) {
85
- return request("DELETE", path, setupData, null, extraHeaders);
259
+ patch(path, body, extraHeaders = {}) {
260
+ return defaultClient.patch(path, body, extraHeaders);
261
+ },
262
+ delete(path, extraHeaders = {}) {
263
+ return defaultClient.delete(path, extraHeaders);
86
264
  },
87
265
  rawGet(path, extraHeaders = {}) {
88
- return raw("GET", path, null, extraHeaders);
266
+ return rawClient.get(path, extraHeaders);
89
267
  },
90
268
  rawPost(path, body, extraHeaders = {}) {
91
- return raw("POST", path, body, extraHeaders);
269
+ return rawClient.post(path, body, extraHeaders);
92
270
  },
93
271
  rawPut(path, body, extraHeaders = {}) {
94
- return raw("PUT", path, body, extraHeaders);
272
+ return rawClient.put(path, body, extraHeaders);
95
273
  },
96
274
  rawPatch(path, body, extraHeaders = {}) {
97
- return raw("PATCH", path, body, extraHeaders);
275
+ return rawClient.patch(path, body, extraHeaders);
98
276
  },
99
277
  rawDelete(path, extraHeaders = {}) {
100
- return raw("DELETE", path, null, extraHeaders);
278
+ return rawClient.delete(path, extraHeaders);
279
+ },
280
+ multipart: {
281
+ post(path, payload, extraHeaders = {}) {
282
+ return requestMultipart(defaultActor, "POST", path, payload, extraHeaders);
283
+ },
284
+ put(path, payload, extraHeaders = {}) {
285
+ return requestMultipart(defaultActor, "PUT", path, payload, extraHeaders);
286
+ },
287
+ patch(path, payload, extraHeaders = {}) {
288
+ return requestMultipart(defaultActor, "PATCH", path, payload, extraHeaders);
289
+ },
290
+ },
291
+ rawMultipart: {
292
+ post(path, payload, extraHeaders = {}) {
293
+ return rawClient.multipart.post(path, payload, extraHeaders);
294
+ },
295
+ put(path, payload, extraHeaders = {}) {
296
+ return rawClient.multipart.put(path, payload, extraHeaders);
297
+ },
298
+ patch(path, payload, extraHeaders = {}) {
299
+ return rawClient.multipart.patch(path, payload, extraHeaders);
300
+ },
101
301
  },
102
- getWithHeaders,
103
302
  };
104
303
  }
105
304
 
106
- export function makeReq(baseUrl, routeHeaders = {}, getHeaders = null) {
305
+ export function makeReq(baseUrl, sessionBundle = null, routeHeaders = {}, getHeaders = null, defaultActor = null) {
107
306
  return createHttpClient({
108
307
  baseUrl,
109
308
  routeHeaders,
309
+ sessionBundle,
310
+ defaultActor,
110
311
  getHeaders,
111
- }).request;
312
+ });
112
313
  }
113
314
 
114
315
  export function makeRawReq(baseUrl, routeHeaders = {}, getRawHeaders = null) {
@@ -119,15 +320,7 @@ export function makeRawReq(baseUrl, routeHeaders = {}, getRawHeaders = null) {
119
320
  }).raw;
120
321
  }
121
322
 
122
- export function makeGetWithHeaders(baseUrl, routeHeaders = {}, getHeaders = null) {
123
- return createHttpClient({
124
- baseUrl,
125
- routeHeaders,
126
- getHeaders,
127
- }).getWithHeaders;
128
- }
129
-
130
- function runHttpRequest(method, path, url, body, headers) {
323
+ function runHttpRequest(method, path, url, body, headers, actorRecord = null, requestOptions = {}) {
131
324
  const ordinal = nextTraceOrdinal();
132
325
  const requestId = buildRequestId(ordinal);
133
326
  const finalHeaders = {
@@ -140,16 +333,18 @@ function runHttpRequest(method, path, url, body, headers) {
140
333
  method,
141
334
  path,
142
335
  url,
336
+ actorRecord,
143
337
  requestHeaders: finalHeaders,
144
338
  });
145
- const options = { headers: finalHeaders };
339
+ const transportOptions = { headers: finalHeaders };
146
340
 
147
341
  let rawResponse;
148
- if (method === "GET") rawResponse = http.get(url, options);
149
- else if (method === "PUT") rawResponse = http.put(url, JSON.stringify(body), options);
150
- else if (method === "POST") rawResponse = http.post(url, JSON.stringify(body), options);
151
- else if (method === "PATCH") rawResponse = http.patch(url, JSON.stringify(body), options);
152
- else if (method === "DELETE") rawResponse = http.del(url, null, options);
342
+ const requestBody = serializeRequestBody(body, requestOptions.serialize || "json");
343
+ if (method === "GET") rawResponse = http.get(url, transportOptions);
344
+ else if (method === "PUT") rawResponse = http.put(url, requestBody, transportOptions);
345
+ else if (method === "POST") rawResponse = http.post(url, requestBody, transportOptions);
346
+ else if (method === "PATCH") rawResponse = http.patch(url, requestBody, transportOptions);
347
+ else if (method === "DELETE") rawResponse = http.del(url, null, transportOptions);
153
348
  else throw new Error(`unsupported method: ${method}`);
154
349
 
155
350
  finalizeTrace(trace, rawResponse);
@@ -190,7 +385,10 @@ export function summarizeHttpTrace(response) {
190
385
  const trace = getHttpTrace(response);
191
386
  if (!trace) return null;
192
387
  return {
388
+ actorName: trace.actorName,
193
389
  id: trace.id,
390
+ organizationKey: trace.organizationKey,
391
+ organizationName: trace.organizationName,
194
392
  requestId: trace.requestId,
195
393
  method: trace.method,
196
394
  path: trace.path,
@@ -235,9 +433,12 @@ export function toBodyPreview(response) {
235
433
  return truncate(rawBody, TRACE_PREVIEW_LIMIT);
236
434
  }
237
435
 
238
- function createTrace({ ordinal, requestId, method, path, url, requestHeaders }) {
436
+ function createTrace({ ordinal, requestId, method, path, url, actorRecord, requestHeaders }) {
239
437
  return {
438
+ actorName: actorRecord?.actorName || null,
240
439
  id: `${traceState.phase}-${String(ordinal).padStart(3, "0")}`,
440
+ organizationKey: actorRecord?.organizationKey || null,
441
+ organizationName: actorRecord?.organizationName || null,
241
442
  requestId,
242
443
  startedAt: new Date().toISOString(),
243
444
  method,
@@ -342,9 +543,9 @@ function normalizeLabel(value, fallback) {
342
543
  return normalized.length > 0 ? normalized : fallback;
343
544
  }
344
545
 
345
- function safeHeaders(builder, setupData) {
546
+ function safeHeaders(builder, context) {
346
547
  if (typeof builder !== "function") return {};
347
- return builder(setupData) || {};
548
+ return builder(context) || {};
348
549
  }
349
550
 
350
551
  function createWrappedResponse(rawResponse, trace) {
@@ -381,3 +582,31 @@ function decodeQueryComponent(value) {
381
582
  return String(value || "");
382
583
  }
383
584
  }
585
+
586
+ function serializeRequestBody(body, mode = "json") {
587
+ if (body == null) return body;
588
+ if (mode === "raw") return body;
589
+ if (typeof body === "string") return body;
590
+ return JSON.stringify(body);
591
+ }
592
+
593
+ function buildMultipartBody(payload = {}) {
594
+ const fields = payload?.fields || {};
595
+ const files = Array.isArray(payload?.files) ? payload.files : [];
596
+ const body = { ...fields };
597
+
598
+ for (const fileEntry of files) {
599
+ if (!fileEntry || typeof fileEntry !== "object") {
600
+ throw new Error("multipart file entries must be objects");
601
+ }
602
+
603
+ const field = String(fileEntry.field || "").trim();
604
+ if (!field) {
605
+ throw new Error("multipart file entries require a field name");
606
+ }
607
+
608
+ body[field] = http.file(fileEntry.data, fileEntry.filename, fileEntry.contentType);
609
+ }
610
+
611
+ return body;
612
+ }