ethagent 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +127 -29
- package/package.json +16 -9
- package/src/app/FirstRun.tsx +192 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +43 -18
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +11 -17
- package/src/chat/ConversationStack.tsx +3 -0
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/TranscriptView.tsx +6 -0
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +5 -3
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -815
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +25 -43
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +166 -101
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +21 -9
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +3 -3
- package/src/ui/TextInput.tsx +191 -29
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -291
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
package/src/providers/gemini.ts
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import { getKey } from '../storage/secrets.js'
|
|
2
|
-
import type { Message, Provider, ProviderCompleteOptions, StreamEvent } from './contracts.js'
|
|
2
|
+
import type { Message, MessageContentBlock, Provider, ProviderCompleteOptions, StreamEvent } from './contracts.js'
|
|
3
3
|
import { ProviderError } from './contracts.js'
|
|
4
4
|
import { providerErrorFromResponse } from './errors.js'
|
|
5
5
|
import { fetchWithRetryStreamEvents } from './retry.js'
|
|
6
6
|
import { iterSseFrames } from './sse.js'
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
export type GeminiToolDefinition = {
|
|
9
|
+
name: string
|
|
10
|
+
description: string
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object'
|
|
13
|
+
properties?: Record<string, unknown>
|
|
14
|
+
required?: string[]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type GeminiPart = {
|
|
19
|
+
text?: string
|
|
20
|
+
functionCall?: {
|
|
21
|
+
name?: string
|
|
22
|
+
args?: Record<string, unknown>
|
|
23
|
+
}
|
|
24
|
+
}
|
|
8
25
|
|
|
9
26
|
type GeminiChunk = {
|
|
10
27
|
candidates?: Array<{
|
|
11
28
|
content?: {
|
|
12
|
-
parts?:
|
|
13
|
-
text?: string
|
|
14
|
-
}>
|
|
29
|
+
parts?: GeminiPart[]
|
|
15
30
|
}
|
|
16
31
|
finishReason?: string
|
|
17
32
|
}>
|
|
@@ -24,15 +39,45 @@ type GeminiChunk = {
|
|
|
24
39
|
}
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
type GeminiContentPart =
|
|
43
|
+
| { text: string }
|
|
44
|
+
| { functionCall: { name: string; args: Record<string, unknown> } }
|
|
45
|
+
| { functionResponse: { name: string; response: Record<string, unknown> } }
|
|
46
|
+
|
|
47
|
+
type GeminiContent = {
|
|
48
|
+
role: 'user' | 'model'
|
|
49
|
+
parts: GeminiContentPart[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type GeminiPayload = {
|
|
53
|
+
contents: GeminiContent[]
|
|
54
|
+
systemInstruction?: { parts: Array<{ text: string }> }
|
|
55
|
+
generationConfig?: { maxOutputTokens?: number }
|
|
56
|
+
tools?: Array<{ functionDeclarations: GeminiToolDefinition[] }>
|
|
57
|
+
toolConfig?: { functionCallingConfig: { mode: 'AUTO' } }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type DoneStopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | 'unknown'
|
|
61
|
+
|
|
27
62
|
const READ_TIMEOUT_MS = 45_000
|
|
28
63
|
|
|
64
|
+
export type GeminiQuotaInfo = {
|
|
65
|
+
retryAfterMs?: number
|
|
66
|
+
metric?: string
|
|
67
|
+
quotaValue?: string
|
|
68
|
+
quotaId?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
29
71
|
export class GeminiProvider implements Provider {
|
|
30
72
|
readonly id = 'gemini' as const
|
|
31
73
|
readonly model: string
|
|
32
|
-
readonly supportsTools
|
|
74
|
+
readonly supportsTools: boolean
|
|
75
|
+
private readonly tools: GeminiToolDefinition[]
|
|
33
76
|
|
|
34
|
-
constructor(opts: { model: string }) {
|
|
77
|
+
constructor(opts: { model: string; tools?: GeminiToolDefinition[] }) {
|
|
35
78
|
this.model = opts.model
|
|
79
|
+
this.tools = opts.tools ?? []
|
|
80
|
+
this.supportsTools = this.tools.length > 0
|
|
36
81
|
}
|
|
37
82
|
|
|
38
83
|
async *complete(
|
|
@@ -40,16 +85,17 @@ export class GeminiProvider implements Provider {
|
|
|
40
85
|
signal: AbortSignal,
|
|
41
86
|
options: ProviderCompleteOptions = {},
|
|
42
87
|
): AsyncIterable<StreamEvent> {
|
|
43
|
-
const
|
|
88
|
+
const rawApiKey = await getKey('gemini')
|
|
89
|
+
const apiKey = rawApiKey?.trim()
|
|
44
90
|
if (!apiKey) {
|
|
45
91
|
const error = new ProviderError('missing API key for gemini (/doctor to verify)')
|
|
46
92
|
yield { type: 'error', message: error.message }
|
|
47
93
|
return
|
|
48
94
|
}
|
|
49
95
|
|
|
50
|
-
const payload = buildGeminiPayload(messages, options)
|
|
96
|
+
const payload = buildGeminiPayload(messages, this.tools, options)
|
|
51
97
|
const modelName = this.model.replace(/^models\//, '')
|
|
52
|
-
const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(modelName)}:streamGenerateContent?alt=sse
|
|
98
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(modelName)}:streamGenerateContent?alt=sse`
|
|
53
99
|
|
|
54
100
|
let response: Response
|
|
55
101
|
try {
|
|
@@ -58,6 +104,7 @@ export class GeminiProvider implements Provider {
|
|
|
58
104
|
headers: {
|
|
59
105
|
'content-type': 'application/json',
|
|
60
106
|
accept: 'text/event-stream',
|
|
107
|
+
'x-goog-api-key': apiKey,
|
|
61
108
|
},
|
|
62
109
|
body: JSON.stringify(payload),
|
|
63
110
|
}, { signal })
|
|
@@ -79,6 +126,9 @@ export class GeminiProvider implements Provider {
|
|
|
79
126
|
|
|
80
127
|
let inputTokens: number | undefined
|
|
81
128
|
let outputTokens: number | undefined
|
|
129
|
+
let stopReason: DoneStopReason = 'unknown'
|
|
130
|
+
let toolCallIndex = 0
|
|
131
|
+
let sawToolCall = false
|
|
82
132
|
|
|
83
133
|
try {
|
|
84
134
|
for await (const frame of iterSseFrames(response.body, signal, READ_TIMEOUT_MS)) {
|
|
@@ -94,9 +144,26 @@ export class GeminiProvider implements Provider {
|
|
|
94
144
|
throw new ProviderError(`prompt blocked: ${blockedReason.toLowerCase()}`)
|
|
95
145
|
}
|
|
96
146
|
|
|
97
|
-
const
|
|
147
|
+
const candidate = parsed.candidates?.[0]
|
|
148
|
+
const parts = candidate?.content?.parts ?? []
|
|
98
149
|
for (const part of parts) {
|
|
99
|
-
if (part.text)
|
|
150
|
+
if (part.text) {
|
|
151
|
+
yield { type: 'text', delta: part.text }
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
if (part.functionCall?.name) {
|
|
155
|
+
const id = `gemini-tool-${toolCallIndex}`
|
|
156
|
+
toolCallIndex += 1
|
|
157
|
+
sawToolCall = true
|
|
158
|
+
const name = part.functionCall.name
|
|
159
|
+
const input = part.functionCall.args ?? {}
|
|
160
|
+
yield { type: 'tool_use_start', id, name }
|
|
161
|
+
yield { type: 'tool_use_stop', id, name, input }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (candidate?.finishReason) {
|
|
166
|
+
stopReason = normalizeFinishReason(candidate.finishReason, sawToolCall)
|
|
100
167
|
}
|
|
101
168
|
|
|
102
169
|
inputTokens = parsed.usageMetadata?.promptTokenCount ?? inputTokens
|
|
@@ -109,44 +176,190 @@ export class GeminiProvider implements Provider {
|
|
|
109
176
|
}
|
|
110
177
|
|
|
111
178
|
if (signal.aborted) return
|
|
112
|
-
|
|
179
|
+
if (sawToolCall) stopReason = 'tool_use'
|
|
180
|
+
yield { type: 'done', inputTokens, outputTokens, stopReason }
|
|
113
181
|
}
|
|
114
182
|
}
|
|
115
183
|
|
|
116
|
-
function buildGeminiPayload(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
systemInstruction?: {
|
|
122
|
-
parts: Array<{ text: string }>
|
|
123
|
-
}
|
|
124
|
-
generationConfig?: {
|
|
125
|
-
maxOutputTokens?: number
|
|
126
|
-
}
|
|
127
|
-
} {
|
|
184
|
+
export function buildGeminiPayload(
|
|
185
|
+
messages: Message[],
|
|
186
|
+
tools: GeminiToolDefinition[] = [],
|
|
187
|
+
options: ProviderCompleteOptions = {},
|
|
188
|
+
): GeminiPayload {
|
|
128
189
|
const systemParts: string[] = []
|
|
129
|
-
const contents:
|
|
130
|
-
|
|
131
|
-
parts: Array<{ text: string }>
|
|
132
|
-
}> = []
|
|
190
|
+
const contents: GeminiContent[] = []
|
|
191
|
+
const toolUseNamesById = new Map<string, string>()
|
|
133
192
|
|
|
134
193
|
for (const message of messages) {
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
194
|
+
const blocks = normalizeBlocks(message.content)
|
|
195
|
+
if (blocks.length === 0) continue
|
|
196
|
+
|
|
137
197
|
if (message.role === 'system') {
|
|
138
|
-
|
|
198
|
+
const systemText = blocks
|
|
199
|
+
.filter((block): block is Extract<MessageContentBlock, { type: 'text' }> => block.type === 'text')
|
|
200
|
+
.map(block => block.text)
|
|
201
|
+
.join('\n\n')
|
|
202
|
+
.trim()
|
|
203
|
+
if (systemText) systemParts.push(systemText)
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (message.role === 'assistant') {
|
|
208
|
+
const parts: GeminiContentPart[] = []
|
|
209
|
+
for (const block of blocks) {
|
|
210
|
+
if (block.type === 'text') {
|
|
211
|
+
parts.push({ text: block.text })
|
|
212
|
+
} else if (block.type === 'tool_use') {
|
|
213
|
+
toolUseNamesById.set(block.id, block.name)
|
|
214
|
+
parts.push({ functionCall: { name: block.name, args: block.input } })
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (parts.length > 0) contents.push({ role: 'model', parts })
|
|
139
218
|
continue
|
|
140
219
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
220
|
+
|
|
221
|
+
const parts: GeminiContentPart[] = []
|
|
222
|
+
for (const block of blocks) {
|
|
223
|
+
if (block.type === 'text') {
|
|
224
|
+
parts.push({ text: block.text })
|
|
225
|
+
} else if (block.type === 'tool_result') {
|
|
226
|
+
const name = toolUseNamesById.get(block.toolUseId) ?? 'unknown'
|
|
227
|
+
const response: Record<string, unknown> = block.isError
|
|
228
|
+
? { content: block.content, isError: true }
|
|
229
|
+
: { content: block.content }
|
|
230
|
+
parts.push({ functionResponse: { name, response } })
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (parts.length > 0) contents.push({ role: 'user', parts })
|
|
145
234
|
}
|
|
146
235
|
|
|
236
|
+
const payload: GeminiPayload = { contents }
|
|
237
|
+
if (systemParts.length > 0) {
|
|
238
|
+
payload.systemInstruction = { parts: [{ text: systemParts.join('\n\n') }] }
|
|
239
|
+
}
|
|
240
|
+
if (options.maxTokens) {
|
|
241
|
+
payload.generationConfig = { maxOutputTokens: options.maxTokens }
|
|
242
|
+
}
|
|
243
|
+
if (tools.length > 0) {
|
|
244
|
+
payload.tools = [{ functionDeclarations: tools }]
|
|
245
|
+
payload.toolConfig = { functionCallingConfig: { mode: 'AUTO' } }
|
|
246
|
+
}
|
|
247
|
+
return payload
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function normalizeBlocks(content: Message['content']): MessageContentBlock[] {
|
|
251
|
+
if (typeof content === 'string') {
|
|
252
|
+
const text = content.trim()
|
|
253
|
+
return text ? [{ type: 'text', text }] : []
|
|
254
|
+
}
|
|
255
|
+
return content.filter(block => {
|
|
256
|
+
if (block.type === 'text') return block.text.trim().length > 0
|
|
257
|
+
return true
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function normalizeFinishReason(reason: string, sawToolCall: boolean): DoneStopReason {
|
|
262
|
+
if (sawToolCall) return 'tool_use'
|
|
263
|
+
switch (reason) {
|
|
264
|
+
case 'STOP':
|
|
265
|
+
return 'end_turn'
|
|
266
|
+
case 'MAX_TOKENS':
|
|
267
|
+
return 'max_tokens'
|
|
268
|
+
case 'STOP_SEQUENCE':
|
|
269
|
+
return 'stop_sequence'
|
|
270
|
+
default:
|
|
271
|
+
return 'unknown'
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
type GeminiQuotaInfoInternal = GeminiQuotaInfo & { kind: 'quota-failure' | 'rate-limit' }
|
|
276
|
+
|
|
277
|
+
function readGeminiQuotaInfo(bodyText: string): GeminiQuotaInfoInternal | undefined {
|
|
278
|
+
let body: unknown
|
|
279
|
+
try {
|
|
280
|
+
body = JSON.parse(bodyText)
|
|
281
|
+
} catch {
|
|
282
|
+
return undefined
|
|
283
|
+
}
|
|
284
|
+
if (Array.isArray(body)) body = body[0]
|
|
285
|
+
if (!body || typeof body !== 'object') return undefined
|
|
286
|
+
|
|
287
|
+
const error = (body as { error?: unknown }).error
|
|
288
|
+
if (!error || typeof error !== 'object') return undefined
|
|
289
|
+
|
|
290
|
+
const details = (error as { details?: unknown }).details
|
|
291
|
+
if (!Array.isArray(details)) return undefined
|
|
292
|
+
|
|
293
|
+
let retryAfterMs: number | undefined
|
|
294
|
+
let metric: string | undefined
|
|
295
|
+
let quotaValue: string | undefined
|
|
296
|
+
let quotaId: string | undefined
|
|
297
|
+
let isQuotaFailure = false
|
|
298
|
+
|
|
299
|
+
for (const detail of details) {
|
|
300
|
+
if (!detail || typeof detail !== 'object') continue
|
|
301
|
+
const type = (detail as { '@type'?: unknown })['@type']
|
|
302
|
+
if (typeof type !== 'string') continue
|
|
303
|
+
|
|
304
|
+
if (/RetryInfo$/.test(type)) {
|
|
305
|
+
const delay = (detail as { retryDelay?: unknown }).retryDelay
|
|
306
|
+
const parsed = parseGoogleDurationMs(delay)
|
|
307
|
+
if (parsed !== undefined) retryAfterMs = parsed
|
|
308
|
+
} else if (/QuotaFailure$/.test(type)) {
|
|
309
|
+
isQuotaFailure = true
|
|
310
|
+
const violations = (detail as { violations?: unknown }).violations
|
|
311
|
+
if (Array.isArray(violations) && violations.length > 0) {
|
|
312
|
+
const first = violations[0] as Record<string, unknown> | undefined
|
|
313
|
+
if (first) {
|
|
314
|
+
const m = first.metric
|
|
315
|
+
const qv = first.quotaValue
|
|
316
|
+
const qi = first.quotaId
|
|
317
|
+
if (typeof m === 'string') metric = m
|
|
318
|
+
if (typeof qv === 'string') quotaValue = qv
|
|
319
|
+
if (typeof qi === 'string') quotaId = qi
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!isQuotaFailure && retryAfterMs === undefined) return undefined
|
|
147
326
|
return {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
327
|
+
kind: isQuotaFailure ? 'quota-failure' : 'rate-limit',
|
|
328
|
+
retryAfterMs,
|
|
329
|
+
metric,
|
|
330
|
+
quotaValue,
|
|
331
|
+
quotaId,
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function parseGoogleDurationMs(value: unknown): number | undefined {
|
|
336
|
+
if (typeof value !== 'string') return undefined
|
|
337
|
+
const m = /^(\d+(?:\.\d+)?)s$/.exec(value.trim())
|
|
338
|
+
if (!m) return undefined
|
|
339
|
+
const seconds = Number(m[1])
|
|
340
|
+
return Number.isFinite(seconds) ? Math.round(seconds * 1000) : undefined
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export async function formatGeminiRateLimitMessage(response: Response): Promise<string | undefined> {
|
|
344
|
+
let bodyText = ''
|
|
345
|
+
try { bodyText = await response.text() } catch { return undefined }
|
|
346
|
+
const info = readGeminiQuotaInfo(bodyText)
|
|
347
|
+
if (!info) return undefined
|
|
348
|
+
const exhausted = info.kind === 'quota-failure'
|
|
349
|
+
const isFreeTier = info.metric ? /free_tier/i.test(info.metric) : false
|
|
350
|
+
const parts = [exhausted ? 'gemini quota hit' : 'gemini rate limit']
|
|
351
|
+
if (info.quotaValue) {
|
|
352
|
+
parts.push(isFreeTier ? `(free-tier cap: ${info.quotaValue})` : `(cap: ${info.quotaValue})`)
|
|
353
|
+
} else if (isFreeTier) {
|
|
354
|
+
parts.push('(free-tier cap)')
|
|
355
|
+
}
|
|
356
|
+
if (info.retryAfterMs !== undefined) {
|
|
357
|
+
const seconds = Math.ceil(info.retryAfterMs / 1000)
|
|
358
|
+
parts.push(`— retry in ~${seconds}s`)
|
|
359
|
+
} else if (exhausted && isFreeTier) {
|
|
360
|
+
parts.push('— enable billing on the API key\'s project, or /model to switch')
|
|
361
|
+
} else if (exhausted) {
|
|
362
|
+
parts.push('— /model to switch providers, or wait for the quota window to reset')
|
|
151
363
|
}
|
|
364
|
+
return parts.join(' ')
|
|
152
365
|
}
|
|
@@ -6,7 +6,7 @@ import type { SessionMode } from '../runtime/sessionMode.js'
|
|
|
6
6
|
import { AnthropicProvider } from './anthropic.js'
|
|
7
7
|
import { GeminiProvider } from './gemini.js'
|
|
8
8
|
import { OpenAIChatProvider } from './openai-chat.js'
|
|
9
|
-
import { anthropicTools, openAITools } from '../tools/registry.js'
|
|
9
|
+
import { anthropicTools, geminiTools, openAITools } from '../tools/registry.js'
|
|
10
10
|
import { openAIBaseUrlFor } from '../models/catalog.js'
|
|
11
11
|
import type { Tool } from '../tools/contracts.js'
|
|
12
12
|
|
|
@@ -37,6 +37,6 @@ export function createProvider(config: EthagentConfig, options: { mode?: Session
|
|
|
37
37
|
case 'anthropic':
|
|
38
38
|
return new AnthropicProvider({ model: config.model, tools: anthropicTools(mode, toolContext) })
|
|
39
39
|
case 'gemini':
|
|
40
|
-
return new GeminiProvider({ model: config.model })
|
|
40
|
+
return new GeminiProvider({ model: config.model, tools: geminiTools(mode, toolContext) })
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/providers/retry.ts
CHANGED
|
@@ -54,5 +54,5 @@ export async function* fetchWithRetryStreamEvents(
|
|
|
54
54
|
await fetchPromise
|
|
55
55
|
if (settled?.state === 'resolved') return settled.response
|
|
56
56
|
if (settled?.state === 'rejected') throw settled.error
|
|
57
|
-
throw new Error('
|
|
57
|
+
throw new Error('Fetch retry completed without a response')
|
|
58
58
|
}
|
|
@@ -24,7 +24,7 @@ export function nextSessionMode(mode: SessionMode): SessionMode {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function sessionModeLabel(mode: SessionMode): string {
|
|
27
|
-
return mode === 'plan' ? '
|
|
27
|
+
return mode === 'plan' ? 'Plan mode' : mode === 'accept-edits' ? 'Accept edits on' : ''
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function modePolicy(mode: PolicyMode): ModePolicy {
|
|
@@ -98,6 +98,7 @@ function buildToolEnabledPrompt(ctx: SystemPromptContext): string {
|
|
|
98
98
|
]
|
|
99
99
|
: ['No agent identity is linked in this session. Do not attempt private identity continuity edits; ask the user to create or load an agent first.']),
|
|
100
100
|
'Use `run_bash` **only** when true shell execution is necessary.',
|
|
101
|
+
'Never use `run_bash` to produce conversational text. Do not call `echo`, `printf`, or similar to emit your reply — write it as your assistant text. Bash is for actions that need a real shell, not for generating words.',
|
|
101
102
|
'**CWD CONTINUITY**: The working directory below is authoritative. After `change_directory` succeeds, use the new path as the base for subsequent actions.',
|
|
102
103
|
'Do not lag behind the CWD. Edit/read relative to the *current* working directory.',
|
|
103
104
|
'If asked for a complete application/site/game, **create the files yourself**. Do not hand back copy-paste templates.',
|
|
@@ -112,6 +113,7 @@ function buildToolEnabledPrompt(ctx: SystemPromptContext): string {
|
|
|
112
113
|
'Local Model Tool Discipline',
|
|
113
114
|
[
|
|
114
115
|
'**PROTOCOL**: Emit tool calls in the native tool-call protocol. Do NOT describe the call in prose first, and do NOT print a JSON blob inside markdown as a substitute for an actual tool call.',
|
|
116
|
+
'**NO ECHO REPLIES**: Never call `run_bash` with `echo`, `printf`, or any command whose only purpose is to print your reply. Reply directly in your assistant text. Bash is for real shell actions only.',
|
|
115
117
|
'**NO FAKE COMPLETIONS**: NEVER claim you have updated or created a file if you have not used the edit tools. Talk is cheap, use the tools.',
|
|
116
118
|
'One tool call per response when a tool is needed. Wait for the tool result before deciding the next step.',
|
|
117
119
|
...(ctx.hasIdentity
|
|
@@ -24,10 +24,6 @@ import {
|
|
|
24
24
|
import type { MessageRow } from '../chat/MessageList.js'
|
|
25
25
|
import { modePolicy, toPermissionMode, type SessionMode } from './sessionMode.js'
|
|
26
26
|
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Tool execution with permission gating
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
27
|
export type ToolExecutorOptions = {
|
|
32
28
|
name: string
|
|
33
29
|
input: Record<string, unknown>
|
|
@@ -188,10 +184,6 @@ function formatToolParseError(err: unknown, toolName?: string): string {
|
|
|
188
184
|
return withToolHint((err as Error).message || 'tool input did not match the required schema')
|
|
189
185
|
}
|
|
190
186
|
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
// Pending tool-use runner (per turn)
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
|
|
195
187
|
export type PendingToolUse = {
|
|
196
188
|
id: string
|
|
197
189
|
name: string
|
|
@@ -239,9 +231,16 @@ export async function runPendingToolUses(args: {
|
|
|
239
231
|
const completedTools: CompletedToolUse[] = []
|
|
240
232
|
|
|
241
233
|
for (const toolUse of args.pendingToolUses) {
|
|
234
|
+
const rowId = args.nextRowId()
|
|
242
235
|
args.updateRows(prev => [
|
|
243
236
|
...prev,
|
|
244
|
-
{
|
|
237
|
+
{
|
|
238
|
+
role: 'tool_call',
|
|
239
|
+
id: rowId,
|
|
240
|
+
name: toolUse.name,
|
|
241
|
+
summary: toolUse.name,
|
|
242
|
+
input: summarizeToolInput(toolUse.input),
|
|
243
|
+
},
|
|
245
244
|
])
|
|
246
245
|
await args.persistTurnMessage({
|
|
247
246
|
version: 2,
|
|
@@ -266,7 +265,7 @@ export async function runPendingToolUses(args: {
|
|
|
266
265
|
}
|
|
267
266
|
|
|
268
267
|
await args.applySessionRule(sessionRule, persistRule)
|
|
269
|
-
await recordToolResult(args, toolUse, result)
|
|
268
|
+
await recordToolResult(args, toolUse, result, rowId)
|
|
270
269
|
}
|
|
271
270
|
|
|
272
271
|
return { cancelled: false, completedTools }
|
|
@@ -279,25 +278,22 @@ async function recordToolResult(
|
|
|
279
278
|
>,
|
|
280
279
|
toolUse: PendingToolUse,
|
|
281
280
|
result: ToolResult,
|
|
281
|
+
rowId: string,
|
|
282
282
|
): Promise<void> {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
content: toolResultContentForRow(toolUse.name, result.content, !result.ok),
|
|
291
|
-
isError: !result.ok,
|
|
292
|
-
},
|
|
293
|
-
])
|
|
283
|
+
const isError = !result.ok
|
|
284
|
+
const resultContent = toolResultContentForRow(toolUse.name, result.content, isError)
|
|
285
|
+
args.updateRows(prev => prev.map(row =>
|
|
286
|
+
row.role === 'tool_call' && row.id === rowId
|
|
287
|
+
? { ...row, result: { content: resultContent, summary: result.summary, isError } }
|
|
288
|
+
: row,
|
|
289
|
+
))
|
|
294
290
|
await args.persistTurnMessage({
|
|
295
291
|
version: 2,
|
|
296
292
|
role: 'tool_result',
|
|
297
293
|
toolUseId: toolUse.id,
|
|
298
294
|
name: toolUse.name,
|
|
299
295
|
content: result.content,
|
|
300
|
-
isError
|
|
296
|
+
isError,
|
|
301
297
|
createdAt: args.nowIso(),
|
|
302
298
|
turnId: args.turnId,
|
|
303
299
|
})
|
|
@@ -8,18 +8,6 @@ export type ToolIntent = {
|
|
|
8
8
|
reason: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* detectDirectToolIntent — typed detection for high-confidence direct
|
|
13
|
-
* filesystem requests. Returns the first matching intent, or null for
|
|
14
|
-
* ambiguous or multi-step engineering requests so the model handles those.
|
|
15
|
-
*
|
|
16
|
-
* Covers:
|
|
17
|
-
* - change_directory: "cd into identity", "go to src/identity"
|
|
18
|
-
* - list_directory: "list files", "show what's here", "ls"
|
|
19
|
-
* - read_file: "read package.json", "open/show/cat <file>"
|
|
20
|
-
*
|
|
21
|
-
* Returns null for anything else.
|
|
22
|
-
*/
|
|
23
11
|
export function detectDirectToolIntent(userText: string): ToolIntent | null {
|
|
24
12
|
const uses = directToolUsesForUserText(userText)
|
|
25
13
|
if (uses.length === 0) return null
|
|
@@ -28,14 +16,6 @@ export function detectDirectToolIntent(userText: string): ToolIntent | null {
|
|
|
28
16
|
return { name: first.name, input: first.input, reason }
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
/**
|
|
32
|
-
* validateAssistantTextAgainstTurnEvidence — checks whether assistant prose
|
|
33
|
-
* claims workspace state that isn't backed by a tool result from the active
|
|
34
|
-
* turn.
|
|
35
|
-
*
|
|
36
|
-
* Returns 'ok' if the text is safe (no unsupported claims), or 'needs-tool'
|
|
37
|
-
* if the text claims state that has no matching tool evidence.
|
|
38
|
-
*/
|
|
39
19
|
export function validateAssistantTextAgainstTurnEvidence(
|
|
40
20
|
text: string,
|
|
41
21
|
evidence: ToolEvidence[],
|