Skip to content

Add azd ai dataset extension for Foundry datasets - #9499

Open
Mohamed Hessien (m7md7sien) wants to merge 42 commits into
Azure:mainfrom
m7md7sien:feat/azure-ai-dataset
Open

Add azd ai dataset extension for Foundry datasets#9499
Mohamed Hessien (m7md7sien) wants to merge 42 commits into
Azure:mainfrom
m7md7sien:feat/azure-ai-dataset

Conversation

@m7md7sien

@m7md7sien Mohamed Hessien (m7md7sien) commented Aug 10, 2026

Copy link
Copy Markdown

Adds azd ai dataset ΓÇö a standalone extension for registering and versioning Foundry datasets.

Why its own extension

A dataset is a general Foundry asset. Fine-tuning and other scenarios need one as much as evaluation does, so the CRUD group does not belong inside the evaluation extension. This extension has its own Go module, lint workflow and release pipeline, and no dependency on azure.ai.evaluations.

The line drawn with the evaluation extension: CRUD moved, generation stayed. generate writes the datasets: entry into the evaluation configuration, so moving it would strand a config write across an extension boundary ΓÇö a generated dataset registered with the service and absent from configuration, which azd up would not reconcile. create/update/list/show/delete/versions need no configuration at all, which is why they could move.

Surface

azd ai dataset create <name>      register a dataset, publishing version 1
azd ai dataset update <name>      publish a new version
azd ai dataset list               list the project's datasets
azd ai dataset show <name>        show a version
azd ai dataset delete <name>      delete a version
azd ai dataset versions list <name>

Notable

  • Every user-facing string lives in one file (internal/messages/messages.go) so the whole voice of the CLI can be reviewed in one sitting. 69 messages.
  • TYPE, not FORMAT. The API accepts format on upload and never returns it, so the previous column was blank for every dataset ever listed.
  • Identity fixed. Carved from the evaluation extension, it had inherited its User-Agent (azd-ext-azure-ai-evaluations) and debug log name (azd-ai-eval-*.log), so a dataset request could not be told from an eval one in a service log or a local one.
  • Auth errors say what to do. A credential that cannot mint a token fails before any 401 and surfaced as a bare SDK error. It now suggests retrying first and logging in second ΓÇö observed failing transiently against a login that was valid.
  • A name the service will reject is refused locally. A dataset name with a space came back as a 400 wrapped in four levels of nested JSON; it is now checked against the accepted character set before the request.
  • A UTF-8 BOM no longer corrupts an upload. Windows editors and PowerShell write one by default, and it was being uploaded into the first row's first key rather than skipped.
  • Nil-guarded the endpoint resolution cascade and closed the azd client on the error paths that abandoned it.

Verified

go build, go vet -tags live,hero, go test ./... and gofmt clean. Exercised live against a Foundry project: create → 1.0, update → 2.0, versions list, show, list, and published to a local registry for a bug bash through real azd.

Not included, deliberately

No registry.json entry and no microsoft.foundry bundle dependency. Both need published artifacts; listing an unpublished extension in the bundle breaks azd extension install microsoft.foundry for everyone. Those land after the first release.

Open question for reviewers

