@oneentry/mcp-platform-server 0.1.1 → 0.1.4
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/LICENSE +21 -0
- package/README.md +62 -61
- package/dist/api/audit.d.ts +0 -11
- package/dist/api/audit.js +0 -10
- package/dist/api/auth.d.ts +0 -18
- package/dist/api/auth.js +0 -20
- package/dist/api/build-catalog.d.ts +1 -14
- package/dist/api/build-catalog.js +16 -77
- package/dist/api/catalog.d.ts +5 -27
- package/dist/api/catalog.d.ts.map +1 -1
- package/dist/api/catalog.js +31 -43
- package/dist/api/catalog.js.map +1 -1
- package/dist/api/client.d.ts +0 -24
- package/dist/api/client.js +6 -28
- package/dist/api/normalize-schema.d.ts +0 -11
- package/dist/api/normalize-schema.js +0 -18
- package/dist/api/policy.d.ts +0 -23
- package/dist/api/policy.js +0 -0
- package/dist/api/shape.d.ts +0 -13
- package/dist/api/shape.js +0 -21
- package/dist/api/swagger-source.d.ts +13 -12
- package/dist/api/swagger-source.js +93 -50
- package/dist/api/types.d.ts +0 -34
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js +0 -1
- package/dist/bin/cli.d.ts +0 -1
- package/dist/bin/cli.js +1 -5
- package/dist/bin/cli.js.map +1 -1
- package/dist/config/config.d.ts +0 -54
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +0 -60
- package/dist/config/config.js.map +1 -1
- package/dist/index.d.ts +2 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/knowledge/chunk.d.ts +0 -15
- package/dist/knowledge/chunk.js +0 -16
- package/dist/knowledge/github.d.ts +0 -25
- package/dist/knowledge/github.js +0 -40
- package/dist/knowledge/loader.d.ts +0 -28
- package/dist/knowledge/loader.d.ts.map +1 -1
- package/dist/knowledge/loader.js +0 -26
- package/dist/knowledge/loader.js.map +1 -1
- package/dist/knowledge/search.d.ts +0 -22
- package/dist/knowledge/search.d.ts.map +1 -1
- package/dist/knowledge/search.js +0 -34
- package/dist/knowledge/search.js.map +1 -1
- package/dist/knowledge/tar.d.ts +0 -15
- package/dist/knowledge/tar.js +0 -33
- package/dist/knowledge/types.d.ts +0 -35
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +0 -1
- package/dist/server.d.ts +0 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +29 -15
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +0 -23
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +0 -18
- package/dist/session.js.map +1 -1
- package/dist/tools/api-call.d.ts +0 -2
- package/dist/tools/api-call.js +0 -23
- package/dist/tools/api-discovery.d.ts +0 -2
- package/dist/tools/api-discovery.js +2 -5
- package/dist/tools/docs.d.ts +0 -2
- package/dist/tools/docs.js +1 -5
- package/dist/tools/guide.d.ts +0 -7
- package/dist/tools/guide.d.ts.map +1 -1
- package/dist/tools/guide.js +2 -9
- package/dist/tools/guide.js.map +1 -1
- package/dist/tools/result.d.ts +0 -8
- package/dist/tools/result.js +0 -7
- package/dist/tools/whoami.d.ts +0 -2
- package/dist/tools/whoami.d.ts.map +1 -1
- package/dist/tools/whoami.js +1 -3
- package/dist/tools/whoami.js.map +1 -1
- package/dist/transports/http.d.ts +0 -6
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +1 -27
- package/dist/transports/http.js.map +1 -1
- package/dist/transports/stdio.d.ts +0 -6
- package/dist/transports/stdio.js +0 -6
- package/dist/transports/stdio.js.map +1 -1
- package/knowledge/operating-rules.md +70 -125
- package/package.json +18 -5
- package/dist/api/build-catalog.d.ts.map +0 -1
- package/dist/api/build-catalog.js.map +0 -1
- package/dist/api/normalize-schema.d.ts.map +0 -1
- package/dist/api/normalize-schema.js.map +0 -1
- package/dist/api/swagger-source.d.ts.map +0 -1
- package/dist/api/swagger-source.js.map +0 -1
- package/dist/knowledge/github.d.ts.map +0 -1
- package/dist/knowledge/github.js.map +0 -1
- package/dist/knowledge/tar.d.ts.map +0 -1
- package/dist/knowledge/tar.js.map +0 -1
package/dist/api/catalog.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Config } from '../config/config.js';
|
|
2
2
|
import { type PermissionMap } from './build-catalog.js';
|
|
3
3
|
import type { Catalog, Operation } from './types.js';
|
|
4
|
-
/** Найденная операция с оценкой релевантности. */
|
|
5
4
|
export interface OperationHit {
|
|
6
5
|
opId: string;
|
|
7
6
|
method: string;
|
|
@@ -12,40 +11,20 @@ export interface OperationHit {
|
|
|
12
11
|
risk: Operation['risk'];
|
|
13
12
|
score: number;
|
|
14
13
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* полнотекстовый движок дал бы ту же выдачу при большем весе.
|
|
20
|
-
*/
|
|
14
|
+
export interface StartupAuth {
|
|
15
|
+
bearer?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
21
18
|
export declare class OperationCatalog {
|
|
22
19
|
private byOpId;
|
|
23
20
|
catalog: Catalog;
|
|
24
21
|
constructor(catalog: Catalog);
|
|
25
|
-
/** Карта прав — единственное, что осталось предсобранным: её нет в swagger. */
|
|
26
22
|
static permissionMap(): PermissionMap;
|
|
27
|
-
|
|
28
|
-
* Строит каталог из swagger подключённого стенда.
|
|
29
|
-
* Недоступный стенд не мешает серверу подняться: каталог получается пустым,
|
|
30
|
-
* а причина уходит в `warnings` и оттуда в `cms_guide` и `cms_whoami`.
|
|
31
|
-
* Пустой каталог безопасен — вызвать по нему нечего, — но молчать о нём нельзя.
|
|
32
|
-
*/
|
|
33
|
-
static resolve(config: Config, bearer?: string): Promise<OperationCatalog>;
|
|
34
|
-
/** Каталог не удалось собрать при старте — стенд был недоступен или закрыт авторизацией. */
|
|
23
|
+
static resolve(config: Config, auth?: StartupAuth): Promise<OperationCatalog>;
|
|
35
24
|
get isEmpty(): boolean;
|
|
36
|
-
/**
|
|
37
|
-
* Заменяет содержимое каталога, собранного вхолостую.
|
|
38
|
-
* Нужно remote-режиму: процесс поднимается без учётных данных, и если стенд
|
|
39
|
-
* отдаёт swagger только авторизованным, единственный шанс получить каталог —
|
|
40
|
-
* первая сессия с токеном. Каталог общий на процесс, поэтому и подмена одна.
|
|
41
|
-
*/
|
|
42
25
|
adopt(other: OperationCatalog): void;
|
|
43
26
|
get(opId: string): Operation | undefined;
|
|
44
27
|
operations(): readonly Operation[];
|
|
45
|
-
/**
|
|
46
|
-
* Подсказывает похожие `opId` — нужна для внятной ошибки, когда модель
|
|
47
|
-
* угадала имя операции вместо того, чтобы взять его из поиска.
|
|
48
|
-
*/
|
|
49
28
|
suggest(opId: string, limit?: number): string[];
|
|
50
29
|
search(params: {
|
|
51
30
|
query: string;
|
|
@@ -55,4 +34,3 @@ export declare class OperationCatalog {
|
|
|
55
34
|
limit?: number;
|
|
56
35
|
}): OperationHit[];
|
|
57
36
|
}
|
|
58
|
-
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/api/catalog.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/api/catalog.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAErD,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,OAAO,EAAE,OAAO;IAS5B,MAAM,CAAC,IAAI,IAAI,gBAAgB;IAK/B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIxC,UAAU,IAAI,SAAS,SAAS,EAAE;IAIlC;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE;IAU1C,MAAM,CAAC,MAAM,EAAE;QACb,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,YAAY,EAAE;CA+CnB"}
|
package/dist/api/catalog.js
CHANGED
|
@@ -2,13 +2,25 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { dataDir } from '../knowledge/loader.js';
|
|
4
4
|
import { buildCatalog } from './build-catalog.js';
|
|
5
|
-
import { fetchSwagger } from './swagger-source.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
import { fetchSwagger, nestSwaggerUrl, nodeSwaggerUrl, } from './swagger-source.js';
|
|
6
|
+
const loginWarning = (error) => `The startup login failed, so the API document was requested without a token: ${error}`;
|
|
7
|
+
const sourceWarnings = (swagger, auth) => {
|
|
8
|
+
const warnings = [];
|
|
9
|
+
const why = swagger.failures.length > 0 ? ` (${swagger.failures.join('; ')})` : '';
|
|
10
|
+
if (swagger.origin === 'cache') {
|
|
11
|
+
warnings.push(`Neither API document could be read${why}; this catalog came from the on-disk cache ` +
|
|
12
|
+
'and may not match the API that is actually running.');
|
|
13
|
+
}
|
|
14
|
+
if (swagger.origin === 'instance-node') {
|
|
15
|
+
warnings.push(`${swagger.url} serves only the operations whitelisted for the workflow "admin-api" ` +
|
|
16
|
+
'node, not the whole Admin API, so this catalog is a small subset of the endpoints ' +
|
|
17
|
+
`that exist. The complete document was not available${why}.`);
|
|
18
|
+
}
|
|
19
|
+
if (auth.error !== undefined) {
|
|
20
|
+
warnings.push(loginWarning(auth.error));
|
|
21
|
+
}
|
|
22
|
+
return warnings;
|
|
23
|
+
};
|
|
12
24
|
export class OperationCatalog {
|
|
13
25
|
byOpId;
|
|
14
26
|
catalog;
|
|
@@ -16,46 +28,33 @@ export class OperationCatalog {
|
|
|
16
28
|
if (catalog.version !== 1) {
|
|
17
29
|
throw new Error(`Unsupported catalog version ${String(catalog.version)}`);
|
|
18
30
|
}
|
|
19
|
-
/** Каталог собирается из документа, полученного по сети: поля нормализуем на входе. */
|
|
20
31
|
this.catalog = { ...catalog, warnings: catalog.warnings ?? [] };
|
|
21
32
|
this.byOpId = new Map(catalog.operations.map((o) => [o.opId, o]));
|
|
22
33
|
}
|
|
23
|
-
/** Карта прав — единственное, что осталось предсобранным: её нет в swagger. */
|
|
24
34
|
static permissionMap() {
|
|
25
35
|
const file = resolve(dataDir(), 'permissions.json');
|
|
26
36
|
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
27
37
|
return { byOpId: parsed.byOpId, permissions: parsed.permissions };
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
* Строит каталог из swagger подключённого стенда.
|
|
31
|
-
* Недоступный стенд не мешает серверу подняться: каталог получается пустым,
|
|
32
|
-
* а причина уходит в `warnings` и оттуда в `cms_guide` и `cms_whoami`.
|
|
33
|
-
* Пустой каталог безопасен — вызвать по нему нечего, — но молчать о нём нельзя.
|
|
34
|
-
*/
|
|
35
|
-
static async resolve(config, bearer) {
|
|
39
|
+
static async resolve(config, auth = {}) {
|
|
36
40
|
const builtAt = new Date().toISOString();
|
|
37
41
|
try {
|
|
38
42
|
const swagger = await fetchSwagger({
|
|
39
43
|
baseUrl: config.baseUrl,
|
|
40
44
|
cacheDir: config.cacheDir,
|
|
41
45
|
timeoutMs: config.requestTimeoutMs,
|
|
42
|
-
...(bearer ? { bearer } : {}),
|
|
46
|
+
...(auth.bearer ? { bearer: auth.bearer } : {}),
|
|
43
47
|
});
|
|
44
48
|
const catalog = buildCatalog({
|
|
45
49
|
rawSwagger: swagger.raw,
|
|
46
50
|
permissions: OperationCatalog.permissionMap(),
|
|
47
51
|
builtAt,
|
|
52
|
+
sourceUrl: swagger.url,
|
|
53
|
+
});
|
|
54
|
+
return new OperationCatalog({
|
|
55
|
+
...catalog,
|
|
56
|
+
warnings: [...catalog.warnings, ...sourceWarnings(swagger, auth)],
|
|
48
57
|
});
|
|
49
|
-
return new OperationCatalog(swagger.origin === 'cache'
|
|
50
|
-
? {
|
|
51
|
-
...catalog,
|
|
52
|
-
warnings: [
|
|
53
|
-
...catalog.warnings,
|
|
54
|
-
`The stand at ${config.baseUrl} was unreachable; this catalog came from the ` +
|
|
55
|
-
'on-disk cache and may not match the API that is actually running.',
|
|
56
|
-
],
|
|
57
|
-
}
|
|
58
|
-
: catalog);
|
|
59
58
|
}
|
|
60
59
|
catch (error) {
|
|
61
60
|
return new OperationCatalog({
|
|
@@ -65,24 +64,19 @@ export class OperationCatalog {
|
|
|
65
64
|
swaggerHash: '',
|
|
66
65
|
permissions: [],
|
|
67
66
|
warnings: [
|
|
68
|
-
`The operation catalog is EMPTY: ${
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
`The operation catalog is EMPTY: ${error instanceof Error ? error.message : String(error)}. ` +
|
|
68
|
+
'No API operation can be searched or called until either ' +
|
|
69
|
+
`${nestSwaggerUrl(config.baseUrl)} or ${nodeSwaggerUrl(config.baseUrl)} ` +
|
|
70
|
+
'answers 200 with an OpenAPI document.',
|
|
71
|
+
...(auth.error === undefined ? [] : [loginWarning(auth.error)]),
|
|
71
72
|
],
|
|
72
73
|
operations: [],
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
/** Каталог не удалось собрать при старте — стенд был недоступен или закрыт авторизацией. */
|
|
77
77
|
get isEmpty() {
|
|
78
78
|
return this.catalog.operations.length === 0;
|
|
79
79
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Заменяет содержимое каталога, собранного вхолостую.
|
|
82
|
-
* Нужно remote-режиму: процесс поднимается без учётных данных, и если стенд
|
|
83
|
-
* отдаёт swagger только авторизованным, единственный шанс получить каталог —
|
|
84
|
-
* первая сессия с токеном. Каталог общий на процесс, поэтому и подмена одна.
|
|
85
|
-
*/
|
|
86
80
|
adopt(other) {
|
|
87
81
|
this.catalog = other.catalog;
|
|
88
82
|
this.byOpId = new Map(other.catalog.operations.map((o) => [o.opId, o]));
|
|
@@ -93,10 +87,6 @@ export class OperationCatalog {
|
|
|
93
87
|
operations() {
|
|
94
88
|
return this.catalog.operations;
|
|
95
89
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Подсказывает похожие `opId` — нужна для внятной ошибки, когда модель
|
|
98
|
-
* угадала имя операции вместо того, чтобы взять его из поиска.
|
|
99
|
-
*/
|
|
100
90
|
suggest(opId, limit = 5) {
|
|
101
91
|
const needle = opId.toLowerCase();
|
|
102
92
|
return this.catalog.operations
|
|
@@ -151,7 +141,6 @@ export class OperationCatalog {
|
|
|
151
141
|
return hits.slice(0, params.limit ?? 15);
|
|
152
142
|
}
|
|
153
143
|
}
|
|
154
|
-
/** Доля общих триграмм — грубая, но достаточная мера похожести имён операций. */
|
|
155
144
|
const overlap = (a, b) => {
|
|
156
145
|
const grams = (value) => {
|
|
157
146
|
const out = new Set();
|
|
@@ -170,4 +159,3 @@ const overlap = (a, b) => {
|
|
|
170
159
|
}
|
|
171
160
|
return right.size === 0 ? 0 : shared / right.size;
|
|
172
161
|
};
|
|
173
|
-
//# sourceMappingURL=catalog.js.map
|
package/dist/api/catalog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/api/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/api/catalog.ts"],"names":[],"mappings":"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"}
|
package/dist/api/client.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import type { TokenStore } from './auth.js';
|
|
2
2
|
import type { Operation } from './types.js';
|
|
3
|
-
/** Аргументы вызова операции, как их присылает модель. */
|
|
4
3
|
export interface CallArgs {
|
|
5
4
|
path?: Record<string, string | number>;
|
|
6
5
|
query?: Record<string, string | number | boolean>;
|
|
7
6
|
body?: unknown;
|
|
8
7
|
}
|
|
9
|
-
/** Нормализованный неуспех вызова. */
|
|
10
8
|
export interface NormalizedError {
|
|
11
9
|
status: number;
|
|
12
10
|
message: string;
|
|
13
11
|
hint?: string;
|
|
14
12
|
}
|
|
15
|
-
/** Результат вызова: либо тело ответа, либо нормализованная ошибка. */
|
|
16
13
|
export type CallResult = {
|
|
17
14
|
ok: true;
|
|
18
15
|
status: number;
|
|
@@ -21,29 +18,10 @@ export type CallResult = {
|
|
|
21
18
|
ok: false;
|
|
22
19
|
error: NormalizedError;
|
|
23
20
|
};
|
|
24
|
-
/** Ошибка на стороне вызывающего — до отправки запроса. */
|
|
25
21
|
export declare class RequestBuildError extends Error {
|
|
26
22
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Возвращает тело в том виде, в каком его надо сериализовать в запрос.
|
|
29
|
-
* Часть MCP-клиентов передаёт объектный аргумент уже сериализованным в строку;
|
|
30
|
-
* без этой нормализации `JSON.stringify` завернул бы его второй раз, и на сервер
|
|
31
|
-
* ушла бы JSON-строка вместо объекта — любая запись падала бы с невнятным 400.
|
|
32
|
-
* Разворачивается только строка, которая целиком является объектом или массивом:
|
|
33
|
-
* скалярная строка — законное тело, и трогать её нельзя.
|
|
34
|
-
*/
|
|
35
23
|
export declare const normalizeBody: (body: unknown) => unknown;
|
|
36
|
-
/**
|
|
37
|
-
* Подставляет path-параметры и собирает query-строку.
|
|
38
|
-
* Отсутствующий обязательный path-параметр — ошибка здесь, а не 404 от сервера:
|
|
39
|
-
* иначе агент получает загадочный ответ вместо указания, чего не хватает.
|
|
40
|
-
*/
|
|
41
24
|
export declare const buildUrl: (baseUrl: string, operation: Operation, args: CallArgs) => string;
|
|
42
|
-
/**
|
|
43
|
-
* HTTP-клиент Admin API.
|
|
44
|
-
* Единственный ретрай — на 401 после обновления токена: любой другой повтор
|
|
45
|
-
* на изменяющей операции рискует создать дубль, поэтому его здесь нет.
|
|
46
|
-
*/
|
|
47
25
|
export declare class AdminApiClient {
|
|
48
26
|
private readonly baseUrl;
|
|
49
27
|
private readonly tokens;
|
|
@@ -54,7 +32,5 @@ export declare class AdminApiClient {
|
|
|
54
32
|
timeoutMs: number;
|
|
55
33
|
});
|
|
56
34
|
call(operation: Operation, args: CallArgs): Promise<CallResult>;
|
|
57
|
-
/** Читает набор прав админа. Пустой массив означает «не удалось выяснить». */
|
|
58
35
|
fetchPermissions(adminId: number): Promise<string[]>;
|
|
59
36
|
}
|
|
60
|
-
//# sourceMappingURL=client.d.ts.map
|
package/dist/api/client.js
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
/** Ошибка на стороне вызывающего — до отправки запроса. */
|
|
2
1
|
export class RequestBuildError extends Error {
|
|
3
2
|
}
|
|
4
|
-
/**
|
|
5
|
-
* Возвращает тело в том виде, в каком его надо сериализовать в запрос.
|
|
6
|
-
* Часть MCP-клиентов передаёт объектный аргумент уже сериализованным в строку;
|
|
7
|
-
* без этой нормализации `JSON.stringify` завернул бы его второй раз, и на сервер
|
|
8
|
-
* ушла бы JSON-строка вместо объекта — любая запись падала бы с невнятным 400.
|
|
9
|
-
* Разворачивается только строка, которая целиком является объектом или массивом:
|
|
10
|
-
* скалярная строка — законное тело, и трогать её нельзя.
|
|
11
|
-
*/
|
|
12
3
|
export const normalizeBody = (body) => {
|
|
13
4
|
if (typeof body !== 'string') {
|
|
14
5
|
return body;
|
|
@@ -25,11 +16,6 @@ export const normalizeBody = (body) => {
|
|
|
25
16
|
return body;
|
|
26
17
|
}
|
|
27
18
|
};
|
|
28
|
-
/**
|
|
29
|
-
* Подставляет path-параметры и собирает query-строку.
|
|
30
|
-
* Отсутствующий обязательный path-параметр — ошибка здесь, а не 404 от сервера:
|
|
31
|
-
* иначе агент получает загадочный ответ вместо указания, чего не хватает.
|
|
32
|
-
*/
|
|
33
19
|
export const buildUrl = (baseUrl, operation, args) => {
|
|
34
20
|
let path = operation.path;
|
|
35
21
|
const provided = args.path ?? {};
|
|
@@ -47,7 +33,7 @@ export const buildUrl = (baseUrl, operation, args) => {
|
|
|
47
33
|
const leftover = /\{([^}]+)\}/.exec(path);
|
|
48
34
|
if (leftover) {
|
|
49
35
|
throw new RequestBuildError(`Path parameter "${leftover[1] ?? ''}" of ${operation.path} was not provided and is not ` +
|
|
50
|
-
'declared in the catalog — the
|
|
36
|
+
'declared in the catalog — the cached API document may be stale.');
|
|
51
37
|
}
|
|
52
38
|
const search = new URLSearchParams();
|
|
53
39
|
for (const [key, value] of Object.entries(args.query ?? {})) {
|
|
@@ -58,23 +44,22 @@ export const buildUrl = (baseUrl, operation, args) => {
|
|
|
58
44
|
const suffix = search.size > 0 ? `?${search.toString()}` : '';
|
|
59
45
|
return `${baseUrl}${path}${suffix}`;
|
|
60
46
|
};
|
|
61
|
-
/** Подбирает подсказку по коду ответа и метаданным операции. */
|
|
62
47
|
const hintFor = (status, operation) => {
|
|
63
48
|
if (status === 400 || status === 422) {
|
|
64
49
|
const loose = operation.body?.schema['x-loose'] === true;
|
|
65
50
|
return (`Validation rejected the payload. ${loose ? 'This body has loosely typed fields — copy the shape from the example in cms_api_describe. ' : ''}` +
|
|
66
|
-
|
|
51
|
+
'Search the knowledge base with cms_docs_search before retrying.');
|
|
67
52
|
}
|
|
68
53
|
if (status === 403) {
|
|
69
54
|
return operation.permission
|
|
70
55
|
? `The admin lacks "${operation.permission}". Ask for the grant; retrying will not help.`
|
|
71
|
-
: 'Forbidden. Check the admin\'s permissions and the module visibility rules
|
|
56
|
+
: 'Forbidden. Check the admin\'s permissions and the module visibility rules.';
|
|
72
57
|
}
|
|
73
58
|
if (status === 404) {
|
|
74
|
-
return 'Not found. The id may belong to another
|
|
59
|
+
return 'Not found. The id may belong to another instance — prefer marker-based operations where they exist.';
|
|
75
60
|
}
|
|
76
61
|
if (status >= 500) {
|
|
77
|
-
return 'Server-side failure
|
|
62
|
+
return 'Server-side failure on the instance. Report it to your operator; do not retry blindly.';
|
|
78
63
|
}
|
|
79
64
|
return undefined;
|
|
80
65
|
};
|
|
@@ -105,11 +90,6 @@ const extractMessage = (body, fallback) => {
|
|
|
105
90
|
}
|
|
106
91
|
return fallback;
|
|
107
92
|
};
|
|
108
|
-
/**
|
|
109
|
-
* HTTP-клиент Admin API.
|
|
110
|
-
* Единственный ретрай — на 401 после обновления токена: любой другой повтор
|
|
111
|
-
* на изменяющей операции рискует создать дубль, поэтому его здесь нет.
|
|
112
|
-
*/
|
|
113
93
|
export class AdminApiClient {
|
|
114
94
|
baseUrl;
|
|
115
95
|
tokens;
|
|
@@ -148,7 +128,7 @@ export class AdminApiClient {
|
|
|
148
128
|
error: {
|
|
149
129
|
status: 0,
|
|
150
130
|
message: `Request to ${url} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
151
|
-
hint: 'Is the
|
|
131
|
+
hint: 'Is the instance reachable at the configured base URL? It must expose the Admin API under /api/admin.',
|
|
152
132
|
},
|
|
153
133
|
};
|
|
154
134
|
}
|
|
@@ -166,7 +146,6 @@ export class AdminApiClient {
|
|
|
166
146
|
}
|
|
167
147
|
return { ok: true, status: response.status, body };
|
|
168
148
|
}
|
|
169
|
-
/** Читает набор прав админа. Пустой массив означает «не удалось выяснить». */
|
|
170
149
|
async fetchPermissions(adminId) {
|
|
171
150
|
try {
|
|
172
151
|
const token = await this.tokens.accessToken();
|
|
@@ -193,4 +172,3 @@ export class AdminApiClient {
|
|
|
193
172
|
}
|
|
194
173
|
}
|
|
195
174
|
}
|
|
196
|
-
//# sourceMappingURL=client.js.map
|
|
@@ -1,14 +1,3 @@
|
|
|
1
1
|
import type { JsonSchema } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Разворачивает `$ref` и нормализует типы за один проход.
|
|
4
|
-
* Циклы (а они есть: сущности ссылаются друг на друга) обрываются заглушкой,
|
|
5
|
-
* иначе разворачивание не завершится; глубина ограничена, чтобы схема осталась читаемой.
|
|
6
|
-
*/
|
|
7
2
|
export declare const normalizeSchema: (node: unknown, components: Record<string, unknown>, depth?: number, refStack?: readonly string[]) => JsonSchema;
|
|
8
|
-
/**
|
|
9
|
-
* Урезает слишком большую схему до верхнего уровня свойств.
|
|
10
|
-
* Схема тела запроса уходит прямо в контекст модели, и развёрнутый DTO
|
|
11
|
-
* на 40 КБ вытеснит из него саму задачу.
|
|
12
|
-
*/
|
|
13
3
|
export declare const capSchema: (schema: JsonSchema) => JsonSchema;
|
|
14
|
-
//# sourceMappingURL=normalize-schema.d.ts.map
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
/** Валидные типы JSON Schema. Всё остальное в этом swagger — TypeScript-выражения. */
|
|
2
1
|
const VALID_TYPES = new Set(['object', 'array', 'string', 'number', 'integer', 'boolean', 'null']);
|
|
3
|
-
/**
|
|
4
|
-
* Приведение TypeScript-подобных типов из swagger к JSON Schema.
|
|
5
|
-
* Проект генерирует схемы из декораторов NestJS, и `type` часто содержит
|
|
6
|
-
* TS-выражение; таблица покрывает частые случаи, остальное помечается x-loose.
|
|
7
|
-
*/
|
|
8
2
|
const TYPE_MAP = {
|
|
9
3
|
date: { type: 'string', format: 'date-time' },
|
|
10
4
|
Date: { type: 'string', format: 'date-time' },
|
|
@@ -30,11 +24,6 @@ const trimDescription = (value) => {
|
|
|
30
24
|
return flat.length > MAX_DESCRIPTION ? `${flat.slice(0, MAX_DESCRIPTION)}…` : flat;
|
|
31
25
|
};
|
|
32
26
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
-
/**
|
|
34
|
-
* Разворачивает `$ref` и нормализует типы за один проход.
|
|
35
|
-
* Циклы (а они есть: сущности ссылаются друг на друга) обрываются заглушкой,
|
|
36
|
-
* иначе разворачивание не завершится; глубина ограничена, чтобы схема осталась читаемой.
|
|
37
|
-
*/
|
|
38
27
|
export const normalizeSchema = (node, components, depth = 0, refStack = []) => {
|
|
39
28
|
if (!isRecord(node)) {
|
|
40
29
|
return {};
|
|
@@ -51,7 +40,6 @@ export const normalizeSchema = (node, components, depth = 0, refStack = []) => {
|
|
|
51
40
|
}
|
|
52
41
|
return normalizeSchema(target, components, depth, [...refStack, name]);
|
|
53
42
|
}
|
|
54
|
-
/** allOf/oneOf/anyOf в этом swagger встречаются только как обёртки — берём первый член. */
|
|
55
43
|
for (const key of ['allOf', 'oneOf', 'anyOf']) {
|
|
56
44
|
const variants = node[key];
|
|
57
45
|
if (Array.isArray(variants) && variants.length > 0) {
|
|
@@ -123,11 +111,6 @@ export const normalizeSchema = (node, components, depth = 0, refStack = []) => {
|
|
|
123
111
|
return out;
|
|
124
112
|
};
|
|
125
113
|
const MAX_SCHEMA_BYTES = 6_144;
|
|
126
|
-
/**
|
|
127
|
-
* Урезает слишком большую схему до верхнего уровня свойств.
|
|
128
|
-
* Схема тела запроса уходит прямо в контекст модели, и развёрнутый DTO
|
|
129
|
-
* на 40 КБ вытеснит из него саму задачу.
|
|
130
|
-
*/
|
|
131
114
|
export const capSchema = (schema) => {
|
|
132
115
|
if (Buffer.byteLength(JSON.stringify(schema), 'utf8') <= MAX_SCHEMA_BYTES) {
|
|
133
116
|
return schema;
|
|
@@ -148,4 +131,3 @@ export const capSchema = (schema) => {
|
|
|
148
131
|
}
|
|
149
132
|
return { ...schema, properties: shallow, 'x-truncated': true };
|
|
150
133
|
};
|
|
151
|
-
//# sourceMappingURL=normalize-schema.js.map
|
package/dist/api/policy.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { AllowLevel } from '../config/config.js';
|
|
2
2
|
import type { AdminIdentity } from './auth.js';
|
|
3
3
|
import type { Operation } from './types.js';
|
|
4
|
-
/** Результат проверки политики перед выполнением операции. */
|
|
5
4
|
export type PolicyDecision = {
|
|
6
5
|
kind: 'allow';
|
|
7
6
|
} | {
|
|
@@ -11,42 +10,20 @@ export type PolicyDecision = {
|
|
|
11
10
|
kind: 'needsConfirm';
|
|
12
11
|
reason: string;
|
|
13
12
|
};
|
|
14
|
-
/**
|
|
15
|
-
* Одноразовые токены подтверждения для необратимых операций.
|
|
16
|
-
* Токен привязан к хэшу (opId + аргументы), поэтому подтверждение одного удаления
|
|
17
|
-
* нельзя переиспользовать для другого — иначе двухшаговость была бы декоративной.
|
|
18
|
-
*/
|
|
19
13
|
export declare class ConfirmStore {
|
|
20
14
|
private readonly issued;
|
|
21
15
|
private static hash;
|
|
22
16
|
issue(opId: string, args: unknown, now?: number): string;
|
|
23
|
-
/**
|
|
24
|
-
* Проверяет токен, не гася его.
|
|
25
|
-
* Нужна, чтобы отказ по другой причине (нет права, не тот уровень allow)
|
|
26
|
-
* не сжигал подтверждение, которое человек уже дал.
|
|
27
|
-
*/
|
|
28
17
|
verify(token: string, opId: string, args: unknown, now?: number): boolean;
|
|
29
|
-
/** Проверяет и немедленно гасит токен. Повторное использование невозможно. */
|
|
30
18
|
consume(token: string, opId: string, args: unknown, now?: number): boolean;
|
|
31
19
|
private sweep;
|
|
32
20
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Проверяет только уровень `--allow`.
|
|
35
|
-
* Вынесено отдельно, чтобы вызывающий мог отказать до аутентификации:
|
|
36
|
-
* обещание «no request was sent» не должно нарушаться самим логином.
|
|
37
|
-
*/
|
|
38
21
|
export declare const checkLevel: (operation: Operation, allow: AllowLevel) => Extract<PolicyDecision, {
|
|
39
22
|
kind: "deny";
|
|
40
23
|
}> | undefined;
|
|
41
|
-
/**
|
|
42
|
-
* Решает, можно ли выполнить операцию.
|
|
43
|
-
* Порядок проверок значим: уровень доступа — до прав, права — до подтверждения,
|
|
44
|
-
* чтобы read-only сервер вообще не выдавал токенов подтверждения.
|
|
45
|
-
*/
|
|
46
24
|
export declare const decide: (params: {
|
|
47
25
|
operation: Operation;
|
|
48
26
|
allow: AllowLevel;
|
|
49
27
|
identity?: AdminIdentity;
|
|
50
28
|
confirmValid: boolean;
|
|
51
29
|
}) => PolicyDecision;
|
|
52
|
-
//# sourceMappingURL=policy.d.ts.map
|
package/dist/api/policy.js
CHANGED
|
Binary file
|
package/dist/api/shape.d.ts
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
/** Результат подготовки ответа к отдаче модели. */
|
|
2
1
|
export interface ShapedResponse {
|
|
3
2
|
body: unknown;
|
|
4
3
|
truncated: boolean;
|
|
5
4
|
}
|
|
6
|
-
/**
|
|
7
|
-
* Приводит ответ Admin API к размеру, пригодному для контекста модели.
|
|
8
|
-
* Массивы урезаются по элементам (а не обрывом JSON), поэтому результат
|
|
9
|
-
* остаётся валидным и самоописанным: агент видит `_truncated` и сужает запрос.
|
|
10
|
-
*/
|
|
11
5
|
export declare const shapeResponse: (raw: unknown, maxBytes: number) => ShapedResponse;
|
|
12
|
-
/**
|
|
13
|
-
* Сжимает текущее состояние цели до размера, который не топит ответ dry-run.
|
|
14
|
-
* Целиком отдаётся только небольшая цель: для создания в коллекции «цель» — это весь
|
|
15
|
-
* список существующих объектов, а он бывает в сотни килобайт и вытесняет собой
|
|
16
|
-
* единственно нужные части плана — политику и собранный запрос.
|
|
17
|
-
*/
|
|
18
6
|
export declare const summarizeTarget: (target: unknown) => unknown;
|
|
19
|
-
//# sourceMappingURL=shape.d.ts.map
|
package/dist/api/shape.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
/** Поля, значения которых бесполезны модели и раздувают ответ. */
|
|
2
1
|
const BLOB_KEYS = new Set(['base64', 'buffer', 'content', 'fileContent', 'data64', 'blob']);
|
|
3
2
|
const BLOB_VALUE_LIMIT = 512;
|
|
4
3
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
-
/** Похоже ли строковое значение на base64-блоб, который незачем показывать. */
|
|
6
4
|
const looksLikeBlob = (key, value) => typeof value === 'string' &&
|
|
7
5
|
value.length > BLOB_VALUE_LIMIT &&
|
|
8
6
|
(BLOB_KEYS.has(key) || /^data:|^[A-Za-z0-9+/=]{512,}$/.test(value));
|
|
9
|
-
/** Рекурсивно заменяет блобы заглушкой с указанием исходного размера. */
|
|
10
7
|
const stripBlobs = (value, key = '') => {
|
|
11
8
|
if (looksLikeBlob(key, value)) {
|
|
12
9
|
return `[stripped ${String(value.length)} chars]`;
|
|
@@ -23,18 +20,12 @@ const stripBlobs = (value, key = '') => {
|
|
|
23
20
|
}
|
|
24
21
|
return value;
|
|
25
22
|
};
|
|
26
|
-
/**
|
|
27
|
-
* Приводит ответ Admin API к размеру, пригодному для контекста модели.
|
|
28
|
-
* Массивы урезаются по элементам (а не обрывом JSON), поэтому результат
|
|
29
|
-
* остаётся валидным и самоописанным: агент видит `_truncated` и сужает запрос.
|
|
30
|
-
*/
|
|
31
23
|
export const shapeResponse = (raw, maxBytes) => {
|
|
32
24
|
const cleaned = stripBlobs(raw);
|
|
33
25
|
const size = (value) => Buffer.byteLength(JSON.stringify(value) ?? '', 'utf8');
|
|
34
26
|
if (size(cleaned) <= maxBytes) {
|
|
35
27
|
return { body: cleaned, truncated: false };
|
|
36
28
|
}
|
|
37
|
-
/** Массив верхнего уровня: оставляем префикс элементов. */
|
|
38
29
|
if (Array.isArray(cleaned)) {
|
|
39
30
|
const kept = takePrefix(cleaned, maxBytes);
|
|
40
31
|
return {
|
|
@@ -49,7 +40,6 @@ export const shapeResponse = (raw, maxBytes) => {
|
|
|
49
40
|
truncated: true,
|
|
50
41
|
};
|
|
51
42
|
}
|
|
52
|
-
/** Пагинированный объект: урезаем самый большой массив внутри него. */
|
|
53
43
|
if (isRecord(cleaned)) {
|
|
54
44
|
const arrayKey = Object.entries(cleaned)
|
|
55
45
|
.filter(([, value]) => Array.isArray(value))
|
|
@@ -83,11 +73,8 @@ export const shapeResponse = (raw, maxBytes) => {
|
|
|
83
73
|
truncated: true,
|
|
84
74
|
};
|
|
85
75
|
};
|
|
86
|
-
/** Бюджет на показ текущего состояния цели в плане dry-run и в запросе подтверждения. */
|
|
87
76
|
const TARGET_LIMIT = 4_000;
|
|
88
|
-
/** Сколько идентификаторов списка показывать в сводке. */
|
|
89
77
|
const TARGET_ID_SAMPLE = 20;
|
|
90
|
-
/** Собирает опознавательные значения элементов списка: id, если он есть, иначе сам элемент. */
|
|
91
78
|
const identify = (items) => items.slice(0, TARGET_ID_SAMPLE).map((item) => {
|
|
92
79
|
if (!isRecord(item)) {
|
|
93
80
|
return item;
|
|
@@ -95,12 +82,6 @@ const identify = (items) => items.slice(0, TARGET_ID_SAMPLE).map((item) => {
|
|
|
95
82
|
const id = item['id'] ?? item['identifier'] ?? item['marker'];
|
|
96
83
|
return id ?? Object.keys(item).slice(0, 3);
|
|
97
84
|
});
|
|
98
|
-
/**
|
|
99
|
-
* Сжимает текущее состояние цели до размера, который не топит ответ dry-run.
|
|
100
|
-
* Целиком отдаётся только небольшая цель: для создания в коллекции «цель» — это весь
|
|
101
|
-
* список существующих объектов, а он бывает в сотни килобайт и вытесняет собой
|
|
102
|
-
* единственно нужные части плана — политику и собранный запрос.
|
|
103
|
-
*/
|
|
104
85
|
export const summarizeTarget = (target) => {
|
|
105
86
|
if (target === undefined || target === null) {
|
|
106
87
|
return null;
|
|
@@ -134,7 +115,6 @@ export const summarizeTarget = (target) => {
|
|
|
134
115
|
}
|
|
135
116
|
return { _summary: { kind: 'value', preview: String(target).slice(0, 200), hint } };
|
|
136
117
|
};
|
|
137
|
-
/** Берёт максимальный префикс массива, укладывающийся в бюджет байтов. */
|
|
138
118
|
const takePrefix = (items, maxBytes) => {
|
|
139
119
|
const kept = [];
|
|
140
120
|
let used = 2;
|
|
@@ -148,4 +128,3 @@ const takePrefix = (items, maxBytes) => {
|
|
|
148
128
|
}
|
|
149
129
|
return kept;
|
|
150
130
|
};
|
|
151
|
-
//# sourceMappingURL=shape.js.map
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
export type SwaggerVariant = 'full' | 'node';
|
|
2
|
+
export interface SwaggerAttempt {
|
|
3
|
+
variant: SwaggerVariant;
|
|
4
|
+
url: string;
|
|
5
|
+
authenticated: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const nestSwaggerUrl: (baseUrl: string) => string;
|
|
8
|
+
export declare const nodeSwaggerUrl: (baseUrl: string) => string;
|
|
9
|
+
export declare const swaggerAttempts: (baseUrl: string) => SwaggerAttempt[];
|
|
2
10
|
export interface SwaggerSource {
|
|
3
|
-
/** Базовый URL Admin API, уже нормализованный (с `/api/admin`, без слэша на конце). */
|
|
4
11
|
baseUrl: string;
|
|
5
12
|
cacheDir: string;
|
|
6
13
|
timeoutMs: number;
|
|
7
|
-
/** Bearer-токен: нужен, если стенд закрывает этот эндпоинт авторизацией. */
|
|
8
14
|
bearer?: string;
|
|
9
15
|
}
|
|
10
|
-
/** Полученный документ и то, откуда он взялся. */
|
|
11
16
|
export interface FetchedSwagger {
|
|
12
17
|
raw: string;
|
|
13
|
-
origin: '
|
|
18
|
+
origin: 'instance-full' | 'instance-node' | 'cache';
|
|
19
|
+
url: string;
|
|
20
|
+
variant: SwaggerVariant;
|
|
21
|
+
failures: string[];
|
|
14
22
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Забирает swagger со стенда, с откатом на кэш.
|
|
17
|
-
* Эндпоинт публичный на большинстве стендов, поэтому первый запрос идёт без
|
|
18
|
-
* авторизации: в remote-режиме на момент старта процесса учётных данных может
|
|
19
|
-
* не быть вовсе. Токен добавляется только если стенд ответил 401/403.
|
|
20
|
-
*/
|
|
21
23
|
export declare const fetchSwagger: (source: SwaggerSource) => Promise<FetchedSwagger>;
|
|
22
|
-
//# sourceMappingURL=swagger-source.d.ts.map
|