cmpnd 0.58.0__tar.gz → 0.59.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 (391) hide show
  1. {cmpnd-0.58.0 → cmpnd-0.59.0}/PKG-INFO +1 -1
  2. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/top.py +132 -2
  3. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/optimization.py +61 -0
  4. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/cli.md +26 -3
  5. {cmpnd-0.58.0 → cmpnd-0.59.0}/pyproject.toml +1 -1
  6. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/test-in-linux.sh +1 -1
  7. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/span_reference_fixture.py +24 -0
  8. cmpnd-0.59.0/tests/test_authored_binding_credentials.py +328 -0
  9. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_top.py +65 -12
  10. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_span_references.py +57 -4
  11. {cmpnd-0.58.0 → cmpnd-0.59.0}/uv.lock +1 -1
  12. {cmpnd-0.58.0 → cmpnd-0.59.0}/.gitignore +0 -0
  13. {cmpnd-0.58.0 → cmpnd-0.59.0}/AGENTS.md +0 -0
  14. {cmpnd-0.58.0 → cmpnd-0.59.0}/CLAUDE.md +0 -0
  15. {cmpnd-0.58.0 → cmpnd-0.59.0}/README.md +0 -0
  16. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/__init__.py +0 -0
  17. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_assessments.py +0 -0
  18. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_authored_import.py +0 -0
  19. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_deployments.py +0 -0
  20. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_exporter_pool.py +0 -0
  21. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_gepa_patch.py +0 -0
  22. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_http.py +0 -0
  23. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_program_patch.py +0 -0
  24. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_rlm_patch.py +0 -0
  25. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_runtime_tools.py +0 -0
  26. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_timespec.py +0 -0
  27. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_traces.py +0 -0
  28. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/_valsets.py +0 -0
  29. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/authored.py +0 -0
  30. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/callback.py +0 -0
  31. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/__init__.py +0 -0
  32. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/api_client.py +0 -0
  33. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/__init__.py +0 -0
  34. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/admin.py +0 -0
  35. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/assessments.py +0 -0
  36. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/auth.py +0 -0
  37. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/datasets.py +0 -0
  38. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/deployments.py +0 -0
  39. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/evals.py +0 -0
  40. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/login.py +0 -0
  41. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/optimizations.py +0 -0
  42. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/org.py +0 -0
  43. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/programs.py +0 -0
  44. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  45. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/signatures.py +0 -0
  46. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  47. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/traces.py +0 -0
  48. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/commands/valsets.py +0 -0
  49. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/credentials.py +0 -0
  50. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/deprecation.py +0 -0
  51. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/pagination.py +0 -0
  52. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/shell.py +0 -0
  53. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/trace_render.py +0 -0
  54. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/tree_help.py +0 -0
  55. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/cli/verbs.py +0 -0
  56. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/client.py +0 -0
  57. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/configuration.py +0 -0
  58. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/context.py +0 -0
  59. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/credentials.py +0 -0
  60. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/dataset_sync.py +0 -0
  61. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/datasets.py +0 -0
  62. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/decorators.py +0 -0
  63. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/deployment.py +0 -0
  64. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/deployment_metadata.py +0 -0
  65. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/deployment_runs.py +0 -0
  66. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/eval_handler.py +0 -0
  67. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/execution.py +0 -0
  68. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/exporter.py +0 -0
  69. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/exporter_http.py +0 -0
  70. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/helpers.py +0 -0
  71. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/identity.py +0 -0
  72. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/imaging.py +0 -0
  73. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/__init__.py +0 -0
  74. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/decode.py +0 -0
  75. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/encode.py +0 -0
  76. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/hash.py +0 -0
  77. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/program.py +0 -0
  78. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/reflection.py +0 -0
  79. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/types.py +0 -0
  80. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/ir/xxh64.py +0 -0
  81. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/lm_configuration.py +0 -0
  82. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/mcp_servers.py +0 -0
  83. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/models.py +0 -0
  84. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/named_programs.py +0 -0
  85. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/optimizers/__init__.py +0 -0
  86. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/optimizers/gepa.py +0 -0
  87. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  88. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/optimizers/resume.py +0 -0
  89. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/optimizers/tracker.py +0 -0
  90. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/packaging.py +0 -0
  91. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/programs.py +0 -0
  92. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/references.py +0 -0
  93. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/retry.py +0 -0
  94. {cmpnd-0.58.0 → cmpnd-0.59.0}/cmpnd/traces.py +0 -0
  95. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/2026-04-02-code-review.md +0 -0
  96. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/client.md +0 -0
  97. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/custom-proposer.md +0 -0
  98. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  99. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  100. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/reading-traces.md +0 -0
  101. {cmpnd-0.58.0 → cmpnd-0.59.0}/docs/sdk-instrumentation.md +0 -0
  102. {cmpnd-0.58.0 → cmpnd-0.59.0}/examples/generalizing_proposer.py +0 -0
  103. {cmpnd-0.58.0 → cmpnd-0.59.0}/examples/local_ollama.py +0 -0
  104. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/LINEAGE.md +0 -0
  105. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/README.md +0 -0
  106. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  107. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  108. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  109. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  110. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  111. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  112. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/py.typed +0 -0
  113. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  114. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/retry.py +0 -0
  115. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  116. {cmpnd-0.58.0 → cmpnd-0.59.0}/ingest/pyproject.toml +0 -0
  117. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/audit-sdist.sh +0 -0
  118. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/capture_review_run.py +0 -0
  119. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/check-pip-install.sh +0 -0
  120. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/check_release_artifacts.py +0 -0
  121. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/fixtures/review_run.json.gz +0 -0
  122. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/gepa_resume_bug.py +0 -0
  123. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/install-test-env.sh +0 -0
  124. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/release-stress.sh +0 -0
  125. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/require_version_bump.py +0 -0
  126. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/seed_review_run.py +0 -0
  127. {cmpnd-0.58.0 → cmpnd-0.59.0}/scripts/selfhost_compat.py +0 -0
  128. {cmpnd-0.58.0 → cmpnd-0.59.0}/stubs/dspy/__init__.pyi +0 -0
  129. {cmpnd-0.58.0 → cmpnd-0.59.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  130. {cmpnd-0.58.0 → cmpnd-0.59.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  131. {cmpnd-0.58.0 → cmpnd-0.59.0}/stubs/dspy/utils/__init__.pyi +0 -0
  132. {cmpnd-0.58.0 → cmpnd-0.59.0}/stubs/dspy/utils/callback.pyi +0 -0
  133. {cmpnd-0.58.0 → cmpnd-0.59.0}/stubs/dspy/utils/dummies.pyi +0 -0
  134. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/__init__.py +0 -0
  135. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/_credential_route.py +0 -0
  136. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/_deployment_signature_helpers.py +0 -0
  137. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/_exporter_helpers.py +0 -0
  138. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/_fake_clock.py +0 -0
  139. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/_identity_helpers.py +0 -0
  140. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/_resolve_refs.py +0 -0
  141. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/_traced_run.py +0 -0
  142. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/conftest.py +0 -0
  143. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/__init__.py +0 -0
  144. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/committee_task.py +0 -0
  145. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/conftest.py +0 -0
  146. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  147. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  148. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/fake_lm.py +0 -0
  149. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/helpers.py +0 -0
  150. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/target_capabilities.py +0 -0
  151. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_assessments_cli.py +0 -0
  152. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_capture_prompts.py +0 -0
  153. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_composite_module.py +0 -0
  154. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  155. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_deploy_execute.py +0 -0
  156. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_deploy_pandas.py +0 -0
  157. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_deploy_programs.py +0 -0
  158. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_edge_cases.py +0 -0
  159. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_example_hash.py +0 -0
  160. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_gepa_optimization.py +0 -0
  161. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_interpreter_spans.py +0 -0
  162. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_lm_accuracy.py +0 -0
  163. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_module_tracing.py +0 -0
  164. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_optimization_lineage.py +0 -0
  165. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  166. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_failures.py +0 -0
  167. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_loop.py +0 -0
  168. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  169. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  170. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_parallel_export.py +0 -0
  171. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_project_attribution.py +0 -0
  172. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_react_tool_spans.py +0 -0
  173. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_save_load.py +0 -0
  174. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_trace_structure.py +0 -0
  175. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_traces_read.py +0 -0
  176. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_user_attribution.py +0 -0
  177. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/e2e/test_valsets_cli.py +0 -0
  178. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  179. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  180. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  181. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  182. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  183. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  184. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  185. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  186. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  187. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/10_react.json +0 -0
  188. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/11_refine.json +0 -0
  189. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/12_retrieve.json +0 -0
  190. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/13_knn.json +0 -0
  191. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/14_parallel.json +0 -0
  192. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/identity_vectors/README.md +0 -0
  193. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/ingest_conformance.py +0 -0
  194. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/__init__.py +0 -0
  195. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/authored_optimize/judge.py +0 -0
  196. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  197. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/conftest.py +0 -0
  198. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  199. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/hosted_optimize_probe.py +0 -0
  200. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/optimization_run_probe.py +0 -0
  201. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  202. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/test_logs_renders_traces.py +0 -0
  203. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/test_optimization_run.py +0 -0
  204. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/test_optimizing_progress.py +0 -0
  205. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/integration/test_unhappy_path.py +0 -0
  206. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/__init__.py +0 -0
  207. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/__main__.py +0 -0
  208. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/drain.py +0 -0
  209. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/ingest.py +0 -0
  210. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/latency_transport.py +0 -0
  211. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/rlm_shape.py +0 -0
  212. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/rlm_size_report.py +0 -0
  213. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/sweep.py +0 -0
  214. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/test_rlm_shape.py +0 -0
  215. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/load/test_rlm_size_report.py +0 -0
  216. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/__init__.py +0 -0
  217. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/conftest.py +0 -0
  218. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/seed_parity.py +0 -0
  219. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_auth.py +0 -0
  220. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_datasets.py +0 -0
  221. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_deployments.py +0 -0
  222. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_evals.py +0 -0
  223. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_health.py +0 -0
  224. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_modules.py +0 -0
  225. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_optimizations.py +0 -0
  226. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_programs.py +0 -0
  227. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_signatures.py +0 -0
  228. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_trace_streaming.py +0 -0
  229. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/parity/test_traces.py +0 -0
  230. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/__init__.py +0 -0
  231. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/_provider.py +0 -0
  232. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/_readiness.py +0 -0
  233. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/_reliability.py +0 -0
  234. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/_result.py +0 -0
  235. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/mint_bedrock_token.py +0 -0
  236. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_deploy_programs.py +0 -0
  237. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_multi_client_stress.py +0 -0
  238. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_optimize_client_committee.py +0 -0
  239. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_optimize_loop.py +0 -0
  240. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  241. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_optimize_stress.py +0 -0
  242. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_provider_roles.py +0 -0
  243. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_readiness.py +0 -0
  244. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_real_provider_twins.py +0 -0
  245. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_reliability.py +0 -0
  246. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_reliability_classify.py +0 -0
  247. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_rlm_wasm.py +0 -0
  248. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/stress/test_trace_search.py +0 -0
  249. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_adapter_reentrancy.py +0 -0
  250. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_assessments_api.py +0 -0
  251. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_authored.py +0 -0
  252. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_authored_bundle_collisions.py +0 -0
  253. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_authored_metric_contract.py +0 -0
  254. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_authored_named_models.py +0 -0
  255. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_authored_optimize.py +0 -0
  256. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_callback.py +0 -0
  257. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_candidate_lineage.py +0 -0
  258. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/__init__.py +0 -0
  259. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/conftest.py +0 -0
  260. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_admin.py +0 -0
  261. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_api_client.py +0 -0
  262. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_assessments.py +0 -0
  263. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_auth.py +0 -0
  264. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_credentials.py +0 -0
  265. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_datasets.py +0 -0
  266. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_deployment_runs.py +0 -0
  267. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_deployments.py +0 -0
  268. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_evals.py +0 -0
  269. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_login.py +0 -0
  270. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_main.py +0 -0
  271. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_optimizations.py +0 -0
  272. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_org.py +0 -0
  273. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_pagination.py +0 -0
  274. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_parity.py +0 -0
  275. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_programs.py +0 -0
  276. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_provider_credentials.py +0 -0
  277. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_schema_sample.py +0 -0
  278. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_shell.py +0 -0
  279. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_signatures.py +0 -0
  280. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_tool_servers.py +0 -0
  281. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_traces.py +0 -0
  282. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  283. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_tree_help.py +0 -0
  284. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cli/test_valsets.py +0 -0
  285. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_client.py +0 -0
  286. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_client_floor.py +0 -0
  287. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_client_program_names.py +0 -0
  288. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_clustering/__init__.py +0 -0
  289. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_clustering/conftest.py +0 -0
  290. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_clustering/test_edge_cases.py +0 -0
  291. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  292. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_clustering/test_type_conversion.py +0 -0
  293. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_cmp551_grouping_identity.py +0 -0
  294. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_configuration.py +0 -0
  295. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_context.py +0 -0
  296. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_context_propagation.py +0 -0
  297. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_credential_kwargs.py +0 -0
  298. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_credential_selection.py +0 -0
  299. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_credential_values.py +0 -0
  300. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_dataset_polling.py +0 -0
  301. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_dataset_sync.py +0 -0
  302. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_datasets.py +0 -0
  303. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_decision_client_spans.py +0 -0
  304. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_decorators.py +0 -0
  305. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_deploy.py +0 -0
  306. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  307. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_deploy_program_id.py +0 -0
  308. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_deployment_metadata.py +0 -0
  309. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_deployment_runs.py +0 -0
  310. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_deployments_read.py +0 -0
  311. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_eval.py +0 -0
  312. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_eval_example_attribution.py +0 -0
  313. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_eval_start.py +0 -0
  314. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_eval_trace_grouping.py +0 -0
  315. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_exception_paths.py +0 -0
  316. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_execute.py +0 -0
  317. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_execute_run_identity.py +0 -0
  318. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_exporter.py +0 -0
  319. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_exporter_program_names.py +0 -0
  320. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_exporter_queue.py +0 -0
  321. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_exporter_real.py +0 -0
  322. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_fake_lm.py +0 -0
  323. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_gepa_callback.py +0 -0
  324. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_gepa_integration.py +0 -0
  325. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_gepa_output_normalization.py +0 -0
  326. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_gepa_resume.py +0 -0
  327. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_identity.py +0 -0
  328. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_imaging.py +0 -0
  329. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_import_weight.py +0 -0
  330. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_ingest_conformance.py +0 -0
  331. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_ingest_second_producer.py +0 -0
  332. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_integration.py +0 -0
  333. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_interpreter_callback.py +0 -0
  334. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_ir_encoder_properties.py +0 -0
  335. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_ir_golden_vectors.py +0 -0
  336. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_ir_roundtrip.py +0 -0
  337. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_lm15_native_calls.py +0 -0
  338. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_lm_configuration.py +0 -0
  339. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_lm_usage_shapes.py +0 -0
  340. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_mcp_tools.py +0 -0
  341. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_models.py +0 -0
  342. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_multi_client_stress_readiness.py +0 -0
  343. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimization_progress.py +0 -0
  344. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimization_start.py +0 -0
  345. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimize.py +0 -0
  346. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimize_budget.py +0 -0
  347. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimize_lift.py +0 -0
  348. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimize_lms.py +0 -0
  349. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimize_proposer.py +0 -0
  350. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimize_reflection_lm.py +0 -0
  351. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_optimize_terminal_failure.py +0 -0
  352. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_packaging.py +0 -0
  353. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_packaging_callable_source.py +0 -0
  354. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_packaging_mcp_tools.py +0 -0
  355. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_packaging_program_source.py +0 -0
  356. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_poll_until.py +0 -0
  357. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_program_id_deprecation.py +0 -0
  358. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_program_identity.py +0 -0
  359. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_program_lineage_persist.py +0 -0
  360. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_program_names_in_deploy.py +0 -0
  361. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_programs_create.py +0 -0
  362. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_project_payload.py +0 -0
  363. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_real_provider_twins_readiness.py +0 -0
  364. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_reflector_properties.py +0 -0
  365. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_reflector_smoke.py +0 -0
  366. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_release_stress_script.py +0 -0
  367. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_replaced_names.py +0 -0
  368. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_repro_reactv2_bugs.py +0 -0
  369. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_require_version_bump.py +0 -0
  370. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_retry.py +0 -0
  371. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_reversible_failures.py +0 -0
  372. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_rlm_patch.py +0 -0
  373. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_run_attribution_paths.py +0 -0
  374. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_runtime_tools.py +0 -0
  375. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_schemas.py +0 -0
  376. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_seal_lm_credential.py +0 -0
  377. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_seed_review_run.py +0 -0
  378. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_signature_inputs.py +0 -0
  379. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_span_sent_once.py +0 -0
  380. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_system_one.py +0 -0
  381. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_trace_context_concurrency.py +0 -0
  382. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_trace_fact_wire_contract.py +0 -0
  383. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_trace_facts.py +0 -0
  384. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_trace_render.py +0 -0
  385. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_traces_read.py +0 -0
  386. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_tracked_gepa.py +0 -0
  387. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_tracker_enrich.py +0 -0
  388. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_truncation.py +0 -0
  389. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_upload_auth_headers.py +0 -0
  390. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_valsets_api.py +0 -0
  391. {cmpnd-0.58.0 → cmpnd-0.59.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.58.0
3
+ Version: 0.59.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>
@@ -890,6 +890,34 @@ def _program_args(args: Any, verb: str, *, needs_name: bool = True) -> str | Non
890
890
  return name
891
891
 
892
892
 
893
+ class _CredentialLookup:
894
+ """Turn a credential name a flag gave into the organization's ``cmpnd.Credential``.
895
+
896
+ Every command that takes a credential name looks it up here, before it stages or
897
+ sends anything. The lookup is ``cmpnd.credentials.get``, the line a Python caller
898
+ writes, so a mistyped name fails on the command line with the organization's names
899
+ and the nearest one. Serve checks the name again when the deploy or run names it,
900
+ which catches a credential revoked in between. Each distinct name is read once.
901
+ """
902
+
903
+ def __init__(self, client: api_client.ApiClient) -> None:
904
+ self._client = client
905
+ self._found: dict[str, Any] = {}
906
+
907
+ def __call__(self, flag: str, name: str) -> Any:
908
+ """The credential ``name``. ``flag`` is how the command line spelled it, for the error."""
909
+ from ... import credentials
910
+
911
+ if name not in self._found:
912
+ try:
913
+ self._found[name] = credentials.get(
914
+ name, api_key=self._client._api_key, endpoint=self._client._endpoint
915
+ )
916
+ except credentials.CredentialNotFound as exc:
917
+ raise api_client.CliError(f"{flag}: {exc}") from exc
918
+ return self._found[name]
919
+
920
+
893
921
  def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
894
922
  """Stage authored Python and deploy the checked bundle.
895
923
 
@@ -904,12 +932,18 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
904
932
  dspy.configure(lm=lm)
905
933
  program = dspy.Predict("question -> answer")
906
934
  PY
935
+
936
+ ``--credential NAME`` names the credential the program's model spends, and is looked
937
+ up before the source is staged.
907
938
  """
908
939
  from pathlib import Path
909
940
 
910
941
  from ... import authored, deployment
911
942
 
912
943
  program_name = _program_args(args, "deploy")
944
+ credential_name = getattr(args, "credential", None)
945
+ if credential_name is not None:
946
+ credential_name = _CredentialLookup(client)(f"--credential {credential_name}", credential_name).name
913
947
  try:
914
948
  if args.source == "-" or args.source is None:
915
949
  prepared = authored.prepare_stdin(
@@ -939,7 +973,7 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
939
973
  metadata=metadata,
940
974
  program_name=program_name,
941
975
  config=sdk_config(client),
942
- credential_name=getattr(args, "credential", None),
976
+ credential_name=credential_name,
943
977
  )
944
978
  except ValueError as exc:
945
979
  raise api_client.CliError(str(exc)) from exc
@@ -978,6 +1012,67 @@ def _optimizer_arg(value: str) -> str:
978
1012
  return value
979
1013
 
980
1014
 
1015
+ def _lm_credential_arg(value: str) -> tuple[str, str]:
1016
+ """Parse ``--lm-credential BINDING=NAME`` into the binding and the credential's name.
1017
+
1018
+ The program's own model is refused here, at parse time, because it spends the run's
1019
+ credential and ``--credential`` already names that.
1020
+ """
1021
+ binding, sep, name = value.partition("=")
1022
+ if not sep or not binding or not name:
1023
+ raise ArgumentTypeError(
1024
+ f"{value!r} is not BINDING=NAME. Give the name of an LM the optimize file's `lms` binds, then a "
1025
+ "credential `cmpnd credentials list` shows, as in judge=typesafe"
1026
+ )
1027
+ from ... import optimization
1028
+
1029
+ if binding == optimization.DEFAULT_LM_BINDING:
1030
+ raise ArgumentTypeError(
1031
+ f"{binding} is the program's own model, which spends the run's credential. Name it with --credential"
1032
+ )
1033
+ return binding, name
1034
+
1035
+
1036
+ def _optimize_credentials(
1037
+ client: api_client.ApiClient, args: Any
1038
+ ) -> tuple[Any, str | Any | None, dict[str, Any] | None]:
1039
+ """Look up every credential an optimize names, before the file is staged.
1040
+
1041
+ Returns the run's credential, the reflection LM to pass (the model name, wrapped in
1042
+ ``cmpnd.HostedLM`` when ``--reflection-credential`` names its credential) and the
1043
+ credential per binding. :class:`_CredentialLookup` turns each name into a
1044
+ ``cmpnd.Credential``, so a mistyped one fails before the import and the deploy a run
1045
+ makes ahead of its first step.
1046
+ """
1047
+ from ...lm_configuration import HostedLM
1048
+
1049
+ pairs: list[tuple[str, str]] = list(getattr(args, "lm_credential", None) or [])
1050
+ seen: set[str] = set()
1051
+ for binding, _ in pairs:
1052
+ if binding in seen:
1053
+ raise api_client.CliError(
1054
+ f"--lm-credential names the binding {binding!r} more than once. Give each binding one credential."
1055
+ )
1056
+ seen.add(binding)
1057
+ reflection_model = getattr(args, "reflection_model", None)
1058
+ reflection_name = getattr(args, "reflection_credential", None)
1059
+ if reflection_name is not None and reflection_model is None:
1060
+ raise api_client.CliError(
1061
+ "--reflection-credential names the credential the reflection model spends, so it needs --reflection-model."
1062
+ )
1063
+
1064
+ lookup = _CredentialLookup(client)
1065
+ run_name = getattr(args, "credential", None)
1066
+ run = lookup(f"--credential {run_name}", run_name) if run_name is not None else None
1067
+ reflection_lm: str | HostedLM | None = reflection_model
1068
+ if reflection_name is not None and reflection_model is not None:
1069
+ reflection_lm = HostedLM(
1070
+ reflection_model, credential=lookup(f"--reflection-credential {reflection_name}", reflection_name)
1071
+ )
1072
+ lm_credentials = {binding: lookup(f"--lm-credential {binding}={name}", name) for binding, name in pairs}
1073
+ return run, reflection_lm, lm_credentials or None
1074
+
1075
+
981
1076
  def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
982
1077
  """Optimize the program, metric and proposer one authored file binds.
983
1078
 
@@ -1001,6 +1096,11 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
1001
1096
  return dspy.Prediction(score=float(ok),
1002
1097
  feedback="" if ok else f"expected {gold.answer}")
1003
1098
  PY
1099
+
1100
+ The file cannot name a credential, because the kernel that imports it reaches no
1101
+ platform. ``--credential`` names the run's, ``--reflection-credential`` the
1102
+ reflection model's and ``--lm-credential BINDING=NAME`` one of the file's ``lms``.
1103
+ Each name is looked up before the file is staged.
1004
1104
  """
1005
1105
  from pathlib import Path
1006
1106
 
@@ -1008,6 +1108,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
1008
1108
 
1009
1109
  deployment_id = getattr(args, "deployment_id", None)
1010
1110
  program_name = _program_args(args, "optimize", needs_name=deployment_id is None)
1111
+ credential, reflection_lm, lm_credentials = _optimize_credentials(client, args)
1011
1112
  try:
1012
1113
  if args.source == "-" or args.source is None:
1013
1114
  prepared = authored.prepare_optimize_stdin(
@@ -1041,7 +1142,9 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
1041
1142
  # The flag is named for what a command line can carry — a model name,
1042
1143
  # never an LM object — and the parameter for what the Python surface
1043
1144
  # accepts. Each layer's name says what that layer takes.
1044
- reflection_lm=getattr(args, "reflection_model", None),
1145
+ reflection_lm=reflection_lm,
1146
+ credential=credential,
1147
+ lm_credentials=lm_credentials,
1045
1148
  max_metric_calls=getattr(args, "max_metric_calls", None),
1046
1149
  auto=getattr(args, "auto", None),
1047
1150
  max_steps=getattr(args, "max_steps", 10),
@@ -1402,6 +1505,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
1402
1505
  "--reflection-model openai/gpt-4o --auto light\n"
1403
1506
  " cmpnd optimize gepa - capital-qa --reflection-model openai/gpt-4o "
1404
1507
  "--max-metric-calls 60 < task.py\n"
1508
+ " cmpnd optimize gepa task.py capital-qa --reflection-model openai/gpt-4o --auto light "
1509
+ "--credential openai --lm-credential judge=typesafe\n"
1405
1510
  " cmpnd optimize gepa task.py --reflection-model openai/gpt-4o --auto light --deployment-id d_a1b2c3d4\n"
1406
1511
  ),
1407
1512
  formatter_class=raw,
@@ -1438,6 +1543,31 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
1438
1543
  "instructions (required for gepa; the server will not substitute the "
1439
1544
  "deployment's own model)",
1440
1545
  )
1546
+ opt.add_argument(
1547
+ "--credential",
1548
+ metavar="NAME",
1549
+ default=None,
1550
+ help="The provider credential the run spends: the program's model, and every model that names no "
1551
+ "credential of its own (default: the organization's default credential). "
1552
+ "`cmpnd credentials list` shows the names",
1553
+ )
1554
+ opt.add_argument(
1555
+ "--reflection-credential",
1556
+ dest="reflection_credential",
1557
+ metavar="NAME",
1558
+ default=None,
1559
+ help="The credential the reflection model spends instead of the run's",
1560
+ )
1561
+ opt.add_argument(
1562
+ "--lm-credential",
1563
+ dest="lm_credential",
1564
+ metavar="BINDING=NAME",
1565
+ type=_lm_credential_arg,
1566
+ action="append",
1567
+ default=None,
1568
+ help="The credential one LM of the file's `lms` spends instead of the run's, as judge=typesafe. Repeat it "
1569
+ "for each binding",
1570
+ )
1441
1571
  # A budget is mandatory and the optimizer will not choose one — see
1442
1572
  # `cmpnd.optimization._apply_budget`. Two flags rather than one because a
1443
1573
  # caller who does not yet know what a run costs reaches for `--auto`, and one
@@ -508,6 +508,62 @@ def _lms_config(config: dict[str, Any], lms: Any) -> None:
508
508
  config["lms"] = declared
509
509
 
510
510
 
511
+ def _lm_credentials_config(config: dict[str, Any], lm_credentials: Any) -> None:
512
+ """Name the credential each of the run's declared LM bindings spends.
513
+
514
+ An authored file declares its ``lms`` and cannot name a credential for one, because
515
+ the kernel that imports it again reaches no platform to look one up. So the call that
516
+ starts the run names it, by binding name, and the entry gains the ``credential`` field
517
+ that ``cmpnd.HostedLM(lm, credential=)`` and ``cmpnd.SystemOne(model, credential=)``
518
+ give it in a live-object call. The wire shape is the same either way.
519
+
520
+ Each binding must be one the run declares, and must not already name a credential:
521
+ a second credential for one binding is two answers to one question, the refusal
522
+ :func:`_reflection_config` makes of ``reflection_credential``. The program's own
523
+ model spends the run's ``credential=``, so ``default-lm`` is refused here.
524
+
525
+ ``config["lms"]`` is replaced rather than edited, because it may be the dict the
526
+ import child reported, which the caller's prepared file still holds.
527
+ """
528
+ if not lm_credentials:
529
+ return
530
+ if not isinstance(lm_credentials, dict):
531
+ raise ValueError(f"lm_credentials must map binding names to credentials (got {type(lm_credentials).__name__})")
532
+ declared: dict[str, Any] = config.get("lms") or {}
533
+ named: dict[str, str] = {}
534
+ for binding, credential in lm_credentials.items():
535
+ if binding == DEFAULT_LM_BINDING:
536
+ raise ValueError(
537
+ f"{DEFAULT_LM_BINDING!r} is the program's own model, and it spends the run's credential. "
538
+ "Name that credential for the run instead of for a binding."
539
+ )
540
+ name = credentials.name_of(credential, f"lm_credentials[{binding!r}]")
541
+ if name is None:
542
+ continue
543
+ if binding not in declared:
544
+ if not declared:
545
+ raise ValueError(
546
+ f"a credential is named for the LM binding {binding!r}, and the optimize file binds no "
547
+ "`lms`, so the run has no binding to spend it."
548
+ )
549
+ raise ValueError(
550
+ f"a credential is named for the LM binding {binding!r}, and the optimize file's `lms` binds no "
551
+ f"such name. It binds {', '.join(repr(each) for each in sorted(declared))}."
552
+ )
553
+ existing = declared[binding].get("credential")
554
+ if existing is not None:
555
+ raise ValueError(
556
+ f"the LM binding {binding!r} already names the credential {existing!r}, and a second one was "
557
+ f"named for it: these are two answers to which credential it spends. Keep one."
558
+ )
559
+ named[binding] = name
560
+ if named:
561
+ config["lms"] = {
562
+ binding: {**entry, "credential": named[binding]} if binding in named else entry
563
+ for binding, entry in declared.items()
564
+ }
565
+
566
+
511
567
  def _check_metric_call(metric: Callable[..., Any], optimizer: str) -> None: # pyright: ignore[reportUnusedFunction] # called by the authored import child
512
568
  """Check the authored metric against the kernel's positional and named arguments."""
513
569
  signature = inspect.signature(metric)
@@ -938,6 +994,7 @@ def optimize_authored(
938
994
  config: dict[str, Any] | None = None,
939
995
  reflection_lm: str | Any | None = None,
940
996
  lms: dict[str, Any] | None = None,
997
+ lm_credentials: dict[str, credentials.Credential] | None = None,
941
998
  credential: credentials.Credential | None = None,
942
999
  max_metric_calls: int | None = None,
943
1000
  auto: str | None = None,
@@ -972,6 +1029,9 @@ def optimize_authored(
972
1029
  resolves, because that deployment already has its program.
973
1030
 
974
1031
  ``credential`` is the run's credential, as on :func:`optimize_deployment`.
1032
+ ``lm_credentials`` maps a binding the file's ``lms`` declares to the credential it
1033
+ spends instead, which the file itself cannot name. A ``cmpnd.HostedLM`` or
1034
+ ``cmpnd.SystemOne`` names one in a live-object call.
975
1035
  """
976
1036
  check_optimizer(optimizer)
977
1037
  credential_name = credentials.name_of(credential, "optimize_authored(credential=)")
@@ -985,6 +1045,7 @@ def optimize_authored(
985
1045
  config["lms"] = prepared.facts.lms
986
1046
  _reflection_config(config, reflection_lm)
987
1047
  _lms_config(config, lms)
1048
+ _lm_credentials_config(config, lm_credentials)
988
1049
  _apply_budget(config, max_metric_calls=max_metric_calls, auto=auto)
989
1050
 
990
1051
  if prepared.program_bundle is None and deployment_id is not None and program_name is not None:
@@ -194,9 +194,11 @@ model and the field, never the value. Seal the key with
194
194
  `cmpnd credentials create` instead.
195
195
 
196
196
  `--credential NAME` names the provider credential the program's model spends.
197
- Without it, the deployment spends the organization's default. The CLI sends the
198
- name as typed, and the platform refuses a name the organization does not hold
199
- and names the nearest one. The deploy prints the protocol, model and
197
+ Without it, the deployment spends the organization's default. The CLI looks
198
+ the name up before it stages the source. A name the organization does not hold
199
+ fails with the names it does hold and the nearest one, and nothing is sent.
200
+ The platform checks the name again at the deploy, which catches a credential
201
+ an owner revoked in between. The deploy prints the protocol, model and
200
202
  credential of each binding, such as
201
203
  `default-lm: chat_completions openai/deepseek-4.1-flash → luna-exec (https://gw.lunaroute.com)`.
202
204
 
@@ -618,6 +620,27 @@ organization must hold provider credentials for every model the run uses.
618
620
  Named model declarations hold model names and sampling settings, and the CLI
619
621
  refuses a provider key on them.
620
622
 
623
+ A run spends the organization's default credential unless the command names
624
+ another. `--credential NAME` names the run's credential. The program's model
625
+ spends it, and so does every model that names no credential of its own.
626
+ `--reflection-credential NAME` names the credential the reflection model
627
+ spends. `--lm-credential BINDING=NAME` names the credential one model in the
628
+ file's `lms` spends, and repeats once per binding:
629
+
630
+ ```bash
631
+ cmpnd optimize gepa task.py capital-qa --reflection-model openai/gpt-5 --auto light \
632
+ --credential openai --lm-credential judge=typesafe
633
+ ```
634
+
635
+ The file cannot name a credential itself, because the kernel imports it again
636
+ and reaches no platform there. These flags send what `credential=`,
637
+ `cmpnd.HostedLM(lm, credential=)` and `cmpnd.SystemOne(model, credential=)`
638
+ send from Python. The CLI looks up every name before it stages the file. A
639
+ name the organization does not hold fails with the names it does hold and the
640
+ nearest one. The CLI also refuses `--lm-credential` for a binding the file's
641
+ `lms` does not declare, for one binding twice and for `default-lm`. The
642
+ program's own model spends the run's credential, so `--credential` names it.
643
+
621
644
  `cmpnd.Client.optimize_deployment(deployment, metric=fn)` in a notebook packages
622
645
  live objects and takes named models through its `lms` argument. An authored file's `lms`
623
646
  binding cannot be combined with `lms` in the parent call or `config["lms"]`.
@@ -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.58.0"
8
+ version = "0.59.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -33,7 +33,7 @@ set -euo pipefail
33
33
 
34
34
  SDK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
35
35
  REPO_DIR="$(cd "${SDK_DIR}/.." && pwd)"
36
- IMAGE="${SDK_TEST_LINUX_IMAGE:-ghcr.io/astral-sh/uv:python3.12-bookworm-slim}"
36
+ IMAGE="${SDK_TEST_LINUX_IMAGE:-ghcr.io/astral-sh/uv:python3.12-trixie-slim}"
37
37
  ARGS=("$@")
38
38
  if [ ${#ARGS[@]} -eq 0 ]; then
39
39
  ARGS=(tests/ -k "not e2e")
@@ -130,6 +130,30 @@ def read_committed() -> str:
130
130
  return gzip.decompress(FIXTURE_PATH.read_bytes()).decode("utf-8")
131
131
 
132
132
 
133
+ def assert_current(committed: str, fresh: str) -> None:
134
+ """Fail when the committed fixture text differs from the fresh render.
135
+
136
+ The texts are hundreds of kilobytes, and pytest's string diff on them runs
137
+ for minutes. This reports the first differing span and its keys instead.
138
+ The comparison stays exact: equal strings pass and any other pair fails.
139
+ """
140
+ if committed == fresh:
141
+ return
142
+ refresh = f"{FIXTURE_PATH.name} is stale; refresh it with `uv run python -m tests.span_reference_fixture`"
143
+ found = json.loads(committed)["trace"]["spans"]
144
+ wanted = json.loads(fresh)["trace"]["spans"]
145
+ for i, (old, new) in enumerate(zip(found, wanted)):
146
+ if old != new:
147
+ # A sentinel, so a key added or removed with a null value still differs.
148
+ missing = object()
149
+ keys = sorted(k for k in old.keys() | new.keys() if old.get(k, missing) != new.get(k, missing))
150
+ raise AssertionError(f"{refresh}. First differing span {i}, differing keys: {keys}")
151
+ if len(found) != len(wanted):
152
+ raise AssertionError(f"{refresh}. It holds {len(found)} spans and the SDK emits {len(wanted)}")
153
+ offset = next((i for i, (a, b) in enumerate(zip(committed, fresh)) if a != b), min(len(committed), len(fresh)))
154
+ raise AssertionError(f"{refresh}. The spans match; the texts differ outside the spans at character {offset}")
155
+
156
+
133
157
  def write(doc: dict[str, Any]) -> None:
134
158
  # mtime=0 so the same document always compresses to the same bytes.
135
159
  FIXTURE_PATH.write_bytes(gzip.compress(render(doc).encode("utf-8"), mtime=0))