@outputai/http 0.4.1-dev.7b85c96.0 → 0.4.1-dev.ae4bd16.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.
package/dist/cost.d.ts CHANGED
@@ -1,16 +1,9 @@
1
1
  import { KyResponse } from 'ky';
2
- export type RequestCost = {
3
- total: number;
4
- components?: Array<{
5
- name: string;
6
- value: number;
7
- }>;
8
- };
9
2
  /**
10
3
  * Attach cost information to the trace of an HTTP Request using the response
11
4
  *
12
5
  * @param response - The response of the HTTP Request to attach the information
13
- * @param cost - The cost information
6
+ * @param value - The price of the HTTP request
14
7
  * @returns
15
8
  */
16
- export declare const addRequestCost: (response: KyResponse | Response, cost: RequestCost) => void;
9
+ export declare const addRequestCost: (response: KyResponse | Response, value: number) => void;
package/dist/cost.js CHANGED
@@ -4,15 +4,16 @@ import { requestIdSymbol } from './consts.js';
4
4
  * Attach cost information to the trace of an HTTP Request using the response
5
5
  *
6
6
  * @param response - The response of the HTTP Request to attach the information
7
- * @param cost - The cost information
7
+ * @param value - The price of the HTTP request
8
8
  * @returns
9
9
  */
