simple-agents-py 0.2.0__tar.gz → 0.2.2__tar.gz

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 (134) hide show
  1. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/Cargo.lock +14 -12
  2. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/Cargo.toml +1 -1
  3. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/PKG-INFO +1 -1
  4. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-cache/src/memory.rs +47 -3
  5. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/Cargo.toml +5 -3
  6. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/src/client.rs +323 -28
  7. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/src/middleware.rs +5 -0
  8. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/Cargo.toml +1 -1
  9. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/Cargo.toml +3 -3
  10. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/Cargo.toml +5 -5
  11. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/uv.lock +1 -1
  12. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/pyproject.toml +1 -1
  13. simple_agents_py-0.2.0/crates/simple-agent-type/TODO.md +0 -590
  14. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/README.md +0 -0
  15. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/Cargo.toml +0 -0
  16. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/README.md +0 -0
  17. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/TEST_GUIDE.md +0 -0
  18. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/examples/basic_usage.rs +0 -0
  19. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/examples/mock_provider.rs +0 -0
  20. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/cache.rs +0 -0
  21. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/coercion.rs +0 -0
  22. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/config.rs +0 -0
  23. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/error.rs +0 -0
  24. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/lib.rs +0 -0
  25. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/message.rs +0 -0
  26. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/provider.rs +0 -0
  27. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/request.rs +0 -0
  28. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/response.rs +0 -0
  29. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/router.rs +0 -0
  30. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/tool.rs +0 -0
  31. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/src/validation.rs +0 -0
  32. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agent-type/tests/integration_test.rs +0 -0
  33. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-cache/Cargo.toml +0 -0
  34. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-cache/src/lib.rs +0 -0
  35. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-cache/src/noop.rs +0 -0
  36. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/README.md +0 -0
  37. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/examples/basic_client.rs +0 -0
  38. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/src/healing.rs +0 -0
  39. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/src/lib.rs +0 -0
  40. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/src/routing.rs +0 -0
  41. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-core/tests/client_integration.rs +0 -0
  42. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/README.md +0 -0
  43. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/benches/parser_benchmarks.rs +0 -0
  44. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/examples/basic_healing.rs +0 -0
  45. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/examples/coercion_demo.rs +0 -0
  46. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/examples/streaming_annotations.rs +0 -0
  47. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/examples/streaming_partial_types.rs +0 -0
  48. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/src/coercion.rs +0 -0
  49. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/src/lib.rs +0 -0
  50. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/src/parser.rs +0 -0
  51. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/src/schema.rs +0 -0
  52. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/src/streaming.rs +0 -0
  53. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/src/string_utils.rs +0 -0
  54. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/tests/parser_tests.rs +0 -0
  55. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/tests/property_tests.rs +0 -0
  56. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/tests/stream_annotations_tests.rs +0 -0
  57. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-healing/tests/streaming_tests.rs +0 -0
  58. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-macros/Cargo.toml +0 -0
  59. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-macros/README.md +0 -0
  60. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-macros/src/lib.rs +0 -0
  61. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-macros/src/partial.rs +0 -0
  62. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-macros/tests/partial_type_tests.rs +0 -0
  63. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/README.md +0 -0
  64. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/anthropic_basic.rs +0 -0
  65. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/anthropic_structured_output.rs +0 -0
  66. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/cache_usage.rs +0 -0
  67. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/custom_api.rs +0 -0
  68. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/healing_fallback.rs +0 -0
  69. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/openai_basic.rs +0 -0
  70. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/openai_structured_output.rs +0 -0
  71. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/openrouter_basic.rs +0 -0
  72. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/retry_demo.rs +0 -0
  73. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/streaming.rs +0 -0
  74. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/streaming_structured.rs +0 -0
  75. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/streaming_with_healing.rs +0 -0
  76. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/test_local_api.rs +0 -0
  77. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/examples/test_reqwest.rs +0 -0
  78. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/run_integration_tests.sh +0 -0
  79. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/anthropic/error.rs +0 -0
  80. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/anthropic/mod.rs +0 -0
  81. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/anthropic/models.rs +0 -0
  82. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/anthropic/streaming.rs +0 -0
  83. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/common/error.rs +0 -0
  84. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/common/http_client.rs +0 -0
  85. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/common/mod.rs +0 -0
  86. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/healing_integration.rs +0 -0
  87. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/lib.rs +0 -0
  88. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/metrics.rs +0 -0
  89. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/openai/error.rs +0 -0
  90. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/openai/mod.rs +0 -0
  91. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/openai/models.rs +0 -0
  92. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/openai/streaming.rs +0 -0
  93. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/openrouter/mod.rs +0 -0
  94. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/rate_limit.rs +0 -0
  95. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/retry.rs +0 -0
  96. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/schema_converter.rs +0 -0
  97. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/streaming_structured.rs +0 -0
  98. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/src/utils.rs +0 -0
  99. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/tests/README.md +0 -0
  100. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/tests/healing_integration_tests.rs +0 -0
  101. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-providers/tests/openai_integration.rs +0 -0
  102. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/README.md +0 -0
  103. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/examples/client_builder_demo.py +0 -0
  104. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/examples/direct_healing_demo.py +0 -0
  105. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/examples/healing_demo.py +0 -0
  106. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/examples/routing_config_demo.py +0 -0
  107. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/examples/streaming_demo.py +0 -0
  108. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/examples/streaming_parser_demo.py +0 -0
  109. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/examples/structured_streaming_demo.py +0 -0
  110. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/py.typed +0 -0
  111. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/simple_agents_py.pyi +0 -0
  112. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/src/lib.rs +0 -0
  113. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_client.py +0 -0
  114. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_client_builder.py +0 -0
  115. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_direct_healing.py +0 -0
  116. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_healing.py +0 -0
  117. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_integration_openai.py +0 -0
  118. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_routing_config.py +0 -0
  119. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_streaming.py +0 -0
  120. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_streaming_parser.py +0 -0
  121. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-py/tests/test_structured_streaming.py +0 -0
  122. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/Cargo.toml +0 -0
  123. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/examples/round_robin_router.rs +0 -0
  124. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/circuit_breaker.rs +0 -0
  125. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/cost.rs +0 -0
  126. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/fallback.rs +0 -0
  127. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/health.rs +0 -0
  128. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/latency.rs +0 -0
  129. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/lib.rs +0 -0
  130. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/retry.rs +0 -0
  131. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/src/round_robin.rs +0 -0
  132. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/crates/simple-agents-router/tests/health_tracker_integration.rs +0 -0
  133. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/py.typed +0 -0
  134. {simple_agents_py-0.2.0 → simple_agents_py-0.2.2}/simple_agents_py.pyi +0 -0
