@warpmetrics/warp 0.0.17 → 0.0.19

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/README.md CHANGED
@@ -14,7 +14,7 @@ npm install @warpmetrics/warp
14
14
 
15
15
  ```js
16
16
  import OpenAI from 'openai';
17
- import { warp, run, group, call, outcome } from '@warpmetrics/warp';
17
+ import { warp, run, group, call, trace, outcome } from '@warpmetrics/warp';
18
18
 
19
19
  const openai = warp(new OpenAI(), { apiKey: 'wm_...' });
20
20
 
@@ -90,6 +90,38 @@ call(r, response);
90
90
  call(g, response, { label: 'extract' }); // with opts
91
91
  ```
92
92
 
93
+ ### `trace(target, data)`
94
+
95
+ Manually record an LLM call for providers not wrapped by `warp()`.
96
+
97
+ ```js
98
+ trace(r, {
99
+ provider: 'google',
100
+ model: 'gemini-2.0-flash',
101
+ messages: [{ role: 'user', content: 'Hello' }],
102
+ response: 'Hi there!',
103
+ tokens: { prompt: 10, completion: 5 },
104
+ latency: 230,
105
+ cost: 0.0001,
106
+ });
107
+ ```
108
+
109
+ | Field | Type | Required | Description |
110
+ |---|---|---|---|
111
+ | `provider` | `string` | Yes | Provider name (e.g. `"google"`, `"cohere"`) |
112
+ | `model` | `string` | Yes | Model identifier |
113
+ | `messages` | `any` | No | Request messages/input |
114
+ | `response` | `string` | No | Response text |
115
+ | `tools` | `string[]` | No | Tool names available |
116
+ | `toolCalls` | `{ id, name, arguments }[]` | No | Tool calls made |
117
+ | `tokens` | `{ prompt?, completion?, total? }` | No | Token usage |
118
+ | `latency` | `number` | No | Duration in milliseconds |
119
+ | `timestamp` | `string` | No | ISO 8601 timestamp (auto-generated if omitted) |
120
+ | `status` | `string` | No | `"success"` (default) or `"error"` |
121
+ | `error` | `string` | No | Error message |
122
+ | `cost` | `number` | No | Cost in USD |
123
+ | `opts` | `Record<string, any>` | No | Custom metadata |
124
+
93
125
  ### `outcome(target, name, opts?)`
94
126
 
95
127
  Record an outcome on any tracked target.
@@ -110,11 +142,12 @@ const r2 = run(a, 'Code Review');
110
142
 
111
143
  ### `ref(target)`
112
144
 
113
- Resolve any target (run, group, or LLM response) to its string ID.
145
+ Resolve any target (run, group, outcome, act, or LLM response) to its string ID. Also accepts raw ID strings (e.g. `"wm_run_..."` loaded from a database) and registers them locally.
114
146
 
115
147
  ```js
116
148
  ref(r) // 'wm_run_01jkx3ndek0gh4r5tmqp9a3bcv'
117
149
  ref(response) // 'wm_call_01jkx3ndef8mn2q7kpvhc4e9ws'
150
+ ref('wm_run_01jkx3ndek0gh4r5tmqp9a3bcv') // adopts and returns the ID
118
151
  ```
119
152
 
120
153
  ### `flush()`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warpmetrics/warp",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Measure your agents, not your LLM calls.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -166,7 +166,7 @@ export function logRun(data) {
166
166
  label: data.label,
167
167
  opts: data.opts || null,
168
168
  refId: data.refId || null,
169
- timestamp: new Date().toISOString(),
169
+ startedAt: data.startedAt || new Date().toISOString(),
170
170
  });
171
171
  }
172
172
 
@@ -175,7 +175,7 @@ export function logGroup(data) {
175
175
  id: data.id,
176
176
  label: data.label,
177
177
  opts: data.opts || null,
178
- timestamp: new Date().toISOString(),
178
+ startedAt: data.startedAt || new Date().toISOString(),
179
179
  });
180
180
  }
181
181
 
