GraphQL Adapter
The @yak-io/graphql package exposes a GraphQL API to the assistant as a single
graphql_<name> tool. The model authors a query or mutation from the SDL you provide, and the
adapter hands the request to your client to execute — Yak never makes the call itself, so your
existing auth, transport, and error handling all apply.
Adapters compose into one tool manifest and one onToolCall via createYakToolset, so GraphQL
calls flow through the same path as every other tool, including
useYakToolEvent.
Installation
npm install @yak-io/graphqlpnpm add @yak-io/graphqlyarn add @yak-io/graphqlbun add @yak-io/graphqlQuick Start
import { createYakToolset, YakProvider, YakWidget } from "@yak-io/react";
import { createGraphQLToolAdapter } from "@yak-io/graphql";
const schema = `
type Order { id: ID! total: Float! status: String! }
type Query {
orders(status: String): [Order!]!
order(id: ID!): Order
}
type Mutation { updateOrderStatus(id: ID!, status: String!): Order }
`;
const toolset = createYakToolset([
createGraphQLToolAdapter({
name: "shop",
schema,
// Run the model-authored request with your own client. Use anything — Apollo, urql,
// graphql-request, or a plain fetch like this. Return `data`; throw to surface an error.
execute: async ({ query, variables }) => {
const res = await fetch("https://api.example.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}` },
body: JSON.stringify({ query, variables }),
});
const { data, errors } = await res.json();
if (errors?.length) throw new Error(errors[0].message);
return data;
},
}),
]);
export default function App() {
return (
<YakProvider
appId="your-app-id"
getConfig={async () => ({ routes, ...(await toolset.getConfig()) })}
onToolCall={toolset.onToolCall}
>
<YakWidget />
</YakProvider>
);
}The model receives the SDL in the tool's description and produces a { query, variables }
request. Yak passes that request straight to your execute callback and returns whatever it
resolves to — you own the endpoint, credentials, and transport.
Already have a configured GraphQL client? Hand the request straight to it:
execute: (req) => myGraphQLClient.request(req.query, req.variables). The adapter only builds the
tool and the request payload — it never owns the client.
Lazy schema (introspection)
If your SDL comes from introspection — or any other deferred source — pass schema as a resolver
instead of a string. The factory stays synchronous, so you still construct the adapter without
await; the resolver runs once when the toolset first materializes its tools, and the resulting SDL
is cached.
import { buildClientSchema, getIntrospectionQuery, printSchema } from "graphql";
createGraphQLToolAdapter({
name: "shop",
// Resolved once, on first use — not at construction.
schema: async () => {
const res = await fetch("https://api.example.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}` },
body: JSON.stringify({ query: getIntrospectionQuery() }),
});
const { data } = await res.json();
return printSchema(buildClientSchema(data));
},
execute: ({ query, variables }) => myGraphQLClient.request(query, variables),
});The resolver is invoked once and cached, so introspection runs a single time — not on every chat
open. A rejection is not cached: a transient failure simply retries on the next load.
@yak-io/prismic's createPrismicGraphQLToolAdapter is built on exactly this seam.
Multiple APIs
Each adapter owns one schema. Compose several — they merge into one manifest and route by tool name automatically:
const toolset = createYakToolset([
createGraphQLToolAdapter({ name: "users", schema: usersSchema, execute: runUsersQuery }),
createGraphQLToolAdapter({ name: "inventory", schema: invSchema, execute: runInventoryQuery }),
]);Configuration
| Option | Type | Description |
|---|---|---|
name | string | Tool name suffix — exposed as graphql_<name>. |
schema | string | (() => string | Promise<string>) | GraphQL SDL — a string, or a resolver returning it (sync or async). Resolved once and cached; use a resolver for introspection. |
execute | (request: GraphQLRequest) => unknown | Promise<unknown> | Runs the model-authored { query, variables, operationName } with your client. Return the result (typically data); throw to surface an error. |
id | string | Stable id for diagnostics. Defaults to the tool name. |
Auth & transport
Your execute callback owns the request, so authentication, headers, base URL, retries, and
error handling are entirely yours — reuse the same authenticated client the rest of your app
already uses. If execute runs in the browser, the endpoint must allow your origin (CORS); to
keep the call server-side instead, route it through your backend (e.g. front your API with
createNextYakHandler and bridge it via createYakServerAdapter).
Migrating from schemaSources
Earlier versions used getConfig().schemaSources plus an onGraphQLSchemaCall prop. Replace both
with a createGraphQLToolAdapter composed through createYakToolset (see Quick Start). The schema
now travels in the tool definition, and your old onGraphQLSchemaCall handler becomes the
adapter's execute callback — same idea (Yak hands you the request, your client runs it), now
scoped per adapter and flowing through onToolCall, so useYakToolEvent fires for GraphQL calls too.
Be careful which operations you expose. Avoid mutations that could be destructive without proper authorization checks on your endpoint.