Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ec5f1e6
feat(routines): implement azd ai routine commands
huimiu May 19, 2026
520ab32
test(routines): add unit tests for endpoint, create, manifest, and mo…
huimiu May 19, 2026
e092aa5
test(routines): align test patterns with azure.ai.agents extension
huimiu May 19, 2026
b9122b6
fix(routines): address CI lint and spell-check failures
huimiu May 19, 2026
32dcd78
fix(routines): remove unused ptrBool helper
huimiu May 19, 2026
f7d1284
docs(routines): remove design spec from PR
huimiu May 19, 2026
8e0d723
fix(routines): close response bodies per page and preserve filter on …
huimiu May 19, 2026
b7d375b
fix(routines): flatten command tree to remove duplicate 'routine rout…
huimiu May 19, 2026
02fb138
fix(routines): align data-plane client with Foundry Routines TypeSpec
huimiu May 19, 2026
4a9cbe3
fix(routines): clarify comment for github_issue fields in RoutineTrigger
huimiu May 19, 2026
1ff5487
fix(routines): address PR review feedback
huimiu May 20, 2026
8ce5954
chore(routines): add .golangci.yaml and AGENTS.md to align with sibli…
huimiu May 22, 2026
4aa3627
fix(routines): use camelCase JSON tags to match Foundry service wire
huimiu May 22, 2026
795d0ab
feat(routines): align with spec PR #43186 and fix HTTP/2 hang
huimiu May 22, 2026
3eedf27
feat(routines): defer recurring/schedule trigger until service is ready
huimiu May 22, 2026
9659512
fix(routines): enforce update-mode manifest merge, env-backed no-prom…
huimiu May 22, 2026
53114fa
fix(routines): replace unknown word 'misroute' in endpoint comment
huimiu May 22, 2026
57eb23c
feat(routines): add exterrors unit tests and .gitignore for bin/
huimiu May 22, 2026
e76c689
style(routines): fix gofmt formatting in test files
huimiu May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/azd/extensions/azure.ai.routines/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin/
17 changes: 17 additions & 0 deletions cli/azd/extensions/azure.ai.routines/.golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "2"

linters:
default: none
enable:
- gosec
- lll
- unused
- errorlint
settings:
lll:
line-length: 220
tab-width: 4

formatters:
enable:
- gofmt
154 changes: 154 additions & 0 deletions cli/azd/extensions/azure.ai.routines/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Azure AI Routines Extension - Agent Instructions

Use this file together with `cli/azd/AGENTS.md`. This guide supplements the root azd
instructions with the conventions that are specific to this extension.

## Overview

`azure.ai.routines` is a first-party azd extension under
`cli/azd/extensions/azure.ai.routines/`. It runs as a separate Go binary and talks
to the azd host over gRPC.

The user-facing surface is `azd ai routine <verb>` — CRUD over Microsoft Foundry
Routines attached to a Foundry project endpoint.

Useful places to start:

- `internal/cmd/`: Cobra commands and verb implementations
- Project-endpoint resolution comes from the sibling `azure.ai.projects`
extension (and the shared cascade); do not re-implement it here.

## Build and test

From `cli/azd/extensions/azure.ai.routines`:

```bash
# Build using developer extension (for local development)
azd x build

# Or build using Go directly
go build

# Run unit tests
go test ./... -count=1
```

If extension work depends on a new azd core change, plan for two PRs:

1. Land the core change in `cli/azd` first.
2. Land the extension change after that, updating this module to the newer azd
dependency with `go get github.com/azure/azure-dev/cli/azd && go mod tidy`.

For local development, draft work, or validating both sides together before the
core PR is merged, you may temporarily add:

```go
replace github.com/azure/azure-dev/cli/azd => ../../
```

That `replace` points this extension at your local `cli/azd` checkout instead of
the version in `go.mod`. Do not merge the extension with that `replace` still
present.

## Error handling

Return plain Go errors by default, and wrap lower-level failures with
`fmt.Errorf("context: %w", err)` where useful.

If this extension grows enough to need stable telemetry categories, error codes,
or user-facing suggestions, introduce an `internal/exterrors` package modeled on
the one in `azure.ai.agents` / `azure.ai.toolboxes`:

- Create a structured error once, as close as possible to the place where you
know the final category, code, and suggestion.
- If `err` is already a structured error, return it unchanged. Do **not** wrap
it with `fmt.Errorf("context: %w", err)` — during gRPC serialization, azd
preserves the structured error's own message/code/category, not the outer
wrapper text.
- Prefer the dedicated helpers at the Azure/gRPC boundary:
- `exterrors.ServiceFromAzure(err, operation)` for `azcore.ResponseError`
(data-plane and ARM calls).
- `exterrors.FromPrompt(err, contextMessage)` for `azdClient.Prompt().*`
failures.

