@effect/ai-openai 4.0.0-beta.4 → 4.0.0-beta.41
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/dist/Generated.d.ts +4060 -4038
- package/dist/Generated.d.ts.map +1 -1
- package/dist/Generated.js +1 -1
- package/dist/Generated.js.map +1 -1
- package/dist/OpenAiClient.d.ts +56 -10
- package/dist/OpenAiClient.d.ts.map +1 -1
- package/dist/OpenAiClient.js +197 -7
- package/dist/OpenAiClient.js.map +1 -1
- package/dist/OpenAiEmbeddingModel.d.ts +85 -0
- package/dist/OpenAiEmbeddingModel.d.ts.map +1 -0
- package/dist/OpenAiEmbeddingModel.js +116 -0
- package/dist/OpenAiEmbeddingModel.js.map +1 -0
- package/dist/OpenAiError.d.ts +22 -32
- package/dist/OpenAiError.d.ts.map +1 -1
- package/dist/OpenAiLanguageModel.d.ts.map +1 -1
- package/dist/OpenAiLanguageModel.js +177 -64
- package/dist/OpenAiLanguageModel.js.map +1 -1
- package/dist/OpenAiTool.d.ts +24 -24
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/errors.js +4 -4
- package/dist/internal/errors.js.map +1 -1
- package/package.json +3 -3
- package/src/Generated.ts +125 -122
- package/src/OpenAiClient.ts +323 -40
- package/src/OpenAiEmbeddingModel.ts +200 -0
- package/src/OpenAiError.ts +24 -32
- package/src/OpenAiLanguageModel.ts +218 -66
- package/src/index.ts +9 -0
- package/src/internal/errors.ts +6 -4
package/src/OpenAiError.ts
CHANGED
|
@@ -57,51 +57,43 @@ export type OpenAiRateLimitMetadata = OpenAiErrorMetadata & {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
declare module "effect/unstable/ai/AiError" {
|
|
60
|
-
export interface
|
|
61
|
-
readonly
|
|
62
|
-
readonly openai?: OpenAiRateLimitMetadata | null
|
|
63
|
-
}
|
|
60
|
+
export interface RateLimitErrorMetadata {
|
|
61
|
+
readonly openai?: OpenAiRateLimitMetadata | null
|
|
64
62
|
}
|
|
65
63
|
|
|
66
|
-
export interface
|
|
67
|
-
readonly
|
|
68
|
-
readonly openai?: OpenAiErrorMetadata | null
|
|
69
|
-
}
|
|
64
|
+
export interface QuotaExhaustedErrorMetadata {
|
|
65
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
export interface
|
|
73
|
-
readonly
|
|
74
|
-
readonly openai?: OpenAiErrorMetadata | null
|
|
75
|
-
}
|
|
68
|
+
export interface AuthenticationErrorMetadata {
|
|
69
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
76
70
|
}
|
|
77
71
|
|
|
78
|
-
export interface
|
|
79
|
-
readonly
|
|
80
|
-
readonly openai?: OpenAiErrorMetadata | null
|
|
81
|
-
}
|
|
72
|
+
export interface ContentPolicyErrorMetadata {
|
|
73
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
82
74
|
}
|
|
83
75
|
|
|
84
|
-
export interface
|
|
85
|
-
readonly
|
|
86
|
-
readonly openai?: OpenAiErrorMetadata | null
|
|
87
|
-
}
|
|
76
|
+
export interface InvalidRequestErrorMetadata {
|
|
77
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
88
78
|
}
|
|
89
79
|
|
|
90
|
-
export interface
|
|
91
|
-
readonly
|
|
92
|
-
readonly openai?: OpenAiErrorMetadata | null
|
|
93
|
-
}
|
|
80
|
+
export interface InternalProviderErrorMetadata {
|
|
81
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
94
82
|
}
|
|
95
83
|
|
|
96
|
-
export interface
|
|
97
|
-
readonly
|
|
98
|
-
readonly openai?: OpenAiErrorMetadata | null
|
|
99
|
-
}
|
|
84
|
+
export interface InvalidOutputErrorMetadata {
|
|
85
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
100
86
|
}
|
|
101
87
|
|
|
102
|
-
export interface
|
|
103
|
-
readonly
|
|
104
|
-
|
|
105
|
-
|
|
88
|
+
export interface StructuredOutputErrorMetadata {
|
|
89
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface UnsupportedSchemaErrorMetadata {
|
|
93
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface UnknownErrorMetadata {
|
|
97
|
+
readonly openai?: OpenAiErrorMetadata | null
|
|
106
98
|
}
|
|
107
99
|
}
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as DateTime from "effect/DateTime"
|
|
10
10
|
import * as Effect from "effect/Effect"
|
|
11
|
-
import * as
|
|
11
|
+
import * as Encoding from "effect/Encoding"
|
|
12
12
|
import { dual } from "effect/Function"
|
|
13
13
|
import * as Layer from "effect/Layer"
|
|
14
|
+
import * as Option from "effect/Option"
|
|
14
15
|
import * as Predicate from "effect/Predicate"
|
|
15
16
|
import * as Redactable from "effect/Redactable"
|
|
16
17
|
import * as Schema from "effect/Schema"
|
|
@@ -23,6 +24,7 @@ import * as AiError from "effect/unstable/ai/AiError"
|
|
|
23
24
|
import * as IdGenerator from "effect/unstable/ai/IdGenerator"
|
|
24
25
|
import * as LanguageModel from "effect/unstable/ai/LanguageModel"
|
|
25
26
|
import * as AiModel from "effect/unstable/ai/Model"
|
|
27
|
+
import { toCodecOpenAI } from "effect/unstable/ai/OpenAiStructuredOutput"
|
|
26
28
|
import type * as Prompt from "effect/unstable/ai/Prompt"
|
|
27
29
|
import type * as Response from "effect/unstable/ai/Response"
|
|
28
30
|
import * as Tool from "effect/unstable/ai/Tool"
|
|
@@ -319,7 +321,7 @@ export const model = (
|
|
|
319
321
|
model: (string & {}) | Model,
|
|
320
322
|
config?: Omit<typeof Config.Service, "model">
|
|
321
323
|
): AiModel.Model<"openai", LanguageModel.LanguageModel, OpenAiClient> =>
|
|
322
|
-
AiModel.make("openai", layer({ model, config }))
|
|
324
|
+
AiModel.make("openai", model, layer({ model, config }))
|
|
323
325
|
|
|
324
326
|
// TODO
|
|
325
327
|
// /**
|
|
@@ -330,7 +332,7 @@ export const model = (
|
|
|
330
332
|
// model: (string & {}) | Model,
|
|
331
333
|
// config?: Omit<typeof Config.Service, "model">
|
|
332
334
|
// ): AiModel.Model<"openai", LanguageModel.LanguageModel | Tokenizer.Tokenizer, OpenAiClient> =>
|
|
333
|
-
// AiModel.make("openai", layerWithTokenizer({ model, config }))
|
|
335
|
+
// AiModel.make("openai", model, layerWithTokenizer({ model, config }))
|
|
334
336
|
|
|
335
337
|
/**
|
|
336
338
|
* Creates an OpenAI language model service.
|
|
@@ -369,7 +371,7 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
369
371
|
options,
|
|
370
372
|
toolNameMapper
|
|
371
373
|
})
|
|
372
|
-
const responseFormat = prepareResponseFormat({
|
|
374
|
+
const responseFormat = yield* prepareResponseFormat({
|
|
373
375
|
config,
|
|
374
376
|
options
|
|
375
377
|
})
|
|
@@ -381,14 +383,16 @@ export const make = Effect.fnUntraced(function*({ model, config: providerConfig
|
|
|
381
383
|
verbosity: config.text?.verbosity ?? null,
|
|
382
384
|
format: responseFormat
|
|
383
385
|
},
|
|
384
|
-
...(
|
|
385
|
-
...(
|
|
386
|
+
...(tools ? { tools } : undefined),
|
|
387
|
+
...(toolChoice ? { tool_choice: toolChoice } : undefined),
|
|
388
|
+
...(options.previousResponseId ? { previous_response_id: options.previousResponseId } : undefined)
|
|
386
389
|
}
|
|
387
390
|
return request
|
|
388
391
|
}
|
|
389
392
|
)
|
|
390
393
|
|
|
391
394
|
return yield* LanguageModel.make({
|
|
395
|
+
codecTransformer: toCodecOpenAI,
|
|
392
396
|
generateText: Effect.fnUntraced(
|
|
393
397
|
function*(options) {
|
|
394
398
|
const config = yield* makeConfig
|
|
@@ -547,16 +551,17 @@ const prepareMessages = Effect.fnUntraced(
|
|
|
547
551
|
if (config.store === false && capabilities.isReasoningModel) {
|
|
548
552
|
include.add("reasoning.encrypted_content")
|
|
549
553
|
}
|
|
550
|
-
if (
|
|
554
|
+
if (codeInterpreterTool) {
|
|
551
555
|
include.add("code_interpreter_call.outputs")
|
|
552
556
|
}
|
|
553
|
-
if (
|
|
557
|
+
if (webSearchTool || webSearchPreviewTool) {
|
|
554
558
|
include.add("web_search_call.action.sources")
|
|
555
559
|
}
|
|
556
560
|
|
|
557
561
|
const messages: Array<typeof Generated.InputItem.Encoded> = []
|
|
562
|
+
const prompt = options.incrementalPrompt ?? options.prompt
|
|
558
563
|
|
|
559
|
-
for (const message of
|
|
564
|
+
for (const message of prompt.content) {
|
|
560
565
|
switch (message.role) {
|
|
561
566
|
case "system": {
|
|
562
567
|
messages.push({
|
|
@@ -592,7 +597,7 @@ const prepareMessages = Effect.fnUntraced(
|
|
|
592
597
|
}
|
|
593
598
|
|
|
594
599
|
if (part.data instanceof Uint8Array) {
|
|
595
|
-
const base64 =
|
|
600
|
+
const base64 = Encoding.encodeBase64(part.data)
|
|
596
601
|
const imageUrl = `data:${mediaType};base64,${base64}`
|
|
597
602
|
content.push({ type: "input_image", image_url: imageUrl, detail })
|
|
598
603
|
}
|
|
@@ -606,7 +611,7 @@ const prepareMessages = Effect.fnUntraced(
|
|
|
606
611
|
}
|
|
607
612
|
|
|
608
613
|
if (part.data instanceof Uint8Array) {
|
|
609
|
-
const base64 =
|
|
614
|
+
const base64 = Encoding.encodeBase64(part.data)
|
|
610
615
|
const fileName = part.fileName ?? `part-${index}.pdf`
|
|
611
616
|
const fileData = `data:application/pdf;base64,${base64}`
|
|
612
617
|
content.push({ type: "input_file", filename: fileName, file_data: fileData })
|
|
@@ -921,7 +926,7 @@ const buildHttpRequestDetails = (
|
|
|
921
926
|
method: request.method,
|
|
922
927
|
url: request.url,
|
|
923
928
|
urlParams: Array.from(request.urlParams),
|
|
924
|
-
hash: request.hash,
|
|
929
|
+
hash: Option.getOrUndefined(request.hash),
|
|
925
930
|
headers: Redactable.redact(request.headers) as Record<string, string>
|
|
926
931
|
})
|
|
927
932
|
|
|
@@ -1036,10 +1041,11 @@ const makeResponse = Effect.fnUntraced(
|
|
|
1036
1041
|
|
|
1037
1042
|
case "function_call": {
|
|
1038
1043
|
hasToolCalls = true
|
|
1044
|
+
|
|
1039
1045
|
const toolName = part.name
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
try: () => Tool.unsafeSecureJsonParse(
|
|
1046
|
+
|
|
1047
|
+
const toolParams = yield* Effect.try({
|
|
1048
|
+
try: () => Tool.unsafeSecureJsonParse(part.arguments),
|
|
1043
1049
|
catch: (cause) =>
|
|
1044
1050
|
AiError.make({
|
|
1045
1051
|
module: "OpenAiLanguageModel",
|
|
@@ -1051,6 +1057,9 @@ const makeResponse = Effect.fnUntraced(
|
|
|
1051
1057
|
})
|
|
1052
1058
|
})
|
|
1053
1059
|
})
|
|
1060
|
+
|
|
1061
|
+
const params = yield* transformToolCallParams(options.tools, part.name, toolParams)
|
|
1062
|
+
|
|
1054
1063
|
parts.push({
|
|
1055
1064
|
type: "tool-call",
|
|
1056
1065
|
id: part.call_id,
|
|
@@ -1370,16 +1379,42 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1370
1379
|
// Track annotations for current message to include in text-end metadata
|
|
1371
1380
|
const activeAnnotations: Array<typeof Generated.Annotation.Encoded> = []
|
|
1372
1381
|
|
|
1382
|
+
type ReasoningSummaryPartStatus = "active" | "can-conclude" | "concluded"
|
|
1383
|
+
type ReasoningPart = {
|
|
1384
|
+
encryptedContent: string | undefined
|
|
1385
|
+
summaryParts: Record<number, ReasoningSummaryPartStatus>
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1373
1388
|
// Track active reasoning items with state machine for proper concluding logic
|
|
1374
|
-
const activeReasoning: Record<string, {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1389
|
+
const activeReasoning: Record<string, ReasoningPart> = {}
|
|
1390
|
+
|
|
1391
|
+
const getOrCreateReasoningPart = (
|
|
1392
|
+
itemId: string,
|
|
1393
|
+
encryptedContent?: string | null
|
|
1394
|
+
): ReasoningPart => {
|
|
1395
|
+
const activePart = activeReasoning[itemId]
|
|
1396
|
+
if (Predicate.isNotUndefined(activePart)) {
|
|
1397
|
+
if (Predicate.isNotNullish(encryptedContent)) {
|
|
1398
|
+
activePart.encryptedContent = encryptedContent
|
|
1399
|
+
}
|
|
1400
|
+
return activePart
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
const reasoningPart: ReasoningPart = {
|
|
1404
|
+
encryptedContent: Predicate.isNotNullish(encryptedContent) ? encryptedContent : undefined,
|
|
1405
|
+
summaryParts: {}
|
|
1406
|
+
}
|
|
1407
|
+
activeReasoning[itemId] = reasoningPart
|
|
1408
|
+
return reasoningPart
|
|
1409
|
+
}
|
|
1378
1410
|
|
|
1379
1411
|
// Track active tool calls with optional provider-specific state
|
|
1380
1412
|
const activeToolCalls: Record<number, {
|
|
1381
1413
|
readonly id: string
|
|
1382
1414
|
readonly name: string
|
|
1415
|
+
readonly functionCall?: {
|
|
1416
|
+
emitted: boolean
|
|
1417
|
+
}
|
|
1383
1418
|
readonly applyPatch?: {
|
|
1384
1419
|
hasDiff: boolean
|
|
1385
1420
|
endEmitted: boolean
|
|
@@ -1529,7 +1564,8 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1529
1564
|
case "function_call": {
|
|
1530
1565
|
activeToolCalls[event.output_index] = {
|
|
1531
1566
|
id: event.item.call_id,
|
|
1532
|
-
name: event.item.name
|
|
1567
|
+
name: event.item.name,
|
|
1568
|
+
functionCall: { emitted: false }
|
|
1533
1569
|
}
|
|
1534
1570
|
parts.push({
|
|
1535
1571
|
type: "tool-params-start",
|
|
@@ -1572,21 +1608,20 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1572
1608
|
}
|
|
1573
1609
|
|
|
1574
1610
|
case "reasoning": {
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
...makeEncryptedContentMetadata(event.item.encrypted_content)
|
|
1611
|
+
const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content)
|
|
1612
|
+
if (Predicate.isUndefined(reasoningPart.summaryParts[0])) {
|
|
1613
|
+
reasoningPart.summaryParts[0] = "active"
|
|
1614
|
+
parts.push({
|
|
1615
|
+
type: "reasoning-start",
|
|
1616
|
+
id: `${event.item.id}:0`,
|
|
1617
|
+
metadata: {
|
|
1618
|
+
openai: {
|
|
1619
|
+
...makeItemIdMetadata(event.item.id),
|
|
1620
|
+
...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
|
|
1621
|
+
}
|
|
1587
1622
|
}
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1623
|
+
})
|
|
1624
|
+
}
|
|
1590
1625
|
break
|
|
1591
1626
|
}
|
|
1592
1627
|
|
|
@@ -1729,12 +1764,20 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1729
1764
|
}
|
|
1730
1765
|
|
|
1731
1766
|
case "function_call": {
|
|
1767
|
+
const toolCall = activeToolCalls[event.output_index]
|
|
1768
|
+
if (Predicate.isNotUndefined(toolCall?.functionCall?.emitted) && toolCall.functionCall.emitted) {
|
|
1769
|
+
delete activeToolCalls[event.output_index]
|
|
1770
|
+
break
|
|
1771
|
+
}
|
|
1732
1772
|
delete activeToolCalls[event.output_index]
|
|
1773
|
+
|
|
1733
1774
|
hasToolCalls = true
|
|
1775
|
+
|
|
1734
1776
|
const toolName = event.item.name
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
|
|
1777
|
+
const toolArgs = event.item.arguments
|
|
1778
|
+
|
|
1779
|
+
const toolParams = yield* Effect.try({
|
|
1780
|
+
try: () => Tool.unsafeSecureJsonParse(toolArgs),
|
|
1738
1781
|
catch: (cause) =>
|
|
1739
1782
|
AiError.make({
|
|
1740
1783
|
module: "OpenAiLanguageModel",
|
|
@@ -1746,10 +1789,14 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1746
1789
|
})
|
|
1747
1790
|
})
|
|
1748
1791
|
})
|
|
1792
|
+
|
|
1793
|
+
const params = yield* transformToolCallParams(options.tools, toolName, toolParams)
|
|
1794
|
+
|
|
1749
1795
|
parts.push({
|
|
1750
1796
|
type: "tool-params-end",
|
|
1751
1797
|
id: event.item.call_id
|
|
1752
1798
|
})
|
|
1799
|
+
|
|
1753
1800
|
parts.push({
|
|
1754
1801
|
type: "tool-call",
|
|
1755
1802
|
id: event.item.call_id,
|
|
@@ -1757,6 +1804,7 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1757
1804
|
params,
|
|
1758
1805
|
metadata: { openai: { ...makeItemIdMetadata(event.item.id) } }
|
|
1759
1806
|
})
|
|
1807
|
+
|
|
1760
1808
|
break
|
|
1761
1809
|
}
|
|
1762
1810
|
|
|
@@ -1862,7 +1910,7 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1862
1910
|
}
|
|
1863
1911
|
|
|
1864
1912
|
case "reasoning": {
|
|
1865
|
-
const reasoningPart =
|
|
1913
|
+
const reasoningPart = getOrCreateReasoningPart(event.item.id, event.item.encrypted_content)
|
|
1866
1914
|
for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
|
|
1867
1915
|
if (status === "active" || status === "can-conclude") {
|
|
1868
1916
|
parts.push({
|
|
@@ -1871,7 +1919,7 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
1871
1919
|
metadata: {
|
|
1872
1920
|
openai: {
|
|
1873
1921
|
...makeItemIdMetadata(event.item.id),
|
|
1874
|
-
...makeEncryptedContentMetadata(
|
|
1922
|
+
...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
|
|
1875
1923
|
}
|
|
1876
1924
|
}
|
|
1877
1925
|
})
|
|
@@ -2006,6 +2054,48 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
2006
2054
|
break
|
|
2007
2055
|
}
|
|
2008
2056
|
|
|
2057
|
+
case "response.function_call_arguments.done": {
|
|
2058
|
+
const toolCall = activeToolCalls[event.output_index]
|
|
2059
|
+
if (
|
|
2060
|
+
Predicate.isNotUndefined(toolCall?.functionCall) &&
|
|
2061
|
+
!toolCall.functionCall.emitted
|
|
2062
|
+
) {
|
|
2063
|
+
hasToolCalls = true
|
|
2064
|
+
|
|
2065
|
+
const toolParams = yield* Effect.try({
|
|
2066
|
+
try: () => Tool.unsafeSecureJsonParse(event.arguments),
|
|
2067
|
+
catch: (cause) =>
|
|
2068
|
+
AiError.make({
|
|
2069
|
+
module: "OpenAiLanguageModel",
|
|
2070
|
+
method: "makeStreamResponse",
|
|
2071
|
+
reason: new AiError.ToolParameterValidationError({
|
|
2072
|
+
toolName: toolCall.name,
|
|
2073
|
+
toolParams: {},
|
|
2074
|
+
description: `Failed securely JSON parse tool parameters: ${cause}`
|
|
2075
|
+
})
|
|
2076
|
+
})
|
|
2077
|
+
})
|
|
2078
|
+
|
|
2079
|
+
const params = yield* transformToolCallParams(options.tools, toolCall.name, toolParams)
|
|
2080
|
+
|
|
2081
|
+
parts.push({
|
|
2082
|
+
type: "tool-params-end",
|
|
2083
|
+
id: toolCall.id
|
|
2084
|
+
})
|
|
2085
|
+
|
|
2086
|
+
parts.push({
|
|
2087
|
+
type: "tool-call",
|
|
2088
|
+
id: toolCall.id,
|
|
2089
|
+
name: toolCall.name,
|
|
2090
|
+
params,
|
|
2091
|
+
metadata: { openai: { ...makeItemIdMetadata(event.item_id) } }
|
|
2092
|
+
})
|
|
2093
|
+
|
|
2094
|
+
toolCall.functionCall.emitted = true
|
|
2095
|
+
}
|
|
2096
|
+
break
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2009
2099
|
case "response.apply_patch_call_operation_diff.delta": {
|
|
2010
2100
|
const toolCall = activeToolCalls[event.output_index]
|
|
2011
2101
|
if (Predicate.isNotUndefined(toolCall?.applyPatch)) {
|
|
@@ -2095,28 +2185,28 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
2095
2185
|
}
|
|
2096
2186
|
|
|
2097
2187
|
case "response.reasoning_summary_part.added": {
|
|
2098
|
-
|
|
2188
|
+
const reasoningPart = getOrCreateReasoningPart(event.item_id)
|
|
2099
2189
|
if (event.summary_index > 0) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
...makeItemIdMetadata(event.item_id),
|
|
2111
|
-
...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
|
|
2112
|
-
}
|
|
2190
|
+
// Conclude all can-conclude parts before starting new one
|
|
2191
|
+
for (const [summaryIndex, status] of Object.entries(reasoningPart.summaryParts)) {
|
|
2192
|
+
if (status === "can-conclude") {
|
|
2193
|
+
parts.push({
|
|
2194
|
+
type: "reasoning-end",
|
|
2195
|
+
id: `${event.item_id}:${summaryIndex}`,
|
|
2196
|
+
metadata: {
|
|
2197
|
+
openai: {
|
|
2198
|
+
...makeItemIdMetadata(event.item_id),
|
|
2199
|
+
...makeEncryptedContentMetadata(reasoningPart.encryptedContent)
|
|
2113
2200
|
}
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2201
|
+
}
|
|
2202
|
+
})
|
|
2203
|
+
reasoningPart.summaryParts[Number(summaryIndex)] = "concluded"
|
|
2117
2204
|
}
|
|
2118
|
-
reasoningPart.summaryParts[event.summary_index] = "active"
|
|
2119
2205
|
}
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
if (Predicate.isUndefined(reasoningPart.summaryParts[event.summary_index])) {
|
|
2209
|
+
reasoningPart.summaryParts[event.summary_index] = "active"
|
|
2120
2210
|
parts.push({
|
|
2121
2211
|
type: "reasoning-start",
|
|
2122
2212
|
id: `${event.item_id}:${event.summary_index}`,
|
|
@@ -2142,6 +2232,7 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
2142
2232
|
}
|
|
2143
2233
|
|
|
2144
2234
|
case "response.reasoning_summary_part.done": {
|
|
2235
|
+
const reasoningPart = getOrCreateReasoningPart(event.item_id)
|
|
2145
2236
|
// When OpenAI stores message data, we can immediately conclude the
|
|
2146
2237
|
// reasoning part given that we do not need the encrypted content
|
|
2147
2238
|
if (config.store === true) {
|
|
@@ -2151,11 +2242,11 @@ const makeStreamResponse = Effect.fnUntraced(
|
|
|
2151
2242
|
metadata: { openai: { ...makeItemIdMetadata(event.item_id) } }
|
|
2152
2243
|
})
|
|
2153
2244
|
// Mark the summary part concluded
|
|
2154
|
-
|
|
2245
|
+
reasoningPart.summaryParts[event.summary_index] = "concluded"
|
|
2155
2246
|
} else {
|
|
2156
2247
|
// Mark the summary part as can-conclude given we still need a
|
|
2157
2248
|
// final summary part with the encrypted content
|
|
2158
|
-
|
|
2249
|
+
reasoningPart.summaryParts[event.summary_index] = "can-conclude"
|
|
2159
2250
|
}
|
|
2160
2251
|
break
|
|
2161
2252
|
}
|
|
@@ -2281,12 +2372,14 @@ const prepareTools = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Too
|
|
|
2281
2372
|
for (const tool of allowedTools) {
|
|
2282
2373
|
if (Tool.isUserDefined(tool)) {
|
|
2283
2374
|
const strict = Tool.getStrictMode(tool) ?? config.strictJsonSchema ?? true
|
|
2375
|
+
const description = Tool.getDescription(tool)
|
|
2376
|
+
const parameters = yield* tryJsonSchema(tool.parametersSchema, "prepareTools")
|
|
2284
2377
|
tools.push({
|
|
2285
2378
|
type: "function",
|
|
2286
2379
|
name: tool.name,
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2380
|
+
parameters,
|
|
2381
|
+
strict,
|
|
2382
|
+
...(Predicate.isNotUndefined(description) ? { description } : undefined)
|
|
2290
2383
|
})
|
|
2291
2384
|
}
|
|
2292
2385
|
|
|
@@ -2480,23 +2573,45 @@ const makeItemIdMetadata = (itemId: string | undefined) => Predicate.isNotUndefi
|
|
|
2480
2573
|
const makeEncryptedContentMetadata = (encryptedContent: string | null | undefined) =>
|
|
2481
2574
|
Predicate.isNotNullish(encryptedContent) ? { encryptedContent } : undefined
|
|
2482
2575
|
|
|
2483
|
-
const
|
|
2576
|
+
const unsupportedSchemaError = (error: unknown, method: string): AiError.AiError =>
|
|
2577
|
+
AiError.make({
|
|
2578
|
+
module: "OpenAiLanguageModel",
|
|
2579
|
+
method,
|
|
2580
|
+
reason: new AiError.UnsupportedSchemaError({
|
|
2581
|
+
description: error instanceof Error ? error.message : String(error)
|
|
2582
|
+
})
|
|
2583
|
+
})
|
|
2584
|
+
|
|
2585
|
+
const tryCodecTransform = <S extends Schema.Top>(schema: S, method: string) =>
|
|
2586
|
+
Effect.try({
|
|
2587
|
+
try: () => toCodecOpenAI(schema),
|
|
2588
|
+
catch: (error) => unsupportedSchemaError(error, method)
|
|
2589
|
+
})
|
|
2590
|
+
|
|
2591
|
+
const tryJsonSchema = <S extends Schema.Top>(schema: S, method: string) =>
|
|
2592
|
+
Effect.try({
|
|
2593
|
+
try: () => Tool.getJsonSchemaFromSchema(schema, { transformer: toCodecOpenAI }),
|
|
2594
|
+
catch: (error) => unsupportedSchemaError(error, method)
|
|
2595
|
+
})
|
|
2596
|
+
|
|
2597
|
+
const prepareResponseFormat = Effect.fnUntraced(function*({ config, options }: {
|
|
2484
2598
|
readonly config: typeof Config.Service
|
|
2485
2599
|
readonly options: LanguageModel.ProviderOptions
|
|
2486
|
-
}): typeof Generated.TextResponseFormatConfiguration.Encoded
|
|
2600
|
+
}): Effect.fn.Return<typeof Generated.TextResponseFormatConfiguration.Encoded, AiError.AiError> {
|
|
2487
2601
|
if (options.responseFormat.type === "json") {
|
|
2488
2602
|
const name = options.responseFormat.objectName
|
|
2489
2603
|
const schema = options.responseFormat.schema
|
|
2604
|
+
const jsonSchema = yield* tryJsonSchema(schema, "prepareResponseFormat")
|
|
2490
2605
|
return {
|
|
2491
2606
|
type: "json_schema",
|
|
2492
2607
|
name,
|
|
2493
2608
|
description: AST.resolveDescription(schema.ast) ?? "Response with a JSON object",
|
|
2494
|
-
schema:
|
|
2609
|
+
schema: jsonSchema,
|
|
2495
2610
|
strict: config.strictJsonSchema ?? true
|
|
2496
2611
|
}
|
|
2497
2612
|
}
|
|
2498
2613
|
return { type: "text" }
|
|
2499
|
-
}
|
|
2614
|
+
})
|
|
2500
2615
|
|
|
2501
2616
|
interface ModelCapabilities {
|
|
2502
2617
|
readonly isReasoningModel: boolean
|
|
@@ -2606,3 +2721,40 @@ const getUsage = (usage: Generated.ResponseUsage | null | undefined): Response.U
|
|
|
2606
2721
|
}
|
|
2607
2722
|
}
|
|
2608
2723
|
}
|
|
2724
|
+
|
|
2725
|
+
const transformToolCallParams = Effect.fnUntraced(function*<Tools extends ReadonlyArray<Tool.Any>>(
|
|
2726
|
+
tools: Tools,
|
|
2727
|
+
toolName: string,
|
|
2728
|
+
toolParams: unknown
|
|
2729
|
+
): Effect.fn.Return<unknown, AiError.AiError> {
|
|
2730
|
+
const tool = tools.find((tool) => tool.name === toolName)
|
|
2731
|
+
|
|
2732
|
+
if (Predicate.isUndefined(tool)) {
|
|
2733
|
+
return yield* AiError.make({
|
|
2734
|
+
module: "OpenAiLanguageModel",
|
|
2735
|
+
method: "makeResponse",
|
|
2736
|
+
reason: new AiError.ToolNotFoundError({
|
|
2737
|
+
toolName,
|
|
2738
|
+
availableTools: tools.map((tool) => tool.name)
|
|
2739
|
+
})
|
|
2740
|
+
})
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
const { codec } = yield* tryCodecTransform(tool.parametersSchema, "makeResponse")
|
|
2744
|
+
|
|
2745
|
+
const transform = Schema.decodeEffect(codec)
|
|
2746
|
+
|
|
2747
|
+
return yield* (
|
|
2748
|
+
transform(toolParams) as Effect.Effect<unknown, Schema.SchemaError>
|
|
2749
|
+
).pipe(Effect.mapError((error) =>
|
|
2750
|
+
AiError.make({
|
|
2751
|
+
module: "OpenAiLanguageModel",
|
|
2752
|
+
method: "makeResponse",
|
|
2753
|
+
reason: new AiError.ToolParameterValidationError({
|
|
2754
|
+
toolName,
|
|
2755
|
+
toolParams,
|
|
2756
|
+
description: error.issue.toString()
|
|
2757
|
+
})
|
|
2758
|
+
})
|
|
2759
|
+
))
|
|
2760
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,15 @@ export * as OpenAiClient from "./OpenAiClient.ts"
|
|
|
24
24
|
*/
|
|
25
25
|
export * as OpenAiConfig from "./OpenAiConfig.ts"
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* OpenAI Embedding Model implementation.
|
|
29
|
+
*
|
|
30
|
+
* Provides an EmbeddingModel implementation for OpenAI's embeddings API.
|
|
31
|
+
*
|
|
32
|
+
* @since 1.0.0
|
|
33
|
+
*/
|
|
34
|
+
export * as OpenAiEmbeddingModel from "./OpenAiEmbeddingModel.ts"
|
|
35
|
+
|
|
27
36
|
/**
|
|
28
37
|
* OpenAI error metadata augmentation.
|
|
29
38
|
*
|
package/src/internal/errors.ts
CHANGED
|
@@ -161,12 +161,14 @@ export const parseRateLimitHeaders = (headers: Record<string, string>) => {
|
|
|
161
161
|
let retryAfter: Duration.Duration | undefined
|
|
162
162
|
if (Predicate.isNotUndefined(retryAfterRaw)) {
|
|
163
163
|
const parsed = Number.parse(retryAfterRaw)
|
|
164
|
-
if (
|
|
165
|
-
retryAfter = Duration.seconds(parsed)
|
|
164
|
+
if (Option.isSome(parsed)) {
|
|
165
|
+
retryAfter = Duration.seconds(parsed.value)
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
const remainingRaw = headers["x-ratelimit-remaining-requests"]
|
|
169
|
-
const remaining = Predicate.isNotUndefined(remainingRaw)
|
|
169
|
+
const remaining = Predicate.isNotUndefined(remainingRaw)
|
|
170
|
+
? Option.getOrNull(Number.parse(remainingRaw))
|
|
171
|
+
: null
|
|
170
172
|
return {
|
|
171
173
|
retryAfter,
|
|
172
174
|
limit: headers["x-ratelimit-limit-requests"] ?? null,
|
|
@@ -187,7 +189,7 @@ export const buildHttpRequestDetails = (
|
|
|
187
189
|
method: request.method,
|
|
188
190
|
url: request.url,
|
|
189
191
|
urlParams: Array.from(request.urlParams),
|
|
190
|
-
hash: request.hash,
|
|
192
|
+
hash: Option.getOrUndefined(request.hash),
|
|
191
193
|
headers: Redactable.redact(request.headers) as Record<string, string>
|
|
192
194
|
})
|
|
193
195
|
|