Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
9d114e1
♻️ refactor(providers): standardize HttpClient type, add mock client,…
tomjnsn Feb 3, 2026
6163e53
✨ feat(providers): implement HTTP layer for Google/Vertex, add compli…
tomjnsn Feb 8, 2026
87fd6a4
feat: implement API key redaction utility
tomjnsn Feb 9, 2026
8d0b76f
test: add failing test for error message redaction
tomjnsn Feb 9, 2026
02f0fdd
feat: redact API keys in error messages
tomjnsn Feb 9, 2026
5311f60
test: add failing test for response size limit
tomjnsn Feb 9, 2026
b01b044
feat: add response size limit to HTTP client
tomjnsn Feb 9, 2026
d58ba59
test: add failing test for SSE buffer limit
tomjnsn Feb 9, 2026
fbc99b7
feat: add buffer size limit to SSE parser
tomjnsn Feb 9, 2026
8bbcc46
fix: return error when header count exceeds limit
tomjnsn Feb 9, 2026
cb08673
docs: document loadApiKey allocator issue
tomjnsn Feb 9, 2026
cc7f1fc
fix: use passed allocator in loadApiKey
tomjnsn Feb 9, 2026
67c474c
test: add failing tests for URL validation
tomjnsn Feb 9, 2026
077f57a
feat: implement URL validation utilities
tomjnsn Feb 9, 2026
fc94dd4
feat: validate provider base URLs
tomjnsn Feb 9, 2026
0f6e181
🐛 fix: propagate errors in anthropic provider headers
tomjnsn Feb 9, 2026
39882fb
🐛 fix: propagate http_client.post errors in openai models
tomjnsn Feb 9, 2026
ead49b4
🐛 fix: propagate errors in openai and azure provider headers
tomjnsn Feb 9, 2026
c2877da
🐛 fix: propagate errors in google provider
tomjnsn Feb 9, 2026
8523555
🐛 fix: propagate errors in all remaining providers
tomjnsn Feb 9, 2026
9d1eb74
🐛 fix: log errors instead of silent catch in mock client
tomjnsn Feb 9, 2026
fda7f51
✨ feat: implement safe integer casting utility
tomjnsn Feb 9, 2026
4cfad6d
🐛 fix: use safe cast for external data in all providers
tomjnsn Feb 9, 2026
d7b8695
📚 docs: add lifetime documentation to vtable interfaces
tomjnsn Feb 9, 2026
2c36625
test: add failing integration test for generateText
tomjnsn Feb 9, 2026
e8b884a
feat: wire up doGenerate in generateText
tomjnsn Feb 9, 2026
af12957
test: add failing integration test for streamText
tomjnsn Feb 9, 2026
f3475f7
feat: wire up streaming in streamText
tomjnsn Feb 9, 2026
b9779ee
test: add failing integration test for embed
tomjnsn Feb 9, 2026
12610c2
feat: wire up doEmbed in embed function
tomjnsn Feb 9, 2026
3500776
test: add failing test for embedMany batching
tomjnsn Feb 9, 2026
66ae1dd
feat: implement batching in embedMany
tomjnsn Feb 9, 2026
7204a5b
test: add failing integration test for generateImage
tomjnsn Feb 9, 2026
1f0b4fa
feat: wire up doGenerate in generateImage
tomjnsn Feb 9, 2026
b93001c
test: verify image data extraction (base64 decode + error case)
tomjnsn Feb 9, 2026
a9267b8
test: add failing test for generateSpeech + streamSpeech test
tomjnsn Feb 9, 2026
b80917f
feat: wire up doGenerate in generateSpeech
tomjnsn Feb 9, 2026
cd2e48f
test: add failing integration test for transcribe
tomjnsn Feb 9, 2026
3ad0d6d
feat: wire up doGenerate in transcribe
tomjnsn Feb 9, 2026
a33c1fb
test: add failing test for SRT parsing
tomjnsn Feb 9, 2026
8a57eb9
feat: complete SRT parsing in transcribe
tomjnsn Feb 9, 2026
10f0de1
test: add failing integration test for generateObject
tomjnsn Feb 9, 2026
85b8b42
feat: wire up model call in generateObject
tomjnsn Feb 9, 2026
1379b90
feat: complete mistral HTTP integration
tomjnsn Feb 9, 2026
5c1f9ad
test: wire integration tests into build system
tomjnsn Feb 9, 2026
0d6f639
test: add E2E tests for multi-turn conversation and error paths
tomjnsn Feb 9, 2026
add5adb
test: add error path tests for streaming, embeddings, and images
tomjnsn Feb 9, 2026
d181124
test: fix provider compliance tests and add 4 new providers
tomjnsn Feb 9, 2026
6c99449
🧪 test: add stress tests for memory leak detection
tomjnsn Feb 9, 2026
042a97d
🧪 test: add comprehensive tests for 9 lightly-tested providers
tomjnsn Feb 9, 2026
a8bafe6
✨ feat: add API improvements - RequestContext, retry policy, builders…
tomjnsn Feb 10, 2026
1df1d78
fix: resolve use-after-free in generateText - arena freed before return
tomjnsn Feb 11, 2026
be6fac2
Merge pull request #15 from tomjnsn/fix/1-generatetext-use-after-free
tomjnsn Feb 11, 2026
bcdb96f
📚 docs: document doStream synchronous callback contract
tomjnsn Feb 11, 2026
df7ff68
Merge pull request #16 from tomjnsn/fix/2-streamtext-stack-lifetime
tomjnsn Feb 11, 2026
ee537e5
♻️ refactor: migrate deprecated std.array_list.Managed to std.ArrayList
tomjnsn Feb 11, 2026
2d1c9f1
Merge pull request #17 from tomjnsn/fix/3-migrate-deprecated-managed
tomjnsn Feb 11, 2026
b8c185b
🐛 fix(provider-utils): replace std.testing.allocator with parameter i…
tomjnsn Feb 11, 2026
8b148a4
Merge pull request #18 from tomjnsn/fix/4-isparsablejson-allocator
tomjnsn Feb 11, 2026
6daf5a2
🐛 fix(ai): fix memory ownership in embed/embedMany provider data
tomjnsn Feb 11, 2026
6c97707
Merge pull request #19 from tomjnsn/fix/5-embed-memory-ownership
tomjnsn Feb 11, 2026
657afd0
🐛 fix(provider): add errdefer cleanup to JsonValue.fromStdJson and clone
tomjnsn Feb 11, 2026
256c080
Merge pull request #20 from tomjnsn/fix/6-jsonvalue-errdefer
tomjnsn Feb 11, 2026
53dc98b
🐛 fix(provider-utils): add errdefer cleanup for partial allocs in hea…
tomjnsn Feb 11, 2026
954659c
Merge pull request #21 from tomjnsn/fix/7-extract-headers-errdefer
tomjnsn Feb 11, 2026
cf88447
🐛 fix(ai): fix allocator mismatch in generateText doGenerate call
tomjnsn Feb 11, 2026
178090b
Merge pull request #22 from tomjnsn/fix/8-generatetext-allocator-mism…
tomjnsn Feb 11, 2026
77be4ea
🐛 fix(provider-utils): use .empty for MockHttpClient ArrayList init
tomjnsn Feb 11, 2026
2f02af1
Merge pull request #23 from tomjnsn/fix/9-mock-client-arraylist-init
tomjnsn Feb 11, 2026
c77a40d
🐛 fix(provider): escape JSON object keys in stringifyTo
tomjnsn Feb 11, 2026
30899c3
Merge pull request #24 from tomjnsn/fix/10-json-key-escaping
tomjnsn Feb 11, 2026
a50331b
🔒 fix(provider): expand API key detection to cover more providers
tomjnsn Feb 11, 2026
047ff0e
Merge pull request #25 from tomjnsn/fix/11-expand-api-key-detection
tomjnsn Feb 11, 2026
251352d
📝 fix(provider-utils): clarify buffer growth check in EventSourceParser
tomjnsn Feb 11, 2026
7c75a7d
Merge pull request #26 from tomjnsn/fix/12-buffer-growth-check
tomjnsn Feb 11, 2026
1a6ada1
🐛 fix(ai): accumulate stream usage on finish instead of overwriting
tomjnsn Feb 11, 2026
0c4c139
Merge pull request #27 from tomjnsn/fix/13-stream-usage-accumulation
tomjnsn Feb 11, 2026
12ccb1a
📝 fix(ai): clarify std.json.Stringify.valueAlloc usage in generate-ob…
tomjnsn Feb 11, 2026
0c0182f
Merge pull request #28 from tomjnsn/fix/14-verify-json-stringify
tomjnsn Feb 11, 2026
8bda382
✨ feat(provider): add ErrorDiagnostic type for rich error context
tomjnsn Feb 11, 2026
c2bf83f
Merge pull request #35 from tomjnsn/feature/29-error-diagnostic-type
tomjnsn Feb 11, 2026
76f4d64
🐛 fix(provider-utils): make HttpClient.post() functional and update p…
tomjnsn Feb 11, 2026
a7f6027
Merge pull request #36 from tomjnsn/feature/30-fix-http-client-post
tomjnsn Feb 11, 2026
0ad1357
✨ feat(provider,ai): add error_diagnostic field to all CallOptions an…
tomjnsn Feb 11, 2026
78a38d7
Merge pull request #37 from tomjnsn/feature/31-error-diagnostic-call-…
tomjnsn Feb 11, 2026
535d98d
✨ feat(openai,anthropic,google): populate ErrorDiagnostic on HTTP fai…
tomjnsn Feb 11, 2026
0d00ccb
Merge pull request #38 from tomjnsn/feature/32-providers-capture-errors
tomjnsn Feb 11, 2026
89c0a32
✨ feat(ai): thread error_diagnostic from high-level APIs to provider …
tomjnsn Feb 11, 2026
b98432c
Merge pull request #39 from tomjnsn/feature/33-thread-diagnostic-thro…
tomjnsn Feb 11, 2026
a61bbb5
✅ test(openai): add E2E error diagnostic tests for HTTP error scenarios
tomjnsn Feb 11, 2026
778f406
Merge pull request #40 from tomjnsn/feature/34-e2e-error-diagnostic-t…
tomjnsn Feb 11, 2026
4604098
✅ feat(ai): add live provider integration tests and real HTTP client
tomjnsn Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat: implement API key redaction utility\n\nImplements redactApiKey\\(\\) and containsApiKey\\(\\) functions that detect\nand redact sensitive API key patterns \\(sk-, sk-proj-, anthropic-sk-ant-\\)\nfrom text to prevent credential leakage in error messages and logs.\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\")"
]
}
}
72 changes: 70 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ packages/

