pi-spark 0.23.1 → 0.24.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/CHANGELOG.md +16 -0
- package/package.json +9 -9
- package/src/features/credits/index.ts +4 -0
- package/src/features/credits/providers/deepseek.ts +12 -12
- package/src/features/credits/providers/openai-codex-panel.ts +3 -1
- package/src/features/credits/status.ts +3 -2
- package/src/features/editor/index.ts +22 -13
- package/src/features/footer/index.ts +3 -3
- package/src/features/presets/selector.ts +2 -0
- package/src/features/recap/index.ts +4 -0
- package/src/features/write/index.ts +4 -15
- package/src/features/write/path.ts +3 -3
- package/src/utils/format.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.24.0](https://github.com/zlliang/pi-spark/compare/v0.23.1...v0.24.0) (2026-09-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* adopt Pi 0.85 extension APIs
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* adopt Pi 0.85 extension APIs ([2cd3208](https://github.com/zlliang/pi-spark/commit/2cd3208fe5bf8aade7828720ae0397da6836fe99))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* capitalize DeepSeek pricing labels ([fffcc40](https://github.com/zlliang/pi-spark/commit/fffcc40c8664056fb54d3c5fea5853f3805a2e4f))
|
|
18
|
+
|
|
3
19
|
## [0.23.1](https://github.com/zlliang/pi-spark/compare/v0.23.0...v0.23.1) (2026-09-12)
|
|
4
20
|
|
|
5
21
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-spark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Pi package that polishes your daily experience",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-coding-agent",
|
|
@@ -44,17 +44,17 @@
|
|
|
44
44
|
"zod": "^4.4.3"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@earendil-works/pi-agent-core": ">=0.
|
|
48
|
-
"@earendil-works/pi-ai": ">=0.
|
|
49
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
50
|
-
"@earendil-works/pi-tui": ">=0.
|
|
47
|
+
"@earendil-works/pi-agent-core": ">=0.85.0",
|
|
48
|
+
"@earendil-works/pi-ai": ">=0.85.0",
|
|
49
|
+
"@earendil-works/pi-coding-agent": ">=0.85.0",
|
|
50
|
+
"@earendil-works/pi-tui": ">=0.85.0",
|
|
51
51
|
"typebox": "*"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@earendil-works/pi-agent-core": ">=0.
|
|
55
|
-
"@earendil-works/pi-ai": ">=0.
|
|
56
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
57
|
-
"@earendil-works/pi-tui": ">=0.
|
|
54
|
+
"@earendil-works/pi-agent-core": ">=0.85.0",
|
|
55
|
+
"@earendil-works/pi-ai": ">=0.85.0",
|
|
56
|
+
"@earendil-works/pi-coding-agent": ">=0.85.0",
|
|
57
|
+
"@earendil-works/pi-tui": ">=0.85.0",
|
|
58
58
|
"@types/node": "*",
|
|
59
59
|
"typebox": "*",
|
|
60
60
|
"typescript": "^7.0.2"
|
|
@@ -50,6 +50,10 @@ export function registerCredits(pi: ExtensionAPI): void {
|
|
|
50
50
|
creditsManager?.refresh(ctx);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
pi.on("session_compact_failed", (_event, ctx) => {
|
|
54
|
+
creditsManager?.refresh(ctx);
|
|
55
|
+
});
|
|
56
|
+
|
|
53
57
|
pi.on("session_before_tree", (event, ctx) => {
|
|
54
58
|
if (!event.preparation.userWantsSummary) return;
|
|
55
59
|
|
|
@@ -25,34 +25,34 @@ export const deepseekProvider: CreditsProvider = {
|
|
|
25
25
|
pricingRules: [
|
|
26
26
|
{
|
|
27
27
|
model: "deepseek-flash",
|
|
28
|
-
defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "
|
|
28
|
+
defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "Off-peak" },
|
|
29
29
|
windows: [
|
|
30
|
-
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "
|
|
31
|
-
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "
|
|
30
|
+
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "Peak" } },
|
|
31
|
+
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "Peak" } },
|
|
32
32
|
],
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
model: "deepseek-v4-flash",
|
|
36
|
-
defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "
|
|
36
|
+
defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "Off-peak" },
|
|
37
37
|
windows: [
|
|
38
|
-
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "
|
|
39
|
-
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "
|
|
38
|
+
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "Peak" } },
|
|
39
|
+
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "Peak" } },
|
|
40
40
|
],
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
model: "deepseek-v4-flash-vision-exp",
|
|
44
|
-
defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "
|
|
44
|
+
defaultCost: { input: 0.15, output: 0.6, cacheRead: 0.003, cacheWrite: 0, label: "Off-peak" },
|
|
45
45
|
windows: [
|
|
46
|
-
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "
|
|
47
|
-
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "
|
|
46
|
+
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "Peak" } },
|
|
47
|
+
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0, label: "Peak" } },
|
|
48
48
|
],
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
model: "deepseek-v4-pro",
|
|
52
|
-
defaultCost: { input: 0.66, output: 1.98, cacheRead: 0.022, cacheWrite: 0, label: "
|
|
52
|
+
defaultCost: { input: 0.66, output: 1.98, cacheRead: 0.022, cacheWrite: 0, label: "Off-peak" },
|
|
53
53
|
windows: [
|
|
54
|
-
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0, label: "
|
|
55
|
-
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0, label: "
|
|
54
|
+
{ start: "01:00", end: "04:00", days: [1, 2, 3, 4, 5], cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0, label: "Peak" } },
|
|
55
|
+
{ start: "06:00", end: "10:00", days: [1, 2, 3, 4, 5], cost: { input: 1.32, output: 3.96, cacheRead: 0.044, cacheWrite: 0, label: "Peak" } },
|
|
56
56
|
],
|
|
57
57
|
},
|
|
58
58
|
],
|
|
@@ -5,7 +5,7 @@ import { Loader } from "../../../components/loader";
|
|
|
5
5
|
import { renderCredits } from "../status";
|
|
6
6
|
|
|
7
7
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import type { SelectItem } from "@earendil-works/pi-tui";
|
|
8
|
+
import type { SelectItem, TuiMouseEvent } from "@earendil-works/pi-tui";
|
|
9
9
|
import type { BankedRateLimitReset, RateLimitResetCreditsResponse } from "./openai-codex";
|
|
10
10
|
import type { Credits } from "../types";
|
|
11
11
|
|
|
@@ -113,6 +113,7 @@ export async function showCodexResetsSelector(ctx: ExtensionContext, load: (sign
|
|
|
113
113
|
finish(null);
|
|
114
114
|
}
|
|
115
115
|
},
|
|
116
|
+
handleMouse: (event: TuiMouseEvent) => container.handleMouse(event),
|
|
116
117
|
};
|
|
117
118
|
});
|
|
118
119
|
|
|
@@ -170,6 +171,7 @@ export async function confirmCodexReset(ctx: ExtensionContext, credit: BankedRat
|
|
|
170
171
|
selectList.handleInput(data);
|
|
171
172
|
tui.requestRender();
|
|
172
173
|
},
|
|
174
|
+
handleMouse: (event: TuiMouseEvent) => container.handleMouse(event),
|
|
173
175
|
};
|
|
174
176
|
});
|
|
175
177
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { hyperlink } from "@earendil-works/pi-tui";
|
|
2
1
|
import prettyMilliseconds from "pretty-ms";
|
|
3
2
|
|
|
3
|
+
import { formatLink } from "../../utils/format";
|
|
4
|
+
|
|
4
5
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import type { Credits, CreditsLane } from "./types";
|
|
6
7
|
|
|
@@ -15,7 +16,7 @@ export function renderCredits(theme: Theme, label: string, credits: Credits, lin
|
|
|
15
16
|
const suffix = credits.suffix ? ` ${theme.fg("dim", credits.suffix)}` : "";
|
|
16
17
|
|
|
17
18
|
const text = `${styledLabel}${value}${suffix}`;
|
|
18
|
-
return link ?
|
|
19
|
+
return link ? formatLink(text, link) : text;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function renderError(theme: Theme, label: string, message: string): string {
|
|
@@ -43,27 +43,28 @@ class Editor extends CustomEditor {
|
|
|
43
43
|
this.tui.requestRender();
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
render(width: number): string[] {
|
|
46
|
+
override render(width: number): string[] {
|
|
47
47
|
// Pi reapplies the thinking-level border color when the level changes. Use the dim color here
|
|
48
|
-
// while preserving Bash mode's dedicated border color.
|
|
48
|
+
// while preserving Bash mode's dedicated border color. Set it before rendering, since the
|
|
49
|
+
// border helpers below run inside `super.render()`.
|
|
49
50
|
if (this.thinkingLevelIndicator === "model" && !this.getText().trimStart().startsWith("!")) {
|
|
50
51
|
this.borderColor = (text) => this.ctx.ui.theme.fg("dim", text);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
return super.render(width);
|
|
55
|
+
}
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
protected override renderTopBorder(width: number, hiddenLineCount: number): string {
|
|
58
|
+
return this.renderBorder(width, this.getTopLeft(hiddenLineCount), this.getTopRight());
|
|
59
|
+
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
protected override renderBottomBorder(width: number, hiddenLineCount: number): string {
|
|
62
|
+
return this.renderBorder(width, this.getScrollHint("↓", hiddenLineCount), "");
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
private
|
|
65
|
+
private renderBorder(width: number, left: string, right: string): string {
|
|
62
66
|
const theme = this.ctx.ui.theme;
|
|
63
67
|
|
|
64
|
-
const left = this.getLeft();
|
|
65
|
-
const right = this.getRight();
|
|
66
|
-
|
|
67
68
|
return new SplitLine(left, right, {
|
|
68
69
|
padding: 1,
|
|
69
70
|
innerPadding: 1,
|
|
@@ -72,16 +73,17 @@ class Editor extends CustomEditor {
|
|
|
72
73
|
}).render(width)[0];
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
private
|
|
76
|
+
private getTopLeft(hiddenLineCount: number): string {
|
|
76
77
|
const theme = this.ctx.ui.theme;
|
|
77
78
|
|
|
78
79
|
const spinner = this.spinner.getFrame();
|
|
79
80
|
const workingMessage = this.workingMessage;
|
|
81
|
+
const workingText = [spinner ? theme.fg("accent", spinner) : undefined, workingMessage ? theme.fg("dim", workingMessage) : undefined].filter(Boolean).join(" ");
|
|
80
82
|
|
|
81
|
-
return [
|
|
83
|
+
return [this.getScrollHint("↑", hiddenLineCount), workingText].filter(Boolean).join(theme.fg("dim", " · "));
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
private
|
|
86
|
+
private getTopRight(): string {
|
|
85
87
|
const theme = this.ctx.ui.theme;
|
|
86
88
|
|
|
87
89
|
const modelBeforeText = this.slots.modelBefore;
|
|
@@ -91,6 +93,13 @@ class Editor extends CustomEditor {
|
|
|
91
93
|
return [modelBeforeText ? theme.fg("dim", modelBeforeText) : undefined, coloredModelText].filter(Boolean).join(theme.fg("dim", " · "));
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
/** Renders Pi's hidden-line hint, which this editor keeps on the left side of the border. */
|
|
97
|
+
private getScrollHint(direction: "↑" | "↓", hiddenLineCount: number): string {
|
|
98
|
+
if (hiddenLineCount <= 0) return "";
|
|
99
|
+
|
|
100
|
+
return this.ctx.ui.theme.fg("dim", `${direction} ${hiddenLineCount} more`);
|
|
101
|
+
}
|
|
102
|
+
|
|
94
103
|
private withThinkingLevelColor(text: string): string {
|
|
95
104
|
const theme = this.ctx.ui.theme;
|
|
96
105
|
const thinkingLevel = this.pi.getThinkingLevel();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
-
import {
|
|
4
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
5
5
|
|
|
6
6
|
import { SplitLine } from "../../components/split-line";
|
|
7
7
|
import { loadConfig } from "../../config";
|
|
8
|
-
import { formatContextUsage, formatCwd, sanitizeText } from "../../utils/format";
|
|
8
|
+
import { formatContextUsage, formatCwd, formatLink, sanitizeText } from "../../utils/format";
|
|
9
9
|
import { getEntryUsage } from "../../utils/usage";
|
|
10
10
|
|
|
11
11
|
import type { ExtensionContext, ExtensionAPI, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
@@ -48,7 +48,7 @@ class FooterComponent implements Component {
|
|
|
48
48
|
private getLeft(): string {
|
|
49
49
|
const cwd = this.ctx.sessionManager.getCwd();
|
|
50
50
|
const url = pathToFileURL(resolve(cwd));
|
|
51
|
-
const cwdText =
|
|
51
|
+
const cwdText = formatLink(formatCwd(cwd, homedir()), url.href);
|
|
52
52
|
const branch = this.footerData.getGitBranch();
|
|
53
53
|
const sessionName = this.ctx.sessionManager.getSessionName();
|
|
54
54
|
|
|
@@ -2,6 +2,7 @@ import { keyHint, rawKeyHint, DynamicBorder } from "@earendil-works/pi-coding-ag
|
|
|
2
2
|
import { Box, Container, SelectList, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import type { TuiMouseEvent } from "@earendil-works/pi-tui";
|
|
5
6
|
import type { PresetManager } from "./manager";
|
|
6
7
|
|
|
7
8
|
export async function showPresetSelector(ctx: ExtensionContext, presetManager: PresetManager): Promise<string | undefined> {
|
|
@@ -51,6 +52,7 @@ export async function showPresetSelector(ctx: ExtensionContext, presetManager: P
|
|
|
51
52
|
selectList.handleInput(data);
|
|
52
53
|
tui.requestRender();
|
|
53
54
|
},
|
|
55
|
+
handleMouse: (event: TuiMouseEvent) => container.handleMouse(event),
|
|
54
56
|
};
|
|
55
57
|
});
|
|
56
58
|
|
|
@@ -56,6 +56,10 @@ export function registerRecap(pi: ExtensionAPI): void {
|
|
|
56
56
|
idleListener?.watch(ctx);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
pi.on("session_compact_failed", (_event, ctx) => {
|
|
60
|
+
idleListener?.watch(ctx);
|
|
61
|
+
});
|
|
62
|
+
|
|
59
63
|
pi.on("session_tree", (_event, ctx) => {
|
|
60
64
|
idleListener?.watch(ctx);
|
|
61
65
|
});
|
|
@@ -139,29 +139,18 @@ function formatWriteCall(path: string | null, content: string | null, highlighte
|
|
|
139
139
|
|
|
140
140
|
export function registerWrite(pi: ExtensionAPI): void {
|
|
141
141
|
let enabled = true;
|
|
142
|
-
const toolCache = new Map<string, ReturnType<typeof createWriteToolDefinition>>();
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (!tool) {
|
|
147
|
-
tool = createWriteToolDefinition(cwd);
|
|
148
|
-
toolCache.set(cwd, tool);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return tool;
|
|
152
|
-
};
|
|
143
|
+
// Pi resolves paths against `ctx.cwd`, falling back to this cwd only when neither is provided.
|
|
144
|
+
const writeTool = createWriteToolDefinition(process.cwd());
|
|
153
145
|
|
|
154
146
|
pi.on("session_start", (_event, ctx) => {
|
|
155
147
|
enabled = loadConfig(ctx).write !== false;
|
|
156
148
|
});
|
|
157
149
|
|
|
158
150
|
pi.registerTool({
|
|
159
|
-
...
|
|
160
|
-
execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
161
|
-
return getWriteTool(ctx.cwd).execute(toolCallId, params, signal, onUpdate, ctx);
|
|
162
|
-
},
|
|
151
|
+
...writeTool,
|
|
163
152
|
renderCall(args, theme, context) {
|
|
164
|
-
if (!enabled) return
|
|
153
|
+
if (!enabled) return writeTool.renderCall!(args, theme, context);
|
|
165
154
|
|
|
166
155
|
const renderArgs = args as { path?: string; file_path?: string; content?: string } | undefined;
|
|
167
156
|
const path = parseArg(renderArgs?.file_path ?? renderArgs?.path);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { formatLink } from "../../utils/format";
|
|
5
6
|
|
|
6
7
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
7
8
|
|
|
@@ -12,9 +13,8 @@ export function renderPath(path: string | null, cwd: string, theme: Theme): stri
|
|
|
12
13
|
const home = homedir();
|
|
13
14
|
const displayPath = path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
14
15
|
const styledPath = theme.fg("accent", displayPath);
|
|
15
|
-
if (!getCapabilities().hyperlinks) return styledPath;
|
|
16
16
|
|
|
17
|
-
return
|
|
17
|
+
return formatLink(styledPath, pathToFileURL(resolvePath(path, cwd)).href);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function resolvePath(path: string, cwd: string): string {
|
package/src/utils/format.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { getCapabilities, hyperlink } from "@earendil-works/pi-tui";
|
|
2
3
|
|
|
3
4
|
import { http } from "./http";
|
|
4
5
|
|
|
5
6
|
import type { ProviderId } from "@earendil-works/pi-ai";
|
|
6
7
|
import type { ContextUsage } from "@earendil-works/pi-coding-agent";
|
|
7
8
|
|
|
9
|
+
/** Links text with OSC 8, honoring the terminal's detected and user-overridden hyperlink support. */
|
|
10
|
+
export function formatLink(text: string, url: string): string {
|
|
11
|
+
return getCapabilities().hyperlinks ? hyperlink(text, url) : text;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
export function formatModel(provider?: ProviderId, model?: string, thinkingLevel?: string): string {
|
|
9
15
|
return provider && model ? `${provider}/${model}${thinkingLevel ? `:${thinkingLevel}` : ""}` : "no-model";
|
|
10
16
|
}
|