ambitry 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -150,6 +150,8 @@ Point a provider at a different upstream — Azure, a gateway, a local model —
150
150
 
151
151
  Cost is computed from published per-token rates. Models with no known rate report `null` rather than a guess; a cost dashboard that quietly invents numbers is worse than one that admits ignorance.
152
152
 
153
+ **One request modification, and the only one.** OpenAI-compatible providers omit token usage from streamed responses unless `stream_options.include_usage` is set, and almost no agent code sets it — so without this, cost and spend caps would be blank for most streaming traffic. Ambitry adds that flag to outgoing OpenAI streaming requests when you haven't set it yourself. The visible effect is one extra terminal chunk carrying usage, which SDKs treat as a normal chunk with no choices. If you set `stream_options` yourself, it's left alone, and Anthropic requests are never modified.
154
+
153
155
  ## Development
154
156
 
155
157
  ```bash
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from 'node:path';
3
+ import { rmSync } from 'node:fs';
3
4
  import { Store } from "./db.js";
4
5
  import { Controls, loadPolicy } from "./policy.js";
5
6
  import { createServer } from "./server.js";
@@ -67,6 +68,13 @@ function main(argv) {
67
68
  const port = Number(flag(argv, 'port') ?? 8787);
68
69
  // Demo traffic goes to its own file so it never mixes with real traces.
69
70
  const dbPath = resolve(flag(argv, 'db') ?? (demo ? 'ambitry-demo.db' : 'ambitry.db'));
71
+ // Each demo run starts clean. Otherwise a second run doubles every number
72
+ // on the dashboard, which is confusing on its own and useless to record.
73
+ if (demo) {
74
+ for (const suffix of ['', '-journal', '-wal', '-shm']) {
75
+ rmSync(`${dbPath}${suffix}`, { force: true });
76
+ }
77
+ }
70
78
  const policyPath = resolve(flag(argv, 'policy') ?? 'ambitry.json');
71
79
  let controls;
72
80
  try {
package/dist/server.js CHANGED
@@ -33,9 +33,22 @@ export function createServer({ store, controls }) {
33
33
  examples: ['http://localhost:8787/anthropic', 'http://localhost:8787/openai/v1'],
34
34
  });
35
35
  }
36
- const body = await readBody(req);
36
+ let body = await readBody(req);
37
37
  const parsed = parseRequest(route.provider.id, body);
38
38
  const requestJson = safeJson(body);
39
+ // OpenAI-compatible providers omit usage from streamed responses unless
40
+ // stream_options.include_usage is set, and almost no agent code sets it.
41
+ // Without this the cost column is empty for most streaming traffic, which
42
+ // guts the spend caps. Anthropic always reports usage, so this is only
43
+ // needed here. The cost is one extra terminal chunk carrying usage, which
44
+ // SDKs treat as a normal chunk with no choices.
45
+ if (route.provider.id === 'openai' && parsed.stream && requestJson && typeof requestJson === 'object') {
46
+ const r = requestJson;
47
+ if (r.stream_options === undefined) {
48
+ r.stream_options = { include_usage: true };
49
+ body = Buffer.from(JSON.stringify(r));
50
+ }
51
+ }
39
52
  // Scanned on the way out, so a leak is caught on the request that carries
40
53
  // it rather than after the provider has already received it.
41
54
  const findings = scanRequest(requestJson);
package/dist/viewer.js CHANGED
@@ -125,9 +125,14 @@ async function refresh() {
125
125
  // The headline. What people came for is "am I leaking anything", and the
126
126
  // answer belongs above the fold, not inside a trace nobody opens.
127
127
  const f = status.findings || [];
128
+ const total = f.reduce((n, x) => n + x.occurrences, 0);
129
+ // "Credentials" only when they all are. An email address is personal data,
130
+ // not a credential, and this line is the first thing anyone reads — the
131
+ // whole positioning rests on not overstating.
132
+ const allCreds = f.every(x => x.severity === 'critical');
133
+ const noun = allCreds ? 'credential' : 'sensitive value';
128
134
  $('alert').innerHTML = !f.length ? '' :
129
- '<div class="alert"><h3>' + f.reduce((n, x) => n + x.occurrences, 0) +
130
- ' credential' + (f.reduce((n, x) => n + x.occurrences, 0) === 1 ? '' : 's') +
135
+ '<div class="alert"><h3>' + total + ' ' + noun + (total === 1 ? '' : 's') +
131
136
  ' sent to your model provider</h3>' +
132
137
  '<p>Found in outgoing prompts over the last 7 days. The finding keeps only a masked preview — ' +
133
138
  'but the prompt itself is in your local trace file, so treat that file as sensitive.</p>' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ambitry",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Audit log and permission layer for AI agents. One-line integration, zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "bin": {