### Key Design Patterns

1. **Vtable Pattern**: Interface abstraction for models instead of traits. Each model type (LanguageModelV3, EmbeddingModelV3, etc.) uses vtables for runtime polymorphism.
1. **Vtable Pattern**: Interface abstraction for models instead of traits. Each model type (LanguageModelV3, EmbeddingModelV3, etc.) uses vtables for runtime polymorphism. See "Pointer Casting and Vtables" section below.

2. **Callback-based Streaming**: Non-async approach using `StreamCallbacks` with `on_part`, `on_error`, and `on_complete` callbacks.

3. **Arena Allocators**: Request-scoped memory management. Use `defer arena.deinit()` for cleanup.

4. **Provider Pattern**: Each provider implements `init()`, `deinit()`, `getProvider()`, and model factory methods (e.g., `languageModel()`).

5. **HttpClient Interface**: Type-erased HTTP client allowing mock injection for testing. Providers accept optional `http_client: ?provider_utils.HttpClient` in settings.

### Core Types

- `packages/provider/src/`: `JsonValue` (custom JSON type), error hierarchy, model interfaces
Expand Down Expand Up @@ -70,4 +72,70 @@ defer arena.deinit();
// Use arena.allocator() for request-scoped allocations
```

Always document whether functions take ownership of allocations or expect the caller to manage memory.
### Ownership Conventions

- **Caller-owned**: Function returns data allocated by the passed allocator. Caller must free.
- **Arena-owned**: Data lives until arena is deinitialized. No manual free needed.
- **Static**: Compile-time data (string literals, const slices). Never free.

### Header Functions

Provider `getHeaders()` functions return `std.StringHashMap([]const u8)`. Caller owns the returned map and must call `deinit()`:

```zig
var headers = provider.getHeaders(allocator);
defer headers.deinit();
```

## Pointer Casting and Vtables

The SDK uses vtables for runtime polymorphism. This requires `@ptrCast` and `@alignCast` when converting between `*anyopaque` and concrete types.

### Pattern

```zig
// Interface definition
pub const HttpClient = struct {
vtable: *const VTable,
impl: *anyopaque, // Type-erased implementation pointer

pub const VTable = struct {
request: *const fn (impl: *anyopaque, ...) void,
};

pub fn request(self: HttpClient, ...) void {
self.vtable.request(self.impl, ...);
}
};

