@shanepadgett/tau-agent 0.5.1 → 0.7.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.
@@ -0,0 +1,68 @@
1
+ function isRecord(value: unknown): value is Record<string, unknown> {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+
5
+ function contentText(value: unknown): string {
6
+ if (typeof value === "string") return value;
7
+ if (!Array.isArray(value)) return "";
8
+ return value
9
+ .map((part) => {
10
+ if (!isRecord(part)) return "";
11
+ return typeof part.text === "string" ? part.text : "";
12
+ })
13
+ .filter(Boolean)
14
+ .join("\n");
15
+ }
16
+
17
+ function normalizeToolOutput(item: Record<string, unknown>): unknown[] {
18
+ if (item.type !== "function_call_output" || !Array.isArray(item.output)) return [item];
19
+ const images = item.output.filter((part) => isRecord(part) && part.type === "input_image");
20
+ if (images.length === 0) return [item];
21
+ const text = contentText(item.output) || "(tool returned image output)";
22
+ return [
23
+ { ...item, output: text },
24
+ {
25
+ role: "user",
26
+ content: [
27
+ { type: "input_text", text: "The previous tool result included image output. Use the attached image." },
28
+ ...images,
29
+ ],
30
+ },
31
+ ];
32
+ }
33
+
34
+ export function rewriteXaiPayload(value: unknown): unknown {
35
+ if (!isRecord(value)) return value;
36
+ const body = { ...value };
37
+ delete body.prompt_cache_retention;
38
+ if (isRecord(body.reasoning)) {
39
+ const effort = body.reasoning.effort;
40
+ body.reasoning =
41
+ typeof effort === "string" && effort !== "none"
42
+ ? { effort: effort === "minimal" ? "low" : effort }
43
+ : undefined;
44
+ }
45
+ if (Array.isArray(body.input)) {
46
+ const instructions: string[] = [];
47
+ const input: unknown[] = [];
48
+ for (const raw of body.input) {
49
+ if (!isRecord(raw)) {
50
+ input.push(raw);
51
+ continue;
52
+ }
53
+ if ((raw.role === "developer" || raw.role === "system") && input.length === 0) {
54
+ const text = contentText(raw.content).trim();
55
+ if (text) instructions.push(text);
56
+ continue;
57
+ }
58
+ input.push(...normalizeToolOutput(raw));
59
+ }
60
+ body.input = input;
61
+ if (instructions.length > 0) {
62
+ body.instructions = [typeof body.instructions === "string" ? body.instructions : "", ...instructions]
63
+ .filter(Boolean)
64
+ .join("\n\n");
65
+ }
66
+ }
67
+ return body;
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shanepadgett/tau-agent",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
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.5.1",
31
+ "@shanepadgett/tau-tui": "0.7.0",
32
32
  "@toon-format/toon": "2.3.0",
33
33
  "smol-toml": "1.7.0"
34
34
  },
package/shared/git.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  const DEFAULT_GIT_TIMEOUT_MS = 10_000;
4
4
 
@@ -7,7 +7,7 @@ export interface GitRunner {
7
7
  run(args: string[], options?: { cwd?: string; optional?: boolean; timeout?: number }): Promise<string>;
8
8
  }
9
9
 
10
- export function createGitRunner(pi: ExtensionAPI, ctx: ExtensionCommandContext): GitRunner {
10
+ export function createGitRunner(pi: ExtensionAPI, ctx: ExtensionContext): GitRunner {
11
11
  return {
12
12
  cwd: ctx.cwd,
13
13
  async run(args, options = {}) {
@@ -10,21 +10,23 @@ const MAX_ATTEMPTS = 5;
10
10
  const MAX_TOOL_ATTEMPTS = 2;
11
11
  const SEVEN_DAYS_MS = 604_800_000;
12
12
 
13
- const PREFERRED_MODELS: ReadonlyArray<{ provider: string; model: string; reasoning: ThinkingLevel }> = [
14
- { provider: "openrouter", model: "cohere/north-mini-code:free", reasoning: "high" },
15
- { provider: "github-copilot", model: "gemini-3.5-flash", reasoning: "high" },
16
- { provider: "openai-codex", model: "gpt-5.4-mini", reasoning: "high" },
17
- { provider: "anthropic", model: "claude-haiku-4-5", reasoning: "high" },
18
- ];
19
-
20
13
  interface GenerationContext {
21
14
  ui: ExtensionContext["ui"];
22
15
  signal: AbortSignal | undefined;
16
+ sessionManager?: { getSessionId(): string };
17
+ }
18
+
19
+ interface ModelFallbackOptions {
20
+ statusKey?: string;
21
+ notifyOnFallback?: boolean;
22
+ maxAttempts?: number;
23
+ onStatus?: (status: string) => void | Promise<void>;
23
24
  }
24
25
 
25
26
  export async function resolveCandidates(
26
27
  ctx: Pick<ExtensionContext, "modelRegistry" | "model" | "cwd" | "isProjectTrusted">,
27
- preferredModels: ReadonlyArray<{ provider: string; model: string; reasoning: ThinkingLevel }> = PREFERRED_MODELS,
28
+ preferredModels: ReadonlyArray<{ provider: string; model: string; reasoning: ThinkingLevel }>,
29
+ includeParentModel: boolean,
28
30
  ): Promise<ModelCandidate[]> {
29
31
  const settings = await loadTauExtensionSettings(ctx, modelFallbackSettings);
30
32
  const candidates: ModelCandidate[] = [];
@@ -47,7 +49,7 @@ export async function resolveCandidates(
47
49
  const model = ctx.modelRegistry.find(preferred.provider, preferred.model);
48
50
  if (model) await add(model, preferred.reasoning);
49
51
  }
50
- if (ctx.model) await add(ctx.model, undefined);
52
+ if (includeParentModel && ctx.model) await add(ctx.model, undefined);
51
53
 
52
54
  if (candidates.length === 0) throw new Error("No authenticated model available for generation.");
53
55
  return candidates;
@@ -59,7 +61,7 @@ export async function generateValidated<T>(
59
61
  prompt: string,
60
62
  validate: (text: string) => T,
61
63
  correctionPrompt?: (error: Error, text: string) => string,
62
- options?: { statusKey?: string; notifyOnFallback?: boolean },
64
+ options?: ModelFallbackOptions,
63
65
  ): Promise<T> {
64
66
  return withModelFallback(ctx, candidates, options, (candidate) =>
65
67
  requestValidated(ctx, candidate, prompt, validate, correctionPrompt),
@@ -73,7 +75,7 @@ export async function generateToolValidated<T>(
73
75
  tool: Tool,
74
76
  validate: (input: unknown) => T,
75
77
  correctionPrompt?: (error: Error, output: string) => string,
76
- options?: { statusKey?: string; notifyOnFallback?: boolean; maxAttempts?: number },
78
+ options?: ModelFallbackOptions,
77
79
  ): Promise<T> {
78
80
  return withModelFallback(ctx, candidates, options, (candidate) =>
79
81
  requestToolValidated(
@@ -91,7 +93,7 @@ export async function generateToolValidated<T>(
91
93
  async function withModelFallback<T>(
92
94
  ctx: GenerationContext,
93
95
  candidates: readonly ModelCandidate[],
94
- options: { statusKey?: string; notifyOnFallback?: boolean } | undefined,
96
+ options: ModelFallbackOptions | undefined,
95
97
  request: (candidate: ModelCandidate) => Promise<T>,
96
98
  ): Promise<T> {
97
99
  const failures: string[] = [];
@@ -101,6 +103,7 @@ async function withModelFallback<T>(
101
103
  for (const [index, candidate] of candidates.entries()) {
102
104
  const label = `${candidate.model.provider}/${candidate.model.id}`;
103
105
  if (statusKey) ctx.ui.setStatus(statusKey, `generating (${label})`);
106
+ await options?.onStatus?.(`Generating with ${label}`);
104
107
 
105
108
  try {
106
109
  return await request(candidate);
@@ -109,6 +112,7 @@ async function withModelFallback<T>(
109
112
  if (shouldCooldownProvider(error)) await markProviderUnavailable(candidate.model.provider);
110
113
  const message = errorText(error);
111
114
  failures.push(`- ${label}: ${message}`);
115
+ if (index < candidates.length - 1) await options?.onStatus?.(`Model failed (${label}); trying next model`);
112
116
  if (index < candidates.length - 1 && notifyOnFallback) {
113
117
  ctx.ui.notify(`Model failed (${label}): ${message}\nTrying next model.`, "info");
114
118
  }
@@ -209,6 +213,7 @@ function completeCandidate(
209
213
  headers: candidate.headers,
210
214
  signal: ctx.signal,
211
215
  reasoning: candidate.reasoning,
216
+ sessionId: ctx.sessionManager?.getSessionId(),
212
217
  });
213
218
  }
214
219
 
@@ -1,23 +0,0 @@
1
- ---
2
- name: context-maintenance
3
- description: Reconcile repository context definitions after meaningful scope changes, with user approval before every update
4
- tools:
5
- - read
6
- - grep
7
- - find
8
- - ls
9
- - context_list
10
- - context_get
11
- - context_check
12
- - context_audit
13
- - context_changes
14
- - context_review
15
- model: openai-codex/gpt-5.6-luna
16
- thinking: high
17
- ---
18
-
19
- Maintain the repository context catalog. Research only the changed or requested scopes. Existing entries should stay small, reusable, and useful for future work. Prefer updating an existing entry over creating a near-duplicate. Do not create one entry per file.
20
-
21
- Use context_changes when the task does not provide exact changed paths. Use context_list, context_get, context_check, and context_audit to understand the current catalog. Call context_review with concrete proposed operations. That tool owns user approval and applies only selected operations.
22
-
23
- If context_review returns feedback, revise the affected proposal or batch and call context_review again. If it returns rejected, stop. Never claim an operation was applied unless context_review reports it applied. Finish with only a short applied/rejected summary.