@shanepadgett/tau-agent 0.7.2 → 0.9.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/extensions/appshot/index.ts +3 -13
- package/extensions/context/README.md +16 -1
- package/extensions/context/definitions.ts +41 -0
- package/extensions/context/index.ts +77 -25
- package/extensions/context/settings.ts +33 -0
- package/extensions/context/sync.ts +77 -37
- package/extensions/context/validation.ts +73 -0
- package/extensions/image-gen/index.ts +1 -8
- package/extensions/publish/README.md +1 -1
- package/extensions/publish/index.ts +1 -0
- package/extensions/runtime-context/README.md +5 -0
- package/extensions/runtime-context/context.ts +138 -0
- package/extensions/runtime-context/index.ts +84 -0
- package/extensions/silent-command-runner/README.md +1 -1
- package/extensions/silent-command-runner/index.ts +9 -51
- package/extensions/soul/README.md +2 -2
- package/extensions/soul/index.ts +3 -6
- package/extensions/soul/prompt.ts +3 -232
- package/extensions/subagent/index.ts +10 -15
- package/extensions/tau-help/help.md +11 -3
- package/extensions/tau-help/index.ts +12 -0
- package/extensions/tool-loader/README.md +13 -0
- package/extensions/tool-loader/index.ts +134 -0
- package/extensions/turn-budget/index.ts +4 -10
- package/extensions/web/codesearch.ts +1 -7
- package/extensions/web/webfetch.ts +1 -7
- package/extensions/web/websearch.ts +1 -7
- package/package.json +2 -2
- package/schemas/tau.schema.json +25 -0
- package/shared/glob.ts +34 -0
- package/shared/model-fallback/index.ts +7 -4
|
@@ -28,7 +28,7 @@ Adds `/commit` for semantic commit grouping, review, and committing selected rep
|
|
|
28
28
|
|
|
29
29
|
## context
|
|
30
30
|
|
|
31
|
-
Adds `/context` to select reusable repository work scopes from `.pi/contexts`, and `/context-sync` to reconcile affected scopes from current Git changes. Folder names are tabs, TOML files are concepts, and TOML sections are selectable entries.
|
|
31
|
+
Adds `/context` to select reusable repository work scopes from `.pi/contexts`, and `/context-sync` to reconcile affected scopes from current Git changes. Tau automatically validates changed-file membership and stale references after agent turns. Folder names are tabs, TOML files are concepts, and TOML sections are selectable entries.
|
|
32
32
|
|
|
33
33
|
## explore
|
|
34
34
|
|
|
@@ -74,13 +74,17 @@ Adds `/reference` to manage separate repositories kept outside the current proje
|
|
|
74
74
|
|
|
75
75
|
Shows a compact display-only marker after each run with wall time and model cost. It does not enter agent context.
|
|
76
76
|
|
|
77
|
+
## runtime-context
|
|
78
|
+
|
|
79
|
+
Supplies the agent with the current local date and an initial root directory snapshot as hidden session context.
|
|
80
|
+
|
|
77
81
|
## silent-command-runner
|
|
78
82
|
|
|
79
83
|
Runs configured commands while keeping their output out of agent context when that is useful.
|
|
80
84
|
|
|
81
85
|
## soul
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
Adds Rok’s persona and operating rules to Pi’s native assistant prompt.
|
|
84
88
|
|
|
85
89
|
## stash
|
|
86
90
|
|
|
@@ -98,13 +102,17 @@ Adds `/tau-help` to show this guide as rendered Markdown in the chat.
|
|
|
98
102
|
|
|
99
103
|
Adds `/tau`, `/tau init [--global|--project]`, and `/tau doctor` for Tau setup and diagnostics.
|
|
100
104
|
|
|
105
|
+
## tool-loader
|
|
106
|
+
|
|
107
|
+
Progressively exposes specialist tools through `load_tools`. Tau normally loads the fixed `web`, `image`, and `appshot` groups itself when needed; supported providers can preserve more prompt-cache reuse.
|
|
108
|
+
|
|
101
109
|
## turn-budget
|
|
102
110
|
|
|
103
111
|
Tracks and limits agent turns to keep work bounded.
|
|
104
112
|
|
|
105
113
|
## web
|
|
106
114
|
|
|
107
|
-
Gives the agent compact `
|
|
115
|
+
Gives the agent compact `websearch`, `webfetch`, and `codesearch` tools for web and implementation research.
|
|
108
116
|
|
|
109
117
|
## Prompts
|
|
110
118
|
|
|
@@ -4,6 +4,14 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { Markdown, type Component, visibleWidth } from "@earendil-works/pi-tui";
|
|
6
6
|
|
|
7
|
+
const TAU_DOCS_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "docs");
|
|
8
|
+
const TAU_DOCS_GUIDANCE = `Tau Agent documentation (read only when the user asks about Tau Agent, Rok, Tau extensions, Tau event APIs, harness behavior, or extending Tau Agent):
|
|
9
|
+
- Tau Agent docs: ${TAU_DOCS_PATH}
|
|
10
|
+
- When asked about: public events / external integration (docs/extending-tau-agent.md), custom subagents (docs/subagents.md), Tau TUI components (docs/tui.md)
|
|
11
|
+
- Resolve Tau docs/... under Tau Agent docs, not the current working directory
|
|
12
|
+
- When working on Tau topics, read the docs and follow .md cross-references before implementing
|
|
13
|
+
- Do not read Tau Agent docs for normal coding tasks`;
|
|
14
|
+
|
|
7
15
|
const TAU_SYMBOL = [
|
|
8
16
|
" %%%%%%%%#######*******+++++",
|
|
9
17
|
" @@%%%%%%%########*******+++++++",
|
|
@@ -46,6 +54,10 @@ class TauHelpMessage implements Component {
|
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
export default function tauHelpExtension(pi: ExtensionAPI): void {
|
|
57
|
+
pi.on("before_agent_start", (event) => ({
|
|
58
|
+
systemPrompt: `${event.systemPrompt}\n\n${TAU_DOCS_GUIDANCE}`,
|
|
59
|
+
}));
|
|
60
|
+
|
|
49
61
|
pi.registerMessageRenderer("tau-help", (message, _options, _theme) => {
|
|
50
62
|
if (typeof message.content !== "string") return undefined;
|
|
51
63
|
return new TauHelpMessage(message.content, getMarkdownTheme());
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Tool Loader
|
|
2
|
+
|
|
3
|
+
Tau progressively exposes specialist tools. Most coding turns do not need web, image, or macOS application schemas, so Pi can load those tools later without discarding supported provider cache prefixes.
|
|
4
|
+
|
|
5
|
+
The agent normally calls `load_tools` itself. Users can also ask Tau to load one of these groups:
|
|
6
|
+
|
|
7
|
+
- `web` for public web and implementation research
|
|
8
|
+
- `image` for raster image generation and editing
|
|
9
|
+
- `appshot` for macOS window discovery, capture, and activation
|
|
10
|
+
|
|
11
|
+
Supported models optimize prompt caching when a group loads. Other models keep the same functional behavior.
|
|
12
|
+
|
|
13
|
+
After changing this extension during development, run `/reload` before testing.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { type Static, Type } from "typebox";
|
|
4
|
+
|
|
5
|
+
const CAPABILITIES = ["web", "image", "appshot"] as const;
|
|
6
|
+
type Capability = (typeof CAPABILITIES)[number];
|
|
7
|
+
|
|
8
|
+
const CAPABILITY_TOOLS: Record<Capability, readonly string[]> = {
|
|
9
|
+
web: ["webfetch", "websearch", "codesearch"],
|
|
10
|
+
image: ["image_gen"],
|
|
11
|
+
appshot: ["list_windows", "screenshot_window", "activate_app"],
|
|
12
|
+
};
|
|
13
|
+
const SPECIALIST_TOOLS = CAPABILITIES.flatMap((capability) => CAPABILITY_TOOLS[capability]);
|
|
14
|
+
|
|
15
|
+
const loadToolsSchema = Type.Object(
|
|
16
|
+
{
|
|
17
|
+
capability: StringEnum(CAPABILITIES, {
|
|
18
|
+
description: "Specialist group to load: web, image, or appshot",
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
{ additionalProperties: false },
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
type LoadToolsParams = Static<typeof loadToolsSchema>;
|
|
25
|
+
|
|
26
|
+
interface LoadToolsDetails {
|
|
27
|
+
version: 1;
|
|
28
|
+
capability: Capability;
|
|
29
|
+
requestedToolNames: string[];
|
|
30
|
+
addedToolNames: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default function toolLoaderExtension(pi: ExtensionAPI): void {
|
|
34
|
+
let managed = false;
|
|
35
|
+
let allowedSpecialistNames = new Set<string>();
|
|
36
|
+
|
|
37
|
+
pi.registerTool(
|
|
38
|
+
defineTool<typeof loadToolsSchema, LoadToolsDetails>({
|
|
39
|
+
name: "load_tools",
|
|
40
|
+
label: "Load Tools",
|
|
41
|
+
description:
|
|
42
|
+
"Load one Tau specialist tool group for the current session. Groups: web for public web and implementation research; image for raster generation and editing; appshot for macOS window discovery, capture, and activation.",
|
|
43
|
+
promptSnippet: "Load a specialist Tau tool group for web research, image generation, or macOS app inspection",
|
|
44
|
+
promptGuidelines: [
|
|
45
|
+
"Use load_tools before attempting a specialist capability whose tools are not currently available.",
|
|
46
|
+
],
|
|
47
|
+
parameters: loadToolsSchema,
|
|
48
|
+
async execute(_toolCallId, params: LoadToolsParams) {
|
|
49
|
+
const before = pi.getActiveTools();
|
|
50
|
+
const requested = [...CAPABILITY_TOOLS[params.capability]];
|
|
51
|
+
const registered = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
52
|
+
const loadable = requested.filter((name) => registered.has(name) && allowedSpecialistNames.has(name));
|
|
53
|
+
if (loadable.length === 0) {
|
|
54
|
+
throw new Error(`No ${params.capability} tools are available in this session's tool configuration.`);
|
|
55
|
+
}
|
|
56
|
+
const beforeSet = new Set(before);
|
|
57
|
+
const next = [...before, ...loadable.filter((name) => !beforeSet.has(name))];
|
|
58
|
+
pi.setActiveTools(next);
|
|
59
|
+
const after = pi.getActiveTools();
|
|
60
|
+
const addedToolNames = after.filter((name) => !beforeSet.has(name));
|
|
61
|
+
const available = requested.filter((name) => after.includes(name));
|
|
62
|
+
const unavailable = requested.filter((name) => !after.includes(name));
|
|
63
|
+
const label = `${params.capability[0]?.toUpperCase()}${params.capability.slice(1)}`;
|
|
64
|
+
const text =
|
|
65
|
+
addedToolNames.length > 0
|
|
66
|
+
? `Loaded ${params.capability} tools: ${addedToolNames.join(", ")}.`
|
|
67
|
+
: `${label} tools are already loaded: ${available.join(", ")}.`;
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: unavailable.length ? `${text} Unavailable: ${unavailable.join(", ")}.` : text,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
details: {
|
|
76
|
+
version: 1,
|
|
77
|
+
capability: params.capability,
|
|
78
|
+
requestedToolNames: requested,
|
|
79
|
+
addedToolNames,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
pi.on("session_start", (_event, ctx) => {
|
|
87
|
+
const initial = pi.getActiveTools();
|
|
88
|
+
const initialSet = new Set(initial);
|
|
89
|
+
allowedSpecialistNames = new Set(SPECIALIST_TOOLS.filter((name) => initialSet.has(name)));
|
|
90
|
+
managed = initialSet.has("load_tools") && SPECIALIST_TOOLS.every((name) => initialSet.has(name));
|
|
91
|
+
if (managed) restoreActiveTools(pi, initial, loadedCapabilities(ctx.sessionManager.getBranch()));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
95
|
+
if (managed) restoreActiveTools(pi, pi.getActiveTools(), loadedCapabilities(ctx.sessionManager.getBranch()));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function restoreActiveTools(pi: ExtensionAPI, current: readonly string[], loaded: ReadonlySet<Capability>): void {
|
|
100
|
+
const specialist = new Set(SPECIALIST_TOOLS);
|
|
101
|
+
const next = current.filter((name) => !specialist.has(name));
|
|
102
|
+
for (const capability of CAPABILITIES) {
|
|
103
|
+
if (loaded.has(capability)) next.push(...CAPABILITY_TOOLS[capability]);
|
|
104
|
+
}
|
|
105
|
+
pi.setActiveTools([...new Set(next)]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function loadedCapabilities(entries: readonly unknown[]): Set<Capability> {
|
|
109
|
+
const loaded = new Set<Capability>();
|
|
110
|
+
for (const value of entries) {
|
|
111
|
+
if (!value || typeof value !== "object") continue;
|
|
112
|
+
const entry = value as Record<string, unknown>;
|
|
113
|
+
if (entry.type !== "message" || !entry.message || typeof entry.message !== "object") continue;
|
|
114
|
+
const message = entry.message as Record<string, unknown>;
|
|
115
|
+
if (message.role !== "toolResult" || message.toolName !== "load_tools" || message.isError === true) continue;
|
|
116
|
+
if (!isLoadToolsDetails(message.details)) continue;
|
|
117
|
+
loaded.add(message.details.capability);
|
|
118
|
+
}
|
|
119
|
+
return loaded;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isLoadToolsDetails(value: unknown): value is LoadToolsDetails {
|
|
123
|
+
if (!value || typeof value !== "object") return false;
|
|
124
|
+
const details = value as Record<string, unknown>;
|
|
125
|
+
return (
|
|
126
|
+
details.version === 1 &&
|
|
127
|
+
typeof details.capability === "string" &&
|
|
128
|
+
CAPABILITIES.includes(details.capability as Capability) &&
|
|
129
|
+
Array.isArray(details.requestedToolNames) &&
|
|
130
|
+
details.requestedToolNames.every((name) => typeof name === "string") &&
|
|
131
|
+
Array.isArray(details.addedToolNames) &&
|
|
132
|
+
details.addedToolNames.every((name) => typeof name === "string")
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -41,13 +41,6 @@ export default function turnBudgetExtension(pi: ExtensionAPI): void {
|
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
pi.on("before_agent_start", (event) => {
|
|
45
|
-
if (!settings.enabled) return undefined;
|
|
46
|
-
return {
|
|
47
|
-
systemPrompt: `${event.systemPrompt}\n\nTurn-budget steering messages are internal instructions. Work within them silently. Do not mention or acknowledge turn counts, budget messages, or budget summaries.`,
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
|
|
51
44
|
pi.on("session_start", async (_event, ctx) => {
|
|
52
45
|
settings = normalizeSettings(await loadTauExtensionSettings(ctx, turnBudgetSettings));
|
|
53
46
|
});
|
|
@@ -96,7 +89,6 @@ export default function turnBudgetExtension(pi: ExtensionAPI): void {
|
|
|
96
89
|
pi.on("after_provider_response", () => {
|
|
97
90
|
activeMarkerSequence = undefined;
|
|
98
91
|
});
|
|
99
|
-
|
|
100
92
|
function markerIsActive(value: unknown): boolean {
|
|
101
93
|
if (activeMarkerSequence === undefined) return false;
|
|
102
94
|
const details = readMarkerDetails(value);
|
|
@@ -114,10 +106,12 @@ function sendTurnBudgetMessage(pi: ExtensionAPI, hint: Hint, sequence: number):
|
|
|
114
106
|
}
|
|
115
107
|
|
|
116
108
|
function formatSteeringMessage(hint: Hint): string {
|
|
109
|
+
const instruction =
|
|
110
|
+
"Internal steering instruction. Work within it silently. Do not mention or acknowledge turn counts, budget messages, or budget summaries.";
|
|
117
111
|
if (hint.kind === "extended") {
|
|
118
|
-
return
|
|
112
|
+
return `${instruction} Turn budget: ${hint.used}/${hint.previousCap} turns used for this user prompt. Soft cap extended to ${hint.newCap}. Batch tools when more tool work remains.`;
|
|
119
113
|
}
|
|
120
|
-
return
|
|
114
|
+
return `${instruction} Turn budget: ${hint.used}/${hint.cap} turns used for this user prompt. Batch tools when more tool work remains.`;
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
function normalizeSettings(value: typeof turnBudgetSettings.defaults): Settings {
|
|
@@ -27,13 +27,7 @@ export function createCodeSearchTool(rowState: ToolRowStateStore) {
|
|
|
27
27
|
name: "codesearch",
|
|
28
28
|
label: "Code Search",
|
|
29
29
|
description:
|
|
30
|
-
"Search Exa for
|
|
31
|
-
promptSnippet: "Search code and documentation context for implementation details",
|
|
32
|
-
promptGuidelines: [
|
|
33
|
-
"Use codesearch for API usage, code examples, and implementation-oriented documentation.",
|
|
34
|
-
"Use websearch for broad discovery and webfetch for a known URL.",
|
|
35
|
-
"Use a separate research workflow instead of codesearch when several searches, fetches, and synthesis are needed.",
|
|
36
|
-
],
|
|
30
|
+
"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.",
|
|
37
31
|
parameters: codeSearchParams,
|
|
38
32
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
39
33
|
const timeout = normalizeTimeout(params.timeout, 25);
|
|
@@ -66,13 +66,7 @@ export function createWebFetchTool(rowState: ToolRowStateStore) {
|
|
|
66
66
|
name: "webfetch",
|
|
67
67
|
label: "Web Fetch",
|
|
68
68
|
description:
|
|
69
|
-
"Fetch a known HTTP(S) URL as Markdown, text, or HTML. Supports inline images, limits response bodies to 5 MB, and truncates text to 2,000 lines or 50 KB.",
|
|
70
|
-
promptSnippet: "Fetch a specific URL and extract readable content",
|
|
71
|
-
promptGuidelines: [
|
|
72
|
-
"Use webfetch when you already have a URL and need its content.",
|
|
73
|
-
"Use websearch for broad discovery and codesearch for implementation-oriented lookups.",
|
|
74
|
-
"Use a separate research workflow instead of webfetch when several searches, fetches, and synthesis are needed.",
|
|
75
|
-
],
|
|
69
|
+
"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.",
|
|
76
70
|
parameters: webFetchParams,
|
|
77
71
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
78
72
|
let url: URL;
|
|
@@ -36,13 +36,7 @@ export function createWebSearchTool(rowState: ToolRowStateStore) {
|
|
|
36
36
|
name: "websearch",
|
|
37
37
|
label: "Web Search",
|
|
38
38
|
description:
|
|
39
|
-
"Search the public web through Exa for current information and relevant pages. Output is truncated to 2,000 lines or 50 KB.",
|
|
40
|
-
promptSnippet: "Search the public web for current or external information",
|
|
41
|
-
promptGuidelines: [
|
|
42
|
-
"Use websearch for broad discovery, then webfetch when you have a specific URL.",
|
|
43
|
-
"Use codesearch for implementation-oriented code and documentation context.",
|
|
44
|
-
"Use a separate research workflow instead of websearch when several searches, fetches, and synthesis are needed.",
|
|
45
|
-
],
|
|
39
|
+
"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.",
|
|
46
40
|
parameters: webSearchParams,
|
|
47
41
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
48
42
|
const timeout = normalizeTimeout(params.timeout, 25);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Tau is a custom agentic harness built with pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@shanepadgett/tau-tui": "0.
|
|
31
|
+
"@shanepadgett/tau-tui": "0.9.1",
|
|
32
32
|
"@toon-format/toon": "2.3.0",
|
|
33
33
|
"smol-toml": "1.7.0"
|
|
34
34
|
},
|
package/schemas/tau.schema.json
CHANGED
|
@@ -12,6 +12,31 @@
|
|
|
12
12
|
"extensions": {
|
|
13
13
|
"type": "object",
|
|
14
14
|
"properties": {
|
|
15
|
+
"context": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"validation": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"enabled": {
|
|
22
|
+
"type": "boolean",
|
|
23
|
+
"default": true,
|
|
24
|
+
"description": "Validate context membership after agent turns."
|
|
25
|
+
},
|
|
26
|
+
"ignoreGlobs": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
"default": [],
|
|
32
|
+
"description": "Project-relative files excluded from context membership validation and sync."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"additionalProperties": false
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"additionalProperties": false
|
|
39
|
+
},
|
|
15
40
|
"footer": {
|
|
16
41
|
"type": "object",
|
|
17
42
|
"properties": {
|
package/shared/glob.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { sep } from "node:path";
|
|
2
|
+
|
|
3
|
+
export function matchGlob(pattern: string, path: string): boolean {
|
|
4
|
+
return matchSegments(normalizeGlob(pattern).split("/"), normalizeGlob(path).split("/"));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function matchSegments(pattern: readonly string[], path: readonly string[]): boolean {
|
|
8
|
+
const [head, ...tail] = pattern;
|
|
9
|
+
if (head === undefined) return path.length === 0;
|
|
10
|
+
if (head === "**") return matchSegments(tail, path) || (path.length > 0 && matchSegments(pattern, path.slice(1)));
|
|
11
|
+
const [pathHead, ...pathTail] = path;
|
|
12
|
+
return pathHead !== undefined && matchSegment(head, pathHead) && matchSegments(tail, pathTail);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function matchSegment(pattern: string, value: string): boolean {
|
|
16
|
+
const source = [...pattern]
|
|
17
|
+
.map((char) => {
|
|
18
|
+
if (char === "*") return "[^/]*";
|
|
19
|
+
if (char === "?") return "[^/]";
|
|
20
|
+
return char.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
21
|
+
})
|
|
22
|
+
.join("");
|
|
23
|
+
return new RegExp(`^${source}$`).test(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeGlob(value: string): string {
|
|
27
|
+
return posixPath(value.trim())
|
|
28
|
+
.replace(/^\.\//, "")
|
|
29
|
+
.replace(/^\/+|\/+$/g, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function posixPath(value: string): string {
|
|
33
|
+
return sep === "/" ? value : value.split(sep).join("/");
|
|
34
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import type { Api, AssistantMessage, Message, Model, ThinkingLevel, Tool } from "@earendil-works/pi-ai";
|
|
2
3
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
3
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -13,7 +14,6 @@ const SEVEN_DAYS_MS = 604_800_000;
|
|
|
13
14
|
interface GenerationContext {
|
|
14
15
|
ui: ExtensionContext["ui"];
|
|
15
16
|
signal: AbortSignal | undefined;
|
|
16
|
-
sessionManager?: { getSessionId(): string };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
interface ModelFallbackOptions {
|
|
@@ -131,9 +131,10 @@ async function requestValidated<T>(
|
|
|
131
131
|
): Promise<T> {
|
|
132
132
|
const userMessage: Message = { role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() };
|
|
133
133
|
const messages: Message[] = [userMessage];
|
|
134
|
+
const sessionId = randomUUID();
|
|
134
135
|
|
|
135
136
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
136
|
-
const response = await completeCandidate(ctx, candidate, messages);
|
|
137
|
+
const response = await completeCandidate(ctx, candidate, messages, sessionId);
|
|
137
138
|
const text = responseText(response);
|
|
138
139
|
if (response.stopReason === "error") {
|
|
139
140
|
const error = new Error(response.errorMessage || "model returned an error");
|
|
@@ -169,9 +170,10 @@ async function requestToolValidated<T>(
|
|
|
169
170
|
maxAttempts = MAX_TOOL_ATTEMPTS,
|
|
170
171
|
): Promise<T> {
|
|
171
172
|
const messages: Message[] = [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }];
|
|
173
|
+
const sessionId = randomUUID();
|
|
172
174
|
|
|
173
175
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
174
|
-
const response = await completeCandidate(ctx, candidate, messages, [tool]);
|
|
176
|
+
const response = await completeCandidate(ctx, candidate, messages, sessionId, [tool]);
|
|
175
177
|
const text = responseText(response);
|
|
176
178
|
const toolCalls = response.content.flatMap((part) => (part.type === "toolCall" ? [part] : []));
|
|
177
179
|
const output = text || formatToolCalls(toolCalls);
|
|
@@ -206,6 +208,7 @@ function completeCandidate(
|
|
|
206
208
|
ctx: GenerationContext,
|
|
207
209
|
candidate: ModelCandidate,
|
|
208
210
|
messages: readonly Message[],
|
|
211
|
+
sessionId: string,
|
|
209
212
|
tools?: Tool[],
|
|
210
213
|
): Promise<AssistantMessage> {
|
|
211
214
|
return completeSimple(candidate.model, tools ? { messages: [...messages], tools } : { messages: [...messages] }, {
|
|
@@ -213,7 +216,7 @@ function completeCandidate(
|
|
|
213
216
|
headers: candidate.headers,
|
|
214
217
|
signal: ctx.signal,
|
|
215
218
|
reasoning: candidate.reasoning,
|
|
216
|
-
sessionId
|
|
219
|
+
sessionId,
|
|
217
220
|
});
|
|
218
221
|
}
|
|
219
222
|
|