pi-herdr-agents 0.0.4 → 0.1.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
CHANGED
|
@@ -314,7 +314,14 @@ exact IDs from your authenticated model catalog:
|
|
|
314
314
|
`subagent({ agent: ... })`. Explicit `model` tool arguments take precedence,
|
|
315
315
|
followed by agent frontmatter, per-agent config, the global default, and finally
|
|
316
316
|
the parent model. Model values must be exact authenticated `provider/model-id`
|
|
317
|
-
references.
|
|
317
|
+
references. A value can contain an ordered comma-separated fallback list, for
|
|
318
|
+
example `provider/preferred, provider/fallback`. The extension validates every
|
|
319
|
+
candidate before launch, retries the preferred model normally, then launches
|
|
320
|
+
later candidates only after a provider/agent request failure. A completed child
|
|
321
|
+
result, including a negative task result, never switches models. Completion
|
|
322
|
+
metadata and the status widget report the model actually used; an exhausted
|
|
323
|
+
list reports every attempted model. Workflow metadata accepts one exact model
|
|
324
|
+
only, to keep approved workflow runtimes deterministic.
|
|
318
325
|
|
|
319
326
|
`config.json` is gitignored in the source tree so local overrides are not
|
|
320
327
|
committed from a checkout. On an installed package root, treat it as disposable
|
|
@@ -356,7 +363,7 @@ subagent({
|
|
|
356
363
|
| `agent` | string | — | Load defaults from agent definition |
|
|
357
364
|
| `fork` | boolean | `false` | Force the full-context fork mode for this spawn, overriding any agent `session-mode` frontmatter |
|
|
358
365
|
| `interactive` | boolean | derived | Mark this spawn as interactive (don't wake the parent on stall/recovery). Defaults to the agent's `interactive` frontmatter, otherwise the inverse of `auto-exit`. |
|
|
359
|
-
| `model` | string | configured or parent | Exact authenticated `provider/model-id
|
|
366
|
+
| `model` | string | configured or parent | Exact authenticated `provider/model-id`, or an ordered comma-separated Pi fallback list; unavailable for Claude CLI and worktree spawns. Resolution is tool argument → agent frontmatter → per-agent config → global config → parent |
|
|
360
367
|
| `thinking` | string | parent level | Pi thinking level (`off` through `max`); omit to inherit the parent |
|
|
361
368
|
| `systemPrompt` | string | — | Role/system-prompt text for a bare spawn; overrides the body for Claude CLI agents, while named Pi agents keep their definition body |
|
|
362
369
|
| `resumeSessionId` | string | — | Claude CLI session ID to resume; separate from the Pi `subagent_resume` tool |
|
|
@@ -683,7 +690,7 @@ and verify them with `/subagent list` plus a smoke launch.
|
|
|
683
690
|
| ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
684
691
|
| `name` | string | Optional explicit agent name used in `agent: "my-agent"`; defaults to the filename stem and must match it in role packs |
|
|
685
692
|
| `description` | string | Shown in `subagents_list` output |
|
|
686
|
-
| `model` | string | Optional exact authenticated Pi model default; omit to use per-agent config, global config, then the parent
|
|
693
|
+
| `model` | string | Optional exact authenticated Pi model default or ordered comma-separated fallback list; omit to use per-agent config, global config, then the parent |
|
|
687
694
|
| `cli` | string | Set to `claude` to launch the Claude CLI instead of Pi |
|
|
688
695
|
| `cli-model` | string | Optional model name passed to a Claude CLI agent; separate from Pi model routing |
|
|
689
696
|
| `thinking` | string | Optional Pi thinking default (`off` through `max`); omit to inherit the parent. Thinking overrides are not supported for Claude CLI agents |
|
|
@@ -46,7 +46,7 @@ For a worktree launch:
|
|
|
46
46
|
- Uncommitted and untracked files from the parent checkout are not copied. Commit anything the child must see before spawning it, or pass the needed context in the task.
|
|
47
47
|
- Worktree creation does not steal terminal focus.
|
|
48
48
|
|
|
49
|
-
`worktree` cannot be set in agent frontmatter and is not exposed by the `/subagent <agent> <task>` shorthand. It is selected per call to the `subagent` tool.
|
|
49
|
+
`worktree` cannot be set in agent frontmatter and is not exposed by the `/subagent <agent> <task>` shorthand. It is selected per call to the `subagent` tool. Ordered model fallback lists are not supported for worktree subagents: a failed attempt retains its worktree and branch for review, so a retry cannot safely reuse the requested branch.
|
|
50
50
|
|
|
51
51
|
## Parent and worker responsibilities
|
|
52
52
|
|
package/package.json
CHANGED
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
import {
|
|
54
54
|
buildAuthenticatedModelCatalog,
|
|
55
55
|
resolveRuntimePlan,
|
|
56
|
+
resolveRuntimePlans,
|
|
56
57
|
wrapPiModelRegistry,
|
|
57
58
|
THINKING_LEVELS,
|
|
58
59
|
isThinkingLevel,
|
|
@@ -202,7 +203,7 @@ const SubagentParams = Type.Object({
|
|
|
202
203
|
model: Type.Optional(
|
|
203
204
|
Type.String({
|
|
204
205
|
description:
|
|
205
|
-
"Exact authenticated provider/model-id. Omit to inherit the parent model.
|
|
206
|
+
"Exact authenticated provider/model-id, or an ordered comma-separated fallback list. Omit to inherit the parent model. Fallbacks are Pi-backed only and cannot be used with worktrees.",
|
|
206
207
|
}),
|
|
207
208
|
),
|
|
208
209
|
thinking: Type.Optional(ThinkingLevelSchema),
|
|
@@ -1147,6 +1148,7 @@ function resolveResultPresentation(
|
|
|
1147
1148
|
| "summary"
|
|
1148
1149
|
| "sessionFile"
|
|
1149
1150
|
| "errorMessage"
|
|
1151
|
+
| "fallbackAttempts"
|
|
1150
1152
|
| "worktree"
|
|
1151
1153
|
>,
|
|
1152
1154
|
name: string,
|
|
@@ -1173,6 +1175,9 @@ function resolveResultPresentation(
|
|
|
1173
1175
|
: `Sub-agent "${name}" completed (${formatElapsed(result.elapsed)}).\n\n${result.summary}`;
|
|
1174
1176
|
}
|
|
1175
1177
|
|
|
1178
|
+
if (result.fallbackAttempts && result.fallbackAttempts.length > 1) {
|
|
1179
|
+
body += `\n\nModels attempted: ${result.fallbackAttempts.join(", ")}`;
|
|
1180
|
+
}
|
|
1176
1181
|
if (result.worktree) body += `\n\n${formatWorktreeHandoff(result.worktree)}`;
|
|
1177
1182
|
const runtimeWarning = runtimeMismatch
|
|
1178
1183
|
? `\n\nRuntime warning: ${runtimeMismatch}`
|
|
@@ -1215,6 +1220,8 @@ interface SubagentResult {
|
|
|
1215
1220
|
error?: string;
|
|
1216
1221
|
/** Provider/agent error message when auto-retry exhausted (overload, rate limit, etc.). */
|
|
1217
1222
|
errorMessage?: string;
|
|
1223
|
+
/** Ordered models launched for this run, including failed fallback attempts. */
|
|
1224
|
+
fallbackAttempts?: string[];
|
|
1218
1225
|
ping?: { name: string; message: string };
|
|
1219
1226
|
worktree?: WorktreeHandoff;
|
|
1220
1227
|
}
|
|
@@ -2045,10 +2052,10 @@ async function launchSubagent(
|
|
|
2045
2052
|
};
|
|
2046
2053
|
},
|
|
2047
2054
|
parentThinking: ThinkingLevel,
|
|
2048
|
-
options?: { surface?: string },
|
|
2055
|
+
options?: { surface?: string; runtimePlan?: ResolvedRuntimePlan; id?: string },
|
|
2049
2056
|
): Promise<RunningSubagent> {
|
|
2050
2057
|
const startTime = Date.now();
|
|
2051
|
-
const id = Math.random().toString(16).slice(2, 10);
|
|
2058
|
+
const id = options?.id ?? Math.random().toString(16).slice(2, 10);
|
|
2052
2059
|
|
|
2053
2060
|
const agentDefs = params.agent
|
|
2054
2061
|
? loadAgentDefaults(params.agent, runtime.pi)
|
|
@@ -2064,19 +2071,21 @@ async function launchSubagent(
|
|
|
2064
2071
|
requireClaudeAdapter(agentDefs?.cli);
|
|
2065
2072
|
if (!ctx.model)
|
|
2066
2073
|
throw new Error("Subagent launch requires a resolved parent model");
|
|
2067
|
-
const runtimePlan =
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
model:
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2074
|
+
const runtimePlan =
|
|
2075
|
+
options?.runtimePlan ??
|
|
2076
|
+
resolveRuntimePlan(
|
|
2077
|
+
{ model: params.model, thinking: params.thinking },
|
|
2078
|
+
{
|
|
2079
|
+
model: resolveModelDefault(params.agent, agentDefs?.model, modelConfig),
|
|
2080
|
+
thinking: agentDefs?.thinking,
|
|
2081
|
+
},
|
|
2082
|
+
{
|
|
2083
|
+
provider: ctx.model.provider,
|
|
2084
|
+
modelId: ctx.model.id,
|
|
2085
|
+
thinking: parentThinking,
|
|
2086
|
+
},
|
|
2087
|
+
wrapPiModelRegistry(ctx.modelRegistry),
|
|
2088
|
+
);
|
|
2080
2089
|
const effectiveModel = runtimePlan.model;
|
|
2081
2090
|
const effectiveTools = params.tools ?? agentDefs?.tools;
|
|
2082
2091
|
const effectiveSkills = params.skills ?? agentDefs?.skills;
|
|
@@ -2484,6 +2493,67 @@ async function launchSubagent(
|
|
|
2484
2493
|
* the summary from the session file, and closes ordinary panes. Worktree
|
|
2485
2494
|
* workspaces are retained for parent review.
|
|
2486
2495
|
*/
|
|
2496
|
+
function resolveSubagentRuntimePlans(
|
|
2497
|
+
params: typeof SubagentParams.static,
|
|
2498
|
+
ctx: Parameters<typeof launchSubagent>[1],
|
|
2499
|
+
parentThinking: ThinkingLevel,
|
|
2500
|
+
): ResolvedRuntimePlan[] {
|
|
2501
|
+
const agentDefs = params.agent
|
|
2502
|
+
? loadAgentDefaults(params.agent, runtime.pi)
|
|
2503
|
+
: null;
|
|
2504
|
+
if (params.agent && !agentDefs) {
|
|
2505
|
+
const diagnostic = discoverAgentCatalog(runtime.pi).diagnostics.find(
|
|
2506
|
+
(candidate) => candidate.agentName === params.agent,
|
|
2507
|
+
);
|
|
2508
|
+
throw new Error(
|
|
2509
|
+
diagnostic?.message ?? `Agent "${params.agent}" was not found.`,
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
if (!ctx.model) throw new Error("Subagent launch requires a resolved parent model");
|
|
2513
|
+
const plans = resolveRuntimePlans(
|
|
2514
|
+
{ model: params.model, thinking: params.thinking },
|
|
2515
|
+
{
|
|
2516
|
+
model: resolveModelDefault(params.agent, agentDefs?.model, modelConfig),
|
|
2517
|
+
thinking: agentDefs?.thinking,
|
|
2518
|
+
},
|
|
2519
|
+
{
|
|
2520
|
+
provider: ctx.model.provider,
|
|
2521
|
+
modelId: ctx.model.id,
|
|
2522
|
+
thinking: parentThinking,
|
|
2523
|
+
},
|
|
2524
|
+
wrapPiModelRegistry(ctx.modelRegistry),
|
|
2525
|
+
);
|
|
2526
|
+
if (agentDefs?.cli === "claude" && plans.length > 1) {
|
|
2527
|
+
throw new Error("Model fallbacks are supported only for Pi-backed subagents.");
|
|
2528
|
+
}
|
|
2529
|
+
if (params.worktree && plans.length > 1) {
|
|
2530
|
+
throw new Error("Model fallbacks are not supported for worktree subagents.");
|
|
2531
|
+
}
|
|
2532
|
+
return plans;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
async function launchSubagentWithFallbacks(
|
|
2536
|
+
params: typeof SubagentParams.static,
|
|
2537
|
+
ctx: Parameters<typeof launchSubagent>[1],
|
|
2538
|
+
parentThinking: ThinkingLevel,
|
|
2539
|
+
plans: ResolvedRuntimePlan[],
|
|
2540
|
+
): Promise<{ running: RunningSubagent; index: number }> {
|
|
2541
|
+
const failures: string[] = [];
|
|
2542
|
+
for (const [index, plan] of plans.entries()) {
|
|
2543
|
+
try {
|
|
2544
|
+
return {
|
|
2545
|
+
running: await launchSubagent(params, ctx, parentThinking, { runtimePlan: plan }),
|
|
2546
|
+
index,
|
|
2547
|
+
};
|
|
2548
|
+
} catch (error) {
|
|
2549
|
+
failures.push(`${plan.model}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
throw new Error(
|
|
2553
|
+
`Subagent could not launch with any configured model. Attempted: ${plans.map((plan) => plan.model).join(", ")}. ${failures.join("; ")}`,
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2487
2557
|
async function watchSubagent(
|
|
2488
2558
|
running: RunningSubagent,
|
|
2489
2559
|
signal: AbortSignal,
|
|
@@ -2676,6 +2746,64 @@ async function watchSubagent(
|
|
|
2676
2746
|
}
|
|
2677
2747
|
}
|
|
2678
2748
|
|
|
2749
|
+
async function watchSubagentWithFallbacks(
|
|
2750
|
+
initial: RunningSubagent,
|
|
2751
|
+
initialPlanIndex: number,
|
|
2752
|
+
params: typeof SubagentParams.static,
|
|
2753
|
+
ctx: Parameters<typeof launchSubagent>[1],
|
|
2754
|
+
parentThinking: ThinkingLevel,
|
|
2755
|
+
plans: ResolvedRuntimePlan[],
|
|
2756
|
+
signal: AbortSignal,
|
|
2757
|
+
): Promise<{ running: RunningSubagent; result: SubagentResult }> {
|
|
2758
|
+
let running = initial;
|
|
2759
|
+
let nextPlan = initialPlanIndex + 1;
|
|
2760
|
+
const attempts = [running.runtimePlan?.model].filter(
|
|
2761
|
+
(model): model is string => !!model,
|
|
2762
|
+
);
|
|
2763
|
+
|
|
2764
|
+
for (;;) {
|
|
2765
|
+
const result = await watchSubagent(running, signal);
|
|
2766
|
+
const shouldRetry = !!result.errorMessage && nextPlan < plans.length;
|
|
2767
|
+
if (!shouldRetry) {
|
|
2768
|
+
return { running, result: { ...result, fallbackAttempts: attempts } };
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
runningSubagents.delete(running.id);
|
|
2772
|
+
updateWidget();
|
|
2773
|
+
const launchErrors: string[] = [];
|
|
2774
|
+
let launchedFallback = false;
|
|
2775
|
+
while (nextPlan < plans.length) {
|
|
2776
|
+
const plan = plans[nextPlan++];
|
|
2777
|
+
attempts.push(plan.model);
|
|
2778
|
+
try {
|
|
2779
|
+
running = await launchSubagent(params, ctx, parentThinking, {
|
|
2780
|
+
runtimePlan: plan,
|
|
2781
|
+
id: initial.id,
|
|
2782
|
+
});
|
|
2783
|
+
running.abortController = initial.abortController;
|
|
2784
|
+
launchedFallback = true;
|
|
2785
|
+
startWidgetRefresh();
|
|
2786
|
+
startStatusRefresh(runtime.pi!);
|
|
2787
|
+
break;
|
|
2788
|
+
} catch (error) {
|
|
2789
|
+
launchErrors.push(
|
|
2790
|
+
`${plan.model}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (!launchedFallback) {
|
|
2795
|
+
return {
|
|
2796
|
+
running,
|
|
2797
|
+
result: {
|
|
2798
|
+
...result,
|
|
2799
|
+
errorMessage: `${result.errorMessage}\n\nFallback launch failures: ${launchErrors.join("; ")}`,
|
|
2800
|
+
fallbackAttempts: attempts,
|
|
2801
|
+
},
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2679
2807
|
export default function subagentsExtension(pi: ExtensionAPI) {
|
|
2680
2808
|
runtime.pi = pi;
|
|
2681
2809
|
let btwChild: BtwChild | undefined;
|
|
@@ -3604,7 +3732,17 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
3604
3732
|
`Unsupported parent thinking level: ${parentThinking}`,
|
|
3605
3733
|
);
|
|
3606
3734
|
}
|
|
3607
|
-
const
|
|
3735
|
+
const runtimePlans = resolveSubagentRuntimePlans(
|
|
3736
|
+
params,
|
|
3737
|
+
ctx,
|
|
3738
|
+
parentThinking,
|
|
3739
|
+
);
|
|
3740
|
+
const { running, index: initialPlanIndex } = await launchSubagentWithFallbacks(
|
|
3741
|
+
params,
|
|
3742
|
+
ctx,
|
|
3743
|
+
parentThinking,
|
|
3744
|
+
runtimePlans,
|
|
3745
|
+
);
|
|
3608
3746
|
|
|
3609
3747
|
// Create a separate AbortController for the watcher
|
|
3610
3748
|
// (the tool's signal completes when we return)
|
|
@@ -3616,16 +3754,24 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
3616
3754
|
startStatusRefresh(pi);
|
|
3617
3755
|
|
|
3618
3756
|
// Fire-and-forget: start watching in background
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3757
|
+
watchSubagentWithFallbacks(
|
|
3758
|
+
running,
|
|
3759
|
+
initialPlanIndex,
|
|
3760
|
+
params,
|
|
3761
|
+
ctx,
|
|
3762
|
+
parentThinking,
|
|
3763
|
+
runtimePlans,
|
|
3764
|
+
watcherAbort.signal,
|
|
3765
|
+
)
|
|
3766
|
+
.then(({ running: completedRunning, result }) => {
|
|
3767
|
+
if (!shouldDeliverSubagentCompletion(completedRunning)) {
|
|
3768
|
+
completedRunning.lifecycle = markDelivery(completedRunning.lifecycle, "suppressed");
|
|
3769
|
+
runningSubagents.delete(completedRunning.id);
|
|
3624
3770
|
updateWidget();
|
|
3625
3771
|
return;
|
|
3626
3772
|
}
|
|
3627
|
-
|
|
3628
|
-
runningSubagents.delete(
|
|
3773
|
+
completedRunning.lifecycle = markDelivery(completedRunning.lifecycle, "delivered");
|
|
3774
|
+
runningSubagents.delete(completedRunning.id);
|
|
3629
3775
|
updateWidget();
|
|
3630
3776
|
const completionApi = selectCompletionApi(pi, runtime.pi);
|
|
3631
3777
|
|
|
@@ -3655,26 +3801,29 @@ export default function subagentsExtension(pi: ExtensionAPI) {
|
|
|
3655
3801
|
|
|
3656
3802
|
const presentation = resolveResultPresentation(
|
|
3657
3803
|
result,
|
|
3658
|
-
|
|
3659
|
-
|
|
3804
|
+
completedRunning.name,
|
|
3805
|
+
completedRunning.runtimePlan?.runtimeMismatch,
|
|
3660
3806
|
);
|
|
3661
3807
|
|
|
3662
3808
|
sendSubagentResult(completionApi, presentation, {
|
|
3663
|
-
name:
|
|
3664
|
-
task:
|
|
3665
|
-
agent:
|
|
3809
|
+
name: completedRunning.name,
|
|
3810
|
+
task: completedRunning.task,
|
|
3811
|
+
agent: completedRunning.agent,
|
|
3666
3812
|
exitCode: result.exitCode,
|
|
3667
3813
|
elapsed: result.elapsed,
|
|
3668
3814
|
sessionFile: result.sessionFile,
|
|
3669
3815
|
...(result.errorMessage
|
|
3670
3816
|
? { errorMessage: result.errorMessage }
|
|
3671
3817
|
: {}),
|
|
3818
|
+
...(result.fallbackAttempts
|
|
3819
|
+
? { fallbackAttempts: result.fallbackAttempts }
|
|
3820
|
+
: {}),
|
|
3672
3821
|
...(result.claudeSessionId
|
|
3673
3822
|
? { claudeSessionId: result.claudeSessionId }
|
|
3674
3823
|
: {}),
|
|
3675
3824
|
...(result.worktree ? { worktree: result.worktree } : {}),
|
|
3676
|
-
...(
|
|
3677
|
-
? { runtimePlan:
|
|
3825
|
+
...(completedRunning.runtimePlan
|
|
3826
|
+
? { runtimePlan: completedRunning.runtimePlan }
|
|
3678
3827
|
: {}),
|
|
3679
3828
|
});
|
|
3680
3829
|
})
|
|
@@ -190,6 +190,16 @@ function selectField(
|
|
|
190
190
|
return { source: "parent" };
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
export function parseModelFallbacks(reference: string): string[] {
|
|
194
|
+
const candidates = reference.split(",").map((candidate) => candidate.trim());
|
|
195
|
+
if (candidates.some((candidate) => candidate === "")) {
|
|
196
|
+
throw new RuntimeResolutionError(
|
|
197
|
+
`model fallback list ${JSON.stringify(reference)} cannot contain an empty candidate`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return candidates;
|
|
201
|
+
}
|
|
202
|
+
|
|
193
203
|
export function resolveRuntimePlan(
|
|
194
204
|
request: RuntimeRequest,
|
|
195
205
|
agentDefaults: RuntimeRequest,
|
|
@@ -272,6 +282,30 @@ export function resolveRuntimePlan(
|
|
|
272
282
|
};
|
|
273
283
|
}
|
|
274
284
|
|
|
285
|
+
/** Resolve every configured fallback before launching the first child. */
|
|
286
|
+
export function resolveRuntimePlans(
|
|
287
|
+
request: RuntimeRequest,
|
|
288
|
+
agentDefaults: RuntimeRequest,
|
|
289
|
+
parent: ParentRuntime,
|
|
290
|
+
registry: ModelRegistryAdapter,
|
|
291
|
+
): ResolvedRuntimePlan[] {
|
|
292
|
+
const selection = selectField(request.model, agentDefaults.model);
|
|
293
|
+
if (!selection.value) {
|
|
294
|
+
return [resolveRuntimePlan(request, agentDefaults, parent, registry)];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return parseModelFallbacks(selection.value).map((model) =>
|
|
298
|
+
resolveRuntimePlan(
|
|
299
|
+
selection.source === "request" ? { ...request, model } : { ...request, model: undefined },
|
|
300
|
+
selection.source === "agent"
|
|
301
|
+
? { ...agentDefaults, model }
|
|
302
|
+
: agentDefaults,
|
|
303
|
+
parent,
|
|
304
|
+
registry,
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
275
309
|
function formatTokenCount(value: number | undefined): string | undefined {
|
|
276
310
|
if (!value || value <= 0) return undefined;
|
|
277
311
|
if (value >= 1_000_000) return `${Number((value / 1_000_000).toFixed(1))}m`;
|