@amodalai/runtime 0.3.64 → 0.3.66

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 (40) hide show
  1. package/dist/src/agent/local-server.js +13 -1
  2. package/dist/src/agent/local-server.js.map +1 -1
  3. package/dist/src/agent/local-server.test.js +19 -0
  4. package/dist/src/agent/local-server.test.js.map +1 -1
  5. package/dist/src/index.d.ts +4 -0
  6. package/dist/src/index.js +3 -0
  7. package/dist/src/index.js.map +1 -1
  8. package/dist/src/routes/ai-stream.js +1 -0
  9. package/dist/src/routes/ai-stream.js.map +1 -1
  10. package/dist/src/routes/chat-stream.js +1 -0
  11. package/dist/src/routes/chat-stream.js.map +1 -1
  12. package/dist/src/routes/chat.js +1 -0
  13. package/dist/src/routes/chat.js.map +1 -1
  14. package/dist/src/routes/route-helpers.js +1 -0
  15. package/dist/src/routes/route-helpers.js.map +1 -1
  16. package/dist/src/routes/route-helpers.test.js +21 -1
  17. package/dist/src/routes/route-helpers.test.js.map +1 -1
  18. package/dist/src/routes/sessions-history.js +20 -0
  19. package/dist/src/routes/sessions-history.js.map +1 -1
  20. package/dist/src/session/manager.d.ts +1 -0
  21. package/dist/src/session/manager.js +33 -1
  22. package/dist/src/session/manager.js.map +1 -1
  23. package/dist/src/session/manager.test.js +25 -0
  24. package/dist/src/session/manager.test.js.map +1 -1
  25. package/dist/src/session/stream-hooks.d.ts +6 -0
  26. package/dist/src/session/types.d.ts +14 -0
  27. package/dist/src/sinks/http-audit-sink.d.ts +57 -0
  28. package/dist/src/sinks/http-audit-sink.js +109 -0
  29. package/dist/src/sinks/http-audit-sink.js.map +1 -0
  30. package/dist/src/sinks/http-audit-sink.test.d.ts +6 -0
  31. package/dist/src/sinks/http-audit-sink.test.js +124 -0
  32. package/dist/src/sinks/http-audit-sink.test.js.map +1 -0
  33. package/dist/src/sinks/http-usage-sink.d.ts +49 -0
  34. package/dist/src/sinks/http-usage-sink.js +101 -0
  35. package/dist/src/sinks/http-usage-sink.js.map +1 -0
  36. package/dist/src/sinks/http-usage-sink.test.d.ts +6 -0
  37. package/dist/src/sinks/http-usage-sink.test.js +105 -0
  38. package/dist/src/sinks/http-usage-sink.test.js.map +1 -0
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/package.json +4 -4
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import { HttpAuditSink } from './http-audit-sink.js';
8
+ function captureFetch(status = 200) {
9
+ const calls = [];
10
+ const fetchMock = vi.fn(async (input, init) => {
11
+ const url = String(input);
12
+ const body = typeof init?.body === 'string'
13
+ ? JSON.parse(init.body)
14
+ : { entries: [] };
15
+ const headers = init?.headers;
16
+ const authorization = headers?.['Authorization'] ?? '';
17
+ calls.push({ url, body, authorization });
18
+ return new Response(null, { status });
19
+ });
20
+ vi.stubGlobal('fetch', fetchMock);
21
+ return { fetchMock, calls };
22
+ }
23
+ describe('HttpAuditSink', () => {
24
+ beforeEach(() => {
25
+ vi.useFakeTimers();
26
+ });
27
+ afterEach(() => {
28
+ vi.useRealTimers();
29
+ vi.unstubAllGlobals();
30
+ });
31
+ it('substitutes {agentId} in the URL template and sends with bearer auth', async () => {
32
+ const { calls } = captureFetch();
33
+ const sink = new HttpAuditSink({
34
+ url: 'https://api.example.com/api/agents/{agentId}/audit-logs',
35
+ getBearerToken: () => 'jwt-value',
36
+ maxBatchSize: 1,
37
+ });
38
+ sink.log('agent/special chars', undefined, {
39
+ event: 'session_completed',
40
+ resource_name: 'session-1',
41
+ });
42
+ await vi.runOnlyPendingTimersAsync();
43
+ expect(calls).toHaveLength(1);
44
+ expect(calls[0]?.url).toBe('https://api.example.com/api/agents/agent%2Fspecial%20chars/audit-logs');
45
+ expect(calls[0]?.authorization).toBe('Bearer jwt-value');
46
+ expect(calls[0]?.body.entries).toHaveLength(1);
47
+ expect(calls[0]?.body.entries[0]?.event).toBe('session_completed');
48
+ expect(calls[0]?.body.entries[0]?.timestamp).toBeDefined();
49
+ await sink.shutdown();
50
+ });
51
+ it('batches entries with the same agent + partition key', async () => {
52
+ const { calls } = captureFetch();
53
+ const sink = new HttpAuditSink({
54
+ url: 'https://api.example.com/audit',
55
+ getBearerToken: () => 'jwt',
56
+ maxBatchSize: 100,
57
+ });
58
+ sink.log('agent-1', 'user-a', { event: 'e1', resource_name: 'r' });
59
+ sink.log('agent-1', 'user-a', { event: 'e2', resource_name: 'r' });
60
+ sink.log('agent-1', 'user-a', { event: 'e3', resource_name: 'r' });
61
+ await sink.flush();
62
+ expect(calls).toHaveLength(1);
63
+ expect(calls[0]?.body.entries).toHaveLength(3);
64
+ await sink.shutdown();
65
+ });
66
+ it('separates batches by partition key', async () => {
67
+ const { calls } = captureFetch();
68
+ const sink = new HttpAuditSink({
69
+ url: 'https://api.example.com/audit',
70
+ getBearerToken: ({ partitionKey }) => partitionKey === 'user-a' ? 'token-a' : 'token-b',
71
+ maxBatchSize: 100,
72
+ });
73
+ sink.log('agent-1', 'user-a', { event: 'e1', resource_name: 'r' });
74
+ sink.log('agent-1', 'user-b', { event: 'e2', resource_name: 'r' });
75
+ await sink.flush();
76
+ expect(calls).toHaveLength(2);
77
+ const tokens = calls.map((c) => c.authorization).sort();
78
+ expect(tokens).toEqual(['Bearer token-a', 'Bearer token-b']);
79
+ await sink.shutdown();
80
+ });
81
+ it('auto-flushes when batch size threshold is hit', async () => {
82
+ const { calls } = captureFetch();
83
+ const sink = new HttpAuditSink({
84
+ url: 'https://api.example.com/audit',
85
+ getBearerToken: () => 'jwt',
86
+ maxBatchSize: 2,
87
+ });
88
+ sink.log('agent-1', undefined, { event: 'e1', resource_name: 'r' });
89
+ sink.log('agent-1', undefined, { event: 'e2', resource_name: 'r' });
90
+ // Auto-flush kicks in after the second entry — give microtasks a tick
91
+ await vi.runOnlyPendingTimersAsync();
92
+ expect(calls).toHaveLength(1);
93
+ expect(calls[0]?.body.entries).toHaveLength(2);
94
+ await sink.shutdown();
95
+ });
96
+ it('preserves the entry-supplied timestamp when present', async () => {
97
+ const { calls } = captureFetch();
98
+ const sink = new HttpAuditSink({
99
+ url: 'https://api.example.com/audit',
100
+ getBearerToken: () => 'jwt',
101
+ maxBatchSize: 1,
102
+ });
103
+ sink.log('agent-1', undefined, {
104
+ event: 'e1',
105
+ resource_name: 'r',
106
+ timestamp: '2026-01-01T00:00:00.000Z',
107
+ });
108
+ await vi.runOnlyPendingTimersAsync();
109
+ expect(calls[0]?.body.entries[0]?.timestamp).toBe('2026-01-01T00:00:00.000Z');
110
+ await sink.shutdown();
111
+ });
112
+ it('does not throw when fetch rejects (fire-and-forget)', async () => {
113
+ vi.stubGlobal('fetch', vi.fn(() => Promise.reject(new Error('network down'))));
114
+ const sink = new HttpAuditSink({
115
+ url: 'https://api.example.com/audit',
116
+ getBearerToken: () => 'jwt',
117
+ maxBatchSize: 1,
118
+ });
119
+ sink.log('agent-1', undefined, { event: 'e1', resource_name: 'r' });
120
+ await expect(sink.flush()).resolves.toBeUndefined();
121
+ await sink.shutdown();
122
+ });
123
+ });
124
+ //# sourceMappingURL=http-audit-sink.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-audit-sink.test.js","sourceRoot":"","sources":["../../../src/sinks/http-audit-sink.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import type { UsageReport } from '../session/stream-hooks.js';
7
+ export interface HttpUsageSinkOptions {
8
+ /**
9
+ * Full URL the batch is POSTed to. May include the literal placeholder
10
+ * `{agentId}`, which is replaced at send time with the URL-encoded
11
+ * `agentId` from the call. Example:
12
+ *
13
+ * 'https://platform.example.com/api/agents/{agentId}/usage'
14
+ */
15
+ url: string;
16
+ /**
17
+ * Resolve the bearer token to send with each batch. Called once per send.
18
+ * Typically returns the per-deploy JWT carried by the runtime — usage is
19
+ * not user-attributed at the auth layer (attribution is in `scopeId`).
20
+ */
21
+ getBearerToken: (ctx: {
22
+ agentId: string;
23
+ }) => string;
24
+ /** Default 2000ms */
25
+ flushIntervalMs?: number;
26
+ /** Default 20 — auto-flush threshold per agent batch */
27
+ maxBatchSize?: number;
28
+ /** Default 10000ms — abort if a single POST takes longer */
29
+ requestTimeoutMs?: number;
30
+ }
31
+ export declare class HttpUsageSink {
32
+ private readonly url;
33
+ private readonly getBearerToken;
34
+ private readonly flushIntervalMs;
35
+ private readonly maxBatchSize;
36
+ private readonly requestTimeoutMs;
37
+ private readonly pending;
38
+ private flushTimer;
39
+ constructor(options: HttpUsageSinkOptions);
40
+ /**
41
+ * Queue a usage report for batched delivery. Fire-and-forget — never throws.
42
+ * Reports are batched per `agentId`; consumers attribute per end-user via
43
+ * each report's `scopeId` field.
44
+ */
45
+ log(agentId: string, report: UsageReport): void;
46
+ flush(): Promise<void>;
47
+ shutdown(): Promise<void>;
48
+ private send;
49
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Batched HTTP sink for per-turn usage reports.
8
+ *
9
+ * Wire to `StreamHooks.onUsageReport` in hosted setups; self-hosters point
10
+ * the URL at their own metering endpoint. Each report carries `scopeId`
11
+ * (end-user identity from the auth JWT) and `sessionId`, so consumers can
12
+ * attribute tokens per end-user — something LiteLLM-style proxies can't do
13
+ * because they only see the deploy's virtual key.
14
+ *
15
+ * Failures are non-fatal — the sink logs and drops the batch rather than
16
+ * blocking chat handling.
17
+ */
18
+ import { log } from '../logger.js';
19
+ const DEFAULT_FLUSH_INTERVAL_MS = 2000;
20
+ const DEFAULT_MAX_BATCH_SIZE = 20;
21
+ const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
22
+ export class HttpUsageSink {
23
+ url;
24
+ getBearerToken;
25
+ flushIntervalMs;
26
+ maxBatchSize;
27
+ requestTimeoutMs;
28
+ pending = new Map();
29
+ flushTimer;
30
+ constructor(options) {
31
+ this.url = options.url;
32
+ this.getBearerToken = options.getBearerToken;
33
+ this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
34
+ this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
35
+ this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
36
+ this.flushTimer = setInterval(() => {
37
+ void this.flush();
38
+ }, this.flushIntervalMs);
39
+ this.flushTimer.unref();
40
+ }
41
+ /**
42
+ * Queue a usage report for batched delivery. Fire-and-forget — never throws.
43
+ * Reports are batched per `agentId`; consumers attribute per end-user via
44
+ * each report's `scopeId` field.
45
+ */
46
+ log(agentId, report) {
47
+ let batch = this.pending.get(agentId);
48
+ if (!batch) {
49
+ batch = { agentId, reports: [] };
50
+ this.pending.set(agentId, batch);
51
+ }
52
+ batch.reports.push(report);
53
+ if (batch.reports.length >= this.maxBatchSize) {
54
+ const toSend = batch;
55
+ this.pending.delete(agentId);
56
+ void this.send(toSend);
57
+ }
58
+ }
59
+ async flush() {
60
+ const batches = [...this.pending.values()];
61
+ this.pending.clear();
62
+ await Promise.all(batches.map((b) => this.send(b)));
63
+ }
64
+ async shutdown() {
65
+ if (this.flushTimer) {
66
+ clearInterval(this.flushTimer);
67
+ this.flushTimer = null;
68
+ }
69
+ await this.flush();
70
+ }
71
+ async send(batch) {
72
+ const url = this.url.replace('{agentId}', encodeURIComponent(batch.agentId));
73
+ const token = this.getBearerToken({ agentId: batch.agentId });
74
+ try {
75
+ const response = await fetch(url, {
76
+ method: 'POST',
77
+ signal: AbortSignal.timeout(this.requestTimeoutMs),
78
+ headers: {
79
+ 'Content-Type': 'application/json',
80
+ Authorization: `Bearer ${token}`,
81
+ },
82
+ body: JSON.stringify({ reports: batch.reports }),
83
+ });
84
+ if (!response.ok) {
85
+ log.warn('usage_sink_http_error', {
86
+ status: response.status,
87
+ agentId: batch.agentId,
88
+ batchSize: batch.reports.length,
89
+ });
90
+ }
91
+ }
92
+ catch (err) {
93
+ log.warn('usage_sink_send_failed', {
94
+ agentId: batch.agentId,
95
+ batchSize: batch.reports.length,
96
+ error: err instanceof Error ? err.message : String(err),
97
+ });
98
+ }
99
+ }
100
+ }
101
+ //# sourceMappingURL=http-usage-sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-usage-sink.js","sourceRoot":"","sources":["../../../src/sinks/http-usage-sink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import { HttpUsageSink } from './http-usage-sink.js';
8
+ function captureFetch(status = 200) {
9
+ const calls = [];
10
+ const fetchMock = vi.fn(async (input, init) => {
11
+ const url = String(input);
12
+ const body = typeof init?.body === 'string'
13
+ ? JSON.parse(init.body)
14
+ : { reports: [] };
15
+ const headers = init?.headers;
16
+ const authorization = headers?.['Authorization'] ?? '';
17
+ calls.push({ url, body, authorization });
18
+ return new Response(null, { status });
19
+ });
20
+ vi.stubGlobal('fetch', fetchMock);
21
+ return { fetchMock, calls };
22
+ }
23
+ function makeReport(scopeId) {
24
+ return {
25
+ sessionId: 'session-1',
26
+ scopeId,
27
+ model: 'claude-sonnet-4-20250514',
28
+ taskAgentRuns: 0,
29
+ tokens: { inputTokens: 100, outputTokens: 50, cachedTokens: 0 },
30
+ };
31
+ }
32
+ describe('HttpUsageSink', () => {
33
+ beforeEach(() => {
34
+ vi.useFakeTimers();
35
+ });
36
+ afterEach(() => {
37
+ vi.useRealTimers();
38
+ vi.unstubAllGlobals();
39
+ });
40
+ it('substitutes {agentId} in the URL template and sends with bearer auth', async () => {
41
+ const { calls } = captureFetch();
42
+ const sink = new HttpUsageSink({
43
+ url: 'https://api.example.com/api/agents/{agentId}/usage',
44
+ getBearerToken: () => 'deploy-jwt',
45
+ maxBatchSize: 1,
46
+ });
47
+ sink.log('agent-1', makeReport('end-user-x'));
48
+ await vi.runOnlyPendingTimersAsync();
49
+ expect(calls).toHaveLength(1);
50
+ expect(calls[0]?.url).toBe('https://api.example.com/api/agents/agent-1/usage');
51
+ expect(calls[0]?.authorization).toBe('Bearer deploy-jwt');
52
+ expect(calls[0]?.body.reports).toHaveLength(1);
53
+ expect(calls[0]?.body.reports[0]?.scopeId).toBe('end-user-x');
54
+ await sink.shutdown();
55
+ });
56
+ it('batches reports per agent regardless of scope', async () => {
57
+ const { calls } = captureFetch();
58
+ const sink = new HttpUsageSink({
59
+ url: 'https://api.example.com/usage',
60
+ getBearerToken: () => 'jwt',
61
+ maxBatchSize: 100,
62
+ });
63
+ sink.log('agent-1', makeReport('user-a'));
64
+ sink.log('agent-1', makeReport('user-b'));
65
+ sink.log('agent-1', makeReport(''));
66
+ await sink.flush();
67
+ expect(calls).toHaveLength(1);
68
+ expect(calls[0]?.body.reports.map((r) => r.scopeId).sort()).toEqual([
69
+ '',
70
+ 'user-a',
71
+ 'user-b',
72
+ ]);
73
+ await sink.shutdown();
74
+ });
75
+ it('keeps batches separate per agent', async () => {
76
+ const { calls } = captureFetch();
77
+ const sink = new HttpUsageSink({
78
+ url: 'https://api.example.com/api/agents/{agentId}/usage',
79
+ getBearerToken: () => 'jwt',
80
+ maxBatchSize: 100,
81
+ });
82
+ sink.log('agent-1', makeReport('user-a'));
83
+ sink.log('agent-2', makeReport('user-a'));
84
+ await sink.flush();
85
+ expect(calls).toHaveLength(2);
86
+ const urls = calls.map((c) => c.url).sort();
87
+ expect(urls).toEqual([
88
+ 'https://api.example.com/api/agents/agent-1/usage',
89
+ 'https://api.example.com/api/agents/agent-2/usage',
90
+ ]);
91
+ await sink.shutdown();
92
+ });
93
+ it('does not throw when fetch rejects (fire-and-forget)', async () => {
94
+ vi.stubGlobal('fetch', vi.fn(() => Promise.reject(new Error('network down'))));
95
+ const sink = new HttpUsageSink({
96
+ url: 'https://api.example.com/usage',
97
+ getBearerToken: () => 'jwt',
98
+ maxBatchSize: 1,
99
+ });
100
+ sink.log('agent-1', makeReport('user-a'));
101
+ await expect(sink.flush()).resolves.toBeUndefined();
102
+ await sink.shutdown();
103
+ });
104
+ });
105
+ //# sourceMappingURL=http-usage-sink.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-usage-sink.test.js","sourceRoot":"","sources":["../../../src/sinks/http-usage-sink.test.ts"],"names":[],"mappings":"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"}