@core-ai/openai 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +262 -69
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2,12 +2,18 @@
2
2
  import OpenAI from "openai";
3
3
 
4
4
  // src/chat-model.ts
5
- import { createStreamResult } from "@core-ai/core-ai";
5
+ import {
6
+ StructuredOutputNoObjectGeneratedError,
7
+ StructuredOutputParseError,
8
+ StructuredOutputValidationError,
9
+ createObjectStreamResult,
10
+ createStreamResult
11
+ } from "@core-ai/core-ai";
6
12
 
7
13
  // src/chat-adapter.ts
8
- import { APIError } from "openai";
9
14
  import { zodToJsonSchema } from "zod-to-json-schema";
10
- import { ProviderError } from "@core-ai/core-ai";
15
+ var DEFAULT_STRUCTURED_OUTPUT_TOOL_NAME = "core_ai_generate_object";
16
+ var DEFAULT_STRUCTURED_OUTPUT_TOOL_DESCRIPTION = "Return a JSON object that matches the requested schema.";
11
17
  function convertMessages(messages) {
12
18
  return messages.map(convertMessage);
13
19
  }
@@ -91,38 +97,66 @@ function convertToolChoice(choice) {
91
97
  }
92
98
  };
93
99
  }
100
+ function getStructuredOutputToolName(options) {
101
+ const trimmedName = options.schemaName?.trim();
102
+ if (trimmedName && trimmedName.length > 0) {
103
+ return trimmedName;
104
+ }
105
+ return DEFAULT_STRUCTURED_OUTPUT_TOOL_NAME;
106
+ }
107
+ function createStructuredOutputOptions(options) {
108
+ const toolName = getStructuredOutputToolName(options);
109
+ return {
110
+ messages: options.messages,
111
+ tools: {
112
+ structured_output: {
113
+ name: toolName,
114
+ description: options.schemaDescription ?? DEFAULT_STRUCTURED_OUTPUT_TOOL_DESCRIPTION,
115
+ parameters: options.schema
116
+ }
117
+ },
118
+ toolChoice: {
119
+ type: "tool",
120
+ toolName
121
+ },
122
+ config: options.config,
123
+ providerOptions: options.providerOptions,
124
+ signal: options.signal
125
+ };
126
+ }
94
127
  function createGenerateRequest(modelId, options) {
95
128
  return {
96
- model: modelId,
97
- messages: convertMessages(options.messages),
98
- ...options.tools && Object.keys(options.tools).length > 0 ? { tools: convertTools(options.tools) } : {},
99
- ...options.toolChoice ? { tool_choice: convertToolChoice(options.toolChoice) } : {},
100
- ...options.config?.temperature !== void 0 ? { temperature: options.config.temperature } : {},
101
- ...options.config?.maxTokens !== void 0 ? { max_tokens: options.config.maxTokens } : {},
102
- ...options.config?.topP !== void 0 ? { top_p: options.config.topP } : {},
103
- ...options.config?.stopSequences ? { stop: options.config.stopSequences } : {},
104
- ...options.config?.frequencyPenalty !== void 0 ? { frequency_penalty: options.config.frequencyPenalty } : {},
105
- ...options.config?.presencePenalty !== void 0 ? { presence_penalty: options.config.presencePenalty } : {},
129
+ ...createRequestBase(modelId, options),
106
130
  ...options.providerOptions
107
131
  };
108
132
  }
