@aliou/pi-synthetic 0.13.5 → 0.15.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 +18 -7
- package/package.json +5 -5
- package/src/config.ts +51 -42
- package/src/extensions/provider/index.test.ts +46 -0
- package/src/extensions/provider/index.ts +37 -89
- package/src/extensions/provider/models.ts +24 -36
- package/src/extensions/usage-status/index.ts +14 -7
- package/src/extensions/web-search/tool.ts +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Pi Synthetic Extension
|
|
4
4
|
|
|
5
|
-
A Pi extension that adds [Synthetic](https://synthetic.new) as a model provider, giving you access to open-source models through
|
|
5
|
+
A Pi extension that adds [Synthetic](https://synthetic.new) as a model provider, giving you access to open-source models through Synthetic's OpenAI-compatible API.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -52,6 +52,14 @@ Once installed, select `synthetic` as your provider and choose from available mo
|
|
|
52
52
|
/model synthetic hf:moonshotai/Kimi-K2.5
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### Model Hosting
|
|
56
|
+
|
|
57
|
+
All models are accessed through Synthetic's API. Some models are hosted by Synthetic directly (`provider: "synthetic"` in the model config); others are proxied by Synthetic to upstream backends such as Fireworks or Together.
|
|
58
|
+
|
|
59
|
+
By default, new installs show only Synthetic-hosted models. You can enable proxied models in `/synthetic:settings` under **Models > Proxied Models**. Existing configurations keep proxied models enabled to preserve prior behavior.
|
|
60
|
+
|
|
61
|
+
The `provider` field in `src/extensions/provider/models.ts` is for maintenance only and is stripped before registering models with Pi, so users always select the `synthetic` provider.
|
|
62
|
+
|
|
55
63
|
### Web Search Tool
|
|
56
64
|
|
|
57
65
|
The extension registers `synthetic_web_search` — a zero-data-retention web search tool. The tool is always visible; it fails with a clear message if credentials are missing or the account lacks a subscription.
|
|
@@ -89,21 +97,24 @@ The extension automatically notifies you when you approach or exceed your Synthe
|
|
|
89
97
|
|
|
90
98
|
## Disabling Features
|
|
91
99
|
|
|
92
|
-
Each feature (provider, web search, quotas command, usage status, quota warnings) is a separate Pi extension. You can disable individual features using `pi config`:
|
|
100
|
+
Each feature (provider, web search, quotas command, sub bar integration, usage status, quota warnings) is a separate Pi extension. You can disable individual features using `pi config`:
|
|
93
101
|
|
|
94
102
|
```
|
|
95
103
|
pi config extensions.disabled add @aliou/pi-synthetic/quota-warnings
|
|
96
104
|
```
|
|
97
105
|
|
|
98
|
-
This prevents the quota-warnings extension from loading while keeping the rest of pi-synthetic active. Replace `quota-warnings` with `web-search`, `command-quotas`, or `provider` to disable other features.
|
|
106
|
+
This prevents the quota-warnings extension from loading while keeping the rest of pi-synthetic active. Replace `quota-warnings` with `web-search`, `command-quotas`, `sub-bar-integration`, `usage-status`, or `provider` to disable other features.
|
|
107
|
+
|
|
108
|
+
The **Proxied Models** setting is not a loadable extension feature. It is a regular setting controlled through `/synthetic:settings`.
|
|
99
109
|
|
|
100
110
|
## Adding or Updating Models
|
|
101
111
|
|
|
102
|
-
Models are hardcoded in `src/
|
|
112
|
+
Models are hardcoded in `src/extensions/provider/models.ts`. To add or update models:
|
|
103
113
|
|
|
104
|
-
1. Edit `src/
|
|
114
|
+
1. Edit `src/extensions/provider/models.ts`
|
|
105
115
|
2. Add the model configuration following the `SyntheticModelConfig` interface
|
|
106
|
-
3.
|
|
116
|
+
3. Set `provider` to the upstream backend Synthetic uses for that model, such as `synthetic`, `fireworks`, or `together`
|
|
117
|
+
4. Run `pnpm run typecheck` to verify
|
|
107
118
|
|
|
108
119
|
## Development
|
|
109
120
|
|
|
@@ -156,7 +167,7 @@ This repository uses [Changesets](https://github.com/changesets/changesets) for
|
|
|
156
167
|
|
|
157
168
|
## Requirements
|
|
158
169
|
|
|
159
|
-
- Pi coding agent v0.
|
|
170
|
+
- Pi coding agent v0.72.0+
|
|
160
171
|
- Synthetic API key (configured in `~/.pi/agent/auth.json` or via `SYNTHETIC_API_KEY`)
|
|
161
172
|
|
|
162
173
|
## Links
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-synthetic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"README.md"
|
|
33
33
|
],
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@mariozechner/pi-coding-agent": "0.
|
|
36
|
-
"@mariozechner/pi-tui": "0.
|
|
35
|
+
"@mariozechner/pi-coding-agent": "0.72.0",
|
|
36
|
+
"@mariozechner/pi-tui": "0.72.0"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@aliou/pi-utils-settings": "^0.13.0",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"@aliou/biome-plugins": "^0.7.0",
|
|
44
44
|
"@biomejs/biome": "^2.4.2",
|
|
45
45
|
"@changesets/cli": "^2.27.11",
|
|
46
|
-
"@mariozechner/pi-coding-agent": "0.
|
|
47
|
-
"
|
|
46
|
+
"@mariozechner/pi-coding-agent": "0.72.0",
|
|
47
|
+
"typebox": "^1.1.37",
|
|
48
48
|
"@types/node": "^25.0.10",
|
|
49
49
|
"husky": "^9.1.7",
|
|
50
50
|
"typescript": "^5.9.3",
|
package/src/config.ts
CHANGED
|
@@ -25,12 +25,7 @@ export interface SyntheticExtensionsRegisterPayload {
|
|
|
25
25
|
feature: SyntheticFeatureId;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Config schema version. Stamped on disk when the initial migration runs or
|
|
30
|
-
* when the config is seeded. Uses the package version; bumping the package
|
|
31
|
-
* does not retrigger migrations (we only run them when `configVersion` is
|
|
32
|
-
* missing), but it records which release first created the file.
|
|
33
|
-
*/
|
|
28
|
+
/** Config schema version. Stamped on disk when config is seeded or migrated. */
|
|
34
29
|
export const SYNTHETIC_CONFIG_VERSION: string = pkg.version;
|
|
35
30
|
|
|
36
31
|
export interface SyntheticConfig {
|
|
@@ -40,6 +35,7 @@ export interface SyntheticConfig {
|
|
|
40
35
|
usageStatus?: boolean;
|
|
41
36
|
quotaWarnings?: boolean;
|
|
42
37
|
subBarIntegration?: boolean;
|
|
38
|
+
proxiedModels?: boolean;
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
export interface ResolvedSyntheticConfig {
|
|
@@ -49,6 +45,7 @@ export interface ResolvedSyntheticConfig {
|
|
|
49
45
|
usageStatus: boolean;
|
|
50
46
|
quotaWarnings: boolean;
|
|
51
47
|
subBarIntegration: boolean;
|
|
48
|
+
proxiedModels: boolean;
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
const DEFAULT_CONFIG: ResolvedSyntheticConfig = {
|
|
@@ -58,39 +55,33 @@ const DEFAULT_CONFIG: ResolvedSyntheticConfig = {
|
|
|
58
55
|
usageStatus: false,
|
|
59
56
|
quotaWarnings: false,
|
|
60
57
|
subBarIntegration: true,
|
|
58
|
+
proxiedModels: false,
|
|
61
59
|
};
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
// is seeded for the first time. Consumed once by the provider extension to
|
|
65
|
-
// display a one-time notice about the new settings UI.
|
|
66
|
-
let pendingMigrationNotice = false;
|
|
67
|
-
|
|
68
|
-
export function hasPendingMigrationNotice(): boolean {
|
|
69
|
-
return pendingMigrationNotice;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function clearPendingMigrationNotice(): void {
|
|
73
|
-
pendingMigrationNotice = false;
|
|
74
|
-
}
|
|
61
|
+
export const pendingMessages: string[] = [];
|
|
75
62
|
|
|
76
|
-
function
|
|
77
|
-
|
|
63
|
+
function needsProxiedModelsMigration(config: SyntheticConfig): boolean {
|
|
64
|
+
if (config.proxiedModels !== undefined) return false;
|
|
65
|
+
if (config.configVersion === undefined) return true;
|
|
66
|
+
return (
|
|
67
|
+
config.configVersion.localeCompare("0.13.5", undefined, {
|
|
68
|
+
numeric: true,
|
|
69
|
+
}) <= 0
|
|
70
|
+
);
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
const migrations: Migration<SyntheticConfig>[] = [
|
|
81
74
|
{
|
|
82
|
-
name: "seed-
|
|
83
|
-
shouldRun:
|
|
75
|
+
name: "seed-proxied-models",
|
|
76
|
+
shouldRun: needsProxiedModelsMigration,
|
|
84
77
|
run: (config) => {
|
|
85
|
-
|
|
78
|
+
pendingMessages.push(
|
|
79
|
+
"This provider now differentiates hosted models from proxied models and allows disabling them. Use `/synthetic:settings` to disable them.",
|
|
80
|
+
);
|
|
86
81
|
return {
|
|
82
|
+
...config,
|
|
87
83
|
configVersion: SYNTHETIC_CONFIG_VERSION,
|
|
88
|
-
|
|
89
|
-
quotasCommand: config.quotasCommand ?? DEFAULT_CONFIG.quotasCommand,
|
|
90
|
-
usageStatus: config.usageStatus ?? DEFAULT_CONFIG.usageStatus,
|
|
91
|
-
quotaWarnings: config.quotaWarnings ?? DEFAULT_CONFIG.quotaWarnings,
|
|
92
|
-
subBarIntegration:
|
|
93
|
-
config.subBarIntegration ?? DEFAULT_CONFIG.subBarIntegration,
|
|
84
|
+
proxiedModels: true,
|
|
94
85
|
};
|
|
95
86
|
},
|
|
96
87
|
},
|
|
@@ -106,8 +97,7 @@ export const configLoader = new ConfigLoader<
|
|
|
106
97
|
|
|
107
98
|
/**
|
|
108
99
|
* Seed the global config file on first use. When no config file exists in
|
|
109
|
-
* any scope, this writes the current defaults
|
|
110
|
-
* global scope and flags the migration notice as pending.
|
|
100
|
+
* any scope, this writes the current defaults with configVersion.
|
|
111
101
|
*
|
|
112
102
|
* Must be called after `configLoader.load()`.
|
|
113
103
|
*/
|
|
@@ -115,18 +105,10 @@ export async function seedSyntheticConfigIfMissing(): Promise<void> {
|
|
|
115
105
|
if (configLoader.hasConfig("global") || configLoader.hasConfig("local")) {
|
|
116
106
|
return;
|
|
117
107
|
}
|
|
118
|
-
markMigrationNoticePending();
|
|
119
108
|
try {
|
|
120
|
-
await configLoader.save("global",
|
|
121
|
-
configVersion: SYNTHETIC_CONFIG_VERSION,
|
|
122
|
-
webSearch: DEFAULT_CONFIG.webSearch,
|
|
123
|
-
quotasCommand: DEFAULT_CONFIG.quotasCommand,
|
|
124
|
-
usageStatus: DEFAULT_CONFIG.usageStatus,
|
|
125
|
-
quotaWarnings: DEFAULT_CONFIG.quotaWarnings,
|
|
126
|
-
subBarIntegration: DEFAULT_CONFIG.subBarIntegration,
|
|
127
|
-
});
|
|
109
|
+
await configLoader.save("global", DEFAULT_CONFIG);
|
|
128
110
|
} catch {
|
|
129
|
-
//
|
|
111
|
+
// Ignore seed failures. Defaults still resolve in memory.
|
|
130
112
|
}
|
|
131
113
|
}
|
|
132
114
|
|
|
@@ -191,10 +173,24 @@ export function registerSyntheticSettings(
|
|
|
191
173
|
const quotaWarnings = tabConfig?.quotaWarnings ?? resolved.quotaWarnings;
|
|
192
174
|
const subBarIntegration =
|
|
193
175
|
tabConfig?.subBarIntegration ?? resolved.subBarIntegration;
|
|
176
|
+
const proxiedModels = tabConfig?.proxiedModels ?? resolved.proxiedModels;
|
|
194
177
|
|
|
195
178
|
const sections: SettingsSection[] = [];
|
|
196
179
|
|
|
197
180
|
sections.push(
|
|
181
|
+
{
|
|
182
|
+
label: "Models",
|
|
183
|
+
items: [
|
|
184
|
+
{
|
|
185
|
+
id: "proxiedModels",
|
|
186
|
+
label: "Proxied Models",
|
|
187
|
+
description:
|
|
188
|
+
"Allow models that Synthetic proxies to upstream backends such as Fireworks or Together. Disable to show only models hosted directly by Synthetic.",
|
|
189
|
+
currentValue: proxiedModels ? "enabled" : "disabled",
|
|
190
|
+
values: ["enabled", "disabled"],
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
198
194
|
{
|
|
199
195
|
label: "Tools",
|
|
200
196
|
items: [
|
|
@@ -250,12 +246,25 @@ export function registerSyntheticSettings(
|
|
|
250
246
|
return sections;
|
|
251
247
|
},
|
|
252
248
|
onSettingChange: (id, newValue, config) => {
|
|
253
|
-
|
|
249
|
+
const featureIds = new Set<string>([
|
|
250
|
+
"webSearch",
|
|
251
|
+
"quotasCommand",
|
|
252
|
+
"usageStatus",
|
|
253
|
+
"quotaWarnings",
|
|
254
|
+
"subBarIntegration",
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
featureIds.has(id) &&
|
|
259
|
+
!getLoadedFeatures().has(id as SyntheticFeatureId)
|
|
260
|
+
) {
|
|
254
261
|
return null;
|
|
255
262
|
}
|
|
256
263
|
|
|
257
264
|
const enabled = newValue === "enabled";
|
|
258
265
|
switch (id) {
|
|
266
|
+
case "proxiedModels":
|
|
267
|
+
return { ...config, proxiedModels: enabled };
|
|
259
268
|
case "webSearch":
|
|
260
269
|
return { ...config, webSearch: enabled };
|
|
261
270
|
case "quotasCommand":
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildSyntheticProviderModels } from "./index";
|
|
3
|
+
import { SYNTHETIC_MODELS } from "./models";
|
|
4
|
+
|
|
5
|
+
describe("buildSyntheticProviderModels", () => {
|
|
6
|
+
it("excludes proxied models when includeProxiedModels is false", () => {
|
|
7
|
+
const models = buildSyntheticProviderModels(false);
|
|
8
|
+
for (const model of models) {
|
|
9
|
+
const source = SYNTHETIC_MODELS.find((m) => m.id === model.id);
|
|
10
|
+
expect(source).toBeDefined();
|
|
11
|
+
expect(source?.provider).toBe("synthetic");
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("includes all models when includeProxiedModels is true", () => {
|
|
16
|
+
const models = buildSyntheticProviderModels(true);
|
|
17
|
+
expect(models).toHaveLength(SYNTHETIC_MODELS.length);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("does not expose the internal provider field", () => {
|
|
21
|
+
const models = buildSyntheticProviderModels(true);
|
|
22
|
+
for (const model of models) {
|
|
23
|
+
expect(model).not.toHaveProperty("provider");
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("sets default compat fields on every model", () => {
|
|
28
|
+
const models = buildSyntheticProviderModels(true);
|
|
29
|
+
for (const model of models) {
|
|
30
|
+
expect(model.compat).toMatchObject({
|
|
31
|
+
supportsDeveloperRole: false,
|
|
32
|
+
});
|
|
33
|
+
expect(model.compat).toHaveProperty("maxTokensField");
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("preserves model-specific compat overrides", () => {
|
|
38
|
+
const models = buildSyntheticProviderModels(true);
|
|
39
|
+
const miniMax = models.find((m) => m.id === "hf:MiniMaxAI/MiniMax-M2.5");
|
|
40
|
+
expect(miniMax).toBeDefined();
|
|
41
|
+
expect(miniMax?.compat).toMatchObject({
|
|
42
|
+
supportsDeveloperRole: false,
|
|
43
|
+
maxTokensField: "max_completion_tokens",
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -1,60 +1,40 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
3
2
|
import {
|
|
4
|
-
clearPendingMigrationNotice,
|
|
5
3
|
configLoader,
|
|
6
4
|
emitSyntheticConfigUpdated,
|
|
7
|
-
|
|
5
|
+
pendingMessages,
|
|
8
6
|
registerSyntheticSettings,
|
|
7
|
+
SYNTHETIC_CONFIG_UPDATED_EVENT,
|
|
9
8
|
SYNTHETIC_EXTENSIONS_REGISTER_EVENT,
|
|
10
9
|
SYNTHETIC_EXTENSIONS_REQUEST_EVENT,
|
|
10
|
+
type SyntheticConfigUpdatedPayload,
|
|
11
11
|
type SyntheticExtensionsRegisterPayload,
|
|
12
12
|
type SyntheticFeatureId,
|
|
13
13
|
seedSyntheticConfigIfMissing,
|
|
14
14
|
} from "../../config";
|
|
15
15
|
import { SYNTHETIC_MODELS } from "./models";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function wrapInRoundedBorder(
|
|
29
|
-
lines: string[],
|
|
30
|
-
width: number,
|
|
31
|
-
colorFn: (text: string) => string,
|
|
32
|
-
): string[] {
|
|
33
|
-
const innerWidth = Math.max(1, width - 2);
|
|
34
|
-
const hBar = "\u2500".repeat(innerWidth);
|
|
35
|
-
const top = colorFn(`\u256D${hBar}\u256E`);
|
|
36
|
-
const bottom = colorFn(`\u2570${hBar}\u256F`);
|
|
37
|
-
const left = colorFn("\u2502");
|
|
38
|
-
const right = colorFn("\u2502");
|
|
39
|
-
|
|
40
|
-
const wrapped = lines.map((line) => {
|
|
41
|
-
const contentWidth = visibleWidth(line);
|
|
42
|
-
const fill = Math.max(0, innerWidth - contentWidth);
|
|
43
|
-
return `${left}${line}${" ".repeat(fill)}${right}`;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
return [top, ...wrapped, bottom];
|
|
17
|
+
export function buildSyntheticProviderModels(includeProxiedModels: boolean) {
|
|
18
|
+
return SYNTHETIC_MODELS.filter(
|
|
19
|
+
(model) => includeProxiedModels || model.provider === "synthetic",
|
|
20
|
+
).map(({ provider: _provider, ...model }) => ({
|
|
21
|
+
...model,
|
|
22
|
+
compat: {
|
|
23
|
+
supportsDeveloperRole: false,
|
|
24
|
+
maxTokensField: "max_tokens" as const,
|
|
25
|
+
...model.compat,
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
47
28
|
}
|
|
48
29
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
text: string,
|
|
52
|
-
colorFn: (text: string) => string,
|
|
53
|
-
): string {
|
|
54
|
-
return text.replace(/`([^`]+)`/g, (_, code) => colorFn(code));
|
|
30
|
+
interface RegisterSyntheticProviderOptions {
|
|
31
|
+
includeProxiedModels: boolean;
|
|
55
32
|
}
|
|
56
33
|
|
|
57
|
-
export function registerSyntheticProvider(
|
|
34
|
+
export function registerSyntheticProvider(
|
|
35
|
+
pi: ExtensionAPI,
|
|
36
|
+
options: RegisterSyntheticProviderOptions,
|
|
37
|
+
): void {
|
|
58
38
|
pi.registerProvider("synthetic", {
|
|
59
39
|
baseUrl: "https://api.synthetic.new/openai/v1",
|
|
60
40
|
apiKey: "SYNTHETIC_API_KEY",
|
|
@@ -63,50 +43,22 @@ export function registerSyntheticProvider(pi: ExtensionAPI): void {
|
|
|
63
43
|
Referer: "https://pi.dev",
|
|
64
44
|
"X-Title": "npm:@aliou/pi-synthetic",
|
|
65
45
|
},
|
|
66
|
-
models:
|
|
67
|
-
...model,
|
|
68
|
-
compat: {
|
|
69
|
-
supportsDeveloperRole: false,
|
|
70
|
-
maxTokensField: "max_tokens",
|
|
71
|
-
...model.compat,
|
|
72
|
-
},
|
|
73
|
-
})),
|
|
46
|
+
models: buildSyntheticProviderModels(options.includeProxiedModels),
|
|
74
47
|
});
|
|
75
48
|
}
|
|
76
49
|
|
|
77
50
|
export default async function (pi: ExtensionAPI) {
|
|
78
51
|
await configLoader.load();
|
|
79
52
|
await seedSyntheticConfigIfMissing();
|
|
80
|
-
registerSyntheticProvider(pi);
|
|
81
53
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
(message, _options, theme) => {
|
|
85
|
-
const rawContent =
|
|
86
|
-
typeof message.content === "string"
|
|
87
|
-
? message.content
|
|
88
|
-
: MIGRATION_NOTICE_CONTENT;
|
|
89
|
-
const accent = (t: string) => theme.fg("accent", t);
|
|
90
|
-
const borderColor = accent;
|
|
91
|
-
const title = theme.bold(accent(MIGRATION_NOTICE_TITLE));
|
|
92
|
-
const body = highlightInlineCode(rawContent, accent);
|
|
54
|
+
const includeProxiedModels = configLoader.getConfig().proxiedModels;
|
|
55
|
+
registerSyntheticProvider(pi, { includeProxiedModels });
|
|
93
56
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const lines = [title, "", ...bodyLines];
|
|
100
|
-
const padded = lines.map((line) => ` ${line} `);
|
|
101
|
-
return wrapInRoundedBorder(padded, width, borderColor);
|
|
102
|
-
},
|
|
103
|
-
handleInput() {
|
|
104
|
-
return false;
|
|
105
|
-
},
|
|
106
|
-
invalidate() {},
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
);
|
|
57
|
+
pi.events.on(SYNTHETIC_CONFIG_UPDATED_EVENT, (data: unknown) => {
|
|
58
|
+
const includeProxiedModels = (data as SyntheticConfigUpdatedPayload).config
|
|
59
|
+
.proxiedModels;
|
|
60
|
+
registerSyntheticProvider(pi, { includeProxiedModels });
|
|
61
|
+
});
|
|
110
62
|
|
|
111
63
|
const loadedFeatures = new Set<SyntheticFeatureId>();
|
|
112
64
|
|
|
@@ -119,21 +71,17 @@ export default async function (pi: ExtensionAPI) {
|
|
|
119
71
|
getLoadedFeatures: () => loadedFeatures,
|
|
120
72
|
});
|
|
121
73
|
|
|
122
|
-
pi.on("session_start", async () => {
|
|
74
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
75
|
+
const messages = pendingMessages.splice(0).map((m) => `- ${m}`);
|
|
76
|
+
if (messages.length > 0) {
|
|
77
|
+
ctx.ui.notify(
|
|
78
|
+
`[synthetic] Migration messages: \n ${messages.join("\n")}`,
|
|
79
|
+
"info",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
123
83
|
loadedFeatures.clear();
|
|
124
84
|
pi.events.emit(SYNTHETIC_EXTENSIONS_REQUEST_EVENT, undefined);
|
|
125
85
|
emitSyntheticConfigUpdated(pi);
|
|
126
|
-
|
|
127
|
-
if (hasPendingMigrationNotice()) {
|
|
128
|
-
clearPendingMigrationNotice();
|
|
129
|
-
pi.sendMessage(
|
|
130
|
-
{
|
|
131
|
-
customType: MIGRATION_NOTICE_MESSAGE_TYPE,
|
|
132
|
-
content: MIGRATION_NOTICE_CONTENT,
|
|
133
|
-
display: true,
|
|
134
|
-
},
|
|
135
|
-
{ triggerTurn: false },
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
86
|
});
|
|
139
87
|
}
|
|
@@ -9,14 +9,6 @@ export interface SyntheticModelConfig extends ProviderModelConfig {
|
|
|
9
9
|
provider: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const SYNTHETIC_REASONING_EFFORT_MAP = {
|
|
13
|
-
minimal: "low",
|
|
14
|
-
low: "low",
|
|
15
|
-
medium: "medium",
|
|
16
|
-
high: "high",
|
|
17
|
-
xhigh: "high",
|
|
18
|
-
} as const;
|
|
19
|
-
|
|
20
12
|
export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
21
13
|
// API: hf:zai-org/GLM-4.7 → ctx=202752
|
|
22
14
|
{
|
|
@@ -24,9 +16,9 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
24
16
|
name: "zai-org/GLM-4.7",
|
|
25
17
|
provider: "synthetic",
|
|
26
18
|
reasoning: true,
|
|
19
|
+
thinkingLevelMap: { minimal: null, xhigh: null },
|
|
27
20
|
compat: {
|
|
28
21
|
supportsReasoningEffort: true,
|
|
29
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
30
22
|
},
|
|
31
23
|
input: ["text"],
|
|
32
24
|
cost: {
|
|
@@ -44,9 +36,9 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
44
36
|
name: "zai-org/GLM-5",
|
|
45
37
|
provider: "synthetic",
|
|
46
38
|
reasoning: true,
|
|
39
|
+
thinkingLevelMap: { minimal: null, xhigh: null },
|
|
47
40
|
compat: {
|
|
48
41
|
supportsReasoningEffort: true,
|
|
49
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
50
42
|
},
|
|
51
43
|
input: ["text"],
|
|
52
44
|
cost: {
|
|
@@ -64,9 +56,9 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
64
56
|
name: "zai-org/GLM-5.1",
|
|
65
57
|
provider: "synthetic",
|
|
66
58
|
reasoning: true,
|
|
59
|
+
thinkingLevelMap: { minimal: null, xhigh: null },
|
|
67
60
|
compat: {
|
|
68
61
|
supportsReasoningEffort: true,
|
|
69
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
70
62
|
supportsDeveloperRole: false,
|
|
71
63
|
},
|
|
72
64
|
input: ["text"],
|
|
@@ -85,9 +77,9 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
85
77
|
name: "zai-org/GLM-4.7-Flash",
|
|
86
78
|
provider: "synthetic",
|
|
87
79
|
reasoning: true,
|
|
80
|
+
thinkingLevelMap: { minimal: null, xhigh: null },
|
|
88
81
|
compat: {
|
|
89
82
|
supportsReasoningEffort: true,
|
|
90
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
91
83
|
},
|
|
92
84
|
input: ["text"],
|
|
93
85
|
cost: {
|
|
@@ -104,7 +96,7 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
104
96
|
id: "hf:meta-llama/Llama-3.3-70B-Instruct",
|
|
105
97
|
name: "meta-llama/Llama-3.3-70B-Instruct",
|
|
106
98
|
provider: "together",
|
|
107
|
-
reasoning:
|
|
99
|
+
reasoning: false,
|
|
108
100
|
input: ["text"],
|
|
109
101
|
cost: {
|
|
110
102
|
input: 0.88,
|
|
@@ -121,10 +113,6 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
121
113
|
name: "deepseek-ai/DeepSeek-R1-0528",
|
|
122
114
|
provider: "together",
|
|
123
115
|
reasoning: true,
|
|
124
|
-
compat: {
|
|
125
|
-
supportsReasoningEffort: true,
|
|
126
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
127
|
-
},
|
|
128
116
|
input: ["text"],
|
|
129
117
|
cost: {
|
|
130
118
|
input: 3,
|
|
@@ -173,10 +161,6 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
173
161
|
name: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
174
162
|
provider: "together",
|
|
175
163
|
reasoning: true,
|
|
176
|
-
compat: {
|
|
177
|
-
supportsReasoningEffort: true,
|
|
178
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
179
|
-
},
|
|
180
164
|
input: ["text"],
|
|
181
165
|
cost: {
|
|
182
166
|
input: 2,
|
|
@@ -193,9 +177,9 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
193
177
|
name: "moonshotai/Kimi-K2.6",
|
|
194
178
|
provider: "synthetic",
|
|
195
179
|
reasoning: true,
|
|
180
|
+
thinkingLevelMap: { minimal: null, low: null, xhigh: null },
|
|
196
181
|
compat: {
|
|
197
182
|
supportsReasoningEffort: true,
|
|
198
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
199
183
|
},
|
|
200
184
|
input: ["text", "image"],
|
|
201
185
|
cost: {
|
|
@@ -207,16 +191,28 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
207
191
|
contextWindow: 262144,
|
|
208
192
|
maxTokens: 65536,
|
|
209
193
|
},
|
|
194
|
+
// API: hf:moonshotai/Kimi-K2.5 → ctx=262144, out=65536
|
|
195
|
+
{
|
|
196
|
+
id: "hf:moonshotai/Kimi-K2.5",
|
|
197
|
+
name: "moonshotai/Kimi-K2.5",
|
|
198
|
+
provider: "together",
|
|
199
|
+
reasoning: true,
|
|
200
|
+
input: ["text", "image"],
|
|
201
|
+
cost: {
|
|
202
|
+
input: 0.5,
|
|
203
|
+
output: 2.8,
|
|
204
|
+
cacheRead: 0.5,
|
|
205
|
+
cacheWrite: 0,
|
|
206
|
+
},
|
|
207
|
+
contextWindow: 262144,
|
|
208
|
+
maxTokens: 65536,
|
|
209
|
+
},
|
|
210
210
|
// API: hf:nvidia/Kimi-K2.5-NVFP4 → ctx=262144; models.dev: out=65536 (NVFP4 quantized)
|
|
211
211
|
{
|
|
212
212
|
id: "hf:nvidia/Kimi-K2.5-NVFP4",
|
|
213
213
|
name: "nvidia/Kimi-K2.5-NVFP4",
|
|
214
214
|
provider: "together",
|
|
215
215
|
reasoning: true,
|
|
216
|
-
compat: {
|
|
217
|
-
supportsReasoningEffort: true,
|
|
218
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
219
|
-
},
|
|
220
216
|
input: ["text", "image"],
|
|
221
217
|
cost: {
|
|
222
218
|
input: 0.5,
|
|
@@ -249,10 +245,6 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
249
245
|
name: "Qwen/Qwen3-235B-A22B-Thinking-2507",
|
|
250
246
|
provider: "together",
|
|
251
247
|
reasoning: true,
|
|
252
|
-
compat: {
|
|
253
|
-
supportsReasoningEffort: true,
|
|
254
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
255
|
-
},
|
|
256
248
|
input: ["text"],
|
|
257
249
|
cost: {
|
|
258
250
|
input: 0.65,
|
|
@@ -269,10 +261,6 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
269
261
|
name: "Qwen/Qwen3.5-397B-A17B",
|
|
270
262
|
provider: "together",
|
|
271
263
|
reasoning: true,
|
|
272
|
-
compat: {
|
|
273
|
-
supportsReasoningEffort: true,
|
|
274
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
275
|
-
},
|
|
276
264
|
input: ["text", "image"],
|
|
277
265
|
cost: {
|
|
278
266
|
input: 0.6,
|
|
@@ -289,6 +277,7 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
289
277
|
name: "MiniMaxAI/MiniMax-M2.5",
|
|
290
278
|
provider: "synthetic",
|
|
291
279
|
reasoning: true,
|
|
280
|
+
thinkingLevelMap: { off: null, minimal: null, low: null, xhigh: null },
|
|
292
281
|
input: ["text"],
|
|
293
282
|
cost: {
|
|
294
283
|
input: 0.4,
|
|
@@ -300,7 +289,6 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
300
289
|
maxTokens: 65536,
|
|
301
290
|
compat: {
|
|
302
291
|
supportsReasoningEffort: true,
|
|
303
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
304
292
|
maxTokensField: "max_completion_tokens",
|
|
305
293
|
},
|
|
306
294
|
},
|
|
@@ -310,9 +298,9 @@ export const SYNTHETIC_MODELS: SyntheticModelConfig[] = [
|
|
|
310
298
|
name: "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
|
|
311
299
|
provider: "synthetic",
|
|
312
300
|
reasoning: true,
|
|
301
|
+
thinkingLevelMap: { minimal: null, low: null, xhigh: null },
|
|
313
302
|
compat: {
|
|
314
303
|
supportsReasoningEffort: true,
|
|
315
|
-
reasoningEffortMap: SYNTHETIC_REASONING_EFFORT_MAP,
|
|
316
304
|
},
|
|
317
305
|
input: ["text"],
|
|
318
306
|
cost: {
|
|
@@ -210,13 +210,20 @@ export default async function (pi: ExtensionAPI) {
|
|
|
210
210
|
void refresher.refreshFor(ctx);
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
-
pi.on("
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
213
|
+
pi.on("session_start", (event, ctx) => {
|
|
214
|
+
// Handle session switches (model_select handles mid-session provider changes)
|
|
215
|
+
if (
|
|
216
|
+
event.reason === "new" ||
|
|
217
|
+
event.reason === "resume" ||
|
|
218
|
+
event.reason === "fork"
|
|
219
|
+
) {
|
|
220
|
+
currentContext = ctx;
|
|
221
|
+
currentProvider = ctx.model?.provider;
|
|
222
|
+
if (enabled && ctx.model?.provider === "synthetic") {
|
|
223
|
+
void refresher.refreshFor(ctx);
|
|
224
|
+
} else {
|
|
225
|
+
refresher.stopAutoRefresh(ctx);
|
|
226
|
+
}
|
|
220
227
|
}
|
|
221
228
|
});
|
|
222
229
|
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
} from "@mariozechner/pi-coding-agent";
|
|
9
9
|
import { getMarkdownTheme, keyHint } from "@mariozechner/pi-coding-agent";
|
|
10
10
|
import { Container, Markdown, Text } from "@mariozechner/pi-tui";
|
|
11
|
-
import { type Static, Type } from "
|
|
11
|
+
import { type Static, Type } from "typebox";
|
|
12
12
|
import { configLoader } from "../../config";
|
|
13
13
|
import { getSyntheticApiKey } from "../../lib/env";
|
|
14
14
|
|