This guide documents the complete workflow for aligning Dart API clients (openai_dart, anthropic_sdk_dart) with their respective official APIs. Based on lessons learned from PRs #826-#837 for openai_dart.
Both packages follow identical structure:
packages/<package_name>/
├── oas/
│ ├── openapi_curated.yaml # Hand-maintained spec (SOURCE OF TRUTH)
│ ├── openapi_official.yaml # Official spec for reference (openai_dart only)
│ └── main.dart # Code generation script
├── lib/
│ ├── <package_name>.dart # Public API exports
│ └── src/
│ ├── client.dart # Custom client wrapper
│ ├── extensions.dart # Helper extensions
│ └── generated/
│ ├── client.dart # Generated base client
│ └── schema/
│ ├── schema.dart # Main schema (parts all others)
│ ├── schema.freezed.dart # Freezed generated
│ ├── schema.g.dart # JSON serialization
│ └── *.dart # Individual schema files
├── test/
│ ├── *_test.dart # Unit and integration tests
│ └── assets/ # Test fixtures
├── example/
├── build.yaml # Build runner config
├── pubspec.yaml
├── README.md
└── CHANGELOG.md
OpenAI:
# Check Stainless SDK for latest spec
curl -o packages/openai_dart/oas/openapi_official.yaml \
"https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-<hash>.yml"Anthropic:
# Download latest spec from Stainless SDK
curl -o packages/anthropic_sdk_dart/oas/anthropic_openapi_official.yaml \
"https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic%2Fanthropic-a49e89deec4e00d1da490808099d66e2001531b12d8666a7f5d0b496f760440d.yml"Use grep and diff to find differences:
# Compare curated vs official
diff packages/<package>/oas/openapi_curated.yaml \
packages/<package>/oas/openapi_official.yaml
# Search for specific schema in official spec
grep -A 50 "ComponentName:" openapi_official.yaml
# Find all schema definitions
grep "^ [A-Z]" openapi_official.yaml | head -50
# Compare specific schema between files
grep -A 30 "Usage:" openapi_curated.yaml
grep -A 30 "Usage:" openapi_official.yamlLook for:
- New schemas - Entirely new object definitions
- New fields - Properties added to existing schemas
- New enum values - Values added to existing enums
- Modified types - Field type changes (rare)
- Deprecated fields - Fields marked for removal
Create one PR per logical change:
- Group related fields together (e.g., all new ImagesResponse fields)
- Separate unrelated additions (e.g., CompletionUsage vs ImagesResponse)
- Keep PRs focused and reviewable
ComponentName:
type: object
description: Human-readable description of the component.
properties:
required_field:
type: string
description: This field is always present.
optional_field:
type: integer
nullable: true
default: null
description: This field may be absent or null.
nested_object:
$ref: "#/components/schemas/NestedSchema"
required:
- required_fieldMyEnumField:
title: MyEnumName # Generates enum with this name
type: string
enum:
- value_one
- value_two
- value_three
nullable: true
description: Description of the enum.# Parent schema
ParentSchema:
type: object
properties:
detail_field:
$ref: "#/components/schemas/DetailSchema"
# Child schema (define separately, not inline)
DetailSchema:
type: object
nullable: true
description: Detailed breakdown information.
properties:
sub_field_one:
type: integer
description: First sub-field.
sub_field_two:
type: string
description: Second sub-field.# Mark non-standard fields with x-openai-compatible: false
provider_specific_field:
type: number
minimum: 0
maximum: 1
nullable: true
default: null
description: Provider-specific parameter.
x-openai-compatible: false # NOT in official OpenAI API| Rule | Example |
|---|---|
| Optional response fields | nullable: true + default: null |
| Snake_case in YAML | cached_tokens, audio_tokens |
| Use $ref for objects | $ref: "#/components/schemas/Name" |
| Enum title for naming | title: ImagesResponseQuality |
| Numeric constraints | minimum: 0, maximum: 2.0 |
Add your new schema or fields to the YAML spec.
cd packages/<package_name>
dart run oas/main.dartThis will:
- Parse the OpenAPI spec
- Generate Dart classes with freezed
- Run build_runner to create .freezed.dart and .g.dart files
Check the generated files in lib/src/generated/schema/:
- New schema file created:
<component_name>.dart - Part statement added to
schema.dart - Freezed and JSON serialization generated
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: invalid_annotation_target
part of open_a_i_schema;
/// Description from YAML
@freezed
abstract class ComponentName with _$ComponentName {
const ComponentName._();
/// Factory constructor for ComponentName
const factory ComponentName({
/// Field description
@JsonKey(name: 'snake_case_field', includeIfNull: false)
Type? fieldName,
}) = _ComponentName;
/// Object construction from a JSON representation
factory ComponentName.fromJson(Map<String, dynamic> json) =>
_$ComponentNameFromJson(json);
/// List of all property names of schema
static const List<String> propertyNames = ['snake_case_field'];
/// Perform validations on the schema property values
String? validateSchema() {
return null;
}
/// Map representation of object (not serialized)
Map<String, dynamic> toMap() {
return {'snake_case_field': fieldName};
}
}When generated names are awkward, add mappings:
// In oas/main.dart
final schemaNameMappings = <String, String>{
'ModelEnumeration': 'ModelCatalog',
'ModelString': 'ModelId',
};
final unionFactoryNameMappings = <String, Map<String, String>>{
'ChatCompletionModel': {
'ModelEnumeration': 'model',
'ModelString': 'modelId',
},
};packages/<package_name>/test/<schema_name>_test.dart
import 'package:<package_name>/<package_name>.dart';
import 'package:test/test.dart';
void main() {
group('ComponentName tests', () {
// Deserialization tests
test('deserializes with all fields', () {
final json = {
'field_one': 100,
'field_two': 'value',
};
final obj = ComponentName.fromJson(json);
expect(obj.fieldOne, 100);
expect(obj.fieldTwo, 'value');
});
test('deserializes with optional fields missing', () {
final json = {'field_one': 100};
final obj = ComponentName.fromJson(json);
expect(obj.fieldOne, 100);
expect(obj.fieldTwo, isNull);
});
test('deserializes with null values', () {
final json = {'field_one': 100, 'field_two': null};
final obj = ComponentName.fromJson(json);
expect(obj.fieldTwo, isNull);
});
test('deserializes with empty JSON', () {
final json = <String, dynamic>{};
final obj = ComponentName.fromJson(json);
expect(obj.fieldOne, isNull);
expect(obj.fieldTwo, isNull);
});
// Serialization tests
test('serializes correctly', () {
const obj = ComponentName(fieldOne: 100, fieldTwo: 'value');
final json = obj.toJson();
expect(json['field_one'], 100);
expect(json['field_two'], 'value');
});
test('serializes with null fields excluded', () {
const obj = ComponentName(fieldOne: 100);
final json = obj.toJson();
expect(json['field_one'], 100);
expect(json.containsKey('field_two'), isFalse);
});
// Enum tests (if applicable)
test('MyEnum values', () {
expect(MyEnum.values, hasLength(3));
expect(MyEnum.values, containsAll([
MyEnum.valueOne,
MyEnum.valueTwo,
MyEnum.valueThree,
]));
});
});
// Parent-child relationship tests
group('ParentSchema with DetailSchema tests', () {
test('deserializes with nested detail object', () {
final json = {
'main_field': 50,
'detail_field': {
'sub_field_one': 10,
'sub_field_two': 'nested',
},
};
final obj = ParentSchema.fromJson(json);
expect(obj.mainField, 50);
expect(obj.detailField, isNotNull);
expect(obj.detailField!.subFieldOne, 10);
expect(obj.detailField!.subFieldTwo, 'nested');
});
});
}- Deserialize with all fields
- Deserialize with optional fields missing
- Deserialize with null values
- Deserialize with empty JSON
- Serialize with all fields
- Serialize excludes null fields
- Enum has expected values
- Nested objects deserialize correctly
- Parent-child relationships work
# Check if formatting needed
dart format packages/<package_name> --set-exit-if-changed
# Apply formatting
dart format packages/<package_name>dart analyze packages/<package_name>Acceptable warnings in test files:
inference_failure_on_collection_literal- Empty list type inferenceavoid_dynamic_calls- Dynamic JSON access in tests
Must fix:
- Any errors
- Warnings in lib/ code
feat/<package>-<brief-description>
fix/<package>-<brief-description>
chore/<package>-<brief-description>
test/<package>-<brief-description>
Examples:
feat/openai-prompt-tokens-detailsfeat/anthropic-extended-thinkingchore/format-openai-dart
<type>(<package>): <description>
[optional body with details]
Types:
feat- New feature or schemafix- Bug fixchore- Maintenance (formatting, deps)test- Test changes onlydocs- Documentation only
Examples:
feat(openai_dart): Add prompt_tokens_details to CompletionUsage
Add PromptTokensDetails schema with cached_tokens and audio_tokens
fields to support the official OpenAI API response format.
gh pr create --title "<type>(<package>): <description>" --body "$(cat <<'EOF'
## Summary
- Brief description of changes
## Changes
- List of specific changes made
## Test plan
- [x] Unit tests added/updated
- [x] Formatter passes
- [x] Analyzer passes
- [ ] CI passes
EOF
)"Before creating PR:
- Branch created from main
- Changes made to openapi_curated.yaml
- Code generator run
- Unit tests written
- All tests pass:
dart test packages/<package>/test/ - Formatter run:
dart format packages/<package> - Analyzer passes:
dart analyze packages/<package> - Commit with proper message
- Push to remote
Step 1: Update YAML
# In oas/anthropic_openapi_curated.yaml
components:
schemas:
TokenUsageDetails:
type: object
nullable: true
description: Detailed breakdown of token usage.
properties:
cache_creation_input_tokens:
type: integer
description: Tokens used to create cache.
cache_read_input_tokens:
type: integer
description: Tokens read from cache.Step 2: Add to parent schema
Usage:
properties:
# ... existing fields ...
details:
$ref: "#/components/schemas/TokenUsageDetails"Step 3: Run generator
cd packages/anthropic_sdk_dart
dart run oas/main.dartStep 4: Write tests
// test/token_usage_details_test.dart
import 'package:anthropic_sdk_dart/anthropic_sdk_dart.dart';
import 'package:test/test.dart';
void main() {
group('TokenUsageDetails tests', () {
test('deserializes with all fields', () {
final json = {
'cache_creation_input_tokens': 100,
'cache_read_input_tokens': 50,
};
final details = TokenUsageDetails.fromJson(json);
expect(details.cacheCreationInputTokens, 100);
expect(details.cacheReadInputTokens, 50);
});
// ... more tests
});
}Step 5: Run checks
dart test packages/anthropic_sdk_dart/test/token_usage_details_test.dart
dart format packages/anthropic_sdk_dart
dart analyze packages/anthropic_sdk_dartStep 6: Create PR
git checkout -b feat/anthropic-token-usage-details
git add .
git commit -m "feat(anthropic_sdk_dart): Add TokenUsageDetails schema"
git push -u origin feat/anthropic-token-usage-details
gh pr create --title "feat(anthropic_sdk_dart): Add TokenUsageDetails schema" --body "..."| Pitfall | Solution |
|---|---|
Forgetting nullable: true |
Always add for optional response fields |
Forgetting includeIfNull: false |
Generator handles this, but verify |
| Inline complex objects | Use $ref to separate schemas |
| Modifying generated code | Never! Update YAML and regenerate |
| Large multi-feature PRs | One logical change per PR |
| Missing tests for enums | Always test enum values count |
| Skipping empty JSON test | Always test deserialization of {} |
| Relative test file paths | Use directory-aware paths (see Section 14) |
| Breaking dependent packages | Always check langchain_* packages after schema changes |
| Non-exhaustive switches | Add all new enum values/union types to switches |
If you encounter bugs in the code generation (openapi_spec package), you can fix them directly:
The generator source is at:
/Users/davidmigloz/repos/openapi_spec
GitHub: https://github.com/davidmigloz/openapi_spec
Edit the relevant files in the openapi_spec repo and test locally.
cd /Users/davidmigloz/repos/openapi_spec
git add .
git commit -m "fix: Description of the fix"
git push origin mainUpdate the commit ref in the root pubspec.yaml of the monorepo:
# In /Users/davidmigloz/repos/langchain_dart/pubspec.yaml
dependency_overrides:
openapi_spec:
git:
url: https://github.com/davidmigloz/openapi_spec.git
ref: <new-commit-sha> # Update thiscd /Users/davidmigloz/repos/langchain_dart
melos bootstrapThis applies the updated dependency to all packages.
Critical: Verify the fix doesn't break generation for other packages:
# Regenerate openai_dart
cd packages/openai_dart
dart run oas/main.dart
dart analyze .
dart test
# Regenerate anthropic_sdk_dart
cd ../anthropic_sdk_dart
dart run oas/main.dart
dart analyze .
dart test
# Check any other packages using openapi_spec- PR for openapi_spec fix (if public contribution needed)
- PR for langchain_dart updating the dependency ref
- Maintains two spec files (curated + official for reference)
- 189+ schema files, covers 8+ API categories
- Has OpenRouter-specific extensions marked with
x-openai-compatible: false - Integration tests require
OPENAI_API_KEY
- Single curated spec file (more focused)
- 50+ schema files, covers Messages, Batches, Models, Token Counting
- Custom client wrapper for streaming
- Integration tests require
ANTHROPIC_API_KEY - Extensions in
extensions.dartfor convenience methods - Beta features use
anthropic-betaheader (e.g., token-counting, extended-thinking) - Versioned tool types for computer use, text editor, bash (see Section 16)
- Sealed classes for union types (Block, ImageBlockSource, MessageStreamEvent, etc.)
# Generate code
dart run oas/main.dart
# Run specific tests
dart test packages/<pkg>/test/<test_file>.dart
# Run all package tests
dart test packages/<pkg>
# Format
dart format packages/<pkg>
# Analyze
dart analyze packages/<pkg>
# Create PR
gh pr create --title "..." --body "..."
# Check git status
git status
# View recent commits
git log --oneline -10Typical files changed when adding a new schema:
packages/<package>/
├── oas/openapi_curated.yaml # YAML changes
├── lib/src/generated/schema/
│ ├── schema.dart # New part statement
│ ├── schema.freezed.dart # Regenerated
│ ├── schema.g.dart # Regenerated
│ └── <new_schema>.dart # New file
└── test/<new_schema>_test.dart # New test file
When SDK schema changes break dependent packages (e.g., langchain_anthropic depends on anthropic_sdk_dart), you must fix them in the same session.
When a simple class becomes a sealed class with union variants:
// BEFORE: Simple class
source: a.ImageBlockSource(
type: a.ImageBlockSourceType.base64,
mediaType: a.ImageBlockSourceMediaType.imageJpeg,
data: imageData,
)
// AFTER: Sealed class with named constructor
source: a.ImageBlockSource.base64ImageSource(
type: 'base64',
mediaType: a.Base64ImageSourceMediaType.imageJpeg,
data: imageData,
)When new enum values or union variants are added, all switch statements must handle them:
// Adding new StopReason values
FinishReason _mapFinishReason(final a.StopReason? reason) => switch (reason) {
a.StopReason.endTurn => FinishReason.stop,
a.StopReason.maxTokens => FinishReason.length,
a.StopReason.stopSequence => FinishReason.stop,
a.StopReason.toolUse => FinishReason.toolCalls,
// NEW: Must add these or Dart analyzer errors
a.StopReason.pauseTurn => FinishReason.unspecified,
a.StopReason.refusal => FinishReason.contentFilter,
null => FinishReason.unspecified,
};When new content block types are added to a union:
// Must handle all new block types
(String, AIChatMessageToolCall?) _mapContentBlock(final a.Block block) =>
switch (block) {
final a.TextBlock t => (t.text, null),
final a.ImageBlock i => (/* handle */, null),
final a.ToolUseBlock tu => (/* handle */, toolCall),
// NEW: Add placeholders for new block types
final a.DocumentBlock _ => ('', null),
final a.RedactedThinkingBlock _ => ('', null),
final a.ServerToolUseBlock _ => ('', null),
final a.WebSearchToolResultBlock _ => ('', null),
final a.MCPToolUseBlock _ => ('', null),
final a.MCPToolResultBlock _ => ('', null),
// ... etc
};-
Run analyzer on dependent packages after regenerating SDK:
dart analyze packages/langchain_anthropic dart analyze packages/langchain_openai
-
Fix all errors - usually exhaustive switch or constructor changes
-
Run tests to verify functionality:
dart test packages/langchain_anthropic/test/ -
Commit SDK and dependent package fixes together or in sequence
Tests that reference asset files must work when run from both the package directory and the repo root.
// This fails when run from repo root
final file = await File('./test/assets/data.txt').readAsString();// Directory-aware path that works from any location
final testDir = Directory.current.path.endsWith('anthropic_sdk_dart')
? Directory.current.path
: '${Directory.current.path}/packages/anthropic_sdk_dart';
final file = await File('$testDir/test/assets/data.txt').readAsString();Some tests depend on API behavior that varies by model or time. Use skip with explanation:
test(
'Test computer tool use',
skip: 'claude-sonnet-4-5-20250929 does not support computer_20241022 tools',
() async {
// test code
},
);
test(
'Test Prompt caching',
skip: 'Prompt caching behavior varies by model and may not return expected cache metrics',
() async {
// test code
},
);Anthropic uses versioned tool type identifiers. These change over time:
| Tool | Current Version | Notes |
|---|---|---|
| Computer Use | computer_20241022 |
Beta, model-specific support |
| Text Editor | text_editor_20250728 |
Updated in 2025 |
| Bash | bash_20250124 |
Updated in 2025 |
Important: Not all models support all tool versions. Verify before using.
Tool types use discriminator mappings in the OpenAPI spec:
Tool:
discriminator:
propertyName: type
mapping:
custom: "#/components/schemas/ToolCustom"
computer_20241022: "#/components/schemas/ToolComputerUse"
text_editor_20250728: "#/components/schemas/ToolTextEditor"
bash_20250124: "#/components/schemas/ToolBash"Some endpoints require beta headers:
// In client, headers are added automatically for beta features
final response = await client.countMessageTokens(
request: request,
// Beta header added: 'anthropic-beta': 'token-counting-2024-11-01'
);When the OpenAPI spec defines a oneOf with discriminator:
ImageBlockSource:
oneOf:
- $ref: "#/components/schemas/Base64ImageSource"
- $ref: "#/components/schemas/UrlImageSource"
discriminator:
propertyName: type
mapping:
base64: "#/components/schemas/Base64ImageSource"
url: "#/components/schemas/UrlImageSource"Generates a sealed class:
@Freezed(unionKey: 'type', unionValueCase: FreezedUnionCase.snake)
sealed class ImageBlockSource with _$ImageBlockSource {
@FreezedUnionValue('base64')
const factory ImageBlockSource.base64ImageSource({
required String type,
required Base64ImageSourceMediaType mediaType,
required String data,
}) = Base64ImageSource;
@FreezedUnionValue('url')
const factory ImageBlockSource.urlImageSource({
required String type,
required String url,
}) = UrlImageSource;
}// Use switch expression for exhaustive matching
final result = switch (imageSource) {
final Base64ImageSource s => s.data,
final UrlImageSource s => s.url,
};When creating multiple dependent PRs (stacked PRs), each PR's base branch should point to the previous PR's branch so GitHub shows only the incremental diff.
WARNING: If you merge with
--delete-branchbefore updating the next PR's base, GitHub will auto-close the next PR because its base branch no longer exists. The PR cannot be reopened and must be recreated.
Wrong order (causes auto-closure):
gh pr merge 889 --squash --delete-branch # Deletes base branch
gh pr edit 890 --base main # FAILS: PR 890 is already closed!Correct order:
gh pr edit 890 --base main # Update base FIRST
gh pr merge 889 --squash --delete-branch # Now safe to delete branchFor each PR in the stack (starting from the one closest to main):
-
Update the NEXT PR's base to main BEFORE merging current PR:
gh pr edit <NEXT_PR_NUMBER> --base main
-
Merge the current PR:
gh pr merge <CURRENT_PR_NUMBER> --squash --delete-branch
-
Rebase the next PR on main:
git checkout <next-branch> git fetch origin main git rebase origin/main
Git will automatically skip already-merged commits (you'll see "skipped previously applied commit" messages).
-
Force push the rebased branch:
git push --force-with-lease
-
Verify the PR shows only its own commits, then repeat from step 1 for the next PR.
# Stack: PR 889 -> PR 890 -> PR 891 (889 is closest to main)
# === Merge PR 889 ===
gh pr edit 890 --base main # Step 1: Update 890's base FIRST
gh pr merge 889 --squash --delete-branch # Step 2: Now merge 889
git checkout feat/pr-890-branch # Step 3: Checkout 890's branch
git fetch origin main
git rebase origin/main # Git skips merged commits
git push --force-with-lease # Step 4: Update remote
# === Merge PR 890 ===
gh pr edit 891 --base main # Step 1: Update 891's base FIRST
gh pr merge 890 --squash --delete-branch # Step 2: Now merge 890
git checkout feat/pr-891-branch # Step 3: Checkout 891's branch
git fetch origin main
git rebase origin/main
git push --force-with-lease
# === Merge PR 891 (last one) ===
gh pr merge 891 --squash --delete-branch # No next PR, just merge| Scenario | What Happens |
|---|---|
| Update base first, then merge | ✅ Next PR stays open, points to main |
| Merge first, then update base | ❌ Base branch deleted → PR auto-closed → Cannot reopen |
| Issue | Cause | Solution |
|---|---|---|
| PR auto-closed after merge | Base branch was deleted before updating | Create new PR: gh pr create --base main --head <branch> |
gh pr edit --base fails |
PR already closed, or GraphQL error | Use API: gh api repos/OWNER/REPO/pulls/NUM -X PATCH -f base=main |
| PR shows too many commits | Branch not rebased after base change | Rebase and force push |
| Rebase conflicts | Commits conflict with squash merge | Use git rebase -i origin/main and drop merged commits |
If regular rebase has conflicts, use interactive mode to explicitly drop merged commits:
git rebase -i origin/mainIn the editor, change pick to drop for commits that were in the already-merged PR:
drop abc1234 feat: change from PR 889 (already merged)
pick def5678 feat: change from PR 890 (keep this)
Save and exit, then force push.
When defining union types (e.g., ChatCompletionToolChoice that can be either an enum string or an object), the anyOf pattern must be inlined in the parent schema rather than defined as a top-level schema.
Top-level anyOf schemas can cause freezed code generation to fail:
Null check operator used on a null value in DefaultValue.defaultValue
The generated class may be empty instead of a proper sealed class.
# This can fail during code generation
ChatCompletionToolChoice:
title: ChatCompletionToolChoice
anyOf:
- $ref: '#/components/schemas/ToolChoiceOption'
- $ref: '#/components/schemas/ToolChoiceTool'
nullable: trueChatCompletionRequest:
properties:
tool_choice:
title: ChatCompletionToolChoice
description: Controls which tool is called.
nullable: true
anyOf:
- type: string
title: ChatCompletionToolChoiceOption
enum: [none, auto, any, required]
- $ref: '#/components/schemas/ToolChoiceTool'This generates a proper sealed class with custom JSON converter:
@freezed
sealed class ChatCompletionToolChoice with _$ChatCompletionToolChoice {
const factory ChatCompletionToolChoice.enumeration(
ChatCompletionToolChoiceOption value,
) = ChatCompletionToolChoiceEnumeration;
const factory ChatCompletionToolChoice.toolChoiceTool(
ToolChoiceTool value,
) = ChatCompletionToolChoiceToolChoiceTool;
}- Union types combining enums and objects (tool_choice, stop sequences)
- Any
anyOfthat mixes primitive types with$refobjects - Fields where the API accepts multiple distinct formats
When a LangChain integration package (e.g., langchain_mistralai) imports both langchain_core and an SDK package (e.g., mistralai_dart), naming conflicts can occur.
// Both packages define a 'Tool' class
import 'package:langchain_core/tools.dart'; // Has Tool
import 'package:mistralai_dart/mistralai_dart.dart'; // Also has Tool
// Error: The name 'Tool' is defined in multiple librariesimport 'package:langchain_core/tools.dart';
import 'package:mistralai_dart/mistralai_dart.dart' as mistral;
// Now use prefix for SDK types
mistral.Tool _mapTool(ToolSpec tool) {
return mistral.Tool(
type: mistral.ToolType.function,
function: mistral.FunctionDefinition(
name: tool.name,
description: tool.description,
parameters: tool.inputJsonSchema,
),
);
}| Class Name | langchain_core | SDK Packages |
|---|---|---|
Tool |
ToolSpec wrapper | API schema |
Message |
ChatMessage | API schema |
Usage |
LanguageModelUsage | API schema |
Always use an as prefix for SDK imports in integration packages to prevent current and future conflicts as APIs evolve.
When making multiple related changes to an API client, the generated files (schema.freezed.dart, schema.g.dart) regenerate completely each time. This makes truly independent stacked PRs impractical.
Each code generation run overwrites:
schema.freezed.dart(~thousands of lines)schema.g.dart(~thousands of lines)schema.dart(part statements)
Creating separate commits for "only tool calling" vs "only response format" is nearly impossible since each regeneration includes all changes.
Instead of N separate PRs per feature, organize by package:
| PR | Scope | Contents |
|---|---|---|
| PR 1 | SDK package | All schema changes (regenerated together) |
| PR 2 | Integration package | Mapper/options changes (depends on PR 1) |
PR 1: mistralai_dart (all schema changes)
- Tool/function calling schemas
- Response format schemas
- Missing parameters
- Updated models enum
- Prediction/reasoning mode
PR 2: langchain_mistralai (integration)
- Tool mappers
- Options updates
- Finish reason handling
Only split SDK changes into multiple PRs when:
- Changes are truly independent (different API endpoints)
- One change is experimental/risky and needs separate review
- Changes affect different major versions
Document created: 2025-12-19 Updated: 2025-12-20 Based on: openai_dart PRs #826-#837, anthropic_sdk_dart API alignment work, mistralai_dart PRs #887-#888