@galdor/provider-openai 0.3.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/convert.d.ts +165 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +302 -0
- package/dist/convert.js.map +1 -0
- package/dist/embed.d.ts +72 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +126 -0
- package/dist/embed.js.map +1 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +89 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/stream.d.ts +40 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +178 -0
- package/dist/stream.js.map +1 -0
- package/package.json +36 -0
- package/src/convert.ts +429 -0
- package/src/embed.test.ts +89 -0
- package/src/embed.ts +162 -0
- package/src/errors.ts +103 -0
- package/src/index.ts +198 -0
- package/src/openai.test.ts +184 -0
- package/src/stream.ts +245 -0
package/dist/embed.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI embeddings — a galdor {@link Embedder} over the `/embeddings` endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Works against the OpenAI API and any OpenAI-compatible endpoint (Mistral,
|
|
5
|
+
* Together, MiniMax, vLLM, Ollama, …) by pointing {@link EmbedderConfig.baseURL}
|
|
6
|
+
* at it. Construct one with {@link newEmbedder}.
|
|
7
|
+
*/
|
|
8
|
+
import { normalizeHTTPError } from "./errors.js";
|
|
9
|
+
/** OpenAI's default embedding model and its native dimensionality. */
|
|
10
|
+
const DEFAULT_MODEL = "text-embedding-3-small";
|
|
11
|
+
const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
12
|
+
/** Native vector size for a model when no explicit `dimensions` is configured. */
|
|
13
|
+
function nativeDim(model) {
|
|
14
|
+
return model === "text-embedding-3-large" ? 3072 : 1536;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A galdor {@link Embedder} backed by the OpenAI embeddings API.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const embedder = newEmbedder({ apiKey: process.env.OPENAI_API_KEY! });
|
|
22
|
+
* const [vec] = await embedder.embed(["quito ecuador capital"]);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class OpenAIEmbedder {
|
|
26
|
+
#apiKey;
|
|
27
|
+
#model;
|
|
28
|
+
/** Configured size, or learned from the first response when not configured. 0 = unknown. */
|
|
29
|
+
#dim;
|
|
30
|
+
#explicitDim;
|
|
31
|
+
#baseURL;
|
|
32
|
+
#organization;
|
|
33
|
+
#project;
|
|
34
|
+
#userAgent;
|
|
35
|
+
#timeoutMs;
|
|
36
|
+
/**
|
|
37
|
+
* @param cfg - Embedder configuration; `apiKey` is required.
|
|
38
|
+
* @throws {Error} When `apiKey` is missing or blank.
|
|
39
|
+
*/
|
|
40
|
+
constructor(cfg) {
|
|
41
|
+
if (!cfg.apiKey || cfg.apiKey.trim() === "")
|
|
42
|
+
throw new Error("openai: apiKey is required");
|
|
43
|
+
this.#apiKey = cfg.apiKey;
|
|
44
|
+
this.#model = cfg.model && cfg.model.trim() !== "" ? cfg.model : DEFAULT_MODEL;
|
|
45
|
+
this.#explicitDim = typeof cfg.dimensions === "number" && cfg.dimensions > 0;
|
|
46
|
+
// Leave 0 (unknown) when not configured; the first response fills it in.
|
|
47
|
+
this.#dim = this.#explicitDim ? cfg.dimensions : 0;
|
|
48
|
+
this.#baseURL = (cfg.baseURL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
49
|
+
this.#organization = cfg.organization ?? "";
|
|
50
|
+
this.#project = cfg.project ?? "";
|
|
51
|
+
this.#userAgent = cfg.userAgent ?? "";
|
|
52
|
+
this.#timeoutMs = cfg.timeoutMs ?? 60_000;
|
|
53
|
+
}
|
|
54
|
+
/** @returns The embedding model id. */
|
|
55
|
+
model() {
|
|
56
|
+
return this.#model;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* @returns The embedding vector size: the configured/learned size when known,
|
|
60
|
+
* otherwise the model's native default until the first response sets it.
|
|
61
|
+
*/
|
|
62
|
+
dimensions() {
|
|
63
|
+
return this.#dim > 0 ? this.#dim : nativeDim(this.#model);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Embed each input text, preserving order: `out[i]` is the vector for `texts[i]`.
|
|
67
|
+
*
|
|
68
|
+
* @param texts - Inputs to embed; an empty array returns `[]`.
|
|
69
|
+
* @returns One vector per input, ordered to match `texts`.
|
|
70
|
+
* @throws {Error} On a non-2xx response, or when the API returns the wrong number of vectors.
|
|
71
|
+
*/
|
|
72
|
+
async embed(texts, signal) {
|
|
73
|
+
if (texts.length === 0)
|
|
74
|
+
return [];
|
|
75
|
+
const body = { model: this.#model, input: texts, encoding_format: "float" };
|
|
76
|
+
// Only the text-embedding-3-* family accepts an explicit `dimensions`.
|
|
77
|
+
if (this.#explicitDim)
|
|
78
|
+
body.dimensions = this.#dim;
|
|
79
|
+
// Bound the whole request; a caller signal aborts alongside the timeout.
|
|
80
|
+
const timeout = this.#timeoutMs > 0 ? AbortSignal.timeout(this.#timeoutMs) : undefined;
|
|
81
|
+
const sig = signal && timeout ? AbortSignal.any([signal, timeout]) : (signal ?? timeout);
|
|
82
|
+
const res = await fetch(`${this.#baseURL}/embeddings`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
authorization: `Bearer ${this.#apiKey}`,
|
|
86
|
+
"content-type": "application/json",
|
|
87
|
+
...(this.#organization ? { "openai-organization": this.#organization } : {}),
|
|
88
|
+
...(this.#project ? { "openai-project": this.#project } : {}),
|
|
89
|
+
...(this.#userAgent ? { "user-agent": this.#userAgent } : {}),
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify(body),
|
|
92
|
+
...(sig ? { signal: sig } : {}),
|
|
93
|
+
});
|
|
94
|
+
if (Math.floor(res.status / 100) !== 2)
|
|
95
|
+
throw await normalizeHTTPError(res);
|
|
96
|
+
const parsed = (await res.json());
|
|
97
|
+
const data = parsed.data ?? [];
|
|
98
|
+
if (data.length !== texts.length) {
|
|
99
|
+
throw new Error(`openai: embeddings returned ${data.length} vectors for ${texts.length} inputs`);
|
|
100
|
+
}
|
|
101
|
+
// The API may return out-of-order; place each vector at its reported index.
|
|
102
|
+
const out = new Array(texts.length);
|
|
103
|
+
for (const item of data) {
|
|
104
|
+
if (item.index < 0 || item.index >= texts.length) {
|
|
105
|
+
throw new Error(`openai: embedding index ${item.index} out of range`);
|
|
106
|
+
}
|
|
107
|
+
out[item.index] = item.embedding;
|
|
108
|
+
}
|
|
109
|
+
// Cache the native dimensionality from the first successful response so
|
|
110
|
+
// dimensions() doesn't lie; a configured Dim stays authoritative.
|
|
111
|
+
if (this.#dim === 0 && out[0])
|
|
112
|
+
this.#dim = out[0].length;
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Construct an {@link OpenAIEmbedder}.
|
|
118
|
+
*
|
|
119
|
+
* @param cfg - Embedder configuration; `apiKey` is required.
|
|
120
|
+
* @returns A ready embedder.
|
|
121
|
+
* @throws {Error} When `apiKey` is missing or blank.
|
|
122
|
+
*/
|
|
123
|
+
export function newEmbedder(cfg) {
|
|
124
|
+
return new OpenAIEmbedder(cfg);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=embed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embed.js","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes failed OpenAI HTTP responses into galdor's typed {@link APIError}
|
|
3
|
+
* hierarchy. Maps the raw status code to an {@link ErrorKind}, then refines that
|
|
4
|
+
* kind from OpenAI's structured `error.type` / `error.code` body when present,
|
|
5
|
+
* and surfaces any `Retry-After` hint.
|
|
6
|
+
*/
|
|
7
|
+
import { APIError, type ErrorKind } from "@galdor/core/provider";
|
|
8
|
+
/**
|
|
9
|
+
* Refine an error classification from OpenAI's structured `error.type` and
|
|
10
|
+
* `error.code` fields, used when the bare status code is too ambiguous to
|
|
11
|
+
* classify on its own — for example, some OpenAI-compatible backends report a
|
|
12
|
+
* blown context window as a generic 400.
|
|
13
|
+
*
|
|
14
|
+
* @param t - The OpenAI `error.type` discriminator, if any.
|
|
15
|
+
* @param code - The OpenAI `error.code` discriminator, if any.
|
|
16
|
+
* @returns The refined {@link ErrorKind}, or `undefined` when neither field is
|
|
17
|
+
* recognized and the caller should fall back to the status-based kind.
|
|
18
|
+
*/
|
|
19
|
+
export declare function kindForType(t: string | undefined, code: string | undefined): ErrorKind | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Minimal structural view of an HTTP response that {@link normalizeHTTPError}
|
|
22
|
+
* needs: the status code, a header accessor, and a text body reader. Any Fetch
|
|
23
|
+
* `Response` satisfies this shape.
|
|
24
|
+
*/
|
|
25
|
+
export interface ResponseLike {
|
|
26
|
+
status: number;
|
|
27
|
+
headers: {
|
|
28
|
+
get(name: string): string | null;
|
|
29
|
+
};
|
|
30
|
+
text(): Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Convert a non-2xx OpenAI response into a typed, classified galdor
|
|
34
|
+
* {@link APIError}.
|
|
35
|
+
*
|
|
36
|
+
* @param res - The failed HTTP response (status, headers, body).
|
|
37
|
+
* @returns A classified {@link APIError} carrying the provider name, status
|
|
38
|
+
* code, best-effort message, and any parsed `Retry-After` delay.
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const res = await fetch(url, opts);
|
|
42
|
+
* if (Math.floor(res.status / 100) !== 2) throw await normalizeHTTPError(res);
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function normalizeHTTPError(res: ResponseLike): Promise<APIError>;
|
|
46
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAY,KAAK,SAAS,EAAmB,MAAM,uBAAuB,CAAC;AAiB5F;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAuBlG;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC9C,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkB7E"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes failed OpenAI HTTP responses into galdor's typed {@link APIError}
|
|
3
|
+
* hierarchy. Maps the raw status code to an {@link ErrorKind}, then refines that
|
|
4
|
+
* kind from OpenAI's structured `error.type` / `error.code` body when present,
|
|
5
|
+
* and surfaces any `Retry-After` hint.
|
|
6
|
+
*/
|
|
7
|
+
import { APIError, classify, parseRetryAfter } from "@galdor/core/provider";
|
|
8
|
+
const PROVIDER_NAME = "openai";
|
|
9
|
+
/** Map a bare HTTP status code to a coarse {@link ErrorKind}. */
|
|
10
|
+
function kindForStatus(code) {
|
|
11
|
+
if (code === 401 || code === 403)
|
|
12
|
+
return "auth";
|
|
13
|
+
if (code === 429)
|
|
14
|
+
return "rate_limited";
|
|
15
|
+
if (code >= 500)
|
|
16
|
+
return "server";
|
|
17
|
+
if (code >= 400)
|
|
18
|
+
return "invalid_request";
|
|
19
|
+
return "server";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Refine an error classification from OpenAI's structured `error.type` and
|
|
23
|
+
* `error.code` fields, used when the bare status code is too ambiguous to
|
|
24
|
+
* classify on its own — for example, some OpenAI-compatible backends report a
|
|
25
|
+
* blown context window as a generic 400.
|
|
26
|
+
*
|
|
27
|
+
* @param t - The OpenAI `error.type` discriminator, if any.
|
|
28
|
+
* @param code - The OpenAI `error.code` discriminator, if any.
|
|
29
|
+
* @returns The refined {@link ErrorKind}, or `undefined` when neither field is
|
|
30
|
+
* recognized and the caller should fall back to the status-based kind.
|
|
31
|
+
*/
|
|
32
|
+
export function kindForType(t, code) {
|
|
33
|
+
switch (t) {
|
|
34
|
+
case "invalid_request_error":
|
|
35
|
+
return code === "context_length_exceeded" ? "context_window" : "invalid_request";
|
|
36
|
+
case "authentication_error":
|
|
37
|
+
case "permission_error":
|
|
38
|
+
return "auth";
|
|
39
|
+
case "rate_limit_error":
|
|
40
|
+
case "tokens_exceeded":
|
|
41
|
+
return "rate_limited";
|
|
42
|
+
case "server_error":
|
|
43
|
+
case "internal_server_error":
|
|
44
|
+
return "server";
|
|
45
|
+
}
|
|
46
|
+
switch (code) {
|
|
47
|
+
case "context_length_exceeded":
|
|
48
|
+
return "context_window";
|
|
49
|
+
case "rate_limit_exceeded":
|
|
50
|
+
return "rate_limited";
|
|
51
|
+
case "invalid_api_key":
|
|
52
|
+
return "auth";
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Convert a non-2xx OpenAI response into a typed, classified galdor
|
|
58
|
+
* {@link APIError}.
|
|
59
|
+
*
|
|
60
|
+
* @param res - The failed HTTP response (status, headers, body).
|
|
61
|
+
* @returns A classified {@link APIError} carrying the provider name, status
|
|
62
|
+
* code, best-effort message, and any parsed `Retry-After` delay.
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const res = await fetch(url, opts);
|
|
66
|
+
* if (Math.floor(res.status / 100) !== 2) throw await normalizeHTTPError(res);
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export async function normalizeHTTPError(res) {
|
|
70
|
+
const text = await res.text().catch(() => "");
|
|
71
|
+
let kind = kindForStatus(res.status);
|
|
72
|
+
let message = `openai: HTTP ${res.status}`;
|
|
73
|
+
if (text) {
|
|
74
|
+
try {
|
|
75
|
+
const body = JSON.parse(text);
|
|
76
|
+
if (body.error?.message)
|
|
77
|
+
message = body.error.message;
|
|
78
|
+
const k = kindForType(body.error?.type, body.error?.code);
|
|
79
|
+
if (k)
|
|
80
|
+
kind = k;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* non-JSON body: keep the status-based kind */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const retryAfter = parseRetryAfter(res.headers.get("retry-after") ?? "", new Date());
|
|
87
|
+
return classify(new APIError({ kind, provider: PROVIDER_NAME, statusCode: res.status, message, ...(retryAfter !== null ? { retryAfter } : {}) }));
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAkB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE5F,MAAM,aAAa,GAAG,QAAQ,CAAC;AAM/B,iEAAiE;AACjE,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC;IAChD,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,cAAc,CAAC;IACxC,IAAI,IAAI,IAAI,GAAG;QAAE,OAAO,QAAQ,CAAC;IACjC,IAAI,IAAI,IAAI,GAAG;QAAE,OAAO,iBAAiB,CAAC;IAC1C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,CAAqB,EAAE,IAAwB;IACzE,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,uBAAuB;YAC1B,OAAO,IAAI,KAAK,yBAAyB,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,iBAAiB,CAAC;QACnF,KAAK,sBAAsB,CAAC;QAC5B,KAAK,kBAAkB;YACrB,OAAO,MAAM,CAAC;QAChB,KAAK,kBAAkB,CAAC;QACxB,KAAK,iBAAiB;YACpB,OAAO,cAAc,CAAC;QACxB,KAAK,cAAc,CAAC;QACpB,KAAK,uBAAuB;YAC1B,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,yBAAyB;YAC5B,OAAO,gBAAgB,CAAC;QAC1B,KAAK,qBAAqB;YACxB,OAAO,cAAc,CAAC;QACxB,KAAK,iBAAiB;YACpB,OAAO,MAAM,CAAC;IAClB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAaD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAiB;IACxD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9C,IAAI,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,OAAO,GAAG,gBAAgB,GAAG,CAAC,MAAM,EAAE,CAAC;IAC3C,IAAI,IAAI,EAAE,CAAC;QACT,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;YACjD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO;gBAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YACtD,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC1D,IAAI,CAAC;gBAAE,IAAI,GAAG,CAAC,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,+CAA+C;QACjD,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACrF,OAAO,QAAQ,CACb,IAAI,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CACjI,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @galdor/provider-openai — OpenAI (Chat Completions API) adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements the galdor {@link Provider} interface over /chat/completions, with
|
|
5
|
+
* tool calling, vision input, structured output (`response_format` json_schema)
|
|
6
|
+
* and SSE streaming.
|
|
7
|
+
*
|
|
8
|
+
* Because the OpenAI Chat Completions surface is the de facto wire standard, the
|
|
9
|
+
* same adapter targets any OpenAI-compatible provider (Groq, Together, MiniMax,
|
|
10
|
+
* Mistral, DeepSeek, vLLM, Ollama, ...) by pointing the `baseURL` config field at
|
|
11
|
+
* their endpoint. The primary entry point is {@link newOpenAI}.
|
|
12
|
+
*/
|
|
13
|
+
import { type Capabilities, type Event, type Provider, type Request, type Response, type RunContext } from "@galdor/core/provider";
|
|
14
|
+
/** Configuration for an {@link OpenAIProvider}. */
|
|
15
|
+
export interface Config {
|
|
16
|
+
/** Authenticates against the OpenAI API. Required. */
|
|
17
|
+
apiKey: string;
|
|
18
|
+
/**
|
|
19
|
+
* Overrides the API endpoint. Default https://api.openai.com/v1. Set this to
|
|
20
|
+
* point at an OpenAI-compatible provider (Groq, Together, MiniMax, Mistral,
|
|
21
|
+
* DeepSeek, vLLM, Ollama, ...). The /v1 segment is part of the baseURL.
|
|
22
|
+
*/
|
|
23
|
+
baseURL?: string;
|
|
24
|
+
/** Sent as openai-organization when non-empty. */
|
|
25
|
+
organization?: string;
|
|
26
|
+
/** Sent as openai-project when non-empty. */
|
|
27
|
+
project?: string;
|
|
28
|
+
/** Appended to the default user-agent when non-empty. */
|
|
29
|
+
userAgent?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Response-header timeout in milliseconds: abort if the server doesn't return
|
|
32
|
+
* headers in time. Streaming bodies are NOT cut off once headers arrive.
|
|
33
|
+
* Defaults to 60000 (60 s); `0` disables it. Also settable via
|
|
34
|
+
* `LLM_HTTP_TIMEOUT` through providerset.
|
|
35
|
+
*/
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* galdor {@link Provider} backed by the OpenAI Chat Completions API (or any
|
|
40
|
+
* OpenAI-compatible endpoint selected via {@link Config.baseURL}).
|
|
41
|
+
*
|
|
42
|
+
* Use {@link newOpenAI} to construct one, or instantiate directly.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
|
|
47
|
+
* const res = await provider.generate({ model: "gpt-4o-mini", messages });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare class OpenAIProvider implements Provider {
|
|
51
|
+
#private;
|
|
52
|
+
/**
|
|
53
|
+
* @param cfg - Provider configuration; `apiKey` is required.
|
|
54
|
+
* @throws {Error} When `apiKey` is missing or blank.
|
|
55
|
+
*/
|
|
56
|
+
constructor(cfg: Config);
|
|
57
|
+
/** @returns The provider's stable identifier, `"openai"`. */
|
|
58
|
+
name(): string;
|
|
59
|
+
/** @returns The feature set this adapter supports. */
|
|
60
|
+
capabilities(): Capabilities;
|
|
61
|
+
/**
|
|
62
|
+
* Run a single non-streaming completion.
|
|
63
|
+
*
|
|
64
|
+
* @param req - The galdor request to send.
|
|
65
|
+
* @param ctx - Optional run context; its `signal` cancels the request.
|
|
66
|
+
* @returns The decoded galdor {@link Response}.
|
|
67
|
+
* @throws {APIError} When the API returns a non-2xx status, or when the body
|
|
68
|
+
* cannot be decoded as JSON.
|
|
69
|
+
*/
|
|
70
|
+
generate(req: Request, ctx?: RunContext): Promise<Response>;
|
|
71
|
+
/**
|
|
72
|
+
* Run a streaming completion, yielding provider {@link Event}s as they arrive.
|
|
73
|
+
*
|
|
74
|
+
* @param req - The galdor request to send.
|
|
75
|
+
* @param ctx - Optional run context; its `signal` cancels the stream.
|
|
76
|
+
* @returns An async iterable of provider events ending in MessageStop.
|
|
77
|
+
* @throws {APIError} When the initial response is non-2xx or an in-stream
|
|
78
|
+
* error frame is received (surfaced while iterating).
|
|
79
|
+
*/
|
|
80
|
+
stream(req: Request, ctx?: RunContext): AsyncIterable<Event>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Construct an {@link OpenAIProvider}.
|
|
84
|
+
*
|
|
85
|
+
* @param cfg - Provider configuration; `apiKey` is required.
|
|
86
|
+
* @returns A ready-to-use provider instance.
|
|
87
|
+
* @throws {Error} When `apiKey` is missing or blank.
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const provider = newOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
|
|
91
|
+
* const res = await provider.generate({ model: "gpt-4o-mini", messages });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function newOpenAI(cfg: Config): OpenAIProvider;
|
|
95
|
+
export { normalizeHTTPError } from "./errors.ts";
|
|
96
|
+
export { type EmbedderConfig, newEmbedder, OpenAIEmbedder } from "./embed.ts";
|
|
97
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,KAAK,EAEV,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,UAAU,EAEhB,MAAM,uBAAuB,CAAC;AAe/B,mDAAmD;AACnD,MAAM,WAAW,MAAM;IACrB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,cAAe,YAAW,QAAQ;;IAQ7C;;;OAGG;gBACS,GAAG,EAAE,MAAM;IAUvB,6DAA6D;IAC7D,IAAI,IAAI,MAAM;IAId,sDAAsD;IACtD,YAAY,IAAI,YAAY;IA0B5B;;;;;;;;OAQG;IACG,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBjE;;;;;;;;OAQG;IACH,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC;CAM7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAErD;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,KAAK,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @galdor/provider-openai — OpenAI (Chat Completions API) adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements the galdor {@link Provider} interface over /chat/completions, with
|
|
5
|
+
* tool calling, vision input, structured output (`response_format` json_schema)
|
|
6
|
+
* and SSE streaming.
|
|
7
|
+
*
|
|
8
|
+
* Because the OpenAI Chat Completions surface is the de facto wire standard, the
|
|
9
|
+
* same adapter targets any OpenAI-compatible provider (Groq, Together, MiniMax,
|
|
10
|
+
* Mistral, DeepSeek, vLLM, Ollama, ...) by pointing the `baseURL` config field at
|
|
11
|
+
* their endpoint. The primary entry point is {@link newOpenAI}.
|
|
12
|
+
*/
|
|
13
|
+
import { APIError, classify, fetchWithHeaderTimeout, validateRequest, } from "@galdor/core/provider";
|
|
14
|
+
import { buildRequest, responseFromWire } from "./convert.js";
|
|
15
|
+
import { normalizeHTTPError } from "./errors.js";
|
|
16
|
+
import { streamChat } from "./stream.js";
|
|
17
|
+
const PROVIDER_NAME = "openai";
|
|
18
|
+
/**
|
|
19
|
+
* Default production API endpoint. It already includes the `/v1` path segment,
|
|
20
|
+
* so the adapter only appends `/chat/completions` — the convention used by the
|
|
21
|
+
* official OpenAI client libraries and by every OpenAI-compatible provider's
|
|
22
|
+
* documentation.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
25
|
+
/**
|
|
26
|
+
* galdor {@link Provider} backed by the OpenAI Chat Completions API (or any
|
|
27
|
+
* OpenAI-compatible endpoint selected via {@link Config.baseURL}).
|
|
28
|
+
*
|
|
29
|
+
* Use {@link newOpenAI} to construct one, or instantiate directly.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! });
|
|
34
|
+
* const res = await provider.generate({ model: "gpt-4o-mini", messages });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class OpenAIProvider {
|
|
38
|
+
#apiKey;
|
|
39
|
+
#baseURL;
|
|
40
|
+
#organization;
|
|
41
|
+
#project;
|
|
42
|
+
#userAgent;
|
|
43
|
+
#timeoutMs;
|
|
44
|
+
/**
|
|
45
|
+
* @param cfg - Provider configuration; `apiKey` is required.
|
|
46
|
+
* @throws {Error} When `apiKey` is missing or blank.
|
|
47
|
+
*/
|
|
48
|
+
constructor(cfg) {
|
|
49
|
+
if (!cfg.apiKey || cfg.apiKey.trim() === "")
|
|
50
|
+
throw new Error("openai: apiKey is required");
|
|
51
|
+
this.#apiKey = cfg.apiKey;
|
|
52
|
+
this.#baseURL = (cfg.baseURL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
53
|
+
this.#organization = cfg.organization ?? "";
|
|
54
|
+
this.#project = cfg.project ?? "";
|
|
55
|
+
this.#userAgent = cfg.userAgent ?? "";
|
|
56
|
+
this.#timeoutMs = cfg.timeoutMs ?? 60_000;
|
|
57
|
+
}
|
|
58
|
+
/** @returns The provider's stable identifier, `"openai"`. */
|
|
59
|
+
name() {
|
|
60
|
+
return PROVIDER_NAME;
|
|
61
|
+
}
|
|
62
|
+
/** @returns The feature set this adapter supports. */
|
|
63
|
+
capabilities() {
|
|
64
|
+
// promptCaching is false: OpenAI's caching is automatic and ignores
|
|
65
|
+
// CacheControl hints. maxContextTokens reflects the gpt-4o long-context tier.
|
|
66
|
+
return {
|
|
67
|
+
streaming: true,
|
|
68
|
+
toolCalling: true,
|
|
69
|
+
structuredOutput: true,
|
|
70
|
+
promptCaching: false,
|
|
71
|
+
visionInput: true,
|
|
72
|
+
reasoning: true,
|
|
73
|
+
maxContextTokens: 128_000,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
#headers() {
|
|
77
|
+
let ua = "galdor-openai/0.1";
|
|
78
|
+
if (this.#userAgent)
|
|
79
|
+
ua += ` ${this.#userAgent}`;
|
|
80
|
+
return {
|
|
81
|
+
authorization: `Bearer ${this.#apiKey}`,
|
|
82
|
+
"content-type": "application/json",
|
|
83
|
+
"user-agent": ua,
|
|
84
|
+
...(this.#organization ? { "openai-organization": this.#organization } : {}),
|
|
85
|
+
...(this.#project ? { "openai-project": this.#project } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run a single non-streaming completion.
|
|
90
|
+
*
|
|
91
|
+
* @param req - The galdor request to send.
|
|
92
|
+
* @param ctx - Optional run context; its `signal` cancels the request.
|
|
93
|
+
* @returns The decoded galdor {@link Response}.
|
|
94
|
+
* @throws {APIError} When the API returns a non-2xx status, or when the body
|
|
95
|
+
* cannot be decoded as JSON.
|
|
96
|
+
*/
|
|
97
|
+
async generate(req, ctx) {
|
|
98
|
+
const capErr = validateRequest(this.capabilities(), req);
|
|
99
|
+
if (capErr)
|
|
100
|
+
throw capErr;
|
|
101
|
+
const wire = buildRequest(req, false);
|
|
102
|
+
const res = await fetchWithHeaderTimeout(`${this.#baseURL}/chat/completions`, { method: "POST", headers: this.#headers(), body: JSON.stringify(wire) }, this.#timeoutMs, ctx?.signal);
|
|
103
|
+
if (Math.floor(res.status / 100) !== 2)
|
|
104
|
+
throw await normalizeHTTPError(res);
|
|
105
|
+
const raw = new Uint8Array(await res.arrayBuffer());
|
|
106
|
+
let body;
|
|
107
|
+
try {
|
|
108
|
+
body = JSON.parse(new TextDecoder().decode(raw));
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
throw classify(new APIError({ kind: "server", provider: PROVIDER_NAME, statusCode: res.status, message: `decode response: ${e.message}` }));
|
|
112
|
+
}
|
|
113
|
+
return responseFromWire(body, raw);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Run a streaming completion, yielding provider {@link Event}s as they arrive.
|
|
117
|
+
*
|
|
118
|
+
* @param req - The galdor request to send.
|
|
119
|
+
* @param ctx - Optional run context; its `signal` cancels the stream.
|
|
120
|
+
* @returns An async iterable of provider events ending in MessageStop.
|
|
121
|
+
* @throws {APIError} When the initial response is non-2xx or an in-stream
|
|
122
|
+
* error frame is received (surfaced while iterating).
|
|
123
|
+
*/
|
|
124
|
+
stream(req, ctx) {
|
|
125
|
+
const capErr = validateRequest(this.capabilities(), req);
|
|
126
|
+
if (capErr)
|
|
127
|
+
throw capErr;
|
|
128
|
+
const wire = buildRequest(req, true);
|
|
129
|
+
return streamChat(`${this.#baseURL}/chat/completions`, this.#headers(), wire, ctx?.signal, this.#timeoutMs);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Construct an {@link OpenAIProvider}.
|
|
134
|
+
*
|
|
135
|
+
* @param cfg - Provider configuration; `apiKey` is required.
|
|
136
|
+
* @returns A ready-to-use provider instance.
|
|
137
|
+
* @throws {Error} When `apiKey` is missing or blank.
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const provider = newOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
|
|
141
|
+
* const res = await provider.generate({ model: "gpt-4o-mini", messages });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export function newOpenAI(cfg) {
|
|
145
|
+
return new OpenAIProvider(cfg);
|
|
146
|
+
}
|
|
147
|
+
export { normalizeHTTPError } from "./errors.js";
|
|
148
|
+
export { newEmbedder, OpenAIEmbedder } from "./embed.js";
|
|
149
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,QAAQ,EAER,QAAQ,EAER,sBAAsB,EAKtB,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAqB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,aAAa,GAAG,QAAQ,CAAC;AAE/B;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;AA2BrD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,cAAc;IAChB,OAAO,CAAS;IAChB,QAAQ,CAAS;IACjB,aAAa,CAAS;IACtB,QAAQ,CAAS;IACjB,UAAU,CAAS;IACnB,UAAU,CAAS;IAE5B;;;OAGG;IACH,YAAY,GAAW;QACrB,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC3F,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC;IAC5C,CAAC;IAED,6DAA6D;IAC7D,IAAI;QACF,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,sDAAsD;IACtD,YAAY;QACV,oEAAoE;QACpE,8EAA8E;QAC9E,OAAO;YACL,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,IAAI;YACjB,gBAAgB,EAAE,IAAI;YACtB,aAAa,EAAE,KAAK;YACpB,WAAW,EAAE,IAAI;YACjB,SAAS,EAAE,IAAI;YACf,gBAAgB,EAAE,OAAO;SAC1B,CAAC;IACJ,CAAC;IAED,QAAQ;QACN,IAAI,EAAE,GAAG,mBAAmB,CAAC;QAC7B,IAAI,IAAI,CAAC,UAAU;YAAE,EAAE,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACjD,OAAO;YACL,aAAa,EAAE,UAAU,IAAI,CAAC,OAAO,EAAE;YACvC,cAAc,EAAE,kBAAkB;YAClC,YAAY,EAAE,EAAE;YAChB,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,QAAQ,CAAC,GAAY,EAAE,GAAgB;QAC3C,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,GAAG,CAAC,CAAC;QACzD,IAAI,MAAM;YAAE,MAAM,MAAM,CAAC;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,sBAAsB,CACtC,GAAG,IAAI,CAAC,QAAQ,mBAAmB,EACnC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EACxE,IAAI,CAAC,UAAU,EACf,GAAG,EAAE,MAAM,CACZ,CAAC;QACF,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC;YAAE,MAAM,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAE5E,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;QACpD,IAAI,IAAkB,CAAC;QACvB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAiB,CAAC;QACnE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,QAAQ,CACZ,IAAI,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,oBAAqB,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CACvI,CAAC;QACJ,CAAC;QACD,OAAO,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,GAAY,EAAE,GAAgB;QACnC,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,GAAG,CAAC,CAAC;QACzD,IAAI,MAAM;YAAE,MAAM,MAAM,CAAC;QACzB,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC,QAAQ,mBAAmB,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9G,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAuB,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI streaming over Server-Sent Events.
|
|
3
|
+
*
|
|
4
|
+
* Decodes each `data: {...}` chunk of the /chat/completions stream into galdor
|
|
5
|
+
* provider {@link Event}s (MessageStart / ContentDelta / ToolCallDelta /
|
|
6
|
+
* MessageStop). The OpenAI stream carries no dedicated opening frame, so
|
|
7
|
+
* MessageStart is synthesized from the first chunk, and MessageStop is deferred
|
|
8
|
+
* to the end: with `stream_options.include_usage = true` the final usage chunk
|
|
9
|
+
* arrives after the `finish_reason` chunk. Some OpenAI-compatible backends close
|
|
10
|
+
* the connection rather than emitting `data: [DONE]`, so the terminal
|
|
11
|
+
* MessageStop is always synthesized from accumulated state, regardless of how
|
|
12
|
+
* the stream ends. Consume the generator with `for await`, or fold it into a
|
|
13
|
+
* single {@link Response} via `collectStream`.
|
|
14
|
+
*/
|
|
15
|
+
import { type Event } from "@galdor/core/provider";
|
|
16
|
+
/**
|
|
17
|
+
* POST a streaming /chat/completions request and yield galdor provider events.
|
|
18
|
+
*
|
|
19
|
+
* Synthesizes a MessageStart from the first chunk, forwards content and tool-call
|
|
20
|
+
* deltas as they arrive, accumulates reasoning and usage, and emits a terminal
|
|
21
|
+
* MessageStop once the upstream stream ends.
|
|
22
|
+
*
|
|
23
|
+
* @param url - The fully-qualified /chat/completions endpoint to POST to.
|
|
24
|
+
* @param headers - Request headers (auth, content-type, etc.); an SSE `Accept`
|
|
25
|
+
* header is added automatically.
|
|
26
|
+
* @param body - The request payload, serialized to JSON.
|
|
27
|
+
* @param signal - Optional abort signal to cancel the in-flight request.
|
|
28
|
+
* @returns An async generator of provider {@link Event}s ending in MessageStop.
|
|
29
|
+
* @throws {APIError} When the HTTP response is non-2xx, or when an in-stream
|
|
30
|
+
* error frame is received.
|
|
31
|
+
* @throws {Error} When a 2xx response unexpectedly carries no body.
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* for await (const ev of streamChat(url, headers, wire, signal)) {
|
|
35
|
+
* if (ev.type === EventType.ContentDelta) process.stdout.write(ev.contentDelta);
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function streamChat(url: string, headers: Record<string, string>, body: unknown, signal: AbortSignal | undefined, timeoutMs?: number): AsyncGenerator<Event>;
|
|
40
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAY,KAAK,KAAK,EAAqC,MAAM,uBAAuB,CAAC;AA8DhG;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAuB,UAAU,CAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,SAAS,SAAI,GACZ,cAAc,CAAC,KAAK,CAAC,CA4CvB"}
|