package/src/core/warp.js CHANGED
@@ -32,13 +32,14 @@ function createInterceptor(originalFn, context, provider) {
32
32
 
33
33
  try {
34
34
  const result = await originalFn.apply(context, args);
35
- const latency = Date.now() - start;
35
+ const duration = Date.now() - start;
36
36
 
37
37
  if (stream) {
38
38
  return wrapStream(result, { callId, provider, model, messages, tools, start });
39
39
  }
40
40
 
41
41
  const ext = provider.extract(result);
42
+ const endedAt = new Date().toISOString();
42
43
 
43
44
  responseRegistry.set(result, {
44
45
  id: callId,
@@ -47,8 +48,9 @@ function createInterceptor(originalFn, context, provider) {
47
48
  response: ext.response,
48
49
  tools: tools ? tools.map(t => t.function?.name || t.name).filter(Boolean) : null,
49
50
  toolCalls: ext.toolCalls,
50
- tokens: ext.tokens, latency,
51
- timestamp: new Date().toISOString(),
51
+ tokens: ext.tokens, duration,
52
+ startedAt: new Date(start).toISOString(),
53
+ endedAt,
52
54
  status: 'success',
53
55
  },
54
56
  });
@@ -56,13 +58,15 @@ function createInterceptor(originalFn, context, provider) {
56
58
  return result;
57
59
  } catch (error) {
58
60
  const errorResult = { _warpError: true };
61
+ const duration = Date.now() - start;
59
62
  responseRegistry.set(errorResult, {
60
63
  id: callId,
61
64
  data: {
62
65
  id: callId, provider: provider.name, model, messages,
63
66
  error: error.message,
64
- latency: Date.now() - start,
65
- timestamp: new Date().toISOString(),
67
+ duration,
68
+ startedAt: new Date(start).toISOString(),
69
+ endedAt: new Date().toISOString(),
66
70
  status: 'error',
67
71
  },
68
72
  });
@@ -95,6 +99,7 @@ function wrapStream(stream, ctx) {
95
99
  ? ctx.provider.normalizeUsage(usage)
96
100
  : { prompt: 0, completion: 0, total: 0 };
97
101
 
102
+ const duration = Date.now() - ctx.start;
98
103
  responseRegistry.set(wrapped, {
99
104
  id: ctx.callId,
100
105
  data: {
@@ -102,8 +107,9 @@ function wrapStream(stream, ctx) {
102
107
  response: content,
103
108
  tools: ctx.tools ? ctx.tools.map(t => t.function?.name || t.name).filter(Boolean) : null,
104
109
  tokens,
105
- latency: Date.now() - ctx.start,
106
- timestamp: new Date().toISOString(),
110
+ duration,
111
+ startedAt: new Date(ctx.start).toISOString(),
112
+ endedAt: new Date().toISOString(),
107
113
  status: 'success',
108
114
  },
109
115
  });
package/src/index.d.ts CHANGED
@@ -47,6 +47,45 @@ export function group(target: Run | Group | string, label: string, opts?: Record
47
47
  /** Track an LLM call by linking a response to a run or group. */
48
48
  export function call(target: Run | Group | string, response: object, opts?: Record<string, any>): void;
49
49
 
50
+ export interface TraceData {
51
+ /** Provider name (e.g. "google", "cohere"). */
52
+ provider: string;
53
+ /** Model identifier. */
54
+ model: string;
55
+ /** Request messages/input. */
56
+ messages?: any;
57
+ /** Response text. */
58
+ response?: string;
59
+ /** Tool names available. */
60
+ tools?: string[];
61
+ /** Tool calls made. */
62
+ toolCalls?: { id?: string; name: string; arguments?: string }[];
63
+ /** Token usage. */
64
+ tokens?: { prompt?: number; completion?: number; total?: number };
65
+ /** Duration in milliseconds. */
66
+ duration?: number;
67
+ /** ISO 8601 timestamp of when the call started. */
68
+ startedAt?: string;
69
+ /** ISO 8601 timestamp of when the call ended (auto-generated if omitted). */
70
+ endedAt?: string;
71
+ /** "success" (default) or "error". */
72
+ status?: string;
73
+ /** Error message. */
74
+ error?: string;
75
+ /** Cost in USD. */
76
+ cost?: number;
77
+ /** Custom metadata. */
78
+ opts?: Record<string, any>;
79
+ }
80
+
81
+ export interface Call {
82
+ readonly id: string;
83
+ readonly _type: 'call';
84
+ }
85
+
86
+ /** Manually record an LLM call for providers not wrapped by warp(). */
87
+ export function trace(target: Run | Group | string, data: TraceData): Call | undefined;
88
+
50
89
  /** Record an outcome on any tracked target. Returns an Outcome handle for use with act(). */
51
90
  export function outcome(
52
91
  target: Run | Group | object | string,
@@ -14,6 +14,7 @@ import { logGroup, logLink, getConfig } from '../core/transport.js';
14
14
  * @returns {{ readonly id: string, readonly _type: 'group' }}
15
15
  */
16
16
  export function group(target, label, opts) {
17
+ const startedAt = new Date().toISOString();
17
18
  const targetId = getRef(target);
18
19
  if (!targetId) {
19
20
  if (getConfig().debug) console.warn('[warpmetrics] group() — target not recognised.');
@@ -29,6 +30,7 @@ export function group(target, label, opts) {
29
30
  label,
30
31
  opts: opts || null,
31
32
  parentId: targetId,
33
+ startedAt,
32
34
  groups: [],
33
35
  calls: [],
34
36
  };
package/src/trace/run.js CHANGED
@@ -14,6 +14,7 @@ import { logRun, getConfig } from '../core/transport.js';
14
14
  * @returns {{ readonly id: string, readonly _type: 'run' }}
15
15
  */
16
16
  export function run(labelOrRef, labelOrOpts, maybeOpts) {
17
+ const startedAt = new Date().toISOString();
17
18
  let refId = null;
18
19
  let label, opts;
19
20
 
@@ -37,6 +38,7 @@ export function run(labelOrRef, labelOrOpts, maybeOpts) {
37
38
  label,
38
39
  opts,
39
40
  refId,
41
+ startedAt,
40
42
  groups: [],
41
43
  calls: [],
42
44
  };
@@ -26,6 +26,10 @@ export function trace(target, data) {
26
26
 
27
27
  const id = generateId('call');
28
28
 
29
+ const endedAt = data.endedAt || data.timestamp || new Date().toISOString();
30
+ const duration = data.duration ?? data.latency ?? null;
31
+ const startedAt = data.startedAt || (duration != null ? new Date(new Date(endedAt).getTime() - duration).toISOString() : endedAt);
32
+
29
33
  const event = {
30
34
  id,
31
35
  provider: data.provider,
@@ -35,8 +39,9 @@ export function trace(target, data) {
35
39
  tools: data.tools || null,
36
40
  toolCalls: data.toolCalls || null,
37
41
  tokens: data.tokens || null,
38
- latency: data.latency ?? null,
39
- timestamp: data.timestamp || new Date().toISOString(),
42
+ duration,
43
+ startedAt,
44
+ endedAt,
40
45
  status: data.status || 'success',
41
46
  };
42
47