@agentguard-run/spend 0.4.0 → 0.4.2
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/CHANGELOG.md +8 -2
- package/README.es-419.md +21 -4
- package/README.md +24 -5
- package/README.pt-BR.md +21 -4
- package/dist/{coach → advisor}/anomaly.d.ts +5 -5
- package/dist/advisor/anomaly.d.ts.map +1 -0
- package/dist/{coach → advisor}/anomaly.js +1 -1
- package/dist/advisor/anomaly.js.map +1 -0
- package/dist/{coach → advisor}/conversation.d.ts +23 -17
- package/dist/advisor/conversation.d.ts.map +1 -0
- package/dist/{coach → advisor}/conversation.js +63 -27
- package/dist/advisor/conversation.js.map +1 -0
- package/dist/{coach → advisor}/forecast.d.ts +4 -4
- package/dist/advisor/forecast.d.ts.map +1 -0
- package/dist/{coach → advisor}/forecast.js +1 -1
- package/dist/advisor/forecast.js.map +1 -0
- package/dist/{coach → advisor}/llm-client.d.ts +10 -10
- package/dist/advisor/llm-client.d.ts.map +1 -0
- package/dist/{coach → advisor}/llm-client.js +12 -12
- package/dist/advisor/llm-client.js.map +1 -0
- package/dist/{coach → advisor}/output.d.ts +12 -12
- package/dist/advisor/output.d.ts.map +1 -0
- package/dist/{coach → advisor}/output.js +42 -13
- package/dist/advisor/output.js.map +1 -0
- package/dist/advisor/posture.d.ts +26 -0
- package/dist/advisor/posture.d.ts.map +1 -0
- package/dist/advisor/posture.js +100 -0
- package/dist/advisor/posture.js.map +1 -0
- package/dist/{coach → advisor}/system-prompt.d.ts +5 -5
- package/dist/advisor/system-prompt.d.ts.map +1 -0
- package/dist/{coach → advisor}/system-prompt.js +21 -8
- package/dist/advisor/system-prompt.js.map +1 -0
- package/dist/cli/advisor.d.ts +5 -0
- package/dist/cli/advisor.d.ts.map +1 -0
- package/dist/cli/{coach.js → advisor.js} +63 -47
- package/dist/cli/advisor.js.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +7 -6
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/telemetry.js +1 -1
- package/package.json +7 -7
- package/src/{coach → advisor}/anomaly.ts +9 -9
- package/src/{coach → advisor}/conversation.ts +80 -39
- package/src/{coach → advisor}/forecast.ts +5 -5
- package/src/{coach → advisor}/llm-client.ts +21 -21
- package/src/{coach → advisor}/output.ts +49 -20
- package/src/advisor/posture.ts +112 -0
- package/src/{coach → advisor}/system-prompt.ts +22 -8
- package/src/cli/{coach.ts → advisor.ts} +74 -58
- package/dist/cli/coach.d.ts +0 -5
- package/dist/cli/coach.d.ts.map +0 -1
- package/dist/cli/coach.js.map +0 -1
- package/dist/coach/anomaly.d.ts.map +0 -1
- package/dist/coach/anomaly.js.map +0 -1
- package/dist/coach/conversation.d.ts.map +0 -1
- package/dist/coach/conversation.js.map +0 -1
- package/dist/coach/forecast.d.ts.map +0 -1
- package/dist/coach/forecast.js.map +0 -1
- package/dist/coach/llm-client.d.ts.map +0 -1
- package/dist/coach/llm-client.js.map +0 -1
- package/dist/coach/output.d.ts.map +0 -1
- package/dist/coach/output.js.map +0 -1
- package/dist/coach/system-prompt.d.ts.map +0 -1
- package/dist/coach/system-prompt.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AgentGuard(TM) Spend:
|
|
2
|
+
* AgentGuard(TM) Spend: Advisor conversation state.
|
|
3
3
|
*
|
|
4
4
|
* Patent notice: Protected by U.S. patent-pending technology
|
|
5
5
|
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
@@ -9,24 +9,26 @@
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import type { CapabilityTier, SpendCap, SpendPolicy } from '../types';
|
|
12
|
+
import { applyPostureCapability, normalizePosture, postureProfile, suggestPostureForVertical, type GovernancePosture } from './posture';
|
|
12
13
|
|
|
13
|
-
export type
|
|
14
|
+
export type AdvisorQuestionId = 'building' | 'posture' | 'scale' | 'tasks' | 'budget' | 'confirm';
|
|
14
15
|
|
|
15
|
-
export interface
|
|
16
|
-
id:
|
|
16
|
+
export interface AdvisorQuestion {
|
|
17
|
+
id: AdvisorQuestionId;
|
|
17
18
|
prompt: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export interface
|
|
21
|
+
export interface AdvisorAnswers {
|
|
21
22
|
building?: string;
|
|
22
23
|
scale?: string;
|
|
23
24
|
tasks?: string;
|
|
24
25
|
budget?: string;
|
|
25
26
|
confirm?: string;
|
|
26
27
|
language?: 'ts' | 'py';
|
|
28
|
+
posture?: GovernancePosture;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
export interface
|
|
31
|
+
export interface AdvisorBusinessProfile {
|
|
30
32
|
vertical: string;
|
|
31
33
|
tenantId: string;
|
|
32
34
|
teamSize: number;
|
|
@@ -41,6 +43,7 @@ export interface CoachBusinessProfile {
|
|
|
41
43
|
perMonthCapCents: number;
|
|
42
44
|
scopeLabel: string;
|
|
43
45
|
language: 'ts' | 'py';
|
|
46
|
+
posture: GovernancePosture;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export interface ProjectedSavingsRow {
|
|
@@ -58,8 +61,9 @@ export interface ProjectedSavings {
|
|
|
58
61
|
savingsPercent: number;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
export const
|
|
64
|
+
export const ADVISOR_QUESTIONS: AdvisorQuestion[] = [
|
|
62
65
|
{ id: 'building', prompt: 'What are you building or running? Include your business type and the agent workflow.' },
|
|
66
|
+
{ id: 'posture', prompt: 'Confirm or change governance posture: Velocity / Standard / Compliance.' },
|
|
63
67
|
{ id: 'scale', prompt: 'How big is the team and roughly how many AI calls, tickets, orders, encounters, matters, or jobs happen per month?' },
|
|
64
68
|
{ id: 'tasks', prompt: 'What are the top 3 AI tasks this agent will perform?' },
|
|
65
69
|
{ id: 'budget', prompt: 'What monthly AI budget or per-task ceiling should this stay under?' },
|
|
@@ -77,51 +81,77 @@ const VERTICAL_HINTS: Array<{ vertical: string; needles: string[]; scopeLabel: s
|
|
|
77
81
|
{ vertical: 'local-services', needles: ['salon', 'gym', 'restaurant', 'landscaping', 'construction', 'photography', 'fitness', 'pet'], scopeLabel: 'job', capability: 'data_write', primary: 'openai/gpt-5-mini', fallback: 'openai/gpt-4o-mini', perCall: 25 },
|
|
78
82
|
];
|
|
79
83
|
|
|
80
|
-
export class
|
|
81
|
-
private answers:
|
|
84
|
+
export class AdvisorConversation {
|
|
85
|
+
private answers: AdvisorAnswers = {};
|
|
82
86
|
private index = 0;
|
|
83
87
|
|
|
84
|
-
constructor(initial?:
|
|
88
|
+
constructor(initial?: AdvisorAnswers) {
|
|
85
89
|
if (initial) this.answers = { ...initial };
|
|
86
90
|
this.index = firstMissingIndex(this.answers);
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
currentQuestion():
|
|
90
|
-
|
|
93
|
+
currentQuestion(): AdvisorQuestion | null {
|
|
94
|
+
const question = ADVISOR_QUESTIONS[this.index] ?? null;
|
|
95
|
+
if (question?.id !== 'posture') return question;
|
|
96
|
+
const vertical = detectVertical(this.answers.building ?? '');
|
|
97
|
+
const suggested = suggestPostureForVertical(vertical);
|
|
98
|
+
return {
|
|
99
|
+
id: 'posture',
|
|
100
|
+
prompt: `Based on ${vertical}, we suggest ${suggested} governance posture. Confirm or change: Velocity / Standard / Compliance.`,
|
|
101
|
+
};
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
answer(value: string): void {
|
|
94
105
|
const question = this.currentQuestion();
|
|
95
106
|
if (!question) return;
|
|
107
|
+
if (question.id === 'posture') {
|
|
108
|
+
this.answers.posture = normalizePosture(value) ?? suggestedPostureForText(this.answers.building ?? '');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
96
111
|
this.answers[question.id] = value.trim();
|
|
97
112
|
}
|
|
98
113
|
|
|
99
|
-
next():
|
|
100
|
-
this.index = Math.min(this.index + 1,
|
|
114
|
+
next(): AdvisorQuestion | null {
|
|
115
|
+
this.index = Math.min(this.index + 1, ADVISOR_QUESTIONS.length);
|
|
101
116
|
return this.currentQuestion();
|
|
102
117
|
}
|
|
103
118
|
|
|
104
|
-
back():
|
|
119
|
+
back(): AdvisorQuestion | null {
|
|
105
120
|
this.index = Math.max(0, this.index - 1);
|
|
106
121
|
return this.currentQuestion();
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
isComplete(): boolean {
|
|
110
|
-
return
|
|
125
|
+
return ADVISOR_QUESTIONS.every((question) => Boolean(this.answers[question.id]?.trim()));
|
|
111
126
|
}
|
|
112
127
|
|
|
113
|
-
snapshot():
|
|
128
|
+
snapshot(): AdvisorAnswers {
|
|
114
129
|
return { ...this.answers };
|
|
115
130
|
}
|
|
116
131
|
|
|
117
|
-
|
|
132
|
+
setPosture(posture: GovernancePosture): void {
|
|
133
|
+
this.answers.posture = posture;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
profile(cwd = process.cwd()): AdvisorBusinessProfile {
|
|
118
137
|
return buildBusinessProfile(this.answers, cwd);
|
|
119
138
|
}
|
|
120
139
|
}
|
|
121
140
|
|
|
122
|
-
export function
|
|
141
|
+
export function detectVertical(text: string): string {
|
|
142
|
+
const joined = text.toLowerCase();
|
|
143
|
+
return (VERTICAL_HINTS.find((candidate) => candidate.needles.some((needle) => joined.includes(needle))) ?? VERTICAL_HINTS[0]!).vertical;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function suggestedPostureForText(text: string): GovernancePosture {
|
|
147
|
+
return suggestPostureForVertical(detectVertical(text));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
export function buildBusinessProfile(answers: AdvisorAnswers, cwd = process.cwd()): AdvisorBusinessProfile {
|
|
123
152
|
const joined = `${answers.building ?? ''} ${answers.scale ?? ''} ${answers.tasks ?? ''}`.toLowerCase();
|
|
124
153
|
const hint = VERTICAL_HINTS.find((candidate) => candidate.needles.some((needle) => joined.includes(needle))) ?? VERTICAL_HINTS[0]!;
|
|
154
|
+
const posture = answers.posture ?? suggestPostureForVertical(hint.vertical);
|
|
125
155
|
const scale = parseScale(answers.scale ?? '');
|
|
126
156
|
const budgetCents = parseBudgetCents(answers.budget ?? '', Math.max(4900, Math.ceil(scale.monthlyVolume * hint.perCall * 0.35)));
|
|
127
157
|
const tasks = parseTasks(answers.tasks ?? answers.building ?? hint.vertical);
|
|
@@ -134,7 +164,7 @@ export function buildBusinessProfile(answers: CoachAnswers, cwd = process.cwd())
|
|
|
134
164
|
monthlyVolume: scale.monthlyVolume,
|
|
135
165
|
tasks,
|
|
136
166
|
monthlyBudgetCents: perMonthCapCents,
|
|
137
|
-
requiredCapability: capabilityFor(joined, hint.capability),
|
|
167
|
+
requiredCapability: capabilityFor(joined, hint.capability, posture),
|
|
138
168
|
primaryModel: hint.primary,
|
|
139
169
|
fallbackModel: hint.fallback,
|
|
140
170
|
perCallCapCents: hint.perCall,
|
|
@@ -142,18 +172,31 @@ export function buildBusinessProfile(answers: CoachAnswers, cwd = process.cwd())
|
|
|
142
172
|
perMonthCapCents,
|
|
143
173
|
scopeLabel: hint.scopeLabel,
|
|
144
174
|
language: answers.language ?? detectProjectLanguage(cwd),
|
|
175
|
+
posture,
|
|
145
176
|
};
|
|
146
177
|
}
|
|
147
178
|
|
|
148
|
-
export function buildPolicyFromProfile(profile:
|
|
179
|
+
export function buildPolicyFromProfile(profile: AdvisorBusinessProfile): SpendPolicy {
|
|
180
|
+
const posture = postureProfile(profile.posture);
|
|
181
|
+
const conservativeBlock = profile.posture === 'compliance' && profile.requiredCapability !== 'read_only';
|
|
182
|
+
const perCallAmount = profile.posture === 'velocity'
|
|
183
|
+
? Math.max(1, Math.ceil(profile.perCallCapCents * 0.6))
|
|
184
|
+
: profile.perCallCapCents;
|
|
149
185
|
const caps: SpendCap[] = [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
186
|
+
conservativeBlock
|
|
187
|
+
? {
|
|
188
|
+
amountCents: perCallAmount,
|
|
189
|
+
window: 'per_call',
|
|
190
|
+
action: 'block',
|
|
191
|
+
reason: 'Per-call regulated capability budget reached',
|
|
192
|
+
}
|
|
193
|
+
: {
|
|
194
|
+
amountCents: perCallAmount,
|
|
195
|
+
window: 'per_call',
|
|
196
|
+
action: 'downgrade',
|
|
197
|
+
downgradeTo: profile.fallbackModel,
|
|
198
|
+
reason: 'Per-call budget reached, route to fallback model',
|
|
199
|
+
},
|
|
157
200
|
{
|
|
158
201
|
amountCents: profile.perDayCapCents,
|
|
159
202
|
window: 'per_day',
|
|
@@ -168,18 +211,18 @@ export function buildPolicyFromProfile(profile: CoachBusinessProfile): SpendPoli
|
|
|
168
211
|
},
|
|
169
212
|
];
|
|
170
213
|
return {
|
|
171
|
-
id: `
|
|
172
|
-
name: `
|
|
214
|
+
id: `advisor-${profile.vertical}-v1`,
|
|
215
|
+
name: `Advisor generated ${profile.vertical} policy`,
|
|
173
216
|
scope: { tenantId: profile.tenantId },
|
|
174
217
|
caps,
|
|
175
|
-
mode:
|
|
218
|
+
mode: posture.defaultMode,
|
|
176
219
|
requiredCapability: profile.requiredCapability,
|
|
177
220
|
version: 1,
|
|
178
221
|
effectiveFrom: new Date().toISOString(),
|
|
179
222
|
};
|
|
180
223
|
}
|
|
181
224
|
|
|
182
|
-
export function projectedSavings(profile:
|
|
225
|
+
export function projectedSavings(profile: AdvisorBusinessProfile): ProjectedSavings {
|
|
183
226
|
const heavyPerCall = Math.max(profile.perCallCapCents * 4, 100);
|
|
184
227
|
const routedPerCall = Math.max(1, Math.ceil(profile.perCallCapCents * 0.45));
|
|
185
228
|
const monthlyBeforeCents = profile.monthlyVolume * heavyPerCall;
|
|
@@ -211,9 +254,9 @@ export function detectProjectLanguage(cwd: string): 'ts' | 'py' {
|
|
|
211
254
|
return 'ts';
|
|
212
255
|
}
|
|
213
256
|
|
|
214
|
-
function firstMissingIndex(answers:
|
|
215
|
-
const index =
|
|
216
|
-
return index === -1 ?
|
|
257
|
+
function firstMissingIndex(answers: AdvisorAnswers): number {
|
|
258
|
+
const index = ADVISOR_QUESTIONS.findIndex((question) => !answers[question.id]?.trim());
|
|
259
|
+
return index === -1 ? ADVISOR_QUESTIONS.length : index;
|
|
217
260
|
}
|
|
218
261
|
|
|
219
262
|
function parseScale(value: string): { teamSize: number; monthlyVolume: number } {
|
|
@@ -241,8 +284,6 @@ function tenantIdFromBusiness(value: string): string {
|
|
|
241
284
|
return cleaned || 'local-business';
|
|
242
285
|
}
|
|
243
286
|
|
|
244
|
-
function capabilityFor(text: string, fallback: CapabilityTier): CapabilityTier {
|
|
245
|
-
|
|
246
|
-
if (/write|update|ledger|chart|patient|health|phi|sox|legal|contract|tax|student|employment/.test(text)) return 'data_write';
|
|
247
|
-
return fallback;
|
|
287
|
+
function capabilityFor(text: string, fallback: CapabilityTier, posture: GovernancePosture): CapabilityTier {
|
|
288
|
+
return applyPostureCapability(posture, text, fallback);
|
|
248
289
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AgentGuard(TM) Spend: local
|
|
2
|
+
* AgentGuard(TM) Spend: local Advisor forecast skeleton.
|
|
3
3
|
*
|
|
4
4
|
* Reads local decision logs only. No network calls are made.
|
|
5
5
|
*
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* 64/071,781; 64/071,789).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type { AdvisorSpendPoint } from './anomaly';
|
|
12
12
|
|
|
13
|
-
export interface
|
|
13
|
+
export interface AdvisorForecast {
|
|
14
14
|
daysObserved: number;
|
|
15
15
|
monthEndCents: number;
|
|
16
16
|
capCents: number | null;
|
|
@@ -18,7 +18,7 @@ export interface CoachForecast {
|
|
|
18
18
|
message: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function forecastMonthEnd(points:
|
|
21
|
+
export function forecastMonthEnd(points: AdvisorSpendPoint[], capCents: number | null = null, now = new Date()): AdvisorForecast {
|
|
22
22
|
const daily = lastThirtyDaily(points, now);
|
|
23
23
|
if (daily.length === 0) {
|
|
24
24
|
return { daysObserved: 0, monthEndCents: 0, capCents, overCap: false, message: 'No local spend history found.' };
|
|
@@ -40,7 +40,7 @@ export function forecastMonthEnd(points: CoachSpendPoint[], capCents: number | n
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function lastThirtyDaily(points:
|
|
43
|
+
function lastThirtyDaily(points: AdvisorSpendPoint[], now: Date): Array<{ day: string; cents: number }> {
|
|
44
44
|
const cutoff = now.getTime() - 30 * 24 * 60 * 60 * 1000;
|
|
45
45
|
const buckets = new Map<string, number>();
|
|
46
46
|
for (const point of points) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AgentGuard(TM) Spend: local
|
|
2
|
+
* AgentGuard(TM) Spend: local Advisor LLM client.
|
|
3
3
|
*
|
|
4
4
|
* All provider calls go from the customer terminal to the configured provider.
|
|
5
5
|
* No AgentGuard service is contacted for prompts or completions.
|
|
@@ -13,15 +13,15 @@ import * as fs from 'fs';
|
|
|
13
13
|
import * as os from 'os';
|
|
14
14
|
import * as path from 'path';
|
|
15
15
|
|
|
16
|
-
export type
|
|
16
|
+
export type AdvisorProvider = 'openrouter' | 'openai' | 'anthropic' | 'compatible' | 'mock';
|
|
17
17
|
|
|
18
|
-
export interface
|
|
18
|
+
export interface AdvisorChatMessage {
|
|
19
19
|
role: 'system' | 'user' | 'assistant';
|
|
20
20
|
content: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export interface
|
|
24
|
-
provider?:
|
|
23
|
+
export interface AdvisorClientOptions {
|
|
24
|
+
provider?: AdvisorProvider;
|
|
25
25
|
apiKey?: string;
|
|
26
26
|
baseUrl?: string;
|
|
27
27
|
model?: string;
|
|
@@ -29,11 +29,11 @@ export interface CoachClientOptions {
|
|
|
29
29
|
fetchImpl?: FetchLike;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export interface
|
|
33
|
-
provider:
|
|
32
|
+
export interface AdvisorClient {
|
|
33
|
+
provider: AdvisorProvider;
|
|
34
34
|
model: string;
|
|
35
35
|
baseUrl: string;
|
|
36
|
-
streamChat(messages:
|
|
36
|
+
streamChat(messages: AdvisorChatMessage[], signal?: AbortSignalLike): AsyncIterable<string>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
type FetchLike = (url: string, init: Record<string, unknown>) => Promise<any>;
|
|
@@ -53,12 +53,12 @@ const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
|
|
|
53
53
|
const DEFAULT_ANTHROPIC_BASE_URL = 'https://api.anthropic.com/v1';
|
|
54
54
|
const DEFAULT_MODEL = 'openai/gpt-4o-mini';
|
|
55
55
|
|
|
56
|
-
export function
|
|
56
|
+
export function resolveAdvisorApiKey(provider: AdvisorProvider = 'openrouter', explicit?: string): string | null {
|
|
57
57
|
if (explicit?.trim()) return explicit.trim();
|
|
58
58
|
if (provider === 'openai' && process.env.OPENAI_API_KEY) return process.env.OPENAI_API_KEY;
|
|
59
59
|
if (provider === 'anthropic' && process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY;
|
|
60
60
|
if ((provider === 'openrouter' || provider === 'compatible') && process.env.OPENROUTER_API_KEY) return process.env.OPENROUTER_API_KEY;
|
|
61
|
-
if (process.env.
|
|
61
|
+
if (process.env.AGENTGUARD_ADVISOR_API_KEY) return process.env.AGENTGUARD_ADVISOR_API_KEY;
|
|
62
62
|
try {
|
|
63
63
|
const key = fs.readFileSync(path.join(agentguardHome(), 'openrouter-key'), 'utf8').trim();
|
|
64
64
|
return key.length > 0 ? key : null;
|
|
@@ -67,23 +67,23 @@ export function resolveCoachApiKey(provider: CoachProvider = 'openrouter', expli
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
export function
|
|
70
|
+
export function createAdvisorClient(options: AdvisorClientOptions = {}): AdvisorClient {
|
|
71
71
|
const provider = options.provider ?? providerFromBaseUrl(options.baseUrl) ?? 'openrouter';
|
|
72
72
|
const model = options.model ?? (provider === 'anthropic' ? 'claude-sonnet-4-6' : DEFAULT_MODEL);
|
|
73
73
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultBaseUrl(provider));
|
|
74
74
|
const fetchImpl = options.fetchImpl ?? globalFetch;
|
|
75
|
-
const apiKey =
|
|
75
|
+
const apiKey = resolveAdvisorApiKey(provider, options.apiKey);
|
|
76
76
|
|
|
77
77
|
return {
|
|
78
78
|
provider,
|
|
79
79
|
model,
|
|
80
80
|
baseUrl,
|
|
81
|
-
async *streamChat(messages:
|
|
81
|
+
async *streamChat(messages: AdvisorChatMessage[], signal?: AbortSignalLike): AsyncIterable<string> {
|
|
82
82
|
if (provider === 'mock') {
|
|
83
|
-
yield 'Mock
|
|
83
|
+
yield 'Mock advisor response.';
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
-
if (!apiKey) throw new Error('No
|
|
86
|
+
if (!apiKey) throw new Error('No Advisor API key configured');
|
|
87
87
|
if (provider === 'anthropic') {
|
|
88
88
|
yield* streamAnthropic({ fetchImpl, baseUrl, model, apiKey, messages, signal, timeoutMs: options.timeoutMs });
|
|
89
89
|
} else {
|
|
@@ -98,7 +98,7 @@ async function* streamOpenAICompatible(args: {
|
|
|
98
98
|
baseUrl: string;
|
|
99
99
|
model: string;
|
|
100
100
|
apiKey: string;
|
|
101
|
-
messages:
|
|
101
|
+
messages: AdvisorChatMessage[];
|
|
102
102
|
signal?: AbortSignalLike;
|
|
103
103
|
timeoutMs?: number;
|
|
104
104
|
}): AsyncIterable<string> {
|
|
@@ -116,7 +116,7 @@ async function* streamOpenAICompatible(args: {
|
|
|
116
116
|
body: JSON.stringify({ model: args.model, messages: args.messages, stream: true }),
|
|
117
117
|
signal,
|
|
118
118
|
});
|
|
119
|
-
if (!response.ok) throw new Error(`
|
|
119
|
+
if (!response.ok) throw new Error(`Advisor provider HTTP ${response.status}`);
|
|
120
120
|
yield* parseSseResponse(response, (json) => json?.choices?.[0]?.delta?.content ?? json?.choices?.[0]?.message?.content ?? '');
|
|
121
121
|
} finally {
|
|
122
122
|
clearTimeout(timer);
|
|
@@ -128,7 +128,7 @@ async function* streamAnthropic(args: {
|
|
|
128
128
|
baseUrl: string;
|
|
129
129
|
model: string;
|
|
130
130
|
apiKey: string;
|
|
131
|
-
messages:
|
|
131
|
+
messages: AdvisorChatMessage[];
|
|
132
132
|
signal?: AbortSignalLike;
|
|
133
133
|
timeoutMs?: number;
|
|
134
134
|
}): AsyncIterable<string> {
|
|
@@ -149,7 +149,7 @@ async function* streamAnthropic(args: {
|
|
|
149
149
|
body: JSON.stringify({ model: args.model, system, messages, max_tokens: 1200, stream: true }),
|
|
150
150
|
signal,
|
|
151
151
|
});
|
|
152
|
-
if (!response.ok) throw new Error(`
|
|
152
|
+
if (!response.ok) throw new Error(`Advisor provider HTTP ${response.status}`);
|
|
153
153
|
yield* parseSseResponse(response, (json) => json?.delta?.text ?? json?.content_block?.text ?? '');
|
|
154
154
|
} finally {
|
|
155
155
|
clearTimeout(timer);
|
|
@@ -201,7 +201,7 @@ function chunkToString(chunk: unknown): string {
|
|
|
201
201
|
return String(chunk ?? '');
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
function defaultBaseUrl(provider:
|
|
204
|
+
function defaultBaseUrl(provider: AdvisorProvider): string {
|
|
205
205
|
if (provider === 'openai') return DEFAULT_OPENAI_BASE_URL;
|
|
206
206
|
if (provider === 'anthropic') return DEFAULT_ANTHROPIC_BASE_URL;
|
|
207
207
|
return DEFAULT_OPENROUTER_BASE_URL;
|
|
@@ -211,7 +211,7 @@ function normalizeBaseUrl(value: string): string {
|
|
|
211
211
|
return value.replace(/\/+$/, '');
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
function providerFromBaseUrl(baseUrl?: string):
|
|
214
|
+
function providerFromBaseUrl(baseUrl?: string): AdvisorProvider | null {
|
|
215
215
|
if (!baseUrl) return null;
|
|
216
216
|
if (baseUrl.includes('anthropic.com')) return 'anthropic';
|
|
217
217
|
if (baseUrl.includes('openai.com')) return 'openai';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AgentGuard(TM) Spend:
|
|
2
|
+
* AgentGuard(TM) Spend: Advisor output writers.
|
|
3
3
|
*
|
|
4
4
|
* Files are written locally under ~/.agentguard by default. Conversation logs
|
|
5
|
-
* stay in ~/.agentguard/
|
|
5
|
+
* stay in ~/.agentguard/advisor-sessions and are never uploaded by this SDK.
|
|
6
6
|
*
|
|
7
7
|
* Patent notice: Protected by U.S. patent-pending technology
|
|
8
8
|
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
@@ -17,18 +17,19 @@ import {
|
|
|
17
17
|
buildPolicyFromProfile,
|
|
18
18
|
formatCents,
|
|
19
19
|
projectedSavings,
|
|
20
|
-
type
|
|
20
|
+
type AdvisorBusinessProfile,
|
|
21
21
|
type ProjectedSavings,
|
|
22
22
|
} from './conversation';
|
|
23
|
+
import { postureProfile } from './posture';
|
|
23
24
|
|
|
24
|
-
export interface
|
|
25
|
+
export interface AdvisorOutputOptions {
|
|
25
26
|
home?: string;
|
|
26
27
|
now?: Date;
|
|
27
28
|
language?: 'ts' | 'py';
|
|
28
29
|
overwrite?: boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export interface
|
|
32
|
+
export interface AdvisorOutputs {
|
|
32
33
|
policy: SpendPolicy;
|
|
33
34
|
policyPath: string;
|
|
34
35
|
quickstartPath: string;
|
|
@@ -39,7 +40,7 @@ export interface CoachOutputs {
|
|
|
39
40
|
savingsTable: string;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export interface
|
|
43
|
+
export interface AdvisorSessionLogger {
|
|
43
44
|
path: string;
|
|
44
45
|
append: (event: string, payload?: Record<string, unknown>) => void;
|
|
45
46
|
}
|
|
@@ -48,12 +49,12 @@ export function agentguardHome(): string {
|
|
|
48
49
|
return process.env.AGENTGUARD_HOME || path.join(os.homedir(), '.agentguard');
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export function
|
|
52
|
-
return path.join(home, '
|
|
52
|
+
export function advisorSessionDir(home = agentguardHome()): string {
|
|
53
|
+
return path.join(home, 'advisor-sessions');
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
export function
|
|
56
|
-
const dir =
|
|
56
|
+
export function createAdvisorSessionLogger(home = agentguardHome(), now = new Date()): AdvisorSessionLogger {
|
|
57
|
+
const dir = advisorSessionDir(home);
|
|
57
58
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
58
59
|
const stamp = now.toISOString().replace(/[:.]/g, '-');
|
|
59
60
|
const file = path.join(dir, `${stamp}.jsonl`);
|
|
@@ -67,10 +68,10 @@ export function createCoachSessionLogger(home = agentguardHome(), now = new Date
|
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
export function
|
|
71
|
+
export function writeAdvisorOutputs(profile: AdvisorBusinessProfile, options: AdvisorOutputOptions = {}): AdvisorOutputs {
|
|
71
72
|
const home = options.home ?? agentguardHome();
|
|
72
73
|
const language = options.language ?? profile.language;
|
|
73
|
-
const sessionDir =
|
|
74
|
+
const sessionDir = advisorSessionDir(home);
|
|
74
75
|
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
75
76
|
fs.mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
|
|
76
77
|
|
|
@@ -92,7 +93,7 @@ export function writeCoachOutputs(profile: CoachBusinessProfile, options: CoachO
|
|
|
92
93
|
return { policy, policyPath, quickstartPath, sessionDir, savings, policyYaml, quickstartCode, savingsTable };
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
export function renderPolicyYaml(policy: SpendPolicy, profile:
|
|
96
|
+
export function renderPolicyYaml(policy: SpendPolicy, profile: AdvisorBusinessProfile, now = new Date()): string {
|
|
96
97
|
const caps = policy.caps.map((cap) => {
|
|
97
98
|
const lines = [
|
|
98
99
|
` # WHY: ${whyForWindow(cap.window)}`,
|
|
@@ -105,14 +106,41 @@ export function renderPolicyYaml(policy: SpendPolicy, profile: CoachBusinessProf
|
|
|
105
106
|
return lines.join('\n');
|
|
106
107
|
}).join('\n');
|
|
107
108
|
const tasks = profile.tasks.map((task) => ` - ${quoteYaml(task)}`).join('\n');
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
const posture = postureProfile(profile.posture);
|
|
110
|
+
const canary = posture.canaryPercent === undefined ? '' : ` canaryPercent: ${posture.canaryPercent}\n`;
|
|
111
|
+
return `# AgentGuard Spend policy generated by agentguard advisor
|
|
112
|
+
# Generated at: ${now.toISOString()}
|
|
113
|
+
# Scope key: ${profile.scopeLabel}
|
|
114
|
+
id: ${policy.id}
|
|
115
|
+
name: ${quoteYaml(policy.name)}
|
|
116
|
+
version: ${policy.version}
|
|
117
|
+
effectiveFrom: ${quoteYaml(policy.effectiveFrom)}
|
|
118
|
+
mode: ${policy.mode}
|
|
119
|
+
requiredCapability: ${policy.requiredCapability ?? 'read_only'}
|
|
120
|
+
scope:
|
|
121
|
+
tenantId: ${quoteYaml(policy.scope.tenantId)}
|
|
122
|
+
models:
|
|
123
|
+
primary: ${profile.primaryModel}
|
|
124
|
+
fallback: ${profile.fallbackModel}
|
|
125
|
+
governancePosture:
|
|
126
|
+
posture: ${profile.posture}
|
|
127
|
+
auditRetentionDays: ${posture.auditRetentionDays}
|
|
128
|
+
approvalGates: ${posture.approvalGates}
|
|
129
|
+
downgradeStyle: ${posture.downgradeStyle}
|
|
130
|
+
${canary}tasks:
|
|
131
|
+
${tasks}
|
|
132
|
+
caps:
|
|
133
|
+
${caps}
|
|
134
|
+
systemInstructions: |
|
|
135
|
+
${indent(systemInstructions(profile), 2)}
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function renderQuickstartTs(policy: SpendPolicy, profile: AdvisorBusinessProfile): string {
|
|
112
140
|
return `import OpenAI from 'openai';\nimport { withSpendGuard, type SpendPolicy } from '@agentguard-run/spend';\n\nconst policy: SpendPolicy = ${JSON.stringify(policy, null, 2)};\n\nconst openrouter = new OpenAI({\n baseURL: 'https://openrouter.ai/api/v1',\n apiKey: process.env.OPENROUTER_API_KEY,\n});\n\nexport const guardedClient = withSpendGuard(openrouter, {\n policy,\n scope: { tenantId: '${escapeTs(profile.tenantId)}', agentId: '${escapeTs(profile.vertical)}' },\n capabilityClaim: '${policy.requiredCapability ?? 'read_only'}',\n});\n\nexport async function runGuardedTask(prompt: string) {\n return guardedClient.chat.completions.create({\n model: '${escapeTs(profile.primaryModel)}',\n messages: [{ role: 'user', content: prompt }],\n });\n}\n`;
|
|
113
141
|
}
|
|
114
142
|
|
|
115
|
-
export function renderQuickstartPy(policy: SpendPolicy, profile:
|
|
143
|
+
export function renderQuickstartPy(policy: SpendPolicy, profile: AdvisorBusinessProfile): string {
|
|
116
144
|
const caps = policy.caps.map((cap) => {
|
|
117
145
|
const args = [`amountCents=${cap.amountCents}`, `window='${cap.window}'`, `action='${cap.action}'`];
|
|
118
146
|
if (cap.downgradeTo) args.push(`downgradeTo='${escapePy(cap.downgradeTo)}'`);
|
|
@@ -150,8 +178,9 @@ function whyForWindow(window: string): string {
|
|
|
150
178
|
return 'Limits spend inside the selected time window.';
|
|
151
179
|
}
|
|
152
180
|
|
|
153
|
-
function systemInstructions(profile:
|
|
154
|
-
|
|
181
|
+
function systemInstructions(profile: AdvisorBusinessProfile): string {
|
|
182
|
+
const posture = postureProfile(profile.posture);
|
|
183
|
+
return `Run ${profile.vertical} tasks with ${profile.requiredCapability} capability under ${posture.label} governance posture. Prefer ${profile.primaryModel} for high-value work and ${profile.fallbackModel} for routine work. Keep evidence pointers in outputs and escalate anything outside the configured capability tier.`;
|
|
155
184
|
}
|
|
156
185
|
|
|
157
186
|
function quoteYaml(value: string): string {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentGuard(TM) Spend: Advisor governance posture profiles.
|
|
3
|
+
*
|
|
4
|
+
* Patent notice: Protected by U.S. patent-pending technology
|
|
5
|
+
* (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
|
|
6
|
+
* 64/071,781; 64/071,789).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CapabilityTier, EnforcementMode } from '../types';
|
|
10
|
+
|
|
11
|
+
export type GovernancePosture = 'velocity' | 'standard' | 'compliance';
|
|
12
|
+
|
|
13
|
+
export interface GovernancePostureProfile {
|
|
14
|
+
posture: GovernancePosture;
|
|
15
|
+
label: string;
|
|
16
|
+
defaultMode: EnforcementMode;
|
|
17
|
+
capabilityStyle: 'permissive' | 'balanced' | 'strict';
|
|
18
|
+
downgradeStyle: 'aggressive' | 'moderate' | 'conservative';
|
|
19
|
+
auditRetentionDays: number;
|
|
20
|
+
approvalGates: boolean;
|
|
21
|
+
canaryPercent?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const GOVERNANCE_POSTURES: Record<GovernancePosture, GovernancePostureProfile> = {
|
|
25
|
+
velocity: {
|
|
26
|
+
posture: 'velocity',
|
|
27
|
+
label: 'Velocity',
|
|
28
|
+
defaultMode: 'shadow',
|
|
29
|
+
capabilityStyle: 'permissive',
|
|
30
|
+
downgradeStyle: 'aggressive',
|
|
31
|
+
auditRetentionDays: 30,
|
|
32
|
+
approvalGates: false,
|
|
33
|
+
},
|
|
34
|
+
standard: {
|
|
35
|
+
posture: 'standard',
|
|
36
|
+
label: 'Standard',
|
|
37
|
+
defaultMode: 'enforce',
|
|
38
|
+
capabilityStyle: 'balanced',
|
|
39
|
+
downgradeStyle: 'moderate',
|
|
40
|
+
auditRetentionDays: 90,
|
|
41
|
+
approvalGates: false,
|
|
42
|
+
},
|
|
43
|
+
compliance: {
|
|
44
|
+
posture: 'compliance',
|
|
45
|
+
label: 'Compliance',
|
|
46
|
+
defaultMode: 'canary',
|
|
47
|
+
capabilityStyle: 'strict',
|
|
48
|
+
downgradeStyle: 'conservative',
|
|
49
|
+
auditRetentionDays: 2555,
|
|
50
|
+
approvalGates: true,
|
|
51
|
+
canaryPercent: 5,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const VERTICAL_POSTURES: Record<string, GovernancePosture> = {
|
|
56
|
+
'law-firm': 'compliance',
|
|
57
|
+
healthcare: 'compliance',
|
|
58
|
+
accounting: 'compliance',
|
|
59
|
+
fintech: 'compliance',
|
|
60
|
+
insurance: 'compliance',
|
|
61
|
+
'real-estate': 'standard',
|
|
62
|
+
marketing: 'standard',
|
|
63
|
+
ecommerce: 'standard',
|
|
64
|
+
'local-services': 'standard',
|
|
65
|
+
dental: 'compliance',
|
|
66
|
+
software: 'velocity',
|
|
67
|
+
startup: 'standard',
|
|
68
|
+
'ai-lab': 'velocity',
|
|
69
|
+
'ai-team': 'velocity',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function normalizePosture(value?: string | null): GovernancePosture | null {
|
|
73
|
+
const normalized = (value ?? '').trim().toLowerCase();
|
|
74
|
+
if (normalized === 'velocity' || normalized === 'standard' || normalized === 'compliance') return normalized;
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function postureProfile(posture: GovernancePosture): GovernancePostureProfile {
|
|
79
|
+
return GOVERNANCE_POSTURES[posture];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function suggestPostureForVertical(vertical: string): GovernancePosture {
|
|
83
|
+
return VERTICAL_POSTURES[vertical] ?? 'standard';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function applyPostureCapability(
|
|
87
|
+
posture: GovernancePosture,
|
|
88
|
+
text: string,
|
|
89
|
+
fallback: CapabilityTier,
|
|
90
|
+
): CapabilityTier {
|
|
91
|
+
const lowered = text.toLowerCase();
|
|
92
|
+
if (posture === 'velocity') {
|
|
93
|
+
if (/execute|wire|ach|payout|capture funds/.test(lowered)) return 'payment_execute';
|
|
94
|
+
if (/refund|payment|charge|dispute|money|invoice/.test(lowered)) return 'payment_initiate';
|
|
95
|
+
return 'read_only';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (posture === 'compliance') {
|
|
99
|
+
if (/refund|payment|charge|dispute|money|invoice|ledger|sox|fintech|bank/.test(lowered)) return 'payment_execute';
|
|
100
|
+
if (/write|update|chart|patient|health|phi|pii|legal|contract|tax|student|employment/.test(lowered)) return 'data_write';
|
|
101
|
+
return fallback === 'read_only' ? 'data_write' : fallback;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (/refund|payment|charge|dispute|money|invoice/.test(lowered)) return 'payment_initiate';
|
|
105
|
+
if (/write|update|ledger|chart|patient|health|phi|pii|sox|legal|contract|tax|student|employment/.test(lowered)) return 'data_write';
|
|
106
|
+
return fallback;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function postureDescription(posture: GovernancePosture): string {
|
|
110
|
+
const profile = postureProfile(posture);
|
|
111
|
+
return `${profile.label}: mode ${profile.defaultMode}, ${profile.capabilityStyle} capabilities, ${profile.downgradeStyle} downgrade chains, ${profile.auditRetentionDays} day audit retention`;
|
|
112
|
+
}
|