ecadlib 0.3.0__py3-none-any.whl

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 (199) hide show
  1. ecadlib/__init__.py +3 -0
  2. ecadlib/__main__.py +5 -0
  3. ecadlib/a365_sync.py +637 -0
  4. ecadlib/agent.py +1407 -0
  5. ecadlib/agent_queue.py +780 -0
  6. ecadlib/agent_watch.py +732 -0
  7. ecadlib/altium365.py +385 -0
  8. ecadlib/altium_dblib_sync.py +530 -0
  9. ecadlib/altium_export.py +209 -0
  10. ecadlib/altium_forms.py +768 -0
  11. ecadlib/altium_lib.py +1089 -0
  12. ecadlib/altium_menu.py +1751 -0
  13. ecadlib/altium_pkg.py +396 -0
  14. ecadlib/altium_project.py +1095 -0
  15. ecadlib/app.py +1458 -0
  16. ecadlib/approvals.py +395 -0
  17. ecadlib/assets/demo_bom.csv +8 -0
  18. ecadlib/assets/kicad_action_plugin.py +607 -0
  19. ecadlib/assets/kicad_reread.py +60 -0
  20. ecadlib/assets/pcblib_template.json.gz +0 -0
  21. ecadlib/bom_inputs.py +339 -0
  22. ecadlib/bom_triage.py +519 -0
  23. ecadlib/cad_model.py +528 -0
  24. ecadlib/cfb.py +171 -0
  25. ecadlib/cli.py +185 -0
  26. ecadlib/clihelp.py +149 -0
  27. ecadlib/cliutil.py +119 -0
  28. ecadlib/completion.py +173 -0
  29. ecadlib/costs.py +276 -0
  30. ecadlib/crosscheck.py +412 -0
  31. ecadlib/datasheet.py +2168 -0
  32. ecadlib/dedupe.py +572 -0
  33. ecadlib/detect.py +170 -0
  34. ecadlib/doctor.py +450 -0
  35. ecadlib/ds_route.py +541 -0
  36. ecadlib/eagle_export.py +1409 -0
  37. ecadlib/easy_setup.py +338 -0
  38. ecadlib/easyeda_convert.py +386 -0
  39. ecadlib/easyeda_pro_export.py +972 -0
  40. ecadlib/eval/__init__.py +0 -0
  41. ecadlib/eval/baselines/kicad-v1.json +404 -0
  42. ecadlib/eval/build_set.py +299 -0
  43. ecadlib/eval/cli.py +119 -0
  44. ecadlib/eval/gate.py +358 -0
  45. ecadlib/eval/golden/demo.txt +379 -0
  46. ecadlib/eval/golden/specs.txt +3120 -0
  47. ecadlib/eval/golden/testpack.txt +463 -0
  48. ecadlib/eval/golden.py +446 -0
  49. ecadlib/eval/replay/kicad-v1.json.gz +0 -0
  50. ecadlib/eval/runner.py +456 -0
  51. ecadlib/eval/score.py +179 -0
  52. ecadlib/eval/sets/kicad-v1.json +630 -0
  53. ecadlib/firstrun.py +400 -0
  54. ecadlib/footprint_adv.py +1006 -0
  55. ecadlib/footprint_bench.py +218 -0
  56. ecadlib/footprint_check.py +562 -0
  57. ecadlib/footprint_gen.py +582 -0
  58. ecadlib/footprint_rules.py +1092 -0
  59. ecadlib/forge.py +267 -0
  60. ecadlib/fp_share.py +95 -0
  61. ecadlib/fp_tidy.py +389 -0
  62. ecadlib/fsutil.py +62 -0
  63. ecadlib/house_style.py +1051 -0
  64. ecadlib/httpcache.py +124 -0
  65. ecadlib/hub.py +1490 -0
  66. ecadlib/hub_copy.py +161 -0
  67. ecadlib/hub_tools.py +79 -0
  68. ecadlib/kicad_dblib.py +257 -0
  69. ecadlib/kicad_export.py +837 -0
  70. ecadlib/kicad_import.py +913 -0
  71. ecadlib/kicad_index.py +160 -0
  72. ecadlib/kicad_menu.py +193 -0
  73. ecadlib/kicad_pcm.py +475 -0
  74. ecadlib/launcher.py +156 -0
  75. ecadlib/librarian.py +963 -0
  76. ecadlib/lifecycle.py +691 -0
  77. ecadlib/llm/__init__.py +276 -0
  78. ecadlib/llm/cli.py +70 -0
  79. ecadlib/llm/openai_compat.py +214 -0
  80. ecadlib/llm/profiles.py +245 -0
  81. ecadlib/llm/rails.py +570 -0
  82. ecadlib/llm/run.py +126 -0
  83. ecadlib/llm/sampling.py +49 -0
  84. ecadlib/mcp_server.py +723 -0
  85. ecadlib/messages.py +419 -0
  86. ecadlib/migrate.py +407 -0
  87. ecadlib/notify.py +329 -0
  88. ecadlib/package_schema.json +1238 -0
  89. ecadlib/package_specs/CP_EIA-3216-18_Kemet-A.json +27 -0
  90. ecadlib/package_specs/C_0402_1005Metric.json +22 -0
  91. ecadlib/package_specs/C_0805_2012Metric.json +22 -0
  92. ecadlib/package_specs/DFN-8-1EP_3x3mm_P0.5mm_EP1.66x2.38mm.json +40 -0
  93. ecadlib/package_specs/D_SMA.json +27 -0
  94. ecadlib/package_specs/D_SMB.json +27 -0
  95. ecadlib/package_specs/D_SOD-123.json +43 -0
  96. ecadlib/package_specs/D_SOD-323.json +43 -0
  97. ecadlib/package_specs/LED_0603_1608Metric.json +23 -0
  98. ecadlib/package_specs/LED_0805_2012Metric.json +23 -0
  99. ecadlib/package_specs/LQFP-48_7x7mm_P0.5mm.json +31 -0
  100. ecadlib/package_specs/L_0805_2012Metric.json +22 -0
  101. ecadlib/package_specs/MSOP-8_3x3mm_P0.65mm.json +31 -0
  102. ecadlib/package_specs/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm.json +39 -0
  103. ecadlib/package_specs/QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm.json +37 -0
  104. ecadlib/package_specs/R_0402_1005Metric.json +22 -0
  105. ecadlib/package_specs/R_0603_1608Metric.json +22 -0
  106. ecadlib/package_specs/R_0805_2012Metric.json +22 -0
  107. ecadlib/package_specs/R_1206_3216Metric.json +22 -0
  108. ecadlib/package_specs/SOIC-16_3.9x9.9mm_P1.27mm.json +32 -0
  109. ecadlib/package_specs/SOIC-8_3.9x4.9mm_P1.27mm.json +32 -0
  110. ecadlib/package_specs/SOT-223-3_TabPin2.json +56 -0
  111. ecadlib/package_specs/SOT-23-5.json +57 -0
  112. ecadlib/package_specs/SOT-23-6.json +62 -0
  113. ecadlib/package_specs/SOT-23.json +48 -0
  114. ecadlib/package_specs/TSSOP-16_4.4x5mm_P0.65mm.json +31 -0
  115. ecadlib/package_specs/VQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm.json +39 -0
  116. ecadlib/package_specs/advanced/BGA-100_11.0x11.0mm_Layout10x10_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
  117. ecadlib/package_specs/advanced/BGA-121_9.0x9.0mm_Layout11x11_P0.8mm_Ball0.4mm_Pad0.35mm_NSMD.json +26 -0
  118. ecadlib/package_specs/advanced/BGA-144_7.0x7.0mm_Layout13x13_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +29 -0
  119. ecadlib/package_specs/advanced/BGA-169_11.0x11.0mm_Layout13x13_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +23 -0
  120. ecadlib/package_specs/advanced/BGA-256_11.0x11.0mm_Layout20x20_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +26 -0
  121. ecadlib/package_specs/advanced/BGA-256_17.0x17.0mm_Layout16x16_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
  122. ecadlib/package_specs/advanced/BGA-324_15.0x15.0mm_Layout18x18_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
  123. ecadlib/package_specs/advanced/BGA-36_3.396x3.466mm_Layout6x6_P0.4mm_Ball0.25mm_Pad0.2mm_NSMD.json +24 -0
  124. ecadlib/package_specs/advanced/BGA-672_27.0x27.0mm_Layout26x26_P1.0mm_Ball0.6mm_Pad0.5mm_NSMD.json +32 -0
  125. ecadlib/package_specs/advanced/BGA-68_5.0x5.0mm_Layout9x9_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +30 -0
  126. ecadlib/package_specs/advanced/BGA-90_8.0x13.0mm_Layout2x3x15_P0.8mm.json +26 -0
  127. ecadlib/package_specs/advanced/DFN-8-1EP_3x3mm_P0.5mm_EP1.65x2.38mm_ThermalVias.json +60 -0
  128. ecadlib/package_specs/advanced/Example_Custom_LPad_Slot.json +24 -0
  129. ecadlib/package_specs/advanced/JST_GH_SM04B-GHS-TB_1x04-1MP_P1.25mm_Horizontal.json +49 -0
  130. ecadlib/package_specs/advanced/JST_PH_B4B-PH-K_1x04_P2.00mm_Vertical.json +33 -0
  131. ecadlib/package_specs/advanced/JST_PH_S4B-PH-SM4-TB_1x04-1MP_P2.00mm_Horizontal.json +55 -0
  132. ecadlib/package_specs/advanced/JST_SH_SM04B-SRSS-TB_1x04-1MP_P1.00mm_Horizontal.json +49 -0
  133. ecadlib/package_specs/advanced/JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical.json +34 -0
  134. ecadlib/package_specs/advanced/JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical.json +34 -0
  135. ecadlib/package_specs/advanced/JST_XH_B6B-XH-A_1x06_P2.50mm_Vertical.json +33 -0
  136. ecadlib/package_specs/advanced/LFBGA-144_10x10mm_Layout12x12_P0.8mm.json +23 -0
  137. ecadlib/package_specs/advanced/Molex_KK-254_AE-6410-04A_1x04_P2.54mm_Vertical.json +34 -0
  138. ecadlib/package_specs/advanced/Molex_PicoBlade_53398-0471_1x04-1MP_P1.25mm_Vertical.json +54 -0
  139. ecadlib/package_specs/advanced/PinHeader_1x05_P1.00mm_Vertical_SMD_Pin1Left.json +14 -0
  140. ecadlib/package_specs/advanced/PinHeader_1x05_P1.27mm_Vertical_SMD_Pin1Left.json +14 -0
  141. ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical.json +12 -0
  142. ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical_SMD_Pin1Left.json +14 -0
  143. ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical.json +12 -0
  144. ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Left.json +25 -0
  145. ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Right.json +14 -0
  146. ecadlib/package_specs/advanced/PinHeader_1x10_P1.00mm_Vertical.json +15 -0
  147. ecadlib/package_specs/advanced/PinHeader_2x05_P1.00mm_Vertical_SMD.json +13 -0
  148. ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical.json +18 -0
  149. ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical_SMD.json +24 -0
  150. ecadlib/package_specs/advanced/PinHeader_2x05_P2.00mm_Vertical_SMD.json +13 -0
  151. ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical.json +12 -0
  152. ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical_SMD.json +24 -0
  153. ecadlib/package_specs/advanced/PinHeader_2x20_P2.54mm_Vertical.json +12 -0
  154. ecadlib/package_specs/advanced/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm_ThermalVias.json +87 -0
  155. ecadlib/package_specs/advanced/UFBGA-132_7x7mm_Layout12x12_P0.5mm.json +43 -0
  156. ecadlib/package_specs/advanced/USB_C_Receptacle_HRO_TYPE-C-31-M-12.json +218 -0
  157. ecadlib/package_specs/standard.json.gz +0 -0
  158. ecadlib/parity.py +1204 -0
  159. ecadlib/parts_export.py +154 -0
  160. ecadlib/partview.py +215 -0
  161. ecadlib/plugins.py +231 -0
  162. ecadlib/providers.py +226 -0
  163. ecadlib/rebuild.py +471 -0
  164. ecadlib/release_check.py +437 -0
  165. ecadlib/reports.py +274 -0
  166. ecadlib/resolve.py +556 -0
  167. ecadlib/review_cli.py +292 -0
  168. ecadlib/rules.py +979 -0
  169. ecadlib/rules_infer.py +1205 -0
  170. ecadlib/sexpr.py +149 -0
  171. ecadlib/sheetsafe.py +65 -0
  172. ecadlib/sim_models.py +1087 -0
  173. ecadlib/sourcing.py +1161 -0
  174. ecadlib/stdparts.py +755 -0
  175. ecadlib/step_gen.py +929 -0
  176. ecadlib/symbol_layout.py +798 -0
  177. ecadlib/team.py +558 -0
  178. ecadlib/team_setup.py +288 -0
  179. ecadlib/term.py +420 -0
  180. ecadlib/term_views.py +64 -0
  181. ecadlib/testpack.py +334 -0
  182. ecadlib/ui/__init__.py +1 -0
  183. ecadlib/ui/__main__.py +3 -0
  184. ecadlib/ui/server.py +1029 -0
  185. ecadlib/ui/static/hairline.js +2 -0
  186. ecadlib/ui/static/index.html +2098 -0
  187. ecadlib/ui/static/setup.html +294 -0
  188. ecadlib/upkeep.py +717 -0
  189. ecadlib/upstream.py +573 -0
  190. ecadlib/userconfig.py +257 -0
  191. ecadlib/visual_check.py +1603 -0
  192. ecadlib/ws_status.py +203 -0
  193. ecadlib-0.3.0.dist-info/METADATA +2245 -0
  194. ecadlib-0.3.0.dist-info/RECORD +199 -0
  195. ecadlib-0.3.0.dist-info/WHEEL +5 -0
  196. ecadlib-0.3.0.dist-info/entry_points.txt +2 -0
  197. ecadlib-0.3.0.dist-info/licenses/LICENSE +21 -0
  198. ecadlib-0.3.0.dist-info/licenses/NOTICE +53 -0
  199. ecadlib-0.3.0.dist-info/top_level.txt +1 -0
