@nogataka/smart-edit 0.0.14
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 +22 -0
- package/README.md +244 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/devtools/generate_prompt_factory.d.ts +5 -0
- package/dist/devtools/generate_prompt_factory.js +114 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +34 -0
- package/dist/interprompt/index.d.ts +2 -0
- package/dist/interprompt/index.js +1 -0
- package/dist/interprompt/jinja_template.d.ts +10 -0
- package/dist/interprompt/jinja_template.js +174 -0
- package/dist/interprompt/multilang_prompt.d.ts +54 -0
- package/dist/interprompt/multilang_prompt.js +302 -0
- package/dist/interprompt/prompt_factory.d.ts +16 -0
- package/dist/interprompt/prompt_factory.js +189 -0
- package/dist/interprompt/util/class_decorators.d.ts +1 -0
- package/dist/interprompt/util/class_decorators.js +1 -0
- package/dist/interprompt/util/index.d.ts +1 -0
- package/dist/interprompt/util/index.js +1 -0
- package/dist/serena/agent.d.ts +118 -0
- package/dist/serena/agent.js +675 -0
- package/dist/serena/agno.d.ts +111 -0
- package/dist/serena/agno.js +278 -0
- package/dist/serena/analytics.d.ts +24 -0
- package/dist/serena/analytics.js +119 -0
- package/dist/serena/cli.d.ts +9 -0
- package/dist/serena/cli.js +731 -0
- package/dist/serena/code_editor.d.ts +42 -0
- package/dist/serena/code_editor.js +239 -0
- package/dist/serena/config/context_mode.d.ts +41 -0
- package/dist/serena/config/context_mode.js +239 -0
- package/dist/serena/config/serena_config.d.ts +134 -0
- package/dist/serena/config/serena_config.js +718 -0
- package/dist/serena/constants.d.ts +18 -0
- package/dist/serena/constants.js +27 -0
- package/dist/serena/dashboard.d.ts +55 -0
- package/dist/serena/dashboard.js +472 -0
- package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
- package/dist/serena/generated/generated_prompt_factory.js +42 -0
- package/dist/serena/gui_log_viewer.d.ts +41 -0
- package/dist/serena/gui_log_viewer.js +436 -0
- package/dist/serena/mcp.d.ts +118 -0
- package/dist/serena/mcp.js +904 -0
- package/dist/serena/project.d.ts +62 -0
- package/dist/serena/project.js +321 -0
- package/dist/serena/prompt_factory.d.ts +20 -0
- package/dist/serena/prompt_factory.js +42 -0
- package/dist/serena/resources/config/contexts/agent.yml +8 -0
- package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
- package/dist/serena/resources/config/contexts/codex.yml +27 -0
- package/dist/serena/resources/config/contexts/context.template.yml +11 -0
- package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
- package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
- package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
- package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
- package/dist/serena/resources/config/modes/editing.yml +112 -0
- package/dist/serena/resources/config/modes/interactive.yml +11 -0
- package/dist/serena/resources/config/modes/mode.template.yml +7 -0
- package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
- package/dist/serena/resources/config/modes/onboarding.yml +16 -0
- package/dist/serena/resources/config/modes/one-shot.yml +15 -0
- package/dist/serena/resources/config/modes/planning.yml +15 -0
- package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
- package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
- package/dist/serena/resources/dashboard/dashboard.js +815 -0
- package/dist/serena/resources/dashboard/index.html +314 -0
- package/dist/serena/resources/dashboard/jquery.min.js +3 -0
- package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs.png +0 -0
- package/dist/serena/resources/project.template.yml +67 -0
- package/dist/serena/resources/serena_config.template.yml +85 -0
- package/dist/serena/symbol.d.ts +199 -0
- package/dist/serena/symbol.js +616 -0
- package/dist/serena/text_utils.d.ts +51 -0
- package/dist/serena/text_utils.js +267 -0
- package/dist/serena/tools/cmd_tools.d.ts +31 -0
- package/dist/serena/tools/cmd_tools.js +48 -0
- package/dist/serena/tools/config_tools.d.ts +53 -0
- package/dist/serena/tools/config_tools.js +176 -0
- package/dist/serena/tools/file_tools.d.ts +231 -0
- package/dist/serena/tools/file_tools.js +511 -0
- package/dist/serena/tools/index.d.ts +7 -0
- package/dist/serena/tools/index.js +7 -0
- package/dist/serena/tools/memory_tools.d.ts +60 -0
- package/dist/serena/tools/memory_tools.js +135 -0
- package/dist/serena/tools/symbol_tools.d.ts +165 -0
- package/dist/serena/tools/symbol_tools.js +362 -0
- package/dist/serena/tools/tools_base.d.ts +162 -0
- package/dist/serena/tools/tools_base.js +378 -0
- package/dist/serena/tools/workflow_tools.d.ts +35 -0
- package/dist/serena/tools/workflow_tools.js +161 -0
- package/dist/serena/util/class_decorators.d.ts +7 -0
- package/dist/serena/util/class_decorators.js +37 -0
- package/dist/serena/util/exception.d.ts +8 -0
- package/dist/serena/util/exception.js +53 -0
- package/dist/serena/util/file_system.d.ts +30 -0
- package/dist/serena/util/file_system.js +352 -0
- package/dist/serena/util/general.d.ts +11 -0
- package/dist/serena/util/general.js +42 -0
- package/dist/serena/util/git.d.ts +11 -0
- package/dist/serena/util/git.js +37 -0
- package/dist/serena/util/inspection.d.ts +45 -0
- package/dist/serena/util/inspection.js +221 -0
- package/dist/serena/util/logging.d.ts +46 -0
- package/dist/serena/util/logging.js +205 -0
- package/dist/serena/util/shell.d.ts +21 -0
- package/dist/serena/util/shell.js +95 -0
- package/dist/serena/util/thread.d.ts +23 -0
- package/dist/serena/util/thread.js +88 -0
- package/dist/serena/version.d.ts +1 -0
- package/dist/serena/version.js +23 -0
- package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
- package/dist/solidlsp/language_servers/autoload.js +25 -0
- package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
- package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
- package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
- package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
- package/dist/solidlsp/language_servers/common.d.ts +41 -0
- package/dist/solidlsp/language_servers/common.js +365 -0
- package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
- package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
- package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
- package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
- package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
- package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
- package/dist/solidlsp/language_servers/gopls.js +59 -0
- package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
- package/dist/solidlsp/language_servers/intelephense.js +121 -0
- package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
- package/dist/solidlsp/language_servers/jedi_server.js +234 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
- package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
- package/dist/solidlsp/language_servers/lua_ls.js +319 -0
- package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
- package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
- package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/pyright_server.js +180 -0
- package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/r_language_server.js +184 -0
- package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
- package/dist/solidlsp/language_servers/ruby_common.js +136 -0
- package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
- package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
- package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
- package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
- package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
- package/dist/solidlsp/language_servers/solargraph.js +208 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
- package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
- package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
- package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
- package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
- package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
- package/dist/solidlsp/language_servers/zls.d.ts +20 -0
- package/dist/solidlsp/language_servers/zls.js +254 -0
- package/dist/solidlsp/ls.d.ts +197 -0
- package/dist/solidlsp/ls.js +507 -0
- package/dist/solidlsp/ls_config.d.ts +43 -0
- package/dist/solidlsp/ls_config.js +157 -0
- package/dist/solidlsp/ls_exceptions.d.ts +5 -0
- package/dist/solidlsp/ls_exceptions.js +14 -0
- package/dist/solidlsp/ls_handler.d.ts +54 -0
- package/dist/solidlsp/ls_handler.js +406 -0
- package/dist/solidlsp/ls_request.d.ts +31 -0
- package/dist/solidlsp/ls_request.js +42 -0
- package/dist/solidlsp/ls_types.d.ts +7 -0
- package/dist/solidlsp/ls_types.js +8 -0
- package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
- package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
- package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
- package/dist/solidlsp/util/subprocess_util.js +11 -0
- package/dist/solidlsp/util/zip.d.ts +25 -0
- package/dist/solidlsp/util/zip.js +188 -0
- package/package.json +65 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SolidLSP の Python 実装における Language Enum の TypeScript 版。
|
|
3
|
+
* 現段階では config 層からの利用に必要な最小機能(列挙値と簡易ユーティリティ)のみを提供する。
|
|
4
|
+
* TODO: SolidLSP モジュール移植時に Python 版の完全な API と一致するよう拡張する。
|
|
5
|
+
*/
|
|
6
|
+
export declare enum Language {
|
|
7
|
+
CSHARP = "csharp",
|
|
8
|
+
PYTHON = "python",
|
|
9
|
+
RUST = "rust",
|
|
10
|
+
JAVA = "java",
|
|
11
|
+
KOTLIN = "kotlin",
|
|
12
|
+
TYPESCRIPT = "typescript",
|
|
13
|
+
GO = "go",
|
|
14
|
+
RUBY = "ruby",
|
|
15
|
+
DART = "dart",
|
|
16
|
+
CPP = "cpp",
|
|
17
|
+
PHP = "php",
|
|
18
|
+
R = "r",
|
|
19
|
+
CLOJURE = "clojure",
|
|
20
|
+
ELIXIR = "elixir",
|
|
21
|
+
TERRAFORM = "terraform",
|
|
22
|
+
SWIFT = "swift",
|
|
23
|
+
BASH = "bash",
|
|
24
|
+
ZIG = "zig",
|
|
25
|
+
LUA = "lua",
|
|
26
|
+
NIX = "nix",
|
|
27
|
+
ERLANG = "erlang",
|
|
28
|
+
AL = "al",
|
|
29
|
+
TYPESCRIPT_VTS = "typescript_vts",
|
|
30
|
+
PYTHON_JEDI = "python_jedi",
|
|
31
|
+
CSHARP_OMNISHARP = "csharp_omnisharp",
|
|
32
|
+
RUBY_SOLARGRAPH = "ruby_solargraph"
|
|
33
|
+
}
|
|
34
|
+
export interface ListLanguagesOptions {
|
|
35
|
+
includeExperimental?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function listLanguages(options?: ListLanguagesOptions): Language[];
|
|
38
|
+
export declare function isExperimentalLanguage(language: Language): boolean;
|
|
39
|
+
export declare function coerceLanguage(value: string): Language;
|
|
40
|
+
export interface FilenameMatcherLike {
|
|
41
|
+
isRelevantFilename(filename: string): boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function getLanguageFilenameMatcher(language: Language): FilenameMatcherLike;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SolidLSP の Python 実装における Language Enum の TypeScript 版。
|
|
3
|
+
* 現段階では config 層からの利用に必要な最小機能(列挙値と簡易ユーティリティ)のみを提供する。
|
|
4
|
+
* TODO: SolidLSP モジュール移植時に Python 版の完全な API と一致するよう拡張する。
|
|
5
|
+
*/
|
|
6
|
+
export var Language;
|
|
7
|
+
(function (Language) {
|
|
8
|
+
Language["CSHARP"] = "csharp";
|
|
9
|
+
Language["PYTHON"] = "python";
|
|
10
|
+
Language["RUST"] = "rust";
|
|
11
|
+
Language["JAVA"] = "java";
|
|
12
|
+
Language["KOTLIN"] = "kotlin";
|
|
13
|
+
Language["TYPESCRIPT"] = "typescript";
|
|
14
|
+
Language["GO"] = "go";
|
|
15
|
+
Language["RUBY"] = "ruby";
|
|
16
|
+
Language["DART"] = "dart";
|
|
17
|
+
Language["CPP"] = "cpp";
|
|
18
|
+
Language["PHP"] = "php";
|
|
19
|
+
Language["R"] = "r";
|
|
20
|
+
Language["CLOJURE"] = "clojure";
|
|
21
|
+
Language["ELIXIR"] = "elixir";
|
|
22
|
+
Language["TERRAFORM"] = "terraform";
|
|
23
|
+
Language["SWIFT"] = "swift";
|
|
24
|
+
Language["BASH"] = "bash";
|
|
25
|
+
Language["ZIG"] = "zig";
|
|
26
|
+
Language["LUA"] = "lua";
|
|
27
|
+
Language["NIX"] = "nix";
|
|
28
|
+
Language["ERLANG"] = "erlang";
|
|
29
|
+
Language["AL"] = "al";
|
|
30
|
+
Language["TYPESCRIPT_VTS"] = "typescript_vts";
|
|
31
|
+
Language["PYTHON_JEDI"] = "python_jedi";
|
|
32
|
+
Language["CSHARP_OMNISHARP"] = "csharp_omnisharp";
|
|
33
|
+
Language["RUBY_SOLARGRAPH"] = "ruby_solargraph";
|
|
34
|
+
})(Language || (Language = {}));
|
|
35
|
+
const EXPERIMENTAL_LANGUAGES = new Set([
|
|
36
|
+
Language.TYPESCRIPT_VTS,
|
|
37
|
+
Language.PYTHON_JEDI,
|
|
38
|
+
Language.CSHARP_OMNISHARP,
|
|
39
|
+
Language.RUBY_SOLARGRAPH
|
|
40
|
+
]);
|
|
41
|
+
const LANGUAGE_LOOKUP = new Map(Object.values(Language).map((value) => [value.toLowerCase(), value]));
|
|
42
|
+
export function listLanguages(options) {
|
|
43
|
+
const includeExperimental = options?.includeExperimental ?? false;
|
|
44
|
+
return Object.values(Language).filter((lang) => includeExperimental || !EXPERIMENTAL_LANGUAGES.has(lang));
|
|
45
|
+
}
|
|
46
|
+
export function isExperimentalLanguage(language) {
|
|
47
|
+
return EXPERIMENTAL_LANGUAGES.has(language);
|
|
48
|
+
}
|
|
49
|
+
export function coerceLanguage(value) {
|
|
50
|
+
const normalized = value.toLowerCase();
|
|
51
|
+
const found = LANGUAGE_LOOKUP.get(normalized);
|
|
52
|
+
if (!found) {
|
|
53
|
+
const valid = Array.from(LANGUAGE_LOOKUP.keys()).sort().join(', ');
|
|
54
|
+
throw new RangeError(`Unsupported language "${value}". 有効な値: ${valid}`);
|
|
55
|
+
}
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
const CASE_INSENSITIVE = process.platform === 'win32';
|
|
59
|
+
function globToRegExp(pattern) {
|
|
60
|
+
let regex = '';
|
|
61
|
+
for (const char of pattern) {
|
|
62
|
+
if (char === '*') {
|
|
63
|
+
regex += '.*';
|
|
64
|
+
}
|
|
65
|
+
else if (char === '?') {
|
|
66
|
+
regex += '.';
|
|
67
|
+
}
|
|
68
|
+
else if ('\\[]{}()+-.^$|'.includes(char)) {
|
|
69
|
+
regex += `\\${char}`;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
regex += char;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return new RegExp(`^${regex}$`, CASE_INSENSITIVE ? 'i' : undefined);
|
|
76
|
+
}
|
|
77
|
+
function createMatcher(patterns) {
|
|
78
|
+
if (patterns.length === 0) {
|
|
79
|
+
return () => true;
|
|
80
|
+
}
|
|
81
|
+
const regexes = patterns.map((pattern) => globToRegExp(pattern));
|
|
82
|
+
return (filename) => regexes.some((regex) => regex.test(filename));
|
|
83
|
+
}
|
|
84
|
+
function getPatternsForLanguage(language) {
|
|
85
|
+
switch (language) {
|
|
86
|
+
case Language.PYTHON:
|
|
87
|
+
case Language.PYTHON_JEDI:
|
|
88
|
+
return ['*.py', '*.pyi'];
|
|
89
|
+
case Language.JAVA:
|
|
90
|
+
return ['*.java'];
|
|
91
|
+
case Language.TYPESCRIPT:
|
|
92
|
+
case Language.TYPESCRIPT_VTS: {
|
|
93
|
+
const patterns = [];
|
|
94
|
+
for (const prefix of ['c', 'm', '']) {
|
|
95
|
+
for (const postfix of ['x', '']) {
|
|
96
|
+
for (const base of ['ts', 'js']) {
|
|
97
|
+
patterns.push(`*.${prefix}${base}${postfix}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return patterns;
|
|
102
|
+
}
|
|
103
|
+
case Language.CSHARP:
|
|
104
|
+
case Language.CSHARP_OMNISHARP:
|
|
105
|
+
return ['*.cs'];
|
|
106
|
+
case Language.RUST:
|
|
107
|
+
return ['*.rs'];
|
|
108
|
+
case Language.GO:
|
|
109
|
+
return ['*.go'];
|
|
110
|
+
case Language.RUBY:
|
|
111
|
+
return ['*.rb', '*.erb'];
|
|
112
|
+
case Language.RUBY_SOLARGRAPH:
|
|
113
|
+
return ['*.rb'];
|
|
114
|
+
case Language.CPP:
|
|
115
|
+
return ['*.cpp', '*.h', '*.hpp', '*.c', '*.hxx', '*.cc', '*.cxx'];
|
|
116
|
+
case Language.KOTLIN:
|
|
117
|
+
return ['*.kt', '*.kts'];
|
|
118
|
+
case Language.DART:
|
|
119
|
+
return ['*.dart'];
|
|
120
|
+
case Language.PHP:
|
|
121
|
+
return ['*.php'];
|
|
122
|
+
case Language.R:
|
|
123
|
+
return ['*.R', '*.r', '*.Rmd', '*.Rnw'];
|
|
124
|
+
case Language.CLOJURE:
|
|
125
|
+
return ['*.clj', '*.cljs', '*.cljc', '*.edn'];
|
|
126
|
+
case Language.ELIXIR:
|
|
127
|
+
return ['*.ex', '*.exs'];
|
|
128
|
+
case Language.TERRAFORM:
|
|
129
|
+
return ['*.tf', '*.tfvars', '*.tfstate'];
|
|
130
|
+
case Language.SWIFT:
|
|
131
|
+
return ['*.swift'];
|
|
132
|
+
case Language.BASH:
|
|
133
|
+
return ['*.sh', '*.bash'];
|
|
134
|
+
case Language.ZIG:
|
|
135
|
+
return ['*.zig', '*.zon'];
|
|
136
|
+
case Language.LUA:
|
|
137
|
+
return ['*.lua'];
|
|
138
|
+
case Language.NIX:
|
|
139
|
+
return ['*.nix'];
|
|
140
|
+
case Language.ERLANG:
|
|
141
|
+
return ['*.erl', '*.hrl', '*.escript', '*.config', '*.app', '*.app.src'];
|
|
142
|
+
case Language.AL:
|
|
143
|
+
return ['*.al', '*.dal'];
|
|
144
|
+
default:
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function getLanguageFilenameMatcher(language) {
|
|
149
|
+
const patterns = getPatternsForLanguage(language);
|
|
150
|
+
if (!patterns.length) {
|
|
151
|
+
throw new RangeError(`Unhandled language '${language}' for filename matcher`);
|
|
152
|
+
}
|
|
153
|
+
const matcher = createMatcher(patterns);
|
|
154
|
+
return {
|
|
155
|
+
isRelevantFilename: (filename) => matcher(filename)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class SolidLSPException extends Error {
|
|
2
|
+
cause;
|
|
3
|
+
constructor(message, cause) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'SolidLSPException';
|
|
6
|
+
this.cause = cause;
|
|
7
|
+
}
|
|
8
|
+
isLanguageServerTerminated() {
|
|
9
|
+
if (!this.cause) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return this.cause.name === 'LanguageServerTerminatedException';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type DocumentSymbolRequestParams, type LanguageServerRequestDelegate } from './ls_request.js';
|
|
2
|
+
import { LSPError, type PayloadLike, type ProcessLaunchInfo } from './lsp_protocol_handler/server.js';
|
|
3
|
+
import type { DocumentSymbolResult, FullSymbolTreeOptions, ReferenceInSymbol, ReferencingSymbolsOptions, SolidLanguageServerHandler, SolidLanguageServerNotifications, SolidLanguageServerRequests, UnifiedSymbolInformation } from './ls.js';
|
|
4
|
+
type WireLogger = (source: string, destination: string, payload: unknown) => void;
|
|
5
|
+
export declare class LanguageServerTerminatedException extends Error {
|
|
6
|
+
readonly cause?: Error;
|
|
7
|
+
constructor(message: string, cause?: Error);
|
|
8
|
+
}
|
|
9
|
+
export interface NodeLanguageServerHandlerOptions {
|
|
10
|
+
logger?: WireLogger;
|
|
11
|
+
startIndependentProcessGroup?: boolean;
|
|
12
|
+
requestTimeoutSeconds?: number | null;
|
|
13
|
+
}
|
|
14
|
+
export declare class NodeLanguageServerHandler implements SolidLanguageServerHandler, LanguageServerRequestDelegate {
|
|
15
|
+
readonly send: SolidLanguageServerRequests;
|
|
16
|
+
readonly notify: SolidLanguageServerNotifications;
|
|
17
|
+
private readonly processLaunchInfo;
|
|
18
|
+
private readonly logger?;
|
|
19
|
+
private readonly startIndependentProcessGroup;
|
|
20
|
+
private child;
|
|
21
|
+
private stdoutBuffer;
|
|
22
|
+
private stdoutFd;
|
|
23
|
+
private requestId;
|
|
24
|
+
private requestTimeoutSeconds;
|
|
25
|
+
private shuttingDown;
|
|
26
|
+
private readonly requestHandlers;
|
|
27
|
+
private readonly notificationHandlers;
|
|
28
|
+
constructor(processLaunchInfo: ProcessLaunchInfo, options?: NodeLanguageServerHandlerOptions);
|
|
29
|
+
setRequestTimeout(timeout: number | null): void;
|
|
30
|
+
isRunning(): boolean;
|
|
31
|
+
start(): void;
|
|
32
|
+
shutdown(): void;
|
|
33
|
+
dispose(): void;
|
|
34
|
+
sendRequest(method: 'textDocument/documentSymbol', params: DocumentSymbolRequestParams): DocumentSymbolResult | null;
|
|
35
|
+
sendRequest(method: 'serena/fullSymbolTree', params: FullSymbolTreeOptions): UnifiedSymbolInformation[] | null;
|
|
36
|
+
sendRequest(method: 'serena/referencingSymbols', params: ReferencingSymbolsOptions): ReferenceInSymbol[] | null;
|
|
37
|
+
sendRequest(method: 'serena/overview', params: string): Record<string, UnifiedSymbolInformation[]> | null;
|
|
38
|
+
sendRequest(method: 'shutdown'): void;
|
|
39
|
+
sendRequest(method: string, params?: unknown): unknown;
|
|
40
|
+
private waitForResponse;
|
|
41
|
+
private readNextMessage;
|
|
42
|
+
private extractMessageFromBuffer;
|
|
43
|
+
private handleServerRequest;
|
|
44
|
+
private handleNotification;
|
|
45
|
+
sendNotification(method: string, params?: PayloadLike): void;
|
|
46
|
+
sendResponse(requestId: number | string | null, params: PayloadLike): void;
|
|
47
|
+
sendErrorResponse(requestId: number | string | null, err: LSPError): void;
|
|
48
|
+
on_request(method: string, handler: (params: unknown) => unknown): void;
|
|
49
|
+
onRequest(method: string, handler: (params: unknown) => unknown): void;
|
|
50
|
+
onNotification(method: string, handler: (params: unknown) => void): void;
|
|
51
|
+
private sendPayload;
|
|
52
|
+
private logWire;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { Buffer } from 'node:buffer';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { ensureDefaultSubprocessOptions } from './util/subprocess_util.js';
|
|
5
|
+
import { SolidLSPException } from './ls_exceptions.js';
|
|
6
|
+
import { LspNotification, LanguageServerRequest } from './ls_request.js';
|
|
7
|
+
import { ErrorCodes } from './ls_types.js';
|
|
8
|
+
import { ENCODING, LSPError, createMessage, makeErrorResponse, makeNotification, makeRequest, makeResponse } from './lsp_protocol_handler/server.js';
|
|
9
|
+
const HEADER_SEPARATOR = Buffer.from('\r\n\r\n', ENCODING);
|
|
10
|
+
function cloneEnv(extra) {
|
|
11
|
+
return { ...process.env, ...(extra ?? {}) };
|
|
12
|
+
}
|
|
13
|
+
function normalizeCommand(cmd) {
|
|
14
|
+
if (Array.isArray(cmd)) {
|
|
15
|
+
const [first, ...rest] = cmd;
|
|
16
|
+
if (!first) {
|
|
17
|
+
throw new Error('Process command cannot be empty.');
|
|
18
|
+
}
|
|
19
|
+
return { command: first, args: rest, shell: false };
|
|
20
|
+
}
|
|
21
|
+
return { command: cmd, args: [], shell: true };
|
|
22
|
+
}
|
|
23
|
+
export class LanguageServerTerminatedException extends Error {
|
|
24
|
+
cause;
|
|
25
|
+
constructor(message, cause) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'LanguageServerTerminatedException';
|
|
28
|
+
this.cause = cause;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class NodeLanguageServerHandler {
|
|
32
|
+
send;
|
|
33
|
+
notify;
|
|
34
|
+
processLaunchInfo;
|
|
35
|
+
logger;
|
|
36
|
+
startIndependentProcessGroup;
|
|
37
|
+
child = null;
|
|
38
|
+
stdoutBuffer = Buffer.alloc(0);
|
|
39
|
+
stdoutFd = null;
|
|
40
|
+
requestId = 1;
|
|
41
|
+
requestTimeoutSeconds;
|
|
42
|
+
shuttingDown = false;
|
|
43
|
+
requestHandlers = new Map();
|
|
44
|
+
notificationHandlers = new Map();
|
|
45
|
+
constructor(processLaunchInfo, options = {}) {
|
|
46
|
+
this.processLaunchInfo = processLaunchInfo;
|
|
47
|
+
this.logger = options.logger;
|
|
48
|
+
this.startIndependentProcessGroup = options.startIndependentProcessGroup ?? true;
|
|
49
|
+
this.requestTimeoutSeconds = options.requestTimeoutSeconds ?? null;
|
|
50
|
+
this.send = new LanguageServerRequest(this);
|
|
51
|
+
this.notify = new LspNotification((method, params) => this.sendNotification(method, (params ?? null)));
|
|
52
|
+
}
|
|
53
|
+
setRequestTimeout(timeout) {
|
|
54
|
+
this.requestTimeoutSeconds = timeout ?? null;
|
|
55
|
+
}
|
|
56
|
+
isRunning() {
|
|
57
|
+
return this.child !== null && this.child.exitCode === null;
|
|
58
|
+
}
|
|
59
|
+
start() {
|
|
60
|
+
if (this.child) {
|
|
61
|
+
throw new Error('Language server already started');
|
|
62
|
+
}
|
|
63
|
+
const { command, args, shell } = normalizeCommand(this.processLaunchInfo.cmd);
|
|
64
|
+
const env = cloneEnv(this.processLaunchInfo.env);
|
|
65
|
+
const cwd = this.processLaunchInfo.cwd ?? process.cwd();
|
|
66
|
+
const spawnOptions = ensureDefaultSubprocessOptions({
|
|
67
|
+
cwd,
|
|
68
|
+
env,
|
|
69
|
+
shell,
|
|
70
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
71
|
+
detached: this.startIndependentProcessGroup
|
|
72
|
+
});
|
|
73
|
+
const child = spawn(command, args, spawnOptions);
|
|
74
|
+
if (!child.stdin || !child.stdout || !child.stderr) {
|
|
75
|
+
child.kill();
|
|
76
|
+
throw new Error('Failed to spawn language server process with stdio pipes.');
|
|
77
|
+
}
|
|
78
|
+
this.child = child;
|
|
79
|
+
this.stdoutBuffer = Buffer.alloc(0);
|
|
80
|
+
this.stdoutFd = getStreamFd(child.stdout);
|
|
81
|
+
child.stdout.pause();
|
|
82
|
+
child.stdin.on('error', (error) => {
|
|
83
|
+
const code = error.code ?? 'UNKNOWN';
|
|
84
|
+
if (code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED') {
|
|
85
|
+
if (!this.shuttingDown) {
|
|
86
|
+
this.logWire('client', 'server', { type: 'stdin-error', code, message: error.message });
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.logWire('client', 'server', { type: 'stdin-error', code, message: error.message });
|
|
91
|
+
});
|
|
92
|
+
child.stderr.setEncoding(ENCODING);
|
|
93
|
+
child.stderr.on('data', (chunk) => {
|
|
94
|
+
const message = chunk.trimEnd();
|
|
95
|
+
if (!message) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (message.toLowerCase().includes('error') || message.startsWith('E[')) {
|
|
99
|
+
if (this.logger) {
|
|
100
|
+
this.logger('server', 'stderr', message);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
child.on('exit', (code, signal) => {
|
|
105
|
+
this.child = null;
|
|
106
|
+
this.stdoutFd = null;
|
|
107
|
+
if (this.shuttingDown) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const reason = `Language server exited with code ${code} signal ${signal ?? 'none'}`;
|
|
111
|
+
this.stdoutBuffer = Buffer.alloc(0);
|
|
112
|
+
this.shuttingDown = false;
|
|
113
|
+
this.logWire('server', 'client', reason);
|
|
114
|
+
});
|
|
115
|
+
const immediateExit = child.exitCode !== null;
|
|
116
|
+
if (immediateExit) {
|
|
117
|
+
const rawStderr = child.stderr.read();
|
|
118
|
+
const stderr = normalizeStreamOutput(rawStderr);
|
|
119
|
+
throw new Error(`Language server process terminated immediately with code ${child.exitCode}. ${stderr}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
shutdown() {
|
|
123
|
+
if (!this.child) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.shuttingDown = true;
|
|
127
|
+
try {
|
|
128
|
+
this.send.shutdown();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
// Ignore errors on shutdown path.
|
|
132
|
+
void error;
|
|
133
|
+
}
|
|
134
|
+
this.notify.exit();
|
|
135
|
+
}
|
|
136
|
+
dispose() {
|
|
137
|
+
const child = this.child;
|
|
138
|
+
this.child = null;
|
|
139
|
+
if (!child) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
child.stdin?.end();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
void error;
|
|
147
|
+
}
|
|
148
|
+
if (child.exitCode === null) {
|
|
149
|
+
child.kill('SIGTERM');
|
|
150
|
+
const timeout = globalThis.setTimeout(() => {
|
|
151
|
+
if (child.exitCode === null) {
|
|
152
|
+
child.kill('SIGKILL');
|
|
153
|
+
}
|
|
154
|
+
}, 5_000);
|
|
155
|
+
child.once('exit', () => globalThis.clearTimeout(timeout));
|
|
156
|
+
}
|
|
157
|
+
child.stdout?.destroy();
|
|
158
|
+
child.stderr?.destroy();
|
|
159
|
+
}
|
|
160
|
+
sendRequest(method, params) {
|
|
161
|
+
const child = this.child;
|
|
162
|
+
if (!child?.stdin || this.stdoutFd === null) {
|
|
163
|
+
throw new SolidLSPException('Language server is not running.');
|
|
164
|
+
}
|
|
165
|
+
const id = this.requestId;
|
|
166
|
+
this.requestId += 1;
|
|
167
|
+
const payload = makeRequest(method, id, (params ?? null));
|
|
168
|
+
this.logWire('client', 'server', payload);
|
|
169
|
+
child.stdin.write(createMessage(payload));
|
|
170
|
+
const response = this.waitForResponse(id, method);
|
|
171
|
+
switch (method) {
|
|
172
|
+
case 'textDocument/documentSymbol':
|
|
173
|
+
return (response ?? null);
|
|
174
|
+
case 'serena/fullSymbolTree':
|
|
175
|
+
return (response ?? null);
|
|
176
|
+
case 'serena/referencingSymbols':
|
|
177
|
+
return (response ?? null);
|
|
178
|
+
case 'serena/overview':
|
|
179
|
+
return (response ?? null);
|
|
180
|
+
case 'shutdown':
|
|
181
|
+
return undefined;
|
|
182
|
+
default:
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
waitForResponse(requestId, method) {
|
|
187
|
+
const timeoutMillis = this.requestTimeoutSeconds != null ? this.requestTimeoutSeconds * 1000 : null;
|
|
188
|
+
const deadline = timeoutMillis != null ? Date.now() + timeoutMillis : null;
|
|
189
|
+
while (true) {
|
|
190
|
+
const message = this.readNextMessage(deadline);
|
|
191
|
+
if (!message) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if ('id' in message && Object.prototype.hasOwnProperty.call(message, 'result')) {
|
|
195
|
+
const response = message;
|
|
196
|
+
if (response.id === requestId) {
|
|
197
|
+
return response.result;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if ('id' in message && Object.prototype.hasOwnProperty.call(message, 'error')) {
|
|
201
|
+
const response = message;
|
|
202
|
+
if (response.id === requestId) {
|
|
203
|
+
const errorPayload = (response.error ?? {});
|
|
204
|
+
const lspError = LSPError.fromLsp(errorPayload);
|
|
205
|
+
throw new SolidLSPException(`Error processing request ${method}`, lspError);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (isJsonRpcRequest(message)) {
|
|
209
|
+
if (Object.prototype.hasOwnProperty.call(message, 'id')) {
|
|
210
|
+
this.handleServerRequest(message);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.handleNotification(message);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
readNextMessage(deadline) {
|
|
219
|
+
const fd = this.stdoutFd;
|
|
220
|
+
if (fd === null) {
|
|
221
|
+
throw new SolidLSPException('Language server stdout is not available.');
|
|
222
|
+
}
|
|
223
|
+
while (true) {
|
|
224
|
+
const message = this.extractMessageFromBuffer();
|
|
225
|
+
if (message) {
|
|
226
|
+
this.logWire('server', 'client', message);
|
|
227
|
+
return message;
|
|
228
|
+
}
|
|
229
|
+
if (deadline !== null && Date.now() > deadline) {
|
|
230
|
+
throw new SolidLSPException(`Request timed out after ${this.requestTimeoutSeconds} seconds.`);
|
|
231
|
+
}
|
|
232
|
+
const chunk = Buffer.allocUnsafe(4096);
|
|
233
|
+
let bytesRead;
|
|
234
|
+
try {
|
|
235
|
+
bytesRead = fs.readSync(fd, chunk, 0, chunk.length, null);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
const err = error;
|
|
239
|
+
if (err.code === 'EAGAIN') {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
throw new SolidLSPException('Failed to read from language server stdout.', err);
|
|
243
|
+
}
|
|
244
|
+
if (bytesRead === 0) {
|
|
245
|
+
if (!this.isRunning()) {
|
|
246
|
+
throw new LanguageServerTerminatedException('Language server terminated while waiting for response.');
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, chunk.subarray(0, bytesRead)]);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
extractMessageFromBuffer() {
|
|
254
|
+
if (this.stdoutBuffer.length === 0) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
const headerEnd = this.stdoutBuffer.indexOf(HEADER_SEPARATOR);
|
|
258
|
+
if (headerEnd === -1) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const header = this.stdoutBuffer.subarray(0, headerEnd).toString(ENCODING);
|
|
262
|
+
const headers = header.split('\r\n');
|
|
263
|
+
let contentLengthValue = null;
|
|
264
|
+
for (const line of headers) {
|
|
265
|
+
try {
|
|
266
|
+
const length = getContentLengthFromHeader(line);
|
|
267
|
+
if (length !== null) {
|
|
268
|
+
contentLengthValue = length;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
throw new SolidLSPException('Invalid Content-Length header.', error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (contentLengthValue === null) {
|
|
277
|
+
// Drop malformed header and continue.
|
|
278
|
+
this.stdoutBuffer = this.stdoutBuffer.subarray(headerEnd + HEADER_SEPARATOR.length);
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const totalLength = headerEnd + HEADER_SEPARATOR.length + contentLengthValue;
|
|
282
|
+
if (this.stdoutBuffer.length < totalLength) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const body = this.stdoutBuffer.subarray(headerEnd + HEADER_SEPARATOR.length, totalLength).toString(ENCODING);
|
|
286
|
+
this.stdoutBuffer = this.stdoutBuffer.subarray(totalLength);
|
|
287
|
+
try {
|
|
288
|
+
return JSON.parse(body);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
throw new SolidLSPException('Malformed JSON payload received from language server.', error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
handleServerRequest(request) {
|
|
295
|
+
const handler = this.requestHandlers.get(request.method);
|
|
296
|
+
if (!handler) {
|
|
297
|
+
const err = new LSPError(ErrorCodes.MethodNotFound, `Unhandled method '${request.method}'.`);
|
|
298
|
+
this.sendPayload(makeErrorResponse(request.id ?? null, err));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const result = handler(request.params);
|
|
303
|
+
this.sendPayload(makeResponse(request.id ?? null, result));
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
const err = error instanceof LSPError ? error : new LSPError(ErrorCodes.InternalError, String(error));
|
|
307
|
+
this.sendPayload(makeErrorResponse(request.id ?? null, err));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
handleNotification(request) {
|
|
311
|
+
const handler = this.notificationHandlers.get(request.method);
|
|
312
|
+
if (!handler) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
handler(request.params);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
void error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
sendNotification(method, params = null) {
|
|
323
|
+
this.sendPayload(makeNotification(method, params));
|
|
324
|
+
}
|
|
325
|
+
sendResponse(requestId, params) {
|
|
326
|
+
this.sendPayload(makeResponse(requestId, params));
|
|
327
|
+
}
|
|
328
|
+
sendErrorResponse(requestId, err) {
|
|
329
|
+
this.sendPayload(makeErrorResponse(requestId, err));
|
|
330
|
+
}
|
|
331
|
+
on_request(method, handler) {
|
|
332
|
+
this.onRequest(method, handler);
|
|
333
|
+
}
|
|
334
|
+
onRequest(method, handler) {
|
|
335
|
+
this.requestHandlers.set(method, handler);
|
|
336
|
+
}
|
|
337
|
+
onNotification(method, handler) {
|
|
338
|
+
this.notificationHandlers.set(method, handler);
|
|
339
|
+
}
|
|
340
|
+
sendPayload(payload) {
|
|
341
|
+
const child = this.child;
|
|
342
|
+
if (!child?.stdin || child.stdin.destroyed) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
this.logWire('client', 'server', payload);
|
|
346
|
+
try {
|
|
347
|
+
child.stdin.write(createMessage(payload));
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
const err = error;
|
|
351
|
+
if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
|
|
352
|
+
if (!this.shuttingDown) {
|
|
353
|
+
throw new LanguageServerTerminatedException('Language server stdin is no longer available.', err);
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
throw new SolidLSPException('Failed to write to language server stdin.', err);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
logWire(source, destination, payload) {
|
|
361
|
+
if (!this.logger) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
this.logger(source, destination, payload);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
void error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function isJsonRpcRequest(message) {
|
|
373
|
+
return typeof message.method === 'string';
|
|
374
|
+
}
|
|
375
|
+
function getStreamFd(stream) {
|
|
376
|
+
if (!stream) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
const direct = stream.fd;
|
|
380
|
+
if (typeof direct === 'number') {
|
|
381
|
+
return direct;
|
|
382
|
+
}
|
|
383
|
+
const handleFd = stream._handle?.fd;
|
|
384
|
+
return typeof handleFd === 'number' ? handleFd : null;
|
|
385
|
+
}
|
|
386
|
+
function normalizeStreamOutput(value) {
|
|
387
|
+
if (typeof value === 'string') {
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
390
|
+
if (Buffer.isBuffer(value)) {
|
|
391
|
+
return value.toString(ENCODING);
|
|
392
|
+
}
|
|
393
|
+
return '';
|
|
394
|
+
}
|
|
395
|
+
function getContentLengthFromHeader(line) {
|
|
396
|
+
const normalized = line.trim().toLowerCase();
|
|
397
|
+
if (!normalized.startsWith('content-length:')) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
const value = normalized.slice('content-length:'.length).trim();
|
|
401
|
+
const parsed = Number.parseInt(value, 10);
|
|
402
|
+
if (Number.isNaN(parsed)) {
|
|
403
|
+
throw new Error(`Invalid Content-Length header value: ${value}`);
|
|
404
|
+
}
|
|
405
|
+
return parsed;
|
|
406
|
+
}
|