gsd-pi 2.12.0 → 2.13.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.
Files changed (62) hide show
  1. package/dist/cli.js +18 -1
  2. package/dist/resource-loader.d.ts +2 -0
  3. package/dist/resource-loader.js +36 -1
  4. package/dist/resources/extensions/gsd/auto-worktree.ts +512 -0
  5. package/dist/resources/extensions/gsd/auto.ts +222 -11
  6. package/dist/resources/extensions/gsd/doctor.ts +198 -2
  7. package/dist/resources/extensions/gsd/git-self-heal.ts +198 -0
  8. package/dist/resources/extensions/gsd/git-service.ts +11 -0
  9. package/dist/resources/extensions/gsd/preferences.ts +17 -1
  10. package/dist/resources/extensions/gsd/prompts/system.md +32 -29
  11. package/dist/resources/extensions/gsd/tests/auto-worktree-merge.test.ts +282 -0
  12. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +259 -0
  13. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +147 -0
  14. package/dist/resources/extensions/gsd/tests/doctor-git.test.ts +264 -0
  15. package/dist/resources/extensions/gsd/tests/git-self-heal.test.ts +235 -0
  16. package/dist/resources/extensions/gsd/tests/isolation-resolver.test.ts +107 -0
  17. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +88 -0
  18. package/dist/resources/extensions/gsd/tests/worktree-e2e.test.ts +321 -0
  19. package/dist/resources/extensions/gsd/worktree-manager.ts +13 -7
  20. package/dist/resources/extensions/search-the-web/native-search.ts +15 -10
  21. package/package.json +1 -1
  22. package/packages/pi-coding-agent/dist/cli/args.js +1 -1
  23. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  24. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +4 -1
  25. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  26. package/packages/pi-coding-agent/dist/core/extensions/runner.js +2 -1
  27. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  28. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +5 -0
  29. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  30. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  31. package/packages/pi-coding-agent/dist/core/sdk.js +3 -3
  32. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  33. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  34. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -0
  35. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  36. package/packages/pi-coding-agent/src/cli/args.ts +1 -1
  37. package/packages/pi-coding-agent/src/core/extensions/runner.ts +2 -1
  38. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  39. package/packages/pi-coding-agent/src/core/sdk.ts +3 -3
  40. package/packages/pi-coding-agent/src/core/system-prompt.ts +9 -0
  41. package/packages/pi-tui/dist/components/editor.d.ts +11 -0
  42. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  43. package/packages/pi-tui/dist/components/editor.js +64 -6
  44. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  45. package/packages/pi-tui/src/components/editor.ts +71 -6
  46. package/src/resources/extensions/gsd/auto-worktree.ts +512 -0
  47. package/src/resources/extensions/gsd/auto.ts +222 -11
  48. package/src/resources/extensions/gsd/doctor.ts +198 -2
  49. package/src/resources/extensions/gsd/git-self-heal.ts +198 -0
  50. package/src/resources/extensions/gsd/git-service.ts +11 -0
  51. package/src/resources/extensions/gsd/preferences.ts +17 -1
  52. package/src/resources/extensions/gsd/prompts/system.md +32 -29
  53. package/src/resources/extensions/gsd/tests/auto-worktree-merge.test.ts +282 -0
  54. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +259 -0
  55. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +147 -0
  56. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +264 -0
  57. package/src/resources/extensions/gsd/tests/git-self-heal.test.ts +235 -0
  58. package/src/resources/extensions/gsd/tests/isolation-resolver.test.ts +107 -0
  59. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +88 -0
  60. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +321 -0
  61. package/src/resources/extensions/gsd/worktree-manager.ts +13 -7
  62. package/src/resources/extensions/search-the-web/native-search.ts +15 -10
