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 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
- const msg = json?.error || res.statusText || `HTTP ${res.status}`;
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 {