@diegopetrucci/pi-librarian 0.1.5 → 0.1.6
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 +3 -4
- package/index.ts +119 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A pi GitHub research scout inspired by `pi-librarian`, with a local checkout cac
|
|
|
4
4
|
|
|
5
5
|
When the `librarian` tool runs, it can cache/reuse repository checkouts locally. Use `/librarian-cache off` to force GitHub API/search and temporary fetched files only, or `/librarian-cache on` to re-enable cached local checkouts.
|
|
6
6
|
|
|
7
|
-
The internal librarian subagent uses a
|
|
7
|
+
The internal librarian subagent uses a fast auto-selected model by default, requests `low` thinking, and prompts its scout to batch independent GitHub probes in parallel. Use `/librarian-config` to set a persistent internal model or thinking-level preference.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -39,7 +39,7 @@ Then reload pi:
|
|
|
39
39
|
- Uses `gh` for GitHub search/API access
|
|
40
40
|
- Uses cached local checkouts only when enabled
|
|
41
41
|
- Toggle cache behavior for future calls with `/librarian-cache on | off | toggle | status`
|
|
42
|
-
- Configure internal subagent defaults with `/librarian-config status | model <provider/model|auto
|
|
42
|
+
- Configure internal subagent defaults with `/librarian-config status | model <provider/model|auto> | thinking <off|minimal|low|medium|high|xhigh|auto> | clear [all|model|thinking]`
|
|
43
43
|
- Cached repos are removed lazily after 7 days without use
|
|
44
44
|
|
|
45
45
|
## Commands
|
|
@@ -54,9 +54,8 @@ Then reload pi:
|
|
|
54
54
|
```text
|
|
55
55
|
/librarian-config status
|
|
56
56
|
/librarian-config model auto
|
|
57
|
-
/librarian-config model current
|
|
58
57
|
/librarian-config model anthropic/claude-haiku-4-5:medium
|
|
59
|
-
/librarian-config thinking
|
|
58
|
+
/librarian-config thinking low
|
|
60
59
|
/librarian-config clear model
|
|
61
60
|
```
|
|
62
61
|
|
package/index.ts
CHANGED
|
@@ -33,9 +33,20 @@ type CacheMode = "disabled" | "enabled";
|
|
|
33
33
|
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
34
34
|
|
|
35
35
|
const DEFAULT_CACHE_MODE: CacheMode = "disabled";
|
|
36
|
-
const DEFAULT_THINKING_LEVEL: ThinkingLevel = "
|
|
36
|
+
const DEFAULT_THINKING_LEVEL: ThinkingLevel = "low";
|
|
37
37
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
38
38
|
|
|
39
|
+
const PREFERRED_FAST_MODEL_PATTERNS = [
|
|
40
|
+
/\bgpt[-_. ]?5\.5(?:[-_. ].*)?\b(?:mini|nano|fast|lite)\b/,
|
|
41
|
+
/\bgpt[-_. ]?5(?:[-_. ].*)?\b(?:mini|nano|fast|lite)\b/,
|
|
42
|
+
/\bgpt[-_. ]?4(?:\.1|o)?(?:[-_. ].*)?\b(?:mini|nano)\b/,
|
|
43
|
+
/\bgemini\b.*\b(?:flash|flash-lite|lite)\b/,
|
|
44
|
+
/\bclaude\b.*\bhaiku\b/,
|
|
45
|
+
/\b(?:mistral|codestral)\b.*\b(?:small|mini|lite)\b/,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const HEAVY_MODEL_PATTERN = /\b(?:opus|pro|ultra|max)\b/;
|
|
49
|
+
|
|
39
50
|
type ToolCall = {
|
|
40
51
|
id: string;
|
|
41
52
|
name: string;
|
|
@@ -106,7 +117,7 @@ const LibrarianParams = Type.Object({
|
|
|
106
117
|
),
|
|
107
118
|
model: Type.Optional(
|
|
108
119
|
Type.String({
|
|
109
|
-
description: "Optional model override for the internal librarian subagent. Use provider/model
|
|
120
|
+
description: "Optional model override for the internal librarian subagent. Use provider/model or auto.",
|
|
110
121
|
}),
|
|
111
122
|
),
|
|
112
123
|
thinkingLevel: Type.Optional(
|
|
@@ -308,7 +319,8 @@ function parseThinkingLevel(value: unknown): ThinkingLevel | undefined {
|
|
|
308
319
|
function normalizeModelPreference(value: unknown): string | undefined {
|
|
309
320
|
if (typeof value !== "string") return undefined;
|
|
310
321
|
const trimmed = value.trim();
|
|
311
|
-
|
|
322
|
+
const normalized = trimmed.toLowerCase();
|
|
323
|
+
if (!trimmed || normalized === "auto" || normalized === "current") return undefined;
|
|
312
324
|
return trimmed;
|
|
313
325
|
}
|
|
314
326
|
|
|
@@ -317,7 +329,11 @@ function parseModelPreference(value: unknown): { model?: string; thinkingLevel?:
|
|
|
317
329
|
if (!model) return {};
|
|
318
330
|
const match = model.match(/^(.*):(off|minimal|low|medium|high|xhigh)$/i);
|
|
319
331
|
if (!match?.[1]) return { model };
|
|
320
|
-
|
|
332
|
+
const baseModel = match[1].trim();
|
|
333
|
+
if (!baseModel || baseModel.toLowerCase() === "auto" || baseModel.toLowerCase() === "current") {
|
|
334
|
+
return { thinkingLevel: parseThinkingLevel(match[2]) };
|
|
335
|
+
}
|
|
336
|
+
return { model: baseModel, thinkingLevel: parseThinkingLevel(match[2]) };
|
|
321
337
|
}
|
|
322
338
|
|
|
323
339
|
async function readLibrarianPreferences(): Promise<LibrarianPreferences> {
|
|
@@ -415,11 +431,25 @@ function rankLibrarianModel(model: any): number {
|
|
|
415
431
|
let score = modelCostScore(model) * 1_000_000;
|
|
416
432
|
if (model.reasoning) score += 50;
|
|
417
433
|
if (/\b(?:mini|nano|haiku|flash|lite|small|fast|instant)\b/.test(text)) score -= 10;
|
|
418
|
-
if (
|
|
434
|
+
if (HEAVY_MODEL_PATTERN.test(text)) score += 1_000;
|
|
419
435
|
if ((model.contextWindow ?? 0) < 32_000) score += 100;
|
|
420
436
|
return score;
|
|
421
437
|
}
|
|
422
438
|
|
|
439
|
+
function isPreferredFastLibrarianModel(model: any): boolean {
|
|
440
|
+
const text = `${model.provider ?? ""} ${model.id ?? ""} ${model.name ?? ""}`.toLowerCase();
|
|
441
|
+
if (HEAVY_MODEL_PATTERN.test(text)) return false;
|
|
442
|
+
return PREFERRED_FAST_MODEL_PATTERNS.some((pattern) => pattern.test(text));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function rankPreferredFastLibrarianModel(model: any): number {
|
|
446
|
+
const text = `${model.provider ?? ""} ${model.id ?? ""} ${model.name ?? ""}`.toLowerCase();
|
|
447
|
+
let score = rankLibrarianModel(model);
|
|
448
|
+
const patternIndex = PREFERRED_FAST_MODEL_PATTERNS.findIndex((pattern) => pattern.test(text));
|
|
449
|
+
if (patternIndex >= 0) score += patternIndex * 100;
|
|
450
|
+
return score;
|
|
451
|
+
}
|
|
452
|
+
|
|
423
453
|
async function findAvailableModel(
|
|
424
454
|
ctx: { model?: any; modelRegistry: { getAvailable(): any[] | Promise<any[]> } },
|
|
425
455
|
modelPreference: string,
|
|
@@ -453,21 +483,6 @@ async function selectLibrarianModel(
|
|
|
453
483
|
thinkingLevel: ThinkingLevel,
|
|
454
484
|
): Promise<{ model: any; details: LibrarianModelDetails }> {
|
|
455
485
|
const normalized = modelPreference?.trim();
|
|
456
|
-
if (normalized?.toLowerCase() === "current") {
|
|
457
|
-
if (!ctx.model) throw new Error("Librarian model=current needs an active pi model, but ctx.model is unavailable.");
|
|
458
|
-
return {
|
|
459
|
-
model: ctx.model,
|
|
460
|
-
details: {
|
|
461
|
-
modelRef: modelRef(ctx.model),
|
|
462
|
-
provider: ctx.model.provider,
|
|
463
|
-
modelId: ctx.model.id,
|
|
464
|
-
thinkingLevel,
|
|
465
|
-
autoSelected: false,
|
|
466
|
-
selectionReason: "Using the caller's current model because librarian model=current is configured.",
|
|
467
|
-
},
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
|
|
471
486
|
if (normalized) {
|
|
472
487
|
const matched = await findAvailableModel(ctx, normalized);
|
|
473
488
|
if (matched) {
|
|
@@ -486,15 +501,18 @@ async function selectLibrarianModel(
|
|
|
486
501
|
}
|
|
487
502
|
|
|
488
503
|
const available = await ctx.modelRegistry.getAvailable();
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
504
|
+
const preferred = available.filter(isPreferredFastLibrarianModel);
|
|
505
|
+
const winner = preferred.length > 0
|
|
506
|
+
? [...preferred].sort((a, b) => rankPreferredFastLibrarianModel(a) - rankPreferredFastLibrarianModel(b))[0]
|
|
507
|
+
: [...available].sort((a, b) => rankLibrarianModel(a) - rankLibrarianModel(b))[0];
|
|
493
508
|
if (!winner) {
|
|
494
509
|
throw new Error("No authenticated models are available for Librarian. Log in or configure an API key first.");
|
|
495
510
|
}
|
|
496
511
|
|
|
497
512
|
const fallbackText = normalized ? ` Configured model ${normalized} was unavailable, so Librarian fell back to auto-selection.` : "";
|
|
513
|
+
const selectionReason = preferred.length > 0
|
|
514
|
+
? `Selected a preferred fast Librarian model.${fallbackText}`
|
|
515
|
+
: `Selected the cheapest available model because no preferred fast Librarian model was available.${fallbackText}`;
|
|
498
516
|
return {
|
|
499
517
|
model: winner,
|
|
500
518
|
details: {
|
|
@@ -503,7 +521,7 @@ async function selectLibrarianModel(
|
|
|
503
521
|
modelId: winner.id,
|
|
504
522
|
thinkingLevel,
|
|
505
523
|
autoSelected: true,
|
|
506
|
-
selectionReason
|
|
524
|
+
selectionReason,
|
|
507
525
|
},
|
|
508
526
|
};
|
|
509
527
|
}
|
|
@@ -621,7 +639,7 @@ function buildSystemPrompt(options: {
|
|
|
621
639
|
? `\nLocal checkout cache is ENABLED for this call.\n- Cache root: ${options.cacheRoot}\n- Use checkout path pattern: ${options.cacheRoot}/github.com/<owner>/<repo>\n- Reuse an existing checkout when it has a .git directory. Fetch/prune before relying on it: git -C "$DIR" fetch --all --prune --tags --quiet\n- If missing, clone with gh repo clone "$REPO" "$DIR" (or git clone https://github.com/$REPO.git "$DIR").\n- If a ref/branch/SHA is requested, fetch it and check it out locally before citing files from that ref.\n- After using a checkout, update its cache marker: touch "$DIR/${CACHE_MARKER_FILE}"\n- Prefer local rg/read inside cached checkouts once a repo is cloned, and cite absolute cached paths with line ranges.\n- Clone only repositories that are relevant to the query; do not bulk-clone broad owner/org scopes unless necessary.`
|
|
622
640
|
: `\nLocal checkout cache is DISABLED for this call.\n- Do not clone repositories.\n- Use gh search/API/tree/contents calls and cache only necessary proof files under ${options.workspace}/repos/<owner>/<repo>/<path>.`;
|
|
623
641
|
|
|
624
|
-
return `You are Librarian, an evidence-first GitHub code scout running inside pi.\n\nUse only the available bash/read tools. Use gh, jq, rg, find/fd, ls, stat, mkdir, base64, and nl -ba for GitHub reconnaissance and numbered evidence. Use read for focused local file inspection.\n\nWorkspace: ${options.workspace}\nDefault gh search limit: ${options.maxSearchResults}\nTurn budget: ${MAX_TURNS} turns total, including your final answer. Stop searching once you have enough evidence.\n${cacheSection}\n\nNon-negotiable constraints:\n- Never treat gh search snippets as proof by themselves. Use fetched files or local checkouts for code-content claims.\n- Keep temporary workspace writes under ${options.workspace}/repos unless local checkout cache is enabled, in which case writes under the cache root are also allowed.
|
|
642
|
+
return `You are Librarian, an evidence-first GitHub code scout running inside pi.\n\nUse only the available bash/read tools. Use gh, jq, rg, find/fd, ls, stat, mkdir, base64, and nl -ba for GitHub reconnaissance and numbered evidence. Use read for focused local file inspection.\n\nWorkspace: ${options.workspace}\nDefault gh search limit: ${options.maxSearchResults}\nTurn budget: ${MAX_TURNS} turns total, including your final answer. Stop searching once you have enough evidence.\n${cacheSection}\n\nPerformance guidance:\n- Prefer fast, parallel exploration. When probes are independent, issue multiple separate tool calls in the same assistant turn instead of waiting for each result one-by-one.\n- Aim for 4-8 independent bash/read calls in an exploration turn when useful: parallel gh searches, tree probes, contents fetches, and targeted file reads.\n- Keep dependent work sequential, and avoid concurrent writes to the same file, checkout, or cache directory.\n\nNon-negotiable constraints:\n- Never treat gh search snippets as proof by themselves. Use fetched files or local checkouts for code-content claims.\n- Keep temporary workspace writes under ${options.workspace}/repos unless local checkout cache is enabled, in which case writes under the cache root are also allowed.
|
|
625
643
|
- A runtime guard blocks destructive shell commands, credential/environment inspection, and reads outside the workspace/cache.\n- Never paste whole files. Use short snippets only when they clarify the evidence.\n- If evidence is partial or access fails (404/403), state the limitation clearly.\n- Do not present anything as fact unless it appeared in tool output or in a file you read.\n\nRecommended search flow:\n1. If symbols/text are known, start with gh search code and the provided repo/owner filters.\n2. If a repo is known but paths are unclear, resolve the default branch and inspect the git tree or contents API.\n3. Fetch or clone only the files/repos required to prove the answer.\n4. Use rg/read/nl -ba locally to produce stable path and line evidence.\n\nUseful gh patterns:\n- gh repo view "$REPO" --json defaultBranchRef --jq '.defaultBranchRef.name'\n- gh search code '<terms>' --json path,repository,sha,url,textMatches --limit ${options.maxSearchResults}\n- gh api "repos/$REPO/git/trees/$REF?recursive=1" > tree.json\n- gh api "repos/$REPO/contents/$FILE?ref=$REF" --jq .content | tr -d '\\n' | base64 --decode > "repos/$REPO/$FILE"\n- rg -n '<pattern>' '<local path>'\n- nl -ba '<local file>' | sed -n '10,30p'\n\nOutput format, exact order:\n## Summary\n1-3 concise sentences.\n## Locations\n- \`path\` or \`path:lineStart-lineEnd\` — what is here and why it matters; include GitHub URL when useful. If nothing relevant is found, write \`- (none)\`.\n## Evidence\n- \`path\` or \`path:lineStart-lineEnd\` — what this proves. Include concise snippets only if useful.\n## Searched\nOnly include when incomplete/not found or when the search path matters. List queries, filters, and probes used.\n## Next steps\nOptional: 1-3 narrow follow-up checks for remaining ambiguity.`;
|
|
626
644
|
}
|
|
627
645
|
|
|
@@ -629,20 +647,56 @@ function buildUserPrompt(query: string, repos: string[], owners: string[], maxSe
|
|
|
629
647
|
return `Task: locate and cite exact GitHub code locations that answer the query.\n\nQuery: ${query}\nRepository filters: ${repos.length ? repos.join(", ") : "(none)"}\nOwner filters: ${owners.length ? owners.join(", ") : "(none)"}\nMax search results per gh search call: ${maxSearchResults}\nLocal checkout cache: ${cache.mode === "enabled" ? `enabled at ${cache.root}` : "disabled"}\nCache decision: ${cache.decisionReason}\n\nRespond directly with concise, citation-heavy findings. Always pass --limit ${maxSearchResults} to gh search code unless a narrower command is clearly better.`;
|
|
630
648
|
}
|
|
631
649
|
|
|
632
|
-
|
|
650
|
+
type AssistantLikeMessage = {
|
|
651
|
+
role?: string;
|
|
652
|
+
content?: unknown;
|
|
653
|
+
stopReason?: unknown;
|
|
654
|
+
errorMessage?: unknown;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
function getLastAssistantMessage(messages: unknown[]): AssistantLikeMessage | undefined {
|
|
633
658
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
634
|
-
const message = messages[i] as
|
|
635
|
-
if (message?.role
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
659
|
+
const message = messages[i] as AssistantLikeMessage;
|
|
660
|
+
if (message?.role === "assistant") return message;
|
|
661
|
+
}
|
|
662
|
+
return undefined;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function extractLastAssistantText(messages: unknown[]): string {
|
|
666
|
+
const message = getLastAssistantMessage(messages);
|
|
667
|
+
if (!message || !Array.isArray(message.content)) return "";
|
|
668
|
+
const parts: string[] = [];
|
|
669
|
+
for (const part of message.content) {
|
|
670
|
+
if (part && typeof part === "object" && (part as { type?: string }).type === "text") {
|
|
671
|
+
const text = (part as { text?: unknown }).text;
|
|
672
|
+
if (typeof text === "string") parts.push(text);
|
|
642
673
|
}
|
|
643
|
-
if (parts.length) return parts.join("").trim();
|
|
644
674
|
}
|
|
645
|
-
return "";
|
|
675
|
+
return parts.join("").trim();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function describeNoAnswerReason(message: AssistantLikeMessage | undefined, turns: number): string {
|
|
679
|
+
if (!message) return "the internal subagent produced no assistant message";
|
|
680
|
+
if (message.stopReason === "error") {
|
|
681
|
+
const errorMessage = typeof message.errorMessage === "string" && message.errorMessage.trim()
|
|
682
|
+
? message.errorMessage.trim()
|
|
683
|
+
: "provider/model error";
|
|
684
|
+
return `the internal subagent stopped with an error: ${errorMessage}`;
|
|
685
|
+
}
|
|
686
|
+
if (message.stopReason === "aborted") return "the internal subagent was aborted before producing an answer";
|
|
687
|
+
if (turns >= MAX_TURNS) return `the internal subagent reached the ${MAX_TURNS}-turn budget without producing a final answer`;
|
|
688
|
+
const stopReason = typeof message.stopReason === "string" && message.stopReason.trim()
|
|
689
|
+
? ` (stopReason: ${message.stopReason.trim()})`
|
|
690
|
+
: "";
|
|
691
|
+
return `the internal subagent completed without final assistant text${stopReason}`;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function formatInternalFailure(details: LibrarianDetails, reason: string): string {
|
|
695
|
+
return [
|
|
696
|
+
`Internal librarian run failed: ${reason}.`,
|
|
697
|
+
`Diagnostics: status=${details.status}; model=${details.model.modelRef}; thinking=${details.model.thinkingLevel}; modelSelection=${details.model.autoSelected ? "auto" : "configured"}; turns=${details.turns}; toolCalls=${details.toolCalls.length}.`,
|
|
698
|
+
"No answer was produced. This is not a reliable \"no results found\" signal; retry later or configure a different Librarian model with /librarian-config.",
|
|
699
|
+
].join("\n");
|
|
646
700
|
}
|
|
647
701
|
|
|
648
702
|
function formatToolCall(call: ToolCall): string {
|
|
@@ -731,16 +785,20 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
731
785
|
if (action === "model") {
|
|
732
786
|
const value = rest.join(" ").trim();
|
|
733
787
|
if (!value) {
|
|
734
|
-
notifyCommand(ctx, "Usage: /librarian-config model <provider/model|auto
|
|
788
|
+
notifyCommand(ctx, "Usage: /librarian-config model <provider/model|auto>", "warning");
|
|
735
789
|
return;
|
|
736
790
|
}
|
|
737
791
|
const normalized = value.toLowerCase();
|
|
792
|
+
if (normalized === "current" || normalized.startsWith("current:")) {
|
|
793
|
+
notifyCommand(ctx, "Librarian model=current is no longer supported. Use /librarian-config model auto or choose an explicit provider/model.", "warning");
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
738
796
|
const parsedModel = parseModelPreference(value);
|
|
739
797
|
const next = normalized === "auto" || normalized === "clear" || normalized === "default"
|
|
740
798
|
? { ...currentPreferences(), model: undefined }
|
|
741
799
|
: {
|
|
742
800
|
...currentPreferences(),
|
|
743
|
-
model:
|
|
801
|
+
model: parsedModel.model,
|
|
744
802
|
thinkingLevel: parsedModel.thinkingLevel ?? thinkingPreference,
|
|
745
803
|
};
|
|
746
804
|
const warning = await savePreferences(next);
|
|
@@ -781,7 +839,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
781
839
|
return;
|
|
782
840
|
}
|
|
783
841
|
|
|
784
|
-
notifyCommand(ctx, "Usage: /librarian-config status | model <provider/model|auto
|
|
842
|
+
notifyCommand(ctx, "Usage: /librarian-config status | model <provider/model|auto> | thinking <off|minimal|low|medium|high|xhigh|auto> | clear [all|model|thinking]", "warning");
|
|
785
843
|
},
|
|
786
844
|
});
|
|
787
845
|
|
|
@@ -847,13 +905,20 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
847
905
|
},
|
|
848
906
|
});
|
|
849
907
|
|
|
908
|
+
pi.on("tool_result", async (event) => {
|
|
909
|
+
if (event.toolName !== "librarian") return undefined;
|
|
910
|
+
const details = event.details as LibrarianDetails | undefined;
|
|
911
|
+
if (details?.status !== "error") return undefined;
|
|
912
|
+
return { isError: true };
|
|
913
|
+
});
|
|
914
|
+
|
|
850
915
|
pi.registerTool({
|
|
851
916
|
name: "librarian",
|
|
852
917
|
label: "Librarian",
|
|
853
918
|
description:
|
|
854
919
|
"GitHub research scout for coding and personal-assistant tasks. Use when the answer likely lives in GitHub repos, exact repo/path locations are unknown, or you'd otherwise do exploratory gh search/tree probes plus local rg/read inspection. Librarian uses an optional 7-day local checkout cache that is disabled by default; toggle it with /librarian-cache. Configure its internal subagent defaults with /librarian-config.",
|
|
855
920
|
promptSnippet:
|
|
856
|
-
"Research GitHub repositories with evidence-first path and line citations; local checkout cache is disabled by default and user-toggleable with /librarian-cache. Internal subagent defaults are user-configurable with /librarian-config and default to
|
|
921
|
+
"Research GitHub repositories with evidence-first path and line citations; local checkout cache is disabled by default and user-toggleable with /librarian-cache. Internal subagent defaults are user-configurable with /librarian-config and default to low thinking.",
|
|
857
922
|
promptGuidelines: [
|
|
858
923
|
"Use librarian when the answer likely requires exploratory GitHub repository search or line-cited evidence from external repos.",
|
|
859
924
|
"Do not use librarian for files already present in the current workspace unless the user asks for external GitHub research.",
|
|
@@ -1034,9 +1099,20 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
1034
1099
|
await Promise.race([promptPromise, timeoutPromise]);
|
|
1035
1100
|
}
|
|
1036
1101
|
|
|
1102
|
+
const lastAssistant = session ? getLastAssistantMessage(session.state.messages) : undefined;
|
|
1037
1103
|
const answer = session ? extractLastAssistantText(session.state.messages) : "";
|
|
1038
|
-
|
|
1039
|
-
|
|
1104
|
+
if (answer) {
|
|
1105
|
+
lastContent = answer;
|
|
1106
|
+
details.status = "done";
|
|
1107
|
+
} else if (aborted) {
|
|
1108
|
+
lastContent = "Aborted";
|
|
1109
|
+
details.status = "aborted";
|
|
1110
|
+
} else {
|
|
1111
|
+
details.status = "error";
|
|
1112
|
+
const reason = describeNoAnswerReason(lastAssistant, details.turns);
|
|
1113
|
+
lastContent = formatInternalFailure(details, reason);
|
|
1114
|
+
details.error = reason;
|
|
1115
|
+
}
|
|
1040
1116
|
details.endedAt = Date.now();
|
|
1041
1117
|
emit(true);
|
|
1042
1118
|
|
package/package.json
CHANGED