ethagent 3.3.4 → 4.0.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 (322) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/.claude-plugin/plugin.json +35 -0
  3. package/LICENSE +1 -1
  4. package/README.md +64 -104
  5. package/commands/ethagent.md +40 -0
  6. package/package.json +16 -16
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -6
  8. package/src/app/keybindings/types.ts +1 -6
  9. package/src/cli/ResetConfirmView.tsx +54 -53
  10. package/src/cli/demo.ts +86 -0
  11. package/src/cli/hookIo.ts +45 -0
  12. package/src/cli/main.tsx +94 -123
  13. package/src/cli/memoryGuard.ts +49 -0
  14. package/src/cli/reset.ts +28 -70
  15. package/src/cli/sessionStart.ts +33 -0
  16. package/src/cli/status.ts +46 -0
  17. package/src/cli/sync.ts +167 -0
  18. package/src/cli/syncAdapters/claude-code.ts +86 -0
  19. package/src/cli/syncAdapters/codex.ts +66 -0
  20. package/src/cli/syncAdapters/index.ts +45 -0
  21. package/src/cli/syncAdapters/managedBlock.ts +175 -0
  22. package/src/cli/syncAdapters/shared.ts +63 -0
  23. package/src/identity/continuity/envelopeParse.ts +20 -1
  24. package/src/identity/continuity/publicSkills.ts +3 -1
  25. package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
  26. package/src/identity/continuity/skills/scaffold.ts +5 -2
  27. package/src/identity/continuity/snapshots.ts +12 -5
  28. package/src/identity/continuity/storage/defaults.ts +20 -19
  29. package/src/identity/continuity/storage/status.ts +1 -1
  30. package/src/identity/ens/ensLookup/constants.ts +1 -1
  31. package/src/identity/manager/IdentityManager.tsx +33 -0
  32. package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
  33. package/src/identity/{hub → manager}/Routes.tsx +48 -34
  34. package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
  35. package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
  36. package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
  37. package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
  38. package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
  39. package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
  40. package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
  41. package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
  42. package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
  43. package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
  44. package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
  45. package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
  46. package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
  47. package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
  48. package/src/identity/{hub → manager}/create/effects.ts +19 -10
  49. package/src/identity/manager/create/importScan.ts +122 -0
  50. package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
  51. package/src/identity/{hub → manager}/custody/actions.ts +1 -15
  52. package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
  53. package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
  54. package/src/identity/{hub → manager}/custody/types.ts +1 -2
  55. package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
  56. package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
  57. package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
  58. package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
  59. package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
  60. package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
  61. package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
  62. package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
  63. package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
  64. package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
  65. package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
  66. package/src/identity/{hub → manager}/profile/effects.ts +1 -3
  67. package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
  68. package/src/identity/{hub → manager}/profile/state.ts +1 -1
  69. package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
  70. package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
  71. package/src/identity/{hub → manager}/restore/apply.ts +1 -1
  72. package/src/identity/{hub → manager}/restore/auth.ts +1 -1
  73. package/src/identity/{hub → manager}/restore/discover.ts +1 -1
  74. package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
  75. package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
  76. package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
  77. package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
  78. package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
  79. package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
  80. package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
  81. package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
  82. package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
  83. package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
  84. package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
  85. package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
  86. package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
  87. package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
  88. package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
  89. package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
  90. package/src/identity/manager/shared/effects/types.ts +30 -0
  91. package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
  92. package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
  93. package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
  94. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
  95. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
  96. package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
  97. package/src/identity/{hub → manager}/shared/utils.ts +6 -10
  98. package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
  99. package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
  100. package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
  101. package/src/identity/{hub → manager}/types.ts +5 -6
  102. package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
  103. package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
  104. package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
  105. package/src/identity/registry/erc8004/discovery.ts +3 -17
  106. package/src/identity/registry/erc8004/utils.ts +1 -1
  107. package/src/identity/storage/ipfs.ts +21 -1
  108. package/src/identity/wallet/browserWallet/html.ts +10 -2
  109. package/src/identity/wallet/browserWallet/http.ts +18 -0
  110. package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
  111. package/src/identity/wallet/browserWallet/requests.ts +10 -28
  112. package/src/identity/wallet/browserWallet/session.ts +26 -33
  113. package/src/identity/wallet/browserWallet/validation.ts +14 -0
  114. package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
  115. package/src/identity/wallet/page/boot.ts +43 -0
  116. package/src/identity/wallet/page/config.ts +59 -0
  117. package/src/identity/wallet/page/constants.ts +12 -0
  118. package/src/identity/wallet/page/copy.ts +47 -68
  119. package/src/identity/wallet/page/css.ts +638 -0
  120. package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
  121. package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
  122. package/src/identity/wallet/page/markup.ts +44 -34
  123. package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
  124. package/src/identity/wallet/page/resize.ts +95 -0
  125. package/src/identity/wallet/page/state.ts +135 -8
  126. package/src/identity/wallet/page/timeline.ts +161 -0
  127. package/src/identity/wallet/page/view.ts +22 -302
  128. package/src/storage/config.ts +30 -80
  129. package/src/storage/reset.ts +31 -0
  130. package/src/storage/secrets.ts +1 -16
  131. package/src/ui/Select.tsx +27 -5
  132. package/src/ui/Spinner.tsx +16 -15
  133. package/src/ui/Surface.tsx +21 -17
  134. package/src/ui/TextArea.tsx +173 -0
  135. package/src/ui/TextInput.tsx +31 -133
  136. package/src/ui/theme.ts +22 -13
  137. package/src/utils/clipboard.ts +0 -140
  138. package/src/app/FirstRun.tsx +0 -577
  139. package/src/app/FirstRunTimeline.tsx +0 -51
  140. package/src/app/firstRunConfig.ts +0 -26
  141. package/src/app/hooks/useCancelRequest.ts +0 -22
  142. package/src/app/hooks/useDoublePress.ts +0 -46
  143. package/src/app/hooks/useExitOnCtrlC.ts +0 -36
  144. package/src/auth/openaiOAuth/credentials.ts +0 -47
  145. package/src/auth/openaiOAuth/crypto.ts +0 -23
  146. package/src/auth/openaiOAuth/index.ts +0 -238
  147. package/src/auth/openaiOAuth/landingPage.ts +0 -116
  148. package/src/auth/openaiOAuth/listener.ts +0 -151
  149. package/src/auth/openaiOAuth/refresh.ts +0 -70
  150. package/src/auth/openaiOAuth/shared.ts +0 -115
  151. package/src/chat/ChatBottomPane.tsx +0 -296
  152. package/src/chat/ChatScreen.tsx +0 -1685
  153. package/src/chat/ConversationStack.tsx +0 -56
  154. package/src/chat/MessageList.tsx +0 -638
  155. package/src/chat/SessionStatus.tsx +0 -53
  156. package/src/chat/chatEnvironment.ts +0 -16
  157. package/src/chat/chatScreenUtils.ts +0 -194
  158. package/src/chat/chatSessionState.ts +0 -146
  159. package/src/chat/chatTurnContext.ts +0 -50
  160. package/src/chat/chatTurnOrchestrator.ts +0 -603
  161. package/src/chat/chatTurnRows.ts +0 -64
  162. package/src/chat/commands.ts +0 -494
  163. package/src/chat/continuityEditReview.ts +0 -42
  164. package/src/chat/display/DiffView.tsx +0 -193
  165. package/src/chat/display/SyntaxText.tsx +0 -192
  166. package/src/chat/display/toolCallDisplay.ts +0 -103
  167. package/src/chat/display/toolResultDisplay.ts +0 -19
  168. package/src/chat/input/ChatInput.tsx +0 -625
  169. package/src/chat/input/chatInputHelpers.ts +0 -62
  170. package/src/chat/input/chatInputState.ts +0 -247
  171. package/src/chat/input/chatPaste.ts +0 -49
  172. package/src/chat/input/imageRefs.ts +0 -30
  173. package/src/chat/input/inputRendering.tsx +0 -93
  174. package/src/chat/input/textCursor.ts +0 -212
  175. package/src/chat/messageMarkdown.ts +0 -220
  176. package/src/chat/messageRows.ts +0 -43
  177. package/src/chat/planImplementation.ts +0 -62
  178. package/src/chat/slashCommandHandlers.ts +0 -122
  179. package/src/chat/slashCommandViews.ts +0 -120
  180. package/src/chat/transcript/TranscriptView.tsx +0 -184
  181. package/src/chat/transcript/transcriptViewport.ts +0 -295
  182. package/src/chat/views/ContextLimitView.tsx +0 -95
  183. package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
  184. package/src/chat/views/CopyPicker.tsx +0 -50
  185. package/src/chat/views/PermissionPrompt.tsx +0 -156
  186. package/src/chat/views/PermissionsView.tsx +0 -165
  187. package/src/chat/views/PlanApprovalView.tsx +0 -91
  188. package/src/chat/views/ResumeView.tsx +0 -273
  189. package/src/chat/views/RewindView.tsx +0 -412
  190. package/src/cli/preview.tsx +0 -14
  191. package/src/cli/updateNotice.ts +0 -54
  192. package/src/identity/continuity/privateEdit/apply.ts +0 -170
  193. package/src/identity/continuity/privateEdit/diff.ts +0 -6
  194. package/src/identity/continuity/privateEdit/files.ts +0 -23
  195. package/src/identity/continuity/privateEdit/types.ts +0 -28
  196. package/src/identity/continuity/privateEdit.ts +0 -46
  197. package/src/identity/hub/IdentityHub.tsx +0 -14
  198. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
  199. package/src/identity/hub/ens/effects.ts +0 -218
  200. package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
  201. package/src/identity/hub/shared/effects/types.ts +0 -53
  202. package/src/identity/hub/shared/reconciliation/index.ts +0 -14
  203. package/src/identity/wallet/page/grainient.ts +0 -278
  204. package/src/identity/wallet/page/html.ts +0 -28
  205. package/src/identity/wallet/page/styles/base.ts +0 -260
  206. package/src/identity/wallet/page/styles/components.ts +0 -262
  207. package/src/identity/wallet/page/styles/index.ts +0 -5
  208. package/src/identity/wallet/page/styles/responsive.ts +0 -247
  209. package/src/identity/wallet/page.tsx +0 -38
  210. package/src/mcp/approvals.ts +0 -113
  211. package/src/mcp/config.ts +0 -235
  212. package/src/mcp/manager.ts +0 -482
  213. package/src/mcp/managerHelpers.ts +0 -70
  214. package/src/mcp/names.ts +0 -19
  215. package/src/mcp/output.ts +0 -96
  216. package/src/models/ModelPicker.tsx +0 -1009
  217. package/src/models/catalog.ts +0 -327
  218. package/src/models/huggingface.ts +0 -712
  219. package/src/models/huggingfaceStorage.ts +0 -136
  220. package/src/models/llamacpp.ts +0 -848
  221. package/src/models/llamacppCommands.ts +0 -44
  222. package/src/models/llamacppConfig.ts +0 -34
  223. package/src/models/llamacppDiscovery.ts +0 -176
  224. package/src/models/llamacppOutput.ts +0 -65
  225. package/src/models/llamacppPreflight.ts +0 -158
  226. package/src/models/modelDisplay.ts +0 -180
  227. package/src/models/modelPickerCatalogFlow.ts +0 -56
  228. package/src/models/modelPickerCredentials.ts +0 -166
  229. package/src/models/modelPickerData.ts +0 -41
  230. package/src/models/modelPickerDisplay.tsx +0 -132
  231. package/src/models/modelPickerHfFlow.ts +0 -192
  232. package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
  233. package/src/models/modelPickerOptions.ts +0 -457
  234. package/src/models/modelPickerTypes.ts +0 -69
  235. package/src/models/modelPickerUninstallFlow.ts +0 -48
  236. package/src/models/modelPickerViewHelpers.ts +0 -174
  237. package/src/models/modelRecommendation.ts +0 -139
  238. package/src/models/providerDisplay.ts +0 -16
  239. package/src/models/runtimeDetection.ts +0 -81
  240. package/src/models/uncensoredCatalog.ts +0 -86
  241. package/src/providers/anthropic.ts +0 -290
  242. package/src/providers/contracts.ts +0 -71
  243. package/src/providers/errors.ts +0 -80
  244. package/src/providers/gemini.ts +0 -391
  245. package/src/providers/openai-chat.ts +0 -474
  246. package/src/providers/openai-responses-format.ts +0 -177
  247. package/src/providers/openai-responses.ts +0 -306
  248. package/src/providers/openaiChatWire.ts +0 -124
  249. package/src/providers/registry.ts +0 -120
  250. package/src/providers/retry.ts +0 -58
  251. package/src/providers/sse.ts +0 -93
  252. package/src/runtime/compaction.ts +0 -395
  253. package/src/runtime/cwd.ts +0 -43
  254. package/src/runtime/providerTurn.ts +0 -38
  255. package/src/runtime/sessionMode.ts +0 -55
  256. package/src/runtime/systemPrompt.ts +0 -213
  257. package/src/runtime/textToolParser.ts +0 -161
  258. package/src/runtime/toolClaimGuards.ts +0 -143
  259. package/src/runtime/toolExecution.ts +0 -304
  260. package/src/runtime/toolIntent.ts +0 -143
  261. package/src/runtime/turn.ts +0 -369
  262. package/src/runtime/turnNudges.ts +0 -223
  263. package/src/runtime/turnTypes.ts +0 -86
  264. package/src/storage/factoryReset.ts +0 -127
  265. package/src/storage/history.ts +0 -58
  266. package/src/storage/permissions.ts +0 -76
  267. package/src/storage/rewind.ts +0 -266
  268. package/src/storage/sessionExport.ts +0 -49
  269. package/src/storage/sessions.ts +0 -495
  270. package/src/tools/bashSafety.ts +0 -186
  271. package/src/tools/bashTool.ts +0 -140
  272. package/src/tools/changeDirectoryTool.ts +0 -213
  273. package/src/tools/contracts.ts +0 -192
  274. package/src/tools/deleteFileTool.ts +0 -116
  275. package/src/tools/editTool.ts +0 -165
  276. package/src/tools/editUtils.ts +0 -170
  277. package/src/tools/fileDiff.ts +0 -261
  278. package/src/tools/listDirectoryTool.ts +0 -55
  279. package/src/tools/listSkillFilesTool.ts +0 -77
  280. package/src/tools/listSkillsTool.ts +0 -68
  281. package/src/tools/mcpResourceTools.ts +0 -95
  282. package/src/tools/permissionRules.ts +0 -85
  283. package/src/tools/privateContinuityEditTool.ts +0 -187
  284. package/src/tools/privateContinuityReadTool.ts +0 -106
  285. package/src/tools/readSkillTool.ts +0 -107
  286. package/src/tools/readTool.ts +0 -85
  287. package/src/tools/registry.ts +0 -103
  288. package/src/tools/writeFileTool.ts +0 -167
  289. package/src/ui/BrandSplash.tsx +0 -133
  290. package/src/ui/terminalTitle.ts +0 -30
  291. package/src/utils/images.ts +0 -140
  292. package/src/utils/markdownSegments.ts +0 -51
  293. package/src/utils/messages.ts +0 -37
  294. package/src/utils/withRetry.ts +0 -324
  295. /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
  296. /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
  297. /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
  298. /package/src/identity/{hub → manager}/custody/state.ts +0 -0
  299. /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
  300. /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
  301. /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
  302. /package/src/identity/{hub → manager}/ens/state.ts +0 -0
  303. /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
  304. /package/src/identity/{hub → manager}/ens/types.ts +0 -0
  305. /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
  306. /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
  307. /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
  308. /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
  309. /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
  310. /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
  311. /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
  312. /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
  313. /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
  314. /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
  315. /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
  316. /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
  317. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
  318. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
  319. /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
  320. /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
  321. /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
  322. /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
