Skip to content

Commit 458d3fc

Browse files
authored
Merge pull request #112 from ut-code/copilot/update-api-call-to-ndjson
Migrate Wandbox API from /api/compile.json to /api/compile.ndjson with streaming support
2 parents a3ab2ee + b3f566e commit 458d3fc

File tree

2 files changed

+142
-43
lines changed

2 files changed

+142
-43
lines changed

app/terminal/wandbox/api.ts

Lines changed: 129 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ export interface CompileParameter {
6262
is_private?: boolean;
6363
"compiler-info"?: CompilerInfo;
6464
}
65+
/**
66+
* Represents a single line in the ndjson response from /api/compile.ndjson
67+
*/
68+
export interface CompileNdjsonResult {
69+
type: string;
70+
data: string;
71+
}
72+
6573
export interface CompileResult {
6674
status: string;
6775
signal: string;
@@ -93,17 +101,15 @@ interface CompileProps {
93101
codes: Code[];
94102
}
95103
export interface CompileResultWithOutput extends CompileResult {
96-
compilerOutput: ReplOutput[];
97-
compilerError: ReplOutput[];
98-
programOutput: ReplOutput[];
99-
programError: ReplOutput[];
104+
output: ReplOutput[];
100105
}
101106

102107
export async function compileAndRun(
103108
options: CompileProps
104109
): Promise<CompileResultWithOutput> {
105-
const result: CompileResult = await fetch(
106-
new URL("/api/compile.json", WANDBOX),
110+
// Call the ndjson API instead of json API
111+
const response = await fetch(
112+
new URL("/api/compile.ndjson", WANDBOX),
107113
{
108114
method: "post",
109115
headers: {
@@ -121,32 +127,124 @@ export async function compileAndRun(
121127
is_private: true,
122128
} satisfies CompileParameter),
123129
}
124-
).then((res) => res.json());
130+
);
131+
132+
if (!response.ok) {
133+
throw new Error(`HTTP error! status: ${response.status}`);
134+
}
135+
136+
// Read the ndjson response as a stream
137+
const reader = response.body?.getReader();
138+
if (!reader) {
139+
throw new Error("Response body is not readable");
140+
}
141+
142+
const decoder = new TextDecoder();
143+
let buffer = "";
144+
const ndjsonResults: CompileNdjsonResult[] = [];
145+
146+
try {
147+
while (true) {
148+
const { done, value } = await reader.read();
149+
if (done) break;
150+
151+
buffer += decoder.decode(value, { stream: true });
152+
const lines = buffer.split("\n");
153+
154+
// Keep the last incomplete line in the buffer
155+
buffer = lines.pop() || "";
156+
157+
for (const line of lines) {
158+
if (line.trim().length > 0) {
159+
ndjsonResults.push(JSON.parse(line) as CompileNdjsonResult);
160+
}
161+
}
162+
}
163+
164+
// Process any remaining data in the buffer
165+
if (buffer.trim().length > 0) {
166+
ndjsonResults.push(JSON.parse(buffer) as CompileNdjsonResult);
167+
}
168+
} finally {
169+
reader.releaseLock();
170+
}
171+
172+
// Merge ndjson results into a CompileResult (same logic as Rust merge_compile_result)
173+
const result: CompileResult = {
174+
status: "",
175+
signal: "",
176+
compiler_output: "",
177+
compiler_error: "",
178+
compiler_message: "",
179+
program_output: "",
180+
program_error: "",
181+
program_message: "",
182+
permlink: "",
183+
url: "",
184+
};
185+
186+
// Build output array in the order messages are received
187+
const output: ReplOutput[] = [];
188+
189+
for (const r of ndjsonResults) {
190+
switch (r.type) {
191+
case "Control":
192+
// Ignore control messages
193+
break;
194+
case "CompilerMessageS":
195+
result.compiler_output += r.data;
196+
result.compiler_message += r.data;
197+
// Add to output in order
198+
if (r.data.trim()) {
199+
for (const line of r.data.trim().split("\n")) {
200+
output.push({ type: "stdout", message: line });
201+
}
202+
}
203+
break;
204+
case "CompilerMessageE":
205+
result.compiler_error += r.data;
206+
result.compiler_message += r.data;
207+
// Add to output in order
208+
if (r.data.trim()) {
209+
for (const line of r.data.trim().split("\n")) {
210+
output.push({ type: "error", message: line });
211+
}
212+
}
213+
break;
214+
case "StdOut":
215+
result.program_output += r.data;
216+
result.program_message += r.data;
217+
// Add to output in order
218+
if (r.data.trim()) {
219+
for (const line of r.data.trim().split("\n")) {
220+
output.push({ type: "stdout", message: line });
221+
}
222+
}
223+
break;
224+
case "StdErr":
225+
result.program_error += r.data;
226+
result.program_message += r.data;
227+
// Add to output in order
228+
if (r.data.trim()) {
229+
for (const line of r.data.trim().split("\n")) {
230+
output.push({ type: "stderr", message: line });
231+
}
232+
}
233+
break;
234+
case "ExitCode":
235+
result.status += r.data;
236+
break;
237+
case "Signal":
238+
result.signal += r.data;
239+
break;
240+
default:
241+
// Ignore unknown types
242+
break;
243+
}
244+
}
245+
125246
return {
126247
...result,
127-
compilerOutput: result.compiler_output.trim()
128-
? result.compiler_output
129-
.trim()
130-
.split("\n")
131-
.map((line) => ({ type: "stdout" as const, message: line }))
132-
: [],
133-
compilerError: result.compiler_error.trim()
134-
? result.compiler_error
135-
.trim()
136-
.split("\n")
137-
.map((line) => ({ type: "error" as const, message: line }))
138-
: [],
139-
programOutput: result.program_output.trim()
140-
? result.program_output
141-
.trim()
142-
.split("\n")
143-
.map((line) => ({ type: "stdout" as const, message: line }))
144-
: [],
145-
programError: result.program_error.trim()
146-
? result.program_error
147-
.trim()
148-
.split("\n")
149-
.map((line) => ({ type: "error" as const, message: line }))
150-
: [],
248+
output,
151249
};
152250
}

app/terminal/wandbox/cpp.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,33 +101,34 @@ export async function cppRunFiles(
101101
],
102102
});
103103

