@ai-sdk/google
Version:
The **[Google Generative AI provider](https://ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai)** for the [AI SDK](https://ai-sdk.dev/docs) contains language model support for the [Google Generative AI](https://ai.google/discover/generativeai/)
186 lines (172 loc) • 6.01 kB
text/typescript
import type {
LanguageModelV3FinishReason,
LanguageModelV3StreamPart,
SharedV3ProviderMetadata,
SharedV3Warning,
} from '@ai-sdk/provider';
import { convertGoogleInteractionsUsage } from './convert-google-interactions-usage';
import { type GoogleInteractionsResponse } from './google-interactions-api';
import { mapGoogleInteractionsFinishReason } from './map-google-interactions-finish-reason';
import { parseGoogleInteractionsOutputs } from './parse-google-interactions-outputs';
/**
* Synthesizes a `LanguageModelV3StreamPart` stream from a fully-resolved
* Interaction response (i.e. the `response` returned after polling a
* `background: true` agent call to a terminal status).
*
* Agent calls cannot use SSE (`stream: true` is incompatible with
* `background: true`), so we deterministically replay the polled outputs as a
* stream sequence in the same order/shape `buildGoogleInteractionsStreamTransform`
* would produce. Each text/reasoning block is emitted as a single delta — the
* server has already produced the whole block by the time we synthesize.
*/
export function synthesizeGoogleInteractionsAgentStream({
response,
warnings,
generateId,
includeRawChunks,
headerServiceTier,
}: {
response: GoogleInteractionsResponse;
warnings: Array<SharedV3Warning>;
generateId: () => string;
includeRawChunks?: boolean;
headerServiceTier?: string;
}): ReadableStream<LanguageModelV3StreamPart> {
return new ReadableStream<LanguageModelV3StreamPart>({
start(controller) {
controller.enqueue({ type: 'stream-start', warnings });
const interactionId =
typeof response.id === 'string' && response.id.length > 0
? response.id
: undefined;
let timestamp: Date | undefined;
const created = response.created;
if (typeof created === 'string') {
const parsed = new Date(created);
if (!Number.isNaN(parsed.getTime())) {
timestamp = parsed;
}
}
controller.enqueue({
type: 'response-metadata',
...(interactionId != null ? { id: interactionId } : {}),
modelId: response.model ?? undefined,
...(timestamp ? { timestamp } : {}),
});
if (includeRawChunks) {
controller.enqueue({ type: 'raw', rawValue: response });
}
const { content, hasFunctionCall } = parseGoogleInteractionsOutputs({
steps: response.steps ?? null,
generateId,
interactionId,
});
let blockCounter = 0;
const nextBlockId = () => `${interactionId ?? 'agent'}:${blockCounter++}`;
for (const part of content) {
switch (part.type) {
case 'text': {
const id = nextBlockId();
const providerMetadata = part.providerMetadata;
controller.enqueue({ type: 'text-start', id });
if (part.text.length > 0) {
controller.enqueue({ type: 'text-delta', id, delta: part.text });
}
controller.enqueue({
type: 'text-end',
id,
...(providerMetadata ? { providerMetadata } : {}),
});
break;
}
case 'reasoning': {
const id = nextBlockId();
const providerMetadata = part.providerMetadata;
controller.enqueue({ type: 'reasoning-start', id });
if (part.text.length > 0) {
controller.enqueue({
type: 'reasoning-delta',
id,
delta: part.text,
});
}
controller.enqueue({
type: 'reasoning-end',
id,
...(providerMetadata ? { providerMetadata } : {}),
});
break;
}
case 'tool-call': {
const providerMetadata = part.providerMetadata;
controller.enqueue({
type: 'tool-input-start',
id: part.toolCallId,
toolName: part.toolName,
...(part.providerExecuted
? { providerExecuted: part.providerExecuted }
: {}),
});
controller.enqueue({
type: 'tool-input-delta',
id: part.toolCallId,
delta: part.input,
});
controller.enqueue({
type: 'tool-input-end',
id: part.toolCallId,
});
controller.enqueue({
type: 'tool-call',
toolCallId: part.toolCallId,
toolName: part.toolName,
input: part.input,
...(part.providerExecuted
? { providerExecuted: part.providerExecuted }
: {}),
...(providerMetadata ? { providerMetadata } : {}),
});
break;
}
case 'tool-result': {
controller.enqueue({
type: 'tool-result',
toolCallId: part.toolCallId,
toolName: part.toolName,
result: part.result,
});
break;
}
case 'source':
case 'file': {
controller.enqueue(part);
break;
}
default:
break;
}
}
const serviceTier = response.service_tier ?? headerServiceTier;
const finishReason: LanguageModelV3FinishReason = {
unified: mapGoogleInteractionsFinishReason({
status: response.status,
hasFunctionCall,
}),
raw: response.status,
};
const providerMetadata: SharedV3ProviderMetadata = {
google: {
...(interactionId != null ? { interactionId } : {}),
...(serviceTier != null ? { serviceTier } : {}),
},
};
controller.enqueue({
type: 'finish',
finishReason,
usage: convertGoogleInteractionsUsage(response.usage),
providerMetadata,
});
controller.close();
},
});
}