@sembl/provider-openai 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sembl contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @sembl/provider-openai
2
+
3
+ OpenAI provider for SEMBL. Structured output uses `response_format: json_schema`
4
+ in strict mode, so the model's reply is shape-checked by the API rather than
5
+ parsed out of prose.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ pnpm add @sembl/core @sembl/provider-openai openai
11
+ ```
12
+
13
+ `openai` is a peer dependency: the host app owns the version and, usually, the
14
+ client instance.
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import { sembl, SemblConfig } from "@sembl/core";
20
+ import { OpenAIProvider } from "@sembl/provider-openai";
21
+
22
+ SemblConfig.configure({
23
+ provider: new OpenAIProvider({ model: "gpt-4o", apiKey }),
24
+ bundle,
25
+ });
26
+ ```
27
+
28
+ ## Options
29
+
30
+ | Option | Default | Notes |
31
+ | ------------- | -------------------- | ------------------------------------------------- |
32
+ | `model` | — | Required. |
33
+ | `apiKey` | `OPENAI_API_KEY` | Falls back to the SDK's own env lookup. |
34
+ | `baseURL` | OpenAI production | Point at a gateway or compatible endpoint. |
35
+ | `temperature` | `0` | |
36
+ | `maxTokens` | SDK default | |
37
+ | `maxRetries` | SDK default (`2`) | Retries are the SDK's own; this configures them. |
38
+ | `timeoutMs` | `120000` | Per attempt. Worst case is roughly this × (`maxRetries` + 1), plus backoff. |
39
+
40
+ ## Errors
41
+
42
+ Failures surface as `OpenAIProviderError` with a `kind` you can branch on
43
+ instead of matching message text:
44
+
45
+ | `kind` | Meaning | `retryable` |
46
+ | ------------- | --------------------------------------------------------------- | ----------- |
47
+ | `api` | Transport or API failure — rate limit, server error, timeout. | per SDK |
48
+ | `truncated` | Output hit the token cap mid-answer (`finish_reason: "length"`). | no |
49
+ | `no_output` | A refusal, or content that wasn't usable JSON. | no |
50
+
51
+ ## Schema dialect
52
+
53
+ This provider emits the `"openai-strict"` dialect: every property appears in
54
+ `required` and optional fields become `anyOf: [T, null]`, as structured outputs
55
+ demand. `FieldConstraints` are deliberately **not** emitted into the schema —
56
+ strict mode rejects a request outright if it carries a keyword it doesn't
57
+ support, so bounds are enforced through the prompt and SEMBL's validator
58
+ instead. See `CONSTRAINT_KEYWORDS` in `@sembl/core` for what would need
59
+ verifying to loosen that.
60
+
61
+ ## Caching
62
+
63
+ OpenAI caches long prompt prefixes automatically; there is nothing to turn on.
64
+ When it reports a hit, `usage.cacheReadTokens` carries it. Note that OpenAI
65
+ counts cached tokens *inside* `promptTokens` (Anthropic reports them
66
+ alongside), so don't add the two together.
@@ -0,0 +1,104 @@
1
+ import { ProviderConfig, Provider, ProviderRequest, ProviderResponse, RuntimeSchema, SchemaBundle } from '@sembl/core';
2
+ import OpenAI from 'openai';
3
+
4
+ /**
5
+ * Configuration specific to the OpenAI provider.
6
+ */
7
+ interface OpenAIProviderConfig extends ProviderConfig {
8
+ /** OpenAI API key. Defaults to OPENAI_API_KEY env var. */
9
+ apiKey?: string;
10
+ /** Base URL for the OpenAI API. Defaults to OpenAI's production endpoint. */
11
+ baseURL?: string;
12
+ /**
13
+ * How many times the SDK retries a failed call before giving up. The SDK
14
+ * retries connection errors, 408/409/429 and 5xx with exponential backoff
15
+ * and honours `retry-after`, so there is nothing to hand-roll here.
16
+ *
17
+ * Defaults to {@link DEFAULT_MAX_RETRIES}.
18
+ */
19
+ maxRetries?: number;
20
+ /**
21
+ * Timeout for a single attempt, in milliseconds. Retries each get their
22
+ * own attempt, so the worst-case wall clock is roughly
23
+ * `timeoutMs * (maxRetries + 1)` plus backoff.
24
+ *
25
+ * Defaults to {@link DEFAULT_TIMEOUT_MS}.
26
+ */
27
+ timeoutMs?: number;
28
+ }
29
+ /** Matches the SDK's own default; stated here so it survives an SDK change. */
30
+ declare const DEFAULT_MAX_RETRIES = 2;
31
+ /**
32
+ * Two minutes per attempt. The SDK's own default is ten, which is a long time
33
+ * for a backend import to sit on one record when the retry is cheap.
34
+ */
35
+ declare const DEFAULT_TIMEOUT_MS = 120000;
36
+
37
+ /**
38
+ * OpenAI provider implementation using structured outputs (json_schema response_format).
39
+ *
40
+ * Retries and timeouts are the SDK's (exponential backoff, `retry-after`
41
+ * aware); this class only chooses the numbers and translates whatever comes
42
+ * back out into an {@link OpenAIProviderError}.
43
+ */
44
+ declare class OpenAIProvider implements Provider {
45
+ private client;
46
+ private config;
47
+ constructor(config: OpenAIProviderConfig);
48
+ complete(request: ProviderRequest): Promise<ProviderResponse>;
49
+ }
50
+
51
+ /**
52
+ * Why a provider call failed, in the terms a caller can act on.
53
+ *
54
+ * A batch import wants to route these differently: `"api"` failures are worth
55
+ * re-queueing, `"truncated"` needs a bigger output budget, and `"no_output"`
56
+ * is a property of that one input's content — retrying it changes nothing.
57
+ *
58
+ * The same three kinds are used by `@sembl/provider-anthropic`, so a caller
59
+ * that branches on `kind` keeps working when the provider is swapped.
60
+ */
61
+ type ProviderErrorKind = "api" | "truncated" | "no_output";
62
+ /**
63
+ * Error thrown by the OpenAI provider.
64
+ *
65
+ * Branch on `kind` rather than matching the message — messages stay
66
+ * diagnostic and are free to change.
67
+ */
68
+ declare class OpenAIProviderError extends Error {
69
+ /** What class of failure this is. */
70
+ readonly kind: ProviderErrorKind;
71
+ /**
72
+ * Whether another attempt could plausibly succeed. The SDK has already
73
+ * retried retryable transport failures (see `maxRetries`); this says only
74
+ * that the failure was transient in nature, so a caller running a queue can
75
+ * re-enqueue the item rather than dead-letter it.
76
+ */
77
+ readonly retryable: boolean;
78
+ /** HTTP status, when the failure came back as an API error. */
79
+ readonly status?: number;
80
+ /** OpenAI's `finish_reason`, when the call returned a choice we rejected. */
81
+ readonly finishReason?: string;
82
+ constructor(message: string, options: {
83
+ kind: ProviderErrorKind;
84
+ retryable: boolean;
85
+ status?: number;
86
+ finishReason?: string;
87
+ cause?: unknown;
88
+ });
89
+ }
90
+
91
+ type ResponseFormatJSONSchema = OpenAI.ChatCompletionCreateParams["response_format"] & {
92
+ type: "json_schema";
93
+ };
94
+ /**
95
+ * Convert a RuntimeSchema to an OpenAI-compatible response_format parameter.
96
+ * Applies OpenAI strict mode constraints:
97
+ * - All properties in `required` array
98
+ * - Optional fields use anyOf: [type, { type: "null" }]
99
+ * - additionalProperties: false on every object
100
+ * - Everything inlined (no $ref)
101
+ */
102
+ declare function toResponseFormat(schema: RuntimeSchema, bundle?: SchemaBundle): ResponseFormatJSONSchema;
103
+
104
+ export { DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS, OpenAIProvider, type OpenAIProviderConfig, OpenAIProviderError, type ProviderErrorKind, toResponseFormat };
package/dist/index.js ADDED
@@ -0,0 +1,151 @@
1
+ // src/openai-provider.ts
2
+ import OpenAI from "openai";
3
+
4
+ // src/openai-config.ts
5
+ var DEFAULT_MAX_RETRIES = 2;
6
+ var DEFAULT_TIMEOUT_MS = 12e4;
7
+
8
+ // src/errors.ts
9
+ import { APIConnectionError, APIError } from "openai";
10
+ var OpenAIProviderError = class extends Error {
11
+ /** What class of failure this is. */
12
+ kind;
13
+ /**
14
+ * Whether another attempt could plausibly succeed. The SDK has already
15
+ * retried retryable transport failures (see `maxRetries`); this says only
16
+ * that the failure was transient in nature, so a caller running a queue can
17
+ * re-enqueue the item rather than dead-letter it.
18
+ */
19
+ retryable;
20
+ /** HTTP status, when the failure came back as an API error. */
21
+ status;
22
+ /** OpenAI's `finish_reason`, when the call returned a choice we rejected. */
23
+ finishReason;
24
+ constructor(message, options) {
25
+ super(message, { cause: options.cause });
26
+ this.name = "OpenAIProviderError";
27
+ this.kind = options.kind;
28
+ this.retryable = options.retryable;
29
+ this.status = options.status;
30
+ this.finishReason = options.finishReason;
31
+ }
32
+ };
33
+ function toProviderError(error) {
34
+ if (error instanceof APIError) {
35
+ const status = error.status;
36
+ const retryable = error instanceof APIConnectionError || status === void 0 || status === 408 || status === 409 || status === 429 || status >= 500;
37
+ return new OpenAIProviderError(
38
+ `OpenAI request failed${status ? ` (${status})` : ""}: ${error.message}`,
39
+ { kind: "api", retryable, status, cause: error }
40
+ );
41
+ }
42
+ return new OpenAIProviderError(
43
+ `OpenAI request failed: ${error instanceof Error ? error.message : String(error)}`,
44
+ { kind: "api", retryable: false, cause: error }
45
+ );
46
+ }
47
+
48
+ // src/schema-converter.ts
49
+ import { toOpenAIJsonSchema } from "@sembl/core";
50
+ function toResponseFormat(schema, bundle) {
51
+ const jsonSchema = toOpenAIJsonSchema(schema, bundle);
52
+ return {
53
+ type: "json_schema",
54
+ json_schema: {
55
+ name: schema.id,
56
+ strict: true,
57
+ schema: jsonSchema.schema
58
+ }
59
+ };
60
+ }
61
+
62
+ // src/openai-provider.ts
63
+ var OpenAIProvider = class {
64
+ client;
65
+ config;
66
+ constructor(config) {
67
+ this.config = config;
68
+ this.client = new OpenAI({
69
+ apiKey: config.apiKey,
70
+ baseURL: config.baseURL,
71
+ maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
72
+ timeout: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
73
+ });
74
+ }
75
+ async complete(request) {
76
+ const responseFormat = toResponseFormat(request.schema, request.bundle);
77
+ let completion;
78
+ try {
79
+ completion = await this.client.chat.completions.create({
80
+ model: this.config.model,
81
+ temperature: this.config.temperature ?? 0,
82
+ max_tokens: this.config.maxTokens,
83
+ messages: [
84
+ { role: "system", content: request.systemPrompt },
85
+ { role: "user", content: request.userInput }
86
+ ],
87
+ response_format: responseFormat
88
+ });
89
+ } catch (error) {
90
+ throw toProviderError(error);
91
+ }
92
+ const choice = completion.choices[0];
93
+ if (choice?.finish_reason === "length") {
94
+ const cap = this.config.maxTokens ? `the ${this.config.maxTokens}-token output cap` : "the model's default output cap";
95
+ throw new OpenAIProviderError(
96
+ `OpenAI hit ${cap} before completing the "${request.schema.id}" object. Raise maxTokens, or coerce into a smaller schema.`,
97
+ { kind: "truncated", retryable: false, finishReason: "length" }
98
+ );
99
+ }
100
+ if (choice?.message?.refusal) {
101
+ throw new OpenAIProviderError(
102
+ `OpenAI declined to extract "${request.schema.id}": ${choice.message.refusal}`,
103
+ {
104
+ kind: "no_output",
105
+ retryable: false,
106
+ finishReason: choice.finish_reason
107
+ }
108
+ );
109
+ }
110
+ if (!choice?.message?.content) {
111
+ throw new OpenAIProviderError(
112
+ `OpenAI returned no content in response (finish_reason: ${choice?.finish_reason ?? "unknown"})`,
113
+ {
114
+ kind: "no_output",
115
+ retryable: false,
116
+ finishReason: choice?.finish_reason
117
+ }
118
+ );
119
+ }
120
+ let data;
121
+ try {
122
+ data = JSON.parse(choice.message.content);
123
+ } catch (error) {
124
+ throw new OpenAIProviderError(
125
+ `OpenAI returned content that is not valid JSON for "${request.schema.id}"`,
126
+ { kind: "no_output", retryable: false, cause: error }
127
+ );
128
+ }
129
+ return {
130
+ data,
131
+ usage: completion.usage ? {
132
+ promptTokens: completion.usage.prompt_tokens,
133
+ completionTokens: completion.usage.completion_tokens,
134
+ totalTokens: completion.usage.total_tokens,
135
+ // OpenAI caches long prefixes automatically and reports only what
136
+ // it served from cache — there is no write to account for.
137
+ ...completion.usage.prompt_tokens_details?.cached_tokens != null && {
138
+ cacheReadTokens: completion.usage.prompt_tokens_details.cached_tokens
139
+ }
140
+ } : void 0
141
+ };
142
+ }
143
+ };
144
+ export {
145
+ DEFAULT_MAX_RETRIES,
146
+ DEFAULT_TIMEOUT_MS,
147
+ OpenAIProvider,
148
+ OpenAIProviderError,
149
+ toResponseFormat
150
+ };
151
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/openai-provider.ts","../src/openai-config.ts","../src/errors.ts","../src/schema-converter.ts"],"sourcesContent":["import OpenAI from \"openai\";\nimport type { Provider, ProviderRequest, ProviderResponse } from \"@sembl/core\";\nimport type { OpenAIProviderConfig } from \"./openai-config.js\";\nimport { DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT_MS } from \"./openai-config.js\";\nimport { OpenAIProviderError, toProviderError } from \"./errors.js\";\nimport { toResponseFormat } from \"./schema-converter.js\";\n\n/**\n * OpenAI provider implementation using structured outputs (json_schema response_format).\n *\n * Retries and timeouts are the SDK's (exponential backoff, `retry-after`\n * aware); this class only chooses the numbers and translates whatever comes\n * back out into an {@link OpenAIProviderError}.\n */\nexport class OpenAIProvider implements Provider {\n private client: OpenAI;\n private config: OpenAIProviderConfig;\n\n constructor(config: OpenAIProviderConfig) {\n this.config = config;\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,\n timeout: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n });\n }\n\n async complete(request: ProviderRequest): Promise<ProviderResponse> {\n const responseFormat = toResponseFormat(request.schema, request.bundle);\n\n let completion;\n try {\n completion = await this.client.chat.completions.create({\n model: this.config.model,\n temperature: this.config.temperature ?? 0,\n max_tokens: this.config.maxTokens,\n messages: [\n { role: \"system\", content: request.systemPrompt },\n { role: \"user\", content: request.userInput },\n ],\n response_format: responseFormat,\n });\n } catch (error) {\n throw toProviderError(error);\n }\n\n const choice = completion.choices[0];\n\n // A truncated response is a budget problem, not a content one: the JSON is\n // cut mid-object, so it would only fail to parse a step later.\n if (choice?.finish_reason === \"length\") {\n const cap = this.config.maxTokens\n ? `the ${this.config.maxTokens}-token output cap`\n : \"the model's default output cap\";\n throw new OpenAIProviderError(\n `OpenAI hit ${cap} before completing the \"${request.schema.id}\" object. ` +\n \"Raise maxTokens, or coerce into a smaller schema.\",\n { kind: \"truncated\", retryable: false, finishReason: \"length\" },\n );\n }\n\n if (choice?.message?.refusal) {\n throw new OpenAIProviderError(\n `OpenAI declined to extract \"${request.schema.id}\": ${choice.message.refusal}`,\n {\n kind: \"no_output\",\n retryable: false,\n finishReason: choice.finish_reason,\n },\n );\n }\n\n if (!choice?.message?.content) {\n throw new OpenAIProviderError(\n `OpenAI returned no content in response (finish_reason: ${choice?.finish_reason ?? \"unknown\"})`,\n {\n kind: \"no_output\",\n retryable: false,\n finishReason: choice?.finish_reason,\n },\n );\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(choice.message.content) as Record<string, unknown>;\n } catch (error) {\n throw new OpenAIProviderError(\n `OpenAI returned content that is not valid JSON for \"${request.schema.id}\"`,\n { kind: \"no_output\", retryable: false, cause: error },\n );\n }\n\n return {\n data,\n usage: completion.usage\n ? {\n promptTokens: completion.usage.prompt_tokens,\n completionTokens: completion.usage.completion_tokens,\n totalTokens: completion.usage.total_tokens,\n // OpenAI caches long prefixes automatically and reports only what\n // it served from cache — there is no write to account for.\n ...(completion.usage.prompt_tokens_details?.cached_tokens !=\n null && {\n cacheReadTokens:\n completion.usage.prompt_tokens_details.cached_tokens,\n }),\n }\n : undefined,\n };\n }\n}\n","import type { ProviderConfig } from \"@sembl/core\";\n\n/**\n * Configuration specific to the OpenAI provider.\n */\nexport interface OpenAIProviderConfig extends ProviderConfig {\n /** OpenAI API key. Defaults to OPENAI_API_KEY env var. */\n apiKey?: string;\n /** Base URL for the OpenAI API. Defaults to OpenAI's production endpoint. */\n baseURL?: string;\n /**\n * How many times the SDK retries a failed call before giving up. The SDK\n * retries connection errors, 408/409/429 and 5xx with exponential backoff\n * and honours `retry-after`, so there is nothing to hand-roll here.\n *\n * Defaults to {@link DEFAULT_MAX_RETRIES}.\n */\n maxRetries?: number;\n /**\n * Timeout for a single attempt, in milliseconds. Retries each get their\n * own attempt, so the worst-case wall clock is roughly\n * `timeoutMs * (maxRetries + 1)` plus backoff.\n *\n * Defaults to {@link DEFAULT_TIMEOUT_MS}.\n */\n timeoutMs?: number;\n}\n\n/** Matches the SDK's own default; stated here so it survives an SDK change. */\nexport const DEFAULT_MAX_RETRIES = 2;\n\n/**\n * Two minutes per attempt. The SDK's own default is ten, which is a long time\n * for a backend import to sit on one record when the retry is cheap.\n */\nexport const DEFAULT_TIMEOUT_MS = 120_000;\n","import { APIConnectionError, APIError } from \"openai\";\n\n/**\n * Why a provider call failed, in the terms a caller can act on.\n *\n * A batch import wants to route these differently: `\"api\"` failures are worth\n * re-queueing, `\"truncated\"` needs a bigger output budget, and `\"no_output\"`\n * is a property of that one input's content — retrying it changes nothing.\n *\n * The same three kinds are used by `@sembl/provider-anthropic`, so a caller\n * that branches on `kind` keeps working when the provider is swapped.\n */\nexport type ProviderErrorKind = \"api\" | \"truncated\" | \"no_output\";\n\n/**\n * Error thrown by the OpenAI provider.\n *\n * Branch on `kind` rather than matching the message — messages stay\n * diagnostic and are free to change.\n */\nexport class OpenAIProviderError extends Error {\n /** What class of failure this is. */\n public readonly kind: ProviderErrorKind;\n /**\n * Whether another attempt could plausibly succeed. The SDK has already\n * retried retryable transport failures (see `maxRetries`); this says only\n * that the failure was transient in nature, so a caller running a queue can\n * re-enqueue the item rather than dead-letter it.\n */\n public readonly retryable: boolean;\n /** HTTP status, when the failure came back as an API error. */\n public readonly status?: number;\n /** OpenAI's `finish_reason`, when the call returned a choice we rejected. */\n public readonly finishReason?: string;\n\n constructor(\n message: string,\n options: {\n kind: ProviderErrorKind;\n retryable: boolean;\n status?: number;\n finishReason?: string;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options.cause });\n this.name = \"OpenAIProviderError\";\n this.kind = options.kind;\n this.retryable = options.retryable;\n this.status = options.status;\n this.finishReason = options.finishReason;\n }\n}\n\n/**\n * Wrap an SDK-level failure as an `OpenAIProviderError`.\n *\n * Retryability is read off the SDK's own error classes rather than the\n * message: connection failures and timeouts are transient by construction,\n * and of the status codes only 408/409/429 and 5xx are worth another attempt —\n * the same set the SDK itself retries internally.\n */\nexport function toProviderError(error: unknown): OpenAIProviderError {\n if (error instanceof APIError) {\n const status = error.status;\n const retryable =\n error instanceof APIConnectionError ||\n status === undefined ||\n status === 408 ||\n status === 409 ||\n status === 429 ||\n status >= 500;\n\n return new OpenAIProviderError(\n `OpenAI request failed${status ? ` (${status})` : \"\"}: ${error.message}`,\n { kind: \"api\", retryable, status, cause: error },\n );\n }\n\n // Anything else (an AbortError, a bug in the transport) is surfaced with the\n // same shape so callers only need one catch.\n return new OpenAIProviderError(\n `OpenAI request failed: ${error instanceof Error ? error.message : String(error)}`,\n { kind: \"api\", retryable: false, cause: error },\n );\n}\n","import type { RuntimeSchema, SchemaBundle } from \"@sembl/core\";\nimport { toOpenAIJsonSchema } from \"@sembl/core\";\nimport type OpenAI from \"openai\";\n\ntype ResponseFormatJSONSchema =\n OpenAI.ChatCompletionCreateParams[\"response_format\"] & { type: \"json_schema\" };\n\n/**\n * Convert a RuntimeSchema to an OpenAI-compatible response_format parameter.\n * Applies OpenAI strict mode constraints:\n * - All properties in `required` array\n * - Optional fields use anyOf: [type, { type: \"null\" }]\n * - additionalProperties: false on every object\n * - Everything inlined (no $ref)\n */\nexport function toResponseFormat(\n schema: RuntimeSchema,\n bundle?: SchemaBundle,\n): ResponseFormatJSONSchema {\n const jsonSchema = toOpenAIJsonSchema(schema, bundle);\n\n return {\n type: \"json_schema\",\n json_schema: {\n name: schema.id,\n strict: true,\n schema: jsonSchema.schema as Record<string, unknown>,\n },\n } as ResponseFormatJSONSchema;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@sembl/provider-openai",
3
+ "version": "0.1.0",
4
+ "description": "OpenAI provider for SEMBL, using structured outputs.",
5
+ "keywords": [
6
+ "llm",
7
+ "structured-output",
8
+ "openai",
9
+ "gpt",
10
+ "extraction"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Sembl contributors",
14
+ "homepage": "https://github.com/nickrunner/sembl#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/nickrunner/sembl/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/nickrunner/sembl.git",
21
+ "directory": "packages/provider-openai"
22
+ },
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "sideEffects": false,
38
+ "engines": {
39
+ "node": ">=20"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "provenance": true
44
+ },
45
+ "dependencies": {
46
+ "@sembl/core": "0.1.0"
47
+ },
48
+ "devDependencies": {
49
+ "openai": "^4.0.0",
50
+ "tsup": "^8.0.0",
51
+ "typescript": "^5.5.0"
52
+ },
53
+ "peerDependencies": {
54
+ "openai": ">=4.0.0"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "dev": "tsup --watch"
59
+ }
60
+ }