Skip to content

Commit d92c7aa

Browse files
authored
feat(ai-sdk): createTool, implementTool helpers (#1151)
Implement or create AI SDK tool from contract/procedure. Closes: #1150 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added @orpc/ai-sdk package enabling integration of oRPC contracts with the Vercel AI SDK * Provides functions to map contract-first definitions to AI SDK tools * Includes comprehensive documentation with TypeScript examples for AI SDK integration * **Documentation** * Added detailed guides for integrating AI SDK tools with oRPC contracts <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent d5a103f commit d92c7aa

File tree

12 files changed

+811
-0
lines changed

12 files changed

+811
-0
lines changed

apps/content/docs/integrations/ai-sdk.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,102 @@ Prefer `eventIteratorToUnproxiedDataStream` over `eventIteratorToStream`.
121121
AI SDK internally uses `structuredClone`, which doesn't support proxied data.
122122
oRPC may proxy events for [metadata](/docs/event-iterator#last-event-id-event-metadata), so unproxy before passing to AI SDK.
123123
:::
124+
125+
## `implementTool` helper
126+
127+
Implements [procedure contract](/docs/contract-first/define-contract) as an [AI SDK tools](https://ai-sdk.dev/docs/foundations/tools) by leveraging existing contract definitions.
128+
129+
```ts twoslash
130+
import { oc } from '@orpc/contract'
131+
import {
132+
AI_SDK_TOOL_META_SYMBOL,
133+
AiSdkToolMeta,
134+
implementTool
135+
} from '@orpc/ai-sdk'
136+
import { z } from 'zod'
137+
138+
interface ORPCMeta extends AiSdkToolMeta {} // optional extend meta
139+
const base = oc.$meta<ORPCMeta>({})
140+
141+
const getWeatherContract = base
142+
.meta({
143+
[AI_SDK_TOOL_META_SYMBOL]: {
144+
name: 'custom-tool-name', // AI SDK tool name
145+
},
146+
})
147+
.route({
148+
summary: 'Get the weather in a location', // AI SDK tool description
149+
})
150+
.input(z.object({
151+
location: z.string().describe('The location to get the weather for'),
152+
}))
153+
.output(z.object({
154+
location: z.string().describe('The location the weather is for'),
155+
temperature: z.number().describe('The temperature in Celsius'),
156+
}))
157+
158+
const getWeatherTool = implementTool(getWeatherContract, {
159+
execute: async ({ location }) => ({
160+
location,
161+
temperature: 72 + Math.floor(Math.random() * 21) - 10,
162+
}),
163+
})
164+
```
165+
166+
::: warning
167+
The `implementTool` helper requires a contract with an `input` schema defined
168+
:::
169+
170+
::: info
171+
Standard [procedures](/docs/procedure) are also compatible with [procedure contracts](/docs/contract-first/define-contract).
172+
:::
173+
174+
## `createTool` helper
175+
176+
Converts a [procedure](/docs/procedure) into an [AI SDK Tool](https://ai-sdk.dev/docs/foundations/tools) by leveraging existing procedure definitions.
177+
178+
```ts twoslash
179+
import { os } from '@orpc/server'
180+
import {
181+
AI_SDK_TOOL_META_SYMBOL,
182+
AiSdkToolMeta,
183+
createTool
184+
} from '@orpc/ai-sdk'
185+
import { z } from 'zod'
186+
187+
interface ORPCMeta extends AiSdkToolMeta {} // optional extend meta
188+
const base = os.$meta<ORPCMeta>({})
189+
190+
const getWeatherProcedure = base
191+
.meta({
192+
[AI_SDK_TOOL_META_SYMBOL]: {
193+
name: 'custom-tool-name', // AI SDK tool name
194+
},
195+
})
196+
.route({
197+
summary: 'Get the weather in a location',
198+
})
199+
.input(z.object({
200+
location: z.string().describe('The location to get the weather for'),
201+
}))
202+
.output(z.object({
203+
location: z.string().describe('The location the weather is for'),
204+
temperature: z.number().describe('The temperature in Celsius'),
205+
}))
206+
.handler(async ({ input }) => ({
207+
location: input.location,
208+
temperature: 72 + Math.floor(Math.random() * 21) - 10,
209+
}))
210+
211+
const getWeatherTool = createTool(getWeatherProcedure, {
212+
context: {}, // provide initial context if needed
213+
})
214+
```
215+
216+
::: warning
217+
The `createTool` helper requires a procedure with an `input` schema defined
218+
:::
219+
220+
::: warning
221+
Validation occurs twice (once for the tool, once for the procedure call). So validation may fail if `inputSchema` or `outputSchema` transform the data into different shapes.
222+
:::

apps/content/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@opentelemetry/instrumentation": "^0.207.0",
1414
"@opentelemetry/sdk-node": "^0.207.0",
1515
"@opentelemetry/sdk-trace-web": "^2.2.0",
16+
"@orpc/ai-sdk": "workspace:*",
1617
"@orpc/arktype": "workspace:*",
1718
"@orpc/client": "workspace:*",
1819
"@orpc/contract": "workspace:*",

packages/ai-sdk/.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Hidden folders and files
2+
.*
3+
!.gitignore
4+
!.*.example
5+
6+
# Common generated folders
7+
logs/
8+
node_modules/
9+
out/
10+
dist/
11+
dist-ssr/
12+
build/
13+
coverage/
14+
temp/
15+
16+
# Common generated files
17+
*.log
18+
*.log.*
19+
*.tsbuildinfo
20+
*.vitest-temp.json
21+
vite.config.ts.timestamp-*
22+
vitest.config.ts.timestamp-*
23+
24+
# Common manual ignore files
25+
*.local
26+
*.pem

packages/ai-sdk/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<div align="center">
2+
<image align="center" src="https://orpc.unnoq.com/logo.webp" width=280 alt="oRPC logo" />
3+
</div>
4+
5+
<h1></h1>
6+
7+
<div align="center">
8+
<a href="https://codecov.io/gh/unnoq/orpc">
9+
<img alt="codecov" src="https://codecov.io/gh/unnoq/orpc/branch/main/graph/badge.svg">
10+
</a>
11+
<a href="https://www.npmjs.com/package/@orpc/ai-sdk">
12+
<img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fai-sdk?logo=npm" />
13+
</a>
14+
<a href="https://github.com/unnoq/orpc/blob/main/LICENSE">
15+
<img alt="MIT License" src="https://img.shields.io/github/license/unnoq/orpc?logo=open-source-initiative" />
16+
</a>
17+
<a href="https://discord.gg/TXEbwRBvQn">
18+
<img alt="Discord" src="https://img.shields.io/discord/1308966753044398161?color=7389D8&label&logo=discord&logoColor=ffffff" />
19+
</a>
20+
<a href="https://deepwiki.com/unnoq/orpc">
21+
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
22+
</a>
23+
</div>
24+
25+
<h3 align="center">Typesafe APIs Made Simple 🪄</h3>
26+
27+
**oRPC is a powerful combination of RPC and OpenAPI**, makes it easy to build APIs that are end-to-end type-safe and adhere to OpenAPI standards
28+
29+
---
30+
31+
## Highlights
32+
33+
- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server.
34+
- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard.
35+
- **📝 Contract-First Development**: Optionally define your API contract before implementation.
36+
- **🔍 First-Class OpenTelemetry**: Seamlessly integrate with OpenTelemetry for observability.
37+
- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte, Angular), SWR, Pinia Colada, and more.
38+
- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
39+
- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators.
40+
- **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL, and more.
41+
- **⏱️ Lazy Router**: Enhance cold start times with our lazy routing feature.
42+
- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming.
43+
- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun, Node.js, and beyond.
44+
- **🔌 Extendability**: Easily extend functionality with plugins, middleware, and interceptors.
45+
46+
## Documentation
47+
48+
You can find the full documentation [here](https://orpc.unnoq.com).
49+
50+
## Packages
51+
52+
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
53+
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
54+
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
55+
- [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
56+
- [@orpc/otel](https://www.npmjs.com/package/@orpc/otel): [OpenTelemetry](https://opentelemetry.io/) integration for observability.
57+
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with [NestJS](https://nestjs.com/).
58+
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
59+
- [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): [TanStack Query](https://tanstack.com/query/latest) integration.
60+
- [@orpc/experimental-react-swr](https://www.npmjs.com/package/@orpc/experimental-react-swr): [SWR](https://swr.vercel.app/) integration.
61+
- [@orpc/vue-colada](https://www.npmjs.com/package/@orpc/vue-colada): Integration with [Pinia Colada](https://pinia-colada.esm.dev/).
62+
- [@orpc/hey-api](https://www.npmjs.com/package/@orpc/hey-api): [Hey API](https://heyapi.dev/) integration.
63+
- [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): More schemas that [Zod](https://zod.dev/) doesn't support yet.
64+
- [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): OpenAPI spec generation from [Valibot](https://valibot.dev/).
65+
- [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): OpenAPI spec generation from [ArkType](https://arktype.io/).
66+
67+
## `@orpc/ai-sdk`
68+
69+
The [AI SDK](https://ai-sdk.dev/) integration for oRPC.
70+
71+
## Sponsors
72+
73+
<p align="center">
74+
<a href="https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg">
75+
<img src='https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg'/>
76+
</a>
77+
</p>
78+
79+
## License
80+
81+
Distributed under the MIT License. See [LICENSE](https://github.com/unnoq/orpc/blob/main/LICENSE) for more information.

packages/ai-sdk/package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@orpc/ai-sdk",
3+
"type": "module",
4+
"version": "0.0.1",
5+
"license": "MIT",
6+
"homepage": "https://orpc.unnoq.com",
7+
"repository": {
8+
"type": "git",
9+
"url": "git+https://github.com/unnoq/orpc.git",
10+
"directory": "packages/ai-sdk"
11+
},
12+
"keywords": [
13+
"ai-sdk",
14+
"orpc"
15+
],
16+
"publishConfig": {
17+
"exports": {
18+
".": {
19+
"types": "./dist/index.d.mts",
20+
"import": "./dist/index.mjs",
21+
"default": "./dist/index.mjs"
22+
}
23+
}
24+
},
25+
"exports": {
26+
".": "./src/index.ts"
27+
},
28+
"files": [
29+
"dist"
30+
],
31+
"scripts": {
32+
"build": "unbuild",
33+
"build:watch": "pnpm run build --watch",
34+
"type:check": "tsc -b"
35+
},
36+
"peerDependencies": {
37+
"ai": ">=5.0.76"
38+
},
39+
"dependencies": {
40+
"@orpc/client": "workspace:*",
41+
"@orpc/contract": "workspace:*",
42+
"@orpc/server": "workspace:*",
43+
"@orpc/shared": "workspace:*"
44+
},
45+
"devDependencies": {
46+
"ai": "6.0.0-beta.85",
47+
"zod": "^4.1.12"
48+
}
49+
}

packages/ai-sdk/src/index.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
it('exports createTool', async () => {
2+
expect(Object.keys(await import('./index'))).toContain('createTool')
3+
})

packages/ai-sdk/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export * from './tool'
2+
3+
export {
4+
AsyncIteratorClass,
5+
asyncIteratorToStream as eventIteratorToStream,
6+
asyncIteratorToUnproxiedDataStream as eventIteratorToUnproxiedDataStream,
7+
streamToAsyncIteratorClass as streamToEventIterator,
8+
} from '@orpc/shared'

0 commit comments

Comments
 (0)