@@ -2142,7 +2142,7 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
2142
2142
 
2143
2143
  [[package]]
2144
2144
  name = "simple-agent-type"
2145
- version = "0.2.0"
2145
+ version = "0.2.2"
2146
2146
  dependencies = [
2147
2147
  "async-trait",
2148
2148
  "blake3",
@@ -2157,7 +2157,7 @@ dependencies = [
2157
2157
 
2158
2158
  [[package]]
2159
2159
  name = "simple-agents-cache"
2160
- version = "0.2.0"
2160
+ version = "0.2.2"
2161
2161
  dependencies = [
2162
2162
  "async-trait",
2163
2163
  "simple-agent-type",
@@ -2166,7 +2166,7 @@ dependencies = [
2166
2166
 
2167
2167
  [[package]]
2168
2168
  name = "simple-agents-cli"
2169
- version = "0.2.0"
2169
+ version = "0.2.2"
2170
2170
  dependencies = [
2171
2171
  "clap",
2172
2172
  "serde",
@@ -2183,10 +2183,11 @@ dependencies = [
2183
2183
 
2184
2184
  [[package]]
2185
2185
  name = "simple-agents-core"
2186
- version = "0.2.0"
2186
+ version = "0.2.2"
2187
2187
  dependencies = [
2188
2188
  "async-trait",
2189
2189
  "futures-core",
2190
+ "futures-util",
2190
2191
  "serde",
2191
2192
  "serde_json",
2192
2193
  "simple-agent-type",
@@ -2194,11 +2195,12 @@ dependencies = [
2194
2195
  "simple-agents-healing",
2195
2196
  "simple-agents-router",
2196
2197
  "tokio",
2198
+ "tracing",
2197
2199
  ]
2198
2200
 
2199
2201
  [[package]]
2200
2202
  name = "simple-agents-examples"
2201
- version = "0.2.0"
2203
+ version = "0.2.2"
2202
2204
  dependencies = [
2203
2205
  "dotenv",
2204
2206
  "futures-util",
@@ -2211,7 +2213,7 @@ dependencies = [
2211
2213
 
2212
2214
  [[package]]
2213
2215
  name = "simple-agents-ffi"
2214
- version = "0.2.0"
2216
+ version = "0.2.2"
2215
2217
  dependencies = [
2216
2218
  "async-trait",
2217
2219
  "simple-agent-type",
@@ -2222,7 +2224,7 @@ dependencies = [
2222
2224
 
2223
2225
  [[package]]
2224
2226
  name = "simple-agents-healing"
2225
- version = "0.2.0"
2227
+ version = "0.2.2"
2226
2228
  dependencies = [
2227
2229
  "criterion",
2228
2230
  "proptest",
@@ -2238,7 +2240,7 @@ dependencies = [
2238
2240
 
2239
2241
  [[package]]
2240
2242
  name = "simple-agents-macros"
2241
- version = "0.2.0"
2243
+ version = "0.2.2"
2242
2244
  dependencies = [
2243
2245
  "proc-macro2",
2244
2246
  "quote",
@@ -2250,7 +2252,7 @@ dependencies = [
2250
2252
 
2251
2253
  [[package]]
2252
2254
  name = "simple-agents-napi"
2253
- version = "0.2.0"
2255
+ version = "0.2.2"
2254
2256
  dependencies = [
2255
2257
  "napi",
2256
2258
  "napi-derive",
@@ -2262,7 +2264,7 @@ dependencies = [
2262
2264
 
2263
2265
  [[package]]
2264
2266
  name = "simple-agents-providers"
2265
- version = "0.2.0"
2267
+ version = "0.2.2"
2266
2268
  dependencies = [
2267
2269
  "async-trait",
2268
2270
  "bytes",
@@ -2290,7 +2292,7 @@ dependencies = [
2290
2292
 
2291
2293
  [[package]]
2292
2294
  name = "simple-agents-py"
2293
- version = "0.2.0"
2295
+ version = "0.2.2"
2294
2296
  dependencies = [
2295
2297
  "async-trait",
2296
2298
  "futures-util",
@@ -2309,7 +2311,7 @@ dependencies = [
2309
2311
 
2310
2312
  [[package]]
2311
2313
  name = "simple-agents-router"
2312
- version = "0.2.0"
2314
+ version = "0.2.2"
2313
2315
  dependencies = [
2314
2316
  "async-trait",
2315
2317
  "futures-core",
@@ -3,7 +3,7 @@ members = ["crates/*"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.0"
6
+ version = "0.2.2"
7
7
  edition = "2021"
8
8
  rust-version = "1.75"
9
9
  authors = ["SimpleAgents Contributors"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple-agents-py
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -132,11 +132,31 @@ impl InMemoryCache {
132
132
  #[async_trait]
133
133
  impl Cache for InMemoryCache {
134
134
  async fn get(&self, key: &str) -> Result<Option<Vec<u8>>> {
135
- // First evict expired entries
136
- self.evict_expired().await;
135
+ {
136
+ let store = self.store.read().await;
137
+ match store.get(key) {
138
+ Some(entry) if !entry.is_expired() => {
139
+ drop(store);
140
+
141
+ let mut store = self.store.write().await;
142
+ if let Some(entry) = store.get_mut(key) {
143
+ if entry.is_expired() {
144
+ store.remove(key);
145
+ return Ok(None);
146
+ }
147
+
148
+ entry.touch();
149
+ return Ok(Some(entry.data.clone()));
150
+ }
151
+
152
+ return Ok(None);
153
+ }
154
+ Some(_) => {} // expired; clean up below
155
+ None => return Ok(None),
156
+ }
157
+ }
137
158
 
138
159
  let mut store = self.store.write().await;
139
-
140
160
  if let Some(entry) = store.get_mut(key) {
141
161
  if entry.is_expired() {
142
162
  store.remove(key);
@@ -162,6 +182,9 @@ impl Cache for InMemoryCache {
162
182
  store.insert(key.to_string(), entry);
163
183
  }
164
184
 
185
+ // Periodically clear expired entries before enforcing limits
186
+ self.evict_expired().await;
187
+
165
188
  // Evict if needed
166
189
  self.evict_lru().await;
167
190
 
@@ -324,6 +347,27 @@ mod tests {
324
347
  assert!(cache.get("key3").await.unwrap().is_some());
325
348
  }
326
349
 
350
+ #[tokio::test]
351
+ async fn test_concurrent_gets_do_not_serialize_readers() {
352
+ let cache = Arc::new(InMemoryCache::new(1024, 10));
353
+ cache
354
+ .set("shared", b"value".to_vec(), Duration::from_secs(60))
355
+ .await
356
+ .unwrap();
357
+
358
+ let mut handles = Vec::new();
359
+ for _ in 0..25 {
360
+ let cache = cache.clone();
361
+ handles.push(tokio::spawn(
362
+ async move { cache.get("shared").await.unwrap() },
363
+ ));
364
+ }
365
+
366
+ for handle in handles {
367
+ assert_eq!(handle.await.unwrap(), Some(b"value".to_vec()));
368
+ }
369
+ }
370
+
327
371
  #[tokio::test]
328
372
  async fn test_cache_name() {
329
373
  let cache = InMemoryCache::new(1024, 10);
@@ -16,11 +16,13 @@ async-trait.workspace = true
16
16
  serde.workspace = true
17
17
  serde_json.workspace = true
18
18
  tokio.workspace = true
19
+ futures-util.workspace = true
20
+ tracing = "0.1"
19
21
 
20
22
  simple-agent-type = { workspace = true }
21
- simple-agents-router = { path = "../simple-agents-router", version = "0.2.0" }
22
- simple-agents-cache = { path = "../simple-agents-cache", version = "0.2.0" }
23
- simple-agents-healing = { path = "../simple-agents-healing", version = "0.2.0" }
23
+ simple-agents-router = { path = "../simple-agents-router", version = "0.2.2" }
24
+ simple-agents-cache = { path = "../simple-agents-cache", version = "0.2.2" }
25
+ simple-agents-healing = { path = "../simple-agents-healing", version = "0.2.2" }
24
26
  futures-core = "0.3"
25
27
 
26
28
  [dev-dependencies]
@@ -4,6 +4,9 @@ use crate::healing::{HealedJsonResponse, HealedSchemaResponse, HealingSettings};
4
4
  use crate::middleware::Middleware;
5
5
  use crate::routing::{RouterEngine, RoutingMode};
6
6
  use async_trait::async_trait;
7
+ use futures_util::future::BoxFuture;
8
+ use futures_util::stream::{self, Stream};
9
+ use futures_util::StreamExt;
7
10
  use simple_agent_type::cache::Cache;
8
11
  use simple_agent_type::cache::CacheKey;
9
12
  use simple_agent_type::prelude::{
@@ -13,8 +16,10 @@ use simple_agents_healing::coercion::CoercionEngine;
13
16
  use simple_agents_healing::parser::JsonishParser;
14
17
  use simple_agents_healing::schema::Schema;
15
18
  use std::collections::HashMap;
16
- use std::sync::{Arc, RwLock};
19
+ use std::sync::Arc;
17
20
  use std::time::{Duration, Instant};
21
+ use tokio::sync::RwLock;
22
+ use tracing::debug;
18
23
 
19
24
  /// Mode for completion post-processing.
20
25
  pub enum CompletionMode {
@@ -75,29 +80,30 @@ impl SimpleAgentsClient {
75
80
  }
76
81
 
77
82
  /// List registered provider names.
78
- pub fn provider_names(&self) -> Result<Vec<String>> {
79
- let state = self.state.read().map_err(|_| {
80
- SimpleAgentsError::Config("provider registry lock poisoned".to_string())
81
- })?;
83
+ pub async fn provider_names(&self) -> Result<Vec<String>> {
84
+ let state = self.state.read().await;
82
85
  Ok(state.provider_map.keys().cloned().collect())
83
86
  }
84
87
 
85
88
  /// Retrieve a provider by name.
86
- pub fn provider(&self, name: &str) -> Result<Option<Arc<dyn Provider>>> {
87
- let state = self.state.read().map_err(|_| {
88
- SimpleAgentsError::Config("provider registry lock poisoned".to_string())
89
- })?;
89
+ pub async fn provider(&self, name: &str) -> Result<Option<Arc<dyn Provider>>> {
90
+ let state = self.state.read().await;
90
91
  Ok(state.provider_map.get(name).cloned())
91
92
  }
92
93
 
93
94
  /// Register an additional provider and rebuild the router.
94
- pub fn register_provider(&self, provider: Arc<dyn Provider>) -> Result<()> {
95
- let mut state = self.state.write().map_err(|_| {
96
- SimpleAgentsError::Config("provider registry lock poisoned".to_string())
97
- })?;
98
- state
99
- .provider_map
100
- .insert(provider.name().to_string(), provider.clone());
95
+ pub async fn register_provider(&self, provider: Arc<dyn Provider>) -> Result<()> {
96
+ let mut state = self.state.write().await;
97
+ let name = provider.name().to_string();
98
+
99
+ if state.provider_map.contains_key(&name) {
100
+ return Err(SimpleAgentsError::Config(format!(
101
+ "provider already registered: {}",
102
+ name
103
+ )));
104
+ }
105
+
106
+ state.provider_map.insert(name, provider.clone());
101
107
  state.providers.push(provider);
102
108
  state.router = Arc::new(self.routing_mode.build_router(state.providers.clone())?);
103
109
  Ok(())
@@ -154,9 +160,7 @@ impl SimpleAgentsClient {
154
160
 
155
161
  let start = Instant::now();
156
162
  let router = {
157
- let state = self.state.read().map_err(|_| {
158
- SimpleAgentsError::Config("provider registry lock poisoned".to_string())
159
- })?;
163
+ let state = self.state.read().await;
160
164
  state.router.clone()
161
165
  };
162
166
  let response = router.complete(request).await;
@@ -225,19 +229,24 @@ impl SimpleAgentsClient {
225
229
  ) -> Result<Box<dyn futures_core::Stream<Item = Result<CompletionChunk>> + Send + Unpin>> {
226
230
  request.validate()?;
227
231
  self.before_request(request).await?;
228
- eprintln!(
229
- "SimpleAgentsClient.stream: model={}, stream={:?}",
230
- request.model, request.stream
232
+ debug!(
233
+ model = %request.model,
234
+ stream = ?request.stream,
235
+ "SimpleAgentsClient.stream start"
231
236
  );
232
237
 
233
238
  let router = {
234
- let state = self.state.read().map_err(|_| {
235
- SimpleAgentsError::Config("provider registry lock poisoned".to_string())
236
- })?;
239
+ let state = self.state.read().await;
237
240
  state.router.clone()
238
241
  };
239
242
 
240
- router.stream(request).await
243
+ let start = Instant::now();
244
+ let middleware = self.middleware.clone();
245
+ let instrumented_request = request.clone();
246
+ let inner = router.stream(request).await?;
247
+
248
+ let wrapped = Self::instrument_stream(inner, instrumented_request, middleware, start);
249
+ Ok(Box::new(wrapped))
241
250
  }
242
251
 
243
252
  fn ensure_healing_enabled(&self) -> Result<()> {
@@ -300,6 +309,69 @@ impl SimpleAgentsClient {
300
309
  }
301
310
  }
302
311
 
312
+ impl SimpleAgentsClient {
313
+ fn instrument_stream(
314
+ inner: Box<dyn Stream<Item = Result<CompletionChunk>> + Send + Unpin>,
315
+ request: CompletionRequest,
316
+ middleware: Vec<Arc<dyn Middleware>>,
317
+ start: Instant,
318
+ ) -> impl Stream<Item = Result<CompletionChunk>> + Send + Unpin {
319
+ struct StreamState {
320
+ inner: Box<dyn Stream<Item = Result<CompletionChunk>> + Send + Unpin>,
321
+ middleware: Vec<Arc<dyn Middleware>>,
322
+ request: CompletionRequest,
323
+ start: Instant,
324
+ done: bool,
325
+ }
326
+
327
+ stream::unfold(
328
+ StreamState {
329
+ inner,
330
+ middleware,
331
+ request,
332
+ start,
333
+ done: false,
334
+ },
335
+ |mut state| -> BoxFuture<Option<(Result<CompletionChunk>, StreamState)>> {
336
+ Box::pin(async move {
337
+ if state.done {
338
+ return None;
339
+ }
340
+
341
+ match state.inner.next().await {
342
+ Some(Ok(chunk)) => Some((Ok(chunk), state)),
343
+ Some(Err(err)) => {
344
+ let latency = state.start.elapsed();
345
+ for middleware in &state.middleware {
346
+ if let Err(mw_err) =
347
+ middleware.on_error(&state.request, &err, latency).await
348
+ {
349
+ state.done = true;
350
+ return Some((Err(mw_err), state));
351
+ }
352
+ }
353
+ state.done = true;
354
+ Some((Err(err), state))
355
+ }
356
+ None => {
357
+ let latency = state.start.elapsed();
358
+ for middleware in &state.middleware {
359
+ if let Err(mw_err) =
360
+ middleware.after_stream(&state.request, latency).await
361
+ {
362
+ state.done = true;
363
+ return Some((Err(mw_err), state));
364
+ }
365
+ }
366
+ None
367
+ }
368
+ }
369
+ })
370
+ },
371
+ )
372
+ }
373
+ }
374
+
303
375
  /// Builder for `SimpleAgentsClient`.
304
376
  pub struct SimpleAgentsClientBuilder {
305
377
  providers: Vec<Arc<dyn Provider>>,
@@ -413,8 +485,11 @@ impl Middleware for () {
413
485
  #[cfg(test)]
414
486
  mod tests {
415
487
  use super::*;
488
+ use futures_util::{stream, StreamExt};
489
+ use simple_agent_type::error::ProviderError;
416
490
  use simple_agent_type::prelude::*;
417
491
  use std::sync::atomic::{AtomicUsize, Ordering};
492
+ use std::time::Duration;
418
493
 
419
494
  struct MockProvider {
420
495
  name: &'static str,
@@ -481,10 +556,230 @@ mod tests {
481
556
  .unwrap();
482
557
 
483
558
  let second = Arc::new(MockProvider::new("p2"));
484
- client.register_provider(second).unwrap();
559
+ client.register_provider(second).await.unwrap();
485
560
 
486
- let names = client.provider_names().unwrap();
561
+ let names = client.provider_names().await.unwrap();
487
562
  assert!(names.contains(&"p1".to_string()));
488
563
  assert!(names.contains(&"p2".to_string()));
489
564
  }
565
+
566
+ #[tokio::test]
567
+ async fn duplicate_provider_registration_fails() {
568
+ let provider = Arc::new(MockProvider::new("p1"));
569
+ let client = SimpleAgentsClientBuilder::new()
570
+ .with_provider(provider.clone())
571
+ .build()
572
+ .unwrap();
573
+
574
+ let result = client.register_provider(provider).await;
575
+ assert!(matches!(
576
+ result,
577
+ Err(SimpleAgentsError::Config(msg)) if msg.contains("provider already registered")
578
+ ));
579
+ }
580
+
581
+ #[derive(Default)]
582
+ struct RecordingMiddleware {
583
+ before: AtomicUsize,
584
+ after_stream: AtomicUsize,
585
+ errors: AtomicUsize,
586
+ }
587
+
588
+ #[async_trait]
589
+ impl Middleware for RecordingMiddleware {
590
+ async fn before_request(&self, _request: &CompletionRequest) -> Result<()> {
591
+ self.before.fetch_add(1, Ordering::Relaxed);
592
+ Ok(())
593
+ }
594
+
595
+ async fn after_stream(
596
+ &self,
597
+ _request: &CompletionRequest,
598
+ _latency: Duration,
599
+ ) -> Result<()> {
600
+ self.after_stream.fetch_add(1, Ordering::Relaxed);
601
+ Ok(())
602
+ }
603
+
604
+ async fn on_error(
605
+ &self,
606
+ _request: &CompletionRequest,
607
+ _error: &SimpleAgentsError,
608
+ _latency: Duration,
609
+ ) -> Result<()> {
610
+ self.errors.fetch_add(1, Ordering::Relaxed);
611
+ Ok(())
612
+ }
613
+
614
+ fn name(&self) -> &str {
615
+ "recording"
616
+ }
617
+ }
618
+
619
+ struct StreamingProvider {
620
+ name: &'static str,
621
+ fail_after_first: bool,
622
+ }
623
+
624
+ impl StreamingProvider {
625
+ fn new(name: &'static str, fail_after_first: bool) -> Self {
626
+ Self {
627
+ name,
628
+ fail_after_first,
629
+ }
630
+ }
631
+
632
+ fn build_chunk(id: &str, content: &str) -> CompletionChunk {
633
+ CompletionChunk {
634
+ id: id.to_string(),
635
+ model: "test-model".to_string(),
636
+ choices: vec![ChoiceDelta {
637
+ index: 0,
638
+ delta: MessageDelta {
639
+ role: Some(Role::Assistant),
640
+ content: Some(content.to_string()),
641
+ },
642
+ finish_reason: None,
643
+ }],
644
+ created: None,
645
+ }
646
+ }
647
+ }
648
+
649
+ #[async_trait]
650
+ impl Provider for StreamingProvider {
651
+ fn name(&self) -> &str {
652
+ self.name
653
+ }
654
+
655
+ fn transform_request(&self, _req: &CompletionRequest) -> Result<ProviderRequest> {
656
+ Ok(ProviderRequest::new("http://example.com"))
657
+ }
658
+
659
+ async fn execute(&self, _req: ProviderRequest) -> Result<ProviderResponse> {
660
+ Ok(ProviderResponse::new(
661
+ 200,
662
+ serde_json::json!({"content": "ok"}),
663
+ ))
664
+ }
665
+
666
+ fn transform_response(&self, _resp: ProviderResponse) -> Result<CompletionResponse> {
667
+ Ok(CompletionResponse {
668
+ id: "resp_stream".to_string(),
669
+ model: "test-model".to_string(),
670
+ choices: vec![CompletionChoice {
671
+ index: 0,
672
+ message: Message::assistant("ok"),
673
+ finish_reason: FinishReason::Stop,
674
+ logprobs: None,
675
+ }],
676
+ usage: Usage::new(1, 1),
677
+ created: None,
678
+ provider: Some(self.name.to_string()),
679
+ healing_metadata: None,
680
+ })
681
+ }
682
+
683
+ async fn execute_stream(
684
+ &self,
685
+ _req: ProviderRequest,
686
+ ) -> Result<Box<dyn futures_core::Stream<Item = Result<CompletionChunk>> + Send + Unpin>>
687
+ {
688
+ let stream = if self.fail_after_first {
689
+ let items: Vec<Result<CompletionChunk>> = vec![
690
+ Ok(Self::build_chunk("chunk-1", "hello")),
691
+ Err(SimpleAgentsError::Provider(ProviderError::ServerError(
692
+ "stream error".to_string(),
693
+ ))),
694
+ ];
695
+ stream::iter(items)
696
+ } else {
697
+ let items: Vec<Result<CompletionChunk>> =
698
+ vec![Ok(Self::build_chunk("chunk-1", "hello"))];
699
+ stream::iter(items)
700
+ };
701
+
702
+ Ok(Box::new(stream))
703
+ }
704
+ }
705
+
706
+ #[tokio::test]
707
+ async fn streaming_invokes_after_stream_on_success() {
708
+ let provider = Arc::new(StreamingProvider::new("p1", false));
709
+ let middleware = Arc::new(RecordingMiddleware::default());
710
+
711
+ let client = SimpleAgentsClientBuilder::new()
712
+ .with_provider(provider)
713
+ .with_middleware(middleware.clone())
714
+ .build()
715
+ .unwrap();
716
+
717
+ let request = CompletionRequest::builder()
718
+ .model("gpt-4")
719
+ .message(Message::user("Hi"))
720
+ .stream(true)
721
+ .build()
722
+ .unwrap();
723
+
724
+ let outcome = client
725
+ .complete(&request, CompletionOptions::default())
726
+ .await
727
+ .unwrap();
728
+
729
+ let mut collected = Vec::new();
730
+ match outcome {
731
+ CompletionOutcome::Stream(mut stream) => {
732
+ while let Some(chunk) = stream.next().await {
733
+ collected.push(chunk.unwrap());
734
+ }
735
+ }
736
+ _ => panic!("expected stream outcome"),
737
+ }
738
+
739
+ assert_eq!(collected.len(), 1);
740
+ assert_eq!(middleware.before.load(Ordering::Relaxed), 1);
741
+ assert_eq!(middleware.after_stream.load(Ordering::Relaxed), 1);
742
+ assert_eq!(middleware.errors.load(Ordering::Relaxed), 0);
743
+ }
744
+
745
+ #[tokio::test]
746
+ async fn streaming_invokes_on_error_on_failure() {
747
+ let provider = Arc::new(StreamingProvider::new("p1", true));
748
+ let middleware = Arc::new(RecordingMiddleware::default());
749
+
750
+ let client = SimpleAgentsClientBuilder::new()
751
+ .with_provider(provider)
752
+ .with_middleware(middleware.clone())
753
+ .build()
754
+ .unwrap();
755
+
756
+ let request = CompletionRequest::builder()
757
+ .model("gpt-4")
758
+ .message(Message::user("Hi"))
759
+ .stream(true)
760
+ .build()
761
+ .unwrap();
762
+
763
+ let outcome = client
764
+ .complete(&request, CompletionOptions::default())
765
+ .await
766
+ .unwrap();
767
+
768
+ let mut chunks = Vec::new();
769
+ match outcome {
770
+ CompletionOutcome::Stream(mut stream) => {
771
+ while let Some(chunk) = stream.next().await {
772
+ chunks.push(chunk);
773
+ }
774
+ }
775
+ _ => panic!("expected stream outcome"),
776
+ }
777
+
778
+ assert_eq!(middleware.before.load(Ordering::Relaxed), 1);
779
+ assert_eq!(middleware.after_stream.load(Ordering::Relaxed), 0);
780
+ assert_eq!(middleware.errors.load(Ordering::Relaxed), 1);
781
+ assert_eq!(chunks.len(), 2);
782
+ assert!(chunks[0].as_ref().is_ok());
783
+ assert!(chunks[1].is_err());
784
+ }
490
785
  }
@@ -24,6 +24,11 @@ pub trait Middleware: Send + Sync {
24
24
  Ok(())
25
25
  }
26
26
 
27
+ /// Called when a streaming request completes successfully.
28
+ async fn after_stream(&self, _request: &CompletionRequest, _latency: Duration) -> Result<()> {
29
+ Ok(())
30
+ }
31
+
27
32
  /// Called when a response is served from cache.
28
33
  async fn on_cache_hit(
29
34
  &self,
@@ -32,7 +32,7 @@ regex-support = ["regex"]
32
32
  tokio = { version = "1.41", features = ["full"] }
33
33
  proptest = "1.5"
34
34
  criterion = { version = "0.5", features = ["html_reports"] }
35
- simple-agents-macros = { path = "../simple-agents-macros", version = "0.2.0" }
35
+ simple-agents-macros = { path = "../simple-agents-macros", version = "0.2.2" }
36
36
 
37
37
  [[test]]
38
38
  name = "parser_tests"
@@ -10,7 +10,7 @@ readme = "README.md"
10
10
 
11
11
  [dependencies]
12
12
  simple-agent-type = { workspace = true }
13
- simple-agents-healing = { path = "../simple-agents-healing", version = "0.2.0" }
13
+ simple-agents-healing = { path = "../simple-agents-healing", version = "0.2.2" }
14
14
  reqwest = { version = "0.12", features = ["json", "stream", "default-tls", "http2", "gzip", "brotli", "deflate"], default-features = false }
15
15
  tokio = { version = "1.42", features = ["full"] }
16
16
  futures = "0.3"
@@ -34,6 +34,6 @@ prometheus = ["dep:metrics-exporter-prometheus"]
34
34
  [dev-dependencies]
35
35
  tokio-test = "0.4"
36
36
  tracing-subscriber = "0.3"
37
- simple-agents-cache = { path = "../simple-agents-cache", version = "0.2.0" }
38
- simple-agents-macros = { path = "../simple-agents-macros", version = "0.2.0" }
37
+ simple-agents-cache = { path = "../simple-agents-cache", version = "0.2.2" }
38
+ simple-agents-macros = { path = "../simple-agents-macros", version = "0.2.2" }
39
39
  dotenv = "0.15"