cachegate 1.2.0 → 1.3.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.
@@ -1,114 +1,114 @@
1
- // model-router/providers/openai.js
2
- const { OpenAI } = require('openai');
3
-
4
- function buildClient(apiKey) {
5
- return new OpenAI({ apiKey });
6
- }
7
-
8
- function estimateCost(model, inputTokens, outputTokens) {
9
- // Approximate pricing per 1M tokens
10
- const rates = {
11
- 'gpt-4o-mini': { input: 0.15, output: 0.6 },
12
- 'gpt-4o': { input: 2.5, output: 10.0 }
13
- };
14
- const rate = rates[model] || { input: 2.5, output: 10.0 };
15
- return ((inputTokens * rate.input) + (outputTokens * rate.output)) / 1_000_000;
16
- }
17
-
18
- async function chat(client, payload) {
19
- const request = {
20
- model: payload.model,
21
- messages: payload.messages,
22
- temperature: typeof payload.temperature === 'number' ? payload.temperature : 0.0,
23
- max_tokens: payload.max_tokens || 1024,
24
- ...(payload.tools && { tools: payload.tools }),
25
- ...(payload.tool_choice && { tool_choice: payload.tool_choice }),
26
- ...(payload.response_format && { response_format: payload.response_format })
27
- };
28
-
29
- const start = Date.now();
30
- const response = await client.chat.completions.create(request);
31
- const latencyMs = Date.now() - start;
32
-
33
- const choice = response.choices[0];
34
- const inputTokens = response.usage.prompt_tokens;
35
- const outputTokens = response.usage.completion_tokens;
36
- const costUsd = estimateCost(payload.model, inputTokens, outputTokens);
37
-
38
- return {
39
- provider: 'openai',
40
- model: payload.model,
41
- latency_ms: latencyMs,
42
- usage: { input_tokens: inputTokens, output_tokens: outputTokens },
43
- cost_usd: costUsd,
44
- content: choice.message.content || '',
45
- tool_calls: choice.message.tool_calls,
46
- raw: response
47
- };
48
- }
49
-
50
- /**
51
- * Pure state-accumulation for one OpenAI streaming chunk - factored out
52
- * from chatStream() so the usage/cost extraction is directly
53
- * unit-testable with canned chunks, no live API needed. Mutates `state`
54
- * ({content, inputTokens, outputTokens}) and calls onDelta() with each
55
- * new piece of assistant text.
56
- *
57
- * OpenAI only includes `usage` on a final, choice-less chunk, and only
58
- * when the request explicitly asked for it (`stream_options:
59
- * {include_usage: true}`, set in chatStream() below) - without that
60
- * flag a streamed OpenAI response has NO usage data at all, which would
61
- * silently make cost_usd wrong (stuck at 0) for every streamed OpenAI
62
- * call. Requesting it explicitly is required, not optional, for the
63
- * cost tracking this whole project is built around to stay honest.
64
- */
65
- function applyStreamChunk(state, chunk, onDelta) {
66
- const choice = chunk.choices && chunk.choices[0];
67
- if (choice && choice.delta && choice.delta.content) {
68
- state.content += choice.delta.content;
69
- onDelta(choice.delta.content);
70
- }
71
- if (chunk.usage) {
72
- state.inputTokens = chunk.usage.prompt_tokens;
73
- state.outputTokens = chunk.usage.completion_tokens;
74
- }
75
- }
76
-
77
- /**
78
- * Streaming counterpart to chat(). Scope: plain text content only - no
79
- * tools/tool_choice forwarded (server.js rejects stream:true + tools
80
- * before this is ever called; see streaming.js for why).
81
- */
82
- async function chatStream(client, payload, { onDelta, signal } = {}) {
83
- const request = {
84
- model: payload.model,
85
- messages: payload.messages,
86
- temperature: typeof payload.temperature === 'number' ? payload.temperature : 0.0,
87
- max_tokens: payload.max_tokens || 1024,
88
- stream: true,
89
- stream_options: { include_usage: true }
90
- };
91
-
92
- const start = Date.now();
93
- const stream = await client.chat.completions.create(request, signal ? { signal } : undefined);
94
-
95
- const state = { content: '', inputTokens: 0, outputTokens: 0 };
96
- for await (const chunk of stream) {
97
- applyStreamChunk(state, chunk, onDelta || (() => {}));
98
- }
99
-
100
- const latencyMs = Date.now() - start;
101
- const costUsd = estimateCost(payload.model, state.inputTokens, state.outputTokens);
102
-
103
- return {
104
- provider: 'openai',
105
- model: payload.model,
106
- latency_ms: latencyMs,
107
- usage: { input_tokens: state.inputTokens, output_tokens: state.outputTokens },
108
- cost_usd: costUsd,
109
- content: state.content,
110
- tool_calls: undefined
111
- };
112
- }
113
-
114
- module.exports = { buildClient, chat, chatStream, applyStreamChunk, estimateCost };
1
+ // model-router/providers/openai.js
2
+ const { OpenAI } = require('openai');
3
+
4
+ function buildClient(apiKey) {
5
+ return new OpenAI({ apiKey });
6
+ }
7
+
8
+ function estimateCost(model, inputTokens, outputTokens) {
9
+ // Approximate pricing per 1M tokens
10
+ const rates = {
11
+ 'gpt-4o-mini': { input: 0.15, output: 0.6 },
12
+ 'gpt-4o': { input: 2.5, output: 10.0 }
13
+ };
14
+ const rate = rates[model] || { input: 2.5, output: 10.0 };
15
+ return ((inputTokens * rate.input) + (outputTokens * rate.output)) / 1_000_000;
16
+ }
17
+
18
+ async function chat(client, payload) {
19
+ const request = {
20
+ model: payload.model,
21
+ messages: payload.messages,
22
+ temperature: typeof payload.temperature === 'number' ? payload.temperature : 0.0,
23
+ max_tokens: payload.max_tokens || 1024,
24
+ ...(payload.tools && { tools: payload.tools }),
25
+ ...(payload.tool_choice && { tool_choice: payload.tool_choice }),
26
+ ...(payload.response_format && { response_format: payload.response_format })
27
+ };
28
+
29
+ const start = Date.now();
30
+ const response = await client.chat.completions.create(request);
31
+ const latencyMs = Date.now() - start;
32
+
33
+ const choice = response.choices[0];
34
+ const inputTokens = response.usage.prompt_tokens;
35
+ const outputTokens = response.usage.completion_tokens;
36
+ const costUsd = estimateCost(payload.model, inputTokens, outputTokens);
37
+
38
+ return {
39
+ provider: 'openai',
40
+ model: payload.model,
41
+ latency_ms: latencyMs,
42
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens },
43
+ cost_usd: costUsd,
44
+ content: choice.message.content || '',
45
+ tool_calls: choice.message.tool_calls,
46
+ raw: response
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Pure state-accumulation for one OpenAI streaming chunk - factored out
52
+ * from chatStream() so the usage/cost extraction is directly
53
+ * unit-testable with canned chunks, no live API needed. Mutates `state`
54
+ * ({content, inputTokens, outputTokens}) and calls onDelta() with each
55
+ * new piece of assistant text.
56
+ *
57
+ * OpenAI only includes `usage` on a final, choice-less chunk, and only
58
+ * when the request explicitly asked for it (`stream_options:
59
+ * {include_usage: true}`, set in chatStream() below) - without that
60
+ * flag a streamed OpenAI response has NO usage data at all, which would
61
+ * silently make cost_usd wrong (stuck at 0) for every streamed OpenAI
62
+ * call. Requesting it explicitly is required, not optional, for the
63
+ * cost tracking this whole project is built around to stay honest.
64
+ */
65
+ function applyStreamChunk(state, chunk, onDelta) {
66
+ const choice = chunk.choices && chunk.choices[0];
67
+ if (choice && choice.delta && choice.delta.content) {
68
+ state.content += choice.delta.content;
69
+ onDelta(choice.delta.content);
70
+ }
71
+ if (chunk.usage) {
72
+ state.inputTokens = chunk.usage.prompt_tokens;
73
+ state.outputTokens = chunk.usage.completion_tokens;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Streaming counterpart to chat(). Scope: plain text content only - no
79
+ * tools/tool_choice forwarded (server.js rejects stream:true + tools
80
+ * before this is ever called; see streaming.js for why).
81
+ */
82
+ async function chatStream(client, payload, { onDelta, signal } = {}) {
83
+ const request = {
84
+ model: payload.model,
85
+ messages: payload.messages,
86
+ temperature: typeof payload.temperature === 'number' ? payload.temperature : 0.0,
87
+ max_tokens: payload.max_tokens || 1024,
88
+ stream: true,
89
+ stream_options: { include_usage: true }
90
+ };
91
+
92
+ const start = Date.now();
93
+ const stream = await client.chat.completions.create(request, signal ? { signal } : undefined);
94
+
95
+ const state = { content: '', inputTokens: 0, outputTokens: 0 };
96
+ for await (const chunk of stream) {
97
+ applyStreamChunk(state, chunk, onDelta || (() => {}));
98
+ }
99
+
100
+ const latencyMs = Date.now() - start;
101
+ const costUsd = estimateCost(payload.model, state.inputTokens, state.outputTokens);
102
+
103
+ return {
104
+ provider: 'openai',
105
+ model: payload.model,
106
+ latency_ms: latencyMs,
107
+ usage: { input_tokens: state.inputTokens, output_tokens: state.outputTokens },
108
+ cost_usd: costUsd,
109
+ content: state.content,
110
+ tool_calls: undefined
111
+ };
112
+ }
113
+
114
+ module.exports = { buildClient, chat, chatStream, applyStreamChunk, estimateCost };