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.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +35 -0
- package/LICENSE +1 -1
- package/README.md +64 -104
- package/commands/ethagent.md +40 -0
- package/package.json +16 -16
- package/src/app/keybindings/KeybindingProvider.tsx +1 -6
- package/src/app/keybindings/types.ts +1 -6
- package/src/cli/ResetConfirmView.tsx +54 -53
- package/src/cli/demo.ts +86 -0
- package/src/cli/hookIo.ts +45 -0
- package/src/cli/main.tsx +94 -123
- package/src/cli/memoryGuard.ts +49 -0
- package/src/cli/reset.ts +28 -70
- package/src/cli/sessionStart.ts +33 -0
- package/src/cli/status.ts +46 -0
- package/src/cli/sync.ts +167 -0
- package/src/cli/syncAdapters/claude-code.ts +86 -0
- package/src/cli/syncAdapters/codex.ts +66 -0
- package/src/cli/syncAdapters/index.ts +45 -0
- package/src/cli/syncAdapters/managedBlock.ts +175 -0
- package/src/cli/syncAdapters/shared.ts +63 -0
- package/src/identity/continuity/envelopeParse.ts +20 -1
- package/src/identity/continuity/publicSkills.ts +3 -1
- package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
- package/src/identity/continuity/skills/scaffold.ts +5 -2
- package/src/identity/continuity/snapshots.ts +12 -5
- package/src/identity/continuity/storage/defaults.ts +20 -19
- package/src/identity/continuity/storage/status.ts +1 -1
- package/src/identity/ens/ensLookup/constants.ts +1 -1
- package/src/identity/manager/IdentityManager.tsx +33 -0
- package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
- package/src/identity/{hub → manager}/Routes.tsx +48 -34
- package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
- package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
- package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
- package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
- package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
- package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
- package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
- package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
- package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
- package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
- package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
- package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
- package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
- package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
- package/src/identity/{hub → manager}/create/effects.ts +19 -10
- package/src/identity/manager/create/importScan.ts +122 -0
- package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
- package/src/identity/{hub → manager}/custody/actions.ts +1 -15
- package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
- package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
- package/src/identity/{hub → manager}/custody/types.ts +1 -2
- package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
- package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
- package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
- package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
- package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
- package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
- package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
- package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
- package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
- package/src/identity/{hub → manager}/profile/effects.ts +1 -3
- package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
- package/src/identity/{hub → manager}/profile/state.ts +1 -1
- package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
- package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
- package/src/identity/{hub → manager}/restore/apply.ts +1 -1
- package/src/identity/{hub → manager}/restore/auth.ts +1 -1
- package/src/identity/{hub → manager}/restore/discover.ts +1 -1
- package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
- package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
- package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
- package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
- package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
- package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
- package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
- package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
- package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
- package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
- package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
- package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
- package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
- package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
- package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
- package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
- package/src/identity/manager/shared/effects/types.ts +30 -0
- package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
- package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
- package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
- package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
- package/src/identity/{hub → manager}/shared/utils.ts +6 -10
- package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
- package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
- package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
- package/src/identity/{hub → manager}/types.ts +5 -6
- package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
- package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
- package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
- package/src/identity/registry/erc8004/discovery.ts +3 -17
- package/src/identity/registry/erc8004/utils.ts +1 -1
- package/src/identity/storage/ipfs.ts +21 -1
- package/src/identity/wallet/browserWallet/html.ts +10 -2
- package/src/identity/wallet/browserWallet/http.ts +18 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
- package/src/identity/wallet/browserWallet/requests.ts +10 -28
- package/src/identity/wallet/browserWallet/session.ts +26 -33
- package/src/identity/wallet/browserWallet/validation.ts +14 -0
- package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
- package/src/identity/wallet/page/boot.ts +43 -0
- package/src/identity/wallet/page/config.ts +59 -0
- package/src/identity/wallet/page/constants.ts +12 -0
- package/src/identity/wallet/page/copy.ts +47 -68
- package/src/identity/wallet/page/css.ts +638 -0
- package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
- package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
- package/src/identity/wallet/page/markup.ts +44 -34
- package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
- package/src/identity/wallet/page/resize.ts +95 -0
- package/src/identity/wallet/page/state.ts +135 -8
- package/src/identity/wallet/page/timeline.ts +161 -0
- package/src/identity/wallet/page/view.ts +22 -302
- package/src/storage/config.ts +30 -80
- package/src/storage/reset.ts +31 -0
- package/src/storage/secrets.ts +1 -16
- package/src/ui/Select.tsx +27 -5
- package/src/ui/Spinner.tsx +16 -15
- package/src/ui/Surface.tsx +21 -17
- package/src/ui/TextArea.tsx +173 -0
- package/src/ui/TextInput.tsx +31 -133
- package/src/ui/theme.ts +22 -13
- package/src/utils/clipboard.ts +0 -140
- package/src/app/FirstRun.tsx +0 -577
- package/src/app/FirstRunTimeline.tsx +0 -51
- package/src/app/firstRunConfig.ts +0 -26
- package/src/app/hooks/useCancelRequest.ts +0 -22
- package/src/app/hooks/useDoublePress.ts +0 -46
- package/src/app/hooks/useExitOnCtrlC.ts +0 -36
- package/src/auth/openaiOAuth/credentials.ts +0 -47
- package/src/auth/openaiOAuth/crypto.ts +0 -23
- package/src/auth/openaiOAuth/index.ts +0 -238
- package/src/auth/openaiOAuth/landingPage.ts +0 -116
- package/src/auth/openaiOAuth/listener.ts +0 -151
- package/src/auth/openaiOAuth/refresh.ts +0 -70
- package/src/auth/openaiOAuth/shared.ts +0 -115
- package/src/chat/ChatBottomPane.tsx +0 -296
- package/src/chat/ChatScreen.tsx +0 -1685
- package/src/chat/ConversationStack.tsx +0 -56
- package/src/chat/MessageList.tsx +0 -638
- package/src/chat/SessionStatus.tsx +0 -53
- package/src/chat/chatEnvironment.ts +0 -16
- package/src/chat/chatScreenUtils.ts +0 -194
- package/src/chat/chatSessionState.ts +0 -146
- package/src/chat/chatTurnContext.ts +0 -50
- package/src/chat/chatTurnOrchestrator.ts +0 -603
- package/src/chat/chatTurnRows.ts +0 -64
- package/src/chat/commands.ts +0 -494
- package/src/chat/continuityEditReview.ts +0 -42
- package/src/chat/display/DiffView.tsx +0 -193
- package/src/chat/display/SyntaxText.tsx +0 -192
- package/src/chat/display/toolCallDisplay.ts +0 -103
- package/src/chat/display/toolResultDisplay.ts +0 -19
- package/src/chat/input/ChatInput.tsx +0 -625
- package/src/chat/input/chatInputHelpers.ts +0 -62
- package/src/chat/input/chatInputState.ts +0 -247
- package/src/chat/input/chatPaste.ts +0 -49
- package/src/chat/input/imageRefs.ts +0 -30
- package/src/chat/input/inputRendering.tsx +0 -93
- package/src/chat/input/textCursor.ts +0 -212
- package/src/chat/messageMarkdown.ts +0 -220
- package/src/chat/messageRows.ts +0 -43
- package/src/chat/planImplementation.ts +0 -62
- package/src/chat/slashCommandHandlers.ts +0 -122
- package/src/chat/slashCommandViews.ts +0 -120
- package/src/chat/transcript/TranscriptView.tsx +0 -184
- package/src/chat/transcript/transcriptViewport.ts +0 -295
- package/src/chat/views/ContextLimitView.tsx +0 -95
- package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
- package/src/chat/views/CopyPicker.tsx +0 -50
- package/src/chat/views/PermissionPrompt.tsx +0 -156
- package/src/chat/views/PermissionsView.tsx +0 -165
- package/src/chat/views/PlanApprovalView.tsx +0 -91
- package/src/chat/views/ResumeView.tsx +0 -273
- package/src/chat/views/RewindView.tsx +0 -412
- package/src/cli/preview.tsx +0 -14
- package/src/cli/updateNotice.ts +0 -54
- package/src/identity/continuity/privateEdit/apply.ts +0 -170
- package/src/identity/continuity/privateEdit/diff.ts +0 -6
- package/src/identity/continuity/privateEdit/files.ts +0 -23
- package/src/identity/continuity/privateEdit/types.ts +0 -28
- package/src/identity/continuity/privateEdit.ts +0 -46
- package/src/identity/hub/IdentityHub.tsx +0 -14
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
- package/src/identity/hub/ens/effects.ts +0 -218
- package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
- package/src/identity/hub/shared/effects/types.ts +0 -53
- package/src/identity/hub/shared/reconciliation/index.ts +0 -14
- package/src/identity/wallet/page/grainient.ts +0 -278
- package/src/identity/wallet/page/html.ts +0 -28
- package/src/identity/wallet/page/styles/base.ts +0 -259
- package/src/identity/wallet/page/styles/components.ts +0 -262
- package/src/identity/wallet/page/styles/index.ts +0 -5
- package/src/identity/wallet/page/styles/responsive.ts +0 -247
- package/src/identity/wallet/page.tsx +0 -38
- package/src/mcp/approvals.ts +0 -113
- package/src/mcp/config.ts +0 -235
- package/src/mcp/manager.ts +0 -482
- package/src/mcp/managerHelpers.ts +0 -70
- package/src/mcp/names.ts +0 -19
- package/src/mcp/output.ts +0 -96
- package/src/models/ModelPicker.tsx +0 -1009
- package/src/models/catalog.ts +0 -327
- package/src/models/huggingface.ts +0 -712
- package/src/models/huggingfaceStorage.ts +0 -136
- package/src/models/llamacpp.ts +0 -848
- package/src/models/llamacppCommands.ts +0 -44
- package/src/models/llamacppConfig.ts +0 -34
- package/src/models/llamacppDiscovery.ts +0 -176
- package/src/models/llamacppOutput.ts +0 -65
- package/src/models/llamacppPreflight.ts +0 -158
- package/src/models/modelDisplay.ts +0 -180
- package/src/models/modelPickerCatalogFlow.ts +0 -56
- package/src/models/modelPickerCredentials.ts +0 -166
- package/src/models/modelPickerData.ts +0 -41
- package/src/models/modelPickerDisplay.tsx +0 -132
- package/src/models/modelPickerHfFlow.ts +0 -192
- package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
- package/src/models/modelPickerOptions.ts +0 -457
- package/src/models/modelPickerTypes.ts +0 -69
- package/src/models/modelPickerUninstallFlow.ts +0 -48
- package/src/models/modelPickerViewHelpers.ts +0 -174
- package/src/models/modelRecommendation.ts +0 -139
- package/src/models/providerDisplay.ts +0 -16
- package/src/models/runtimeDetection.ts +0 -81
- package/src/models/uncensoredCatalog.ts +0 -86
- package/src/providers/anthropic.ts +0 -290
- package/src/providers/contracts.ts +0 -71
- package/src/providers/errors.ts +0 -80
- package/src/providers/gemini.ts +0 -391
- package/src/providers/openai-chat.ts +0 -474
- package/src/providers/openai-responses-format.ts +0 -177
- package/src/providers/openai-responses.ts +0 -306
- package/src/providers/openaiChatWire.ts +0 -124
- package/src/providers/registry.ts +0 -120
- package/src/providers/retry.ts +0 -58
- package/src/providers/sse.ts +0 -93
- package/src/runtime/compaction.ts +0 -395
- package/src/runtime/cwd.ts +0 -43
- package/src/runtime/providerTurn.ts +0 -38
- package/src/runtime/sessionMode.ts +0 -55
- package/src/runtime/systemPrompt.ts +0 -213
- package/src/runtime/textToolParser.ts +0 -161
- package/src/runtime/toolClaimGuards.ts +0 -143
- package/src/runtime/toolExecution.ts +0 -304
- package/src/runtime/toolIntent.ts +0 -143
- package/src/runtime/turn.ts +0 -369
- package/src/runtime/turnNudges.ts +0 -223
- package/src/runtime/turnTypes.ts +0 -86
- package/src/storage/factoryReset.ts +0 -127
- package/src/storage/history.ts +0 -58
- package/src/storage/permissions.ts +0 -76
- package/src/storage/rewind.ts +0 -266
- package/src/storage/sessionExport.ts +0 -49
- package/src/storage/sessions.ts +0 -495
- package/src/tools/bashSafety.ts +0 -186
- package/src/tools/bashTool.ts +0 -140
- package/src/tools/changeDirectoryTool.ts +0 -213
- package/src/tools/contracts.ts +0 -192
- package/src/tools/deleteFileTool.ts +0 -116
- package/src/tools/editTool.ts +0 -165
- package/src/tools/editUtils.ts +0 -170
- package/src/tools/fileDiff.ts +0 -261
- package/src/tools/listDirectoryTool.ts +0 -55
- package/src/tools/listSkillFilesTool.ts +0 -77
- package/src/tools/listSkillsTool.ts +0 -68
- package/src/tools/mcpResourceTools.ts +0 -95
- package/src/tools/permissionRules.ts +0 -85
- package/src/tools/privateContinuityEditTool.ts +0 -187
- package/src/tools/privateContinuityReadTool.ts +0 -106
- package/src/tools/readSkillTool.ts +0 -107
- package/src/tools/readTool.ts +0 -85
- package/src/tools/registry.ts +0 -103
- package/src/tools/writeFileTool.ts +0 -167
- package/src/ui/BrandSplash.tsx +0 -133
- package/src/ui/terminalTitle.ts +0 -30
- package/src/utils/images.ts +0 -140
- package/src/utils/markdownSegments.ts +0 -51
- package/src/utils/messages.ts +0 -37
- package/src/utils/withRetry.ts +0 -324
- /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
- /package/src/identity/{hub → manager}/custody/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
- /package/src/identity/{hub → manager}/ens/state.ts +0 -0
- /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
- /package/src/identity/{hub → manager}/ens/types.ts +0 -0
- /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
- /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
- /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
- /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
- /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
- /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
- /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
- /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
- /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
- /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
|
-
}
|