ethagent 2.1.1 → 2.3.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/package.json +2 -1
- package/src/app/FirstRun.tsx +1 -7
- package/src/app/FirstRunTimeline.tsx +1 -1
- package/src/auth/openaiOAuth/credentials.ts +47 -0
- package/src/auth/openaiOAuth/crypto.ts +23 -0
- package/src/auth/openaiOAuth/index.ts +238 -0
- package/src/auth/openaiOAuth/landingPage.ts +125 -0
- package/src/auth/openaiOAuth/listener.ts +151 -0
- package/src/auth/openaiOAuth/refresh.ts +70 -0
- package/src/auth/openaiOAuth/shared.ts +115 -0
- package/src/chat/ChatBottomPane.tsx +20 -11
- package/src/chat/ChatScreen.tsx +160 -35
- package/src/chat/ConversationStack.tsx +1 -1
- package/src/chat/MessageList.tsx +185 -72
- package/src/chat/SessionStatus.tsx +3 -1
- package/src/chat/chatScreenUtils.ts +11 -15
- package/src/chat/chatSessionState.ts +3 -2
- package/src/chat/chatTurnOrchestrator.ts +1 -7
- package/src/chat/commands.ts +28 -27
- package/src/chat/display/DiffView.tsx +193 -0
- package/src/chat/display/SyntaxText.tsx +192 -0
- package/src/chat/display/toolCallDisplay.ts +103 -0
- package/src/chat/display/toolResultDisplay.ts +19 -0
- package/src/chat/{ChatInput.tsx → input/ChatInput.tsx} +36 -23
- package/src/chat/{TranscriptView.tsx → transcript/TranscriptView.tsx} +24 -50
- package/src/chat/{transcriptViewport.ts → transcript/transcriptViewport.ts} +12 -30
- package/src/chat/{ContextLimitView.tsx → views/ContextLimitView.tsx} +3 -3
- package/src/chat/{ContinuityEditReviewView.tsx → views/ContinuityEditReviewView.tsx} +11 -3
- package/src/chat/{CopyPicker.tsx → views/CopyPicker.tsx} +4 -5
- package/src/chat/{PermissionPrompt.tsx → views/PermissionPrompt.tsx} +16 -17
- package/src/chat/{PermissionsView.tsx → views/PermissionsView.tsx} +6 -6
- package/src/chat/{PlanApprovalView.tsx → views/PlanApprovalView.tsx} +4 -4
- package/src/chat/{ResumeView.tsx → views/ResumeView.tsx} +35 -35
- package/src/chat/views/RewindView.tsx +410 -0
- package/src/identity/continuity/privateEdit/diff.ts +2 -78
- package/src/identity/ens/agentRecords.ts +5 -19
- package/src/identity/ens/ensAutomation/setup.ts +0 -1
- package/src/identity/ens/ensAutomation/types.ts +0 -1
- package/src/identity/hub/OperationalRoutes.tsx +23 -32
- package/src/identity/hub/Routes.tsx +13 -13
- package/src/identity/hub/{flows/continuity → continuity}/ContinuityDashboardScreen.tsx +9 -9
- package/src/identity/hub/{flows/continuity → continuity}/RebackupStorageScreen.tsx +2 -2
- package/src/identity/hub/{flows/continuity → continuity}/RecoveryConfirmScreen.tsx +5 -5
- package/src/identity/hub/{flows/continuity → continuity}/SavePromptScreen.tsx +5 -5
- package/src/identity/hub/{effects/rebackup/runRebackup.ts → continuity/effects.ts} +19 -19
- package/src/identity/hub/{effects/rebackup → continuity}/index.ts +1 -1
- package/src/identity/hub/{effects/shared → continuity}/snapshot.ts +8 -8
- package/src/identity/hub/{effects/rebackup → continuity}/vault.ts +15 -15
- package/src/identity/hub/{flows/create → create}/CreateFlow.tsx +13 -13
- package/src/identity/hub/{effects/create.ts → create/effects.ts} +4 -4
- package/src/identity/hub/{flows/custody → custody}/CustodyEditFlow.tsx +10 -48
- package/src/identity/hub/{flows/custody/custodyFlowActions.ts → custody/actions.ts} +11 -9
- package/src/identity/hub/{flows/custody/custodyFlowHelpers.ts → custody/helpers.ts} +4 -4
- package/src/identity/hub/{effects/vault → custody}/preflight.ts +5 -5
- package/src/identity/hub/{flows/custody/custodyFlowRoutes.tsx → custody/routes.tsx} +8 -8
- package/src/identity/hub/{flows/custody/custodyEffects.ts → custody/transactions.ts} +9 -9
- package/src/identity/hub/{flows/custody/custodyFlowTypes.ts → custody/types.ts} +6 -6
- package/src/identity/hub/{flows/custody/custodyFlowEffects.ts → custody/useCustodyEffects.ts} +7 -7
- package/src/identity/hub/{flows/custody → custody}/useCustodyFlow.tsx +5 -5
- package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +241 -0
- package/src/identity/hub/{flows/ens → ens}/EnsEditFlow.tsx +27 -82
- package/src/identity/hub/{flows/ens → ens}/EnsEditMaintenanceScreens.tsx +25 -65
- package/src/identity/hub/{flows/ens → ens}/EnsEditReviewScreens.tsx +12 -30
- package/src/identity/hub/ens/EnsEditRunners.tsx +62 -0
- package/src/identity/hub/{flows/ens → ens}/EnsEditShared.tsx +15 -14
- package/src/identity/hub/{flows/ens → ens}/EnsEditSimpleScreens.tsx +68 -217
- package/src/identity/hub/{flows/ens/IdentityHubEnsFlow.tsx → ens/EnsFlow.tsx} +18 -11
- package/src/identity/hub/{flows/ens/OperatorWalletsScreen.tsx → ens/EnsOperatorWalletsScreen.tsx} +17 -48
- package/src/identity/hub/{advancedEnsValidation.ts → ens/advancedEnsValidation.ts} +2 -2
- package/src/identity/hub/{flows/ens/ensEditCopy.ts → ens/editCopy.ts} +4 -4
- package/src/identity/hub/{effects/ens/flows.ts → ens/effects.ts} +7 -7
- package/src/identity/hub/{effects/ens → ens}/index.ts +1 -1
- package/src/identity/hub/{model/ens.ts → ens/state.ts} +1 -1
- package/src/identity/hub/{effects/ens → ens}/transactions.ts +232 -232
- package/src/identity/hub/{flows/ens/ensEditTypes.ts → ens/types.ts} +12 -26
- package/src/identity/hub/identityHubReducer.ts +3 -3
- package/src/identity/hub/{flows/profile → profile}/EditProfileFlow.tsx +17 -10
- package/src/identity/hub/{effects/publicProfile/runPublicProfileSave.ts → profile/effects.ts} +55 -177
- package/src/identity/hub/{model → profile}/identity.ts +3 -3
- package/src/identity/hub/{effects/profile/profileState.ts → profile/state.ts} +181 -173
- package/src/identity/hub/{flows/restore → restore}/RestoreFlow.tsx +21 -21
- package/src/identity/hub/{effects/restore → restore}/apply.ts +10 -10
- package/src/identity/hub/{effects/restore → restore}/auth.ts +7 -7
- package/src/identity/hub/{effects/restore → restore}/discover.ts +6 -6
- package/src/identity/hub/{effects/restore → restore}/envelopes.ts +2 -2
- package/src/identity/hub/{effects/restore → restore}/fetch.ts +3 -3
- package/src/identity/hub/{effects/restore/shared.ts → restore/helpers.ts} +6 -6
- package/src/identity/hub/{effects/restore → restore}/recovery.ts +10 -10
- package/src/identity/hub/{effects/restore → restore}/resolve.ts +4 -4
- package/src/identity/hub/restore/restoreAdmin.ts +34 -0
- package/src/identity/hub/{flows/restore/useRestoreFlowEffects.ts → restore/useRestoreEffects.ts} +5 -5
- package/src/identity/hub/{flows/settings → settings}/StorageCredentialScreen.tsx +5 -5
- package/src/identity/hub/{components → shared/components}/BusyScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/DetailsScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/ErrorScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/FlowTimeline.tsx +1 -1
- package/src/identity/hub/{components → shared/components}/IdentitySummary.tsx +16 -11
- package/src/identity/hub/{components → shared/components}/MenuScreen.tsx +8 -9
- package/src/identity/hub/{components → shared/components}/NetworkScreen.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/PinataJwtInput.tsx +4 -4
- package/src/identity/hub/{components → shared/components}/UnlinkedIdentityScreen.tsx +5 -5
- package/src/identity/hub/{components → shared/components}/WalletApprovalScreen.tsx +6 -6
- package/src/identity/hub/{components → shared/components}/menuFlagsFromReconciliation.ts +2 -4
- package/src/identity/hub/{effects/shared → shared/effects}/profilePrep.ts +1 -1
- package/src/identity/hub/{effects → shared/effects}/receipts.ts +2 -2
- package/src/identity/hub/{effects/shared → shared/effects}/sync.ts +6 -47
- package/src/identity/hub/{effects → shared/effects}/types.ts +3 -3
- package/src/identity/hub/{model → shared/model}/copy.ts +2 -2
- package/src/identity/hub/{model → shared/model}/errors.ts +5 -5
- package/src/identity/hub/{model → shared/model}/network.ts +3 -3
- package/src/identity/hub/{operatorWallets.ts → shared/operatorWallets.ts} +1 -1
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/hook.ts +1 -2
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/ownership.ts +2 -2
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/run.ts +7 -40
- package/src/identity/hub/{reconciliation → shared/reconciliation}/agentReconciliation/types.ts +0 -4
- package/src/identity/hub/{reconciliation → shared/reconciliation}/index.ts +0 -7
- package/src/identity/hub/shared/reconciliation/walletSetup.ts +27 -0
- package/src/identity/hub/{utils.ts → shared/utils.ts} +5 -5
- package/src/identity/hub/{flows/token-transfer/IdentityHubTokenTransferFlow.tsx → transfer/TokenTransferFlow.tsx} +8 -8
- package/src/identity/hub/{flows/token-transfer → transfer}/TokenTransferScreens.tsx +14 -14
- package/src/identity/hub/{effects/token-transfer/runTokenTransfer.ts → transfer/effects.ts} +16 -16
- package/src/identity/hub/{effects/token-transfer → transfer}/progress.ts +1 -1
- package/src/identity/hub/useIdentityHubController.ts +11 -11
- package/src/identity/hub/useIdentityHubSideEffects.ts +11 -11
- package/src/identity/wallet/browserWallet/types.ts +0 -5
- package/src/identity/wallet/page/copy.ts +1 -31
- package/src/identity/wallet/walletPurposeCompat.ts +0 -2
- package/src/models/ModelPicker.tsx +248 -8
- package/src/models/catalog.ts +29 -1
- package/src/models/modelPickerOptions.ts +12 -10
- package/src/models/providerDisplay.ts +16 -0
- package/src/providers/errors.ts +6 -4
- package/src/providers/openai-chat.ts +2 -1
- package/src/providers/openai-responses-format.ts +156 -0
- package/src/providers/openai-responses.ts +276 -0
- package/src/providers/registry.ts +85 -8
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +4 -2
- package/src/runtime/toolExecution.ts +9 -6
- package/src/runtime/turn.ts +29 -1
- package/src/storage/rewind.ts +20 -0
- package/src/storage/secrets.ts +4 -1
- package/src/storage/sessions.ts +2 -1
- package/src/tools/bashSafety.ts +7 -3
- package/src/tools/bashTool.ts +1 -1
- package/src/tools/contracts.ts +3 -0
- package/src/tools/deleteFileTool.ts +8 -3
- package/src/tools/editTool.ts +10 -5
- package/src/tools/fileDiff.ts +261 -0
- package/src/tools/privateContinuityEditTool.ts +11 -1
- package/src/tools/writeFileTool.ts +8 -3
- package/src/ui/Spinner.tsx +25 -3
- package/src/ui/TextInput.tsx +2 -2
- package/src/ui/theme.ts +17 -0
- package/src/utils/clipboard.ts +10 -7
- package/src/utils/openExternal.ts +20 -10
- package/src/chat/RewindView.tsx +0 -386
- package/src/chat/toolResultDisplay.ts +0 -8
- package/src/identity/ens/ensRegistration.ts +0 -199
- package/src/identity/hub/effects/index.ts +0 -74
- package/src/identity/hub/effects/publicProfile/index.ts +0 -5
- package/src/identity/hub/effects/restore/restoreEffects.ts +0 -22
- package/src/identity/hub/effects/restoreAdmin.ts +0 -93
- package/src/identity/hub/effects/token-transfer/index.ts +0 -6
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +0 -336
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +0 -198
- package/src/identity/hub/reconciliation/walletSetup.ts +0 -220
- /package/src/chat/{chatInputState.ts → input/chatInputState.ts} +0 -0
- /package/src/chat/{chatPaste.ts → input/chatPaste.ts} +0 -0
- /package/src/chat/{textCursor.ts → input/textCursor.ts} +0 -0
- /package/src/identity/hub/{model/continuity.ts → continuity/state.ts} +0 -0
- /package/src/identity/hub/{model/custody.ts → custody/state.ts} +0 -0
- /package/src/identity/hub/{effects/restore → restore}/index.ts +0 -0
- /package/src/identity/hub/{model → shared/model}/format.ts +0 -0
- /package/src/identity/hub/{reconciliation → shared/reconciliation}/useAgentReconciliation.ts +0 -0
- /package/src/identity/hub/{txGuard.ts → shared/txGuard.ts} +0 -0
- /package/src/identity/hub/{model/transfer.ts → transfer/state.ts} +0 -0
|
@@ -25,8 +25,11 @@ import {
|
|
|
25
25
|
type GgufMachineFit,
|
|
26
26
|
} from './modelRecommendation.js'
|
|
27
27
|
import { hasKey, rmKey, setKey } from '../storage/secrets.js'
|
|
28
|
+
import { OpenAIOAuthService } from '../auth/openaiOAuth/index.js'
|
|
29
|
+
import { hasOpenAIOAuthCredentials, rmOpenAIOAuthCredentials } from '../auth/openaiOAuth/credentials.js'
|
|
30
|
+
import { openExternalUrl } from '../utils/openExternal.js'
|
|
28
31
|
import { defaultModelFor, type EthagentConfig, type ProviderId } from '../storage/config.js'
|
|
29
|
-
import { clearModelCatalogCache, discoverProviderModels, type ModelCatalogResult } from './catalog.js'
|
|
32
|
+
import { clearModelCatalogCache, discoverProviderModels, isOpenAIOAuthAllowedModel, OPENAI_OAUTH_DEFAULT_MODEL, type ModelCatalogResult } from './catalog.js'
|
|
30
33
|
import { contextWindowInfo } from '../runtime/compaction.js'
|
|
31
34
|
import {
|
|
32
35
|
createHfDownloadPlan,
|
|
@@ -56,6 +59,7 @@ import {
|
|
|
56
59
|
LOCAL_MODEL_LINK_HINT,
|
|
57
60
|
MODEL_PICKER_CLOUD_PROVIDERS,
|
|
58
61
|
orderModelsForContextFit,
|
|
62
|
+
type CloudCredentialKind,
|
|
59
63
|
type CloudProviderId,
|
|
60
64
|
type ModelPickerContextFit,
|
|
61
65
|
type ModelPickerOptionsData,
|
|
@@ -89,6 +93,8 @@ type State =
|
|
|
89
93
|
| { kind: 'catalog'; provider: CloudProviderId; data: LoadedData }
|
|
90
94
|
| { kind: 'keyEntry'; provider: CloudProviderId; action: 'set' | 'edit'; data: LoadedData; submitting: boolean; error?: string }
|
|
91
95
|
| { kind: 'keyManage'; provider: CloudProviderId; data: LoadedData; submitting: boolean; error?: string }
|
|
96
|
+
| { kind: 'oauthManage'; data: LoadedData; submitting: boolean; error?: string }
|
|
97
|
+
| { kind: 'oauthLogin'; data: LoadedData; phase: 'waiting' | 'exchanging' | 'error'; url?: string; message?: string }
|
|
92
98
|
| { kind: 'hfInput'; data: LoadedData; error?: string }
|
|
93
99
|
| { kind: 'hfLoading'; data: LoadedData; input: string }
|
|
94
100
|
| { kind: 'hfFilePick'; data: LoadedData; input: string; repo: HuggingFaceRepoInfo; files: HuggingFaceSibling[] }
|
|
@@ -119,18 +125,29 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
119
125
|
}) => {
|
|
120
126
|
const [state, setState] = useState<State>({ kind: 'loading' })
|
|
121
127
|
const hfAbortRef = useRef<AbortController | null>(null)
|
|
128
|
+
const oauthServiceRef = useRef<OpenAIOAuthService | null>(null)
|
|
122
129
|
|
|
123
130
|
useEffect(() => {
|
|
124
131
|
let cancelled = false
|
|
125
132
|
void (async () => {
|
|
126
|
-
const [llamaCpp, hfModels, machineSpec, keyEntries] = await Promise.all([
|
|
133
|
+
const [llamaCpp, hfModels, machineSpec, keyEntries, openaiOauth] = await Promise.all([
|
|
127
134
|
probeLlamaCpp(),
|
|
128
135
|
loadHfPickerModels(),
|
|
129
136
|
detectSpec(),
|
|
130
137
|
Promise.all(MODEL_PICKER_CLOUD_PROVIDERS.map(async p => [p, await hasKey(p)] as const)),
|
|
138
|
+
hasOpenAIOAuthCredentials(),
|
|
131
139
|
])
|
|
132
140
|
if (cancelled) return
|
|
133
|
-
const
|
|
141
|
+
const rawCloudKeys = Object.fromEntries(keyEntries) as Partial<Record<ProviderId, boolean>>
|
|
142
|
+
const cloudKeys: Partial<Record<ProviderId, boolean>> = {
|
|
143
|
+
...rawCloudKeys,
|
|
144
|
+
openai: rawCloudKeys.openai === true || openaiOauth,
|
|
145
|
+
}
|
|
146
|
+
const cloudCredentialKinds: Partial<Record<ProviderId, CloudCredentialKind>> = {}
|
|
147
|
+
if (openaiOauth) cloudCredentialKinds.openai = 'oauth'
|
|
148
|
+
else if (rawCloudKeys.openai === true) cloudCredentialKinds.openai = 'apikey'
|
|
149
|
+
if (rawCloudKeys.anthropic === true) cloudCredentialKinds.anthropic = 'apikey'
|
|
150
|
+
if (rawCloudKeys.gemini === true) cloudCredentialKinds.gemini = 'apikey'
|
|
134
151
|
const catalogEntries = await Promise.all(
|
|
135
152
|
MODEL_PICKER_CLOUD_PROVIDERS
|
|
136
153
|
.filter(provider => cloudKeys[provider])
|
|
@@ -144,6 +161,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
144
161
|
machineSpec,
|
|
145
162
|
cloudKeys,
|
|
146
163
|
cloudCatalogs,
|
|
164
|
+
cloudCredentialKinds,
|
|
147
165
|
}
|
|
148
166
|
if (featuredHfRepo) {
|
|
149
167
|
const installedFeatured = await findInstalledHfModelForInput(featuredHfRepo)
|
|
@@ -163,6 +181,8 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
163
181
|
|
|
164
182
|
useEffect(() => () => {
|
|
165
183
|
hfAbortRef.current?.abort()
|
|
184
|
+
oauthServiceRef.current?.cleanup()
|
|
185
|
+
oauthServiceRef.current = null
|
|
166
186
|
}, [])
|
|
167
187
|
|
|
168
188
|
if (state.kind === 'loading') {
|
|
@@ -569,7 +589,45 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
569
589
|
setState({ kind: 'list', data: state.data })
|
|
570
590
|
return
|
|
571
591
|
}
|
|
572
|
-
void deleteKey(state, currentConfig, setState)
|
|
592
|
+
void deleteKey(state, currentConfig, setState, onPick, currentProvider)
|
|
593
|
+
}}
|
|
594
|
+
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
595
|
+
/>
|
|
596
|
+
)}
|
|
597
|
+
{error ? <Text color={theme.accentError}>{error}</Text> : null}
|
|
598
|
+
</Surface>
|
|
599
|
+
)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (state.kind === 'oauthManage') {
|
|
603
|
+
const { submitting, error } = state
|
|
604
|
+
return (
|
|
605
|
+
<Surface
|
|
606
|
+
title="ChatGPT Sign-in"
|
|
607
|
+
subtitle="Manage your ChatGPT sign-in."
|
|
608
|
+
footer="enter select · esc back"
|
|
609
|
+
>
|
|
610
|
+
{submitting ? (
|
|
611
|
+
<Spinner label="signing out..." />
|
|
612
|
+
) : (
|
|
613
|
+
<Select
|
|
614
|
+
options={[
|
|
615
|
+
{ value: 'hdr:account', label: 'Account', disabled: true, role: 'section', bold: true },
|
|
616
|
+
{ value: 'signin', label: 'Sign in Again', indent: 2 },
|
|
617
|
+
{ value: 'signout', label: 'Sign Out', indent: 2 },
|
|
618
|
+
{ value: 'hdr:nav', label: 'Navigation', disabled: true, role: 'section', bold: true },
|
|
619
|
+
{ value: 'cancel', label: 'Back', indent: 2 },
|
|
620
|
+
]}
|
|
621
|
+
onSubmit={(value) => {
|
|
622
|
+
if (value === 'signin') {
|
|
623
|
+
void startOpenAIOAuthFlow(state.data, currentConfig, setState, oauthServiceRef, onPick)
|
|
624
|
+
return
|
|
625
|
+
}
|
|
626
|
+
if (value === 'cancel') {
|
|
627
|
+
setState({ kind: 'list', data: state.data })
|
|
628
|
+
return
|
|
629
|
+
}
|
|
630
|
+
void signOutOAuth(state, currentConfig, setState, onPick, currentProvider)
|
|
573
631
|
}}
|
|
574
632
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
575
633
|
/>
|
|
@@ -579,6 +637,70 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
579
637
|
)
|
|
580
638
|
}
|
|
581
639
|
|
|
640
|
+
if (state.kind === 'oauthLogin') {
|
|
641
|
+
if (state.phase === 'error') {
|
|
642
|
+
return (
|
|
643
|
+
<Surface
|
|
644
|
+
title="OpenAI Sign-in Failed"
|
|
645
|
+
subtitle={state.message ?? 'Sign-in did not complete.'}
|
|
646
|
+
tone="error"
|
|
647
|
+
footer="enter select · esc back"
|
|
648
|
+
>
|
|
649
|
+
<Select<'retry' | 'apikey' | 'back'>
|
|
650
|
+
options={[
|
|
651
|
+
{ value: 'retry', label: 'Try Again' },
|
|
652
|
+
{ value: 'apikey', label: 'Add API Key Instead' },
|
|
653
|
+
{ value: 'back', label: 'Back To Picker' },
|
|
654
|
+
]}
|
|
655
|
+
onSubmit={choice => {
|
|
656
|
+
if (choice === 'retry') void startOpenAIOAuthFlow(state.data, currentConfig, setState, oauthServiceRef, onPick)
|
|
657
|
+
else if (choice === 'apikey') setState({ kind: 'keyEntry', provider: 'openai', action: 'set', data: state.data, submitting: false })
|
|
658
|
+
else setState({ kind: 'list', data: state.data })
|
|
659
|
+
}}
|
|
660
|
+
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
661
|
+
/>
|
|
662
|
+
</Surface>
|
|
663
|
+
)
|
|
664
|
+
}
|
|
665
|
+
if (state.phase === 'exchanging') {
|
|
666
|
+
return (
|
|
667
|
+
<Surface title="Finishing OpenAI Sign-in" subtitle="Exchanging credentials with auth.openai.com.">
|
|
668
|
+
<Spinner label="completing sign-in..." />
|
|
669
|
+
</Surface>
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
return (
|
|
673
|
+
<Surface
|
|
674
|
+
title="Sign in with ChatGPT"
|
|
675
|
+
subtitle="Opened your browser to auth.openai.com. Approve to continue."
|
|
676
|
+
footer="esc cancel"
|
|
677
|
+
>
|
|
678
|
+
<Spinner label="waiting for browser sign-in..." />
|
|
679
|
+
{state.url ? (
|
|
680
|
+
<Box flexDirection="column" marginTop={1}>
|
|
681
|
+
<Text color={theme.dim}>If the browser did not open, visit:</Text>
|
|
682
|
+
<Text color={theme.dim}>{state.url}</Text>
|
|
683
|
+
</Box>
|
|
684
|
+
) : null}
|
|
685
|
+
<Box marginTop={1}>
|
|
686
|
+
<Select<'cancel'>
|
|
687
|
+
options={[{ value: 'cancel', label: 'Cancel Sign-in' }]}
|
|
688
|
+
onSubmit={() => {
|
|
689
|
+
oauthServiceRef.current?.cleanup()
|
|
690
|
+
oauthServiceRef.current = null
|
|
691
|
+
setState({ kind: 'list', data: state.data })
|
|
692
|
+
}}
|
|
693
|
+
onCancel={() => {
|
|
694
|
+
oauthServiceRef.current?.cleanup()
|
|
695
|
+
oauthServiceRef.current = null
|
|
696
|
+
setState({ kind: 'list', data: state.data })
|
|
697
|
+
}}
|
|
698
|
+
/>
|
|
699
|
+
</Box>
|
|
700
|
+
</Surface>
|
|
701
|
+
)
|
|
702
|
+
}
|
|
703
|
+
|
|
582
704
|
if (state.kind === 'catalog') {
|
|
583
705
|
const catalog = state.data.cloudCatalogs[state.provider]
|
|
584
706
|
const options = buildCatalogOptions(state.provider, catalog, currentProvider, currentModel, contextFit)
|
|
@@ -648,7 +770,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
648
770
|
options={options}
|
|
649
771
|
initialIndex={initialIndex === -1 ? 0 : initialIndex}
|
|
650
772
|
maxVisible={12}
|
|
651
|
-
onSubmit={(value) => handleSubmit(value, state, setState, onPick, onCancel)}
|
|
773
|
+
onSubmit={(value) => handleSubmit(value, state, setState, onPick, onCancel, currentConfig, oauthServiceRef)}
|
|
652
774
|
onCancel={() => setState({ kind: 'list', data: state.data })}
|
|
653
775
|
/>
|
|
654
776
|
</Surface>
|
|
@@ -669,7 +791,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({
|
|
|
669
791
|
options={options}
|
|
670
792
|
initialIndex={initialIndex === -1 ? 0 : initialIndex}
|
|
671
793
|
maxVisible={10}
|
|
672
|
-
onSubmit={(value) => handleSubmit(value, state, setState, onPick, onCancel)}
|
|
794
|
+
onSubmit={(value) => handleSubmit(value, state, setState, onPick, onCancel, currentConfig, oauthServiceRef)}
|
|
673
795
|
onCancel={onCancel}
|
|
674
796
|
/>
|
|
675
797
|
</Surface>
|
|
@@ -682,6 +804,8 @@ function handleSubmit(
|
|
|
682
804
|
setState: (s: State) => void,
|
|
683
805
|
onPick: (sel: ModelPickerSelection) => void,
|
|
684
806
|
onCancel: () => void,
|
|
807
|
+
currentConfig: EthagentConfig,
|
|
808
|
+
oauthServiceRef: React.MutableRefObject<OpenAIOAuthService | null>,
|
|
685
809
|
): void {
|
|
686
810
|
if (value.startsWith('hdr:')) return
|
|
687
811
|
if (value === 'cancel') {
|
|
@@ -727,12 +851,20 @@ function handleSubmit(
|
|
|
727
851
|
const parsed = parseKeyValue(value)
|
|
728
852
|
if (!parsed) return
|
|
729
853
|
if (parsed.action === 'manage') {
|
|
854
|
+
if (parsed.provider === 'openai' && state.data.cloudCredentialKinds?.openai === 'oauth') {
|
|
855
|
+
setState({ kind: 'oauthManage', data: state.data, submitting: false })
|
|
856
|
+
return
|
|
857
|
+
}
|
|
730
858
|
setState({ kind: 'keyManage', provider: parsed.provider, data: state.data, submitting: false })
|
|
731
859
|
return
|
|
732
860
|
}
|
|
733
861
|
setState({ kind: 'keyEntry', provider: parsed.provider, action: parsed.action, data: state.data, submitting: false })
|
|
734
862
|
return
|
|
735
863
|
}
|
|
864
|
+
if (value === 'oauth:openai') {
|
|
865
|
+
void startOpenAIOAuthFlow(state.data, currentConfig, setState, oauthServiceRef, onPick)
|
|
866
|
+
return
|
|
867
|
+
}
|
|
736
868
|
if (value.startsWith('catalog:')) {
|
|
737
869
|
const provider = value.slice('catalog:'.length)
|
|
738
870
|
if (isCloudProvider(provider)) setState({ kind: 'catalog', provider, data: state.data })
|
|
@@ -908,36 +1040,144 @@ async function submitKey(
|
|
|
908
1040
|
}
|
|
909
1041
|
}
|
|
910
1042
|
|
|
1043
|
+
async function startOpenAIOAuthFlow(
|
|
1044
|
+
data: LoadedData,
|
|
1045
|
+
currentConfig: EthagentConfig,
|
|
1046
|
+
setState: (s: State) => void,
|
|
1047
|
+
serviceRef: React.MutableRefObject<OpenAIOAuthService | null>,
|
|
1048
|
+
onPick: (sel: ModelPickerSelection) => void,
|
|
1049
|
+
): Promise<void> {
|
|
1050
|
+
serviceRef.current?.cleanup()
|
|
1051
|
+
const service = new OpenAIOAuthService()
|
|
1052
|
+
serviceRef.current = service
|
|
1053
|
+
setState({ kind: 'oauthLogin', data, phase: 'waiting' })
|
|
1054
|
+
try {
|
|
1055
|
+
const result = await service.start(authUrl => {
|
|
1056
|
+
openExternalUrl(authUrl)
|
|
1057
|
+
setState({ kind: 'oauthLogin', data, phase: 'waiting', url: authUrl })
|
|
1058
|
+
})
|
|
1059
|
+
if (serviceRef.current !== service) return
|
|
1060
|
+
setState({ kind: 'oauthLogin', data, phase: 'exchanging' })
|
|
1061
|
+
if (result.kind === 'apikey') {
|
|
1062
|
+
if (typeof result.apiKey !== 'string' || result.apiKey.length === 0) {
|
|
1063
|
+
throw new Error(`OAuth result was apikey kind but apiKey is ${typeof result.apiKey}; refusing to store.`)
|
|
1064
|
+
}
|
|
1065
|
+
try {
|
|
1066
|
+
await setKey('openai', result.apiKey)
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
throw new Error(`Storing the OpenAI API key failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
let refreshed: LoadedData
|
|
1072
|
+
try {
|
|
1073
|
+
refreshed = await refreshProviderKeyState(data, currentConfig, 'openai')
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
throw new Error(`Refreshing the OpenAI provider state failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
1076
|
+
}
|
|
1077
|
+
if (serviceRef.current !== service) return
|
|
1078
|
+
serviceRef.current = null
|
|
1079
|
+
if (result.kind === 'oauth-only' && !isOpenAIOAuthAllowedModel(currentConfig.model)) {
|
|
1080
|
+
onPick({ kind: 'cloud', provider: 'openai', model: OPENAI_OAUTH_DEFAULT_MODEL, keyJustSet: true })
|
|
1081
|
+
return
|
|
1082
|
+
}
|
|
1083
|
+
setState({ kind: 'list', data: refreshed })
|
|
1084
|
+
} catch (err: unknown) {
|
|
1085
|
+
if (serviceRef.current !== service) return
|
|
1086
|
+
serviceRef.current = null
|
|
1087
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
1088
|
+
if (message === 'OpenAI sign-in was cancelled.') {
|
|
1089
|
+
setState({ kind: 'list', data })
|
|
1090
|
+
return
|
|
1091
|
+
}
|
|
1092
|
+
setState({ kind: 'oauthLogin', data, phase: 'error', message })
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
911
1096
|
async function deleteKey(
|
|
912
1097
|
state: Extract<State, { kind: 'keyManage' }>,
|
|
913
1098
|
currentConfig: EthagentConfig,
|
|
914
1099
|
setState: (s: State) => void,
|
|
1100
|
+
onPick: (sel: ModelPickerSelection) => void,
|
|
1101
|
+
currentProvider: ProviderId,
|
|
915
1102
|
): Promise<void> {
|
|
916
1103
|
setState({ ...state, submitting: true, error: undefined })
|
|
917
1104
|
try {
|
|
918
1105
|
await rmKey(state.provider)
|
|
1106
|
+
if (state.provider === 'openai') await rmOpenAIOAuthCredentials()
|
|
919
1107
|
const data = await refreshProviderKeyState(state.data, currentConfig, state.provider)
|
|
1108
|
+
if (currentProvider === state.provider) {
|
|
1109
|
+
const fallback = pickFallbackSelection(data, state.provider)
|
|
1110
|
+
if (fallback) {
|
|
1111
|
+
onPick(fallback)
|
|
1112
|
+
return
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
setState({ kind: 'list', data })
|
|
1116
|
+
} catch (err: unknown) {
|
|
1117
|
+
setState({ ...state, submitting: false, error: (err as Error).message })
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
async function signOutOAuth(
|
|
1122
|
+
state: Extract<State, { kind: 'oauthManage' }>,
|
|
1123
|
+
currentConfig: EthagentConfig,
|
|
1124
|
+
setState: (s: State) => void,
|
|
1125
|
+
onPick: (sel: ModelPickerSelection) => void,
|
|
1126
|
+
currentProvider: ProviderId,
|
|
1127
|
+
): Promise<void> {
|
|
1128
|
+
setState({ ...state, submitting: true, error: undefined })
|
|
1129
|
+
try {
|
|
1130
|
+
await rmKey('openai')
|
|
1131
|
+
await rmOpenAIOAuthCredentials()
|
|
1132
|
+
const data = await refreshProviderKeyState(state.data, currentConfig, 'openai')
|
|
1133
|
+
if (currentProvider === 'openai') {
|
|
1134
|
+
const fallback = pickFallbackSelection(data, 'openai')
|
|
1135
|
+
if (fallback) {
|
|
1136
|
+
onPick(fallback)
|
|
1137
|
+
return
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
920
1140
|
setState({ kind: 'list', data })
|
|
921
1141
|
} catch (err: unknown) {
|
|
922
1142
|
setState({ ...state, submitting: false, error: (err as Error).message })
|
|
923
1143
|
}
|
|
924
1144
|
}
|
|
925
1145
|
|
|
1146
|
+
function pickFallbackSelection(data: LoadedData, removed: ProviderId): ModelPickerSelection | null {
|
|
1147
|
+
for (const provider of MODEL_PICKER_CLOUD_PROVIDERS) {
|
|
1148
|
+
if (provider === removed) continue
|
|
1149
|
+
if (data.cloudKeys[provider] !== true) continue
|
|
1150
|
+
const catalogModel = data.cloudCatalogs[provider]?.entries[0]?.id
|
|
1151
|
+
const model = catalogModel ?? defaultModelFor(provider)
|
|
1152
|
+
return { kind: 'cloud', provider, model, keyJustSet: false }
|
|
1153
|
+
}
|
|
1154
|
+
if (data.hfModels.length > 0) {
|
|
1155
|
+
return { kind: 'llamacpp', model: data.hfModels[0]!.id }
|
|
1156
|
+
}
|
|
1157
|
+
return null
|
|
1158
|
+
}
|
|
1159
|
+
|
|
926
1160
|
async function refreshProviderKeyState(
|
|
927
1161
|
data: LoadedData,
|
|
928
1162
|
currentConfig: EthagentConfig,
|
|
929
1163
|
provider: CloudProviderId,
|
|
930
1164
|
): Promise<LoadedData> {
|
|
931
1165
|
clearModelCatalogCache()
|
|
932
|
-
const
|
|
1166
|
+
const apiKeySet = await hasKey(provider)
|
|
1167
|
+
const oauthSet = provider === 'openai' ? await hasOpenAIOAuthCredentials() : false
|
|
1168
|
+
const keySet = apiKeySet || oauthSet
|
|
933
1169
|
const cloudKeys = { ...data.cloudKeys, [provider]: keySet }
|
|
1170
|
+
const cloudCredentialKinds: Partial<Record<ProviderId, CloudCredentialKind>> = { ...(data.cloudCredentialKinds ?? {}) }
|
|
1171
|
+
if (oauthSet) cloudCredentialKinds[provider] = 'oauth'
|
|
1172
|
+
else if (apiKeySet) cloudCredentialKinds[provider] = 'apikey'
|
|
1173
|
+
else delete cloudCredentialKinds[provider]
|
|
934
1174
|
const cloudCatalogs = { ...data.cloudCatalogs }
|
|
935
1175
|
if (keySet) {
|
|
936
1176
|
cloudCatalogs[provider] = await discoverProviderModels(configForProvider(currentConfig, provider))
|
|
937
1177
|
} else {
|
|
938
1178
|
delete cloudCatalogs[provider]
|
|
939
1179
|
}
|
|
940
|
-
return { ...data, cloudKeys, cloudCatalogs }
|
|
1180
|
+
return { ...data, cloudKeys, cloudCatalogs, cloudCredentialKinds }
|
|
941
1181
|
}
|
|
942
1182
|
|
|
943
1183
|
function configForProvider(config: EthagentConfig, provider: CloudProviderId): EthagentConfig {
|
package/src/models/catalog.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { defaultModelFor, type EthagentConfig, type ProviderId } from '../storage/config.js'
|
|
2
2
|
import { getKey } from '../storage/secrets.js'
|
|
3
3
|
import { loadLocalHfModels } from './huggingface.js'
|
|
4
|
+
import { hasOpenAIOAuthCredentials } from '../auth/openaiOAuth/credentials.js'
|
|
5
|
+
import { providerDisplayName } from './providerDisplay.js'
|
|
6
|
+
|
|
7
|
+
const OPENAI_OAUTH_MODEL_IDS = ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex', 'gpt-5.2'] as const
|
|
8
|
+
|
|
9
|
+
export const OPENAI_OAUTH_DEFAULT_MODEL = 'gpt-5.4'
|
|
10
|
+
|
|
11
|
+
export function isOpenAIOAuthAllowedModel(model: string): boolean {
|
|
12
|
+
return (OPENAI_OAUTH_MODEL_IDS as readonly string[]).includes(model)
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
export type ModelCatalogSource = 'installed' | 'discovered' | 'fallback'
|
|
6
16
|
|
|
@@ -72,7 +82,12 @@ export async function discoverProviderModels(
|
|
|
72
82
|
|
|
73
83
|
const loadKey = deps.loadKey ?? getKey
|
|
74
84
|
const apiKey = await loadKey(provider)
|
|
75
|
-
if (!apiKey)
|
|
85
|
+
if (!apiKey) {
|
|
86
|
+
if (provider === 'openai' && await hasOpenAIOAuthCredentials()) {
|
|
87
|
+
return openAIOAuthCatalog()
|
|
88
|
+
}
|
|
89
|
+
return fallbackResult(config, `missing ${providerDisplayName(provider)} API key`)
|
|
90
|
+
}
|
|
76
91
|
|
|
77
92
|
const baseUrl = provider === 'openai' ? openAIBaseUrlFor(config) : ''
|
|
78
93
|
const key = cacheKey(provider, baseUrl, true)
|
|
@@ -98,6 +113,19 @@ export async function discoverProviderModels(
|
|
|
98
113
|
}
|
|
99
114
|
}
|
|
100
115
|
|
|
116
|
+
function openAIOAuthCatalog(): ModelCatalogResult {
|
|
117
|
+
return {
|
|
118
|
+
provider: 'openai',
|
|
119
|
+
status: 'ok',
|
|
120
|
+
entries: OPENAI_OAUTH_MODEL_IDS.map(id => ({
|
|
121
|
+
provider: 'openai' as ProviderId,
|
|
122
|
+
id,
|
|
123
|
+
label: id,
|
|
124
|
+
source: 'discovered' as const,
|
|
125
|
+
})),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
101
129
|
function fallbackResult(config: EthagentConfig, error?: string): ModelCatalogResult {
|
|
102
130
|
const provider = config.provider
|
|
103
131
|
return {
|
|
@@ -7,21 +7,14 @@ import { type SelectOption } from '../ui/Select.js'
|
|
|
7
7
|
import { formatLocalHfModelDisplayName, formatModelDisplayName } from './modelDisplay.js'
|
|
8
8
|
import { localModelId, quantizationFromFilename } from './huggingface.js'
|
|
9
9
|
import type { UncensoredCatalogEntry } from './uncensoredCatalog.js'
|
|
10
|
+
import { cloudProviderDisplayName, providerDisplayName, type CloudProviderId } from './providerDisplay.js'
|
|
10
11
|
|
|
11
|
-
export type CloudProviderId
|
|
12
|
+
export { cloudProviderDisplayName, providerDisplayName, type CloudProviderId }
|
|
12
13
|
|
|
13
14
|
export const MODEL_PICKER_CLOUD_PROVIDERS: CloudProviderId[] = ['openai', 'anthropic', 'gemini']
|
|
14
15
|
export const LOCAL_MODEL_LINK_HINT = 'Paste a GGUF link'
|
|
15
16
|
export const LOCAL_MODEL_LINK_EXAMPLE = 'e.g. https://huggingface.co/Qwen/Qwen3-8B-GGUF'
|
|
16
17
|
|
|
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
|
-
|
|
25
18
|
export type LocalHfPickerModel = {
|
|
26
19
|
id: string
|
|
27
20
|
displayName: string
|
|
@@ -32,6 +25,8 @@ export type LocalHfPickerModel = {
|
|
|
32
25
|
status: 'ready' | 'incomplete'
|
|
33
26
|
}
|
|
34
27
|
|
|
28
|
+
export type CloudCredentialKind = 'apikey' | 'oauth'
|
|
29
|
+
|
|
35
30
|
export type ModelPickerOptionsData = {
|
|
36
31
|
llamaCpp: {
|
|
37
32
|
binaryPresent: boolean
|
|
@@ -42,6 +37,7 @@ export type ModelPickerOptionsData = {
|
|
|
42
37
|
machineSpec?: SpecSnapshot
|
|
43
38
|
cloudKeys: Partial<Record<ProviderId, boolean>>
|
|
44
39
|
cloudCatalogs: Partial<Record<ProviderId, ModelCatalogResult>>
|
|
40
|
+
cloudCredentialKinds?: Partial<Record<ProviderId, CloudCredentialKind>>
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
export type ModelPickerContextFit = {
|
|
@@ -78,6 +74,9 @@ export function buildModelPickerOptions(
|
|
|
78
74
|
options.push(groupOption(`hdr:cloud:${provider}`, cloudProviderDisplayName(provider)))
|
|
79
75
|
const keySet = data.cloudKeys[provider] === true
|
|
80
76
|
if (!keySet) {
|
|
77
|
+
if (provider === 'openai') {
|
|
78
|
+
options.push(utilityOption('oauth:openai', 'Sign in with ChatGPT', 'Use your ChatGPT subscription'))
|
|
79
|
+
}
|
|
81
80
|
options.push(utilityOption(`key:set:${provider}`, 'Add API Key'))
|
|
82
81
|
continue
|
|
83
82
|
}
|
|
@@ -105,7 +104,10 @@ export function buildModelPickerOptions(
|
|
|
105
104
|
))
|
|
106
105
|
}
|
|
107
106
|
options.push(utilityOption(`catalog:${provider}`, 'Full Catalog'))
|
|
108
|
-
|
|
107
|
+
const manageLabel = provider === 'openai' && data.cloudCredentialKinds?.openai === 'oauth'
|
|
108
|
+
? 'Manage ChatGPT Sign-in'
|
|
109
|
+
: 'Manage API Key'
|
|
110
|
+
options.push(utilityOption(`key:manage:${provider}`, manageLabel))
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
options.push(sectionOption('hdr:exit', 'Exit'))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ProviderId } from '../storage/config.js'
|
|
2
|
+
|
|
3
|
+
export type CloudProviderId = Exclude<ProviderId, 'llamacpp'>
|
|
4
|
+
|
|
5
|
+
export function cloudProviderDisplayName(provider: CloudProviderId): string {
|
|
6
|
+
switch (provider) {
|
|
7
|
+
case 'openai': return 'OpenAI'
|
|
8
|
+
case 'anthropic': return 'Anthropic'
|
|
9
|
+
case 'gemini': return 'Gemini'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function providerDisplayName(provider: ProviderId): string {
|
|
14
|
+
if (provider === 'llamacpp') return 'llama.cpp'
|
|
15
|
+
return cloudProviderDisplayName(provider)
|
|
16
|
+
}
|
package/src/providers/errors.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ProviderId } from '../storage/config.js'
|
|
2
2
|
import { ProviderError } from './contracts.js'
|
|
3
3
|
import { formatGeminiRateLimitMessage } from './gemini.js'
|
|
4
|
+
import { providerDisplayName } from '../models/providerDisplay.js'
|
|
4
5
|
|
|
5
6
|
type ErrorBody =
|
|
6
7
|
| string
|
|
@@ -30,19 +31,20 @@ export async function providerErrorFromResponse(
|
|
|
30
31
|
&& /API[_ ]?key( not valid| not found|_invalid)|invalid api key/i.test(detail)
|
|
31
32
|
) {
|
|
32
33
|
return new ProviderError(
|
|
33
|
-
'
|
|
34
|
+
'Gemini: API key rejected — verify your key at https://aistudio.google.com/app/apikey, then run /key gemini to set it again',
|
|
34
35
|
)
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
if (provider !== 'llamacpp') {
|
|
39
|
+
const name = providerDisplayName(provider)
|
|
38
40
|
if (response.status === 401 || response.status === 403) {
|
|
39
|
-
return new ProviderError(`auth failed: check your ${
|
|
41
|
+
return new ProviderError(`auth failed: check your ${name} key (/doctor to verify)`)
|
|
40
42
|
}
|
|
41
43
|
if (response.status === 429) {
|
|
42
|
-
return new ProviderError(detail || `${
|
|
44
|
+
return new ProviderError(detail || `${name} rate limit exceeded`, { transient: true })
|
|
43
45
|
}
|
|
44
46
|
if (response.status >= 500) {
|
|
45
|
-
return new ProviderError(detail || `${
|
|
47
|
+
return new ProviderError(detail || `${name} server error (${response.status})`, { transient: true })
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -5,6 +5,7 @@ import { providerErrorFromResponse } from './errors.js'
|
|
|
5
5
|
import { fetchWithRetryStreamEvents } from './retry.js'
|
|
6
6
|
import { iterSseFrames } from './sse.js'
|
|
7
7
|
import { messageTextContent } from '../utils/messages.js'
|
|
8
|
+
import { providerDisplayName } from '../models/providerDisplay.js'
|
|
8
9
|
|
|
9
10
|
export type OpenAIToolDefinition = {
|
|
10
11
|
type: 'function'
|
|
@@ -369,7 +370,7 @@ function providerNetworkErrorMessage(
|
|
|
369
370
|
): string {
|
|
370
371
|
const message = (err as Error).message || fallback
|
|
371
372
|
if (provider !== 'llamacpp') return message
|
|
372
|
-
return `${provider} request failed at ${baseUrl}: ${message}`
|
|
373
|
+
return `${providerDisplayName(provider)} request failed at ${baseUrl}: ${message}`
|
|
373
374
|
}
|
|
374
375
|
|
|
375
376
|
class ContentThinkingParser {
|