@n2world/orchestrator 1.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/dist/agent-os-rd.d.ts +100 -0
- package/dist/agent-os-rd.js +258 -0
- package/dist/audit-store.d.ts +14 -0
- package/dist/audit-store.js +107 -0
- package/dist/beta-runner.d.ts +95 -0
- package/dist/beta-runner.js +251 -0
- package/dist/beta.d.ts +102 -0
- package/dist/beta.js +180 -0
- package/dist/browser-agent.d.ts +90 -0
- package/dist/browser-agent.js +223 -0
- package/dist/channel-gateway.d.ts +74 -0
- package/dist/channel-gateway.js +270 -0
- package/dist/channels.d.ts +120 -0
- package/dist/channels.js +432 -0
- package/dist/chat-store.d.ts +29 -0
- package/dist/chat-store.js +120 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +607 -0
- package/dist/command-screen.d.ts +12 -0
- package/dist/command-screen.js +44 -0
- package/dist/commit-gate.d.ts +98 -0
- package/dist/commit-gate.js +258 -0
- package/dist/companion-api.d.ts +37 -0
- package/dist/companion-api.js +101 -0
- package/dist/conversation-graph.d.ts +39 -0
- package/dist/conversation-graph.js +92 -0
- package/dist/cost-estimator.d.ts +27 -0
- package/dist/cost-estimator.js +42 -0
- package/dist/cron-runner.d.ts +31 -0
- package/dist/cron-runner.js +46 -0
- package/dist/dashboard/chat.html +326 -0
- package/dist/dashboard/dental.html +58 -0
- package/dist/dashboard/freebie.png +0 -0
- package/dist/dashboard/icon-192.png +0 -0
- package/dist/dashboard/index.html +892 -0
- package/dist/dashboard/manifest.json +15 -0
- package/dist/dashboard/service-worker.js +28 -0
- package/dist/dashboard-server.d.ts +37 -0
- package/dist/dashboard-server.js +457 -0
- package/dist/dental-intake-service.d.ts +37 -0
- package/dist/dental-intake-service.js +61 -0
- package/dist/dental-metrics.d.ts +25 -0
- package/dist/dental-metrics.js +37 -0
- package/dist/docking.d.ts +36 -0
- package/dist/docking.js +73 -0
- package/dist/finance-mcts-candidate.d.ts +37 -0
- package/dist/finance-mcts-candidate.js +106 -0
- package/dist/finance-regulation-kr.d.ts +33 -0
- package/dist/finance-regulation-kr.js +104 -0
- package/dist/finance-workflow.d.ts +135 -0
- package/dist/finance-workflow.js +242 -0
- package/dist/gateway.d.ts +18 -0
- package/dist/gateway.js +123 -0
- package/dist/governance.d.ts +39 -0
- package/dist/governance.js +48 -0
- package/dist/governed-executor.d.ts +31 -0
- package/dist/governed-executor.js +63 -0
- package/dist/governed-llm.d.ts +41 -0
- package/dist/governed-llm.js +83 -0
- package/dist/gpu-bridge.d.ts +16 -0
- package/dist/gpu-bridge.js +53 -0
- package/dist/health.d.ts +47 -0
- package/dist/health.js +66 -0
- package/dist/identity-link.d.ts +32 -0
- package/dist/identity-link.js +98 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +417 -0
- package/dist/integrations/emr-adapter.d.ts +41 -0
- package/dist/integrations/emr-adapter.js +63 -0
- package/dist/kakao-oauth.d.ts +16 -0
- package/dist/kakao-oauth.js +87 -0
- package/dist/knowledge-graph.d.ts +53 -0
- package/dist/knowledge-graph.js +156 -0
- package/dist/llm.d.ts +65 -0
- package/dist/llm.js +357 -0
- package/dist/mcp-client-guard.d.ts +32 -0
- package/dist/mcp-client-guard.js +179 -0
- package/dist/mcp-macaroon.d.ts +75 -0
- package/dist/mcp-macaroon.js +161 -0
- package/dist/mcts-kernel-bridge.d.ts +36 -0
- package/dist/mcts-kernel-bridge.js +99 -0
- package/dist/mcts-prior.d.ts +79 -0
- package/dist/mcts-prior.js +170 -0
- package/dist/model-router.d.ts +51 -0
- package/dist/model-router.js +75 -0
- package/dist/multi-axis-lift.d.ts +43 -0
- package/dist/multi-axis-lift.js +141 -0
- package/dist/net-guard.d.ts +39 -0
- package/dist/net-guard.js +141 -0
- package/dist/onboarding.d.ts +38 -0
- package/dist/onboarding.js +94 -0
- package/dist/oracle-anchored-search.d.ts +25 -0
- package/dist/oracle-anchored-search.js +50 -0
- package/dist/oracle.d.ts +22 -0
- package/dist/oracle.js +116 -0
- package/dist/p6-governance.d.ts +150 -0
- package/dist/p6-governance.js +252 -0
- package/dist/pairing.d.ts +22 -0
- package/dist/pairing.js +81 -0
- package/dist/personalization.d.ts +35 -0
- package/dist/personalization.js +73 -0
- package/dist/pglite-hnsw-bridge.d.ts +118 -0
- package/dist/pglite-hnsw-bridge.js +311 -0
- package/dist/pglite-store.d.ts +59 -0
- package/dist/pglite-store.js +180 -0
- package/dist/playbook.d.ts +79 -0
- package/dist/playbook.js +83 -0
- package/dist/playbooks/dental-intake.d.ts +20 -0
- package/dist/playbooks/dental-intake.js +112 -0
- package/dist/predictive-agent.d.ts +157 -0
- package/dist/predictive-agent.js +535 -0
- package/dist/prompt-optimizer.d.ts +18 -0
- package/dist/prompt-optimizer.js +104 -0
- package/dist/rate-limiter.d.ts +25 -0
- package/dist/rate-limiter.js +75 -0
- package/dist/safety-anneal.d.ts +83 -0
- package/dist/safety-anneal.js +153 -0
- package/dist/sandbox-controller.d.ts +12 -0
- package/dist/sandbox-controller.js +95 -0
- package/dist/satisfaction-metrics.d.ts +26 -0
- package/dist/satisfaction-metrics.js +61 -0
- package/dist/sensor-bridge.d.ts +53 -0
- package/dist/sensor-bridge.js +133 -0
- package/dist/session-repair.d.ts +27 -0
- package/dist/session-repair.js +66 -0
- package/dist/slack-finance-intake.d.ts +42 -0
- package/dist/slack-finance-intake.js +122 -0
- package/dist/symbolic-dynamics.d.ts +113 -0
- package/dist/symbolic-dynamics.js +420 -0
- package/dist/telemetry.d.ts +19 -0
- package/dist/telemetry.js +68 -0
- package/dist/text-embedding.d.ts +6 -0
- package/dist/text-embedding.js +42 -0
- package/dist/tier-classifier.d.ts +20 -0
- package/dist/tier-classifier.js +58 -0
- package/dist/tier-guard.d.ts +36 -0
- package/dist/tier-guard.js +56 -0
- package/dist/tui.d.ts +9 -0
- package/dist/tui.js +214 -0
- package/dist/update-security.d.ts +31 -0
- package/dist/update-security.js +112 -0
- package/dist/v-calibration.d.ts +16 -0
- package/dist/v-calibration.js +42 -0
- package/dist/value-calibration.d.ts +41 -0
- package/dist/value-calibration.js +133 -0
- package/dist/value-head.d.ts +20 -0
- package/dist/value-head.js +91 -0
- package/dist/wal-buffer.d.ts +23 -0
- package/dist/wal-buffer.js +144 -0
- package/dist/wiki-synthesizer.d.ts +80 -0
- package/dist/wiki-synthesizer.js +0 -0
- package/dist/worker-agent.d.ts +10 -0
- package/dist/worker-agent.js +19 -0
- package/package.json +65 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { CrossValidationOracle } from './oracle';
|
|
2
|
+
export type ActionKind = 'read' | 'compute' | 'file-write-temp' | 'file-delete' | 'kernel-resource' | 'external-effect' | 'network-egress';
|
|
3
|
+
export interface ProposedAction {
|
|
4
|
+
id: string;
|
|
5
|
+
/** 실행될 명령/행동 텍스트. */
|
|
6
|
+
command: string;
|
|
7
|
+
kind?: ActionKind;
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
/** 되돌릴 수 없는/외부 효과 행동인가. commit-gate 는 이런 행동에만 호출된다(빈도·비용 통제). */
|
|
11
|
+
export declare function isIrreversible(action: ProposedAction): boolean;
|
|
12
|
+
export type GateLayer = 'not-gated' | 'rule' | 'model-critic' | 'deny-by-default' | 'rule-only-passthrough';
|
|
13
|
+
export interface CriticVerdict {
|
|
14
|
+
flagged: boolean;
|
|
15
|
+
confidence: number;
|
|
16
|
+
reason: string;
|
|
17
|
+
tokens: number | null;
|
|
18
|
+
}
|
|
19
|
+
export type CriticFn = (action: ProposedAction) => Promise<CriticVerdict>;
|
|
20
|
+
export interface GateDecision {
|
|
21
|
+
id: string;
|
|
22
|
+
allowed: boolean;
|
|
23
|
+
layer: GateLayer;
|
|
24
|
+
reason: string;
|
|
25
|
+
ruleFlagged: boolean;
|
|
26
|
+
ruleMatched: string[];
|
|
27
|
+
criticFlagged: boolean | null;
|
|
28
|
+
criticConfidence: number | null;
|
|
29
|
+
denyByDefault: boolean;
|
|
30
|
+
/** 이 결정에 든 비용(누적은 stats()). */
|
|
31
|
+
cost: {
|
|
32
|
+
criticCalls: number;
|
|
33
|
+
latencyMs: number;
|
|
34
|
+
tokens: number | null;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface CommitGateOptions {
|
|
38
|
+
/** 모델-비평(LLM) 사용. 기본 true. 키 없거나 실패하면 자동으로 deny-by-default 폴백. */
|
|
39
|
+
useCritic?: boolean;
|
|
40
|
+
/** 비평 불가/저신뢰 시 되돌릴 수 없는 행동 거부(안전망). 기본 true. */
|
|
41
|
+
denyByDefault?: boolean;
|
|
42
|
+
/** 저신뢰 임계(이 미만이면 deny-by-default). 기본 0.5. */
|
|
43
|
+
confidenceFloor?: number;
|
|
44
|
+
/** 비평 함수 주입(테스트/모의용). 미지정 시 llm.ts 기반 기본 비평. */
|
|
45
|
+
critic?: CriticFn;
|
|
46
|
+
}
|
|
47
|
+
export declare class CommitGate {
|
|
48
|
+
private oracle;
|
|
49
|
+
private notGated;
|
|
50
|
+
private gated;
|
|
51
|
+
private ruleDenies;
|
|
52
|
+
private criticDenies;
|
|
53
|
+
private criticAllows;
|
|
54
|
+
private denyByDefaults;
|
|
55
|
+
private criticCalls;
|
|
56
|
+
private totalLatencyMs;
|
|
57
|
+
private totalTokens;
|
|
58
|
+
private tokenMeasured;
|
|
59
|
+
constructor(oracle?: CrossValidationOracle);
|
|
60
|
+
/**
|
|
61
|
+
* 되돌릴 수 없는 행동의 commit-gate 평가. 규칙층(oracle 재사용) → 모델-비평(llm 재사용) → deny-by-default.
|
|
62
|
+
*/
|
|
63
|
+
evaluate(action: ProposedAction, opts?: CommitGateOptions): Promise<GateDecision>;
|
|
64
|
+
private decision;
|
|
65
|
+
/** 누적 통계 — 호출 빈도·지연·토큰 비용 공개(헌법 제2조). */
|
|
66
|
+
stats(): {
|
|
67
|
+
notGated: number;
|
|
68
|
+
gated: number;
|
|
69
|
+
ruleDenies: number;
|
|
70
|
+
criticDenies: number;
|
|
71
|
+
criticAllows: number;
|
|
72
|
+
denyByDefaults: number;
|
|
73
|
+
criticCalls: number;
|
|
74
|
+
avgCriticLatencyMs: number;
|
|
75
|
+
totalTokens: number | null;
|
|
76
|
+
criticInvocationRate: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export interface GoldAction extends ProposedAction {
|
|
80
|
+
violation: boolean;
|
|
81
|
+
tier: 'rule' | 'semantic';
|
|
82
|
+
}
|
|
83
|
+
export interface GateEvalResult {
|
|
84
|
+
total: number;
|
|
85
|
+
violations: number;
|
|
86
|
+
benign: number;
|
|
87
|
+
/** 위반 포착률 = 거부된 위반 / 전체 위반. */
|
|
88
|
+
violationCatchRate: number;
|
|
89
|
+
/** 양성 과차단율 = 거부된 양성 / 전체 양성(deny-by-default 비용). */
|
|
90
|
+
benignOverBlockRate: number;
|
|
91
|
+
byTier: Record<'rule' | 'semantic', {
|
|
92
|
+
violations: number;
|
|
93
|
+
caught: number;
|
|
94
|
+
catchRate: number;
|
|
95
|
+
}>;
|
|
96
|
+
}
|
|
97
|
+
/** 게이트로 골드셋을 평가해 위반 포착률·과차단율을 tier 별로 분리 보고. */
|
|
98
|
+
export declare function evaluateGate(gate: CommitGate, gold: GoldAction[], opts?: CommitGateOptions): Promise<GateEvalResult>;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// P4.7 — commit-gate 모델-비평 (계획서 v2.4 §4.4, oracle.ts/llm.ts 재사용·축소)
|
|
4
|
+
// ----------------------------------------------------------------------------
|
|
5
|
+
// 되돌릴 수 없는 행동에 한해 헌법 정합성을 검증한다. **새 서브시스템을 만들지 않고**
|
|
6
|
+
// `oracle.ts`(규칙 스크린)·`llm.ts`(모델-비평)를 재사용한다(단순화·복잡도 예산).
|
|
7
|
+
//
|
|
8
|
+
// 무한 회귀 정직 인정(master §2): **모델-비평가도 검증되지 않은 오라클**이다. 최종 접지는
|
|
9
|
+
// 베타 사용자 라벨(B0/B1, 접지 스택 3층)이며, 본 게이트는 그 위의 보조 검증일 뿐이다.
|
|
10
|
+
// Deny-by-Default: OOD/저신뢰 시 되돌릴 수 없는 행동을 거부하는 안전망 — **충분조건은 아니다.**
|
|
11
|
+
// 비용 공개(헌법 제2조): 비평 호출 빈도·지연·토큰을 실측 누적해 공개한다. 미공개면 비평 층 단순화(영가설).
|
|
12
|
+
// ============================================================================
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.CommitGate = void 0;
|
|
15
|
+
exports.isIrreversible = isIrreversible;
|
|
16
|
+
exports.evaluateGate = evaluateGate;
|
|
17
|
+
const oracle_1 = require("./oracle");
|
|
18
|
+
const llm_1 = require("./llm");
|
|
19
|
+
const IRREVERSIBLE_KINDS = new Set([
|
|
20
|
+
'file-delete',
|
|
21
|
+
'kernel-resource',
|
|
22
|
+
'external-effect',
|
|
23
|
+
'network-egress',
|
|
24
|
+
]);
|
|
25
|
+
// kind 미지정 시 텍스트 휴리스틱(파괴/외부효과 키워드).
|
|
26
|
+
const IRREVERSIBLE_HINT = /\b(rm|rmdir|dd|mkfs|drop\s+table|delete\s+from|truncate|shutdown|reboot|halt|poweroff|chmod|chown|iptables|ufw|setenforce|cpu\.weight|cgroup|kill(all)?|pkill|curl|wget|scp|rsync|git\s+push)\b/i;
|
|
27
|
+
/** 되돌릴 수 없는/외부 효과 행동인가. commit-gate 는 이런 행동에만 호출된다(빈도·비용 통제). */
|
|
28
|
+
function isIrreversible(action) {
|
|
29
|
+
if (action.kind)
|
|
30
|
+
return IRREVERSIBLE_KINDS.has(action.kind);
|
|
31
|
+
return IRREVERSIBLE_HINT.test(action.command);
|
|
32
|
+
}
|
|
33
|
+
/** llm.ts 재사용 기본 모델-비평 — 헌법 원칙 위반 여부를 JSON 으로 판정. */
|
|
34
|
+
async function defaultCritic(action) {
|
|
35
|
+
const system = '당신은 되돌릴 수 없는 행동의 헌법 정합성을 검토하는 비평가다. ' +
|
|
36
|
+
'원칙: (1)파괴/데이터 손실 금지 (2)동의 없는 외부 반출 금지 (3)시스템 안전 훼손 금지 (4)사용자 영향 프리뷰. ' +
|
|
37
|
+
'반드시 JSON 한 줄로만 답하라: {"decision":"ALLOW"|"DENY","confidence":0~1,"reason":"..."}';
|
|
38
|
+
const prompt = `행동 id: ${action.id}\n명령: ${action.command}\n설명: ${action.description ?? '(없음)'}\n` +
|
|
39
|
+
'이 되돌릴 수 없는 행동이 위 원칙을 위반하는가? JSON 으로만 답하라.';
|
|
40
|
+
const { text, usage } = await (0, llm_1.llmCompleteUsage)(prompt, system);
|
|
41
|
+
const parsed = parseCriticJson(text);
|
|
42
|
+
return {
|
|
43
|
+
flagged: parsed.decision === 'DENY',
|
|
44
|
+
confidence: parsed.confidence,
|
|
45
|
+
reason: parsed.reason,
|
|
46
|
+
tokens: usage?.inputTokens != null || usage?.outputTokens != null
|
|
47
|
+
? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0)
|
|
48
|
+
: null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function parseCriticJson(text) {
|
|
52
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
53
|
+
if (m) {
|
|
54
|
+
try {
|
|
55
|
+
const o = JSON.parse(m[0]);
|
|
56
|
+
const decision = String(o.decision).toUpperCase() === 'DENY' ? 'DENY' : 'ALLOW';
|
|
57
|
+
const confidence = typeof o.confidence === 'number' ? Math.max(0, Math.min(1, o.confidence)) : 0.5;
|
|
58
|
+
return { decision, confidence, reason: String(o.reason ?? '') };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* fall through */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 파싱 실패 → 저신뢰 ALLOW(상위에서 deny-by-default 가 처리).
|
|
65
|
+
return { decision: 'ALLOW', confidence: 0, reason: '비평 응답 파싱 실패(저신뢰)' };
|
|
66
|
+
}
|
|
67
|
+
class CommitGate {
|
|
68
|
+
oracle;
|
|
69
|
+
notGated = 0;
|
|
70
|
+
gated = 0;
|
|
71
|
+
ruleDenies = 0;
|
|
72
|
+
criticDenies = 0;
|
|
73
|
+
criticAllows = 0;
|
|
74
|
+
denyByDefaults = 0;
|
|
75
|
+
criticCalls = 0;
|
|
76
|
+
totalLatencyMs = 0;
|
|
77
|
+
totalTokens = 0;
|
|
78
|
+
tokenMeasured = false;
|
|
79
|
+
constructor(oracle) {
|
|
80
|
+
this.oracle = oracle ?? new oracle_1.CrossValidationOracle();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 되돌릴 수 없는 행동의 commit-gate 평가. 규칙층(oracle 재사용) → 모델-비평(llm 재사용) → deny-by-default.
|
|
84
|
+
*/
|
|
85
|
+
async evaluate(action, opts = {}) {
|
|
86
|
+
// 1) 규칙층(oracle.ts 재사용)은 **모든 행동에 항상 적용**한다 — 싸고 안전한 1차 망.
|
|
87
|
+
// 알려진 위험 패턴이면 되돌릴 수 있든 없든 즉시 거부(예: 포크밤·git reset --hard).
|
|
88
|
+
const screen = this.oracle.screenDangerousAction(action.command);
|
|
89
|
+
if (screen.flagged) {
|
|
90
|
+
this.ruleDenies++;
|
|
91
|
+
return this.decision(action, false, 'rule', `규칙 위반: ${screen.matched.join(', ')}`, {
|
|
92
|
+
ruleFlagged: true,
|
|
93
|
+
ruleMatched: screen.matched,
|
|
94
|
+
criticFlagged: null,
|
|
95
|
+
criticConfidence: null,
|
|
96
|
+
denyByDefault: false,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// 2) 규칙 통과 + 되돌릴 수 있는 행동은 비싼 모델-비평을 호출하지 않는다(빈도·비용 통제).
|
|
100
|
+
if (!isIrreversible(action)) {
|
|
101
|
+
this.notGated++;
|
|
102
|
+
return this.decision(action, true, 'not-gated', '되돌릴 수 있는 행동 — 비평 비호출', {
|
|
103
|
+
ruleFlagged: false,
|
|
104
|
+
ruleMatched: [],
|
|
105
|
+
criticFlagged: null,
|
|
106
|
+
criticConfidence: null,
|
|
107
|
+
denyByDefault: false,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
this.gated++;
|
|
111
|
+
const useCritic = opts.useCritic !== false;
|
|
112
|
+
const denyByDefault = opts.denyByDefault !== false;
|
|
113
|
+
const floor = opts.confidenceFloor ?? 0.5;
|
|
114
|
+
// 3) 모델-비평(llm.ts 재사용) — 규칙을 통과한 의미적 위반 포착.
|
|
115
|
+
if (useCritic) {
|
|
116
|
+
const criticFn = opts.critic ?? defaultCritic;
|
|
117
|
+
const t0 = nowMs();
|
|
118
|
+
let verdict = null;
|
|
119
|
+
try {
|
|
120
|
+
verdict = await criticFn(action);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
verdict = null; // 비평 불가(키 없음/오류)
|
|
124
|
+
}
|
|
125
|
+
const latency = nowMs() - t0;
|
|
126
|
+
if (verdict) {
|
|
127
|
+
this.criticCalls++;
|
|
128
|
+
this.totalLatencyMs += latency;
|
|
129
|
+
if (verdict.tokens != null) {
|
|
130
|
+
this.totalTokens += verdict.tokens;
|
|
131
|
+
this.tokenMeasured = true;
|
|
132
|
+
}
|
|
133
|
+
const cost = { criticCalls: 1, latencyMs: +latency.toFixed(3), tokens: verdict.tokens };
|
|
134
|
+
if (verdict.flagged) {
|
|
135
|
+
this.criticDenies++;
|
|
136
|
+
return this.decision(action, false, 'model-critic', `비평 위반: ${verdict.reason}`, {
|
|
137
|
+
ruleFlagged: false,
|
|
138
|
+
ruleMatched: [],
|
|
139
|
+
criticFlagged: true,
|
|
140
|
+
criticConfidence: verdict.confidence,
|
|
141
|
+
denyByDefault: false,
|
|
142
|
+
cost,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// 비평이 통과시켰으나 저신뢰 → deny-by-default 안전망(충분조건 아님).
|
|
146
|
+
if (verdict.confidence < floor && denyByDefault) {
|
|
147
|
+
this.denyByDefaults++;
|
|
148
|
+
return this.decision(action, false, 'deny-by-default', `저신뢰 비평(conf=${verdict.confidence}) — 안전망 거부(충분조건 아님)`, {
|
|
149
|
+
ruleFlagged: false,
|
|
150
|
+
ruleMatched: [],
|
|
151
|
+
criticFlagged: false,
|
|
152
|
+
criticConfidence: verdict.confidence,
|
|
153
|
+
denyByDefault: true,
|
|
154
|
+
cost,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
this.criticAllows++;
|
|
158
|
+
return this.decision(action, true, 'model-critic', `비평 통과(conf=${verdict.confidence})`, {
|
|
159
|
+
ruleFlagged: false,
|
|
160
|
+
ruleMatched: [],
|
|
161
|
+
criticFlagged: false,
|
|
162
|
+
criticConfidence: verdict.confidence,
|
|
163
|
+
denyByDefault: false,
|
|
164
|
+
cost,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// 비평 불가 → deny-by-default(되돌릴 수 없는 행동).
|
|
168
|
+
}
|
|
169
|
+
// 4) 비평 미사용/불가 → deny-by-default 또는 규칙만 통과.
|
|
170
|
+
if (denyByDefault) {
|
|
171
|
+
this.denyByDefaults++;
|
|
172
|
+
return this.decision(action, false, 'deny-by-default', '비평 불가 — 되돌릴 수 없는 행동 안전망 거부(충분조건 아님)', {
|
|
173
|
+
ruleFlagged: false,
|
|
174
|
+
ruleMatched: [],
|
|
175
|
+
criticFlagged: null,
|
|
176
|
+
criticConfidence: null,
|
|
177
|
+
denyByDefault: true,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return this.decision(action, true, 'rule-only-passthrough', '규칙만 통과(비평 없음 — 명시)', {
|
|
181
|
+
ruleFlagged: false,
|
|
182
|
+
ruleMatched: [],
|
|
183
|
+
criticFlagged: null,
|
|
184
|
+
criticConfidence: null,
|
|
185
|
+
denyByDefault: false,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
decision(action, allowed, layer, reason, parts) {
|
|
189
|
+
return {
|
|
190
|
+
id: action.id,
|
|
191
|
+
allowed,
|
|
192
|
+
layer,
|
|
193
|
+
reason,
|
|
194
|
+
ruleFlagged: parts.ruleFlagged ?? false,
|
|
195
|
+
ruleMatched: parts.ruleMatched ?? [],
|
|
196
|
+
criticFlagged: parts.criticFlagged ?? null,
|
|
197
|
+
criticConfidence: parts.criticConfidence ?? null,
|
|
198
|
+
denyByDefault: parts.denyByDefault ?? false,
|
|
199
|
+
cost: parts.cost ?? { criticCalls: 0, latencyMs: 0, tokens: null },
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/** 누적 통계 — 호출 빈도·지연·토큰 비용 공개(헌법 제2조). */
|
|
203
|
+
stats() {
|
|
204
|
+
return {
|
|
205
|
+
notGated: this.notGated,
|
|
206
|
+
gated: this.gated,
|
|
207
|
+
ruleDenies: this.ruleDenies,
|
|
208
|
+
criticDenies: this.criticDenies,
|
|
209
|
+
criticAllows: this.criticAllows,
|
|
210
|
+
denyByDefaults: this.denyByDefaults,
|
|
211
|
+
criticCalls: this.criticCalls,
|
|
212
|
+
avgCriticLatencyMs: this.criticCalls > 0 ? +(this.totalLatencyMs / this.criticCalls).toFixed(3) : 0,
|
|
213
|
+
totalTokens: this.tokenMeasured ? this.totalTokens : null, // 토큰 미측정(키 없음)이면 null(정직)
|
|
214
|
+
criticInvocationRate: this.gated > 0 ? +(this.criticCalls / this.gated).toFixed(3) : 0,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
exports.CommitGate = CommitGate;
|
|
219
|
+
function nowMs() {
|
|
220
|
+
const g = globalThis;
|
|
221
|
+
return typeof g.performance?.now === 'function' ? g.performance.now() : Date.now();
|
|
222
|
+
}
|
|
223
|
+
/** 게이트로 골드셋을 평가해 위반 포착률·과차단율을 tier 별로 분리 보고. */
|
|
224
|
+
async function evaluateGate(gate, gold, opts = {}) {
|
|
225
|
+
const byTier = {
|
|
226
|
+
rule: { violations: 0, caught: 0, catchRate: 0 },
|
|
227
|
+
semantic: { violations: 0, caught: 0, catchRate: 0 },
|
|
228
|
+
};
|
|
229
|
+
let violations = 0, benign = 0, caughtViolations = 0, blockedBenign = 0;
|
|
230
|
+
for (const g of gold) {
|
|
231
|
+
const d = await gate.evaluate(g, opts);
|
|
232
|
+
const denied = !d.allowed;
|
|
233
|
+
if (g.violation) {
|
|
234
|
+
violations++;
|
|
235
|
+
byTier[g.tier].violations++;
|
|
236
|
+
if (denied) {
|
|
237
|
+
caughtViolations++;
|
|
238
|
+
byTier[g.tier].caught++;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
benign++;
|
|
243
|
+
if (denied)
|
|
244
|
+
blockedBenign++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const t of ['rule', 'semantic']) {
|
|
248
|
+
byTier[t].catchRate = byTier[t].violations > 0 ? +(byTier[t].caught / byTier[t].violations).toFixed(4) : 0;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
total: gold.length,
|
|
252
|
+
violations,
|
|
253
|
+
benign,
|
|
254
|
+
violationCatchRate: violations > 0 ? +(caughtViolations / violations).toFixed(4) : 0,
|
|
255
|
+
benignOverBlockRate: benign > 0 ? +(blockedBenign / benign).toFixed(4) : 0,
|
|
256
|
+
byTier,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { GovernanceController } from './governance';
|
|
2
|
+
export interface CompanionStatus {
|
|
3
|
+
killed: boolean;
|
|
4
|
+
budgetRemaining: number;
|
|
5
|
+
auditCount: number;
|
|
6
|
+
/** 자격증명은 *존재 여부*만(값 절대 비노출). */
|
|
7
|
+
hasExternalKey: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface AuthContext {
|
|
10
|
+
token?: string;
|
|
11
|
+
/** 요청이 루프백(127.0.0.1/::1)에서 왔는가. */
|
|
12
|
+
loopback: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class CompanionApi {
|
|
15
|
+
private gov;
|
|
16
|
+
private token;
|
|
17
|
+
constructor(gov: GovernanceController, token?: string);
|
|
18
|
+
/** 발급 토큰(컴패니언 페어링용). 운영자 본인에게만 1회 표시. */
|
|
19
|
+
pairingToken(): string;
|
|
20
|
+
/** 인증: 루프백 + 토큰 일치만 허용. 둘 중 하나라도 실패면 거부. */
|
|
21
|
+
authorize(ctx: AuthContext): {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
reason: string;
|
|
24
|
+
};
|
|
25
|
+
private timingSafeEq;
|
|
26
|
+
/** 상태 스냅샷 — 인증 필요. 자격증명 값은 절대 포함하지 않는다. */
|
|
27
|
+
status(ctx: AuthContext): {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
status?: CompanionStatus;
|
|
30
|
+
reason: string;
|
|
31
|
+
};
|
|
32
|
+
/** 킬스위치 토글 — 인증 필요. 운영자 컴패니언에서 긴급 중단/재개. */
|
|
33
|
+
setKill(ctx: AuthContext, kill: boolean): {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
reason: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// v2.5.2 Phase 4 — 디바이스 컴패니언 로컬 API 계약 (TS측, 검증 가능)
|
|
4
|
+
// ----------------------------------------------------------------------------
|
|
5
|
+
// 계획서 v2.5.2 §3.2/§Phase4: macOS 메뉴바 등 컴패니언이 코어에 붙는 *로컬 인증* 계약.
|
|
6
|
+
// - 루프백 전용 + 베어러 토큰(자격증명 비노출)
|
|
7
|
+
// - 노출: 세션/비용/킬스위치 상태. **API 키·비밀은 절대 반환하지 않는다.**
|
|
8
|
+
//
|
|
9
|
+
// 정직 고지(제1계명): 본 모듈은 컴패니언 *서버측 계약/인증*이며 테스트로 검증된다.
|
|
10
|
+
// 네이티브 앱(Swift) 자체는 별도(이 환경 미빌드). 토큰·루프백 강제는 라우팅에서 적용해야 효력.
|
|
11
|
+
// ============================================================================
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.CompanionApi = void 0;
|
|
47
|
+
const crypto = __importStar(require("crypto"));
|
|
48
|
+
class CompanionApi {
|
|
49
|
+
gov;
|
|
50
|
+
token;
|
|
51
|
+
constructor(gov, token) {
|
|
52
|
+
this.gov = gov;
|
|
53
|
+
// 토큰 미지정 시 1회성 무작위 발급(자격증명 비노출 원칙).
|
|
54
|
+
this.token = token ?? crypto.randomBytes(24).toString('base64url');
|
|
55
|
+
}
|
|
56
|
+
/** 발급 토큰(컴패니언 페어링용). 운영자 본인에게만 1회 표시. */
|
|
57
|
+
pairingToken() { return this.token; }
|
|
58
|
+
/** 인증: 루프백 + 토큰 일치만 허용. 둘 중 하나라도 실패면 거부. */
|
|
59
|
+
authorize(ctx) {
|
|
60
|
+
if (!ctx.loopback)
|
|
61
|
+
return { ok: false, reason: '루프백 외 접근 거부' };
|
|
62
|
+
if (!ctx.token || !this.timingSafeEq(ctx.token, this.token))
|
|
63
|
+
return { ok: false, reason: '토큰 불일치' };
|
|
64
|
+
return { ok: true, reason: 'ok' };
|
|
65
|
+
}
|
|
66
|
+
timingSafeEq(a, b) {
|
|
67
|
+
const ba = Buffer.from(a), bb = Buffer.from(b);
|
|
68
|
+
if (ba.length !== bb.length)
|
|
69
|
+
return false;
|
|
70
|
+
return crypto.timingSafeEqual(ba, bb);
|
|
71
|
+
}
|
|
72
|
+
/** 상태 스냅샷 — 인증 필요. 자격증명 값은 절대 포함하지 않는다. */
|
|
73
|
+
status(ctx) {
|
|
74
|
+
const auth = this.authorize(ctx);
|
|
75
|
+
if (!auth.ok)
|
|
76
|
+
return { ok: false, reason: auth.reason };
|
|
77
|
+
const s = this.gov.status();
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
reason: 'ok',
|
|
81
|
+
status: {
|
|
82
|
+
killed: s.killed,
|
|
83
|
+
budgetRemaining: s.budgetRemaining,
|
|
84
|
+
auditCount: s.auditCount,
|
|
85
|
+
hasExternalKey: !!(process.env.ANTHROPIC_API_KEY || process.env.GEMINI_API_KEY), // 존재여부만
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** 킬스위치 토글 — 인증 필요. 운영자 컴패니언에서 긴급 중단/재개. */
|
|
90
|
+
setKill(ctx, kill) {
|
|
91
|
+
const auth = this.authorize(ctx);
|
|
92
|
+
if (!auth.ok)
|
|
93
|
+
return { ok: false, reason: auth.reason };
|
|
94
|
+
if (kill)
|
|
95
|
+
this.gov.kill('companion');
|
|
96
|
+
else
|
|
97
|
+
this.gov.reset();
|
|
98
|
+
return { ok: true, reason: kill ? 'killed' : 'reset' };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.CompanionApi = CompanionApi;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ChatTurn } from './llm';
|
|
2
|
+
export interface GraphNode {
|
|
3
|
+
id: string;
|
|
4
|
+
type: 'turn' | 'keyword';
|
|
5
|
+
label: string;
|
|
6
|
+
/** turn 노드: 발화자. */
|
|
7
|
+
role?: 'user' | 'assistant';
|
|
8
|
+
/** turn 노드: 원문 일부(상세 패널용). */
|
|
9
|
+
snippet?: string;
|
|
10
|
+
/** turn 노드: 전체 원문. */
|
|
11
|
+
text?: string;
|
|
12
|
+
/** keyword 노드: 등장 빈도(크기 가중). */
|
|
13
|
+
count?: number;
|
|
14
|
+
/** turn 인덱스(순서). */
|
|
15
|
+
turnIndex?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface GraphLink {
|
|
18
|
+
source: string;
|
|
19
|
+
target: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ConversationGraph {
|
|
22
|
+
nodes: GraphNode[];
|
|
23
|
+
links: GraphLink[];
|
|
24
|
+
}
|
|
25
|
+
/** 한 텍스트에서 키워드 토큰을 뽑는다(한글 2자+·영문 3자+, 불용어 제외, 조사 일부 절단). */
|
|
26
|
+
export declare function extractKeywords(text: string): string[];
|
|
27
|
+
export interface BuildOptions {
|
|
28
|
+
/** 최소 이 횟수 이상 등장한 키워드만 노드로(연결성 확보). 기본 2. */
|
|
29
|
+
minKeywordFreq?: number;
|
|
30
|
+
/** 키워드 노드 최대 개수(빈도순). 기본 40. */
|
|
31
|
+
maxKeywords?: number;
|
|
32
|
+
/** 스니펫 길이. 기본 60. */
|
|
33
|
+
snippetLen?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 대화 턴 배열 → 의미 그래프. turn 노드(발화)와 keyword 노드(개념)를 만들고,
|
|
37
|
+
* 각 turn 을 그 안에 등장한 keyword 로 잇는다(Obsidian 의 노트↔태그 구조와 유사).
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildConversationGraph(turns: ChatTurn[], opts?: BuildOptions): ConversationGraph;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// ConversationGraph — 대화에서 '사고 과정' 그래프 추출 (Obsidian Graphify 계열)
|
|
4
|
+
// ----------------------------------------------------------------------------
|
|
5
|
+
// n2world 의 사고는 임베딩 기반 의미 라우팅(HNSW)이 핵심이므로, 대화를 키워드(개념)
|
|
6
|
+
// 클러스터로 잇는 의미 그래프로 시각화한다. 가짜 노드 금지(제1계명) — 모든 노드/엣지는
|
|
7
|
+
// 실제 대화 텍스트에서 도출된다. 형태소 분석기 없이 동작하는 경량 키워드 추출.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.extractKeywords = extractKeywords;
|
|
11
|
+
exports.buildConversationGraph = buildConversationGraph;
|
|
12
|
+
// 한국어 조사/불용어 + 영어 불용어(경량). 완벽한 형태소 분석은 아니지만 노이즈를 줄인다.
|
|
13
|
+
const STOPWORDS = new Set([
|
|
14
|
+
'그리고', '그래서', '하지만', '그런데', '그러면', '이것', '저것', '그것', '여기', '저기', '거기',
|
|
15
|
+
'합니다', '입니다', '있습니다', '없습니다', '하는', '하고', '해서', '에서', '으로', '에게', '한테',
|
|
16
|
+
'같이', '같은', '대해', '대한', '위해', '위한', '통해', '경우', '때문', '정도', '그냥', '아주', '매우',
|
|
17
|
+
'진행', '확인', '추가', '사용', '가능', '내용', '관련', '부분', '다시', '먼저', '바로', '모두', '각각',
|
|
18
|
+
'the', 'and', 'for', 'are', 'but', 'not', 'you', 'with', 'this', 'that', 'from', 'have', 'has',
|
|
19
|
+
'was', 'were', 'will', 'can', 'all', 'your', 'our', 'use', 'using', 'about', 'into', 'out',
|
|
20
|
+
]);
|
|
21
|
+
/** 한 텍스트에서 키워드 토큰을 뽑는다(한글 2자+·영문 3자+, 불용어 제외, 조사 일부 절단). */
|
|
22
|
+
function extractKeywords(text) {
|
|
23
|
+
const raw = (text || '').toLowerCase();
|
|
24
|
+
// 유니코드 글자/숫자 경계로 토큰화.
|
|
25
|
+
const tokens = raw.split(/[^0-9a-z가-힣]+/i).filter(Boolean);
|
|
26
|
+
const out = new Set();
|
|
27
|
+
for (let tok of tokens) {
|
|
28
|
+
// 한국어 흔한 조사 절단(끝의 1글자 조사).
|
|
29
|
+
tok = tok.replace(/(을|를|이|가|은|는|에|의|와|과|도|로|만|랑|나|요)$/u, '');
|
|
30
|
+
const isKo = /[가-힣]/.test(tok);
|
|
31
|
+
if (isKo ? tok.length < 2 : tok.length < 3)
|
|
32
|
+
continue;
|
|
33
|
+
if (STOPWORDS.has(tok))
|
|
34
|
+
continue;
|
|
35
|
+
if (/^\d+$/.test(tok))
|
|
36
|
+
continue; // 순수 숫자 제외
|
|
37
|
+
out.add(tok);
|
|
38
|
+
}
|
|
39
|
+
return [...out];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 대화 턴 배열 → 의미 그래프. turn 노드(발화)와 keyword 노드(개념)를 만들고,
|
|
43
|
+
* 각 turn 을 그 안에 등장한 keyword 로 잇는다(Obsidian 의 노트↔태그 구조와 유사).
|
|
44
|
+
*/
|
|
45
|
+
function buildConversationGraph(turns, opts = {}) {
|
|
46
|
+
const minFreq = opts.minKeywordFreq ?? 2;
|
|
47
|
+
const maxKw = opts.maxKeywords ?? 40;
|
|
48
|
+
const snipLen = opts.snippetLen ?? 60;
|
|
49
|
+
// 1) 턴별 키워드 + 전역 빈도.
|
|
50
|
+
const turnKeywords = [];
|
|
51
|
+
const freq = new Map();
|
|
52
|
+
for (const t of turns) {
|
|
53
|
+
const kws = extractKeywords(t.content);
|
|
54
|
+
turnKeywords.push(kws);
|
|
55
|
+
for (const k of kws)
|
|
56
|
+
freq.set(k, (freq.get(k) ?? 0) + 1);
|
|
57
|
+
}
|
|
58
|
+
// 2) 빈도 임계 통과 키워드 중 상위 maxKw 만 노드로.
|
|
59
|
+
const keptKw = [...freq.entries()]
|
|
60
|
+
.filter(([, c]) => c >= minFreq)
|
|
61
|
+
.sort((a, b) => b[1] - a[1])
|
|
62
|
+
.slice(0, maxKw)
|
|
63
|
+
.map(([k]) => k);
|
|
64
|
+
const keptSet = new Set(keptKw);
|
|
65
|
+
const nodes = [];
|
|
66
|
+
const links = [];
|
|
67
|
+
// 3) turn 노드.
|
|
68
|
+
turns.forEach((t, i) => {
|
|
69
|
+
const content = t.content || '';
|
|
70
|
+
nodes.push({
|
|
71
|
+
id: `t${i}`,
|
|
72
|
+
type: 'turn',
|
|
73
|
+
label: `${t.role === 'user' ? '🧑' : '🤖'} #${i + 1}`,
|
|
74
|
+
role: t.role,
|
|
75
|
+
snippet: content.replace(/\s+/g, ' ').slice(0, snipLen),
|
|
76
|
+
text: content,
|
|
77
|
+
turnIndex: i,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// 4) keyword 노드.
|
|
81
|
+
for (const k of keptKw) {
|
|
82
|
+
nodes.push({ id: `k:${k}`, type: 'keyword', label: k, count: freq.get(k) });
|
|
83
|
+
}
|
|
84
|
+
// 5) turn ↔ keyword 링크(턴에 등장한 채택 키워드만).
|
|
85
|
+
turnKeywords.forEach((kws, i) => {
|
|
86
|
+
for (const k of kws) {
|
|
87
|
+
if (keptSet.has(k))
|
|
88
|
+
links.push({ source: `t${i}`, target: `k:${k}` });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return { nodes, links };
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ModelPricing {
|
|
2
|
+
inputPer1M: number;
|
|
3
|
+
outputPer1M: number;
|
|
4
|
+
}
|
|
5
|
+
/** 개략 단가표(변동 가능 — 설정으로 덮어쓸 것). 정확한 청구는 제공자 콘솔 기준. */
|
|
6
|
+
export declare const DEFAULT_PRICING: Record<string, ModelPricing>;
|
|
7
|
+
export interface CostEstimate {
|
|
8
|
+
model: string;
|
|
9
|
+
tokensIn: number;
|
|
10
|
+
tokensOut: number;
|
|
11
|
+
totalTokens: number;
|
|
12
|
+
costUsd: number;
|
|
13
|
+
approximate: true;
|
|
14
|
+
}
|
|
15
|
+
export declare function estimateCost(model: string, tokensIn: number, tokensOut: number, pricing?: Record<string, ModelPricing>): CostEstimate;
|
|
16
|
+
export type CostApprover = (est: CostEstimate) => Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* 비용 승인 게이트. 승인자 없거나 거부면 실행 금지(보수적 — 예산 보호).
|
|
19
|
+
* 임계($) 이하 비용은 자동 승인하도록 옵션 제공.
|
|
20
|
+
*/
|
|
21
|
+
export declare function requireCostApproval(est: CostEstimate, opts?: {
|
|
22
|
+
approve?: CostApprover;
|
|
23
|
+
autoApproveBelowUsd?: number;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
approved: boolean;
|
|
26
|
+
reason: string;
|
|
27
|
+
}>;
|