Skip to main content

Chapter 11: The worka-sdk for Frontend

TL;DR

  • Import the hook: import { useTool } from '@worka-ai/sdk';
  • Instantiate it in your component:
    const { call, data, isLoading, error } = useTool('tool_name', target?);
  • Return Values:
    • call(params?): The async function you execute to run the tool.
    • data: The successful response from the tool.
    • isLoading: A boolean for the loading state.
    • error: The error object if the call fails.
  • Targeting:
    • Omit the target parameter to call a tool on your pack's own backend.
    • Provide a target object ({ tenant: string, name: string }) to call a tool on another pack (e.g., a virtual pack like worka/ui).
  • Best Practice: To prevent infinite re-render loops, always memoize the target object if you provide it.
    import { useMemo } from 'react';
    const uiTarget = useMemo(() => ({ tenant: 'worka', name: 'ui' }), []);
    const { call: openTab } = useTool('open_tab', uiTarget);

Your pack's UI does not run in a vacuum. It needs a way to communicate with its own backend, other packs, and the Worka Host itself. The @worka-ai/sdk is the secure bridge that makes this possible.

It is the only supported way for a pack's UI to make these calls. Direct access to the host's invoke function is blocked by the sandbox.

The useTool Hook in Detail

The primary export of the SDK is the useTool React hook. It handles the complexity of authentication, routing, and asynchronous state management, giving you a clean and simple API.

Let's look at a complete example:

import { useTool } from '@worka-ai/sdk';
import { useMemo } from 'react';
import { Button, Spinner, Text } from '@chakra-ui/react';

function MyComponent() {
// Target the 'system' pack
const systemTarget = useMemo(() => ({ tenant: 'worka', name: 'system' }), []);

// Get the tools to list projects
const { call: listProjects, data, isLoading, error } = useTool('list_projects', systemTarget);

if (isLoading) {
return <Spinner />;
}

if (error) {
return <Text color="red.500">{error.message}</Text>;
}

return (
<>
<Button onClick={() => listProjects()}>Load Projects</Button>
{data && <Text>Projects Loaded: {data.length}</Text>}
</>
);
}

Parameters

  1. tool (string, required): The name of the tool you want to call (e.g., 'list_projects').
  2. target (object, optional): An object specifying the tenant and name of the pack you want to call.
    • If you omit this parameter, the call is automatically routed to your pack's own backend MCP server.
    • This is required to call tools on other packs or on Worka's virtual packs (like worka/ui or worka/db).

Return Value

The hook returns an object with four key properties that cover the entire lifecycle of a request:

  • call: This is the async function you execute to trigger the tool call. You can pass a JSON object to it as an argument, which will be sent as the parameters for the tool (e.g., call({ id: 123 })).
  • isLoading: A boolean that is true from the moment call is executed until a response is received. This is perfect for showing loading spinners or disabling buttons.
  • data: If the tool call is successful, this property will be populated with the response from the backend.
  • error: If the tool call fails for any reason (e.g., network error, backend logic error), this property will contain the error object.

Best Practice: Preventing Re-Renders

In React, if you pass an object literal like { tenant: 'worka', name: 'system' } directly as a prop or hook parameter, a new object is created on every single render. This will cause the useTool hook to think its parameters have changed, leading to infinite loops.

To prevent this, you must wrap the target object in a useMemo hook. This ensures that the same object instance is passed to useTool on every render, unless its dependencies change.

Incorrect (causes loops):

// A new object is created on every render, causing the hook to re-run
const { call } = useTool('open_tab', { tenant: 'worka', name: 'ui' });

Correct (stable):

import { useMemo } from 'react';

// The same object is used on every render
const uiTarget = useMemo(() => ({ tenant: 'worka', name: 'ui' }), []);
const { call } = useTool('open_tab', uiTarget);