ecadlib/llm/run.py ADDED
@@ -0,0 +1,126 @@
1
+ """One read of a datasheet by any model, with the rails its plan names (used by datasheet.Reader).
2
+
3
+ info(ref, rules) endpoint + profile + rail plan for a model reference (cached, registered for pricing)
4
+ ask(job, info, send, judge, client) -> a Messages-shaped answer with an "ecadlib" record of what ran
5
+
6
+ A frontier model with PDF input and JSON-schema output on Anthropic gets job["body"] exactly as datasheet.build_request
7
+ made it (the same bytes as before this module existed); the only support it may add is a second turn after an answer
8
+ that fails datasheet's checks (feedback_retry), sent as the same request with the answer and the errors appended."""
9
+ from . import _ai, endpoint, key_for, register, profiles as PR, rails as R, sampling as SM
10
+
11
+ _INFO = {}
12
+
13
+
14
+ def _rules_key(rules):
15
+ """The cache key for a rules table: its content. Not id(rules): a freed table's id is reused by the next one, which
16
+ then got the old table's plan (seen as an extra feedback turn with feedback_retry = false, Windows CI)."""
17
+ import json
18
+ try:
19
+ return json.dumps(rules, sort_keys=True, default=str)
20
+ except (TypeError, ValueError):
21
+ return repr(rules)
22
+
23
+
24
+ def info(ref, rules=None, section="datasheet", key=None, get=None, post=None):
25
+ k = (ref, section, _rules_key(rules))
26
+ if k not in _INFO:
27
+ ep = endpoint(rules, section, ref)
28
+ prof = PR.resolve(ep, rules, key_for(ep, key) if ep["provider"] != "anthropic" else None, get, post)
29
+ plan = R.plan(prof, ep["assist"], ep["feedback_retry"])
30
+ register(ep["ref"], ep, prof)
31
+ _INFO[k] = {"ep": ep, "prof": prof, "plan": plan, "sampling": SM.params(ep, prof, _ai(rules).get("temperature"))}
32
+ return _INFO[k]
33
+
34
+
35
+ def plain(inf):
36
+ """Anthropic, and nothing beyond the support layer: the request goes out exactly as built."""
37
+ return inf["ep"]["provider"] == "anthropic" and not inf["plan"]["guided"]
38
+
39
+
40
+ def _synth(ext, usage, model, served=None):
41
+ import json
42
+ return {"id": "ecadlib-steps", "type": "message", "role": "assistant", "model": served or model,
43
+ "content": [{"type": "text", "text": json.dumps(ext)}], "stop_reason": "end_turn", "stop_details": None,
44
+ "usage": usage}
45
+
46
+
47
+ def _better(k1, w1, k2, w2):
48
+ return R.rank(k2) < R.rank(k1) or (R.rank(k2) == R.rank(k1) and len(w2) <= len(w1))
49
+
50
+
51
+ def ask(job, inf, send, judge, client=None, allow_feedback=True):
52
+ """send(body) -> answer for this endpoint (Messages shape; a parse failure is still returned as the answer).
53
+ judge(msg) -> (ok | incomplete | failed | refused | truncated, reasons)."""
54
+ p, prof = inf["plan"], inf["prof"]
55
+ schema = job["body"]["output_config"]["format"]["schema"]
56
+ notes, rec = [], {"tier": p["tier"], "assist": p["assist"], "profile": {k: prof.get(k) for k in
57
+ ("tier", "pdf", "vision", "json_schema", "context", "source", "detect") if prof.get(k) is not None},
58
+ "rails": list(p["rails"]), "endpoint": inf["ep"]["name"], "provider": inf["ep"]["provider"]}
59
+ fb = allow_feedback and "feedback_retry" in p["rails"]
60
+ smp = inf.get("sampling") or {}
61
+ if smp: # temperature / seed where the model takes them (llm.sampling)
62
+ send = (lambda b, _send=send: _send({**b, **smp}))
63
+ rec["sampling"] = dict(smp)
64
+ if "split_steps" in p["rails"]:
65
+ ext, usage, steps, ctx = R.run_steps(client, job["body"], prof, p, job["part"], job["package"], notes)
66
+ served = next((s.get("model_served") for s in steps if s.get("model_served")), None)
67
+ msg = _synth(ext, usage, job["body"]["model"], served)
68
+ kind, why = judge(msg)
69
+ if kind == "failed" and fb:
70
+ from ..datasheet import DatasheetError
71
+ try:
72
+ ext2, u2 = R.redo_step(client, job["body"], prof, p, ctx, steps, ext, why, notes)
73
+ msg2 = _synth(ext2, R.add_usage(usage, u2), job["body"]["model"], served)
74
+ kind2, why2 = judge(msg2)
75
+ rec["feedback"] = {"errors": why[:15], "before": kind, "after": kind2, "kept": "retry" if _better(kind, why, kind2, why2) else "first"}
76
+ msg = msg2 if _better(kind, why, kind2, why2) else dict(msg, usage=msg2["usage"])
77
+ except DatasheetError as e:
78
+ rec["feedback"] = {"errors": why[:15], "before": kind, "after": f"error: {e}", "kept": "first"}
79
+ rec["steps"] = [{k: v for k, v in s.items() if k != "model_served"} for s in steps]
80
+ else:
81
+ body = R.adapt_body(job["body"], prof, p, notes) if p["guided"] else job["body"]
82
+ msg = R.repair_message(send(body), schema, p)
83
+ if fb:
84
+ kind, why = judge(msg)
85
+ if kind == "failed":
86
+ msg2 = R.repair_message(send(R.feedback_body(body, msg, why)), schema, p)
87
+ kind2, why2 = judge(msg2)
88
+ keep = _better(kind, why, kind2, why2)
89
+ rec["feedback"] = {"errors": why[:15], "before": kind, "after": kind2, "kept": "retry" if keep else "first"}
90
+ usage = R.add_usage(msg.get("usage"), msg2.get("usage"))
91
+ msg = dict(msg2 if keep else msg, usage=usage)
92
+ if notes:
93
+ rec["notes"] = notes
94
+ if msg.get("ecadlib", {}).get("repair"):
95
+ rec["repair"] = msg["ecadlib"]["repair"]
96
+ if p["guided"] or rec.get("feedback") or smp:
97
+ msg = dict(msg, ecadlib=rec)
98
+ return msg
99
+
100
+
101
+ def provenance(inf, msg=None, route=None, pages_picked=False):
102
+ """The sidecar's "ai" record: which model, profile and rails produced this part, and which support rails ran."""
103
+ rec = dict((msg or {}).get("ecadlib") or {})
104
+ if not rec:
105
+ p = inf["plan"]
106
+ rec = {"tier": p["tier"], "assist": p["assist"], "rails": list(p["rails"]), "endpoint": inf["ep"]["name"],
107
+ "provider": inf["ep"]["provider"],
108
+ "profile": {k: inf["prof"].get(k) for k in ("tier", "pdf", "vision", "json_schema", "context", "source")}}
109
+ ran = ["checks_gate", "units_in_code", "geometry_in_code", "provenance"]
110
+ if pages_picked:
111
+ ran.append("page_picking")
112
+ if rec.get("feedback"):
113
+ ran.append("feedback_retry")
114
+ if route and len(route.get("attempts") or []) > 1:
115
+ ran.append("escalation")
116
+ rec["support_ran"] = ran
117
+ rec["model"] = inf["ep"]["ref"]
118
+ return rec
119
+
120
+
121
+ def wants(rules, rail, section="datasheet"):
122
+ """Does the configured model's plan include this rail? (False when the setup can't be resolved.)"""
123
+ try:
124
+ return rail in info(endpoint(rules, section)["ref"], rules, section)["plan"]["rails"]
125
+ except Exception:
126
+ return False
@@ -0,0 +1,49 @@
1
+ """Repeatable answers: sampling settings where a provider takes them ([ai] temperature).
2
+
3
+ [ai]
4
+ temperature = "auto" # auto | off | a number from 0 to 2
5
+
6
+ auto: temperature 0 (and a fixed seed on OpenAI-compatible servers, which most of them honour) for models that accept
7
+ a temperature; nothing for models that reject it. Rejected (claude-api skill, Thinking & Effort table, 2026-10): every
8
+ Claude model from Opus 4.7 / Sonnet 5 / Haiku 5.5 on (temperature removed or only the default accepted: a 400
9
+ otherwise), OpenAI's reasoning models (gpt-5*, o1/o3/o4) and DeepSeek's reasoner. So a request to the default model
10
+ (Claude Opus 5.5) is byte-identical with and without this setting; older Claude models (Opus/Sonnet 4.5-4.6,
11
+ Haiku 4.5) get temperature 0. [ai.profiles."<model>"] sampling = true | false overrides the table for one model.
12
+ off: never send a sampling parameter. A number: that temperature, where the model takes one.
13
+
14
+ Repeat runs give the same answer mainly through the result cache (PDF hash + model + pages + prompt/schema version +
15
+ rails): a second run replays the cached answer and pays nothing. The sampling settings are not part of the cache key,
16
+ so changing them never throws away answers already paid for."""
17
+ import re
18
+
19
+ SEED = 7351
20
+ REJECTS = re.compile(r"claude-(?!(opus|sonnet)-4-[56]$|haiku-4-5$).*|gpt-5.*|o[134](-.*)?|.*reasoner.*")
21
+
22
+
23
+ def takes_sampling(model, prof=None):
24
+ if prof and prof.get("sampling") is not None:
25
+ return bool(prof["sampling"])
26
+ from .profiles import bare
27
+ return not REJECTS.fullmatch(bare(model))
28
+
29
+
30
+ def params(ep, prof, setting="auto"):
31
+ """-> {} or {"temperature": t[, "seed": SEED]} for one endpoint + profile."""
32
+ if setting in (None, "", "off") or isinstance(setting, bool):
33
+ return {}
34
+ if not takes_sampling(ep["model"], prof):
35
+ return {}
36
+ t = 0.0 if setting == "auto" else float(setting)
37
+ out = {"temperature": t}
38
+ if ep["provider"] != "anthropic":
39
+ out["seed"] = SEED
40
+ return out
41
+
42
+
43
+ def check(v):
44
+ """Validation message for [ai] temperature, or None."""
45
+ if v in ("auto", "off"):
46
+ return None
47
+ if isinstance(v, (int, float)) and not isinstance(v, bool) and 0 <= v <= 2:
48
+ return None
49
+ return "must be \"auto\", \"off\" or a number from 0 to 2"