@@ -1,140 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
- import type { ImageBlock, Message, MessageContentBlock } from '../providers/contracts.js'
4
-
5
- const IMAGE_MARKER_RE = /\[image:\s*([^\]]+?)\]/gi
6
- const PLACEHOLDER_RE = /^([<{[].*[>}\]]|#\d+)$/
7
-
8
- export class ImageLoadError extends Error {
9
- readonly imagePath: string
10
- constructor(imagePath: string, message: string) {
11
- super(message)
12
- this.name = 'ImageLoadError'
13
- this.imagePath = imagePath
14
- }
15
- }
16
-
17
- export function collapseImagePathsToRefs(text: string): string {
18
- let counter = 0
19
- return text.replace(IMAGE_MARKER_RE, (full, raw: string) => {
20
- const trimmed = raw.trim()
21
- if (!trimmed || PLACEHOLDER_RE.test(trimmed)) return full
22
- counter += 1
23
- return `[Image #${counter}]`
24
- })
25
- }
26
-
27
- export function modelSupportsImages(
28
- provider: string,
29
- model: string,
30
- extra?: { mmprojPath?: string },
31
- ): boolean {
32
- const normalized = model.toLowerCase()
33
- switch (provider) {
34
- case 'anthropic':
35
- return /claude-3|claude-sonnet-4|claude-opus-4|claude-haiku-4/.test(normalized)
36
- case 'gemini':
37
- return /gemini-1\.5|gemini-2\.0|gemini-2\.5/.test(normalized)
38
- case 'openai':
39
- if (normalized.includes('gpt-3.5')) return false
40
- return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-4-vision|gpt-5|o1|o3|o4|chatgpt-4/.test(normalized)
41
- case 'llamacpp':
42
- return Boolean(extra?.mmprojPath)
43
- default:
44
- return false
45
- }
46
- }
47
-
48
- export function hasImageBlocks(messages: Message[]): boolean {
49
- return messages.some(message => Array.isArray(message.content) && message.content.some(block => block.type === 'image'))
50
- }
51
-
52
- export function userTextToContentBlocks(text: string): string | MessageContentBlock[] {
53
- const blocks = parseImageMarkers(text)
54
- return blocks.length === 1 && blocks[0]?.type === 'text' ? blocks[0].text : blocks
55
- }
56
-
57
- export function parseImageMarkers(text: string): MessageContentBlock[] {
58
- const out: MessageContentBlock[] = []
59
- let lastIndex = 0
60
- let match: RegExpExecArray | null
61
-
62
- while ((match = IMAGE_MARKER_RE.exec(text)) !== null) {
63
- const full = match[0]
64
- const rawPath = match[1]?.trim() ?? ''
65
- if (match.index > lastIndex) {
66
- const prefix = text.slice(lastIndex, match.index)
67
- if (prefix) out.push({ type: 'text', text: prefix })
68
- }
69
- if (rawPath && !PLACEHOLDER_RE.test(rawPath)) {
70
- out.push({ type: 'image', path: rawPath })
71
- } else {
72
- out.push({ type: 'text', text: full })
73
- }
74
- lastIndex = match.index + full.length
75
- }
76
-
77
- if (lastIndex < text.length) {
78
- const suffix = text.slice(lastIndex)
79
- if (suffix) out.push({ type: 'text', text: suffix })
80
- }
81
-
82
- if (out.length === 0) return text ? [{ type: 'text', text }] : []
83
- return mergeAdjacentTextBlocks(out)
84
- }
85
-
86
- export async function loadImageBlock(block: ImageBlock): Promise<ImageBlock> {
87
- if (block.dataBase64 && block.mimeType) return block
88
- if (block.url) return block
89
- const rawPath = block.path?.trim() ?? ''
90
- if (!rawPath) throw new ImageLoadError(rawPath, 'image path is empty')
91
- if (PLACEHOLDER_RE.test(rawPath)) {
92
- throw new ImageLoadError(rawPath, `image path looks like a placeholder, not a real file: ${rawPath}`)
93
- }
94
- let file: Buffer
95
- try {
96
- file = await fs.readFile(rawPath)
97
- } catch (err: unknown) {
98
- const code = (err as NodeJS.ErrnoException).code
99
- if (code === 'ENOENT') {
100
- throw new ImageLoadError(rawPath, `image file not found: ${rawPath}`)
101
- }
102
- throw new ImageLoadError(rawPath, `could not read image at ${rawPath}: ${(err as Error).message}`)
103
- }
104
- const mimeType = block.mimeType ?? mimeTypeForPath(rawPath)
105
- return {
106
- ...block,
107
- path: rawPath,
108
- mimeType,
109
- dataBase64: file.toString('base64'),
110
- }
111
- }
112
-
113
- export function imagePlaceholder(pathValue: string): string {
114
- return `[image: ${path.basename(pathValue)}]`
115
- }
116
-
117
- function mergeAdjacentTextBlocks(blocks: MessageContentBlock[]): MessageContentBlock[] {
118
- const out: MessageContentBlock[] = []
119
- for (const block of blocks) {
120
- const prev = out[out.length - 1]
121
- if (block.type === 'text' && prev?.type === 'text') {
122
- prev.text += block.text
123
- continue
124
- }
125
- out.push(block)
126
- }
127
- return out
128
- }
129
-
130
- function mimeTypeForPath(filePath: string): string {
131
- switch (path.extname(filePath).toLowerCase()) {
132
- case '.png': return 'image/png'
133
- case '.jpg':
134
- case '.jpeg': return 'image/jpeg'
135
- case '.webp': return 'image/webp'
136
- case '.gif': return 'image/gif'
137
- case '.bmp': return 'image/bmp'
138
- default: return 'application/octet-stream'
139
- }
140
- }
@@ -1,51 +0,0 @@
1
- export type Segment =
2
- | { kind: 'text'; content: string; preview: string }
3
- | { kind: 'code'; lang: string | null; content: string; preview: string }
4
-
5
- const FENCE = /^[ \t]{0,3}```([\w+-]*)[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}```[ \t]*(?:\r?\n|$)/gm
6
-
7
- export function parseSegments(markdown: string): Segment[] {
8
- if (!markdown) return []
9
- const segments: Segment[] = []
10
- let lastIndex = 0
11
- FENCE.lastIndex = 0
12
- let match: RegExpExecArray | null
13
- while ((match = FENCE.exec(markdown)) !== null) {
14
- const before = markdown.slice(lastIndex, match.index)
15
- const textSeg = toTextSegment(before)
16
- if (textSeg) segments.push(textSeg)
17
- const lang = match[1] && match[1].length > 0 ? match[1] : null
18
- const body = match[2] ?? ''
19
- segments.push({
20
- kind: 'code',
21
- lang,
22
- content: body,
23
- preview: codePreview(lang, body),
24
- })
25
- lastIndex = match.index + match[0].length
26
- }
27
- const tail = markdown.slice(lastIndex)
28
- const tailSeg = toTextSegment(tail)
29
- if (tailSeg) segments.push(tailSeg)
30
- return segments
31
- }
32
-
33
- function toTextSegment(raw: string): Segment | null {
34
- const cleaned = raw.replace(/\r\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim()
35
- if (!cleaned) return null
36
- return { kind: 'text', content: cleaned, preview: textPreview(cleaned) }
37
- }
38
-
39
- function textPreview(text: string): string {
40
- const firstLine = text.split('\n').find(line => line.trim().length > 0) ?? text
41
- const trimmed = firstLine.trim()
42
- const chars = text.length
43
- const snippet = trimmed.length > 48 ? trimmed.slice(0, 47) + '…' : trimmed
44
- return `text · ${chars} chars · ${snippet}`
45
- }
46
-
47
- function codePreview(lang: string | null, body: string): string {
48
- const lineCount = body.length === 0 ? 0 : body.split('\n').length
49
- const label = lang ?? 'code'
50
- return `code · ${label} · ${lineCount} line${lineCount === 1 ? '' : 's'}`
51
- }
@@ -1,37 +0,0 @@
1
- import path from 'node:path'
2
- import type { Message, MessageContentBlock } from '../providers/contracts.js'
3
-
4
- export function systemMessage(content: string): Message {
5
- return { role: 'system', content }
6
- }
7
-
8
- export function userMessage(content: string | MessageContentBlock[]): Message {
9
- return { role: 'user', content }
10
- }
11
-
12
- export function assistantMessage(content: string | MessageContentBlock[]): Message {
13
- return { role: 'assistant', content }
14
- }
15
-
16
- export function messageTextContent(message: Message): string {
17
- return typeof message.content === 'string' ? message.content : blocksToText(message.content)
18
- }
19
-
20
- export function blocksToText(blocks: MessageContentBlock[]): string {
21
- return blocks
22
- .map(block => {
23
- if (block.type === 'text') return block.text
24
- if (block.type === 'image') return `[image attached: ${path.basename(block.path)}]`
25
- if (block.type === 'tool_use') return `[tool use: ${block.name}]`
26
- return block.isError
27
- ? `[tool error: ${block.content}]`
28
- : `[tool result: ${block.content}]`
29
- })
30
- .join('\n')
31
- }
32
-
33
- export function approximateTokens(messages: Message[]): number {
34
- let chars = 0
35
- for (const m of messages) chars += messageTextContent(m).length
36
- return Math.ceil(chars / 3)
37
- }
@@ -1,324 +0,0 @@
1
- export type RetryClassification = {
2
- retryable: boolean
3
- retryAfterMs?: number
4
- reason: string
5
- status?: number
6
- code?: string
7
- }
8
-
9
- export type RetryBodyHint = {
10
- retryAfterMs?: number
11
- fatal?: boolean
12
- reason?: string
13
- }
14
-
15
- export type RetryPolicy = {
16
- maxRetries: number
17
- baseDelayMs: number
18
- maxDelayMs: number
19
- retryAfterCapMs: number
20
- jitterRatio: number
21
- }
22
-
23
- export type RetryEvent = {
24
- attempt: number
25
- nextAttempt: number
26
- maxRetries: number
27
- delayMs: number
28
- reason: string
29
- retryAfterMs?: number
30
- status?: number
31
- code?: string
32
- }
33
-
34
- const RETRYABLE_NET_CODES = new Set([
35
- 'ECONNRESET',
36
- 'EPIPE',
37
- 'ETIMEDOUT',
38
- 'ECONNREFUSED',
39
- 'EHOSTUNREACH',
40
- 'ENETUNREACH',
41
- 'EAI_AGAIN',
42
- ])
43
-
44
- const RETRYABLE_STATUS = new Set([408, 409, 425, 429, 500, 502, 503, 504, 529])
45
- const DEFAULT_BASE_DELAY_MS = 500
46
- const DEFAULT_MAX_DELAY_MS = 32_000
47
- const DEFAULT_JITTER_RATIO = 0.25
48
- const DEFAULT_MAX_RETRIES = 4
49
-
50
- export const DEFAULT_RETRY_POLICY: RetryPolicy = {
51
- maxRetries: DEFAULT_MAX_RETRIES,
52
- baseDelayMs: DEFAULT_BASE_DELAY_MS,
53
- maxDelayMs: DEFAULT_MAX_DELAY_MS,
54
- retryAfterCapMs: DEFAULT_MAX_DELAY_MS * 4,
55
- jitterRatio: DEFAULT_JITTER_RATIO,
56
- }
57
-
58
- export function classifyRetryableFetchError(err: unknown): RetryClassification {
59
- if (!err || typeof err !== 'object') return { retryable: false, reason: 'unknown error' }
60
- const code = (err as { code?: unknown }).code
61
- if (typeof code === 'string' && RETRYABLE_NET_CODES.has(code)) {
62
- return { retryable: true, reason: code, code }
63
- }
64
- const cause = (err as { cause?: unknown }).cause
65
- if (cause && typeof cause === 'object') {
66
- const causeCode = (cause as { code?: unknown }).code
67
- if (typeof causeCode === 'string' && RETRYABLE_NET_CODES.has(causeCode)) {
68
- return { retryable: true, reason: causeCode, code: causeCode }
69
- }
70
- }
71
- const message = (err as { message?: unknown }).message
72
- if (typeof message === 'string' && /fetch failed|network|socket hang up|ECONNRESET/i.test(message)) {
73
- return { retryable: true, reason: 'fetch failed' }
74
- }
75
- return { retryable: false, reason: 'non-retryable error' }
76
- }
77
-
78
- export function classifyRetryableResponse(response: Response, nowMs: number = Date.now()): RetryClassification {
79
- if (response.ok) return { retryable: false, reason: 'ok', status: response.status }
80
- if (!RETRYABLE_STATUS.has(response.status)) {
81
- return { retryable: false, reason: `HTTP ${response.status}`, status: response.status }
82
- }
83
- const retryAfter = response.headers.get('retry-after')
84
- const retryAfterMs = retryAfter ? parseRetryAfter(retryAfter, nowMs) : undefined
85
- return { retryable: true, retryAfterMs, reason: `HTTP ${response.status}`, status: response.status }
86
- }
87
-
88
- export function classifyRetryableProviderResponse(
89
- response: Response,
90
- nowMs: number = Date.now(),
91
- rateLimitResetProvider?: RateLimitResetProvider,
92
- ): RetryClassification {
93
- const classification = classifyRetryableResponse(response, nowMs)
94
- if (
95
- !classification.retryable ||
96
- classification.retryAfterMs !== undefined ||
97
- response.status !== 429 ||
98
- !rateLimitResetProvider
99
- ) {
100
- return classification
101
- }
102
- return {
103
- ...classification,
104
- retryAfterMs: rateLimitResetDelayMs(response.headers, rateLimitResetProvider, nowMs),
105
- }
106
- }
107
-
108
- export function computeBackoffMs(
109
- attempt: number,
110
- retryAfterMs: number | undefined,
111
- maxDelayMs = DEFAULT_MAX_DELAY_MS,
112
- baseDelayMs = DEFAULT_BASE_DELAY_MS,
113
- rng: () => number = Math.random,
114
- jitterRatio = DEFAULT_JITTER_RATIO,
115
- retryAfterCapMs = maxDelayMs * 4,
116
- ): number {
117
- if (retryAfterMs !== undefined && Number.isFinite(retryAfterMs)) {
118
- return Math.min(Math.max(retryAfterMs, 0), retryAfterCapMs)
119
- }
120
- const expo = Math.min(baseDelayMs * Math.pow(2, attempt - 1), maxDelayMs)
121
- const jitter = rng() * jitterRatio * expo
122
- return Math.floor(expo + jitter)
123
- }
124
-
125
- export function retryPolicyFromOptions(options: FetchWithRetryOptions = {}): RetryPolicy {
126
- const maxDelayMs = options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs
127
- return {
128
- maxRetries: options.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
129
- baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
130
- maxDelayMs,
131
- retryAfterCapMs: options.retryAfterCapMs ?? maxDelayMs * 4,
132
- jitterRatio: options.jitterRatio ?? DEFAULT_RETRY_POLICY.jitterRatio,
133
- }
134
- }
135
-
136
- export function parseRetryAfter(headerValue: string, nowMs: number = Date.now()): number | undefined {
137
- const trimmed = headerValue.trim()
138
- if (!trimmed) return undefined
139
- const seconds = Number(trimmed)
140
- if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000)
141
- const dateMs = Date.parse(trimmed)
142
- if (Number.isFinite(dateMs)) return Math.max(0, dateMs - nowMs)
143
- return undefined
144
- }
145
-
146
- export function parseOpenAIRateLimitResetMs(value: string): number | undefined {
147
- if (!value) return undefined
148
- const match = /^(?:(\d+)h)?(?:(\d+)m(?!s))?(?:(\d+)s)?(?:(\d+)ms)?$/.exec(value.trim())
149
- if (!match || match[0] === '') return undefined
150
- const hours = Number.parseInt(match[1] ?? '0', 10)
151
- const minutes = Number.parseInt(match[2] ?? '0', 10)
152
- const seconds = Number.parseInt(match[3] ?? '0', 10)
153
- const milliseconds = Number.parseInt(match[4] ?? '0', 10)
154
- const total = hours * 3_600_000 + minutes * 60_000 + seconds * 1000 + milliseconds
155
- return total > 0 ? total : undefined
156
- }
157
-
158
- export type RateLimitResetProvider = 'anthropic' | 'openai-compatible'
159
-
160
- export function rateLimitResetDelayMs(
161
- headers: Headers,
162
- provider: RateLimitResetProvider,
163
- nowMs: number = Date.now(),
164
- capMs = DEFAULT_RETRY_POLICY.retryAfterCapMs,
165
- ): number | undefined {
166
- if (provider === 'anthropic') {
167
- const reset = headers.get('anthropic-ratelimit-unified-reset')
168
- if (!reset) return undefined
169
- const unixSeconds = Number(reset)
170
- if (!Number.isFinite(unixSeconds)) return undefined
171
- const delay = unixSeconds * 1000 - nowMs
172
- return delay > 0 ? Math.min(delay, capMs) : undefined
173
- }
174
-
175
- const requestDelay = parseOpenAIRateLimitResetMs(headers.get('x-ratelimit-reset-requests') ?? '')
176
- const tokenDelay = parseOpenAIRateLimitResetMs(headers.get('x-ratelimit-reset-tokens') ?? '')
177
- const delay = Math.max(requestDelay ?? 0, tokenDelay ?? 0)
178
- return delay > 0 ? Math.min(delay, capMs) : undefined
179
- }
180
-
181
- export type FetchWithRetryOptions = {
182
- maxRetries?: number
183
- baseDelayMs?: number
184
- maxDelayMs?: number
185
- retryAfterCapMs?: number
186
- jitterRatio?: number
187
- rateLimitResetProvider?: RateLimitResetProvider
188
- parseRetryHintFromBody?: (body: string) => RetryBodyHint | undefined
189
- signal?: AbortSignal
190
- fetchImpl?: typeof fetch
191
- sleep?: (ms: number, signal?: AbortSignal) => Promise<void>
192
- rng?: () => number
193
- now?: () => number
194
- onRetry?: (event: RetryEvent) => void
195
- }
196
-
197
- export async function fetchWithRetry(
198
- input: string,
199
- init: RequestInit,
200
- options: FetchWithRetryOptions = {},
201
- ): Promise<Response> {
202
- const policy = retryPolicyFromOptions(options)
203
- const fetchImpl = options.fetchImpl ?? fetch
204
- const sleepImpl = options.sleep ?? sleep
205
- const rng = options.rng ?? Math.random
206
- const now = options.now ?? Date.now
207
- let lastError: unknown
208
- for (let attempt = 1; attempt <= policy.maxRetries + 1; attempt += 1) {
209
- if (options.signal?.aborted) throw new DOMException('aborted', 'AbortError')
210
-
211
- try {
212
- const response = await fetchImpl(input, { ...init, signal: options.signal })
213
- if (response.ok) return response
214
-
215
- let classification = classifyRetryableProviderResponse(response, now(), options.rateLimitResetProvider)
216
- let bufferedResponse: Response | undefined
217
- if (
218
- classification.retryable
219
- && response.status === 429
220
- && options.parseRetryHintFromBody
221
- ) {
222
- let bodyText = ''
223
- try { bodyText = await response.text() } catch {}
224
- bufferedResponse = new Response(bodyText, {
225
- status: response.status,
226
- statusText: response.statusText,
227
- headers: response.headers,
228
- })
229
- const hint = bodyText ? safeParseRetryHint(bodyText, options.parseRetryHintFromBody) : undefined
230
- if (hint?.fatal) {
231
- return bufferedResponse
232
- }
233
- if (hint?.retryAfterMs !== undefined) {
234
- classification = { ...classification, retryAfterMs: hint.retryAfterMs }
235
- }
236
- if (hint?.reason) {
237
- classification = { ...classification, reason: `${classification.reason} ${hint.reason}` }
238
- }
239
- }
240
- if (!classification.retryable || attempt > policy.maxRetries) return bufferedResponse ?? response
241
-
242
- if (!bufferedResponse) {
243
- try { await response.body?.cancel() } catch {}
244
- }
245
- const delayMs = computeBackoffMs(
246
- attempt,
247
- classification.retryAfterMs,
248
- policy.maxDelayMs,
249
- policy.baseDelayMs,
250
- rng,
251
- policy.jitterRatio,
252
- policy.retryAfterCapMs,
253
- )
254
- options.onRetry?.(retryEvent(attempt, policy.maxRetries, delayMs, classification))
255
- await sleepImpl(delayMs, options.signal)
256
- continue
257
- } catch (err) {
258
- lastError = err
259
- if (options.signal?.aborted) throw err
260
- const classification = classifyRetryableFetchError(err)
261
- if (!classification.retryable || attempt > policy.maxRetries) throw err
262
-
263
- const delayMs = computeBackoffMs(
264
- attempt,
265
- classification.retryAfterMs,
266
- policy.maxDelayMs,
267
- policy.baseDelayMs,
268
- rng,
269
- policy.jitterRatio,
270
- policy.retryAfterCapMs,
271
- )
272
- options.onRetry?.(retryEvent(attempt, policy.maxRetries, delayMs, classification))
273
- await sleepImpl(delayMs, options.signal)
274
- }
275
- }
276
- throw lastError ?? new Error('fetchWithRetry exhausted')
277
- }
278
-
279
- function safeParseRetryHint(
280
- body: string,
281
- parser: (body: string) => RetryBodyHint | undefined,
282
- ): RetryBodyHint | undefined {
283
- try {
284
- return parser(body)
285
- } catch {
286
- return undefined
287
- }
288
- }
289
-
290
- function retryEvent(
291
- attempt: number,
292
- maxRetries: number,
293
- delayMs: number,
294
- classification: RetryClassification,
295
- ): RetryEvent {
296
- return {
297
- attempt,
298
- nextAttempt: attempt + 1,
299
- maxRetries,
300
- delayMs,
301
- reason: classification.reason,
302
- ...(classification.retryAfterMs !== undefined ? { retryAfterMs: classification.retryAfterMs } : {}),
303
- ...(classification.status !== undefined ? { status: classification.status } : {}),
304
- ...(classification.code !== undefined ? { code: classification.code } : {}),
305
- }
306
- }
307
-
308
- export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
309
- return new Promise((resolve, reject) => {
310
- if (signal?.aborted) {
311
- reject(new DOMException('aborted', 'AbortError'))
312
- return
313
- }
314
- const timer = setTimeout(() => {
315
- signal?.removeEventListener('abort', onAbort)
316
- resolve()
317
- }, ms)
318
- const onAbort = () => {
319
- clearTimeout(timer)
320
- reject(new DOMException('aborted', 'AbortError'))
321
- }
322
- signal?.addEventListener('abort', onAbort, { once: true })
323
- })
324
- }
File without changes
File without changes
File without changes
File without changes
File without changes