@swarmclawai/swarmclaw 1.3.5 → 1.4.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/README.md +37 -1
- package/package.json +10 -3
- package/src/.env.local +4 -0
- package/src/app/api/.well-known/agent-card/route.ts +46 -0
- package/src/app/api/a2a/route.ts +56 -0
- package/src/app/api/a2a/tasks/[taskId]/status/route.ts +49 -0
- package/src/app/api/chats/[id]/deploy/route.ts +2 -2
- package/src/app/api/openclaw/sync/route.ts +1 -1
- package/src/app/api/swarmfeed/channels/route.ts +14 -0
- package/src/app/api/swarmfeed/posts/route.ts +60 -0
- package/src/app/api/swarmfeed/route.ts +37 -0
- package/src/app/protocols/builder/[templateId]/page.tsx +93 -0
- package/src/app/protocols/page.tsx +16 -7
- package/src/app/swarmfeed/page.tsx +7 -0
- package/src/cli/index.js +19 -0
- package/src/cli/spec.js +8 -0
- package/src/components/agents/agent-avatar.tsx +2 -5
- package/src/components/agents/agent-sheet.tsx +10 -0
- package/src/components/auth/access-key-gate.tsx +25 -0
- package/src/components/layout/sidebar-rail.tsx +52 -0
- package/src/components/protocols/builder/edge-editor.tsx +43 -0
- package/src/components/protocols/builder/edge-types/branch-edge.tsx +33 -0
- package/src/components/protocols/builder/edge-types/default-edge.tsx +18 -0
- package/src/components/protocols/builder/edge-types/index.ts +3 -0
- package/src/components/protocols/builder/edge-types/loop-edge.tsx +19 -0
- package/src/components/protocols/builder/node-inspector.tsx +227 -0
- package/src/components/protocols/builder/node-palette.tsx +97 -0
- package/src/components/protocols/builder/node-types/branch-node.tsx +34 -0
- package/src/components/protocols/builder/node-types/complete-node.tsx +17 -0
- package/src/components/protocols/builder/node-types/for-each-node.tsx +21 -0
- package/src/components/protocols/builder/node-types/index.ts +9 -0
- package/src/components/protocols/builder/node-types/join-node.tsx +18 -0
- package/src/components/protocols/builder/node-types/loop-node.tsx +22 -0
- package/src/components/protocols/builder/node-types/parallel-node.tsx +31 -0
- package/src/components/protocols/builder/node-types/phase-node.tsx +52 -0
- package/src/components/protocols/builder/node-types/subflow-node.tsx +23 -0
- package/src/components/protocols/builder/node-types/swarm-node.tsx +26 -0
- package/src/components/protocols/builder/protocol-builder-canvas.tsx +184 -0
- package/src/components/protocols/builder/run-overlay.tsx +29 -0
- package/src/components/protocols/builder/template-gallery.tsx +53 -0
- package/src/components/protocols/builder/validation-panel.tsx +57 -0
- package/src/components/skills/skills-workspace.tsx +1 -9
- package/src/features/protocols/builder/hooks/index.ts +2 -0
- package/src/features/protocols/builder/hooks/use-canvas-validation.ts +14 -0
- package/src/features/protocols/builder/hooks/use-run-overlay.ts +39 -0
- package/src/features/protocols/builder/hooks/use-template-sync.ts +45 -0
- package/src/features/protocols/builder/protocol-builder-store.ts +233 -0
- package/src/features/protocols/builder/utils/node-position-layout.ts +41 -0
- package/src/features/protocols/builder/utils/nodes-to-template.test.ts +179 -0
- package/src/features/protocols/builder/utils/nodes-to-template.ts +49 -0
- package/src/features/protocols/builder/utils/template-to-nodes.test.ts +314 -0
- package/src/features/protocols/builder/utils/template-to-nodes.ts +169 -0
- package/src/features/protocols/builder/validators/dag-validator.test.ts +150 -0
- package/src/features/protocols/builder/validators/dag-validator.ts +119 -0
- package/src/features/swarmfeed/agent-social-settings.tsx +277 -0
- package/src/features/swarmfeed/compose-post.tsx +139 -0
- package/src/features/swarmfeed/feed-page.tsx +136 -0
- package/src/features/swarmfeed/post-card.tsx +114 -0
- package/src/features/swarmfeed/queries.ts +28 -0
- package/src/lib/a2a/agent-card.ts +61 -0
- package/src/lib/a2a/auth.ts +54 -0
- package/src/lib/a2a/client.ts +133 -0
- package/src/lib/a2a/discovery.ts +116 -0
- package/src/lib/a2a/handlers.ts +176 -0
- package/src/lib/a2a/json-rpc-router.ts +38 -0
- package/src/lib/a2a/types.ts +95 -0
- package/src/lib/app/navigation.ts +1 -0
- package/src/lib/app/view-constants.ts +9 -1
- package/src/lib/providers/anthropic.ts +111 -107
- package/src/lib/providers/openai.ts +146 -142
- package/src/lib/server/agents/main-agent-loop.test.ts +94 -0
- package/src/lib/server/agents/main-agent-loop.ts +377 -41
- package/src/lib/server/chat-execution/chat-execution-disabled.test.ts +14 -31
- package/src/lib/server/chat-execution/chat-execution-eval-history.test.ts +11 -34
- package/src/lib/server/chat-execution/chat-execution-grounding.test.ts +15 -34
- package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +35 -36
- package/src/lib/server/chat-execution/chat-execution.ts +12 -7
- package/src/lib/server/extensions.ts +11 -0
- package/src/lib/server/knowledge-sources.test.ts +46 -0
- package/src/lib/server/knowledge-sources.ts +34 -16
- package/src/lib/server/openclaw/sync.ts +4 -4
- package/src/lib/server/protocols/protocol-a2a-delegate.ts +135 -0
- package/src/lib/server/protocols/protocol-normalization.ts +1 -0
- package/src/lib/server/protocols/protocol-step-helpers.test.ts +1 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +1 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +2 -0
- package/src/lib/server/protocols/protocol-types.ts +1 -0
- package/src/lib/server/session-tools/delegate.ts +151 -77
- package/src/lib/server/storage-auth.ts +10 -2
- package/src/lib/server/storage-normalization.ts +11 -0
- package/src/lib/server/storage.ts +100 -0
- package/src/lib/server/test-utils/run-with-temp-data-dir.ts +15 -2
- package/src/lib/server/working-state/service.test.ts +2 -3
- package/src/lib/server/working-state/service.ts +37 -6
- package/src/lib/swarmfeed-client.ts +157 -0
- package/src/lib/validation/schemas.ts +1 -1
- package/src/stores/slices/data-slice.ts +3 -0
- package/src/stores/use-approval-store.ts +4 -1
- package/src/types/agent.ts +31 -1
- package/src/types/index.ts +1 -0
- package/src/types/protocol.ts +19 -0
- package/src/types/session.ts +1 -1
- package/src/types/swarmfeed.ts +30 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { hmrSingleton, errorMessage } from '@/lib/shared-utils'
|
|
2
|
+
import type { A2AMethod, A2AMethodHandler, A2AContext, JsonRpcRequest, JsonRpcResponse } from './types'
|
|
3
|
+
import { JSON_RPC_ERRORS } from './types'
|
|
4
|
+
|
|
5
|
+
export class JsonRpcRouter {
|
|
6
|
+
private handlers = new Map<string, A2AMethodHandler>()
|
|
7
|
+
|
|
8
|
+
register(method: A2AMethod | string, handler: A2AMethodHandler): void {
|
|
9
|
+
this.handlers.set(method, handler)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async route(request: JsonRpcRequest, context: A2AContext): Promise<JsonRpcResponse> {
|
|
13
|
+
const handler = this.handlers.get(request.method)
|
|
14
|
+
if (!handler) {
|
|
15
|
+
return {
|
|
16
|
+
jsonrpc: '2.0',
|
|
17
|
+
error: { code: JSON_RPC_ERRORS.METHOD_NOT_FOUND, message: 'Method not found', data: { method: request.method } },
|
|
18
|
+
id: request.id,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const result = await handler(request.params ?? {}, context)
|
|
23
|
+
return { jsonrpc: '2.0', result, id: request.id }
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return {
|
|
26
|
+
jsonrpc: '2.0',
|
|
27
|
+
error: { code: JSON_RPC_ERRORS.INTERNAL_ERROR, message: errorMessage(err) },
|
|
28
|
+
id: request.id,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
listMethods(): string[] {
|
|
34
|
+
return [...this.handlers.keys()]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const a2aRouter = hmrSingleton('a2a_jsonrpc_router', () => new JsonRpcRouter())
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
// --- A2A Agent Card ---
|
|
4
|
+
// Ref: https://a2a-protocol.org/v0.3.0/specification/#agent-card
|
|
5
|
+
|
|
6
|
+
export const AgentCardCapabilitySchema = z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
methods: z.array(z.string()).optional(),
|
|
9
|
+
description: z.string().optional(),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const AgentCardSkillSchema = z.object({
|
|
13
|
+
name: z.string(),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
parameters: z.record(z.string(), z.unknown()).optional(),
|
|
16
|
+
returns: z.record(z.string(), z.unknown()).optional(),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const AgentCardSchema = z.object({
|
|
20
|
+
name: z.string(),
|
|
21
|
+
description: z.string(),
|
|
22
|
+
version: z.string(),
|
|
23
|
+
protocolVersion: z.string().default('0.3.0'),
|
|
24
|
+
apiEndpoint: z.string().url(),
|
|
25
|
+
capabilities: z.array(AgentCardCapabilitySchema).default([]),
|
|
26
|
+
skills: z.array(AgentCardSkillSchema).default([]),
|
|
27
|
+
authMethods: z.array(z.enum(['api_key', 'ed25519', 'oauth2'])).default(['api_key']),
|
|
28
|
+
supportsStreaming: z.boolean().default(true),
|
|
29
|
+
supportsAsync: z.boolean().default(true),
|
|
30
|
+
rateLimit: z.object({
|
|
31
|
+
requestsPerMinute: z.number().optional(),
|
|
32
|
+
maxConcurrentRequests: z.number().optional(),
|
|
33
|
+
}).optional(),
|
|
34
|
+
extensions: z.array(z.object({
|
|
35
|
+
name: z.string(),
|
|
36
|
+
version: z.string(),
|
|
37
|
+
url: z.string().url().optional(),
|
|
38
|
+
})).default([]),
|
|
39
|
+
tags: z.array(z.string()).default([]),
|
|
40
|
+
icon: z.string().url().optional(),
|
|
41
|
+
website: z.string().url().optional(),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export type AgentCard = z.infer<typeof AgentCardSchema>
|
|
45
|
+
|
|
46
|
+
// --- JSON-RPC 2.0 ---
|
|
47
|
+
// Ref: https://www.jsonrpc.org/specification
|
|
48
|
+
|
|
49
|
+
export const JsonRpcRequestSchema = z.object({
|
|
50
|
+
jsonrpc: z.literal('2.0'),
|
|
51
|
+
method: z.string(),
|
|
52
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
53
|
+
id: z.union([z.string(), z.number()]).optional(),
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
export type JsonRpcRequest = z.infer<typeof JsonRpcRequestSchema>
|
|
57
|
+
|
|
58
|
+
export interface JsonRpcResponse<T = unknown> {
|
|
59
|
+
jsonrpc: '2.0'
|
|
60
|
+
result?: T
|
|
61
|
+
error?: { code: number; message: string; data?: unknown }
|
|
62
|
+
id?: string | number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- A2A Method Types ---
|
|
66
|
+
|
|
67
|
+
export type A2AMethod = 'executeTask' | 'getStatus' | 'cancelTask' | 'discoverAgents'
|
|
68
|
+
|
|
69
|
+
export type A2AMethodHandler = (params: Record<string, unknown>, context: A2AContext) => Promise<unknown>
|
|
70
|
+
|
|
71
|
+
export interface A2AContext {
|
|
72
|
+
agentId: string
|
|
73
|
+
requesterId: string
|
|
74
|
+
timestamp: Date
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type A2ATaskStatus = 'submitted' | 'working' | 'completed' | 'failed' | 'cancelled'
|
|
78
|
+
|
|
79
|
+
// --- A2A Client Options ---
|
|
80
|
+
|
|
81
|
+
export interface A2AClientOptions {
|
|
82
|
+
timeout?: number
|
|
83
|
+
credentialId?: string | null
|
|
84
|
+
retryAttempts?: number
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --- JSON-RPC Error Codes ---
|
|
88
|
+
|
|
89
|
+
export const JSON_RPC_ERRORS = {
|
|
90
|
+
PARSE_ERROR: -32700,
|
|
91
|
+
METHOD_NOT_FOUND: -32601,
|
|
92
|
+
INVALID_PARAMS: -32602,
|
|
93
|
+
INTERNAL_ERROR: -32603,
|
|
94
|
+
AUTH_FAILED: -32000,
|
|
95
|
+
} as const
|
|
@@ -27,6 +27,7 @@ export const VIEW_LABELS: Record<AppView, string> = {
|
|
|
27
27
|
settings: 'Settings',
|
|
28
28
|
projects: 'Projects',
|
|
29
29
|
activity: 'Activity',
|
|
30
|
+
swarmfeed: 'Feed',
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export const CREATE_LABELS: Partial<Record<AppView, string>> = {
|
|
@@ -71,6 +72,7 @@ export const VIEW_DESCRIPTIONS: Record<AppView, string> = {
|
|
|
71
72
|
settings: 'Manage defaults, providers, secrets, and automation settings',
|
|
72
73
|
projects: 'Group agents, tasks & schedules into projects',
|
|
73
74
|
activity: 'Audit trail of all entity mutations',
|
|
75
|
+
swarmfeed: 'Social feed for AI agents to post, follow, and engage',
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
export const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'agents' | 'home'>, { icon: string; title: string; description: string; features: string[] }> = {
|
|
@@ -213,10 +215,16 @@ export const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'agents' | 'home'>, { ic
|
|
|
213
215
|
description: 'Audit trail of all entity mutations across the system.',
|
|
214
216
|
features: ['Track agent, task, and connector changes', 'Filter by entity type and action', 'Real-time updates via WebSocket', 'Relative timestamps'],
|
|
215
217
|
},
|
|
218
|
+
swarmfeed: {
|
|
219
|
+
icon: 'rss',
|
|
220
|
+
title: 'Feed',
|
|
221
|
+
description: 'A social feed where your AI agents post updates, follow each other, and engage with content.',
|
|
222
|
+
features: ['Agents post status updates and insights', 'Follow agents and browse trending content', 'Channel-based topic organization', 'Like, repost, and reply interactions'],
|
|
223
|
+
},
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
export const FULL_WIDTH_VIEWS = new Set<AppView>([
|
|
219
227
|
'home', 'org_chart', 'inbox', 'chatrooms', 'protocols', 'schedules', 'secrets', 'wallets', 'providers', 'skills',
|
|
220
228
|
'connectors', 'webhooks', 'mcp_servers', 'knowledge', 'extensions',
|
|
221
|
-
'usage', 'runs', 'autonomy', 'logs', 'settings', 'activity', 'projects',
|
|
229
|
+
'usage', 'runs', 'autonomy', 'logs', 'settings', 'activity', 'projects', 'swarmfeed',
|
|
222
230
|
])
|
|
@@ -26,119 +26,123 @@ async function fileToContentBlocks(filePath: string): Promise<Array<Record<strin
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function streamAnthropicChat({ session, message, imagePath, apiKey, systemPrompt, write, active, loadHistory, onUsage, signal }: StreamChatOptions): Promise<string> {
|
|
29
|
-
return new Promise(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
;(async () => {
|
|
31
|
+
try {
|
|
32
|
+
const messages = await buildMessages(session, message, imagePath, loadHistory)
|
|
33
|
+
const model = session.model || 'claude-sonnet-4-6'
|
|
34
|
+
let usageInput = 0
|
|
35
|
+
let usageOutput = 0
|
|
36
|
+
|
|
37
|
+
const body: Record<string, unknown> = {
|
|
38
|
+
model,
|
|
39
|
+
max_tokens: ANTHROPIC_MAX_TOKENS,
|
|
40
|
+
messages,
|
|
41
|
+
stream: true,
|
|
42
|
+
}
|
|
43
|
+
if (systemPrompt) {
|
|
44
|
+
body.system = systemPrompt
|
|
45
|
+
}
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const payload = JSON.stringify(body)
|
|
48
|
+
const abortController = { aborted: false }
|
|
49
|
+
let fullResponse = ''
|
|
50
|
+
let apiReqRef: ReturnType<typeof https.request> | null = null
|
|
51
|
+
|
|
52
|
+
if (signal) {
|
|
53
|
+
if (signal.aborted) {
|
|
54
|
+
abortController.aborted = true
|
|
55
|
+
} else {
|
|
56
|
+
signal.addEventListener('abort', () => {
|
|
57
|
+
abortController.aborted = true
|
|
58
|
+
apiReqRef?.destroy()
|
|
59
|
+
}, { once: true })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
const apiReq = https.request({
|
|
64
|
+
hostname: PROVIDER_DEFAULTS.anthropic,
|
|
65
|
+
path: '/v1/messages',
|
|
66
|
+
method: 'POST',
|
|
67
|
+
timeout: 60_000,
|
|
68
|
+
headers: {
|
|
69
|
+
'x-api-key': apiKey || '',
|
|
70
|
+
'anthropic-version': '2023-06-01',
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
},
|
|
73
|
+
}, (apiRes) => {
|
|
74
|
+
if (apiRes.statusCode !== 200) {
|
|
75
|
+
let errBody = ''
|
|
76
|
+
apiRes.on('data', (c: Buffer) => errBody += c)
|
|
77
|
+
apiRes.on('end', () => {
|
|
78
|
+
const msg = `Anthropic error ${apiRes.statusCode}: ${errBody.slice(0, 200)}`
|
|
79
|
+
log.error(TAG, `[${session.id}] ${msg}`)
|
|
80
|
+
let errMsg = `Anthropic API error (${apiRes.statusCode})`
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(errBody)
|
|
83
|
+
if (parsed.error?.message) errMsg = parsed.error.message
|
|
84
|
+
} catch {}
|
|
85
|
+
writeSSE(write, 'err', errMsg)
|
|
86
|
+
active.delete(session.id)
|
|
87
|
+
reject(new Error(msg))
|
|
88
|
+
})
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let buf = ''
|
|
93
|
+
apiRes.on('data', (chunk: Buffer) => {
|
|
94
|
+
if (abortController.aborted) return
|
|
95
|
+
buf += chunk.toString()
|
|
96
|
+
const lines = buf.split('\n')
|
|
97
|
+
buf = lines.pop()!
|
|
98
|
+
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
if (!line.startsWith('data: ')) continue
|
|
101
|
+
const data = line.slice(6).trim()
|
|
102
|
+
if (!data) continue
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(data)
|
|
105
|
+
if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
|
|
106
|
+
fullResponse += parsed.delta.text
|
|
107
|
+
writeSSE(write, 'd', parsed.delta.text)
|
|
108
|
+
}
|
|
109
|
+
if (parsed.type === 'message_start' && parsed.message?.usage) {
|
|
110
|
+
usageInput = parsed.message.usage.input_tokens || 0
|
|
111
|
+
}
|
|
112
|
+
if (parsed.type === 'message_delta' && parsed.usage) {
|
|
113
|
+
usageOutput = parsed.usage.output_tokens || 0
|
|
114
|
+
}
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
60
118
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'anthropic-version': '2023-06-01',
|
|
69
|
-
'Content-Type': 'application/json',
|
|
70
|
-
},
|
|
71
|
-
}, (apiRes) => {
|
|
72
|
-
if (apiRes.statusCode !== 200) {
|
|
73
|
-
let errBody = ''
|
|
74
|
-
apiRes.on('data', (c: Buffer) => errBody += c)
|
|
75
|
-
apiRes.on('end', () => {
|
|
76
|
-
const msg = `Anthropic error ${apiRes.statusCode}: ${errBody.slice(0, 200)}`
|
|
77
|
-
log.error(TAG, `[${session.id}] ${msg}`)
|
|
78
|
-
let errMsg = `Anthropic API error (${apiRes.statusCode})`
|
|
79
|
-
try {
|
|
80
|
-
const parsed = JSON.parse(errBody)
|
|
81
|
-
if (parsed.error?.message) errMsg = parsed.error.message
|
|
82
|
-
} catch {}
|
|
83
|
-
writeSSE(write, 'err', errMsg)
|
|
84
|
-
active.delete(session.id)
|
|
85
|
-
reject(new Error(msg))
|
|
119
|
+
apiRes.on('end', () => {
|
|
120
|
+
if (onUsage && (usageInput > 0 || usageOutput > 0)) {
|
|
121
|
+
onUsage({ inputTokens: usageInput, outputTokens: usageOutput })
|
|
122
|
+
}
|
|
123
|
+
active.delete(session.id)
|
|
124
|
+
resolve(fullResponse)
|
|
125
|
+
})
|
|
86
126
|
})
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
127
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (abortController.aborted) return
|
|
93
|
-
buf += chunk.toString()
|
|
94
|
-
const lines = buf.split('\n')
|
|
95
|
-
buf = lines.pop()!
|
|
96
|
-
|
|
97
|
-
for (const line of lines) {
|
|
98
|
-
if (!line.startsWith('data: ')) continue
|
|
99
|
-
const data = line.slice(6).trim()
|
|
100
|
-
if (!data) continue
|
|
101
|
-
try {
|
|
102
|
-
const parsed = JSON.parse(data)
|
|
103
|
-
if (parsed.type === 'content_block_delta' && parsed.delta?.text) {
|
|
104
|
-
fullResponse += parsed.delta.text
|
|
105
|
-
writeSSE(write, 'd', parsed.delta.text)
|
|
106
|
-
}
|
|
107
|
-
if (parsed.type === 'message_start' && parsed.message?.usage) {
|
|
108
|
-
usageInput = parsed.message.usage.input_tokens || 0
|
|
109
|
-
}
|
|
110
|
-
if (parsed.type === 'message_delta' && parsed.usage) {
|
|
111
|
-
usageOutput = parsed.usage.output_tokens || 0
|
|
112
|
-
}
|
|
113
|
-
} catch {}
|
|
114
|
-
}
|
|
115
|
-
})
|
|
128
|
+
apiReqRef = apiReq
|
|
129
|
+
active.set(session.id, { kill: () => { abortController.aborted = true; apiReq.destroy() } })
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
apiReq.on('error', (e) => {
|
|
135
|
-
log.error(TAG, `[${session.id}] anthropic request error:`, e.message)
|
|
136
|
-
writeSSE(write, 'err', e.message)
|
|
137
|
-
active.delete(session.id)
|
|
138
|
-
reject(e)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
apiReq.end(payload)
|
|
131
|
+
apiReq.on('timeout', () => {
|
|
132
|
+
log.error(TAG, `[${session.id}] anthropic request timed out after 60s`)
|
|
133
|
+
apiReq.destroy(new Error('Request timed out after 60s'))
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
apiReq.on('error', (e) => {
|
|
137
|
+
log.error(TAG, `[${session.id}] anthropic request error:`, e.message)
|
|
138
|
+
writeSSE(write, 'err', e.message)
|
|
139
|
+
active.delete(session.id)
|
|
140
|
+
reject(e)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
apiReq.end(payload)
|
|
144
|
+
} catch (err) { reject(err) }
|
|
145
|
+
})()
|
|
142
146
|
})
|
|
143
147
|
}
|
|
144
148
|
|