@outputai/cli 0.12.0 → 0.12.1-next.1243f78.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/api/generated/api.d.ts +105 -99
- package/dist/api/generated/api.js +118 -6
- package/dist/assets/docker/docker-compose-dev.yml +1 -1
- package/dist/generated/framework_version.json +1 -1
- package/dist/services/cost_calculator.js +13 -4
- package/dist/services/cost_calculator.spec.js +68 -4
- package/dist/types/cost.d.ts +3 -0
- package/dist/utils/cost_formatter.js +20 -4
- package/dist/utils/cost_formatter.spec.d.ts +1 -0
- package/dist/utils/cost_formatter.spec.js +60 -0
- package/oclif.manifest.json +1 -1
- package/package.json +6 -6
- package/dist/api/orval_post_process.d.ts +0 -11
- package/dist/api/orval_post_process.js +0 -46
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generated by orval
|
|
2
|
+
* Generated by orval 🍺
|
|
3
3
|
* Do not edit manually.
|
|
4
4
|
* Output.ai API
|
|
5
5
|
* API for managing and executing Output.ai workflows
|
|
@@ -49,6 +49,9 @@ export const WorkflowResultV2ResponseV = {
|
|
|
49
49
|
export const getGetHealthUrl = () => {
|
|
50
50
|
return `/health`;
|
|
51
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* @summary Health check the API
|
|
54
|
+
*/
|
|
52
55
|
export const getHealth = async (options) => {
|
|
53
56
|
return customFetchInstance(getGetHealthUrl(), {
|
|
54
57
|
...options,
|
|
@@ -58,6 +61,9 @@ export const getHealth = async (options) => {
|
|
|
58
61
|
export const getGetReadyUrl = () => {
|
|
59
62
|
return `/ready`;
|
|
60
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* @summary Check if the API is ready to answer requests
|
|
66
|
+
*/
|
|
61
67
|
export const getReady = async (options) => {
|
|
62
68
|
return customFetchInstance(getGetReadyUrl(), {
|
|
63
69
|
...options,
|
|
@@ -67,6 +73,10 @@ export const getReady = async (options) => {
|
|
|
67
73
|
export const getPostWorkflowRunUrl = () => {
|
|
68
74
|
return `/workflow/run`;
|
|
69
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Executes a workflow and waits for it to complete before returning the result
|
|
78
|
+
* @summary Execute a workflow synchronously
|
|
79
|
+
*/
|
|
70
80
|
export const postWorkflowRun = async (postWorkflowRunBody, options) => {
|
|
71
81
|
return customFetchInstance(getPostWorkflowRunUrl(), {
|
|
72
82
|
...options,
|
|
@@ -78,6 +88,9 @@ export const postWorkflowRun = async (postWorkflowRunBody, options) => {
|
|
|
78
88
|
export const getPostWorkflowStartUrl = () => {
|
|
79
89
|
return `/workflow/start`;
|
|
80
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* @summary Start a workflow asynchronously
|
|
93
|
+
*/
|
|
81
94
|
export const postWorkflowStart = async (postWorkflowStartBody, options) => {
|
|
82
95
|
return customFetchInstance(getPostWorkflowStartUrl(), {
|
|
83
96
|
...options,
|
|
@@ -89,6 +102,10 @@ export const postWorkflowStart = async (postWorkflowStartBody, options) => {
|
|
|
89
102
|
export const getGetWorkflowIdStatusUrl = (id) => {
|
|
90
103
|
return `/workflow/${id}/status`;
|
|
91
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* Returns the status of the latest run for the given workflow. To pin a specific run, use `/workflow/{id}/runs/{rid}/status`.
|
|
107
|
+
* @summary Get workflow execution status (latest run)
|
|
108
|
+
*/
|
|
92
109
|
export const getWorkflowIdStatus = async (id, options) => {
|
|
93
110
|
return customFetchInstance(getGetWorkflowIdStatusUrl(id), {
|
|
94
111
|
...options,
|
|
@@ -98,6 +115,9 @@ export const getWorkflowIdStatus = async (id, options) => {
|
|
|
98
115
|
export const getGetWorkflowIdRunsRidStatusUrl = (id, rid) => {
|
|
99
116
|
return `/workflow/${id}/runs/${rid}/status`;
|
|
100
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* @summary Get workflow execution status for a specific run
|
|
120
|
+
*/
|
|
101
121
|
export const getWorkflowIdRunsRidStatus = async (id, rid, options) => {
|
|
102
122
|
return customFetchInstance(getGetWorkflowIdRunsRidStatusUrl(id, rid), {
|
|
103
123
|
...options,
|
|
@@ -107,6 +127,9 @@ export const getWorkflowIdRunsRidStatus = async (id, rid, options) => {
|
|
|
107
127
|
export const getPatchWorkflowIdRunsRidStopUrl = (id, rid) => {
|
|
108
128
|
return `/workflow/${id}/runs/${rid}/stop`;
|
|
109
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* @summary Stop a specific workflow run
|
|
132
|
+
*/
|
|
110
133
|
export const patchWorkflowIdRunsRidStop = async (id, rid, options) => {
|
|
111
134
|
return customFetchInstance(getPatchWorkflowIdRunsRidStopUrl(id, rid), {
|
|
112
135
|
...options,
|
|
@@ -116,6 +139,11 @@ export const patchWorkflowIdRunsRidStop = async (id, rid, options) => {
|
|
|
116
139
|
export const getPatchWorkflowIdStopUrl = (id) => {
|
|
117
140
|
return `/workflow/${id}/stop`;
|
|
118
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Stops the latest run of the given workflow. The returned `runId` reflects the run at describe-time and may differ from the cancelled run if a new execution started concurrently. Deprecated; use `PATCH /workflow/{id}/runs/{rid}/stop` to pin a specific run. Scheduled for removal after 2026-07-16.
|
|
144
|
+
* @deprecated
|
|
145
|
+
* @summary [Deprecated] Stop the latest workflow run
|
|
146
|
+
*/
|
|
119
147
|
export const patchWorkflowIdStop = async (id, options) => {
|
|
120
148
|
return customFetchInstance(getPatchWorkflowIdStopUrl(id), {
|
|
121
149
|
...options,
|
|
@@ -125,6 +153,10 @@ export const patchWorkflowIdStop = async (id, options) => {
|
|
|
125
153
|
export const getPostWorkflowIdRunsRidTerminateUrl = (id, rid) => {
|
|
126
154
|
return `/workflow/${id}/runs/${rid}/terminate`;
|
|
127
155
|
};
|
|
156
|
+
/**
|
|
157
|
+
* Force terminates a workflow run. Unlike stop/cancel, terminate immediately stops the run without allowing cleanup.
|
|
158
|
+
* @summary Terminate a specific workflow run (force stop)
|
|
159
|
+
*/
|
|
128
160
|
export const postWorkflowIdRunsRidTerminate = async (id, rid, postWorkflowIdRunsRidTerminateBody, options) => {
|
|
129
161
|
return customFetchInstance(getPostWorkflowIdRunsRidTerminateUrl(id, rid), {
|
|
130
162
|
...options,
|
|
@@ -136,6 +168,11 @@ export const postWorkflowIdRunsRidTerminate = async (id, rid, postWorkflowIdRuns
|
|
|
136
168
|
export const getPostWorkflowIdTerminateUrl = (id) => {
|
|
137
169
|
return `/workflow/${id}/terminate`;
|
|
138
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* Force terminates the latest run. Deprecated; use `POST /workflow/{id}/runs/{rid}/terminate` to target a specific run. Scheduled for removal after 2026-07-16.
|
|
173
|
+
* @deprecated
|
|
174
|
+
* @summary [Deprecated] Terminate the latest workflow run
|
|
175
|
+
*/
|
|
139
176
|
export const postWorkflowIdTerminate = async (id, postWorkflowIdTerminateBody, options) => {
|
|
140
177
|
return customFetchInstance(getPostWorkflowIdTerminateUrl(id), {
|
|
141
178
|
...options,
|
|
@@ -147,6 +184,10 @@ export const postWorkflowIdTerminate = async (id, postWorkflowIdTerminateBody, o
|
|
|
147
184
|
export const getPostWorkflowIdRunsRidResetUrl = (id, rid) => {
|
|
148
185
|
return `/workflow/${id}/runs/${rid}/reset`;
|
|
149
186
|
};
|
|
187
|
+
/**
|
|
188
|
+
* Resets a pinned workflow run to the point after a completed step, creating a new run that replays from that point. The current execution is terminated.
|
|
189
|
+
* @summary Reset a specific workflow run to re-run from after a completed step
|
|
190
|
+
*/
|
|
150
191
|
export const postWorkflowIdRunsRidReset = async (id, rid, resetWorkflowRequest, options) => {
|
|
151
192
|
return customFetchInstance(getPostWorkflowIdRunsRidResetUrl(id, rid), {
|
|
152
193
|
...options,
|
|
@@ -158,6 +199,11 @@ export const postWorkflowIdRunsRidReset = async (id, rid, resetWorkflowRequest,
|
|
|
158
199
|
export const getPostWorkflowIdResetUrl = (id) => {
|
|
159
200
|
return `/workflow/${id}/reset`;
|
|
160
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Resets the latest run. Deprecated; use `POST /workflow/{id}/runs/{rid}/reset` to target a specific run. Scheduled for removal after 2026-07-16.
|
|
204
|
+
* @deprecated
|
|
205
|
+
* @summary [Deprecated] Reset the latest workflow run
|
|
206
|
+
*/
|
|
161
207
|
export const postWorkflowIdReset = async (id, resetWorkflowRequest, options) => {
|
|
162
208
|
return customFetchInstance(getPostWorkflowIdResetUrl(id), {
|
|
163
209
|
...options,
|
|
@@ -169,6 +215,10 @@ export const postWorkflowIdReset = async (id, resetWorkflowRequest, options) =>
|
|
|
169
215
|
export const getGetWorkflowIdResultUrl = (id) => {
|
|
170
216
|
return `/workflow/${id}/result`;
|
|
171
217
|
};
|
|
218
|
+
/**
|
|
219
|
+
* Returns the result of the latest run for the given workflow. To pin a specific run, use `/workflow/{id}/runs/{rid}/result`.
|
|
220
|
+
* @summary Return the result of a workflow (latest run)
|
|
221
|
+
*/
|
|
172
222
|
export const getWorkflowIdResult = async (id, options) => {
|
|
173
223
|
return customFetchInstance(getGetWorkflowIdResultUrl(id), {
|
|
174
224
|
...options,
|
|
@@ -178,6 +228,9 @@ export const getWorkflowIdResult = async (id, options) => {
|
|
|
178
228
|
export const getGetWorkflowIdRunsRidResultUrl = (id, rid) => {
|
|
179
229
|
return `/workflow/${id}/runs/${rid}/result`;
|
|
180
230
|
};
|
|
231
|
+
/**
|
|
232
|
+
* @summary Return the result of a specific workflow run
|
|
233
|
+
*/
|
|
181
234
|
export const getWorkflowIdRunsRidResult = async (id, rid, options) => {
|
|
182
235
|
return customFetchInstance(getGetWorkflowIdRunsRidResultUrl(id, rid), {
|
|
183
236
|
...options,
|
|
@@ -187,6 +240,10 @@ export const getWorkflowIdRunsRidResult = async (id, rid, options) => {
|
|
|
187
240
|
export const getGetWorkflowIdInputUrl = (id) => {
|
|
188
241
|
return `/workflow/${id}/input`;
|
|
189
242
|
};
|
|
243
|
+
/**
|
|
244
|
+
* Returns the original input passed to the latest run of the given workflow. Works for workflows in any state, including running. To pin a specific run, use `/workflow/{id}/runs/{rid}/input`.
|
|
245
|
+
* @summary Return the original input of a workflow (latest run)
|
|
246
|
+
*/
|
|
190
247
|
export const getWorkflowIdInput = async (id, options) => {
|
|
191
248
|
return customFetchInstance(getGetWorkflowIdInputUrl(id), {
|
|
192
249
|
...options,
|
|
@@ -196,6 +253,9 @@ export const getWorkflowIdInput = async (id, options) => {
|
|
|
196
253
|
export const getGetWorkflowIdRunsRidInputUrl = (id, rid) => {
|
|
197
254
|
return `/workflow/${id}/runs/${rid}/input`;
|
|
198
255
|
};
|
|
256
|
+
/**
|
|
257
|
+
* @summary Return the original input of a specific workflow run
|
|
258
|
+
*/
|
|
199
259
|
export const getWorkflowIdRunsRidInput = async (id, rid, options) => {
|
|
200
260
|
return customFetchInstance(getGetWorkflowIdRunsRidInputUrl(id, rid), {
|
|
201
261
|
...options,
|
|
@@ -205,6 +265,10 @@ export const getWorkflowIdRunsRidInput = async (id, rid, options) => {
|
|
|
205
265
|
export const getGetWorkflowIdTraceLogUrl = (id) => {
|
|
206
266
|
return `/workflow/${id}/trace-log`;
|
|
207
267
|
};
|
|
268
|
+
/**
|
|
269
|
+
* Returns trace data for the latest run of the given workflow. If trace is stored remotely (S3), fetches and returns the data inline. If trace is local only, returns the local path. To pin a specific run, use `/workflow/{id}/runs/{rid}/trace-log`.
|
|
270
|
+
* @summary Get workflow trace log data (latest run)
|
|
271
|
+
*/
|
|
208
272
|
export const getWorkflowIdTraceLog = async (id, options) => {
|
|
209
273
|
return customFetchInstance(getGetWorkflowIdTraceLogUrl(id), {
|
|
210
274
|
...options,
|
|
@@ -214,6 +278,10 @@ export const getWorkflowIdTraceLog = async (id, options) => {
|
|
|
214
278
|
export const getGetWorkflowIdRunsRidTraceLogUrl = (id, rid) => {
|
|
215
279
|
return `/workflow/${id}/runs/${rid}/trace-log`;
|
|
216
280
|
};
|
|
281
|
+
/**
|
|
282
|
+
* Returns trace data for a pinned workflow run. If trace is stored remotely (S3), fetches and returns the data inline. If trace is local only, returns the local path.
|
|
283
|
+
* @summary Get workflow trace log data for a specific run
|
|
284
|
+
*/
|
|
217
285
|
export const getWorkflowIdRunsRidTraceLog = async (id, rid, options) => {
|
|
218
286
|
return customFetchInstance(getGetWorkflowIdRunsRidTraceLogUrl(id, rid), {
|
|
219
287
|
...options,
|
|
@@ -224,12 +292,16 @@ export const getGetWorkflowIdHistoryUrl = (id, params) => {
|
|
|
224
292
|
const normalizedParams = new URLSearchParams();
|
|
225
293
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
226
294
|
if (value !== undefined) {
|
|
227
|
-
normalizedParams.append(key, value === null ? 'null' : value
|
|
295
|
+
normalizedParams.append(key, value === null ? 'null' : String(value));
|
|
228
296
|
}
|
|
229
297
|
});
|
|
230
298
|
const stringifiedParams = normalizedParams.toString();
|
|
231
299
|
return stringifiedParams.length > 0 ? `/workflow/${id}/history?${stringifiedParams}` : `/workflow/${id}/history`;
|
|
232
300
|
};
|
|
301
|
+
/**
|
|
302
|
+
* Returns decoded Temporal history events with optional payload inclusion. First page includes workflow metadata; subsequent pages return events only.
|
|
303
|
+
* @summary Get paginated workflow execution history
|
|
304
|
+
*/
|
|
233
305
|
export const getWorkflowIdHistory = async (id, params, options) => {
|
|
234
306
|
return customFetchInstance(getGetWorkflowIdHistoryUrl(id, params), {
|
|
235
307
|
...options,
|
|
@@ -240,12 +312,15 @@ export const getGetWorkflowIdRunsRidHistoryUrl = (id, rid, params) => {
|
|
|
240
312
|
const normalizedParams = new URLSearchParams();
|
|
241
313
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
242
314
|
if (value !== undefined) {
|
|
243
|
-
normalizedParams.append(key, value === null ? 'null' : value
|
|
315
|
+
normalizedParams.append(key, value === null ? 'null' : String(value));
|
|
244
316
|
}
|
|
245
317
|
});
|
|
246
318
|
const stringifiedParams = normalizedParams.toString();
|
|
247
319
|
return stringifiedParams.length > 0 ? `/workflow/${id}/runs/${rid}/history?${stringifiedParams}` : `/workflow/${id}/runs/${rid}/history`;
|
|
248
320
|
};
|
|
321
|
+
/**
|
|
322
|
+
* @summary Get paginated workflow execution history for a specific run
|
|
323
|
+
*/
|
|
249
324
|
export const getWorkflowIdRunsRidHistory = async (id, rid, params, options) => {
|
|
250
325
|
return customFetchInstance(getGetWorkflowIdRunsRidHistoryUrl(id, rid, params), {
|
|
251
326
|
...options,
|
|
@@ -256,12 +331,16 @@ export const getGetWorkflowIdHistoryStreamUrl = (id, params) => {
|
|
|
256
331
|
const normalizedParams = new URLSearchParams();
|
|
257
332
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
258
333
|
if (value !== undefined) {
|
|
259
|
-
normalizedParams.append(key, value === null ? 'null' : value
|
|
334
|
+
normalizedParams.append(key, value === null ? 'null' : String(value));
|
|
260
335
|
}
|
|
261
336
|
});
|
|
262
337
|
const stringifiedParams = normalizedParams.toString();
|
|
263
338
|
return stringifiedParams.length > 0 ? `/workflow/${id}/history/stream?${stringifiedParams}` : `/workflow/${id}/history/stream`;
|
|
264
339
|
};
|
|
340
|
+
/**
|
|
341
|
+
* Opens a persistent SSE connection that delivers Temporal workflow history events in real time. Emits named events: `workflow` (metadata, once), `history` (event batches), `done` (terminal state), `server_error` (post-flush errors). The `done` event carries `{ reason, newRunId? }` where `reason` is the terminal Temporal event type (`WORKFLOW_EXECUTION_COMPLETED`, `WORKFLOW_EXECUTION_FAILED`, `WORKFLOW_EXECUTION_TIMED_OUT`, `WORKFLOW_EXECUTION_CANCELED`, `WORKFLOW_EXECUTION_TERMINATED`, `WORKFLOW_EXECUTION_CONTINUED_AS_NEW`) and `newRunId` is present only when the terminal event chains a follow-on run. `server_error` carries `{ error, message, workflowId, runId }`. Errors before the stream opens are returned as JSON HTTP responses (400/404); once open, failures arrive as a `server_error` event. Supports reconnect via `Last-Event-ID` header or `lastEventId` query param.
|
|
342
|
+
* @summary Stream workflow history events via Server-Sent Events
|
|
343
|
+
*/
|
|
265
344
|
export const getWorkflowIdHistoryStream = async (id, params, options) => {
|
|
266
345
|
return customFetchInstance(getGetWorkflowIdHistoryStreamUrl(id, params), {
|
|
267
346
|
...options,
|
|
@@ -272,12 +351,16 @@ export const getGetWorkflowIdRunsRidHistoryStreamUrl = (id, rid, params) => {
|
|
|
272
351
|
const normalizedParams = new URLSearchParams();
|
|
273
352
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
274
353
|
if (value !== undefined) {
|
|
275
|
-
normalizedParams.append(key, value === null ? 'null' : value
|
|
354
|
+
normalizedParams.append(key, value === null ? 'null' : String(value));
|
|
276
355
|
}
|
|
277
356
|
});
|
|
278
357
|
const stringifiedParams = normalizedParams.toString();
|
|
279
358
|
return stringifiedParams.length > 0 ? `/workflow/${id}/runs/${rid}/history/stream?${stringifiedParams}` : `/workflow/${id}/runs/${rid}/history/stream`;
|
|
280
359
|
};
|
|
360
|
+
/**
|
|
361
|
+
* Same as /workflow/{id}/history/stream but targets a specific run ID.
|
|
362
|
+
* @summary Stream pinned-run workflow history events via Server-Sent Events
|
|
363
|
+
*/
|
|
281
364
|
export const getWorkflowIdRunsRidHistoryStream = async (id, rid, params, options) => {
|
|
282
365
|
return customFetchInstance(getGetWorkflowIdRunsRidHistoryStreamUrl(id, rid, params), {
|
|
283
366
|
...options,
|
|
@@ -287,6 +370,9 @@ export const getWorkflowIdRunsRidHistoryStream = async (id, rid, params, options
|
|
|
287
370
|
export const getGetWorkflowCatalogIdUrl = (id) => {
|
|
288
371
|
return `/workflow/catalog/${id}`;
|
|
289
372
|
};
|
|
373
|
+
/**
|
|
374
|
+
* @summary Get a specific workflow catalog by ID
|
|
375
|
+
*/
|
|
290
376
|
export const getWorkflowCatalogId = async (id, options) => {
|
|
291
377
|
return customFetchInstance(getGetWorkflowCatalogIdUrl(id), {
|
|
292
378
|
...options,
|
|
@@ -296,6 +382,9 @@ export const getWorkflowCatalogId = async (id, options) => {
|
|
|
296
382
|
export const getGetWorkflowCatalogUrl = () => {
|
|
297
383
|
return `/workflow/catalog`;
|
|
298
384
|
};
|
|
385
|
+
/**
|
|
386
|
+
* @summary Get the default workflow catalog
|
|
387
|
+
*/
|
|
299
388
|
export const getWorkflowCatalog = async (options) => {
|
|
300
389
|
return customFetchInstance(getGetWorkflowCatalogUrl(), {
|
|
301
390
|
...options,
|
|
@@ -306,12 +395,16 @@ export const getGetWorkflowRunsUrl = (params) => {
|
|
|
306
395
|
const normalizedParams = new URLSearchParams();
|
|
307
396
|
Object.entries(params || {}).forEach(([key, value]) => {
|
|
308
397
|
if (value !== undefined) {
|
|
309
|
-
normalizedParams.append(key, value === null ? 'null' : value
|
|
398
|
+
normalizedParams.append(key, value === null ? 'null' : String(value));
|
|
310
399
|
}
|
|
311
400
|
});
|
|
312
401
|
const stringifiedParams = normalizedParams.toString();
|
|
313
402
|
return stringifiedParams.length > 0 ? `/workflow/runs?${stringifiedParams}` : `/workflow/runs`;
|
|
314
403
|
};
|
|
404
|
+
/**
|
|
405
|
+
* Returns a list of workflow runs with optional filtering by workflow type
|
|
406
|
+
* @summary List workflow runs
|
|
407
|
+
*/
|
|
315
408
|
export const getWorkflowRuns = async (params, options) => {
|
|
316
409
|
return customFetchInstance(getGetWorkflowRunsUrl(params), {
|
|
317
410
|
...options,
|
|
@@ -321,6 +414,10 @@ export const getWorkflowRuns = async (params, options) => {
|
|
|
321
414
|
export const getPostWorkflowIdFeedbackUrl = (id) => {
|
|
322
415
|
return `/workflow/${id}/feedback`;
|
|
323
416
|
};
|
|
417
|
+
/**
|
|
418
|
+
* Always targets the latest run; `runId` cannot be pinned for Temporal signal operations.
|
|
419
|
+
* @summary Send feedback to a workflow
|
|
420
|
+
*/
|
|
324
421
|
export const postWorkflowIdFeedback = async (id, postWorkflowIdFeedbackBody, options) => {
|
|
325
422
|
return customFetchInstance(getPostWorkflowIdFeedbackUrl(id), {
|
|
326
423
|
...options,
|
|
@@ -332,6 +429,10 @@ export const postWorkflowIdFeedback = async (id, postWorkflowIdFeedbackBody, opt
|
|
|
332
429
|
export const getPostWorkflowIdSignalSignalUrl = (id, signal) => {
|
|
333
430
|
return `/workflow/${id}/signal/${signal}`;
|
|
334
431
|
};
|
|
432
|
+
/**
|
|
433
|
+
* Always targets the latest run; `runId` cannot be pinned for Temporal signal operations.
|
|
434
|
+
* @summary Send a signal to an workflow
|
|
435
|
+
*/
|
|
335
436
|
export const postWorkflowIdSignalSignal = async (id, signal, postWorkflowIdSignalSignalBody, options) => {
|
|
336
437
|
return customFetchInstance(getPostWorkflowIdSignalSignalUrl(id, signal), {
|
|
337
438
|
...options,
|
|
@@ -343,6 +444,10 @@ export const postWorkflowIdSignalSignal = async (id, signal, postWorkflowIdSigna
|
|
|
343
444
|
export const getPostWorkflowIdQueryQueryUrl = (id, query) => {
|
|
344
445
|
return `/workflow/${id}/query/${query}`;
|
|
345
446
|
};
|
|
447
|
+
/**
|
|
448
|
+
* Always targets the latest run; `runId` cannot be pinned for Temporal query operations.
|
|
449
|
+
* @summary Send a query to an workflow
|
|
450
|
+
*/
|
|
346
451
|
export const postWorkflowIdQueryQuery = async (id, query, postWorkflowIdQueryQueryBody, options) => {
|
|
347
452
|
return customFetchInstance(getPostWorkflowIdQueryQueryUrl(id, query), {
|
|
348
453
|
...options,
|
|
@@ -354,6 +459,10 @@ export const postWorkflowIdQueryQuery = async (id, query, postWorkflowIdQueryQue
|
|
|
354
459
|
export const getPostWorkflowIdUpdateUpdateUrl = (id, update) => {
|
|
355
460
|
return `/workflow/${id}/update/${update}`;
|
|
356
461
|
};
|
|
462
|
+
/**
|
|
463
|
+
* Always targets the latest run; `runId` cannot be pinned for Temporal update operations.
|
|
464
|
+
* @summary Execute an update on an workflow
|
|
465
|
+
*/
|
|
357
466
|
export const postWorkflowIdUpdateUpdate = async (id, update, postWorkflowIdUpdateUpdateBody, options) => {
|
|
358
467
|
return customFetchInstance(getPostWorkflowIdUpdateUpdateUrl(id, update), {
|
|
359
468
|
...options,
|
|
@@ -366,6 +475,9 @@ export const postWorkflowIdUpdateUpdate = async (id, update, postWorkflowIdUpdat
|
|
|
366
475
|
export const getPostHeartbeatUrl = () => {
|
|
367
476
|
return `/heartbeat`;
|
|
368
477
|
};
|
|
478
|
+
/**
|
|
479
|
+
* @summary A dummy post endpoint for test only
|
|
480
|
+
*/
|
|
369
481
|
export const postHeartbeat = async (options) => {
|
|
370
482
|
return customFetchInstance(getPostHeartbeatUrl(), {
|
|
371
483
|
...options,
|
|
@@ -84,7 +84,8 @@ function parseLegacyLLMUsageEvent(node, stepName, event) {
|
|
|
84
84
|
model: event.modelId || 'unknown',
|
|
85
85
|
usage: eventTokenUsage(event.usage ?? []),
|
|
86
86
|
originalCost: event.total,
|
|
87
|
-
lines: event.usage ?? []
|
|
87
|
+
lines: event.usage ?? [],
|
|
88
|
+
incomplete: false
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
91
|
function parseNormalizedLLMUsage(node, stepName, event) {
|
|
@@ -100,7 +101,11 @@ function parseNormalizedLLMUsage(node, stepName, event) {
|
|
|
100
101
|
model: event.modelId || 'unknown',
|
|
101
102
|
usage: normalizedItemTokenUsage(event.items),
|
|
102
103
|
originalCost: 0,
|
|
103
|
-
lines
|
|
104
|
+
lines,
|
|
105
|
+
// This path runs whenever cost computation produced no llm:generation:cost
|
|
106
|
+
// attribute at all (e.g. pricing config missing, not just unrated grounding),
|
|
107
|
+
// so every line here is unpriced by construction, not only grounding calls.
|
|
108
|
+
incomplete: true
|
|
104
109
|
};
|
|
105
110
|
}
|
|
106
111
|
function parseLLMCostEvent(node, stepName, event) {
|
|
@@ -116,7 +121,10 @@ function parseLLMCostEvent(node, stepName, event) {
|
|
|
116
121
|
model: event.modelId || 'unknown',
|
|
117
122
|
usage: normalizedItemTokenUsage(event.items),
|
|
118
123
|
originalCost: event.total ?? 0,
|
|
119
|
-
lines
|
|
124
|
+
lines,
|
|
125
|
+
// An INCOMPLETE event carries a real but unpriced charge (e.g. unrated
|
|
126
|
+
// grounding). Flag it so the report can mark the understated total.
|
|
127
|
+
incomplete: event.status === 'incomplete'
|
|
120
128
|
};
|
|
121
129
|
}
|
|
122
130
|
export function extractValue(obj, path) {
|
|
@@ -461,7 +469,8 @@ function aggregateLLMCosts(llmCalls, config) {
|
|
|
461
469
|
cached: call.usage.cachedInputTokens ?? 0,
|
|
462
470
|
reasoning: call.usage.reasoningTokens ?? 0,
|
|
463
471
|
originalCost,
|
|
464
|
-
adjustedCost
|
|
472
|
+
adjustedCost,
|
|
473
|
+
incomplete: call.incomplete
|
|
465
474
|
});
|
|
466
475
|
totals.inputTokens += call.usage.inputTokens ?? 0;
|
|
467
476
|
totals.outputTokens += call.usage.outputTokens ?? 0;
|
|
@@ -37,10 +37,11 @@ function llmEventNode(id, model, lines) {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
function llmCostNode(id, model, items) {
|
|
40
|
+
function llmCostNode(id, model, items, status = 'precise') {
|
|
41
41
|
const totalOf = (group) => items.filter(item => item.group === group).reduce((sum, item) => sum + (item.total ?? 0), 0);
|
|
42
42
|
const input = totalOf('input');
|
|
43
43
|
const output = totalOf('output');
|
|
44
|
+
const request = totalOf('request');
|
|
44
45
|
return {
|
|
45
46
|
id,
|
|
46
47
|
kind: 'llm',
|
|
@@ -52,8 +53,9 @@ function llmCostNode(id, model, items) {
|
|
|
52
53
|
modelId: model,
|
|
53
54
|
input,
|
|
54
55
|
output,
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
request,
|
|
57
|
+
total: input + output + request,
|
|
58
|
+
status,
|
|
57
59
|
items
|
|
58
60
|
}
|
|
59
61
|
}
|
|
@@ -280,6 +282,66 @@ describe('findLLMCalls', () => {
|
|
|
280
282
|
]);
|
|
281
283
|
expect(calls[0].originalCost).toBeCloseTo(0.001965, 8);
|
|
282
284
|
});
|
|
285
|
+
it('reads a priced grounding request item without counting it as tokens', () => {
|
|
286
|
+
const items = [
|
|
287
|
+
{ group: 'input', label: 'no_cache', amount: 1032, ppm: 0.3, total: 0.0003096, status: 'ok' },
|
|
288
|
+
{ group: 'output', label: 'text', amount: 793, ppm: 2.5, total: 0.0019825, status: 'ok' },
|
|
289
|
+
{ group: 'request', label: 'grounding_prompt', amount: 1, ppm: 35_000, total: 0.035, status: 'ok' }
|
|
290
|
+
];
|
|
291
|
+
const calls = findLLMCalls({
|
|
292
|
+
kind: 'workflow',
|
|
293
|
+
children: [llmCostNode('grounded', 'gemini-2.5-flash', items)]
|
|
294
|
+
});
|
|
295
|
+
expect(calls[0].incomplete).toBe(false);
|
|
296
|
+
expect(calls[0].originalCost).toBeCloseTo(0.0372921, 8);
|
|
297
|
+
// Grounding bills per request, so it must not inflate token usage.
|
|
298
|
+
expect(calls[0].usage).toEqual({
|
|
299
|
+
inputTokens: 1032,
|
|
300
|
+
cachedInputTokens: 0,
|
|
301
|
+
outputTokens: 793,
|
|
302
|
+
reasoningTokens: 0
|
|
303
|
+
});
|
|
304
|
+
expect(calls[0].lines.find(l => l.type === 'request_grounding_prompt')).toEqual({
|
|
305
|
+
type: 'request_grounding_prompt',
|
|
306
|
+
ppm: 35_000,
|
|
307
|
+
amount: 1,
|
|
308
|
+
total: 0.035
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
it('preserves a priced grounding charge as-charged when re-pricing at costs.yml rates', () => {
|
|
312
|
+
const items = [
|
|
313
|
+
{ group: 'input', label: 'no_cache', amount: 1032, ppm: 0.3, total: 0.0003096, status: 'ok' },
|
|
314
|
+
{ group: 'output', label: 'text', amount: 793, ppm: 2.5, total: 0.0019825, status: 'ok' },
|
|
315
|
+
{ group: 'request', label: 'grounding_prompt', amount: 1, ppm: 35_000, total: 0.035, status: 'ok' }
|
|
316
|
+
];
|
|
317
|
+
const config = {
|
|
318
|
+
models: { 'gemini-2.5-flash': { provider: 'google-vertex', input: 0.3, output: 2.5 } },
|
|
319
|
+
services: {}
|
|
320
|
+
};
|
|
321
|
+
const report = calculateCost({ kind: 'workflow', name: 'w', children: [llmCostNode('grounded', 'gemini-2.5-flash', items)] }, config);
|
|
322
|
+
// grounding has no costs.yml line rate, so it degrades to its as-charged total, not $0.
|
|
323
|
+
expect(report.llmAdjustedCost).toBeCloseTo(0.0372921, 8);
|
|
324
|
+
});
|
|
325
|
+
it('flags an unrated grounding charge as incomplete instead of a silent $0', () => {
|
|
326
|
+
const items = [
|
|
327
|
+
{ group: 'input', label: 'no_cache', amount: 1000, ppm: 1, total: 0.001, status: 'ok' },
|
|
328
|
+
{ group: 'output', label: null, amount: 500, ppm: 5, total: 0.0025, status: 'ok' },
|
|
329
|
+
{ group: 'request', label: 'grounding', amount: 3, ppm: null, total: null, status: 'missing' }
|
|
330
|
+
];
|
|
331
|
+
const calls = findLLMCalls({
|
|
332
|
+
kind: 'workflow',
|
|
333
|
+
children: [llmCostNode('unrated', 'gemini-9-ultra', items, 'incomplete')]
|
|
334
|
+
});
|
|
335
|
+
expect(calls[0].incomplete).toBe(true);
|
|
336
|
+
// The unrated line still shows up (as $0), but the call carries the signal.
|
|
337
|
+
expect(calls[0].lines.find(l => l.type === 'request_grounding')).toEqual({
|
|
338
|
+
type: 'request_grounding',
|
|
339
|
+
ppm: 0,
|
|
340
|
+
amount: 3,
|
|
341
|
+
total: 0
|
|
342
|
+
});
|
|
343
|
+
expect(calls[0].usage).toMatchObject({ inputTokens: 1000, outputTokens: 500 });
|
|
344
|
+
});
|
|
283
345
|
it('reads normalized usage without interpreting token totals as cost', () => {
|
|
284
346
|
const items = [
|
|
285
347
|
{ group: 'input', label: null, amount: 100 },
|
|
@@ -303,7 +365,9 @@ describe('findLLMCalls', () => {
|
|
|
303
365
|
outputTokens: 110,
|
|
304
366
|
reasoningTokens: 70
|
|
305
367
|
},
|
|
306
|
-
originalCost: 0
|
|
368
|
+
originalCost: 0,
|
|
369
|
+
// Usage-only means cost computation produced no priced result at all.
|
|
370
|
+
incomplete: true
|
|
307
371
|
});
|
|
308
372
|
expect(calls[0].lines).toEqual([
|
|
309
373
|
{ type: 'input', ppm: 0, amount: 100, total: 0 },
|
package/dist/types/cost.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export interface LLMCall {
|
|
|
51
51
|
usage: TokenUsage;
|
|
52
52
|
originalCost: number;
|
|
53
53
|
lines: LLMUsageLine[];
|
|
54
|
+
incomplete: boolean;
|
|
54
55
|
}
|
|
55
56
|
export interface HTTPCall {
|
|
56
57
|
stepName: string;
|
|
@@ -109,6 +110,7 @@ export interface LLMCostResult {
|
|
|
109
110
|
reasoning: number;
|
|
110
111
|
originalCost: number;
|
|
111
112
|
adjustedCost: number;
|
|
113
|
+
incomplete: boolean;
|
|
112
114
|
}
|
|
113
115
|
export interface ServiceCostResult {
|
|
114
116
|
step: string;
|
|
@@ -155,6 +157,7 @@ export interface LLMModelSummary {
|
|
|
155
157
|
count: number;
|
|
156
158
|
originalCost: number;
|
|
157
159
|
adjustedCost: number;
|
|
160
|
+
incomplete: boolean;
|
|
158
161
|
}
|
|
159
162
|
export interface HostSummary {
|
|
160
163
|
host: string;
|
|
@@ -25,15 +25,23 @@ function formatCurrency(amount) {
|
|
|
25
25
|
function pluralize(count, singular) {
|
|
26
26
|
return count === 1 ? `1 ${singular}` : `${count} ${singular}s`;
|
|
27
27
|
}
|
|
28
|
+
// Marks a figure whose call had a real but unpriced charge (e.g. unrated
|
|
29
|
+
// grounding), so a $0.00 line reads as "unpriced" rather than a clean zero.
|
|
30
|
+
const INCOMPLETE_MARKER = '*';
|
|
31
|
+
const INCOMPLETE_FOOTNOTE = '* cost incomplete (unpriced charge such as grounding, or missing usage); the total understates the bill';
|
|
32
|
+
function markIncomplete(text, incomplete) {
|
|
33
|
+
return incomplete ? `${text} ${INCOMPLETE_MARKER}` : text;
|
|
34
|
+
}
|
|
28
35
|
export function parseCostData(report) {
|
|
29
36
|
const byModel = {};
|
|
30
37
|
for (const r of report.llmCalls) {
|
|
31
38
|
if (!byModel[r.model]) {
|
|
32
|
-
byModel[r.model] = { count: 0, originalCost: 0, adjustedCost: 0 };
|
|
39
|
+
byModel[r.model] = { count: 0, originalCost: 0, adjustedCost: 0, incomplete: false };
|
|
33
40
|
}
|
|
34
41
|
byModel[r.model].count++;
|
|
35
42
|
byModel[r.model].originalCost += r.originalCost;
|
|
36
43
|
byModel[r.model].adjustedCost += r.adjustedCost;
|
|
44
|
+
byModel[r.model].incomplete ||= r.incomplete;
|
|
37
45
|
}
|
|
38
46
|
const llmModels = Object.entries(byModel)
|
|
39
47
|
.sort((a, b) => b[1].adjustedCost - a[1].adjustedCost)
|
|
@@ -82,12 +90,13 @@ function formatSummary(data) {
|
|
|
82
90
|
style: { head: ['cyan'] },
|
|
83
91
|
colAligns: ['left', 'right', 'right', 'right']
|
|
84
92
|
});
|
|
93
|
+
const anyIncomplete = data.llmModels.some(m => m.incomplete);
|
|
85
94
|
for (const m of data.llmModels) {
|
|
86
95
|
table.push([
|
|
87
96
|
m.model,
|
|
88
97
|
pluralize(m.count, 'call'),
|
|
89
|
-
formatCurrency(m.originalCost),
|
|
90
|
-
formatCurrency(m.adjustedCost)
|
|
98
|
+
markIncomplete(formatCurrency(m.originalCost), m.incomplete),
|
|
99
|
+
markIncomplete(formatCurrency(m.adjustedCost), m.incomplete)
|
|
91
100
|
]);
|
|
92
101
|
}
|
|
93
102
|
table.push([
|
|
@@ -98,6 +107,9 @@ function formatSummary(data) {
|
|
|
98
107
|
]);
|
|
99
108
|
lines.push('LLM Costs:');
|
|
100
109
|
lines.push(table.toString());
|
|
110
|
+
if (anyIncomplete) {
|
|
111
|
+
lines.push(INCOMPLETE_FOOTNOTE);
|
|
112
|
+
}
|
|
101
113
|
lines.push('');
|
|
102
114
|
}
|
|
103
115
|
if (data.hosts.length > 0) {
|
|
@@ -146,6 +158,7 @@ function formatVerbose(data) {
|
|
|
146
158
|
style: { head: ['cyan'] },
|
|
147
159
|
colAligns
|
|
148
160
|
});
|
|
161
|
+
const anyIncomplete = data.llmCalls.some(r => r.incomplete);
|
|
149
162
|
for (const r of data.llmCalls) {
|
|
150
163
|
const row = [
|
|
151
164
|
r.step,
|
|
@@ -159,7 +172,7 @@ function formatVerbose(data) {
|
|
|
159
172
|
if (data.verbose.hasReasoning) {
|
|
160
173
|
row.push(formatNumber(r.reasoning));
|
|
161
174
|
}
|
|
162
|
-
row.push(formatCurrency(r.originalCost), formatCurrency(r.adjustedCost));
|
|
175
|
+
row.push(markIncomplete(formatCurrency(r.originalCost), r.incomplete), markIncomplete(formatCurrency(r.adjustedCost), r.incomplete));
|
|
163
176
|
table.push(row);
|
|
164
177
|
}
|
|
165
178
|
const totalRow = [
|
|
@@ -178,6 +191,9 @@ function formatVerbose(data) {
|
|
|
178
191
|
table.push(totalRow);
|
|
179
192
|
lines.push('LLM Calls:');
|
|
180
193
|
lines.push(table.toString());
|
|
194
|
+
if (anyIncomplete) {
|
|
195
|
+
lines.push(INCOMPLETE_FOOTNOTE);
|
|
196
|
+
}
|
|
181
197
|
lines.push('');
|
|
182
198
|
}
|
|
183
199
|
if (data.httpDetails.length > 0) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|