@inflexa-ai/harness 0.14.2 → 0.15.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/README.md +2 -1
- package/dist/agents/conversation-agent.d.ts +7 -0
- package/dist/agents/conversation-agent.js +5 -3
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +2 -1
- package/dist/billing/noop-usage-recorder.d.ts +7 -0
- package/dist/billing/noop-usage-recorder.js +10 -0
- package/dist/billing/usage-recorder.d.ts +63 -0
- package/dist/billing/usage-recorder.js +15 -0
- package/dist/contracts/chat-events.d.ts +44 -3
- package/dist/contracts/chat-parts.d.ts +38 -2
- package/dist/contracts/chat-parts.js +1 -1
- package/dist/contracts/data-profile.d.ts +26 -0
- package/dist/contracts/data-profile.js +26 -0
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/message.d.ts +21 -1
- package/dist/contracts/part-registry.js +1 -0
- package/dist/contracts/schemas/chat-events.d.ts +48 -2
- package/dist/contracts/schemas/chat-events.js +8 -1
- package/dist/contracts/schemas/chat-parts.d.ts +46 -3
- package/dist/contracts/schemas/chat-parts.js +13 -0
- package/dist/contracts/schemas/index.d.ts +2 -1
- package/dist/contracts/schemas/index.js +2 -1
- package/dist/contracts/schemas/usage.d.ts +11 -0
- package/dist/contracts/schemas/usage.js +18 -0
- package/dist/contracts/usage.d.ts +51 -0
- package/dist/contracts/usage.js +41 -0
- package/dist/execution/artifact-metadata.d.ts +3 -0
- package/dist/execution/artifact-metadata.js +1 -0
- package/dist/execution/dbos-workflow-purger.d.ts +30 -0
- package/dist/execution/dbos-workflow-purger.js +193 -0
- package/dist/execution/post-step-pipeline.d.ts +3 -0
- package/dist/execution/post-step-pipeline.js +2 -0
- package/dist/execution/reconcile-manifest.js +28 -15
- package/dist/execution/report-runner.d.ts +16 -0
- package/dist/execution/report-runner.js +4 -0
- package/dist/execution/run-synthesis.d.ts +3 -0
- package/dist/execution/run-synthesis.js +2 -0
- package/dist/execution/step-summary.d.ts +3 -0
- package/dist/execution/step-summary.js +1 -0
- package/dist/execution/workflow-purger.d.ts +38 -0
- package/dist/execution/workflow-purger.js +24 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +27 -0
- package/dist/lib/metrics.js +2 -1
- package/dist/loop/metrics.d.ts +18 -0
- package/dist/loop/metrics.js +18 -1
- package/dist/loop/run-agent.d.ts +35 -0
- package/dist/loop/run-agent.js +149 -23
- package/dist/loop/run-to-terminal.d.ts +5 -2
- package/dist/loop/run-to-terminal.js +36 -2
- package/dist/loop/tool-detail.d.ts +54 -0
- package/dist/loop/tool-detail.js +105 -0
- package/dist/loop/tool-outcome.d.ts +21 -0
- package/dist/loop/tool-outcome.js +25 -0
- package/dist/loop/types.d.ts +15 -1
- package/dist/memory/content-to-cortex.d.ts +28 -4
- package/dist/memory/content-to-cortex.js +70 -14
- package/dist/memory/thread-history.d.ts +21 -1
- package/dist/memory/thread-history.js +29 -7
- package/dist/memory/thread-store.d.ts +65 -8
- package/dist/memory/thread-store.js +81 -17
- package/dist/provenance/collector.d.ts +5 -3
- package/dist/provenance/collector.js +5 -3
- package/dist/providers/ai-sdk.js +91 -16
- package/dist/providers/streaming-chat.js +5 -0
- package/dist/providers/types.d.ts +23 -0
- package/dist/providers/types.js +1 -0
- package/dist/runtime/assemble.d.ts +25 -8
- package/dist/runtime/assemble.js +7 -4
- package/dist/sandbox/sandbox-step-translate.d.ts +22 -1
- package/dist/sandbox/sandbox-step-translate.js +23 -36
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.js +3 -0
- package/dist/state/init.js +28 -0
- package/dist/state/purge-analysis.d.ts +74 -0
- package/dist/state/purge-analysis.js +205 -0
- package/dist/state/vector-store.d.ts +6 -0
- package/dist/state/vector-store.js +22 -2
- package/dist/tasks/data-profile-activity.d.ts +14 -2
- package/dist/tasks/data-profile-activity.js +1 -1
- package/dist/tasks/data-profile.d.ts +3 -0
- package/dist/tasks/data-profile.js +9 -3
- package/dist/tools/bio/pubmed.js +15 -0
- package/dist/tools/bio/search-gene.js +12 -0
- package/dist/tools/define-tool.d.ts +41 -3
- package/dist/tools/define-tool.js +4 -1
- package/dist/tools/detail-resolver.d.ts +35 -0
- package/dist/tools/detail-resolver.js +39 -0
- package/dist/tools/execute-analysis.js +3 -0
- package/dist/tools/iterate-report.d.ts +3 -0
- package/dist/tools/iterate-report.js +3 -0
- package/dist/tools/memory/update-working-memory.js +8 -0
- package/dist/tools/research/generate-analogy-report.d.ts +3 -0
- package/dist/tools/research/generate-analogy-report.js +6 -0
- package/dist/tools/research/generate-plan.d.ts +3 -0
- package/dist/tools/research/generate-plan.js +6 -0
- package/dist/tools/research/inspect-run.js +4 -0
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +6 -0
- package/dist/tools/workspace/edit-file.js +2 -0
- package/dist/tools/workspace/execute-command.js +25 -0
- package/dist/tools/workspace/grep.js +3 -0
- package/dist/tools/workspace/list-files.js +2 -0
- package/dist/tools/workspace/read-file.js +7 -0
- package/dist/tools/workspace/workspace-search.js +1 -0
- package/dist/tools/workspace/write-file.js +3 -0
- package/dist/workflows/execute-analysis.d.ts +7 -0
- package/dist/workflows/execute-analysis.js +31 -2
- package/dist/workflows/execute-target-assessment.d.ts +3 -0
- package/dist/workflows/sandbox-step.d.ts +23 -0
- package/dist/workflows/sandbox-step.js +33 -2
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +1 -0
- package/package.json +1 -1
package/dist/providers/ai-sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateText, streamText } from "ai";
|
|
1
|
+
import { generateText, streamText, wrapLanguageModel } from "ai";
|
|
2
2
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3
3
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
4
4
|
import { APICallError } from "@ai-sdk/provider";
|
|
@@ -222,6 +222,10 @@ function createRetry(signal, logger) {
|
|
|
222
222
|
* vendor's own snake_case.
|
|
223
223
|
*
|
|
224
224
|
* `inputTokens` is the total billed prefix, cache reads included.
|
|
225
|
+
*
|
|
226
|
+
* `reasoningTokens` rides the neutral `outputTokenDetails` beside the text
|
|
227
|
+
* count and is copied verbatim: providers disagree on whether reasoning tokens
|
|
228
|
+
* are already inside the output total, so the harness reconciles nothing.
|
|
225
229
|
*/
|
|
226
230
|
function toChatUsage(usage) {
|
|
227
231
|
if (usage === undefined)
|
|
@@ -231,22 +235,72 @@ function toChatUsage(usage) {
|
|
|
231
235
|
outputTokens: usage.outputTokens,
|
|
232
236
|
cacheCreationInputTokens: usage.inputTokenDetails?.cacheWriteTokens,
|
|
233
237
|
cacheReadInputTokens: usage.inputTokenDetails?.cacheReadTokens,
|
|
238
|
+
reasoningTokens: usage.outputTokenDetails?.reasoningTokens,
|
|
234
239
|
};
|
|
235
240
|
}
|
|
236
|
-
|
|
241
|
+
/** The id of the model bound into this provider — a bare id string is its own id. */
|
|
242
|
+
function requestedModelIdOf(model) {
|
|
243
|
+
return typeof model === "string" ? model : model.modelId;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Capture the served model id at the raw model boundary, because that is the
|
|
247
|
+
* only place it is still distinguishable from the requested one: `generateText`
|
|
248
|
+
* and `streamText` both coalesce an unreported response model id to the bound
|
|
249
|
+
* model's own id, so reading `result.response.modelId` would silently republish
|
|
250
|
+
* the requested id as an endpoint's claim. A middleware sees the provider's
|
|
251
|
+
* result before that fallback applies, so "reported nothing" stays absent.
|
|
252
|
+
*
|
|
253
|
+
* Built per call, never per provider: one provider instance serves concurrent
|
|
254
|
+
* calls, and a shared mutable slot would hand one call another's answer. A bare
|
|
255
|
+
* model-id string has no wire result to observe, so it captures nothing.
|
|
256
|
+
*/
|
|
257
|
+
function captureServedModelId(model) {
|
|
258
|
+
if (typeof model === "string")
|
|
259
|
+
return { model, servedModelId: () => undefined };
|
|
260
|
+
let served;
|
|
261
|
+
const wrapped = wrapLanguageModel({
|
|
262
|
+
model,
|
|
263
|
+
middleware: {
|
|
264
|
+
wrapGenerate: async ({ doGenerate }) => {
|
|
265
|
+
const result = await doGenerate();
|
|
266
|
+
served = result.response?.modelId;
|
|
267
|
+
return result;
|
|
268
|
+
},
|
|
269
|
+
wrapStream: async ({ doStream }) => {
|
|
270
|
+
const result = await doStream();
|
|
271
|
+
return {
|
|
272
|
+
...result,
|
|
273
|
+
stream: result.stream.pipeThrough(new TransformStream({
|
|
274
|
+
transform(chunk, controller) {
|
|
275
|
+
if (chunk.type === "response-metadata" && chunk.modelId !== undefined)
|
|
276
|
+
served = chunk.modelId;
|
|
277
|
+
controller.enqueue(chunk);
|
|
278
|
+
},
|
|
279
|
+
})),
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
return { model: wrapped, servedModelId: () => served };
|
|
285
|
+
}
|
|
286
|
+
function responseFromMessages(input) {
|
|
287
|
+
const { messages, fallbackText, finishReason, rawFinishReason, requestedModelId, servedModelId } = input;
|
|
288
|
+
const usage = toChatUsage(input.usage);
|
|
237
289
|
const message = [...messages].reverse().find((m) => m.role === "assistant");
|
|
238
290
|
if (message === undefined) {
|
|
239
|
-
return {
|
|
240
|
-
message: { role: "assistant", content: fallbackText },
|
|
241
|
-
finishReason,
|
|
242
|
-
rawFinishReason,
|
|
243
|
-
usage: toChatUsage(usage),
|
|
244
|
-
};
|
|
291
|
+
return { message: { role: "assistant", content: fallbackText }, finishReason, rawFinishReason, usage, requestedModelId, servedModelId };
|
|
245
292
|
}
|
|
246
|
-
return { message, finishReason, rawFinishReason, usage
|
|
293
|
+
return { message, finishReason, rawFinishReason, usage, requestedModelId, servedModelId };
|
|
247
294
|
}
|
|
248
|
-
function responseFromGenerate(result) {
|
|
249
|
-
return responseFromMessages(
|
|
295
|
+
function responseFromGenerate(result, identity) {
|
|
296
|
+
return responseFromMessages({
|
|
297
|
+
messages: result.responseMessages,
|
|
298
|
+
fallbackText: result.text,
|
|
299
|
+
finishReason: result.finishReason,
|
|
300
|
+
rawFinishReason: result.rawFinishReason,
|
|
301
|
+
usage: result.usage,
|
|
302
|
+
...identity,
|
|
303
|
+
});
|
|
250
304
|
}
|
|
251
305
|
/**
|
|
252
306
|
* Drop empty text parts — and messages left with no content at all — before
|
|
@@ -279,17 +333,19 @@ export function createAiSdkProvider(deps) {
|
|
|
279
333
|
const capabilities = { toolCalling: deps.capabilities?.toolCalling ?? true };
|
|
280
334
|
const logger = (deps.logger ?? createNoopLogger()).named("providers.ai-sdk");
|
|
281
335
|
const maxOutputTokens = deps.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
336
|
+
const requestedModelId = requestedModelIdOf(deps.model);
|
|
282
337
|
routeSdkWarningsTo(logger);
|
|
283
338
|
function chat(req, session, signal) {
|
|
284
339
|
const run = async () => {
|
|
285
340
|
const retry = createRetry(signal, logger);
|
|
341
|
+
const capture = captureServedModelId(deps.model);
|
|
286
342
|
try {
|
|
287
343
|
const result = await retry(async () => {
|
|
288
344
|
// Attribution headers are time-limited; resolving them inside the
|
|
289
345
|
// retried closure keeps them fresh across a multi-minute window.
|
|
290
346
|
const headers = await resolveBillingHeaders(deps.resolveBilling, session);
|
|
291
347
|
return generateText({
|
|
292
|
-
model:
|
|
348
|
+
model: capture.model,
|
|
293
349
|
system: req.system,
|
|
294
350
|
messages: sanitizeMessages(req.messages),
|
|
295
351
|
tools: req.tools,
|
|
@@ -304,7 +360,7 @@ export function createAiSdkProvider(deps) {
|
|
|
304
360
|
providerOptions: req.providerOptions,
|
|
305
361
|
});
|
|
306
362
|
});
|
|
307
|
-
return ok(responseFromGenerate(result));
|
|
363
|
+
return ok(responseFromGenerate(result, { requestedModelId, servedModelId: capture.servedModelId() }));
|
|
308
364
|
}
|
|
309
365
|
catch (e) {
|
|
310
366
|
if (isAbortError(e) || signal?.aborted)
|
|
@@ -316,6 +372,7 @@ export function createAiSdkProvider(deps) {
|
|
|
316
372
|
}
|
|
317
373
|
async function* chatStream(req, session, signal) {
|
|
318
374
|
const retry = createRetry(signal, logger);
|
|
375
|
+
const capture = captureServedModelId(deps.model);
|
|
319
376
|
try {
|
|
320
377
|
// Retry covers only stream establishment: streamText defers wire errors
|
|
321
378
|
// to consumption, so a failure is not visible until the stream is read.
|
|
@@ -328,7 +385,7 @@ export function createAiSdkProvider(deps) {
|
|
|
328
385
|
const opened = await retry(async () => {
|
|
329
386
|
const headers = await resolveBillingHeaders(deps.resolveBilling, session);
|
|
330
387
|
const result = streamText({
|
|
331
|
-
model:
|
|
388
|
+
model: capture.model,
|
|
332
389
|
system: req.system,
|
|
333
390
|
messages: sanitizeMessages(req.messages),
|
|
334
391
|
tools: req.tools,
|
|
@@ -364,7 +421,15 @@ export function createAiSdkProvider(deps) {
|
|
|
364
421
|
return { kind: "streaming", result, iterator, firstDelta: first.value };
|
|
365
422
|
});
|
|
366
423
|
if (opened.kind === "completed") {
|
|
367
|
-
const response = responseFromMessages(
|
|
424
|
+
const response = responseFromMessages({
|
|
425
|
+
messages: opened.messages,
|
|
426
|
+
fallbackText: "",
|
|
427
|
+
finishReason: opened.finishReason,
|
|
428
|
+
rawFinishReason: opened.rawFinishReason,
|
|
429
|
+
usage: opened.usage,
|
|
430
|
+
requestedModelId,
|
|
431
|
+
servedModelId: capture.servedModelId(),
|
|
432
|
+
});
|
|
368
433
|
yield { type: "done", response };
|
|
369
434
|
return;
|
|
370
435
|
}
|
|
@@ -377,7 +442,17 @@ export function createAiSdkProvider(deps) {
|
|
|
377
442
|
text += next.value;
|
|
378
443
|
yield { type: "text-delta", text: next.value };
|
|
379
444
|
}
|
|
380
|
-
const response = responseFromMessages(
|
|
445
|
+
const response = responseFromMessages({
|
|
446
|
+
messages: await result.responseMessages,
|
|
447
|
+
fallbackText: text,
|
|
448
|
+
finishReason: await result.finishReason,
|
|
449
|
+
rawFinishReason: await result.rawFinishReason,
|
|
450
|
+
usage: await result.usage,
|
|
451
|
+
requestedModelId,
|
|
452
|
+
// Read after the drain: the metadata chunk carrying the served id
|
|
453
|
+
// reaches the capture only as the stream is consumed.
|
|
454
|
+
servedModelId: capture.servedModelId(),
|
|
455
|
+
});
|
|
381
456
|
yield { type: "done", response };
|
|
382
457
|
}
|
|
383
458
|
catch (e) {
|
|
@@ -67,6 +67,11 @@ export function createStreamingChat(provider, onText) {
|
|
|
67
67
|
// response is authoritative, so return it rather than the assembled deltas.
|
|
68
68
|
if (final !== undefined)
|
|
69
69
|
return ok(final);
|
|
70
|
+
// The partial carries no model identity and no usage: it is
|
|
71
|
+
// assembled from deltas rather than from a provider response, so
|
|
72
|
+
// nothing here reported which model answered or what it cost.
|
|
73
|
+
// Stamping the requested id would manufacture a claim the
|
|
74
|
+
// endpoint never made.
|
|
70
75
|
return ok({ message: { role: "assistant", content: partial }, finishReason: "aborted" });
|
|
71
76
|
}
|
|
72
77
|
return err(toProviderError(e, workload));
|
|
@@ -46,6 +46,11 @@ export type PromptCachePolicy = {
|
|
|
46
46
|
* seeds the cache (billed at a premium; it only pays for itself once a later
|
|
47
47
|
* call reads it back).
|
|
48
48
|
*
|
|
49
|
+
* `reasoningTokens` is exactly what the provider reported — never derived from,
|
|
50
|
+
* nor reconciled against, `outputTokens`. Whether reasoning tokens are counted
|
|
51
|
+
* inside the output total varies by provider, so any arithmetic between the two
|
|
52
|
+
* would be a guess dressed as a figure.
|
|
53
|
+
*
|
|
49
54
|
* Every field is optional: a provider that reports no usage at all, or reports
|
|
50
55
|
* totals without a cache breakdown, is legitimate. Absent means "not reported",
|
|
51
56
|
* never "zero".
|
|
@@ -55,6 +60,7 @@ export interface ChatUsage {
|
|
|
55
60
|
readonly outputTokens?: number;
|
|
56
61
|
readonly cacheCreationInputTokens?: number;
|
|
57
62
|
readonly cacheReadInputTokens?: number;
|
|
63
|
+
readonly reasoningTokens?: number;
|
|
58
64
|
}
|
|
59
65
|
export interface ChatResponse {
|
|
60
66
|
readonly message: Extract<ModelMessage, {
|
|
@@ -72,6 +78,23 @@ export interface ChatResponse {
|
|
|
72
78
|
readonly finishReason: FinishReason | "aborted";
|
|
73
79
|
readonly rawFinishReason?: string;
|
|
74
80
|
readonly usage?: ChatUsage;
|
|
81
|
+
/**
|
|
82
|
+
* The id of the model this provider instance is bound to — what the caller
|
|
83
|
+
* asked for. Absent when the binding names no id.
|
|
84
|
+
*/
|
|
85
|
+
readonly requestedModelId?: string;
|
|
86
|
+
/**
|
|
87
|
+
* The model id the provider itself reported as having answered. Absent
|
|
88
|
+
* whenever the provider reported none — it is NEVER filled in from
|
|
89
|
+
* `requestedModelId`, because a claim about which model answered is only
|
|
90
|
+
* worth anything when the endpoint actually made it.
|
|
91
|
+
*
|
|
92
|
+
* A value differing from `requestedModelId` is not an error: the pair exists
|
|
93
|
+
* so a consumer can observe an endpoint or proxy serving a different model
|
|
94
|
+
* version than the one configured. Treat the comparison as best-effort
|
|
95
|
+
* diagnostics, never as an invariant.
|
|
96
|
+
*/
|
|
97
|
+
readonly servedModelId?: string;
|
|
75
98
|
}
|
|
76
99
|
export type ChatStreamEvent = {
|
|
77
100
|
readonly type: "text-delta";
|
package/dist/providers/types.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* before launch — a blue/green drain treats them as a single cohort.
|
|
16
16
|
*/
|
|
17
17
|
import { type ConversationAgentDeps } from "../agents/conversation-agent.js";
|
|
18
|
+
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
18
19
|
import type { ResourcePolicy } from "../config/resource-limits.js";
|
|
19
20
|
import type { AgentDefinition } from "../loop/types.js";
|
|
20
21
|
import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalysisResult } from "../workflows/execute-analysis.js";
|
|
@@ -27,12 +28,20 @@ export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxSt
|
|
|
27
28
|
* Deps bundles for the durable workflows. `executeAnalysis` is a builder
|
|
28
29
|
* because its `sandboxStepCallable` is the registered sandbox-step callable,
|
|
29
30
|
* which does not exist until registration runs inside `assembleCoreRuntime`.
|
|
31
|
+
*
|
|
32
|
+
* Each bag omits `usageRecorder` for the same reason `ConversationAssemblyDeps`
|
|
33
|
+
* does: the recorder is resolved once below and stamped onto every bag this
|
|
34
|
+
* call registers, so one runtime reports to one ledger. Leaving it settable
|
|
35
|
+
* here would let an embedder wire a recorder the workflows use and the
|
|
36
|
+
* conversation agent does not — a half-wired ledger that reads as a complete
|
|
37
|
+
* one. The protection has to hold on both sides of the assembly, not just the
|
|
38
|
+
* conversation side.
|
|
30
39
|
*/
|
|
31
40
|
export interface CoreWorkflowDeps {
|
|
32
|
-
readonly sandboxStep: SandboxStepDeps
|
|
33
|
-
readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => ExecuteAnalysisDeps
|
|
34
|
-
readonly executeTargetAssessment: ExecuteTargetAssessmentDeps
|
|
35
|
-
readonly dataProfile: DataProfileDeps
|
|
41
|
+
readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder">;
|
|
42
|
+
readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder">;
|
|
43
|
+
readonly executeTargetAssessment: Omit<ExecuteTargetAssessmentDeps, "usageRecorder">;
|
|
44
|
+
readonly dataProfile: Omit<DataProfileDeps, "usageRecorder">;
|
|
36
45
|
}
|
|
37
46
|
/** The registered, callable workflow handles. */
|
|
38
47
|
export interface RegisteredWorkflows {
|
|
@@ -42,11 +51,12 @@ export interface RegisteredWorkflows {
|
|
|
42
51
|
readonly dataProfile: (input: DataProfileWorkflowInput) => Promise<void>;
|
|
43
52
|
}
|
|
44
53
|
/**
|
|
45
|
-
* Conversation-agent deps minus the workflow callable
|
|
46
|
-
*
|
|
47
|
-
* a stale callable
|
|
54
|
+
* Conversation-agent deps minus the workflow callable, the resource policy, and
|
|
55
|
+
* the usage recorder — `assembleCoreRuntime` supplies those itself so a caller
|
|
56
|
+
* cannot wire a stale callable, a policy that diverges from the one the
|
|
57
|
+
* workflows see, or a recorder that only half the agent tree reports to.
|
|
48
58
|
*/
|
|
49
|
-
export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy">;
|
|
59
|
+
export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder">;
|
|
50
60
|
export interface CoreRuntimeDeps {
|
|
51
61
|
readonly conversation: ConversationAssemblyDeps;
|
|
52
62
|
readonly workflows: CoreWorkflowDeps;
|
|
@@ -55,6 +65,13 @@ export interface CoreRuntimeDeps {
|
|
|
55
65
|
* point for planner/routing guidance and workflow-input budget snapshots.
|
|
56
66
|
*/
|
|
57
67
|
readonly resourcePolicy?: ResourcePolicy;
|
|
68
|
+
/**
|
|
69
|
+
* LLM usage-accounting seam. Resolved once here and stamped onto the
|
|
70
|
+
* conversation agent and every workflow deps bag this call registers, so
|
|
71
|
+
* one runtime reports to one ledger. Omitted falls back to
|
|
72
|
+
* `createNoopUsageRecorder()`.
|
|
73
|
+
*/
|
|
74
|
+
readonly usageRecorder?: UsageRecorder;
|
|
58
75
|
}
|
|
59
76
|
export interface CoreRuntime {
|
|
60
77
|
readonly conversationAgent: AgentDefinition;
|
package/dist/runtime/assemble.js
CHANGED
|
@@ -15,20 +15,23 @@
|
|
|
15
15
|
* before launch — a blue/green drain treats them as a single cohort.
|
|
16
16
|
*/
|
|
17
17
|
import { createConversationAgent } from "../agents/conversation-agent.js";
|
|
18
|
+
import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
|
|
18
19
|
import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
|
|
19
20
|
import { registerSandboxStep } from "../workflows/sandbox-step.js";
|
|
20
21
|
import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
|
|
21
22
|
import { registerDataProfileWorkflow } from "../tasks/data-profile.js";
|
|
22
23
|
export function assembleCoreRuntime(deps) {
|
|
23
24
|
const { conversation, workflows: wf, resourcePolicy } = deps;
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
25
|
+
const usageRecorder = deps.usageRecorder ?? createNoopUsageRecorder();
|
|
26
|
+
const sandboxStep = registerSandboxStep({ ...wf.sandboxStep, usageRecorder });
|
|
27
|
+
const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), usageRecorder });
|
|
28
|
+
const executeTargetAssessment = registerExecuteTargetAssessment({ ...wf.executeTargetAssessment, usageRecorder });
|
|
29
|
+
const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder });
|
|
28
30
|
const conversationAgent = createConversationAgent({
|
|
29
31
|
...conversation,
|
|
30
32
|
executeAnalysisWorkflow: executeAnalysis,
|
|
31
33
|
resourcePolicy,
|
|
34
|
+
usageRecorder,
|
|
32
35
|
});
|
|
33
36
|
return {
|
|
34
37
|
conversationAgent,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* into its emitters.
|
|
7
7
|
*/
|
|
8
8
|
import type { ChatDataPart, EmitFn } from "../loop/types.js";
|
|
9
|
+
import type { ToolDetailResolver } from "../tools/detail-resolver.js";
|
|
9
10
|
/**
|
|
10
11
|
* Stable reconciliation id for a step's reconciling parts
|
|
11
12
|
* (`data-step-activity`, `data-step-file-tree`). One id per (runId, stepId)
|
|
@@ -18,7 +19,27 @@ import type { ChatDataPart, EmitFn } from "../loop/types.js";
|
|
|
18
19
|
* producers construct it identically.
|
|
19
20
|
*/
|
|
20
21
|
export declare function stepPartId(kind: string, runId: string, stepId: string): string;
|
|
21
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The live-activity phrase for one tool call: the tool's name, then its own
|
|
24
|
+
* description of this call when it has one.
|
|
25
|
+
*
|
|
26
|
+
* The description comes from the tool's `describeCall` hook, resolved through
|
|
27
|
+
* `resolveDetail` — the same resolver the chat chip and the reloaded transcript
|
|
28
|
+
* use. A caller supplies the resolver for the agent whose calls it is
|
|
29
|
+
* describing, because a sandbox agent's tool list is not the conversation
|
|
30
|
+
* roster's and no single list serves both.
|
|
31
|
+
*
|
|
32
|
+
* The name always leads, and that is load-bearing rather than cosmetic. This
|
|
33
|
+
* phrase is rendered alone, with no tool name beside it — unlike the chat chip,
|
|
34
|
+
* which prints the name itself and can afford a bare detail. `write_file` and
|
|
35
|
+
* `edit_file` both describe a call by its path, so a detail on its own would
|
|
36
|
+
* render a write and an edit of the same file identically.
|
|
37
|
+
*
|
|
38
|
+
* A tool with no hook, an unknown tool, and an input its schema rejects all fall
|
|
39
|
+
* back to the name alone. No verb is added: the phrase is carried on a part that
|
|
40
|
+
* already states the phase, so "Running" would only restate it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function activityForTool(name: string, input?: unknown, resolveDetail?: ToolDetailResolver): string;
|
|
22
43
|
/** On-change working-tree delta the sandbox executor posts per exec (paths
|
|
23
44
|
* only; the executor skips directories). */
|
|
24
45
|
export interface SandboxTreeDelta {
|
|
@@ -19,42 +19,29 @@
|
|
|
19
19
|
export function stepPartId(kind, runId, stepId) {
|
|
20
20
|
return `${kind}-${runId}-${stepId}`;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (name === "execute_command") {
|
|
46
|
-
const cmd = obj.command;
|
|
47
|
-
if (!Array.isArray(cmd))
|
|
48
|
-
return null;
|
|
49
|
-
const script = cmd.find((a) => typeof a === "string" && /\.(py|R|r|sh|js|ts|ipynb)$/.test(a));
|
|
50
|
-
return script ? baseName(script) : null;
|
|
51
|
-
}
|
|
52
|
-
return typeof obj.path === "string" && obj.path.length > 0 ? baseName(obj.path) : null;
|
|
53
|
-
}
|
|
54
|
-
export function activityForTool(name, input) {
|
|
55
|
-
const label = TOOL_ACTIVITY[name] ?? `Running ${name}`;
|
|
56
|
-
const file = activityFileName(name, input);
|
|
57
|
-
return file ? `${label} ${file}` : label;
|
|
22
|
+
/**
|
|
23
|
+
* The live-activity phrase for one tool call: the tool's name, then its own
|
|
24
|
+
* description of this call when it has one.
|
|
25
|
+
*
|
|
26
|
+
* The description comes from the tool's `describeCall` hook, resolved through
|
|
27
|
+
* `resolveDetail` — the same resolver the chat chip and the reloaded transcript
|
|
28
|
+
* use. A caller supplies the resolver for the agent whose calls it is
|
|
29
|
+
* describing, because a sandbox agent's tool list is not the conversation
|
|
30
|
+
* roster's and no single list serves both.
|
|
31
|
+
*
|
|
32
|
+
* The name always leads, and that is load-bearing rather than cosmetic. This
|
|
33
|
+
* phrase is rendered alone, with no tool name beside it — unlike the chat chip,
|
|
34
|
+
* which prints the name itself and can afford a bare detail. `write_file` and
|
|
35
|
+
* `edit_file` both describe a call by its path, so a detail on its own would
|
|
36
|
+
* render a write and an edit of the same file identically.
|
|
37
|
+
*
|
|
38
|
+
* A tool with no hook, an unknown tool, and an input its schema rejects all fall
|
|
39
|
+
* back to the name alone. No verb is added: the phrase is carried on a part that
|
|
40
|
+
* already states the phase, so "Running" would only restate it.
|
|
41
|
+
*/
|
|
42
|
+
export function activityForTool(name, input, resolveDetail) {
|
|
43
|
+
const detail = resolveDetail?.(name, input);
|
|
44
|
+
return detail === undefined ? name : `${name} ${detail}`;
|
|
58
45
|
}
|
|
59
46
|
/** Extract the tree delta from a `data-sandbox-event` part, or null when the
|
|
60
47
|
* part is some other sandbox event. The wrapper shape is set by
|
|
@@ -57,6 +57,7 @@ export declare const SynthesizedFindingSchema: z.ZodObject<{
|
|
|
57
57
|
"clinical-evidence": "clinical-evidence";
|
|
58
58
|
}>;
|
|
59
59
|
actionType: z.ZodOptional<z.ZodEnum<{
|
|
60
|
+
none: "none";
|
|
60
61
|
"therapeutic-target": "therapeutic-target";
|
|
61
62
|
"predictive-biomarker": "predictive-biomarker";
|
|
62
63
|
"prognostic-biomarker": "prognostic-biomarker";
|
|
@@ -65,7 +66,6 @@ export declare const SynthesizedFindingSchema: z.ZodObject<{
|
|
|
65
66
|
"resistance-mechanism": "resistance-mechanism";
|
|
66
67
|
"patient-stratification": "patient-stratification";
|
|
67
68
|
"mechanism-of-action": "mechanism-of-action";
|
|
68
|
-
none: "none";
|
|
69
69
|
}>>;
|
|
70
70
|
rationale: z.ZodString;
|
|
71
71
|
}, z.core.$strip>>;
|
|
@@ -123,6 +123,7 @@ export declare const RunSynthesisSchema: z.ZodObject<{
|
|
|
123
123
|
"clinical-evidence": "clinical-evidence";
|
|
124
124
|
}>;
|
|
125
125
|
actionType: z.ZodOptional<z.ZodEnum<{
|
|
126
|
+
none: "none";
|
|
126
127
|
"therapeutic-target": "therapeutic-target";
|
|
127
128
|
"predictive-biomarker": "predictive-biomarker";
|
|
128
129
|
"prognostic-biomarker": "prognostic-biomarker";
|
|
@@ -131,7 +132,6 @@ export declare const RunSynthesisSchema: z.ZodObject<{
|
|
|
131
132
|
"resistance-mechanism": "resistance-mechanism";
|
|
132
133
|
"patient-stratification": "patient-stratification";
|
|
133
134
|
"mechanism-of-action": "mechanism-of-action";
|
|
134
|
-
none: "none";
|
|
135
135
|
}>>;
|
|
136
136
|
rationale: z.ZodString;
|
|
137
137
|
}, z.core.$strip>>;
|
package/dist/state/index.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type { InsertPlanInput, UpsertPlanInput } from "./plans.js";
|
|
|
23
23
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, loadDataProfileStatus, loadSeedInputFileIds, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
24
24
|
export type { DataProfileStatus, DataProfileResult, DataProfileInputFile, DataProfileFile, DataProfileQualityAssessment } from "./data-profile.js";
|
|
25
25
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
|
26
|
+
export { createAnalysisPurge } from "./purge-analysis.js";
|
|
27
|
+
export type { AnalysisPurge, AnalysisPurgeDeps, AnalysisPurgeOutcome } from "./purge-analysis.js";
|
|
26
28
|
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, SynthesisStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
|
27
29
|
export type { AnalysisStateRow, ArtifactRow, CortexRunRow, StepExecutionRow, CortexPlanRow } from "./schema.js";
|
|
28
30
|
export { insertAssessment, updateProgress, setDossier, markFailed, getAssessment, listAssessmentsByOrg, softDeleteAssessment, TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentRowSchema, } from "./target-assessments.js";
|
package/dist/state/index.js
CHANGED
|
@@ -16,5 +16,8 @@ export { setSandboxRef, setActiveExecId, clearSandboxRef, queryActiveSandboxes,
|
|
|
16
16
|
export { insertPlan, upsertPlan, loadPlan } from "./plans.js";
|
|
17
17
|
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, loadDataProfileStatus, loadSeedInputFileIds, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
18
18
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses } from "./analyses-metrics.js";
|
|
19
|
+
// Not per-entity: one analysis's whole Postgres footprint, reached from its id
|
|
20
|
+
// alone, with the workflow ledger behind an injected seam.
|
|
21
|
+
export { createAnalysisPurge } from "./purge-analysis.js";
|
|
19
22
|
export { AnalysisStateRowSchema, ArtifactRole, ArtifactRowSchema, RunStatus, StepExecutionStatus, SynthesisStatus, CortexRunRowSchema, StepExecutionRowSchema, CortexPlanRowSchema, } from "./schema.js";
|
|
20
23
|
export { insertAssessment, updateProgress, setDossier, markFailed, getAssessment, listAssessmentsByOrg, softDeleteAssessment, TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentRowSchema, } from "./target-assessments.js";
|
package/dist/state/init.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { backfillAiSdkMessageEnvelopes } from "../memory/message-backfill.js";
|
|
10
10
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
11
|
+
// No ';' may appear anywhere in this text, comments included: it is executed by
|
|
12
|
+
// splitting on ';' (see `DDL.split` below), so a semicolon inside a comment cuts
|
|
13
|
+
// the statement it precedes in half and Postgres rejects the fragment.
|
|
11
14
|
const DDL = `
|
|
12
15
|
-- Analysis-level state (status, context, data-profile tracking, billing identity).
|
|
13
16
|
--
|
|
@@ -227,6 +230,25 @@ CREATE INDEX IF NOT EXISTS idx_cortex_regulatory_chunks_metadata
|
|
|
227
230
|
-- re-sent to the provider on the next turn, where a reordered prefix misses the
|
|
228
231
|
-- prompt cache. JSON stores the text verbatim. Nothing indexes this column, and
|
|
229
232
|
-- the JSON operators retractLastTurn uses work the same on either type.
|
|
233
|
+
--
|
|
234
|
+
-- reported_usage holds a whole turn's TokenUsageRollup — what providers reported
|
|
235
|
+
-- for the turn this row completed — and is written only on the LAST assistant row
|
|
236
|
+
-- of a turn, so a reloaded transcript can show the figure the live turn showed.
|
|
237
|
+
-- It is NOT the tokens column beside it, and the two must never be read for
|
|
238
|
+
-- each other: tokens is an offline js-tiktoken estimate stamped on EVERY row so
|
|
239
|
+
-- the read path can window by budget without a provider round trip, and it exists
|
|
240
|
+
-- for rows no provider ever saw. reported_usage is a provider's own report, and
|
|
241
|
+
-- covers a turn rather than a row. Both are plausible-looking integers on one row,
|
|
242
|
+
-- which is exactly why the column names carry the distinction.
|
|
243
|
+
--
|
|
244
|
+
-- Nullable with no default: absent means nothing was reported, and a zero would
|
|
245
|
+
-- claim a turn cost nothing. A row written before this column existed is absent
|
|
246
|
+
-- for the same reason, so the migration needs no backfill.
|
|
247
|
+
--
|
|
248
|
+
-- JSONB here, unlike message_envelope above. The verbatim-bytes requirement that
|
|
249
|
+
-- forces JSON on the envelope is about model content that is re-sent to the
|
|
250
|
+
-- provider. This record is harness-generated, has a fixed field set, and never
|
|
251
|
+
-- reaches a prompt, so it does not share that requirement.
|
|
230
252
|
CREATE TABLE IF NOT EXISTS messages (
|
|
231
253
|
thread_id TEXT NOT NULL,
|
|
232
254
|
seq BIGINT NOT NULL,
|
|
@@ -234,6 +256,7 @@ CREATE TABLE IF NOT EXISTS messages (
|
|
|
234
256
|
content_jsonb JSONB,
|
|
235
257
|
message_envelope JSON,
|
|
236
258
|
tokens INTEGER NOT NULL,
|
|
259
|
+
reported_usage JSONB,
|
|
237
260
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
238
261
|
PRIMARY KEY (thread_id, seq)
|
|
239
262
|
);
|
|
@@ -432,6 +455,11 @@ export async function initCortexState(pool, injected) {
|
|
|
432
455
|
// dropping an absent NOT NULL no-ops, so this is a no-op on fresh DBs.
|
|
433
456
|
"ALTER TABLE cortex_analysis_state ALTER COLUMN data_profile_status DROP NOT NULL",
|
|
434
457
|
"ALTER TABLE messages ADD COLUMN IF NOT EXISTS message_envelope JSON",
|
|
458
|
+
// The turn's reported usage rollup (see the CREATE TABLE comment).
|
|
459
|
+
// Purely additive: nullable with no default, so adding it rewrites no
|
|
460
|
+
// row and every pre-existing message reads back with no rollup — which
|
|
461
|
+
// is the honest value, the figures having never been recorded.
|
|
462
|
+
"ALTER TABLE messages ADD COLUMN IF NOT EXISTS reported_usage JSONB",
|
|
435
463
|
// Demote an existing JSONB envelope column to JSON (see the CREATE
|
|
436
464
|
// TABLE comment). Type-guarded because ALTER ... TYPE rewrites the
|
|
437
465
|
// whole table and this runs at every boot; scoped to
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* purgeAnalysis — the single place that knows what an analysis's persisted
|
|
3
|
+
* Postgres footprint is.
|
|
4
|
+
*
|
|
5
|
+
* A host asks for it with an `analysisId` and nothing else: every keyed store,
|
|
6
|
+
* the analysis's dynamic vector-table name, and its workflow ids are all
|
|
7
|
+
* derivable from that id, so no caller enumerates tables or supplies a run list.
|
|
8
|
+
* The workflow ledger is part of the footprint, not a follow-on — the sandbox
|
|
9
|
+
* transcripts and run-event streams live there and are the dominant share of an
|
|
10
|
+
* analysis's stored bytes — and it is reached through the injected
|
|
11
|
+
* `WorkflowPurger` seam so the durability engine stays out of the state layer.
|
|
12
|
+
*
|
|
13
|
+
* There is no single transaction across the stages: the purge spans app tables, a
|
|
14
|
+
* DDL `DROP TABLE`, and the engine's system schema, so instead of faking atomicity
|
|
15
|
+
* every stage is idempotent and the whole operation is safe to re-run. A purge of
|
|
16
|
+
* an unknown or already-purged analysis therefore succeeds, reporting zeroes. Each
|
|
17
|
+
* stage carries the reason it sits where it does.
|
|
18
|
+
*
|
|
19
|
+
* A purge is NOT serialized against work still starting on the analysis. The
|
|
20
|
+
* mapping from an analysis to its workflows is read once, up front, out of
|
|
21
|
+
* `cortex_runs`; a run inserted after that read is outside the captured set, and
|
|
22
|
+
* the `cortex_runs` delete later in the same purge removes the only row that could
|
|
23
|
+
* ever have named it. Its workflow row and every byte cascading off it then belong
|
|
24
|
+
* to no analysis — no retry reaches them, because nothing is left that attributes
|
|
25
|
+
* them to one. Quiesce the analysis before purging it: no new runs, no new
|
|
26
|
+
* data-profile triggers. This module cannot observe a host's in-flight work, so it
|
|
27
|
+
* does not enforce that.
|
|
28
|
+
*
|
|
29
|
+
* What it does not reach, so that absent coverage is never read as delivered:
|
|
30
|
+
* scheduled operational workflows (they belong to no analysis and accumulate
|
|
31
|
+
* independently of any purge), target assessments and their annotations (a
|
|
32
|
+
* separate top-level entity), the shared regulatory corpus, `messages` rows whose
|
|
33
|
+
* thread row is already gone (nothing attributes them to an analysis), and
|
|
34
|
+
* workspace files on disk (the embedder owns their disposal).
|
|
35
|
+
*/
|
|
36
|
+
import { ResultAsync } from "neverthrow";
|
|
37
|
+
import type { Pool } from "pg";
|
|
38
|
+
import type { WorkflowPurger } from "../execution/workflow-purger.js";
|
|
39
|
+
import { type DbError } from "../lib/db-result.js";
|
|
40
|
+
import type { Logger } from "../lib/logger.js";
|
|
41
|
+
/**
|
|
42
|
+
* What one invocation reclaimed. It is a report of this call, not a claim that the
|
|
43
|
+
* analysis is now empty: a re-run still reclaims whatever a failed stage left
|
|
44
|
+
* behind, and rows written under a workflow the purge already deleted, which trip
|
|
45
|
+
* their foreign key.
|
|
46
|
+
*/
|
|
47
|
+
export interface AnalysisPurgeOutcome {
|
|
48
|
+
readonly threads: number;
|
|
49
|
+
readonly messages: number;
|
|
50
|
+
/**
|
|
51
|
+
* How many workflows the ledger delete targeted, counted immediately before it
|
|
52
|
+
* and outside any transaction, because the engine owns the delete and reports no
|
|
53
|
+
* count of its own. It is not a post-delete tally: a row arriving between the
|
|
54
|
+
* count and the delete is deleted without being counted.
|
|
55
|
+
*/
|
|
56
|
+
readonly workflows: number;
|
|
57
|
+
readonly vectorIndexDropped: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface AnalysisPurgeDeps {
|
|
60
|
+
readonly pool: Pool;
|
|
61
|
+
/** The workflow-ledger seam. Cancellation and deletion by id, engine-agnostic. */
|
|
62
|
+
readonly workflows: WorkflowPurger;
|
|
63
|
+
readonly logger?: Logger;
|
|
64
|
+
}
|
|
65
|
+
export interface AnalysisPurge {
|
|
66
|
+
/**
|
|
67
|
+
* Remove the analysis's entire persisted Postgres footprint. Absence is a
|
|
68
|
+
* normal outcome: an unknown or already-purged analysis succeeds with zeroes.
|
|
69
|
+
* Every failure rides the error channel — the operation never reports a purge
|
|
70
|
+
* it did not achieve. The caller quiesces the analysis first (module note).
|
|
71
|
+
*/
|
|
72
|
+
purgeAnalysis(analysisId: string): ResultAsync<AnalysisPurgeOutcome, DbError>;
|
|
73
|
+
}
|
|
74
|
+
export declare function createAnalysisPurge({ pool, workflows, logger: injected }: AnalysisPurgeDeps): AnalysisPurge;
|