@gitkraken/core-gitlens 0.24.0 → 0.26.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.
Files changed (176) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +23 -15
  3. package/dist/git/errors.d.ts +6 -5
  4. package/dist/git/errors.js +7 -6
  5. package/dist/git/errors.js.map +1 -1
  6. package/dist/git/features.d.ts +1 -1
  7. package/dist/git/features.js +1 -1
  8. package/dist/git/features.js.map +1 -1
  9. package/dist/git/models/remoteProvider.d.ts +4 -1
  10. package/dist/git/models/remoteProvider.js.map +1 -1
  11. package/dist/git/providers/operations.d.ts +15 -0
  12. package/dist/git/providers/provider.d.ts +4 -2
  13. package/dist/git/utils/branch.utils.d.ts +4 -1
  14. package/dist/git/utils/branch.utils.js +5 -2
  15. package/dist/git/utils/branch.utils.js.map +1 -1
  16. package/dist/git/utils/mergeConflicts.utils.js +2 -2
  17. package/dist/git/utils/mergeConflicts.utils.js.map +1 -1
  18. package/dist/git/watching/worktreeSummary.d.ts +15 -3
  19. package/dist/git/watching/worktreeSummary.js +179 -31
  20. package/dist/git/watching/worktreeSummary.js.map +1 -1
  21. package/dist/git-cli/cliGitProvider.d.ts +30 -3
  22. package/dist/git-cli/cliGitProvider.js +102 -40
  23. package/dist/git-cli/cliGitProvider.js.map +1 -1
  24. package/dist/git-cli/exec/git.d.ts +9 -1
  25. package/dist/git-cli/exec/git.js +23 -4
  26. package/dist/git-cli/exec/git.js.map +1 -1
  27. package/dist/git-cli/exec/gitDirResolver.d.ts +26 -0
  28. package/dist/git-cli/exec/gitDirResolver.js +181 -0
  29. package/dist/git-cli/exec/gitDirResolver.js.map +1 -0
  30. package/dist/git-cli/exec/headState.js +3 -3
  31. package/dist/git-cli/exec/headState.js.map +1 -1
  32. package/dist/git-cli/exec/indexRefresh.d.ts +78 -0
  33. package/dist/git-cli/exec/indexRefresh.js +159 -0
  34. package/dist/git-cli/exec/indexRefresh.js.map +1 -0
  35. package/dist/git-cli/providers/branchRemote.js +2 -5
  36. package/dist/git-cli/providers/branchRemote.js.map +1 -1
  37. package/dist/git-cli/providers/branches.js +4 -4
  38. package/dist/git-cli/providers/branches.js.map +1 -1
  39. package/dist/git-cli/providers/config.d.ts +2 -0
  40. package/dist/git-cli/providers/config.js +26 -4
  41. package/dist/git-cli/providers/config.js.map +1 -1
  42. package/dist/git-cli/providers/maintenance.js +1 -1
  43. package/dist/git-cli/providers/operations.d.ts +4 -1
  44. package/dist/git-cli/providers/operations.js +33 -25
  45. package/dist/git-cli/providers/operations.js.map +1 -1
  46. package/dist/git-cli/providers/pausedOperations.d.ts +3 -1
  47. package/dist/git-cli/providers/pausedOperations.js +10 -7
  48. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  49. package/dist/git-cli/providers/refs.js +25 -1
  50. package/dist/git-cli/providers/refs.js.map +1 -1
  51. package/dist/git-cli/providers/staging.js +1 -1
  52. package/dist/git-cli/providers/stash.d.ts +3 -1
  53. package/dist/git-cli/providers/stash.js +8 -5
  54. package/dist/git-cli/providers/stash.js.map +1 -1
  55. package/dist/git-cli/providers/worktrees.d.ts +6 -1
  56. package/dist/git-cli/providers/worktrees.js +6 -2
  57. package/dist/git-cli/providers/worktrees.js.map +1 -1
  58. package/dist/git-cli/service.d.ts +2 -0
  59. package/dist/git-cli/service.js +2 -1
  60. package/dist/git-cli/service.js.map +1 -1
  61. package/dist/ipc/discovery.js +1 -1
  62. package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
  63. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  64. package/dist/plus/ai/errors.d.ts +4 -1
  65. package/dist/plus/ai/errors.js +4 -0
  66. package/dist/plus/ai/errors.js.map +1 -1
  67. package/dist/plus/ai/providers/mistralProvider.js +1 -1
  68. package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
  69. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +2 -1
  70. package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
  71. package/dist/plus/git-github/api/github.js +12 -13
  72. package/dist/plus/git-github/api/github.js.map +1 -1
  73. package/dist/plus/git-github/api/token.d.ts +2 -0
  74. package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +1 -1
  75. package/dist/plus/integrations/authentication/cloudIntegrationService.js +6 -1
  76. package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -1
  77. package/dist/plus/integrations/authentication/configuredIntegrationService.js +2 -2
  78. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +18 -5
  79. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -1
  80. package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +3 -1
  81. package/dist/plus/integrations/authentication/integrationAuthenticationService.js +4 -0
  82. package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -1
  83. package/dist/plus/integrations/authentication/models.d.ts +2 -0
  84. package/dist/plus/integrations/authentication/models.js +2 -1
  85. package/dist/plus/integrations/authentication/models.js.map +1 -1
  86. package/dist/plus/integrations/authentication/observedAuthenticationProvider.js +1 -1
  87. package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts +5 -4
  88. package/dist/plus/integrations/authentication/rejectedTokenTracker.js +5 -4
  89. package/dist/plus/integrations/authentication/rejectedTokenTracker.js.map +1 -1
  90. package/dist/plus/integrations/index.d.ts +2 -2
  91. package/dist/plus/integrations/index.js +1 -1
  92. package/dist/plus/integrations/index.js.map +1 -1
  93. package/dist/plus/integrations/integrationService.d.ts +6 -0
  94. package/dist/plus/integrations/integrationService.js +3 -3
  95. package/dist/plus/integrations/integrationService.js.map +1 -1
  96. package/dist/plus/integrations/lite.js +1 -1
  97. package/dist/plus/integrations/manager.d.ts +51 -3
  98. package/dist/plus/integrations/models/gitHostIntegration.d.ts +14 -0
  99. package/dist/plus/integrations/models/gitHostIntegration.js +50 -41
  100. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  101. package/dist/plus/integrations/models/integration.d.ts +27 -5
  102. package/dist/plus/integrations/models/integration.js +57 -15
  103. package/dist/plus/integrations/models/integration.js.map +1 -1
  104. package/dist/plus/integrations/models/issueReads.d.ts +6 -0
  105. package/dist/plus/integrations/models/issuesIntegration.js +11 -11
  106. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  107. package/dist/plus/integrations/providers/azure/azure.js +2 -2
  108. package/dist/plus/integrations/providers/azureDevOps.js +1 -1
  109. package/dist/plus/integrations/providers/azureDevOpsServer.js +1 -1
  110. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +1 -1
  111. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  112. package/dist/plus/integrations/providers/bitbucket-server.js +1 -1
  113. package/dist/plus/integrations/providers/bitbucket.js +1 -1
  114. package/dist/plus/integrations/providers/github.js +1 -1
  115. package/dist/plus/integrations/providers/gitlab/gitlab.js +2 -2
  116. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  117. package/dist/plus/integrations/providers/gitlab.js +1 -1
  118. package/dist/plus/integrations/providers/jira-server.js +2 -2
  119. package/dist/plus/integrations/providers/jira.js +1 -1
  120. package/dist/plus/integrations/providers/linear.js +2 -2
  121. package/dist/plus/integrations/providers/linear.js.map +1 -1
  122. package/dist/plus/integrations/providers/models.js +2 -1
  123. package/dist/plus/integrations/providers/models.js.map +1 -1
  124. package/dist/plus/integrations/providers/providerErrors.js +9 -0
  125. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  126. package/dist/plus/integrations/providers/providersApi.js +1 -1
  127. package/dist/plus/integrations/providers/trello.js +1 -1
  128. package/dist/plus/integrations/providers/utils.js +1 -1
  129. package/dist/plus/integrations/reads/counts.js +1 -1
  130. package/dist/plus/integrations/reads/cursors.js +1 -1
  131. package/dist/plus/integrations/reads/drains.d.ts +5 -4
  132. package/dist/plus/integrations/reads/drains.js +28 -8
  133. package/dist/plus/integrations/reads/drains.js.map +1 -1
  134. package/dist/plus/integrations/reads/hierarchy.js +1 -1
  135. package/dist/plus/integrations/reads/issueBatch.d.ts +1 -1
  136. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  137. package/dist/plus/integrations/reads/issueTracker.js +1 -1
  138. package/dist/plus/integrations/reads/issues.d.ts +2 -0
  139. package/dist/plus/integrations/reads/issues.js +5 -3
  140. package/dist/plus/integrations/reads/issues.js.map +1 -1
  141. package/dist/plus/integrations/reads/paging.d.ts +5 -6
  142. package/dist/plus/integrations/reads/paging.js +5 -6
  143. package/dist/plus/integrations/reads/paging.js.map +1 -1
  144. package/dist/plus/integrations/reads/pullRequests.d.ts +4 -0
  145. package/dist/plus/integrations/reads/pullRequests.js +4 -2
  146. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  147. package/dist/plus/integrations/reads/resolveRepository.js +1 -1
  148. package/dist/plus/integrations/reads/sweeps.js +42 -10
  149. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  150. package/dist/plus/integrations/results.d.ts +23 -3
  151. package/dist/plus/integrations/results.js.map +1 -1
  152. package/dist/plus/notifications/api.d.ts +20 -0
  153. package/dist/plus/notifications/api.js +89 -0
  154. package/dist/plus/notifications/api.js.map +1 -0
  155. package/dist/plus/notifications/context.d.ts +28 -0
  156. package/dist/plus/notifications/context.js +0 -0
  157. package/dist/plus/notifications/errors.d.ts +13 -0
  158. package/dist/plus/notifications/errors.js +22 -0
  159. package/dist/plus/notifications/errors.js.map +1 -0
  160. package/dist/plus/notifications/models.d.ts +94 -0
  161. package/dist/plus/notifications/models.js +0 -0
  162. package/dist/plus/notifications/notificationsService.d.ts +115 -0
  163. package/dist/plus/notifications/notificationsService.js +381 -0
  164. package/dist/plus/notifications/notificationsService.js.map +1 -0
  165. package/dist/plus/notifications/parse.d.ts +12 -0
  166. package/dist/plus/notifications/parse.js +152 -0
  167. package/dist/plus/notifications/parse.js.map +1 -0
  168. package/dist/plus/notifications/presentation.d.ts +28 -0
  169. package/dist/plus/notifications/presentation.js +55 -0
  170. package/dist/plus/notifications/presentation.js.map +1 -0
  171. package/dist/utils/env/node/exec.d.ts +10 -1
  172. package/dist/utils/env/node/exec.js +41 -4
  173. package/dist/utils/env/node/exec.js.map +1 -1
  174. package/docs/integrations.md +21 -2
  175. package/docs/notifications.md +239 -0
  176. package/package.json +21 -1
