cmpnd 0.85.1__tar.gz → 0.86.0__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 (415) hide show
  1. {cmpnd-0.85.1 → cmpnd-0.86.0}/PKG-INFO +1 -1
  2. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_http.py +12 -6
  3. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/admin.py +59 -19
  4. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/verbs.py +2 -0
  5. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/cli.md +29 -14
  6. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/sdk-instrumentation.md +2 -0
  7. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/exporter_http.py +5 -3
  8. cmpnd-0.86.0/ingest/cmpnd_ingest/user_agent.py +52 -0
  9. {cmpnd-0.85.1 → cmpnd-0.86.0}/pyproject.toml +1 -1
  10. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_admin.py +51 -10
  11. cmpnd-0.86.0/tests/test_user_agent.py +109 -0
  12. {cmpnd-0.85.1 → cmpnd-0.86.0}/uv.lock +1 -1
  13. {cmpnd-0.85.1 → cmpnd-0.86.0}/.gitignore +0 -0
  14. {cmpnd-0.85.1 → cmpnd-0.86.0}/AGENTS.md +0 -0
  15. {cmpnd-0.85.1 → cmpnd-0.86.0}/CLAUDE.md +0 -0
  16. {cmpnd-0.85.1 → cmpnd-0.86.0}/README.md +0 -0
  17. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/__init__.py +0 -0
  18. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_assessments.py +0 -0
  19. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_authored_import.py +0 -0
  20. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_deployments.py +0 -0
  21. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_exporter_pool.py +0 -0
  22. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_gepa_patch.py +0 -0
  23. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_program_patch.py +0 -0
  24. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_rlm_patch.py +0 -0
  25. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_runtime_tools.py +0 -0
  26. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_timespec.py +0 -0
  27. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_traces.py +0 -0
  28. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/_valsets.py +0 -0
  29. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/authored.py +0 -0
  30. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/callback.py +0 -0
  31. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/__init__.py +0 -0
  32. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/api_client.py +0 -0
  33. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/__init__.py +0 -0
  34. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/assessments.py +0 -0
  35. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/auth.py +0 -0
  36. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/datasets.py +0 -0
  37. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/deployments.py +0 -0
  38. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/evals.py +0 -0
  39. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/login.py +0 -0
  40. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/optimizations.py +0 -0
  41. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/org.py +0 -0
  42. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/programs.py +0 -0
  43. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  44. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  45. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/top.py +0 -0
  46. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/traces.py +0 -0
  47. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/commands/valsets.py +0 -0
  48. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/credentials.py +0 -0
  49. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/deprecation.py +0 -0
  50. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/pagination.py +0 -0
  51. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/shell.py +0 -0
  52. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/trace_render.py +0 -0
  53. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/tree_help.py +0 -0
  54. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/cli/views.py +0 -0
  55. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/client.py +0 -0
  56. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/configuration.py +0 -0
  57. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/context.py +0 -0
  58. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/credentials.py +0 -0
  59. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/dataset_sync.py +0 -0
  60. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/datasets.py +0 -0
  61. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/decorators.py +0 -0
  62. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/deployment.py +0 -0
  63. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/deployment_metadata.py +0 -0
  64. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/deployment_runs.py +0 -0
  65. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/eval_handler.py +0 -0
  66. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/execution.py +0 -0
  67. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/exporter.py +0 -0
  68. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/exporter_http.py +0 -0
  69. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/helpers.py +0 -0
  70. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/identity.py +0 -0
  71. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/imaging.py +0 -0
  72. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/__init__.py +0 -0
  73. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/decode.py +0 -0
  74. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/encode.py +0 -0
  75. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/hash.py +0 -0
  76. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/program.py +0 -0
  77. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/reflection.py +0 -0
  78. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/types.py +0 -0
  79. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/ir/xxh64.py +0 -0
  80. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/lm_configuration.py +0 -0
  81. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/mcp_servers.py +0 -0
  82. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/models.py +0 -0
  83. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/named_programs.py +0 -0
  84. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/optimization.py +0 -0
  85. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/optimizers/__init__.py +0 -0
  86. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/optimizers/gepa.py +0 -0
  87. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  88. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/optimizers/resume.py +0 -0
  89. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/optimizers/tracker.py +0 -0
  90. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/packaging.py +0 -0
  91. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/program_naming.py +0 -0
  92. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/programs.py +0 -0
  93. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/references.py +0 -0
  94. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/retry.py +0 -0
  95. {cmpnd-0.85.1 → cmpnd-0.86.0}/cmpnd/traces.py +0 -0
  96. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/2026-04-02-code-review.md +0 -0
  97. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/client.md +0 -0
  98. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/custom-proposer.md +0 -0
  99. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  100. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  101. {cmpnd-0.85.1 → cmpnd-0.86.0}/docs/reading-traces.md +0 -0
  102. {cmpnd-0.85.1 → cmpnd-0.86.0}/examples/generalizing_proposer.py +0 -0
  103. {cmpnd-0.85.1 → cmpnd-0.86.0}/examples/local_ollama.py +0 -0
  104. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/LINEAGE.md +0 -0
  105. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/README.md +0 -0
  106. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  107. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  108. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  109. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  110. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  111. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/py.typed +0 -0
  112. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  113. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/retry.py +0 -0
  114. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/span_context.py +0 -0
  115. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  116. {cmpnd-0.85.1 → cmpnd-0.86.0}/ingest/pyproject.toml +0 -0
  117. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/audit-sdist.sh +0 -0
  118. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/capture_review_run.py +0 -0
  119. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/check-pip-install.sh +0 -0
  120. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/check_release_artifacts.py +0 -0
  121. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/fixtures/review_run.json.gz +0 -0
  122. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/gepa_resume_bug.py +0 -0
  123. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/install-test-env.sh +0 -0
  124. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/release-gate.sh +0 -0
  125. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/release-real-models.sh +0 -0
  126. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/require_version_bump.py +0 -0
  127. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/seed_review_run.py +0 -0
  128. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/selfhost_compat.py +0 -0
  129. {cmpnd-0.85.1 → cmpnd-0.86.0}/scripts/test-in-linux.sh +0 -0
  130. {cmpnd-0.85.1 → cmpnd-0.86.0}/stubs/dspy/__init__.pyi +0 -0
  131. {cmpnd-0.85.1 → cmpnd-0.86.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  132. {cmpnd-0.85.1 → cmpnd-0.86.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  133. {cmpnd-0.85.1 → cmpnd-0.86.0}/stubs/dspy/utils/__init__.pyi +0 -0
  134. {cmpnd-0.85.1 → cmpnd-0.86.0}/stubs/dspy/utils/callback.pyi +0 -0
  135. {cmpnd-0.85.1 → cmpnd-0.86.0}/stubs/dspy/utils/dummies.pyi +0 -0
  136. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/__init__.py +0 -0
  137. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/_credential_route.py +0 -0
  138. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/_deployment_signature_helpers.py +0 -0
  139. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/_exporter_helpers.py +0 -0
  140. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/_fake_clock.py +0 -0
  141. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/_identity_helpers.py +0 -0
  142. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/_resolve_refs.py +0 -0
  143. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/_traced_run.py +0 -0
  144. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/conftest.py +0 -0
  145. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/__init__.py +0 -0
  146. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/committee_task.py +0 -0
  147. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/conftest.py +0 -0
  148. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/continuation_task.py +0 -0
  149. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  150. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  151. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/fake_lm.py +0 -0
  152. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/helpers.py +0 -0
  153. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/target_capabilities.py +0 -0
  154. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_assessments_cli.py +0 -0
  155. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_capture_prompts.py +0 -0
  156. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_composite_module.py +0 -0
  157. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  158. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_deploy_execute.py +0 -0
  159. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_deploy_pandas.py +0 -0
  160. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_deploy_programs.py +0 -0
  161. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_edge_cases.py +0 -0
  162. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_example_hash.py +0 -0
  163. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_gepa_optimization.py +0 -0
  164. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_interpreter_spans.py +0 -0
  165. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_lm_accuracy.py +0 -0
  166. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_module_tracing.py +0 -0
  167. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimization_lineage.py +0 -0
  168. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  169. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
  170. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimize_continuation.py +0 -0
  171. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
  172. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimize_failures.py +0 -0
  173. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimize_loop.py +0 -0
  174. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  175. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  176. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_parallel_export.py +0 -0
  177. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_project_attribution.py +0 -0
  178. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_react_tool_spans.py +0 -0
  179. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_save_load.py +0 -0
  180. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_trace_structure.py +0 -0
  181. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_traces_read.py +0 -0
  182. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_user_attribution.py +0 -0
  183. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/e2e/test_valsets_cli.py +0 -0
  184. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  185. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  186. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  187. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  188. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  189. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  190. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  191. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  192. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  193. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/10_react.json +0 -0
  194. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/11_refine.json +0 -0
  195. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/12_retrieve.json +0 -0
  196. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/13_knn.json +0 -0
  197. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/14_parallel.json +0 -0
  198. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/identity_vectors/README.md +0 -0
  199. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/ingest_conformance.py +0 -0
  200. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/__init__.py +0 -0
  201. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/authored_optimize/judge.py +0 -0
  202. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  203. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/conftest.py +0 -0
  204. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  205. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/hosted_optimize_probe.py +0 -0
  206. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/optimization_run_probe.py +0 -0
  207. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  208. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/test_logs_renders_traces.py +0 -0
  209. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/test_optimization_run.py +0 -0
  210. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/test_optimizing_progress.py +0 -0
  211. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/integration/test_unhappy_path.py +0 -0
  212. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/lm15_prompt_fixture.py +0 -0
  213. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/lm15_prompts/four_turn_predict.json +0 -0
  214. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/lm15_prompts/rlm_six_turns.json +0 -0
  215. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/__init__.py +0 -0
  216. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/__main__.py +0 -0
  217. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/drain.py +0 -0
  218. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/ingest.py +0 -0
  219. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/latency_transport.py +0 -0
  220. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/rlm_shape.py +0 -0
  221. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/rlm_size_report.py +0 -0
  222. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/sweep.py +0 -0
  223. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/test_rlm_shape.py +0 -0
  224. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/load/test_rlm_size_report.py +0 -0
  225. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/__init__.py +0 -0
  226. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/conftest.py +0 -0
  227. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/seed_parity.py +0 -0
  228. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_auth.py +0 -0
  229. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_datasets.py +0 -0
  230. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_deployments.py +0 -0
  231. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_evals.py +0 -0
  232. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_health.py +0 -0
  233. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_modules.py +0 -0
  234. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_optimizations.py +0 -0
  235. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_programs.py +0 -0
  236. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_signatures.py +0 -0
  237. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_trace_streaming.py +0 -0
  238. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/parity/test_traces.py +0 -0
  239. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/__init__.py +0 -0
  240. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/_provider.py +0 -0
  241. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/_readiness.py +0 -0
  242. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/_reliability.py +0 -0
  243. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/_result.py +0 -0
  244. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/mint_bedrock_token.py +0 -0
  245. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_bedrock_converse.py +0 -0
  246. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_deploy_programs.py +0 -0
  247. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_multi_client_stress.py +0 -0
  248. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_optimize_client_committee.py +0 -0
  249. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_optimize_continuation.py +0 -0
  250. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_optimize_loop.py +0 -0
  251. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_optimize_loop_wasm.py +0 -0
  252. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_optimize_stress.py +0 -0
  253. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_provider_roles.py +0 -0
  254. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_readiness.py +0 -0
  255. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_real_provider_twins.py +0 -0
  256. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_reliability.py +0 -0
  257. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_reliability_classify.py +0 -0
  258. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_rlm_wasm.py +0 -0
  259. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/real_models/test_trace_search.py +0 -0
  260. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/span_reference_fixture.py +0 -0
  261. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_adapter_reentrancy.py +0 -0
  262. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_assessments_api.py +0 -0
  263. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_authored.py +0 -0
  264. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_authored_binding_credentials.py +0 -0
  265. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_authored_bundle_collisions.py +0 -0
  266. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_authored_metric_contract.py +0 -0
  267. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_authored_named_models.py +0 -0
  268. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_authored_optimize.py +0 -0
  269. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_callback.py +0 -0
  270. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_candidate_lineage.py +0 -0
  271. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/__init__.py +0 -0
  272. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/conftest.py +0 -0
  273. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_api_client.py +0 -0
  274. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_assessments.py +0 -0
  275. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_auth.py +0 -0
  276. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_credentials.py +0 -0
  277. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_datasets.py +0 -0
  278. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_deployment_runs.py +0 -0
  279. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_deployments.py +0 -0
  280. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_evals.py +0 -0
  281. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_login.py +0 -0
  282. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_main.py +0 -0
  283. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_optimizations.py +0 -0
  284. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_org.py +0 -0
  285. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_pagination.py +0 -0
  286. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_parity.py +0 -0
  287. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_programs.py +0 -0
  288. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_provider_credentials.py +0 -0
  289. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_schema_sample.py +0 -0
  290. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_shell.py +0 -0
  291. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_tool_servers.py +0 -0
  292. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_top.py +0 -0
  293. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_traces.py +0 -0
  294. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
  295. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  296. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_tree_help.py +0 -0
  297. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cli/test_valsets.py +0 -0
  298. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_client.py +0 -0
  299. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_client_floor.py +0 -0
  300. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_client_program_names.py +0 -0
  301. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_clustering/__init__.py +0 -0
  302. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_clustering/conftest.py +0 -0
  303. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_clustering/test_edge_cases.py +0 -0
  304. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  305. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_clustering/test_type_conversion.py +0 -0
  306. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_cmp551_grouping_identity.py +0 -0
  307. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_configuration.py +0 -0
  308. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_context.py +0 -0
  309. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_context_propagation.py +0 -0
  310. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_credential_kwargs.py +0 -0
  311. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_credential_selection.py +0 -0
  312. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_credential_values.py +0 -0
  313. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_dataset_polling.py +0 -0
  314. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_dataset_sync.py +0 -0
  315. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_datasets.py +0 -0
  316. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_decision_client_spans.py +0 -0
  317. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_decorators.py +0 -0
  318. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_deploy.py +0 -0
  319. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  320. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_deploy_program_id.py +0 -0
  321. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_deployment_metadata.py +0 -0
  322. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_deployment_runs.py +0 -0
  323. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_deployments_read.py +0 -0
  324. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_eval.py +0 -0
  325. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_eval_example_attribution.py +0 -0
  326. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_eval_program_id.py +0 -0
  327. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_eval_start.py +0 -0
  328. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_eval_trace_grouping.py +0 -0
  329. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_exception_paths.py +0 -0
  330. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_execute.py +0 -0
  331. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_execute_run_identity.py +0 -0
  332. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_exporter.py +0 -0
  333. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_exporter_program_names.py +0 -0
  334. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_exporter_queue.py +0 -0
  335. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_exporter_real.py +0 -0
  336. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_fake_lm.py +0 -0
  337. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_gepa_callback.py +0 -0
  338. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_gepa_integration.py +0 -0
  339. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_gepa_output_normalization.py +0 -0
  340. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_gepa_resume.py +0 -0
  341. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_identity.py +0 -0
  342. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_imaging.py +0 -0
  343. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_import_weight.py +0 -0
  344. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_ingest_conformance.py +0 -0
  345. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_ingest_second_producer.py +0 -0
  346. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_integration.py +0 -0
  347. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_interpreter_callback.py +0 -0
  348. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_ir_encoder_properties.py +0 -0
  349. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_ir_golden_vectors.py +0 -0
  350. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_ir_roundtrip.py +0 -0
  351. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_lm15_native_calls.py +0 -0
  352. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_lm15_prompt_fixtures.py +0 -0
  353. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_lm15_prompt_references.py +0 -0
  354. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_lm_configuration.py +0 -0
  355. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_lm_usage_shapes.py +0 -0
  356. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_mcp_tools.py +0 -0
  357. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_models.py +0 -0
  358. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_multi_client_stress_readiness.py +0 -0
  359. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_no_signature_hash_on_the_wire.py +0 -0
  360. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimization_phase_callback.py +0 -0
  361. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimization_program_names.py +0 -0
  362. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimization_progress.py +0 -0
  363. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimization_start.py +0 -0
  364. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimize.py +0 -0
  365. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimize_budget.py +0 -0
  366. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimize_lift.py +0 -0
  367. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimize_lms.py +0 -0
  368. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimize_proposer.py +0 -0
  369. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimize_reflection_lm.py +0 -0
  370. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_optimize_terminal_failure.py +0 -0
  371. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_packaging.py +0 -0
  372. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_packaging_callable_source.py +0 -0
  373. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_packaging_mcp_tools.py +0 -0
  374. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_packaging_program_source.py +0 -0
  375. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_poll_until.py +0 -0
  376. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_program_id_deprecation.py +0 -0
  377. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_program_identity.py +0 -0
  378. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_program_lineage_persist.py +0 -0
  379. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_program_names_in_deploy.py +0 -0
  380. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_program_naming.py +0 -0
  381. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_programs_create.py +0 -0
  382. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_project_payload.py +0 -0
  383. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_real_provider_twins_readiness.py +0 -0
  384. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_reflector_properties.py +0 -0
  385. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_reflector_smoke.py +0 -0
  386. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_release_gate_script.py +0 -0
  387. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_release_real_models_script.py +0 -0
  388. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_replaced_names.py +0 -0
  389. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_repro_reactv2_bugs.py +0 -0
  390. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_require_version_bump.py +0 -0
  391. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_retry.py +0 -0
  392. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_reversible_failures.py +0 -0
  393. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_rlm_patch.py +0 -0
  394. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_run_attribution_paths.py +0 -0
  395. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_runtime_tools.py +0 -0
  396. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_schemas.py +0 -0
  397. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_seal_lm_credential.py +0 -0
  398. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_seed_review_run.py +0 -0
  399. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_signature_inputs.py +0 -0
  400. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_span_references.py +0 -0
  401. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_span_sent_once.py +0 -0
  402. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_span_submission.py +0 -0
  403. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_span_submission_wire_contract.py +0 -0
  404. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_system_one.py +0 -0
  405. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_trace_context_concurrency.py +0 -0
  406. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_trace_fact_wire_contract.py +0 -0
  407. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_trace_facts.py +0 -0
  408. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_trace_render.py +0 -0
  409. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_traces_read.py +0 -0
  410. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_tracked_gepa.py +0 -0
  411. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_tracker_enrich.py +0 -0
  412. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_truncation.py +0 -0
  413. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_upload_auth_headers.py +0 -0
  414. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_valsets_api.py +0 -0
  415. {cmpnd-0.85.1 → cmpnd-0.86.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.85.1
3
+ Version: 0.86.0
4
4
  Summary: DSPy observability and deployment SDK for cmpnd
5
5
  Project-URL: Homepage, https://cmpnd.ai
6
6
  Author-email: cmpnd <hello@cmpnd.ai>
@@ -16,7 +16,7 @@ from typing import Any
16
16
  from urllib.parse import urlsplit
17
17
 
18
18
  import httpx
19
- from cmpnd_ingest import client_floor
19
+ from cmpnd_ingest import client_floor, user_agent
20
20
 
21
21
 
22
22
  class ApiError(Exception):
@@ -79,16 +79,22 @@ def server_detail(response: httpx.Response) -> str:
79
79
 
80
80
 
81
81
  def new_client(**kwargs: Any) -> httpx.Client:
82
- """An ``httpx.Client`` that warns when the server no longer supports this
83
- release (:mod:`cmpnd_ingest.client_floor`).
82
+ """An ``httpx.Client`` that names this release to the server and warns when
83
+ the server no longer supports it (:mod:`cmpnd_ingest.client_floor`).
84
84
 
85
- Every client the SDK builds comes from here, so every answer the platform
86
- sends gets the check. A caller's own ``event_hooks`` run after it.
85
+ Every client the SDK builds comes from here, so every request carries the
86
+ release's ``User-Agent`` (:mod:`cmpnd_ingest.user_agent`) and every answer
87
+ the platform sends gets the check. A caller's own ``headers`` override the
88
+ ``User-Agent``, and its own ``event_hooks`` run after the check.
87
89
  """
88
90
  hooks = client_floor.event_hooks()
89
91
  for event, callers in (kwargs.pop("event_hooks", None) or {}).items():
90
92
  hooks.setdefault(event, []).extend(callers)
91
- return httpx.Client(event_hooks=hooks, **kwargs)
93
+ # httpx.Headers matches names without regard to case, so a caller's `user-agent`
94
+ # replaces the default rather than travelling beside it.
95
+ headers = httpx.Headers(user_agent.headers())
96
+ headers.update(kwargs.pop("headers", None) or {})
97
+ return httpx.Client(event_hooks=hooks, headers=headers, **kwargs)
92
98
 
93
99
 
94
100
  _DEFAULT_PORTS = {"http": 80, "https": 443}
@@ -10,8 +10,8 @@ inspect, create, approve/reject the pending-signup queue, promote/demote admin,
10
10
  disable/enable, reset a password, and manage a user's API-key scopes). `cmpnd
11
11
  admin org plan …` and `cmpnd admin org overrides …` move an org between plans
12
12
  and override one of its limits, `cmpnd admin org flags …` pins a feature flag
13
- for one org, `cmpnd admin plans …` edits each plan's
14
- default limits and feature flags, and `cmpnd admin signup …` sets who may sign up
13
+ for one org, `cmpnd admin plans …` edits a plan's draft, publishes it as a new
14
+ version, moves orgs between versions and pins a plan's feature flags, and `cmpnd admin signup …` sets who may sign up
15
15
  and the plan a new account's org lands on. `cmpnd admin feature-flags …` reads and
16
16
  toggles the server's runtime flags. These hit
17
17
  /api/v1/admin/* and require an API key carrying the admin:* scopes — the server
@@ -259,24 +259,55 @@ def _register_user(sub: _SubParsersAction[ArgumentParser]) -> None:
259
259
  set_scopes.set_defaults(func=handle_user_set_scopes)
260
260
 
261
261
 
262
+ def _positive_version(raw: str) -> int:
263
+ """A plan version is a whole number of 1 or more."""
264
+ try:
265
+ version = int(raw)
266
+ except ValueError:
267
+ version = 0
268
+ if version < 1:
269
+ raise ArgumentTypeError(f"{raw!r} is not a version: give a whole number of 1 or more")
270
+ return version
271
+
272
+
262
273
  def _register_plans(sub: _SubParsersAction[ArgumentParser]) -> None:
263
- plans = sub.add_parser("plans", help="Each plan's default limits and feature flags")
274
+ plans = sub.add_parser("plans", help="Each plan's versions, draft and feature flags")
264
275
  psub = plans.add_subparsers(dest="plans_action")
265
276
 
266
- ls = psub.add_parser("list", help="List every plan's limits and feature flags")
277
+ ls = psub.add_parser(
278
+ "list", help="List every plan's versions with their values and org counts, its draft and its feature flags"
279
+ )
267
280
  ls.set_defaults(func=handle_plans_list)
268
281
 
269
- limits = psub.add_parser("limits", help="A plan's default limits")
270
- lsub = limits.add_subparsers(dest="plans_limits_action")
271
- limit_update = lsub.add_parser("update", help="Set a plan's default for a limit; 'none' means no limit")
272
- limit_update.add_argument("plan", help=PLAN_HELP)
273
- limit_update.add_argument("name", help="Limit name, such as optimizations.concurrent")
274
- limit_update.add_argument("value", type=_limit_value, help="A whole number, or 'none' for no limit")
275
- limit_update.set_defaults(func=handle_plans_limits_update)
276
- limit_delete = lsub.add_parser("delete", help="Delete a plan's default for a limit")
277
- limit_delete.add_argument("plan", help=PLAN_HELP)
278
- limit_delete.add_argument("name", help="Limit name")
279
- limit_delete.set_defaults(func=handle_plans_limits_delete)
282
+ # A published version never changes, so every value write goes to the plan's
283
+ # draft, which serve opens from the current version on the first write.
284
+ draft = psub.add_parser("draft", help="A plan's draft, the next version before it is published")
285
+ dsub = draft.add_subparsers(dest="plans_draft_action")
286
+ draft_update = dsub.add_parser(
287
+ "update", help="Set a value in the plan's draft, opening one if needed; 'none' means no limit"
288
+ )
289
+ draft_update.add_argument("plan", help=PLAN_HELP)
290
+ draft_update.add_argument("name", help="Limit name, such as optimizations.concurrent")
291
+ draft_update.add_argument("value", type=_limit_value, help="A whole number, or 'none' for no limit")
292
+ draft_update.set_defaults(func=handle_plans_draft_update)
293
+ draft_delete = dsub.add_parser("delete", help="Delete a value from the plan's draft, so it sets no limit")
294
+ draft_delete.add_argument("plan", help=PLAN_HELP)
295
+ draft_delete.add_argument("name", help="Limit name")
296
+ draft_delete.set_defaults(func=handle_plans_draft_delete)
297
+ draft_publish = dsub.add_parser("publish", help="Publish the plan's draft as its current version; it moves no org")
298
+ draft_publish.add_argument("plan", help=PLAN_HELP)
299
+ draft_publish.add_argument("--note", default="", help="Why this version exists")
300
+ draft_publish.set_defaults(func=handle_plans_draft_publish)
301
+
302
+ move = psub.add_parser("move", help="Move orgs to a published version of their plan; their overrides stay")
303
+ move.add_argument("plan", help=PLAN_HELP)
304
+ move.add_argument("version", type=_positive_version, help="The published version to move to")
305
+ who = move.add_mutually_exclusive_group(required=True)
306
+ who.add_argument(
307
+ "--from-version", dest="from_version", type=_positive_version, help="Move every org on this version"
308
+ )
309
+ who.add_argument("--org", dest="org_id", help="Move this one org")
310
+ move.set_defaults(func=handle_plans_move)
280
311
 
281
312
  flags = psub.add_parser("flags", help="A plan's feature flags")
282
313
  fsub = flags.add_subparsers(dest="plans_flags_action")
@@ -430,12 +461,21 @@ def handle_plans_list(client: api_client.ApiClient, _args: Any) -> Any:
430
461
  return client.get("/api/v1/admin/plans")
431
462
 
432
463
 
433
- def handle_plans_limits_update(client: api_client.ApiClient, args: Any) -> Any:
434
- return client.put(f"/api/v1/admin/plans/{args.plan}/limits/{args.name}", json={"value": args.value})
464
+ def handle_plans_draft_update(client: api_client.ApiClient, args: Any) -> Any:
465
+ return client.put(f"/api/v1/admin/plans/{args.plan}/draft/limits/{args.name}", json={"value": args.value})
466
+
467
+
468
+ def handle_plans_draft_delete(client: api_client.ApiClient, args: Any) -> Any:
469
+ return client.delete(f"/api/v1/admin/plans/{args.plan}/draft/limits/{args.name}")
470
+
471
+
472
+ def handle_plans_draft_publish(client: api_client.ApiClient, args: Any) -> Any:
473
+ return client.post(f"/api/v1/admin/plans/{args.plan}/draft/publish", json={"note": args.note})
435
474
 
436
475
 
437
- def handle_plans_limits_delete(client: api_client.ApiClient, args: Any) -> Any:
438
- return client.delete(f"/api/v1/admin/plans/{args.plan}/limits/{args.name}")
476
+ def handle_plans_move(client: api_client.ApiClient, args: Any) -> Any:
477
+ body: dict[str, Any] = {"org_id": args.org_id} if args.org_id is not None else {"from_version": args.from_version}
478
+ return client.post(f"/api/v1/admin/plans/{args.plan}/versions/{args.version}/move", json=body)
439
479
 
440
480
 
441
481
  def handle_plans_flags_update(client: api_client.ApiClient, args: Any) -> Any:
@@ -168,6 +168,8 @@ DOMAIN_VERBS: dict[str, str] = {
168
168
  "restore": "returns a program from the Trash with its history, which create would not",
169
169
  "purge": "deletes a program in the Trash for good, the irreversible step after trash",
170
170
  "rename": "gives a program a new current name and frees the old one for any program, which update would hide",
171
+ "publish": "freezes a plan's draft as its next version, which no write changes again and update would not say",
172
+ "move": "puts orgs on another version of their plan; the version and its values stay as they are",
171
173
  }
172
174
 
173
175
  #: Leaves naming a related collection or sub-record rather than an action.
@@ -944,15 +944,17 @@ reject USER_ID --org-id ORG_ID` declines a pending membership.
944
944
 
945
945
  ### Plans, limits and signups
946
946
 
947
- Every org has a plan, `free`, `pro` or `enterprise`, and each plan sets
948
- default limits and feature flags. An org's own override beats its plan.
949
- `cmpnd admin org list` shows each org's plan, and `--plan free` keeps only the
950
- orgs on Free. `cmpnd admin org plan show ORG_ID` shows the org's plan and every
951
- limit, with the layer that set it. `cmpnd admin org plan update ORG_ID PLAN` moves the
952
- org. `cmpnd admin org overrides update ORG_ID NAME VALUE` overrides one limit,
953
- and a value of `none` lifts the plan's limit for that org.
954
- `cmpnd admin org overrides delete ORG_ID NAME` deletes the override, so the org
955
- follows its plan again.
947
+ Every org has a plan, `free`, `pro` or `enterprise`, and follows one published
948
+ version of it. A version sets the org's limits and never changes. An org's own
949
+ override beats its version. `cmpnd admin org list` shows each org's plan, and
950
+ `--plan free` keeps only the orgs on Free. `cmpnd admin org plan show ORG_ID`
951
+ shows the org's plan, `plan_version`, the plan's `current_version` and every
952
+ limit, with the layer that set it. `cmpnd admin org plan update ORG_ID PLAN`
953
+ moves the org to that plan's current version, and leaves an org already on the
954
+ plan where it is. `cmpnd admin org overrides update ORG_ID NAME VALUE`
955
+ overrides one limit, and a value of `none` lifts the version's limit for that
956
+ org. `cmpnd admin org overrides delete ORG_ID NAME` deletes the override, so
957
+ the org follows its version again. Overrides stay through every move.
956
958
 
957
959
  `cmpnd admin org usage ORG_ID` shows what each limit bounds now, in `in_use`,
958
960
  and the org's activity in the last 24 hours, 7 days and 30 days, in `windows`.
@@ -971,11 +973,24 @@ their date survives only while a membership keeps it. `oldest_key_at` and
971
973
  the user still holds, because leaving an org deletes the user's keys there.
972
974
  Sign-ins are not recorded, and deployment keys do not count as the user's keys.
973
975
 
974
- `cmpnd admin plans list` shows every plan's limits and feature flags.
975
- `cmpnd admin plans limits update PLAN NAME VALUE` and `limits delete PLAN NAME`
976
- change a plan's default for every org on it. `cmpnd admin plans flags update
977
- PLAN NAME [--disabled]` pins a flag for the plan, and `flags delete` hands the
978
- plan back to the global default. A plan's flag must already exist, so create
976
+ `cmpnd admin plans list` shows each plan's current version and its values,
977
+ every published version with its values, publisher, note and org count, the
978
+ draft, and the plan's feature flags. A change to a plan's limits takes three
979
+ steps, and no org moves until the last:
980
+
981
+ 1. `cmpnd admin plans draft update PLAN NAME VALUE` sets a value in the plan's
982
+ draft, and `draft delete PLAN NAME` removes one, so the draft sets no limit
983
+ for it. The first edit opens the draft as a copy of the current version.
984
+ 2. `cmpnd admin plans draft publish PLAN --note TEXT` publishes the draft as the
985
+ plan's current version. New orgs land on it.
986
+ 3. `cmpnd admin plans move PLAN VERSION --from-version N` moves every org on
987
+ version N to VERSION, and `--org ORG_ID` moves one org. It answers `moved`,
988
+ the number of orgs it moved, and refuses a draft or a version the plan
989
+ lacks.
990
+
991
+ `cmpnd admin plans flags update PLAN NAME [--disabled]` pins a flag for the
992
+ plan, and `flags delete` hands the plan back to the global default. Flags have
993
+ no versions. A plan's flag must already exist, so create
979
994
  it first with `cmpnd admin feature-flags set`.
980
995
 
981
996
  `cmpnd admin org flags list ORG_ID` shows every flag with the org's own row,
@@ -373,6 +373,8 @@ Two callers implement the waiting:
373
373
 
374
374
  Every API answer names the oldest cmpnd release the server supports, in the `X-Cmpnd-Minimum-Client-Version` header. `cmpnd_ingest/client_floor.py` reads it in an httpx response hook. When the installed `cmpnd` is older, it raises an `UpgradeRequiredWarning` once per process that names both versions and the command `pip install -U cmpnd`. The server refuses nothing for a client's version, so a call below the floor still goes through, and the warning appears beside whatever it does. Every client the SDK builds comes from `cmpnd._http.new_client`, which installs the hook, and the exporter installs it in `_client_kwargs`. `tests/test_client_floor.py` fails on an `httpx.Client` built any other way in `cmpnd/`. An install of `cmpnd_ingest` without `cmpnd` has no version to compare and says nothing. `CMPND_NO_UPGRADE_WARNING=1` silences the warning for a user who stays on an older release on purpose, and the warning names that switch. `cmpnd.UpgradeRequiredWarning` is public, so a `warnings` filter silences it in code.
375
375
 
376
+ Every request names its release in the other direction, so the server can count which releases are in use. `cmpnd_ingest/user_agent.py` builds the `User-Agent` value, such as `cmpnd/0.85.2 python/3.12.13 httpx/0.28.1 dspy/3.4.1`. The first product is `cmpnd` for the SDK and `cmpnd-ingest` for a producer that installs only the transport, such as the optimizer's search process. A library that is not installed is left out. `cmpnd._http.new_client` and the exporter's `_client_kwargs` both send it. A caller that passes its own `User-Agent` to `new_client` keeps it. `tests/test_user_agent.py` checks the value that reaches the wire.
377
+
376
378
  ### The optimize loop and transient tolerance
377
379
 
378
380
  `optimization._poll_until_terminal` is what a caller blocks on for the length of a run, so one transient answer or one reset connection must not destroy a healthy run. It rides through transient answers on the shared budget, resetting after each successful poll, and never extends past the caller's `timeout` (default 600s per step) — a backoff wait is clipped by the deadline rather than overshooting it.
@@ -34,7 +34,7 @@ from typing import Any, cast
34
34
 
35
35
  import httpx
36
36
 
37
- from . import client_floor, program_names, request_log, retry
37
+ from . import client_floor, program_names, request_log, retry, user_agent
38
38
  from .bodies import EncodedTraceBody, SpanBody, TraceBody
39
39
  from .protocols import (
40
40
  EvalRunRecord,
@@ -1106,11 +1106,13 @@ class BatchExporter:
1106
1106
 
1107
1107
  def _client_kwargs(self) -> dict[str, Any]:
1108
1108
  """Shared httpx.Client construction args for the background and sync
1109
- clients, so both carry the same base URL, auth header, client-floor
1110
- check (``client_floor``) and, in tests, injected transport."""
1109
+ clients, so both carry the same base URL, auth header, release
1110
+ ``User-Agent`` (``user_agent``), client-floor check (``client_floor``)
1111
+ and, in tests, injected transport."""
1111
1112
  kwargs: dict[str, Any] = {
1112
1113
  "base_url": self._config.endpoint,
1113
1114
  "headers": {
1115
+ **user_agent.headers(),
1114
1116
  "X-API-Key": self._config.api_key,
1115
1117
  "Content-Type": "application/json",
1116
1118
  },
@@ -0,0 +1,52 @@
1
+ """The ``User-Agent`` every client the SDK builds sends, so the server can count releases.
2
+
3
+ The value names the installed distribution first, then the runtime it runs on:
4
+
5
+ cmpnd/0.85.2 python/3.12.13 httpx/0.28.1 dspy/3.4.1
6
+
7
+ The first product is ``cmpnd`` for the SDK and ``cmpnd-ingest`` for a producer that
8
+ installs only the transport, so the two never count as one. A distribution that is not
9
+ installed, such as ``dspy`` for a transport-only producer, is left out. A value that
10
+ cannot be read becomes ``unknown`` rather than failing a request.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import functools
16
+ import importlib.metadata
17
+ import platform
18
+
19
+ # The distributions that provide this package, in the order the first one found names
20
+ # the client. Only one is installed at a time (ingest/README.md).
21
+ _CLIENTS = ("cmpnd", "cmpnd-ingest")
22
+ # Runtime libraries whose version shapes what a client sends.
23
+ _LIBRARIES = ("httpx", "dspy")
24
+
25
+
26
+ def _version(distribution: str) -> str | None:
27
+ """The installed version, or None when it is not installed or cannot be read.
28
+
29
+ Any failure to read metadata counts as absent, because a broken install's
30
+ metadata must not stop a client from being built.
31
+ """
32
+ try:
33
+ return importlib.metadata.version(distribution)
34
+ except Exception:
35
+ return None
36
+
37
+
38
+ @functools.cache
39
+ def user_agent() -> str:
40
+ """The ``User-Agent`` header value for this process."""
41
+ client = next(
42
+ (f"{name}/{version}" for name in _CLIENTS if (version := _version(name)) is not None),
43
+ "cmpnd/unknown",
44
+ )
45
+ products = [client, f"python/{platform.python_version()}"]
46
+ products += [f"{name}/{version}" for name in _LIBRARIES if (version := _version(name)) is not None]
47
+ return " ".join(products)
48
+
49
+
50
+ def headers() -> dict[str, str]:
51
+ """The headers that identify this client, to merge into a client's own."""
52
+ return {"User-Agent": user_agent()}
@@ -5,7 +5,7 @@ name = "cmpnd"
5
5
  # it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
6
6
  # hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
7
7
  # this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
8
- version = "0.85.1"
8
+ version = "0.86.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -360,20 +360,61 @@ class TestAdminPlans:
360
360
  assert args.func(mock_client, args) == {"plans": []}
361
361
  mock_client.get.assert_called_once_with("/api/v1/admin/plans")
362
362
 
363
- def test_set_limit_and_none(self, mock_client):
364
- args = _parse("plans", "limits", "update", "pro", "optimizations.concurrent", "5")
363
+ def test_draft_update_writes_the_draft_and_none_sends_null(self, mock_client):
364
+ args = _parse("plans", "draft", "update", "pro", "optimizations.concurrent", "5")
365
365
  args.func(mock_client, args)
366
- mock_client.put.assert_called_with("/api/v1/admin/plans/pro/limits/optimizations.concurrent", json={"value": 5})
367
- args = _parse("plans", "limits", "update", "free", "optimizations.concurrent", "none")
366
+ mock_client.put.assert_called_with(
367
+ "/api/v1/admin/plans/pro/draft/limits/optimizations.concurrent", json={"value": 5}
368
+ )
369
+ args = _parse("plans", "draft", "update", "free", "optimizations.concurrent", "none")
368
370
  args.func(mock_client, args)
369
371
  mock_client.put.assert_called_with(
370
- "/api/v1/admin/plans/free/limits/optimizations.concurrent", json={"value": None}
372
+ "/api/v1/admin/plans/free/draft/limits/optimizations.concurrent", json={"value": None}
371
373
  )
372
374
 
373
- def test_clear_limit(self, mock_client):
374
- args = _parse("plans", "limits", "delete", "pro", "optimizations.concurrent")
375
+ def test_draft_delete(self, mock_client):
376
+ args = _parse("plans", "draft", "delete", "pro", "optimizations.concurrent")
377
+ args.func(mock_client, args)
378
+ mock_client.delete.assert_called_once_with("/api/v1/admin/plans/pro/draft/limits/optimizations.concurrent")
379
+
380
+ def test_draft_publish_sends_the_note(self, mock_client):
381
+ mock_client.post.return_value = {"plan": "pro", "version": 2}
382
+ args = _parse("plans", "draft", "publish", "pro", "--note", "Five at a time")
383
+ assert args.func(mock_client, args) == {"plan": "pro", "version": 2}
384
+ mock_client.post.assert_called_once_with(
385
+ "/api/v1/admin/plans/pro/draft/publish", json={"note": "Five at a time"}
386
+ )
387
+ args = _parse("plans", "draft", "publish", "free")
388
+ args.func(mock_client, args)
389
+ mock_client.post.assert_called_with("/api/v1/admin/plans/free/draft/publish", json={"note": ""})
390
+
391
+ def test_move_by_version_or_by_org(self, mock_client):
392
+ mock_client.post.return_value = {"plan": "pro", "version": 2, "moved": 3}
393
+ args = _parse("plans", "move", "pro", "2", "--from-version", "1")
394
+ assert args.func(mock_client, args)["moved"] == 3
395
+ mock_client.post.assert_called_with("/api/v1/admin/plans/pro/versions/2/move", json={"from_version": 1})
396
+ args = _parse("plans", "move", "pro", "2", "--org", "o_1")
375
397
  args.func(mock_client, args)
376
- mock_client.delete.assert_called_once_with("/api/v1/admin/plans/pro/limits/optimizations.concurrent")
398
+ mock_client.post.assert_called_with("/api/v1/admin/plans/pro/versions/2/move", json={"org_id": "o_1"})
399
+
400
+ def test_move_needs_exactly_one_source_and_a_whole_version(self):
401
+ import pytest
402
+
403
+ for argv in (
404
+ ("plans", "move", "pro", "2"),
405
+ ("plans", "move", "pro", "2", "--from-version", "1", "--org", "o_1"),
406
+ ("plans", "move", "pro", "two", "--org", "o_1"),
407
+ ("plans", "move", "pro", "0", "--org", "o_1"),
408
+ ("plans", "move", "pro", "2", "--from-version", "zero"),
409
+ ):
410
+ with pytest.raises(SystemExit):
411
+ _parse(*argv)
412
+
413
+ def test_the_in_place_limit_commands_are_gone(self):
414
+ import pytest
415
+
416
+ with pytest.raises(SystemExit):
417
+ _parse("plans", "limits", "update", "pro", "optimizations.concurrent", "5")
377
418
 
378
419
  def test_set_flag_enables_by_default_and_disables_with_the_flag(self, mock_client):
379
420
  args = _parse("plans", "flags", "update", "free", "beta")
@@ -389,10 +430,10 @@ class TestAdminPlans:
389
430
  mock_client.delete.assert_called_once_with("/api/v1/admin/plans/free/feature-flags/beta")
390
431
 
391
432
  def test_plan_names_are_serves_to_check(self, mock_client):
392
- args = _parse("plans", "limits", "update", "platinum", "optimizations.concurrent", "5")
433
+ args = _parse("plans", "draft", "update", "platinum", "optimizations.concurrent", "5")
393
434
  args.func(mock_client, args)
394
435
  mock_client.put.assert_called_once_with(
395
- "/api/v1/admin/plans/platinum/limits/optimizations.concurrent", json={"value": 5}
436
+ "/api/v1/admin/plans/platinum/draft/limits/optimizations.concurrent", json={"value": 5}
396
437
  )
397
438
 
398
439
 
@@ -0,0 +1,109 @@
1
+ """Every client the SDK builds names its release in ``User-Agent``, so the server can
2
+ count which releases are in use.
3
+
4
+ Driven over real ``httpx.Client`` instances with ``MockTransport``: the property is
5
+ what reaches the wire.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import platform
11
+ from collections.abc import Iterator
12
+
13
+ import httpx
14
+ import pytest
15
+ from cmpnd_ingest import user_agent
16
+
17
+ import cmpnd
18
+ from cmpnd import _http
19
+ from cmpnd.configuration import Config
20
+ from cmpnd.exporter import BatchExporter
21
+
22
+
23
+ @pytest.fixture(autouse=True)
24
+ def _fresh_user_agent() -> Iterator[None]:
25
+ user_agent.user_agent.cache_clear()
26
+ yield
27
+ user_agent.user_agent.cache_clear()
28
+
29
+
30
+ def _sent_user_agent(client: httpx.Client, requests: list[httpx.Request]) -> str:
31
+ with client:
32
+ client.get("https://app.cmpnd.ai/api/v1/deployments")
33
+ assert len(requests) == 1
34
+ return requests[0].headers["User-Agent"]
35
+
36
+
37
+ def _capturing(requests: list[httpx.Request]) -> httpx.MockTransport:
38
+ def handle(request: httpx.Request) -> httpx.Response:
39
+ requests.append(request)
40
+ return httpx.Response(200, json={})
41
+
42
+ return httpx.MockTransport(handle)
43
+
44
+
45
+ def test_the_user_agent_names_the_release_python_and_libraries() -> None:
46
+ products = user_agent.user_agent().split(" ")
47
+ assert products[0] == f"cmpnd/{cmpnd.__version__}"
48
+ assert products[1] == f"python/{platform.python_version()}"
49
+ assert f"httpx/{httpx.__version__}" in products
50
+
51
+
52
+ def test_an_api_client_sends_the_release_user_agent() -> None:
53
+ requests: list[httpx.Request] = []
54
+ sent = _sent_user_agent(_http.new_client(transport=_capturing(requests)), requests)
55
+ assert sent == user_agent.user_agent()
56
+ assert sent.startswith(f"cmpnd/{cmpnd.__version__} ")
57
+
58
+
59
+ def test_a_callers_own_user_agent_wins() -> None:
60
+ requests: list[httpx.Request] = []
61
+ client = _http.new_client(transport=_capturing(requests), headers={"User-Agent": "mine/1"})
62
+ assert _sent_user_agent(client, requests) == "mine/1"
63
+
64
+
65
+ @pytest.mark.parametrize("name", ["user-agent", "USER-AGENT"])
66
+ def test_a_callers_user_agent_wins_in_any_case(name: str) -> None:
67
+ requests: list[httpx.Request] = []
68
+ client = _http.new_client(transport=_capturing(requests), headers={name: "mine/1"})
69
+ _sent_user_agent(client, requests)
70
+ assert requests[0].headers.get_list("User-Agent") == ["mine/1"]
71
+
72
+
73
+ def test_a_callers_httpx_headers_win() -> None:
74
+ requests: list[httpx.Request] = []
75
+ client = _http.new_client(transport=_capturing(requests), headers=httpx.Headers({"User-Agent": "mine/1"}))
76
+ _sent_user_agent(client, requests)
77
+ assert requests[0].headers.get_list("User-Agent") == ["mine/1"]
78
+
79
+
80
+ def test_the_exporter_sends_the_release_user_agent() -> None:
81
+ requests: list[httpx.Request] = []
82
+ exporter = BatchExporter(Config(api_key="k", endpoint="http://localhost:9999"), transport=_capturing(requests))
83
+ sent = _sent_user_agent(httpx.Client(**exporter._client_kwargs()), requests)
84
+ assert sent == user_agent.user_agent()
85
+ assert requests[0].headers["X-API-Key"] == "k"
86
+
87
+
88
+ def test_a_transport_only_producer_names_itself(monkeypatch: pytest.MonkeyPatch) -> None:
89
+ versions = {"cmpnd-ingest": "0.1.0", "httpx": "0.28.1"}
90
+ monkeypatch.setattr(user_agent, "_version", versions.get)
91
+ assert user_agent.user_agent() == f"cmpnd-ingest/0.1.0 python/{platform.python_version()} httpx/0.28.1"
92
+
93
+
94
+ def test_an_unreadable_release_still_sends_a_user_agent(monkeypatch: pytest.MonkeyPatch) -> None:
95
+ monkeypatch.setattr(user_agent, "_version", lambda _name: None)
96
+ assert user_agent.user_agent() == f"cmpnd/unknown python/{platform.python_version()}"
97
+
98
+
99
+ def test_unreadable_metadata_still_builds_both_clients(monkeypatch: pytest.MonkeyPatch) -> None:
100
+ def broken(_name: str) -> str:
101
+ raise OSError("metadata unreadable")
102
+
103
+ monkeypatch.setattr(user_agent.importlib.metadata, "version", broken)
104
+ requests: list[httpx.Request] = []
105
+ expected = f"cmpnd/unknown python/{platform.python_version()}"
106
+ assert _sent_user_agent(_http.new_client(transport=_capturing(requests)), requests) == expected
107
+ requests.clear()
108
+ exporter = BatchExporter(Config(api_key="k", endpoint="http://localhost:9999"), transport=_capturing(requests))
109
+ assert _sent_user_agent(httpx.Client(**exporter._client_kwargs()), requests) == expected
@@ -505,7 +505,7 @@ wheels = [
505
505
 
506
506
  [[package]]
507
507
  name = "cmpnd"
508
- version = "0.85.1"
508
+ version = "0.86.0"
509
509
  source = { editable = "." }
510
510
  default-groups = ["dspy-3-4-1"]
511
511
  dependencies = [
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