// Implementation
pub const MockHttpClient = struct {
// ... fields ...

pub fn asInterface(self: *MockHttpClient) HttpClient {
return .{
.vtable = &vtable,
.impl = self, // Implicit cast to *anyopaque
};
}

const vtable = HttpClient.VTable{
.request = doRequest,
};

fn doRequest(impl: *anyopaque, ...) void {
// Cast back to concrete type - alignment is guaranteed since
// impl was originally a *MockHttpClient
const self: *MockHttpClient = @ptrCast(@alignCast(impl));
// ... implementation ...
}
};
```

### Alignment Safety

The `@alignCast` is safe when:
1. The pointer was originally the concrete type before being cast to `*anyopaque`
2. The vtable and impl are always paired correctly (same instance)

All vtable implementations in this codebase follow this pattern, ensuring alignment is preserved through the type-erasure round-trip.
82 changes: 79 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ A comprehensive AI SDK for Zig, ported from the Vercel AI SDK. This SDK provides
- **Transcription**: Speech-to-text capabilities
- **Middleware**: Extensible request/response transformation
- **Memory Safe**: Uses arena allocators for efficient memory management
- **Testable**: MockHttpClient for unit testing without network calls
- **Type-Erased HTTP**: Pluggable HTTP client interface via vtables

## Supported Providers

Expand Down Expand Up @@ -187,9 +189,34 @@ zig build run-example
The SDK uses several key patterns:

1. **Arena Allocators**: Request-scoped memory management
2. **Vtable Pattern**: Interface abstraction for models
3. **Callback-based Streaming**: Non-blocking I/O
2. **Vtable Pattern**: Interface abstraction for models and HTTP clients
3. **Callback-based Streaming**: Non-blocking I/O with SSE parsing
4. **Provider Abstraction**: Unified interface across providers
5. **Type-Erased HTTP**: Pluggable `HttpClient` interface for real and mock implementations

### HTTP Client Interface

The SDK uses a type-erased HTTP client interface that allows dependency injection:

```zig
const provider_utils = @import("provider-utils");

