-
Notifications
You must be signed in to change notification settings - Fork 0
moving to interactions API #4
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?
Conversation
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.
Pull request overview
This PR migrates the codebase from using Google's GenAI chat/stream API to the newer interactions API. The changes introduce CLI argument parsing for better configuration, update dependencies significantly, and restructure how the agent communicates with the AI model.
Changes:
- Migrated from chat API to interactions API with stateful conversation handling via interaction IDs
- Added CLI argument parsing for model selection, base URL, verbose mode, and thinking budget
- Updated @google/genai from v1.10.0 to v1.38.0 and other dependencies to latest versions
- Removed index.html file (web interface) and updated CI to use Node 24
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Updated all dependencies; registry changed to npmmirror.com (Chinese mirror) |
| src/main.mts | Complete API migration with new request/response handling, CLI args, schema normalization, and experimental warning suppression |
| package.json | Updated @google/genai to 1.38.0, chalk to 5.6.2, string-width to 8.1.0, and moved vite to devDependencies |
| index.html | Deleted (web interface removed) |
| .github/workflows/upload.yaml | Updated Node version from 22 to 24 |
| .gitattributes | Removed calcit.cirru linguist configuration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const macrophyllaModel = | ||
| (args.model as string) ?? | ||
| process.env["MACROPHYLLA_MODEL"] ?? | ||
| "gemini-3-flash-preview"; |
Copilot
AI
Jan 26, 2026
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.
The model name "gemini-3-flash-preview" appears to be non-standard. Based on Google's typical naming conventions, this should likely be "gemini-2.0-flash-exp" or similar. Please verify this model name exists and is correct, as using a non-existent model will cause runtime errors.
| "gemini-3-flash-preview"; | |
| "gemini-2.0-flash"; |
| baseUrl: geminiBaseUrl, | ||
| } as any); |
Copilot
AI
Jan 26, 2026
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.
Using as any to bypass type checking when creating the GoogleGenAI instance indicates the baseUrl parameter may not be typed correctly in the library. This could hide type errors and lead to runtime failures if the API structure doesn't match expectations. Consider verifying the correct way to pass baseUrl in version 1.38.0 of @google/genai, or file an issue with the library maintainers if this is a typing limitation.
| baseUrl: geminiBaseUrl, | |
| } as any); | |
| ...(geminiBaseUrl ? { baseUrl: geminiBaseUrl } : {}), | |
| }); |
| if (!schema) return schema; | ||
| const newSchema = { ...schema }; | ||
| if (typeof newSchema.type === "number") { | ||
| const types = [ | ||
| "unspecified", | ||
| "string", | ||
| "number", | ||
| "integer", | ||
| "boolean", | ||
| "array", | ||
| "object", | ||
| ]; | ||
| newSchema.type = types[newSchema.type] || newSchema.type; | ||
| } | ||
| if (typeof newSchema.type === "string") { | ||
| newSchema.type = newSchema.type.toLowerCase(); | ||
| } | ||
| if (newSchema.properties) { | ||
| const newProps: any = {}; | ||
| for (const key in newSchema.properties) { | ||
| newProps[key] = normalizeSchema(newSchema.properties[key]); | ||
| } | ||
| newSchema.properties = newProps; | ||
| } | ||
| if (newSchema.items) { | ||
| newSchema.items = normalizeSchema(newSchema.items); | ||
| } |
Copilot
AI
Jan 26, 2026
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.
The normalizeSchema function handles type conversion but doesn't validate that the input schema is well-formed. Consider adding validation to check for unexpected schema structures or malformed data, which could cause runtime errors when the schema is used by the API.
| if (!schema) return schema; | |
| const newSchema = { ...schema }; | |
| if (typeof newSchema.type === "number") { | |
| const types = [ | |
| "unspecified", | |
| "string", | |
| "number", | |
| "integer", | |
| "boolean", | |
| "array", | |
| "object", | |
| ]; | |
| newSchema.type = types[newSchema.type] || newSchema.type; | |
| } | |
| if (typeof newSchema.type === "string") { | |
| newSchema.type = newSchema.type.toLowerCase(); | |
| } | |
| if (newSchema.properties) { | |
| const newProps: any = {}; | |
| for (const key in newSchema.properties) { | |
| newProps[key] = normalizeSchema(newSchema.properties[key]); | |
| } | |
| newSchema.properties = newProps; | |
| } | |
| if (newSchema.items) { | |
| newSchema.items = normalizeSchema(newSchema.items); | |
| } | |
| // Basic structural validation: schema must be a non-null object. | |
| if (schema === null || typeof schema !== "object") { | |
| throw new TypeError( | |
| `normalizeSchema expected a non-null object, received ${typeof schema}`, | |
| ); | |
| } | |
| const newSchema: any = { ...schema }; | |
| // Normalize and validate the "type" field if present. | |
| const allowedTypes = [ | |
| "unspecified", | |
| "string", | |
| "number", | |
| "integer", | |
| "boolean", | |
| "array", | |
| "object", | |
| ]; | |
| if ( | |
| newSchema.type !== undefined && | |
| typeof newSchema.type !== "number" && | |
| typeof newSchema.type !== "string" | |
| ) { | |
| throw new TypeError( | |
| `schema.type must be a string or number when present; received ${typeof newSchema.type}`, | |
| ); | |
| } | |
| if (typeof newSchema.type === "number") { | |
| newSchema.type = allowedTypes[newSchema.type] || newSchema.type; | |
| } | |
| if (typeof newSchema.type === "string") { | |
| newSchema.type = newSchema.type.toLowerCase(); | |
| if (!allowedTypes.includes(newSchema.type)) { | |
| throw new Error(`Unsupported schema type: ${newSchema.type}`); | |
| } | |
| } | |
| // Validate and normalize nested "properties" if present. | |
| if (newSchema.properties !== undefined) { | |
| if ( | |
| newSchema.properties === null || | |
| typeof newSchema.properties !== "object" || | |
| Array.isArray(newSchema.properties) | |
| ) { | |
| throw new TypeError("schema.properties must be a non-null object map"); | |
| } | |
| const newProps: any = {}; | |
| for (const key in newSchema.properties) { | |
| if (Object.prototype.hasOwnProperty.call(newSchema.properties, key)) { | |
| newProps[key] = normalizeSchema(newSchema.properties[key]); | |
| } | |
| } | |
| newSchema.properties = newProps; | |
| } | |
| // Validate and normalize "items" for array schemas if present. | |
| if (newSchema.items !== undefined) { | |
| if (newSchema.items === null || typeof newSchema.items !== "object") { | |
| throw new TypeError("schema.items must be a non-null object"); | |
| } | |
| newSchema.items = normalizeSchema(newSchema.items); | |
| } |
| } else if (state.history.length > 0) { | ||
| // If we have history but no lastInteractionId, use stateless mode by sending full history | ||
| requestPayload.input = state.history.concat([ | ||
| { role: "user", content: state.currentMessageParts }, | ||
| ]); |
Copilot
AI
Jan 26, 2026
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.
The fallback logic in lines 172-177 attempts to send full history when there's no lastInteractionId, but this reassigns requestPayload.input which would overwrite the current user message. The concat creates a new array but the structure may not match what the API expects - verify that the interactions API supports this fallback pattern and that the message format is correct.
| } else if (state.history.length > 0) { | |
| // If we have history but no lastInteractionId, use stateless mode by sending full history | |
| requestPayload.input = state.history.concat([ | |
| { role: "user", content: state.currentMessageParts }, | |
| ]); |
| history: any[]; | ||
| currentMessageParts: any[]; |
Copilot
AI
Jan 26, 2026
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.
Extensive use of any types throughout the code (lines 46-47, 81, 105, 124, 141-142, 150, 161, 179, 181-182, 228-230, 323) significantly weakens type safety. While this may be necessary during API migration, consider creating proper TypeScript interfaces or types for the interactions API structures (e.g., InteractionRequest, InteractionResponse, InteractionChunk) to maintain type safety and catch potential runtime errors at compile time.
| history: any[]; | |
| currentMessageParts: any[]; | |
| history: Content[]; | |
| currentMessageParts: Part[]; |
| // --- Suppress Experimental Warning --- | ||
| const originalWarn = console.warn; | ||
| console.warn = (...args: any[]) => { | ||
| if ( | ||
| typeof args[0] === "string" && | ||
| args[0].includes("Interactions usage is experimental") | ||
| ) { | ||
| return; | ||
| } | ||
| originalWarn(...args); | ||
| }; | ||
|
|
Copilot
AI
Jan 26, 2026
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.
The code suppresses experimental warnings by overriding console.warn, which is a risky approach that could hide legitimate warnings. Consider using more targeted warning suppression or documenting why this experimental API is acceptable for production use. Additionally, this suppression occurs before all imports, which means it might miss warnings from import-time code execution.
| // --- Suppress Experimental Warning --- | |
| const originalWarn = console.warn; | |
| console.warn = (...args: any[]) => { | |
| if ( | |
| typeof args[0] === "string" && | |
| args[0].includes("Interactions usage is experimental") | |
| ) { | |
| return; | |
| } | |
| originalWarn(...args); | |
| }; |
| if ( | ||
| output.type === "function_call" && | ||
| !newToolCalls.some((c) => c.id === output.id) | ||
| ) { | ||
| newToolCalls.push(output); | ||
| } |
Copilot
AI
Jan 26, 2026
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.
The code checks for duplicate function calls by comparing IDs (!newToolCalls.some((c) => c.id === output.id)), but if function calls from content.delta events don't have IDs or have different ID structures than those in interaction.complete, this could lead to duplicate or missing tool calls. Verify the ID structure is consistent across both event types.
| return { | ||
| modelResponseParts, | ||
| toolCalls: newToolCalls.length > 0 ? newToolCalls : undefined, | ||
| interactionId, | ||
| }; |
Copilot
AI
Jan 26, 2026
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.
If the stream completes without an interaction.complete event, the interactionId will be an empty string, which may cause issues when used as previous_interaction_id in subsequent requests. Consider handling this case by either throwing an error, logging a warning, or falling back to stateless mode.
| ]); | ||
| } | ||
|
|
||
| const response = await (genAi as any).interactions.create(requestPayload); |
Copilot
AI
Jan 26, 2026
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.
The code uses (genAi as any).interactions.create(requestPayload) which bypasses type checking. This indicates the interactions API is not officially typed in the @google/genai library. Consider checking if there's official documentation or types for this API, and document the expected structure of requestPayload and response for future maintainability.
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| node-version: 24 |
Copilot
AI
Jan 26, 2026
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.
The Node.js version was updated from 22 to 24 in the CI workflow. Node.js 24 is not yet released (as of January 2025, the latest LTS is Node 22). This appears to be a future-proofing change or potentially an error. Verify that Node 24 is available and stable, or consider using Node 22 or 23 (current versions) instead.
| node-version: 24 | |
| node-version: 22 |
No description provided.