conduyt 1.2.1 → 1.4.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.
- package/dist/client.js +87 -1
- package/dist/index.js +1203 -2
- package/package.json +2 -2
package/dist/client.js
CHANGED
|
@@ -20,7 +20,14 @@ export class ConduytClient {
|
|
|
20
20
|
const text = await res.text();
|
|
21
21
|
const json = text ? safeParse(text) : null;
|
|
22
22
|
if (!res.ok) {
|
|
23
|
-
|
|
23
|
+
// Preserve structured non-2xx bodies: several endpoints return the data
|
|
24
|
+
// a caller needs to proceed IN the error body (e.g. custom-field delete
|
|
25
|
+
// 409 carries valueCount/dependencyCount for the --confirm flow).
|
|
26
|
+
// Reducing those to statusText would strand the user.
|
|
27
|
+
const structured = json && typeof json === "object" && !json.error
|
|
28
|
+
? JSON.stringify(json)
|
|
29
|
+
: null;
|
|
30
|
+
const msg = json?.error || structured || res.statusText || `HTTP ${res.status}`;
|
|
24
31
|
throw new Error(`Conduyt API ${res.status}: ${msg}`);
|
|
25
32
|
}
|
|
26
33
|
return json;
|
|
@@ -37,6 +44,85 @@ export class ConduytClient {
|
|
|
37
44
|
del(path) {
|
|
38
45
|
return this.request("DELETE", path);
|
|
39
46
|
}
|
|
47
|
+
// Stream `ai chat`'s text/event-stream response to stdout as clean answer
|
|
48
|
+
// text. The server frames SSE records as `data: {"text":"..."}\n\n`, ends
|
|
49
|
+
// with `data: [DONE]\n\n`, and reports in-stream failures as
|
|
50
|
+
// `data: {"error":"..."}\n\n` — so we parse events (buffering across chunk
|
|
51
|
+
// splits), print only the text deltas, stop cleanly on [DONE], and THROW on
|
|
52
|
+
// an error event so the command exits non-zero instead of "succeeding" with
|
|
53
|
+
// raw protocol noise.
|
|
54
|
+
async stream(method, path, body) {
|
|
55
|
+
const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
56
|
+
const res = await fetch(url, {
|
|
57
|
+
method,
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
Accept: "text/event-stream",
|
|
62
|
+
},
|
|
63
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok || !res.body) {
|
|
66
|
+
const text = await res.text();
|
|
67
|
+
const json = text ? safeParse(text) : null;
|
|
68
|
+
const msg = json?.error || res.statusText || `HTTP ${res.status}`;
|
|
69
|
+
throw new Error(`Conduyt API ${res.status}: ${msg}`);
|
|
70
|
+
}
|
|
71
|
+
const reader = res.body.getReader();
|
|
72
|
+
const decoder = new TextDecoder();
|
|
73
|
+
let buffer = "";
|
|
74
|
+
let wroteAny = false;
|
|
75
|
+
const handleEvent = (payload) => {
|
|
76
|
+
if (payload === "[DONE]")
|
|
77
|
+
return "done";
|
|
78
|
+
const parsed = safeParse(payload);
|
|
79
|
+
if (parsed && typeof parsed === "object") {
|
|
80
|
+
const rec = parsed;
|
|
81
|
+
if (typeof rec.error === "string" && rec.error) {
|
|
82
|
+
if (wroteAny)
|
|
83
|
+
process.stdout.write("\n");
|
|
84
|
+
throw new Error(`AI stream error: ${rec.error}`);
|
|
85
|
+
}
|
|
86
|
+
if (typeof rec.text === "string" && rec.text) {
|
|
87
|
+
process.stdout.write(rec.text);
|
|
88
|
+
wroteAny = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
};
|
|
93
|
+
const drainBuffer = () => {
|
|
94
|
+
// SSE events are separated by a blank line; fields we care about are
|
|
95
|
+
// `data: <payload>` lines (multi-line data joins with \n per spec).
|
|
96
|
+
let sep;
|
|
97
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
98
|
+
const rawEvent = buffer.slice(0, sep);
|
|
99
|
+
buffer = buffer.slice(sep + 2);
|
|
100
|
+
const dataLines = rawEvent
|
|
101
|
+
.split("\n")
|
|
102
|
+
.filter((l) => l.startsWith("data:"))
|
|
103
|
+
.map((l) => l.slice(5).replace(/^ /, ""));
|
|
104
|
+
if (dataLines.length === 0)
|
|
105
|
+
continue;
|
|
106
|
+
if (handleEvent(dataLines.join("\n")) === "done")
|
|
107
|
+
return "done";
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
};
|
|
111
|
+
outer: for (;;) {
|
|
112
|
+
const { done, value } = await reader.read();
|
|
113
|
+
if (value) {
|
|
114
|
+
buffer += decoder.decode(value, { stream: true });
|
|
115
|
+
if (drainBuffer() === "done")
|
|
116
|
+
break outer;
|
|
117
|
+
}
|
|
118
|
+
if (done)
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
buffer += decoder.decode();
|
|
122
|
+
drainBuffer();
|
|
123
|
+
if (wroteAny)
|
|
124
|
+
process.stdout.write("\n");
|
|
125
|
+
}
|
|
40
126
|
}
|
|
41
127
|
function safeParse(text) {
|
|
42
128
|
try {
|