coco-framework 0.1.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 (400) hide show
  1. coco/__init__.py +3 -0
  2. coco/agents/__init__.py +9 -0
  3. coco/agents/_subagent_transformer.py +307 -0
  4. coco/agents/factory.py +2164 -0
  5. coco/agents/middleware/__init__.py +100 -0
  6. coco/agents/middleware/_execution.py +385 -0
  7. coco/agents/middleware/_redaction.py +454 -0
  8. coco/agents/middleware/_retry.py +134 -0
  9. coco/agents/middleware/_trace_policy.py +61 -0
  10. coco/agents/middleware/context_editing.py +306 -0
  11. coco/agents/middleware/file_search.py +433 -0
  12. coco/agents/middleware/human_in_the_loop.py +680 -0
  13. coco/agents/middleware/internal_call_transformer.py +108 -0
  14. coco/agents/middleware/model_call_limit.py +267 -0
  15. coco/agents/middleware/model_fallback.py +417 -0
  16. coco/agents/middleware/model_retry.py +327 -0
  17. coco/agents/middleware/pii.py +878 -0
  18. coco/agents/middleware/provider_tool_search.py +307 -0
  19. coco/agents/middleware/shell_tool.py +949 -0
  20. coco/agents/middleware/summarization.py +910 -0
  21. coco/agents/middleware/todo.py +357 -0
  22. coco/agents/middleware/tool_call_limit.py +495 -0
  23. coco/agents/middleware/tool_emulator.py +251 -0
  24. coco/agents/middleware/tool_error.py +209 -0
  25. coco/agents/middleware/tool_retry.py +421 -0
  26. coco/agents/middleware/tool_selection.py +515 -0
  27. coco/agents/middleware/types.py +2201 -0
  28. coco/agents/structured_output.py +472 -0
  29. coco/chat_models/__init__.py +7 -0
  30. coco/chat_models/base.py +1082 -0
  31. coco/embeddings/__init__.py +18 -0
  32. coco/embeddings/base.py +279 -0
  33. coco/mcp/__init__.py +36 -0
  34. coco/mcp/adapter.py +245 -0
  35. coco/mcp/elicitation.py +394 -0
  36. coco/mcp/tools.py +307 -0
  37. coco/messages/__init__.py +73 -0
  38. coco/py.typed +0 -0
  39. coco/rate_limiters/__init__.py +13 -0
  40. coco/tools/__init__.py +22 -0
  41. coco/tools/tool_node.py +20 -0
  42. coco_anthropic/__init__.py +15 -0
  43. coco_anthropic/_client_utils.py +81 -0
  44. coco_anthropic/_compat.py +228 -0
  45. coco_anthropic/_sdk_compat.py +124 -0
  46. coco_anthropic/_version.py +3 -0
  47. coco_anthropic/chat_models.py +3349 -0
  48. coco_anthropic/data/__init__.py +1 -0
  49. coco_anthropic/data/_profiles.py +491 -0
  50. coco_anthropic/data/profile_augmentations.toml +69 -0
  51. coco_anthropic/experimental.py +140 -0
  52. coco_anthropic/llms.py +428 -0
  53. coco_anthropic/middleware/__init__.py +25 -0
  54. coco_anthropic/middleware/anthropic_tools.py +1197 -0
  55. coco_anthropic/middleware/bash.py +84 -0
  56. coco_anthropic/middleware/file_search.py +360 -0
  57. coco_anthropic/middleware/prompt_caching.py +276 -0
  58. coco_anthropic/output_parsers.py +93 -0
  59. coco_anthropic/py.typed +0 -0
  60. coco_core/__init__.py +20 -0
  61. coco_core/_api/__init__.py +87 -0
  62. coco_core/_api/beta_decorator.py +266 -0
  63. coco_core/_api/deprecation.py +637 -0
  64. coco_core/_api/internal.py +23 -0
  65. coco_core/_api/path.py +50 -0
  66. coco_core/_import_utils.py +41 -0
  67. coco_core/_protocol.py +625 -0
  68. coco_core/_security/__init__.py +36 -0
  69. coco_core/_security/_exceptions.py +9 -0
  70. coco_core/_security/_policy.py +313 -0
  71. coco_core/_security/_ssrf_protection.py +155 -0
  72. coco_core/_security/_transport.py +254 -0
  73. coco_core/agents.py +258 -0
  74. coco_core/caches.py +272 -0
  75. coco_core/callbacks/__init__.py +132 -0
  76. coco_core/callbacks/base.py +1229 -0
  77. coco_core/callbacks/file.py +266 -0
  78. coco_core/callbacks/manager.py +2826 -0
  79. coco_core/callbacks/stdout.py +123 -0
  80. coco_core/callbacks/streaming_stdout.py +154 -0
  81. coco_core/callbacks/usage.py +124 -0
  82. coco_core/chat_history.py +267 -0
  83. coco_core/chat_loaders.py +26 -0
  84. coco_core/chat_sessions.py +19 -0
  85. coco_core/cross_encoders.py +18 -0
  86. coco_core/document_loaders/__init__.py +36 -0
  87. coco_core/document_loaders/base.py +155 -0
  88. coco_core/document_loaders/blob_loaders.py +38 -0
  89. coco_core/documents/__init__.py +55 -0
  90. coco_core/documents/base.py +347 -0
  91. coco_core/documents/compressor.py +74 -0
  92. coco_core/documents/transformers.py +79 -0
  93. coco_core/embeddings/__init__.py +31 -0
  94. coco_core/embeddings/embeddings.py +78 -0
  95. coco_core/embeddings/fake.py +129 -0
  96. coco_core/env.py +22 -0
  97. coco_core/example_selectors/__init__.py +47 -0
  98. coco_core/example_selectors/base.py +60 -0
  99. coco_core/example_selectors/length_based.py +131 -0
  100. coco_core/example_selectors/semantic_similarity.py +366 -0
  101. coco_core/exceptions.py +166 -0
  102. coco_core/globals.py +72 -0
  103. coco_core/indexing/__init__.py +53 -0
  104. coco_core/indexing/api.py +954 -0
  105. coco_core/indexing/base.py +661 -0
  106. coco_core/indexing/in_memory.py +104 -0
  107. coco_core/language_models/__init__.py +116 -0
  108. coco_core/language_models/_compat_bridge.py +844 -0
  109. coco_core/language_models/_utils.py +343 -0
  110. coco_core/language_models/base.py +500 -0
  111. coco_core/language_models/chat_model_stream.py +1525 -0
  112. coco_core/language_models/chat_models.py +2748 -0
  113. coco_core/language_models/fake.py +137 -0
  114. coco_core/language_models/fake_chat_models.py +396 -0
  115. coco_core/language_models/llms.py +1569 -0
  116. coco_core/language_models/model_profile.py +183 -0
  117. coco_core/load/__init__.py +44 -0
  118. coco_core/load/_validation.py +191 -0
  119. coco_core/load/dump.py +120 -0
  120. coco_core/load/load.py +828 -0
  121. coco_core/load/mapping.py +1097 -0
  122. coco_core/load/serializable.py +406 -0
  123. coco_core/messages/__init__.py +198 -0
  124. coco_core/messages/ai.py +854 -0
  125. coco_core/messages/base.py +518 -0
  126. coco_core/messages/block_translators/__init__.py +112 -0
  127. coco_core/messages/block_translators/anthropic.py +534 -0
  128. coco_core/messages/block_translators/bedrock.py +108 -0
  129. coco_core/messages/block_translators/bedrock_converse.py +329 -0
  130. coco_core/messages/block_translators/coco_v0.py +303 -0
  131. coco_core/messages/block_translators/google_genai.py +610 -0
  132. coco_core/messages/block_translators/google_vertexai.py +21 -0
  133. coco_core/messages/block_translators/groq.py +157 -0
  134. coco_core/messages/block_translators/openai.py +1089 -0
  135. coco_core/messages/chat.py +64 -0
  136. coco_core/messages/content.py +1488 -0
  137. coco_core/messages/function.py +62 -0
  138. coco_core/messages/human.py +70 -0
  139. coco_core/messages/modifier.py +33 -0
  140. coco_core/messages/system.py +70 -0
  141. coco_core/messages/tool.py +418 -0
  142. coco_core/messages/utils.py +2442 -0
  143. coco_core/output_parsers/__init__.py +101 -0
  144. coco_core/output_parsers/base.py +361 -0
  145. coco_core/output_parsers/format_instructions.py +16 -0
  146. coco_core/output_parsers/json.py +139 -0
  147. coco_core/output_parsers/list.py +249 -0
  148. coco_core/output_parsers/openai_functions.py +324 -0
  149. coco_core/output_parsers/openai_tools.py +383 -0
  150. coco_core/output_parsers/pydantic.py +141 -0
  151. coco_core/output_parsers/string.py +63 -0
  152. coco_core/output_parsers/transform.py +175 -0
  153. coco_core/output_parsers/xml.py +301 -0
  154. coco_core/outputs/__init__.py +66 -0
  155. coco_core/outputs/chat_generation.py +147 -0
  156. coco_core/outputs/chat_result.py +39 -0
  157. coco_core/outputs/generation.py +80 -0
  158. coco_core/outputs/llm_result.py +112 -0
  159. coco_core/outputs/run_info.py +22 -0
  160. coco_core/prompt_values.py +161 -0
  161. coco_core/prompts/__init__.py +101 -0
  162. coco_core/prompts/base.py +508 -0
  163. coco_core/prompts/chat.py +1495 -0
  164. coco_core/prompts/dict.py +177 -0
  165. coco_core/prompts/few_shot.py +483 -0
  166. coco_core/prompts/few_shot_with_templates.py +239 -0
  167. coco_core/prompts/image.py +177 -0
  168. coco_core/prompts/loading.py +296 -0
  169. coco_core/prompts/message.py +97 -0
  170. coco_core/prompts/prompt.py +312 -0
  171. coco_core/prompts/string.py +399 -0
  172. coco_core/prompts/structured.py +214 -0
  173. coco_core/py.typed +0 -0
  174. coco_core/rate_limiters.py +254 -0
  175. coco_core/retrievers.py +328 -0
  176. coco_core/runnables/__init__.py +136 -0
  177. coco_core/runnables/base.py +6725 -0
  178. coco_core/runnables/branch.py +457 -0
  179. coco_core/runnables/config.py +712 -0
  180. coco_core/runnables/configurable.py +710 -0
  181. coco_core/runnables/fallbacks.py +663 -0
  182. coco_core/runnables/graph.py +741 -0
  183. coco_core/runnables/graph_ascii.py +366 -0
  184. coco_core/runnables/graph_mermaid.py +503 -0
  185. coco_core/runnables/graph_png.py +215 -0
  186. coco_core/runnables/history.py +631 -0
  187. coco_core/runnables/passthrough.py +849 -0
  188. coco_core/runnables/retry.py +379 -0
  189. coco_core/runnables/router.py +239 -0
  190. coco_core/runnables/schema.py +188 -0
  191. coco_core/runnables/utils.py +798 -0
  192. coco_core/stores.py +291 -0
  193. coco_core/structured_query.py +203 -0
  194. coco_core/sys_info.py +154 -0
  195. coco_core/tools/__init__.py +95 -0
  196. coco_core/tools/base.py +1948 -0
  197. coco_core/tools/convert.py +497 -0
  198. coco_core/tools/render.py +67 -0
  199. coco_core/tools/retriever.py +94 -0
  200. coco_core/tools/simple.py +208 -0
  201. coco_core/tools/structured.py +322 -0
  202. coco_core/tracers/__init__.py +47 -0
  203. coco_core/tracers/_compat.py +95 -0
  204. coco_core/tracers/_context.py +337 -0
  205. coco_core/tracers/_streaming.py +53 -0
  206. coco_core/tracers/base.py +955 -0
  207. coco_core/tracers/coco.py +279 -0
  208. coco_core/tracers/context.py +205 -0
  209. coco_core/tracers/core.py +766 -0
  210. coco_core/tracers/event_stream.py +1101 -0
  211. coco_core/tracers/log_stream.py +765 -0
  212. coco_core/tracers/memory_stream.py +200 -0
  213. coco_core/tracers/root_listeners.py +130 -0
  214. coco_core/tracers/run_collector.py +40 -0
  215. coco_core/tracers/schemas.py +14 -0
  216. coco_core/tracers/stdout.py +205 -0
  217. coco_core/utils/__init__.py +111 -0
  218. coco_core/utils/_gateway.py +274 -0
  219. coco_core/utils/_merge.py +218 -0
  220. coco_core/utils/aiter.py +357 -0
  221. coco_core/utils/env.py +86 -0
  222. coco_core/utils/formatting.py +80 -0
  223. coco_core/utils/function_calling.py +869 -0
  224. coco_core/utils/html.py +132 -0
  225. coco_core/utils/image.py +15 -0
  226. coco_core/utils/input.py +82 -0
  227. coco_core/utils/interactive_env.py +12 -0
  228. coco_core/utils/iter.py +229 -0
  229. coco_core/utils/json.py +230 -0
  230. coco_core/utils/json_schema.py +273 -0
  231. coco_core/utils/mustache.py +706 -0
  232. coco_core/utils/pydantic.py +631 -0
  233. coco_core/utils/strings.py +69 -0
  234. coco_core/utils/usage.py +61 -0
  235. coco_core/utils/utils.py +528 -0
  236. coco_core/utils/uuid.py +57 -0
  237. coco_core/vectorstores/__init__.py +53 -0
  238. coco_core/vectorstores/base.py +1111 -0
  239. coco_core/vectorstores/in_memory.py +546 -0
  240. coco_core/vectorstores/utils.py +163 -0
  241. coco_core/version.py +3 -0
  242. coco_framework-0.1.0.dist-info/METADATA +184 -0
  243. coco_framework-0.1.0.dist-info/RECORD +400 -0
  244. coco_framework-0.1.0.dist-info/WHEEL +4 -0
  245. coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
  246. coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
  247. coco_graph/_internal/__init__.py +4 -0
  248. coco_graph/_internal/_cache.py +31 -0
  249. coco_graph/_internal/_config.py +474 -0
  250. coco_graph/_internal/_constants.py +140 -0
  251. coco_graph/_internal/_fields.py +213 -0
  252. coco_graph/_internal/_future.py +220 -0
  253. coco_graph/_internal/_pydantic.py +275 -0
  254. coco_graph/_internal/_queue.py +124 -0
  255. coco_graph/_internal/_replay.py +90 -0
  256. coco_graph/_internal/_retry.py +29 -0
  257. coco_graph/_internal/_runnable.py +969 -0
  258. coco_graph/_internal/_scratchpad.py +19 -0
  259. coco_graph/_internal/_serde.py +253 -0
  260. coco_graph/_internal/_timeout.py +25 -0
  261. coco_graph/_internal/_typing.py +54 -0
  262. coco_graph/cache/base/__init__.py +48 -0
  263. coco_graph/cache/base/py.typed +0 -0
  264. coco_graph/cache/memory/__init__.py +73 -0
  265. coco_graph/cache/redis/__init__.py +144 -0
  266. coco_graph/cache/sqlite/__init__.py +120 -0
  267. coco_graph/callbacks.py +394 -0
  268. coco_graph/channels/__init__.py +29 -0
  269. coco_graph/channels/any_value.py +72 -0
  270. coco_graph/channels/base.py +121 -0
  271. coco_graph/channels/binop.py +155 -0
  272. coco_graph/channels/delta.py +202 -0
  273. coco_graph/channels/ephemeral_value.py +79 -0
  274. coco_graph/channels/last_value.py +151 -0
  275. coco_graph/channels/named_barrier_value.py +167 -0
  276. coco_graph/channels/topic.py +94 -0
  277. coco_graph/channels/untracked_value.py +73 -0
  278. coco_graph/checkpoint/base/__init__.py +861 -0
  279. coco_graph/checkpoint/base/id.py +109 -0
  280. coco_graph/checkpoint/base/py.typed +0 -0
  281. coco_graph/checkpoint/memory/__init__.py +698 -0
  282. coco_graph/checkpoint/memory/py.typed +0 -0
  283. coco_graph/checkpoint/postgres/__init__.py +602 -0
  284. coco_graph/checkpoint/postgres/_ainternal.py +23 -0
  285. coco_graph/checkpoint/postgres/_internal.py +21 -0
  286. coco_graph/checkpoint/postgres/aio.py +691 -0
  287. coco_graph/checkpoint/postgres/base.py +666 -0
  288. coco_graph/checkpoint/postgres/py.typed +0 -0
  289. coco_graph/checkpoint/postgres/shallow.py +967 -0
  290. coco_graph/checkpoint/serde/__init__.py +0 -0
  291. coco_graph/checkpoint/serde/_msgpack.py +97 -0
  292. coco_graph/checkpoint/serde/base.py +64 -0
  293. coco_graph/checkpoint/serde/encrypted.py +80 -0
  294. coco_graph/checkpoint/serde/event_hooks.py +52 -0
  295. coco_graph/checkpoint/serde/jsonplus.py +883 -0
  296. coco_graph/checkpoint/serde/py.typed +0 -0
  297. coco_graph/checkpoint/serde/types.py +68 -0
  298. coco_graph/checkpoint/sqlite/__init__.py +645 -0
  299. coco_graph/checkpoint/sqlite/_delta.py +172 -0
  300. coco_graph/checkpoint/sqlite/aio.py +738 -0
  301. coco_graph/checkpoint/sqlite/py.typed +0 -0
  302. coco_graph/checkpoint/sqlite/utils.py +116 -0
  303. coco_graph/config.py +196 -0
  304. coco_graph/constants.py +64 -0
  305. coco_graph/errors.py +241 -0
  306. coco_graph/func/__init__.py +620 -0
  307. coco_graph/graph/__init__.py +12 -0
  308. coco_graph/graph/_branch.py +225 -0
  309. coco_graph/graph/_node.py +103 -0
  310. coco_graph/graph/message.py +437 -0
  311. coco_graph/graph/state.py +1978 -0
  312. coco_graph/graph/ui.py +227 -0
  313. coco_graph/managed/__init__.py +3 -0
  314. coco_graph/managed/base.py +31 -0
  315. coco_graph/managed/is_last_step.py +24 -0
  316. coco_graph/prebuilt/__init__.py +23 -0
  317. coco_graph/prebuilt/_tool_call_stream.py +117 -0
  318. coco_graph/prebuilt/_tool_call_transformer.py +165 -0
  319. coco_graph/prebuilt/chat_agent_executor.py +1015 -0
  320. coco_graph/prebuilt/interrupt.py +105 -0
  321. coco_graph/prebuilt/py.typed +0 -0
  322. coco_graph/prebuilt/tool_node.py +2030 -0
  323. coco_graph/prebuilt/tool_validator.py +221 -0
  324. coco_graph/pregel/__init__.py +3 -0
  325. coco_graph/pregel/_algo.py +1460 -0
  326. coco_graph/pregel/_call.py +298 -0
  327. coco_graph/pregel/_checkpoint.py +331 -0
  328. coco_graph/pregel/_config.py +0 -0
  329. coco_graph/pregel/_draw.py +294 -0
  330. coco_graph/pregel/_executor.py +223 -0
  331. coco_graph/pregel/_io.py +174 -0
  332. coco_graph/pregel/_log.py +3 -0
  333. coco_graph/pregel/_loop.py +1988 -0
  334. coco_graph/pregel/_messages.py +461 -0
  335. coco_graph/pregel/_read.py +312 -0
  336. coco_graph/pregel/_retry.py +854 -0
  337. coco_graph/pregel/_runner.py +941 -0
  338. coco_graph/pregel/_tools.py +268 -0
  339. coco_graph/pregel/_utils.py +221 -0
  340. coco_graph/pregel/_validate.py +120 -0
  341. coco_graph/pregel/_write.py +192 -0
  342. coco_graph/pregel/debug.py +302 -0
  343. coco_graph/pregel/main.py +4364 -0
  344. coco_graph/pregel/protocol.py +288 -0
  345. coco_graph/pregel/types.py +38 -0
  346. coco_graph/py.typed +0 -0
  347. coco_graph/runtime.py +350 -0
  348. coco_graph/store/base/__init__.py +1322 -0
  349. coco_graph/store/base/batch.py +371 -0
  350. coco_graph/store/base/embed.py +436 -0
  351. coco_graph/store/base/py.typed +0 -0
  352. coco_graph/store/memory/__init__.py +592 -0
  353. coco_graph/store/memory/py.typed +0 -0
  354. coco_graph/store/postgres/__init__.py +4 -0
  355. coco_graph/store/postgres/aio.py +592 -0
  356. coco_graph/store/postgres/base.py +1480 -0
  357. coco_graph/store/postgres/py.typed +0 -0
  358. coco_graph/store/sqlite/__init__.py +4 -0
  359. coco_graph/store/sqlite/aio.py +632 -0
  360. coco_graph/store/sqlite/base.py +1514 -0
  361. coco_graph/stream/__init__.py +45 -0
  362. coco_graph/stream/_convert.py +32 -0
  363. coco_graph/stream/_mux.py +523 -0
  364. coco_graph/stream/_types.py +330 -0
  365. coco_graph/stream/run_stream.py +735 -0
  366. coco_graph/stream/stream_channel.py +341 -0
  367. coco_graph/stream/transformers.py +1039 -0
  368. coco_graph/types.py +1079 -0
  369. coco_graph/typing.py +48 -0
  370. coco_graph/utils/__init__.py +1 -0
  371. coco_graph/utils/config.py +4 -0
  372. coco_graph/utils/runnable.py +3 -0
  373. coco_graph/version.py +13 -0
  374. coco_graph/warnings.py +69 -0
  375. coco_openai/__init__.py +20 -0
  376. coco_openai/_compat.py +45 -0
  377. coco_openai/_version.py +3 -0
  378. coco_openai/chat_models/__init__.py +6 -0
  379. coco_openai/chat_models/_client_utils.py +684 -0
  380. coco_openai/chat_models/_compat.py +525 -0
  381. coco_openai/chat_models/azure.py +1227 -0
  382. coco_openai/chat_models/base.py +5654 -0
  383. coco_openai/chat_models/codex.py +557 -0
  384. coco_openai/chatgpt_oauth.py +1063 -0
  385. coco_openai/data/__init__.py +1 -0
  386. coco_openai/data/_profiles.py +1655 -0
  387. coco_openai/data/profile_augmentations.toml +127 -0
  388. coco_openai/embeddings/__init__.py +6 -0
  389. coco_openai/embeddings/azure.py +240 -0
  390. coco_openai/embeddings/base.py +820 -0
  391. coco_openai/llms/__init__.py +6 -0
  392. coco_openai/llms/azure.py +249 -0
  393. coco_openai/llms/base.py +919 -0
  394. coco_openai/middleware/__init__.py +11 -0
  395. coco_openai/middleware/openai_moderation.py +484 -0
  396. coco_openai/output_parsers/__init__.py +9 -0
  397. coco_openai/output_parsers/tools.py +9 -0
  398. coco_openai/py.typed +0 -0
  399. coco_openai/tools/__init__.py +5 -0
  400. coco_openai/tools/custom_tool.py +111 -0
