@core-ai/core-ai 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 +21 -0
- package/README.md +32 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +132 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Omnifact (https://omnifact.ai)
|
|
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,32 @@
|
|
|
1
|
+
# @core-ai/core-ai
|
|
2
|
+
|
|
3
|
+
Type-safe LLM abstraction layer over native provider SDKs.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @core-ai/core-ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Provider packages are published separately:
|
|
12
|
+
|
|
13
|
+
- `@core-ai/openai`
|
|
14
|
+
- `@core-ai/anthropic`
|
|
15
|
+
- `@core-ai/google-genai`
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { generate } from '@core-ai/core-ai';
|
|
21
|
+
import { createOpenAI } from '@core-ai/openai';
|
|
22
|
+
|
|
23
|
+
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
24
|
+
const model = openai.chatModel('gpt-5-mini');
|
|
25
|
+
|
|
26
|
+
const result = await generate({
|
|
27
|
+
model,
|
|
28
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(result.content);
|
|
32
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
type Message = SystemMessage | UserMessage | AssistantMessage | ToolResultMessage;
|
|
4
|
+
type SystemMessage = {
|
|
5
|
+
role: 'system';
|
|
6
|
+
content: string;
|
|
7
|
+
};
|
|
8
|
+
type UserMessage = {
|
|
9
|
+
role: 'user';
|
|
10
|
+
content: string | UserContentPart[];
|
|
11
|
+
};
|
|
12
|
+
type UserContentPart = TextPart | ImagePart | FilePart;
|
|
13
|
+
type TextPart = {
|
|
14
|
+
type: 'text';
|
|
15
|
+
text: string;
|
|
16
|
+
};
|
|
17
|
+
type ImagePart = {
|
|
18
|
+
type: 'image';
|
|
19
|
+
source: {
|
|
20
|
+
type: 'base64';
|
|
21
|
+
mediaType: string;
|
|
22
|
+
data: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'url';
|
|
25
|
+
url: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
type FilePart = {
|
|
29
|
+
type: 'file';
|
|
30
|
+
data: string;
|
|
31
|
+
mimeType: string;
|
|
32
|
+
filename?: string;
|
|
33
|
+
};
|
|
34
|
+
type AssistantMessage = {
|
|
35
|
+
role: 'assistant';
|
|
36
|
+
content: string | null;
|
|
37
|
+
toolCalls?: ToolCall[];
|
|
38
|
+
};
|
|
39
|
+
type ToolCall = {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
arguments: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
type ToolResultMessage = {
|
|
45
|
+
role: 'tool';
|
|
46
|
+
toolCallId: string;
|
|
47
|
+
content: string;
|
|
48
|
+
isError?: boolean;
|
|
49
|
+
};
|
|
50
|
+
type ToolDefinition = {
|
|
51
|
+
name: string;
|
|
52
|
+
description: string;
|
|
53
|
+
parameters: z.ZodType;
|
|
54
|
+
};
|
|
55
|
+
type ToolSet = Record<string, ToolDefinition>;
|
|
56
|
+
type ToolChoice = 'auto' | 'none' | 'required' | {
|
|
57
|
+
type: 'tool';
|
|
58
|
+
toolName: string;
|
|
59
|
+
};
|
|
60
|
+
type ChatModel = {
|
|
61
|
+
readonly provider: string;
|
|
62
|
+
readonly modelId: string;
|
|
63
|
+
generate(options: GenerateOptions): Promise<GenerateResult>;
|
|
64
|
+
stream(options: GenerateOptions): Promise<StreamResult>;
|
|
65
|
+
};
|
|
66
|
+
type ModelConfig = {
|
|
67
|
+
temperature?: number;
|
|
68
|
+
maxTokens?: number;
|
|
69
|
+
topP?: number;
|
|
70
|
+
stopSequences?: string[];
|
|
71
|
+
frequencyPenalty?: number;
|
|
72
|
+
presencePenalty?: number;
|
|
73
|
+
};
|
|
74
|
+
type GenerateOptions = {
|
|
75
|
+
messages: Message[];
|
|
76
|
+
tools?: ToolSet;
|
|
77
|
+
toolChoice?: ToolChoice;
|
|
78
|
+
config?: ModelConfig;
|
|
79
|
+
providerOptions?: Record<string, unknown>;
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
};
|
|
82
|
+
type GenerateResult = {
|
|
83
|
+
content: string | null;
|
|
84
|
+
toolCalls: ToolCall[];
|
|
85
|
+
finishReason: FinishReason;
|
|
86
|
+
usage: ChatUsage;
|
|
87
|
+
};
|
|
88
|
+
type FinishReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'unknown';
|
|
89
|
+
/**
|
|
90
|
+
* Token usage reported by the model after a chat completion.
|
|
91
|
+
*
|
|
92
|
+
* `outputTokens` is the **total** output token count, including both visible
|
|
93
|
+
* text and any internal reasoning/thinking the model performed.
|
|
94
|
+
* `reasoningTokens` is the subset of `outputTokens` consumed by reasoning.
|
|
95
|
+
* For non-reasoning models (or providers that don't report it separately)
|
|
96
|
+
* this will be `0`.
|
|
97
|
+
*
|
|
98
|
+
* Provider mapping:
|
|
99
|
+
* - **OpenAI**: `reasoningTokens` comes from `completion_tokens_details.reasoning_tokens`.
|
|
100
|
+
* - **Google Gemini**: `reasoningTokens` comes from `thoughtsTokenCount`;
|
|
101
|
+
* `outputTokens` = `candidatesTokenCount + thoughtsTokenCount`.
|
|
102
|
+
* - **Anthropic**: `reasoningTokens` is always `0` (thinking tokens are
|
|
103
|
+
* included in `output_tokens` but not reported separately by the API).
|
|
104
|
+
*/
|
|
105
|
+
type ChatUsage = {
|
|
106
|
+
/** Number of tokens in the input prompt. */
|
|
107
|
+
inputTokens: number;
|
|
108
|
+
/** Total output tokens, including both visible text and reasoning. */
|
|
109
|
+
outputTokens: number;
|
|
110
|
+
/** Tokens consumed by internal reasoning/thinking. Subset of `outputTokens`. */
|
|
111
|
+
reasoningTokens: number;
|
|
112
|
+
/** Sum of all tokens (`inputTokens + outputTokens`). */
|
|
113
|
+
totalTokens: number;
|
|
114
|
+
};
|
|
115
|
+
type StreamEvent = {
|
|
116
|
+
type: 'content-delta';
|
|
117
|
+
text: string;
|
|
118
|
+
} | {
|
|
119
|
+
type: 'tool-call-start';
|
|
120
|
+
toolCallId: string;
|
|
121
|
+
toolName: string;
|
|
122
|
+
} | {
|
|
123
|
+
type: 'tool-call-delta';
|
|
124
|
+
toolCallId: string;
|
|
125
|
+
argumentsDelta: string;
|
|
126
|
+
} | {
|
|
127
|
+
type: 'tool-call-end';
|
|
128
|
+
toolCall: ToolCall;
|
|
129
|
+
} | {
|
|
130
|
+
type: 'finish';
|
|
131
|
+
finishReason: FinishReason;
|
|
132
|
+
usage: ChatUsage;
|
|
133
|
+
};
|
|
134
|
+
type StreamResult = AsyncIterable<StreamEvent> & {
|
|
135
|
+
toResponse(): Promise<GenerateResult>;
|
|
136
|
+
};
|
|
137
|
+
type EmbeddingModel = {
|
|
138
|
+
readonly provider: string;
|
|
139
|
+
readonly modelId: string;
|
|
140
|
+
embed(options: EmbedOptions): Promise<EmbedResult>;
|
|
141
|
+
};
|
|
142
|
+
type EmbedOptions = {
|
|
143
|
+
input: string | string[];
|
|
144
|
+
dimensions?: number;
|
|
145
|
+
providerOptions?: Record<string, unknown>;
|
|
146
|
+
};
|
|
147
|
+
type EmbedResult = {
|
|
148
|
+
embeddings: number[][];
|
|
149
|
+
usage: EmbeddingUsage;
|
|
150
|
+
};
|
|
151
|
+
type EmbeddingUsage = {
|
|
152
|
+
inputTokens: number;
|
|
153
|
+
};
|
|
154
|
+
type ImageModel = {
|
|
155
|
+
readonly provider: string;
|
|
156
|
+
readonly modelId: string;
|
|
157
|
+
generate(options: ImageGenerateOptions): Promise<ImageGenerateResult>;
|
|
158
|
+
};
|
|
159
|
+
type ImageGenerateOptions = {
|
|
160
|
+
prompt: string;
|
|
161
|
+
n?: number;
|
|
162
|
+
size?: string;
|
|
163
|
+
providerOptions?: Record<string, unknown>;
|
|
164
|
+
};
|
|
165
|
+
type ImageGenerateResult = {
|
|
166
|
+
images: GeneratedImage[];
|
|
167
|
+
};
|
|
168
|
+
type GeneratedImage = {
|
|
169
|
+
base64?: string;
|
|
170
|
+
url?: string;
|
|
171
|
+
revisedPrompt?: string;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
declare class LLMError extends Error {
|
|
175
|
+
readonly cause?: unknown;
|
|
176
|
+
constructor(message: string, cause?: unknown);
|
|
177
|
+
}
|
|
178
|
+
declare class ProviderError extends LLMError {
|
|
179
|
+
readonly provider: string;
|
|
180
|
+
readonly statusCode?: number;
|
|
181
|
+
constructor(message: string, provider: string, statusCode?: number, cause?: unknown);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
declare function defineTool(options: ToolDefinition): ToolDefinition;
|
|
185
|
+
|
|
186
|
+
type GenerateParams = GenerateOptions & {
|
|
187
|
+
model: ChatModel;
|
|
188
|
+
};
|
|
189
|
+
declare function generate(params: GenerateParams): Promise<GenerateResult>;
|
|
190
|
+
|
|
191
|
+
type StreamParams = GenerateOptions & {
|
|
192
|
+
model: ChatModel;
|
|
193
|
+
};
|
|
194
|
+
declare function stream(params: StreamParams): Promise<StreamResult>;
|
|
195
|
+
|
|
196
|
+
declare function createStreamResult(source: AsyncIterable<StreamEvent>): StreamResult;
|
|
197
|
+
|
|
198
|
+
type EmbedParams = EmbedOptions & {
|
|
199
|
+
model: EmbeddingModel;
|
|
200
|
+
};
|
|
201
|
+
declare function embed(params: EmbedParams): Promise<EmbedResult>;
|
|
202
|
+
|
|
203
|
+
type GenerateImageParams = ImageGenerateOptions & {
|
|
204
|
+
model: ImageModel;
|
|
205
|
+
};
|
|
206
|
+
declare function generateImage(params: GenerateImageParams): Promise<ImageGenerateResult>;
|
|
207
|
+
|
|
208
|
+
export { type AssistantMessage, type ChatModel, type ChatUsage, type EmbedOptions, type EmbedResult, type EmbeddingModel, type EmbeddingUsage, type FilePart, type FinishReason, type GenerateOptions, type GenerateResult, type GeneratedImage, type ImageGenerateOptions, type ImageGenerateResult, type ImageModel, type ImagePart, LLMError, type Message, type ModelConfig, ProviderError, type StreamEvent, type StreamResult, type SystemMessage, type TextPart, type ToolCall, type ToolChoice, type ToolDefinition, type ToolResultMessage, type ToolSet, type UserContentPart, type UserMessage, createStreamResult, defineTool, embed, generate, generateImage, stream };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var LLMError = class extends Error {
|
|
3
|
+
cause;
|
|
4
|
+
constructor(message, cause) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "LLMError";
|
|
7
|
+
this.cause = cause;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var ProviderError = class extends LLMError {
|
|
11
|
+
provider;
|
|
12
|
+
statusCode;
|
|
13
|
+
constructor(message, provider, statusCode, cause) {
|
|
14
|
+
super(message, cause);
|
|
15
|
+
this.name = "ProviderError";
|
|
16
|
+
this.provider = provider;
|
|
17
|
+
this.statusCode = statusCode;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/tool.ts
|
|
22
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
23
|
+
function defineTool(options) {
|
|
24
|
+
return options;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/generate.ts
|
|
28
|
+
async function generate(params) {
|
|
29
|
+
if (params.messages.length === 0) {
|
|
30
|
+
throw new LLMError("messages must not be empty");
|
|
31
|
+
}
|
|
32
|
+
const { model, ...options } = params;
|
|
33
|
+
return model.generate(options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/stream-chat.ts
|
|
37
|
+
async function stream(params) {
|
|
38
|
+
if (params.messages.length === 0) {
|
|
39
|
+
throw new LLMError("messages must not be empty");
|
|
40
|
+
}
|
|
41
|
+
const { model, ...options } = params;
|
|
42
|
+
return model.stream(options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/stream.ts
|
|
46
|
+
function createStreamResult(source) {
|
|
47
|
+
let resolveResponse;
|
|
48
|
+
const responsePromise = new Promise((resolve) => {
|
|
49
|
+
resolveResponse = resolve;
|
|
50
|
+
});
|
|
51
|
+
let iteratorCreated = false;
|
|
52
|
+
async function* iterate() {
|
|
53
|
+
let content = "";
|
|
54
|
+
const toolCalls = [];
|
|
55
|
+
let finishReason = "unknown";
|
|
56
|
+
let usage = {
|
|
57
|
+
inputTokens: 0,
|
|
58
|
+
outputTokens: 0,
|
|
59
|
+
reasoningTokens: 0,
|
|
60
|
+
totalTokens: 0
|
|
61
|
+
};
|
|
62
|
+
for await (const event of source) {
|
|
63
|
+
if (event.type === "content-delta") {
|
|
64
|
+
content += event.text;
|
|
65
|
+
} else if (event.type === "tool-call-end") {
|
|
66
|
+
toolCalls.push(event.toolCall);
|
|
67
|
+
} else if (event.type === "finish") {
|
|
68
|
+
finishReason = event.finishReason;
|
|
69
|
+
usage = event.usage;
|
|
70
|
+
}
|
|
71
|
+
yield event;
|
|
72
|
+
}
|
|
73
|
+
resolveResponse?.({
|
|
74
|
+
content: content.length > 0 ? content : null,
|
|
75
|
+
toolCalls,
|
|
76
|
+
finishReason,
|
|
77
|
+
usage
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const generator = iterate();
|
|
81
|
+
return {
|
|
82
|
+
[Symbol.asyncIterator]() {
|
|
83
|
+
if (iteratorCreated) {
|
|
84
|
+
throw new Error("Stream can only be iterated once");
|
|
85
|
+
}
|
|
86
|
+
iteratorCreated = true;
|
|
87
|
+
return generator;
|
|
88
|
+
},
|
|
89
|
+
toResponse() {
|
|
90
|
+
if (!iteratorCreated) {
|
|
91
|
+
iteratorCreated = true;
|
|
92
|
+
(async () => {
|
|
93
|
+
for await (const _event of generator) {
|
|
94
|
+
}
|
|
95
|
+
})();
|
|
96
|
+
}
|
|
97
|
+
return responsePromise;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/embed.ts
|
|
103
|
+
async function embed(params) {
|
|
104
|
+
const { input } = params;
|
|
105
|
+
if (typeof input === "string" && input.length === 0) {
|
|
106
|
+
throw new LLMError("input must not be empty");
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(input) && input.length === 0) {
|
|
109
|
+
throw new LLMError("input must not be empty");
|
|
110
|
+
}
|
|
111
|
+
const { model, ...options } = params;
|
|
112
|
+
return model.embed(options);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/generate-image.ts
|
|
116
|
+
async function generateImage(params) {
|
|
117
|
+
if (params.prompt.length === 0) {
|
|
118
|
+
throw new LLMError("prompt must not be empty");
|
|
119
|
+
}
|
|
120
|
+
const { model, ...options } = params;
|
|
121
|
+
return model.generate(options);
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
LLMError,
|
|
125
|
+
ProviderError,
|
|
126
|
+
createStreamResult,
|
|
127
|
+
defineTool,
|
|
128
|
+
embed,
|
|
129
|
+
generate,
|
|
130
|
+
generateImage,
|
|
131
|
+
stream
|
|
132
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@core-ai/core-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe LLM abstraction layer over native provider SDKs",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Omnifact (https://omnifact.ai)",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/agdevhq/ai-core.git",
|
|
10
|
+
"directory": "packages/core-ai"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["llm", "ai", "sdk", "openai", "anthropic", "google-genai"],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"lint": "eslint src/ --max-warnings 0",
|
|
29
|
+
"check-types": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "npm run build",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"zod": "^3.25.76",
|
|
36
|
+
"zod-to-json-schema": "^3.24.5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@core-ai/eslint-config": "^0.0.0",
|
|
40
|
+
"@core-ai/typescript-config": "^0.0.0",
|
|
41
|
+
"typescript": "^5.7.3",
|
|
42
|
+
"vitest": "^3.2.4"
|
|
43
|
+
}
|
|
44
|
+
}
|