ethagent 3.0.1 → 3.1.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 (73) hide show
  1. package/README.md +6 -1
  2. package/package.json +3 -1
  3. package/src/app/FirstRun.tsx +1 -24
  4. package/src/app/firstRunConfig.ts +26 -0
  5. package/src/auth/openaiOAuth/landingPage.ts +2 -11
  6. package/src/chat/ChatScreen.tsx +32 -117
  7. package/src/chat/MessageList.tsx +18 -260
  8. package/src/chat/chatEnvironment.ts +16 -0
  9. package/src/chat/chatTurnContext.ts +50 -0
  10. package/src/chat/chatTurnOrchestrator.ts +5 -112
  11. package/src/chat/chatTurnRows.ts +64 -0
  12. package/src/chat/commands.ts +3 -178
  13. package/src/chat/continuityEditReview.ts +42 -0
  14. package/src/chat/input/ChatInput.tsx +10 -144
  15. package/src/chat/input/chatInputHelpers.ts +62 -0
  16. package/src/chat/input/inputRendering.tsx +93 -0
  17. package/src/chat/messageMarkdown.ts +220 -0
  18. package/src/chat/messageRows.ts +43 -0
  19. package/src/chat/planImplementation.ts +62 -0
  20. package/src/chat/slashCommandHandlers.ts +165 -0
  21. package/src/chat/slashCommandViews.ts +120 -0
  22. package/src/cli/main.tsx +7 -0
  23. package/src/identity/continuity/challenges.ts +123 -0
  24. package/src/identity/continuity/envelope.ts +49 -1484
  25. package/src/identity/continuity/envelopeCreate.ts +322 -0
  26. package/src/identity/continuity/envelopeCrypto.ts +182 -0
  27. package/src/identity/continuity/envelopeParse.ts +441 -0
  28. package/src/identity/continuity/envelopeTypes.ts +204 -0
  29. package/src/identity/continuity/envelopeVersion.ts +1 -0
  30. package/src/identity/continuity/payloadNormalization.ts +183 -0
  31. package/src/identity/continuity/publicSkills.ts +5 -5
  32. package/src/identity/continuity/skills/loadSkills.ts +12 -69
  33. package/src/identity/continuity/skills/skillPaths.ts +76 -0
  34. package/src/identity/continuity/skillsNormalization.ts +119 -0
  35. package/src/identity/continuity/snapshotToken.ts +28 -0
  36. package/src/identity/hub/continuity/completion.ts +67 -0
  37. package/src/identity/hub/continuity/effects.ts +5 -62
  38. package/src/identity/hub/profile/effects.ts +6 -170
  39. package/src/identity/hub/profile/operatorSave.ts +202 -0
  40. package/src/identity/registry/erc8004/metadata.ts +31 -23
  41. package/src/identity/wallet/browserWallet/html.ts +1 -57
  42. package/src/identity/wallet/browserWallet/walletPageSource.ts +85 -0
  43. package/src/identity/wallet/page/controller.ts +1 -1
  44. package/src/identity/wallet/page/errorView.ts +122 -0
  45. package/src/identity/wallet/page/view.ts +3 -114
  46. package/src/mcp/manager.ts +8 -66
  47. package/src/mcp/managerHelpers.ts +70 -0
  48. package/src/models/ModelPicker.tsx +69 -889
  49. package/src/models/huggingface.ts +20 -137
  50. package/src/models/huggingfaceStorage.ts +136 -0
  51. package/src/models/llamacpp.ts +37 -303
  52. package/src/models/llamacppCommands.ts +44 -0
  53. package/src/models/llamacppConfig.ts +34 -0
  54. package/src/models/llamacppDiscovery.ts +176 -0
  55. package/src/models/llamacppOutput.ts +65 -0
  56. package/src/models/modelPickerCatalogFlow.ts +56 -0
  57. package/src/models/modelPickerCredentials.ts +166 -0
  58. package/src/models/modelPickerData.ts +41 -0
  59. package/src/models/modelPickerDisplay.tsx +132 -0
  60. package/src/models/modelPickerHfFlow.ts +192 -0
  61. package/src/models/modelPickerLocalRunnerFlow.ts +115 -0
  62. package/src/models/modelPickerTypes.ts +69 -0
  63. package/src/models/modelPickerUninstallFlow.ts +48 -0
  64. package/src/models/modelPickerViewHelpers.ts +174 -0
  65. package/src/providers/openai-chat.ts +5 -124
  66. package/src/providers/openaiChatWire.ts +124 -0
  67. package/src/runtime/providerTurn.ts +38 -0
  68. package/src/runtime/textToolParser.ts +161 -0
  69. package/src/runtime/toolIntent.ts +1 -1
  70. package/src/runtime/turn.ts +43 -499
  71. package/src/runtime/turnNudges.ts +223 -0
  72. package/src/runtime/turnTypes.ts +86 -0
  73. package/src/ui/terminalTitle.ts +30 -0
