@tanstack/openai-base 0.9.14 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/adapters/chat-completions-text.js +2 -1
- 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 +26 -3
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/tools/apply-patch-tool.js +3 -2
- package/dist/esm/tools/apply-patch-tool.js.map +1 -1
- package/dist/esm/tools/code-interpreter-tool.js +4 -3
- package/dist/esm/tools/code-interpreter-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.js +4 -3
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/custom-tool.js +4 -3
- package/dist/esm/tools/custom-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.js +4 -3
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/image-generation-tool.d.ts +2 -4
- package/dist/esm/tools/image-generation-tool.js +6 -7
- package/dist/esm/tools/image-generation-tool.js.map +1 -1
- package/dist/esm/tools/index.js +14 -0
- package/dist/esm/tools/local-shell-tool.js +3 -2
- package/dist/esm/tools/local-shell-tool.js.map +1 -1
- package/dist/esm/tools/mcp-tool.js +7 -6
- package/dist/esm/tools/mcp-tool.js.map +1 -1
- package/dist/esm/tools/openai-provider-tool.d.ts +20 -0
- package/dist/esm/tools/openai-provider-tool.js +49 -0
- package/dist/esm/tools/openai-provider-tool.js.map +1 -0
- package/dist/esm/tools/shell-tool.js +4 -3
- package/dist/esm/tools/shell-tool.js.map +1 -1
- package/dist/esm/tools/tool-converter.js +7 -18
- package/dist/esm/tools/tool-converter.js.map +1 -1
- package/dist/esm/tools/web-search-preview-tool.d.ts +1 -2
- package/dist/esm/tools/web-search-preview-tool.js +5 -5
- package/dist/esm/tools/web-search-preview-tool.js.map +1 -1
- package/dist/esm/tools/web-search-tool.d.ts +2 -4
- package/dist/esm/tools/web-search-tool.js +6 -7
- package/dist/esm/tools/web-search-tool.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/chat-completions-text.ts +11 -0
- package/src/adapters/responses-text.ts +86 -7
- package/src/tools/apply-patch-tool.ts +9 -5
- package/src/tools/code-interpreter-tool.ts +17 -8
- package/src/tools/computer-use-tool.ts +14 -7
- package/src/tools/custom-tool.ts +14 -7
- package/src/tools/file-search-tool.ts +14 -7
- package/src/tools/image-generation-tool.ts +19 -11
- package/src/tools/local-shell-tool.ts +9 -5
- package/src/tools/mcp-tool.ts +19 -9
- package/src/tools/openai-provider-tool.ts +72 -0
- package/src/tools/shell-tool.ts +17 -9
- package/src/tools/tool-converter.ts +28 -43
- package/src/tools/web-search-preview-tool.ts +17 -8
- package/src/tools/web-search-tool.ts +18 -10
|
@@ -31,6 +31,10 @@ import type {
|
|
|
31
31
|
TextOptions,
|
|
32
32
|
} from '@tanstack/ai'
|
|
33
33
|
|
|
34
|
+
// Base64 encoding of the '%PDF' file header — every PDF payload starts with
|
|
35
|
+
// these bytes, so inline document data must begin with this prefix.
|
|
36
|
+
const PDF_BASE64_MAGIC = 'JVBERi'
|
|
37
|
+
|
|
34
38
|
/**
|
|
35
39
|
* Provider-specific metadata that preserves the Responses API output item ID.
|
|
36
40
|
*
|
|
@@ -1795,7 +1799,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1795
1799
|
throw new Error(
|
|
1796
1800
|
`User message for ${this.name} has no content parts. ` +
|
|
1797
1801
|
`Empty user messages would produce a paid request with no input; ` +
|
|
1798
|
-
`provide at least one text/image/audio part or omit the message.`,
|
|
1802
|
+
`provide at least one text/image/audio/document part or omit the message.`,
|
|
1799
1803
|
)
|
|
1800
1804
|
}
|
|
1801
1805
|
|
|
@@ -1811,7 +1815,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1811
1815
|
|
|
1812
1816
|
/**
|
|
1813
1817
|
* Converts a ContentPart to Responses API input content item.
|
|
1814
|
-
* Handles text, image, and
|
|
1818
|
+
* Handles text, image, audio, and document (PDF) content parts.
|
|
1815
1819
|
* Override this in subclasses for additional content types or provider-specific metadata.
|
|
1816
1820
|
*/
|
|
1817
1821
|
protected convertContentPartToInput(part: ContentPart): ResponseInputContent {
|
|
@@ -1855,7 +1859,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1855
1859
|
}
|
|
1856
1860
|
}
|
|
1857
1861
|
// Wrap raw base64 in a data URL — `input_file` rejects bare base64
|
|
1858
|
-
// payloads (matches the image branch above
|
|
1862
|
+
// payloads (matches the image branch above).
|
|
1859
1863
|
// Default the MIME if missing so we never interpolate `undefined`.
|
|
1860
1864
|
const audioValue = part.source.value
|
|
1861
1865
|
const audioMime = part.source.mimeType || 'application/octet-stream'
|
|
@@ -1868,12 +1872,87 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1868
1872
|
}
|
|
1869
1873
|
}
|
|
1870
1874
|
|
|
1875
|
+
case 'document': {
|
|
1876
|
+
const documentMetadata = part.metadata as
|
|
1877
|
+
| { filename?: string; detail?: 'auto' | 'low' | 'high' }
|
|
1878
|
+
| undefined
|
|
1879
|
+
// Spread `detail` only when provided so the API applies its own
|
|
1880
|
+
// default ('auto'). The Responses API accepts 'auto' | 'low' | 'high',
|
|
1881
|
+
// but the pinned OpenAI SDK's `ResponseInputFile.detail` type still
|
|
1882
|
+
// lists only 'low' | 'high' — cast so 'auto' (a valid API value) can
|
|
1883
|
+
// pass through without a type error.
|
|
1884
|
+
const documentDetail =
|
|
1885
|
+
documentMetadata?.detail !== undefined
|
|
1886
|
+
? { detail: documentMetadata.detail as 'low' | 'high' }
|
|
1887
|
+
: {}
|
|
1888
|
+
if (part.source.type === 'url') {
|
|
1889
|
+
// The Responses API fetches the PDF itself; filename and MIME
|
|
1890
|
+
// type are inferred from the response.
|
|
1891
|
+
return {
|
|
1892
|
+
type: 'input_file',
|
|
1893
|
+
file_url: part.source.value,
|
|
1894
|
+
...documentDetail,
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
// This adapter supports only PDF documents; anything else is
|
|
1898
|
+
// rejected. MIME types are case-insensitive and can carry
|
|
1899
|
+
// ;parameters (RFC 2045), so the type is normalized before comparing.
|
|
1900
|
+
const documentValue = part.source.value
|
|
1901
|
+
const documentMime = (
|
|
1902
|
+
(part.source.mimeType || 'application/pdf').split(';')[0] ?? ''
|
|
1903
|
+
)
|
|
1904
|
+
.trim()
|
|
1905
|
+
.toLowerCase()
|
|
1906
|
+
if (documentMime !== 'application/pdf') {
|
|
1907
|
+
throw new Error(
|
|
1908
|
+
`${this.name} document parts only support application/pdf ` +
|
|
1909
|
+
`(received ${documentMime})`,
|
|
1910
|
+
)
|
|
1911
|
+
}
|
|
1912
|
+
// A pre-wrapped data URL carries its own media type — validate it too.
|
|
1913
|
+
if (
|
|
1914
|
+
documentValue.startsWith('data:') &&
|
|
1915
|
+
!/^data:application\/pdf[;,]/i.test(documentValue)
|
|
1916
|
+
) {
|
|
1917
|
+
throw new Error(
|
|
1918
|
+
`${this.name} document parts only support application/pdf ` +
|
|
1919
|
+
`(received data URL with non-PDF media type)`,
|
|
1920
|
+
)
|
|
1921
|
+
}
|
|
1922
|
+
// Sniff the payload so a non-PDF labeled (or unlabeled) as PDF is
|
|
1923
|
+
// caught locally instead of by an opaque provider 400. Only base64
|
|
1924
|
+
// payloads are checked; a rare `data:` URL without `;base64` is left
|
|
1925
|
+
// to the server.
|
|
1926
|
+
const documentBase64 = documentValue.startsWith('data:')
|
|
1927
|
+
? /;base64,/i.test(documentValue)
|
|
1928
|
+
? documentValue.slice(documentValue.indexOf(',') + 1)
|
|
1929
|
+
: ''
|
|
1930
|
+
: documentValue
|
|
1931
|
+
if (documentBase64 && !documentBase64.startsWith(PDF_BASE64_MAGIC)) {
|
|
1932
|
+
throw new Error(
|
|
1933
|
+
`${this.name} document parts only support application/pdf ` +
|
|
1934
|
+
`(inline data does not start with the %PDF header)`,
|
|
1935
|
+
)
|
|
1936
|
+
}
|
|
1937
|
+
// Wrap raw base64 in a data URL — `input_file` rejects bare base64
|
|
1938
|
+
// payloads (matches the image and audio branches above).
|
|
1939
|
+
const documentFileData = documentValue.startsWith('data:')
|
|
1940
|
+
? documentValue
|
|
1941
|
+
: `data:${documentMime};base64,${documentValue}`
|
|
1942
|
+
return {
|
|
1943
|
+
type: 'input_file',
|
|
1944
|
+
// The Responses API requires a filename alongside PDF `file_data`.
|
|
1945
|
+
filename: documentMetadata?.filename || 'document.pdf',
|
|
1946
|
+
file_data: documentFileData,
|
|
1947
|
+
...documentDetail,
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1871
1951
|
case 'video':
|
|
1872
|
-
case 'document':
|
|
1873
1952
|
default:
|
|
1874
|
-
// OpenAI Responses API doesn't accept native video
|
|
1875
|
-
//
|
|
1876
|
-
//
|
|
1953
|
+
// OpenAI Responses API doesn't accept native video parts on this
|
|
1954
|
+
// path — surface as explicit unsupported error so callers see the
|
|
1955
|
+
// same message regardless of which content type leaked through.
|
|
1877
1956
|
throw new Error(`Unsupported content part type: ${part.type}`)
|
|
1878
1957
|
}
|
|
1879
1958
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openAIProviderTool } from './openai-provider-tool'
|
|
1
2
|
import type { ApplyPatchTool as ApplyPatchToolConfig } from 'openai/resources/responses/responses'
|
|
2
3
|
import type { Tool } from '@tanstack/ai'
|
|
3
4
|
|
|
@@ -24,9 +25,12 @@ export function convertApplyPatchToolToAdapterFormat(
|
|
|
24
25
|
* re-wrap this in their own package.
|
|
25
26
|
*/
|
|
26
27
|
export function applyPatchTool(): Tool {
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
return openAIProviderTool(
|
|
29
|
+
{
|
|
30
|
+
name: 'apply_patch',
|
|
31
|
+
description: 'Apply a patch to modify files',
|
|
32
|
+
metadata: {},
|
|
33
|
+
},
|
|
34
|
+
'apply_patch',
|
|
35
|
+
)
|
|
32
36
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOpenAIProviderToolMetadata,
|
|
3
|
+
openAIProviderTool,
|
|
4
|
+
} from './openai-provider-tool'
|
|
1
5
|
import type { Tool as SDKTool } from 'openai/resources/responses/responses'
|
|
2
6
|
import type { Tool } from '@tanstack/ai'
|
|
3
7
|
|
|
@@ -14,7 +18,9 @@ export type CodeInterpreterTool = CodeInterpreterToolConfig
|
|
|
14
18
|
export function convertCodeInterpreterToolToAdapterFormat(
|
|
15
19
|
tool: Tool,
|
|
16
20
|
): CodeInterpreterToolConfig {
|
|
17
|
-
const metadata =
|
|
21
|
+
const metadata = getOpenAIProviderToolMetadata(
|
|
22
|
+
tool,
|
|
23
|
+
) as CodeInterpreterToolConfig
|
|
18
24
|
return {
|
|
19
25
|
type: 'code_interpreter',
|
|
20
26
|
container: metadata.container,
|
|
@@ -30,12 +36,15 @@ export function convertCodeInterpreterToolToAdapterFormat(
|
|
|
30
36
|
export function codeInterpreterTool(
|
|
31
37
|
container: CodeInterpreterToolConfig,
|
|
32
38
|
): Tool {
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
return openAIProviderTool(
|
|
40
|
+
{
|
|
41
|
+
name: 'code_interpreter',
|
|
42
|
+
description: 'Execute code in a sandboxed environment',
|
|
43
|
+
metadata: {
|
|
44
|
+
type: 'code_interpreter',
|
|
45
|
+
container,
|
|
46
|
+
},
|
|
39
47
|
},
|
|
40
|
-
|
|
48
|
+
'code_interpreter',
|
|
49
|
+
)
|
|
41
50
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOpenAIProviderToolMetadata,
|
|
3
|
+
openAIProviderTool,
|
|
4
|
+
} from './openai-provider-tool'
|
|
1
5
|
import type { ComputerUsePreviewTool as ComputerUseToolConfig } from 'openai/resources/responses/responses'
|
|
2
6
|
import type { Tool } from '@tanstack/ai'
|
|
3
7
|
|
|
@@ -12,7 +16,7 @@ export type ComputerUseTool = ComputerUseToolConfig
|
|
|
12
16
|
export function convertComputerUseToolToAdapterFormat(
|
|
13
17
|
tool: Tool,
|
|
14
18
|
): ComputerUseToolConfig {
|
|
15
|
-
const metadata = tool
|
|
19
|
+
const metadata = getOpenAIProviderToolMetadata(tool) as ComputerUseToolConfig
|
|
16
20
|
return {
|
|
17
21
|
type: 'computer_use_preview',
|
|
18
22
|
display_height: metadata.display_height,
|
|
@@ -28,11 +32,14 @@ export function convertComputerUseToolToAdapterFormat(
|
|
|
28
32
|
* re-wrap this in their own package.
|
|
29
33
|
*/
|
|
30
34
|
export function computerUseTool(toolData: ComputerUseToolConfig): Tool {
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
return openAIProviderTool(
|
|
36
|
+
{
|
|
37
|
+
name: 'computer_use_preview',
|
|
38
|
+
description: 'Control a virtual computer',
|
|
39
|
+
metadata: {
|
|
40
|
+
...toolData,
|
|
41
|
+
},
|
|
36
42
|
},
|
|
37
|
-
|
|
43
|
+
'computer_use',
|
|
44
|
+
)
|
|
38
45
|
}
|
package/src/tools/custom-tool.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOpenAIProviderToolMetadata,
|
|
3
|
+
openAIProviderTool,
|
|
4
|
+
} from './openai-provider-tool'
|
|
1
5
|
import type { CustomTool as CustomToolConfig } from 'openai/resources/responses/responses'
|
|
2
6
|
import type { Tool } from '@tanstack/ai'
|
|
3
7
|
|
|
@@ -10,7 +14,7 @@ export type CustomTool = CustomToolConfig
|
|
|
10
14
|
* Converts a standard Tool to OpenAI CustomTool format
|
|
11
15
|
*/
|
|
12
16
|
export function convertCustomToolToAdapterFormat(tool: Tool): CustomToolConfig {
|
|
13
|
-
const metadata = tool
|
|
17
|
+
const metadata = getOpenAIProviderToolMetadata(tool) as CustomToolConfig
|
|
14
18
|
// Conditional spread: the SDK's `CustomToolConfig` declares optional
|
|
15
19
|
// fields as `description?: string` (no `| undefined`) under
|
|
16
20
|
// exactOptionalPropertyTypes, so we omit absent fields rather than
|
|
@@ -29,11 +33,14 @@ export function convertCustomToolToAdapterFormat(tool: Tool): CustomToolConfig {
|
|
|
29
33
|
* Creates a standard Tool from CustomTool parameters.
|
|
30
34
|
*/
|
|
31
35
|
export function customTool(toolData: CustomToolConfig): Tool {
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
return openAIProviderTool(
|
|
37
|
+
{
|
|
38
|
+
name: 'custom',
|
|
39
|
+
description: toolData.description || 'A custom tool',
|
|
40
|
+
metadata: {
|
|
41
|
+
...toolData,
|
|
42
|
+
},
|
|
37
43
|
},
|
|
38
|
-
|
|
44
|
+
'custom',
|
|
45
|
+
)
|
|
39
46
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOpenAIProviderToolMetadata,
|
|
3
|
+
openAIProviderTool,
|
|
4
|
+
} from './openai-provider-tool'
|
|
1
5
|
import type { FileSearchTool as FileSearchToolConfig } from 'openai/resources/responses/responses'
|
|
2
6
|
import type { Tool } from '@tanstack/ai'
|
|
3
7
|
|
|
@@ -21,7 +25,7 @@ export type FileSearchTool = FileSearchToolConfig
|
|
|
21
25
|
export function convertFileSearchToolToAdapterFormat(
|
|
22
26
|
tool: Tool,
|
|
23
27
|
): FileSearchToolConfig {
|
|
24
|
-
const metadata = tool
|
|
28
|
+
const metadata = getOpenAIProviderToolMetadata(tool) as FileSearchToolConfig
|
|
25
29
|
// Conditional spread: SDK's `FileSearchToolConfig` declares the
|
|
26
30
|
// optional fields without `| undefined`, so we omit absent values
|
|
27
31
|
// rather than passing them through as explicit `undefined`.
|
|
@@ -46,13 +50,16 @@ export function convertFileSearchToolToAdapterFormat(
|
|
|
46
50
|
*/
|
|
47
51
|
export function fileSearchTool(toolData: FileSearchToolConfig): Tool {
|
|
48
52
|
validateMaxNumResults(toolData.max_num_results)
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
return openAIProviderTool(
|
|
54
|
+
{
|
|
55
|
+
name: 'file_search',
|
|
56
|
+
description: 'Search files in vector stores',
|
|
57
|
+
metadata: {
|
|
58
|
+
...toolData,
|
|
59
|
+
},
|
|
54
60
|
},
|
|
55
|
-
|
|
61
|
+
'file_search',
|
|
62
|
+
)
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export { validateMaxNumResults }
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOpenAIProviderToolMetadata,
|
|
3
|
+
openAIProviderTool,
|
|
4
|
+
} from './openai-provider-tool'
|
|
1
5
|
import type { Tool as SDKTool } from 'openai/resources/responses/responses'
|
|
2
6
|
import type { Tool } from '@tanstack/ai'
|
|
3
7
|
|
|
@@ -16,15 +20,16 @@ const validatePartialImages = (value: number | undefined) => {
|
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Converts a standard Tool to OpenAI ImageGenerationTool format. Spread
|
|
19
|
-
* `metadata` first, then force `type: 'image_generation'` last
|
|
20
|
-
* `metadata.type`
|
|
21
|
-
* the dispatcher (which routed by `tool.name`) would emit a tool whose
|
|
22
|
-
* runtime `type` doesn't match `image_generation`.
|
|
23
|
+
* `metadata` first, then force `type: 'image_generation'` last so a stray
|
|
24
|
+
* `metadata.type` cannot shadow the wire discriminator.
|
|
23
25
|
*/
|
|
24
26
|
export function convertImageGenerationToolToAdapterFormat(
|
|
25
27
|
tool: Tool,
|
|
26
28
|
): ImageGenerationToolConfig {
|
|
27
|
-
const metadata = tool
|
|
29
|
+
const metadata = getOpenAIProviderToolMetadata(tool) as Omit<
|
|
30
|
+
ImageGenerationToolConfig,
|
|
31
|
+
'type'
|
|
32
|
+
>
|
|
28
33
|
return {
|
|
29
34
|
...metadata,
|
|
30
35
|
type: 'image_generation',
|
|
@@ -41,13 +46,16 @@ export function imageGenerationTool(
|
|
|
41
46
|
toolData: Omit<ImageGenerationToolConfig, 'type'>,
|
|
42
47
|
): Tool {
|
|
43
48
|
validatePartialImages(toolData.partial_images)
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
return openAIProviderTool(
|
|
50
|
+
{
|
|
51
|
+
name: 'image_generation',
|
|
52
|
+
description: 'Generate images based on text descriptions',
|
|
53
|
+
metadata: {
|
|
54
|
+
...toolData,
|
|
55
|
+
},
|
|
49
56
|
},
|
|
50
|
-
|
|
57
|
+
'image_generation',
|
|
58
|
+
)
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
export { validatePartialImages }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openAIProviderTool } from './openai-provider-tool'
|
|
1
2
|
import type { Tool as SDKTool } from 'openai/resources/responses/responses'
|
|
2
3
|
import type { Tool } from '@tanstack/ai'
|
|
3
4
|
|
|
@@ -26,9 +27,12 @@ export function convertLocalShellToolToAdapterFormat(
|
|
|
26
27
|
* re-wrap this in their own package.
|
|
27
28
|
*/
|
|
28
29
|
export function localShellTool(): Tool {
|
|
29
|
-
return
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
return openAIProviderTool(
|
|
31
|
+
{
|
|
32
|
+
name: 'local_shell',
|
|
33
|
+
description: 'Execute local shell commands',
|
|
34
|
+
metadata: {},
|
|
35
|
+
},
|
|
36
|
+
'local_shell',
|
|
37
|
+
)
|
|
34
38
|
}
|
package/src/tools/mcp-tool.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOpenAIProviderToolMetadata,
|
|
3
|
+
openAIProviderTool,
|
|
4
|
+
} from './openai-provider-tool'
|
|
1
5
|
import type { Tool as SDKTool } from 'openai/resources/responses/responses'
|
|
2
6
|
import type { Tool } from '@tanstack/ai'
|
|
3
7
|
|
|
@@ -21,15 +25,18 @@ export function validateMCPtool(tool: MCPToolConfig) {
|
|
|
21
25
|
* Converts a standard Tool to OpenAI MCPTool format
|
|
22
26
|
*/
|
|
23
27
|
export function convertMCPToolToAdapterFormat(tool: Tool): MCPToolConfig {
|
|
24
|
-
const metadata = tool
|
|
28
|
+
const metadata = getOpenAIProviderToolMetadata(tool) as Omit<
|
|
29
|
+
MCPToolConfig,
|
|
30
|
+
'type'
|
|
31
|
+
>
|
|
25
32
|
|
|
26
|
-
const
|
|
33
|
+
const convertedTool: MCPToolConfig = {
|
|
27
34
|
...metadata,
|
|
28
35
|
type: 'mcp',
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
validateMCPtool(
|
|
32
|
-
return
|
|
38
|
+
validateMCPtool(convertedTool)
|
|
39
|
+
return convertedTool
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
/**
|
|
@@ -41,9 +48,12 @@ export function convertMCPToolToAdapterFormat(tool: Tool): MCPToolConfig {
|
|
|
41
48
|
export function mcpTool(toolData: Omit<MCPToolConfig, 'type'>): Tool {
|
|
42
49
|
validateMCPtool({ ...toolData, type: 'mcp' })
|
|
43
50
|
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
return openAIProviderTool(
|
|
52
|
+
{
|
|
53
|
+
name: 'mcp',
|
|
54
|
+
description: toolData.server_description || '',
|
|
55
|
+
metadata: toolData,
|
|
56
|
+
},
|
|
57
|
+
'mcp',
|
|
58
|
+
)
|
|
49
59
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Tool } from '@tanstack/ai'
|
|
2
|
+
|
|
3
|
+
const OPENAI_PROVIDER_TOOL_KINDS = {
|
|
4
|
+
apply_patch: 'openai.apply_patch',
|
|
5
|
+
code_interpreter: 'openai.code_interpreter',
|
|
6
|
+
computer_use: 'openai.computer_use',
|
|
7
|
+
custom: 'openai.custom',
|
|
8
|
+
file_search: 'openai.file_search',
|
|
9
|
+
image_generation: 'openai.image_generation',
|
|
10
|
+
local_shell: 'openai.local_shell',
|
|
11
|
+
mcp: 'openai.mcp',
|
|
12
|
+
shell: 'openai.shell',
|
|
13
|
+
web_search: 'openai.web_search',
|
|
14
|
+
web_search_preview: 'openai.web_search_preview',
|
|
15
|
+
} as const
|
|
16
|
+
|
|
17
|
+
export type OpenAIProviderToolKind = keyof typeof OPENAI_PROVIDER_TOOL_KINDS
|
|
18
|
+
|
|
19
|
+
export function openAIProviderTool(
|
|
20
|
+
tool: Tool,
|
|
21
|
+
toolKind: OpenAIProviderToolKind,
|
|
22
|
+
): Tool {
|
|
23
|
+
return {
|
|
24
|
+
...tool,
|
|
25
|
+
metadata: {
|
|
26
|
+
...tool.metadata,
|
|
27
|
+
__kind: OPENAI_PROVIDER_TOOL_KINDS[toolKind],
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getOpenAIProviderToolKind(
|
|
33
|
+
tool: Tool,
|
|
34
|
+
): OpenAIProviderToolKind | undefined {
|
|
35
|
+
const toolKind = tool.metadata?.['__kind']
|
|
36
|
+
switch (toolKind) {
|
|
37
|
+
case 'openai.apply_patch':
|
|
38
|
+
return 'apply_patch'
|
|
39
|
+
case 'openai.code_interpreter':
|
|
40
|
+
return 'code_interpreter'
|
|
41
|
+
case 'openai.computer_use':
|
|
42
|
+
return 'computer_use'
|
|
43
|
+
case 'openai.custom':
|
|
44
|
+
return 'custom'
|
|
45
|
+
case 'openai.file_search':
|
|
46
|
+
return 'file_search'
|
|
47
|
+
case 'openai.image_generation':
|
|
48
|
+
return 'image_generation'
|
|
49
|
+
case 'openai.local_shell':
|
|
50
|
+
return 'local_shell'
|
|
51
|
+
case 'openai.mcp':
|
|
52
|
+
return 'mcp'
|
|
53
|
+
case 'openai.shell':
|
|
54
|
+
return 'shell'
|
|
55
|
+
case 'openai.web_search':
|
|
56
|
+
return 'web_search'
|
|
57
|
+
case 'openai.web_search_preview':
|
|
58
|
+
return 'web_search_preview'
|
|
59
|
+
default:
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Returns adapter metadata without the internal runtime discriminator. */
|
|
65
|
+
export function getOpenAIProviderToolMetadata(tool: Tool): Tool['metadata'] {
|
|
66
|
+
if (!tool.metadata) {
|
|
67
|
+
return undefined
|
|
68
|
+
}
|
|
69
|
+
const { __kind: _kind, ...metadata } = tool.metadata
|
|
70
|
+
void _kind
|
|
71
|
+
return metadata
|
|
72
|
+
}
|
package/src/tools/shell-tool.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getOpenAIProviderToolMetadata,
|
|
3
|
+
openAIProviderTool,
|
|
4
|
+
} from './openai-provider-tool'
|
|
1
5
|
import type { FunctionShellTool as ShellToolConfig } from 'openai/resources/responses/responses'
|
|
2
6
|
import type { Tool } from '@tanstack/ai'
|
|
3
7
|
|
|
@@ -21,7 +25,8 @@ export interface ShellToolFactoryConfig {
|
|
|
21
25
|
* `environment` (container config + skills) stored in metadata.
|
|
22
26
|
*/
|
|
23
27
|
export function convertShellToolToAdapterFormat(tool: Tool): ShellToolConfig {
|
|
24
|
-
const metadata = (tool
|
|
28
|
+
const metadata = (getOpenAIProviderToolMetadata(tool) ??
|
|
29
|
+
{}) as ShellToolFactoryConfig
|
|
25
30
|
return {
|
|
26
31
|
type: 'shell',
|
|
27
32
|
...(metadata.environment !== undefined && {
|
|
@@ -37,13 +42,16 @@ export function convertShellToolToAdapterFormat(tool: Tool): ShellToolConfig {
|
|
|
37
42
|
* re-wrap this in their own package.
|
|
38
43
|
*/
|
|
39
44
|
export function shellTool(config: ShellToolFactoryConfig = {}): Tool {
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
return openAIProviderTool(
|
|
46
|
+
{
|
|
47
|
+
name: 'shell',
|
|
48
|
+
description: 'Execute shell commands',
|
|
49
|
+
metadata: {
|
|
50
|
+
...(config.environment !== undefined && {
|
|
51
|
+
environment: config.environment,
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
47
54
|
},
|
|
48
|
-
|
|
55
|
+
'shell',
|
|
56
|
+
)
|
|
49
57
|
}
|
|
@@ -7,62 +7,47 @@ import { convertFunctionToolToAdapterFormat } from './function-tool'
|
|
|
7
7
|
import { convertImageGenerationToolToAdapterFormat } from './image-generation-tool'
|
|
8
8
|
import { convertLocalShellToolToAdapterFormat } from './local-shell-tool'
|
|
9
9
|
import { convertMCPToolToAdapterFormat } from './mcp-tool'
|
|
10
|
+
import { getOpenAIProviderToolKind } from './openai-provider-tool'
|
|
10
11
|
import { convertShellToolToAdapterFormat } from './shell-tool'
|
|
11
12
|
import { convertWebSearchPreviewToolToAdapterFormat } from './web-search-preview-tool'
|
|
12
13
|
import { convertWebSearchToolToAdapterFormat } from './web-search-tool'
|
|
13
14
|
import type { OpenAITool } from './index'
|
|
15
|
+
import { assertUniqueToolNames } from '@tanstack/ai/adapter-internals'
|
|
14
16
|
import type { Tool } from '@tanstack/ai'
|
|
15
17
|
|
|
16
|
-
const SPECIAL_TOOL_NAMES = new Set([
|
|
17
|
-
'apply_patch',
|
|
18
|
-
'code_interpreter',
|
|
19
|
-
'computer_use_preview',
|
|
20
|
-
'file_search',
|
|
21
|
-
'image_generation',
|
|
22
|
-
'local_shell',
|
|
23
|
-
'mcp',
|
|
24
|
-
'shell',
|
|
25
|
-
'web_search_preview',
|
|
26
|
-
'web_search',
|
|
27
|
-
'custom',
|
|
28
|
-
])
|
|
29
|
-
|
|
30
18
|
/**
|
|
31
19
|
* Converts an array of standard Tools to OpenAI-specific format
|
|
32
20
|
*/
|
|
33
21
|
export function convertToolsToProviderFormat(
|
|
34
22
|
tools: Array<Tool>,
|
|
35
23
|
): Array<OpenAITool> {
|
|
24
|
+
assertUniqueToolNames(tools)
|
|
36
25
|
return tools.map((tool) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return convertCustomToolToAdapterFormat(tool)
|
|
63
|
-
}
|
|
26
|
+
switch (getOpenAIProviderToolKind(tool)) {
|
|
27
|
+
case 'apply_patch':
|
|
28
|
+
return convertApplyPatchToolToAdapterFormat(tool)
|
|
29
|
+
case 'code_interpreter':
|
|
30
|
+
return convertCodeInterpreterToolToAdapterFormat(tool)
|
|
31
|
+
case 'computer_use':
|
|
32
|
+
return convertComputerUseToolToAdapterFormat(tool)
|
|
33
|
+
case 'custom':
|
|
34
|
+
return convertCustomToolToAdapterFormat(tool)
|
|
35
|
+
case 'file_search':
|
|
36
|
+
return convertFileSearchToolToAdapterFormat(tool)
|
|
37
|
+
case 'image_generation':
|
|
38
|
+
return convertImageGenerationToolToAdapterFormat(tool)
|
|
39
|
+
case 'local_shell':
|
|
40
|
+
return convertLocalShellToolToAdapterFormat(tool)
|
|
41
|
+
case 'mcp':
|
|
42
|
+
return convertMCPToolToAdapterFormat(tool)
|
|
43
|
+
case 'shell':
|
|
44
|
+
return convertShellToolToAdapterFormat(tool)
|
|
45
|
+
case 'web_search_preview':
|
|
46
|
+
return convertWebSearchPreviewToolToAdapterFormat(tool)
|
|
47
|
+
case 'web_search':
|
|
48
|
+
return convertWebSearchToolToAdapterFormat(tool)
|
|
49
|
+
case undefined:
|
|
50
|
+
return convertFunctionToolToAdapterFormat(tool)
|
|
64
51
|
}
|
|
65
|
-
|
|
66
|
-
return convertFunctionToolToAdapterFormat(tool)
|
|
67
52
|
})
|
|
68
53
|
}
|