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
78 changes: 66 additions & 12 deletions core/src/sessions/vertex_ai_session_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import {Client} from '@google-cloud/vertexai/build/src/genai/client.js';
import {Sessions} from '@google-cloud/vertexai/build/src/genai/sessions.js';
import {
EventActions as ApiEventActions,
AppendAgentEngineSessionEventConfig,
AppendAgentEngineSessionEventRequestParameters,
EventMetadata,
Expand Down Expand Up @@ -40,6 +41,7 @@ import {createSession, Session} from './session.js';
const DEFAULT_MAX_ATTEMPTS = 30;
const GRPC_NOT_FOUND = 5;
const HTTP_NOT_FOUND = 404;
const HTTP_BAD_REQUEST = 400;

/**
* Checks if the given URI is a Vertex AI session service URI.
Expand Down Expand Up @@ -431,12 +433,16 @@ export class VertexAiSessionService extends BaseSessionService {
}

const config = partialCopy<AppendAgentEngineSessionEventConfig>(event, [
'content',
'actions',
'errorCode',
'errorMessage',
]);

const content = event.content && dropUnsupportedPartFields(event.content);
config.content = content;
config.actions = event.actions
? toApiEventActions(event.actions)
: undefined;

config.eventMetadata = {
...partialCopy<EventMetadata>(event, [
'partial',
Expand All @@ -450,7 +456,7 @@ export class VertexAiSessionService extends BaseSessionService {
Object.keys(customMetadata).length > 0 ? customMetadata : undefined,
};

config.rawEvent = JSON.parse(JSON.stringify(event)) as Record<
config.rawEvent = JSON.parse(JSON.stringify({...event, content})) as Record<
string,
unknown
>;
Expand All @@ -466,24 +472,66 @@ export class VertexAiSessionService extends BaseSessionService {
try {
await this.sessions.events.append(params);
} catch (error) {
if (!isInvalidArgumentError(error)) {
throw error;
}
logger.warn(
'Failed to append event with rawEvent, falling back...',
'appendEvent was rejected with rawEvent; retrying without it.',
error,
);
delete config.rawEvent;
await this.sessions.events.append({
name: `reasoningEngines/${reasoningEngineId}/sessions/${session.id}`,
author: event.author || 'user',
invocationId: event.invocationId || `inv-${Date.now()}`,
timestamp: new Date(event.timestamp).toISOString(),
config,
});
await this.sessions.events.append(params);
}

return event;
}
}

/**
* Returns a copy of `content` without Part fields the Agent Engine Sessions
* API rejects.
*
* `partMetadata` is a Gemini Developer API-only field; the Sessions API fails
* appendEvent with 400 INVALID_ARGUMENT ("Unknown name \"part_metadata\"").
*/
function dropUnsupportedPartFields(content: Content): Content {
if (!content.parts) {
return content;
}
return {
...content,
parts: content.parts.map((part) => {
const copy = {...part};
delete copy.partMetadata;
return copy;
}),
};
}

/**
* Maps ADK `EventActions` onto the Sessions API wire shape. ADK's
* `transferToAgent` is the API's `transferAgent` (adk-python writes the same
* field as `transfer_agent`); every other field keeps its name, including
* `requestedToolConfirmations`, which the SDK type omits but `_fromApiEvent`
* reads back.
*/
function toApiEventActions(actions: EventActions): ApiEventActions {
const {transferToAgent, ...rest} = actions;
return {...rest, transferAgent: transferToAgent};
}

/**
* True when the service rejected the request payload itself, which is what an
* API that does not know `rawEvent` returns. Any other failure must propagate:
* the event may already be persisted, so retrying would append it twice.
*
* Matched structurally on the `ApiError`'s `status`, for the reason given in
* getSession's catch.
*/
function isInvalidArgumentError(error: unknown): boolean {
return (error as {status?: number} | null)?.status === HTTP_BAD_REQUEST;
}

interface ExtendedEventActions extends EventActions {
compaction?: {
startTime: number;
Expand Down Expand Up @@ -554,7 +602,12 @@ function _fromApiEvent(apiEventObj: VertexAiSessionEvent): Event {
'requestedToolConfirmations'
] as Record<string, ToolConfirmation>) || {},
skipSummarization: actions['skipSummarization'] as boolean | undefined,
transferToAgent: actions['transferAgent'] as string | undefined,
// Earlier adk-js versions copied `event.actions` onto the request
// verbatim, so sessions they wrote store ADK's own `transferToAgent` key.
transferToAgent: (actions['transferAgent'] ??
(actions as Record<string, unknown>)['transferToAgent']) as
| string
| undefined,
escalate: actions['escalate'] as boolean | undefined,
compaction: compactionData || undefined,
};
Expand All @@ -574,6 +627,7 @@ function _fromApiEvent(apiEventObj: VertexAiSessionEvent): Event {
turnComplete: eventMetadata['turnComplete'] as boolean | undefined,
interrupted: eventMetadata['interrupted'] as boolean | undefined,
branch: eventMetadata['branch'] as string | undefined,
groundingMetadata: eventMetadata.groundingMetadata,
customMetadata,
longRunningToolIds: eventMetadata['longRunningToolIds'] as
| string[]
Expand Down
Loading
Loading