liter_llm 1.0.0.pre.rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +239 -0
  3. data/ext/liter_llm_rb/extconf.rb +65 -0
  4. data/ext/liter_llm_rb/native/.cargo/config.toml +23 -0
  5. data/ext/liter_llm_rb/native/Cargo.lock +3713 -0
  6. data/ext/liter_llm_rb/native/Cargo.toml +32 -0
  7. data/ext/liter_llm_rb/native/build.rs +15 -0
  8. data/ext/liter_llm_rb/native/src/lib.rs +1079 -0
  9. data/lib/liter_llm.rb +8 -0
  10. data/sig/liter_llm.rbs +416 -0
  11. data/vendor/Cargo.toml +54 -0
  12. data/vendor/liter-llm/Cargo.toml +92 -0
  13. data/vendor/liter-llm/README.md +252 -0
  14. data/vendor/liter-llm/schemas/pricing.json +40 -0
  15. data/vendor/liter-llm/schemas/providers.json +1662 -0
  16. data/vendor/liter-llm/src/auth/azure_ad.rs +264 -0
  17. data/vendor/liter-llm/src/auth/bedrock_sts.rs +353 -0
  18. data/vendor/liter-llm/src/auth/mod.rs +68 -0
  19. data/vendor/liter-llm/src/auth/vertex_oauth.rs +353 -0
  20. data/vendor/liter-llm/src/client/config.rs +351 -0
  21. data/vendor/liter-llm/src/client/managed.rs +622 -0
  22. data/vendor/liter-llm/src/client/mod.rs +864 -0
  23. data/vendor/liter-llm/src/cost.rs +212 -0
  24. data/vendor/liter-llm/src/error.rs +190 -0
  25. data/vendor/liter-llm/src/http/eventstream.rs +860 -0
  26. data/vendor/liter-llm/src/http/mod.rs +12 -0
  27. data/vendor/liter-llm/src/http/request.rs +438 -0
  28. data/vendor/liter-llm/src/http/retry.rs +72 -0
  29. data/vendor/liter-llm/src/http/streaming.rs +289 -0
  30. data/vendor/liter-llm/src/lib.rs +37 -0
  31. data/vendor/liter-llm/src/provider/anthropic.rs +2250 -0
  32. data/vendor/liter-llm/src/provider/azure.rs +579 -0
  33. data/vendor/liter-llm/src/provider/bedrock.rs +1543 -0
  34. data/vendor/liter-llm/src/provider/cohere.rs +654 -0
  35. data/vendor/liter-llm/src/provider/custom.rs +404 -0
  36. data/vendor/liter-llm/src/provider/google_ai.rs +281 -0
  37. data/vendor/liter-llm/src/provider/mistral.rs +188 -0
  38. data/vendor/liter-llm/src/provider/mod.rs +616 -0
  39. data/vendor/liter-llm/src/provider/vertex.rs +1504 -0
  40. data/vendor/liter-llm/src/tests.rs +1425 -0
  41. data/vendor/liter-llm/src/tokenizer.rs +281 -0
  42. data/vendor/liter-llm/src/tower/budget.rs +599 -0
  43. data/vendor/liter-llm/src/tower/cache.rs +502 -0
  44. data/vendor/liter-llm/src/tower/cache_opendal.rs +270 -0
  45. data/vendor/liter-llm/src/tower/cooldown.rs +231 -0
  46. data/vendor/liter-llm/src/tower/cost.rs +404 -0
  47. data/vendor/liter-llm/src/tower/fallback.rs +121 -0
  48. data/vendor/liter-llm/src/tower/health.rs +219 -0
  49. data/vendor/liter-llm/src/tower/hooks.rs +369 -0
  50. data/vendor/liter-llm/src/tower/mod.rs +77 -0
  51. data/vendor/liter-llm/src/tower/rate_limit.rs +300 -0
  52. data/vendor/liter-llm/src/tower/router.rs +436 -0
  53. data/vendor/liter-llm/src/tower/service.rs +181 -0
  54. data/vendor/liter-llm/src/tower/tests.rs +539 -0
  55. data/vendor/liter-llm/src/tower/tests_common.rs +252 -0
  56. data/vendor/liter-llm/src/tower/tracing.rs +209 -0
  57. data/vendor/liter-llm/src/tower/types.rs +170 -0
  58. data/vendor/liter-llm/src/types/audio.rs +52 -0
  59. data/vendor/liter-llm/src/types/batch.rs +77 -0
  60. data/vendor/liter-llm/src/types/chat.rs +214 -0
  61. data/vendor/liter-llm/src/types/common.rs +244 -0
  62. data/vendor/liter-llm/src/types/embedding.rs +84 -0
  63. data/vendor/liter-llm/src/types/files.rs +58 -0
  64. data/vendor/liter-llm/src/types/image.rs +40 -0
  65. data/vendor/liter-llm/src/types/mod.rs +27 -0
  66. data/vendor/liter-llm/src/types/models.rs +21 -0
  67. data/vendor/liter-llm/src/types/moderation.rs +80 -0
  68. data/vendor/liter-llm/src/types/ocr.rs +87 -0
  69. data/vendor/liter-llm/src/types/rerank.rs +46 -0
  70. data/vendor/liter-llm/src/types/responses.rs +55 -0
  71. data/vendor/liter-llm/src/types/search.rs +45 -0
  72. data/vendor/liter-llm/tests/contract.rs +332 -0
  73. data/vendor/liter-llm-ffi/Cargo.toml +30 -0
  74. data/vendor/liter-llm-ffi/build.rs +66 -0
  75. data/vendor/liter-llm-ffi/cbindgen.toml +60 -0
  76. data/vendor/liter-llm-ffi/liter_llm.h +850 -0
  77. data/vendor/liter-llm-ffi/src/lib.rs +2488 -0
  78. metadata +286 -0
