ethagent 1.1.2 → 2.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/LICENSE +21 -21
- package/README.md +126 -30
- package/package.json +7 -2
- package/src/app/FirstRun.tsx +190 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +35 -15
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +10 -22
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +4 -2
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -817
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +164 -99
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +20 -8
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +2 -2
- package/src/ui/TextInput.tsx +63 -20
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -371
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
buildLocalModelCatalogOptions,
|
|
52
52
|
buildModelPickerOptions,
|
|
53
53
|
catalogOptionValue,
|
|
54
|
+
cloudProviderDisplayName,
|
|
54
55
|
LOCAL_MODEL_LINK_EXAMPLE,
|
|
55
56
|
LOCAL_MODEL_LINK_HINT,
|
|
56
57
|
MODEL_PICKER_CLOUD_PROVIDERS,
|
|
@@ -153,7 +154,6 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
setState({ kind: 'list', data })
|
|
156
|
-
// If a featured repo was provided (first-run local flow), auto-inspect it
|
|
157
157
|
if (featuredHfRepo) {
|
|
158
158
|
await inspectHfInput({ kind: 'hfInput', data }, featuredHfRepo, setState)
|
|
159
159
|
}
|
|
@@ -168,7 +168,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
168
168
|
if (state.kind === 'loading') {
|
|
169
169
|
return (
|
|
170
170
|
<Surface title={contextFit ? 'Switch to Larger-Context Model' : 'Switch Provider · Model'} subtitle="Loading providers and models.">
|
|
171
|
-
<Spinner label="
|
|
171
|
+
<Spinner label="loading providers..." />
|
|
172
172
|
</Surface>
|
|
173
173
|
)
|
|
174
174
|
}
|
|
@@ -186,7 +186,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
186
186
|
onSubmit={value => void inspectHfInput(state, value, setState)}
|
|
187
187
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
188
188
|
/>
|
|
189
|
-
{state.error ? <Text color=
|
|
189
|
+
{state.error ? <Text color={theme.accentError}>{state.error}</Text> : null}
|
|
190
190
|
</Surface>
|
|
191
191
|
)
|
|
192
192
|
}
|
|
@@ -194,7 +194,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
194
194
|
if (state.kind === 'hfLoading') {
|
|
195
195
|
return (
|
|
196
196
|
<Surface title="Checking Model Link" subtitle={state.input}>
|
|
197
|
-
<Spinner label="
|
|
197
|
+
<Spinner label="reading model page..." />
|
|
198
198
|
</Surface>
|
|
199
199
|
)
|
|
200
200
|
}
|
|
@@ -266,7 +266,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
266
266
|
return (
|
|
267
267
|
<Surface title="Downloading Model" subtitle={state.plan.displayName}>
|
|
268
268
|
<Text color={theme.dim}>{state.progress.status}</Text>
|
|
269
|
-
<ProgressBar progress={progress} suffix={suffix}
|
|
269
|
+
<ProgressBar progress={progress} suffix={suffix} />
|
|
270
270
|
</Surface>
|
|
271
271
|
)
|
|
272
272
|
}
|
|
@@ -441,7 +441,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
441
441
|
return (
|
|
442
442
|
<Surface title="Installing Local Runner" subtitle="This may take a few minutes.">
|
|
443
443
|
<ElapsedSpinner startedAt={state.startedAt} label={state.progress.label} />
|
|
444
|
-
<ProgressBar progress={state.progress.progress}
|
|
444
|
+
<ProgressBar progress={state.progress.progress} />
|
|
445
445
|
</Surface>
|
|
446
446
|
)
|
|
447
447
|
}
|
|
@@ -481,7 +481,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
481
481
|
onCancel={() => setState({ kind: 'localRunnerSetup', data: state.data, model: state.model })}
|
|
482
482
|
/>
|
|
483
483
|
)}
|
|
484
|
-
{state.error ? <Text color=
|
|
484
|
+
{state.error ? <Text color={theme.accentError}>{state.error}</Text> : null}
|
|
485
485
|
</Surface>
|
|
486
486
|
)
|
|
487
487
|
}
|
|
@@ -489,7 +489,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
489
489
|
if (state.kind === 'localRunnerStarting') {
|
|
490
490
|
return (
|
|
491
491
|
<Surface title="Starting Local Model" subtitle={state.model.displayName}>
|
|
492
|
-
<ElapsedSpinner startedAt={state.startedAt} label="
|
|
492
|
+
<ElapsedSpinner startedAt={state.startedAt} label="starting local runner..." />
|
|
493
493
|
</Surface>
|
|
494
494
|
)
|
|
495
495
|
}
|
|
@@ -518,38 +518,41 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
518
518
|
|
|
519
519
|
if (state.kind === 'keyEntry') {
|
|
520
520
|
const { provider, action, submitting, error } = state
|
|
521
|
+
const providerName = cloudProviderDisplayName(provider)
|
|
522
|
+
const actionLabel = action === 'set' ? 'Add' : 'Replace'
|
|
521
523
|
return (
|
|
522
524
|
<Surface
|
|
523
|
-
title={`${
|
|
525
|
+
title={`${actionLabel} ${providerName} API Key`}
|
|
524
526
|
subtitle="Stored in your OS keyring when available; never written to config in plaintext."
|
|
525
527
|
footer="enter save · esc back"
|
|
526
528
|
>
|
|
527
529
|
{submitting ? (
|
|
528
|
-
<Spinner label={`saving ${
|
|
530
|
+
<Spinner label={`saving ${providerName} key...`} />
|
|
529
531
|
) : (
|
|
530
532
|
<TextInput
|
|
531
|
-
label={`${
|
|
533
|
+
label={`${providerName} key`}
|
|
532
534
|
placeholder={providerKeyPlaceholder(provider)}
|
|
533
535
|
isSecret
|
|
534
536
|
onSubmit={(value) => void submitKey(state, value, currentConfig, setState)}
|
|
535
537
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
536
538
|
/>
|
|
537
539
|
)}
|
|
538
|
-
{error ? <Text color=
|
|
540
|
+
{error ? <Text color={theme.accentError}>{error}</Text> : null}
|
|
539
541
|
</Surface>
|
|
540
542
|
)
|
|
541
543
|
}
|
|
542
544
|
|
|
543
545
|
if (state.kind === 'keyManage') {
|
|
544
546
|
const { provider, submitting, error } = state
|
|
547
|
+
const providerName = cloudProviderDisplayName(provider)
|
|
545
548
|
return (
|
|
546
549
|
<Surface
|
|
547
|
-
title={`${
|
|
550
|
+
title={`${providerName} API Key`}
|
|
548
551
|
subtitle="Manage the stored key for this provider."
|
|
549
552
|
footer="enter select · esc back"
|
|
550
553
|
>
|
|
551
554
|
{submitting ? (
|
|
552
|
-
<Spinner label={`removing ${
|
|
555
|
+
<Spinner label={`removing ${providerName} key...`} />
|
|
553
556
|
) : (
|
|
554
557
|
<Select
|
|
555
558
|
options={[
|
|
@@ -571,13 +574,11 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
571
574
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
572
575
|
/>
|
|
573
576
|
)}
|
|
574
|
-
{error ? <Text color=
|
|
577
|
+
{error ? <Text color={theme.accentError}>{error}</Text> : null}
|
|
575
578
|
</Surface>
|
|
576
579
|
)
|
|
577
580
|
}
|
|
578
581
|
|
|
579
|
-
|
|
580
|
-
|
|
581
582
|
if (state.kind === 'catalog') {
|
|
582
583
|
const catalog = state.data.cloudCatalogs[state.provider]
|
|
583
584
|
const options = buildCatalogOptions(state.provider, catalog, currentProvider, currentModel, contextFit)
|
|
@@ -588,7 +589,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
588
589
|
})
|
|
589
590
|
return (
|
|
590
591
|
<Surface
|
|
591
|
-
title={`${
|
|
592
|
+
title={`${cloudProviderDisplayName(state.provider)} Full Catalog`}
|
|
592
593
|
subtitle={contextFit ? contextFitSubtitle(contextFit) : 'All discovered models for this provider'}
|
|
593
594
|
footer="enter select · esc back"
|
|
594
595
|
>
|
|
@@ -609,7 +610,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
609
610
|
if (state.kind === 'localCatalogLoading') {
|
|
610
611
|
return (
|
|
611
612
|
<Surface title="View Full Catalog" subtitle="Loading curated local GGUF files.">
|
|
612
|
-
<Spinner label="
|
|
613
|
+
<Spinner label="reading hugging face files..." />
|
|
613
614
|
</Surface>
|
|
614
615
|
)
|
|
615
616
|
}
|
|
@@ -647,7 +648,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
647
648
|
options={options}
|
|
648
649
|
initialIndex={initialIndex === -1 ? 0 : initialIndex}
|
|
649
650
|
maxVisible={12}
|
|
650
|
-
onSubmit={(value) => handleSubmit(value, state, setState, onPick)}
|
|
651
|
+
onSubmit={(value) => handleSubmit(value, state, setState, onPick, onCancel)}
|
|
651
652
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
652
653
|
/>
|
|
653
654
|
</Surface>
|
|
@@ -668,7 +669,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
668
669
|
options={options}
|
|
669
670
|
initialIndex={initialIndex === -1 ? 0 : initialIndex}
|
|
670
671
|
maxVisible={10}
|
|
671
|
-
onSubmit={(value) => handleSubmit(value, state, setState, onPick)}
|
|
672
|
+
onSubmit={(value) => handleSubmit(value, state, setState, onPick, onCancel)}
|
|
672
673
|
onCancel={onCancel}
|
|
673
674
|
/>
|
|
674
675
|
</Surface>
|
|
@@ -680,8 +681,17 @@ function handleSubmit(
|
|
|
680
681
|
state: Extract<State, { kind: 'list' | 'localCatalog' }>,
|
|
681
682
|
setState: (s: State) => void,
|
|
682
683
|
onPick: (sel: ModelPickerSelection) => void,
|
|
684
|
+
onCancel: () => void,
|
|
683
685
|
): void {
|
|
684
686
|
if (value.startsWith('hdr:')) return
|
|
687
|
+
if (value === 'cancel') {
|
|
688
|
+
onCancel()
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
if (value === 'back' && state.kind === 'localCatalog') {
|
|
692
|
+
setState({ kind: 'list', data: state.data })
|
|
693
|
+
return
|
|
694
|
+
}
|
|
685
695
|
if (value.startsWith('hf:')) {
|
|
686
696
|
const id = value.slice(3)
|
|
687
697
|
if (id === 'download') {
|
|
@@ -1256,7 +1266,7 @@ async function runRunnerSetup(
|
|
|
1256
1266
|
const startedAt = Date.now()
|
|
1257
1267
|
const initialProgress: LlamaCppInstallProgress = {
|
|
1258
1268
|
phase: 'checking',
|
|
1259
|
-
label: '
|
|
1269
|
+
label: 'preparing local runner...',
|
|
1260
1270
|
progress: 0.04,
|
|
1261
1271
|
}
|
|
1262
1272
|
const updateProgress = (progress: LlamaCppInstallProgress): void => {
|
|
@@ -1361,14 +1371,14 @@ function modelMetadataSubtext(size: string, indicators: string[]): string | unde
|
|
|
1361
1371
|
}
|
|
1362
1372
|
|
|
1363
1373
|
function riskColor(risk: string): string {
|
|
1364
|
-
if (risk === 'high') return
|
|
1374
|
+
if (risk === 'high') return theme.accentError
|
|
1365
1375
|
if (risk === 'medium') return theme.dim
|
|
1366
|
-
return theme.
|
|
1376
|
+
return theme.accentPeriwinkle
|
|
1367
1377
|
}
|
|
1368
1378
|
|
|
1369
1379
|
function fitColor(fit: GgufMachineFit): string {
|
|
1370
|
-
if (fit === 'too-large') return
|
|
1371
|
-
if (fit === 'tight') return theme.
|
|
1380
|
+
if (fit === 'too-large') return theme.accentError
|
|
1381
|
+
if (fit === 'tight') return theme.accentPeriwinkle
|
|
1372
1382
|
return theme.dim
|
|
1373
1383
|
}
|
|
1374
1384
|
|
|
@@ -1433,10 +1443,6 @@ function runnerPathPlaceholder(): string {
|
|
|
1433
1443
|
return '/path/to/llama-server'
|
|
1434
1444
|
}
|
|
1435
1445
|
|
|
1436
|
-
function capitalize(value: string): string {
|
|
1437
|
-
return value.charAt(0).toUpperCase() + value.slice(1)
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
1446
|
function isCloudProvider(value: string | undefined): value is CloudProviderId {
|
|
1441
1447
|
return value === 'openai' || value === 'anthropic' || value === 'gemini'
|
|
1442
1448
|
}
|
package/src/models/catalog.ts
CHANGED
|
@@ -236,10 +236,13 @@ async function discoverGeminiModels(
|
|
|
236
236
|
apiKey: string,
|
|
237
237
|
): Promise<ModelCatalogEntry[]> {
|
|
238
238
|
const response = await fetchImpl(
|
|
239
|
-
|
|
239
|
+
'https://generativelanguage.googleapis.com/v1beta/models',
|
|
240
240
|
{
|
|
241
241
|
method: 'GET',
|
|
242
|
-
headers: {
|
|
242
|
+
headers: {
|
|
243
|
+
Accept: 'application/json',
|
|
244
|
+
'x-goog-api-key': apiKey.trim(),
|
|
245
|
+
},
|
|
243
246
|
},
|
|
244
247
|
)
|
|
245
248
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
@@ -182,7 +182,7 @@ export async function uninstallLocalHfModel(
|
|
|
182
182
|
const modelPath = path.resolve(model.localPath)
|
|
183
183
|
const partialPath = path.resolve(`${model.localPath}.partial`)
|
|
184
184
|
if (!isPathInside(cacheRoot, modelPath) || !isPathInside(cacheRoot, partialPath)) {
|
|
185
|
-
throw new Error('
|
|
185
|
+
throw new Error('Refusing to uninstall a local model outside EthAgent model cache')
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
const unlink = deps.unlink ?? ((target: string) => fs.unlink(target))
|
|
@@ -203,10 +203,10 @@ export function parseHuggingFaceRef(input: string): HuggingFaceRef {
|
|
|
203
203
|
const url = new URL(trimmed)
|
|
204
204
|
const host = url.hostname.toLowerCase()
|
|
205
205
|
if (host !== 'huggingface.co' && host !== 'www.huggingface.co') {
|
|
206
|
-
throw new Error('
|
|
206
|
+
throw new Error('Expected a huggingface.co model link')
|
|
207
207
|
}
|
|
208
208
|
const parts = url.pathname.split('/').filter(Boolean)
|
|
209
|
-
if (parts.length < 2) throw new Error('
|
|
209
|
+
if (parts.length < 2) throw new Error('Expected a Hugging Face repo link')
|
|
210
210
|
const repoId = `${decodeURIComponent(parts[0]!)}/${decodeURIComponent(parts[1]!)}`
|
|
211
211
|
const mode = parts[2]
|
|
212
212
|
if (mode === 'blob' || mode === 'resolve' || mode === 'tree') {
|
|
@@ -221,7 +221,7 @@ export function parseHuggingFaceRef(input: string): HuggingFaceRef {
|
|
|
221
221
|
|
|
222
222
|
const withoutPrefix = trimmed.replace(/^hf:\/\//i, '')
|
|
223
223
|
const parts = withoutPrefix.split('/').filter(Boolean)
|
|
224
|
-
if (parts.length < 2) throw new Error('
|
|
224
|
+
if (parts.length < 2) throw new Error('Expected repo id like org/model or a huggingface.co link')
|
|
225
225
|
const repoId = `${parts[0]!}/${parts[1]!}`
|
|
226
226
|
let fileParts = parts.slice(2)
|
|
227
227
|
const mode = fileParts[0]
|
|
@@ -242,7 +242,7 @@ export async function fetchHuggingFaceRepoInfo(
|
|
|
242
242
|
const response = await fetchImpl(url, { headers: { Accept: 'application/json' } })
|
|
243
243
|
if (!response.ok) {
|
|
244
244
|
if (response.status === 401 || response.status === 403) {
|
|
245
|
-
throw new Error('
|
|
245
|
+
throw new Error('Repo is gated or private')
|
|
246
246
|
}
|
|
247
247
|
if (response.status === 404) throw new Error('Hugging Face repo not found')
|
|
248
248
|
throw new Error(`Hugging Face API HTTP ${response.status}`)
|
|
@@ -301,7 +301,7 @@ export async function createHfDownloadPlan(
|
|
|
301
301
|
const repo = await fetchHuggingFaceRepoInfo(ref, deps.fetchImpl)
|
|
302
302
|
const files = ggufFiles(repo)
|
|
303
303
|
if (files.length === 0) {
|
|
304
|
-
throw new Error('
|
|
304
|
+
throw new Error('No compatible local model files found for this link')
|
|
305
305
|
}
|
|
306
306
|
const selected = selectedFilename
|
|
307
307
|
? files.find(file => file.filename === selectedFilename)
|
|
@@ -388,7 +388,7 @@ export async function* downloadHfModel(
|
|
|
388
388
|
fetchImpl: FetchImpl = fetch,
|
|
389
389
|
): AsyncIterable<HfDownloadProgress> {
|
|
390
390
|
if (plan.review.runtime !== 'llama.cpp runnable') {
|
|
391
|
-
throw new Error('
|
|
391
|
+
throw new Error('Selected file is not compatible with local chat')
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
await fs.mkdir(path.dirname(plan.localPath), { recursive: true })
|
|
@@ -411,7 +411,7 @@ export async function* downloadHfModel(
|
|
|
411
411
|
while (true) {
|
|
412
412
|
const { done, value } = await reader.read()
|
|
413
413
|
if (done) break
|
|
414
|
-
if (signal?.aborted) throw new Error('
|
|
414
|
+
if (signal?.aborted) throw new Error('Cancelled')
|
|
415
415
|
const buffer = Buffer.from(value)
|
|
416
416
|
hash.update(buffer)
|
|
417
417
|
await handle.write(buffer)
|
|
@@ -513,7 +513,7 @@ async function unlinkIfPresent(
|
|
|
513
513
|
const code = (err as NodeJS.ErrnoException).code
|
|
514
514
|
if (code === 'ENOENT') return
|
|
515
515
|
if (code === 'EBUSY' || code === 'EPERM' || code === 'EACCES') {
|
|
516
|
-
throw new Error('
|
|
516
|
+
throw new Error('That model file is currently in use. Stop the local runner and try uninstall again.')
|
|
517
517
|
}
|
|
518
518
|
throw err
|
|
519
519
|
}
|
package/src/models/llamacpp.ts
CHANGED
|
@@ -278,12 +278,12 @@ export async function installLlamaCppRunner(
|
|
|
278
278
|
): Promise<LlamaCppInstallResult> {
|
|
279
279
|
const plans = llamaCppInstallPlans(platform)
|
|
280
280
|
const failures: string[] = []
|
|
281
|
-
onProgress?.({ phase: 'checking', label: 'checking local runner installers', progress: 0.08 })
|
|
281
|
+
onProgress?.({ phase: 'checking', label: 'checking local runner installers...', progress: 0.08 })
|
|
282
282
|
for (const plan of plans) {
|
|
283
283
|
onProgress?.({ phase: 'installing', label: installerProgressLabel(plan), progress: 0.34 })
|
|
284
284
|
const result = await runInstallCommand(plan, plan.timeoutMs ?? 10 * 60_000)
|
|
285
285
|
if (result.ok) {
|
|
286
|
-
onProgress?.({ phase: 'finding', label: 'finding llama-server', progress: 0.78 })
|
|
286
|
+
onProgress?.({ phase: 'finding', label: 'finding llama-server...', progress: 0.78 })
|
|
287
287
|
const binary = await findAndPersistLlamaCppServer(platform)
|
|
288
288
|
if (binary.path) return { ok: true, serverPath: binary.path }
|
|
289
289
|
const cliPaths = await discoverLlamaCppCliPaths(process.env, platform)
|
|
@@ -679,11 +679,11 @@ function cleanInstallLine(line: string): string {
|
|
|
679
679
|
}
|
|
680
680
|
|
|
681
681
|
function installerProgressLabel(plan: LlamaCppInstallPlan): string {
|
|
682
|
-
if (plan.command === 'winget') return 'installing with Windows package manager'
|
|
683
|
-
if (plan.command === 'brew') return 'installing with Homebrew'
|
|
684
|
-
if (plan.command === 'nix') return 'installing with Nix'
|
|
685
|
-
if (plan.command === 'port') return 'installing with MacPorts'
|
|
686
|
-
return `installing with ${plan.label}
|
|
682
|
+
if (plan.command === 'winget') return 'installing with Windows package manager...'
|
|
683
|
+
if (plan.command === 'brew') return 'installing with Homebrew...'
|
|
684
|
+
if (plan.command === 'nix') return 'installing with Nix...'
|
|
685
|
+
if (plan.command === 'port') return 'installing with MacPorts...'
|
|
686
|
+
return `installing with ${plan.label}...`
|
|
687
687
|
}
|
|
688
688
|
|
|
689
689
|
function formatInstallFailure(label: string, result: RunInstallResult): string {
|
|
@@ -735,7 +735,7 @@ async function installLlamaCppFromSource(
|
|
|
735
735
|
await ensureConfigDir()
|
|
736
736
|
await fs.mkdir(root, { recursive: true })
|
|
737
737
|
|
|
738
|
-
onProgress?.({ phase: 'checking', label: 'checking build tools', progress: 0.08 })
|
|
738
|
+
onProgress?.({ phase: 'checking', label: 'checking build tools...', progress: 0.08 })
|
|
739
739
|
const hasGit = await runCommand('git', ['--version'])
|
|
740
740
|
if (!hasGit || hasGit.code !== 0) {
|
|
741
741
|
return {
|
|
@@ -757,14 +757,14 @@ async function installLlamaCppFromSource(
|
|
|
757
757
|
|
|
758
758
|
try {
|
|
759
759
|
await fs.access(path.join(repoDir, '.git'))
|
|
760
|
-
onProgress?.({ phase: 'building', label: 'updating local runner source', progress: 0.22 })
|
|
760
|
+
onProgress?.({ phase: 'building', label: 'updating local runner source...', progress: 0.22 })
|
|
761
761
|
const update = await runInstallCommand(
|
|
762
762
|
{ label: 'update llama.cpp source', command: 'git', args: ['-C', repoDir, 'pull', '--ff-only'], timeoutMs: 5 * 60_000 },
|
|
763
763
|
5 * 60_000,
|
|
764
764
|
)
|
|
765
765
|
if (!update.ok) return buildFailure(update)
|
|
766
766
|
} catch {
|
|
767
|
-
onProgress?.({ phase: 'building', label: 'downloading local runner source', progress: 0.22 })
|
|
767
|
+
onProgress?.({ phase: 'building', label: 'downloading local runner source...', progress: 0.22 })
|
|
768
768
|
const clone = await runInstallCommand(
|
|
769
769
|
{ label: 'clone llama.cpp source', command: 'git', args: ['clone', '--depth', '1', 'https://github.com/ggml-org/llama.cpp.git', repoDir], timeoutMs: 10 * 60_000 },
|
|
770
770
|
10 * 60_000,
|
|
@@ -772,14 +772,14 @@ async function installLlamaCppFromSource(
|
|
|
772
772
|
if (!clone.ok) return buildFailure(clone)
|
|
773
773
|
}
|
|
774
774
|
|
|
775
|
-
onProgress?.({ phase: 'building', label: 'configuring local runner', progress: 0.48 })
|
|
775
|
+
onProgress?.({ phase: 'building', label: 'configuring local runner...', progress: 0.48 })
|
|
776
776
|
const configure = await runInstallCommand(
|
|
777
777
|
{ label: 'configure llama.cpp', command: 'cmake', args: ['-S', repoDir, '-B', buildDir, '-DCMAKE_BUILD_TYPE=Release'], timeoutMs: 5 * 60_000 },
|
|
778
778
|
5 * 60_000,
|
|
779
779
|
)
|
|
780
780
|
if (!configure.ok) return buildFailure(configure)
|
|
781
781
|
|
|
782
|
-
onProgress?.({ phase: 'building', label: 'building local runner', progress: 0.68 })
|
|
782
|
+
onProgress?.({ phase: 'building', label: 'building local runner...', progress: 0.68 })
|
|
783
783
|
const build = await runInstallCommand(
|
|
784
784
|
{
|
|
785
785
|
label: 'build llama-server',
|
|
@@ -795,7 +795,7 @@ async function installLlamaCppFromSource(
|
|
|
795
795
|
?? (await discoverLlamaCppServerPaths(process.env, platform))[0]
|
|
796
796
|
if (builtServerPath) {
|
|
797
797
|
await setLlamaCppServerPath(builtServerPath)
|
|
798
|
-
onProgress?.({ phase: 'finding', label: 'local runner ready', progress: 1 })
|
|
798
|
+
onProgress?.({ phase: 'finding', label: 'local runner ready...', progress: 1 })
|
|
799
799
|
return { ok: true, serverPath: builtServerPath }
|
|
800
800
|
}
|
|
801
801
|
|
|
@@ -74,6 +74,18 @@ function formatRepoAndFile(repoId: string, filename: string, maxLength: number):
|
|
|
74
74
|
const full = `${repoId}${HF_SEPARATOR}${file}`
|
|
75
75
|
if (full.length <= maxLength) return full
|
|
76
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
|
+
|
|
77
89
|
const separatorBudget = HF_SEPARATOR.length
|
|
78
90
|
const partBudget = maxLength - separatorBudget
|
|
79
91
|
if (partBudget <= 8) return truncateMiddle(full, maxLength)
|
|
@@ -103,3 +115,66 @@ function formatRepoAndFile(repoId: string, filename: string, maxLength: number):
|
|
|
103
115
|
function friendlyFilename(filename: string): string {
|
|
104
116
|
return filename.split('/').pop() ?? filename
|
|
105
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
|
+
}
|
|
@@ -14,6 +14,14 @@ export const MODEL_PICKER_CLOUD_PROVIDERS: CloudProviderId[] = ['openai', 'anthr
|
|
|
14
14
|
export const LOCAL_MODEL_LINK_HINT = 'Paste a GGUF link'
|
|
15
15
|
export const LOCAL_MODEL_LINK_EXAMPLE = 'e.g. https://huggingface.co/Qwen/Qwen3-8B-GGUF'
|
|
16
16
|
|
|
17
|
+
export function cloudProviderDisplayName(provider: CloudProviderId): string {
|
|
18
|
+
switch (provider) {
|
|
19
|
+
case 'openai': return 'OpenAI'
|
|
20
|
+
case 'anthropic': return 'Anthropic'
|
|
21
|
+
case 'gemini': return 'Gemini'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export type LocalHfPickerModel = {
|
|
18
26
|
id: string
|
|
19
27
|
displayName: string
|
|
@@ -67,10 +75,10 @@ export function buildModelPickerOptions(
|
|
|
67
75
|
|
|
68
76
|
options.push(sectionOption('hdr:cloud', 'Cloud'))
|
|
69
77
|
for (const provider of MODEL_PICKER_CLOUD_PROVIDERS) {
|
|
70
|
-
options.push(groupOption(`hdr:cloud:${provider}`, provider))
|
|
78
|
+
options.push(groupOption(`hdr:cloud:${provider}`, cloudProviderDisplayName(provider)))
|
|
71
79
|
const keySet = data.cloudKeys[provider] === true
|
|
72
80
|
if (!keySet) {
|
|
73
|
-
options.push(utilityOption(`key:set:${provider}`, 'API Key
|
|
81
|
+
options.push(utilityOption(`key:set:${provider}`, 'Add API Key'))
|
|
74
82
|
continue
|
|
75
83
|
}
|
|
76
84
|
|
|
@@ -97,9 +105,12 @@ export function buildModelPickerOptions(
|
|
|
97
105
|
))
|
|
98
106
|
}
|
|
99
107
|
options.push(utilityOption(`catalog:${provider}`, 'Full Catalog'))
|
|
100
|
-
options.push(utilityOption(`key:manage:${provider}`, 'API Key
|
|
108
|
+
options.push(utilityOption(`key:manage:${provider}`, 'Manage API Key'))
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
options.push(sectionOption('hdr:exit', 'Exit'))
|
|
112
|
+
options.push(utilityOption('cancel', 'Close Model Picker', 'Return to chat without changing model'))
|
|
113
|
+
|
|
103
114
|
return options
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -139,6 +150,8 @@ export function buildLocalModelCatalogOptions(
|
|
|
139
150
|
if (data.hfModels.length > 0) {
|
|
140
151
|
options.push(utilityOption('local:uninstall', 'Uninstall Downloaded GGUF'))
|
|
141
152
|
}
|
|
153
|
+
options.push(sectionOption('hdr:navigation', 'Navigation'))
|
|
154
|
+
options.push(utilityOption('back', 'Back To Picker', 'Return to model picker'))
|
|
142
155
|
return options
|
|
143
156
|
}
|
|
144
157
|
|
|
@@ -8,7 +8,6 @@ import type { SpecSnapshot } from './runtimeDetection.js'
|
|
|
8
8
|
|
|
9
9
|
const GB = 1024 * 1024 * 1024
|
|
10
10
|
|
|
11
|
-
/** Featured local model repo for first-run setup and the model picker catalog. */
|
|
12
11
|
export const FEATURED_HF_REPO = 'HauhauCS/Qwen3.5-9B-Uncensored-HauhauCS-Aggressive'
|
|
13
12
|
export const FEATURED_HF_REPO_URL = `https://huggingface.co/${FEATURED_HF_REPO}`
|
|
14
13
|
|
package/src/providers/errors.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProviderId } from '../storage/config.js'
|
|
2
2
|
import { ProviderError } from './contracts.js'
|
|
3
|
+
import { formatGeminiRateLimitMessage } from './gemini.js'
|
|
3
4
|
|
|
4
5
|
type ErrorBody =
|
|
5
6
|
| string
|
|
@@ -16,8 +17,23 @@ export async function providerErrorFromResponse(
|
|
|
16
17
|
provider: ProviderId,
|
|
17
18
|
response: Response,
|
|
18
19
|
): Promise<ProviderError> {
|
|
20
|
+
if (provider === 'gemini' && response.status === 429) {
|
|
21
|
+
const short = await formatGeminiRateLimitMessage(response.clone())
|
|
22
|
+
if (short) return new ProviderError(short, { transient: true })
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
const detail = await readErrorDetail(response)
|
|
20
26
|
|
|
27
|
+
if (
|
|
28
|
+
provider === 'gemini'
|
|
29
|
+
&& response.status === 400
|
|
30
|
+
&& /API[_ ]?key( not valid| not found|_invalid)|invalid api key/i.test(detail)
|
|
31
|
+
) {
|
|
32
|
+
return new ProviderError(
|
|
33
|
+
'gemini: API key rejected — verify your key at https://aistudio.google.com/app/apikey, then run /key gemini to set it again',
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
if (provider !== 'llamacpp') {
|
|
22
38
|
if (response.status === 401 || response.status === 403) {
|
|
23
39
|
return new ProviderError(`auth failed: check your ${provider} key (/doctor to verify)`)
|