Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dato-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ For now, use it only on a sandbox or test project, never on production or on a p

## Setup

1. A project administrator picks OpenAI or Anthropic in the plugin settings, adds the provider API key and chooses a model.
1. A project administrator picks OpenAI, Anthropic, or Vercel AI Gateway in the plugin settings, adds the provider API key and chooses a model.
2. Each user connects their own DatoCMS account. When asked which projects to authorize, select only the project where the plugin is installed.
3. Open **Agent (Beta)** or the record sidebar and describe what you need.

The provider API key is shared by the whole project. Each user's DatoCMS connection and recent chats are stored in their browser.

Vercel AI Gateway uses an API key from **Vercel → AI Gateway → API keys**. Requests are billed to that team's AI Gateway credits, or to bring-your-own provider keys configured on the gateway. The model list starts with a short recommended set (Claude Sonnet 5.5 by default, plus Opus, Haiku, GPT, Gemini, and Grok options). **Show all tool-capable models** reveals the rest of the catalog. Models that are not Claude are translated through the gateway's Anthropic Messages API, so tool calls work the same way, but zero data retention depends on the model: only models marked ZDR on every route keep that guarantee. Fast mode selects the model's `-fast` variant when the catalog publishes one.

## Permissions

The agent can never do more than the user's role allows. On top of that, the access level chosen when connecting DatoCMS applies (**Only read content** blocks every write), and the plugin's **Read Only** setting turns off all agent writes for everyone.
Expand Down
6 changes: 5 additions & 1 deletion dato-agent/src/entrypoints/AgentFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3572,6 +3572,10 @@ export default function AgentFrame(props: AgentFrameProps) {
},
model: activeModel(props.config),
modelMaxOutputTokens: activeModelMaxOutputTokens(props.config),
modelReasoningEfforts:
props.config.provider === 'vercel'
? props.config.vercelModelReasoningEfforts
: undefined,
reasoningEffort: activeReasoningEffort(props.config),
fastMode: activeFastMode(props.config),
readOnly: props.config.readOnly,
Expand Down Expand Up @@ -4178,7 +4182,7 @@ export default function AgentFrame(props: AgentFrameProps) {
);
const injectHostContext = Boolean(
hostContext &&
(turn.provider === 'anthropic' ||
(turn.provider !== 'openai' ||
!previousResponseId ||
conversationRef.current.hostContextFingerprint !==
hostContext.fingerprint),
Expand Down
65 changes: 63 additions & 2 deletions dato-agent/src/entrypoints/ConfigScreen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ type Option = {
value: string;
};

function selectableOptions(
options: ReadonlyArray<Option | { options?: readonly Option[] }> | undefined,
): Option[] {
return (options ?? []).flatMap((option) =>
'options' in option && Array.isArray(option.options)
? [...option.options]
: 'value' in option
? [option]
: [],
);
}

vi.mock('datocms-react-ui', () => ({
Canvas: ({ children }: { children: ReactNode }) => <>{children}</>,
FieldGroup: ({ children }: { children: ReactNode }) => <div>{children}</div>,
Expand Down Expand Up @@ -120,13 +132,13 @@ vi.mock('datocms-react-ui', () => ({
isDisabled?: boolean;
isLoading?: boolean;
isMulti?: boolean;
options?: Option[];
options?: Array<Option | { label?: string; options: Option[] }>;
required?: boolean;
};
value?: Option | readonly Option[] | null;
}) => {
const selectedOptions = Array.isArray(value) ? value : value ? [value] : [];
const availableOptions = selectInputProps?.options ?? [];
const availableOptions = selectableOptions(selectInputProps?.options);
const renderedOptions = [
...availableOptions,
...selectedOptions.filter(
Expand Down Expand Up @@ -397,6 +409,55 @@ describe('ConfigScreen', () => {
);
});

it('offers recommended gateway models and reveals the rest on request', async () => {
modelMocks.listProviderModels.mockResolvedValue([
{
id: 'anthropic/claude-sonnet-5.5',
label: 'Claude Sonnet 5.5 (Anthropic)',
reasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
recommended: true,
supportsFastMode: true,
},
{
id: 'openai/gpt-5-nano',
label: 'GPT-5 nano (OpenAI)',
reasoningEfforts: ['low', 'high'],
recommended: false,
supportsFastMode: false,
},
]);
const { ctx } = createCtx({
parameters: {
...PARAMETERS,
provider: 'vercel',
vercelApiKey: 'vck_project',
},
});
render(<ConfigScreen ctx={ctx} />);
await finishModelDiscovery();

expect(screen.getByLabelText('AI Gateway API key')).toBeRequired();
expect(screen.getByText(/Vercel → AI Gateway → API keys/)).toBeVisible();
const model = screen.getByLabelText('Model');
expect(model).toHaveValue('anthropic/claude-sonnet-5.5');
expect(
within(model).getByRole('option', {
name: 'Claude Sonnet 5.5 (Anthropic)',
}),
).toBeVisible();
expect(
within(model).queryByRole('option', { name: 'GPT-5 nano (OpenAI)' }),
).not.toBeInTheDocument();

fireEvent.click(screen.getByLabelText('Show all tool-capable models'));
expect(
within(screen.getByLabelText('Model')).getByRole('option', {
name: 'GPT-5 nano (OpenAI)',
}),
).toBeVisible();
expect(screen.getByText(/faster gateway variant/i)).toBeVisible();
});

it('switches vendor fields without losing inactive OpenAI values', async () => {
const { ctx } = createCtx();
render(<ConfigScreen ctx={ctx} />);
Expand Down
Loading