@agentguard-run/spend 0.2.2 → 0.4.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/LICENSE +1 -1
  3. package/README.es-419.md +37 -100
  4. package/README.md +58 -121
  5. package/README.pt-BR.md +37 -100
  6. package/dist/bindings/anthropic.d.ts +11 -0
  7. package/dist/bindings/anthropic.d.ts.map +1 -0
  8. package/dist/bindings/anthropic.js +116 -0
  9. package/dist/bindings/anthropic.js.map +1 -0
  10. package/dist/bindings/bedrock.d.ts +11 -0
  11. package/dist/bindings/bedrock.d.ts.map +1 -0
  12. package/dist/bindings/bedrock.js +177 -0
  13. package/dist/bindings/bedrock.js.map +1 -0
  14. package/dist/cli/auth.d.ts +7 -0
  15. package/dist/cli/auth.d.ts.map +1 -0
  16. package/dist/cli/auth.js +189 -0
  17. package/dist/cli/auth.js.map +1 -0
  18. package/dist/cli/coach.d.ts +5 -0
  19. package/dist/cli/coach.d.ts.map +1 -0
  20. package/dist/cli/coach.js +257 -0
  21. package/dist/cli/coach.js.map +1 -0
  22. package/dist/cli/colors.d.ts +8 -3
  23. package/dist/cli/colors.d.ts.map +1 -1
  24. package/dist/cli/colors.js +93 -4
  25. package/dist/cli/colors.js.map +1 -1
  26. package/dist/cli/demo.d.ts.map +1 -1
  27. package/dist/cli/demo.js +23 -2
  28. package/dist/cli/demo.js.map +1 -1
  29. package/dist/cli/main.d.ts +0 -6
  30. package/dist/cli/main.d.ts.map +1 -1
  31. package/dist/cli/main.js +42 -16
  32. package/dist/cli/main.js.map +1 -1
  33. package/dist/cli/models.d.ts +18 -0
  34. package/dist/cli/models.d.ts.map +1 -0
  35. package/dist/cli/models.js +277 -0
  36. package/dist/cli/models.js.map +1 -0
  37. package/dist/cli/tips.d.ts +21 -0
  38. package/dist/cli/tips.d.ts.map +1 -0
  39. package/dist/cli/tips.js +191 -0
  40. package/dist/cli/tips.js.map +1 -0
  41. package/dist/cli/wizard.d.ts +27 -0
  42. package/dist/cli/wizard.d.ts.map +1 -0
  43. package/dist/cli/wizard.js +182 -0
  44. package/dist/cli/wizard.js.map +1 -0
  45. package/dist/coach/anomaly.d.ts +26 -0
  46. package/dist/coach/anomaly.d.ts.map +1 -0
  47. package/dist/coach/anomaly.js +119 -0
  48. package/dist/coach/anomaly.js.map +1 -0
  49. package/dist/coach/conversation.d.ts +69 -0
  50. package/dist/coach/conversation.d.ts.map +1 -0
  51. package/dist/coach/conversation.js +228 -0
  52. package/dist/coach/conversation.js.map +1 -0
  53. package/dist/coach/forecast.d.ts +19 -0
  54. package/dist/coach/forecast.d.ts.map +1 -0
  55. package/dist/coach/forecast.js +57 -0
  56. package/dist/coach/forecast.js.map +1 -0
  57. package/dist/coach/llm-client.d.ts +41 -0
  58. package/dist/coach/llm-client.d.ts.map +1 -0
  59. package/dist/coach/llm-client.js +248 -0
  60. package/dist/coach/llm-client.js.map +1 -0
  61. package/dist/coach/output.d.ts +41 -0
  62. package/dist/coach/output.d.ts.map +1 -0
  63. package/dist/coach/output.js +173 -0
  64. package/dist/coach/output.js.map +1 -0
  65. package/dist/coach/system-prompt.d.ts +20 -0
  66. package/dist/coach/system-prompt.d.ts.map +1 -0
  67. package/dist/coach/system-prompt.js +177 -0
  68. package/dist/coach/system-prompt.js.map +1 -0
  69. package/dist/cost-table.d.ts +11 -36
  70. package/dist/cost-table.d.ts.map +1 -1
  71. package/dist/cost-table.js +114 -45
  72. package/dist/cost-table.js.map +1 -1
  73. package/dist/index.d.ts +9 -3
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +30 -2
  76. package/dist/index.js.map +1 -1
  77. package/dist/openrouter-catalog.d.ts +56 -0
  78. package/dist/openrouter-catalog.d.ts.map +1 -0
  79. package/dist/openrouter-catalog.js +183 -0
  80. package/dist/openrouter-catalog.js.map +1 -0
  81. package/dist/spend-guard.d.ts +38 -55
  82. package/dist/spend-guard.d.ts.map +1 -1
  83. package/dist/spend-guard.js +268 -83
  84. package/dist/spend-guard.js.map +1 -1
  85. package/dist/telemetry.d.ts.map +1 -1
  86. package/dist/telemetry.js +52 -21
  87. package/dist/telemetry.js.map +1 -1
  88. package/dist/templates/index.d.ts +17 -0
  89. package/dist/templates/index.d.ts.map +1 -0
  90. package/dist/templates/index.js +100 -0
  91. package/dist/templates/index.js.map +1 -0
  92. package/dist/types.d.ts +18 -3
  93. package/dist/types.d.ts.map +1 -1
  94. package/package.json +39 -4
  95. package/src/bindings/anthropic.ts +142 -0
  96. package/src/bindings/bedrock.ts +200 -0
  97. package/src/cli/auth.ts +145 -0
  98. package/src/cli/coach.ts +249 -0
  99. package/src/cli/models.ts +236 -0
  100. package/src/cli/tips.ts +161 -0
  101. package/src/cli/wizard.ts +160 -0
  102. package/src/coach/anomaly.ts +98 -0
  103. package/src/coach/conversation.ts +248 -0
  104. package/src/coach/forecast.ts +64 -0
  105. package/src/coach/llm-client.ts +247 -0
  106. package/src/coach/output.ts +172 -0
  107. package/src/coach/system-prompt.ts +181 -0
  108. package/src/openrouter-catalog.ts +180 -0
  109. package/src/templates/agent-support.yaml +30 -0
  110. package/src/templates/chargeback-evidence.yaml +30 -0
  111. package/src/templates/code-scan.yaml +30 -0
  112. package/src/templates/index.ts +109 -0
  113. package/src/templates/payment-approval.yaml +30 -0
  114. package/src/templates/risk-review.yaml +30 -0
  115. package/tests/fixtures/openrouter-catalog.json +1 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * AgentGuard(TM) Spend: Coach output writers.