@@ -0,0 +1,38 @@
1
+ import type { Message, Provider, StreamEvent } from '../providers/contracts.js'
2
+ import type { ProviderTurnEvent } from './turnTypes.js'
3
+
4
+ export async function* runProviderTurn(
5
+ provider: Provider,
6
+ messages: Message[],
7
+ signal: AbortSignal,
8
+ ): AsyncIterable<ProviderTurnEvent> {
9
+ if (signal.aborted) {
10
+ yield { type: 'cancelled' }
11
+ return
12
+ }
13
+ for await (const ev of provider.complete(messages, signal)) {
14
+ if (signal.aborted) {
15
+ yield { type: 'cancelled' }
16
+ return
17
+ }
18
+ yield normalize(ev)
19
+ if (ev.type === 'done' || ev.type === 'error') return
20
+ }
21
+ if (signal.aborted) {
22
+ yield { type: 'cancelled' }
23
+ }
24
+ }
25
+
26
+ function normalize(event: StreamEvent): ProviderTurnEvent {
27
+ switch (event.type) {
28
+ case 'text': return { type: 'text', delta: event.delta }
29
+ case 'thinking': return { type: 'thinking', delta: event.delta }
30
+ case 'thinking_end': return { type: 'thinking_end' }
31
+ case 'retry': return event
32
+ case 'tool_use_start': return event
33
+ case 'tool_use_delta': return event
34
+ case 'tool_use_stop': return event
35
+ case 'done': return { type: 'done', stopReason: event.stopReason }
36
+ case 'error': return { type: 'error', message: event.message }
37
+ }
38
+ }
@@ -0,0 +1,161 @@
1
+ import type { Provider } from '../providers/contracts.js'
2
+ import { getTool } from '../tools/registry.js'
3
+ import type { PendingToolUse } from './turnTypes.js'
4
+
5
+ export function parseLocalModelTextToolUse(
6
+ provider: Pick<Provider, 'id'>,
7
+ assistantText: string,
8
+ iterationIndex = 0,
9
+ ): PendingToolUse | null {
10
+ const parsed = parseLocalModelTextToolUses(provider, assistantText, iterationIndex)
11
+ return parsed.length === 1 ? parsed[0]! : null
12
+ }
13
+
14
+ export function parseLocalModelTextToolUses(
15
+ provider: Pick<Provider, 'id'>,
16
+ assistantText: string,
17
+ iterationIndex = 0,
18
+ ): PendingToolUse[] {
19
+ if (provider.id !== 'llamacpp') return []
20
+
21
+ const calls = extractTextToolCalls(assistantText)
22
+ if (calls.length === 0) return []
23
+
24
+ return calls.map((call, index) => ({
25
+ id: calls.length === 1 ? `local-text-tool-${iterationIndex}` : `local-text-tool-${iterationIndex}-${index}`,
26
+ name: call.name,
27
+ input: call.input,
28
+ }))
29
+ }
30
+
31
+ function extractTextToolCalls(text: string): Array<{ name: string; input: Record<string, unknown> }> {
32
+ const payloads = extractToolPayloadCandidates(text)
33
+ const calls = payloads.flatMap(parseTextToolPayloads)
34
+ return calls.filter(call => typeof call.name === 'string' && isRecord(call.input) && Boolean(getTool(call.name)))
35
+ }
36
+
37
+ function extractToolPayloadCandidates(text: string): string[] {
38
+ const trimmed = text.trim()
39
+ if (!trimmed) return []
40
+
41
+ const exact = normalizeToolPayloadCandidate(trimmed)
42
+ if (exact.startsWith('{') && exact.endsWith('}')) return [exact]
43
+ if (exact.startsWith('[') && exact.endsWith(']')) return [exact]
44
+
45
+ const fencedOnlyMatch = trimmed.match(/^```[^\r\n]*\r?\n([\s\S]*?)\r?\n```$/i)
46
+ if (fencedOnlyMatch) return [normalizeToolPayloadCandidate(fencedOnlyMatch[1]!)]
47
+
48
+ const embedded = [
49
+ ...[...trimmed.matchAll(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/gi)].map(match => match[1]!),
50
+ ...[...trimmed.matchAll(/```[^\r\n]*\r?\n([\s\S]*?)\r?\n```/g)].map(match => match[1]!),
51
+ ...extractStandaloneJsonPayloads(trimmed),
52
+ ].map(normalizeToolPayloadCandidate)
53
+
54
+ return [...new Set(embedded)]
55
+ }
56
+
57
+ function extractStandaloneJsonPayloads(text: string): string[] {
58
+ const lines = text.split(/\r?\n/)
59
+ const out: string[] = []
60
+
61
+ for (let i = 0; i < lines.length; i += 1) {
62
+ const line = lines[i] ?? ''
63
+ const first = normalizeToolPayloadCandidate(line)
64
+ if (!first.startsWith('{') && !first.startsWith('[')) continue
65
+
66
+ let candidate = line
67
+ for (let j = i; j < lines.length; j += 1) {
68
+ if (j > i) candidate += `\n${lines[j] ?? ''}`
69
+ const normalized = normalizeToolPayloadCandidate(candidate)
70
+ if (canParseJson(normalized)) {
71
+ out.push(normalized)
72
+ i = j
73
+ break
74
+ }
75
+ if (candidate.length > 20_000) break
76
+ }
77
+ }
78
+
79
+ return out
80
+ }
81
+
82
+ function canParseJson(value: string): boolean {
83
+ try {
84
+ JSON.parse(value)
85
+ return true
86
+ } catch {
87
+ return false
88
+ }
89
+ }
90
+
91
+ function normalizeToolPayloadCandidate(candidate: string): string {
92
+ let normalized = candidate
93
+ .trim()
94
+ .split(/\r?\n/)
95
+ .map(line => line.replace(/^\s*\d+\s+(?=[{\[<"])/, ''))
96
+ .join('\n')
97
+ .trim()
98
+
99
+ const toolCallMatch = normalized.match(/^<tool_call>\s*([\s\S]*?)\s*<\/tool_call>$/i)
100
+ if (toolCallMatch) normalized = toolCallMatch[1]!.trim()
101
+ return normalized
102
+ }
103
+
104
+ function parseTextToolPayloads(payload: string): Array<{ name: string; input: Record<string, unknown> }> {
105
+ let parsed: unknown
106
+ try {
107
+ parsed = JSON.parse(payload)
108
+ } catch {
109
+ return []
110
+ }
111
+
112
+ return normalizeParsedToolPayloads(parsed)
113
+ }
114
+
115
+ function normalizeParsedToolPayloads(value: unknown): Array<{ name: string; input: Record<string, unknown> }> {
116
+ if (Array.isArray(value)) {
117
+ return value.flatMap(normalizeParsedToolPayloads)
118
+ }
119
+ if (!isRecord(value)) return []
120
+
121
+ const toolCalls = value.tool_calls
122
+ if (Array.isArray(toolCalls)) {
123
+ return toolCalls.flatMap(normalizeParsedToolPayloads)
124
+ }
125
+
126
+ const fn = value.function
127
+ if (isRecord(fn)) {
128
+ const call = normalizeNameAndInput(fn.name, fn.arguments)
129
+ return call ? [call] : []
130
+ }
131
+
132
+ const name = value.name ?? value.tool ?? value.tool_name ?? value.function_name
133
+ const rawInput = value.arguments ?? value.input ?? value.parameters ?? value.args ?? {}
134
+ const call = normalizeNameAndInput(name, rawInput)
135
+ return call ? [call] : []
136
+ }
137
+
138
+ function normalizeNameAndInput(
139
+ name: unknown,
140
+ rawInput: unknown,
141
+ ): { name: string; input: Record<string, unknown> } | null {
142
+ if (typeof name !== 'string') return null
143
+ const input = parseToolInput(rawInput)
144
+ if (!input) return null
145
+ return { name, input }
146
+ }
147
+
148
+ function parseToolInput(rawInput: unknown): Record<string, unknown> | null {
149
+ if (isRecord(rawInput)) return rawInput
150
+ if (typeof rawInput !== 'string') return null
151
+ try {
152
+ const parsed = JSON.parse(rawInput)
153
+ return isRecord(parsed) ? parsed : null
154
+ } catch {
155
+ return null
156
+ }
157
+ }
158
+
159
+ function isRecord(value: unknown): value is Record<string, unknown> {
160
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
161
+ }
@@ -1,6 +1,6 @@
1
1
  import type { PendingToolUse } from './turn.js'
2
2
  import { unsupportedToolStateClaims, type ToolEvidence } from './toolClaimGuards.js'
3
- export { parseLocalModelTextToolUses as extractLocalTextToolUses } from './turn.js'
3
+ export { parseLocalModelTextToolUses as extractLocalTextToolUses } from './textToolParser.js'
4
4
 
5
5
  export type ToolIntent = {
6
6
  name: string