@@ -1 +1 @@
1
- {"version":3,"file":"claudeCodeTranscript.js","names":["readdir"],"sources":["../../../../../plus/agents/src/providers/claudeCodeTranscript.ts"],"sourcesContent":["import { createReadStream } from 'fs';\nimport { open, readdir, readFile, stat } from 'fs/promises';\nimport { homedir } from 'os';\nimport { basename, dirname, join } from 'path';\nimport { createInterface } from 'readline';\n\nexport interface TranscriptTitles {\n\tcustom?: string;\n\tai?: string;\n\tagent?: string;\n}\n\n/** A transcript file discovered by {@link ClaudeCodeTranscriptReader.listSessions}, before its\n * contents are read — everything here comes from `readdir` + `stat`. */\nexport interface TranscriptSessionEntry {\n\treadonly sessionId: string;\n\treadonly path: string;\n\treadonly lastActivityMs: number;\n\treadonly size: number;\n}\n\n/** A {@link TranscriptSessionEntry} enriched with the fields recovered from the file's head/tail. */\nexport interface TranscriptSessionSummary extends TranscriptSessionEntry {\n\treadonly titles: TranscriptTitles;\n\treadonly lastPrompt?: string;\n}\n\nexport interface TranscriptSessionListing {\n\treadonly sessions: TranscriptSessionSummary[];\n\t/** Every transcript in the directory, not just the summarized slice — drives \"Showing N of M\". */\n\treadonly total: number;\n}\n\n/** A transcript found outside the queried cwd's project directory, plus the directory Claude\n * actually uses to resolve it for `--resume`. */\nexport interface ResumableTranscriptSessionSummary extends TranscriptSessionSummary {\n\treadonly cwd: string;\n}\n\nexport interface ResumableTranscriptSessionListing {\n\treadonly sessions: ResumableTranscriptSessionSummary[];\n\treadonly total: number;\n}\n\nexport interface EndedTranscriptDetails {\n\ttitles: TranscriptTitles;\n\tfirstPrompt?: string;\n\tlastPrompt?: string;\n}\n\ninterface CacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tnextOffset: number;\n\ttitles: TranscriptTitles;\n}\n\ninterface ListingCacheEntry {\n\tentries: TranscriptSessionEntry[];\n\tresolvedAt: number;\n}\n\ninterface NameCacheEntry {\n\tnames: string[];\n\tresolvedAt: number;\n}\n\ninterface SummaryCacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tsummary: TranscriptSessionSummary;\n}\n\ninterface TitleEntry {\n\ttype: string;\n\tsessionId?: string;\n\tcustomTitle?: string;\n\taiTitle?: string;\n\tagentName?: string;\n\tlastPrompt?: string;\n}\n\n/** Bytes read from each end of a transcript when summarizing. Titles and prompts cluster at both\n * extremes, and files run to tens of MB, so reading whole files is not an option. */\nconst summaryWindowSize = 64 * 1024;\n/** Listings are cheap (readdir + stat) but re-run per panel/sheet open; a short TTL absorbs bursts.\n * Time-based rather than dir-mtime-based because appends move file mtimes without touching the dir. */\nconst listingCacheTtlMs = 10 * 1000;\n/** Summaries are keyed by session, and a busy project has hundreds — unlike the per-live-session\n * title cache, this needs a ceiling. Sized to hold a whole junk-heavy store: the listing scan runs\n * to exhaustion, and each entry is tiny (a path plus a few short strings), so a cap below the\n * store's size would evict-and-re-read the junk tail on every listing instead of paying its\n * windowed reads once. */\nconst summaryCacheLimit = 1000;\nconst defaultListLimit = 50;\n\n/**\n * Reads Claude Code transcript JSONL files at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`\n * to surface `custom-title`, `ai-title`, and `agent-name` entries as fallback session names.\n *\n * Maintains a per-session cache keyed by mtime + last-read byte offset, so repeated calls only\n * read the appended tail. Last occurrence per type wins.\n *\n * `resolve` is async, so a `forget` (or a newer `resolve`) for the same session can land between\n * its `await`s. A per-session generation counter is stamped at entry and re-checked before the\n * final cache write, so a stale `resolve` can't resurrect a forgotten entry or clobber a newer\n * resolve's result.\n */\nexport class ClaudeCodeTranscriptReader {\n\tprivate readonly _cache = new Map<string, CacheEntry>();\n\tprivate readonly _generations = new Map<string, number>();\n\tprivate readonly _listings = new Map<string, ListingCacheEntry>();\n\t/** Transcript file names per project directory — the readdir behind both the plain listing and\n\t * the by-id scan, which sweeps every project directory. */\n\tprivate readonly _names = new Map<string, NameCacheEntry>();\n\t/** Project directories under {@link getProjectsRoot}, re-scanned by every by-id recovery. */\n\tprivate _projectDirs: { dirs: string[]; resolvedAt: number } | undefined;\n\t/** Insertion-ordered LRU — re-inserted on hit, oldest key evicted past {@link summaryCacheLimit}. */\n\tprivate readonly _summaries = new Map<string, SummaryCacheEntry>();\n\t/** Resolved project path per transcript path, bounded like {@link _summaries}. */\n\tprivate readonly _transcriptCwds = new Map<string, string>();\n\tprivate _nextGen = 0;\n\t/** Bumped by {@link invalidateListings}; async listing scans capture it at entry and skip their\n\t * cache write when it moved, so a stale in-flight result can't re-seed a just-cleared cache. */\n\tprivate _listingGen = 0;\n\n\t/** Drops the directory-listing caches (entries, names, project dirs) so the next listing re-reads\n\t * the store. Called when a session ends or is removed: its transcript may have appeared inside\n\t * the TTL window, and a cached listing would hide the new file from the very fetch meant to\n\t * surface it. Summary and cwd caches are keyed by file identity and stay valid, so they're kept.\n\t * The generation bump makes any in-flight listing skip its own cache write — otherwise a scan\n\t * started before the invalidation would re-seed the caches with pre-end results, freshly\n\t * timestamped (same discipline as `resolve`'s per-session generations). */\n\tinvalidateListings(): void {\n\t\tthis._listingGen++;\n\t\tthis._listings.clear();\n\t\tthis._names.clear();\n\t\tthis._projectDirs = undefined;\n\t}\n\n\tasync resolve(sessionId: string, cwd: string | undefined): Promise<TranscriptTitles | undefined> {\n\t\tconst gen = ++this._nextGen;\n\t\tthis._generations.set(sessionId, gen);\n\n\t\tconst cached = this._cache.get(sessionId);\n\t\tconst path = cached?.path ?? (await this.locateTranscript(sessionId, cwd));\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch {\n\t\t\t// File disappeared; drop any stale cache entry so a future call retries discovery.\n\t\t\t// Skip the delete if a newer resolve/forget claimed the slot — their state is fresher.\n\t\t\tif (this._generations.get(sessionId) === gen) {\n\t\t\t\tthis._cache.delete(sessionId);\n\t\t\t\tthis._generations.delete(sessionId);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (stats.mtimeMs === cached?.mtimeMs && stats.size === cached?.size) {\n\t\t\treturn cached.titles;\n\t\t}\n\n\t\t// Truncation or rewrite: stat shrank below our read cursor or mtime moved backward → full re-scan.\n\t\tconst startFromScratch = cached == null || stats.size < cached.nextOffset || stats.mtimeMs < cached.mtimeMs;\n\t\tconst startOffset = startFromScratch ? 0 : cached.nextOffset;\n\t\tconst baseTitles: TranscriptTitles = startFromScratch ? {} : { ...cached.titles };\n\n\t\tconst { titles, consumedEnd } =\n\t\t\tstats.size > startOffset\n\t\t\t\t? await this.readRange(path, startOffset, stats.size, sessionId, baseTitles)\n\t\t\t\t: { titles: baseTitles, consumedEnd: startOffset };\n\n\t\t// If `forget` ran or a newer `resolve` started while we were awaiting, skip the cache write\n\t\t// — the entry we'd be writing reflects stale state.\n\t\tif (this._generations.get(sessionId) !== gen) return titles;\n\n\t\tconst entry: CacheEntry = {\n\t\t\tpath: path,\n\t\t\tmtimeMs: stats.mtimeMs,\n\t\t\tsize: stats.size,\n\t\t\tnextOffset: consumedEnd,\n\t\t\ttitles: titles,\n\t\t};\n\t\tthis._cache.set(sessionId, entry);\n\t\treturn titles;\n\t}\n\n\tforget(sessionId: string): void {\n\t\tthis._cache.delete(sessionId);\n\t\tthis._generations.delete(sessionId);\n\t}\n\n\t/**\n\t * One-shot read for an *ended* (terminal, static) session: extracts the transcript titles plus\n\t * the first and last user prompts. Unlike {@link resolve}, it bypasses the tail cache — a finished\n\t * transcript never grows, so it's read once on demand (when an ended row is opened) and the\n\t * result applied to the session. `last-prompt` entries carry the prompt as of that point; the first\n\t * one bearing a value is the session's opening prompt, the last its most recent. Returns `undefined`\n\t * when no transcript is found (archived/purged) — the caller keeps whatever durable-store fields it\n\t * already has.\n\t *\n\t * Streams the file line-by-line rather than buffering it whole: a finished transcript can run to\n\t * tens of MB, but this keeps only one line resident at a time. A *full* scan (not a head/tail\n\t * window) is required for correctness — when the first turn is large the opening `last-prompt` sits\n\t * past any fixed head window, and a windowed read would then mistake a later prompt for the first,\n\t * poisoning the derived session name.\n\t */\n\tasync resolveEndedDetails(sessionId: string, cwd: string | undefined): Promise<EndedTranscriptDetails | undefined> {\n\t\tconst path = await this.locateTranscript(sessionId, cwd);\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch (ex) {\n\t\t\t// A transcript that vanished is terminal; an I/O or permission failure must propagate so\n\t\t\t// the caller retries rather than caching this session as permanently unresolvable.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\t\tif (stats.size === 0) return undefined;\n\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet firstPrompt: string | undefined;\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst rl = createInterface({ input: createReadStream(path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t\tif (!line.includes('last-prompt')) continue;\n\n\t\t\t\tconst entry = parseSummaryLine(line, sessionId);\n\t\t\t\tif (entry?.type === 'last-prompt' && entry.lastPrompt != null && entry.lastPrompt.length > 0) {\n\t\t\t\t\tfirstPrompt ??= entry.lastPrompt;\n\t\t\t\t\tlastPrompt = entry.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn { titles: titles, firstPrompt: firstPrompt, lastPrompt: lastPrompt };\n\t}\n\n\t/**\n\t * Lists the transcripts of sessions whose working directory is `cwd`, most-recently-active first,\n\t * summarizing until `limit` summarizable transcripts are found — not just the first `limit` on\n\t * disk, since junk transcripts (dropped below) would otherwise starve the result.\n\t * `excludeSessionIds` is skipped before `limit` applies, and excluded entries are also dropped\n\t * from `total` — they're already shown elsewhere (e.g. as live sessions), so a caller's \"N of M\"\n\t * count must not double-count them.\n\t *\n\t * Claude homes a transcript under the directory encoding the cwd from which the session started.\n\t * That directory is authoritative for where `claude --resume <id>` can find the transcript, but\n\t * Claude does not reliably move it when the session later changes worktrees. Callers that have a\n\t * durable session-to-worktree association can recover those moved sessions with\n\t * {@link listSessionsByIds}; this method intentionally remains the cheap legacy directory listing.\n\t *\n\t * Discovery (readdir + stat) covers the whole directory and is cheap; summarizing is not, so it's\n\t * capped. Entries whose summary yields neither a title nor a prompt are dropped — those are aborted\n\t * or empty transcripts with nothing to show or search on.\n\t */\n\tasync listSessions(\n\t\tcwd: string,\n\t\toptions?: { limit?: number; excludeSessionIds?: ReadonlySet<string> },\n\t): Promise<TranscriptSessionListing> {\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir == null) return { sessions: [], total: 0 };\n\n\t\tconst entries = await this.listEntries(dir);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst exclude = options?.excludeSessionIds;\n\t\t// Zero-byte transcripts can never summarize — skip the read entirely and keep them out of\n\t\t// `total`, which otherwise counts sessions no `limit` could ever surface.\n\t\tconst candidates = entries.filter(e => e.size > 0 && exclude?.has(e.sessionId) !== true);\n\t\tconst { sessions, exhausted } = await this.collectSessions(candidates, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\treturn summary != null && hasSummaryContent(summary) ? summary : undefined;\n\t\t});\n\n\t\t// An exhausted scan proved the exact valid count; otherwise unscanned candidates keep\n\t\t// `total` an upper bound so paging knows more MAY exist.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : candidates.length };\n\t}\n\n\t/**\n\t * Finds a known set of session ids across every Claude project directory except `excludeCwd`'s.\n\t * This is the recovery path for durable CLI records whose worktree changed after Claude chose the\n\t * transcript's project directory. The caller supplies the association; transcript contents alone\n\t * are deliberately not used to guess which current worktree owns an arbitrary old session.\n\t *\n\t * Each result carries the project path encoded by the directory that actually holds the transcript,\n\t * so resuming it does not repeat discovery from the wrong worktree. Duplicate ids are counted once,\n\t * with the newest transcript winning.\n\t */\n\tasync listSessionsByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\toptions?: { limit?: number; excludeCwd?: string },\n\t): Promise<ResumableTranscriptSessionListing> {\n\t\tif (sessionIds.size === 0) return { sessions: [], total: 0 };\n\n\t\tconst excludeDir = await this.resolveProjectDir(options?.excludeCwd);\n\t\t// Same zero-byte drop as `listSessions` — see the rationale there.\n\t\tconst entries = (await this.listEntriesByIds(sessionIds, excludeDir)).filter(e => e.size > 0);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst { sessions, exhausted } = await this.collectSessions(entries, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\tif (summary == null || !hasSummaryContent(summary)) return undefined;\n\n\t\t\tconst cwd = await this.resolveTranscriptCwd(entry);\n\t\t\treturn cwd != null ? { ...summary, cwd: cwd } : undefined;\n\t\t});\n\n\t\t// Exhausted scans prove the exact count — see `listSessions`.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : entries.length };\n\t}\n\n\t/** Discovers every transcript in `dir`, newest first. The directory also holds one sibling\n\t * subdirectory per session (`<uuid>/subagents`, `<uuid>/tool-results`), so entries must be filtered\n\t * to files — a busy project has roughly as many subdirectories as transcripts. */\n\tprivate async listEntries(dir: string): Promise<TranscriptSessionEntry[]> {\n\t\tconst cached = this._listings.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.entries;\n\n\t\tconst gen = this._listingGen;\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await this.listTranscriptNames(dir);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = (await this.statEntries(dir, names)).sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._listings.set(dir, { entries: entries, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Stats `names` in `dir`, dropping the ones that failed — a transcript can vanish (or be\n\t * archived) between the readdir and the stat. Unsorted; callers order as they need.\n\t * Protected (like {@link readSummary}) so tests can interleave against the listing flow. */\n\tprotected async statEntries(dir: string, names: string[]): Promise<TranscriptSessionEntry[]> {\n\t\tconst settled = await Promise.allSettled(\n\t\t\tnames.map(async (name): Promise<TranscriptSessionEntry> => {\n\t\t\t\tconst path = join(dir, name);\n\t\t\t\tconst stats = await stat(path);\n\t\t\t\treturn {\n\t\t\t\t\tsessionId: basename(name, '.jsonl'),\n\t\t\t\t\tpath: path,\n\t\t\t\t\tlastActivityMs: stats.mtimeMs,\n\t\t\t\t\tsize: stats.size,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\n\t\treturn settled.filter(r => r.status === 'fulfilled').map(r => r.value);\n\t}\n\n\t/** The `.jsonl` file names in `dir`, cached for {@link listingCacheTtlMs}. Failures propagate\n\t * uncached: an unreadable directory means different things to different callers, and the next\n\t * call must be free to get a real answer. */\n\tprivate async listTranscriptNames(dir: string): Promise<string[]> {\n\t\tconst cached = this._names.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.names;\n\n\t\tconst gen = this._listingGen;\n\t\tconst dirents = await readdir(dir, { withFileTypes: true });\n\t\tconst names = dirents.filter(d => d.isFile() && d.name.endsWith('.jsonl')).map(d => d.name);\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._names.set(dir, { names: names, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn names;\n\t}\n\n\t/** Every project directory under the projects root, cached for {@link listingCacheTtlMs} — a\n\t * by-id recovery sweeps all of them, once per queried worktree. */\n\tprivate async listProjectDirs(): Promise<string[]> {\n\t\tconst cached = this._projectDirs;\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.dirs;\n\n\t\tconst gen = this._listingGen;\n\t\tconst root = this.getProjectsRoot();\n\t\tconst dirents = await readdir(root, { withFileTypes: true });\n\t\tconst dirs = dirents.filter(d => d.isDirectory()).map(d => join(root, d.name));\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._projectDirs = { dirs: dirs, resolvedAt: Date.now() };\n\t\t}\n\n\t\treturn dirs;\n\t}\n\n\t/** Scans project directory names once, then stats only requested transcript ids. */\n\tprivate async listEntriesByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\texcludeDir: string | undefined,\n\t): Promise<TranscriptSessionEntry[]> {\n\t\tlet idsToFind = sessionIds;\n\t\tif (excludeDir != null) {\n\t\t\ttry {\n\t\t\t\tconst names = await this.listTranscriptNames(excludeDir);\n\t\t\t\tconst idsInExcludedDir = new Set(names.map(name => basename(name, '.jsonl')));\n\t\t\t\tidsToFind = new Set([...sessionIds].filter(id => !idsInExcludedDir.has(id)));\n\t\t\t\tif (idsToFind.size === 0) return [];\n\t\t\t} catch {\n\t\t\t\t// If the exact directory can't be inspected, don't risk returning a duplicate from a\n\t\t\t\t// stale transcript copy elsewhere. The ordinary listing still owns the exact path.\n\t\t\t\treturn [];\n\t\t\t}\n\t\t}\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await this.listProjectDirs();\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn [];\n\t\t}\n\n\t\tconst settledDirs = await Promise.allSettled(\n\t\t\tdirs.map(async dir => {\n\t\t\t\tif (dir === excludeDir) return [];\n\n\t\t\t\tconst names = (await this.listTranscriptNames(dir)).filter(name =>\n\t\t\t\t\tidsToFind.has(basename(name, '.jsonl')),\n\t\t\t\t);\n\t\t\t\treturn this.statEntries(dir, names);\n\t\t\t}),\n\t\t);\n\n\t\tconst bySessionId = new Map<string, TranscriptSessionEntry>();\n\t\tfor (const settledDir of settledDirs) {\n\t\t\tif (settledDir.status !== 'fulfilled') continue;\n\n\t\t\tfor (const entry of settledDir.value) {\n\t\t\t\tconst existing = bySessionId.get(entry.sessionId);\n\t\t\t\tif (existing == null || entry.lastActivityMs > existing.lastActivityMs) {\n\t\t\t\t\tbySessionId.set(entry.sessionId, entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn [...bySessionId.values()].sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\t}\n\n\t/** `exhausted` reports whether every candidate was scanned — an exhausted scan has proven the\n\t * exact valid count, so callers can report it instead of an upper bound that would keep a\n\t * \"Show More\" affordance alive with nothing left to show. */\n\tprivate async collectSessions<T>(\n\t\tcandidates: readonly TranscriptSessionEntry[],\n\t\tlimit: number,\n\t\tresolve: (entry: TranscriptSessionEntry) => Promise<T | undefined>,\n\t): Promise<{ sessions: T[]; exhausted: boolean }> {\n\t\tconst sessions: T[] = [];\n\t\tconst pushResolved = (settled: PromiseSettledResult<T | undefined>[]): void => {\n\t\t\tfor (const result of settled) {\n\t\t\t\tif (result.status !== 'fulfilled' || result.value == null) continue;\n\n\t\t\t\tsessions.push(result.value);\n\t\t\t}\n\t\t};\n\n\t\t// limit <= 0 means \"no ceiling\" — resolve every candidate.\n\t\tif (limit <= 0) {\n\t\t\tpushResolved(await Promise.allSettled(candidates.map(resolve)));\n\t\t\treturn { sessions: sessions, exhausted: true };\n\t\t}\n\n\t\t// Scans until `limit` results or the candidates run out — a fixed scan ceiling would let a run\n\t\t// of junk transcripts hide every older real session behind it (and the picker then claims there\n\t\t// are none at all). The cost stays bounded: callers pre-drop zero-byte candidates read-free, and\n\t\t// a junk summary is windowed, tiny, and LRU-cached, so the sweep is paid in syscalls, once.\n\t\tlet cursor = 0;\n\t\twhile (sessions.length < limit && cursor < candidates.length) {\n\t\t\t// Batched by remaining need so a hit-rich head stops the scan early instead of resolving\n\t\t\t// every candidate up front.\n\t\t\tconst batch = candidates.slice(cursor, cursor + (limit - sessions.length));\n\t\t\tcursor += batch.length;\n\t\t\tpushResolved(await Promise.allSettled(batch.map(resolve)));\n\t\t}\n\n\t\treturn { sessions: sessions, exhausted: cursor >= candidates.length };\n\t}\n\n\t/** Summarizes one transcript, reusing the cached result while its mtime and size are unchanged. */\n\tprivate async resolveSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary | undefined> {\n\t\tconst cached = this._summaries.get(entry.sessionId);\n\t\tif (\n\t\t\tcached != null &&\n\t\t\tcached.path === entry.path &&\n\t\t\tcached.mtimeMs === entry.lastActivityMs &&\n\t\t\tcached.size === entry.size\n\t\t) {\n\t\t\t// Refresh recency for the LRU.\n\t\t\tthis._summaries.delete(entry.sessionId);\n\t\t\tthis._summaries.set(entry.sessionId, cached);\n\t\t\treturn cached.summary;\n\t\t}\n\n\t\tlet summary: TranscriptSessionSummary;\n\t\ttry {\n\t\t\tsummary = await this.readSummary(entry);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis._summaries.set(entry.sessionId, {\n\t\t\tpath: entry.path,\n\t\t\tmtimeMs: entry.lastActivityMs,\n\t\t\tsize: entry.size,\n\t\t\tsummary: summary,\n\t\t});\n\t\tif (this._summaries.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._summaries.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._summaries.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t\treturn summary;\n\t}\n\n\t/** Recovers the project path represented by the transcript's containing directory. A resolved\n\t * answer is cached: it's a line-scan of the whole transcript, and the directory holding a given\n\t * path never changes. A miss isn't cached — a `cwd` line can still land on append. */\n\tprivate async resolveTranscriptCwd(entry: TranscriptSessionEntry): Promise<string | undefined> {\n\t\tconst cached = this._transcriptCwds.get(entry.path);\n\t\tif (cached != null) return cached;\n\n\t\tconst projectDir = dirname(entry.path);\n\t\tconst projectDirName = basename(projectDir);\n\t\tconst matchesProjectDir = (cwd: string): boolean =>\n\t\t\tencodeProjectDirName(cwd).toLowerCase() === projectDirName.toLowerCase();\n\n\t\ttry {\n\t\t\tconst index = JSON.parse(await readFile(join(projectDir, 'sessions-index.json'), 'utf8')) as {\n\t\t\t\tentries?: { sessionId?: unknown; projectPath?: unknown }[];\n\t\t\t};\n\t\t\tif (Array.isArray(index.entries)) {\n\t\t\t\tconst indexed = index.entries.find(item => item?.sessionId === entry.sessionId);\n\t\t\t\tif (typeof indexed?.projectPath === 'string' && matchesProjectDir(indexed.projectPath)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, indexed.projectPath);\n\t\t\t\t\treturn indexed.projectPath;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// The index is optional and can lag or be malformed. The transcript is authoritative below.\n\t\t}\n\n\t\tconst rl = createInterface({ input: createReadStream(entry.path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tif (!line.includes('\"cwd\"')) continue;\n\n\t\t\t\tlet parsed: { cwd?: unknown; sessionId?: unknown };\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(line) as { cwd?: unknown; sessionId?: unknown };\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (parsed.sessionId != null && parsed.sessionId !== entry.sessionId) continue;\n\n\t\t\t\tif (typeof parsed.cwd === 'string' && matchesProjectDir(parsed.cwd)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, parsed.cwd);\n\t\t\t\t\treturn parsed.cwd;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate rememberTranscriptCwd(path: string, cwd: string): void {\n\t\tthis._transcriptCwds.set(path, cwd);\n\t\tif (this._transcriptCwds.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._transcriptCwds.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._transcriptCwds.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Reads a summary from the first and last {@link summaryWindowSize} bytes of the transcript.\n\t *\n\t * Deliberately does not reuse `resolve` — that scans from the last-read offset and buffers the whole\n\t * range, which for a cold multi-MB transcript means loading the entire file. Titles are written early\n\t * and re-written as they're refined, while prompts land at the tail, so the two windows recover both\n\t * at a fraction of the bytes. The tail is parsed second so the newest value wins.\n\t */\n\tprotected async readSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary> {\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst apply = (buffer: Buffer, dropPartialFirstLine: boolean): void => {\n\t\t\tconst text = buffer.toString('utf8');\n\t\t\tconst lines = text.split('\\n');\n\t\t\t// A window starting mid-file almost always opens mid-line; that fragment can't be parsed.\n\t\t\tif (dropPartialFirstLine) {\n\t\t\t\tlines.shift();\n\t\t\t}\n\t\t\tfor (const line of lines) {\n\t\t\t\tconst parsed = parseSummaryLine(line, entry.sessionId);\n\t\t\t\tif (parsed == null) continue;\n\n\t\t\t\tapplyTitleEntry(parsed, titles);\n\t\t\t\tif (parsed.lastPrompt != null && parsed.lastPrompt.length > 0) {\n\t\t\t\t\tlastPrompt = parsed.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst handle = await open(entry.path, 'r');\n\t\ttry {\n\t\t\tif (entry.size <= summaryWindowSize * 2) {\n\t\t\t\tconst whole = Buffer.alloc(entry.size);\n\t\t\t\tawait handle.read(whole, 0, entry.size, 0);\n\t\t\t\tapply(whole, false);\n\t\t\t} else {\n\t\t\t\tconst head = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(head, 0, summaryWindowSize, 0);\n\t\t\t\tapply(head, false);\n\n\t\t\t\tconst tail = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(tail, 0, summaryWindowSize, entry.size - summaryWindowSize);\n\t\t\t\tapply(tail, true);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\n\t\treturn {\n\t\t\t...entry,\n\t\t\ttitles: titles,\n\t\t\tlastPrompt: lastPrompt,\n\t\t};\n\t}\n\n\tprotected getProjectsRoot(): string {\n\t\treturn join(homedir(), '.claude', 'projects');\n\t}\n\n\t/** Resolves the `~/.claude/projects` directory holding the sessions whose working directory is\n\t * `cwd`, or `undefined` when none exists. Falls back to a case-insensitive name match because on\n\t * Windows our\n\t * paths carry a lower-cased drive letter (`normalizePath`) while Claude encodes the OS-native\n\t * `C:\\...` — so the exact name never matches by case. */\n\tprotected async resolveProjectDir(cwd: string | undefined): Promise<string | undefined> {\n\t\tif (cwd == null || cwd.length === 0) return undefined;\n\n\t\tconst root = this.getProjectsRoot();\n\t\tconst encoded = encodeProjectDirName(cwd);\n\n\t\tconst exact = join(root, encoded);\n\t\tif (await directoryExists(exact)) return exact;\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\t// Only a missing projects root means \"no transcript\". A transient failure propagates so the\n\t\t\t// caller retries instead of caching a permanent miss.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst lowered = encoded.toLowerCase();\n\t\tconst match = dirs.find(d => d !== encoded && d.toLowerCase() === lowered);\n\t\treturn match != null ? join(root, match) : undefined;\n\t}\n\n\tprotected async locateTranscript(sessionId: string, cwd: string | undefined): Promise<string | undefined> {\n\t\tconst fileName = `${sessionId}.jsonl`;\n\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir != null) {\n\t\t\tconst candidate = join(dir, fileName);\n\t\t\tif (await fileExists(candidate)) return candidate;\n\t\t}\n\n\t\t// The recorded cwd drifts whenever the agent `cd`s, so it can encode to a directory the session\n\t\t// doesn't actually live in — scan every project for the file as a last resort.\n\t\tconst root = this.getProjectsRoot();\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst candidates = await Promise.all(\n\t\t\tdirs.map(async d => {\n\t\t\t\tconst candidate = join(root, d, fileName);\n\t\t\t\treturn (await fileExists(candidate)) ? candidate : undefined;\n\t\t\t}),\n\t\t);\n\t\treturn candidates.find(c => c != null);\n\t}\n\n\t/**\n\t * Reads bytes `[start, end)` from `path` and applies any title entries it finds to a copy of\n\t * `baseTitles`. Returns the merged titles and the byte position immediately after the last\n\t * newline observed — partial trailing lines (writer hasn't flushed `\\n` yet) are intentionally\n\t * left unconsumed so they're re-read on the next pass.\n\t */\n\tprotected async readRange(\n\t\tpath: string,\n\t\tstart: number,\n\t\tend: number,\n\t\tsessionId: string,\n\t\tbaseTitles: TranscriptTitles,\n\t): Promise<{ titles: TranscriptTitles; consumedEnd: number }> {\n\t\tconst buffer = await readSlice(path, start, end);\n\t\tconst titles: TranscriptTitles = { ...baseTitles };\n\n\t\tlet lastNewlineEnd = start;\n\t\tlet lineStart = 0;\n\t\tfor (let i = 0; i < buffer.length; i++) {\n\t\t\tif (buffer[i] !== 0x0a) continue;\n\n\t\t\t// Slice the line (trim a trailing \\r for CRLF files), then advance the cursor past the \\n\n\t\t\t// regardless of whether the line yielded anything — we've fully observed those bytes.\n\t\t\tconst lineEnd = i > lineStart && buffer[i - 1] === 0x0d ? i - 1 : i;\n\t\t\tif (lineEnd > lineStart) {\n\t\t\t\tconst line = buffer.toString('utf8', lineStart, lineEnd);\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t}\n\t\t\tlineStart = i + 1;\n\t\t\tlastNewlineEnd = start + i + 1;\n\t\t}\n\n\t\treturn { titles: titles, consumedEnd: lastNewlineEnd };\n\t}\n}\n\nfunction applyTitleLine(line: string, sessionId: string, titles: TranscriptTitles): void {\n\tif (!isLikelyTitleLine(line)) return;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return;\n\n\tapplyTitleEntry(entry, titles);\n}\n\n/** Parses one transcript line for {@link ClaudeCodeTranscriptReader.readSummary}, rejecting entries\n * that belong to another session. Can't use the `-title` prefilter that {@link isLikelyTitleLine}\n * applies: `last-prompt` matches neither of its markers. */\nfunction parseSummaryLine(line: string, sessionId: string): TitleEntry | undefined {\n\tif (line.length === 0 || !line.includes('\"type\"')) return undefined;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return undefined;\n\n\treturn entry;\n}\n\nfunction applyTitleEntry(entry: TitleEntry, titles: TranscriptTitles): void {\n\tswitch (entry.type) {\n\t\tcase 'custom-title':\n\t\t\tif (typeof entry.customTitle === 'string' && entry.customTitle.length > 0) {\n\t\t\t\ttitles.custom = entry.customTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'ai-title':\n\t\t\tif (typeof entry.aiTitle === 'string' && entry.aiTitle.length > 0) {\n\t\t\t\ttitles.ai = entry.aiTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'agent-name':\n\t\t\tif (typeof entry.agentName === 'string' && entry.agentName.length > 0) {\n\t\t\t\ttitles.agent = entry.agentName;\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\n/** Encodes a working directory into its `~/.claude/projects` directory name. Claude replaces every\n * non-alphanumeric character — not just separators — preserving runs (`/home/e/.claude` →\n * `-home-e--claude`) and case. Separators-only would leave the dot in our own worktree convention\n * (`<repo>.worktrees/<name>`) intact and compute a directory that never exists. */\nexport function encodeProjectDirName(cwd: string): string {\n\treturn cwd.replace(/[^a-zA-Z0-9]/g, '-');\n}\n\nfunction isLikelyTitleLine(line: string): boolean {\n\tif (!line.includes('\"type\"')) return false;\n\treturn line.includes('-title') || line.includes('agent-name');\n}\n\n/** A transcript with no title and no prompt is an aborted or empty session — nothing to name it by,\n * nothing to search it on. */\nfunction hasSummaryContent(summary: TranscriptSessionSummary): boolean {\n\tconst { custom, ai, agent } = summary.titles;\n\treturn custom != null || ai != null || agent != null || summary.lastPrompt != null;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isFile();\n\t} catch (ex) {\n\t\t// See {@link isMissingEntry}: only a genuine absence is an answer.\n\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\treturn false;\n\t}\n}\n\nasync function directoryExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isDirectory();\n\t} catch (ex) {\n\t\t// \"Not there\" is an answer; anything else (EACCES, EIO, a mount hiccup) is a transient\n\t\t// failure the caller must be able to tell apart — see {@link isMissingEntry}.\n\t\tif (isMissingEntry(ex)) return false;\n\n\t\tthrow ex;\n\t}\n}\n\n/** True for the \"path simply isn't there\" errno codes, as opposed to a transient I/O or permission\n * failure. Callers cache a genuine absence as terminal, so a transient failure must not look like\n * one — it would pin a session to its fallback name until the window reloads. */\nfunction isMissingEntry(ex: unknown): boolean {\n\tconst code = (ex as NodeJS.ErrnoException | undefined)?.code;\n\treturn code === 'ENOENT' || code === 'ENOTDIR';\n}\n\nfunction readSlice(path: string, start: number, end: number): Promise<Buffer> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks: Buffer[] = [];\n\t\tconst stream = createReadStream(path, { start: start, end: end - 1 });\n\t\tstream.on('data', chunk => chunks.push(chunk as Buffer));\n\t\tstream.on('end', () => resolve(Buffer.concat(chunks)));\n\t\tstream.on('error', reject);\n\t});\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"claudeCodeTranscript.js","names":[],"sources":["../../../../../plus/agents/src/providers/claudeCodeTranscript.ts"],"sourcesContent":["import { createReadStream } from 'fs';\nimport { open, readdir, readFile, stat } from 'fs/promises';\nimport { homedir } from 'os';\nimport { basename, dirname, join } from 'path';\nimport { createInterface } from 'readline';\n\nexport interface TranscriptTitles {\n\tcustom?: string;\n\tai?: string;\n\tagent?: string;\n}\n\n/** A transcript file discovered by {@link ClaudeCodeTranscriptReader.listSessions}, before its\n * contents are read — everything here comes from `readdir` + `stat`. */\nexport interface TranscriptSessionEntry {\n\treadonly sessionId: string;\n\treadonly path: string;\n\treadonly lastActivityMs: number;\n\treadonly size: number;\n}\n\n/** A {@link TranscriptSessionEntry} enriched with the fields recovered from the file's head/tail. */\nexport interface TranscriptSessionSummary extends TranscriptSessionEntry {\n\treadonly titles: TranscriptTitles;\n\treadonly lastPrompt?: string;\n}\n\nexport interface TranscriptSessionListing {\n\treadonly sessions: TranscriptSessionSummary[];\n\t/** Every transcript in the directory, not just the summarized slice — drives \"Showing N of M\". */\n\treadonly total: number;\n}\n\n/** A transcript found outside the queried cwd's project directory, plus the directory Claude\n * actually uses to resolve it for `--resume`. */\nexport interface ResumableTranscriptSessionSummary extends TranscriptSessionSummary {\n\treadonly cwd: string;\n}\n\nexport interface ResumableTranscriptSessionListing {\n\treadonly sessions: ResumableTranscriptSessionSummary[];\n\treadonly total: number;\n}\n\nexport interface EndedTranscriptDetails {\n\ttitles: TranscriptTitles;\n\tfirstPrompt?: string;\n\tlastPrompt?: string;\n}\n\ninterface CacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tnextOffset: number;\n\ttitles: TranscriptTitles;\n}\n\ninterface ListingCacheEntry {\n\tentries: TranscriptSessionEntry[];\n\tresolvedAt: number;\n}\n\ninterface NameCacheEntry {\n\tnames: string[];\n\tresolvedAt: number;\n}\n\ninterface SummaryCacheEntry {\n\tpath: string;\n\tmtimeMs: number;\n\tsize: number;\n\tsummary: TranscriptSessionSummary;\n}\n\ninterface TitleEntry {\n\ttype: string;\n\tsessionId?: string;\n\tcustomTitle?: string;\n\taiTitle?: string;\n\tagentName?: string;\n\tlastPrompt?: string;\n}\n\n/** Bytes read from each end of a transcript when summarizing. Titles and prompts cluster at both\n * extremes, and files run to tens of MB, so reading whole files is not an option. */\nconst summaryWindowSize = 64 * 1024;\n/** Listings are cheap (readdir + stat) but re-run per panel/sheet open; a short TTL absorbs bursts.\n * Time-based rather than dir-mtime-based because appends move file mtimes without touching the dir. */\nconst listingCacheTtlMs = 10 * 1000;\n/** Summaries are keyed by session, and a busy project has hundreds — unlike the per-live-session\n * title cache, this needs a ceiling. Sized to hold a whole junk-heavy store: the listing scan runs\n * to exhaustion, and each entry is tiny (a path plus a few short strings), so a cap below the\n * store's size would evict-and-re-read the junk tail on every listing instead of paying its\n * windowed reads once. */\nconst summaryCacheLimit = 1000;\nconst defaultListLimit = 50;\n\n/**\n * Reads Claude Code transcript JSONL files at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`\n * to surface `custom-title`, `ai-title`, and `agent-name` entries as fallback session names.\n *\n * Maintains a per-session cache keyed by mtime + last-read byte offset, so repeated calls only\n * read the appended tail. Last occurrence per type wins.\n *\n * `resolve` is async, so a `forget` (or a newer `resolve`) for the same session can land between\n * its `await`s. A per-session generation counter is stamped at entry and re-checked before the\n * final cache write, so a stale `resolve` can't resurrect a forgotten entry or clobber a newer\n * resolve's result.\n */\nexport class ClaudeCodeTranscriptReader {\n\tprivate readonly _cache = new Map<string, CacheEntry>();\n\tprivate readonly _generations = new Map<string, number>();\n\tprivate readonly _listings = new Map<string, ListingCacheEntry>();\n\t/** Transcript file names per project directory — the readdir behind both the plain listing and\n\t * the by-id scan, which sweeps every project directory. */\n\tprivate readonly _names = new Map<string, NameCacheEntry>();\n\t/** Project directories under {@link getProjectsRoot}, re-scanned by every by-id recovery. */\n\tprivate _projectDirs: { dirs: string[]; resolvedAt: number } | undefined;\n\t/** Insertion-ordered LRU — re-inserted on hit, oldest key evicted past {@link summaryCacheLimit}. */\n\tprivate readonly _summaries = new Map<string, SummaryCacheEntry>();\n\t/** Resolved project path per transcript path, bounded like {@link _summaries}. */\n\tprivate readonly _transcriptCwds = new Map<string, string>();\n\tprivate _nextGen = 0;\n\t/** Bumped by {@link invalidateListings}; async listing scans capture it at entry and skip their\n\t * cache write when it moved, so a stale in-flight result can't re-seed a just-cleared cache. */\n\tprivate _listingGen = 0;\n\n\t/** Drops the directory-listing caches (entries, names, project dirs) so the next listing re-reads\n\t * the store. Called when a session ends or is removed: its transcript may have appeared inside\n\t * the TTL window, and a cached listing would hide the new file from the very fetch meant to\n\t * surface it. Summary and cwd caches are keyed by file identity and stay valid, so they're kept.\n\t * The generation bump makes any in-flight listing skip its own cache write — otherwise a scan\n\t * started before the invalidation would re-seed the caches with pre-end results, freshly\n\t * timestamped (same discipline as `resolve`'s per-session generations). */\n\tinvalidateListings(): void {\n\t\tthis._listingGen++;\n\t\tthis._listings.clear();\n\t\tthis._names.clear();\n\t\tthis._projectDirs = undefined;\n\t}\n\n\tasync resolve(sessionId: string, cwd: string | undefined): Promise<TranscriptTitles | undefined> {\n\t\tconst gen = ++this._nextGen;\n\t\tthis._generations.set(sessionId, gen);\n\n\t\tconst cached = this._cache.get(sessionId);\n\t\tconst path = cached?.path ?? (await this.locateTranscript(sessionId, cwd));\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch {\n\t\t\t// File disappeared; drop any stale cache entry so a future call retries discovery.\n\t\t\t// Skip the delete if a newer resolve/forget claimed the slot — their state is fresher.\n\t\t\tif (this._generations.get(sessionId) === gen) {\n\t\t\t\tthis._cache.delete(sessionId);\n\t\t\t\tthis._generations.delete(sessionId);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (stats.mtimeMs === cached?.mtimeMs && stats.size === cached?.size) {\n\t\t\treturn cached.titles;\n\t\t}\n\n\t\t// Truncation or rewrite: stat shrank below our read cursor or mtime moved backward → full re-scan.\n\t\tconst startFromScratch = cached == null || stats.size < cached.nextOffset || stats.mtimeMs < cached.mtimeMs;\n\t\tconst startOffset = startFromScratch ? 0 : cached.nextOffset;\n\t\tconst baseTitles: TranscriptTitles = startFromScratch ? {} : { ...cached.titles };\n\n\t\tconst { titles, consumedEnd } =\n\t\t\tstats.size > startOffset\n\t\t\t\t? await this.readRange(path, startOffset, stats.size, sessionId, baseTitles)\n\t\t\t\t: { titles: baseTitles, consumedEnd: startOffset };\n\n\t\t// If `forget` ran or a newer `resolve` started while we were awaiting, skip the cache write\n\t\t// — the entry we'd be writing reflects stale state.\n\t\tif (this._generations.get(sessionId) !== gen) return titles;\n\n\t\tconst entry: CacheEntry = {\n\t\t\tpath: path,\n\t\t\tmtimeMs: stats.mtimeMs,\n\t\t\tsize: stats.size,\n\t\t\tnextOffset: consumedEnd,\n\t\t\ttitles: titles,\n\t\t};\n\t\tthis._cache.set(sessionId, entry);\n\t\treturn titles;\n\t}\n\n\tforget(sessionId: string): void {\n\t\tthis._cache.delete(sessionId);\n\t\tthis._generations.delete(sessionId);\n\t}\n\n\t/**\n\t * One-shot read for an *ended* (terminal, static) session: extracts the transcript titles plus\n\t * the first and last user prompts. Unlike {@link resolve}, it bypasses the tail cache — a finished\n\t * transcript never grows, so it's read once on demand (when an ended row is opened) and the\n\t * result applied to the session. `last-prompt` entries carry the prompt as of that point; the first\n\t * one bearing a value is the session's opening prompt, the last its most recent. Returns `undefined`\n\t * when no transcript is found (archived/purged) — the caller keeps whatever durable-store fields it\n\t * already has.\n\t *\n\t * Streams the file line-by-line rather than buffering it whole: a finished transcript can run to\n\t * tens of MB, but this keeps only one line resident at a time. A *full* scan (not a head/tail\n\t * window) is required for correctness — when the first turn is large the opening `last-prompt` sits\n\t * past any fixed head window, and a windowed read would then mistake a later prompt for the first,\n\t * poisoning the derived session name.\n\t */\n\tasync resolveEndedDetails(sessionId: string, cwd: string | undefined): Promise<EndedTranscriptDetails | undefined> {\n\t\tconst path = await this.locateTranscript(sessionId, cwd);\n\t\tif (path == null) return undefined;\n\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await stat(path);\n\t\t} catch (ex) {\n\t\t\t// A transcript that vanished is terminal; an I/O or permission failure must propagate so\n\t\t\t// the caller retries rather than caching this session as permanently unresolvable.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\t\tif (stats.size === 0) return undefined;\n\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet firstPrompt: string | undefined;\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst rl = createInterface({ input: createReadStream(path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t\tif (!line.includes('last-prompt')) continue;\n\n\t\t\t\tconst entry = parseSummaryLine(line, sessionId);\n\t\t\t\tif (entry?.type === 'last-prompt' && entry.lastPrompt != null && entry.lastPrompt.length > 0) {\n\t\t\t\t\tfirstPrompt ??= entry.lastPrompt;\n\t\t\t\t\tlastPrompt = entry.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn { titles: titles, firstPrompt: firstPrompt, lastPrompt: lastPrompt };\n\t}\n\n\t/**\n\t * Lists the transcripts of sessions whose working directory is `cwd`, most-recently-active first,\n\t * summarizing until `limit` summarizable transcripts are found — not just the first `limit` on\n\t * disk, since junk transcripts (dropped below) would otherwise starve the result.\n\t * `excludeSessionIds` is skipped before `limit` applies, and excluded entries are also dropped\n\t * from `total` — they're already shown elsewhere (e.g. as live sessions), so a caller's \"N of M\"\n\t * count must not double-count them.\n\t *\n\t * Claude homes a transcript under the directory encoding the cwd from which the session started.\n\t * That directory is authoritative for where `claude --resume <id>` can find the transcript, but\n\t * Claude does not reliably move it when the session later changes worktrees. Callers that have a\n\t * durable session-to-worktree association can recover those moved sessions with\n\t * {@link listSessionsByIds}; this method intentionally remains the cheap legacy directory listing.\n\t *\n\t * Discovery (readdir + stat) covers the whole directory and is cheap; summarizing is not, so it's\n\t * capped. Entries whose summary yields neither a title nor a prompt are dropped — those are aborted\n\t * or empty transcripts with nothing to show or search on.\n\t */\n\tasync listSessions(\n\t\tcwd: string,\n\t\toptions?: { limit?: number; excludeSessionIds?: ReadonlySet<string> },\n\t): Promise<TranscriptSessionListing> {\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir == null) return { sessions: [], total: 0 };\n\n\t\tconst entries = await this.listEntries(dir);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst exclude = options?.excludeSessionIds;\n\t\t// Zero-byte transcripts can never summarize — skip the read entirely and keep them out of\n\t\t// `total`, which otherwise counts sessions no `limit` could ever surface.\n\t\tconst candidates = entries.filter(e => e.size > 0 && exclude?.has(e.sessionId) !== true);\n\t\tconst { sessions, exhausted } = await this.collectSessions(candidates, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\treturn summary != null && hasSummaryContent(summary) ? summary : undefined;\n\t\t});\n\n\t\t// An exhausted scan proved the exact valid count; otherwise unscanned candidates keep\n\t\t// `total` an upper bound so paging knows more MAY exist.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : candidates.length };\n\t}\n\n\t/**\n\t * Finds a known set of session ids across every Claude project directory except `excludeCwd`'s.\n\t * This is the recovery path for durable CLI records whose worktree changed after Claude chose the\n\t * transcript's project directory. The caller supplies the association; transcript contents alone\n\t * are deliberately not used to guess which current worktree owns an arbitrary old session.\n\t *\n\t * Each result carries the project path encoded by the directory that actually holds the transcript,\n\t * so resuming it does not repeat discovery from the wrong worktree. Duplicate ids are counted once,\n\t * with the newest transcript winning.\n\t */\n\tasync listSessionsByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\toptions?: { limit?: number; excludeCwd?: string },\n\t): Promise<ResumableTranscriptSessionListing> {\n\t\tif (sessionIds.size === 0) return { sessions: [], total: 0 };\n\n\t\tconst excludeDir = await this.resolveProjectDir(options?.excludeCwd);\n\t\t// Same zero-byte drop as `listSessions` — see the rationale there.\n\t\tconst entries = (await this.listEntriesByIds(sessionIds, excludeDir)).filter(e => e.size > 0);\n\t\tif (entries.length === 0) return { sessions: [], total: 0 };\n\n\t\tconst limit = options?.limit ?? defaultListLimit;\n\t\tconst { sessions, exhausted } = await this.collectSessions(entries, limit, async entry => {\n\t\t\tconst summary = await this.resolveSummary(entry);\n\t\t\tif (summary == null || !hasSummaryContent(summary)) return undefined;\n\n\t\t\tconst cwd = await this.resolveTranscriptCwd(entry);\n\t\t\treturn cwd != null ? { ...summary, cwd: cwd } : undefined;\n\t\t});\n\n\t\t// Exhausted scans prove the exact count — see `listSessions`.\n\t\treturn { sessions: sessions, total: exhausted ? sessions.length : entries.length };\n\t}\n\n\t/** Discovers every transcript in `dir`, newest first. The directory also holds one sibling\n\t * subdirectory per session (`<uuid>/subagents`, `<uuid>/tool-results`), so entries must be filtered\n\t * to files — a busy project has roughly as many subdirectories as transcripts. */\n\tprivate async listEntries(dir: string): Promise<TranscriptSessionEntry[]> {\n\t\tconst cached = this._listings.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.entries;\n\n\t\tconst gen = this._listingGen;\n\t\tlet names: string[];\n\t\ttry {\n\t\t\tnames = await this.listTranscriptNames(dir);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = (await this.statEntries(dir, names)).sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._listings.set(dir, { entries: entries, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/** Stats `names` in `dir`, dropping the ones that failed — a transcript can vanish (or be\n\t * archived) between the readdir and the stat. Unsorted; callers order as they need.\n\t * Protected (like {@link readSummary}) so tests can interleave against the listing flow. */\n\tprotected async statEntries(dir: string, names: string[]): Promise<TranscriptSessionEntry[]> {\n\t\tconst settled = await Promise.allSettled(\n\t\t\tnames.map(async (name): Promise<TranscriptSessionEntry> => {\n\t\t\t\tconst path = join(dir, name);\n\t\t\t\tconst stats = await stat(path);\n\t\t\t\treturn {\n\t\t\t\t\tsessionId: basename(name, '.jsonl'),\n\t\t\t\t\tpath: path,\n\t\t\t\t\tlastActivityMs: stats.mtimeMs,\n\t\t\t\t\tsize: stats.size,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\n\t\treturn settled.filter(r => r.status === 'fulfilled').map(r => r.value);\n\t}\n\n\t/** The `.jsonl` file names in `dir`, cached for {@link listingCacheTtlMs}. Failures propagate\n\t * uncached: an unreadable directory means different things to different callers, and the next\n\t * call must be free to get a real answer. */\n\tprivate async listTranscriptNames(dir: string): Promise<string[]> {\n\t\tconst cached = this._names.get(dir);\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.names;\n\n\t\tconst gen = this._listingGen;\n\t\tconst dirents = await readdir(dir, { withFileTypes: true });\n\t\tconst names = dirents.filter(d => d.isFile() && d.name.endsWith('.jsonl')).map(d => d.name);\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._names.set(dir, { names: names, resolvedAt: Date.now() });\n\t\t}\n\n\t\treturn names;\n\t}\n\n\t/** Every project directory under the projects root, cached for {@link listingCacheTtlMs} — a\n\t * by-id recovery sweeps all of them, once per queried worktree. */\n\tprivate async listProjectDirs(): Promise<string[]> {\n\t\tconst cached = this._projectDirs;\n\t\tif (cached != null && Date.now() - cached.resolvedAt < listingCacheTtlMs) return cached.dirs;\n\n\t\tconst gen = this._listingGen;\n\t\tconst root = this.getProjectsRoot();\n\t\tconst dirents = await readdir(root, { withFileTypes: true });\n\t\tconst dirs = dirents.filter(d => d.isDirectory()).map(d => join(root, d.name));\n\t\tif (gen === this._listingGen) {\n\t\t\tthis._projectDirs = { dirs: dirs, resolvedAt: Date.now() };\n\t\t}\n\n\t\treturn dirs;\n\t}\n\n\t/** Scans project directory names once, then stats only requested transcript ids. */\n\tprivate async listEntriesByIds(\n\t\tsessionIds: ReadonlySet<string>,\n\t\texcludeDir: string | undefined,\n\t): Promise<TranscriptSessionEntry[]> {\n\t\tlet idsToFind = sessionIds;\n\t\tif (excludeDir != null) {\n\t\t\ttry {\n\t\t\t\tconst names = await this.listTranscriptNames(excludeDir);\n\t\t\t\tconst idsInExcludedDir = new Set(names.map(name => basename(name, '.jsonl')));\n\t\t\t\tidsToFind = new Set([...sessionIds].filter(id => !idsInExcludedDir.has(id)));\n\t\t\t\tif (idsToFind.size === 0) return [];\n\t\t\t} catch {\n\t\t\t\t// If the exact directory can't be inspected, don't risk returning a duplicate from a\n\t\t\t\t// stale transcript copy elsewhere. The ordinary listing still owns the exact path.\n\t\t\t\treturn [];\n\t\t\t}\n\t\t}\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await this.listProjectDirs();\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn [];\n\t\t}\n\n\t\tconst settledDirs = await Promise.allSettled(\n\t\t\tdirs.map(async dir => {\n\t\t\t\tif (dir === excludeDir) return [];\n\n\t\t\t\tconst names = (await this.listTranscriptNames(dir)).filter(name =>\n\t\t\t\t\tidsToFind.has(basename(name, '.jsonl')),\n\t\t\t\t);\n\t\t\t\treturn this.statEntries(dir, names);\n\t\t\t}),\n\t\t);\n\n\t\tconst bySessionId = new Map<string, TranscriptSessionEntry>();\n\t\tfor (const settledDir of settledDirs) {\n\t\t\tif (settledDir.status !== 'fulfilled') continue;\n\n\t\t\tfor (const entry of settledDir.value) {\n\t\t\t\tconst existing = bySessionId.get(entry.sessionId);\n\t\t\t\tif (existing == null || entry.lastActivityMs > existing.lastActivityMs) {\n\t\t\t\t\tbySessionId.set(entry.sessionId, entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn [...bySessionId.values()].sort((a, b) => b.lastActivityMs - a.lastActivityMs);\n\t}\n\n\t/** `exhausted` reports whether every candidate was scanned — an exhausted scan has proven the\n\t * exact valid count, so callers can report it instead of an upper bound that would keep a\n\t * \"Show More\" affordance alive with nothing left to show. */\n\tprivate async collectSessions<T>(\n\t\tcandidates: readonly TranscriptSessionEntry[],\n\t\tlimit: number,\n\t\tresolve: (entry: TranscriptSessionEntry) => Promise<T | undefined>,\n\t): Promise<{ sessions: T[]; exhausted: boolean }> {\n\t\tconst sessions: T[] = [];\n\t\tconst pushResolved = (settled: PromiseSettledResult<T | undefined>[]): void => {\n\t\t\tfor (const result of settled) {\n\t\t\t\tif (result.status !== 'fulfilled' || result.value == null) continue;\n\n\t\t\t\tsessions.push(result.value);\n\t\t\t}\n\t\t};\n\n\t\t// limit <= 0 means \"no ceiling\" — resolve every candidate.\n\t\tif (limit <= 0) {\n\t\t\tpushResolved(await Promise.allSettled(candidates.map(resolve)));\n\t\t\treturn { sessions: sessions, exhausted: true };\n\t\t}\n\n\t\t// Scans until `limit` results or the candidates run out — a fixed scan ceiling would let a run\n\t\t// of junk transcripts hide every older real session behind it (and the picker then claims there\n\t\t// are none at all). The cost stays bounded: callers pre-drop zero-byte candidates read-free, and\n\t\t// a junk summary is windowed, tiny, and LRU-cached, so the sweep is paid in syscalls, once.\n\t\tlet cursor = 0;\n\t\twhile (sessions.length < limit && cursor < candidates.length) {\n\t\t\t// Batched by remaining need so a hit-rich head stops the scan early instead of resolving\n\t\t\t// every candidate up front.\n\t\t\tconst batch = candidates.slice(cursor, cursor + (limit - sessions.length));\n\t\t\tcursor += batch.length;\n\t\t\tpushResolved(await Promise.allSettled(batch.map(resolve)));\n\t\t}\n\n\t\treturn { sessions: sessions, exhausted: cursor >= candidates.length };\n\t}\n\n\t/** Summarizes one transcript, reusing the cached result while its mtime and size are unchanged. */\n\tprivate async resolveSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary | undefined> {\n\t\tconst cached = this._summaries.get(entry.sessionId);\n\t\tif (\n\t\t\tcached != null &&\n\t\t\tcached.path === entry.path &&\n\t\t\tcached.mtimeMs === entry.lastActivityMs &&\n\t\t\tcached.size === entry.size\n\t\t) {\n\t\t\t// Refresh recency for the LRU.\n\t\t\tthis._summaries.delete(entry.sessionId);\n\t\t\tthis._summaries.set(entry.sessionId, cached);\n\t\t\treturn cached.summary;\n\t\t}\n\n\t\tlet summary: TranscriptSessionSummary;\n\t\ttry {\n\t\t\tsummary = await this.readSummary(entry);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis._summaries.set(entry.sessionId, {\n\t\t\tpath: entry.path,\n\t\t\tmtimeMs: entry.lastActivityMs,\n\t\t\tsize: entry.size,\n\t\t\tsummary: summary,\n\t\t});\n\t\tif (this._summaries.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._summaries.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._summaries.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t\treturn summary;\n\t}\n\n\t/** Recovers the project path represented by the transcript's containing directory. A resolved\n\t * answer is cached: it's a line-scan of the whole transcript, and the directory holding a given\n\t * path never changes. A miss isn't cached — a `cwd` line can still land on append. */\n\tprivate async resolveTranscriptCwd(entry: TranscriptSessionEntry): Promise<string | undefined> {\n\t\tconst cached = this._transcriptCwds.get(entry.path);\n\t\tif (cached != null) return cached;\n\n\t\tconst projectDir = dirname(entry.path);\n\t\tconst projectDirName = basename(projectDir);\n\t\tconst matchesProjectDir = (cwd: string): boolean =>\n\t\t\tencodeProjectDirName(cwd).toLowerCase() === projectDirName.toLowerCase();\n\n\t\ttry {\n\t\t\tconst index = JSON.parse(await readFile(join(projectDir, 'sessions-index.json'), 'utf8')) as {\n\t\t\t\tentries?: { sessionId?: unknown; projectPath?: unknown }[];\n\t\t\t};\n\t\t\tif (Array.isArray(index.entries)) {\n\t\t\t\tconst indexed = index.entries.find(item => item?.sessionId === entry.sessionId);\n\t\t\t\tif (typeof indexed?.projectPath === 'string' && matchesProjectDir(indexed.projectPath)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, indexed.projectPath);\n\t\t\t\t\treturn indexed.projectPath;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// The index is optional and can lag or be malformed. The transcript is authoritative below.\n\t\t}\n\n\t\tconst rl = createInterface({ input: createReadStream(entry.path, { encoding: 'utf8' }), crlfDelay: Infinity });\n\t\ttry {\n\t\t\tfor await (const line of rl) {\n\t\t\t\tif (!line.includes('\"cwd\"')) continue;\n\n\t\t\t\tlet parsed: { cwd?: unknown; sessionId?: unknown };\n\t\t\t\ttry {\n\t\t\t\t\tparsed = JSON.parse(line) as { cwd?: unknown; sessionId?: unknown };\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (parsed.sessionId != null && parsed.sessionId !== entry.sessionId) continue;\n\n\t\t\t\tif (typeof parsed.cwd === 'string' && matchesProjectDir(parsed.cwd)) {\n\t\t\t\t\tthis.rememberTranscriptCwd(entry.path, parsed.cwd);\n\t\t\t\t\treturn parsed.cwd;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\trl.close();\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate rememberTranscriptCwd(path: string, cwd: string): void {\n\t\tthis._transcriptCwds.set(path, cwd);\n\t\tif (this._transcriptCwds.size > summaryCacheLimit) {\n\t\t\tconst oldest = this._transcriptCwds.keys().next();\n\t\t\tif (!oldest.done) {\n\t\t\t\tthis._transcriptCwds.delete(oldest.value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Reads a summary from the first and last {@link summaryWindowSize} bytes of the transcript.\n\t *\n\t * Deliberately does not reuse `resolve` — that scans from the last-read offset and buffers the whole\n\t * range, which for a cold multi-MB transcript means loading the entire file. Titles are written early\n\t * and re-written as they're refined, while prompts land at the tail, so the two windows recover both\n\t * at a fraction of the bytes. The tail is parsed second so the newest value wins.\n\t */\n\tprotected async readSummary(entry: TranscriptSessionEntry): Promise<TranscriptSessionSummary> {\n\t\tconst titles: TranscriptTitles = {};\n\t\tlet lastPrompt: string | undefined;\n\n\t\tconst apply = (buffer: Buffer, dropPartialFirstLine: boolean): void => {\n\t\t\tconst text = buffer.toString('utf8');\n\t\t\tconst lines = text.split('\\n');\n\t\t\t// A window starting mid-file almost always opens mid-line; that fragment can't be parsed.\n\t\t\tif (dropPartialFirstLine) {\n\t\t\t\tlines.shift();\n\t\t\t}\n\t\t\tfor (const line of lines) {\n\t\t\t\tconst parsed = parseSummaryLine(line, entry.sessionId);\n\t\t\t\tif (parsed == null) continue;\n\n\t\t\t\tapplyTitleEntry(parsed, titles);\n\t\t\t\tif (parsed.lastPrompt != null && parsed.lastPrompt.length > 0) {\n\t\t\t\t\tlastPrompt = parsed.lastPrompt;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst handle = await open(entry.path, 'r');\n\t\ttry {\n\t\t\tif (entry.size <= summaryWindowSize * 2) {\n\t\t\t\tconst whole = Buffer.alloc(entry.size);\n\t\t\t\tawait handle.read(whole, 0, entry.size, 0);\n\t\t\t\tapply(whole, false);\n\t\t\t} else {\n\t\t\t\tconst head = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(head, 0, summaryWindowSize, 0);\n\t\t\t\tapply(head, false);\n\n\t\t\t\tconst tail = Buffer.alloc(summaryWindowSize);\n\t\t\t\tawait handle.read(tail, 0, summaryWindowSize, entry.size - summaryWindowSize);\n\t\t\t\tapply(tail, true);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\n\t\treturn {\n\t\t\t...entry,\n\t\t\ttitles: titles,\n\t\t\tlastPrompt: lastPrompt,\n\t\t};\n\t}\n\n\tprotected getProjectsRoot(): string {\n\t\treturn join(homedir(), '.claude', 'projects');\n\t}\n\n\t/** Resolves the `~/.claude/projects` directory holding the sessions whose working directory is\n\t * `cwd`, or `undefined` when none exists. Falls back to a case-insensitive name match because on\n\t * Windows our\n\t * paths carry a lower-cased drive letter (`normalizePath`) while Claude encodes the OS-native\n\t * `C:\\...` — so the exact name never matches by case. */\n\tprotected async resolveProjectDir(cwd: string | undefined): Promise<string | undefined> {\n\t\tif (cwd == null || cwd.length === 0) return undefined;\n\n\t\tconst root = this.getProjectsRoot();\n\t\tconst encoded = encodeProjectDirName(cwd);\n\n\t\tconst exact = join(root, encoded);\n\t\tif (await directoryExists(exact)) return exact;\n\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\t// Only a missing projects root means \"no transcript\". A transient failure propagates so the\n\t\t\t// caller retries instead of caching a permanent miss.\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst lowered = encoded.toLowerCase();\n\t\tconst match = dirs.find(d => d !== encoded && d.toLowerCase() === lowered);\n\t\treturn match != null ? join(root, match) : undefined;\n\t}\n\n\tprotected async locateTranscript(sessionId: string, cwd: string | undefined): Promise<string | undefined> {\n\t\tconst fileName = `${sessionId}.jsonl`;\n\n\t\tconst dir = await this.resolveProjectDir(cwd);\n\t\tif (dir != null) {\n\t\t\tconst candidate = join(dir, fileName);\n\t\t\tif (await fileExists(candidate)) return candidate;\n\t\t}\n\n\t\t// The recorded cwd drifts whenever the agent `cd`s, so it can encode to a directory the session\n\t\t// doesn't actually live in — scan every project for the file as a last resort.\n\t\tconst root = this.getProjectsRoot();\n\t\tlet dirs: string[];\n\t\ttry {\n\t\t\tdirs = await readdir(root);\n\t\t} catch (ex) {\n\t\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst candidates = await Promise.all(\n\t\t\tdirs.map(async d => {\n\t\t\t\tconst candidate = join(root, d, fileName);\n\t\t\t\treturn (await fileExists(candidate)) ? candidate : undefined;\n\t\t\t}),\n\t\t);\n\t\treturn candidates.find(c => c != null);\n\t}\n\n\t/**\n\t * Reads bytes `[start, end)` from `path` and applies any title entries it finds to a copy of\n\t * `baseTitles`. Returns the merged titles and the byte position immediately after the last\n\t * newline observed — partial trailing lines (writer hasn't flushed `\\n` yet) are intentionally\n\t * left unconsumed so they're re-read on the next pass.\n\t */\n\tprotected async readRange(\n\t\tpath: string,\n\t\tstart: number,\n\t\tend: number,\n\t\tsessionId: string,\n\t\tbaseTitles: TranscriptTitles,\n\t): Promise<{ titles: TranscriptTitles; consumedEnd: number }> {\n\t\tconst buffer = await readSlice(path, start, end);\n\t\tconst titles: TranscriptTitles = { ...baseTitles };\n\n\t\tlet lastNewlineEnd = start;\n\t\tlet lineStart = 0;\n\t\tfor (let i = 0; i < buffer.length; i++) {\n\t\t\tif (buffer[i] !== 0x0a) continue;\n\n\t\t\t// Slice the line (trim a trailing \\r for CRLF files), then advance the cursor past the \\n\n\t\t\t// regardless of whether the line yielded anything — we've fully observed those bytes.\n\t\t\tconst lineEnd = i > lineStart && buffer[i - 1] === 0x0d ? i - 1 : i;\n\t\t\tif (lineEnd > lineStart) {\n\t\t\t\tconst line = buffer.toString('utf8', lineStart, lineEnd);\n\t\t\t\tapplyTitleLine(line, sessionId, titles);\n\t\t\t}\n\t\t\tlineStart = i + 1;\n\t\t\tlastNewlineEnd = start + i + 1;\n\t\t}\n\n\t\treturn { titles: titles, consumedEnd: lastNewlineEnd };\n\t}\n}\n\nfunction applyTitleLine(line: string, sessionId: string, titles: TranscriptTitles): void {\n\tif (!isLikelyTitleLine(line)) return;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return;\n\n\tapplyTitleEntry(entry, titles);\n}\n\n/** Parses one transcript line for {@link ClaudeCodeTranscriptReader.readSummary}, rejecting entries\n * that belong to another session. Can't use the `-title` prefilter that {@link isLikelyTitleLine}\n * applies: `last-prompt` matches neither of its markers. */\nfunction parseSummaryLine(line: string, sessionId: string): TitleEntry | undefined {\n\tif (line.length === 0 || !line.includes('\"type\"')) return undefined;\n\n\tlet entry: TitleEntry;\n\ttry {\n\t\tentry = JSON.parse(line) as TitleEntry;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (entry.sessionId != null && entry.sessionId !== sessionId) return undefined;\n\n\treturn entry;\n}\n\nfunction applyTitleEntry(entry: TitleEntry, titles: TranscriptTitles): void {\n\tswitch (entry.type) {\n\t\tcase 'custom-title':\n\t\t\tif (typeof entry.customTitle === 'string' && entry.customTitle.length > 0) {\n\t\t\t\ttitles.custom = entry.customTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'ai-title':\n\t\t\tif (typeof entry.aiTitle === 'string' && entry.aiTitle.length > 0) {\n\t\t\t\ttitles.ai = entry.aiTitle;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'agent-name':\n\t\t\tif (typeof entry.agentName === 'string' && entry.agentName.length > 0) {\n\t\t\t\ttitles.agent = entry.agentName;\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\n/** Encodes a working directory into its `~/.claude/projects` directory name. Claude replaces every\n * non-alphanumeric character — not just separators — preserving runs (`/home/e/.claude` →\n * `-home-e--claude`) and case. Separators-only would leave the dot in our own worktree convention\n * (`<repo>.worktrees/<name>`) intact and compute a directory that never exists. */\nexport function encodeProjectDirName(cwd: string): string {\n\treturn cwd.replace(/[^a-zA-Z0-9]/g, '-');\n}\n\nfunction isLikelyTitleLine(line: string): boolean {\n\tif (!line.includes('\"type\"')) return false;\n\treturn line.includes('-title') || line.includes('agent-name');\n}\n\n/** A transcript with no title and no prompt is an aborted or empty session — nothing to name it by,\n * nothing to search it on. */\nfunction hasSummaryContent(summary: TranscriptSessionSummary): boolean {\n\tconst { custom, ai, agent } = summary.titles;\n\treturn custom != null || ai != null || agent != null || summary.lastPrompt != null;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isFile();\n\t} catch (ex) {\n\t\t// See {@link isMissingEntry}: only a genuine absence is an answer.\n\t\tif (!isMissingEntry(ex)) throw ex;\n\n\t\treturn false;\n\t}\n}\n\nasync function directoryExists(path: string): Promise<boolean> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn stats.isDirectory();\n\t} catch (ex) {\n\t\t// \"Not there\" is an answer; anything else (EACCES, EIO, a mount hiccup) is a transient\n\t\t// failure the caller must be able to tell apart — see {@link isMissingEntry}.\n\t\tif (isMissingEntry(ex)) return false;\n\n\t\tthrow ex;\n\t}\n}\n\n/** True for the \"path simply isn't there\" errno codes, as opposed to a transient I/O or permission\n * failure. Callers cache a genuine absence as terminal, so a transient failure must not look like\n * one — it would pin a session to its fallback name until the window reloads. */\nfunction isMissingEntry(ex: unknown): boolean {\n\tconst code = (ex as NodeJS.ErrnoException | undefined)?.code;\n\treturn code === 'ENOENT' || code === 'ENOTDIR';\n}\n\nfunction readSlice(path: string, start: number, end: number): Promise<Buffer> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks: Buffer[] = [];\n\t\tconst stream = createReadStream(path, { start: start, end: end - 1 });\n\t\tstream.on('data', chunk => chunks.push(chunk as Buffer));\n\t\tstream.on('end', () => resolve(Buffer.concat(chunks)));\n\t\tstream.on('error', reject);\n\t});\n}\n"],"mappings":";;;;;;;;AAsFA,MAAM,oBAAoB;;;AAG1B,MAAM,oBAAoB;;;;;;AAM1B,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;;;;;;;;;;;;;AAczB,IAAa,6BAAb,MAAwC;CACvC,yBAA0B,IAAI,IAAwB;CACtD,+BAAgC,IAAI,IAAoB;CACxD,4BAA6B,IAAI,IAA+B;;;CAGhE,yBAA0B,IAAI,IAA4B;;CAE1D;;CAEA,6BAA8B,IAAI,IAA+B;;CAEjE,kCAAmC,IAAI,IAAoB;CAC3D,WAAmB;;;CAGnB,cAAsB;;;;;;;;CAStB,qBAA2B;EAC1B,KAAK;EACL,KAAK,UAAU,MAAM;EACrB,KAAK,OAAO,MAAM;EAClB,KAAK,eAAe,KAAA;CACrB;CAEA,MAAM,QAAQ,WAAmB,KAAgE;EAChG,MAAM,MAAM,EAAE,KAAK;EACnB,KAAK,aAAa,IAAI,WAAW,GAAG;EAEpC,MAAM,SAAS,KAAK,OAAO,IAAI,SAAS;EACxC,MAAM,OAAO,QAAQ,QAAS,MAAM,KAAK,iBAAiB,WAAW,GAAG;EACxE,IAAI,QAAQ,MAAM,OAAO,KAAA;EAEzB,IAAI;EACJ,IAAI;GACH,QAAQ,MAAM,KAAK,IAAI;EACxB,QAAQ;GAGP,IAAI,KAAK,aAAa,IAAI,SAAS,MAAM,KAAK;IAC7C,KAAK,OAAO,OAAO,SAAS;IAC5B,KAAK,aAAa,OAAO,SAAS;GACnC;GACA;EACD;EAEA,IAAI,MAAM,YAAY,QAAQ,WAAW,MAAM,SAAS,QAAQ,MAC/D,OAAO,OAAO;EAIf,MAAM,mBAAmB,UAAU,QAAQ,MAAM,OAAO,OAAO,cAAc,MAAM,UAAU,OAAO;EACpG,MAAM,cAAc,mBAAmB,IAAI,OAAO;EAClD,MAAM,aAA+B,mBAAmB,CAAC,IAAI,EAAE,GAAG,OAAO,OAAO;EAEhF,MAAM,EAAE,QAAQ,gBACf,MAAM,OAAO,cACV,MAAM,KAAK,UAAU,MAAM,aAAa,MAAM,MAAM,WAAW,UAAU,IACzE;GAAE,QAAQ;GAAY,aAAa;EAAY;EAInD,IAAI,KAAK,aAAa,IAAI,SAAS,MAAM,KAAK,OAAO;EAErD,MAAM,QAAoB;GACnB;GACN,SAAS,MAAM;GACf,MAAM,MAAM;GACZ,YAAY;GACJ;EACT;EACA,KAAK,OAAO,IAAI,WAAW,KAAK;EAChC,OAAO;CACR;CAEA,OAAO,WAAyB;EAC/B,KAAK,OAAO,OAAO,SAAS;EAC5B,KAAK,aAAa,OAAO,SAAS;CACnC;;;;;;;;;;;;;;;;CAiBA,MAAM,oBAAoB,WAAmB,KAAsE;EAClH,MAAM,OAAO,MAAM,KAAK,iBAAiB,WAAW,GAAG;EACvD,IAAI,QAAQ,MAAM,OAAO,KAAA;EAEzB,IAAI;EACJ,IAAI;GACH,QAAQ,MAAM,KAAK,IAAI;EACxB,SAAS,IAAI;GAGZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B;EACD;EACA,IAAI,MAAM,SAAS,GAAG,OAAO,KAAA;EAE7B,MAAM,SAA2B,CAAC;EAClC,IAAI;EACJ,IAAI;EAEJ,MAAM,KAAK,gBAAgB;GAAE,OAAO,iBAAiB,MAAM,EAAE,UAAU,OAAO,CAAC;GAAG,WAAW;EAAS,CAAC;EACvG,IAAI;GACH,WAAW,MAAM,QAAQ,IAAI;IAC5B,eAAe,MAAM,WAAW,MAAM;IACtC,IAAI,CAAC,KAAK,SAAS,aAAa,GAAG;IAEnC,MAAM,QAAQ,iBAAiB,MAAM,SAAS;IAC9C,IAAI,OAAO,SAAS,iBAAiB,MAAM,cAAc,QAAQ,MAAM,WAAW,SAAS,GAAG;KAC7F,gBAAgB,MAAM;KACtB,aAAa,MAAM;IACpB;GACD;EACD,UAAU;GACT,GAAG,MAAM;EACV;EAEA,OAAO;GAAU;GAAqB;GAAyB;EAAW;CAC3E;;;;;;;;;;;;;;;;;;;CAoBA,MAAM,aACL,KACA,SACoC;EACpC,MAAM,MAAM,MAAM,KAAK,kBAAkB,GAAG;EAC5C,IAAI,OAAO,MAAM,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAEjD,MAAM,UAAU,MAAM,KAAK,YAAY,GAAG;EAC1C,IAAI,QAAQ,WAAW,GAAG,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAE1D,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,UAAU,SAAS;EAGzB,MAAM,aAAa,QAAQ,QAAO,MAAK,EAAE,OAAO,KAAK,SAAS,IAAI,EAAE,SAAS,MAAM,IAAI;EACvF,MAAM,EAAE,UAAU,cAAc,MAAM,KAAK,gBAAgB,YAAY,OAAO,OAAM,UAAS;GAC5F,MAAM,UAAU,MAAM,KAAK,eAAe,KAAK;GAC/C,OAAO,WAAW,QAAQ,kBAAkB,OAAO,IAAI,UAAU,KAAA;EAClE,CAAC;EAID,OAAO;GAAY;GAAU,OAAO,YAAY,SAAS,SAAS,WAAW;EAAO;CACrF;;;;;;;;;;;CAYA,MAAM,kBACL,YACA,SAC6C;EAC7C,IAAI,WAAW,SAAS,GAAG,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAE3D,MAAM,aAAa,MAAM,KAAK,kBAAkB,SAAS,UAAU;EAEnE,MAAM,WAAW,MAAM,KAAK,iBAAiB,YAAY,UAAU,EAAA,CAAG,QAAO,MAAK,EAAE,OAAO,CAAC;EAC5F,IAAI,QAAQ,WAAW,GAAG,OAAO;GAAE,UAAU,CAAC;GAAG,OAAO;EAAE;EAE1D,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,EAAE,UAAU,cAAc,MAAM,KAAK,gBAAgB,SAAS,OAAO,OAAM,UAAS;GACzF,MAAM,UAAU,MAAM,KAAK,eAAe,KAAK;GAC/C,IAAI,WAAW,QAAQ,CAAC,kBAAkB,OAAO,GAAG,OAAO,KAAA;GAE3D,MAAM,MAAM,MAAM,KAAK,qBAAqB,KAAK;GACjD,OAAO,OAAO,OAAO;IAAE,GAAG;IAAc;GAAI,IAAI,KAAA;EACjD,CAAC;EAGD,OAAO;GAAY;GAAU,OAAO,YAAY,SAAS,SAAS,QAAQ;EAAO;CAClF;;;;CAKA,MAAc,YAAY,KAAgD;EACzE,MAAM,SAAS,KAAK,UAAU,IAAI,GAAG;EACrC,IAAI,UAAU,QAAQ,KAAK,IAAI,IAAI,OAAO,aAAa,mBAAmB,OAAO,OAAO;EAExF,MAAM,MAAM,KAAK;EACjB,IAAI;EACJ,IAAI;GACH,QAAQ,MAAM,KAAK,oBAAoB,GAAG;EAC3C,QAAQ;GACP,OAAO,CAAC;EACT;EAEA,MAAM,WAAW,MAAM,KAAK,YAAY,KAAK,KAAK,EAAA,CAAG,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;EAEvG,IAAI,QAAQ,KAAK,aAChB,KAAK,UAAU,IAAI,KAAK;GAAW;GAAS,YAAY,KAAK,IAAI;EAAE,CAAC;EAGrE,OAAO;CACR;;;;CAKA,MAAgB,YAAY,KAAa,OAAoD;EAc5F,QAAO,MAbe,QAAQ,WAC7B,MAAM,IAAI,OAAO,SAA0C;GAC1D,MAAM,OAAO,KAAK,KAAK,IAAI;GAC3B,MAAM,QAAQ,MAAM,KAAK,IAAI;GAC7B,OAAO;IACN,WAAW,SAAS,MAAM,QAAQ;IAC5B;IACN,gBAAgB,MAAM;IACtB,MAAM,MAAM;GACb;EACD,CAAC,CACF,EAAA,CAEe,QAAO,MAAK,EAAE,WAAW,WAAW,CAAC,CAAC,KAAI,MAAK,EAAE,KAAK;CACtE;;;;CAKA,MAAc,oBAAoB,KAAgC;EACjE,MAAM,SAAS,KAAK,OAAO,IAAI,GAAG;EAClC,IAAI,UAAU,QAAQ,KAAK,IAAI,IAAI,OAAO,aAAa,mBAAmB,OAAO,OAAO;EAExF,MAAM,MAAM,KAAK;EAEjB,MAAM,SAAQ,MADQ,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC,EAAA,CACpC,QAAO,MAAK,EAAE,OAAO,KAAK,EAAE,KAAK,SAAS,QAAQ,CAAC,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI;EAC1F,IAAI,QAAQ,KAAK,aAChB,KAAK,OAAO,IAAI,KAAK;GAAS;GAAO,YAAY,KAAK,IAAI;EAAE,CAAC;EAG9D,OAAO;CACR;;;CAIA,MAAc,kBAAqC;EAClD,MAAM,SAAS,KAAK;EACpB,IAAI,UAAU,QAAQ,KAAK,IAAI,IAAI,OAAO,aAAa,mBAAmB,OAAO,OAAO;EAExF,MAAM,MAAM,KAAK;EACjB,MAAM,OAAO,KAAK,gBAAgB;EAElC,MAAM,QAAO,MADS,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC,EAAA,CACtC,QAAO,MAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAI,MAAK,KAAK,MAAM,EAAE,IAAI,CAAC;EAC7E,IAAI,QAAQ,KAAK,aAChB,KAAK,eAAe;GAAQ;GAAM,YAAY,KAAK,IAAI;EAAE;EAG1D,OAAO;CACR;;CAGA,MAAc,iBACb,YACA,YACoC;EACpC,IAAI,YAAY;EAChB,IAAI,cAAc,MACjB,IAAI;GACH,MAAM,QAAQ,MAAM,KAAK,oBAAoB,UAAU;GACvD,MAAM,mBAAmB,IAAI,IAAI,MAAM,KAAI,SAAQ,SAAS,MAAM,QAAQ,CAAC,CAAC;GAC5E,YAAY,IAAI,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,QAAO,OAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;GAC3E,IAAI,UAAU,SAAS,GAAG,OAAO,CAAC;EACnC,QAAQ;GAGP,OAAO,CAAC;EACT;EAGD,IAAI;EACJ,IAAI;GACH,OAAO,MAAM,KAAK,gBAAgB;EACnC,SAAS,IAAI;GACZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B,OAAO,CAAC;EACT;EAEA,MAAM,cAAc,MAAM,QAAQ,WACjC,KAAK,IAAI,OAAM,QAAO;GACrB,IAAI,QAAQ,YAAY,OAAO,CAAC;GAEhC,MAAM,SAAS,MAAM,KAAK,oBAAoB,GAAG,EAAA,CAAG,QAAO,SAC1D,UAAU,IAAI,SAAS,MAAM,QAAQ,CAAC,CACvC;GACA,OAAO,KAAK,YAAY,KAAK,KAAK;EACnC,CAAC,CACF;EAEA,MAAM,8BAAc,IAAI,IAAoC;EAC5D,KAAK,MAAM,cAAc,aAAa;GACrC,IAAI,WAAW,WAAW,aAAa;GAEvC,KAAK,MAAM,SAAS,WAAW,OAAO;IACrC,MAAM,WAAW,YAAY,IAAI,MAAM,SAAS;IAChD,IAAI,YAAY,QAAQ,MAAM,iBAAiB,SAAS,gBACvD,YAAY,IAAI,MAAM,WAAW,KAAK;GAExC;EACD;EAEA,OAAO,CAAC,GAAG,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;CACpF;;;;CAKA,MAAc,gBACb,YACA,OACA,SACiD;EACjD,MAAM,WAAgB,CAAC;EACvB,MAAM,gBAAgB,YAAyD;GAC9E,KAAK,MAAM,UAAU,SAAS;IAC7B,IAAI,OAAO,WAAW,eAAe,OAAO,SAAS,MAAM;IAE3D,SAAS,KAAK,OAAO,KAAK;GAC3B;EACD;EAGA,IAAI,SAAS,GAAG;GACf,aAAa,MAAM,QAAQ,WAAW,WAAW,IAAI,OAAO,CAAC,CAAC;GAC9D,OAAO;IAAY;IAAU,WAAW;GAAK;EAC9C;EAMA,IAAI,SAAS;EACb,OAAO,SAAS,SAAS,SAAS,SAAS,WAAW,QAAQ;GAG7D,MAAM,QAAQ,WAAW,MAAM,QAAQ,UAAU,QAAQ,SAAS,OAAO;GACzE,UAAU,MAAM;GAChB,aAAa,MAAM,QAAQ,WAAW,MAAM,IAAI,OAAO,CAAC,CAAC;EAC1D;EAEA,OAAO;GAAY;GAAU,WAAW,UAAU,WAAW;EAAO;CACrE;;CAGA,MAAc,eAAe,OAA8E;EAC1G,MAAM,SAAS,KAAK,WAAW,IAAI,MAAM,SAAS;EAClD,IACC,UAAU,QACV,OAAO,SAAS,MAAM,QACtB,OAAO,YAAY,MAAM,kBACzB,OAAO,SAAS,MAAM,MACrB;GAED,KAAK,WAAW,OAAO,MAAM,SAAS;GACtC,KAAK,WAAW,IAAI,MAAM,WAAW,MAAM;GAC3C,OAAO,OAAO;EACf;EAEA,IAAI;EACJ,IAAI;GACH,UAAU,MAAM,KAAK,YAAY,KAAK;EACvC,QAAQ;GACP;EACD;EAEA,KAAK,WAAW,IAAI,MAAM,WAAW;GACpC,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,MAAM,MAAM;GACH;EACV,CAAC;EACD,IAAI,KAAK,WAAW,OAAO,mBAAmB;GAC7C,MAAM,SAAS,KAAK,WAAW,KAAK,CAAC,CAAC,KAAK;GAC3C,IAAI,CAAC,OAAO,MACX,KAAK,WAAW,OAAO,OAAO,KAAK;EAErC;EACA,OAAO;CACR;;;;CAKA,MAAc,qBAAqB,OAA4D;EAC9F,MAAM,SAAS,KAAK,gBAAgB,IAAI,MAAM,IAAI;EAClD,IAAI,UAAU,MAAM,OAAO;EAE3B,MAAM,aAAa,QAAQ,MAAM,IAAI;EACrC,MAAM,iBAAiB,SAAS,UAAU;EAC1C,MAAM,qBAAqB,QAC1B,qBAAqB,GAAG,CAAC,CAAC,YAAY,MAAM,eAAe,YAAY;EAExE,IAAI;GACH,MAAM,QAAQ,KAAK,MAAM,MAAM,SAAS,KAAK,YAAY,qBAAqB,GAAG,MAAM,CAAC;GAGxF,IAAI,MAAM,QAAQ,MAAM,OAAO,GAAG;IACjC,MAAM,UAAU,MAAM,QAAQ,MAAK,SAAQ,MAAM,cAAc,MAAM,SAAS;IAC9E,IAAI,OAAO,SAAS,gBAAgB,YAAY,kBAAkB,QAAQ,WAAW,GAAG;KACvF,KAAK,sBAAsB,MAAM,MAAM,QAAQ,WAAW;KAC1D,OAAO,QAAQ;IAChB;GACD;EACD,QAAQ,CAER;EAEA,MAAM,KAAK,gBAAgB;GAAE,OAAO,iBAAiB,MAAM,MAAM,EAAE,UAAU,OAAO,CAAC;GAAG,WAAW;EAAS,CAAC;EAC7G,IAAI;GACH,WAAW,MAAM,QAAQ,IAAI;IAC5B,IAAI,CAAC,KAAK,SAAS,SAAO,GAAG;IAE7B,IAAI;IACJ,IAAI;KACH,SAAS,KAAK,MAAM,IAAI;IACzB,QAAQ;KACP;IACD;IACA,IAAI,OAAO,aAAa,QAAQ,OAAO,cAAc,MAAM,WAAW;IAEtE,IAAI,OAAO,OAAO,QAAQ,YAAY,kBAAkB,OAAO,GAAG,GAAG;KACpE,KAAK,sBAAsB,MAAM,MAAM,OAAO,GAAG;KACjD,OAAO,OAAO;IACf;GACD;EACD,UAAU;GACT,GAAG,MAAM;EACV;CAGD;CAEA,sBAA8B,MAAc,KAAmB;EAC9D,KAAK,gBAAgB,IAAI,MAAM,GAAG;EAClC,IAAI,KAAK,gBAAgB,OAAO,mBAAmB;GAClD,MAAM,SAAS,KAAK,gBAAgB,KAAK,CAAC,CAAC,KAAK;GAChD,IAAI,CAAC,OAAO,MACX,KAAK,gBAAgB,OAAO,OAAO,KAAK;EAE1C;CACD;;;;;;;;;CAUA,MAAgB,YAAY,OAAkE;EAC7F,MAAM,SAA2B,CAAC;EAClC,IAAI;EAEJ,MAAM,SAAS,QAAgB,yBAAwC;GAEtE,MAAM,QADO,OAAO,SAAS,MACZ,CAAC,CAAC,MAAM,IAAI;GAE7B,IAAI,sBACH,MAAM,MAAM;GAEb,KAAK,MAAM,QAAQ,OAAO;IACzB,MAAM,SAAS,iBAAiB,MAAM,MAAM,SAAS;IACrD,IAAI,UAAU,MAAM;IAEpB,gBAAgB,QAAQ,MAAM;IAC9B,IAAI,OAAO,cAAc,QAAQ,OAAO,WAAW,SAAS,GAC3D,aAAa,OAAO;GAEtB;EACD;EAEA,MAAM,SAAS,MAAM,KAAK,MAAM,MAAM,GAAG;EACzC,IAAI;GACH,IAAI,MAAM,QAAQ,oBAAoB,GAAG;IACxC,MAAM,QAAQ,OAAO,MAAM,MAAM,IAAI;IACrC,MAAM,OAAO,KAAK,OAAO,GAAG,MAAM,MAAM,CAAC;IACzC,MAAM,OAAO,KAAK;GACnB,OAAO;IACN,MAAM,OAAO,OAAO,MAAM,iBAAiB;IAC3C,MAAM,OAAO,KAAK,MAAM,GAAG,mBAAmB,CAAC;IAC/C,MAAM,MAAM,KAAK;IAEjB,MAAM,OAAO,OAAO,MAAM,iBAAiB;IAC3C,MAAM,OAAO,KAAK,MAAM,GAAG,mBAAmB,MAAM,OAAO,iBAAiB;IAC5E,MAAM,MAAM,IAAI;GACjB;EACD,UAAU;GACT,MAAM,OAAO,MAAM;EACpB;EAEA,OAAO;GACN,GAAG;GACK;GACI;EACb;CACD;CAEA,kBAAoC;EACnC,OAAO,KAAK,QAAQ,GAAG,WAAW,UAAU;CAC7C;;;;;;CAOA,MAAgB,kBAAkB,KAAsD;EACvF,IAAI,OAAO,QAAQ,IAAI,WAAW,GAAG,OAAO,KAAA;EAE5C,MAAM,OAAO,KAAK,gBAAgB;EAClC,MAAM,UAAU,qBAAqB,GAAG;EAExC,MAAM,QAAQ,KAAK,MAAM,OAAO;EAChC,IAAI,MAAM,gBAAgB,KAAK,GAAG,OAAO;EAEzC,IAAI;EACJ,IAAI;GACH,OAAO,MAAM,QAAQ,IAAI;EAC1B,SAAS,IAAI;GAGZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B;EACD;EAEA,MAAM,UAAU,QAAQ,YAAY;EACpC,MAAM,QAAQ,KAAK,MAAK,MAAK,MAAM,WAAW,EAAE,YAAY,MAAM,OAAO;EACzE,OAAO,SAAS,OAAO,KAAK,MAAM,KAAK,IAAI,KAAA;CAC5C;CAEA,MAAgB,iBAAiB,WAAmB,KAAsD;EACzG,MAAM,WAAW,GAAG,UAAU;EAE9B,MAAM,MAAM,MAAM,KAAK,kBAAkB,GAAG;EAC5C,IAAI,OAAO,MAAM;GAChB,MAAM,YAAY,KAAK,KAAK,QAAQ;GACpC,IAAI,MAAM,WAAW,SAAS,GAAG,OAAO;EACzC;EAIA,MAAM,OAAO,KAAK,gBAAgB;EAClC,IAAI;EACJ,IAAI;GACH,OAAO,MAAM,QAAQ,IAAI;EAC1B,SAAS,IAAI;GACZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;GAE/B;EACD;EAQA,QAAO,MANkB,QAAQ,IAChC,KAAK,IAAI,OAAM,MAAK;GACnB,MAAM,YAAY,KAAK,MAAM,GAAG,QAAQ;GACxC,OAAQ,MAAM,WAAW,SAAS,IAAK,YAAY,KAAA;EACpD,CAAC,CACF,EAAA,CACkB,MAAK,MAAK,KAAK,IAAI;CACtC;;;;;;;CAQA,MAAgB,UACf,MACA,OACA,KACA,WACA,YAC6D;EAC7D,MAAM,SAAS,MAAM,UAAU,MAAM,OAAO,GAAG;EAC/C,MAAM,SAA2B,EAAE,GAAG,WAAW;EAEjD,IAAI,iBAAiB;EACrB,IAAI,YAAY;EAChB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GACvC,IAAI,OAAO,OAAO,IAAM;GAIxB,MAAM,UAAU,IAAI,aAAa,OAAO,IAAI,OAAO,KAAO,IAAI,IAAI;GAClE,IAAI,UAAU,WAEb,eADa,OAAO,SAAS,QAAQ,WAAW,OAC9B,GAAG,WAAW,MAAM;GAEvC,YAAY,IAAI;GAChB,iBAAiB,QAAQ,IAAI;EAC9B;EAEA,OAAO;GAAU;GAAQ,aAAa;EAAe;CACtD;AACD;AAEA,SAAS,eAAe,MAAc,WAAmB,QAAgC;CACxF,IAAI,CAAC,kBAAkB,IAAI,GAAG;CAE9B,IAAI;CACJ,IAAI;EACH,QAAQ,KAAK,MAAM,IAAI;CACxB,QAAQ;EACP;CACD;CACA,IAAI,MAAM,aAAa,QAAQ,MAAM,cAAc,WAAW;CAE9D,gBAAgB,OAAO,MAAM;AAC9B;;;;AAKA,SAAS,iBAAiB,MAAc,WAA2C;CAClF,IAAI,KAAK,WAAW,KAAK,CAAC,KAAK,SAAS,UAAQ,GAAG,OAAO,KAAA;CAE1D,IAAI;CACJ,IAAI;EACH,QAAQ,KAAK,MAAM,IAAI;CACxB,QAAQ;EACP;CACD;CACA,IAAI,MAAM,aAAa,QAAQ,MAAM,cAAc,WAAW,OAAO,KAAA;CAErE,OAAO;AACR;AAEA,SAAS,gBAAgB,OAAmB,QAAgC;CAC3E,QAAQ,MAAM,MAAd;EACC,KAAK;GACJ,IAAI,OAAO,MAAM,gBAAgB,YAAY,MAAM,YAAY,SAAS,GACvE,OAAO,SAAS,MAAM;GAEvB;EACD,KAAK;GACJ,IAAI,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,SAAS,GAC/D,OAAO,KAAK,MAAM;GAEnB;EACD,KAAK,cACJ,IAAI,OAAO,MAAM,cAAc,YAAY,MAAM,UAAU,SAAS,GACnE,OAAO,QAAQ,MAAM;CAGxB;AACD;;;;;AAMA,SAAgB,qBAAqB,KAAqB;CACzD,OAAO,IAAI,QAAQ,iBAAiB,GAAG;AACxC;AAEA,SAAS,kBAAkB,MAAuB;CACjD,IAAI,CAAC,KAAK,SAAS,UAAQ,GAAG,OAAO;CACrC,OAAO,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,YAAY;AAC7D;;;AAIA,SAAS,kBAAkB,SAA4C;CACtE,MAAM,EAAE,QAAQ,IAAI,UAAU,QAAQ;CACtC,OAAO,UAAU,QAAQ,MAAM,QAAQ,SAAS,QAAQ,QAAQ,cAAc;AAC/E;AAEA,eAAe,WAAW,MAAgC;CACzD,IAAI;EAEH,QAAO,MADa,KAAK,IAAI,EAAA,CAChB,OAAO;CACrB,SAAS,IAAI;EAEZ,IAAI,CAAC,eAAe,EAAE,GAAG,MAAM;EAE/B,OAAO;CACR;AACD;AAEA,eAAe,gBAAgB,MAAgC;CAC9D,IAAI;EAEH,QAAO,MADa,KAAK,IAAI,EAAA,CAChB,YAAY;CAC1B,SAAS,IAAI;EAGZ,IAAI,eAAe,EAAE,GAAG,OAAO;EAE/B,MAAM;CACP;AACD;;;;AAKA,SAAS,eAAe,IAAsB;CAC7C,MAAM,OAAQ,IAA0C;CACxD,OAAO,SAAS,YAAY,SAAS;AACtC;AAEA,SAAS,UAAU,MAAc,OAAe,KAA8B;CAC7E,OAAO,IAAI,SAAS,SAAS,WAAW;EACvC,MAAM,SAAmB,CAAC;EAC1B,MAAM,SAAS,iBAAiB,MAAM;GAAS;GAAO,KAAK,MAAM;EAAE,CAAC;EACpE,OAAO,GAAG,SAAQ,UAAS,OAAO,KAAK,KAAe,CAAC;EACvD,OAAO,GAAG,aAAa,QAAQ,OAAO,OAAO,MAAM,CAAC,CAAC;EACrD,OAAO,GAAG,SAAS,MAAM;CAC1B,CAAC;AACF"}
@@ -36,7 +36,10 @@ export declare function classifyNetworkError(ex: unknown): AIErrorReason.NoNetwo
36
36
  *
37
37
  * `RequestTooLarge` and `NoRequestData` are therefore deliberately excluded — they're properties of
38
38
  * the one request that failed.
39
+ *
40
+ * An `AuthenticationRequiredError` (expired GitKraken session) counts too: it isn't an `AIError`, but
41
+ * every remaining request will fail identically until the user signs in again.
39
42
  */
40
- export declare function isAIUnavailableError(ex: unknown): ex is AIError;
43
+ export declare function isAIUnavailableError(ex: unknown): ex is AIError | AuthenticationRequiredError;
41
44
  //#endregion
42
45
  //# sourceMappingURL=errors.d.ts.map
@@ -139,8 +139,12 @@ function classifyNetworkError(ex) {
139
139
  *
140
140
  * `RequestTooLarge` and `NoRequestData` are therefore deliberately excluded — they're properties of
141
141
  * the one request that failed.
142
+ *
143
+ * An `AuthenticationRequiredError` (expired GitKraken session) counts too: it isn't an `AIError`, but
144
+ * every remaining request will fail identically until the user signs in again.
142
145
  */
143
146
  function isAIUnavailableError(ex) {
147
+ if (ex instanceof AuthenticationRequiredError) return true;
144
148
  if (!(ex instanceof AIError)) return false;
145
149
  switch (ex.reason) {
146
150
  case 0:
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../../../plus/ai/src/errors.ts"],"sourcesContent":["import * as l10n from '@vscode/l10n';\n\nexport const enum AIErrorReason {\n\tDeniedByOrganization,\n\tDeniedByUser,\n\tNoEntitlement,\n\tNoRequestData,\n\tRateLimitExceeded,\n\tRateLimitOrFundsExceeded,\n\tRequestTooLarge,\n\tModelNotSupported,\n\tServiceCapacityExceeded,\n\tUnauthorized,\n\tUserQuotaExceeded,\n\tNoNetwork,\n\tUnreachable,\n}\n\nexport class AIError extends Error {\n\treadonly diagnosticMessage: string;\n\treadonly original?: Error;\n\treadonly reason: AIErrorReason | undefined;\n\n\tconstructor(reason: AIErrorReason, original?: Error) {\n\t\tlet message: string;\n\t\tlet diagnosticMessage: string;\n\t\tswitch (reason) {\n\t\t\tcase AIErrorReason.NoEntitlement:\n\t\t\t\tdiagnosticMessage = 'You do not have the required entitlement to use this feature';\n\t\t\t\tmessage = l10n.t('You do not have the required entitlement to use this feature');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.RequestTooLarge:\n\t\t\t\tdiagnosticMessage = 'The request is too large';\n\t\t\t\tmessage = l10n.t('The request is too large');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.UserQuotaExceeded:\n\t\t\t\tdiagnosticMessage = 'You have exceeded your user token limit';\n\t\t\t\tmessage = l10n.t('You have exceeded your user token limit');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.RateLimitExceeded:\n\t\t\t\tdiagnosticMessage = 'Rate limit exceeded';\n\t\t\t\tmessage = l10n.t('Rate limit exceeded');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.RateLimitOrFundsExceeded:\n\t\t\t\tdiagnosticMessage = 'Rate limit exceeded or your account is out of funds';\n\t\t\t\tmessage = l10n.t('Rate limit exceeded or your account is out of funds');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.ServiceCapacityExceeded:\n\t\t\t\tdiagnosticMessage = 'Service capacity exceeded';\n\t\t\t\tmessage = l10n.t('Service capacity exceeded');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.NoNetwork:\n\t\t\t\tdiagnosticMessage = 'Unable to reach the AI service. Please check your internet connection.';\n\t\t\t\tmessage = l10n.t('Unable to reach the AI service. Please check your internet connection.');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.Unreachable:\n\t\t\t\tdiagnosticMessage = 'The AI service is temporarily unreachable.';\n\t\t\t\tmessage = l10n.t('The AI service is temporarily unreachable.');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.NoRequestData:\n\t\t\t\tdiagnosticMessage = original?.message ?? 'No data was provided for the request';\n\t\t\t\tmessage = original?.message ?? l10n.t('No data was provided for the request');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.ModelNotSupported:\n\t\t\t\tdiagnosticMessage = 'Model not supported for this request';\n\t\t\t\tmessage = l10n.t('Model not supported for this request');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.Unauthorized:\n\t\t\t\tdiagnosticMessage = 'You are not authorized to use the specified provider or model';\n\t\t\t\tmessage = l10n.t('You are not authorized to use the specified provider or model');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.DeniedByOrganization:\n\t\t\t\tdiagnosticMessage = 'Your organization has denied access to the specified provider or model';\n\t\t\t\tmessage = l10n.t('Your organization has denied access to the specified provider or model');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.DeniedByUser:\n\t\t\t\tdiagnosticMessage = 'You have denied access to the specified provider or model';\n\t\t\t\tmessage = l10n.t('You have denied access to the specified provider or model');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tdiagnosticMessage = original?.message ?? 'An unknown error occurred';\n\t\t\t\tmessage = original?.message ?? l10n.t('An unknown error occurred');\n\t\t\t\tbreak;\n\t\t}\n\n\t\tsuper(message);\n\n\t\tthis.diagnosticMessage = diagnosticMessage;\n\t\tthis.original = original;\n\t\tthis.reason = reason;\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n\n\tget diagnosticString(): string {\n\t\treturn `${this.name}: ${this.diagnosticMessage}`;\n\t}\n}\n\nexport class AuthenticationRequiredError extends Error {\n\tconstructor() {\n\t\tsuper(l10n.t('Authentication required'));\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\nexport class AINoRequestDataError extends AIError {\n\tconstructor(message?: string) {\n\t\tsuper(AIErrorReason.NoRequestData, message ? new Error(message) : undefined);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\nconst noNetworkErrorCodes = new Set([\n\t'ENOTFOUND',\n\t'ECONNREFUSED',\n\t'EAI_AGAIN',\n\t'EHOSTUNREACH',\n\t'ENETUNREACH',\n\t'ENETDOWN',\n\t'UND_ERR_CONNECT_TIMEOUT',\n]);\n\nconst unreachableErrorCodes = new Set(['ECONNRESET', 'ETIMEDOUT', 'UND_ERR_SOCKET']);\n\nexport function classifyNetworkError(ex: unknown): AIErrorReason.NoNetwork | AIErrorReason.Unreachable | undefined {\n\tlet current: unknown = ex;\n\tlet sawFetchFailed = false;\n\tfor (let depth = 0; depth < 5 && current != null; depth++) {\n\t\tif (!(current instanceof Error)) break;\n\n\t\tif (current.name === 'TypeError' && current.message === 'fetch failed') {\n\t\t\tsawFetchFailed = true;\n\t\t}\n\t\tconst code = (current as { code?: unknown }).code;\n\t\tif (typeof code === 'string') {\n\t\t\tif (noNetworkErrorCodes.has(code)) return AIErrorReason.NoNetwork;\n\t\t\tif (unreachableErrorCodes.has(code)) return AIErrorReason.Unreachable;\n\t\t}\n\n\t\tcurrent = (current as { cause?: unknown }).cause;\n\t}\n\treturn sawFetchFailed ? AIErrorReason.NoNetwork : undefined;\n}\n\n/**\n * Whether an error means AI itself is unavailable, rather than this particular request being\n * unacceptable. The distinction matters to any caller running a loop: an unavailable-AI failure will\n * repeat identically for every remaining item, so the loop should stop and say so, while a\n * request-shaped failure (too large, no data) may well succeed on the next item.\n *\n * `RequestTooLarge` and `NoRequestData` are therefore deliberately excluded — they're properties of\n * the one request that failed.\n */\nexport function isAIUnavailableError(ex: unknown): ex is AIError {\n\tif (!(ex instanceof AIError)) return false;\n\n\tswitch (ex.reason) {\n\t\tcase AIErrorReason.DeniedByOrganization:\n\t\tcase AIErrorReason.DeniedByUser:\n\t\tcase AIErrorReason.NoEntitlement:\n\t\tcase AIErrorReason.RateLimitExceeded:\n\t\tcase AIErrorReason.RateLimitOrFundsExceeded:\n\t\tcase AIErrorReason.ServiceCapacityExceeded:\n\t\tcase AIErrorReason.Unauthorized:\n\t\tcase AIErrorReason.UserQuotaExceeded:\n\t\tcase AIErrorReason.NoNetwork:\n\t\tcase AIErrorReason.Unreachable:\n\t\tcase AIErrorReason.ModelNotSupported:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../../../plus/ai/src/errors.ts"],"sourcesContent":["import * as l10n from '@vscode/l10n';\n\nexport const enum AIErrorReason {\n\tDeniedByOrganization,\n\tDeniedByUser,\n\tNoEntitlement,\n\tNoRequestData,\n\tRateLimitExceeded,\n\tRateLimitOrFundsExceeded,\n\tRequestTooLarge,\n\tModelNotSupported,\n\tServiceCapacityExceeded,\n\tUnauthorized,\n\tUserQuotaExceeded,\n\tNoNetwork,\n\tUnreachable,\n}\n\nexport class AIError extends Error {\n\treadonly diagnosticMessage: string;\n\treadonly original?: Error;\n\treadonly reason: AIErrorReason | undefined;\n\n\tconstructor(reason: AIErrorReason, original?: Error) {\n\t\tlet message: string;\n\t\tlet diagnosticMessage: string;\n\t\tswitch (reason) {\n\t\t\tcase AIErrorReason.NoEntitlement:\n\t\t\t\tdiagnosticMessage = 'You do not have the required entitlement to use this feature';\n\t\t\t\tmessage = l10n.t('You do not have the required entitlement to use this feature');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.RequestTooLarge:\n\t\t\t\tdiagnosticMessage = 'The request is too large';\n\t\t\t\tmessage = l10n.t('The request is too large');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.UserQuotaExceeded:\n\t\t\t\tdiagnosticMessage = 'You have exceeded your user token limit';\n\t\t\t\tmessage = l10n.t('You have exceeded your user token limit');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.RateLimitExceeded:\n\t\t\t\tdiagnosticMessage = 'Rate limit exceeded';\n\t\t\t\tmessage = l10n.t('Rate limit exceeded');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.RateLimitOrFundsExceeded:\n\t\t\t\tdiagnosticMessage = 'Rate limit exceeded or your account is out of funds';\n\t\t\t\tmessage = l10n.t('Rate limit exceeded or your account is out of funds');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.ServiceCapacityExceeded:\n\t\t\t\tdiagnosticMessage = 'Service capacity exceeded';\n\t\t\t\tmessage = l10n.t('Service capacity exceeded');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.NoNetwork:\n\t\t\t\tdiagnosticMessage = 'Unable to reach the AI service. Please check your internet connection.';\n\t\t\t\tmessage = l10n.t('Unable to reach the AI service. Please check your internet connection.');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.Unreachable:\n\t\t\t\tdiagnosticMessage = 'The AI service is temporarily unreachable.';\n\t\t\t\tmessage = l10n.t('The AI service is temporarily unreachable.');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.NoRequestData:\n\t\t\t\tdiagnosticMessage = original?.message ?? 'No data was provided for the request';\n\t\t\t\tmessage = original?.message ?? l10n.t('No data was provided for the request');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.ModelNotSupported:\n\t\t\t\tdiagnosticMessage = 'Model not supported for this request';\n\t\t\t\tmessage = l10n.t('Model not supported for this request');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.Unauthorized:\n\t\t\t\tdiagnosticMessage = 'You are not authorized to use the specified provider or model';\n\t\t\t\tmessage = l10n.t('You are not authorized to use the specified provider or model');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.DeniedByOrganization:\n\t\t\t\tdiagnosticMessage = 'Your organization has denied access to the specified provider or model';\n\t\t\t\tmessage = l10n.t('Your organization has denied access to the specified provider or model');\n\t\t\t\tbreak;\n\t\t\tcase AIErrorReason.DeniedByUser:\n\t\t\t\tdiagnosticMessage = 'You have denied access to the specified provider or model';\n\t\t\t\tmessage = l10n.t('You have denied access to the specified provider or model');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tdiagnosticMessage = original?.message ?? 'An unknown error occurred';\n\t\t\t\tmessage = original?.message ?? l10n.t('An unknown error occurred');\n\t\t\t\tbreak;\n\t\t}\n\n\t\tsuper(message);\n\n\t\tthis.diagnosticMessage = diagnosticMessage;\n\t\tthis.original = original;\n\t\tthis.reason = reason;\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n\n\tget diagnosticString(): string {\n\t\treturn `${this.name}: ${this.diagnosticMessage}`;\n\t}\n}\n\nexport class AuthenticationRequiredError extends Error {\n\tconstructor() {\n\t\tsuper(l10n.t('Authentication required'));\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\nexport class AINoRequestDataError extends AIError {\n\tconstructor(message?: string) {\n\t\tsuper(AIErrorReason.NoRequestData, message ? new Error(message) : undefined);\n\n\t\tError.captureStackTrace?.(this, new.target);\n\t}\n}\n\nconst noNetworkErrorCodes = new Set([\n\t'ENOTFOUND',\n\t'ECONNREFUSED',\n\t'EAI_AGAIN',\n\t'EHOSTUNREACH',\n\t'ENETUNREACH',\n\t'ENETDOWN',\n\t'UND_ERR_CONNECT_TIMEOUT',\n]);\n\nconst unreachableErrorCodes = new Set(['ECONNRESET', 'ETIMEDOUT', 'UND_ERR_SOCKET']);\n\nexport function classifyNetworkError(ex: unknown): AIErrorReason.NoNetwork | AIErrorReason.Unreachable | undefined {\n\tlet current: unknown = ex;\n\tlet sawFetchFailed = false;\n\tfor (let depth = 0; depth < 5 && current != null; depth++) {\n\t\tif (!(current instanceof Error)) break;\n\n\t\tif (current.name === 'TypeError' && current.message === 'fetch failed') {\n\t\t\tsawFetchFailed = true;\n\t\t}\n\t\tconst code = (current as { code?: unknown }).code;\n\t\tif (typeof code === 'string') {\n\t\t\tif (noNetworkErrorCodes.has(code)) return AIErrorReason.NoNetwork;\n\t\t\tif (unreachableErrorCodes.has(code)) return AIErrorReason.Unreachable;\n\t\t}\n\n\t\tcurrent = (current as { cause?: unknown }).cause;\n\t}\n\treturn sawFetchFailed ? AIErrorReason.NoNetwork : undefined;\n}\n\n/**\n * Whether an error means AI itself is unavailable, rather than this particular request being\n * unacceptable. The distinction matters to any caller running a loop: an unavailable-AI failure will\n * repeat identically for every remaining item, so the loop should stop and say so, while a\n * request-shaped failure (too large, no data) may well succeed on the next item.\n *\n * `RequestTooLarge` and `NoRequestData` are therefore deliberately excluded — they're properties of\n * the one request that failed.\n *\n * An `AuthenticationRequiredError` (expired GitKraken session) counts too: it isn't an `AIError`, but\n * every remaining request will fail identically until the user signs in again.\n */\nexport function isAIUnavailableError(ex: unknown): ex is AIError | AuthenticationRequiredError {\n\tif (ex instanceof AuthenticationRequiredError) return true;\n\tif (!(ex instanceof AIError)) return false;\n\n\tswitch (ex.reason) {\n\t\tcase AIErrorReason.DeniedByOrganization:\n\t\tcase AIErrorReason.DeniedByUser:\n\t\tcase AIErrorReason.NoEntitlement:\n\t\tcase AIErrorReason.RateLimitExceeded:\n\t\tcase AIErrorReason.RateLimitOrFundsExceeded:\n\t\tcase AIErrorReason.ServiceCapacityExceeded:\n\t\tcase AIErrorReason.Unauthorized:\n\t\tcase AIErrorReason.UserQuotaExceeded:\n\t\tcase AIErrorReason.NoNetwork:\n\t\tcase AIErrorReason.Unreachable:\n\t\tcase AIErrorReason.ModelNotSupported:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
@@ -234,7 +234,7 @@ var MistralProvider = class extends OpenAICompatibleProviderBase {
234
234
  return super.fetchCore(action, model, apiKey, request, signal);
235
235
  }
236
236
  async handleFetchFailure(rsp, action, model, retries, maxInputTokens, body, sentTools) {
237
- if (rsp.status !== 404 && rsp.status !== 429) {
237
+ if (rsp.status !== 404 && rsp.status !== 429 && rsp.status !== 401 && rsp.status !== 403) {
238
238
  let json;
239
239
  try {
240
240
  json = body != null ? JSON.parse(body) : await rsp.json();
@@ -1 +1 @@
1
- {"version":3,"file":"mistralProvider.js","names":["provider"],"sources":["../../../../../plus/ai/src/providers/mistralProvider.ts"],"sourcesContent":["import { mistralProviderDescriptor as provider } from '../constants.js';\nimport { AIError, AIErrorReason } from '../errors.js';\nimport type { AIActionType, AIModel } from '../models/model.js';\nimport { getReducedMaxInputTokens } from '../utils/ai.utils.js';\nimport { OpenAICompatibleProviderBase } from './openAICompatibleProviderBase.js';\n\ntype MistralModel = AIModel<typeof provider.id>;\nconst models: MistralModel[] = [\n\t{\n\t\tid: 'mistral-medium-latest',\n\t\tname: 'Mistral Medium 3.5',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'mistral-medium-3-5',\n\t\tname: 'Mistral Medium 3.5',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-medium-2505',\n\t\tname: 'Mistral Medium',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'zai-glm-5-2',\n\t\tname: 'GLM 5.2',\n\t\tmaxTokens: { input: 1000000, output: 128000 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'mistral-small-latest',\n\t\tname: 'Mistral Small 4',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\tdefault: true,\n\t},\n\t{\n\t\tid: 'mistral-small-2603',\n\t\tname: 'Mistral Small 4',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-small-2503',\n\t\tname: 'Mistral Small',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-large-latest',\n\t\tname: 'Mistral Large 3',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'mistral-large-2512',\n\t\tname: 'Mistral Large 3',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-large-2411',\n\t\tname: 'Mistral Large',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'ministral-14b-latest',\n\t\tname: 'Ministral 3 14B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'ministral-14b-2512',\n\t\tname: 'Ministral 3 14B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'ministral-8b-latest',\n\t\tname: 'Ministral 3 8B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'ministral-8b-2512',\n\t\tname: 'Ministral 3 8B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'ministral-3b-latest',\n\t\tname: 'Ministral 3 3B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'ministral-3b-2512',\n\t\tname: 'Ministral 3 3B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'codestral-latest',\n\t\tname: 'Codestral',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'codestral-2508',\n\t\tname: 'Codestral',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'codestral-2501',\n\t\tname: 'Codestral',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'devstral-small-latest',\n\t\tname: 'Devstral Small',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'devstral-small-2505',\n\t\tname: 'Devstral Small',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n];\n\nexport class MistralProvider extends OpenAICompatibleProviderBase<typeof provider.id> {\n\treadonly id = provider.id;\n\treadonly name = provider.name;\n\treadonly supportsTools = true;\n\tprotected readonly descriptor = provider;\n\tprotected readonly config = {\n\t\tkeyUrl: 'https://console.mistral.ai/api-keys',\n\t\tkeyValidator: /^[a-zA-Z0-9]{32}$/,\n\t};\n\n\tgetModels(): Promise<readonly AIModel<typeof provider.id>[]> {\n\t\treturn Promise.resolve(models);\n\t}\n\n\tprotected getUrl(_model: AIModel<typeof provider.id>): string {\n\t\treturn 'https://api.mistral.ai/v1/chat/completions';\n\t}\n\n\tprotected override fetchCore<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<typeof provider.id>,\n\t\tapiKey: string,\n\t\trequest: object,\n\t\tsignal: AbortSignal | undefined,\n\t): Promise<Response> {\n\t\tif ('max_completion_tokens' in request) {\n\t\t\tconst { max_completion_tokens: max, ...rest } = request;\n\t\t\trequest = max ? { max_tokens: max, ...rest } : rest;\n\t\t}\n\t\treturn super.fetchCore(action, model, apiKey, request, signal);\n\t}\n\n\tprotected override async handleFetchFailure<TAction extends AIActionType>(\n\t\trsp: Response,\n\t\taction: TAction,\n\t\tmodel: AIModel<typeof provider.id>,\n\t\tretries: number,\n\t\tmaxInputTokens: number,\n\t\tbody?: string,\n\t\tsentTools?: boolean,\n\t): Promise<{ retry: true; maxInputTokens: number; withoutTools?: boolean }> {\n\t\tif (rsp.status !== 404 && rsp.status !== 429) {\n\t\t\tlet json;\n\t\t\ttry {\n\t\t\t\tjson = (body != null ? JSON.parse(body) : await rsp.json()) as MistralError | undefined;\n\t\t\t} catch {}\n\n\t\t\tdebugger;\n\n\t\t\tconst message = `${json?.type}: ${json?.message?.detail\n\t\t\t\t?.map(d => `${d.msg} (${d.type} @ ${d.loc.join(',')})`)\n\t\t\t\t.join(', ')}`;\n\n\t\t\tif (json?.type === 'invalid_request_error') {\n\t\t\t\tif (message?.includes('prompt is too long')) {\n\t\t\t\t\tif (retries < 3) {\n\t\t\t\t\t\treturn { retry: true, maxInputTokens: getReducedMaxInputTokens(maxInputTokens, retries + 1) };\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow new AIError(\n\t\t\t\t\t\tAIErrorReason.RequestTooLarge,\n\t\t\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${message || rsp.statusText}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// This override throws for every non-404/429 status, so the base class's tools-rejection\n\t\t\t// recovery would never run for Mistral — check it here to keep the text-only fallback\n\t\t\tif (sentTools && this.isToolsRejection(rsp.status, message)) {\n\t\t\t\treturn { retry: true, maxInputTokens: maxInputTokens, withoutTools: true };\n\t\t\t}\n\n\t\t\tthrow new Error(`(${this.name}) ${rsp.status}: ${message || rsp.statusText}`);\n\t\t}\n\n\t\treturn super.handleFetchFailure(rsp, action, model, retries, maxInputTokens, body, sentTools);\n\t}\n}\n\ninterface MistralError {\n\tobject: 'error';\n\ttype: 'invalid_request_error' | string;\n\tmessage: {\n\t\tdetail: {\n\t\t\ttype: string;\n\t\t\tmsg: string;\n\t\t\tloc: string[];\n\t\t\tinput: number;\n\t\t}[];\n\t};\n\tcode: unknown | null;\n\tparam: unknown | null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mistralProvider.js","names":["provider"],"sources":["../../../../../plus/ai/src/providers/mistralProvider.ts"],"sourcesContent":["import { mistralProviderDescriptor as provider } from '../constants.js';\nimport { AIError, AIErrorReason } from '../errors.js';\nimport type { AIActionType, AIModel } from '../models/model.js';\nimport { getReducedMaxInputTokens } from '../utils/ai.utils.js';\nimport { OpenAICompatibleProviderBase } from './openAICompatibleProviderBase.js';\n\ntype MistralModel = AIModel<typeof provider.id>;\nconst models: MistralModel[] = [\n\t{\n\t\tid: 'mistral-medium-latest',\n\t\tname: 'Mistral Medium 3.5',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'mistral-medium-3-5',\n\t\tname: 'Mistral Medium 3.5',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-medium-2505',\n\t\tname: 'Mistral Medium',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'zai-glm-5-2',\n\t\tname: 'GLM 5.2',\n\t\tmaxTokens: { input: 1000000, output: 128000 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'mistral-small-latest',\n\t\tname: 'Mistral Small 4',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\tdefault: true,\n\t},\n\t{\n\t\tid: 'mistral-small-2603',\n\t\tname: 'Mistral Small 4',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-small-2503',\n\t\tname: 'Mistral Small',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-large-latest',\n\t\tname: 'Mistral Large 3',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'mistral-large-2512',\n\t\tname: 'Mistral Large 3',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'mistral-large-2411',\n\t\tname: 'Mistral Large',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'ministral-14b-latest',\n\t\tname: 'Ministral 3 14B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'ministral-14b-2512',\n\t\tname: 'Ministral 3 14B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'ministral-8b-latest',\n\t\tname: 'Ministral 3 8B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'ministral-8b-2512',\n\t\tname: 'Ministral 3 8B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'ministral-3b-latest',\n\t\tname: 'Ministral 3 3B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'ministral-3b-2512',\n\t\tname: 'Ministral 3 3B',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'codestral-latest',\n\t\tname: 'Codestral',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t},\n\t{\n\t\tid: 'codestral-2508',\n\t\tname: 'Codestral',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'codestral-2501',\n\t\tname: 'Codestral',\n\t\tmaxTokens: { input: 262144, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'devstral-small-latest',\n\t\tname: 'Devstral Small',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n\t{\n\t\tid: 'devstral-small-2505',\n\t\tname: 'Devstral Small',\n\t\tmaxTokens: { input: 131072, output: 4096 },\n\t\tprovider: provider,\n\t\thidden: true,\n\t},\n];\n\nexport class MistralProvider extends OpenAICompatibleProviderBase<typeof provider.id> {\n\treadonly id = provider.id;\n\treadonly name = provider.name;\n\treadonly supportsTools = true;\n\tprotected readonly descriptor = provider;\n\tprotected readonly config = {\n\t\tkeyUrl: 'https://console.mistral.ai/api-keys',\n\t\tkeyValidator: /^[a-zA-Z0-9]{32}$/,\n\t};\n\n\tgetModels(): Promise<readonly AIModel<typeof provider.id>[]> {\n\t\treturn Promise.resolve(models);\n\t}\n\n\tprotected getUrl(_model: AIModel<typeof provider.id>): string {\n\t\treturn 'https://api.mistral.ai/v1/chat/completions';\n\t}\n\n\tprotected override fetchCore<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<typeof provider.id>,\n\t\tapiKey: string,\n\t\trequest: object,\n\t\tsignal: AbortSignal | undefined,\n\t): Promise<Response> {\n\t\tif ('max_completion_tokens' in request) {\n\t\t\tconst { max_completion_tokens: max, ...rest } = request;\n\t\t\trequest = max ? { max_tokens: max, ...rest } : rest;\n\t\t}\n\t\treturn super.fetchCore(action, model, apiKey, request, signal);\n\t}\n\n\tprotected override async handleFetchFailure<TAction extends AIActionType>(\n\t\trsp: Response,\n\t\taction: TAction,\n\t\tmodel: AIModel<typeof provider.id>,\n\t\tretries: number,\n\t\tmaxInputTokens: number,\n\t\tbody?: string,\n\t\tsentTools?: boolean,\n\t): Promise<{ retry: true; maxInputTokens: number; withoutTools?: boolean }> {\n\t\t// 401/403 fall through to the base, which maps them to `AIError(Unauthorized)`\n\t\tif (rsp.status !== 404 && rsp.status !== 429 && rsp.status !== 401 && rsp.status !== 403) {\n\t\t\tlet json;\n\t\t\ttry {\n\t\t\t\tjson = (body != null ? JSON.parse(body) : await rsp.json()) as MistralError | undefined;\n\t\t\t} catch {}\n\n\t\t\tdebugger;\n\n\t\t\tconst message = `${json?.type}: ${json?.message?.detail\n\t\t\t\t?.map(d => `${d.msg} (${d.type} @ ${d.loc.join(',')})`)\n\t\t\t\t.join(', ')}`;\n\n\t\t\tif (json?.type === 'invalid_request_error') {\n\t\t\t\tif (message?.includes('prompt is too long')) {\n\t\t\t\t\tif (retries < 3) {\n\t\t\t\t\t\treturn { retry: true, maxInputTokens: getReducedMaxInputTokens(maxInputTokens, retries + 1) };\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow new AIError(\n\t\t\t\t\t\tAIErrorReason.RequestTooLarge,\n\t\t\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${message || rsp.statusText}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// This override throws for every non-404/429 status, so the base class's tools-rejection\n\t\t\t// recovery would never run for Mistral — check it here to keep the text-only fallback\n\t\t\tif (sentTools && this.isToolsRejection(rsp.status, message)) {\n\t\t\t\treturn { retry: true, maxInputTokens: maxInputTokens, withoutTools: true };\n\t\t\t}\n\n\t\t\tthrow new Error(`(${this.name}) ${rsp.status}: ${message || rsp.statusText}`);\n\t\t}\n\n\t\treturn super.handleFetchFailure(rsp, action, model, retries, maxInputTokens, body, sentTools);\n\t}\n}\n\ninterface MistralError {\n\tobject: 'error';\n\ttype: 'invalid_request_error' | string;\n\tmessage: {\n\t\tdetail: {\n\t\t\ttype: string;\n\t\t\tmsg: string;\n\t\t\tloc: string[];\n\t\t\tinput: number;\n\t\t}[];\n\t};\n\tcode: unknown | null;\n\tparam: unknown | null;\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { CancellationError, isCancellationError } from "../../../utils/cancellation.js";
2
2
  import { Logger as _logger, getLoggableName } from "../../../utils/logger.js";
3
3
  import { maybeStartScopedLogger } from "../../../utils/logger.scoped.js";
4
- import _usingCtx from "../../../_virtual/@oxc-project+runtime/helpers/esm/usingCtx.js";
5
4
  import { uuid } from "../../../utils/crypto.js";
5
+ import _usingCtx from "../../../_virtual/@oxc-project+runtime/helpers/esm/usingCtx.js";
6
6
  import { AIError } from "../errors.js";
7
7
  import { getActionName, getReducedMaxInputTokens, getValidatedTemperature } from "../utils/ai.utils.js";
8
8
  import { isResponseFormatRejected, rememberResponseFormatRejection } from "./responseFormatCache.js";
@@ -321,6 +321,7 @@ var OpenAICompatibleProviderBase = class {
321
321
  json = body != null ? JSON.parse(body) : await rsp.json();
322
322
  } catch {}
323
323
  if (Array.isArray(json)) json = json[0];
324
+ if (rsp.status === 401 || rsp.status === 403) throw new AIError(9, /* @__PURE__ */ new Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`));
324
325
  if (json?.error?.code === "context_length_exceeded") {
325
326
  if (retries < 3) return {
326
327
  retry: true,
@@ -1 +1 @@
1
- {"version":3,"file":"openAICompatibleProviderBase.js","names":[],"sources":["../../../../../plus/ai/src/providers/openAICompatibleProviderBase.ts"],"sourcesContent":["import * as l10n from '@vscode/l10n';\nimport { CancellationError, isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uuid } from '@gitlens/utils/crypto.js';\nimport { getLoggableName, Logger } from '@gitlens/utils/logger.js';\nimport { maybeStartScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { AIProviders } from '../constants.js';\nimport { AIError, AIErrorReason } from '../errors.js';\nimport type { AIActionType, AIModel, AIProviderDescriptor } from '../models/model.js';\nimport type {\n\tAIChatMessage,\n\tAIChatMessageRole,\n\tAIFinishReason,\n\tAIProvider,\n\tAIProviderResponse,\n\tAIResponseFormat,\n\tAIToolCall,\n\tAIToolDefinition,\n\tJSONSchema,\n} from '../models/provider.js';\nimport { getActionName, getReducedMaxInputTokens, getValidatedTemperature } from '../utils/ai.utils.js';\nimport type { AIProviderContext } from './context.js';\nimport { isResponseFormatRejected, rememberResponseFormatRejection } from './responseFormatCache.js';\n\n// Native-format rejection wording varies by provider (\"response_format\", \"structured outputs\",\n// Gemini's \"Json mode is not enabled\", and its OpenAI-compat layer's `generation_config.response_schema`\n// path in INVALID_ARGUMENT bodies, ...)\nconst formatRejectionRegex =\n\t/response[_ ]?(?:format|schema)|structured[_ ]?output|output_config|json_schema|json[_ ]?mode/i;\n\nfunction getRequestErrorMessage(action: AIActionType, providerName: string, message: string): string {\n\tconst args = { provider: providerName, message: message };\n\tswitch (action) {\n\t\tcase 'explain-changes':\n\t\t\treturn l10n.t('Unable to Explain Changes: ({provider}) {message}', args);\n\t\tcase 'review-changes':\n\t\t\treturn l10n.t('Unable to Review Changes: ({provider}) {message}', args);\n\t\tcase 'generate-commitMessage':\n\t\t\treturn l10n.t('Unable to Generate Commit Message: ({provider}) {message}', args);\n\t\tcase 'generate-stashMessage':\n\t\t\treturn l10n.t('Unable to Generate Stash Message: ({provider}) {message}', args);\n\t\tcase 'generate-changelog':\n\t\t\treturn l10n.t('Unable to Generate Changelog: ({provider}) {message}', args);\n\t\tcase 'generate-create-cloudPatch':\n\t\t\treturn l10n.t('Unable to Create Cloud Patch Details: ({provider}) {message}', args);\n\t\tcase 'generate-create-pullRequest':\n\t\t\treturn l10n.t('Unable to Create Pull Request Details: ({provider}) {message}', args);\n\t\tcase 'generate-commits':\n\t\t\treturn l10n.t('Unable to Generate Commits: ({provider}) {message}', args);\n\t\tcase 'conflict-resolution':\n\t\t\treturn l10n.t('Unable to Resolve Conflicts (Preview): ({provider}) {message}', args);\n\t\tcase 'generate-searchQuery':\n\t\t\treturn l10n.t('Unable to Generate Search Query: ({provider}) {message}', args);\n\t}\n}\n\nexport abstract class OpenAICompatibleProviderBase<T extends AIProviders> implements AIProvider<T> {\n\tconstructor(protected readonly context: AIProviderContext) {}\n\n\tdispose(): void {}\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tabstract readonly id: T;\n\tabstract readonly name: string;\n\tprotected abstract readonly descriptor: AIProviderDescriptor<T>;\n\tprotected abstract readonly config: { keyUrl?: string; keyValidator?: RegExp };\n\n\tasync configured(silent: boolean): Promise<boolean> {\n\t\ttry {\n\t\t\tconst apiKey = await this.getApiKey(silent);\n\t\t\treturn apiKey != null;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) return false;\n\n\t\t\tthrow ex;\n\t\t}\n\t}\n\n\tasync getApiKey(silent: boolean): Promise<string | undefined> {\n\t\tconst orgConf = this.context.getProviderConfig(this.id);\n\t\tif (!orgConf.enabled) return undefined;\n\t\tif (orgConf.key) return orgConf.key;\n\n\t\tconst { keyUrl, keyValidator } = this.config;\n\n\t\treturn this.context.getApiKey(\n\t\t\t{\n\t\t\t\tid: this.id,\n\t\t\t\tname: this.name,\n\t\t\t\trequiresAccount: this.descriptor.requiresAccount,\n\t\t\t\tvalidator: keyValidator != null ? v => keyValidator.test(v) : () => true,\n\t\t\t\turl: keyUrl,\n\t\t\t},\n\t\t\tsilent,\n\t\t);\n\t}\n\n\tabstract getModels(): Promise<readonly AIModel<T>[]>;\n\n\tprotected isResponseFormatSessionRejected(model: AIModel<T>, responseFormat: AIResponseFormat): boolean {\n\t\treturn isResponseFormatRejected(this.id, model, responseFormat);\n\t}\n\n\tprotected rememberResponseFormatRejection(model: AIModel<T>, responseFormat: AIResponseFormat): void {\n\t\trememberResponseFormatRejection(this.id, model, responseFormat);\n\t}\n\n\tprotected abstract getUrl(_model: AIModel<T>): string | undefined;\n\n\tprotected getHeaders<TAction extends AIActionType>(\n\t\t_action: TAction,\n\t\tapiKey: string,\n\t\t_model: AIModel<T>,\n\t\t_url: string,\n\t\t// Deliberately unused here: only the GitKraken provider forwards the conversation ID (as\n\t\t// `GK-Conversation-ID`); it must never reach third-party APIs.\n\t\t_conversationId?: string,\n\t): Record<string, string> | Promise<Record<string, string>> {\n\t\treturn {\n\t\t\tAccept: 'application/json',\n\t\t\t'Content-Type': 'application/json',\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t};\n\t}\n\n\tasync sendRequest<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tgetMessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\toptions: {\n\t\t\tsignal: AbortSignal;\n\t\t\tmodelOptions?: { outputTokens?: number; temperature?: number };\n\t\t\tconversationId?: string;\n\t\t\tresponseFormat?: AIResponseFormat;\n\t\t\ttools?: readonly AIToolDefinition[];\n\t\t},\n\t): Promise<AIProviderResponse<void> | undefined> {\n\t\tusing scope = maybeStartScopedLogger(`${getLoggableName(this)}.sendRequest`);\n\n\t\ttry {\n\t\t\tconst result = await this.fetch(\n\t\t\t\taction,\n\t\t\t\tmodel,\n\t\t\t\tapiKey,\n\t\t\t\tgetMessages,\n\t\t\t\toptions.modelOptions,\n\t\t\t\toptions.responseFormat,\n\t\t\t\toptions.signal,\n\t\t\t\toptions.conversationId,\n\t\t\t\toptions.tools,\n\t\t\t);\n\t\t\treturn result;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) {\n\t\t\t\tscope?.error(ex, `Cancelled request to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tscope?.error(ex, `Unable to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\tif (ex instanceof AIError) throw ex;\n\n\t\t\tdebugger;\n\t\t\tthrow new Error(getRequestErrorMessage(action, model.provider.name, ex.message), { cause: ex });\n\t\t}\n\t}\n\n\tprotected async fetch<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tmessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\tmodelOptions?: { outputTokens?: number; temperature?: number },\n\t\tresponseFormat?: AIResponseFormat,\n\t\tsignal?: AbortSignal,\n\t\tconversationId?: string,\n\t\ttools?: readonly AIToolDefinition[],\n\t): Promise<AIProviderResponse<void>> {\n\t\tlet retries = 0;\n\t\tlet maxInputTokens = model.maxTokens.input;\n\t\t// Latched when the provider rejects the `tools` field, so the retry drops tools entirely and\n\t\t// the caller can stop offering them for the rest of the session.\n\t\tlet toolsRejected = false;\n\n\t\twhile (true) {\n\t\t\tconst useTools = tools?.length && !toolsRejected ? tools : undefined;\n\t\t\tconst { messages: msgs, system } = this.extractSystemPrompt(await messages(maxInputTokens, retries));\n\t\t\tconst request: ChatCompletionRequest = {\n\t\t\t\tmodel: model.id,\n\t\t\t\tmessages: this.serializeMessages(msgs),\n\t\t\t\t...(system != null ? { system: system } : undefined),\n\t\t\t\t...(useTools != null ? this.serializeTools(useTools) : undefined),\n\t\t\t\tstream: false,\n\t\t\t\tmax_completion_tokens: model.maxTokens.output\n\t\t\t\t\t? Math.min(modelOptions?.outputTokens ?? Infinity, model.maxTokens.output)\n\t\t\t\t\t: modelOptions?.outputTokens,\n\t\t\t\ttemperature: getValidatedTemperature(\n\t\t\t\t\tmodel,\n\t\t\t\t\tmodelOptions?.temperature ?? model.temperature,\n\t\t\t\t\tthis.context.defaultTemperature,\n\t\t\t\t),\n\t\t\t};\n\t\t\tif (\n\t\t\t\tresponseFormat != null &&\n\t\t\t\t!this.isResponseFormatSessionRejected(model, responseFormat) &&\n\t\t\t\tthis.supportsResponseFormat(model)\n\t\t\t) {\n\t\t\t\tthis.applyResponseFormat(request, model, responseFormat);\n\t\t\t}\n\n\t\t\tlet rsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t// Read once and forward to `handleFetchFailure` so the error body isn't decoded twice\n\t\t\tlet errorBody: string | undefined;\n\t\t\tif (!rsp.ok && (request.response_format != null || request.output_config != null)) {\n\t\t\t\t// If the native format was rejected (stale model snapshot, older Azure api-version,\n\t\t\t\t// proxy converter gaps, OpenRouter's require_parameters matching no endpoint), strip\n\t\t\t\t// it and resend the same request — the prompt still describes the JSON. Resending\n\t\t\t\t// directly avoids re-running the (non-idempotent) prompt build and its side effects\n\t\t\t\t// Leave it `undefined` (not '') when the read fails, so `handleFetchFailure` falls\n\t\t\t\t// back to reading the still-unconsumed response instead of parsing an empty string\n\t\t\t\terrorBody = await rsp\n\t\t\t\t\t.clone()\n\t\t\t\t\t.text()\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t\tif (this.isResponseFormatRejection(rsp.status, errorBody ?? '')) {\n\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t`(${this.name}) ${model.id}: native response format was rejected (${rsp.status}); retrying without it`,\n\t\t\t\t\t);\n\t\t\t\t\tconst rejected = responseFormat;\n\t\t\t\t\tresponseFormat = undefined;\n\t\t\t\t\tdelete request.response_format;\n\t\t\t\t\tdelete request.output_config;\n\t\t\t\t\tdelete request.provider;\n\t\t\t\t\trsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t\t\terrorBody = undefined;\n\t\t\t\t\t// Memoize only when stripping fixed it — proof the format (not e.g. an oversized\n\t\t\t\t\t// prompt behind the same wrapped status) caused the failure\n\t\t\t\t\tif (rsp.ok && rejected != null) {\n\t\t\t\t\t\tthis.rememberResponseFormatRejection(model, rejected);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!rsp.ok) {\n\t\t\t\tconst result = await this.handleFetchFailure(\n\t\t\t\t\trsp,\n\t\t\t\t\taction,\n\t\t\t\t\tmodel,\n\t\t\t\t\tretries,\n\t\t\t\t\tmaxInputTokens,\n\t\t\t\t\terrorBody,\n\t\t\t\t\tuseTools != null,\n\t\t\t\t);\n\t\t\t\tif (result.retry) {\n\t\t\t\t\tmaxInputTokens = result.maxInputTokens;\n\t\t\t\t\tif (result.withoutTools) {\n\t\t\t\t\t\t// Say so explicitly: the caller degrades silently by design, so the only remaining\n\t\t\t\t\t\t// evidence would be `toolCalls=0` — indistinguishable from a model that simply chose\n\t\t\t\t\t\t// not to consult the repository. That ambiguity is the whole reason to log it.\n\t\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t\t`(${this.name}) ${model.id}: tools were rejected (${rsp.status}); retrying without them — repository consultation is unavailable for the rest of this session`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\ttoolsRejected = true;\n\t\t\t\t\t}\n\t\t\t\t\tretries++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data: ChatCompletionResponse = (await rsp.json()) as ChatCompletionResponse;\n\t\t\tconst toolCalls = this.parseToolCalls(data);\n\t\t\t// A tool-call-only turn carries no text, which `extractContent` correctly reports as '' — it\n\t\t\t// selects the text block by type, so a `tool_use` (or thinking) block is never mistaken for one.\n\t\t\t// The calls ride alongside on `toolCalls` below.\n\t\t\tconst content = this.extractContent(data);\n\t\t\t// Some providers/proxies (e.g. OpenRouter) report upstream failures as an OK response\n\t\t\t// carrying an error envelope and no choices — surface that instead of empty content\n\t\t\tif (!content && toolCalls == null && data.error != null) {\n\t\t\t\tif (data.error.code === 429 || data.error.code === '429') {\n\t\t\t\t\tthrow new AIError(\n\t\t\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\t\t\tnew Error(`(${this.name}) ${data.error.message || 'Rate limit exceeded'}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(`(${this.name}) ${data.error.message || 'Unknown error'}`);\n\t\t\t}\n\n\t\t\tconst result: AIProviderResponse<void> = {\n\t\t\t\tid: data.id ?? uuid(),\n\t\t\t\tcontent: content,\n\t\t\t\tfinishReason: this.extractFinishReason(data),\n\t\t\t\tmodel: model,\n\t\t\t\t...(toolCalls?.length ? { toolCalls: toolCalls } : undefined),\n\t\t\t\t...(toolsRejected ? { toolsRejected: true } : undefined),\n\t\t\t\tusage: {\n\t\t\t\t\tpromptTokens: data.usage?.prompt_tokens ?? data.usage?.input_tokens,\n\t\t\t\t\tcompletionTokens: data.usage?.completion_tokens ?? data.usage?.output_tokens,\n\t\t\t\t\ttotalTokens: data.usage?.total_tokens,\n\t\t\t\t\tlimits:\n\t\t\t\t\t\tdata?.usage?.gk != null\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tused: data.usage.gk.used,\n\t\t\t\t\t\t\t\t\tlimit: data.usage.gk.limit,\n\t\t\t\t\t\t\t\t\tresetsOn: new Date(data.usage.gk.resets_on),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t\tresult: undefined,\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the message-related request fields. The default sends every message inline (OpenAI\n\t * Chat Completions semantics); providers whose API carries the initial system prompt separately\n\t * (e.g. Anthropic's top-level `system`) override this to extract it from the messages.\n\t */\n\tprotected extractSystemPrompt(messages: AIChatMessage<AIChatMessageRole>[]): {\n\t\tmessages: AIChatMessage<AIChatMessageRole>[];\n\t\tsystem?: string;\n\t} {\n\t\treturn { messages: messages };\n\t}\n\n\t/** Whether the given model accepts a native response-format/schema field; when false the\n\t * prompt's JSON description is the only format control (the request is unchanged).\n\t * Dynamic-catalog providers (GitKraken, OpenRouter) populate\n\t * {@link AIModel.supportsStructuredOutputs}; static providers override with id-based gates */\n\tprotected supportsResponseFormat(model: AIModel<T>): boolean {\n\t\treturn model.supportsStructuredOutputs ?? true;\n\t}\n\n\t/**\n\t * Whether a failed response reads as a rejection of the `tools` field rather than a problem with the\n\t * prompt — the trigger for one retry without tools, so a tool-using feature degrades to single-shot\n\t * instead of failing outright.\n\t *\n\t * Providers that wrap upstream rejections in their own envelope (e.g. GitKraken) must override this:\n\t * the wrapper's prose names neither \"tool\" nor \"function\", so the text match here cannot see through\n\t * it and the fallback would never fire.\n\t */\n\tprotected isToolsRejection(status: number, message: string | undefined): boolean {\n\t\tif (status !== 400 || !message) return false;\n\n\t\tconst m = message.toLowerCase();\n\t\treturn (\n\t\t\t(m.includes('tool') || m.includes('function')) &&\n\t\t\t(m.includes('unsupported') ||\n\t\t\t\tm.includes('not supported') ||\n\t\t\t\tm.includes('unknown') ||\n\t\t\t\tm.includes('unrecognized') ||\n\t\t\t\tm.includes('invalid') ||\n\t\t\t\tm.includes('unexpected'))\n\t\t);\n\t}\n\n\t/** Whether a failed response means the native response format itself was rejected — the\n\t * trigger for stripping it and retrying via the prompt-described JSON. Providers that wrap\n\t * upstream rejections in their own envelope (e.g. GitKraken) extend this */\n\tprotected isResponseFormatRejection(status: number, body: string): boolean {\n\t\treturn (\n\t\t\t(status === 400 && formatRejectionRegex.test(body)) ||\n\t\t\t// OpenRouter answers 404 when `require_parameters` matches no serving endpoint\n\t\t\t(status === 404 && /no endpoints found/i.test(body))\n\t\t);\n\t}\n\n\t/** Translates the canonical response format onto the request; called only when\n\t * {@link supportsResponseFormat} allows it. Base emits the OpenAI `response_format.json_schema`\n\t * envelope. Overrides must REPLACE this for providers with a different native field\n\t * (e.g. Anthropic `output_config`) — never leave both on the request. */\n\tprotected applyResponseFormat(\n\t\trequest: ChatCompletionRequest,\n\t\t_model: AIModel<T>,\n\t\tresponseFormat: AIResponseFormat,\n\t): void {\n\t\trequest.response_format = {\n\t\t\ttype: 'json_schema',\n\t\t\tjson_schema: {\n\t\t\t\tname: responseFormat.name,\n\t\t\t\tstrict: true,\n\t\t\t\tschema: responseFormat.schema,\n\t\t\t},\n\t\t};\n\t}\n\n\t// Finds the text block by type rather than position — with some Anthropic models (e.g. Sonnet 5,\n\t// where thinking runs by default) a thinking block can precede the text block\n\tprotected extractContent(data: ChatCompletionResponse): string {\n\t\treturn (\n\t\t\tdata.choices?.[0]?.message?.content?.trim() ??\n\t\t\tdata.content?.find(b => b.type === 'text')?.text?.trim() ??\n\t\t\t''\n\t\t);\n\t}\n\n\t/** Normalizes abnormal completion signals (all arrive as HTTP 200) so consumers can\n\t * distinguish truncated/filtered/refused output from a malformed-but-complete reply */\n\tprotected extractFinishReason(data: ChatCompletionResponse): AIFinishReason | undefined {\n\t\tconst choice = data.choices?.[0];\n\t\tif (choice != null) {\n\t\t\t// Truthy check — some OpenAI-compatible servers serialize `\"refusal\": \"\"` on normal replies\n\t\t\tif (choice.message?.refusal) return 'refusal';\n\t\t\tif (choice.finish_reason === 'length') return 'length';\n\t\t\tif (choice.finish_reason === 'content_filter') return 'content_filter';\n\t\t\treturn undefined;\n\t\t}\n\n\t\tswitch (data.stop_reason) {\n\t\t\t// `model_context_window_exceeded` means the context filled before the output limit — the\n\t\t\t// reply is truncated all the same\n\t\t\tcase 'max_tokens':\n\t\t\tcase 'model_context_window_exceeded':\n\t\t\t\treturn 'length';\n\t\t\tcase 'refusal':\n\t\t\t\treturn 'refusal';\n\t\t\tdefault:\n\t\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Maps messages onto the provider's wire shape. The default is OpenAI Chat Completions: assistant\n\t * tool calls go in `tool_calls` with `arguments` JSON-stringified, and tool results are `tool`-role\n\t * entries keyed by `tool_call_id`. Providers with a different tool encoding (e.g. Anthropic's\n\t * content blocks) override this.\n\t */\n\tprotected serializeMessages(messages: AIChatMessage<AIChatMessageRole>[]): unknown[] {\n\t\treturn messages.map(m => {\n\t\t\tif (m.role === 'tool') {\n\t\t\t\t// A tool result is only addressable by the id of the call it answers. Without one, send it\n\t\t\t\t// as plain text — the same shape used for providers that can't carry tool calls at all —\n\t\t\t\t// rather than a `tool_call_id: undefined` the provider rejects with nothing to trace it to.\n\t\t\t\tif (m.toolCallId == null) return { role: 'user', content: m.content };\n\n\t\t\t\treturn { role: 'tool', tool_call_id: m.toolCallId, content: m.content };\n\t\t\t}\n\n\t\t\tif (m.role === 'assistant' && m.toolCalls?.length) {\n\t\t\t\treturn {\n\t\t\t\t\trole: 'assistant',\n\t\t\t\t\t// OpenAI requires `content` to be present, but allows null on a tool-call-only turn\n\t\t\t\t\tcontent: m.content || null,\n\t\t\t\t\ttool_calls: m.toolCalls.map(c => ({\n\t\t\t\t\t\tid: c.id,\n\t\t\t\t\t\ttype: 'function',\n\t\t\t\t\t\tfunction: { name: c.name, arguments: JSON.stringify(c.args) },\n\t\t\t\t\t\t// Only the GitKraken proxy mints these, so the field can only ever be present on a\n\t\t\t\t\t\t// conversation with that proxy — no other provider sees it.\n\t\t\t\t\t\t...(c.providerSignature != null ? { thought_signature: c.providerSignature } : undefined),\n\t\t\t\t\t})),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn { role: m.role, content: m.content };\n\t\t});\n\t}\n\n\t/** Returns the tool-related request fields. The default is the OpenAI `function` envelope. */\n\tprotected serializeTools(tools: readonly AIToolDefinition[]): { tools: unknown[] } {\n\t\treturn {\n\t\t\ttools: tools.map(t => ({\n\t\t\t\ttype: 'function',\n\t\t\t\tfunction: { name: t.name, description: t.description, parameters: t.parameters },\n\t\t\t})),\n\t\t};\n\t}\n\n\t/**\n\t * Extracts tool calls from a response, handling both the OpenAI shape\n\t * (`choices[].message.tool_calls`, whose `arguments` is a JSON string) and the Anthropic shape\n\t * (`content[]` blocks of type `tool_use`) — mirroring how {@link fetch} already reads content\n\t * from both.\n\t */\n\tprotected parseToolCalls(data: ChatCompletionResponse): AIToolCall[] | undefined {\n\t\tconst calls: AIToolCall[] = [];\n\n\t\tfor (const call of data.choices?.[0]?.message?.tool_calls ?? []) {\n\t\t\tlet args: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\t// Providers can emit '' for a no-argument call\n\t\t\t\targs = call.function.arguments ? (JSON.parse(call.function.arguments) as Record<string, unknown>) : {};\n\t\t\t} catch {\n\t\t\t\t// Leave args empty — the tool dispatcher reports the resulting validation error back to\n\t\t\t\t// the model, which is more recoverable than failing the whole request here.\n\t\t\t}\n\n\t\t\tcalls.push({\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.function.name,\n\t\t\t\targs: args,\n\t\t\t\t...(call.thought_signature != null ? { providerSignature: call.thought_signature } : undefined),\n\t\t\t});\n\t\t}\n\n\t\tfor (const block of data.content ?? []) {\n\t\t\tif (block.type !== 'tool_use' || block.id == null || block.name == null) continue;\n\n\t\t\tcalls.push({ id: block.id, name: block.name, args: block.input ?? {} });\n\t\t}\n\n\t\treturn calls.length ? calls : undefined;\n\t}\n\n\tprotected async handleFetchFailure<TAction extends AIActionType>(\n\t\trsp: Response,\n\t\t_action: TAction,\n\t\tmodel: AIModel<T>,\n\t\tretries: number,\n\t\tmaxInputTokens: number,\n\t\t/** The already-read error body, when the caller had to read it (avoids a second decode) */\n\t\tbody?: string,\n\t\tsentTools?: boolean,\n\t): Promise<{ retry: true; maxInputTokens: number; withoutTools?: boolean }> {\n\t\tif (rsp.status === 404) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.Unauthorized,\n\t\t\t\tnew Error(`Your API key doesn't seem to have access to the selected '${model.id}' model`),\n\t\t\t);\n\t\t}\n\t\tif (rsp.status === 429) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\tnew Error(\n\t\t\t\t\t`(${this.name}) ${rsp.status}: Too many requests (rate limit exceeded) or your account is out of funds`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tlet json;\n\t\ttry {\n\t\t\tjson = (body != null ? JSON.parse(body) : await rsp.json()) as\n\t\t\t\t| { error?: { code: string; message: string } }\n\t\t\t\t| undefined;\n\t\t} catch {}\n\n\t\tif (Array.isArray(json)) {\n\t\t\tjson = json[0];\n\t\t}\n\t\tif (json?.error?.code === 'context_length_exceeded') {\n\t\t\tif (retries < 3) {\n\t\t\t\treturn { retry: true, maxInputTokens: getReducedMaxInputTokens(maxInputTokens, retries + 1) };\n\t\t\t}\n\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RequestTooLarge,\n\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`),\n\t\t\t);\n\t\t}\n\n\t\t// A provider that advertises the OpenAI shape but rejects `tools` (an older or partial\n\t\t// implementation) gets one retry without them, so the caller degrades to text-only instead of\n\t\t// failing outright.\n\t\tif (sentTools && this.isToolsRejection(rsp.status, json?.error?.message)) {\n\t\t\treturn { retry: true, maxInputTokens: maxInputTokens, withoutTools: true };\n\t\t}\n\n\t\tthrow new Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`);\n\t}\n\n\tprotected async fetchCore<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\trequest: object,\n\t\tsignal: AbortSignal | undefined,\n\t\tconversationId?: string,\n\t): Promise<Response> {\n\t\tconst url = this.getUrl(model);\n\t\tif (!url) {\n\t\t\tthrow new Error(`(${this.name}) ${getActionName(action)}: No URL configured`);\n\t\t}\n\n\t\tconst body = JSON.stringify(request);\n\n\t\t// A rejected request is otherwise undiagnosable: a proxying backend reports only its own\n\t\t// \"upstream AI provider error (<provider> HTTP <status>)\", so which field the upstream objected to\n\t\t// can only be inferred. Log the request's shape — which fields were sent, and how much — but not\n\t\t// the messages, which carry the user's source code.\n\t\tLogger.trace(\n\t\t\t`(${this.name}) ${getActionName(action)} → ${model.id}: fields=[${Object.keys(request)\n\t\t\t\t.sort()\n\t\t\t\t.join(',')}] bytes=${body.length}`,\n\t\t);\n\n\t\ttry {\n\t\t\treturn await this.context.fetch(url, {\n\t\t\t\theaders: await this.getHeaders(action, apiKey, model, url, conversationId),\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: body,\n\t\t\t\tsignal: signal,\n\t\t\t});\n\t\t} catch (ex) {\n\t\t\tif (ex.name === 'AbortError') throw new CancellationError(ex);\n\t\t\tthrow ex;\n\t\t}\n\t}\n}\n\nexport interface ChatCompletionRequest {\n\tmodel: string;\n\t/** Provider-shaped messages from {@link OpenAICompatibleProviderBase.serializeMessages} */\n\tmessages: unknown[];\n\n\t/** Anthropic carries the initial system prompt here instead of as a `system`-role message */\n\tsystem?: string;\n\n\t/** Tool definitions, shaped by {@link OpenAICompatibleProviderBase.serializeTools} */\n\ttools?: unknown[];\n\n\t/** @deprecated but used by Anthropic & Gemini */\n\tmax_tokens?: number;\n\t/** Currently can't be used for Anthropic & Gemini */\n\tmax_completion_tokens?: number;\n\tmetadata?: Record<string, string>;\n\tstream?: boolean;\n\ttemperature?: number;\n\ttop_p?: number;\n\n\t/** Not supported by many models/providers */\n\treasoning_effort?: 'low' | 'medium' | 'high';\n\n\t/** OpenAI-compatible structured output (set via {@link OpenAICompatibleProviderBase.applyResponseFormat}) */\n\tresponse_format?: { type: 'json_schema'; json_schema: { name: string; strict: boolean; schema: JSONSchema } };\n\t/** Anthropic-native structured output (set only by the Anthropic override) */\n\toutput_config?: { format: { type: 'json_schema'; schema: JSONSchema } };\n\t/** OpenRouter-only routing constraint (set only by the OpenRouter override) */\n\tprovider?: { require_parameters: boolean };\n}\n\nexport interface ChatCompletionResponse {\n\tid: string;\n\tmodel: string;\n\t/** OpenAI compatible output */\n\tchoices?: {\n\t\tindex: number;\n\t\tmessage: {\n\t\t\trole: string;\n\t\t\tcontent: string | null;\n\t\t\t/** OpenAI-only; most OpenAI-compatible servers omit it entirely */\n\t\t\trefusal?: string | null;\n\t\t\ttool_calls?: {\n\t\t\t\tid: string;\n\t\t\t\ttype: string;\n\t\t\t\tfunction: { name: string; arguments: string };\n\t\t\t\t/** GitKraken proxy only — an opaque provider reasoning token to round-trip */\n\t\t\t\tthought_signature?: string;\n\t\t\t}[];\n\t\t};\n\t\tfinish_reason: string;\n\t}[];\n\t/** Anthropic output (non-text blocks, e.g. thinking, carry no text). `tool_use` blocks add the\n\t * call fields, which is why `id`/`name` are optional here — {@link parseToolCalls} narrows on\n\t * `type` and then checks them. */\n\tcontent?: { type: string; text?: string; id?: string; name?: string; input?: Record<string, unknown> }[];\n\t/** Error envelope some providers/proxies return with an OK status (e.g. OpenRouter upstream failures) */\n\terror?: { message?: string; code?: number | string } | null;\n\t/** Anthropic stop reason */\n\tstop_reason?: string;\n\tusage: {\n\t\t/** OpenAI compatible */\n\t\tprompt_tokens?: number;\n\t\tcompletion_tokens?: number;\n\t\ttotal_tokens?: number;\n\n\t\t/** Anthropic */\n\t\tinput_tokens?: number;\n\t\toutput_tokens?: number;\n\n\t\t/** GitKraken */\n\t\tgk: {\n\t\t\tused: number;\n\t\t\tlimit: number;\n\t\t\tresets_on: string;\n\t\t};\n\t};\n}\n"],"mappings":";;;;;;;;;;AA0BA,MAAM,uBACL;AAED,SAAS,uBAAuB,QAAsB,cAAsB,SAAyB;CACpG,MAAM,OAAO;EAAE,UAAU;EAAuB;CAAQ;CACxD,QAAQ,QAAR;EACC,KAAK,mBACJ,OAAO,KAAK,EAAE,qDAAqD,IAAI;EACxE,KAAK,kBACJ,OAAO,KAAK,EAAE,oDAAoD,IAAI;EACvE,KAAK,0BACJ,OAAO,KAAK,EAAE,6DAA6D,IAAI;EAChF,KAAK,yBACJ,OAAO,KAAK,EAAE,4DAA4D,IAAI;EAC/E,KAAK,sBACJ,OAAO,KAAK,EAAE,wDAAwD,IAAI;EAC3E,KAAK,8BACJ,OAAO,KAAK,EAAE,gEAAgE,IAAI;EACnF,KAAK,+BACJ,OAAO,KAAK,EAAE,iEAAiE,IAAI;EACpF,KAAK,oBACJ,OAAO,KAAK,EAAE,sDAAsD,IAAI;EACzE,KAAK,uBACJ,OAAO,KAAK,EAAE,iEAAiE,IAAI;EACpF,KAAK,wBACJ,OAAO,KAAK,EAAE,2DAA2D,IAAI;CAC/E;AACD;AAEA,IAAsB,+BAAtB,MAAmG;CACnE;CAA/B,YAAY,SAA+C;EAA5B,KAAA,UAAA;CAA6B;CAE5D,UAAgB,CAAC;CACjB,CAAC,OAAO,WAAiB;EACxB,KAAK,QAAQ;CACd;CAOA,MAAM,WAAW,QAAmC;EACnD,IAAI;GAEH,OAAO,MADc,KAAK,UAAU,MAAM,KACzB;EAClB,SAAS,IAAI;GACZ,IAAI,oBAAoB,EAAE,GAAG,OAAO;GAEpC,MAAM;EACP;CACD;CAEA,MAAM,UAAU,QAA8C;EAC7D,MAAM,UAAU,KAAK,QAAQ,kBAAkB,KAAK,EAAE;EACtD,IAAI,CAAC,QAAQ,SAAS,OAAO,KAAA;EAC7B,IAAI,QAAQ,KAAK,OAAO,QAAQ;EAEhC,MAAM,EAAE,QAAQ,iBAAiB,KAAK;EAEtC,OAAO,KAAK,QAAQ,UACnB;GACC,IAAI,KAAK;GACT,MAAM,KAAK;GACX,iBAAiB,KAAK,WAAW;GACjC,WAAW,gBAAgB,QAAO,MAAK,aAAa,KAAK,CAAC,UAAU;GACpE,KAAK;EACN,GACA,MACD;CACD;CAIA,gCAA0C,OAAmB,gBAA2C;EACvG,OAAO,yBAAyB,KAAK,IAAI,OAAO,cAAc;CAC/D;CAEA,gCAA0C,OAAmB,gBAAwC;EACpG,gCAAgC,KAAK,IAAI,OAAO,cAAc;CAC/D;CAIA,WACC,SACA,QACA,QACA,MAGA,iBAC2D;EAC3D,OAAO;GACN,QAAQ;GACR,gBAAgB;GAChB,eAAe,UAAU;EAC1B;CACD;CAEA,MAAM,YACL,QACA,OACA,QACA,aACA,SAOgD;;;GAChD,MAAM,QAAA,YAAA,EAAQ,uBAAuB,GAAG,gBAAgB,IAAI,EAAE,aAAa,CAAA;GAE3E,IAAI;IAYH,OAAO,MAXc,KAAK,MACzB,QACA,OACA,QACA,aACA,QAAQ,cACR,QAAQ,gBACR,QAAQ,QACR,QAAQ,gBACR,QAAQ,KACT;GAED,SAAS,IAAI;IACZ,IAAI,oBAAoB,EAAE,GAAG;KAC5B,OAAO,MAAM,IAAI,wBAAwB,cAAc,MAAM,EAAE,KAAK,MAAM,SAAS,KAAK,EAAE;KAC1F,MAAM;IACP;IAEA,OAAO,MAAM,IAAI,aAAa,cAAc,MAAM,EAAE,KAAK,MAAM,SAAS,KAAK,EAAE;IAC/E,IAAI,cAAc,SAAS,MAAM;IAEjC;IACA,MAAM,IAAI,MAAM,uBAAuB,QAAQ,MAAM,SAAS,MAAM,GAAG,OAAO,GAAG,EAAE,OAAO,GAAG,CAAC;GAC/F;;;;;;CACD;CAEA,MAAgB,MACf,QACA,OACA,QACA,UACA,cACA,gBACA,QACA,gBACA,OACoC;EACpC,IAAI,UAAU;EACd,IAAI,iBAAiB,MAAM,UAAU;EAGrC,IAAI,gBAAgB;EAEpB,OAAO,MAAM;GACZ,MAAM,WAAW,OAAO,UAAU,CAAC,gBAAgB,QAAQ,KAAA;GAC3D,MAAM,EAAE,UAAU,MAAM,WAAW,KAAK,oBAAoB,MAAM,SAAS,gBAAgB,OAAO,CAAC;GACnG,MAAM,UAAiC;IACtC,OAAO,MAAM;IACb,UAAU,KAAK,kBAAkB,IAAI;IACrC,GAAI,UAAU,OAAO,EAAU,OAAO,IAAI,KAAA;IAC1C,GAAI,YAAY,OAAO,KAAK,eAAe,QAAQ,IAAI,KAAA;IACvD,QAAQ;IACR,uBAAuB,MAAM,UAAU,SACpC,KAAK,IAAI,cAAc,gBAAgB,UAAU,MAAM,UAAU,MAAM,IACvE,cAAc;IACjB,aAAa,wBACZ,OACA,cAAc,eAAe,MAAM,aACnC,KAAK,QAAQ,kBACd;GACD;GACA,IACC,kBAAkB,QAClB,CAAC,KAAK,gCAAgC,OAAO,cAAc,KAC3D,KAAK,uBAAuB,KAAK,GAEjC,KAAK,oBAAoB,SAAS,OAAO,cAAc;GAGxD,IAAI,MAAM,MAAM,KAAK,UAAU,QAAQ,OAAO,QAAQ,SAAS,QAAQ,cAAc;GAErF,IAAI;GACJ,IAAI,CAAC,IAAI,OAAO,QAAQ,mBAAmB,QAAQ,QAAQ,iBAAiB,OAAO;IAOlF,YAAY,MAAM,IAChB,MAAM,CAAC,CACP,KAAK,CAAC,CACN,YAAY,KAAA,CAAS;IACvB,IAAI,KAAK,0BAA0B,IAAI,QAAQ,aAAa,EAAE,GAAG;KAChE,QAAO,KACN,IAAI,KAAK,KAAK,IAAI,MAAM,GAAG,yCAAyC,IAAI,OAAO,uBAChF;KACA,MAAM,WAAW;KACjB,iBAAiB,KAAA;KACjB,OAAO,QAAQ;KACf,OAAO,QAAQ;KACf,OAAO,QAAQ;KACf,MAAM,MAAM,KAAK,UAAU,QAAQ,OAAO,QAAQ,SAAS,QAAQ,cAAc;KACjF,YAAY,KAAA;KAGZ,IAAI,IAAI,MAAM,YAAY,MACzB,KAAK,gCAAgC,OAAO,QAAQ;IAEtD;GACD;GAEA,IAAI,CAAC,IAAI,IAAI;IACZ,MAAM,SAAS,MAAM,KAAK,mBACzB,KACA,QACA,OACA,SACA,gBACA,WACA,YAAY,IACb;IACA,IAAI,OAAO,OAAO;KACjB,iBAAiB,OAAO;KACxB,IAAI,OAAO,cAAc;MAIxB,QAAO,KACN,IAAI,KAAK,KAAK,IAAI,MAAM,GAAG,yBAAyB,IAAI,OAAO,+FAChE;MACA,gBAAgB;KACjB;KACA;KACA;IACD;GACD;GAEA,MAAM,OAAgC,MAAM,IAAI,KAAK;GACrD,MAAM,YAAY,KAAK,eAAe,IAAI;GAI1C,MAAM,UAAU,KAAK,eAAe,IAAI;GAGxC,IAAI,CAAC,WAAW,aAAa,QAAQ,KAAK,SAAS,MAAM;IACxD,IAAI,KAAK,MAAM,SAAS,OAAO,KAAK,MAAM,SAAS,OAClD,MAAM,IAAI,QAAA,mBAET,IAAI,MAAM,IAAI,KAAK,KAAK,IAAI,KAAK,MAAM,WAAW,uBAAuB,CAC1E;IAED,MAAM,IAAI,MAAM,IAAI,KAAK,KAAK,IAAI,KAAK,MAAM,WAAW,iBAAiB;GAC1E;GAwBA,OAAO;IArBN,IAAI,KAAK,MAAM,KAAK;IACX;IACT,cAAc,KAAK,oBAAoB,IAAI;IACpC;IACP,GAAI,WAAW,SAAS,EAAa,UAAU,IAAI,KAAA;IACnD,GAAI,gBAAgB,EAAE,eAAe,KAAK,IAAI,KAAA;IAC9C,OAAO;KACN,cAAc,KAAK,OAAO,iBAAiB,KAAK,OAAO;KACvD,kBAAkB,KAAK,OAAO,qBAAqB,KAAK,OAAO;KAC/D,aAAa,KAAK,OAAO;KACzB,QACC,MAAM,OAAO,MAAM,OAChB;MACA,MAAM,KAAK,MAAM,GAAG;MACpB,OAAO,KAAK,MAAM,GAAG;MACrB,UAAU,IAAI,KAAK,KAAK,MAAM,GAAG,SAAS;KAC3C,IACC,KAAA;IACL;IACA,QAAQ,KAAA;GAEG;EACb;CACD;;;;;;CAOA,oBAA8B,UAG5B;EACD,OAAO,EAAY,SAAS;CAC7B;;;;;CAMA,uBAAiC,OAA4B;EAC5D,OAAO,MAAM,6BAA6B;CAC3C;;;;;;;;;;CAWA,iBAA2B,QAAgB,SAAsC;EAChF,IAAI,WAAW,OAAO,CAAC,SAAS,OAAO;EAEvC,MAAM,IAAI,QAAQ,YAAY;EAC9B,QACE,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,UAAU,OAC3C,EAAE,SAAS,aAAa,KACxB,EAAE,SAAS,eAAe,KAC1B,EAAE,SAAS,SAAS,KACpB,EAAE,SAAS,cAAc,KACzB,EAAE,SAAS,SAAS,KACpB,EAAE,SAAS,YAAY;CAE1B;;;;CAKA,0BAAoC,QAAgB,MAAuB;EAC1E,OACE,WAAW,OAAO,qBAAqB,KAAK,IAAI,KAEhD,WAAW,OAAO,sBAAsB,KAAK,IAAI;CAEpD;;;;;CAMA,oBACC,SACA,QACA,gBACO;EACP,QAAQ,kBAAkB;GACzB,MAAM;GACN,aAAa;IACZ,MAAM,eAAe;IACrB,QAAQ;IACR,QAAQ,eAAe;GACxB;EACD;CACD;CAIA,eAAyB,MAAsC;EAC9D,OACC,KAAK,UAAU,EAAE,EAAE,SAAS,SAAS,KAAK,KAC1C,KAAK,SAAS,MAAK,MAAK,EAAE,SAAS,MAAM,CAAC,EAAE,MAAM,KAAK,KACvD;CAEF;;;CAIA,oBAA8B,MAA0D;EACvF,MAAM,SAAS,KAAK,UAAU;EAC9B,IAAI,UAAU,MAAM;GAEnB,IAAI,OAAO,SAAS,SAAS,OAAO;GACpC,IAAI,OAAO,kBAAkB,UAAU,OAAO;GAC9C,IAAI,OAAO,kBAAkB,kBAAkB,OAAO;GACtD;EACD;EAEA,QAAQ,KAAK,aAAb;GAGC,KAAK;GACL,KAAK,iCACJ,OAAO;GACR,KAAK,WACJ,OAAO;GACR,SACC;EACF;CACD;;;;;;;CAQA,kBAA4B,UAAyD;EACpF,OAAO,SAAS,KAAI,MAAK;GACxB,IAAI,EAAE,SAAS,QAAQ;IAItB,IAAI,EAAE,cAAc,MAAM,OAAO;KAAE,MAAM;KAAQ,SAAS,EAAE;IAAQ;IAEpE,OAAO;KAAE,MAAM;KAAQ,cAAc,EAAE;KAAY,SAAS,EAAE;IAAQ;GACvE;GAEA,IAAI,EAAE,SAAS,eAAe,EAAE,WAAW,QAC1C,OAAO;IACN,MAAM;IAEN,SAAS,EAAE,WAAW;IACtB,YAAY,EAAE,UAAU,KAAI,OAAM;KACjC,IAAI,EAAE;KACN,MAAM;KACN,UAAU;MAAE,MAAM,EAAE;MAAM,WAAW,KAAK,UAAU,EAAE,IAAI;KAAE;KAG5D,GAAI,EAAE,qBAAqB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,IAAI,KAAA;IAChF,EAAE;GACH;GAGD,OAAO;IAAE,MAAM,EAAE;IAAM,SAAS,EAAE;GAAQ;EAC3C,CAAC;CACF;;CAGA,eAAyB,OAA0D;EAClF,OAAO,EACN,OAAO,MAAM,KAAI,OAAM;GACtB,MAAM;GACN,UAAU;IAAE,MAAM,EAAE;IAAM,aAAa,EAAE;IAAa,YAAY,EAAE;GAAW;EAChF,EAAE,EACH;CACD;;;;;;;CAQA,eAAyB,MAAwD;EAChF,MAAM,QAAsB,CAAC;EAE7B,KAAK,MAAM,QAAQ,KAAK,UAAU,EAAE,EAAE,SAAS,cAAc,CAAC,GAAG;GAChE,IAAI,OAAgC,CAAC;GACrC,IAAI;IAEH,OAAO,KAAK,SAAS,YAAa,KAAK,MAAM,KAAK,SAAS,SAAS,IAAgC,CAAC;GACtG,QAAQ,CAGR;GAEA,MAAM,KAAK;IACV,IAAI,KAAK;IACT,MAAM,KAAK,SAAS;IACd;IACN,GAAI,KAAK,qBAAqB,OAAO,EAAE,mBAAmB,KAAK,kBAAkB,IAAI,KAAA;GACtF,CAAC;EACF;EAEA,KAAK,MAAM,SAAS,KAAK,WAAW,CAAC,GAAG;GACvC,IAAI,MAAM,SAAS,cAAc,MAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM;GAEzE,MAAM,KAAK;IAAE,IAAI,MAAM;IAAI,MAAM,MAAM;IAAM,MAAM,MAAM,SAAS,CAAC;GAAE,CAAC;EACvE;EAEA,OAAO,MAAM,SAAS,QAAQ,KAAA;CAC/B;CAEA,MAAgB,mBACf,KACA,SACA,OACA,SACA,gBAEA,MACA,WAC2E;EAC3E,IAAI,IAAI,WAAW,KAClB,MAAM,IAAI,QAAA,mBAET,IAAI,MAAM,6DAA6D,MAAM,GAAG,QAAQ,CACzF;EAED,IAAI,IAAI,WAAW,KAClB,MAAM,IAAI,QAAA,mBAET,IAAI,MACH,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,0EAC9B,CACD;EAGD,IAAI;EACJ,IAAI;GACH,OAAQ,QAAQ,OAAO,KAAK,MAAM,IAAI,IAAI,MAAM,IAAI,KAAK;EAG1D,QAAQ,CAAC;EAET,IAAI,MAAM,QAAQ,IAAI,GACrB,OAAO,KAAK;EAEb,IAAI,MAAM,OAAO,SAAS,2BAA2B;GACpD,IAAI,UAAU,GACb,OAAO;IAAE,OAAO;IAAM,gBAAgB,yBAAyB,gBAAgB,UAAU,CAAC;GAAE;GAG7F,MAAM,IAAI,QAAA,mBAET,IAAI,MAAM,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,IAAI,MAAM,OAAO,WAAW,IAAI,YAAY,CACpF;EACD;EAKA,IAAI,aAAa,KAAK,iBAAiB,IAAI,QAAQ,MAAM,OAAO,OAAO,GACtE,OAAO;GAAE,OAAO;GAAsB;GAAgB,cAAc;EAAK;EAG1E,MAAM,IAAI,MAAM,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,IAAI,MAAM,OAAO,WAAW,IAAI,YAAY;CAC1F;CAEA,MAAgB,UACf,QACA,OACA,QACA,SACA,QACA,gBACoB;EACpB,MAAM,MAAM,KAAK,OAAO,KAAK;EAC7B,IAAI,CAAC,KACJ,MAAM,IAAI,MAAM,IAAI,KAAK,KAAK,IAAI,cAAc,MAAM,EAAE,oBAAoB;EAG7E,MAAM,OAAO,KAAK,UAAU,OAAO;EAMnC,QAAO,MACN,IAAI,KAAK,KAAK,IAAI,cAAc,MAAM,EAAE,KAAK,MAAM,GAAG,YAAY,OAAO,KAAK,OAAO,CAAC,CACpF,KAAK,CAAC,CACN,KAAK,GAAG,EAAE,UAAU,KAAK,QAC5B;EAEA,IAAI;GACH,OAAO,MAAM,KAAK,QAAQ,MAAM,KAAK;IACpC,SAAS,MAAM,KAAK,WAAW,QAAQ,QAAQ,OAAO,KAAK,cAAc;IACzE,QAAQ;IACF;IACE;GACT,CAAC;EACF,SAAS,IAAI;GACZ,IAAI,GAAG,SAAS,cAAc,MAAM,IAAI,kBAAkB,EAAE;GAC5D,MAAM;EACP;CACD;AACD"}
1
+ {"version":3,"file":"openAICompatibleProviderBase.js","names":[],"sources":["../../../../../plus/ai/src/providers/openAICompatibleProviderBase.ts"],"sourcesContent":["import * as l10n from '@vscode/l10n';\nimport { CancellationError, isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { uuid } from '@gitlens/utils/crypto.js';\nimport { getLoggableName, Logger } from '@gitlens/utils/logger.js';\nimport { maybeStartScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type { AIProviders } from '../constants.js';\nimport { AIError, AIErrorReason } from '../errors.js';\nimport type { AIActionType, AIModel, AIProviderDescriptor } from '../models/model.js';\nimport type {\n\tAIChatMessage,\n\tAIChatMessageRole,\n\tAIFinishReason,\n\tAIProvider,\n\tAIProviderResponse,\n\tAIResponseFormat,\n\tAIToolCall,\n\tAIToolDefinition,\n\tJSONSchema,\n} from '../models/provider.js';\nimport { getActionName, getReducedMaxInputTokens, getValidatedTemperature } from '../utils/ai.utils.js';\nimport type { AIProviderContext } from './context.js';\nimport { isResponseFormatRejected, rememberResponseFormatRejection } from './responseFormatCache.js';\n\n// Native-format rejection wording varies by provider (\"response_format\", \"structured outputs\",\n// Gemini's \"Json mode is not enabled\", and its OpenAI-compat layer's `generation_config.response_schema`\n// path in INVALID_ARGUMENT bodies, ...)\nconst formatRejectionRegex =\n\t/response[_ ]?(?:format|schema)|structured[_ ]?output|output_config|json_schema|json[_ ]?mode/i;\n\nfunction getRequestErrorMessage(action: AIActionType, providerName: string, message: string): string {\n\tconst args = { provider: providerName, message: message };\n\tswitch (action) {\n\t\tcase 'explain-changes':\n\t\t\treturn l10n.t('Unable to Explain Changes: ({provider}) {message}', args);\n\t\tcase 'review-changes':\n\t\t\treturn l10n.t('Unable to Review Changes: ({provider}) {message}', args);\n\t\tcase 'generate-commitMessage':\n\t\t\treturn l10n.t('Unable to Generate Commit Message: ({provider}) {message}', args);\n\t\tcase 'generate-stashMessage':\n\t\t\treturn l10n.t('Unable to Generate Stash Message: ({provider}) {message}', args);\n\t\tcase 'generate-changelog':\n\t\t\treturn l10n.t('Unable to Generate Changelog: ({provider}) {message}', args);\n\t\tcase 'generate-create-cloudPatch':\n\t\t\treturn l10n.t('Unable to Create Cloud Patch Details: ({provider}) {message}', args);\n\t\tcase 'generate-create-pullRequest':\n\t\t\treturn l10n.t('Unable to Create Pull Request Details: ({provider}) {message}', args);\n\t\tcase 'generate-commits':\n\t\t\treturn l10n.t('Unable to Generate Commits: ({provider}) {message}', args);\n\t\tcase 'conflict-resolution':\n\t\t\treturn l10n.t('Unable to Resolve Conflicts (Preview): ({provider}) {message}', args);\n\t\tcase 'generate-searchQuery':\n\t\t\treturn l10n.t('Unable to Generate Search Query: ({provider}) {message}', args);\n\t}\n}\n\nexport abstract class OpenAICompatibleProviderBase<T extends AIProviders> implements AIProvider<T> {\n\tconstructor(protected readonly context: AIProviderContext) {}\n\n\tdispose(): void {}\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tabstract readonly id: T;\n\tabstract readonly name: string;\n\tprotected abstract readonly descriptor: AIProviderDescriptor<T>;\n\tprotected abstract readonly config: { keyUrl?: string; keyValidator?: RegExp };\n\n\tasync configured(silent: boolean): Promise<boolean> {\n\t\ttry {\n\t\t\tconst apiKey = await this.getApiKey(silent);\n\t\t\treturn apiKey != null;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) return false;\n\n\t\t\tthrow ex;\n\t\t}\n\t}\n\n\tasync getApiKey(silent: boolean): Promise<string | undefined> {\n\t\tconst orgConf = this.context.getProviderConfig(this.id);\n\t\tif (!orgConf.enabled) return undefined;\n\t\tif (orgConf.key) return orgConf.key;\n\n\t\tconst { keyUrl, keyValidator } = this.config;\n\n\t\treturn this.context.getApiKey(\n\t\t\t{\n\t\t\t\tid: this.id,\n\t\t\t\tname: this.name,\n\t\t\t\trequiresAccount: this.descriptor.requiresAccount,\n\t\t\t\tvalidator: keyValidator != null ? v => keyValidator.test(v) : () => true,\n\t\t\t\turl: keyUrl,\n\t\t\t},\n\t\t\tsilent,\n\t\t);\n\t}\n\n\tabstract getModels(): Promise<readonly AIModel<T>[]>;\n\n\tprotected isResponseFormatSessionRejected(model: AIModel<T>, responseFormat: AIResponseFormat): boolean {\n\t\treturn isResponseFormatRejected(this.id, model, responseFormat);\n\t}\n\n\tprotected rememberResponseFormatRejection(model: AIModel<T>, responseFormat: AIResponseFormat): void {\n\t\trememberResponseFormatRejection(this.id, model, responseFormat);\n\t}\n\n\tprotected abstract getUrl(_model: AIModel<T>): string | undefined;\n\n\tprotected getHeaders<TAction extends AIActionType>(\n\t\t_action: TAction,\n\t\tapiKey: string,\n\t\t_model: AIModel<T>,\n\t\t_url: string,\n\t\t// Deliberately unused here: only the GitKraken provider forwards the conversation ID (as\n\t\t// `GK-Conversation-ID`); it must never reach third-party APIs.\n\t\t_conversationId?: string,\n\t): Record<string, string> | Promise<Record<string, string>> {\n\t\treturn {\n\t\t\tAccept: 'application/json',\n\t\t\t'Content-Type': 'application/json',\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t};\n\t}\n\n\tasync sendRequest<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tgetMessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\toptions: {\n\t\t\tsignal: AbortSignal;\n\t\t\tmodelOptions?: { outputTokens?: number; temperature?: number };\n\t\t\tconversationId?: string;\n\t\t\tresponseFormat?: AIResponseFormat;\n\t\t\ttools?: readonly AIToolDefinition[];\n\t\t},\n\t): Promise<AIProviderResponse<void> | undefined> {\n\t\tusing scope = maybeStartScopedLogger(`${getLoggableName(this)}.sendRequest`);\n\n\t\ttry {\n\t\t\tconst result = await this.fetch(\n\t\t\t\taction,\n\t\t\t\tmodel,\n\t\t\t\tapiKey,\n\t\t\t\tgetMessages,\n\t\t\t\toptions.modelOptions,\n\t\t\t\toptions.responseFormat,\n\t\t\t\toptions.signal,\n\t\t\t\toptions.conversationId,\n\t\t\t\toptions.tools,\n\t\t\t);\n\t\t\treturn result;\n\t\t} catch (ex) {\n\t\t\tif (isCancellationError(ex)) {\n\t\t\t\tscope?.error(ex, `Cancelled request to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tscope?.error(ex, `Unable to ${getActionName(action)}: (${model.provider.name})`);\n\t\t\tif (ex instanceof AIError) throw ex;\n\n\t\t\tdebugger;\n\t\t\tthrow new Error(getRequestErrorMessage(action, model.provider.name, ex.message), { cause: ex });\n\t\t}\n\t}\n\n\tprotected async fetch<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\tmessages: (maxInputTokens: number, retries: number) => Promise<AIChatMessage<AIChatMessageRole>[]>,\n\t\tmodelOptions?: { outputTokens?: number; temperature?: number },\n\t\tresponseFormat?: AIResponseFormat,\n\t\tsignal?: AbortSignal,\n\t\tconversationId?: string,\n\t\ttools?: readonly AIToolDefinition[],\n\t): Promise<AIProviderResponse<void>> {\n\t\tlet retries = 0;\n\t\tlet maxInputTokens = model.maxTokens.input;\n\t\t// Latched when the provider rejects the `tools` field, so the retry drops tools entirely and\n\t\t// the caller can stop offering them for the rest of the session.\n\t\tlet toolsRejected = false;\n\n\t\twhile (true) {\n\t\t\tconst useTools = tools?.length && !toolsRejected ? tools : undefined;\n\t\t\tconst { messages: msgs, system } = this.extractSystemPrompt(await messages(maxInputTokens, retries));\n\t\t\tconst request: ChatCompletionRequest = {\n\t\t\t\tmodel: model.id,\n\t\t\t\tmessages: this.serializeMessages(msgs),\n\t\t\t\t...(system != null ? { system: system } : undefined),\n\t\t\t\t...(useTools != null ? this.serializeTools(useTools) : undefined),\n\t\t\t\tstream: false,\n\t\t\t\tmax_completion_tokens: model.maxTokens.output\n\t\t\t\t\t? Math.min(modelOptions?.outputTokens ?? Infinity, model.maxTokens.output)\n\t\t\t\t\t: modelOptions?.outputTokens,\n\t\t\t\ttemperature: getValidatedTemperature(\n\t\t\t\t\tmodel,\n\t\t\t\t\tmodelOptions?.temperature ?? model.temperature,\n\t\t\t\t\tthis.context.defaultTemperature,\n\t\t\t\t),\n\t\t\t};\n\t\t\tif (\n\t\t\t\tresponseFormat != null &&\n\t\t\t\t!this.isResponseFormatSessionRejected(model, responseFormat) &&\n\t\t\t\tthis.supportsResponseFormat(model)\n\t\t\t) {\n\t\t\t\tthis.applyResponseFormat(request, model, responseFormat);\n\t\t\t}\n\n\t\t\tlet rsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t// Read once and forward to `handleFetchFailure` so the error body isn't decoded twice\n\t\t\tlet errorBody: string | undefined;\n\t\t\tif (!rsp.ok && (request.response_format != null || request.output_config != null)) {\n\t\t\t\t// If the native format was rejected (stale model snapshot, older Azure api-version,\n\t\t\t\t// proxy converter gaps, OpenRouter's require_parameters matching no endpoint), strip\n\t\t\t\t// it and resend the same request — the prompt still describes the JSON. Resending\n\t\t\t\t// directly avoids re-running the (non-idempotent) prompt build and its side effects\n\t\t\t\t// Leave it `undefined` (not '') when the read fails, so `handleFetchFailure` falls\n\t\t\t\t// back to reading the still-unconsumed response instead of parsing an empty string\n\t\t\t\terrorBody = await rsp\n\t\t\t\t\t.clone()\n\t\t\t\t\t.text()\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t\tif (this.isResponseFormatRejection(rsp.status, errorBody ?? '')) {\n\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t`(${this.name}) ${model.id}: native response format was rejected (${rsp.status}); retrying without it`,\n\t\t\t\t\t);\n\t\t\t\t\tconst rejected = responseFormat;\n\t\t\t\t\tresponseFormat = undefined;\n\t\t\t\t\tdelete request.response_format;\n\t\t\t\t\tdelete request.output_config;\n\t\t\t\t\tdelete request.provider;\n\t\t\t\t\trsp = await this.fetchCore(action, model, apiKey, request, signal, conversationId);\n\t\t\t\t\terrorBody = undefined;\n\t\t\t\t\t// Memoize only when stripping fixed it — proof the format (not e.g. an oversized\n\t\t\t\t\t// prompt behind the same wrapped status) caused the failure\n\t\t\t\t\tif (rsp.ok && rejected != null) {\n\t\t\t\t\t\tthis.rememberResponseFormatRejection(model, rejected);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!rsp.ok) {\n\t\t\t\tconst result = await this.handleFetchFailure(\n\t\t\t\t\trsp,\n\t\t\t\t\taction,\n\t\t\t\t\tmodel,\n\t\t\t\t\tretries,\n\t\t\t\t\tmaxInputTokens,\n\t\t\t\t\terrorBody,\n\t\t\t\t\tuseTools != null,\n\t\t\t\t);\n\t\t\t\tif (result.retry) {\n\t\t\t\t\tmaxInputTokens = result.maxInputTokens;\n\t\t\t\t\tif (result.withoutTools) {\n\t\t\t\t\t\t// Say so explicitly: the caller degrades silently by design, so the only remaining\n\t\t\t\t\t\t// evidence would be `toolCalls=0` — indistinguishable from a model that simply chose\n\t\t\t\t\t\t// not to consult the repository. That ambiguity is the whole reason to log it.\n\t\t\t\t\t\tLogger.warn(\n\t\t\t\t\t\t\t`(${this.name}) ${model.id}: tools were rejected (${rsp.status}); retrying without them — repository consultation is unavailable for the rest of this session`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\ttoolsRejected = true;\n\t\t\t\t\t}\n\t\t\t\t\tretries++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data: ChatCompletionResponse = (await rsp.json()) as ChatCompletionResponse;\n\t\t\tconst toolCalls = this.parseToolCalls(data);\n\t\t\t// A tool-call-only turn carries no text, which `extractContent` correctly reports as '' — it\n\t\t\t// selects the text block by type, so a `tool_use` (or thinking) block is never mistaken for one.\n\t\t\t// The calls ride alongside on `toolCalls` below.\n\t\t\tconst content = this.extractContent(data);\n\t\t\t// Some providers/proxies (e.g. OpenRouter) report upstream failures as an OK response\n\t\t\t// carrying an error envelope and no choices — surface that instead of empty content\n\t\t\tif (!content && toolCalls == null && data.error != null) {\n\t\t\t\tif (data.error.code === 429 || data.error.code === '429') {\n\t\t\t\t\tthrow new AIError(\n\t\t\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\t\t\tnew Error(`(${this.name}) ${data.error.message || 'Rate limit exceeded'}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow new Error(`(${this.name}) ${data.error.message || 'Unknown error'}`);\n\t\t\t}\n\n\t\t\tconst result: AIProviderResponse<void> = {\n\t\t\t\tid: data.id ?? uuid(),\n\t\t\t\tcontent: content,\n\t\t\t\tfinishReason: this.extractFinishReason(data),\n\t\t\t\tmodel: model,\n\t\t\t\t...(toolCalls?.length ? { toolCalls: toolCalls } : undefined),\n\t\t\t\t...(toolsRejected ? { toolsRejected: true } : undefined),\n\t\t\t\tusage: {\n\t\t\t\t\tpromptTokens: data.usage?.prompt_tokens ?? data.usage?.input_tokens,\n\t\t\t\t\tcompletionTokens: data.usage?.completion_tokens ?? data.usage?.output_tokens,\n\t\t\t\t\ttotalTokens: data.usage?.total_tokens,\n\t\t\t\t\tlimits:\n\t\t\t\t\t\tdata?.usage?.gk != null\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tused: data.usage.gk.used,\n\t\t\t\t\t\t\t\t\tlimit: data.usage.gk.limit,\n\t\t\t\t\t\t\t\t\tresetsOn: new Date(data.usage.gk.resets_on),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t},\n\t\t\t\tresult: undefined,\n\t\t\t};\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the message-related request fields. The default sends every message inline (OpenAI\n\t * Chat Completions semantics); providers whose API carries the initial system prompt separately\n\t * (e.g. Anthropic's top-level `system`) override this to extract it from the messages.\n\t */\n\tprotected extractSystemPrompt(messages: AIChatMessage<AIChatMessageRole>[]): {\n\t\tmessages: AIChatMessage<AIChatMessageRole>[];\n\t\tsystem?: string;\n\t} {\n\t\treturn { messages: messages };\n\t}\n\n\t/** Whether the given model accepts a native response-format/schema field; when false the\n\t * prompt's JSON description is the only format control (the request is unchanged).\n\t * Dynamic-catalog providers (GitKraken, OpenRouter) populate\n\t * {@link AIModel.supportsStructuredOutputs}; static providers override with id-based gates */\n\tprotected supportsResponseFormat(model: AIModel<T>): boolean {\n\t\treturn model.supportsStructuredOutputs ?? true;\n\t}\n\n\t/**\n\t * Whether a failed response reads as a rejection of the `tools` field rather than a problem with the\n\t * prompt — the trigger for one retry without tools, so a tool-using feature degrades to single-shot\n\t * instead of failing outright.\n\t *\n\t * Providers that wrap upstream rejections in their own envelope (e.g. GitKraken) must override this:\n\t * the wrapper's prose names neither \"tool\" nor \"function\", so the text match here cannot see through\n\t * it and the fallback would never fire.\n\t */\n\tprotected isToolsRejection(status: number, message: string | undefined): boolean {\n\t\tif (status !== 400 || !message) return false;\n\n\t\tconst m = message.toLowerCase();\n\t\treturn (\n\t\t\t(m.includes('tool') || m.includes('function')) &&\n\t\t\t(m.includes('unsupported') ||\n\t\t\t\tm.includes('not supported') ||\n\t\t\t\tm.includes('unknown') ||\n\t\t\t\tm.includes('unrecognized') ||\n\t\t\t\tm.includes('invalid') ||\n\t\t\t\tm.includes('unexpected'))\n\t\t);\n\t}\n\n\t/** Whether a failed response means the native response format itself was rejected — the\n\t * trigger for stripping it and retrying via the prompt-described JSON. Providers that wrap\n\t * upstream rejections in their own envelope (e.g. GitKraken) extend this */\n\tprotected isResponseFormatRejection(status: number, body: string): boolean {\n\t\treturn (\n\t\t\t(status === 400 && formatRejectionRegex.test(body)) ||\n\t\t\t// OpenRouter answers 404 when `require_parameters` matches no serving endpoint\n\t\t\t(status === 404 && /no endpoints found/i.test(body))\n\t\t);\n\t}\n\n\t/** Translates the canonical response format onto the request; called only when\n\t * {@link supportsResponseFormat} allows it. Base emits the OpenAI `response_format.json_schema`\n\t * envelope. Overrides must REPLACE this for providers with a different native field\n\t * (e.g. Anthropic `output_config`) — never leave both on the request. */\n\tprotected applyResponseFormat(\n\t\trequest: ChatCompletionRequest,\n\t\t_model: AIModel<T>,\n\t\tresponseFormat: AIResponseFormat,\n\t): void {\n\t\trequest.response_format = {\n\t\t\ttype: 'json_schema',\n\t\t\tjson_schema: {\n\t\t\t\tname: responseFormat.name,\n\t\t\t\tstrict: true,\n\t\t\t\tschema: responseFormat.schema,\n\t\t\t},\n\t\t};\n\t}\n\n\t// Finds the text block by type rather than position — with some Anthropic models (e.g. Sonnet 5,\n\t// where thinking runs by default) a thinking block can precede the text block\n\tprotected extractContent(data: ChatCompletionResponse): string {\n\t\treturn (\n\t\t\tdata.choices?.[0]?.message?.content?.trim() ??\n\t\t\tdata.content?.find(b => b.type === 'text')?.text?.trim() ??\n\t\t\t''\n\t\t);\n\t}\n\n\t/** Normalizes abnormal completion signals (all arrive as HTTP 200) so consumers can\n\t * distinguish truncated/filtered/refused output from a malformed-but-complete reply */\n\tprotected extractFinishReason(data: ChatCompletionResponse): AIFinishReason | undefined {\n\t\tconst choice = data.choices?.[0];\n\t\tif (choice != null) {\n\t\t\t// Truthy check — some OpenAI-compatible servers serialize `\"refusal\": \"\"` on normal replies\n\t\t\tif (choice.message?.refusal) return 'refusal';\n\t\t\tif (choice.finish_reason === 'length') return 'length';\n\t\t\tif (choice.finish_reason === 'content_filter') return 'content_filter';\n\t\t\treturn undefined;\n\t\t}\n\n\t\tswitch (data.stop_reason) {\n\t\t\t// `model_context_window_exceeded` means the context filled before the output limit — the\n\t\t\t// reply is truncated all the same\n\t\t\tcase 'max_tokens':\n\t\t\tcase 'model_context_window_exceeded':\n\t\t\t\treturn 'length';\n\t\t\tcase 'refusal':\n\t\t\t\treturn 'refusal';\n\t\t\tdefault:\n\t\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Maps messages onto the provider's wire shape. The default is OpenAI Chat Completions: assistant\n\t * tool calls go in `tool_calls` with `arguments` JSON-stringified, and tool results are `tool`-role\n\t * entries keyed by `tool_call_id`. Providers with a different tool encoding (e.g. Anthropic's\n\t * content blocks) override this.\n\t */\n\tprotected serializeMessages(messages: AIChatMessage<AIChatMessageRole>[]): unknown[] {\n\t\treturn messages.map(m => {\n\t\t\tif (m.role === 'tool') {\n\t\t\t\t// A tool result is only addressable by the id of the call it answers. Without one, send it\n\t\t\t\t// as plain text — the same shape used for providers that can't carry tool calls at all —\n\t\t\t\t// rather than a `tool_call_id: undefined` the provider rejects with nothing to trace it to.\n\t\t\t\tif (m.toolCallId == null) return { role: 'user', content: m.content };\n\n\t\t\t\treturn { role: 'tool', tool_call_id: m.toolCallId, content: m.content };\n\t\t\t}\n\n\t\t\tif (m.role === 'assistant' && m.toolCalls?.length) {\n\t\t\t\treturn {\n\t\t\t\t\trole: 'assistant',\n\t\t\t\t\t// OpenAI requires `content` to be present, but allows null on a tool-call-only turn\n\t\t\t\t\tcontent: m.content || null,\n\t\t\t\t\ttool_calls: m.toolCalls.map(c => ({\n\t\t\t\t\t\tid: c.id,\n\t\t\t\t\t\ttype: 'function',\n\t\t\t\t\t\tfunction: { name: c.name, arguments: JSON.stringify(c.args) },\n\t\t\t\t\t\t// Only the GitKraken proxy mints these, so the field can only ever be present on a\n\t\t\t\t\t\t// conversation with that proxy — no other provider sees it.\n\t\t\t\t\t\t...(c.providerSignature != null ? { thought_signature: c.providerSignature } : undefined),\n\t\t\t\t\t})),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn { role: m.role, content: m.content };\n\t\t});\n\t}\n\n\t/** Returns the tool-related request fields. The default is the OpenAI `function` envelope. */\n\tprotected serializeTools(tools: readonly AIToolDefinition[]): { tools: unknown[] } {\n\t\treturn {\n\t\t\ttools: tools.map(t => ({\n\t\t\t\ttype: 'function',\n\t\t\t\tfunction: { name: t.name, description: t.description, parameters: t.parameters },\n\t\t\t})),\n\t\t};\n\t}\n\n\t/**\n\t * Extracts tool calls from a response, handling both the OpenAI shape\n\t * (`choices[].message.tool_calls`, whose `arguments` is a JSON string) and the Anthropic shape\n\t * (`content[]` blocks of type `tool_use`) — mirroring how {@link fetch} already reads content\n\t * from both.\n\t */\n\tprotected parseToolCalls(data: ChatCompletionResponse): AIToolCall[] | undefined {\n\t\tconst calls: AIToolCall[] = [];\n\n\t\tfor (const call of data.choices?.[0]?.message?.tool_calls ?? []) {\n\t\t\tlet args: Record<string, unknown> = {};\n\t\t\ttry {\n\t\t\t\t// Providers can emit '' for a no-argument call\n\t\t\t\targs = call.function.arguments ? (JSON.parse(call.function.arguments) as Record<string, unknown>) : {};\n\t\t\t} catch {\n\t\t\t\t// Leave args empty — the tool dispatcher reports the resulting validation error back to\n\t\t\t\t// the model, which is more recoverable than failing the whole request here.\n\t\t\t}\n\n\t\t\tcalls.push({\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.function.name,\n\t\t\t\targs: args,\n\t\t\t\t...(call.thought_signature != null ? { providerSignature: call.thought_signature } : undefined),\n\t\t\t});\n\t\t}\n\n\t\tfor (const block of data.content ?? []) {\n\t\t\tif (block.type !== 'tool_use' || block.id == null || block.name == null) continue;\n\n\t\t\tcalls.push({ id: block.id, name: block.name, args: block.input ?? {} });\n\t\t}\n\n\t\treturn calls.length ? calls : undefined;\n\t}\n\n\tprotected async handleFetchFailure<TAction extends AIActionType>(\n\t\trsp: Response,\n\t\t_action: TAction,\n\t\tmodel: AIModel<T>,\n\t\tretries: number,\n\t\tmaxInputTokens: number,\n\t\t/** The already-read error body, when the caller had to read it (avoids a second decode) */\n\t\tbody?: string,\n\t\tsentTools?: boolean,\n\t): Promise<{ retry: true; maxInputTokens: number; withoutTools?: boolean }> {\n\t\tif (rsp.status === 404) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.Unauthorized,\n\t\t\t\tnew Error(`Your API key doesn't seem to have access to the selected '${model.id}' model`),\n\t\t\t);\n\t\t}\n\t\tif (rsp.status === 429) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RateLimitOrFundsExceeded,\n\t\t\t\tnew Error(\n\t\t\t\t\t`(${this.name}) ${rsp.status}: Too many requests (rate limit exceeded) or your account is out of funds`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tlet json;\n\t\ttry {\n\t\t\tjson = (body != null ? JSON.parse(body) : await rsp.json()) as\n\t\t\t\t| { error?: { code: string; message: string } }\n\t\t\t\t| undefined;\n\t\t} catch {}\n\n\t\tif (Array.isArray(json)) {\n\t\t\tjson = json[0];\n\t\t}\n\n\t\t// A revoked/invalid key or no access: AI is unavailable to this user, not a one-off failure.\n\t\t// Neither retry probe below can apply — both only match a 400 (or the OpenRouter 404 handled above)\n\t\tif (rsp.status === 401 || rsp.status === 403) {\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.Unauthorized,\n\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`),\n\t\t\t);\n\t\t}\n\n\t\tif (json?.error?.code === 'context_length_exceeded') {\n\t\t\tif (retries < 3) {\n\t\t\t\treturn { retry: true, maxInputTokens: getReducedMaxInputTokens(maxInputTokens, retries + 1) };\n\t\t\t}\n\n\t\t\tthrow new AIError(\n\t\t\t\tAIErrorReason.RequestTooLarge,\n\t\t\t\tnew Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`),\n\t\t\t);\n\t\t}\n\n\t\t// A provider that advertises the OpenAI shape but rejects `tools` (an older or partial\n\t\t// implementation) gets one retry without them, so the caller degrades to text-only instead of\n\t\t// failing outright.\n\t\tif (sentTools && this.isToolsRejection(rsp.status, json?.error?.message)) {\n\t\t\treturn { retry: true, maxInputTokens: maxInputTokens, withoutTools: true };\n\t\t}\n\n\t\tthrow new Error(`(${this.name}) ${rsp.status}: ${json?.error?.message || rsp.statusText}`);\n\t}\n\n\tprotected async fetchCore<TAction extends AIActionType>(\n\t\taction: TAction,\n\t\tmodel: AIModel<T>,\n\t\tapiKey: string,\n\t\trequest: object,\n\t\tsignal: AbortSignal | undefined,\n\t\tconversationId?: string,\n\t): Promise<Response> {\n\t\tconst url = this.getUrl(model);\n\t\tif (!url) {\n\t\t\tthrow new Error(`(${this.name}) ${getActionName(action)}: No URL configured`);\n\t\t}\n\n\t\tconst body = JSON.stringify(request);\n\n\t\t// A rejected request is otherwise undiagnosable: a proxying backend reports only its own\n\t\t// \"upstream AI provider error (<provider> HTTP <status>)\", so which field the upstream objected to\n\t\t// can only be inferred. Log the request's shape — which fields were sent, and how much — but not\n\t\t// the messages, which carry the user's source code.\n\t\tLogger.trace(\n\t\t\t`(${this.name}) ${getActionName(action)} → ${model.id}: fields=[${Object.keys(request)\n\t\t\t\t.sort()\n\t\t\t\t.join(',')}] bytes=${body.length}`,\n\t\t);\n\n\t\ttry {\n\t\t\treturn await this.context.fetch(url, {\n\t\t\t\theaders: await this.getHeaders(action, apiKey, model, url, conversationId),\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: body,\n\t\t\t\tsignal: signal,\n\t\t\t});\n\t\t} catch (ex) {\n\t\t\tif (ex.name === 'AbortError') throw new CancellationError(ex);\n\t\t\tthrow ex;\n\t\t}\n\t}\n}\n\nexport interface ChatCompletionRequest {\n\tmodel: string;\n\t/** Provider-shaped messages from {@link OpenAICompatibleProviderBase.serializeMessages} */\n\tmessages: unknown[];\n\n\t/** Anthropic carries the initial system prompt here instead of as a `system`-role message */\n\tsystem?: string;\n\n\t/** Tool definitions, shaped by {@link OpenAICompatibleProviderBase.serializeTools} */\n\ttools?: unknown[];\n\n\t/** @deprecated but used by Anthropic & Gemini */\n\tmax_tokens?: number;\n\t/** Currently can't be used for Anthropic & Gemini */\n\tmax_completion_tokens?: number;\n\tmetadata?: Record<string, string>;\n\tstream?: boolean;\n\ttemperature?: number;\n\ttop_p?: number;\n\n\t/** Not supported by many models/providers */\n\treasoning_effort?: 'low' | 'medium' | 'high';\n\n\t/** OpenAI-compatible structured output (set via {@link OpenAICompatibleProviderBase.applyResponseFormat}) */\n\tresponse_format?: { type: 'json_schema'; json_schema: { name: string; strict: boolean; schema: JSONSchema } };\n\t/** Anthropic-native structured output (set only by the Anthropic override) */\n\toutput_config?: { format: { type: 'json_schema'; schema: JSONSchema } };\n\t/** OpenRouter-only routing constraint (set only by the OpenRouter override) */\n\tprovider?: { require_parameters: boolean };\n}\n\nexport interface ChatCompletionResponse {\n\tid: string;\n\tmodel: string;\n\t/** OpenAI compatible output */\n\tchoices?: {\n\t\tindex: number;\n\t\tmessage: {\n\t\t\trole: string;\n\t\t\tcontent: string | null;\n\t\t\t/** OpenAI-only; most OpenAI-compatible servers omit it entirely */\n\t\t\trefusal?: string | null;\n\t\t\ttool_calls?: {\n\t\t\t\tid: string;\n\t\t\t\ttype: string;\n\t\t\t\tfunction: { name: string; arguments: string };\n\t\t\t\t/** GitKraken proxy only — an opaque provider reasoning token to round-trip */\n\t\t\t\tthought_signature?: string;\n\t\t\t}[];\n\t\t};\n\t\tfinish_reason: string;\n\t}[];\n\t/** Anthropic output (non-text blocks, e.g. thinking, carry no text). `tool_use` blocks add the\n\t * call fields, which is why `id`/`name` are optional here — {@link parseToolCalls} narrows on\n\t * `type` and then checks them. */\n\tcontent?: { type: string; text?: string; id?: string; name?: string; input?: Record<string, unknown> }[];\n\t/** Error envelope some providers/proxies return with an OK status (e.g. OpenRouter upstream failures) */\n\terror?: { message?: string; code?: number | string } | null;\n\t/** Anthropic stop reason */\n\tstop_reason?: string;\n\tusage: {\n\t\t/** OpenAI compatible */\n\t\tprompt_tokens?: number;\n\t\tcompletion_tokens?: number;\n\t\ttotal_tokens?: number;\n\n\t\t/** Anthropic */\n\t\tinput_tokens?: number;\n\t\toutput_tokens?: number;\n\n\t\t/** GitKraken */\n\t\tgk: {\n\t\t\tused: number;\n\t\t\tlimit: number;\n\t\t\tresets_on: string;\n\t\t};\n\t};\n}\n"],"mappings":"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"}