huaweicloud-devkit 1.0.2-dev.9 → 1.0.2-next.10
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/.agents/plugins/marketplace.json +20 -20
- package/LICENSE +201 -201
- package/README.md +178 -138
- package/README.zh-CN.md +176 -138
- package/bin/setup.cjs +10 -8
- package/integrations/opencode/commands/huaweicloud-doctor.md +14 -14
- package/integrations/opencode/opencode.json +11 -14
- package/integrations/opencode/skills/huaweicloud-core/SKILL.md +32 -32
- package/package.json +60 -44
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +31 -44
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +30 -43
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +31 -44
- package/plugins/huaweicloud-core/.mcp.json +13 -15
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +31 -44
- package/plugins/huaweicloud-core/hooks/hooks.json +26 -26
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +136 -136
- package/plugins/huaweicloud-core/safety/policy.json +76 -100
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +227 -199
- package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +164 -164
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +48 -48
- package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +50 -50
- package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +103 -101
- package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +90 -90
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +191 -190
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +171 -124
- package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +55 -55
- package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +70 -70
- package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +57 -56
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +106 -97
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +36 -29
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +29 -23
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +188 -185
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +144 -133
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +31 -31
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +57 -55
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +52 -52
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +132 -131
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +60 -58
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +72 -71
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +119 -119
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +142 -142
- package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +56 -56
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +69 -65
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +111 -111
- package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +148 -122
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +56 -56
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +65 -65
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +134 -127
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +49 -49
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +31 -26
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +101 -100
- package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +40 -40
- package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +56 -56
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +229 -224
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +173 -134
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +69 -69
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +84 -84
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +77 -76
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +60 -59
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +50 -50
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +63 -63
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +56 -56
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +4 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +56 -47
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +4 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +138 -128
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +4 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +111 -96
- package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +4 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +20 -20
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +30 -28
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +59 -60
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +4 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +83 -83
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +4 -4
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +54 -1
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +103 -95
- package/plugins/huaweicloud-core/src/auth/service.mjs +63 -63
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +283 -265
- package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +130 -129
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +68 -0
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +94 -0
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +140 -137
- package/plugins/huaweicloud-core/src/safety-policy.mjs +356 -302
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +86 -87
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +150 -153
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +196 -105
- package/plugins/huaweicloud-core/src/search-market.mjs +208 -169
- package/plugins/huaweicloud-core/src/setup-cli.mjs +2622 -1594
- package/plugins/huaweicloud-core/src/tools.mjs +463 -111
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +447 -427
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +128 -132
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +215 -227
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +189 -202
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +164 -158
- package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -19
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -338
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { getProxyDispatcher } from './proxy/proxy-agent.mjs';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const SNAPSHOT_PATH = join(__dirname, 'data', 'icons-manifest.v1.json');
|
|
8
|
+
const MANIFEST_URL = 'https://open.huaweicloud.com/openplatform/icons/manifest.v1.json';
|
|
9
|
+
const ICONS_PAGE_URL = 'https://open.huaweicloud.com/openplatform/icons.html';
|
|
10
|
+
const HTTP_TIMEOUT_MS = 10000;
|
|
11
|
+
const MAX_RESULTS = 5;
|
|
12
|
+
|
|
13
|
+
let cachedManifest = null;
|
|
14
|
+
|
|
15
|
+
export function clearIconCache() {
|
|
16
|
+
cachedManifest = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function fetchLiveManifest() {
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const timer = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
|
|
22
|
+
try {
|
|
23
|
+
const dispatcher = await getProxyDispatcher(MANIFEST_URL);
|
|
24
|
+
const fetchOpts = {
|
|
25
|
+
headers: { 'User-Agent': 'huaweicloud-devkit/1.0' },
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
};
|
|
28
|
+
if (dispatcher) fetchOpts.dispatcher = dispatcher;
|
|
29
|
+
const resp = await fetch(MANIFEST_URL, fetchOpts);
|
|
30
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
31
|
+
const data = await resp.json();
|
|
32
|
+
if (!data || !Array.isArray(data.icons)) {
|
|
33
|
+
throw new Error('Manifest is missing the icons array.');
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
} finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function loadSnapshot() {
|
|
42
|
+
const manifest = JSON.parse(readFileSync(SNAPSHOT_PATH, 'utf8'));
|
|
43
|
+
if (!manifest || !Array.isArray(manifest.icons)) {
|
|
44
|
+
throw new Error('Bundled icons manifest is corrupted.');
|
|
45
|
+
}
|
|
46
|
+
return manifest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function loadManifest() {
|
|
50
|
+
if (cachedManifest) return cachedManifest;
|
|
51
|
+
if (process.env.HUAWEICLOUD_ICONS_OFFLINE === '1') {
|
|
52
|
+
cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
|
|
53
|
+
return cachedManifest;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const live = await fetchLiveManifest();
|
|
57
|
+
cachedManifest = { manifest: live, source: 'live' };
|
|
58
|
+
} catch {
|
|
59
|
+
cachedManifest = { manifest: loadSnapshot(), source: 'snapshot' };
|
|
60
|
+
}
|
|
61
|
+
return cachedManifest;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeToken(token) {
|
|
65
|
+
return token.toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function scoreIcon(icon, tokens, index) {
|
|
69
|
+
let total = 0;
|
|
70
|
+
const matched = [];
|
|
71
|
+
for (const token of tokens) {
|
|
72
|
+
let best = 0;
|
|
73
|
+
const id = (icon.id || '').toLowerCase();
|
|
74
|
+
const name = (icon.name || '').toLowerCase();
|
|
75
|
+
if (id === token || (index.aliases[token] || []).includes(id)) {
|
|
76
|
+
best = 10;
|
|
77
|
+
} else if (name === token) {
|
|
78
|
+
best = 9;
|
|
79
|
+
} else if (name.startsWith(token)) {
|
|
80
|
+
best = 8;
|
|
81
|
+
} else if (name.includes(token)) {
|
|
82
|
+
best = 7;
|
|
83
|
+
} else if (id.includes(token)) {
|
|
84
|
+
best = 5;
|
|
85
|
+
} else if ((index.aliases[id] || []).some((a) => a.includes(token))) {
|
|
86
|
+
best = 6;
|
|
87
|
+
} else if ((index.tags[id] || []).some((t) => t.includes(token))) {
|
|
88
|
+
best = 4;
|
|
89
|
+
} else if ((index.categories[id] || '').includes(token)) {
|
|
90
|
+
best = 3;
|
|
91
|
+
} else if ((index.descriptions[id] || '').includes(token)) {
|
|
92
|
+
best = 2;
|
|
93
|
+
}
|
|
94
|
+
if (best > 0) {
|
|
95
|
+
total += best;
|
|
96
|
+
matched.push(token);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [total, matched];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function getServiceIcon(service = '', category = '') {
|
|
103
|
+
const query = String(service || '').trim();
|
|
104
|
+
const catFilter = String(category || '')
|
|
105
|
+
.trim()
|
|
106
|
+
.toLowerCase();
|
|
107
|
+
if (!query && !catFilter) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
error:
|
|
111
|
+
'service or category is required. Examples: ecs, obs, modelarts, 对象存储, 虚拟私有云, or a category such as 计算 / 存储 / 人工智能.',
|
|
112
|
+
iconsPageUrl: ICONS_PAGE_URL,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const { manifest, source } = await loadManifest();
|
|
116
|
+
const tokens = query.replace(/[,;]/g, ' ').split(/\s+/).filter(Boolean).map(normalizeToken);
|
|
117
|
+
|
|
118
|
+
const index = { aliases: {}, tags: {}, categories: {}, descriptions: {} };
|
|
119
|
+
for (const icon of manifest.icons) {
|
|
120
|
+
const id = (icon.id || '').toLowerCase();
|
|
121
|
+
index.aliases[id] = (icon.aliases || []).map((a) => String(a).toLowerCase());
|
|
122
|
+
index.tags[id] = (icon.tags || []).map((t) => String(t).toLowerCase());
|
|
123
|
+
index.categories[id] = [
|
|
124
|
+
String(icon.category || '').toLowerCase(),
|
|
125
|
+
String(icon.subcategory || '').toLowerCase(),
|
|
126
|
+
].join(' ');
|
|
127
|
+
index.descriptions[id] = String(icon.description || '').toLowerCase();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const results = [];
|
|
131
|
+
for (const icon of manifest.icons) {
|
|
132
|
+
const categoryMatches = !catFilter || String(icon.category || '').toLowerCase() === catFilter;
|
|
133
|
+
if (!categoryMatches) continue;
|
|
134
|
+
const [score, matched] = tokens.length ? scoreIcon(icon, tokens, index) : [0, []];
|
|
135
|
+
if (tokens.length && score === 0) continue;
|
|
136
|
+
results.push({
|
|
137
|
+
id: icon.id,
|
|
138
|
+
name: icon.name,
|
|
139
|
+
category: icon.category,
|
|
140
|
+
subcategory: icon.subcategory || undefined,
|
|
141
|
+
description: icon.description || undefined,
|
|
142
|
+
aliases: icon.aliases,
|
|
143
|
+
product_url: icon.product_url,
|
|
144
|
+
logo: {
|
|
145
|
+
source_url: icon.logo?.source_url,
|
|
146
|
+
local_path: icon.logo?.local_path,
|
|
147
|
+
},
|
|
148
|
+
architecture: icon.architecture
|
|
149
|
+
? { status: icon.architecture.status, local_path: icon.architecture.local_path }
|
|
150
|
+
: undefined,
|
|
151
|
+
score,
|
|
152
|
+
matched,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
results.sort((a, b) => b.score - a.score);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
ok: true,
|
|
159
|
+
query,
|
|
160
|
+
category: catFilter || undefined,
|
|
161
|
+
source,
|
|
162
|
+
manifestGeneratedAt: manifest.generated_at,
|
|
163
|
+
count: results.length,
|
|
164
|
+
iconsPageUrl: ICONS_PAGE_URL,
|
|
165
|
+
note:
|
|
166
|
+
source === 'live'
|
|
167
|
+
? 'Live manifest from open.huaweicloud.com.'
|
|
168
|
+
: 'Live manifest unreachable; using bundled snapshot. Upgrade the package to refresh the bundled snapshot.',
|
|
169
|
+
results: results.slice(0, MAX_RESULTS),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -1,129 +1,130 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
let
|
|
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
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return;
|
|
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
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
3
|
+
import { stdin, stdout } from 'node:process';
|
|
4
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
5
|
+
import { resolve, dirname } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
|
|
8
|
+
|
|
9
|
+
// The MCP server is now loaded by a live agent session. Clear the install marker
|
|
10
|
+
// in this plugin dir so `doctor` no longer reports "restart needed".
|
|
11
|
+
try {
|
|
12
|
+
const pluginDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const marker = resolve(pluginDir, '.installed');
|
|
14
|
+
if (existsSync(marker)) rmSync(marker, { force: true });
|
|
15
|
+
} catch {}
|
|
16
|
+
|
|
17
|
+
let buffer = Buffer.alloc(0);
|
|
18
|
+
let useContentLengthFraming = true;
|
|
19
|
+
|
|
20
|
+
stdin.on('data', (chunk) => {
|
|
21
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
22
|
+
readFrames();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function readFrames() {
|
|
26
|
+
while (true) {
|
|
27
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
28
|
+
if (headerEnd !== -1) {
|
|
29
|
+
useContentLengthFraming = true;
|
|
30
|
+
const consumed = parseContentLengthFrame(headerEnd);
|
|
31
|
+
if (!consumed) return;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lf = buffer.indexOf('\n');
|
|
36
|
+
if (lf !== -1) {
|
|
37
|
+
useContentLengthFraming = false;
|
|
38
|
+
const line = buffer.subarray(0, lf).toString('utf8').trim();
|
|
39
|
+
buffer = buffer.subarray(lf + 1);
|
|
40
|
+
if (line) void handleMessage(JSON.parse(line));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseContentLengthFrame(headerEnd) {
|
|
49
|
+
const header = buffer.subarray(0, headerEnd).toString('utf8');
|
|
50
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
51
|
+
if (!match) {
|
|
52
|
+
buffer = Buffer.alloc(0);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
const length = Number(match[1]);
|
|
56
|
+
const bodyStart = headerEnd + 4;
|
|
57
|
+
const bodyEnd = bodyStart + length;
|
|
58
|
+
if (buffer.length < bodyEnd) return false;
|
|
59
|
+
const body = buffer.subarray(bodyStart, bodyEnd).toString('utf8');
|
|
60
|
+
buffer = buffer.subarray(bodyEnd);
|
|
61
|
+
void handleMessage(JSON.parse(body));
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function handleMessage(message) {
|
|
66
|
+
if (!Object.hasOwn(message, 'id')) {
|
|
67
|
+
if (message.method === 'notifications/initialized') return;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const result = await dispatch(message.method, message.params || {});
|
|
72
|
+
writeMessage({ jsonrpc: '2.0', id: message.id, result });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
writeMessage({
|
|
75
|
+
jsonrpc: '2.0',
|
|
76
|
+
id: message.id,
|
|
77
|
+
error: {
|
|
78
|
+
code: -32603,
|
|
79
|
+
message: error.message,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function dispatch(method, params) {
|
|
86
|
+
if (method === 'initialize') {
|
|
87
|
+
return {
|
|
88
|
+
protocolVersion: params.protocolVersion || '2024-11-05',
|
|
89
|
+
capabilities: {
|
|
90
|
+
tools: {},
|
|
91
|
+
},
|
|
92
|
+
serverInfo: {
|
|
93
|
+
name: 'huaweicloud-devkit',
|
|
94
|
+
version: '0.1.0',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (method === 'tools/list') {
|
|
100
|
+
return { tools: TOOL_DEFINITIONS };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (method === 'tools/call') {
|
|
104
|
+
const result = await callTool(params.name, params.arguments || {});
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: JSON.stringify(result, null, 2),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
isError: false,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (method === 'resources/list') {
|
|
117
|
+
return { resources: [] };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
throw new Error(`Unsupported method: ${method}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function writeMessage(message) {
|
|
124
|
+
const json = JSON.stringify(message);
|
|
125
|
+
if (useContentLengthFraming) {
|
|
126
|
+
stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
|
|
127
|
+
} else {
|
|
128
|
+
stdout.write(json + '\n');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { getProxyUrlForTarget } from './proxy-config.mjs';
|
|
2
|
+
|
|
3
|
+
let cachedDispatcher = undefined;
|
|
4
|
+
let cachedDispatcherProxyUrl = null;
|
|
5
|
+
|
|
6
|
+
async function importUndici() {
|
|
7
|
+
return await import('undici');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function getProxyDispatcher(targetUrl) {
|
|
11
|
+
const proxyUrl = getProxyUrlForTarget(targetUrl);
|
|
12
|
+
if (!proxyUrl) return undefined;
|
|
13
|
+
|
|
14
|
+
if (cachedDispatcher && cachedDispatcherProxyUrl === proxyUrl) {
|
|
15
|
+
return cachedDispatcher;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { ProxyAgent } = await importUndici();
|
|
19
|
+
cachedDispatcher = new ProxyAgent(proxyUrl);
|
|
20
|
+
cachedDispatcherProxyUrl = proxyUrl;
|
|
21
|
+
return cachedDispatcher;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function clearProxyDispatcherCache() {
|
|
25
|
+
cachedDispatcher = undefined;
|
|
26
|
+
cachedDispatcherProxyUrl = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function createProxyWebSocket(url, protocols) {
|
|
30
|
+
const proxyUrl = getProxyUrlForTarget(url);
|
|
31
|
+
if (!proxyUrl) {
|
|
32
|
+
return new globalThis.WebSocket(url, protocols);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const dispatcher = await getProxyDispatcher(url);
|
|
36
|
+
const { WebSocket: UndiciWebSocket } = await importUndici();
|
|
37
|
+
|
|
38
|
+
const wsOptions = { dispatcher };
|
|
39
|
+
if (protocols) {
|
|
40
|
+
if (Array.isArray(protocols)) {
|
|
41
|
+
wsOptions.protocols = protocols;
|
|
42
|
+
} else {
|
|
43
|
+
wsOptions.protocols = [protocols];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return new UndiciWebSocket(url, wsOptions);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getWebSocketImpl(targetUrl) {
|
|
51
|
+
const proxyUrl = getProxyUrlForTarget(targetUrl);
|
|
52
|
+
if (!proxyUrl) return globalThis.WebSocket;
|
|
53
|
+
|
|
54
|
+
const dispatcher = await getProxyDispatcher(targetUrl);
|
|
55
|
+
const { WebSocket: UndiciWebSocket } = await importUndici();
|
|
56
|
+
|
|
57
|
+
class ProxyWebSocket extends UndiciWebSocket {
|
|
58
|
+
constructor(url, protocols) {
|
|
59
|
+
const options = { dispatcher };
|
|
60
|
+
if (protocols) {
|
|
61
|
+
options.protocols = Array.isArray(protocols) ? protocols : [protocols];
|
|
62
|
+
}
|
|
63
|
+
super(url, options);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return ProxyWebSocket;
|
|
68
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
|
|
5
|
+
function baseHome() {
|
|
6
|
+
return process.env.HUAWEICLOUD_HOME || homedir();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function proxyConfigPath() {
|
|
10
|
+
return join(baseHome(), '.config', 'huaweicloud', 'proxy.json');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readProxyConfig() {
|
|
14
|
+
const path = proxyConfigPath();
|
|
15
|
+
if (!existsSync(path)) return null;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function writeProxyConfig(config = {}) {
|
|
24
|
+
const path = proxyConfigPath();
|
|
25
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
26
|
+
const payload = {
|
|
27
|
+
https_proxy: String(config.https_proxy || ''),
|
|
28
|
+
http_proxy: String(config.http_proxy || ''),
|
|
29
|
+
no_proxy: String(config.no_proxy || ''),
|
|
30
|
+
};
|
|
31
|
+
writeFileSync(path, JSON.stringify(payload, null, 2), { encoding: 'utf8' });
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function clearProxyConfig() {
|
|
36
|
+
const path = proxyConfigPath();
|
|
37
|
+
if (!existsSync(path)) return false;
|
|
38
|
+
rmSync(path);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function shouldBypassProxy(hostname, noProxyList) {
|
|
43
|
+
if (!noProxyList.length) return false;
|
|
44
|
+
const lower = hostname.toLowerCase();
|
|
45
|
+
for (const pattern of noProxyList) {
|
|
46
|
+
const p = pattern.trim().toLowerCase();
|
|
47
|
+
if (!p) continue;
|
|
48
|
+
if (p === lower) return true;
|
|
49
|
+
if (p === '*') return true;
|
|
50
|
+
if (p.startsWith('*.')) {
|
|
51
|
+
const domain = p.slice(1);
|
|
52
|
+
if (lower.endsWith(domain) || lower === p.slice(2)) return true;
|
|
53
|
+
}
|
|
54
|
+
if (p.startsWith('.') && lower.endsWith(p)) return true;
|
|
55
|
+
if (lower.endsWith('.' + p)) return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getProxySettings(targetUrl) {
|
|
61
|
+
const envHttps = process.env.HTTPS_PROXY || process.env.https_proxy || '';
|
|
62
|
+
const envHttp = process.env.HTTP_PROXY || process.env.http_proxy || '';
|
|
63
|
+
const envNoProxy = process.env.NO_PROXY || process.env.no_proxy || '';
|
|
64
|
+
|
|
65
|
+
const fileConfig = readProxyConfig();
|
|
66
|
+
|
|
67
|
+
const https_proxy = envHttps || fileConfig?.https_proxy || fileConfig?.HTTPS_PROXY || '';
|
|
68
|
+
const http_proxy = envHttp || fileConfig?.http_proxy || fileConfig?.HTTP_PROXY || '';
|
|
69
|
+
const no_proxy = envNoProxy || fileConfig?.no_proxy || fileConfig?.NO_PROXY || '';
|
|
70
|
+
|
|
71
|
+
if (!https_proxy && !http_proxy) return null;
|
|
72
|
+
|
|
73
|
+
const noProxyList = no_proxy
|
|
74
|
+
.split(',')
|
|
75
|
+
.map((s) => s.trim())
|
|
76
|
+
.filter(Boolean);
|
|
77
|
+
|
|
78
|
+
if (targetUrl) {
|
|
79
|
+
const parsed = new URL(targetUrl);
|
|
80
|
+
if (shouldBypassProxy(parsed.hostname, noProxyList)) return null;
|
|
81
|
+
const isHttps = parsed.protocol === 'https:';
|
|
82
|
+
const proxyUrl = isHttps ? https_proxy : http_proxy;
|
|
83
|
+
if (!proxyUrl) return null;
|
|
84
|
+
return { proxyUrl, noProxyList, targetProtocol: parsed.protocol };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { https_proxy, http_proxy, no_proxy, noProxyList };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getProxyUrlForTarget(targetUrl) {
|
|
91
|
+
const settings = getProxySettings(targetUrl);
|
|
92
|
+
if (!settings) return null;
|
|
93
|
+
return settings.proxyUrl || null;
|
|
94
|
+
}
|