@@ -0,0 +1,341 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections import deque
5
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
6
+ from typing import TYPE_CHECKING, Generic, TypeVar
7
+
8
+ if TYPE_CHECKING:
9
+ from coco_graph.stream._mux import StreamMux
10
+
11
+ T = TypeVar("T")
12
+
13
+
14
+ class StreamChannel(Generic[T]):
15
+ """Single-consumer drainable queue for streaming events, with optional
16
+ protocol auto-forwarding.
17
+
18
+ When constructed with a `name`, the StreamMux auto-wires every
19
+ `push()` to also inject a `ProtocolEvent` into the main event stream
20
+ using the channel's name as the method. When constructed without a
21
+ name, the channel is local-only — items are only visible to
22
+ in-process consumers that iterate the channel directly.
23
+
24
+ Items are popped off the front as the consumer advances — there is
25
+ no retention beyond what's currently queued. A channel accepts
26
+ exactly one subscriber; a second `__iter__` / `__aiter__` call
27
+ raises. Use `tee(n)` / `atee(n)` for fan-out.
28
+
29
+ Starts unbound — neither `__iter__` nor `__aiter__` is available
30
+ until the StreamMux calls `_bind(is_async)`. After binding, only
31
+ the matching iteration protocol works; the other raises `TypeError`.
32
+
33
+ Pump wiring (set by the run stream, not by `_bind`):
34
+ - `_request_more`: sync pump callable, returns True if a new
35
+ event was produced.
36
+ - `_arequest_more`: async pump coroutine factory, same contract.
37
+
38
+ Memory is bounded by caller pace: both sync and async use caller-
39
+ driven pumps, so each cursor advance produces at most one event.
40
+
41
+ Lazy-subscribe: `push` appends to the local buffer only when a
42
+ subscriber has registered. Auto-forward via `_wire_fn` always fires
43
+ regardless of subscription state.
44
+
45
+ Lifecycle (`close` / `fail`) is managed by the mux — transformers
46
+ don't need to close their channels manually.
47
+ """
48
+
49
+ def __init__(self, name: str | None = None, *, maxlen: int | None = None) -> None:
50
+ """Initialize the channel.
51
+
52
+ Args:
53
+ name: Optional protocol channel name. When set, the
54
+ StreamMux wires every `push()` to also inject a
55
+ `ProtocolEvent` into the main event stream. Surfaced
56
+ on the wire as `custom:<name>` for user-defined
57
+ transformers, or as `<name>` for channels owned by a
58
+ native transformer (`_native = True`). When `None`,
59
+ the channel is local-only.
60
+ maxlen: Accepted for forward compatibility; currently
61
+ unused. The caller-driven pump bounds memory naturally
62
+ for single-consumer use.
63
+
64
+ Raises:
65
+ ValueError: If `maxlen` is not a positive integer or `None`.
66
+ """
67
+ if maxlen is not None and maxlen <= 0:
68
+ raise ValueError("StreamChannel maxlen must be a positive int or None")
69
+ self.name = name
70
+ self._items: deque[tuple[int, T]] = deque()
71
+ self._maxlen: int | None = maxlen
72
+ self._closed = False
73
+ self._error: BaseException | None = None
74
+
75
+ self._is_async: bool | None = None
76
+
77
+ self._subscribed = False
78
+
79
+ self._request_more: Callable[[], bool] | None = None
80
+ self._arequest_more: Callable[[], Awaitable[bool]] | None = None
81
+
82
+ self._wire_fn: Callable[[T], None] | None = None
83
+ self._mux: StreamMux | None = None
84
+
85
+ # ------------------------------------------------------------------
86
+ # Binding
87
+ # ------------------------------------------------------------------
88
+
89
+ def _bind_mux(self, mux: StreamMux) -> None:
90
+ self._mux = mux
91
+
92
+ def _bind(self, *, is_async: bool) -> None:
93
+ """Bind this channel to sync or async mode.
94
+
95
+ Called by the StreamMux after transformer registration. Must be
96
+ called exactly once before any iteration.
97
+
98
+ Args:
99
+ is_async: True to enable async iteration, False for sync.
100
+
101
+ Raises:
102
+ RuntimeError: If the channel has already been bound.
103
+ """
104
+ if self._is_async is not None:
105
+ raise RuntimeError("StreamChannel is already bound")
106
+ self._is_async = is_async
107
+
108
+ # ------------------------------------------------------------------
109
+ # Mux wiring (not called by transformers directly)
110
+ # ------------------------------------------------------------------
111
+
112
+ def _wire(self, fn: Callable[[T], None]) -> None:
113
+ """Install the auto-forward callback (called by StreamMux)."""
114
+ self._wire_fn = fn
115
+
116
+ # ------------------------------------------------------------------
117
+ # Producer API
118
+ # ------------------------------------------------------------------
119
+
120
+ def push(self, item: T) -> None:
121
+ """Append an item. Auto-forwards if wired.
122
+
123
+ The local buffer append is a no-op when no subscriber is
124
+ registered, but auto-forwarding always fires so wired events
125
+ reach the main event log regardless of subscription state.
126
+
127
+ Items are stored as `(stamp, item)` tuples where stamp is a
128
+ monotonic counter from the owning mux. Stamps are stripped by
129
+ the default cursors; raw stamped tuples are visible on `_items`.
130
+
131
+ Raises:
132
+ RuntimeError: If the channel is closed (and subscribed).
133
+ """
134
+ if self._subscribed:
135
+ if self._closed:
136
+ raise RuntimeError("Cannot push to a closed StreamChannel")
137
+ stamp = self._mux._next_push_seq() if self._mux is not None else 0
138
+ self._items.append((stamp, item))
139
+ if self._wire_fn is not None:
140
+ self._wire_fn(item)
141
+
142
+ def close(self) -> None:
143
+ """Mark the channel as complete."""
144
+ self._closed = True
145
+
146
+ def fail(self, err: BaseException) -> None:
147
+ """Mark the channel as errored.
148
+
149
+ Args:
150
+ err: The exception to surface to the subscriber.
151
+ """
152
+ self._error = err
153
+ self._closed = True
154
+
155
+ # ------------------------------------------------------------------
156
+ # Sync iteration (caller-driven pump)
157
+ # ------------------------------------------------------------------
158
+
159
+ def __iter__(self) -> Iterator[T]:
160
+ """Subscribe and return a sync cursor. Can be called only once.
161
+
162
+ Raises:
163
+ TypeError: If the channel is unbound or bound to async mode.
164
+ RuntimeError: If the channel already has a subscriber.
165
+ """
166
+ if self._is_async is None:
167
+ raise TypeError(
168
+ "StreamChannel has not been bound yet. "
169
+ "Register the transformer with a StreamMux first."
170
+ )
171
+ if self._is_async:
172
+ raise TypeError(
173
+ "This StreamChannel is bound to async mode — use 'async for' instead."
174
+ )
175
+ if self._subscribed:
176
+ raise RuntimeError(
177
+ "StreamChannel already has a subscriber; use .tee(n) for fan-out."
178
+ )
179
+ self._subscribed = True
180
+ return self._sync_cursor()
181
+
182
+ def _sync_cursor(self) -> Iterator[T]:
183
+ while True:
184
+ if self._items:
185
+ _stamp, item = self._items.popleft()
186
+ yield item
187
+ elif self._closed:
188
+ if self._error is not None:
189
+ raise self._error
190
+ return
191
+ elif self._request_more is not None:
192
+ if not self._request_more():
193
+ if not self._items and not self._closed:
194
+ return
195
+ else:
196
+ return
197
+
198
+ # ------------------------------------------------------------------
199
+ # Async iteration (caller-driven pump)
200
+ # ------------------------------------------------------------------
201
+
202
+ def __aiter__(self) -> AsyncIterator[T]:
203
+ """Subscribe and return an async cursor. Can be called only once.
204
+
205
+ Raises:
206
+ TypeError: If the channel is unbound or bound to sync mode.
207
+ RuntimeError: If the channel already has a subscriber.
208
+ """
209
+ if self._is_async is None:
210
+ raise TypeError(
211
+ "StreamChannel has not been bound yet. "
212
+ "Register the transformer with a StreamMux first."
213
+ )
214
+ if not self._is_async:
215
+ raise TypeError(
216
+ "This StreamChannel is bound to sync mode — use 'for' instead."
217
+ )
218
+ if self._subscribed:
219
+ raise RuntimeError(
220
+ "StreamChannel already has a subscriber; use .atee(n) for fan-out."
221
+ )
222
+ self._subscribed = True
223
+ return self._async_cursor()
224
+
225
+ async def _async_cursor(self) -> AsyncIterator[T]:
226
+ while True:
227
+ if self._items:
228
+ _stamp, item = self._items.popleft()
229
+ yield item
230
+ elif self._closed:
231
+ if self._error is not None:
232
+ raise self._error
233
+ return
234
+ elif self._arequest_more is not None:
235
+ if not await self._arequest_more():
236
+ if not self._items and not self._closed:
237
+ return
238
+ else:
239
+ return
240
+
241
+ # ------------------------------------------------------------------
242
+ # Fan-out via tee
243
+ # ------------------------------------------------------------------
244
+
245
+ def tee(self, n: int = 2) -> tuple[Iterator[T], ...]:
246
+ """Subscribe and return `n` independent sync iterators.
247
+
248
+ Each branch has its own buffer; items pulled from the
249
+ underlying cursor are copied into every branch. Branches are
250
+ naturally bounded by caller pace since the sync pump is
251
+ caller-driven.
252
+
253
+ Args:
254
+ n: Number of branches to create. Must be >= 1.
255
+
256
+ Returns:
257
+ A tuple of `n` iterators over the same underlying stream.
258
+
259
+ Raises:
260
+ TypeError: If the channel is unbound or bound to async mode.
261
+ RuntimeError: If the channel already has a subscriber.
262
+ ValueError: If `n` < 1.
263
+ """
264
+ if n < 1:
265
+ raise ValueError("tee() requires n >= 1")
266
+ source = self.__iter__()
267
+ buffers: list[deque[T]] = [deque() for _ in range(n)]
268
+ exhausted = [False]
269
+
270
+ def branch(i: int) -> Iterator[T]:
271
+ buf = buffers[i]
272
+ while True:
273
+ if buf:
274
+ yield buf.popleft()
275
+ elif exhausted[0]:
276
+ return
277
+ else:
278
+ try:
279
+ item = next(source)
280
+ except StopIteration:
281
+ exhausted[0] = True
282
+ return
283
+ for b in buffers:
284
+ b.append(item)
285
+
286
+ return tuple(branch(i) for i in range(n))
287
+
288
+ def atee(self, n: int = 2) -> tuple[AsyncIterator[T], ...]:
289
+ """Subscribe and return `n` independent async iterators.
290
+
291
+ Caller-driven fan-out: each branch's `__anext__` either pops
292
+ from its own buffer or, under a shared `asyncio.Lock`, pulls
293
+ one item from the underlying cursor and distributes it to
294
+ every branch's buffer.
295
+
296
+ Args:
297
+ n: Number of branches to create. Must be >= 1.
298
+
299
+ Returns:
300
+ A tuple of `n` async iterators over the same underlying
301
+ stream.
302
+
303
+ Raises:
304
+ TypeError: If the channel is unbound or bound to sync mode.
305
+ RuntimeError: If the channel already has a subscriber.
306
+ ValueError: If `n` < 1.
307
+ """
308
+ if n < 1:
309
+ raise ValueError("atee() requires n >= 1")
310
+ source = self.__aiter__()
311
+ buffers: list[deque[T]] = [deque() for _ in range(n)]
312
+ exhausted = [False]
313
+ error: list[BaseException | None] = [None]
314
+ lock = asyncio.Lock()
315
+
316
+ async def branch(i: int) -> AsyncIterator[T]:
317
+ buf = buffers[i]
318
+ while True:
319
+ if buf:
320
+ yield buf.popleft()
321
+ continue
322
+ if exhausted[0]:
323
+ if error[0] is not None:
324
+ raise error[0]
325
+ return
326
+ async with lock:
327
+ if buf or exhausted[0]:
328
+ continue
329
+ try:
330
+ item = await source.__anext__()
331
+ except StopAsyncIteration:
332
+ exhausted[0] = True
333
+ continue
334
+ except Exception as e:
335
+ error[0] = e
336
+ exhausted[0] = True
337
+ continue
338
+ for b in buffers:
339
+ b.append(item)
340
+
341
+ return tuple(branch(i) for i in range(n))