Skip to content

Conversation

@sashimikun
Copy link

This commit introduces a complete Go SDK equivalent to the existing Python SDK for the Jules Agent API.

Features:

  • Sessions API: Create and manage AI agent sessions with full lifecycle support
  • Activities API: Track and retrieve session activities with automatic pagination
  • Sources API: Manage source repositories and retrieve repository information
  • Comprehensive error handling with specific error types (Authentication, NotFound, Validation, RateLimit, Server)
  • Automatic retry logic with exponential backoff for failed requests
  • HTTP connection pooling for efficient resource management
  • Context support for cancellation and timeouts
  • Wait for completion with configurable polling intervals

Project Structure:

  • jules/: Core SDK package with all API clients and models
  • examples/: Sample code demonstrating SDK usage
  • README.md: Comprehensive documentation with examples
  • go.mod: Go module definition

The SDK follows Go best practices and provides the same functionality as the Python SDK with idiomatic Go patterns including:

  • Struct-based configuration
  • Context for request lifecycle management
  • Error wrapping for better error handling
  • Interfaces for extensibility

Generated with Claude Code

This commit introduces a complete Go SDK equivalent to the existing Python SDK for the Jules Agent API.

Features:
- Sessions API: Create and manage AI agent sessions with full lifecycle support
- Activities API: Track and retrieve session activities with automatic pagination
- Sources API: Manage source repositories and retrieve repository information
- Comprehensive error handling with specific error types (Authentication, NotFound, Validation, RateLimit, Server)
- Automatic retry logic with exponential backoff for failed requests
- HTTP connection pooling for efficient resource management
- Context support for cancellation and timeouts
- Wait for completion with configurable polling intervals

Project Structure:
- jules/: Core SDK package with all API clients and models
- examples/: Sample code demonstrating SDK usage
- README.md: Comprehensive documentation with examples
- go.mod: Go module definition

The SDK follows Go best practices and provides the same functionality as the Python SDK
with idiomatic Go patterns including:
- Struct-based configuration
- Context for request lifecycle management
- Error wrapping for better error handling
- Interfaces for extensibility

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello @sashimikun, 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 delivers a new Go SDK for the Jules Agent API, enabling Go developers to seamlessly interact with AI agent sessions, track activities, and manage source repositories. The SDK is built with robustness in mind, featuring automatic retries, connection pooling, and a structured error handling mechanism. It adheres to Go's best practices, ensuring an idiomatic and developer-friendly experience, and comes with thorough documentation and examples to facilitate quick adoption.

Highlights

  • New Go SDK: Introduces a complete Go SDK for the Jules Agent API, providing equivalent functionality to the existing Python SDK.
  • API Coverage: Includes comprehensive APIs for managing Sessions, Activities, and Sources within the Jules Agent platform.
  • Robust Error Handling: Features comprehensive error handling with specific error types for authentication, not found, validation, rate limits, server errors, and timeouts.
  • Network Resilience: Implements automatic retry logic with exponential backoff for failed requests and efficient HTTP connection pooling.
  • Go Idioms and Best Practices: The SDK is designed following idiomatic Go patterns, including struct-based configuration, context support for cancellation and timeouts, error wrapping, and interfaces for extensibility.
  • Documentation and Examples: A detailed README.md provides installation instructions, quick start guides, API references, and configuration options, complemented by practical usage examples.
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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

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

  1. 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.

Copy link

@gemini-code-assist gemini-code-assist bot left a 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 a comprehensive Go SDK for the Jules Agent API. The structure is well-organized, and the features cover the necessary API interactions, including sessions, activities, and sources. The implementation includes good practices like context support, connection pooling, and automatic retries.

My review focuses on improving robustness, performance, and adherence to Go idioms. Key areas for improvement include fixing a critical resource leak in the HTTP client's retry loop, making query parameter construction safer, and improving the efficiency of response parsing. I've also noted a potential panic in one of the examples and a non-standard Go module version. Overall, this is a solid foundation for the SDK, and addressing these points will make it even more reliable and efficient.

Comment on lines +107 to +111
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using defer resp.Body.Close() inside a loop is a classic Go pitfall that can lead to resource leaks. The deferred call will only execute when the request function returns, not at the end of each loop iteration. If a request is retried (e.g., on a 5xx error), the connection from the previous attempt will remain open. The response body should be closed explicitly within the loop, immediately after it has been read.

Suggested change
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
respBody, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

@@ -0,0 +1,3 @@
module github.com/sashimikun/jules-agent-sdk-go

go 1.24.7

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The go directive in go.mod should specify the minor language version (e.g., 1.24), not a specific patch version like 1.24.7. Using a patch version can cause build problems for users with other Go 1.24 patch versions installed.

