@core-ai/core-ai 0.5.1 → 0.6.1

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/README.md CHANGED
@@ -15,6 +15,7 @@ Provider packages are published separately:
15
15
  - `@core-ai/openai`
16
16
  - `@core-ai/anthropic`
17
17
  - `@core-ai/google-genai`
18
+ - `@core-ai/mistral`
18
19
 
19
20
  ## Usage
20
21
 
package/dist/index.d.ts CHANGED
@@ -42,7 +42,18 @@ type AssistantTextPart = {
42
42
  type ReasoningPart = {
43
43
  type: 'reasoning';
44
44
  text: string;
45
- providerMetadata?: Record<string, unknown>;
45
+ /**
46
+ * Provider-namespaced metadata for this reasoning block. The top-level key is
47
+ * the provider identifier (e.g. `'anthropic'`, `'openai'`), which also serves as
48
+ * the ownership discriminator: an adapter checks for the presence of its own key
49
+ * to detect cross-provider blocks. Cross-provider blocks are downgraded to plain
50
+ * text (preserving context) rather than forwarding opaque metadata that would
51
+ * cause an API error on the receiving provider.
52
+ *
53
+ * @example Anthropic: `{ anthropic: { signature: '...' } }`
54
+ * @example OpenAI: `{ openai: { encryptedContent: '...' } }`
55
+ */
56
+ providerMetadata?: Record<string, Record<string, unknown>>;
46
57
  };
47
58
  type ToolCallPart = {
48
59
  type: 'tool-call';
@@ -78,26 +89,31 @@ type ChatModel = {
78
89
  readonly provider: string;
79
90
  readonly modelId: string;
80
91
  generate(options: GenerateOptions): Promise<GenerateResult>;
81
- stream(options: GenerateOptions): Promise<StreamResult>;
92
+ stream(options: GenerateOptions): Promise<ChatStream>;
82
93
  generateObject<TSchema extends z.ZodType>(options: GenerateObjectOptions<TSchema>): Promise<GenerateObjectResult<TSchema>>;
83
- streamObject<TSchema extends z.ZodType>(options: StreamObjectOptions<TSchema>): Promise<StreamObjectResult<TSchema>>;
94
+ streamObject<TSchema extends z.ZodType>(options: StreamObjectOptions<TSchema>): Promise<ObjectStream<TSchema>>;
84
95
  };
85
- type ModelConfig = {
96
+ interface GenerateProviderOptions {
97
+ [key: string]: Record<string, unknown> | undefined;
98
+ }
99
+ interface EmbedProviderOptions {
100
+ [key: string]: Record<string, unknown> | undefined;
101
+ }
102
+ interface ImageProviderOptions {
103
+ [key: string]: Record<string, unknown> | undefined;
104
+ }
105
+ type BaseGenerateOptions = {
106
+ messages: Message[];
86
107
  temperature?: number;
87
108
  maxTokens?: number;
88
109
  topP?: number;
89
- stopSequences?: string[];
90
- frequencyPenalty?: number;
91
- presencePenalty?: number;
92
- };
93
- type GenerateOptions = {
94
- messages: Message[];
95
110
  reasoning?: ReasoningConfig;
111
+ providerOptions?: GenerateProviderOptions;
112
+ signal?: AbortSignal;
113
+ };
114
+ type GenerateOptions = BaseGenerateOptions & {
96
115
  tools?: ToolSet;
97
116
  toolChoice?: ToolChoice;
98
- config?: ModelConfig;
99
- providerOptions?: Record<string, unknown>;
100
- signal?: AbortSignal;
101
117
  };
102
118
  type GenerateResult = {
103
119
  parts: AssistantContentPart[];
@@ -107,15 +123,10 @@ type GenerateResult = {
107
123
  finishReason: FinishReason;
108
124
  usage: ChatUsage;
109
125
  };
110
- type GenerateObjectOptions<TSchema extends z.ZodType> = {
111
- messages: Message[];
126
+ type GenerateObjectOptions<TSchema extends z.ZodType> = BaseGenerateOptions & {
112
127
  schema: TSchema;
113
128
  schemaName?: string;
114
129
  schemaDescription?: string;
115
- reasoning?: ReasoningConfig;
116
- config?: ModelConfig;
117
- providerOptions?: Record<string, unknown>;
118
- signal?: AbortSignal;
119
130
  };
120
131
  type StreamObjectOptions<TSchema extends z.ZodType> = GenerateObjectOptions<TSchema>;
121
132
  type GenerateObjectResult<TSchema extends z.ZodType> = {
@@ -170,6 +181,7 @@ type StreamEvent = {
170
181
  text: string;
171
182
  } | {
172
183
  type: 'reasoning-end';
184
+ providerMetadata?: Record<string, Record<string, unknown>>;
173
185
  } | {
174
186
  type: 'text-delta';
175
187
  text: string;
@@ -189,8 +201,21 @@ type StreamEvent = {
189
201
  finishReason: FinishReason;
190
202
  usage: ChatUsage;
191
203
  };
192
- type StreamResult = AsyncIterable<StreamEvent> & {
193
- toResponse(): Promise<GenerateResult>;
204
+ /**
205
+ * Handle for a single in-flight chat streaming operation.
206
+ *
207
+ * The handle is replayable: iterating after some or all events have already
208
+ * arrived replays the buffered event history before waiting for later events.
209
+ *
210
+ * `result` resolves with the aggregated final response when the operation
211
+ * completes successfully, and rejects on abort or upstream failure.
212
+ *
213
+ * `events` always resolves with all observed events up to the terminal point,
214
+ * including abort and failure cases.
215
+ */
216
+ type ChatStream = AsyncIterable<StreamEvent> & {
217
+ readonly result: Promise<GenerateResult>;
218
+ readonly events: Promise<readonly StreamEvent[]>;
194
219
  };
195
220
  type ObjectStreamEvent<TSchema extends z.ZodType> = {
196
221
  type: 'object-delta';
@@ -203,8 +228,16 @@ type ObjectStreamEvent<TSchema extends z.ZodType> = {
203
228
  finishReason: FinishReason;
204
229
  usage: ChatUsage;
205
230
  };
206
- type StreamObjectResult<TSchema extends z.ZodType> = AsyncIterable<ObjectStreamEvent<TSchema>> & {
207
- toResponse(): Promise<GenerateObjectResult<TSchema>>;
231
+ /**
232
+ * Handle for a single in-flight structured object streaming operation.
233
+ *
234
+ * The lifecycle semantics mirror `ChatStream`: iteration is replayable,
235
+ * `result` settles independently of event consumption, and `events` resolves
236
+ * with the observed history.
237
+ */
238
+ type ObjectStream<TSchema extends z.ZodType> = AsyncIterable<ObjectStreamEvent<TSchema>> & {
239
+ readonly result: Promise<GenerateObjectResult<TSchema>>;
240
+ readonly events: Promise<readonly ObjectStreamEvent<TSchema>[]>;
208
241
  };
209
242
  type EmbeddingModel = {
210
243
  readonly provider: string;
@@ -214,7 +247,7 @@ type EmbeddingModel = {
214
247
  type EmbedOptions = {
215
248
  input: string | string[];
216
249
  dimensions?: number;
217
- providerOptions?: Record<string, unknown>;
250
+ providerOptions?: EmbedProviderOptions;
218
251
  };
219
252
  type EmbedResult = {
220
253
  embeddings: number[][];
@@ -237,7 +270,7 @@ type ImageGenerateOptions = {
237
270
  prompt: string;
238
271
  n?: number;
239
272
  size?: string;
240
- providerOptions?: Record<string, unknown>;
273
+ providerOptions?: ImageProviderOptions;
241
274
  };
242
275
  type ImageGenerateResult = {
243
276
  images: GeneratedImage[];
@@ -252,6 +285,9 @@ declare class LLMError extends Error {
252
285
  readonly cause?: unknown;
253
286
  constructor(message: string, cause?: unknown);
254
287
  }
288
+ declare class StreamAbortedError extends LLMError {
289
+ constructor(message?: string, cause?: unknown);
290
+ }
255
291
  declare class ProviderError extends LLMError {
256
292
  readonly provider: string;
257
293
  readonly statusCode?: number;
@@ -298,15 +334,21 @@ declare function generateObject<TSchema extends z.ZodType>(params: GenerateObjec
298
334
  type StreamParams = GenerateOptions & {
299
335
  model: ChatModel;
300
336
  };
301
- declare function stream(params: StreamParams): Promise<StreamResult>;
337
+ declare function stream(params: StreamParams): Promise<ChatStream>;
302
338
 
303
339
  type StreamObjectParams<TSchema extends z.ZodType> = StreamObjectOptions<TSchema> & {
304
340
  model: ChatModel;
305
341
  };
306
- declare function streamObject<TSchema extends z.ZodType>(params: StreamObjectParams<TSchema>): Promise<StreamObjectResult<TSchema>>;
307
- declare function createObjectStreamResult<TSchema extends z.ZodType>(source: AsyncIterable<ObjectStreamEvent<TSchema>>): StreamObjectResult<TSchema>;
342
+ declare function streamObject<TSchema extends z.ZodType>(params: StreamObjectParams<TSchema>): Promise<ObjectStream<TSchema>>;
343
+ declare function createObjectStream<TSchema extends z.ZodType>(source: AsyncIterable<ObjectStreamEvent<TSchema>>, options?: {
344
+ signal?: AbortSignal;
345
+ }): ObjectStream<TSchema>;
346
+
347
+ declare function createChatStream(source: AsyncIterable<StreamEvent> | (() => Promise<AsyncIterable<StreamEvent>>), options?: {
348
+ signal?: AbortSignal;
349
+ }): ChatStream;
308
350
 
309
- declare function createStreamResult(source: AsyncIterable<StreamEvent>): StreamResult;
351
+ declare function getProviderMetadata<T extends Record<string, unknown>>(providerMetadata: Record<string, Record<string, unknown>> | undefined, provider: string): T | undefined;
310
352
 
311
353
  type EmbedParams = EmbedOptions & {
312
354
  model: EmbeddingModel;
@@ -318,4 +360,4 @@ type GenerateImageParams = ImageGenerateOptions & {
318
360
  };
319
361
  declare function generateImage(params: GenerateImageParams): Promise<ImageGenerateResult>;
320
362
 
321
- export { type AssistantContentPart, type AssistantMessage, type AssistantTextPart, type ChatInputTokenDetails, type ChatModel, type ChatOutputTokenDetails, type ChatUsage, type EmbedOptions, type EmbedResult, type EmbeddingModel, type EmbeddingUsage, type FilePart, type FinishReason, type GenerateObjectOptions, type GenerateObjectResult, type GenerateOptions, type GenerateResult, type GeneratedImage, type ImageGenerateOptions, type ImageGenerateResult, type ImageModel, type ImagePart, LLMError, type Message, type ModelConfig, type ObjectStreamEvent, ProviderError, type ReasoningConfig, type ReasoningEffort, type ReasoningPart, type StreamEvent, type StreamObjectOptions, type StreamObjectResult, type StreamResult, StructuredOutputError, StructuredOutputNoObjectGeneratedError, StructuredOutputParseError, StructuredOutputValidationError, type SystemMessage, type TextPart, type ToolCall, type ToolCallPart, type ToolChoice, type ToolDefinition, type ToolResultMessage, type ToolSet, type UserContentPart, type UserMessage, assistantMessage, createObjectStreamResult, createStreamResult, defineTool, embed, generate, generateImage, generateObject, resultToMessage, stream, streamObject };
363
+ export { type AssistantContentPart, type AssistantMessage, type AssistantTextPart, type BaseGenerateOptions, type ChatInputTokenDetails, type ChatModel, type ChatOutputTokenDetails, type ChatStream, type ChatUsage, type EmbedOptions, type EmbedProviderOptions, type EmbedResult, type EmbeddingModel, type EmbeddingUsage, type FilePart, type FinishReason, type GenerateObjectOptions, type GenerateObjectResult, type GenerateOptions, type GenerateProviderOptions, type GenerateResult, type GeneratedImage, type ImageGenerateOptions, type ImageGenerateResult, type ImageModel, type ImagePart, type ImageProviderOptions, LLMError, type Message, type ObjectStream, type ObjectStreamEvent, ProviderError, type ReasoningConfig, type ReasoningEffort, type ReasoningPart, StreamAbortedError, type StreamEvent, type StreamObjectOptions, StructuredOutputError, StructuredOutputNoObjectGeneratedError, StructuredOutputParseError, StructuredOutputValidationError, type SystemMessage, type TextPart, type ToolCall, type ToolCallPart, type ToolChoice, type ToolDefinition, type ToolResultMessage, type ToolSet, type UserContentPart, type UserMessage, assistantMessage, createChatStream, createObjectStream, defineTool, embed, generate, generateImage, generateObject, getProviderMetadata, resultToMessage, stream, streamObject };
package/dist/index.js CHANGED
@@ -7,6 +7,12 @@ var LLMError = class extends Error {
7
7
  this.cause = cause;
8
8
  }
9
9
  };
10
+ var StreamAbortedError = class extends LLMError {
11
+ constructor(message = "stream aborted", cause) {
12
+ super(message, cause);
13
+ this.name = "StreamAbortedError";
14
+ }
15
+ };
10
16
  var ProviderError = class extends LLMError {
11
17
  provider;
12
18
  statusCode;
@@ -68,263 +74,439 @@ function assistantMessage(content) {
68
74
  };
69
75
  }
70
76
 
71
- // src/generate.ts
72
- async function generate(params) {
73
- if (params.messages.length === 0) {
77
+ // src/assertions.ts
78
+ function isEmptyText(value) {
79
+ return value.length === 0;
80
+ }
81
+ function assertNonEmptyMessages(messages) {
82
+ if (messages.length === 0) {
74
83
  throw new LLMError("messages must not be empty");
75
84
  }
85
+ }
86
+ function assertNonEmptyEmbedInput(input) {
87
+ const isEmptyString = typeof input === "string" && isEmptyText(input);
88
+ const isEmptyArray = Array.isArray(input) && input.length === 0;
89
+ if (isEmptyString || isEmptyArray) {
90
+ throw new LLMError("input must not be empty");
91
+ }
92
+ }
93
+ function assertNonEmptyPrompt(prompt) {
94
+ if (isEmptyText(prompt)) {
95
+ throw new LLMError("prompt must not be empty");
96
+ }
97
+ }
98
+
99
+ // src/model-options.ts
100
+ function splitModelFromParams(params) {
76
101
  const { model, ...options } = params;
102
+ return {
103
+ model,
104
+ options
105
+ };
106
+ }
107
+
108
+ // src/generate.ts
109
+ async function generate(params) {
110
+ assertNonEmptyMessages(params.messages);
111
+ const { model, options } = splitModelFromParams(params);
77
112
  return model.generate(options);
78
113
  }
79
114
 
80
115
  // src/generate-object.ts
81
116
  async function generateObject(params) {
82
- if (params.messages.length === 0) {
83
- throw new LLMError("messages must not be empty");
84
- }
85
- const { model, ...options } = params;
117
+ assertNonEmptyMessages(params.messages);
118
+ const { model, options } = splitModelFromParams(params);
86
119
  return model.generateObject(options);
87
120
  }
88
121
 
89
122
  // src/stream-chat.ts
90
123
  async function stream(params) {
91
- if (params.messages.length === 0) {
92
- throw new LLMError("messages must not be empty");
93
- }
94
- const { model, ...options } = params;
124
+ assertNonEmptyMessages(params.messages);
125
+ const { model, options } = splitModelFromParams(params);
95
126
  return model.stream(options);
96
127
  }
97
128
 
98
- // src/stream-object.ts
99
- async function streamObject(params) {
100
- if (params.messages.length === 0) {
101
- throw new LLMError("messages must not be empty");
129
+ // src/base-stream.ts
130
+ function createStream(options) {
131
+ const { source, reduceEvent, finalizeResult, signal } = options;
132
+ const iterator = source[Symbol.asyncIterator]();
133
+ const bufferedEvents = [];
134
+ let terminalState = { status: "running" };
135
+ let resolveResult;
136
+ let rejectResult;
137
+ let resolveEvents;
138
+ const result = new Promise((resolve, reject) => {
139
+ resolveResult = resolve;
140
+ rejectResult = reject;
141
+ });
142
+ void result.catch(() => {
143
+ });
144
+ const events = new Promise((resolve) => {
145
+ resolveEvents = resolve;
146
+ });
147
+ const waiters = /* @__PURE__ */ new Set();
148
+ let closeSourceIteratorPromise;
149
+ function notifyWaiters() {
150
+ for (const waiter of waiters) {
151
+ waiter();
152
+ }
153
+ waiters.clear();
102
154
  }
103
- const { model, ...options } = params;
104
- return model.streamObject(options);
105
- }
106
- function createObjectStreamResult(source) {
107
- let resolveResponse;
108
- let rejectResponse;
109
- const responsePromise = new Promise(
110
- (resolve, reject) => {
111
- resolveResponse = resolve;
112
- rejectResponse = reject;
155
+ function resolveWhenUpdated() {
156
+ if (terminalState.status !== "running") {
157
+ return Promise.resolve();
158
+ }
159
+ return new Promise((resolve) => {
160
+ waiters.add(resolve);
161
+ });
162
+ }
163
+ function cleanupSignalListener() {
164
+ signal?.removeEventListener("abort", abortStream);
165
+ }
166
+ function settleCompleted(finalResult) {
167
+ if (terminalState.status !== "running") {
168
+ return;
169
+ }
170
+ terminalState = {
171
+ status: "completed",
172
+ result: finalResult
173
+ };
174
+ cleanupSignalListener();
175
+ resolveResult?.(finalResult);
176
+ resolveEvents?.([...bufferedEvents]);
177
+ notifyWaiters();
178
+ }
179
+ function settleRejected(error) {
180
+ if (terminalState.status !== "running") {
181
+ return;
113
182
  }
114
- );
115
- let iteratorCreated = false;
116
- async function* iterate() {
117
- let objectResult;
118
- let finishReason = "unknown";
119
- let usage = {
120
- inputTokens: 0,
121
- outputTokens: 0,
122
- inputTokenDetails: {
123
- cacheReadTokens: 0,
124
- cacheWriteTokens: 0
125
- },
126
- outputTokenDetails: {}
183
+ terminalState = {
184
+ status: "rejected",
185
+ error
127
186
  };
187
+ cleanupSignalListener();
188
+ rejectResult?.(error);
189
+ resolveEvents?.([...bufferedEvents]);
190
+ notifyWaiters();
191
+ }
192
+ function closeSourceIterator() {
193
+ if (closeSourceIteratorPromise) {
194
+ return closeSourceIteratorPromise;
195
+ }
196
+ closeSourceIteratorPromise = (async () => {
197
+ try {
198
+ await iterator.return?.();
199
+ } catch {
200
+ }
201
+ })();
202
+ return closeSourceIteratorPromise;
203
+ }
204
+ function abortStream() {
205
+ if (terminalState.status !== "running") {
206
+ return;
207
+ }
208
+ settleRejected(new StreamAbortedError("stream aborted"));
209
+ void closeSourceIterator();
210
+ }
211
+ if (signal) {
212
+ if (signal.aborted) {
213
+ abortStream();
214
+ } else {
215
+ signal.addEventListener("abort", abortStream, { once: true });
216
+ }
217
+ }
218
+ async function pump() {
128
219
  try {
129
- for await (const event of source) {
130
- if (event.type === "object") {
131
- objectResult = event.object;
132
- } else if (event.type === "finish") {
133
- finishReason = event.finishReason;
134
- usage = event.usage;
220
+ while (terminalState.status === "running") {
221
+ const next = await iterator.next();
222
+ if (terminalState.status !== "running") {
223
+ await closeSourceIterator();
224
+ return;
135
225
  }
136
- yield event;
137
- }
138
- if (objectResult === void 0) {
139
- throw new LLMError(
140
- "object stream completed without emitting a final object"
141
- );
226
+ if (next.done) {
227
+ try {
228
+ settleCompleted(finalizeResult());
229
+ } catch (error) {
230
+ settleRejected(error);
231
+ }
232
+ return;
233
+ }
234
+ bufferedEvents.push(next.value);
235
+ notifyWaiters();
236
+ reduceEvent(next.value);
142
237
  }
143
- resolveResponse?.({
144
- object: objectResult,
145
- finishReason,
146
- usage
147
- });
148
238
  } catch (error) {
149
- rejectResponse?.(error);
150
- throw error;
239
+ settleRejected(error);
240
+ await closeSourceIterator();
151
241
  }
152
242
  }
153
- const generator = iterate();
243
+ void pump();
154
244
  return {
155
245
  [Symbol.asyncIterator]() {
156
- if (iteratorCreated) {
157
- throw new Error("Stream can only be iterated once");
158
- }
159
- iteratorCreated = true;
160
- return generator;
161
- },
162
- toResponse() {
163
- if (!iteratorCreated) {
164
- iteratorCreated = true;
165
- (async () => {
166
- try {
167
- for await (const _event of generator) {
246
+ let index = 0;
247
+ let closed = false;
248
+ return {
249
+ async next() {
250
+ if (closed) {
251
+ return {
252
+ done: true,
253
+ value: void 0
254
+ };
255
+ }
256
+ while (!closed && index >= bufferedEvents.length) {
257
+ if (terminalState.status === "completed") {
258
+ return {
259
+ done: true,
260
+ value: void 0
261
+ };
168
262
  }
169
- } catch {
263
+ if (terminalState.status === "rejected") {
264
+ throw terminalState.error;
265
+ }
266
+ await resolveWhenUpdated();
170
267
  }
171
- })();
172
- }
173
- return responsePromise;
174
- }
268
+ if (closed) {
269
+ return {
270
+ done: true,
271
+ value: void 0
272
+ };
273
+ }
274
+ const value = bufferedEvents[index];
275
+ index += 1;
276
+ return {
277
+ done: false,
278
+ value
279
+ };
280
+ },
281
+ async return() {
282
+ closed = true;
283
+ notifyWaiters();
284
+ return {
285
+ done: true,
286
+ value: void 0
287
+ };
288
+ }
289
+ };
290
+ },
291
+ result,
292
+ events
175
293
  };
176
294
  }
177
295
 
178
- // src/stream.ts
179
- function createStreamResult(source) {
180
- let resolveResponse;
181
- const responsePromise = new Promise((resolve) => {
182
- resolveResponse = resolve;
183
- });
184
- let iteratorCreated = false;
185
- async function* iterate() {
186
- const parts = [];
187
- let textBuffer = "";
188
- let reasoningBuffer = "";
189
- let insideReasoning = false;
190
- let finishReason = "unknown";
191
- let usage = {
192
- inputTokens: 0,
193
- outputTokens: 0,
194
- inputTokenDetails: {
195
- cacheReadTokens: 0,
196
- cacheWriteTokens: 0
197
- },
198
- outputTokenDetails: {}
199
- };
200
- const flushText = () => {
201
- if (textBuffer.length === 0) {
202
- return;
203
- }
204
- parts.push({
205
- type: "text",
206
- text: textBuffer
207
- });
208
- textBuffer = "";
209
- };
210
- const flushReasoning = () => {
211
- if (reasoningBuffer.length === 0) {
212
- return;
213
- }
214
- parts.push({
215
- type: "reasoning",
216
- text: reasoningBuffer
217
- });
218
- reasoningBuffer = "";
219
- };
220
- for await (const event of source) {
221
- if (event.type === "reasoning-start") {
222
- flushText();
223
- flushReasoning();
224
- insideReasoning = true;
225
- } else if (event.type === "reasoning-delta") {
226
- if (!insideReasoning) {
227
- flushText();
228
- insideReasoning = true;
229
- }
230
- reasoningBuffer += event.text;
231
- } else if (event.type === "reasoning-end") {
232
- flushReasoning();
233
- insideReasoning = false;
234
- } else if (event.type === "text-delta") {
235
- if (insideReasoning) {
236
- flushReasoning();
237
- insideReasoning = false;
238
- }
239
- textBuffer += event.text;
240
- } else if (event.type === "tool-call-end") {
241
- flushText();
242
- flushReasoning();
243
- insideReasoning = false;
244
- parts.push({
245
- type: "tool-call",
246
- toolCall: event.toolCall
247
- });
296
+ // src/stream-object.ts
297
+ async function streamObject(params) {
298
+ assertNonEmptyMessages(params.messages);
299
+ const { model, ...options } = params;
300
+ return model.streamObject(options);
301
+ }
302
+ function createObjectStream(source, options = {}) {
303
+ const { signal } = options;
304
+ let objectState = {
305
+ status: "pending"
306
+ };
307
+ let finishReason = "unknown";
308
+ let usage = {
309
+ inputTokens: 0,
310
+ outputTokens: 0,
311
+ inputTokenDetails: {
312
+ cacheReadTokens: 0,
313
+ cacheWriteTokens: 0
314
+ },
315
+ outputTokenDetails: {}
316
+ };
317
+ return createStream({
318
+ source,
319
+ signal,
320
+ reduceEvent(event) {
321
+ if (event.type === "object") {
322
+ objectState = {
323
+ status: "ready",
324
+ object: event.object
325
+ };
248
326
  } else if (event.type === "finish") {
249
327
  finishReason = event.finishReason;
250
328
  usage = event.usage;
251
329
  }
252
- yield event;
330
+ },
331
+ finalizeResult() {
332
+ if (objectState.status !== "ready") {
333
+ throw new LLMError(
334
+ "object stream completed without emitting a final object"
335
+ );
336
+ }
337
+ return {
338
+ object: objectState.object,
339
+ finishReason,
340
+ usage
341
+ };
342
+ }
343
+ });
344
+ }
345
+
346
+ // src/stream.ts
347
+ function createChatStream(source, options = {}) {
348
+ const { signal } = options;
349
+ const resolvedSource = typeof source === "function" ? (async function* () {
350
+ yield* await source();
351
+ })() : source;
352
+ const parts = [];
353
+ let textBuffer = "";
354
+ let reasoningBuffer = "";
355
+ let reasoningProviderMetadata;
356
+ let insideReasoning = false;
357
+ let finishReason = "unknown";
358
+ let usage = {
359
+ inputTokens: 0,
360
+ outputTokens: 0,
361
+ inputTokenDetails: {
362
+ cacheReadTokens: 0,
363
+ cacheWriteTokens: 0
364
+ },
365
+ outputTokenDetails: {}
366
+ };
367
+ const flushText = () => {
368
+ if (textBuffer.length === 0) {
369
+ return;
370
+ }
371
+ parts.push({
372
+ type: "text",
373
+ text: textBuffer
374
+ });
375
+ textBuffer = "";
376
+ };
377
+ const flushReasoning = () => {
378
+ if (reasoningBuffer.length === 0 && reasoningProviderMetadata === void 0) {
379
+ return;
253
380
  }
381
+ parts.push({
382
+ type: "reasoning",
383
+ text: reasoningBuffer,
384
+ ...reasoningProviderMetadata ? { providerMetadata: reasoningProviderMetadata } : {}
385
+ });
386
+ reasoningBuffer = "";
387
+ reasoningProviderMetadata = void 0;
388
+ };
389
+ const startReasoning = () => {
254
390
  flushText();
255
391
  flushReasoning();
256
- const content = parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
257
- const reasoning = parts.flatMap((part) => part.type === "reasoning" ? [part.text] : []).join("");
258
- const toolCalls = parts.flatMap(
259
- (part) => part.type === "tool-call" ? [part.toolCall] : []
260
- );
261
- resolveResponse?.({
262
- parts,
263
- content: content.length > 0 ? content : null,
264
- reasoning: reasoning.length > 0 ? reasoning : null,
265
- toolCalls,
266
- finishReason,
267
- usage
392
+ insideReasoning = true;
393
+ };
394
+ const appendReasoning = (text) => {
395
+ if (!insideReasoning) {
396
+ flushText();
397
+ insideReasoning = true;
398
+ }
399
+ reasoningBuffer += text;
400
+ };
401
+ const endReasoning = (providerMetadata) => {
402
+ reasoningProviderMetadata = providerMetadata;
403
+ flushReasoning();
404
+ insideReasoning = false;
405
+ };
406
+ const appendText = (text) => {
407
+ if (insideReasoning) {
408
+ flushReasoning();
409
+ insideReasoning = false;
410
+ }
411
+ textBuffer += text;
412
+ };
413
+ const appendToolCall = (toolCall) => {
414
+ flushText();
415
+ flushReasoning();
416
+ insideReasoning = false;
417
+ parts.push({
418
+ type: "tool-call",
419
+ toolCall
268
420
  });
269
- }
270
- const generator = iterate();
271
- return {
272
- [Symbol.asyncIterator]() {
273
- if (iteratorCreated) {
274
- throw new Error("Stream can only be iterated once");
421
+ };
422
+ const setFinish = (event) => {
423
+ finishReason = event.finishReason;
424
+ usage = event.usage;
425
+ };
426
+ return createStream({
427
+ source: resolvedSource,
428
+ signal,
429
+ reduceEvent(event) {
430
+ switch (event.type) {
431
+ case "reasoning-start":
432
+ startReasoning();
433
+ break;
434
+ case "reasoning-delta":
435
+ appendReasoning(event.text);
436
+ break;
437
+ case "reasoning-end":
438
+ endReasoning(event.providerMetadata);
439
+ break;
440
+ case "text-delta":
441
+ appendText(event.text);
442
+ break;
443
+ case "tool-call-end":
444
+ appendToolCall(event.toolCall);
445
+ break;
446
+ case "finish":
447
+ setFinish(event);
448
+ break;
449
+ default:
450
+ break;
275
451
  }
276
- iteratorCreated = true;
277
- return generator;
278
452
  },
279
- toResponse() {
280
- if (!iteratorCreated) {
281
- iteratorCreated = true;
282
- (async () => {
283
- for await (const _event of generator) {
284
- }
285
- })();
286
- }
287
- return responsePromise;
453
+ finalizeResult() {
454
+ flushText();
455
+ flushReasoning();
456
+ const content = parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
457
+ const reasoning = parts.flatMap(
458
+ (part) => part.type === "reasoning" ? [part.text] : []
459
+ ).join("");
460
+ const toolCalls = parts.flatMap(
461
+ (part) => part.type === "tool-call" ? [part.toolCall] : []
462
+ );
463
+ return {
464
+ parts,
465
+ content: content.length > 0 ? content : null,
466
+ reasoning: reasoning.length > 0 ? reasoning : null,
467
+ toolCalls,
468
+ finishReason,
469
+ usage
470
+ };
288
471
  }
289
- };
472
+ });
473
+ }
474
+
475
+ // src/provider-metadata.ts
476
+ function getProviderMetadata(providerMetadata, provider) {
477
+ return providerMetadata?.[provider];
290
478
  }
291
479
 
292
480
  // src/embed.ts
293
481
  async function embed(params) {
294
- const { input } = params;
295
- if (typeof input === "string" && input.length === 0) {
296
- throw new LLMError("input must not be empty");
297
- }
298
- if (Array.isArray(input) && input.length === 0) {
299
- throw new LLMError("input must not be empty");
300
- }
301
- const { model, ...options } = params;
482
+ assertNonEmptyEmbedInput(params.input);
483
+ const { model, options } = splitModelFromParams(params);
302
484
  return model.embed(options);
303
485
  }
304
486
 
305
487
  // src/generate-image.ts
306
488
  async function generateImage(params) {
307
- if (params.prompt.length === 0) {
308
- throw new LLMError("prompt must not be empty");
309
- }
310
- const { model, ...options } = params;
489
+ assertNonEmptyPrompt(params.prompt);
490
+ const { model, options } = splitModelFromParams(params);
311
491
  return model.generate(options);
312
492
  }
313
493
  export {
314
494
  LLMError,
315
495
  ProviderError,
496
+ StreamAbortedError,
316
497
  StructuredOutputError,
317
498
  StructuredOutputNoObjectGeneratedError,
318
499
  StructuredOutputParseError,
319
500
  StructuredOutputValidationError,
320
501
  assistantMessage,
321
- createObjectStreamResult,
322
- createStreamResult,
502
+ createChatStream,
503
+ createObjectStream,
323
504
  defineTool,
324
505
  embed,
325
506
  generate,
326
507
  generateImage,
327
508
  generateObject,
509
+ getProviderMetadata,
328
510
  resultToMessage,
329
511
  stream,
330
512
  streamObject
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/core-ai",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "Type-safe LLM abstraction layer over native provider SDKs",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",
@@ -47,8 +47,9 @@
47
47
  "zod-to-json-schema": "^3.25.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@core-ai/eslint-config": "^0.0.0",
51
- "@core-ai/typescript-config": "^0.0.0",
50
+ "@core-ai/eslint-config": "*",
51
+ "@core-ai/testing": "*",
52
+ "@core-ai/typescript-config": "*",
52
53
  "typescript": "^5.7.3",
53
54
  "vitest": "^3.2.4"
54
55
  }