@vtstech/pi-api 1.0.4 → 1.0.6
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 +46 -0
- package/api.js +230 -42
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @vtstech/pi-api
|
|
2
|
+
|
|
3
|
+
API Mode Switcher extension for the [Pi Coding Agent](https://github.com/badlogic/pi-mono).
|
|
4
|
+
|
|
5
|
+
Runtime switching of API modes, base URLs, thinking settings, and compat flags in `models.json`. Supports all 10 Pi API modes.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pi install "npm:@vtstech/pi-api"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/api Show current provider config (mode, URL, compat flags)
|
|
17
|
+
/api mode <mode> Switch API mode (partial match supported)
|
|
18
|
+
/api url <url> Switch base URL
|
|
19
|
+
/api think on|off|auto Toggle thinking for all models in provider
|
|
20
|
+
/api compat <key> View compat flags
|
|
21
|
+
/api compat <key> <val> Set compat flag
|
|
22
|
+
/api modes List all 10 supported API modes
|
|
23
|
+
/api providers List all configured providers
|
|
24
|
+
/api reload Hint to run /reload
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Supported API Modes
|
|
28
|
+
|
|
29
|
+
`anthropic-messages` · `openai-completions` · `openai-responses` · `azure-openai-responses` · `openai-codex-responses` · `mistral-conversations` · `google-generative-ai` · `google-gemini-cli` · `google-vertex` · `bedrock-converse-stream`
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Partial mode matching — `/api mode openai-r` matches `openai-responses`
|
|
34
|
+
- Auto-detect local provider — targets the first `localhost`/`ollama` provider by default
|
|
35
|
+
- Batch thinking toggle — set `reasoning: true/false` across all models at once
|
|
36
|
+
- Compat flag management — get/set `supportsDeveloperRole`, `thinkingFormat`, `maxTokensField`, etc.
|
|
37
|
+
- Tab-completion for sub-commands
|
|
38
|
+
|
|
39
|
+
## Links
|
|
40
|
+
|
|
41
|
+
- [Full Documentation](https://github.com/VTSTech/pi-coding-agent#api-mode-switcher-apits)
|
|
42
|
+
- [Changelog](https://github.com/VTSTech/pi-coding-agent/blob/main/CHANGELOG.md)
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT — [VTSTech](https://www.vts-tech.org)
|
package/api.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// .build-npm/api/api.temp.ts
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
2
5
|
import { section, ok, info, warn } from "@vtstech/pi-shared/format";
|
|
3
|
-
import { readModelsJson, writeModelsJson, getOllamaBaseUrl } from "@vtstech/pi-shared/ollama";
|
|
6
|
+
import { readModelsJson, writeModelsJson, getOllamaBaseUrl, BUILTIN_PROVIDERS } from "@vtstech/pi-shared/ollama";
|
|
4
7
|
var API_MODES = {
|
|
5
8
|
"anthropic-messages": "Anthropic Claude API and compatibles",
|
|
6
9
|
"openai-completions": "OpenAI Chat Completions API and compatibles",
|
|
@@ -35,6 +38,21 @@ var COMPAT_FLAGS = {
|
|
|
35
38
|
values: ["qwen", "deepseek", "default"]
|
|
36
39
|
}
|
|
37
40
|
};
|
|
41
|
+
var SETTINGS_PATH = path.join(os.homedir(), ".pi", "agent", "settings.json");
|
|
42
|
+
function readSettings() {
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
45
|
+
return JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
function writeSettings(data) {
|
|
52
|
+
const dir = path.dirname(SETTINGS_PATH);
|
|
53
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
54
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
55
|
+
}
|
|
38
56
|
function getLocalProvider(config) {
|
|
39
57
|
for (const [name, provider] of Object.entries(config.providers)) {
|
|
40
58
|
const url = provider.baseUrl || "";
|
|
@@ -53,7 +71,7 @@ function resolveProvider(config, explicit) {
|
|
|
53
71
|
}
|
|
54
72
|
function api_temp_default(pi) {
|
|
55
73
|
const branding = [
|
|
56
|
-
` \u26A1 Pi API Mode Switcher v1.0.
|
|
74
|
+
` \u26A1 Pi API Mode Switcher v1.0.6`,
|
|
57
75
|
` Written by VTSTech`,
|
|
58
76
|
` GitHub: https://github.com/VTSTech`,
|
|
59
77
|
` Website: www.vts-tech.org`
|
|
@@ -65,31 +83,34 @@ function api_temp_default(pi) {
|
|
|
65
83
|
const sub = parts[0]?.toLowerCase() || "";
|
|
66
84
|
const rest = parts.slice(1).join(" ");
|
|
67
85
|
const config = readModelsJson();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
if (sub !== "provider" && sub !== "providers") {
|
|
87
|
+
const provider = resolveProvider(config);
|
|
88
|
+
if (!provider) {
|
|
89
|
+
ctx.ui.notify("No providers found in models.json", "error");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
switch (sub) {
|
|
93
|
+
case "":
|
|
94
|
+
case "show":
|
|
95
|
+
return showConfig(provider);
|
|
96
|
+
case "mode":
|
|
97
|
+
return setMode(ctx, provider.name, rest);
|
|
98
|
+
case "url":
|
|
99
|
+
return setUrl(ctx, provider.name, rest);
|
|
100
|
+
case "think":
|
|
101
|
+
return setThink(ctx, provider.name, rest);
|
|
102
|
+
case "compat":
|
|
103
|
+
return handleCompat(ctx, provider.name, rest);
|
|
104
|
+
case "reload":
|
|
105
|
+
return reloadConfig(ctx);
|
|
106
|
+
case "modes":
|
|
107
|
+
return listModes();
|
|
108
|
+
default:
|
|
109
|
+
ctx.ui.notify(`Unknown sub-command: "${sub}". Use: mode, url, think, compat, reload, modes, provider, providers`, "error");
|
|
110
|
+
}
|
|
72
111
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
case "show":
|
|
76
|
-
return showConfig(provider);
|
|
77
|
-
case "mode":
|
|
78
|
-
return setMode(ctx, provider.name, rest);
|
|
79
|
-
case "url":
|
|
80
|
-
return setUrl(ctx, provider.name, rest);
|
|
81
|
-
case "think":
|
|
82
|
-
return setThink(ctx, provider.name, rest);
|
|
83
|
-
case "compat":
|
|
84
|
-
return handleCompat(ctx, provider.name, rest);
|
|
85
|
-
case "reload":
|
|
86
|
-
return reloadConfig(ctx);
|
|
87
|
-
case "modes":
|
|
88
|
-
return listModes();
|
|
89
|
-
case "providers":
|
|
90
|
-
return listProviders(config);
|
|
91
|
-
default:
|
|
92
|
-
ctx.ui.notify(`Unknown sub-command: "${sub}". Use: mode, url, think, compat, reload, modes, providers`, "error");
|
|
112
|
+
if (sub === "provider" || sub === "providers") {
|
|
113
|
+
return handleProvider(ctx, config, rest);
|
|
93
114
|
}
|
|
94
115
|
}
|
|
95
116
|
});
|
|
@@ -355,22 +376,135 @@ function api_temp_default(pi) {
|
|
|
355
376
|
display: { type: "content", content: lines.join("\n") }
|
|
356
377
|
});
|
|
357
378
|
}
|
|
358
|
-
function
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
lines
|
|
379
|
+
function handleProvider(ctx, config, arg) {
|
|
380
|
+
const parts = arg.trim().split(/\s+/);
|
|
381
|
+
const sub = parts[0]?.toLowerCase() || "";
|
|
382
|
+
const rest = parts.slice(1).join(" ");
|
|
383
|
+
const providerNames = Object.keys(config.providers);
|
|
384
|
+
if (!sub || sub === "show" || sub === "list") {
|
|
385
|
+
const settings = readSettings();
|
|
386
|
+
const defaultProvider = settings.defaultProvider || "(not set)";
|
|
387
|
+
const defaultModel = settings.defaultModel || "(not set)";
|
|
388
|
+
const lines = [branding];
|
|
389
|
+
lines.push(section("DEFAULT PROVIDER"));
|
|
390
|
+
lines.push(ok(`Provider: ${defaultProvider}`));
|
|
391
|
+
lines.push(info(`Model: ${defaultModel}`));
|
|
392
|
+
lines.push(info(`Source: ${SETTINGS_PATH}`));
|
|
393
|
+
lines.push(section("CONFIGURED PROVIDERS"));
|
|
394
|
+
if (providerNames.length === 0) {
|
|
395
|
+
lines.push(info(" (none \u2014 add providers to models.json)"));
|
|
396
|
+
} else {
|
|
397
|
+
for (const [name, provider] of Object.entries(config.providers)) {
|
|
398
|
+
const p = provider;
|
|
399
|
+
const modelCount = p.models?.length || 0;
|
|
400
|
+
const url = p.baseUrl || "(no URL)";
|
|
401
|
+
const api = p.api || "(no mode)";
|
|
402
|
+
const isDefault = name === defaultProvider;
|
|
403
|
+
const isLocal = url.includes("localhost") || url.includes("127.0.0.1") || url.includes("0.0.0.0") || name === "ollama";
|
|
404
|
+
const marker = isDefault ? ok(" \u25C0 default") : "";
|
|
405
|
+
const tag = isLocal ? " (local)" : " (cloud)";
|
|
406
|
+
lines.push(info(` ${name}${tag}${marker}`));
|
|
407
|
+
lines.push(info(` API: ${api} | URL: ${url} | Models: ${modelCount}`));
|
|
408
|
+
if (modelCount > 0) {
|
|
409
|
+
const first = p.models[0].id;
|
|
410
|
+
lines.push(info(` First model: ${first}${modelCount > 1 ? ` (+${modelCount - 1} more)` : ""}`));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const unconfigured = Object.entries(BUILTIN_PROVIDERS).filter(
|
|
415
|
+
([name]) => !providerNames.includes(name)
|
|
416
|
+
);
|
|
417
|
+
if (unconfigured.length > 0) {
|
|
418
|
+
lines.push(section("AVAILABLE PROVIDERS"));
|
|
419
|
+
lines.push(info(" (not in models.json \u2014 configure with API key env var)"));
|
|
420
|
+
for (const [name, info2] of unconfigured) {
|
|
421
|
+
const hasKey = !!process.env[info2.envKey];
|
|
422
|
+
const status = hasKey ? ok("API key set") : info("no API key");
|
|
423
|
+
lines.push(info(` ${name.padEnd(14)} ${info2.api.padEnd(26)} ${status}`));
|
|
424
|
+
lines.push(info(` URL: ${info2.baseUrl}`));
|
|
425
|
+
lines.push(info(` Env: ${info2.envKey}`));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
lines.push("");
|
|
429
|
+
lines.push(info("Usage: /api provider set <name> \u2014 set default provider"));
|
|
430
|
+
lines.push(info(" /api provider list \u2014 show all providers"));
|
|
431
|
+
pi.sendMessage({
|
|
432
|
+
customType: "api-provider",
|
|
433
|
+
content: lines.join("\n"),
|
|
434
|
+
display: { type: "content", content: lines.join("\n") }
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
368
437
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
438
|
+
if (sub === "set" || sub === "change" || sub === "switch") {
|
|
439
|
+
const targetName = rest;
|
|
440
|
+
if (!targetName) {
|
|
441
|
+
const lines2 = [branding];
|
|
442
|
+
lines2.push(section("SET DEFAULT PROVIDER"));
|
|
443
|
+
lines2.push(info("Usage: /api provider set <name>"));
|
|
444
|
+
lines2.push(info(""));
|
|
445
|
+
lines2.push(info("Configured providers:"));
|
|
446
|
+
for (const name of providerNames) {
|
|
447
|
+
const p = config.providers[name];
|
|
448
|
+
const isLocal = (p.baseUrl || "").includes("localhost") || (p.baseUrl || "").includes("127.0.0.1") || name === "ollama";
|
|
449
|
+
lines2.push(info(` ${name}${isLocal ? " (local)" : " (cloud)"}`));
|
|
450
|
+
}
|
|
451
|
+
const builtins = Object.keys(BUILTIN_PROVIDERS).filter((n) => !providerNames.includes(n));
|
|
452
|
+
if (builtins.length > 0) {
|
|
453
|
+
lines2.push(info("Built-in providers:"));
|
|
454
|
+
for (const name of builtins) {
|
|
455
|
+
lines2.push(info(` ${name} (built-in)`));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
pi.sendMessage({
|
|
459
|
+
customType: "api-provider-set",
|
|
460
|
+
content: lines2.join("\n"),
|
|
461
|
+
display: { type: "content", content: lines2.join("\n") }
|
|
462
|
+
});
|
|
463
|
+
ctx.ui.notify("Specify a provider name: /api provider set <name>", "info");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const isBuiltin = targetName in BUILTIN_PROVIDERS;
|
|
467
|
+
if (!config.providers[targetName] && !isBuiltin) {
|
|
468
|
+
const allNames = [...providerNames, ...Object.keys(BUILTIN_PROVIDERS).filter((n) => !providerNames.includes(n))];
|
|
469
|
+
ctx.ui.notify(`Provider "${targetName}" not found. Available: ${allNames.join(", ")}`, "error");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const settings = readSettings();
|
|
473
|
+
const oldProvider = settings.defaultProvider || "(not set)";
|
|
474
|
+
const oldModel = settings.defaultModel || "(not set)";
|
|
475
|
+
settings.defaultProvider = targetName;
|
|
476
|
+
const targetModels = config.providers[targetName]?.models || [];
|
|
477
|
+
if (targetModels.length > 0) {
|
|
478
|
+
settings.defaultModel = targetModels[0].id;
|
|
479
|
+
} else if (isBuiltin) {
|
|
480
|
+
settings.defaultModel = "(Pi default)";
|
|
481
|
+
}
|
|
482
|
+
writeSettings(settings);
|
|
483
|
+
const lines = [branding];
|
|
484
|
+
lines.push(section("DEFAULT PROVIDER CHANGED"));
|
|
485
|
+
lines.push(ok(`New provider: ${targetName}`));
|
|
486
|
+
lines.push(info(`Old provider: ${oldProvider}`));
|
|
487
|
+
lines.push(info(`Old model: ${oldModel}`));
|
|
488
|
+
if (targetModels.length > 0) {
|
|
489
|
+
lines.push(ok(`Auto-set model: ${targetModels[0].id}`));
|
|
490
|
+
lines.push(info(`Available models: ${targetModels.map((m) => m.id).join(", ")}`));
|
|
491
|
+
} else if (isBuiltin) {
|
|
492
|
+
lines.push(info(`Built-in provider \u2014 Pi will use its default model`));
|
|
493
|
+
lines.push(info(`Ensure ${BUILTIN_PROVIDERS[targetName].envKey} is set`));
|
|
494
|
+
}
|
|
495
|
+
lines.push(warn("Run /reload to apply changes in Pi"));
|
|
496
|
+
pi.sendMessage({
|
|
497
|
+
customType: "api-provider-changed",
|
|
498
|
+
content: lines.join("\n"),
|
|
499
|
+
display: { type: "content", content: lines.join("\n") }
|
|
500
|
+
});
|
|
501
|
+
ctx.ui.notify(`Default provider set to ${targetName}`, "success");
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (providerNames.includes(sub) || sub in BUILTIN_PROVIDERS) {
|
|
505
|
+
return handleProvider(ctx, config, `set ${sub}`);
|
|
506
|
+
}
|
|
507
|
+
ctx.ui.notify(`Unknown sub-command: "${sub}". Use: show, set, list, or a provider name`, "error");
|
|
374
508
|
}
|
|
375
509
|
pi.registerCompletion?.("api", {
|
|
376
510
|
getCompletions: () => {
|
|
@@ -381,10 +515,64 @@ function api_temp_default(pi) {
|
|
|
381
515
|
{ value: "compat", label: "compat", description: "View/set compat flags" },
|
|
382
516
|
{ value: "reload", label: "reload", description: "Reload models.json" },
|
|
383
517
|
{ value: "modes", label: "modes", description: "List all supported API modes" },
|
|
384
|
-
{ value: "
|
|
518
|
+
{ value: "provider", label: "provider", description: "Show, set, or list all providers" }
|
|
385
519
|
];
|
|
386
520
|
}
|
|
387
521
|
});
|
|
522
|
+
pi.registerCompletion?.("api", {
|
|
523
|
+
getCompletions: () => [],
|
|
524
|
+
getArgumentCompletions: (args) => {
|
|
525
|
+
const sub = args[0]?.toLowerCase() || "";
|
|
526
|
+
if (sub === "provider" && args.length >= 2) {
|
|
527
|
+
const action = args[1]?.toLowerCase() || "";
|
|
528
|
+
if (["set", "change", "switch"].includes(action) && args.length === 3) {
|
|
529
|
+
const config = readModelsJson();
|
|
530
|
+
const items = [];
|
|
531
|
+
for (const name of Object.keys(config.providers)) {
|
|
532
|
+
items.push({ value: name, label: name, description: `Set ${name} as default provider` });
|
|
533
|
+
}
|
|
534
|
+
for (const name of Object.keys(BUILTIN_PROVIDERS)) {
|
|
535
|
+
if (!config.providers[name]) {
|
|
536
|
+
items.push({ value: name, label: name, description: `Set ${name} (built-in)` });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return items;
|
|
540
|
+
}
|
|
541
|
+
if (args.length === 2) {
|
|
542
|
+
const config = readModelsJson();
|
|
543
|
+
const items = [
|
|
544
|
+
{ value: "set", label: "set", description: "Set default provider" },
|
|
545
|
+
{ value: "list", label: "list", description: "Show all providers" },
|
|
546
|
+
{ value: "show", label: "show", description: "Show current provider" }
|
|
547
|
+
];
|
|
548
|
+
for (const name of Object.keys(config.providers)) {
|
|
549
|
+
items.push({ value: name, label: name, description: `Switch to ${name}` });
|
|
550
|
+
}
|
|
551
|
+
for (const name of Object.keys(BUILTIN_PROVIDERS)) {
|
|
552
|
+
if (!config.providers[name]) {
|
|
553
|
+
items.push({ value: name, label: name, description: `Switch to ${name} (built-in)` });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return items;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (sub === "mode" && args.length === 2) {
|
|
560
|
+
return Object.keys(API_MODES).map((mode) => ({
|
|
561
|
+
value: mode,
|
|
562
|
+
label: mode,
|
|
563
|
+
description: API_MODES[mode]
|
|
564
|
+
}));
|
|
565
|
+
}
|
|
566
|
+
if (sub === "think" && args.length === 2) {
|
|
567
|
+
return [
|
|
568
|
+
{ value: "on", label: "on", description: "Enable thinking for all models" },
|
|
569
|
+
{ value: "off", label: "off", description: "Disable thinking for all models" },
|
|
570
|
+
{ value: "auto", label: "auto", description: "Auto-detect thinking from model name" }
|
|
571
|
+
];
|
|
572
|
+
}
|
|
573
|
+
return [];
|
|
574
|
+
}
|
|
575
|
+
});
|
|
388
576
|
}
|
|
389
577
|
export {
|
|
390
578
|
api_temp_default as default
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtstech/pi-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "API Mode Switcher extension for Pi Coding Agent",
|
|
5
5
|
"main": "api.js",
|
|
6
|
-
"keywords": ["pi-
|
|
6
|
+
"keywords": ["pi-extensions"],
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"access": "public",
|
|
9
9
|
"type": "module",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/VTSTech/pi-coding-agent"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@vtstech/pi-shared": "1.0.
|
|
17
|
+
"@vtstech/pi-shared": "1.0.6"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@mariozechner/pi-coding-agent": ">=0.66"
|