109
133
  function createStreamRequest(modelId, options) {
110
134
  return {
111
- model: modelId,
112
- messages: convertMessages(options.messages),
135
+ ...createRequestBase(modelId, options),
113
136
  stream: true,
114
137
  stream_options: {
115
138
  include_usage: true
116
139
  },
140
+ ...options.providerOptions
141
+ };
142
+ }
143
+ function createRequestBase(modelId, options) {
144
+ return {
145
+ model: modelId,
146
+ messages: convertMessages(options.messages),
117
147
  ...options.tools && Object.keys(options.tools).length > 0 ? { tools: convertTools(options.tools) } : {},
118
148
  ...options.toolChoice ? { tool_choice: convertToolChoice(options.toolChoice) } : {},
119
- ...options.config?.temperature !== void 0 ? { temperature: options.config.temperature } : {},
120
- ...options.config?.maxTokens !== void 0 ? { max_tokens: options.config.maxTokens } : {},
121
- ...options.config?.topP !== void 0 ? { top_p: options.config.topP } : {},
122
- ...options.config?.stopSequences ? { stop: options.config.stopSequences } : {},
123
- ...options.config?.frequencyPenalty !== void 0 ? { frequency_penalty: options.config.frequencyPenalty } : {},
124
- ...options.config?.presencePenalty !== void 0 ? { presence_penalty: options.config.presencePenalty } : {},
125
- ...options.providerOptions
149
+ ...mapConfigToRequestFields(options.config)
150
+ };
151
+ }
152
+ function mapConfigToRequestFields(config) {
153
+ return {
154
+ ...config?.temperature !== void 0 ? { temperature: config.temperature } : {},
155
+ ...config?.maxTokens !== void 0 ? { max_tokens: config.maxTokens } : {},
156
+ ...config?.topP !== void 0 ? { top_p: config.topP } : {},
157
+ ...config?.stopSequences ? { stop: config.stopSequences } : {},
158
+ ...config?.frequencyPenalty !== void 0 ? { frequency_penalty: config.frequencyPenalty } : {},
159
+ ...config?.presencePenalty !== void 0 ? { presence_penalty: config.presencePenalty } : {}
126
160
  };
127
161
  }
