-
Notifications
You must be signed in to change notification settings - Fork 151
Expand file tree
/
Copy pathutils.ts
More file actions
129 lines (109 loc) · 4.1 KB
/
utils.ts
File metadata and controls
129 lines (109 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { homedir, platform } from "node:os";
import { join } from "node:path";
import type { McpConfig, ServerEntry } from "./types.ts";
async function execOpen(pi: ExtensionAPI, target: string, browser?: string) {
const os = platform();
if (os === "darwin") {
return browser ? pi.exec("open", ["-a", browser, target]) : pi.exec("open", [target]);
}
if (os === "win32") {
return browser
? pi.exec("cmd", ["/c", "start", "", browser, target])
: pi.exec("cmd", ["/c", "start", "", target]);
}
return browser ? pi.exec(browser, [target]) : pi.exec("xdg-open", [target]);
}
export async function openUrl(pi: ExtensionAPI, url: string, browser?: string): Promise<void> {
const result = await execOpen(pi, url, browser);
if (result.code !== 0) {
throw new Error(result.stderr || `Failed to open browser (exit code ${result.code})`);
}
}
export async function openPath(pi: ExtensionAPI, targetPath: string): Promise<void> {
const result = await execOpen(pi, targetPath);
if (result.code !== 0) {
throw new Error(result.stderr || `Failed to open path (exit code ${result.code})`);
}
}
export async function parallelLimit<T, R>(
items: T[],
limit: number,
fn: (item: T) => Promise<R>
): Promise<R[]> {
const results: R[] = [];
let index = 0;
async function worker() {
while (index < items.length) {
const i = index++;
results[i] = await fn(items[i]);
}
}
const workers = Array(Math.min(limit, items.length)).fill(null).map(() => worker());
await Promise.all(workers);
return results;
}
export function getConfigPathFromArgv(): string | undefined {
const idx = process.argv.indexOf("--mcp-config");
if (idx >= 0 && idx + 1 < process.argv.length) {
return process.argv[idx + 1];
}
return undefined;
}
export function interpolateEnvVars(value: string): string {
return value
.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] ?? "")
.replace(/\$env:(\w+)/g, (_, name) => process.env[name] ?? "");
}
export function interpolateEnvRecord(values: Record<string, string> | undefined): Record<string, string> | undefined {
if (!values) return undefined;
const resolved: Record<string, string> = {};
for (const [key, value] of Object.entries(values)) {
resolved[key] = interpolateEnvVars(value);
}
return resolved;
}
export function resolveConfigPath(value: string | undefined): string | undefined {
if (value === undefined) return undefined;
const resolved = interpolateEnvVars(value);
if (resolved === "~") return homedir();
if (resolved.startsWith("~/") || resolved.startsWith("~\\")) {
return join(homedir(), resolved.slice(2));
}
return resolved;
}
export function resolveBearerToken(definition: Pick<ServerEntry, "bearerToken" | "bearerTokenEnv">): string | undefined {
if (definition.bearerToken !== undefined) {
return interpolateEnvVars(definition.bearerToken);
}
return definition.bearerTokenEnv ? process.env[definition.bearerTokenEnv] : undefined;
}
export function truncateAtWord(text: string, target: number): string {
if (!text || text.length <= target) return text;
const truncated = text.slice(0, target);
const lastSpace = truncated.lastIndexOf(" ");
if (lastSpace > target * 0.6) {
return truncated.slice(0, lastSpace) + "...";
}
return truncated + "...";
}
export function formatAuthRequiredMessage(
config: Pick<McpConfig, "settings">,
serverName: string,
defaultMessage: string,
): string {
const template = config.settings?.authRequiredMessage;
return template ? template.replaceAll("${server}", serverName) : defaultMessage;
}
/**
* Extract the adapter-owned UI stream mode from tool metadata.
*/
export function extractToolUiStreamMode(toolMeta: Record<string, unknown> | undefined): "eager" | "stream-first" | undefined {
const uiMeta = toolMeta?.ui;
if (!uiMeta || typeof uiMeta !== "object") return undefined;
const streamMode = (uiMeta as Record<string, unknown>)["pi-mcp-adapter.streamMode"];
if (streamMode === "eager" || streamMode === "stream-first") {
return streamMode;
}
return undefined;
}