Repository navigation
fix(cache): preserve tools prefix in side-query for Anthropic prompt-cache hits #6225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
0ff640a
fa04bea
9aaa329
2ab6f80
7405fed
8d53a0e
4f2e89f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -156,6 +156,7 @@ async function generateViaForkedQuery( | |
| cacheSafeParams, | ||
| jsonSchema: SUGGESTION_SCHEMA, | ||
| model, | ||
| preserveTools: true, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in 8d53a0e:
All 49 tests pass. |
||
| }); | ||
|
|
||
| if (result.jsonResult) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -612,6 +612,263 @@ describe('runForkedAgent (cache path)', () => { | |
| expect(createRuntimeContentGeneratorView).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('does not strip tools when preserveTools is true', async () => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in 2ab6f80 — new test
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in 8d53a0e — new test The test:
All 49 tests pass (18 forkedAgent.cache + 21 suggestionGenerator + 10 speculation). |
||
| saveCacheSafeParams( | ||
| { | ||
| systemInstruction: 'You are helpful', | ||
| tools: [ | ||
| { | ||
| functionDeclarations: [ | ||
| { name: 'edit', description: 'Edit a file' }, | ||
| { name: 'shell', description: 'Run a command' }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| [{ role: 'user', parts: [{ text: 'hello' }] }], | ||
| 'test-model', | ||
| ); | ||
|
|
||
| let capturedParams: unknown = null; | ||
|
|
||
| const mockSendMessageStream = vi.fn( | ||
| (_model: string, params: unknown, _promptId: string) => { | ||
| capturedParams = params; | ||
| async function* generate() { | ||
| yield { | ||
| type: StreamEventType.CHUNK, | ||
| value: { | ||
| candidates: [ | ||
| { | ||
| content: { | ||
| role: 'model', | ||
| parts: [{ text: '{"suggestion":"run tests"}' }], | ||
| }, | ||
| }, | ||
| ], | ||
| usageMetadata: { | ||
| promptTokenCount: 10, | ||
| candidatesTokenCount: 5, | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
| return Promise.resolve(generate()); | ||
| }, | ||
| ); | ||
|
|
||
| vi.mocked(GeminiChat).mockImplementation( | ||
| () => | ||
| ({ | ||
| sendMessageStream: mockSendMessageStream, | ||
| }) as unknown as GeminiChat, | ||
| ); | ||
|
|
||
| await runForkedAgent({ | ||
| config: {} as Config, | ||
| userMessage: 'suggest something', | ||
| cacheSafeParams: getCacheSafeParams()!, | ||
| preserveTools: true, | ||
| }); | ||
|
|
||
| const sendParams = capturedParams as { | ||
| config?: { tools?: unknown }; | ||
| }; | ||
| expect(sendParams.config!.tools).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('strips tools when preserveTools is explicitly false', async () => { | ||
| saveCacheSafeParams( | ||
| { | ||
| tools: [ | ||
| { | ||
| functionDeclarations: [ | ||
| { name: 'edit', description: 'Edit a file' }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| [], | ||
| 'test-model', | ||
| ); | ||
|
|
||
| let capturedParams: unknown = null; | ||
|
|
||
| const mockSendMessageStream = vi.fn( | ||
| (_model: string, params: unknown, _promptId: string) => { | ||
| capturedParams = params; | ||
| async function* generate() { | ||
| yield { | ||
| type: StreamEventType.CHUNK, | ||
| value: { | ||
| candidates: [ | ||
| { | ||
| content: { | ||
| role: 'model', | ||
| parts: [{ text: 'ok' }], | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
| } | ||
| return Promise.resolve(generate()); | ||
| }, | ||
| ); | ||
|
|
||
| vi.mocked(GeminiChat).mockImplementation( | ||
| () => | ||
| ({ | ||
| sendMessageStream: mockSendMessageStream, | ||
| }) as unknown as GeminiChat, | ||
| ); | ||
|
|
||
| await runForkedAgent({ | ||
| config: {} as Config, | ||
| userMessage: 'suggest something', | ||
| cacheSafeParams: getCacheSafeParams()!, | ||
| preserveTools: false, | ||
| }); | ||
|
|
||
| const sendParams = capturedParams as { | ||
| config?: { tools?: unknown }; | ||
| }; | ||
| expect(sendParams.config!.tools).toEqual([]); | ||
| }); | ||
|
|
||
| it('filters out functionCall parts when preserveTools is true', async () => { | ||
| saveCacheSafeParams( | ||
| { | ||
| systemInstruction: 'You are helpful', | ||
| tools: [ | ||
| { | ||
| functionDeclarations: [ | ||
| { name: 'edit', description: 'Edit a file' }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| [], | ||
| 'test-model', | ||
| ); | ||
|
|
||
| const mockSendMessageStream = vi.fn( | ||
| (_model: string, _params: unknown, _promptId: string) => { | ||
| async function* generate() { | ||
| yield { | ||
| type: StreamEventType.CHUNK, | ||
| value: { | ||
| candidates: [ | ||
| { | ||
| content: { | ||
| role: 'model', | ||
| parts: [ | ||
| { text: 'some text' }, | ||
| { | ||
| functionCall: { | ||
| name: 'edit', | ||
| args: { file: 'a.ts' }, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| ], | ||
| usageMetadata: { | ||
| promptTokenCount: 10, | ||
| candidatesTokenCount: 5, | ||
| totalTokenCount: 15, | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
| return Promise.resolve(generate()); | ||
| }, | ||
| ); | ||
|
|
||
| vi.mocked(GeminiChat).mockImplementation( | ||
| () => | ||
| ({ | ||
| sendMessageStream: mockSendMessageStream, | ||
| }) as unknown as GeminiChat, | ||
| ); | ||
|
|
||
| const result = await runForkedAgent({ | ||
| config: {} as Config, | ||
| userMessage: 'suggest something', | ||
| cacheSafeParams: getCacheSafeParams()!, | ||
| preserveTools: true, | ||
| }); | ||
|
|
||
| expect(result.text).toBe('some text'); | ||
| }); | ||
|
|
||
| it('returns null text when response contains only functionCall parts', async () => { | ||
| saveCacheSafeParams( | ||
| { | ||
| systemInstruction: 'You are helpful', | ||
| tools: [ | ||
| { | ||
| functionDeclarations: [ | ||
| { name: 'edit', description: 'Edit a file' }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| [], | ||
| 'test-model', | ||
| ); | ||
|
|
||
| const mockSendMessageStream = vi.fn( | ||
| (_model: string, _params: unknown, _promptId: string) => { | ||
| async function* generate() { | ||
| yield { | ||
| type: StreamEventType.CHUNK, | ||
| value: { | ||
| candidates: [ | ||
| { | ||
| content: { | ||
| role: 'model', | ||
| parts: [ | ||
| { | ||
| functionCall: { | ||
| name: 'edit', | ||
| args: { file: 'a.ts' }, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| ], | ||
| usageMetadata: { | ||
| promptTokenCount: 10, | ||
| candidatesTokenCount: 5, | ||
| totalTokenCount: 15, | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
| return Promise.resolve(generate()); | ||
| }, | ||
| ); | ||
|
|
||
| vi.mocked(GeminiChat).mockImplementation( | ||
| () => | ||
| ({ | ||
| sendMessageStream: mockSendMessageStream, | ||
| }) as unknown as GeminiChat, | ||
| ); | ||
|
|
||
| const result = await runForkedAgent({ | ||
| config: {} as Config, | ||
| userMessage: 'suggest something', | ||
| cacheSafeParams: getCacheSafeParams()!, | ||
| preserveTools: true, | ||
| }); | ||
|
|
||
| expect(result.text).toBeNull(); | ||
| }); | ||
|
|
||
| it('throws when CacheSafeParams are not available', async () => { | ||
| const mockConfig = {} as unknown as Config; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 2ab6f80 — new test
'passes preserveTools: true to runForkedAgent'inspeculation.test.ts. The test runs a text-only speculation to completion (triggeringgeneratePipelinedSuggestion), then assertsrunForkedAgentwas called withpreserveTools: true. All 9 tests in the file pass.