@andesine/sdk 0.1.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.
@@ -0,0 +1,193 @@
1
+ import { EventDecoderStream } from "@orpc/standard-server";
2
+ //#region src/error.ts
3
+ /** An HTTP API failure with its response, error code, contract data, and suggested next steps. */
4
+ var AndesineAPIError = class extends Error {
5
+ defined;
6
+ status;
7
+ code;
8
+ data;
9
+ response;
10
+ /**
11
+ * @param response - Failed Fetch response, retained for status and headers such as Retry-After.
12
+ * @param body - Parsed error envelope, or the original body for an unrecognized error format.
13
+ */
14
+ constructor(response, body) {
15
+ const error = body && typeof body === "object" ? body : {};
16
+ super(typeof error.message === "string" ? error.message : `Andesine request failed (${response.status})`);
17
+ this.name = "AndesineAPIError";
18
+ this.defined = error.defined === true;
19
+ this.status = response.status;
20
+ this.code = typeof error.code === "string" ? error.code : "HTTP_ERROR";
21
+ this.data = "code" in error ? error.data : body;
22
+ this.response = response;
23
+ }
24
+ /**
25
+ * Check and narrow the code and data of an error declared by the API contract.
26
+ * @param code - Contract error code to match.
27
+ * @returns True only when the API marks the error as defined and its code matches.
28
+ */
29
+ is(code) {
30
+ return this.defined && this.code === code;
31
+ }
32
+ /** Suggested next steps. Do not use the wording for program logic. */
33
+ get hints() {
34
+ const hints = this.data && typeof this.data === "object" && "hints" in this.data ? this.data.hints : void 0;
35
+ return Array.isArray(hints) ? hints.filter((hint) => typeof hint === "string") : [];
36
+ }
37
+ };
38
+ /** An API error frame received after SSE headers. status is the frame status, not HTTP 200. */
39
+ var AndesineStreamError = class extends AndesineAPIError {
40
+ status;
41
+ /**
42
+ * @param response - Original SSE response; response.status retains its actual HTTP status.
43
+ * @param body - Parsed error frame. Its numeric status overrides the HTTP status on this error.
44
+ */
45
+ constructor(response, body) {
46
+ super(response, body);
47
+ this.name = "AndesineStreamError";
48
+ this.status = body && typeof body === "object" && "status" in body && typeof body.status === "number" ? body.status : response.status;
49
+ }
50
+ };
51
+ /** A local invalid/incomplete stream failure. It does not invent an API error or HTTP status. */
52
+ var AndesineStreamProtocolError = class extends Error {
53
+ code;
54
+ response;
55
+ /**
56
+ * @param code - INVALID_STREAM for malformed events; INCOMPLETE_STREAM for missing completion.
57
+ * @param response - Original SSE response for inspection.
58
+ * @param message - Description of the protocol failure.
59
+ * @param options - Optional underlying cause.
60
+ */
61
+ constructor(code, response, message, options) {
62
+ super(message, options);
63
+ this.name = "AndesineStreamProtocolError";
64
+ this.code = code;
65
+ this.response = response;
66
+ }
67
+ };
68
+ //#endregion
69
+ //#region src/answer-stream.ts
70
+ const isEventBody = (value) => {
71
+ return value !== null && typeof value === "object";
72
+ };
73
+ const isStreamErrorBody = (value) => {
74
+ const hasCode = typeof value.code === "string";
75
+ const status = value.status;
76
+ return hasCode && typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599;
77
+ };
78
+ const createAnswerStream = (response, options = {}) => {
79
+ const body = response.body;
80
+ const contentType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
81
+ const isEventStream = body !== null && contentType === "text/event-stream";
82
+ const controller = new AbortController();
83
+ const signal = options.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal;
84
+ const cancel = () => {
85
+ controller.abort();
86
+ options.cancel?.();
87
+ };
88
+ const invalid = (message) => {
89
+ return new AndesineStreamProtocolError("INVALID_STREAM", response, message);
90
+ };
91
+ if (!isEventStream) {
92
+ cancel();
93
+ body?.cancel().catch(() => {});
94
+ throw invalid("Expected an SSE response");
95
+ }
96
+ const reader = body.pipeThrough(new TextDecoderStream("utf-8", { fatal: true })).pipeThrough(new EventDecoderStream()).getReader();
97
+ const abort = () => {
98
+ reader.cancel().catch(() => {});
99
+ options.cleanup?.();
100
+ };
101
+ const cleanup = async () => {
102
+ signal.removeEventListener("abort", abort);
103
+ options.cleanup?.();
104
+ await reader.cancel().catch(() => {});
105
+ reader.releaseLock();
106
+ };
107
+ const iterate = async function* () {
108
+ let started = false;
109
+ let completed = false;
110
+ let answer = "";
111
+ try {
112
+ while (true) {
113
+ signal.throwIfAborted();
114
+ const frame = await reader.read();
115
+ signal.throwIfAborted();
116
+ if (frame.done || frame.value.event === "done") {
117
+ if (!completed) throw new AndesineStreamProtocolError("INCOMPLETE_STREAM", response, "The answer stream ended without completion");
118
+ return;
119
+ }
120
+ const { event, data } = frame.value;
121
+ const isComment = !event && data === void 0;
122
+ const isSupportedEvent = event === "message" || event === "error";
123
+ if (isComment) continue;
124
+ if (!isSupportedEvent) throw invalid("Unexpected SSE event");
125
+ let value;
126
+ try {
127
+ value = JSON.parse(data ?? "");
128
+ } catch {
129
+ throw invalid("Invalid JSON in an SSE event");
130
+ }
131
+ if (!isEventBody(value)) throw invalid("Invalid stream event body");
132
+ if (event === "error") {
133
+ if (!isStreamErrorBody(value)) throw invalid("Invalid stream error body");
134
+ throw new AndesineStreamError(response, value);
135
+ }
136
+ if (!(!completed && "type" in value)) throw invalid("Unexpected answer event");
137
+ switch (value.type) {
138
+ case "sources":
139
+ if (!(!started && Array.isArray(value.sources))) throw invalid("Invalid sources event");
140
+ started = true;
141
+ break;
142
+ case "textDelta": {
143
+ const text = value.text;
144
+ if (!(started && typeof text === "string" && text.length > 0)) throw invalid("Invalid text delta");
145
+ answer += text;
146
+ break;
147
+ }
148
+ case "completed": {
149
+ const hasAnswer = typeof value.answer === "string";
150
+ const hasSources = Array.isArray(value.sources);
151
+ const matchesDeltas = answer.length === 0 || value.answer === answer;
152
+ if (!(started && hasAnswer && hasSources && matchesDeltas)) throw invalid("Invalid completion event");
153
+ completed = true;
154
+ break;
155
+ }
156
+ default: throw invalid("Unknown answer event type");
157
+ }
158
+ yield value;
159
+ }
160
+ } catch (error) {
161
+ signal.throwIfAborted();
162
+ if (error instanceof AndesineStreamError || error instanceof AndesineStreamProtocolError) throw error;
163
+ throw new AndesineStreamProtocolError("INCOMPLETE_STREAM", response, "The answer stream was interrupted", { cause: error });
164
+ } finally {
165
+ await cleanup();
166
+ }
167
+ };
168
+ const iterator = iterate();
169
+ signal.addEventListener("abort", abort, { once: true });
170
+ if (signal.aborted) abort();
171
+ return {
172
+ next: () => iterator.next(),
173
+ return: async () => {
174
+ cancel();
175
+ abort();
176
+ await cleanup();
177
+ return iterator.return();
178
+ },
179
+ throw: async (error) => {
180
+ cancel();
181
+ abort();
182
+ await cleanup();
183
+ return iterator.throw(error);
184
+ },
185
+ [Symbol.asyncIterator]() {
186
+ return this;
187
+ }
188
+ };
189
+ };
190
+ //#endregion
191
+ export { AndesineStreamProtocolError as i, AndesineAPIError as n, AndesineStreamError as r, createAnswerStream as t };
192
+
193
+ //# sourceMappingURL=answer-stream-7N_gLhVS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-stream-7N_gLhVS.js","names":[],"sources":["../src/error.ts","../src/answer-stream.ts"],"sourcesContent":["import type { APIErrorCode, APIErrorData } from \"./operation\";\n\n/** An HTTP API failure with its response, error code, contract data, and suggested next steps. */\nclass AndesineAPIError extends Error {\n readonly defined: boolean;\n readonly status: number;\n readonly code: string;\n readonly data: unknown;\n readonly response: Response;\n\n /**\n * @param response - Failed Fetch response, retained for status and headers such as Retry-After.\n * @param body - Parsed error envelope, or the original body for an unrecognized error format.\n */\n constructor(response: Response, body: unknown) {\n const error = body && typeof body === \"object\" ? (body as Record<string, unknown>) : {};\n\n super(\n typeof error.message === \"string\"\n ? error.message\n : `Andesine request failed (${response.status})`\n );\n this.name = \"AndesineAPIError\";\n this.defined = error.defined === true;\n this.status = response.status;\n this.code = typeof error.code === \"string\" ? error.code : \"HTTP_ERROR\";\n this.data = \"code\" in error ? error.data : body;\n this.response = response;\n }\n /**\n * Check and narrow the code and data of an error declared by the API contract.\n * @param code - Contract error code to match.\n * @returns True only when the API marks the error as defined and its code matches.\n */\n is<C extends APIErrorCode>(\n code: C\n ): this is this & { defined: true; code: C; data: APIErrorData<C> } {\n return this.defined && this.code === code;\n }\n\n /** Suggested next steps. Do not use the wording for program logic. */\n get hints(): string[] {\n const hints =\n this.data && typeof this.data === \"object\" && \"hints\" in this.data\n ? this.data.hints\n : undefined;\n\n return Array.isArray(hints)\n ? hints.filter((hint): hint is string => typeof hint === \"string\")\n : [];\n }\n}\n\n/** An API error frame received after SSE headers. status is the frame status, not HTTP 200. */\nclass AndesineStreamError extends AndesineAPIError {\n override readonly status: number;\n /**\n * @param response - Original SSE response; response.status retains its actual HTTP status.\n * @param body - Parsed error frame. Its numeric status overrides the HTTP status on this error.\n */\n constructor(response: Response, body: unknown) {\n super(response, body);\n this.name = \"AndesineStreamError\";\n this.status =\n body && typeof body === \"object\" && \"status\" in body && typeof body.status === \"number\"\n ? body.status\n : response.status;\n }\n}\n\n/** A local invalid/incomplete stream failure. It does not invent an API error or HTTP status. */\nclass AndesineStreamProtocolError extends Error {\n readonly code: \"INVALID_STREAM\" | \"INCOMPLETE_STREAM\";\n readonly response: Response;\n\n /**\n * @param code - INVALID_STREAM for malformed events; INCOMPLETE_STREAM for missing completion.\n * @param response - Original SSE response for inspection.\n * @param message - Description of the protocol failure.\n * @param options - Optional underlying cause.\n */\n constructor(\n code: \"INVALID_STREAM\" | \"INCOMPLETE_STREAM\",\n response: Response,\n message: string,\n options?: ErrorOptions\n ) {\n super(message, options);\n this.name = \"AndesineStreamProtocolError\";\n this.code = code;\n this.response = response;\n }\n}\n\nexport { AndesineAPIError, AndesineStreamError, AndesineStreamProtocolError };\n","import { EventDecoderStream } from \"@orpc/standard-server\";\nimport type { AnswerEvent } from \"./generated/schema\";\nimport { AndesineStreamError, AndesineStreamProtocolError } from \"./error\";\n\ninterface AnswerStreamOptions {\n signal?: AbortSignal;\n cancel?: () => void;\n cleanup?: () => void;\n}\n\nconst isEventBody = (value: unknown): value is Record<string, unknown> => {\n return value !== null && typeof value === \"object\";\n};\nconst isStreamErrorBody = (value: Record<string, unknown>): boolean => {\n const hasCode = typeof value.code === \"string\";\n const status = value.status;\n const hasErrorStatus =\n typeof status === \"number\" && Number.isInteger(status) && status >= 400 && status <= 599;\n\n return hasCode && hasErrorStatus;\n};\n\nconst createAnswerStream = (\n response: Response,\n options: AnswerStreamOptions = {}\n): AsyncIterableIterator<AnswerEvent> => {\n const body = response.body;\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0]?.trim().toLowerCase();\n const isEventStream = body !== null && contentType === \"text/event-stream\";\n const controller = new AbortController();\n const signal = options.signal\n ? AbortSignal.any([controller.signal, options.signal])\n : controller.signal;\n const cancel = () => {\n controller.abort();\n options.cancel?.();\n };\n const invalid = (message: string) => {\n return new AndesineStreamProtocolError(\"INVALID_STREAM\", response, message);\n };\n\n if (!isEventStream) {\n cancel();\n void body?.cancel().catch(() => {});\n throw invalid(\"Expected an SSE response\");\n }\n\n const reader = body\n .pipeThrough(new TextDecoderStream(\"utf-8\", { fatal: true }))\n .pipeThrough(new EventDecoderStream())\n .getReader();\n const abort = () => {\n void reader.cancel().catch(() => {});\n options.cleanup?.();\n };\n const cleanup = async () => {\n signal.removeEventListener(\"abort\", abort);\n options.cleanup?.();\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n };\n const iterate = async function* (): AsyncGenerator<AnswerEvent, void, unknown> {\n let started = false;\n let completed = false;\n let answer = \"\";\n\n try {\n while (true) {\n signal.throwIfAborted();\n\n const frame = await reader.read();\n\n signal.throwIfAborted();\n\n if (frame.done || frame.value.event === \"done\") {\n if (!completed) {\n throw new AndesineStreamProtocolError(\n \"INCOMPLETE_STREAM\",\n response,\n \"The answer stream ended without completion\"\n );\n }\n return;\n }\n\n const { event, data } = frame.value;\n const isComment = !event && data === undefined;\n const isSupportedEvent = event === \"message\" || event === \"error\";\n\n if (isComment) continue;\n\n if (!isSupportedEvent) {\n throw invalid(\"Unexpected SSE event\");\n }\n\n let value: unknown;\n\n try {\n value = JSON.parse(data ?? \"\");\n } catch {\n throw invalid(\"Invalid JSON in an SSE event\");\n }\n\n if (!isEventBody(value)) throw invalid(\"Invalid stream event body\");\n\n if (event === \"error\") {\n if (!isStreamErrorBody(value)) throw invalid(\"Invalid stream error body\");\n\n throw new AndesineStreamError(response, value);\n }\n\n const canAcceptAnswerEvent = !completed && \"type\" in value;\n\n if (!canAcceptAnswerEvent) throw invalid(\"Unexpected answer event\");\n\n switch (value.type) {\n case \"sources\": {\n const isValidSources = !started && Array.isArray(value.sources);\n\n if (!isValidSources) throw invalid(\"Invalid sources event\");\n\n started = true;\n break;\n }\n case \"textDelta\": {\n const text = value.text;\n const isValidDelta = started && typeof text === \"string\" && text.length > 0;\n\n if (!isValidDelta) throw invalid(\"Invalid text delta\");\n\n answer += text;\n break;\n }\n case \"completed\": {\n const hasAnswer = typeof value.answer === \"string\";\n const hasSources = Array.isArray(value.sources);\n const matchesDeltas = answer.length === 0 || value.answer === answer;\n const isValidCompletion = started && hasAnswer && hasSources && matchesDeltas;\n\n if (!isValidCompletion) throw invalid(\"Invalid completion event\");\n\n completed = true;\n break;\n }\n default:\n throw invalid(\"Unknown answer event type\");\n }\n\n yield value as AnswerEvent;\n }\n } catch (error) {\n signal.throwIfAborted();\n const isKnownStreamError =\n error instanceof AndesineStreamError || error instanceof AndesineStreamProtocolError;\n\n if (isKnownStreamError) throw error;\n\n throw new AndesineStreamProtocolError(\n \"INCOMPLETE_STREAM\",\n response,\n \"The answer stream was interrupted\",\n { cause: error }\n );\n } finally {\n await cleanup();\n }\n };\n const iterator = iterate();\n\n signal.addEventListener(\"abort\", abort, { once: true });\n if (signal.aborted) abort();\n\n return {\n next: () => iterator.next(),\n return: async () => {\n cancel();\n abort();\n await cleanup();\n return iterator.return();\n },\n throw: async (error: unknown) => {\n cancel();\n abort();\n await cleanup();\n return iterator.throw(error);\n },\n [Symbol.asyncIterator]() {\n return this;\n }\n };\n};\n\nexport { createAnswerStream };\n"],"mappings":"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"}