govyn 0.0.1 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +263 -1
- package/configs/multi-provider.yaml +68 -0
- package/configs/openai-only.yaml +45 -0
- package/configs/team-setup.yaml +88 -0
- package/dist/action-logger.d.ts +128 -0
- package/dist/action-logger.js +356 -0
- package/dist/action-logger.js.map +1 -0
- package/dist/admin-cli.d.ts +2 -0
- package/dist/admin-cli.js +36 -0
- package/dist/admin-cli.js.map +1 -0
- package/dist/agents.d.ts +23 -0
- package/dist/agents.js +59 -0
- package/dist/agents.js.map +1 -0
- package/dist/alert-api.d.ts +14 -0
- package/dist/alert-api.js +355 -0
- package/dist/alert-api.js.map +1 -0
- package/dist/alert-manager.d.ts +77 -0
- package/dist/alert-manager.js +267 -0
- package/dist/alert-manager.js.map +1 -0
- package/dist/approval-api.d.ts +19 -0
- package/dist/approval-api.js +82 -0
- package/dist/approval-api.js.map +1 -0
- package/dist/approval-timeout.d.ts +29 -0
- package/dist/approval-timeout.js +45 -0
- package/dist/approval-timeout.js.map +1 -0
- package/dist/approval.d.ts +78 -0
- package/dist/approval.js +101 -0
- package/dist/approval.js.map +1 -0
- package/dist/auth.d.ts +47 -0
- package/dist/auth.js +335 -0
- package/dist/auth.js.map +1 -0
- package/dist/budget-api.d.ts +20 -0
- package/dist/budget-api.js +85 -0
- package/dist/budget-api.js.map +1 -0
- package/dist/budget-enforcer.d.ts +102 -0
- package/dist/budget-enforcer.js +294 -0
- package/dist/budget-enforcer.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +267 -0
- package/dist/config.js.map +1 -0
- package/dist/cost-aggregator.d.ts +69 -0
- package/dist/cost-aggregator.js +305 -0
- package/dist/cost-aggregator.js.map +1 -0
- package/dist/cost-api.d.ts +29 -0
- package/dist/cost-api.js +128 -0
- package/dist/cost-api.js.map +1 -0
- package/dist/database-url.d.ts +6 -0
- package/dist/database-url.js +47 -0
- package/dist/database-url.js.map +1 -0
- package/dist/db-retention.d.ts +53 -0
- package/dist/db-retention.js +82 -0
- package/dist/db-retention.js.map +1 -0
- package/dist/db-schema.d.ts +17 -0
- package/dist/db-schema.js +167 -0
- package/dist/db-schema.js.map +1 -0
- package/dist/db-writer.d.ts +55 -0
- package/dist/db-writer.js +115 -0
- package/dist/db-writer.js.map +1 -0
- package/dist/db.d.ts +33 -0
- package/dist/db.js +78 -0
- package/dist/db.js.map +1 -0
- package/dist/events.d.ts +77 -0
- package/dist/events.js +12 -0
- package/dist/events.js.map +1 -0
- package/dist/health.d.ts +14 -0
- package/dist/health.js +49 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/init-wizard.d.ts +12 -0
- package/dist/init-wizard.js +206 -0
- package/dist/init-wizard.js.map +1 -0
- package/dist/log-api.d.ts +20 -0
- package/dist/log-api.js +371 -0
- package/dist/log-api.js.map +1 -0
- package/dist/log-rotator.d.ts +55 -0
- package/dist/log-rotator.js +157 -0
- package/dist/log-rotator.js.map +1 -0
- package/dist/loop-detector.d.ts +71 -0
- package/dist/loop-detector.js +122 -0
- package/dist/loop-detector.js.map +1 -0
- package/dist/persistence-types.d.ts +165 -0
- package/dist/persistence-types.js +2 -0
- package/dist/persistence-types.js.map +1 -0
- package/dist/persistence.d.ts +185 -0
- package/dist/persistence.js +785 -0
- package/dist/persistence.js.map +1 -0
- package/dist/policy-api.d.ts +25 -0
- package/dist/policy-api.js +347 -0
- package/dist/policy-api.js.map +1 -0
- package/dist/policy-engine.d.ts +76 -0
- package/dist/policy-engine.js +835 -0
- package/dist/policy-engine.js.map +1 -0
- package/dist/policy-file.d.ts +10 -0
- package/dist/policy-file.js +52 -0
- package/dist/policy-file.js.map +1 -0
- package/dist/policy-parser.d.ts +21 -0
- package/dist/policy-parser.js +560 -0
- package/dist/policy-parser.js.map +1 -0
- package/dist/policy-types.d.ts +216 -0
- package/dist/policy-types.js +8 -0
- package/dist/policy-types.js.map +1 -0
- package/dist/policy-watcher.d.ts +54 -0
- package/dist/policy-watcher.js +116 -0
- package/dist/policy-watcher.js.map +1 -0
- package/dist/pricing.d.ts +69 -0
- package/dist/pricing.js +93 -0
- package/dist/pricing.js.map +1 -0
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +47 -0
- package/dist/prompt.js.map +1 -0
- package/dist/providers/anthropic.d.ts +18 -0
- package/dist/providers/anthropic.js +61 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/custom.d.ts +19 -0
- package/dist/providers/custom.js +54 -0
- package/dist/providers/custom.js.map +1 -0
- package/dist/providers/openai.d.ts +17 -0
- package/dist/providers/openai.js +48 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/proxy.d.ts +57 -0
- package/dist/proxy.js +477 -0
- package/dist/proxy.js.map +1 -0
- package/dist/router.d.ts +23 -0
- package/dist/router.js +89 -0
- package/dist/router.js.map +1 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +139 -0
- package/dist/runtime.js.map +1 -0
- package/dist/security.d.ts +64 -0
- package/dist/security.js +422 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +1147 -0
- package/dist/server.js.map +1 -0
- package/dist/sqlite-schema.d.ts +6 -0
- package/dist/sqlite-schema.js +134 -0
- package/dist/sqlite-schema.js.map +1 -0
- package/dist/streaming.d.ts +24 -0
- package/dist/streaming.js +63 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tokens.d.ts +45 -0
- package/dist/tokens.js +237 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +344 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GovynAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,265 @@
|
|
|
1
1
|
# Govyn
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The governance proxy for AI agents. Not an SDK. Not a wrapper. A wall.**
|
|
4
|
+
|
|
5
|
+
Every other agent governance tool is a library you import. If your agent — or any code it touches — makes a direct HTTP call, governance disappears. Govyn is different. It's an API proxy that holds your real API keys. Your agents only get a proxy URL. There is no alternative path to the real API. Governance is enforced by architecture, not by convention.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
SDK MODEL:
|
|
9
|
+
Agent [has real API key] → tries wrapper → OpenAI API
|
|
10
|
+
Agent [has real API key] → skips wrapper → OpenAI API ← governance bypassed
|
|
11
|
+
|
|
12
|
+
PROXY MODEL:
|
|
13
|
+
Agent [no real API key] → Govyn Proxy [has real key, enforces rules] → OpenAI API
|
|
14
|
+
Agent [no real API key] → OpenAI API directly → REJECTED (no key)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
SDK governance is a door lock — effective until someone finds another door.
|
|
18
|
+
Govyn is a wall. There are no other doors.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Per-agent budgets** — Set daily/monthly spend limits per agent with hard (block) or soft (warn) enforcement
|
|
25
|
+
- **Cost tracking** — Real-time cost aggregation across OpenAI, Anthropic, and compatible providers
|
|
26
|
+
- **Loop detection** — Automatically detect and block agents stuck in repetitive call patterns
|
|
27
|
+
- **Policy engine** — YAML-based rules: rate limits, model restrictions, require-approval gates, and custom policies
|
|
28
|
+
- **Action logging** — Every request logged with agent identity, cost, tokens, latency, and full context
|
|
29
|
+
- **Multi-provider** — Route OpenAI and Anthropic traffic through a single proxy with per-provider API keys
|
|
30
|
+
- **Locked-down management API** — Local-only by default, with optional admin API key and browser origin allowlist for remote dashboards
|
|
31
|
+
- **Zero agent changes** — Agents just point at a different base URL. No SDK imports, no code changes
|
|
32
|
+
- **Fail-open by default** — Proxy errors don't break your agents. Configurable to fail-closed for high-security deployments
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
Get from zero to a governed API call in under 5 minutes.
|
|
37
|
+
|
|
38
|
+
### Prerequisites
|
|
39
|
+
|
|
40
|
+
- Node.js 20+
|
|
41
|
+
- An LLM API key (OpenAI or Anthropic)
|
|
42
|
+
|
|
43
|
+
### Install and Configure
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx govyn init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The wizard walks you through provider selection, API key configuration, budget limits, agent naming, and persistence. It produces a local `govyn.config.yaml` in the current directory, defaults `database.url` to `sqlite:./govyn.db`, and can also point at PostgreSQL if you already run one.
|
|
50
|
+
|
|
51
|
+
### Start the Proxy
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx govyn
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The proxy starts on port 4000 by default.
|
|
58
|
+
|
|
59
|
+
### Docker
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
docker run -p 4000:4000 -e OPENAI_API_KEY=sk-... govyn
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or with Docker Compose:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
docker-compose up
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For persistent self-hosting, mount a volume for your runtime files (`govyn.config.yaml`, `govyn.auth.json`, `govyn.db`, `policies.yaml`, and `logs/`) or point `database.url` at PostgreSQL instead of the default SQLite file.
|
|
72
|
+
|
|
73
|
+
### Verify
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
curl http://localhost:4000/health
|
|
77
|
+
# {"status":"ok"}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Make Your First Governed Request
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
curl http://localhost:4000/v1/openai/v1/chat/completions \
|
|
84
|
+
-H "Content-Type: application/json" \
|
|
85
|
+
-H "X-Govyn-Agent: my-first-agent" \
|
|
86
|
+
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hello"}]}'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The proxy forwards to OpenAI, tracks cost, enforces budget limits, and logs the action — all transparently.
|
|
90
|
+
|
|
91
|
+
## Python SDK
|
|
92
|
+
|
|
93
|
+
For Python agents, use the `govynai` package for drop-in wrappers around the OpenAI and Anthropic SDKs:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install govynai[all]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from govynai import GovynOpenAI
|
|
101
|
+
|
|
102
|
+
client = GovynOpenAI(agent_id="my-agent")
|
|
103
|
+
response = client.chat.completions.create(
|
|
104
|
+
model="gpt-4o-mini",
|
|
105
|
+
messages=[{"role": "user", "content": "Hello"}]
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The wrapper automatically routes through your Govyn proxy, injects agent headers, and surfaces governance errors as typed exceptions. See [`python-sdk/`](./python-sdk/) for full documentation.
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
|
|
113
|
+
Govyn uses a local `govyn.config.yaml` runtime file. Run `npx govyn init` to generate one interactively in your working directory, or create one manually. The repo ships example templates under [`configs/`](./configs/); it does not ship a ready-to-use operator config.
|
|
114
|
+
|
|
115
|
+
`govyn.config.yaml` is your deployment file. Keep it local to your environment and do not treat it as a shared repo sample.
|
|
116
|
+
|
|
117
|
+
The OSS dashboard also uses a local `govyn.auth.json` file for its single admin account. Create it during `npx govyn init` or later with `npx govyn admin setup`. Keep that file local and out of git too.
|
|
118
|
+
|
|
119
|
+
Govyn now uses SQLite by default for persistence. The default runtime database is `./govyn.db`, which powers approvals, alerts, cost history, and other durable operational data on a single host. Keep that file local and out of git too.
|
|
120
|
+
|
|
121
|
+
For the smallest local-only starting point, use [`configs/openai-only.yaml`](./configs/openai-only.yaml) or generate one with `npx govyn init`. A minimal manual config looks like this:
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
version: 1
|
|
125
|
+
proxy:
|
|
126
|
+
port: 4000
|
|
127
|
+
host: 127.0.0.1
|
|
128
|
+
|
|
129
|
+
providers:
|
|
130
|
+
openai:
|
|
131
|
+
base_url: https://api.openai.com
|
|
132
|
+
api_key_env: OPENAI_API_KEY
|
|
133
|
+
anthropic:
|
|
134
|
+
base_url: https://api.anthropic.com
|
|
135
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
136
|
+
|
|
137
|
+
agents:
|
|
138
|
+
research-agent: {}
|
|
139
|
+
|
|
140
|
+
database:
|
|
141
|
+
url: sqlite:./govyn.db
|
|
142
|
+
|
|
143
|
+
budgets:
|
|
144
|
+
research-agent:
|
|
145
|
+
daily_limit: 10.00
|
|
146
|
+
monthly_limit: 100.00
|
|
147
|
+
limit_type: hard
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
If you omit `database.url`, Govyn still defaults to `sqlite:./govyn.db` beside your config. Keeping it explicit in your local config makes the deployment shape clearer.
|
|
151
|
+
|
|
152
|
+
If you expose the proxy off-machine, add your own generated `agents.<name>.api_keys`. If you manage the proxy from a remote dashboard, create the local admin account on the host and add the dashboard origin under `security.trusted_origins`. Only set `security.admin_api_key_env` if you also want automation or break-glass API access.
|
|
153
|
+
|
|
154
|
+
## Dashboard Auth
|
|
155
|
+
|
|
156
|
+
- The OSS dashboard uses one local admin username/password account.
|
|
157
|
+
- There is no Clerk, no email-based reset flow, no signup, and no OIDC/SAML in this repo.
|
|
158
|
+
- Create the admin account with `npx govyn admin setup`, or let `npx govyn init` create it during first-run setup.
|
|
159
|
+
- Reset the password locally with `npx govyn admin reset-password`.
|
|
160
|
+
- Browser logins use an `HttpOnly` session cookie. `GOVYN_ADMIN_API_KEY` remains available for automation and break-glass recovery, not normal dashboard sign-in.
|
|
161
|
+
|
|
162
|
+
## Persistence
|
|
163
|
+
|
|
164
|
+
- Default OSS self-hosting: `database.url: sqlite:./govyn.db`
|
|
165
|
+
- SQLite is the recommended default for one host, one admin, and the normal self-hosted OSS setup.
|
|
166
|
+
- SQLite powers approvals, alerts, cost history, and other durable operational state without requiring a separate database service.
|
|
167
|
+
- Switch to PostgreSQL by setting `database.url: postgres://...` when you need a shared database, managed backups, or multiple Govyn instances against the same backend.
|
|
168
|
+
- The example configs under [`configs/`](./configs/) show the SQLite default and include commented PostgreSQL upgrade hints.
|
|
169
|
+
|
|
170
|
+
See [`configs/openai-only.yaml`](./configs/openai-only.yaml) for the canonical minimal example, or browse [`configs/`](./configs/) for more setups (single provider, multi-provider, teams).
|
|
171
|
+
|
|
172
|
+
## Security Defaults
|
|
173
|
+
|
|
174
|
+
- Govyn does not ship any real agent keys, admin keys, or provider secrets. Every key shown in docs or UI is a placeholder or generated locally for the operator to adopt.
|
|
175
|
+
- The proxy binds to `127.0.0.1` by default so a fresh install is local-only.
|
|
176
|
+
- If you bind the proxy to a non-loopback host such as `0.0.0.0`, Govyn automatically requires `Authorization: Bearer <agent-api-key>` on proxied model requests. Configure those keys under `agents.<name>.api_keys`.
|
|
177
|
+
- You can explicitly set `security.require_agent_api_key: false`, but that creates an unauthenticated spend surface and is unsafe on shared or public networks.
|
|
178
|
+
- `/api/*` management endpoints are restricted to local requests by default.
|
|
179
|
+
- Once the local dashboard admin exists, browser management uses the dashboard session cookie instead of a pasted API key.
|
|
180
|
+
- To manage the proxy from another browser origin, add that origin under `security.trusted_origins` and sign in with the local admin username/password.
|
|
181
|
+
- `GOVYN_ADMIN_API_KEY` (or another env var via `security.admin_api_key_env`) is for automation, CLI tooling, and break-glass remote API access via `X-Govyn-Admin-Key`.
|
|
182
|
+
- Browser dashboards must be explicitly listed under `security.trusted_origins`. Localhost origins are allowed automatically for development.
|
|
183
|
+
- Browser-origin management requests from untrusted origins are rejected even on localhost, which blocks CSRF-style admin actions against a developer machine.
|
|
184
|
+
- `GET /api/approvals/:id` remains accessible without admin auth so the approval polling flow continues to work for agents.
|
|
185
|
+
- Approval tokens are single-use and bound to the original approved agent, target path, and request body.
|
|
186
|
+
- Alert webhooks reject loopback and private-network destinations, resolve DNS before connect, and block redirects to prevent SSRF against internal services.
|
|
187
|
+
|
|
188
|
+
### Generating Agent Keys
|
|
189
|
+
|
|
190
|
+
- Generate your own long random value for every agent. Do not reuse provider API keys as agent API keys.
|
|
191
|
+
- The dashboard Settings page includes a browser-local generator and copy-ready YAML snippet. It helps you prepare local config only; it does not write proxy config, and the generated key is not sent to the proxy unless you manually add it to your config.
|
|
192
|
+
- If you prefer the terminal, `node -e "console.log('gvn_' + require('node:crypto').randomBytes(32).toString('hex'))"` prints a suitable key.
|
|
193
|
+
|
|
194
|
+
## Policy Engine
|
|
195
|
+
|
|
196
|
+
Define governance rules in `policies.yaml`:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
policies:
|
|
200
|
+
- name: require-approval-for-gpt4
|
|
201
|
+
match:
|
|
202
|
+
model: "gpt-4*"
|
|
203
|
+
action: require_approval
|
|
204
|
+
message: "GPT-4 usage requires human approval"
|
|
205
|
+
|
|
206
|
+
- name: block-production-models-at-night
|
|
207
|
+
match:
|
|
208
|
+
model: "gpt-4o"
|
|
209
|
+
schedule:
|
|
210
|
+
deny: "0 22 * * *-0 6 * * *"
|
|
211
|
+
action: block
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## API Endpoints
|
|
215
|
+
|
|
216
|
+
| Endpoint | Description |
|
|
217
|
+
|----------|-------------|
|
|
218
|
+
| `GET /health` | Health check |
|
|
219
|
+
| `GET /api/costs` | Cost summaries per agent |
|
|
220
|
+
| `GET /api/budgets` | Budget status and remaining limits |
|
|
221
|
+
| `GET /api/logs` | Query action logs |
|
|
222
|
+
| `GET /api/policies` | List active policies |
|
|
223
|
+
| `POST /v1/openai/...` | Proxied OpenAI requests |
|
|
224
|
+
| `POST /v1/anthropic/...` | Proxied Anthropic requests |
|
|
225
|
+
|
|
226
|
+
## Architecture
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
┌─────────────┐ ┌──────────────────────────────────┐ ┌─────────────┐
|
|
230
|
+
│ Agent A │────▶│ Govyn Proxy │────▶│ OpenAI │
|
|
231
|
+
│ (no key) │ │ │ │ API │
|
|
232
|
+
└─────────────┘ │ ┌──────────┐ ┌──────────────┐ │ └─────────────┘
|
|
233
|
+
│ │ Policy │ │ Budget │ │
|
|
234
|
+
┌─────────────┐ │ │ Engine │ │ Enforcer │ │ ┌─────────────┐
|
|
235
|
+
│ Agent B │────▶│ └──────────┘ └──────────────┘ │────▶│ Anthropic │
|
|
236
|
+
│ (no key) │ │ ┌──────────┐ ┌──────────────┐ │ │ API │
|
|
237
|
+
└─────────────┘ │ │ Loop │ │ Action │ │ └─────────────┘
|
|
238
|
+
│ │ Detector │ │ Logger │ │
|
|
239
|
+
┌─────────────┐ │ └──────────┘ └──────────────┘ │
|
|
240
|
+
│ Agent C │────▶│ │
|
|
241
|
+
│ (no key) │ │ Real API keys live here only │
|
|
242
|
+
└─────────────┘ └──────────────────────────────────┘
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Agents never hold real API keys. The proxy is the only path to the real APIs.
|
|
246
|
+
|
|
247
|
+
## Project Structure
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
govyn/
|
|
251
|
+
├── src/ # Proxy server (TypeScript)
|
|
252
|
+
├── python-sdk/ # Python SDK (govynai package)
|
|
253
|
+
├── configs/ # Example configurations
|
|
254
|
+
├── templates/ # Init wizard templates
|
|
255
|
+
├── tests/ # Proxy test suite
|
|
256
|
+
└── docs/ # Documentation
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Contributing
|
|
260
|
+
|
|
261
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on submitting pull requests, reporting issues, and development setup.
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
[MIT](./LICENSE) — Copyright (c) 2026 GovynAI
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Govyn Config: Multi-Provider
|
|
2
|
+
# OpenAI + Anthropic + a custom local LLM (Ollama), two agents with different budgets.
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
|
|
6
|
+
proxy:
|
|
7
|
+
port: 4000
|
|
8
|
+
host: 127.0.0.1 # Local-only default. For remote exposure, change this to 0.0.0.0 and add your own agent API keys.
|
|
9
|
+
|
|
10
|
+
providers:
|
|
11
|
+
openai:
|
|
12
|
+
base_url: https://api.openai.com
|
|
13
|
+
api_key_env: OPENAI_API_KEY
|
|
14
|
+
|
|
15
|
+
anthropic:
|
|
16
|
+
base_url: https://api.anthropic.com
|
|
17
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
18
|
+
|
|
19
|
+
custom:
|
|
20
|
+
ollama:
|
|
21
|
+
base_url: http://localhost:11434 # Local Ollama instance
|
|
22
|
+
# No api_key_env needed for local Ollama
|
|
23
|
+
|
|
24
|
+
agents:
|
|
25
|
+
research-agent:
|
|
26
|
+
# Add your own generated api_keys only if you expose the proxy off-machine.
|
|
27
|
+
# api_keys:
|
|
28
|
+
# - gvn_generate_your_own_long_random_secret_here
|
|
29
|
+
coding-agent:
|
|
30
|
+
# api_keys:
|
|
31
|
+
# - gvn_generate_your_own_second_long_random_secret_here
|
|
32
|
+
|
|
33
|
+
database:
|
|
34
|
+
url: sqlite:./govyn.db # Default OSS persistence for approvals, alerts, and history.
|
|
35
|
+
# For a shared/team deployment later, switch to PostgreSQL:
|
|
36
|
+
# url: postgres://govyn:change-me@db.example.com:5432/govyn
|
|
37
|
+
|
|
38
|
+
# Custom pricing for models not in the built-in pricing table
|
|
39
|
+
pricing:
|
|
40
|
+
llama3:
|
|
41
|
+
input: 0.00 # Free for local models
|
|
42
|
+
output: 0.00
|
|
43
|
+
|
|
44
|
+
budgets:
|
|
45
|
+
research-agent:
|
|
46
|
+
daily_limit: 10.00
|
|
47
|
+
monthly_limit: 200.00
|
|
48
|
+
limit_type: hard
|
|
49
|
+
soft_warning_percent: 80
|
|
50
|
+
|
|
51
|
+
coding-agent:
|
|
52
|
+
daily_limit: 25.00
|
|
53
|
+
monthly_limit: 500.00
|
|
54
|
+
limit_type: soft # Soft limit: warn but don't block
|
|
55
|
+
soft_warning_percent: 70
|
|
56
|
+
|
|
57
|
+
security:
|
|
58
|
+
# Only needed for remote dashboard or admin API access.
|
|
59
|
+
# admin_api_key_env: GOVYN_ADMIN_API_KEY
|
|
60
|
+
# trusted_origins:
|
|
61
|
+
# - https://dashboard.example.com
|
|
62
|
+
|
|
63
|
+
logging:
|
|
64
|
+
enabled: true
|
|
65
|
+
directory: ./logs
|
|
66
|
+
default_mode: metadata
|
|
67
|
+
stdout: true
|
|
68
|
+
file: true
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Govyn Config: OpenAI-Only
|
|
2
|
+
# Minimal setup for proxying OpenAI requests with a single agent and daily budget.
|
|
3
|
+
|
|
4
|
+
version: 1
|
|
5
|
+
|
|
6
|
+
proxy:
|
|
7
|
+
port: 4000
|
|
8
|
+
host: 127.0.0.1 # Local-only default. For remote exposure, change this to 0.0.0.0 and add your own agent API key.
|
|
9
|
+
|
|
10
|
+
providers:
|
|
11
|
+
openai:
|
|
12
|
+
base_url: https://api.openai.com # Default OpenAI API endpoint
|
|
13
|
+
api_key_env: OPENAI_API_KEY # Reads from this environment variable
|
|
14
|
+
|
|
15
|
+
agents:
|
|
16
|
+
my-agent:
|
|
17
|
+
# Local-only example. If you expose the proxy on a non-loopback host,
|
|
18
|
+
# uncomment the lines below and generate your own long random secret.
|
|
19
|
+
# api_keys:
|
|
20
|
+
# - gvn_generate_your_own_long_random_secret_here
|
|
21
|
+
|
|
22
|
+
database:
|
|
23
|
+
url: sqlite:./govyn.db # Default OSS persistence. Govyn creates this local SQLite file automatically.
|
|
24
|
+
# For a shared or managed database later, switch to PostgreSQL:
|
|
25
|
+
# url: postgres://govyn:change-me@db.example.com:5432/govyn
|
|
26
|
+
|
|
27
|
+
budgets:
|
|
28
|
+
my-agent:
|
|
29
|
+
daily_limit: 5.00 # Hard stop at $5/day
|
|
30
|
+
monthly_limit: 100.00 # Hard stop at $100/month
|
|
31
|
+
limit_type: hard # hard = block requests, soft = warn only
|
|
32
|
+
soft_warning_percent: 80 # Emit warning header at 80% usage
|
|
33
|
+
|
|
34
|
+
security:
|
|
35
|
+
# Only needed for remote dashboard or admin API access.
|
|
36
|
+
# admin_api_key_env: GOVYN_ADMIN_API_KEY
|
|
37
|
+
# trusted_origins:
|
|
38
|
+
# - https://dashboard.example.com
|
|
39
|
+
|
|
40
|
+
logging:
|
|
41
|
+
enabled: true
|
|
42
|
+
directory: ./logs
|
|
43
|
+
default_mode: metadata # 'metadata' = summary only, 'full-payload' = include bodies
|
|
44
|
+
stdout: true # Print log lines to stdout (useful for Docker)
|
|
45
|
+
file: true # Write JSONL log files to disk
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Govyn Config: Team Setup
|
|
2
|
+
# Multi-agent team with soft/hard budgets and mixed logging modes.
|
|
3
|
+
# Full-payload logging for the debug agent, metadata-only for others.
|
|
4
|
+
|
|
5
|
+
version: 1
|
|
6
|
+
|
|
7
|
+
proxy:
|
|
8
|
+
port: 4000
|
|
9
|
+
host: 127.0.0.1 # Local-only default. For remote exposure, change this to 0.0.0.0 and add your own agent API keys.
|
|
10
|
+
|
|
11
|
+
providers:
|
|
12
|
+
openai:
|
|
13
|
+
base_url: https://api.openai.com
|
|
14
|
+
api_key_env: OPENAI_API_KEY
|
|
15
|
+
|
|
16
|
+
anthropic:
|
|
17
|
+
base_url: https://api.anthropic.com
|
|
18
|
+
api_key_env: ANTHROPIC_API_KEY
|
|
19
|
+
|
|
20
|
+
agents:
|
|
21
|
+
frontend-agent:
|
|
22
|
+
# Add your own generated api_keys only if you expose the proxy off-machine.
|
|
23
|
+
# api_keys:
|
|
24
|
+
# - gvn_generate_your_own_frontend_secret_here
|
|
25
|
+
loop_detection:
|
|
26
|
+
threshold: 5 # Stricter loop detection for UI agents
|
|
27
|
+
window_seconds: 30
|
|
28
|
+
cooldown_seconds: 120
|
|
29
|
+
|
|
30
|
+
backend-agent:
|
|
31
|
+
# api_keys:
|
|
32
|
+
# - gvn_generate_your_own_backend_secret_here
|
|
33
|
+
loop_detection:
|
|
34
|
+
threshold: 15 # More lenient for batch processing
|
|
35
|
+
window_seconds: 60
|
|
36
|
+
cooldown_seconds: 300
|
|
37
|
+
|
|
38
|
+
debug-agent:
|
|
39
|
+
# api_keys:
|
|
40
|
+
# - gvn_generate_your_own_debug_secret_here
|
|
41
|
+
|
|
42
|
+
database:
|
|
43
|
+
url: sqlite:./govyn.db # Default single-host persistence.
|
|
44
|
+
# For a shared team deployment or multiple Govyn instances, switch to PostgreSQL:
|
|
45
|
+
# url: postgres://govyn:change-me@db.example.com:5432/govyn
|
|
46
|
+
|
|
47
|
+
budgets:
|
|
48
|
+
frontend-agent:
|
|
49
|
+
daily_limit: 5.00
|
|
50
|
+
monthly_limit: 50.00
|
|
51
|
+
limit_type: hard # Hard limit: block at threshold
|
|
52
|
+
soft_warning_percent: 80
|
|
53
|
+
|
|
54
|
+
backend-agent:
|
|
55
|
+
daily_limit: 20.00
|
|
56
|
+
monthly_limit: 400.00
|
|
57
|
+
limit_type: soft # Soft limit: warn but allow
|
|
58
|
+
soft_warning_percent: 70
|
|
59
|
+
|
|
60
|
+
debug-agent:
|
|
61
|
+
daily_limit: 2.00
|
|
62
|
+
monthly_limit: 20.00
|
|
63
|
+
limit_type: hard
|
|
64
|
+
soft_warning_percent: 90
|
|
65
|
+
|
|
66
|
+
security:
|
|
67
|
+
# Only needed for remote dashboard or admin API access.
|
|
68
|
+
# admin_api_key_env: GOVYN_ADMIN_API_KEY
|
|
69
|
+
# trusted_origins:
|
|
70
|
+
# - https://dashboard.example.com
|
|
71
|
+
|
|
72
|
+
logging:
|
|
73
|
+
enabled: true
|
|
74
|
+
directory: ./logs
|
|
75
|
+
default_mode: metadata # Most agents get metadata-only logging
|
|
76
|
+
stdout: true
|
|
77
|
+
file: true
|
|
78
|
+
max_body_size: 524288 # 512KB max body capture
|
|
79
|
+
rotation_max_size_mb: 25 # Rotate at 25MB (smaller for teams)
|
|
80
|
+
rotation_interval_hours: 12 # Rotate every 12 hours
|
|
81
|
+
retention_days: 14 # Keep logs for 2 weeks
|
|
82
|
+
payload_retention_days: 3 # Keep payload files for 3 days
|
|
83
|
+
|
|
84
|
+
# Per-agent logging mode overrides
|
|
85
|
+
agent_modes:
|
|
86
|
+
debug-agent: full-payload # Full request/response bodies for debugging
|
|
87
|
+
frontend-agent: metadata # Summary only
|
|
88
|
+
backend-agent: metadata # Summary only
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async action logger for the Govyn proxy server.
|
|
3
|
+
*
|
|
4
|
+
* Writes structured JSONL log entries for every proxied request.
|
|
5
|
+
* Supports two modes:
|
|
6
|
+
* - metadata: summary fields only (default)
|
|
7
|
+
* - full-payload: stores request/response bodies as separate JSON files
|
|
8
|
+
*
|
|
9
|
+
* Design:
|
|
10
|
+
* - log() is synchronous and non-blocking (zero added latency)
|
|
11
|
+
* - Entries are buffered in memory and flushed to disk on a 1-second interval
|
|
12
|
+
* - storePayload() is fire-and-forget async (errors logged to stderr)
|
|
13
|
+
* - Dual output: stdout AND/OR file, either disableable in config
|
|
14
|
+
*/
|
|
15
|
+
import type { LogEntry, LoggingConfig, LoggingMode } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* ActionLogger writes structured JSONL log entries for proxied requests.
|
|
18
|
+
*
|
|
19
|
+
* Non-blocking by design: log() pushes to an in-memory buffer that is
|
|
20
|
+
* flushed to disk on a 1-second unref'd interval. storePayload() writes
|
|
21
|
+
* payload files asynchronously without awaiting in the request path.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ActionLogger {
|
|
24
|
+
/** Exposed config for consumers that need to read settings (e.g., maxBodySize) */
|
|
25
|
+
readonly config: LoggingConfig;
|
|
26
|
+
/** Internal write buffer — entries waiting to be flushed to disk */
|
|
27
|
+
private buffer;
|
|
28
|
+
/** Path to the current active JSONL log file */
|
|
29
|
+
private currentFilePath;
|
|
30
|
+
/** Path to the payloads subdirectory */
|
|
31
|
+
private payloadsDir;
|
|
32
|
+
/** Periodic flush interval handle */
|
|
33
|
+
private flushInterval;
|
|
34
|
+
/** Log rotator for size/time-based rotation and retention cleanup */
|
|
35
|
+
private rotator;
|
|
36
|
+
constructor(config: LoggingConfig);
|
|
37
|
+
/**
|
|
38
|
+
* Read-only access to the log directory path.
|
|
39
|
+
* Used by the log query API to locate JSONL files.
|
|
40
|
+
*/
|
|
41
|
+
get logDirectory(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Get the full filesystem path to a payload file.
|
|
44
|
+
*
|
|
45
|
+
* @param payloadId - The payload ID (used as filename stem)
|
|
46
|
+
* @returns Full path to the payload JSON file
|
|
47
|
+
*/
|
|
48
|
+
getPayloadPath(payloadId: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Log a structured entry. Non-blocking — adds to buffer and optionally writes to stdout.
|
|
51
|
+
* This is the zero-latency guarantee: no file I/O in the hot path.
|
|
52
|
+
* Automatically sets storage_region from config if not already set on the entry.
|
|
53
|
+
*
|
|
54
|
+
* @param entry - The structured log entry to record
|
|
55
|
+
*/
|
|
56
|
+
log(entry: LogEntry): void;
|
|
57
|
+
/**
|
|
58
|
+
* Store a full payload (request + response bodies) as a separate JSON file.
|
|
59
|
+
* Fully async — fire and forget. Errors are logged to stderr but never thrown.
|
|
60
|
+
*
|
|
61
|
+
* @param payloadId - Unique ID for this payload (used as filename)
|
|
62
|
+
* @param requestBody - Raw request body buffer (or null)
|
|
63
|
+
* @param responseBody - Raw response body buffer (or null)
|
|
64
|
+
* @param truncated - Whether either body was truncated due to size limits
|
|
65
|
+
*/
|
|
66
|
+
storePayload(payloadId: string, requestBody: Buffer | null, responseBody: Buffer | null, truncated: boolean): void;
|
|
67
|
+
/**
|
|
68
|
+
* Flush buffered entries to the current JSONL file.
|
|
69
|
+
* Uses synchronous append to ensure atomicity of the batch write.
|
|
70
|
+
*/
|
|
71
|
+
flush(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Get the logging mode for a specific agent.
|
|
74
|
+
* Returns the agent-specific override if set, otherwise the default mode.
|
|
75
|
+
*
|
|
76
|
+
* @param agentId - The agent to look up
|
|
77
|
+
* @returns The LoggingMode to use for this agent
|
|
78
|
+
*/
|
|
79
|
+
getMode(agentId: string): LoggingMode;
|
|
80
|
+
/**
|
|
81
|
+
* Set the logging mode for a specific agent at runtime.
|
|
82
|
+
* Does NOT persist to YAML — runtime-only toggle.
|
|
83
|
+
*
|
|
84
|
+
* @param agentId - The agent to configure
|
|
85
|
+
* @param mode - The logging mode to set
|
|
86
|
+
*/
|
|
87
|
+
setMode(agentId: string, mode: LoggingMode): void;
|
|
88
|
+
/**
|
|
89
|
+
* Get the path of the current active JSONL log file.
|
|
90
|
+
* Useful for rotation logic and diagnostics.
|
|
91
|
+
*/
|
|
92
|
+
getCurrentFilePath(): string;
|
|
93
|
+
/**
|
|
94
|
+
* Purge all log entries and associated payload files older than the given date.
|
|
95
|
+
*
|
|
96
|
+
* - Reads all JSONL files (current + rotated) in the log directory
|
|
97
|
+
* - For each file: filters out entries with timestamps before the date
|
|
98
|
+
* - Deletes payload files for removed entries that have has_payload=true
|
|
99
|
+
* - Handles gzipped rotated files: decompress, filter, recompress or delete
|
|
100
|
+
* - Returns counts of deleted log entries and deleted payload files
|
|
101
|
+
*
|
|
102
|
+
* @param date - Purge entries with timestamps strictly before this date
|
|
103
|
+
* @returns Counts of deleted log entries and payload files
|
|
104
|
+
*/
|
|
105
|
+
purgeBefore(date: Date): {
|
|
106
|
+
deletedLogs: number;
|
|
107
|
+
deletedPayloads: number;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Purge entries from a plain JSONL file.
|
|
111
|
+
* Rewrites the file with only entries newer than the cutoff date.
|
|
112
|
+
*/
|
|
113
|
+
private purgeJsonlFile;
|
|
114
|
+
/**
|
|
115
|
+
* Purge entries from a gzipped rotated JSONL file.
|
|
116
|
+
* Decompresses, filters, and either recompresses or deletes if empty.
|
|
117
|
+
*/
|
|
118
|
+
private purgeGzipFile;
|
|
119
|
+
/**
|
|
120
|
+
* Close the logger: flush remaining buffer and stop the flush interval.
|
|
121
|
+
*/
|
|
122
|
+
close(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Generate a unique ID for log entries and payload files.
|
|
125
|
+
* Uses crypto.randomUUID() for guaranteed uniqueness.
|
|
126
|
+
*/
|
|
127
|
+
static generateId(): string;
|
|
128
|
+
}
|