ethagent 3.3.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -260
- 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,306 +0,0 @@
|
|
|
1
|
-
import type { ProviderId } from '../storage/config.js'
|
|
2
|
-
import type { Message, Provider, ProviderCompleteOptions, StreamEvent } from './contracts.js'
|
|
3
|
-
import { ProviderError } from './contracts.js'
|
|
4
|
-
import { providerErrorFromResponse } from './errors.js'
|
|
5
|
-
import { fetchWithRetryStreamEvents } from './retry.js'
|
|
6
|
-
import { iterSseEvents } from './sse.js'
|
|
7
|
-
import { buildResponsesBody } from './openai-responses-format.js'
|
|
8
|
-
import { supportsOpenAIImages, type OpenAIToolDefinition } from './openai-chat.js'
|
|
9
|
-
import { hasImageBlocks, ImageLoadError } from '../utils/images.js'
|
|
10
|
-
|
|
11
|
-
const READ_TIMEOUT_MS = 45_000
|
|
12
|
-
|
|
13
|
-
type DoneStopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | 'unknown'
|
|
14
|
-
|
|
15
|
-
export type OpenAIResponsesProviderOptions = {
|
|
16
|
-
model: string
|
|
17
|
-
baseUrl: string
|
|
18
|
-
accessToken: string
|
|
19
|
-
accountId?: string
|
|
20
|
-
originator?: string
|
|
21
|
-
tools?: OpenAIToolDefinition[]
|
|
22
|
-
maxRetries?: number
|
|
23
|
-
refresh?: () => Promise<string>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type StreamingToolCall = {
|
|
27
|
-
callId: string
|
|
28
|
-
name: string
|
|
29
|
-
inputJson: string
|
|
30
|
-
started: boolean
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class OpenAIResponsesProvider implements Provider {
|
|
34
|
-
readonly id: ProviderId = 'openai'
|
|
35
|
-
readonly model: string
|
|
36
|
-
readonly supportsTools: boolean
|
|
37
|
-
private accessToken: string
|
|
38
|
-
private readonly baseUrl: string
|
|
39
|
-
private readonly accountId?: string
|
|
40
|
-
private readonly originator: string
|
|
41
|
-
private readonly tools: OpenAIToolDefinition[]
|
|
42
|
-
private readonly maxRetries?: number
|
|
43
|
-
private readonly refresh?: () => Promise<string>
|
|
44
|
-
|
|
45
|
-
constructor(opts: OpenAIResponsesProviderOptions) {
|
|
46
|
-
this.model = opts.model
|
|
47
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, '')
|
|
48
|
-
this.accessToken = opts.accessToken
|
|
49
|
-
this.accountId = opts.accountId
|
|
50
|
-
this.originator = opts.originator ?? 'codex_cli_rs'
|
|
51
|
-
this.tools = opts.tools ?? []
|
|
52
|
-
this.maxRetries = opts.maxRetries
|
|
53
|
-
this.refresh = opts.refresh
|
|
54
|
-
this.supportsTools = this.tools.length > 0
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async *complete(
|
|
58
|
-
messages: Message[],
|
|
59
|
-
signal: AbortSignal,
|
|
60
|
-
options: ProviderCompleteOptions = {},
|
|
61
|
-
): AsyncIterable<StreamEvent> {
|
|
62
|
-
if (!this.accessToken) {
|
|
63
|
-
const error = new ProviderError('missing OAuth access token for openai (sign in again via the model picker)')
|
|
64
|
-
yield { type: 'error', message: error.message }
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (hasImageBlocks(messages) && !supportsOpenAIImages(this.model)) {
|
|
69
|
-
yield { type: 'error', message: `image input is not enabled for ${this.model}` }
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let attempt = 0
|
|
74
|
-
while (true) {
|
|
75
|
-
attempt += 1
|
|
76
|
-
let body: string
|
|
77
|
-
try {
|
|
78
|
-
body = JSON.stringify(await buildResponsesBody({
|
|
79
|
-
model: this.model,
|
|
80
|
-
messages,
|
|
81
|
-
tools: this.tools,
|
|
82
|
-
maxOutputTokens: options.maxTokens,
|
|
83
|
-
}))
|
|
84
|
-
} catch (err: unknown) {
|
|
85
|
-
if (err instanceof ImageLoadError) {
|
|
86
|
-
yield { type: 'error', message: err.message }
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
throw err
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let response: Response
|
|
93
|
-
try {
|
|
94
|
-
response = yield* fetchWithRetryStreamEvents(`${this.baseUrl}/responses`, {
|
|
95
|
-
method: 'POST',
|
|
96
|
-
headers: this.requestHeaders(),
|
|
97
|
-
body,
|
|
98
|
-
}, { signal, maxRetries: this.maxRetries, rateLimitResetProvider: 'openai-compatible' })
|
|
99
|
-
} catch (err: unknown) {
|
|
100
|
-
if (signal.aborted) return
|
|
101
|
-
yield { type: 'error', message: networkErrorMessage(this.baseUrl, err) }
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (response.status === 401 && this.refresh && attempt === 1) {
|
|
106
|
-
try {
|
|
107
|
-
this.accessToken = await this.refresh()
|
|
108
|
-
continue
|
|
109
|
-
} catch (refreshErr) {
|
|
110
|
-
const message = refreshErr instanceof Error ? refreshErr.message : String(refreshErr)
|
|
111
|
-
yield { type: 'error', message: `OpenAI sign-in expired and refresh failed: ${message}` }
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
const error = await providerErrorFromResponse('openai', response)
|
|
118
|
-
yield { type: 'error', message: error.message }
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
if (!response.body) {
|
|
122
|
-
yield { type: 'error', message: 'empty response body' }
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
yield* this.parseStream(response.body, signal)
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private requestHeaders(): Record<string, string> {
|
|
132
|
-
const headers: Record<string, string> = {
|
|
133
|
-
'Content-Type': 'application/json',
|
|
134
|
-
Accept: 'text/event-stream',
|
|
135
|
-
Authorization: `Bearer ${this.accessToken}`,
|
|
136
|
-
originator: this.originator,
|
|
137
|
-
}
|
|
138
|
-
if (this.accountId) headers['chatgpt-account-id'] = this.accountId
|
|
139
|
-
return headers
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private async *parseStream(body: ReadableStream<Uint8Array>, signal: AbortSignal): AsyncIterable<StreamEvent> {
|
|
143
|
-
const toolCalls = new Map<string, StreamingToolCall>()
|
|
144
|
-
let inputTokens: number | undefined
|
|
145
|
-
let outputTokens: number | undefined
|
|
146
|
-
let stopReason: DoneStopReason = 'unknown'
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
for await (const frame of iterSseEvents(body, signal, READ_TIMEOUT_MS)) {
|
|
150
|
-
const eventName = frame.event ?? ''
|
|
151
|
-
if (!frame.data || frame.data === '[DONE]') continue
|
|
152
|
-
let parsed: Record<string, unknown>
|
|
153
|
-
try {
|
|
154
|
-
parsed = JSON.parse(frame.data) as Record<string, unknown>
|
|
155
|
-
} catch {
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
switch (eventName) {
|
|
160
|
-
case 'response.output_text.delta': {
|
|
161
|
-
const delta = typeof parsed.delta === 'string' ? parsed.delta : ''
|
|
162
|
-
if (delta) yield { type: 'text', delta }
|
|
163
|
-
break
|
|
164
|
-
}
|
|
165
|
-
case 'response.reasoning_summary_text.delta':
|
|
166
|
-
case 'response.reasoning.delta':
|
|
167
|
-
case 'response.reasoning_text.delta': {
|
|
168
|
-
const delta = typeof parsed.delta === 'string' ? parsed.delta : ''
|
|
169
|
-
if (delta) yield { type: 'thinking', delta }
|
|
170
|
-
break
|
|
171
|
-
}
|
|
172
|
-
case 'response.output_item.added': {
|
|
173
|
-
const item = (parsed.item ?? {}) as Record<string, unknown>
|
|
174
|
-
if (item.type === 'function_call') {
|
|
175
|
-
const callId = pickString(item.call_id) ?? pickString(item.id) ?? `tool-${toolCalls.size}`
|
|
176
|
-
const name = pickString(item.name) ?? ''
|
|
177
|
-
toolCalls.set(callId, { callId, name, inputJson: '', started: false })
|
|
178
|
-
if (name) {
|
|
179
|
-
toolCalls.get(callId)!.started = true
|
|
180
|
-
yield { type: 'tool_use_start', id: callId, name }
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
break
|
|
184
|
-
}
|
|
185
|
-
case 'response.function_call_arguments.delta': {
|
|
186
|
-
const callId = resolveCallId(parsed, toolCalls)
|
|
187
|
-
const delta = typeof parsed.delta === 'string' ? parsed.delta : ''
|
|
188
|
-
if (!callId || !delta) break
|
|
189
|
-
const existing = toolCalls.get(callId)
|
|
190
|
-
if (!existing) break
|
|
191
|
-
existing.inputJson += delta
|
|
192
|
-
yield { type: 'tool_use_delta', id: callId, delta }
|
|
193
|
-
break
|
|
194
|
-
}
|
|
195
|
-
case 'response.output_item.done': {
|
|
196
|
-
const item = (parsed.item ?? {}) as Record<string, unknown>
|
|
197
|
-
if (item.type === 'function_call') {
|
|
198
|
-
const callId = pickString(item.call_id) ?? pickString(item.id)
|
|
199
|
-
if (!callId) break
|
|
200
|
-
const existing = toolCalls.get(callId)
|
|
201
|
-
const name = pickString(item.name) ?? existing?.name ?? ''
|
|
202
|
-
const argsJson = pickString(item.arguments) ?? existing?.inputJson ?? ''
|
|
203
|
-
stopReason = 'tool_use'
|
|
204
|
-
yield {
|
|
205
|
-
type: 'tool_use_stop',
|
|
206
|
-
id: callId,
|
|
207
|
-
name,
|
|
208
|
-
input: parseToolArguments(argsJson),
|
|
209
|
-
}
|
|
210
|
-
toolCalls.delete(callId)
|
|
211
|
-
}
|
|
212
|
-
break
|
|
213
|
-
}
|
|
214
|
-
case 'response.completed': {
|
|
215
|
-
const usage = (parsed.response as { usage?: Record<string, unknown> } | undefined)?.usage
|
|
216
|
-
const tokens = readUsage(usage)
|
|
217
|
-
if (tokens.input !== undefined) inputTokens = tokens.input
|
|
218
|
-
if (tokens.output !== undefined) outputTokens = tokens.output
|
|
219
|
-
if (stopReason !== 'tool_use') stopReason = 'end_turn'
|
|
220
|
-
break
|
|
221
|
-
}
|
|
222
|
-
case 'response.failed':
|
|
223
|
-
case 'response.error':
|
|
224
|
-
case 'error': {
|
|
225
|
-
const error = parsed.error as { message?: string } | undefined
|
|
226
|
-
const message = error?.message ?? 'Responses API error'
|
|
227
|
-
yield { type: 'error', message }
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
case 'response.incomplete': {
|
|
231
|
-
const reason = pickString((parsed.response as { incomplete_details?: { reason?: string } } | undefined)?.incomplete_details?.reason)
|
|
232
|
-
if (reason === 'max_output_tokens') stopReason = 'max_tokens'
|
|
233
|
-
break
|
|
234
|
-
}
|
|
235
|
-
default:
|
|
236
|
-
break
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} catch (err: unknown) {
|
|
240
|
-
if (signal.aborted) return
|
|
241
|
-
yield { type: 'error', message: networkErrorMessage(this.baseUrl, err, 'stream error') }
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (signal.aborted) return
|
|
246
|
-
yield { type: 'done', inputTokens, outputTokens, stopReason }
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function pickString(value: unknown): string | undefined {
|
|
251
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function resolveCallId(parsed: Record<string, unknown>, calls: Map<string, StreamingToolCall>): string | undefined {
|
|
255
|
-
return (
|
|
256
|
-
pickString(parsed.call_id)
|
|
257
|
-
?? pickString(parsed.item_id)
|
|
258
|
-
?? pickString((parsed.item as Record<string, unknown> | undefined)?.call_id)
|
|
259
|
-
?? pickString((parsed.item as Record<string, unknown> | undefined)?.id)
|
|
260
|
-
?? (calls.size === 1 ? Array.from(calls.keys())[0] : undefined)
|
|
261
|
-
)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function readUsage(usage: Record<string, unknown> | undefined): { input?: number; output?: number } {
|
|
265
|
-
if (!usage) return {}
|
|
266
|
-
const input = typeof usage.input_tokens === 'number'
|
|
267
|
-
? usage.input_tokens
|
|
268
|
-
: typeof usage.prompt_tokens === 'number' ? usage.prompt_tokens : undefined
|
|
269
|
-
const output = typeof usage.output_tokens === 'number'
|
|
270
|
-
? usage.output_tokens
|
|
271
|
-
: typeof usage.completion_tokens === 'number' ? usage.completion_tokens : undefined
|
|
272
|
-
return { input, output }
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function parseToolArguments(input: string): Record<string, unknown> {
|
|
276
|
-
const trimmed = input.trim()
|
|
277
|
-
if (!trimmed) return {}
|
|
278
|
-
try {
|
|
279
|
-
return coerceToToolArguments(JSON.parse(trimmed))
|
|
280
|
-
} catch {
|
|
281
|
-
return {}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function coerceToToolArguments(value: unknown): Record<string, unknown> {
|
|
286
|
-
if (typeof value === 'string') {
|
|
287
|
-
const trimmed = value.trim()
|
|
288
|
-
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
289
|
-
try {
|
|
290
|
-
return coerceToToolArguments(JSON.parse(trimmed))
|
|
291
|
-
} catch {
|
|
292
|
-
return {}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return {}
|
|
296
|
-
}
|
|
297
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
298
|
-
return value as Record<string, unknown>
|
|
299
|
-
}
|
|
300
|
-
return {}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function networkErrorMessage(baseUrl: string, err: unknown, fallback = 'network error'): string {
|
|
304
|
-
const message = (err as Error).message || fallback
|
|
305
|
-
return `openai request failed at ${baseUrl}: ${message}`
|
|
306
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { Message, MessageContentBlock } from './contracts.js'
|
|
2
|
-
import { messageTextContent } from '../utils/messages.js'
|
|
3
|
-
import { loadImageBlock } from '../utils/images.js'
|
|
4
|
-
|
|
5
|
-
export async function toWireMessages(messages: Message[]): Promise<Array<Record<string, unknown>>> {
|
|
6
|
-
const out: Array<Record<string, unknown>> = []
|
|
7
|
-
|
|
8
|
-
for (const message of messages) {
|
|
9
|
-
if (typeof message.content === 'string') {
|
|
10
|
-
out.push({ role: message.role, content: message.content })
|
|
11
|
-
continue
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (message.role === 'user') {
|
|
15
|
-
const toolResults = message.content.filter(isToolResultBlock)
|
|
16
|
-
if (toolResults.length > 0) {
|
|
17
|
-
for (const block of toolResults) {
|
|
18
|
-
out.push({
|
|
19
|
-
role: 'tool',
|
|
20
|
-
tool_call_id: block.toolUseId,
|
|
21
|
-
content: block.content,
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
const nonToolBlocks = message.content.filter(block => block.type !== 'tool_result')
|
|
25
|
-
if (nonToolBlocks.length > 0) {
|
|
26
|
-
out.push({ role: 'user', content: await toOpenAIUserContent(nonToolBlocks) })
|
|
27
|
-
}
|
|
28
|
-
continue
|
|
29
|
-
}
|
|
30
|
-
out.push({ role: 'user', content: await toOpenAIUserContent(message.content) })
|
|
31
|
-
continue
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (message.role === 'assistant') {
|
|
35
|
-
const textParts = message.content.filter(isTextBlock).map(block => block.text)
|
|
36
|
-
const toolCalls = message.content.filter(isToolUseBlock).map(block => ({
|
|
37
|
-
id: block.id,
|
|
38
|
-
type: 'function',
|
|
39
|
-
function: {
|
|
40
|
-
name: block.name,
|
|
41
|
-
arguments: JSON.stringify(block.input),
|
|
42
|
-
},
|
|
43
|
-
}))
|
|
44
|
-
out.push({
|
|
45
|
-
role: 'assistant',
|
|
46
|
-
content: textParts.join(''),
|
|
47
|
-
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
48
|
-
})
|
|
49
|
-
continue
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const toolResults = message.content.filter(isToolResultBlock)
|
|
53
|
-
if (toolResults.length > 0) {
|
|
54
|
-
for (const block of toolResults) {
|
|
55
|
-
out.push({
|
|
56
|
-
role: 'tool',
|
|
57
|
-
tool_call_id: block.toolUseId,
|
|
58
|
-
content: block.content,
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
continue
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
out.push({ role: message.role, content: messageTextContent(message) })
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return normalizeSystemMessages(out)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function toOpenAIUserContent(blocks: MessageContentBlock[]): Promise<Array<Record<string, unknown>>> {
|
|
71
|
-
const parts: Array<Record<string, unknown>> = []
|
|
72
|
-
for (const block of blocks) {
|
|
73
|
-
if (block.type === 'text') {
|
|
74
|
-
if (block.text.length > 0) parts.push({ type: 'text', text: block.text })
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
if (block.type === 'image') {
|
|
78
|
-
const loaded = await loadImageBlock(block)
|
|
79
|
-
if (loaded.url) {
|
|
80
|
-
parts.push({ type: 'image_url', image_url: { url: loaded.url } })
|
|
81
|
-
} else if (loaded.dataBase64 && loaded.mimeType) {
|
|
82
|
-
parts.push({ type: 'image_url', image_url: { url: `data:${loaded.mimeType};base64,${loaded.dataBase64}` } })
|
|
83
|
-
}
|
|
84
|
-
continue
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return parts.length > 0 ? parts : [{ type: 'text', text: '' }]
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function normalizeSystemMessages(messages: Array<Record<string, unknown>>): Array<Record<string, unknown>> {
|
|
91
|
-
const systemContents: string[] = []
|
|
92
|
-
const nonSystem: Array<Record<string, unknown>> = []
|
|
93
|
-
|
|
94
|
-
for (const message of messages) {
|
|
95
|
-
if (message.role === 'system') {
|
|
96
|
-
if (typeof message.content === 'string' && message.content.length > 0) {
|
|
97
|
-
systemContents.push(message.content)
|
|
98
|
-
}
|
|
99
|
-
continue
|
|
100
|
-
}
|
|
101
|
-
nonSystem.push(message)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (systemContents.length === 0) return nonSystem
|
|
105
|
-
return [
|
|
106
|
-
{
|
|
107
|
-
role: 'system',
|
|
108
|
-
content: systemContents.join('\n\n'),
|
|
109
|
-
},
|
|
110
|
-
...nonSystem,
|
|
111
|
-
]
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function isTextBlock(block: MessageContentBlock): block is Extract<MessageContentBlock, { type: 'text' }> {
|
|
115
|
-
return block.type === 'text'
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function isToolUseBlock(block: MessageContentBlock): block is Extract<MessageContentBlock, { type: 'tool_use' }> {
|
|
119
|
-
return block.type === 'tool_use'
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function isToolResultBlock(block: MessageContentBlock): block is Extract<MessageContentBlock, { type: 'tool_result' }> {
|
|
123
|
-
return block.type === 'tool_result'
|
|
124
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type { EthagentConfig } from '../storage/config.js'
|
|
2
|
-
import { localProviderBaseUrlFor } from '../storage/config.js'
|
|
3
|
-
import { getKey } from '../storage/secrets.js'
|
|
4
|
-
import type { Provider } from './contracts.js'
|
|
5
|
-
import type { SessionMode } from '../runtime/sessionMode.js'
|
|
6
|
-
import { AnthropicProvider } from './anthropic.js'
|
|
7
|
-
import { GeminiProvider } from './gemini.js'
|
|
8
|
-
import { OpenAIChatProvider } from './openai-chat.js'
|
|
9
|
-
import { OpenAIResponsesProvider } from './openai-responses.js'
|
|
10
|
-
import { anthropicTools, geminiTools, openAITools } from '../tools/registry.js'
|
|
11
|
-
import { openAIBaseUrlFor, OPENAI_OAUTH_DEFAULT_MODEL, isOpenAIOAuthAllowedModel } from '../models/catalog.js'
|
|
12
|
-
import {
|
|
13
|
-
getOpenAIOAuthCredentials,
|
|
14
|
-
setOpenAIOAuthCredentials,
|
|
15
|
-
type OpenAIOAuthCredentials,
|
|
16
|
-
} from '../auth/openaiOAuth/credentials.js'
|
|
17
|
-
import { refreshOpenAIAccessToken, shouldRefresh } from '../auth/openaiOAuth/refresh.js'
|
|
18
|
-
import type { Tool } from '../tools/contracts.js'
|
|
19
|
-
|
|
20
|
-
export const OPENAI_CHATGPT_BACKEND_URL = 'https://chatgpt.com/backend-api/codex'
|
|
21
|
-
|
|
22
|
-
export function isLocalProvider(provider: string): boolean {
|
|
23
|
-
return provider === 'llamacpp'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function createProvider(config: EthagentConfig, options: { mode?: SessionMode; dynamicTools?: Tool[] } = {}): Provider {
|
|
27
|
-
const mode = options.mode ?? 'chat'
|
|
28
|
-
const toolContext = { hasIdentity: Boolean(config.identity), dynamicTools: options.dynamicTools }
|
|
29
|
-
switch (config.provider) {
|
|
30
|
-
case 'llamacpp':
|
|
31
|
-
return new OpenAIChatProvider({
|
|
32
|
-
id: 'llamacpp',
|
|
33
|
-
model: config.model,
|
|
34
|
-
baseUrl: localProviderBaseUrlFor('llamacpp', config.baseUrl),
|
|
35
|
-
apiKey: 'llamacpp',
|
|
36
|
-
tools: openAITools(mode, toolContext),
|
|
37
|
-
hasVisionProjector: Boolean(config.localMmprojPath),
|
|
38
|
-
})
|
|
39
|
-
case 'openai':
|
|
40
|
-
return createOpenAIProvider(config, openAITools(mode, toolContext))
|
|
41
|
-
case 'anthropic':
|
|
42
|
-
return new AnthropicProvider({ model: config.model, tools: anthropicTools(mode, toolContext) })
|
|
43
|
-
case 'gemini':
|
|
44
|
-
return new GeminiProvider({ model: config.model, tools: geminiTools(mode, toolContext) })
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function createOpenAIProvider(config: EthagentConfig, tools: ReturnType<typeof openAITools>): Provider {
|
|
49
|
-
return new OpenAIRoutingProvider(config, tools)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
class OpenAIRoutingProvider implements Provider {
|
|
53
|
-
readonly id = 'openai' as const
|
|
54
|
-
readonly model: string
|
|
55
|
-
readonly supportsTools: boolean
|
|
56
|
-
private delegate: Provider | null = null
|
|
57
|
-
private readonly config: EthagentConfig
|
|
58
|
-
private readonly tools: ReturnType<typeof openAITools>
|
|
59
|
-
|
|
60
|
-
constructor(config: EthagentConfig, tools: ReturnType<typeof openAITools>) {
|
|
61
|
-
this.config = config
|
|
62
|
-
this.tools = tools
|
|
63
|
-
this.model = config.model
|
|
64
|
-
this.supportsTools = tools.length > 0
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async *complete(...args: Parameters<Provider['complete']>): ReturnType<Provider['complete']> {
|
|
68
|
-
if (!this.delegate) this.delegate = await this.resolveDelegate()
|
|
69
|
-
yield* this.delegate.complete(...args)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private async resolveDelegate(): Promise<Provider> {
|
|
73
|
-
const oauth = await loadFreshOAuthCredentials()
|
|
74
|
-
if (oauth) {
|
|
75
|
-
const oauthModel = isOpenAIOAuthAllowedModel(this.model) ? this.model : OPENAI_OAUTH_DEFAULT_MODEL
|
|
76
|
-
return new OpenAIResponsesProvider({
|
|
77
|
-
model: oauthModel,
|
|
78
|
-
baseUrl: OPENAI_CHATGPT_BACKEND_URL,
|
|
79
|
-
accessToken: oauth.accessToken,
|
|
80
|
-
accountId: oauth.accountId,
|
|
81
|
-
tools: this.tools,
|
|
82
|
-
refresh: async () => {
|
|
83
|
-
const next = await loadFreshOAuthCredentials({ force: true })
|
|
84
|
-
if (!next) throw new Error('No OAuth credentials available')
|
|
85
|
-
return next.accessToken
|
|
86
|
-
},
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
return new OpenAIChatProvider({
|
|
90
|
-
id: 'openai',
|
|
91
|
-
model: this.model,
|
|
92
|
-
baseUrl: openAIBaseUrlFor(this.config),
|
|
93
|
-
loadApiKey: () => getKey('openai'),
|
|
94
|
-
tools: this.tools,
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function loadFreshOAuthCredentials(options: { force?: boolean } = {}): Promise<OpenAIOAuthCredentials | null> {
|
|
100
|
-
const current = await getOpenAIOAuthCredentials()
|
|
101
|
-
if (!current) return null
|
|
102
|
-
if (!options.force && !shouldRefresh(current)) return current
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const refreshed = await refreshOpenAIAccessToken(current.refreshToken)
|
|
106
|
-
const now = Date.now()
|
|
107
|
-
const next: OpenAIOAuthCredentials = {
|
|
108
|
-
accessToken: refreshed.accessToken,
|
|
109
|
-
refreshToken: refreshed.refreshToken,
|
|
110
|
-
idToken: refreshed.idToken ?? current.idToken,
|
|
111
|
-
accountId: refreshed.accountId ?? current.accountId,
|
|
112
|
-
expiresAt: now + refreshed.expiresIn * 1000,
|
|
113
|
-
lastRefreshAt: now,
|
|
114
|
-
}
|
|
115
|
-
await setOpenAIOAuthCredentials(next)
|
|
116
|
-
return next
|
|
117
|
-
} catch {
|
|
118
|
-
return current
|
|
119
|
-
}
|
|
120
|
-
}
|
package/src/providers/retry.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { ProviderRetryStreamEvent } from './contracts.js'
|
|
2
|
-
import { fetchWithRetry, type FetchWithRetryOptions, type RetryEvent } from '../utils/withRetry.js'
|
|
3
|
-
|
|
4
|
-
type FetchSettled =
|
|
5
|
-
| { state: 'resolved'; response: Response }
|
|
6
|
-
| { state: 'rejected'; error: unknown }
|
|
7
|
-
|
|
8
|
-
export async function* fetchWithRetryStreamEvents(
|
|
9
|
-
input: string,
|
|
10
|
-
init: RequestInit,
|
|
11
|
-
options: FetchWithRetryOptions = {},
|
|
12
|
-
): AsyncGenerator<ProviderRetryStreamEvent, Response, void> {
|
|
13
|
-
const retryEvents: ProviderRetryStreamEvent[] = []
|
|
14
|
-
let settled: FetchSettled | undefined
|
|
15
|
-
let wake: (() => void) | undefined
|
|
16
|
-
|
|
17
|
-
const wakeWaiter = () => {
|
|
18
|
-
const current = wake
|
|
19
|
-
wake = undefined
|
|
20
|
-
current?.()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const waitForChange = (): Promise<void> => new Promise(resolve => {
|
|
24
|
-
wake = resolve
|
|
25
|
-
if (settled || retryEvents.length > 0) wakeWaiter()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const fetchPromise = fetchWithRetry(input, init, {
|
|
29
|
-
...options,
|
|
30
|
-
onRetry: (event: RetryEvent) => {
|
|
31
|
-
options.onRetry?.(event)
|
|
32
|
-
retryEvents.push({ type: 'retry', ...event })
|
|
33
|
-
wakeWaiter()
|
|
34
|
-
},
|
|
35
|
-
}).then(
|
|
36
|
-
response => {
|
|
37
|
-
settled = { state: 'resolved', response }
|
|
38
|
-
wakeWaiter()
|
|
39
|
-
},
|
|
40
|
-
error => {
|
|
41
|
-
settled = { state: 'rejected', error }
|
|
42
|
-
wakeWaiter()
|
|
43
|
-
},
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
while (!settled || retryEvents.length > 0) {
|
|
47
|
-
while (retryEvents.length > 0) {
|
|
48
|
-
yield retryEvents.shift()!
|
|
49
|
-
}
|
|
50
|
-
if (settled) break
|
|
51
|
-
await waitForChange()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await fetchPromise
|
|
55
|
-
if (settled?.state === 'resolved') return settled.response
|
|
56
|
-
if (settled?.state === 'rejected') throw settled.error
|
|
57
|
-
throw new Error('Fetch retry completed without a response')
|
|
58
|
-
}
|
package/src/providers/sse.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
export type SseEvent = {
|
|
2
|
-
event: string | null
|
|
3
|
-
data: string
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export async function* iterSseFrames(
|
|
7
|
-
body: ReadableStream<Uint8Array>,
|
|
8
|
-
signal: AbortSignal,
|
|
9
|
-
readTimeoutMs: number,
|
|
10
|
-
): AsyncIterable<string> {
|
|
11
|
-
for await (const event of iterSseEvents(body, signal, readTimeoutMs)) {
|
|
12
|
-
yield event.data
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function* iterSseEvents(
|
|
17
|
-
body: ReadableStream<Uint8Array>,
|
|
18
|
-
signal: AbortSignal,
|
|
19
|
-
readTimeoutMs: number,
|
|
20
|
-
): AsyncIterable<SseEvent> {
|
|
21
|
-
const reader = body.getReader()
|
|
22
|
-
const decoder = new TextDecoder()
|
|
23
|
-
let buffer = ''
|
|
24
|
-
try {
|
|
25
|
-
while (!signal.aborted) {
|
|
26
|
-
const { done, value } = await readWithTimeout(reader, readTimeoutMs)
|
|
27
|
-
if (done) break
|
|
28
|
-
buffer += decoder.decode(value, { stream: true })
|
|
29
|
-
let boundary = findFrameBoundary(buffer)
|
|
30
|
-
while (boundary !== -1) {
|
|
31
|
-
const raw = buffer.slice(0, boundary)
|
|
32
|
-
const separator = buffer.slice(boundary).match(/^\r?\n\r?\n/)?.[0] ?? '\n\n'
|
|
33
|
-
buffer = buffer.slice(boundary + separator.length)
|
|
34
|
-
const event = extractSseEvent(raw)
|
|
35
|
-
if (event) yield event
|
|
36
|
-
boundary = findFrameBoundary(buffer)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const tail = buffer.trim()
|
|
41
|
-
if (tail) {
|
|
42
|
-
const event = extractSseEvent(tail)
|
|
43
|
-
if (event) yield event
|
|
44
|
-
}
|
|
45
|
-
} finally {
|
|
46
|
-
try { reader.releaseLock() } catch { void 0 }
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function readWithTimeout(
|
|
51
|
-
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
52
|
-
timeoutMs: number,
|
|
53
|
-
): Promise<ReadableStreamReadResult<Uint8Array>> {
|
|
54
|
-
let timer: NodeJS.Timeout | undefined
|
|
55
|
-
const timeout = new Promise<never>((_, reject) => {
|
|
56
|
-
timer = setTimeout(() => {
|
|
57
|
-
reject(new Error(`no response from model in ${Math.round(timeoutMs / 1000)}s`))
|
|
58
|
-
}, timeoutMs)
|
|
59
|
-
})
|
|
60
|
-
try {
|
|
61
|
-
return await Promise.race([reader.read(), timeout])
|
|
62
|
-
} finally {
|
|
63
|
-
if (timer) clearTimeout(timer)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function extractDataPayload(frame: string): string | null {
|
|
68
|
-
return extractSseEvent(frame)?.data ?? null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function extractSseEvent(frame: string): SseEvent | null {
|
|
72
|
-
const lines = frame.split(/\r?\n/)
|
|
73
|
-
const dataLines: string[] = []
|
|
74
|
-
let eventName: string | null = null
|
|
75
|
-
|
|
76
|
-
for (const line of lines) {
|
|
77
|
-
if (line.startsWith('event:')) {
|
|
78
|
-
eventName = line.slice(6).trim() || null
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
if (line.startsWith('data:')) {
|
|
82
|
-
dataLines.push(line.slice(5).replace(/^ /, ''))
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (dataLines.length === 0) return null
|
|
87
|
-
return { event: eventName, data: dataLines.join('\n') }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function findFrameBoundary(buffer: string): number {
|
|
91
|
-
const match = /\r?\n\r?\n/.exec(buffer)
|
|
92
|
-
return match?.index ?? -1
|
|
93
|
-
}
|