@thurstonsand/pi-librarian 0.3.1 → 0.4.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/RELEASE.md +13 -0
- package/docs/designs/05-pi-0806-attach-and-model-resolution.md +171 -0
- package/extensions/librarian/attach.ts +31 -4
- package/extensions/librarian/model.ts +41 -29
- package/extensions/librarian/settings.ts +23 -9
- package/extensions/librarian.ts +16 -8
- package/package.json +9 -9
package/RELEASE.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
# Release notes
|
|
4
4
|
|
|
5
|
+
## 0.4.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added compact, expandable transcript entries for `/librarian` attach and detach changes, including the exact repo tools affected.
|
|
10
|
+
- Added support for Pi's `max` thinking level.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Changed `librarian.model` to use Pi's native model-pattern resolution.
|
|
15
|
+
- Raised the minimum supported Pi version to 0.80.6.
|
|
16
|
+
- Changed persisted attach entries to require tool snapshots. Older attach entries are now ignored.
|
|
17
|
+
|
|
5
18
|
## 0.3.1
|
|
6
19
|
|
|
7
20
|
### Changed
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Pi 0.80.6 attach entries, thinking, and model resolution
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
## Decision Summary
|
|
8
|
+
|
|
9
|
+
Adopt Pi 0.80.6's custom-entry renderer, CLI model resolver, and `max` thinking level. Attach state changes become durable transcript accordions with historical tool snapshots; configured models follow Pi's native model-pattern semantics and fall back to the current session model only when Pi cannot resolve them.
|
|
10
|
+
|
|
11
|
+
## Problem Statement / Background
|
|
12
|
+
|
|
13
|
+
Pi-librarian already persists `/librarian` attach state as `pi-librarian:attach` custom entries, but those entries are invisible in the transcript. The command compensates with transient notifications, so a resumed session can restore its state without showing when or how the state changed.
|
|
14
|
+
|
|
15
|
+
Pi 0.80.4 added `registerEntryRenderer`, which can present state records without sending them to the model. The rendered entry must snapshot the affected tool names so historical entries remain accurate if pi-librarian's attachable toolset changes later; older incomplete shapes are deliberately unsupported.
|
|
16
|
+
|
|
17
|
+
Configured librarian models currently use local fuzzy matching over `modelRegistry.getAvailable()`. Pi exports `resolveCliModel`, the canonical resolver for exact references, bare and provider-scoped fuzzy patterns, and custom model IDs. Pi-librarian should use that behavior directly rather than impose a separate availability policy.
|
|
18
|
+
|
|
19
|
+
Pi 0.80.6 extends the public `ThinkingLevel` type with `max`. Pi exposes model-specific `getSupportedThinkingLevels`, but no public runtime list of every accepted setting value, so settings validation must keep one local runtime list aligned with Pi's public type.
|
|
20
|
+
|
|
21
|
+
## Goals
|
|
22
|
+
|
|
23
|
+
- Make actual attach and detach changes visible as durable, compact transcript history without duplicate notifications.
|
|
24
|
+
- Preserve the exact toolset affected by each new historical entry.
|
|
25
|
+
- Reject attach entries that do not match the current data shape without migration behavior.
|
|
26
|
+
- Use Pi's canonical configured-model resolution and fallback to the current session model only on resolution failure.
|
|
27
|
+
- Make every configured-model fallback visible and actionable.
|
|
28
|
+
- Accept and preserve Pi 0.80.6's `max` thinking level from settings through nested runs.
|
|
29
|
+
|
|
30
|
+
## Non-Goals
|
|
31
|
+
|
|
32
|
+
- Persist `/librarian status` queries or repeated explicit `on`/`off` no-ops.
|
|
33
|
+
- Send attach state entries to the LLM.
|
|
34
|
+
- Change nested-run extension loading to use `InlineExtension`.
|
|
35
|
+
- Add a separate authentication or availability gate after Pi resolves a configured model.
|
|
36
|
+
- Choose a different fallback model when no current session model exists.
|
|
37
|
+
|
|
38
|
+
## Exposed Shape
|
|
39
|
+
|
|
40
|
+
### `/librarian` command feedback
|
|
41
|
+
|
|
42
|
+
An actual state change appends one `pi-librarian:attach` custom entry and does not also show a notification. `/librarian status` remains a transient notification. `/librarian on` while attached and `/librarian off` while detached remain transient no-ops, using symmetrical wording:
|
|
43
|
+
|
|
44
|
+
- `Librarian tools already attached.`
|
|
45
|
+
- `Librarian tools already detached.`
|
|
46
|
+
|
|
47
|
+
### Attach transcript entry
|
|
48
|
+
|
|
49
|
+
The compact line is stable when expanded:
|
|
50
|
+
|
|
51
|
+
- `Librarian tools attached`
|
|
52
|
+
- `Librarian tools detached`
|
|
53
|
+
|
|
54
|
+
Attached state uses success styling; detached state uses muted styling. Expanding a current entry reveals the exact affected tool names beneath the unchanged compact line. Tool names appear in their registered order.
|
|
55
|
+
|
|
56
|
+
New entries persist:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
{
|
|
60
|
+
attached: boolean;
|
|
61
|
+
tools: string[];
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Both attach and detach entries snapshot the complete affected tool list. Any entry that does not match the complete current shape renders `Librarian attach state unavailable` and does not influence restored state.
|
|
66
|
+
|
|
67
|
+
### Thinking-level settings
|
|
68
|
+
|
|
69
|
+
`librarian.thinkingLevel` accepts `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. The selected value crosses settings, model resolution, and nested-run boundaries unchanged; provider/model capability handling remains Pi's responsibility. Because Pi has no public runtime constant for the complete setting vocabulary, pi-librarian keeps one local list checked against Pi's public `ThinkingLevel` type.
|
|
70
|
+
|
|
71
|
+
### Configured model resolution
|
|
72
|
+
|
|
73
|
+
`librarian.model` accepts the same patterns as Pi: exact or fuzzy bare patterns such as `opus`, provider-scoped patterns such as `anthropic/opus`, and custom model IDs supported by `resolveCliModel`. `resolveLibrarianModel` passes the pattern directly to Pi against `modelRegistry.getAll()` and accepts any returned model. Resolver warnings, including custom-ID fallback warnings, are shown without rejecting the model.
|
|
74
|
+
|
|
75
|
+
When configured resolution returns an error or no model, each librarian invocation:
|
|
76
|
+
|
|
77
|
+
1. falls back to `ctx.model` when present;
|
|
78
|
+
2. emits a warning naming the configured model, the failure reason, and the current fallback model; and
|
|
79
|
+
3. runs with the requested librarian thinking level unchanged.
|
|
80
|
+
|
|
81
|
+
If no current model exists, the librarian tool retains its existing execution error. No warning is needed in the successful configured-model or unconfigured current-model paths.
|
|
82
|
+
|
|
83
|
+
## Design Decisions
|
|
84
|
+
|
|
85
|
+
### 1. Entries record state changes, not every command response
|
|
86
|
+
|
|
87
|
+
Only actual state changes belong in durable transcript history. Status queries and repeated explicit states do not change session state, so notifications remain the appropriate surface. Removing the transition notification avoids presenting the same event twice.
|
|
88
|
+
|
|
89
|
+
### 2. New entries snapshot the affected tools
|
|
90
|
+
|
|
91
|
+
Deriving names from the current `ATTACHABLE_TOOL_NAMES` would make old transcript entries change meaning after the toolset evolves. Storing the names adds minor duplication but preserves historical truth. Detach entries retain the list because removal has the same affected scope as attachment.
|
|
92
|
+
|
|
93
|
+
### 3. Entry rendering has no compatibility shape
|
|
94
|
+
|
|
95
|
+
Attach entries either match the complete current `{ attached, tools }` shape or render the generic unavailable-state line. Carrying a legacy branch would preserve data that cannot satisfy the historical-toolset requirement.
|
|
96
|
+
|
|
97
|
+
### 4. Expansion is additive
|
|
98
|
+
|
|
99
|
+
Expanded rendering keeps the compact line unchanged and adds details below it. This treats transcript expansion as an accordion rather than substituting one representation for another.
|
|
100
|
+
|
|
101
|
+
### 5. Pi owns model resolution
|
|
102
|
+
|
|
103
|
+
`resolveCliModel` owns provider normalization, fuzzy matching, priority, and custom model-ID fallback. Pi-librarian accepts its result directly. Authentication and provider failures belong to nested execution rather than a second model-selection policy.
|
|
104
|
+
|
|
105
|
+
### 6. Fallback warnings occur on every affected invocation
|
|
106
|
+
|
|
107
|
+
Repeated warnings are intentional. Every run that differs from configured intent should say so at the point of use; silently deduplicating could hide a continuing configuration problem from later research runs.
|
|
108
|
+
|
|
109
|
+
## Edge Cases & Failure Modes
|
|
110
|
+
|
|
111
|
+
- **Old or malformed attach entry:** does not alter restored state and renders a neutral unavailable-state line.
|
|
112
|
+
- **Toolset changes after an entry was written:** new rendering uses the entry's snapshot, preserving historical scope.
|
|
113
|
+
- **Repeated explicit state:** appends no entry and reports that tools are already attached or detached.
|
|
114
|
+
- **Unknown provider:** warns with Pi's resolution reason and falls back to the current model.
|
|
115
|
+
- **Resolved but unauthenticated model:** is selected; nested execution reports any resulting authentication failure.
|
|
116
|
+
- **Resolver-created custom model ID:** is selected and Pi's warning is shown.
|
|
117
|
+
- **No configured model:** uses the current model without warning.
|
|
118
|
+
- **No usable configured or current model:** librarian execution throws the existing no-model error.
|
|
119
|
+
|
|
120
|
+
## Alternatives
|
|
121
|
+
|
|
122
|
+
### Keep transient transition notifications alongside entries
|
|
123
|
+
|
|
124
|
+
- **Status:** Rejected
|
|
125
|
+
- **Decision:** It duplicates one event in two UI surfaces. The durable entry is sufficient feedback for an actual state change.
|
|
126
|
+
|
|
127
|
+
### Derive tool names while rendering
|
|
128
|
+
|
|
129
|
+
- **Status:** Rejected
|
|
130
|
+
- **Decision:** Historical entries would silently change when the attachable toolset changes.
|
|
131
|
+
|
|
132
|
+
### Version the entry immediately
|
|
133
|
+
|
|
134
|
+
- **Status:** Rejected
|
|
135
|
+
- **Decision:** There is one accepted shape and no migration behavior. A version adds ceremony without distinguishing supported semantics.
|
|
136
|
+
|
|
137
|
+
### Filter resolved models through `getAvailable()`
|
|
138
|
+
|
|
139
|
+
- **Status:** Rejected
|
|
140
|
+
- **Decision:** It diverges from Pi's native model-pattern behavior and blocks intentional custom or differently authenticated models. Nested execution owns operational model failures.
|
|
141
|
+
|
|
142
|
+
### Warn once at startup or once per session
|
|
143
|
+
|
|
144
|
+
- **Status:** Rejected
|
|
145
|
+
- **Decision:** Each resolver warning or fallback should remain explicit at its point of use.
|
|
146
|
+
|
|
147
|
+
## Implementation Plan
|
|
148
|
+
|
|
149
|
+
- [x] Phase 1: Raise the Pi dependency floor
|
|
150
|
+
- Goal: Make the new Pi APIs and thinking level part of pi-librarian's supported runtime contract.
|
|
151
|
+
- Files: `package.json`, `package-lock.json`, `extensions/librarian/settings.ts`, tests.
|
|
152
|
+
- Work: Raise relevant `@earendil-works/pi-*` development and peer dependency minimums to 0.80.6, refresh the npm lockfile, and accept `max` through a local runtime list checked against Pi's public `ThinkingLevel` type.
|
|
153
|
+
- Validation: Inspect root constraints and resolved package versions; include package validation in the full quality gate.
|
|
154
|
+
|
|
155
|
+
- [x] Phase 2: Render durable attach state
|
|
156
|
+
- Goal: Replace duplicate transition notifications with strict-shape transcript accordions.
|
|
157
|
+
- Files: `extensions/librarian/attach.ts`, `extensions/librarian.ts`, `test/attach.test.ts`.
|
|
158
|
+
- Work: Define the current entry shape; snapshot tool names from `ATTACHABLE_TOOL_NAMES`; register the entry renderer; preserve compact content while expanded; render every non-current shape generically; remove actual-transition notifications; make no-op wording symmetrical.
|
|
159
|
+
- Validation: Focused tests for state restoration, persisted snapshots, compact and expanded rendering, generic invalid-shape rendering, and active-tool mutation.
|
|
160
|
+
|
|
161
|
+
- [x] Phase 3: Adopt canonical model resolution
|
|
162
|
+
- Goal: Use `resolveCliModel` with Pi-native model-pattern and fallback behavior.
|
|
163
|
+
- Files: `extensions/librarian/model.ts`, `extensions/librarian.ts`, `test/model.test.ts`.
|
|
164
|
+
- Work: Resolve bare and provider-scoped configured patterns through Pi; accept returned models and warnings directly; return a specific warning when falling back to the current model; preserve no-model behavior.
|
|
165
|
+
- Validation: Focused tests for bare and provider-scoped fuzzy resolution, Pi model priority, custom-ID fallback, resolution errors, warning text, current fallback, missing fallback, and preservation of `max`.
|
|
166
|
+
|
|
167
|
+
- [x] Phase 4: Integration validation
|
|
168
|
+
- Goal: Prove the upgrade works as one coherent change.
|
|
169
|
+
- Files: implementation and test files above; design status only if implementation materially diverges.
|
|
170
|
+
- Work: Run focused tests, typecheck against the upgraded Pi API, and inspect the final staged/unstaged split without altering it.
|
|
171
|
+
- Validation: `npm test -- test/attach.test.ts test/model.test.ts test/settings.test.ts`; `npm run check`.
|
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type Component, Text } from "@earendil-works/pi-tui";
|
|
2
3
|
import { Type } from "typebox";
|
|
3
4
|
import { safeParseTypeBoxValue } from "../shared/typebox.ts";
|
|
4
5
|
import { ATTACHABLE_TOOL_NAMES } from "./tools/names.ts";
|
|
5
6
|
|
|
6
7
|
export const ATTACH_ENTRY_TYPE = "pi-librarian:attach";
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
const ATTACH_ENTRY_SCHEMA = Type.Object({
|
|
9
10
|
attached: Type.Boolean(),
|
|
11
|
+
tools: Type.Array(Type.String()),
|
|
10
12
|
});
|
|
11
13
|
|
|
14
|
+
export interface AttachEntryData {
|
|
15
|
+
attached: boolean;
|
|
16
|
+
tools: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
export function readAttachState(ctx: ExtensionContext): boolean {
|
|
13
20
|
let attached = false;
|
|
14
21
|
for (const entry of ctx.sessionManager.getEntries()) {
|
|
15
22
|
if (entry.type !== "custom" || entry.customType !== ATTACH_ENTRY_TYPE) {
|
|
16
23
|
continue;
|
|
17
24
|
}
|
|
18
|
-
const parsed = safeParseTypeBoxValue(
|
|
25
|
+
const parsed = safeParseTypeBoxValue(ATTACH_ENTRY_SCHEMA, entry.data);
|
|
19
26
|
if (parsed) {
|
|
20
27
|
attached = parsed.attached;
|
|
21
28
|
}
|
|
@@ -37,5 +44,25 @@ export function applyAttachState(pi: ExtensionAPI, attached: boolean): void {
|
|
|
37
44
|
|
|
38
45
|
export function setAttachState(pi: ExtensionAPI, attached: boolean): void {
|
|
39
46
|
applyAttachState(pi, attached);
|
|
40
|
-
pi.appendEntry(ATTACH_ENTRY_TYPE, {
|
|
47
|
+
pi.appendEntry<AttachEntryData>(ATTACH_ENTRY_TYPE, {
|
|
48
|
+
attached,
|
|
49
|
+
tools: [...ATTACHABLE_TOOL_NAMES],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function renderAttachEntry(data: unknown, expanded: boolean, theme: Theme): Component {
|
|
54
|
+
const entry = safeParseTypeBoxValue(ATTACH_ENTRY_SCHEMA, data);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
return new Text(theme.fg("warning", "Librarian attach state unavailable"), 0, 0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const state = entry.attached ? "attached" : "detached";
|
|
60
|
+
const color = entry.attached ? "success" : "muted";
|
|
61
|
+
let text = theme.fg(color, `Librarian tools ${state}`);
|
|
62
|
+
|
|
63
|
+
if (expanded) {
|
|
64
|
+
text += `\n${entry.tools.map((tool) => theme.fg("dim", ` ${tool}`)).join("\n")}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Text(text, 0, 0);
|
|
41
68
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
3
|
-
import type
|
|
3
|
+
import { type ExtensionContext, resolveCliModel } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import type { ModelReference } from "./settings.ts";
|
|
5
5
|
|
|
6
6
|
export type LibrarianModelSource = "configured" | "current";
|
|
@@ -9,31 +9,21 @@ export interface LibrarianModelResolution {
|
|
|
9
9
|
model: Model<Api>;
|
|
10
10
|
thinkingLevel: ThinkingLevel;
|
|
11
11
|
source: LibrarianModelSource;
|
|
12
|
+
warning?: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (exactMatches.length === 1) {
|
|
22
|
-
return exactMatches[0];
|
|
15
|
+
function configuredModelFailure(
|
|
16
|
+
configuredModel: ModelReference,
|
|
17
|
+
error: string | undefined,
|
|
18
|
+
warning: string | undefined,
|
|
19
|
+
): string {
|
|
20
|
+
if (error) {
|
|
21
|
+
return error;
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(model) =>
|
|
27
|
-
model.id.toLowerCase().includes(normalizedPattern) ||
|
|
28
|
-
model.name?.toLowerCase().includes(normalizedPattern),
|
|
29
|
-
);
|
|
30
|
-
if (partialMatches.length === 0) {
|
|
31
|
-
return undefined;
|
|
23
|
+
if (warning) {
|
|
24
|
+
return warning;
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
const aliases = partialMatches.filter((model) => isAlias(model.id));
|
|
35
|
-
const candidates = aliases.length > 0 ? aliases : partialMatches;
|
|
36
|
-
return candidates.toSorted((a, b) => b.id.localeCompare(a.id))[0];
|
|
26
|
+
return `Could not resolve configured model "${configuredModel}".`;
|
|
37
27
|
}
|
|
38
28
|
|
|
39
29
|
export function resolveLibrarianModel(
|
|
@@ -41,19 +31,41 @@ export function resolveLibrarianModel(
|
|
|
41
31
|
configuredModel: ModelReference | undefined,
|
|
42
32
|
thinkingLevel: ThinkingLevel,
|
|
43
33
|
): LibrarianModelResolution | undefined {
|
|
34
|
+
let failure: string | undefined;
|
|
35
|
+
|
|
44
36
|
if (configuredModel) {
|
|
45
|
-
const
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
const resolved = resolveCliModel({
|
|
38
|
+
...(configuredModel.provider ? { cliProvider: configuredModel.provider } : {}),
|
|
39
|
+
cliModel: configuredModel.modelId,
|
|
40
|
+
modelRegistry: ctx.modelRegistry,
|
|
41
|
+
});
|
|
42
|
+
if (resolved.model) {
|
|
43
|
+
const resolution: LibrarianModelResolution = {
|
|
44
|
+
model: resolved.model,
|
|
45
|
+
thinkingLevel,
|
|
46
|
+
source: "configured",
|
|
47
|
+
};
|
|
48
|
+
if (resolved.warning) {
|
|
49
|
+
resolution.warning = resolved.warning;
|
|
50
|
+
}
|
|
51
|
+
return resolution;
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
failure = configuredModelFailure(configuredModel, resolved.error, resolved.warning);
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
if (!ctx.model) {
|
|
55
58
|
return undefined;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
if (!configuredModel) {
|
|
62
|
+
return { model: ctx.model, thinkingLevel, source: "current" };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
model: ctx.model,
|
|
67
|
+
thinkingLevel,
|
|
68
|
+
source: "current",
|
|
69
|
+
warning: `Configured librarian model "${configuredModel}" is unavailable: ${failure} Using current model "${ctx.model.provider}/${ctx.model.id}".`,
|
|
70
|
+
};
|
|
59
71
|
}
|
|
@@ -5,13 +5,24 @@ import { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import { type Static, Type } from "typebox";
|
|
6
6
|
import { parseTypeBoxValue } from "../shared/typebox.ts";
|
|
7
7
|
|
|
8
|
+
const THINKING_LEVELS = [
|
|
9
|
+
"off",
|
|
10
|
+
"minimal",
|
|
11
|
+
"low",
|
|
12
|
+
"medium",
|
|
13
|
+
"high",
|
|
14
|
+
"xhigh",
|
|
15
|
+
"max",
|
|
16
|
+
] as const satisfies readonly ThinkingLevel[];
|
|
17
|
+
|
|
8
18
|
const THINKING_LEVEL_SCHEMA = Type.Union([
|
|
9
|
-
Type.Literal(
|
|
10
|
-
Type.Literal(
|
|
11
|
-
Type.Literal(
|
|
12
|
-
Type.Literal(
|
|
13
|
-
Type.Literal(
|
|
14
|
-
Type.Literal(
|
|
19
|
+
Type.Literal(THINKING_LEVELS[0]),
|
|
20
|
+
Type.Literal(THINKING_LEVELS[1]),
|
|
21
|
+
Type.Literal(THINKING_LEVELS[2]),
|
|
22
|
+
Type.Literal(THINKING_LEVELS[3]),
|
|
23
|
+
Type.Literal(THINKING_LEVELS[4]),
|
|
24
|
+
Type.Literal(THINKING_LEVELS[5]),
|
|
25
|
+
Type.Literal(THINKING_LEVELS[6]),
|
|
15
26
|
]);
|
|
16
27
|
|
|
17
28
|
const LIBRARIAN_FILE_SETTINGS_SCHEMA = Type.Object({
|
|
@@ -35,12 +46,12 @@ type LibrarianFileSettings = Static<typeof LIBRARIAN_FILE_SETTINGS_SCHEMA>;
|
|
|
35
46
|
|
|
36
47
|
export class ModelReference {
|
|
37
48
|
constructor(
|
|
38
|
-
readonly provider: string,
|
|
49
|
+
readonly provider: string | undefined,
|
|
39
50
|
readonly modelId: string,
|
|
40
51
|
) {}
|
|
41
52
|
|
|
42
53
|
toString(): string {
|
|
43
|
-
return `${this.provider}/${this.modelId}
|
|
54
|
+
return this.provider ? `${this.provider}/${this.modelId}` : this.modelId;
|
|
44
55
|
}
|
|
45
56
|
}
|
|
46
57
|
|
|
@@ -92,7 +103,10 @@ function parseModelReference(value: string | undefined): ModelReference | undefi
|
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
const slashIndex = trimmed.indexOf("/");
|
|
95
|
-
if (slashIndex
|
|
106
|
+
if (slashIndex === -1) {
|
|
107
|
+
return new ModelReference(undefined, trimmed);
|
|
108
|
+
}
|
|
109
|
+
if (slashIndex === 0 || slashIndex === trimmed.length - 1) {
|
|
96
110
|
return undefined;
|
|
97
111
|
}
|
|
98
112
|
|
package/extensions/librarian.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { ExtensionAPI, SessionStartEvent } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ATTACH_ENTRY_TYPE,
|
|
6
|
+
type AttachEntryData,
|
|
7
|
+
applyAttachState,
|
|
8
|
+
readAttachState,
|
|
9
|
+
renderAttachEntry,
|
|
10
|
+
setAttachState,
|
|
11
|
+
} from "./librarian/attach.ts";
|
|
5
12
|
import { collectExtraToolWarnings, resolveExtraTools } from "./librarian/extra-tools.ts";
|
|
6
13
|
import { createGitHubClientProvider } from "./librarian/github.ts";
|
|
7
14
|
import { resolveLibrarianModel } from "./librarian/model.ts";
|
|
@@ -50,6 +57,10 @@ export default function librarianExtension(pi: ExtensionAPI): void {
|
|
|
50
57
|
const settings = loadSettings();
|
|
51
58
|
const githubClient = createGitHubClientProvider();
|
|
52
59
|
|
|
60
|
+
pi.registerEntryRenderer<AttachEntryData>(ATTACH_ENTRY_TYPE, (entry, { expanded }, theme) =>
|
|
61
|
+
renderAttachEntry(entry.data, expanded, theme),
|
|
62
|
+
);
|
|
63
|
+
|
|
53
64
|
const attachableTools = [
|
|
54
65
|
createSearchReposTool(githubClient),
|
|
55
66
|
searchCodeTool,
|
|
@@ -94,6 +105,9 @@ export default function librarianExtension(pi: ExtensionAPI): void {
|
|
|
94
105
|
"No model available for the librarian. Configure librarian.model or select a session model.",
|
|
95
106
|
);
|
|
96
107
|
}
|
|
108
|
+
if (resolution.warning) {
|
|
109
|
+
ctx.ui.notify(resolution.warning, "warning");
|
|
110
|
+
}
|
|
97
111
|
|
|
98
112
|
const extraTools = resolveExtraTools(pi.getAllTools(), settings);
|
|
99
113
|
|
|
@@ -167,19 +181,13 @@ export default function librarianExtension(pi: ExtensionAPI): void {
|
|
|
167
181
|
argument === "on" ? true : argument === "off" ? false : !currentlyAttached;
|
|
168
182
|
if (nextAttached === currentlyAttached) {
|
|
169
183
|
ctx.ui.notify(
|
|
170
|
-
nextAttached ? "Librarian tools already attached." : "Librarian tools
|
|
184
|
+
nextAttached ? "Librarian tools already attached." : "Librarian tools already detached.",
|
|
171
185
|
"info",
|
|
172
186
|
);
|
|
173
187
|
return;
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
setAttachState(pi, nextAttached);
|
|
177
|
-
ctx.ui.notify(
|
|
178
|
-
nextAttached
|
|
179
|
-
? `Attached librarian tools: ${ATTACHABLE_TOOL_NAMES.join(", ")}`
|
|
180
|
-
: "Detached librarian tools.",
|
|
181
|
-
"info",
|
|
182
|
-
);
|
|
183
191
|
},
|
|
184
192
|
});
|
|
185
193
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thurstonsand/pi-librarian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "GitHub research subagent for pi: deep-dive specific repos, discover across the ecosystem",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@biomejs/biome": "^2.4.14",
|
|
46
|
-
"@earendil-works/pi-agent-core": "^0.80.
|
|
47
|
-
"@earendil-works/pi-ai": "^0.80.
|
|
48
|
-
"@earendil-works/pi-coding-agent": "^0.80.
|
|
49
|
-
"@earendil-works/pi-tui": "^0.80.
|
|
46
|
+
"@earendil-works/pi-agent-core": "^0.80.6",
|
|
47
|
+
"@earendil-works/pi-ai": "^0.80.6",
|
|
48
|
+
"@earendil-works/pi-coding-agent": "^0.80.6",
|
|
49
|
+
"@earendil-works/pi-tui": "^0.80.6",
|
|
50
50
|
"@types/node": "^25.6.2",
|
|
51
51
|
"husky": "^9.1.7",
|
|
52
52
|
"lint-staged": "^17.0.3",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"vitest": "^4.1.5"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@earendil-works/pi-agent-core": ">=0.80.
|
|
59
|
-
"@earendil-works/pi-ai": ">=0.80.
|
|
60
|
-
"@earendil-works/pi-coding-agent": ">=0.80.
|
|
61
|
-
"@earendil-works/pi-tui": ">=0.80.
|
|
58
|
+
"@earendil-works/pi-agent-core": ">=0.80.6",
|
|
59
|
+
"@earendil-works/pi-ai": ">=0.80.6",
|
|
60
|
+
"@earendil-works/pi-coding-agent": ">=0.80.6",
|
|
61
|
+
"@earendil-works/pi-tui": ">=0.80.6",
|
|
62
62
|
"typebox": ">=1.1.24"
|
|
63
63
|
},
|
|
64
64
|
"lint-staged": {
|