10
- export const addRequestCost = (response, cost) => {
10
+ export const addRequestCost = (response, value) => {
11
11
  const eventId = Reflect.get(response, requestIdSymbol);
12
12
  if (!eventId) {
13
13
  console.warn('addRequestCost(): The "response" argument did not originate from @outputai/http, no costs were added.');
14
14
  return;
15
15
  }
16
- Tracing.addEventAttribute({ eventId, name: Tracing.Attribute.COST, value: cost });
17
- emitEvent('cost:http:request', { requestId: eventId, url: response.url, cost });
16
+ const attribute = new Tracing.Attribute.HTTPRequestCost(response.url, eventId, value);
17
+ Tracing.addEventAttribute({ eventId, attribute });
18
+ emitEvent('cost:http:request', attribute);
18
19
  };
package/dist/cost.spec.js CHANGED
@@ -1,14 +1,28 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import { requestIdSymbol } from './consts.js';
3
- vi.mock('@outputai/core/sdk_activity_integration', () => ({
4
- Tracing: {
5
- addEventAttribute: vi.fn(),
6
- Attribute: {
7
- COST: 'cost'
3
+ vi.mock('@outputai/core/sdk_activity_integration', () => {
4
+ class HTTPRequestCost {
5
+ static TYPE = 'http:request:cost';
6
+ type = HTTPRequestCost.TYPE;
7
+ url;
8
+ requestId;
9
+ total;
10
+ constructor(url, requestId, total) {
11
+ this.url = url;
12
+ this.requestId = requestId;
13
+ this.total = total;
8
14
  }
9
- },
10
- emitEvent: vi.fn()
11
- }));
15
+ }
16
+ return {
17
+ Tracing: {
18
+ addEventAttribute: vi.fn(),
19
+ Attribute: {
20
+ HTTPRequestCost
21
+ }
22
+ },
23
+ emitEvent: vi.fn()
24
+ };
25
+ });
12
26
  import { Tracing, emitEvent } from '@outputai/core/sdk_activity_integration';
13
27
  import { addRequestCost } from './cost.js';
14
28
  const tracing = vi.mocked(Tracing, true);
@@ -24,7 +38,7 @@ describe('addRequestCost', () => {
24
38
  });
25
39
  it('shortcircuits when the response has no http request id', () => {
26
40
  const response = new Response();
27
- const cost = { total: 1 };
41
+ const cost = 1;
28
42
  addRequestCost(response, cost);
29
43
  expect(console.warn).toHaveBeenCalledWith('addRequestCost(): The "response" argument did not originate from @outputai/http, no costs were added.');
30
44
  expect(tracing.addEventAttribute).not.toHaveBeenCalled();
@@ -33,56 +47,36 @@ describe('addRequestCost', () => {
33
47
  it('records cost on the trace event when the response carries the request id', () => {
34
48
  const response = new Response(undefined, { status: 200 });
35
49
  Reflect.set(response, requestIdSymbol, 'evt-cost-1');
36
- const cost = { total: 2.5 };
50
+ const cost = 2.5;
37
51
  addRequestCost(response, cost);
38
52
  expect(console.warn).not.toHaveBeenCalled();
39
53
  expect(tracing.addEventAttribute).toHaveBeenCalledWith({
40
54
  eventId: 'evt-cost-1',
41
- name: Tracing.Attribute.COST,
42
- value: cost
43
- });
44
- expect(emit).toHaveBeenCalledWith('cost:http:request', {
45
- requestId: 'evt-cost-1',
46
- url: response.url,
47
- cost
55
+ attribute: expect.objectContaining({
56
+ type: Tracing.Attribute.HTTPRequestCost.TYPE,
57
+ url: response.url,
58
+ requestId: 'evt-cost-1',
59
+ total: cost
60
+ })
48
61
  });
62
+ const attribute = tracing.addEventAttribute.mock.calls[0][0].attribute;
63
+ expect(emit).toHaveBeenCalledWith('cost:http:request', attribute);
49
64
  });
50
- it('forwards multiple components to tracing', () => {
65
+ it('records zero cost on the trace event', () => {
51
66
  const response = new Response();
52
67
  Reflect.set(response, requestIdSymbol, 'evt-cost-2');
53
- const cost = {
54
- total: 10,
55
- components: [
56
- { name: 'input', value: 3 },
57
- { name: 'output', value: 7 }
58
- ]
59
- };
68
+ const cost = 0;
60
69
  addRequestCost(response, cost);
61
70
  expect(tracing.addEventAttribute).toHaveBeenCalledWith({
62
71
  eventId: 'evt-cost-2',
63
- name: Tracing.Attribute.COST,
64
- value: cost
65
- });
66
- expect(emit).toHaveBeenCalledWith('cost:http:request', {
67
- requestId: 'evt-cost-2',
68
- url: response.url,
69
- cost
70
- });
71
- });
72
- it('forwards an empty components array to tracing', () => {
73
- const response = new Response();
74
- Reflect.set(response, requestIdSymbol, 'evt-cost-3');
75
- const cost = { total: 1, components: [] };
76
- addRequestCost(response, cost);
77
- expect(tracing.addEventAttribute).toHaveBeenCalledWith({
78
- eventId: 'evt-cost-3',
79
- name: Tracing.Attribute.COST,
80
- value: cost
81
- });
82
- expect(emit).toHaveBeenCalledWith('cost:http:request', {
83
- requestId: 'evt-cost-3',
84
- url: response.url,
85
- cost
72
+ attribute: expect.objectContaining({
73
+ type: Tracing.Attribute.HTTPRequestCost.TYPE,
74
+ url: response.url,
75
+ requestId: 'evt-cost-2',
76
+ total: cost
77
+ })
86
78
  });
79
+ const attribute = tracing.addEventAttribute.mock.calls[0][0].attribute;
80
+ expect(emit).toHaveBeenCalledWith('cost:http:request', attribute);
87
81
  });
88
82
  });
@@ -13,7 +13,7 @@ RequestInit };
13
13
  * Behaves the same as any fetch function except:
14
14
  * - Sets a request header called `x-request--trace-id` with a random UUID;
15
15
  * - Sends the request, response, error and/or failure to the Trace system;
16
- * - Emits a `http:request` event on every call (success, http_error, network_error).
16
+ * - Emits a `http:request` event on every call (success, error, failure).
17
17
  *
18
18
  * @see {@link https://fetch.spec.whatwg.org/}
19
19
  * @param input - URL string, URL object or Request object (undici's or Node's)
@@ -15,7 +15,7 @@ export * as undici from 'undici';
15
15
  * Behaves the same as any fetch function except:
16
16
  * - Sets a request header called `x-request--trace-id` with a random UUID;
17
17
  * - Sends the request, response, error and/or failure to the Trace system;
18
- * - Emits a `http:request` event on every call (success, http_error, network_error).
18
+ * - Emits a `http:request` event on every call (success, error, failure).
19
19
  *
20
20
  * @see {@link https://fetch.spec.whatwg.org/}
21
21
  * @param input - URL string, URL object or Request object (undici's or Node's)
@@ -36,7 +36,7 @@ export const fetch = async (input, init) => {
36
36
  await logRequest({ requestId, request });
37
37
  try {
38
38
  const response = await undici.fetch(request);
39
- const durationMs = Math.round(performance.now() - startedAt);
39
+ const durationMs = performance.now() - startedAt;
40
40
  // This enriches the response of the request id, so it is identifiable later.
41
41
  addRequestIdToResponse(response, requestId);
42
42
  if (response.status > 399) {
@@ -47,7 +47,7 @@ export const fetch = async (input, init) => {
47
47
  return response;
48
48
  }
49
49
  catch (error) {
50
- const durationMs = Math.round(performance.now() - startedAt);
50
+ const durationMs = performance.now() - startedAt;
51
51
  logFailure({ requestId, error: error, method, url, durationMs });
52
52
  throw error;
53
53
  }
@@ -12,7 +12,7 @@ export declare const logRequest: ({ requestId, request }: {
12
12
  }) => Promise<void>;
13
13
  /**
14
14
  * Sends the trace error event for an http response with error status
15
- * and emits a `http:request` event with `outcome: 'http_error'`.
15
+ * and emits a `http:request` event with `outcome: 'error'`.
16
16
  *
17
17
  * @param options
18
18
  * @param options.requestId - id of the request
@@ -48,7 +48,7 @@ export declare const logResponse: ({ requestId, response, method, url, durationM
48
48
  }) => Promise<void>;
49
49
  /**
50
50
  * Creates the trace error event for a network/connection failure
51
- * and emits a `http:request` event with `outcome: 'network_error'`.
51
+ * and emits a `http:request` event with `outcome: 'failure'`.
52
52
  *
53
53
  * @param options
54
54
  * @param options.requestId - id of the request
@@ -20,11 +20,11 @@ export const logRequest = async ({ requestId, request }) => {
20
20
  ...(config.logVerbose && { headers: redactHeaders(request.headers), body: await parseBody(request) })
21
21
  }
22
22
  });
23
- Tracing.addEventAttribute({ eventId: requestId, name: 'requestId', value: requestId });
23
+ Tracing.addEventAttribute({ eventId: requestId, attribute: new Tracing.Attribute.HTTPRequestCount(request.url, requestId) });
24
24
  };
25
25
  /**
26
26
  * Sends the trace error event for an http response with error status
27
- * and emits a `http:request` event with `outcome: 'http_error'`.
27
+ * and emits a `http:request` event with `outcome: 'error'`.
28
28
  *
29
29
  * @param options
30
30
  * @param options.requestId - id of the request
@@ -43,7 +43,7 @@ export const logError = async ({ requestId, response, method, url, durationMs })
43
43
  }
44
44
  });
45
45
  emitHttpRequestEvent({
46
- requestId, method, url, status: response.status, durationMs, outcome: 'http_error'
46
+ requestId, method, url, status: response.status, durationMs, outcome: 'error'
47
47
  });
48
48
  };
49
49
  /**
@@ -71,7 +71,7 @@ export const logResponse = async ({ requestId, response, method, url, durationMs
71
71
  };
72
72
  /**
73
73
  * Creates the trace error event for a network/connection failure
74
- * and emits a `http:request` event with `outcome: 'network_error'`.
74
+ * and emits a `http:request` event with `outcome: 'failure'`.
75
75
  *
76
76
  * @param options
77
77
  * @param options.requestId - id of the request
@@ -83,6 +83,6 @@ export const logResponse = async ({ requestId, response, method, url, durationMs
83
83
  export const logFailure = ({ requestId, error, method, url, durationMs }) => {
84
84
  Tracing.addEventError({ id: requestId, details: serializeError(error) });
85
85
  emitHttpRequestEvent({
86
- requestId, method, url, status: undefined, durationMs, outcome: 'network_error'
86
+ requestId, method, url, status: undefined, durationMs, outcome: 'failure'
87
87
  });
88
88
  };
@@ -1,14 +1,29 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { Response, Request } from 'undici';
3
- vi.mock('@outputai/core/sdk_activity_integration', () => ({
4
- Tracing: {
5
- addEventStart: vi.fn(),
6
- addEventEnd: vi.fn(),
7
- addEventError: vi.fn(),
8
- addEventAttribute: vi.fn()
9
- },
10
- emitEvent: vi.fn()
11
- }));
3
+ vi.mock('@outputai/core/sdk_activity_integration', () => {
4
+ class HTTPRequestCount {
5
+ static TYPE = 'http:request:count';
6
+ type = HTTPRequestCount.TYPE;
7
+ url;
8
+ requestId;
9
+ constructor(url, requestId) {
10
+ this.url = url;
11
+ this.requestId = requestId;
12
+ }
13
+ }
14
+ return {
15
+ Tracing: {
16
+ addEventStart: vi.fn(),
17
+ addEventEnd: vi.fn(),
18
+ addEventError: vi.fn(),
19
+ addEventAttribute: vi.fn(),
20
+ Attribute: {
21
+ HTTPRequestCount
22
+ }
23
+ },
24
+ emitEvent: vi.fn()
25
+ };
26
+ });
12
27
  import { Tracing, emitEvent } from '@outputai/core/sdk_activity_integration';
13
28
  const tracing = vi.mocked(Tracing, true);
14
29
  const emit = vi.mocked(emitEvent, true);
@@ -32,11 +47,14 @@ beforeEach(() => {
32
47
  });
33
48
  describe('fetch/logger', () => {
34
49
  describe('logRequest', () => {
35
- const expectRequestIdAttribute = (requestId) => {
50
+ const expectRequestCountAttribute = (requestId, url) => {
36
51
  expect(tracing.addEventAttribute).toHaveBeenCalledWith({
37
52
  eventId: requestId,
38
- name: 'requestId',
39
- value: requestId
53
+ attribute: expect.objectContaining({
54
+ type: Tracing.Attribute.HTTPRequestCount.TYPE,
55
+ url,
56
+ requestId
57
+ })
40
58
  });
41
59
  };
42
60
  it('records minimal details when verbose is off', async () => {
@@ -52,14 +70,14 @@ describe('fetch/logger', () => {
52
70
  url: 'https://api.example.com/r'
53
71
  }
54
72
  });
55
- expectRequestIdAttribute('req-1');
73
+ expectRequestCountAttribute('req-1', 'https://api.example.com/r');
56
74
  });
57
75
  it('defaults method to GET', async () => {
58
76
  const { logRequest } = await logLogger(false);
59
77
  const request = new Request('https://x.test');
60
78
  await logRequest({ requestId: 'r2', request });
61
79
  expect(tracing.addEventStart.mock.calls[0][0].details.method).toBe('GET');
62
- expectRequestIdAttribute('r2');
80
+ expectRequestCountAttribute('r2', 'https://x.test/');
63
81
  });
64
82
  it('includes redacted headers and parsed body when verbose is on', async () => {
65
83
  const { logRequest } = await logLogger(true);
@@ -84,7 +102,7 @@ describe('fetch/logger', () => {
84
102
  body: { x: 1 }
85
103
  }
86
104
  });
87
- expectRequestIdAttribute('req-v');
105
+ expectRequestCountAttribute('req-v', 'https://api.example.com/p');
88
106
  });
89
107
  });
90
108
  describe('logError', () => {
@@ -222,7 +240,7 @@ describe('fetch/logger', () => {
222
240
  outcome: 'success'
223
241
  });
224
242
  });
225
- it('emits http:request with outcome=http_error on logError', async () => {
243
+ it('emits http:request with outcome=error on logError', async () => {
226
244
  const { logError } = await logLogger(false);
227
245
  const response = new Response('boom', { status: 500 });
228
246
  await logError({
@@ -238,10 +256,10 @@ describe('fetch/logger', () => {
238
256
  url: 'https://api.example.com/err',
239
257
  status: 500,
240
258
  durationMs: 15,
241
- outcome: 'http_error'
259
+ outcome: 'error'
242
260
  });
243
261
  });
244
- it('emits http:request with outcome=network_error on logFailure (status undefined)', async () => {
262
+ it('emits http:request with outcome=failure on logFailure (status undefined)', async () => {
245
263
  const { logFailure } = await logLogger(false);
246
264
  const err = new TypeError('network');
247
265
  logFailure({
@@ -257,7 +275,7 @@ describe('fetch/logger', () => {
257
275
  url: 'https://api.example.com/net',
258
276
  status: undefined,
259
277
  durationMs: 9,
260
- outcome: 'network_error'
278
+ outcome: 'failure'
261
279
  });
262
280
  });
263
281
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/http",
3
- "version": "0.4.1-dev.7b85c96.0",
3
+ "version": "0.4.1-dev.ae4bd16.0",
4
4
  "description": "Framework abstraction to make HTTP calls with tracing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "ky": "1.14.3",
13
13
  "undici": "8.1.0",
14
- "@outputai/core": "0.4.1-dev.7b85c96.0"
14
+ "@outputai/core": "0.4.1-dev.ae4bd16.0"
15
15
  },
16
16
  "license": "Apache-2.0",
17
17
  "publishConfig": {