ethagent 3.3.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +35 -0
- package/LICENSE +1 -1
- package/README.md +64 -104
- package/commands/ethagent.md +40 -0
- package/package.json +16 -16
- package/src/app/keybindings/KeybindingProvider.tsx +1 -6
- package/src/app/keybindings/types.ts +1 -6
- package/src/cli/ResetConfirmView.tsx +54 -53
- package/src/cli/demo.ts +86 -0
- package/src/cli/hookIo.ts +45 -0
- package/src/cli/main.tsx +94 -123
- package/src/cli/memoryGuard.ts +49 -0
- package/src/cli/reset.ts +28 -70
- package/src/cli/sessionStart.ts +33 -0
- package/src/cli/status.ts +46 -0
- package/src/cli/sync.ts +167 -0
- package/src/cli/syncAdapters/claude-code.ts +86 -0
- package/src/cli/syncAdapters/codex.ts +66 -0
- package/src/cli/syncAdapters/index.ts +45 -0
- package/src/cli/syncAdapters/managedBlock.ts +175 -0
- package/src/cli/syncAdapters/shared.ts +63 -0
- package/src/identity/continuity/envelopeParse.ts +20 -1
- package/src/identity/continuity/publicSkills.ts +3 -1
- package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
- package/src/identity/continuity/skills/scaffold.ts +5 -2
- package/src/identity/continuity/snapshots.ts +12 -5
- package/src/identity/continuity/storage/defaults.ts +20 -19
- package/src/identity/continuity/storage/status.ts +1 -1
- package/src/identity/ens/ensLookup/constants.ts +1 -1
- package/src/identity/manager/IdentityManager.tsx +33 -0
- package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
- package/src/identity/{hub → manager}/Routes.tsx +48 -34
- package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
- package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
- package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
- package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
- package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
- package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
- package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
- package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
- package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
- package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
- package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
- package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
- package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
- package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
- package/src/identity/{hub → manager}/create/effects.ts +19 -10
- package/src/identity/manager/create/importScan.ts +122 -0
- package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
- package/src/identity/{hub → manager}/custody/actions.ts +1 -15
- package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
- package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
- package/src/identity/{hub → manager}/custody/types.ts +1 -2
- package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
- package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
- package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
- package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
- package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
- package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
- package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
- package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
- package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
- package/src/identity/{hub → manager}/profile/effects.ts +1 -3
- package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
- package/src/identity/{hub → manager}/profile/state.ts +1 -1
- package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
- package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
- package/src/identity/{hub → manager}/restore/apply.ts +1 -1
- package/src/identity/{hub → manager}/restore/auth.ts +1 -1
- package/src/identity/{hub → manager}/restore/discover.ts +1 -1
- package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
- package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
- package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
- package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
- package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
- package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
- package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
- package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
- package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
- package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
- package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
- package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
- package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
- package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
- package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
- package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
- package/src/identity/manager/shared/effects/types.ts +30 -0
- package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
- package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
- package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
- package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
- package/src/identity/{hub → manager}/shared/utils.ts +6 -10
- package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
- package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
- package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
- package/src/identity/{hub → manager}/types.ts +5 -6
- package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
- package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
- package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
- package/src/identity/registry/erc8004/discovery.ts +3 -17
- package/src/identity/registry/erc8004/utils.ts +1 -1
- package/src/identity/storage/ipfs.ts +21 -1
- package/src/identity/wallet/browserWallet/html.ts +10 -2
- package/src/identity/wallet/browserWallet/http.ts +18 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
- package/src/identity/wallet/browserWallet/requests.ts +10 -28
- package/src/identity/wallet/browserWallet/session.ts +26 -33
- package/src/identity/wallet/browserWallet/validation.ts +14 -0
- package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
- package/src/identity/wallet/page/boot.ts +43 -0
- package/src/identity/wallet/page/config.ts +59 -0
- package/src/identity/wallet/page/constants.ts +12 -0
- package/src/identity/wallet/page/copy.ts +47 -68
- package/src/identity/wallet/page/css.ts +638 -0
- package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
- package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
- package/src/identity/wallet/page/markup.ts +44 -34
- package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
- package/src/identity/wallet/page/resize.ts +95 -0
- package/src/identity/wallet/page/state.ts +135 -8
- package/src/identity/wallet/page/timeline.ts +161 -0
- package/src/identity/wallet/page/view.ts +22 -302
- package/src/storage/config.ts +30 -80
- package/src/storage/reset.ts +31 -0
- package/src/storage/secrets.ts +1 -16
- package/src/ui/Select.tsx +27 -5
- package/src/ui/Spinner.tsx +16 -15
- package/src/ui/Surface.tsx +21 -17
- package/src/ui/TextArea.tsx +173 -0
- package/src/ui/TextInput.tsx +31 -133
- package/src/ui/theme.ts +22 -13
- package/src/utils/clipboard.ts +0 -140
- package/src/app/FirstRun.tsx +0 -577
- package/src/app/FirstRunTimeline.tsx +0 -51
- package/src/app/firstRunConfig.ts +0 -26
- package/src/app/hooks/useCancelRequest.ts +0 -22
- package/src/app/hooks/useDoublePress.ts +0 -46
- package/src/app/hooks/useExitOnCtrlC.ts +0 -36
- package/src/auth/openaiOAuth/credentials.ts +0 -47
- package/src/auth/openaiOAuth/crypto.ts +0 -23
- package/src/auth/openaiOAuth/index.ts +0 -238
- package/src/auth/openaiOAuth/landingPage.ts +0 -116
- package/src/auth/openaiOAuth/listener.ts +0 -151
- package/src/auth/openaiOAuth/refresh.ts +0 -70
- package/src/auth/openaiOAuth/shared.ts +0 -115
- package/src/chat/ChatBottomPane.tsx +0 -296
- package/src/chat/ChatScreen.tsx +0 -1685
- package/src/chat/ConversationStack.tsx +0 -56
- package/src/chat/MessageList.tsx +0 -638
- package/src/chat/SessionStatus.tsx +0 -53
- package/src/chat/chatEnvironment.ts +0 -16
- package/src/chat/chatScreenUtils.ts +0 -194
- package/src/chat/chatSessionState.ts +0 -146
- package/src/chat/chatTurnContext.ts +0 -50
- package/src/chat/chatTurnOrchestrator.ts +0 -603
- package/src/chat/chatTurnRows.ts +0 -64
- package/src/chat/commands.ts +0 -494
- package/src/chat/continuityEditReview.ts +0 -42
- package/src/chat/display/DiffView.tsx +0 -193
- package/src/chat/display/SyntaxText.tsx +0 -192
- package/src/chat/display/toolCallDisplay.ts +0 -103
- package/src/chat/display/toolResultDisplay.ts +0 -19
- package/src/chat/input/ChatInput.tsx +0 -625
- package/src/chat/input/chatInputHelpers.ts +0 -62
- package/src/chat/input/chatInputState.ts +0 -247
- package/src/chat/input/chatPaste.ts +0 -49
- package/src/chat/input/imageRefs.ts +0 -30
- package/src/chat/input/inputRendering.tsx +0 -93
- package/src/chat/input/textCursor.ts +0 -212
- package/src/chat/messageMarkdown.ts +0 -220
- package/src/chat/messageRows.ts +0 -43
- package/src/chat/planImplementation.ts +0 -62
- package/src/chat/slashCommandHandlers.ts +0 -122
- package/src/chat/slashCommandViews.ts +0 -120
- package/src/chat/transcript/TranscriptView.tsx +0 -184
- package/src/chat/transcript/transcriptViewport.ts +0 -295
- package/src/chat/views/ContextLimitView.tsx +0 -95
- package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
- package/src/chat/views/CopyPicker.tsx +0 -50
- package/src/chat/views/PermissionPrompt.tsx +0 -156
- package/src/chat/views/PermissionsView.tsx +0 -165
- package/src/chat/views/PlanApprovalView.tsx +0 -91
- package/src/chat/views/ResumeView.tsx +0 -273
- package/src/chat/views/RewindView.tsx +0 -412
- package/src/cli/preview.tsx +0 -14
- package/src/cli/updateNotice.ts +0 -54
- package/src/identity/continuity/privateEdit/apply.ts +0 -170
- package/src/identity/continuity/privateEdit/diff.ts +0 -6
- package/src/identity/continuity/privateEdit/files.ts +0 -23
- package/src/identity/continuity/privateEdit/types.ts +0 -28
- package/src/identity/continuity/privateEdit.ts +0 -46
- package/src/identity/hub/IdentityHub.tsx +0 -14
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
- package/src/identity/hub/ens/effects.ts +0 -218
- package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
- package/src/identity/hub/shared/effects/types.ts +0 -53
- package/src/identity/hub/shared/reconciliation/index.ts +0 -14
- package/src/identity/wallet/page/grainient.ts +0 -278
- package/src/identity/wallet/page/html.ts +0 -28
- package/src/identity/wallet/page/styles/base.ts +0 -259
- package/src/identity/wallet/page/styles/components.ts +0 -262
- package/src/identity/wallet/page/styles/index.ts +0 -5
- package/src/identity/wallet/page/styles/responsive.ts +0 -247
- package/src/identity/wallet/page.tsx +0 -38
- package/src/mcp/approvals.ts +0 -113
- package/src/mcp/config.ts +0 -235
- package/src/mcp/manager.ts +0 -482
- package/src/mcp/managerHelpers.ts +0 -70
- package/src/mcp/names.ts +0 -19
- package/src/mcp/output.ts +0 -96
- package/src/models/ModelPicker.tsx +0 -1009
- package/src/models/catalog.ts +0 -327
- package/src/models/huggingface.ts +0 -712
- package/src/models/huggingfaceStorage.ts +0 -136
- package/src/models/llamacpp.ts +0 -848
- package/src/models/llamacppCommands.ts +0 -44
- package/src/models/llamacppConfig.ts +0 -34
- package/src/models/llamacppDiscovery.ts +0 -176
- package/src/models/llamacppOutput.ts +0 -65
- package/src/models/llamacppPreflight.ts +0 -158
- package/src/models/modelDisplay.ts +0 -180
- package/src/models/modelPickerCatalogFlow.ts +0 -56
- package/src/models/modelPickerCredentials.ts +0 -166
- package/src/models/modelPickerData.ts +0 -41
- package/src/models/modelPickerDisplay.tsx +0 -132
- package/src/models/modelPickerHfFlow.ts +0 -192
- package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
- package/src/models/modelPickerOptions.ts +0 -457
- package/src/models/modelPickerTypes.ts +0 -69
- package/src/models/modelPickerUninstallFlow.ts +0 -48
- package/src/models/modelPickerViewHelpers.ts +0 -174
- package/src/models/modelRecommendation.ts +0 -139
- package/src/models/providerDisplay.ts +0 -16
- package/src/models/runtimeDetection.ts +0 -81
- package/src/models/uncensoredCatalog.ts +0 -86
- package/src/providers/anthropic.ts +0 -290
- package/src/providers/contracts.ts +0 -71
- package/src/providers/errors.ts +0 -80
- package/src/providers/gemini.ts +0 -391
- package/src/providers/openai-chat.ts +0 -474
- package/src/providers/openai-responses-format.ts +0 -177
- package/src/providers/openai-responses.ts +0 -306
- package/src/providers/openaiChatWire.ts +0 -124
- package/src/providers/registry.ts +0 -120
- package/src/providers/retry.ts +0 -58
- package/src/providers/sse.ts +0 -93
- package/src/runtime/compaction.ts +0 -395
- package/src/runtime/cwd.ts +0 -43
- package/src/runtime/providerTurn.ts +0 -38
- package/src/runtime/sessionMode.ts +0 -55
- package/src/runtime/systemPrompt.ts +0 -213
- package/src/runtime/textToolParser.ts +0 -161
- package/src/runtime/toolClaimGuards.ts +0 -143
- package/src/runtime/toolExecution.ts +0 -304
- package/src/runtime/toolIntent.ts +0 -143
- package/src/runtime/turn.ts +0 -369
- package/src/runtime/turnNudges.ts +0 -223
- package/src/runtime/turnTypes.ts +0 -86
- package/src/storage/factoryReset.ts +0 -127
- package/src/storage/history.ts +0 -58
- package/src/storage/permissions.ts +0 -76
- package/src/storage/rewind.ts +0 -266
- package/src/storage/sessionExport.ts +0 -49
- package/src/storage/sessions.ts +0 -495
- package/src/tools/bashSafety.ts +0 -186
- package/src/tools/bashTool.ts +0 -140
- package/src/tools/changeDirectoryTool.ts +0 -213
- package/src/tools/contracts.ts +0 -192
- package/src/tools/deleteFileTool.ts +0 -116
- package/src/tools/editTool.ts +0 -165
- package/src/tools/editUtils.ts +0 -170
- package/src/tools/fileDiff.ts +0 -261
- package/src/tools/listDirectoryTool.ts +0 -55
- package/src/tools/listSkillFilesTool.ts +0 -77
- package/src/tools/listSkillsTool.ts +0 -68
- package/src/tools/mcpResourceTools.ts +0 -95
- package/src/tools/permissionRules.ts +0 -85
- package/src/tools/privateContinuityEditTool.ts +0 -187
- package/src/tools/privateContinuityReadTool.ts +0 -106
- package/src/tools/readSkillTool.ts +0 -107
- package/src/tools/readTool.ts +0 -85
- package/src/tools/registry.ts +0 -103
- package/src/tools/writeFileTool.ts +0 -167
- package/src/ui/BrandSplash.tsx +0 -133
- package/src/ui/terminalTitle.ts +0 -30
- package/src/utils/images.ts +0 -140
- package/src/utils/markdownSegments.ts +0 -51
- package/src/utils/messages.ts +0 -37
- package/src/utils/withRetry.ts +0 -324
- /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
- /package/src/identity/{hub → manager}/custody/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
- /package/src/identity/{hub → manager}/ens/state.ts +0 -0
- /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
- /package/src/identity/{hub → manager}/ens/types.ts +0 -0
- /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
- /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
- /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
- /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
- /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
- /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
- /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
- /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
- /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
package/src/utils/images.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import type { ImageBlock, Message, MessageContentBlock } from '../providers/contracts.js'
|
|
4
|
-
|
|
5
|
-
const IMAGE_MARKER_RE = /\[image:\s*([^\]]+?)\]/gi
|
|
6
|
-
const PLACEHOLDER_RE = /^([<{[].*[>}\]]|#\d+)$/
|
|
7
|
-
|
|
8
|
-
export class ImageLoadError extends Error {
|
|
9
|
-
readonly imagePath: string
|
|
10
|
-
constructor(imagePath: string, message: string) {
|
|
11
|
-
super(message)
|
|
12
|
-
this.name = 'ImageLoadError'
|
|
13
|
-
this.imagePath = imagePath
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function collapseImagePathsToRefs(text: string): string {
|
|
18
|
-
let counter = 0
|
|
19
|
-
return text.replace(IMAGE_MARKER_RE, (full, raw: string) => {
|
|
20
|
-
const trimmed = raw.trim()
|
|
21
|
-
if (!trimmed || PLACEHOLDER_RE.test(trimmed)) return full
|
|
22
|
-
counter += 1
|
|
23
|
-
return `[Image #${counter}]`
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function modelSupportsImages(
|
|
28
|
-
provider: string,
|
|
29
|
-
model: string,
|
|
30
|
-
extra?: { mmprojPath?: string },
|
|
31
|
-
): boolean {
|
|
32
|
-
const normalized = model.toLowerCase()
|
|
33
|
-
switch (provider) {
|
|
34
|
-
case 'anthropic':
|
|
35
|
-
return /claude-3|claude-sonnet-4|claude-opus-4|claude-haiku-4/.test(normalized)
|
|
36
|
-
case 'gemini':
|
|
37
|
-
return /gemini-1\.5|gemini-2\.0|gemini-2\.5/.test(normalized)
|
|
38
|
-
case 'openai':
|
|
39
|
-
if (normalized.includes('gpt-3.5')) return false
|
|
40
|
-
return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-4-vision|gpt-5|o1|o3|o4|chatgpt-4/.test(normalized)
|
|
41
|
-
case 'llamacpp':
|
|
42
|
-
return Boolean(extra?.mmprojPath)
|
|
43
|
-
default:
|
|
44
|
-
return false
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function hasImageBlocks(messages: Message[]): boolean {
|
|
49
|
-
return messages.some(message => Array.isArray(message.content) && message.content.some(block => block.type === 'image'))
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function userTextToContentBlocks(text: string): string | MessageContentBlock[] {
|
|
53
|
-
const blocks = parseImageMarkers(text)
|
|
54
|
-
return blocks.length === 1 && blocks[0]?.type === 'text' ? blocks[0].text : blocks
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function parseImageMarkers(text: string): MessageContentBlock[] {
|
|
58
|
-
const out: MessageContentBlock[] = []
|
|
59
|
-
let lastIndex = 0
|
|
60
|
-
let match: RegExpExecArray | null
|
|
61
|
-
|
|
62
|
-
while ((match = IMAGE_MARKER_RE.exec(text)) !== null) {
|
|
63
|
-
const full = match[0]
|
|
64
|
-
const rawPath = match[1]?.trim() ?? ''
|
|
65
|
-
if (match.index > lastIndex) {
|
|
66
|
-
const prefix = text.slice(lastIndex, match.index)
|
|
67
|
-
if (prefix) out.push({ type: 'text', text: prefix })
|
|
68
|
-
}
|
|
69
|
-
if (rawPath && !PLACEHOLDER_RE.test(rawPath)) {
|
|
70
|
-
out.push({ type: 'image', path: rawPath })
|
|
71
|
-
} else {
|
|
72
|
-
out.push({ type: 'text', text: full })
|
|
73
|
-
}
|
|
74
|
-
lastIndex = match.index + full.length
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (lastIndex < text.length) {
|
|
78
|
-
const suffix = text.slice(lastIndex)
|
|
79
|
-
if (suffix) out.push({ type: 'text', text: suffix })
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (out.length === 0) return text ? [{ type: 'text', text }] : []
|
|
83
|
-
return mergeAdjacentTextBlocks(out)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function loadImageBlock(block: ImageBlock): Promise<ImageBlock> {
|
|
87
|
-
if (block.dataBase64 && block.mimeType) return block
|
|
88
|
-
if (block.url) return block
|
|
89
|
-
const rawPath = block.path?.trim() ?? ''
|
|
90
|
-
if (!rawPath) throw new ImageLoadError(rawPath, 'image path is empty')
|
|
91
|
-
if (PLACEHOLDER_RE.test(rawPath)) {
|
|
92
|
-
throw new ImageLoadError(rawPath, `image path looks like a placeholder, not a real file: ${rawPath}`)
|
|
93
|
-
}
|
|
94
|
-
let file: Buffer
|
|
95
|
-
try {
|
|
96
|
-
file = await fs.readFile(rawPath)
|
|
97
|
-
} catch (err: unknown) {
|
|
98
|
-
const code = (err as NodeJS.ErrnoException).code
|
|
99
|
-
if (code === 'ENOENT') {
|
|
100
|
-
throw new ImageLoadError(rawPath, `image file not found: ${rawPath}`)
|
|
101
|
-
}
|
|
102
|
-
throw new ImageLoadError(rawPath, `could not read image at ${rawPath}: ${(err as Error).message}`)
|
|
103
|
-
}
|
|
104
|
-
const mimeType = block.mimeType ?? mimeTypeForPath(rawPath)
|
|
105
|
-
return {
|
|
106
|
-
...block,
|
|
107
|
-
path: rawPath,
|
|
108
|
-
mimeType,
|
|
109
|
-
dataBase64: file.toString('base64'),
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function imagePlaceholder(pathValue: string): string {
|
|
114
|
-
return `[image: ${path.basename(pathValue)}]`
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function mergeAdjacentTextBlocks(blocks: MessageContentBlock[]): MessageContentBlock[] {
|
|
118
|
-
const out: MessageContentBlock[] = []
|
|
119
|
-
for (const block of blocks) {
|
|
120
|
-
const prev = out[out.length - 1]
|
|
121
|
-
if (block.type === 'text' && prev?.type === 'text') {
|
|
122
|
-
prev.text += block.text
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
out.push(block)
|
|
126
|
-
}
|
|
127
|
-
return out
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function mimeTypeForPath(filePath: string): string {
|
|
131
|
-
switch (path.extname(filePath).toLowerCase()) {
|
|
132
|
-
case '.png': return 'image/png'
|
|
133
|
-
case '.jpg':
|
|
134
|
-
case '.jpeg': return 'image/jpeg'
|
|
135
|
-
case '.webp': return 'image/webp'
|
|
136
|
-
case '.gif': return 'image/gif'
|
|
137
|
-
case '.bmp': return 'image/bmp'
|
|
138
|
-
default: return 'application/octet-stream'
|
|
139
|
-
}
|
|
140
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
export type Segment =
|
|
2
|
-
| { kind: 'text'; content: string; preview: string }
|
|
3
|
-
| { kind: 'code'; lang: string | null; content: string; preview: string }
|
|
4
|
-
|
|
5
|
-
const FENCE = /^[ \t]{0,3}```([\w+-]*)[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}```[ \t]*(?:\r?\n|$)/gm
|
|
6
|
-
|
|
7
|
-
export function parseSegments(markdown: string): Segment[] {
|
|
8
|
-
if (!markdown) return []
|
|
9
|
-
const segments: Segment[] = []
|
|
10
|
-
let lastIndex = 0
|
|
11
|
-
FENCE.lastIndex = 0
|
|
12
|
-
let match: RegExpExecArray | null
|
|
13
|
-
while ((match = FENCE.exec(markdown)) !== null) {
|
|
14
|
-
const before = markdown.slice(lastIndex, match.index)
|
|
15
|
-
const textSeg = toTextSegment(before)
|
|
16
|
-
if (textSeg) segments.push(textSeg)
|
|
17
|
-
const lang = match[1] && match[1].length > 0 ? match[1] : null
|
|
18
|
-
const body = match[2] ?? ''
|
|
19
|
-
segments.push({
|
|
20
|
-
kind: 'code',
|
|
21
|
-
lang,
|
|
22
|
-
content: body,
|
|
23
|
-
preview: codePreview(lang, body),
|
|
24
|
-
})
|
|
25
|
-
lastIndex = match.index + match[0].length
|
|
26
|
-
}
|
|
27
|
-
const tail = markdown.slice(lastIndex)
|
|
28
|
-
const tailSeg = toTextSegment(tail)
|
|
29
|
-
if (tailSeg) segments.push(tailSeg)
|
|
30
|
-
return segments
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function toTextSegment(raw: string): Segment | null {
|
|
34
|
-
const cleaned = raw.replace(/\r\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim()
|
|
35
|
-
if (!cleaned) return null
|
|
36
|
-
return { kind: 'text', content: cleaned, preview: textPreview(cleaned) }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function textPreview(text: string): string {
|
|
40
|
-
const firstLine = text.split('\n').find(line => line.trim().length > 0) ?? text
|
|
41
|
-
const trimmed = firstLine.trim()
|
|
42
|
-
const chars = text.length
|
|
43
|
-
const snippet = trimmed.length > 48 ? trimmed.slice(0, 47) + '…' : trimmed
|
|
44
|
-
return `text · ${chars} chars · ${snippet}`
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function codePreview(lang: string | null, body: string): string {
|
|
48
|
-
const lineCount = body.length === 0 ? 0 : body.split('\n').length
|
|
49
|
-
const label = lang ?? 'code'
|
|
50
|
-
return `code · ${label} · ${lineCount} line${lineCount === 1 ? '' : 's'}`
|
|
51
|
-
}
|
package/src/utils/messages.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
import type { Message, MessageContentBlock } from '../providers/contracts.js'
|
|
3
|
-
|
|
4
|
-
export function systemMessage(content: string): Message {
|
|
5
|
-
return { role: 'system', content }
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function userMessage(content: string | MessageContentBlock[]): Message {
|
|
9
|
-
return { role: 'user', content }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function assistantMessage(content: string | MessageContentBlock[]): Message {
|
|
13
|
-
return { role: 'assistant', content }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function messageTextContent(message: Message): string {
|
|
17
|
-
return typeof message.content === 'string' ? message.content : blocksToText(message.content)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function blocksToText(blocks: MessageContentBlock[]): string {
|
|
21
|
-
return blocks
|
|
22
|
-
.map(block => {
|
|
23
|
-
if (block.type === 'text') return block.text
|
|
24
|
-
if (block.type === 'image') return `[image attached: ${path.basename(block.path)}]`
|
|
25
|
-
if (block.type === 'tool_use') return `[tool use: ${block.name}]`
|
|
26
|
-
return block.isError
|
|
27
|
-
? `[tool error: ${block.content}]`
|
|
28
|
-
: `[tool result: ${block.content}]`
|
|
29
|
-
})
|
|
30
|
-
.join('\n')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function approximateTokens(messages: Message[]): number {
|
|
34
|
-
let chars = 0
|
|
35
|
-
for (const m of messages) chars += messageTextContent(m).length
|
|
36
|
-
return Math.ceil(chars / 3)
|
|
37
|
-
}
|
package/src/utils/withRetry.ts
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
export type RetryClassification = {
|
|
2
|
-
retryable: boolean
|
|
3
|
-
retryAfterMs?: number
|
|
4
|
-
reason: string
|
|
5
|
-
status?: number
|
|
6
|
-
code?: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type RetryBodyHint = {
|
|
10
|
-
retryAfterMs?: number
|
|
11
|
-
fatal?: boolean
|
|
12
|
-
reason?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type RetryPolicy = {
|
|
16
|
-
maxRetries: number
|
|
17
|
-
baseDelayMs: number
|
|
18
|
-
maxDelayMs: number
|
|
19
|
-
retryAfterCapMs: number
|
|
20
|
-
jitterRatio: number
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type RetryEvent = {
|
|
24
|
-
attempt: number
|
|
25
|
-
nextAttempt: number
|
|
26
|
-
maxRetries: number
|
|
27
|
-
delayMs: number
|
|
28
|
-
reason: string
|
|
29
|
-
retryAfterMs?: number
|
|
30
|
-
status?: number
|
|
31
|
-
code?: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const RETRYABLE_NET_CODES = new Set([
|
|
35
|
-
'ECONNRESET',
|
|
36
|
-
'EPIPE',
|
|
37
|
-
'ETIMEDOUT',
|
|
38
|
-
'ECONNREFUSED',
|
|
39
|
-
'EHOSTUNREACH',
|
|
40
|
-
'ENETUNREACH',
|
|
41
|
-
'EAI_AGAIN',
|
|
42
|
-
])
|
|
43
|
-
|
|
44
|
-
const RETRYABLE_STATUS = new Set([408, 409, 425, 429, 500, 502, 503, 504, 529])
|
|
45
|
-
const DEFAULT_BASE_DELAY_MS = 500
|
|
46
|
-
const DEFAULT_MAX_DELAY_MS = 32_000
|
|
47
|
-
const DEFAULT_JITTER_RATIO = 0.25
|
|
48
|
-
const DEFAULT_MAX_RETRIES = 4
|
|
49
|
-
|
|
50
|
-
export const DEFAULT_RETRY_POLICY: RetryPolicy = {
|
|
51
|
-
maxRetries: DEFAULT_MAX_RETRIES,
|
|
52
|
-
baseDelayMs: DEFAULT_BASE_DELAY_MS,
|
|
53
|
-
maxDelayMs: DEFAULT_MAX_DELAY_MS,
|
|
54
|
-
retryAfterCapMs: DEFAULT_MAX_DELAY_MS * 4,
|
|
55
|
-
jitterRatio: DEFAULT_JITTER_RATIO,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function classifyRetryableFetchError(err: unknown): RetryClassification {
|
|
59
|
-
if (!err || typeof err !== 'object') return { retryable: false, reason: 'unknown error' }
|
|
60
|
-
const code = (err as { code?: unknown }).code
|
|
61
|
-
if (typeof code === 'string' && RETRYABLE_NET_CODES.has(code)) {
|
|
62
|
-
return { retryable: true, reason: code, code }
|
|
63
|
-
}
|
|
64
|
-
const cause = (err as { cause?: unknown }).cause
|
|
65
|
-
if (cause && typeof cause === 'object') {
|
|
66
|
-
const causeCode = (cause as { code?: unknown }).code
|
|
67
|
-
if (typeof causeCode === 'string' && RETRYABLE_NET_CODES.has(causeCode)) {
|
|
68
|
-
return { retryable: true, reason: causeCode, code: causeCode }
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const message = (err as { message?: unknown }).message
|
|
72
|
-
if (typeof message === 'string' && /fetch failed|network|socket hang up|ECONNRESET/i.test(message)) {
|
|
73
|
-
return { retryable: true, reason: 'fetch failed' }
|
|
74
|
-
}
|
|
75
|
-
return { retryable: false, reason: 'non-retryable error' }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function classifyRetryableResponse(response: Response, nowMs: number = Date.now()): RetryClassification {
|
|
79
|
-
if (response.ok) return { retryable: false, reason: 'ok', status: response.status }
|
|
80
|
-
if (!RETRYABLE_STATUS.has(response.status)) {
|
|
81
|
-
return { retryable: false, reason: `HTTP ${response.status}`, status: response.status }
|
|
82
|
-
}
|
|
83
|
-
const retryAfter = response.headers.get('retry-after')
|
|
84
|
-
const retryAfterMs = retryAfter ? parseRetryAfter(retryAfter, nowMs) : undefined
|
|
85
|
-
return { retryable: true, retryAfterMs, reason: `HTTP ${response.status}`, status: response.status }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function classifyRetryableProviderResponse(
|
|
89
|
-
response: Response,
|
|
90
|
-
nowMs: number = Date.now(),
|
|
91
|
-
rateLimitResetProvider?: RateLimitResetProvider,
|
|
92
|
-
): RetryClassification {
|
|
93
|
-
const classification = classifyRetryableResponse(response, nowMs)
|
|
94
|
-
if (
|
|
95
|
-
!classification.retryable ||
|
|
96
|
-
classification.retryAfterMs !== undefined ||
|
|
97
|
-
response.status !== 429 ||
|
|
98
|
-
!rateLimitResetProvider
|
|
99
|
-
) {
|
|
100
|
-
return classification
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
...classification,
|
|
104
|
-
retryAfterMs: rateLimitResetDelayMs(response.headers, rateLimitResetProvider, nowMs),
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function computeBackoffMs(
|
|
109
|
-
attempt: number,
|
|
110
|
-
retryAfterMs: number | undefined,
|
|
111
|
-
maxDelayMs = DEFAULT_MAX_DELAY_MS,
|
|
112
|
-
baseDelayMs = DEFAULT_BASE_DELAY_MS,
|
|
113
|
-
rng: () => number = Math.random,
|
|
114
|
-
jitterRatio = DEFAULT_JITTER_RATIO,
|
|
115
|
-
retryAfterCapMs = maxDelayMs * 4,
|
|
116
|
-
): number {
|
|
117
|
-
if (retryAfterMs !== undefined && Number.isFinite(retryAfterMs)) {
|
|
118
|
-
return Math.min(Math.max(retryAfterMs, 0), retryAfterCapMs)
|
|
119
|
-
}
|
|
120
|
-
const expo = Math.min(baseDelayMs * Math.pow(2, attempt - 1), maxDelayMs)
|
|
121
|
-
const jitter = rng() * jitterRatio * expo
|
|
122
|
-
return Math.floor(expo + jitter)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function retryPolicyFromOptions(options: FetchWithRetryOptions = {}): RetryPolicy {
|
|
126
|
-
const maxDelayMs = options.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs
|
|
127
|
-
return {
|
|
128
|
-
maxRetries: options.maxRetries ?? DEFAULT_RETRY_POLICY.maxRetries,
|
|
129
|
-
baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
|
|
130
|
-
maxDelayMs,
|
|
131
|
-
retryAfterCapMs: options.retryAfterCapMs ?? maxDelayMs * 4,
|
|
132
|
-
jitterRatio: options.jitterRatio ?? DEFAULT_RETRY_POLICY.jitterRatio,
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function parseRetryAfter(headerValue: string, nowMs: number = Date.now()): number | undefined {
|
|
137
|
-
const trimmed = headerValue.trim()
|
|
138
|
-
if (!trimmed) return undefined
|
|
139
|
-
const seconds = Number(trimmed)
|
|
140
|
-
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000)
|
|
141
|
-
const dateMs = Date.parse(trimmed)
|
|
142
|
-
if (Number.isFinite(dateMs)) return Math.max(0, dateMs - nowMs)
|
|
143
|
-
return undefined
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function parseOpenAIRateLimitResetMs(value: string): number | undefined {
|
|
147
|
-
if (!value) return undefined
|
|
148
|
-
const match = /^(?:(\d+)h)?(?:(\d+)m(?!s))?(?:(\d+)s)?(?:(\d+)ms)?$/.exec(value.trim())
|
|
149
|
-
if (!match || match[0] === '') return undefined
|
|
150
|
-
const hours = Number.parseInt(match[1] ?? '0', 10)
|
|
151
|
-
const minutes = Number.parseInt(match[2] ?? '0', 10)
|
|
152
|
-
const seconds = Number.parseInt(match[3] ?? '0', 10)
|
|
153
|
-
const milliseconds = Number.parseInt(match[4] ?? '0', 10)
|
|
154
|
-
const total = hours * 3_600_000 + minutes * 60_000 + seconds * 1000 + milliseconds
|
|
155
|
-
return total > 0 ? total : undefined
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export type RateLimitResetProvider = 'anthropic' | 'openai-compatible'
|
|
159
|
-
|
|
160
|
-
export function rateLimitResetDelayMs(
|
|
161
|
-
headers: Headers,
|
|
162
|
-
provider: RateLimitResetProvider,
|
|
163
|
-
nowMs: number = Date.now(),
|
|
164
|
-
capMs = DEFAULT_RETRY_POLICY.retryAfterCapMs,
|
|
165
|
-
): number | undefined {
|
|
166
|
-
if (provider === 'anthropic') {
|
|
167
|
-
const reset = headers.get('anthropic-ratelimit-unified-reset')
|
|
168
|
-
if (!reset) return undefined
|
|
169
|
-
const unixSeconds = Number(reset)
|
|
170
|
-
if (!Number.isFinite(unixSeconds)) return undefined
|
|
171
|
-
const delay = unixSeconds * 1000 - nowMs
|
|
172
|
-
return delay > 0 ? Math.min(delay, capMs) : undefined
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const requestDelay = parseOpenAIRateLimitResetMs(headers.get('x-ratelimit-reset-requests') ?? '')
|
|
176
|
-
const tokenDelay = parseOpenAIRateLimitResetMs(headers.get('x-ratelimit-reset-tokens') ?? '')
|
|
177
|
-
const delay = Math.max(requestDelay ?? 0, tokenDelay ?? 0)
|
|
178
|
-
return delay > 0 ? Math.min(delay, capMs) : undefined
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export type FetchWithRetryOptions = {
|
|
182
|
-
maxRetries?: number
|
|
183
|
-
baseDelayMs?: number
|
|
184
|
-
maxDelayMs?: number
|
|
185
|
-
retryAfterCapMs?: number
|
|
186
|
-
jitterRatio?: number
|
|
187
|
-
rateLimitResetProvider?: RateLimitResetProvider
|
|
188
|
-
parseRetryHintFromBody?: (body: string) => RetryBodyHint | undefined
|
|
189
|
-
signal?: AbortSignal
|
|
190
|
-
fetchImpl?: typeof fetch
|
|
191
|
-
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>
|
|
192
|
-
rng?: () => number
|
|
193
|
-
now?: () => number
|
|
194
|
-
onRetry?: (event: RetryEvent) => void
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export async function fetchWithRetry(
|
|
198
|
-
input: string,
|
|
199
|
-
init: RequestInit,
|
|
200
|
-
options: FetchWithRetryOptions = {},
|
|
201
|
-
): Promise<Response> {
|
|
202
|
-
const policy = retryPolicyFromOptions(options)
|
|
203
|
-
const fetchImpl = options.fetchImpl ?? fetch
|
|
204
|
-
const sleepImpl = options.sleep ?? sleep
|
|
205
|
-
const rng = options.rng ?? Math.random
|
|
206
|
-
const now = options.now ?? Date.now
|
|
207
|
-
let lastError: unknown
|
|
208
|
-
for (let attempt = 1; attempt <= policy.maxRetries + 1; attempt += 1) {
|
|
209
|
-
if (options.signal?.aborted) throw new DOMException('aborted', 'AbortError')
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
const response = await fetchImpl(input, { ...init, signal: options.signal })
|
|
213
|
-
if (response.ok) return response
|
|
214
|
-
|
|
215
|
-
let classification = classifyRetryableProviderResponse(response, now(), options.rateLimitResetProvider)
|
|
216
|
-
let bufferedResponse: Response | undefined
|
|
217
|
-
if (
|
|
218
|
-
classification.retryable
|
|
219
|
-
&& response.status === 429
|
|
220
|
-
&& options.parseRetryHintFromBody
|
|
221
|
-
) {
|
|
222
|
-
let bodyText = ''
|
|
223
|
-
try { bodyText = await response.text() } catch {}
|
|
224
|
-
bufferedResponse = new Response(bodyText, {
|
|
225
|
-
status: response.status,
|
|
226
|
-
statusText: response.statusText,
|
|
227
|
-
headers: response.headers,
|
|
228
|
-
})
|
|
229
|
-
const hint = bodyText ? safeParseRetryHint(bodyText, options.parseRetryHintFromBody) : undefined
|
|
230
|
-
if (hint?.fatal) {
|
|
231
|
-
return bufferedResponse
|
|
232
|
-
}
|
|
233
|
-
if (hint?.retryAfterMs !== undefined) {
|
|
234
|
-
classification = { ...classification, retryAfterMs: hint.retryAfterMs }
|
|
235
|
-
}
|
|
236
|
-
if (hint?.reason) {
|
|
237
|
-
classification = { ...classification, reason: `${classification.reason} ${hint.reason}` }
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (!classification.retryable || attempt > policy.maxRetries) return bufferedResponse ?? response
|
|
241
|
-
|
|
242
|
-
if (!bufferedResponse) {
|
|
243
|
-
try { await response.body?.cancel() } catch {}
|
|
244
|
-
}
|
|
245
|
-
const delayMs = computeBackoffMs(
|
|
246
|
-
attempt,
|
|
247
|
-
classification.retryAfterMs,
|
|
248
|
-
policy.maxDelayMs,
|
|
249
|
-
policy.baseDelayMs,
|
|
250
|
-
rng,
|
|
251
|
-
policy.jitterRatio,
|
|
252
|
-
policy.retryAfterCapMs,
|
|
253
|
-
)
|
|
254
|
-
options.onRetry?.(retryEvent(attempt, policy.maxRetries, delayMs, classification))
|
|
255
|
-
await sleepImpl(delayMs, options.signal)
|
|
256
|
-
continue
|
|
257
|
-
} catch (err) {
|
|
258
|
-
lastError = err
|
|
259
|
-
if (options.signal?.aborted) throw err
|
|
260
|
-
const classification = classifyRetryableFetchError(err)
|
|
261
|
-
if (!classification.retryable || attempt > policy.maxRetries) throw err
|
|
262
|
-
|
|
263
|
-
const delayMs = computeBackoffMs(
|
|
264
|
-
attempt,
|
|
265
|
-
classification.retryAfterMs,
|
|
266
|
-
policy.maxDelayMs,
|
|
267
|
-
policy.baseDelayMs,
|
|
268
|
-
rng,
|
|
269
|
-
policy.jitterRatio,
|
|
270
|
-
policy.retryAfterCapMs,
|
|
271
|
-
)
|
|
272
|
-
options.onRetry?.(retryEvent(attempt, policy.maxRetries, delayMs, classification))
|
|
273
|
-
await sleepImpl(delayMs, options.signal)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
throw lastError ?? new Error('fetchWithRetry exhausted')
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function safeParseRetryHint(
|
|
280
|
-
body: string,
|
|
281
|
-
parser: (body: string) => RetryBodyHint | undefined,
|
|
282
|
-
): RetryBodyHint | undefined {
|
|
283
|
-
try {
|
|
284
|
-
return parser(body)
|
|
285
|
-
} catch {
|
|
286
|
-
return undefined
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function retryEvent(
|
|
291
|
-
attempt: number,
|
|
292
|
-
maxRetries: number,
|
|
293
|
-
delayMs: number,
|
|
294
|
-
classification: RetryClassification,
|
|
295
|
-
): RetryEvent {
|
|
296
|
-
return {
|
|
297
|
-
attempt,
|
|
298
|
-
nextAttempt: attempt + 1,
|
|
299
|
-
maxRetries,
|
|
300
|
-
delayMs,
|
|
301
|
-
reason: classification.reason,
|
|
302
|
-
...(classification.retryAfterMs !== undefined ? { retryAfterMs: classification.retryAfterMs } : {}),
|
|
303
|
-
...(classification.status !== undefined ? { status: classification.status } : {}),
|
|
304
|
-
...(classification.code !== undefined ? { code: classification.code } : {}),
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
309
|
-
return new Promise((resolve, reject) => {
|
|
310
|
-
if (signal?.aborted) {
|
|
311
|
-
reject(new DOMException('aborted', 'AbortError'))
|
|
312
|
-
return
|
|
313
|
-
}
|
|
314
|
-
const timer = setTimeout(() => {
|
|
315
|
-
signal?.removeEventListener('abort', onAbort)
|
|
316
|
-
resolve()
|
|
317
|
-
}, ms)
|
|
318
|
-
const onAbort = () => {
|
|
319
|
-
clearTimeout(timer)
|
|
320
|
-
reject(new DOMException('aborted', 'AbortError'))
|
|
321
|
-
}
|
|
322
|
-
signal?.addEventListener('abort', onAbort, { once: true })
|
|
323
|
-
})
|
|
324
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|