@codehz/ai 0.1.5 → 0.1.6
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/dist/index.d.mts +51 -7
- package/dist/index.mjs +95 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/chat-completions.ts +9 -10
- package/src/adapters/messages.ts +8 -10
- package/src/adapters/ollama.ts +5 -8
- package/src/adapters/responses.ts +2 -9
- package/src/helpers/index.ts +6 -0
- package/src/helpers/usage-mapping.ts +150 -0
- package/src/types/response.ts +7 -0
package/package.json
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
contentBlocksToText,
|
|
22
22
|
} from "../helpers/mapping.js";
|
|
23
23
|
import { emitMalformedStreamWarning } from "../helpers/adapter-auxiliary.js";
|
|
24
|
+
import { usageFromChatCompletions } from "../helpers/usage-mapping.js";
|
|
24
25
|
|
|
25
26
|
import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
|
|
26
27
|
|
|
@@ -73,7 +74,13 @@ type ChatChunk = {
|
|
|
73
74
|
created: number;
|
|
74
75
|
model: string;
|
|
75
76
|
choices: ChatChunkChoice[];
|
|
76
|
-
usage?: {
|
|
77
|
+
usage?: {
|
|
78
|
+
prompt_tokens: number;
|
|
79
|
+
completion_tokens: number;
|
|
80
|
+
total_tokens: number;
|
|
81
|
+
prompt_tokens_details?: { cached_tokens?: number };
|
|
82
|
+
completion_tokens_details?: { reasoning_tokens?: number };
|
|
83
|
+
};
|
|
77
84
|
};
|
|
78
85
|
|
|
79
86
|
type ChatChunkChoice = {
|
|
@@ -485,15 +492,7 @@ export class ChatCompletionsAdapter extends AdapterBase {
|
|
|
485
492
|
|
|
486
493
|
// usage 可能在最终 chunk 中
|
|
487
494
|
if (chunk.usage) {
|
|
488
|
-
auxiliary.recordUsage(
|
|
489
|
-
{
|
|
490
|
-
inputTokens: chunk.usage.prompt_tokens,
|
|
491
|
-
outputTokens: chunk.usage.completion_tokens,
|
|
492
|
-
totalTokens: chunk.usage.total_tokens,
|
|
493
|
-
},
|
|
494
|
-
"final",
|
|
495
|
-
chunk.usage,
|
|
496
|
-
);
|
|
495
|
+
auxiliary.recordUsage(usageFromChatCompletions(chunk.usage), "final", chunk.usage);
|
|
497
496
|
}
|
|
498
497
|
|
|
499
498
|
for (const choice of chunk.choices) {
|
package/src/adapters/messages.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
contentBlocksToText,
|
|
25
25
|
} from "../helpers/mapping.js";
|
|
26
26
|
import { emitMalformedStreamWarning } from "../helpers/adapter-auxiliary.js";
|
|
27
|
+
import { usageFromAnthropicMessages } from "../helpers/usage-mapping.js";
|
|
27
28
|
|
|
28
29
|
import { parseSSEEvents } from "../helpers/sse-parser.js";
|
|
29
30
|
|
|
@@ -131,7 +132,12 @@ type MessagesSSEEvent =
|
|
|
131
132
|
type: "message_delta";
|
|
132
133
|
data: {
|
|
133
134
|
delta: { stop_reason?: string; stop_sequence?: string | null };
|
|
134
|
-
usage: {
|
|
135
|
+
usage: {
|
|
136
|
+
input_tokens: number;
|
|
137
|
+
output_tokens: number;
|
|
138
|
+
cache_creation_input_tokens?: number;
|
|
139
|
+
cache_read_input_tokens?: number;
|
|
140
|
+
};
|
|
135
141
|
};
|
|
136
142
|
}
|
|
137
143
|
| { type: "message_stop"; data: Record<string, never> }
|
|
@@ -616,15 +622,7 @@ export class MessagesAdapter extends AdapterBase {
|
|
|
616
622
|
stopSequence = sseEvent.data.delta.stop_sequence;
|
|
617
623
|
const u = sseEvent.data.usage;
|
|
618
624
|
if (u) {
|
|
619
|
-
auxiliary.recordUsage(
|
|
620
|
-
{
|
|
621
|
-
inputTokens: u.input_tokens,
|
|
622
|
-
outputTokens: u.output_tokens,
|
|
623
|
-
totalTokens: u.input_tokens + u.output_tokens,
|
|
624
|
-
},
|
|
625
|
-
"stream",
|
|
626
|
-
u,
|
|
627
|
-
);
|
|
625
|
+
auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
|
|
628
626
|
}
|
|
629
627
|
continue;
|
|
630
628
|
}
|
package/src/adapters/ollama.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
contentBlocksToText,
|
|
28
28
|
} from "../helpers/mapping.js";
|
|
29
29
|
import { emitMalformedStreamWarning } from "../helpers/adapter-auxiliary.js";
|
|
30
|
+
import { usageFromOllama } from "../helpers/usage-mapping.js";
|
|
30
31
|
|
|
31
32
|
import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
|
|
32
33
|
|
|
@@ -478,14 +479,10 @@ export class OllamaAdapter extends AdapterBase {
|
|
|
478
479
|
(chunk.prompt_eval_count !== undefined || chunk.eval_count !== undefined)
|
|
479
480
|
) {
|
|
480
481
|
auxiliary.recordUsage(
|
|
481
|
-
{
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
chunk.prompt_eval_count !== undefined && chunk.eval_count !== undefined
|
|
486
|
-
? chunk.prompt_eval_count + chunk.eval_count
|
|
487
|
-
: undefined,
|
|
488
|
-
},
|
|
482
|
+
usageFromOllama({
|
|
483
|
+
prompt_eval_count: chunk.prompt_eval_count,
|
|
484
|
+
eval_count: chunk.eval_count,
|
|
485
|
+
}),
|
|
489
486
|
"final",
|
|
490
487
|
{
|
|
491
488
|
prompt_eval_count: chunk.prompt_eval_count,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
contentBlocksToText,
|
|
23
23
|
} from "../helpers/mapping.js";
|
|
24
24
|
import { emitMalformedStreamWarning } from "../helpers/adapter-auxiliary.js";
|
|
25
|
+
import { usageFromOpenAIResponses } from "../helpers/usage-mapping.js";
|
|
25
26
|
|
|
26
27
|
import { parseSSEEvents } from "../helpers/sse-parser.js";
|
|
27
28
|
|
|
@@ -448,15 +449,7 @@ export class ResponsesAdapter extends AdapterBase {
|
|
|
448
449
|
if (completedResponse) {
|
|
449
450
|
rawResponseId = completedResponse.id;
|
|
450
451
|
if (completedResponse.usage) {
|
|
451
|
-
auxiliary.recordUsage(
|
|
452
|
-
{
|
|
453
|
-
inputTokens: completedResponse.usage.input_tokens,
|
|
454
|
-
outputTokens: completedResponse.usage.output_tokens,
|
|
455
|
-
totalTokens: completedResponse.usage.total_tokens,
|
|
456
|
-
},
|
|
457
|
-
"final",
|
|
458
|
-
completedResponse.usage,
|
|
459
|
-
);
|
|
452
|
+
auxiliary.recordUsage(usageFromOpenAIResponses(completedResponse.usage), "final", completedResponse.usage);
|
|
460
453
|
}
|
|
461
454
|
}
|
|
462
455
|
|
package/src/helpers/index.ts
CHANGED
|
@@ -34,3 +34,9 @@ export { syntheticStream } from "./synthetic-stream.js";
|
|
|
34
34
|
export type { SyntheticStreamOptions } from "./synthetic-stream.js";
|
|
35
35
|
export { AuxiliaryCollector } from "./auxiliary-collector.js";
|
|
36
36
|
export type { UsageSource, BillingSource, LookupResult } from "./auxiliary-collector.js";
|
|
37
|
+
export {
|
|
38
|
+
usageFromAnthropicMessages,
|
|
39
|
+
usageFromChatCompletions,
|
|
40
|
+
usageFromOllama,
|
|
41
|
+
usageFromOpenAIResponses,
|
|
42
|
+
} from "./usage-mapping.js";
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider usage → canonical Usage 映射
|
|
3
|
+
*
|
|
4
|
+
* best-effort 提取 reasoning / cache / billable 等扩展字段。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Usage } from "../types/index.js";
|
|
8
|
+
|
|
9
|
+
function num(value: unknown): number | undefined {
|
|
10
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function record(obj: Record<string, number | undefined>): Partial<Usage> {
|
|
14
|
+
const out: Partial<Usage> = {};
|
|
15
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
16
|
+
if (value !== undefined) {
|
|
17
|
+
(out as Record<string, number>)[key] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function billableFromOpenAIStyle(
|
|
24
|
+
inputTokens: number | undefined,
|
|
25
|
+
outputTokens: number | undefined,
|
|
26
|
+
cachedInputTokens: number | undefined,
|
|
27
|
+
reasoningTokens: number | undefined,
|
|
28
|
+
): Pick<Usage, "billableInputTokens" | "billableOutputTokens"> {
|
|
29
|
+
let billableInputTokens: number | undefined;
|
|
30
|
+
if (inputTokens !== undefined) {
|
|
31
|
+
billableInputTokens =
|
|
32
|
+
cachedInputTokens !== undefined ? Math.max(0, inputTokens - cachedInputTokens) : inputTokens;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let billableOutputTokens: number | undefined;
|
|
36
|
+
if (outputTokens !== undefined) {
|
|
37
|
+
billableOutputTokens =
|
|
38
|
+
reasoningTokens !== undefined ? Math.max(0, outputTokens - reasoningTokens) : outputTokens;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return record({ billableInputTokens, billableOutputTokens });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** OpenAI Chat Completions `usage` */
|
|
45
|
+
export function usageFromChatCompletions(raw: {
|
|
46
|
+
prompt_tokens?: number;
|
|
47
|
+
completion_tokens?: number;
|
|
48
|
+
total_tokens?: number;
|
|
49
|
+
prompt_tokens_details?: { cached_tokens?: number; [key: string]: unknown };
|
|
50
|
+
completion_tokens_details?: { reasoning_tokens?: number; [key: string]: unknown };
|
|
51
|
+
}): Partial<Usage> {
|
|
52
|
+
const inputTokens = num(raw.prompt_tokens);
|
|
53
|
+
const outputTokens = num(raw.completion_tokens);
|
|
54
|
+
const cachedInputTokens = num(raw.prompt_tokens_details?.cached_tokens);
|
|
55
|
+
const reasoningTokens = num(raw.completion_tokens_details?.reasoning_tokens);
|
|
56
|
+
const totalTokens =
|
|
57
|
+
num(raw.total_tokens) ??
|
|
58
|
+
(inputTokens !== undefined && outputTokens !== undefined ? inputTokens + outputTokens : undefined);
|
|
59
|
+
|
|
60
|
+
return record({
|
|
61
|
+
inputTokens,
|
|
62
|
+
outputTokens,
|
|
63
|
+
totalTokens,
|
|
64
|
+
cachedInputTokens,
|
|
65
|
+
reasoningTokens,
|
|
66
|
+
...billableFromOpenAIStyle(inputTokens, outputTokens, cachedInputTokens, reasoningTokens),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** OpenAI Responses API `usage` */
|
|
71
|
+
export function usageFromOpenAIResponses(raw: {
|
|
72
|
+
input_tokens?: number;
|
|
73
|
+
output_tokens?: number;
|
|
74
|
+
total_tokens?: number;
|
|
75
|
+
input_tokens_details?: { cached_tokens?: number; [key: string]: unknown };
|
|
76
|
+
output_tokens_details?: { reasoning_tokens?: number; [key: string]: unknown };
|
|
77
|
+
[key: string]: unknown;
|
|
78
|
+
}): Partial<Usage> {
|
|
79
|
+
const inputTokens = num(raw.input_tokens);
|
|
80
|
+
const outputTokens = num(raw.output_tokens);
|
|
81
|
+
const cachedInputTokens = num(raw.input_tokens_details?.cached_tokens);
|
|
82
|
+
const reasoningTokens = num(raw.output_tokens_details?.reasoning_tokens);
|
|
83
|
+
const totalTokens =
|
|
84
|
+
num(raw.total_tokens) ??
|
|
85
|
+
(inputTokens !== undefined && outputTokens !== undefined ? inputTokens + outputTokens : undefined);
|
|
86
|
+
|
|
87
|
+
return record({
|
|
88
|
+
inputTokens,
|
|
89
|
+
outputTokens,
|
|
90
|
+
totalTokens,
|
|
91
|
+
cachedInputTokens,
|
|
92
|
+
reasoningTokens,
|
|
93
|
+
...billableFromOpenAIStyle(inputTokens, outputTokens, cachedInputTokens, reasoningTokens),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Anthropic Messages `usage`(message_start / message_delta) */
|
|
98
|
+
export function usageFromAnthropicMessages(raw: {
|
|
99
|
+
input_tokens?: number;
|
|
100
|
+
output_tokens?: number;
|
|
101
|
+
cache_creation_input_tokens?: number;
|
|
102
|
+
cache_read_input_tokens?: number;
|
|
103
|
+
[key: string]: unknown;
|
|
104
|
+
}): Partial<Usage> {
|
|
105
|
+
const inputTokens = num(raw.input_tokens);
|
|
106
|
+
const outputTokens = num(raw.output_tokens);
|
|
107
|
+
const cacheWriteInputTokens = num(raw.cache_creation_input_tokens);
|
|
108
|
+
const cachedInputTokens = num(raw.cache_read_input_tokens);
|
|
109
|
+
|
|
110
|
+
const inputParts = [inputTokens, cacheWriteInputTokens, cachedInputTokens].filter(
|
|
111
|
+
(n): n is number => n !== undefined,
|
|
112
|
+
);
|
|
113
|
+
const summedInput = inputParts.length > 0 ? inputParts.reduce((sum, n) => sum + n, 0) : undefined;
|
|
114
|
+
const totalTokens =
|
|
115
|
+
summedInput !== undefined && outputTokens !== undefined ? summedInput + outputTokens : undefined;
|
|
116
|
+
|
|
117
|
+
let billableInputTokens: number | undefined;
|
|
118
|
+
if (inputTokens !== undefined || cacheWriteInputTokens !== undefined) {
|
|
119
|
+
billableInputTokens = (inputTokens ?? 0) + (cacheWriteInputTokens ?? 0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return record({
|
|
123
|
+
inputTokens,
|
|
124
|
+
outputTokens,
|
|
125
|
+
totalTokens,
|
|
126
|
+
cachedInputTokens,
|
|
127
|
+
cacheWriteInputTokens,
|
|
128
|
+
billableInputTokens,
|
|
129
|
+
billableOutputTokens: outputTokens,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Ollama 流式 chunk(无 cache / reasoning 细分时仅填基础与 billable 镜像) */
|
|
134
|
+
export function usageFromOllama(raw: {
|
|
135
|
+
prompt_eval_count?: number;
|
|
136
|
+
eval_count?: number;
|
|
137
|
+
}): Partial<Usage> {
|
|
138
|
+
const inputTokens = num(raw.prompt_eval_count);
|
|
139
|
+
const outputTokens = num(raw.eval_count);
|
|
140
|
+
const totalTokens =
|
|
141
|
+
inputTokens !== undefined && outputTokens !== undefined ? inputTokens + outputTokens : undefined;
|
|
142
|
+
|
|
143
|
+
return record({
|
|
144
|
+
inputTokens,
|
|
145
|
+
outputTokens,
|
|
146
|
+
totalTokens,
|
|
147
|
+
billableInputTokens: inputTokens,
|
|
148
|
+
billableOutputTokens: outputTokens,
|
|
149
|
+
});
|
|
150
|
+
}
|
package/src/types/response.ts
CHANGED
|
@@ -13,13 +13,20 @@ export type StopReason = "end_turn" | "tool_call" | "max_output_tokens" | "conte
|
|
|
13
13
|
// ── 辅助信息类型 ──────────────────────────────────────────────
|
|
14
14
|
|
|
15
15
|
export type Usage = {
|
|
16
|
+
/** Provider prompt / input token count */
|
|
16
17
|
inputTokens?: number;
|
|
18
|
+
/** Provider completion / output token count */
|
|
17
19
|
outputTokens?: number;
|
|
20
|
+
/** Reasoning tokens when provider exposes output breakdown (e.g. OpenAI Responses) */
|
|
18
21
|
reasoningTokens?: number;
|
|
19
22
|
totalTokens?: number;
|
|
23
|
+
/** Tokens read from prompt cache (OpenAI cached_tokens, Anthropic cache_read_input_tokens) */
|
|
20
24
|
cachedInputTokens?: number;
|
|
25
|
+
/** Tokens written to prompt cache (Anthropic cache_creation_input_tokens) */
|
|
21
26
|
cacheWriteInputTokens?: number;
|
|
27
|
+
/** Best-effort billable input (full-rate input; excludes discounted cache reads where known) */
|
|
22
28
|
billableInputTokens?: number;
|
|
29
|
+
/** Best-effort billable output (non-reasoning slice when provider gives reasoning breakdown) */
|
|
23
30
|
billableOutputTokens?: number;
|
|
24
31
|
};
|
|
25
32
|
|