Suggested change
go 1.24.7
go 1.24

fmt.Println("\n=== Creating Session ===")
session, err := client.Sessions.Create(ctx, &jules.CreateSessionRequest{
Prompt: "Add a new feature to improve error handling",
Source: sources[0].Name, // Use the first source

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Accessing sources[0] on this line will cause a panic if client.Sources.ListAll returns an empty slice. Although this is an example, it's a good opportunity to demonstrate safe coding practices by checking if the slice is empty before accessing an element.


// Parse response
var result ActivitiesListResponse
respBytes, _ := json.Marshal(resp)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error from json.Marshal is being ignored. While marshalling a map[string]interface{} is unlikely to fail here, it's best practice to handle all errors. This pattern of ignoring the marshal error is also present in the List methods of jules/sessions.go and jules/sources.go.

Suggested change
respBytes, _ := json.Marshal(resp)
respBytes, err := json.Marshal(resp)
if err != nil {
return nil, fmt.Errorf("failed to marshal activities list: %w", err)
}

Comment on lines +114 to +127
func (a *ActivitiesAPI) parseActivity(data map[string]interface{}) (*Activity, error) {
// Convert to JSON and back to properly parse the activity
jsonBytes, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal activity data: %w", err)
}

var activity Activity
if err := json.Unmarshal(jsonBytes, &activity); err != nil {
return nil, fmt.Errorf("failed to parse activity: %w", err)
}

return &activity, nil
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The map -> JSON -> struct conversion pattern used here is inefficient as it involves extra memory allocations and CPU cycles for marshalling and unmarshalling. A more performant approach would be to decode the map into the struct directly using a library like mitchellh/mapstructure. This same pattern is used in parseSession and parseSource and could be improved there as well.

Comment on lines +31 to +53
func (s *SessionsAPI) Create(ctx context.Context, req *CreateSessionRequest) (*Session, error) {
// Build request body
body := map[string]interface{}{
"prompt": req.Prompt,
"sourceContext": map[string]interface{}{
"source": req.Source,
},
}

if req.StartingBranch != "" {
sourceContext := body["sourceContext"].(map[string]interface{})
sourceContext["githubRepoContext"] = map[string]interface{}{
"startingBranch": req.StartingBranch,
}
}

if req.Title != "" {
body["title"] = req.Title
}

if req.RequirePlanApproval {
body["requirePlanApproval"] = true
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Building the request body using map[string]interface{} and type assertions like body["sourceContext"].(map[string]interface{}) is fragile and not type-safe. A change in the map structure could lead to a runtime panic. It would be more robust to define a private struct for the request body and marshal that to JSON. This improves type safety, readability, and maintainability.


// Parse response
var result SessionsListResponse
respBytes, _ := json.Marshal(resp)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error from json.Marshal is being ignored. While marshalling a map[string]interface{} is unlikely to fail here, it's best practice to handle all errors.

Suggested change
respBytes, _ := json.Marshal(resp)
respBytes, err := json.Marshal(resp)
if err != nil {
return nil, fmt.Errorf("failed to marshal sessions list: %w", err)
}

Comment on lines +43 to +63
if opts != nil {
query := ""
if opts.Filter != "" {
query += fmt.Sprintf("filter=%s", opts.Filter)
}
if opts.PageSize > 0 {
if query != "" {
query += "&"
}
query += fmt.Sprintf("pageSize=%d", opts.PageSize)
}
if opts.PageToken != "" {
if query != "" {
query += "&"
}
query += fmt.Sprintf("pageToken=%s", opts.PageToken)
}
if query != "" {
path += "?" + query
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Building query strings manually by concatenating strings is fragile and can be error-prone, especially with multiple optional parameters. Using the net/url package provides a more robust and idiomatic way to construct query parameters. This approach also improves readability. This feedback also applies to the List methods in jules/activities.go and jules/sessions.go. You'll need to add "net/url" to your imports for this change.

if opts != nil {
    params := url.Values{}
    if opts.Filter != "" {
        params.Set("filter", opts.Filter)
    }
    if opts.PageSize > 0 {
        params.Set("pageSize", fmt.Sprintf("%d", opts.PageSize))
    }
    if opts.PageToken != "" {
        params.Set("pageToken", opts.PageToken)
    }
    if len(params) > 0 {
        path += "?" + params.Encode()
    }
}


// Parse response
var result SourcesListResponse
respBytes, _ := json.Marshal(resp)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error from json.Marshal is being ignored. While marshalling a map[string]interface{} is unlikely to fail here, it's best practice to handle all errors.

respBytes, err := json.Marshal(resp)
if err != nil {
    return nil, fmt.Errorf("failed to marshal sources list: %w", err)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants