@airmcp-dev/meter 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/classifier/index.d.ts +5 -0
- package/dist/classifier/index.d.ts.map +1 -0
- package/dist/classifier/index.js +6 -0
- package/dist/classifier/index.js.map +1 -0
- package/dist/classifier/layer-classifier.d.ts +15 -0
- package/dist/classifier/layer-classifier.d.ts.map +1 -0
- package/dist/classifier/layer-classifier.js +40 -0
- package/dist/classifier/layer-classifier.js.map +1 -0
- package/dist/classifier/layer-defs.d.ts +4 -0
- package/dist/classifier/layer-defs.d.ts.map +1 -0
- package/dist/classifier/layer-defs.js +60 -0
- package/dist/classifier/layer-defs.js.map +1 -0
- package/dist/classifier/rules.d.ts +14 -0
- package/dist/classifier/rules.d.ts.map +1 -0
- package/dist/classifier/rules.js +67 -0
- package/dist/classifier/rules.js.map +1 -0
- package/dist/cost/budget.d.ts +17 -0
- package/dist/cost/budget.d.ts.map +1 -0
- package/dist/cost/budget.js +74 -0
- package/dist/cost/budget.js.map +1 -0
- package/dist/cost/call-tracker.d.ts +23 -0
- package/dist/cost/call-tracker.d.ts.map +1 -0
- package/dist/cost/call-tracker.js +70 -0
- package/dist/cost/call-tracker.js.map +1 -0
- package/dist/cost/index.d.ts +4 -0
- package/dist/cost/index.d.ts.map +1 -0
- package/dist/cost/index.js +6 -0
- package/dist/cost/index.js.map +1 -0
- package/dist/cost/token-tracker.d.ts +19 -0
- package/dist/cost/token-tracker.d.ts.map +1 -0
- package/dist/cost/token-tracker.js +54 -0
- package/dist/cost/token-tracker.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/aggregator.d.ts +15 -0
- package/dist/metrics/aggregator.d.ts.map +1 -0
- package/dist/metrics/aggregator.js +51 -0
- package/dist/metrics/aggregator.js.map +1 -0
- package/dist/metrics/collector.d.ts +28 -0
- package/dist/metrics/collector.d.ts.map +1 -0
- package/dist/metrics/collector.js +38 -0
- package/dist/metrics/collector.js.map +1 -0
- package/dist/metrics/exporter.d.ts +13 -0
- package/dist/metrics/exporter.d.ts.map +1 -0
- package/dist/metrics/exporter.js +55 -0
- package/dist/metrics/exporter.js.map +1 -0
- package/dist/metrics/index.d.ts +4 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +6 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/router/cost-router.d.ts +23 -0
- package/dist/router/cost-router.d.ts.map +1 -0
- package/dist/router/cost-router.js +43 -0
- package/dist/router/cost-router.js.map +1 -0
- package/dist/router/index.d.ts +3 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +4 -0
- package/dist/router/index.js.map +1 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
|
|
17
|
+
Copyright 2026 CodePedia Labs (labs@codepedia.kr)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/classifier/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — classifier/index.ts
|
|
3
|
+
export { LayerClassifier } from './layer-classifier.js';
|
|
4
|
+
export { LAYER_DEFINITIONS, getLayerDef } from './layer-defs.js';
|
|
5
|
+
export { BUILTIN_RULES } from './rules.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/classifier/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,0CAA0C;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ClassificationResult } from '../types.js';
|
|
2
|
+
import { type ClassificationRule } from './rules.js';
|
|
3
|
+
export declare class LayerClassifier {
|
|
4
|
+
private rules;
|
|
5
|
+
constructor(customRules?: ClassificationRule[]);
|
|
6
|
+
/**
|
|
7
|
+
* 도구 호출을 분류한다.
|
|
8
|
+
* 첫 번째 매칭 규칙의 결과를 반환.
|
|
9
|
+
* 매칭 없으면 L4(중간) 기본값.
|
|
10
|
+
*/
|
|
11
|
+
classify(toolName: string, params?: Record<string, any>): ClassificationResult;
|
|
12
|
+
/** 규칙을 추가한다 (최우선) */
|
|
13
|
+
addRule(rule: ClassificationRule): void;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=layer-classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer-classifier.d.ts","sourceRoot":"","sources":["../../src/classifier/layer-classifier.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAS,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpE,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAuB;gBAExB,WAAW,CAAC,EAAE,kBAAkB,EAAE;IAK9C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,oBAAoB;IAmB9E,qBAAqB;IACrB,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI;CAGxC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — classifier/layer-classifier.ts
|
|
3
|
+
//
|
|
4
|
+
// 도구 호출을 L1~L7로 자동 분류하는 엔진.
|
|
5
|
+
// 내장 규칙 + 사용자 정의 규칙 평가.
|
|
6
|
+
import { BUILTIN_RULES } from './rules.js';
|
|
7
|
+
export class LayerClassifier {
|
|
8
|
+
rules;
|
|
9
|
+
constructor(customRules) {
|
|
10
|
+
// 사용자 규칙이 내장 규칙보다 우선
|
|
11
|
+
this.rules = [...(customRules || []), ...BUILTIN_RULES];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 도구 호출을 분류한다.
|
|
15
|
+
* 첫 번째 매칭 규칙의 결과를 반환.
|
|
16
|
+
* 매칭 없으면 L4(중간) 기본값.
|
|
17
|
+
*/
|
|
18
|
+
classify(toolName, params) {
|
|
19
|
+
for (const rule of this.rules) {
|
|
20
|
+
if (rule.match(toolName, params)) {
|
|
21
|
+
return {
|
|
22
|
+
layer: rule.layer,
|
|
23
|
+
confidence: rule.confidence,
|
|
24
|
+
reason: rule.reason,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// 기본값: L4 (중간)
|
|
29
|
+
return {
|
|
30
|
+
layer: 'L4',
|
|
31
|
+
confidence: 0.3,
|
|
32
|
+
reason: 'No matching rule — default to L4',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** 규칙을 추가한다 (최우선) */
|
|
36
|
+
addRule(rule) {
|
|
37
|
+
this.rules.unshift(rule);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=layer-classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer-classifier.js","sourceRoot":"","sources":["../../src/classifier/layer-classifier.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,qDAAqD;AACrD,EAAE;AACF,4BAA4B;AAC5B,wBAAwB;AAGxB,OAAO,EAAE,aAAa,EAA2B,MAAM,YAAY,CAAC;AAEpE,MAAM,OAAO,eAAe;IAClB,KAAK,CAAuB;IAEpC,YAAY,WAAkC;QAC5C,qBAAqB;QACrB,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC;IAC1D,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,QAAgB,EAAE,MAA4B;QACrD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;gBACjC,OAAO;oBACL,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;iBACpB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,eAAe;QACf,OAAO;YACL,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,GAAG;YACf,MAAM,EAAE,kCAAkC;SAC3C,CAAC;IACJ,CAAC;IAED,qBAAqB;IACrB,OAAO,CAAC,IAAwB;QAC9B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer-defs.d.ts","sourceRoot":"","sources":["../../src/classifier/layer-defs.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAkDrD,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,CAElD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — classifier/layer-defs.ts
|
|
3
|
+
//
|
|
4
|
+
// Pylon-7 기반 7계층 정의.
|
|
5
|
+
// 상위(L1)→하위(L7)로 갈수록 토큰 소모와 위험도 증가.
|
|
6
|
+
export const LAYER_DEFINITIONS = {
|
|
7
|
+
L1: {
|
|
8
|
+
layer: 'L1',
|
|
9
|
+
name: 'Static Response',
|
|
10
|
+
description: 'No LLM needed. Cached/static data return.',
|
|
11
|
+
costWeight: 1,
|
|
12
|
+
riskLevel: 0,
|
|
13
|
+
},
|
|
14
|
+
L2: {
|
|
15
|
+
layer: 'L2',
|
|
16
|
+
name: 'Simple Lookup',
|
|
17
|
+
description: 'DB query, file read. Minimal processing.',
|
|
18
|
+
costWeight: 3,
|
|
19
|
+
riskLevel: 0.1,
|
|
20
|
+
},
|
|
21
|
+
L3: {
|
|
22
|
+
layer: 'L3',
|
|
23
|
+
name: 'Transform',
|
|
24
|
+
description: 'Data transformation, format conversion.',
|
|
25
|
+
costWeight: 5,
|
|
26
|
+
riskLevel: 0.15,
|
|
27
|
+
},
|
|
28
|
+
L4: {
|
|
29
|
+
layer: 'L4',
|
|
30
|
+
name: 'Compute',
|
|
31
|
+
description: 'Calculation, aggregation, analysis.',
|
|
32
|
+
costWeight: 10,
|
|
33
|
+
riskLevel: 0.2,
|
|
34
|
+
},
|
|
35
|
+
L5: {
|
|
36
|
+
layer: 'L5',
|
|
37
|
+
name: 'External API',
|
|
38
|
+
description: 'External service call. Network I/O.',
|
|
39
|
+
costWeight: 20,
|
|
40
|
+
riskLevel: 0.4,
|
|
41
|
+
},
|
|
42
|
+
L6: {
|
|
43
|
+
layer: 'L6',
|
|
44
|
+
name: 'LLM Inference',
|
|
45
|
+
description: 'AI model call. High token cost.',
|
|
46
|
+
costWeight: 50,
|
|
47
|
+
riskLevel: 0.6,
|
|
48
|
+
},
|
|
49
|
+
L7: {
|
|
50
|
+
layer: 'L7',
|
|
51
|
+
name: 'Multi-Step Agent',
|
|
52
|
+
description: 'Autonomous agent with tool chaining. Highest cost/risk.',
|
|
53
|
+
costWeight: 100,
|
|
54
|
+
riskLevel: 0.9,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export function getLayerDef(layer) {
|
|
58
|
+
return LAYER_DEFINITIONS[layer];
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=layer-defs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer-defs.js","sourceRoot":"","sources":["../../src/classifier/layer-defs.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,+CAA+C;AAC/C,EAAE;AACF,qBAAqB;AACrB,oCAAoC;AAIpC,MAAM,CAAC,MAAM,iBAAiB,GAA4B;IACxD,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,2CAA2C;QACxD,UAAU,EAAE,CAAC;QACb,SAAS,EAAE,CAAC;KACb;IACD,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,0CAA0C;QACvD,UAAU,EAAE,CAAC;QACb,SAAS,EAAE,GAAG;KACf;IACD,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,yCAAyC;QACtD,UAAU,EAAE,CAAC;QACb,SAAS,EAAE,IAAI;KAChB;IACD,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,qCAAqC;QAClD,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,GAAG;KACf;IACD,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,cAAc;QACpB,WAAW,EAAE,qCAAqC;QAClD,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,GAAG;KACf;IACD,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,iCAAiC;QAC9C,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,GAAG;KACf;IACD,EAAE,EAAE;QACF,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,kBAAkB;QACxB,WAAW,EAAE,yDAAyD;QACtE,UAAU,EAAE,GAAG;QACf,SAAS,EAAE,GAAG;KACf;CACF,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Layer } from '../types.js';
|
|
2
|
+
export interface ClassificationRule {
|
|
3
|
+
/** 매칭 조건 */
|
|
4
|
+
match: (toolName: string, params?: Record<string, any>) => boolean;
|
|
5
|
+
/** 분류 결과 */
|
|
6
|
+
layer: Layer;
|
|
7
|
+
/** 신뢰도 (0~1) */
|
|
8
|
+
confidence: number;
|
|
9
|
+
/** 이유 */
|
|
10
|
+
reason: string;
|
|
11
|
+
}
|
|
12
|
+
/** 내장 분류 규칙 */
|
|
13
|
+
export declare const BUILTIN_RULES: ClassificationRule[];
|
|
14
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/classifier/rules.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,WAAW,kBAAkB;IACjC,YAAY;IACZ,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;IACnE,YAAY;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,gBAAgB;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS;IACT,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAe;AACf,eAAO,MAAM,aAAa,EAAE,kBAAkB,EAgE7C,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — classifier/rules.ts
|
|
3
|
+
//
|
|
4
|
+
// 도구 호출을 7계층으로 분류하는 규칙.
|
|
5
|
+
// 도구명, 파라미터 패턴, 메타데이터 기반 휴리스틱.
|
|
6
|
+
/** 내장 분류 규칙 */
|
|
7
|
+
export const BUILTIN_RULES = [
|
|
8
|
+
// L1: 캐시/상수 반환
|
|
9
|
+
{
|
|
10
|
+
match: (name) => /^(ping|health|version|echo)$/i.test(name),
|
|
11
|
+
layer: 'L1',
|
|
12
|
+
confidence: 0.95,
|
|
13
|
+
reason: 'Static response tool',
|
|
14
|
+
},
|
|
15
|
+
// L2: 단순 조회
|
|
16
|
+
{
|
|
17
|
+
match: (name) => /^(get|read|find|lookup|list|show|fetch_local)$/i.test(name),
|
|
18
|
+
layer: 'L2',
|
|
19
|
+
confidence: 0.8,
|
|
20
|
+
reason: 'Simple data lookup',
|
|
21
|
+
},
|
|
22
|
+
// L3: 변환
|
|
23
|
+
{
|
|
24
|
+
match: (name) => /^(convert|transform|format|parse|encode|decode)$/i.test(name),
|
|
25
|
+
layer: 'L3',
|
|
26
|
+
confidence: 0.8,
|
|
27
|
+
reason: 'Data transformation',
|
|
28
|
+
},
|
|
29
|
+
// L4: 연산
|
|
30
|
+
{
|
|
31
|
+
match: (name) => /^(compute|calculate|aggregate|analyze|summarize|count)$/i.test(name),
|
|
32
|
+
layer: 'L4',
|
|
33
|
+
confidence: 0.75,
|
|
34
|
+
reason: 'Computation/analysis',
|
|
35
|
+
},
|
|
36
|
+
// L5: 외부 API
|
|
37
|
+
{
|
|
38
|
+
match: (name, params) => {
|
|
39
|
+
if (/^(fetch|request|call_api|webhook|http|post|put|delete)$/i.test(name))
|
|
40
|
+
return true;
|
|
41
|
+
// URL 파라미터가 있으면 외부 호출로 판단
|
|
42
|
+
if (params) {
|
|
43
|
+
const vals = Object.values(params).map(String);
|
|
44
|
+
return vals.some((v) => /^https?:\/\//.test(v));
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
},
|
|
48
|
+
layer: 'L5',
|
|
49
|
+
confidence: 0.85,
|
|
50
|
+
reason: 'External API call',
|
|
51
|
+
},
|
|
52
|
+
// L6: LLM 추론
|
|
53
|
+
{
|
|
54
|
+
match: (name) => /^(generate|complete|chat|embed|infer|predict|classify_ai)$/i.test(name),
|
|
55
|
+
layer: 'L6',
|
|
56
|
+
confidence: 0.9,
|
|
57
|
+
reason: 'LLM inference',
|
|
58
|
+
},
|
|
59
|
+
// L7: 에이전트
|
|
60
|
+
{
|
|
61
|
+
match: (name) => /^(agent|think|plan|execute|reason|chain|orchestrate)$/i.test(name),
|
|
62
|
+
layer: 'L7',
|
|
63
|
+
confidence: 0.85,
|
|
64
|
+
reason: 'Multi-step agent operation',
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/classifier/rules.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,0CAA0C;AAC1C,EAAE;AACF,wBAAwB;AACxB,+BAA+B;AAe/B,eAAe;AACf,MAAM,CAAC,MAAM,aAAa,GAAyB;IACjD,eAAe;IACf;QACE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3D,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,sBAAsB;KAC/B;IAED,YAAY;IACZ;QACE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,iDAAiD,CAAC,IAAI,CAAC,IAAI,CAAC;QAC7E,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,oBAAoB;KAC7B;IAED,SAAS;IACT;QACE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,mDAAmD,CAAC,IAAI,CAAC,IAAI,CAAC;QAC/E,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,qBAAqB;KAC9B;IAED,SAAS;IACT;QACE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,0DAA0D,CAAC,IAAI,CAAC,IAAI,CAAC;QACtF,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,sBAAsB;KAC/B;IAED,aAAa;IACb;QACE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YACtB,IAAI,0DAA0D,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YACvF,0BAA0B;YAC1B,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAClD,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,mBAAmB;KAC5B;IAED,aAAa;IACb;QACE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,6DAA6D,CAAC,IAAI,CAAC,IAAI,CAAC;QACzF,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,eAAe;KACxB;IAED,WAAW;IACX;QACE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,wDAAwD,CAAC,IAAI,CAAC,IAAI,CAAC;QACpF,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,4BAA4B;KACrC;CACF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BudgetConfig, BudgetCheckResult } from '../types.js';
|
|
2
|
+
import { TokenTracker } from './token-tracker.js';
|
|
3
|
+
export declare class BudgetManager {
|
|
4
|
+
private config;
|
|
5
|
+
private tracker;
|
|
6
|
+
constructor(config: BudgetConfig, tracker: TokenTracker);
|
|
7
|
+
/**
|
|
8
|
+
* 호출 전에 예산 내인지 확인한다.
|
|
9
|
+
* block 모드면 초과 시 차단, warn 모드면 경고만.
|
|
10
|
+
*/
|
|
11
|
+
check(estimatedTokens?: number): BudgetCheckResult;
|
|
12
|
+
/** 설정을 업데이트한다 */
|
|
13
|
+
updateConfig(config: Partial<BudgetConfig>): void;
|
|
14
|
+
/** 현재 설정을 반환한다 */
|
|
15
|
+
getConfig(): BudgetConfig;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/cost/budget.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAc,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAe;gBAElB,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY;IAKvD;;;OAGG;IACH,KAAK,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,iBAAiB;IAuDlD,iBAAiB;IACjB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAIjD,kBAAkB;IAClB,SAAS,IAAI,YAAY;CAG1B"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — cost/budget.ts
|
|
3
|
+
//
|
|
4
|
+
// 토큰/비용 예산 관리.
|
|
5
|
+
// 일일/월간 한도를 설정하고 초과 시 경고 또는 차단.
|
|
6
|
+
export class BudgetManager {
|
|
7
|
+
config;
|
|
8
|
+
tracker;
|
|
9
|
+
constructor(config, tracker) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.tracker = tracker;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 호출 전에 예산 내인지 확인한다.
|
|
15
|
+
* block 모드면 초과 시 차단, warn 모드면 경고만.
|
|
16
|
+
*/
|
|
17
|
+
check(estimatedTokens) {
|
|
18
|
+
// 호출당 토큰 제한
|
|
19
|
+
if (this.config.maxTokensPerCall && estimatedTokens) {
|
|
20
|
+
if (estimatedTokens > this.config.maxTokensPerCall) {
|
|
21
|
+
return {
|
|
22
|
+
allowed: this.config.onExceed !== 'block',
|
|
23
|
+
remaining: this.config.maxTokensPerCall - estimatedTokens,
|
|
24
|
+
limit: this.config.maxTokensPerCall,
|
|
25
|
+
period: 'per-call',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 일일 한도
|
|
30
|
+
if (this.config.dailyLimit) {
|
|
31
|
+
const todayCost = this.tracker.today().reduce((sum, u) => sum + u.estimatedCost, 0);
|
|
32
|
+
const remaining = this.config.dailyLimit - todayCost;
|
|
33
|
+
if (remaining <= 0) {
|
|
34
|
+
return {
|
|
35
|
+
allowed: this.config.onExceed !== 'block',
|
|
36
|
+
remaining,
|
|
37
|
+
limit: this.config.dailyLimit,
|
|
38
|
+
period: 'daily',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 월간 한도
|
|
43
|
+
if (this.config.monthlyLimit) {
|
|
44
|
+
const monthStart = new Date();
|
|
45
|
+
monthStart.setDate(1);
|
|
46
|
+
monthStart.setHours(0, 0, 0, 0);
|
|
47
|
+
const monthCost = this.tracker.since(monthStart).reduce((sum, u) => sum + u.estimatedCost, 0);
|
|
48
|
+
const remaining = this.config.monthlyLimit - monthCost;
|
|
49
|
+
if (remaining <= 0) {
|
|
50
|
+
return {
|
|
51
|
+
allowed: this.config.onExceed !== 'block',
|
|
52
|
+
remaining,
|
|
53
|
+
limit: this.config.monthlyLimit,
|
|
54
|
+
period: 'monthly',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
allowed: true,
|
|
60
|
+
remaining: Infinity,
|
|
61
|
+
limit: 0,
|
|
62
|
+
period: 'daily',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** 설정을 업데이트한다 */
|
|
66
|
+
updateConfig(config) {
|
|
67
|
+
Object.assign(this.config, config);
|
|
68
|
+
}
|
|
69
|
+
/** 현재 설정을 반환한다 */
|
|
70
|
+
getConfig() {
|
|
71
|
+
return { ...this.config };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/cost/budget.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,qCAAqC;AACrC,EAAE;AACF,eAAe;AACf,gCAAgC;AAKhC,MAAM,OAAO,aAAa;IAChB,MAAM,CAAe;IACrB,OAAO,CAAe;IAE9B,YAAY,MAAoB,EAAE,OAAqB;QACrD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAwB;QAC5B,YAAY;QACZ,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,eAAe,EAAE,CAAC;YACpD,IAAI,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACnD,OAAO;oBACL,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,OAAO;oBACzC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,GAAG,eAAe;oBACzD,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB;oBACnC,MAAM,EAAE,UAAU;iBACnB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,QAAQ;QACR,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;YACpF,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;YAErD,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;gBACnB,OAAO;oBACL,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,OAAO;oBACzC,SAAS;oBACT,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;oBAC7B,MAAM,EAAE,OAAO;iBAChB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,QAAQ;QACR,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAC7B,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC;YAC9B,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACtB,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;YAC9F,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,SAAS,CAAC;YAEvD,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;gBACnB,OAAO;oBACL,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,OAAO;oBACzC,SAAS;oBACT,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY;oBAC/B,MAAM,EAAE,SAAS;iBAClB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,QAAQ;YACnB,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,OAAO;SAChB,CAAC;IACJ,CAAC;IAED,iBAAiB;IACjB,YAAY,CAAC,MAA6B;QACxC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,kBAAkB;IAClB,SAAS;QACP,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CallMetric, Layer } from '../types.js';
|
|
2
|
+
export declare class CallTracker {
|
|
3
|
+
private metrics;
|
|
4
|
+
private maxHistory;
|
|
5
|
+
constructor(maxHistory?: number);
|
|
6
|
+
/** 호출 메트릭을 기록한다 */
|
|
7
|
+
record(toolName: string, layer: Layer, latencyMs: number, success: boolean, serverId?: string): CallMetric;
|
|
8
|
+
/** 총 호출 수 */
|
|
9
|
+
totalCalls(): number;
|
|
10
|
+
/** 성공률 (0~1) */
|
|
11
|
+
successRate(): number;
|
|
12
|
+
/** 평균 지연시간 (ms) */
|
|
13
|
+
avgLatency(): number;
|
|
14
|
+
/** 계층별 호출 분포 */
|
|
15
|
+
layerDistribution(): Record<Layer, number>;
|
|
16
|
+
/** 도구별 호출 수 */
|
|
17
|
+
toolCounts(): Record<string, number>;
|
|
18
|
+
/** 기간별 필터 */
|
|
19
|
+
since(date: Date): CallMetric[];
|
|
20
|
+
/** 전체 기록 */
|
|
21
|
+
getAll(): readonly CallMetric[];
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=call-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-tracker.d.ts","sourceRoot":"","sources":["../../src/cost/call-tracker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAErD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,GAAE,MAAe;IAIvC,mBAAmB;IACnB,MAAM,CACJ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,UAAU;IAkBb,aAAa;IACb,UAAU,IAAI,MAAM;IAIpB,gBAAgB;IAChB,WAAW,IAAI,MAAM;IAMrB,mBAAmB;IACnB,UAAU,IAAI,MAAM;IAMpB,gBAAgB;IAChB,iBAAiB,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC;IAQ1C,eAAe;IACf,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQpC,aAAa;IACb,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE;IAI/B,YAAY;IACZ,MAAM,IAAI,SAAS,UAAU,EAAE;CAGhC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — cost/call-tracker.ts
|
|
3
|
+
//
|
|
4
|
+
// 도구 호출 횟수와 지연시간을 추적한다.
|
|
5
|
+
export class CallTracker {
|
|
6
|
+
metrics = [];
|
|
7
|
+
maxHistory;
|
|
8
|
+
constructor(maxHistory = 10_000) {
|
|
9
|
+
this.maxHistory = maxHistory;
|
|
10
|
+
}
|
|
11
|
+
/** 호출 메트릭을 기록한다 */
|
|
12
|
+
record(toolName, layer, latencyMs, success, serverId) {
|
|
13
|
+
const metric = {
|
|
14
|
+
toolName,
|
|
15
|
+
serverId,
|
|
16
|
+
layer,
|
|
17
|
+
latencyMs,
|
|
18
|
+
success,
|
|
19
|
+
timestamp: new Date(),
|
|
20
|
+
};
|
|
21
|
+
this.metrics.push(metric);
|
|
22
|
+
if (this.metrics.length > this.maxHistory) {
|
|
23
|
+
this.metrics = this.metrics.slice(-this.maxHistory);
|
|
24
|
+
}
|
|
25
|
+
return metric;
|
|
26
|
+
}
|
|
27
|
+
/** 총 호출 수 */
|
|
28
|
+
totalCalls() {
|
|
29
|
+
return this.metrics.length;
|
|
30
|
+
}
|
|
31
|
+
/** 성공률 (0~1) */
|
|
32
|
+
successRate() {
|
|
33
|
+
if (this.metrics.length === 0)
|
|
34
|
+
return 1;
|
|
35
|
+
const success = this.metrics.filter((m) => m.success).length;
|
|
36
|
+
return success / this.metrics.length;
|
|
37
|
+
}
|
|
38
|
+
/** 평균 지연시간 (ms) */
|
|
39
|
+
avgLatency() {
|
|
40
|
+
if (this.metrics.length === 0)
|
|
41
|
+
return 0;
|
|
42
|
+
const sum = this.metrics.reduce((s, m) => s + m.latencyMs, 0);
|
|
43
|
+
return sum / this.metrics.length;
|
|
44
|
+
}
|
|
45
|
+
/** 계층별 호출 분포 */
|
|
46
|
+
layerDistribution() {
|
|
47
|
+
const dist = { L1: 0, L2: 0, L3: 0, L4: 0, L5: 0, L6: 0, L7: 0 };
|
|
48
|
+
for (const m of this.metrics) {
|
|
49
|
+
dist[m.layer]++;
|
|
50
|
+
}
|
|
51
|
+
return dist;
|
|
52
|
+
}
|
|
53
|
+
/** 도구별 호출 수 */
|
|
54
|
+
toolCounts() {
|
|
55
|
+
const counts = {};
|
|
56
|
+
for (const m of this.metrics) {
|
|
57
|
+
counts[m.toolName] = (counts[m.toolName] || 0) + 1;
|
|
58
|
+
}
|
|
59
|
+
return counts;
|
|
60
|
+
}
|
|
61
|
+
/** 기간별 필터 */
|
|
62
|
+
since(date) {
|
|
63
|
+
return this.metrics.filter((m) => m.timestamp >= date);
|
|
64
|
+
}
|
|
65
|
+
/** 전체 기록 */
|
|
66
|
+
getAll() {
|
|
67
|
+
return this.metrics;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=call-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-tracker.js","sourceRoot":"","sources":["../../src/cost/call-tracker.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,2CAA2C;AAC3C,EAAE;AACF,wBAAwB;AAIxB,MAAM,OAAO,WAAW;IACd,OAAO,GAAiB,EAAE,CAAC;IAC3B,UAAU,CAAS;IAE3B,YAAY,aAAqB,MAAM;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,mBAAmB;IACnB,MAAM,CACJ,QAAgB,EAChB,KAAY,EACZ,SAAiB,EACjB,OAAgB,EAChB,QAAiB;QAEjB,MAAM,MAAM,GAAe;YACzB,QAAQ;YACR,QAAQ;YACR,KAAK;YACL,SAAS;YACT,OAAO;YACP,SAAS,EAAE,IAAI,IAAI,EAAE;SACtB,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YAC1C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,aAAa;IACb,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED,gBAAgB;IAChB,WAAW;QACT,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QAC7D,OAAO,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IACvC,CAAC;IAED,mBAAmB;IACnB,UAAU;QACR,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAC9D,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IACnC,CAAC;IAED,gBAAgB;IAChB,iBAAiB;QACf,MAAM,IAAI,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;QAC1F,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,eAAe;IACf,UAAU;QACR,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,aAAa;IACb,KAAK,CAAC,IAAU;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,YAAY;IACZ,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cost/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — cost/index.ts
|
|
3
|
+
export { TokenTracker } from './token-tracker.js';
|
|
4
|
+
export { CallTracker } from './call-tracker.js';
|
|
5
|
+
export { BudgetManager } from './budget.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cost/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,oCAAoC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TokenUsage } from '../types.js';
|
|
2
|
+
export declare class TokenTracker {
|
|
3
|
+
private history;
|
|
4
|
+
private maxHistory;
|
|
5
|
+
constructor(maxHistory?: number);
|
|
6
|
+
/** 토큰 사용을 기록한다 */
|
|
7
|
+
record(toolName: string, input: number, output: number, costPerToken?: number): TokenUsage;
|
|
8
|
+
/** 총 토큰 사용량 */
|
|
9
|
+
totalTokens(): number;
|
|
10
|
+
/** 총 추정 비용 */
|
|
11
|
+
totalCost(): number;
|
|
12
|
+
/** 도구별 토큰 사용량 */
|
|
13
|
+
byTool(): Record<string, number>;
|
|
14
|
+
/** 기간별 필터 */
|
|
15
|
+
since(date: Date): TokenUsage[];
|
|
16
|
+
/** 오늘 사용량 */
|
|
17
|
+
today(): TokenUsage[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=token-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../../src/cost/token-tracker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,GAAE,MAAe;IAIvC,kBAAkB;IAClB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAE,MAAU,GAAG,UAAU;IAkB7F,eAAe;IACf,WAAW,IAAI,MAAM;IAIrB,cAAc;IACd,SAAS,IAAI,MAAM;IAInB,iBAAiB;IACjB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQhC,aAAa;IACb,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE;IAI/B,aAAa;IACb,KAAK,IAAI,UAAU,EAAE;CAKtB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — cost/token-tracker.ts
|
|
3
|
+
//
|
|
4
|
+
// 도구 호출별 토큰 사용량을 추적한다.
|
|
5
|
+
export class TokenTracker {
|
|
6
|
+
history = [];
|
|
7
|
+
maxHistory;
|
|
8
|
+
constructor(maxHistory = 10_000) {
|
|
9
|
+
this.maxHistory = maxHistory;
|
|
10
|
+
}
|
|
11
|
+
/** 토큰 사용을 기록한다 */
|
|
12
|
+
record(toolName, input, output, costPerToken = 0) {
|
|
13
|
+
const usage = {
|
|
14
|
+
toolName,
|
|
15
|
+
inputTokens: input,
|
|
16
|
+
outputTokens: output,
|
|
17
|
+
totalTokens: input + output,
|
|
18
|
+
estimatedCost: (input + output) * costPerToken,
|
|
19
|
+
timestamp: new Date(),
|
|
20
|
+
};
|
|
21
|
+
this.history.push(usage);
|
|
22
|
+
if (this.history.length > this.maxHistory) {
|
|
23
|
+
this.history = this.history.slice(-this.maxHistory);
|
|
24
|
+
}
|
|
25
|
+
return usage;
|
|
26
|
+
}
|
|
27
|
+
/** 총 토큰 사용량 */
|
|
28
|
+
totalTokens() {
|
|
29
|
+
return this.history.reduce((sum, u) => sum + u.totalTokens, 0);
|
|
30
|
+
}
|
|
31
|
+
/** 총 추정 비용 */
|
|
32
|
+
totalCost() {
|
|
33
|
+
return this.history.reduce((sum, u) => sum + u.estimatedCost, 0);
|
|
34
|
+
}
|
|
35
|
+
/** 도구별 토큰 사용량 */
|
|
36
|
+
byTool() {
|
|
37
|
+
const result = {};
|
|
38
|
+
for (const u of this.history) {
|
|
39
|
+
result[u.toolName] = (result[u.toolName] || 0) + u.totalTokens;
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
/** 기간별 필터 */
|
|
44
|
+
since(date) {
|
|
45
|
+
return this.history.filter((u) => u.timestamp >= date);
|
|
46
|
+
}
|
|
47
|
+
/** 오늘 사용량 */
|
|
48
|
+
today() {
|
|
49
|
+
const start = new Date();
|
|
50
|
+
start.setHours(0, 0, 0, 0);
|
|
51
|
+
return this.since(start);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=token-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/cost/token-tracker.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,4CAA4C;AAC5C,EAAE;AACF,uBAAuB;AAIvB,MAAM,OAAO,YAAY;IACf,OAAO,GAAiB,EAAE,CAAC;IAC3B,UAAU,CAAS;IAE3B,YAAY,aAAqB,MAAM;QACrC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,kBAAkB;IAClB,MAAM,CAAC,QAAgB,EAAE,KAAa,EAAE,MAAc,EAAE,eAAuB,CAAC;QAC9E,MAAM,KAAK,GAAe;YACxB,QAAQ;YACR,WAAW,EAAE,KAAK;YAClB,YAAY,EAAE,MAAM;YACpB,WAAW,EAAE,KAAK,GAAG,MAAM;YAC3B,aAAa,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,YAAY;YAC9C,SAAS,EAAE,IAAI,IAAI,EAAE;SACtB,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YAC1C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,WAAW;QACT,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,cAAc;IACd,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,iBAAiB;IACjB,MAAM;QACJ,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC;QACjE,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,aAAa;IACb,KAAK,CAAC,IAAU;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,aAAa;IACb,KAAK;QACH,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;QACzB,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { LayerClassifier } from './classifier/index.js';
|
|
2
|
+
export { LAYER_DEFINITIONS, getLayerDef } from './classifier/index.js';
|
|
3
|
+
export { BUILTIN_RULES } from './classifier/index.js';
|
|
4
|
+
export { TokenTracker } from './cost/index.js';
|
|
5
|
+
export { CallTracker } from './cost/index.js';
|
|
6
|
+
export { BudgetManager } from './cost/index.js';
|
|
7
|
+
export { MetricsCollector } from './metrics/index.js';
|
|
8
|
+
export { MetricsAggregator } from './metrics/index.js';
|
|
9
|
+
export { MetricsExporter } from './metrics/index.js';
|
|
10
|
+
export { selectCheapest } from './router/index.js';
|
|
11
|
+
export type { Layer, LayerDef, ClassificationResult, TokenUsage, CallMetric, AggregatedMetrics, BudgetConfig, BudgetCheckResult, } from './types.js';
|
|
12
|
+
export type { ClassificationRule } from './classifier/index.js';
|
|
13
|
+
export type { CostCandidate, CostRouteResult } from './router/index.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,YAAY,EACV,KAAK,EACL,QAAQ,EACR,oBAAoB,EACpB,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — index.ts
|
|
3
|
+
// re-export only. 로직 없음.
|
|
4
|
+
// ── Classifier ──
|
|
5
|
+
export { LayerClassifier } from './classifier/index.js';
|
|
6
|
+
export { LAYER_DEFINITIONS, getLayerDef } from './classifier/index.js';
|
|
7
|
+
export { BUILTIN_RULES } from './classifier/index.js';
|
|
8
|
+
// ── Cost ──
|
|
9
|
+
export { TokenTracker } from './cost/index.js';
|
|
10
|
+
export { CallTracker } from './cost/index.js';
|
|
11
|
+
export { BudgetManager } from './cost/index.js';
|
|
12
|
+
// ── Metrics ──
|
|
13
|
+
export { MetricsCollector } from './metrics/index.js';
|
|
14
|
+
export { MetricsAggregator } from './metrics/index.js';
|
|
15
|
+
export { MetricsExporter } from './metrics/index.js';
|
|
16
|
+
// ── Router ──
|
|
17
|
+
export { selectCheapest } from './router/index.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,+BAA+B;AAC/B,yBAAyB;AAEzB,mBAAmB;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,eAAe;AACf,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AggregatedMetrics } from '../types.js';
|
|
2
|
+
import { MetricsCollector } from './collector.js';
|
|
3
|
+
export declare class MetricsAggregator {
|
|
4
|
+
private collector;
|
|
5
|
+
constructor(collector: MetricsCollector);
|
|
6
|
+
/**
|
|
7
|
+
* 지정 기간의 집계 메트릭을 반환한다.
|
|
8
|
+
*/
|
|
9
|
+
aggregate(from: Date, to?: Date): AggregatedMetrics;
|
|
10
|
+
/** 최근 1시간 집계 */
|
|
11
|
+
lastHour(): AggregatedMetrics;
|
|
12
|
+
/** 오늘 집계 */
|
|
13
|
+
today(): AggregatedMetrics;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=aggregator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregator.d.ts","sourceRoot":"","sources":["../../src/metrics/aggregator.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAS,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,SAAS,CAAmB;gBAExB,SAAS,EAAE,gBAAgB;IAIvC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAE,IAAiB,GAAG,iBAAiB;IA+B/D,gBAAgB;IAChB,QAAQ,IAAI,iBAAiB;IAI7B,YAAY;IACZ,KAAK,IAAI,iBAAiB;CAK3B"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — metrics/aggregator.ts
|
|
3
|
+
//
|
|
4
|
+
// 시간별 메트릭 집계.
|
|
5
|
+
// 분/시간/일 단위로 집계하여 추세 분석 가능.
|
|
6
|
+
export class MetricsAggregator {
|
|
7
|
+
collector;
|
|
8
|
+
constructor(collector) {
|
|
9
|
+
this.collector = collector;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 지정 기간의 집계 메트릭을 반환한다.
|
|
13
|
+
*/
|
|
14
|
+
aggregate(from, to = new Date()) {
|
|
15
|
+
const calls = this.collector.calls.since(from);
|
|
16
|
+
const tokens = this.collector.tokens.since(from);
|
|
17
|
+
const totalCalls = calls.length;
|
|
18
|
+
const successCount = calls.filter((c) => c.success).length;
|
|
19
|
+
const avgLatency = totalCalls > 0 ? calls.reduce((s, c) => s + c.latencyMs, 0) / totalCalls : 0;
|
|
20
|
+
const layerDist = { L1: 0, L2: 0, L3: 0, L4: 0, L5: 0, L6: 0, L7: 0 };
|
|
21
|
+
for (const c of calls) {
|
|
22
|
+
layerDist[c.layer]++;
|
|
23
|
+
}
|
|
24
|
+
const toolCounts = {};
|
|
25
|
+
for (const c of calls) {
|
|
26
|
+
toolCounts[c.toolName] = (toolCounts[c.toolName] || 0) + 1;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
totalCalls,
|
|
30
|
+
successRate: totalCalls > 0 ? successCount / totalCalls : 1,
|
|
31
|
+
avgLatencyMs: avgLatency,
|
|
32
|
+
totalTokens: tokens.reduce((s, t) => s + t.totalTokens, 0),
|
|
33
|
+
totalCost: tokens.reduce((s, t) => s + t.estimatedCost, 0),
|
|
34
|
+
layerDistribution: layerDist,
|
|
35
|
+
toolCounts,
|
|
36
|
+
from,
|
|
37
|
+
to,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** 최근 1시간 집계 */
|
|
41
|
+
lastHour() {
|
|
42
|
+
return this.aggregate(new Date(Date.now() - 3600_000));
|
|
43
|
+
}
|
|
44
|
+
/** 오늘 집계 */
|
|
45
|
+
today() {
|
|
46
|
+
const start = new Date();
|
|
47
|
+
start.setHours(0, 0, 0, 0);
|
|
48
|
+
return this.aggregate(start);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=aggregator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregator.js","sourceRoot":"","sources":["../../src/metrics/aggregator.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,4CAA4C;AAC5C,EAAE;AACF,cAAc;AACd,4BAA4B;AAK5B,MAAM,OAAO,iBAAiB;IACpB,SAAS,CAAmB;IAEpC,YAAY,SAA2B;QACrC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,IAAU,EAAE,KAAW,IAAI,IAAI,EAAE;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEjD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;QAChC,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QAC3D,MAAM,UAAU,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAEhG,MAAM,SAAS,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;QAC/F,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,CAAC;QAED,MAAM,UAAU,GAA2B,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO;YACL,UAAU;YACV,WAAW,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC3D,YAAY,EAAE,UAAU;YACxB,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YAC1D,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;YAC1D,iBAAiB,EAAE,SAAS;YAC5B,UAAU;YACV,IAAI;YACJ,EAAE;SACH,CAAC;IACJ,CAAC;IAED,gBAAgB;IAChB,QAAQ;QACN,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,YAAY;IACZ,KAAK;QACH,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;QACzB,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Layer } from '../types.js';
|
|
2
|
+
import { CallTracker } from '../cost/call-tracker.js';
|
|
3
|
+
import { TokenTracker } from '../cost/token-tracker.js';
|
|
4
|
+
export declare class MetricsCollector {
|
|
5
|
+
readonly calls: CallTracker;
|
|
6
|
+
readonly tokens: TokenTracker;
|
|
7
|
+
constructor();
|
|
8
|
+
/**
|
|
9
|
+
* 도구 호출을 기록한다 (호출 메트릭 + 토큰 사용 동시).
|
|
10
|
+
*/
|
|
11
|
+
recordCall(toolName: string, layer: Layer, latencyMs: number, success: boolean, tokenUsage?: {
|
|
12
|
+
input: number;
|
|
13
|
+
output: number;
|
|
14
|
+
costPerToken?: number;
|
|
15
|
+
}, serverId?: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* 현재 시점의 스냅샷을 반환한다.
|
|
18
|
+
*/
|
|
19
|
+
snapshot(): {
|
|
20
|
+
totalCalls: number;
|
|
21
|
+
successRate: number;
|
|
22
|
+
avgLatencyMs: number;
|
|
23
|
+
totalTokens: number;
|
|
24
|
+
totalCost: number;
|
|
25
|
+
layerDistribution: Record<Layer, number>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../src/metrics/collector.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA6C,KAAK,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;;IAO9B;;OAEG;IACH,UAAU,CACR,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,EACrE,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI;IAaP;;OAEG;IACH,QAAQ,IAAI;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;KAC1C;CAUF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — metrics/collector.ts
|
|
3
|
+
//
|
|
4
|
+
// 메트릭 수집기. CallTracker + TokenTracker의 데이터를 모아서
|
|
5
|
+
// 집계 가능한 형태로 관리.
|
|
6
|
+
import { CallTracker } from '../cost/call-tracker.js';
|
|
7
|
+
import { TokenTracker } from '../cost/token-tracker.js';
|
|
8
|
+
export class MetricsCollector {
|
|
9
|
+
calls;
|
|
10
|
+
tokens;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.calls = new CallTracker();
|
|
13
|
+
this.tokens = new TokenTracker();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 도구 호출을 기록한다 (호출 메트릭 + 토큰 사용 동시).
|
|
17
|
+
*/
|
|
18
|
+
recordCall(toolName, layer, latencyMs, success, tokenUsage, serverId) {
|
|
19
|
+
this.calls.record(toolName, layer, latencyMs, success, serverId);
|
|
20
|
+
if (tokenUsage) {
|
|
21
|
+
this.tokens.record(toolName, tokenUsage.input, tokenUsage.output, tokenUsage.costPerToken || 0);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 현재 시점의 스냅샷을 반환한다.
|
|
26
|
+
*/
|
|
27
|
+
snapshot() {
|
|
28
|
+
return {
|
|
29
|
+
totalCalls: this.calls.totalCalls(),
|
|
30
|
+
successRate: this.calls.successRate(),
|
|
31
|
+
avgLatencyMs: this.calls.avgLatency(),
|
|
32
|
+
totalTokens: this.tokens.totalTokens(),
|
|
33
|
+
totalCost: this.tokens.totalCost(),
|
|
34
|
+
layerDistribution: this.calls.layerDistribution(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector.js","sourceRoot":"","sources":["../../src/metrics/collector.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,2CAA2C;AAC3C,EAAE;AACF,gDAAgD;AAChD,iBAAiB;AAGjB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,MAAM,OAAO,gBAAgB;IAClB,KAAK,CAAc;IACnB,MAAM,CAAe;IAE9B;QACE,IAAI,CAAC,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,UAAU,CACR,QAAgB,EAChB,KAAY,EACZ,SAAiB,EACjB,OAAgB,EAChB,UAAqE,EACrE,QAAiB;QAEjB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAEjE,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,MAAM,CAChB,QAAQ,EACR,UAAU,CAAC,KAAK,EAChB,UAAU,CAAC,MAAM,EACjB,UAAU,CAAC,YAAY,IAAI,CAAC,CAC7B,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,QAAQ;QAQN,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE;YACnC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YACrC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE;YACrC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE;YACtC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;YAClC,iBAAiB,EAAE,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE;SAClD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AggregatedMetrics } from '../types.js';
|
|
2
|
+
export declare class MetricsExporter {
|
|
3
|
+
/**
|
|
4
|
+
* JSON 형식으로 내보낸다.
|
|
5
|
+
*/
|
|
6
|
+
toJSON(metrics: AggregatedMetrics): string;
|
|
7
|
+
/**
|
|
8
|
+
* Prometheus 텍스트 형식으로 내보낸다.
|
|
9
|
+
* Prometheus의 /metrics 엔드포인트에서 사용.
|
|
10
|
+
*/
|
|
11
|
+
toPrometheus(metrics: AggregatedMetrics): string;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=exporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../../src/metrics/exporter.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAS,MAAM,aAAa,CAAC;AAI5D,qBAAa,eAAe;IAC1B;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM;IAI1C;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM;CA4CjD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — metrics/exporter.ts
|
|
3
|
+
//
|
|
4
|
+
// 메트릭을 외부 형식으로 내보낸다.
|
|
5
|
+
// Prometheus 텍스트 포맷 + JSON.
|
|
6
|
+
const LAYERS = ['L1', 'L2', 'L3', 'L4', 'L5', 'L6', 'L7'];
|
|
7
|
+
export class MetricsExporter {
|
|
8
|
+
/**
|
|
9
|
+
* JSON 형식으로 내보낸다.
|
|
10
|
+
*/
|
|
11
|
+
toJSON(metrics) {
|
|
12
|
+
return JSON.stringify(metrics, null, 2);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Prometheus 텍스트 형식으로 내보낸다.
|
|
16
|
+
* Prometheus의 /metrics 엔드포인트에서 사용.
|
|
17
|
+
*/
|
|
18
|
+
toPrometheus(metrics) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
// 총 호출 수
|
|
21
|
+
lines.push('# HELP air_tool_calls_total Total number of tool calls');
|
|
22
|
+
lines.push('# TYPE air_tool_calls_total counter');
|
|
23
|
+
lines.push(`air_tool_calls_total ${metrics.totalCalls}`);
|
|
24
|
+
// 성공률
|
|
25
|
+
lines.push('# HELP air_tool_success_rate Tool call success rate');
|
|
26
|
+
lines.push('# TYPE air_tool_success_rate gauge');
|
|
27
|
+
lines.push(`air_tool_success_rate ${metrics.successRate.toFixed(4)}`);
|
|
28
|
+
// 평균 지연시간
|
|
29
|
+
lines.push('# HELP air_tool_latency_avg_ms Average tool call latency in ms');
|
|
30
|
+
lines.push('# TYPE air_tool_latency_avg_ms gauge');
|
|
31
|
+
lines.push(`air_tool_latency_avg_ms ${metrics.avgLatencyMs.toFixed(2)}`);
|
|
32
|
+
// 총 토큰
|
|
33
|
+
lines.push('# HELP air_tokens_total Total tokens consumed');
|
|
34
|
+
lines.push('# TYPE air_tokens_total counter');
|
|
35
|
+
lines.push(`air_tokens_total ${metrics.totalTokens}`);
|
|
36
|
+
// 총 비용
|
|
37
|
+
lines.push('# HELP air_cost_total Total estimated cost in USD');
|
|
38
|
+
lines.push('# TYPE air_cost_total counter');
|
|
39
|
+
lines.push(`air_cost_total ${metrics.totalCost.toFixed(6)}`);
|
|
40
|
+
// 계층별 분포
|
|
41
|
+
lines.push('# HELP air_layer_calls Tool calls by layer');
|
|
42
|
+
lines.push('# TYPE air_layer_calls gauge');
|
|
43
|
+
for (const layer of LAYERS) {
|
|
44
|
+
lines.push(`air_layer_calls{layer="${layer}"} ${metrics.layerDistribution[layer] || 0}`);
|
|
45
|
+
}
|
|
46
|
+
// 도구별 호출 수
|
|
47
|
+
lines.push('# HELP air_tool_calls Tool calls by tool name');
|
|
48
|
+
lines.push('# TYPE air_tool_calls gauge');
|
|
49
|
+
for (const [tool, count] of Object.entries(metrics.toolCounts)) {
|
|
50
|
+
lines.push(`air_tool_calls{tool="${tool}"} ${count}`);
|
|
51
|
+
}
|
|
52
|
+
return lines.join('\n') + '\n';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=exporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../../src/metrics/exporter.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,0CAA0C;AAC1C,EAAE;AACF,qBAAqB;AACrB,4BAA4B;AAI5B,MAAM,MAAM,GAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEnE,MAAM,OAAO,eAAe;IAC1B;;OAEG;IACH,MAAM,CAAC,OAA0B;QAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,OAA0B;QACrC,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,SAAS;QACT,KAAK,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,wBAAwB,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QAEzD,MAAM;QACN,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC,yBAAyB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAEtE,UAAU;QACV,KAAK,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;QAC7E,KAAK,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,2BAA2B,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAEzE,OAAO;QACP,KAAK,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,CAAC,oBAAoB,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QAEtD,OAAO;QACP,KAAK,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;QAChE,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,kBAAkB,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAE7D,SAAS;QACT,KAAK,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAC3C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,0BAA0B,KAAK,MAAM,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3F,CAAC;QAED,WAAW;QACX,KAAK,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/D,KAAK,CAAC,IAAI,CAAC,wBAAwB,IAAI,MAAM,KAAK,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACjC,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — metrics/index.ts
|
|
3
|
+
export { MetricsCollector } from './collector.js';
|
|
4
|
+
export { MetricsAggregator } from './aggregator.js';
|
|
5
|
+
export { MetricsExporter } from './exporter.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,uCAAuC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Layer } from '../types.js';
|
|
2
|
+
export interface CostCandidate {
|
|
3
|
+
serverId: string;
|
|
4
|
+
toolName: string;
|
|
5
|
+
layer: Layer;
|
|
6
|
+
/** 최근 평균 지연시간 (ms) */
|
|
7
|
+
avgLatencyMs?: number;
|
|
8
|
+
/** 최근 성공률 (0~1) */
|
|
9
|
+
successRate?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface CostRouteResult {
|
|
12
|
+
selected: CostCandidate;
|
|
13
|
+
reason: string;
|
|
14
|
+
score: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 후보들 중 최소 비용 경로를 선택한다.
|
|
18
|
+
*
|
|
19
|
+
* 점수 = costWeight × (1 / successRate) × latencyFactor
|
|
20
|
+
* → 낮을수록 좋음
|
|
21
|
+
*/
|
|
22
|
+
export declare function selectCheapest(candidates: CostCandidate[]): CostRouteResult;
|
|
23
|
+
//# sourceMappingURL=cost-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-router.d.ts","sourceRoot":"","sources":["../../src/router/cost-router.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,sBAAsB;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,eAAe,CAiC3E"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/meter — router/cost-router.ts
|
|
3
|
+
//
|
|
4
|
+
// 최소 비용 경로 라우팅.
|
|
5
|
+
// 같은 도구를 여러 서버가 제공할 때, 계층(L1~L7) 기반으로
|
|
6
|
+
// 가장 비용 효율적인 서버를 선택한다.
|
|
7
|
+
// Pylon-7의 핵심: "최소 비용으로 목적 달성."
|
|
8
|
+
import { LAYER_DEFINITIONS } from '../classifier/layer-defs.js';
|
|
9
|
+
/**
|
|
10
|
+
* 후보들 중 최소 비용 경로를 선택한다.
|
|
11
|
+
*
|
|
12
|
+
* 점수 = costWeight × (1 / successRate) × latencyFactor
|
|
13
|
+
* → 낮을수록 좋음
|
|
14
|
+
*/
|
|
15
|
+
export function selectCheapest(candidates) {
|
|
16
|
+
if (candidates.length === 0) {
|
|
17
|
+
throw new Error('No candidates for cost routing');
|
|
18
|
+
}
|
|
19
|
+
if (candidates.length === 1) {
|
|
20
|
+
return {
|
|
21
|
+
selected: candidates[0],
|
|
22
|
+
reason: 'Only one candidate',
|
|
23
|
+
score: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const scored = candidates.map((c) => {
|
|
27
|
+
const layerDef = LAYER_DEFINITIONS[c.layer];
|
|
28
|
+
const costWeight = layerDef.costWeight;
|
|
29
|
+
const successFactor = 1 / (c.successRate ?? 1);
|
|
30
|
+
const latencyFactor = 1 + (c.avgLatencyMs ?? 0) / 10_000; // 10초 기준 정규화
|
|
31
|
+
const score = costWeight * successFactor * latencyFactor;
|
|
32
|
+
return { candidate: c, score };
|
|
33
|
+
});
|
|
34
|
+
// 점수 오름차순 정렬 (낮을수록 좋음)
|
|
35
|
+
scored.sort((a, b) => a.score - b.score);
|
|
36
|
+
const best = scored[0];
|
|
37
|
+
return {
|
|
38
|
+
selected: best.candidate,
|
|
39
|
+
reason: `Cheapest path: ${best.candidate.layer} (score: ${best.score.toFixed(2)})`,
|
|
40
|
+
score: best.score,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=cost-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-router.js","sourceRoot":"","sources":["../../src/router/cost-router.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,4CAA4C;AAC5C,EAAE;AACF,gBAAgB;AAChB,sCAAsC;AACtC,uBAAuB;AACvB,gCAAgC;AAGhC,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAkBhE;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,UAA2B;IACxD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;YACvB,MAAM,EAAE,oBAAoB;YAC5B,KAAK,EAAE,CAAC;SACT,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAClC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;QACvC,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,aAAa;QAEvE,MAAM,KAAK,GAAG,UAAU,GAAG,aAAa,GAAG,aAAa,CAAC;QAEzD,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,uBAAuB;IACvB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAEvB,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,SAAS;QACxB,MAAM,EAAE,kBAAkB,IAAI,CAAC,SAAS,CAAC,KAAK,YAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAClF,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,sCAAsC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** 7-Layer 계층 정의 (Pylon-7 기반) */
|
|
2
|
+
export type Layer = 'L1' | 'L2' | 'L3' | 'L4' | 'L5' | 'L6' | 'L7';
|
|
3
|
+
/** 계층별 정보 */
|
|
4
|
+
export interface LayerDef {
|
|
5
|
+
layer: Layer;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
/** 상대적 토큰 비용 (L1=1, L7=100) */
|
|
9
|
+
costWeight: number;
|
|
10
|
+
/** 위험도 (0~1) */
|
|
11
|
+
riskLevel: number;
|
|
12
|
+
}
|
|
13
|
+
/** 분류 결과 */
|
|
14
|
+
export interface ClassificationResult {
|
|
15
|
+
layer: Layer;
|
|
16
|
+
confidence: number;
|
|
17
|
+
reason: string;
|
|
18
|
+
}
|
|
19
|
+
/** 토큰 사용 기록 */
|
|
20
|
+
export interface TokenUsage {
|
|
21
|
+
toolName: string;
|
|
22
|
+
inputTokens: number;
|
|
23
|
+
outputTokens: number;
|
|
24
|
+
totalTokens: number;
|
|
25
|
+
estimatedCost: number;
|
|
26
|
+
timestamp: Date;
|
|
27
|
+
}
|
|
28
|
+
/** 호출 메트릭 */
|
|
29
|
+
export interface CallMetric {
|
|
30
|
+
toolName: string;
|
|
31
|
+
serverId?: string;
|
|
32
|
+
layer: Layer;
|
|
33
|
+
latencyMs: number;
|
|
34
|
+
success: boolean;
|
|
35
|
+
timestamp: Date;
|
|
36
|
+
tokenUsage?: TokenUsage;
|
|
37
|
+
}
|
|
38
|
+
/** 집계 메트릭 */
|
|
39
|
+
export interface AggregatedMetrics {
|
|
40
|
+
/** 총 호출 수 */
|
|
41
|
+
totalCalls: number;
|
|
42
|
+
/** 성공률 (0~1) */
|
|
43
|
+
successRate: number;
|
|
44
|
+
/** 평균 지연시간 (ms) */
|
|
45
|
+
avgLatencyMs: number;
|
|
46
|
+
/** 총 토큰 사용량 */
|
|
47
|
+
totalTokens: number;
|
|
48
|
+
/** 총 추정 비용 ($) */
|
|
49
|
+
totalCost: number;
|
|
50
|
+
/** 계층별 호출 분포 */
|
|
51
|
+
layerDistribution: Record<Layer, number>;
|
|
52
|
+
/** 도구별 호출 수 */
|
|
53
|
+
toolCounts: Record<string, number>;
|
|
54
|
+
/** 집계 시작 시각 */
|
|
55
|
+
from: Date;
|
|
56
|
+
/** 집계 종료 시각 */
|
|
57
|
+
to: Date;
|
|
58
|
+
}
|
|
59
|
+
/** 예산 설정 */
|
|
60
|
+
export interface BudgetConfig {
|
|
61
|
+
/** 일일 최대 비용 ($) */
|
|
62
|
+
dailyLimit?: number;
|
|
63
|
+
/** 월간 최대 비용 ($) */
|
|
64
|
+
monthlyLimit?: number;
|
|
65
|
+
/** 호출당 최대 토큰 */
|
|
66
|
+
maxTokensPerCall?: number;
|
|
67
|
+
/** 예산 초과 시 동작 */
|
|
68
|
+
onExceed: 'warn' | 'block';
|
|
69
|
+
}
|
|
70
|
+
/** 예산 체크 결과 */
|
|
71
|
+
export interface BudgetCheckResult {
|
|
72
|
+
allowed: boolean;
|
|
73
|
+
remaining: number;
|
|
74
|
+
limit: number;
|
|
75
|
+
period: 'daily' | 'monthly' | 'per-call';
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,iCAAiC;AACjC,MAAM,MAAM,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnE,aAAa;AACb,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,YAAY;AACZ,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAe;AACf,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,aAAa;AACb,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,aAAa;AACb,MAAM,WAAW,iBAAiB;IAChC,aAAa;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB;IAChB,iBAAiB,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACzC,eAAe;IACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,eAAe;IACf,IAAI,EAAE,IAAI,CAAC;IACX,eAAe;IACf,EAAE,EAAE,IAAI,CAAC;CACV;AAED,YAAY;AACZ,MAAM,WAAW,YAAY;IAC3B,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB;IACjB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,eAAe;AACf,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;CAC1C"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,+BAA+B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@airmcp-dev/meter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP performance metering — 7-Layer classification, token cost tracking, metrics export",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"lint": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@airmcp-dev/core": "^0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"typescript": "^5.5.0",
|
|
22
|
+
"vitest": "^2.0.0",
|
|
23
|
+
"@types/node": "^20.0.0"
|
|
24
|
+
},
|
|
25
|
+
"author": "CodePedia Labs",
|
|
26
|
+
"homepage": "https://airmcp.dev",
|
|
27
|
+
"bugs": { "url": "https://github.com/airmcp-dev/air/issues" },
|
|
28
|
+
"repository": { "type": "git", "url": "https://github.com/airmcp-dev/air", "directory": "packages/meter" },
|
|
29
|
+
"keywords": ["mcp", "ai", "agent", "air"],
|
|
30
|
+
"license": "Apache-2.0"
|
|
31
|
+
}
|