agentxl 1.0.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 +409 -0
- package/bin/agentxl.js +377 -0
- package/dist/agent/auth.d.ts +2 -0
- package/dist/agent/auth.d.ts.map +1 -0
- package/dist/agent/auth.js +2 -0
- package/dist/agent/auth.js.map +1 -0
- package/dist/agent/models.d.ts +21 -0
- package/dist/agent/models.d.ts.map +1 -0
- package/dist/agent/models.js +46 -0
- package/dist/agent/models.js.map +1 -0
- package/dist/agent/provider/azure-provider.d.ts +2 -0
- package/dist/agent/provider/azure-provider.d.ts.map +1 -0
- package/dist/agent/provider/azure-provider.js +2 -0
- package/dist/agent/provider/azure-provider.js.map +1 -0
- package/dist/agent/session.d.ts +43 -0
- package/dist/agent/session.d.ts.map +1 -0
- package/dist/agent/session.js +145 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/agent/tools/excel-tools.d.ts +2 -0
- package/dist/agent/tools/excel-tools.d.ts.map +1 -0
- package/dist/agent/tools/excel-tools.js +2 -0
- package/dist/agent/tools/excel-tools.js.map +1 -0
- package/dist/server/certs.d.ts +17 -0
- package/dist/server/certs.d.ts.map +1 -0
- package/dist/server/certs.js +38 -0
- package/dist/server/certs.js.map +1 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +376 -0
- package/dist/server/index.js.map +1 -0
- package/manifest/.gitkeep +0 -0
- package/manifest/manifest.xml +99 -0
- package/package.json +82 -0
- package/taskpane/dist/assets/icon-16.png +0 -0
- package/taskpane/dist/assets/icon-32.png +0 -0
- package/taskpane/dist/assets/icon-64.png +0 -0
- package/taskpane/dist/assets/icon-80.png +0 -0
- package/taskpane/dist/assets/index-6sMpIYxE.css +1 -0
- package/taskpane/dist/assets/index-DyLrQ3Aa.js +164 -0
- package/taskpane/dist/index.html +15 -0
package/bin/agentxl.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, existsSync } from "fs";
|
|
4
|
+
import { resolve, join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { createInterface } from "readline";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Package info
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
const pkgPath = join(__dirname, "..", "package.json");
|
|
17
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
18
|
+
const VERSION = pkg.version;
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Arg parsing
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const command = args[0];
|
|
26
|
+
|
|
27
|
+
function getFlag(name) {
|
|
28
|
+
const eqIdx = args.findIndex((a) => a.startsWith(`--${name}=`));
|
|
29
|
+
if (eqIdx !== -1) return args[eqIdx].split("=")[1];
|
|
30
|
+
const spaceIdx = args.indexOf(`--${name}`);
|
|
31
|
+
if (spaceIdx !== -1 && args[spaceIdx + 1]) return args[spaceIdx + 1];
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const hasFlag = (name) => args.includes(`--${name}`);
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Terminal I/O helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function prompt(question) {
|
|
42
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
rl.question(question, (answer) => {
|
|
45
|
+
rl.close();
|
|
46
|
+
resolve(answer.trim());
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function promptSecret(question) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
process.stdout.write(question);
|
|
54
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
55
|
+
if (process.stdin.isTTY) process.stdin.setRawMode?.(false);
|
|
56
|
+
rl.on("line", (line) => {
|
|
57
|
+
rl.close();
|
|
58
|
+
process.stdout.write("\n");
|
|
59
|
+
resolve(line.trim());
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function openUrl(url) {
|
|
65
|
+
const { exec } = await import("child_process");
|
|
66
|
+
const cmd =
|
|
67
|
+
process.platform === "win32"
|
|
68
|
+
? `start "" "${url}"`
|
|
69
|
+
: process.platform === "darwin"
|
|
70
|
+
? `open "${url}"`
|
|
71
|
+
: `xdg-open "${url}"`;
|
|
72
|
+
exec(cmd);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Step-by-step progress output
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
/** Print a step status: ✅ done, ⏳ in progress, ❌ failed */
|
|
80
|
+
function step(icon, text) {
|
|
81
|
+
console.log(` ${icon} ${text}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Auth flow using Pi SDK
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
async function checkAuth() {
|
|
89
|
+
const { AuthStorage } = await import("@mariozechner/pi-coding-agent");
|
|
90
|
+
|
|
91
|
+
const piAuthPath = join(homedir(), ".pi", "agent", "auth.json");
|
|
92
|
+
const agentxlAuthPath = join(homedir(), ".agentxl", "auth.json");
|
|
93
|
+
const authPath = existsSync(piAuthPath) ? piAuthPath : agentxlAuthPath;
|
|
94
|
+
const authStorage = new AuthStorage(authPath);
|
|
95
|
+
|
|
96
|
+
return authStorage.list().length > 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function runAuthFlow() {
|
|
100
|
+
const { AuthStorage } = await import("@mariozechner/pi-coding-agent");
|
|
101
|
+
const { getOAuthProviders } = await import("@mariozechner/pi-ai");
|
|
102
|
+
|
|
103
|
+
const piAuthPath = join(homedir(), ".pi", "agent", "auth.json");
|
|
104
|
+
const agentxlAuthPath = join(homedir(), ".agentxl", "auth.json");
|
|
105
|
+
const authPath = existsSync(piAuthPath) ? piAuthPath : agentxlAuthPath;
|
|
106
|
+
const authStorage = new AuthStorage(authPath);
|
|
107
|
+
|
|
108
|
+
// Check if already authenticated
|
|
109
|
+
if (authStorage.list().length > 0) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(`
|
|
114
|
+
No API credentials found. Let's get you set up.
|
|
115
|
+
|
|
116
|
+
How would you like to connect?
|
|
117
|
+
|
|
118
|
+
Use an existing subscription (no API key needed):
|
|
119
|
+
1. Claude Pro/Max — sign in with your Anthropic account
|
|
120
|
+
2. ChatGPT Plus/Pro — sign in with your OpenAI account
|
|
121
|
+
3. GitHub Copilot — sign in with your GitHub account
|
|
122
|
+
4. Gemini — sign in with your Google account
|
|
123
|
+
|
|
124
|
+
Use an API key:
|
|
125
|
+
5. Paste an API key (Anthropic, OpenRouter, or OpenAI)
|
|
126
|
+
|
|
127
|
+
No account yet?
|
|
128
|
+
→ Create a free OpenRouter account at https://openrouter.ai
|
|
129
|
+
Get an API key instantly. Free models available.
|
|
130
|
+
`);
|
|
131
|
+
|
|
132
|
+
// Build choices — OAuth providers + API key
|
|
133
|
+
const oauthProviders = getOAuthProviders();
|
|
134
|
+
const choices = [];
|
|
135
|
+
for (const p of oauthProviders) {
|
|
136
|
+
choices.push({ type: "oauth", id: p.id, name: p.name, provider: p });
|
|
137
|
+
}
|
|
138
|
+
choices.push({ type: "apikey", id: "apikey", name: "Paste an API key" });
|
|
139
|
+
|
|
140
|
+
const answer = await prompt(" Enter choice (1-" + choices.length + "): ");
|
|
141
|
+
const idx = parseInt(answer, 10) - 1;
|
|
142
|
+
|
|
143
|
+
if (isNaN(idx) || idx < 0 || idx >= choices.length) {
|
|
144
|
+
console.error("\n Invalid choice. Run 'agentxl login' to try again.\n");
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const choice = choices[idx];
|
|
149
|
+
|
|
150
|
+
if (choice.type === "oauth") {
|
|
151
|
+
console.log(`\n Signing in with ${choice.name}...\n`);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await authStorage.login(choice.id, {
|
|
155
|
+
onAuth: (info) => {
|
|
156
|
+
console.log(` 🌐 Opening browser for sign-in...`);
|
|
157
|
+
console.log(` ${info.url}\n`);
|
|
158
|
+
if (info.instructions) {
|
|
159
|
+
console.log(` ${info.instructions}\n`);
|
|
160
|
+
}
|
|
161
|
+
openUrl(info.url);
|
|
162
|
+
},
|
|
163
|
+
onPrompt: async (p) => {
|
|
164
|
+
const answer = await prompt(` ${p.message}: `);
|
|
165
|
+
return answer;
|
|
166
|
+
},
|
|
167
|
+
onProgress: (message) => {
|
|
168
|
+
console.log(` ${message}`);
|
|
169
|
+
},
|
|
170
|
+
onManualCodeInput: async () => {
|
|
171
|
+
const code = await prompt(" Enter the code from the browser: ");
|
|
172
|
+
return code;
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.log(`\n ✅ Signed in with ${choice.name}\n`);
|
|
177
|
+
return true;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error(`\n ❌ Sign-in failed: ${err.message}\n`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
// API key flow
|
|
184
|
+
console.log(`
|
|
185
|
+
Paste your API key below.
|
|
186
|
+
|
|
187
|
+
Key prefixes:
|
|
188
|
+
sk-ant-... → Anthropic (Claude)
|
|
189
|
+
sk-or-... → OpenRouter (100+ models)
|
|
190
|
+
sk-... → OpenAI (GPT-4o)
|
|
191
|
+
`);
|
|
192
|
+
|
|
193
|
+
const key = await promptSecret(" API key: ");
|
|
194
|
+
|
|
195
|
+
if (!key) {
|
|
196
|
+
console.error("\n No key entered. Run 'agentxl login' to try again.\n");
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Auto-detect provider from key prefix
|
|
201
|
+
let provider;
|
|
202
|
+
if (key.startsWith("sk-ant-")) provider = "anthropic";
|
|
203
|
+
else if (key.startsWith("sk-or-")) provider = "openrouter";
|
|
204
|
+
else if (key.startsWith("sk-")) provider = "openai";
|
|
205
|
+
else {
|
|
206
|
+
const p = await prompt(" Could not detect provider. Enter provider name (anthropic/openrouter/openai): ");
|
|
207
|
+
provider = p.toLowerCase();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
authStorage.set(provider, { type: "api_key", key });
|
|
211
|
+
console.log(`\n ✅ API key saved for ${provider}\n`);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Commands
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
function printHelp() {
|
|
221
|
+
console.log(`
|
|
222
|
+
AgentXL v${VERSION} — AI agent for Microsoft Excel
|
|
223
|
+
|
|
224
|
+
Usage:
|
|
225
|
+
agentxl start [options] Start the AgentXL server
|
|
226
|
+
agentxl login Set up or change API credentials
|
|
227
|
+
agentxl --version Print version
|
|
228
|
+
agentxl --help Show this help
|
|
229
|
+
|
|
230
|
+
Options:
|
|
231
|
+
--port <number> Port to listen on (default: 3001)
|
|
232
|
+
--verbose Log all HTTP requests
|
|
233
|
+
|
|
234
|
+
Examples:
|
|
235
|
+
agentxl start
|
|
236
|
+
agentxl start --port 3002
|
|
237
|
+
agentxl login
|
|
238
|
+
`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function start() {
|
|
242
|
+
const port = parseInt(getFlag("port") || "3001", 10);
|
|
243
|
+
|
|
244
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
245
|
+
console.error(`Error: Invalid port number. Must be 1-65535.`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(`
|
|
250
|
+
┌──────────────────────────────────────┐
|
|
251
|
+
│ AgentXL v${VERSION.padEnd(19)}│
|
|
252
|
+
│ AI agent for Microsoft Excel │
|
|
253
|
+
└──────────────────────────────────────┘
|
|
254
|
+
`);
|
|
255
|
+
|
|
256
|
+
// ── Step 1: Load modules ───────────────────────────────────────────────
|
|
257
|
+
let ensureCerts, startServer, stopServer, setVerbose;
|
|
258
|
+
try {
|
|
259
|
+
const certs = await import("../dist/server/certs.js");
|
|
260
|
+
const server = await import("../dist/server/index.js");
|
|
261
|
+
ensureCerts = certs.ensureCerts;
|
|
262
|
+
startServer = server.startServer;
|
|
263
|
+
stopServer = server.stopServer;
|
|
264
|
+
setVerbose = server.setVerbose;
|
|
265
|
+
} catch (err) {
|
|
266
|
+
step("❌", "Could not load AgentXL server modules");
|
|
267
|
+
console.error(" Run 'npm run build' first to compile TypeScript.");
|
|
268
|
+
console.error(` ${err.message}`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── Step 2: Check auth ─────────────────────────────────────────────────
|
|
273
|
+
const hasAuth = await checkAuth();
|
|
274
|
+
if (hasAuth) {
|
|
275
|
+
step("✅", "Auth ready");
|
|
276
|
+
} else {
|
|
277
|
+
const authed = await runAuthFlow();
|
|
278
|
+
if (!authed) process.exit(1);
|
|
279
|
+
step("✅", "Auth ready");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── Step 3: HTTPS certificates ─────────────────────────────────────────
|
|
283
|
+
try {
|
|
284
|
+
const certPair = await ensureCerts();
|
|
285
|
+
step("✅", "HTTPS certificate ready");
|
|
286
|
+
|
|
287
|
+
// ── Step 4: Start server ───────────────────────────────────────────────
|
|
288
|
+
if (hasFlag("verbose")) setVerbose(true);
|
|
289
|
+
await startServer(port, certPair);
|
|
290
|
+
step("✅", `Server running at https://localhost:${port}`);
|
|
291
|
+
} catch (err) {
|
|
292
|
+
step("❌", `Server failed to start: ${err.message}`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── Post-start guidance ────────────────────────────────────────────────
|
|
297
|
+
const manifestPath = resolve(__dirname, "..", "manifest", "manifest.xml");
|
|
298
|
+
const manifestExists = existsSync(manifestPath);
|
|
299
|
+
|
|
300
|
+
console.log(`
|
|
301
|
+
─────────────────────────────────────────────────
|
|
302
|
+
All systems go. Here's what to do next:
|
|
303
|
+
─────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
🌐 Test in browser (confirm everything works):
|
|
306
|
+
https://localhost:${port}/taskpane/
|
|
307
|
+
|
|
308
|
+
📎 Load in Excel (one-time setup):
|
|
309
|
+
1. Excel → File → Options → Trust Center → Trust Center Settings
|
|
310
|
+
2. Trusted Add-in Catalogs → add path: ${manifestExists ? dirname(manifestPath) : "[manifest folder]"}
|
|
311
|
+
3. Check "Show in Menu" → OK → OK
|
|
312
|
+
4. Restart Excel
|
|
313
|
+
5. Insert → My Add-ins → SHARED FOLDER → AgentXL → Add
|
|
314
|
+
|
|
315
|
+
After setup, just run 'agentxl start' and click
|
|
316
|
+
AgentXL on the Home ribbon. No re-sideloading needed.
|
|
317
|
+
|
|
318
|
+
💬 Try your first message:
|
|
319
|
+
"What can you help me with in this workbook?"
|
|
320
|
+
`);
|
|
321
|
+
|
|
322
|
+
// ── Graceful shutdown ──────────────────────────────────────────────────
|
|
323
|
+
let shuttingDown = false;
|
|
324
|
+
const shutdown = () => {
|
|
325
|
+
if (shuttingDown) return;
|
|
326
|
+
shuttingDown = true;
|
|
327
|
+
console.log("\n AgentXL stopped. Goodbye!\n");
|
|
328
|
+
const forceExit = setTimeout(() => process.exit(0), 2000);
|
|
329
|
+
forceExit.unref?.();
|
|
330
|
+
stopServer().then(() => process.exit(0));
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
process.on("SIGINT", shutdown);
|
|
334
|
+
process.on("SIGTERM", shutdown);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function login() {
|
|
338
|
+
console.log("");
|
|
339
|
+
const authed = await runAuthFlow();
|
|
340
|
+
if (authed) {
|
|
341
|
+
console.log(" Run 'agentxl start' to launch the server.\n");
|
|
342
|
+
}
|
|
343
|
+
process.exit(authed ? 0 : 1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
// Main
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
if (hasFlag("version") || command === "--version") {
|
|
351
|
+
console.log(VERSION);
|
|
352
|
+
process.exit(0);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (hasFlag("help") || command === "--help" || command === "help") {
|
|
356
|
+
printHelp();
|
|
357
|
+
process.exit(0);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (command === "start") {
|
|
361
|
+
start().catch((err) => {
|
|
362
|
+
console.error(`\n ❌ ${err.message || err}\n`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
});
|
|
365
|
+
} else if (command === "login") {
|
|
366
|
+
login().catch((err) => {
|
|
367
|
+
console.error(`\n ❌ ${err.message || err}\n`);
|
|
368
|
+
process.exit(1);
|
|
369
|
+
});
|
|
370
|
+
} else if (!command) {
|
|
371
|
+
printHelp();
|
|
372
|
+
process.exit(0);
|
|
373
|
+
} else {
|
|
374
|
+
console.error(`Unknown command: ${command}`);
|
|
375
|
+
console.error(`Run 'agentxl --help' for usage.`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/agent/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/agent/auth.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default model selection per provider.
|
|
3
|
+
*
|
|
4
|
+
* Checks providers in order: Anthropic → OpenRouter → OpenAI.
|
|
5
|
+
* Prefers subscriptions (OAuth) over API keys — subscriptions are already paid for.
|
|
6
|
+
* Returns the first model that has auth configured.
|
|
7
|
+
*/
|
|
8
|
+
import type { Model, Api } from "@mariozechner/pi-ai";
|
|
9
|
+
import type { ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
10
|
+
/**
|
|
11
|
+
* Get the best available model based on configured auth.
|
|
12
|
+
*
|
|
13
|
+
* Priority:
|
|
14
|
+
* 1. Subscriptions (OAuth) — Anthropic, OpenAI Codex (already paid for)
|
|
15
|
+
* 2. API keys — OpenRouter, OpenAI, etc.
|
|
16
|
+
*
|
|
17
|
+
* Within each tier, checks PREFERRED_MODELS in order.
|
|
18
|
+
* Returns null if no provider has auth configured.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getDefaultModel(modelRegistry: ModelRegistry): Model<Api> | null;
|
|
21
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/agent/models.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAUnE;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,aAAa,GAC3B,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAsBnB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default model selection per provider.
|
|
3
|
+
*
|
|
4
|
+
* Checks providers in order: Anthropic → OpenRouter → OpenAI.
|
|
5
|
+
* Prefers subscriptions (OAuth) over API keys — subscriptions are already paid for.
|
|
6
|
+
* Returns the first model that has auth configured.
|
|
7
|
+
*/
|
|
8
|
+
/** Provider → preferred model ID, checked in priority order. */
|
|
9
|
+
const PREFERRED_MODELS = [
|
|
10
|
+
{ provider: "anthropic", modelId: "claude-sonnet-4-20250514" },
|
|
11
|
+
{ provider: "openai-codex", modelId: "gpt-5.1" },
|
|
12
|
+
{ provider: "openrouter", modelId: "anthropic/claude-sonnet-4" },
|
|
13
|
+
{ provider: "openai", modelId: "gpt-4o" },
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Get the best available model based on configured auth.
|
|
17
|
+
*
|
|
18
|
+
* Priority:
|
|
19
|
+
* 1. Subscriptions (OAuth) — Anthropic, OpenAI Codex (already paid for)
|
|
20
|
+
* 2. API keys — OpenRouter, OpenAI, etc.
|
|
21
|
+
*
|
|
22
|
+
* Within each tier, checks PREFERRED_MODELS in order.
|
|
23
|
+
* Returns null if no provider has auth configured.
|
|
24
|
+
*/
|
|
25
|
+
export function getDefaultModel(modelRegistry) {
|
|
26
|
+
// Only consider models that have auth configured
|
|
27
|
+
const available = modelRegistry.getAvailable();
|
|
28
|
+
if (available.length === 0)
|
|
29
|
+
return null;
|
|
30
|
+
// Split into OAuth (subscriptions) vs API key models
|
|
31
|
+
const oauthModels = available.filter((m) => modelRegistry.isUsingOAuth(m));
|
|
32
|
+
const apiKeyModels = available.filter((m) => !modelRegistry.isUsingOAuth(m));
|
|
33
|
+
// Check preferred models — subscriptions first, then API keys
|
|
34
|
+
for (const pool of [oauthModels, apiKeyModels]) {
|
|
35
|
+
for (const { provider, modelId } of PREFERRED_MODELS) {
|
|
36
|
+
const match = pool.find((m) => m.provider === provider && m.id === modelId);
|
|
37
|
+
if (match)
|
|
38
|
+
return match;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Fallback: first OAuth model, then first API key model
|
|
42
|
+
if (oauthModels.length > 0)
|
|
43
|
+
return oauthModels[0];
|
|
44
|
+
return available[0];
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/agent/models.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,gEAAgE;AAChE,MAAM,gBAAgB,GAAiD;IACrE,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,0BAA0B,EAAE;IAC9D,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE;IAChD,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,2BAA2B,EAAE;IAChE,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;CAC1C,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,aAA4B;IAE5B,iDAAiD;IACjD,MAAM,SAAS,GAAG,aAAa,CAAC,YAAY,EAAE,CAAC;IAC/C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,qDAAqD;IACrD,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7E,8DAA8D;IAC9D,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,gBAAgB,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CACrB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CACnD,CAAC;YACF,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC;IAClD,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-provider.d.ts","sourceRoot":"","sources":["../../../src/agent/provider/azure-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-provider.js","sourceRoot":"","sources":["../../../src/agent/provider/azure-provider.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi SDK agent session management.
|
|
3
|
+
*
|
|
4
|
+
* Module-level singleton: one session, persists across requests.
|
|
5
|
+
* Provides init, get, reset, and auth-check exports for the server.
|
|
6
|
+
*/
|
|
7
|
+
import { AuthStorage, ModelRegistry, type AgentSession } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
declare let authStorage: AuthStorage;
|
|
9
|
+
declare let modelRegistry: ModelRegistry;
|
|
10
|
+
/**
|
|
11
|
+
* Initialize a new agent session.
|
|
12
|
+
* Picks the best available model, creates a Pi SDK session with no built-in
|
|
13
|
+
* tools (Excel-only agent — custom tools come in Module 2).
|
|
14
|
+
*/
|
|
15
|
+
export declare function initSession(): Promise<AgentSession>;
|
|
16
|
+
/**
|
|
17
|
+
* Get the current session, or create one if none exists.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getSession(): Promise<AgentSession>;
|
|
20
|
+
/**
|
|
21
|
+
* Check if any provider has auth configured.
|
|
22
|
+
* Fast check — does not refresh OAuth tokens.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isAuthenticated(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Get the provider for the model the session is actually using.
|
|
27
|
+
* Before a session is created, returns the provider that getDefaultModel()
|
|
28
|
+
* would select — same ranking logic, same result.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAuthProvider(): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Dispose the current session, rebuild auth, and clear state.
|
|
33
|
+
* Called when auth changes so the next request creates a fresh session
|
|
34
|
+
* from the current auth source.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resetSession(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Abort the current in-flight prompt, if any.
|
|
39
|
+
* Used on client disconnect to stop wasting tokens.
|
|
40
|
+
*/
|
|
41
|
+
export declare function abortSession(): Promise<void>;
|
|
42
|
+
export { authStorage, modelRegistry };
|
|
43
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/agent/session.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAEL,WAAW,EACX,aAAa,EAGb,KAAK,YAAY,EAClB,MAAM,+BAA+B,CAAC;AA2BvC,QAAA,IAAI,WAAW,aAAqC,CAAC;AACrD,QAAA,IAAI,aAAa,eAAiC,CAAC;AA0BnD;;;;GAIG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,YAAY,CAAC,CA8BzD;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAKxD;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAIzC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAQ/C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAMnC;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAQlD;AAGD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi SDK agent session management.
|
|
3
|
+
*
|
|
4
|
+
* Module-level singleton: one session, persists across requests.
|
|
5
|
+
* Provides init, get, reset, and auth-check exports for the server.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { existsSync } from "fs";
|
|
10
|
+
import { createAgentSession, AuthStorage, ModelRegistry, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { getDefaultModel } from "./models.js";
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Paths
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const AGENTXL_DIR = join(homedir(), ".agentxl");
|
|
16
|
+
const AGENTXL_AUTH_PATH = join(AGENTXL_DIR, "auth.json");
|
|
17
|
+
const PI_AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
|
|
18
|
+
/**
|
|
19
|
+
* Resolve auth file path.
|
|
20
|
+
* Uses AgentXL's own auth.json if it exists, otherwise falls back to
|
|
21
|
+
* Pi's auth.json. This lets users who already have Pi set up get seamless
|
|
22
|
+
* auth — same subscriptions, same OAuth tokens, auto-refreshed.
|
|
23
|
+
*/
|
|
24
|
+
function resolveAuthPath() {
|
|
25
|
+
if (existsSync(AGENTXL_AUTH_PATH))
|
|
26
|
+
return AGENTXL_AUTH_PATH;
|
|
27
|
+
if (existsSync(PI_AUTH_PATH))
|
|
28
|
+
return PI_AUTH_PATH;
|
|
29
|
+
return AGENTXL_AUTH_PATH; // default (will be created on first auth)
|
|
30
|
+
}
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Singletons — rebuilt on resetSession() to pick up auth changes
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
let authStorage = new AuthStorage(resolveAuthPath());
|
|
35
|
+
let modelRegistry = new ModelRegistry(authStorage);
|
|
36
|
+
/** Active agent session (null until first prompt) */
|
|
37
|
+
let currentSession = null;
|
|
38
|
+
/** Provider selected for the active session */
|
|
39
|
+
let selectedProvider = null;
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Internal: rebuild auth/model singletons
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Rebuild AuthStorage and ModelRegistry from current auth path.
|
|
45
|
+
* Called by resetSession() so runtime auth changes are picked up.
|
|
46
|
+
*/
|
|
47
|
+
function rebuildAuth() {
|
|
48
|
+
authStorage = new AuthStorage(resolveAuthPath());
|
|
49
|
+
modelRegistry = new ModelRegistry(authStorage);
|
|
50
|
+
selectedProvider = null;
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Public API
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Initialize a new agent session.
|
|
57
|
+
* Picks the best available model, creates a Pi SDK session with no built-in
|
|
58
|
+
* tools (Excel-only agent — custom tools come in Module 2).
|
|
59
|
+
*/
|
|
60
|
+
export async function initSession() {
|
|
61
|
+
// Refresh to pick up any new keys
|
|
62
|
+
modelRegistry.refresh();
|
|
63
|
+
const model = getDefaultModel(modelRegistry);
|
|
64
|
+
if (!model) {
|
|
65
|
+
throw new Error("No model available. Run 'agentxl login' to set up authentication " +
|
|
66
|
+
"(API key or subscription).");
|
|
67
|
+
}
|
|
68
|
+
// Track the selected provider
|
|
69
|
+
selectedProvider = model.provider;
|
|
70
|
+
const { session } = await createAgentSession({
|
|
71
|
+
model,
|
|
72
|
+
thinkingLevel: "medium",
|
|
73
|
+
tools: [], // No built-in tools (read/bash/edit/write)
|
|
74
|
+
customTools: [], // Excel tools come in Module 2
|
|
75
|
+
sessionManager: SessionManager.inMemory(),
|
|
76
|
+
settingsManager: SettingsManager.inMemory({
|
|
77
|
+
compaction: { enabled: false },
|
|
78
|
+
}),
|
|
79
|
+
authStorage,
|
|
80
|
+
modelRegistry,
|
|
81
|
+
});
|
|
82
|
+
currentSession = session;
|
|
83
|
+
return session;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the current session, or create one if none exists.
|
|
87
|
+
*/
|
|
88
|
+
export async function getSession() {
|
|
89
|
+
if (currentSession) {
|
|
90
|
+
return currentSession;
|
|
91
|
+
}
|
|
92
|
+
return initSession();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if any provider has auth configured.
|
|
96
|
+
* Fast check — does not refresh OAuth tokens.
|
|
97
|
+
*/
|
|
98
|
+
export function isAuthenticated() {
|
|
99
|
+
modelRegistry.refresh();
|
|
100
|
+
const available = modelRegistry.getAvailable();
|
|
101
|
+
return available.length > 0;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get the provider for the model the session is actually using.
|
|
105
|
+
* Before a session is created, returns the provider that getDefaultModel()
|
|
106
|
+
* would select — same ranking logic, same result.
|
|
107
|
+
*/
|
|
108
|
+
export function getAuthProvider() {
|
|
109
|
+
// If a session exists, return its actual provider
|
|
110
|
+
if (selectedProvider)
|
|
111
|
+
return selectedProvider;
|
|
112
|
+
// No session yet — preview what getDefaultModel() would pick
|
|
113
|
+
modelRegistry.refresh();
|
|
114
|
+
const model = getDefaultModel(modelRegistry);
|
|
115
|
+
return model?.provider ?? null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Dispose the current session, rebuild auth, and clear state.
|
|
119
|
+
* Called when auth changes so the next request creates a fresh session
|
|
120
|
+
* from the current auth source.
|
|
121
|
+
*/
|
|
122
|
+
export function resetSession() {
|
|
123
|
+
if (currentSession) {
|
|
124
|
+
currentSession.dispose();
|
|
125
|
+
currentSession = null;
|
|
126
|
+
}
|
|
127
|
+
rebuildAuth();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Abort the current in-flight prompt, if any.
|
|
131
|
+
* Used on client disconnect to stop wasting tokens.
|
|
132
|
+
*/
|
|
133
|
+
export async function abortSession() {
|
|
134
|
+
if (currentSession) {
|
|
135
|
+
try {
|
|
136
|
+
await currentSession.abort();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Ignore errors during abort — session may already be idle
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Exports for testing
|
|
144
|
+
export { authStorage, modelRegistry };
|
|
145
|
+
//# sourceMappingURL=session.js.map
|