antigravity-chat-proxy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +362 -0
- package/app/api/v1/artifacts/[convId]/[filename]/route.ts +75 -0
- package/app/api/v1/artifacts/[convId]/route.ts +47 -0
- package/app/api/v1/artifacts/active/[filename]/route.ts +50 -0
- package/app/api/v1/artifacts/active/route.ts +89 -0
- package/app/api/v1/artifacts/route.ts +43 -0
- package/app/api/v1/chat/action/route.ts +30 -0
- package/app/api/v1/chat/approve/route.ts +21 -0
- package/app/api/v1/chat/history/route.ts +23 -0
- package/app/api/v1/chat/mode/route.ts +59 -0
- package/app/api/v1/chat/new/route.ts +21 -0
- package/app/api/v1/chat/reject/route.ts +21 -0
- package/app/api/v1/chat/route.ts +105 -0
- package/app/api/v1/chat/state/route.ts +23 -0
- package/app/api/v1/chat/stream/route.ts +258 -0
- package/app/api/v1/conversations/active/route.ts +117 -0
- package/app/api/v1/conversations/route.ts +189 -0
- package/app/api/v1/conversations/select/route.ts +114 -0
- package/app/api/v1/debug/dom/route.ts +30 -0
- package/app/api/v1/debug/scrape/route.ts +56 -0
- package/app/api/v1/health/route.ts +13 -0
- package/app/api/v1/windows/cdp-start/route.ts +32 -0
- package/app/api/v1/windows/cdp-status/route.ts +32 -0
- package/app/api/v1/windows/close/route.ts +67 -0
- package/app/api/v1/windows/open/route.ts +49 -0
- package/app/api/v1/windows/recent/route.ts +25 -0
- package/app/api/v1/windows/route.ts +27 -0
- package/app/api/v1/windows/select/route.ts +35 -0
- package/app/debug/page.tsx +228 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +1234 -0
- package/app/layout.tsx +42 -0
- package/app/page.tsx +10 -0
- package/bin/cli.js +601 -0
- package/components/agent-message.tsx +63 -0
- package/components/artifact-panel.tsx +133 -0
- package/components/chat-container.tsx +82 -0
- package/components/chat-input.tsx +92 -0
- package/components/conversation-selector.tsx +97 -0
- package/components/header.tsx +302 -0
- package/components/hitl-dialog.tsx +23 -0
- package/components/message-list.tsx +41 -0
- package/components/thinking-block.tsx +14 -0
- package/components/tool-call-card.tsx +75 -0
- package/components/typing-indicator.tsx +11 -0
- package/components/user-message.tsx +13 -0
- package/components/welcome-screen.tsx +38 -0
- package/hooks/use-artifacts.ts +85 -0
- package/hooks/use-chat.ts +278 -0
- package/hooks/use-conversations.ts +190 -0
- package/lib/actions/hitl.ts +113 -0
- package/lib/actions/new-chat.ts +116 -0
- package/lib/actions/send-message.ts +31 -0
- package/lib/actions/switch-conversation.ts +92 -0
- package/lib/cdp/connection.ts +95 -0
- package/lib/cdp/process-manager.ts +327 -0
- package/lib/cdp/recent-projects.ts +137 -0
- package/lib/cdp/selectors.ts +11 -0
- package/lib/context.ts +38 -0
- package/lib/init.ts +48 -0
- package/lib/logger.ts +32 -0
- package/lib/scraper/agent-mode.ts +122 -0
- package/lib/scraper/agent-state.ts +756 -0
- package/lib/scraper/chat-history.ts +138 -0
- package/lib/scraper/ide-conversations.ts +124 -0
- package/lib/sse/diff-states.ts +141 -0
- package/lib/types.ts +146 -0
- package/lib/utils.ts +7 -0
- package/next.config.ts +7 -0
- package/package.json +50 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/tsconfig.json +34 -0
package/lib/context.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared server-side context singleton.
|
|
3
|
+
*
|
|
4
|
+
* Module-level state persists across API route invocations in the same
|
|
5
|
+
* Node.js process (Next.js dev server keeps the process alive).
|
|
6
|
+
* This replaces the mutable `ctx` object passed through the old codebase.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ProxyContext } from './types';
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
// eslint-disable-next-line no-var
|
|
13
|
+
var __PROXY_CTX: ProxyContext | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ctx: ProxyContext = globalThis.__PROXY_CTX || {
|
|
17
|
+
workbenchPage: null,
|
|
18
|
+
browser: null,
|
|
19
|
+
allWorkbenches: [],
|
|
20
|
+
activeWindowIdx: 0,
|
|
21
|
+
activeConversationId: null,
|
|
22
|
+
activeTitle: null,
|
|
23
|
+
lastActionTimestamp: 0,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// In development, HMR clears module state but keeps globalThis.
|
|
27
|
+
// In production, Next.js server retains module state per Node process,
|
|
28
|
+
// but using globalThis ensures a single source of truth across all
|
|
29
|
+
// dynamic route executions.
|
|
30
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
31
|
+
globalThis.__PROXY_CTX = ctx;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getContext(): ProxyContext {
|
|
35
|
+
return ctx;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default ctx;
|
package/lib/init.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP initialization.
|
|
3
|
+
* Ensures the CDP connection is established before any API route runs.
|
|
4
|
+
* Called lazily on first API request.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { connectToWorkbench } from './cdp/connection';
|
|
8
|
+
import { logger } from './logger';
|
|
9
|
+
import ctx from './context';
|
|
10
|
+
|
|
11
|
+
let initialized = false;
|
|
12
|
+
let initPromise: Promise<void> | null = null;
|
|
13
|
+
|
|
14
|
+
export async function ensureCdpConnection(): Promise<void> {
|
|
15
|
+
if (initialized && ctx.workbenchPage) return;
|
|
16
|
+
|
|
17
|
+
if (!initPromise) {
|
|
18
|
+
initPromise = (async () => {
|
|
19
|
+
try {
|
|
20
|
+
await connectToWorkbench(ctx);
|
|
21
|
+
initialized = true;
|
|
22
|
+
|
|
23
|
+
// Auto-reconnect on IDE close/crash
|
|
24
|
+
ctx.browser?.on('disconnected', () => {
|
|
25
|
+
logger.error('[CDP] Browser disconnected. Will reconnect on next request.');
|
|
26
|
+
initialized = false;
|
|
27
|
+
initPromise = null;
|
|
28
|
+
ctx.workbenchPage = null;
|
|
29
|
+
ctx.browser = null;
|
|
30
|
+
ctx.allWorkbenches = [];
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
ctx.workbenchPage?.on('close', () => {
|
|
34
|
+
logger.warn('[CDP] Workbench page closed. Will reconnect on next request.');
|
|
35
|
+
initialized = false;
|
|
36
|
+
initPromise = null;
|
|
37
|
+
ctx.workbenchPage = null;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
} catch (e: any) {
|
|
41
|
+
logger.error('[CDP Init] Failed to connect:', e.message);
|
|
42
|
+
initPromise = null; // Allow retry
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await initPromise;
|
|
48
|
+
}
|
package/lib/logger.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
|
|
3
|
+
class Logger {
|
|
4
|
+
private level: LogLevel = 'info';
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.level = (process.env.LOG_LEVEL as LogLevel) || 'info';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private shouldLog(level: LogLevel): boolean {
|
|
11
|
+
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];
|
|
12
|
+
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
debug(message: string, meta?: any) {
|
|
16
|
+
if (this.shouldLog('debug')) console.debug(`[DEBUG] ${message}`, meta || '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
info(message: string, meta?: any) {
|
|
20
|
+
if (this.shouldLog('info')) console.info(`[INFO] ${message}`, meta || '');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
warn(message: string, meta?: any) {
|
|
24
|
+
if (this.shouldLog('warn')) console.warn(`[WARN] ${message}`, meta || '');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
error(message: string, meta?: any) {
|
|
28
|
+
if (this.shouldLog('error')) console.error(`[ERROR] ${message}`, meta || '');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const logger = new Logger();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent conversation mode scraper and switcher.
|
|
3
|
+
* Reads and sets the Plan/Fast mode in the Antigravity IDE via CDP.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { logger } from '@/lib/logger';
|
|
7
|
+
import { sleep } from '@/lib/utils';
|
|
8
|
+
import type { ProxyContext } from '../types';
|
|
9
|
+
|
|
10
|
+
export type AgentMode = 'planning' | 'fast';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read the current conversation mode from the IDE's agent panel DOM.
|
|
14
|
+
* Looks for the mode button's span text near the chat input.
|
|
15
|
+
*/
|
|
16
|
+
export async function getAgentMode(ctx: ProxyContext): Promise<AgentMode> {
|
|
17
|
+
if (!ctx.workbenchPage) throw new Error('Not connected to Antigravity');
|
|
18
|
+
|
|
19
|
+
const mode = await ctx.workbenchPage.evaluate(() => {
|
|
20
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
21
|
+
if (!panel) return null;
|
|
22
|
+
|
|
23
|
+
// The mode button contains a <span class="text-xs select-none"> with text "Planning" or "Fast"
|
|
24
|
+
const spans = panel.querySelectorAll('span.text-xs.select-none');
|
|
25
|
+
for (const span of spans) {
|
|
26
|
+
const text = (span.textContent || '').trim().toLowerCase();
|
|
27
|
+
if (text === 'planning' || text === 'fast') {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!mode) {
|
|
35
|
+
logger.warn('[Mode] Could not detect current mode, defaulting to "planning"');
|
|
36
|
+
return 'planning';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return mode as AgentMode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Switch the conversation mode in the IDE.
|
|
44
|
+
* Clicks the mode button to open the dropdown, then selects the target mode.
|
|
45
|
+
*/
|
|
46
|
+
export async function setAgentMode(ctx: ProxyContext, targetMode: AgentMode): Promise<void> {
|
|
47
|
+
if (!ctx.workbenchPage) throw new Error('Not connected to Antigravity');
|
|
48
|
+
|
|
49
|
+
const currentMode = await getAgentMode(ctx);
|
|
50
|
+
if (currentMode === targetMode) {
|
|
51
|
+
logger.info(`[Mode] Already in "${targetMode}" mode, skipping.`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
logger.info(`[Mode] Switching from "${currentMode}" to "${targetMode}"...`);
|
|
56
|
+
|
|
57
|
+
// Step 1: Click the mode button to open the dialog
|
|
58
|
+
const clicked = await ctx.workbenchPage.evaluate(() => {
|
|
59
|
+
const panel = document.querySelector('.antigravity-agent-side-panel');
|
|
60
|
+
if (!panel) return false;
|
|
61
|
+
|
|
62
|
+
const spans = panel.querySelectorAll('span.text-xs.select-none');
|
|
63
|
+
for (const span of spans) {
|
|
64
|
+
const text = (span.textContent || '').trim().toLowerCase();
|
|
65
|
+
if (text === 'planning' || text === 'fast') {
|
|
66
|
+
const btn = span.closest('button');
|
|
67
|
+
if (btn) {
|
|
68
|
+
btn.click();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!clicked) {
|
|
77
|
+
throw new Error('Could not find mode button to click');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await sleep(400);
|
|
81
|
+
|
|
82
|
+
// Step 2: Find the dialog and click the target option
|
|
83
|
+
const targetLabel = targetMode === 'planning' ? 'Planning' : 'Fast';
|
|
84
|
+
|
|
85
|
+
const selected = await ctx.workbenchPage.evaluate((label: string) => {
|
|
86
|
+
// Find the "Conversation mode" dialog
|
|
87
|
+
const dialogs = document.querySelectorAll('[role="dialog"]');
|
|
88
|
+
for (const dialog of dialogs) {
|
|
89
|
+
const text = (dialog.textContent || '').trim();
|
|
90
|
+
if (!text.includes('Conversation mode')) continue;
|
|
91
|
+
|
|
92
|
+
// Find the option with matching text
|
|
93
|
+
const options = dialog.querySelectorAll('.font-medium');
|
|
94
|
+
for (const opt of options) {
|
|
95
|
+
if ((opt.textContent || '').trim() === label) {
|
|
96
|
+
// Click the option's parent (the clickable row)
|
|
97
|
+
const clickTarget = opt.closest('[class*="cursor-pointer"]');
|
|
98
|
+
if (clickTarget) {
|
|
99
|
+
(clickTarget as HTMLElement).click();
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// Fallback: click the option itself
|
|
103
|
+
(opt as HTMLElement).click();
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}, targetLabel);
|
|
110
|
+
|
|
111
|
+
if (!selected) {
|
|
112
|
+
// Close dialog by pressing Escape
|
|
113
|
+
await ctx.workbenchPage.keyboard.press('Escape');
|
|
114
|
+
throw new Error(`Could not find "${targetLabel}" option in mode dialog`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await sleep(300);
|
|
118
|
+
|
|
119
|
+
// Verify the switch
|
|
120
|
+
const newMode = await getAgentMode(ctx);
|
|
121
|
+
logger.info(`[Mode] Mode is now "${newMode}".`);
|
|
122
|
+
}
|