@shanepadgett/tau-agent 0.44.1 → 0.45.1
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/docs/extending-tau-agent.md +3 -3
- package/extensions/appshot/index.ts +3 -0
- package/extensions/aside/index.ts +3 -11
- package/extensions/attention/README.md +2 -4
- package/extensions/attention/index.ts +2 -40
- package/extensions/auto-name/index.ts +5 -8
- package/extensions/cache-diagnostics/index.ts +1 -1
- package/extensions/codex-priority/README.md +7 -0
- package/extensions/codex-priority/index.ts +95 -0
- package/extensions/compaction/README.md +5 -0
- package/extensions/compaction/index.ts +47 -0
- package/extensions/cost-report/README.md +1 -1
- package/extensions/cost-report/analyze.ts +11 -60
- package/extensions/cost-report/html.ts +1 -37
- package/extensions/cost-report/types.ts +0 -9
- package/extensions/handoff/index.ts +1 -1
- package/extensions/image-gen/index.ts +1 -0
- package/extensions/review/README.md +1 -1
- package/extensions/run-summary/README.md +1 -1
- package/extensions/run-summary/index.ts +6 -24
- package/extensions/runtime-context/README.md +1 -1
- package/extensions/silent-command-runner/index.ts +38 -69
- package/extensions/soul/README.md +3 -5
- package/extensions/soul/index.ts +59 -135
- package/extensions/soul/prompt.ts +2 -0
- package/extensions/tau-help/help.md +10 -2
- package/extensions/tool-approval/index.ts +8 -28
- package/extensions/tool-loader/README.md +5 -5
- package/extensions/tool-loader/index.ts +10 -222
- package/extensions/web/codesearch.ts +1 -0
- package/extensions/web/webfetch.ts +1 -0
- package/extensions/web/websearch.ts +1 -0
- package/package.json +2 -2
- package/shared/events.ts +9 -22
- package/shared/model-effort.ts +12 -8
- package/shared/model-fallback/index.ts +12 -29
- package/shared/model-fallback/types.ts +1 -5
- package/shared/prompt-contributions.ts +0 -2
- package/src/tool-loading/index.ts +5 -42
- package/extensions/soul/context.ts +0 -115
- package/extensions/soul/state.ts +0 -114
- package/extensions/soul/tools.ts +0 -31
|
@@ -1,228 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type Static, Type } from "typebox";
|
|
3
|
-
import { registeredDeferredToolGroups, type DeferredToolGroupInfo } from "../../src/tool-loading/index.ts";
|
|
4
|
-
import { emitTauEvent } from "../../shared/events.ts";
|
|
5
|
-
import { BoundedTextResultBuilder } from "../../shared/bounded-text-result.ts";
|
|
6
|
-
import { createTemporaryOutputStore } from "../../shared/temporary-output-store.ts";
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
2
|
|
|
8
|
-
const
|
|
9
|
-
{
|
|
10
|
-
capability: Type.String({
|
|
11
|
-
minLength: 1,
|
|
12
|
-
description: "Registered specialist group ID, such as web, image, appshot, or a package-provided group",
|
|
13
|
-
}),
|
|
14
|
-
},
|
|
15
|
-
{ additionalProperties: false },
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
type LoadToolsParams = Static<typeof loadToolsSchema>;
|
|
19
|
-
|
|
20
|
-
interface LoadToolsDetails {
|
|
21
|
-
version: 1;
|
|
22
|
-
capability: string;
|
|
23
|
-
requestedToolNames: string[];
|
|
24
|
-
addedToolNames: string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const PENDING_TYPE = "tau.tool-loader.pending";
|
|
28
|
-
const APPLIED_TYPE = "tau.tool-loader.applied";
|
|
29
|
-
|
|
30
|
-
interface AppliedLoad {
|
|
31
|
-
pendingId: string;
|
|
32
|
-
names: string[];
|
|
33
|
-
}
|
|
3
|
+
const TOOL_SEARCH = "tool_search";
|
|
34
4
|
|
|
35
5
|
export default function toolLoaderExtension(pi: ExtensionAPI): void {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
44
|
-
temporaryOutput = createTemporaryOutputStore();
|
|
45
|
-
await temporaryOutput.start();
|
|
46
|
-
const groups = registeredDeferredToolGroups(pi);
|
|
47
|
-
pi.registerTool(createLoadToolsTool(pi, groups));
|
|
48
|
-
|
|
49
|
-
const initial = pi.getActiveTools();
|
|
50
|
-
const initialSet = new Set(initial);
|
|
51
|
-
allowedToolNames = new Map(
|
|
52
|
-
groups.map((group) => [group.id, new Set(group.toolNames.filter((name) => initialSet.has(name)))]),
|
|
53
|
-
);
|
|
54
|
-
managedToolNames = new Set(groups.flatMap((group) => group.toolNames));
|
|
55
|
-
managed = initialSet.has("load_tools") && groups.length > 0;
|
|
56
|
-
if (managed) restoreActiveTools(pi, initial, loadedCapabilities(ctx.sessionManager.getBranch(), groups), groups);
|
|
57
|
-
});
|
|
58
|
-
pi.on("session_shutdown", () => temporaryOutput.shutdown());
|
|
59
|
-
|
|
60
|
-
pi.on("session_tree", (_event, ctx) => {
|
|
61
|
-
if (managed) {
|
|
62
|
-
restoreActiveTools(
|
|
63
|
-
pi,
|
|
64
|
-
pi.getActiveTools(),
|
|
65
|
-
loadedCapabilities(ctx.sessionManager.getBranch(), registeredDeferredToolGroups(pi)),
|
|
66
|
-
registeredDeferredToolGroups(pi),
|
|
67
|
-
);
|
|
6
|
+
pi.on("session_start", (_event, ctx) => {
|
|
7
|
+
const tools = pi.getAllTools();
|
|
8
|
+
if (!tools.some((tool) => tool.exposure === "deferred")) return;
|
|
9
|
+
if (!tools.some((tool) => tool.name === TOOL_SEARCH)) {
|
|
10
|
+
ctx.ui.notify("Deferred tool groups need Pi's built-in tool_search extension, which is disabled.", "warning");
|
|
11
|
+
return;
|
|
68
12
|
}
|
|
13
|
+
const active = pi.getActiveTools();
|
|
14
|
+
if (!active.includes(TOOL_SEARCH)) pi.setActiveTools([...active, TOOL_SEARCH]);
|
|
69
15
|
});
|
|
70
|
-
|
|
71
|
-
pi.on("session_compact", (_event, ctx) => {
|
|
72
|
-
const branch = ctx.sessionManager.getBranch();
|
|
73
|
-
const applied = new Set(
|
|
74
|
-
branch.flatMap((entry) =>
|
|
75
|
-
entry.type === "custom" && entry.customType === APPLIED_TYPE ? [(entry.data as AppliedLoad).pendingId] : [],
|
|
76
|
-
),
|
|
77
|
-
);
|
|
78
|
-
for (const entry of branch) {
|
|
79
|
-
if (
|
|
80
|
-
entry.type !== "custom" ||
|
|
81
|
-
entry.customType !== PENDING_TYPE ||
|
|
82
|
-
applied.has(entry.id) ||
|
|
83
|
-
!Array.isArray(entry.data)
|
|
84
|
-
)
|
|
85
|
-
continue;
|
|
86
|
-
const names = entry.data.filter((name): name is string => typeof name === "string");
|
|
87
|
-
const allowed = new Set([...allowedToolNames.values()].flatMap((group) => [...group]));
|
|
88
|
-
pi.setActiveTools([...new Set([...pi.getActiveTools(), ...names.filter((name) => allowed.has(name))])]);
|
|
89
|
-
pi.appendEntry(APPLIED_TYPE, { pendingId: entry.id, names });
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
function createLoadToolsTool(pi: ExtensionAPI, groups: readonly DeferredToolGroupInfo[]) {
|
|
94
|
-
return defineTool<typeof loadToolsSchema, LoadToolsDetails>({
|
|
95
|
-
name: "load_tools",
|
|
96
|
-
label: "Load Tools",
|
|
97
|
-
description: `Load one registered Tau specialist tool group for the current session.${formatGroupCatalog(groups)}`,
|
|
98
|
-
promptSnippet: "Load a registered specialist tool group when the current tools cannot perform the task",
|
|
99
|
-
promptGuidelines: [
|
|
100
|
-
"Use load_tools before attempting a registered specialist capability whose tools are not currently available.",
|
|
101
|
-
],
|
|
102
|
-
parameters: loadToolsSchema,
|
|
103
|
-
async execute(_toolCallId, params: LoadToolsParams, signal, _onUpdate, ctx) {
|
|
104
|
-
const group = registeredDeferredToolGroups(pi).find((candidate) => candidate.id === params.capability);
|
|
105
|
-
if (group === undefined) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Unknown specialist tool group: ${params.capability}.${formatGroupCatalog(registeredDeferredToolGroups(pi))}`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const before = pi.getActiveTools();
|
|
112
|
-
const requested = [...group.toolNames];
|
|
113
|
-
const registered = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
114
|
-
const allowed = allowedToolNames.get(group.id) ?? new Set<string>();
|
|
115
|
-
const loadable = requested.filter((name) => registered.has(name) && allowed.has(name));
|
|
116
|
-
if (loadable.length === 0) {
|
|
117
|
-
throw new Error(`No ${params.capability} tools are available in this session's tool configuration.`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const beforeSet = new Set(before);
|
|
121
|
-
const next = [...before, ...loadable.filter((name) => !beforeSet.has(name))];
|
|
122
|
-
const nextSet = new Set(next);
|
|
123
|
-
let blocked: string | null = null;
|
|
124
|
-
emitTauEvent(pi, "tau:prompt.tools.check", {
|
|
125
|
-
ctx,
|
|
126
|
-
tools: pi.getAllTools().filter((tool) => nextSet.has(tool.name)),
|
|
127
|
-
reject(reason) {
|
|
128
|
-
blocked = reason;
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
if (blocked) {
|
|
132
|
-
pi.appendEntry(PENDING_TYPE, loadable);
|
|
133
|
-
return boundedResult(
|
|
134
|
-
`${blocked} ${params.capability} is queued for activation after successful compaction.`,
|
|
135
|
-
{ version: 1, capability: params.capability, requestedToolNames: requested, addedToolNames: [] },
|
|
136
|
-
signal,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
pi.setActiveTools(next);
|
|
140
|
-
const after = pi.getActiveTools();
|
|
141
|
-
const addedToolNames = requested.filter((name) => !beforeSet.has(name) && after.includes(name));
|
|
142
|
-
const available = requested.filter((name) => after.includes(name));
|
|
143
|
-
const unavailable = requested.filter((name) => !after.includes(name));
|
|
144
|
-
const text =
|
|
145
|
-
addedToolNames.length > 0
|
|
146
|
-
? `Loaded ${params.capability} tools: ${addedToolNames.join(", ")}.`
|
|
147
|
-
: `${params.capability} tools are already loaded: ${available.join(", ")}.`;
|
|
148
|
-
|
|
149
|
-
return boundedResult(
|
|
150
|
-
unavailable.length ? `${text} Unavailable: ${unavailable.join(", ")}.` : text,
|
|
151
|
-
{
|
|
152
|
-
version: 1,
|
|
153
|
-
capability: params.capability,
|
|
154
|
-
requestedToolNames: requested,
|
|
155
|
-
addedToolNames,
|
|
156
|
-
},
|
|
157
|
-
signal,
|
|
158
|
-
);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function boundedResult(text: string, details: LoadToolsDetails, signal: AbortSignal | undefined) {
|
|
164
|
-
const builder = new BoundedTextResultBuilder(temporaryOutput, "head");
|
|
165
|
-
try {
|
|
166
|
-
await builder.append(text);
|
|
167
|
-
signal?.throwIfAborted();
|
|
168
|
-
const result = await builder.finish();
|
|
169
|
-
return {
|
|
170
|
-
content: [{ type: "text" as const, text: result.content }],
|
|
171
|
-
details: { ...details, overflow: result.overflow },
|
|
172
|
-
};
|
|
173
|
-
} catch (error) {
|
|
174
|
-
await builder.abort();
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function restoreActiveTools(
|
|
180
|
-
pi: ExtensionAPI,
|
|
181
|
-
current: readonly string[],
|
|
182
|
-
loaded: ReadonlySet<string>,
|
|
183
|
-
groups: readonly DeferredToolGroupInfo[],
|
|
184
|
-
): void {
|
|
185
|
-
const next = current.filter((name) => !managedToolNames.has(name));
|
|
186
|
-
for (const group of groups) {
|
|
187
|
-
if (!loaded.has(group.id)) continue;
|
|
188
|
-
const allowed = allowedToolNames.get(group.id) ?? new Set<string>();
|
|
189
|
-
next.push(...group.toolNames.filter((name) => allowed.has(name)));
|
|
190
|
-
}
|
|
191
|
-
pi.setActiveTools([...new Set(next)]);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function formatGroupCatalog(groups: readonly DeferredToolGroupInfo[]): string {
|
|
196
|
-
if (groups.length === 0) return " No specialist groups are registered.";
|
|
197
|
-
const catalog = groups.map((group) => `${group.id}: ${group.description}`).join("; ");
|
|
198
|
-
return ` Registered groups: ${catalog}.`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function loadedCapabilities(entries: readonly SessionEntry[], groups: readonly DeferredToolGroupInfo[]): Set<string> {
|
|
202
|
-
const loaded = new Set<string>();
|
|
203
|
-
for (const entry of entries) {
|
|
204
|
-
if (entry.type === "custom" && entry.customType === APPLIED_TYPE) {
|
|
205
|
-
const { names } = entry.data as AppliedLoad;
|
|
206
|
-
for (const group of groups) if (group.toolNames.some((name) => names.includes(name))) loaded.add(group.id);
|
|
207
|
-
}
|
|
208
|
-
if (entry.type !== "message") continue;
|
|
209
|
-
const message = entry.message;
|
|
210
|
-
if (message.role !== "toolResult" || message.toolName !== "load_tools" || message.isError === true) continue;
|
|
211
|
-
if (!isLoadToolsDetails(message.details)) continue;
|
|
212
|
-
if (message.details.addedToolNames.length > 0) loaded.add(message.details.capability);
|
|
213
|
-
}
|
|
214
|
-
return loaded;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function isLoadToolsDetails(value: unknown): value is LoadToolsDetails {
|
|
218
|
-
if (!value || typeof value !== "object") return false;
|
|
219
|
-
const details = value as Record<string, unknown>;
|
|
220
|
-
return (
|
|
221
|
-
details.version === 1 &&
|
|
222
|
-
typeof details.capability === "string" &&
|
|
223
|
-
Array.isArray(details.requestedToolNames) &&
|
|
224
|
-
details.requestedToolNames.every((name) => typeof name === "string") &&
|
|
225
|
-
Array.isArray(details.addedToolNames) &&
|
|
226
|
-
details.addedToolNames.every((name) => typeof name === "string")
|
|
227
|
-
);
|
|
228
16
|
}
|
|
@@ -26,6 +26,7 @@ export function createCodeSearchTool(rowState: ToolRowStateStore) {
|
|
|
26
26
|
return defineTool<typeof codeSearchParams, CodeSearchDetails | undefined>({
|
|
27
27
|
name: "codesearch",
|
|
28
28
|
label: "Code Search",
|
|
29
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
29
30
|
description:
|
|
30
31
|
"Search Exa for API usage, code examples, and implementation-oriented documentation context. Use websearch for broad discovery and webfetch for a known URL. Use a separate research workflow when several searches, fetches, and synthesis are needed. Output is truncated to 2,000 lines or 50 KB.",
|
|
31
32
|
parameters: codeSearchParams,
|
|
@@ -177,6 +177,7 @@ export function createWebFetchTool(rowState: ToolRowStateStore) {
|
|
|
177
177
|
return defineTool<typeof webFetchParams, WebFetchDetails | undefined>({
|
|
178
178
|
name: "webfetch",
|
|
179
179
|
label: "Web Fetch",
|
|
180
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
180
181
|
description:
|
|
181
182
|
"Fetch a known HTTP(S) URL as Markdown, text, or HTML. Use webfetch when you already have a URL; use websearch for broad discovery and codesearch for implementation-oriented lookups. Use a separate research workflow when several searches, fetches, and synthesis are needed. Supports inline images, limits response bodies to 5 MB, and truncates text to 2,000 lines or 50 KB.",
|
|
182
183
|
parameters: webFetchParams,
|
|
@@ -35,6 +35,7 @@ export function createWebSearchTool(rowState: ToolRowStateStore) {
|
|
|
35
35
|
return defineTool<typeof webSearchParams, WebSearchDetails | undefined>({
|
|
36
36
|
name: "websearch",
|
|
37
37
|
label: "Web Search",
|
|
38
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
38
39
|
description:
|
|
39
40
|
"Search the public web through Exa for current information and relevant pages. Use websearch for broad discovery, then webfetch for a known URL; use codesearch for implementation-oriented code and documentation context. Use a separate research workflow when several searches, fetches, and synthesis are needed. Output is truncated to 2,000 lines or 50 KB.",
|
|
40
41
|
parameters: webSearchParams,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"description": "Tau is a custom agentic harness built with pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@ast-grep/wasm": "0.45.3",
|
|
38
|
-
"@shanepadgett/tau-tui": "0.
|
|
38
|
+
"@shanepadgett/tau-tui": "0.45.1",
|
|
39
39
|
"@vscode/tree-sitter-wasm": "0.3.1",
|
|
40
40
|
"image-size": "2.0.4",
|
|
41
41
|
"smol-toml": "1.8.0",
|
package/shared/events.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { Tool } from "@earendil-works/pi-ai";
|
|
3
2
|
import type { ToolRowVisualState } from "./tool-row-state.js";
|
|
4
3
|
import type { ScriptSourceStore } from "./script-source.ts";
|
|
5
4
|
import type { FileInjectionRequest, PreparedFileInjection } from "../src/file-injection/index.ts";
|
|
6
5
|
|
|
7
6
|
export type TauAgentEvents = {
|
|
8
|
-
/** @internal Soul's runtime-scoped prompt contributors. */
|
|
7
|
+
/** @internal Soul's runtime-scoped prompt contributors. Each source becomes one named Pi prompt section. */
|
|
9
8
|
"tau:prompt.sources": {
|
|
10
9
|
accept(source: {
|
|
11
10
|
key: string;
|
|
@@ -14,26 +13,13 @@ export type TauAgentEvents = {
|
|
|
14
13
|
read(ctx: ExtensionContext): Promise<string>;
|
|
15
14
|
}): void;
|
|
16
15
|
};
|
|
17
|
-
/** @internal
|
|
18
|
-
"tau:prompt.tools.check": {
|
|
19
|
-
ctx: ExtensionContext;
|
|
20
|
-
tools: Tool[];
|
|
21
|
-
reject(reason: string): void;
|
|
22
|
-
};
|
|
23
|
-
/** @internal Exact projected Soul instructions, for prompt viewers. */
|
|
16
|
+
/** @internal Soul's complete instructions for the current turn, for prompt viewers. */
|
|
24
17
|
"tau:prompt.snapshot": { text: string };
|
|
25
18
|
"tau:agent.blocked": {
|
|
26
19
|
title?: string;
|
|
27
20
|
body?: string;
|
|
28
21
|
source?: string;
|
|
29
22
|
};
|
|
30
|
-
"tau:attention.hold.acquire": {
|
|
31
|
-
id: string;
|
|
32
|
-
};
|
|
33
|
-
"tau:attention.hold.release": {
|
|
34
|
-
id: string;
|
|
35
|
-
disposition: "notify" | "discard";
|
|
36
|
-
};
|
|
37
23
|
"tau:file-mutation.applied": {
|
|
38
24
|
source: "patch";
|
|
39
25
|
toolCallId: string;
|
|
@@ -49,10 +35,6 @@ export type TauAgentEvents = {
|
|
|
49
35
|
snapshotRanges?: Array<{ startLine: number; endLine: number }>;
|
|
50
36
|
}>;
|
|
51
37
|
};
|
|
52
|
-
/** @internal Runtime-scoped discovery for deferred tool groups. */
|
|
53
|
-
"tau:deferred-tool-group.request": {
|
|
54
|
-
accept(group: { id: string; description: string; toolNames: readonly string[] }): void;
|
|
55
|
-
};
|
|
56
38
|
/** @internal Runtime-scoped request for Explore-owned file preparation. */
|
|
57
39
|
"tau:file-injection.prepare": {
|
|
58
40
|
request: FileInjectionRequest;
|
|
@@ -103,10 +85,15 @@ interface TauEventSubscription {
|
|
|
103
85
|
stop(): void;
|
|
104
86
|
}
|
|
105
87
|
|
|
106
|
-
|
|
88
|
+
type TauEventSubscriptionRegistry = WeakMap<
|
|
107
89
|
ExtensionAPI["events"],
|
|
108
90
|
Map<string, Map<keyof TauAgentEvents, TauEventSubscription>>
|
|
109
|
-
|
|
91
|
+
>;
|
|
92
|
+
|
|
93
|
+
// Global and project installs load separate module instances but share one pi.events bus.
|
|
94
|
+
const registryKey = Symbol.for("tau-agent.eventSubscriptions");
|
|
95
|
+
const registryHost = globalThis as typeof globalThis & { [registryKey]?: TauEventSubscriptionRegistry };
|
|
96
|
+
const tauEventSubscriptions: TauEventSubscriptionRegistry = (registryHost[registryKey] ??= new WeakMap());
|
|
110
97
|
|
|
111
98
|
export function emitTauEvent<Name extends keyof TauAgentEvents>(
|
|
112
99
|
pi: EmitEventAPI,
|
package/shared/model-effort.ts
CHANGED
|
@@ -31,27 +31,31 @@ export interface EffortCandidateOptions {
|
|
|
31
31
|
|
|
32
32
|
const MODEL_PREFERENCES: Record<ModelEffort, readonly ProviderPreference[]> = {
|
|
33
33
|
quick: [
|
|
34
|
+
{ provider: "openai-codex", models: [{ model: "gpt-6-luna", reasoning: "medium" }] },
|
|
35
|
+
{ provider: "openai", models: [{ model: "gpt-6-luna", reasoning: "medium" }] },
|
|
36
|
+
{ provider: "xai", models: [{ model: "grok-4.7", reasoning: "medium" }] },
|
|
37
|
+
{ provider: "anthropic", models: [{ model: "claude-sonnet-5-5", reasoning: "medium" }] },
|
|
34
38
|
{
|
|
35
|
-
provider: "
|
|
39
|
+
provider: "opencode-go",
|
|
36
40
|
models: [
|
|
37
|
-
{ model: "
|
|
38
|
-
{ model: "
|
|
41
|
+
{ model: "deepseek-v4.1-flash", reasoning: "high" },
|
|
42
|
+
{ model: "glm-5.3-flash", reasoning: "high" },
|
|
39
43
|
],
|
|
40
44
|
},
|
|
41
|
-
{ provider: "
|
|
42
|
-
{ provider: "anthropic", models: [{ model: "claude-sonnet-5", reasoning: "medium" }] },
|
|
45
|
+
{ provider: "openrouter", models: [{ model: "deepseek/deepseek-v4.1-flash", reasoning: "high" }] },
|
|
43
46
|
],
|
|
44
47
|
standard: [
|
|
45
48
|
{
|
|
46
49
|
provider: "openai-codex",
|
|
47
50
|
models: [
|
|
51
|
+
{ model: "gpt-6.1-sol", reasoning: "high" },
|
|
48
52
|
{ model: "gpt-6-sol", reasoning: "high" },
|
|
49
53
|
{ model: "gpt-6-luna", reasoning: "max" },
|
|
50
54
|
{ model: "gpt-5.6-luna", reasoning: "max" },
|
|
51
55
|
],
|
|
52
56
|
},
|
|
53
57
|
{ provider: "xai", models: [{ model: "grok-4.7", reasoning: "high" }] },
|
|
54
|
-
{ provider: "anthropic", models: [{ model: "claude-sonnet-5", reasoning: "high" }] },
|
|
58
|
+
{ provider: "anthropic", models: [{ model: "claude-sonnet-5-5", reasoning: "high" }] },
|
|
55
59
|
],
|
|
56
60
|
deep: [
|
|
57
61
|
{
|
|
@@ -61,8 +65,8 @@ const MODEL_PREFERENCES: Record<ModelEffort, readonly ProviderPreference[]> = {
|
|
|
61
65
|
{
|
|
62
66
|
provider: "anthropic",
|
|
63
67
|
models: [
|
|
64
|
-
{ model: "claude-opus-5
|
|
65
|
-
{ model: "claude-fable-5
|
|
68
|
+
{ model: "claude-opus-5-5", reasoning: "medium" },
|
|
69
|
+
{ model: "claude-fable-5-1", reasoning: "low" },
|
|
66
70
|
],
|
|
67
71
|
},
|
|
68
72
|
],
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import {
|
|
3
|
-
normalizeContext,
|
|
4
3
|
type Api,
|
|
5
4
|
type AssistantMessage,
|
|
6
5
|
type Message,
|
|
@@ -20,6 +19,7 @@ const SEVEN_DAYS_MS = 604_800_000;
|
|
|
20
19
|
|
|
21
20
|
interface GenerationContext {
|
|
22
21
|
ui: ExtensionContext["ui"];
|
|
22
|
+
modelRegistry: ExtensionContext["modelRegistry"];
|
|
23
23
|
signal: AbortSignal | undefined;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -40,32 +40,22 @@ export async function resolveCandidates(
|
|
|
40
40
|
const seen = new Set<string>();
|
|
41
41
|
const blocked = currentBlockedProviders(settings.cooldowns ?? {});
|
|
42
42
|
|
|
43
|
-
const add =
|
|
43
|
+
const add = (model: Model<Api>, reasoning: ThinkingLevel | undefined): void => {
|
|
44
44
|
if (blocked.has(model.provider)) return;
|
|
45
45
|
const key = `${model.provider}/${model.id}`;
|
|
46
46
|
if (seen.has(key)) return;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
if (!provider) return;
|
|
50
|
-
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
51
|
-
if (!auth.ok) return;
|
|
48
|
+
if (!ctx.modelRegistry.hasConfiguredAuth(model)) return;
|
|
52
49
|
|
|
53
50
|
seen.add(key);
|
|
54
|
-
candidates.push({
|
|
55
|
-
model,
|
|
56
|
-
provider,
|
|
57
|
-
apiKey: auth.apiKey,
|
|
58
|
-
headers: auth.headers,
|
|
59
|
-
env: auth.env,
|
|
60
|
-
reasoning,
|
|
61
|
-
});
|
|
51
|
+
candidates.push({ model, reasoning });
|
|
62
52
|
};
|
|
63
53
|
|
|
64
54
|
for (const preferred of preferredModels) {
|
|
65
55
|
const model = ctx.modelRegistry.find(preferred.provider, preferred.model);
|
|
66
|
-
if (model)
|
|
56
|
+
if (model) add(model, preferred.reasoning);
|
|
67
57
|
}
|
|
68
|
-
if (includeParentModel && ctx.model)
|
|
58
|
+
if (includeParentModel && ctx.model) add(ctx.model, undefined);
|
|
69
59
|
|
|
70
60
|
if (candidates.length === 0) throw new Error("No authenticated model available for generation.");
|
|
71
61
|
return candidates;
|
|
@@ -253,19 +243,12 @@ function completeCandidate(
|
|
|
253
243
|
sessionId: string,
|
|
254
244
|
tools?: Tool[],
|
|
255
245
|
): Promise<AssistantMessage> {
|
|
256
|
-
return
|
|
257
|
-
.streamSimple(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
headers: candidate.headers,
|
|
263
|
-
env: candidate.env,
|
|
264
|
-
signal: ctx.signal,
|
|
265
|
-
reasoning: candidate.reasoning,
|
|
266
|
-
sessionId,
|
|
267
|
-
},
|
|
268
|
-
)
|
|
246
|
+
return ctx.modelRegistry
|
|
247
|
+
.streamSimple(candidate.model, tools ? { messages: [...messages], tools } : { messages: [...messages] }, {
|
|
248
|
+
signal: ctx.signal,
|
|
249
|
+
reasoning: candidate.reasoning,
|
|
250
|
+
sessionId,
|
|
251
|
+
})
|
|
269
252
|
.result();
|
|
270
253
|
}
|
|
271
254
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import type { Api, Model,
|
|
1
|
+
import type { Api, Model, ThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
2
|
|
|
3
3
|
export interface ModelCandidate {
|
|
4
4
|
model: Model<Api>;
|
|
5
|
-
provider: Provider;
|
|
6
|
-
apiKey: string | undefined;
|
|
7
|
-
headers: ProviderHeaders | undefined;
|
|
8
|
-
env: ProviderEnv | undefined;
|
|
9
5
|
reasoning: ThinkingLevel | undefined;
|
|
10
6
|
}
|
|
@@ -3,8 +3,6 @@ import { emitTauEvent, onTauEventImmediately, type TauAgentEvents } from "./even
|
|
|
3
3
|
|
|
4
4
|
type PromptSource = Parameters<TauAgentEvents["tau:prompt.sources"]["accept"]>[0];
|
|
5
5
|
|
|
6
|
-
export type PromptValue = Pick<PromptSource, "key" | "section" | "refresh"> & { text: string };
|
|
7
|
-
|
|
8
6
|
export function collectPromptSources(pi: ExtensionAPI): PromptSource[] {
|
|
9
7
|
const sources: PromptSource[] = [];
|
|
10
8
|
emitTauEvent(pi, "tau:prompt.sources", { accept: (source) => sources.push(source) });
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { emitTauEvent, onTauEventImmediately } from "../../shared/events.ts";
|
|
3
2
|
|
|
4
3
|
export interface DeferredToolGroup {
|
|
5
4
|
id: string;
|
|
@@ -7,52 +6,16 @@ export interface DeferredToolGroup {
|
|
|
7
6
|
tools: readonly ToolDefinition[];
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export interface DeferredToolGroupInfo {
|
|
13
|
-
id: string;
|
|
14
|
-
description: string;
|
|
15
|
-
toolNames: readonly string[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Pi evaluates each extension in an isolated module graph, so module-local registries are not shared.
|
|
19
|
-
const DEFERRED_TOOL_GROUP_REQUEST_EVENT = "tau:deferred-tool-group.request";
|
|
20
|
-
|
|
21
|
-
export function registerDeferredToolGroup(pi: DeferredToolHost, group: DeferredToolGroup): void {
|
|
9
|
+
/** Registers tools that stay out of the model's declarations until Pi's `tool_search` loads them. */
|
|
10
|
+
export function registerDeferredToolGroup(pi: Pick<ExtensionAPI, "registerTool">, group: DeferredToolGroup): void {
|
|
22
11
|
const id = group.id.trim();
|
|
23
12
|
if (id.length === 0 || id !== group.id) throw new Error("Deferred tool group ID must be non-empty and trimmed");
|
|
24
13
|
if (group.description.trim().length === 0) throw new Error(`Deferred tool group ${id} needs a description`);
|
|
25
14
|
if (group.tools.length === 0) throw new Error(`Deferred tool group ${id} needs at least one tool`);
|
|
26
|
-
|
|
27
|
-
const toolNames = group.tools.map((tool) => tool.name);
|
|
28
|
-
if (new Set(toolNames).size !== toolNames.length) {
|
|
15
|
+
if (new Set(group.tools.map((tool) => tool.name)).size !== group.tools.length) {
|
|
29
16
|
throw new Error(`Deferred tool group ${id} contains duplicate tool names`);
|
|
30
17
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const groups = registeredDeferredToolGroups(pi);
|
|
34
|
-
if (groups.some((existing) => existing.id === id)) {
|
|
35
|
-
throw new Error(`Deferred tool group is already registered: ${id}`);
|
|
36
|
-
}
|
|
37
|
-
for (const existing of groups) {
|
|
38
|
-
if (toolNames.some((name) => existing.toolNames.includes(name))) {
|
|
39
|
-
throw new Error(`Deferred tool group ${id} overlaps an existing tool group`);
|
|
40
|
-
}
|
|
18
|
+
for (const tool of group.tools) {
|
|
19
|
+
pi.registerTool({ ...tool, exposure: "deferred", namespace: { name: id, description: group.description } });
|
|
41
20
|
}
|
|
42
|
-
|
|
43
|
-
for (const tool of group.tools) pi.registerTool(tool);
|
|
44
|
-
const info = { id, description: group.description, toolNames } satisfies DeferredToolGroupInfo;
|
|
45
|
-
onTauEventImmediately(pi, `deferred-tool-group.${id}`, DEFERRED_TOOL_GROUP_REQUEST_EVENT, ({ accept }) => {
|
|
46
|
-
accept(info);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function registeredDeferredToolGroups(pi: Pick<ExtensionAPI, "events">): readonly DeferredToolGroupInfo[] {
|
|
51
|
-
const groups: DeferredToolGroupInfo[] = [];
|
|
52
|
-
emitTauEvent(pi, DEFERRED_TOOL_GROUP_REQUEST_EVENT, {
|
|
53
|
-
accept(group: DeferredToolGroupInfo) {
|
|
54
|
-
groups.push(group);
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
return groups;
|
|
58
21
|
}
|