Suppress generated Java classes that duplicate openai-java models, using @@alternateType in TypeSpec and manual serialization bridges. Use after dup-classes has identified actionable duplicates.
npx skills add https://github.com/Azure/azure-sdk-for-java --skill dedup-openai
Use this skill after the dup-classes skill has identified actionable duplicates. This skill suppresses the generated classes and bridges to the openai-java equivalents.
tsp-location.yaml must exist in the current directory.tsp-client sync) so TempTypeSpecFiles/ exists.openai-java dependency must be in the project's pom.xml.dup-classes skill first).Only standalone models that don't participate in a discriminator hierarchy. A model is standalone if:
Tool, TextResponseFormatConfiguration, or another base class with a fromJson discriminatorfromJson methodStructural equivalents — classes that extend a base type in a discriminator hierarchy (e.g., FunctionTool extends Tool). The SDK's polymorphic serialization requires these. They produce identical JSON but are not actionable. Do NOT attempt to suppress them.
| Mechanism | When to use | Effect |
|-----------|-------------|--------|
| @@alternateType(OpenAI.X, { identity: "com.openai.models.X" }, "java") | The model is referenced as a field type or union member in other models | Codegen replaces the type with the openai-java class. The generated class is NOT emitted at all. |
| @@access(OpenAI.X, Access.internal, "java") | The model is NOT referenced by any public model | Codegen moves the class to implementation.models. |
Prefer @@alternateType — it fully prevents emission and is the cleanest approach. Use @@access(internal) only as a supplement when a model isn't reachable through the type graph but is still emitted.
@@access(internal) alone may not workIf a model is referenced by a union or property in a public model, @@access(internal) will NOT move it. The codegen keeps it public because removing it would break the type graph. Example: ComparisonFilter is a member of the Filters union used by FileSearchTool.filters — @@access(internal) has no effect, but @@alternateType prevents emission entirely.
Locate the client.tsp (or client.java.tsp) in TempTypeSpecFiles/sdk-*/:
find TempTypeSpecFiles -name "client*.tsp" -path "*/sdk-*"
Add @@alternateType directives for each actionable duplicate:
// De-dup: map to openai-java equivalents
@@alternateType(OpenAI.ComparisonFilter, { identity: "com.openai.models.ComparisonFilter" }, "java");
@@alternateType(OpenAI.Reasoning, { identity: "com.openai.models.Reasoning" }, "java");
Finding the correct model name: The TypeSpec models are in the OpenAI namespace. Search the openai-typespec package:
grep -rn "^model <ClassName>" TempTypeSpecFiles/node_modules/@azure-tools/openai-typespec/src/ --include="*.tsp"
tsp-client generate
After generation, verify the suppressed classes are gone:
# Should NOT exist:
ls src/main/java/com/azure/ai/agents/models/<SuppressedClass>.java
# Should NOT exist in implementation either (with @@alternateType):
ls src/main/java/com/azure/ai/agents/implementation/models/<SuppressedClass>.java
When a model's property type changes from a generated JsonSerializable class to an openai-java class, the toJson/fromJson methods in parent models will break because the openai-java type doesn't implement JsonSerializable.
Pattern for toJson — use OpenAIJsonHelper.toBinaryData():
// Before (generated, won't compile):
jsonWriter.writeJsonField("reasoning", this.reasoning);
// After:
if (this.reasoning != null) {
jsonWriter.writeFieldName("reasoning");
OpenAIJsonHelper.toBinaryData(this.reasoning).writeTo(jsonWriter);
}
Pattern for fromJson — read as BinaryData, convert with OpenAIJsonHelper.fromBinaryData():
// Before (generated, won't compile):
reasoning = Reasoning.fromJson(reader);
// After:
BinaryData reasoningData
= reader.getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped()));
reasoning = OpenAIJsonHelper.fromBinaryData(reasoningData, com.openai.models.Reasoning.class);
Pattern for getter/setter — use the openai-java type directly, with javadoc above and marker comment inside the body:
// Field stores the openai-java type directly (no BinaryData indirection)
private com.openai.models.Reasoning reasoning; // AI Tooling: openai-java de-dup
/**
* Gets the reasoning configuration.
* @return the reasoning, or null if not set.
*/
public com.openai.models.Reasoning getReasoning() {
// AI Tooling: openai-java de-dup
return this.reasoning;
}
/**
* Sets the reasoning configuration.
* @param reasoning the reasoning to set.
* @return this object.
*/
public PromptAgentDefinition setReasoning(com.openai.models.Reasoning reasoning) {
// AI Tooling: openai-java de-dup
this.reasoning = reasoning;
return this;
}
Remove @Generated from any method you modify so the codegen preserves your changes on re-generation. See Codegen survival rules for comment/javadoc placement.
When a property is already BinaryData (e.g., because it's a union type), add distinctly named setter methods for the openai-java types. Do NOT overload setX with different parameter types — this causes null-ambiguity. Use descriptive names instead:
/**
* Sets the filters using an openai-java ComparisonFilter.
* @param filter the filter to apply, or null to clear.
* @return this object.
*/
public FileSearchTool setComparisonFilter(com.openai.models.ComparisonFilter filter) {
// AI Tooling: openai-java de-dup
this.filters = OpenAIJsonHelper.toBinaryData(filter);
return this;
}
/**
* Sets the filters using an openai-java CompoundFilter.
* @param filter the filter to apply, or null to clear.
* @return this object.
*/
public FileSearchTool setCompoundFilter(com.openai.models.CompoundFilter filter) {
// AI Tooling: openai-java de-dup
this.filters = OpenAIJsonHelper.toBinaryData(filter);
return this;
}
OpenAIJsonHelper methods if neededThe OpenAIJsonHelper class in com.azure.ai.agents.implementation may need two bridge methods:
// Serialize openai-java object → BinaryData (writes as JSON object, not quoted string)
public static BinaryData toBinaryData(Object openAIObject)
// Deserialize BinaryData → openai-java type
public static <T> T fromBinaryData(BinaryData data, Class<T> type)
These use the openai-java ObjectMappers.jsonMapper() (which handles Kotlin internals correctly). Do NOT use BinaryData.fromObject() or BinaryData.toObject() with openai-java types — the default Jackson ObjectMapper cannot serialize Kotlin SynchronizedLazyImpl fields.
Write round-trip tests verifying the JSON shape is preserved. Test pattern:
@Test
public void testRoundTrip() throws IOException {
// Build with openai-java type
Reasoning reasoning = Reasoning.builder().effort(ReasoningEffort.HIGH).build();
PromptAgentDefinition original = new PromptAgentDefinition("gpt-4o").setReasoning(reasoning);
// Serialize
String json = serialize(original);
assertTrue(json.contains("\"effort\":\"high\""));
// Deserialize
PromptAgentDefinition deserialized = deserialize(json);
assertEquals(ReasoningEffort.HIGH, deserialized.getReasoning().effort().get());
// Re-serialize and compare
assertEquals(json, serialize(deserialized));
}
Cover: all enum values, null/absent fields, combined with other fields, polymorphic deserialization via parent fromJson.
If a local checkout of Azure/azure-rest-api-specs is available, apply the same client.tsp edits there. Derive the path from tsp-location.yaml:
<spec_repo>/<directory>/client.tsp
The TypeSpec Java codegen (tsp-client update / tsp-client generate) will re-generate files on every run. Methods without @Generated are preserved (body intact), but everything above the method signature (javadoc, comments) is regenerated. Follow these rules so your manual edits survive:
@Generated from any method you modify. The codegen will not overwrite the method body.@Generated, the codegen preserves the javadoc you wrote.// ✅ SURVIVES codegen: javadoc above, marker inside body
/**
* Gets the reasoning configuration.
* @return the reasoning, or null if not set.
*/
public com.openai.models.Reasoning getReasoning() {
// AI Tooling: openai-java de-dup ← inside body, survives
return this.reasoning;
}
// ❌ WIPED by codegen: marker above signature
// AI Tooling: openai-java de-dup ← above signature, gets wiped
public com.openai.models.Reasoning getReasoning() {
return this.reasoning;
}
// ✅ SURVIVES codegen: field marker on same line
private com.openai.models.Reasoning reasoning; // AI Tooling: openai-java de-dup
// ❌ WIPED by codegen: field marker on line above
// AI Tooling: openai-java de-dup
private com.openai.models.Reasoning reasoning;
| Problem | Cause | Fix |
|---------|-------|-----|
| Class stays public despite @@access(internal) | Referenced by a union or property in a public model | Use @@alternateType instead |
| BinaryData.fromObject(openAIObj) throws SynchronizedLazyImpl error | Default Jackson can't serialize Kotlin internals | Use OpenAIJsonHelper.toBinaryData() which uses ObjectMappers.jsonMapper() |
| BinaryData.fromString(json).writeTo(writer) writes quoted string | fromString creates text content, not JSON | Use BinaryData.fromObject(reader.readUntyped()) to store as a JSON object |
| Getter/setter bridge through BinaryData on every call | Unnecessary indirection | Store the openai-java type directly in the field; bridge only in toJson/fromJson |
| Tried to suppress a Tool subclass | Structural equivalent, not an actionable duplicate | Don't suppress — it's needed for polymorphic deserialization |
| Javadoc/comments above method wiped after codegen | Codegen rewrites everything above non-@Generated method signatures | Place marker comments inside the method body; javadoc survives if @Generated is removed (see Codegen survival rules) |
| Overloaded setters cause null ambiguity | setFilters(null) matches BinaryData, ComparisonFilter, and CompoundFilter | Use distinct method names: setComparisonFilter(), setCompoundFilter() |
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take azure/dedup-openai from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.