cliskill 1.1.0 → 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-LHS6LLBW.js → chunk-SDHXIBKZ.js} +905 -278
- package/dist/chunk-SDHXIBKZ.js.map +1 -0
- package/dist/index.d.ts +108 -29
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LHS6LLBW.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:
|
|
@@ -5540,6 +5837,32 @@ var EnhancedMemoryStore = class {
|
|
|
5540
5837
|
}
|
|
5541
5838
|
return result;
|
|
5542
5839
|
}
|
|
5840
|
+
/**
|
|
5841
|
+
* Get a formatted summary of all memories for injection into system prompt.
|
|
5842
|
+
* Returns empty string if no memories stored.
|
|
5843
|
+
*/
|
|
5844
|
+
getSummary() {
|
|
5845
|
+
if (this.entries.size === 0) return "";
|
|
5846
|
+
const sections = [];
|
|
5847
|
+
for (const [topic, entries] of this.entries) {
|
|
5848
|
+
const latest = entries[entries.length - 1];
|
|
5849
|
+
if (!latest) continue;
|
|
5850
|
+
const age = Date.now() - latest.timestamp;
|
|
5851
|
+
const ageDays = Math.floor(age / (1e3 * 60 * 60 * 24));
|
|
5852
|
+
const ageStr = ageDays === 0 ? "today" : ageDays === 1 ? "yesterday" : `${ageDays}d ago`;
|
|
5853
|
+
sections.push(`- **${topic}**: ${latest.content} (${ageStr})`);
|
|
5854
|
+
}
|
|
5855
|
+
if (sections.length === 0) return "";
|
|
5856
|
+
return [
|
|
5857
|
+
"<global-memory>",
|
|
5858
|
+
"The following memories were saved in previous sessions and persist across conversations:",
|
|
5859
|
+
"",
|
|
5860
|
+
...sections,
|
|
5861
|
+
"",
|
|
5862
|
+
"Use the memory tool to save new facts or search/recall existing memories.",
|
|
5863
|
+
"</global-memory>"
|
|
5864
|
+
].join("\n");
|
|
5865
|
+
}
|
|
5543
5866
|
async persist(topic) {
|
|
5544
5867
|
const entries = this.entries.get(topic);
|
|
5545
5868
|
if (!entries || entries.length === 0) {
|
|
@@ -5555,6 +5878,25 @@ var EnhancedMemoryStore = class {
|
|
|
5555
5878
|
}
|
|
5556
5879
|
};
|
|
5557
5880
|
|
|
5881
|
+
// src/memory/global-memory.ts
|
|
5882
|
+
var cachedSummary = null;
|
|
5883
|
+
async function loadGlobalMemorySummary() {
|
|
5884
|
+
if (cachedSummary !== null) return cachedSummary;
|
|
5885
|
+
try {
|
|
5886
|
+
const dir = getGlobalMemoryDir();
|
|
5887
|
+
const store = new EnhancedMemoryStore(dir);
|
|
5888
|
+
await store.init();
|
|
5889
|
+
cachedSummary = store.getSummary();
|
|
5890
|
+
return cachedSummary;
|
|
5891
|
+
} catch {
|
|
5892
|
+
cachedSummary = "";
|
|
5893
|
+
return "";
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
function invalidateGlobalMemoryCache() {
|
|
5897
|
+
cachedSummary = null;
|
|
5898
|
+
}
|
|
5899
|
+
|
|
5558
5900
|
// src/tools/builtins/memory-tool.ts
|
|
5559
5901
|
var memoryInputSchema = z19.object({
|
|
5560
5902
|
action: z19.enum(["save", "recall", "search", "list", "delete"]).describe(
|
|
@@ -5590,6 +5932,7 @@ var MemoryTool = class extends BaseTool {
|
|
|
5590
5932
|
return "Error: topic and content are required for save action";
|
|
5591
5933
|
}
|
|
5592
5934
|
await store.addMemory(input.topic, input.content, input.tags ?? [], "agent");
|
|
5935
|
+
invalidateGlobalMemoryCache();
|
|
5593
5936
|
return `\u2705 Saved to "${input.topic}": ${input.content.slice(0, 100)}${input.content.length > 100 ? "..." : ""}`;
|
|
5594
5937
|
}
|
|
5595
5938
|
case "recall": {
|
|
@@ -5637,6 +5980,7 @@ var MemoryTool = class extends BaseTool {
|
|
|
5637
5980
|
entry.entry.lastAccessed = 0;
|
|
5638
5981
|
}
|
|
5639
5982
|
await store.prune();
|
|
5983
|
+
invalidateGlobalMemoryCache();
|
|
5640
5984
|
return `\u{1F5D1}\uFE0F Deleted ${topicEntries.length} entries from "${input.topic}"`;
|
|
5641
5985
|
}
|
|
5642
5986
|
default:
|
|
@@ -6401,25 +6745,166 @@ ${m.content}`;
|
|
|
6401
6745
|
].join("\n");
|
|
6402
6746
|
}
|
|
6403
6747
|
|
|
6404
|
-
// src/
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
const
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6748
|
+
// src/services/session-recovery.ts
|
|
6749
|
+
import { readFile as readFile9, readdir as readdir5 } from "fs/promises";
|
|
6750
|
+
import { join as join10 } from "path";
|
|
6751
|
+
import { existsSync as existsSync5 } from "fs";
|
|
6752
|
+
async function recoverSession(filePath) {
|
|
6753
|
+
const raw = await readFile9(filePath, "utf-8");
|
|
6754
|
+
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
6755
|
+
const entries = [];
|
|
6756
|
+
for (const line of lines) {
|
|
6757
|
+
try {
|
|
6758
|
+
const entry = JSON.parse(line);
|
|
6759
|
+
if (entry.type && entry.content !== void 0) {
|
|
6760
|
+
entries.push(entry);
|
|
6761
|
+
}
|
|
6762
|
+
} catch {
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
const messages = reconstructMessages(entries);
|
|
6766
|
+
const firstEntry = entries[0];
|
|
6767
|
+
const timestamp = firstEntry?.timestamp ?? 0;
|
|
6768
|
+
return {
|
|
6769
|
+
sessionId: filePath,
|
|
6770
|
+
filePath,
|
|
6771
|
+
timestamp,
|
|
6772
|
+
messages,
|
|
6773
|
+
entryCount: entries.length
|
|
6774
|
+
};
|
|
6419
6775
|
}
|
|
6420
|
-
async function
|
|
6421
|
-
const
|
|
6422
|
-
|
|
6776
|
+
async function listSessions(limit = 20) {
|
|
6777
|
+
const dir = getSessionsDir();
|
|
6778
|
+
if (!existsSync5(dir)) return [];
|
|
6779
|
+
const files = await readdir5(dir);
|
|
6780
|
+
const sessionFiles = files.filter((f) => f.startsWith("session-") && f.endsWith(".jsonl"));
|
|
6781
|
+
const sessions = [];
|
|
6782
|
+
for (const fileName of sessionFiles) {
|
|
6783
|
+
const filePath = join10(dir, fileName);
|
|
6784
|
+
try {
|
|
6785
|
+
const raw = await readFile9(filePath, "utf-8");
|
|
6786
|
+
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
6787
|
+
let lastActivity = 0;
|
|
6788
|
+
let entryCount = 0;
|
|
6789
|
+
for (const line of lines) {
|
|
6790
|
+
try {
|
|
6791
|
+
const entry = JSON.parse(line);
|
|
6792
|
+
if (entry.timestamp && entry.timestamp > lastActivity) {
|
|
6793
|
+
lastActivity = entry.timestamp;
|
|
6794
|
+
}
|
|
6795
|
+
entryCount++;
|
|
6796
|
+
} catch {
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
const dateStr = fileName.replace("session-", "").replace(".jsonl", "");
|
|
6800
|
+
const fileTimestamp = Date.parse(dateStr) || lastActivity;
|
|
6801
|
+
sessions.push({
|
|
6802
|
+
fileName,
|
|
6803
|
+
filePath,
|
|
6804
|
+
timestamp: fileTimestamp,
|
|
6805
|
+
entryCount,
|
|
6806
|
+
lastActivity
|
|
6807
|
+
});
|
|
6808
|
+
} catch {
|
|
6809
|
+
}
|
|
6810
|
+
}
|
|
6811
|
+
sessions.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
6812
|
+
return sessions.slice(0, limit);
|
|
6813
|
+
}
|
|
6814
|
+
async function findLatestSession() {
|
|
6815
|
+
const sessions = await listSessions(1);
|
|
6816
|
+
return sessions[0] ?? null;
|
|
6817
|
+
}
|
|
6818
|
+
async function getLastSessionSummary() {
|
|
6819
|
+
try {
|
|
6820
|
+
const latest = await findLatestSession();
|
|
6821
|
+
if (!latest) return "";
|
|
6822
|
+
const raw = await readFile9(latest.filePath, "utf-8");
|
|
6823
|
+
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
6824
|
+
const userMessages = [];
|
|
6825
|
+
const assistantTopics = [];
|
|
6826
|
+
for (const line of lines) {
|
|
6827
|
+
try {
|
|
6828
|
+
const entry = JSON.parse(line);
|
|
6829
|
+
if (entry.type === "user" && entry.content.trim()) {
|
|
6830
|
+
userMessages.push(entry.content.trim());
|
|
6831
|
+
} else if (entry.type === "assistant" && entry.content.trim()) {
|
|
6832
|
+
const preview = entry.content.trim().slice(0, 120);
|
|
6833
|
+
assistantTopics.push(preview);
|
|
6834
|
+
}
|
|
6835
|
+
} catch {
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
if (userMessages.length === 0) return "";
|
|
6839
|
+
const date = new Date(latest.lastActivity).toLocaleString();
|
|
6840
|
+
const lastUserMsg = userMessages[userMessages.length - 1];
|
|
6841
|
+
const lastAssistant = assistantTopics.length > 0 ? assistantTopics[assistantTopics.length - 1] : "N/A";
|
|
6842
|
+
return [
|
|
6843
|
+
"<last-session-context>",
|
|
6844
|
+
`Previous session (${date}, ${lines.length} entries):`,
|
|
6845
|
+
` Last user message: ${lastUserMsg.slice(0, 200)}`,
|
|
6846
|
+
` Last assistant response: ${lastAssistant.slice(0, 200)}`,
|
|
6847
|
+
` Total user messages in session: ${userMessages.length}`,
|
|
6848
|
+
"",
|
|
6849
|
+
"Use the memory tool to recall specific details from past interactions.",
|
|
6850
|
+
"</last-session-context>"
|
|
6851
|
+
].join("\n");
|
|
6852
|
+
} catch {
|
|
6853
|
+
return "";
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
function reconstructMessages(entries) {
|
|
6857
|
+
const messages = [];
|
|
6858
|
+
let i = 0;
|
|
6859
|
+
while (i < entries.length) {
|
|
6860
|
+
const entry = entries[i];
|
|
6861
|
+
if (entry.type === "user") {
|
|
6862
|
+
const content = [{ type: "text", text: entry.content }];
|
|
6863
|
+
messages.push({ role: "user", content });
|
|
6864
|
+
i++;
|
|
6865
|
+
} else if (entry.type === "assistant") {
|
|
6866
|
+
const content = [{ type: "text", text: entry.content }];
|
|
6867
|
+
messages.push({ role: "assistant", content });
|
|
6868
|
+
i++;
|
|
6869
|
+
} else if (entry.type === "tool_use") {
|
|
6870
|
+
const lastMsg = messages[messages.length - 1];
|
|
6871
|
+
if (lastMsg && lastMsg.role === "assistant") {
|
|
6872
|
+
}
|
|
6873
|
+
i++;
|
|
6874
|
+
} else if (entry.type === "tool_result") {
|
|
6875
|
+
const content = [{
|
|
6876
|
+
type: "tool_result",
|
|
6877
|
+
toolUseId: `recovered-${i}`,
|
|
6878
|
+
content: entry.content
|
|
6879
|
+
}];
|
|
6880
|
+
messages.push({ role: "user", content });
|
|
6881
|
+
i++;
|
|
6882
|
+
} else {
|
|
6883
|
+
i++;
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
return messages;
|
|
6887
|
+
}
|
|
6888
|
+
|
|
6889
|
+
// src/core/loop.ts
|
|
6890
|
+
var MAX_TURNS = 50;
|
|
6891
|
+
var MAX_CONSECUTIVE_TOOL_ERRORS = 3;
|
|
6892
|
+
var MAX_TOOL_OUTPUT_CHARS = 5e4;
|
|
6893
|
+
var MIN_OUTPUT_TOKENS = 16384;
|
|
6894
|
+
var MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3;
|
|
6895
|
+
var ESCALATED_MAX_TOKENS = 65536;
|
|
6896
|
+
function sanitizeToolOutput(output, maxChars = MAX_TOOL_OUTPUT_CHARS) {
|
|
6897
|
+
const sanitized = output.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
6898
|
+
if (sanitized.length <= maxChars) return sanitized;
|
|
6899
|
+
const truncated = sanitized.slice(0, maxChars);
|
|
6900
|
+
const omitted = sanitized.length - maxChars;
|
|
6901
|
+
return `${truncated}
|
|
6902
|
+
|
|
6903
|
+
... [truncated ${omitted} characters]`;
|
|
6904
|
+
}
|
|
6905
|
+
async function* runAgentLoop(userMessage, deps) {
|
|
6906
|
+
const { adapter, toolRegistry, config, systemPrompt, cwd: cwd2, abortSignal, onPermissionRequest } = deps;
|
|
6907
|
+
const autoMode = deps.autoModeManager ?? new AutoModeManager(config.autoMode);
|
|
6423
6908
|
const fastMode = deps.fastModeManager ?? new FastModeManager(config.fastMode);
|
|
6424
6909
|
const contextWindow = deps.contextWindowManager ?? new ContextWindowManager(config.contextWindow);
|
|
6425
6910
|
const compactor = new ContextCompactor({
|
|
@@ -6463,7 +6948,9 @@ ${result.summary}` }]
|
|
|
6463
6948
|
});
|
|
6464
6949
|
const toolDefs = toolRegistry.getToolDefinitions();
|
|
6465
6950
|
const projectMemory = await loadProjectMemoryPrompt(cwd2);
|
|
6466
|
-
const
|
|
6951
|
+
const globalMemory = await loadGlobalMemorySummary();
|
|
6952
|
+
const lastSessionSummary = await getLastSessionSummary();
|
|
6953
|
+
const effectiveSystemPrompt = systemPrompt + fastMode.getSystemPromptModifier() + (projectMemory ? "\n\n" + projectMemory : "") + (globalMemory ? "\n\n" + globalMemory : "") + (lastSessionSummary ? "\n\n" + lastSessionSummary : "");
|
|
6467
6954
|
const toolContext = {
|
|
6468
6955
|
cwd: cwd2,
|
|
6469
6956
|
abortSignal,
|
|
@@ -6949,7 +7436,7 @@ function createMissingToolResults(toolCalls, errorMessage) {
|
|
|
6949
7436
|
import { useState, useEffect, useCallback, useRef, useMemo, memo } from "react";
|
|
6950
7437
|
import { Box, Text, render, useInput, useApp, useStdout } from "ink";
|
|
6951
7438
|
import { readdirSync } from "fs";
|
|
6952
|
-
import { join as
|
|
7439
|
+
import { join as join11, basename as basename2, dirname as dirname3 } from "path";
|
|
6953
7440
|
import { exec as exec2, execSync as execSync2 } from "child_process";
|
|
6954
7441
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6955
7442
|
var C = {
|
|
@@ -7543,10 +8030,10 @@ function InkApp({ model, toolCount, onSubmit, onCancel }) {
|
|
|
7543
8030
|
const selected = currentFiles[selectedIdx];
|
|
7544
8031
|
if (selected) {
|
|
7545
8032
|
if (selected.isDir) {
|
|
7546
|
-
setFileCwd(
|
|
8033
|
+
setFileCwd(join11(fileCwd, selected.name));
|
|
7547
8034
|
setSelectedIdx(0);
|
|
7548
8035
|
} else {
|
|
7549
|
-
openInEditor(
|
|
8036
|
+
openInEditor(join11(fileCwd, selected.name));
|
|
7550
8037
|
}
|
|
7551
8038
|
}
|
|
7552
8039
|
return;
|
|
@@ -7841,109 +8328,208 @@ function buildSystemPrompt() {
|
|
|
7841
8328
|
// src/mcp/client.ts
|
|
7842
8329
|
import { spawn as spawn2 } from "child_process";
|
|
7843
8330
|
import { createInterface } from "readline";
|
|
7844
|
-
var
|
|
8331
|
+
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
8332
|
+
var CONNECT_TIMEOUT = 3e4;
|
|
8333
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
7845
8334
|
var MCPClient = class {
|
|
7846
8335
|
config;
|
|
7847
8336
|
process = null;
|
|
7848
8337
|
rl = null;
|
|
7849
|
-
|
|
8338
|
+
connectionState = "disconnected";
|
|
7850
8339
|
nextId = 1;
|
|
7851
8340
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
7852
|
-
|
|
8341
|
+
serverCapabilities = {};
|
|
8342
|
+
serverInfo;
|
|
8343
|
+
serverInstructions;
|
|
8344
|
+
connectReject = null;
|
|
7853
8345
|
constructor(config) {
|
|
7854
8346
|
this.config = config;
|
|
7855
8347
|
}
|
|
7856
8348
|
async connect() {
|
|
7857
|
-
if (this.connected) return;
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
...this.config.env
|
|
7861
|
-
};
|
|
7862
|
-
this.process = spawn2(this.config.command, this.config.args, {
|
|
7863
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
7864
|
-
env
|
|
7865
|
-
});
|
|
7866
|
-
this.process.on("error", (err) => {
|
|
7867
|
-
this.cleanup();
|
|
7868
|
-
throw err;
|
|
7869
|
-
});
|
|
7870
|
-
this.process.on("exit", () => {
|
|
7871
|
-
this.cleanup();
|
|
7872
|
-
});
|
|
7873
|
-
if (!this.process.stdout) {
|
|
7874
|
-
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`);
|
|
7875
8352
|
}
|
|
7876
|
-
this.
|
|
7877
|
-
|
|
7878
|
-
this.
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
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;
|
|
7883
8362
|
}
|
|
7884
|
-
await this.sendRequest("initialize", {
|
|
7885
|
-
protocolVersion: "2024-11-05",
|
|
7886
|
-
capabilities: {},
|
|
7887
|
-
clientInfo: { name: "cliskill", version: "1.0.0" }
|
|
7888
|
-
});
|
|
7889
|
-
this.sendNotification("notifications/initialized", {});
|
|
7890
|
-
this.connected = true;
|
|
7891
8363
|
}
|
|
7892
8364
|
async disconnect() {
|
|
7893
|
-
if (
|
|
8365
|
+
if (this.connectionState === "disconnected") return;
|
|
7894
8366
|
for (const [, pending] of this.pendingRequests) {
|
|
7895
8367
|
clearTimeout(pending.timer);
|
|
7896
8368
|
pending.reject(new Error("Connection closed"));
|
|
7897
8369
|
}
|
|
7898
8370
|
this.pendingRequests.clear();
|
|
7899
|
-
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
|
+
}
|
|
7900
8394
|
this.cleanup();
|
|
7901
8395
|
}
|
|
7902
8396
|
async listTools() {
|
|
7903
8397
|
const response = await this.sendRequest("tools/list", {});
|
|
8398
|
+
this.assertNoError(response);
|
|
7904
8399
|
const result = response.result;
|
|
7905
8400
|
return result?.tools ?? [];
|
|
7906
8401
|
}
|
|
7907
8402
|
async callTool(name, args) {
|
|
7908
8403
|
const response = await this.sendRequest("tools/call", { name, arguments: args });
|
|
7909
|
-
|
|
8404
|
+
this.assertNoError(response);
|
|
8405
|
+
const result = response.result;
|
|
8406
|
+
return result ?? { content: [{ type: "text", text: "No result from MCP server" }] };
|
|
7910
8407
|
}
|
|
7911
8408
|
async listResources() {
|
|
7912
8409
|
const response = await this.sendRequest("resources/list", {});
|
|
8410
|
+
this.assertNoError(response);
|
|
7913
8411
|
const result = response.result;
|
|
7914
8412
|
return result?.resources ?? [];
|
|
7915
8413
|
}
|
|
7916
8414
|
async readResource(uri) {
|
|
7917
8415
|
const response = await this.sendRequest("resources/read", { uri });
|
|
8416
|
+
this.assertNoError(response);
|
|
7918
8417
|
return response.result;
|
|
7919
8418
|
}
|
|
7920
8419
|
async listPrompts() {
|
|
7921
8420
|
const response = await this.sendRequest("prompts/list", {});
|
|
8421
|
+
this.assertNoError(response);
|
|
7922
8422
|
const result = response.result;
|
|
7923
8423
|
return result?.prompts ?? [];
|
|
7924
8424
|
}
|
|
7925
8425
|
isConnected() {
|
|
7926
|
-
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", {});
|
|
7927
8508
|
}
|
|
7928
8509
|
sendRequest(method, params) {
|
|
7929
8510
|
return new Promise((resolve10, reject) => {
|
|
7930
8511
|
if (!this.process?.stdin) {
|
|
7931
|
-
reject(new Error(
|
|
8512
|
+
reject(new Error(`MCP server "${this.config.name}" is not connected`));
|
|
7932
8513
|
return;
|
|
7933
8514
|
}
|
|
7934
8515
|
const id = this.nextId++;
|
|
7935
8516
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
7936
8517
|
const timer = setTimeout(() => {
|
|
7937
8518
|
this.pendingRequests.delete(id);
|
|
7938
|
-
reject(new Error(
|
|
7939
|
-
|
|
8519
|
+
reject(new Error(
|
|
8520
|
+
`Request timeout: ${method} (id=${id}) on server "${this.config.name}"`
|
|
8521
|
+
));
|
|
8522
|
+
}, DEFAULT_REQUEST_TIMEOUT);
|
|
8523
|
+
timer.unref();
|
|
7940
8524
|
this.pendingRequests.set(id, { resolve: resolve10, reject, timer });
|
|
7941
8525
|
const data = JSON.stringify(request) + "\n";
|
|
7942
8526
|
this.process.stdin.write(data, (err) => {
|
|
7943
8527
|
if (err) {
|
|
7944
8528
|
clearTimeout(timer);
|
|
7945
8529
|
this.pendingRequests.delete(id);
|
|
7946
|
-
reject(
|
|
8530
|
+
reject(new Error(
|
|
8531
|
+
`Failed to send request to MCP server "${this.config.name}": ${err.message}`
|
|
8532
|
+
));
|
|
7947
8533
|
}
|
|
7948
8534
|
});
|
|
7949
8535
|
});
|
|
@@ -7952,19 +8538,23 @@ var MCPClient = class {
|
|
|
7952
8538
|
if (!this.process?.stdin) return;
|
|
7953
8539
|
const notification = { jsonrpc: "2.0", method, params };
|
|
7954
8540
|
const data = JSON.stringify(notification) + "\n";
|
|
7955
|
-
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
|
+
});
|
|
7956
8548
|
}
|
|
7957
8549
|
handleLine(line) {
|
|
7958
|
-
|
|
8550
|
+
if (!line.trim()) return;
|
|
7959
8551
|
let response;
|
|
7960
8552
|
try {
|
|
7961
|
-
response = JSON.parse(
|
|
8553
|
+
response = JSON.parse(line);
|
|
7962
8554
|
} catch {
|
|
7963
8555
|
return;
|
|
7964
|
-
} finally {
|
|
7965
|
-
this.buffer = "";
|
|
7966
8556
|
}
|
|
7967
|
-
if (response.id
|
|
8557
|
+
if (response.id != null) {
|
|
7968
8558
|
const pending = this.pendingRequests.get(response.id);
|
|
7969
8559
|
if (pending) {
|
|
7970
8560
|
clearTimeout(pending.timer);
|
|
@@ -7973,12 +8563,47 @@ var MCPClient = class {
|
|
|
7973
8563
|
}
|
|
7974
8564
|
}
|
|
7975
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
|
+
}
|
|
7976
8599
|
cleanup() {
|
|
7977
|
-
this.
|
|
8600
|
+
this.connectionState = "disconnected";
|
|
7978
8601
|
this.rl?.close();
|
|
7979
8602
|
this.rl = null;
|
|
7980
8603
|
this.process = null;
|
|
7981
|
-
this.
|
|
8604
|
+
this.serverCapabilities = {};
|
|
8605
|
+
this.serverInfo = void 0;
|
|
8606
|
+
this.serverInstructions = void 0;
|
|
7982
8607
|
}
|
|
7983
8608
|
};
|
|
7984
8609
|
|
|
@@ -7986,28 +8611,51 @@ var MCPClient = class {
|
|
|
7986
8611
|
var MCPConnectionManager = class {
|
|
7987
8612
|
clients = /* @__PURE__ */ new Map();
|
|
7988
8613
|
async addServer(config) {
|
|
8614
|
+
if (this.clients.has(config.name)) {
|
|
8615
|
+
await this.removeServer(config.name);
|
|
8616
|
+
}
|
|
7989
8617
|
const client = new MCPClient(config);
|
|
7990
|
-
|
|
7991
|
-
|
|
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
|
+
}
|
|
7992
8630
|
}
|
|
7993
8631
|
async removeServer(name) {
|
|
7994
8632
|
const client = this.clients.get(name);
|
|
7995
|
-
if (client)
|
|
8633
|
+
if (!client) return;
|
|
8634
|
+
try {
|
|
7996
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 {
|
|
7997
8640
|
this.clients.delete(name);
|
|
7998
8641
|
}
|
|
7999
8642
|
}
|
|
8000
8643
|
getConnectedServers() {
|
|
8001
8644
|
return Array.from(this.clients.entries()).filter(([, client]) => client.isConnected()).map(([name]) => name);
|
|
8002
8645
|
}
|
|
8646
|
+
getClient(name) {
|
|
8647
|
+
return this.clients.get(name);
|
|
8648
|
+
}
|
|
8003
8649
|
async getAllTools() {
|
|
8004
8650
|
const allTools = [];
|
|
8005
|
-
for (const [, client] of this.clients) {
|
|
8651
|
+
for (const [name, client] of this.clients) {
|
|
8006
8652
|
if (!client.isConnected()) continue;
|
|
8007
8653
|
try {
|
|
8008
8654
|
const tools = await client.listTools();
|
|
8009
8655
|
allTools.push(...tools);
|
|
8010
|
-
} 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}`);
|
|
8011
8659
|
}
|
|
8012
8660
|
}
|
|
8013
8661
|
return allTools;
|
|
@@ -8027,17 +8675,40 @@ var MCPConnectionManager = class {
|
|
|
8027
8675
|
if (!client || !client.isConnected()) {
|
|
8028
8676
|
return [];
|
|
8029
8677
|
}
|
|
8030
|
-
|
|
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
|
+
}
|
|
8031
8685
|
}
|
|
8032
8686
|
async disconnectAll() {
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
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
|
+
}
|
|
8037
8695
|
}
|
|
8038
|
-
|
|
8696
|
+
);
|
|
8697
|
+
await Promise.allSettled(disconnectPromises);
|
|
8039
8698
|
this.clients.clear();
|
|
8040
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
|
+
}
|
|
8041
8712
|
};
|
|
8042
8713
|
|
|
8043
8714
|
// src/mcp/mcp-tool-adapter.ts
|
|
@@ -8046,19 +8717,23 @@ var MCPToolAdapter = class {
|
|
|
8046
8717
|
name;
|
|
8047
8718
|
description;
|
|
8048
8719
|
inputSchema;
|
|
8049
|
-
riskLevel
|
|
8720
|
+
riskLevel;
|
|
8050
8721
|
concurrencySafe = true;
|
|
8051
|
-
readOnly
|
|
8722
|
+
readOnly;
|
|
8052
8723
|
serverName;
|
|
8724
|
+
originalToolName;
|
|
8053
8725
|
mcpManager;
|
|
8054
8726
|
rawInputSchema;
|
|
8055
|
-
constructor(serverName, toolName, description, inputSchema2, mcpManager) {
|
|
8727
|
+
constructor(serverName, toolName, description, inputSchema2, mcpManager, annotations) {
|
|
8728
|
+
this.serverName = serverName;
|
|
8729
|
+
this.originalToolName = toolName;
|
|
8056
8730
|
this.name = `mcp_${serverName}_${toolName}`;
|
|
8057
8731
|
this.description = description;
|
|
8058
|
-
this.serverName = serverName;
|
|
8059
8732
|
this.mcpManager = mcpManager;
|
|
8060
8733
|
this.rawInputSchema = inputSchema2;
|
|
8061
8734
|
this.inputSchema = this.buildZodSchema(inputSchema2);
|
|
8735
|
+
this.readOnly = annotations?.readOnlyHint === true;
|
|
8736
|
+
this.riskLevel = this.deriveRiskLevel(annotations);
|
|
8062
8737
|
}
|
|
8063
8738
|
async execute(input, context) {
|
|
8064
8739
|
const allowed = await context.checkPermission(this.name, JSON.stringify(input));
|
|
@@ -8066,8 +8741,16 @@ var MCPToolAdapter = class {
|
|
|
8066
8741
|
return `Error: Permission denied for MCP tool: ${this.name}`;
|
|
8067
8742
|
}
|
|
8068
8743
|
try {
|
|
8069
|
-
const result = await this.mcpManager.callTool(
|
|
8070
|
-
|
|
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);
|
|
8071
8754
|
} catch (err) {
|
|
8072
8755
|
return `Error calling MCP tool ${this.name}: ${err.message}`;
|
|
8073
8756
|
}
|
|
@@ -8080,12 +8763,29 @@ var MCPToolAdapter = class {
|
|
|
8080
8763
|
};
|
|
8081
8764
|
}
|
|
8082
8765
|
getOriginalName() {
|
|
8083
|
-
|
|
8084
|
-
return parts.slice(2).join("_");
|
|
8766
|
+
return this.originalToolName;
|
|
8085
8767
|
}
|
|
8086
8768
|
getServerName() {
|
|
8087
8769
|
return this.serverName;
|
|
8088
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
|
+
}
|
|
8089
8789
|
buildZodSchema(jsonSchema) {
|
|
8090
8790
|
const properties = jsonSchema.properties ?? {};
|
|
8091
8791
|
const required = new Set(jsonSchema.required ?? []);
|
|
@@ -8101,10 +8801,21 @@ var MCPToolAdapter = class {
|
|
|
8101
8801
|
switch (type) {
|
|
8102
8802
|
case "string":
|
|
8103
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
|
+
}
|
|
8104
8811
|
return z25.string();
|
|
8105
8812
|
case "number":
|
|
8106
|
-
case "integer":
|
|
8107
|
-
|
|
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
|
+
}
|
|
8108
8819
|
case "boolean":
|
|
8109
8820
|
return z25.boolean();
|
|
8110
8821
|
case "array":
|
|
@@ -8133,120 +8844,20 @@ async function registerMCPTools(mcpManager, registerFn) {
|
|
|
8133
8844
|
tool.name,
|
|
8134
8845
|
tool.description ?? `MCP tool: ${tool.name}`,
|
|
8135
8846
|
tool.inputSchema,
|
|
8136
|
-
mcpManager
|
|
8847
|
+
mcpManager,
|
|
8848
|
+
tool.annotations
|
|
8137
8849
|
);
|
|
8138
8850
|
registerFn(adapter);
|
|
8139
8851
|
registered.push(adapter.name);
|
|
8140
8852
|
}
|
|
8141
|
-
} 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}`);
|
|
8142
8856
|
}
|
|
8143
8857
|
}
|
|
8144
8858
|
return registered;
|
|
8145
8859
|
}
|
|
8146
8860
|
|
|
8147
|
-
// src/services/session-recovery.ts
|
|
8148
|
-
import { readFile as readFile9, readdir as readdir5 } from "fs/promises";
|
|
8149
|
-
import { join as join11 } from "path";
|
|
8150
|
-
import { existsSync as existsSync5 } from "fs";
|
|
8151
|
-
async function recoverSession(filePath) {
|
|
8152
|
-
const raw = await readFile9(filePath, "utf-8");
|
|
8153
|
-
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
8154
|
-
const entries = [];
|
|
8155
|
-
for (const line of lines) {
|
|
8156
|
-
try {
|
|
8157
|
-
const entry = JSON.parse(line);
|
|
8158
|
-
if (entry.type && entry.content !== void 0) {
|
|
8159
|
-
entries.push(entry);
|
|
8160
|
-
}
|
|
8161
|
-
} catch {
|
|
8162
|
-
}
|
|
8163
|
-
}
|
|
8164
|
-
const messages = reconstructMessages(entries);
|
|
8165
|
-
const firstEntry = entries[0];
|
|
8166
|
-
const timestamp = firstEntry?.timestamp ?? 0;
|
|
8167
|
-
return {
|
|
8168
|
-
sessionId: filePath,
|
|
8169
|
-
filePath,
|
|
8170
|
-
timestamp,
|
|
8171
|
-
messages,
|
|
8172
|
-
entryCount: entries.length
|
|
8173
|
-
};
|
|
8174
|
-
}
|
|
8175
|
-
async function listSessions(limit = 20) {
|
|
8176
|
-
const dir = getSessionsDir();
|
|
8177
|
-
if (!existsSync5(dir)) return [];
|
|
8178
|
-
const files = await readdir5(dir);
|
|
8179
|
-
const sessionFiles = files.filter((f) => f.startsWith("session-") && f.endsWith(".jsonl"));
|
|
8180
|
-
const sessions = [];
|
|
8181
|
-
for (const fileName of sessionFiles) {
|
|
8182
|
-
const filePath = join11(dir, fileName);
|
|
8183
|
-
try {
|
|
8184
|
-
const raw = await readFile9(filePath, "utf-8");
|
|
8185
|
-
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
8186
|
-
let lastActivity = 0;
|
|
8187
|
-
let entryCount = 0;
|
|
8188
|
-
for (const line of lines) {
|
|
8189
|
-
try {
|
|
8190
|
-
const entry = JSON.parse(line);
|
|
8191
|
-
if (entry.timestamp && entry.timestamp > lastActivity) {
|
|
8192
|
-
lastActivity = entry.timestamp;
|
|
8193
|
-
}
|
|
8194
|
-
entryCount++;
|
|
8195
|
-
} catch {
|
|
8196
|
-
}
|
|
8197
|
-
}
|
|
8198
|
-
const dateStr = fileName.replace("session-", "").replace(".jsonl", "");
|
|
8199
|
-
const fileTimestamp = Date.parse(dateStr) || lastActivity;
|
|
8200
|
-
sessions.push({
|
|
8201
|
-
fileName,
|
|
8202
|
-
filePath,
|
|
8203
|
-
timestamp: fileTimestamp,
|
|
8204
|
-
entryCount,
|
|
8205
|
-
lastActivity
|
|
8206
|
-
});
|
|
8207
|
-
} catch {
|
|
8208
|
-
}
|
|
8209
|
-
}
|
|
8210
|
-
sessions.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
8211
|
-
return sessions.slice(0, limit);
|
|
8212
|
-
}
|
|
8213
|
-
async function findLatestSession() {
|
|
8214
|
-
const sessions = await listSessions(1);
|
|
8215
|
-
return sessions[0] ?? null;
|
|
8216
|
-
}
|
|
8217
|
-
function reconstructMessages(entries) {
|
|
8218
|
-
const messages = [];
|
|
8219
|
-
let i = 0;
|
|
8220
|
-
while (i < entries.length) {
|
|
8221
|
-
const entry = entries[i];
|
|
8222
|
-
if (entry.type === "user") {
|
|
8223
|
-
const content = [{ type: "text", text: entry.content }];
|
|
8224
|
-
messages.push({ role: "user", content });
|
|
8225
|
-
i++;
|
|
8226
|
-
} else if (entry.type === "assistant") {
|
|
8227
|
-
const content = [{ type: "text", text: entry.content }];
|
|
8228
|
-
messages.push({ role: "assistant", content });
|
|
8229
|
-
i++;
|
|
8230
|
-
} else if (entry.type === "tool_use") {
|
|
8231
|
-
const lastMsg = messages[messages.length - 1];
|
|
8232
|
-
if (lastMsg && lastMsg.role === "assistant") {
|
|
8233
|
-
}
|
|
8234
|
-
i++;
|
|
8235
|
-
} else if (entry.type === "tool_result") {
|
|
8236
|
-
const content = [{
|
|
8237
|
-
type: "tool_result",
|
|
8238
|
-
toolUseId: `recovered-${i}`,
|
|
8239
|
-
content: entry.content
|
|
8240
|
-
}];
|
|
8241
|
-
messages.push({ role: "user", content });
|
|
8242
|
-
i++;
|
|
8243
|
-
} else {
|
|
8244
|
-
i++;
|
|
8245
|
-
}
|
|
8246
|
-
}
|
|
8247
|
-
return messages;
|
|
8248
|
-
}
|
|
8249
|
-
|
|
8250
8861
|
// src/ui/repl.ts
|
|
8251
8862
|
var SessionSaver = class {
|
|
8252
8863
|
filePath;
|
|
@@ -8311,7 +8922,7 @@ async function connectMcpServers(config, toolRegistry) {
|
|
|
8311
8922
|
console.error(`MCP server "${serverConfig.name}" connection failed: ${err.message}`);
|
|
8312
8923
|
}
|
|
8313
8924
|
}
|
|
8314
|
-
if (connectedCount === 0) return
|
|
8925
|
+
if (connectedCount === 0) return null;
|
|
8315
8926
|
const registeredTools = await registerMCPTools(mcpManager, (tool) => {
|
|
8316
8927
|
try {
|
|
8317
8928
|
toolRegistry.register(tool);
|
|
@@ -8321,6 +8932,7 @@ async function connectMcpServers(config, toolRegistry) {
|
|
|
8321
8932
|
if (registeredTools.length > 0) {
|
|
8322
8933
|
console.log(` MCP: ${connectedCount} server(s) connected, ${registeredTools.length} tool(s) loaded`);
|
|
8323
8934
|
}
|
|
8935
|
+
mcpManager.registerShutdownHandlers();
|
|
8324
8936
|
return mcpManager;
|
|
8325
8937
|
}
|
|
8326
8938
|
async function runTuiRepl(config) {
|
|
@@ -10564,6 +11176,13 @@ var PROVIDER_OPTIONS = [
|
|
|
10564
11176
|
requiresApiKey: true,
|
|
10565
11177
|
format: "openai-compatible"
|
|
10566
11178
|
},
|
|
11179
|
+
{
|
|
11180
|
+
id: "anthropic",
|
|
11181
|
+
label: "Anthropic Claude",
|
|
11182
|
+
defaultBaseUrl: "https://api.anthropic.com",
|
|
11183
|
+
requiresApiKey: true,
|
|
11184
|
+
format: "anthropic-compatible"
|
|
11185
|
+
},
|
|
10567
11186
|
{
|
|
10568
11187
|
id: "ollama",
|
|
10569
11188
|
label: "Ollama (local)",
|
|
@@ -10763,13 +11382,18 @@ import { useState as useState7, useEffect as useEffect2 } from "react";
|
|
|
10763
11382
|
import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
|
|
10764
11383
|
|
|
10765
11384
|
// src/connect/fetch-models.ts
|
|
10766
|
-
async function fetchModelsFromApi(baseUrl, apiKey) {
|
|
11385
|
+
async function fetchModelsFromApi(baseUrl, apiKey, format) {
|
|
10767
11386
|
const url = baseUrl.replace(/\/$/, "") + "/models";
|
|
10768
11387
|
const headers = {
|
|
10769
11388
|
"Content-Type": "application/json"
|
|
10770
11389
|
};
|
|
10771
11390
|
if (apiKey) {
|
|
10772
|
-
|
|
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
|
+
}
|
|
10773
11397
|
}
|
|
10774
11398
|
const response = await fetch(url, {
|
|
10775
11399
|
method: "GET",
|
|
@@ -10787,9 +11411,12 @@ async function fetchModelsFromApi(baseUrl, apiKey) {
|
|
|
10787
11411
|
}
|
|
10788
11412
|
var CATEGORY_PATTERNS = [
|
|
10789
11413
|
{ pattern: /o[1-4](?:-|$|-mini)/i, category: "reasoning" },
|
|
11414
|
+
{ pattern: /claude-opus|claude-[34]\.(\d)+/i, category: "reasoning" },
|
|
10790
11415
|
{ pattern: /reason|think|deep/i, category: "reasoning" },
|
|
11416
|
+
{ pattern: /claude-sonnet/i, category: "code" },
|
|
10791
11417
|
{ pattern: /code|codestral|deepseek-coder|qwen.*coder|starcoder/i, category: "code" },
|
|
10792
11418
|
{ pattern: /mini|flash|fast|turbo|haiku|lite/i, category: "fast" },
|
|
11419
|
+
{ pattern: /claude-haiku/i, category: "fast" },
|
|
10793
11420
|
{ pattern: /creative|dall/i, category: "creative" }
|
|
10794
11421
|
];
|
|
10795
11422
|
function classifyModelName(modelName) {
|
|
@@ -10865,7 +11492,7 @@ function ModelStep() {
|
|
|
10865
11492
|
setPhase("fallback");
|
|
10866
11493
|
return;
|
|
10867
11494
|
}
|
|
10868
|
-
fetchModelsFromApi(state.baseUrl, state.apiKey || void 0).then((fetched) => {
|
|
11495
|
+
fetchModelsFromApi(state.baseUrl, state.apiKey || void 0, state.provider?.format).then((fetched) => {
|
|
10869
11496
|
if (fetched.length === 0) {
|
|
10870
11497
|
setError("No models found at this endpoint");
|
|
10871
11498
|
setPhase("fallback");
|
|
@@ -11553,4 +12180,4 @@ export {
|
|
|
11553
12180
|
MCPConnectionManager,
|
|
11554
12181
|
runCli
|
|
11555
12182
|
};
|
|
11556
|
-
//# sourceMappingURL=chunk-
|
|
12183
|
+
//# sourceMappingURL=chunk-SDHXIBKZ.js.map
|