pi-spark 0.9.3 → 0.9.5

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
@@ -80,7 +80,7 @@ Example:
80
80
  ### Models
81
81
 
82
82
  - The agent can call the `model` tool with two actions:
83
- - `list`: gets all currently usable models with their metadata, with optional `provider` and `model` substring filters and `offset`/`limit` paging.
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>): ModelMetadata {
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 all models the user can use " +
45
- "currently (auth configured), including metadata such as provider, id, name, API type, " +
46
- "reasoning support, input modalities, cost, context window, and max output tokens. " +
47
- "Lists can be filtered with provider/model queries and paged with offset/limit. " +
48
- `List output is one JSON object per line, truncated to ${DEFAULT_MAX_LINES} models or ` +
49
- `${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,
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 available pi models, the active provider or model, or the current thinking level.",
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
  "\"current\" returns the active model with metadata and thinking level; " +
58
- "\"list\" returns all currently usable models with metadata.",
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
  })),
@@ -73,10 +79,11 @@ export default function (pi: ExtensionAPI) {
73
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:${args.offset ?? 1}`);
79
- if (args.limit !== undefined) text += theme.fg("warning", ` to:${(args.offset ?? 1) + args.limit - 1}`);
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}`);
80
87
 
81
88
  return new Text(text, 0, 0);
82
89
  },
@@ -109,13 +116,15 @@ export default function (pi: ExtensionAPI) {
109
116
 
110
117
  if (models.length > 0 && expanded) {
111
118
  models.forEach((model) => {
112
- container.addChild(new Text(theme.fg("muted", formatModel(model.provider, model.id)), 0, 0));
119
+ const label = formatModel(model.provider, model.id);
120
+ const noAuthHint = !model.available ? theme.fg("dim", " (unavailable)") : "";
121
+ container.addChild(new Text(theme.fg("muted", label) + noAuthHint, 0, 0));
113
122
  });
114
123
 
115
124
  container.addChild(new Spacer(1));
116
125
  }
117
126
 
118
- const summary = total > 0 ? `${models.length !== total ? `${models.length} of ` : ""}${total} ${filtered ? "matched" : "available"} model${total === 1 ? "" : "s"} listed` : `0 models ${filtered ? "matched" : "available"}`;
127
+ const summary = total > 0 ? `${models.length !== total ? `${models.length} of ` : ""}${total} ${filtered ? "matched " : ""}model${total === 1 ? "" : "s"} listed` : `0 models${filtered ? " matched" : ""}`;
119
128
  const truncatedHint = details.truncation?.truncated ? theme.fg("warning", " (truncated)") : "";
120
129
  const expandHint = models.length > 0 && !expanded ? theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`) : "";
121
130
  container.addChild(new Text(theme.fg("muted", summary) + truncatedHint + expandHint, 0, 0));
@@ -128,7 +137,7 @@ export default function (pi: ExtensionAPI) {
128
137
  if (!model) throw new Error("No model is currently selected");
129
138
 
130
139
  const thinkingLevel = pi.getThinkingLevel();
131
- const current = { model: toMetadata(model), thinkingLevel };
140
+ const current = { model: toMetadata(model, true), thinkingLevel };
132
141
 
133
142
  return {
134
143
  content: [{ type: "text", text: JSON.stringify(current) }],
@@ -140,16 +149,19 @@ export default function (pi: ExtensionAPI) {
140
149
  const modelQuery = params.model?.trim().toLowerCase();
141
150
  const filtered = providerQuery !== undefined || modelQuery !== undefined;
142
151
 
143
- const matched = ctx.modelRegistry.getAvailable().filter((model) => {
152
+ const scope = params.scope ?? "available";
153
+ const source = scope === "all" ? ctx.modelRegistry.getAll() : ctx.modelRegistry.getAvailable();
154
+
155
+ const matched = source.filter((model) => {
144
156
  if (providerQuery && !model.provider.toLowerCase().includes(providerQuery)) return false;
145
157
  if (modelQuery && !model.id.toLowerCase().includes(modelQuery) && !model.name.toLowerCase().includes(modelQuery)) return false;
146
158
  return true;
147
- }).map(toMetadata);
159
+ }).map((model) => toMetadata(model, scope === "all" ? ctx.modelRegistry.hasConfiguredAuth(model) : true));
148
160
  const total = matched.length;
149
161
 
150
162
  if (total === 0) {
151
163
  return {
152
- content: [{ type: "text", text: `0 models ${filtered ? "matched" : "available"}` }],
164
+ content: [{ type: "text", text: `0 models${filtered ? " matched" : ""}` }],
153
165
  details: { action: "list", models: [], total } satisfies ModelToolDetails,
154
166
  };
155
167
  }
@@ -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. Use this when a concise name would make the " +
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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-spark",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "A small, opinionated collection of pi extensions",
5
5
  "keywords": [
6
6
  "pi-coding-agent",