cliskill 1.1.1 → 1.1.2
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/dist/bootstrap/cli.js +1 -1
- package/dist/{chunk-PRO7DR3G.js → chunk-SDHXIBKZ.js} +693 -153
- package/dist/chunk-SDHXIBKZ.js.map +1 -0
- package/dist/index.d.ts +103 -29
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-PRO7DR3G.js.map +0 -1
|
@@ -28,8 +28,8 @@ var providerSchema = z.object({
|
|
|
28
28
|
apiKey: z.string().optional(),
|
|
29
29
|
/** Default model to use */
|
|
30
30
|
model: z.string().min(1),
|
|
31
|
-
/** API format: "openai-compatible" | "messages-compatible" | "glm-compatible" | "custom" */
|
|
32
|
-
format: z.enum(["openai-compatible", "messages-compatible", "glm-compatible", "custom"]),
|
|
31
|
+
/** API format: "openai-compatible" | "messages-compatible" | "glm-compatible" | "anthropic-compatible" | "custom" */
|
|
32
|
+
format: z.enum(["openai-compatible", "messages-compatible", "glm-compatible", "anthropic-compatible", "custom"]),
|
|
33
33
|
/** Custom headers to send with each request */
|
|
34
34
|
headers: z.record(z.string()).optional(),
|
|
35
35
|
/** Request timeout in milliseconds */
|
|
@@ -269,6 +269,36 @@ function applyEnvironmentVariables(config) {
|
|
|
269
269
|
}];
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
+
const anthropicApiKey = process.env.ANTHROPIC_API_KEY || process.env.CLISKILL_ANTHROPIC_API_KEY;
|
|
273
|
+
const anthropicBaseUrl = process.env.ANTHROPIC_BASE_URL || process.env.CLISKILL_ANTHROPIC_BASE_URL;
|
|
274
|
+
const anthropicModel = process.env.CLISKILL_ANTHROPIC_MODEL;
|
|
275
|
+
if (anthropicApiKey || anthropicBaseUrl) {
|
|
276
|
+
const existingAnthropic = result.providers.find((p) => p.format === "anthropic-compatible");
|
|
277
|
+
if (existingAnthropic) {
|
|
278
|
+
const updated = { ...existingAnthropic };
|
|
279
|
+
if (anthropicApiKey) updated.apiKey = anthropicApiKey;
|
|
280
|
+
if (anthropicBaseUrl) updated.baseUrl = anthropicBaseUrl;
|
|
281
|
+
if (anthropicModel) updated.model = anthropicModel;
|
|
282
|
+
result.providers = [
|
|
283
|
+
updated,
|
|
284
|
+
...result.providers.filter((p) => p.format !== "anthropic-compatible")
|
|
285
|
+
];
|
|
286
|
+
} else {
|
|
287
|
+
const anthropicProvider = {
|
|
288
|
+
name: "anthropic",
|
|
289
|
+
baseUrl: anthropicBaseUrl ?? "https://api.anthropic.com",
|
|
290
|
+
model: anthropicModel ?? "claude-sonnet-4-20250514",
|
|
291
|
+
format: "anthropic-compatible",
|
|
292
|
+
apiKey: anthropicApiKey,
|
|
293
|
+
timeout: 18e4,
|
|
294
|
+
maxTokens: 65536
|
|
295
|
+
};
|
|
296
|
+
result.providers = [...result.providers, anthropicProvider];
|
|
297
|
+
if (!result.defaultProvider) {
|
|
298
|
+
result.defaultProvider = "anthropic";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
272
302
|
const envAutoMode = process.env.CLISKILL_AUTO_MODE;
|
|
273
303
|
if (envAutoMode) {
|
|
274
304
|
result.autoMode = {
|
|
@@ -457,6 +487,81 @@ var BaseAdapter = class {
|
|
|
457
487
|
|
|
458
488
|
// src/connect/generic-adapter.ts
|
|
459
489
|
import { randomUUID } from "crypto";
|
|
490
|
+
|
|
491
|
+
// src/connect/network-errors.ts
|
|
492
|
+
var NETWORK_ERROR_PATTERNS = [
|
|
493
|
+
"ECONNRESET",
|
|
494
|
+
"ECONNABORTED",
|
|
495
|
+
"ETIMEDOUT",
|
|
496
|
+
"EHOSTUNREACH",
|
|
497
|
+
"ENETUNREACH",
|
|
498
|
+
"WSAETIMEDOUT",
|
|
499
|
+
"WSAECONNRESET",
|
|
500
|
+
"WSAECONNABORTED",
|
|
501
|
+
"fetch failed",
|
|
502
|
+
"network"
|
|
503
|
+
];
|
|
504
|
+
var ABORT_PATTERNS = [
|
|
505
|
+
"AbortError",
|
|
506
|
+
"abort",
|
|
507
|
+
"Abort",
|
|
508
|
+
"timeout",
|
|
509
|
+
"Timeout"
|
|
510
|
+
];
|
|
511
|
+
function isAbortOrNetworkError(err) {
|
|
512
|
+
const name = err.name ?? "";
|
|
513
|
+
const msg = err.message ?? "";
|
|
514
|
+
const code = err.code ?? "";
|
|
515
|
+
if (name === "AbortError") return true;
|
|
516
|
+
const combined = `${name} ${msg} ${code}`;
|
|
517
|
+
for (const pattern of ABORT_PATTERNS) {
|
|
518
|
+
if (combined.includes(pattern)) return true;
|
|
519
|
+
}
|
|
520
|
+
for (const pattern of NETWORK_ERROR_PATTERNS) {
|
|
521
|
+
if (combined.includes(pattern)) return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/connect/sse-parser.ts
|
|
527
|
+
async function* readStreamLines(body) {
|
|
528
|
+
const reader = body.getReader();
|
|
529
|
+
const decoder = new TextDecoder();
|
|
530
|
+
let buffer = "";
|
|
531
|
+
let scanFrom = 0;
|
|
532
|
+
try {
|
|
533
|
+
while (true) {
|
|
534
|
+
let done;
|
|
535
|
+
let value;
|
|
536
|
+
try {
|
|
537
|
+
const result = await reader.read();
|
|
538
|
+
done = result.done;
|
|
539
|
+
value = result.value;
|
|
540
|
+
} catch (err) {
|
|
541
|
+
if (isAbortOrNetworkError(err)) break;
|
|
542
|
+
throw err;
|
|
543
|
+
}
|
|
544
|
+
if (done) break;
|
|
545
|
+
buffer += decoder.decode(value, { stream: true });
|
|
546
|
+
let nlIdx;
|
|
547
|
+
while ((nlIdx = buffer.indexOf("\n", scanFrom)) !== -1) {
|
|
548
|
+
yield buffer.substring(scanFrom, nlIdx);
|
|
549
|
+
scanFrom = nlIdx + 1;
|
|
550
|
+
}
|
|
551
|
+
if (scanFrom > 0) {
|
|
552
|
+
buffer = buffer.substring(scanFrom);
|
|
553
|
+
scanFrom = 0;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (buffer.length > 0) {
|
|
557
|
+
yield buffer;
|
|
558
|
+
}
|
|
559
|
+
} finally {
|
|
560
|
+
reader.releaseLock();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/connect/generic-adapter.ts
|
|
460
565
|
var GenericCompatAdapter = class extends BaseAdapter {
|
|
461
566
|
constructor(config, name) {
|
|
462
567
|
super();
|
|
@@ -593,38 +698,15 @@ var GenericCompatAdapter = class extends BaseAdapter {
|
|
|
593
698
|
};
|
|
594
699
|
}
|
|
595
700
|
async *parseSSEStream(body) {
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const result = await reader.read();
|
|
605
|
-
done = result.done;
|
|
606
|
-
value = result.value;
|
|
607
|
-
} catch (err) {
|
|
608
|
-
if (isAbortOrNetworkError(err)) break;
|
|
609
|
-
throw err;
|
|
610
|
-
}
|
|
611
|
-
if (done) break;
|
|
612
|
-
buffer += decoder.decode(value, { stream: true });
|
|
613
|
-
const lines = buffer.split("\n");
|
|
614
|
-
buffer = lines.pop() ?? "";
|
|
615
|
-
for (const line of lines) {
|
|
616
|
-
const trimmed = line.trim();
|
|
617
|
-
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
618
|
-
if (!trimmed.startsWith("data: ")) continue;
|
|
619
|
-
try {
|
|
620
|
-
const json = JSON.parse(trimmed.slice(6));
|
|
621
|
-
yield* this.processChunk(json);
|
|
622
|
-
} catch {
|
|
623
|
-
}
|
|
624
|
-
}
|
|
701
|
+
for await (const line of readStreamLines(body)) {
|
|
702
|
+
const trimmed = line.trim();
|
|
703
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
704
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
705
|
+
try {
|
|
706
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
707
|
+
yield* this.processChunk(json);
|
|
708
|
+
} catch {
|
|
625
709
|
}
|
|
626
|
-
} finally {
|
|
627
|
-
reader.releaseLock();
|
|
628
710
|
}
|
|
629
711
|
}
|
|
630
712
|
/** Track active tool calls: index → { id, name, args } for emitting tool_use_end */
|
|
@@ -672,11 +754,252 @@ var GenericCompatAdapter = class extends BaseAdapter {
|
|
|
672
754
|
}
|
|
673
755
|
}
|
|
674
756
|
};
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
757
|
+
|
|
758
|
+
// src/connect/anthropic-adapter.ts
|
|
759
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
760
|
+
var MESSAGES_PATH = "/v1/messages";
|
|
761
|
+
var AnthropicAdapter = class extends BaseAdapter {
|
|
762
|
+
constructor(config, name) {
|
|
763
|
+
super();
|
|
764
|
+
this.config = config;
|
|
765
|
+
this.name = name ?? `anthropic:${config.model}`;
|
|
766
|
+
}
|
|
767
|
+
config;
|
|
768
|
+
name;
|
|
769
|
+
async complete(request) {
|
|
770
|
+
const url = this.buildUrl(MESSAGES_PATH);
|
|
771
|
+
const body = this.buildRequestBody(request, false);
|
|
772
|
+
const response = await this.fetchWithRetry(url, () => ({
|
|
773
|
+
method: "POST",
|
|
774
|
+
headers: this.buildHeaders(),
|
|
775
|
+
body: JSON.stringify(body),
|
|
776
|
+
signal: this.buildSignal(request.signal)
|
|
777
|
+
}));
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
const text = await response.text().catch(() => "unknown error");
|
|
780
|
+
throw new Error(`Anthropic error (${response.status}): ${text}`);
|
|
781
|
+
}
|
|
782
|
+
const data = await response.json();
|
|
783
|
+
return this.mapResponse(data);
|
|
784
|
+
}
|
|
785
|
+
async *stream(request) {
|
|
786
|
+
const url = this.buildUrl(MESSAGES_PATH);
|
|
787
|
+
const body = this.buildRequestBody(request, true);
|
|
788
|
+
const response = await this.fetchWithRetry(url, () => ({
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers: this.buildHeaders(),
|
|
791
|
+
body: JSON.stringify(body),
|
|
792
|
+
signal: this.buildStreamingSignal(request.signal)
|
|
793
|
+
}));
|
|
794
|
+
if (!response.ok) {
|
|
795
|
+
const text = await response.text().catch(() => "unknown error");
|
|
796
|
+
throw new Error(`Anthropic error (${response.status}): ${text}`);
|
|
797
|
+
}
|
|
798
|
+
if (!response.body) throw new Error("No response body for streaming");
|
|
799
|
+
yield* this.parseSSEStream(response.body);
|
|
800
|
+
}
|
|
801
|
+
async validate() {
|
|
802
|
+
try {
|
|
803
|
+
const url = this.buildUrl("/v1/models");
|
|
804
|
+
const response = await fetch(url, {
|
|
805
|
+
headers: this.buildHeaders(),
|
|
806
|
+
signal: AbortSignal.timeout(1e4)
|
|
807
|
+
});
|
|
808
|
+
if (!response.ok) return { ok: false, error: `HTTP ${response.status}` };
|
|
809
|
+
return { ok: true };
|
|
810
|
+
} catch (err) {
|
|
811
|
+
return { ok: false, error: err.message };
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
dispose() {
|
|
815
|
+
}
|
|
816
|
+
buildHeaders() {
|
|
817
|
+
const headers = {
|
|
818
|
+
"Content-Type": "application/json",
|
|
819
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
820
|
+
...this.config.headers
|
|
821
|
+
};
|
|
822
|
+
if (this.config.apiKey) {
|
|
823
|
+
headers["x-api-key"] = this.config.apiKey;
|
|
824
|
+
}
|
|
825
|
+
return headers;
|
|
826
|
+
}
|
|
827
|
+
buildRequestBody(request, stream) {
|
|
828
|
+
const messages = [];
|
|
829
|
+
for (const msg of request.messages) {
|
|
830
|
+
const mapped = this.mapMessage(msg);
|
|
831
|
+
if (mapped) messages.push(...mapped);
|
|
832
|
+
}
|
|
833
|
+
const body = {
|
|
834
|
+
model: this.config.model,
|
|
835
|
+
messages,
|
|
836
|
+
max_tokens: request.maxTokens ?? this.config.maxTokens,
|
|
837
|
+
temperature: request.temperature,
|
|
838
|
+
stream
|
|
839
|
+
};
|
|
840
|
+
if (request.systemPrompt) {
|
|
841
|
+
body.system = request.systemPrompt;
|
|
842
|
+
}
|
|
843
|
+
if (request.stopSequences && request.stopSequences.length > 0) {
|
|
844
|
+
body.stop_sequences = request.stopSequences;
|
|
845
|
+
}
|
|
846
|
+
if (request.tools && request.tools.length > 0) {
|
|
847
|
+
body.tools = request.tools.map(this.mapTool);
|
|
848
|
+
}
|
|
849
|
+
return body;
|
|
850
|
+
}
|
|
851
|
+
mapMessage(msg) {
|
|
852
|
+
const textParts = msg.content.filter((b) => b.type === "text");
|
|
853
|
+
const toolUseParts = msg.content.filter((b) => b.type === "tool_use");
|
|
854
|
+
const toolResultParts = msg.content.filter((b) => b.type === "tool_result");
|
|
855
|
+
if (msg.role === "system") {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
if (msg.role === "assistant") {
|
|
859
|
+
const blocks = [];
|
|
860
|
+
const textContent2 = textParts.map((t) => t.text).join("");
|
|
861
|
+
if (textContent2) {
|
|
862
|
+
blocks.push({ type: "text", text: textContent2 });
|
|
863
|
+
}
|
|
864
|
+
for (const tc of toolUseParts) {
|
|
865
|
+
blocks.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.input });
|
|
866
|
+
}
|
|
867
|
+
return [{ role: "assistant", content: blocks.length > 0 ? blocks : "" }];
|
|
868
|
+
}
|
|
869
|
+
if (toolResultParts.length > 0) {
|
|
870
|
+
const blocks = [];
|
|
871
|
+
for (const result of toolResultParts) {
|
|
872
|
+
const sanitizedContent = result.content.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "") || "(empty output)";
|
|
873
|
+
blocks.push({ type: "tool_result", tool_use_id: result.toolUseId, content: sanitizedContent });
|
|
874
|
+
}
|
|
875
|
+
const textContent2 = textParts.map((t) => t.text).join("");
|
|
876
|
+
if (textContent2) {
|
|
877
|
+
blocks.unshift({ type: "text", text: textContent2 });
|
|
878
|
+
}
|
|
879
|
+
return [{ role: "user", content: blocks }];
|
|
880
|
+
}
|
|
881
|
+
const textContent = textParts.map((t) => t.text).join("");
|
|
882
|
+
return [{ role: msg.role, content: textContent || "" }];
|
|
883
|
+
}
|
|
884
|
+
mapTool(tool) {
|
|
885
|
+
return {
|
|
886
|
+
name: tool.name,
|
|
887
|
+
description: tool.description,
|
|
888
|
+
input_schema: tool.inputSchema
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
mapResponse(data) {
|
|
892
|
+
const contentBlocks = [];
|
|
893
|
+
for (const block of data.content) {
|
|
894
|
+
if (block.type === "text") {
|
|
895
|
+
contentBlocks.push({ type: "text", text: block.text });
|
|
896
|
+
} else if (block.type === "tool_use") {
|
|
897
|
+
contentBlocks.push({
|
|
898
|
+
type: "tool_use",
|
|
899
|
+
id: block.id,
|
|
900
|
+
name: block.name,
|
|
901
|
+
input: block.input
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
message: { role: "assistant", content: contentBlocks },
|
|
907
|
+
stopReason: data.stop_reason ?? "end_turn",
|
|
908
|
+
usage: {
|
|
909
|
+
inputTokens: data.usage?.input_tokens ?? 0,
|
|
910
|
+
outputTokens: data.usage?.output_tokens ?? 0,
|
|
911
|
+
cacheReadTokens: data.usage?.cache_read_input_tokens,
|
|
912
|
+
cacheWriteTokens: data.usage?.cache_creation_input_tokens
|
|
913
|
+
},
|
|
914
|
+
model: data.model
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
async *parseSSEStream(body) {
|
|
918
|
+
let currentModel = "";
|
|
919
|
+
let currentToolId = "";
|
|
920
|
+
let currentToolName = "";
|
|
921
|
+
let toolInputBuffer = "";
|
|
922
|
+
for await (const line of readStreamLines(body)) {
|
|
923
|
+
const trimmed = line.trim();
|
|
924
|
+
if (!trimmed || trimmed.startsWith("event:")) continue;
|
|
925
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
926
|
+
const jsonStr = trimmed.slice(6).trim();
|
|
927
|
+
if (!jsonStr) continue;
|
|
928
|
+
let event;
|
|
929
|
+
try {
|
|
930
|
+
event = JSON.parse(jsonStr);
|
|
931
|
+
} catch {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
switch (event.type) {
|
|
935
|
+
case "message_start": {
|
|
936
|
+
if (event.message) {
|
|
937
|
+
currentModel = event.message.model ?? "";
|
|
938
|
+
yield { type: "message_start", model: currentModel };
|
|
939
|
+
}
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case "content_block_start": {
|
|
943
|
+
const block = event.content_block;
|
|
944
|
+
if (block?.type === "tool_use") {
|
|
945
|
+
currentToolId = block.id ?? "";
|
|
946
|
+
currentToolName = block.name ?? "";
|
|
947
|
+
toolInputBuffer = "";
|
|
948
|
+
yield { type: "tool_use_start", id: currentToolId, name: currentToolName };
|
|
949
|
+
}
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
case "content_block_delta": {
|
|
953
|
+
const delta = event.delta;
|
|
954
|
+
if (!delta) break;
|
|
955
|
+
if (delta.type === "text_delta" && delta.text) {
|
|
956
|
+
yield { type: "text_delta", text: delta.text };
|
|
957
|
+
} else if (delta.type === "input_json_delta" && delta.partial_json) {
|
|
958
|
+
toolInputBuffer += delta.partial_json;
|
|
959
|
+
yield { type: "tool_use_delta", id: currentToolId, inputDelta: delta.partial_json };
|
|
960
|
+
}
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
case "content_block_stop": {
|
|
964
|
+
if (currentToolId && currentToolName) {
|
|
965
|
+
let input = {};
|
|
966
|
+
try {
|
|
967
|
+
input = JSON.parse(toolInputBuffer || "{}");
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
yield { type: "tool_use_end", id: currentToolId, name: currentToolName, input };
|
|
971
|
+
currentToolId = "";
|
|
972
|
+
currentToolName = "";
|
|
973
|
+
toolInputBuffer = "";
|
|
974
|
+
}
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
case "message_delta": {
|
|
978
|
+
const delta = event.delta;
|
|
979
|
+
if (delta?.stop_reason) {
|
|
980
|
+
const usage = event.usage ?? { output_tokens: 0 };
|
|
981
|
+
yield {
|
|
982
|
+
type: "message_end",
|
|
983
|
+
stopReason: delta.stop_reason,
|
|
984
|
+
usage: { inputTokens: 0, outputTokens: usage.output_tokens }
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
case "message_stop": {
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
case "ping": {
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
case "error": {
|
|
996
|
+
const errorData = event;
|
|
997
|
+
throw new Error(`Anthropic stream error: ${errorData.error?.message ?? "unknown"}`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
680
1003
|
|
|
681
1004
|
// src/connect/glm-adapter.ts
|
|
682
1005
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -831,38 +1154,15 @@ var GLMAdapter = class extends BaseAdapter {
|
|
|
831
1154
|
};
|
|
832
1155
|
}
|
|
833
1156
|
async *parseSSEStream(body) {
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const result = await reader.read();
|
|
843
|
-
done = result.done;
|
|
844
|
-
value = result.value;
|
|
845
|
-
} catch (err) {
|
|
846
|
-
if (isAbortOrNetworkError2(err)) break;
|
|
847
|
-
throw err;
|
|
848
|
-
}
|
|
849
|
-
if (done) break;
|
|
850
|
-
buffer += decoder.decode(value, { stream: true });
|
|
851
|
-
const lines = buffer.split("\n");
|
|
852
|
-
buffer = lines.pop() ?? "";
|
|
853
|
-
for (const line of lines) {
|
|
854
|
-
const trimmed = line.trim();
|
|
855
|
-
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
856
|
-
if (!trimmed.startsWith("data: ")) continue;
|
|
857
|
-
try {
|
|
858
|
-
const json = JSON.parse(trimmed.slice(6));
|
|
859
|
-
yield* this.processChunk(json);
|
|
860
|
-
} catch {
|
|
861
|
-
}
|
|
862
|
-
}
|
|
1157
|
+
for await (const line of readStreamLines(body)) {
|
|
1158
|
+
const trimmed = line.trim();
|
|
1159
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
1160
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1161
|
+
try {
|
|
1162
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
1163
|
+
yield* this.processChunk(json);
|
|
1164
|
+
} catch {
|
|
863
1165
|
}
|
|
864
|
-
} finally {
|
|
865
|
-
reader.releaseLock();
|
|
866
1166
|
}
|
|
867
1167
|
}
|
|
868
1168
|
/** Track active tool calls: index → { id, name, args } for emitting tool_use_end */
|
|
@@ -917,11 +1217,6 @@ var GLMAdapter = class extends BaseAdapter {
|
|
|
917
1217
|
}
|
|
918
1218
|
}
|
|
919
1219
|
};
|
|
920
|
-
function isAbortOrNetworkError2(err) {
|
|
921
|
-
const name = err.name ?? "";
|
|
922
|
-
const msg = err.message ?? "";
|
|
923
|
-
return name === "AbortError" || msg.includes("abort") || msg.includes("Abort") || msg.includes("timeout") || msg.includes("Timeout") || msg.includes("fetch failed") || msg.includes("ECONNRESET") || msg.includes("network");
|
|
924
|
-
}
|
|
925
1220
|
|
|
926
1221
|
// src/connect/registry.ts
|
|
927
1222
|
var AdapterRegistry = class {
|
|
@@ -955,6 +1250,8 @@ var AdapterRegistry = class {
|
|
|
955
1250
|
return new GenericCompatAdapter(config);
|
|
956
1251
|
case "glm-compatible":
|
|
957
1252
|
return new GLMAdapter(config);
|
|
1253
|
+
case "anthropic-compatible":
|
|
1254
|
+
return new AnthropicAdapter(config);
|
|
958
1255
|
case "custom":
|
|
959
1256
|
throw new Error(`Custom provider format requires a custom adapter for "${config.name}".`);
|
|
960
1257
|
default:
|
|
@@ -8031,109 +8328,208 @@ function buildSystemPrompt() {
|
|
|
8031
8328
|
// src/mcp/client.ts
|
|
8032
8329
|
import { spawn as spawn2 } from "child_process";
|
|
8033
8330
|
import { createInterface } from "readline";
|
|
8034
|
-
var
|
|
8331
|
+
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
8332
|
+
var CONNECT_TIMEOUT = 3e4;
|
|
8333
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
8035
8334
|
var MCPClient = class {
|
|
8036
8335
|
config;
|
|
8037
8336
|
process = null;
|
|
8038
8337
|
rl = null;
|
|
8039
|
-
|
|
8338
|
+
connectionState = "disconnected";
|
|
8040
8339
|
nextId = 1;
|
|
8041
8340
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
8042
|
-
|
|
8341
|
+
serverCapabilities = {};
|
|
8342
|
+
serverInfo;
|
|
8343
|
+
serverInstructions;
|
|
8344
|
+
connectReject = null;
|
|
8043
8345
|
constructor(config) {
|
|
8044
8346
|
this.config = config;
|
|
8045
8347
|
}
|
|
8046
8348
|
async connect() {
|
|
8047
|
-
if (this.connected) return;
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
...this.config.env
|
|
8051
|
-
};
|
|
8052
|
-
this.process = spawn2(this.config.command, this.config.args, {
|
|
8053
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
8054
|
-
env
|
|
8055
|
-
});
|
|
8056
|
-
this.process.on("error", (err) => {
|
|
8057
|
-
this.cleanup();
|
|
8058
|
-
throw err;
|
|
8059
|
-
});
|
|
8060
|
-
this.process.on("exit", () => {
|
|
8061
|
-
this.cleanup();
|
|
8062
|
-
});
|
|
8063
|
-
if (!this.process.stdout) {
|
|
8064
|
-
throw new Error("MCP server stdout is not available");
|
|
8349
|
+
if (this.connectionState === "connected") return;
|
|
8350
|
+
if (this.connectionState === "connecting") {
|
|
8351
|
+
throw new Error(`MCP server "${this.config.name}" is already connecting`);
|
|
8065
8352
|
}
|
|
8066
|
-
this.
|
|
8067
|
-
|
|
8068
|
-
this.
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8353
|
+
this.connectionState = "connecting";
|
|
8354
|
+
try {
|
|
8355
|
+
await this.spawnProcess();
|
|
8356
|
+
await this.performHandshake();
|
|
8357
|
+
this.connectionState = "connected";
|
|
8358
|
+
} catch (error) {
|
|
8359
|
+
this.cleanup();
|
|
8360
|
+
this.connectionState = "failed";
|
|
8361
|
+
throw error;
|
|
8073
8362
|
}
|
|
8074
|
-
await this.sendRequest("initialize", {
|
|
8075
|
-
protocolVersion: "2024-11-05",
|
|
8076
|
-
capabilities: {},
|
|
8077
|
-
clientInfo: { name: "cliskill", version: "1.0.0" }
|
|
8078
|
-
});
|
|
8079
|
-
this.sendNotification("notifications/initialized", {});
|
|
8080
|
-
this.connected = true;
|
|
8081
8363
|
}
|
|
8082
8364
|
async disconnect() {
|
|
8083
|
-
if (
|
|
8365
|
+
if (this.connectionState === "disconnected") return;
|
|
8084
8366
|
for (const [, pending] of this.pendingRequests) {
|
|
8085
8367
|
clearTimeout(pending.timer);
|
|
8086
8368
|
pending.reject(new Error("Connection closed"));
|
|
8087
8369
|
}
|
|
8088
8370
|
this.pendingRequests.clear();
|
|
8089
|
-
this.process.
|
|
8371
|
+
if (this.process != null && !this.process.killed) {
|
|
8372
|
+
try {
|
|
8373
|
+
this.process.kill("SIGTERM");
|
|
8374
|
+
await new Promise((resolve10) => {
|
|
8375
|
+
if (!this.process) {
|
|
8376
|
+
resolve10();
|
|
8377
|
+
return;
|
|
8378
|
+
}
|
|
8379
|
+
const forceKillTimer = setTimeout(() => {
|
|
8380
|
+
try {
|
|
8381
|
+
this.process?.kill("SIGKILL");
|
|
8382
|
+
} catch {
|
|
8383
|
+
}
|
|
8384
|
+
resolve10();
|
|
8385
|
+
}, 5e3);
|
|
8386
|
+
this.process.once("exit", () => {
|
|
8387
|
+
clearTimeout(forceKillTimer);
|
|
8388
|
+
resolve10();
|
|
8389
|
+
});
|
|
8390
|
+
});
|
|
8391
|
+
} catch {
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
8090
8394
|
this.cleanup();
|
|
8091
8395
|
}
|
|
8092
8396
|
async listTools() {
|
|
8093
8397
|
const response = await this.sendRequest("tools/list", {});
|
|
8398
|
+
this.assertNoError(response);
|
|
8094
8399
|
const result = response.result;
|
|
8095
8400
|
return result?.tools ?? [];
|
|
8096
8401
|
}
|
|
8097
8402
|
async callTool(name, args) {
|
|
8098
8403
|
const response = await this.sendRequest("tools/call", { name, arguments: args });
|
|
8099
|
-
|
|
8404
|
+
this.assertNoError(response);
|
|
8405
|
+
const result = response.result;
|
|
8406
|
+
return result ?? { content: [{ type: "text", text: "No result from MCP server" }] };
|
|
8100
8407
|
}
|
|
8101
8408
|
async listResources() {
|
|
8102
8409
|
const response = await this.sendRequest("resources/list", {});
|
|
8410
|
+
this.assertNoError(response);
|
|
8103
8411
|
const result = response.result;
|
|
8104
8412
|
return result?.resources ?? [];
|
|
8105
8413
|
}
|
|
8106
8414
|
async readResource(uri) {
|
|
8107
8415
|
const response = await this.sendRequest("resources/read", { uri });
|
|
8416
|
+
this.assertNoError(response);
|
|
8108
8417
|
return response.result;
|
|
8109
8418
|
}
|
|
8110
8419
|
async listPrompts() {
|
|
8111
8420
|
const response = await this.sendRequest("prompts/list", {});
|
|
8421
|
+
this.assertNoError(response);
|
|
8112
8422
|
const result = response.result;
|
|
8113
8423
|
return result?.prompts ?? [];
|
|
8114
8424
|
}
|
|
8115
8425
|
isConnected() {
|
|
8116
|
-
return this.connected;
|
|
8426
|
+
return this.connectionState === "connected";
|
|
8427
|
+
}
|
|
8428
|
+
getState() {
|
|
8429
|
+
return this.connectionState;
|
|
8430
|
+
}
|
|
8431
|
+
getServerInfo() {
|
|
8432
|
+
return this.serverInfo;
|
|
8433
|
+
}
|
|
8434
|
+
getServerInstructions() {
|
|
8435
|
+
return this.serverInstructions;
|
|
8436
|
+
}
|
|
8437
|
+
getCapabilities() {
|
|
8438
|
+
return this.serverCapabilities;
|
|
8439
|
+
}
|
|
8440
|
+
getConfig() {
|
|
8441
|
+
return this.config;
|
|
8442
|
+
}
|
|
8443
|
+
async spawnProcess() {
|
|
8444
|
+
const env = {
|
|
8445
|
+
...process.env,
|
|
8446
|
+
...this.config.env
|
|
8447
|
+
};
|
|
8448
|
+
this.process = spawn2(this.config.command, this.config.args, {
|
|
8449
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
8450
|
+
env
|
|
8451
|
+
});
|
|
8452
|
+
this.process.on("error", (err) => {
|
|
8453
|
+
this.handleProcessError(err);
|
|
8454
|
+
});
|
|
8455
|
+
this.process.on("exit", (code, signal) => {
|
|
8456
|
+
if (this.connectionState === "connected" || this.connectionState === "connecting") {
|
|
8457
|
+
this.handleProcessExit(code, signal);
|
|
8458
|
+
}
|
|
8459
|
+
});
|
|
8460
|
+
if (!this.process.stdout) {
|
|
8461
|
+
throw new Error(`MCP server "${this.config.name}": stdout is not available`);
|
|
8462
|
+
}
|
|
8463
|
+
if (!this.process.stdin) {
|
|
8464
|
+
throw new Error(`MCP server "${this.config.name}": stdin is not available`);
|
|
8465
|
+
}
|
|
8466
|
+
this.rl = createInterface({ input: this.process.stdout });
|
|
8467
|
+
this.rl.on("line", (line) => {
|
|
8468
|
+
this.handleLine(line);
|
|
8469
|
+
});
|
|
8470
|
+
if (this.process.stderr) {
|
|
8471
|
+
this.process.stderr.on("data", (data) => {
|
|
8472
|
+
const text = data.toString().trim();
|
|
8473
|
+
if (text) {
|
|
8474
|
+
console.error(`[MCP:${this.config.name}:stderr] ${text}`);
|
|
8475
|
+
}
|
|
8476
|
+
});
|
|
8477
|
+
}
|
|
8478
|
+
}
|
|
8479
|
+
async performHandshake() {
|
|
8480
|
+
const connectPromise = this.sendRequest("initialize", {
|
|
8481
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
8482
|
+
capabilities: {},
|
|
8483
|
+
clientInfo: { name: "cliskill", version: "1.0.0" }
|
|
8484
|
+
});
|
|
8485
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
8486
|
+
const timer = setTimeout(() => {
|
|
8487
|
+
reject(new Error(
|
|
8488
|
+
`MCP server "${this.config.name}" connection timed out after ${CONNECT_TIMEOUT}ms`
|
|
8489
|
+
));
|
|
8490
|
+
}, CONNECT_TIMEOUT);
|
|
8491
|
+
timer.unref();
|
|
8492
|
+
});
|
|
8493
|
+
const response = await Promise.race([connectPromise, timeoutPromise]);
|
|
8494
|
+
this.assertNoError(response);
|
|
8495
|
+
const initResult = response.result;
|
|
8496
|
+
if (!initResult) {
|
|
8497
|
+
throw new Error(`MCP server "${this.config.name}" returned empty initialize result`);
|
|
8498
|
+
}
|
|
8499
|
+
if (initResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
|
|
8500
|
+
throw new Error(
|
|
8501
|
+
`MCP server "${this.config.name}" uses incompatible protocol version: ${initResult.protocolVersion} (expected ${MCP_PROTOCOL_VERSION})`
|
|
8502
|
+
);
|
|
8503
|
+
}
|
|
8504
|
+
this.serverCapabilities = initResult.capabilities ?? {};
|
|
8505
|
+
this.serverInfo = initResult.serverInfo;
|
|
8506
|
+
this.serverInstructions = initResult.instructions;
|
|
8507
|
+
this.sendNotification("notifications/initialized", {});
|
|
8117
8508
|
}
|
|
8118
8509
|
sendRequest(method, params) {
|
|
8119
8510
|
return new Promise((resolve10, reject) => {
|
|
8120
8511
|
if (!this.process?.stdin) {
|
|
8121
|
-
reject(new Error(
|
|
8512
|
+
reject(new Error(`MCP server "${this.config.name}" is not connected`));
|
|
8122
8513
|
return;
|
|
8123
8514
|
}
|
|
8124
8515
|
const id = this.nextId++;
|
|
8125
8516
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
8126
8517
|
const timer = setTimeout(() => {
|
|
8127
8518
|
this.pendingRequests.delete(id);
|
|
8128
|
-
reject(new Error(
|
|
8129
|
-
|
|
8519
|
+
reject(new Error(
|
|
8520
|
+
`Request timeout: ${method} (id=${id}) on server "${this.config.name}"`
|
|
8521
|
+
));
|
|
8522
|
+
}, DEFAULT_REQUEST_TIMEOUT);
|
|
8523
|
+
timer.unref();
|
|
8130
8524
|
this.pendingRequests.set(id, { resolve: resolve10, reject, timer });
|
|
8131
8525
|
const data = JSON.stringify(request) + "\n";
|
|
8132
8526
|
this.process.stdin.write(data, (err) => {
|
|
8133
8527
|
if (err) {
|
|
8134
8528
|
clearTimeout(timer);
|
|
8135
8529
|
this.pendingRequests.delete(id);
|
|
8136
|
-
reject(
|
|
8530
|
+
reject(new Error(
|
|
8531
|
+
`Failed to send request to MCP server "${this.config.name}": ${err.message}`
|
|
8532
|
+
));
|
|
8137
8533
|
}
|
|
8138
8534
|
});
|
|
8139
8535
|
});
|
|
@@ -8142,19 +8538,23 @@ var MCPClient = class {
|
|
|
8142
8538
|
if (!this.process?.stdin) return;
|
|
8143
8539
|
const notification = { jsonrpc: "2.0", method, params };
|
|
8144
8540
|
const data = JSON.stringify(notification) + "\n";
|
|
8145
|
-
this.process.stdin.write(data)
|
|
8541
|
+
this.process.stdin.write(data, (err) => {
|
|
8542
|
+
if (err) {
|
|
8543
|
+
console.error(
|
|
8544
|
+
`[MCP:${this.config.name}] Failed to send notification "${method}": ${err.message}`
|
|
8545
|
+
);
|
|
8546
|
+
}
|
|
8547
|
+
});
|
|
8146
8548
|
}
|
|
8147
8549
|
handleLine(line) {
|
|
8148
|
-
|
|
8550
|
+
if (!line.trim()) return;
|
|
8149
8551
|
let response;
|
|
8150
8552
|
try {
|
|
8151
|
-
response = JSON.parse(
|
|
8553
|
+
response = JSON.parse(line);
|
|
8152
8554
|
} catch {
|
|
8153
8555
|
return;
|
|
8154
|
-
} finally {
|
|
8155
|
-
this.buffer = "";
|
|
8156
8556
|
}
|
|
8157
|
-
if (response.id
|
|
8557
|
+
if (response.id != null) {
|
|
8158
8558
|
const pending = this.pendingRequests.get(response.id);
|
|
8159
8559
|
if (pending) {
|
|
8160
8560
|
clearTimeout(pending.timer);
|
|
@@ -8163,12 +8563,47 @@ var MCPClient = class {
|
|
|
8163
8563
|
}
|
|
8164
8564
|
}
|
|
8165
8565
|
}
|
|
8566
|
+
handleProcessError(err) {
|
|
8567
|
+
const message = `MCP server "${this.config.name}" process error: ${err.message}`;
|
|
8568
|
+
console.error(`[MCP] ${message}`);
|
|
8569
|
+
for (const [, pending] of this.pendingRequests) {
|
|
8570
|
+
clearTimeout(pending.timer);
|
|
8571
|
+
pending.reject(new Error(message));
|
|
8572
|
+
}
|
|
8573
|
+
this.pendingRequests.clear();
|
|
8574
|
+
if (this.connectReject) {
|
|
8575
|
+
this.connectReject(new Error(message));
|
|
8576
|
+
this.connectReject = null;
|
|
8577
|
+
}
|
|
8578
|
+
this.cleanup();
|
|
8579
|
+
}
|
|
8580
|
+
handleProcessExit(code, signal) {
|
|
8581
|
+
const reason = signal ? `terminated by signal ${signal}` : `exited with code ${code}`;
|
|
8582
|
+
const message = `MCP server "${this.config.name}" ${reason}`;
|
|
8583
|
+
for (const [, pending] of this.pendingRequests) {
|
|
8584
|
+
clearTimeout(pending.timer);
|
|
8585
|
+
pending.reject(new Error(message));
|
|
8586
|
+
}
|
|
8587
|
+
this.pendingRequests.clear();
|
|
8588
|
+
this.cleanup();
|
|
8589
|
+
}
|
|
8590
|
+
assertNoError(response) {
|
|
8591
|
+
if (response.error) {
|
|
8592
|
+
const { code, message, data } = response.error;
|
|
8593
|
+
const detail = data ? ` | data: ${JSON.stringify(data)}` : "";
|
|
8594
|
+
throw new Error(
|
|
8595
|
+
`MCP server "${this.config.name}" returned error [${code}]: ${message}${detail}`
|
|
8596
|
+
);
|
|
8597
|
+
}
|
|
8598
|
+
}
|
|
8166
8599
|
cleanup() {
|
|
8167
|
-
this.
|
|
8600
|
+
this.connectionState = "disconnected";
|
|
8168
8601
|
this.rl?.close();
|
|
8169
8602
|
this.rl = null;
|
|
8170
8603
|
this.process = null;
|
|
8171
|
-
this.
|
|
8604
|
+
this.serverCapabilities = {};
|
|
8605
|
+
this.serverInfo = void 0;
|
|
8606
|
+
this.serverInstructions = void 0;
|
|
8172
8607
|
}
|
|
8173
8608
|
};
|
|
8174
8609
|
|
|
@@ -8176,28 +8611,51 @@ var MCPClient = class {
|
|
|
8176
8611
|
var MCPConnectionManager = class {
|
|
8177
8612
|
clients = /* @__PURE__ */ new Map();
|
|
8178
8613
|
async addServer(config) {
|
|
8614
|
+
if (this.clients.has(config.name)) {
|
|
8615
|
+
await this.removeServer(config.name);
|
|
8616
|
+
}
|
|
8179
8617
|
const client = new MCPClient(config);
|
|
8180
|
-
|
|
8181
|
-
|
|
8618
|
+
try {
|
|
8619
|
+
await client.connect();
|
|
8620
|
+
this.clients.set(config.name, client);
|
|
8621
|
+
} catch (error) {
|
|
8622
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8623
|
+
console.error(`[MCP] Failed to connect server "${config.name}": ${message}`);
|
|
8624
|
+
try {
|
|
8625
|
+
await client.disconnect();
|
|
8626
|
+
} catch {
|
|
8627
|
+
}
|
|
8628
|
+
throw error;
|
|
8629
|
+
}
|
|
8182
8630
|
}
|
|
8183
8631
|
async removeServer(name) {
|
|
8184
8632
|
const client = this.clients.get(name);
|
|
8185
|
-
if (client)
|
|
8633
|
+
if (!client) return;
|
|
8634
|
+
try {
|
|
8186
8635
|
await client.disconnect();
|
|
8636
|
+
} catch (error) {
|
|
8637
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8638
|
+
console.error(`[MCP] Error disconnecting server "${name}": ${message}`);
|
|
8639
|
+
} finally {
|
|
8187
8640
|
this.clients.delete(name);
|
|
8188
8641
|
}
|
|
8189
8642
|
}
|
|
8190
8643
|
getConnectedServers() {
|
|
8191
8644
|
return Array.from(this.clients.entries()).filter(([, client]) => client.isConnected()).map(([name]) => name);
|
|
8192
8645
|
}
|
|
8646
|
+
getClient(name) {
|
|
8647
|
+
return this.clients.get(name);
|
|
8648
|
+
}
|
|
8193
8649
|
async getAllTools() {
|
|
8194
8650
|
const allTools = [];
|
|
8195
|
-
for (const [, client] of this.clients) {
|
|
8651
|
+
for (const [name, client] of this.clients) {
|
|
8196
8652
|
if (!client.isConnected()) continue;
|
|
8197
8653
|
try {
|
|
8198
8654
|
const tools = await client.listTools();
|
|
8199
8655
|
allTools.push(...tools);
|
|
8200
|
-
} catch {
|
|
8656
|
+
} catch (error) {
|
|
8657
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8658
|
+
console.error(`[MCP] Failed to list tools from "${name}": ${message}`);
|
|
8201
8659
|
}
|
|
8202
8660
|
}
|
|
8203
8661
|
return allTools;
|
|
@@ -8217,17 +8675,40 @@ var MCPConnectionManager = class {
|
|
|
8217
8675
|
if (!client || !client.isConnected()) {
|
|
8218
8676
|
return [];
|
|
8219
8677
|
}
|
|
8220
|
-
|
|
8678
|
+
try {
|
|
8679
|
+
return await client.listTools();
|
|
8680
|
+
} catch (error) {
|
|
8681
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8682
|
+
console.error(`[MCP] Failed to list tools from "${serverName}": ${message}`);
|
|
8683
|
+
return [];
|
|
8684
|
+
}
|
|
8221
8685
|
}
|
|
8222
8686
|
async disconnectAll() {
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8687
|
+
const disconnectPromises = Array.from(this.clients.entries()).map(
|
|
8688
|
+
async ([name, client]) => {
|
|
8689
|
+
try {
|
|
8690
|
+
await client.disconnect();
|
|
8691
|
+
} catch (error) {
|
|
8692
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8693
|
+
console.error(`[MCP] Error disconnecting server "${name}": ${message}`);
|
|
8694
|
+
}
|
|
8227
8695
|
}
|
|
8228
|
-
|
|
8696
|
+
);
|
|
8697
|
+
await Promise.allSettled(disconnectPromises);
|
|
8229
8698
|
this.clients.clear();
|
|
8230
8699
|
}
|
|
8700
|
+
/** Register graceful shutdown handlers for SIGINT/SIGTERM. */
|
|
8701
|
+
registerShutdownHandlers() {
|
|
8702
|
+
const handler = async () => {
|
|
8703
|
+
await this.disconnectAll();
|
|
8704
|
+
};
|
|
8705
|
+
process.on("SIGINT", handler);
|
|
8706
|
+
process.on("SIGTERM", handler);
|
|
8707
|
+
return () => {
|
|
8708
|
+
process.off("SIGINT", handler);
|
|
8709
|
+
process.off("SIGTERM", handler);
|
|
8710
|
+
};
|
|
8711
|
+
}
|
|
8231
8712
|
};
|
|
8232
8713
|
|
|
8233
8714
|
// src/mcp/mcp-tool-adapter.ts
|
|
@@ -8236,19 +8717,23 @@ var MCPToolAdapter = class {
|
|
|
8236
8717
|
name;
|
|
8237
8718
|
description;
|
|
8238
8719
|
inputSchema;
|
|
8239
|
-
riskLevel
|
|
8720
|
+
riskLevel;
|
|
8240
8721
|
concurrencySafe = true;
|
|
8241
|
-
readOnly
|
|
8722
|
+
readOnly;
|
|
8242
8723
|
serverName;
|
|
8724
|
+
originalToolName;
|
|
8243
8725
|
mcpManager;
|
|
8244
8726
|
rawInputSchema;
|
|
8245
|
-
constructor(serverName, toolName, description, inputSchema2, mcpManager) {
|
|
8727
|
+
constructor(serverName, toolName, description, inputSchema2, mcpManager, annotations) {
|
|
8728
|
+
this.serverName = serverName;
|
|
8729
|
+
this.originalToolName = toolName;
|
|
8246
8730
|
this.name = `mcp_${serverName}_${toolName}`;
|
|
8247
8731
|
this.description = description;
|
|
8248
|
-
this.serverName = serverName;
|
|
8249
8732
|
this.mcpManager = mcpManager;
|
|
8250
8733
|
this.rawInputSchema = inputSchema2;
|
|
8251
8734
|
this.inputSchema = this.buildZodSchema(inputSchema2);
|
|
8735
|
+
this.readOnly = annotations?.readOnlyHint === true;
|
|
8736
|
+
this.riskLevel = this.deriveRiskLevel(annotations);
|
|
8252
8737
|
}
|
|
8253
8738
|
async execute(input, context) {
|
|
8254
8739
|
const allowed = await context.checkPermission(this.name, JSON.stringify(input));
|
|
@@ -8256,8 +8741,16 @@ var MCPToolAdapter = class {
|
|
|
8256
8741
|
return `Error: Permission denied for MCP tool: ${this.name}`;
|
|
8257
8742
|
}
|
|
8258
8743
|
try {
|
|
8259
|
-
const result = await this.mcpManager.callTool(
|
|
8260
|
-
|
|
8744
|
+
const result = await this.mcpManager.callTool(
|
|
8745
|
+
this.serverName,
|
|
8746
|
+
this.originalToolName,
|
|
8747
|
+
input
|
|
8748
|
+
);
|
|
8749
|
+
if (result.isError) {
|
|
8750
|
+
const errorText = result.content.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n");
|
|
8751
|
+
return `Error from MCP tool ${this.name}: ${errorText || "Unknown error"}`;
|
|
8752
|
+
}
|
|
8753
|
+
return this.formatResult(result);
|
|
8261
8754
|
} catch (err) {
|
|
8262
8755
|
return `Error calling MCP tool ${this.name}: ${err.message}`;
|
|
8263
8756
|
}
|
|
@@ -8270,12 +8763,29 @@ var MCPToolAdapter = class {
|
|
|
8270
8763
|
};
|
|
8271
8764
|
}
|
|
8272
8765
|
getOriginalName() {
|
|
8273
|
-
|
|
8274
|
-
return parts.slice(2).join("_");
|
|
8766
|
+
return this.originalToolName;
|
|
8275
8767
|
}
|
|
8276
8768
|
getServerName() {
|
|
8277
8769
|
return this.serverName;
|
|
8278
8770
|
}
|
|
8771
|
+
deriveRiskLevel(annotations) {
|
|
8772
|
+
if (annotations?.destructiveHint === true) return "destructive";
|
|
8773
|
+
if (annotations?.readOnlyHint === true) return "readonly";
|
|
8774
|
+
return "safe";
|
|
8775
|
+
}
|
|
8776
|
+
formatResult(result) {
|
|
8777
|
+
const parts = [];
|
|
8778
|
+
for (const block of result.content) {
|
|
8779
|
+
if (block.type === "text" && block.text != null) {
|
|
8780
|
+
parts.push(block.text);
|
|
8781
|
+
} else if (block.type === "image" && block.data != null) {
|
|
8782
|
+
parts.push(`[Image: ${block.mimeType ?? "unknown"}, ${block.data.length} bytes base64]`);
|
|
8783
|
+
} else if (block.type === "resource" && block.text != null) {
|
|
8784
|
+
parts.push(block.text);
|
|
8785
|
+
}
|
|
8786
|
+
}
|
|
8787
|
+
return parts.length > 0 ? parts.join("\n") : JSON.stringify(result, null, 2);
|
|
8788
|
+
}
|
|
8279
8789
|
buildZodSchema(jsonSchema) {
|
|
8280
8790
|
const properties = jsonSchema.properties ?? {};
|
|
8281
8791
|
const required = new Set(jsonSchema.required ?? []);
|
|
@@ -8291,10 +8801,21 @@ var MCPToolAdapter = class {
|
|
|
8291
8801
|
switch (type) {
|
|
8292
8802
|
case "string":
|
|
8293
8803
|
if (prop.enum) return z25.enum(prop.enum);
|
|
8804
|
+
if (prop.format === "date-time") return z25.string().datetime();
|
|
8805
|
+
if (typeof prop.minLength === "number" || typeof prop.maxLength === "number") {
|
|
8806
|
+
let schema = z25.string();
|
|
8807
|
+
if (typeof prop.minLength === "number") schema = schema.min(prop.minLength);
|
|
8808
|
+
if (typeof prop.maxLength === "number") schema = schema.max(prop.maxLength);
|
|
8809
|
+
return schema;
|
|
8810
|
+
}
|
|
8294
8811
|
return z25.string();
|
|
8295
8812
|
case "number":
|
|
8296
|
-
case "integer":
|
|
8297
|
-
|
|
8813
|
+
case "integer": {
|
|
8814
|
+
let schema = z25.number();
|
|
8815
|
+
if (typeof prop.minimum === "number") schema = schema.min(prop.minimum);
|
|
8816
|
+
if (typeof prop.maximum === "number") schema = schema.max(prop.maximum);
|
|
8817
|
+
return schema;
|
|
8818
|
+
}
|
|
8298
8819
|
case "boolean":
|
|
8299
8820
|
return z25.boolean();
|
|
8300
8821
|
case "array":
|
|
@@ -8323,12 +8844,15 @@ async function registerMCPTools(mcpManager, registerFn) {
|
|
|
8323
8844
|
tool.name,
|
|
8324
8845
|
tool.description ?? `MCP tool: ${tool.name}`,
|
|
8325
8846
|
tool.inputSchema,
|
|
8326
|
-
mcpManager
|
|
8847
|
+
mcpManager,
|
|
8848
|
+
tool.annotations
|
|
8327
8849
|
);
|
|
8328
8850
|
registerFn(adapter);
|
|
8329
8851
|
registered.push(adapter.name);
|
|
8330
8852
|
}
|
|
8331
|
-
} catch {
|
|
8853
|
+
} catch (error) {
|
|
8854
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8855
|
+
console.error(`[MCP] Failed to register tools from "${serverName}": ${message}`);
|
|
8332
8856
|
}
|
|
8333
8857
|
}
|
|
8334
8858
|
return registered;
|
|
@@ -8398,7 +8922,7 @@ async function connectMcpServers(config, toolRegistry) {
|
|
|
8398
8922
|
console.error(`MCP server "${serverConfig.name}" connection failed: ${err.message}`);
|
|
8399
8923
|
}
|
|
8400
8924
|
}
|
|
8401
|
-
if (connectedCount === 0) return
|
|
8925
|
+
if (connectedCount === 0) return null;
|
|
8402
8926
|
const registeredTools = await registerMCPTools(mcpManager, (tool) => {
|
|
8403
8927
|
try {
|
|
8404
8928
|
toolRegistry.register(tool);
|
|
@@ -8408,6 +8932,7 @@ async function connectMcpServers(config, toolRegistry) {
|
|
|
8408
8932
|
if (registeredTools.length > 0) {
|
|
8409
8933
|
console.log(` MCP: ${connectedCount} server(s) connected, ${registeredTools.length} tool(s) loaded`);
|
|
8410
8934
|
}
|
|
8935
|
+
mcpManager.registerShutdownHandlers();
|
|
8411
8936
|
return mcpManager;
|
|
8412
8937
|
}
|
|
8413
8938
|
async function runTuiRepl(config) {
|
|
@@ -10651,6 +11176,13 @@ var PROVIDER_OPTIONS = [
|
|
|
10651
11176
|
requiresApiKey: true,
|
|
10652
11177
|
format: "openai-compatible"
|
|
10653
11178
|
},
|
|
11179
|
+
{
|
|
11180
|
+
id: "anthropic",
|
|
11181
|
+
label: "Anthropic Claude",
|
|
11182
|
+
defaultBaseUrl: "https://api.anthropic.com",
|
|
11183
|
+
requiresApiKey: true,
|
|
11184
|
+
format: "anthropic-compatible"
|
|
11185
|
+
},
|
|
10654
11186
|
{
|
|
10655
11187
|
id: "ollama",
|
|
10656
11188
|
label: "Ollama (local)",
|
|
@@ -10850,13 +11382,18 @@ import { useState as useState7, useEffect as useEffect2 } from "react";
|
|
|
10850
11382
|
import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
|
|
10851
11383
|
|
|
10852
11384
|
// src/connect/fetch-models.ts
|
|
10853
|
-
async function fetchModelsFromApi(baseUrl, apiKey) {
|
|
11385
|
+
async function fetchModelsFromApi(baseUrl, apiKey, format) {
|
|
10854
11386
|
const url = baseUrl.replace(/\/$/, "") + "/models";
|
|
10855
11387
|
const headers = {
|
|
10856
11388
|
"Content-Type": "application/json"
|
|
10857
11389
|
};
|
|
10858
11390
|
if (apiKey) {
|
|
10859
|
-
|
|
11391
|
+
if (format === "anthropic-compatible") {
|
|
11392
|
+
headers["x-api-key"] = apiKey;
|
|
11393
|
+
headers["anthropic-version"] = "2023-06-01";
|
|
11394
|
+
} else {
|
|
11395
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
11396
|
+
}
|
|
10860
11397
|
}
|
|
10861
11398
|
const response = await fetch(url, {
|
|
10862
11399
|
method: "GET",
|
|
@@ -10874,9 +11411,12 @@ async function fetchModelsFromApi(baseUrl, apiKey) {
|
|
|
10874
11411
|
}
|
|
10875
11412
|
var CATEGORY_PATTERNS = [
|
|
10876
11413
|
{ pattern: /o[1-4](?:-|$|-mini)/i, category: "reasoning" },
|
|
11414
|
+
{ pattern: /claude-opus|claude-[34]\.(\d)+/i, category: "reasoning" },
|
|
10877
11415
|
{ pattern: /reason|think|deep/i, category: "reasoning" },
|
|
11416
|
+
{ pattern: /claude-sonnet/i, category: "code" },
|
|
10878
11417
|
{ pattern: /code|codestral|deepseek-coder|qwen.*coder|starcoder/i, category: "code" },
|
|
10879
11418
|
{ pattern: /mini|flash|fast|turbo|haiku|lite/i, category: "fast" },
|
|
11419
|
+
{ pattern: /claude-haiku/i, category: "fast" },
|
|
10880
11420
|
{ pattern: /creative|dall/i, category: "creative" }
|
|
10881
11421
|
];
|
|
10882
11422
|
function classifyModelName(modelName) {
|
|
@@ -10952,7 +11492,7 @@ function ModelStep() {
|
|
|
10952
11492
|
setPhase("fallback");
|
|
10953
11493
|
return;
|
|
10954
11494
|
}
|
|
10955
|
-
fetchModelsFromApi(state.baseUrl, state.apiKey || void 0).then((fetched) => {
|
|
11495
|
+
fetchModelsFromApi(state.baseUrl, state.apiKey || void 0, state.provider?.format).then((fetched) => {
|
|
10956
11496
|
if (fetched.length === 0) {
|
|
10957
11497
|
setError("No models found at this endpoint");
|
|
10958
11498
|
setPhase("fallback");
|
|
@@ -11640,4 +12180,4 @@ export {
|
|
|
11640
12180
|
MCPConnectionManager,
|
|
11641
12181
|
runCli
|
|
11642
12182
|
};
|
|
11643
|
-
//# sourceMappingURL=chunk-
|
|
12183
|
+
//# sourceMappingURL=chunk-SDHXIBKZ.js.map
|