-
Notifications
You must be signed in to change notification settings - Fork 5
Http server and Session manager #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
f82a190
vercel ai adpater for gemini cli
shivammittal274 add3f78
tests fixed based upon v5
shivammittal274 65252b0
remove logic for normalisation for openai (not needed)
shivammittal274 0765f9b
tests fixed based upon v5
shivammittal274 a61b371
agent core logic
shivammittal274 e710c39
pulled main
shivammittal274 31a1ea6
session management and http server code
shivammittal274 4a19abe
session management and http server code
shivammittal274 5fd4464
session management and http server code
shivammittal274 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| export class HttpAgentError extends Error { | ||
| constructor( | ||
| message: string, | ||
| public statusCode: number = 500, | ||
| public code?: string, | ||
| ) { | ||
| super(message); | ||
| this.name = this.constructor.name; | ||
| Error.captureStackTrace(this, this.constructor); | ||
| } | ||
|
|
||
| toJSON() { | ||
| return { | ||
| error: { | ||
| name: this.name, | ||
| message: this.message, | ||
| code: this.code, | ||
| statusCode: this.statusCode, | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| export class ValidationError extends HttpAgentError { | ||
| constructor(message: string, public details?: unknown) { | ||
| super(message, 400, 'VALIDATION_ERROR'); | ||
| } | ||
|
|
||
| override toJSON() { | ||
| return { | ||
| error: { | ||
| name: this.name, | ||
| message: this.message, | ||
| code: this.code, | ||
| statusCode: this.statusCode, | ||
| details: this.details, | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| export class SessionNotFoundError extends HttpAgentError { | ||
| constructor(public conversationId: string) { | ||
| super(`Session "${conversationId}" not found.`, 404, 'SESSION_NOT_FOUND'); | ||
| } | ||
| } | ||
|
|
||
| export class AgentExecutionError extends HttpAgentError { | ||
| constructor(message: string, public originalError?: Error) { | ||
| super(message, 500, 'AGENT_EXECUTION_ERROR'); | ||
| } | ||
|
|
||
| override toJSON() { | ||
| return { | ||
| error: { | ||
| name: this.name, | ||
| message: this.message, | ||
| code: this.code, | ||
| statusCode: this.statusCode, | ||
| originalError: this.originalError?.message, | ||
| }, | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| import { Hono } from 'hono'; | ||
| import { cors } from 'hono/cors'; | ||
| import { stream } from 'hono/streaming'; | ||
| import { serve } from '@hono/node-server'; | ||
| import { formatDataStreamPart } from '@ai-sdk/ui-utils'; | ||
| import { logger } from '@browseros/common'; | ||
| import type { Context, Next } from 'hono'; | ||
| import type { ContentfulStatusCode } from 'hono/utils/http-status'; | ||
| import type { z } from 'zod'; | ||
|
|
||
| import { SessionManager } from '../session/SessionManager.js'; | ||
| import { HttpAgentError, ValidationError, AgentExecutionError } from '../errors.js'; | ||
| import { ChatRequestSchema, HttpServerConfigSchema } from './types.js'; | ||
| import type { HttpServerConfig, ValidatedHttpServerConfig, ChatRequest } from './types.js'; | ||
|
|
||
| type AppVariables = { | ||
| validatedBody: unknown; | ||
| }; | ||
|
|
||
| const DEFAULT_MCP_SERVER_URL = 'http://127.0.0.1:9150/mcp'; | ||
| const DEFAULT_TEMP_DIR = '/tmp'; | ||
|
|
||
| function validateRequest<T>(schema: z.ZodType<T>) { | ||
| return async (c: Context<{ Variables: AppVariables }>, next: Next) => { | ||
| try { | ||
| const body = await c.req.json(); | ||
| const validated = schema.parse(body); | ||
| c.set('validatedBody', validated); | ||
| await next(); | ||
| } catch (err) { | ||
| if (err && typeof err === 'object' && 'issues' in err) { | ||
| const zodError = err as { issues: unknown }; | ||
| logger.warn('Request validation failed', { issues: zodError.issues }); | ||
| throw new ValidationError('Request validation failed', zodError.issues); | ||
| } | ||
| throw err; | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| export function createHttpServer(config: HttpServerConfig) { | ||
| const validatedConfig: ValidatedHttpServerConfig = HttpServerConfigSchema.parse(config); | ||
| const mcpServerUrl = validatedConfig.mcpServerUrl || process.env.MCP_SERVER_URL || DEFAULT_MCP_SERVER_URL; | ||
|
|
||
| const app = new Hono<{ Variables: AppVariables }>(); | ||
| const sessionManager = new SessionManager(); | ||
|
|
||
| app.use( | ||
| '/*', | ||
| cors({ | ||
| origin: (origin) => origin || '*', | ||
| allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'], | ||
| allowHeaders: ['Content-Type', 'Authorization'], | ||
| credentials: true, | ||
| }), | ||
| ); | ||
|
|
||
| app.onError((err, c) => { | ||
| const error = err as Error; | ||
|
|
||
| if (error instanceof HttpAgentError) { | ||
| logger.warn('HTTP Agent Error', { | ||
| name: error.name, | ||
| message: error.message, | ||
| code: error.code, | ||
| statusCode: error.statusCode, | ||
| }); | ||
| return c.json(error.toJSON(), error.statusCode as ContentfulStatusCode); | ||
| } | ||
|
|
||
| logger.error('Unhandled Error', { | ||
| message: error.message, | ||
| stack: error.stack, | ||
| }); | ||
|
|
||
| return c.json( | ||
| { | ||
| error: { | ||
| name: 'InternalServerError', | ||
| message: error.message || 'An unexpected error occurred', | ||
| code: 'INTERNAL_SERVER_ERROR', | ||
| statusCode: 500, | ||
| }, | ||
| }, | ||
| 500, | ||
| ); | ||
| }); | ||
|
|
||
| app.get('/health', (c) => c.json({ status: 'ok' })); | ||
|
|
||
| app.post('/chat', validateRequest(ChatRequestSchema), async (c) => { | ||
| const request = c.get('validatedBody') as ChatRequest; | ||
|
|
||
| logger.info('Chat request received', { | ||
| conversationId: request.conversationId, | ||
| provider: request.provider, | ||
| model: request.model, | ||
| }); | ||
|
|
||
| c.header('Content-Type', 'text/plain; charset=utf-8'); | ||
| c.header('X-Vercel-AI-Data-Stream', 'v1'); | ||
| c.header('Cache-Control', 'no-cache'); | ||
| c.header('Connection', 'keep-alive'); | ||
|
|
||
| // Get abort signal from the raw request - fires when client disconnects | ||
| const abortSignal = c.req.raw.signal; | ||
|
|
||
| return stream(c, async (honoStream) => { | ||
| try { | ||
| const agent = await sessionManager.getOrCreate({ | ||
| conversationId: request.conversationId, | ||
| provider: request.provider, | ||
| model: request.model, | ||
| apiKey: request.apiKey, | ||
| baseUrl: request.baseUrl, | ||
| // Azure-specific | ||
| resourceName: request.resourceName, | ||
| // AWS Bedrock-specific | ||
| region: request.region, | ||
| accessKeyId: request.accessKeyId, | ||
| secretAccessKey: request.secretAccessKey, | ||
| sessionToken: request.sessionToken, | ||
| // Agent-specific | ||
| tempDir: validatedConfig.tempDir || DEFAULT_TEMP_DIR, | ||
| mcpServerUrl, | ||
| }); | ||
|
|
||
| await agent.execute(request.message, honoStream, abortSignal); | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : 'Agent execution failed'; | ||
| logger.error('Agent execution error', { | ||
| conversationId: request.conversationId, | ||
| error: errorMessage, | ||
| }); | ||
| await honoStream.write(formatDataStreamPart('error', errorMessage)); | ||
| throw new AgentExecutionError('Agent execution failed', error instanceof Error ? error : undefined); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| app.delete('/chat/:conversationId', (c) => { | ||
| const conversationId = c.req.param('conversationId'); | ||
| const deleted = sessionManager.delete(conversationId); | ||
|
|
||
| if (deleted) { | ||
| return c.json({ | ||
| success: true, | ||
| message: `Session ${conversationId} deleted`, | ||
| sessionCount: sessionManager.count(), | ||
| }); | ||
| } | ||
|
|
||
| return c.json({ | ||
| success: false, | ||
| message: `Session ${conversationId} not found`, | ||
| }, 404); | ||
| }); | ||
|
|
||
| const server = serve({ | ||
| fetch: app.fetch, | ||
| port: validatedConfig.port, | ||
| hostname: validatedConfig.host, | ||
| }); | ||
|
|
||
| logger.info('HTTP Agent Server started', { | ||
| port: validatedConfig.port, | ||
| host: validatedConfig.host, | ||
| }); | ||
|
|
||
| return { | ||
| app, | ||
| server, | ||
| config: validatedConfig, | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export { createHttpServer } from './HttpServer.js'; | ||
| export { HttpServerConfigSchema, ChatRequestSchema } from './types.js'; | ||
| export type { HttpServerConfig, ValidatedHttpServerConfig, ChatRequest } from './types.js'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { z } from 'zod'; | ||
| import { VercelAIConfigSchema } from '../agent/gemini-vercel-sdk-adapter/types.js'; | ||
|
|
||
| /** | ||
| * Chat request schema extends VercelAIConfig with request-specific fields | ||
| */ | ||
| export const ChatRequestSchema = VercelAIConfigSchema.extend({ | ||
| conversationId: z.string().uuid(), | ||
| message: z.string().min(1, 'Message cannot be empty'), | ||
| }); | ||
|
|
||
| export type ChatRequest = z.infer<typeof ChatRequestSchema>; | ||
|
|
||
| export interface HttpServerConfig { | ||
| port: number; | ||
| host?: string; | ||
| corsOrigins?: string[]; | ||
| tempDir?: string; | ||
| mcpServerUrl?: string; | ||
| } | ||
|
|
||
| export const HttpServerConfigSchema = z.object({ | ||
| port: z.number().int().positive(), | ||
| host: z.string().optional().default('0.0.0.0'), | ||
| corsOrigins: z.array(z.string()).optional().default(['*']), | ||
| tempDir: z.string().optional().default('/tmp'), | ||
| mcpServerUrl: z.string().optional(), | ||
| }); | ||
|
|
||
| export type ValidatedHttpServerConfig = z.infer<typeof HttpServerConfigSchema>; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@greptileai review the security vulnerability aspect and suggest any fixes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the security aspects of this HTTP server implementation, I can identify several potential vulnerabilities and areas for improvement:
Security Vulnerabilities Identified:
/chatendpoint is completely openSuggested Fixes:
For the CORS configuration specifically (since that's the line your comment is on), here's a more secure approach:
Additional Security Recommendations:
X-Frame-Options,X-Content-Type-Options, etc.Would you like me to provide implementations for any of these additional security measures?