@diegopetrucci/pi-librarian 0.1.7 → 0.1.10
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 +1 -1
- package/index.ts +248 -89
- package/package.json +5 -1
package/.pi-fleet-tested-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.80.6
|
package/README.md
CHANGED
|
@@ -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> | thinking <off|minimal|low|medium|high|xhigh|auto> | clear [all|model|thinking]`
|
|
42
|
+
- Configure internal subagent defaults with `/librarian-config status | model <provider/model|auto> | thinking <off|minimal|low|medium|high|xhigh|max|auto> | clear [all|model|thinking]`
|
|
43
43
|
- Cached repos are removed lazily after 7 days without use
|
|
44
44
|
|
|
45
45
|
## Commands
|
package/index.ts
CHANGED
|
@@ -30,11 +30,25 @@ const CACHE_CONFIG_FILE = "librarian.json";
|
|
|
30
30
|
type LibrarianStatus = "running" | "done" | "error" | "aborted";
|
|
31
31
|
|
|
32
32
|
type CacheMode = "disabled" | "enabled";
|
|
33
|
-
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
33
|
+
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
34
|
+
type ThinkingLevelMap = Partial<Record<ThinkingLevel, unknown | null>>;
|
|
35
|
+
type PiModel = {
|
|
36
|
+
provider: string;
|
|
37
|
+
id: string;
|
|
38
|
+
reasoning?: boolean;
|
|
39
|
+
thinkingLevelMap?: ThinkingLevelMap;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type CreateAgentSessionOptions = NonNullable<Parameters<typeof createAgentSession>[0]>;
|
|
43
|
+
|
|
44
|
+
function getModelRuntimeOption(ctx: { modelRegistry?: unknown }): Pick<CreateAgentSessionOptions, "modelRuntime"> {
|
|
45
|
+
const modelRuntime = (ctx.modelRegistry as { runtime?: CreateAgentSessionOptions["modelRuntime"] } | undefined)?.runtime;
|
|
46
|
+
return modelRuntime ? { modelRuntime } : {};
|
|
47
|
+
}
|
|
34
48
|
|
|
35
49
|
const DEFAULT_CACHE_MODE: CacheMode = "disabled";
|
|
36
50
|
const DEFAULT_THINKING_LEVEL: ThinkingLevel = "low";
|
|
37
|
-
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
51
|
+
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
38
52
|
|
|
39
53
|
const PREFERRED_FAST_MODEL_PATTERNS = [
|
|
40
54
|
/\bgpt[-_. ]?5\.5(?:[-_. ].*)?\b(?:mini|nano|fast|lite)\b/,
|
|
@@ -316,6 +330,29 @@ function parseThinkingLevel(value: unknown): ThinkingLevel | undefined {
|
|
|
316
330
|
return (THINKING_LEVELS as readonly string[]).includes(normalized) ? (normalized as ThinkingLevel) : undefined;
|
|
317
331
|
}
|
|
318
332
|
|
|
333
|
+
function isThinkingLevelSupported(model: PiModel, level: ThinkingLevel): boolean {
|
|
334
|
+
if (!model.reasoning) return level === "off";
|
|
335
|
+
const map = model.thinkingLevelMap;
|
|
336
|
+
if (level === "xhigh" || level === "max") {
|
|
337
|
+
return !!map && Object.prototype.hasOwnProperty.call(map, level) && map[level] != null;
|
|
338
|
+
}
|
|
339
|
+
return map?.[level] !== null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function resolveThinkingLevel(model: PiModel, requested: ThinkingLevel): ThinkingLevel {
|
|
343
|
+
if (isThinkingLevelSupported(model, requested)) return requested;
|
|
344
|
+
const requestedIndex = THINKING_LEVELS.indexOf(requested);
|
|
345
|
+
for (let index = requestedIndex + 1; index < THINKING_LEVELS.length; index += 1) {
|
|
346
|
+
const level = THINKING_LEVELS[index];
|
|
347
|
+
if (isThinkingLevelSupported(model, level)) return level;
|
|
348
|
+
}
|
|
349
|
+
for (let index = requestedIndex - 1; index >= 0; index -= 1) {
|
|
350
|
+
const level = THINKING_LEVELS[index];
|
|
351
|
+
if (isThinkingLevelSupported(model, level)) return level;
|
|
352
|
+
}
|
|
353
|
+
return "off";
|
|
354
|
+
}
|
|
355
|
+
|
|
319
356
|
function normalizeModelPreference(value: unknown): string | undefined {
|
|
320
357
|
if (typeof value !== "string") return undefined;
|
|
321
358
|
const trimmed = value.trim();
|
|
@@ -327,7 +364,7 @@ function normalizeModelPreference(value: unknown): string | undefined {
|
|
|
327
364
|
function parseModelPreference(value: unknown): { model?: string; thinkingLevel?: ThinkingLevel } {
|
|
328
365
|
const model = normalizeModelPreference(value);
|
|
329
366
|
if (!model) return {};
|
|
330
|
-
const match = model.match(/^(.*):(off|minimal|low|medium|high|xhigh)$/i);
|
|
367
|
+
const match = model.match(/^(.*):(off|minimal|low|medium|high|xhigh|max)$/i);
|
|
331
368
|
if (!match?.[1]) return { model };
|
|
332
369
|
const baseModel = match[1].trim();
|
|
333
370
|
if (!baseModel || baseModel.toLowerCase() === "auto" || baseModel.toLowerCase() === "current") {
|
|
@@ -477,53 +514,104 @@ async function findAvailableModel(
|
|
|
477
514
|
return undefined;
|
|
478
515
|
}
|
|
479
516
|
|
|
480
|
-
|
|
517
|
+
// A model the catalog advertises but the active provider/subscription cannot
|
|
518
|
+
// serve surfaces as a not-found/404-style error (legacy snapshots or
|
|
519
|
+
// access-gated tiers). These are NOT transient: the same model keeps failing,
|
|
520
|
+
// so we fall back to the next candidate instead of retrying it.
|
|
521
|
+
const MODEL_AVAILABILITY_ERROR_PATTERN =
|
|
522
|
+
/\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;
|
|
523
|
+
|
|
524
|
+
function isModelAvailabilityError(message: string | undefined): boolean {
|
|
525
|
+
if (!message) return false;
|
|
526
|
+
return MODEL_AVAILABILITY_ERROR_PATTERN.test(message);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
type LibrarianCandidate = { model: any; details: LibrarianModelDetails };
|
|
530
|
+
|
|
531
|
+
function withLibrarianFallbackReason(candidate: LibrarianCandidate, previous: LibrarianCandidate): LibrarianCandidate {
|
|
532
|
+
return {
|
|
533
|
+
model: candidate.model,
|
|
534
|
+
details: {
|
|
535
|
+
...candidate.details,
|
|
536
|
+
selectionReason: `${candidate.details.selectionReason} (Fell back from ${previous.details.modelRef} after a model-availability error.)`,
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Build an ordered list of model candidates to try. The catalog can advertise
|
|
542
|
+
// models the active provider/subscription cannot actually serve, so callers run
|
|
543
|
+
// these in order and skip ones that fail with a model-availability error,
|
|
544
|
+
// ultimately falling back to the known-good current session model.
|
|
545
|
+
async function buildLibrarianCandidates(
|
|
481
546
|
ctx: { model?: any; modelRegistry: { getAvailable(): any[] | Promise<any[]> } },
|
|
482
547
|
modelPreference: string | undefined,
|
|
483
548
|
thinkingLevel: ThinkingLevel,
|
|
484
|
-
): Promise<
|
|
549
|
+
): Promise<LibrarianCandidate[]> {
|
|
550
|
+
const candidates: LibrarianCandidate[] = [];
|
|
551
|
+
const seen = new Set<string>();
|
|
552
|
+
const push = (model: any, details: LibrarianModelDetails) => {
|
|
553
|
+
if (!model) return;
|
|
554
|
+
const key = `${model.provider}/${model.id}`.toLowerCase();
|
|
555
|
+
if (seen.has(key)) return;
|
|
556
|
+
seen.add(key);
|
|
557
|
+
candidates.push({ model, details });
|
|
558
|
+
};
|
|
559
|
+
|
|
485
560
|
const normalized = modelPreference?.trim();
|
|
561
|
+
let configuredMatched: any | undefined;
|
|
486
562
|
if (normalized) {
|
|
487
|
-
|
|
488
|
-
if (
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
selectionReason: "Using the configured librarian model.",
|
|
498
|
-
},
|
|
499
|
-
};
|
|
563
|
+
configuredMatched = await findAvailableModel(ctx, normalized);
|
|
564
|
+
if (configuredMatched) {
|
|
565
|
+
push(configuredMatched, {
|
|
566
|
+
modelRef: modelRef(configuredMatched),
|
|
567
|
+
provider: configuredMatched.provider,
|
|
568
|
+
modelId: configuredMatched.id,
|
|
569
|
+
thinkingLevel: resolveThinkingLevel(configuredMatched, thinkingLevel),
|
|
570
|
+
autoSelected: false,
|
|
571
|
+
selectionReason: "Using the configured librarian model.",
|
|
572
|
+
});
|
|
500
573
|
}
|
|
501
574
|
}
|
|
502
575
|
|
|
503
576
|
const available = await ctx.modelRegistry.getAvailable();
|
|
504
577
|
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
|
-
|
|
578
|
+
const ranked = preferred.length > 0
|
|
579
|
+
? [...preferred].sort((a, b) => rankPreferredFastLibrarianModel(a) - rankPreferredFastLibrarianModel(b))
|
|
580
|
+
: [...available].sort((a, b) => rankLibrarianModel(a) - rankLibrarianModel(b));
|
|
581
|
+
const fallbackText = normalized && !configuredMatched
|
|
582
|
+
? ` Configured model ${normalized} was unavailable, so Librarian fell back to auto-selection.`
|
|
583
|
+
: "";
|
|
584
|
+
ranked.forEach((model, index) => {
|
|
585
|
+
const topReason = preferred.length > 0
|
|
586
|
+
? `Selected a preferred fast Librarian model.${fallbackText}`
|
|
587
|
+
: `Selected the cheapest available model because no preferred fast Librarian model was available.${fallbackText}`;
|
|
588
|
+
push(model, {
|
|
589
|
+
modelRef: modelRef(model),
|
|
590
|
+
provider: model.provider,
|
|
591
|
+
modelId: model.id,
|
|
592
|
+
thinkingLevel: resolveThinkingLevel(model, thinkingLevel),
|
|
593
|
+
autoSelected: true,
|
|
594
|
+
selectionReason: index === 0 ? topReason : `Auto-selected ${modelRef(model)} as a lower-ranked fallback.`,
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Final fallback: the active session model is known to be servable.
|
|
599
|
+
if (ctx.model) {
|
|
600
|
+
push(ctx.model, {
|
|
601
|
+
modelRef: modelRef(ctx.model),
|
|
602
|
+
provider: ctx.model.provider,
|
|
603
|
+
modelId: ctx.model.id,
|
|
604
|
+
thinkingLevel: resolveThinkingLevel(ctx.model, thinkingLevel),
|
|
605
|
+
autoSelected: true,
|
|
606
|
+
selectionReason: `Used the current session model ${modelRef(ctx.model)} as a final fallback.`,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (candidates.length === 0) {
|
|
509
611
|
throw new Error("No authenticated models are available for Librarian. Log in or configure an API key first.");
|
|
510
612
|
}
|
|
511
613
|
|
|
512
|
-
|
|
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}`;
|
|
516
|
-
return {
|
|
517
|
-
model: winner,
|
|
518
|
-
details: {
|
|
519
|
-
modelRef: modelRef(winner),
|
|
520
|
-
provider: winner.provider,
|
|
521
|
-
modelId: winner.id,
|
|
522
|
-
thinkingLevel,
|
|
523
|
-
autoSelected: true,
|
|
524
|
-
selectionReason,
|
|
525
|
-
},
|
|
526
|
-
};
|
|
614
|
+
return candidates;
|
|
527
615
|
}
|
|
528
616
|
|
|
529
617
|
function resolveToolPath(cwd: string, rawPath: string): string {
|
|
@@ -726,6 +814,14 @@ function isAbortLikeError(error: unknown): boolean {
|
|
|
726
814
|
return /aborted|cancelled|canceled/i.test(message);
|
|
727
815
|
}
|
|
728
816
|
|
|
817
|
+
export const __test__ = {
|
|
818
|
+
buildLibrarianCandidates,
|
|
819
|
+
findAvailableModel,
|
|
820
|
+
isModelAvailabilityError,
|
|
821
|
+
parseModelPreference,
|
|
822
|
+
resolveThinkingLevel,
|
|
823
|
+
};
|
|
824
|
+
|
|
729
825
|
export default function librarianExtension(pi: ExtensionAPI) {
|
|
730
826
|
let cachePreference: CacheMode = DEFAULT_CACHE_MODE;
|
|
731
827
|
let modelPreference: string | undefined;
|
|
@@ -839,7 +935,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
839
935
|
return;
|
|
840
936
|
}
|
|
841
937
|
|
|
842
|
-
notifyCommand(ctx, "Usage: /librarian-config status | model <provider/model|auto> | thinking <off|minimal|low|medium|high|xhigh|auto> | clear [all|model|thinking]", "warning");
|
|
938
|
+
notifyCommand(ctx, "Usage: /librarian-config status | model <provider/model|auto> | thinking <off|minimal|low|medium|high|xhigh|max|auto> | clear [all|model|thinking]", "warning");
|
|
843
939
|
},
|
|
844
940
|
});
|
|
845
941
|
|
|
@@ -944,7 +1040,8 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
944
1040
|
parseThinkingLevel((params as { thinkingLevel?: unknown }).thinkingLevel) ??
|
|
945
1041
|
explicitModel.thinkingLevel ??
|
|
946
1042
|
thinkingPreference;
|
|
947
|
-
const
|
|
1043
|
+
const candidates = await buildLibrarianCandidates(ctx, explicitModel.model ?? modelPreference, thinkingLevel);
|
|
1044
|
+
const selectedModel = candidates[0];
|
|
948
1045
|
|
|
949
1046
|
const workspaceBase = path.join(os.tmpdir(), "pi-librarian");
|
|
950
1047
|
await fs.mkdir(workspaceBase, { recursive: true });
|
|
@@ -1045,62 +1142,124 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
1045
1142
|
|
|
1046
1143
|
await resourceLoader.reload();
|
|
1047
1144
|
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1145
|
+
const runAttempt = async (
|
|
1146
|
+
candidate: LibrarianCandidate,
|
|
1147
|
+
): Promise<{ answer: string; lastAssistant: AssistantLikeMessage | undefined }> => {
|
|
1148
|
+
details.model = candidate.details;
|
|
1149
|
+
details.turns = 0;
|
|
1150
|
+
details.toolCalls = [];
|
|
1151
|
+
emit(true);
|
|
1152
|
+
|
|
1153
|
+
const created = await createAgentSession({
|
|
1154
|
+
cwd: workspace,
|
|
1155
|
+
...getModelRuntimeOption(ctx),
|
|
1156
|
+
resourceLoader,
|
|
1157
|
+
sessionManager: SessionManager.inMemory(workspace),
|
|
1158
|
+
model: candidate.model,
|
|
1159
|
+
thinkingLevel: candidate.details.thinkingLevel,
|
|
1160
|
+
tools: ["read", "bash"],
|
|
1161
|
+
});
|
|
1057
1162
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1163
|
+
session = created.session as typeof session;
|
|
1164
|
+
unsubscribe = (created.session as any).subscribe((event: any) => {
|
|
1165
|
+
switch (event.type) {
|
|
1166
|
+
case "turn_end":
|
|
1167
|
+
details.turns += 1;
|
|
1168
|
+
emit();
|
|
1169
|
+
break;
|
|
1170
|
+
case "tool_execution_start":
|
|
1171
|
+
details.toolCalls.push({
|
|
1172
|
+
id: event.toolCallId,
|
|
1173
|
+
name: event.toolName,
|
|
1174
|
+
args: event.args,
|
|
1175
|
+
startedAt: Date.now(),
|
|
1176
|
+
});
|
|
1177
|
+
if (details.toolCalls.length > MAX_TOOL_CALLS_TO_KEEP) {
|
|
1178
|
+
details.toolCalls.splice(0, details.toolCalls.length - MAX_TOOL_CALLS_TO_KEEP);
|
|
1179
|
+
}
|
|
1180
|
+
emit(true);
|
|
1181
|
+
break;
|
|
1182
|
+
case "tool_execution_end": {
|
|
1183
|
+
const call = details.toolCalls.find((item) => item.id === event.toolCallId);
|
|
1184
|
+
if (call) {
|
|
1185
|
+
call.endedAt = Date.now();
|
|
1186
|
+
call.isError = event.isError;
|
|
1187
|
+
}
|
|
1188
|
+
emit(true);
|
|
1189
|
+
break;
|
|
1082
1190
|
}
|
|
1083
|
-
emit(true);
|
|
1084
|
-
break;
|
|
1085
1191
|
}
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
try {
|
|
1195
|
+
if (!aborted) {
|
|
1196
|
+
const promptPromise = created.session.prompt(buildUserPrompt(query, repos, owners, maxSearchResults, details.cache), {
|
|
1197
|
+
expandPromptTemplates: false,
|
|
1198
|
+
});
|
|
1199
|
+
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
1200
|
+
runTimeout = setTimeout(() => {
|
|
1201
|
+
abort();
|
|
1202
|
+
reject(new Error(`Librarian timed out after ${Math.round(MAX_RUN_MS / 1000)} seconds.`));
|
|
1203
|
+
}, MAX_RUN_MS);
|
|
1204
|
+
});
|
|
1205
|
+
await Promise.race([promptPromise, timeoutPromise]);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const lastAssistant = session ? getLastAssistantMessage(session.state.messages) : undefined;
|
|
1209
|
+
const answer = session ? extractLastAssistantText(session.state.messages) : "";
|
|
1210
|
+
return { answer, lastAssistant };
|
|
1211
|
+
} finally {
|
|
1212
|
+
if (runTimeout) {
|
|
1213
|
+
clearTimeout(runTimeout);
|
|
1214
|
+
runTimeout = undefined;
|
|
1215
|
+
}
|
|
1216
|
+
unsubscribe?.();
|
|
1217
|
+
unsubscribe = undefined;
|
|
1218
|
+
session?.dispose();
|
|
1219
|
+
session = undefined;
|
|
1086
1220
|
}
|
|
1087
|
-
}
|
|
1221
|
+
};
|
|
1088
1222
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1223
|
+
let answer = "";
|
|
1224
|
+
let lastAssistant: AssistantLikeMessage | undefined;
|
|
1225
|
+
let attemptErrorReason: string | undefined;
|
|
1226
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
1227
|
+
if (aborted) break;
|
|
1228
|
+
const candidate =
|
|
1229
|
+
index === 0 ? candidates[index] : withLibrarianFallbackReason(candidates[index], candidates[index - 1]);
|
|
1230
|
+
|
|
1231
|
+
let attempt: { answer: string; lastAssistant: AssistantLikeMessage | undefined };
|
|
1232
|
+
try {
|
|
1233
|
+
attempt = await runAttempt(candidate);
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
// Let aborts/timeouts propagate to the outer handler.
|
|
1236
|
+
if (aborted || isAbortLikeError(error)) throw error;
|
|
1237
|
+
// Some providers throw (rather than carry the error on the assistant
|
|
1238
|
+
// message) when a model is unavailable; treat that like a
|
|
1239
|
+
// message-carried availability error and fall back to the next model.
|
|
1240
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1241
|
+
if (index < candidates.length - 1 && isModelAvailabilityError(message)) {
|
|
1242
|
+
answer = "";
|
|
1243
|
+
lastAssistant = undefined;
|
|
1244
|
+
attemptErrorReason = `the internal subagent stopped with an error: ${message}`;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
throw error;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
answer = attempt.answer;
|
|
1251
|
+
lastAssistant = attempt.lastAssistant;
|
|
1252
|
+
if (answer || aborted) break;
|
|
1253
|
+
|
|
1254
|
+
attemptErrorReason = describeNoAnswerReason(lastAssistant, details.turns);
|
|
1255
|
+
const errorMessage =
|
|
1256
|
+
lastAssistant && lastAssistant.stopReason === "error" && typeof lastAssistant.errorMessage === "string"
|
|
1257
|
+
? lastAssistant.errorMessage
|
|
1258
|
+
: undefined;
|
|
1259
|
+
const canFallBack = index < candidates.length - 1 && isModelAvailabilityError(errorMessage);
|
|
1260
|
+
if (!canFallBack) break;
|
|
1100
1261
|
}
|
|
1101
1262
|
|
|
1102
|
-
const lastAssistant = session ? getLastAssistantMessage(session.state.messages) : undefined;
|
|
1103
|
-
const answer = session ? extractLastAssistantText(session.state.messages) : "";
|
|
1104
1263
|
if (answer) {
|
|
1105
1264
|
lastContent = answer;
|
|
1106
1265
|
details.status = "done";
|
|
@@ -1109,7 +1268,7 @@ export default function librarianExtension(pi: ExtensionAPI) {
|
|
|
1109
1268
|
details.status = "aborted";
|
|
1110
1269
|
} else {
|
|
1111
1270
|
details.status = "error";
|
|
1112
|
-
const reason = describeNoAnswerReason(lastAssistant, details.turns);
|
|
1271
|
+
const reason = attemptErrorReason ?? describeNoAnswerReason(lastAssistant, details.turns);
|
|
1113
1272
|
lastContent = formatInternalFailure(details, reason);
|
|
1114
1273
|
details.error = reason;
|
|
1115
1274
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-librarian",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "A pi GitHub research scout with a toggleable local repo checkout cache under the user's OS cache directory.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -33,5 +33,9 @@
|
|
|
33
33
|
"@earendil-works/pi-coding-agent": "*",
|
|
34
34
|
"@earendil-works/pi-tui": "*",
|
|
35
35
|
"typebox": "*"
|
|
36
|
+
},
|
|
37
|
+
"type": "module",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.19.0"
|
|
36
40
|
}
|
|
37
41
|
}
|