// Use the standard HTTP client (default)
var provider = openai.createOpenAI(allocator);

// Or inject a mock client for testing
var mock = provider_utils.MockHttpClient.init(allocator);
defer mock.deinit();

mock.setResponse(.{
.status_code = 200,
.body = "{\"choices\":[{\"message\":{\"content\":\"Hello!\"}}]}",
});

var provider = openai.createOpenAIWithSettings(allocator, .{
.http_client = mock.asInterface(),
});
```

## Memory Management

Expand Down Expand Up @@ -251,7 +278,56 @@ zig-ai-sdk/

## Requirements

- Zig 0.13.0 or later
- Zig 0.15.0 or later

## Testing

The SDK includes comprehensive unit tests for all providers:

```bash
# Run all tests
zig build test
```

### MockHttpClient

For unit testing provider implementations without network calls:

```zig
const allocator = std.testing.allocator;

var mock = provider_utils.MockHttpClient.init(allocator);
defer mock.deinit();

// Configure expected response
mock.setResponse(.{
.status_code = 200,
.body = "{\"id\":\"123\",\"choices\":[...]}",
});

// Pass to provider
var provider = openai.createOpenAIWithSettings(allocator, .{
.http_client = mock.asInterface(),
});

// Make request...

// Verify request was made correctly
const req = mock.lastRequest().?;
try std.testing.expectEqualStrings("POST", req.method.toString());
```

## Recent Changes

### v0.2.0 (Current Fork)

- **HTTP Client Interface**: Standardized `HttpClient` type across all providers with vtable-based polymorphism
- **MockHttpClient**: Added mock HTTP client for testing without network calls
- **Memory Safety**: Improved allocator passing to `getHeaders()` functions
- **Google/Vertex HTTP**: Implemented full HTTP layer for Google and Vertex providers (language, embedding, image models)
- **Response Types**: Added proper response parsing types for Google and Vertex APIs
- **Anthropic API**: Updated to API version `2024-06-01`
- **Compliance Tests**: Added comprehensive tests for OpenAI, Anthropic, Azure, Google, and Vertex providers

## Contributing

Expand Down
56 changes: 56 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub fn build(b: *std.Build) void {
});
google_vertex_mod.addImport("provider", provider_mod);
google_vertex_mod.addImport("provider-utils", provider_utils_mod);
google_vertex_mod.addImport("google", google_mod);

// Azure provider
const azure_mod = b.addModule("azure", .{
Expand Down Expand Up @@ -329,8 +330,44 @@ pub fn build(b: *std.Build) void {
.{ .path = "packages/groq/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod }, .{ .name = "openai-compatible", .mod = openai_compatible_mod } } },
.{ .path = "packages/elevenlabs/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/deepgram/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/hume/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/replicate/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/fal/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/luma/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/lmnt/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/assemblyai/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/gladia/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/revai/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/black-forest-labs/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod } } },
.{ .path = "packages/azure/src/index.zig", .imports = &.{ .{ .name = "provider", .mod = provider_mod }, .{ .name = "provider-utils", .mod = provider_utils_mod }, .{ .name = "openai", .mod = openai_mod } } },
// Integration tests
.{ .path = "tests/integration/provider_test.zig", .imports = &.{
.{ .name = "provider", .mod = provider_mod },
.{ .name = "provider-utils", .mod = provider_utils_mod },
.{ .name = "ai", .mod = ai_mod },
.{ .name = "openai", .mod = openai_mod },
.{ .name = "anthropic", .mod = anthropic_mod },
.{ .name = "google", .mod = google_mod },
.{ .name = "azure", .mod = azure_mod },
.{ .name = "mistral", .mod = mistral_mod },
.{ .name = "cohere", .mod = cohere_mod },
.{ .name = "groq", .mod = groq_mod },
.{ .name = "xai", .mod = xai_mod },
.{ .name = "perplexity", .mod = perplexity_mod },
.{ .name = "togetherai", .mod = togetherai_mod },
.{ .name = "fireworks", .mod = fireworks_mod },
.{ .name = "cerebras", .mod = cerebras_mod },
.{ .name = "huggingface", .mod = huggingface_mod },
.{ .name = "replicate", .mod = replicate_mod },
.{ .name = "elevenlabs", .mod = elevenlabs_mod },
.{ .name = "deepgram", .mod = deepgram_mod },
} },
.{ .path = "tests/integration/similarity_test.zig", .imports = &.{
.{ .name = "ai", .mod = ai_mod },
} },
.{ .path = "tests/integration/tool_test.zig", .imports = &.{
.{ .name = "ai", .mod = ai_mod },
} },
};

for (test_configs) |config| {
Expand All @@ -348,6 +385,25 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&b.addRunArtifact(tests).step);
}

// Live provider integration tests (requires API keys)
const test_live_step = b.step("test-live", "Run live provider integration tests (requires API keys)");
const live_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/integration/live_provider_test.zig"),
.target = target,
.optimize = optimize,
}),
.test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple },
});
live_tests.root_module.addImport("ai", ai_mod);
live_tests.root_module.addImport("provider", provider_mod);
live_tests.root_module.addImport("provider-utils", provider_utils_mod);
live_tests.root_module.addImport("openai", openai_mod);
live_tests.root_module.addImport("azure", azure_mod);
live_tests.root_module.addImport("xai", xai_mod);
// TODO: Add anthropic, google, google-vertex once their vtable serialization bugs are fixed
test_live_step.dependOn(&b.addRunArtifact(live_tests).step);

// Example executable
const example = b.addExecutable(.{
.name = "ai-example",
Expand Down
67 changes: 67 additions & 0 deletions examples/lifetime_example.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const std = @import("std");

// This example demonstrates correct lifetime management for vtable-based
// interfaces (LanguageModelV3, EmbeddingModelV3, HttpClient, etc.).

// ============================================================
// CORRECT: The model outlives the interface
// ============================================================
//
// var provider = createAnthropic(allocator);
// defer provider.deinit();
//
// var model = provider.messages("claude-sonnet-4-5");
// // model.asLanguageModel() borrows a pointer to model's vtable + impl.
// // The returned LanguageModelV3 is valid as long as `model` is alive.
// const iface = model.asLanguageModel();
// _ = iface; // safe to use here
//
// ============================================================
// INCORRECT: Dangling pointer - model goes out of scope
// ============================================================
//
// fn getModel(provider: *AnthropicProvider) LanguageModelV3 {
// var model = provider.messages("claude-sonnet-4-5");
// return model.asLanguageModel();
// // BUG: `model` is stack-allocated and destroyed when this
// // function returns. The returned LanguageModelV3.impl now
// // points to freed stack memory.
// }
//
// ============================================================
// CORRECT: Keep the concrete model alive alongside the interface
// ============================================================
//
// const ModelHandle = struct {
// model: AnthropicMessagesLanguageModel,
//
// pub fn asLanguageModel(self: *ModelHandle) LanguageModelV3 {
// return self.model.asLanguageModel();
// }
// };
//
// var handle = ModelHandle{ .model = provider.messages("claude-sonnet-4-5") };
// const iface = handle.asLanguageModel();
// // iface is valid as long as handle is alive
// _ = iface;
//
// ============================================================
// Key Rules
// ============================================================
//
// 1. The concrete implementation (model, http client, etc.) MUST outlive
// the type-erased interface (LanguageModelV3, HttpClient, etc.).
//
// 2. The vtable pointer should reference a file-level `const` with static
// lifetime. All implementations in this SDK follow this pattern.
//
// 3. Never return a type-erased interface from a function where the
// concrete implementation is a local variable.
//
// 4. When storing interfaces in structs, also store (or reference) the
// concrete implementation to keep it alive.

pub fn main() void {
// This file is documentation only. See the comments above for
// lifetime management patterns.
}
Loading