feat(antigravity): add thought signature support for multi-turn conversations

Gemini 3 Pro requires thoughtSignature on function call blocks in
subsequent requests. This commit:

- Add thought-signature-store for session-based signature storage
- Extract signature from both streaming (SSE) and non-streaming responses
- Inject signature into functionCall parts on subsequent requests
- Maintain consistent sessionId per fetch instance

Debug logging available via ANTIGRAVITY_DEBUG=1

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-13 01:16:53 +09:00
parent 3d273ff853
commit 5fd59afacf
5 changed files with 358 additions and 75 deletions

View File

@@ -465,6 +465,61 @@ export function isStreamingResponse(response: Response): boolean {
return contentType.includes("text/event-stream")
}
/**
* Extract thought signature from SSE payload text
*
* Looks for thoughtSignature in SSE events:
* data: { "response": { "candidates": [{ "content": { "parts": [{ "thoughtSignature": "..." }] } }] } }
*
* Returns the last found signature (most recent in the stream).
*
* @param payload - SSE payload text
* @returns Last thought signature if found
*/
export function extractSignatureFromSsePayload(payload: string): string | undefined {
const lines = payload.split("\n")
let lastSignature: string | undefined
for (const line of lines) {
if (!line.startsWith("data:")) {
continue
}
const json = line.slice(5).trim()
if (!json || json === "[DONE]") {
continue
}
try {
const parsed = JSON.parse(json) as Record<string, unknown>
// Check in response wrapper (Antigravity format)
const response = (parsed.response || parsed) as Record<string, unknown>
const candidates = response.candidates as Array<Record<string, unknown>> | undefined
if (candidates && Array.isArray(candidates)) {
for (const candidate of candidates) {
const content = candidate.content as Record<string, unknown> | undefined
const parts = content?.parts as Array<Record<string, unknown>> | undefined
if (parts && Array.isArray(parts)) {
for (const part of parts) {
const sig = (part.thoughtSignature || part.thought_signature) as string | undefined
if (sig && typeof sig === "string") {
lastSignature = sig
}
}
}
}
}
} catch {
// Continue to next line if parsing fails
}
}
return lastSignature
}
/**
* Extract usage from SSE payload text
*