@airmcp-dev/core 0.1.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/LICENSE +17 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +11 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +5 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +4 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +59 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +5 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/request-context.d.ts +4 -0
- package/dist/context/request-context.d.ts.map +1 -0
- package/dist/context/request-context.js +14 -0
- package/dist/context/request-context.js.map +1 -0
- package/dist/context/server-context.d.ts +12 -0
- package/dist/context/server-context.d.ts.map +1 -0
- package/dist/context/server-context.js +25 -0
- package/dist/context/server-context.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/after-hook.d.ts +4 -0
- package/dist/middleware/after-hook.d.ts.map +1 -0
- package/dist/middleware/after-hook.js +18 -0
- package/dist/middleware/after-hook.js.map +1 -0
- package/dist/middleware/before-hook.d.ts +4 -0
- package/dist/middleware/before-hook.d.ts.map +1 -0
- package/dist/middleware/before-hook.js +40 -0
- package/dist/middleware/before-hook.js.map +1 -0
- package/dist/middleware/chain.d.ts +11 -0
- package/dist/middleware/chain.d.ts.map +1 -0
- package/dist/middleware/chain.js +84 -0
- package/dist/middleware/chain.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +27 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +63 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +9 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/meter-middleware.d.ts +19 -0
- package/dist/middleware/meter-middleware.d.ts.map +1 -0
- package/dist/middleware/meter-middleware.js +139 -0
- package/dist/middleware/meter-middleware.js.map +1 -0
- package/dist/middleware/shield-middleware.d.ts +16 -0
- package/dist/middleware/shield-middleware.d.ts.map +1 -0
- package/dist/middleware/shield-middleware.js +163 -0
- package/dist/middleware/shield-middleware.js.map +1 -0
- package/dist/plugin/builtin/auth.d.ts +16 -0
- package/dist/plugin/builtin/auth.d.ts.map +1 -0
- package/dist/plugin/builtin/auth.js +60 -0
- package/dist/plugin/builtin/auth.js.map +1 -0
- package/dist/plugin/builtin/cache.d.ts +12 -0
- package/dist/plugin/builtin/cache.d.ts.map +1 -0
- package/dist/plugin/builtin/cache.js +65 -0
- package/dist/plugin/builtin/cache.js.map +1 -0
- package/dist/plugin/builtin/circuit-breaker.d.ts +12 -0
- package/dist/plugin/builtin/circuit-breaker.d.ts.map +1 -0
- package/dist/plugin/builtin/circuit-breaker.js +76 -0
- package/dist/plugin/builtin/circuit-breaker.js.map +1 -0
- package/dist/plugin/builtin/cors.d.ts +12 -0
- package/dist/plugin/builtin/cors.d.ts.map +1 -0
- package/dist/plugin/builtin/cors.js +29 -0
- package/dist/plugin/builtin/cors.js.map +1 -0
- package/dist/plugin/builtin/dedup.d.ts +8 -0
- package/dist/plugin/builtin/dedup.d.ts.map +1 -0
- package/dist/plugin/builtin/dedup.js +64 -0
- package/dist/plugin/builtin/dedup.js.map +1 -0
- package/dist/plugin/builtin/dryrun.d.ts +12 -0
- package/dist/plugin/builtin/dryrun.d.ts.map +1 -0
- package/dist/plugin/builtin/dryrun.js +61 -0
- package/dist/plugin/builtin/dryrun.js.map +1 -0
- package/dist/plugin/builtin/fallback.d.ts +5 -0
- package/dist/plugin/builtin/fallback.d.ts.map +1 -0
- package/dist/plugin/builtin/fallback.js +42 -0
- package/dist/plugin/builtin/fallback.js.map +1 -0
- package/dist/plugin/builtin/i18n.d.ts +12 -0
- package/dist/plugin/builtin/i18n.d.ts.map +1 -0
- package/dist/plugin/builtin/i18n.js +51 -0
- package/dist/plugin/builtin/i18n.js.map +1 -0
- package/dist/plugin/builtin/logger-json.d.ts +12 -0
- package/dist/plugin/builtin/logger-json.d.ts.map +1 -0
- package/dist/plugin/builtin/logger-json.js +52 -0
- package/dist/plugin/builtin/logger-json.js.map +1 -0
- package/dist/plugin/builtin/logger.d.ts +3 -0
- package/dist/plugin/builtin/logger.d.ts.map +1 -0
- package/dist/plugin/builtin/logger.js +22 -0
- package/dist/plugin/builtin/logger.js.map +1 -0
- package/dist/plugin/builtin/metrics.d.ts +14 -0
- package/dist/plugin/builtin/metrics.d.ts.map +1 -0
- package/dist/plugin/builtin/metrics.js +56 -0
- package/dist/plugin/builtin/metrics.js.map +1 -0
- package/dist/plugin/builtin/queue.d.ts +12 -0
- package/dist/plugin/builtin/queue.d.ts.map +1 -0
- package/dist/plugin/builtin/queue.js +98 -0
- package/dist/plugin/builtin/queue.js.map +1 -0
- package/dist/plugin/builtin/ratelimit-per-user.d.ts +12 -0
- package/dist/plugin/builtin/ratelimit-per-user.d.ts.map +1 -0
- package/dist/plugin/builtin/ratelimit-per-user.js +45 -0
- package/dist/plugin/builtin/ratelimit-per-user.js.map +1 -0
- package/dist/plugin/builtin/retry.d.ts +12 -0
- package/dist/plugin/builtin/retry.d.ts.map +1 -0
- package/dist/plugin/builtin/retry.js +51 -0
- package/dist/plugin/builtin/retry.js.map +1 -0
- package/dist/plugin/builtin/sanitizer.d.ts +14 -0
- package/dist/plugin/builtin/sanitizer.d.ts.map +1 -0
- package/dist/plugin/builtin/sanitizer.js +55 -0
- package/dist/plugin/builtin/sanitizer.js.map +1 -0
- package/dist/plugin/builtin/timeout.d.ts +3 -0
- package/dist/plugin/builtin/timeout.d.ts.map +1 -0
- package/dist/plugin/builtin/timeout.js +30 -0
- package/dist/plugin/builtin/timeout.js.map +1 -0
- package/dist/plugin/builtin/transform.d.ts +11 -0
- package/dist/plugin/builtin/transform.d.ts.map +1 -0
- package/dist/plugin/builtin/transform.js +51 -0
- package/dist/plugin/builtin/transform.js.map +1 -0
- package/dist/plugin/builtin/validator.d.ts +9 -0
- package/dist/plugin/builtin/validator.d.ts.map +1 -0
- package/dist/plugin/builtin/validator.js +43 -0
- package/dist/plugin/builtin/validator.js.map +1 -0
- package/dist/plugin/builtin/webhook.d.ts +16 -0
- package/dist/plugin/builtin/webhook.d.ts.map +1 -0
- package/dist/plugin/builtin/webhook.js +78 -0
- package/dist/plugin/builtin/webhook.js.map +1 -0
- package/dist/plugin/index.d.ts +23 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +34 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/plugin-hook.d.ts +11 -0
- package/dist/plugin/plugin-hook.d.ts.map +1 -0
- package/dist/plugin/plugin-hook.js +39 -0
- package/dist/plugin/plugin-hook.js.map +1 -0
- package/dist/plugin/plugin-manager.d.ts +22 -0
- package/dist/plugin/plugin-manager.d.ts.map +1 -0
- package/dist/plugin/plugin-manager.js +71 -0
- package/dist/plugin/plugin-manager.js.map +1 -0
- package/dist/plugin/plugin-schema.d.ts +6 -0
- package/dist/plugin/plugin-schema.d.ts.map +1 -0
- package/dist/plugin/plugin-schema.js +18 -0
- package/dist/plugin/plugin-schema.js.map +1 -0
- package/dist/prompt/define-prompt.d.ts +3 -0
- package/dist/prompt/define-prompt.d.ts.map +1 -0
- package/dist/prompt/define-prompt.js +17 -0
- package/dist/prompt/define-prompt.js.map +1 -0
- package/dist/prompt/index.d.ts +2 -0
- package/dist/prompt/index.d.ts.map +1 -0
- package/dist/prompt/index.js +4 -0
- package/dist/prompt/index.js.map +1 -0
- package/dist/resource/define-resource.d.ts +3 -0
- package/dist/resource/define-resource.d.ts.map +1 -0
- package/dist/resource/define-resource.js +19 -0
- package/dist/resource/define-resource.js.map +1 -0
- package/dist/resource/index.d.ts +3 -0
- package/dist/resource/index.d.ts.map +1 -0
- package/dist/resource/index.js +5 -0
- package/dist/resource/index.js.map +1 -0
- package/dist/resource/resource-template.d.ts +5 -0
- package/dist/resource/resource-template.d.ts.map +1 -0
- package/dist/resource/resource-template.js +17 -0
- package/dist/resource/resource-template.js.map +1 -0
- package/dist/server/define-server.d.ts +14 -0
- package/dist/server/define-server.d.ts.map +1 -0
- package/dist/server/define-server.js +175 -0
- package/dist/server/define-server.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +6 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lifecycle.d.ts +3 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/lifecycle.js +32 -0
- package/dist/server/lifecycle.js.map +1 -0
- package/dist/server/server-runner.d.ts +42 -0
- package/dist/server/server-runner.d.ts.map +1 -0
- package/dist/server/server-runner.js +255 -0
- package/dist/server/server-runner.js.map +1 -0
- package/dist/storage/file-store.d.ts +25 -0
- package/dist/storage/file-store.d.ts.map +1 -0
- package/dist/storage/file-store.js +177 -0
- package/dist/storage/file-store.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory-store.d.ts +18 -0
- package/dist/storage/memory-store.d.ts.map +1 -0
- package/dist/storage/memory-store.js +80 -0
- package/dist/storage/memory-store.js.map +1 -0
- package/dist/storage/storage-adapter.d.ts +4 -0
- package/dist/storage/storage-adapter.d.ts.map +1 -0
- package/dist/storage/storage-adapter.js +32 -0
- package/dist/storage/storage-adapter.js.map +1 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +56 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +95 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/tool/define-tool.d.ts +23 -0
- package/dist/tool/define-tool.d.ts.map +1 -0
- package/dist/tool/define-tool.js +28 -0
- package/dist/tool/define-tool.js.map +1 -0
- package/dist/tool/index.d.ts +5 -0
- package/dist/tool/index.d.ts.map +1 -0
- package/dist/tool/index.js +6 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/tool-result.d.ts +10 -0
- package/dist/tool/tool-result.d.ts.map +1 -0
- package/dist/tool/tool-result.js +34 -0
- package/dist/tool/tool-result.js.map +1 -0
- package/dist/tool/tool-schema.d.ts +7 -0
- package/dist/tool/tool-schema.d.ts.map +1 -0
- package/dist/tool/tool-schema.js +97 -0
- package/dist/tool/tool-schema.js.map +1 -0
- package/dist/transport/auto-detect.d.ts +4 -0
- package/dist/transport/auto-detect.d.ts.map +1 -0
- package/dist/transport/auto-detect.js +21 -0
- package/dist/transport/auto-detect.js.map +1 -0
- package/dist/transport/http-adapter.d.ts +4 -0
- package/dist/transport/http-adapter.d.ts.map +1 -0
- package/dist/transport/http-adapter.js +10 -0
- package/dist/transport/http-adapter.js.map +1 -0
- package/dist/transport/index.d.ts +5 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +7 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/sse-adapter.d.ts +3 -0
- package/dist/transport/sse-adapter.d.ts.map +1 -0
- package/dist/transport/sse-adapter.js +8 -0
- package/dist/transport/sse-adapter.js.map +1 -0
- package/dist/transport/stdio-adapter.d.ts +3 -0
- package/dist/transport/stdio-adapter.d.ts.map +1 -0
- package/dist/transport/stdio-adapter.js +8 -0
- package/dist/transport/stdio-adapter.js.map +1 -0
- package/dist/types/config.d.ts +101 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +5 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/middleware.d.ts +48 -0
- package/dist/types/middleware.d.ts.map +1 -0
- package/dist/types/middleware.js +4 -0
- package/dist/types/middleware.js.map +1 -0
- package/dist/types/plugin-manifest.d.ts +59 -0
- package/dist/types/plugin-manifest.d.ts.map +1 -0
- package/dist/types/plugin-manifest.js +9 -0
- package/dist/types/plugin-manifest.js.map +1 -0
- package/dist/types/plugin.d.ts +50 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +4 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/types/prompt.d.ts +21 -0
- package/dist/types/prompt.d.ts.map +1 -0
- package/dist/types/prompt.js +4 -0
- package/dist/types/prompt.js.map +1 -0
- package/dist/types/resource.d.ts +25 -0
- package/dist/types/resource.d.ts.map +1 -0
- package/dist/types/resource.js +4 -0
- package/dist/types/resource.js.map +1 -0
- package/dist/types/server.d.ts +54 -0
- package/dist/types/server.d.ts.map +1 -0
- package/dist/types/server.js +4 -0
- package/dist/types/server.js.map +1 -0
- package/dist/types/storage.d.ts +40 -0
- package/dist/types/storage.d.ts.map +1 -0
- package/dist/types/storage.js +4 -0
- package/dist/types/storage.js.map +1 -0
- package/dist/types/tool.d.ts +54 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/tool.js +4 -0
- package/dist/types/tool.js.map +1 -0
- package/dist/types/transport.d.ts +12 -0
- package/dist/types/transport.d.ts.map +1 -0
- package/dist/types/transport.js +4 -0
- package/dist/types/transport.js.map +1 -0
- package/package.json +23 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/core — middleware/error-handler.ts
|
|
3
|
+
// 에러 → MCP 프로토콜 형식 자동 변환
|
|
4
|
+
//
|
|
5
|
+
// MCP 프로토콜 에러 코드:
|
|
6
|
+
// -32700: Parse error
|
|
7
|
+
// -32600: Invalid Request
|
|
8
|
+
// -32601: Method not found
|
|
9
|
+
// -32602: Invalid params
|
|
10
|
+
// -32603: Internal error
|
|
11
|
+
// -32000 ~ -32099: Server error (implementation-defined)
|
|
12
|
+
/** air 전용 에러 클래스 — 에러 코드와 메타데이터를 포함 */
|
|
13
|
+
export class AirError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
details;
|
|
16
|
+
constructor(message, code = -32603, details) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'AirError';
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.details = details;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** MCP 표준 에러 팩토리 */
|
|
24
|
+
export const McpErrors = {
|
|
25
|
+
/** 도구를 찾을 수 없음 */
|
|
26
|
+
toolNotFound: (name) => new AirError(`Tool "${name}" not found`, -32601, { tool: name }),
|
|
27
|
+
/** 파라미터 검증 실패 */
|
|
28
|
+
invalidParams: (message, params) => new AirError(`Invalid params: ${message}`, -32602, params),
|
|
29
|
+
/** 내부 서버 에러 */
|
|
30
|
+
internal: (message, cause) => new AirError(`Internal error: ${message}`, -32603, { cause: cause?.message }),
|
|
31
|
+
/** 접근 거부 */
|
|
32
|
+
forbidden: (message) => new AirError(message, -32000, { type: 'forbidden' }),
|
|
33
|
+
/** 레이트 리밋 초과 */
|
|
34
|
+
rateLimited: (tool, retryAfterMs) => new AirError(`Rate limit exceeded for "${tool}"`, -32001, { tool, retryAfterMs }),
|
|
35
|
+
/** 위협 감지 */
|
|
36
|
+
threatDetected: (type, severity) => new AirError(`Threat detected: ${type}`, -32002, { threatType: type, severity }),
|
|
37
|
+
/** 타임아웃 */
|
|
38
|
+
timeout: (tool, timeoutMs) => new AirError(`Tool "${tool}" timed out after ${timeoutMs}ms`, -32003, { tool, timeoutMs }),
|
|
39
|
+
};
|
|
40
|
+
/** 에러를 MCP 프로토콜 형식으로 변환 */
|
|
41
|
+
function formatError(error) {
|
|
42
|
+
const message = error instanceof AirError
|
|
43
|
+
? `[${error.code}] ${error.message}`
|
|
44
|
+
: `Error: ${error.message}`;
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: 'text', text: message }],
|
|
47
|
+
isError: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** 에러 → MCP 응답 변환 미들웨어 */
|
|
51
|
+
export function errorBoundaryMiddleware() {
|
|
52
|
+
return {
|
|
53
|
+
name: 'air:error-boundary',
|
|
54
|
+
onError: async (ctx, error) => {
|
|
55
|
+
// 에러 로그 (stderr로 출력, stdout은 MCP 프로토콜 전용)
|
|
56
|
+
const toolName = ctx.tool?.name || 'unknown';
|
|
57
|
+
const requestId = ctx.requestId || 'no-id';
|
|
58
|
+
console.error(`[air:error] ${toolName} (${requestId}): ${error.message}`);
|
|
59
|
+
return formatError(error);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { MiddlewareChain } from './chain.js';
|
|
2
|
+
export { validationMiddleware } from './before-hook.js';
|
|
3
|
+
export { loggingMiddleware } from './after-hook.js';
|
|
4
|
+
export { errorBoundaryMiddleware, AirError, McpErrors } from './error-handler.js';
|
|
5
|
+
export { createShieldMiddleware, getAuditLog, clearAuditLog } from './shield-middleware.js';
|
|
6
|
+
export { createMeterMiddleware, getMetricsSnapshot, resetMetricsHistory } from './meter-middleware.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5F,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/core — middleware/index.ts (re-export only)
|
|
3
|
+
export { MiddlewareChain } from './chain.js';
|
|
4
|
+
export { validationMiddleware } from './before-hook.js';
|
|
5
|
+
export { loggingMiddleware } from './after-hook.js';
|
|
6
|
+
export { errorBoundaryMiddleware, AirError, McpErrors } from './error-handler.js';
|
|
7
|
+
export { createShieldMiddleware, getAuditLog, clearAuditLog } from './shield-middleware.js';
|
|
8
|
+
export { createMeterMiddleware, getMetricsSnapshot, resetMetricsHistory } from './meter-middleware.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,0DAA0D;AAE1D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5F,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AirMiddleware } from '../types/middleware.js';
|
|
2
|
+
import type { MeterConfig } from '../types/config.js';
|
|
3
|
+
type Layer = 'L1' | 'L2' | 'L3' | 'L4' | 'L5' | 'L6' | 'L7';
|
|
4
|
+
/**
|
|
5
|
+
* MeterConfig로부터 측정 미들웨어를 생성한다.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createMeterMiddleware(config: MeterConfig): AirMiddleware;
|
|
8
|
+
/** 메트릭 스냅샷 조회 */
|
|
9
|
+
export declare function getMetricsSnapshot(): {
|
|
10
|
+
totalCalls: number;
|
|
11
|
+
successRate: number;
|
|
12
|
+
avgLatencyMs: number;
|
|
13
|
+
layerDistribution: Record<Layer, number>;
|
|
14
|
+
toolCounts: Record<string, number>;
|
|
15
|
+
};
|
|
16
|
+
/** 메트릭 초기화 */
|
|
17
|
+
export declare function resetMetricsHistory(): void;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=meter-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/meter-middleware.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAuC,MAAM,wBAAwB,CAAC;AACjG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,KAAK,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AA2D5D;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAmDxE;AAED,iBAAiB;AACjB,wBAAgB,kBAAkB,IAAI;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC,CAgCA;AAED,cAAc;AACd,wBAAgB,mBAAmB,SAIlC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/core — middleware/meter-middleware.ts
|
|
3
|
+
//
|
|
4
|
+
// MeterConfig → AirMiddleware 자동 생성.
|
|
5
|
+
// defineServer에서 meter 설정이 있으면 이 미들웨어가 체인에 자동 등록된다.
|
|
6
|
+
//
|
|
7
|
+
// 담당: 7-Layer 분류, 호출 추적, 지연시간 측정
|
|
8
|
+
const CLASSIFY_RULES = [
|
|
9
|
+
{ match: (n) => /^(ping|health|version|echo)$/i.test(n), layer: 'L1' },
|
|
10
|
+
{ match: (n) => /^(get|read|find|lookup|list|show)$/i.test(n), layer: 'L2' },
|
|
11
|
+
{ match: (n) => /^(convert|transform|format|parse|encode|decode)$/i.test(n), layer: 'L3' },
|
|
12
|
+
{ match: (n) => /^(compute|calculate|aggregate|analyze|summarize)$/i.test(n), layer: 'L4' },
|
|
13
|
+
{
|
|
14
|
+
match: (n, p) => {
|
|
15
|
+
if (/^(fetch|request|call_api|webhook|http|post|put|delete)$/i.test(n))
|
|
16
|
+
return true;
|
|
17
|
+
if (p)
|
|
18
|
+
return Object.values(p).some((v) => typeof v === 'string' && /^https?:\/\//.test(v));
|
|
19
|
+
return false;
|
|
20
|
+
},
|
|
21
|
+
layer: 'L5',
|
|
22
|
+
},
|
|
23
|
+
{ match: (n) => /^(generate|complete|chat|embed|infer|predict)$/i.test(n), layer: 'L6' },
|
|
24
|
+
{ match: (n) => /^(agent|think|plan|execute|reason|chain|orchestrate)$/i.test(n), layer: 'L7' },
|
|
25
|
+
];
|
|
26
|
+
function classify(toolName, params) {
|
|
27
|
+
for (const rule of CLASSIFY_RULES) {
|
|
28
|
+
if (rule.match(toolName, params))
|
|
29
|
+
return rule.layer;
|
|
30
|
+
}
|
|
31
|
+
return 'L4'; // 기본값
|
|
32
|
+
}
|
|
33
|
+
const MAX_HISTORY = 10_000;
|
|
34
|
+
const callBuffer = new Array(MAX_HISTORY).fill(null);
|
|
35
|
+
let bufferHead = 0;
|
|
36
|
+
let bufferCount = 0;
|
|
37
|
+
function pushRecord(record) {
|
|
38
|
+
callBuffer[bufferHead] = record;
|
|
39
|
+
bufferHead = (bufferHead + 1) % MAX_HISTORY;
|
|
40
|
+
if (bufferCount < MAX_HISTORY)
|
|
41
|
+
bufferCount++;
|
|
42
|
+
}
|
|
43
|
+
function getRecords() {
|
|
44
|
+
const result = [];
|
|
45
|
+
const start = bufferCount < MAX_HISTORY ? 0 : bufferHead;
|
|
46
|
+
for (let i = 0; i < bufferCount; i++) {
|
|
47
|
+
const idx = (start + i) % MAX_HISTORY;
|
|
48
|
+
if (callBuffer[idx])
|
|
49
|
+
result.push(callBuffer[idx]);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* MeterConfig로부터 측정 미들웨어를 생성한다.
|
|
55
|
+
*/
|
|
56
|
+
export function createMeterMiddleware(config) {
|
|
57
|
+
const classifyEnabled = config.classify !== false;
|
|
58
|
+
const trackEnabled = config.trackCalls !== false;
|
|
59
|
+
return {
|
|
60
|
+
name: 'air:meter',
|
|
61
|
+
before: async (ctx) => {
|
|
62
|
+
// 분류 결과를 meta에 저장 (after에서 사용)
|
|
63
|
+
if (classifyEnabled) {
|
|
64
|
+
const layer = classify(ctx.tool.name, ctx.params);
|
|
65
|
+
ctx.meta.meter = { layer, startedAt: Date.now() };
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
ctx.meta.meter = { layer: 'L4', startedAt: Date.now() };
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
after: async (ctx) => {
|
|
72
|
+
if (!trackEnabled)
|
|
73
|
+
return;
|
|
74
|
+
const layer = ctx.meta.meter?.layer || 'L4';
|
|
75
|
+
const record = {
|
|
76
|
+
tool: ctx.tool.name,
|
|
77
|
+
layer,
|
|
78
|
+
latencyMs: ctx.duration,
|
|
79
|
+
success: true,
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
};
|
|
82
|
+
pushRecord(record);
|
|
83
|
+
},
|
|
84
|
+
onError: async (ctx, error) => {
|
|
85
|
+
if (!trackEnabled)
|
|
86
|
+
return undefined;
|
|
87
|
+
const layer = ctx.meta.meter?.layer || 'L4';
|
|
88
|
+
const latency = Date.now() - (ctx.meta.meter?.startedAt || ctx.startedAt);
|
|
89
|
+
pushRecord({
|
|
90
|
+
tool: ctx.tool.name,
|
|
91
|
+
layer,
|
|
92
|
+
latencyMs: latency,
|
|
93
|
+
success: false,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
});
|
|
96
|
+
// 에러는 처리하지 않고 다음으로 넘김
|
|
97
|
+
return undefined;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** 메트릭 스냅샷 조회 */
|
|
102
|
+
export function getMetricsSnapshot() {
|
|
103
|
+
const records = getRecords();
|
|
104
|
+
const total = records.length;
|
|
105
|
+
if (total === 0) {
|
|
106
|
+
return {
|
|
107
|
+
totalCalls: 0,
|
|
108
|
+
successRate: 1,
|
|
109
|
+
avgLatencyMs: 0,
|
|
110
|
+
layerDistribution: { L1: 0, L2: 0, L3: 0, L4: 0, L5: 0, L6: 0, L7: 0 },
|
|
111
|
+
toolCounts: {},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
let successCount = 0;
|
|
115
|
+
let latencySum = 0;
|
|
116
|
+
const layers = { L1: 0, L2: 0, L3: 0, L4: 0, L5: 0, L6: 0, L7: 0 };
|
|
117
|
+
const tools = {};
|
|
118
|
+
for (const r of records) {
|
|
119
|
+
if (r.success)
|
|
120
|
+
successCount++;
|
|
121
|
+
latencySum += r.latencyMs;
|
|
122
|
+
layers[r.layer]++;
|
|
123
|
+
tools[r.tool] = (tools[r.tool] || 0) + 1;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
totalCalls: total,
|
|
127
|
+
successRate: successCount / total,
|
|
128
|
+
avgLatencyMs: latencySum / total,
|
|
129
|
+
layerDistribution: layers,
|
|
130
|
+
toolCounts: tools,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** 메트릭 초기화 */
|
|
134
|
+
export function resetMetricsHistory() {
|
|
135
|
+
callBuffer.fill(null);
|
|
136
|
+
bufferHead = 0;
|
|
137
|
+
bufferCount = 0;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=meter-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter-middleware.js","sourceRoot":"","sources":["../../src/middleware/meter-middleware.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,oDAAoD;AACpD,EAAE;AACF,qCAAqC;AACrC,oDAAoD;AACpD,EAAE;AACF,iCAAiC;AAQjC,MAAM,cAAc,GAGf;IACH,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;IACtE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,qCAAqC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;IAC5E,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,mDAAmD,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;IAC1F,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,oDAAoD,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;IAC3F;QACE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACd,IAAI,0DAA0D,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YACpF,IAAI,CAAC;gBAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5F,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,EAAE,IAAI;KACZ;IACD,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,iDAAiD,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;IACxF,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,wDAAwD,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE;CAChG,CAAC;AAEF,SAAS,QAAQ,CAAC,QAAgB,EAAE,MAA4B;IAC9D,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC;IACtD,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,MAAM;AACrB,CAAC;AAWD,MAAM,WAAW,GAAG,MAAM,CAAC;AAC3B,MAAM,UAAU,GAA0B,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5E,IAAI,UAAU,GAAG,CAAC,CAAC;AACnB,IAAI,WAAW,GAAG,CAAC,CAAC;AAEpB,SAAS,UAAU,CAAC,MAAkB;IACpC,UAAU,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC;IAChC,UAAU,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC;IAC5C,IAAI,WAAW,GAAG,WAAW;QAAE,WAAW,EAAE,CAAC;AAC/C,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC;QACtC,IAAI,UAAU,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAE,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAmB;IACvD,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC;IAClD,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,KAAK,KAAK,CAAC;IAEjD,OAAO;QACL,IAAI,EAAE,WAAW;QAEjB,MAAM,EAAE,KAAK,EAAE,GAAsB,EAAoC,EAAE;YACzE,+BAA+B;YAC/B,IAAI,eAAe,EAAE,CAAC;gBACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;gBAClD,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACpD,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC1D,CAAC;QACH,CAAC;QAED,KAAK,EAAE,KAAK,EAAE,GAA0D,EAAiB,EAAE;YACzF,IAAI,CAAC,YAAY;gBAAE,OAAO;YAE1B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,IAAI,CAAC;YAE5C,MAAM,MAAM,GAAe;gBACzB,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI;gBACnB,KAAK;gBACL,SAAS,EAAE,GAAG,CAAC,QAAQ;gBACvB,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC;YAEF,UAAU,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,GAAsB,EAAE,KAAY,EAAgB,EAAE;YACpE,IAAI,CAAC,YAAY;gBAAE,OAAO,SAAS,CAAC;YAEpC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,IAAI,CAAC;YAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;YAE1E,UAAU,CAAC;gBACT,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI;gBACnB,KAAK;gBACL,SAAS,EAAE,OAAO;gBAClB,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC,CAAC;YAEH,sBAAsB;YACtB,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,iBAAiB;AACjB,MAAM,UAAU,kBAAkB;IAOhC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IAC7B,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,OAAO;YACL,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;YACf,iBAAiB,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE;YACtE,UAAU,EAAE,EAAE;SACf,CAAC;IACJ,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAA2B,CAAC;IAC5F,MAAM,KAAK,GAA2B,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,OAAO;YAAE,YAAY,EAAE,CAAC;QAC9B,UAAU,IAAI,CAAC,CAAC,SAAS,CAAC;QAC1B,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAClB,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO;QACL,UAAU,EAAE,KAAK;QACjB,WAAW,EAAE,YAAY,GAAG,KAAK;QACjC,YAAY,EAAE,UAAU,GAAG,KAAK;QAChC,iBAAiB,EAAE,MAAM;QACzB,UAAU,EAAE,KAAK;KAClB,CAAC;AACJ,CAAC;AAED,cAAc;AACd,MAAM,UAAU,mBAAmB;IACjC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,UAAU,GAAG,CAAC,CAAC;IACf,WAAW,GAAG,CAAC,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AirMiddleware } from '../types/middleware.js';
|
|
2
|
+
import type { ShieldConfig } from '../types/config.js';
|
|
3
|
+
/**
|
|
4
|
+
* ShieldConfig로부터 보안 미들웨어를 생성한다.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createShieldMiddleware(config: ShieldConfig): AirMiddleware;
|
|
7
|
+
/** 감사 로그 조회 (외부에서 접근용) */
|
|
8
|
+
export declare function getAuditLog(): {
|
|
9
|
+
timestamp: string;
|
|
10
|
+
tool: string;
|
|
11
|
+
decision: "allowed" | "denied" | "threat" | "rate-limited";
|
|
12
|
+
reason?: string;
|
|
13
|
+
}[];
|
|
14
|
+
/** 감사 로그 초기화 */
|
|
15
|
+
export declare function clearAuditLog(): void;
|
|
16
|
+
//# sourceMappingURL=shield-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shield-middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/shield-middleware.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAuC,MAAM,wBAAwB,CAAC;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAyCvD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CA0H1E;AAED,0BAA0B;AAC1B,wBAAgB,WAAW;eAtId,MAAM;UACX,MAAM;cACF,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,cAAc;aACjD,MAAM;IAqIhB;AAED,gBAAgB;AAChB,wBAAgB,aAAa,SAE5B"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/core — middleware/shield-middleware.ts
|
|
3
|
+
//
|
|
4
|
+
// ShieldConfig → AirMiddleware 자동 생성.
|
|
5
|
+
// defineServer에서 shield 설정이 있으면 이 미들웨어가 체인에 자동 등록된다.
|
|
6
|
+
//
|
|
7
|
+
// 담당: 정책 체크, 위협 탐지, 레이트 리밋, 감사 로그
|
|
8
|
+
// ── 내장 위협 패턴 (경량 버전) ──
|
|
9
|
+
const THREAT_PATTERNS = [
|
|
10
|
+
{ pattern: /ignore\s+(all\s+)?previous\s+instructions/i, type: 'prompt-injection', severity: 'high', description: 'Prompt injection: ignore previous instructions' },
|
|
11
|
+
{ pattern: /you\s+are\s+now\s+(a|an)\s+/i, type: 'prompt-injection', severity: 'high', description: 'Prompt injection: role reassignment' },
|
|
12
|
+
{ pattern: /\bsystem\s*:\s*/i, type: 'prompt-injection', severity: 'medium', description: 'Prompt injection: system prompt attempt' },
|
|
13
|
+
{ pattern: /\.\.\//g, type: 'path-traversal', severity: 'high', description: 'Path traversal: directory escape' },
|
|
14
|
+
{ pattern: /\/etc\/(passwd|shadow|hosts)/i, type: 'path-traversal', severity: 'critical', description: 'Path traversal: sensitive system file' },
|
|
15
|
+
{ pattern: /[;&|`$]\s*(rm|chmod|chown|curl|wget|nc|bash|sh|python|node)\b/i, type: 'command-injection', severity: 'critical', description: 'Command injection: shell command' },
|
|
16
|
+
{ pattern: /\$\(.*\)/, type: 'command-injection', severity: 'high', description: 'Command injection: command substitution' },
|
|
17
|
+
{ pattern: /<tool_description>|<function_call>/i, type: 'tool-poisoning', severity: 'high', description: 'Tool poisoning: embedded tool definition' },
|
|
18
|
+
];
|
|
19
|
+
/** 정책 매칭 */
|
|
20
|
+
function matchesTarget(ruleTarget, toolName) {
|
|
21
|
+
if (ruleTarget === '*')
|
|
22
|
+
return true;
|
|
23
|
+
if (ruleTarget === toolName)
|
|
24
|
+
return true;
|
|
25
|
+
if (ruleTarget.endsWith('*') && toolName.startsWith(ruleTarget.slice(0, -1)))
|
|
26
|
+
return true;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/** 파라미터 값들을 재귀적으로 문자열 추출 */
|
|
30
|
+
function flattenValues(obj) {
|
|
31
|
+
if (typeof obj === 'string')
|
|
32
|
+
return [obj];
|
|
33
|
+
if (Array.isArray(obj))
|
|
34
|
+
return obj.flatMap(flattenValues);
|
|
35
|
+
if (obj && typeof obj === 'object')
|
|
36
|
+
return Object.values(obj).flatMap(flattenValues);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
/** 레이트 리밋 윈도우 추적 */
|
|
40
|
+
const rateLimitWindows = new Map();
|
|
41
|
+
/** 감사 로그 저장소 */
|
|
42
|
+
const auditLog = [];
|
|
43
|
+
/**
|
|
44
|
+
* ShieldConfig로부터 보안 미들웨어를 생성한다.
|
|
45
|
+
*/
|
|
46
|
+
export function createShieldMiddleware(config) {
|
|
47
|
+
// 정책 규칙을 우선순위 내림차순 정렬
|
|
48
|
+
const policies = (config.policies || [])
|
|
49
|
+
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
50
|
+
const threatEnabled = config.threatDetection !== false;
|
|
51
|
+
const auditEnabled = config.audit !== false;
|
|
52
|
+
const defaultRateWindow = config.rateLimit?.windowMs || 60_000;
|
|
53
|
+
const defaultRateMax = config.rateLimit?.maxCalls || 100;
|
|
54
|
+
const perToolRate = config.rateLimit?.perTool || {};
|
|
55
|
+
return {
|
|
56
|
+
name: 'air:shield',
|
|
57
|
+
before: async (ctx) => {
|
|
58
|
+
const toolName = ctx.tool.name;
|
|
59
|
+
// ── 1. 정책 체크 ──
|
|
60
|
+
for (const rule of policies) {
|
|
61
|
+
if (!matchesTarget(rule.target, toolName))
|
|
62
|
+
continue;
|
|
63
|
+
if (rule.action === 'deny') {
|
|
64
|
+
if (auditEnabled) {
|
|
65
|
+
auditLog.push({
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
tool: toolName,
|
|
68
|
+
decision: 'denied',
|
|
69
|
+
reason: `Policy: ${rule.name}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
abort: true,
|
|
74
|
+
abortResponse: {
|
|
75
|
+
content: [{ type: 'text', text: `[Shield] Access denied: tool "${toolName}" is blocked by policy "${rule.name}". Contact the server administrator if you believe this is an error.` }],
|
|
76
|
+
isError: true,
|
|
77
|
+
},
|
|
78
|
+
meta: { shield: { decision: 'denied', rule: rule.name, code: -32000 } },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (rule.action === 'allow')
|
|
82
|
+
break; // 허용이면 이후 규칙 스킵
|
|
83
|
+
}
|
|
84
|
+
// ── 2. 위협 탐지 ──
|
|
85
|
+
if (threatEnabled) {
|
|
86
|
+
const values = flattenValues(ctx.params);
|
|
87
|
+
for (const value of values) {
|
|
88
|
+
for (const tp of THREAT_PATTERNS) {
|
|
89
|
+
if (tp.pattern.test(value)) {
|
|
90
|
+
tp.pattern.lastIndex = 0; // global flag reset
|
|
91
|
+
if (auditEnabled) {
|
|
92
|
+
auditLog.push({
|
|
93
|
+
timestamp: new Date().toISOString(),
|
|
94
|
+
tool: toolName,
|
|
95
|
+
decision: 'threat',
|
|
96
|
+
reason: `${tp.type} (${tp.severity})`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
abort: true,
|
|
101
|
+
abortResponse: {
|
|
102
|
+
content: [{ type: 'text', text: `[Shield] Threat detected in "${toolName}": ${tp.description} (severity: ${tp.severity}). The request has been blocked for security.` }],
|
|
103
|
+
isError: true,
|
|
104
|
+
},
|
|
105
|
+
meta: { shield: { decision: 'threat', type: tp.type, severity: tp.severity, code: -32002 } },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
tp.pattern.lastIndex = 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ── 3. 레이트 리밋 ──
|
|
113
|
+
if (config.rateLimit) {
|
|
114
|
+
const toolConfig = perToolRate[toolName];
|
|
115
|
+
const windowMs = toolConfig?.windowMs || defaultRateWindow;
|
|
116
|
+
const maxCalls = toolConfig?.maxCalls || defaultRateMax;
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
const key = toolName;
|
|
119
|
+
const timestamps = rateLimitWindows.get(key) || [];
|
|
120
|
+
// 윈도우 밖의 오래된 타임스탬프 제거
|
|
121
|
+
const valid = timestamps.filter((t) => t > now - windowMs);
|
|
122
|
+
if (valid.length >= maxCalls) {
|
|
123
|
+
if (auditEnabled) {
|
|
124
|
+
auditLog.push({
|
|
125
|
+
timestamp: new Date().toISOString(),
|
|
126
|
+
tool: toolName,
|
|
127
|
+
decision: 'rate-limited',
|
|
128
|
+
reason: `${valid.length}/${maxCalls} in ${windowMs}ms`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
abort: true,
|
|
133
|
+
abortResponse: {
|
|
134
|
+
content: [{ type: 'text', text: `[Shield] Rate limit exceeded for "${toolName}": ${maxCalls} calls per ${windowMs / 1000}s. Please wait and try again.` }],
|
|
135
|
+
isError: true,
|
|
136
|
+
},
|
|
137
|
+
meta: { shield: { decision: 'rate-limited', code: -32001 } },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
valid.push(now);
|
|
141
|
+
rateLimitWindows.set(key, valid);
|
|
142
|
+
}
|
|
143
|
+
// ── 감사 로그: 허용 ──
|
|
144
|
+
if (auditEnabled) {
|
|
145
|
+
auditLog.push({
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
tool: toolName,
|
|
148
|
+
decision: 'allowed',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
ctx.meta.shield = { decision: 'allowed' };
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** 감사 로그 조회 (외부에서 접근용) */
|
|
156
|
+
export function getAuditLog() {
|
|
157
|
+
return [...auditLog];
|
|
158
|
+
}
|
|
159
|
+
/** 감사 로그 초기화 */
|
|
160
|
+
export function clearAuditLog() {
|
|
161
|
+
auditLog.length = 0;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=shield-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shield-middleware.js","sourceRoot":"","sources":["../../src/middleware/shield-middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AirPlugin } from '../../types/plugin.js';
|
|
2
|
+
interface AuthOptions {
|
|
3
|
+
/** 인증 방식 */
|
|
4
|
+
type: 'api-key' | 'bearer';
|
|
5
|
+
/** 허용할 키/토큰 목록 */
|
|
6
|
+
keys?: string[];
|
|
7
|
+
/** 커스텀 검증 함수 */
|
|
8
|
+
verify?: (token: string) => Promise<boolean> | boolean;
|
|
9
|
+
/** 인증 없이 허용할 도구 (기본: []) */
|
|
10
|
+
publicTools?: string[];
|
|
11
|
+
/** 인증 헤더/파라미터 이름 (기본: _auth) */
|
|
12
|
+
paramName?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function authPlugin(options: AuthOptions): AirPlugin;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/plugin/builtin/auth.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD,UAAU,WAAW;IACnB,YAAY;IACZ,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,gBAAgB;IAChB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACvD,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAgD1D"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/core — plugin/builtin/auth.ts
|
|
3
|
+
//
|
|
4
|
+
// 간단한 인증 플러그인.
|
|
5
|
+
// API 키 또는 Bearer 토큰으로 도구 접근을 제한.
|
|
6
|
+
// 프로덕션에서는 OAuth 2.1을 사용하되, 개발/내부용으로 이 플러그인 사용.
|
|
7
|
+
//
|
|
8
|
+
// @example
|
|
9
|
+
// defineServer({
|
|
10
|
+
// use: [authPlugin({
|
|
11
|
+
// type: 'api-key',
|
|
12
|
+
// keys: ['sk-abc123', 'sk-def456'],
|
|
13
|
+
// })],
|
|
14
|
+
// });
|
|
15
|
+
export function authPlugin(options) {
|
|
16
|
+
const publicTools = new Set(options.publicTools || []);
|
|
17
|
+
const validKeys = new Set(options.keys || []);
|
|
18
|
+
const paramName = options.paramName || '_auth';
|
|
19
|
+
async function isValid(token) {
|
|
20
|
+
if (options.verify)
|
|
21
|
+
return options.verify(token);
|
|
22
|
+
return validKeys.has(token);
|
|
23
|
+
}
|
|
24
|
+
const middleware = {
|
|
25
|
+
name: 'air:auth',
|
|
26
|
+
before: async (ctx) => {
|
|
27
|
+
// 공개 도구는 인증 스킵
|
|
28
|
+
if (publicTools.has(ctx.tool.name))
|
|
29
|
+
return;
|
|
30
|
+
const token = ctx.params[paramName];
|
|
31
|
+
if (!token) {
|
|
32
|
+
return {
|
|
33
|
+
abort: true,
|
|
34
|
+
abortResponse: {
|
|
35
|
+
content: [{ type: 'text', text: `[Auth] Authentication required for "${ctx.tool.name}". Pass "${paramName}" parameter.` }],
|
|
36
|
+
isError: true,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const valid = await isValid(token);
|
|
41
|
+
if (!valid) {
|
|
42
|
+
return {
|
|
43
|
+
abort: true,
|
|
44
|
+
abortResponse: {
|
|
45
|
+
content: [{ type: 'text', text: `[Auth] Invalid credentials for "${ctx.tool.name}".` }],
|
|
46
|
+
isError: true,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// 인증 성공 — _auth 파라미터를 제거하여 핸들러에 전달하지 않음
|
|
51
|
+
const { [paramName]: _, ...cleanParams } = ctx.params;
|
|
52
|
+
return { params: cleanParams, meta: { authenticated: true } };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
meta: { name: 'air:auth', version: '1.0.0' },
|
|
57
|
+
middleware: [middleware],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/plugin/builtin/auth.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,4CAA4C;AAC5C,EAAE;AACF,eAAe;AACf,kCAAkC;AAClC,+CAA+C;AAC/C,EAAE;AACF,WAAW;AACX,mBAAmB;AACnB,yBAAyB;AACzB,yBAAyB;AACzB,0CAA0C;AAC1C,WAAW;AACX,QAAQ;AAkBR,MAAM,UAAU,UAAU,CAAC,OAAoB;IAC7C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC;IAE/C,KAAK,UAAU,OAAO,CAAC,KAAa;QAClC,IAAI,OAAO,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjD,OAAO,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,UAAU,GAAkB;QAChC,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,eAAe;YACf,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO;YAE3C,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAW,CAAC;YAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO;oBACL,KAAK,EAAE,IAAI;oBACX,aAAa,EAAE;wBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,uCAAuC,GAAG,CAAC,IAAI,CAAC,IAAI,YAAY,SAAS,cAAc,EAAE,CAAC;wBAC1H,OAAO,EAAE,IAAI;qBACd;iBACF,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;YACnC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO;oBACL,KAAK,EAAE,IAAI;oBACX,aAAa,EAAE;wBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mCAAmC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;wBACvF,OAAO,EAAE,IAAI;qBACd;iBACF,CAAC;YACJ,CAAC;YAED,wCAAwC;YACxC,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,GAAG,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YACtD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,CAAC;QAChE,CAAC;KACF,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE;QAC5C,UAAU,EAAE,CAAC,UAAU,CAAC;KACzB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AirPlugin } from '../../types/plugin.js';
|
|
2
|
+
interface CacheOptions {
|
|
3
|
+
/** 캐시 유지 시간 ms (기본: 60000) */
|
|
4
|
+
ttlMs?: number;
|
|
5
|
+
/** 캐시 최대 항목 수 (기본: 1000) */
|
|
6
|
+
maxEntries?: number;
|
|
7
|
+
/** 캐시 제외 도구 */
|
|
8
|
+
exclude?: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function cachePlugin(options?: CacheOptions): AirPlugin;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/plugin/builtin/cache.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD,UAAU,YAAY;IACpB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAOD,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,SAAS,CA2D7D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/core — plugin/builtin/cache.ts
|
|
3
|
+
//
|
|
4
|
+
// 도구 결과 캐시 플러그인.
|
|
5
|
+
// 같은 파라미터로 같은 도구를 호출하면 캐시된 결과를 반환.
|
|
6
|
+
//
|
|
7
|
+
// @example
|
|
8
|
+
// defineServer({
|
|
9
|
+
// use: [cachePlugin({ ttlMs: 60_000 })],
|
|
10
|
+
// });
|
|
11
|
+
export function cachePlugin(options) {
|
|
12
|
+
const ttlMs = options?.ttlMs ?? 60_000;
|
|
13
|
+
const maxEntries = options?.maxEntries ?? 1000;
|
|
14
|
+
const exclude = new Set(options?.exclude || []);
|
|
15
|
+
const cache = new Map();
|
|
16
|
+
function makeKey(toolName, params) {
|
|
17
|
+
return `${toolName}:${JSON.stringify(params, Object.keys(params).sort())}`;
|
|
18
|
+
}
|
|
19
|
+
function evictExpired() {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
for (const [key, entry] of cache.entries()) {
|
|
22
|
+
if (now - entry.cachedAt > ttlMs) {
|
|
23
|
+
cache.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const middleware = {
|
|
28
|
+
name: 'air:cache',
|
|
29
|
+
before: async (ctx) => {
|
|
30
|
+
if (exclude.has(ctx.tool.name))
|
|
31
|
+
return;
|
|
32
|
+
evictExpired();
|
|
33
|
+
const key = makeKey(ctx.tool.name, ctx.params);
|
|
34
|
+
const entry = cache.get(key);
|
|
35
|
+
if (entry && Date.now() - entry.cachedAt <= ttlMs) {
|
|
36
|
+
ctx.meta._cached = true;
|
|
37
|
+
return {
|
|
38
|
+
abort: true,
|
|
39
|
+
abortResponse: entry.result,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
ctx.meta._cacheKey = key;
|
|
43
|
+
},
|
|
44
|
+
after: async (ctx) => {
|
|
45
|
+
if (exclude.has(ctx.tool.name))
|
|
46
|
+
return;
|
|
47
|
+
if (ctx.meta._cached)
|
|
48
|
+
return;
|
|
49
|
+
const key = ctx.meta._cacheKey;
|
|
50
|
+
if (!key)
|
|
51
|
+
return;
|
|
52
|
+
if (cache.size >= maxEntries) {
|
|
53
|
+
const firstKey = cache.keys().next().value;
|
|
54
|
+
if (firstKey)
|
|
55
|
+
cache.delete(firstKey);
|
|
56
|
+
}
|
|
57
|
+
cache.set(key, { result: ctx.result, cachedAt: Date.now() });
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
meta: { name: 'air:cache', version: '1.0.0' },
|
|
62
|
+
middleware: [middleware],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/plugin/builtin/cache.ts"],"names":[],"mappings":"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"}
|