@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/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
+ }