128
162
  function mapGenerateResponse(response) {
@@ -217,7 +251,9 @@ async function* transformStream(stream) {
217
251
  }
218
252
  if (choice.delta.tool_calls) {
219
253
  for (const partialToolCall of choice.delta.tool_calls) {
220
- const current = bufferedToolCalls.get(partialToolCall.index) ?? {
254
+ const current = bufferedToolCalls.get(
255
+ partialToolCall.index
256
+ ) ?? {
221
257
  id: partialToolCall.id ?? `tool-${partialToolCall.index}`,
222
258
  name: partialToolCall.function?.name ?? "",
223
259
  arguments: ""
@@ -284,7 +320,11 @@ function safeParseJsonObject(json) {
284
320
  return {};
285
321
  }
286
322
  }
287
- function wrapError(error) {
323
+
324
+ // src/openai-error.ts
325
+ import { APIError } from "openai";
326
+ import { ProviderError } from "@core-ai/core-ai";
327
+ function wrapOpenAIError(error) {
288
328
  if (error instanceof APIError) {
289
329
  return new ProviderError(error.message, "openai", error.status, error);
290
330
  }
@@ -298,33 +338,210 @@ function wrapError(error) {
298
338
 
299
339
  // src/chat-model.ts
300
340
  function createOpenAIChatModel(client, modelId) {
341
+ const provider = "openai";
342
+ async function callOpenAIChatCompletionsApi(request) {
343
+ try {
344
+ return await client.chat.completions.create(
345
+ request
346
+ );
347
+ } catch (error) {
348
+ throw wrapOpenAIError(error);
349
+ }
350
+ }
351
+ async function generateChat(options) {
352
+ const request = createGenerateRequest(modelId, options);
353
+ const response = await callOpenAIChatCompletionsApi(request);
354
+ return mapGenerateResponse(response);
355
+ }
356
+ async function streamChat(options) {
357
+ const request = createStreamRequest(modelId, options);
358
+ const stream = await callOpenAIChatCompletionsApi(request);
359
+ return createStreamResult(transformStream(stream));
360
+ }
301
361
  return {
302
- provider: "openai",
362
+ provider,
303
363
  modelId,
304
- async generate(options) {
305
- try {
306
- const request = createGenerateRequest(modelId, options);
307
- const response = await client.chat.completions.create(request);
308
- return mapGenerateResponse(response);
309
- } catch (error) {
310
- throw wrapError(error);
311
- }
364
+ generate: generateChat,
365
+ stream: streamChat,
366
+ async generateObject(options) {
367
+ const structuredOptions = createStructuredOutputOptions(options);
368
+ const result = await generateChat(structuredOptions);
369
+ const toolName = getStructuredOutputToolName(options);
370
+ const object = extractStructuredObject(
371
+ result,
372
+ options.schema,
373
+ provider,
374
+ toolName
375
+ );
376
+ return {
377
+ object,
378
+ finishReason: result.finishReason,
379
+ usage: result.usage
380
+ };
312
381
  },
313
- async stream(options) {
314
- try {
315
- const request = createStreamRequest(modelId, options);
316
- const stream = await client.chat.completions.create(request);
317
- return createStreamResult(transformStream(stream));
318
- } catch (error) {
319
- throw wrapError(error);
320
- }
382
+ async streamObject(options) {
383
+ const structuredOptions = createStructuredOutputOptions(options);
384
+ const stream = await streamChat(structuredOptions);
385
+ const toolName = getStructuredOutputToolName(options);
386
+ return createObjectStreamResult(
387
+ transformStructuredOutputStream(
388
+ stream,
389
+ options.schema,
390
+ provider,
391
+ toolName
392
+ )
393
+ );
321
394
  }
322
395
  };
323
396
  }
397
+ function extractStructuredObject(result, schema, provider, toolName) {
398
+ const structuredToolCall = result.toolCalls.find(
399
+ (toolCall) => toolCall.name === toolName
400
+ );
401
+ if (structuredToolCall) {
402
+ return validateStructuredToolArguments(
403
+ schema,
404
+ structuredToolCall.arguments,
405
+ provider
406
+ );
407
+ }
408
+ const rawOutput = result.content?.trim();
409
+ if (rawOutput && rawOutput.length > 0) {
410
+ return parseAndValidateStructuredPayload(schema, rawOutput, provider);
411
+ }
412
+ throw new StructuredOutputNoObjectGeneratedError(
413
+ "model did not emit a structured object payload",
414
+ provider
415
+ );
416
+ }
417
+ async function* transformStructuredOutputStream(stream, schema, provider, toolName) {
418
+ let validatedObject;
419
+ let contentBuffer = "";
420
+ const toolArgumentDeltas = /* @__PURE__ */ new Map();
421
+ for await (const event of stream) {
422
+ if (event.type === "content-delta") {
423
+ contentBuffer += event.text;
424
+ yield {
425
+ type: "object-delta",
426
+ text: event.text
427
+ };
428
+ continue;
429
+ }
430
+ if (event.type === "tool-call-delta") {
431
+ const previous = toolArgumentDeltas.get(event.toolCallId) ?? "";
432
+ toolArgumentDeltas.set(
433
+ event.toolCallId,
434
+ `${previous}${event.argumentsDelta}`
435
+ );
436
+ yield {
437
+ type: "object-delta",
438
+ text: event.argumentsDelta
439
+ };
440
+ continue;
441
+ }
442
+ if (event.type === "tool-call-end" && event.toolCall.name === toolName) {
443
+ validatedObject = validateStructuredToolArguments(
444
+ schema,
445
+ event.toolCall.arguments,
446
+ provider
447
+ );
448
+ yield {
449
+ type: "object",
450
+ object: validatedObject
451
+ };
452
+ continue;
453
+ }
454
+ if (event.type === "finish") {
455
+ if (validatedObject === void 0) {
456
+ const fallbackPayload = getFallbackStructuredPayload(
457
+ contentBuffer,
458
+ toolArgumentDeltas
459
+ );
460
+ if (!fallbackPayload) {
461
+ throw new StructuredOutputNoObjectGeneratedError(
462
+ "structured output stream ended without an object payload",
463
+ provider
464
+ );
465
+ }
466
+ validatedObject = parseAndValidateStructuredPayload(
467
+ schema,
468
+ fallbackPayload,
469
+ provider
470
+ );
471
+ yield {
472
+ type: "object",
473
+ object: validatedObject
474
+ };
475
+ }
476
+ yield {
477
+ type: "finish",
478
+ finishReason: event.finishReason,
479
+ usage: event.usage
480
+ };
481
+ }
482
+ }
483
+ }
484
+ function getFallbackStructuredPayload(contentBuffer, toolArgumentDeltas) {
485
+ for (const delta of toolArgumentDeltas.values()) {
486
+ const trimmed = delta.trim();
487
+ if (trimmed.length > 0) {
488
+ return trimmed;
489
+ }
490
+ }
491
+ const trimmedContent = contentBuffer.trim();
492
+ if (trimmedContent.length > 0) {
493
+ return trimmedContent;
494
+ }
495
+ return void 0;
496
+ }
497
+ function validateStructuredToolArguments(schema, toolArguments, provider) {
498
+ return validateStructuredObject(
499
+ schema,
500
+ toolArguments,
501
+ provider,
502
+ JSON.stringify(toolArguments)
503
+ );
504
+ }
505
+ function parseAndValidateStructuredPayload(schema, rawPayload, provider) {
506
+ const parsedPayload = parseJson(rawPayload, provider);
507
+ return validateStructuredObject(schema, parsedPayload, provider, rawPayload);
508
+ }
509
+ function parseJson(rawOutput, provider) {
510
+ try {
511
+ return JSON.parse(rawOutput);
512
+ } catch (error) {
513
+ throw new StructuredOutputParseError(
514
+ "failed to parse structured output as JSON",
515
+ provider,
516
+ {
517
+ rawOutput,
518
+ cause: error
519
+ }
520
+ );
521
+ }
522
+ }
523
+ function validateStructuredObject(schema, value, provider, rawOutput) {
524
+ const parsed = schema.safeParse(value);
525
+ if (parsed.success) {
526
+ return parsed.data;
527
+ }
528
+ throw new StructuredOutputValidationError(
529
+ "structured output does not match schema",
530
+ provider,
531
+ formatZodIssues(parsed.error.issues),
532
+ {
533
+ rawOutput
534
+ }
535
+ );
536
+ }
537
+ function formatZodIssues(issues) {
538
+ return issues.map((issue) => {
539
+ const path = issue.path.length > 0 ? issue.path.map((segment) => String(segment)).join(".") : "<root>";
540
+ return `${path}: ${issue.message}`;
541
+ });
542
+ }
324
543
 
325
544
  // src/embedding-model.ts
326
- import { APIError as APIError2 } from "openai";
327
- import { ProviderError as ProviderError2 } from "@core-ai/core-ai";
328
545
  function createOpenAIEmbeddingModel(client, modelId) {
329
546
  return {
330
547
  provider: "openai",
@@ -344,26 +561,13 @@ function createOpenAIEmbeddingModel(client, modelId) {
344
561
  }
345
562
  };
346
563
  } catch (error) {
347
- throw wrapError2(error);
564
+ throw wrapOpenAIError(error);
348
565
  }
349
566
  }
350
567
  };
351
568
  }
352
- function wrapError2(error) {
353
- if (error instanceof APIError2) {
354
- return new ProviderError2(error.message, "openai", error.status, error);
355
- }
356
- return new ProviderError2(
357
- error instanceof Error ? error.message : String(error),
358
- "openai",
359
- void 0,
360
- error
361
- );
362
- }
363
569
 
364
570
  // src/image-model.ts
365
- import { APIError as APIError3 } from "openai";
366
- import { ProviderError as ProviderError3 } from "@core-ai/core-ai";
367
571
  function createOpenAIImageModel(client, modelId) {
368
572
  return {
369
573
  provider: "openai",
@@ -388,22 +592,11 @@ function createOpenAIImageModel(client, modelId) {
388
592
  }))
389
593
  };
390
594
  } catch (error) {
391
- throw wrapError3(error);
595
+ throw wrapOpenAIError(error);
392
596
  }
393
597
  }
394
598
  };
395
599
  }
396
- function wrapError3(error) {
397
- if (error instanceof APIError3) {
398
- return new ProviderError3(error.message, "openai", error.status, error);
399
- }
400
- return new ProviderError3(
401
- error instanceof Error ? error.message : String(error),
402
- "openai",
403
- void 0,
404
- error
405
- );
406
- }
407
600
 
408
601
  // src/provider.ts
409
602
  function createOpenAI(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/openai",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "OpenAI provider package for @core-ai/core-ai",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",
@@ -43,7 +43,7 @@
43
43
  "test:watch": "vitest"
44
44
  },
45
45
  "dependencies": {
46
- "@core-ai/core-ai": "^0.2.1",
46
+ "@core-ai/core-ai": "^0.3.0",
47
47
  "openai": "^6.1.0",
48
48
  "zod-to-json-schema": "^3.25.1"
49
49
  },