@@ -0,0 +1,321 @@
1
+ /**
2
+ * worktree-e2e.test.ts -- End-to-end tests for worktree-isolated git flow.
3
+ *
4
+ * Covers 5 cross-cutting groups not tested by individual slice tests:
5
+ * 1. Full lifecycle chain (create -> slice commits -> merge to milestone -> merge to main)
6
+ * 2. Preference gating (shouldUseWorktreeIsolation with overrides)
7
+ * 3. merge_to_main mode resolution (getMergeToMainMode)
8
+ * 4. Self-heal in merge context (abortAndReset, withMergeHeal)
9
+ * 5. Doctor detection of orphaned worktrees
10
+ */
11
+
12
+ import {
13
+ mkdtempSync, mkdirSync, writeFileSync, rmSync,
14
+ existsSync, realpathSync, readFileSync,
15
+ } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { tmpdir } from "node:os";
18
+ import { execSync } from "node:child_process";
19
+
20
+ import {
21
+ createAutoWorktree,
22
+ mergeMilestoneToMain,
23
+ mergeSliceToMilestone,
24
+ shouldUseWorktreeIsolation,
25
+ } from "../auto-worktree.ts";
26
+ import { getSliceBranchName } from "../worktree.ts";
27
+ import { abortAndReset, withMergeHeal, MergeConflictError } from "../git-self-heal.ts";
28
+ import { runGSDDoctor } from "../doctor.ts";
29
+ import { createTestContext } from "./test-helpers.ts";
30
+
31
+ const { assertEq, assertTrue, assertMatch, report } = createTestContext();
32
+
33
+ // ---- Helpers ----
34
+
35
+ function run(cmd: string, cwd: string): string {
36
+ return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
37
+ }
38
+
39
+ function createTempRepo(): string {
40
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-e2e-test-")));
41
+ run("git init", dir);
42
+ run("git config user.email test@test.com", dir);
43
+ run("git config user.name Test", dir);
44
+ writeFileSync(join(dir, "README.md"), "# test\n");
45
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
46
+ writeFileSync(join(dir, ".gsd", "STATE.md"), "# State\n");
47
+ run("git add .", dir);
48
+ run("git commit -m init", dir);
49
+ run("git branch -M main", dir);
50
+ return dir;
51
+ }
52
+
53
+ function makeRoadmap(
54
+ milestoneId: string,
55
+ title: string,
56
+ slices: Array<{ id: string; title: string }>,
57
+ ): string {
58
+ const sliceLines = slices.map(s => `- [x] **${s.id}: ${s.title}**`).join("\n");
59
+ return `# ${milestoneId}: ${title}\n\n## Slices\n${sliceLines}\n`;
60
+ }
61
+
62
+ function addSliceToMilestone(
63
+ repo: string,
64
+ wtPath: string,
65
+ milestoneId: string,
66
+ sliceId: string,
67
+ sliceTitle: string,
68
+ commits: Array<{ file: string; content: string; message: string }>,
69
+ ): void {
70
+ const normalizedPath = wtPath.replaceAll("\\", "/");
71
+ const marker = "/.gsd/worktrees/";
72
+ const idx = normalizedPath.indexOf(marker);
73
+ const worktreeName = idx !== -1 ? normalizedPath.slice(idx + marker.length).split("/")[0] : null;
74
+
75
+ const sliceBranch = getSliceBranchName(milestoneId, sliceId, worktreeName);
76
+
77
+ run(`git checkout -b ${sliceBranch}`, wtPath);
78
+ for (const c of commits) {
79
+ writeFileSync(join(wtPath, c.file), c.content);
80
+ run("git add .", wtPath);
81
+ run(`git commit -m "${c.message}"`, wtPath);
82
+ }
83
+ run(`git checkout milestone/${milestoneId}`, wtPath);
84
+ mergeSliceToMilestone(repo, milestoneId, sliceId, sliceTitle);
85
+ }
86
+
87
+ async function main(): Promise<void> {
88
+ const savedCwd = process.cwd();
89
+ const tempDirs: string[] = [];
90
+
91
+ try {
92
+ // ================================================================
93
+ // Group 1: Full lifecycle chain
94
+ // ================================================================
95
+ console.log("\n=== Full lifecycle: worktree -> slices -> milestone merge -> main ===");
96
+ {
97
+ const repo = createTempRepo();
98
+ tempDirs.push(repo);
99
+
100
+ // Count commits on main before
101
+ const mainLogBefore = run("git log --oneline main", repo);
102
+ const commitCountBefore = mainLogBefore.split("\n").length;
103
+
104
+ // Create worktree for M001
105
+ const wtPath = createAutoWorktree(repo, "M001");
106
+ tempDirs.push(wtPath);
107
+ assertTrue(existsSync(wtPath), "worktree directory created");
108
+
109
+ // Add two slices with commits
110
+ addSliceToMilestone(repo, wtPath, "M001", "S01", "Add auth", [
111
+ { file: "auth.ts", content: "export const auth = true;\n", message: "feat: add auth" },
112
+ ]);
113
+ addSliceToMilestone(repo, wtPath, "M001", "S02", "Add dashboard", [
114
+ { file: "dash.ts", content: "export const dash = true;\n", message: "feat: add dashboard" },
115
+ ]);
116
+
117
+ // Build roadmap content
118
+ const roadmapContent = makeRoadmap("M001", "First milestone", [
119
+ { id: "S01", title: "Add auth" },
120
+ { id: "S02", title: "Add dashboard" },
121
+ ]);
122
+
123
+ // Merge milestone to main
124
+ process.chdir(wtPath);
125
+ const result = mergeMilestoneToMain(repo, "M001", roadmapContent);
126
+ process.chdir(savedCwd);
127
+
128
+ // Assert exactly one new commit on main
129
+ const mainLogAfter = run("git log --oneline main", repo);
130
+ const commitCountAfter = mainLogAfter.split("\n").length;
131
+ assertEq(commitCountAfter, commitCountBefore + 1, "exactly one new commit on main");
132
+
133
+ // Commit message contains both slice titles
134
+ const lastCommitMsg = run("git log -1 --format=%B main", repo);
135
+ assertMatch(lastCommitMsg, /Add auth/, "commit message contains S01 title");
136
+ assertMatch(lastCommitMsg, /Add dashboard/, "commit message contains S02 title");
137
+
138
+ // Worktree directory removed
139
+ assertTrue(!existsSync(wtPath), "worktree directory removed after merge");
140
+
141
+ // Milestone branch deleted
142
+ const branches = run("git branch", repo);
143
+ assertTrue(!branches.includes("milestone/M001"), "milestone branch deleted");
144
+ }
145
+
146
+ // ================================================================
147
+ // Group 2: Preference gating (shouldUseWorktreeIsolation)
148
+ // ================================================================
149
+ console.log("\n=== Preference gating ===");
150
+ {
151
+ const repo = createTempRepo();
152
+ tempDirs.push(repo);
153
+
154
+ // Override to branch mode
155
+ const branchResult = shouldUseWorktreeIsolation(repo, { isolation: "branch" });
156
+ assertEq(branchResult, false, "isolation=branch returns false");
157
+
158
+ // Override to worktree mode
159
+ const wtResult = shouldUseWorktreeIsolation(repo, { isolation: "worktree" });
160
+ assertEq(wtResult, true, "isolation=worktree returns true");
161
+
162
+ // Default (no legacy branches) returns true
163
+ const defaultResult = shouldUseWorktreeIsolation(repo);
164
+ assertEq(defaultResult, true, "new project defaults to worktree (true)");
165
+ }
166
+
167
+ // ================================================================
168
+ // Group 3: merge_to_main mode resolution
169
+ // ================================================================
170
+ console.log("\n=== merge_to_main mode ===");
171
+ {
172
+ // getMergeToMainMode reads from loadEffectiveGSDPreferences — test via legacy branch detection
173
+ // Instead, test that the function returns the default "milestone" when no prefs set
174
+ // (Cannot inject overridePrefs — function signature doesn't accept them)
175
+ // We verify the shouldUseWorktreeIsolation override path handles legacy detection
176
+ const repo = createTempRepo();
177
+ tempDirs.push(repo);
178
+
179
+ // Create a legacy gsd/*/* branch to test legacy detection
180
+ run("git checkout -b gsd/M001/S01", repo);
181
+ writeFileSync(join(repo, "legacy.txt"), "legacy\n");
182
+ run("git add .", repo);
183
+ run("git commit -m legacy", repo);
184
+ run("git checkout main", repo);
185
+
186
+ const legacyResult = shouldUseWorktreeIsolation(repo);
187
+ assertEq(legacyResult, false, "legacy gsd branches detected -> branch mode");
188
+
189
+ // Explicit worktree override wins over legacy detection
190
+ const overrideResult = shouldUseWorktreeIsolation(repo, { isolation: "worktree" });
191
+ assertEq(overrideResult, true, "explicit worktree override wins over legacy");
192
+ }
193
+
194
+ // ================================================================
195
+ // Group 4: Self-heal (abortAndReset, withMergeHeal)
196
+ // ================================================================
197
+ console.log("\n=== Self-heal ===");
198
+ {
199
+ // 4a: abortAndReset cleans up MERGE_HEAD
200
+ const repo = createTempRepo();
201
+ tempDirs.push(repo);
202
+
203
+ // Create conflicting branches
204
+ run("git checkout -b feature", repo);
205
+ writeFileSync(join(repo, "conflict.txt"), "feature content\n");
206
+ run("git add .", repo);
207
+ run("git commit -m feature", repo);
208
+ run("git checkout main", repo);
209
+ writeFileSync(join(repo, "conflict.txt"), "main content\n");
210
+ run("git add .", repo);
211
+ run("git commit -m main-change", repo);
212
+
213
+ // Trigger merge conflict
214
+ try { run("git merge feature", repo); } catch { /* expected */ }
215
+ assertTrue(existsSync(join(repo, ".git", "MERGE_HEAD")), "MERGE_HEAD exists before abort");
216
+
217
+ const abortResult = abortAndReset(repo);
218
+ assertTrue(!existsSync(join(repo, ".git", "MERGE_HEAD")), "MERGE_HEAD removed after abort");
219
+ assertTrue(abortResult.cleaned.length > 0, "abortAndReset reports cleaned items");
220
+ }
221
+ {
222
+ // 4b: withMergeHeal throws MergeConflictError for real conflicts
223
+ const repo = createTempRepo();
224
+ tempDirs.push(repo);
225
+
226
+ run("git checkout -b conflict-branch", repo);
227
+ writeFileSync(join(repo, "file.txt"), "branch version\n");
228
+ run("git add .", repo);
229
+ run("git commit -m branch-ver", repo);
230
+ run("git checkout main", repo);
231
+ writeFileSync(join(repo, "file.txt"), "main version\n");
232
+ run("git add .", repo);
233
+ run("git commit -m main-ver", repo);
234
+
235
+ let caughtError: unknown = null;
236
+ try {
237
+ withMergeHeal(repo, () => {
238
+ execSync("git merge conflict-branch", { cwd: repo, stdio: "pipe" });
239
+ });
240
+ } catch (e) {
241
+ caughtError = e;
242
+ }
243
+ assertTrue(caughtError instanceof MergeConflictError, "withMergeHeal throws MergeConflictError");
244
+ if (caughtError instanceof MergeConflictError) {
245
+ assertTrue(caughtError.conflictedFiles.length > 0, "MergeConflictError has conflictedFiles");
246
+ }
247
+ }
248
+
249
+ // ================================================================
250
+ // Group 5: Doctor detects orphaned worktrees
251
+ // Skip on Windows: git worktree path resolution in temp dirs uses
252
+ // UNC/8.3 forms that don't match after normalization.
253
+ // ================================================================
254
+ if (process.platform !== "win32") {
255
+ console.log("\n=== Doctor: orphaned worktree detection ===");
256
+ {
257
+ // Build a repo with a completed milestone
258
+ const repo = createTempRepo();
259
+ tempDirs.push(repo);
260
+
261
+ // Create completed milestone roadmap
262
+ const msDir = join(repo, ".gsd", "milestones", "M001");
263
+ mkdirSync(msDir, { recursive: true });
264
+ writeFileSync(join(msDir, "ROADMAP.md"), `---
265
+ id: M001
266
+ title: "Test Milestone"
267
+ ---
268
+
269
+ # M001: Test Milestone
270
+
271
+ ## Vision
272
+ Test
273
+
274
+ ## Success Criteria
275
+ - Done
276
+
277
+ ## Slices
278
+ - [x] **S01: Test slice** \`risk:low\` \`depends:[]\`
279
+ > After this: done
280
+
281
+ ## Boundary Map
282
+ _None_
283
+ `);
284
+ run("git add -A", repo);
285
+ run("git commit -m \"add milestone\"", repo);
286
+
287
+ // Create orphaned worktree
288
+ mkdirSync(join(repo, ".gsd", "worktrees"), { recursive: true });
289
+ run("git worktree add -b milestone/M001 .gsd/worktrees/M001", repo);
290
+
291
+ // Detect
292
+ const detect = await runGSDDoctor(repo);
293
+ const orphanIssues = detect.issues.filter(i => i.code === "orphaned_auto_worktree");
294
+ assertTrue(orphanIssues.length > 0, "doctor detects orphaned worktree");
295
+ assertEq(orphanIssues[0]?.unitId, "M001", "orphaned worktree unitId is M001");
296
+
297
+ // Fix
298
+ const fixed = await runGSDDoctor(repo, { fix: true });
299
+ assertTrue(
300
+ fixed.fixesApplied.some(f => f.includes("removed orphaned worktree")),
301
+ "doctor fix removes orphaned worktree",
302
+ );
303
+
304
+ // Verify gone
305
+ const wtList = run("git worktree list", repo);
306
+ assertTrue(!wtList.includes("milestone/M001"), "worktree gone after doctor fix");
307
+ }
308
+ } else {
309
+ console.log("\n=== Doctor: orphaned worktree detection (skipped on Windows) ===");
310
+ }
311
+ } finally {
312
+ process.chdir(savedCwd);
313
+ for (const d of tempDirs) {
314
+ try { rmSync(d, { recursive: true, force: true }); } catch { /* ignore */ }
315
+ }
316
+ }
317
+
318
+ report();
319
+ }
320
+
321
+ main();
@@ -17,7 +17,7 @@
17
17
 
