@herouucn/opencode-commandcode 0.1.0 → 0.1.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/LICENSE +21 -21
- package/_version.txt +1 -1
- package/index.ts +32 -32
- package/manifest.json +34 -34
- package/models.json +2182 -2182
- package/package.json +3 -3
- package/src/auth.ts +43 -43
- package/src/catalog-break.ts +41 -41
- package/src/catalog.ts +769 -769
- package/src/convert.ts +242 -242
- package/src/costs-docs.ts +179 -179
- package/src/costs-models-dev.ts +173 -173
- package/src/manifest.ts +134 -134
- package/src/model.ts +168 -168
- package/src/startup.ts +43 -43
- package/src/stream.ts +237 -237
package/src/stream.ts
CHANGED
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
LanguageModelV3StreamPart,
|
|
3
|
-
LanguageModelV3Usage,
|
|
4
|
-
LanguageModelV3FinishReason,
|
|
5
|
-
} from "@ai-sdk/provider";
|
|
6
|
-
|
|
7
|
-
type RawEvent = Record<string, unknown> & { type: string };
|
|
8
|
-
|
|
9
|
-
function mapFinishReason(raw: string): LanguageModelV3FinishReason["unified"] {
|
|
10
|
-
switch (raw) {
|
|
11
|
-
case "stop":
|
|
12
|
-
case "end_turn":
|
|
13
|
-
return "stop";
|
|
14
|
-
case "tool_calls":
|
|
15
|
-
case "tool-calls":
|
|
16
|
-
return "tool-calls";
|
|
17
|
-
case "length":
|
|
18
|
-
case "max_tokens":
|
|
19
|
-
case "max-tokens":
|
|
20
|
-
case "max_output_tokens":
|
|
21
|
-
return "length";
|
|
22
|
-
case "content_filter":
|
|
23
|
-
return "content-filter";
|
|
24
|
-
default:
|
|
25
|
-
return "other";
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function mapUsage(raw: Record<string, unknown>): LanguageModelV3Usage {
|
|
30
|
-
const inputDetails = (raw.inputTokenDetails ?? raw.input_token_details ?? {}) as Record<
|
|
31
|
-
string,
|
|
32
|
-
unknown
|
|
33
|
-
>;
|
|
34
|
-
const outputDetails = (raw.outputTokenDetails ?? raw.output_token_details ?? {}) as Record<
|
|
35
|
-
string,
|
|
36
|
-
unknown
|
|
37
|
-
>;
|
|
38
|
-
return {
|
|
39
|
-
inputTokens: {
|
|
40
|
-
total:
|
|
41
|
-
typeof raw.inputTokens === "number"
|
|
42
|
-
? raw.inputTokens
|
|
43
|
-
: typeof raw.prompt_tokens === "number"
|
|
44
|
-
? raw.prompt_tokens
|
|
45
|
-
: undefined,
|
|
46
|
-
noCache:
|
|
47
|
-
typeof inputDetails.noCacheTokens === "number" ? inputDetails.noCacheTokens : undefined,
|
|
48
|
-
cacheRead:
|
|
49
|
-
typeof inputDetails.cacheReadTokens === "number" ? inputDetails.cacheReadTokens : undefined,
|
|
50
|
-
cacheWrite:
|
|
51
|
-
typeof inputDetails.cacheWriteTokens === "number"
|
|
52
|
-
? inputDetails.cacheWriteTokens
|
|
53
|
-
: undefined,
|
|
54
|
-
},
|
|
55
|
-
outputTokens: {
|
|
56
|
-
total:
|
|
57
|
-
typeof raw.outputTokens === "number"
|
|
58
|
-
? raw.outputTokens
|
|
59
|
-
: typeof raw.completion_tokens === "number"
|
|
60
|
-
? raw.completion_tokens
|
|
61
|
-
: undefined,
|
|
62
|
-
text: typeof outputDetails.textTokens === "number" ? outputDetails.textTokens : undefined,
|
|
63
|
-
reasoning:
|
|
64
|
-
typeof outputDetails.reasoningTokens === "number"
|
|
65
|
-
? outputDetails.reasoningTokens
|
|
66
|
-
: undefined,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function toStreamPart(event: RawEvent): LanguageModelV3StreamPart | null {
|
|
72
|
-
switch (event.type) {
|
|
73
|
-
case "start":
|
|
74
|
-
return { type: "stream-start", warnings: [] };
|
|
75
|
-
|
|
76
|
-
case "text-start":
|
|
77
|
-
return { type: "text-start", id: event.id as string };
|
|
78
|
-
case "text-delta":
|
|
79
|
-
return {
|
|
80
|
-
type: "text-delta",
|
|
81
|
-
id: event.id as string,
|
|
82
|
-
delta: (event.text ?? event.delta ?? "") as string,
|
|
83
|
-
};
|
|
84
|
-
case "text-end":
|
|
85
|
-
return { type: "text-end", id: event.id as string };
|
|
86
|
-
|
|
87
|
-
case "reasoning-start":
|
|
88
|
-
return { type: "reasoning-start", id: event.id as string };
|
|
89
|
-
case "reasoning-delta":
|
|
90
|
-
return {
|
|
91
|
-
type: "reasoning-delta",
|
|
92
|
-
id: event.id as string,
|
|
93
|
-
delta: (event.text ?? event.delta ?? "") as string,
|
|
94
|
-
};
|
|
95
|
-
case "reasoning-end":
|
|
96
|
-
return { type: "reasoning-end", id: event.id as string };
|
|
97
|
-
|
|
98
|
-
case "tool-input-start":
|
|
99
|
-
return {
|
|
100
|
-
type: "tool-input-start",
|
|
101
|
-
id: event.id as string,
|
|
102
|
-
toolName: event.toolName as string,
|
|
103
|
-
dynamic: event.dynamic as boolean | undefined,
|
|
104
|
-
};
|
|
105
|
-
case "tool-input-delta":
|
|
106
|
-
return {
|
|
107
|
-
type: "tool-input-delta",
|
|
108
|
-
id: event.id as string,
|
|
109
|
-
delta: (event.delta ?? "") as string,
|
|
110
|
-
};
|
|
111
|
-
case "tool-input-end":
|
|
112
|
-
return { type: "tool-input-end", id: event.id as string };
|
|
113
|
-
|
|
114
|
-
case "tool-call": {
|
|
115
|
-
const input = event.input ?? event.args ?? event.arguments;
|
|
116
|
-
return {
|
|
117
|
-
type: "tool-call",
|
|
118
|
-
toolCallId: (event.toolCallId ?? event.id ?? "") as string,
|
|
119
|
-
toolName: event.toolName as string,
|
|
120
|
-
input: typeof input === "string" ? input : JSON.stringify(input ?? {}),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
case "finish-step": {
|
|
125
|
-
const usage = event.usage ?? event.totalUsage ?? {};
|
|
126
|
-
const rawReason = (event.finishReason ?? event.rawFinishReason ?? "stop") as string;
|
|
127
|
-
return {
|
|
128
|
-
type: "finish",
|
|
129
|
-
finishReason: { unified: mapFinishReason(rawReason), raw: rawReason },
|
|
130
|
-
usage: mapUsage(
|
|
131
|
-
typeof usage === "object" && usage !== null ? (usage as Record<string, unknown>) : {},
|
|
132
|
-
),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
case "finish":
|
|
137
|
-
return null;
|
|
138
|
-
|
|
139
|
-
case "response-metadata":
|
|
140
|
-
return {
|
|
141
|
-
type: "response-metadata",
|
|
142
|
-
id: event.id as string | undefined,
|
|
143
|
-
modelId: event.modelId as string | undefined,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
case "error":
|
|
147
|
-
return { type: "error", error: event.error ?? event.message ?? "Unknown error" };
|
|
148
|
-
|
|
149
|
-
default:
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Assumes line-delimited JSON over SSE: one JSON object per `data:` line.
|
|
155
|
-
// Multi-line `data:` fields are not supported — the buffer splits on `\n`.
|
|
156
|
-
export function parseStreamEvents(
|
|
157
|
-
body: ReadableStream<Uint8Array>,
|
|
158
|
-
): ReadableStream<LanguageModelV3StreamPart> {
|
|
159
|
-
const reader = body.getReader();
|
|
160
|
-
const decoder = new TextDecoder();
|
|
161
|
-
let buffer = "";
|
|
162
|
-
|
|
163
|
-
return new ReadableStream<LanguageModelV3StreamPart>({
|
|
164
|
-
async pull(controller) {
|
|
165
|
-
try {
|
|
166
|
-
while (true) {
|
|
167
|
-
const lines = buffer.split("\n");
|
|
168
|
-
// Strip trailing \r from Windows-style \r\n line endings
|
|
169
|
-
for (let i = 0; i < lines.length; i++) {
|
|
170
|
-
const line = lines[i];
|
|
171
|
-
if (line !== undefined && line.endsWith("\r")) lines[i] = line.slice(0, -1);
|
|
172
|
-
}
|
|
173
|
-
buffer = lines.pop() ?? "";
|
|
174
|
-
|
|
175
|
-
for (const line of lines) {
|
|
176
|
-
const trimmed = line.trim();
|
|
177
|
-
if (!trimmed || trimmed.startsWith(":") || trimmed === "[DONE]") continue;
|
|
178
|
-
|
|
179
|
-
let jsonStr = trimmed;
|
|
180
|
-
if (jsonStr.startsWith("data: ")) jsonStr = jsonStr.slice(6);
|
|
181
|
-
if (jsonStr.startsWith("data:")) jsonStr = jsonStr.slice(5);
|
|
182
|
-
if (!jsonStr || jsonStr === "[DONE]") continue;
|
|
183
|
-
|
|
184
|
-
let parsed: RawEvent;
|
|
185
|
-
try {
|
|
186
|
-
parsed = JSON.parse(jsonStr);
|
|
187
|
-
} catch {
|
|
188
|
-
// intentionally silent: skip malformed SSE lines
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (typeof parsed !== "object" || parsed === null || typeof parsed.type !== "string")
|
|
193
|
-
continue;
|
|
194
|
-
|
|
195
|
-
const part = toStreamPart(parsed);
|
|
196
|
-
if (part) controller.enqueue(part);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const { done, value } = await reader.read();
|
|
200
|
-
if (done) {
|
|
201
|
-
if (buffer.trim()) {
|
|
202
|
-
const trimmed = buffer.trim();
|
|
203
|
-
if (trimmed && trimmed !== "[DONE]" && !trimmed.startsWith(":")) {
|
|
204
|
-
let jsonStr = trimmed;
|
|
205
|
-
if (jsonStr.startsWith("data: ")) jsonStr = jsonStr.slice(6);
|
|
206
|
-
if (jsonStr.startsWith("data:")) jsonStr = jsonStr.slice(5);
|
|
207
|
-
try {
|
|
208
|
-
const parsed = JSON.parse(jsonStr);
|
|
209
|
-
if (
|
|
210
|
-
typeof parsed === "object" &&
|
|
211
|
-
parsed !== null &&
|
|
212
|
-
typeof parsed.type === "string"
|
|
213
|
-
) {
|
|
214
|
-
const part = toStreamPart(parsed);
|
|
215
|
-
if (part) controller.enqueue(part);
|
|
216
|
-
}
|
|
217
|
-
} catch {
|
|
218
|
-
// intentionally silent: skip malformed final buffer
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
controller.close();
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
buffer += decoder.decode(value, { stream: true });
|
|
227
|
-
}
|
|
228
|
-
} catch (err) {
|
|
229
|
-
controller.enqueue({ type: "error", error: err });
|
|
230
|
-
controller.close();
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
cancel() {
|
|
234
|
-
reader.cancel();
|
|
235
|
-
},
|
|
236
|
-
});
|
|
237
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
LanguageModelV3StreamPart,
|
|
3
|
+
LanguageModelV3Usage,
|
|
4
|
+
LanguageModelV3FinishReason,
|
|
5
|
+
} from "@ai-sdk/provider";
|
|
6
|
+
|
|
7
|
+
type RawEvent = Record<string, unknown> & { type: string };
|
|
8
|
+
|
|
9
|
+
function mapFinishReason(raw: string): LanguageModelV3FinishReason["unified"] {
|
|
10
|
+
switch (raw) {
|
|
11
|
+
case "stop":
|
|
12
|
+
case "end_turn":
|
|
13
|
+
return "stop";
|
|
14
|
+
case "tool_calls":
|
|
15
|
+
case "tool-calls":
|
|
16
|
+
return "tool-calls";
|
|
17
|
+
case "length":
|
|
18
|
+
case "max_tokens":
|
|
19
|
+
case "max-tokens":
|
|
20
|
+
case "max_output_tokens":
|
|
21
|
+
return "length";
|
|
22
|
+
case "content_filter":
|
|
23
|
+
return "content-filter";
|
|
24
|
+
default:
|
|
25
|
+
return "other";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function mapUsage(raw: Record<string, unknown>): LanguageModelV3Usage {
|
|
30
|
+
const inputDetails = (raw.inputTokenDetails ?? raw.input_token_details ?? {}) as Record<
|
|
31
|
+
string,
|
|
32
|
+
unknown
|
|
33
|
+
>;
|
|
34
|
+
const outputDetails = (raw.outputTokenDetails ?? raw.output_token_details ?? {}) as Record<
|
|
35
|
+
string,
|
|
36
|
+
unknown
|
|
37
|
+
>;
|
|
38
|
+
return {
|
|
39
|
+
inputTokens: {
|
|
40
|
+
total:
|
|
41
|
+
typeof raw.inputTokens === "number"
|
|
42
|
+
? raw.inputTokens
|
|
43
|
+
: typeof raw.prompt_tokens === "number"
|
|
44
|
+
? raw.prompt_tokens
|
|
45
|
+
: undefined,
|
|
46
|
+
noCache:
|
|
47
|
+
typeof inputDetails.noCacheTokens === "number" ? inputDetails.noCacheTokens : undefined,
|
|
48
|
+
cacheRead:
|
|
49
|
+
typeof inputDetails.cacheReadTokens === "number" ? inputDetails.cacheReadTokens : undefined,
|
|
50
|
+
cacheWrite:
|
|
51
|
+
typeof inputDetails.cacheWriteTokens === "number"
|
|
52
|
+
? inputDetails.cacheWriteTokens
|
|
53
|
+
: undefined,
|
|
54
|
+
},
|
|
55
|
+
outputTokens: {
|
|
56
|
+
total:
|
|
57
|
+
typeof raw.outputTokens === "number"
|
|
58
|
+
? raw.outputTokens
|
|
59
|
+
: typeof raw.completion_tokens === "number"
|
|
60
|
+
? raw.completion_tokens
|
|
61
|
+
: undefined,
|
|
62
|
+
text: typeof outputDetails.textTokens === "number" ? outputDetails.textTokens : undefined,
|
|
63
|
+
reasoning:
|
|
64
|
+
typeof outputDetails.reasoningTokens === "number"
|
|
65
|
+
? outputDetails.reasoningTokens
|
|
66
|
+
: undefined,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toStreamPart(event: RawEvent): LanguageModelV3StreamPart | null {
|
|
72
|
+
switch (event.type) {
|
|
73
|
+
case "start":
|
|
74
|
+
return { type: "stream-start", warnings: [] };
|
|
75
|
+
|
|
76
|
+
case "text-start":
|
|
77
|
+
return { type: "text-start", id: event.id as string };
|
|
78
|
+
case "text-delta":
|
|
79
|
+
return {
|
|
80
|
+
type: "text-delta",
|
|
81
|
+
id: event.id as string,
|
|
82
|
+
delta: (event.text ?? event.delta ?? "") as string,
|
|
83
|
+
};
|
|
84
|
+
case "text-end":
|
|
85
|
+
return { type: "text-end", id: event.id as string };
|
|
86
|
+
|
|
87
|
+
case "reasoning-start":
|
|
88
|
+
return { type: "reasoning-start", id: event.id as string };
|
|
89
|
+
case "reasoning-delta":
|
|
90
|
+
return {
|
|
91
|
+
type: "reasoning-delta",
|
|
92
|
+
id: event.id as string,
|
|
93
|
+
delta: (event.text ?? event.delta ?? "") as string,
|
|
94
|
+
};
|
|
95
|
+
case "reasoning-end":
|
|
96
|
+
return { type: "reasoning-end", id: event.id as string };
|
|
97
|
+
|
|
98
|
+
case "tool-input-start":
|
|
99
|
+
return {
|
|
100
|
+
type: "tool-input-start",
|
|
101
|
+
id: event.id as string,
|
|
102
|
+
toolName: event.toolName as string,
|
|
103
|
+
dynamic: event.dynamic as boolean | undefined,
|
|
104
|
+
};
|
|
105
|
+
case "tool-input-delta":
|
|
106
|
+
return {
|
|
107
|
+
type: "tool-input-delta",
|
|
108
|
+
id: event.id as string,
|
|
109
|
+
delta: (event.delta ?? "") as string,
|
|
110
|
+
};
|
|
111
|
+
case "tool-input-end":
|
|
112
|
+
return { type: "tool-input-end", id: event.id as string };
|
|
113
|
+
|
|
114
|
+
case "tool-call": {
|
|
115
|
+
const input = event.input ?? event.args ?? event.arguments;
|
|
116
|
+
return {
|
|
117
|
+
type: "tool-call",
|
|
118
|
+
toolCallId: (event.toolCallId ?? event.id ?? "") as string,
|
|
119
|
+
toolName: event.toolName as string,
|
|
120
|
+
input: typeof input === "string" ? input : JSON.stringify(input ?? {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
case "finish-step": {
|
|
125
|
+
const usage = event.usage ?? event.totalUsage ?? {};
|
|
126
|
+
const rawReason = (event.finishReason ?? event.rawFinishReason ?? "stop") as string;
|
|
127
|
+
return {
|
|
128
|
+
type: "finish",
|
|
129
|
+
finishReason: { unified: mapFinishReason(rawReason), raw: rawReason },
|
|
130
|
+
usage: mapUsage(
|
|
131
|
+
typeof usage === "object" && usage !== null ? (usage as Record<string, unknown>) : {},
|
|
132
|
+
),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case "finish":
|
|
137
|
+
return null;
|
|
138
|
+
|
|
139
|
+
case "response-metadata":
|
|
140
|
+
return {
|
|
141
|
+
type: "response-metadata",
|
|
142
|
+
id: event.id as string | undefined,
|
|
143
|
+
modelId: event.modelId as string | undefined,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
case "error":
|
|
147
|
+
return { type: "error", error: event.error ?? event.message ?? "Unknown error" };
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Assumes line-delimited JSON over SSE: one JSON object per `data:` line.
|
|
155
|
+
// Multi-line `data:` fields are not supported — the buffer splits on `\n`.
|
|
156
|
+
export function parseStreamEvents(
|
|
157
|
+
body: ReadableStream<Uint8Array>,
|
|
158
|
+
): ReadableStream<LanguageModelV3StreamPart> {
|
|
159
|
+
const reader = body.getReader();
|
|
160
|
+
const decoder = new TextDecoder();
|
|
161
|
+
let buffer = "";
|
|
162
|
+
|
|
163
|
+
return new ReadableStream<LanguageModelV3StreamPart>({
|
|
164
|
+
async pull(controller) {
|
|
165
|
+
try {
|
|
166
|
+
while (true) {
|
|
167
|
+
const lines = buffer.split("\n");
|
|
168
|
+
// Strip trailing \r from Windows-style \r\n line endings
|
|
169
|
+
for (let i = 0; i < lines.length; i++) {
|
|
170
|
+
const line = lines[i];
|
|
171
|
+
if (line !== undefined && line.endsWith("\r")) lines[i] = line.slice(0, -1);
|
|
172
|
+
}
|
|
173
|
+
buffer = lines.pop() ?? "";
|
|
174
|
+
|
|
175
|
+
for (const line of lines) {
|
|
176
|
+
const trimmed = line.trim();
|
|
177
|
+
if (!trimmed || trimmed.startsWith(":") || trimmed === "[DONE]") continue;
|
|
178
|
+
|
|
179
|
+
let jsonStr = trimmed;
|
|
180
|
+
if (jsonStr.startsWith("data: ")) jsonStr = jsonStr.slice(6);
|
|
181
|
+
if (jsonStr.startsWith("data:")) jsonStr = jsonStr.slice(5);
|
|
182
|
+
if (!jsonStr || jsonStr === "[DONE]") continue;
|
|
183
|
+
|
|
184
|
+
let parsed: RawEvent;
|
|
185
|
+
try {
|
|
186
|
+
parsed = JSON.parse(jsonStr);
|
|
187
|
+
} catch {
|
|
188
|
+
// intentionally silent: skip malformed SSE lines
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.type !== "string")
|
|
193
|
+
continue;
|
|
194
|
+
|
|
195
|
+
const part = toStreamPart(parsed);
|
|
196
|
+
if (part) controller.enqueue(part);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { done, value } = await reader.read();
|
|
200
|
+
if (done) {
|
|
201
|
+
if (buffer.trim()) {
|
|
202
|
+
const trimmed = buffer.trim();
|
|
203
|
+
if (trimmed && trimmed !== "[DONE]" && !trimmed.startsWith(":")) {
|
|
204
|
+
let jsonStr = trimmed;
|
|
205
|
+
if (jsonStr.startsWith("data: ")) jsonStr = jsonStr.slice(6);
|
|
206
|
+
if (jsonStr.startsWith("data:")) jsonStr = jsonStr.slice(5);
|
|
207
|
+
try {
|
|
208
|
+
const parsed = JSON.parse(jsonStr);
|
|
209
|
+
if (
|
|
210
|
+
typeof parsed === "object" &&
|
|
211
|
+
parsed !== null &&
|
|
212
|
+
typeof parsed.type === "string"
|
|
213
|
+
) {
|
|
214
|
+
const part = toStreamPart(parsed);
|
|
215
|
+
if (part) controller.enqueue(part);
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// intentionally silent: skip malformed final buffer
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
controller.close();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
buffer += decoder.decode(value, { stream: true });
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
controller.enqueue({ type: "error", error: err });
|
|
230
|
+
controller.close();
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
cancel() {
|
|
234
|
+
reader.cancel();
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|