pi-spark 0.9.2 → 0.9.4
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 +1 -1
- package/extensions/models/index.ts +37 -26
- package/extensions/name/index.ts +4 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ Example:
|
|
|
80
80
|
### Models
|
|
81
81
|
|
|
82
82
|
- The agent can call the `model` tool with two actions:
|
|
83
|
-
- `list`:
|
|
83
|
+
- `list`: lists models with their metadata, with optional `scope`, `provider` and `model` substring filters, and `offset`/`limit` paging.
|
|
84
84
|
- `current`: gets the active provider, model, and thinking level.
|
|
85
85
|
|
|
86
86
|
### Name
|
|
@@ -15,19 +15,21 @@ const DEFAULT_THINKING_LEVEL: ModelThinkingLevel = "medium";
|
|
|
15
15
|
type ModelMetadata = Omit<Model<Api>, "headers" | "compat"> & {
|
|
16
16
|
thinkingLevels: ModelThinkingLevel[];
|
|
17
17
|
defaultThinkingLevel: ModelThinkingLevel;
|
|
18
|
+
available: boolean;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
type ModelToolDetails =
|
|
21
22
|
| { action: "current"; current: { model: ModelMetadata; thinkingLevel: ModelThinkingLevel } }
|
|
22
23
|
| { action: "list"; models: ModelMetadata[]; total: number; truncation?: TruncationResult }
|
|
23
24
|
|
|
24
|
-
function toMetadata(model: Model<Api
|
|
25
|
+
function toMetadata(model: Model<Api>, available: boolean): ModelMetadata {
|
|
25
26
|
const { headers: _headers, compat: _compat, ...metadata } = model;
|
|
26
27
|
|
|
27
28
|
return {
|
|
28
29
|
...metadata,
|
|
29
30
|
thinkingLevels: getSupportedThinkingLevels(model),
|
|
30
31
|
defaultThinkingLevel: clampThinkingLevel(model, DEFAULT_THINKING_LEVEL),
|
|
32
|
+
available,
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -41,22 +43,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
41
43
|
label: "model",
|
|
42
44
|
description:
|
|
43
45
|
"Inspect pi's model state. The \"current\" action returns the active model with metadata " +
|
|
44
|
-
"and thinking level. The \"list\" action returns
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
promptSnippet: "List available models or show the current model in use",
|
|
46
|
+
"and thinking level. The \"list\" action returns models with metadata such as provider, " +
|
|
47
|
+
"id, name, API type, reasoning support, input modalities, cost, context window, and max " +
|
|
48
|
+
"output tokens. Lists can be filtered with scope/provider/model queries and paged with " +
|
|
49
|
+
`offset/limit. List output is one JSON object per line, truncated to ${DEFAULT_MAX_LINES} ` +
|
|
50
|
+
`models or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,
|
|
51
|
+
promptSnippet: "List models or show the current model in use",
|
|
51
52
|
promptGuidelines: [
|
|
52
|
-
"Use model when you need to know
|
|
53
|
+
"Use model when you need to know pi models, the active provider or model, or the current thinking level.",
|
|
53
54
|
],
|
|
54
55
|
parameters: Type.Object({
|
|
55
56
|
action: StringEnum(["list", "current"] as const, {
|
|
56
57
|
description:
|
|
57
|
-
"\"
|
|
58
|
-
"\"
|
|
58
|
+
"\"current\" returns the active model with metadata and thinking level; " +
|
|
59
|
+
"\"list\" returns models with metadata.",
|
|
59
60
|
}),
|
|
61
|
+
scope: Type.Optional(StringEnum(["all", "available"] as const, {
|
|
62
|
+
description:
|
|
63
|
+
"For list: \"all\" lists every pi model; \"available\" (default) lists only " +
|
|
64
|
+
"models with auth configured.",
|
|
65
|
+
})),
|
|
60
66
|
provider: Type.Optional(Type.String({
|
|
61
67
|
description: "For list: filter by provider, case-insensitive substring (e.g., \"vercel\", \"moonshot\")",
|
|
62
68
|
})),
|
|
@@ -70,15 +76,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
70
76
|
description: "For list: maximum number of models to return"
|
|
71
77
|
})),
|
|
72
78
|
}),
|
|
73
|
-
renderCall(args, theme
|
|
79
|
+
renderCall(args, theme) {
|
|
74
80
|
let text = `${theme.bold(theme.fg("toolTitle", "model"))} ${theme.fg("accent", args.action)}`;
|
|
75
81
|
|
|
82
|
+
if (args.action === "list") text += theme.fg("muted", ` ${args.scope ?? "available"}`);
|
|
76
83
|
if (args.provider) text += theme.fg("muted", ` provider:${args.provider}`);
|
|
77
84
|
if (args.model) text += theme.fg("muted", ` model:${args.model}`);
|
|
78
|
-
if (args.offset !== undefined || args.limit !== undefined) text += theme.fg("warning", ` from
|
|
79
|
-
if (args.limit !== undefined) text += theme.fg("warning", ` to
|
|
80
|
-
|
|
81
|
-
if (!expanded) text += theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`);
|
|
85
|
+
if (args.offset !== undefined || args.limit !== undefined) text += theme.fg("warning", ` from ${args.offset ?? 1}`);
|
|
86
|
+
if (args.limit !== undefined) text += theme.fg("warning", ` to ${(args.offset ?? 1) + args.limit - 1}`);
|
|
82
87
|
|
|
83
88
|
return new Text(text, 0, 0);
|
|
84
89
|
},
|
|
@@ -109,26 +114,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
109
114
|
const models = details.models ?? [];
|
|
110
115
|
const total = details.total ?? models.length;
|
|
111
116
|
|
|
112
|
-
if (expanded) {
|
|
117
|
+
if (models.length > 0 && expanded) {
|
|
113
118
|
models.forEach((model) => {
|
|
114
|
-
|
|
119
|
+
const label = formatModel(model.provider, model.id);
|
|
120
|
+
container.addChild(new Text(theme.fg(model.available ? "muted" : "dim", label), 0, 0));
|
|
115
121
|
});
|
|
116
122
|
|
|
117
123
|
container.addChild(new Spacer(1));
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
const summary = `${models.length !== total ? `${models.length} of ` : ""}${total} ${filtered ? "matched" : "
|
|
121
|
-
|
|
126
|
+
const summary = total > 0 ? `${models.length !== total ? `${models.length} of ` : ""}${total} ${filtered ? "matched " : ""}model${total === 1 ? "" : "s"} listed` : `0 models${filtered ? " matched" : ""}`;
|
|
127
|
+
const truncatedHint = details.truncation?.truncated ? theme.fg("warning", " (truncated)") : "";
|
|
128
|
+
const expandHint = models.length > 0 && !expanded ? theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`) : "";
|
|
129
|
+
container.addChild(new Text(theme.fg("muted", summary) + truncatedHint + expandHint, 0, 0));
|
|
122
130
|
|
|
123
131
|
return container;
|
|
124
132
|
},
|
|
125
133
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
126
134
|
if (params.action === "current") {
|
|
127
135
|
const model = ctx.model;
|
|
128
|
-
if (!model) throw new Error("No model is currently selected
|
|
136
|
+
if (!model) throw new Error("No model is currently selected");
|
|
129
137
|
|
|
130
138
|
const thinkingLevel = pi.getThinkingLevel();
|
|
131
|
-
const current = { model: toMetadata(model), thinkingLevel };
|
|
139
|
+
const current = { model: toMetadata(model, true), thinkingLevel };
|
|
132
140
|
|
|
133
141
|
return {
|
|
134
142
|
content: [{ type: "text", text: JSON.stringify(current) }],
|
|
@@ -140,16 +148,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
140
148
|
const modelQuery = params.model?.trim().toLowerCase();
|
|
141
149
|
const filtered = providerQuery !== undefined || modelQuery !== undefined;
|
|
142
150
|
|
|
143
|
-
const
|
|
151
|
+
const scope = params.scope ?? "available";
|
|
152
|
+
const source = scope === "all" ? ctx.modelRegistry.getAll() : ctx.modelRegistry.getAvailable();
|
|
153
|
+
|
|
154
|
+
const matched = source.filter((model) => {
|
|
144
155
|
if (providerQuery && !model.provider.toLowerCase().includes(providerQuery)) return false;
|
|
145
156
|
if (modelQuery && !model.id.toLowerCase().includes(modelQuery) && !model.name.toLowerCase().includes(modelQuery)) return false;
|
|
146
157
|
return true;
|
|
147
|
-
}).map(toMetadata);
|
|
158
|
+
}).map((model) => toMetadata(model, scope === "all" ? ctx.modelRegistry.hasConfiguredAuth(model) : true));
|
|
148
159
|
const total = matched.length;
|
|
149
160
|
|
|
150
161
|
if (total === 0) {
|
|
151
162
|
return {
|
|
152
|
-
content: [{ type: "text", text: `0 models
|
|
163
|
+
content: [{ type: "text", text: `0 models${filtered ? " matched" : ""}` }],
|
|
153
164
|
details: { action: "list", models: [], total } satisfies ModelToolDetails,
|
|
154
165
|
};
|
|
155
166
|
}
|
|
@@ -157,7 +168,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
168
|
// Convert from 1-indexed offset to 0-indexed array access.
|
|
158
169
|
const startIndex = params.offset ? Math.max(0, params.offset - 1) : 0;
|
|
159
170
|
if (startIndex >= total) {
|
|
160
|
-
throw new Error(`Offset ${params.offset} is beyond end of list (${total} models total)
|
|
171
|
+
throw new Error(`Offset ${params.offset} is beyond end of list (${total} models total)`);
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
const endIndex = params.limit !== undefined ? Math.min(startIndex + params.limit, total) : total;
|
package/extensions/name/index.ts
CHANGED
|
@@ -18,9 +18,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
18
18
|
label: "name",
|
|
19
19
|
description:
|
|
20
20
|
"Set or refresh the current session's concise name, shown in the session selector " +
|
|
21
|
-
"instead of the first-message preview.
|
|
22
|
-
"session easier to recognize later, such as after a long opening prompt or a substantial " +
|
|
23
|
-
"topic shift.",
|
|
21
|
+
"instead of the first-message preview.",
|
|
24
22
|
promptSnippet: "Set or refresh the current session's concise name",
|
|
25
23
|
promptGuidelines: [
|
|
26
24
|
"Use name when the session would benefit from a concise, recognizable name, especially after a long, vague, or pasted opening prompt.",
|
|
@@ -72,12 +70,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
72
70
|
},
|
|
73
71
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
74
72
|
const name = sanitizeText(params.name);
|
|
75
|
-
if (!name) throw new Error("Session name was empty after normalization
|
|
73
|
+
if (!name) throw new Error("Session name was empty after normalization; provide a short, non-empty phrase");
|
|
76
74
|
|
|
77
75
|
const previous = pi.getSessionName() ?? null;
|
|
78
76
|
if (previous === name) {
|
|
79
77
|
return {
|
|
80
|
-
content: [{ type: "text", text: `Session is already named "${name}"
|
|
78
|
+
content: [{ type: "text", text: `Session is already named "${name}"; no change` }],
|
|
81
79
|
details: { changed: false, previous },
|
|
82
80
|
};
|
|
83
81
|
}
|
|
@@ -85,7 +83,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
85
83
|
pi.setSessionName(name);
|
|
86
84
|
|
|
87
85
|
return {
|
|
88
|
-
content: [{ type: "text", text: `${previous ? `Renamed session from "${previous}" to "${name}"
|
|
86
|
+
content: [{ type: "text", text: `${previous ? `Renamed session from "${previous}" to "${name}"` : `Named session "${name}"`}` }],
|
|
89
87
|
details: { changed: true, previous },
|
|
90
88
|
};
|
|
91
89
|
},
|