@dexcost/sdk 0.2.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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/adapters/_netbytes.d.ts +31 -0
- package/dist/adapters/_netbytes.d.ts.map +1 -0
- package/dist/adapters/_netbytes.js +154 -0
- package/dist/adapters/_netbytes.js.map +1 -0
- package/dist/adapters/aws-lambda.d.ts +41 -0
- package/dist/adapters/aws-lambda.d.ts.map +1 -0
- package/dist/adapters/aws-lambda.js +65 -0
- package/dist/adapters/aws-lambda.js.map +1 -0
- package/dist/adapters/browser.d.ts +52 -0
- package/dist/adapters/browser.d.ts.map +1 -0
- package/dist/adapters/browser.js +127 -0
- package/dist/adapters/browser.js.map +1 -0
- package/dist/adapters/compute-wrap.d.ts +33 -0
- package/dist/adapters/compute-wrap.d.ts.map +1 -0
- package/dist/adapters/compute-wrap.js +188 -0
- package/dist/adapters/compute-wrap.js.map +1 -0
- package/dist/adapters/data/aws_lambda_pricing.json +61 -0
- package/dist/adapters/gpu-wrap.d.ts +31 -0
- package/dist/adapters/gpu-wrap.d.ts.map +1 -0
- package/dist/adapters/gpu-wrap.js +147 -0
- package/dist/adapters/gpu-wrap.js.map +1 -0
- package/dist/adapters/http.d.ts +58 -0
- package/dist/adapters/http.d.ts.map +1 -0
- package/dist/adapters/http.js +769 -0
- package/dist/adapters/http.js.map +1 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/network-accountant.d.ts +63 -0
- package/dist/adapters/network-accountant.d.ts.map +1 -0
- package/dist/adapters/network-accountant.js +153 -0
- package/dist/adapters/network-accountant.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +225 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/scanner.d.ts +39 -0
- package/dist/cli/scanner.d.ts.map +1 -0
- package/dist/cli/scanner.js +480 -0
- package/dist/cli/scanner.js.map +1 -0
- package/dist/clients.d.ts +54 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +240 -0
- package/dist/clients.js.map +1 -0
- package/dist/cloud-detect.d.ts +96 -0
- package/dist/cloud-detect.d.ts.map +1 -0
- package/dist/cloud-detect.js +545 -0
- package/dist/cloud-detect.js.map +1 -0
- package/dist/core/auto-task.d.ts +20 -0
- package/dist/core/auto-task.d.ts.map +1 -0
- package/dist/core/auto-task.js +34 -0
- package/dist/core/auto-task.js.map +1 -0
- package/dist/core/cgroup-reader.d.ts +45 -0
- package/dist/core/cgroup-reader.d.ts.map +1 -0
- package/dist/core/cgroup-reader.js +124 -0
- package/dist/core/cgroup-reader.js.map +1 -0
- package/dist/core/cgroup-walker.d.ts +60 -0
- package/dist/core/cgroup-walker.d.ts.map +1 -0
- package/dist/core/cgroup-walker.js +166 -0
- package/dist/core/cgroup-walker.js.map +1 -0
- package/dist/core/compute-accountant.d.ts +51 -0
- package/dist/core/compute-accountant.d.ts.map +1 -0
- package/dist/core/compute-accountant.js +179 -0
- package/dist/core/compute-accountant.js.map +1 -0
- package/dist/core/compute-runtime.d.ts +42 -0
- package/dist/core/compute-runtime.d.ts.map +1 -0
- package/dist/core/compute-runtime.js +80 -0
- package/dist/core/compute-runtime.js.map +1 -0
- package/dist/core/config.d.ts +44 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +66 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/context.d.ts +76 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +91 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/fargate-metadata.d.ts +27 -0
- package/dist/core/fargate-metadata.d.ts.map +1 -0
- package/dist/core/fargate-metadata.js +102 -0
- package/dist/core/fargate-metadata.js.map +1 -0
- package/dist/core/gpu-accountant.d.ts +104 -0
- package/dist/core/gpu-accountant.d.ts.map +1 -0
- package/dist/core/gpu-accountant.js +383 -0
- package/dist/core/gpu-accountant.js.map +1 -0
- package/dist/core/gpu-runtime.d.ts +58 -0
- package/dist/core/gpu-runtime.d.ts.map +1 -0
- package/dist/core/gpu-runtime.js +131 -0
- package/dist/core/gpu-runtime.js.map +1 -0
- package/dist/core/heuristics.d.ts +74 -0
- package/dist/core/heuristics.d.ts.map +1 -0
- package/dist/core/heuristics.js +182 -0
- package/dist/core/heuristics.js.map +1 -0
- package/dist/core/models.d.ts +149 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +226 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/nvml-reader.d.ts +114 -0
- package/dist/core/nvml-reader.d.ts.map +1 -0
- package/dist/core/nvml-reader.js +323 -0
- package/dist/core/nvml-reader.js.map +1 -0
- package/dist/core/session.d.ts +48 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +123 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/tracker.d.ts +364 -0
- package/dist/core/tracker.d.ts.map +1 -0
- package/dist/core/tracker.js +1073 -0
- package/dist/core/tracker.js.map +1 -0
- package/dist/data/compute_prices.json +180 -0
- package/dist/data/egress_prices.json +418 -0
- package/dist/data/gpu_prices.json +412 -0
- package/dist/data/service_prices.json +2595 -0
- package/dist/dev-console.d.ts +12 -0
- package/dist/dev-console.d.ts.map +1 -0
- package/dist/dev-console.js +60 -0
- package/dist/dev-console.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/instruments/anthropic.d.ts +26 -0
- package/dist/instruments/anthropic.d.ts.map +1 -0
- package/dist/instruments/anthropic.js +242 -0
- package/dist/instruments/anthropic.js.map +1 -0
- package/dist/instruments/bedrock.d.ts +29 -0
- package/dist/instruments/bedrock.d.ts.map +1 -0
- package/dist/instruments/bedrock.js +215 -0
- package/dist/instruments/bedrock.js.map +1 -0
- package/dist/instruments/cohere.d.ts +29 -0
- package/dist/instruments/cohere.d.ts.map +1 -0
- package/dist/instruments/cohere.js +237 -0
- package/dist/instruments/cohere.js.map +1 -0
- package/dist/instruments/gemini.d.ts +30 -0
- package/dist/instruments/gemini.d.ts.map +1 -0
- package/dist/instruments/gemini.js +247 -0
- package/dist/instruments/gemini.js.map +1 -0
- package/dist/instruments/index.d.ts +35 -0
- package/dist/instruments/index.d.ts.map +1 -0
- package/dist/instruments/index.js +54 -0
- package/dist/instruments/index.js.map +1 -0
- package/dist/instruments/mcp.d.ts +24 -0
- package/dist/instruments/mcp.d.ts.map +1 -0
- package/dist/instruments/mcp.js +459 -0
- package/dist/instruments/mcp.js.map +1 -0
- package/dist/instruments/openai.d.ts +26 -0
- package/dist/instruments/openai.d.ts.map +1 -0
- package/dist/instruments/openai.js +221 -0
- package/dist/instruments/openai.js.map +1 -0
- package/dist/instruments/vercel-ai.d.ts +28 -0
- package/dist/instruments/vercel-ai.d.ts.map +1 -0
- package/dist/instruments/vercel-ai.js +192 -0
- package/dist/instruments/vercel-ai.js.map +1 -0
- package/dist/integrations/langchain.d.ts +65 -0
- package/dist/integrations/langchain.d.ts.map +1 -0
- package/dist/integrations/langchain.js +165 -0
- package/dist/integrations/langchain.js.map +1 -0
- package/dist/middleware/express.d.ts +55 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +101 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/index.d.ts +6 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/pricing/compute-pricing.d.ts +57 -0
- package/dist/pricing/compute-pricing.d.ts.map +1 -0
- package/dist/pricing/compute-pricing.js +627 -0
- package/dist/pricing/compute-pricing.js.map +1 -0
- package/dist/pricing/cost_map.json +37665 -0
- package/dist/pricing/egress-pricing.d.ts +55 -0
- package/dist/pricing/egress-pricing.d.ts.map +1 -0
- package/dist/pricing/egress-pricing.js +226 -0
- package/dist/pricing/egress-pricing.js.map +1 -0
- package/dist/pricing/engine.d.ts +24 -0
- package/dist/pricing/engine.d.ts.map +1 -0
- package/dist/pricing/engine.js +148 -0
- package/dist/pricing/engine.js.map +1 -0
- package/dist/pricing/gpu-pricing.d.ts +63 -0
- package/dist/pricing/gpu-pricing.d.ts.map +1 -0
- package/dist/pricing/gpu-pricing.js +484 -0
- package/dist/pricing/gpu-pricing.js.map +1 -0
- package/dist/pricing/rates.d.ts +17 -0
- package/dist/pricing/rates.d.ts.map +1 -0
- package/dist/pricing/rates.js +102 -0
- package/dist/pricing/rates.js.map +1 -0
- package/dist/pricing/service-catalog.d.ts +87 -0
- package/dist/pricing/service-catalog.d.ts.map +1 -0
- package/dist/pricing/service-catalog.js +406 -0
- package/dist/pricing/service-catalog.js.map +1 -0
- package/dist/schema/dexcost-event.v1.json +111 -0
- package/dist/schema/dexcost-task.v1.json +160 -0
- package/dist/schema/validate.d.ts +15 -0
- package/dist/schema/validate.d.ts.map +1 -0
- package/dist/schema/validate.js +87 -0
- package/dist/schema/validate.js.map +1 -0
- package/dist/security/redaction.d.ts +55 -0
- package/dist/security/redaction.d.ts.map +1 -0
- package/dist/security/redaction.js +144 -0
- package/dist/security/redaction.js.map +1 -0
- package/dist/transport/buffer.d.ts +117 -0
- package/dist/transport/buffer.d.ts.map +1 -0
- package/dist/transport/buffer.js +759 -0
- package/dist/transport/buffer.js.map +1 -0
- package/dist/transport/pusher.d.ts +89 -0
- package/dist/transport/pusher.d.ts.map +1 -0
- package/dist/transport/pusher.js +323 -0
- package/dist/transport/pusher.js.map +1 -0
- package/package.json +93 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dexwox Innovations and contributors
|
|
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
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# @dexcost/sdk
|
|
2
|
+
|
|
3
|
+
**Agent Unit Economics SDK for Node.js** — track LLM costs, non-LLM service fees, and retry waste attributed to customers, projects, and workflows.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @dexcost/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
With LLM provider SDKs (peer dependencies):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @dexcost/sdk openai @anthropic-ai/sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { init, track, close } from '@dexcost/sdk';
|
|
21
|
+
|
|
22
|
+
init({ apiKey: 'dx_live_...' }); // or set DEXCOST_API_KEY env var
|
|
23
|
+
|
|
24
|
+
await track({ taskType: 'summarise', customerId: 'acme' }, async (task) => {
|
|
25
|
+
// LLM calls are auto-captured — just use OpenAI/Anthropic normally
|
|
26
|
+
const response = await openai.chat.completions.create({
|
|
27
|
+
model: 'gpt-4o',
|
|
28
|
+
messages: [{ role: 'user', content: 'Summarise this document' }],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Record non-LLM costs manually
|
|
32
|
+
task.recordCost('pdf_parser', 0.002);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await close();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Auto-Instrumentation
|
|
39
|
+
|
|
40
|
+
dexcost auto-instruments **6 LLM providers** and the **global fetch API**.
|
|
41
|
+
|
|
42
|
+
### LLM Providers
|
|
43
|
+
|
|
44
|
+
| Provider | Package | Auto-Patched |
|
|
45
|
+
|----------|---------|-------------|
|
|
46
|
+
| OpenAI | `openai` | `chat.completions.create` |
|
|
47
|
+
| Anthropic | `@anthropic-ai/sdk` | `messages.create` |
|
|
48
|
+
| Vercel AI | `ai` | Vercel AI SDK functions |
|
|
49
|
+
| Google Gemini | `@google/generative-ai` | `generateContent` |
|
|
50
|
+
| AWS Bedrock | `@aws-sdk/client-bedrock-runtime` | `invokeModel` |
|
|
51
|
+
| Cohere | `cohere-ai` | `chat` / `generate` |
|
|
52
|
+
|
|
53
|
+
LLM provider packages are **peer dependencies** — install only the ones you use. dexcost detects them at runtime and patches automatically.
|
|
54
|
+
|
|
55
|
+
### HTTP (Non-LLM Cost Capture)
|
|
56
|
+
|
|
57
|
+
dexcost patches `globalThis.fetch` to capture HTTP calls to domains in the [163-service catalog](src/data/service_prices.json) — Pinecone, Twilio, SendGrid, Stripe, Firecrawl, Exa, and more. Costs are extracted from response headers/body and recorded as `external_cost` events.
|
|
58
|
+
|
|
59
|
+
### Controlling Instrumentation
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Instrument only specific providers
|
|
63
|
+
init({ autoInstrument: ['openai', 'anthropic'] });
|
|
64
|
+
|
|
65
|
+
// Disable all auto-instrumentation
|
|
66
|
+
init({ autoInstrument: [] });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
### `init()` Options
|
|
72
|
+
|
|
73
|
+
| Option | Type | Default | Description |
|
|
74
|
+
|--------|------|---------|-------------|
|
|
75
|
+
| `apiKey` | `string` | `DEXCOST_API_KEY` env | API key for cloud push |
|
|
76
|
+
| `autoInstrument` | `string[]` | All 6 providers | Which LLM SDKs to patch |
|
|
77
|
+
| `batchSize` | `number` | `100` | Events per sync batch |
|
|
78
|
+
| `flushIntervalMs` | `number` | `30000` | Milliseconds between sync pushes |
|
|
79
|
+
| `redactFields` | `string[]` | `undefined` | Field names to redact from event details |
|
|
80
|
+
| `hashCustomerId` | `boolean` | `false` | SHA-256 hash customer_id before storage |
|
|
81
|
+
| `environment` | `string` | `undefined` | Set to `"development"` for dev console mode |
|
|
82
|
+
| `dbPath` | `string` | `~/.dexcost/buffer.db` | Path to local SQLite buffer |
|
|
83
|
+
| `enableRetryHeuristics` | `boolean` | `false` | Auto-detect retries via pattern matching |
|
|
84
|
+
|
|
85
|
+
### Environment Variables
|
|
86
|
+
|
|
87
|
+
| Variable | Description |
|
|
88
|
+
|----------|-------------|
|
|
89
|
+
| `DEXCOST_API_KEY` | API key (if not passed to `init()`) |
|
|
90
|
+
| `DEXCOST_ENDPOINT` | Control Layer URL (default: `https://api.dexcost.io`) |
|
|
91
|
+
| `DEXCOST_ENV` | Set to `development` for dev console output |
|
|
92
|
+
|
|
93
|
+
## API
|
|
94
|
+
|
|
95
|
+
### Singleton Pattern
|
|
96
|
+
|
|
97
|
+
dexcost uses a singleton. Call `init()` once at app startup:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { init, getTracker, track, flush, close } from '@dexcost/sdk';
|
|
101
|
+
|
|
102
|
+
init({ apiKey: 'dx_live_...' });
|
|
103
|
+
|
|
104
|
+
// Use the global track() anywhere
|
|
105
|
+
await track({ taskType: 'chat', customerId: 'acme' }, async (task) => {
|
|
106
|
+
task.recordLlmCall('openai', 'gpt-4o', 800, 150);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Or get the tracker instance
|
|
110
|
+
const tracker = getTracker();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### TrackedTask Methods
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
await track({ taskType: '...' }, async (task) => {
|
|
117
|
+
// Record LLM call manually (usually auto-captured)
|
|
118
|
+
task.recordLlmCall('openai', 'gpt-4o', 800, 150);
|
|
119
|
+
|
|
120
|
+
// Record non-LLM cost
|
|
121
|
+
task.recordCost('pinecone', 0.001);
|
|
122
|
+
|
|
123
|
+
// Record usage (cost computed from registered rates)
|
|
124
|
+
task.recordUsage('s3_storage', 1024);
|
|
125
|
+
|
|
126
|
+
// Mark a retry
|
|
127
|
+
task.markRetry('rate_limit', 0.005);
|
|
128
|
+
|
|
129
|
+
// Link to external trace
|
|
130
|
+
task.linkTrace('datadog', 'trace-abc123');
|
|
131
|
+
|
|
132
|
+
// End with status (auto-detected from exceptions)
|
|
133
|
+
task.end('success');
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Customer Attribution
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { setContext, track } from '@dexcost/sdk';
|
|
141
|
+
|
|
142
|
+
setContext({ customerId: 'acme-corp', projectId: 'proj-alpha' });
|
|
143
|
+
|
|
144
|
+
// All tasks inherit the context
|
|
145
|
+
await track({ taskType: 'resolve_ticket' }, async (task) => {
|
|
146
|
+
// task.task.customerId === 'acme-corp'
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Context Propagation
|
|
151
|
+
|
|
152
|
+
dexcost uses `AsyncLocalStorage` — task context propagates across `await`, `Promise.all`, `setTimeout`, and any async operation without manual threading.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { getCurrentTask } from '@dexcost/sdk';
|
|
156
|
+
|
|
157
|
+
// Inside any async function within a tracked task:
|
|
158
|
+
const task = getCurrentTask(); // Returns the active Task or undefined
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Nested Tasks
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
await track({ taskType: 'pipeline' }, async (parent) => {
|
|
165
|
+
await track({ taskType: 'step_1' }, async (child) => {
|
|
166
|
+
// child.task.parentTaskId === parent.task.taskId (auto-linked)
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Dev Mode
|
|
172
|
+
|
|
173
|
+
Set `DEXCOST_ENV=development` or pass `environment: "development"` to `init()`. In dev mode:
|
|
174
|
+
- Cost events are printed to the console
|
|
175
|
+
- No data is pushed to the cloud
|
|
176
|
+
|
|
177
|
+
## Express Middleware
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { createExpressMiddleware } from '@dexcost/sdk';
|
|
181
|
+
|
|
182
|
+
app.use(createExpressMiddleware({
|
|
183
|
+
taskType: 'api_request',
|
|
184
|
+
extractCustomerId: (req) => req.headers['x-customer-id'],
|
|
185
|
+
}));
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Runtime Dependencies
|
|
189
|
+
|
|
190
|
+
- `better-sqlite3` — local event buffer
|
|
191
|
+
- `ajv` — JSON Schema validation
|
|
192
|
+
- `js-yaml` — rate file parsing
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npm install
|
|
198
|
+
npm test
|
|
199
|
+
npm run build
|
|
200
|
+
npm run lint
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Privacy
|
|
204
|
+
|
|
205
|
+
When you connect to the Dexcost Control Layer, the SDK transmits usage data
|
|
206
|
+
subject to our [Privacy Policy](https://dexcost.io/privacy).
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for the HTTP network adapter: destination classification and
|
|
3
|
+
* byte measurement. Pure functions — no SDK state, no I/O beyond parsing.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors python/src/dexcost/adapters/_netbytes.py.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Return whether *host* is internal traffic.
|
|
9
|
+
*
|
|
10
|
+
* - `true` — host is an RFC1918 / loopback / link-local IPv4 literal, an
|
|
11
|
+
* IPv6 ULA (`fc00::/7`), IPv6 link-local (`fe80::/10`), or
|
|
12
|
+
* IPv6 loopback (`::1`).
|
|
13
|
+
* - `false` — host parses as an IP literal but is not in the above ranges.
|
|
14
|
+
* - `null` — host is a name (not an IP literal); the SDK does not perform
|
|
15
|
+
* an extra DNS lookup to resolve it.
|
|
16
|
+
*
|
|
17
|
+
* Note: CGNAT shared address space (100.64.0.0/10, RFC 6598) is classified
|
|
18
|
+
* `false` — Python's `ipaddress.is_private` does not include it, and we
|
|
19
|
+
* mirror that behaviour for cross-SDK parity.
|
|
20
|
+
*/
|
|
21
|
+
export declare function classifyDestination(host: string): boolean | null;
|
|
22
|
+
/**
|
|
23
|
+
* Approximate the on-the-wire byte size of one HTTP message.
|
|
24
|
+
*
|
|
25
|
+
* `request line + header block + body`. Used for both directions: pass the
|
|
26
|
+
* request method/url/headers for bytes-out, or `"" / "" / response headers`
|
|
27
|
+
* for bytes-in. `bodyLen` is the known body length in bytes; negative values
|
|
28
|
+
* are clamped to zero.
|
|
29
|
+
*/
|
|
30
|
+
export declare function measureBytesFromHeaders(method: string, url: string, headers: Record<string, string>, bodyLen: number): number;
|
|
31
|
+
//# sourceMappingURL=_netbytes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_netbytes.d.ts","sourceRoot":"","sources":["../../src/adapters/_netbytes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA+BhE;AAeD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,EAAE,MAAM,GACd,MAAM,CAKR"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for the HTTP network adapter: destination classification and
|
|
3
|
+
* byte measurement. Pure functions — no SDK state, no I/O beyond parsing.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors python/src/dexcost/adapters/_netbytes.py.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// IP literal parsing (open-coded, no `ipaddr.js` dependency — recommended in
|
|
9
|
+
// the network-capture plan to avoid adding a dep just for classification).
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const IPV4_RE = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
12
|
+
function parseIPv4(host) {
|
|
13
|
+
const m = IPV4_RE.exec(host);
|
|
14
|
+
if (!m)
|
|
15
|
+
return null;
|
|
16
|
+
const octets = [];
|
|
17
|
+
for (let i = 1; i <= 4; i++) {
|
|
18
|
+
const n = Number(m[i]);
|
|
19
|
+
if (!Number.isInteger(n) || n < 0 || n > 255)
|
|
20
|
+
return null;
|
|
21
|
+
// Reject leading zeros beyond a single "0" (e.g. "010" is ambiguous on the
|
|
22
|
+
// wire). ipaddress.ip_address in Python is similarly strict in 3.10+.
|
|
23
|
+
if (m[i].length > 1 && m[i].startsWith("0"))
|
|
24
|
+
return null;
|
|
25
|
+
octets.push(n);
|
|
26
|
+
}
|
|
27
|
+
return octets;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Loose-grammar IPv6 detector: covers the common literal shapes including
|
|
31
|
+
* `::`-compressed forms. We do not need full numeric reconstruction because
|
|
32
|
+
* the classifier only inspects the textual prefix (`fc00::/7`, `fe80::/10`,
|
|
33
|
+
* `::1`).
|
|
34
|
+
*/
|
|
35
|
+
function isIPv6Literal(host) {
|
|
36
|
+
if (host.length === 0)
|
|
37
|
+
return false;
|
|
38
|
+
// Strip optional zone-id (`fe80::1%eth0`) before validation.
|
|
39
|
+
const bare = host.split("%", 1)[0];
|
|
40
|
+
if (bare.indexOf(":") === -1)
|
|
41
|
+
return false;
|
|
42
|
+
// Disallow obvious non-hex characters.
|
|
43
|
+
if (!/^[0-9a-fA-F:.]+$/.test(bare))
|
|
44
|
+
return false;
|
|
45
|
+
// At most one "::".
|
|
46
|
+
const dblIdx = bare.indexOf("::");
|
|
47
|
+
if (dblIdx !== -1 && bare.indexOf("::", dblIdx + 1) !== -1)
|
|
48
|
+
return false;
|
|
49
|
+
// Split and sanity-check each group.
|
|
50
|
+
const parts = bare.split(":");
|
|
51
|
+
if (parts.length < 3 || parts.length > 8)
|
|
52
|
+
return false;
|
|
53
|
+
for (const p of parts) {
|
|
54
|
+
if (p.length === 0)
|
|
55
|
+
continue; // produced by "::"
|
|
56
|
+
if (p.length > 4) {
|
|
57
|
+
// Could be an embedded IPv4 ("::ffff:1.2.3.4") — accept if last segment.
|
|
58
|
+
if (p === parts[parts.length - 1] && parseIPv4(p) !== null)
|
|
59
|
+
continue;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
function isPrivateIPv4(octets) {
|
|
66
|
+
const [a, b] = octets;
|
|
67
|
+
// 10.0.0.0/8
|
|
68
|
+
if (a === 10)
|
|
69
|
+
return true;
|
|
70
|
+
// 172.16.0.0/12
|
|
71
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
72
|
+
return true;
|
|
73
|
+
// 192.168.0.0/16
|
|
74
|
+
if (a === 192 && b === 168)
|
|
75
|
+
return true;
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function isLoopbackIPv4(octets) {
|
|
79
|
+
return octets[0] === 127;
|
|
80
|
+
}
|
|
81
|
+
function isLinkLocalIPv4(octets) {
|
|
82
|
+
return octets[0] === 169 && octets[1] === 254;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Return whether *host* is internal traffic.
|
|
86
|
+
*
|
|
87
|
+
* - `true` — host is an RFC1918 / loopback / link-local IPv4 literal, an
|
|
88
|
+
* IPv6 ULA (`fc00::/7`), IPv6 link-local (`fe80::/10`), or
|
|
89
|
+
* IPv6 loopback (`::1`).
|
|
90
|
+
* - `false` — host parses as an IP literal but is not in the above ranges.
|
|
91
|
+
* - `null` — host is a name (not an IP literal); the SDK does not perform
|
|
92
|
+
* an extra DNS lookup to resolve it.
|
|
93
|
+
*
|
|
94
|
+
* Note: CGNAT shared address space (100.64.0.0/10, RFC 6598) is classified
|
|
95
|
+
* `false` — Python's `ipaddress.is_private` does not include it, and we
|
|
96
|
+
* mirror that behaviour for cross-SDK parity.
|
|
97
|
+
*/
|
|
98
|
+
export function classifyDestination(host) {
|
|
99
|
+
if (!host)
|
|
100
|
+
return null;
|
|
101
|
+
const v4 = parseIPv4(host);
|
|
102
|
+
if (v4 !== null) {
|
|
103
|
+
return (isPrivateIPv4(v4) || isLoopbackIPv4(v4) || isLinkLocalIPv4(v4));
|
|
104
|
+
}
|
|
105
|
+
if (isIPv6Literal(host)) {
|
|
106
|
+
const lower = host.toLowerCase().split("%", 1)[0];
|
|
107
|
+
// ::1 loopback (also covers "0:0:0:0:0:0:0:1").
|
|
108
|
+
if (lower === "::1")
|
|
109
|
+
return true;
|
|
110
|
+
if (/^0:0:0:0:0:0:0:0*1$/.test(lower))
|
|
111
|
+
return true;
|
|
112
|
+
// fc00::/7 — ULA. First byte is 0xfc or 0xfd; in text form that means
|
|
113
|
+
// the address starts with "fc" or "fd" followed by a hex digit (or "::").
|
|
114
|
+
if (/^f[cd][0-9a-f]{0,2}:/.test(lower) || /^f[cd]:/.test(lower)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
// fe80::/10 — link-local. First 10 bits are 1111111010, so the leading
|
|
118
|
+
// 16 bits are between 0xfe80 and 0xfebf.
|
|
119
|
+
const firstGroup = lower.split(":")[0];
|
|
120
|
+
if (firstGroup.length > 0 && firstGroup.length <= 4) {
|
|
121
|
+
const v = parseInt(firstGroup, 16);
|
|
122
|
+
if (!Number.isNaN(v) && v >= 0xfe80 && v <= 0xfebf)
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Byte-size measurement
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
function _headersByteLen(headers) {
|
|
133
|
+
let total = 0;
|
|
134
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
135
|
+
// ": " + CRLF == 4 extra bytes per header line.
|
|
136
|
+
total += String(key).length + String(value).length + 4;
|
|
137
|
+
}
|
|
138
|
+
return total + 2; // trailing CRLF that ends the header block
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Approximate the on-the-wire byte size of one HTTP message.
|
|
142
|
+
*
|
|
143
|
+
* `request line + header block + body`. Used for both directions: pass the
|
|
144
|
+
* request method/url/headers for bytes-out, or `"" / "" / response headers`
|
|
145
|
+
* for bytes-in. `bodyLen` is the known body length in bytes; negative values
|
|
146
|
+
* are clamped to zero.
|
|
147
|
+
*/
|
|
148
|
+
export function measureBytesFromHeaders(method, url, headers, bodyLen) {
|
|
149
|
+
// method + url + " HTTP/1.1\r\n" → +12 trailing bytes
|
|
150
|
+
const requestLine = String(method).length + String(url).length + 12;
|
|
151
|
+
const body = Math.max(0, Math.trunc(Number(bodyLen) || 0));
|
|
152
|
+
return requestLine + _headersByteLen(headers) + body;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=_netbytes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_netbytes.js","sourceRoot":"","sources":["../../src/adapters/_netbytes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Lambda cost adapter — compute cost from duration, memory, and region.
|
|
3
|
+
*
|
|
4
|
+
* `lambdaCost` is a pure function that returns the cost (in USD) and a
|
|
5
|
+
* breakdown for a single Lambda invocation, using bundled pricing data
|
|
6
|
+
* (no network I/O). Mirrors the Python SDK's `adapters/aws_lambda.py`.
|
|
7
|
+
*/
|
|
8
|
+
/** Breakdown of a Lambda invocation's cost. */
|
|
9
|
+
export interface LambdaCostDetails {
|
|
10
|
+
region: string;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
memoryMb: number;
|
|
13
|
+
gbSeconds: number;
|
|
14
|
+
durationCostUsd: number;
|
|
15
|
+
requestCostUsd: number;
|
|
16
|
+
ratePerGbSecond: number;
|
|
17
|
+
}
|
|
18
|
+
/** Result of {@link lambdaCost}. */
|
|
19
|
+
export interface LambdaCostResult {
|
|
20
|
+
/** Total invocation cost in USD. */
|
|
21
|
+
costUsd: number;
|
|
22
|
+
/** Cost breakdown. */
|
|
23
|
+
details: LambdaCostDetails;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Return a sorted list of AWS region codes with bundled pricing data.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getSupportedRegions(): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Calculate the cost of a single AWS Lambda invocation.
|
|
31
|
+
*
|
|
32
|
+
* Pure function — no I/O, no side effects. Uses the bundled
|
|
33
|
+
* `aws_lambda_pricing.json` for rates.
|
|
34
|
+
*
|
|
35
|
+
* @param durationMs - Execution duration in milliseconds (>= 0).
|
|
36
|
+
* @param memoryMb - Allocated memory in MB (> 0).
|
|
37
|
+
* @param region - AWS region code (e.g. `"us-east-1"`).
|
|
38
|
+
* @throws Error when `region` is unknown, `durationMs` < 0, or `memoryMb` <= 0.
|
|
39
|
+
*/
|
|
40
|
+
export declare function lambdaCost(durationMs: number, memoryMb: number, region: string): LambdaCostResult;
|
|
41
|
+
//# sourceMappingURL=aws-lambda.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws-lambda.d.ts","sourceRoot":"","sources":["../../src/adapters/aws-lambda.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuBH,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,gBAAgB,CAuClB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Lambda cost adapter — compute cost from duration, memory, and region.
|
|
3
|
+
*
|
|
4
|
+
* `lambdaCost` is a pure function that returns the cost (in USD) and a
|
|
5
|
+
* breakdown for a single Lambda invocation, using bundled pricing data
|
|
6
|
+
* (no network I/O). Mirrors the Python SDK's `adapters/aws_lambda.py`.
|
|
7
|
+
*/
|
|
8
|
+
// Sprint 3 Theme E / §4.2.3 — Node 18 compat: runtime JSON load.
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
const _thisDir = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const pricingData = JSON.parse(readFileSync(join(_thisDir, "data", "aws_lambda_pricing.json"), "utf-8"));
|
|
14
|
+
const _pricing = pricingData;
|
|
15
|
+
/**
|
|
16
|
+
* Return a sorted list of AWS region codes with bundled pricing data.
|
|
17
|
+
*/
|
|
18
|
+
export function getSupportedRegions() {
|
|
19
|
+
return Object.keys(_pricing.regions).sort();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Calculate the cost of a single AWS Lambda invocation.
|
|
23
|
+
*
|
|
24
|
+
* Pure function — no I/O, no side effects. Uses the bundled
|
|
25
|
+
* `aws_lambda_pricing.json` for rates.
|
|
26
|
+
*
|
|
27
|
+
* @param durationMs - Execution duration in milliseconds (>= 0).
|
|
28
|
+
* @param memoryMb - Allocated memory in MB (> 0).
|
|
29
|
+
* @param region - AWS region code (e.g. `"us-east-1"`).
|
|
30
|
+
* @throws Error when `region` is unknown, `durationMs` < 0, or `memoryMb` <= 0.
|
|
31
|
+
*/
|
|
32
|
+
export function lambdaCost(durationMs, memoryMb, region) {
|
|
33
|
+
if (durationMs < 0) {
|
|
34
|
+
throw new Error(`durationMs must be >= 0, got ${durationMs}`);
|
|
35
|
+
}
|
|
36
|
+
if (memoryMb <= 0) {
|
|
37
|
+
throw new Error(`memoryMb must be > 0, got ${memoryMb}`);
|
|
38
|
+
}
|
|
39
|
+
const regionPricing = _pricing.regions[region];
|
|
40
|
+
if (regionPricing === undefined) {
|
|
41
|
+
const supported = getSupportedRegions().join(", ");
|
|
42
|
+
throw new Error(`Unknown AWS region '${region}'. Supported regions: ${supported}`);
|
|
43
|
+
}
|
|
44
|
+
// GB-seconds = duration (s) * memory (GB)
|
|
45
|
+
const durationSeconds = durationMs / 1000;
|
|
46
|
+
const memoryGb = memoryMb / 1024;
|
|
47
|
+
const gbSeconds = durationSeconds * memoryGb;
|
|
48
|
+
const ratePerGbSecond = Number(regionPricing.duration_per_gb_second);
|
|
49
|
+
const requestCharge = Number(regionPricing.request_per_invocation);
|
|
50
|
+
const durationCost = gbSeconds * ratePerGbSecond;
|
|
51
|
+
const totalCost = durationCost + requestCharge;
|
|
52
|
+
return {
|
|
53
|
+
costUsd: totalCost,
|
|
54
|
+
details: {
|
|
55
|
+
region,
|
|
56
|
+
durationMs,
|
|
57
|
+
memoryMb,
|
|
58
|
+
gbSeconds,
|
|
59
|
+
durationCostUsd: durationCost,
|
|
60
|
+
requestCostUsd: requestCharge,
|
|
61
|
+
ratePerGbSecond,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=aws-lambda.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws-lambda.js","sourceRoot":"","sources":["../../src/adapters/aws-lambda.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,iEAAiE;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACzD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAC5B,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,yBAAyB,CAAC,EAAE,OAAO,CAAC,CACzE,CAAC;AAWF,MAAM,QAAQ,GAAG,WAA2C,CAAC;AAqB7D;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CACxB,UAAkB,EAClB,QAAgB,EAChB,MAAc;IAEd,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,uBAAuB,MAAM,yBAAyB,SAAS,EAAE,CAClE,CAAC;IACJ,CAAC;IAED,0CAA0C;IAC1C,MAAM,eAAe,GAAG,UAAU,GAAG,IAAI,CAAC;IAC1C,MAAM,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;IACjC,MAAM,SAAS,GAAG,eAAe,GAAG,QAAQ,CAAC;IAE7C,MAAM,eAAe,GAAG,MAAM,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IACrE,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAEnE,MAAM,YAAY,GAAG,SAAS,GAAG,eAAe,CAAC;IACjD,MAAM,SAAS,GAAG,YAAY,GAAG,aAAa,CAAC;IAE/C,OAAO;QACL,OAAO,EAAE,SAAS;QAClB,OAAO,EAAE;YACP,MAAM;YACN,UAAU;YACV,QAAQ;YACR,SAAS;YACT,eAAe,EAAE,YAAY;YAC7B,cAAc,EAAE,aAAa;YAC7B,eAAe;SAChB;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser cost adapter — automatic cost tracking for Playwright sessions.
|
|
3
|
+
*
|
|
4
|
+
* `trackBrowser` wraps a block of Playwright work, measures wall-clock
|
|
5
|
+
* time, and records a `compute_cost` event proportional to session
|
|
6
|
+
* duration. Mirrors the Python SDK's `adapters/browser.py`.
|
|
7
|
+
*
|
|
8
|
+
* `playwright` is an optional peer dependency — this adapter is duck-typed
|
|
9
|
+
* and never imports it, so it works with any object exposing a `.url`.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
*
|
|
13
|
+
* import { trackBrowser } from "dexcost";
|
|
14
|
+
*
|
|
15
|
+
* await tracker.track({ taskType: "scrape" }, async () => {
|
|
16
|
+
* await trackBrowser(page, async () => {
|
|
17
|
+
* await page.goto("https://example.com");
|
|
18
|
+
* }, { ratePerMinute: 0.01 });
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
import { type CostEvent } from "../core/models.js";
|
|
22
|
+
import type { EventBuffer } from "../transport/buffer.js";
|
|
23
|
+
/**
|
|
24
|
+
* Wire the browser adapter to a storage buffer. The CostTracker calls this on
|
|
25
|
+
* construction so `trackBrowser()` cost events reach SQLite and the sync
|
|
26
|
+
* pusher. Pass `null` to detach (events then stay in-memory only).
|
|
27
|
+
*/
|
|
28
|
+
export declare function setBrowserBuffer(buffer: EventBuffer | null): void;
|
|
29
|
+
/** Return all events recorded by the browser adapter since the last clear. */
|
|
30
|
+
export declare function getBrowserEvents(): CostEvent[];
|
|
31
|
+
/** Clear the browser adapter's recorded events list. */
|
|
32
|
+
export declare function clearBrowserEvents(): void;
|
|
33
|
+
/** Options for {@link trackBrowser}. */
|
|
34
|
+
export interface TrackBrowserOptions {
|
|
35
|
+
/** Cost per minute of browser usage in USD. Defaults to `0.01`. */
|
|
36
|
+
ratePerMinute?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run `fn` while measuring browser session wall-clock time, then record a
|
|
40
|
+
* `compute_cost` event with `costUsd = elapsedMinutes * ratePerMinute`.
|
|
41
|
+
*
|
|
42
|
+
* The event is only recorded when there is an active task in the context
|
|
43
|
+
* (via `getCurrentTask()`). When no task is active, the timing wrapper is
|
|
44
|
+
* a silent pass-through. The cost is always recorded even if `fn` throws.
|
|
45
|
+
*
|
|
46
|
+
* @param page - A Playwright `Page` (or any object with a `.url`). Not
|
|
47
|
+
* type-checked, to avoid requiring `playwright` as a hard dependency.
|
|
48
|
+
* @param fn - The browser work to run and time.
|
|
49
|
+
* @param options - Optional `ratePerMinute` override.
|
|
50
|
+
*/
|
|
51
|
+
export declare function trackBrowser<T>(page: any, fn: () => Promise<T>, options?: TrackBrowserOptions): Promise<T>;
|
|
52
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/adapters/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAwB1D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEjE;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,IAAI,SAAS,EAAE,CAE9C;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,IAAI,EAAE,GAAG,EACT,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,CAAC,CAAC,CASZ"}
|