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.
Files changed (322) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/.claude-plugin/plugin.json +35 -0
  3. package/LICENSE +1 -1
  4. package/README.md +64 -104
  5. package/commands/ethagent.md +40 -0
  6. package/package.json +16 -16
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -6
  8. package/src/app/keybindings/types.ts +1 -6
  9. package/src/cli/ResetConfirmView.tsx +54 -53
  10. package/src/cli/demo.ts +86 -0
  11. package/src/cli/hookIo.ts +45 -0
  12. package/src/cli/main.tsx +94 -123
  13. package/src/cli/memoryGuard.ts +49 -0
  14. package/src/cli/reset.ts +28 -70
  15. package/src/cli/sessionStart.ts +33 -0
  16. package/src/cli/status.ts +46 -0
  17. package/src/cli/sync.ts +167 -0
  18. package/src/cli/syncAdapters/claude-code.ts +86 -0
  19. package/src/cli/syncAdapters/codex.ts +66 -0
  20. package/src/cli/syncAdapters/index.ts +45 -0
  21. package/src/cli/syncAdapters/managedBlock.ts +175 -0
  22. package/src/cli/syncAdapters/shared.ts +63 -0
  23. package/src/identity/continuity/envelopeParse.ts +20 -1
  24. package/src/identity/continuity/publicSkills.ts +3 -1
  25. package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
  26. package/src/identity/continuity/skills/scaffold.ts +5 -2
  27. package/src/identity/continuity/snapshots.ts +12 -5
  28. package/src/identity/continuity/storage/defaults.ts +20 -19
  29. package/src/identity/continuity/storage/status.ts +1 -1
  30. package/src/identity/ens/ensLookup/constants.ts +1 -1
  31. package/src/identity/manager/IdentityManager.tsx +33 -0
  32. package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
  33. package/src/identity/{hub → manager}/Routes.tsx +48 -34
  34. package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
  35. package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
  36. package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
  37. package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
  38. package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
  39. package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
  40. package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
  41. package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
  42. package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
  43. package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
  44. package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
  45. package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
  46. package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
  47. package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
  48. package/src/identity/{hub → manager}/create/effects.ts +19 -10
  49. package/src/identity/manager/create/importScan.ts +122 -0
  50. package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
  51. package/src/identity/{hub → manager}/custody/actions.ts +1 -15
  52. package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
  53. package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
  54. package/src/identity/{hub → manager}/custody/types.ts +1 -2
  55. package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
  56. package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
  57. package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
  58. package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
  59. package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
  60. package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
  61. package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
  62. package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
  63. package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
  64. package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
  65. package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
  66. package/src/identity/{hub → manager}/profile/effects.ts +1 -3
  67. package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
  68. package/src/identity/{hub → manager}/profile/state.ts +1 -1
  69. package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
  70. package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
  71. package/src/identity/{hub → manager}/restore/apply.ts +1 -1
  72. package/src/identity/{hub → manager}/restore/auth.ts +1 -1
  73. package/src/identity/{hub → manager}/restore/discover.ts +1 -1
  74. package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
  75. package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
  76. package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
  77. package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
  78. package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
  79. package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
  80. package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
  81. package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
  82. package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
  83. package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
  84. package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
  85. package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
  86. package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
  87. package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
  88. package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
  89. package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
  90. package/src/identity/manager/shared/effects/types.ts +30 -0
  91. package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
  92. package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
  93. package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
  94. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
  95. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
  96. package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
  97. package/src/identity/{hub → manager}/shared/utils.ts +6 -10
  98. package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
  99. package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
  100. package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
  101. package/src/identity/{hub → manager}/types.ts +5 -6
  102. package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
  103. package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
  104. package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
  105. package/src/identity/registry/erc8004/discovery.ts +3 -17
  106. package/src/identity/registry/erc8004/utils.ts +1 -1
  107. package/src/identity/storage/ipfs.ts +21 -1
  108. package/src/identity/wallet/browserWallet/html.ts +10 -2
  109. package/src/identity/wallet/browserWallet/http.ts +18 -0
  110. package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
  111. package/src/identity/wallet/browserWallet/requests.ts +10 -28
  112. package/src/identity/wallet/browserWallet/session.ts +26 -33
  113. package/src/identity/wallet/browserWallet/validation.ts +14 -0
  114. package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
  115. package/src/identity/wallet/page/boot.ts +43 -0
  116. package/src/identity/wallet/page/config.ts +59 -0
  117. package/src/identity/wallet/page/constants.ts +12 -0
  118. package/src/identity/wallet/page/copy.ts +47 -68
  119. package/src/identity/wallet/page/css.ts +638 -0
  120. package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
  121. package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
  122. package/src/identity/wallet/page/markup.ts +44 -34
  123. package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
  124. package/src/identity/wallet/page/resize.ts +95 -0
  125. package/src/identity/wallet/page/state.ts +135 -8
  126. package/src/identity/wallet/page/timeline.ts +161 -0
  127. package/src/identity/wallet/page/view.ts +22 -302
  128. package/src/storage/config.ts +30 -80
  129. package/src/storage/reset.ts +31 -0
  130. package/src/storage/secrets.ts +1 -16
  131. package/src/ui/Select.tsx +27 -5
  132. package/src/ui/Spinner.tsx +16 -15
  133. package/src/ui/Surface.tsx +21 -17
  134. package/src/ui/TextArea.tsx +173 -0
  135. package/src/ui/TextInput.tsx +31 -133
  136. package/src/ui/theme.ts +22 -13
  137. package/src/utils/clipboard.ts +0 -140
  138. package/src/app/FirstRun.tsx +0 -577
  139. package/src/app/FirstRunTimeline.tsx +0 -51
  140. package/src/app/firstRunConfig.ts +0 -26
  141. package/src/app/hooks/useCancelRequest.ts +0 -22
  142. package/src/app/hooks/useDoublePress.ts +0 -46
  143. package/src/app/hooks/useExitOnCtrlC.ts +0 -36
  144. package/src/auth/openaiOAuth/credentials.ts +0 -47
  145. package/src/auth/openaiOAuth/crypto.ts +0 -23
  146. package/src/auth/openaiOAuth/index.ts +0 -238
  147. package/src/auth/openaiOAuth/landingPage.ts +0 -116
  148. package/src/auth/openaiOAuth/listener.ts +0 -151
  149. package/src/auth/openaiOAuth/refresh.ts +0 -70
  150. package/src/auth/openaiOAuth/shared.ts +0 -115
  151. package/src/chat/ChatBottomPane.tsx +0 -296
  152. package/src/chat/ChatScreen.tsx +0 -1685
  153. package/src/chat/ConversationStack.tsx +0 -56
  154. package/src/chat/MessageList.tsx +0 -638
  155. package/src/chat/SessionStatus.tsx +0 -53
  156. package/src/chat/chatEnvironment.ts +0 -16
  157. package/src/chat/chatScreenUtils.ts +0 -194
  158. package/src/chat/chatSessionState.ts +0 -146
  159. package/src/chat/chatTurnContext.ts +0 -50
  160. package/src/chat/chatTurnOrchestrator.ts +0 -603
  161. package/src/chat/chatTurnRows.ts +0 -64
  162. package/src/chat/commands.ts +0 -494
  163. package/src/chat/continuityEditReview.ts +0 -42
  164. package/src/chat/display/DiffView.tsx +0 -193
  165. package/src/chat/display/SyntaxText.tsx +0 -192
  166. package/src/chat/display/toolCallDisplay.ts +0 -103
  167. package/src/chat/display/toolResultDisplay.ts +0 -19
  168. package/src/chat/input/ChatInput.tsx +0 -625
  169. package/src/chat/input/chatInputHelpers.ts +0 -62
  170. package/src/chat/input/chatInputState.ts +0 -247
  171. package/src/chat/input/chatPaste.ts +0 -49
  172. package/src/chat/input/imageRefs.ts +0 -30
  173. package/src/chat/input/inputRendering.tsx +0 -93
  174. package/src/chat/input/textCursor.ts +0 -212
  175. package/src/chat/messageMarkdown.ts +0 -220
  176. package/src/chat/messageRows.ts +0 -43
  177. package/src/chat/planImplementation.ts +0 -62
  178. package/src/chat/slashCommandHandlers.ts +0 -122
  179. package/src/chat/slashCommandViews.ts +0 -120
  180. package/src/chat/transcript/TranscriptView.tsx +0 -184
  181. package/src/chat/transcript/transcriptViewport.ts +0 -295
  182. package/src/chat/views/ContextLimitView.tsx +0 -95
  183. package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
  184. package/src/chat/views/CopyPicker.tsx +0 -50
  185. package/src/chat/views/PermissionPrompt.tsx +0 -156
  186. package/src/chat/views/PermissionsView.tsx +0 -165
  187. package/src/chat/views/PlanApprovalView.tsx +0 -91
  188. package/src/chat/views/ResumeView.tsx +0 -273
  189. package/src/chat/views/RewindView.tsx +0 -412
  190. package/src/cli/preview.tsx +0 -14
  191. package/src/cli/updateNotice.ts +0 -54
  192. package/src/identity/continuity/privateEdit/apply.ts +0 -170
  193. package/src/identity/continuity/privateEdit/diff.ts +0 -6
  194. package/src/identity/continuity/privateEdit/files.ts +0 -23
  195. package/src/identity/continuity/privateEdit/types.ts +0 -28
  196. package/src/identity/continuity/privateEdit.ts +0 -46
  197. package/src/identity/hub/IdentityHub.tsx +0 -14
  198. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
  199. package/src/identity/hub/ens/effects.ts +0 -218
  200. package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
  201. package/src/identity/hub/shared/effects/types.ts +0 -53
  202. package/src/identity/hub/shared/reconciliation/index.ts +0 -14
  203. package/src/identity/wallet/page/grainient.ts +0 -278
  204. package/src/identity/wallet/page/html.ts +0 -28
  205. package/src/identity/wallet/page/styles/base.ts +0 -259
  206. package/src/identity/wallet/page/styles/components.ts +0 -262
  207. package/src/identity/wallet/page/styles/index.ts +0 -5
  208. package/src/identity/wallet/page/styles/responsive.ts +0 -247
  209. package/src/identity/wallet/page.tsx +0 -38
  210. package/src/mcp/approvals.ts +0 -113
  211. package/src/mcp/config.ts +0 -235
  212. package/src/mcp/manager.ts +0 -482
  213. package/src/mcp/managerHelpers.ts +0 -70
  214. package/src/mcp/names.ts +0 -19
  215. package/src/mcp/output.ts +0 -96
  216. package/src/models/ModelPicker.tsx +0 -1009
  217. package/src/models/catalog.ts +0 -327
  218. package/src/models/huggingface.ts +0 -712
  219. package/src/models/huggingfaceStorage.ts +0 -136
  220. package/src/models/llamacpp.ts +0 -848
  221. package/src/models/llamacppCommands.ts +0 -44
  222. package/src/models/llamacppConfig.ts +0 -34
  223. package/src/models/llamacppDiscovery.ts +0 -176
  224. package/src/models/llamacppOutput.ts +0 -65
  225. package/src/models/llamacppPreflight.ts +0 -158
  226. package/src/models/modelDisplay.ts +0 -180
  227. package/src/models/modelPickerCatalogFlow.ts +0 -56
  228. package/src/models/modelPickerCredentials.ts +0 -166
  229. package/src/models/modelPickerData.ts +0 -41
  230. package/src/models/modelPickerDisplay.tsx +0 -132
  231. package/src/models/modelPickerHfFlow.ts +0 -192
  232. package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
  233. package/src/models/modelPickerOptions.ts +0 -457
  234. package/src/models/modelPickerTypes.ts +0 -69
  235. package/src/models/modelPickerUninstallFlow.ts +0 -48
  236. package/src/models/modelPickerViewHelpers.ts +0 -174
  237. package/src/models/modelRecommendation.ts +0 -139
  238. package/src/models/providerDisplay.ts +0 -16
  239. package/src/models/runtimeDetection.ts +0 -81
  240. package/src/models/uncensoredCatalog.ts +0 -86
  241. package/src/providers/anthropic.ts +0 -290
  242. package/src/providers/contracts.ts +0 -71
  243. package/src/providers/errors.ts +0 -80
  244. package/src/providers/gemini.ts +0 -391
  245. package/src/providers/openai-chat.ts +0 -474
  246. package/src/providers/openai-responses-format.ts +0 -177
  247. package/src/providers/openai-responses.ts +0 -306
  248. package/src/providers/openaiChatWire.ts +0 -124
  249. package/src/providers/registry.ts +0 -120
  250. package/src/providers/retry.ts +0 -58
  251. package/src/providers/sse.ts +0 -93
  252. package/src/runtime/compaction.ts +0 -395
  253. package/src/runtime/cwd.ts +0 -43
  254. package/src/runtime/providerTurn.ts +0 -38
  255. package/src/runtime/sessionMode.ts +0 -55
  256. package/src/runtime/systemPrompt.ts +0 -213
  257. package/src/runtime/textToolParser.ts +0 -161
  258. package/src/runtime/toolClaimGuards.ts +0 -143
  259. package/src/runtime/toolExecution.ts +0 -304
  260. package/src/runtime/toolIntent.ts +0 -143
  261. package/src/runtime/turn.ts +0 -369
  262. package/src/runtime/turnNudges.ts +0 -223
  263. package/src/runtime/turnTypes.ts +0 -86
  264. package/src/storage/factoryReset.ts +0 -127
  265. package/src/storage/history.ts +0 -58
  266. package/src/storage/permissions.ts +0 -76
  267. package/src/storage/rewind.ts +0 -266
  268. package/src/storage/sessionExport.ts +0 -49
  269. package/src/storage/sessions.ts +0 -495
  270. package/src/tools/bashSafety.ts +0 -186
  271. package/src/tools/bashTool.ts +0 -140
  272. package/src/tools/changeDirectoryTool.ts +0 -213
  273. package/src/tools/contracts.ts +0 -192
  274. package/src/tools/deleteFileTool.ts +0 -116
  275. package/src/tools/editTool.ts +0 -165
  276. package/src/tools/editUtils.ts +0 -170
  277. package/src/tools/fileDiff.ts +0 -261
  278. package/src/tools/listDirectoryTool.ts +0 -55
  279. package/src/tools/listSkillFilesTool.ts +0 -77
  280. package/src/tools/listSkillsTool.ts +0 -68
  281. package/src/tools/mcpResourceTools.ts +0 -95
  282. package/src/tools/permissionRules.ts +0 -85
  283. package/src/tools/privateContinuityEditTool.ts +0 -187
  284. package/src/tools/privateContinuityReadTool.ts +0 -106
  285. package/src/tools/readSkillTool.ts +0 -107
  286. package/src/tools/readTool.ts +0 -85
  287. package/src/tools/registry.ts +0 -103
  288. package/src/tools/writeFileTool.ts +0 -167
  289. package/src/ui/BrandSplash.tsx +0 -133
  290. package/src/ui/terminalTitle.ts +0 -30
  291. package/src/utils/images.ts +0 -140
  292. package/src/utils/markdownSegments.ts +0 -51
  293. package/src/utils/messages.ts +0 -37
  294. package/src/utils/withRetry.ts +0 -324
  295. /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
  296. /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
  297. /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
  298. /package/src/identity/{hub → manager}/custody/state.ts +0 -0
  299. /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
  300. /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
  301. /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
  302. /package/src/identity/{hub → manager}/ens/state.ts +0 -0
  303. /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
  304. /package/src/identity/{hub → manager}/ens/types.ts +0 -0
  305. /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
  306. /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
  307. /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
  308. /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
  309. /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
  310. /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
  311. /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
  312. /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
  313. /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
  314. /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
  315. /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
  316. /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
  317. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
  318. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
  319. /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
  320. /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
  321. /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
  322. /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
