@tanstack/openai-base 0.3.2 → 0.3.4
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/esm/adapters/chat-completions-text.js +34 -18
- package/dist/esm/adapters/chat-completions-text.js.map +1 -1
- package/dist/esm/adapters/responses-text.d.ts +1 -1
- package/dist/esm/adapters/responses-text.js +41 -24
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/dist/esm/tools/custom-tool.js +4 -2
- package/dist/esm/tools/custom-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.js +7 -3
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/index.d.ts +1 -1
- package/dist/esm/utils/request-options.js +4 -1
- package/dist/esm/utils/request-options.js.map +1 -1
- package/dist/esm/utils/schema-converter.js +1 -1
- package/dist/esm/utils/schema-converter.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/chat-completions-text.ts +44 -23
- package/src/adapters/responses-text.ts +61 -28
- package/src/tools/custom-tool.ts +8 -2
- package/src/tools/file-search-tool.ts +10 -3
- package/src/tools/index.ts +1 -1
- package/src/utils/request-options.ts +9 -1
- package/src/utils/schema-converter.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/openai-base",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Shared base adapters for OpenAI-SDK-backed providers in TanStack AI (Chat Completions and Responses)",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"@tanstack/ai-utils": "0.2.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@tanstack/ai": "^0.
|
|
36
|
+
"@tanstack/ai": "^0.20.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@vitest/coverage-v8": "4.0.14",
|
|
40
40
|
"vite": "^7.3.3",
|
|
41
41
|
"zod": "^4.2.0",
|
|
42
|
-
"@tanstack/ai": "0.
|
|
42
|
+
"@tanstack/ai": "0.20.1"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "vite build",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventType } from '@tanstack/ai'
|
|
1
|
+
import { EventType, normalizeSystemPrompts } from '@tanstack/ai'
|
|
2
2
|
import { BaseTextAdapter } from '@tanstack/ai/adapters'
|
|
3
3
|
import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
|
|
4
4
|
import { generateId, transformNullsToUndefined } from '@tanstack/ai-utils'
|
|
@@ -46,7 +46,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
46
46
|
TMessageMetadata,
|
|
47
47
|
TToolCapabilities
|
|
48
48
|
> {
|
|
49
|
-
readonly kind = 'text' as const
|
|
49
|
+
override readonly kind = 'text' as const
|
|
50
50
|
readonly name: string
|
|
51
51
|
protected client: OpenAI
|
|
52
52
|
|
|
@@ -110,14 +110,20 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// Emit AG-UI RUN_ERROR
|
|
113
|
+
// Emit AG-UI RUN_ERROR. Conditional `code` spread keeps the wire
|
|
114
|
+
// shape spec-compliant under `exactOptionalPropertyTypes`: AG-UI's
|
|
115
|
+
// `RunErrorEvent.code` is `string?` (absent vs explicit `undefined`
|
|
116
|
+
// matter), so we omit the key when there's no code.
|
|
114
117
|
yield {
|
|
115
118
|
type: EventType.RUN_ERROR,
|
|
116
119
|
model: options.model,
|
|
117
120
|
timestamp: Date.now(),
|
|
118
121
|
message: errorPayload.message,
|
|
119
122
|
code: errorPayload.code,
|
|
120
|
-
error:
|
|
123
|
+
error: {
|
|
124
|
+
message: errorPayload.message,
|
|
125
|
+
code: errorPayload.code,
|
|
126
|
+
},
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
options.logger.errors(`${this.name}.chatStream fatal`, {
|
|
@@ -518,14 +524,21 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
518
524
|
`${this.name}.structuredOutputStream failed`,
|
|
519
525
|
)
|
|
520
526
|
|
|
527
|
+
// Conditional `code` spread keeps the wire shape spec-compliant under
|
|
528
|
+
// `exactOptionalPropertyTypes`: AG-UI's `RunErrorEvent.code` is `string?`
|
|
529
|
+
// (absent vs explicit `undefined` matter).
|
|
530
|
+
const resolvedCode = isAbort ? 'aborted' : errorPayload.code
|
|
521
531
|
yield {
|
|
522
532
|
type: EventType.RUN_ERROR,
|
|
523
533
|
runId: aguiState.runId,
|
|
524
534
|
model: lastModel || chatOptions.model,
|
|
525
535
|
timestamp,
|
|
526
536
|
message: errorPayload.message,
|
|
527
|
-
|
|
528
|
-
error: {
|
|
537
|
+
...(resolvedCode !== undefined && { code: resolvedCode }),
|
|
538
|
+
error: {
|
|
539
|
+
message: errorPayload.message,
|
|
540
|
+
...(resolvedCode !== undefined && { code: resolvedCode }),
|
|
541
|
+
},
|
|
529
542
|
}
|
|
530
543
|
|
|
531
544
|
chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {
|
|
@@ -788,17 +801,17 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
788
801
|
const index = toolCallDelta.index
|
|
789
802
|
|
|
790
803
|
// Initialize or update the tool call in progress
|
|
791
|
-
|
|
792
|
-
|
|
804
|
+
let toolCall = toolCallsInProgress.get(index)
|
|
805
|
+
if (!toolCall) {
|
|
806
|
+
toolCall = {
|
|
793
807
|
id: toolCallDelta.id || '',
|
|
794
808
|
name: toolCallDelta.function?.name || '',
|
|
795
809
|
arguments: '',
|
|
796
810
|
started: false,
|
|
797
|
-
}
|
|
811
|
+
}
|
|
812
|
+
toolCallsInProgress.set(index, toolCall)
|
|
798
813
|
}
|
|
799
814
|
|
|
800
|
-
const toolCall = toolCallsInProgress.get(index)!
|
|
801
|
-
|
|
802
815
|
// Update with any new data from the delta
|
|
803
816
|
if (toolCallDelta.id) {
|
|
804
817
|
toolCall.id = toolCallDelta.id
|
|
@@ -1030,19 +1043,22 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
1030
1043
|
? 'tool_calls'
|
|
1031
1044
|
: (pendingFinishReason ?? 'stop')
|
|
1032
1045
|
|
|
1046
|
+
// Conditional `usage` spread: AG-UI's `RunFinishedEvent.usage` is
|
|
1047
|
+
// optional with no `| undefined`; omit the key entirely when no usage
|
|
1048
|
+
// arrived rather than emitting `usage: undefined`.
|
|
1033
1049
|
yield {
|
|
1034
1050
|
type: EventType.RUN_FINISHED,
|
|
1035
1051
|
runId: aguiState.runId,
|
|
1036
1052
|
threadId: aguiState.threadId,
|
|
1037
1053
|
model: lastModel || options.model,
|
|
1038
1054
|
timestamp: Date.now(),
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1055
|
+
...(lastUsage && {
|
|
1056
|
+
usage: {
|
|
1057
|
+
promptTokens: lastUsage.prompt_tokens || 0,
|
|
1058
|
+
completionTokens: lastUsage.completion_tokens || 0,
|
|
1059
|
+
totalTokens: lastUsage.total_tokens || 0,
|
|
1060
|
+
},
|
|
1061
|
+
}),
|
|
1046
1062
|
finishReason,
|
|
1047
1063
|
}
|
|
1048
1064
|
}
|
|
@@ -1058,14 +1074,18 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
1058
1074
|
source: `${this.name}.processStreamChunks`,
|
|
1059
1075
|
})
|
|
1060
1076
|
|
|
1061
|
-
// Emit AG-UI RUN_ERROR
|
|
1077
|
+
// Emit AG-UI RUN_ERROR with conditional `code` spread (see chatStream's
|
|
1078
|
+
// catch block for the rationale).
|
|
1062
1079
|
yield {
|
|
1063
1080
|
type: EventType.RUN_ERROR,
|
|
1064
1081
|
model: options.model,
|
|
1065
1082
|
timestamp: Date.now(),
|
|
1066
1083
|
message: errorPayload.message,
|
|
1067
|
-
code: errorPayload.code,
|
|
1068
|
-
error:
|
|
1084
|
+
...(errorPayload.code !== undefined && { code: errorPayload.code }),
|
|
1085
|
+
error: {
|
|
1086
|
+
message: errorPayload.message,
|
|
1087
|
+
...(errorPayload.code !== undefined && { code: errorPayload.code }),
|
|
1088
|
+
},
|
|
1069
1089
|
}
|
|
1070
1090
|
}
|
|
1071
1091
|
}
|
|
@@ -1088,10 +1108,11 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
1088
1108
|
const messages: Array<ChatCompletionMessageParam> = []
|
|
1089
1109
|
|
|
1090
1110
|
// Add system prompts first
|
|
1091
|
-
|
|
1111
|
+
const systemPrompts = normalizeSystemPrompts(options.systemPrompts)
|
|
1112
|
+
if (systemPrompts.length > 0) {
|
|
1092
1113
|
messages.push({
|
|
1093
1114
|
role: 'system',
|
|
1094
|
-
content:
|
|
1115
|
+
content: systemPrompts.map((p) => p.content).join('\n'),
|
|
1095
1116
|
})
|
|
1096
1117
|
}
|
|
1097
1118
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventType } from '@tanstack/ai'
|
|
1
|
+
import { EventType, normalizeSystemPrompts } from '@tanstack/ai'
|
|
2
2
|
import { BaseTextAdapter } from '@tanstack/ai/adapters'
|
|
3
3
|
import { toRunErrorPayload } from '@tanstack/ai/adapter-internals'
|
|
4
4
|
import { generateId, transformNullsToUndefined } from '@tanstack/ai-utils'
|
|
@@ -46,7 +46,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
46
46
|
TMessageMetadata,
|
|
47
47
|
TToolCapabilities
|
|
48
48
|
> {
|
|
49
|
-
readonly kind = 'text' as const
|
|
49
|
+
override readonly kind = 'text' as const
|
|
50
50
|
readonly name: string
|
|
51
51
|
protected client: OpenAI
|
|
52
52
|
|
|
@@ -74,7 +74,8 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
74
74
|
ended?: boolean
|
|
75
75
|
// Set when args.done arrives before TOOL_CALL_START could fire
|
|
76
76
|
// (output_item.added lacked a name). output_item.done picks these
|
|
77
|
-
// up to emit the missing END.
|
|
77
|
+
// up to emit the missing END. Allow explicit `undefined` so the
|
|
78
|
+
// emission paths can re-clear the slot after handing it off.
|
|
78
79
|
pendingArguments?: string
|
|
79
80
|
}
|
|
80
81
|
>()
|
|
@@ -132,14 +133,20 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
// Emit AG-UI RUN_ERROR
|
|
136
|
+
// Emit AG-UI RUN_ERROR. Conditional `code` spread keeps the wire
|
|
137
|
+
// shape spec-compliant under `exactOptionalPropertyTypes`: AG-UI's
|
|
138
|
+
// `RunErrorEvent.code` is `string?` (absent vs explicit `undefined`
|
|
139
|
+
// matter), so we omit the key when there's no code.
|
|
136
140
|
yield {
|
|
137
141
|
type: EventType.RUN_ERROR,
|
|
138
142
|
model: options.model,
|
|
139
143
|
timestamp: Date.now(),
|
|
140
144
|
message: errorPayload.message,
|
|
141
145
|
code: errorPayload.code,
|
|
142
|
-
error:
|
|
146
|
+
error: {
|
|
147
|
+
message: errorPayload.message,
|
|
148
|
+
code: errorPayload.code,
|
|
149
|
+
},
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
options.logger.errors(`${this.name}.chatStream fatal`, {
|
|
@@ -439,11 +446,11 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
439
446
|
yield* openReasoning()
|
|
440
447
|
// openReasoning() guarantees reasoningMessageId is set on first call;
|
|
441
448
|
// TS can't see through the generator side-effect.
|
|
442
|
-
|
|
449
|
+
if (!reasoningMessageId) continue
|
|
443
450
|
accumulatedReasoning += reasoningDelta
|
|
444
451
|
yield {
|
|
445
452
|
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
446
|
-
messageId,
|
|
453
|
+
messageId: reasoningMessageId,
|
|
447
454
|
delta: reasoningDelta,
|
|
448
455
|
model,
|
|
449
456
|
timestamp,
|
|
@@ -499,14 +506,17 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
499
506
|
).response
|
|
500
507
|
const message =
|
|
501
508
|
response?.error?.message || 'Responses API stream failed'
|
|
509
|
+
const code = response?.error?.code
|
|
510
|
+
// Conditional `code` spread keeps the wire shape spec-compliant
|
|
511
|
+
// under `exactOptionalPropertyTypes` (see chatStream catch).
|
|
502
512
|
yield {
|
|
503
513
|
type: EventType.RUN_ERROR,
|
|
504
514
|
runId: aguiState.runId,
|
|
505
515
|
model,
|
|
506
516
|
timestamp,
|
|
507
517
|
message,
|
|
508
|
-
code
|
|
509
|
-
error: { message, code
|
|
518
|
+
...(code !== undefined && { code }),
|
|
519
|
+
error: { message, ...(code !== undefined && { code }) },
|
|
510
520
|
}
|
|
511
521
|
return
|
|
512
522
|
}
|
|
@@ -606,14 +616,20 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
606
616
|
`${this.name}.structuredOutputStream failed`,
|
|
607
617
|
)
|
|
608
618
|
|
|
619
|
+
// Conditional `code` spread keeps the wire shape spec-compliant under
|
|
620
|
+
// `exactOptionalPropertyTypes` (see chatStream catch).
|
|
621
|
+
const resolvedCode = isAbort ? 'aborted' : errorPayload.code
|
|
609
622
|
yield {
|
|
610
623
|
type: EventType.RUN_ERROR,
|
|
611
624
|
runId: aguiState.runId,
|
|
612
625
|
model,
|
|
613
626
|
timestamp,
|
|
614
627
|
message: errorPayload.message,
|
|
615
|
-
|
|
616
|
-
error: {
|
|
628
|
+
...(resolvedCode !== undefined && { code: resolvedCode }),
|
|
629
|
+
error: {
|
|
630
|
+
message: errorPayload.message,
|
|
631
|
+
...(resolvedCode !== undefined && { code: resolvedCode }),
|
|
632
|
+
},
|
|
617
633
|
}
|
|
618
634
|
|
|
619
635
|
chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {
|
|
@@ -742,7 +758,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
742
758
|
name: string
|
|
743
759
|
started: boolean
|
|
744
760
|
ended?: boolean
|
|
745
|
-
pendingArguments?: string
|
|
761
|
+
pendingArguments?: string | undefined
|
|
746
762
|
}
|
|
747
763
|
>,
|
|
748
764
|
options: TextOptions<TProviderOptions>,
|
|
@@ -1151,25 +1167,25 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1151
1167
|
if (chunk.type === 'response.output_item.added') {
|
|
1152
1168
|
const item = chunk.item
|
|
1153
1169
|
if (item.type === 'function_call' && item.id) {
|
|
1154
|
-
const existing = toolCallMetadata.get(item.id)
|
|
1155
1170
|
// Track the item as soon as we see it so subsequent arg deltas
|
|
1156
1171
|
// aren't logged as orphans, but only emit TOOL_CALL_START when
|
|
1157
1172
|
// both id AND name are populated. Emitting START with an empty
|
|
1158
1173
|
// name would propagate into TOOL_CALL_END (which reads the same
|
|
1159
1174
|
// metadata) and route the tool call to whatever name happens to
|
|
1160
1175
|
// match `''` downstream — a silent misroute.
|
|
1161
|
-
|
|
1162
|
-
|
|
1176
|
+
let metadata = toolCallMetadata.get(item.id)
|
|
1177
|
+
if (!metadata) {
|
|
1178
|
+
metadata = {
|
|
1163
1179
|
index: chunk.output_index,
|
|
1164
1180
|
name: item.name || '',
|
|
1165
1181
|
started: false,
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1182
|
+
}
|
|
1183
|
+
toolCallMetadata.set(item.id, metadata)
|
|
1184
|
+
} else if (!metadata.name && item.name) {
|
|
1168
1185
|
// A later output_item.added for the same id finally carries
|
|
1169
1186
|
// the name. Update so the gated emission below can fire.
|
|
1170
|
-
|
|
1187
|
+
metadata.name = item.name
|
|
1171
1188
|
}
|
|
1172
|
-
const metadata = toolCallMetadata.get(item.id)!
|
|
1173
1189
|
if (!metadata.started && metadata.name) {
|
|
1174
1190
|
yield {
|
|
1175
1191
|
type: EventType.TOOL_CALL_START,
|
|
@@ -1496,15 +1512,18 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1496
1512
|
}
|
|
1497
1513
|
|
|
1498
1514
|
if (chunk.type === 'error') {
|
|
1515
|
+
// Conditional `code` spread keeps the wire shape spec-compliant
|
|
1516
|
+
// under `exactOptionalPropertyTypes` (see chatStream catch).
|
|
1517
|
+
const code = chunk.code ?? undefined
|
|
1499
1518
|
yield {
|
|
1500
1519
|
type: EventType.RUN_ERROR,
|
|
1501
1520
|
model: model || options.model,
|
|
1502
1521
|
timestamp: Date.now(),
|
|
1503
1522
|
message: chunk.message,
|
|
1504
|
-
code
|
|
1523
|
+
...(code !== undefined && { code }),
|
|
1505
1524
|
error: {
|
|
1506
1525
|
message: chunk.message,
|
|
1507
|
-
code
|
|
1526
|
+
...(code !== undefined && { code }),
|
|
1508
1527
|
},
|
|
1509
1528
|
}
|
|
1510
1529
|
// RUN_ERROR is terminal — don't let the synthetic RUN_FINISHED
|
|
@@ -1531,13 +1550,16 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1531
1550
|
timestamp: Date.now(),
|
|
1532
1551
|
}
|
|
1533
1552
|
}
|
|
1553
|
+
// Omit `usage` entirely (vs `usage: undefined`) — the synthetic
|
|
1554
|
+
// RUN_FINISHED for truncated streams has no usage data, and AG-UI's
|
|
1555
|
+
// `RunFinishedEvent.usage` is optional without `| undefined` under
|
|
1556
|
+
// `exactOptionalPropertyTypes`.
|
|
1534
1557
|
yield {
|
|
1535
1558
|
type: EventType.RUN_FINISHED,
|
|
1536
1559
|
runId: aguiState.runId,
|
|
1537
1560
|
threadId: aguiState.threadId,
|
|
1538
1561
|
model: model || options.model,
|
|
1539
1562
|
timestamp: Date.now(),
|
|
1540
|
-
usage: undefined,
|
|
1541
1563
|
finishReason: toolCallMetadata.size > 0 ? 'tool_calls' : 'stop',
|
|
1542
1564
|
}
|
|
1543
1565
|
}
|
|
@@ -1552,13 +1574,18 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1552
1574
|
error: errorPayload,
|
|
1553
1575
|
source: `${this.name}.processStreamChunks`,
|
|
1554
1576
|
})
|
|
1577
|
+
// Emit AG-UI RUN_ERROR with conditional `code` spread (see chatStream
|
|
1578
|
+
// catch for the rationale).
|
|
1555
1579
|
yield {
|
|
1556
1580
|
type: EventType.RUN_ERROR,
|
|
1557
1581
|
model: options.model,
|
|
1558
1582
|
timestamp: Date.now(),
|
|
1559
1583
|
message: errorPayload.message,
|
|
1560
|
-
code: errorPayload.code,
|
|
1561
|
-
error:
|
|
1584
|
+
...(errorPayload.code !== undefined && { code: errorPayload.code }),
|
|
1585
|
+
error: {
|
|
1586
|
+
message: errorPayload.message,
|
|
1587
|
+
...(errorPayload.code !== undefined && { code: errorPayload.code }),
|
|
1588
|
+
},
|
|
1562
1589
|
}
|
|
1563
1590
|
}
|
|
1564
1591
|
}
|
|
@@ -1599,10 +1626,11 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1599
1626
|
}),
|
|
1600
1627
|
...(options.topP !== undefined && { top_p: options.topP }),
|
|
1601
1628
|
...(options.metadata !== undefined && { metadata: options.metadata }),
|
|
1602
|
-
...(
|
|
1603
|
-
options.systemPrompts
|
|
1604
|
-
|
|
1605
|
-
}
|
|
1629
|
+
...(() => {
|
|
1630
|
+
const prompts = normalizeSystemPrompts(options.systemPrompts)
|
|
1631
|
+
if (prompts.length === 0) return {}
|
|
1632
|
+
return { instructions: prompts.map((p) => p.content).join('\n') }
|
|
1633
|
+
})(),
|
|
1606
1634
|
input,
|
|
1607
1635
|
// Conditional spread: `tools: undefined` would clobber any
|
|
1608
1636
|
// modelOptions.tools the caller set above.
|
|
@@ -1764,7 +1792,12 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1764
1792
|
}
|
|
1765
1793
|
}
|
|
1766
1794
|
|
|
1795
|
+
case 'video':
|
|
1796
|
+
case 'document':
|
|
1767
1797
|
default:
|
|
1798
|
+
// OpenAI Responses API doesn't accept native video/document parts on
|
|
1799
|
+
// this path — surface as explicit unsupported error so callers see
|
|
1800
|
+
// the same message regardless of which content type leaked through.
|
|
1768
1801
|
throw new Error(`Unsupported content part type: ${part.type}`)
|
|
1769
1802
|
}
|
|
1770
1803
|
}
|
package/src/tools/custom-tool.ts
CHANGED
|
@@ -11,11 +11,17 @@ export type CustomTool = CustomToolConfig
|
|
|
11
11
|
*/
|
|
12
12
|
export function convertCustomToolToAdapterFormat(tool: Tool): CustomToolConfig {
|
|
13
13
|
const metadata = tool.metadata as CustomToolConfig
|
|
14
|
+
// Conditional spread: the SDK's `CustomToolConfig` declares optional
|
|
15
|
+
// fields as `description?: string` (no `| undefined`) under
|
|
16
|
+
// exactOptionalPropertyTypes, so we omit absent fields rather than
|
|
17
|
+
// passing them through as explicit `undefined`.
|
|
14
18
|
return {
|
|
15
19
|
type: 'custom',
|
|
16
20
|
name: metadata.name,
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
...(metadata.description !== undefined && {
|
|
22
|
+
description: metadata.description,
|
|
23
|
+
}),
|
|
24
|
+
...(metadata.format !== undefined && { format: metadata.format }),
|
|
19
25
|
}
|
|
20
26
|
}
|
|
21
27
|
|
|
@@ -22,12 +22,19 @@ export function convertFileSearchToolToAdapterFormat(
|
|
|
22
22
|
tool: Tool,
|
|
23
23
|
): FileSearchToolConfig {
|
|
24
24
|
const metadata = tool.metadata as FileSearchToolConfig
|
|
25
|
+
// Conditional spread: SDK's `FileSearchToolConfig` declares the
|
|
26
|
+
// optional fields without `| undefined`, so we omit absent values
|
|
27
|
+
// rather than passing them through as explicit `undefined`.
|
|
25
28
|
return {
|
|
26
29
|
type: 'file_search',
|
|
27
30
|
vector_store_ids: metadata.vector_store_ids,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
...(metadata.max_num_results !== undefined && {
|
|
32
|
+
max_num_results: metadata.max_num_results,
|
|
33
|
+
}),
|
|
34
|
+
...(metadata.ranking_options !== undefined && {
|
|
35
|
+
ranking_options: metadata.ranking_options,
|
|
36
|
+
}),
|
|
37
|
+
...(metadata.filters !== undefined && { filters: metadata.filters }),
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
|
package/src/tools/index.ts
CHANGED
|
@@ -35,7 +35,7 @@ export * from './image-generation-tool'
|
|
|
35
35
|
export * from './local-shell-tool'
|
|
36
36
|
export * from './mcp-tool'
|
|
37
37
|
export * from './shell-tool'
|
|
38
|
-
export * from './tool-choice'
|
|
38
|
+
export type * from './tool-choice'
|
|
39
39
|
export * from './tool-converter'
|
|
40
40
|
export * from './web-search-preview-tool'
|
|
41
41
|
export * from './web-search-tool'
|
|
@@ -12,5 +12,13 @@ export function extractRequestOptions(
|
|
|
12
12
|
request: Request | RequestInit | undefined,
|
|
13
13
|
): { headers?: HeadersInit; signal?: AbortSignal | null } {
|
|
14
14
|
if (!request) return {}
|
|
15
|
-
|
|
15
|
+
// Conditional spread: under exactOptionalPropertyTypes the target's
|
|
16
|
+
// `headers?: HeadersInit` and `signal?: AbortSignal | null` forbid an
|
|
17
|
+
// explicit `undefined`. Omit the keys entirely when the source values
|
|
18
|
+
// are absent so the OpenAI SDK sees `headers: undefined` as "not set"
|
|
19
|
+
// rather than a present-but-undefined value.
|
|
20
|
+
return {
|
|
21
|
+
...(request.headers !== undefined && { headers: request.headers }),
|
|
22
|
+
...(request.signal != null && { signal: request.signal }),
|
|
23
|
+
}
|
|
16
24
|
}
|
|
@@ -15,7 +15,8 @@ export function makeStructuredOutputCompatible(
|
|
|
15
15
|
): Record<string, any> {
|
|
16
16
|
const result = { ...schema }
|
|
17
17
|
const required =
|
|
18
|
-
originalRequired ??
|
|
18
|
+
originalRequired ??
|
|
19
|
+
(Array.isArray(result['required']) ? result['required'] : [])
|
|
19
20
|
|
|
20
21
|
if (result.type === 'object' && result.properties) {
|
|
21
22
|
const properties = { ...result.properties }
|