@nixxie-cms/ai 1.0.0 → 2.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # @nixxie-cms/ai
2
+
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 450043a:
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [0fe028d]
12
+ - Updated dependencies [690e433]
13
+ - Updated dependencies [2c0d28c]
14
+ - Updated dependencies [450043a]
15
+ - @nixxie-cms/core@2.0.0
@@ -0,0 +1,15 @@
1
+ import type { NixxieAiCompleteOptions, NixxieAiCompletion, NixxieAiMessage, NixxieAiService } from '@nixxie-cms/core';
2
+ import type { AiConfig } from "./types.js";
3
+ /** Anthropic (Claude) provider, backed by the official @anthropic-ai/sdk. */
4
+ export declare class AnthropicProvider implements NixxieAiService {
5
+ private config;
6
+ private model;
7
+ private maxTokens;
8
+ private client;
9
+ constructor(config: AiConfig);
10
+ complete(prompt: string, options?: NixxieAiCompleteOptions): Promise<NixxieAiCompletion>;
11
+ chat(messages: NixxieAiMessage[], options?: NixxieAiCompleteOptions): Promise<NixxieAiCompletion>;
12
+ embed(text: string): Promise<number[]>;
13
+ embedMany(texts: string[]): Promise<number[][]>;
14
+ }
15
+ //# sourceMappingURL=AnthropicProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnthropicProvider.d.ts","sourceRoot":"../../../src","sources":["AnthropicProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,eAAe,EAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAe;AAcvC,6EAA6E;AAC7E,qBAAa,iBAAkB,YAAW,eAAe;IACvD,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,MAAM,CAAK;gBAEP,MAAM,EAAE,QAAQ;IAQtB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIxF,IAAI,CACR,QAAQ,EAAE,eAAe,EAAE,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC;IAqCxB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAMtC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;CA6BtD"}
@@ -0,0 +1,15 @@
1
+ import type { NixxieAiCompleteOptions, NixxieAiCompletion, NixxieAiMessage, NixxieAiService } from '@nixxie-cms/core';
2
+ import type { AiConfig } from "./types.js";
3
+ /** OpenAI provider, backed by the official openai SDK. */
4
+ export declare class OpenAiProvider implements NixxieAiService {
5
+ private config;
6
+ private model;
7
+ private maxTokens;
8
+ private client;
9
+ constructor(config: AiConfig);
10
+ complete(prompt: string, options?: NixxieAiCompleteOptions): Promise<NixxieAiCompletion>;
11
+ chat(messages: NixxieAiMessage[], options?: NixxieAiCompleteOptions): Promise<NixxieAiCompletion>;
12
+ embed(text: string): Promise<number[]>;
13
+ embedMany(texts: string[]): Promise<number[][]>;
14
+ }
15
+ //# sourceMappingURL=OpenAiProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenAiProvider.d.ts","sourceRoot":"../../../src","sources":["OpenAiProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,eAAe,EAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAe;AAavC,0DAA0D;AAC1D,qBAAa,cAAe,YAAW,eAAe;IACpD,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,MAAM,CAAK;gBAEP,MAAM,EAAE,QAAQ;IAQtB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIxF,IAAI,CACR,QAAQ,EAAE,eAAe,EAAE,EAC3B,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC;IAuBxB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAMtC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;CAetD"}
@@ -0,0 +1,9 @@
1
+ import type { NixxieAiService } from '@nixxie-cms/core';
2
+ import { AnthropicProvider } from "./AnthropicProvider.js";
3
+ import { OpenAiProvider } from "./OpenAiProvider.js";
4
+ import type { AiConfig } from "./types.js";
5
+ export declare function createAi(config: AiConfig): NixxieAiService;
6
+ export { AnthropicProvider, OpenAiProvider };
7
+ export type { AiConfig, AiProvider } from "./types.js";
8
+ export type { NixxieAiService, NixxieAiMessage, NixxieAiCompleteOptions, NixxieAiCompletion, } from '@nixxie-cms/core';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"../../../src","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,iBAAiB,EAAE,+BAA2B;AACvD,OAAO,EAAE,cAAc,EAAE,4BAAwB;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAe;AAEvC,wBAAgB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAW1D;AAED,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAA;AAC5C,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,mBAAe;AACnD,YAAY,EACV,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,26 @@
1
+ import type { NixxieAiCompleteOptions, NixxieAiCompletion, NixxieAiMessage, NixxieAiService } from '@nixxie-cms/core';
2
+ export type { NixxieAiCompleteOptions, NixxieAiCompletion, NixxieAiMessage, NixxieAiService };
3
+ export type AiProvider = 'anthropic' | 'openai';
4
+ export type AiConfig = {
5
+ /** LLM provider. Default: 'anthropic'. */
6
+ provider?: AiProvider;
7
+ /** API key for the provider. */
8
+ apiKey: string;
9
+ /**
10
+ * Default model for completions. Defaults to 'claude-opus-4-8' (Anthropic) or 'gpt-4o' (OpenAI).
11
+ */
12
+ model?: string;
13
+ /** Override the API base URL (proxies, gateways, Azure, etc.). */
14
+ baseUrl?: string;
15
+ /** Default max output tokens. Default: 4096. */
16
+ maxTokens?: number;
17
+ /**
18
+ * Model used for `embed`/`embedMany`. Anthropic has no native embeddings endpoint, so for the
19
+ * Anthropic provider embeddings are produced via Voyage AI (set `voyageApiKey`). Defaults to
20
+ * 'voyage-3' (Voyage) or 'text-embedding-3-small' (OpenAI).
21
+ */
22
+ embeddingModel?: string;
23
+ /** Voyage AI key, used for embeddings when `provider` is 'anthropic'. */
24
+ voyageApiKey?: string;
25
+ };
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"../../../src","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAClB,eAAe,EACf,eAAe,EAChB,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,eAAe,EAAE,eAAe,EAAE,CAAA;AAE7F,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAA;AAE/C,MAAM,MAAM,QAAQ,GAAG;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA"}
@@ -1,2 +1,2 @@
1
- export * from "../src/index.js";
2
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibml4eGllLWNtcy1haS5janMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9
1
+ export * from "./declarations/src/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibml4eGllLWNtcy1haS5janMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4vZGVjbGFyYXRpb25zL3NyYy9pbmRleC5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0=
@@ -1,16 +1,192 @@
1
- "use strict";
2
- // this file might look strange and you might be wondering what it's for
3
- // it's lets you import your source files by importing this entrypoint
4
- // as you would import it if it was built with preconstruct build
5
- // this file is slightly different to some others though
6
- // it has a require hook which compiles your code with Babel
7
- // this means that you don't have to set up @babel/register or anything like that
8
- // but you can still require this module and it'll be compiled
1
+ 'use strict';
9
2
 
