diff --git a/sdk/ai/.skills/codegen/SKILL.md b/sdk/ai/.skills/codegen/SKILL.md new file mode 100644 index 000000000000..6e2b17892445 --- /dev/null +++ b/sdk/ai/.skills/codegen/SKILL.md @@ -0,0 +1,61 @@ +--- +name: codegen +description: Generate code from TypeSpec via tsp-client (update, sync, generate). Requires a tsp-location.yaml in the current working directory. Supports updating the commit hash before running. +--- + +# TypeSpec Code Generation (tsp-client) + +Use this skill to run `tsp-client` workflows for projects that include a `tsp-location.yaml` file. + +## Preconditions +- You must be in the directory that contains `tsp-location.yaml`. +- If the file is missing, warn the user and ask for the correct directory (do not run commands). + +## Commit hash update +If the user provides a commit hash, update the `commit:` field in `tsp-location.yaml` **before** running tsp-client. +- Read the file and locate the `commit:` line. +- Replace the value with the provided hash (keep the same key name and formatting). +- Example: + - Before: `commit: 6267b6...` + - After: `commit: ` + +## Commands + +### `tsp-client update` +Pull the latest codegen tooling or definitions (default action when the user is vague). +```bash +tsp-client update +``` + +### `tsp-client sync` +Fetch/sync TypeSpec inputs for the project. +```bash +tsp-client sync +``` + +### `tsp-client generate` +Generate code from TypeSpec inputs. +```bash +tsp-client generate +``` + +Keep the synced TypeSpec inputs: +```bash +tsp-client generate --save-inputs +``` + +## Steps +1. Verify `tsp-location.yaml` exists in the current directory. If not, stop and ask for the correct location. +2. If the user provided a commit hash, update the `commit:` value in `tsp-location.yaml`. +3. Determine the user intent: + - **Refresh/update/ingest changes from a commit**: run `tsp-client update`. + - **Fetch/sync spec from the current commit**: run `tsp-client sync`. + - **Generate from fetched spec**: run `tsp-client generate` (use `--save-inputs` only if the user asks to keep inputs). + - **Generate (no fetch requested)**: run `tsp-client generate`. +4. If the user doesn’t specify, default to `tsp-client update`. +5. If the project defines or creates a `TempTypeSpecFiles` folder and the user wants code generation, run `tsp-client generate` (with `--save-inputs` if requested). +6. If a tsp-client command fails, report the error output and suggest checking the TypeSpec repo/commit referenced in `tsp-location.yaml`. Build a GitHub URL from `repo:` and `directory:` (and include the `commit:` as the ref), e.g.: + - Repo: `Azure/azure-rest-api-specs` + - Commit: `6267b6...` + - Directory: `specification/cognitiveservices/OpenAI.Inference` + - URL: `https://github.com/Azure/azure-rest-api-specs/tree/6267b6.../specification/cognitiveservices/OpenAI.Inference` diff --git a/sdk/ai/.skills/cspell.yaml b/sdk/ai/.skills/cspell.yaml new file mode 100644 index 000000000000..89de4646568f --- /dev/null +++ b/sdk/ai/.skills/cspell.yaml @@ -0,0 +1,15 @@ +import: + - ../../../.vscode/cspell.json +overrides: + - filename: "**/sdk/ai/.skills/*" + words: + - Danimal + - Dcheckstyle + - Dcodesnippet + - Djacoco + - dpkg + - Drevapi + - Dspotbugs + - Dspotless + - FQCN + - javap \ No newline at end of file diff --git a/sdk/ai/.skills/dup-classes/SKILL.md b/sdk/ai/.skills/dup-classes/SKILL.md new file mode 100644 index 000000000000..3e3804509723 --- /dev/null +++ b/sdk/ai/.skills/dup-classes/SKILL.md @@ -0,0 +1,61 @@ +--- +name: dup-classes +description: Verify whether generated Java classes duplicate openai-java models by comparing fields/types (names may differ). Use when checking for duplicate model coverage. +--- + +# Duplicate Class Verification (Generated vs openai-java) + +Use this skill to compare generated Java models against the `openai-java` dependency. The goal is **field-by-field** comparison of model shapes, even when class or field names differ. + +## Inputs to confirm +- Generated source root (e.g., `src/main/java/...`) +- The relevant `pom.xml` (module) to resolve the `openai-java` dependency version +- Optional: package or class name hints to narrow the search + +## Steps +1. **Locate the pom.xml** in the current directory tree (`find . -name pom.xml`). If multiple, ask which module to use. +2. **Resolve openai-java**: + - Search the chosen pom for `openai-java` (or an explicit group/artifact provided by the user). + - Resolve the version (including properties) and locate the JAR in `~/.m2/repository`. +3. **List candidate classes**: + - Generated classes: scan the source root for `class` and `record` declarations. + - openai-java classes: `jar tf | rg '\.class$'` (filter by package hints if provided). +4. **Extract field signatures** (names may differ; compare shape): + - **Generated source**: + - For `record`, use the component list in the `record` declaration. + - For `class`, extract non-static field declarations (type + count) and note any `@JsonProperty` names. + - **openai-java JAR**: + - Use `javap -classpath -p ` to list fields (ignore `static`). + - If you need annotations, use `javap -classpath -p -verbose ` and look for `RuntimeVisibleAnnotations`. +5. **Compare shapes**: + - Compare **field count** and **field types** (order-independent). + - If `@JsonProperty` names exist in generated sources, compare those names with the openai-java field names. +6. **Report duplicates**: + - Provide a table: generated class → openai-java class, with matching field types and any name mismatches. + - Flag candidates with high similarity (same count and same types) even if names differ. + +## Useful commands + +### List generated class names +```bash +rg -n "^(public\s+)?(final\s+)?(class|record)\s+" +``` + +### Extract field lines from source (classes) +```bash +rg -n "^(\s*)(public|protected|private)\s+(static\s+)?(final\s+)?[A-Za-z0-9_<>,\[\].]+\s+[A-Za-z0-9_]+;" +``` + +### Extract record components +```bash +rg -n "record\s+[A-Za-z0-9_]+\s*\(([^)]*)\)" +``` + +### Inspect fields in a JAR class +```bash +javap -classpath -p +``` + +## Notes +- Use `search-m2` if you need help locating the dependency version or JAR path. +- If the user provides only a vague class hint, narrow candidates by package or field count first. diff --git a/sdk/ai/.skills/github/SKILL.md b/sdk/ai/.skills/github/SKILL.md new file mode 100644 index 000000000000..c8ee9fe1be6d --- /dev/null +++ b/sdk/ai/.skills/github/SKILL.md @@ -0,0 +1,156 @@ +--- +name: github +description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries." +--- + +# GitHub Skill + +Use the `gh` CLI to interact with GitHub. Always pass `--repo owner/repo` when not inside a cloned git directory. + +## Pull Requests + +List open PRs: +```bash +gh pr list --repo owner/repo +``` + +View a specific PR (summary, checks, comments): +```bash +gh pr view 55 --repo owner/repo +``` + +Check CI status on a PR: +```bash +gh pr checks 55 --repo owner/repo +``` + +## CI / Workflow Runs + +List recent runs: +```bash +gh run list --repo owner/repo --limit 10 +``` + +View a run summary (steps, status): +```bash +gh run view --repo owner/repo +``` + +View logs for failed steps only: +```bash +gh run view --repo owner/repo --log-failed +``` + +Re-run failed jobs: +```bash +gh run rerun --repo owner/repo --failed +``` + +## Issues + +List open issues (optionally filter by label): +```bash +gh issue list --repo owner/repo +gh issue list --repo owner/repo --label bug +``` + +View a specific issue: +```bash +gh issue view 42 --repo owner/repo +``` + +Create an issue: +```bash +gh issue create --repo owner/repo --title "Title" --body "Description" --label bug +``` + +## JSON Output & Filtering + +Most commands support `--json` with `--jq` for structured output: + +```bash +# List PR numbers and titles +gh pr list --repo owner/repo --json number,title --jq '.[] | "\(.number): \(.title)"' + +# List issues with assignees +gh issue list --repo owner/repo --json number,title,assignees \ + --jq '.[] | "\(.number): \(.title) → \(.assignees[].login // "unassigned")"' +``` + +## Advanced: `gh api` + +Use `gh api` for data or actions not covered by other subcommands. + +Fetch a PR with specific fields: +```bash +gh api repos/owner/repo/pulls/55 --jq '.title, .state, .user.login' +``` + +List check runs for a commit: +```bash +gh api repos/owner/repo/commits//check-runs \ + --jq '.check_runs[] | "\(.name): \(.conclusion)"' +``` + +Paginate results (e.g., all issues): +```bash +gh api --paginate repos/owner/repo/issues --jq '.[].title' +``` + +## Steps + +1. Check if `gh` is installed by running `gh --version`. + - If the command is **not found**, install it (see [Installation](#installation) below). +2. Check if `gh` is authenticated by running `gh auth status`. + - If not authenticated, run `gh auth login`. +3. If `owner/repo` is not provided, check if there is a `.git` directory and infer the remote via `gh repo view --json nameWithOwner`. Otherwise ask the user for the repo. +4. Choose the appropriate subcommand (`pr`, `issue`, `run`, `api`) based on the user's request. +5. Prefer structured subcommands (`gh pr`, `gh issue`, `gh run`) over raw `gh api` when they cover the use case. +6. Use `--json` + `--jq` when the user needs specific fields or wants to pipe output into further processing. +7. If a workflow run is failing, start with `gh pr checks` for a quick overview, then `gh run view --log-failed` for detailed output. +8. Report results clearly; if output is large, summarize and highlight the relevant parts. + +## Installation + +If `gh` is missing, install it using the recommended method for the current OS. +Detect the OS first, then run the matching command. + +### Windows +```powershell +winget install --id GitHub.cli +``` +> Note: open a **new terminal window** after installation for PATH changes to take effect. + +### macOS +```shell +brew install gh +``` + +### Linux (Debian / Ubuntu) +```bash +(type -p wget >/dev/null || (sudo apt update && sudo apt install wget -y)) \ + && sudo mkdir -p -m 755 /etc/apt/keyrings \ + && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + && cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ + && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && sudo mkdir -p -m 755 /etc/apt/sources.list.d \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && sudo apt update \ + && sudo apt install gh -y +``` + +### Linux (Fedora / RHEL / CentOS) +```bash +sudo dnf install 'dnf-command(config-manager)' +sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo +sudo dnf install gh --repo gh-cli +``` + +After installation, verify with `gh --version`, then authenticate with `gh auth login` if needed. + +## Notes + +- `gh` must be authenticated (`gh auth status`). If not, run `gh auth login` first. +- `--repo` accepts both `owner/repo` shorthand and full HTTPS URLs. +- For `gh api`, use `--method POST/PATCH/DELETE` for write operations and pass body fields with `-f field=value` or `-F field=`. +- `gh run list` defaults to the current branch when run inside a git repo; pass `--branch ` to target a specific branch. diff --git a/sdk/ai/.skills/release-notes/SKILL.md b/sdk/ai/.skills/release-notes/SKILL.md new file mode 100644 index 000000000000..e61eeed21659 --- /dev/null +++ b/sdk/ai/.skills/release-notes/SKILL.md @@ -0,0 +1,204 @@ +--- +name: release-notes +description: "Update CHANGELOG.md and README.md for an Azure SDK for Java package based on a GitHub PR. Use when the user wants to write or update release notes, changelogs, or readme docs from a PR reference." +--- + +# Release Notes Skill + +Update `CHANGELOG.md` and/or `README.md` for an Azure SDK for Java package using a GitHub PR as the source of truth. + +## Prerequisites + +- `gh` CLI authenticated (`gh auth status`). +- The current working directory must be the package root (where `CHANGELOG.md` and `README.md` live). + +## Inputs + +Ask the user for any missing inputs before proceeding: + +| Input | Required | Description | +|-------|----------|-------------| +| PR URL or number | **Yes** | GitHub PR to use as the source of changes. | +| Package directory | No | Defaults to `cwd`. Override if the user specifies a different package. | +| Scope | No | `changelog`, `readme`, or `both` (default: `both`). | + +## Step 1 — Gather PR information + +Use `gh` to collect the data you need. The diff may be too large for `gh pr diff`; fall back to the files API. + +```bash +# PR metadata +gh pr view --json title,body + +# File list with status (added/modified/removed/renamed) +gh api repos/{owner}/{repo}/pulls//files --paginate \ + --jq '.[] | .status + " " + .filename' + +# Renamed files (old → new) +gh api repos/{owner}/{repo}/pulls//files --paginate \ + --jq '.[] | select(.status == "renamed") | "\(.previous_filename) -> \(.filename)"' + +# Patch for a specific file (when you need detail) +gh api "repos/{owner}/{repo}/pulls//files?per_page=100" \ + --jq '.[] | select(.filename | test("")) | .patch' +``` + +Collect: +- Added, removed, and renamed model/enum classes. +- Changes to client classes (method renames, new methods, removed methods). +- Changes to `*ServiceVersion.java` (version string changes). +- Changes to `*ClientBuilder.java` (base URL, authentication, new builder methods). +- Changes to `module-info.java` (transitive exports, new requires). +- Changes to customization files. +- New or modified samples. + +## Step 2 — Check for existing entries + +Before writing anything, read the current `CHANGELOG.md` and `README.md` (if in scope) **in full** and compare their content against the changes you collected in Step 1. + +### 2a. Identify overlapping entries + +For each change you plan to document, check whether an entry already covers it: + +- **Exact match** — an existing bullet describes the same rename, addition, or removal using the same class/method names. +- **Topical overlap** — an existing bullet covers the same area (e.g., "tool renames" or "new sub-client") but with different detail, wording, or scope. + +### 2b. Report findings to the user + +If **any** overlap is found, **stop and consult the user before editing**. Present a summary like: + +> The following changes from PR #NNN already appear to be covered in the current files: +> +> **CHANGELOG.md** +> - _Features Added_ already mentions `FooClient` addition (line …). +> - _Breaking Changes_ already has a bullet about tool renames that partially overlaps the renames in this PR. +> +> **README.md** +> - The "Key concepts" section already lists the `BarClient` sub-client. +> +> Would you like me to: +> 1. Skip the entries that are already covered and only add the new ones? +> 2. Merge/update the overlapping entries (tell me how you'd like them worded)? +> 3. Proceed anyway and add everything as new entries? + +Wait for the user's response before continuing to Step 3 or Step 4. + +### 2c. No overlap + +If there is **no** overlap at all, inform the user briefly (e.g., "No existing entries overlap with this PR — proceeding to update.") and continue. + +## Step 3 — Update CHANGELOG.md + +### Format rules (CI-enforced) + +The CHANGELOG structure is **strict**. Every version section must contain exactly these headings in this order: + +```markdown +## (date or Unreleased) + +### Features Added + +### Breaking Changes + +### Bugs Fixed + +### Other Changes +``` + +- **Do not** add, remove, rename, or reorder these headings. +- **Do not** delete or modify existing entries — all changes are **additive only**. Append new bullets below existing ones. +- If Step 2 identified a topical overlap and the user chose to merge/update an existing entry, that is the **only** case where you may edit an existing bullet — and only as the user directed. +- Only modify the target version section (usually the `(Unreleased)` one). +- Each entry is a markdown list item starting with `- `. + +### What goes where + +| Heading | Content | +|---------|---------| +| **Features Added** | New public classes, methods, enums, samples, tools, client capabilities. | +| **Breaking Changes** | Renamed/removed classes, renamed methods, changed enum values, changed method signatures, service version changes. | +| **Bugs Fixed** | Fixes to incorrect behavior (e.g., URL construction, serialization bugs). | +| **Other Changes** | Dependency updates, spec regeneration, module-info changes, internal refactors. | + +### Writing guidelines + +1. **Summarize, don't enumerate.** Group related changes under a single bullet when there is an overarching pattern (e.g., "Methods across sub-clients were renamed to include the resource name" with sub-bullets for each client). Don't list every file touched. +2. **Consumer perspective.** Only mention changes that matter to a user of the library. Internal implementation model renames that are not in the public API can be omitted. +3. **Use code formatting** for class names, method names, and enum values: `` `ClassName` ``. +4. **Show before → after** for renames: `` `OldName` → `NewName` `` or `` `OldName` renamed to `NewName` ``. +5. **Group repetitive renames** by pattern. For example, if 10 tool classes were renamed from `*AgentTool` to `*Tool`, write one bullet with representative examples rather than 10 bullets. +6. **Mention service version changes** (e.g., date-based to `v1`) in Breaking Changes. +7. **Don't over-list new models.** If the PR adds dozens of generated models, mention only the notable ones (new tool types, new feature-area models) and say "and related types" or similar. +8. **Omit trivial internal changes** like parameter reordering in generated `@HostParam`/`@QueryParam` annotations, checkstyle suppression updates, or whitespace. + +## Step 4 — Update README.md + +### Format rules (CI-enforced) + +The README structure is also checked by CI. Follow the existing heading hierarchy exactly: + +``` +# client library for Java +## Documentation +## Getting started +### Prerequisites +### Adding the package to your product +### Authentication +## Key concepts +### +## Examples +### +### Service API versions +#### Select a service API version +## Troubleshooting +## Next steps +## Contributing + +``` + +- **Do not** remove or reorder the top-level headings. +- **Do not** change the `[//]: #` version-update markers. +- **Do not** delete or rewrite existing prose or snippets unless they reference renamed APIs from this PR and Step 2 confirmed no conflict (or the user approved the change). +- You **may** add new `###` subsections under `## Key concepts` or `## Examples`. +- Keep existing code snippets intact unless they reference renamed APIs. + +### What to update + +1. **Package description** (opening paragraph): mention the REST API version if it changed (e.g., "targets the **v1** REST API"). +2. **Code snippets**: update any code that references renamed methods, classes, or builder patterns. Keep the `java com.azure...` snippet tags intact. +3. **Sub-client lists**: if new sub-clients were added, add them. Mark preview sub-clients with **(preview)**. +4. **Preview tools / features**: if the package defines `Tool` subclasses, document which are GA and which are preview (look for `Preview` in the class name or discriminator value). Use a table. +5. **Opt-in flags / experimental features**: if the package uses `FoundryFeaturesOptInKeys`, `AgentDefinitionFeatureKeys`, or `Foundry-Features` headers, document: + - Which sub-clients auto-set the header (check `*Impl.java` for hardcoded `foundryFeatures` strings). + - Which accept it as an optional parameter. + - List known flag values. +6. **OpenAI direct-usage snippets**: if the builder URL construction changed (e.g., `/openai` → `/openai/v1`), update the snippet and surrounding prose. Remove references to removed imports like `AzureUrlPathMode` or `AzureOpenAIServiceVersion` if they no longer apply. + +### Determining preview tools + +For the `azure-ai-agents` package, look at classes extending `com.azure.ai.agents.models.Tool`: + +```bash +# Read the Tool.java discriminator to find all subtypes +grep -A1 'equals(discriminatorValue)' src/main/java/com/azure/ai/agents/models/Tool.java +``` + +Tools whose discriminator value or class name contains `preview` are preview tools. All others are GA. + +### Determining preview operation groups + +Check which `*Impl.java` files hardcode a `foundryFeatures` value: + +```bash +grep -rl "final String foundryFeatures" src/main/java/*/implementation/*.java +``` + +Those operation groups are preview and auto-opt-in. Also check convenience client classes for `FoundryFeaturesOptInKeys` parameters — those are opt-in by caller. + +## Notes + +- **All edits are additive.** Never remove or rewrite existing content unless the user explicitly approves it after being consulted in Step 2. +- When the PR diff is too large for `gh pr diff` (HTTP 406), use `gh api .../pulls//files --paginate` instead. +- Paginate with `--paginate` and page with `?per_page=100&page=N` as needed. +- Always read the existing CHANGELOG and README **before** editing to avoid duplicating entries or breaking structure. +- If the PR title/body provides a summary, use it as a starting point but verify against the actual file changes. diff --git a/sdk/ai/.skills/run-tests/SKILL.md b/sdk/ai/.skills/run-tests/SKILL.md new file mode 100644 index 000000000000..49c95632165c --- /dev/null +++ b/sdk/ai/.skills/run-tests/SKILL.md @@ -0,0 +1,58 @@ +--- +name: run-tests +description: Run project tests using Maven (mvn). Use when the user asks to run tests. +--- + +# Run Tests (Maven) + +Use Maven (`mvn`) to run tests. Confirm you are in a directory with a `pom.xml` (project root or module root). + +## Common commands + +### All tests (default) +```bash +mvn test +``` + +### Specific module (multi-module) +```bash +mvn -pl -am test +``` + +### Specific test class or method (Surefire) +```bash +mvn -Dtest=MyTest test +mvn -Dtest=MyTest#myMethod test +``` + +## Test modes (AZURE_TEST_MODE) +If the user asks for live/record/playback, set the env var for the command: +```powershell +$env:AZURE_TEST_MODE = "LIVE" +mvn test +``` + +## Running tests with secrets + +Tests often require environment variables with connection strings, keys, or other secrets. If another skill or tool for loading secrets or work resources is available, consult the user and use it. Otherwise, ask the user to provide the required environment variables manually. To discover which variables are needed, search for classes named `*ClientTestBase` in the test sources — these typically define the expected environment variable names. + +## Steps +1. Ensure you're in the correct Maven project directory (contains `pom.xml`). If not, ask for the correct path. +2. Start simple: `mvn "-Dtest=" test`. Only add flags if something fails. +3. If the user provides a test name, use `-Dtest=`. +4. If the user specifies a module, use `-pl -am test`. +5. If the user specifies a test mode (LIVE/RECORD/PLAYBACK), set the env var. +6. If tests require secrets, use an available skill or tool for loading secrets if one exists (consult the user first); otherwise ask the user to provide the required environment variables manually. +7. If the command fails, report the error output and ask how they want to proceed. + +## Troubleshooting (only add flags when needed) + +Apply these **only** if the simple `mvn test` command fails with the specific error described: + +- **Samples fail to compile on Java 8 base-testCompile**: add `-Dbuildhelper.addtestsource.skip=true -Dbuildhelper.addtestresource.skip=true` +- **JPMS/module-path errors** (e.g., `okio` module issues): add `-Dsurefire.useModulePath=false` +- **Build plugins block the run**: add skip flags as needed, e.g. `-Denforcer.skip=true -Dcodesnippet.skip=true -Dcheckstyle.skip=true` +- **Reactor blocking errors in async tests** (Netty thread): add `$env:AZURE_TEST_HTTP_CLIENTS = "okhttp"` +- **SSL handshake / PKIX path building failed** (`SSLHandshakeException`, `unable to find valid certification path to requested target`): the JVM's trust store is missing the corporate root CA. On **Windows**, add `-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT` to use the Windows certificate store. On **Linux/macOS**, the corporate root CA must be imported into the JVM trust store (or configure a custom one via `-Djavax.net.ssl.trustStore` / `-Djavax.net.ssl.trustStorePassword`). This is common on corporate networks with proxy/firewall TLS interception. + +Do NOT preemptively add all these flags. Start simple and escalate only on failure. diff --git a/sdk/ai/.skills/search-m2/SKILL.md b/sdk/ai/.skills/search-m2/SKILL.md new file mode 100644 index 000000000000..d3a09aa52afd --- /dev/null +++ b/sdk/ai/.skills/search-m2/SKILL.md @@ -0,0 +1,49 @@ +--- +name: search-m2 +description: Search for Java classes inside Maven dependencies in ~/.m2. Use when the user asks to locate classes or inspect JARs. Cross-reference pom.xml files in the current directory to resolve dependency names/versions. +--- + +# Search Maven Local Repository (search-m2) + +Use this skill to find which dependency JAR contains a class and to inspect JAR contents in `~/.m2/repository`. + +## Preconditions +- Work from the user’s current directory. +- Look for `pom.xml` files in the current directory tree and use them to resolve dependency names and versions. + +## Key paths +- Maven local repo: `~/.m2/repository` +- Dependency JAR path pattern: + `~/.m2/repository////-.jar` + +## Steps +1. **Discover pom files**: run `find . -name pom.xml` from the current directory. + - If multiple poms exist, prefer the closest one to the current directory or ask the user which project/module to use. +2. **Extract dependency coordinates**: + - If the user provides a group/artifact, search the poms for that dependency and read its version. + - If versions are defined via properties (e.g., `${foo.version}`), resolve the property from the same pom (or parent if obvious). +3. **Resolve the JAR path** using the groupId/artifactId/version mapping above. + - If the version is not found, list available versions in `~/.m2/repository///` and ask the user which to inspect. +4. **Search for classes**: + - List classes: `jar tf | rg '\.class$'` + - Find a specific class: `jar tf | rg '(\.class)?$'` +5. **Inspect class details** (if requested): + - `javap -classpath ` + +## Useful commands + +### Find a class across all jars (fallback) +```bash +rg -g '*.jar' --files ~/.m2/repository | while read -r jar; do + jar tf "$jar" | rg -q 'com/example/MyClass.class' && echo "$jar" +done +``` + +### Resolve versions for a dependency +```bash +ls ~/.m2/repository/// +``` + +## Notes +- If the user is vague, start by identifying the relevant pom.xml and extracting dependency coordinates. +- If the dependency is transitive and not in the pom, check `mvn -q -DskipTests dependency:tree` (only if needed and the user agrees). diff --git a/sdk/ai/.skills/test-proxy/SKILL.md b/sdk/ai/.skills/test-proxy/SKILL.md new file mode 100644 index 000000000000..18644e275288 --- /dev/null +++ b/sdk/ai/.skills/test-proxy/SKILL.md @@ -0,0 +1,36 @@ +--- +name: test-proxy +description: Push test-proxy recordings/assets using the test-proxy CLI (e.g., test-proxy push -a assets.json). Use when publishing recordings. +--- + +# Test Proxy Recordings + +Use this skill to publish recordings to the test-proxy assets repo. + +## Command +```bash +test-proxy push -a assets.json +``` + +## Steps +1. Check if `test-proxy` is installed by running `test-proxy --version`. + - If the command is **not found**, install it (see [Installation](#installation) below). +2. Confirm the assets file path (default: `assets.json` in the current directory). +3. If the file location is unclear, search for it or ask the user. +4. Run `test-proxy push -a `. +5. Report success or any errors. + +## Installation + +If `test-proxy` is missing, install it as a .NET global tool. + +### Prerequisites +.NET 8.0 (LTS) or later must be installed. Verify with `dotnet --version`. +If missing, ask the user to install the .NET SDK from https://dotnet.microsoft.com/download. + +### Install command +```powershell +dotnet tool update azure.sdk.tools.testproxy --global --prerelease --add-source https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json --ignore-failed-sources +``` + +After installation, verify with `test-proxy --version`. diff --git a/sdk/ai/.skills/tsp-naming-collision/SKILL.md b/sdk/ai/.skills/tsp-naming-collision/SKILL.md new file mode 100644 index 000000000000..de6c9afa3a78 --- /dev/null +++ b/sdk/ai/.skills/tsp-naming-collision/SKILL.md @@ -0,0 +1,145 @@ +--- +name: tsp-naming-collision +description: Fix Java codegen parameter names that end with a numeric suffix (e.g. createAgentRequest1) caused by TypeSpec model names colliding with synthetic body type names. Use when generated Java client methods have parameter names ending in '1'. +--- + +# Fix TypeSpec Naming Collisions in Java Codegen + +Fix parameter and implementation-model names that end with a numeric suffix (e.g. `createAgentRequest1`) in generated Java client code. This happens when a TypeSpec **named `model`** collides with the **synthetic body type** the Java codegen creates for an operation that spreads that model. + +## Root Cause + +When a TypeSpec route **spreads** a named model into an operation's parameters (via `...ModelName`), the Java codegen creates a synthetic body type to hold the body properties. It names this type `{OperationName}Request` (PascalCase of the operation name + `Request`). If a TypeSpec `model` already has that exact name, the codegen resolves the collision by appending `1`. + +Example collision: +- TypeSpec model: `CreateAgentRequest` +- Operation: `createAgent` spreads `...CreateAgentRequest` +- Codegen synthetic body: wants name `CreateAgentRequest` → collision → `CreateAgentRequest1` +- Result: parameter `createAgentRequest1`, implementation class `CreateAgentRequest1.java` + +> **Why aliases don't collide:** TypeSpec `alias` declarations don't occupy a name in the type namespace, so there is no collision. If the upstream spec used `alias CreateAgentRequest = { ... }` instead of `model CreateAgentRequest { ... }`, no fix would be needed. + +## Preconditions + +- You must be in the directory that contains `tsp-location.yaml`. +- The TypeSpec must already be synced locally into `TempTypeSpecFiles/`. If not, run `tsp-client sync` first. +- Identify the `client.tsp` file inside `TempTypeSpecFiles/` (usually under a subdirectory like `sdk-agents/`). This is the customization file where fixes are applied. + +## Important: TempTypeSpecFiles is volatile + +`TempTypeSpecFiles/` is regenerated on every `tsp-client sync` or `tsp-client update`. **Changes made only in `TempTypeSpecFiles/` will be lost.** Always apply the same edits to the corresponding `client.tsp` in a local checkout of `Azure/azure-rest-api-specs` (if available) so the changes can be committed to a PR. + +## Workflow + +### 1. Identify affected operations + +Search the generated Java client classes for parameter names ending with `1`: + +```bash +grep -n 'Request1[,)]' src/main/java/com/azure/ai/agents/*Client.java +``` + +Also check for implementation model classes with the `1` suffix: + +```bash +find . -name "*Request1.java" -path "*/implementation/models/*" +``` + +Collect the list of affected names (e.g. `createAgentRequest1`, `updateAgentRequest1`). + +### 2. Trace back to the TypeSpec models + +For each affected parameter, find the TypeSpec model that causes the collision. The model name matches the parameter name (PascalCase, without the `1`). + +Search the `.tsp` files: + +```bash +grep -rn "model CreateAgentRequest\|model UpdateAgentRequest" TempTypeSpecFiles/ --include="*.tsp" +``` + +Confirm the model is used via spread (`...ModelName`) in the route definitions: + +```bash +grep -rn "CreateAgentRequest\|UpdateAgentRequest" TempTypeSpecFiles/ --include="*.tsp" +``` + +Verify the model is a `model` (not an `alias`). Only named `model` types cause collisions. + +### 3. Add `@@clientName` overrides + +Edit the `client.tsp` customization file in `TempTypeSpecFiles/`. Add a `@@clientName` directive for each colliding model to give it a different client-side name. This frees the original name for the codegen's synthetic body type. + +Use a consistent naming convention. Recommended: rename `*Request` → `*Input`: + +```tsp +// Rename request models to avoid collision with synthetic body types generated +// by the Java codegen. The codegen names synthetic bodies as {OperationName}Request, +// which clashes with the identically-named TypeSpec models, causing a "1" suffix. +@@clientName(CreateAgentRequest, "CreateAgentInput"); +@@clientName(UpdateAgentRequest, "UpdateAgentInput"); +``` + +> **Note:** These models are typically not emitted as public Java classes — they only exist to be spread into operations. The rename is purely internal and does not affect the public API surface. + +### 4. Regenerate and verify + +Generate with `--save-inputs` to preserve the edited TypeSpec files: + +```bash +tsp-client generate --save-inputs +``` + +Verify the `1` suffix is gone: + +```bash +# Should return zero matches +grep -c "Request1" src/main/java/com/azure/ai/agents/*Client.java + +# Old *Request1.java files should no longer exist +find src -name "*Request1.java" -path "*/implementation/models/*" + +# New clean-named files should exist +find src -name "*Request.java" -path "*/implementation/models/*" +``` + +Compile to confirm no breakage: + +```bash +mvn compile -Denforcer.skip=true -Dcodesnippet.skip=true -Dcheckstyle.skip=true \ + -Dspotbugs.skip=true -Dspotless.skip=true -Drevapi.skip=true -Djacoco.skip=true \ + -Dmaven.javadoc.skip=true -Dshade.skip=true -Danimal.sniffer.skip=true +``` + +### 5. Apply changes to the local spec repo (if available) + +If the user has a local checkout of `Azure/azure-rest-api-specs`, apply the **same `client.tsp` edits** there. Derive the file path from `tsp-location.yaml`: + +- `directory` field gives the relative spec path (e.g. `specification/ai-foundry/data-plane/Foundry/src/sdk-agents`) +- The file to edit is `client.tsp` inside that directory + +For example, if the local repo is at ``: + +``` +/specification/ai-foundry/data-plane/Foundry/src/sdk-agents/client.tsp +``` + +Verify the file exists before editing. If it doesn't, warn the user and print the expected path. + +### 6. Full round-trip from the remote spec (optional) + +If the user wants to validate the fix end-to-end from the remote repo: + +1. Commit and push the `client.tsp` changes in the spec repo +2. Get the new commit hash +3. Update the `commit:` field in `tsp-location.yaml` with the new hash +4. Run `tsp-client update` to sync and regenerate from the remote +5. Verify the `1` suffix is gone and the build compiles + +## Troubleshooting + +| Symptom | Cause | Fix | +|---------|-------|-----| +| `1` suffix persists after adding `@@clientName` | The `@@clientName` target doesn't match the TypeSpec model name exactly | Double-check the model name is the TypeSpec name (not the Java name); names are case-sensitive | +| New suffix appears (e.g. `2`) | Multiple models collide with the same synthetic name | Ensure every colliding model has a unique `@@clientName` | +| Build fails after regeneration | Handwritten code references the old `*1` names | Update any manual references in custom client code, tests, or samples | +| Changes lost after `tsp-client sync` | `TempTypeSpecFiles/` was overwritten | Apply changes to the spec repo `client.tsp` (see step 5) | diff --git a/sdk/ai/.skills/tsp-type-override/SKILL.md b/sdk/ai/.skills/tsp-type-override/SKILL.md new file mode 100644 index 000000000000..b7a45f1341ed --- /dev/null +++ b/sdk/ai/.skills/tsp-type-override/SKILL.md @@ -0,0 +1,207 @@ +--- +name: tsp-type-override +description: Override TypeSpec types with Java-native types (e.g. OffsetDateTime, DayOfWeek) using @@alternateType in a client.java.tsp file. Use when a TypeSpec model field has an incorrect or too-generic type that should map to a specific Java type. +--- + +# TypeSpec Type Override for Java + +Override types in generated Java code by adding `@@alternateType` decorators to the `client.java.tsp` file. + +## Preconditions +- You must be in the directory that contains `tsp-location.yaml`. +- The TypeSpec must already be synced locally into `TempTypeSpecFiles/`. If not, run `tsp-client sync` first. +- Identify the `client.java.tsp` file inside `TempTypeSpecFiles/`. This is the **only** file you should edit. + +## Important: TempTypeSpecFiles is volatile + +`TempTypeSpecFiles/` is a **transient working directory** managed by `tsp-client`. It is regenerated on every `tsp-client sync` or `tsp-client update` and is typically gitignored. **Any changes made only in `TempTypeSpecFiles/` will be lost** on the next sync/update cycle. + +If the user provides a **local checkout of `Azure/azure-rest-api-specs`**, always apply the same `client.java.tsp` edits there so the changes are preserved and can be committed to a PR. The path to the spec file inside that repo can be derived from `tsp-location.yaml`: +- `directory` field gives the relative path (e.g. `specification/ai-foundry/data-plane/Foundry`) +- The file to edit is `client.java.tsp` inside that directory + +## Workflow + +### 1. Locate the TypeSpec model and field + +Search the `.tsp` files under `TempTypeSpecFiles/` for the model and field the user wants to override: + +```bash +grep -rn "\|" TempTypeSpecFiles/ --include="*.tsp" +``` + +Read the model definition to confirm the current type of the target field (e.g. `string`, `int32`, a union, etc.). + +### 2. Determine the correct decorator form + +There are **two forms** of `@@alternateType`. Choose based on the target type: + +#### Form A — TypeSpec built-in type (preferred when possible) + +Use when there is a TypeSpec scalar that the Java emitter already maps to the desired Java type. + +| Desired Java type | TypeSpec alternate | +|-----------------------|------------------------| +| `OffsetDateTime` | `utcDateTime` | +| `Duration` | `duration` | +| `byte[]` | `bytes` | +| `long` / `Long` | `int64` | +| `double` / `Double` | `float64` | + +Syntax (applied to a **model property**, scoped to Java): + +```tsp +@@alternateType(ModelName.fieldName, utcDateTime, "java"); +``` + +This form is **fully supported** on model properties. + +#### Form B — External Java type via identity + +Use when no TypeSpec scalar maps to the desired Java type (e.g. `java.time.DayOfWeek`). + +Syntax (applied to the **type definition itself**, not a property): + +```tsp +@@alternateType(TypeName, { identity: "fully.qualified.ClassName" }, "java"); +``` + +> **Important constraints for external types:** +> - External types (`{ identity: ... }`) **cannot** be applied to model properties — they must target the type definition (Model, Enum, Union, Scalar). +> - A `scope` parameter (e.g. `"java"`) is **required** for external types. +> - **Known limitation (as of typespec-java 0.39.x):** The Java emitter does not fully support external types on Enum/Union definitions. It will still generate the class instead of referencing the JDK type. This is tracked as a bug. Only use Form B for Model types until the emitter is fixed. + +### 3. Apply the override + +Edit the `client.java.tsp` file inside `TempTypeSpecFiles/`. Add the decorator(s) under the type-replacement section (usually at the bottom of the file): + +```tsp +// EvaluatorVersion datetime fields are typed as string in the spec +@@alternateType(EvaluatorVersion.created_at, utcDateTime, "java"); +``` + +### 4. Generate and verify + +Always generate with `--save-inputs` so the edited TypeSpec files are preserved: + +```bash +tsp-client generate --save-inputs +``` + +After generation, verify the Java source uses the expected type: + +```bash +grep -n "OffsetDateTime\|DayOfWeek\|" src/main/java/com/azure/ai/projects/models/.java +``` + +Check that: +- The field type changed (e.g. `private OffsetDateTime createdAt;`) +- The getter return type changed (e.g. `public OffsetDateTime getCreatedAt()`) +- The JSON deserialization uses the correct parser (e.g. `CoreUtils.parseBestOffsetDateTime`) +- No spurious files were generated (e.g. under `src/main/java/java/`) + +### 5. Write unit tests for serialization/deserialization + +After generation, **always** write a unit test that verifies the generated model serializes and deserializes the overridden type to the **same wire-format values** defined in the original TypeSpec. This is critical because the emitter may generate serialization code that does not match the API wire format (e.g. `java.time.DayOfWeek.name()` produces `"MONDAY"` but the TypeSpec union defined `"Monday"`). + +Place the test class under `src/test/java/` in the model's package (e.g. `com.azure.ai.projects.models`). + +The test must cover three scenarios: + +1. **Serialization** — Construct the model with the Java type, serialize to JSON, and assert the JSON string values match the TSP-defined wire format (e.g. PascalCase `"Monday"`, not UPPER_CASE `"MONDAY"`). +2. **Deserialization** — Parse a JSON string using the TSP-defined wire-format values and assert the Java type is correctly populated. +3. **Round-trip** — Serialize → deserialize and assert the original values are preserved. + +Example test skeleton: + +```java +@Test +void serializationProducesWireFormatValues() throws IOException { + // Build model with the Java type + var schedule = new WeeklyRecurrenceSchedule(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.FRIDAY)); + String json = toJsonString(schedule); + // Assert the wire values match the TSP union/enum values, NOT the Java enum constant names + String expected = "{\"daysOfWeek\":[\"Monday\",\"Friday\"],\"type\":\"Weekly\"}"; + assertEquals(expected, json); +} + +@Test +void deserializationParsesWireFormatValues() throws IOException { + // Use TSP-defined wire-format values + String json = "{\"daysOfWeek\":[\"Monday\",\"Wednesday\"],\"type\":\"Weekly\"}"; + WeeklyRecurrenceSchedule schedule; + try (JsonReader reader = JsonProviders.createReader(json)) { + schedule = WeeklyRecurrenceSchedule.fromJson(reader); + } + assertEquals(Arrays.asList(DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY), schedule.getDaysOfWeek()); +} + +@Test +void roundTripPreservesValues() throws IOException { + var original = new WeeklyRecurrenceSchedule(Arrays.asList(DayOfWeek.SUNDAY, DayOfWeek.SATURDAY)); + String json = toJsonString(original); + WeeklyRecurrenceSchedule deserialized; + try (JsonReader reader = JsonProviders.createReader(json)) { + deserialized = WeeklyRecurrenceSchedule.fromJson(reader); + } + assertEquals(original.getDaysOfWeek(), deserialized.getDaysOfWeek()); +} +``` + +#### If the tests fail: customize toJson/fromJson + +When the emitter generates incorrect serialization (e.g. `element.name()` instead of PascalCase), you must manually fix the `toJson` and `fromJson` methods in the generated model class: + +1. **Remove the `@Generated` annotation** from `toJson` and `fromJson`. This ensures your customizations survive future `tsp-client generate` / `tsp-client update` runs — the codegen will not overwrite methods that lack `@Generated`. +2. Fix the serialization logic to convert between the Java type and the TSP wire format. For example, for `java.time.DayOfWeek`: + - **`toJson`**: convert `DayOfWeek.MONDAY` → `"Monday"` (PascalCase) using a helper like: + ```java + private static String toPascalCase(DayOfWeek day) { + String name = day.name(); + return name.charAt(0) + name.substring(1).toLowerCase(Locale.ROOT); + } + ``` + - **`fromJson`**: convert `"Monday"` → `DayOfWeek.MONDAY` by uppercasing before `valueOf()`: + ```java + DayOfWeek.valueOf(reader.getString().toUpperCase(Locale.ROOT)) + ``` +3. Re-run the unit tests and confirm all three scenarios pass. + +### 6. Apply changes to the local spec repo (if provided) + +If the user supplied a local checkout path for `Azure/azure-rest-api-specs`, apply the **same edits** to the `client.java.tsp` there. Derive the file path from `tsp-location.yaml`: + +``` +//client.java.tsp +``` + +For example, if `directory: specification/ai-foundry/data-plane/Foundry` and the local repo is at `~/code/azure-rest-api-specs`: + +``` +~/code/azure-rest-api-specs/specification/ai-foundry/data-plane/Foundry/client.java.tsp +``` + +Verify the file exists before editing. If it doesn't, warn the user and print the expected path. + +### 7. Remind the user about the spec PR + +After confirming the generated code is correct, remind the user: + +> The `@@alternateType` changes in `client.java.tsp` are local overrides in `TempTypeSpecFiles/`. +> For these to persist across future code generations, the same changes must be contributed to the +> **Azure/azure-rest-api-specs** repository via a pull request targeting the corresponding +> `client.java.tsp` file under the `specification/` directory. +> +> Build the PR URL from `tsp-location.yaml`: +> - Repo: `repo` field (e.g. `Azure/azure-rest-api-specs`) +> - Directory: `directory` field (e.g. `specification/ai-foundry/data-plane/Foundry`) +> - File: `client.java.tsp` in that directory + +## Troubleshooting + +| Symptom | Cause | Fix | +|---------|-------|-----| +| Generated class still uses `String` | Decorator not picked up | Verify the model/field names match exactly (case-sensitive, use the TypeSpec name, not the Java name) | +| File generated under `src/main/java/java/time/...` | External type identity used on an Enum/Union | Remove the decorator — this is the known emitter bug for Enum/Union external types | +| Compiler error on `@@alternateType` | Wrong target kind | External types must target type definitions, not properties. TypeSpec built-ins can target properties. | +| Warning: `external-type-on-model-property` | External type `{ identity: ... }` applied to a property | Move the decorator to the type definition instead |