graph-agents-cli 0.3.1__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 (291) hide show
  1. graph_agents_cli/__init__.py +26 -0
  2. graph_agents_cli/_api_policy.py +2145 -0
  3. graph_agents_cli/_approvals.py +400 -0
  4. graph_agents_cli/_build.py +186 -0
  5. graph_agents_cli/_build_info.json +7 -0
  6. graph_agents_cli/_chat_client.py +462 -0
  7. graph_agents_cli/_click.py +157 -0
  8. graph_agents_cli/_defaults.py +139 -0
  9. graph_agents_cli/_experiments.py +64 -0
  10. graph_agents_cli/_http.py +192 -0
  11. graph_agents_cli/_output.py +83 -0
  12. graph_agents_cli/_project.py +462 -0
  13. graph_agents_cli/_remote.py +220 -0
  14. graph_agents_cli/_response_schema.py +264 -0
  15. graph_agents_cli/_runner.py +319 -0
  16. graph_agents_cli/_skills_check.py +274 -0
  17. graph_agents_cli/_tools.py +189 -0
  18. graph_agents_cli/_trust.py +66 -0
  19. graph_agents_cli/api/__init__.py +15 -0
  20. graph_agents_cli/api/_changes.py +506 -0
  21. graph_agents_cli/api/_files.py +658 -0
  22. graph_agents_cli/api/cmd_api.py +2480 -0
  23. graph_agents_cli/deploy/__init__.py +15 -0
  24. graph_agents_cli/deploy/_config.py +171 -0
  25. graph_agents_cli/deploy/_image.py +128 -0
  26. graph_agents_cli/deploy/_kube.py +286 -0
  27. graph_agents_cli/deploy/_modes.py +234 -0
  28. graph_agents_cli/deploy/_preflight.py +370 -0
  29. graph_agents_cli/deploy/_values.py +168 -0
  30. graph_agents_cli/deploy/cmd_deploy.py +1866 -0
  31. graph_agents_cli/deploy/gitops.py +562 -0
  32. graph_agents_cli/deploy/local_load.py +273 -0
  33. graph_agents_cli/dev/__init__.py +13 -0
  34. graph_agents_cli/dev/cmd_build.py +131 -0
  35. graph_agents_cli/dev/cmd_install.py +78 -0
  36. graph_agents_cli/dev/cmd_lint.py +119 -0
  37. graph_agents_cli/dev/cmd_playground.py +297 -0
  38. graph_agents_cli/dev/policy_check.py +1287 -0
  39. graph_agents_cli/eval/__init__.py +22 -0
  40. graph_agents_cli/eval/_client.py +670 -0
  41. graph_agents_cli/eval/_common.py +177 -0
  42. graph_agents_cli/eval/_judge.py +168 -0
  43. graph_agents_cli/eval/_judge_runner.py +238 -0
  44. graph_agents_cli/eval/_paths.py +212 -0
  45. graph_agents_cli/eval/checks.py +581 -0
  46. graph_agents_cli/eval/cmd_analyze.py +278 -0
  47. graph_agents_cli/eval/cmd_compare.py +284 -0
  48. graph_agents_cli/eval/cmd_eval_group.py +80 -0
  49. graph_agents_cli/eval/cmd_generate.py +558 -0
  50. graph_agents_cli/eval/cmd_grade.py +466 -0
  51. graph_agents_cli/eval/cmd_metric.py +156 -0
  52. graph_agents_cli/eval/cmd_run.py +370 -0
  53. graph_agents_cli/eval/cmd_submit.py +400 -0
  54. graph_agents_cli/eval/config.py +435 -0
  55. graph_agents_cli/eval/dataset.py +350 -0
  56. graph_agents_cli/eval/gate.py +420 -0
  57. graph_agents_cli/eval/transcript.py +192 -0
  58. graph_agents_cli/extension/__init__.py +13 -0
  59. graph_agents_cli/extension/_compat.py +86 -0
  60. graph_agents_cli/extension/_loader.py +293 -0
  61. graph_agents_cli/extension/_manifest.py +135 -0
  62. graph_agents_cli/extension/_overrides.py +195 -0
  63. graph_agents_cli/extension/_paths.py +91 -0
  64. graph_agents_cli/extension/_refs.py +193 -0
  65. graph_agents_cli/extension/_resolver.py +453 -0
  66. graph_agents_cli/extension/_schema.py +106 -0
  67. graph_agents_cli/extension/_spec.py +253 -0
  68. graph_agents_cli/extension/_sync.py +102 -0
  69. graph_agents_cli/extension/_trust.py +58 -0
  70. graph_agents_cli/extension/cmd_extension_add.py +259 -0
  71. graph_agents_cli/extension/cmd_extension_group.py +57 -0
  72. graph_agents_cli/extension/cmd_extension_list.py +56 -0
  73. graph_agents_cli/extension/cmd_extension_remove.py +61 -0
  74. graph_agents_cli/extension/cmd_extension_update.py +195 -0
  75. graph_agents_cli/info/__init__.py +13 -0
  76. graph_agents_cli/info/cmd_info.py +222 -0
  77. graph_agents_cli/infra/__init__.py +15 -0
  78. graph_agents_cli/infra/checks.py +1169 -0
  79. graph_agents_cli/infra/cmd_infra.py +103 -0
  80. graph_agents_cli/main.py +591 -0
  81. graph_agents_cli/peer/__init__.py +15 -0
  82. graph_agents_cli/peer/_generate.py +254 -0
  83. graph_agents_cli/peer/cmd_peer.py +1151 -0
  84. graph_agents_cli/run/__init__.py +13 -0
  85. graph_agents_cli/run/_local_server.py +1157 -0
  86. graph_agents_cli/run/_signals.py +141 -0
  87. graph_agents_cli/run/cmd_approvals.py +530 -0
  88. graph_agents_cli/run/cmd_run.py +1421 -0
  89. graph_agents_cli/scaffold/__init__.py +19 -0
  90. graph_agents_cli/scaffold/agents/README.md +24 -0
  91. graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
  92. graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
  93. graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
  94. graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
  95. graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
  96. graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
  97. graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
  98. graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
  99. graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
  100. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
  101. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
  102. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
  103. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
  104. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
  105. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
  106. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
  107. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
  108. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
  109. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
  110. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
  111. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
  112. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
  113. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
  114. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
  115. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
  116. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
  117. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
  118. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
  119. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
  120. graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
  121. graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
  122. graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
  123. graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
  124. graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
  125. graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
  126. graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
  127. graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
  128. graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
  129. graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
  130. graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
  131. graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
  132. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
  133. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
  134. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
  135. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
  136. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
  137. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
  138. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
  139. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
  140. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
  141. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
  142. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
  143. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
  144. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
  145. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
  146. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
  147. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
  148. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
  149. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
  150. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
  151. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
  152. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
  153. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
  154. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
  155. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
  156. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
  157. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
  158. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
  159. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
  160. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
  161. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
  162. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
  163. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
  164. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
  165. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
  166. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
  167. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
  168. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
  169. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
  170. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
  171. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
  172. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
  173. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
  174. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
  175. graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
  176. graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
  177. graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
  178. graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
  179. graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
  180. graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
  181. graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
  182. graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
  183. graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
  184. graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
  185. graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
  186. graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
  187. graph_agents_cli/scaffold/commands/__init__.py +13 -0
  188. graph_agents_cli/scaffold/commands/create.py +1424 -0
  189. graph_agents_cli/scaffold/commands/enhance.py +1652 -0
  190. graph_agents_cli/scaffold/commands/upgrade.py +570 -0
  191. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
  192. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
  193. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
  194. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
  195. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
  196. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
  197. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
  198. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
  199. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
  200. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
  201. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
  202. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
  203. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
  204. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
  205. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
  206. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
  207. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
  208. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
  209. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
  210. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
  211. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
  212. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
  213. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
  214. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
  215. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
  216. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
  217. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
  218. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
  219. graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
  220. graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
  221. graph_agents_cli/scaffold/utils/__init__.py +13 -0
  222. graph_agents_cli/scaffold/utils/backup.py +212 -0
  223. graph_agents_cli/scaffold/utils/build_record.py +257 -0
  224. graph_agents_cli/scaffold/utils/cli_options.py +184 -0
  225. graph_agents_cli/scaffold/utils/fs.py +83 -0
  226. graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
  227. graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
  228. graph_agents_cli/scaffold/utils/keyedit.py +768 -0
  229. graph_agents_cli/scaffold/utils/keymerge.py +537 -0
  230. graph_agents_cli/scaffold/utils/language.py +138 -0
  231. graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
  232. graph_agents_cli/scaffold/utils/logging.py +77 -0
  233. graph_agents_cli/scaffold/utils/manifest.py +292 -0
  234. graph_agents_cli/scaffold/utils/merge.py +970 -0
  235. graph_agents_cli/scaffold/utils/merge3.py +216 -0
  236. graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
  237. graph_agents_cli/scaffold/utils/remote_template.py +376 -0
  238. graph_agents_cli/scaffold/utils/template.py +1352 -0
  239. graph_agents_cli/scaffold/utils/upgrade.py +894 -0
  240. graph_agents_cli/scaffold/utils/version.py +438 -0
  241. graph_agents_cli/secrets/__init__.py +15 -0
  242. graph_agents_cli/secrets/_apply.py +954 -0
  243. graph_agents_cli/secrets/_required.py +188 -0
  244. graph_agents_cli/secrets/cmd_secrets.py +211 -0
  245. graph_agents_cli/setup/__init__.py +13 -0
  246. graph_agents_cli/setup/_antigravity.py +221 -0
  247. graph_agents_cli/setup/cmd_auth.py +1030 -0
  248. graph_agents_cli/setup/cmd_dev_token.py +513 -0
  249. graph_agents_cli/setup/cmd_setup.py +428 -0
  250. graph_agents_cli/setup/cmd_update.py +140 -0
  251. graph_agents_cli/skills/__init__.py +13 -0
  252. graph_agents_cli/skills/_bundle.py +65 -0
  253. graph_agents_cli/skills/data/README.md +19 -0
  254. graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
  255. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
  256. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
  257. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
  258. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
  259. graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
  260. graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
  261. graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
  262. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
  263. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
  264. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
  265. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
  266. graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
  267. graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
  268. graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
  269. graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
  270. graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
  271. graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
  272. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
  273. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
  274. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
  275. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
  276. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
  277. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
  278. graph_agents_cli/system/__init__.py +15 -0
  279. graph_agents_cli/system/_apply.py +519 -0
  280. graph_agents_cli/system/_checks.py +1023 -0
  281. graph_agents_cli/system/_deploy.py +215 -0
  282. graph_agents_cli/system/_model.py +363 -0
  283. graph_agents_cli/system/_system.py +664 -0
  284. graph_agents_cli/system/_views.py +208 -0
  285. graph_agents_cli/system/cmd_system.py +423 -0
  286. graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
  287. graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
  288. graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
  289. graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
  290. graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
  291. graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
