@hopemyl619/deepseek 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ // 响应转换器 - V1 API 兼容版本
2
+
3
+ import type {
4
+ LanguageModelV1,
5
+ LanguageModelV1FinishReason,
6
+ LanguageModelV1CallWarning,
7
+ LanguageModelV1FunctionToolCall,
8
+ } from '@ai-sdk/provider';
9
+ import type { DeepSeekResponse } from '../types';
10
+
11
+ // V1 API 返回结果类型
12
+ export interface V1GenerateResult {
13
+ text?: string;
14
+ reasoning?: string;
15
+ usage: {
16
+ promptTokens: number;
17
+ completionTokens: number;
18
+ };
19
+ finishReason: LanguageModelV1FinishReason;
20
+ toolCalls?: LanguageModelV1FunctionToolCall[];
21
+ rawCall: {
22
+ rawPrompt: unknown;
23
+ rawSettings: Record<string, unknown>;
24
+ };
25
+ warnings?: LanguageModelV1CallWarning[];
26
+ }
27
+
28
+ export class ResponseTransformer {
29
+ transform(apiResponse: DeepSeekResponse): V1GenerateResult {
30
+ const choice = apiResponse.choices?.[0];
31
+ const message = choice?.message || {};
32
+
33
+ // 修复 finish_reason
34
+ const finishReason = this.fixFinishReason(
35
+ choice?.finish_reason,
36
+ message.tool_calls
37
+ );
38
+
39
+ return {
40
+ text: message.content || '',
41
+ finishReason: finishReason as LanguageModelV1FinishReason,
42
+ usage: {
43
+ promptTokens: apiResponse.usage?.prompt_tokens || 0,
44
+ completionTokens: apiResponse.usage?.completion_tokens || 0,
45
+ },
46
+ rawCall: {
47
+ rawPrompt: '',
48
+ rawSettings: {},
49
+ },
50
+ toolCalls: this.transformToolCalls(message.tool_calls),
51
+ warnings: [],
52
+ };
53
+ }
54
+
55
+ private fixFinishReason(
56
+ originalFinishReason: string | undefined,
57
+ toolCalls: any[] | undefined
58
+ ): string {
59
+ // 如果有 tool_calls 但 finish_reason 是 "stop",修复为 "tool-calls"
60
+ if (toolCalls && toolCalls.length > 0 && originalFinishReason === 'stop') {
61
+ return 'tool-calls';
62
+ }
63
+
64
+ // 映射其他 finish_reason 值
65
+ const finishReasonMap: Record<string, string> = {
66
+ 'stop': 'stop',
67
+ 'length': 'length',
68
+ 'content_filter': 'content-filter',
69
+ 'tool_calls': 'tool-calls',
70
+ 'function_call': 'tool-calls',
71
+ };
72
+
73
+ return finishReasonMap[originalFinishReason || ''] || 'stop';
74
+ }
75
+
76
+ private transformToolCalls(toolCalls: any[] | undefined): LanguageModelV1FunctionToolCall[] {
77
+ if (!toolCalls || toolCalls.length === 0) {
78
+ return [];
79
+ }
80
+
81
+ return toolCalls.map((tc: any) => ({
82
+ toolCallType: 'function' as const,
83
+ toolCallId: tc.id,
84
+ toolName: tc.function.name,
85
+ args: typeof tc.function.arguments === 'string'
86
+ ? tc.function.arguments
87
+ : JSON.stringify(tc.function.arguments),
88
+ }));
89
+ }
90
+ }
@@ -0,0 +1,176 @@
1
+ // 流解析器
2
+
3
+ import type { LanguageModelV1StreamPart } from '@ai-sdk/provider';
4
+ import type { DeepSeekStreamResponse, DeepSeekUsage } from '../types';
5
+
6
+ interface DeepSeekStreamChunk {
7
+ id: string;
8
+ object: string;
9
+ created: number;
10
+ model: string;
11
+ choices: Array<{
12
+ index: number;
13
+ delta: {
14
+ role?: string;
15
+ content?: string;
16
+ tool_calls?: Array<{
17
+ id: string;
18
+ type: 'function';
19
+ function: {
20
+ name: string;
21
+ arguments: string;
22
+ };
23
+ }>;
24
+ };
25
+ finish_reason?: string;
26
+ }>;
27
+ usage?: DeepSeekUsage;
28
+ }
29
+
30
+ export class StreamParser {
31
+ async *parse(body: ReadableStream<Uint8Array>): AsyncIterable<LanguageModelV1StreamPart> {
32
+ const reader = body.getReader();
33
+ const decoder = new TextDecoder('utf-8');
34
+ let buffer = '';
35
+ let partialJson = '';
36
+
37
+ try {
38
+ while (true) {
39
+ const { done, value } = await reader.read();
40
+ if (done) break;
41
+
42
+ buffer += decoder.decode(value, { stream: true });
43
+ const lines = buffer.split('\n');
44
+ buffer = lines.pop() || '';
45
+
46
+ for (const line of lines) {
47
+ if (line.trim() === '') continue;
48
+ if (line.startsWith(':')) continue; // SSE 注释
49
+
50
+ if (line.startsWith('data: ')) {
51
+ const data = line.slice(6).trim();
52
+ if (data === '[DONE]') {
53
+ yield* this.processPartialJson(partialJson);
54
+ partialJson = '';
55
+ return;
56
+ }
57
+
58
+ try {
59
+ // 尝试解析 JSON
60
+ const parsed = JSON.parse(data);
61
+ yield* this.processPartialJson(partialJson);
62
+ partialJson = '';
63
+ yield* this.processChunk(parsed);
64
+ } catch (e) {
65
+ // JSON 解析失败,可能是部分数据
66
+ partialJson += data;
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ // 处理剩余的部分 JSON
73
+ yield* this.processPartialJson(partialJson);
74
+ } finally {
75
+ reader.releaseLock();
76
+ }
77
+ }
78
+
79
+ private async *processPartialJson(partialJson: string): AsyncIterable<LanguageModelV1StreamPart> {
80
+ if (!partialJson.trim()) return;
81
+
82
+ try {
83
+ const parsed = JSON.parse(partialJson);
84
+ yield* this.processChunk(parsed);
85
+ } catch (e) {
86
+ // 仍然无法解析,跳过
87
+ console.warn('Failed to parse partial JSON:', partialJson);
88
+ }
89
+ }
90
+
91
+ private async *processChunk(chunk: DeepSeekStreamChunk): AsyncIterable<LanguageModelV1StreamPart> {
92
+ const delta = chunk.choices?.[0]?.delta;
93
+
94
+ // 工具调用增量 - JSON in content 格式
95
+ if (delta?.content) {
96
+ const toolCallData = this.tryParseToolCall(delta.content);
97
+ if (toolCallData) {
98
+ yield {
99
+ type: 'tool-call-delta',
100
+ toolCallType: 'function',
101
+ toolCallId: toolCallData.id || `tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
102
+ toolName: toolCallData.name,
103
+ argsTextDelta: JSON.stringify(toolCallData.arguments || {}),
104
+ } as LanguageModelV1StreamPart;
105
+ } else {
106
+ // 普通文本增量
107
+ yield {
108
+ type: 'text-delta',
109
+ textDelta: delta.content,
110
+ } as LanguageModelV1StreamPart;
111
+ }
112
+ }
113
+
114
+ // 工具调用增量 - 标准 tool_calls 格式
115
+ if (delta?.tool_calls && delta.tool_calls.length > 0) {
116
+ for (const toolCall of delta.tool_calls) {
117
+ yield {
118
+ type: 'tool-call-delta',
119
+ toolCallType: 'function',
120
+ toolCallId: toolCall.id,
121
+ toolName: toolCall.function?.name || '',
122
+ argsTextDelta: toolCall.function?.arguments || '',
123
+ } as LanguageModelV1StreamPart;
124
+ }
125
+ }
126
+
127
+ // 完成信号
128
+ const finishReason = chunk.choices?.[0]?.finish_reason;
129
+ if (finishReason) {
130
+ // V1 API: usage 在 finish 类型的 part 中
131
+ const usage = chunk.usage
132
+ ? {
133
+ promptTokens: chunk.usage.prompt_tokens,
134
+ completionTokens: chunk.usage.completion_tokens,
135
+ }
136
+ : { promptTokens: 0, completionTokens: 0 };
137
+
138
+ yield {
139
+ type: 'finish',
140
+ finishReason: this.mapFinishReason(finishReason),
141
+ usage,
142
+ } as LanguageModelV1StreamPart;
143
+ }
144
+ }
145
+
146
+ private mapFinishReason(reason: string): 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown' {
147
+ const reasonMap: Record<string, 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other' | 'unknown'> = {
148
+ 'stop': 'stop',
149
+ 'length': 'length',
150
+ 'content_filter': 'content-filter',
151
+ 'tool_calls': 'tool-calls',
152
+ 'function_call': 'tool-calls',
153
+ 'error': 'error',
154
+ };
155
+ return reasonMap[reason] || 'unknown';
156
+ }
157
+
158
+ private tryParseToolCall(content: string): { id?: string; name: string; arguments: Record<string, unknown> } | null {
159
+ try {
160
+ const parsed = JSON.parse(content);
161
+ // 检查是否是工具调用格式: { name: "...", arguments: {...} }
162
+ if (parsed && typeof parsed.name === 'string' && (parsed.arguments === undefined || typeof parsed.arguments === 'object')) {
163
+ return {
164
+ id: parsed.id,
165
+ name: parsed.name,
166
+ arguments: parsed.arguments || {},
167
+ };
168
+ }
169
+ } catch {
170
+ // JSON 解析失败,不是工具调用
171
+ }
172
+ return null;
173
+ }
174
+ }
175
+
176
+