@diegopetrucci/pi-extensions 0.1.52 → 0.1.54
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-fleet-tested-version +1 -1
- package/README.md +35 -15
- package/extensions/agent-workflow-audit/.pi-fleet-tested-version +1 -1
- package/extensions/agent-workflow-audit/index.ts +12 -0
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-git-diff/git.ts +1 -0
- package/extensions/annotate-git-diff/index.ts +1 -1
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +2 -1
- package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-last-message/index.ts +1 -1
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/brrr/.pi-fleet-tested-version +1 -1
- package/extensions/brrr/index.ts +4 -3
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
- package/extensions/claude-fast/package.json +1 -1
- package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/.pi-fleet-tested-version +1 -1
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
- package/extensions/context-inspector/index.ts +5 -0
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/.pi-fleet-tested-version +1 -1
- package/extensions/contrarian/index.ts +160 -35
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/.pi-fleet-tested-version +1 -1
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/git-footer/.pi-fleet-tested-version +1 -1
- package/extensions/git-footer/index.ts +42 -14
- package/extensions/git-footer/package.json +1 -1
- package/extensions/gnosis/.pi-fleet-tested-version +1 -1
- package/extensions/gnosis/package.json +1 -1
- package/extensions/illustrations-to-explain-things/.pi-fleet-tested-version +1 -1
- package/extensions/illustrations-to-explain-things/package.json +1 -1
- package/extensions/inline-bash/.pi-fleet-tested-version +1 -1
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/.pi-fleet-tested-version +1 -1
- package/extensions/librarian/index.ts +206 -85
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
- package/extensions/minimal-footer/index.ts +132 -72
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/.pi-fleet-tested-version +1 -1
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
- package/extensions/openai-fast/package.json +1 -1
- package/extensions/oracle/.pi-fleet-tested-version +1 -1
- package/extensions/oracle/index.ts +146 -34
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
- package/extensions/permission-gate/index.ts +5 -1
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/index.ts +6 -0
- package/extensions/quiet-tools/package.json +1 -1
- package/extensions/review/.pi-fleet-tested-version +1 -1
- package/extensions/review/index.ts +251 -197
- package/extensions/review/package.json +1 -1
- package/extensions/todo/.pi-fleet-tested-version +1 -1
- package/extensions/todo/index.ts +1 -1
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
- package/extensions/triage-comments/index.ts +25 -3
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +6 -5
|
@@ -31,6 +31,11 @@ type CommandRunner = (
|
|
|
31
31
|
options: { cwd: string; signal: AbortSignal },
|
|
32
32
|
) => Promise<CommandResult>;
|
|
33
33
|
|
|
34
|
+
type RunCommandSafelyResult =
|
|
35
|
+
| { kind: "ok"; result: CommandResult }
|
|
36
|
+
| { kind: "transient" }
|
|
37
|
+
| { kind: "unavailable" };
|
|
38
|
+
|
|
34
39
|
type TimerHandle = unknown;
|
|
35
40
|
|
|
36
41
|
type Clock = {
|
|
@@ -286,6 +291,10 @@ function defaultClock(): Clock {
|
|
|
286
291
|
};
|
|
287
292
|
}
|
|
288
293
|
|
|
294
|
+
function isCommandUnavailableError(error: unknown): boolean {
|
|
295
|
+
return !!error && typeof error === "object" && (error as { code?: unknown }).code === "ENOENT";
|
|
296
|
+
}
|
|
297
|
+
|
|
289
298
|
class GitFooterCache {
|
|
290
299
|
private readonly cwd: () => string;
|
|
291
300
|
private readonly runner: CommandRunner;
|
|
@@ -345,7 +354,7 @@ class GitFooterCache {
|
|
|
345
354
|
const result = await this.fetchGitStatus();
|
|
346
355
|
if (this.disposed) return;
|
|
347
356
|
if (result.kind === "transient") return;
|
|
348
|
-
if (result.kind === "not-a-repo") {
|
|
357
|
+
if (result.kind === "not-a-repo" || result.kind === "unavailable") {
|
|
349
358
|
this.statusSnapshot = undefined;
|
|
350
359
|
this.pullRequestSnapshot = undefined;
|
|
351
360
|
this.lastSeenBranch = undefined;
|
|
@@ -371,8 +380,8 @@ class GitFooterCache {
|
|
|
371
380
|
|
|
372
381
|
const pr = await this.fetchPullRequest();
|
|
373
382
|
if (this.disposed) return;
|
|
374
|
-
if (pr
|
|
375
|
-
else if (
|
|
383
|
+
if (pr.kind === "ok") this.pullRequestSnapshot = pr.pullRequest;
|
|
384
|
+
else if (pr.kind === "not-found" || pr.kind === "unavailable") this.pullRequestSnapshot = undefined;
|
|
376
385
|
|
|
377
386
|
this.emitChangeIfSnapshotsChanged(previousStatusSnapshot, previousPullRequestSnapshot);
|
|
378
387
|
}
|
|
@@ -399,32 +408,40 @@ class GitFooterCache {
|
|
|
399
408
|
| { kind: "ok"; status: GitStatusSnapshot }
|
|
400
409
|
| { kind: "not-a-repo" }
|
|
401
410
|
| { kind: "transient" }
|
|
411
|
+
| { kind: "unavailable" }
|
|
402
412
|
> {
|
|
403
413
|
const result = await this.runCommandSafely("git", GIT_STATUS_ARGS, this.gitTimeoutMs);
|
|
404
|
-
if (
|
|
405
|
-
if (result.exitCode !== 0) return { kind: "not-a-repo" };
|
|
406
|
-
return { kind: "ok", status: parseGitStatusPorcelainV2(result.stdout) };
|
|
414
|
+
if (result.kind !== "ok") return result;
|
|
415
|
+
if (result.result.exitCode !== 0) return { kind: "not-a-repo" };
|
|
416
|
+
return { kind: "ok", status: parseGitStatusPorcelainV2(result.result.stdout) };
|
|
407
417
|
}
|
|
408
418
|
|
|
409
|
-
private async fetchPullRequest(): Promise<
|
|
419
|
+
private async fetchPullRequest(): Promise<
|
|
420
|
+
| { kind: "ok"; pullRequest: PullRequestSnapshot | undefined }
|
|
421
|
+
| { kind: "not-found" }
|
|
422
|
+
| { kind: "transient" }
|
|
423
|
+
| { kind: "unavailable" }
|
|
424
|
+
> {
|
|
410
425
|
const result = await this.runCommandSafely("gh", GH_PR_VIEW_ARGS, this.ghTimeoutMs);
|
|
411
|
-
if (
|
|
412
|
-
|
|
426
|
+
if (result.kind !== "ok") return result;
|
|
427
|
+
if (result.result.exitCode !== 0) return { kind: "not-found" };
|
|
428
|
+
return { kind: "ok", pullRequest: parsePullRequestJson(result.result.stdout) };
|
|
413
429
|
}
|
|
414
430
|
|
|
415
431
|
private async runCommandSafely(
|
|
416
432
|
command: string,
|
|
417
433
|
args: readonly string[],
|
|
418
434
|
timeoutMs: number,
|
|
419
|
-
): Promise<
|
|
420
|
-
if (this.disposed) return
|
|
435
|
+
): Promise<RunCommandSafelyResult> {
|
|
436
|
+
if (this.disposed) return { kind: "transient" };
|
|
421
437
|
const controller = new AbortController();
|
|
422
438
|
this.inflightControllers.add(controller);
|
|
423
439
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
424
440
|
try {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
441
|
+
const result = await this.runner(command, args, { cwd: this.cwd(), signal: controller.signal });
|
|
442
|
+
return { kind: "ok", result };
|
|
443
|
+
} catch (error) {
|
|
444
|
+
return isCommandUnavailableError(error) ? { kind: "unavailable" } : { kind: "transient" };
|
|
428
445
|
} finally {
|
|
429
446
|
clearTimeout(timeoutId);
|
|
430
447
|
this.inflightControllers.delete(controller);
|
|
@@ -478,3 +495,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
478
495
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
479
496
|
});
|
|
480
497
|
}
|
|
498
|
+
|
|
499
|
+
export const __testing = {
|
|
500
|
+
GitFooterCache,
|
|
501
|
+
GIT_STATUS_ARGS,
|
|
502
|
+
GH_PR_VIEW_ARGS,
|
|
503
|
+
parseGitStatusPorcelainV2,
|
|
504
|
+
parsePullRequestJson,
|
|
505
|
+
formatGitStatusFooterSegment,
|
|
506
|
+
formatPullRequestFooterSegment,
|
|
507
|
+
formatGitFooterStatus,
|
|
508
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.80.3
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.80.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-illustrations-to-explain-things",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A pi skill for generating clean, absurd Xiaohei-style article illustrations, shot lists, and visual metaphors.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.80.3
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.80.3
|
|
@@ -477,53 +477,104 @@ async function findAvailableModel(
|
|
|
477
477
|
return undefined;
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
-
|
|
480
|
+
// A model the catalog advertises but the active provider/subscription cannot
|
|
481
|
+
// serve surfaces as a not-found/404-style error (legacy snapshots or
|
|
482
|
+
// access-gated tiers). These are NOT transient: the same model keeps failing,
|
|
483
|
+
// so we fall back to the next candidate instead of retrying it.
|
|
484
|
+
const MODEL_AVAILABILITY_ERROR_PATTERN =
|
|
485
|
+
/\b(?:404|403|not[_ ]?found(?:[_ ]?error)?|model[_ ]?not[_ ]?found(?:[_ ]?error)?|no such model|unknown model|does not exist|is not available|not available|model[_ ]?not[_ ]?available|unsupported model|invalid model|forbidden|access[ _-]?denied|permission[ _-]?denied|not[ _-]?entitled|do(?:es)? not have access)\b/i;
|
|
486
|
+
|
|
487
|
+
function isModelAvailabilityError(message: string | undefined): boolean {
|
|
488
|
+
if (!message) return false;
|
|
489
|
+
return MODEL_AVAILABILITY_ERROR_PATTERN.test(message);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
type LibrarianCandidate = { model: any; details: LibrarianModelDetails };
|
|
493
|
+
|
|
494
|
+
function withLibrarianFallbackReason(candidate: LibrarianCandidate, previous: LibrarianCandidate): LibrarianCandidate {
|
|
495
|
+
return {
|
|
496
|
+
model: candidate.model,
|
|
497
|
+
details: {
|
|
498
|
+
...candidate.details,
|
|
499
|
+
selectionReason: `${candidate.details.selectionReason} (Fell back from ${previous.details.modelRef} after a model-availability error.)`,
|
|
500
|
+
},
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Build an ordered list of model candidates to try. The catalog can advertise
|
|
505
|
+
// models the active provider/subscription cannot actually serve, so callers run
|
|
506
|
+
// these in order and skip ones that fail with a model-availability error,
|
|
507
|
+
// ultimately falling back to the known-good current session model.
|
|
508
|
+
async function buildLibrarianCandidates(
|
|
481
509
|
ctx: { model?: any; modelRegistry: { getAvailable(): any[] | Promise<any[]> } },
|
|
482
510
|
modelPreference: string | undefined,
|
|
483
511
|
thinkingLevel: ThinkingLevel,
|
|
484
|
-
): Promise<
|
|
512
|
+
): Promise<LibrarianCandidate[]> {
|
|
513
|
+
const candidates: LibrarianCandidate[] = [];
|
|
514
|
+
const seen = new Set<string>();
|
|
515
|
+
const push = (model: any, details: LibrarianModelDetails) => {
|
|
516
|
+
if (!model) return;
|
|
517
|
+
const key = `${model.provider}/${model.id}`.toLowerCase();
|
|
518
|
+
if (seen.has(key)) return;
|
|
519
|
+
seen.add(key);
|
|
520
|
+
candidates.push({ model, details });
|
|
521
|
+
};
|
|
522
|
+
|
|
485
523
|
const normalized = modelPreference?.trim();
|
|
524
|
+
let configuredMatched: any | undefined;
|
|
486
525
|
if (normalized) {
|
|
487
|
-
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
selectionReason: "Using the configured librarian model.",
|
|
498
|
-
},
|
|
499
|
-
};
|
|
526
|
+
configuredMatched = await findAvailableModel(ctx, normalized);
|
|
527
|
+
if (configuredMatched) {
|
|
528
|
+
push(configuredMatched, {
|
|
529
|
+
modelRef: modelRef(configuredMatched),
|
|
530
|
+
provider: configuredMatched.provider,
|
|
531
|
+
modelId: configuredMatched.id,
|
|
532
|
+
thinkingLevel,
|
|
533
|
+
autoSelected: false,
|
|
534
|
+
selectionReason: "Using the configured librarian model.",
|
|
535
|
+
});
|
|
500
536
|
}
|
|
501
537
|
}
|
|
502
538
|
|
|
503
539
|
const available = await ctx.modelRegistry.getAvailable();
|
|
504
540
|
const preferred = available.filter(isPreferredFastLibrarianModel);
|
|
505
|
-
const
|
|
506
|
-
? [...preferred].sort((a, b) => rankPreferredFastLibrarianModel(a) - rankPreferredFastLibrarianModel(b))
|
|
507
|
-
: [...available].sort((a, b) => rankLibrarianModel(a) - rankLibrarianModel(b))
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
541
|
+
const ranked = preferred.length > 0
|
|
542
|
+
? [...preferred].sort((a, b) => rankPreferredFastLibrarianModel(a) - rankPreferredFastLibrarianModel(b))
|
|
543
|
+
: [...available].sort((a, b) => rankLibrarianModel(a) - rankLibrarianModel(b));
|
|
544
|
+
const fallbackText = normalized && !configuredMatched
|
|
545
|
+
? ` Configured model ${normalized} was unavailable, so Librarian fell back to auto-selection.`
|
|
546
|
+
: "";
|
|
547
|
+
ranked.forEach((model, index) => {
|
|
548
|
+
const topReason = preferred.length > 0
|
|
549
|
+
? `Selected a preferred fast Librarian model.${fallbackText}`
|
|
550
|
+
: `Selected the cheapest available model because no preferred fast Librarian model was available.${fallbackText}`;
|
|
551
|
+
push(model, {
|
|
552
|
+
modelRef: modelRef(model),
|
|
553
|
+
provider: model.provider,
|
|
554
|
+
modelId: model.id,
|
|
555
|
+
thinkingLevel,
|
|
556
|
+
autoSelected: true,
|
|
557
|
+
selectionReason: index === 0 ? topReason : `Auto-selected ${modelRef(model)} as a lower-ranked fallback.`,
|
|
558
|
+
});
|
|
559
|
+
});
|
|
511
560
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
details: {
|
|
519
|
-
modelRef: modelRef(winner),
|
|
520
|
-
provider: winner.provider,
|
|
521
|
-
modelId: winner.id,
|
|
561
|
+
// Final fallback: the active session model is known to be servable.
|
|
562
|
+
if (ctx.model) {
|
|
563
|
+
push(ctx.model, {
|
|
564
|
+
modelRef: modelRef(ctx.model),
|
|
565
|
+
provider: ctx.model.provider,
|
|
566
|
+
modelId: ctx.model.id,
|
|
522
567
|
thinkingLevel,
|
|
523
568
|
autoSelected: true,
|
|
524
|
-
selectionReason
|
|
525
|
-
}
|
|
526
|
-
}
|
|
569
|
+
selectionReason: `Used the current session model ${modelRef(ctx.model)} as a final fallback.`,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (candidates.length === 0) {
|
|
574
|
+
throw new Error("No authenticated models are available for Librarian. Log in or configure an API key first.");
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return candidates;
|
|
527
578
|
}
|
|
528
579
|
|
|
529
580
|
function resolveToolPath(cwd: string, rawPath: string): string {
|
|
@@ -726,6 +777,13 @@ function isAbortLikeError(error: unknown): boolean {
|
|
|
726
777
|
return /aborted|cancelled|canceled/i.test(message);
|
|
727
778
|
}
|
|
728
779
|
|
|
780
|
+
export const __test__ = {
|
|
781
|
+
buildLibrarianCandidates,
|
|
782
|
+
findAvailableModel,
|
|
783
|
+
isModelAvailabilityError,
|
|
784
|
+
parseModelPreference,
|
|
785
|
+
};
|
|
786
|
+
|
|
729
787
|
export default function librarianExtension(pi: ExtensionAPI) {
|
|
730
788
|
let cachePreference: CacheMode = DEFAULT_CACHE_MODE;
|
|
731
789
|
let modelPreference: string | undefined;
|
|
@@ -944,7 +1002,8 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
944
1002
|
parseThinkingLevel((params as { thinkingLevel?: unknown }).thinkingLevel) ??
|
|
945
1003
|
explicitModel.thinkingLevel ??
|
|
946
1004
|
thinkingPreference;
|
|
947
|
-
const
|
|
1005
|
+
const candidates = await buildLibrarianCandidates(ctx, explicitModel.model ?? modelPreference, thinkingLevel);
|
|
1006
|
+
const selectedModel = candidates[0];
|
|
948
1007
|
|
|
949
1008
|
const workspaceBase = path.join(os.tmpdir(), "pi-librarian");
|
|
950
1009
|
await fs.mkdir(workspaceBase, { recursive: true });
|
|
@@ -1045,62 +1104,124 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
1045
1104
|
|
|
1046
1105
|
await resourceLoader.reload();
|
|
1047
1106
|
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1107
|
+
const runAttempt = async (
|
|
1108
|
+
candidate: LibrarianCandidate,
|
|
1109
|
+
): Promise<{ answer: string; lastAssistant: AssistantLikeMessage | undefined }> => {
|
|
1110
|
+
details.model = candidate.details;
|
|
1111
|
+
details.turns = 0;
|
|
1112
|
+
details.toolCalls = [];
|
|
1113
|
+
emit(true);
|
|
1114
|
+
|
|
1115
|
+
const created = await createAgentSession({
|
|
1116
|
+
cwd: workspace,
|
|
1117
|
+
modelRegistry: ctx.modelRegistry,
|
|
1118
|
+
resourceLoader,
|
|
1119
|
+
sessionManager: SessionManager.inMemory(workspace),
|
|
1120
|
+
model: candidate.model,
|
|
1121
|
+
thinkingLevel,
|
|
1122
|
+
tools: ["read", "bash"],
|
|
1123
|
+
});
|
|
1057
1124
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1125
|
+
session = created.session as typeof session;
|
|
1126
|
+
unsubscribe = (created.session as any).subscribe((event: any) => {
|
|
1127
|
+
switch (event.type) {
|
|
1128
|
+
case "turn_end":
|
|
1129
|
+
details.turns += 1;
|
|
1130
|
+
emit();
|
|
1131
|
+
break;
|
|
1132
|
+
case "tool_execution_start":
|
|
1133
|
+
details.toolCalls.push({
|
|
1134
|
+
id: event.toolCallId,
|
|
1135
|
+
name: event.toolName,
|
|
1136
|
+
args: event.args,
|
|
1137
|
+
startedAt: Date.now(),
|
|
1138
|
+
});
|
|
1139
|
+
if (details.toolCalls.length > MAX_TOOL_CALLS_TO_KEEP) {
|
|
1140
|
+
details.toolCalls.splice(0, details.toolCalls.length - MAX_TOOL_CALLS_TO_KEEP);
|
|
1141
|
+
}
|
|
1142
|
+
emit(true);
|
|
1143
|
+
break;
|
|
1144
|
+
case "tool_execution_end": {
|
|
1145
|
+
const call = details.toolCalls.find((item) => item.id === event.toolCallId);
|
|
1146
|
+
if (call) {
|
|
1147
|
+
call.endedAt = Date.now();
|
|
1148
|
+
call.isError = event.isError;
|
|
1149
|
+
}
|
|
1150
|
+
emit(true);
|
|
1151
|
+
break;
|
|
1082
1152
|
}
|
|
1083
|
-
emit(true);
|
|
1084
|
-
break;
|
|
1085
1153
|
}
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
try {
|
|
1157
|
+
if (!aborted) {
|
|
1158
|
+
const promptPromise = created.session.prompt(buildUserPrompt(query, repos, owners, maxSearchResults, details.cache), {
|
|
1159
|
+
expandPromptTemplates: false,
|
|
1160
|
+
});
|
|
1161
|
+
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
1162
|
+
runTimeout = setTimeout(() => {
|
|
1163
|
+
abort();
|
|
1164
|
+
reject(new Error(`Librarian timed out after ${Math.round(MAX_RUN_MS / 1000)} seconds.`));
|
|
1165
|
+
}, MAX_RUN_MS);
|
|
1166
|
+
});
|
|
1167
|
+
await Promise.race([promptPromise, timeoutPromise]);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const lastAssistant = session ? getLastAssistantMessage(session.state.messages) : undefined;
|
|
1171
|
+
const answer = session ? extractLastAssistantText(session.state.messages) : "";
|
|
1172
|
+
return { answer, lastAssistant };
|
|
1173
|
+
} finally {
|
|
1174
|
+
if (runTimeout) {
|
|
1175
|
+
clearTimeout(runTimeout);
|
|
1176
|
+
runTimeout = undefined;
|
|
1177
|
+
}
|
|
1178
|
+
unsubscribe?.();
|
|
1179
|
+
unsubscribe = undefined;
|
|
1180
|
+
session?.dispose();
|
|
1181
|
+
session = undefined;
|
|
1086
1182
|
}
|
|
1087
|
-
}
|
|
1183
|
+
};
|
|
1088
1184
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1185
|
+
let answer = "";
|
|
1186
|
+
let lastAssistant: AssistantLikeMessage | undefined;
|
|
1187
|
+
let attemptErrorReason: string | undefined;
|
|
1188
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
1189
|
+
if (aborted) break;
|
|
1190
|
+
const candidate =
|
|
1191
|
+
index === 0 ? candidates[index] : withLibrarianFallbackReason(candidates[index], candidates[index - 1]);
|
|
1192
|
+
|
|
1193
|
+
let attempt: { answer: string; lastAssistant: AssistantLikeMessage | undefined };
|
|
1194
|
+
try {
|
|
1195
|
+
attempt = await runAttempt(candidate);
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
// Let aborts/timeouts propagate to the outer handler.
|
|
1198
|
+
if (aborted || isAbortLikeError(error)) throw error;
|
|
1199
|
+
// Some providers throw (rather than carry the error on the assistant
|
|
1200
|
+
// message) when a model is unavailable; treat that like a
|
|
1201
|
+
// message-carried availability error and fall back to the next model.
|
|
1202
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1203
|
+
if (index < candidates.length - 1 && isModelAvailabilityError(message)) {
|
|
1204
|
+
answer = "";
|
|
1205
|
+
lastAssistant = undefined;
|
|
1206
|
+
attemptErrorReason = `the internal subagent stopped with an error: ${message}`;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
answer = attempt.answer;
|
|
1213
|
+
lastAssistant = attempt.lastAssistant;
|
|
1214
|
+
if (answer || aborted) break;
|
|
1215
|
+
|
|
1216
|
+
attemptErrorReason = describeNoAnswerReason(lastAssistant, details.turns);
|
|
1217
|
+
const errorMessage =
|
|
1218
|
+
lastAssistant && lastAssistant.stopReason === "error" && typeof lastAssistant.errorMessage === "string"
|
|
1219
|
+
? lastAssistant.errorMessage
|
|
1220
|
+
: undefined;
|
|
1221
|
+
const canFallBack = index < candidates.length - 1 && isModelAvailabilityError(errorMessage);
|
|
1222
|
+
if (!canFallBack) break;
|
|
1100
1223
|
}
|
|
1101
1224
|
|
|
1102
|
-
const lastAssistant = session ? getLastAssistantMessage(session.state.messages) : undefined;
|
|
1103
|
-
const answer = session ? extractLastAssistantText(session.state.messages) : "";
|
|
1104
1225
|
if (answer) {
|
|
1105
1226
|
lastContent = answer;
|
|
1106
1227
|
details.status = "done";
|
|
@@ -1109,7 +1230,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
1109
1230
|
details.status = "aborted";
|
|
1110
1231
|
} else {
|
|
1111
1232
|
details.status = "error";
|
|
1112
|
-
const reason = describeNoAnswerReason(lastAssistant, details.turns);
|
|
1233
|
+
const reason = attemptErrorReason ?? describeNoAnswerReason(lastAssistant, details.turns);
|
|
1113
1234
|
lastContent = formatInternalFailure(details, reason);
|
|
1114
1235
|
details.error = reason;
|
|
1115
1236
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.80.3
|