| name | generate-from-typespec |
| description | Generate Python SDK code from a TypeSpec specification using the local emitter. Use this skill when the user wants to generate/regenerate a Python client from a TypeSpec spec, provides a GitHub URL or local path to a TypeSpec project, or says things like "generate from this spec", "emit Python from this tsp", "regenerate the SDK", or "compile this TypeSpec for Python".
|
Generate From TypeSpec Skill
Compiles a TypeSpec specification using the local @typespec/http-client-python
emitter and generates Python SDK code. Supports both branded (@azure-tools/typespec-python)
and unbranded (@typespec/http-client-python) generation.
Inputs
The caller must provide:
- Spec path — either:
- A local file path to a
.tsp entry point (e.g., client.tsp, main.tsp)
- A raw GitHub URL pointing to a TypeSpec project directory or file
(e.g.,
https://github.com/Azure/azure-rest-api-specs/tree/main/specification/.../Foundry/src/sdk-service-agentserver-contracts/client.tsp)
- Flavor —
azure (branded) or unbranded. If not provided, ask the user.
- Emitter output directory — the full resolved path where generated code
should be written (e.g.,
~/Desktop/github/azure-sdk-for-python/sdk/agentserver/azure-ai-agentserver-responses).
If not provided, ask the user.
- Additional options (optional) — any extra
key=value emitter options the
user wants applied on top of the tspconfig options. These override tspconfig
values if there's a conflict (e.g., generate-typeddict=false).
Workflow
Step 1: Resolve the spec path
If the input is a GitHub URL:
- Parse the URL to extract
owner, repo, ref (branch/commit), and path.
- Check if the repository is cloned locally (look under common locations like
~/Desktop/github/<repo-name>, ~/<repo-name>, etc.).
- If found locally, check out the correct ref/commit if needed:
cd <local-repo>
git fetch origin <ref>
git checkout <ref> -- <path-to-spec-dir>/
- If not found locally, ask the user where the repo is cloned, or offer to clone it.
If the input is a local path:
Verify the file/directory exists. If the path points to a directory, look for
client.tsp or main.tsp as the entry point.
Step 2: Locate and parse tspconfig.yaml
Look for tspconfig.yaml in the same directory as the spec entry point, then
walk up parent directories until one is found.
current_dir="<spec-dir>"
while [ "$current_dir" != "/" ]; do
if [ -f "$current_dir/tspconfig.yaml" ]; then
echo "Found: $current_dir/tspconfig.yaml"
break
fi
current_dir=$(dirname "$current_dir")
done
Step 3: Extract Python emitter options from tspconfig.yaml
Parse the tspconfig.yaml and extract the options block for the Python emitter.
The emitter may appear under either name:
| Flavor | Emitter key in tspconfig.yaml |
|---|
| Branded | @azure-tools/typespec-python |
| Unbranded | @typespec/http-client-python |
Important cross-flavor rule: If the user requests a different flavor than
what's in the tspconfig, carry over ALL options from the tspconfig's Python
emitter block. For example:
- tspconfig has options under
@azure-tools/typespec-python but user wants
unbranded → use all those options, but emit under @typespec/http-client-python
- tspconfig has options under
@typespec/http-client-python but user wants
branded → use all those options, but emit under @azure-tools/typespec-python
If no Python emitter options exist in the tspconfig at all, ask the user for:
emitter-output-dir (required — where to write the generated code)
package-name (required)
namespace (optional — omit to let @clientNamespace decorators resolve naturally)
Step 4: Determine the flavor
Use this precedence:
- If the user explicitly stated
azure or unbranded, use that.
- If the tspconfig has a
flavor option set, mention it to the user and confirm.
- If the tspconfig only has one Python emitter key, infer:
@azure-tools/typespec-python → azure
@typespec/http-client-python → unbranded
- If still ambiguous, ask the user:
"Should I generate as branded (azure flavor) or unbranded?"
Step 5: Find the TypeSpec compiler
Look for the tsp CLI in the spec repo's node_modules:
< spec-repo-root > /node_modules/@typespec/compiler/cmd/tsp.js
If not found, fall back to the global tsp command, or check the typespec
monorepo's compiler:
~/Desktop/github/typespec/packages/compiler/cmd/tsp.js
Step 6: Construct and run the compile command
Build the tsp compile command using:
- Entry point: The resolved
.tsp file from Step 1
--emit: Always the local emitter path:
~/Desktop/github/typespec/packages/http-client-python
--option flags: One for each option from the tspconfig, prefixed with
the local emitter name @typespec/http-client-python (regardless of
what the tspconfig called it). Any additional options provided by the user
are appended last and override tspconfig values if there's a conflict.
Template:
< tsp-cli-path > compile < entry-point.tsp > --emit ~/Desktop/github/typespec/packages/http-client-python \
--option "@typespec/http-client-python.<key1>=<value1>" \
--option "@typespec/http-client-python.<key2>=<value2>" \
...
Option mapping rules:
| tspconfig key | CLI --option key | Notes |
|---|
emitter-output-dir | emitter-output-dir | Resolve {output-dir}, {service-dir} variables |
package-mode | package-mode | Usually dataplane or mgmt |
package-name | package-name | |
namespace | namespace | Omit if not in tspconfig — see note below |
api-version | api-version | |
flavor | flavor | Set to azure for branded, omit for unbranded |
generate-test | generate-test | |
generate-sample | generate-sample | |
models-mode | models-mode | e.g., dpg, msrest, typeddict |
| Any other option | Pass through as-is | |
Namespace note: Do NOT pass --namespace unless it is explicitly set in the
tspconfig or by the user. When omitted, the emitter lets TCGC resolve
@clientNamespace decorators correctly. Passing a namespace when @clientNamespace
is used in the spec can cause incorrect directory nesting.
emitter-output-dir: Always use the value provided by the user (Input #3).
Ignore the emitter-output-dir from the tspconfig — it typically contains
unresolvable template variables like {output-dir} and {service-dir}.
Step 7: Run the compilation
cd <spec-directory>
<constructed-compile-command>
Set a timeout of at least 180 seconds — compilation can take a few minutes.
Check the output:
- Warnings only → success
- Errors → report to the user with the full error output
Step 8: Verify and clean up
After successful compilation:
-
Show the generated directory structure:
find < output-dir > -type d | sort
-
Verify the output matches expectations (e.g., TypedDict-only if models-mode=none).
-
If the generation overwrote files in an existing package, warn the user and
offer to revert non-generated files:
cd <sdk-repo>
git diff --name-status <package-dir>/ | grep -v "<expected-generated-path>"
Notes
The --namespace trap
When a TypeSpec uses @clientNamespace to map types into a different namespace,
TCGC resolves the namespace. If you also pass --namespace, TCGC tries to
replace the root of the @clientNamespace value with the flag, which can produce
doubled prefixes like azure.azure.ai.projects.... Only pass --namespace
when the tspconfig explicitly sets it.
Branded vs unbranded emitter names
The local emitter is always @typespec/http-client-python on the CLI --emit
and --option flags. The flavor option controls branded behavior:
- Branded:
--option "@typespec/http-client-python.flavor=azure"
- Unbranded: omit the
flavor option entirely
Common additional options the user may request
| User request | Option to add |
|---|
| TypedDict only | --option "@typespec/http-client-python.models-mode=none" |
| No TypedDicts | --option "@typespec/http-client-python.generate-typeddict=false" |
| No tests | --option "@typespec/http-client-python.generate-test=false" |
| No samples | --option "@typespec/http-client-python.generate-sample=false" |