pi2dsh 0.2.0 → 0.3.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 +29 -19
- package/README.zh.md +29 -19
- package/dist/build-DsGYAgiT.mjs +4416 -0
- package/dist/build-DsGYAgiT.mjs.map +1 -0
- package/dist/cli.mjs +2 -2
- package/dist/compat/pi-ai.d.mts +186 -2435
- package/dist/compat/pi-ai.d.mts.map +1 -1
- package/dist/compat/pi-ai.mjs +2771 -4520
- package/dist/compat/pi-ai.mjs.map +1 -1
- package/dist/compat/pi-coding-agent.d.mts +333 -14
- package/dist/compat/pi-coding-agent.d.mts.map +1 -1
- package/dist/compat/pi-coding-agent.mjs +2 -2
- package/dist/compat/pi-tui.mjs +1 -3620
- package/dist/host.mjs +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{mcp-config-jL9w70It.mjs → mcp-config-gqg7GJEE.mjs} +115 -51
- package/dist/mcp-config-gqg7GJEE.mjs.map +1 -0
- package/dist/pi-coding-agent-Z1hTs61i.mjs +5203 -0
- package/dist/pi-coding-agent-Z1hTs61i.mjs.map +1 -0
- package/dist/pi-tui-5CYLcj-_.mjs +4512 -0
- package/dist/pi-tui-5CYLcj-_.mjs.map +1 -0
- package/dist/pi-types-BLt46jaf.d.mts +2494 -0
- package/dist/pi-types-BLt46jaf.d.mts.map +1 -0
- package/dist/{rolldown-runtime-C2Q2p085.mjs → rolldown-runtime-D-uZhY3_.mjs} +3 -2
- package/dist/{runtime-D84Hv_3m.mjs → runtime-oLd2EInK.mjs} +690 -25
- package/dist/runtime-oLd2EInK.mjs.map +1 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{source-D7Ir-rPT.mjs → source-0sA5z08z.mjs} +6 -2
- package/dist/source-0sA5z08z.mjs.map +1 -0
- package/package.json +4 -1
- package/dist/compat/pi-tui.mjs.map +0 -1
- package/dist/mcp-config-jL9w70It.mjs.map +0 -1
- package/dist/pi-coding-agent-Dsg6_0ua.mjs +0 -2060
- package/dist/pi-coding-agent-Dsg6_0ua.mjs.map +0 -1
- package/dist/pi-tui-utils-CcaVtm-3.mjs +0 -895
- package/dist/pi-tui-utils-CcaVtm-3.mjs.map +0 -1
- package/dist/pi-types-KazmR2O5.d.mts +0 -62
- package/dist/pi-types-KazmR2O5.d.mts.map +0 -1
- package/dist/pi-uuid-Db8ShZsK.mjs +0 -47
- package/dist/pi-uuid-Db8ShZsK.mjs.map +0 -1
- package/dist/runtime-D84Hv_3m.mjs.map +0 -1
- package/dist/source-D7Ir-rPT.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { E as __setSubagentSessionFactory, S as Theme, f as ExtensionRunner } from "./pi-coding-agent-Z1hTs61i.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
-
import { access, readFile } from "node:fs/promises";
|
|
6
|
-
import { join } from "node:path";
|
|
5
|
+
import { access, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { EventEmitter } from "node:events";
|
|
9
9
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -277,6 +277,573 @@ var PiSessionBridge = class {
|
|
|
277
277
|
}
|
|
278
278
|
};
|
|
279
279
|
//#endregion
|
|
280
|
+
//#region src/subagent-bridge.ts
|
|
281
|
+
let subagentSerial = 0;
|
|
282
|
+
var PiBridgedAgentSession = class {
|
|
283
|
+
agent = {};
|
|
284
|
+
messages = [];
|
|
285
|
+
#host;
|
|
286
|
+
#handle;
|
|
287
|
+
#session;
|
|
288
|
+
#tools;
|
|
289
|
+
#subscribers = /* @__PURE__ */ new Set();
|
|
290
|
+
#disposers = [];
|
|
291
|
+
#activeToolNames;
|
|
292
|
+
#sessionName = "";
|
|
293
|
+
#turns = 0;
|
|
294
|
+
#aborted = false;
|
|
295
|
+
constructor(host, handle, tools) {
|
|
296
|
+
this.#host = host;
|
|
297
|
+
this.#handle = handle;
|
|
298
|
+
this.#session = handle.agent.session ?? {};
|
|
299
|
+
this.#tools = tools;
|
|
300
|
+
this.#activeToolNames = tools.map((tool) => tool?.name).filter((name) => typeof name === "string");
|
|
301
|
+
const cordis = host.cordis;
|
|
302
|
+
const offEvents = cordis.on("session/event", (session, event) => {
|
|
303
|
+
if (session !== this.#session) return;
|
|
304
|
+
this.#project(event);
|
|
305
|
+
});
|
|
306
|
+
const offPre = cordis.on("tools/pre-execute", async (exec, next) => {
|
|
307
|
+
if (exec.agent !== this.#handle.agent || this.agent.beforeToolCall === void 0) return next();
|
|
308
|
+
const decision = await this.agent.beforeToolCall({ toolCall: {
|
|
309
|
+
name: exec.name,
|
|
310
|
+
id: exec.callId,
|
|
311
|
+
arguments: exec.arguments
|
|
312
|
+
} }, void 0);
|
|
313
|
+
if (decision?.block === true) return {
|
|
314
|
+
kind: "deny",
|
|
315
|
+
reason: decision.reason ?? `Tool "${exec.name}" is not available to this subagent.`
|
|
316
|
+
};
|
|
317
|
+
return next();
|
|
318
|
+
});
|
|
319
|
+
this.#disposers.push(offEvents, offPre);
|
|
320
|
+
}
|
|
321
|
+
#project(event) {
|
|
322
|
+
const type = event.type;
|
|
323
|
+
const emit = (piEvent) => {
|
|
324
|
+
for (const handler of [...this.#subscribers]) try {
|
|
325
|
+
handler(piEvent);
|
|
326
|
+
} catch {}
|
|
327
|
+
};
|
|
328
|
+
if (type === "turn/start") emit({
|
|
329
|
+
type: "turn_start",
|
|
330
|
+
turnIndex: Number(event.data.turn ?? 1) - 1
|
|
331
|
+
});
|
|
332
|
+
if (type === "tool/call") {
|
|
333
|
+
const data = event.data;
|
|
334
|
+
let args = {};
|
|
335
|
+
try {
|
|
336
|
+
args = JSON.parse(String(data.arguments ?? "{}"));
|
|
337
|
+
} catch {
|
|
338
|
+
args = {};
|
|
339
|
+
}
|
|
340
|
+
emit({
|
|
341
|
+
type: "tool_execution_start",
|
|
342
|
+
toolCallId: data.callId,
|
|
343
|
+
toolName: data.name,
|
|
344
|
+
args
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
if (type === "assistant/chunk") {
|
|
348
|
+
const chunk = event.data.chunk ?? {};
|
|
349
|
+
const delta = typeof chunk.text === "string" ? chunk.text : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
350
|
+
emit({
|
|
351
|
+
type: "message_update",
|
|
352
|
+
message: {
|
|
353
|
+
role: "assistant",
|
|
354
|
+
content: [{
|
|
355
|
+
type: "text",
|
|
356
|
+
text: delta
|
|
357
|
+
}]
|
|
358
|
+
},
|
|
359
|
+
assistantMessageEvent: {
|
|
360
|
+
type: "text_delta",
|
|
361
|
+
delta,
|
|
362
|
+
...chunk
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const message = this.#host.messageFromSessionEvent(event);
|
|
367
|
+
if (message !== void 0) {
|
|
368
|
+
this.messages.push(message);
|
|
369
|
+
emit({
|
|
370
|
+
type: "message_start",
|
|
371
|
+
message
|
|
372
|
+
});
|
|
373
|
+
emit({
|
|
374
|
+
type: "message_end",
|
|
375
|
+
message
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (type === "turn/end") {
|
|
379
|
+
this.#turns += 1;
|
|
380
|
+
emit({
|
|
381
|
+
type: "turn_end",
|
|
382
|
+
turnIndex: Number(event.data.turn ?? 1) - 1,
|
|
383
|
+
message: {
|
|
384
|
+
role: "assistant",
|
|
385
|
+
content: []
|
|
386
|
+
},
|
|
387
|
+
toolResults: []
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
subscribe(handler) {
|
|
392
|
+
this.#subscribers.add(handler);
|
|
393
|
+
return () => this.#subscribers.delete(handler);
|
|
394
|
+
}
|
|
395
|
+
async prompt(text) {
|
|
396
|
+
const blocks = await this.#host.piContentToDsh([{
|
|
397
|
+
type: "text",
|
|
398
|
+
text: String(text)
|
|
399
|
+
}]);
|
|
400
|
+
const cordis = this.#host.cordis;
|
|
401
|
+
const completed = new Promise((resolve) => {
|
|
402
|
+
const off = cordis.on("session/event", (session, event) => {
|
|
403
|
+
if (session !== this.#session) return;
|
|
404
|
+
if (event.type === "turn/end") {
|
|
405
|
+
off();
|
|
406
|
+
resolve();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
this.#disposers.push(off);
|
|
410
|
+
});
|
|
411
|
+
this.#host.deliver(this.#handle.agent, createUserMessage({
|
|
412
|
+
content: blocks,
|
|
413
|
+
source: {
|
|
414
|
+
kind: "plugin",
|
|
415
|
+
plugin: this.#host.messageSource
|
|
416
|
+
}
|
|
417
|
+
}), "followup");
|
|
418
|
+
await completed;
|
|
419
|
+
}
|
|
420
|
+
steer(text) {
|
|
421
|
+
this.#host.piContentToDsh([{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: String(text)
|
|
424
|
+
}]).then((blocks) => {
|
|
425
|
+
this.#host.deliver(this.#handle.agent, createUserMessage({
|
|
426
|
+
content: blocks,
|
|
427
|
+
source: {
|
|
428
|
+
kind: "plugin",
|
|
429
|
+
plugin: this.#host.messageSource
|
|
430
|
+
}
|
|
431
|
+
}), "steer");
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
abort() {
|
|
435
|
+
this.#aborted = true;
|
|
436
|
+
const cancel = this.#handle.agent.cancel;
|
|
437
|
+
cancel?.({
|
|
438
|
+
kind: "hook",
|
|
439
|
+
reason: "pi2dsh subagent abort()"
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
setSessionName(name) {
|
|
443
|
+
this.#sessionName = String(name);
|
|
444
|
+
}
|
|
445
|
+
getSessionName() {
|
|
446
|
+
return this.#sessionName;
|
|
447
|
+
}
|
|
448
|
+
getSessionStats() {
|
|
449
|
+
return {
|
|
450
|
+
turns: this.#turns,
|
|
451
|
+
messages: this.messages.length,
|
|
452
|
+
aborted: this.#aborted
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
getAllTools() {
|
|
456
|
+
return [...this.#tools];
|
|
457
|
+
}
|
|
458
|
+
getActiveToolNames() {
|
|
459
|
+
return [...this.#activeToolNames];
|
|
460
|
+
}
|
|
461
|
+
setActiveToolsByName(names) {
|
|
462
|
+
this.#activeToolNames = names.filter((name) => typeof name === "string");
|
|
463
|
+
}
|
|
464
|
+
async bindExtensions(_bindings = {}) {}
|
|
465
|
+
async dispose() {
|
|
466
|
+
for (const dispose of this.#disposers.splice(0)) try {
|
|
467
|
+
dispose();
|
|
468
|
+
} catch {}
|
|
469
|
+
this.#subscribers.clear();
|
|
470
|
+
await this.#handle.dispose();
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
async function createBridgedAgentSession(host, options) {
|
|
474
|
+
const agents = host.cordis.get("agents");
|
|
475
|
+
if (agents?.create === void 0) throw new Error("pi2dsh: createAgentSession() needs the DSH agent registry in the host composition");
|
|
476
|
+
subagentSerial += 1;
|
|
477
|
+
const sessionId = `pi2dsh-sub-${Date.now().toString(36)}-${subagentSerial}`;
|
|
478
|
+
let handle;
|
|
479
|
+
try {
|
|
480
|
+
handle = await agents.create({
|
|
481
|
+
sessionId,
|
|
482
|
+
meta: {
|
|
483
|
+
cwd: typeof options.cwd === "string" ? options.cwd : host.cwd(),
|
|
484
|
+
origin: "subagent",
|
|
485
|
+
...host.parentSessionId() !== void 0 ? { parentSession: host.parentSessionId() } : {}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
} catch (error) {
|
|
489
|
+
throw new Error(`pi2dsh: subagent creation needs the DSH host loop (model runtime) to provide the agent factory; this composition cannot run one (${error instanceof Error ? error.message : String(error)})`);
|
|
490
|
+
}
|
|
491
|
+
const tools = [...Array.isArray(options.tools) ? options.tools : [], ...Array.isArray(options.customTools) ? options.customTools : []];
|
|
492
|
+
return { session: new PiBridgedAgentSession(host, handle, tools) };
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/compat/vendor/pi-ai-abort.ts
|
|
496
|
+
function abortReason(signal) {
|
|
497
|
+
if (signal.reason !== void 0) return signal.reason;
|
|
498
|
+
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
499
|
+
error.name = "AbortError";
|
|
500
|
+
return error;
|
|
501
|
+
}
|
|
502
|
+
/** Create an operation-local signal for public APIs whose signal is optional. */
|
|
503
|
+
function operationSignal(signal) {
|
|
504
|
+
return signal ?? new AbortController().signal;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Stop waiting for an operation when its signal aborts while continuing to
|
|
508
|
+
* observe the abandoned promise so a later rejection is always handled.
|
|
509
|
+
*/
|
|
510
|
+
function raceWithAbortSignal(operation, signal) {
|
|
511
|
+
if (signal.aborted) {
|
|
512
|
+
operation.catch(() => {});
|
|
513
|
+
return Promise.reject(abortReason(signal));
|
|
514
|
+
}
|
|
515
|
+
return new Promise((resolve, reject) => {
|
|
516
|
+
let settled = false;
|
|
517
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
518
|
+
const onAbort = () => {
|
|
519
|
+
if (settled) return;
|
|
520
|
+
settled = true;
|
|
521
|
+
cleanup();
|
|
522
|
+
reject(abortReason(signal));
|
|
523
|
+
};
|
|
524
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
525
|
+
operation.then((value) => {
|
|
526
|
+
if (settled) return;
|
|
527
|
+
settled = true;
|
|
528
|
+
cleanup();
|
|
529
|
+
resolve(value);
|
|
530
|
+
}, (error) => {
|
|
531
|
+
if (settled) return;
|
|
532
|
+
settled = true;
|
|
533
|
+
cleanup();
|
|
534
|
+
reject(error);
|
|
535
|
+
});
|
|
536
|
+
if (signal.aborted) onAbort();
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/compat/vendor/pi-ai-credential-store.ts
|
|
541
|
+
/**
|
|
542
|
+
* Default in-memory credential store. Apps inject persistent stores.
|
|
543
|
+
* Keyed by `Provider.id`, one credential per provider; see `CredentialStore`.
|
|
544
|
+
* Writes are serialized per provider through a promise chain.
|
|
545
|
+
*/
|
|
546
|
+
var InMemoryCredentialStore = class {
|
|
547
|
+
credentials = /* @__PURE__ */ new Map();
|
|
548
|
+
chains = /* @__PURE__ */ new Map();
|
|
549
|
+
/** Serialize tasks per provider id without releasing the chain before active work settles. */
|
|
550
|
+
enqueue(providerId, task, options) {
|
|
551
|
+
const signal = operationSignal(options?.signal);
|
|
552
|
+
const previous = this.chains.get(providerId) ?? Promise.resolve();
|
|
553
|
+
const queued = (async () => {
|
|
554
|
+
await previous.catch(() => {});
|
|
555
|
+
signal.throwIfAborted();
|
|
556
|
+
return task();
|
|
557
|
+
})();
|
|
558
|
+
const tail = queued.catch(() => {});
|
|
559
|
+
this.chains.set(providerId, tail);
|
|
560
|
+
tail.then(() => {
|
|
561
|
+
if (this.chains.get(providerId) === tail) this.chains.delete(providerId);
|
|
562
|
+
});
|
|
563
|
+
return raceWithAbortSignal(queued, signal);
|
|
564
|
+
}
|
|
565
|
+
async read(providerId, options) {
|
|
566
|
+
options?.signal?.throwIfAborted();
|
|
567
|
+
return this.credentials.get(providerId);
|
|
568
|
+
}
|
|
569
|
+
async list(options) {
|
|
570
|
+
options?.signal?.throwIfAborted();
|
|
571
|
+
return [...this.credentials].map(([providerId, credential]) => ({
|
|
572
|
+
providerId,
|
|
573
|
+
type: credential.type
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
modify(providerId, fn, options) {
|
|
577
|
+
return this.enqueue(providerId, async () => {
|
|
578
|
+
const current = this.credentials.get(providerId);
|
|
579
|
+
const next = await fn(current);
|
|
580
|
+
options?.signal?.throwIfAborted();
|
|
581
|
+
if (next !== void 0) this.credentials.set(providerId, next);
|
|
582
|
+
return next ?? current;
|
|
583
|
+
}, options);
|
|
584
|
+
}
|
|
585
|
+
delete(providerId, options) {
|
|
586
|
+
return this.enqueue(providerId, async () => {
|
|
587
|
+
this.credentials.delete(providerId);
|
|
588
|
+
}, options);
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/compat/vendor/pi-ai-diagnostics.ts
|
|
593
|
+
function formatThrownValue(value) {
|
|
594
|
+
if (value instanceof Error) return value.message || value.name;
|
|
595
|
+
if (typeof value === "string") return value;
|
|
596
|
+
return String(value);
|
|
597
|
+
}
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/compat/vendor/pi-ai-auth-resolve.ts
|
|
600
|
+
var ModelsError = class extends Error {
|
|
601
|
+
code;
|
|
602
|
+
constructor(code, message, options) {
|
|
603
|
+
super(withCauseDetail(message, options?.cause), options);
|
|
604
|
+
this.name = "ModelsError";
|
|
605
|
+
this.code = code;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
/** Callers surface `error.message` only, so keep the underlying reason in it. */
|
|
609
|
+
function withCauseDetail(message, cause) {
|
|
610
|
+
if (cause === void 0 || cause === null) return message;
|
|
611
|
+
const detail = formatThrownValue(cause).trim();
|
|
612
|
+
if (!detail || message.includes(detail)) return message;
|
|
613
|
+
return `${message}: ${detail}`;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Auth resolution shared by the `Models` and `ImagesModels` collections.
|
|
617
|
+
* A stored credential owns the provider: ambient/env is consulted only when
|
|
618
|
+
* nothing is stored. No silent env fallback after a failed refresh or for a
|
|
619
|
+
* credential type without a matching handler.
|
|
620
|
+
*/
|
|
621
|
+
function resolveProviderAuth(provider, credentials, authContext, overrides) {
|
|
622
|
+
const signal = operationSignal(overrides?.signal);
|
|
623
|
+
return raceWithAbortSignal(resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal), signal);
|
|
624
|
+
}
|
|
625
|
+
async function resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal) {
|
|
626
|
+
signal.throwIfAborted();
|
|
627
|
+
const requestAuthContext = overrides?.env ? overlayEnvAuthContext(authContext, overrides.env) : authContext;
|
|
628
|
+
if (overrides?.apiKey !== void 0 && provider.auth.apiKey) return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, {
|
|
629
|
+
type: "api_key",
|
|
630
|
+
key: overrides.apiKey,
|
|
631
|
+
env: overrides.env
|
|
632
|
+
}, signal);
|
|
633
|
+
const stored = await readCredential(credentials, provider.id, signal);
|
|
634
|
+
if (stored) {
|
|
635
|
+
if (stored.type === "oauth" && provider.auth.oauth) return resolveStoredOAuth(credentials, provider.id, provider.auth.oauth, stored, signal, overrides?.minOAuthValidityMs);
|
|
636
|
+
if (stored.type === "api_key" && provider.auth.apiKey) {
|
|
637
|
+
const credential = overrides?.env ? {
|
|
638
|
+
...stored,
|
|
639
|
+
env: {
|
|
640
|
+
...stored.env,
|
|
641
|
+
...overrides.env
|
|
642
|
+
}
|
|
643
|
+
} : stored;
|
|
644
|
+
return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, credential, signal);
|
|
645
|
+
}
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
return provider.auth.apiKey ? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, void 0, signal) : void 0;
|
|
649
|
+
}
|
|
650
|
+
function overlayEnvAuthContext(base, env) {
|
|
651
|
+
return {
|
|
652
|
+
env: async (name) => env[name] || await base.env(name),
|
|
653
|
+
fileExists: (path) => base.fileExists(path)
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
const DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 3e5;
|
|
657
|
+
const DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15e3;
|
|
658
|
+
/**
|
|
659
|
+
* OAuth resolution with double-checked locking: tokens with less than five
|
|
660
|
+
* minutes remaining lock, re-check expiry under the lock, refresh once
|
|
661
|
+
* globally, and persist the rotated credential before release.
|
|
662
|
+
*/
|
|
663
|
+
async function resolveStoredOAuth(credentials, providerId, oauth, stored, signal, minOAuthValidityMs) {
|
|
664
|
+
const minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);
|
|
665
|
+
const expiresSoon = (credential) => Date.now() + minimumValidityMs >= credential.expires;
|
|
666
|
+
let credential = stored;
|
|
667
|
+
if (expiresSoon(credential)) {
|
|
668
|
+
let post;
|
|
669
|
+
try {
|
|
670
|
+
post = await credentials.modify(providerId, async (current) => {
|
|
671
|
+
if (current?.type !== "oauth") return void 0;
|
|
672
|
+
if (!expiresSoon(current)) return void 0;
|
|
673
|
+
try {
|
|
674
|
+
const refreshSignal = AbortSignal.any([signal, AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS)]);
|
|
675
|
+
return await oauth.refresh(current, refreshSignal);
|
|
676
|
+
} catch (error) {
|
|
677
|
+
throw new ModelsError("oauth", `OAuth refresh failed for ${providerId}`, { cause: error });
|
|
678
|
+
}
|
|
679
|
+
}, { signal });
|
|
680
|
+
} catch (error) {
|
|
681
|
+
if (error instanceof ModelsError) throw error;
|
|
682
|
+
throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
|
|
683
|
+
}
|
|
684
|
+
if (post?.type !== "oauth") return void 0;
|
|
685
|
+
credential = post;
|
|
686
|
+
if (minOAuthValidityMs !== void 0 && expiresSoon(credential)) throw new ModelsError("oauth", `OAuth refresh returned a token that expires too soon for ${providerId}`);
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
return {
|
|
690
|
+
auth: await oauth.toAuth(credential),
|
|
691
|
+
source: "OAuth"
|
|
692
|
+
};
|
|
693
|
+
} catch (error) {
|
|
694
|
+
throw new ModelsError("oauth", `OAuth auth derivation failed for ${providerId}`, { cause: error });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async function resolveApiKey(authContext, apiKey, providerId, credential, signal) {
|
|
698
|
+
try {
|
|
699
|
+
return await apiKey.resolve({
|
|
700
|
+
ctx: authContext,
|
|
701
|
+
credential,
|
|
702
|
+
signal
|
|
703
|
+
});
|
|
704
|
+
} catch (error) {
|
|
705
|
+
throw new ModelsError("auth", `API key auth failed for provider ${providerId}`, { cause: error });
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function readCredential(credentials, providerId, signal) {
|
|
709
|
+
try {
|
|
710
|
+
return await credentials.read(providerId, { signal });
|
|
711
|
+
} catch (error) {
|
|
712
|
+
throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error });
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/compat/vendor/pi-oauth-adapt.ts
|
|
717
|
+
function adaptOAuth(config) {
|
|
718
|
+
return {
|
|
719
|
+
name: config.name,
|
|
720
|
+
isSubscription: config.isSubscription,
|
|
721
|
+
login: async (callbacks) => {
|
|
722
|
+
return {
|
|
723
|
+
...await config.login({
|
|
724
|
+
onAuth: (info) => callbacks.notify({
|
|
725
|
+
type: "auth_url",
|
|
726
|
+
...info
|
|
727
|
+
}),
|
|
728
|
+
onDeviceCode: (info) => callbacks.notify({
|
|
729
|
+
type: "device_code",
|
|
730
|
+
...info
|
|
731
|
+
}),
|
|
732
|
+
onPrompt: (prompt) => callbacks.prompt({
|
|
733
|
+
type: "text",
|
|
734
|
+
...prompt
|
|
735
|
+
}),
|
|
736
|
+
onProgress: (message) => callbacks.notify({
|
|
737
|
+
type: "progress",
|
|
738
|
+
message
|
|
739
|
+
}),
|
|
740
|
+
onManualCodeInput: () => callbacks.prompt({
|
|
741
|
+
type: "manual_code",
|
|
742
|
+
message: "Paste the authorization code"
|
|
743
|
+
}),
|
|
744
|
+
onSelect: (prompt) => callbacks.prompt({
|
|
745
|
+
type: "select",
|
|
746
|
+
...prompt
|
|
747
|
+
}),
|
|
748
|
+
signal: callbacks.signal
|
|
749
|
+
}),
|
|
750
|
+
type: "oauth"
|
|
751
|
+
};
|
|
752
|
+
},
|
|
753
|
+
refresh: async (credential, signal) => ({
|
|
754
|
+
...await config.refreshToken(credential, signal),
|
|
755
|
+
type: "oauth"
|
|
756
|
+
}),
|
|
757
|
+
toAuth: async (credential) => ({ apiKey: config.getApiKey(credential) })
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/oauth-bridge.ts
|
|
762
|
+
var FileCredentialStore = class extends InMemoryCredentialStore {
|
|
763
|
+
#path;
|
|
764
|
+
constructor(path) {
|
|
765
|
+
super();
|
|
766
|
+
this.#path = path;
|
|
767
|
+
try {
|
|
768
|
+
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
769
|
+
for (const [providerId, credential] of Object.entries(data)) this.credentials.set(providerId, credential);
|
|
770
|
+
} catch {}
|
|
771
|
+
}
|
|
772
|
+
get path() {
|
|
773
|
+
return this.#path;
|
|
774
|
+
}
|
|
775
|
+
async #persist() {
|
|
776
|
+
const credentials = this.credentials;
|
|
777
|
+
const data = Object.fromEntries(credentials);
|
|
778
|
+
await mkdir(dirname(this.#path), { recursive: true });
|
|
779
|
+
const temp = `${this.#path}.tmp-${process.pid}-${Math.floor(Math.random() * 1e9).toString(36)}`;
|
|
780
|
+
await writeFile(temp, `${JSON.stringify(data, null, 2)}\n`, { mode: 384 });
|
|
781
|
+
await rename(temp, this.#path);
|
|
782
|
+
}
|
|
783
|
+
modify(providerId, fn, options) {
|
|
784
|
+
return super.modify(providerId, async (current) => {
|
|
785
|
+
const next = await fn(current);
|
|
786
|
+
if (next !== void 0) {
|
|
787
|
+
this.credentials.set(providerId, next);
|
|
788
|
+
await this.#persist();
|
|
789
|
+
}
|
|
790
|
+
return next;
|
|
791
|
+
}, options);
|
|
792
|
+
}
|
|
793
|
+
delete(providerId, options) {
|
|
794
|
+
return super.delete(providerId, options).then(() => this.#persist());
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
function oauthInteraction(ui, signal) {
|
|
798
|
+
return {
|
|
799
|
+
signal: signal ?? new AbortController().signal,
|
|
800
|
+
async prompt(prompt) {
|
|
801
|
+
if (prompt.type === "select") {
|
|
802
|
+
const options = prompt.options ?? [];
|
|
803
|
+
const picked = await ui.select(prompt.message, options.map((option) => option.label));
|
|
804
|
+
return options.find((option) => option.label === picked)?.id ?? picked;
|
|
805
|
+
}
|
|
806
|
+
return ui.input(prompt.message, prompt.placeholder);
|
|
807
|
+
},
|
|
808
|
+
notify(event) {
|
|
809
|
+
if (event.type === "auth_url") ui.notify(`Open this URL to authorize: ${event.url}${event.instructions !== void 0 ? `\n${event.instructions}` : ""}`);
|
|
810
|
+
else if (event.type === "device_code") ui.notify(`Visit ${event.verificationUri} and enter code ${event.userCode}`);
|
|
811
|
+
else if (event.message !== void 0) ui.notify(event.message);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function oauthConfigOf(providerConfig) {
|
|
816
|
+
const oauth = providerConfig?.oauth;
|
|
817
|
+
return typeof oauth === "object" && oauth !== null && typeof oauth.login === "function" ? oauth : void 0;
|
|
818
|
+
}
|
|
819
|
+
function oauthAdapterOf(oauthConfig) {
|
|
820
|
+
if (typeof oauthConfig.toAuth === "function") return oauthConfig;
|
|
821
|
+
return adaptOAuth(oauthConfig);
|
|
822
|
+
}
|
|
823
|
+
function providerSupportsOAuth(providerConfig) {
|
|
824
|
+
return oauthConfigOf(providerConfig) !== void 0;
|
|
825
|
+
}
|
|
826
|
+
async function loginPiProvider(options) {
|
|
827
|
+
const oauthConfig = oauthConfigOf(options.providerConfig);
|
|
828
|
+
if (oauthConfig === void 0) throw new Error(`${options.providerName ?? options.providerId} does not support oauth login`);
|
|
829
|
+
const credential = await oauthAdapterOf(oauthConfig).login(oauthInteraction(options.ui, options.signal));
|
|
830
|
+
await options.store.modify(options.providerId, async () => credential);
|
|
831
|
+
return credential;
|
|
832
|
+
}
|
|
833
|
+
async function resolveOAuthApiKey(options) {
|
|
834
|
+
const oauthConfig = oauthConfigOf(options.providerConfig);
|
|
835
|
+
if (oauthConfig === void 0) return void 0;
|
|
836
|
+
return (await resolveProviderAuth({
|
|
837
|
+
id: options.providerId,
|
|
838
|
+
name: options.providerName ?? options.providerId,
|
|
839
|
+
auth: { oauth: oauthAdapterOf(oauthConfig) }
|
|
840
|
+
}, options.store, { env: async () => void 0 }, options.signal !== void 0 ? { signal: options.signal } : void 0))?.auth?.apiKey;
|
|
841
|
+
}
|
|
842
|
+
async function storedOAuthCredential(store, providerId) {
|
|
843
|
+
const stored = await store.read(providerId, void 0);
|
|
844
|
+
return stored?.type === "oauth" ? stored : void 0;
|
|
845
|
+
}
|
|
846
|
+
//#endregion
|
|
280
847
|
//#region src/runtime.ts
|
|
281
848
|
function logger(ctx) {
|
|
282
849
|
const candidate = ctx.logger;
|
|
@@ -600,6 +1167,7 @@ function contextFor(ctx, state, agent, signal, command = false) {
|
|
|
600
1167
|
}
|
|
601
1168
|
};
|
|
602
1169
|
const session = agentSession(agent);
|
|
1170
|
+
const providerConfig = (name) => state.providers.get(name);
|
|
603
1171
|
const base = {
|
|
604
1172
|
ui,
|
|
605
1173
|
mode: "rpc",
|
|
@@ -615,12 +1183,45 @@ function contextFor(ctx, state, agent, signal, command = false) {
|
|
|
615
1183
|
find: (_provider, _modelId) => void 0,
|
|
616
1184
|
getError: () => void 0,
|
|
617
1185
|
hasConfiguredAuth: (_model) => false,
|
|
618
|
-
getProviderAuthStatus: (
|
|
619
|
-
getProvider: (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
1186
|
+
getProviderAuthStatus: (provider) => providerSupportsOAuth(providerConfig(provider)) ? "oauth" : "none",
|
|
1187
|
+
getProvider: (provider) => providerConfig(provider),
|
|
1188
|
+
getRegisteredProviderConfig: (provider) => providerConfig(provider),
|
|
1189
|
+
getRegisteredProviderIds: () => [...state.providers.keys()],
|
|
1190
|
+
getProviderDisplayName: (provider) => {
|
|
1191
|
+
const config = providerConfig(provider);
|
|
1192
|
+
return typeof config?.name === "string" ? config.name : provider;
|
|
1193
|
+
},
|
|
1194
|
+
getProviderAuth: async (provider) => {
|
|
1195
|
+
const config = providerConfig(provider);
|
|
1196
|
+
if (!providerSupportsOAuth(config)) return void 0;
|
|
1197
|
+
if (await storedOAuthCredential(oauthStoreOf(state), provider) === void 0) return void 0;
|
|
1198
|
+
const apiKey = await resolveOAuthApiKey({
|
|
1199
|
+
providerId: provider,
|
|
1200
|
+
providerConfig: config,
|
|
1201
|
+
store: oauthStoreOf(state)
|
|
1202
|
+
});
|
|
1203
|
+
if (apiKey === void 0) return void 0;
|
|
1204
|
+
return {
|
|
1205
|
+
auth: {
|
|
1206
|
+
apiKey,
|
|
1207
|
+
baseUrl: config.baseUrl
|
|
1208
|
+
},
|
|
1209
|
+
source: "OAuth"
|
|
1210
|
+
};
|
|
1211
|
+
},
|
|
1212
|
+
getApiKeyForProvider: async (provider) => {
|
|
1213
|
+
const config = providerConfig(provider);
|
|
1214
|
+
if (!providerSupportsOAuth(config)) return void 0;
|
|
1215
|
+
return resolveOAuthApiKey({
|
|
1216
|
+
providerId: provider,
|
|
1217
|
+
providerConfig: config,
|
|
1218
|
+
store: oauthStoreOf(state)
|
|
1219
|
+
});
|
|
1220
|
+
},
|
|
1221
|
+
isUsingOAuth: (model) => {
|
|
1222
|
+
const provider = String(model?.provider ?? "");
|
|
1223
|
+
return provider.length > 0 && providerSupportsOAuth(providerConfig(provider));
|
|
1224
|
+
},
|
|
624
1225
|
refresh: async () => ({
|
|
625
1226
|
models: [],
|
|
626
1227
|
errors: []
|
|
@@ -988,8 +1589,42 @@ function subscribeInterceptors(ctx, state) {
|
|
|
988
1589
|
};
|
|
989
1590
|
});
|
|
990
1591
|
}
|
|
1592
|
+
function oauthStoreOf(state) {
|
|
1593
|
+
state.oauthStore ??= new FileCredentialStore(join(getAgentDir(), "auth.json"));
|
|
1594
|
+
return state.oauthStore;
|
|
1595
|
+
}
|
|
1596
|
+
function ensureLoginCommand(ctx, state) {
|
|
1597
|
+
if (state.loginCommandRegistered === true) return;
|
|
1598
|
+
state.loginCommandRegistered = true;
|
|
1599
|
+
registerCommand(ctx, state, {
|
|
1600
|
+
name: "login",
|
|
1601
|
+
description: "Log in to a Pi provider through its own OAuth flow",
|
|
1602
|
+
argumentHint: "<provider>",
|
|
1603
|
+
async handler(args, commandContext) {
|
|
1604
|
+
const oauthProviders = [...state.providers.entries()].filter(([, config]) => providerSupportsOAuth(config)).map(([name]) => name);
|
|
1605
|
+
if (oauthProviders.length === 0) throw new Error("no registered Pi provider supports OAuth login");
|
|
1606
|
+
const ui = commandContext.ui;
|
|
1607
|
+
let providerId = args.trim().split(/\s+/u)[0] ?? "";
|
|
1608
|
+
if (providerId.length === 0) providerId = oauthProviders.length === 1 ? oauthProviders[0] : String(await ui.select("Log in to which provider?", oauthProviders) ?? "");
|
|
1609
|
+
const config = state.providers.get(providerId);
|
|
1610
|
+
if (config === void 0 || !providerSupportsOAuth(config)) throw new Error(`unknown OAuth provider ${JSON.stringify(providerId)}; available: ${oauthProviders.join(", ")}`);
|
|
1611
|
+
const oauthName = config.oauth?.name ?? providerId;
|
|
1612
|
+
const commandSignal = commandContext.signal;
|
|
1613
|
+
await loginPiProvider({
|
|
1614
|
+
providerId,
|
|
1615
|
+
providerName: oauthName,
|
|
1616
|
+
providerConfig: config,
|
|
1617
|
+
store: oauthStoreOf(state),
|
|
1618
|
+
ui,
|
|
1619
|
+
...commandSignal !== void 0 ? { signal: commandSignal } : {}
|
|
1620
|
+
});
|
|
1621
|
+
ui.notify(`Logged in to ${oauthName}; credential stored in ${oauthStoreOf(state).path}`);
|
|
1622
|
+
return `Logged in to ${oauthName}`;
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
991
1626
|
function registerTool(ctx, state, tool) {
|
|
992
|
-
if (state.tools.has(tool.name))
|
|
1627
|
+
if (state.tools.has(tool.name)) unregisterTool(state, tool.name);
|
|
993
1628
|
const normalized = normalizeToolSchema(tool.parameters);
|
|
994
1629
|
for (const warning of normalized.warnings) logger(ctx).warn(`[pi2dsh] tool ${tool.name}: ${warning}`);
|
|
995
1630
|
state.tools.set(tool.name, tool);
|
|
@@ -1206,7 +1841,10 @@ function createPiApi(ctx, state) {
|
|
|
1206
1841
|
const name = typeof providerOrName === "string" ? providerOrName : String(providerOrName?.name ?? "unnamed");
|
|
1207
1842
|
const value = typeof providerOrName === "string" ? config ?? {} : providerOrName;
|
|
1208
1843
|
state.providers.set(name, value);
|
|
1209
|
-
|
|
1844
|
+
if (providerSupportsOAuth(value)) {
|
|
1845
|
+
ensureLoginCommand(ctx, state);
|
|
1846
|
+
logger(ctx).info(`[pi2dsh] Pi provider ${JSON.stringify(name)} supports OAuth — log in with /login ${name}`);
|
|
1847
|
+
} else logger(ctx).info(`[pi2dsh] recorded Pi provider ${JSON.stringify(name)}; model calls stay on DSH llm adapters`);
|
|
1210
1848
|
},
|
|
1211
1849
|
unregisterProvider(name) {
|
|
1212
1850
|
state.providers.delete(name);
|
|
@@ -1255,18 +1893,21 @@ function createPiApi(ctx, state) {
|
|
|
1255
1893
|
return executePiCommand(service, cwdOf(currentAgent(state)), command, args, options);
|
|
1256
1894
|
},
|
|
1257
1895
|
getActiveTools: () => getActiveTools(ctx, state),
|
|
1258
|
-
getAllTools: () =>
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1896
|
+
getAllTools: () => {
|
|
1897
|
+
const visiblePiTools = new Set(state.runner.getAllRegisteredTools().map((record) => record.definition?.name).filter((name) => typeof name === "string"));
|
|
1898
|
+
return toolRuntime(ctx, currentAgent(state)).schemas(currentAgent(state)).filter((tool) => !state.tools.has(tool.name) || visiblePiTools.has(tool.name)).map((tool) => ({
|
|
1899
|
+
name: tool.name,
|
|
1900
|
+
description: tool.description ?? "",
|
|
1901
|
+
parameters: tool.parameters ?? {},
|
|
1902
|
+
source: state.tools.has(tool.name) ? "extension" : "builtin",
|
|
1903
|
+
sourceInfo: {
|
|
1904
|
+
path: "",
|
|
1905
|
+
source: state.tools.has(tool.name) ? "pi2dsh" : "dsh",
|
|
1906
|
+
scope: "session",
|
|
1907
|
+
origin: "runtime"
|
|
1908
|
+
}
|
|
1909
|
+
}));
|
|
1910
|
+
},
|
|
1270
1911
|
setActiveTools: (names) => setActiveTools(ctx, state, names),
|
|
1271
1912
|
getCommands: () => [...state.commands.values()].map((command) => ({
|
|
1272
1913
|
name: command.name,
|
|
@@ -1439,9 +2080,20 @@ async function loadExtensions(rootDir, manifest, api, onExtensionError) {
|
|
|
1439
2080
|
async function applyPiPackage(ctx, options) {
|
|
1440
2081
|
if (options.manifest.schemaVersion !== 1) throw new Error(`unsupported pi2dsh manifest version ${String(options.manifest.schemaVersion)}`);
|
|
1441
2082
|
const rootDir = fileURLToPath(options.rootUrl);
|
|
2083
|
+
const runtimeTools = /* @__PURE__ */ new Map();
|
|
2084
|
+
const piToolRecords = () => [...runtimeTools.values()].map((tool) => ({
|
|
2085
|
+
definition: tool,
|
|
2086
|
+
sourceInfo: {
|
|
2087
|
+
path: "",
|
|
2088
|
+
source: "pi2dsh",
|
|
2089
|
+
scope: "session",
|
|
2090
|
+
origin: "package"
|
|
2091
|
+
}
|
|
2092
|
+
}));
|
|
1442
2093
|
const state = {
|
|
1443
2094
|
handlers: /* @__PURE__ */ new Map(),
|
|
1444
|
-
tools:
|
|
2095
|
+
tools: runtimeTools,
|
|
2096
|
+
runner: new ExtensionRunner(piToolRecords),
|
|
1445
2097
|
toolDisposers: /* @__PURE__ */ new Map(),
|
|
1446
2098
|
toolRestrictions: /* @__PURE__ */ new WeakMap(),
|
|
1447
2099
|
commands: /* @__PURE__ */ new Map(),
|
|
@@ -1483,6 +2135,19 @@ async function applyPiPackage(ctx, options) {
|
|
|
1483
2135
|
});
|
|
1484
2136
|
}
|
|
1485
2137
|
}
|
|
2138
|
+
__setSubagentSessionFactory((subagentOptions) => createBridgedAgentSession({
|
|
2139
|
+
cordis: ctx,
|
|
2140
|
+
cwd: () => cwdOf(currentAgent(state)),
|
|
2141
|
+
parentSessionId: () => {
|
|
2142
|
+
const session = agentSession(currentAgent(state));
|
|
2143
|
+
return session === void 0 ? void 0 : String(session.id ?? "") || void 0;
|
|
2144
|
+
},
|
|
2145
|
+
piContentToDsh: (content) => piToDshContent(ctx, content),
|
|
2146
|
+
deliver: (agent, message, mode) => deliverAgentMessage(agent, message, mode),
|
|
2147
|
+
messageFromSessionEvent,
|
|
2148
|
+
messageSource: state.messageSource
|
|
2149
|
+
}, subagentOptions));
|
|
2150
|
+
ctx.effect(() => () => __setSubagentSessionFactory(void 0));
|
|
1486
2151
|
await registerPromptCommands(ctx, state, rootDir, options.manifest);
|
|
1487
2152
|
await loadExtensions(rootDir, options.manifest, createPiApi(ctx, state), (failure) => logger(ctx).warn(`[pi2dsh] extension entry failed and was skipped (matching Pi's per-extension error isolation): ${failure}`));
|
|
1488
2153
|
logger(ctx).info(`[pi2dsh] loaded ${options.manifest.package.name}: ${state.tools.size} tools, ${state.commands.size} commands, ${options.manifest.skillDirs.length} skill roots`);
|
|
@@ -1496,4 +2161,4 @@ const runtimeInternals = {
|
|
|
1496
2161
|
//#endregion
|
|
1497
2162
|
export { normalizeToolSchema as n, runtimeInternals as r, applyPiPackage as t };
|
|
1498
2163
|
|
|
1499
|
-
//# sourceMappingURL=runtime-
|
|
2164
|
+
//# sourceMappingURL=runtime-oLd2EInK.mjs.map
|