mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-23 20:40:41 -07:00
feat(teleportation): add missing module files
This commit is contained in:
81
packages/core/src/teleportation/TELEPORTATION.md
Normal file
81
packages/core/src/teleportation/TELEPORTATION.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Trajectory Teleportation: Antigravity to Gemini CLI
|
||||
|
||||
This document explains how the Gemini CLI discovers, reads, and converts
|
||||
Antigravity (Jetski) trajectories to enable session resumption.
|
||||
|
||||
## Overview
|
||||
|
||||
The teleportation feature allows you to pick up a conversation in the Gemini CLI
|
||||
that was started in the Antigravity (Jetski) IDE.
|
||||
|
||||
## 1. Discovery
|
||||
|
||||
The CLI identifies Antigravity sessions by scanning the local filesystem.
|
||||
|
||||
- **Storage Location**: `~/.antigravity/conversations`
|
||||
- **File Format**: Binary Protobuf files with the `.pb` extension.
|
||||
- **Session IDs**: The filenames (e.g., `f81d4fae-7dec.pb`) serve as the unique
|
||||
identifiers for resumption.
|
||||
|
||||
## 2. Decryption & Parsing
|
||||
|
||||
Since Antigravity stores data in a specialized binary format, the CLI uses a
|
||||
dedicated teleporter bundle:
|
||||
|
||||
- **Logic**: `trajectory_teleporter.min.js` (bundled in
|
||||
`@google/gemini-cli-core`).
|
||||
- **Process**: The binary `.pb` file is read into a Buffer and passed to the
|
||||
teleporter's `trajectoryToJson` function, which outputs a standard JavaScript
|
||||
object.
|
||||
|
||||
## 3. Conversion Logic
|
||||
|
||||
The conversion layer
|
||||
([converter.ts](file:///Users/sshon/developments/gemini-cli/packages/core/src/teleportation/converter.ts))
|
||||
translates the technical "Steps" of an Antigravity trajectory into the CLI's
|
||||
`ConversationRecord` format:
|
||||
|
||||
- **User Input**: Maps `CORTEX_STEP_TYPE_USER_INPUT` (type 14) to `user`
|
||||
messages.
|
||||
- **Model Responses**: Maps `CORTEX_STEP_TYPE_PLANNER_RESPONSE` (type 15) to
|
||||
`gemini` messages.
|
||||
- **Thoughts & Reasoning**: Extracts reasoning content from the Antigravity step
|
||||
and populates the `thoughts` array in the CLI record, preserving the model's
|
||||
logic.
|
||||
- **Tool Calls**: Maps Antigravity tool execution steps to CLI `ToolCallRecord`
|
||||
objects, including status mapping (Success/Error) and argument parsing.
|
||||
|
||||
## 4. Session Resumption
|
||||
|
||||
Once converted:
|
||||
|
||||
1. The record is injected into the CLI's `ChatRecordingService`.
|
||||
2. Users can continue the conversation seamlessly via the `/chat resume`
|
||||
command.
|
||||
|
||||
## Maintenance & Updates
|
||||
|
||||
You are correct that if Antigravity's Protobuf definitions change, the
|
||||
`trajectory_teleporter.min.js` bundle will need to be updated to maintain
|
||||
compatibility.
|
||||
|
||||
### When to Update
|
||||
|
||||
- If new step types are added to Antigravity that the CLI should support.
|
||||
- If the binary format of the `.pb` files changes.
|
||||
- If the encryption key or algorithm is rotated.
|
||||
|
||||
### How to Regenerate the Bundle
|
||||
|
||||
To keep the CLI up to date:
|
||||
|
||||
1. Update `trajectory_teleporter.ts` in the Antigravity workspace.
|
||||
2. Re-bundle using `esbuild` or a similar tool to produce a new
|
||||
`trajectory_teleporter.min.js`.
|
||||
3. Copy the new `.min.js` into `packages/core/src/teleportation/`.
|
||||
4. Rebuild the Gemini CLI.
|
||||
|
||||
> [!TIP] In the long term, this logic could be moved to a shared NPM package
|
||||
> published from the Antigravity repository, allowing the Gemini CLI to stay
|
||||
> updated via simple `npm update`. 3. Users can continue the conversation
|
||||
> seamlessly via the `/chat resume` command.
|
||||
189
packages/core/src/teleportation/converter.ts
Normal file
189
packages/core/src/teleportation/converter.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import {
|
||||
type ConversationRecord,
|
||||
type MessageRecord,
|
||||
type ToolCallRecord,
|
||||
} from '../services/chatRecordingService.js';
|
||||
import { CoreToolCallStatus } from '../scheduler/types.js';
|
||||
|
||||
/**
|
||||
* Converts an Antigravity Trajectory JSON to a Gemini CLI ConversationRecord.
|
||||
*/
|
||||
export function convertAgyToCliRecord(agyJson: unknown): ConversationRecord {
|
||||
if (typeof agyJson !== 'object' || agyJson === null) {
|
||||
throw new Error('Invalid AGY JSON');
|
||||
}
|
||||
const json = agyJson as Record<string, unknown>;
|
||||
const messages: MessageRecord[] = [];
|
||||
const sessionId = (json['trajectoryId'] as string) || 'agy-session';
|
||||
const startTime = new Date().toISOString(); // Default to now if not found
|
||||
|
||||
let currentGeminiMessage: (MessageRecord & { type: 'gemini' }) | null = null;
|
||||
|
||||
const steps = (json['steps'] as any[]) || [];
|
||||
|
||||
for (const step of steps) {
|
||||
const s = step as Record<string, unknown>;
|
||||
const metadata = s['metadata'] as Record<string, unknown> | undefined;
|
||||
const timestamp =
|
||||
(metadata?.['timestamp'] as string) || new Date().toISOString();
|
||||
const stepId =
|
||||
(metadata?.['stepId'] as string) ||
|
||||
Math.random().toString(36).substring(7);
|
||||
|
||||
switch (s['type']) {
|
||||
case 14: // CORTEX_STEP_TYPE_USER_INPUT
|
||||
case 'CORTEX_STEP_TYPE_USER_INPUT': {
|
||||
// Close current Gemini message if open
|
||||
currentGeminiMessage = null;
|
||||
const userInput = s['userInput'] as Record<string, unknown> | undefined;
|
||||
messages.push({
|
||||
id: stepId,
|
||||
timestamp,
|
||||
type: 'user',
|
||||
content: [{ text: (userInput?.['userResponse'] as string) || '' }],
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 15: // CORTEX_STEP_TYPE_PLANNER_RESPONSE
|
||||
case 'CORTEX_STEP_TYPE_PLANNER_RESPONSE': {
|
||||
const plannerResponse = s['plannerResponse'] as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
const response = plannerResponse?.['response'] || '';
|
||||
const thinking = plannerResponse?.['thinking'] || '';
|
||||
currentGeminiMessage = {
|
||||
id: stepId,
|
||||
timestamp,
|
||||
type: 'gemini',
|
||||
content: [{ text: response as string }],
|
||||
thoughts: thinking
|
||||
? [
|
||||
{
|
||||
subject: 'Thinking',
|
||||
description: thinking as string,
|
||||
timestamp,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
toolCalls: [],
|
||||
};
|
||||
messages.push(currentGeminiMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
case 7: // CORTEX_STEP_TYPE_GREP_SEARCH
|
||||
case 'CORTEX_STEP_TYPE_GREP_SEARCH':
|
||||
case 8: // CORTEX_STEP_TYPE_VIEW_FILE
|
||||
case 'CORTEX_STEP_TYPE_VIEW_FILE':
|
||||
case 9: // CORTEX_STEP_TYPE_LIST_DIRECTORY
|
||||
case 'CORTEX_STEP_TYPE_LIST_DIRECTORY':
|
||||
case 21: // CORTEX_STEP_TYPE_RUN_COMMAND
|
||||
case 'CORTEX_STEP_TYPE_RUN_COMMAND':
|
||||
case 85: // CORTEX_STEP_TYPE_BROWSER_SUBAGENT
|
||||
case 'CORTEX_STEP_TYPE_BROWSER_SUBAGENT':
|
||||
case 86: // CORTEX_STEP_TYPE_FILE_CHANGE
|
||||
case 'CORTEX_STEP_TYPE_FILE_CHANGE': {
|
||||
if (!currentGeminiMessage) {
|
||||
// If no planner response preceded this, create a dummy one
|
||||
const adjunctMessage: MessageRecord = {
|
||||
id: `adjunct-${stepId}`,
|
||||
timestamp,
|
||||
type: 'gemini',
|
||||
content: [],
|
||||
toolCalls: [],
|
||||
thoughts: [],
|
||||
};
|
||||
messages.push(adjunctMessage);
|
||||
currentGeminiMessage = adjunctMessage as MessageRecord & {
|
||||
type: 'gemini';
|
||||
};
|
||||
}
|
||||
|
||||
if (currentGeminiMessage) {
|
||||
currentGeminiMessage.toolCalls?.push(mapAgyStepToToolCall(s));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Skip unknown steps
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
projectHash: 'agy-imported',
|
||||
startTime,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
messages,
|
||||
};
|
||||
}
|
||||
|
||||
function mapAgyStepToToolCall(step: Record<string, any>): ToolCallRecord {
|
||||
const timestamp =
|
||||
(step['metadata']?.['timestamp'] as string) || new Date().toISOString();
|
||||
const id =
|
||||
(step['metadata']?.['stepId'] as string) ||
|
||||
Math.random().toString(36).substring(7);
|
||||
let name = 'unknown_tool';
|
||||
let args: any = {};
|
||||
let result: any = null;
|
||||
|
||||
if (step['viewFile']) {
|
||||
name = 'view_file';
|
||||
args = { AbsolutePath: step['viewFile']['absolutePathUri'] };
|
||||
result = [{ text: step['viewFile']['content'] || '' }];
|
||||
} else if (step['listDirectory']) {
|
||||
name = 'list_dir';
|
||||
args = { DirectoryPath: step['listDirectory']['directoryPathUri'] };
|
||||
} else if (step['grepSearch']) {
|
||||
name = 'grep_search';
|
||||
args = {
|
||||
Query: step['grepSearch']['query'],
|
||||
SearchPath: step['grepSearch']['searchPathUri'],
|
||||
};
|
||||
result = [{ text: step['grepSearch']['rawOutput'] || '' }];
|
||||
} else if (step['runCommand']) {
|
||||
name = 'run_command';
|
||||
args = { CommandLine: step['runCommand']['commandLine'] };
|
||||
result = [{ text: step['runCommand']['combinedOutput']?.['full'] || '' }];
|
||||
} else if (step['fileChange']) {
|
||||
name = 'replace_file_content'; // Or multi_replace_file_content
|
||||
args = { TargetFile: step['fileChange']['absolutePathUri'] };
|
||||
} else if (step['browserSubagent']) {
|
||||
name = 'browser_subagent';
|
||||
args = { Task: step['browserSubagent']['task'] };
|
||||
} else if (step['generic']) {
|
||||
const generic = step['generic'] as Record<string, unknown>;
|
||||
name = generic['toolName'] as string;
|
||||
try {
|
||||
args = JSON.parse(generic['argsJson'] as string);
|
||||
} catch {
|
||||
args = {};
|
||||
}
|
||||
result = [{ text: (generic['responseJson'] as string) || '' }];
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
args: args as Record<string, unknown>,
|
||||
result,
|
||||
status:
|
||||
step['status'] === 3 || step['status'] === 'CORTEX_STEP_STATUS_DONE'
|
||||
? CoreToolCallStatus.Success
|
||||
: CoreToolCallStatus.Error,
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
34254
packages/core/src/teleportation/trajectory_teleporter.min.js
vendored
Normal file
34254
packages/core/src/teleportation/trajectory_teleporter.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user