-
Notifications
You must be signed in to change notification settings - Fork 5
Png image #54
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
base: main
Are you sure you want to change the base?
Png image #54
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
png-image | 0dc21cc | Aug 11 2025, 09:32 PM |
🎉 Snyk checks have passed. No issues have been found so far.✅ security/snyk check is complete. No issues have been found. (View Details) ✅ license/snyk check is complete. No issues have been found. (View Details) |
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.
Summary of Changes
Hello @shikharbsar, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces the capability to generate and serve PNG images of data visualizations directly from ThoughtSpot. It establishes a new API endpoint for image retrieval, backed by a temporary token system utilizing Cloudflare KV for secure access to session details. The changes also enhance the server's context to facilitate environment access and update various configurations and type definitions to support the new functionality.
Highlights
- Image Retrieval Endpoint: Introduced a new GET endpoint
/data/imginsrc/handlers.tsto serve PNG images of ThoughtSpot visualizations. This endpoint uses a temporary token to retrieve necessary session and access details from a KV store. - Temporary Token Management: Implemented a system in
src/servers/mcp-server.tsandsrc/servers/openai-mcp-server.tsto generate unique tokens, store ThoughtSpot session ID, generation number, instance URL, and access token in Cloudflare KV, and provide a URL for image retrieval in the API response. - Server Context Enhancement: Modified the
Contextinterface insrc/servers/mcp-server-base.tsand refactoredinstrumentedMCPServerinsrc/utils.tsto allow Durable Objects to access the Cloudflare Workers environment (env), enabling KV interactions. - ThoughtSpot Integration for Images: Added a new
getAnswerImagePNGmethod tosrc/thoughtspot/thoughtspot-service.tsto interact with the ThoughtSpot API and export answer reports as PNG images. - Configuration and Type Updates: Updated environment variable access in
src/index.ts, addedHOST_NAMEtowrangler.jsoncandworker-configuration.d.ts, and performed various type definition cleanups inworker-configuration.d.ts.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
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.
Code Review
This pull request introduces functionality to generate and serve PNG images of ThoughtSpot answers. It adds a new endpoint /data/img which uses a token-based mechanism with KV store to retrieve image data. The changes touch multiple files to support this flow, including modifications to the MCP servers to generate image URLs, and updates to the ThoughtSpot service to fetch the image data. The PR also includes some refactoring to pass environment variables to server instances and updates to dependency configurations.
My review focuses on ensuring the new endpoint is robust, identifying potential runtime errors, and suggesting improvements for code clarity, consistency, and maintainability. I've pointed out a critical issue with a method call that would lead to a runtime error, and a high-severity issue regarding a potentially accidental removal of a configuration block. Other comments focus on removing debug logs, improving type safety, and standardizing naming conventions.
src/servers/mcp-server.ts
Outdated
| accessToken: this.ctx.props.accessToken | ||
| }; | ||
| await putInKV(token, tokenData, this.ctx.env); | ||
| tokenUrl = `${this.ctx.env?.HOST_NAME}/data/img?token=${token}`; |
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.
Maybe we call token something like uniqueId or tempId to not make the agent believe its a token protected resource and should not be opened
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.
Done
src/servers/openai-mcp-server.ts
Outdated
| const token = crypto.randomUUID(); | ||
| const tokenData = { | ||
| sessionId: answer.session_identifier, | ||
| generationNo: answer.generation_number, | ||
| instanceURL: this.ctx.props.instanceUrl, | ||
| accessToken: this.ctx.props.accessToken | ||
| }; | ||
| await putInKV(token, tokenData, this.ctx.env); | ||
| tokenUrl = `${this.ctx.env?.HOST_NAME}/data/img?token=${token}`; |
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.
this seems to be duplicate code
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.
Done
src/servers/openai-mcp-server.ts
Outdated
| tokenUrl = `${this.ctx.env?.HOST_NAME}/data/img?token=${token}`; | ||
| } | ||
| let content = answer.data; | ||
| if (tokenUrl) { |
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.
will tokenUrl be not available in any case ? If not we can remove this conditionality
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.
done
src/servers/openai-mcp-server.ts
Outdated
| **CHART VISUALIZATION AVAILABLE:** | ||
| There is a data visualization chart for this query available at: "${tokenUrl}". | ||
| This URL contains a PNG image showing the visual representation of the above data for the question: "${question}". Please fetch and display this image to provide a complete analysis with both the data and its visual representation. The image must be displayed in the response of this question. | ||
| Use just this image in the response, do not use any other images or visualizations or charts. It may take time to get the image, so please wait for the image to be fetched and displayed in the response.`; |
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.
Can we make this consistent across different mcp servers ? Maybe move to the common service layer.
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.
done
wrangler.jsonc
Outdated
| "HONEYCOMB_DATASET": "", | ||
| "HONEYCOMB_API_KEY": "" | ||
| "HONEYCOMB_API_KEY": "", | ||
| "HOST_NAME": "https://agent.thoughtspot.app" |
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.
We have created aliases for this, and might also change this in the future. Lets not hardcode this. This should be available in the request payload headers.
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.
Yeah, added it as part of props, let me know if I need to change it.
src/handlers.ts
Outdated
| async getAnswerImage(request: Request, env: Env) { | ||
| const span = getActiveSpan(); | ||
| const url = new URL(request.url); | ||
| const token = url.searchParams.get('token') || url.searchParams.get('uniqueId') || ''; |
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.
remove token
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.
Rename the variable as well
src/handlers.ts
Outdated
| const tokenData = await getFromKV(token, env); | ||
| if (!tokenData) { | ||
| span?.setStatus({ code: SpanStatusCode.ERROR, message: "Token not found" }); | ||
| return new Response("Token not found", { status: 404 }); |
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.
Can we create an error response utility for the handlers as well.
src/handlers.ts
Outdated
| const sessionId = (tokenData as any).sessionId; | ||
| const generationNo = (tokenData as any).GenNo || (tokenData as any).generationNo; // Handle both field names | ||
| const instanceURL = (tokenData as any).instanceURL; | ||
| const accessToken = (tokenData as any).accessToken; |
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.
const { sessionId, generationNo, instanceURL, accessToken } = tokenData as any;
src/servers/api-server.ts
Outdated
| @WithSpan('api-get-answer-image') | ||
| async getAnswerImage(props: Props, sessionId: string, generationNo: number) { | ||
| const service = this.getThoughtSpotService(props); | ||
| return await service.getAnswerImage(sessionId, generationNo); | ||
| } |
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.
this might not be needed.
src/servers/mcp-server-base.ts
Outdated
| } | ||
|
|
||
| protected async getAnswerContent(answer: any, question: string) { | ||
| const tokenUrl = await this.getTokenUrl(answer.session_identifier, answer.generation_number); |
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.
createImageUrl
src/servers/mcp-server-base.ts
Outdated
| instanceURL: this.ctx.props.instanceUrl, | ||
| accessToken: this.ctx.props.accessToken | ||
| }; | ||
| await putInKV(token, tokenData, this.ctx.env); |
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.
should not put in a get function. Lets rename this createImageUrl
src/servers/mcp-server-base.ts
Outdated
| protected async getTokenUrl(sessionId: string, generationNo: number) { | ||
| let tokenUrl = ""; | ||
| // Generate token and store in KV store | ||
| if (this.ctx.env?.OAUTH_KV) { |
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.
Lets put this in a try ... catch. The putInKV method should throw if OAUTH_KV is not available. And we should handle that condition in the catch
src/servers/mcp-server-base.ts
Outdated
| }; | ||
| await putInKV(token, tokenData, this.ctx.env); | ||
| const hostName = this.ctx.props.hostName; | ||
| if (hostName && (hostName.startsWith("http") || hostName.startsWith("https"))) { |
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.
handle this while setting the prop
| } | ||
|
|
||
| @WithSpan('get-answer-image') | ||
| async getAnswerImagePNG(sessionId: string, GenNo: number): Promise<HttpFile> { |
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.
genNo
src/utils.ts
Outdated
| private _server: T | undefined; | ||
| private _env: Env; | ||
|
|
||
| // Lazy getter for server that creates it when first accessed | ||
| // | ||
| // WHY THIS APPROACH: | ||
| // Originally we passed 'this' directly as Context: `server = new MCPServer(this)` | ||
| // This worked when Context was just { props: Props }, but broke when we added env. | ||
| // | ||
| // PROBLEMS WITH ORIGINAL APPROACH: | ||
| // 1. McpAgent's 'env' property is protected, but Context expects public | ||
| // 2. TypeScript error: "Property 'env' is protected but public in Context" | ||
| // | ||
| // WHY NOT CONSTRUCTOR CREATION: | ||
| // We tried creating server in constructor: `new MCPServer({ props: this.props, env })` | ||
| // But this.props is undefined during constructor - it gets set later by McpAgent._init() | ||
| // Runtime error: "Cannot read properties of undefined (reading 'instanceUrl')" | ||
| // | ||
| // SOLUTION - LAZY INITIALIZATION: | ||
| // - Store env from constructor (available immediately) | ||
| // - Create server only when first accessed (after props are set by McpAgent lifecycle) | ||
| // - Combine both props and env into proper Context object | ||
| get server(): T { | ||
| if (!this._server) { | ||
| const context: Context = { | ||
| props: this.props, // Available after McpAgent._init() sets it | ||
| env: this._env // Stored from constructor | ||
| }; | ||
| this._server = new MCPServer(context); | ||
| } | ||
| return this._server; | ||
| } |
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.
declare public env: Env;
And then move creation of the server inside the init method.
No description provided.