ai-world-sdk 1.2.7 → 1.3.0
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/__tests__/vscode-login.test.d.ts +1 -0
- package/dist/__tests__/vscode-login.test.js +447 -0
- package/dist/agent-skills.d.ts +96 -0
- package/dist/agent-skills.js +186 -0
- package/dist/config.d.ts +3 -3
- package/dist/config.js +2 -2
- package/dist/database-requests.d.ts +151 -0
- package/dist/database-requests.js +242 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -1
- package/dist/login.d.ts +6 -0
- package/dist/login.js +33 -1
- package/dist/vscode-login.d.ts +102 -0
- package/dist/vscode-login.js +517 -0
- package/package.json +6 -1
- package/skills/ai-world-sdk/SKILL.md +64 -1
- package/skills/ai-world-sdk/docs/agent-skills.md +78 -0
- package/skills/ai-world-sdk/docs/common-mistakes.md +3 -0
- package/skills/ai-world-sdk/docs/database-requests.md +68 -0
- package/skills/ai-world-sdk/docs/provider-and-models.md +22 -7
- package/skills/ai-world-sdk/docs/vscode-login.md +198 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
// ── Mock vscode module ──────────────────────────────────────────────────────
|
|
39
|
+
const mockStatusBarItem = {
|
|
40
|
+
text: "",
|
|
41
|
+
tooltip: "",
|
|
42
|
+
command: "",
|
|
43
|
+
backgroundColor: undefined,
|
|
44
|
+
show: jest.fn(),
|
|
45
|
+
hide: jest.fn(),
|
|
46
|
+
dispose: jest.fn(),
|
|
47
|
+
};
|
|
48
|
+
const mockEventEmitter = {
|
|
49
|
+
event: jest.fn(),
|
|
50
|
+
fire: jest.fn(),
|
|
51
|
+
dispose: jest.fn(),
|
|
52
|
+
};
|
|
53
|
+
const mockPanel = {
|
|
54
|
+
webview: {
|
|
55
|
+
html: "",
|
|
56
|
+
onDidReceiveMessage: jest.fn(() => ({ dispose: jest.fn() })),
|
|
57
|
+
postMessage: jest.fn(),
|
|
58
|
+
},
|
|
59
|
+
reveal: jest.fn(),
|
|
60
|
+
dispose: jest.fn(),
|
|
61
|
+
onDidDispose: jest.fn(() => ({ dispose: jest.fn() })),
|
|
62
|
+
};
|
|
63
|
+
jest.mock("vscode", () => ({
|
|
64
|
+
EventEmitter: jest.fn(() => mockEventEmitter),
|
|
65
|
+
StatusBarAlignment: { Left: 1, Right: 2 },
|
|
66
|
+
ThemeColor: jest.fn((id) => ({ id })),
|
|
67
|
+
ViewColumn: { One: 1 },
|
|
68
|
+
Uri: {
|
|
69
|
+
parse: jest.fn((s) => ({ toString: () => s, fsPath: s })),
|
|
70
|
+
file: jest.fn((s) => ({ fsPath: s, toString: () => `file://${s}` })),
|
|
71
|
+
},
|
|
72
|
+
window: {
|
|
73
|
+
createStatusBarItem: jest.fn(() => mockStatusBarItem),
|
|
74
|
+
createWebviewPanel: jest.fn(() => mockPanel),
|
|
75
|
+
showInformationMessage: jest.fn(),
|
|
76
|
+
showWarningMessage: jest.fn(),
|
|
77
|
+
showErrorMessage: jest.fn(),
|
|
78
|
+
},
|
|
79
|
+
workspace: { workspaceFolders: undefined },
|
|
80
|
+
commands: {
|
|
81
|
+
registerCommand: jest.fn((_cmd, _cb) => ({ dispose: jest.fn() })),
|
|
82
|
+
},
|
|
83
|
+
env: {
|
|
84
|
+
openExternal: jest.fn().mockResolvedValue(true),
|
|
85
|
+
},
|
|
86
|
+
}), { virtual: true });
|
|
87
|
+
const vscode_login_1 = require("../vscode-login");
|
|
88
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
89
|
+
let nextPort = 19200;
|
|
90
|
+
function getTestPort() { return nextPort++; }
|
|
91
|
+
/** 创建带可用 SecretStorage mock 的 context */
|
|
92
|
+
function createMockContext(extName = "test-extension") {
|
|
93
|
+
const store = new Map();
|
|
94
|
+
return {
|
|
95
|
+
subscriptions: [],
|
|
96
|
+
secrets: {
|
|
97
|
+
get: jest.fn((key) => Promise.resolve(store.get(key))),
|
|
98
|
+
store: jest.fn((key, value) => { store.set(key, value); return Promise.resolve(); }),
|
|
99
|
+
delete: jest.fn((key) => { store.delete(key); return Promise.resolve(); }),
|
|
100
|
+
_store: store,
|
|
101
|
+
},
|
|
102
|
+
extension: { packageJSON: { name: extName } },
|
|
103
|
+
extensionUri: { fsPath: "/mock" },
|
|
104
|
+
globalStorageUri: { fsPath: "/mock/.global" },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
mockStatusBarItem.text = "";
|
|
111
|
+
mockStatusBarItem.tooltip = "";
|
|
112
|
+
});
|
|
113
|
+
describe("AIWorldExtension - 构造与基本属性", () => {
|
|
114
|
+
it("使用默认值创建实例", () => {
|
|
115
|
+
const ctx = createMockContext();
|
|
116
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
117
|
+
expect(ext.baseUrl).toBe("https://aiworld.local:8000");
|
|
118
|
+
expect(ext.isAuthenticated).toBe(false);
|
|
119
|
+
expect(ext.token).toBeUndefined();
|
|
120
|
+
expect(ext.user).toBeUndefined();
|
|
121
|
+
ext.dispose();
|
|
122
|
+
});
|
|
123
|
+
it("pluginId 默认取 context.extension.packageJSON.name", () => {
|
|
124
|
+
const ctx = createMockContext("my-cool-plugin");
|
|
125
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
126
|
+
expect(ext.pluginId).toBe("my-cool-plugin");
|
|
127
|
+
ext.dispose();
|
|
128
|
+
});
|
|
129
|
+
it("options.pluginId 覆盖默认值", () => {
|
|
130
|
+
const ctx = createMockContext("default-name");
|
|
131
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx, { pluginId: "override-id" });
|
|
132
|
+
expect(ext.pluginId).toBe("override-id");
|
|
133
|
+
ext.dispose();
|
|
134
|
+
});
|
|
135
|
+
it("使用自定义选项", () => {
|
|
136
|
+
const ctx = createMockContext();
|
|
137
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx, {
|
|
138
|
+
baseUrl: "http://localhost:9999",
|
|
139
|
+
pluginId: "test-ext",
|
|
140
|
+
debug: true,
|
|
141
|
+
});
|
|
142
|
+
expect(ext.baseUrl).toBe("http://localhost:9999");
|
|
143
|
+
expect(ext.pluginId).toBe("test-ext");
|
|
144
|
+
ext.dispose();
|
|
145
|
+
});
|
|
146
|
+
it("注册了 3 个命令", () => {
|
|
147
|
+
const vsc = require("vscode");
|
|
148
|
+
const ctx = createMockContext();
|
|
149
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
150
|
+
const calls = vsc.commands.registerCommand.mock.calls.map((c) => c[0]);
|
|
151
|
+
expect(calls).toContain("aiWorld.login");
|
|
152
|
+
expect(calls).toContain("aiWorld.logout");
|
|
153
|
+
expect(calls).toContain("aiWorld.showLoginPanel");
|
|
154
|
+
ext.dispose();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe("AIWorldExtension - SecretStorage 缓存", () => {
|
|
158
|
+
let authServer = null;
|
|
159
|
+
afterEach((done) => {
|
|
160
|
+
if (authServer?.listening) {
|
|
161
|
+
authServer.close(() => { authServer = null; done(); });
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
authServer = null;
|
|
165
|
+
done();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
it("_bootstrap 从 SecretStorage 读取缓存 token", async () => {
|
|
169
|
+
authServer = http.createServer((req, res) => {
|
|
170
|
+
if (req.url === "/api/auth/me" && req.headers.authorization === "Bearer secret-tok") {
|
|
171
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
172
|
+
res.end(JSON.stringify({ id: 1, full_name: "SecretUser" }));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
res.writeHead(401);
|
|
176
|
+
res.end();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
await new Promise((r) => authServer.listen(0, r));
|
|
180
|
+
const port = authServer.address().port;
|
|
181
|
+
const ctx = createMockContext();
|
|
182
|
+
ctx.secrets._store.set("ai-world:token", "secret-tok");
|
|
183
|
+
ctx.secrets._store.set("ai-world:baseUrl", `http://localhost:${port}`);
|
|
184
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx, { baseUrl: `http://localhost:${port}` });
|
|
185
|
+
await ext._bootstrap(false);
|
|
186
|
+
expect(ext.isAuthenticated).toBe(true);
|
|
187
|
+
expect(ext.token).toBe("secret-tok");
|
|
188
|
+
expect(ext.user?.full_name).toBe("SecretUser");
|
|
189
|
+
ext.dispose();
|
|
190
|
+
});
|
|
191
|
+
it("login 成功后写入 SecretStorage", async () => {
|
|
192
|
+
authServer = http.createServer((req, res) => {
|
|
193
|
+
if (req.url === "/api/auth/me" && req.headers.authorization === "Bearer new-tok") {
|
|
194
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
195
|
+
res.end(JSON.stringify({ id: 2, full_name: "NewUser" }));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
res.writeHead(401);
|
|
199
|
+
res.end();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
await new Promise((r) => authServer.listen(0, r));
|
|
203
|
+
const authPort = authServer.address().port;
|
|
204
|
+
const cbPort = getTestPort();
|
|
205
|
+
const vsc = require("vscode");
|
|
206
|
+
vsc.env.openExternal.mockImplementation(async () => {
|
|
207
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
208
|
+
await new Promise((resolve, reject) => {
|
|
209
|
+
const req = http.get(`http://localhost:${cbPort}/callback?token=new-tok`, (res) => {
|
|
210
|
+
res.resume();
|
|
211
|
+
res.on("end", resolve);
|
|
212
|
+
});
|
|
213
|
+
req.on("error", reject);
|
|
214
|
+
});
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
217
|
+
const ctx = createMockContext();
|
|
218
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx, {
|
|
219
|
+
baseUrl: `http://localhost:${authPort}`,
|
|
220
|
+
callbackPort: cbPort,
|
|
221
|
+
});
|
|
222
|
+
await ext.login();
|
|
223
|
+
expect(ctx.secrets._store.get("ai-world:token")).toBe("new-tok");
|
|
224
|
+
expect(JSON.parse(ctx.secrets._store.get("ai-world:user")).full_name).toBe("NewUser");
|
|
225
|
+
expect(ctx.secrets._store.get("ai-world:baseUrl")).toBe(`http://localhost:${authPort}`);
|
|
226
|
+
ext.dispose();
|
|
227
|
+
});
|
|
228
|
+
it("logout 清除 SecretStorage", async () => {
|
|
229
|
+
const ctx = createMockContext();
|
|
230
|
+
ctx.secrets._store.set("ai-world:token", "old");
|
|
231
|
+
ctx.secrets._store.set("ai-world:user", '{"id":1}');
|
|
232
|
+
ctx.secrets._store.set("ai-world:baseUrl", "http://x");
|
|
233
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
234
|
+
ext._token = "old";
|
|
235
|
+
await ext.logout();
|
|
236
|
+
expect(ctx.secrets._store.has("ai-world:token")).toBe(false);
|
|
237
|
+
expect(ctx.secrets._store.has("ai-world:user")).toBe(false);
|
|
238
|
+
expect(ctx.secrets._store.has("ai-world:baseUrl")).toBe(false);
|
|
239
|
+
ext.dispose();
|
|
240
|
+
});
|
|
241
|
+
it("缓存 token 过期时自动清除 SecretStorage", async () => {
|
|
242
|
+
authServer = http.createServer((_req, res) => { res.writeHead(401); res.end(); });
|
|
243
|
+
await new Promise((r) => authServer.listen(0, r));
|
|
244
|
+
const port = authServer.address().port;
|
|
245
|
+
const ctx = createMockContext();
|
|
246
|
+
ctx.secrets._store.set("ai-world:token", "expired");
|
|
247
|
+
ctx.secrets._store.set("ai-world:baseUrl", `http://localhost:${port}`);
|
|
248
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx, { baseUrl: `http://localhost:${port}` });
|
|
249
|
+
await ext._bootstrap(false);
|
|
250
|
+
expect(ext.isAuthenticated).toBe(false);
|
|
251
|
+
expect(ctx.secrets._store.has("ai-world:token")).toBe(false);
|
|
252
|
+
ext.dispose();
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe("AIWorldExtension - _bootstrap 提示", () => {
|
|
256
|
+
it("autoLogin=true 时弹出提示而不直接打开浏览器", async () => {
|
|
257
|
+
const vsc = require("vscode");
|
|
258
|
+
vsc.window.showWarningMessage.mockResolvedValue("稍后");
|
|
259
|
+
vsc.env.openExternal.mockClear();
|
|
260
|
+
const ctx = createMockContext();
|
|
261
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
262
|
+
await ext._bootstrap(true);
|
|
263
|
+
expect(vsc.window.showWarningMessage).toHaveBeenCalledWith("AI World: 未登录,部分功能不可用", "登录", "稍后");
|
|
264
|
+
expect(vsc.env.openExternal).not.toHaveBeenCalled();
|
|
265
|
+
ext.dispose();
|
|
266
|
+
});
|
|
267
|
+
it("弹窗点击登录后才触发浏览器打开", async () => {
|
|
268
|
+
let authServer = http.createServer((_req, res) => { res.writeHead(401); res.end(); });
|
|
269
|
+
await new Promise((r) => authServer.listen(0, r));
|
|
270
|
+
const authPort = authServer.address().port;
|
|
271
|
+
const cbPort = getTestPort();
|
|
272
|
+
const vsc = require("vscode");
|
|
273
|
+
vsc.env.openExternal.mockImplementation(async () => {
|
|
274
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
275
|
+
await new Promise((resolve, reject) => {
|
|
276
|
+
const req = http.get(`http://localhost:${cbPort}/callback?token=prompt-tok`, (res) => {
|
|
277
|
+
res.resume();
|
|
278
|
+
res.on("end", resolve);
|
|
279
|
+
});
|
|
280
|
+
req.on("error", reject);
|
|
281
|
+
});
|
|
282
|
+
return true;
|
|
283
|
+
});
|
|
284
|
+
vsc.window.showWarningMessage.mockResolvedValue("登录");
|
|
285
|
+
const ctx = createMockContext();
|
|
286
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx, {
|
|
287
|
+
baseUrl: `http://localhost:${authPort}`,
|
|
288
|
+
callbackPort: cbPort,
|
|
289
|
+
});
|
|
290
|
+
await ext._bootstrap(true);
|
|
291
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
292
|
+
expect(vsc.env.openExternal).toHaveBeenCalled();
|
|
293
|
+
expect(ext.isAuthenticated).toBe(true);
|
|
294
|
+
expect(ext.token).toBe("prompt-tok");
|
|
295
|
+
ext.dispose();
|
|
296
|
+
authServer.close();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
describe("AIWorldExtension - login / logout", () => {
|
|
300
|
+
let authServer = null;
|
|
301
|
+
afterEach((done) => {
|
|
302
|
+
if (authServer?.listening) {
|
|
303
|
+
authServer.close(() => { authServer = null; done(); });
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
authServer = null;
|
|
307
|
+
done();
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
it("login 超时时抛出错误", async () => {
|
|
311
|
+
authServer = http.createServer((_req, res) => { res.writeHead(401); res.end(); });
|
|
312
|
+
await new Promise((r) => authServer.listen(0, r));
|
|
313
|
+
const authPort = authServer.address().port;
|
|
314
|
+
const cbPort = getTestPort();
|
|
315
|
+
const vsc = require("vscode");
|
|
316
|
+
vsc.env.openExternal.mockResolvedValue(true);
|
|
317
|
+
const ctx = createMockContext();
|
|
318
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx, {
|
|
319
|
+
baseUrl: `http://localhost:${authPort}`,
|
|
320
|
+
callbackPort: cbPort,
|
|
321
|
+
timeout: 500,
|
|
322
|
+
});
|
|
323
|
+
await expect(ext.login()).rejects.toThrow("登录超时");
|
|
324
|
+
ext.dispose();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
describe("AIWorldExtension - showLoginPanel", () => {
|
|
328
|
+
it("创建 webview 面板", () => {
|
|
329
|
+
const vsc = require("vscode");
|
|
330
|
+
const ctx = createMockContext();
|
|
331
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
332
|
+
ext.showLoginPanel();
|
|
333
|
+
expect(vsc.window.createWebviewPanel).toHaveBeenCalledWith("aiWorldLogin", "AI World 登录", 1, expect.objectContaining({ enableScripts: true }));
|
|
334
|
+
ext.dispose();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe("AIWorldExtension - _getLoginPanelHtml", () => {
|
|
338
|
+
it("未登录时包含登录按钮", () => {
|
|
339
|
+
const ctx = createMockContext();
|
|
340
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
341
|
+
const html = ext._getLoginPanelHtml();
|
|
342
|
+
expect(html).toContain("通过飞书登录");
|
|
343
|
+
expect(html).toContain("未登录");
|
|
344
|
+
expect(html).not.toContain("退出登录");
|
|
345
|
+
ext.dispose();
|
|
346
|
+
});
|
|
347
|
+
it("已登录时显示用户信息和退出按钮", () => {
|
|
348
|
+
const ctx = createMockContext("my-ext");
|
|
349
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
350
|
+
ext._token = "x";
|
|
351
|
+
ext._user = { id: 1, full_name: "张三" };
|
|
352
|
+
const html = ext._getLoginPanelHtml();
|
|
353
|
+
expect(html).toContain("已登录");
|
|
354
|
+
expect(html).toContain("张三");
|
|
355
|
+
expect(html).toContain("my-ext");
|
|
356
|
+
expect(html).toContain("退出登录");
|
|
357
|
+
ext.dispose();
|
|
358
|
+
});
|
|
359
|
+
it("包含 acquireVsCodeApi 和 postMessage", () => {
|
|
360
|
+
const ctx = createMockContext();
|
|
361
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
362
|
+
const html = ext._getLoginPanelHtml();
|
|
363
|
+
expect(html).toContain("acquireVsCodeApi");
|
|
364
|
+
expect(html).toContain("postMessage");
|
|
365
|
+
ext.dispose();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
describe("initAIWorld / getAIWorld", () => {
|
|
369
|
+
let authServer = null;
|
|
370
|
+
afterEach((done) => {
|
|
371
|
+
if (authServer?.listening) {
|
|
372
|
+
authServer.close(() => { authServer = null; done(); });
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
authServer = null;
|
|
376
|
+
done();
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
it("initAIWorld 返回实例", async () => {
|
|
380
|
+
const ctx = createMockContext();
|
|
381
|
+
const ext = await (0, vscode_login_1.initAIWorld)(ctx, { autoLogin: false });
|
|
382
|
+
expect(ext).toBeInstanceOf(vscode_login_1.AIWorldExtension);
|
|
383
|
+
expect(ext.isAuthenticated).toBe(false);
|
|
384
|
+
ext.dispose();
|
|
385
|
+
});
|
|
386
|
+
it("getAIWorld 返回同一实例", async () => {
|
|
387
|
+
const ctx = createMockContext();
|
|
388
|
+
const ext = await (0, vscode_login_1.initAIWorld)(ctx, { autoLogin: false });
|
|
389
|
+
expect((0, vscode_login_1.getAIWorld)()).toBe(ext);
|
|
390
|
+
ext.dispose();
|
|
391
|
+
});
|
|
392
|
+
it("未初始化时 getAIWorld 抛出错误", () => {
|
|
393
|
+
try {
|
|
394
|
+
(0, vscode_login_1.getAIWorld)().dispose();
|
|
395
|
+
}
|
|
396
|
+
catch { /* noop */ }
|
|
397
|
+
expect(() => (0, vscode_login_1.getAIWorld)()).toThrow("请先调用 initAIWorld");
|
|
398
|
+
});
|
|
399
|
+
it("initAIWorld 带缓存 token 自动恢复", async () => {
|
|
400
|
+
authServer = http.createServer((req, res) => {
|
|
401
|
+
if (req.url === "/api/auth/me" && req.headers.authorization === "Bearer cached") {
|
|
402
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
403
|
+
res.end(JSON.stringify({ id: 3, full_name: "Cached" }));
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
res.writeHead(401);
|
|
407
|
+
res.end();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
await new Promise((r) => authServer.listen(0, r));
|
|
411
|
+
const port = authServer.address().port;
|
|
412
|
+
const ctx = createMockContext();
|
|
413
|
+
ctx.secrets._store.set("ai-world:token", "cached");
|
|
414
|
+
ctx.secrets._store.set("ai-world:baseUrl", `http://localhost:${port}`);
|
|
415
|
+
const ext = await (0, vscode_login_1.initAIWorld)(ctx, {
|
|
416
|
+
baseUrl: `http://localhost:${port}`,
|
|
417
|
+
autoLogin: false,
|
|
418
|
+
});
|
|
419
|
+
expect(ext.isAuthenticated).toBe(true);
|
|
420
|
+
expect(ext.user?.full_name).toBe("Cached");
|
|
421
|
+
ext.dispose();
|
|
422
|
+
});
|
|
423
|
+
it("pluginId 自动取 package.json name", async () => {
|
|
424
|
+
const ctx = createMockContext("auto-name-plugin");
|
|
425
|
+
const ext = await (0, vscode_login_1.initAIWorld)(ctx, { autoLogin: false });
|
|
426
|
+
expect(ext.pluginId).toBe("auto-name-plugin");
|
|
427
|
+
ext.dispose();
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
describe("Status Bar", () => {
|
|
431
|
+
it("未登录时显示警告样式", () => {
|
|
432
|
+
const ctx = createMockContext();
|
|
433
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
434
|
+
expect(mockStatusBarItem.text).toContain("未登录");
|
|
435
|
+
expect(mockStatusBarItem.show).toHaveBeenCalled();
|
|
436
|
+
ext.dispose();
|
|
437
|
+
});
|
|
438
|
+
it("已登录时显示用户名", () => {
|
|
439
|
+
const ctx = createMockContext();
|
|
440
|
+
const ext = new vscode_login_1.AIWorldExtension(ctx);
|
|
441
|
+
ext._token = "t";
|
|
442
|
+
ext._user = { id: 1, full_name: "StatusUser" };
|
|
443
|
+
ext._updateStatusBar();
|
|
444
|
+
expect(mockStatusBarItem.text).toContain("StatusUser");
|
|
445
|
+
ext.dispose();
|
|
446
|
+
});
|
|
447
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Skill Client
|
|
3
|
+
* Agent Skills 管理客户端
|
|
4
|
+
* 通过后端 /api/agent-skills 上传、下载、删除、列出 Skills
|
|
5
|
+
*/
|
|
6
|
+
export interface AgentSkillClientConfig {
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface AgentSkillInfo {
|
|
12
|
+
skill_name: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
user_id?: number;
|
|
16
|
+
created_at?: string;
|
|
17
|
+
updated_at?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface AgentSkillListResponse {
|
|
20
|
+
items: AgentSkillInfo[];
|
|
21
|
+
total: number;
|
|
22
|
+
page: number;
|
|
23
|
+
page_size: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ListSkillsOptions {
|
|
26
|
+
name?: string;
|
|
27
|
+
page?: number;
|
|
28
|
+
pageSize?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Agent Skill Client
|
|
32
|
+
* Agent Skills 管理客户端类
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { AgentSkillClient } from 'ai-world-sdk';
|
|
37
|
+
*
|
|
38
|
+
* const skills = new AgentSkillClient();
|
|
39
|
+
*
|
|
40
|
+
* // 列出所有 skills
|
|
41
|
+
* const list = await skills.list({ page: 1, pageSize: 20 });
|
|
42
|
+
*
|
|
43
|
+
* // 获取 skill 详情
|
|
44
|
+
* const detail = await skills.get('web-search');
|
|
45
|
+
*
|
|
46
|
+
* // 上传 skill(需要 File 或 Blob)
|
|
47
|
+
* const zipFile = new File([zipContent], 'web-search.zip');
|
|
48
|
+
* await skills.upload('web-search', zipFile);
|
|
49
|
+
*
|
|
50
|
+
* // 下载 skill
|
|
51
|
+
* const blob = await skills.download('web-search');
|
|
52
|
+
*
|
|
53
|
+
* // 删除 skill
|
|
54
|
+
* await skills.delete('web-search');
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare class AgentSkillClient {
|
|
58
|
+
private baseUrl;
|
|
59
|
+
private headers;
|
|
60
|
+
constructor(config?: AgentSkillClientConfig);
|
|
61
|
+
private handleErrorResponse;
|
|
62
|
+
/**
|
|
63
|
+
* List agent skills
|
|
64
|
+
* 列出 Agent Skills
|
|
65
|
+
*/
|
|
66
|
+
list(options?: ListSkillsOptions): Promise<AgentSkillListResponse>;
|
|
67
|
+
/**
|
|
68
|
+
* Get skill detail
|
|
69
|
+
* 获取 Skill 详情
|
|
70
|
+
*/
|
|
71
|
+
get(skillName: string): Promise<AgentSkillInfo>;
|
|
72
|
+
/**
|
|
73
|
+
* Upload a skill (zip file)
|
|
74
|
+
* 上传 Skill
|
|
75
|
+
* @param skillName - Skill 名称
|
|
76
|
+
* @param file - zip 文件
|
|
77
|
+
* @param description - 自定义显示描述(不填则使用 SKILL.md 中的描述)
|
|
78
|
+
*/
|
|
79
|
+
upload(skillName: string, file: File | Blob, description?: string): Promise<{
|
|
80
|
+
message: string;
|
|
81
|
+
skill_name: string;
|
|
82
|
+
is_update: boolean;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Download a skill as zip blob
|
|
86
|
+
* 下载 Skill(返回 Blob)
|
|
87
|
+
*/
|
|
88
|
+
download(skillName: string): Promise<Blob>;
|
|
89
|
+
/**
|
|
90
|
+
* Delete a skill
|
|
91
|
+
* 删除 Skill
|
|
92
|
+
*/
|
|
93
|
+
delete(skillName: string): Promise<{
|
|
94
|
+
message: string;
|
|
95
|
+
}>;
|
|
96
|
+
}
|