Skip to content

feat(azure.ai.agents): add ConnectionManager for CRUD on Foundry project connections#8030

Draft
Nathandrake229 wants to merge 1 commit intoAzure:mainfrom
Nathandrake229:feature/agent-connection-crud
Draft

feat(azure.ai.agents): add ConnectionManager for CRUD on Foundry project connections#8030
Nathandrake229 wants to merge 1 commit intoAzure:mainfrom
Nathandrake229:feature/agent-connection-crud

Conversation

@Nathandrake229
Copy link
Copy Markdown
Contributor

Summary

Add a \ConnectionManager\ service layer that provides CRUD operations on connected resources (connections) in a Foundry project.

What's included

New: \connection_manager.go\

  • \ConnectionManager\ struct combining ARM SDK (\ProjectConnectionsClient) for create/get/update/delete/list with data-plane client (\FoundryProjectsClient) for reading credentials/secrets
  • Clean domain types: \ConnectionInfo, \ConnectionDetail, \CreateConnectionParams, \UpdateConnectionParams\
  • 6 operations: \List, \Get, \GetWithCredentials, \Create, \Update, \Delete\
  • Supports ApiKey, CustomKeys, and None auth types
  • GET-then-PUT pattern for Update (ARM doesn't support PATCH on connections)
  • Raw JSON credential parsing to extract arbitrary custom key names from CustomKeys connections

New: \connection_manager_test.go\

  • 14 unit tests covering body construction for all auth types, credential parsing (including the \key-name ambiguity edge case), helper functions, and ARM response conversion

Modified: \ oundry_projects_client.go\

  • Refactored \GetConnectionWithCredentials\ to use a \getConnectionWithCredentialsRaw\ helper that returns raw bytes, enabling flexible credential field parsing without breaking existing callers

Design context

  • ARM API never returns credential values — data-plane \getConnectionWithCredentials\ is required for secrets
  • Connections are ARM resources under \Microsoft.CognitiveServices/accounts/{account}/projects/{project}/connections/{name}\
  • The hybrid approach (ARM for CRUD + data-plane for secrets) matches existing patterns in the extension

Testing

All 40 tests pass (14 new + 26 existing in the azure package).

…ect connections

Add ConnectionManager struct that combines the ARM SDK (ProjectConnectionsClient)
for create/get/update/delete/list operations with the data-plane client
(FoundryProjectsClient) for reading credentials/secrets.

Key components:
- ConnectionManager with List, Get, GetWithCredentials, Create, Update, Delete
- Clean domain types (ConnectionInfo, ConnectionDetail, CreateConnectionParams,
  UpdateConnectionParams) that don't leak ARM SDK types to callers
- Support for ApiKey, CustomKeys, and None auth types
- GET-then-PUT pattern for Update (ARM doesn't support PATCH on connections)
- Raw JSON credential parsing to extract arbitrary custom key names
- Refactored FoundryProjectsClient.GetConnectionWithCredentials to expose
  a raw response variant for flexible credential parsing

Includes 14 unit tests covering body construction, credential parsing,
helper functions, and ARM response conversion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

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

4 findings on the new ConnectionManager.

The main issue: fetchCredentials hits the same data-plane endpoint twice for non-ApiKey connections - once parsed, once raw. Since getConnectionWithCredentialsRaw already exists, you can call it once and parse from those bytes. That cuts the round-trips in half.

CI's go-fix check is also failing. Run go fix ./... from the extension directory to replace to.Ptr() with new().

// fetchCredentials calls the data-plane getConnectionWithCredentials endpoint
// and returns the credential fields as a flat map (excluding the "type" discriminator).
func (m *ConnectionManager) fetchCredentials(ctx context.Context, name string) (map[string]string, error) {
conn, err := m.dpClient.GetConnectionWithCredentials(ctx, name)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This calls GetConnectionWithCredentials (which internally calls getConnectionWithCredentialsRaw + unmarshal), then for non-ApiKey types, fetchRawCredentials calls getConnectionWithCredentialsRaw again. That's two HTTP round-trips to the same endpoint.

Since you need the raw bytes for CustomKeys anyway, consider calling getConnectionWithCredentialsRaw once and parsing both the Connection struct (for type detection) and the raw credentials from the same bytes:

func (m *ConnectionManager) fetchCredentials(ctx context.Context, name string) (map[string]string, error) {
    raw, err := m.dpClient.getConnectionWithCredentialsRaw(ctx, name)
    if err != nil {
        return nil, err
    }

    var conn Connection
    if err := json.Unmarshal(raw, &conn); err != nil {
        return nil, fmt.Errorf("parsing connection response: %w", err)
    }

    if conn.Credentials.Type == CredentialTypeApiKey && conn.Credentials.Key != "" {
        return map[string]string{"key": conn.Credentials.Key}, nil
    }

    return parseRawCredentials(raw)
}

This also lets you extract parseRawCredentials as a standalone function that's directly testable (see comment on the test file).

}
var s string
if err := json.Unmarshal(v, &s); err != nil {
continue // skip non-string values
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Silently skipping non-string values could hide credential data the caller needs. Consider at minimum a log.Printf here so unexpected credential formats don't go unnoticed during debugging.

}
}

func TestParseRawCredentials(t *testing.T) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These tests duplicate the credential-parsing logic inline instead of calling the actual code. If fetchRawCredentials changes, these tests still pass because they're testing a copy of the algorithm, not the real thing.

If you extract the parsing into a standalone parseRawCredentials(raw []byte) (map[string]string, error) function (as suggested on the other comment), these tests can call it directly and verify the real code path.

Properties: &armcognitiveservices.APIKeyAuthConnectionProperties{
AuthType: &authType,
Category: &category,
Target: to.Ptr(params.Target),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CI's go-fix check is failing because Go 1.26 prefers new(val) over to.Ptr(val). This applies to all 7 to.Ptr call sites in this file. Run go fix ./... from the extension directory - it'll auto-fix these and likely remove the now-unused to import.

@JeffreyCA JeffreyCA added the ext-agents azure.ai.agents extension label May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-agents azure.ai.agents extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants