llmshim 0.7.1__tar.gz → 0.7.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 (191) hide show
  1. llmshim-0.7.2/.github/scripts/npm-verify-served.sh +37 -0
  2. {llmshim-0.7.1 → llmshim-0.7.2}/.github/workflows/release.yml +18 -0
  3. {llmshim-0.7.1 → llmshim-0.7.2}/Cargo.lock +1 -1
  4. {llmshim-0.7.1 → llmshim-0.7.2}/Cargo.toml +1 -1
  5. {llmshim-0.7.1 → llmshim-0.7.2}/PKG-INFO +1 -1
  6. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/concepts/routing.md +22 -0
  7. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/models.md +4 -1
  8. {llmshim-0.7.1 → llmshim-0.7.2}/src/router.rs +43 -7
  9. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_router.rs +60 -0
  10. {llmshim-0.7.1 → llmshim-0.7.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  11. {llmshim-0.7.1 → llmshim-0.7.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  12. {llmshim-0.7.1 → llmshim-0.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {llmshim-0.7.1 → llmshim-0.7.2}/.github/workflows/catalog-refresh.yml +0 -0
  14. {llmshim-0.7.1 → llmshim-0.7.2}/.github/workflows/pages.yml +0 -0
  15. {llmshim-0.7.1 → llmshim-0.7.2}/.gitignore +0 -0
  16. {llmshim-0.7.1 → llmshim-0.7.2}/CLAUDE.md +0 -0
  17. {llmshim-0.7.1 → llmshim-0.7.2}/CODE_OF_CONDUCT.md +0 -0
  18. {llmshim-0.7.1 → llmshim-0.7.2}/CONTRIBUTING.md +0 -0
  19. {llmshim-0.7.1 → llmshim-0.7.2}/LICENSE-APACHE +0 -0
  20. {llmshim-0.7.1 → llmshim-0.7.2}/LICENSE-MIT +0 -0
  21. {llmshim-0.7.1 → llmshim-0.7.2}/NOTICE +0 -0
  22. {llmshim-0.7.1 → llmshim-0.7.2}/README.md +0 -0
  23. {llmshim-0.7.1 → llmshim-0.7.2}/SECURITY.md +0 -0
  24. {llmshim-0.7.1 → llmshim-0.7.2}/benchmarks/bench.rs +0 -0
  25. {llmshim-0.7.1 → llmshim-0.7.2}/benchmarks/bench_python.py +0 -0
  26. {llmshim-0.7.1 → llmshim-0.7.2}/benchmarks/gateway_loadtest.rs +0 -0
  27. {llmshim-0.7.1 → llmshim-0.7.2}/benchmarks/loadtest.rs +0 -0
  28. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/Cargo.toml +0 -0
  29. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/LICENSE-APACHE +0 -0
  30. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/LICENSE-MIT +0 -0
  31. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/README.md +0 -0
  32. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/data/LICENSE.models.dev +0 -0
  33. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/data/README.md +0 -0
  34. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/data/models.dev.json +0 -0
  35. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/aliases.rs +0 -0
  36. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/builtin.rs +0 -0
  37. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/capabilities.rs +0 -0
  38. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/lib.rs +0 -0
  39. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/merge.rs +0 -0
  40. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/parse.rs +0 -0
  41. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/refresh.rs +0 -0
  42. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/src/types.rs +0 -0
  43. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/tests/catalog.rs +0 -0
  44. {llmshim-0.7.1 → llmshim-0.7.2}/crates/llmshim-catalog/tests/refresh.rs +0 -0
  45. {llmshim-0.7.1 → llmshim-0.7.2}/docs/.gitignore +0 -0
  46. {llmshim-0.7.1 → llmshim-0.7.2}/docs/book.toml +0 -0
  47. {llmshim-0.7.1 → llmshim-0.7.2}/docs/mermaid-init.js +0 -0
  48. {llmshim-0.7.1 → llmshim-0.7.2}/docs/mermaid.min.js +0 -0
  49. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/SUMMARY.md +0 -0
  50. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/concepts/contracts.md +0 -0
  51. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/concepts/conversations.md +0 -0
  52. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/concepts/portability.md +0 -0
  53. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/concepts/translation-flow.md +0 -0
  54. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/caching.md +0 -0
  55. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/capabilities.md +0 -0
  56. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/fallbacks.md +0 -0
  57. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/images.md +0 -0
  58. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/native-controls.md +0 -0
  59. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/reasoning.md +0 -0
  60. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/schemas.md +0 -0
  61. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/streaming.md +0 -0
  62. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/guides/tools.md +0 -0
  63. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/introduction.md +0 -0
  64. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/proxy/deployment.md +0 -0
  65. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/proxy/http-api.md +0 -0
  66. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/proxy/native-apis.md +0 -0
  67. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/proxy/scaling.md +0 -0
  68. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/api.md +0 -0
  69. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/cli.md +0 -0
  70. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/configuration.md +0 -0
  71. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/errors.md +0 -0
  72. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/providers.md +0 -0
  73. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/request-fields.md +0 -0
  74. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/reference/surfaces.md +0 -0
  75. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/start/choose.md +0 -0
  76. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/start/cli.md +0 -0
  77. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/start/clients.md +0 -0
  78. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/start/configure.md +0 -0
  79. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/start/proxy.md +0 -0
  80. {llmshim-0.7.1 → llmshim-0.7.2}/docs/src/start/rust.md +0 -0
  81. {llmshim-0.7.1 → llmshim-0.7.2}/examples/chat.rs +0 -0
  82. {llmshim-0.7.1 → llmshim-0.7.2}/examples/stream.rs +0 -0
  83. {llmshim-0.7.1 → llmshim-0.7.2}/llmshim/__init__.py +0 -0
  84. {llmshim-0.7.1 → llmshim-0.7.2}/llmshim/_client.py +0 -0
  85. {llmshim-0.7.1 → llmshim-0.7.2}/llmshim/_server.py +0 -0
  86. {llmshim-0.7.1 → llmshim-0.7.2}/llmshim/types.py +0 -0
  87. {llmshim-0.7.1 → llmshim-0.7.2}/pyproject.toml +0 -0
  88. {llmshim-0.7.1 → llmshim-0.7.2}/src/breaker.rs +0 -0
  89. {llmshim-0.7.1 → llmshim-0.7.2}/src/cache.rs +0 -0
  90. {llmshim-0.7.1 → llmshim-0.7.2}/src/cli.rs +0 -0
  91. {llmshim-0.7.1 → llmshim-0.7.2}/src/client.rs +0 -0
  92. {llmshim-0.7.1 → llmshim-0.7.2}/src/config.rs +0 -0
  93. {llmshim-0.7.1 → llmshim-0.7.2}/src/cost.rs +0 -0
  94. {llmshim-0.7.1 → llmshim-0.7.2}/src/env.rs +0 -0
  95. {llmshim-0.7.1 → llmshim-0.7.2}/src/error/normalize.rs +0 -0
  96. {llmshim-0.7.1 → llmshim-0.7.2}/src/error.rs +0 -0
  97. {llmshim-0.7.1 → llmshim-0.7.2}/src/fallback.rs +0 -0
  98. {llmshim-0.7.1 → llmshim-0.7.2}/src/gateway/auth.rs +0 -0
  99. {llmshim-0.7.1 → llmshim-0.7.2}/src/gateway/distributed.rs +0 -0
  100. {llmshim-0.7.1 → llmshim-0.7.2}/src/gateway/http.rs +0 -0
  101. {llmshim-0.7.1 → llmshim-0.7.2}/src/gateway/idempotency.rs +0 -0
  102. {llmshim-0.7.1 → llmshim-0.7.2}/src/gateway/metrics.rs +0 -0
  103. {llmshim-0.7.1 → llmshim-0.7.2}/src/gateway/mod.rs +0 -0
  104. {llmshim-0.7.1 → llmshim-0.7.2}/src/gateway/quota.rs +0 -0
  105. {llmshim-0.7.1 → llmshim-0.7.2}/src/lib.rs +0 -0
  106. {llmshim-0.7.1 → llmshim-0.7.2}/src/log.rs +0 -0
  107. {llmshim-0.7.1 → llmshim-0.7.2}/src/main.rs +0 -0
  108. {llmshim-0.7.1 → llmshim-0.7.2}/src/models.rs +0 -0
  109. {llmshim-0.7.1 → llmshim-0.7.2}/src/provider.rs +0 -0
  110. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/anthropic.rs +0 -0
  111. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/anthropic_reasoning.rs +0 -0
  112. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/anthropic_signature.rs +0 -0
  113. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/chatgpt/auth.rs +0 -0
  114. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/chatgpt/mod.rs +0 -0
  115. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/chatgpt/streaming.rs +0 -0
  116. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/gemini.rs +0 -0
  117. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/mod.rs +0 -0
  118. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/openai.rs +0 -0
  119. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/openai_compat.rs +0 -0
  120. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/openrouter.rs +0 -0
  121. {llmshim-0.7.1 → llmshim-0.7.2}/src/providers/xai.rs +0 -0
  122. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/convert.rs +0 -0
  123. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/error.rs +0 -0
  124. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/handlers.rs +0 -0
  125. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/health.rs +0 -0
  126. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/mod.rs +0 -0
  127. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/ratelimit.rs +0 -0
  128. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/types.rs +0 -0
  129. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/wire/mod.rs +0 -0
  130. {llmshim-0.7.1 → llmshim-0.7.2}/src/proxy/wire/receipts.rs +0 -0
  131. {llmshim-0.7.1 → llmshim-0.7.2}/src/reasoning/normalize.rs +0 -0
  132. {llmshim-0.7.1 → llmshim-0.7.2}/src/reasoning.rs +0 -0
  133. {llmshim-0.7.1 → llmshim-0.7.2}/src/schema/memo.rs +0 -0
  134. {llmshim-0.7.1 → llmshim-0.7.2}/src/schema/mod.rs +0 -0
  135. {llmshim-0.7.1 → llmshim-0.7.2}/src/schema/validate.rs +0 -0
  136. {llmshim-0.7.1 → llmshim-0.7.2}/src/schema/walk.rs +0 -0
  137. {llmshim-0.7.1 → llmshim-0.7.2}/src/shim.rs +0 -0
  138. {llmshim-0.7.1 → llmshim-0.7.2}/src/streaming.rs +0 -0
  139. {llmshim-0.7.1 → llmshim-0.7.2}/src/toolcall/streaming.rs +0 -0
  140. {llmshim-0.7.1 → llmshim-0.7.2}/src/toolcall.rs +0 -0
  141. {llmshim-0.7.1 → llmshim-0.7.2}/src/usage.rs +0 -0
  142. {llmshim-0.7.1 → llmshim-0.7.2}/src/vision.rs +0 -0
  143. {llmshim-0.7.1 → llmshim-0.7.2}/tests/fixtures/chatgpt-red.png +0 -0
  144. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration.rs +0 -0
  145. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_chatgpt.rs +0 -0
  146. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_chatgpt_proxy.rs +0 -0
  147. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_current_models.rs +0 -0
  148. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_fallback.rs +0 -0
  149. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_gemini.rs +0 -0
  150. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_gemini_tools.rs +0 -0
  151. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_long_context.rs +0 -0
  152. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_multimodel.rs +0 -0
  153. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_openrouter.rs +0 -0
  154. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_proxy.rs +0 -0
  155. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_sglang.rs +0 -0
  156. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_thinking.rs +0 -0
  157. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_tool_roundtrip.rs +0 -0
  158. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_vision.rs +0 -0
  159. {llmshim-0.7.1 → llmshim-0.7.2}/tests/integration_xai.rs +0 -0
  160. {llmshim-0.7.1 → llmshim-0.7.2}/tests/support/completion_status.rs +0 -0
  161. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_advertised_models.rs +0 -0
  162. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_anthropic.rs +0 -0
  163. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_cache.rs +0 -0
  164. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_chatgpt.rs +0 -0
  165. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_cli.rs +0 -0
  166. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_client_breaker.rs +0 -0
  167. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_fable.rs +0 -0
  168. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_fallback.rs +0 -0
  169. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_fast_mode.rs +0 -0
  170. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_gemini.rs +0 -0
  171. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_log.rs +0 -0
  172. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_models.rs +0 -0
  173. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_multimodel.rs +0 -0
  174. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_openai.rs +0 -0
  175. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_openai_compat.rs +0 -0
  176. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_openrouter.rs +0 -0
  177. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_provider_contracts.rs +0 -0
  178. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_proxy.rs +0 -0
  179. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_proxy_convert.rs +0 -0
  180. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_reasoning.rs +0 -0
  181. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_reasoning_profile.rs +0 -0
  182. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_schema.rs +0 -0
  183. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_shim.rs +0 -0
  184. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_signature.rs +0 -0
  185. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_sse.rs +0 -0
  186. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_toolcall.rs +0 -0
  187. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_tools.rs +0 -0
  188. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_usage.rs +0 -0
  189. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_vision.rs +0 -0
  190. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_wire.rs +0 -0
  191. {llmshim-0.7.1 → llmshim-0.7.2}/tests/unit_xai.rs +0 -0
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # `npm publish` exiting 0 means the registry *accepted* the tarball, not that it
3
+ # serves it. 0.7.1 was accepted, logged to Sigstore, answered "may take a few
4
+ # minutes to become available" — and half an hour later still served nothing,
5
+ # behind a green job. So a publish is not done until `npm view` returns the
6
+ # version. Run from the package's directory, after its publish step.
7
+ #
8
+ # Bound: 20 polls, 30 s apart — about ten minutes. npm's "few minutes" has been
9
+ # minutes, never seconds, and a version still unserved after ten is stuck in
10
+ # npm's processing, which more waiting here will not fix.
11
+ #
12
+ # Compares `npm view`'s output to the version rather than its exit status: for a
13
+ # package that exists with a version that does not, npm's exit status has
14
+ # differed across releases. `--prefer-online` revalidates on every poll, so the
15
+ # first poll's packument — cached without the new version — cannot answer for
16
+ # the rest of the window.
17
+ set -euo pipefail
18
+
19
+ ATTEMPTS=20
20
+ INTERVAL=30
21
+ NAME=$(node -p "require('./package.json').name")
22
+ VER=$(node -p "require('./package.json').version")
23
+
24
+ for attempt in $(seq 1 "$ATTEMPTS"); do
25
+ served=$(npm view --prefer-online "$NAME@$VER" version 2>/dev/null || true)
26
+ if [ "$served" = "$VER" ]; then
27
+ echo "$NAME@$VER is served by the registry."
28
+ exit 0
29
+ fi
30
+ echo "poll $attempt/$ATTEMPTS: $NAME@$VER not served yet"
31
+ if [ "$attempt" -lt "$ATTEMPTS" ]; then
32
+ sleep "$INTERVAL"
33
+ fi
34
+ done
35
+
36
+ echo "::error::$NAME@$VER was accepted by npm but is still not served after $((ATTEMPTS * INTERVAL / 60)) minutes. The publish step exited 0 and its output carries the Sigstore provenance line, so the tarball is stuck in npm's processing, not missing from this run. Look at https://www.npmjs.com/package/$NAME?activeTab=versions and https://status.npmjs.org; once it appears, re-run this job — the publish step sees it and skips. If it never appears, npm will refuse a second publish of the same version and support has to release it."
37
+ exit 1
@@ -223,6 +223,9 @@ jobs:
223
223
  else
224
224
  npm publish --access public --provenance
225
225
  fi
226
+ - name: Verify npm serves llmshim-darwin-arm64
227
+ working-directory: clients/typescript/packages/llmshim-darwin-arm64
228
+ run: bash "$GITHUB_WORKSPACE/.github/scripts/npm-verify-served.sh"
226
229
  - name: Publish llmshim-darwin-x64
227
230
  working-directory: clients/typescript/packages/llmshim-darwin-x64
228
231
  run: |
@@ -234,6 +237,9 @@ jobs:
234
237
  else
235
238
  npm publish --access public --provenance
236
239
  fi
240
+ - name: Verify npm serves llmshim-darwin-x64
241
+ working-directory: clients/typescript/packages/llmshim-darwin-x64
242
+ run: bash "$GITHUB_WORKSPACE/.github/scripts/npm-verify-served.sh"
237
243
 
238
244
  npm-linux-x64-binary:
239
245
  needs: test
@@ -274,6 +280,9 @@ jobs:
274
280
  else
275
281
  npm publish --access public --provenance
276
282
  fi
283
+ - name: Verify npm serves llmshim-linux-x64
284
+ working-directory: clients/typescript/packages/llmshim-linux-x64
285
+ run: bash "$GITHUB_WORKSPACE/.github/scripts/npm-verify-served.sh"
277
286
 
278
287
  # Native arm64 runner (GA, free on public repos) — no cross-compilation needed.
279
288
  npm-linux-arm64-binary:
@@ -315,6 +324,9 @@ jobs:
315
324
  else
316
325
  npm publish --access public --provenance
317
326
  fi
327
+ - name: Verify npm serves llmshim-linux-arm64
328
+ working-directory: clients/typescript/packages/llmshim-linux-arm64
329
+ run: bash "$GITHUB_WORKSPACE/.github/scripts/npm-verify-served.sh"
318
330
 
319
331
  npm-windows-binary:
320
332
  needs: test
@@ -358,6 +370,10 @@ jobs:
358
370
  else
359
371
  npm publish --access public --provenance
360
372
  fi
373
+ - name: Verify npm serves llmshim-win32-x64
374
+ shell: bash
375
+ working-directory: clients/typescript/packages/llmshim-win32-x64
376
+ run: bash "$GITHUB_WORKSPACE/.github/scripts/npm-verify-served.sh"
361
377
 
362
378
  # --- Publish TypeScript client to npm ---
363
379
  # First release must be published manually once (npm's trusted-publisher UI
@@ -410,6 +426,8 @@ jobs:
410
426
  else
411
427
  npm publish --access public --provenance
412
428
  fi
429
+ - name: Verify npm serves the TypeScript client
430
+ run: bash "$GITHUB_WORKSPACE/.github/scripts/npm-verify-served.sh"
413
431
 
414
432
  # --- Publish Ruby client to RubyGems ---
415
433
  # Uses a "pending trusted publisher" — configurable on rubygems.org before
@@ -1180,7 +1180,7 @@ checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
1180
1180
 
1181
1181
  [[package]]
1182
1182
  name = "llmshim"
1183
- version = "0.7.1"
1183
+ version = "0.7.2"
1184
1184
  dependencies = [
1185
1185
  "async-stream",
1186
1186
  "async-trait",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "llmshim"
3
- version = "0.7.1"
3
+ version = "0.7.2"
4
4
  edition = "2021"
5
5
  description = "Blazing fast LLM API translation layer in pure Rust"
6
6
  license = "MIT OR Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llmshim
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -136,3 +136,25 @@ let router = llmshim::router::Router::from_env();
136
136
 
137
137
  Applications that manage secrets themselves can call `Router::from_env()`
138
138
  directly or construct a Router by registering provider implementations.
139
+
140
+ ## Catalog refresh is the daemon's default, not the embedder's
141
+
142
+ `Router::from_env()` also schedules one background fetch of the model catalog
143
+ (prices, context windows, capabilities). That is right for the proxy, which
144
+ starts once and runs for days. A program that starts many times a day, or runs
145
+ air-gapped, should build its router with
146
+ `Router::from_env_without_catalog_refresh()` — identical, except that
147
+ constructing it makes no network call — and refresh only when it decides to:
148
+
149
+ ```rust
150
+ let router = llmshim::router::Router::from_env_without_catalog_refresh();
151
+ if user_asked_for_it {
152
+ // Rides the caller's Tokio runtime; `None` when there is none, when
153
+ // LLMSHIM_CATALOG_OFFLINE=1 is set, or when the local catalog is invalid.
154
+ router.refresh_catalog_in_background();
155
+ }
156
+ ```
157
+
158
+ The offline router still resolves and prices every model in the vendored
159
+ snapshot, any earlier cached download, and the local override files; its data
160
+ is simply never newer than the disk.
@@ -28,7 +28,10 @@ compile-time API for curated and historical facts.
28
28
 
29
29
  Startup reads the vendored floor, cached data, and local overrides without
30
30
  waiting for network. Refresh uses ETags and a 24-hour TTL; errors keep the old
31
- snapshot available. Offline mode uses only vendored and local layers. User
31
+ snapshot available. `Router::from_env()` schedules one such refresh;
32
+ `Router::from_env_without_catalog_refresh()` schedules none and leaves it to
33
+ `Router::refresh_catalog_in_background()`. Offline mode uses only vendored and
34
+ local layers. User
32
35
  overrides live in `~/.config/llmshim/models.toml`, project overrides in
33
36
  `.llmshim/models.toml`, and cached data in `~/.cache/llmshim/models.dev.json`.
34
37
  Local overrides win; verified builtin assertions win over models.dev data.
@@ -176,15 +176,35 @@ impl Router {
176
176
  .ok_or_else(|| ShimError::UnknownProvider(key.to_string()))
177
177
  }
178
178
 
179
- /// Build a router from provider env vars and a saved ChatGPT login.
179
+ /// Build a router from provider env vars and a saved ChatGPT login, and
180
+ /// schedule one background refresh of the model catalog.
181
+ ///
182
+ /// This is the daemon's constructor. The proxy starts once and serves for
183
+ /// days, so a single fetch at startup is what keeps its prices and
184
+ /// capabilities current for the rest of its life. A process that starts
185
+ /// many times a day, or must run air-gapped, wants
186
+ /// [`Router::from_env_without_catalog_refresh`] instead and decides for
187
+ /// itself when — or whether — to call
188
+ /// [`Router::refresh_catalog_in_background`].
180
189
  pub fn from_env() -> Self {
181
- let mut router = Router::new();
190
+ let router = Self::from_env_without_catalog_refresh();
191
+ router.refresh_catalog_in_background();
192
+ router
193
+ }
182
194
 
183
- // Only startup's local snapshot is synchronous. No catalog HTTP fetch
184
- // is ever awaited by a model request.
185
- if let Ok(catalog) = crate::catalog::global() {
186
- catalog.refresh_in_background();
187
- }
195
+ /// [`Router::from_env`] minus the catalog refresh: building a router makes
196
+ /// no network call.
197
+ ///
198
+ /// The catalog is still there — the vendored snapshot, whatever an earlier
199
+ /// refresh cached on disk, and the local override files — so `resolve` and
200
+ /// pricing work exactly as they do after `from_env`; the data is simply
201
+ /// never newer than the disk. For a short-lived embedder that is the right
202
+ /// default: a CLI that phones home for a price list before the user has
203
+ /// typed anything is doing something nobody asked for, and a machine with
204
+ /// no route out should not have to discover `LLMSHIM_CATALOG_OFFLINE` to
205
+ /// stop it. The fetch becomes something the caller asks for.
206
+ pub fn from_env_without_catalog_refresh() -> Self {
207
+ let mut router = Router::new();
188
208
 
189
209
  // Named routes are configuration, not discovery: they come from
190
210
  // ~/.llmshim/config.toml and nothing synthesizes a default set.
@@ -230,6 +250,22 @@ impl Router {
230
250
  router
231
251
  }
232
252
 
253
+ /// Refresh the shared model catalog from the network, detached from every
254
+ /// request: only startup's local snapshot is synchronous, and no catalog
255
+ /// HTTP fetch is ever awaited by a model request.
256
+ ///
257
+ /// The refresh rides the caller's Tokio runtime and never creates one, so
258
+ /// `None` means nothing was scheduled: there is no runtime on this thread,
259
+ /// `LLMSHIM_CATALOG_OFFLINE=1` is set, or the local catalog configuration
260
+ /// is invalid. The catalog is process-wide — it is what every router in
261
+ /// the process resolves against — so refreshing through one router
262
+ /// refreshes it for all of them.
263
+ pub fn refresh_catalog_in_background(
264
+ &self,
265
+ ) -> Option<tokio::task::JoinHandle<crate::catalog::RefreshOutcome>> {
266
+ crate::catalog::global().ok()?.refresh_in_background()
267
+ }
268
+
233
269
  /// Resolve model string to (provider, model_name).
234
270
  pub fn resolve(&self, model: &str) -> Result<(&dyn Provider, String)> {
235
271
  let (key, model) = self.resolve_key(model)?;
@@ -432,3 +432,63 @@ fn routes_are_configuration_and_parse_from_the_config_file() {
432
432
  "the target is not a setting"
433
433
  );
434
434
  }
435
+
436
+ // ============================================================
437
+ // from_env / from_env_without_catalog_refresh
438
+ // ============================================================
439
+
440
+ fn alive_tasks() -> usize {
441
+ tokio::runtime::Handle::current()
442
+ .metrics()
443
+ .num_alive_tasks()
444
+ }
445
+
446
+ fn catalog_offline() -> bool {
447
+ std::env::var("LLMSHIM_CATALOG_OFFLINE").is_ok_and(|v| v == "1")
448
+ }
449
+
450
+ /// The refresh's one observable is the task it spawns. On a current-thread
451
+ /// runtime that task cannot run until this test yields, and the test never
452
+ /// does, so the fetch never starts even without `LLMSHIM_CATALOG_OFFLINE`.
453
+ ///
454
+ /// Under CI's `LLMSHIM_CATALOG_OFFLINE=1` neither constructor may spawn, so
455
+ /// only the first assertion discriminates there; the `from_env` half is live
456
+ /// when a developer runs the tests without the variable.
457
+ #[tokio::test(flavor = "current_thread")]
458
+ async fn offline_constructor_spawns_no_refresh_and_from_env_still_does() {
459
+ let before = alive_tasks();
460
+ let router = Router::from_env_without_catalog_refresh();
461
+ assert_eq!(
462
+ alive_tasks(),
463
+ before,
464
+ "constructing the router spawned a task"
465
+ );
466
+
467
+ let expected = usize::from(!catalog_offline());
468
+ let scheduled = router.refresh_catalog_in_background();
469
+ assert_eq!(scheduled.is_some(), !catalog_offline());
470
+ assert_eq!(alive_tasks(), before + expected);
471
+ if let Some(task) = scheduled {
472
+ task.abort();
473
+ }
474
+
475
+ let before = alive_tasks();
476
+ let _ = Router::from_env();
477
+ assert_eq!(
478
+ alive_tasks(),
479
+ before + expected,
480
+ "from_env no longer refreshes"
481
+ );
482
+ }
483
+
484
+ /// No refresh still means a catalog: the vendored snapshot resolves without
485
+ /// any network having happened.
486
+ #[test]
487
+ fn offline_constructor_resolves_a_bundled_model() {
488
+ let router = Router::from_env_without_catalog_refresh()
489
+ .register("anthropic", Box::new(Anthropic::new("test".into())));
490
+ let (provider, model) = router.resolve("anthropic/claude-sonnet-5").unwrap();
491
+ assert_eq!(provider.name(), "anthropic");
492
+ assert_eq!(model, "claude-sonnet-5");
493
+ assert!(llmshim::catalog::resolve("anthropic/claude-sonnet-5").is_some());
494
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes