@venturekit-pro/ai 0.0.0-dev.20260701100017 → 0.0.1
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 +48 -0
- package/dist/chat/anthropic.d.ts.map +1 -1
- package/dist/chat/anthropic.js +39 -32
- package/dist/chat/anthropic.js.map +1 -1
- package/dist/chat/google.d.ts.map +1 -1
- package/dist/chat/google.js +32 -25
- package/dist/chat/google.js.map +1 -1
- package/dist/chat/openai.d.ts.map +1 -1
- package/dist/chat/openai.js +34 -27
- package/dist/chat/openai.js.map +1 -1
- package/dist/cost-ledger/index.d.ts +2 -0
- package/dist/cost-ledger/index.d.ts.map +1 -1
- package/dist/cost-ledger/index.js +1 -0
- package/dist/cost-ledger/index.js.map +1 -1
- package/dist/cost-ledger/migrations/vk_ai_cost_0001_init.sql +6 -6
- package/dist/cost-ledger/migrations/vk_ai_cost_0002_llm_models.sql +4 -4
- package/dist/cost-ledger/query.js +4 -4
- package/dist/cost-ledger/record.js +2 -2
- package/dist/cost-ledger/record.js.map +1 -1
- package/dist/cost-ledger/windows.d.ts +82 -0
- package/dist/cost-ledger/windows.d.ts.map +1 -0
- package/dist/cost-ledger/windows.js +147 -0
- package/dist/cost-ledger/windows.js.map +1 -0
- package/dist/image/client.d.ts +32 -0
- package/dist/image/client.d.ts.map +1 -0
- package/dist/image/client.js +25 -0
- package/dist/image/client.js.map +1 -0
- package/dist/image/google.d.ts +24 -0
- package/dist/image/google.d.ts.map +1 -0
- package/dist/image/google.js +60 -0
- package/dist/image/google.js.map +1 -0
- package/dist/image/index.d.ts +11 -0
- package/dist/image/index.d.ts.map +1 -0
- package/dist/image/index.js +9 -0
- package/dist/image/index.js.map +1 -0
- package/dist/image/types.d.ts +67 -0
- package/dist/image/types.d.ts.map +1 -0
- package/dist/image/types.js +19 -0
- package/dist/image/types.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/model-catalog/crud.d.ts +42 -0
- package/dist/model-catalog/crud.d.ts.map +1 -0
- package/dist/model-catalog/crud.js +162 -0
- package/dist/model-catalog/crud.js.map +1 -0
- package/dist/model-catalog/index.d.ts +11 -0
- package/dist/model-catalog/index.d.ts.map +1 -0
- package/dist/model-catalog/index.js +10 -0
- package/dist/model-catalog/index.js.map +1 -0
- package/dist/model-catalog/types.d.ts +97 -0
- package/dist/model-catalog/types.d.ts.map +1 -0
- package/dist/model-catalog/types.js +16 -0
- package/dist/model-catalog/types.js.map +1 -0
- package/package.json +3 -3
- package/src/cost-ledger/migrations/vk_ai_cost_0001_init.sql +6 -6
- package/src/cost-ledger/migrations/vk_ai_cost_0002_llm_models.sql +4 -4
package/README.md
CHANGED
|
@@ -108,6 +108,54 @@ const agent = createAgent({
|
|
|
108
108
|
const response = await agent.run('How do I set up authentication?');
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
## Chat Completion
|
|
112
|
+
|
|
113
|
+
Provider-neutral single- or multi-turn text completion across Anthropic,
|
|
114
|
+
OpenAI, and Google. Construct one client per `(provider, apiKey)` and call
|
|
115
|
+
`.complete()`; supply `pricing` to get a USD cost stamp back.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { createChatClient } from '@venturekit-pro/ai';
|
|
119
|
+
|
|
120
|
+
const client = createChatClient({ provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY! });
|
|
121
|
+
|
|
122
|
+
const result = await client.complete({
|
|
123
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
124
|
+
messages: [
|
|
125
|
+
{ role: 'system', content: 'You are a concise assistant.' },
|
|
126
|
+
{ role: 'user', content: 'Three words about cats.' },
|
|
127
|
+
],
|
|
128
|
+
maxTokens: 100,
|
|
129
|
+
pricing: { costPer1kInputUsd: 0.003, costPer1kOutputUsd: 0.015 },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
console.log(result.output, result.usage, result.cost);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Pair with `withFallback` / `retryWithBackoff` from the fallback module to add
|
|
136
|
+
primary→backup routing or exponential-backoff retries.
|
|
137
|
+
|
|
138
|
+
## Image Generation
|
|
139
|
+
|
|
140
|
+
Provider-neutral text-to-image (currently Google Imagen), returning raw image
|
|
141
|
+
bytes. The caller owns prompt composition (brand/style suffixes) and cost
|
|
142
|
+
attribution — the result carries provider/model echo and latency only.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { createImageClient } from '@venturekit-pro/ai';
|
|
146
|
+
|
|
147
|
+
const client = createImageClient({ provider: 'google', apiKey: process.env.GOOGLE_API_KEY! });
|
|
148
|
+
|
|
149
|
+
const { images } = await client.generate({
|
|
150
|
+
model: 'imagen-4.0-ultra-generate-001',
|
|
151
|
+
prompt: 'A sunlit Casablanca apartment, editorial photographic style.',
|
|
152
|
+
aspectRatio: '16:9',
|
|
153
|
+
count: 1,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// images[0].bytes (Uint8Array), images[0].mimeType
|
|
157
|
+
```
|
|
158
|
+
|
|
111
159
|
## API Reference
|
|
112
160
|
|
|
113
161
|
See the [API reference](https://venturekit.dev/api-reference/ai) for full documentation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/chat/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/chat/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAsF/B"}
|
package/dist/chat/anthropic.js
CHANGED
|
@@ -43,10 +43,17 @@ export async function completeWithAnthropic(config, request) {
|
|
|
43
43
|
if (system !== undefined)
|
|
44
44
|
body.system = system;
|
|
45
45
|
const controller = new AbortController();
|
|
46
|
+
// The timeout must cover the FULL request — establishing the
|
|
47
|
+
// connection AND reading the response body. Clearing it the moment
|
|
48
|
+
// `fetch` resolves (i.e. once response headers arrive) would leave the
|
|
49
|
+
// `response.json()` / `response.text()` body reads below unprotected:
|
|
50
|
+
// a provider that sends headers then stalls the body would hang this
|
|
51
|
+
// call — and any workflow run awaiting it — indefinitely. Keep the
|
|
52
|
+
// abort signal armed until the body is fully consumed; clear it only
|
|
53
|
+
// in `finally` (success, error, or abort).
|
|
46
54
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
47
|
-
let response;
|
|
48
55
|
try {
|
|
49
|
-
response = await fetch(`${baseUrl}/v1/messages`, {
|
|
56
|
+
const response = await fetch(`${baseUrl}/v1/messages`, {
|
|
50
57
|
method: 'POST',
|
|
51
58
|
headers: {
|
|
52
59
|
'x-api-key': config.apiKey,
|
|
@@ -57,40 +64,40 @@ export async function completeWithAnthropic(config, request) {
|
|
|
57
64
|
body: JSON.stringify(body),
|
|
58
65
|
signal: controller.signal,
|
|
59
66
|
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const text = (await response.text()).slice(0, 500);
|
|
69
|
+
throw new Error(`[ai/chat/anthropic] ${response.status} ${response.statusText}: ${text}`);
|
|
70
|
+
}
|
|
71
|
+
const json = (await response.json());
|
|
72
|
+
// Anthropic returns a `content` array of blocks; we concatenate
|
|
73
|
+
// all `text` blocks. Tool-use blocks pass through silently (this
|
|
74
|
+
// module doesn't support tools — if a caller routes a tool-using
|
|
75
|
+
// prompt here, they'll see an empty completion and our error
|
|
76
|
+
// below will fire).
|
|
77
|
+
const output = (json.content ?? [])
|
|
78
|
+
.filter((b) => b.type === 'text' && typeof b.text === 'string')
|
|
79
|
+
.map((b) => b.text)
|
|
80
|
+
.join('');
|
|
81
|
+
if (output.length === 0) {
|
|
82
|
+
throw new Error(`[ai/chat/anthropic] Empty completion (stop_reason=${json.stop_reason ?? 'unknown'})`);
|
|
83
|
+
}
|
|
84
|
+
const usage = {
|
|
85
|
+
tokensIn: json.usage?.input_tokens ?? null,
|
|
86
|
+
tokensOut: json.usage?.output_tokens ?? null,
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
output,
|
|
90
|
+
provider: 'anthropic',
|
|
91
|
+
model: request.model,
|
|
92
|
+
usage,
|
|
93
|
+
cost: stampCost(usage, request.pricing),
|
|
94
|
+
latencyMs: Date.now() - startedAt,
|
|
95
|
+
finishReason: mapFinishReason(json.stop_reason ?? null),
|
|
96
|
+
};
|
|
60
97
|
}
|
|
61
98
|
finally {
|
|
62
99
|
clearTimeout(timer);
|
|
63
100
|
}
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
const text = (await response.text()).slice(0, 500);
|
|
66
|
-
throw new Error(`[ai/chat/anthropic] ${response.status} ${response.statusText}: ${text}`);
|
|
67
|
-
}
|
|
68
|
-
const json = (await response.json());
|
|
69
|
-
// Anthropic returns a `content` array of blocks; we concatenate
|
|
70
|
-
// all `text` blocks. Tool-use blocks pass through silently (this
|
|
71
|
-
// module doesn't support tools — if a caller routes a tool-using
|
|
72
|
-
// prompt here, they'll see an empty completion and our error
|
|
73
|
-
// below will fire).
|
|
74
|
-
const output = (json.content ?? [])
|
|
75
|
-
.filter((b) => b.type === 'text' && typeof b.text === 'string')
|
|
76
|
-
.map((b) => b.text)
|
|
77
|
-
.join('');
|
|
78
|
-
if (output.length === 0) {
|
|
79
|
-
throw new Error(`[ai/chat/anthropic] Empty completion (stop_reason=${json.stop_reason ?? 'unknown'})`);
|
|
80
|
-
}
|
|
81
|
-
const usage = {
|
|
82
|
-
tokensIn: json.usage?.input_tokens ?? null,
|
|
83
|
-
tokensOut: json.usage?.output_tokens ?? null,
|
|
84
|
-
};
|
|
85
|
-
return {
|
|
86
|
-
output,
|
|
87
|
-
provider: 'anthropic',
|
|
88
|
-
model: request.model,
|
|
89
|
-
usage,
|
|
90
|
-
cost: stampCost(usage, request.pricing),
|
|
91
|
-
latencyMs: Date.now() - startedAt,
|
|
92
|
-
finishReason: mapFinishReason(json.stop_reason ?? null),
|
|
93
|
-
};
|
|
94
101
|
}
|
|
95
102
|
/**
|
|
96
103
|
* Split a leading `system` message off the conversation. Anthropic
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/chat/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAkB3C,sEAAsE;AACtE,MAAM,yBAAyB,GAAG,YAAY,CAAC;AAE/C,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAA8B,EAC9B,OAA8B;IAE9B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,2BAA2B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACpF,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,yBAAyB,CAAC;IAClE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC;IAElE,+DAA+D;IAC/D,8DAA8D;IAC9D,gDAAgD;IAChD,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE/D,MAAM,IAAI,GAA4B;QACpC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;QACrC,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;KAC1E,CAAC;IACF,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS;QAAE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAC9E,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1D,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAChF,IAAI,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAE/C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/chat/anthropic.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/chat/google.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/chat/google.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAoF/B"}
|
package/dist/chat/google.js
CHANGED
|
@@ -45,42 +45,49 @@ export async function completeWithGoogle(config, request) {
|
|
|
45
45
|
// the URL.
|
|
46
46
|
const url = `${baseUrl}/${apiVersion}/models/${encodeURIComponent(request.model)}:generateContent?key=${encodeURIComponent(config.apiKey)}`;
|
|
47
47
|
const controller = new AbortController();
|
|
48
|
+
// The timeout must cover the FULL request — establishing the
|
|
49
|
+
// connection AND reading the response body. Clearing it the moment
|
|
50
|
+
// `fetch` resolves (i.e. once response headers arrive) would leave the
|
|
51
|
+
// `response.json()` / `response.text()` body reads below unprotected:
|
|
52
|
+
// a provider that sends headers then stalls the body would hang this
|
|
53
|
+
// call — and any workflow run awaiting it — indefinitely. Keep the
|
|
54
|
+
// abort signal armed until the body is fully consumed; clear it only
|
|
55
|
+
// in `finally` (success, error, or abort).
|
|
48
56
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
49
|
-
let response;
|
|
50
57
|
try {
|
|
51
|
-
response = await fetch(url, {
|
|
58
|
+
const response = await fetch(url, {
|
|
52
59
|
method: 'POST',
|
|
53
60
|
headers: { 'Content-Type': 'application/json' },
|
|
54
61
|
body: JSON.stringify(body),
|
|
55
62
|
signal: controller.signal,
|
|
56
63
|
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const text = (await response.text()).slice(0, 500);
|
|
66
|
+
throw new Error(`[ai/chat/google] ${response.status} ${response.statusText}: ${text}`);
|
|
67
|
+
}
|
|
68
|
+
const json = (await response.json());
|
|
69
|
+
const candidate = json.candidates?.[0];
|
|
70
|
+
const text = candidate?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';
|
|
71
|
+
if (text.length === 0) {
|
|
72
|
+
throw new Error(`[ai/chat/google] Empty completion (finishReason=${candidate?.finishReason ?? 'unknown'})`);
|
|
73
|
+
}
|
|
74
|
+
const usage = {
|
|
75
|
+
tokensIn: json.usageMetadata?.promptTokenCount ?? null,
|
|
76
|
+
tokensOut: json.usageMetadata?.candidatesTokenCount ?? null,
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
output: text,
|
|
80
|
+
provider: 'google',
|
|
81
|
+
model: request.model,
|
|
82
|
+
usage,
|
|
83
|
+
cost: stampCost(usage, request.pricing),
|
|
84
|
+
latencyMs: Date.now() - startedAt,
|
|
85
|
+
finishReason: mapFinishReason(candidate?.finishReason ?? null),
|
|
86
|
+
};
|
|
57
87
|
}
|
|
58
88
|
finally {
|
|
59
89
|
clearTimeout(timer);
|
|
60
90
|
}
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
const text = (await response.text()).slice(0, 500);
|
|
63
|
-
throw new Error(`[ai/chat/google] ${response.status} ${response.statusText}: ${text}`);
|
|
64
|
-
}
|
|
65
|
-
const json = (await response.json());
|
|
66
|
-
const candidate = json.candidates?.[0];
|
|
67
|
-
const text = candidate?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';
|
|
68
|
-
if (text.length === 0) {
|
|
69
|
-
throw new Error(`[ai/chat/google] Empty completion (finishReason=${candidate?.finishReason ?? 'unknown'})`);
|
|
70
|
-
}
|
|
71
|
-
const usage = {
|
|
72
|
-
tokensIn: json.usageMetadata?.promptTokenCount ?? null,
|
|
73
|
-
tokensOut: json.usageMetadata?.candidatesTokenCount ?? null,
|
|
74
|
-
};
|
|
75
|
-
return {
|
|
76
|
-
output: text,
|
|
77
|
-
provider: 'google',
|
|
78
|
-
model: request.model,
|
|
79
|
-
usage,
|
|
80
|
-
cost: stampCost(usage, request.pricing),
|
|
81
|
-
latencyMs: Date.now() - startedAt,
|
|
82
|
-
finishReason: mapFinishReason(candidate?.finishReason ?? null),
|
|
83
|
-
};
|
|
84
91
|
}
|
|
85
92
|
function splitSystem(messages) {
|
|
86
93
|
let system;
|
package/dist/chat/google.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/chat/google.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAuB3C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAA2B,EAC3B,OAA8B;IAE9B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,2CAA2C,CAAC,CAAC,OAAO,CACrF,MAAM,EACN,EAAE,CACH,CAAC;IACF,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,QAAQ,CAAC;IACjD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC;IAElE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE/D,MAAM,IAAI,GAA4B;QACpC,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjC,IAAI,EAAE,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;YAC/C,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC7B,CAAC,CAAC;QACH,gBAAgB,EAAE;YAChB,eAAe,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;YAC1C,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpF;KACF,CAAC;IACF,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC,iBAAiB,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACzD,CAAC;IAED,mEAAmE;IACnE,2DAA2D;IAC3D,mEAAmE;IACnE,WAAW;IACX,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,UAAU,WAAW,kBAAkB,CAC/D,OAAO,CAAC,KAAK,CACd,wBAAwB,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IAE7D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/chat/google.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/chat/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/chat/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAyE/B"}
|
package/dist/chat/openai.js
CHANGED
|
@@ -37,10 +37,17 @@ export async function completeWithOpenAi(config, request) {
|
|
|
37
37
|
if (request.stop && request.stop.length > 0)
|
|
38
38
|
body.stop = request.stop;
|
|
39
39
|
const controller = new AbortController();
|
|
40
|
+
// The timeout must cover the FULL request — establishing the
|
|
41
|
+
// connection AND reading the response body. Clearing it the moment
|
|
42
|
+
// `fetch` resolves (i.e. once response headers arrive) would leave the
|
|
43
|
+
// `response.json()` / `response.text()` body reads below unprotected:
|
|
44
|
+
// a provider that sends headers then stalls the body would hang this
|
|
45
|
+
// call — and any workflow run awaiting it — indefinitely. Keep the
|
|
46
|
+
// abort signal armed until the body is fully consumed; clear it only
|
|
47
|
+
// in `finally` (success, error, or abort).
|
|
40
48
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
41
|
-
let response;
|
|
42
49
|
try {
|
|
43
|
-
response = await fetch(`${baseUrl}/v1/chat/completions`, {
|
|
50
|
+
const response = await fetch(`${baseUrl}/v1/chat/completions`, {
|
|
44
51
|
method: 'POST',
|
|
45
52
|
headers: {
|
|
46
53
|
Authorization: `Bearer ${config.apiKey}`,
|
|
@@ -52,35 +59,35 @@ export async function completeWithOpenAi(config, request) {
|
|
|
52
59
|
body: JSON.stringify(body),
|
|
53
60
|
signal: controller.signal,
|
|
54
61
|
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
// Truncate the response body so the error message stays
|
|
64
|
+
// log-friendly. Full body is recoverable via the upstream
|
|
65
|
+
// caller's `audit_events.error_message` when wired through.
|
|
66
|
+
const text = (await response.text()).slice(0, 500);
|
|
67
|
+
throw new Error(`[ai/chat/openai] ${response.status} ${response.statusText}: ${text}`);
|
|
68
|
+
}
|
|
69
|
+
const json = (await response.json());
|
|
70
|
+
const text = json.choices?.[0]?.message?.content;
|
|
71
|
+
if (!text) {
|
|
72
|
+
throw new Error('[ai/chat/openai] Empty completion');
|
|
73
|
+
}
|
|
74
|
+
const usage = {
|
|
75
|
+
tokensIn: json.usage?.prompt_tokens ?? null,
|
|
76
|
+
tokensOut: json.usage?.completion_tokens ?? null,
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
output: text,
|
|
80
|
+
provider: 'openai',
|
|
81
|
+
model: request.model,
|
|
82
|
+
usage,
|
|
83
|
+
cost: stampCost(usage, request.pricing),
|
|
84
|
+
latencyMs: Date.now() - startedAt,
|
|
85
|
+
finishReason: mapFinishReason(json.choices?.[0]?.finish_reason ?? null),
|
|
86
|
+
};
|
|
55
87
|
}
|
|
56
88
|
finally {
|
|
57
89
|
clearTimeout(timer);
|
|
58
90
|
}
|
|
59
|
-
if (!response.ok) {
|
|
60
|
-
// Truncate the response body so the error message stays
|
|
61
|
-
// log-friendly. Full body is recoverable via the upstream
|
|
62
|
-
// caller's `audit_events.error_message` when wired through.
|
|
63
|
-
const text = (await response.text()).slice(0, 500);
|
|
64
|
-
throw new Error(`[ai/chat/openai] ${response.status} ${response.statusText}: ${text}`);
|
|
65
|
-
}
|
|
66
|
-
const json = (await response.json());
|
|
67
|
-
const text = json.choices?.[0]?.message?.content;
|
|
68
|
-
if (!text) {
|
|
69
|
-
throw new Error('[ai/chat/openai] Empty completion');
|
|
70
|
-
}
|
|
71
|
-
const usage = {
|
|
72
|
-
tokensIn: json.usage?.prompt_tokens ?? null,
|
|
73
|
-
tokensOut: json.usage?.completion_tokens ?? null,
|
|
74
|
-
};
|
|
75
|
-
return {
|
|
76
|
-
output: text,
|
|
77
|
-
provider: 'openai',
|
|
78
|
-
model: request.model,
|
|
79
|
-
usage,
|
|
80
|
-
cost: stampCost(usage, request.pricing),
|
|
81
|
-
latencyMs: Date.now() - startedAt,
|
|
82
|
-
finishReason: mapFinishReason(json.choices?.[0]?.finish_reason ?? null),
|
|
83
|
-
};
|
|
84
91
|
}
|
|
85
92
|
function mapFinishReason(raw) {
|
|
86
93
|
if (!raw)
|
package/dist/chat/openai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/chat/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAqB3C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAA2B,EAC3B,OAA8B;IAE9B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,wBAAwB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjF,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC;IAElE,wEAAwE;IACxE,MAAM,IAAI,GAA4B;QACpC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAc,EAAE,EAAE,CAAC,CAAC;YAClD,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB,CAAC,CAAC;QACH,qBAAqB,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;KACjD,CAAC;IACF,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS;QAAE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAC9E,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1D,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/chat/openai.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,5 +9,7 @@ export type { LlmCostInput, LlmCostEvent, LlmCostRollup, Querier, } from './type
|
|
|
9
9
|
export { recordLlmCost } from './record.js';
|
|
10
10
|
export { listLlmCosts, costByTarget, costByCorrelation, costByTenant, costByMonth, } from './query.js';
|
|
11
11
|
export type { ListLlmCostsArgs, CostByTargetArgs, CostByCorrelationArgs, CostByTenantArgs, CostByMonthArgs, MonthlyCostBucket, } from './query.js';
|
|
12
|
+
export { computeWindows, rollupCostWindow } from './windows.js';
|
|
13
|
+
export type { CostWindowKey, CostWindowRange, CostTotals, CostByModelRow, CostBySourceRow, CostRollupResult, RollupCostWindowArgs, } from './windows.js';
|
|
12
14
|
export { getAiCostMigrationsDir } from './path.js';
|
|
13
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cost-ledger/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,OAAO,GACR,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cost-ledger/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,OAAO,GACR,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhE,YAAY,EACV,aAAa,EACb,eAAe,EACf,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { recordLlmCost } from './record.js';
|
|
9
9
|
export { listLlmCosts, costByTarget, costByCorrelation, costByTenant, costByMonth, } from './query.js';
|
|
10
|
+
export { computeWindows, rollupCostWindow } from './windows.js';
|
|
10
11
|
export { getAiCostMigrationsDir } from './path.js';
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cost-ledger/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;AAWpB,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cost-ledger/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC;AAWpB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAYhE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
18
18
|
|
|
19
|
-
CREATE TABLE IF NOT EXISTS
|
|
19
|
+
CREATE TABLE IF NOT EXISTS vk_llm_cost_events (
|
|
20
20
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
21
21
|
-- Nullable to match the audit-events convention; platform-wide
|
|
22
22
|
-- LLM calls (e.g. eval / smoke tests) carry tenant_id = NULL.
|
|
@@ -56,19 +56,19 @@ CREATE TABLE IF NOT EXISTS llm_cost_events (
|
|
|
56
56
|
-- ─── Indexes ────────────────────────────────────────────────────────
|
|
57
57
|
|
|
58
58
|
CREATE INDEX IF NOT EXISTS llm_cost_events_tenant_created_idx
|
|
59
|
-
ON
|
|
59
|
+
ON vk_llm_cost_events (tenant_id, created_at DESC);
|
|
60
60
|
|
|
61
61
|
CREATE INDEX IF NOT EXISTS llm_cost_events_target_idx
|
|
62
|
-
ON
|
|
62
|
+
ON vk_llm_cost_events (tenant_id, target_type, target_id, created_at DESC);
|
|
63
63
|
|
|
64
64
|
CREATE INDEX IF NOT EXISTS llm_cost_events_correlation_idx
|
|
65
|
-
ON
|
|
65
|
+
ON vk_llm_cost_events (correlation_id, created_at DESC)
|
|
66
66
|
WHERE correlation_id IS NOT NULL;
|
|
67
67
|
|
|
68
68
|
CREATE INDEX IF NOT EXISTS llm_cost_events_tenant_provider_idx
|
|
69
|
-
ON
|
|
69
|
+
ON vk_llm_cost_events (tenant_id, provider, created_at DESC);
|
|
70
70
|
|
|
71
71
|
-- Idempotency dedup. Partial because most rows have NULL key.
|
|
72
72
|
CREATE UNIQUE INDEX IF NOT EXISTS llm_cost_events_idempotency_unique
|
|
73
|
-
ON
|
|
73
|
+
ON vk_llm_cost_events (idempotency_key)
|
|
74
74
|
WHERE idempotency_key IS NOT NULL;
|
|
@@ -51,7 +51,7 @@ CREATE TYPE llm_model_status AS ENUM (
|
|
|
51
51
|
|
|
52
52
|
-- ─── llm_models ────────────────────────────────────────────────────
|
|
53
53
|
|
|
54
|
-
CREATE TABLE IF NOT EXISTS
|
|
54
|
+
CREATE TABLE IF NOT EXISTS vk_llm_models (
|
|
55
55
|
-- Stable catalog id. App-side rows (e.g. `editorial_personas.model`
|
|
56
56
|
-- in the CMS) store THIS, not the vendor's wire id. Lets vendors
|
|
57
57
|
-- rename / replace a SKU without re-stamping every consumer.
|
|
@@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS llm_models (
|
|
|
69
69
|
-- When status='deprecated', the suggested replacement `key`. Used
|
|
70
70
|
-- by the CMS's persona picker to surface "Use <replacement>
|
|
71
71
|
-- instead" nudges (§4.10).
|
|
72
|
-
replacement_key varchar(64) REFERENCES
|
|
72
|
+
replacement_key varchar(64) REFERENCES vk_llm_models(key) ON DELETE SET NULL,
|
|
73
73
|
|
|
74
74
|
-- ─── Pricing (USD per 1K tokens) ────────────────────────────────
|
|
75
75
|
-- Nullable for experimental / on-prem models with no published
|
|
@@ -116,9 +116,9 @@ CREATE TABLE IF NOT EXISTS llm_models (
|
|
|
116
116
|
);
|
|
117
117
|
|
|
118
118
|
CREATE INDEX IF NOT EXISTS llm_models_status_idx
|
|
119
|
-
ON
|
|
119
|
+
ON vk_llm_models (status);
|
|
120
120
|
CREATE INDEX IF NOT EXISTS llm_models_provider_idx
|
|
121
|
-
ON
|
|
121
|
+
ON vk_llm_models (provider);
|
|
122
122
|
|
|
123
123
|
-- ─── No seed ───────────────────────────────────────────────────────
|
|
124
124
|
-- Intentionally empty. See file header for the rationale: catalog
|
|
@@ -21,13 +21,13 @@ async function fetchTotals(querier, whereClause, params) {
|
|
|
21
21
|
COUNT(*) AS "eventCount",
|
|
22
22
|
COALESCE(SUM(tokens_in), 0) AS "totalTokensIn",
|
|
23
23
|
COALESCE(SUM(tokens_out), 0) AS "totalTokensOut"
|
|
24
|
-
FROM
|
|
24
|
+
FROM vk_llm_cost_events
|
|
25
25
|
WHERE ${whereClause}`, params);
|
|
26
26
|
return rows[0];
|
|
27
27
|
}
|
|
28
28
|
async function fetchProviderSplit(querier, whereClause, params) {
|
|
29
29
|
const rows = await querier(`SELECT provider, COALESCE(SUM(cost_usd), 0) AS "costUsd"
|
|
30
|
-
FROM
|
|
30
|
+
FROM vk_llm_cost_events
|
|
31
31
|
WHERE ${whereClause}
|
|
32
32
|
GROUP BY provider`, params);
|
|
33
33
|
const out = {};
|
|
@@ -82,7 +82,7 @@ export async function listLlmCosts(querier, args) {
|
|
|
82
82
|
}
|
|
83
83
|
const limit = Math.min(Math.max(args.limit ?? 50, 1), 500);
|
|
84
84
|
params.push(limit);
|
|
85
|
-
return querier(`SELECT * FROM
|
|
85
|
+
return querier(`SELECT * FROM vk_llm_cost_events
|
|
86
86
|
WHERE ${conditions.join(' AND ')}
|
|
87
87
|
ORDER BY created_at DESC, id DESC
|
|
88
88
|
LIMIT $${params.length}`, params);
|
|
@@ -122,7 +122,7 @@ export async function costByMonth(querier, args) {
|
|
|
122
122
|
COALESCE(SUM(c.tokens_in), 0) AS "totalTokensIn",
|
|
123
123
|
COALESCE(SUM(c.tokens_out), 0) AS "totalTokensOut"
|
|
124
124
|
FROM months
|
|
125
|
-
LEFT JOIN
|
|
125
|
+
LEFT JOIN vk_llm_cost_events c
|
|
126
126
|
ON c.tenant_id = $1
|
|
127
127
|
AND c.created_at >= months.month_start
|
|
128
128
|
AND c.created_at < months.month_start + interval '1 month'
|
|
@@ -25,7 +25,7 @@ export async function recordLlmCost(querier, input) {
|
|
|
25
25
|
}
|
|
26
26
|
const payload = sanitizePayload(input.payload);
|
|
27
27
|
const sql = `
|
|
28
|
-
INSERT INTO
|
|
28
|
+
INSERT INTO vk_llm_cost_events (
|
|
29
29
|
tenant_id,
|
|
30
30
|
source,
|
|
31
31
|
target_type,
|
|
@@ -74,7 +74,7 @@ export async function recordLlmCost(querier, input) {
|
|
|
74
74
|
if (input.idempotencyKey &&
|
|
75
75
|
isUniqueViolation(err) &&
|
|
76
76
|
isIdempotencyConstraint(err)) {
|
|
77
|
-
const existing = await querier(`SELECT * FROM
|
|
77
|
+
const existing = await querier(`SELECT * FROM vk_llm_cost_events WHERE idempotency_key = $1 LIMIT 1`, [input.idempotencyKey]);
|
|
78
78
|
if (existing[0])
|
|
79
79
|
return existing[0];
|
|
80
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/cost-ledger/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAgB,EAChB,KAAmB;IAEnB,iEAAiE;IACjE,6DAA6D;IAC7D,oBAAoB;IACpB,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1D,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACpE,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAE/C,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;GAqBX,CAAC;IAEF,MAAM,MAAM,GAAc;QACxB,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,MAAM;QACZ,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI;QAC1B,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,IAAI;QACxB,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,KAAK;QACX,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,YAAY;QAClB,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,aAAa,IAAI,IAAI;QAC3B,KAAK,CAAC,cAAc,IAAI,IAAI;QAC5B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;KACxB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAiB,GAAG,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IACE,KAAK,CAAC,cAAc;YACpB,iBAAiB,CAAC,GAAG,CAAC;YACtB,uBAAuB,CAAC,GAAG,CAAC,EAC5B,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAC5B,
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/cost-ledger/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAgB,EAChB,KAAmB;IAEnB,iEAAiE;IACjE,6DAA6D;IAC7D,oBAAoB;IACpB,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1D,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACpE,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAE/C,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;GAqBX,CAAC;IAEF,MAAM,MAAM,GAAc;QACxB,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,MAAM;QACZ,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI;QAC1B,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,IAAI;QACxB,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,KAAK;QACX,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,YAAY;QAClB,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,aAAa,IAAI,IAAI;QAC3B,KAAK,CAAC,cAAc,IAAI,IAAI;QAC5B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;KACxB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAiB,GAAG,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IACE,KAAK,CAAC,cAAc;YACpB,iBAAiB,CAAC,GAAG,CAAC;YACtB,uBAAuB,CAAC,GAAG,CAAC,EAC5B,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAC5B,qEAAqE,EACrE,CAAC,KAAK,CAAC,cAAc,CAAC,CACvB,CAAC;YACF,IAAI,QAAQ,CAAC,CAAC,CAAC;gBAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,0EAA0E;AAE1E,SAAS,eAAe,CAAC,OAAgB;IACvC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACzD,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC3D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAY;IACrC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClD,OAAQ,GAAyB,CAAC,IAAI,KAAK,OAAO,CAAC;AACrD,CAAC;AAED,SAAS,uBAAuB,CAAC,GAAY;IAC3C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,CAAC,GAAG,GAA+C,CAAC;IAC1D,IAAI,CAAC,CAAC,UAAU,KAAK,oCAAoC;QAAE,OAAO,IAAI,CAAC;IACvE,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Window-based cost rollups over `vk_llm_cost_events`.
|
|
3
|
+
*
|
|
4
|
+
* Generic, app-agnostic aggregation for the "cost dashboard" use-case:
|
|
5
|
+
* pick a trailing window (`7d` / `30d` / `90d` / `ytd`), get KPI totals
|
|
6
|
+
* + a per-model and per-source breakdown, plus the same totals for the
|
|
7
|
+
* immediately-prior window so the page can render a period-over-period
|
|
8
|
+
* delta.
|
|
9
|
+
*
|
|
10
|
+
* Distinct from the `query.ts` rollups (which split by `provider` and
|
|
11
|
+
* return `LlmCostRollup`): this module is keyed on the canonical
|
|
12
|
+
* `cost_micro_usd` (bigint — safer to SUM than the `cost_usd` numeric)
|
|
13
|
+
* and surfaces both micro-USD (client math) and USD (display).
|
|
14
|
+
*
|
|
15
|
+
* App-specific rollups that JOIN the consumer's own tables (e.g. the
|
|
16
|
+
* CMS's per-blog-post rollup over `editorial_runs`) stay in the app —
|
|
17
|
+
* this module only covers the table the package owns.
|
|
18
|
+
*/
|
|
19
|
+
import type { Querier } from './types.js';
|
|
20
|
+
export type CostWindowKey = '7d' | '30d' | '90d' | 'ytd';
|
|
21
|
+
export interface CostWindowRange {
|
|
22
|
+
/** Inclusive start (ISO). */
|
|
23
|
+
startedAt: string;
|
|
24
|
+
/** Exclusive end (ISO) — typically `now`. */
|
|
25
|
+
endedAt: string;
|
|
26
|
+
}
|
|
27
|
+
export interface CostTotals {
|
|
28
|
+
costMicroUsd: number;
|
|
29
|
+
costUsd: number;
|
|
30
|
+
calls: number;
|
|
31
|
+
tokensIn: number;
|
|
32
|
+
tokensOut: number;
|
|
33
|
+
}
|
|
34
|
+
export interface CostByModelRow {
|
|
35
|
+
provider: string;
|
|
36
|
+
model: string;
|
|
37
|
+
calls: number;
|
|
38
|
+
costMicroUsd: number;
|
|
39
|
+
costUsd: number;
|
|
40
|
+
tokensIn: number;
|
|
41
|
+
tokensOut: number;
|
|
42
|
+
}
|
|
43
|
+
export interface CostBySourceRow {
|
|
44
|
+
source: string;
|
|
45
|
+
calls: number;
|
|
46
|
+
costMicroUsd: number;
|
|
47
|
+
costUsd: number;
|
|
48
|
+
}
|
|
49
|
+
export interface CostRollupResult {
|
|
50
|
+
window: CostWindowRange;
|
|
51
|
+
prevWindow: CostWindowRange;
|
|
52
|
+
totals: CostTotals;
|
|
53
|
+
prevTotals: CostTotals;
|
|
54
|
+
byModel: CostByModelRow[];
|
|
55
|
+
bySource: CostBySourceRow[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve `(current, previous)` ranges for the given key. `now` is
|
|
59
|
+
* threaded through so callers / tests can pin the clock.
|
|
60
|
+
*
|
|
61
|
+
* The `ytd` prior window is the same span last year, ending at "today"
|
|
62
|
+
* one year ago. Fixed windows (`7d`/`30d`/`90d`) use the immediately
|
|
63
|
+
* preceding span of equal length.
|
|
64
|
+
*/
|
|
65
|
+
export declare function computeWindows(key: CostWindowKey, now?: Date): {
|
|
66
|
+
current: CostWindowRange;
|
|
67
|
+
previous: CostWindowRange;
|
|
68
|
+
};
|
|
69
|
+
export interface RollupCostWindowArgs {
|
|
70
|
+
tenantId: string;
|
|
71
|
+
windowKey: CostWindowKey;
|
|
72
|
+
/** Pin the clock (tests / replay). Defaults to `new Date()`. */
|
|
73
|
+
now?: Date;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Roll up costs for one tenant over the given window. Issues four
|
|
77
|
+
* parallel queries (totals, prev totals, byModel, bySource) and combines
|
|
78
|
+
* them into a single dashboard DTO. Empty windows return zeros (not
|
|
79
|
+
* NULLs) so callers can do delta math without null-guarding.
|
|
80
|
+
*/
|
|
81
|
+
export declare function rollupCostWindow(querier: Querier, args: RollupCostWindowArgs): Promise<CostRollupResult>;
|
|
82
|
+
//# sourceMappingURL=windows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../../src/cost-ledger/windows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,eAAe,CAAC;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,aAAa,EAClB,GAAG,GAAE,IAAiB,GACrB;IAAE,OAAO,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAA;CAAE,CAoCzD;AAwHD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,aAAa,CAAC;IACzB,gEAAgE;IAChE,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAgB3B"}
|