mirror of
https://github.com/LostRuins/koboldcpp.git
synced 2026-09-06 21:11:24 +02:00
Merge branch 'upstream' into concedo_experimental
# Conflicts: # .devops/openvino.Dockerfile # .github/workflows/build-cache.yml # .github/workflows/build-openvino.yml # .github/workflows/build-self-hosted.yml # .github/workflows/release.yml # docs/backend/OPENVINO.md # docs/backend/SYCL.md # docs/ops.md # docs/ops/SYCL.csv # ggml/src/ggml-opencl/ggml-opencl.cpp # ggml/src/ggml-opencl/kernels/mul_mv_f16_f32_l4.cl # ggml/src/ggml-openvino/.clang-format # ggml/src/ggml-openvino/CMakeLists.txt # ggml/src/ggml-openvino/ggml-decoder.cpp # ggml/src/ggml-openvino/ggml-decoder.h # ggml/src/ggml-openvino/ggml-openvino-extra.cpp # ggml/src/ggml-openvino/ggml-openvino-extra.h # ggml/src/ggml-openvino/ggml-openvino.cpp # ggml/src/ggml-openvino/ggml-quants.cpp # ggml/src/ggml-openvino/ggml-quants.h # ggml/src/ggml-openvino/openvino/decoder.h # ggml/src/ggml-openvino/openvino/frontend.h # ggml/src/ggml-openvino/openvino/input_model.h # ggml/src/ggml-openvino/openvino/node_context.h # ggml/src/ggml-openvino/openvino/op/cont.cpp # ggml/src/ggml-openvino/openvino/op/cpy.cpp # ggml/src/ggml-openvino/openvino/op/flash_attn_ext.cpp # ggml/src/ggml-openvino/openvino/op/get_rows.cpp # ggml/src/ggml-openvino/openvino/op/glu_geglu.cpp # ggml/src/ggml-openvino/openvino/op/glu_swiglu.cpp # ggml/src/ggml-openvino/openvino/op/mulmat.cpp # ggml/src/ggml-openvino/openvino/op/permute.cpp # ggml/src/ggml-openvino/openvino/op/reshape.cpp # ggml/src/ggml-openvino/openvino/op/rms_norm.cpp # ggml/src/ggml-openvino/openvino/op/rope.cpp # ggml/src/ggml-openvino/openvino/op/set_rows.cpp # ggml/src/ggml-openvino/openvino/op/softmax.cpp # ggml/src/ggml-openvino/openvino/op/transpose.cpp # ggml/src/ggml-openvino/openvino/op/unary_silu.cpp # ggml/src/ggml-openvino/openvino/op/view.cpp # ggml/src/ggml-openvino/openvino/op_table.cpp # ggml/src/ggml-openvino/openvino/op_table.h # ggml/src/ggml-openvino/openvino/pass/mark_decompression_convert_constant_folding.h # ggml/src/ggml-openvino/openvino/translate_session.cpp # ggml/src/ggml-openvino/openvino/translate_session.h # ggml/src/ggml-openvino/openvino/utils.cpp # ggml/src/ggml-openvino/openvino/utils.h # ggml/src/ggml-openvino/utils.cpp # ggml/src/ggml-openvino/utils.h # ggml/src/ggml-sycl/common.hpp # ggml/src/ggml-sycl/ggml-sycl.cpp
This commit is contained in:
+82
-76
@@ -11,8 +11,13 @@
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#if defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# ifndef NOMINMAX
|
||||
# define NOMINMAX
|
||||
# endif
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
# define isatty _isatty
|
||||
@@ -62,16 +67,15 @@ static const char* g_col[] = {
|
||||
};
|
||||
|
||||
struct common_log_entry {
|
||||
enum ggml_log_level level;
|
||||
|
||||
bool prefix;
|
||||
|
||||
int64_t timestamp;
|
||||
enum ggml_log_level level {GGML_LOG_LEVEL_INFO};
|
||||
|
||||
std::vector<char> msg;
|
||||
|
||||
// signals the worker thread to stop
|
||||
bool is_end;
|
||||
int64_t timestamp { 0 };
|
||||
bool is_end { false }; // signals the worker thread to stop
|
||||
bool prefix { false };
|
||||
|
||||
common_log_entry(size_t size = 256) : msg(size) { }
|
||||
|
||||
void print(FILE * file = nullptr) const {
|
||||
FILE * fcur = file;
|
||||
@@ -122,22 +126,15 @@ struct common_log_entry {
|
||||
};
|
||||
|
||||
struct common_log {
|
||||
// default capacity - will be expanded if needed
|
||||
common_log() : common_log(256) {}
|
||||
|
||||
common_log(size_t capacity) {
|
||||
file = nullptr;
|
||||
prefix = false;
|
||||
// default capacity
|
||||
common_log(size_t capacity = 512) {
|
||||
file = nullptr;
|
||||
prefix = false;
|
||||
timestamps = false;
|
||||
running = false;
|
||||
t_start = t_us();
|
||||
|
||||
// initial message size - will be expanded if longer messages arrive
|
||||
entries.resize(capacity);
|
||||
for (auto & entry : entries) {
|
||||
entry.msg.resize(256);
|
||||
}
|
||||
running = false;
|
||||
t_start = t_us();
|
||||
|
||||
queue.resize(capacity, common_log_entry(256));
|
||||
head = 0;
|
||||
tail = 0;
|
||||
|
||||
@@ -152,9 +149,10 @@ struct common_log {
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mtx;
|
||||
std::thread thrd;
|
||||
std::condition_variable cv;
|
||||
std::mutex mtx;
|
||||
std::thread thrd;
|
||||
std::condition_variable cv_new; // new entry
|
||||
std::condition_variable cv_full; // wait on full
|
||||
|
||||
FILE * file;
|
||||
|
||||
@@ -164,24 +162,53 @@ private:
|
||||
|
||||
int64_t t_start;
|
||||
|
||||
// ring buffer of entries
|
||||
std::vector<common_log_entry> entries;
|
||||
// queue of entries
|
||||
std::vector<common_log_entry> queue;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
|
||||
// worker thread copies into this
|
||||
common_log_entry cur;
|
||||
bool print_entry(const common_log_entry & e) const {
|
||||
if (e.is_end) return true;
|
||||
|
||||
e.print();
|
||||
if (file) {
|
||||
e.print(file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flush_queue(size_t start_head, size_t end_tail, size_t & out_head) const {
|
||||
bool stop = false;
|
||||
size_t h = start_head;
|
||||
while (h != end_tail && !stop) {
|
||||
stop = print_entry(queue[h]);
|
||||
h = (h + 1) % queue.size();
|
||||
}
|
||||
out_head = h;
|
||||
return stop;
|
||||
}
|
||||
|
||||
public:
|
||||
bool is_full() const {
|
||||
return ((tail + 1) % queue.size()) == head;
|
||||
}
|
||||
|
||||
bool is_empty() const {
|
||||
return head == tail;
|
||||
}
|
||||
|
||||
void add(enum ggml_log_level level, const char * fmt, va_list args) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
|
||||
// block if the queue is full
|
||||
cv_full.wait(lock, [this]() { return !running || !is_full(); });
|
||||
|
||||
if (!running) {
|
||||
// discard messages while the worker thread is paused
|
||||
return;
|
||||
}
|
||||
|
||||
auto & entry = entries[tail];
|
||||
auto & entry = queue[tail];
|
||||
|
||||
{
|
||||
// cannot use args twice, so make a copy in case we need to expand the buffer
|
||||
@@ -216,38 +243,16 @@ public:
|
||||
va_end(args_copy);
|
||||
}
|
||||
|
||||
entry.level = level;
|
||||
entry.prefix = prefix;
|
||||
entry.is_end = false;
|
||||
entry.level = level;
|
||||
entry.prefix = prefix;
|
||||
entry.timestamp = 0;
|
||||
if (timestamps) {
|
||||
entry.timestamp = t_us() - t_start;
|
||||
}
|
||||
entry.is_end = false;
|
||||
|
||||
tail = (tail + 1) % entries.size();
|
||||
if (tail == head) {
|
||||
// expand the buffer
|
||||
std::vector<common_log_entry> new_entries(2*entries.size());
|
||||
|
||||
size_t new_tail = 0;
|
||||
|
||||
do {
|
||||
new_entries[new_tail] = std::move(entries[head]);
|
||||
|
||||
head = (head + 1) % entries.size();
|
||||
new_tail = (new_tail + 1);
|
||||
} while (head != tail);
|
||||
|
||||
head = 0;
|
||||
tail = new_tail;
|
||||
|
||||
for (size_t i = tail; i < new_entries.size(); i++) {
|
||||
new_entries[i].msg.resize(256);
|
||||
}
|
||||
|
||||
entries = std::move(new_entries);
|
||||
}
|
||||
cv.notify_one();
|
||||
tail = (tail + 1) % queue.size();
|
||||
cv_new.notify_one();
|
||||
}
|
||||
|
||||
void resume() {
|
||||
@@ -261,23 +266,24 @@ public:
|
||||
|
||||
thrd = std::thread([this]() {
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
cv.wait(lock, [this]() { return head != tail; });
|
||||
cur = entries[head];
|
||||
std::unique_lock<std::mutex> lock(mtx);
|
||||
cv_new.wait(lock, [this]() { return !is_empty(); });
|
||||
|
||||
head = (head + 1) % entries.size();
|
||||
}
|
||||
size_t cached_head = head;
|
||||
size_t cached_tail = tail;
|
||||
|
||||
if (cur.is_end) {
|
||||
lock.unlock(); // drop the lock during flush
|
||||
|
||||
size_t next_head;
|
||||
bool stop = flush_queue(cached_head, cached_tail, next_head);
|
||||
|
||||
lock.lock();
|
||||
head = next_head;
|
||||
cv_full.notify_all();
|
||||
|
||||
if (stop) {
|
||||
break;
|
||||
}
|
||||
|
||||
cur.print(); // stdout and stderr
|
||||
|
||||
if (file) {
|
||||
cur.print(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -293,13 +299,13 @@ public:
|
||||
running = false;
|
||||
|
||||
// push an entry to signal the worker thread to stop
|
||||
{
|
||||
auto & entry = entries[tail];
|
||||
entry.is_end = true;
|
||||
auto & entry = queue[tail];
|
||||
entry.is_end = true;
|
||||
tail = (tail + 1) % queue.size();
|
||||
|
||||
tail = (tail + 1) % entries.size();
|
||||
}
|
||||
cv.notify_one();
|
||||
// wakeup everyone
|
||||
cv_new.notify_one();
|
||||
cv_full.notify_all();
|
||||
}
|
||||
|
||||
thrd.join();
|
||||
|
||||
@@ -422,7 +422,7 @@ std::map<std::string, std::string> merge_maps(const std::map<std::string, std::s
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<std::future<void>> compiles;
|
||||
static std::deque<std::future<void>> compiles;
|
||||
void string_to_spv(std::string name, const std::string& source, const std::map<std::string, std::string>& defines, bool fp16 = true, bool coopmat = false, bool coopmat2 = false, bool f16acc = false, const std::string& suffix = "") {
|
||||
name = name + (f16acc ? "_f16acc" : "") + (coopmat ? "_cm1" : "") + (coopmat2 ? "_cm2" : (fp16 ? "" : "_fp32")) + suffix;
|
||||
std::string out_path = join_paths(output_dir, name + ".spv");
|
||||
@@ -452,6 +452,11 @@ void string_to_spv(std::string name, const std::string& source, const std::map<s
|
||||
string_to_spv_func, name, in_path, out_path, defines, coopmat, generate_dep_file, std::move(slot)));
|
||||
// Don't write the same dep file from multiple processes
|
||||
generate_dep_file = false;
|
||||
|
||||
// Clean up completed futures - don't accumulate virtual memory for completed threads' stacks.
|
||||
while (!compiles.empty() && compiles.front().wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
||||
compiles.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void matmul_shaders(bool fp16, MatMulIdType matmul_id_type, bool coopmat, bool coopmat2, bool f16acc, bool dot2 = false) {
|
||||
|
||||
+58
-45
@@ -1088,6 +1088,10 @@ ggml_tensor * llm_graph_context::build_lora_mm(
|
||||
ggml_tensor * w_s) const {
|
||||
ggml_tensor * res = ggml_mul_mat(ctx0, w, cur);
|
||||
|
||||
if (w_s) {
|
||||
res = ggml_mul(ctx0, res, w_s);
|
||||
}
|
||||
|
||||
for (const auto & lora : *loras) {
|
||||
llama_adapter_lora_weight * lw = lora.first->get_weight(w);
|
||||
if (lw == nullptr) {
|
||||
@@ -1106,18 +1110,24 @@ ggml_tensor * llm_graph_context::build_lora_mm(
|
||||
res = ggml_add(ctx0, res, ab_cur);
|
||||
}
|
||||
|
||||
if (w_s) {
|
||||
res = ggml_mul(ctx0, res, w_s);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ggml_tensor * llm_graph_context::build_lora_mm_id(
|
||||
ggml_tensor * w, // ggml_tensor * as
|
||||
ggml_tensor * cur, // ggml_tensor * b
|
||||
ggml_tensor * ids) const {
|
||||
ggml_tensor * ids,
|
||||
ggml_tensor * w_s) const {
|
||||
ggml_tensor * res = ggml_mul_mat_id(ctx0, w, cur, ids);
|
||||
|
||||
if (w_s) {
|
||||
const int64_t n_expert = w_s->ne[0];
|
||||
const int64_t n_tokens = cur->ne[2];
|
||||
ggml_tensor * s = ggml_reshape_3d(ctx0, w_s, 1, n_expert, 1);
|
||||
s = ggml_repeat_4d(ctx0, s, 1, n_expert, n_tokens, 1);
|
||||
s = ggml_get_rows(ctx0, s, ids);
|
||||
res = ggml_mul(ctx0, res, s);
|
||||
}
|
||||
for (const auto & lora : *loras) {
|
||||
llama_adapter_lora_weight * lw = lora.first->get_weight(w);
|
||||
if (lw == nullptr) {
|
||||
@@ -1269,6 +1279,29 @@ ggml_tensor * llm_graph_context::build_ffn(
|
||||
llm_ffn_op_type type_op,
|
||||
llm_ffn_gate_type type_gate,
|
||||
int il) const {
|
||||
// NVFP4 support is currently restricted to
|
||||
// 1) LORA absence (*_s would be applied after LORA residual, which is incorrect)
|
||||
// 2) bias absense (*_s would be applied after bias addition, which is incorrect)
|
||||
// TODO: disambiguate LLM-architectural scales (which use *_s) from NVFP4 scale_2 (which also uses *_s currently)
|
||||
auto has_lora = [this](ggml_tensor * w) {
|
||||
if (!w) {
|
||||
return false;
|
||||
}
|
||||
for (const auto & lora : *loras) {
|
||||
if (lora.first->get_weight(w) != nullptr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
GGML_ASSERT(!up_s || !up_b || !up || up->type != GGML_TYPE_NVFP4);
|
||||
GGML_ASSERT(!gate_s || !gate_b || !gate || gate->type != GGML_TYPE_NVFP4);
|
||||
GGML_ASSERT(!down_s || !down_b || !down || down->type != GGML_TYPE_NVFP4);
|
||||
GGML_ASSERT(!up_s || !up || up->type != GGML_TYPE_NVFP4 || !has_lora(up));
|
||||
GGML_ASSERT(!gate_s || !gate || gate->type != GGML_TYPE_NVFP4 || !has_lora(gate));
|
||||
GGML_ASSERT(!down_s || !down || down->type != GGML_TYPE_NVFP4 || !has_lora(down));
|
||||
|
||||
ggml_tensor * tmp = up ? build_lora_mm(up, cur) : cur;
|
||||
cb(tmp, "ffn_up", il);
|
||||
|
||||
@@ -1627,23 +1660,18 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
||||
|
||||
if (gate_up_exps) {
|
||||
// merged gate_up path: one mul_mat_id, then split into gate and up views
|
||||
ggml_tensor * gate_up = build_lora_mm_id(gate_up_exps, cur, selected_experts); // [n_ff*2, n_expert_used, n_tokens]
|
||||
ggml_tensor * gate_up = build_lora_mm_id(gate_up_exps, cur, selected_experts, up_exps_s); // [n_ff*2, n_expert_used, n_tokens]
|
||||
cb(gate_up, "ffn_moe_gate_up", il);
|
||||
|
||||
if (up_exps_s) {
|
||||
cb(gate_up, "ffn_moe_gate_up_scaled", il);
|
||||
}
|
||||
|
||||
if (gate_up_exps_b) {
|
||||
gate_up = ggml_add_id(ctx0, gate_up, gate_up_exps_b, selected_experts);
|
||||
cb(gate_up, "ffn_moe_gate_up_biased", il);
|
||||
}
|
||||
|
||||
// apply per-expert scale2 to merged gate_up (use up_exps_s since gate and up are fused)
|
||||
if (up_exps_s) {
|
||||
ggml_tensor * s = ggml_reshape_3d(ctx0, up_exps_s, 1, n_expert, 1);
|
||||
s = ggml_repeat_4d(ctx0, s, 1, n_expert, n_tokens, 1);
|
||||
s = ggml_get_rows(ctx0, s, selected_experts); // [1, n_expert_used, n_tokens]
|
||||
gate_up = ggml_mul(ctx0, gate_up, s);
|
||||
cb(gate_up, "ffn_moe_gate_up_scaled", il);
|
||||
}
|
||||
|
||||
const int64_t n_ff = gate_up->ne[0] / 2;
|
||||
cur = ggml_view_3d(ctx0, gate_up, n_ff, gate_up->ne[1], gate_up->ne[2], gate_up->nb[1], gate_up->nb[2], 0);
|
||||
cb(cur, "ffn_moe_gate", il);
|
||||
@@ -1651,43 +1679,33 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
||||
cb(up, "ffn_moe_up", il);
|
||||
} else {
|
||||
// separate gate and up path
|
||||
up = build_lora_mm_id(up_exps, cur, selected_experts); // [n_ff, n_expert_used, n_tokens]
|
||||
up = build_lora_mm_id(up_exps, cur, selected_experts, up_exps_s); // [n_ff, n_expert_used, n_tokens]
|
||||
cb(up, "ffn_moe_up", il);
|
||||
|
||||
if (up_exps_s) {
|
||||
cb(up, "ffn_moe_up_scaled", il);
|
||||
}
|
||||
|
||||
if (up_exps_b) {
|
||||
up = ggml_add_id(ctx0, up, up_exps_b, selected_experts);
|
||||
cb(up, "ffn_moe_up_biased", il);
|
||||
}
|
||||
|
||||
// apply per-expert scale2 to up
|
||||
if (up_exps_s) {
|
||||
ggml_tensor * s = ggml_reshape_3d(ctx0, up_exps_s, 1, n_expert, 1);
|
||||
s = ggml_repeat_4d(ctx0, s, 1, n_expert, n_tokens, 1);
|
||||
s = ggml_get_rows(ctx0, s, selected_experts); // [1, n_expert_used, n_tokens]
|
||||
up = ggml_mul(ctx0, up, s);
|
||||
cb(up, "ffn_moe_up_scaled", il);
|
||||
}
|
||||
|
||||
if (gate_exps) {
|
||||
cur = build_lora_mm_id(gate_exps, cur, selected_experts); // [n_ff, n_expert_used, n_tokens]
|
||||
cur = build_lora_mm_id(gate_exps, cur, selected_experts, gate_exps_s); // [n_ff, n_expert_used, n_tokens]
|
||||
cb(cur, "ffn_moe_gate", il);
|
||||
} else {
|
||||
cur = up;
|
||||
}
|
||||
|
||||
if (gate_exps_s) {
|
||||
cb(cur, "ffn_moe_gate_scaled", il);
|
||||
}
|
||||
|
||||
if (gate_exps_b) {
|
||||
cur = ggml_add_id(ctx0, cur, gate_exps_b, selected_experts);
|
||||
cb(cur, "ffn_moe_gate_biased", il);
|
||||
}
|
||||
|
||||
// apply per-expert scale2 to gate
|
||||
if (gate_exps_s) {
|
||||
ggml_tensor * s = ggml_reshape_3d(ctx0, gate_exps_s, 1, n_expert, 1);
|
||||
s = ggml_repeat_4d(ctx0, s, 1, n_expert, n_tokens, 1);
|
||||
s = ggml_get_rows(ctx0, s, selected_experts); // [1, n_expert_used, n_tokens]
|
||||
cur = ggml_mul(ctx0, cur, s);
|
||||
cb(cur, "ffn_moe_gate_scaled", il);
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_gate = gate_exps || gate_up_exps;
|
||||
@@ -1759,23 +1777,18 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
|
||||
GGML_ABORT("fatal error");
|
||||
}
|
||||
|
||||
experts = build_lora_mm_id(down_exps, cur, selected_experts); // [n_embd, n_expert_used, n_tokens]
|
||||
experts = build_lora_mm_id(down_exps, cur, selected_experts, down_exps_s); // [n_embd, n_expert_used, n_tokens]
|
||||
cb(experts, "ffn_moe_down", il);
|
||||
|
||||
if (down_exps_s) {
|
||||
cb(experts, "ffn_moe_down_scaled", il);
|
||||
}
|
||||
|
||||
if (down_exps_b) {
|
||||
experts = ggml_add_id(ctx0, experts, down_exps_b, selected_experts);
|
||||
cb(experts, "ffn_moe_down_biased", il);
|
||||
}
|
||||
|
||||
// apply per-expert scale2 to down
|
||||
if (down_exps_s) {
|
||||
ggml_tensor * s = ggml_reshape_3d(ctx0, down_exps_s, 1, n_expert, 1);
|
||||
s = ggml_repeat_4d(ctx0, s, 1, n_expert, n_tokens, 1);
|
||||
s = ggml_get_rows(ctx0, s, selected_experts); // [1, n_expert_used, n_tokens]
|
||||
experts = ggml_mul(ctx0, experts, s);
|
||||
cb(experts, "ffn_moe_down_scaled", il);
|
||||
}
|
||||
|
||||
if (!weight_before_ffn) {
|
||||
experts = ggml_mul(ctx0, experts, weights);
|
||||
cb(experts, "ffn_moe_weighted", il);
|
||||
|
||||
+3
-2
@@ -853,11 +853,12 @@ struct llm_graph_context {
|
||||
ggml_tensor * cur,
|
||||
ggml_tensor * w_s = nullptr) const;
|
||||
|
||||
// do mat_mul_id, while optionally apply lora
|
||||
// do mat_mul_id, while optionally apply lora and per-expert scale
|
||||
ggml_tensor * build_lora_mm_id(
|
||||
ggml_tensor * w, // ggml_tensor * as
|
||||
ggml_tensor * cur, // ggml_tensor * b
|
||||
ggml_tensor * ids) const;
|
||||
ggml_tensor * ids,
|
||||
ggml_tensor * w_s = nullptr) const;
|
||||
|
||||
ggml_tensor * build_norm(
|
||||
ggml_tensor * cur,
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
DATA_ERROR_HANDLED_ATTR,
|
||||
BOOL_TRUE_STRING,
|
||||
SETTINGS_KEYS,
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
MERMAID_WRAPPER_CLASS,
|
||||
MERMAID_BLOCK_CLASS,
|
||||
MERMAID_LANGUAGE,
|
||||
@@ -53,7 +54,11 @@
|
||||
SVG_TAG_PREFIX,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_RENDERED_ATTR,
|
||||
SVG_INLINE_SHADOW_STYLE
|
||||
SVG_INLINE_SHADOW_STYLE,
|
||||
TOGGLE_SOURCE_BTN_CLASS,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED,
|
||||
DIAGRAM_VIEW_SOURCE
|
||||
} from '$lib/constants';
|
||||
import { ColorMode, UrlProtocol } from '$lib/enums';
|
||||
import { FileTypeText } from '$lib/enums/files.enums';
|
||||
@@ -501,6 +506,23 @@
|
||||
async function handleMermaidClick(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
// Toggle a diagram block between its rendered view and its source view.
|
||||
// Shared by mermaid and svg, css drives the visibility from the wrapper mode.
|
||||
const toggleBtn = target.closest(`.${TOGGLE_SOURCE_BTN_CLASS}`);
|
||||
if (toggleBtn) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const wrapper = toggleBtn.closest(`.${MERMAID_WRAPPER_CLASS}, .${SVG_WRAPPER_CLASS}`);
|
||||
if (!wrapper) return;
|
||||
|
||||
const isSource = wrapper.getAttribute(DIAGRAM_VIEW_MODE_ATTR) === DIAGRAM_VIEW_SOURCE;
|
||||
const next = isSource ? DIAGRAM_VIEW_RENDERED : DIAGRAM_VIEW_SOURCE;
|
||||
wrapper.setAttribute(DIAGRAM_VIEW_MODE_ATTR, next);
|
||||
toggleBtn.setAttribute('aria-pressed', String(!isSource));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if clicking on copy or preview button in mermaid block
|
||||
const copyBtn = target.closest(`.${MERMAID_WRAPPER_CLASS} .copy-code-btn`);
|
||||
const previewBtn = target.closest(`.${MERMAID_WRAPPER_CLASS} .preview-code-btn`);
|
||||
@@ -573,6 +595,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// A click on the header chrome targets the action buttons, never the
|
||||
// diagram. Guard so a header click can not fall through to the click to
|
||||
// zoom branches below, whatever the scroll position or stacking.
|
||||
if (target.closest(`.${CODE_BLOCK_HEADER_CLASS}`)) return;
|
||||
|
||||
// Open preview when clicking the svg block itself. A final block carries its
|
||||
// source, a streaming block does not and is mirrored live into the dialog.
|
||||
const svgEl = target.closest(`.${SVG_BLOCK_CLASS}`);
|
||||
|
||||
@@ -300,7 +300,8 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
}
|
||||
|
||||
.markdown-content :global(.copy-code-btn),
|
||||
.markdown-content :global(.preview-code-btn) {
|
||||
.markdown-content :global(.preview-code-btn),
|
||||
.markdown-content :global(.toggle-source-btn) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -312,15 +313,22 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
}
|
||||
|
||||
.markdown-content :global(.copy-code-btn:hover),
|
||||
.markdown-content :global(.preview-code-btn:hover) {
|
||||
.markdown-content :global(.preview-code-btn:hover),
|
||||
.markdown-content :global(.toggle-source-btn:hover) {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.markdown-content :global(.copy-code-btn:active),
|
||||
.markdown-content :global(.preview-code-btn:active) {
|
||||
.markdown-content :global(.preview-code-btn:active),
|
||||
.markdown-content :global(.toggle-source-btn:active) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Pressed state marks the source view as active */
|
||||
.markdown-content :global(.toggle-source-btn[aria-pressed='true']) {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.markdown-content :global(.code-block-wrapper pre) {
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
@@ -629,8 +637,8 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-items: safe center;
|
||||
justify-content: safe center;
|
||||
padding: 3rem 1rem 1rem;
|
||||
}
|
||||
|
||||
@@ -645,7 +653,9 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
/* Diagram block uses same header styling as code blocks */
|
||||
/* Diagram block uses same header styling as code blocks. The header floats over
|
||||
scrollable diagram content and stays transparent, so the overflow shows up to
|
||||
the box edge. It keeps a z-index so it stays the click target above content. */
|
||||
.markdown-content :global(.mermaid-block-wrapper .code-block-header),
|
||||
.markdown-content :global(.svg-block-wrapper .code-block-header) {
|
||||
display: flex;
|
||||
@@ -657,6 +667,7 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.markdown-content :global(.mermaid-block-wrapper .code-block-actions),
|
||||
@@ -683,6 +694,31 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
/* Source view stays hidden while the block renders, css swaps the two views
|
||||
from the wrapper mode so the click handler only flips one attribute. The view
|
||||
reuses the code block scroll container, so it matches the app code blocks. */
|
||||
.markdown-content :global(.diagram-source) {
|
||||
display: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-content :global(.diagram-source pre) {
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.markdown-content :global([data-view-mode='source'] .mermaid-scroll-container),
|
||||
.markdown-content :global([data-view-mode='source'] .svg-scroll-container) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.markdown-content :global([data-view-mode='source'] .diagram-source) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Streaming mermaid block - empty preview box */
|
||||
.mermaid-streaming-block {
|
||||
min-height: 300px;
|
||||
|
||||
+63
-7
@@ -7,12 +7,16 @@ import type { Element, ElementContent } from 'hast';
|
||||
import {
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
CODE_BLOCK_ACTIONS_CLASS,
|
||||
CODE_BLOCK_SCROLL_CONTAINER_CLASS,
|
||||
CODE_LANGUAGE_CLASS,
|
||||
COPY_CODE_BTN_CLASS,
|
||||
PREVIEW_CODE_BTN_CLASS,
|
||||
TOGGLE_SOURCE_BTN_CLASS,
|
||||
DIAGRAM_SOURCE_CLASS,
|
||||
RELATIVE_CLASS,
|
||||
COPY_ICON_SVG,
|
||||
PREVIEW_ICON_SVG
|
||||
PREVIEW_ICON_SVG,
|
||||
CODE_ICON_SVG
|
||||
} from '$lib/constants';
|
||||
|
||||
export interface BlockIdGenerator {
|
||||
@@ -32,14 +36,16 @@ export function createIconElement(svg: string): Element {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button element with icon.
|
||||
* Creates a button element with icon. Extra properties merge onto the button,
|
||||
* which lets a stateful button carry attributes like aria-pressed.
|
||||
*/
|
||||
export function createButton(
|
||||
className: string,
|
||||
title: string,
|
||||
iconSvg: string,
|
||||
id: string,
|
||||
idAttribute: string
|
||||
idAttribute: string,
|
||||
extraProperties: Record<string, string> = {}
|
||||
): Element {
|
||||
return {
|
||||
type: 'element',
|
||||
@@ -48,7 +54,8 @@ export function createButton(
|
||||
className: [className],
|
||||
[idAttribute]: id,
|
||||
title,
|
||||
type: 'button'
|
||||
type: 'button',
|
||||
...extraProperties
|
||||
},
|
||||
children: [createIconElement(iconSvg)]
|
||||
};
|
||||
@@ -72,6 +79,52 @@ export function createPreviewButton(
|
||||
return createButton(PREVIEW_CODE_BTN_CLASS, title, PREVIEW_ICON_SVG, id, idAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button that toggles a diagram block between its rendered view and
|
||||
* its source view. aria-pressed starts false, the rendered view is the default.
|
||||
*/
|
||||
export function createToggleSourceButton(
|
||||
id: string,
|
||||
idAttribute: string,
|
||||
title: string = 'Toggle source'
|
||||
): Element {
|
||||
return createButton(TOGGLE_SOURCE_BTN_CLASS, title, CODE_ICON_SVG, id, idAttribute, {
|
||||
'aria-pressed': 'false'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a source view for a diagram block. It reuses the code block scroll
|
||||
* container so it matches the app code blocks, and wraps the highlighted code
|
||||
* element captured at transform time. A missing code element falls back to a
|
||||
* plain code node built from the raw source.
|
||||
*/
|
||||
export function createSourceView(
|
||||
codeElement: Element | undefined,
|
||||
source: string,
|
||||
language: string
|
||||
): Element {
|
||||
const code: Element = codeElement ?? {
|
||||
type: 'element',
|
||||
tagName: 'code',
|
||||
properties: { className: ['hljs', `language-${language}`] },
|
||||
children: [{ type: 'text', value: source }]
|
||||
};
|
||||
return {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: [DIAGRAM_SOURCE_CLASS, CODE_BLOCK_SCROLL_CONTAINER_CLASS] },
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'pre',
|
||||
properties: {},
|
||||
children: [code]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a block header with language label and action buttons.
|
||||
*/
|
||||
@@ -116,14 +169,17 @@ export function createScrollContainer(preElement: Element, scrollContainerClass:
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper element with header and scroll container.
|
||||
* Creates a wrapper element with header and scroll container. Extra children
|
||||
* append after the scroll container, which lets a block carry a source view
|
||||
* alongside its rendered output.
|
||||
*/
|
||||
export function createWrapper(
|
||||
header: Element,
|
||||
preElement: Element,
|
||||
wrapperClass: string,
|
||||
scrollContainerClass: string,
|
||||
additionalAttributes?: Record<string, string>
|
||||
additionalAttributes?: Record<string, string>,
|
||||
extraChildren: Element[] = []
|
||||
): Element {
|
||||
return {
|
||||
type: 'element',
|
||||
@@ -132,7 +188,7 @@ export function createWrapper(
|
||||
className: [wrapperClass, RELATIVE_CLASS],
|
||||
...additionalAttributes
|
||||
} as Element['properties'],
|
||||
children: [header, createScrollContainer(preElement, scrollContainerClass)]
|
||||
children: [header, createScrollContainer(preElement, scrollContainerClass), ...extraChildren]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -19,12 +19,17 @@ import {
|
||||
MERMAID_BLOCK_CLASS,
|
||||
MERMAID_LANGUAGE,
|
||||
MERMAID_SYNTAX_ATTR,
|
||||
MERMAID_ID_ATTR
|
||||
MERMAID_ID_ATTR,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED
|
||||
} from '$lib/constants';
|
||||
import type { DiagramPreData } from './pre-transform';
|
||||
import {
|
||||
createBlockHeader,
|
||||
createCopyButton,
|
||||
createPreviewButton,
|
||||
createToggleSourceButton,
|
||||
createSourceView,
|
||||
createWrapper,
|
||||
generateBlockId
|
||||
} from './code-block-utils';
|
||||
@@ -75,16 +80,23 @@ export const rehypeEnhanceMermaidBlocks: Plugin<[], Root> = () => {
|
||||
|
||||
const actions = [
|
||||
createCopyButton(mermaidId, MERMAID_ID_ATTR, 'Copy mermaid syntax'),
|
||||
createToggleSourceButton(mermaidId, MERMAID_ID_ATTR, 'Toggle mermaid source'),
|
||||
createPreviewButton(mermaidId, MERMAID_ID_ATTR, 'Preview diagram')
|
||||
];
|
||||
|
||||
const header = createBlockHeader(MERMAID_LANGUAGE, mermaidId, MERMAID_ID_ATTR, actions);
|
||||
const preservedCode = (node.data as DiagramPreData | undefined)?.sourceCode;
|
||||
const sourceView = createSourceView(preservedCode, diagramText, MERMAID_LANGUAGE);
|
||||
const wrapper = createWrapper(
|
||||
header,
|
||||
node,
|
||||
MERMAID_WRAPPER_CLASS,
|
||||
MERMAID_SCROLL_CONTAINER_CLASS,
|
||||
{ [MERMAID_ID_ATTR]: mermaidId }
|
||||
{
|
||||
[MERMAID_ID_ATTR]: mermaidId,
|
||||
[DIAGRAM_VIEW_MODE_ATTR]: DIAGRAM_VIEW_RENDERED
|
||||
},
|
||||
[sourceView]
|
||||
);
|
||||
|
||||
// Replace pre with wrapper in parent
|
||||
|
||||
+20
-4
@@ -18,12 +18,17 @@ import {
|
||||
SVG_BLOCK_CLASS,
|
||||
SVG_LANGUAGE,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_ID_ATTR
|
||||
SVG_ID_ATTR,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED
|
||||
} from '$lib/constants';
|
||||
import type { DiagramPreData } from './pre-transform';
|
||||
import {
|
||||
createBlockHeader,
|
||||
createCopyButton,
|
||||
createPreviewButton,
|
||||
createToggleSourceButton,
|
||||
createSourceView,
|
||||
createWrapper,
|
||||
generateBlockId
|
||||
} from './code-block-utils';
|
||||
@@ -65,13 +70,24 @@ export const rehypeEnhanceSvgBlocks: Plugin<[], Root> = () => {
|
||||
|
||||
const actions = [
|
||||
createCopyButton(svgId, SVG_ID_ATTR, 'Copy svg source'),
|
||||
createToggleSourceButton(svgId, SVG_ID_ATTR, 'Toggle svg source'),
|
||||
createPreviewButton(svgId, SVG_ID_ATTR, 'Preview svg')
|
||||
];
|
||||
|
||||
const header = createBlockHeader(SVG_LANGUAGE, svgId, SVG_ID_ATTR, actions);
|
||||
const wrapper = createWrapper(header, node, SVG_WRAPPER_CLASS, SVG_SCROLL_CONTAINER_CLASS, {
|
||||
[SVG_ID_ATTR]: svgId
|
||||
});
|
||||
const preservedCode = (node.data as DiagramPreData | undefined)?.sourceCode;
|
||||
const sourceView = createSourceView(preservedCode, svgSource, SVG_LANGUAGE);
|
||||
const wrapper = createWrapper(
|
||||
header,
|
||||
node,
|
||||
SVG_WRAPPER_CLASS,
|
||||
SVG_SCROLL_CONTAINER_CLASS,
|
||||
{
|
||||
[SVG_ID_ATTR]: svgId,
|
||||
[DIAGRAM_VIEW_MODE_ATTR]: DIAGRAM_VIEW_RENDERED
|
||||
},
|
||||
[sourceView]
|
||||
);
|
||||
|
||||
// Replace pre with wrapper in parent
|
||||
(parent.children as ElementContent[])[index] = wrapper;
|
||||
|
||||
+13
-1
@@ -2,6 +2,15 @@ import type { Plugin } from 'unified';
|
||||
import type { Root, Element, ElementContent, Text } from 'hast';
|
||||
import { visit } from 'unist-util-visit';
|
||||
|
||||
/**
|
||||
* Metadata a diagram pre carries on its unist data field. The source code holds
|
||||
* the highlighted code element captured before the pre became a render target,
|
||||
* which the enhancer reuses to build a matching source view.
|
||||
*/
|
||||
export interface DiagramPreData {
|
||||
sourceCode: Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively extracts all text content from a HAST node.
|
||||
* Handles nested elements (e.g., span wrappers from syntax highlighting).
|
||||
@@ -69,7 +78,10 @@ export function createPreTransform(
|
||||
properties: {
|
||||
className: [targetClass]
|
||||
},
|
||||
children: [{ type: 'text', value: text } as Text]
|
||||
children: [{ type: 'text', value: text } as Text],
|
||||
// Keep the highlighted code element so the block can offer a source
|
||||
// view that matches the app code blocks without re highlighting.
|
||||
data: { sourceCode: codeElement } satisfies DiagramPreData
|
||||
};
|
||||
|
||||
(parent.children as ElementContent[])[index] = pre;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Shared constants for diagram blocks (mermaid and svg) that toggle between a
|
||||
// rendered view and a source view. The wrapper carries the active mode, css
|
||||
// drives the visibility, the click handler only flips the attribute.
|
||||
|
||||
export const DIAGRAM_VIEW_MODE_ATTR = 'data-view-mode';
|
||||
export const DIAGRAM_VIEW_RENDERED = 'rendered';
|
||||
export const DIAGRAM_VIEW_SOURCE = 'source';
|
||||
export const DIAGRAM_SOURCE_CLASS = 'diagram-source';
|
||||
export const TOGGLE_SOURCE_BTN_CLASS = 'toggle-source-btn';
|
||||
@@ -39,3 +39,5 @@ export const MODALITY_LABELS = {
|
||||
export const COPY_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`;
|
||||
|
||||
export const PREVIEW_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye lucide-eye-icon"><path d="M2.062 12.345a1 1 0 0 1 0-.69C3.5 7.73 7.36 5 12 5s8.5 2.73 9.938 6.655a1 1 0 0 1 0 .69C20.5 16.27 16.64 19 12 19s-8.5-2.73-9.938-6.655"/><circle cx="12" cy="12" r="3"/></svg>`;
|
||||
|
||||
export const CODE_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code lucide-code-icon"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg>`;
|
||||
|
||||
@@ -30,6 +30,7 @@ export * from './literal-html';
|
||||
export * from './markdown';
|
||||
export * from './mermaid-blocks';
|
||||
export * from './svg-blocks';
|
||||
export * from './diagram-blocks';
|
||||
export * from './max-bundle-size';
|
||||
export * from './mcp';
|
||||
export * from './mcp-form';
|
||||
|
||||
@@ -84,3 +84,8 @@ export const MCP_TRANSPORT_ICONS: Record<MCPTransportType, Component> = {
|
||||
[MCPTransportType.STREAMABLE_HTTP]: Globe,
|
||||
[MCPTransportType.SSE]: Radio
|
||||
};
|
||||
|
||||
/** Standard SSE endpoint path indicators */
|
||||
export const MCP_SSE_ENDPOINT = '/sse';
|
||||
export const MCP_SSE_ENDPOINT_SLASH = '/sse/';
|
||||
export const MCP_SSE_ENDPOINT_QUERY = '/sse?';
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
DEFAULT_MCP_CONFIG,
|
||||
DEFAULT_CLIENT_VERSION,
|
||||
DEFAULT_IMAGE_MIME_TYPE,
|
||||
MCP_PARTIAL_REDACT_HEADERS
|
||||
MCP_PARTIAL_REDACT_HEADERS,
|
||||
CORS_PROXY_ENDPOINT
|
||||
} from '$lib/constants';
|
||||
import {
|
||||
MCPConnectionPhase,
|
||||
@@ -236,6 +237,36 @@ export class MCPService {
|
||||
|
||||
return {
|
||||
fetch: async (input, init) => {
|
||||
if (useProxy && typeof window !== 'undefined') {
|
||||
let requestUrlStr = '';
|
||||
if (typeof input === 'string') {
|
||||
requestUrlStr = input;
|
||||
} else if (input instanceof URL) {
|
||||
requestUrlStr = input.href;
|
||||
}
|
||||
|
||||
if (requestUrlStr) {
|
||||
const parsedRequestUrl = new URL(requestUrlStr, window.location.origin);
|
||||
if (
|
||||
parsedRequestUrl.origin === window.location.origin &&
|
||||
!parsedRequestUrl.pathname.includes(CORS_PROXY_ENDPOINT)
|
||||
) {
|
||||
const originalConfigUrl = new URL(config.url);
|
||||
const realTargetUrl = new URL(
|
||||
parsedRequestUrl.pathname + parsedRequestUrl.search,
|
||||
originalConfigUrl.origin
|
||||
);
|
||||
const proxiedUrl = buildProxiedUrl(realTargetUrl.href);
|
||||
|
||||
if (typeof input === 'string') {
|
||||
input = proxiedUrl.href;
|
||||
} else if (input instanceof URL) {
|
||||
input = proxiedUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const startedAt = performance.now();
|
||||
const requestHeaders = new Headers(baseInit.headers);
|
||||
|
||||
@@ -403,6 +434,32 @@ export class MCPService {
|
||||
};
|
||||
}
|
||||
|
||||
if (config.transport === MCPTransportType.SSE) {
|
||||
const url = useProxy ? buildProxiedUrl(config.url) : new URL(config.url);
|
||||
const { fetch: diagnosticFetch, disable: stopPhaseLogging } = this.createDiagnosticFetch(
|
||||
serverName,
|
||||
config,
|
||||
requestInit,
|
||||
url,
|
||||
useProxy,
|
||||
onLog
|
||||
);
|
||||
|
||||
if (import.meta.env.DEV && import.meta.env.VITE_DEBUG) {
|
||||
console.log(`[MCPService] Creating SSE transport for ${url.href}`);
|
||||
}
|
||||
|
||||
return {
|
||||
transport: new SSEClientTransport(url, {
|
||||
requestInit,
|
||||
fetch: diagnosticFetch,
|
||||
eventSourceInit: { fetch: diagnosticFetch }
|
||||
}),
|
||||
type: MCPTransportType.SSE,
|
||||
stopPhaseLogging
|
||||
};
|
||||
}
|
||||
|
||||
const url = useProxy ? buildProxiedUrl(config.url) : new URL(config.url);
|
||||
const { fetch: diagnosticFetch, disable: stopPhaseLogging } = this.createDiagnosticFetch(
|
||||
serverName,
|
||||
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
DISPLAY_NAME_SEPARATOR_REGEX,
|
||||
PATH_SEPARATOR,
|
||||
RESOURCE_TEXT_CONTENT_SEPARATOR,
|
||||
DEFAULT_RESOURCE_FILENAME
|
||||
DEFAULT_RESOURCE_FILENAME,
|
||||
MCP_SSE_ENDPOINT,
|
||||
MCP_SSE_ENDPOINT_SLASH,
|
||||
MCP_SSE_ENDPOINT_QUERY
|
||||
} from '$lib/constants';
|
||||
import {
|
||||
Database,
|
||||
@@ -41,10 +44,22 @@ import type { MimeTypeUnion } from '$lib/types/common';
|
||||
export function detectMcpTransportFromUrl(url: string): MCPTransportType {
|
||||
const normalized = url.trim().toLowerCase();
|
||||
|
||||
return normalized.startsWith(UrlProtocol.WEBSOCKET) ||
|
||||
if (
|
||||
normalized.startsWith(UrlProtocol.WEBSOCKET) ||
|
||||
normalized.startsWith(UrlProtocol.WEBSOCKET_SECURE)
|
||||
? MCPTransportType.WEBSOCKET
|
||||
: MCPTransportType.STREAMABLE_HTTP;
|
||||
) {
|
||||
return MCPTransportType.WEBSOCKET;
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.endsWith(MCP_SSE_ENDPOINT) ||
|
||||
normalized.endsWith(MCP_SSE_ENDPOINT_SLASH) ||
|
||||
normalized.includes(MCP_SSE_ENDPOINT_QUERY)
|
||||
) {
|
||||
return MCPTransportType.SSE;
|
||||
}
|
||||
|
||||
return MCPTransportType.STREAMABLE_HTTP;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+1
-1
@@ -41,7 +41,7 @@ if (LLAMA_BUILD_BORINGSSL)
|
||||
set(FIPS OFF CACHE BOOL "Enable FIPS (BoringSSL)")
|
||||
|
||||
set(BORINGSSL_GIT "https://boringssl.googlesource.com/boringssl" CACHE STRING "BoringSSL git repository")
|
||||
set(BORINGSSL_VERSION "0.20260526.0" CACHE STRING "BoringSSL version")
|
||||
set(BORINGSSL_VERSION "0.20260616.0" CACHE STRING "BoringSSL version")
|
||||
|
||||
message(STATUS "Fetching BoringSSL version ${BORINGSSL_VERSION}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user