10
- // this bit of code imports the require hook and registers it
11
- let unregister = require("../../../node_modules/.pnpm/@preconstruct+hook@0.4.0/node_modules/@preconstruct/hook").___internalHook(typeof __dirname === 'undefined' ? undefined : __dirname, "../../..", "..");
3
+ Object.defineProperty(exports, '__esModule', { value: true });
12
4
 
13
- // this re-exports the source file
14
- module.exports = require("../src/index.ts");
5
+ const DEFAULT_MODEL$1 = 'claude-opus-4-8';
6
+ function loadAnthropic() {
7
+ try {
8
+ var _require$default;
9
+ return (_require$default = require('@anthropic-ai/sdk').default) !== null && _require$default !== void 0 ? _require$default : require('@anthropic-ai/sdk');
10
+ } catch {
11
+ throw new Error('The Anthropic provider requires @anthropic-ai/sdk. Run: npm install @anthropic-ai/sdk');
12
+ }
13
+ }
15
14
 
16
- unregister();
15
+ /** Anthropic (Claude) provider, backed by the official @anthropic-ai/sdk. */
16
+ class AnthropicProvider {
17
+ constructor(config) {
18
+ var _config$model, _config$maxTokens;
19
+ this.config = config;
20
+ this.model = (_config$model = config.model) !== null && _config$model !== void 0 ? _config$model : DEFAULT_MODEL$1;
21
+ this.maxTokens = (_config$maxTokens = config.maxTokens) !== null && _config$maxTokens !== void 0 ? _config$maxTokens : 4096;
22
+ const Anthropic = loadAnthropic();
23
+ this.client = new Anthropic({
24
+ apiKey: config.apiKey,
25
+ baseURL: config.baseUrl
26
+ });
27
+ }
28
+ async complete(prompt, options) {
29
+ return this.chat([{
30
+ role: 'user',
31
+ content: prompt
32
+ }], options);
33
+ }
34
+ async chat(messages, options) {
35
+ var _options$model, _options$maxTokens, _response$content, _response$model, _response$usage, _response$usage2;
36
+ // Anthropic takes the system prompt as a top-level field, not a message.
37
+ const system = [options === null || options === void 0 ? void 0 : options.system, ...messages.filter(m => m.role === 'system').map(m => m.content)].filter(Boolean).join('\n\n');
38
+ const turns = messages.filter(m => m.role !== 'system').map(m => ({
39
+ role: m.role,
40
+ content: m.content
41
+ }));
42
+ const model = (_options$model = options === null || options === void 0 ? void 0 : options.model) !== null && _options$model !== void 0 ? _options$model : this.model;
43
+ const response = await this.client.messages.create({
44
+ model,
45
+ max_tokens: (_options$maxTokens = options === null || options === void 0 ? void 0 : options.maxTokens) !== null && _options$maxTokens !== void 0 ? _options$maxTokens : this.maxTokens,
46
+ system: system || undefined,
47
+ messages: turns,
48
+ ...((options === null || options === void 0 ? void 0 : options.temperature) !== undefined ? {
49
+ temperature: options.temperature
50
+ } : {})
51
+ });
52
+ const text = ((_response$content = response.content) !== null && _response$content !== void 0 ? _response$content : []).filter(block => block.type === 'text').map(block => block.text).join('');
53
+ return {
54
+ text,
55
+ model: (_response$model = response.model) !== null && _response$model !== void 0 ? _response$model : model,
56
+ usage: {
57
+ inputTokens: (_response$usage = response.usage) === null || _response$usage === void 0 ? void 0 : _response$usage.input_tokens,
58
+ outputTokens: (_response$usage2 = response.usage) === null || _response$usage2 === void 0 ? void 0 : _response$usage2.output_tokens
59
+ }
60
+ };
61
+ }
62
+ async embed(text) {
63
+ const [vector] = await this.embedMany([text]);
64
+ if (!vector) throw new Error('Embedding provider returned no vector');
65
+ return vector;
66
+ }
67
+ async embedMany(texts) {
68
+ var _this$config$embeddin, _data$data;
69
+ if (!this.config.voyageApiKey) {
70
+ throw new Error('Anthropic has no native embeddings endpoint. Set `voyageApiKey` to generate embeddings via Voyage AI, or use the OpenAI provider.');
71
+ }
72
+ const model = (_this$config$embeddin = this.config.embeddingModel) !== null && _this$config$embeddin !== void 0 ? _this$config$embeddin : 'voyage-3';
73
+ const res = await fetch('https://api.voyageai.com/v1/embeddings', {
74
+ method: 'POST',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ Authorization: `Bearer ${this.config.voyageApiKey}`
78
+ },
79
+ body: JSON.stringify({
80
+ input: texts,
81
+ model
82
+ })
83
+ });
84
+ if (!res.ok) throw new Error(`Voyage embeddings failed (${res.status}): ${await res.text()}`);
85
+ const data = await res.json();
86
+ const items = (_data$data = data.data) !== null && _data$data !== void 0 ? _data$data : [];
87
+ if (items.length !== texts.length) {
88
+ throw new Error(`Voyage returned ${items.length} embeddings for ${texts.length} inputs`);
89
+ }
90
+ // The API does not guarantee response order matches input order — sort by `index`.
91
+ return items.slice().sort((a, b) => {
92
+ var _a$index, _b$index;
93
+ return ((_a$index = a.index) !== null && _a$index !== void 0 ? _a$index : 0) - ((_b$index = b.index) !== null && _b$index !== void 0 ? _b$index : 0);
94
+ }).map(d => d.embedding);
95
+ }
96
+ }
97
+
98
+ const DEFAULT_MODEL = 'gpt-4o';
99
+ const DEFAULT_EMBEDDING_MODEL = 'text-embedding-3-small';
100
+ function loadOpenAi() {
101
+ try {
102
+ var _require$default;
103
+ return (_require$default = require('openai').default) !== null && _require$default !== void 0 ? _require$default : require('openai');
104
+ } catch {
105
+ throw new Error('The OpenAI provider requires the openai package. Run: npm install openai');
106
+ }
107
+ }
108
+
109
+ /** OpenAI provider, backed by the official openai SDK. */
110
+ class OpenAiProvider {
111
+ constructor(config) {
112
+ var _config$model, _config$maxTokens;
113
+ this.config = config;
114
+ this.model = (_config$model = config.model) !== null && _config$model !== void 0 ? _config$model : DEFAULT_MODEL;
115
+ this.maxTokens = (_config$maxTokens = config.maxTokens) !== null && _config$maxTokens !== void 0 ? _config$maxTokens : 4096;
116
+ const OpenAI = loadOpenAi();
117
+ this.client = new OpenAI({
118
+ apiKey: config.apiKey,
119
+ baseURL: config.baseUrl
120
+ });
121
+ }
122
+ async complete(prompt, options) {
123
+ return this.chat([{
124
+ role: 'user',
125
+ content: prompt
126
+ }], options);
127
+ }
128
+ async chat(messages, options) {
129
+ var _options$model, _options$maxTokens, _response$choices$0$m, _response$choices, _response$model, _response$usage, _response$usage2;
130
+ const allMessages = options !== null && options !== void 0 && options.system ? [{
131
+ role: 'system',
132
+ content: options.system
133
+ }, ...messages] : messages;
134
+ const model = (_options$model = options === null || options === void 0 ? void 0 : options.model) !== null && _options$model !== void 0 ? _options$model : this.model;
135
+ const response = await this.client.chat.completions.create({
136
+ model,
137
+ max_tokens: (_options$maxTokens = options === null || options === void 0 ? void 0 : options.maxTokens) !== null && _options$maxTokens !== void 0 ? _options$maxTokens : this.maxTokens,
138
+ messages: allMessages,
139
+ ...((options === null || options === void 0 ? void 0 : options.temperature) !== undefined ? {
140
+ temperature: options.temperature
141
+ } : {})
142
+ });
143
+ return {
144
+ text: (_response$choices$0$m = (_response$choices = response.choices) === null || _response$choices === void 0 || (_response$choices = _response$choices[0]) === null || _response$choices === void 0 || (_response$choices = _response$choices.message) === null || _response$choices === void 0 ? void 0 : _response$choices.content) !== null && _response$choices$0$m !== void 0 ? _response$choices$0$m : '',
145
+ model: (_response$model = response.model) !== null && _response$model !== void 0 ? _response$model : model,
146
+ usage: {
147
+ inputTokens: (_response$usage = response.usage) === null || _response$usage === void 0 ? void 0 : _response$usage.prompt_tokens,
148
+ outputTokens: (_response$usage2 = response.usage) === null || _response$usage2 === void 0 ? void 0 : _response$usage2.completion_tokens
149
+ }
150
+ };
151
+ }
152
+ async embed(text) {
153
+ const [vector] = await this.embedMany([text]);
154
+ if (!vector) throw new Error('Embedding provider returned no vector');
155
+ return vector;
156
+ }
157
+ async embedMany(texts) {
158
+ var _this$config$embeddin, _response$data;
159
+ const response = await this.client.embeddings.create({
160
+ model: (_this$config$embeddin = this.config.embeddingModel) !== null && _this$config$embeddin !== void 0 ? _this$config$embeddin : DEFAULT_EMBEDDING_MODEL,
161
+ input: texts
162
+ });
163
+ const items = (_response$data = response.data) !== null && _response$data !== void 0 ? _response$data : [];
164
+ if (items.length !== texts.length) {
165
+ throw new Error(`OpenAI returned ${items.length} embeddings for ${texts.length} inputs`);
166
+ }
167
+ // The API does not guarantee response order matches input order — sort by `index`.
168
+ return items.slice().sort((a, b) => {
169
+ var _a$index, _b$index;
170
+ return ((_a$index = a.index) !== null && _a$index !== void 0 ? _a$index : 0) - ((_b$index = b.index) !== null && _b$index !== void 0 ? _b$index : 0);
171
+ }).map(d => d.embedding);
172
+ }
173
+ }
174
+
175
+ function createAi(config) {
176
+ var _config$provider;
177
+ switch ((_config$provider = config.provider) !== null && _config$provider !== void 0 ? _config$provider : 'anthropic') {
178
+ case 'anthropic':
179
+ return new AnthropicProvider(config);
180
+ case 'openai':
181
+ return new OpenAiProvider(config);
182
+ default:
183
+ {
184
+ const exhaustive = config.provider;
185
+ throw new Error(`Unknown AI provider: ${exhaustive}`);
186
+ }
187
+ }
188
+ }
189
+
190
+ exports.AnthropicProvider = AnthropicProvider;
191
+ exports.OpenAiProvider = OpenAiProvider;
192
+ exports.createAi = createAi;
@@ -0,0 +1,186 @@
1
+ const DEFAULT_MODEL$1 = 'claude-opus-4-8';
2
+ function loadAnthropic() {
3
+ try {
4
+ var _require$default;
5
+ return (_require$default = require('@anthropic-ai/sdk').default) !== null && _require$default !== void 0 ? _require$default : require('@anthropic-ai/sdk');
6
+ } catch {
7
+ throw new Error('The Anthropic provider requires @anthropic-ai/sdk. Run: npm install @anthropic-ai/sdk');
8
+ }
9
+ }
10
+
11
+ /** Anthropic (Claude) provider, backed by the official @anthropic-ai/sdk. */
12
+ class AnthropicProvider {
13
+ constructor(config) {
14
+ var _config$model, _config$maxTokens;
15
+ this.config = config;
16
+ this.model = (_config$model = config.model) !== null && _config$model !== void 0 ? _config$model : DEFAULT_MODEL$1;
17
+ this.maxTokens = (_config$maxTokens = config.maxTokens) !== null && _config$maxTokens !== void 0 ? _config$maxTokens : 4096;
18
+ const Anthropic = loadAnthropic();
19
+ this.client = new Anthropic({
20
+ apiKey: config.apiKey,
21
+ baseURL: config.baseUrl
22
+ });
23
+ }
24
+ async complete(prompt, options) {
25
+ return this.chat([{
26
+ role: 'user',
27
+ content: prompt
28
+ }], options);
29
+ }
30
+ async chat(messages, options) {
31
+ var _options$model, _options$maxTokens, _response$content, _response$model, _response$usage, _response$usage2;
32
+ // Anthropic takes the system prompt as a top-level field, not a message.
33
+ const system = [options === null || options === void 0 ? void 0 : options.system, ...messages.filter(m => m.role === 'system').map(m => m.content)].filter(Boolean).join('\n\n');
34
+ const turns = messages.filter(m => m.role !== 'system').map(m => ({
35
+ role: m.role,
36
+ content: m.content
37
+ }));
38
+ const model = (_options$model = options === null || options === void 0 ? void 0 : options.model) !== null && _options$model !== void 0 ? _options$model : this.model;
39
+ const response = await this.client.messages.create({
40
+ model,
41
+ max_tokens: (_options$maxTokens = options === null || options === void 0 ? void 0 : options.maxTokens) !== null && _options$maxTokens !== void 0 ? _options$maxTokens : this.maxTokens,
42
+ system: system || undefined,
43
+ messages: turns,
44
+ ...((options === null || options === void 0 ? void 0 : options.temperature) !== undefined ? {
45
+ temperature: options.temperature
46
+ } : {})
47
+ });
48
+ const text = ((_response$content = response.content) !== null && _response$content !== void 0 ? _response$content : []).filter(block => block.type === 'text').map(block => block.text).join('');
49
+ return {
50
+ text,
51
+ model: (_response$model = response.model) !== null && _response$model !== void 0 ? _response$model : model,
52
+ usage: {
53
+ inputTokens: (_response$usage = response.usage) === null || _response$usage === void 0 ? void 0 : _response$usage.input_tokens,
54
+ outputTokens: (_response$usage2 = response.usage) === null || _response$usage2 === void 0 ? void 0 : _response$usage2.output_tokens
55
+ }
56
+ };
57
+ }
58
+ async embed(text) {
59
+ const [vector] = await this.embedMany([text]);
60
+ if (!vector) throw new Error('Embedding provider returned no vector');
61
+ return vector;
62
+ }
63
+ async embedMany(texts) {
64
+ var _this$config$embeddin, _data$data;
65
+ if (!this.config.voyageApiKey) {
66
+ throw new Error('Anthropic has no native embeddings endpoint. Set `voyageApiKey` to generate embeddings via Voyage AI, or use the OpenAI provider.');
67
+ }
68
+ const model = (_this$config$embeddin = this.config.embeddingModel) !== null && _this$config$embeddin !== void 0 ? _this$config$embeddin : 'voyage-3';
69
+ const res = await fetch('https://api.voyageai.com/v1/embeddings', {
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/json',
73
+ Authorization: `Bearer ${this.config.voyageApiKey}`
74
+ },
75
+ body: JSON.stringify({
76
+ input: texts,
77
+ model
78
+ })
79
+ });
80
+ if (!res.ok) throw new Error(`Voyage embeddings failed (${res.status}): ${await res.text()}`);
81
+ const data = await res.json();
82
+ const items = (_data$data = data.data) !== null && _data$data !== void 0 ? _data$data : [];
83
+ if (items.length !== texts.length) {
84
+ throw new Error(`Voyage returned ${items.length} embeddings for ${texts.length} inputs`);
85
+ }
86
+ // The API does not guarantee response order matches input order — sort by `index`.
87
+ return items.slice().sort((a, b) => {
88
+ var _a$index, _b$index;
89
+ return ((_a$index = a.index) !== null && _a$index !== void 0 ? _a$index : 0) - ((_b$index = b.index) !== null && _b$index !== void 0 ? _b$index : 0);
90
+ }).map(d => d.embedding);
91
+ }
92
+ }
93
+
94
+ const DEFAULT_MODEL = 'gpt-4o';
95
+ const DEFAULT_EMBEDDING_MODEL = 'text-embedding-3-small';
96
+ function loadOpenAi() {
97
+ try {
98
+ var _require$default;
99
+ return (_require$default = require('openai').default) !== null && _require$default !== void 0 ? _require$default : require('openai');
100
+ } catch {
101
+ throw new Error('The OpenAI provider requires the openai package. Run: npm install openai');
102
+ }
103
+ }
104
+
105
+ /** OpenAI provider, backed by the official openai SDK. */
106
+ class OpenAiProvider {
107
+ constructor(config) {
108
+ var _config$model, _config$maxTokens;
109
+ this.config = config;
110
+ this.model = (_config$model = config.model) !== null && _config$model !== void 0 ? _config$model : DEFAULT_MODEL;
111
+ this.maxTokens = (_config$maxTokens = config.maxTokens) !== null && _config$maxTokens !== void 0 ? _config$maxTokens : 4096;
112
+ const OpenAI = loadOpenAi();
113
+ this.client = new OpenAI({
114
+ apiKey: config.apiKey,
115
+ baseURL: config.baseUrl
116
+ });
117
+ }
118
+ async complete(prompt, options) {
119
+ return this.chat([{
120
+ role: 'user',
121
+ content: prompt
122
+ }], options);
123
+ }
124
+ async chat(messages, options) {
125
+ var _options$model, _options$maxTokens, _response$choices$0$m, _response$choices, _response$model, _response$usage, _response$usage2;
126
+ const allMessages = options !== null && options !== void 0 && options.system ? [{
127
+ role: 'system',
128
+ content: options.system
129
+ }, ...messages] : messages;
130
+ const model = (_options$model = options === null || options === void 0 ? void 0 : options.model) !== null && _options$model !== void 0 ? _options$model : this.model;
131
+ const response = await this.client.chat.completions.create({
132
+ model,
133
+ max_tokens: (_options$maxTokens = options === null || options === void 0 ? void 0 : options.maxTokens) !== null && _options$maxTokens !== void 0 ? _options$maxTokens : this.maxTokens,
134
+ messages: allMessages,
135
+ ...((options === null || options === void 0 ? void 0 : options.temperature) !== undefined ? {
136
+ temperature: options.temperature
137
+ } : {})
138
+ });
139
+ return {
140
+ text: (_response$choices$0$m = (_response$choices = response.choices) === null || _response$choices === void 0 || (_response$choices = _response$choices[0]) === null || _response$choices === void 0 || (_response$choices = _response$choices.message) === null || _response$choices === void 0 ? void 0 : _response$choices.content) !== null && _response$choices$0$m !== void 0 ? _response$choices$0$m : '',
141
+ model: (_response$model = response.model) !== null && _response$model !== void 0 ? _response$model : model,
142
+ usage: {
143
+ inputTokens: (_response$usage = response.usage) === null || _response$usage === void 0 ? void 0 : _response$usage.prompt_tokens,
144
+ outputTokens: (_response$usage2 = response.usage) === null || _response$usage2 === void 0 ? void 0 : _response$usage2.completion_tokens
145
+ }
146
+ };
147
+ }
148
+ async embed(text) {
149
+ const [vector] = await this.embedMany([text]);
150
+ if (!vector) throw new Error('Embedding provider returned no vector');
151
+ return vector;
152
+ }
153
+ async embedMany(texts) {
154
+ var _this$config$embeddin, _response$data;
155
+ const response = await this.client.embeddings.create({
156
+ model: (_this$config$embeddin = this.config.embeddingModel) !== null && _this$config$embeddin !== void 0 ? _this$config$embeddin : DEFAULT_EMBEDDING_MODEL,
157
+ input: texts
158
+ });
159
+ const items = (_response$data = response.data) !== null && _response$data !== void 0 ? _response$data : [];
160
+ if (items.length !== texts.length) {
161
+ throw new Error(`OpenAI returned ${items.length} embeddings for ${texts.length} inputs`);
162
+ }
163
+ // The API does not guarantee response order matches input order — sort by `index`.
164
+ return items.slice().sort((a, b) => {
165
+ var _a$index, _b$index;
166
+ return ((_a$index = a.index) !== null && _a$index !== void 0 ? _a$index : 0) - ((_b$index = b.index) !== null && _b$index !== void 0 ? _b$index : 0);
167
+ }).map(d => d.embedding);
168
+ }
169
+ }
170
+
171
+ function createAi(config) {
172
+ var _config$provider;
173
+ switch ((_config$provider = config.provider) !== null && _config$provider !== void 0 ? _config$provider : 'anthropic') {
174
+ case 'anthropic':
175
+ return new AnthropicProvider(config);
176
+ case 'openai':
177
+ return new OpenAiProvider(config);
178
+ default:
179
+ {
180
+ const exhaustive = config.provider;
181
+ throw new Error(`Unknown AI provider: ${exhaustive}`);
182
+ }
183
+ }
184
+ }
185
+
186
+ export { AnthropicProvider, OpenAiProvider, createAi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nixxie-cms/ai",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/nixxie-cms-ai.cjs.js",
6
6
  "module": "dist/nixxie-cms-ai.esm.js",
@@ -16,10 +16,10 @@
16
16
  "@babel/runtime": "^7.24.7"
17
17
  },
18
18
  "devDependencies": {
19
- "@nixxie-cms/core": "^1.0.0"
19
+ "@nixxie-cms/core": "^2.0.0"
20
20
  },
21
21
  "peerDependencies": {
22
- "@nixxie-cms/core": "^1.0.0"
22
+ "@nixxie-cms/core": "^2.0.0"
23
23
  },
24
24
  "optionalDependencies": {
25
25
  "@anthropic-ai/sdk": "^0.69.0",