@fyeeme/pi-dynamic-workflows 2.0.1 → 2.0.2
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 +5 -2
- package/README.zh-CN.md +1 -1
- package/index.ts +5 -1
- package/package.json +66 -65
- package/src/classifier-hook.ts +56 -0
- package/src/runner/index.ts +7 -1
- package/src/runner/stage-executor.ts +59 -20
- package/src/types.ts +30 -0
package/README.md
CHANGED
|
@@ -180,6 +180,9 @@ defineWorkflow({
|
|
|
180
180
|
// results: { candidates, winner, judges: [{winner, reason}] }
|
|
181
181
|
|
|
182
182
|
// Classify input, then run the matching route's sub-steps.
|
|
183
|
+
// On pi 0.99+ hosts with a classifier model, the category comes from
|
|
184
|
+
// ModelRuntime.classify() (no classification subprocess); without one, an
|
|
185
|
+
// agent answers the classification. The step result records which path ran.
|
|
183
186
|
defineWorkflow({
|
|
184
187
|
name: "route",
|
|
185
188
|
steps: [
|
|
@@ -195,7 +198,7 @@ defineWorkflow({
|
|
|
195
198
|
},
|
|
196
199
|
],
|
|
197
200
|
});
|
|
198
|
-
// results: { category, matched, route: StepResult[], routeStatus }
|
|
201
|
+
// results: { category, matched, route: StepResult[], routeStatus, path }
|
|
199
202
|
```
|
|
200
203
|
|
|
201
204
|
Judge/classifier JSON is parsed leniently (LLMs return `"true"`/`"0"` as strings); route nesting is depth-capped to catch cycles.
|
|
@@ -314,7 +317,7 @@ const result = await runWorkflow({ workflow: wf, cwd: tempDir, now: 1000, dispat
|
|
|
314
317
|
| `loop_until` | `prompt(ctx,i)`, `until(ctx,i)`, `maxIterations?` | array of per-iteration outputs |
|
|
315
318
|
| `adversarial` | `produce`, `rubric[]`, `judges?`, `minPass?` | `{ candidate, passed, passCount, judges }` |
|
|
316
319
|
| `tournament` | `candidates`, `judges`, `produce` | `{ candidates, winner, judges }` |
|
|
317
|
-
| `classify_route` | `classifier`, `routes: Record<cat, Step[]>`, `fallback?` | `{ category, matched, route, routeStatus }` |
|
|
320
|
+
| `classify_route` | `classifier`, `routes: Record<cat, Step[]>`, `fallback?` | `{ category, matched, route, routeStatus, path }` |
|
|
318
321
|
| `sub_workflow` | `workflow: WorkflowDefinition`, `input?`, `inheritBudget?` | `{ steps, status, workflowName, error }` |
|
|
319
322
|
| `loop_until_dry` | `agent(item, i)`, `keyOf?`, `merge?`, `maxRounds?`, `dryThreshold?` | array of discovered items |
|
|
320
323
|
|
package/README.zh-CN.md
CHANGED
|
@@ -185,7 +185,7 @@ defineWorkflow({
|
|
|
185
185
|
},
|
|
186
186
|
],
|
|
187
187
|
});
|
|
188
|
-
// results: { category, matched, route: StepResult[], routeStatus }
|
|
188
|
+
// results: { category, matched, route: StepResult[], routeStatus, path }
|
|
189
189
|
```
|
|
190
190
|
|
|
191
191
|
评判/分类的 JSON 采用宽松解析(LLM 常把 `"true"`/`"0"` 当字符串返回);路由嵌套有深度上限以防循环。
|
package/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
20
20
|
import { Type } from "typebox";
|
|
21
21
|
import piSubagents from "@fyeeme/pi-subagents";
|
|
22
22
|
import { defineWorkflow, runWorkflow } from "./src/index.ts";
|
|
23
|
+
import { createClassifyHook } from "./src/classifier-hook.ts";
|
|
23
24
|
import type { Budget, StepContext, StepDefinition, WorkflowDefinition } from "./src/types.ts";
|
|
24
25
|
import { WorkflowError } from "./src/errors.ts";
|
|
25
26
|
import { discoverWorkflowLibrary, loadLibraryWorkflow } from "./src/library.ts";
|
|
@@ -80,7 +81,7 @@ const StepSchema = Type.Union([
|
|
|
80
81
|
Type.Object({
|
|
81
82
|
id: Type.String(),
|
|
82
83
|
type: Type.Literal("classify_route"),
|
|
83
|
-
prompt: Type.String({ description: "
|
|
84
|
+
prompt: Type.String({ description: "Classification prompt; routing runs on a classifier model when the host has one (pi 0.99), otherwise an agent replies with JSON {category: \"...\"}" }),
|
|
84
85
|
routes: Type.Record(
|
|
85
86
|
Type.String(),
|
|
86
87
|
Type.Array(Type.Object({ id: Type.String(), prompt: Type.String(), model: Type.Optional(Type.String()) })),
|
|
@@ -413,6 +414,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
413
414
|
cwd: params.cwd ?? ctx.cwd,
|
|
414
415
|
now: params.now ?? Date.now(),
|
|
415
416
|
signal,
|
|
417
|
+
// pi 0.99 classifier routing: classify_route prefers ModelRuntime.classify()
|
|
418
|
+
// when a classifier model is available; otherwise the agent path runs.
|
|
419
|
+
classify: createClassifyHook(ctx.modelRegistry),
|
|
416
420
|
});
|
|
417
421
|
|
|
418
422
|
const lines = [
|
package/package.json
CHANGED
|
@@ -1,67 +1,68 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
2
|
+
"name": "@fyeeme/pi-dynamic-workflows",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Deterministic TypeScript workflow orchestration for pi. Fuses the pi-dynamic-workflows design (declarative graph, 10 step primitives, heuristic planner, outcome collectors) with Claude Code's workflow engine coordination mechanisms (deterministic sandbox, cache-key resume, per-agent abort map, dynamic budget, runaway caps).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "fyeeme",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"pi-package",
|
|
13
|
+
"pi",
|
|
14
|
+
"dynamic-workflow",
|
|
15
|
+
"workflow",
|
|
16
|
+
"orchestration",
|
|
17
|
+
"fan-out",
|
|
18
|
+
"pipeline",
|
|
19
|
+
"multi-agent"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"*.ts",
|
|
23
|
+
"src/**/*.ts",
|
|
24
|
+
"sessions/**/*.ts",
|
|
25
|
+
"workflows/**/*.ts",
|
|
26
|
+
"skills/**/*.md",
|
|
27
|
+
"prompts/**/*.md",
|
|
28
|
+
"README.md",
|
|
29
|
+
"README.zh-CN.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"pi": {
|
|
33
|
+
"extensions": [
|
|
34
|
+
"./index.ts"
|
|
35
|
+
],
|
|
36
|
+
"skills": [
|
|
37
|
+
"skills/workflow-author"
|
|
38
|
+
],
|
|
39
|
+
"prompts": [
|
|
40
|
+
"prompts"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "vitest --run",
|
|
45
|
+
"typecheck": "tsc"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@fyeeme/pi-subagents": "2.1.1"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@earendil-works/pi-ai": ">=0.99.0",
|
|
52
|
+
"@earendil-works/pi-coding-agent": ">=0.99.0",
|
|
53
|
+
"@earendil-works/pi-tui": ">=0.99.0",
|
|
54
|
+
"jiti": ">=2.0.0",
|
|
55
|
+
"typebox": ">=1.0.0",
|
|
56
|
+
"typescript": ">=5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@earendil-works/pi-ai": "0.99.2",
|
|
60
|
+
"@earendil-works/pi-coding-agent": "0.99.2",
|
|
61
|
+
"@earendil-works/pi-agent-core": "0.99.2",
|
|
62
|
+
"@earendil-works/pi-tui": "0.99.2",
|
|
63
|
+
"@types/node": "22.19.19",
|
|
64
|
+
"jiti": "2.7.0",
|
|
65
|
+
"typebox": "1.1.38",
|
|
66
|
+
"typescript": "5.9.3"
|
|
67
|
+
}
|
|
67
68
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* classify_route classifier hook — pi 0.99 `ModelRuntime.classify()` path.
|
|
3
|
+
*
|
|
4
|
+
* Model discovery (design D3): a step-declared `model` is honored only when it
|
|
5
|
+
* resolves to a classifier-type model with working credentials; otherwise the
|
|
6
|
+
* first available classifier on the host. None → `undefined` (the runner falls
|
|
7
|
+
* back to the agent path). The choice question's criteria are the route names;
|
|
8
|
+
* the answer is returned verbatim so undeclared categories resolve through
|
|
9
|
+
* fallback sub-steps exactly like the agent path (Routing equivalence).
|
|
10
|
+
*/
|
|
11
|
+
import type { ClassifierApi, ClassifierContext, ClassifierModel, ClassifierResult } from "@earendil-works/pi-ai";
|
|
12
|
+
import type { ClassifyHook } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
/** The registry surface the hook needs — satisfied by the extension context's
|
|
15
|
+
* ModelRegistry (pi 0.99+); tests mock this narrow slice. */
|
|
16
|
+
export interface ClassifierRegistrySlice {
|
|
17
|
+
getAvailableOfType(type: "classifier"): Promise<readonly ClassifierModel<ClassifierApi>[]>;
|
|
18
|
+
classify(model: ClassifierModel<ClassifierApi>, context: ClassifierContext): Promise<ClassifierResult>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createClassifyHook(registry: ClassifierRegistrySlice): ClassifyHook {
|
|
22
|
+
return async (prompt, routeNames, model) => {
|
|
23
|
+
const available = await registry.getAvailableOfType("classifier");
|
|
24
|
+
if (available.length === 0) return undefined;
|
|
25
|
+
const chosen = (model ? available.find((m) => m.id === model) : undefined) ?? available[0]!;
|
|
26
|
+
|
|
27
|
+
const result = await registry.classify(chosen, {
|
|
28
|
+
state: { prompt, routes: [...routeNames] },
|
|
29
|
+
questions: {
|
|
30
|
+
route: {
|
|
31
|
+
type: "choice",
|
|
32
|
+
instructions: prompt,
|
|
33
|
+
criteria: Object.fromEntries(routeNames.map((name) => [name, name])),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (result.stopReason !== "stop") return undefined;
|
|
38
|
+
const answer = result.answers.route;
|
|
39
|
+
if (answer?.type !== "choice") return undefined;
|
|
40
|
+
|
|
41
|
+
const u = result.usage;
|
|
42
|
+
return {
|
|
43
|
+
category: answer.choice,
|
|
44
|
+
usage: u
|
|
45
|
+
? {
|
|
46
|
+
input: u.input ?? 0,
|
|
47
|
+
output: u.output ?? 0,
|
|
48
|
+
cacheRead: u.cacheRead ?? 0,
|
|
49
|
+
cacheWrite: u.cacheWrite ?? 0,
|
|
50
|
+
totalTokens: u.totalTokens ?? (u.input ?? 0) + (u.output ?? 0),
|
|
51
|
+
cost: u.cost?.total ?? 0,
|
|
52
|
+
}
|
|
53
|
+
: undefined,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}
|
package/src/runner/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { BudgetPool } from "../budget/index.ts";
|
|
|
19
19
|
import { Journal, type RunManifest } from "../cache/index.ts";
|
|
20
20
|
import type { AgentLifecycleListeners } from "../lifecycle.ts";
|
|
21
21
|
import { generateRunId } from "../state/index.ts";
|
|
22
|
-
import type { Budget, RunResult, WorkflowDefinition } from "../types.ts";
|
|
22
|
+
import type { Budget, ClassifyHook, RunResult, WorkflowDefinition } from "../types.ts";
|
|
23
23
|
import {
|
|
24
24
|
createSpawnRegistry,
|
|
25
25
|
spawnAgent,
|
|
@@ -73,6 +73,11 @@ export interface RunWorkflowOptions {
|
|
|
73
73
|
/** A6: policy gate invoked before the first dispatch. Return { allow: false,
|
|
74
74
|
* reason } to deny the run with a policy-gate error. */
|
|
75
75
|
readonly policyGate?: (workflow: WorkflowDefinition) => { readonly allow: boolean; readonly reason?: string } | Promise<{ readonly allow: boolean; readonly reason?: string }>;
|
|
76
|
+
/** pi 0.99 classifier hook for classify_route steps: when provided and a
|
|
77
|
+
* classifier model is available, route categories come from
|
|
78
|
+
* ModelRuntime.classify() instead of a spawned classification agent;
|
|
79
|
+
* unavailability and failures fall back to the agent path. */
|
|
80
|
+
readonly classify?: ClassifyHook;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
export async function runWorkflow(opts: RunWorkflowOptions): Promise<RunResult> {
|
|
@@ -136,6 +141,7 @@ export async function runWorkflow(opts: RunWorkflowOptions): Promise<RunResult>
|
|
|
136
141
|
maxPromptBytes: opts.maxPromptBytes ?? DEFAULT_MAX_PROMPT_BYTES,
|
|
137
142
|
degradedStepIds: new Set(),
|
|
138
143
|
allowChildRecursion: opts.allowChildRecursion ?? false,
|
|
144
|
+
classify: opts.classify,
|
|
139
145
|
dispatched: 0,
|
|
140
146
|
};
|
|
141
147
|
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
AgentCallSpec,
|
|
36
36
|
AgentOpts,
|
|
37
37
|
AgentStep,
|
|
38
|
+
ClassifyHook,
|
|
38
39
|
ClassifyRouteStep,
|
|
39
40
|
CodeStep,
|
|
40
41
|
LoopUntilDryStep,
|
|
@@ -81,6 +82,9 @@ export interface StepExecContext {
|
|
|
81
82
|
budgetPolicy: "throw" | "null";
|
|
82
83
|
/** A6: max resolved-prompt byte size; oversize throws a size-limit error. */
|
|
83
84
|
maxPromptBytes: number;
|
|
85
|
+
/** pi 0.99 classifier hook for classify_route (undefined → agent path only).
|
|
86
|
+
* See ClassifyHook / spec workflow-classifier-routing. */
|
|
87
|
+
readonly classify?: ClassifyHook;
|
|
84
88
|
/** A3: step ids that degraded to null under the "null" policy this run. */
|
|
85
89
|
degradedStepIds: Set<string>;
|
|
86
90
|
/** Recursion opt-in propagated to every spawned workflow sub-agent: when
|
|
@@ -555,28 +559,63 @@ async function execTournament(step: TournamentStep, ctx: StepContext, exec: Step
|
|
|
555
559
|
async function execClassifyRoute(step: ClassifyRouteStep, ctx: StepContext, exec: StepExecContext): Promise<StepResult> {
|
|
556
560
|
const start = Date.now();
|
|
557
561
|
const classifyPrompt = await resolvePrompt(step.classifier, ctx);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
562
|
+
|
|
563
|
+
// pi 0.99 classifier path first (spec: workflow-classifier-routing): when a
|
|
564
|
+
// classifier hook is available and answers, the category comes from its
|
|
565
|
+
// ranked choice and no classification subprocess is spawned. Unavailable or
|
|
566
|
+
// failing hook → the original agent path, unchanged (fallback contract).
|
|
567
|
+
let classifyPath: "classifier" | "agent" = "agent";
|
|
568
|
+
let category = "";
|
|
569
|
+
let classifyStats: StepStats = zeroStats;
|
|
570
|
+
if (exec.classify) {
|
|
571
|
+
try {
|
|
572
|
+
const hookResult = await exec.classify(classifyPrompt, Object.keys(step.routes), step.classifier.model);
|
|
573
|
+
if (hookResult) {
|
|
574
|
+
classifyPath = "classifier";
|
|
575
|
+
category = parseCategoryStr(hookResult.category);
|
|
576
|
+
const u = hookResult.usage;
|
|
577
|
+
classifyStats = {
|
|
578
|
+
tokens: u ? u.totalTokens : 0,
|
|
579
|
+
cost: u ? u.cost : 0,
|
|
580
|
+
durationMs: 0,
|
|
581
|
+
agents: 0,
|
|
582
|
+
failures: 0,
|
|
583
|
+
usage: u
|
|
584
|
+
? { input: u.input, output: u.output, cacheRead: u.cacheRead, cacheWrite: u.cacheWrite }
|
|
585
|
+
: undefined,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
// classifier call failed → agent fallback (spec: Fallback to the agent path)
|
|
590
|
+
}
|
|
569
591
|
}
|
|
570
592
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
593
|
+
if (classifyPath === "agent") {
|
|
594
|
+
const classify = await dispatchAgentCall(`${step.id}#classify`, classifyPrompt, step.classifier, exec);
|
|
595
|
+
if (!classify.ok) {
|
|
596
|
+
return stepResult(
|
|
597
|
+
step.id,
|
|
598
|
+
"classify_route",
|
|
599
|
+
classify.aborted ? "skipped" : "failed",
|
|
600
|
+
undefined,
|
|
601
|
+
withDuration(classify.stats, start),
|
|
602
|
+
undefined,
|
|
603
|
+
classify.aborted ? undefined : "dispatch-error",
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// A3: a degraded classifier (budget exhausted under the "null" policy)
|
|
608
|
+
// returns value null — the step degrades to a null result per the README
|
|
609
|
+
// contract; do not fabricate a route run from an empty category.
|
|
610
|
+
if (classify.value === null) {
|
|
611
|
+
return stepResult(step.id, "classify_route", "done", null, withDuration(classify.stats, start));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const parsed = parseFirstJson(classify.value ?? "") as { category?: unknown } | undefined;
|
|
615
|
+
category = parseCategoryStr(parsed?.category);
|
|
616
|
+
classifyStats = classify.stats;
|
|
576
617
|
}
|
|
577
618
|
|
|
578
|
-
const parsed = parseFirstJson(classify.value ?? "") as { category?: unknown } | undefined;
|
|
579
|
-
const category = parseCategoryStr(parsed?.category);
|
|
580
619
|
const routeSteps = step.routes[category] ?? step.fallback ?? [];
|
|
581
620
|
|
|
582
621
|
exec.depth++;
|
|
@@ -592,8 +631,8 @@ async function execClassifyRoute(step: ClassifyRouteStep, ctx: StepContext, exec
|
|
|
592
631
|
step.id,
|
|
593
632
|
"classify_route",
|
|
594
633
|
status,
|
|
595
|
-
{ category, matched: category in step.routes, route: sub.steps, routeStatus: sub.status },
|
|
596
|
-
withDuration(addStats(
|
|
634
|
+
{ category, matched: category in step.routes, route: sub.steps, routeStatus: sub.status, path: classifyPath },
|
|
635
|
+
withDuration(addStats(classifyStats, aggregateStats(sub.steps.map((s) => s.stats), 0)), start),
|
|
597
636
|
undefined,
|
|
598
637
|
sub.errorCategory,
|
|
599
638
|
);
|
package/src/types.ts
CHANGED
|
@@ -49,6 +49,36 @@ export interface Budget {
|
|
|
49
49
|
// Step statistics + result
|
|
50
50
|
// ---------------------------------------------------------------------------
|
|
51
51
|
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// classify_route classifier hook (pi 0.99 ModelRuntime.classify)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/** Result of a successful classifier-path classification for classify_route.
|
|
57
|
+
* `category` is the classifier's chosen route name (may be undeclared — the
|
|
58
|
+
* runner resolves it through fallback sub-steps exactly like the agent path). */
|
|
59
|
+
export interface ClassifyHookResult {
|
|
60
|
+
readonly category: string;
|
|
61
|
+
/** Classifier token usage for truthful step stats; omit when unknown. */
|
|
62
|
+
readonly usage?: {
|
|
63
|
+
readonly input: number;
|
|
64
|
+
readonly output: number;
|
|
65
|
+
readonly cacheRead: number;
|
|
66
|
+
readonly cacheWrite: number;
|
|
67
|
+
readonly totalTokens: number;
|
|
68
|
+
readonly cost: number;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Optional classifier hook injected by the extension entry (spec:
|
|
73
|
+
* workflow-classifier-routing). Returns undefined when no classifier model is
|
|
74
|
+
* available; a throw also falls back to the agent path. Route sub-steps still
|
|
75
|
+
* run as agent dispatches either way. */
|
|
76
|
+
export type ClassifyHook = (
|
|
77
|
+
prompt: string,
|
|
78
|
+
routeNames: readonly string[],
|
|
79
|
+
model?: string,
|
|
80
|
+
) => Promise<ClassifyHookResult | undefined>;
|
|
81
|
+
|
|
52
82
|
export interface StepStats {
|
|
53
83
|
readonly tokens: number;
|
|
54
84
|
readonly cost: number;
|