Skip to content

Commit d57f725

Browse files
committed
test(providers): assert between_tools on recorded request bodies
1 parent 1e77422 commit d57f725

1 file changed

Lines changed: 49 additions & 96 deletions

File tree

‎apps/sim/providers/anthropic/core.test.ts‎

Lines changed: 49 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -304,51 +304,6 @@ describe('executeAnthropicProviderRequest forced tool use', () => {
304304
)
305305
})
306306

307-
/**
308-
* Claude Sonnet 5.5 rejects `thinking.type: "disabled"` with a 400 and names `between_tools`
309-
* (which takes no effort or display field) as its lowest setting. Every other model keeps
310-
* `none` as "send no thinking config".
311-
*/
312-
describe('executeAnthropicProviderRequest none thinking level', () => {
313-
async function sendWithThinkingNone(model: string) {
314-
const create = vi.fn().mockResolvedValue({
315-
id: 'msg-none',
316-
type: 'message',
317-
role: 'assistant',
318-
model,
319-
content: [{ type: 'text', text: 'Done' }],
320-
stop_reason: 'end_turn',
321-
stop_sequence: null,
322-
usage: { input_tokens: 2, output_tokens: 2 },
323-
})
324-
await executeAnthropicProviderRequest(
325-
{
326-
model,
327-
apiKey: 'test-key',
328-
maxTokens: 1024,
329-
thinkingLevel: 'none',
330-
agentEvents: true,
331-
messages: [{ role: 'user', content: 'Hello' }],
332-
},
333-
{
334-
providerId: 'anthropic',
335-
providerLabel: 'Anthropic',
336-
createClient: () => ({ messages: { create } }) as never,
337-
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
338-
}
339-
)
340-
return create.mock.calls[0][0] as Anthropic.Messages.MessageCreateParams
341-
}
342-
343-
it.each(['claude-sonnet-5', 'claude-opus-5-5'])(
344-
'sends no thinking config on %s',
345-
async (model) => {
346-
const payload = await sendWithThinkingNone(model)
347-
expect(payload.thinking).toBeUndefined()
348-
}
349-
)
350-
})
351-
352307
describe('executeAnthropicProviderRequest native structured outputs', () => {
353308
/** The subset of the wire schema these assertions read back. */
354309
interface WireSchemaNode {
@@ -793,8 +748,11 @@ describe('streaming', () => {
793748

794749
/**
795750
* Both tool loops rebuild the request for every turn after a tool call, so
796-
* Sonnet 5.5's `none` mapping must survive past the first request: a later
797-
* turn without it would silently run adaptive thinking at the API default.
751+
* the `none` mapping must hold past the first request. Claude Sonnet 5.5
752+
* rejects `thinking.type: "disabled"` and names `between_tools` (which takes
753+
* no effort or display field) as its lowest setting; a later turn without it
754+
* would silently run adaptive thinking. Every other model keeps `none` as
755+
* "send no thinking config".
798756
*/
799757
describe('executeAnthropicProviderRequest none thinking level across tool turns', () => {
800758
const lookupTool = {
@@ -804,28 +762,24 @@ describe('streaming', () => {
804762
params: {},
805763
parameters: { type: 'object', properties: {}, required: [] },
806764
}
807-
const toolTurn = message(
808-
[{ type: 'tool_use', id: 'tool-1', name: 'lookup', input: {} }],
809-
'tool_use'
810-
)
811-
const answerTurn = message([{ type: 'text', text: 'done' }], 'end_turn')
765+
const turns = [
766+
message([{ type: 'tool_use', id: 'tool-1', name: 'lookup', input: {} }], 'tool_use'),
767+
message([{ type: 'text', text: 'done' }], 'end_turn'),
768+
]
812769

813-
function expectBareBetweenTools(payloads: Anthropic.Messages.MessageCreateParams[]) {
814-
expect(payloads).toHaveLength(2)
815-
for (const payload of payloads) {
816-
expect(payload.thinking).toEqual({ type: 'between_tools' })
817-
expect(payload.output_config).toBeUndefined()
818-
}
819-
}
820-
821-
it('sends bare between_tools on every non-streaming turn', async () => {
770+
/** Runs a tool exchange and returns every request body sent over the SDK boundary. */
771+
async function runToolExchange(model: string, streaming: boolean) {
822772
mockExecuteTool.mockResolvedValue({ success: true, output: { value: 'tool result' } })
823-
const create = vi.fn().mockResolvedValueOnce(toolTurn).mockResolvedValueOnce(answerTurn)
824-
825-
await executeAnthropicProviderRequest(
773+
const sent: Anthropic.Messages.MessageCreateParams[] = []
774+
const nextTurn = (payload: Anthropic.Messages.MessageCreateParams) => {
775+
sent.push(payload)
776+
return turns[sent.length - 1]
777+
}
778+
const result = await executeAnthropicProviderRequest(
826779
{
827-
model: 'claude-sonnet-5-5',
780+
model,
828781
apiKey: 'test-key',
782+
stream: streaming,
829783
maxTokens: 1024,
830784
thinkingLevel: 'none',
831785
agentEvents: true,
@@ -835,43 +789,42 @@ describe('streaming', () => {
835789
{
836790
providerId: 'anthropic',
837791
providerLabel: 'Anthropic',
838-
createClient: () => ({ messages: { create } }) as never,
792+
createClient: () =>
793+
({
794+
messages: streaming
795+
? {
796+
stream: (payload: never) =>
797+
stream([{ type: 'message_stop' }], nextTurn(payload)),
798+
}
799+
: { create: async (payload: never) => nextTurn(payload) },
800+
}) as never,
839801
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
840802
}
841803
)
804+
if (streaming) await collectEvents(result as StreamingExecution)
805+
return sent
806+
}
842807

843-
expectBareBetweenTools(create.mock.calls.map((call) => call[0]))
844-
})
845-
846-
it('sends bare between_tools on every streaming turn', async () => {
847-
mockExecuteTool.mockResolvedValue({ success: true, output: { value: 'tool result' } })
848-
const createStream = vi
849-
.fn()
850-
.mockReturnValueOnce(stream([{ type: 'message_stop' }], toolTurn))
851-
.mockReturnValueOnce(stream([{ type: 'message_stop' }], answerTurn))
852-
853-
const result = (await executeAnthropicProviderRequest(
854-
{
855-
model: 'claude-sonnet-5-5',
856-
apiKey: 'test-key',
857-
stream: true,
858-
maxTokens: 1024,
859-
thinkingLevel: 'none',
860-
agentEvents: true,
861-
messages: [{ role: 'user', content: 'Look this up' }],
862-
tools: [lookupTool],
863-
},
864-
{
865-
providerId: 'anthropic',
866-
providerLabel: 'Anthropic',
867-
createClient: () => ({ messages: { stream: createStream } }) as never,
868-
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
808+
it.each([false, true])(
809+
'sends bare between_tools on every turn (streaming: %s)',
810+
async (streaming) => {
811+
const sent = await runToolExchange('claude-sonnet-5-5', streaming)
812+
expect(sent).toHaveLength(2)
813+
for (const payload of sent) {
814+
expect(payload.thinking).toEqual({ type: 'between_tools' })
815+
expect(payload.output_config).toBeUndefined()
869816
}
870-
)) as StreamingExecution
871-
await collectEvents(result)
817+
}
818+
)
872819

873-
expectBareBetweenTools(createStream.mock.calls.map((call) => call[0]))
874-
})
820+
it.each(['claude-sonnet-5', 'claude-opus-5-5'])(
821+
'sends no thinking config on %s',
822+
async (model) => {
823+
const sent = await runToolExchange(model, false)
824+
expect(sent).toHaveLength(2)
825+
for (const payload of sent) expect(payload.thinking).toBeUndefined()
826+
}
827+
)
875828
})
876829
})
877830

0 commit comments

Comments
 (0)