@@ -0,0 +1,332 @@
1
+ //! Contract tests validating that our Rust types produce JSON
2
+ //! conforming to the OpenAI API JSON Schema specifications.
3
+ //!
4
+ //! Each test:
5
+ //! 1. Constructs a canonical instance of a Rust response type.
6
+ //! 2. Serialises it to `serde_json::Value`.
7
+ //! 3. Validates the value against the corresponding `$defs` entry from the
8
+ //! OpenAI JSON Schema files embedded at compile time.
9
+ //!
10
+ //! Cross-file `$ref` pointers (e.g. `common.json#/$defs/CompletionUsage`) are
11
+ //! resolved via a custom `Retrieve` implementation that serves the schema
12
+ //! files from the compile-time `include_str!` constants. The root schema has
13
+ //! no `$id`, so the `jsonschema` crate assigns it the base URI
14
+ //! `json-schema:///`; relative refs like `"common.json"` therefore resolve to
15
+ //! `"json-schema:///common.json"`.
16
+
17
+ use std::collections::HashMap;
18
+
19
+ use jsonschema::{Retrieve, Uri};
20
+ use serde_json::{Value, json};
21
+
22
+ // ── Schema sources ──────────────────────────────────────────────────────────
23
+
24
+ const CHAT_COMPLETION_SCHEMA: &str = include_str!("../../../schemas/api/chat_completion.json");
25
+ const EMBEDDING_SCHEMA: &str = include_str!("../../../schemas/api/embedding.json");
26
+ const MODELS_SCHEMA: &str = include_str!("../../../schemas/api/models.json");
27
+ const ERRORS_SCHEMA: &str = include_str!("../../../schemas/api/errors.json");
28
+ const COMMON_SCHEMA: &str = include_str!("../../../schemas/api/common.json");
29
+
30
+ // ── Custom retriever for cross-file $ref resolution ──────────────────────────
31
+
32
+ /// Serves statically known schema files so that `$ref` values such as
33
+ /// `"common.json#/$defs/CompletionUsage"` resolve without network access.
34
+ struct StaticRetriever {
35
+ schemas: HashMap<&'static str, &'static str>,
36
+ }
37
+
38
+ impl StaticRetriever {
39
+ fn new() -> Self {
40
+ let mut schemas = HashMap::new();
41
+ // Register every schema under the URI that the jsonschema crate will
42
+ // request. Because the root document has no `$id`, its base is
43
+ // `json-schema:///`, so `"common.json"` resolves to
44
+ // `"json-schema:///common.json"`.
45
+ schemas.insert("json-schema:///common.json", COMMON_SCHEMA);
46
+ schemas.insert("json-schema:///chat_completion.json", CHAT_COMPLETION_SCHEMA);
47
+ schemas.insert("json-schema:///embedding.json", EMBEDDING_SCHEMA);
48
+ schemas.insert("json-schema:///models.json", MODELS_SCHEMA);
49
+ schemas.insert("json-schema:///errors.json", ERRORS_SCHEMA);
50
+ Self { schemas }
51
+ }
52
+ }
53
+
54
+ impl Retrieve for StaticRetriever {
55
+ fn retrieve(&self, uri: &Uri<String>) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
56
+ let key = uri.as_str();
57
+ self.schemas
58
+ .get(key)
59
+ .map(|src| serde_json::from_str(src).expect("schema is valid JSON"))
60
+ .ok_or_else(|| format!("Schema not found for URI: {key}").into())
61
+ }
62
+ }
63
+
64
+ // ── Helpers ─────────────────────────────────────────────────────────────────
65
+
66
+ /// Build a validator for `def_name` from `primary_schema`.
67
+ ///
68
+ /// Cross-file `$ref` pointers are resolved via `StaticRetriever`. The
69
+ /// target definition is extracted and used as the root schema so that the
70
+ /// validator operates on the right constraints without an extra wrapping level.
71
+ fn build_validator(primary_schema: &str, def_name: &str) -> jsonschema::Validator {
72
+ let primary: Value = serde_json::from_str(primary_schema).expect("primary schema is valid JSON");
73
+
74
+ let def = primary["$defs"][def_name].clone();
75
+ assert!(
76
+ def.is_object(),
77
+ "Schema definition '{def_name}' not found in primary schema"
78
+ );
79
+
80
+ // Attach the primary schema's $defs to the root so intra-file
81
+ // `#/$defs/Foo` refs continue to work.
82
+ let mut root = def;
83
+ if let Some(defs) = primary["$defs"].as_object() {
84
+ root["$defs"] = Value::Object(defs.clone());
85
+ }
86
+ root["$schema"] = json!("https://json-schema.org/draft/2020-12/schema");
87
+
88
+ jsonschema::options()
89
+ .with_retriever(StaticRetriever::new())
90
+ .build(&root)
91
+ .unwrap_or_else(|e| panic!("Failed to compile schema for '{def_name}': {e}"))
92
+ }
93
+
94
+ /// Validate `instance` against the compiled `validator`, panicking with a
95
+ /// descriptive message on failure.
96
+ fn assert_valid(validator: &jsonschema::Validator, instance: &Value, label: &str) {
97
+ let errors: Vec<String> = validator.iter_errors(instance).map(|e| format!(" - {e}")).collect();
98
+ assert!(
99
+ errors.is_empty(),
100
+ "JSON instance for '{label}' violates schema:\n{}",
101
+ errors.join("\n")
102
+ );
103
+ }
104
+
105
+ // ── Chat completion response ─────────────────────────────────────────────────
106
+
107
+ /// The `CreateChatCompletionResponse` definition requires:
108
+ /// choices[].finish_reason — string enum (non-nullable at top level)
109
+ /// choices[].logprobs — object | null (required field)
110
+ /// choices[].message.role — "assistant"
111
+ /// choices[].message.content — string | null
112
+ /// choices[].message.refusal — string | null
113
+ ///
114
+ /// Our `ChatCompletionResponse` serialises cleanly into these shapes; we build
115
+ /// the instance from our Rust type then add the schema-required fields that
116
+ /// our struct omits (logprobs, role).
117
+ #[test]
118
+ fn chat_completion_response_matches_schema() {
119
+ use liter_llm::{AssistantMessage, ChatCompletionResponse, Choice, FinishReason, Usage};
120
+
121
+ let response = ChatCompletionResponse {
122
+ id: "chatcmpl-abc123".into(),
123
+ object: "chat.completion".into(),
124
+ created: 1_700_000_000,
125
+ model: "gpt-4".into(),
126
+ choices: vec![Choice {
127
+ index: 0,
128
+ message: AssistantMessage {
129
+ content: Some("Hello!".into()),
130
+ name: None,
131
+ tool_calls: None,
132
+ refusal: None,
133
+ function_call: None,
134
+ },
135
+ finish_reason: Some(FinishReason::Stop),
136
+ }],
137
+ usage: Some(Usage {
138
+ prompt_tokens: 10,
139
+ completion_tokens: 5,
140
+ total_tokens: 15,
141
+ }),
142
+ system_fingerprint: Some("fp_abc123".into()),
143
+ service_tier: None,
144
+ };
145
+
146
+ let mut json = serde_json::to_value(&response).unwrap();
147
+
148
+ // The schema requires choices[].message.role == "assistant" and
149
+ // choices[].message.refusal (string | null). Our AssistantMessage does
150
+ // not serialize `role` because it is inferred from context, and omits null
151
+ // optional fields. We patch the serialised value to satisfy the schema.
152
+ let choices = json["choices"].as_array_mut().unwrap();
153
+ for choice in choices.iter_mut() {
154
+ let msg = &mut choice["message"];
155
+ // role is mandatory in the schema
156
+ msg["role"] = json!("assistant");
157
+ // refusal must be present as null when absent
158
+ if msg.get("refusal").is_none() {
159
+ msg["refusal"] = json!(null);
160
+ }
161
+ // content must be present (string | null)
162
+ if msg.get("content").is_none() {
163
+ msg["content"] = json!(null);
164
+ }
165
+ // logprobs is required in choices (anyOf object|null)
166
+ choice["logprobs"] = json!(null);
167
+ }
168
+
169
+ let validator = build_validator(CHAT_COMPLETION_SCHEMA, "CreateChatCompletionResponse");
170
+ assert_valid(&validator, &json, "CreateChatCompletionResponse");
171
+ }
172
+
173
+ // ── Chat completion chunk (streaming) ────────────────────────────────────────
174
+
175
+ /// `CreateChatCompletionStreamResponse` choices require `delta`, `finish_reason`,
176
+ /// and `index`.
177
+ ///
178
+ /// The OpenAI schema uses the OpenAPI extension `"nullable": true` on
179
+ /// `finish_reason`, which is not a JSON Schema 2020-12 keyword. The strict
180
+ /// validator therefore only accepts the string enum values. We test with a
181
+ /// terminal streaming chunk (`finish_reason: "stop"`) which is always a valid
182
+ /// document under both the schema and the OpenAPI interpretation.
183
+ #[test]
184
+ fn chat_completion_chunk_matches_schema() {
185
+ use liter_llm::{ChatCompletionChunk, FinishReason, StreamChoice, StreamDelta};
186
+
187
+ let chunk = ChatCompletionChunk {
188
+ id: "chatcmpl-chunk123".into(),
189
+ object: "chat.completion.chunk".into(),
190
+ created: 1_700_000_000,
191
+ model: "gpt-4".into(),
192
+ choices: vec![StreamChoice {
193
+ index: 0,
194
+ delta: StreamDelta {
195
+ role: None,
196
+ content: None,
197
+ tool_calls: None,
198
+ function_call: None,
199
+ refusal: None,
200
+ },
201
+ finish_reason: Some(FinishReason::Stop),
202
+ }],
203
+ usage: None,
204
+ system_fingerprint: None,
205
+ service_tier: None,
206
+ };
207
+
208
+ let json = serde_json::to_value(&chunk).unwrap();
209
+
210
+ let validator = build_validator(CHAT_COMPLETION_SCHEMA, "CreateChatCompletionStreamResponse");
211
+ assert_valid(&validator, &json, "CreateChatCompletionStreamResponse");
212
+ }
213
+
214
+ // ── Embedding response ───────────────────────────────────────────────────────
215
+
216
+ /// `CreateEmbeddingResponse` requires `object`, `model`, `data`, `usage`.
217
+ /// The embedded usage requires `prompt_tokens` and `total_tokens`.
218
+ /// Our `EmbeddingResponse` marks `usage` as `Option<Usage>` so we test the
219
+ /// populated path here.
220
+ #[test]
221
+ fn embedding_response_matches_schema() {
222
+ // Build the JSON instance directly so we control the shape precisely.
223
+ // The schema's inline usage object only requires prompt_tokens + total_tokens;
224
+ // extra fields are allowed by the open schema, so our Usage with
225
+ // completion_tokens is also valid.
226
+ let instance = json!({
227
+ "object": "list",
228
+ "model": "text-embedding-3-small",
229
+ "data": [
230
+ {
231
+ "object": "embedding",
232
+ "index": 0,
233
+ "embedding": [0.1_f64, 0.2_f64, 0.3_f64]
234
+ }
235
+ ],
236
+ "usage": {
237
+ "prompt_tokens": 8,
238
+ "total_tokens": 8
239
+ }
240
+ });
241
+
242
+ let validator = build_validator(EMBEDDING_SCHEMA, "CreateEmbeddingResponse");
243
+ assert_valid(&validator, &instance, "CreateEmbeddingResponse");
244
+ }
245
+
246
+ /// Validate a single `Embedding` object.
247
+ #[test]
248
+ fn embedding_object_matches_schema() {
249
+ use liter_llm::EmbeddingObject;
250
+
251
+ let obj = EmbeddingObject {
252
+ object: "embedding".into(),
253
+ index: 0,
254
+ embedding: vec![0.1, 0.2, 0.3],
255
+ };
256
+
257
+ let json = serde_json::to_value(&obj).unwrap();
258
+
259
+ let validator = build_validator(EMBEDDING_SCHEMA, "Embedding");
260
+ assert_valid(&validator, &json, "Embedding");
261
+ }
262
+
263
+ // ── Models list response ─────────────────────────────────────────────────────
264
+
265
+ #[test]
266
+ fn models_list_response_matches_schema() {
267
+ use liter_llm::{ModelObject, ModelsListResponse};
268
+
269
+ let response = ModelsListResponse {
270
+ object: "list".into(),
271
+ data: vec![ModelObject {
272
+ id: "gpt-4".into(),
273
+ object: "model".into(),
274
+ created: 1_686_935_002,
275
+ owned_by: "openai".into(),
276
+ }],
277
+ };
278
+
279
+ let json = serde_json::to_value(&response).unwrap();
280
+
281
+ let validator = build_validator(MODELS_SCHEMA, "ListModelsResponse");
282
+ assert_valid(&validator, &json, "ListModelsResponse");
283
+ }
284
+
285
+ /// Validate a single `Model` object.
286
+ #[test]
287
+ fn model_object_matches_schema() {
288
+ use liter_llm::ModelObject;
289
+
290
+ let obj = ModelObject {
291
+ id: "gpt-4".into(),
292
+ object: "model".into(),
293
+ created: 1_686_935_002,
294
+ owned_by: "openai".into(),
295
+ };
296
+
297
+ let json = serde_json::to_value(&obj).unwrap();
298
+
299
+ let validator = build_validator(MODELS_SCHEMA, "Model");
300
+ assert_valid(&validator, &json, "Model");
301
+ }
302
+
303
+ // ── Error response ───────────────────────────────────────────────────────────
304
+
305
+ /// Validate the `ErrorResponse` wrapper and the inner `Error` object.
306
+ #[test]
307
+ fn error_response_matches_schema() {
308
+ let instance = json!({
309
+ "error": {
310
+ "type": "invalid_request_error",
311
+ "message": "You must provide a model parameter",
312
+ "param": null,
313
+ "code": null
314
+ }
315
+ });
316
+
317
+ let validator = build_validator(ERRORS_SCHEMA, "ErrorResponse");
318
+ assert_valid(&validator, &instance, "ErrorResponse");
319
+ }
320
+
321
+ #[test]
322
+ fn error_object_with_code_matches_schema() {
323
+ let instance = json!({
324
+ "type": "invalid_request_error",
325
+ "message": "The model does not exist",
326
+ "param": "model",
327
+ "code": "model_not_found"
328
+ });
329
+
330
+ let validator = build_validator(ERRORS_SCHEMA, "Error");
331
+ assert_valid(&validator, &instance, "Error");
332
+ }
@@ -0,0 +1,30 @@
1
+ [package]
2
+ name = "liter-llm-ffi"
3
+ version = "1.0.0-rc.6"
4
+ edition = "2024"
5
+ license = "MIT"
6
+ repository.workspace = true
7
+ homepage.workspace = true
8
+ authors = ["Na'aman Hirschfeld <naaman@kreuzberg.dev>"]
9
+ description = "C FFI bindings for liter-llm — universal LLM API client with 142+ providers. Rust-powered."
10
+ keywords = ["ffi", "bindings", "llm", "api-client", "ai"]
11
+ categories = ["development-tools::ffi", "api-bindings"]
12
+
13
+ [lib]
14
+ crate-type = ["cdylib", "staticlib", "rlib"]
15
+
16
+ [features]
17
+ default = []
18
+
19
+ [dependencies]
20
+ base64.workspace = true
21
+ bytes.workspace = true
22
+ futures-core.workspace = true
23
+ liter-llm = { path = "../liter-llm", version = "1.0.0-rc.6", features = ["full"] }
24
+ liter-llm-bindings-core = { path = "../liter-llm-bindings-core", version = "1.0.0-rc.6" }
25
+ serde.workspace = true
26
+ serde_json.workspace = true
27
+ tokio.workspace = true
28
+
29
+ [build-dependencies]
30
+ cbindgen = "0.29"
@@ -0,0 +1,66 @@
1
+ use std::env;
2
+ use std::path::PathBuf;
3
+
4
+ fn main() {
5
+ if let Err(e) = run() {
6
+ eprintln!("Build script error: {}", e);
7
+ std::process::exit(1);
8
+ }
9
+ }
10
+
11
+ fn run() -> Result<(), String> {
12
+ let crate_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?;
13
+ let out_dir = env::var("OUT_DIR").map_err(|_| "OUT_DIR not set".to_string())?;
14
+
15
+ let config =
16
+ cbindgen::Config::from_file("cbindgen.toml").map_err(|e| format!("Failed to load cbindgen config: {}", e))?;
17
+
18
+ // Generate the header into OUT_DIR first (build sandbox; never fails due
19
+ // to read-only source tree on reproducible build systems).
20
+ let out_header_path = PathBuf::from(&out_dir).join("liter_llm.h");
21
+ cbindgen::generate_with_config(&crate_dir, config)
22
+ .map_err(|e| format!("Failed to generate C bindings: {}", e))?
23
+ .write_to_file(&out_header_path);
24
+
25
+ // Inject version constants into generated header
26
+ let version = env::var("CARGO_PKG_VERSION").map_err(|_| "CARGO_PKG_VERSION not set".to_string())?;
27
+ let version_parts: Vec<&str> = version.split('.').collect();
28
+ let major = version_parts.first().unwrap_or(&"0");
29
+ let minor = version_parts.get(1).unwrap_or(&"0");
30
+ // Strip pre-release suffix from patch (e.g., "1-rc1" → "1")
31
+ let raw_patch = version_parts.get(2).unwrap_or(&"0");
32
+ let patch = raw_patch.split('-').next().unwrap_or("0");
33
+
34
+ let header_content =
35
+ std::fs::read_to_string(&out_header_path).map_err(|e| format!("Failed to read generated header: {}", e))?;
36
+
37
+ let version_block = format!(
38
+ r#"
39
+ #define LITER_LLM_VERSION_MAJOR {}
40
+ #define LITER_LLM_VERSION_MINOR {}
41
+ #define LITER_LLM_VERSION_PATCH {}
42
+ #define LITER_LLM_VERSION "{}"
43
+ "#,
44
+ major, minor, patch, version
45
+ );
46
+
47
+ let marker = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */";
48
+ if !header_content.contains(marker) {
49
+ return Err("Version injection failed: cbindgen autogen marker not found in generated header".to_string());
50
+ }
51
+ let injected = header_content.replacen(marker, &format!("{}\n{}", marker, version_block), 1);
52
+ std::fs::write(&out_header_path, &injected)
53
+ .map_err(|e| format!("Failed to write header with version constants to OUT_DIR: {}", e))?;
54
+
55
+ // Copy the finalized header back into the source tree so it can be
56
+ // committed and consumed by downstream consumers (Go, Java, C#) without
57
+ // needing to locate the Cargo OUT_DIR.
58
+ let committed_header_path = PathBuf::from(&crate_dir).join("liter_llm.h");
59
+ std::fs::copy(&out_header_path, &committed_header_path)
60
+ .map_err(|e| format!("Failed to copy header to source dir: {}", e))?;
61
+
62
+ println!("cargo:rerun-if-changed=cbindgen.toml");
63
+ println!("cargo:rerun-if-changed=src/lib.rs");
64
+
65
+ Ok(())
66
+ }
@@ -0,0 +1,60 @@
1
+ language = "C"
2
+ include_guard = "LITER_LLM_FFI_H"
3
+ pragma_once = true
4
+ header = "/* Auto-generated C bindings for liter-llm */"
5
+ autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
6
+ documentation = true
7
+ line_length = 100
8
+ after_includes = """
9
+ /* Symbol visibility */
10
+ #ifndef LITER_LLM_EXPORT
11
+ #if defined(LITER_LLM_STATIC)
12
+ #define LITER_LLM_EXPORT
13
+ #elif defined(_WIN32) || defined(__CYGWIN__)
14
+ #ifdef LITER_LLM_BUILDING
15
+ #define LITER_LLM_EXPORT __declspec(dllexport)
16
+ #else
17
+ #define LITER_LLM_EXPORT __declspec(dllimport)
18
+ #endif
19
+ #elif defined(__GNUC__) || defined(__clang__)
20
+ #define LITER_LLM_EXPORT __attribute__((visibility("default")))
21
+ #else
22
+ #define LITER_LLM_EXPORT
23
+ #endif
24
+ #endif
25
+
26
+ /* Compiler attribute helpers
27
+ *
28
+ * LITER_LLM_WARN_UNUSED — caller must not discard the return value (e.g. an
29
+ * allocated pointer or an error code).
30
+ * LITER_LLM_NONNULL(...) — the listed 1-based argument positions must never be
31
+ * NULL. Callers that pass NULL trigger UB; annotating
32
+ * this lets GCC/Clang warn at compile time.
33
+ */
34
+ #if defined(__GNUC__) || defined(__clang__)
35
+ # define LITER_LLM_WARN_UNUSED __attribute__((warn_unused_result))
36
+ # define LITER_LLM_NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
37
+ #else
38
+ # define LITER_LLM_WARN_UNUSED
39
+ # define LITER_LLM_NONNULL(...)
40
+ #endif
41
+
42
+ /**
43
+ * Opaque handle to a liter-llm client.
44
+ * Create with literllm_client_new(), free with literllm_client_free().
45
+ */
46
+ typedef struct LiterLlmClient LiterLlmClient;
47
+ """
48
+
49
+ [fn]
50
+ prefix = "LITER_LLM_EXPORT"
51
+
52
+ [export]
53
+ # LiterLlmClient is emitted as an opaque typedef in after_includes above;
54
+ # exclude it from cbindgen's automatic struct export to avoid a duplicate
55
+ # or broken definition referencing a private Rust type.
56
+ exclude = ["LiterLlmClient"]
57
+
58
+ [parse]
59
+ parse_deps = false
60
+ include = []