18
18
  import { existsSync, mkdirSync, realpathSync } from "node:fs";
19
19
  import { execSync } from "node:child_process";
20
- import { join, resolve } from "node:path";
20
+ import { join, resolve, sep } from "node:path";
21
21
 
22
22
  // ─── Types ─────────────────────────────────────────────────────────────────
23
23
 
@@ -120,15 +120,17 @@ export function worktreeBranchName(name: string): string {
120
120
  /**
121
121
  * Create a new git worktree under .gsd/worktrees/<name>/ with branch worktree/<name>.
122
122
  * The branch is created from the current HEAD of the main branch.
123
+ *
124
+ * @param opts.branch — override the default `worktree/<name>` branch name
123
125
  */
124
- export function createWorktree(basePath: string, name: string): WorktreeInfo {
126
+ export function createWorktree(basePath: string, name: string, opts: { branch?: string } = {}): WorktreeInfo {
125
127
  // Validate name: alphanumeric, hyphens, underscores only
126
128
  if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
127
129
  throw new Error(`Invalid worktree name "${name}". Use only letters, numbers, hyphens, and underscores.`);
128
130
  }
129
131
 
130
132
  const wtPath = worktreePath(basePath, name);
131
- const branch = worktreeBranchName(name);
133
+ const branch = opts.branch ?? worktreeBranchName(name);
132
134
 
133
135
  if (existsSync(wtPath)) {
134
136
  throw new Error(`Worktree "${name}" already exists at ${wtPath}`);
@@ -211,7 +213,11 @@ export function listWorktrees(basePath: string): WorktreeInfo[] {
211
213
 
212
214
  const entryPath = wtLine.replace("worktree ", "");
213
215
  const branch = branchLine.replace("branch refs/heads/", "");
214
- const branchWorktreeName = branch.startsWith("worktree/") ? branch.slice("worktree/".length) : null;
216
+ const branchWorktreeName = branch.startsWith("worktree/")
217
+ ? branch.slice("worktree/".length)
218
+ : branch.startsWith("milestone/")
219
+ ? branch.slice("milestone/".length)
220
+ : null;
215
221
  const entryVariants = [resolve(entryPath)];
216
222
  if (existsSync(entryPath)) {
217
223
  entryVariants.push(realpathSync(entryPath));
@@ -260,17 +266,17 @@ export function listWorktrees(basePath: string): WorktreeInfo[] {
260
266
  export function removeWorktree(
261
267
  basePath: string,
262
268
  name: string,
263
- opts: { deleteBranch?: boolean; force?: boolean } = {},
269
+ opts: { deleteBranch?: boolean; force?: boolean; branch?: string } = {},
264
270
  ): void {
265
271
  const wtPath = worktreePath(basePath, name);
266
272
  const resolvedWtPath = existsSync(wtPath) ? realpathSync(wtPath) : wtPath;
267
- const branch = worktreeBranchName(name);
273
+ const branch = opts.branch ?? worktreeBranchName(name);
268
274
  const { deleteBranch = true, force = false } = opts;
269
275
 
270
276
  // If we're inside the worktree, move out first — git can't remove an in-use directory
271
277
  const cwd = process.cwd();
272
278
  const resolvedCwd = existsSync(cwd) ? realpathSync(cwd) : cwd;
273
- if (resolvedCwd === resolvedWtPath || resolvedCwd.startsWith(resolvedWtPath + "/")) {
279
+ if (resolvedCwd === resolvedWtPath || resolvedCwd.startsWith(resolvedWtPath + sep)) {
274
280
  process.chdir(basePath);
275
281
  }
276
282
 
@@ -105,16 +105,21 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
105
105
  const payload = event.payload as Record<string, unknown>;
106
106
  if (!payload) return;
107
107
 
108
- // Detect Anthropic provider. Prefer the model_select flag when available,
109
- // but fall back to checking the model name in the payload. model_select
110
- // may not fire when the session restores with the same model already set
111
- // (modelsAreEqual guard in the SDK suppresses the event). When model_select
112
- // HAS fired and said "not Anthropic" (e.g. Copilot serving claude-*),
113
- // respect that don't override with model name heuristic.
114
- const modelName = typeof payload.model === "string" ? payload.model : "";
115
- const isAnthropic = modelSelectFired
116
- ? isAnthropicProvider
117
- : modelName.startsWith("claude-");
108
+ // Detect Anthropic provider. Use the model object from the event (most
109
+ // reliable comes directly from the resolved Model), then fall back to
110
+ // the model_select flag, then to the model name heuristic (last resort).
111
+ // The model name heuristic is needed for session restores where
112
+ // modelsAreEqual suppresses model_select AND the SDK doesn't pass model.
113
+ const eventModel = event.model as { provider: string } | undefined;
114
+ let isAnthropic: boolean;
115
+ if (eventModel?.provider) {
116
+ isAnthropic = eventModel.provider === "anthropic";
117
+ } else if (modelSelectFired) {
118
+ isAnthropic = isAnthropicProvider;
119
+ } else {
120
+ const modelName = typeof payload.model === "string" ? payload.model : "";
121
+ isAnthropic = modelName.startsWith("claude-");
122
+ }
118
123
  if (!isAnthropic) return;
119
124
 
120
125
  // Strip thinking blocks from history to avoid signature validation errors