takomi 2.1.1 → 2.1.3
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/.pi/README.md +124 -124
- package/.pi/agents/architect.md +15 -15
- package/.pi/agents/coder.md +14 -14
- package/.pi/agents/designer.md +17 -17
- package/.pi/agents/orchestrator.md +22 -22
- package/.pi/agents/reviewer.md +16 -16
- package/.pi/extensions/oauth-router/README.md +125 -125
- package/.pi/extensions/oauth-router/commands.ts +380 -380
- package/.pi/extensions/oauth-router/config.ts +200 -200
- package/.pi/extensions/oauth-router/index.ts +41 -41
- package/.pi/extensions/oauth-router/oauth-flow.ts +154 -154
- package/.pi/extensions/oauth-router/oauth-store.ts +121 -121
- package/.pi/extensions/oauth-router/package.json +14 -14
- package/.pi/extensions/oauth-router/policies.ts +27 -27
- package/.pi/extensions/oauth-router/provider.ts +492 -492
- package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -98
- package/.pi/extensions/oauth-router/state.ts +174 -174
- package/.pi/extensions/oauth-router/types.ts +153 -153
- package/.pi/extensions/takomi-runtime/command-text.ts +130 -130
- package/.pi/extensions/takomi-runtime/commands.ts +179 -179
- package/.pi/extensions/takomi-runtime/context-panel.ts +282 -282
- package/.pi/extensions/takomi-runtime/index.ts +1288 -1288
- package/.pi/extensions/takomi-runtime/profile.ts +114 -114
- package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -105
- package/.pi/extensions/takomi-runtime/shared.ts +492 -492
- package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -364
- package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -501
- package/.pi/extensions/takomi-runtime/subagent-types.ts +83 -83
- package/.pi/extensions/takomi-runtime/ui.ts +133 -133
- package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -18
- package/.pi/extensions/takomi-subagents/agents.ts +113 -113
- package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -95
- package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -26
- package/.pi/extensions/takomi-subagents/dispatch.ts +215 -215
- package/.pi/extensions/takomi-subagents/index.ts +75 -75
- package/.pi/extensions/takomi-subagents/live-updates.ts +83 -83
- package/.pi/extensions/takomi-subagents/native-render.ts +174 -174
- package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -209
- package/.pi/themes/takomi-noir.json +81 -81
- package/package.json +59 -59
- package/src/cli.js +2 -1
- package/src/doctor.js +87 -84
- package/src/pi-harness.js +355 -351
- package/src/pi-installer.js +193 -171
- package/src/pi-takomi-core/index.ts +4 -4
- package/src/pi-takomi-core/orchestration.ts +402 -402
- package/src/pi-takomi-core/routing.ts +93 -93
- package/src/pi-takomi-core/types.ts +173 -173
- package/src/pi-takomi-core/workflows.ts +299 -299
- package/src/skills-installer.js +101 -101
|
@@ -1,200 +1,200 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import type { RouterConfig, RouterModelConfig, RouterUpstreamConfig } from "./types.ts";
|
|
5
|
-
|
|
6
|
-
export const EXTENSION_ROOT = join(homedir(), ".pi", "agent", "extensions", "oauth-router");
|
|
7
|
-
export const DATA_ROOT = join(homedir(), ".pi", "agent", "oauth-router");
|
|
8
|
-
export const CONFIG_PATH = join(DATA_ROOT, "config.json");
|
|
9
|
-
export const CREDENTIALS_PATH = join(DATA_ROOT, "credentials.json");
|
|
10
|
-
export const STATE_PATH = join(DATA_ROOT, "state.json");
|
|
11
|
-
|
|
12
|
-
const DEFAULT_MODELS: RouterModelConfig[] = [
|
|
13
|
-
{
|
|
14
|
-
id: "gpt-4o",
|
|
15
|
-
name: "GPT-4o",
|
|
16
|
-
reasoning: false,
|
|
17
|
-
input: ["text", "image"],
|
|
18
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
19
|
-
contextWindow: 128000,
|
|
20
|
-
maxTokens: 16384,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
id: "gpt-4.1",
|
|
24
|
-
name: "GPT-4.1",
|
|
25
|
-
reasoning: false,
|
|
26
|
-
input: ["text", "image"],
|
|
27
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
28
|
-
contextWindow: 128000,
|
|
29
|
-
maxTokens: 16384,
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: "o4-mini",
|
|
33
|
-
name: "o4-mini",
|
|
34
|
-
reasoning: true,
|
|
35
|
-
input: ["text", "image"],
|
|
36
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
37
|
-
contextWindow: 200000,
|
|
38
|
-
maxTokens: 100000,
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
id: "gpt-5.1",
|
|
42
|
-
name: "GPT-5.1",
|
|
43
|
-
reasoning: true,
|
|
44
|
-
input: ["text", "image"],
|
|
45
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
46
|
-
contextWindow: 272000,
|
|
47
|
-
maxTokens: 128000,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: "gpt-5.4",
|
|
51
|
-
name: "GPT-5.4",
|
|
52
|
-
reasoning: true,
|
|
53
|
-
input: ["text", "image"],
|
|
54
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
55
|
-
contextWindow: 272000,
|
|
56
|
-
maxTokens: 128000,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: "gpt-5.4-mini",
|
|
60
|
-
name: "GPT-5.4 Mini",
|
|
61
|
-
reasoning: true,
|
|
62
|
-
input: ["text", "image"],
|
|
63
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
64
|
-
contextWindow: 272000,
|
|
65
|
-
maxTokens: 128000,
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: "gpt-5.5",
|
|
69
|
-
name: "GPT-5.5",
|
|
70
|
-
reasoning: true,
|
|
71
|
-
input: ["text", "image"],
|
|
72
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
73
|
-
contextWindow: 272000,
|
|
74
|
-
maxTokens: 128000,
|
|
75
|
-
},
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
const DEFAULT_UPSTREAMS: RouterUpstreamConfig[] = [
|
|
79
|
-
{
|
|
80
|
-
id: "openai-compatible",
|
|
81
|
-
label: "OpenAI Compatible API",
|
|
82
|
-
description: "API-key fallback route for standard OpenAI-compatible responses endpoints.",
|
|
83
|
-
baseUrl: "https://api.openai.com/v1",
|
|
84
|
-
api: "openai-responses",
|
|
85
|
-
authMode: "api-key",
|
|
86
|
-
enabled: true,
|
|
87
|
-
modelIds: ["gpt-4o", "gpt-4.1", "o4-mini"],
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: "chatgpt-codex",
|
|
91
|
-
label: "ChatGPT Codex OAuth",
|
|
92
|
-
description: "OAuth-backed ChatGPT Plus/Pro Codex route using Pi's OpenAI Codex OAuth implementation.",
|
|
93
|
-
baseUrl: "https://chatgpt.com/backend-api",
|
|
94
|
-
api: "openai-codex-responses",
|
|
95
|
-
authMode: "oauth",
|
|
96
|
-
oauthProviderId: "openai-codex",
|
|
97
|
-
enabled: true,
|
|
98
|
-
modelIds: ["gpt-5.1", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5"],
|
|
99
|
-
},
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
export const DEFAULT_CONFIG: RouterConfig = {
|
|
103
|
-
version: 1,
|
|
104
|
-
providerName: "oauth-router",
|
|
105
|
-
policy: "round-robin",
|
|
106
|
-
tokenRefreshSkewMs: 60_000,
|
|
107
|
-
rateLimitCooldownMs: 120_000,
|
|
108
|
-
transientPenaltyMs: 30_000,
|
|
109
|
-
models: DEFAULT_MODELS,
|
|
110
|
-
upstreams: DEFAULT_UPSTREAMS,
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
function applySecurePermissions(path: string, mode: number) {
|
|
114
|
-
try {
|
|
115
|
-
chmodSync(path, mode);
|
|
116
|
-
} catch {
|
|
117
|
-
// Best effort only. Windows commonly ignores POSIX chmod semantics.
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function ensureDirectory(path: string) {
|
|
122
|
-
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
123
|
-
applySecurePermissions(path, 0o700);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function writeJsonFile(path: string, value: unknown, secure = true) {
|
|
127
|
-
ensureDirectory(dirname(path));
|
|
128
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: secure ? 0o600 : 0o644 });
|
|
129
|
-
applySecurePermissions(path, secure ? 0o600 : 0o644);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function deepClone<T>(value: T): T {
|
|
133
|
-
return JSON.parse(JSON.stringify(value)) as T;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function mergeModelConfigs(candidateModels: RouterModelConfig[] | undefined): RouterModelConfig[] {
|
|
137
|
-
if (!Array.isArray(candidateModels) || candidateModels.length === 0) {
|
|
138
|
-
return deepClone(DEFAULT_CONFIG.models);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const merged = new Map(DEFAULT_CONFIG.models.map((model) => [model.id, deepClone(model)]));
|
|
142
|
-
for (const model of candidateModels) {
|
|
143
|
-
const previous = merged.get(model.id) ?? ({} as RouterModelConfig);
|
|
144
|
-
merged.set(model.id, { ...previous, ...deepClone(model), id: model.id });
|
|
145
|
-
}
|
|
146
|
-
return Array.from(merged.values());
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function mergeUpstreamConfigs(candidateUpstreams: RouterUpstreamConfig[] | undefined): RouterUpstreamConfig[] {
|
|
150
|
-
if (!Array.isArray(candidateUpstreams) || candidateUpstreams.length === 0) {
|
|
151
|
-
return deepClone(DEFAULT_CONFIG.upstreams);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const merged = new Map(DEFAULT_CONFIG.upstreams.map((upstream) => [upstream.id, deepClone(upstream)]));
|
|
155
|
-
for (const upstream of candidateUpstreams) {
|
|
156
|
-
const previous = merged.get(upstream.id);
|
|
157
|
-
if (!previous) {
|
|
158
|
-
merged.set(upstream.id, deepClone(upstream));
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const modelIds = Array.from(new Set([...(previous.modelIds ?? []), ...(upstream.modelIds ?? [])]));
|
|
163
|
-
merged.set(upstream.id, { ...previous, ...deepClone(upstream), id: upstream.id, modelIds });
|
|
164
|
-
}
|
|
165
|
-
return Array.from(merged.values());
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function mergeConfig(candidate: Partial<RouterConfig> | undefined): RouterConfig {
|
|
169
|
-
return {
|
|
170
|
-
...DEFAULT_CONFIG,
|
|
171
|
-
...candidate,
|
|
172
|
-
providerName: "oauth-router",
|
|
173
|
-
version: 1,
|
|
174
|
-
models: mergeModelConfigs(candidate?.models),
|
|
175
|
-
upstreams: mergeUpstreamConfigs(candidate?.upstreams),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function loadRouterConfig(): RouterConfig {
|
|
180
|
-
ensureDirectory(DATA_ROOT);
|
|
181
|
-
|
|
182
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
183
|
-
const initial = deepClone(DEFAULT_CONFIG);
|
|
184
|
-
writeJsonFile(CONFIG_PATH, initial, false);
|
|
185
|
-
return initial;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as Partial<RouterConfig>;
|
|
190
|
-
const merged = mergeConfig(parsed);
|
|
191
|
-
if (JSON.stringify(parsed) !== JSON.stringify(merged)) {
|
|
192
|
-
writeJsonFile(CONFIG_PATH, merged, false);
|
|
193
|
-
}
|
|
194
|
-
return merged;
|
|
195
|
-
} catch {
|
|
196
|
-
const fallback = deepClone(DEFAULT_CONFIG);
|
|
197
|
-
writeJsonFile(CONFIG_PATH, fallback, false);
|
|
198
|
-
return fallback;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import type { RouterConfig, RouterModelConfig, RouterUpstreamConfig } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
export const EXTENSION_ROOT = join(homedir(), ".pi", "agent", "extensions", "oauth-router");
|
|
7
|
+
export const DATA_ROOT = join(homedir(), ".pi", "agent", "oauth-router");
|
|
8
|
+
export const CONFIG_PATH = join(DATA_ROOT, "config.json");
|
|
9
|
+
export const CREDENTIALS_PATH = join(DATA_ROOT, "credentials.json");
|
|
10
|
+
export const STATE_PATH = join(DATA_ROOT, "state.json");
|
|
11
|
+
|
|
12
|
+
const DEFAULT_MODELS: RouterModelConfig[] = [
|
|
13
|
+
{
|
|
14
|
+
id: "gpt-4o",
|
|
15
|
+
name: "GPT-4o",
|
|
16
|
+
reasoning: false,
|
|
17
|
+
input: ["text", "image"],
|
|
18
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
19
|
+
contextWindow: 128000,
|
|
20
|
+
maxTokens: 16384,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "gpt-4.1",
|
|
24
|
+
name: "GPT-4.1",
|
|
25
|
+
reasoning: false,
|
|
26
|
+
input: ["text", "image"],
|
|
27
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
28
|
+
contextWindow: 128000,
|
|
29
|
+
maxTokens: 16384,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "o4-mini",
|
|
33
|
+
name: "o4-mini",
|
|
34
|
+
reasoning: true,
|
|
35
|
+
input: ["text", "image"],
|
|
36
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
37
|
+
contextWindow: 200000,
|
|
38
|
+
maxTokens: 100000,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "gpt-5.1",
|
|
42
|
+
name: "GPT-5.1",
|
|
43
|
+
reasoning: true,
|
|
44
|
+
input: ["text", "image"],
|
|
45
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
46
|
+
contextWindow: 272000,
|
|
47
|
+
maxTokens: 128000,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "gpt-5.4",
|
|
51
|
+
name: "GPT-5.4",
|
|
52
|
+
reasoning: true,
|
|
53
|
+
input: ["text", "image"],
|
|
54
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
55
|
+
contextWindow: 272000,
|
|
56
|
+
maxTokens: 128000,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "gpt-5.4-mini",
|
|
60
|
+
name: "GPT-5.4 Mini",
|
|
61
|
+
reasoning: true,
|
|
62
|
+
input: ["text", "image"],
|
|
63
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
64
|
+
contextWindow: 272000,
|
|
65
|
+
maxTokens: 128000,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "gpt-5.5",
|
|
69
|
+
name: "GPT-5.5",
|
|
70
|
+
reasoning: true,
|
|
71
|
+
input: ["text", "image"],
|
|
72
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
73
|
+
contextWindow: 272000,
|
|
74
|
+
maxTokens: 128000,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const DEFAULT_UPSTREAMS: RouterUpstreamConfig[] = [
|
|
79
|
+
{
|
|
80
|
+
id: "openai-compatible",
|
|
81
|
+
label: "OpenAI Compatible API",
|
|
82
|
+
description: "API-key fallback route for standard OpenAI-compatible responses endpoints.",
|
|
83
|
+
baseUrl: "https://api.openai.com/v1",
|
|
84
|
+
api: "openai-responses",
|
|
85
|
+
authMode: "api-key",
|
|
86
|
+
enabled: true,
|
|
87
|
+
modelIds: ["gpt-4o", "gpt-4.1", "o4-mini"],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "chatgpt-codex",
|
|
91
|
+
label: "ChatGPT Codex OAuth",
|
|
92
|
+
description: "OAuth-backed ChatGPT Plus/Pro Codex route using Pi's OpenAI Codex OAuth implementation.",
|
|
93
|
+
baseUrl: "https://chatgpt.com/backend-api",
|
|
94
|
+
api: "openai-codex-responses",
|
|
95
|
+
authMode: "oauth",
|
|
96
|
+
oauthProviderId: "openai-codex",
|
|
97
|
+
enabled: true,
|
|
98
|
+
modelIds: ["gpt-5.1", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5"],
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
export const DEFAULT_CONFIG: RouterConfig = {
|
|
103
|
+
version: 1,
|
|
104
|
+
providerName: "oauth-router",
|
|
105
|
+
policy: "round-robin",
|
|
106
|
+
tokenRefreshSkewMs: 60_000,
|
|
107
|
+
rateLimitCooldownMs: 120_000,
|
|
108
|
+
transientPenaltyMs: 30_000,
|
|
109
|
+
models: DEFAULT_MODELS,
|
|
110
|
+
upstreams: DEFAULT_UPSTREAMS,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function applySecurePermissions(path: string, mode: number) {
|
|
114
|
+
try {
|
|
115
|
+
chmodSync(path, mode);
|
|
116
|
+
} catch {
|
|
117
|
+
// Best effort only. Windows commonly ignores POSIX chmod semantics.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function ensureDirectory(path: string) {
|
|
122
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
123
|
+
applySecurePermissions(path, 0o700);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function writeJsonFile(path: string, value: unknown, secure = true) {
|
|
127
|
+
ensureDirectory(dirname(path));
|
|
128
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: secure ? 0o600 : 0o644 });
|
|
129
|
+
applySecurePermissions(path, secure ? 0o600 : 0o644);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function deepClone<T>(value: T): T {
|
|
133
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function mergeModelConfigs(candidateModels: RouterModelConfig[] | undefined): RouterModelConfig[] {
|
|
137
|
+
if (!Array.isArray(candidateModels) || candidateModels.length === 0) {
|
|
138
|
+
return deepClone(DEFAULT_CONFIG.models);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const merged = new Map(DEFAULT_CONFIG.models.map((model) => [model.id, deepClone(model)]));
|
|
142
|
+
for (const model of candidateModels) {
|
|
143
|
+
const previous = merged.get(model.id) ?? ({} as RouterModelConfig);
|
|
144
|
+
merged.set(model.id, { ...previous, ...deepClone(model), id: model.id });
|
|
145
|
+
}
|
|
146
|
+
return Array.from(merged.values());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function mergeUpstreamConfigs(candidateUpstreams: RouterUpstreamConfig[] | undefined): RouterUpstreamConfig[] {
|
|
150
|
+
if (!Array.isArray(candidateUpstreams) || candidateUpstreams.length === 0) {
|
|
151
|
+
return deepClone(DEFAULT_CONFIG.upstreams);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const merged = new Map(DEFAULT_CONFIG.upstreams.map((upstream) => [upstream.id, deepClone(upstream)]));
|
|
155
|
+
for (const upstream of candidateUpstreams) {
|
|
156
|
+
const previous = merged.get(upstream.id);
|
|
157
|
+
if (!previous) {
|
|
158
|
+
merged.set(upstream.id, deepClone(upstream));
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const modelIds = Array.from(new Set([...(previous.modelIds ?? []), ...(upstream.modelIds ?? [])]));
|
|
163
|
+
merged.set(upstream.id, { ...previous, ...deepClone(upstream), id: upstream.id, modelIds });
|
|
164
|
+
}
|
|
165
|
+
return Array.from(merged.values());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function mergeConfig(candidate: Partial<RouterConfig> | undefined): RouterConfig {
|
|
169
|
+
return {
|
|
170
|
+
...DEFAULT_CONFIG,
|
|
171
|
+
...candidate,
|
|
172
|
+
providerName: "oauth-router",
|
|
173
|
+
version: 1,
|
|
174
|
+
models: mergeModelConfigs(candidate?.models),
|
|
175
|
+
upstreams: mergeUpstreamConfigs(candidate?.upstreams),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function loadRouterConfig(): RouterConfig {
|
|
180
|
+
ensureDirectory(DATA_ROOT);
|
|
181
|
+
|
|
182
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
183
|
+
const initial = deepClone(DEFAULT_CONFIG);
|
|
184
|
+
writeJsonFile(CONFIG_PATH, initial, false);
|
|
185
|
+
return initial;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as Partial<RouterConfig>;
|
|
190
|
+
const merged = mergeConfig(parsed);
|
|
191
|
+
if (JSON.stringify(parsed) !== JSON.stringify(merged)) {
|
|
192
|
+
writeJsonFile(CONFIG_PATH, merged, false);
|
|
193
|
+
}
|
|
194
|
+
return merged;
|
|
195
|
+
} catch {
|
|
196
|
+
const fallback = deepClone(DEFAULT_CONFIG);
|
|
197
|
+
writeJsonFile(CONFIG_PATH, fallback, false);
|
|
198
|
+
return fallback;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { registerRouterCommands, formatStatusReport } from "./commands.ts";
|
|
3
|
-
import { registerRouterProvider, RouterRuntime } from "./provider.ts";
|
|
4
|
-
|
|
5
|
-
function updateFooter(pi: ExtensionAPI, runtime: RouterRuntime, notify = false) {
|
|
6
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
7
|
-
const rows = runtime.getStatusRows();
|
|
8
|
-
const healthy = rows.filter((row) => {
|
|
9
|
-
if (!row.enabled) return false;
|
|
10
|
-
if (row.authHealth !== "ok") return false;
|
|
11
|
-
if (row.cooldownUntil && row.cooldownUntil > Date.now()) return false;
|
|
12
|
-
if (row.penaltyUntil && row.penaltyUntil > Date.now()) return false;
|
|
13
|
-
return true;
|
|
14
|
-
}).length;
|
|
15
|
-
|
|
16
|
-
ctx.ui.setStatus("oauth-router", `oauth-router ${healthy}/${rows.length || 0} healthy | ${runtime.getPolicy()}`);
|
|
17
|
-
if (notify) {
|
|
18
|
-
ctx.ui.notify("oauth-router loaded", "info");
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default function (pi: ExtensionAPI) {
|
|
24
|
-
const runtime = new RouterRuntime();
|
|
25
|
-
|
|
26
|
-
registerRouterProvider(pi, runtime);
|
|
27
|
-
registerRouterCommands(pi, runtime);
|
|
28
|
-
updateFooter(pi, runtime, false);
|
|
29
|
-
|
|
30
|
-
pi.registerCommand("router-debug-report", {
|
|
31
|
-
description: "Emit a detailed oauth-router report",
|
|
32
|
-
handler: async () => {
|
|
33
|
-
pi.sendMessage({
|
|
34
|
-
customType: "oauth-router",
|
|
35
|
-
content: formatStatusReport(runtime),
|
|
36
|
-
display: true,
|
|
37
|
-
details: { source: "oauth-router", debug: true },
|
|
38
|
-
});
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
}
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { registerRouterCommands, formatStatusReport } from "./commands.ts";
|
|
3
|
+
import { registerRouterProvider, RouterRuntime } from "./provider.ts";
|
|
4
|
+
|
|
5
|
+
function updateFooter(pi: ExtensionAPI, runtime: RouterRuntime, notify = false) {
|
|
6
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
7
|
+
const rows = runtime.getStatusRows();
|
|
8
|
+
const healthy = rows.filter((row) => {
|
|
9
|
+
if (!row.enabled) return false;
|
|
10
|
+
if (row.authHealth !== "ok") return false;
|
|
11
|
+
if (row.cooldownUntil && row.cooldownUntil > Date.now()) return false;
|
|
12
|
+
if (row.penaltyUntil && row.penaltyUntil > Date.now()) return false;
|
|
13
|
+
return true;
|
|
14
|
+
}).length;
|
|
15
|
+
|
|
16
|
+
ctx.ui.setStatus("oauth-router", `oauth-router ${healthy}/${rows.length || 0} healthy | ${runtime.getPolicy()}`);
|
|
17
|
+
if (notify) {
|
|
18
|
+
ctx.ui.notify("oauth-router loaded", "info");
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function (pi: ExtensionAPI) {
|
|
24
|
+
const runtime = new RouterRuntime();
|
|
25
|
+
|
|
26
|
+
registerRouterProvider(pi, runtime);
|
|
27
|
+
registerRouterCommands(pi, runtime);
|
|
28
|
+
updateFooter(pi, runtime, false);
|
|
29
|
+
|
|
30
|
+
pi.registerCommand("router-debug-report", {
|
|
31
|
+
description: "Emit a detailed oauth-router report",
|
|
32
|
+
handler: async () => {
|
|
33
|
+
pi.sendMessage({
|
|
34
|
+
customType: "oauth-router",
|
|
35
|
+
content: formatStatusReport(runtime),
|
|
36
|
+
display: true,
|
|
37
|
+
details: { source: "oauth-router", debug: true },
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|