@@ -1,474 +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 { iterSseFrames } from './sse.js'
7
- import { hasImageBlocks, ImageLoadError } from '../utils/images.js'
8
- import { providerDisplayName } from '../models/providerDisplay.js'
9
- import { toWireMessages } from './openaiChatWire.js'
10
-
11
- export { toWireMessages } from './openaiChatWire.js'
12
-
13
- export type OpenAIToolDefinition = {
14
- type: 'function'
15
- function: {
16
- name: string
17
- description: string
18
- parameters: {
19
- type: 'object'
20
- properties?: Record<string, unknown>
21
- required?: string[]
22
- }
23
- }
24
- }
25
-
26
- type Options = {
27
- id: ProviderId
28
- model: string
29
- baseUrl: string
30
- apiKey?: string
31
- loadApiKey?: () => Promise<string | null>
32
- tools?: OpenAIToolDefinition[]
33
- maxRetries?: number
34
- hasVisionProjector?: boolean
35
- }
36
-
37
- type ChatChunk = {
38
- choices?: Array<{
39
- delta?: {
40
- content?: string | null
41
- reasoning_content?: string | null
42
- reasoning?: string | null
43
- thinking?: string | null
44
- tool_calls?: Array<{
45
- index?: number
46
- id?: string | null
47
- type?: 'function'
48
- function?: {
49
- name?: string | null
50
- arguments?: string | null
51
- }
52
- }>
53
- }
54
- finish_reason?: string | null
55
- }>
56
- usage?: {
57
- prompt_tokens?: number
58
- completion_tokens?: number
59
- } | null
60
- }
61
-
62
- type ToolCallDelta = NonNullable<NonNullable<NonNullable<ChatChunk['choices']>[number]['delta']>['tool_calls']>[number]
63
-
64
- type StreamingToolCall = {
65
- id: string
66
- name: string
67
- inputJson: string
68
- started: boolean
69
- }
70
-
71
- const READ_TIMEOUT_MS = 45_000
72
- type DoneStopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | 'unknown'
73
-
74
- export class OpenAIChatProvider implements Provider {
75
- readonly id: ProviderId
76
- readonly model: string
77
- readonly supportsTools: boolean
78
- private readonly baseUrl: string
79
- private readonly apiKey: string
80
- private readonly loadApiKey?: () => Promise<string | null>
81
- private readonly tools: OpenAIToolDefinition[]
82
- private readonly maxRetries?: number
83
- private readonly hasVisionProjector: boolean
84
-
85
- constructor(opts: Options) {
86
- this.id = opts.id
87
- this.model = opts.model
88
- this.baseUrl = opts.baseUrl.replace(/\/+$/, '')
89
- this.apiKey = opts.apiKey ?? ''
90
- this.loadApiKey = opts.loadApiKey
91
- this.tools = opts.tools ?? []
92
- this.maxRetries = opts.maxRetries
93
- this.supportsTools = this.tools.length > 0
94
- this.hasVisionProjector = opts.hasVisionProjector ?? false
95
- }
96
-
97
- async *complete(
98
- messages: Message[],
99
- signal: AbortSignal,
100
- options: ProviderCompleteOptions = {},
101
- ): AsyncIterable<StreamEvent> {
102
- const apiKey = await this.resolveApiKey()
103
- if (!apiKey && this.id !== 'llamacpp') {
104
- const error = new ProviderError(`missing API key for ${this.id} (/doctor to verify)`)
105
- yield { type: 'error', message: error.message }
106
- return
107
- }
108
- if (hasImageBlocks(messages)) {
109
- if (this.id === 'llamacpp' && !this.hasVisionProjector) {
110
- const hint = localModelNameHintsVision(this.model)
111
- ? '; open alt+p and run "Add Vision Encoder" on this model to enable image input'
112
- : ''
113
- yield { type: 'error', message: `image input is not enabled for local model "${this.model}" (no vision projector loaded)${hint}` }
114
- return
115
- }
116
- if (this.id === 'openai' && !supportsOpenAIImages(this.model)) {
117
- yield { type: 'error', message: `image input is not enabled for ${this.model}` }
118
- return
119
- }
120
- }
121
-
122
- const headers: Record<string, string> = {
123
- 'Content-Type': 'application/json',
124
- Accept: 'text/event-stream',
125
- }
126
- if (apiKey) headers.Authorization = `Bearer ${apiKey}`
127
-
128
- let wireMessages: Array<Record<string, unknown>>
129
- try {
130
- wireMessages = await toWireMessages(messages)
131
- } catch (err: unknown) {
132
- if (err instanceof ImageLoadError) {
133
- yield { type: 'error', message: err.message }
134
- return
135
- }
136
- throw err
137
- }
138
-
139
- let response: Response
140
- try {
141
- response = yield* fetchWithRetryStreamEvents(`${this.baseUrl}/chat/completions`, {
142
- method: 'POST',
143
- headers,
144
- body: JSON.stringify({
145
- model: this.model,
146
- messages: wireMessages,
147
- tools: this.tools.length > 0 ? this.tools : undefined,
148
- tool_choice: this.tools.length > 0 ? 'auto' : undefined,
149
- stream: true,
150
- stream_options: { include_usage: true },
151
- max_tokens: options.maxTokens,
152
- }),
153
- }, { signal, maxRetries: this.maxRetries, rateLimitResetProvider: 'openai-compatible' })
154
- } catch (err: unknown) {
155
- if (signal.aborted) return
156
- const message = providerNetworkErrorMessage(this.id, this.baseUrl, err)
157
- yield { type: 'error', message }
158
- return
159
- }
160
-
161
- if (!response.ok) {
162
- const error = await providerErrorFromResponse(this.id, response)
163
- yield { type: 'error', message: error.message }
164
- return
165
- }
166
- if (!response.body) {
167
- yield { type: 'error', message: 'empty response body' }
168
- return
169
- }
170
-
171
- let inputTokens: number | undefined
172
- let outputTokens: number | undefined
173
- let stopReason: DoneStopReason = 'unknown'
174
- const toolCalls = new Map<number, StreamingToolCall>()
175
- const contentThinkingParser = new ContentThinkingParser(this.id)
176
- let reasoningPending = false
177
-
178
- try {
179
- for await (const frame of iterSseFrames(response.body, signal, READ_TIMEOUT_MS)) {
180
- if (frame === '[DONE]') break
181
- let parsed: ChatChunk
182
- try {
183
- parsed = JSON.parse(frame) as ChatChunk
184
- } catch {
185
- continue
186
- }
187
-
188
- const choice = parsed.choices?.[0]
189
- const delta = choice?.delta
190
- const text = typeof delta?.content === 'string' ? delta.content : ''
191
- const reasoning =
192
- typeof delta?.reasoning_content === 'string'
193
- ? delta.reasoning_content
194
- : typeof delta?.reasoning === 'string'
195
- ? delta.reasoning
196
- : typeof delta?.thinking === 'string'
197
- ? delta.thinking
198
- : ''
199
-
200
- if (reasoning.length > 0) {
201
- yield { type: 'thinking', delta: reasoning }
202
- reasoningPending = true
203
- }
204
- if (text.length > 0) {
205
- if (reasoningPending) {
206
- yield { type: 'thinking_end' }
207
- reasoningPending = false
208
- }
209
- for (const event of contentThinkingParser.push(text)) {
210
- yield event
211
- }
212
- }
213
-
214
- const toolCallDeltas = delta?.tool_calls ?? []
215
- if (toolCallDeltas.length > 0 && reasoningPending) {
216
- yield { type: 'thinking_end' }
217
- reasoningPending = false
218
- }
219
- for (const event of applyStreamingToolCallDelta(toolCalls, toolCallDeltas)) {
220
- yield event
221
- }
222
-
223
- if (choice?.finish_reason) {
224
- if (reasoningPending) {
225
- yield { type: 'thinking_end' }
226
- reasoningPending = false
227
- }
228
- stopReason = normalizeFinishReason(choice.finish_reason)
229
- }
230
- if (parsed.usage) {
231
- inputTokens = parsed.usage.prompt_tokens ?? inputTokens
232
- outputTokens = parsed.usage.completion_tokens ?? outputTokens
233
- }
234
- }
235
- } catch (err: unknown) {
236
- if (signal.aborted) return
237
- yield { type: 'error', message: providerNetworkErrorMessage(this.id, this.baseUrl, err, 'stream error') }
238
- return
239
- }
240
-
241
- if (signal.aborted) return
242
- for (const event of contentThinkingParser.flush()) {
243
- yield event
244
- }
245
- if (reasoningPending) {
246
- yield { type: 'thinking_end' }
247
- reasoningPending = false
248
- }
249
-
250
- let streamEmittedToolUses = 0
251
- if (stopReason === 'tool_use' || toolCalls.size > 0) {
252
- for (const [, toolCall] of [...toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
253
- if (!toolCall.name) continue
254
- streamEmittedToolUses += 1
255
- yield {
256
- type: 'tool_use_stop',
257
- id: toolCall.id,
258
- name: toolCall.name,
259
- input: parseToolArguments(toolCall.inputJson),
260
- }
261
- }
262
- }
263
-
264
- yield { type: 'done', inputTokens, outputTokens, stopReason }
265
- }
266
-
267
- private async resolveApiKey(): Promise<string> {
268
- if (this.apiKey) return this.apiKey
269
- if (!this.loadApiKey) return ''
270
- return (await this.loadApiKey()) ?? ''
271
- }
272
-
273
- }
274
-
275
- export function supportsOpenAIImages(model: string): boolean {
276
- const normalized = model.toLowerCase()
277
- if (normalized.includes('gpt-3.5')) return false
278
- return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-4-vision|gpt-5|o1|o3|o4|chatgpt-4/.test(normalized)
279
- }
280
-
281
- export function localModelNameHintsVision(model: string): boolean {
282
- const normalized = model.toLowerCase()
283
- return /llava|bakllava|qwen[-_.]?vl|qwen2[-_.]?vl|qwen2\.5[-_.]?vl|minicpm-?v|llama-3\.2.*vision|mllama|cogvlm|internvl|moondream|pixtral|phi-?3[\.-]?vision|phi-?3\.5[\.-]?vision|smolvlm/.test(normalized)
284
- }
285
-
286
- function parseToolArguments(inputJson: string): Record<string, unknown> {
287
- if (!inputJson.trim()) return {}
288
- const direct = tryParseJsonOnce(inputJson)
289
- if (direct !== undefined) return coerceToToolArguments(direct)
290
- const repaired = repairJsonObject(inputJson)
291
- if (!repaired) return {}
292
- const parsedRepaired = tryParseJsonOnce(repaired)
293
- return parsedRepaired === undefined ? {} : coerceToToolArguments(parsedRepaired)
294
- }
295
-
296
- function tryParseJsonOnce(value: string): unknown {
297
- try {
298
- return JSON.parse(value)
299
- } catch {
300
- return undefined
301
- }
302
- }
303
-
304
- function coerceToToolArguments(value: unknown): Record<string, unknown> {
305
- if (typeof value === 'string') {
306
- const trimmed = value.trim()
307
- if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
308
- const inner = tryParseJsonOnce(trimmed)
309
- if (inner !== undefined) return coerceToToolArguments(inner)
310
- }
311
- return {}
312
- }
313
- if (value && typeof value === 'object' && !Array.isArray(value)) {
314
- return value as Record<string, unknown>
315
- }
316
- return {}
317
- }
318
-
319
- function* applyStreamingToolCallDelta(
320
- toolCalls: Map<number, StreamingToolCall>,
321
- deltas: ToolCallDelta[] | undefined,
322
- ): Iterable<StreamEvent> {
323
- for (const toolCallDelta of deltas ?? []) {
324
- const index = toolCallDelta.index ?? 0
325
- const existing = toolCalls.get(index) ?? createStreamingToolCall(index, toolCallDelta)
326
-
327
- if (toolCallDelta.id) existing.id = toolCallDelta.id
328
- if (toolCallDelta.function?.name) existing.name = toolCallDelta.function.name
329
- if (toolCallDelta.function?.arguments) {
330
- existing.inputJson += toolCallDelta.function.arguments
331
- }
332
- if (!existing.started && existing.name) {
333
- existing.started = true
334
- yield { type: 'tool_use_start', id: existing.id, name: existing.name }
335
- }
336
- if (toolCallDelta.function?.arguments) {
337
- yield { type: 'tool_use_delta', id: existing.id, delta: toolCallDelta.function.arguments }
338
- }
339
-
340
- toolCalls.set(index, existing)
341
- }
342
- }
343
-
344
- function createStreamingToolCall(
345
- index: number,
346
- delta: ToolCallDelta,
347
- ): StreamingToolCall {
348
- return {
349
- id: delta.id ?? `tool-${index}`,
350
- name: delta.function?.name ?? '',
351
- inputJson: '',
352
- started: false,
353
- }
354
- }
355
-
356
- function normalizeFinishReason(reason: string): DoneStopReason {
357
- if (reason === 'stop') return 'end_turn'
358
- if (reason === 'tool_calls') return 'tool_use'
359
- if (reason === 'length') return 'max_tokens'
360
- if (reason === 'stop_sequence') return 'stop_sequence'
361
- return 'unknown'
362
- }
363
-
364
- function providerNetworkErrorMessage(
365
- provider: ProviderId,
366
- baseUrl: string,
367
- err: unknown,
368
- fallback = 'network error',
369
- ): string {
370
- const message = (err as Error).message || fallback
371
- if (provider !== 'llamacpp') return message
372
- return `${providerDisplayName(provider)} request failed at ${baseUrl}: ${message}`
373
- }
374
-
375
- class ContentThinkingParser {
376
- private state: 'text' | 'thinking' = 'text'
377
- private buffer = ''
378
-
379
- constructor(private readonly provider: ProviderId) {}
380
-
381
- *push(delta: string): Iterable<StreamEvent> {
382
- if (!this.shouldParse()) {
383
- yield { type: 'text', delta }
384
- return
385
- }
386
-
387
- this.buffer += delta
388
- yield* this.drain(false)
389
- }
390
-
391
- *flush(): Iterable<StreamEvent> {
392
- if (!this.shouldParse() || this.buffer.length === 0) return
393
- const content = this.buffer
394
- this.buffer = ''
395
- yield { type: this.state === 'thinking' ? 'thinking' : 'text', delta: content }
396
- }
397
-
398
- private *drain(flush: boolean): Iterable<StreamEvent> {
399
- while (this.buffer.length > 0) {
400
- const tag = this.state === 'text' ? '<think>' : '</think>'
401
- const tagIndex = indexOfIgnoreCase(this.buffer, tag)
402
-
403
- if (tagIndex !== -1) {
404
- const before = this.buffer.slice(0, tagIndex)
405
- if (before.length > 0) {
406
- yield { type: this.state === 'thinking' ? 'thinking' : 'text', delta: before }
407
- }
408
- this.buffer = this.buffer.slice(tagIndex + tag.length)
409
- const wasThinking = this.state === 'thinking'
410
- this.state = this.state === 'text' ? 'thinking' : 'text'
411
- if (wasThinking) yield { type: 'thinking_end' }
412
- continue
413
- }
414
-
415
- const keep = flush ? 0 : partialTagPrefixLength(this.buffer, tag)
416
- const emit = this.buffer.slice(0, this.buffer.length - keep)
417
- this.buffer = this.buffer.slice(this.buffer.length - keep)
418
- if (emit.length > 0) {
419
- yield { type: this.state === 'thinking' ? 'thinking' : 'text', delta: emit }
420
- }
421
- return
422
- }
423
- }
424
-
425
- private shouldParse(): boolean {
426
- return this.provider === 'llamacpp'
427
- }
428
- }
429
-
430
- function indexOfIgnoreCase(value: string, search: string): number {
431
- return value.toLowerCase().indexOf(search.toLowerCase())
432
- }
433
-
434
- function partialTagPrefixLength(value: string, tag: string): number {
435
- const max = Math.min(value.length, tag.length - 1)
436
- const lowerValue = value.toLowerCase()
437
- const lowerTag = tag.toLowerCase()
438
- for (let size = max; size > 0; size -= 1) {
439
- if (lowerValue.endsWith(lowerTag.slice(0, size))) return size
440
- }
441
- return 0
442
- }
443
-
444
- function repairJsonObject(input: string): string | undefined {
445
- const start = input.indexOf('{')
446
- if (start === -1) return undefined
447
-
448
- let depth = 0
449
- let inString = false
450
- let escaped = false
451
- for (let index = start; index < input.length; index += 1) {
452
- const char = input[index]!
453
- if (escaped) {
454
- escaped = false
455
- continue
456
- }
457
- if (char === '\\') {
458
- escaped = true
459
- continue
460
- }
461
- if (char === '"') {
462
- inString = !inString
463
- continue
464
- }
465
- if (inString) continue
466
- if (char === '{') depth += 1
467
- if (char === '}') {
468
- depth -= 1
469
- if (depth === 0) return input.slice(start, index + 1)
470
- }
471
- }
472
-
473
- return depth > 0 ? `${input.slice(start)}${'}'.repeat(depth)}` : undefined
474
- }
@@ -1,177 +0,0 @@
1
- import type { Message, MessageContentBlock } from './contracts.js'
2
- import { messageTextContent } from '../utils/messages.js'
3
- import type { OpenAIToolDefinition } from './openai-chat.js'
4
- import { loadImageBlock } from '../utils/images.js'
5
-
6
- export type ResponsesInputContent =
7
- | { type: 'input_text'; text: string }
8
- | { type: 'input_image'; image_url: string }
9
- | { type: 'output_text'; text: string }
10
-
11
- export type ResponsesInputItem =
12
- | { type: 'message'; role: 'user' | 'assistant'; content: ResponsesInputContent[] }
13
- | { type: 'function_call'; id?: string; call_id: string; name: string; arguments: string }
14
- | { type: 'function_call_output'; call_id: string; output: string }
15
-
16
- export type ResponsesTool = {
17
- type: 'function'
18
- name: string
19
- description: string
20
- parameters: Record<string, unknown>
21
- }
22
-
23
- export type ResponsesRequestBody = {
24
- model: string
25
- input: ResponsesInputItem[]
26
- instructions?: string
27
- tools?: ResponsesTool[]
28
- tool_choice?: 'auto' | 'none' | 'required'
29
- parallel_tool_calls?: boolean
30
- stream: true
31
- store: false
32
- max_output_tokens?: number
33
- }
34
-
35
- export async function buildResponsesBody(args: {
36
- model: string
37
- messages: Message[]
38
- tools: OpenAIToolDefinition[]
39
- maxOutputTokens?: number
40
- }): Promise<ResponsesRequestBody> {
41
- const { instructions, items } = await splitMessages(args.messages)
42
- const body: ResponsesRequestBody = {
43
- model: args.model,
44
- input: items,
45
- stream: true,
46
- store: false,
47
- }
48
- if (instructions) body.instructions = instructions
49
- if (args.tools.length > 0) {
50
- body.tools = args.tools.map(tool => ({
51
- type: 'function' as const,
52
- name: tool.function.name,
53
- description: tool.function.description,
54
- parameters: tool.function.parameters as Record<string, unknown>,
55
- }))
56
- body.parallel_tool_calls = true
57
- body.tool_choice = 'auto'
58
- }
59
- if (args.maxOutputTokens !== undefined) {
60
- body.max_output_tokens = args.maxOutputTokens
61
- }
62
- return body
63
- }
64
-
65
- async function splitMessages(messages: Message[]): Promise<{
66
- instructions?: string
67
- items: ResponsesInputItem[]
68
- }> {
69
- const instructions: string[] = []
70
- const items: ResponsesInputItem[] = []
71
-
72
- for (const message of messages) {
73
- if (message.role === 'system') {
74
- const text = typeof message.content === 'string'
75
- ? message.content
76
- : messageTextContent(message)
77
- if (text) instructions.push(text)
78
- continue
79
- }
80
-
81
- if (message.role === 'user') {
82
- const blocks = normalizeBlocks(message.content)
83
- const toolResults = blocks.filter(isToolResultBlock)
84
- if (toolResults.length > 0) {
85
- for (const block of toolResults) {
86
- items.push({
87
- type: 'function_call_output',
88
- call_id: block.toolUseId,
89
- output: block.content,
90
- })
91
- }
92
- const remainingText = blocks
93
- .filter(isTextBlock)
94
- .map(block => block.text)
95
- .join('')
96
- if (remainingText) {
97
- items.push({
98
- type: 'message',
99
- role: 'user',
100
- content: [{ type: 'input_text', text: remainingText }],
101
- })
102
- }
103
- continue
104
- }
105
- const content = await toOpenAIResponsesUserContent(blocks)
106
- if (content.length > 0) {
107
- items.push({
108
- type: 'message',
109
- role: 'user',
110
- content,
111
- })
112
- }
113
- continue
114
- }
115
-
116
- const blocks = normalizeBlocks(message.content)
117
- const text = blocks.filter(isTextBlock).map(block => block.text).join('')
118
- if (text) {
119
- items.push({
120
- type: 'message',
121
- role: 'assistant',
122
- content: [{ type: 'output_text', text }],
123
- })
124
- }
125
- for (const block of blocks.filter(isToolUseBlock)) {
126
- items.push({
127
- type: 'function_call',
128
- call_id: block.id,
129
- name: block.name,
130
- arguments: JSON.stringify(block.input),
131
- })
132
- }
133
- }
134
-
135
- return {
136
- instructions: instructions.length > 0 ? instructions.join('\n\n') : undefined,
137
- items,
138
- }
139
- }
140
-
141
- async function toOpenAIResponsesUserContent(blocks: MessageContentBlock[]): Promise<ResponsesInputContent[]> {
142
- const content: ResponsesInputContent[] = []
143
- for (const block of blocks) {
144
- if (block.type === 'text') {
145
- if (block.text) content.push({ type: 'input_text', text: block.text })
146
- continue
147
- }
148
- if (block.type === 'image') {
149
- const loaded = await loadImageBlock(block)
150
- if (loaded.url) {
151
- content.push({ type: 'input_image', image_url: loaded.url })
152
- } else if (loaded.dataBase64 && loaded.mimeType) {
153
- content.push({ type: 'input_image', image_url: `data:${loaded.mimeType};base64,${loaded.dataBase64}` })
154
- }
155
- }
156
- }
157
- return content
158
- }
159
-
160
- function normalizeBlocks(content: Message['content']): MessageContentBlock[] {
161
- if (typeof content === 'string') {
162
- return content ? [{ type: 'text', text: content }] : []
163
- }
164
- return content
165
- }
166
-
167
- function isTextBlock(block: MessageContentBlock): block is Extract<MessageContentBlock, { type: 'text' }> {
168
- return block.type === 'text'
169
- }
170
-
171
- function isToolUseBlock(block: MessageContentBlock): block is Extract<MessageContentBlock, { type: 'tool_use' }> {
172
- return block.type === 'tool_use'
173
- }
174
-
175
- function isToolResultBlock(block: MessageContentBlock): block is Extract<MessageContentBlock, { type: 'tool_result' }> {
176
- return block.type === 'tool_result'
177
- }