ethagent 3.3.3 → 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 -259
  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,44 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
-
3
- export type RunResult = {
4
- code: number
5
- stdout: string
6
- stderr: string
7
- }
8
-
9
- export function runCommand(cmd: string, args: string[], timeoutMs = 2000): Promise<RunResult | null> {
10
- return new Promise(resolve => {
11
- let settled = false
12
- let child: ReturnType<typeof spawn>
13
- try {
14
- child = spawn(cmd, args, { windowsHide: true })
15
- } catch {
16
- resolve(null)
17
- return
18
- }
19
-
20
- let stdout = ''
21
- let stderr = ''
22
- const timer = setTimeout(() => {
23
- if (settled) return
24
- settled = true
25
- try { child.kill() } catch { void 0 }
26
- resolve(null)
27
- }, timeoutMs)
28
-
29
- child.stdout?.on('data', chunk => { stdout += chunk.toString() })
30
- child.stderr?.on('data', chunk => { stderr += chunk.toString() })
31
- child.on('error', () => {
32
- if (settled) return
33
- settled = true
34
- clearTimeout(timer)
35
- resolve(null)
36
- })
37
- child.on('close', code => {
38
- if (settled) return
39
- settled = true
40
- clearTimeout(timer)
41
- resolve({ code: code ?? -1, stdout, stderr })
42
- })
43
- })
44
- }
@@ -1,34 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
- import { atomicWriteText } from '../storage/atomicWrite.js'
4
- import { ensureConfigDir, getConfigDir } from '../storage/config.js'
5
-
6
- export type LocalRunnerConfig = {
7
- llamaServerPath?: string
8
- }
9
-
10
- export function getLocalRunnerConfigPath(): string {
11
- return path.join(getConfigDir(), 'local-runner.json')
12
- }
13
-
14
- export async function loadLocalRunnerConfig(): Promise<LocalRunnerConfig> {
15
- try {
16
- const raw = await fs.readFile(getLocalRunnerConfigPath(), 'utf8')
17
- const parsed = JSON.parse(raw) as unknown
18
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {}
19
- const value = (parsed as { llamaServerPath?: unknown }).llamaServerPath
20
- return typeof value === 'string' && value.trim() ? { llamaServerPath: value.trim() } : {}
21
- } catch (err: unknown) {
22
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {}
23
- return {}
24
- }
25
- }
26
-
27
- export async function saveLocalRunnerConfig(config: LocalRunnerConfig): Promise<void> {
28
- await ensureConfigDir()
29
- await atomicWriteText(getLocalRunnerConfigPath(), JSON.stringify(config, null, 2) + '\n')
30
- }
31
-
32
- export async function setLlamaCppServerPath(serverPath: string): Promise<void> {
33
- await saveLocalRunnerConfig({ llamaServerPath: serverPath.trim() })
34
- }
@@ -1,176 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
- import { getConfigDir } from '../storage/config.js'
4
- import {
5
- loadLocalRunnerConfig,
6
- setLlamaCppServerPath,
7
- } from './llamacppConfig.js'
8
- import { runCommand } from './llamacppCommands.js'
9
-
10
- export async function detectLlamaCppServerBinary(extraCandidates: string[] = []): Promise<{ path: string | null; version: string | null }> {
11
- const config = await loadLocalRunnerConfig()
12
- const candidates = [
13
- ...llamaCppServerCandidates(process.env, process.platform, config.llamaServerPath),
14
- ...extraCandidates,
15
- ]
16
- for (const candidate of candidates) {
17
- const result = await runCommand(candidate, ['--version'])
18
- if (!result) continue
19
- const output = `${result.stdout}\n${result.stderr}`.trim()
20
- if (result.code === 0 || output.length > 0) {
21
- return { path: candidate, version: firstLine(output) || 'installed' }
22
- }
23
- }
24
- return { path: null, version: null }
25
- }
26
-
27
- export function llamaCppServerCandidates(
28
- env: NodeJS.ProcessEnv = process.env,
29
- platform: NodeJS.Platform = process.platform,
30
- configuredPath?: string,
31
- ): string[] {
32
- const candidates: string[] = []
33
- appendCandidate(candidates, configuredPath)
34
- appendCandidate(candidates, env.LLAMA_SERVER_PATH)
35
- appendCandidate(candidates, env.LLAMACPP_SERVER_PATH)
36
- appendCandidate(candidates, 'llama-server')
37
- appendCandidate(candidates, 'llama-server.exe')
38
-
39
- if (platform === 'win32') {
40
- appendCandidate(candidates, env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'Programs', 'llama.cpp', 'llama-server.exe') : undefined)
41
- appendCandidate(candidates, env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'llama.cpp', 'llama-server.exe') : undefined)
42
- appendCandidate(candidates, env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps', 'llama-server.exe') : undefined)
43
- appendCandidate(candidates, env.ProgramFiles ? path.join(env.ProgramFiles, 'llama.cpp', 'llama-server.exe') : undefined)
44
- appendCandidate(candidates, env['ProgramFiles(x86)'] ? path.join(env['ProgramFiles(x86)'], 'llama.cpp', 'llama-server.exe') : undefined)
45
- appendCandidate(candidates, env.USERPROFILE ? path.join(env.USERPROFILE, 'scoop', 'shims', 'llama-server.exe') : undefined)
46
- appendCandidate(candidates, env.USERPROFILE ? path.join(env.USERPROFILE, 'scoop', 'apps', 'llama.cpp', 'current', 'llama-server.exe') : undefined)
47
- } else if (platform === 'darwin') {
48
- appendCandidate(candidates, '/opt/homebrew/bin/llama-server')
49
- appendCandidate(candidates, '/usr/local/bin/llama-server')
50
- appendCandidate(candidates, '/opt/local/bin/llama-server')
51
- appendCandidate(candidates, env.HOME ? path.join(env.HOME, '.nix-profile', 'bin', 'llama-server') : undefined)
52
- appendCandidate(candidates, env.HOME ? path.join(env.HOME, '.local', 'bin', 'llama-server') : undefined)
53
- } else {
54
- appendCandidate(candidates, '/usr/local/bin/llama-server')
55
- appendCandidate(candidates, '/usr/bin/llama-server')
56
- appendCandidate(candidates, env.HOME ? path.join(env.HOME, '.nix-profile', 'bin', 'llama-server') : undefined)
57
- appendCandidate(candidates, env.HOME ? path.join(env.HOME, '.local', 'bin', 'llama-server') : undefined)
58
- }
59
-
60
- return candidates
61
- }
62
-
63
- export function llamaCppSearchRoots(
64
- env: NodeJS.ProcessEnv = process.env,
65
- platform: NodeJS.Platform = process.platform,
66
- ): string[] {
67
- const roots: string[] = []
68
- if (platform === 'win32') {
69
- appendCandidate(roots, env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'Microsoft', 'WinGet', 'Packages') : undefined)
70
- appendCandidate(roots, env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps') : undefined)
71
- appendCandidate(roots, env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'Programs', 'llama.cpp') : undefined)
72
- appendCandidate(roots, env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'llama.cpp') : undefined)
73
- appendCandidate(roots, env.ProgramFiles ? path.join(env.ProgramFiles, 'llama.cpp') : undefined)
74
- appendCandidate(roots, env.ProgramFiles ? path.join(env.ProgramFiles, 'WindowsApps') : undefined)
75
- appendCandidate(roots, env.USERPROFILE ? path.join(env.USERPROFILE, 'scoop', 'apps', 'llama.cpp') : undefined)
76
- appendCandidate(roots, env.USERPROFILE ? path.join(env.USERPROFILE, 'scoop', 'shims') : undefined)
77
- appendCandidate(roots, path.join(getConfigDir(), 'runners', 'llama.cpp', 'build'))
78
- appendCandidate(roots, path.join(getConfigDir(), 'runners', 'llama.cpp', 'build', 'bin'))
79
- return roots
80
- }
81
-
82
- appendCandidate(roots, '/opt/homebrew/bin')
83
- appendCandidate(roots, '/usr/local/bin')
84
- appendCandidate(roots, '/opt/local/bin')
85
- appendCandidate(roots, '/usr/bin')
86
- appendCandidate(roots, env.HOME ? path.join(env.HOME, '.nix-profile', 'bin') : undefined)
87
- appendCandidate(roots, env.HOME ? path.join(env.HOME, '.local', 'bin') : undefined)
88
- appendCandidate(roots, path.join(getConfigDir(), 'runners', 'llama.cpp', 'build'))
89
- appendCandidate(roots, path.join(getConfigDir(), 'runners', 'llama.cpp', 'build', 'bin'))
90
- return roots
91
- }
92
-
93
- export async function discoverLlamaCppServerPaths(
94
- env: NodeJS.ProcessEnv = process.env,
95
- platform: NodeJS.Platform = process.platform,
96
- ): Promise<string[]> {
97
- return discoverExecutablePaths(platform === 'win32' ? ['llama-server.exe', 'llama-server'] : ['llama-server'], env, platform)
98
- }
99
-
100
- export async function discoverLlamaCppCliPaths(
101
- env: NodeJS.ProcessEnv = process.env,
102
- platform: NodeJS.Platform = process.platform,
103
- ): Promise<string[]> {
104
- return discoverExecutablePaths(platform === 'win32' ? ['llama-cli.exe', 'llama-cli'] : ['llama-cli'], env, platform)
105
- }
106
-
107
- export async function findAndPersistLlamaCppServer(
108
- platform: NodeJS.Platform = process.platform,
109
- ): Promise<{ path: string | null; version: string | null }> {
110
- const direct = await detectLlamaCppServerBinary()
111
- if (direct.path) return direct
112
- const discovered = await discoverLlamaCppServerPaths(process.env, platform)
113
- const found = await detectLlamaCppServerBinary(discovered)
114
- if (found.path) {
115
- await setLlamaCppServerPath(found.path).catch(() => {})
116
- }
117
- return found
118
- }
119
-
120
- async function discoverExecutablePaths(
121
- names: string[],
122
- env: NodeJS.ProcessEnv,
123
- platform: NodeJS.Platform,
124
- ): Promise<string[]> {
125
- const found: string[] = []
126
- const lowered = new Set(names.map(name => name.toLowerCase()))
127
- for (const root of llamaCppSearchRoots(env, platform)) {
128
- await walkForExecutable(root, lowered, found, 0, 5)
129
- if (found.length >= 20) break
130
- }
131
- return found
132
- }
133
-
134
- async function walkForExecutable(
135
- dir: string,
136
- names: Set<string>,
137
- found: string[],
138
- depth: number,
139
- maxDepth: number,
140
- ): Promise<void> {
141
- if (depth > maxDepth || found.length >= 20) return
142
- let entries: Array<import('node:fs').Dirent>
143
- try {
144
- entries = await fs.readdir(dir, { withFileTypes: true })
145
- } catch {
146
- return
147
- }
148
-
149
- for (const entry of entries) {
150
- if (found.length >= 20) return
151
- const fullPath = path.join(dir, entry.name)
152
- const lowerName = entry.name.toLowerCase()
153
- if ((entry.isFile() || entry.isSymbolicLink()) && names.has(lowerName)) {
154
- appendCandidate(found, fullPath)
155
- continue
156
- }
157
- if (entry.isDirectory() && shouldDescendRunnerDir(entry.name, depth)) {
158
- await walkForExecutable(fullPath, names, found, depth + 1, maxDepth)
159
- }
160
- }
161
- }
162
-
163
- function shouldDescendRunnerDir(name: string, depth: number): boolean {
164
- const lower = name.toLowerCase()
165
- if (/(llama|ggml|bin|build|release|debug|current|package|windowsapps|x64|arm64)/.test(lower)) return true
166
- return depth > 0 && lower.length <= 24
167
- }
168
-
169
- function firstLine(text: string): string {
170
- return text.split(/\r?\n/).map(line => line.trim()).find(Boolean) ?? ''
171
- }
172
-
173
- function appendCandidate(candidates: string[], candidate: string | undefined): void {
174
- if (!candidate || candidates.includes(candidate)) return
175
- candidates.push(candidate)
176
- }
@@ -1,65 +0,0 @@
1
- import type { LlamaCppInstallPlan, LlamaCppInstallResult } from './llamacpp.js'
2
-
3
- type RunInstallResult = { ok: true } | { ok: false; message: string; detail?: string }
4
-
5
- export function summarizeInstallOutput(output: string): string | undefined {
6
- const lines = output
7
- .split(/\r?\n/)
8
- .map(cleanInstallLine)
9
- .filter(Boolean)
10
- .filter(line => !/^[\-\\|/_.=\s]+$/.test(line))
11
- .filter(line => !/^\d+(\.\d+)?\s*(B|KB|MB|GB)\s*\/\s*\d+/i.test(line))
12
- const unique = [...new Set(lines)]
13
- return unique.slice(-6).join('\n') || undefined
14
- }
15
-
16
- export function humanInstallError(plan: LlamaCppInstallPlan, code: number | null): string {
17
- if (plan.command === 'winget') return 'Windows could not install the local runner automatically.'
18
- if (plan.command === 'brew') return 'Homebrew could not install the local runner automatically.'
19
- if (plan.command === 'nix') return 'Nix could not install the local runner automatically.'
20
- if (plan.command === 'port') return 'MacPorts could not install the local runner automatically.'
21
- if (plan.command === 'git') return 'ethagent could not download the local runner source.'
22
- if (plan.command === 'cmake') return 'ethagent could not build the local runner.'
23
- return code === null
24
- ? `${plan.label} did not complete.`
25
- : `${plan.label} failed with exit code ${code}.`
26
- }
27
-
28
- export function installFailureDetail(code: number | null, output: string): string | undefined {
29
- const details = [
30
- code === null ? undefined : `exit code ${code}`,
31
- summarizeInstallOutput(output),
32
- ].filter((item): item is string => Boolean(item))
33
- return details.join('\n') || undefined
34
- }
35
-
36
- function cleanInstallLine(line: string): string {
37
- return line
38
- .replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '')
39
- .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '')
40
- .replace(/\s+/g, ' ')
41
- .trim()
42
- }
43
-
44
- export function installerProgressLabel(plan: LlamaCppInstallPlan): string {
45
- if (plan.command === 'winget') return 'installing with Windows package manager...'
46
- if (plan.command === 'brew') return 'installing with Homebrew...'
47
- if (plan.command === 'nix') return 'installing with Nix...'
48
- if (plan.command === 'port') return 'installing with MacPorts...'
49
- return `installing with ${plan.label}...`
50
- }
51
-
52
- export function formatInstallFailure(label: string, result: RunInstallResult): string {
53
- if (result.ok) return label
54
- return [label, result.message, result.detail].filter(Boolean).join(': ')
55
- }
56
-
57
- export function buildFailure(result: RunInstallResult): LlamaCppInstallResult {
58
- return {
59
- ok: false,
60
- code: 'build-failed',
61
- message: 'ethagent could not build the local runner.',
62
- detail: result.ok ? undefined : [result.message, result.detail].filter(Boolean).join('\n'),
63
- recovery: ['runner-path', 'retry-install', 'back'],
64
- }
65
- }
@@ -1,158 +0,0 @@
1
- import {
2
- fetchLlamaCppContextSize,
3
- startLlamaCppServer,
4
- stopLlamaCppServer,
5
- type LlamaCppStartFailureCode,
6
- type LlamaCppStartResult,
7
- } from './llamacpp.js'
8
- import { findLocalHfModel, type LocalHfModel } from './huggingface.js'
9
- import { localProviderBaseUrlFor, type EthagentConfig } from '../storage/config.js'
10
- import { formatModelDisplayName } from './modelDisplay.js'
11
-
12
- export type LlamaCppPreflightResult =
13
- | { ok: true; alreadyRunning: boolean }
14
- | {
15
- ok: false
16
- code: LlamaCppStartFailureCode
17
- message: string
18
- detail?: string
19
- servedModels?: string[]
20
- }
21
-
22
- export type LlamaCppPreflightDeps = {
23
- fetchImpl?: typeof fetch
24
- findLocalModel?: typeof findLocalHfModel
25
- startServer?: typeof startLlamaCppServer
26
- stopServer?: typeof stopLlamaCppServer
27
- timeoutMs?: number
28
- }
29
-
30
- type ModelsProbe =
31
- | { up: true; models: string[] }
32
- | { up: false; models: [] }
33
-
34
- export async function ensureLlamaCppRunnerReady(
35
- config: EthagentConfig,
36
- deps: LlamaCppPreflightDeps = {},
37
- ): Promise<LlamaCppPreflightResult> {
38
- if (config.provider !== 'llamacpp') return { ok: true, alreadyRunning: true }
39
-
40
- const baseUrl = localProviderBaseUrlFor('llamacpp', config.baseUrl)
41
- const local = await (deps.findLocalModel ?? findLocalHfModel)(config.model)
42
- if (!local || local.status !== 'ready') {
43
- return {
44
- ok: false,
45
- code: 'model-file-missing',
46
- message: formatPreflightFailure(
47
- 'local model is not imported',
48
- config.model,
49
- 'choose an imported GGUF model from View Full Catalog or Add Local Model File',
50
- ),
51
- }
52
- }
53
-
54
- const probe = await probeLlamaCppModels(baseUrl, deps)
55
- if (probe.up) {
56
- if (probe.models.length > 0 && !probe.models.includes(config.model)) {
57
- return {
58
- ok: false,
59
- code: 'different-model-running',
60
- message: formatPreflightFailure(
61
- 'local runner is serving a different model',
62
- config.model,
63
- `a different local model is already running (${probe.models.join(', ')}); stop it before switching models`,
64
- ),
65
- servedModels: probe.models,
66
- }
67
- }
68
- if (!local.mmprojPath) {
69
- void fetchLlamaCppContextSize(llamaCppServerHostFromBaseUrl(baseUrl))
70
- return { ok: true, alreadyRunning: true }
71
- }
72
- await (deps.stopServer ?? stopLlamaCppServer)().catch(() => null)
73
- }
74
-
75
- const result = await (deps.startServer ?? startLlamaCppServer)({
76
- modelPath: local.localPath,
77
- modelAlias: local.id,
78
- host: llamaCppServerHostFromBaseUrl(baseUrl),
79
- mmprojPath: local.mmprojPath,
80
- })
81
- if (result.ok) return { ok: true, alreadyRunning: result.alreadyRunning }
82
- return withPreflightMessage(result, local)
83
- }
84
-
85
- export async function probeLlamaCppModels(
86
- baseUrl: string,
87
- deps: Pick<LlamaCppPreflightDeps, 'fetchImpl' | 'timeoutMs'> = {},
88
- ): Promise<ModelsProbe> {
89
- const controller = new AbortController()
90
- const timer = setTimeout(() => controller.abort(), deps.timeoutMs ?? 800)
91
- try {
92
- const response = await (deps.fetchImpl ?? fetch)(llamaCppModelsEndpointForBaseUrl(baseUrl), {
93
- signal: controller.signal,
94
- })
95
- if (!response.ok) return { up: false, models: [] }
96
- const data = await response.json() as { data?: Array<{ id?: unknown }> }
97
- return {
98
- up: true,
99
- models: (data.data ?? [])
100
- .map(item => typeof item.id === 'string' ? item.id : '')
101
- .filter(Boolean),
102
- }
103
- } catch {
104
- return { up: false, models: [] }
105
- } finally {
106
- clearTimeout(timer)
107
- }
108
- }
109
-
110
- export function llamaCppModelsEndpointForBaseUrl(baseUrl: string): string {
111
- const url = new URL(baseUrl)
112
- const path = stripTrailingSlash(url.pathname)
113
- url.pathname = path.endsWith('/v1') ? `${path}/models` : `${path}/v1/models`
114
- url.search = ''
115
- url.hash = ''
116
- return url.toString()
117
- }
118
-
119
- export function llamaCppServerHostFromBaseUrl(baseUrl: string): string {
120
- const url = new URL(baseUrl)
121
- const path = stripTrailingSlash(url.pathname)
122
- url.pathname = path.endsWith('/v1') ? stripTrailingSlash(path.slice(0, -3)) || '/' : path || '/'
123
- url.search = ''
124
- url.hash = ''
125
- return stripTrailingSlash(url.toString())
126
- }
127
-
128
- function withPreflightMessage(
129
- result: Extract<LlamaCppStartResult, { ok: false }>,
130
- local: LocalHfModel,
131
- ): Extract<LlamaCppPreflightResult, { ok: false }> {
132
- return {
133
- ok: false,
134
- code: result.code,
135
- message: formatPreflightFailure(
136
- 'local runner is not reachable',
137
- local.id,
138
- result.message,
139
- local.displayName,
140
- ),
141
- detail: result.detail,
142
- servedModels: result.servedModels,
143
- }
144
- }
145
-
146
- function formatPreflightFailure(
147
- prefix: string,
148
- modelId: string,
149
- reason: string,
150
- displayName?: string,
151
- ): string {
152
- const model = formatModelDisplayName('llamacpp', modelId, { displayName, maxLength: 64 })
153
- return `${prefix}; failed to start ${model}: ${reason}`
154
- }
155
-
156
- function stripTrailingSlash(value: string): string {
157
- return value.replace(/\/+$/, '')
158
- }
@@ -1,180 +0,0 @@
1
- export type ModelDisplayProvider = string
2
-
3
- type ModelDisplayOptions = {
4
- maxLength?: number
5
- displayName?: string
6
- }
7
-
8
- const DEFAULT_MODEL_DISPLAY_MAX = 64
9
- const HF_SEPARATOR = ' / '
10
-
11
- export function formatModelDisplayName(
12
- provider: ModelDisplayProvider,
13
- model: string,
14
- options: ModelDisplayOptions = {},
15
- ): string {
16
- const maxLength = options.maxLength ?? DEFAULT_MODEL_DISPLAY_MAX
17
- if (provider === 'llamacpp') {
18
- return formatLocalHfModelDisplayName(model, {
19
- maxLength,
20
- displayName: options.displayName,
21
- })
22
- }
23
- return truncateMiddle(model, maxLength)
24
- }
25
-
26
- export function formatLocalHfModelDisplayName(
27
- modelId: string,
28
- options: ModelDisplayOptions = {},
29
- ): string {
30
- const maxLength = options.maxLength ?? DEFAULT_MODEL_DISPLAY_MAX
31
- const parsed = parseLocalHfModelId(modelId)
32
- const label = options.displayName?.trim()
33
- if (label) {
34
- const parts = splitLocalHfDisplayName(label)
35
- if (parts) return formatRepoAndFile(parts.repoId, parts.filename, maxLength)
36
- if (parsed) return formatRepoAndFile(parsed.repoId, parsed.filename, maxLength)
37
- return truncateMiddle(label, maxLength)
38
- }
39
-
40
- if (parsed) return formatRepoAndFile(parsed.repoId, parsed.filename, maxLength)
41
- return truncateMiddle(modelId, maxLength)
42
- }
43
-
44
- export function truncateMiddle(value: string, maxLength: number): string {
45
- if (maxLength <= 0) return ''
46
- if (value.length <= maxLength) return value
47
- if (maxLength <= 3) return value.slice(0, maxLength)
48
- const remaining = maxLength - 3
49
- const head = Math.ceil(remaining / 2)
50
- const tail = Math.floor(remaining / 2)
51
- return `${value.slice(0, head)}...${value.slice(value.length - tail)}`
52
- }
53
-
54
- function parseLocalHfModelId(modelId: string): { repoId: string; filename: string } | null {
55
- const hash = modelId.indexOf('#')
56
- if (hash <= 0 || hash === modelId.length - 1) return null
57
- return {
58
- repoId: modelId.slice(0, hash),
59
- filename: modelId.slice(hash + 1),
60
- }
61
- }
62
-
63
- function splitLocalHfDisplayName(label: string): { repoId: string; filename: string } | null {
64
- const separator = label.indexOf(HF_SEPARATOR)
65
- if (separator <= 0 || separator === label.length - HF_SEPARATOR.length) return null
66
- return {
67
- repoId: label.slice(0, separator),
68
- filename: label.slice(separator + HF_SEPARATOR.length),
69
- }
70
- }
71
-
72
- function formatRepoAndFile(repoId: string, filename: string, maxLength: number): string {
73
- const file = friendlyFilename(filename)
74
- const full = `${repoId}${HF_SEPARATOR}${file}`
75
- if (full.length <= maxLength) return full
76
-
77
- const compactFile = compactModelFilename(file, maxLength)
78
- if (maxLength <= 32 || compactFile.length >= maxLength - HF_SEPARATOR.length - 6) {
79
- return truncateEndClean(compactFile, maxLength)
80
- }
81
-
82
- const repoBudget = maxLength - HF_SEPARATOR.length - compactFile.length
83
- const compactRepo = compactRepoId(repoId, repoBudget)
84
- if (compactRepo) {
85
- const compact = `${compactRepo}${HF_SEPARATOR}${compactFile}`
86
- if (compact.length <= maxLength) return compact
87
- }
88
-
89
- const separatorBudget = HF_SEPARATOR.length
90
- const partBudget = maxLength - separatorBudget
91
- if (partBudget <= 8) return truncateMiddle(full, maxLength)
92
-
93
- let repoMax = Math.min(repoId.length, Math.max(8, Math.floor(partBudget * 0.45)))
94
- let fileMax = partBudget - repoMax
95
-
96
- if (fileMax > file.length) {
97
- repoMax = Math.min(repoId.length, repoMax + fileMax - file.length)
98
- fileMax = file.length
99
- }
100
- if (repoMax > repoId.length) {
101
- fileMax = Math.min(file.length, fileMax + repoMax - repoId.length)
102
- repoMax = repoId.length
103
- }
104
- if (fileMax < 8 && partBudget >= 16) {
105
- fileMax = 8
106
- repoMax = partBudget - fileMax
107
- }
108
-
109
- return truncateMiddle(
110
- `${truncateMiddle(repoId, repoMax)}${HF_SEPARATOR}${truncateMiddle(file, fileMax)}`,
111
- maxLength,
112
- )
113
- }
114
-
115
- function friendlyFilename(filename: string): string {
116
- return filename.split('/').pop() ?? filename
117
- }
118
-
119
- function compactRepoId(repoId: string, maxLength: number): string {
120
- if (maxLength <= 0) return ''
121
- if (repoId.length <= maxLength) return repoId
122
- const parts = repoId.split('/').filter(Boolean)
123
- const owner = parts.length > 1 ? parts[0] ?? '' : ''
124
- const repoName = parts.at(-1) ?? repoId
125
- if (owner.length > 0) {
126
- const nameBudget = maxLength - owner.length - 1
127
- if (nameBudget >= 6) {
128
- const compactName = compactModelCore(repoName, nameBudget)
129
- const withOwner = `${owner}/${compactName}`
130
- if (withOwner.length <= maxLength) return withOwner
131
- }
132
- }
133
- return compactModelCore(repoName, maxLength)
134
- }
135
-
136
- function compactModelFilename(filename: string, maxLength: number): string {
137
- const withoutExtension = filename.replace(/\.gguf$/i, '')
138
- const compact = compactModelCore(withoutExtension, maxLength)
139
- if (compact.length <= maxLength) return compact
140
- return truncateEndClean(compact, maxLength)
141
- }
142
-
143
- function compactModelCore(value: string, maxLength: number): string {
144
- if (maxLength <= 0) return ''
145
- const cleaned = value
146
- .replace(/\.gguf$/i, '')
147
- .replace(/[_-]+/g, ' ')
148
- .replace(/(^|[^0-9])\./g, '$1 ')
149
- .replace(/\.(?=[^0-9]|$)/g, ' ')
150
- .replace(/\bgguf\b/gi, '')
151
- .replace(/\s+/g, ' ')
152
- .trim()
153
- if (!cleaned) return truncateEndClean(value, maxLength)
154
-
155
- const tokens = cleaned.split(' ')
156
- const sizeIndex = tokens.findIndex(token => /^\d+(?:\.\d+)?[bm]$/i.test(token))
157
- const familyTokens = sizeIndex > 0 ? tokens.slice(0, Math.min(sizeIndex, 3)) : tokens.slice(0, Math.min(tokens.length, 3))
158
- const size = sizeIndex >= 0 ? tokens[sizeIndex] : undefined
159
- const context = tokens.find(token => /^\d+k$/i.test(token))
160
- const quant = quantizationLabel(value)
161
- const parts = [familyTokens.join(' '), size, context, quant]
162
- .filter((part): part is string => Boolean(part))
163
- .filter((part, index, all) => all.findIndex(other => other.toLowerCase() === part.toLowerCase()) === index)
164
- const compact = parts.join(' ').trim() || cleaned
165
- if (compact.length <= maxLength) return compact
166
- return truncateEndClean(compact, maxLength)
167
- }
168
-
169
- function quantizationLabel(value: string): string | undefined {
170
- const match = value.match(/(?:^|[-_.\s])((?:Q\d(?:_[A-Za-z0-9]+)*)|BF16|F16|FP16)(?:$|[-_.\s])/i)
171
- return match?.[1]?.toUpperCase()
172
- }
173
-
174
- function truncateEndClean(value: string, maxLength: number): string {
175
- if (maxLength <= 0) return ''
176
- if (value.length <= maxLength) return value
177
- if (maxLength <= 3) return value.slice(0, maxLength)
178
- const sliced = value.slice(0, maxLength - 3).replace(/[\s._/-]+$/g, '')
179
- return `${sliced || value.slice(0, maxLength - 3)}...`
180
- }