## Release preparation

A new extension release ships in two PRs:

### PR 1 — Version bump

Bumps the extension to the new version. Touches only:

- `version.txt` — new semver string
- `extension.yaml` — `version:` field
- `CHANGELOG.md` — new release section at the top

Once merged, the team triggers the CI release pipeline, which builds, signs, and
publishes the extension binaries as a GitHub release.

### PR 2 — Registry update

After the GitHub release is live, a follow-up PR updates
`cli/azd/extensions/registry.json` so azd users can install the new version.
The contents of that file are produced by running `azd x publish` against the
published release artifacts (which computes the artifact URLs and checksums).
The resulting PR should contain only the regenerated `registry.json` entry for
the extension, and in some cases updated test snapshots as well.

## Output: `log` vs `fmt`

Extensions write directly to stdout/stderr — there is no `Console` abstraction
from azd core.

- **`fmt.Print*`** — user-facing output (stdout). Pair with `output.With*Format`
helpers for styled text.
- **`log.Print*`** — developer diagnostics (stderr). Hidden unless `--debug`
is set. Never use `log` for anything the user needs to see.
- Do not use `log.Fatal` or `log.Panic` for expected failures — return an error
instead.

```go
// ✅ log — internal detail the user doesn't need to see
log.Printf("routine show: pending-routine read failed for %q: %v", name, err)

// ✅ fmt — user-facing status and results
fmt.Printf("Created routine %s at version %s.\n", name, version)

// ❌ fmt used for debug noise — user sees internal details they can't act on
fmt.Printf("Parsed endpoint: host=%s, path=%s\n", host, path) // use log.Printf

// ❌ log used for user-facing info — user never sees it without --debug
log.Printf("No routines found on project") // use fmt.Print*
```

## Other extension conventions

- Use modern Go 1.26 patterns where they help readability.
- Reserved azd globals (`--output`, `--no-prompt`) are inherited from `extCtx`,
not registered as flags on each verb.
- Lowercase-normalize `--output` when reading it from `extCtx` so downstream
branches can compare with `== "json"`.
- When using `PromptSubscription()`, create credentials with
`Subscription.UserTenantId`, not `Subscription.TenantId`.

## API spec alignment

The authoritative TypeSpec is in
[`azure-rest-api-specs` PR #43186](https://github.com/Azure/azure-rest-api-specs/pull/43186)
(`specification/ai-foundry/data-plane/Foundry/src/routines/`). The client in
`internal/pkg/routines/` tracks that spec, with the following deliberate
divergences that exist purely to stay compatible with the currently deployed
Foundry service. Each divergence is also noted inline in the code.

| Concern | Spec (PR #43186) | Live service | Client choice |
|---|---|---|---|
| Wire field naming | `snake_case` | `camelCase` | camelCase JSON tags |
| `InvokeAgentResponsesApiRoutineAction` agent field | `agent_name` | `agentId` | `AgentID` / `agentId` |
| `:dispatch_async` action segment | snake_case | `:dispatchAsync` only | camelCase URL |
| `POST :enable` / `POST :disable` | dedicated routes | 404 | GET+PUT fallback |
| `:github_issue_opened` trigger | renamed in spec | accepts old `github_issue` | CLI keeps `github_issue` wire value (trigger feature is deferred at the CLI anyway) |
| `AgentsPagedResult<T>` envelope | `data` + `last_id` + `has_more` | `value` + `nextLink` (routines) / `value` + `nextPageToken` (runs) | matches service |
| `task_id` on `DispatchRoutineResponse` / `RoutineRun` | new in spec | already emitted by service | added (`TaskID` / `taskId`) |

When the service catches up to the spec, revisit these one at a time.

6 changes: 5 additions & 1 deletion cli/azd/extensions/azure.ai.routines/cspell.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
import: ../../.vscode/cspell.yaml
words: []
words:
- exterrors
- sess
- routineName
- azdProjectSources
2 changes: 1 addition & 1 deletion cli/azd/extensions/azure.ai.routines/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module azure.ai.routines


go 1.26.1

require (
Expand All @@ -15,6 +14,7 @@ require (
require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/extensions/azure.ai.routines/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0 h1:JI8PcWOImyvIUEZ0Bbmfe05FOlWkMi2KhjG+cAKaUms=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0/go.mod h1:nJLFPGJkyKfDDyJiPuHIXsCi/gpJkm07EvRgiX7SGlI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
Expand Down
Loading
Loading