104-
const traceIndex = result.programError.findIndex(
105-
(line) => line.message === "Stack trace:"
104+
// Find stack trace in the output
105+
const traceIndex = result.output.findIndex(
106+
(line) => line.type === "stderr" && line.message === "Stack trace:"
106107
);
107-
const outputs: ReplOutput[] = [
108-
...result.compilerOutput,
109-
...result.compilerError,
110-
...result.programOutput,
111-
...(traceIndex >= 0
112-
? result.programError.slice(0, traceIndex)
113-
: result.programError),
114-
];
108+
109+
let outputs: ReplOutput[];
110+
115111
if (traceIndex >= 0) {
116112
// CPP_STACKTRACE_HANDLER のコードで出力されるスタックトレースを、js側でパースしていい感じに表示する
113+
outputs = result.output.slice(0, traceIndex);
117114
outputs.push({
118115
type: "trace" as const,
119116
message: "Stack trace (filtered):",
120117
});
121-
for (const line of result.programError.slice(traceIndex + 1)) {
118+
119+
for (const line of result.output.slice(traceIndex + 1)) {
122120
// ユーザーのソースコードだけを対象にする
123-
if (line.message.includes("/home/wandbox")) {
121+
if (line.type === "stderr" && line.message.includes("/home/wandbox")) {
124122
outputs.push({
125123
type: "trace" as const,
126124
message: line.message.replace("/home/wandbox/", ""),
127125
});
128126
}
129127
}
128+
} else {
129+
outputs = [...result.output];
130130
}
131+
131132
if (result.status !== "0") {
132133
outputs.push({
133134
type: "system" as const,

0 commit comments

Comments
 (0)