3
+ *
4
+ * Files are written locally under ~/.agentguard by default. Conversation logs
5
+ * stay in ~/.agentguard/coach-sessions and are never uploaded by this SDK.
6
+ *
7
+ * Patent notice: Protected by U.S. patent-pending technology
8
+ * (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
9
+ * 64/071,781; 64/071,789).
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as os from 'os';
14
+ import * as path from 'path';
15
+ import type { SpendPolicy } from '../types';
16
+ import {
17
+ buildPolicyFromProfile,
18
+ formatCents,
19
+ projectedSavings,
20
+ type CoachBusinessProfile,
21
+ type ProjectedSavings,
22
+ } from './conversation';
23
+
24
+ export interface CoachOutputOptions {
25
+ home?: string;
26
+ now?: Date;
27
+ language?: 'ts' | 'py';
28
+ overwrite?: boolean;
29
+ }
30
+
31
+ export interface CoachOutputs {
32
+ policy: SpendPolicy;
33
+ policyPath: string;
34
+ quickstartPath: string;
35
+ sessionDir: string;
36
+ savings: ProjectedSavings;
37
+ policyYaml: string;
38
+ quickstartCode: string;
39
+ savingsTable: string;
40
+ }
41
+
42
+ export interface CoachSessionLogger {
43
+ path: string;
44
+ append: (event: string, payload?: Record<string, unknown>) => void;
45
+ }
46
+
47
+ export function agentguardHome(): string {
48
+ return process.env.AGENTGUARD_HOME || path.join(os.homedir(), '.agentguard');
49
+ }
50
+
51
+ export function coachSessionDir(home = agentguardHome()): string {
52
+ return path.join(home, 'coach-sessions');
53
+ }
54
+
55
+ export function createCoachSessionLogger(home = agentguardHome(), now = new Date()): CoachSessionLogger {
56
+ const dir = coachSessionDir(home);
57
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
58
+ const stamp = now.toISOString().replace(/[:.]/g, '-');
59
+ const file = path.join(dir, `${stamp}.jsonl`);
60
+ fs.closeSync(fs.openSync(file, 'a', 0o600));
61
+ return {
62
+ path: file,
63
+ append(event: string, payload: Record<string, unknown> = {}) {
64
+ const line = JSON.stringify({ ts: new Date().toISOString(), event, ...payload }) + '\n';
65
+ fs.appendFileSync(file, line, { encoding: 'utf8', mode: 0o600 });
66
+ },
67
+ };
68
+ }
69
+
70
+ export function writeCoachOutputs(profile: CoachBusinessProfile, options: CoachOutputOptions = {}): CoachOutputs {
71
+ const home = options.home ?? agentguardHome();
72
+ const language = options.language ?? profile.language;
73
+ const sessionDir = coachSessionDir(home);
74
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
75
+ fs.mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
76
+
77
+ const policy = buildPolicyFromProfile(profile);
78
+ const policyPath = path.join(home, 'policy.yaml');
79
+ const quickstartPath = path.join(home, language === 'py' ? 'quickstart.py' : 'quickstart.ts');
80
+ const savings = projectedSavings(profile);
81
+ const policyYaml = renderPolicyYaml(policy, profile, options.now ?? new Date());
82
+ const quickstartCode = language === 'py' ? renderQuickstartPy(policy, profile) : renderQuickstartTs(policy, profile);
83
+ const savingsTable = renderSavingsTable(savings);
84
+
85
+ if (!options.overwrite) {
86
+ assertWritableTarget(policyPath);
87
+ assertWritableTarget(quickstartPath);
88
+ }
89
+ atomicWrite(policyPath, policyYaml, 0o600);
90
+ atomicWrite(quickstartPath, quickstartCode, 0o600);
91
+
92
+ return { policy, policyPath, quickstartPath, sessionDir, savings, policyYaml, quickstartCode, savingsTable };
93
+ }
94
+
95
+ export function renderPolicyYaml(policy: SpendPolicy, profile: CoachBusinessProfile, now = new Date()): string {
96
+ const caps = policy.caps.map((cap) => {
97
+ const lines = [
98
+ ` # WHY: ${whyForWindow(cap.window)}`,
99
+ ` - amountCents: ${cap.amountCents}`,
100
+ ` window: ${cap.window}`,
101
+ ` action: ${cap.action}`,
102
+ ];
103
+ if (cap.downgradeTo) lines.push(` downgradeTo: ${cap.downgradeTo}`);
104
+ if (cap.reason) lines.push(` reason: ${quoteYaml(cap.reason)}`);
105
+ return lines.join('\n');
106
+ }).join('\n');
107
+ const tasks = profile.tasks.map((task) => ` - ${quoteYaml(task)}`).join('\n');
108
+ return `# AgentGuard Spend policy generated by agentguard coach\n# Generated at: ${now.toISOString()}\n# Scope key: ${profile.scopeLabel}\nid: ${policy.id}\nname: ${quoteYaml(policy.name)}\nversion: ${policy.version}\neffectiveFrom: ${quoteYaml(policy.effectiveFrom)}\nmode: ${policy.mode}\nrequiredCapability: ${policy.requiredCapability ?? 'read_only'}\nscope:\n tenantId: ${quoteYaml(policy.scope.tenantId)}\nmodels:\n primary: ${profile.primaryModel}\n fallback: ${profile.fallbackModel}\ntasks:\n${tasks}\ncaps:\n${caps}\nsystemInstructions: |\n${indent(systemInstructions(profile), 2)}\n`;
109
+ }
110
+
111
+ export function renderQuickstartTs(policy: SpendPolicy, profile: CoachBusinessProfile): string {
112
+ 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
+ }
114
+
115
+ export function renderQuickstartPy(policy: SpendPolicy, profile: CoachBusinessProfile): string {
116
+ const caps = policy.caps.map((cap) => {
117
+ const args = [`amountCents=${cap.amountCents}`, `window='${cap.window}'`, `action='${cap.action}'`];
118
+ if (cap.downgradeTo) args.push(`downgradeTo='${escapePy(cap.downgradeTo)}'`);
119
+ return ` SpendCap(${args.join(', ')}),`;
120
+ }).join('\n');
121
+ return `from openai import OpenAI\nfrom agentguard_spend import with_spend_guard\nfrom agentguard_spend.types import SpendPolicy, SpendCap\n\npolicy = SpendPolicy(\n id='${escapePy(policy.id)}',\n name='${escapePy(policy.name)}',\n scope={'tenantId': '${escapePy(profile.tenantId)}'},\n caps=[\n${caps}\n ],\n mode='${policy.mode}',\n requiredCapability='${policy.requiredCapability ?? 'read_only'}',\n version=${policy.version},\n effectiveFrom='${escapePy(policy.effectiveFrom)}',\n)\n\nopenrouter = OpenAI(base_url='https://openrouter.ai/api/v1')\nguarded_client = with_spend_guard(\n openrouter,\n policy=policy,\n scope={'tenantId': '${escapePy(profile.tenantId)}', 'agentId': '${escapePy(profile.vertical)}'},\n capability_claim='${policy.requiredCapability ?? 'read_only'}',\n)\n`;
122
+ }
123
+
124
+ export function renderSavingsTable(savings: ProjectedSavings): string {
125
+ const rows = [
126
+ 'Projected monthly savings',
127
+ `Before AgentGuard: ${formatCents(savings.monthlyBeforeCents)}`,
128
+ `After AgentGuard: ${formatCents(savings.monthlyAfterCents)}`,
129
+ `Savings: ${formatCents(savings.monthlySavingsCents)} (${savings.savingsPercent}%)`,
130
+ ];
131
+ return rows.join('\n');
132
+ }
133
+
134
+ function atomicWrite(file: string, content: string, mode: number): void {
135
+ const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
136
+ fs.writeFileSync(temp, content, { encoding: 'utf8', mode });
137
+ fs.renameSync(temp, file);
138
+ fs.chmodSync(file, mode);
139
+ }
140
+
141
+ function assertWritableTarget(file: string): void {
142
+ if (!fs.existsSync(file)) return;
143
+ fs.accessSync(file, fs.constants.W_OK);
144
+ }
145
+
146
+ function whyForWindow(window: string): string {
147
+ if (window === 'per_call') return 'Bounds one agent action and routes overflow to the fallback model.';
148
+ if (window === 'per_day') return 'Catches runaway loops and unexpected daily volume.';
149
+ if (window === 'per_month') return 'Keeps the finance view predictable for the billing cycle.';
150
+ return 'Limits spend inside the selected time window.';
151
+ }
152
+
153
+ function systemInstructions(profile: CoachBusinessProfile): string {
154
+ return `Run ${profile.vertical} tasks with ${profile.requiredCapability} capability. 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
+ }
156
+
157
+ function quoteYaml(value: string): string {
158
+ return JSON.stringify(value);
159
+ }
160
+
161
+ function indent(value: string, spaces: number): string {
162
+ const prefix = ' '.repeat(spaces);
163
+ return value.split('\n').map((line) => prefix + line).join('\n');
164
+ }
165
+
166
+ function escapeTs(value: string): string {
167
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
168
+ }
169
+
170
+ function escapePy(value: string): string {
171
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
172
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * AgentGuard(TM) Spend: Coach system prompt.
3
+ *
4
+ * AgentGuard Coach runs locally in the customer terminal. It may call the
5
+ * customer's chosen OpenAI-compatible provider, but it never calls AgentGuard
6
+ * infrastructure for prompts, completions, provider keys, signing keys, or
7
+ * policy details.
8
+ *
9
+ * Patent notice: Protected by U.S. patent-pending technology
10
+ * (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
11
+ * 64/071,781; 64/071,789).
12
+ */
13
+
14
+ export interface CoachPromptSection {
15
+ id: string;
16
+ title: string;
17
+ content: string;
18
+ }
19
+
20
+ export const COACH_SYSTEM_PROMPT_SECTIONS: CoachPromptSection[] = [
21
+ {
22
+ id: 'role',
23
+ title: 'Role',
24
+ content: `You are AgentGuard Coach, helping a business owner configure AgentGuard Spend for their specific company. You produce a complete policy.yaml plus quickstart code customized to their vertical. You never make up cap values without explaining the math. You ask one question at a time, keep the tone direct, and optimize for a working local setup in under 90 seconds.`,
25
+ },
26
+ {
27
+ id: 'sdk-knowledge',
28
+ title: 'SDK Knowledge',
29
+ content: `AgentGuard Spend is a zero-data-plane SDK. Policy evaluation runs inside the customer process. Prompts, completions, provider API keys, signing keys, policies, and cost overrides never go to AgentGuard infrastructure.
30
+
31
+ Enforcement actions:
32
+ - allow: call proceeds unchanged.
33
+ - downgrade: model is rewritten to the cap's downgradeTo model, then the call proceeds.
34
+ - shadow: decision is signed and logged, but traffic is not changed.
35
+ - block: provider call is stopped before bytes leave the customer process.
36
+
37
+ Spend windows:
38
+ - per_call: one call only.
39
+ - per_minute: burst protection.
40
+ - per_hour: short campaign or batch guard.
41
+ - per_day: normal operating budget.
42
+ - per_month: finance-facing budget ceiling.
43
+
44
+ Capability tiers in ascending order:
45
+ - read_only: read records and produce summaries or recommendations.
46
+ - data_write: write database fields, tickets, messages, notes, or drafts.
47
+ - payment_initiate: create or recommend payment, refund, dispute, or charge flows.
48
+ - payment_execute: execute money movement only with a verified attestation.
49
+
50
+ Policy schema:
51
+ SpendPolicy has id, name, scope, caps, mode, version, effectiveFrom, and optional requiredCapability. SpendScope has tenantId plus optional userId, teamId, agentId, taskId, provider. SpendCap has amountCents, window, action, optional downgradeTo, optional reason.
52
+
53
+ Provider wrappers:
54
+ - withSpendGuard wraps OpenAI-compatible chat.completions.create clients, including OpenRouter.
55
+ - withSpendGuardAnthropic wraps Anthropic messages.create and messages.stream.
56
+ - withSpendGuardBedrock wraps BedrockRuntimeClient send for InvokeModelCommand and InvokeModelWithResponseStreamCommand.
57
+
58
+ Streaming true-up:
59
+ The SDK reserves against projected tokens before a stream starts. On stream completion or cancellation, it signs a settlement entry with actual usage when the provider supplies usage, and falls back to the local token estimator when needed.`,
60
+ },
61
+ {
62
+ id: 'vertical-encyclopedia-core',
63
+ title: 'Vertical Encyclopedia: Installed Skills',
64
+ content: `Use these condensed vertical patterns as RAG context, not training data.
65
+
66
+ Law firm:
67
+ Tasks: contract redline, novel contract review, legal research, brief drafting, deposition prep, discovery review. Models: anthropic/claude-haiku-4-5 for boilerplate and discovery, anthropic/claude-sonnet-4-6 for research and novel terms, anthropic/claude-opus-4-7 for brief drafting and deposition analysis. Caps: $0.25 discovery tagging, $1 contract redline, $3 novel review, $4 legal research, $8 brief drafting. Scope key: matter or client matter code in agentId, attorney in userId. Compliance: privilege workflows need zero proxy, per-matter accounting, jurisdiction-aware model allowlists, and signed decision receipts for audit packets.
68
+
69
+ Healthcare:
70
+ Tasks: patient triage, chart review, clinical documentation drafting, prior authorization narrative, medical literature search, patient education, insurance verification, appointment scheduling. Models: BAA-covered Bedrock Anthropic for PHI, OpenRouter openai/gpt-5-mini or anthropic/claude-haiku-4-5 for non-PHI. Caps: $0.10 education, $0.25 insurance verification, $0.50 triage, $2 chart review, $3 documentation, $5 prior authorization. Scope key: encounter or patient encounter in agentId, clinician in userId. Compliance: PHI workflows require data_write minimum plus provider BAA controls. Non-PHI can use OpenRouter.
71
+
72
+ E-commerce:
73
+ Tasks: support triage, order status, returns, refund authorization, chargeback evidence, fraud screen, product recommendations, abandoned cart. Models: anthropic/claude-haiku-4-5 and openai/gpt-5-mini for bulk support, anthropic/claude-sonnet-4-6 for escalated fraud and high-value refund review, anthropic/claude-opus-4-7 for chargeback evidence. Caps: $0.05 order status, $0.10 support triage, $0.25 returns or small refund, $1 high-value refund review, $5 chargeback evidence. Scope key: order or dispute id in agentId. Compliance: payment-touching workflows require payment_initiate, and money movement requires payment_execute with attestation.
74
+
75
+ Accounting:
76
+ Tasks: receipt OCR, GL categorization, bank reconciliation, anomaly flagging, month-end accrual drafting, audit prep narrative, tax research, tax memo drafting, financial statement footnotes. Models: openai/gpt-5-mini for OCR and anomaly flagging, anthropic/claude-haiku-4-5 for GL categorization, anthropic/claude-sonnet-4-6 for accruals and audit prep, anthropic/claude-opus-4-7 for tax research and tax memos. Caps: $0.05 OCR, $0.10 GL and bank rec, $1.50 accruals, $2 audit prep, $5 tax research, $7 tax memo. Scope key: client engagement in agentId. Compliance: SOX and audit workflows require data_write for ledger changes, with retained signed receipts.
77
+
78
+ Real estate:
79
+ Tasks: listing description, luxury listing copy, comparable research, buyer-match scoring, contract analysis, mortgage doc review, tenant communication, lease drafting, ticket triage, title issue research. Models: openai/gpt-5-mini for listings and ticket triage, anthropic/claude-haiku-4-5 for comps and tenant comms, anthropic/claude-sonnet-4-6 for mortgage docs and lease drafting, anthropic/claude-opus-4-7 for title and purchase agreement analysis. Caps: $0.05 listing and tickets, $0.10 buyer match or tenant comm, $0.25 comps, $0.50 luxury copy, $2 mortgage docs, $3 lease drafting, $5 contract analysis, $6 title research. Scope key: listing, loan, or transaction id in agentId. Compliance: fair-housing review, state disclosure review, and per-transaction receipts.`,
80
+ },
81
+ {
82
+ id: 'vertical-encyclopedia-extra',
83
+ title: 'Vertical Encyclopedia: Additional Profiles',
84
+ content: `Insurance: claims intake on openai/gpt-5-mini at $0.10, claim summary on anthropic/claude-haiku-4-5 at $0.25, coverage review on anthropic/claude-sonnet-4-6 at $2, litigation packet on anthropic/claude-opus-4-7 at $6. Scope: claim id. Capability: data_write for claim notes.
85
+ Marketing agency: brief intake on gpt-5-mini at $0.05, content variants on claude-haiku at $0.10, brand strategy on claude-sonnet at $1, campaign audit on gpt-5-mini at $0.25. Scope: client project.
86
+ Software dev team: PR summary on gpt-5-mini at $0.10, code scan on gemini-3-flash-preview at $0.10, architecture review on claude-sonnet at $2, release risk review on claude-opus at $5. Scope: repo or PR.
87
+ Education: lesson outline on gpt-5-mini at $0.05, tutoring response on claude-haiku at $0.10, assessment rubric on claude-sonnet at $1, academic integrity review on claude-sonnet at $2. Scope: class or student session. Student records require data_write for gradebook updates.
88
+ Tutoring: diagnostic quiz on gpt-5-mini at $0.05, explanation on claude-haiku at $0.10, learning plan on claude-sonnet at $1. Scope: learner session.
89
+ Local services: intake summary on gpt-5-mini at $0.05, quote draft on claude-haiku at $0.10, dispute response on claude-sonnet at $1. Scope: job id.
90
+ Restaurant: menu copy on gpt-5-mini at $0.05, review response on claude-haiku at $0.10, supplier negotiation prep on claude-sonnet at $1. Scope: location or vendor.
91
+ Fitness and gym: class copy on gpt-5-mini at $0.05, member support on claude-haiku at $0.10, retention plan on claude-sonnet at $1. Scope: member segment. Health notes require data_write.
92
+ Dental practice: appointment support on gpt-5-mini at $0.05, patient education on claude-haiku at $0.10, insurance narrative on claude-sonnet at $2. Scope: encounter. PHI requires data_write and BAA-covered provider.
93
+ Salon and spa: booking support on gpt-5-mini at $0.05, client message on claude-haiku at $0.10, inventory planning on gpt-5-mini at $0.10. Scope: location or appointment.
94
+ Construction: bid takeoff summary on gpt-5-mini at $0.25, RFI draft on claude-haiku at $0.25, contract risk on claude-sonnet at $2, claim narrative on claude-opus at $5. Scope: project.
95
+ Landscaping: estimate draft on gpt-5-mini at $0.05, route support on claude-haiku at $0.10, seasonal campaign on gpt-5-mini at $0.10. Scope: route or job.
96
+ Pet care: appointment notes on gpt-5-mini at $0.05, customer support on claude-haiku at $0.10, incident summary on claude-sonnet at $1. Scope: booking or pet profile.
97
+ Photography: inquiry response on gpt-5-mini at $0.05, proposal on claude-haiku at $0.10, contract review on claude-sonnet at $1. Scope: client shoot.
98
+ Freelance design: brief summary on gpt-5-mini at $0.05, concept copy on claude-haiku at $0.10, proposal on claude-sonnet at $1. Scope: client project.
99
+ Podcast: show notes on gpt-5-mini at $0.05, clip ideas on claude-haiku at $0.10, sponsor proposal on claude-sonnet at $1. Scope: episode.
100
+ Manufacturing: work-order summary on gpt-5-mini at $0.10, supplier risk on claude-sonnet at $2, QA incident report on claude-sonnet at $1. Scope: work order.
101
+ Logistics: shipment exception triage on gpt-5-mini at $0.05, carrier dispute on claude-sonnet at $1, route planning narrative on claude-haiku at $0.25. Scope: shipment.
102
+ Nonprofit: donor email on gpt-5-mini at $0.05, grant outline on claude-sonnet at $1, impact report on claude-sonnet at $2. Scope: campaign or grant.
103
+ HR and recruiting: resume screen on gpt-5-mini at $0.05, interview packet on claude-haiku at $0.10, policy review on claude-sonnet at $1. Scope: requisition. Employment decisions need human review.
104
+ Travel agency: itinerary draft on gpt-5-mini at $0.10, disruption support on claude-haiku at $0.25, VIP trip plan on claude-sonnet at $2. Scope: trip.
105
+ Banking and fintech: support triage on gpt-5-mini at $0.05, KYC summary on claude-sonnet at $1, dispute packet on claude-sonnet at $2, payment workflow requires payment_initiate.
106
+ Government contractor: RFP summary on claude-haiku at $0.25, compliance matrix on claude-sonnet at $2, proposal draft on claude-opus at $6. Scope: opportunity. Data residency may constrain providers.
107
+ Energy and utilities: ticket triage on gpt-5-mini at $0.05, outage summary on claude-haiku at $0.25, regulatory response on claude-sonnet at $2. Scope: incident.
108
+ Agriculture: grant draft on claude-sonnet at $1, field note summary on gpt-5-mini at $0.05, compliance report on claude-sonnet at $2. Scope: farm or field.
109
+ Property management: tenant ticket on gpt-5-mini at $0.05, lease question on claude-haiku at $0.25, legal escalation on claude-sonnet at $2. Scope: property or unit.
110
+ Automotive dealer: lead response on gpt-5-mini at $0.05, finance packet review on claude-sonnet at $1, service estimate support on claude-haiku at $0.10. Scope: lead or repair order.
111
+ Creator business: content repurpose on gpt-5-mini at $0.05, sponsor pitch on claude-sonnet at $1, audience analysis on claude-haiku at $0.25. Scope: campaign.
112
+ Security operations: alert summary on gpt-5-mini at $0.05, incident report on claude-sonnet at $2, postmortem on claude-opus at $5. Scope: incident.`,
113
+ },
114
+ {
115
+ id: 'cost-table-summary',
116
+ title: 'Cost Table Summary',
117
+ content: `Use only known model names. Prices are approximate dollars per 1M tokens and must be presented as estimates.
118
+ openai/gpt-5: input $5.00, output $15.00.
119
+ openai/gpt-5-mini: input $0.50, output $2.00.
120
+ openai/gpt-4o: input $2.50, output $10.00.
121
+ openai/gpt-4o-mini: input $0.15, output $0.60.
122
+ anthropic/claude-opus-4-7: input $20.00, output $100.00.
123
+ anthropic/claude-opus-4-6: input $15.00, output $75.00.
124
+ anthropic/claude-sonnet-4-6: input $3.00, output $15.00.
125
+ anthropic/claude-sonnet-4-5: input $3.00, output $15.00.
126
+ anthropic/claude-haiku-4-5: input $1.00, output $5.00.
127
+ google/gemini-3.1-pro-preview: input $2.00, output $10.00.
128
+ google/gemini-3-flash-preview: input $0.20, output $1.00.
129
+ anthropic.claude-opus-4-v1:0: input $15.00, output $75.00.
130
+ anthropic.claude-sonnet-4-v1:0: input $3.00, output $15.00.
131
+ amazon.nova-pro-v1:0: input $0.80, output $3.20.
132
+ amazon.nova-lite-v1:0: input $0.06, output $0.24.
133
+ For unknown live OpenRouter models, ask the customer to run agentguard models --sync-pricing before using them in a policy.`,
134
+ },
135
+ {
136
+ id: 'policy-reference',
137
+ title: 'Policy YAML Reference',
138
+ content: `Annotated policy template:
139
+ id: business-task-v1
140
+ name: Human readable policy name
141
+ version: 1
142
+ effectiveFrom: "2026-05-28T00:00:00.000Z"
143
+ mode: enforce
144
+ requiredCapability: read_only
145
+ scope:
146
+ tenantId: customer-business-id
147
+ teamId: optional-team
148
+ agentId: optional-project-or-transaction
149
+ caps:
150
+ # WHY: Per-call cap bounds one agent action.
151
+ - amountCents: 50
152
+ window: per_call
153
+ action: downgrade
154
+ downgradeTo: openai/gpt-4o-mini
155
+ reason: "Per-call budget reached, route to fallback"
156
+ # WHY: Daily cap catches loops and unexpected traffic.
157
+ - amountCents: 2500
158
+ window: per_day
159
+ action: block
160
+ reason: "Daily budget reached"
161
+ # WHY: Monthly cap gives finance a clear ceiling.
162
+ - amountCents: 50000
163
+ window: per_month
164
+ action: block
165
+ reason: "Monthly budget reached"`,
166
+ },
167
+ {
168
+ id: 'conversation-flow',
169
+ title: 'Conversation Flow Instructions',
170
+ content: `Ask one question at a time. The required flow is: Q1 what are you building, Q2 team size and monthly volume, Q3 top three AI tasks, Q4 monthly budget or per-task budget, Q5 confirm and offer refinements. After Q5, show projected savings math before finalizing. Always explain cap values with arithmetic. Always offer to write the local file. If the vertical is unknown, ask for customer type, task list, and whether any task touches payments, health data, financial records, legal privilege, student records, or regulated employment decisions. Suggest agentguard demo at the end.`,
171
+ },
172
+ {
173
+ id: 'hard-rules',
174
+ title: 'Hard Rules',
175
+ content: `Never propose a proxy architecture. Never suggest managed-key services. Never invent model names or prices outside the cost table. For HIPAA, SOX, privilege, student records, financial records, or employment workflows, require capability gating with data_write minimum when data can be written. For payment-touching workflows, require payment_initiate. For money movement, require payment_execute plus verified attestation. Keep conversation logs local in ~/.agentguard/coach-sessions.`,
176
+ },
177
+ ];
178
+
179
+ export const MASTER_SYSTEM_PROMPT = COACH_SYSTEM_PROMPT_SECTIONS
180
+ .map((section) => `## ${section.title}\n${section.content}`)
181
+ .join('\n\n');
@@ -0,0 +1,180 @@
1
+ /**
2
+ * AgentGuard(TM) Spend: OpenRouter catalog sync.
3
+ *
4
+ * OpenRouter requests go directly from the customer runtime to openrouter.ai.
5
+ * AgentGuard infrastructure does not receive prompts, completions, keys,
6
+ * policy files, or pricing overrides.
7
+ *
8
+ * Patent notice: Protected by U.S. patent-pending technology
9
+ * (App. Nos. 63/983,615; 63/983,621; 63/983,843; 63/984,626;
10
+ * 64/071,781; 64/071,789).
11
+ */
12
+
13
+ import * as fs from 'fs';
14
+ import * as https from 'https';
15
+ import * as os from 'os';
16
+ import * as path from 'path';
17
+ import { DEFAULT_COST_OVERRIDES_PATH, persistCostOverrides, setCostOverride, type ModelCost } from './cost-table';
18
+
19
+ export interface OpenRouterPricing {
20
+ prompt?: string;
21
+ completion?: string;
22
+ image?: string;
23
+ request?: string;
24
+ }
25
+
26
+ export interface OpenRouterModel {
27
+ id: string;
28
+ name?: string;
29
+ context_length?: number;
30
+ pricing?: OpenRouterPricing;
31
+ architecture?: { modality?: string; tokenizer?: string; instruct_type?: string | null };
32
+ top_provider?: { context_length?: number; max_completion_tokens?: number | null; is_moderated?: boolean };
33
+ per_request_limits?: Record<string, unknown> | null;
34
+ [key: string]: unknown;
35
+ }
36
+
37
+ export interface OpenRouterCatalog {
38
+ data: OpenRouterModel[];
39
+ fetchedAt?: string;
40
+ }
41
+
42
+ export interface FetchCatalogOptions {
43
+ ttlMs?: number;
44
+ force?: boolean;
45
+ endpoint?: string;
46
+ cachePath?: string;
47
+ }
48
+
49
+ export interface SyncPricingResult {
50
+ count: number;
51
+ applied: string[];
52
+ }
53
+
54
+ const DAY_MS = 24 * 60 * 60 * 1000;
55
+ const STATE_DIR = process.env.AGENTGUARD_HOME || path.join(os.homedir(), '.agentguard');
56
+ const DEFAULT_CACHE_PATH = path.join(STATE_DIR, 'openrouter-catalog.json');
57
+ const DEFAULT_ENDPOINT = 'https://openrouter.ai/api/v1/models';
58
+
59
+ let memoryCatalog: OpenRouterCatalog | null = null;
60
+
61
+ export async function fetchCatalog(opts: FetchCatalogOptions = {}): Promise<OpenRouterCatalog> {
62
+ const ttlMs = opts.ttlMs ?? DAY_MS;
63
+ const cachePath = opts.cachePath ?? DEFAULT_CACHE_PATH;
64
+ if (!opts.force) {
65
+ const cached = readCachedCatalog(cachePath, ttlMs);
66
+ if (cached) return cached;
67
+ }
68
+
69
+ const fixturePath = process.env.AGENTGUARD_OPENROUTER_CATALOG_FIXTURE;
70
+ if (fixturePath) {
71
+ const fixture = parseCatalog(fs.readFileSync(fixturePath, 'utf8'));
72
+ return saveCatalog(fixture, cachePath);
73
+ }
74
+
75
+ try {
76
+ const body = await getJson(opts.endpoint ?? DEFAULT_ENDPOINT);
77
+ const catalog = parseCatalog(body);
78
+ return saveCatalog(catalog, cachePath);
79
+ } catch (err) {
80
+ const cached = readCachedCatalog(cachePath, Number.MAX_SAFE_INTEGER);
81
+ if (cached) return cached;
82
+ throw err;
83
+ }
84
+ }
85
+
86
+ export function getCachedCatalog(cachePath: string = DEFAULT_CACHE_PATH): OpenRouterCatalog | null {
87
+ if (memoryCatalog) return memoryCatalog;
88
+ return readCachedCatalog(cachePath, Number.MAX_SAFE_INTEGER);
89
+ }
90
+
91
+ export async function syncPricingIntoCostTable(opts: FetchCatalogOptions = {}): Promise<SyncPricingResult> {
92
+ const catalog = await fetchCatalog(opts);
93
+ const applied: string[] = [];
94
+ for (const model of catalog.data) {
95
+ const cost = modelCostFromOpenRouter(model);
96
+ if (!cost) continue;
97
+ setCostOverride(model.id, cost);
98
+ applied.push(model.id);
99
+ }
100
+ persistCostOverrides(DEFAULT_COST_OVERRIDES_PATH);
101
+ return { count: applied.length, applied };
102
+ }
103
+
104
+ export function persistOverrides(filePath?: string): void {
105
+ persistCostOverrides(filePath);
106
+ }
107
+
108
+ export function modelCostFromOpenRouter(model: OpenRouterModel): ModelCost | null {
109
+ const prompt = Number(model.pricing?.prompt);
110
+ const completion = Number(model.pricing?.completion);
111
+ if (!Number.isFinite(prompt) || !Number.isFinite(completion) || prompt < 0 || completion < 0) {
112
+ return null;
113
+ }
114
+ return {
115
+ inputCentsPerKtok: prompt * 100 * 1000,
116
+ outputCentsPerKtok: completion * 100 * 1000,
117
+ };
118
+ }
119
+
120
+ function readCachedCatalog(filePath: string, ttlMs: number): OpenRouterCatalog | null {
121
+ try {
122
+ const stat = fs.statSync(filePath);
123
+ if (Date.now() - stat.mtimeMs > ttlMs) return null;
124
+ const catalog = parseCatalog(fs.readFileSync(filePath, 'utf8'));
125
+ memoryCatalog = catalog;
126
+ return catalog;
127
+ } catch {
128
+ return null;
129
+ }
130
+ }
131
+
132
+ function saveCatalog(catalog: OpenRouterCatalog, filePath: string): OpenRouterCatalog {
133
+ const stamped: OpenRouterCatalog = { ...catalog, fetchedAt: catalog.fetchedAt ?? new Date().toISOString() };
134
+ memoryCatalog = stamped;
135
+ try {
136
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
137
+ fs.writeFileSync(filePath, JSON.stringify(stamped, null, 2) + '\n', { mode: 0o600 });
138
+ try { fs.chmodSync(filePath, 0o600); } catch { return stamped; }
139
+ } catch {
140
+ return stamped;
141
+ }
142
+ return stamped;
143
+ }
144
+
145
+ function parseCatalog(body: string): OpenRouterCatalog {
146
+ const parsed = JSON.parse(body) as OpenRouterCatalog;
147
+ if (!parsed || !Array.isArray(parsed.data)) throw new Error('OpenRouter catalog response must include data[]');
148
+ const data = parsed.data.filter((item): item is OpenRouterModel => Boolean(item && typeof item.id === 'string'));
149
+ return { ...parsed, data };
150
+ }
151
+
152
+ function getJson(endpoint: string): Promise<string> {
153
+ return new Promise((resolve, reject) => {
154
+ const url = new URL(endpoint);
155
+ const req = https.request(
156
+ {
157
+ method: 'GET',
158
+ hostname: url.hostname,
159
+ path: url.pathname + url.search,
160
+ headers: { accept: 'application/json' },
161
+ timeout: 5000,
162
+ },
163
+ (res) => {
164
+ const chunks: Buffer[] = [];
165
+ res.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
166
+ res.on('end', () => {
167
+ const body = Buffer.concat(chunks).toString('utf8');
168
+ if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
169
+ reject(new Error('OpenRouter catalog fetch failed with HTTP ' + String(res.statusCode)));
170
+ return;
171
+ }
172
+ resolve(body);
173
+ });
174
+ },
175
+ );
176
+ req.on('error', reject);
177
+ req.on('timeout', () => req.destroy(new Error('OpenRouter catalog fetch timed out')));
178
+ req.end();
179
+ });
180
+ }
@@ -0,0 +1,30 @@
1
+ # AgentGuard Spend task template: agent-support
2
+ # Local-only policy file. Prompts, completions, API keys, and signing keys stay in the customer runtime.
3
+ id: agent-support-v1
4
+ name: Agent support workflow
5
+ version: 1
6
+ effectiveFrom: "2026-05-27T00:00:00.000Z"
7
+ mode: enforce
8
+ requiredCapability: data_write
9
+ scope:
10
+ tenantId: my-tenant
11
+ models:
12
+ primary: openai/gpt-4o-mini
13
+ fallback: google/gemini-3-flash-preview
14
+ allowed:
15
+ - openai/gpt-4o-mini
16
+ - google/gemini-3-flash-preview
17
+ caps:
18
+ # WHY: 25 cents per call bounds one agent action while keeping normal work flowing.
19
+ - amountCents: 25
20
+ window: per_call
21
+ action: downgrade
22
+ downgradeTo: google/gemini-3-flash-preview
23
+ reason: "Per-call budget reached, routing to fallback model"
24
+ # WHY: Daily cap catches loops and unexpected traffic before monthly budgets drift.
25
+ - amountCents: 10000
26
+ window: per_day
27
+ action: block
28
+ reason: "Daily budget reached"
29
+ systemInstructions: |
30
+ Draft support replies and update allowed fields only after policy checks. Escalate billing, payment, and identity changes.
@@ -0,0 +1,30 @@
1
+ # AgentGuard Spend task template: chargeback-evidence
2
+ # Local-only policy file. Prompts, completions, API keys, and signing keys stay in the customer runtime.
3
+ id: chargeback-evidence-v1
4
+ name: Chargeback evidence agent
5
+ version: 1
6
+ effectiveFrom: "2026-05-27T00:00:00.000Z"
7
+ mode: enforce
8
+ requiredCapability: read_only
9
+ scope:
10
+ tenantId: my-tenant
11
+ models:
12
+ primary: openai/gpt-5-mini
13
+ fallback: openai/gpt-4o-mini
14
+ allowed:
15
+ - openai/gpt-5-mini
16
+ - openai/gpt-4o-mini
17
+ caps:
18
+ # WHY: 100 cents per call bounds one agent action while keeping normal work flowing.
19
+ - amountCents: 100
20
+ window: per_call
21
+ action: downgrade
22
+ downgradeTo: openai/gpt-4o-mini
23
+ reason: "Per-call budget reached, routing to fallback model"
24
+ # WHY: Daily cap catches loops and unexpected traffic before monthly budgets drift.
25
+ - amountCents: 5000
26
+ window: per_day
27
+ action: block
28
+ reason: "Daily budget reached"
29
+ systemInstructions: |
30
+ Assemble claim evidence from provided records. Cite source IDs and keep disputed facts separate from verified records.
@@ -0,0 +1,30 @@
1
+ # AgentGuard Spend task template: code-scan
2
+ # Local-only policy file. Prompts, completions, API keys, and signing keys stay in the customer runtime.
3
+ id: code-scan-v1
4
+ name: Code scan agent
5
+ version: 1
6
+ effectiveFrom: "2026-05-27T00:00:00.000Z"
7
+ mode: enforce
8
+ requiredCapability: read_only
9
+ scope:
10
+ tenantId: my-tenant
11
+ models:
12
+ primary: google/gemini-3-flash-preview
13
+ fallback: openai/gpt-4o-mini
14
+ allowed:
15
+ - google/gemini-3-flash-preview
16
+ - openai/gpt-4o-mini
17
+ caps:
18
+ # WHY: 10 cents per call bounds one agent action while keeping normal work flowing.
19
+ - amountCents: 10
20
+ window: per_call
21
+ action: downgrade
22
+ downgradeTo: openai/gpt-4o-mini
23
+ reason: "Per-call budget reached, routing to fallback model"
24
+ # WHY: Daily cap catches loops and unexpected traffic before monthly budgets drift.
25
+ - amountCents: 3000
26
+ window: per_day
27
+ action: block
28
+ reason: "Daily budget reached"
29
+ systemInstructions: |
30
+ Scan code for spend, audit, and integration risks. Return findings with file paths and minimal fix guidance.