@@ -0,0 +1,216 @@
1
+ # Copyright 2026 graph-agents-cli contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """A fake OIDC issuer for tests of agents calling agents: a JWKS and RFC 8693 token exchange.
16
+
17
+ It signs RS256 tokens with one key, serves the key set at `/jwks` and exchanges
18
+ tokens at `/token` (`grant_type=urn:ietf:params:oauth:grant-type:token-exchange`)
19
+ for the clients it knows (HTTP Basic client authentication, or the client id and
20
+ secret in the form: `client_secret_post`). An exchanged token
21
+ keeps the subject token's `sub`, names the calling client in `act` (the subject
22
+ token's own `act` nested inside it, RFC 8693 section 4.1), has the requested
23
+ `audience`, `azp` set to the client and `expires_in` of at most 300 s. It refuses
24
+ an audience the client may not act for (`may_act`), a service token (one whose
25
+ `sub` is its own client) and a token it did not issue. Every exchange request is
26
+ recorded (`requests`: the form, secrets included, and how the client
27
+ authenticated), and a test can make it misbehave: `hold` (a `threading.Event`
28
+ cleared: every `/token` request waits until it is set, a hung issuer), `answers`
29
+ (queued `(status, body)` answers returned instead of an exchange),
30
+ `expires_in` (the lifetime it grants; None leaves it out of the answer) and
31
+ `act` (False: exchanged tokens name no actor, only `azp`, as some issuers do).
32
+ Loopback only, on a free port; nothing leaves the process.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import base64
38
+ import json
39
+ import threading
40
+ import time
41
+ import uuid
42
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
43
+ from typing import Any
44
+ from urllib.parse import parse_qs, unquote_plus
45
+
46
+ import jwt
47
+ from cryptography.hazmat.primitives.asymmetric import rsa
48
+
49
+ TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"
50
+ ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
51
+ EXCHANGED_TTL_S = 300
52
+
53
+
54
+ class FakeIssuer:
55
+ """The issuer: `login` mints a person's token, `/token` exchanges it for an agent."""
56
+
57
+ def __init__(self, clients: dict[str, tuple[str, set[str]]]) -> None:
58
+ """`clients`: client id -> (secret, the audiences it may exchange tokens for)."""
59
+ self.key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
60
+ self.kid = uuid.uuid4().hex[:8]
61
+ self.clients = clients
62
+ self.exchanges: list[dict[str, Any]] = []
63
+ self.requests: list[dict[str, Any]] = []
64
+ self.hold = threading.Event()
65
+ self.hold.set()
66
+ self.answers: list[tuple[int, dict[str, Any]]] = []
67
+ self.expires_in: int | None = EXCHANGED_TTL_S
68
+ self.act = True
69
+ issuer = self
70
+
71
+ class Handler(BaseHTTPRequestHandler):
72
+ def do_GET(self) -> None:
73
+ if self.path != "/jwks":
74
+ self._answer(404, {"error": "not_found"})
75
+ return
76
+ self._answer(200, issuer.jwks())
77
+
78
+ def do_POST(self) -> None:
79
+ if self.path != "/token":
80
+ self._answer(404, {"error": "not_found"})
81
+ return
82
+ length = int(self.headers.get("content-length") or 0)
83
+ form = {k: v[0] for k, v in parse_qs(self.rfile.read(length).decode()).items()}
84
+ authorization = self.headers.get("authorization") or ""
85
+ issuer.requests.append({"form": form, "authorization": authorization})
86
+ issuer.hold.wait(timeout=120)
87
+ if issuer.answers:
88
+ status, body = issuer.answers.pop(0)
89
+ else:
90
+ status, body = issuer.exchange(authorization, form)
91
+ self._answer(status, body)
92
+
93
+ def _answer(self, status: int, body: dict[str, Any]) -> None:
94
+ data = json.dumps(body).encode()
95
+ self.send_response(status)
96
+ self.send_header("Content-Type", "application/json")
97
+ self.send_header("Content-Length", str(len(data)))
98
+ self.end_headers()
99
+ self.wfile.write(data)
100
+
101
+ def log_message(self, *args: Any) -> None:
102
+ pass
103
+
104
+ self.httpd = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
105
+ self.url = f"http://127.0.0.1:{self.httpd.server_address[1]}"
106
+ self.issuer = f"{self.url}/"
107
+ self.thread = threading.Thread(target=self.httpd.serve_forever, daemon=True)
108
+ self.thread.start()
109
+
110
+ @property
111
+ def jwks_url(self) -> str:
112
+ return f"{self.url}/jwks"
113
+
114
+ @property
115
+ def token_url(self) -> str:
116
+ return f"{self.url}/token"
117
+
118
+ def close(self) -> None:
119
+ self.hold.set()
120
+ self.httpd.shutdown()
121
+ self.httpd.server_close()
122
+
123
+ def jwks(self) -> dict[str, Any]:
124
+ public = jwt.algorithms.RSAAlgorithm.to_jwk(self.key.public_key(), as_dict=True)
125
+ return {"keys": [{**public, "kid": self.kid, "use": "sig", "alg": "RS256"}]}
126
+
127
+ def sign(self, claims: dict[str, Any]) -> str:
128
+ return jwt.encode(claims, self.key, algorithm="RS256", headers={"kid": self.kid})
129
+
130
+ def login(
131
+ self,
132
+ sub: str,
133
+ *,
134
+ audience: str,
135
+ client: str = "web",
136
+ roles: tuple[str, ...] = (),
137
+ ttl_s: int = 3600,
138
+ ) -> str:
139
+ """A person's own token for `audience`, as a sign-in client obtains it."""
140
+ now = int(time.time())
141
+ return self.sign(
142
+ {
143
+ "iss": self.issuer,
144
+ "sub": sub,
145
+ "aud": audience,
146
+ "azp": client,
147
+ "roles": list(roles),
148
+ "iat": now,
149
+ "exp": now + ttl_s,
150
+ "jti": uuid.uuid4().hex,
151
+ }
152
+ )
153
+
154
+ def exchange(self, authorization: str, form: dict[str, str]) -> tuple[int, dict[str, Any]]:
155
+ """RFC 8693 token exchange: `(status, body)`."""
156
+ scheme, _, credentials = authorization.partition(" ")
157
+ if scheme.lower() == "basic":
158
+ try:
159
+ pair = base64.b64decode(credentials).decode()
160
+ except Exception:
161
+ pair = ""
162
+ # RFC 6749 section 2.3.1: each part form-encoded, then joined and base64-encoded.
163
+ client, _, secret = (unquote_plus(part) for part in pair.partition(":"))
164
+ elif not authorization and "client_id" in form:
165
+ client, secret = form.get("client_id") or "", form.get("client_secret") or ""
166
+ else:
167
+ client, secret = "", ""
168
+ known = self.clients.get(client)
169
+ if known is None or known[0] != secret:
170
+ return 401, {"error": "invalid_client"}
171
+ if form.get("grant_type") != TOKEN_EXCHANGE:
172
+ return 400, {"error": "unsupported_grant_type"}
173
+ audience = form.get("audience") or ""
174
+ if audience not in known[1]:
175
+ return 400, {"error": "invalid_target"}
176
+ try:
177
+ subject = jwt.decode(
178
+ form.get("subject_token") or "",
179
+ self.key.public_key(),
180
+ algorithms=["RS256"],
181
+ issuer=self.issuer,
182
+ options={"verify_aud": False},
183
+ )
184
+ except jwt.PyJWTError:
185
+ return 400, {"error": "invalid_grant"}
186
+ if subject.get("sub") == subject.get("azp"):
187
+ return 400, {"error": "invalid_grant"} # no exchange of a service's own token
188
+ actor: dict[str, Any] = {"sub": client}
189
+ if isinstance(subject.get("act"), dict):
190
+ actor["act"] = subject["act"]
191
+ now = int(time.time())
192
+ exchanged = {
193
+ "iss": self.issuer,
194
+ "sub": subject["sub"],
195
+ "aud": audience,
196
+ "azp": client,
197
+ "act": actor,
198
+ "roles": subject.get("roles") or [],
199
+ "iat": now,
200
+ "exp": min(now + (self.expires_in or 60), int(subject.get("exp") or now)),
201
+ "jti": uuid.uuid4().hex,
202
+ }
203
+ if not self.act:
204
+ del exchanged["act"]
205
+ for claim in ("scope", "resource"):
206
+ if form.get(claim):
207
+ exchanged[claim] = form[claim]
208
+ self.exchanges.append({"client": client, "audience": audience, "sub": subject["sub"]})
209
+ answer: dict[str, Any] = {
210
+ "access_token": self.sign(exchanged),
211
+ "issued_token_type": ACCESS_TOKEN_TYPE,
212
+ "token_type": "Bearer",
213
+ }
214
+ if self.expires_in is not None:
215
+ answer["expires_in"] = exchanged["exp"] - now
216
+ return 200, answer
@@ -0,0 +1,357 @@
1
+ # Copyright 2026 graph-agents-cli contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """A fake OpenAI API over HTTP for tests of the model settings: Chat Completions and Responses.
16
+
17
+ It serves `POST /v1/chat/completions` and `POST /v1/responses`, streamed
18
+ (server-sent events, as OpenAI sends them) or not, and answers from the
19
+ conversation: after a tool result, `Found: <the result>`; a request whose last
20
+ user text names one of the request's function tools (a word of its name), a
21
+ call of that tool with every required string argument set to the text after the
22
+ last "in "/"for "; anything else, `ok`. It refuses what OpenAI refuses (400,
23
+ OpenAI's error body), so a test notices a request sent to the wrong API:
24
+ `stream_options` or a top-level `reasoning_effort` on the Responses API, and
25
+ `reasoning` on Chat Completions. With `effort_refuses_tools` (a model like
26
+ the one Track C met: F15) Chat Completions also refuses function tools unless
27
+ `reasoning_effort` is `none`, naming `/v1/responses`. Streamed Chat Completions
28
+ carry usage only when `stream_options.include_usage` asks for it; Responses
29
+ always carry it (`response.completed`). Every request is recorded (`requests`:
30
+ the path from `/v1/` on, and the JSON body). Any prefix before `/v1` is
31
+ accepted: `base_url()` gives each test one of its own, since langchain-openai
32
+ keeps one HTTP client per base URL, bound to the event loop that made it.
33
+ Loopback only, on a free port; nothing leaves the process.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import json
39
+ import re
40
+ import threading
41
+ import time
42
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
43
+ from typing import Any
44
+
45
+ CHAT_USAGE = {"prompt_tokens": 11, "completion_tokens": 5, "total_tokens": 16}
46
+ RESPONSES_USAGE = {
47
+ "input_tokens": 13,
48
+ "input_tokens_details": {"cached_tokens": 0},
49
+ "output_tokens": 7,
50
+ "output_tokens_details": {"reasoning_tokens": 2},
51
+ "total_tokens": 20,
52
+ }
53
+ F15 = (
54
+ "Function tools with reasoning_effort are not supported for {model} in "
55
+ "/v1/chat/completions. Please use /v1/responses instead, or set reasoning_effort to 'none'."
56
+ )
57
+ _SUBJECT = re.compile(r"\b(?:in|for)\s+([^?.!]+)", re.IGNORECASE)
58
+
59
+
60
+ def _error(message: str, param: str | None = None, code: str | None = None) -> dict[str, Any]:
61
+ return {
62
+ "error": {"message": message, "type": "invalid_request_error", "param": param, "code": code}
63
+ }
64
+
65
+
66
+ def _call_for(text: str, tools: list[dict[str, Any]]) -> tuple[str, str] | None:
67
+ """(tool name, JSON arguments) when `text` names a tool (a word of its name), else None."""
68
+ words = set(re.findall(r"[a-z0-9]+", text.lower()))
69
+ for tool in tools:
70
+ name = str(tool["name"])
71
+ if not {w for w in name.lower().split("_") if len(w) > 2} & words:
72
+ continue
73
+ matches = list(_SUBJECT.finditer(text))
74
+ subject = matches[-1].group(1).strip() if matches else text
75
+ schema = tool.get("parameters") or {}
76
+ properties = schema.get("properties") or {}
77
+ args = {
78
+ key: subject
79
+ for key in schema.get("required") or []
80
+ if (properties.get(key) or {}).get("type", "string") == "string"
81
+ }
82
+ return name, json.dumps(args)
83
+ return None
84
+
85
+
86
+ def _text_of(content: Any) -> str:
87
+ if isinstance(content, str):
88
+ return content
89
+ if isinstance(content, list):
90
+ return "".join(str(block.get("text", "")) for block in content if isinstance(block, dict))
91
+ return ""
92
+
93
+
94
+ class FakeOpenAI:
95
+ """The server: `base_url` is what OPENAI_BASE_URL names; `requests` what it received."""
96
+
97
+ def __init__(self) -> None:
98
+ self.requests: list[dict[str, Any]] = []
99
+ self.effort_refuses_tools = False
100
+ self._ids = 0
101
+ server = self
102
+
103
+ class Handler(BaseHTTPRequestHandler):
104
+ protocol_version = "HTTP/1.1"
105
+
106
+ def do_POST(self) -> None:
107
+ length = int(self.headers.get("content-length") or 0)
108
+ body = json.loads(self.rfile.read(length) or b"{}")
109
+ path = self.path[self.path.find("/v1/") :]
110
+ server.requests.append({"path": path, "body": body})
111
+ if self.path.endswith("/chat/completions"):
112
+ status, payload, events = server.chat(body)
113
+ elif self.path.endswith("/responses"):
114
+ status, payload, events = server.responses(body)
115
+ else:
116
+ status, payload, events = 404, _error(f"no route {self.path}"), None
117
+ if events is None:
118
+ data = json.dumps(payload).encode()
119
+ self.send_response(status)
120
+ self.send_header("content-type", "application/json")
121
+ else:
122
+ data = "".join(f"{event}\n\n" for event in events).encode()
123
+ self.send_response(200)
124
+ self.send_header("content-type", "text/event-stream")
125
+ self.send_header("content-length", str(len(data)))
126
+ self.end_headers()
127
+ self.wfile.write(data)
128
+
129
+ def log_message(self, *args: Any) -> None:
130
+ return None
131
+
132
+ self.httpd = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
133
+ self.thread = threading.Thread(target=self.httpd.serve_forever, daemon=True)
134
+ self.thread.start()
135
+
136
+ def base_url(self) -> str:
137
+ """A base URL of this server no earlier call returned (OPENAI_BASE_URL)."""
138
+ self._ids += 1
139
+ return f"http://127.0.0.1:{self.httpd.server_address[1]}/c{self._ids}/v1"
140
+
141
+ def close(self) -> None:
142
+ self.httpd.shutdown()
143
+ self.httpd.server_close()
144
+
145
+ def paths(self) -> list[str]:
146
+ return [r["path"] for r in self.requests]
147
+
148
+ def _id(self, prefix: str) -> str:
149
+ self._ids += 1
150
+ return f"{prefix}_{self._ids}"
151
+
152
+ # --- Chat Completions ----------------------------------------------------------------
153
+
154
+ def chat(self, body: dict[str, Any]) -> tuple[int, dict[str, Any], list[str] | None]:
155
+ if "reasoning" in body:
156
+ return 400, _error("Unknown parameter: 'reasoning'.", "reasoning", "unknown"), None
157
+ tools = [t["function"] for t in body.get("tools") or [] if t.get("type") == "function"]
158
+ effort = body.get("reasoning_effort")
159
+ if self.effort_refuses_tools and tools and effort != "none":
160
+ return 400, _error(F15.format(model=body.get("model")), "reasoning_effort"), None
161
+ messages = body.get("messages") or []
162
+ last = messages[-1] if messages else {}
163
+ text, call = "ok", None
164
+ if last.get("role") == "tool":
165
+ text = f"Found: {_text_of(last.get('content'))}"
166
+ else:
167
+ asked = next(
168
+ (_text_of(m.get("content")) for m in reversed(messages) if m["role"] == "user"), ""
169
+ )
170
+ call = _call_for(asked, tools)
171
+ head = {"id": self._id("chatcmpl"), "created": int(time.time()), "model": body["model"]}
172
+ call_id = self._id("call")
173
+ if not body.get("stream"):
174
+ message: dict[str, Any] = {"role": "assistant", "content": None if call else text}
175
+ if call:
176
+ message["tool_calls"] = [
177
+ {
178
+ "id": call_id,
179
+ "type": "function",
180
+ "function": {"name": call[0], "arguments": call[1]},
181
+ }
182
+ ]
183
+ choice = {
184
+ "index": 0,
185
+ "message": message,
186
+ "finish_reason": "tool_calls" if call else "stop",
187
+ }
188
+ return (
189
+ 200,
190
+ {**head, "object": "chat.completion", "choices": [choice], "usage": CHAT_USAGE},
191
+ None,
192
+ )
193
+
194
+ def chunk(delta: dict[str, Any], finish: str | None = None) -> str:
195
+ choice = {"index": 0, "delta": delta, "finish_reason": finish}
196
+ return "data: " + json.dumps(
197
+ {**head, "object": "chat.completion.chunk", "choices": [choice]}
198
+ )
199
+
200
+ if call:
201
+ calls = [
202
+ {
203
+ "index": 0,
204
+ "id": call_id,
205
+ "type": "function",
206
+ "function": {"name": call[0], "arguments": call[1]},
207
+ }
208
+ ]
209
+ events = [chunk({"role": "assistant", "content": None, "tool_calls": calls})]
210
+ events.append(chunk({}, "tool_calls"))
211
+ else:
212
+ events = [chunk({"role": "assistant", "content": ""})]
213
+ events += [chunk({"content": text[i : i + 8]}) for i in range(0, len(text), 8)]
214
+ events.append(chunk({}, "stop"))
215
+ if (body.get("stream_options") or {}).get("include_usage"):
216
+ usage = {**head, "object": "chat.completion.chunk", "choices": [], "usage": CHAT_USAGE}
217
+ events.append("data: " + json.dumps(usage))
218
+ events.append("data: [DONE]")
219
+ return 200, {}, events
220
+
221
+ # --- Responses -----------------------------------------------------------------------
222
+
223
+ def responses(self, body: dict[str, Any]) -> tuple[int, dict[str, Any], list[str] | None]:
224
+ for unknown in ("stream_options", "reasoning_effort", "messages"):
225
+ if unknown in body:
226
+ return 400, _error(f"Unknown parameter: '{unknown}'.", unknown, "unknown"), None
227
+ tools = [t for t in body.get("tools") or [] if t.get("type") == "function"]
228
+ items = body.get("input") or []
229
+ if isinstance(items, str):
230
+ items = [{"role": "user", "content": items}]
231
+ last = items[-1] if items else {}
232
+ text, call = "ok", None
233
+ if last.get("type") == "function_call_output":
234
+ text = f"Found: {last.get('output')}"
235
+ else:
236
+ asked = next(
237
+ (
238
+ _text_of(i.get("content"))
239
+ for i in reversed(items)
240
+ if i.get("role") == "user" and i.get("type", "message") == "message"
241
+ ),
242
+ "",
243
+ )
244
+ call = _call_for(asked, tools)
245
+ response_id = self._id("resp")
246
+ if call:
247
+ item: dict[str, Any] = {
248
+ "id": self._id("fc"),
249
+ "type": "function_call",
250
+ "call_id": self._id("call"),
251
+ "name": call[0],
252
+ "arguments": call[1],
253
+ "status": "completed",
254
+ }
255
+ else:
256
+ item = {
257
+ "id": self._id("msg"),
258
+ "type": "message",
259
+ "role": "assistant",
260
+ "status": "completed",
261
+ "content": [{"type": "output_text", "text": text, "annotations": []}],
262
+ }
263
+ response = {
264
+ "id": response_id,
265
+ "object": "response",
266
+ "created_at": int(time.time()),
267
+ "model": body["model"],
268
+ "status": "completed",
269
+ "error": None,
270
+ "incomplete_details": None,
271
+ "instructions": None,
272
+ "metadata": {},
273
+ "output": [item],
274
+ "parallel_tool_calls": True,
275
+ "temperature": None,
276
+ "tool_choice": "auto",
277
+ "tools": body.get("tools") or [],
278
+ "top_p": None,
279
+ "reasoning": body.get("reasoning"),
280
+ "usage": RESPONSES_USAGE,
281
+ }
282
+ if not body.get("stream"):
283
+ return 200, response, None
284
+ sequence = iter(range(1000))
285
+
286
+ def event(kind: str, **fields: Any) -> str:
287
+ data = {"type": kind, "sequence_number": next(sequence), **fields}
288
+ return f"event: {kind}\ndata: {json.dumps(data)}"
289
+
290
+ started = {**response, "status": "in_progress", "output": [], "usage": None}
291
+ events = [
292
+ event("response.created", response=started),
293
+ event("response.in_progress", response=started),
294
+ ]
295
+ if call:
296
+ added = {**item, "arguments": "", "status": "in_progress"}
297
+ events += [
298
+ event("response.output_item.added", output_index=0, item=added),
299
+ event(
300
+ "response.function_call_arguments.delta",
301
+ item_id=item["id"],
302
+ output_index=0,
303
+ delta=item["arguments"],
304
+ ),
305
+ event(
306
+ "response.function_call_arguments.done",
307
+ item_id=item["id"],
308
+ output_index=0,
309
+ arguments=item["arguments"],
310
+ ),
311
+ ]
312
+ else:
313
+ added = {**item, "status": "in_progress", "content": []}
314
+ part = {"type": "output_text", "text": "", "annotations": []}
315
+ events += [
316
+ event("response.output_item.added", output_index=0, item=added),
317
+ event(
318
+ "response.content_part.added",
319
+ item_id=item["id"],
320
+ output_index=0,
321
+ content_index=0,
322
+ part=part,
323
+ ),
324
+ ]
325
+ events += [
326
+ event(
327
+ "response.output_text.delta",
328
+ item_id=item["id"],
329
+ output_index=0,
330
+ content_index=0,
331
+ delta=text[i : i + 8],
332
+ logprobs=[],
333
+ )
334
+ for i in range(0, len(text), 8)
335
+ ]
336
+ events += [
337
+ event(
338
+ "response.output_text.done",
339
+ item_id=item["id"],
340
+ output_index=0,
341
+ content_index=0,
342
+ text=text,
343
+ logprobs=[],
344
+ ),
345
+ event(
346
+ "response.content_part.done",
347
+ item_id=item["id"],
348
+ output_index=0,
349
+ content_index=0,
350
+ part={**part, "text": text},
351
+ ),
352
+ ]
353
+ events += [
354
+ event("response.output_item.done", output_index=0, item=item),
355
+ event("response.completed", response=response),
356
+ ]
357
+ return 200, {}, events