Developer docs · Browse & search
EMBED

Embedding in your product

Your server signs a person in, and their page works with agents and workflows directly.

A page never holds your key. Your server signs its user in and hands the page a client secret. From then on the page works with the API directly.

ts
// Once, by an owner or administrator
await jwtson.embedding.allowOrigin('https://app.example.com');

// Once per agent or workflow, or under Client apps in the console. The name always means the latest published version.
await jwtson.clientTargets.set('assistant', agent);

// Your server, after your own sign-in
const session = await jwtson.clientSessions.create({
  subject: user.id,                     // Sent signed to your tools. An id, not a name or email
  audience: `company:${user.companyId}`, // What groups live inside. The page can't change it
  attributes: { plan: user.plan },      // Also sent signed to your tools
  requestsPerMinute: 120,               // Worth setting, since a page can start work
});
res.json(session.forPage);
ts
// Their page
import { connectClient } from '@jwtson/sdk';

const jwtson = connectClient(await fetch('/my-server/session').then(reply => reply.json()));

const run = await jwtson.start('assistant', { question }); // Theirs alone
for await (const text of run.textStream()) show(text);

await jwtson.in(`project:${id}`).start('assistant', { question }); // Shared with their audience
const chat = jwtson.in(`project:${id}`).chat('assistant');
await chat.send('And then?');

Groups need nothing set up. Any name works, including one created a moment ago, and switching is just calling in again with the same secret.

Who can see what. A run in a group is visible to anyone in that audience. A run in no group is its user's own. Another audience sees nothing, whatever name it tries. With no audience, a session sees only its own runs.

Groups aren't a permission system. Anyone in an audience can open any group in it if they know its name, and reading a finished answer calls no tool. If some users must not see a group, block it, from sign-in or at any time:

ts
await jwtson.clientSessions.create({ subject, audience, block: ['project:7'] });
await jwtson.clientSessions.changeAccess({ subject, block: ['project:7'] }); // Takes effect on the next request

Blocked means they can't start or read anything in that group, even their own earlier runs. Nothing enforces this for you. If you can't accept that, leave audience off and keep shared views behind your own server.

Your tools are where your checks count. verifyToolCall gives you call.client, with your subject and attributes and the run's audience and group, all signed. Anything in the arguments came from the model or the page. Checks in the page are useful for what you show, but anyone can get around them. A tool with no check of its own lets any signed-in user reach its data.

ts
const call = await verifyToolCall({ ... });
await assertAccess(call.client.subject, call.client.group);

Roles are the same as anywhere else: role: 'viewer' reads, operator also starts and answers, and is the default, and reviewer reads and approves where the workflow's approvers name that role. Nobody approves their own run unless the workflow allows it. only: ['assistant'] narrows what one session can start, for a plan or a feature.

A page can pick up where it left off. jwtson.runs() and jwtson.chats() list what this user can reach, jwtson.in(group).runs() looks in one group, jwtson.run(id) and jwtson.openChat(id) reopen one. Starting again with the same idempotencyKey reaches the same run, so a lost answer can't start a second. run.events({ after }) gives what happened in order from a number you saved. stream() is live text, and result() is the answer. for (const item of await run.pending()) gives what's waiting, to item.answer(value) or item.approve().

A session lasts an hour by default, 30 days at most. On your server:

ts
await session.renew({ expiresInSeconds: 3600 }); // Same secret, so the page has nothing to do
await session.logOff();                          // Ends this one straight away
await jwtson.clientSessions.logOff(user.id);     // Logs them off everywhere

In the page, call await jwtson.close() when your own sign-out or idle timeout runs. Runs carry on, and they can find them again after signing in. Your server can start work with no page open: { forClient: { subject: user.id, audience, group } }. Your own features, like search with no model, stay on your own API.

Next

@jwtson/react has hooks and components for a signed-in user's page, and apps/client-example is a complete app. For other frameworks, run.watch() gives one current value for a run, with subscribe, getSnapshot and stop.

From the @jwtson/sdk README, as the code stands.