qwenproxy-cli 1.0.0 → 1.0.2
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 +14 -14
- package/README.md +906 -906
- package/bin/qwenproxy.js +5 -1
- package/package.json +77 -78
- package/src/api/error-classifier.ts +159 -159
- package/src/api/error-helpers.ts +118 -118
- package/src/api/models.ts +261 -261
- package/src/api/server.ts +860 -859
- package/src/cache/memory-cache.ts +385 -385
- package/src/clean-cache.ts +204 -204
- package/src/core/account-concurrency.ts +671 -671
- package/src/core/account-manager.ts +301 -297
- package/src/core/account-priority.ts +163 -163
- package/src/core/accounts.ts +186 -186
- package/src/core/config.ts +383 -383
- package/src/core/crypto-utils.ts +79 -79
- package/src/core/database.ts +276 -276
- package/src/core/errors.ts +118 -118
- package/src/core/logger.ts +269 -269
- package/src/core/memory-usage.ts +84 -84
- package/src/core/metrics.ts +291 -291
- package/src/core/model-alias.ts +77 -77
- package/src/core/model-registry.ts +544 -544
- package/src/core/mutex.ts +119 -119
- package/src/core/paths.ts +199 -199
- package/src/core/prompt-limits.ts +214 -214
- package/src/core/reasoning-effort.ts +102 -102
- package/src/core/stream-registry.ts +96 -96
- package/src/core/waf-isolation.ts +117 -117
- package/src/core/watchdog.ts +195 -195
- package/src/delete-chats.ts +23 -23
- package/src/index.ts +65 -64
- package/src/login.ts +147 -147
- package/src/reset-cooldowns.ts +11 -11
- package/src/routes/anthropic/index.ts +355 -355
- package/src/routes/anthropic/translate.ts +522 -522
- package/src/routes/anthropic/types.ts +154 -154
- package/src/routes/anthropic/validation.ts +144 -144
- package/src/routes/chat/account.ts +1817 -1817
- package/src/routes/chat/context.ts +241 -241
- package/src/routes/chat/errors.ts +85 -85
- package/src/routes/chat/helpers.ts +268 -268
- package/src/routes/chat/index.ts +618 -618
- package/src/routes/chat/media.ts +285 -285
- package/src/routes/chat/retry-policy.ts +754 -754
- package/src/routes/chat/stop.ts +98 -98
- package/src/routes/chat/streaming.ts +2710 -2710
- package/src/routes/chat/validation.ts +526 -526
- package/src/routes/chat.ts +2 -2
- package/src/routes/completions.ts +290 -290
- package/src/routes/images.ts +139 -139
- package/src/routes/responses/adapter.ts +503 -503
- package/src/routes/responses/index.ts +405 -405
- package/src/routes/responses/state.ts +230 -230
- package/src/routes/responses/streaming.ts +528 -528
- package/src/routes/responses/types.ts +285 -285
- package/src/routes/responses/validation.ts +202 -202
- package/src/routes/upload.ts +731 -731
- package/src/routes/videos.ts +214 -214
- package/src/services/auth-playwright.ts +173 -173
- package/src/services/captcha-coordinator.ts +161 -161
- package/src/services/captcha-solver.ts +553 -553
- package/src/services/chat-cleanup.ts +80 -80
- package/src/services/context-meter.ts +317 -317
- package/src/services/fingerprint.ts +242 -242
- package/src/services/human-behavior.ts +173 -173
- package/src/services/media-generation.ts +1748 -1748
- package/src/services/playwright.ts +2878 -2800
- package/src/services/qwen-chat-pool.ts +345 -345
- package/src/services/qwen-errors.ts +133 -133
- package/src/services/qwen-headers.ts +79 -79
- package/src/services/qwen-thread-state.ts +393 -393
- package/src/services/qwen-url.ts +19 -19
- package/src/services/qwen.ts +3126 -3126
- package/src/services/session-keeper.ts +88 -88
- package/src/services/token-estimation-metrics.ts +118 -118
- package/src/sync/claude-code.ts +75 -75
- package/src/sync/codex.ts +123 -123
- package/src/sync/index.ts +362 -362
- package/src/sync/omp.ts +105 -105
- package/src/sync/opencode.ts +214 -214
- package/src/sync/types.ts +53 -53
- package/src/sync/utils.ts +27 -27
- package/src/sync-clients.ts +189 -189
- package/src/tools/instructions.ts +137 -137
- package/src/tools/manifest.ts +81 -81
- package/src/tools/parser.ts +2989 -2989
- package/src/tools/toolcall-tags.ts +142 -142
- package/src/tui/app.ts +259 -264
- package/src/tui/index.ts +61 -61
- package/src/tui/markdown.ts +258 -258
- package/src/tui/proxy-client.ts +331 -326
- package/src/tui/screen.ts +294 -278
- package/src/tui/server-manager.ts +270 -270
- package/src/tui/theme.ts +432 -432
- package/src/tui/types.ts +33 -33
- package/src/tui/views/accounts-view.ts +656 -656
- package/src/tui/views/chat-view.ts +1018 -823
- package/src/tui/views/logs-view.ts +479 -413
- package/src/tui/views/status-view.ts +204 -204
- package/src/tui/views/storage-view.ts +304 -291
- package/src/tui/views/sync-view.ts +409 -409
- package/src/types/ali-oss.d.ts +32 -32
- package/src/update-cli.ts +121 -0
- package/src/utils/context-truncation.ts +84 -84
- package/src/utils/json.ts +380 -380
- package/src/utils/session-id.ts +37 -37
- package/src/utils/tool-call-guard.ts +84 -84
- package/src/utils/types.ts +109 -109
|
@@ -1,268 +1,268 @@
|
|
|
1
|
-
import { Usage } from "../../utils/types.ts";
|
|
2
|
-
|
|
3
|
-
export interface DeltaResult {
|
|
4
|
-
delta: string;
|
|
5
|
-
matchedContent: string;
|
|
6
|
-
contentLength: number;
|
|
7
|
-
contentSuffix: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function buildDeltaResult(delta: string, matchedContent: string): DeltaResult {
|
|
11
|
-
return {
|
|
12
|
-
delta,
|
|
13
|
-
matchedContent,
|
|
14
|
-
contentLength: matchedContent.length,
|
|
15
|
-
contentSuffix: matchedContent.slice(-32),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function getIncrementalDelta(
|
|
20
|
-
oldStr: string,
|
|
21
|
-
newStr: string,
|
|
22
|
-
previousLength = oldStr.length,
|
|
23
|
-
previousSuffix = oldStr.slice(-32),
|
|
24
|
-
): DeltaResult {
|
|
25
|
-
if (!oldStr) {
|
|
26
|
-
return buildDeltaResult(newStr, newStr);
|
|
27
|
-
}
|
|
28
|
-
if (newStr === oldStr) {
|
|
29
|
-
return buildDeltaResult("", oldStr);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Fast path for cumulative Qwen chunks: validate the old boundary using a
|
|
33
|
-
// short suffix instead of scanning the whole previous content with startsWith.
|
|
34
|
-
// Using 32-byte window (O(1)) since Qwen streams with incremental_output=true.
|
|
35
|
-
if (newStr.length > previousLength && previousLength > 0) {
|
|
36
|
-
const checkLen = Math.min(32, previousLength, previousSuffix.length);
|
|
37
|
-
const expectedSuffix = previousSuffix.slice(-checkLen);
|
|
38
|
-
const actualSuffix = newStr.slice(
|
|
39
|
-
previousLength - checkLen,
|
|
40
|
-
previousLength,
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
if (expectedSuffix === actualSuffix) {
|
|
44
|
-
return buildDeltaResult(newStr.slice(previousLength), newStr);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (newStr.length >= oldStr.length && newStr.startsWith(oldStr)) {
|
|
49
|
-
return buildDeltaResult(newStr.substring(oldStr.length), newStr);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Heuristic to detect if newStr is cumulative or incremental. Compare in
|
|
53
|
-
// small segments first to reduce per-character work on long responses.
|
|
54
|
-
const scanWindow = Math.min(2000, oldStr.length);
|
|
55
|
-
let commonPrefixLen = 0;
|
|
56
|
-
const maxLen = Math.min(scanWindow, newStr.length);
|
|
57
|
-
const segmentLen = 64;
|
|
58
|
-
|
|
59
|
-
while (commonPrefixLen + segmentLen <= maxLen) {
|
|
60
|
-
if (
|
|
61
|
-
oldStr.slice(commonPrefixLen, commonPrefixLen + segmentLen) !==
|
|
62
|
-
newStr.slice(commonPrefixLen, commonPrefixLen + segmentLen)
|
|
63
|
-
) {
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
commonPrefixLen += segmentLen;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
while (
|
|
70
|
-
commonPrefixLen < maxLen &&
|
|
71
|
-
oldStr[commonPrefixLen] === newStr[commonPrefixLen]
|
|
72
|
-
) {
|
|
73
|
-
commonPrefixLen++;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const threshold = Math.min(scanWindow, 4);
|
|
77
|
-
if (commonPrefixLen >= threshold) {
|
|
78
|
-
return buildDeltaResult(newStr.substring(commonPrefixLen), newStr);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Treat as strictly incremental to avoid false-positive corruptions
|
|
82
|
-
return buildDeltaResult(newStr, oldStr + newStr);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function formatThinkingSummaryContent(delta: any): string {
|
|
86
|
-
const titles = Array.isArray(delta?.extra?.summary_title?.content)
|
|
87
|
-
? delta.extra.summary_title.content.filter(
|
|
88
|
-
(item: unknown): item is string => typeof item === "string",
|
|
89
|
-
)
|
|
90
|
-
: [];
|
|
91
|
-
const thoughts = Array.isArray(delta?.extra?.summary_thought?.content)
|
|
92
|
-
? delta.extra.summary_thought.content.filter(
|
|
93
|
-
(item: unknown): item is string => typeof item === "string",
|
|
94
|
-
)
|
|
95
|
-
: [];
|
|
96
|
-
|
|
97
|
-
const sectionCount = Math.max(titles.length, thoughts.length);
|
|
98
|
-
const sections: string[] = [];
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < sectionCount; i++) {
|
|
101
|
-
const title = titles[i]?.trim() || "";
|
|
102
|
-
const thought = thoughts[i]?.trim() || "";
|
|
103
|
-
|
|
104
|
-
if (title && thought) {
|
|
105
|
-
sections.push(`**${title}**\n\n${thought}`);
|
|
106
|
-
} else if (title) {
|
|
107
|
-
sections.push(`**${title}**`);
|
|
108
|
-
} else if (thought) {
|
|
109
|
-
sections.push(thought);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return sections.join("\n\n");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function isAbortError(err: unknown): boolean {
|
|
117
|
-
if (err instanceof DOMException) {
|
|
118
|
-
return err.name === "AbortError";
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!err || typeof err !== "object") return false;
|
|
122
|
-
|
|
123
|
-
const maybeError = err as { name?: unknown; message?: unknown };
|
|
124
|
-
const name = maybeError.name;
|
|
125
|
-
const message = maybeError.message;
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
name === "AbortError" ||
|
|
129
|
-
(typeof message === "string" && /abort(ed)?/i.test(message))
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function shouldSuppressStreamAbort(
|
|
134
|
-
err: unknown,
|
|
135
|
-
clientDisconnected: boolean,
|
|
136
|
-
requestAborted: boolean,
|
|
137
|
-
streamStillRegistered: boolean,
|
|
138
|
-
): boolean {
|
|
139
|
-
return (
|
|
140
|
-
isAbortError(err) &&
|
|
141
|
-
(clientDisconnected || requestAborted || !streamStillRegistered)
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export interface UsageAccumulator {
|
|
146
|
-
promptTokens: number;
|
|
147
|
-
completionTokens: number;
|
|
148
|
-
totalTokens: number;
|
|
149
|
-
hasRealPromptTokens: boolean;
|
|
150
|
-
hasRealCompletionTokens: boolean;
|
|
151
|
-
hasRealTotalTokens: boolean;
|
|
152
|
-
cachedPromptTokens: number;
|
|
153
|
-
promptTextTokens?: number;
|
|
154
|
-
reasoningTokens?: number;
|
|
155
|
-
completionTextTokens?: number;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function asFiniteNumber(value: unknown): number | null {
|
|
159
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function createUsageAccumulator(
|
|
163
|
-
estimatedPromptTokens: number,
|
|
164
|
-
): UsageAccumulator {
|
|
165
|
-
return {
|
|
166
|
-
promptTokens: estimatedPromptTokens,
|
|
167
|
-
completionTokens: 0,
|
|
168
|
-
totalTokens: estimatedPromptTokens,
|
|
169
|
-
hasRealPromptTokens: false,
|
|
170
|
-
hasRealCompletionTokens: false,
|
|
171
|
-
hasRealTotalTokens: false,
|
|
172
|
-
cachedPromptTokens: 0,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function applyUpstreamUsage(
|
|
177
|
-
accumulator: UsageAccumulator,
|
|
178
|
-
candidate: unknown,
|
|
179
|
-
): void {
|
|
180
|
-
if (!candidate || typeof candidate !== "object") return;
|
|
181
|
-
|
|
182
|
-
const usage = candidate as Record<string, unknown>;
|
|
183
|
-
const promptTokens = asFiniteNumber(usage.input_tokens);
|
|
184
|
-
const completionTokens = asFiniteNumber(usage.output_tokens);
|
|
185
|
-
const totalTokens = asFiniteNumber(usage.total_tokens);
|
|
186
|
-
|
|
187
|
-
if (promptTokens !== null) {
|
|
188
|
-
accumulator.promptTokens = promptTokens;
|
|
189
|
-
accumulator.hasRealPromptTokens = true;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (completionTokens !== null) {
|
|
193
|
-
accumulator.completionTokens = completionTokens;
|
|
194
|
-
accumulator.hasRealCompletionTokens = true;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (totalTokens !== null) {
|
|
198
|
-
accumulator.totalTokens = totalTokens;
|
|
199
|
-
accumulator.hasRealTotalTokens = true;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const promptTokensDetails =
|
|
203
|
-
usage.prompt_tokens_details &&
|
|
204
|
-
typeof usage.prompt_tokens_details === "object"
|
|
205
|
-
? (usage.prompt_tokens_details as Record<string, unknown>)
|
|
206
|
-
: null;
|
|
207
|
-
const inputTokensDetails =
|
|
208
|
-
usage.input_tokens_details && typeof usage.input_tokens_details === "object"
|
|
209
|
-
? (usage.input_tokens_details as Record<string, unknown>)
|
|
210
|
-
: null;
|
|
211
|
-
const outputTokensDetails =
|
|
212
|
-
usage.output_tokens_details &&
|
|
213
|
-
typeof usage.output_tokens_details === "object"
|
|
214
|
-
? (usage.output_tokens_details as Record<string, unknown>)
|
|
215
|
-
: null;
|
|
216
|
-
|
|
217
|
-
const cachedTokens = asFiniteNumber(promptTokensDetails?.cached_tokens);
|
|
218
|
-
if (cachedTokens !== null) {
|
|
219
|
-
accumulator.cachedPromptTokens = cachedTokens;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const promptTextTokens = asFiniteNumber(inputTokensDetails?.text_tokens);
|
|
223
|
-
if (promptTextTokens !== null) {
|
|
224
|
-
accumulator.promptTextTokens = promptTextTokens;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const reasoningTokens = asFiniteNumber(outputTokensDetails?.reasoning_tokens);
|
|
228
|
-
if (reasoningTokens !== null) {
|
|
229
|
-
accumulator.reasoningTokens = reasoningTokens;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const completionTextTokens = asFiniteNumber(outputTokensDetails?.text_tokens);
|
|
233
|
-
if (completionTextTokens !== null) {
|
|
234
|
-
accumulator.completionTextTokens = completionTextTokens;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export function buildUsage(accumulator: UsageAccumulator): Usage {
|
|
239
|
-
const usage: Usage = {
|
|
240
|
-
prompt_tokens: accumulator.promptTokens,
|
|
241
|
-
completion_tokens: accumulator.completionTokens,
|
|
242
|
-
total_tokens: accumulator.hasRealTotalTokens
|
|
243
|
-
? accumulator.totalTokens
|
|
244
|
-
: accumulator.promptTokens + accumulator.completionTokens,
|
|
245
|
-
prompt_tokens_details: {
|
|
246
|
-
cached_tokens: accumulator.cachedPromptTokens,
|
|
247
|
-
...(accumulator.promptTextTokens !== undefined
|
|
248
|
-
? { text_tokens: accumulator.promptTextTokens }
|
|
249
|
-
: {}),
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
accumulator.reasoningTokens !== undefined ||
|
|
255
|
-
accumulator.completionTextTokens !== undefined
|
|
256
|
-
) {
|
|
257
|
-
usage.completion_tokens_details = {
|
|
258
|
-
...(accumulator.reasoningTokens !== undefined
|
|
259
|
-
? { reasoning_tokens: accumulator.reasoningTokens }
|
|
260
|
-
: {}),
|
|
261
|
-
...(accumulator.completionTextTokens !== undefined
|
|
262
|
-
? { text_tokens: accumulator.completionTextTokens }
|
|
263
|
-
: {}),
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return usage;
|
|
268
|
-
}
|
|
1
|
+
import { Usage } from "../../utils/types.ts";
|
|
2
|
+
|
|
3
|
+
export interface DeltaResult {
|
|
4
|
+
delta: string;
|
|
5
|
+
matchedContent: string;
|
|
6
|
+
contentLength: number;
|
|
7
|
+
contentSuffix: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function buildDeltaResult(delta: string, matchedContent: string): DeltaResult {
|
|
11
|
+
return {
|
|
12
|
+
delta,
|
|
13
|
+
matchedContent,
|
|
14
|
+
contentLength: matchedContent.length,
|
|
15
|
+
contentSuffix: matchedContent.slice(-32),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getIncrementalDelta(
|
|
20
|
+
oldStr: string,
|
|
21
|
+
newStr: string,
|
|
22
|
+
previousLength = oldStr.length,
|
|
23
|
+
previousSuffix = oldStr.slice(-32),
|
|
24
|
+
): DeltaResult {
|
|
25
|
+
if (!oldStr) {
|
|
26
|
+
return buildDeltaResult(newStr, newStr);
|
|
27
|
+
}
|
|
28
|
+
if (newStr === oldStr) {
|
|
29
|
+
return buildDeltaResult("", oldStr);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fast path for cumulative Qwen chunks: validate the old boundary using a
|
|
33
|
+
// short suffix instead of scanning the whole previous content with startsWith.
|
|
34
|
+
// Using 32-byte window (O(1)) since Qwen streams with incremental_output=true.
|
|
35
|
+
if (newStr.length > previousLength && previousLength > 0) {
|
|
36
|
+
const checkLen = Math.min(32, previousLength, previousSuffix.length);
|
|
37
|
+
const expectedSuffix = previousSuffix.slice(-checkLen);
|
|
38
|
+
const actualSuffix = newStr.slice(
|
|
39
|
+
previousLength - checkLen,
|
|
40
|
+
previousLength,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (expectedSuffix === actualSuffix) {
|
|
44
|
+
return buildDeltaResult(newStr.slice(previousLength), newStr);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (newStr.length >= oldStr.length && newStr.startsWith(oldStr)) {
|
|
49
|
+
return buildDeltaResult(newStr.substring(oldStr.length), newStr);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Heuristic to detect if newStr is cumulative or incremental. Compare in
|
|
53
|
+
// small segments first to reduce per-character work on long responses.
|
|
54
|
+
const scanWindow = Math.min(2000, oldStr.length);
|
|
55
|
+
let commonPrefixLen = 0;
|
|
56
|
+
const maxLen = Math.min(scanWindow, newStr.length);
|
|
57
|
+
const segmentLen = 64;
|
|
58
|
+
|
|
59
|
+
while (commonPrefixLen + segmentLen <= maxLen) {
|
|
60
|
+
if (
|
|
61
|
+
oldStr.slice(commonPrefixLen, commonPrefixLen + segmentLen) !==
|
|
62
|
+
newStr.slice(commonPrefixLen, commonPrefixLen + segmentLen)
|
|
63
|
+
) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
commonPrefixLen += segmentLen;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
while (
|
|
70
|
+
commonPrefixLen < maxLen &&
|
|
71
|
+
oldStr[commonPrefixLen] === newStr[commonPrefixLen]
|
|
72
|
+
) {
|
|
73
|
+
commonPrefixLen++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const threshold = Math.min(scanWindow, 4);
|
|
77
|
+
if (commonPrefixLen >= threshold) {
|
|
78
|
+
return buildDeltaResult(newStr.substring(commonPrefixLen), newStr);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Treat as strictly incremental to avoid false-positive corruptions
|
|
82
|
+
return buildDeltaResult(newStr, oldStr + newStr);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function formatThinkingSummaryContent(delta: any): string {
|
|
86
|
+
const titles = Array.isArray(delta?.extra?.summary_title?.content)
|
|
87
|
+
? delta.extra.summary_title.content.filter(
|
|
88
|
+
(item: unknown): item is string => typeof item === "string",
|
|
89
|
+
)
|
|
90
|
+
: [];
|
|
91
|
+
const thoughts = Array.isArray(delta?.extra?.summary_thought?.content)
|
|
92
|
+
? delta.extra.summary_thought.content.filter(
|
|
93
|
+
(item: unknown): item is string => typeof item === "string",
|
|
94
|
+
)
|
|
95
|
+
: [];
|
|
96
|
+
|
|
97
|
+
const sectionCount = Math.max(titles.length, thoughts.length);
|
|
98
|
+
const sections: string[] = [];
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < sectionCount; i++) {
|
|
101
|
+
const title = titles[i]?.trim() || "";
|
|
102
|
+
const thought = thoughts[i]?.trim() || "";
|
|
103
|
+
|
|
104
|
+
if (title && thought) {
|
|
105
|
+
sections.push(`**${title}**\n\n${thought}`);
|
|
106
|
+
} else if (title) {
|
|
107
|
+
sections.push(`**${title}**`);
|
|
108
|
+
} else if (thought) {
|
|
109
|
+
sections.push(thought);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return sections.join("\n\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function isAbortError(err: unknown): boolean {
|
|
117
|
+
if (err instanceof DOMException) {
|
|
118
|
+
return err.name === "AbortError";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!err || typeof err !== "object") return false;
|
|
122
|
+
|
|
123
|
+
const maybeError = err as { name?: unknown; message?: unknown };
|
|
124
|
+
const name = maybeError.name;
|
|
125
|
+
const message = maybeError.message;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
name === "AbortError" ||
|
|
129
|
+
(typeof message === "string" && /abort(ed)?/i.test(message))
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function shouldSuppressStreamAbort(
|
|
134
|
+
err: unknown,
|
|
135
|
+
clientDisconnected: boolean,
|
|
136
|
+
requestAborted: boolean,
|
|
137
|
+
streamStillRegistered: boolean,
|
|
138
|
+
): boolean {
|
|
139
|
+
return (
|
|
140
|
+
isAbortError(err) &&
|
|
141
|
+
(clientDisconnected || requestAborted || !streamStillRegistered)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface UsageAccumulator {
|
|
146
|
+
promptTokens: number;
|
|
147
|
+
completionTokens: number;
|
|
148
|
+
totalTokens: number;
|
|
149
|
+
hasRealPromptTokens: boolean;
|
|
150
|
+
hasRealCompletionTokens: boolean;
|
|
151
|
+
hasRealTotalTokens: boolean;
|
|
152
|
+
cachedPromptTokens: number;
|
|
153
|
+
promptTextTokens?: number;
|
|
154
|
+
reasoningTokens?: number;
|
|
155
|
+
completionTextTokens?: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function asFiniteNumber(value: unknown): number | null {
|
|
159
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function createUsageAccumulator(
|
|
163
|
+
estimatedPromptTokens: number,
|
|
164
|
+
): UsageAccumulator {
|
|
165
|
+
return {
|
|
166
|
+
promptTokens: estimatedPromptTokens,
|
|
167
|
+
completionTokens: 0,
|
|
168
|
+
totalTokens: estimatedPromptTokens,
|
|
169
|
+
hasRealPromptTokens: false,
|
|
170
|
+
hasRealCompletionTokens: false,
|
|
171
|
+
hasRealTotalTokens: false,
|
|
172
|
+
cachedPromptTokens: 0,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function applyUpstreamUsage(
|
|
177
|
+
accumulator: UsageAccumulator,
|
|
178
|
+
candidate: unknown,
|
|
179
|
+
): void {
|
|
180
|
+
if (!candidate || typeof candidate !== "object") return;
|
|
181
|
+
|
|
182
|
+
const usage = candidate as Record<string, unknown>;
|
|
183
|
+
const promptTokens = asFiniteNumber(usage.input_tokens);
|
|
184
|
+
const completionTokens = asFiniteNumber(usage.output_tokens);
|
|
185
|
+
const totalTokens = asFiniteNumber(usage.total_tokens);
|
|
186
|
+
|
|
187
|
+
if (promptTokens !== null) {
|
|
188
|
+
accumulator.promptTokens = promptTokens;
|
|
189
|
+
accumulator.hasRealPromptTokens = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (completionTokens !== null) {
|
|
193
|
+
accumulator.completionTokens = completionTokens;
|
|
194
|
+
accumulator.hasRealCompletionTokens = true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (totalTokens !== null) {
|
|
198
|
+
accumulator.totalTokens = totalTokens;
|
|
199
|
+
accumulator.hasRealTotalTokens = true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const promptTokensDetails =
|
|
203
|
+
usage.prompt_tokens_details &&
|
|
204
|
+
typeof usage.prompt_tokens_details === "object"
|
|
205
|
+
? (usage.prompt_tokens_details as Record<string, unknown>)
|
|
206
|
+
: null;
|
|
207
|
+
const inputTokensDetails =
|
|
208
|
+
usage.input_tokens_details && typeof usage.input_tokens_details === "object"
|
|
209
|
+
? (usage.input_tokens_details as Record<string, unknown>)
|
|
210
|
+
: null;
|
|
211
|
+
const outputTokensDetails =
|
|
212
|
+
usage.output_tokens_details &&
|
|
213
|
+
typeof usage.output_tokens_details === "object"
|
|
214
|
+
? (usage.output_tokens_details as Record<string, unknown>)
|
|
215
|
+
: null;
|
|
216
|
+
|
|
217
|
+
const cachedTokens = asFiniteNumber(promptTokensDetails?.cached_tokens);
|
|
218
|
+
if (cachedTokens !== null) {
|
|
219
|
+
accumulator.cachedPromptTokens = cachedTokens;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const promptTextTokens = asFiniteNumber(inputTokensDetails?.text_tokens);
|
|
223
|
+
if (promptTextTokens !== null) {
|
|
224
|
+
accumulator.promptTextTokens = promptTextTokens;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const reasoningTokens = asFiniteNumber(outputTokensDetails?.reasoning_tokens);
|
|
228
|
+
if (reasoningTokens !== null) {
|
|
229
|
+
accumulator.reasoningTokens = reasoningTokens;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const completionTextTokens = asFiniteNumber(outputTokensDetails?.text_tokens);
|
|
233
|
+
if (completionTextTokens !== null) {
|
|
234
|
+
accumulator.completionTextTokens = completionTextTokens;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function buildUsage(accumulator: UsageAccumulator): Usage {
|
|
239
|
+
const usage: Usage = {
|
|
240
|
+
prompt_tokens: accumulator.promptTokens,
|
|
241
|
+
completion_tokens: accumulator.completionTokens,
|
|
242
|
+
total_tokens: accumulator.hasRealTotalTokens
|
|
243
|
+
? accumulator.totalTokens
|
|
244
|
+
: accumulator.promptTokens + accumulator.completionTokens,
|
|
245
|
+
prompt_tokens_details: {
|
|
246
|
+
cached_tokens: accumulator.cachedPromptTokens,
|
|
247
|
+
...(accumulator.promptTextTokens !== undefined
|
|
248
|
+
? { text_tokens: accumulator.promptTextTokens }
|
|
249
|
+
: {}),
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
if (
|
|
254
|
+
accumulator.reasoningTokens !== undefined ||
|
|
255
|
+
accumulator.completionTextTokens !== undefined
|
|
256
|
+
) {
|
|
257
|
+
usage.completion_tokens_details = {
|
|
258
|
+
...(accumulator.reasoningTokens !== undefined
|
|
259
|
+
? { reasoning_tokens: accumulator.reasoningTokens }
|
|
260
|
+
: {}),
|
|
261
|
+
...(accumulator.completionTextTokens !== undefined
|
|
262
|
+
? { text_tokens: accumulator.completionTextTokens }
|
|
263
|
+
: {}),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return usage;
|
|
268
|
+
}
|