azd ai dataset versions list <unknown-name> errors and exits 1 here, while the same command in the evaluation extension (#9500) lists nothing and exits 0. I tried making the eval side match this one and it broke two tests whose names are the argument against it ΓÇö "an unknown name lists nothing rather than failing" and "deleting an unregistered dataset is idempotent, not an error", the second because delete idempotence is verified by listing what is left.

A list is a filter rather than a lookup, so empty-and-successful is defensible and -o json callers range over the array instead of branching on an error. By that reasoning this extension's versions list is the one that should move. dataset show on an unknown name is not in question: that one is a lookup and should fail, as it does.

Draft ΓÇö not requesting reviewers yet.

Fixes #9550

Datasets are a general Foundry asset that fine-tuning and other scenarios
need as much as evaluation does, so the CRUD group ships as its own
extension rather than inside azure.ai.evaluations. Generation stays with
the evaluation extension, because it writes that extension's config file.

Standalone: no dependency on azure.ai.evaluations, its own go module,
lint workflow and release pipeline.
Adds internal/messages, holding the extension's errors, warnings and status lines behind one named function each, grouped under Datasets, Config, Output and Talking to the service. Wording is unchanged; the call sites now name the situation instead of spelling out the sentence. Cobra help, wire constants and table headers stay where they are.
Three things carried over from the extension this was carved out of. The
User-Agent announced azure-ai-evaluations and the debug log was written to
azd-ai-eval-<date>.log, so a dataset request could not be told from an eval
one in either a service log or a local one.

The FORMAT column was worse than wrong: format is a field the API accepts on
upload and never sends back, so the column was blank for every dataset ever
listed. It shows type, which the service does return.
Carved from the same code, so it had the same two: a nil Environment or
GetValue response panicking inside the endpoint cascade, and an azd client
abandoned when endpoint or credential setup failed.
Same credential failure and same 401/403 path as the eval extension, which
this was carved from.
Same intermittent credential failure as the eval extension.
Same three as the eval extension, which this was carved from.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
7 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the customer-reported identify a customer issue label Aug 10, 2026
A BOM at the head of a .jsonl is uploaded as part of the first row's first
key, so every consumer of the dataset sees one malformed record -- and nothing
fails until something tries to read that row. Notepad, VS Code on Windows and
PowerShell all write one.

A file holding nothing but a BOM is still empty, and is still refused as such.
Found by bug-bashing the off-path cases.

A missing dataset reported "has no versions", which is not what happened -- a
dataset cannot exist with no versions, and the service answers an unknown name
with an empty list rather than a 404. Listing its versions was worse: it said
"No datasets found", as though the project had none at all. Both now say the
name is not a dataset and point at the listing, matching how the evaluations
extension already answers the same question.

A name with a space took a file upload and then a 400 carrying four levels of
nested JSON, with the sentence that matters at the bottom. The rule is simple
and documented, so it is checked before the round trip.
A mistyped path surfaced as 'GetFileAttributesEx <path>: The system cannot find the file specified.' The syscall name is noise to the person who mistyped it.
--from-file <file> resolved to the file's DIRECTORY, and the upload helper then took whichever .jsonl sorted first. Pointing at one dataset in a folder holding several registered a different one under that name. A test pinned the old behaviour: 'a file resolves to the directory the upload scans'.
The unit tests cover the client layer and the helpers; nothing exercised the command surface a user touches - flag parsing, exit codes, the rendered table, and whether -o json emits something a script can consume. 18 tests behind the live tag, gated on AZURE_AI_DATASET_E2E_LIVE, type-checked by ci-test.ps1 like the eval extension's suites.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
7 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a standalone Foundry dataset extension with CRUD/versioning commands, API clients, tests, documentation, and release automation.

Changes:

  • Adds dataset creation, updates, listing, inspection, deletion, and version listing.
  • Adds endpoint resolution, validation, structured errors, and JSON/table output.
  • Adds unit/live tests plus build, lint, and release infrastructure.

Reviewed changes

Copilot reviewed 49 out of 50 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
eng/pipelines/release-ext-azure-ai-dataset.yml Adds extension release pipeline.
cli/azd/extensions/azure.ai.dataset/version.txt Sets initial beta version.
cli/azd/extensions/azure.ai.dataset/tests/cli/harness_test.go Adds live CLI harness.
cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go Tests dataset CLI workflows.
cli/azd/extensions/azure.ai.dataset/README.md Documents installation and commands.
cli/azd/extensions/azure.ai.dataset/main.go Adds extension entry point.
cli/azd/extensions/azure.ai.dataset/internal/version/version.go Defines build metadata.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/version_test.go Tests version handling.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/uri_test.go Tests URI resolution.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/upload_version_test.go Tests conflict-aware uploads.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go Implements dataset and storage operations.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations_wire_test.go Tests API request contracts.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/models.go Defines API models and file handling.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/list.go Implements listing and version ordering.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/download_wire_test.go Tests download HTTP behavior.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/download_test.go Tests blob selection logic.
cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/bom_test.go Tests BOM and file handling.
cli/azd/extensions/azure.ai.dataset/internal/messages/messages.go Centralizes user-facing messages.
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/validator.go Validates Foundry endpoints.
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/types.go Defines endpoint-resolution types.
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/store.go Reads shared project context.
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/resolver.go Implements endpoint precedence.
cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/resolver_test.go Tests endpoint resolution.
cli/azd/extensions/azure.ai.dataset/internal/exterrors/errors.go Adds structured error helpers.
cli/azd/extensions/azure.ai.dataset/internal/exterrors/codes.go Defines structured error codes.
cli/azd/extensions/azure.ai.dataset/internal/cmd/table_test.go Tests table rendering.
cli/azd/extensions/azure.ai.dataset/internal/cmd/surface_test.go Tests command surface and flags.
cli/azd/extensions/azure.ai.dataset/internal/cmd/root.go Builds the command tree.
cli/azd/extensions/azure.ai.dataset/internal/cmd/output.go Implements JSON and text output.
cli/azd/extensions/azure.ai.dataset/internal/cmd/output_test.go Tests output formatting.
cli/azd/extensions/azure.ai.dataset/internal/cmd/names.go Validates dataset names.
cli/azd/extensions/azure.ai.dataset/internal/cmd/names_test.go Tests naming constraints.
cli/azd/extensions/azure.ai.dataset/internal/cmd/helpers_test.go Tests command helpers.
cli/azd/extensions/azure.ai.dataset/internal/cmd/debug.go Configures debug logging.
cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go Implements dataset commands.
cli/azd/extensions/azure.ai.dataset/internal/cmd/context.go Creates command service context.
cli/azd/extensions/azure.ai.dataset/internal/cmd/artifacts.go Defines persistence helpers.
cli/azd/extensions/azure.ai.dataset/internal/cmd/apiversions.go Defines service API versions.
cli/azd/extensions/azure.ai.dataset/go.mod Defines the Go module.
cli/azd/extensions/azure.ai.dataset/extension.yaml Declares extension metadata.
cli/azd/extensions/azure.ai.dataset/cspell.yaml Configures spell checking.
cli/azd/extensions/azure.ai.dataset/ci-test.ps1 Adds CI test execution.
cli/azd/extensions/azure.ai.dataset/ci-build.ps1 Adds CI binary builds.
cli/azd/extensions/azure.ai.dataset/CHANGELOG.md Records the initial release.
cli/azd/extensions/azure.ai.dataset/build.sh Adds Unix cross-platform builds.
cli/azd/extensions/azure.ai.dataset/build.ps1 Adds Windows cross-platform builds.
cli/azd/extensions/azure.ai.dataset/.golangci.yaml Configures Go linting.
cli/azd/extensions/azure.ai.dataset/.gitignore Ignores generated artifacts.
.github/workflows/lint-ext-azure-ai-dataset.yml Adds GitHub lint workflow.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cli/azd/extensions/azure.ai.dataset/ci-build.ps1 Outdated
Comment thread cli/azd/extensions/azure.ai.dataset/build.sh Outdated
Comment thread cli/azd/extensions/azure.ai.dataset/build.ps1 Outdated
Comment thread cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go Outdated
Comment thread cli/azd/extensions/azure.ai.dataset/internal/messages/messages.go
Comment thread cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/operations.go Outdated
Comment thread cli/azd/extensions/azure.ai.dataset/internal/exterrors/errors.go Outdated
Comment thread cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go Outdated
Comment thread cli/azd/extensions/azure.ai.dataset/CHANGELOG.md Outdated
Two lines of ci-test.ps1 assumed PowerShell 6 or later. $IsWindows does
not exist on Windows PowerShell 5.1, so the .exe suffix was never
appended; and Join-Path there takes a single child path, so the
three-argument call failed outright. The script then found no gotestsum,
printed the fallback notice, and ran bare `go test` with no JUnit report
-- while still exiting 0, which is why it went unnoticed.
Copilot AI review requested due to automatic review settings August 10, 2026 05:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 59 out of 60 changed files in this pull request and generated no new comments.

Suppressed comments (10)

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:232

  • The API client explicitly treats a 404 from this endpoint as an unknown/versionless dataset, but this command returns that 404 before reaching the advertised empty-list behavior. Convert IsNotFound(err) to an empty DatasetList; otherwise versions list <unknown> still exits nonzero.
			list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
			if err != nil {
				return messages.ListingDatasetVersions(name, err)
			}

cli/azd/extensions/azure.ai.dataset/ci-build.ps1:45

  • These linker targets still use the evaluation module path. This module is azureaidataset, so the release build does not stamp its Version, Commit, or BuildDate variables and the dataset User-Agent remains .../dev.
    "-X 'azureaieval/internal/version.Version=$Version' " +
    "-X 'azureaieval/internal/version.Commit=$SourceVersion' " +
    "-X 'azureaieval/internal/version.BuildDate=$(Get-Date -Format o)' "

cli/azd/extensions/azure.ai.dataset/build.ps1:44

  • This copied evaluation module path means local PowerShell builds do not stamp azureaidataset/internal/version; the binary keeps the development version in its User-Agent. Point VERSION_PATH at this module.
$VERSION_PATH = "azureaieval/internal/version"

cli/azd/extensions/azure.ai.dataset/build.sh:36

  • This copied evaluation module path means shell builds do not stamp azureaidataset/internal/version; the binary keeps the development version in its User-Agent. Point VERSION_PATH at this module.
VERSION_PATH="azureaieval/internal/version"

cli/azd/extensions/azure.ai.dataset/.gitignore:5

  • Debug logging creates azd-ai-dataset-YYYY-MM-DD.log, but this pattern only ignores the old evaluation filename. Dataset debug logs will remain unignored and can be committed accidentally.
azd-ai-eval-*.log

cli/azd/extensions/azure.ai.dataset/ci-test.ps1:10

  • The live harness checks AZURE_AI_DATASET_E2E_LIVE, so this setup instruction names an environment variable that cannot enable the suite.
# AZURE_AI_EVAL_E2E_LIVE and a project endpoint. They are still type-checked

cli/azd/extensions/azure.ai.dataset/README.md:28

  • Both references are stale: the evaluation extension exposes azd ai eval generate, not azd ai eval dataset generate, and its configuration is azure.eval.yaml. As written, the documented command fails and points readers at the obsolete filename.
Generation is `azd ai eval dataset generate`, in `azure.ai.evaluations`, and
stays there: it writes the `datasets:` entry in `evals/eval.yaml`, which is that

cli/azd/extensions/azure.ai.dataset/README.md:41

  • This omits two implemented resolution levels and the AZURE_AI_PROJECT_ENDPOINT fallback. Users relying on shared project context or the legacy key cannot infer why a value wins; document the same four-level order implemented by projectctx.Resolve.
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:93

  • Listing failures are currently indistinguishable from an absent dataset. A 403 or transport error makes exists false, and both point probes discard their errors too, so update reports “does not exist” while create may proceed despite being unable to verify existence. Propagate non-404 errors and only treat empty/404 responses as absence.

This issue also appears on line 229 of the same file.

			exists := err == nil && existing != nil && len(existing.Value) > 0

cli/azd/extensions/azure.ai.dataset/CHANGELOG.md:17

  • No command in this extension calls DownloadDatasetContent, so the first-release changelog promises a read-back capability that users cannot invoke. Remove this feature bullet or add the corresponding public command before release.
- Reads dataset content back, whether the service hands out a blob URI or the
  container holding it.

The version listing is eventually consistent, and latestRegisteredVersion already documents that an unknown dataset and a listing that has not caught up are indistinguishable -- both come back empty with no error. Gating update on that emptiness meant a create followed by an update was refused for a dataset that plainly existed, and sent to create, which fails in turn once the listing catches up and reports the name taken. The two errors pointed at each other.

So absence now has to be confirmed by an outright 404 before it refuses. An empty listing is inconclusive and publishes instead, which is what the caller asked for either way: the upload does not care whether the name was new. This is the same tolerance UploadNextVersion already applies to a stale listing on 409.

The version probe stays as a fast path, but it can only ever prove existence -- a dataset whose early versions were deleted has none left to find -- so it was never able to close this on its own.
build.ps1, build.sh and ci-build.ps1 began as copies of the eval extension's and kept its module path, so -X named azureaieval/internal/version in a module called azureaidataset. The linker discards -X for a symbol that does not exist, so this failed silently: every release binary reported version dev in its User-Agent and no service-side log could tell which build a caller was running.

The same copy left .gitignore ignoring azd-ai-eval-*.log while debug.go writes azd-ai-dataset-*.log, so debug logs were never ignored, and ci-test.ps1 documenting AZURE_AI_EVAL_E2E_LIVE while the harness reads AZURE_AI_DATASET_E2E_LIVE, so anyone following it would set the wrong variable and see the live suite skip while reporting success.

A test now compares the linker path in all three scripts against the module in go.mod, because a silently discarded flag is exactly the kind of regression nothing else would catch. Reported by the Copilot reviewer on the PR.
datasetUploadSource accepts DATA.JSONL both as a named file and when scanning a directory, but ReadFirstJSONLFile compared the extension exactly. The CLI resolves a directory to a single file before calling it, so this was not reachable from either extension's commands, but the two layers disagreed and any caller passing a directory would hit it. Reported by the Copilot reviewer.
Copilot AI review requested due to automatic review settings August 16, 2026 15:06
…eate

README named �zd ai eval dataset generate and evals/eval.yaml; the eval extension's surface is �zd ai eval generate and it writes evals/azure.eval.yaml, reading a legacy eval.yaml only when that is what the project already has.

A version that does not exist pointed the reader at �zd ai dataset list, which lists datasets and not versions, so the suggested command could not answer the question that was asked. It now names �ersions list <name>.

The shared create/update builder registered --version on both, but the upload derives the next version from it, so create --version 4.0 published 5.0 rather than a first version -- and left the existence probe, which looks for the versions a first publish can carry, unable to find what it had just written. Verified against --help that create no longer offers it and update still does. All reported by the Copilot reviewer.
@m7md7sien

Copy link
Copy Markdown
Author

Copilot review threads — disposition

Working through the open Copilot threads. All of them predate the current head, so several were already addressed; the rest are fixed in the commits below. Grouping them rather than replying 31 times.

Fixed earlier in this PR

  • SAS credential written to logs and error text (~10 threads). url.URL.Redacted() masks a userinfo password and leaves the query, so sig survived. Every log site now goes through logSafeURL, which drops the query and fragment, and every SAS-backed Do wraps failures with redactURLError, because http.Client.Do embeds the full URL in a *url.Error. Tests pin the premise as well as the fix — they assert that Redacted() alone still leaks, so the tests fail if someone reverts to it.
  • latestRegisteredVersion treating every listing failure as "no versions". A 403 or a timeout restarted an existing dataset at 1.0. It now returns the error and only a 404 means versionless.
  • Legacy errors.As. Both IsVersionConflict and IsNotFound use errors.AsType[*azcore.ResponseError].
  • Point-read probe looking for 1. It derives from NextVersion("") so it looks for the 1.0 a create actually writes, and keeps 1 for datasets registered by a generation job, the SDK or the portal.

Fixed now

  • LatestVersion fallback unreachable (2 threads). bestOrder started below -1, so the first unorderable version became best and the documented fallback to the last entry never ran. Correct here — and the sibling copy in the evaluations extension still had it, which I have also fixed.
  • Build scripts stamping the wrong module. build.ps1, build.sh and ci-build.ps1 named azureaieval/internal/version inside a module called azureaidataset. The linker discards -X for a symbol that does not exist, so this failed silently and every release binary reported dev in its User-Agent — nothing could tell which build a caller was running. A test now compares the linker path in all three scripts against the module in go.mod, since a silently discarded flag is exactly what nothing else would catch.
  • .gitignore ignoring azd-ai-eval-*.log while debug.go writes azd-ai-dataset-*.log, so debug logs were never ignored.
  • ci-test.ps1 documenting AZURE_AI_EVAL_E2E_LIVE while the harness reads AZURE_AI_DATASET_E2E_LIVE, so anyone following it would set the wrong variable and watch the live suite skip while reporting success.
  • --version offered on create. The shared builder registered it on both verbs, but the upload derives the next version from it, so create --version 4.0 published 5.0. Now registered on update only; verified against --help.
  • Version-not-found hint pointed at azd ai dataset list, which lists datasets rather than versions. It now names versions list <name>.
  • README naming azd ai eval dataset generate and evals/eval.yaml. The surface is azd ai eval generate and the file is evals/azure.eval.yaml.
  • Case-sensitive .jsonl scan in ReadFirstJSONLFile while the command layer used EqualFold. Not reachable from the CLI, which resolves a directory to a single file first, but the two layers disagreed.

Also addressed, by a different route than suggested

  • Listing failures becoming "does not exist" on update. Rather than reporting the listing error, the gate now refuses only when the service confirms the name is unknown with a 404. An empty listing cannot distinguish an unknown dataset from one the listing has not caught up with, so gating on it meant create then update was refused for a dataset that plainly existed — and sent to create, which fails in turn once the listing catches up. The two errors pointed at each other.

Not taking

  • Deterministic blob name <name>.jsonl. Raised as an overwrite risk under concurrent publishes. The alternative — a unique name per publish — leaves several .jsonl beside each other, and the container-listing fallback in DownloadDatasetContent picks the first by name, so a download could return rows no version points at. That is the wider harm. The tradeoff is documented at the call site and holds until the service allocates the version rather than the client guessing it from a lagging listing.

Resolving these threads now. Anything I have mischaracterised, please reopen.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 60 out of 61 changed files in this pull request and generated no new comments.

Suppressed comments (9)

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:370

  • [azd-code-reviewer] This 404 branch contradicts TestCLIDeleteIsIdempotent: a missing version still exits nonzero. Treat not-found as a successful no-op and continue to the normal JSON/text result; only other delete failures should be returned.
				return err
			}
			defer ec.Close()

			if err := ec.datasetClient.DeleteDatasetVersion(

cli/azd/extensions/azure.ai.dataset/README.md:28

  • [azd-code-reviewer] The referenced generation command does not exist in the companion extension; its surface is azd ai eval generate, and its configuration is azure.eval.yaml. As written, users are sent to an unknown command and the wrong configuration name.
Generation is `azd ai eval generate`, in `azure.ai.evaluations`, and stays
there: it writes the `datasets:` entry in `evals/azure.eval.yaml`, which is that

cli/azd/extensions/azure.ai.dataset/README.md:41

  • [azd-code-reviewer] This list omits two sources the implementation checks before the host environment: the azure.ai.agents global project context and the AZURE_AI_PROJECT_ENDPOINT fallback. Document the actual precedence so users can diagnose why a particular endpoint was selected.
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:237

  • [azd-code-reviewer] An unknown dataset can return 404 here, so this exits before renderDatasets can produce the documented empty-success result. latestRegisteredVersion already treats this response as versionless. Translate 404 to an empty list so the new CLI tests and implementation agree.
			ec, err := newDatasetContext(ctx, endpointFlg)
			if err != nil {
				return err

cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/validator.go:72

  • [azd-code-reviewer] Every caller discards pathWarning, so an endpoint missing /api/projects/<project> is silently accepted and requests are sent to the wrong base path. Either make this path check a validation error or propagate the warning through Resolve and render it before the request.
    cli/azd/extensions/azure.ai.dataset/README.md:6
  • [azd-code-reviewer] This onboarding command cannot currently resolve the extension because the registry entry is deliberately absent, as lines 54–57 acknowledge. Replace it with complete local azd x pack/azd x publish instructions until the registry entry lands, otherwise the first documented step always fails.
$ azd extension install azure.ai.dataset

cli/azd/extensions/azure.ai.dataset/README.md:22

  • [azd-code-reviewer] The documented delete invocation omits the mandatory --version flag, so copying it fails with “--version is required.” Include the required flag in the command table.
| `azd ai dataset delete <name>` | Delete a dataset version |

cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:51

  • [azd-code-reviewer] Cleanup silently returns when listing or JSON decoding fails and ignores every delete result, so failed cleanup leaves datasets in the shared live project while the suite still reports success. Assert each cleanup operation and decode through the existing helpers so leaked service state is visible.
    cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/resolver.go:143
  • [azd-code-reviewer] This adds reads of AZURE_AI_PROJECT_ENDPOINT, but that variable is absent from the repository’s environment-variable reference, and the existing FOUNDRY_PROJECT_ENDPOINT entry mentions only the agents extension. Update cli/azd/docs/environment-variables.md to document both variables and this extension’s precedence.

Copilot AI review requested due to automatic review settings August 16, 2026 15:14
The lint/go-fix gate rewrites strings.Split to the Go 1.24 iterator and fails when that rewrite is not already applied.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 60 out of 61 changed files in this pull request and generated no new comments.

Suppressed comments (9)

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:374

  • This reverses the documented and tested idempotence guarantee: when the service returns 404 for an already-absent version, delete exits nonzero. Treat not-found as an already-completed delete and continue to the normal human/JSON success response. [azd-code-reviewer]
				if dataset_api.IsNotFound(err) {
					return messages.DatasetVersionNotFound(name, version)

cli/azd/extensions/azure.ai.dataset/README.md:41

  • The documented cascade omits both the persisted azd ai project set value and the AZURE_AI_PROJECT_ENDPOINT fallback, so users may troubleshoot or override the wrong source. Document all four implemented levels in their actual precedence order. [azd-code-reviewer]
Every command resolves the Foundry project endpoint in this order:

1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:243

  • The advertised empty-and-success behavior does not handle the API's 404 form for an unknown dataset. TestUploadNextVersionTreatsAnUnknownDatasetAsVersionless explicitly models /versions returning 404, but this command returns that error instead of []. Normalize only not-found here and keep propagating other failures. [azd-code-reviewer]
			list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
			if err != nil {
				return messages.ListingDatasetVersions(name, err)
			}

cli/azd/extensions/azure.ai.dataset/README.md:22

  • This usage is incomplete: the implementation requires --version, so copying the documented command always fails before deletion. Include the required flag in the command table. [azd-code-reviewer]
| `azd ai dataset delete <name>` | Delete a dataset version |

cli/azd/extensions/azure.ai.dataset/internal/foundry/projectctx/resolver.go:139

  • This extension newly reads AZURE_AI_PROJECT_ENDPOINT, but that variable is absent from cli/azd/docs/environment-variables.md, and the existing FOUNDRY_PROJECT_ENDPOINT entry still says it is agents-only. Update the environment-variable reference so this fallback is discoverable and its precedence is accurate. [azd-code-reviewer]
    cli/azd/extensions/azure.ai.dataset/tests/cli/harness_test.go:130
  • This test harness cannot actually detect the hang asserted by TestCLIRequiredValuesFailInsteadOfHanging: exec.Command has no deadline, so a regression blocks until the entire go test timeout. Run subprocesses with exec.CommandContext and a short per-command timeout, and fail with the captured output when it expires. [azd-code-reviewer]
    cli/azd/extensions/azure.ai.dataset/CHANGELOG.md:17
  • The release note claims a user-visible read-back feature, but no command calls DownloadDatasetContent; only tests do, and the package is internal to this module. Either expose the claimed capability through the CLI or remove it from the release notes. [azd-code-reviewer]
- Reads dataset content back, whether the service hands out a blob URI or the
  container holding it.

cli/azd/extensions/azure.ai.dataset/.golangci.yaml:12

  • The repository's Go guidance sets lll to 125 characters (cli/azd/AGENTS.md:97-108), but this module allows 220, so its dedicated CI accepts lines the repository standard rejects. Restore the 125-character limit and wrap the existing overlong source line. [azd-code-reviewer]
      line-length: 220

cli/azd/extensions/azure.ai.dataset/internal/cmd/surface_test.go:245

  • Remove the accidental // embedded in this sentence and wrap it to the repository's 125-character limit. [azd-code-reviewer]
// A message pointing at `azd ai eval dataset ...` is almost always the copy// these commands came from rather than a deliberate cross-extension pointer.

The Blob service answers a container listing one page at a time and returns a NextMarker, which this dropped -- it parsed the first page and stopped. DownloadDatasetContent falls back to listing the container and taking the first .jsonl by name, so a container holding more objects than fit in a page could report no file at all, or a different one, depending on where the page happened to end. The marker is the service's own opaque value replayed into a URL this code builds, so the walk carries none of the risk that following a body-supplied link would.

Ported from the evaluations extension, which already had this and the tests for it; this copy had neither. Also makes blobHTTPClient a single shared client, as that copy does.

Eighth divergence found between the two copies of dataset_api. Reported by a review agent diffing them.
Copilot AI review requested due to automatic review settings August 16, 2026 15:42
The URI is the SAS the dataset service issued, not caller input, which is why the evaluations copy already suppresses G704 on the same call. Porting the paginated listing without it failed golangci-lint.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 61 out of 62 changed files in this pull request and generated no new comments.

Suppressed comments (8)

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:375

  • [azd-code-reviewer] This explicitly turns a not-found delete into failure, while TestCLIDeleteIsIdempotent defines deleting an unregistered version as successful. Treat 404 as an idempotent success so cleanup scripts do not fail when the version is already absent.
				if dataset_api.IsNotFound(err) {
					return messages.DatasetVersionNotFound(name, version)
				}

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:99

  • [azd-code-reviewer] A non-404 listing failure leaves exists=false, the probes ignore their errors, and create is then allowed to continue. If the transient failure clears when UploadNextVersion lists again, create can publish a new version of an existing dataset instead of failing as already-exists. Propagate an inconclusive list error for create.
			if err := checkAssetExistence(
				verb, "dataset", name, exists, dataset_api.IsNotFound(listErr),
			); err != nil {

cli/azd/extensions/azure.ai.dataset/README.md:41

  • [azd-code-reviewer] This omits two implemented resolution levels: AZURE_AI_PROJECT_ENDPOINT is a fallback at both environment levels, and persisted azd ai project set context is checked before host variables. Users following this order can diagnose or update the wrong source. Document the actual four-level cascade.
Every command resolves the Foundry project endpoint in this order:

1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name

cli/azd/extensions/azure.ai.dataset/internal/cmd/root.go:16

  • [azd-code-reviewer] The inherited --environment/AZD_ENVIRONMENT value is discarded here. Both endpoint resolution and version persistence call Environment().GetCurrent(), which resolves the project's default environment, so -e staging can target the default Foundry project and write EVAL_DATASET_VERSION into the wrong environment. Thread extCtx.Environment through the context/resolver and use the named environment when it is set.
	rootCmd, _ := azdext.NewExtensionRootCommand(azdext.ExtensionCommandOptions{

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:243

  • [azd-code-reviewer] A 404 still returns an error here, contradicting the new command contract and the live tests that require an unknown name to produce an empty successful list. Normalize IsNotFound(err) to an empty DatasetList; otherwise versions list <unknown> still exits 1 on services that report absence with 404.
			list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
			if err != nil {
				return messages.ListingDatasetVersions(name, err)
			}

cli/azd/extensions/azure.ai.dataset/internal/cmd/output.go:34

  • [azd-code-reviewer] Unsupported output values are silently treated as human/table output because only json is recognized. For example, -o yaml exits successfully with non-YAML text, which can break automation. Register per-command output options with the extension SDK (for example json and table) so unsupported values fail validation.
	return strings.ToLower(v)
}

// isJSON reports whether the command should emit machine-readable output.
func isJSON(cmd *cobra.Command) bool {
	return outputFormat(cmd) == outputJSON

cli/azd/extensions/azure.ai.dataset/internal/messages/messages.go:357

  • [azd-code-reviewer] This message always claims --no-prompt is active, but requireFlag is also called during ordinary interactive invocations such as dataset delete <name>. The resulting error is factually wrong; either prompt when allowed or use a mode-neutral required-flag message.
    cli/azd/extensions/azure.ai.dataset/README.md:22
  • [azd-code-reviewer] The documented delete invocation always fails because the implementation requires --version. Include the required flag so the command table is directly runnable.
| `azd ai dataset delete <name>` | Delete a dataset version |

Copilot AI review requested due to automatic review settings August 16, 2026 15:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 61 out of 62 changed files in this pull request and generated no new comments.

Suppressed comments (7)

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:376

  • A 404 currently becomes DatasetVersionNotFound, so deleting an already-absent version exits non-zero. This directly contradicts the added idempotence test. Treat not-found as successful deletion and only return other service failures. (azd-code-reviewer)
				if dataset_api.IsNotFound(err) {
					return messages.DatasetVersionNotFound(name, version)
				}
				return messages.DeletingDatasetVersion(name, version, err)

cli/azd/extensions/azure.ai.dataset/README.md:42

  • The documented cascade omits two implemented sources: persisted azd ai project set context and the AZURE_AI_PROJECT_ENDPOINT fallback at both environment levels. Users can therefore get an endpoint they did not expect without the README explaining its precedence. Document all four levels in resolver order. (azd-code-reviewer)
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:249

  • An unknown dataset is returned as HTTP 404 by this API, so this branch currently exits with an error even though the new CLI tests require versions list to succeed with an empty array. Normalize a not-found response to an empty DatasetList; preserve other failures. (azd-code-reviewer)
			list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
			if err != nil {
				return messages.ListingDatasetVersions(name, err)

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:309

  • The API can return 404 for this version listing, but this path wraps it as a raw “resolving latest version” failure. That contradicts TestCLIUnknownDatasetIsBrief, which expects the same concise DatasetNotFound result used for an empty listing. Translate not-found before wrapping other errors. (azd-code-reviewer)
				list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
				if err != nil {
					return messages.ResolvingLatestDatasetVersion(name, err)

cli/azd/extensions/azure.ai.dataset/README.md:22

  • This documented invocation always fails because delete requires --version. Include the required flag so the command table is executable as written. (azd-code-reviewer)
| `azd ai dataset delete <name>` | Delete a dataset version |

cli/azd/extensions/azure.ai.dataset/internal/pkg/dataset_api/models.go:44

  • These transport aliases are serialized directly by every -o json path. A response using data_uri therefore emits data_uri, while a response using dataUri emits dataUri, so scripts receive different schemas for the same field. Map Dataset to a stable CLI output type that always exposes one resolved dataUri field. (azd-code-reviewer)
    cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:51
  • Cleanup silently returns when listing or JSON decoding fails and ignores every delete exit code, so the live suite can pass while leaving datasets behind. Make cleanup failures fail the test instead of swallowing them. (azd-code-reviewer)

… copy does

The two copies of dataset_api had drifted in exactly the two places bugs kept appearing: SAS redaction lived inline here and in an internal/urlsafe package there, and paging was split across list.go and operations.go here but had its own file there. Nine divergences have been found between these copies so far, four of them in these two areas.

This adopts the eval extension's shape so the remaining difference is a handful of function names rather than a different structure. Once both extensions are in main, a test can diff the two surfaces and fail on drift; it cannot run today because they are on separate branches.
Copilot AI review requested due to automatic review settings August 16, 2026 20:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated no new comments.

Suppressed comments (8)

cli/azd/extensions/azure.ai.dataset/internal/cmd/root.go:45

  • [azd-code-reviewer] None of the leaf commands registers its supported --output values, so the SDK neither validates the flag nor publishes valid values in command metadata; for example, -o jsno silently falls back to human output. Register output options on each executable command, as in azure.ai.projects/internal/cmd/project_show.go:59-63; the SDK contract is documented in pkg/azdext/extension_command.go:250-260.
	rootCmd.AddCommand(

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:243

  • [azd-code-reviewer] This still returns an error when the service represents an unknown dataset with 404, contradicting the documented empty-success behavior and TestCLIVersionsListOfAnUnknownNameSucceeds. Convert only a not-found response into an empty list; preserve other failures.
			list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
			if err != nil {
				return messages.ListingDatasetVersions(name, err)
			}

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:377

  • [azd-code-reviewer] Returning DatasetVersionNotFound makes deletion non-idempotent, while the PR's intended contract and TestCLIDeleteIsIdempotent require a missing version to succeed. Ignore only 404 and continue to the normal success output; keep surfacing all other failures.
				if dataset_api.IsNotFound(err) {
					return messages.DatasetVersionNotFound(name, version)
				}

cli/azd/extensions/azure.ai.dataset/internal/messages/messages.go:357

  • [azd-code-reviewer] This message always claims --no-prompt is active, but both create and delete call it whenever a required flag is absent, including ordinary interactive invocations. Remove the mode claim unless the caller actually checked that flag.
    cli/azd/extensions/azure.ai.dataset/README.md:22
  • [azd-code-reviewer] This documented command always fails because delete requires --version. Include the required flag so the command table is executable as written.
| `azd ai dataset delete <name>` | Delete a dataset version |

cli/azd/extensions/azure.ai.dataset/README.md:41

  • [azd-code-reviewer] The documented resolution order omits two implemented sources: the AZURE_AI_PROJECT_ENDPOINT fallback at both environment levels and the persisted global project context between them. Users debugging why a flagless command selected an endpoint would get the wrong precedence from this section.
1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name

cli/azd/extensions/azure.ai.dataset/CHANGELOG.md:17

  • [azd-code-reviewer] No command exposes content download and DownloadDatasetContent has no production caller, so this release-note bullet advertises a capability users cannot invoke. Remove the bullet or add the corresponding command surface.
- Reads dataset content back, whether the service hands out a blob URI or the
  container holding it.

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:309

  • [azd-code-reviewer] An unknown dataset can produce 404 from the version-list call (the API tests explicitly model this), so show <unknown> currently returns the wrapped HTTP response instead of the brief DatasetNotFound error promised by TestCLIUnknownDatasetIsBrief. Translate not-found here before wrapping other listing failures.
				list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
				if err != nil {
					return messages.ResolvingLatestDatasetVersion(name, err)

The shared redaction package arrived with the convergence commit but its name did not, so cspell failed on every import of it.
Copilot AI review requested due to automatic review settings August 16, 2026 20:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated no new comments.

Suppressed comments (8)

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:375

  • azd-code-reviewer: This contradicts TestCLIDeleteIsIdempotent: deleting a missing version returns an error here. Treat the service's 404 as successful no-op deletion so cleanup scripts remain idempotent.
				if dataset_api.IsNotFound(err) {
					return messages.DatasetVersionNotFound(name, version)
				}

cli/azd/extensions/azure.ai.dataset/README.md:41

  • azd-code-reviewer: The documented endpoint cascade omits the AZURE_AI_PROJECT_ENDPOINT fallbacks and the global extensions.ai-agents.project.context source that projectctx.Resolve checks before host variables. Users can therefore be surprised by an endpoint taking precedence over the three documented sources.
Every command resolves the Foundry project endpoint in this order:

1. `--project-endpoint`
2. `FOUNDRY_PROJECT_ENDPOINT` in the active azd environment
3. the host environment variable of the same name

cli/azd/extensions/azure.ai.dataset/README.md:22

  • azd-code-reviewer: The documented delete invocation always fails because the implementation requires --version. Include the required flag so the command table contains a runnable example.
| `azd ai dataset delete <name>` | Delete a dataset version |

cli/azd/extensions/azure.ai.dataset/internal/cmd/dataset.go:243

  • azd-code-reviewer: A 404 still takes this error path, so versions list <unknown> exits nonzero instead of returning the empty result promised by the new tests and command comment. Convert only IsNotFound(err) to an empty DatasetList; preserve other service failures.
			list, err := ec.datasetClient.ListDatasetVersions(ctx, name, ProjectEndpointAPIVersion)
			if err != nil {
				return messages.ListingDatasetVersions(name, err)
			}

cli/azd/extensions/azure.ai.dataset/internal/cmd/output.go:29

  • azd-code-reviewer: Any output value other than json is silently treated as human output, so a typo such as -o jsn succeeds and emits a table instead of rejecting an unsupported format. Register per-command azdext.FlagOptions with the supported values/defaults, as required for SDK-managed inherited flags.
	v, err := cmd.Flags().GetString("output")
	if err != nil {
		return ""
	}
	return strings.ToLower(v)

cli/azd/extensions/azure.ai.dataset/README.md:6

  • azd-code-reviewer: This onboarding command is guaranteed to fail in the state introduced by this PR: lines 54-57 explicitly say the missing registry entry prevents azd extension install from resolving. Replace it with working local pack/publish instructions until the registry entry lands, or add that entry before publishing this README.

This issue also appears on line 22 of the same file.

$ azd extension install azure.ai.dataset

cli/azd/extensions/azure.ai.dataset/tests/cli/dataset_test.go:52

  • azd-code-reviewer: Cleanup silently returns when listing or decoding fails, and it also ignores failed delete results. A transient service failure can therefore leave live datasets behind while the suite remains green; report cleanup failures and continue attempting the remaining versions.
    cli/azd/extensions/azure.ai.dataset/CHANGELOG.md:17
  • azd-code-reviewer: No command calls DownloadDatasetContent; only unit tests exercise it, and show returns metadata rather than file content. This release note advertises a user-visible capability the extension does not expose. Remove the bullet or add the corresponding command surface.
- Reads dataset content back, whether the service hands out a blob URI or the
  container holding it.

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

Labels

area/evals Evaluations, benchmarks, quality checks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an azd ai dataset extension for Foundry datasets

2 participants