@bahulam/code 2.6.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 +80 -0
- package/package.json +49 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +222 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +306 -0
- package/src/commands/agent.mjs +220 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +200 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +308 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +104 -0
- package/src/core/approval.mjs +476 -0
- package/src/core/attachments.mjs +380 -0
- package/src/core/backend-url.mjs +55 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +311 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +448 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +537 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +337 -0
- package/src/core/safety.mjs +203 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +829 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +1950 -0
- package/src/core/trust.mjs +158 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +426 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +360 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +652 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +269 -0
- package/src/terminal/repl-explore.mjs +35 -0
- package/src/terminal/repl-format.mjs +257 -0
- package/src/terminal/repl-render.mjs +561 -0
- package/src/terminal/repl-resume.mjs +625 -0
- package/src/terminal/repl-state.mjs +103 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +3832 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +240 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +641 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +263 -0
- package/src/ui/banner.mjs +235 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/input-dock.mjs +444 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/slash-commands.mjs +245 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +463 -0
- package/src/ui/tool-details.mjs +312 -0
- package/src/ui/transcript-block.mjs +21 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP Transport — new MCP transport.
|
|
3
|
+
*
|
|
4
|
+
* Uses POST with SSE response body for bidirectional communication.
|
|
5
|
+
* Each request is a POST that returns a streaming SSE response.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class StreamableHttpTransport {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} url - HTTP endpoint URL
|
|
11
|
+
* @param {object} [options] - { headers, timeout, sessionId }
|
|
12
|
+
*/
|
|
13
|
+
constructor(url, options = {}) {
|
|
14
|
+
this.url = url;
|
|
15
|
+
this.headers = options.headers || {};
|
|
16
|
+
this.timeout = options.timeout || 30000;
|
|
17
|
+
this.sessionId = options.sessionId || null;
|
|
18
|
+
this.connected = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async connect() {
|
|
22
|
+
// Test connectivity with an empty request
|
|
23
|
+
const res = await fetch(this.url, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
Accept: 'text/event-stream',
|
|
28
|
+
...this.headers,
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ jsonrpc: '2.0', method: 'ping', params: {} }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (res.ok) {
|
|
34
|
+
this.sessionId = res.headers.get('x-session-id') || this.sessionId;
|
|
35
|
+
this.connected = true;
|
|
36
|
+
}
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Send a JSON-RPC request and receive a streaming SSE response.
|
|
42
|
+
* Collects all events and returns the final result.
|
|
43
|
+
*/
|
|
44
|
+
async request(method, params) {
|
|
45
|
+
const id = Date.now();
|
|
46
|
+
const body = { jsonrpc: '2.0', id, method, params };
|
|
47
|
+
|
|
48
|
+
const headers = {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
Accept: 'text/event-stream',
|
|
51
|
+
...this.headers,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (this.sessionId) {
|
|
55
|
+
headers['x-session-id'] = this.sessionId;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const res = await fetch(this.url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers,
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
throw new Error(`sHTTP error: ${res.status} ${res.statusText}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const contentType = res.headers.get('content-type') || '';
|
|
69
|
+
|
|
70
|
+
// If SSE response, stream it
|
|
71
|
+
if (contentType.includes('text/event-stream')) {
|
|
72
|
+
return this._readSSEResponse(res);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Otherwise, plain JSON
|
|
76
|
+
return res.json();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async _readSSEResponse(res) {
|
|
80
|
+
const reader = res.body.getReader();
|
|
81
|
+
const decoder = new TextDecoder();
|
|
82
|
+
let buffer = '';
|
|
83
|
+
let result = null;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
while (true) {
|
|
87
|
+
const { done, value } = await reader.read();
|
|
88
|
+
if (done) break;
|
|
89
|
+
|
|
90
|
+
buffer += decoder.decode(value, { stream: true });
|
|
91
|
+
const events = buffer.split('\n\n');
|
|
92
|
+
buffer = events.pop() || '';
|
|
93
|
+
|
|
94
|
+
for (const raw of events) {
|
|
95
|
+
const parsed = this._parseEvent(raw);
|
|
96
|
+
if (parsed?.data?.result !== undefined) {
|
|
97
|
+
result = parsed.data.result;
|
|
98
|
+
} else if (parsed?.data?.error) {
|
|
99
|
+
throw new Error(parsed.data.error.message || 'Unknown error');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
reader.releaseLock();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_parseEvent(raw) {
|
|
111
|
+
const dataLines = [];
|
|
112
|
+
for (const line of raw.split('\n')) {
|
|
113
|
+
if (line.startsWith('data: ')) dataLines.push(line.slice(6));
|
|
114
|
+
}
|
|
115
|
+
if (dataLines.length === 0) return null;
|
|
116
|
+
try {
|
|
117
|
+
return { data: JSON.parse(dataLines.join('\n')) };
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async send(message) {
|
|
124
|
+
return this.request(message.method, message.params);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async disconnect() {
|
|
128
|
+
this.connected = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE Transport — Server-Sent Events transport for MCP.
|
|
3
|
+
*
|
|
4
|
+
* Connects to an MCP server over HTTP using SSE for server-to-client
|
|
5
|
+
* messages and POST for client-to-server messages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class SseTransport {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} url - SSE endpoint URL
|
|
11
|
+
* @param {object} [options] - { headers, timeout }
|
|
12
|
+
*/
|
|
13
|
+
constructor(url, options = {}) {
|
|
14
|
+
this.url = url;
|
|
15
|
+
this.headers = options.headers || {};
|
|
16
|
+
this.timeout = options.timeout || 30000;
|
|
17
|
+
this.connected = false;
|
|
18
|
+
this.messageHandlers = [];
|
|
19
|
+
this.reader = null;
|
|
20
|
+
this.abortController = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async connect() {
|
|
24
|
+
this.abortController = new AbortController();
|
|
25
|
+
|
|
26
|
+
const res = await fetch(this.url, {
|
|
27
|
+
headers: {
|
|
28
|
+
Accept: 'text/event-stream',
|
|
29
|
+
...this.headers,
|
|
30
|
+
},
|
|
31
|
+
signal: this.abortController.signal,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
throw new Error(`SSE connect failed: HTTP ${res.status}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.connected = true;
|
|
39
|
+
this.reader = res.body.getReader();
|
|
40
|
+
this._readLoop();
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async _readLoop() {
|
|
45
|
+
const decoder = new TextDecoder();
|
|
46
|
+
let buffer = '';
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
while (this.connected) {
|
|
50
|
+
const { done, value } = await this.reader.read();
|
|
51
|
+
if (done) break;
|
|
52
|
+
|
|
53
|
+
buffer += decoder.decode(value, { stream: true });
|
|
54
|
+
const events = buffer.split('\n\n');
|
|
55
|
+
buffer = events.pop() || '';
|
|
56
|
+
|
|
57
|
+
for (const raw of events) {
|
|
58
|
+
const parsed = this._parseSSE(raw);
|
|
59
|
+
if (parsed) {
|
|
60
|
+
for (const handler of this.messageHandlers) {
|
|
61
|
+
handler(parsed);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err.name !== 'AbortError') {
|
|
68
|
+
this._handleError(err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_parseSSE(raw) {
|
|
74
|
+
let eventType = 'message';
|
|
75
|
+
const dataLines = [];
|
|
76
|
+
|
|
77
|
+
for (const line of raw.split('\n')) {
|
|
78
|
+
if (line.startsWith('event: ')) eventType = line.slice(7).trim();
|
|
79
|
+
else if (line.startsWith('data: ')) dataLines.push(line.slice(6));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (dataLines.length === 0) return null;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
return {
|
|
86
|
+
type: eventType,
|
|
87
|
+
data: JSON.parse(dataLines.join('\n')),
|
|
88
|
+
};
|
|
89
|
+
} catch {
|
|
90
|
+
return { type: eventType, data: dataLines.join('\n') };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async send(message) {
|
|
95
|
+
const res = await fetch(this.url, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: {
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
...this.headers,
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify(message),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw new Error(`SSE send failed: HTTP ${res.status}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const text = await res.text();
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(text);
|
|
111
|
+
} catch {
|
|
112
|
+
return text;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
onMessage(handler) {
|
|
117
|
+
this.messageHandlers.push(handler);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_handleError(err) {
|
|
121
|
+
if (process.env.MCP_DEBUG) {
|
|
122
|
+
console.error(`[SSE transport] Error: ${err.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async disconnect() {
|
|
127
|
+
this.connected = false;
|
|
128
|
+
this.abortController?.abort();
|
|
129
|
+
this.reader = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Transport — bidirectional MCP transport over WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Uses the ws protocol for full-duplex communication with MCP servers.
|
|
5
|
+
* Falls back to a stub if WebSocket is not available in the runtime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class WebSocketTransport {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} url - WebSocket URL (ws:// or wss://)
|
|
11
|
+
* @param {object} [options] - { headers, timeout, protocols }
|
|
12
|
+
*/
|
|
13
|
+
constructor(url, options = {}) {
|
|
14
|
+
this.url = url;
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.timeout = options.timeout || 30000;
|
|
17
|
+
this.ws = null;
|
|
18
|
+
this.connected = false;
|
|
19
|
+
this.requestId = 0;
|
|
20
|
+
this.pending = new Map();
|
|
21
|
+
this.messageHandlers = [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async connect() {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
try {
|
|
27
|
+
// Use global WebSocket if available (Node 22+)
|
|
28
|
+
const WS = globalThis.WebSocket;
|
|
29
|
+
if (!WS) {
|
|
30
|
+
throw new Error('WebSocket not available in this runtime');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.ws = new WS(this.url, this.options.protocols);
|
|
34
|
+
|
|
35
|
+
const timeout = setTimeout(() => {
|
|
36
|
+
reject(new Error('WebSocket connection timeout'));
|
|
37
|
+
}, this.timeout);
|
|
38
|
+
|
|
39
|
+
this.ws.onopen = () => {
|
|
40
|
+
clearTimeout(timeout);
|
|
41
|
+
this.connected = true;
|
|
42
|
+
resolve(this);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.ws.onmessage = (event) => {
|
|
46
|
+
this._handleMessage(event.data);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.ws.onerror = (err) => {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
reject(new Error(`WebSocket error: ${err.message || 'unknown'}`));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
this.ws.onclose = () => {
|
|
55
|
+
this.connected = false;
|
|
56
|
+
for (const [, { reject: rej }] of this.pending) {
|
|
57
|
+
rej(new Error('WebSocket closed'));
|
|
58
|
+
}
|
|
59
|
+
this.pending.clear();
|
|
60
|
+
};
|
|
61
|
+
} catch (err) {
|
|
62
|
+
reject(err);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_handleMessage(data) {
|
|
68
|
+
try {
|
|
69
|
+
const msg = JSON.parse(typeof data === 'string' ? data : data.toString());
|
|
70
|
+
|
|
71
|
+
// Handle response to pending request
|
|
72
|
+
if (msg.id && this.pending.has(msg.id)) {
|
|
73
|
+
const { resolve, reject } = this.pending.get(msg.id);
|
|
74
|
+
this.pending.delete(msg.id);
|
|
75
|
+
if (msg.error) {
|
|
76
|
+
reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
77
|
+
} else {
|
|
78
|
+
resolve(msg.result);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle notifications
|
|
84
|
+
for (const handler of this.messageHandlers) {
|
|
85
|
+
handler(msg);
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// Malformed message
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async request(method, params) {
|
|
93
|
+
if (!this.connected) throw new Error('WebSocket not connected');
|
|
94
|
+
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const id = ++this.requestId;
|
|
97
|
+
this.pending.set(id, { resolve, reject });
|
|
98
|
+
|
|
99
|
+
const timeout = setTimeout(() => {
|
|
100
|
+
this.pending.delete(id);
|
|
101
|
+
reject(new Error(`WebSocket request timeout: ${method}`));
|
|
102
|
+
}, this.timeout);
|
|
103
|
+
|
|
104
|
+
const origResolve = resolve;
|
|
105
|
+
this.pending.set(id, {
|
|
106
|
+
resolve: (val) => { clearTimeout(timeout); origResolve(val); },
|
|
107
|
+
reject: (err) => { clearTimeout(timeout); reject(err); },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.ws.send(JSON.stringify({
|
|
111
|
+
jsonrpc: '2.0',
|
|
112
|
+
id,
|
|
113
|
+
method,
|
|
114
|
+
params: params || {},
|
|
115
|
+
}));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async send(message) {
|
|
120
|
+
return this.request(message.method, message.params);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onMessage(handler) {
|
|
124
|
+
this.messageHandlers.push(handler);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async disconnect() {
|
|
128
|
+
this.connected = false;
|
|
129
|
+
if (this.ws) {
|
|
130
|
+
this.ws.close();
|
|
131
|
+
this.ws = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|