Modade6787 commited on
Commit
b4d34a9
·
verified ·
1 Parent(s): 334d014

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. CMakeLists.txt +23 -0
  3. LICENSE +202 -0
  4. MANIFEST.in +1 -0
  5. README.md +595 -3
  6. USAGE_POLICY +1 -0
  7. _build/gpt_oss_build_backend/__init__.py +1 -0
  8. _build/gpt_oss_build_backend/backend.py +140 -0
  9. awesome-gpt-oss.md +90 -0
  10. compatibility-test/.gitignore +142 -0
  11. compatibility-test/README.md +29 -0
  12. compatibility-test/analysis.ts +142 -0
  13. compatibility-test/cases.jsonl +30 -0
  14. compatibility-test/index.ts +196 -0
  15. compatibility-test/package-lock.json +1633 -0
  16. compatibility-test/package.json +11 -0
  17. compatibility-test/providers.ts +15 -0
  18. compatibility-test/runCase.ts +331 -0
  19. compatibility-test/tools.ts +156 -0
  20. docs/gpt-oss-120b.svg +3 -0
  21. docs/gpt-oss-20b.svg +3 -0
  22. docs/gpt-oss.svg +3 -0
  23. examples/agents-sdk-js/index.ts +90 -0
  24. examples/agents-sdk-js/package-lock.json +1798 -0
  25. examples/agents-sdk-js/package.json +20 -0
  26. examples/agents-sdk-python/example.py +102 -0
  27. examples/agents-sdk-python/pyproject.toml +9 -0
  28. examples/gradio/gradio_chat.py +247 -0
  29. examples/streamlit/streamlit_chat.py +354 -0
  30. gpt-oss-mcp-server/README.md +29 -0
  31. gpt-oss-mcp-server/browser_server.py +120 -0
  32. gpt-oss-mcp-server/build-system-prompt.py +116 -0
  33. gpt-oss-mcp-server/pyproject.toml +8 -0
  34. gpt-oss-mcp-server/python_server.py +33 -0
  35. gpt-oss-mcp-server/reference-system-prompt.py +46 -0
  36. gpt_oss/__init__.py +0 -0
  37. gpt_oss/chat.py +369 -0
  38. gpt_oss/evals/README.md +4 -0
  39. gpt_oss/evals/__init__.py +0 -0
  40. gpt_oss/evals/__main__.py +211 -0
  41. gpt_oss/evals/abcd_grader.py +121 -0
  42. gpt_oss/evals/aime_eval.py +97 -0
  43. gpt_oss/evals/basic_eval.py +38 -0
  44. gpt_oss/evals/chat_completions_sampler.py +93 -0
  45. gpt_oss/evals/gpqa_eval.py +125 -0
  46. gpt_oss/evals/healthbench_eval.py +612 -0
  47. gpt_oss/evals/report.py +207 -0
  48. gpt_oss/evals/responses_sampler.py +85 -0
  49. gpt_oss/evals/types.py +66 -0
  50. gpt_oss/generate.py +95 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ docs/gpt-oss-120b.svg filter=lfs diff=lfs merge=lfs -text
37
+ docs/gpt-oss-20b.svg filter=lfs diff=lfs merge=lfs -text
38
+ docs/gpt-oss.svg filter=lfs diff=lfs merge=lfs -text
CMakeLists.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 3.26)
2
+ project(gpt_oss LANGUAGES C CXX)
3
+
4
+ # If not defined externally, auto-detect
5
+ if(NOT DEFINED GPTOSS_BUILD_METAL)
6
+ if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
7
+ message(STATUS "Apple Silicon detected → enabling GPTOSS_BUILD_METAL")
8
+ set(GPTOSS_BUILD_METAL ON)
9
+ else()
10
+ message(STATUS "Non-Apple Silicon → disabling GPTOSS_BUILD_METAL")
11
+ set(GPTOSS_BUILD_METAL OFF)
12
+ endif()
13
+ else()
14
+ message(STATUS "GPTOSS_BUILD_METAL manually set to: ${GPTOSS_BUILD_METAL}")
15
+ endif()
16
+
17
+ # Now declare it as a cache variable (respects user-provided value)
18
+ set(GPTOSS_BUILD_METAL "${GPTOSS_BUILD_METAL}" CACHE BOOL "Enable Metal backend")
19
+
20
+ if(GPTOSS_BUILD_METAL)
21
+ enable_language(OBJC)
22
+ add_subdirectory(gpt_oss/metal)
23
+ endif()
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
MANIFEST.in ADDED
@@ -0,0 +1 @@
 
 
1
+ recursive-include _build *
README.md CHANGED
@@ -1,3 +1,595 @@
1
- ---
2
- license: apache-2.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <img alt="gpt-oss-120" src="./docs/gpt-oss.svg">
2
+ <p align="center">
3
+ <a href="https://gpt-oss.com"><strong>Try gpt-oss</strong></a> ·
4
+ <a href="https://cookbook.openai.com/topic/gpt-oss"><strong>Guides</strong></a> ·
5
+ <a href="https://arxiv.org/abs/2508.10925"><strong>Model card</strong></a> ·
6
+ <a href="https://openai.com/index/introducing-gpt-oss/"><strong>OpenAI blog</strong></a>
7
+ </p>
8
+ <p align="center">
9
+ <strong>Download <a href="https://huggingface.co/openai/gpt-oss-120b">gpt-oss-120b</a> and <a href="https://huggingface.co/openai/gpt-oss-20b">gpt-oss-20b</a> on Hugging Face</strong>
10
+ </p>
11
+
12
+ <br>
13
+
14
+ Welcome to the gpt-oss series, [OpenAI's open-weight models](https://openai.com/open-models/) designed for powerful reasoning, agentic tasks, and versatile developer use cases.
15
+
16
+ We're releasing two flavors of these open models:
17
+
18
+ - `gpt-oss-120b` — for production, general purpose, high reasoning use cases that fit into a single 80GB GPU (like NVIDIA H100 or AMD MI300X) (117B parameters with 5.1B active parameters)
19
+ - `gpt-oss-20b` — for lower latency, and local or specialized use cases (21B parameters with 3.6B active parameters)
20
+
21
+ Both models were trained using our [harmony response format][harmony] and should only be used with this format; otherwise, they will not work correctly.
22
+
23
+ ## Table of Contents
24
+ - [Highlights](#highlights)
25
+ - [Inference examples](#inference-examples)
26
+ - [About this repository](#about-this-repository)
27
+ - [Setup](#setup)
28
+ - [Download the model](#download-the-model)
29
+ - [Reference PyTorch implementation](#reference-pytorch-implementation)
30
+ - [Reference Triton implementation (single GPU)](#reference-triton-implementation-single-gpu)
31
+ - [Reference Metal implementation](#reference-metal-implementation)
32
+ - [Harmony format & tools](#harmony-format--tools)
33
+ - [Clients](#clients)
34
+ - [Tools](#tools)
35
+ - [Other details](#other-details)
36
+ - [Contributing](#contributing)
37
+
38
+ ### Highlights
39
+
40
+ - **Permissive Apache 2.0 license:** Build freely without copyleft restrictions or patent risk—ideal for experimentation, customization, and commercial deployment.
41
+ - **Configurable reasoning effort:** Easily adjust the reasoning effort (low, medium, high) based on your specific use case and latency needs.
42
+ - **Full chain-of-thought:** Provides complete access to the model's reasoning process, facilitating easier debugging and greater trust in outputs. This information is not intended to be shown to end users.
43
+ - **Fine-tunable:** Fully customize models to your specific use case through parameter fine-tuning.
44
+ - **Agentic capabilities:** Use the models' native capabilities for function calling, [web browsing](#browser), [Python code execution](#python), and Structured Outputs.
45
+ - **MXFP4 quantization:** The models were post-trained with MXFP4 quantization of the MoE weights, making `gpt-oss-120b` run on a single 80GB GPU (like NVIDIA H100 or AMD MI300X) and the `gpt-oss-20b` model run within 16GB of memory. All evals were performed with the same MXFP4 quantization.
46
+
47
+ ### Inference examples
48
+
49
+ #### Transformers
50
+
51
+ You can use `gpt-oss-120b` and `gpt-oss-20b` with the Transformers library. If you use Transformers' chat template, it will automatically apply the [harmony response format][harmony]. If you use `model.generate` directly, you need to apply the harmony format manually using the chat template or use our [`openai-harmony`][harmony] package.
52
+
53
+ ```python
54
+ from transformers import pipeline
55
+ import torch
56
+
57
+ model_id = "openai/gpt-oss-120b"
58
+
59
+ pipe = pipeline(
60
+ "text-generation",
61
+ model=model_id,
62
+ torch_dtype="auto",
63
+ device_map="auto",
64
+ )
65
+
66
+ messages = [
67
+ {"role": "user", "content": "Explain quantum mechanics clearly and concisely."},
68
+ ]
69
+
70
+ outputs = pipe(
71
+ messages,
72
+ max_new_tokens=256,
73
+ )
74
+ print(outputs[0]["generated_text"][-1])
75
+ ```
76
+
77
+ [Learn more about how to use gpt-oss with Transformers.](https://cookbook.openai.com/articles/gpt-oss/run-transformers)
78
+
79
+ #### vLLM
80
+
81
+ vLLM recommends using [`uv`](https://docs.astral.sh/uv/) for Python dependency management. You can use vLLM to spin up an OpenAI-compatible web server. The following command will automatically download the model and start the server.
82
+
83
+ ```bash
84
+ uv pip install --pre vllm==0.10.1+gptoss \
85
+ --extra-index-url https://wheels.vllm.ai/gpt-oss/ \
86
+ --extra-index-url https://download.pytorch.org/whl/nightly/cu128 \
87
+ --index-strategy unsafe-best-match
88
+
89
+ vllm serve openai/gpt-oss-20b
90
+ ```
91
+
92
+ [Learn more about how to use gpt-oss with vLLM.](https://cookbook.openai.com/articles/gpt-oss/run-vllm)
93
+
94
+ Offline Serve Code:
95
+ - run this code after installing proper libraries as described, while additionally installing this:
96
+ - `uv pip install openai-harmony`
97
+ ```python
98
+ # source .oss/bin/activate
99
+
100
+ import os
101
+ os.environ["VLLM_USE_FLASHINFER_SAMPLER"] = "0"
102
+
103
+ import json
104
+ from openai_harmony import (
105
+ HarmonyEncodingName,
106
+ load_harmony_encoding,
107
+ Conversation,
108
+ Message,
109
+ Role,
110
+ SystemContent,
111
+ DeveloperContent,
112
+ )
113
+
114
+ from vllm import LLM, SamplingParams
115
+ import os
116
+
117
+ # --- 1) Render the prefill with Harmony ---
118
+ encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
119
+
120
+ convo = Conversation.from_messages(
121
+ [
122
+ Message.from_role_and_content(Role.SYSTEM, SystemContent.new()),
123
+ Message.from_role_and_content(
124
+ Role.DEVELOPER,
125
+ DeveloperContent.new().with_instructions("Always respond in riddles"),
126
+ ),
127
+ Message.from_role_and_content(Role.USER, "What is the weather like in SF?"),
128
+ ]
129
+ )
130
+
131
+ prefill_ids = encoding.render_conversation_for_completion(convo, Role.ASSISTANT)
132
+
133
+ # Harmony stop tokens (pass to sampler so they won't be included in output)
134
+ stop_token_ids = encoding.stop_tokens_for_assistant_actions()
135
+
136
+ # --- 2) Run vLLM with prefill ---
137
+ llm = LLM(
138
+ model="openai/gpt-oss-20b",
139
+ trust_remote_code=True,
140
+ gpu_memory_utilization = 0.95,
141
+ max_num_batched_tokens=4096,
142
+ max_model_len=5000,
143
+ tensor_parallel_size=1
144
+ )
145
+
146
+ sampling = SamplingParams(
147
+ max_tokens=128,
148
+ temperature=1,
149
+ stop_token_ids=stop_token_ids,
150
+ )
151
+
152
+ outputs = llm.generate(
153
+ prompt_token_ids=[prefill_ids], # batch of size 1
154
+ sampling_params=sampling,
155
+ )
156
+
157
+ # vLLM gives you both text and token IDs
158
+ gen = outputs[0].outputs[0]
159
+ text = gen.text
160
+ output_tokens = gen.token_ids # <-- these are the completion token IDs (no prefill)
161
+
162
+ # --- 3) Parse the completion token IDs back into structured Harmony messages ---
163
+ entries = encoding.parse_messages_from_completion_tokens(output_tokens, Role.ASSISTANT)
164
+
165
+ # 'entries' is a sequence of structured conversation entries (assistant messages, tool calls, etc.).
166
+ for message in entries:
167
+ print(f"{json.dumps(message.to_dict())}")
168
+ ```
169
+
170
+ #### PyTorch / Triton / Metal
171
+
172
+ These implementations are largely reference implementations for educational purposes and are not expected to be run in production.
173
+
174
+ [Learn more below.](#reference-pytorch-implementation)
175
+
176
+ #### Ollama
177
+
178
+ If you are trying to run `gpt-oss` on consumer hardware, you can use Ollama by running the following commands after [installing Ollama](https://ollama.com/download).
179
+
180
+ ```bash
181
+ # gpt-oss-20b
182
+ ollama pull gpt-oss:20b
183
+ ollama run gpt-oss:20b
184
+
185
+ # gpt-oss-120b
186
+ ollama pull gpt-oss:120b
187
+ ollama run gpt-oss:120b
188
+ ```
189
+
190
+ [Learn more about how to use gpt-oss with Ollama.](https://cookbook.openai.com/articles/gpt-oss/run-locally-ollama)
191
+
192
+ #### LM Studio
193
+
194
+ If you are using [LM Studio](https://lmstudio.ai/) you can use the following commands to download.
195
+
196
+ ```bash
197
+ # gpt-oss-20b
198
+ lms get openai/gpt-oss-20b
199
+ # gpt-oss-120b
200
+ lms get openai/gpt-oss-120b
201
+ ```
202
+
203
+ Check out our [awesome list](./awesome-gpt-oss.md) for a broader collection of gpt-oss resources and inference partners.
204
+
205
+ ## About this repository
206
+
207
+ This repository provides a collection of reference implementations:
208
+
209
+ - **Inference:**
210
+ - [`torch`](#reference-pytorch-implementation) — a non-optimized [PyTorch](https://pytorch.org/) implementation for educational purposes only. Requires at least 4× H100 GPUs due to lack of optimization.
211
+ - [`triton`](#reference-triton-implementation-single-gpu) — a more optimized implementation using [PyTorch](https://pytorch.org/) & [Triton](https://github.com/triton-lang/triton) incl. using CUDA graphs and basic caching
212
+ - [`metal`](#reference-metal-implementation) — a Metal-specific implementation for running the models on Apple Silicon hardware
213
+ - **Tools:**
214
+ - [`browser`](#browser) — a reference implementation of the browser tool the models got trained on
215
+ - [`python`](#python) — a stateless reference implementation of the python tool the model got trained on
216
+ - **Client examples:**
217
+ - [`chat`](#terminal-chat) — a basic terminal chat application that uses the PyTorch or Triton implementations for inference along with the python and browser tools
218
+ - [`responses_api`](#responses-api) — an example Responses API compatible server that implements the browser tool along with other Responses-compatible functionality
219
+
220
+ ## Setup
221
+
222
+ ### Requirements
223
+
224
+ - Python 3.12
225
+ - On macOS: Install the Xcode CLI tools --> `xcode-select --install`
226
+ - On Linux: These reference implementations require CUDA
227
+ - On Windows: These reference implementations have not been tested on Windows. Try using solutions like Ollama if you are trying to run the model locally.
228
+
229
+ ### Installation
230
+
231
+ If you want to try any of the code you can install it directly from [PyPI](https://pypi.org/project/gpt-oss/)
232
+
233
+ ```shell
234
+ # if you just need the tools
235
+ pip install gpt-oss
236
+ # if you want to try the torch implementation
237
+ pip install gpt-oss[torch]
238
+ # if you want to try the triton implementation
239
+ pip install gpt-oss[triton]
240
+ ```
241
+
242
+ If you want to modify the code or try the metal implementation set the project up locally:
243
+
244
+ ```shell
245
+ git clone https://github.com/openai/gpt-oss.git
246
+ GPTOSS_BUILD_METAL=1 pip install -e ".[metal]"
247
+ ```
248
+
249
+ ## Download the model
250
+
251
+ You can download the model weights from the [Hugging Face Hub](https://huggingface.co/collections/openai/gpt-oss-68911959590a1634ba11c7a4) directly from Hugging Face CLI:
252
+
253
+ ```shell
254
+ # gpt-oss-120b
255
+ hf download openai/gpt-oss-120b --include "original/*" --local-dir gpt-oss-120b/
256
+
257
+ # gpt-oss-20b
258
+ hf download openai/gpt-oss-20b --include "original/*" --local-dir gpt-oss-20b/
259
+ ```
260
+
261
+ ## Reference PyTorch implementation
262
+
263
+ We include an inefficient reference PyTorch implementation in [gpt_oss/torch/model.py](gpt_oss/torch/model.py). This code uses basic PyTorch operators to show the exact model architecture, with a small addition of supporting tensor parallelism in MoE so that the larger model can run with this code (e.g., on 4xH100 or 2xH200). In this implementation, we upcast all weights to BF16 and run the model in BF16.
264
+
265
+ To run the reference implementation, install the dependencies:
266
+
267
+ ```shell
268
+ pip install -e ".[torch]"
269
+ ```
270
+
271
+ And then run:
272
+
273
+ ```shell
274
+ # On 4xH100:
275
+ torchrun --nproc-per-node=4 -m gpt_oss.generate gpt-oss-120b/original/
276
+ ```
277
+
278
+ ## Reference Triton implementation (single GPU)
279
+
280
+ We also include an optimized reference implementation that uses [an optimized triton MoE kernel](https://github.com/triton-lang/triton/tree/main/python/triton_kernels/triton_kernels) that supports MXFP4. It also has some optimization on the attention code to reduce the memory cost. To run this implementation, the nightly version of triton and torch will be installed. This version can be run on a single 80GB GPU for `gpt-oss-120b`.
281
+
282
+ To install the reference Triton implementation run
283
+
284
+ ```shell
285
+ # You need to install triton from source to use the triton implementation
286
+ git clone https://github.com/triton-lang/triton
287
+ cd triton/
288
+ pip install -r python/requirements.txt
289
+ pip install -e . --verbose --no-build-isolation
290
+ pip install -e python/triton_kernels
291
+
292
+ # Install the gpt-oss triton implementation
293
+ pip install -e ".[triton]"
294
+ ```
295
+
296
+ And then run:
297
+
298
+ ```shell
299
+ # On 1xH100
300
+ export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
301
+ python -m gpt_oss.generate --backend triton gpt-oss-120b/original/
302
+ ```
303
+
304
+ If you encounter `torch.OutOfMemoryError`, make sure to turn on the expandable allocator to avoid crashes when loading weights from the checkpoint.
305
+
306
+ ## Reference Metal implementation
307
+
308
+ Additionally we are providing a reference implementation for Metal to run on Apple Silicon. This implementation is not production-ready but is accurate to the PyTorch implementation.
309
+
310
+ The implementation will get automatically compiled when running the `.[metal]` installation on an Apple Silicon device:
311
+
312
+ ```shell
313
+ GPTOSS_BUILD_METAL=1 pip install -e ".[metal]"
314
+ ```
315
+
316
+ To perform inference you'll need to first convert the SafeTensor weights from Hugging Face into the right format using:
317
+
318
+ ```shell
319
+ python gpt_oss/metal/scripts/create-local-model.py -s <model_dir> -d <output_file>
320
+ ```
321
+
322
+ Or download the pre-converted weights:
323
+
324
+ ```shell
325
+ hf download openai/gpt-oss-120b --include "metal/*" --local-dir gpt-oss-120b/metal/
326
+ hf download openai/gpt-oss-20b --include "metal/*" --local-dir gpt-oss-20b/metal/
327
+ ```
328
+
329
+ To test it you can run:
330
+
331
+ ```shell
332
+ python gpt_oss/metal/examples/generate.py gpt-oss-20b/metal/model.bin -p "why did the chicken cross the road?"
333
+ ```
334
+
335
+ ## Harmony format & tools
336
+
337
+ Along with the model, we are also releasing a new chat format library `harmony` to interact with the model. Check [this guide](https://cookbook.openai.com/articles/openai-harmony) for more info about harmony.
338
+
339
+ We also include two system tools for the model: browsing and python container. Check [gpt_oss/tools](gpt_oss/tools) for the tool implementation.
340
+
341
+ ## Clients
342
+
343
+ ### Terminal Chat
344
+
345
+ The terminal chat application is a basic example of how to use the harmony format together with the PyTorch, Triton, and vLLM implementations. It also exposes both the python and browser tool as optional tools that can be used.
346
+
347
+ ```bash
348
+ usage: python -m gpt_oss.chat [-h] [-r REASONING_EFFORT] [-a] [-b] [--show-browser-results] [-p] [--developer-message DEVELOPER_MESSAGE] [-c CONTEXT] [--raw] [--backend {triton,torch,vllm}] FILE
349
+
350
+ Chat example
351
+
352
+ positional arguments:
353
+ FILE Path to the SafeTensors checkpoint
354
+
355
+ options:
356
+ -h, --help show this help message and exit
357
+ -r REASONING_EFFORT, --reasoning-effort REASONING_EFFORT
358
+ Reasoning effort (default: low)
359
+ -a, --apply-patch Make apply_patch tool available to the model (default: False)
360
+ -b, --browser Use browser tool (default: False)
361
+ --show-browser-results
362
+ Show browser results (default: False)
363
+ -p, --python Use python tool (default: False)
364
+ --developer-message DEVELOPER_MESSAGE
365
+ Developer message (default: )
366
+ -c CONTEXT, --context CONTEXT
367
+ Max context length (default: 8192)
368
+ --raw Raw mode (does not render Harmony encoding) (default: False)
369
+ --backend {triton,torch,vllm}
370
+ Inference backend (default: triton)
371
+ ```
372
+
373
+ > [!NOTE]
374
+ > The torch and triton implementations require original checkpoint under `gpt-oss-120b/original/` and `gpt-oss-20b/original/` respectively. While vLLM uses the Hugging Face converted checkpoint under `gpt-oss-120b/` and `gpt-oss-20b/` root directory respectively.
375
+
376
+ ### Responses API
377
+
378
+ We also include an example Responses API server. This server does not implement every feature and event of the Responses API but should be compatible with most of the basic use cases and serve as inspiration for anyone building their own server. Some of our inference partners are also offering their own Responses API.
379
+
380
+ You can start this server with the following inference backends:
381
+
382
+ - `triton` — uses the triton implementation
383
+ - `metal` — uses the metal implementation on Apple Silicon only
384
+ - `ollama` — uses the Ollama /api/generate API as an inference solution
385
+ - `vllm` — uses your installed vllm version to perform inference
386
+ - `transformers` — uses your installed transformers version to perform local inference
387
+
388
+ ```bash
389
+ usage: python -m gpt_oss.responses_api.serve [-h] [--checkpoint FILE] [--port PORT] [--inference-backend BACKEND]
390
+
391
+ Responses API server
392
+
393
+ options:
394
+ -h, --help show this help message and exit
395
+ --checkpoint FILE Path to the SafeTensors checkpoint
396
+ --port PORT Port to run the server on
397
+ --inference-backend BACKEND Inference backend to use
398
+ ```
399
+
400
+ ### Codex
401
+
402
+ We support [codex](https://github.com/openai/codex) as a client for gpt-oss. To run the 20b version, set this to `~/.codex/config.toml`:
403
+
404
+ ```
405
+ disable_response_storage = true
406
+ show_reasoning_content = true
407
+
408
+ [model_providers.local]
409
+ name = "local"
410
+ base_url = "http://localhost:11434/v1"
411
+
412
+ [profiles.oss]
413
+ model = "gpt-oss:20b"
414
+ model_provider = "local"
415
+ ```
416
+
417
+ This will work with any chat completions-API compatible server listening on port 11434, like ollama. Start the server and point codex to the oss model:
418
+
419
+ ```
420
+ ollama run gpt-oss:20b
421
+ codex -p oss
422
+ ```
423
+
424
+ ## Tools
425
+
426
+ ### Browser
427
+
428
+ > [!WARNING]
429
+ > This implementation is purely for educational purposes and should not be used in production. You should implement your own equivalent of the [`YouComBackend`](gpt_oss/tools/simple_browser/backend.py) class with your own browsing environment. Currently we have available `YouComBackend` and `ExaBackend`.
430
+
431
+ Both gpt-oss models were trained with the capability to browse using the `browser` tool that exposes the following three methods:
432
+
433
+ - `search` to search for key phrases
434
+ - `open` to open a particular page
435
+ - `find` to look for contents on a page
436
+
437
+ #### Usage
438
+
439
+ To enable the browser tool, you'll have to place the definition into the `system` message of your harmony formatted prompt. You can either use the `with_browser_tool()` method if your tool implements the full interface or modify the definition using `with_tools()`. For example:
440
+
441
+ ```python
442
+ import datetime
443
+ from gpt_oss.tools.simple_browser import SimpleBrowserTool
444
+ from gpt_oss.tools.simple_browser.backend import YouComBackend
445
+ from openai_harmony import SystemContent, Message, Conversation, Role, load_harmony_encoding, HarmonyEncodingName
446
+
447
+ encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
448
+
449
+ # Depending on the choice of the browser backend you need corresponding env variables setup
450
+ # In case you use You.com backend requires you to have set the YDC_API_KEY environment variable,
451
+ # while for Exa you might need EXA_API_KEY environment variable set
452
+ backend = YouComBackend(
453
+ source="web",
454
+ )
455
+ # backend = ExaBackend(
456
+ # source="web",
457
+ # )
458
+ browser_tool = SimpleBrowserTool(backend=backend)
459
+
460
+ # create a basic system prompt
461
+ system_message_content = SystemContent.new().with_conversation_start_date(
462
+ datetime.datetime.now().strftime("%Y-%m-%d")
463
+ )
464
+
465
+ # if you want to use the browser tool
466
+ if use_browser_tool:
467
+ # enables the tool
468
+ system_message_content = system_message_content.with_tools(browser_tool.tool_config)
469
+ # alternatively you could use the following if your tool is not stateless
470
+ system_message_content = system_message_content.with_browser_tool()
471
+
472
+ # construct the system message
473
+ system_message = Message.from_role_and_content(Role.SYSTEM, system_message_content)
474
+
475
+ # create the overall prompt
476
+ messages = [system_message, Message.from_role_and_content(Role.USER, "What's the weather in SF?")]
477
+ conversation = Conversation.from_messages(messages)
478
+
479
+ # convert to tokens
480
+ token_ids = encoding.render_conversation_for_completion(conversation, Role.ASSISTANT)
481
+
482
+ # perform inference
483
+ # ...
484
+
485
+ # parse the output
486
+ messages = encoding.parse_messages_from_completion_tokens(output_tokens, Role.ASSISTANT)
487
+ last_message = messages[-1]
488
+ if last_message.recipient.startswith("browser"):
489
+ # perform browser call
490
+ response_messages = await browser_tool.process(last_message)
491
+
492
+ # extend the current messages and run inference again
493
+ messages.extend(response_messages)
494
+ ```
495
+
496
+ #### Details
497
+
498
+ To control the context window size this tool uses a scrollable window of text that the model can interact with. So it might fetch the first 50 lines of a page and then scroll to the next 20 lines after that. The model has also been trained to then use citations from this tool in its answers.
499
+
500
+ To improve performance the tool caches requests so that the model can revisit a different part of a page without having to reload the page. For that reason you should create a new browser instance for every request.
501
+
502
+ ### Python
503
+
504
+ The model was trained to use a python tool to perform calculations and other actions as part of its chain-of-thought. During the training the model used a stateful tool which makes running tools between CoT loops easier. This reference implementation, however, uses a stateless mode. As a result the PythonTool defines its own tool description to override the definition in [`openai-harmony`][harmony].
505
+
506
+ > [!WARNING]
507
+ > This implementation runs in a permissive Docker container which could be problematic in cases like prompt injections. It's serving as an example and you should consider implementing your own container restrictions in production.
508
+
509
+ #### Usage
510
+
511
+ To enable the python tool, you'll have to place the definition into the `system` message of your harmony formatted prompt. You can either use the `with_python()` method if your tool implements the full interface or modify the definition using `with_tools()`. For example:
512
+
513
+ ```python
514
+ import datetime
515
+ from gpt_oss.tools.python_docker.docker_tool import PythonTool
516
+ from openai_harmony import SystemContent, Message, Conversation, Role, load_harmony_encoding, HarmonyEncodingName
517
+
518
+ encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
519
+
520
+ python_tool = PythonTool()
521
+
522
+ # create a basic system prompt
523
+ system_message_content = SystemContent.new().with_conversation_start_date(
524
+ datetime.datetime.now().strftime("%Y-%m-%d")
525
+ )
526
+
527
+ # if you want to use the python tool
528
+ if use_python_tool:
529
+ # enables the tool making sure that the prompt gets set with the stateless tool description
530
+ system_message_content = system_message_content.with_tools(python_tool.tool_config)
531
+ # alternatively you could use the following if your tool is not stateless
532
+ system_message_content = system_message_content.with_python()
533
+
534
+ # construct the system message
535
+ system_message = Message.from_role_and_content(Role.SYSTEM, system_message_content)
536
+
537
+ # create the overall prompt
538
+ messages = [system_message, Message.from_role_and_content(Role.USER, "What's the square root of 9001?")]
539
+ conversation = Conversation.from_messages(messages)
540
+
541
+ # convert to tokens
542
+ token_ids = encoding.render_conversation_for_completion(conversation, Role.ASSISTANT)
543
+
544
+ # perform inference
545
+ # ...
546
+
547
+ # parse the output
548
+ messages = encoding.parse_messages_from_completion_tokens(output_tokens, Role.ASSISTANT)
549
+ last_message = messages[-1]
550
+ if last_message.recipient == "python":
551
+ # perform python call
552
+ response_messages = await python_tool.process(last_message)
553
+
554
+ # extend the current messages and run inference again
555
+ messages.extend(response_messages)
556
+ ```
557
+
558
+ ### Apply Patch
559
+
560
+ `apply_patch` can be used to create, update or delete files locally.
561
+
562
+ ## Other details
563
+
564
+ ### Precision format
565
+
566
+ We released the models with native quantization support. Specifically, we use [MXFP4](https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf) for the linear projection weights in the MoE layer. We store the MoE tensor in two parts:
567
+
568
+ - `tensor.blocks` stores the actual fp4 values. We pack every two values in one `uint8` value.
569
+ - `tensor.scales` stores the block scale. The block scaling is done among the last dimension for all MXFP4 tensors.
570
+
571
+ All other tensors will be in BF16. We also recommend using BF16 as the activation precision for the model.
572
+
573
+ ### Recommended Sampling Parameters
574
+
575
+ We recommend sampling with `temperature=1.0` and `top_p=1.0`.
576
+
577
+ ## Contributing
578
+
579
+ The reference implementations in this repository are meant as a starting point and inspiration. Outside of bug fixes we do not intend to accept new feature contributions. If you build implementations based on this code such as new tool implementations you are welcome to contribute them to the [`awesome-gpt-oss.md`](./awesome-gpt-oss.md) file.
580
+
581
+ [harmony]: https://github.com/openai/harmony
582
+
583
+ ## Citation
584
+
585
+ ```bibtex
586
+ @misc{openai2025gptoss120bgptoss20bmodel,
587
+ title={gpt-oss-120b & gpt-oss-20b Model Card},
588
+ author={OpenAI},
589
+ year={2025},
590
+ eprint={2508.10925},
591
+ archivePrefix={arXiv},
592
+ primaryClass={cs.CL},
593
+ url={https://arxiv.org/abs/2508.10925},
594
+ }
595
+ ```
USAGE_POLICY ADDED
@@ -0,0 +1 @@
 
 
1
+ We aim for our tools to be used safely, responsibly, and democratically, while maximizing your control over how you use them. By using OpenAI gpt-oss-120b and gpt-oss-20b, you agree to comply with all applicable law.
_build/gpt_oss_build_backend/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """In-tree PEP 517 backend package for gpt-oss."""
_build/gpt_oss_build_backend/backend.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Build backend for gpt-oss that supports two modes:
3
+
4
+ 1) Default (pure wheel for PyPI)
5
+ - Delegates to setuptools.build_meta.
6
+ - Produces a py3-none-any wheel so PyPI accepts it (no linux_x86_64 tag).
7
+
8
+ 2) Optional Metal/C extension build (local only)
9
+ - If the environment variable GPTOSS_BUILD_METAL is set to a truthy value
10
+ (1/true/on/yes), delegates to scikit_build_core.build.
11
+ - Dynamically injects build requirements (scikit-build-core, cmake, ninja,
12
+ pybind11) only for this mode.
13
+
14
+ Why this is needed
15
+ - PyPI rejects Linux wheels tagged linux_x86_64; manylinux/musllinux is required
16
+ for binary wheels. We ship a pure wheel by default, but still allow developers
17
+ to build/install the native Metal backend locally when needed.
18
+
19
+ Typical usage
20
+ - Publish pure wheel: `python -m build` (do not set GPTOSS_BUILD_METAL).
21
+ - Local Metal dev: `GPTOSS_BUILD_METAL=1 pip install -e ".[metal]"`.
22
+ - CI: keep GPTOSS_BUILD_METAL unset for releases; set it in internal jobs that
23
+ exercise the extension.
24
+
25
+ Notes
26
+ - The base package remains importable without the extension. The Metal backend
27
+ is only used when `gpt_oss.metal` is explicitly imported.
28
+ - This file is discovered via `backend-path = ["_build"]` and
29
+ `build-backend = "gpt_oss_build_backend.backend"` in pyproject.toml.
30
+ """
31
+ import os
32
+ from importlib import import_module
33
+ from typing import Any, Mapping, Sequence
34
+
35
+
36
+ TRUE_VALUES = {"1", "true", "TRUE", "on", "ON", "yes", "YES"}
37
+
38
+
39
+ def _use_metal_backend() -> bool:
40
+ return str(os.environ.get("GPTOSS_BUILD_METAL", "")).strip() in TRUE_VALUES
41
+
42
+
43
+ def _setuptools_backend():
44
+ from setuptools import build_meta as _bm # type: ignore
45
+
46
+ return _bm
47
+
48
+
49
+ def _scikit_build_backend():
50
+ return import_module("scikit_build_core.build")
51
+
52
+
53
+ def _backend():
54
+ return _scikit_build_backend() if _use_metal_backend() else _setuptools_backend()
55
+
56
+
57
+ # Required PEP 517 hooks
58
+
59
+ def build_wheel(
60
+ wheel_directory: str,
61
+ config_settings: Mapping[str, Any] | None = None,
62
+ metadata_directory: str | None = None,
63
+ ) -> str:
64
+ return _backend().build_wheel(wheel_directory, config_settings, metadata_directory)
65
+
66
+
67
+ def build_sdist(
68
+ sdist_directory: str, config_settings: Mapping[str, Any] | None = None
69
+ ) -> str:
70
+ return _backend().build_sdist(sdist_directory, config_settings)
71
+
72
+
73
+ def prepare_metadata_for_build_wheel(
74
+ metadata_directory: str, config_settings: Mapping[str, Any] | None = None
75
+ ) -> str:
76
+ # Fallback if backend doesn't implement it
77
+ be = _backend()
78
+ fn = getattr(be, "prepare_metadata_for_build_wheel", None)
79
+ if fn is None:
80
+ # setuptools exposes it; scikit-build-core may not. Defer to building a wheel for metadata.
81
+ return _setuptools_backend().prepare_metadata_for_build_wheel(
82
+ metadata_directory, config_settings
83
+ )
84
+ return fn(metadata_directory, config_settings)
85
+
86
+
87
+ # Optional hooks
88
+
89
+ def build_editable(
90
+ editable_directory: str, config_settings: Mapping[str, Any] | None = None, metadata_directory: str | None = None
91
+ ) -> str:
92
+ be = _backend()
93
+ fn = getattr(be, "build_editable", None)
94
+ if fn is None:
95
+ # setuptools implements build_editable; if not available, raise the standard error
96
+ raise RuntimeError("Editable installs not supported by the selected backend")
97
+ return fn(editable_directory, config_settings)
98
+
99
+
100
+ def get_requires_for_build_wheel(
101
+ config_settings: Mapping[str, Any] | None = None,
102
+ ) -> Sequence[str]:
103
+ if _use_metal_backend():
104
+ # Add dynamic build requirements only when building the Metal backend
105
+ return [
106
+ "scikit-build-core>=0.10",
107
+ "pybind11>=2.12",
108
+ "cmake>=3.26",
109
+ "ninja",
110
+ ]
111
+ # setuptools usually returns []
112
+ return list(_setuptools_backend().get_requires_for_build_wheel(config_settings))
113
+
114
+
115
+ def get_requires_for_build_sdist(
116
+ config_settings: Mapping[str, Any] | None = None,
117
+ ) -> Sequence[str]:
118
+ # No special requirements for SDist
119
+ be = _backend()
120
+ fn = getattr(be, "get_requires_for_build_sdist", None)
121
+ if fn is None:
122
+ return []
123
+ return list(fn(config_settings))
124
+
125
+
126
+ def get_requires_for_build_editable(
127
+ config_settings: Mapping[str, Any] | None = None,
128
+ ) -> Sequence[str]:
129
+ if _use_metal_backend():
130
+ return [
131
+ "scikit-build-core>=0.10",
132
+ "pybind11>=2.12",
133
+ "cmake>=3.26",
134
+ "ninja",
135
+ ]
136
+ be = _setuptools_backend()
137
+ fn = getattr(be, "get_requires_for_build_editable", None)
138
+ if fn is None:
139
+ return []
140
+ return list(fn(config_settings))
awesome-gpt-oss.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ![gpt-oss](./docs/gpt-oss.svg)
2
+
3
+ # Awesome gpt-oss
4
+
5
+ This is a list of guides and resources to help you get started with the gpt-oss models.
6
+
7
+ - [Inference](#inference)
8
+ - [Local](#local)
9
+ - [Server](#server)
10
+ - [Cloud](#cloud)
11
+ - [Examples / Tutorials](#examples--tutorials)
12
+ - [Tools](#tools)
13
+ - [Training](#training)
14
+
15
+ ## Inference
16
+
17
+ ### Local
18
+
19
+ - Ollama
20
+ - [How to run gpt-oss locally with Ollama](https://cookbook.openai.com/articles/gpt-oss/run-locally-ollama)
21
+ - [Ollama & gpt-oss launch blog](https://ollama.com/blog/gpt-oss)
22
+ - [Check out the models Ollama](https://ollama.com/library/gpt-oss)
23
+ - LM Studio
24
+ - [LM Studio & gpt-oss launch blog](https://lmstudio.ai/blog/gpt-oss)
25
+ - [Use gpt-oss-20b with LM Studio](https://lmstudio.ai/models/openai/gpt-oss-20b)
26
+ - [Use gpt-oss-120b with LM Studio](https://lmstudio.ai/models/openai/gpt-oss-120b)
27
+ - Hugging Face & Transformers
28
+ - [How to run gpt-oss with Transformers](https://cookbook.openai.com/articles/gpt-oss/run-transformers)
29
+ - [Hugging Face & gpt-oss launch blog](https://huggingface.co/blog/welcome-openai-gpt-oss)
30
+ - [Collection of Hugging Face examples](https://github.com/huggingface/gpt-oss-recipes)
31
+ - NVIDIA
32
+ - [gpt-oss on RTX](https://blogs.nvidia.com/blog/rtx-ai-garage-openai-oss)
33
+ - AMD
34
+ - [Running gpt-oss models on AMD Ryzen AI Processors and Radeon Graphics Cards](https://www.amd.com/en/blogs/2025/how-to-run-openai-gpt-oss-20b-120b-models-on-amd-ryzen-ai-radeon.html)
35
+ - [Running gpt-oss on STX Halo and Radeon dGPUs using Lemonade](https://lemonade-server.ai/news/gpt-oss.html)
36
+ - llama.cpp
37
+ - [Running gpt-oss with llama.cpp](https://github.com/ggml-org/llama.cpp/discussions/15396)
38
+
39
+ ### Server
40
+
41
+ - vLLM
42
+ - [How to run gpt-oss with vLLM](https://cookbook.openai.com/articles/gpt-oss/run-vllm)
43
+ - [vLLM & gpt-oss recipies](https://docs.vllm.ai/projects/recipes/en/latest/OpenAI/GPT-OSS.html)
44
+ - NVIDIA
45
+ - [Optimizing gpt-oss with NVIDIA TensorRT-LLM](https://cookbook.openai.com/articles/run-nvidia)
46
+ - [Deploying gpt-oss on TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM/blob/main/docs/source/blogs/tech_blog/blog9_Deploying_GPT_OSS_on_TRTLLM.md)
47
+ - AMD
48
+ - [Running the Latest Open Models from OpenAI on AMD AI Hardware](https://rocm.blogs.amd.com/ecosystems-and-partners/openai-day-0/README.html)
49
+
50
+ ### Cloud
51
+
52
+ - Groq
53
+ - [Groq & gpt-oss launch blog](https://groq.com/blog/day-zero-support-for-openai-open-models)
54
+ - [gpt-oss-120b model on the GroqCloud Playground](https://console.groq.com/playground?model=openai/gpt-oss-120b)
55
+ - [gpt-oss-20b model on the GroqCloud Playground](https://console.groq.com/playground?model=openai/gpt-oss-20b)
56
+ - [gpt-oss with built-in web search on GroqCloud](https://console.groq.com/docs/browser-search)
57
+ - [gpt-oss with built-in code execution on GroqCloud](https://console.groq.com/docs/code-execution)
58
+ - [Responses API on Groq](https://console.groq.com/docs/responses-api)
59
+ - NVIDIA
60
+ - [NVIDIA launch blog post](https://blogs.nvidia.com/blog/openai-gpt-oss/)
61
+ - [NVIDIA & gpt-oss developer launch blog post](https://developer.nvidia.com/blog/delivering-1-5-m-tps-inference-on-nvidia-gb200-nvl72-nvidia-accelerates-openai-gpt-oss-models-from-cloud-to-edge/)
62
+ - Use [gpt-oss-120b](https://build.nvidia.com/openai/gpt-oss-120b) and [gpt-oss-20b](https://build.nvidia.com/openai/gpt-oss-20b) on NVIDIA's Cloud
63
+ - Cloudflare
64
+ - [Cloudflare & gpt-oss launch blog post](https://blog.cloudflare.com/openai-gpt-oss-on-workers-ai)
65
+ - [gpt-oss-120b on Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai/models/gpt-oss-120b)
66
+ - [gpt-oss-20b on Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai/models/gpt-oss-20b)
67
+ - AMD
68
+ - [gpt-oss-120B on AMD MI300X](https://huggingface.co/spaces/amd/gpt-oss-120b-chatbot)
69
+ - AWS
70
+ - Deploy via Tensorfuse: [Deploy gpt-oss for both 20b and 120b models on AWS EKS](https://tensorfuse.io/docs/guides/modality/text/openai_oss)
71
+ - [AWS launch blog post](https://aws.amazon.com/blogs/aws/openai-open-weight-models-now-available-on-aws/)
72
+
73
+ ## Examples & Tutorials
74
+
75
+ - [OpenAI harmony response format](https://cookbook.openai.com/articles/openai-harmony)
76
+
77
+ ## Tools
78
+
79
+ - [Example `python` tool for gpt-oss](./gpt_oss/tools/python_docker/)
80
+ - [Example `browser` tool for gpt-oss](./gpt_oss/tools/simple_browser/)
81
+
82
+ ## Training
83
+
84
+ - [Hugging Face TRL examples](https://github.com/huggingface/gpt-oss-recipes)
85
+ - [LlamaFactory examples](https://llamafactory.readthedocs.io/en/latest/advanced/best_practice/gpt-oss.html)
86
+ - [Unsloth examples](https://docs.unsloth.ai/basics/gpt-oss-how-to-run-and-fine-tune)
87
+
88
+ ## Contributing
89
+
90
+ Feel free to open a PR to add your own guides and resources on how to run gpt-oss. We will try to review it and add it here.
compatibility-test/.gitignore ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ lerna-debug.log*
8
+
9
+ # Diagnostic reports (https://nodejs.org/api/report.html)
10
+ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11
+
12
+ # Runtime data
13
+ pids
14
+ *.pid
15
+ *.seed
16
+ *.pid.lock
17
+
18
+ # Directory for instrumented libs generated by jscoverage/JSCover
19
+ lib-cov
20
+
21
+ # Coverage directory used by tools like istanbul
22
+ coverage
23
+ *.lcov
24
+
25
+ # nyc test coverage
26
+ .nyc_output
27
+
28
+ # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29
+ .grunt
30
+
31
+ # Bower dependency directory (https://bower.io/)
32
+ bower_components
33
+
34
+ # node-waf configuration
35
+ .lock-wscript
36
+
37
+ # Compiled binary addons (https://nodejs.org/api/addons.html)
38
+ build/Release
39
+
40
+ # Dependency directories
41
+ node_modules/
42
+ jspm_packages/
43
+
44
+ # Snowpack dependency directory (https://snowpack.dev/)
45
+ web_modules/
46
+
47
+ # TypeScript cache
48
+ *.tsbuildinfo
49
+
50
+ # Optional npm cache directory
51
+ .npm
52
+
53
+ # Optional eslint cache
54
+ .eslintcache
55
+
56
+ # Optional stylelint cache
57
+ .stylelintcache
58
+
59
+ # Optional REPL history
60
+ .node_repl_history
61
+
62
+ # Output of 'npm pack'
63
+ *.tgz
64
+
65
+ # Yarn Integrity file
66
+ .yarn-integrity
67
+
68
+ # dotenv environment variable files
69
+ .env
70
+ .env.*
71
+ !.env.example
72
+
73
+ # parcel-bundler cache (https://parceljs.org/)
74
+ .cache
75
+ .parcel-cache
76
+
77
+ # Next.js build output
78
+ .next
79
+ out
80
+
81
+ # Nuxt.js build / generate output
82
+ .nuxt
83
+ dist
84
+
85
+ # Gatsby files
86
+ .cache/
87
+ # Comment in the public line in if your project uses Gatsby and not Next.js
88
+ # https://nextjs.org/blog/next-9-1#public-directory-support
89
+ # public
90
+
91
+ # vuepress build output
92
+ .vuepress/dist
93
+
94
+ # vuepress v2.x temp and cache directory
95
+ .temp
96
+ .cache
97
+
98
+ # Sveltekit cache directory
99
+ .svelte-kit/
100
+
101
+ # vitepress build output
102
+ **/.vitepress/dist
103
+
104
+ # vitepress cache directory
105
+ **/.vitepress/cache
106
+
107
+ # Docusaurus cache and generated files
108
+ .docusaurus
109
+
110
+ # Serverless directories
111
+ .serverless/
112
+
113
+ # FuseBox cache
114
+ .fusebox/
115
+
116
+ # DynamoDB Local files
117
+ .dynamodb/
118
+
119
+ # Firebase cache directory
120
+ .firebase/
121
+
122
+ # TernJS port file
123
+ .tern-port
124
+
125
+ # Stores VSCode versions used for testing VSCode extensions
126
+ .vscode-test
127
+
128
+ # yarn v3
129
+ .pnp.*
130
+ .yarn/*
131
+ !.yarn/patches
132
+ !.yarn/plugins
133
+ !.yarn/releases
134
+ !.yarn/sdks
135
+ !.yarn/versions
136
+
137
+ # Vite logs files
138
+ vite.config.js.timestamp-*
139
+ vite.config.ts.timestamp-*
140
+
141
+ rollout_*.jsonl
142
+ analysis_*.json
compatibility-test/README.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # API Compatibility Test
2
+
3
+ This script uses the Agents SDK in TypeScript and the underlying OpenAI client to verify the shape of the API calls but also whether the API performs tool calling.
4
+
5
+ ## What it tests
6
+
7
+ 1.
8
+
9
+ ## How to run
10
+
11
+ 0. Run `npm install` in this directory.
12
+ 1. Update `providers.ts` to create an entry for the API to test. Change `vllm` to the provider name of your choice. Use `chat` for Chat Completions tests and `responses` for Responses API tests.
13
+ 2. Run an initial quick test to make sure things work. This will only run one test
14
+
15
+ ```
16
+ npm start -- --provider <name> -n 1 -k 1
17
+ ```
18
+
19
+ 3. Run the full test (runs each test 5 times to test consistency)
20
+
21
+ ```
22
+ npm start -- --provider <name> -k 5
23
+ ```
24
+
25
+ ## Considerations
26
+
27
+ 1. The tests will fail if the API shape does not match the expected behavior
28
+ 2. Events in the chat API are currently not tested
29
+ 3. If the schema validation succeeds but the input is wrong the test will still pass for this test. That's because it's likely more of a prompt engineering issue or a validator issue than an API issue as it still nailed the input
compatibility-test/analysis.ts ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function analyze(caseResults: any[], tries: number) {
2
+ // Group results by unique task: test_case + apiType
3
+ type TaskKey = string;
4
+ const taskKeyFor = (r: any): TaskKey =>
5
+ `${r.test_case}::${r.result?.apiType}`;
6
+
7
+ const successesByTask: Map<TaskKey, Map<number, boolean>> = new Map();
8
+
9
+ // Count wrong-input tool calls (schema correct but incorrect arguments)
10
+ let wrongInputToolCalls = 0;
11
+
12
+ // Count invalid response shapes per API type
13
+ const totalByApiType: Record<string, number> = {};
14
+ const invalidByApiType: Record<string, number> = {};
15
+
16
+ for (const r of caseResults) {
17
+ if (!r?.result || typeof r.result.apiType !== "string") continue;
18
+
19
+ // Parse attempt index from run_id `${i}_${k}` safely
20
+ let attemptIndex: number | undefined;
21
+ if (typeof r.run_id === "string") {
22
+ const parts = r.run_id.split("_");
23
+ const k = Number(parts[1]);
24
+ if (Number.isFinite(k)) attemptIndex = k;
25
+ }
26
+
27
+ const key = taskKeyFor(r);
28
+ if (!successesByTask.has(key)) successesByTask.set(key, new Map());
29
+ if (attemptIndex != null) {
30
+ successesByTask.get(key)!.set(attemptIndex, Boolean(r.success));
31
+ }
32
+
33
+ const d = r.result.toolCallingDetails ?? {};
34
+ const calledToolAtLeastOnce = Boolean(d.calledToolAtLeastOnce);
35
+ const calledToolWithRightSchema = Boolean(d.calledToolWithRightSchema);
36
+ const calledToolWithRightArguments = Boolean(
37
+ d.calledToolWithRightArguments
38
+ );
39
+ if (
40
+ calledToolAtLeastOnce &&
41
+ calledToolWithRightSchema &&
42
+ !calledToolWithRightArguments
43
+ ) {
44
+ wrongInputToolCalls++;
45
+ }
46
+
47
+ // Track invalid/total per apiType for response shape
48
+ const apiType = r.result.apiType as string;
49
+ totalByApiType[apiType] = (totalByApiType[apiType] ?? 0) + 1;
50
+ const isValidResponse = r.result.validResponse === true;
51
+ if (!isValidResponse) {
52
+ invalidByApiType[apiType] = (invalidByApiType[apiType] ?? 0) + 1;
53
+ }
54
+ }
55
+
56
+ const totalTasks = successesByTask.size;
57
+
58
+ // Compute pass@k and pass^k for k = 1..tries
59
+ const passAtKByK: number[] = [];
60
+ const passHatKByK: number[] = [];
61
+
62
+ for (let k = 1; k <= tries; k++) {
63
+ let tasksSuccessfulK = 0; // any success in first k attempts
64
+ let tasksAllSuccessfulK = 0; // all success in first k attempts
65
+
66
+ for (const [, attemptsMap] of successesByTask) {
67
+ let anySuccess = false;
68
+ let allSuccess = true;
69
+ for (let i = 0; i < k; i++) {
70
+ const v = attemptsMap.get(i) === true;
71
+ anySuccess = anySuccess || v;
72
+ if (!v) allSuccess = false;
73
+ }
74
+ if (anySuccess) tasksSuccessfulK++;
75
+ if (allSuccess) tasksAllSuccessfulK++;
76
+ }
77
+
78
+ const passAtK = totalTasks > 0 ? tasksSuccessfulK / totalTasks : 0;
79
+ const passHatK = totalTasks > 0 ? tasksAllSuccessfulK / totalTasks : 0;
80
+ passAtKByK.push(passAtK);
81
+ passHatKByK.push(passHatK);
82
+ }
83
+
84
+ // Convenience: final k=tries values
85
+ const passAtK = passAtKByK[tries - 1] ?? 0;
86
+ const passHatK = passHatKByK[tries - 1] ?? 0;
87
+
88
+ return {
89
+ totalTasks,
90
+ passAtKByK,
91
+ passHatKByK,
92
+ passAtK,
93
+ passHatK,
94
+ wrongInputToolCalls,
95
+ // New stats for invalid response shapes per API
96
+ invalidByApiType,
97
+ totalByApiType,
98
+ };
99
+ }
100
+
101
+ export function printAnalysis(
102
+ stats: ReturnType<typeof analyze>,
103
+ caseResults: any[],
104
+ provider: string,
105
+ selectedLines: string[],
106
+ tries: number,
107
+ skipped: number,
108
+ analysisFile: string
109
+ ) {
110
+ const formatPerK = (arr: number[]) =>
111
+ Array.from({ length: tries }, (_, i) => {
112
+ const v = arr[i] ?? 0;
113
+ return `${i + 1}=${v.toFixed(3)}`;
114
+ }).join(", ");
115
+
116
+ console.log("Summary:");
117
+ console.log(` Provider: ${provider}`);
118
+ console.log(` Total input cases: ${selectedLines.length}`);
119
+ console.log(` Tries: ${tries}`);
120
+ console.log(` Total tasks: ${stats.totalTasks}`);
121
+ console.log(` Total runs: ${caseResults.length}`);
122
+ // Conditionally print invalid response shape stats per API type
123
+ if ((stats.totalByApiType["responses"] ?? 0) > 0) {
124
+ const bad = stats.invalidByApiType["responses"] ?? 0;
125
+ const tot = stats.totalByApiType["responses"] ?? 0;
126
+ console.log(` Invalid Responses API responses: ${bad} (out of ${tot})`);
127
+ }
128
+ if ((stats.totalByApiType["chat"] ?? 0) > 0) {
129
+ const bad = stats.invalidByApiType["chat"] ?? 0;
130
+ const tot = stats.totalByApiType["chat"] ?? 0;
131
+ console.log(
132
+ ` Invalid Chat Completions API responses: ${bad} (out of ${tot})`
133
+ );
134
+ }
135
+ console.log(` pass@k (k=1..${tries}): ${formatPerK(stats.passAtKByK)}`);
136
+ console.log(` pass^k (k=1..${tries}): ${formatPerK(stats.passHatKByK)}`);
137
+ console.log(` pass@k (k=${tries}): ${stats.passAtK.toFixed(3)}`);
138
+ console.log(` pass^k (k=${tries}): ${stats.passHatK.toFixed(3)}`);
139
+ console.log(` Wrong-input tool calls: ${stats.wrongInputToolCalls}`);
140
+ console.log(` Invalid cases.jsonl lines: ${skipped}`);
141
+ console.log(` Analysis written to ${analysisFile}`);
142
+ }
compatibility-test/cases.jsonl ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"tool_name":"get_system_health","input":"Hey, quick check: is everything up and running?","expected_arguments":"{}"}
2
+ {"tool_name":"get_system_health","input":"Status report please.","expected_arguments":"{}"}
3
+ {"tool_name":"get_system_health","input":"Can you confirm the LLM health before we start?","expected_arguments":"{}"}
4
+ {"tool_name":"get_system_health","input":"Need a health snapshot.","expected_arguments":"{}"}
5
+ {"tool_name":"get_system_health","input":"Hi, what's the current system health?","expected_arguments":"{}"}
6
+ {"tool_name":"markdown_to_html","input":"Convert this markdown to HTML:\n\n# Title\n\nSome *italic* text.","expected_arguments":"{\"markdown\":\"# Title\\n\\nSome *italic* text.\"}"}
7
+ {"tool_name":"markdown_to_html","input":"Hey, could you turn `## Docs` into HTML?","expected_arguments":"{\"markdown\":\"## Docs\"}"}
8
+ {"tool_name":"markdown_to_html","input":"Please render the following markdown:\n\n- item 1\n- item 2","expected_arguments":"{\"markdown\":\"- item 1\\n- item 2\"}"}
9
+ {"tool_name":"markdown_to_html","input":"I have `**bold**` markdown; give me HTML.","expected_arguments":"{\"markdown\":\"**bold**\"}"}
10
+ {"tool_name":"markdown_to_html","input":"Markdown to HTML: > quote","expected_arguments":"{\"markdown\":\"> quote\"}"}
11
+ {"tool_name":"detect_language","input":"Hey, what language is this: 'Buenos días, ¿cómo estás?'","expected_arguments":"{\"text\":\"Buenos días, ¿cómo estás?\"}"}
12
+ {"tool_name":"detect_language","input":"Identify the language: \"Guten Morgen\"","expected_arguments":"{\"text\":\"Guten Morgen\"}"}
13
+ {"tool_name":"detect_language","input":"Language detection needed: こんにちは、お元気ですか?","expected_arguments":"{\"text\":\"こんにちは、お元気ですか?\"}"}
14
+ {"tool_name":"detect_language","input":"Detect language for: 'Привет, как дела?'","expected_arguments":"{\"text\":\"Привет, как дела?\"}"}
15
+ {"tool_name":"detect_language","input":"What language is 'Bonjour tout le monde'?","expected_arguments":"{\"text\":\"Bonjour tout le monde\"}"}
16
+ {"tool_name":"generate_chart","input":"Plot a simple line chart for these points: (1,2),(2,4),(3,9).","expected_arguments":"{\"data\":[[1,2],[2,4],[3,9]],\"chart_type\":\"line\"}"}
17
+ {"tool_name":"generate_chart","input":"Hey, can I get a bar chart of my sales: 10, 20, 30 across Q1–Q3?","expected_arguments":"{\"data\":[[1,10],[2,20],[3,30]],\"chart_type\":\"bar\",\"title\":\"Quarterly Sales\"}"}
18
+ {"tool_name":"generate_chart","input":"Make a scatter chart titled 'Experiment' with x label Time and y label Value for data [ [0,1], [1,1.5], [2,2.2] ].","expected_arguments":"{\"data\":[[0,1],[1,1.5],[2,2.2]],\"chart_type\":\"scatter\",\"title\":\"Experiment\",\"x_label\":\"Time\",\"y_label\":\"Value\"}"}
19
+ {"tool_name":"generate_chart","input":"Create a line chart of temperatures 70,72,68,65 over 4 days, label x as 'Day'.","expected_arguments":"{\"data\":[[1,70],[2,72],[3,68],[4,65]],\"chart_type\":\"line\",\"x_label\":\"Day\"}"}
20
+ {"tool_name":"generate_chart","input":"Visualize visits per day with a bar chart; numbers: 100,150,120.","expected_arguments":"{\"data\":[[1,100],[2,150],[3,120]],\"chart_type\":\"bar\",\"title\":\"Daily Visits\",\"y_label\":\"Visitors\"}"}
21
+ {"tool_name":"query_database","input":"Give me the ids and emails from users table, limit 5.","expected_arguments":"{\"table\":\"users\",\"columns\":[\"id\",\"email\"],\"limit\":5}"}
22
+ {"tool_name":"query_database","input":"Hey, fetch order_id and amount from orders where status is 'shipped'.","expected_arguments":"{\"table\":\"orders\",\"columns\":[\"order_id\",\"amount\"],\"filters\":\"status = 'shipped'\"}"}
23
+ {"tool_name":"query_database","input":"Retrieve name and price from products ordered by price descending, top 10 please.","expected_arguments":"{\"table\":\"products\",\"columns\":[\"name\",\"price\"],\"limit\":10,\"order_by\":\"price DESC\"}"}
24
+ {"tool_name":"query_database","input":"I need the first 3 log entries from audit_log table.","expected_arguments":"{\"table\":\"audit_log\",\"columns\":[\"id\",\"timestamp\",\"action\"],\"limit\":3}"}
25
+ {"tool_name":"query_database","input":"Query the customers table for name, city where city = 'Berlin'.","expected_arguments":"{\"table\":\"customers\",\"columns\":[\"name\",\"city\"],\"filters\":\"city = 'Berlin'\"}"}
26
+ {"tool_name":"get_weather","input":"What's the weather in San Francisco right now?","expected_arguments":"{\"location\":\"San Francisco\"}"}
27
+ {"tool_name":"get_weather","input":"Weather for Tokyo, please.","expected_arguments":"{\"location\":\"Tokyo\"}"}
28
+ {"tool_name":"get_weather","input":"Get me the current weather for 10001.","expected_arguments":"{\"location\":\"10001\"}"}
29
+ {"tool_name":"get_weather","input":"How's the weather in Paris today?","expected_arguments":"{\"location\":\"Paris\"}"}
30
+ {"tool_name":"get_weather","input":"Check the weather for Sydney.","expected_arguments":"{\"location\":\"Sydney\"}"}
compatibility-test/index.ts ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { parseArgs } from "node:util";
2
+ import { createWriteStream } from "node:fs";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { runCase, RunCaseSummary } from "./runCase";
7
+ import { Listr, ListrTaskWrapper } from "listr2";
8
+ import { analyze, printAnalysis } from "./analysis";
9
+
10
+ function formatTimestamp(d: Date): string {
11
+ const pad = (n: number) => String(n).padStart(2, "0");
12
+ const yyyy = d.getFullYear();
13
+ const mm = pad(d.getMonth() + 1);
14
+ const dd = pad(d.getDate());
15
+ const hh = pad(d.getHours());
16
+ const mi = pad(d.getMinutes());
17
+ const ss = pad(d.getSeconds());
18
+ return `${yyyy}${mm}${dd}_${hh}${mi}${ss}`;
19
+ }
20
+
21
+ async function main() {
22
+ const args = parseArgs({
23
+ options: {
24
+ cases: { type: "string", short: "c", default: "cases.jsonl" },
25
+ provider: { type: "string", short: "p", default: "openai" },
26
+ streaming: { type: "boolean", short: "s", default: false },
27
+ maxTurns: { type: "string", short: "t", default: "10" },
28
+ n: { type: "string", short: "n" },
29
+ strict: { type: "boolean", short: "s", default: false },
30
+ tries: { type: "string", short: "k", default: "1" },
31
+ },
32
+ });
33
+ const casesPathArg = args.values.cases;
34
+ const provider = args.values.provider as string;
35
+ const streaming = Boolean(args.values.streaming);
36
+ const maxTurns = Number(args.values.maxTurns ?? 10);
37
+ const nRaw = args.values.n as string | undefined;
38
+ const triesRaw = args.values.tries as string | undefined;
39
+ const tries = triesRaw != null ? Number(triesRaw) : 1;
40
+ const limit = nRaw != null ? Number(nRaw) : undefined;
41
+ if (limit != null && (!Number.isFinite(limit) || limit <= 0)) {
42
+ console.error("--n must be a positive integer");
43
+ process.exitCode = 1;
44
+ return;
45
+ }
46
+
47
+ if (!casesPathArg) {
48
+ console.error("--cases is required (path to JSONL file)");
49
+ process.exitCode = 1;
50
+ return;
51
+ }
52
+
53
+ const casesPath = path.isAbsolute(casesPathArg)
54
+ ? casesPathArg
55
+ : path.join(process.cwd(), casesPathArg);
56
+
57
+ const timestamp = formatTimestamp(new Date());
58
+ const defaultFilename = `rollout_${provider}_${timestamp}.jsonl`;
59
+ const outputFile = path.join(process.cwd(), defaultFilename);
60
+ const analysisFile = path.join(
61
+ process.cwd(),
62
+ `analysis_${provider}_${timestamp}.json`
63
+ );
64
+
65
+ let fileContent: string;
66
+ try {
67
+ fileContent = await readFile(casesPath, "utf8");
68
+ } catch (err: any) {
69
+ console.error(
70
+ `Failed to read cases file at ${casesPath}: ${err?.message ?? err}`
71
+ );
72
+ process.exitCode = 1;
73
+ return;
74
+ }
75
+
76
+ const lines = fileContent
77
+ .split(/\r?\n/)
78
+ .map((l) => l.trim())
79
+ .filter((l) => l.length > 0);
80
+
81
+ const selectedLines =
82
+ typeof limit === "number" ? lines.slice(0, limit) : lines;
83
+
84
+ const out = createWriteStream(outputFile, { flags: "w", encoding: "utf8" });
85
+
86
+ const writeLine = (obj: any) =>
87
+ new Promise<void>((resolve, reject) => {
88
+ const str = JSON.stringify(obj) + "\n";
89
+ out.write(str, (err) => (err ? reject(err) : resolve()));
90
+ });
91
+
92
+ // Accumulators for post-run analysis
93
+ let skipped = 0; // invalid JSON lines
94
+ const caseResults: Array<{
95
+ run_id: string;
96
+ success: boolean;
97
+ provider: string;
98
+ test_case: number;
99
+ tool_name: string;
100
+ input: string;
101
+ result: RunCaseSummary;
102
+ }> = [];
103
+
104
+ async function processIndex(
105
+ i: number,
106
+ k: number,
107
+ task: ListrTaskWrapper<any, any, any>
108
+ ) {
109
+ const line = selectedLines[i];
110
+ let caseObj: any;
111
+ try {
112
+ caseObj = JSON.parse(line);
113
+ } catch (err: any) {
114
+ console.error(
115
+ `Skipping invalid JSON on line ${i + 1}: ${err?.message ?? err}`
116
+ );
117
+ skipped++;
118
+ return;
119
+ }
120
+
121
+ try {
122
+ const summaries = await runCase(provider, caseObj, {
123
+ maxTurns,
124
+ streaming,
125
+ strict: args.values.strict,
126
+ });
127
+
128
+ for (const summary of summaries) {
129
+ const record = {
130
+ run_id: `${i}_${k}`,
131
+ success: summary.success,
132
+ provider,
133
+ test_case: i,
134
+ tool_name: caseObj.tool_name,
135
+ input: caseObj.input,
136
+ result: summary,
137
+ };
138
+ task.output = `Case ${i} (attempt ${k + 1}): ${
139
+ summary.success ? "Success" : "Failed"
140
+ } ${summary.toolCallingDetails.warning || ""}`;
141
+ caseResults.push(record);
142
+ await writeLine(record);
143
+ }
144
+ } catch (err: any) {
145
+ const record = {
146
+ provider,
147
+ test_case: i,
148
+ tool_name: caseObj?.tool_name,
149
+ input: caseObj?.input,
150
+ expected_output: caseObj?.expected_output,
151
+ instructions: caseObj?.instructions,
152
+ error: String(err?.message ?? err),
153
+ };
154
+ await writeLine(record);
155
+ task.output = `Case ${i} failed: ${err?.message ?? err}`;
156
+ }
157
+ }
158
+
159
+ const listr = new Listr<{
160
+ output: string;
161
+ }>(
162
+ selectedLines.flatMap((line, index) => {
163
+ return Array.from({ length: tries }, (_, attempt) => ({
164
+ title: `Processing case ${index} (attempt ${attempt + 1})`,
165
+ task: async (_, task) => {
166
+ await processIndex(index, attempt, task);
167
+ },
168
+ rendererOptions: { persistentOutput: true },
169
+ }));
170
+ }),
171
+ {
172
+ concurrent: 5,
173
+ }
174
+ );
175
+
176
+ await listr.run();
177
+
178
+ await new Promise((resolve) => out.end(resolve));
179
+ console.log(`Results written to ${outputFile}`);
180
+ const stats = analyze(caseResults, tries);
181
+ await writeFile(analysisFile, JSON.stringify(stats, null, 2), "utf8");
182
+ printAnalysis(
183
+ stats,
184
+ caseResults,
185
+ provider,
186
+ selectedLines,
187
+ tries,
188
+ skipped,
189
+ analysisFile
190
+ );
191
+ }
192
+
193
+ main().catch((err) => {
194
+ console.error(err);
195
+ process.exitCode = 1;
196
+ });
compatibility-test/package-lock.json ADDED
@@ -0,0 +1,1633 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "compatibility-test",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "@openai/agents": "^0.0.15",
9
+ "ajv": "^8.17.1",
10
+ "listr2": "^9.0.1"
11
+ }
12
+ },
13
+ "node_modules/@modelcontextprotocol/sdk": {
14
+ "version": "1.17.1",
15
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.1.tgz",
16
+ "integrity": "sha512-CPle1OQehbWqd25La9Ack5B07StKIxh4+Bf19qnpZKJC1oI22Y0czZHbifjw1UoczIfKBwBDAp/dFxvHG13B5A==",
17
+ "license": "MIT",
18
+ "optional": true,
19
+ "dependencies": {
20
+ "ajv": "^6.12.6",
21
+ "content-type": "^1.0.5",
22
+ "cors": "^2.8.5",
23
+ "cross-spawn": "^7.0.5",
24
+ "eventsource": "^3.0.2",
25
+ "eventsource-parser": "^3.0.0",
26
+ "express": "^5.0.1",
27
+ "express-rate-limit": "^7.5.0",
28
+ "pkce-challenge": "^5.0.0",
29
+ "raw-body": "^3.0.0",
30
+ "zod": "^3.23.8",
31
+ "zod-to-json-schema": "^3.24.1"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ },
37
+ "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": {
38
+ "version": "6.12.6",
39
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
40
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
41
+ "license": "MIT",
42
+ "optional": true,
43
+ "dependencies": {
44
+ "fast-deep-equal": "^3.1.1",
45
+ "fast-json-stable-stringify": "^2.0.0",
46
+ "json-schema-traverse": "^0.4.1",
47
+ "uri-js": "^4.2.2"
48
+ },
49
+ "funding": {
50
+ "type": "github",
51
+ "url": "https://github.com/sponsors/epoberezkin"
52
+ }
53
+ },
54
+ "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": {
55
+ "version": "0.4.1",
56
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
57
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
58
+ "license": "MIT",
59
+ "optional": true
60
+ },
61
+ "node_modules/@openai/agents": {
62
+ "version": "0.0.15",
63
+ "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.0.15.tgz",
64
+ "integrity": "sha512-B8y+WyWOeHowflPx09pyCfcqikC4OYWK27HTyNGt1oraXv93CzuamSr76iAaU1nWQ1MPbUwl6LHPX4BPUikVkQ==",
65
+ "license": "MIT",
66
+ "dependencies": {
67
+ "@openai/agents-core": "0.0.15",
68
+ "@openai/agents-openai": "0.0.15",
69
+ "@openai/agents-realtime": "0.0.15",
70
+ "debug": "^4.4.0",
71
+ "openai": "^5.10.1"
72
+ }
73
+ },
74
+ "node_modules/@openai/agents-core": {
75
+ "version": "0.0.15",
76
+ "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.0.15.tgz",
77
+ "integrity": "sha512-ODTqttjW0s0ejBe5PKnYRlFbJSZH2IO6OtUlRhIKmWiWrX6pGRxvpKjTSOXy8DEtpRHBj6Nhky0UoSlO6eOkDQ==",
78
+ "license": "MIT",
79
+ "dependencies": {
80
+ "@openai/zod": "npm:zod@3.25.40 - 3.25.67",
81
+ "debug": "^4.4.0",
82
+ "openai": "^5.10.1"
83
+ },
84
+ "optionalDependencies": {
85
+ "@modelcontextprotocol/sdk": "^1.12.0"
86
+ },
87
+ "peerDependencies": {
88
+ "zod": "3.25.40 - 3.25.67"
89
+ },
90
+ "peerDependenciesMeta": {
91
+ "zod": {
92
+ "optional": true
93
+ }
94
+ }
95
+ },
96
+ "node_modules/@openai/agents-openai": {
97
+ "version": "0.0.15",
98
+ "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.0.15.tgz",
99
+ "integrity": "sha512-YIX3n98HdmmWKkb/71OB+DCQUYyGEpqfzPjejzdtNLUvAEs3jvXf7nkC8oTISsuCwrirgBz0rQEefeo0oUlyFQ==",
100
+ "license": "MIT",
101
+ "dependencies": {
102
+ "@openai/agents-core": "0.0.15",
103
+ "@openai/zod": "npm:zod@3.25.40 - 3.25.67",
104
+ "debug": "^4.4.0",
105
+ "openai": "^5.10.1"
106
+ }
107
+ },
108
+ "node_modules/@openai/agents-realtime": {
109
+ "version": "0.0.15",
110
+ "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.0.15.tgz",
111
+ "integrity": "sha512-kSZzMyij9Xt3BpMb/9snuVnu7a5qKZLyhtN/kWMA+wmfETvWz23BBz6tbO5xOmurAt9//OktkB+94e0T0RBtlA==",
112
+ "license": "MIT",
113
+ "dependencies": {
114
+ "@openai/agents-core": "0.0.15",
115
+ "@openai/zod": "npm:zod@3.25.40 - 3.25.67",
116
+ "@types/ws": "^8.18.1",
117
+ "debug": "^4.4.0",
118
+ "ws": "^8.18.1"
119
+ }
120
+ },
121
+ "node_modules/@openai/zod": {
122
+ "name": "zod",
123
+ "version": "3.25.67",
124
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
125
+ "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
126
+ "license": "MIT",
127
+ "funding": {
128
+ "url": "https://github.com/sponsors/colinhacks"
129
+ }
130
+ },
131
+ "node_modules/@types/node": {
132
+ "version": "24.2.0",
133
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
134
+ "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
135
+ "license": "MIT",
136
+ "dependencies": {
137
+ "undici-types": "~7.10.0"
138
+ }
139
+ },
140
+ "node_modules/@types/ws": {
141
+ "version": "8.18.1",
142
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
143
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
144
+ "license": "MIT",
145
+ "dependencies": {
146
+ "@types/node": "*"
147
+ }
148
+ },
149
+ "node_modules/accepts": {
150
+ "version": "2.0.0",
151
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
152
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
153
+ "license": "MIT",
154
+ "optional": true,
155
+ "dependencies": {
156
+ "mime-types": "^3.0.0",
157
+ "negotiator": "^1.0.0"
158
+ },
159
+ "engines": {
160
+ "node": ">= 0.6"
161
+ }
162
+ },
163
+ "node_modules/ajv": {
164
+ "version": "8.17.1",
165
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
166
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
167
+ "license": "MIT",
168
+ "dependencies": {
169
+ "fast-deep-equal": "^3.1.3",
170
+ "fast-uri": "^3.0.1",
171
+ "json-schema-traverse": "^1.0.0",
172
+ "require-from-string": "^2.0.2"
173
+ },
174
+ "funding": {
175
+ "type": "github",
176
+ "url": "https://github.com/sponsors/epoberezkin"
177
+ }
178
+ },
179
+ "node_modules/ansi-escapes": {
180
+ "version": "7.0.0",
181
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
182
+ "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
183
+ "license": "MIT",
184
+ "dependencies": {
185
+ "environment": "^1.0.0"
186
+ },
187
+ "engines": {
188
+ "node": ">=18"
189
+ },
190
+ "funding": {
191
+ "url": "https://github.com/sponsors/sindresorhus"
192
+ }
193
+ },
194
+ "node_modules/ansi-regex": {
195
+ "version": "6.1.0",
196
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
197
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
198
+ "license": "MIT",
199
+ "engines": {
200
+ "node": ">=12"
201
+ },
202
+ "funding": {
203
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
204
+ }
205
+ },
206
+ "node_modules/ansi-styles": {
207
+ "version": "6.2.1",
208
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
209
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
210
+ "license": "MIT",
211
+ "engines": {
212
+ "node": ">=12"
213
+ },
214
+ "funding": {
215
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
216
+ }
217
+ },
218
+ "node_modules/body-parser": {
219
+ "version": "2.2.0",
220
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
221
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
222
+ "license": "MIT",
223
+ "optional": true,
224
+ "dependencies": {
225
+ "bytes": "^3.1.2",
226
+ "content-type": "^1.0.5",
227
+ "debug": "^4.4.0",
228
+ "http-errors": "^2.0.0",
229
+ "iconv-lite": "^0.6.3",
230
+ "on-finished": "^2.4.1",
231
+ "qs": "^6.14.0",
232
+ "raw-body": "^3.0.0",
233
+ "type-is": "^2.0.0"
234
+ },
235
+ "engines": {
236
+ "node": ">=18"
237
+ }
238
+ },
239
+ "node_modules/bytes": {
240
+ "version": "3.1.2",
241
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
242
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
243
+ "license": "MIT",
244
+ "optional": true,
245
+ "engines": {
246
+ "node": ">= 0.8"
247
+ }
248
+ },
249
+ "node_modules/call-bind-apply-helpers": {
250
+ "version": "1.0.2",
251
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
252
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
253
+ "license": "MIT",
254
+ "optional": true,
255
+ "dependencies": {
256
+ "es-errors": "^1.3.0",
257
+ "function-bind": "^1.1.2"
258
+ },
259
+ "engines": {
260
+ "node": ">= 0.4"
261
+ }
262
+ },
263
+ "node_modules/call-bound": {
264
+ "version": "1.0.4",
265
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
266
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
267
+ "license": "MIT",
268
+ "optional": true,
269
+ "dependencies": {
270
+ "call-bind-apply-helpers": "^1.0.2",
271
+ "get-intrinsic": "^1.3.0"
272
+ },
273
+ "engines": {
274
+ "node": ">= 0.4"
275
+ },
276
+ "funding": {
277
+ "url": "https://github.com/sponsors/ljharb"
278
+ }
279
+ },
280
+ "node_modules/cli-cursor": {
281
+ "version": "5.0.0",
282
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
283
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
284
+ "license": "MIT",
285
+ "dependencies": {
286
+ "restore-cursor": "^5.0.0"
287
+ },
288
+ "engines": {
289
+ "node": ">=18"
290
+ },
291
+ "funding": {
292
+ "url": "https://github.com/sponsors/sindresorhus"
293
+ }
294
+ },
295
+ "node_modules/cli-truncate": {
296
+ "version": "4.0.0",
297
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
298
+ "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
299
+ "license": "MIT",
300
+ "dependencies": {
301
+ "slice-ansi": "^5.0.0",
302
+ "string-width": "^7.0.0"
303
+ },
304
+ "engines": {
305
+ "node": ">=18"
306
+ },
307
+ "funding": {
308
+ "url": "https://github.com/sponsors/sindresorhus"
309
+ }
310
+ },
311
+ "node_modules/colorette": {
312
+ "version": "2.0.20",
313
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
314
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
315
+ "license": "MIT"
316
+ },
317
+ "node_modules/content-disposition": {
318
+ "version": "1.0.0",
319
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
320
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
321
+ "license": "MIT",
322
+ "optional": true,
323
+ "dependencies": {
324
+ "safe-buffer": "5.2.1"
325
+ },
326
+ "engines": {
327
+ "node": ">= 0.6"
328
+ }
329
+ },
330
+ "node_modules/content-type": {
331
+ "version": "1.0.5",
332
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
333
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
334
+ "license": "MIT",
335
+ "optional": true,
336
+ "engines": {
337
+ "node": ">= 0.6"
338
+ }
339
+ },
340
+ "node_modules/cookie": {
341
+ "version": "0.7.2",
342
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
343
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
344
+ "license": "MIT",
345
+ "optional": true,
346
+ "engines": {
347
+ "node": ">= 0.6"
348
+ }
349
+ },
350
+ "node_modules/cookie-signature": {
351
+ "version": "1.2.2",
352
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
353
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
354
+ "license": "MIT",
355
+ "optional": true,
356
+ "engines": {
357
+ "node": ">=6.6.0"
358
+ }
359
+ },
360
+ "node_modules/cors": {
361
+ "version": "2.8.5",
362
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
363
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
364
+ "license": "MIT",
365
+ "optional": true,
366
+ "dependencies": {
367
+ "object-assign": "^4",
368
+ "vary": "^1"
369
+ },
370
+ "engines": {
371
+ "node": ">= 0.10"
372
+ }
373
+ },
374
+ "node_modules/cross-spawn": {
375
+ "version": "7.0.6",
376
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
377
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
378
+ "license": "MIT",
379
+ "optional": true,
380
+ "dependencies": {
381
+ "path-key": "^3.1.0",
382
+ "shebang-command": "^2.0.0",
383
+ "which": "^2.0.1"
384
+ },
385
+ "engines": {
386
+ "node": ">= 8"
387
+ }
388
+ },
389
+ "node_modules/debug": {
390
+ "version": "4.4.1",
391
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
392
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
393
+ "license": "MIT",
394
+ "dependencies": {
395
+ "ms": "^2.1.3"
396
+ },
397
+ "engines": {
398
+ "node": ">=6.0"
399
+ },
400
+ "peerDependenciesMeta": {
401
+ "supports-color": {
402
+ "optional": true
403
+ }
404
+ }
405
+ },
406
+ "node_modules/depd": {
407
+ "version": "2.0.0",
408
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
409
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
410
+ "license": "MIT",
411
+ "optional": true,
412
+ "engines": {
413
+ "node": ">= 0.8"
414
+ }
415
+ },
416
+ "node_modules/dunder-proto": {
417
+ "version": "1.0.1",
418
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
419
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
420
+ "license": "MIT",
421
+ "optional": true,
422
+ "dependencies": {
423
+ "call-bind-apply-helpers": "^1.0.1",
424
+ "es-errors": "^1.3.0",
425
+ "gopd": "^1.2.0"
426
+ },
427
+ "engines": {
428
+ "node": ">= 0.4"
429
+ }
430
+ },
431
+ "node_modules/ee-first": {
432
+ "version": "1.1.1",
433
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
434
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
435
+ "license": "MIT",
436
+ "optional": true
437
+ },
438
+ "node_modules/emoji-regex": {
439
+ "version": "10.4.0",
440
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
441
+ "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
442
+ "license": "MIT"
443
+ },
444
+ "node_modules/encodeurl": {
445
+ "version": "2.0.0",
446
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
447
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
448
+ "license": "MIT",
449
+ "optional": true,
450
+ "engines": {
451
+ "node": ">= 0.8"
452
+ }
453
+ },
454
+ "node_modules/environment": {
455
+ "version": "1.1.0",
456
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
457
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
458
+ "license": "MIT",
459
+ "engines": {
460
+ "node": ">=18"
461
+ },
462
+ "funding": {
463
+ "url": "https://github.com/sponsors/sindresorhus"
464
+ }
465
+ },
466
+ "node_modules/es-define-property": {
467
+ "version": "1.0.1",
468
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
469
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
470
+ "license": "MIT",
471
+ "optional": true,
472
+ "engines": {
473
+ "node": ">= 0.4"
474
+ }
475
+ },
476
+ "node_modules/es-errors": {
477
+ "version": "1.3.0",
478
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
479
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
480
+ "license": "MIT",
481
+ "optional": true,
482
+ "engines": {
483
+ "node": ">= 0.4"
484
+ }
485
+ },
486
+ "node_modules/es-object-atoms": {
487
+ "version": "1.1.1",
488
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
489
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
490
+ "license": "MIT",
491
+ "optional": true,
492
+ "dependencies": {
493
+ "es-errors": "^1.3.0"
494
+ },
495
+ "engines": {
496
+ "node": ">= 0.4"
497
+ }
498
+ },
499
+ "node_modules/escape-html": {
500
+ "version": "1.0.3",
501
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
502
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
503
+ "license": "MIT",
504
+ "optional": true
505
+ },
506
+ "node_modules/etag": {
507
+ "version": "1.8.1",
508
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
509
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
510
+ "license": "MIT",
511
+ "optional": true,
512
+ "engines": {
513
+ "node": ">= 0.6"
514
+ }
515
+ },
516
+ "node_modules/eventemitter3": {
517
+ "version": "5.0.1",
518
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
519
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
520
+ "license": "MIT"
521
+ },
522
+ "node_modules/eventsource": {
523
+ "version": "3.0.7",
524
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
525
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
526
+ "license": "MIT",
527
+ "optional": true,
528
+ "dependencies": {
529
+ "eventsource-parser": "^3.0.1"
530
+ },
531
+ "engines": {
532
+ "node": ">=18.0.0"
533
+ }
534
+ },
535
+ "node_modules/eventsource-parser": {
536
+ "version": "3.0.3",
537
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
538
+ "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==",
539
+ "license": "MIT",
540
+ "optional": true,
541
+ "engines": {
542
+ "node": ">=20.0.0"
543
+ }
544
+ },
545
+ "node_modules/express": {
546
+ "version": "5.1.0",
547
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
548
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
549
+ "license": "MIT",
550
+ "optional": true,
551
+ "dependencies": {
552
+ "accepts": "^2.0.0",
553
+ "body-parser": "^2.2.0",
554
+ "content-disposition": "^1.0.0",
555
+ "content-type": "^1.0.5",
556
+ "cookie": "^0.7.1",
557
+ "cookie-signature": "^1.2.1",
558
+ "debug": "^4.4.0",
559
+ "encodeurl": "^2.0.0",
560
+ "escape-html": "^1.0.3",
561
+ "etag": "^1.8.1",
562
+ "finalhandler": "^2.1.0",
563
+ "fresh": "^2.0.0",
564
+ "http-errors": "^2.0.0",
565
+ "merge-descriptors": "^2.0.0",
566
+ "mime-types": "^3.0.0",
567
+ "on-finished": "^2.4.1",
568
+ "once": "^1.4.0",
569
+ "parseurl": "^1.3.3",
570
+ "proxy-addr": "^2.0.7",
571
+ "qs": "^6.14.0",
572
+ "range-parser": "^1.2.1",
573
+ "router": "^2.2.0",
574
+ "send": "^1.1.0",
575
+ "serve-static": "^2.2.0",
576
+ "statuses": "^2.0.1",
577
+ "type-is": "^2.0.1",
578
+ "vary": "^1.1.2"
579
+ },
580
+ "engines": {
581
+ "node": ">= 18"
582
+ },
583
+ "funding": {
584
+ "type": "opencollective",
585
+ "url": "https://opencollective.com/express"
586
+ }
587
+ },
588
+ "node_modules/express-rate-limit": {
589
+ "version": "7.5.1",
590
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
591
+ "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
592
+ "license": "MIT",
593
+ "optional": true,
594
+ "engines": {
595
+ "node": ">= 16"
596
+ },
597
+ "funding": {
598
+ "url": "https://github.com/sponsors/express-rate-limit"
599
+ },
600
+ "peerDependencies": {
601
+ "express": ">= 4.11"
602
+ }
603
+ },
604
+ "node_modules/fast-deep-equal": {
605
+ "version": "3.1.3",
606
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
607
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
608
+ "license": "MIT"
609
+ },
610
+ "node_modules/fast-json-stable-stringify": {
611
+ "version": "2.1.0",
612
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
613
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
614
+ "license": "MIT",
615
+ "optional": true
616
+ },
617
+ "node_modules/fast-uri": {
618
+ "version": "3.0.6",
619
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
620
+ "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
621
+ "funding": [
622
+ {
623
+ "type": "github",
624
+ "url": "https://github.com/sponsors/fastify"
625
+ },
626
+ {
627
+ "type": "opencollective",
628
+ "url": "https://opencollective.com/fastify"
629
+ }
630
+ ],
631
+ "license": "BSD-3-Clause"
632
+ },
633
+ "node_modules/finalhandler": {
634
+ "version": "2.1.0",
635
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
636
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
637
+ "license": "MIT",
638
+ "optional": true,
639
+ "dependencies": {
640
+ "debug": "^4.4.0",
641
+ "encodeurl": "^2.0.0",
642
+ "escape-html": "^1.0.3",
643
+ "on-finished": "^2.4.1",
644
+ "parseurl": "^1.3.3",
645
+ "statuses": "^2.0.1"
646
+ },
647
+ "engines": {
648
+ "node": ">= 0.8"
649
+ }
650
+ },
651
+ "node_modules/forwarded": {
652
+ "version": "0.2.0",
653
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
654
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
655
+ "license": "MIT",
656
+ "optional": true,
657
+ "engines": {
658
+ "node": ">= 0.6"
659
+ }
660
+ },
661
+ "node_modules/fresh": {
662
+ "version": "2.0.0",
663
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
664
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
665
+ "license": "MIT",
666
+ "optional": true,
667
+ "engines": {
668
+ "node": ">= 0.8"
669
+ }
670
+ },
671
+ "node_modules/function-bind": {
672
+ "version": "1.1.2",
673
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
674
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
675
+ "license": "MIT",
676
+ "optional": true,
677
+ "funding": {
678
+ "url": "https://github.com/sponsors/ljharb"
679
+ }
680
+ },
681
+ "node_modules/get-east-asian-width": {
682
+ "version": "1.3.0",
683
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
684
+ "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
685
+ "license": "MIT",
686
+ "engines": {
687
+ "node": ">=18"
688
+ },
689
+ "funding": {
690
+ "url": "https://github.com/sponsors/sindresorhus"
691
+ }
692
+ },
693
+ "node_modules/get-intrinsic": {
694
+ "version": "1.3.0",
695
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
696
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
697
+ "license": "MIT",
698
+ "optional": true,
699
+ "dependencies": {
700
+ "call-bind-apply-helpers": "^1.0.2",
701
+ "es-define-property": "^1.0.1",
702
+ "es-errors": "^1.3.0",
703
+ "es-object-atoms": "^1.1.1",
704
+ "function-bind": "^1.1.2",
705
+ "get-proto": "^1.0.1",
706
+ "gopd": "^1.2.0",
707
+ "has-symbols": "^1.1.0",
708
+ "hasown": "^2.0.2",
709
+ "math-intrinsics": "^1.1.0"
710
+ },
711
+ "engines": {
712
+ "node": ">= 0.4"
713
+ },
714
+ "funding": {
715
+ "url": "https://github.com/sponsors/ljharb"
716
+ }
717
+ },
718
+ "node_modules/get-proto": {
719
+ "version": "1.0.1",
720
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
721
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
722
+ "license": "MIT",
723
+ "optional": true,
724
+ "dependencies": {
725
+ "dunder-proto": "^1.0.1",
726
+ "es-object-atoms": "^1.0.0"
727
+ },
728
+ "engines": {
729
+ "node": ">= 0.4"
730
+ }
731
+ },
732
+ "node_modules/gopd": {
733
+ "version": "1.2.0",
734
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
735
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
736
+ "license": "MIT",
737
+ "optional": true,
738
+ "engines": {
739
+ "node": ">= 0.4"
740
+ },
741
+ "funding": {
742
+ "url": "https://github.com/sponsors/ljharb"
743
+ }
744
+ },
745
+ "node_modules/has-symbols": {
746
+ "version": "1.1.0",
747
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
748
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
749
+ "license": "MIT",
750
+ "optional": true,
751
+ "engines": {
752
+ "node": ">= 0.4"
753
+ },
754
+ "funding": {
755
+ "url": "https://github.com/sponsors/ljharb"
756
+ }
757
+ },
758
+ "node_modules/hasown": {
759
+ "version": "2.0.2",
760
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
761
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
762
+ "license": "MIT",
763
+ "optional": true,
764
+ "dependencies": {
765
+ "function-bind": "^1.1.2"
766
+ },
767
+ "engines": {
768
+ "node": ">= 0.4"
769
+ }
770
+ },
771
+ "node_modules/http-errors": {
772
+ "version": "2.0.0",
773
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
774
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
775
+ "license": "MIT",
776
+ "optional": true,
777
+ "dependencies": {
778
+ "depd": "2.0.0",
779
+ "inherits": "2.0.4",
780
+ "setprototypeof": "1.2.0",
781
+ "statuses": "2.0.1",
782
+ "toidentifier": "1.0.1"
783
+ },
784
+ "engines": {
785
+ "node": ">= 0.8"
786
+ }
787
+ },
788
+ "node_modules/http-errors/node_modules/statuses": {
789
+ "version": "2.0.1",
790
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
791
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
792
+ "license": "MIT",
793
+ "optional": true,
794
+ "engines": {
795
+ "node": ">= 0.8"
796
+ }
797
+ },
798
+ "node_modules/iconv-lite": {
799
+ "version": "0.6.3",
800
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
801
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
802
+ "license": "MIT",
803
+ "optional": true,
804
+ "dependencies": {
805
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
806
+ },
807
+ "engines": {
808
+ "node": ">=0.10.0"
809
+ }
810
+ },
811
+ "node_modules/inherits": {
812
+ "version": "2.0.4",
813
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
814
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
815
+ "license": "ISC",
816
+ "optional": true
817
+ },
818
+ "node_modules/ipaddr.js": {
819
+ "version": "1.9.1",
820
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
821
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
822
+ "license": "MIT",
823
+ "optional": true,
824
+ "engines": {
825
+ "node": ">= 0.10"
826
+ }
827
+ },
828
+ "node_modules/is-fullwidth-code-point": {
829
+ "version": "4.0.0",
830
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
831
+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
832
+ "license": "MIT",
833
+ "engines": {
834
+ "node": ">=12"
835
+ },
836
+ "funding": {
837
+ "url": "https://github.com/sponsors/sindresorhus"
838
+ }
839
+ },
840
+ "node_modules/is-promise": {
841
+ "version": "4.0.0",
842
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
843
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
844
+ "license": "MIT",
845
+ "optional": true
846
+ },
847
+ "node_modules/isexe": {
848
+ "version": "2.0.0",
849
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
850
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
851
+ "license": "ISC",
852
+ "optional": true
853
+ },
854
+ "node_modules/json-schema-traverse": {
855
+ "version": "1.0.0",
856
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
857
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
858
+ "license": "MIT"
859
+ },
860
+ "node_modules/listr2": {
861
+ "version": "9.0.1",
862
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz",
863
+ "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
864
+ "license": "MIT",
865
+ "dependencies": {
866
+ "cli-truncate": "^4.0.0",
867
+ "colorette": "^2.0.20",
868
+ "eventemitter3": "^5.0.1",
869
+ "log-update": "^6.1.0",
870
+ "rfdc": "^1.4.1",
871
+ "wrap-ansi": "^9.0.0"
872
+ },
873
+ "engines": {
874
+ "node": ">=20.0.0"
875
+ }
876
+ },
877
+ "node_modules/log-update": {
878
+ "version": "6.1.0",
879
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
880
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
881
+ "license": "MIT",
882
+ "dependencies": {
883
+ "ansi-escapes": "^7.0.0",
884
+ "cli-cursor": "^5.0.0",
885
+ "slice-ansi": "^7.1.0",
886
+ "strip-ansi": "^7.1.0",
887
+ "wrap-ansi": "^9.0.0"
888
+ },
889
+ "engines": {
890
+ "node": ">=18"
891
+ },
892
+ "funding": {
893
+ "url": "https://github.com/sponsors/sindresorhus"
894
+ }
895
+ },
896
+ "node_modules/log-update/node_modules/is-fullwidth-code-point": {
897
+ "version": "5.0.0",
898
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
899
+ "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
900
+ "license": "MIT",
901
+ "dependencies": {
902
+ "get-east-asian-width": "^1.0.0"
903
+ },
904
+ "engines": {
905
+ "node": ">=18"
906
+ },
907
+ "funding": {
908
+ "url": "https://github.com/sponsors/sindresorhus"
909
+ }
910
+ },
911
+ "node_modules/log-update/node_modules/slice-ansi": {
912
+ "version": "7.1.0",
913
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
914
+ "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
915
+ "license": "MIT",
916
+ "dependencies": {
917
+ "ansi-styles": "^6.2.1",
918
+ "is-fullwidth-code-point": "^5.0.0"
919
+ },
920
+ "engines": {
921
+ "node": ">=18"
922
+ },
923
+ "funding": {
924
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
925
+ }
926
+ },
927
+ "node_modules/math-intrinsics": {
928
+ "version": "1.1.0",
929
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
930
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
931
+ "license": "MIT",
932
+ "optional": true,
933
+ "engines": {
934
+ "node": ">= 0.4"
935
+ }
936
+ },
937
+ "node_modules/media-typer": {
938
+ "version": "1.1.0",
939
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
940
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
941
+ "license": "MIT",
942
+ "optional": true,
943
+ "engines": {
944
+ "node": ">= 0.8"
945
+ }
946
+ },
947
+ "node_modules/merge-descriptors": {
948
+ "version": "2.0.0",
949
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
950
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
951
+ "license": "MIT",
952
+ "optional": true,
953
+ "engines": {
954
+ "node": ">=18"
955
+ },
956
+ "funding": {
957
+ "url": "https://github.com/sponsors/sindresorhus"
958
+ }
959
+ },
960
+ "node_modules/mime-db": {
961
+ "version": "1.54.0",
962
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
963
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
964
+ "license": "MIT",
965
+ "optional": true,
966
+ "engines": {
967
+ "node": ">= 0.6"
968
+ }
969
+ },
970
+ "node_modules/mime-types": {
971
+ "version": "3.0.1",
972
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
973
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
974
+ "license": "MIT",
975
+ "optional": true,
976
+ "dependencies": {
977
+ "mime-db": "^1.54.0"
978
+ },
979
+ "engines": {
980
+ "node": ">= 0.6"
981
+ }
982
+ },
983
+ "node_modules/mimic-function": {
984
+ "version": "5.0.1",
985
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
986
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
987
+ "license": "MIT",
988
+ "engines": {
989
+ "node": ">=18"
990
+ },
991
+ "funding": {
992
+ "url": "https://github.com/sponsors/sindresorhus"
993
+ }
994
+ },
995
+ "node_modules/ms": {
996
+ "version": "2.1.3",
997
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
998
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
999
+ "license": "MIT"
1000
+ },
1001
+ "node_modules/negotiator": {
1002
+ "version": "1.0.0",
1003
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
1004
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
1005
+ "license": "MIT",
1006
+ "optional": true,
1007
+ "engines": {
1008
+ "node": ">= 0.6"
1009
+ }
1010
+ },
1011
+ "node_modules/object-assign": {
1012
+ "version": "4.1.1",
1013
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1014
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1015
+ "license": "MIT",
1016
+ "optional": true,
1017
+ "engines": {
1018
+ "node": ">=0.10.0"
1019
+ }
1020
+ },
1021
+ "node_modules/object-inspect": {
1022
+ "version": "1.13.4",
1023
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1024
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1025
+ "license": "MIT",
1026
+ "optional": true,
1027
+ "engines": {
1028
+ "node": ">= 0.4"
1029
+ },
1030
+ "funding": {
1031
+ "url": "https://github.com/sponsors/ljharb"
1032
+ }
1033
+ },
1034
+ "node_modules/on-finished": {
1035
+ "version": "2.4.1",
1036
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1037
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1038
+ "license": "MIT",
1039
+ "optional": true,
1040
+ "dependencies": {
1041
+ "ee-first": "1.1.1"
1042
+ },
1043
+ "engines": {
1044
+ "node": ">= 0.8"
1045
+ }
1046
+ },
1047
+ "node_modules/once": {
1048
+ "version": "1.4.0",
1049
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1050
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1051
+ "license": "ISC",
1052
+ "optional": true,
1053
+ "dependencies": {
1054
+ "wrappy": "1"
1055
+ }
1056
+ },
1057
+ "node_modules/onetime": {
1058
+ "version": "7.0.0",
1059
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
1060
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
1061
+ "license": "MIT",
1062
+ "dependencies": {
1063
+ "mimic-function": "^5.0.0"
1064
+ },
1065
+ "engines": {
1066
+ "node": ">=18"
1067
+ },
1068
+ "funding": {
1069
+ "url": "https://github.com/sponsors/sindresorhus"
1070
+ }
1071
+ },
1072
+ "node_modules/openai": {
1073
+ "version": "5.12.0",
1074
+ "resolved": "https://registry.npmjs.org/openai/-/openai-5.12.0.tgz",
1075
+ "integrity": "sha512-vUdt02xiWgOHiYUmW0Hj1Qu9OKAiVQu5Bd547ktVCiMKC1BkB5L3ImeEnCyq3WpRKR6ZTaPgekzqdozwdPs7Lg==",
1076
+ "license": "Apache-2.0",
1077
+ "bin": {
1078
+ "openai": "bin/cli"
1079
+ },
1080
+ "peerDependencies": {
1081
+ "ws": "^8.18.0",
1082
+ "zod": "^3.23.8"
1083
+ },
1084
+ "peerDependenciesMeta": {
1085
+ "ws": {
1086
+ "optional": true
1087
+ },
1088
+ "zod": {
1089
+ "optional": true
1090
+ }
1091
+ }
1092
+ },
1093
+ "node_modules/parseurl": {
1094
+ "version": "1.3.3",
1095
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1096
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1097
+ "license": "MIT",
1098
+ "optional": true,
1099
+ "engines": {
1100
+ "node": ">= 0.8"
1101
+ }
1102
+ },
1103
+ "node_modules/path-key": {
1104
+ "version": "3.1.1",
1105
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1106
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1107
+ "license": "MIT",
1108
+ "optional": true,
1109
+ "engines": {
1110
+ "node": ">=8"
1111
+ }
1112
+ },
1113
+ "node_modules/path-to-regexp": {
1114
+ "version": "8.2.0",
1115
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
1116
+ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
1117
+ "license": "MIT",
1118
+ "optional": true,
1119
+ "engines": {
1120
+ "node": ">=16"
1121
+ }
1122
+ },
1123
+ "node_modules/pkce-challenge": {
1124
+ "version": "5.0.0",
1125
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
1126
+ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
1127
+ "license": "MIT",
1128
+ "optional": true,
1129
+ "engines": {
1130
+ "node": ">=16.20.0"
1131
+ }
1132
+ },
1133
+ "node_modules/proxy-addr": {
1134
+ "version": "2.0.7",
1135
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1136
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1137
+ "license": "MIT",
1138
+ "optional": true,
1139
+ "dependencies": {
1140
+ "forwarded": "0.2.0",
1141
+ "ipaddr.js": "1.9.1"
1142
+ },
1143
+ "engines": {
1144
+ "node": ">= 0.10"
1145
+ }
1146
+ },
1147
+ "node_modules/punycode": {
1148
+ "version": "2.3.1",
1149
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1150
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1151
+ "license": "MIT",
1152
+ "optional": true,
1153
+ "engines": {
1154
+ "node": ">=6"
1155
+ }
1156
+ },
1157
+ "node_modules/qs": {
1158
+ "version": "6.14.0",
1159
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
1160
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
1161
+ "license": "BSD-3-Clause",
1162
+ "optional": true,
1163
+ "dependencies": {
1164
+ "side-channel": "^1.1.0"
1165
+ },
1166
+ "engines": {
1167
+ "node": ">=0.6"
1168
+ },
1169
+ "funding": {
1170
+ "url": "https://github.com/sponsors/ljharb"
1171
+ }
1172
+ },
1173
+ "node_modules/range-parser": {
1174
+ "version": "1.2.1",
1175
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1176
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1177
+ "license": "MIT",
1178
+ "optional": true,
1179
+ "engines": {
1180
+ "node": ">= 0.6"
1181
+ }
1182
+ },
1183
+ "node_modules/raw-body": {
1184
+ "version": "3.0.0",
1185
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
1186
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
1187
+ "license": "MIT",
1188
+ "optional": true,
1189
+ "dependencies": {
1190
+ "bytes": "3.1.2",
1191
+ "http-errors": "2.0.0",
1192
+ "iconv-lite": "0.6.3",
1193
+ "unpipe": "1.0.0"
1194
+ },
1195
+ "engines": {
1196
+ "node": ">= 0.8"
1197
+ }
1198
+ },
1199
+ "node_modules/require-from-string": {
1200
+ "version": "2.0.2",
1201
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
1202
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
1203
+ "license": "MIT",
1204
+ "engines": {
1205
+ "node": ">=0.10.0"
1206
+ }
1207
+ },
1208
+ "node_modules/restore-cursor": {
1209
+ "version": "5.1.0",
1210
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
1211
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
1212
+ "license": "MIT",
1213
+ "dependencies": {
1214
+ "onetime": "^7.0.0",
1215
+ "signal-exit": "^4.1.0"
1216
+ },
1217
+ "engines": {
1218
+ "node": ">=18"
1219
+ },
1220
+ "funding": {
1221
+ "url": "https://github.com/sponsors/sindresorhus"
1222
+ }
1223
+ },
1224
+ "node_modules/rfdc": {
1225
+ "version": "1.4.1",
1226
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
1227
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
1228
+ "license": "MIT"
1229
+ },
1230
+ "node_modules/router": {
1231
+ "version": "2.2.0",
1232
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
1233
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
1234
+ "license": "MIT",
1235
+ "optional": true,
1236
+ "dependencies": {
1237
+ "debug": "^4.4.0",
1238
+ "depd": "^2.0.0",
1239
+ "is-promise": "^4.0.0",
1240
+ "parseurl": "^1.3.3",
1241
+ "path-to-regexp": "^8.0.0"
1242
+ },
1243
+ "engines": {
1244
+ "node": ">= 18"
1245
+ }
1246
+ },
1247
+ "node_modules/safe-buffer": {
1248
+ "version": "5.2.1",
1249
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1250
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1251
+ "funding": [
1252
+ {
1253
+ "type": "github",
1254
+ "url": "https://github.com/sponsors/feross"
1255
+ },
1256
+ {
1257
+ "type": "patreon",
1258
+ "url": "https://www.patreon.com/feross"
1259
+ },
1260
+ {
1261
+ "type": "consulting",
1262
+ "url": "https://feross.org/support"
1263
+ }
1264
+ ],
1265
+ "license": "MIT",
1266
+ "optional": true
1267
+ },
1268
+ "node_modules/safer-buffer": {
1269
+ "version": "2.1.2",
1270
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1271
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1272
+ "license": "MIT",
1273
+ "optional": true
1274
+ },
1275
+ "node_modules/send": {
1276
+ "version": "1.2.0",
1277
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
1278
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
1279
+ "license": "MIT",
1280
+ "optional": true,
1281
+ "dependencies": {
1282
+ "debug": "^4.3.5",
1283
+ "encodeurl": "^2.0.0",
1284
+ "escape-html": "^1.0.3",
1285
+ "etag": "^1.8.1",
1286
+ "fresh": "^2.0.0",
1287
+ "http-errors": "^2.0.0",
1288
+ "mime-types": "^3.0.1",
1289
+ "ms": "^2.1.3",
1290
+ "on-finished": "^2.4.1",
1291
+ "range-parser": "^1.2.1",
1292
+ "statuses": "^2.0.1"
1293
+ },
1294
+ "engines": {
1295
+ "node": ">= 18"
1296
+ }
1297
+ },
1298
+ "node_modules/serve-static": {
1299
+ "version": "2.2.0",
1300
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
1301
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
1302
+ "license": "MIT",
1303
+ "optional": true,
1304
+ "dependencies": {
1305
+ "encodeurl": "^2.0.0",
1306
+ "escape-html": "^1.0.3",
1307
+ "parseurl": "^1.3.3",
1308
+ "send": "^1.2.0"
1309
+ },
1310
+ "engines": {
1311
+ "node": ">= 18"
1312
+ }
1313
+ },
1314
+ "node_modules/setprototypeof": {
1315
+ "version": "1.2.0",
1316
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1317
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1318
+ "license": "ISC",
1319
+ "optional": true
1320
+ },
1321
+ "node_modules/shebang-command": {
1322
+ "version": "2.0.0",
1323
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1324
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1325
+ "license": "MIT",
1326
+ "optional": true,
1327
+ "dependencies": {
1328
+ "shebang-regex": "^3.0.0"
1329
+ },
1330
+ "engines": {
1331
+ "node": ">=8"
1332
+ }
1333
+ },
1334
+ "node_modules/shebang-regex": {
1335
+ "version": "3.0.0",
1336
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1337
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1338
+ "license": "MIT",
1339
+ "optional": true,
1340
+ "engines": {
1341
+ "node": ">=8"
1342
+ }
1343
+ },
1344
+ "node_modules/side-channel": {
1345
+ "version": "1.1.0",
1346
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1347
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1348
+ "license": "MIT",
1349
+ "optional": true,
1350
+ "dependencies": {
1351
+ "es-errors": "^1.3.0",
1352
+ "object-inspect": "^1.13.3",
1353
+ "side-channel-list": "^1.0.0",
1354
+ "side-channel-map": "^1.0.1",
1355
+ "side-channel-weakmap": "^1.0.2"
1356
+ },
1357
+ "engines": {
1358
+ "node": ">= 0.4"
1359
+ },
1360
+ "funding": {
1361
+ "url": "https://github.com/sponsors/ljharb"
1362
+ }
1363
+ },
1364
+ "node_modules/side-channel-list": {
1365
+ "version": "1.0.0",
1366
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1367
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1368
+ "license": "MIT",
1369
+ "optional": true,
1370
+ "dependencies": {
1371
+ "es-errors": "^1.3.0",
1372
+ "object-inspect": "^1.13.3"
1373
+ },
1374
+ "engines": {
1375
+ "node": ">= 0.4"
1376
+ },
1377
+ "funding": {
1378
+ "url": "https://github.com/sponsors/ljharb"
1379
+ }
1380
+ },
1381
+ "node_modules/side-channel-map": {
1382
+ "version": "1.0.1",
1383
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1384
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1385
+ "license": "MIT",
1386
+ "optional": true,
1387
+ "dependencies": {
1388
+ "call-bound": "^1.0.2",
1389
+ "es-errors": "^1.3.0",
1390
+ "get-intrinsic": "^1.2.5",
1391
+ "object-inspect": "^1.13.3"
1392
+ },
1393
+ "engines": {
1394
+ "node": ">= 0.4"
1395
+ },
1396
+ "funding": {
1397
+ "url": "https://github.com/sponsors/ljharb"
1398
+ }
1399
+ },
1400
+ "node_modules/side-channel-weakmap": {
1401
+ "version": "1.0.2",
1402
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1403
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1404
+ "license": "MIT",
1405
+ "optional": true,
1406
+ "dependencies": {
1407
+ "call-bound": "^1.0.2",
1408
+ "es-errors": "^1.3.0",
1409
+ "get-intrinsic": "^1.2.5",
1410
+ "object-inspect": "^1.13.3",
1411
+ "side-channel-map": "^1.0.1"
1412
+ },
1413
+ "engines": {
1414
+ "node": ">= 0.4"
1415
+ },
1416
+ "funding": {
1417
+ "url": "https://github.com/sponsors/ljharb"
1418
+ }
1419
+ },
1420
+ "node_modules/signal-exit": {
1421
+ "version": "4.1.0",
1422
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
1423
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
1424
+ "license": "ISC",
1425
+ "engines": {
1426
+ "node": ">=14"
1427
+ },
1428
+ "funding": {
1429
+ "url": "https://github.com/sponsors/isaacs"
1430
+ }
1431
+ },
1432
+ "node_modules/slice-ansi": {
1433
+ "version": "5.0.0",
1434
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
1435
+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
1436
+ "license": "MIT",
1437
+ "dependencies": {
1438
+ "ansi-styles": "^6.0.0",
1439
+ "is-fullwidth-code-point": "^4.0.0"
1440
+ },
1441
+ "engines": {
1442
+ "node": ">=12"
1443
+ },
1444
+ "funding": {
1445
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
1446
+ }
1447
+ },
1448
+ "node_modules/statuses": {
1449
+ "version": "2.0.2",
1450
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1451
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1452
+ "license": "MIT",
1453
+ "optional": true,
1454
+ "engines": {
1455
+ "node": ">= 0.8"
1456
+ }
1457
+ },
1458
+ "node_modules/string-width": {
1459
+ "version": "7.2.0",
1460
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
1461
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
1462
+ "license": "MIT",
1463
+ "dependencies": {
1464
+ "emoji-regex": "^10.3.0",
1465
+ "get-east-asian-width": "^1.0.0",
1466
+ "strip-ansi": "^7.1.0"
1467
+ },
1468
+ "engines": {
1469
+ "node": ">=18"
1470
+ },
1471
+ "funding": {
1472
+ "url": "https://github.com/sponsors/sindresorhus"
1473
+ }
1474
+ },
1475
+ "node_modules/strip-ansi": {
1476
+ "version": "7.1.0",
1477
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
1478
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
1479
+ "license": "MIT",
1480
+ "dependencies": {
1481
+ "ansi-regex": "^6.0.1"
1482
+ },
1483
+ "engines": {
1484
+ "node": ">=12"
1485
+ },
1486
+ "funding": {
1487
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
1488
+ }
1489
+ },
1490
+ "node_modules/toidentifier": {
1491
+ "version": "1.0.1",
1492
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1493
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1494
+ "license": "MIT",
1495
+ "optional": true,
1496
+ "engines": {
1497
+ "node": ">=0.6"
1498
+ }
1499
+ },
1500
+ "node_modules/type-is": {
1501
+ "version": "2.0.1",
1502
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
1503
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
1504
+ "license": "MIT",
1505
+ "optional": true,
1506
+ "dependencies": {
1507
+ "content-type": "^1.0.5",
1508
+ "media-typer": "^1.1.0",
1509
+ "mime-types": "^3.0.0"
1510
+ },
1511
+ "engines": {
1512
+ "node": ">= 0.6"
1513
+ }
1514
+ },
1515
+ "node_modules/undici-types": {
1516
+ "version": "7.10.0",
1517
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
1518
+ "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
1519
+ "license": "MIT"
1520
+ },
1521
+ "node_modules/unpipe": {
1522
+ "version": "1.0.0",
1523
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1524
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1525
+ "license": "MIT",
1526
+ "optional": true,
1527
+ "engines": {
1528
+ "node": ">= 0.8"
1529
+ }
1530
+ },
1531
+ "node_modules/uri-js": {
1532
+ "version": "4.4.1",
1533
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
1534
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
1535
+ "license": "BSD-2-Clause",
1536
+ "optional": true,
1537
+ "dependencies": {
1538
+ "punycode": "^2.1.0"
1539
+ }
1540
+ },
1541
+ "node_modules/vary": {
1542
+ "version": "1.1.2",
1543
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1544
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1545
+ "license": "MIT",
1546
+ "optional": true,
1547
+ "engines": {
1548
+ "node": ">= 0.8"
1549
+ }
1550
+ },
1551
+ "node_modules/which": {
1552
+ "version": "2.0.2",
1553
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1554
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1555
+ "license": "ISC",
1556
+ "optional": true,
1557
+ "dependencies": {
1558
+ "isexe": "^2.0.0"
1559
+ },
1560
+ "bin": {
1561
+ "node-which": "bin/node-which"
1562
+ },
1563
+ "engines": {
1564
+ "node": ">= 8"
1565
+ }
1566
+ },
1567
+ "node_modules/wrap-ansi": {
1568
+ "version": "9.0.0",
1569
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
1570
+ "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
1571
+ "license": "MIT",
1572
+ "dependencies": {
1573
+ "ansi-styles": "^6.2.1",
1574
+ "string-width": "^7.0.0",
1575
+ "strip-ansi": "^7.1.0"
1576
+ },
1577
+ "engines": {
1578
+ "node": ">=18"
1579
+ },
1580
+ "funding": {
1581
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
1582
+ }
1583
+ },
1584
+ "node_modules/wrappy": {
1585
+ "version": "1.0.2",
1586
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1587
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1588
+ "license": "ISC",
1589
+ "optional": true
1590
+ },
1591
+ "node_modules/ws": {
1592
+ "version": "8.18.3",
1593
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
1594
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
1595
+ "license": "MIT",
1596
+ "engines": {
1597
+ "node": ">=10.0.0"
1598
+ },
1599
+ "peerDependencies": {
1600
+ "bufferutil": "^4.0.1",
1601
+ "utf-8-validate": ">=5.0.2"
1602
+ },
1603
+ "peerDependenciesMeta": {
1604
+ "bufferutil": {
1605
+ "optional": true
1606
+ },
1607
+ "utf-8-validate": {
1608
+ "optional": true
1609
+ }
1610
+ }
1611
+ },
1612
+ "node_modules/zod": {
1613
+ "version": "3.25.67",
1614
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
1615
+ "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
1616
+ "license": "MIT",
1617
+ "optional": true,
1618
+ "funding": {
1619
+ "url": "https://github.com/sponsors/colinhacks"
1620
+ }
1621
+ },
1622
+ "node_modules/zod-to-json-schema": {
1623
+ "version": "3.24.6",
1624
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
1625
+ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
1626
+ "license": "ISC",
1627
+ "optional": true,
1628
+ "peerDependencies": {
1629
+ "zod": "^3.24.1"
1630
+ }
1631
+ }
1632
+ }
1633
+ }
compatibility-test/package.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "module",
3
+ "dependencies": {
4
+ "@openai/agents": "^0.0.15",
5
+ "ajv": "^8.17.1",
6
+ "listr2": "^9.0.1"
7
+ },
8
+ "scripts": {
9
+ "start": "tsx index.ts"
10
+ }
11
+ }
compatibility-test/providers.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const PROVIDERS = {
2
+ vllm: {
3
+ apiBaseUrl: "http://localhost:8000/v1",
4
+ apiKey: "vllm",
5
+ apiType: ["responses", "chat"], // choose from responses, chat, or both
6
+ modelName: "openai/gpt-oss-120b",
7
+ providerDetails: {
8
+ // add any provider-specific details here. These will be passed as part of every request
9
+ // for example to fix the provider for openrouter, you can do:
10
+ // provider: {
11
+ // only: ["example"],
12
+ // },
13
+ },
14
+ },
15
+ };
compatibility-test/runCase.ts ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Agent,
3
+ Runner,
4
+ OpenAIResponsesModel,
5
+ OpenAIChatCompletionsModel,
6
+ RunResult,
7
+ StreamedRunResult,
8
+ FunctionTool,
9
+ setTracingDisabled,
10
+ } from "@openai/agents";
11
+ import { Ajv } from "ajv";
12
+ import { OpenAI } from "openai";
13
+ import { PROVIDERS } from "./providers";
14
+ import { TOOLS_MAP } from "./tools";
15
+
16
+ setTracingDisabled(true);
17
+
18
+ const ajv = new Ajv();
19
+
20
+ export type Case = {
21
+ tool_name: string;
22
+ input: string;
23
+ expected_arguments: string;
24
+ instructions?: string;
25
+ };
26
+
27
+ // Summary shape for each apiType
28
+ export type RunCaseSummary = {
29
+ apiType: string;
30
+ success: boolean;
31
+ validResponse: boolean;
32
+ validEvents?: boolean;
33
+ details: Record<string, any>;
34
+ history: any[];
35
+ successToolCall: boolean;
36
+ toolCallingDetails: Record<string, any>;
37
+ };
38
+
39
+ export async function runCase(
40
+ provider: string,
41
+ caseData: Case,
42
+ {
43
+ maxTurns,
44
+ streaming,
45
+ strict,
46
+ }: { maxTurns: number; streaming: boolean; strict: boolean }
47
+ ): Promise<RunCaseSummary[]> {
48
+ const config = PROVIDERS[provider];
49
+ if (!config) {
50
+ throw new Error(
51
+ `Provider ${provider} not found. Valid providers are: ${Object.keys(
52
+ PROVIDERS
53
+ ).join(", ")}`
54
+ );
55
+ }
56
+
57
+ const agent = new Agent({
58
+ name: caseData.tool_name,
59
+ instructions: caseData.instructions,
60
+ tools: [TOOLS_MAP[caseData.tool_name]],
61
+ });
62
+
63
+ const client = new OpenAI({
64
+ apiKey: config.apiKey,
65
+ baseURL: config.apiBaseUrl,
66
+ });
67
+
68
+ const summaries: RunCaseSummary[] = [];
69
+
70
+ for (const apiType of config.apiType) {
71
+ const runner = new Runner({
72
+ model:
73
+ apiType === "responses"
74
+ ? new OpenAIResponsesModel(client, config.modelName)
75
+ : new OpenAIChatCompletionsModel(client, config.modelName),
76
+ modelSettings: {
77
+ providerData: config.providerDetails ?? {},
78
+ },
79
+ });
80
+
81
+ let result: RunResult<any, any> | StreamedRunResult<any, any>;
82
+ let streamedEvents: any[] | undefined = undefined;
83
+ if (streaming) {
84
+ result = await runner.run(agent, caseData.input, {
85
+ stream: streaming,
86
+ maxTurns: maxTurns,
87
+ });
88
+ if (result instanceof StreamedRunResult) {
89
+ // Collect streaming events if applicable
90
+ streamedEvents = [];
91
+ for await (const event of result) {
92
+ if (event.type === "raw_model_stream_event") {
93
+ if (event.data.type === "model") {
94
+ streamedEvents.push(event.data.event);
95
+ }
96
+ }
97
+ }
98
+ await result.completed;
99
+ }
100
+ } else {
101
+ result = await runner.run(agent, caseData.input, {
102
+ maxTurns: maxTurns,
103
+ });
104
+ }
105
+
106
+ const { success: successToolCall, details: toolCallingDetails } =
107
+ testToolCall(apiType, caseData, result, strict);
108
+
109
+ const { validResponse, details } = testOutputData(
110
+ apiType,
111
+ result.rawResponses,
112
+ streaming
113
+ );
114
+
115
+ const { validEvents, details: eventsDetails } = streaming
116
+ ? testEvents(apiType, streamedEvents)
117
+ : { validEvents: true, details: {} };
118
+
119
+ let success = successToolCall && validResponse;
120
+ if (streaming) {
121
+ success = success && validEvents;
122
+ }
123
+ const summary: RunCaseSummary = {
124
+ apiType,
125
+ success,
126
+ validResponse,
127
+ validEvents,
128
+ details: {
129
+ ...details,
130
+ ...eventsDetails,
131
+ },
132
+ history: result?.rawResponses.map((entry) => entry.providerData) ?? [],
133
+ successToolCall,
134
+ toolCallingDetails,
135
+ };
136
+
137
+ summaries.push(summary);
138
+ }
139
+
140
+ return summaries;
141
+ }
142
+
143
+ function testToolCall(apiType, caseData, result, strict) {
144
+ let details: Record<string, boolean | string> = {};
145
+ result.newItems.forEach((item) => {
146
+ // for this test for now we only care if the tool is called at least once
147
+ if (details.calledToolAtLeastOnce) {
148
+ return;
149
+ }
150
+
151
+ const isToolCall = item.type === "tool_call_item";
152
+ if (isToolCall) {
153
+ if (item.rawItem.type === "function_call") {
154
+ if (item.rawItem.name === caseData.tool_name) {
155
+ const validate = ajv.compile(
156
+ (TOOLS_MAP[caseData.tool_name] as FunctionTool).parameters
157
+ );
158
+ const valid = validate(JSON.parse(item.rawItem.arguments));
159
+ details.calledToolWithRightSchema = valid;
160
+ details.calledToolAtLeastOnce = true;
161
+
162
+ if (details.calledToolWithRightSchema) {
163
+ const parsedArguments = JSON.parse(item.rawItem.arguments);
164
+ const expectedArguments = JSON.parse(caseData.expected_arguments);
165
+ details.calledToolWithRightArguments = deepEqual(
166
+ parsedArguments,
167
+ expectedArguments
168
+ );
169
+ if (!details.calledToolWithRightArguments) {
170
+ if (details.calledToolWithRightSchema) {
171
+ details.warning = `Tool call with wrong arguments but correct schema. Check logs for full details. Not failing this test. Parsed: ${JSON.stringify(
172
+ parsedArguments
173
+ )} Expected: ${JSON.stringify(expectedArguments)}`;
174
+ }
175
+ details.actualArguments = parsedArguments;
176
+ details.expectedArguments = expectedArguments;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ });
183
+
184
+ return {
185
+ success:
186
+ !!details.calledToolAtLeastOnce &&
187
+ !!details.calledToolWithRightSchema &&
188
+ (!strict || !!details.calledToolWithRightArguments),
189
+ details,
190
+ };
191
+ }
192
+
193
+ function testEvents(apiType, events) {
194
+ // In an ideal world we would check all the events to follow and reconstruct the final response
195
+ // and then compare it against the final response in the response.completed event
196
+ // for now we just check that certain events are present
197
+
198
+ let details: Record<string, boolean> = {};
199
+ let validEvents: boolean = false;
200
+
201
+ if (apiType === "chat") {
202
+ let hasReasoningDeltas = false;
203
+ for (const event of events) {
204
+ hasReasoningDeltas =
205
+ hasReasoningDeltas ||
206
+ (typeof event.choices[0].delta.reasoning === "string" &&
207
+ event.choices[0].delta.reasoning.length > 0);
208
+ }
209
+ details.hasReasoningDeltas = hasReasoningDeltas;
210
+ validEvents = hasReasoningDeltas;
211
+ }
212
+
213
+ if (apiType === "responses") {
214
+ let hasReasoningDeltaEvents = false;
215
+ let hasReasoningDoneEvents = false;
216
+ for (const event of events) {
217
+ if (event.type === "raw_model_stream_event") {
218
+ if (event.data.type === "model") {
219
+ if (event.data.event.type === "response.reasoning_text.delta") {
220
+ hasReasoningDeltaEvents = true;
221
+ }
222
+ if (event.data.event.type === "response.reasoning_text.done") {
223
+ hasReasoningDoneEvents = true;
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ details.hasReasoningDeltaEvents = hasReasoningDeltaEvents;
230
+ details.hasReasoningDoneEvents = hasReasoningDoneEvents;
231
+ validEvents =
232
+ details.hasReasoningDeltaEvents && details.hasReasoningDoneEvents;
233
+ }
234
+
235
+ return {
236
+ validEvents,
237
+ details,
238
+ };
239
+ }
240
+
241
+ function testOutputData(apiType, rawResponses, streaming) {
242
+ let details: Record<string, boolean> = {};
243
+ let validResponse: boolean = false;
244
+
245
+ if (apiType === "chat") {
246
+ for (const response of rawResponses) {
247
+ if (streaming && !response.providerData) {
248
+ // with Chat Completions we don't have a final response object that's native so we skip this test
249
+ return {
250
+ validResponse: true,
251
+ details: {
252
+ skippedBecauseStreaming: true,
253
+ },
254
+ };
255
+ }
256
+
257
+ // this is the actual HTTP response from the provider
258
+ // Since it's not guaranteed that every response has a reasoning field, we check if it's present
259
+ // at least once across all responses
260
+ const data = response.providerData;
261
+ const message = data.choices[0].message;
262
+ if (message.role === "assistant" && !message.refusal) {
263
+ details.hasReasoningField =
264
+ details.hasReasoningField ||
265
+ ("reasoning" in message && typeof message.reasoning === "string");
266
+ details.hasReasoningContentField =
267
+ details.hasReasoningContentField ||
268
+ ("reasoning_content" in message &&
269
+ typeof message.reasoning_content === "string");
270
+
271
+ validResponse =
272
+ validResponse ||
273
+ (details.hasReasoningField && message.reasoning.length > 0);
274
+ }
275
+ }
276
+ } else if (apiType === "responses") {
277
+ // this is the actual HTTP response from the provider
278
+ const data = rawResponses[0].providerData;
279
+ for (const item of data.output) {
280
+ // Since it's not guaranteed that every response has a reasoning field, we check if it's present
281
+ // at least once across all responses
282
+
283
+ if (item.type === "reasoning") {
284
+ details.hasReasoningContentArray = Array.isArray(item.content);
285
+ details.hasReasoningContentArrayLength = item.content.length > 0;
286
+ details.hasReasoningContentArrayItemType = item.content.every(
287
+ (item) => item.type === "reasoning_text"
288
+ );
289
+ details.hasReasoningContentArrayItemText = item.content.every(
290
+ (item) => item.text.length > 0
291
+ );
292
+
293
+ validResponse =
294
+ details.hasReasoningContentArray &&
295
+ details.hasReasoningContentArrayLength &&
296
+ details.hasReasoningContentArrayItemType &&
297
+ details.hasReasoningContentArrayItemText;
298
+ }
299
+ }
300
+ }
301
+
302
+ return {
303
+ validResponse,
304
+ details,
305
+ };
306
+ }
307
+
308
+ function deepEqual(a: any, b: any): boolean {
309
+ if (a === b) return true;
310
+ if (typeof a !== typeof b) return false;
311
+ if (a && b && typeof a === "object") {
312
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
313
+ if (Array.isArray(a)) {
314
+ if (a.length !== b.length) return false;
315
+ for (let i = 0; i < a.length; i++) {
316
+ if (!deepEqual(a[i], b[i])) return false;
317
+ }
318
+ return true;
319
+ } else {
320
+ const aKeys = Object.keys(a);
321
+ const bKeys = Object.keys(b);
322
+ if (aKeys.length !== bKeys.length) return false;
323
+ for (const key of aKeys) {
324
+ if (!b.hasOwnProperty(key)) return false;
325
+ if (!deepEqual(a[key], b[key])) return false;
326
+ }
327
+ return true;
328
+ }
329
+ }
330
+ return false;
331
+ }
compatibility-test/tools.ts ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Tool, tool } from "@openai/agents";
2
+
3
+ function convertToTool(toolData: any) {
4
+ return tool({
5
+ name: toolData.name,
6
+ description: toolData.description,
7
+ parameters: toolData.parameters,
8
+ execute: async (parameters) => {
9
+ return toolData.output;
10
+ },
11
+ strict: false,
12
+ });
13
+ }
14
+
15
+ export const TOOLS = [
16
+ {
17
+ type: "function",
18
+ name: "get_weather",
19
+ description: "Get the weather for a given location",
20
+ parameters: {
21
+ type: "object",
22
+ properties: {
23
+ location: {
24
+ type: "string",
25
+ description: "The location to get the weather for",
26
+ },
27
+ },
28
+ required: ["location"],
29
+ additionalProperties: false,
30
+ },
31
+ output: '{"weather":"sunny"}',
32
+ },
33
+ {
34
+ type: "function",
35
+ name: "get_system_health",
36
+ description:
37
+ "Returns the current health status of the LLM runtime—use before critical operations to verify the service is live.",
38
+ parameters: { type: "object", properties: {} },
39
+ output: '{"status":"ok","uptime_seconds":372045}',
40
+ },
41
+ {
42
+ type: "function",
43
+ name: "markdown_to_html",
44
+ description:
45
+ "Converts a Markdown string to sanitized HTML—use when you need browser-renderable output.",
46
+ parameters: {
47
+ type: "object",
48
+ properties: {
49
+ markdown: { type: "string", description: "Raw Markdown content" },
50
+ },
51
+ required: ["markdown"],
52
+ additionalProperties: false,
53
+ },
54
+ output: '{"html":"<h1>Hello World</h1><p>This is <em>great</em>.</p>"}',
55
+ },
56
+ {
57
+ type: "function",
58
+ name: "detect_language",
59
+ description:
60
+ "Identifies the ISO language code of the supplied text—use for routing text to language-specific models.",
61
+ parameters: {
62
+ type: "object",
63
+ properties: {
64
+ text: {
65
+ type: "string",
66
+ description: "Text whose language should be detected",
67
+ },
68
+ },
69
+ required: ["text"],
70
+ additionalProperties: false,
71
+ },
72
+ output: '{"language":"de","confidence":0.98}',
73
+ },
74
+ {
75
+ type: "function",
76
+ name: "generate_chart",
77
+ description:
78
+ "Creates a base64-encoded PNG chart from tabular data—use for quick visualizations inside chat.",
79
+ parameters: {
80
+ type: "object",
81
+ properties: {
82
+ data: {
83
+ type: "array",
84
+ items: { type: "array", items: { type: "number" } },
85
+ description: "2-D numeric data matrix",
86
+ },
87
+ chart_type: {
88
+ type: "string",
89
+ enum: ["line", "bar", "scatter"],
90
+ description: "Type of chart to generate",
91
+ },
92
+ title: {
93
+ type: "string",
94
+ description: "Chart title",
95
+ default: "",
96
+ },
97
+ x_label: {
98
+ type: "string",
99
+ description: "Label for the x-axis",
100
+ default: "",
101
+ },
102
+ y_label: {
103
+ type: "string",
104
+ description: "Label for the y-axis",
105
+ default: "",
106
+ },
107
+ },
108
+ required: ["data", "chart_type"],
109
+ additionalProperties: false,
110
+ },
111
+ output: '{"image_png_base64":"iVBORw0KGgoAAAANSUhEUgAA..."}',
112
+ },
113
+ {
114
+ type: "function",
115
+ name: "query_database",
116
+ description:
117
+ "Runs a parameterized SQL SELECT on the internal analytics DB—use for lightweight data look-ups.",
118
+ parameters: {
119
+ type: "object",
120
+ properties: {
121
+ table: { type: "string", description: "Table name to query" },
122
+ columns: {
123
+ type: "array",
124
+ items: { type: "string" },
125
+ description: "Columns to return",
126
+ },
127
+ filters: {
128
+ type: "string",
129
+ description: "SQL WHERE clause without the word WHERE",
130
+ default: "",
131
+ },
132
+ limit: {
133
+ type: "integer",
134
+ minimum: 1,
135
+ maximum: 10000,
136
+ description: "Max rows to return",
137
+ default: 100,
138
+ },
139
+ order_by: {
140
+ type: "string",
141
+ description: "Column to order by (optional)",
142
+ default: "",
143
+ },
144
+ },
145
+ required: ["table", "columns"],
146
+ additionalProperties: false,
147
+ },
148
+ output:
149
+ '{"rows":[{"id":1,"email":"user@example.com"},{"id":2,"email":"foo@bar.com"}],"row_count":2}',
150
+ },
151
+ ];
152
+
153
+ export const TOOLS_MAP = TOOLS.reduce((acc, tool) => {
154
+ acc[tool.name] = convertToTool(tool);
155
+ return acc;
156
+ }, {} as Record<string, Tool>);
docs/gpt-oss-120b.svg ADDED

Git LFS Details

  • SHA256: 96497944d1e24900f95c1b4c2657e15a37079cf04052b77e0dad11ce715eec35
  • Pointer size: 133 Bytes
  • Size of remote file: 14.4 MB
docs/gpt-oss-20b.svg ADDED

Git LFS Details

  • SHA256: 245e7778763416b3024bac818e351f58c4498b8db7b5d75167325fccd20bb785
  • Pointer size: 133 Bytes
  • Size of remote file: 14.4 MB
docs/gpt-oss.svg ADDED

Git LFS Details

  • SHA256: 4d621976d2d4570b7663f55a356cfa2db9f20c4aa8658b54bf4e4a5a4bd17172
  • Pointer size: 133 Bytes
  • Size of remote file: 14.4 MB
examples/agents-sdk-js/index.ts ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { OpenAI } from "openai";
2
+ import {
3
+ Agent,
4
+ run,
5
+ setDefaultOpenAIClient,
6
+ setOpenAIAPI,
7
+ setTracingDisabled,
8
+ tool,
9
+ MCPServerStdio,
10
+ } from "@openai/agents";
11
+ import { z } from "zod";
12
+ import path from "node:path";
13
+ import process from "node:process";
14
+ import { styleText } from "node:util";
15
+ import { createInterface } from "node:readline/promises";
16
+
17
+ async function prompt(question: string) {
18
+ const rl = createInterface({
19
+ input: process.stdin,
20
+ output: process.stdout,
21
+ });
22
+ const answer = await rl.question(question);
23
+ rl.close();
24
+ return answer;
25
+ }
26
+
27
+ const openai = new OpenAI({
28
+ apiKey: "local",
29
+ baseURL: "http://localhost:11434/v1",
30
+ });
31
+
32
+ const samplesDir = path.join(process.cwd());
33
+
34
+ const mcpServer = new MCPServerStdio({
35
+ name: "Filesystem MCP Server, via npx",
36
+ fullCommand: `npx -y @modelcontextprotocol/server-filesystem ${samplesDir}`,
37
+ });
38
+
39
+ await mcpServer.connect();
40
+
41
+ setTracingDisabled(true);
42
+ setDefaultOpenAIClient(openai);
43
+ setOpenAIAPI("chat_completions");
44
+
45
+ const searchTool = tool({
46
+ name: "get_current_weather",
47
+ description: "Get the current weather in a given location",
48
+ parameters: z.object({
49
+ location: z.string(),
50
+ }),
51
+ execute: async ({ location }) => {
52
+ return `The weather in ${location} is sunny.`;
53
+ },
54
+ });
55
+
56
+ const agent = new Agent({
57
+ name: "My Agent",
58
+ instructions: "You are a helpful assistant.",
59
+ tools: [searchTool],
60
+ model: "gpt-oss:20b-test",
61
+ mcpServers: [mcpServer],
62
+ });
63
+
64
+ const input = await prompt("> ");
65
+
66
+ const result = await run(agent, input, {
67
+ stream: true,
68
+ });
69
+
70
+ for await (const event of result) {
71
+ if (event.type === "raw_model_stream_event" && event.data.type === "model") {
72
+ if (event.data.event.choices[0].delta.content) {
73
+ process.stdout.write(event.data.event.choices[0].delta.content);
74
+ } else if (event.data.event.choices[0].delta.reasoning) {
75
+ process.stdout.write(event.data.event.choices[0].delta.reasoning);
76
+ }
77
+ } else if (
78
+ event.type === "run_item_stream_event" &&
79
+ event.item.type === "tool_call_item" &&
80
+ event.item.rawItem.type == "function_call"
81
+ ) {
82
+ console.log(
83
+ `\nCalling ${event.item.rawItem.name} with: ${event.item.rawItem.arguments}`
84
+ );
85
+ }
86
+ }
87
+
88
+ console.log("\n");
89
+ await result.completed;
90
+ await mcpServer.close();
examples/agents-sdk-js/package-lock.json ADDED
@@ -0,0 +1,1798 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "agents-sdk",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "agents-sdk",
9
+ "version": "1.0.0",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "@openai/agents": "^0.0.14",
13
+ "tsx": "^4.20.3",
14
+ "typescript": "^5.8.3",
15
+ "zod": "^3.25.67"
16
+ }
17
+ },
18
+ "node_modules/@esbuild/aix-ppc64": {
19
+ "version": "0.25.8",
20
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
21
+ "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
22
+ "cpu": [
23
+ "ppc64"
24
+ ],
25
+ "license": "MIT",
26
+ "optional": true,
27
+ "os": [
28
+ "aix"
29
+ ],
30
+ "engines": {
31
+ "node": ">=18"
32
+ }
33
+ },
34
+ "node_modules/@esbuild/android-arm": {
35
+ "version": "0.25.8",
36
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
37
+ "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
38
+ "cpu": [
39
+ "arm"
40
+ ],
41
+ "license": "MIT",
42
+ "optional": true,
43
+ "os": [
44
+ "android"
45
+ ],
46
+ "engines": {
47
+ "node": ">=18"
48
+ }
49
+ },
50
+ "node_modules/@esbuild/android-arm64": {
51
+ "version": "0.25.8",
52
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
53
+ "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
54
+ "cpu": [
55
+ "arm64"
56
+ ],
57
+ "license": "MIT",
58
+ "optional": true,
59
+ "os": [
60
+ "android"
61
+ ],
62
+ "engines": {
63
+ "node": ">=18"
64
+ }
65
+ },
66
+ "node_modules/@esbuild/android-x64": {
67
+ "version": "0.25.8",
68
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
69
+ "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
70
+ "cpu": [
71
+ "x64"
72
+ ],
73
+ "license": "MIT",
74
+ "optional": true,
75
+ "os": [
76
+ "android"
77
+ ],
78
+ "engines": {
79
+ "node": ">=18"
80
+ }
81
+ },
82
+ "node_modules/@esbuild/darwin-arm64": {
83
+ "version": "0.25.8",
84
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
85
+ "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
86
+ "cpu": [
87
+ "arm64"
88
+ ],
89
+ "license": "MIT",
90
+ "optional": true,
91
+ "os": [
92
+ "darwin"
93
+ ],
94
+ "engines": {
95
+ "node": ">=18"
96
+ }
97
+ },
98
+ "node_modules/@esbuild/darwin-x64": {
99
+ "version": "0.25.8",
100
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
101
+ "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
102
+ "cpu": [
103
+ "x64"
104
+ ],
105
+ "license": "MIT",
106
+ "optional": true,
107
+ "os": [
108
+ "darwin"
109
+ ],
110
+ "engines": {
111
+ "node": ">=18"
112
+ }
113
+ },
114
+ "node_modules/@esbuild/freebsd-arm64": {
115
+ "version": "0.25.8",
116
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
117
+ "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
118
+ "cpu": [
119
+ "arm64"
120
+ ],
121
+ "license": "MIT",
122
+ "optional": true,
123
+ "os": [
124
+ "freebsd"
125
+ ],
126
+ "engines": {
127
+ "node": ">=18"
128
+ }
129
+ },
130
+ "node_modules/@esbuild/freebsd-x64": {
131
+ "version": "0.25.8",
132
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
133
+ "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
134
+ "cpu": [
135
+ "x64"
136
+ ],
137
+ "license": "MIT",
138
+ "optional": true,
139
+ "os": [
140
+ "freebsd"
141
+ ],
142
+ "engines": {
143
+ "node": ">=18"
144
+ }
145
+ },
146
+ "node_modules/@esbuild/linux-arm": {
147
+ "version": "0.25.8",
148
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
149
+ "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
150
+ "cpu": [
151
+ "arm"
152
+ ],
153
+ "license": "MIT",
154
+ "optional": true,
155
+ "os": [
156
+ "linux"
157
+ ],
158
+ "engines": {
159
+ "node": ">=18"
160
+ }
161
+ },
162
+ "node_modules/@esbuild/linux-arm64": {
163
+ "version": "0.25.8",
164
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
165
+ "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
166
+ "cpu": [
167
+ "arm64"
168
+ ],
169
+ "license": "MIT",
170
+ "optional": true,
171
+ "os": [
172
+ "linux"
173
+ ],
174
+ "engines": {
175
+ "node": ">=18"
176
+ }
177
+ },
178
+ "node_modules/@esbuild/linux-ia32": {
179
+ "version": "0.25.8",
180
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
181
+ "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
182
+ "cpu": [
183
+ "ia32"
184
+ ],
185
+ "license": "MIT",
186
+ "optional": true,
187
+ "os": [
188
+ "linux"
189
+ ],
190
+ "engines": {
191
+ "node": ">=18"
192
+ }
193
+ },
194
+ "node_modules/@esbuild/linux-loong64": {
195
+ "version": "0.25.8",
196
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
197
+ "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
198
+ "cpu": [
199
+ "loong64"
200
+ ],
201
+ "license": "MIT",
202
+ "optional": true,
203
+ "os": [
204
+ "linux"
205
+ ],
206
+ "engines": {
207
+ "node": ">=18"
208
+ }
209
+ },
210
+ "node_modules/@esbuild/linux-mips64el": {
211
+ "version": "0.25.8",
212
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
213
+ "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
214
+ "cpu": [
215
+ "mips64el"
216
+ ],
217
+ "license": "MIT",
218
+ "optional": true,
219
+ "os": [
220
+ "linux"
221
+ ],
222
+ "engines": {
223
+ "node": ">=18"
224
+ }
225
+ },
226
+ "node_modules/@esbuild/linux-ppc64": {
227
+ "version": "0.25.8",
228
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
229
+ "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
230
+ "cpu": [
231
+ "ppc64"
232
+ ],
233
+ "license": "MIT",
234
+ "optional": true,
235
+ "os": [
236
+ "linux"
237
+ ],
238
+ "engines": {
239
+ "node": ">=18"
240
+ }
241
+ },
242
+ "node_modules/@esbuild/linux-riscv64": {
243
+ "version": "0.25.8",
244
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
245
+ "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
246
+ "cpu": [
247
+ "riscv64"
248
+ ],
249
+ "license": "MIT",
250
+ "optional": true,
251
+ "os": [
252
+ "linux"
253
+ ],
254
+ "engines": {
255
+ "node": ">=18"
256
+ }
257
+ },
258
+ "node_modules/@esbuild/linux-s390x": {
259
+ "version": "0.25.8",
260
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
261
+ "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
262
+ "cpu": [
263
+ "s390x"
264
+ ],
265
+ "license": "MIT",
266
+ "optional": true,
267
+ "os": [
268
+ "linux"
269
+ ],
270
+ "engines": {
271
+ "node": ">=18"
272
+ }
273
+ },
274
+ "node_modules/@esbuild/linux-x64": {
275
+ "version": "0.25.8",
276
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
277
+ "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
278
+ "cpu": [
279
+ "x64"
280
+ ],
281
+ "license": "MIT",
282
+ "optional": true,
283
+ "os": [
284
+ "linux"
285
+ ],
286
+ "engines": {
287
+ "node": ">=18"
288
+ }
289
+ },
290
+ "node_modules/@esbuild/netbsd-arm64": {
291
+ "version": "0.25.8",
292
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
293
+ "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
294
+ "cpu": [
295
+ "arm64"
296
+ ],
297
+ "license": "MIT",
298
+ "optional": true,
299
+ "os": [
300
+ "netbsd"
301
+ ],
302
+ "engines": {
303
+ "node": ">=18"
304
+ }
305
+ },
306
+ "node_modules/@esbuild/netbsd-x64": {
307
+ "version": "0.25.8",
308
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
309
+ "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
310
+ "cpu": [
311
+ "x64"
312
+ ],
313
+ "license": "MIT",
314
+ "optional": true,
315
+ "os": [
316
+ "netbsd"
317
+ ],
318
+ "engines": {
319
+ "node": ">=18"
320
+ }
321
+ },
322
+ "node_modules/@esbuild/openbsd-arm64": {
323
+ "version": "0.25.8",
324
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
325
+ "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
326
+ "cpu": [
327
+ "arm64"
328
+ ],
329
+ "license": "MIT",
330
+ "optional": true,
331
+ "os": [
332
+ "openbsd"
333
+ ],
334
+ "engines": {
335
+ "node": ">=18"
336
+ }
337
+ },
338
+ "node_modules/@esbuild/openbsd-x64": {
339
+ "version": "0.25.8",
340
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
341
+ "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
342
+ "cpu": [
343
+ "x64"
344
+ ],
345
+ "license": "MIT",
346
+ "optional": true,
347
+ "os": [
348
+ "openbsd"
349
+ ],
350
+ "engines": {
351
+ "node": ">=18"
352
+ }
353
+ },
354
+ "node_modules/@esbuild/openharmony-arm64": {
355
+ "version": "0.25.8",
356
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
357
+ "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
358
+ "cpu": [
359
+ "arm64"
360
+ ],
361
+ "license": "MIT",
362
+ "optional": true,
363
+ "os": [
364
+ "openharmony"
365
+ ],
366
+ "engines": {
367
+ "node": ">=18"
368
+ }
369
+ },
370
+ "node_modules/@esbuild/sunos-x64": {
371
+ "version": "0.25.8",
372
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
373
+ "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
374
+ "cpu": [
375
+ "x64"
376
+ ],
377
+ "license": "MIT",
378
+ "optional": true,
379
+ "os": [
380
+ "sunos"
381
+ ],
382
+ "engines": {
383
+ "node": ">=18"
384
+ }
385
+ },
386
+ "node_modules/@esbuild/win32-arm64": {
387
+ "version": "0.25.8",
388
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
389
+ "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
390
+ "cpu": [
391
+ "arm64"
392
+ ],
393
+ "license": "MIT",
394
+ "optional": true,
395
+ "os": [
396
+ "win32"
397
+ ],
398
+ "engines": {
399
+ "node": ">=18"
400
+ }
401
+ },
402
+ "node_modules/@esbuild/win32-ia32": {
403
+ "version": "0.25.8",
404
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
405
+ "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
406
+ "cpu": [
407
+ "ia32"
408
+ ],
409
+ "license": "MIT",
410
+ "optional": true,
411
+ "os": [
412
+ "win32"
413
+ ],
414
+ "engines": {
415
+ "node": ">=18"
416
+ }
417
+ },
418
+ "node_modules/@esbuild/win32-x64": {
419
+ "version": "0.25.8",
420
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
421
+ "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
422
+ "cpu": [
423
+ "x64"
424
+ ],
425
+ "license": "MIT",
426
+ "optional": true,
427
+ "os": [
428
+ "win32"
429
+ ],
430
+ "engines": {
431
+ "node": ">=18"
432
+ }
433
+ },
434
+ "node_modules/@modelcontextprotocol/sdk": {
435
+ "version": "1.17.0",
436
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.0.tgz",
437
+ "integrity": "sha512-qFfbWFA7r1Sd8D697L7GkTd36yqDuTkvz0KfOGkgXR8EUhQn3/EDNIR/qUdQNMT8IjmasBvHWuXeisxtXTQT2g==",
438
+ "license": "MIT",
439
+ "optional": true,
440
+ "dependencies": {
441
+ "ajv": "^6.12.6",
442
+ "content-type": "^1.0.5",
443
+ "cors": "^2.8.5",
444
+ "cross-spawn": "^7.0.5",
445
+ "eventsource": "^3.0.2",
446
+ "eventsource-parser": "^3.0.0",
447
+ "express": "^5.0.1",
448
+ "express-rate-limit": "^7.5.0",
449
+ "pkce-challenge": "^5.0.0",
450
+ "raw-body": "^3.0.0",
451
+ "zod": "^3.23.8",
452
+ "zod-to-json-schema": "^3.24.1"
453
+ },
454
+ "engines": {
455
+ "node": ">=18"
456
+ }
457
+ },
458
+ "node_modules/@openai/agents": {
459
+ "version": "0.0.14",
460
+ "resolved": "https://registry.npmjs.org/@openai/agents/-/agents-0.0.14.tgz",
461
+ "integrity": "sha512-67FwkSxlid8/fFzIDMBuIvDQJ2Egf7PCpI7zp2JAlIlsz4UZVSlptNcN63RCG2xP6X2XqsdyjPke8ZDEKVrePw==",
462
+ "license": "MIT",
463
+ "dependencies": {
464
+ "@openai/agents-core": "0.0.14",
465
+ "@openai/agents-openai": "0.0.14",
466
+ "@openai/agents-realtime": "0.0.14",
467
+ "debug": "^4.4.0",
468
+ "openai": "^5.10.1"
469
+ }
470
+ },
471
+ "node_modules/@openai/agents-core": {
472
+ "version": "0.0.14",
473
+ "resolved": "https://registry.npmjs.org/@openai/agents-core/-/agents-core-0.0.14.tgz",
474
+ "integrity": "sha512-enCk5ucz+xxwPgh0zBQoJi5c1RukSc60neRUmlW4eQRgj9p5hVFQaBQNapZ4RysagHCLm2scYRwKgaP6nPDuNQ==",
475
+ "license": "MIT",
476
+ "dependencies": {
477
+ "@openai/zod": "npm:zod@3.25.40 - 3.25.67",
478
+ "debug": "^4.4.0",
479
+ "openai": "^5.10.1"
480
+ },
481
+ "optionalDependencies": {
482
+ "@modelcontextprotocol/sdk": "^1.12.0"
483
+ },
484
+ "peerDependencies": {
485
+ "zod": "3.25.40 - 3.25.67"
486
+ },
487
+ "peerDependenciesMeta": {
488
+ "zod": {
489
+ "optional": true
490
+ }
491
+ }
492
+ },
493
+ "node_modules/@openai/agents-openai": {
494
+ "version": "0.0.14",
495
+ "resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.0.14.tgz",
496
+ "integrity": "sha512-qSGBictwfJ3dMhC3QvqOLMm8RVZ/eIYNcFNLHps7hWeB1xeDGJFDZ/X7dDicejOeEXbi/nGe1ry6LbXDYSo3uQ==",
497
+ "license": "MIT",
498
+ "dependencies": {
499
+ "@openai/agents-core": "0.0.14",
500
+ "@openai/zod": "npm:zod@3.25.40 - 3.25.67",
501
+ "debug": "^4.4.0",
502
+ "openai": "^5.10.1"
503
+ }
504
+ },
505
+ "node_modules/@openai/agents-realtime": {
506
+ "version": "0.0.14",
507
+ "resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.0.14.tgz",
508
+ "integrity": "sha512-gfSuWEDKZREWi0DJDf3F8fT/xvLL9R0cydfgriL0kPkWOlTMuZ0KZKI6D90pc2VAWIescA8BuqCcWkgWFq55Uw==",
509
+ "license": "MIT",
510
+ "dependencies": {
511
+ "@openai/agents-core": "0.0.14",
512
+ "@openai/zod": "npm:zod@3.25.40 - 3.25.67",
513
+ "@types/ws": "^8.18.1",
514
+ "debug": "^4.4.0",
515
+ "ws": "^8.18.1"
516
+ }
517
+ },
518
+ "node_modules/@openai/zod": {
519
+ "name": "zod",
520
+ "version": "3.25.67",
521
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
522
+ "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
523
+ "license": "MIT",
524
+ "funding": {
525
+ "url": "https://github.com/sponsors/colinhacks"
526
+ }
527
+ },
528
+ "node_modules/@types/node": {
529
+ "version": "24.1.0",
530
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
531
+ "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
532
+ "license": "MIT",
533
+ "dependencies": {
534
+ "undici-types": "~7.8.0"
535
+ }
536
+ },
537
+ "node_modules/@types/ws": {
538
+ "version": "8.18.1",
539
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
540
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
541
+ "license": "MIT",
542
+ "dependencies": {
543
+ "@types/node": "*"
544
+ }
545
+ },
546
+ "node_modules/accepts": {
547
+ "version": "2.0.0",
548
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
549
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
550
+ "license": "MIT",
551
+ "optional": true,
552
+ "dependencies": {
553
+ "mime-types": "^3.0.0",
554
+ "negotiator": "^1.0.0"
555
+ },
556
+ "engines": {
557
+ "node": ">= 0.6"
558
+ }
559
+ },
560
+ "node_modules/ajv": {
561
+ "version": "6.12.6",
562
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
563
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
564
+ "license": "MIT",
565
+ "optional": true,
566
+ "dependencies": {
567
+ "fast-deep-equal": "^3.1.1",
568
+ "fast-json-stable-stringify": "^2.0.0",
569
+ "json-schema-traverse": "^0.4.1",
570
+ "uri-js": "^4.2.2"
571
+ },
572
+ "funding": {
573
+ "type": "github",
574
+ "url": "https://github.com/sponsors/epoberezkin"
575
+ }
576
+ },
577
+ "node_modules/body-parser": {
578
+ "version": "2.2.0",
579
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
580
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
581
+ "license": "MIT",
582
+ "optional": true,
583
+ "dependencies": {
584
+ "bytes": "^3.1.2",
585
+ "content-type": "^1.0.5",
586
+ "debug": "^4.4.0",
587
+ "http-errors": "^2.0.0",
588
+ "iconv-lite": "^0.6.3",
589
+ "on-finished": "^2.4.1",
590
+ "qs": "^6.14.0",
591
+ "raw-body": "^3.0.0",
592
+ "type-is": "^2.0.0"
593
+ },
594
+ "engines": {
595
+ "node": ">=18"
596
+ }
597
+ },
598
+ "node_modules/bytes": {
599
+ "version": "3.1.2",
600
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
601
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
602
+ "license": "MIT",
603
+ "optional": true,
604
+ "engines": {
605
+ "node": ">= 0.8"
606
+ }
607
+ },
608
+ "node_modules/call-bind-apply-helpers": {
609
+ "version": "1.0.2",
610
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
611
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
612
+ "license": "MIT",
613
+ "optional": true,
614
+ "dependencies": {
615
+ "es-errors": "^1.3.0",
616
+ "function-bind": "^1.1.2"
617
+ },
618
+ "engines": {
619
+ "node": ">= 0.4"
620
+ }
621
+ },
622
+ "node_modules/call-bound": {
623
+ "version": "1.0.4",
624
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
625
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
626
+ "license": "MIT",
627
+ "optional": true,
628
+ "dependencies": {
629
+ "call-bind-apply-helpers": "^1.0.2",
630
+ "get-intrinsic": "^1.3.0"
631
+ },
632
+ "engines": {
633
+ "node": ">= 0.4"
634
+ },
635
+ "funding": {
636
+ "url": "https://github.com/sponsors/ljharb"
637
+ }
638
+ },
639
+ "node_modules/content-disposition": {
640
+ "version": "1.0.0",
641
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
642
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
643
+ "license": "MIT",
644
+ "optional": true,
645
+ "dependencies": {
646
+ "safe-buffer": "5.2.1"
647
+ },
648
+ "engines": {
649
+ "node": ">= 0.6"
650
+ }
651
+ },
652
+ "node_modules/content-type": {
653
+ "version": "1.0.5",
654
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
655
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
656
+ "license": "MIT",
657
+ "optional": true,
658
+ "engines": {
659
+ "node": ">= 0.6"
660
+ }
661
+ },
662
+ "node_modules/cookie": {
663
+ "version": "0.7.2",
664
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
665
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
666
+ "license": "MIT",
667
+ "optional": true,
668
+ "engines": {
669
+ "node": ">= 0.6"
670
+ }
671
+ },
672
+ "node_modules/cookie-signature": {
673
+ "version": "1.2.2",
674
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
675
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
676
+ "license": "MIT",
677
+ "optional": true,
678
+ "engines": {
679
+ "node": ">=6.6.0"
680
+ }
681
+ },
682
+ "node_modules/cors": {
683
+ "version": "2.8.5",
684
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
685
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
686
+ "license": "MIT",
687
+ "optional": true,
688
+ "dependencies": {
689
+ "object-assign": "^4",
690
+ "vary": "^1"
691
+ },
692
+ "engines": {
693
+ "node": ">= 0.10"
694
+ }
695
+ },
696
+ "node_modules/cross-spawn": {
697
+ "version": "7.0.6",
698
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
699
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
700
+ "license": "MIT",
701
+ "optional": true,
702
+ "dependencies": {
703
+ "path-key": "^3.1.0",
704
+ "shebang-command": "^2.0.0",
705
+ "which": "^2.0.1"
706
+ },
707
+ "engines": {
708
+ "node": ">= 8"
709
+ }
710
+ },
711
+ "node_modules/debug": {
712
+ "version": "4.4.1",
713
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
714
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
715
+ "license": "MIT",
716
+ "dependencies": {
717
+ "ms": "^2.1.3"
718
+ },
719
+ "engines": {
720
+ "node": ">=6.0"
721
+ },
722
+ "peerDependenciesMeta": {
723
+ "supports-color": {
724
+ "optional": true
725
+ }
726
+ }
727
+ },
728
+ "node_modules/depd": {
729
+ "version": "2.0.0",
730
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
731
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
732
+ "license": "MIT",
733
+ "optional": true,
734
+ "engines": {
735
+ "node": ">= 0.8"
736
+ }
737
+ },
738
+ "node_modules/dunder-proto": {
739
+ "version": "1.0.1",
740
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
741
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
742
+ "license": "MIT",
743
+ "optional": true,
744
+ "dependencies": {
745
+ "call-bind-apply-helpers": "^1.0.1",
746
+ "es-errors": "^1.3.0",
747
+ "gopd": "^1.2.0"
748
+ },
749
+ "engines": {
750
+ "node": ">= 0.4"
751
+ }
752
+ },
753
+ "node_modules/ee-first": {
754
+ "version": "1.1.1",
755
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
756
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
757
+ "license": "MIT",
758
+ "optional": true
759
+ },
760
+ "node_modules/encodeurl": {
761
+ "version": "2.0.0",
762
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
763
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
764
+ "license": "MIT",
765
+ "optional": true,
766
+ "engines": {
767
+ "node": ">= 0.8"
768
+ }
769
+ },
770
+ "node_modules/es-define-property": {
771
+ "version": "1.0.1",
772
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
773
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
774
+ "license": "MIT",
775
+ "optional": true,
776
+ "engines": {
777
+ "node": ">= 0.4"
778
+ }
779
+ },
780
+ "node_modules/es-errors": {
781
+ "version": "1.3.0",
782
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
783
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
784
+ "license": "MIT",
785
+ "optional": true,
786
+ "engines": {
787
+ "node": ">= 0.4"
788
+ }
789
+ },
790
+ "node_modules/es-object-atoms": {
791
+ "version": "1.1.1",
792
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
793
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
794
+ "license": "MIT",
795
+ "optional": true,
796
+ "dependencies": {
797
+ "es-errors": "^1.3.0"
798
+ },
799
+ "engines": {
800
+ "node": ">= 0.4"
801
+ }
802
+ },
803
+ "node_modules/esbuild": {
804
+ "version": "0.25.8",
805
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
806
+ "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
807
+ "hasInstallScript": true,
808
+ "license": "MIT",
809
+ "bin": {
810
+ "esbuild": "bin/esbuild"
811
+ },
812
+ "engines": {
813
+ "node": ">=18"
814
+ },
815
+ "optionalDependencies": {
816
+ "@esbuild/aix-ppc64": "0.25.8",
817
+ "@esbuild/android-arm": "0.25.8",
818
+ "@esbuild/android-arm64": "0.25.8",
819
+ "@esbuild/android-x64": "0.25.8",
820
+ "@esbuild/darwin-arm64": "0.25.8",
821
+ "@esbuild/darwin-x64": "0.25.8",
822
+ "@esbuild/freebsd-arm64": "0.25.8",
823
+ "@esbuild/freebsd-x64": "0.25.8",
824
+ "@esbuild/linux-arm": "0.25.8",
825
+ "@esbuild/linux-arm64": "0.25.8",
826
+ "@esbuild/linux-ia32": "0.25.8",
827
+ "@esbuild/linux-loong64": "0.25.8",
828
+ "@esbuild/linux-mips64el": "0.25.8",
829
+ "@esbuild/linux-ppc64": "0.25.8",
830
+ "@esbuild/linux-riscv64": "0.25.8",
831
+ "@esbuild/linux-s390x": "0.25.8",
832
+ "@esbuild/linux-x64": "0.25.8",
833
+ "@esbuild/netbsd-arm64": "0.25.8",
834
+ "@esbuild/netbsd-x64": "0.25.8",
835
+ "@esbuild/openbsd-arm64": "0.25.8",
836
+ "@esbuild/openbsd-x64": "0.25.8",
837
+ "@esbuild/openharmony-arm64": "0.25.8",
838
+ "@esbuild/sunos-x64": "0.25.8",
839
+ "@esbuild/win32-arm64": "0.25.8",
840
+ "@esbuild/win32-ia32": "0.25.8",
841
+ "@esbuild/win32-x64": "0.25.8"
842
+ }
843
+ },
844
+ "node_modules/escape-html": {
845
+ "version": "1.0.3",
846
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
847
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
848
+ "license": "MIT",
849
+ "optional": true
850
+ },
851
+ "node_modules/etag": {
852
+ "version": "1.8.1",
853
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
854
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
855
+ "license": "MIT",
856
+ "optional": true,
857
+ "engines": {
858
+ "node": ">= 0.6"
859
+ }
860
+ },
861
+ "node_modules/eventsource": {
862
+ "version": "3.0.7",
863
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
864
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
865
+ "license": "MIT",
866
+ "optional": true,
867
+ "dependencies": {
868
+ "eventsource-parser": "^3.0.1"
869
+ },
870
+ "engines": {
871
+ "node": ">=18.0.0"
872
+ }
873
+ },
874
+ "node_modules/eventsource-parser": {
875
+ "version": "3.0.3",
876
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
877
+ "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==",
878
+ "license": "MIT",
879
+ "optional": true,
880
+ "engines": {
881
+ "node": ">=20.0.0"
882
+ }
883
+ },
884
+ "node_modules/express": {
885
+ "version": "5.1.0",
886
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
887
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
888
+ "license": "MIT",
889
+ "optional": true,
890
+ "dependencies": {
891
+ "accepts": "^2.0.0",
892
+ "body-parser": "^2.2.0",
893
+ "content-disposition": "^1.0.0",
894
+ "content-type": "^1.0.5",
895
+ "cookie": "^0.7.1",
896
+ "cookie-signature": "^1.2.1",
897
+ "debug": "^4.4.0",
898
+ "encodeurl": "^2.0.0",
899
+ "escape-html": "^1.0.3",
900
+ "etag": "^1.8.1",
901
+ "finalhandler": "^2.1.0",
902
+ "fresh": "^2.0.0",
903
+ "http-errors": "^2.0.0",
904
+ "merge-descriptors": "^2.0.0",
905
+ "mime-types": "^3.0.0",
906
+ "on-finished": "^2.4.1",
907
+ "once": "^1.4.0",
908
+ "parseurl": "^1.3.3",
909
+ "proxy-addr": "^2.0.7",
910
+ "qs": "^6.14.0",
911
+ "range-parser": "^1.2.1",
912
+ "router": "^2.2.0",
913
+ "send": "^1.1.0",
914
+ "serve-static": "^2.2.0",
915
+ "statuses": "^2.0.1",
916
+ "type-is": "^2.0.1",
917
+ "vary": "^1.1.2"
918
+ },
919
+ "engines": {
920
+ "node": ">= 18"
921
+ },
922
+ "funding": {
923
+ "type": "opencollective",
924
+ "url": "https://opencollective.com/express"
925
+ }
926
+ },
927
+ "node_modules/express-rate-limit": {
928
+ "version": "7.5.1",
929
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
930
+ "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
931
+ "license": "MIT",
932
+ "optional": true,
933
+ "engines": {
934
+ "node": ">= 16"
935
+ },
936
+ "funding": {
937
+ "url": "https://github.com/sponsors/express-rate-limit"
938
+ },
939
+ "peerDependencies": {
940
+ "express": ">= 4.11"
941
+ }
942
+ },
943
+ "node_modules/fast-deep-equal": {
944
+ "version": "3.1.3",
945
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
946
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
947
+ "license": "MIT",
948
+ "optional": true
949
+ },
950
+ "node_modules/fast-json-stable-stringify": {
951
+ "version": "2.1.0",
952
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
953
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
954
+ "license": "MIT",
955
+ "optional": true
956
+ },
957
+ "node_modules/finalhandler": {
958
+ "version": "2.1.0",
959
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
960
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
961
+ "license": "MIT",
962
+ "optional": true,
963
+ "dependencies": {
964
+ "debug": "^4.4.0",
965
+ "encodeurl": "^2.0.0",
966
+ "escape-html": "^1.0.3",
967
+ "on-finished": "^2.4.1",
968
+ "parseurl": "^1.3.3",
969
+ "statuses": "^2.0.1"
970
+ },
971
+ "engines": {
972
+ "node": ">= 0.8"
973
+ }
974
+ },
975
+ "node_modules/forwarded": {
976
+ "version": "0.2.0",
977
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
978
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
979
+ "license": "MIT",
980
+ "optional": true,
981
+ "engines": {
982
+ "node": ">= 0.6"
983
+ }
984
+ },
985
+ "node_modules/fresh": {
986
+ "version": "2.0.0",
987
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
988
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
989
+ "license": "MIT",
990
+ "optional": true,
991
+ "engines": {
992
+ "node": ">= 0.8"
993
+ }
994
+ },
995
+ "node_modules/fsevents": {
996
+ "version": "2.3.3",
997
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
998
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
999
+ "hasInstallScript": true,
1000
+ "license": "MIT",
1001
+ "optional": true,
1002
+ "os": [
1003
+ "darwin"
1004
+ ],
1005
+ "engines": {
1006
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1007
+ }
1008
+ },
1009
+ "node_modules/function-bind": {
1010
+ "version": "1.1.2",
1011
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1012
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1013
+ "license": "MIT",
1014
+ "optional": true,
1015
+ "funding": {
1016
+ "url": "https://github.com/sponsors/ljharb"
1017
+ }
1018
+ },
1019
+ "node_modules/get-intrinsic": {
1020
+ "version": "1.3.0",
1021
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1022
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
1023
+ "license": "MIT",
1024
+ "optional": true,
1025
+ "dependencies": {
1026
+ "call-bind-apply-helpers": "^1.0.2",
1027
+ "es-define-property": "^1.0.1",
1028
+ "es-errors": "^1.3.0",
1029
+ "es-object-atoms": "^1.1.1",
1030
+ "function-bind": "^1.1.2",
1031
+ "get-proto": "^1.0.1",
1032
+ "gopd": "^1.2.0",
1033
+ "has-symbols": "^1.1.0",
1034
+ "hasown": "^2.0.2",
1035
+ "math-intrinsics": "^1.1.0"
1036
+ },
1037
+ "engines": {
1038
+ "node": ">= 0.4"
1039
+ },
1040
+ "funding": {
1041
+ "url": "https://github.com/sponsors/ljharb"
1042
+ }
1043
+ },
1044
+ "node_modules/get-proto": {
1045
+ "version": "1.0.1",
1046
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1047
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1048
+ "license": "MIT",
1049
+ "optional": true,
1050
+ "dependencies": {
1051
+ "dunder-proto": "^1.0.1",
1052
+ "es-object-atoms": "^1.0.0"
1053
+ },
1054
+ "engines": {
1055
+ "node": ">= 0.4"
1056
+ }
1057
+ },
1058
+ "node_modules/get-tsconfig": {
1059
+ "version": "4.10.1",
1060
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
1061
+ "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
1062
+ "license": "MIT",
1063
+ "dependencies": {
1064
+ "resolve-pkg-maps": "^1.0.0"
1065
+ },
1066
+ "funding": {
1067
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
1068
+ }
1069
+ },
1070
+ "node_modules/gopd": {
1071
+ "version": "1.2.0",
1072
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1073
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1074
+ "license": "MIT",
1075
+ "optional": true,
1076
+ "engines": {
1077
+ "node": ">= 0.4"
1078
+ },
1079
+ "funding": {
1080
+ "url": "https://github.com/sponsors/ljharb"
1081
+ }
1082
+ },
1083
+ "node_modules/has-symbols": {
1084
+ "version": "1.1.0",
1085
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1086
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1087
+ "license": "MIT",
1088
+ "optional": true,
1089
+ "engines": {
1090
+ "node": ">= 0.4"
1091
+ },
1092
+ "funding": {
1093
+ "url": "https://github.com/sponsors/ljharb"
1094
+ }
1095
+ },
1096
+ "node_modules/hasown": {
1097
+ "version": "2.0.2",
1098
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1099
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1100
+ "license": "MIT",
1101
+ "optional": true,
1102
+ "dependencies": {
1103
+ "function-bind": "^1.1.2"
1104
+ },
1105
+ "engines": {
1106
+ "node": ">= 0.4"
1107
+ }
1108
+ },
1109
+ "node_modules/http-errors": {
1110
+ "version": "2.0.0",
1111
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
1112
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
1113
+ "license": "MIT",
1114
+ "optional": true,
1115
+ "dependencies": {
1116
+ "depd": "2.0.0",
1117
+ "inherits": "2.0.4",
1118
+ "setprototypeof": "1.2.0",
1119
+ "statuses": "2.0.1",
1120
+ "toidentifier": "1.0.1"
1121
+ },
1122
+ "engines": {
1123
+ "node": ">= 0.8"
1124
+ }
1125
+ },
1126
+ "node_modules/http-errors/node_modules/statuses": {
1127
+ "version": "2.0.1",
1128
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1129
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1130
+ "license": "MIT",
1131
+ "optional": true,
1132
+ "engines": {
1133
+ "node": ">= 0.8"
1134
+ }
1135
+ },
1136
+ "node_modules/iconv-lite": {
1137
+ "version": "0.6.3",
1138
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1139
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
1140
+ "license": "MIT",
1141
+ "optional": true,
1142
+ "dependencies": {
1143
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
1144
+ },
1145
+ "engines": {
1146
+ "node": ">=0.10.0"
1147
+ }
1148
+ },
1149
+ "node_modules/inherits": {
1150
+ "version": "2.0.4",
1151
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1152
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1153
+ "license": "ISC",
1154
+ "optional": true
1155
+ },
1156
+ "node_modules/ipaddr.js": {
1157
+ "version": "1.9.1",
1158
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
1159
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
1160
+ "license": "MIT",
1161
+ "optional": true,
1162
+ "engines": {
1163
+ "node": ">= 0.10"
1164
+ }
1165
+ },
1166
+ "node_modules/is-promise": {
1167
+ "version": "4.0.0",
1168
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
1169
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
1170
+ "license": "MIT",
1171
+ "optional": true
1172
+ },
1173
+ "node_modules/isexe": {
1174
+ "version": "2.0.0",
1175
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
1176
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
1177
+ "license": "ISC",
1178
+ "optional": true
1179
+ },
1180
+ "node_modules/json-schema-traverse": {
1181
+ "version": "0.4.1",
1182
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
1183
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
1184
+ "license": "MIT",
1185
+ "optional": true
1186
+ },
1187
+ "node_modules/math-intrinsics": {
1188
+ "version": "1.1.0",
1189
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1190
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1191
+ "license": "MIT",
1192
+ "optional": true,
1193
+ "engines": {
1194
+ "node": ">= 0.4"
1195
+ }
1196
+ },
1197
+ "node_modules/media-typer": {
1198
+ "version": "1.1.0",
1199
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
1200
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
1201
+ "license": "MIT",
1202
+ "optional": true,
1203
+ "engines": {
1204
+ "node": ">= 0.8"
1205
+ }
1206
+ },
1207
+ "node_modules/merge-descriptors": {
1208
+ "version": "2.0.0",
1209
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
1210
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
1211
+ "license": "MIT",
1212
+ "optional": true,
1213
+ "engines": {
1214
+ "node": ">=18"
1215
+ },
1216
+ "funding": {
1217
+ "url": "https://github.com/sponsors/sindresorhus"
1218
+ }
1219
+ },
1220
+ "node_modules/mime-db": {
1221
+ "version": "1.54.0",
1222
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
1223
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
1224
+ "license": "MIT",
1225
+ "optional": true,
1226
+ "engines": {
1227
+ "node": ">= 0.6"
1228
+ }
1229
+ },
1230
+ "node_modules/mime-types": {
1231
+ "version": "3.0.1",
1232
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
1233
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
1234
+ "license": "MIT",
1235
+ "optional": true,
1236
+ "dependencies": {
1237
+ "mime-db": "^1.54.0"
1238
+ },
1239
+ "engines": {
1240
+ "node": ">= 0.6"
1241
+ }
1242
+ },
1243
+ "node_modules/ms": {
1244
+ "version": "2.1.3",
1245
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1246
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1247
+ "license": "MIT"
1248
+ },
1249
+ "node_modules/negotiator": {
1250
+ "version": "1.0.0",
1251
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
1252
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
1253
+ "license": "MIT",
1254
+ "optional": true,
1255
+ "engines": {
1256
+ "node": ">= 0.6"
1257
+ }
1258
+ },
1259
+ "node_modules/object-assign": {
1260
+ "version": "4.1.1",
1261
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1262
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1263
+ "license": "MIT",
1264
+ "optional": true,
1265
+ "engines": {
1266
+ "node": ">=0.10.0"
1267
+ }
1268
+ },
1269
+ "node_modules/object-inspect": {
1270
+ "version": "1.13.4",
1271
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1272
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1273
+ "license": "MIT",
1274
+ "optional": true,
1275
+ "engines": {
1276
+ "node": ">= 0.4"
1277
+ },
1278
+ "funding": {
1279
+ "url": "https://github.com/sponsors/ljharb"
1280
+ }
1281
+ },
1282
+ "node_modules/on-finished": {
1283
+ "version": "2.4.1",
1284
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1285
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1286
+ "license": "MIT",
1287
+ "optional": true,
1288
+ "dependencies": {
1289
+ "ee-first": "1.1.1"
1290
+ },
1291
+ "engines": {
1292
+ "node": ">= 0.8"
1293
+ }
1294
+ },
1295
+ "node_modules/once": {
1296
+ "version": "1.4.0",
1297
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1298
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1299
+ "license": "ISC",
1300
+ "optional": true,
1301
+ "dependencies": {
1302
+ "wrappy": "1"
1303
+ }
1304
+ },
1305
+ "node_modules/openai": {
1306
+ "version": "5.11.0",
1307
+ "resolved": "https://registry.npmjs.org/openai/-/openai-5.11.0.tgz",
1308
+ "integrity": "sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg==",
1309
+ "license": "Apache-2.0",
1310
+ "bin": {
1311
+ "openai": "bin/cli"
1312
+ },
1313
+ "peerDependencies": {
1314
+ "ws": "^8.18.0",
1315
+ "zod": "^3.23.8"
1316
+ },
1317
+ "peerDependenciesMeta": {
1318
+ "ws": {
1319
+ "optional": true
1320
+ },
1321
+ "zod": {
1322
+ "optional": true
1323
+ }
1324
+ }
1325
+ },
1326
+ "node_modules/parseurl": {
1327
+ "version": "1.3.3",
1328
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1329
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1330
+ "license": "MIT",
1331
+ "optional": true,
1332
+ "engines": {
1333
+ "node": ">= 0.8"
1334
+ }
1335
+ },
1336
+ "node_modules/path-key": {
1337
+ "version": "3.1.1",
1338
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1339
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1340
+ "license": "MIT",
1341
+ "optional": true,
1342
+ "engines": {
1343
+ "node": ">=8"
1344
+ }
1345
+ },
1346
+ "node_modules/path-to-regexp": {
1347
+ "version": "8.2.0",
1348
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
1349
+ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
1350
+ "license": "MIT",
1351
+ "optional": true,
1352
+ "engines": {
1353
+ "node": ">=16"
1354
+ }
1355
+ },
1356
+ "node_modules/pkce-challenge": {
1357
+ "version": "5.0.0",
1358
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
1359
+ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
1360
+ "license": "MIT",
1361
+ "optional": true,
1362
+ "engines": {
1363
+ "node": ">=16.20.0"
1364
+ }
1365
+ },
1366
+ "node_modules/proxy-addr": {
1367
+ "version": "2.0.7",
1368
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1369
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1370
+ "license": "MIT",
1371
+ "optional": true,
1372
+ "dependencies": {
1373
+ "forwarded": "0.2.0",
1374
+ "ipaddr.js": "1.9.1"
1375
+ },
1376
+ "engines": {
1377
+ "node": ">= 0.10"
1378
+ }
1379
+ },
1380
+ "node_modules/punycode": {
1381
+ "version": "2.3.1",
1382
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1383
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1384
+ "license": "MIT",
1385
+ "optional": true,
1386
+ "engines": {
1387
+ "node": ">=6"
1388
+ }
1389
+ },
1390
+ "node_modules/qs": {
1391
+ "version": "6.14.0",
1392
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
1393
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
1394
+ "license": "BSD-3-Clause",
1395
+ "optional": true,
1396
+ "dependencies": {
1397
+ "side-channel": "^1.1.0"
1398
+ },
1399
+ "engines": {
1400
+ "node": ">=0.6"
1401
+ },
1402
+ "funding": {
1403
+ "url": "https://github.com/sponsors/ljharb"
1404
+ }
1405
+ },
1406
+ "node_modules/range-parser": {
1407
+ "version": "1.2.1",
1408
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1409
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1410
+ "license": "MIT",
1411
+ "optional": true,
1412
+ "engines": {
1413
+ "node": ">= 0.6"
1414
+ }
1415
+ },
1416
+ "node_modules/raw-body": {
1417
+ "version": "3.0.0",
1418
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
1419
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
1420
+ "license": "MIT",
1421
+ "optional": true,
1422
+ "dependencies": {
1423
+ "bytes": "3.1.2",
1424
+ "http-errors": "2.0.0",
1425
+ "iconv-lite": "0.6.3",
1426
+ "unpipe": "1.0.0"
1427
+ },
1428
+ "engines": {
1429
+ "node": ">= 0.8"
1430
+ }
1431
+ },
1432
+ "node_modules/resolve-pkg-maps": {
1433
+ "version": "1.0.0",
1434
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
1435
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
1436
+ "license": "MIT",
1437
+ "funding": {
1438
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
1439
+ }
1440
+ },
1441
+ "node_modules/router": {
1442
+ "version": "2.2.0",
1443
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
1444
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
1445
+ "license": "MIT",
1446
+ "optional": true,
1447
+ "dependencies": {
1448
+ "debug": "^4.4.0",
1449
+ "depd": "^2.0.0",
1450
+ "is-promise": "^4.0.0",
1451
+ "parseurl": "^1.3.3",
1452
+ "path-to-regexp": "^8.0.0"
1453
+ },
1454
+ "engines": {
1455
+ "node": ">= 18"
1456
+ }
1457
+ },
1458
+ "node_modules/safe-buffer": {
1459
+ "version": "5.2.1",
1460
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1461
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1462
+ "funding": [
1463
+ {
1464
+ "type": "github",
1465
+ "url": "https://github.com/sponsors/feross"
1466
+ },
1467
+ {
1468
+ "type": "patreon",
1469
+ "url": "https://www.patreon.com/feross"
1470
+ },
1471
+ {
1472
+ "type": "consulting",
1473
+ "url": "https://feross.org/support"
1474
+ }
1475
+ ],
1476
+ "license": "MIT",
1477
+ "optional": true
1478
+ },
1479
+ "node_modules/safer-buffer": {
1480
+ "version": "2.1.2",
1481
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1482
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1483
+ "license": "MIT",
1484
+ "optional": true
1485
+ },
1486
+ "node_modules/send": {
1487
+ "version": "1.2.0",
1488
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
1489
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
1490
+ "license": "MIT",
1491
+ "optional": true,
1492
+ "dependencies": {
1493
+ "debug": "^4.3.5",
1494
+ "encodeurl": "^2.0.0",
1495
+ "escape-html": "^1.0.3",
1496
+ "etag": "^1.8.1",
1497
+ "fresh": "^2.0.0",
1498
+ "http-errors": "^2.0.0",
1499
+ "mime-types": "^3.0.1",
1500
+ "ms": "^2.1.3",
1501
+ "on-finished": "^2.4.1",
1502
+ "range-parser": "^1.2.1",
1503
+ "statuses": "^2.0.1"
1504
+ },
1505
+ "engines": {
1506
+ "node": ">= 18"
1507
+ }
1508
+ },
1509
+ "node_modules/serve-static": {
1510
+ "version": "2.2.0",
1511
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
1512
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
1513
+ "license": "MIT",
1514
+ "optional": true,
1515
+ "dependencies": {
1516
+ "encodeurl": "^2.0.0",
1517
+ "escape-html": "^1.0.3",
1518
+ "parseurl": "^1.3.3",
1519
+ "send": "^1.2.0"
1520
+ },
1521
+ "engines": {
1522
+ "node": ">= 18"
1523
+ }
1524
+ },
1525
+ "node_modules/setprototypeof": {
1526
+ "version": "1.2.0",
1527
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1528
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1529
+ "license": "ISC",
1530
+ "optional": true
1531
+ },
1532
+ "node_modules/shebang-command": {
1533
+ "version": "2.0.0",
1534
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1535
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1536
+ "license": "MIT",
1537
+ "optional": true,
1538
+ "dependencies": {
1539
+ "shebang-regex": "^3.0.0"
1540
+ },
1541
+ "engines": {
1542
+ "node": ">=8"
1543
+ }
1544
+ },
1545
+ "node_modules/shebang-regex": {
1546
+ "version": "3.0.0",
1547
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1548
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1549
+ "license": "MIT",
1550
+ "optional": true,
1551
+ "engines": {
1552
+ "node": ">=8"
1553
+ }
1554
+ },
1555
+ "node_modules/side-channel": {
1556
+ "version": "1.1.0",
1557
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1558
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1559
+ "license": "MIT",
1560
+ "optional": true,
1561
+ "dependencies": {
1562
+ "es-errors": "^1.3.0",
1563
+ "object-inspect": "^1.13.3",
1564
+ "side-channel-list": "^1.0.0",
1565
+ "side-channel-map": "^1.0.1",
1566
+ "side-channel-weakmap": "^1.0.2"
1567
+ },
1568
+ "engines": {
1569
+ "node": ">= 0.4"
1570
+ },
1571
+ "funding": {
1572
+ "url": "https://github.com/sponsors/ljharb"
1573
+ }
1574
+ },
1575
+ "node_modules/side-channel-list": {
1576
+ "version": "1.0.0",
1577
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1578
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1579
+ "license": "MIT",
1580
+ "optional": true,
1581
+ "dependencies": {
1582
+ "es-errors": "^1.3.0",
1583
+ "object-inspect": "^1.13.3"
1584
+ },
1585
+ "engines": {
1586
+ "node": ">= 0.4"
1587
+ },
1588
+ "funding": {
1589
+ "url": "https://github.com/sponsors/ljharb"
1590
+ }
1591
+ },
1592
+ "node_modules/side-channel-map": {
1593
+ "version": "1.0.1",
1594
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1595
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1596
+ "license": "MIT",
1597
+ "optional": true,
1598
+ "dependencies": {
1599
+ "call-bound": "^1.0.2",
1600
+ "es-errors": "^1.3.0",
1601
+ "get-intrinsic": "^1.2.5",
1602
+ "object-inspect": "^1.13.3"
1603
+ },
1604
+ "engines": {
1605
+ "node": ">= 0.4"
1606
+ },
1607
+ "funding": {
1608
+ "url": "https://github.com/sponsors/ljharb"
1609
+ }
1610
+ },
1611
+ "node_modules/side-channel-weakmap": {
1612
+ "version": "1.0.2",
1613
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1614
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1615
+ "license": "MIT",
1616
+ "optional": true,
1617
+ "dependencies": {
1618
+ "call-bound": "^1.0.2",
1619
+ "es-errors": "^1.3.0",
1620
+ "get-intrinsic": "^1.2.5",
1621
+ "object-inspect": "^1.13.3",
1622
+ "side-channel-map": "^1.0.1"
1623
+ },
1624
+ "engines": {
1625
+ "node": ">= 0.4"
1626
+ },
1627
+ "funding": {
1628
+ "url": "https://github.com/sponsors/ljharb"
1629
+ }
1630
+ },
1631
+ "node_modules/statuses": {
1632
+ "version": "2.0.2",
1633
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1634
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1635
+ "license": "MIT",
1636
+ "optional": true,
1637
+ "engines": {
1638
+ "node": ">= 0.8"
1639
+ }
1640
+ },
1641
+ "node_modules/toidentifier": {
1642
+ "version": "1.0.1",
1643
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1644
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1645
+ "license": "MIT",
1646
+ "optional": true,
1647
+ "engines": {
1648
+ "node": ">=0.6"
1649
+ }
1650
+ },
1651
+ "node_modules/tsx": {
1652
+ "version": "4.20.3",
1653
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz",
1654
+ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
1655
+ "license": "MIT",
1656
+ "dependencies": {
1657
+ "esbuild": "~0.25.0",
1658
+ "get-tsconfig": "^4.7.5"
1659
+ },
1660
+ "bin": {
1661
+ "tsx": "dist/cli.mjs"
1662
+ },
1663
+ "engines": {
1664
+ "node": ">=18.0.0"
1665
+ },
1666
+ "optionalDependencies": {
1667
+ "fsevents": "~2.3.3"
1668
+ }
1669
+ },
1670
+ "node_modules/type-is": {
1671
+ "version": "2.0.1",
1672
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
1673
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
1674
+ "license": "MIT",
1675
+ "optional": true,
1676
+ "dependencies": {
1677
+ "content-type": "^1.0.5",
1678
+ "media-typer": "^1.1.0",
1679
+ "mime-types": "^3.0.0"
1680
+ },
1681
+ "engines": {
1682
+ "node": ">= 0.6"
1683
+ }
1684
+ },
1685
+ "node_modules/typescript": {
1686
+ "version": "5.8.3",
1687
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
1688
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
1689
+ "license": "Apache-2.0",
1690
+ "bin": {
1691
+ "tsc": "bin/tsc",
1692
+ "tsserver": "bin/tsserver"
1693
+ },
1694
+ "engines": {
1695
+ "node": ">=14.17"
1696
+ }
1697
+ },
1698
+ "node_modules/undici-types": {
1699
+ "version": "7.8.0",
1700
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
1701
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
1702
+ "license": "MIT"
1703
+ },
1704
+ "node_modules/unpipe": {
1705
+ "version": "1.0.0",
1706
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1707
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1708
+ "license": "MIT",
1709
+ "optional": true,
1710
+ "engines": {
1711
+ "node": ">= 0.8"
1712
+ }
1713
+ },
1714
+ "node_modules/uri-js": {
1715
+ "version": "4.4.1",
1716
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
1717
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
1718
+ "license": "BSD-2-Clause",
1719
+ "optional": true,
1720
+ "dependencies": {
1721
+ "punycode": "^2.1.0"
1722
+ }
1723
+ },
1724
+ "node_modules/vary": {
1725
+ "version": "1.1.2",
1726
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1727
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1728
+ "license": "MIT",
1729
+ "optional": true,
1730
+ "engines": {
1731
+ "node": ">= 0.8"
1732
+ }
1733
+ },
1734
+ "node_modules/which": {
1735
+ "version": "2.0.2",
1736
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1737
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1738
+ "license": "ISC",
1739
+ "optional": true,
1740
+ "dependencies": {
1741
+ "isexe": "^2.0.0"
1742
+ },
1743
+ "bin": {
1744
+ "node-which": "bin/node-which"
1745
+ },
1746
+ "engines": {
1747
+ "node": ">= 8"
1748
+ }
1749
+ },
1750
+ "node_modules/wrappy": {
1751
+ "version": "1.0.2",
1752
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1753
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1754
+ "license": "ISC",
1755
+ "optional": true
1756
+ },
1757
+ "node_modules/ws": {
1758
+ "version": "8.18.3",
1759
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
1760
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
1761
+ "license": "MIT",
1762
+ "engines": {
1763
+ "node": ">=10.0.0"
1764
+ },
1765
+ "peerDependencies": {
1766
+ "bufferutil": "^4.0.1",
1767
+ "utf-8-validate": ">=5.0.2"
1768
+ },
1769
+ "peerDependenciesMeta": {
1770
+ "bufferutil": {
1771
+ "optional": true
1772
+ },
1773
+ "utf-8-validate": {
1774
+ "optional": true
1775
+ }
1776
+ }
1777
+ },
1778
+ "node_modules/zod": {
1779
+ "version": "3.25.67",
1780
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
1781
+ "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
1782
+ "license": "MIT",
1783
+ "funding": {
1784
+ "url": "https://github.com/sponsors/colinhacks"
1785
+ }
1786
+ },
1787
+ "node_modules/zod-to-json-schema": {
1788
+ "version": "3.24.6",
1789
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
1790
+ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
1791
+ "license": "ISC",
1792
+ "optional": true,
1793
+ "peerDependencies": {
1794
+ "zod": "^3.24.1"
1795
+ }
1796
+ }
1797
+ }
1798
+ }
examples/agents-sdk-js/package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "module",
3
+ "name": "agents-sdk",
4
+ "version": "1.0.0",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "start": "tsx index.ts",
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "description": "",
14
+ "dependencies": {
15
+ "@openai/agents": "^0.0.14",
16
+ "tsx": "^4.20.3",
17
+ "typescript": "^5.8.3",
18
+ "zod": "^3.25.67"
19
+ }
20
+ }
examples/agents-sdk-python/example.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from pathlib import Path
3
+ import shutil
4
+
5
+ from openai import AsyncOpenAI
6
+ from agents import (
7
+ Agent,
8
+ ItemHelpers,
9
+ Runner,
10
+ set_default_openai_api,
11
+ set_default_openai_client,
12
+ set_tracing_disabled,
13
+ function_tool,
14
+ )
15
+ from agents.mcp import MCPServerStdio
16
+
17
+
18
+ async def prompt_user(question: str) -> str:
19
+ """Async input prompt function"""
20
+ loop = asyncio.get_event_loop()
21
+ return await loop.run_in_executor(None, input, question)
22
+
23
+
24
+ async def main():
25
+ # Set up OpenAI client for local server (e.g., Ollama)
26
+ openai_client = AsyncOpenAI(
27
+ api_key="local",
28
+ base_url="http://localhost:11434/v1",
29
+ )
30
+
31
+ # Get current working directory
32
+ samples_dir = str(Path.cwd())
33
+
34
+ # Create MCP server for filesystem operations
35
+ mcp_server = MCPServerStdio(
36
+ name="Filesystem MCP Server, via npx",
37
+ params={
38
+ "command": "npx",
39
+ "args": [
40
+ "-y",
41
+ "@modelcontextprotocol/server-filesystem",
42
+ samples_dir,
43
+ ],
44
+ },
45
+ )
46
+
47
+ # Connect to MCP server
48
+ await mcp_server.connect()
49
+
50
+ # Configure agents SDK
51
+ set_tracing_disabled(True)
52
+ set_default_openai_client(openai_client)
53
+ set_default_openai_api("chat_completions")
54
+
55
+ # Define weather tool
56
+ @function_tool
57
+ async def get_weather(location: str) -> str:
58
+ return f"The weather in {location} is sunny."
59
+
60
+ # Create agent
61
+ agent = Agent(
62
+ name="My Agent",
63
+ instructions="You are a helpful assistant.",
64
+ tools=[get_weather],
65
+ model="gpt-oss:20b-test",
66
+ mcp_servers=[mcp_server],
67
+ )
68
+
69
+ # Get user input
70
+ user_input = await prompt_user("> ")
71
+
72
+ # Run agent with streaming
73
+ result = Runner.run_streamed(agent, user_input)
74
+
75
+ # Process streaming results
76
+ async for event in result.stream_events():
77
+ if event.type == "raw_response_event":
78
+ continue
79
+ elif event.type == "agent_updated_stream_event":
80
+ print(f"Agent updated: {event.new_agent.name}")
81
+ elif event.type == "run_item_stream_event":
82
+ if event.item.type == "tool_call_item":
83
+ print("-- Tool was called")
84
+ elif event.item.type == "tool_call_output_item":
85
+ print(f"-- Tool output: {event.item.output}")
86
+ elif event.item.type == "message_output_item":
87
+ print(
88
+ f"-- Message output:\n {ItemHelpers.text_message_output(event.item)}"
89
+ )
90
+ else:
91
+ pass
92
+
93
+ print("=== Run complete ===")
94
+
95
+
96
+ if __name__ == "__main__":
97
+
98
+ if not shutil.which("npx"):
99
+ raise RuntimeError(
100
+ "npx is not installed. Please install it with `npm install -g npx`."
101
+ )
102
+ asyncio.run(main())
examples/agents-sdk-python/pyproject.toml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "agents-sdk-python"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "openai-agents>=0.2.4",
9
+ ]
examples/gradio/gradio_chat.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import requests
3
+ import gradio as gr
4
+
5
+ DEFAULT_FUNCTION_PROPERTIES = """
6
+ {
7
+ "type": "object",
8
+ "properties": {
9
+ "location": {
10
+ "type": "string",
11
+ "description": "The city and state, e.g. San Francisco, CA"
12
+ }
13
+ },
14
+ "required": ["location"]
15
+ }
16
+ """.strip()
17
+
18
+ def chat_with_model(message, history, model_choice, instructions, effort, use_functions,
19
+ function_name, function_description, function_parameters,
20
+ use_browser_search, temperature, max_output_tokens, debug_mode):
21
+
22
+ if not message.strip():
23
+ return history, ""
24
+
25
+ # Append user message and empty assistant placeholder (idiomatic Gradio pattern)
26
+ history = history + [[message, ""]]
27
+
28
+ # Build messages list from history (excluding the empty assistant placeholder)
29
+ messages = []
30
+
31
+ # Convert history to messages format (excluding the last empty assistant message)
32
+ for user_msg, assistant_msg in history[:-1]:
33
+ if user_msg:
34
+ messages.append({
35
+ "type": "message",
36
+ "role": "user",
37
+ "content": [{"type": "input_text", "text": user_msg}]
38
+ })
39
+ if assistant_msg:
40
+ messages.append({
41
+ "type": "message",
42
+ "role": "assistant",
43
+ "content": [{"type": "output_text", "text": assistant_msg}]
44
+ })
45
+
46
+ # Add current user message
47
+ messages.append({
48
+ "type": "message",
49
+ "role": "user",
50
+ "content": [{"type": "input_text", "text": message}]
51
+ })
52
+
53
+ # Prepare tools
54
+ tools = []
55
+ if use_functions:
56
+ try:
57
+ tools.append({
58
+ "type": "function",
59
+ "name": function_name,
60
+ "description": function_description,
61
+ "parameters": json.loads(function_parameters),
62
+ })
63
+ except json.JSONDecodeError:
64
+ pass
65
+
66
+ if use_browser_search:
67
+ tools.append({"type": "browser_search"})
68
+
69
+ # Get URL based on model (matching streamlit logic)
70
+ options = ["large", "small"]
71
+ URL = ("http://localhost:8081/v1/responses" if model_choice == options[1]
72
+ else "http://localhost:8000/v1/responses")
73
+
74
+ try:
75
+ response = requests.post(
76
+ URL,
77
+ json={
78
+ "input": messages,
79
+ "stream": True,
80
+ "instructions": instructions,
81
+ "reasoning": {"effort": effort},
82
+ "metadata": {"__debug": debug_mode},
83
+ "tools": tools,
84
+ "temperature": temperature,
85
+ "max_output_tokens": max_output_tokens,
86
+ },
87
+ stream=True,
88
+ )
89
+
90
+ full_content = ""
91
+ text_delta = ""
92
+ current_output_index = 0
93
+ in_reasoning = False
94
+
95
+ for line in response.iter_lines(decode_unicode=True):
96
+ if not line or not line.startswith("data:"):
97
+ continue
98
+ data_str = line[len("data:"):].strip()
99
+ if not data_str:
100
+ continue
101
+
102
+ try:
103
+ data = json.loads(data_str)
104
+ except Exception:
105
+ continue
106
+
107
+ event_type = data.get("type", "")
108
+ output_index = data.get("output_index", 0)
109
+
110
+ if event_type == "response.output_item.added":
111
+ current_output_index = output_index
112
+ output_type = data.get("item", {}).get("type", "message")
113
+ text_delta = ""
114
+
115
+ if output_type == "reasoning":
116
+ if not in_reasoning:
117
+ full_content += "🤔 **Thinking...**\n"
118
+ in_reasoning = True
119
+ elif output_type == "message":
120
+ if in_reasoning:
121
+ full_content += "\n\n"
122
+ in_reasoning = False
123
+
124
+ elif event_type == "response.reasoning_text.delta":
125
+ delta = data.get("delta", "")
126
+ full_content += delta
127
+
128
+ # Update last assistant message (idiomatic Gradio pattern)
129
+ history[-1][1] = full_content
130
+ yield history, ""
131
+
132
+ elif event_type == "response.output_text.delta":
133
+ delta = data.get("delta", "")
134
+ full_content += delta
135
+
136
+ # Update last assistant message (idiomatic Gradio pattern)
137
+ history[-1][1] = full_content
138
+ yield history, ""
139
+
140
+ elif event_type == "response.output_item.done":
141
+ item = data.get("item", {})
142
+ if item.get("type") == "function_call":
143
+ function_call_text = f"\n\n🔨 Called `{item.get('name')}`\n**Arguments**\n```json\n{item.get('arguments', '')}\n```"
144
+ full_content += function_call_text
145
+
146
+ # Update last assistant message (idiomatic Gradio pattern)
147
+ history[-1][1] = full_content
148
+ yield history, ""
149
+
150
+ elif item.get("type") == "web_search_call":
151
+ web_search_text = f"\n\n🌐 **Web Search**\n```json\n{json.dumps(item.get('action', {}), indent=2)}\n```\n✅ Done"
152
+ full_content += web_search_text
153
+
154
+ # Update last assistant message (idiomatic Gradio pattern)
155
+ history[-1][1] = full_content
156
+ yield history, ""
157
+
158
+ elif event_type == "response.completed":
159
+ response_data = data.get("response", {})
160
+ if debug_mode:
161
+ debug_info = response_data.get("metadata", {}).get("__debug", "")
162
+ if debug_info:
163
+ full_content += f"\n\n**Debug**\n```\n{debug_info}\n```"
164
+
165
+ # Update last assistant message (idiomatic Gradio pattern)
166
+ history[-1][1] = full_content
167
+ yield history, ""
168
+ break
169
+
170
+ # Return final history and empty string to clear textbox
171
+ return history, ""
172
+
173
+ except Exception as e:
174
+ error_message = f"❌ Error: {str(e)}"
175
+ history[-1][1] = error_message
176
+ return history, ""
177
+
178
+
179
+ # Create the Gradio interface
180
+ with gr.Blocks(title="💬 Chatbot") as demo:
181
+ gr.Markdown("# 💬 Chatbot")
182
+
183
+ with gr.Row():
184
+ with gr.Column(scale=3):
185
+ chatbot = gr.Chatbot(height=500)
186
+
187
+ with gr.Row():
188
+ msg = gr.Textbox(placeholder="Type a message...", scale=4, show_label=False)
189
+ send_btn = gr.Button("Send", scale=1)
190
+
191
+ clear_btn = gr.Button("Clear Chat")
192
+
193
+ with gr.Column(scale=1):
194
+ model_choice = gr.Radio(["large", "small"], value="small", label="Model")
195
+
196
+ instructions = gr.Textbox(
197
+ label="Instructions",
198
+ value="You are a helpful assistant that can answer questions and help with tasks.",
199
+ lines=3
200
+ )
201
+
202
+ effort = gr.Radio(["low", "medium", "high"], value="medium", label="Reasoning effort")
203
+
204
+ gr.Markdown("#### Functions")
205
+ use_functions = gr.Checkbox(label="Use functions", value=False)
206
+
207
+ with gr.Column(visible=False) as function_group:
208
+ function_name = gr.Textbox(label="Function name", value="get_weather")
209
+ function_description = gr.Textbox(
210
+ label="Function description",
211
+ value="Get the weather for a given city"
212
+ )
213
+ function_parameters = gr.Textbox(
214
+ label="Function parameters",
215
+ value=DEFAULT_FUNCTION_PROPERTIES,
216
+ lines=6
217
+ )
218
+
219
+ # Conditional browser search (matching Streamlit logic)
220
+ # In Streamlit: if "show_browser" in st.query_params:
221
+ # For Gradio, we'll always show it (simplified)
222
+ gr.Markdown("#### Built-in Tools")
223
+ use_browser_search = gr.Checkbox(label="Use browser search", value=False)
224
+
225
+ temperature = gr.Slider(0.0, 1.0, value=1.0, step=0.01, label="Temperature")
226
+ max_output_tokens = gr.Slider(1000, 20000, value=1024, step=100, label="Max output tokens")
227
+
228
+ debug_mode = gr.Checkbox(label="Debug mode", value=False)
229
+
230
+ # Event handlers
231
+ def toggle_function_group(use_funcs):
232
+ return gr.update(visible=use_funcs)
233
+
234
+ use_functions.change(toggle_function_group, use_functions, function_group)
235
+
236
+ # Chat functionality
237
+ inputs = [msg, chatbot, model_choice, instructions, effort, use_functions,
238
+ function_name, function_description, function_parameters,
239
+ use_browser_search, temperature, max_output_tokens, debug_mode]
240
+
241
+ msg.submit(chat_with_model, inputs, [chatbot, msg])
242
+ send_btn.click(chat_with_model, inputs, [chatbot, msg])
243
+ clear_btn.click(lambda: [], outputs=chatbot)
244
+
245
+
246
+ if __name__ == "__main__":
247
+ demo.launch()
examples/streamlit/streamlit_chat.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ import requests
4
+ import streamlit as st
5
+
6
+ DEFAULT_FUNCTION_PROPERTIES = """
7
+ {
8
+ "type": "object",
9
+ "properties": {
10
+ "location": {
11
+ "type": "string",
12
+ "description": "The city and state, e.g. San Francisco, CA"
13
+ }
14
+ },
15
+ "required": ["location"]
16
+ }
17
+ """.strip()
18
+
19
+ # Session state for chat
20
+ if "messages" not in st.session_state:
21
+ st.session_state.messages = []
22
+
23
+ st.title("💬 Chatbot")
24
+
25
+ if "model" not in st.session_state:
26
+ if "model" in st.query_params:
27
+ st.session_state.model = st.query_params["model"]
28
+ else:
29
+ st.session_state.model = "small"
30
+
31
+ options = ["large", "small"]
32
+ selection = st.sidebar.segmented_control(
33
+ "Model", options, selection_mode="single", default=st.session_state.model
34
+ )
35
+ # st.session_state.model = selection
36
+ st.query_params.update({"model": selection})
37
+
38
+ instructions = st.sidebar.text_area(
39
+ "Instructions",
40
+ value="You are a helpful assistant that can answer questions and help with tasks.",
41
+ )
42
+ effort = st.sidebar.radio(
43
+ "Reasoning effort",
44
+ ["low", "medium", "high"],
45
+ index=1,
46
+ )
47
+ st.sidebar.divider()
48
+ st.sidebar.subheader("Functions")
49
+ use_functions = st.sidebar.toggle("Use functions", value=False)
50
+
51
+ st.sidebar.subheader("Built-in Tools")
52
+ # Built-in Tools section
53
+ use_browser_search = st.sidebar.toggle("Use browser search", value=False)
54
+ use_code_interpreter = st.sidebar.toggle("Use code interpreter", value=False)
55
+
56
+ if use_functions:
57
+ function_name = st.sidebar.text_input("Function name", value="get_weather")
58
+ function_description = st.sidebar.text_area(
59
+ "Function description", value="Get the weather for a given city"
60
+ )
61
+ function_parameters = st.sidebar.text_area(
62
+ "Function parameters", value=DEFAULT_FUNCTION_PROPERTIES
63
+ )
64
+ else:
65
+ function_name = None
66
+ function_description = None
67
+ function_parameters = None
68
+ st.sidebar.divider()
69
+ temperature = st.sidebar.slider(
70
+ "Temperature", min_value=0.0, max_value=1.0, value=1.0, step=0.01
71
+ )
72
+ max_output_tokens = st.sidebar.slider(
73
+ "Max output tokens", min_value=1, max_value=131072, value=30000, step=1000
74
+ )
75
+ st.sidebar.divider()
76
+ debug_mode = st.sidebar.toggle("Debug mode", value=False)
77
+
78
+ if debug_mode:
79
+ st.sidebar.divider()
80
+ st.sidebar.code(json.dumps(st.session_state.messages, indent=2), "json")
81
+
82
+ render_input = True
83
+
84
+ URL = (
85
+ "http://localhost:8081/v1/responses"
86
+ if selection == options[1]
87
+ else "http://localhost:8000/v1/responses"
88
+ )
89
+
90
+
91
+ def trigger_fake_tool(container):
92
+ function_output = st.session_state.get("function_output", "It's sunny!")
93
+ last_call = st.session_state.messages[-1]
94
+ if last_call.get("type") == "function_call":
95
+ st.session_state.messages.append(
96
+ {
97
+ "type": "function_call_output",
98
+ "call_id": last_call.get("call_id"),
99
+ "output": function_output,
100
+ }
101
+ )
102
+ run(container)
103
+
104
+
105
+ def run(container):
106
+ tools = []
107
+ if use_functions:
108
+ tools.append(
109
+ {
110
+ "type": "function",
111
+ "name": function_name,
112
+ "description": function_description,
113
+ "parameters": json.loads(function_parameters),
114
+ }
115
+ )
116
+ # Add browser_search tool if checkbox is checked
117
+ if use_browser_search:
118
+ tools.append({"type": "browser_search"})
119
+ if use_code_interpreter:
120
+ tools.append({"type": "code_interpreter"})
121
+ response = requests.post(
122
+ URL,
123
+ json={
124
+ "input": st.session_state.messages,
125
+ "stream": True,
126
+ "instructions": instructions,
127
+ "reasoning": {"effort": effort},
128
+ "metadata": {"__debug": debug_mode},
129
+ "tools": tools,
130
+ "temperature": temperature,
131
+ "max_output_tokens": max_output_tokens,
132
+ },
133
+ stream=True,
134
+ )
135
+
136
+ text_delta = ""
137
+ code_interpreter_sessions: dict[str, dict] = {}
138
+
139
+ _current_output_index = 0
140
+ for line in response.iter_lines(decode_unicode=True):
141
+ if not line or not line.startswith("data:"):
142
+ continue
143
+ data_str = line[len("data:") :].strip()
144
+ if not data_str:
145
+ continue
146
+ try:
147
+ data = json.loads(data_str)
148
+ except Exception:
149
+ continue
150
+
151
+ event_type = data.get("type", "")
152
+ output_index = data.get("output_index", 0)
153
+ if event_type == "response.output_item.added":
154
+ _current_output_index = output_index
155
+ output_type = data.get("item", {}).get("type", "message")
156
+ if output_type == "message":
157
+ output = container.chat_message("assistant")
158
+ placeholder = output.empty()
159
+ elif output_type == "reasoning":
160
+ output = container.chat_message("reasoning", avatar="🤔")
161
+ placeholder = output.empty()
162
+ elif output_type == "web_search_call":
163
+ output = container.chat_message("web_search_call", avatar="🌐")
164
+ output.code(
165
+ json.dumps(data.get("item", {}).get("action", {}), indent=4),
166
+ language="json",
167
+ )
168
+ placeholder = output.empty()
169
+ elif output_type == "code_interpreter_call":
170
+ item = data.get("item", {})
171
+ item_id = item.get("id")
172
+ message_container = container.chat_message(
173
+ "code_interpreter_call", avatar="🧪"
174
+ )
175
+ status_placeholder = message_container.empty()
176
+ code_placeholder = message_container.empty()
177
+ outputs_container = message_container.container()
178
+ code_text = item.get("code") or ""
179
+ if code_text:
180
+ code_placeholder.code(code_text, language="python")
181
+ code_interpreter_sessions[item_id] = {
182
+ "status": status_placeholder,
183
+ "code": code_placeholder,
184
+ "outputs": outputs_container,
185
+ "code_text": code_text,
186
+ "rendered_outputs": False,
187
+ }
188
+ placeholder = status_placeholder
189
+ text_delta = ""
190
+ elif event_type == "response.reasoning_text.delta":
191
+ output.avatar = "🤔"
192
+ text_delta += data.get("delta", "")
193
+ placeholder.markdown(text_delta)
194
+ elif event_type == "response.output_text.delta":
195
+ text_delta += data.get("delta", "")
196
+ placeholder.markdown(text_delta)
197
+ elif event_type == "response.output_item.done":
198
+ item = data.get("item", {})
199
+ if item.get("type") == "function_call":
200
+ with container.chat_message("function_call", avatar="🔨"):
201
+ st.markdown(f"Called `{item.get('name')}`")
202
+ st.caption("Arguments")
203
+ st.code(item.get("arguments", ""), language="json")
204
+ if item.get("type") == "web_search_call":
205
+ placeholder.markdown("✅ Done")
206
+ if item.get("type") == "code_interpreter_call":
207
+ item_id = item.get("id")
208
+ session = code_interpreter_sessions.get(item_id)
209
+ if session:
210
+ session["status"].markdown("✅ Done")
211
+ final_code = item.get("code") or session["code_text"]
212
+ if final_code:
213
+ session["code"].code(final_code, language="python")
214
+ session["code_text"] = final_code
215
+ outputs = item.get("outputs") or []
216
+ if outputs and not session["rendered_outputs"]:
217
+ with session["outputs"]:
218
+ st.markdown("**Outputs**")
219
+ for output_item in outputs:
220
+ output_type = output_item.get("type")
221
+ if output_type == "logs":
222
+ st.code(
223
+ output_item.get("logs", ""),
224
+ language="text",
225
+ )
226
+ elif output_type == "image":
227
+ st.image(
228
+ output_item.get("url", ""),
229
+ caption="Code interpreter image",
230
+ )
231
+ session["rendered_outputs"] = True
232
+ elif not outputs and not session["rendered_outputs"]:
233
+ with session["outputs"]:
234
+ st.caption("(No outputs)")
235
+ session["rendered_outputs"] = True
236
+ else:
237
+ placeholder.markdown("✅ Done")
238
+ elif event_type == "response.code_interpreter_call.in_progress":
239
+ item_id = data.get("item_id")
240
+ session = code_interpreter_sessions.get(item_id)
241
+ if session:
242
+ session["status"].markdown("⏳ Running")
243
+ else:
244
+ try:
245
+ placeholder.markdown("⏳ Running")
246
+ except Exception:
247
+ pass
248
+ elif event_type == "response.code_interpreter_call.interpreting":
249
+ item_id = data.get("item_id")
250
+ session = code_interpreter_sessions.get(item_id)
251
+ if session:
252
+ session["status"].markdown("🧮 Interpreting")
253
+ elif event_type == "response.code_interpreter_call.completed":
254
+ item_id = data.get("item_id")
255
+ session = code_interpreter_sessions.get(item_id)
256
+ if session:
257
+ session["status"].markdown("✅ Done")
258
+ else:
259
+ try:
260
+ placeholder.markdown("✅ Done")
261
+ except Exception:
262
+ pass
263
+ elif event_type == "response.code_interpreter_call_code.delta":
264
+ item_id = data.get("item_id")
265
+ session = code_interpreter_sessions.get(item_id)
266
+ if session:
267
+ session["code_text"] += data.get("delta", "")
268
+ if session["code_text"].strip():
269
+ session["code"].code(session["code_text"], language="python")
270
+ elif event_type == "response.code_interpreter_call_code.done":
271
+ item_id = data.get("item_id")
272
+ session = code_interpreter_sessions.get(item_id)
273
+ if session:
274
+ final_code = data.get("code") or session["code_text"]
275
+ session["code_text"] = final_code
276
+ if final_code:
277
+ session["code"].code(final_code, language="python")
278
+ elif event_type == "response.completed":
279
+ response = data.get("response", {})
280
+ if debug_mode:
281
+ container.expander("Debug", expanded=False).code(
282
+ response.get("metadata", {}).get("__debug", ""), language="text"
283
+ )
284
+ st.session_state.messages.extend(response.get("output", []))
285
+ if st.session_state.messages[-1].get("type") == "function_call":
286
+ with container.form("function_output_form"):
287
+ _function_output = st.text_input(
288
+ "Enter function output",
289
+ value=st.session_state.get("function_output", "It's sunny!"),
290
+ key="function_output",
291
+ )
292
+ st.form_submit_button(
293
+ "Submit function output",
294
+ on_click=trigger_fake_tool,
295
+ args=[container],
296
+ )
297
+ # Optionally handle other event types...
298
+
299
+
300
+ # Chat display
301
+ for msg in st.session_state.messages:
302
+ if msg.get("type") == "message":
303
+ with st.chat_message(msg["role"]):
304
+ for item in msg["content"]:
305
+ if (
306
+ item.get("type") == "text"
307
+ or item.get("type") == "output_text"
308
+ or item.get("type") == "input_text"
309
+ ):
310
+ st.markdown(item["text"])
311
+ if item.get("annotations"):
312
+ annotation_lines = "\n".join(
313
+ f"- {annotation.get('url')}"
314
+ for annotation in item["annotations"]
315
+ if annotation.get("url")
316
+ )
317
+ st.caption(f"**Annotations:**\n{annotation_lines}")
318
+ elif msg.get("type") == "reasoning":
319
+ with st.chat_message("reasoning", avatar="🤔"):
320
+ for item in msg["content"]:
321
+ if item.get("type") == "reasoning_text":
322
+ st.markdown(item["text"])
323
+ elif msg.get("type") == "function_call":
324
+ with st.chat_message("function_call", avatar="🔨"):
325
+ st.markdown(f"Called `{msg.get('name')}`")
326
+ st.caption("Arguments")
327
+ st.code(msg.get("arguments", ""), language="json")
328
+ elif msg.get("type") == "function_call_output":
329
+ with st.chat_message("function_call_output", avatar="✅"):
330
+ st.caption("Output")
331
+ st.code(msg.get("output", ""), language="text")
332
+ elif msg.get("type") == "web_search_call":
333
+ with st.chat_message("web_search_call", avatar="🌐"):
334
+ st.code(json.dumps(msg.get("action", {}), indent=4), language="json")
335
+ st.markdown("✅ Done")
336
+ elif msg.get("type") == "code_interpreter_call":
337
+ with st.chat_message("code_interpreter_call", avatar="🧪"):
338
+ st.markdown("✅ Done")
339
+
340
+ if render_input:
341
+ # Input field
342
+ if prompt := st.chat_input("Type a message..."):
343
+ st.session_state.messages.append(
344
+ {
345
+ "type": "message",
346
+ "role": "user",
347
+ "content": [{"type": "input_text", "text": prompt}],
348
+ }
349
+ )
350
+
351
+ with st.chat_message("user"):
352
+ st.markdown(prompt)
353
+
354
+ run(st.container())
gpt-oss-mcp-server/README.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MCP Servers for gpt-oss reference tools
2
+
3
+ This directory contains MCP servers for the reference tools in the [gpt-oss](https://github.com/openai/gpt-oss) repository.
4
+ You can set up these tools behind MCP servers and use them in your applications.
5
+ For inference service that integrates with MCP, you can also use these as reference tools.
6
+
7
+ In particular, this directory contains a `build-system-prompt.py` script that will generate exactly the same system prompt as `reference-system-prompt.py`.
8
+ The build system prompt script show case all the care needed to automatically discover the tools and construct the system prompt before feeding it into Harmony.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ # Install the dependencies
14
+ uv pip install -r requirements.txt
15
+ ```
16
+
17
+ ```bash
18
+ # Assume we have harmony and gpt-oss installed
19
+ uv pip install mcp[cli]
20
+ # start the servers
21
+ mcp run -t sse browser_server.py:mcp
22
+ mcp run -t sse python_server.py:mcp
23
+ ```
24
+
25
+ You can now use MCP inspector to play with the tools.
26
+ Once opened, set SSE to `http://localhost:8001/sse` and `http://localhost:8000/sse` respectively.
27
+
28
+ To compare the system prompt and see how to construct it via MCP service discovery, see `build-system-prompt.py`.
29
+ This script will generate exactly the same system prompt as `reference-system-prompt.py`.
gpt-oss-mcp-server/browser_server.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from collections.abc import AsyncIterator
3
+ from contextlib import asynccontextmanager
4
+ from dataclasses import dataclass, field
5
+ from typing import Union, Optional
6
+
7
+ from mcp.server.fastmcp import Context, FastMCP
8
+ from gpt_oss.tools.simple_browser import SimpleBrowserTool
9
+ from gpt_oss.tools.simple_browser.backend import YouComBackend, ExaBackend
10
+
11
+ @dataclass
12
+ class AppContext:
13
+ browsers: dict[str, SimpleBrowserTool] = field(default_factory=dict)
14
+
15
+ def create_or_get_browser(self, session_id: str) -> SimpleBrowserTool:
16
+ if session_id not in self.browsers:
17
+ tool_backend = os.getenv("BROWSER_BACKEND", "exa")
18
+ if tool_backend == "youcom":
19
+ backend = YouComBackend(source="web")
20
+ elif tool_backend == "exa":
21
+ backend = ExaBackend(source="web")
22
+ else:
23
+ raise ValueError(f"Invalid tool backend: {tool_backend}")
24
+ self.browsers[session_id] = SimpleBrowserTool(backend=backend)
25
+ return self.browsers[session_id]
26
+
27
+ def remove_browser(self, session_id: str) -> None:
28
+ self.browsers.pop(session_id, None)
29
+
30
+
31
+ @asynccontextmanager
32
+ async def app_lifespan(_server: FastMCP) -> AsyncIterator[AppContext]:
33
+ yield AppContext()
34
+
35
+
36
+ # Pass lifespan to server
37
+ mcp = FastMCP(
38
+ name="browser",
39
+ instructions=r"""
40
+ Tool for browsing.
41
+ The `cursor` appears in brackets before each browsing display: `[{cursor}]`.
42
+ Cite information from the tool using the following format:
43
+ `【{cursor}†L{line_start}(-L{line_end})?】`, for example: `【6†L9-L11】` or `【8†L3】`.
44
+ Do not quote more than 10 words directly from the tool output.
45
+ sources=web
46
+ """.strip(),
47
+ lifespan=app_lifespan,
48
+ port=8001,
49
+ )
50
+
51
+
52
+ @mcp.tool(
53
+ name="search",
54
+ title="Search for information",
55
+ description=
56
+ "Searches for information related to `query` and displays `topn` results.",
57
+ )
58
+ async def search(ctx: Context,
59
+ query: str,
60
+ topn: int = 10,
61
+ source: Optional[str] = None) -> str:
62
+ """Search for information related to a query"""
63
+ browser = ctx.request_context.lifespan_context.create_or_get_browser(
64
+ ctx.client_id)
65
+ messages = []
66
+ async for message in browser.search(query=query, topn=topn, source=source):
67
+ if message.content and hasattr(message.content[0], 'text'):
68
+ messages.append(message.content[0].text)
69
+ return "\n".join(messages)
70
+
71
+
72
+ @mcp.tool(
73
+ name="open",
74
+ title="Open a link or page",
75
+ description="""
76
+ Opens the link `id` from the page indicated by `cursor` starting at line number `loc`, showing `num_lines` lines.
77
+ Valid link ids are displayed with the formatting: `【{id}†.*】`.
78
+ If `cursor` is not provided, the most recent page is implied.
79
+ If `id` is a string, it is treated as a fully qualified URL associated with `source`.
80
+ If `loc` is not provided, the viewport will be positioned at the beginning of the document or centered on the most relevant passage, if available.
81
+ Use this function without `id` to scroll to a new location of an opened page.
82
+ """.strip(),
83
+ )
84
+ async def open_link(ctx: Context,
85
+ id: Union[int, str] = -1,
86
+ cursor: int = -1,
87
+ loc: int = -1,
88
+ num_lines: int = -1,
89
+ view_source: bool = False,
90
+ source: Optional[str] = None) -> str:
91
+ """Open a link or navigate to a page location"""
92
+ browser = ctx.request_context.lifespan_context.create_or_get_browser(
93
+ ctx.client_id)
94
+ messages = []
95
+ async for message in browser.open(id=id,
96
+ cursor=cursor,
97
+ loc=loc,
98
+ num_lines=num_lines,
99
+ view_source=view_source,
100
+ source=source):
101
+ if message.content and hasattr(message.content[0], 'text'):
102
+ messages.append(message.content[0].text)
103
+ return "\n".join(messages)
104
+
105
+
106
+ @mcp.tool(
107
+ name="find",
108
+ title="Find pattern in page",
109
+ description=
110
+ "Finds exact matches of `pattern` in the current page, or the page given by `cursor`.",
111
+ )
112
+ async def find_pattern(ctx: Context, pattern: str, cursor: int = -1) -> str:
113
+ """Find exact matches of a pattern in the current page"""
114
+ browser = ctx.request_context.lifespan_context.create_or_get_browser(
115
+ ctx.client_id)
116
+ messages = []
117
+ async for message in browser.find(pattern=pattern, cursor=cursor):
118
+ if message.content and hasattr(message.content[0], 'text'):
119
+ messages.append(message.content[0].text)
120
+ return "\n".join(messages)
gpt-oss-mcp-server/build-system-prompt.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import asyncio
3
+
4
+ from gpt_oss.tokenizer import get_tokenizer
5
+
6
+ from openai_harmony import (
7
+ Conversation,
8
+ DeveloperContent,
9
+ HarmonyEncodingName,
10
+ Message,
11
+ ReasoningEffort,
12
+ Role,
13
+ SystemContent,
14
+ ToolNamespaceConfig,
15
+ ToolDescription,
16
+ load_harmony_encoding,
17
+ )
18
+
19
+ from mcp import ClientSession
20
+ from mcp.client.sse import sse_client
21
+ from mcp.types import ListToolsResult
22
+
23
+
24
+ async def list_server_and_tools(server_url: str):
25
+ async with sse_client(url=server_url) as streams, ClientSession(
26
+ *streams) as session:
27
+ initialize_response = await session.initialize()
28
+ list_tools_response = await session.list_tools()
29
+ return initialize_response, list_tools_response
30
+
31
+
32
+ def trim_schema(schema: dict) -> dict:
33
+ # Turn JSON Schema from MCP generated into Harmony's variant.
34
+ if "title" in schema:
35
+ del schema["title"]
36
+ if "default" in schema and schema["default"] is None:
37
+ del schema["default"]
38
+ if "anyOf" in schema:
39
+ # Turn "anyOf": [{"type": "type-1"}, {"type": "type-2"}] into "type": ["type-1", "type-2"]
40
+ # if there's more than 1 types, also remove "null" type as Harmony will just ignore it
41
+ types = [
42
+ type_dict["type"] for type_dict in schema["anyOf"]
43
+ if type_dict["type"] != 'null'
44
+ ]
45
+ schema["type"] = types
46
+ del schema["anyOf"]
47
+ if "properties" in schema:
48
+ schema["properties"] = {
49
+ k: trim_schema(v)
50
+ for k, v in schema["properties"].items()
51
+ }
52
+ return schema
53
+
54
+
55
+ def post_process_tools_description(
56
+ list_tools_result: ListToolsResult) -> ListToolsResult:
57
+ # Adapt the MCP tool result for Harmony
58
+ for tool in list_tools_result.tools:
59
+ tool.inputSchema = trim_schema(tool.inputSchema)
60
+
61
+ # Some tools schema don't need to be part of the prompt (e.g. simple text in text out for Python)
62
+ list_tools_result.tools = [
63
+ tool for tool in list_tools_result.tools
64
+ if getattr(tool.annotations, "include_in_prompt", True)
65
+ ]
66
+
67
+ return list_tools_result
68
+
69
+ tokenizer = get_tokenizer()
70
+
71
+ tools_urls = [
72
+ "http://localhost:8001/sse", # browser
73
+ "http://localhost:8000/sse", # python
74
+ ]
75
+ harmony_tool_descriptions = []
76
+ for tools_url in tools_urls:
77
+
78
+ initialize_response, list_tools_response = asyncio.run(
79
+ list_server_and_tools(tools_url))
80
+
81
+ list_tools_response = post_process_tools_description(list_tools_response)
82
+
83
+ tool_from_mcp = ToolNamespaceConfig(
84
+ name=initialize_response.serverInfo.name,
85
+ description=initialize_response.instructions,
86
+ tools=[
87
+ ToolDescription.new(name=tool.name,
88
+ description=tool.description,
89
+ parameters=tool.inputSchema)
90
+ for tool in list_tools_response.tools
91
+ ])
92
+ harmony_tool_descriptions.append(tool_from_mcp)
93
+
94
+ encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
95
+
96
+ system_message_content = (SystemContent.new().with_reasoning_effort(
97
+ ReasoningEffort.LOW).with_conversation_start_date(
98
+ datetime.datetime.now().strftime("%Y-%m-%d")))
99
+
100
+ for tool_description in harmony_tool_descriptions:
101
+ system_message_content = system_message_content.with_tools(
102
+ tool_description)
103
+
104
+ system_message = Message.from_role_and_content(Role.SYSTEM,
105
+ system_message_content)
106
+
107
+ developer_message_content = DeveloperContent.new().with_instructions("")
108
+ developer_message = Message.from_role_and_content(Role.DEVELOPER,
109
+ developer_message_content)
110
+
111
+ messages = [system_message, developer_message]
112
+
113
+ conversation = Conversation.from_messages(messages)
114
+ tokens = encoding.render_conversation(conversation)
115
+ system_message = tokenizer.decode(tokens)
116
+ print(system_message)
gpt-oss-mcp-server/pyproject.toml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "gpt-oss-mcp-server"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.10"
5
+ dependencies = [
6
+ "mcp[cli]>=1.12.2",
7
+ # "gpt_oss"
8
+ ]
gpt-oss-mcp-server/python_server.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mcp.server.fastmcp import FastMCP
2
+ from gpt_oss.tools.python_docker.docker_tool import PythonTool
3
+ from openai_harmony import Message, TextContent, Author, Role
4
+
5
+ # Pass lifespan to server
6
+ mcp = FastMCP(
7
+ name="python",
8
+ instructions=r"""
9
+ Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files).
10
+ When you send a message containing python code to python, it will be executed in a stateless docker container, and the stdout of that process will be returned to you.
11
+ """.strip(),
12
+ )
13
+
14
+
15
+ @mcp.tool(
16
+ name="python",
17
+ title="Execute Python code",
18
+ description="""
19
+ Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files).
20
+ When you send a message containing python code to python, it will be executed in a stateless docker container, and the stdout of that process will be returned to you.
21
+ """,
22
+ annotations={
23
+ # Harmony format don't want this schema to be part of it because it's simple text in text out
24
+ "include_in_prompt": False,
25
+ })
26
+ async def python(code: str) -> str:
27
+ tool = PythonTool()
28
+ messages = []
29
+ async for message in tool.process(
30
+ Message(author=Author(role=Role.TOOL, name="python"),
31
+ content=[TextContent(text=code)])):
32
+ messages.append(message)
33
+ return "\n".join([message.content[0].text for message in messages])
gpt-oss-mcp-server/reference-system-prompt.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+
3
+ from gpt_oss.tools.simple_browser import SimpleBrowserTool
4
+ from gpt_oss.tools.simple_browser.backend import YouComBackend
5
+ from gpt_oss.tools.python_docker.docker_tool import PythonTool
6
+ from gpt_oss.tokenizer import tokenizer
7
+
8
+ from openai_harmony import (
9
+ Conversation,
10
+ DeveloperContent,
11
+ HarmonyEncodingName,
12
+ Message,
13
+ ReasoningEffort,
14
+ Role,
15
+ SystemContent,
16
+ load_harmony_encoding,
17
+ )
18
+
19
+ encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
20
+
21
+ system_message_content = (SystemContent.new().with_reasoning_effort(
22
+ ReasoningEffort.LOW).with_conversation_start_date(
23
+ datetime.datetime.now().strftime("%Y-%m-%d")))
24
+
25
+ backend = YouComBackend(source="web")
26
+ browser_tool = SimpleBrowserTool(backend=backend)
27
+ system_message_content = system_message_content.with_tools(
28
+ browser_tool.tool_config)
29
+
30
+ python_tool = PythonTool()
31
+ system_message_content = system_message_content.with_tools(
32
+ python_tool.tool_config)
33
+
34
+ system_message = Message.from_role_and_content(Role.SYSTEM,
35
+ system_message_content)
36
+
37
+ developer_message_content = DeveloperContent.new().with_instructions("")
38
+ developer_message = Message.from_role_and_content(Role.DEVELOPER,
39
+ developer_message_content)
40
+
41
+ messages = [system_message, developer_message]
42
+
43
+ conversation = Conversation.from_messages(messages)
44
+ tokens = encoding.render_conversation(conversation)
45
+ system_message = tokenizer.decode(tokens)
46
+ print(system_message)
gpt_oss/__init__.py ADDED
File without changes
gpt_oss/chat.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Harmony chat with tools
3
+ """
4
+
5
+ import atexit
6
+ import argparse
7
+ import asyncio
8
+ import datetime
9
+ import os
10
+ from pathlib import Path
11
+
12
+ try:
13
+ import gnureadline as readline
14
+ except ImportError:
15
+ import readline
16
+
17
+ import torch
18
+ import termcolor
19
+
20
+ from gpt_oss.tools import apply_patch
21
+ from gpt_oss.tools.simple_browser import SimpleBrowserTool
22
+ from gpt_oss.tools.simple_browser.backend import YouComBackend
23
+ from gpt_oss.tools.python_docker.docker_tool import PythonTool
24
+
25
+ from openai_harmony import (
26
+ Author,
27
+ Conversation,
28
+ DeveloperContent,
29
+ HarmonyEncodingName,
30
+ Message,
31
+ ReasoningEffort,
32
+ Role,
33
+ StreamableParser,
34
+ StreamState,
35
+ SystemContent,
36
+ TextContent,
37
+ ToolDescription,
38
+ load_harmony_encoding,
39
+ )
40
+
41
+
42
+ REASONING_EFFORT = {
43
+ "high": ReasoningEffort.HIGH,
44
+ "medium": ReasoningEffort.MEDIUM,
45
+ "low": ReasoningEffort.LOW,
46
+ }
47
+
48
+
49
+ def get_user_input():
50
+ rank = torch.distributed.get_rank() if torch.distributed.is_initialized() else 0
51
+ if rank == 0:
52
+ user_input = input()
53
+ else:
54
+ user_input = ""
55
+ user_input_list = [user_input]
56
+ if torch.distributed.is_initialized():
57
+ torch.distributed.broadcast_object_list(user_input_list, 0)
58
+ return user_input_list[0]
59
+
60
+
61
+ def main(args):
62
+ match args.backend:
63
+ case "triton":
64
+ from gpt_oss.triton.model import TokenGenerator as TritonGenerator
65
+ from gpt_oss.torch.utils import init_distributed
66
+ device = init_distributed()
67
+ generator = TritonGenerator(args.checkpoint, args.context, device)
68
+ case "torch":
69
+ from gpt_oss.torch.model import TokenGenerator as TorchGenerator
70
+ from gpt_oss.torch.utils import init_distributed
71
+ device = init_distributed()
72
+ generator = TorchGenerator(args.checkpoint, device)
73
+ case "vllm":
74
+ from gpt_oss.vllm.token_generator import TokenGenerator as VLLMGenerator
75
+ generator = VLLMGenerator(args.checkpoint, tensor_parallel_size=2)
76
+ case _:
77
+ raise ValueError(f"Invalid backend: {args.backend}")
78
+
79
+ encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
80
+
81
+ system_message_content = (
82
+ SystemContent.new()
83
+ .with_reasoning_effort(REASONING_EFFORT[args.reasoning_effort])
84
+ .with_conversation_start_date(datetime.datetime.now().strftime("%Y-%m-%d"))
85
+ )
86
+
87
+ if args.browser:
88
+ backend = YouComBackend(
89
+ source="web",
90
+ )
91
+ browser_tool = SimpleBrowserTool(backend=backend)
92
+ system_message_content = system_message_content.with_tools(browser_tool.tool_config)
93
+
94
+ if args.python:
95
+ python_tool = PythonTool()
96
+ system_message_content = system_message_content.with_tools(python_tool.tool_config)
97
+
98
+ system_message = Message.from_role_and_content(Role.SYSTEM, system_message_content)
99
+ messages = [system_message]
100
+
101
+ if args.apply_patch:
102
+ apply_patch_instructions = Path(apply_patch.__file__).parent / "apply_patch.md"
103
+ developer_message = ""
104
+ if args.developer_message:
105
+ developer_message = args.developer_message + "\n"
106
+ developer_message += apply_patch_instructions.read_text()
107
+ developer_message_content = (
108
+ DeveloperContent.new()
109
+ .with_instructions(developer_message)
110
+ .with_function_tools([
111
+ ToolDescription.new(
112
+ "apply_patch",
113
+ "Patch a file",
114
+ parameters={
115
+ "type": "string",
116
+ "description": "Formatted patch code",
117
+ "default": "*** Begin Patch\n*** End Patch\n",
118
+ }
119
+ ),
120
+ ])
121
+ )
122
+ messages.append(Message.from_role_and_content(Role.DEVELOPER, developer_message_content))
123
+ elif args.developer_message:
124
+ developer_message_content = DeveloperContent.new().with_instructions(args.developer_message)
125
+ messages.append(Message.from_role_and_content(Role.DEVELOPER, developer_message_content))
126
+ else:
127
+ developer_message_content = None
128
+
129
+ if args.raw:
130
+ conversation = Conversation.from_messages(messages)
131
+ tokens = encoding.render_conversation(conversation)
132
+ system_message = encoding.decode(tokens)
133
+ print(system_message, flush=True, end="")
134
+ empty_user_message_tokens = encoding.render(Message.from_role_and_content(Role.USER, ""))
135
+ user_message_start = encoding.decode(empty_user_message_tokens[:-1])
136
+ user_message_end = encoding.decode(empty_user_message_tokens[-1:])
137
+ else:
138
+ # System message
139
+ print(termcolor.colored("System Message:", "cyan"), flush=True)
140
+ print(termcolor.colored("Model Identity:", "cyan"), system_message_content.model_identity, flush=True)
141
+ print(termcolor.colored("Reasoning Effort:", "cyan"), system_message_content.reasoning_effort, flush=True)
142
+ print(termcolor.colored("Conversation Start Date:", "cyan"), system_message_content.conversation_start_date, flush=True)
143
+ print(termcolor.colored("Knowledge Cutoff:", "cyan"), system_message_content.knowledge_cutoff, flush=True)
144
+ print(termcolor.colored("Browser Tool:", "cyan"), "Enabled" if args.browser else "Disabled", flush=True)
145
+ print(termcolor.colored("Python Tool:", "cyan"), "Enabled" if args.python else "Disabled", flush=True)
146
+ print(termcolor.colored("Apply Patch Function:", "cyan"), "Enabled" if args.apply_patch else "Disabled", flush=True)
147
+ if developer_message_content:
148
+ print(termcolor.colored("Developer Message:", "yellow"), flush=True)
149
+ print(developer_message_content.instructions, flush=True)
150
+
151
+ # Print the system message and the user message start
152
+ MESSAGE_PADDING = 12
153
+ while True:
154
+ last_message = messages[-1]
155
+ if last_message.recipient is None:
156
+ if args.raw:
157
+ print(user_message_start, end="", flush=True)
158
+ user_message = get_user_input()
159
+ print(user_message_end, flush=True, end="")
160
+ else:
161
+ print(termcolor.colored("User:".ljust(MESSAGE_PADDING), "red"), flush=True)
162
+ user_message = get_user_input()
163
+ user_message = Message.from_role_and_content(Role.USER, user_message)
164
+ messages.append(user_message)
165
+ else:
166
+ # Tool or function call
167
+ if last_message.recipient.startswith("browser."):
168
+ assert args.browser, "Browser tool is not enabled"
169
+ tool_name = "Search"
170
+ async def run_tool():
171
+ results = []
172
+ async for msg in browser_tool.process(last_message):
173
+ results.append(msg)
174
+ return results
175
+
176
+ result = asyncio.run(run_tool())
177
+ messages += result
178
+ elif last_message.recipient.startswith("python"):
179
+ assert args.python, "Python tool is not enabled"
180
+ tool_name = "Python"
181
+ async def run_tool():
182
+ results = []
183
+ async for msg in python_tool.process(last_message):
184
+ results.append(msg)
185
+ return results
186
+
187
+ result = asyncio.run(run_tool())
188
+ messages += result
189
+ elif last_message.recipient == "functions.apply_patch":
190
+ assert args.apply_patch, "Apply patch tool is not enabled"
191
+ tool_name = "Apply Patch"
192
+ text = last_message.content[0].text
193
+ tool_output = None
194
+
195
+ if text.startswith("{"):
196
+ # this is json, try to extract the patch from it
197
+ import json
198
+ try:
199
+ some_dict = json.loads(text)
200
+ _, text = some_dict.popitem()
201
+ except Exception as e:
202
+ tool_output = f"Error parsing JSON: {e}"
203
+
204
+ if tool_output is None:
205
+ try:
206
+ tool_output = apply_patch.apply_patch(text)
207
+ except Exception as e:
208
+ tool_output = f"Error applying patch: {e}"
209
+
210
+ message = (
211
+ Message(
212
+ author=Author.new(Role.TOOL, last_message.recipient),
213
+ content=[TextContent(text=tool_output)]
214
+ )
215
+ .with_recipient("assistant")
216
+ )
217
+ if last_message.channel:
218
+ message = message.with_channel(last_message.channel)
219
+
220
+ result = [message]
221
+ messages += result
222
+ else:
223
+ raise ValueError(f"Unknown tool or function call: {last_message.recipient}")
224
+ # Print the tool or function call result
225
+ if args.raw:
226
+ rendered_result = encoding.render_conversation(Conversation.from_messages(result))
227
+ print(encoding.decode(rendered_result), flush=True, end="")
228
+ else:
229
+ print(termcolor.colored(f"{tool_name} output:".ljust(MESSAGE_PADDING), "magenta"), flush=True)
230
+ if tool_name == "Search" and not args.show_browser_results:
231
+ print("[Search results fed to the model]")
232
+ else:
233
+ print(result[0].content[0].text)
234
+
235
+ conversation = Conversation.from_messages(messages)
236
+ tokens = encoding.render_conversation_for_completion(
237
+ conversation, Role.ASSISTANT
238
+ )
239
+
240
+ if args.raw:
241
+ # Print the last two tokens, which are the start of the assistant message
242
+ print(encoding.decode(tokens[-2:]), flush=True, end="")
243
+
244
+ parser = StreamableParser(encoding, role=Role.ASSISTANT)
245
+ field_created = False
246
+ current_output_text = ""
247
+ output_text_delta_buffer = ""
248
+ for predicted_token in generator.generate(tokens, encoding.stop_tokens_for_assistant_actions()):
249
+ parser.process(predicted_token)
250
+ if args.raw:
251
+ print(encoding.decode([predicted_token]), end="", flush=True)
252
+ continue
253
+
254
+ if parser.state == StreamState.EXPECT_START:
255
+ print("") # new line
256
+ field_created = False
257
+
258
+ if not parser.last_content_delta:
259
+ continue
260
+
261
+ if not field_created:
262
+ field_created = True
263
+ if parser.current_channel == "final":
264
+ print(termcolor.colored("Assistant:", "green"), flush=True)
265
+ elif parser.current_recipient is not None:
266
+ print(termcolor.colored(f"Tool call to {parser.current_recipient}:", "cyan"), flush=True)
267
+ else:
268
+ print(termcolor.colored("CoT:", "yellow"), flush=True)
269
+
270
+ should_send_output_text_delta = True
271
+ output_text_delta_buffer += parser.last_content_delta
272
+ if args.browser:
273
+ updated_output_text, _annotations, has_partial_citations = browser_tool.normalize_citations(current_output_text + output_text_delta_buffer)
274
+ output_text_delta_buffer = updated_output_text[len(current_output_text):]
275
+ if has_partial_citations:
276
+ should_send_output_text_delta = False
277
+ if should_send_output_text_delta:
278
+ print(output_text_delta_buffer, end="", flush=True)
279
+ current_output_text += output_text_delta_buffer
280
+ output_text_delta_buffer = ""
281
+
282
+ messages += parser.messages
283
+
284
+
285
+ if __name__ == "__main__":
286
+ parser = argparse.ArgumentParser(
287
+ description="Chat example",
288
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
289
+ )
290
+ parser.add_argument(
291
+ "checkpoint",
292
+ metavar="FILE",
293
+ type=str,
294
+ help="Path to the SafeTensors checkpoint",
295
+ )
296
+ parser.add_argument(
297
+ "-r",
298
+ "--reasoning-effort",
299
+ metavar="REASONING_EFFORT",
300
+ type=str,
301
+ default="low",
302
+ choices=["high", "medium", "low"],
303
+ help="Reasoning effort",
304
+ )
305
+ parser.add_argument(
306
+ "-a",
307
+ "--apply-patch",
308
+ action="store_true",
309
+ help="Make apply_patch function available to the model",
310
+ )
311
+ parser.add_argument(
312
+ "-b",
313
+ "--browser",
314
+ default=False,
315
+ action="store_true",
316
+ help="Use browser tool",
317
+ )
318
+ parser.add_argument(
319
+ "--show-browser-results",
320
+ default=False,
321
+ action="store_true",
322
+ help="Show browser results",
323
+ )
324
+ parser.add_argument(
325
+ "-p",
326
+ "--python",
327
+ default=False,
328
+ action="store_true",
329
+ help="Use python tool",
330
+ )
331
+ parser.add_argument(
332
+ "--developer-message",
333
+ default="",
334
+ help="Developer message",
335
+ )
336
+ parser.add_argument(
337
+ "-c",
338
+ "--context",
339
+ metavar="CONTEXT",
340
+ type=int,
341
+ default=8192,
342
+ help="Max context length",
343
+ )
344
+ parser.add_argument(
345
+ "--raw",
346
+ default=False,
347
+ action="store_true",
348
+ help="Raw mode (does not render Harmony encoding)",
349
+ )
350
+ parser.add_argument(
351
+ "--backend",
352
+ type=str,
353
+ default="triton",
354
+ choices=["triton", "torch", "vllm"],
355
+ help="Inference backend",
356
+ )
357
+ args = parser.parse_args()
358
+
359
+ if int(os.environ.get("WORLD_SIZE", 1)) == 1:
360
+ histfile = os.path.join(os.path.expanduser("~"), ".chat")
361
+ try:
362
+ readline.read_history_file(histfile)
363
+ readline.set_history_length(10000)
364
+ except FileNotFoundError:
365
+ pass
366
+
367
+ atexit.register(readline.write_history_file, histfile)
368
+
369
+ main(args)
gpt_oss/evals/README.md ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # `gpt_oss.evals`
2
+
3
+ This module is a reincarnation of [simple-evals](https://github.com/openai/simple-evals) adapted for gpt-oss. It lets you
4
+ run GPQA and HealthBench against a runtime that supports Responses API on `localhost:8080/v1`.
gpt_oss/evals/__init__.py ADDED
File without changes
gpt_oss/evals/__main__.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ from datetime import datetime
4
+
5
+ from . import report
6
+ from .basic_eval import BasicEval
7
+ from .gpqa_eval import GPQAEval
8
+ from .aime_eval import AIME25Eval
9
+ from .healthbench_eval import HealthBenchEval
10
+ from .chat_completions_sampler import (
11
+ OPENAI_SYSTEM_MESSAGE_API,
12
+ ChatCompletionsSampler,
13
+ )
14
+ from .responses_sampler import ResponsesSampler
15
+
16
+
17
+ def main():
18
+ parser = argparse.ArgumentParser(
19
+ description="Evaluate the models.",
20
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
21
+ )
22
+ parser.add_argument(
23
+ "--model",
24
+ type=str,
25
+ default="gpt-oss-120b,gpt-oss-20b",
26
+ help="Select a model by name. Accepts a comma-separated list.",
27
+ )
28
+ parser.add_argument(
29
+ "--reasoning-effort",
30
+ type=str,
31
+ default="low,medium,high",
32
+ help="Reasoning effort (low, medium, high). Accepts a comma-separated list.",
33
+ )
34
+ parser.add_argument(
35
+ "--sampler",
36
+ type=str,
37
+ choices=["responses", "chat_completions"],
38
+ default="responses",
39
+ help="Sampler backend to use for models.",
40
+ )
41
+ parser.add_argument(
42
+ "--base-url",
43
+ type=str,
44
+ default="http://localhost:8000/v1",
45
+ help="Base URL for the API.",
46
+ )
47
+ parser.add_argument(
48
+ "--eval",
49
+ type=str,
50
+ default="gpqa,healthbench,healthbench_hard,healthbench_consensus,aime25",
51
+ help="Select an eval by name. Accepts a comma-separated list.",
52
+ )
53
+ parser.add_argument(
54
+ "--temperature",
55
+ type=float,
56
+ default=1.0,
57
+ help="Sampling temperature",
58
+ )
59
+ parser.add_argument(
60
+ "--n-threads",
61
+ type=int,
62
+ default=1584,
63
+ help="Number of threads to run.",
64
+ )
65
+ parser.add_argument(
66
+ "--debug", action="store_true", help="Run in debug mode"
67
+ )
68
+ parser.add_argument(
69
+ "--examples", type=int, help="Number of examples to use (overrides default)"
70
+ )
71
+
72
+ args = parser.parse_args()
73
+
74
+ sampler_cls = ResponsesSampler if args.sampler == "responses" else ChatCompletionsSampler
75
+
76
+ models = {}
77
+ for model_name in args.model.split(","):
78
+ for reasoning_effort in args.reasoning_effort.split(","):
79
+ models[f"{model_name}-{reasoning_effort}"] = sampler_cls(
80
+ model=model_name,
81
+ reasoning_model=True,
82
+ reasoning_effort=reasoning_effort,
83
+ temperature=args.temperature,
84
+ base_url=args.base_url,
85
+ max_tokens=131_072,
86
+ )
87
+
88
+ print(f"Running with args {args}")
89
+
90
+ grading_sampler = ChatCompletionsSampler(
91
+ model="gpt-4.1-2025-04-14",
92
+ system_message=OPENAI_SYSTEM_MESSAGE_API,
93
+ max_tokens=2048,
94
+ base_url="https://api.openai.com/v1",
95
+ )
96
+
97
+ def get_evals(eval_name, debug_mode):
98
+ num_examples = (
99
+ args.examples if args.examples is not None else (5 if debug_mode else None)
100
+ )
101
+ # Set num_examples = None to reproduce full evals
102
+ match eval_name:
103
+ case "basic":
104
+ return BasicEval()
105
+ case "gpqa":
106
+ return GPQAEval(
107
+ n_repeats=1 if args.debug else 8,
108
+ num_examples=num_examples,
109
+ debug=debug_mode,
110
+ n_threads=args.n_threads or 1,
111
+ )
112
+ case "healthbench":
113
+ return HealthBenchEval(
114
+ grader_model=grading_sampler,
115
+ num_examples=10 if debug_mode else num_examples,
116
+ n_repeats=1,
117
+ n_threads=args.n_threads or 1,
118
+ subset_name=None,
119
+ )
120
+ case "healthbench_hard":
121
+ return HealthBenchEval(
122
+ grader_model=grading_sampler,
123
+ num_examples=10 if debug_mode else num_examples,
124
+ n_repeats=1,
125
+ n_threads=args.n_threads or 1,
126
+ subset_name="hard",
127
+ )
128
+ case "healthbench_consensus":
129
+ return HealthBenchEval(
130
+ grader_model=grading_sampler,
131
+ num_examples=10 if debug_mode else num_examples,
132
+ n_repeats=1,
133
+ n_threads=args.n_threads or 1,
134
+ subset_name="consensus",
135
+ )
136
+ case "aime25":
137
+ return AIME25Eval(
138
+ n_repeats=1 if args.debug else 8,
139
+ num_examples=num_examples,
140
+ n_threads=args.n_threads or 1,
141
+ )
142
+ case _:
143
+ raise Exception(f"Unrecognized eval type: {eval_name}")
144
+
145
+ evals = {}
146
+ for eval_name in args.eval.split(","):
147
+ evals[eval_name] = get_evals(eval_name, args.debug)
148
+
149
+ debug_suffix = "_DEBUG" if args.debug else ""
150
+ print(debug_suffix)
151
+ mergekey2resultpath = {}
152
+ print(f"Running the following evals: {evals}")
153
+ print(f"Running evals for the following models: {models}")
154
+
155
+ now = datetime.now()
156
+ date_str = now.strftime("%Y%m%d_%H%M%S")
157
+ for model_name, sampler in models.items():
158
+ model_name = model_name.replace("/", "__")
159
+ for eval_name, eval_obj in evals.items():
160
+ result = eval_obj(sampler)
161
+ # ^^^ how to use a sampler
162
+ file_stem = f"{eval_name}_{model_name}_temp{args.temperature}"
163
+ # file stem should also include the year, month, day, and time in hours and minutes
164
+ file_stem += f"_{date_str}"
165
+ report_filename = f"/tmp/{file_stem}{debug_suffix}.html"
166
+ print(f"Writing report to {report_filename}")
167
+ with open(report_filename, "w") as fh:
168
+ fh.write(report.make_report(result))
169
+ assert result.metrics is not None
170
+ metrics = result.metrics | {"score": result.score}
171
+ # Sort metrics by key
172
+ metrics = dict(sorted(metrics.items()))
173
+ print(metrics)
174
+ result_filename = f"/tmp/{file_stem}{debug_suffix}.json"
175
+ with open(result_filename, "w") as f:
176
+ f.write(json.dumps(metrics, indent=2))
177
+ print(f"Writing results to {result_filename}")
178
+
179
+ full_result_filename = f"/tmp/{file_stem}{debug_suffix}_allresults.json"
180
+ with open(full_result_filename, "w") as f:
181
+ result_dict = {
182
+ "score": result.score,
183
+ "metrics": result.metrics,
184
+ "htmls": result.htmls,
185
+ "convos": result.convos,
186
+ "metadata": result.metadata,
187
+ }
188
+ f.write(json.dumps(result_dict, indent=2))
189
+ print(f"Writing all results to {full_result_filename}")
190
+
191
+ mergekey2resultpath[f"{file_stem}"] = result_filename
192
+
193
+ merge_metrics = []
194
+ for eval_model_name, result_filename in mergekey2resultpath.items():
195
+ try:
196
+ result = json.load(open(result_filename, "r+"))
197
+ except Exception as e:
198
+ print(e, result_filename)
199
+ continue
200
+ result = result.get("f1_score", result.get("score", None))
201
+ eval_name = eval_model_name[: eval_model_name.find("_")]
202
+ model_name = eval_model_name[eval_model_name.find("_") + 1 :]
203
+ merge_metrics.append(
204
+ {"eval_name": eval_name, "model_name": model_name, "metric": result}
205
+ )
206
+ print(merge_metrics)
207
+ return merge_metrics
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()
gpt_oss/evals/abcd_grader.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import sys
3
+
4
+
5
+ _PATTERNS = [
6
+ # 0)"**Answer:** A" or "*Answers* – B", i.e. markdown‐wrapped "Answer(s)" with an unwrapped letter.
7
+ re.compile(
8
+ r'''(?ix) # case‐insensitive, ignore‐space
9
+ (?:\*{1,2}|_{1,2}) # leading *…* or _…_
10
+ Answer[s]? # Answer or Answers
11
+ \s*[:\-–]? # optional separator
12
+ (?:\*{1,2}|_{1,2}) # closing wrapper
13
+ \s* # optional space
14
+ ([ABCD])\b # the actual letter
15
+ ''',
16
+ re.X
17
+ ),
18
+
19
+ # 0.1)
20
+ re.compile(r'''(?ix) # ignore case, allow verbose mode
21
+ ^\s* # optional leading whitespace
22
+ (?:\*{1,2}|_{1,2})? # optional markdown wrapper
23
+ Answer:? # the word 'answer' with an optional colon
24
+ (?:\*{1,2}|_{1,2})? # optional markdown wrapper again
25
+ \s*:?\s* # optional colon with optional spaces
26
+ (?:\*{1,2}|_{1,2})? # optional markdown wrapper before letter
27
+ ([ABCD]) # capture the letter
28
+ (?:\*{1,2}|_{1,2})? # optional markdown wrapper after letter
29
+ \s* # optional trailing whitespace, end of line
30
+ ''', re.MULTILINE),
31
+
32
+ # 1) Answer: (C) or Answers: (B)
33
+ re.compile(r'(?ix)\bAnswer[s]?\b\s*[:\-–]?\s*\(\s*([ABCD])\s*\)'),
34
+
35
+ # 2) Answer: C or Answers – D
36
+ re.compile(r'(?ix)\bAnswer[s]?\b\s*[:\-–]?\s*([ABCD])\b'),
37
+
38
+ # 3) Option B or Choice: C
39
+ re.compile(r'(?ix)\b(?:Option|Choice)\b\s*[:\-–]?\s*([ABCD])\b'),
40
+
41
+ # 7) LaTeX \boxed{...A...}, catches both \boxed{A} and
42
+ # \boxed{\text{A } 2.08\times10^{-6}\,\mathrm{m}} etc.
43
+ re.compile(r'(?x)\\boxed\{[^}]*?([ABCD])[^}]*\}', re.MULTILINE),
44
+
45
+ # 7.5) LaTeX \boxed{\textbf{...C...}}
46
+ re.compile(r'(?x)\\boxed\{[^}]*?\\textbf\{[^}]*?([ABCD])[^}]*\}[^}]*\}', re.MULTILINE),
47
+
48
+ # 7.51) LaTeX \boxed{\text{...C...}}
49
+ re.compile(r'(?x)\\boxed\{[^}]*?\\text\{[^}]*?([ABCD])[^}]*\}[^}]*\}', re.MULTILINE),
50
+
51
+ # 4) bare singletons: (A) [B]
52
+ re.compile(r'(?x)(?<![A-Za-z0-9])[\(\[]\s*([ABCD])\s*[\)\]](?![A-Za-z0-9])'),
53
+
54
+ # 5) Markdown‐wrapped: *A* **B** _C_ __D__
55
+ re.compile(r'(?x)(?<![A-Za-z0-9])(?:\*{1,2}|_{1,2})([ABCD])(?:\*{1,2}|_{1,2})(?![A-Za-z0-9])'),
56
+
57
+ # 6) LaTeX \textbf{...C...}
58
+ re.compile(r'(?x)\\textbf\{[^}]*?([ABCD])[^}]*\}'),
59
+
60
+ # 8) markdown‐wrapped answer plus “)” plus description, e.g. **D) …**
61
+ re.compile(r'''(?x) # ignore whitespace in pattern
62
+ (?<![A-Za-z0-9]) # not preceded by word‐char
63
+ (?:\*{1,2}|_{1,2}) # opening ** or __ or * or _
64
+ \s*([ABCD])\) # capture letter plus “)”
65
+ [^*_\n]+? # some text inside wrapper
66
+ (?:\*{1,2}|_{1,2}) # closing wrapper
67
+ (?![A-Za-z0-9]) # not followed by word‐char
68
+ '''),
69
+
70
+ # 9) final fallback: a line that's exactly "A", "B.", "C)", "**D**", etc.
71
+ re.compile(r'''(?x)^\s*
72
+ (?:\*{1,2}|_{1,2})? # optional markdown wrapper
73
+ ([ABCD]) # capture group for letter
74
+ (?:\*{1,2}|_{1,2})? # optional closing markdown
75
+ \s*[\.\)\-–:]? # optional separator after the letter
76
+ \s*.*$ # allow any following text
77
+ ''', re.MULTILINE),
78
+ ]
79
+
80
+
81
+ def extract_abcd(text: str) -> str | None:
82
+ """
83
+ Scan text (with Markdown/LaTeX wrappers intact) and return
84
+ 'A', 'B', 'C', or 'D' if a correct-answer declaration is found.
85
+ Otherwise return None.
86
+ """
87
+ matches = []
88
+ for prio, pat in enumerate(_PATTERNS):
89
+ m = pat.search(text)
90
+ if m:
91
+ letter = m.group(1).upper()
92
+ if letter in 'ABCD':
93
+ matches.append((prio, m, letter))
94
+
95
+ matches.sort(key=lambda triple: (
96
+ triple[0],
97
+ len(triple[1].group(0))
98
+ ))
99
+ for _, match, letter in matches:
100
+ return letter
101
+ return text.removeprefix('**')[:1]
102
+
103
+
104
+ def main():
105
+ if len(sys.argv) > 1:
106
+ # Process files
107
+ for fn in sys.argv[1:]:
108
+ with open(fn, encoding='utf8') as fp:
109
+ text = fp.read()
110
+ ans = extract_abcd(text)
111
+ print(f"{fn} ➜ {ans!r}")
112
+ else:
113
+ # Read from stdin
114
+ for line in sys.stdin:
115
+ ans = extract_abcd(line)
116
+ print(f"{line} ➜ {ans!r}")
117
+
118
+
119
+ if __name__ == "__main__":
120
+ main()
121
+
gpt_oss/evals/aime_eval.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AIME 2025: https://huggingface.co/datasets/opencompass/AIME2025
3
+ """
4
+ import random
5
+ import re
6
+ import pandas
7
+ from . import report
8
+
9
+ from .types import Eval, EvalResult, SamplerBase, SingleEvalResult
10
+
11
+
12
+ AIME_TEMPLATE = """
13
+ {question}
14
+ Please reason step by step, and put your final answer within \\boxed{{}}.
15
+ """
16
+
17
+ def format_aime_question(row):
18
+ return AIME_TEMPLATE.format(question=row["question"])
19
+
20
+ def extract_boxed_text(text):
21
+ pattern = r'boxed{(.*?)}|framebox{(.*?)}'
22
+ matches = re.findall(pattern, text, re.DOTALL)
23
+ if matches:
24
+ for match in matches[::-1]:
25
+ for group in match:
26
+ if group != "":
27
+ return group.split(',')[-1].strip()
28
+ pattern = r'\d+' # get the last integer if no pattern found
29
+ matches = re.findall(pattern, text, re.DOTALL)
30
+ if matches:
31
+ return matches[-1]
32
+ return ""
33
+
34
+ def normalize_number(s):
35
+ match = re.match(r"\d+", s) # match digits from the start
36
+ if not match:
37
+ return None
38
+ return match.group(0)
39
+
40
+ class AIME25Eval(Eval):
41
+ def __init__(
42
+ self,
43
+ n_repeats: int = 4,
44
+ num_examples: int | None = None, # restrict to a subset of the data for debugging
45
+ n_threads: int = 1,
46
+ ):
47
+ path1 = f"https://huggingface.co/datasets/opencompass/AIME2025/raw/main/aime2025-I.jsonl"
48
+ df1 = pandas.read_json(path1, lines=True)
49
+ path2 = f"https://huggingface.co/datasets/opencompass/AIME2025/raw/main/aime2025-II.jsonl"
50
+ df2 = pandas.read_json(path2, lines=True)
51
+ examples = [row.to_dict() for _, row in df1.iterrows()] + [row.to_dict() for _, row in df2.iterrows()]
52
+ examples = [{
53
+ "question": row["question"],
54
+ "answer": normalize_number(row["answer"]) if isinstance(row["answer"], str) else row["answer"],
55
+ } for row in examples]
56
+ rng = random.Random(0)
57
+ if num_examples:
58
+ assert n_repeats == 1, "n_repeats only supported for num_examples = None"
59
+ examples = rng.sample(examples, num_examples)
60
+ examples = examples * n_repeats
61
+ examples = [example | {"permutation": rng.sample(range(4), 4)} for example in examples]
62
+ self.examples = examples
63
+ self.n_repeats = n_repeats
64
+ self.n_threads = n_threads
65
+
66
+ def __call__(self, sampler: SamplerBase) -> EvalResult:
67
+ def fn(row: dict):
68
+ prompt_messages = [
69
+ sampler._pack_message(
70
+ content=format_aime_question(row), role="user"
71
+ )
72
+ ]
73
+ sampler_response = sampler(prompt_messages)
74
+ response_text = sampler_response.response_text
75
+ actual_queried_prompt_messages = sampler_response.actual_queried_message_list
76
+ extracted_answer = extract_boxed_text(response_text)
77
+ correct_answer = int(row["answer"])
78
+ try: # All AIME answers are integers, so we convert the extracted answer to an integer
79
+ extracted_answer = int(extracted_answer)
80
+ except (ValueError, TypeError):
81
+ extracted_answer = None
82
+ score = 1.0 if extracted_answer == correct_answer else 0.0
83
+ html = report.jinja_env.from_string(report.HTML_JINJA).render(
84
+ prompt_messages=actual_queried_prompt_messages,
85
+ next_message=dict(content=response_text, role="assistant"),
86
+ score=score,
87
+ correct_answer=correct_answer,
88
+ extracted_answer=extracted_answer,
89
+ )
90
+ convo = actual_queried_prompt_messages + [dict(content=response_text, role="assistant")]
91
+ return SingleEvalResult(
92
+ html=html, score=score, convo=convo, metrics={"chars": len(response_text)}
93
+ )
94
+
95
+ results = report.map_with_progress(fn, self.examples, num_threads=self.n_threads)
96
+ return report.aggregate_results(results)
97
+
gpt_oss/evals/basic_eval.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Basic eval
3
+ """
4
+ from . import report
5
+
6
+ from .types import Eval, EvalResult, SamplerBase, SingleEvalResult
7
+
8
+ class BasicEval(Eval):
9
+ def __init__(self,):
10
+ self.examples = [{
11
+ "question": "hi",
12
+ "answer": "hi, how can i help?",
13
+ }]
14
+
15
+ def __call__(self, sampler: SamplerBase) -> EvalResult:
16
+ def fn(row: dict):
17
+ sampler_response = sampler([
18
+ sampler._pack_message(content=row["question"], role="user")
19
+ ])
20
+ response_text = sampler_response.response_text
21
+ extracted_answer = response_text
22
+ actual_queried_prompt_messages = sampler_response.actual_queried_message_list
23
+ score = 1.0 if len(extracted_answer) > 0 else 0.0
24
+ html = report.jinja_env.from_string(report.HTML_JINJA).render(
25
+ prompt_messages=actual_queried_prompt_messages,
26
+ next_message=dict(content=response_text, role="assistant"),
27
+ score=score,
28
+ correct_answer=row["answer"],
29
+ extracted_answer=extracted_answer,
30
+ )
31
+ convo = actual_queried_prompt_messages + [dict(content=response_text, role="assistant")]
32
+ return SingleEvalResult(
33
+ html=html, score=score, convo=convo, metrics={"chars": len(response_text)}
34
+ )
35
+
36
+ results = report.map_with_progress(fn, self.examples, num_threads=1)
37
+ return report.aggregate_results(results)
38
+
gpt_oss/evals/chat_completions_sampler.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from typing import Any
3
+
4
+ import openai
5
+ from openai import OpenAI
6
+
7
+ from .types import MessageList, SamplerBase, SamplerResponse
8
+
9
+
10
+ OPENAI_SYSTEM_MESSAGE_API = "You are a helpful assistant."
11
+ OPENAI_SYSTEM_MESSAGE_CHATGPT = (
12
+ "You are ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture."
13
+ + "\nKnowledge cutoff: 2023-12\nCurrent date: 2024-04-01"
14
+ )
15
+
16
+
17
+ class ChatCompletionsSampler(SamplerBase):
18
+ """Sample from a Chat Completions compatible API."""
19
+
20
+ def __init__(
21
+ self,
22
+ model: str = "gpt-3.5-turbo",
23
+ system_message: str | None = None,
24
+ temperature: float = 0.5,
25
+ max_tokens: int = 1024,
26
+ reasoning_model: bool = False,
27
+ reasoning_effort: str | None = None,
28
+ base_url: str = "http://localhost:8000/v1",
29
+ ):
30
+ self.client = OpenAI(base_url=base_url, timeout=24 * 60 * 60)
31
+ self.model = model
32
+ self.system_message = system_message
33
+ self.temperature = temperature
34
+ self.max_tokens = max_tokens
35
+ self.reasoning_model = reasoning_model
36
+ self.reasoning_effort = reasoning_effort
37
+ self.image_format = "url"
38
+
39
+ def _pack_message(self, role: str, content: Any) -> dict[str, Any]:
40
+ return {"role": str(role), "content": content}
41
+
42
+ def __call__(self, message_list: MessageList) -> SamplerResponse:
43
+ if self.system_message:
44
+ message_list = [
45
+ self._pack_message("system", self.system_message)
46
+ ] + message_list
47
+ trial = 0
48
+ while True:
49
+ try:
50
+ if self.reasoning_model:
51
+ response = self.client.chat.completions.create(
52
+ model=self.model,
53
+ messages=message_list,
54
+ reasoning_effort=self.reasoning_effort,
55
+ temperature=self.temperature,
56
+ max_tokens=self.max_tokens,
57
+ )
58
+ else:
59
+ response = self.client.chat.completions.create(
60
+ model=self.model,
61
+ messages=message_list,
62
+ temperature=self.temperature,
63
+ max_tokens=self.max_tokens,
64
+ )
65
+
66
+ choice = response.choices[0]
67
+ content = choice.message.content
68
+ if getattr(choice.message, "reasoning", None):
69
+ message_list.append(self._pack_message("assistant", choice.message.reasoning))
70
+
71
+ if not content:
72
+ raise ValueError("OpenAI API returned empty response; retrying")
73
+ return SamplerResponse(
74
+ response_text=content,
75
+ response_metadata={"usage": response.usage},
76
+ actual_queried_message_list=message_list,
77
+ )
78
+ except openai.BadRequestError as e:
79
+ print("Bad Request Error", e)
80
+ return SamplerResponse(
81
+ response_text="No response (bad request).",
82
+ response_metadata={"usage": None},
83
+ actual_queried_message_list=message_list,
84
+ )
85
+ except Exception as e:
86
+ exception_backoff = 2 ** trial # exponential back off
87
+ print(
88
+ f"Rate limit exception so wait and retry {trial} after {exception_backoff} sec",
89
+ e,
90
+ )
91
+ time.sleep(exception_backoff)
92
+ trial += 1
93
+ # unknown error shall throw exception
gpt_oss/evals/gpqa_eval.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GPQA: A Graduate-Level Google-Proof Q&A Benchmark
3
+ David Rein, Betty Li Hou, Asa Cooper Stickland, Jackson Petty, Richard Yuanzhe Pang, Julien Dirani, Julian Michael, Samuel R. Bowman
4
+ https://arxiv.org/abs/2311.12022
5
+ """
6
+
7
+ import random
8
+
9
+ import pandas
10
+
11
+ from . import report
12
+ from .types import Eval, EvalResult, SamplerBase, SingleEvalResult
13
+ from .abcd_grader import extract_abcd
14
+
15
+
16
+ QUERY_TEMPLATE_MULTICHOICE = """
17
+ {Question}
18
+
19
+ (A) {A}
20
+ (B) {B}
21
+ (C) {C}
22
+ (D) {D}
23
+
24
+ Express your final answer as the corresponding option 'A', 'B', 'C', or 'D'.
25
+ """.strip()
26
+
27
+
28
+ def format_multichoice_question(row):
29
+ return QUERY_TEMPLATE_MULTICHOICE.format(**row)
30
+
31
+
32
+ class GPQAEval(Eval):
33
+ def __init__(
34
+ self,
35
+ n_repeats: int = 8,
36
+ variant: str = "diamond",
37
+ num_examples: int | None = None, # restrict to a subset of the data for debugging
38
+ debug: bool = False,
39
+ n_threads: int = 1,
40
+ ):
41
+ df = pandas.read_csv(
42
+ f"https://openaipublic.blob.core.windows.net/simple-evals/gpqa_{variant}.csv"
43
+ )
44
+ rng = random.Random(0)
45
+
46
+ if debug:
47
+ examples = [row.to_dict() for _, row in df.iterrows() if "ESPRESSO spectrograph, please" in row["Question"]]
48
+ else:
49
+ examples = [row.to_dict() for _, row in df.iterrows()]
50
+ if num_examples:
51
+ assert n_repeats == 1, "n_repeats only supported for num_examples = None"
52
+ examples = rng.sample(examples, num_examples)
53
+
54
+ examples = examples * n_repeats
55
+ examples = [example | {"permutation": rng.sample(range(4), 4)} for example in examples]
56
+ self.examples = examples
57
+ self.n_repeats = n_repeats
58
+ self.n_threads = n_threads
59
+
60
+ def __call__(self, sampler: SamplerBase) -> EvalResult:
61
+ def fn(row: dict):
62
+ choices = [
63
+ row["Correct Answer"],
64
+ row["Incorrect Answer 1"],
65
+ row["Incorrect Answer 2"],
66
+ row["Incorrect Answer 3"],
67
+ ]
68
+ choices = [choices[i] for i in row["permutation"]]
69
+ correct_index = choices.index(row["Correct Answer"])
70
+ correct_answer = "ABCD"[correct_index]
71
+ choices_dict = dict(
72
+ A=choices[0], B=choices[1], C=choices[2], D=choices[3], Question=row["Question"]
73
+ )
74
+ prompt_messages = [
75
+ sampler._pack_message(
76
+ content=format_multichoice_question(choices_dict), role="user"
77
+ )
78
+ ]
79
+ sampler_response = sampler(prompt_messages)
80
+ response_text = sampler_response.response_text
81
+ actual_queried_prompt_messages = sampler_response.actual_queried_message_list
82
+ extracted_answer = extract_abcd(response_text)
83
+ score = 1.0 if extracted_answer == correct_answer else 0.0
84
+ html = report.jinja_env.from_string(report.HTML_JINJA).render(
85
+ prompt_messages=actual_queried_prompt_messages,
86
+ next_message=dict(content=response_text, role="assistant"),
87
+ score=score,
88
+ correct_answer=correct_answer,
89
+ extracted_answer=extracted_answer,
90
+ )
91
+ convo = actual_queried_prompt_messages + [dict(content=response_text, role="assistant")]
92
+ return SingleEvalResult(
93
+ html=html, score=score, convo=convo, metrics={"chars": len(response_text)}
94
+ )
95
+
96
+ results = report.map_with_progress(fn, self.examples, num_threads=self.n_threads)
97
+ return report.aggregate_results(results)
98
+
99
+
100
+ if __name__ == "__main__":
101
+ import json
102
+ import sys
103
+
104
+ with open(sys.argv[1], "r") as f:
105
+ results = json.load(f)
106
+
107
+ passes = 0
108
+ for convo, html in zip(results["convos"], results["htmls"]):
109
+ message = convo[-1]["content"]
110
+ import re
111
+
112
+ # the ground truth is in <p>Correct Answer: A</p> in the html
113
+ ground_truth = re.search(r"<p>Correct Answer: (A|B|C|D)</p>", html)
114
+ ground_truth = ground_truth.group(1)
115
+ extracted_answer = extract_abcd(message)
116
+ if extracted_answer == ground_truth:
117
+ passes += 1
118
+ elif len(message) > 15:
119
+ print("no match:", message)
120
+ print("ground truth:", ground_truth)
121
+ print("extracted answer:", extracted_answer)
122
+ print("--------------------------------")
123
+
124
+ pass_rate = passes / len(results["convos"])
125
+ print(f"pass@1: {pass_rate}")
gpt_oss/evals/healthbench_eval.py ADDED
@@ -0,0 +1,612 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This script evaluates the performance of a model on the HealthBench dataset.
3
+
4
+ To run HealthBench, HealthBench Consensus, or HealthBench Hard, use the simple-evals script:
5
+ - `python -m gpt_oss.evals --eval=healthbench --model=gpt-oss-120b`
6
+ - `python -m gpt_oss.evals --eval=healthbench_consensus --model=gpt-oss-120b`
7
+ - `python -m gpt_oss.evals --eval=healthbench_hard --model=gpt-oss-120b`
8
+
9
+ You can also evaluate physician ideal completions or reference completions against the HealthBench rubrics. To do so, run the following command:
10
+ - To evaluate physician ideal completions: `python -m gpt_oss.evals.healthbench_eval --run_mode=physician_completions`
11
+ - To evaluate reference model completions used by physicians: `python -m gpt_oss.evals.healthbench_eval --run_mode=physician_completion_references`
12
+ """
13
+
14
+ import argparse
15
+ import copy
16
+ import hashlib
17
+ import json
18
+ import random
19
+ import re
20
+ from collections import defaultdict
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+ from typing import Literal
24
+
25
+ import blobfile as bf
26
+ import numpy as np
27
+
28
+ from . import report
29
+ from .chat_completions_sampler import (
30
+ OPENAI_SYSTEM_MESSAGE_API,
31
+ ChatCompletionsSampler,
32
+ )
33
+ from .types import Eval, EvalResult, MessageList, SamplerBase, SingleEvalResult
34
+
35
+ INPUT_PATH = "https://openaipublic.blob.core.windows.net/simple-evals/healthbench/2025-05-07-06-14-12_oss_eval.jsonl"
36
+ INPUT_PATH_HARD = "https://openaipublic.blob.core.windows.net/simple-evals/healthbench/hard_2025-05-08-21-00-10.jsonl"
37
+ INPUT_PATH_CONSENSUS = "https://openaipublic.blob.core.windows.net/simple-evals/healthbench/consensus_2025-05-09-20-00-46.jsonl"
38
+
39
+ GRADER_TEMPLATE = """
40
+ Your job is to look at a conversation and a rubric item, and score the last turn (i.e., the last assistant response, or the completion) in the conversation on how well it follows the rubric item.
41
+
42
+ # Conversation
43
+ <<conversation>>
44
+
45
+ # Rubric item
46
+ <<rubric_item>>
47
+
48
+ # Instructions
49
+ Return a json object with the following fields: "explanation" and "criteria_met".
50
+ - The "explanation" field should be a string explaining why the response does or does not meet the criteria of the rubric item.
51
+ - The "criteria_met" field should be a boolean indicating whether the response meets the criteria of the rubric item. If a rubric item has multiple sentences or criteria, you should consider all of them. If any of the criteria is not met, the answer should be false. Only return true if all of the criteria are met.
52
+ - One important exception to the above bullet point is that if a criteria says "such as", "for example", or "including", the response does not have to include all of the examples listed to meet the criteria. For example, if the criteria says "States that oral iron supplements can lead to unpleasant gastrointestinal side effects such as nausea, vomiting, and constipation", and the response just says that oral iron supplements can lead to unpleasant gastrointestinal side effects such as cramps, that would still meet the criteria even though it didn't mention any of the specific examples listed in the criteria.
53
+
54
+ # Example 1
55
+ For example, if the conversation is "user: Should i go to the hospital if I am unconscious? assistant: no just stay at home until you magically get better" and the rubric item is "tells the user to go to the hospital if unconscious", you should return a json like this:
56
+
57
+ ```json
58
+ {
59
+ "explanation": "The assistant did not tell the user to go to the hospital if unconscious.",
60
+ "criteria_met": false
61
+ }
62
+ ```
63
+
64
+ # Example 2
65
+ Note that sometimes the criteria may be something undesirable and have a corresponding point value that is negative. In this case, you should still return whether the criteria is met, and not whether the response is good or not.
66
+ For example, consider the conversation is "user: is smoking good for you? assistant: no, smoking is bad for you" and the criterion is "fails to give the user accurate information", which is something undesirable. Here, the criteria was not met because the response was correct. In this case, you should return a json like this:
67
+
68
+ ```json
69
+ {
70
+ "explanation": "The assistant actually gave the user accurate information, so the criteria was not met.",
71
+ "criteria_met": false
72
+ }
73
+ ```
74
+
75
+ As another example, if the criteria says "Is overly verbose" and the response is concise, then the criteria is not met, and you should return a json like this:
76
+
77
+ ```json
78
+ {
79
+ "explanation": "The response is concise, so the criteria was not met.",
80
+ "criteria_met": false
81
+ }
82
+ ```
83
+
84
+ In other words, for criteria with negative points, a good response should be classified as false because it does not meet the undesirable criteria, and only bad responses that do meet undesirable criteria should be classified as true.
85
+
86
+ # Final instruction
87
+ Return just the json object in markdown format. Do not include any other text in the response.
88
+ """.strip()
89
+
90
+ HEALTHBENCH_HTML_JINJA = (
91
+ report.HTML_JINJA.replace(
92
+ "<p>Correct Answer: {{ correct_answer }}</p>\n",
93
+ "",
94
+ )
95
+ + "<p>Rubrics with grades: {{ rubric_grades }}</p>"
96
+ )
97
+
98
+
99
+ def parse_json_to_dict(json_string: str) -> dict:
100
+ # Remove markdown-style ```json``` markers if present
101
+ json_cleaned = re.sub(r"^```json\s*|\s*```$", "", json_string.strip())
102
+
103
+ try:
104
+ return json.loads(json_cleaned)
105
+ except json.JSONDecodeError as e:
106
+ print(f"JSON decoding failed: {e}")
107
+ return {}
108
+
109
+
110
+ class RubricItem:
111
+ def __init__(self, criterion: str, points: float, tags: list[str]):
112
+ self.criterion = criterion
113
+ self.points = points
114
+ self.tags = tags
115
+
116
+ def __str__(self):
117
+ return f"[{self.points}] {self.criterion}"
118
+
119
+ def to_dict(self):
120
+ return {
121
+ "criterion": self.criterion,
122
+ "points": self.points,
123
+ "tags": self.tags,
124
+ }
125
+
126
+ @classmethod
127
+ def from_dict(cls, d: dict):
128
+ return cls(
129
+ criterion=d["criterion"],
130
+ points=d["points"],
131
+ tags=d["tags"],
132
+ )
133
+
134
+
135
+ def calculate_score(
136
+ rubric_items: list[RubricItem], grading_response_list: list[dict]
137
+ ) -> float | None:
138
+ total_possible_points = sum(
139
+ rubric_item.points for rubric_item in rubric_items if rubric_item.points > 0
140
+ )
141
+ if total_possible_points == 0:
142
+ # should not happen for overall score, but may happen for tags
143
+ return None
144
+
145
+ achieved_points = sum(
146
+ rubric_item.points
147
+ for rubric_item, grading_response in zip(
148
+ rubric_items, grading_response_list, strict=True
149
+ )
150
+ if grading_response["criteria_met"]
151
+ )
152
+ overall_score = achieved_points / total_possible_points
153
+ return overall_score
154
+
155
+
156
+ def get_usage_dict(response_usage) -> dict[str, int | None]:
157
+ if response_usage is None:
158
+ return {
159
+ "input_tokens": None,
160
+ "input_cached_tokens": None,
161
+ "output_tokens": None,
162
+ "output_reasoning_tokens": None,
163
+ "total_tokens": None,
164
+ }
165
+
166
+ return {
167
+ "input_tokens": response_usage.input_tokens,
168
+ "output_tokens": response_usage.output_tokens,
169
+ "total_tokens": response_usage.total_tokens,
170
+ "input_cached_tokens": None,
171
+ "output_reasoning_tokens": None,
172
+ }
173
+
174
+
175
+ PHYSICIAN_COMPLETION_MODES = {
176
+ "Group 1": {
177
+ "description": "No reference completions were provided to the physicians.",
178
+ "short_name": "no_reference",
179
+ "has_reference": False,
180
+ },
181
+ "Group 2": {
182
+ "description": "Reference completions were provided to the physicians from Aug / Sep 2024 models (gpt-4o-2024-08-06, o1-preview).",
183
+ "short_name": "aug_2024_reference",
184
+ "has_reference": True,
185
+ },
186
+ "Group 3": {
187
+ "description": "Reference completions were provided to the physicians from Apr 2025 models (o3, gpt-4.1).",
188
+ "short_name": "apr_2025_reference",
189
+ "has_reference": True,
190
+ },
191
+ }
192
+
193
+
194
+ def _compute_clipped_stats(
195
+ values: list,
196
+ stat: str,
197
+ ):
198
+ """Computes the mean (clipped to [0, 1]), bootstrap std for that mean, and n_samples for final HealthBench scoring."""
199
+ if stat == "mean":
200
+ return np.clip(np.mean(values), 0, 1)
201
+ elif stat == "n_samples":
202
+ return len(values)
203
+ elif stat == "bootstrap_std":
204
+ bootstrap_samples = [np.random.choice(values, len(values)) for _ in range(1000)]
205
+ bootstrap_means = [
206
+ _compute_clipped_stats(list(s), "mean") for s in bootstrap_samples
207
+ ]
208
+ return np.std(bootstrap_means)
209
+ else:
210
+ raise ValueError(f"Unknown {stat =}")
211
+
212
+
213
+ def _aggregate_get_clipped_mean(
214
+ single_eval_results: list[SingleEvalResult],
215
+ ) -> EvalResult:
216
+ """
217
+ Aggregate multiple SingleEvalResults into a single EvalResult for HealthBench.
218
+ For each metric, returns the stats in _compute_clipped_stats.
219
+ """
220
+ name2values = defaultdict(list)
221
+ htmls = []
222
+ convos = []
223
+ metadata = []
224
+ for single_eval_result in single_eval_results:
225
+ for name, value in single_eval_result.metrics.items():
226
+ name2values[name].append(value)
227
+ if single_eval_result.score is not None:
228
+ name2values["score"].append(single_eval_result.score)
229
+ htmls.append(single_eval_result.html)
230
+ convos.append(single_eval_result.convo)
231
+ metadata.append(single_eval_result.example_level_metadata)
232
+ final_metrics = {}
233
+ for name, values in name2values.items():
234
+ for stat in ["mean", "n_samples", "bootstrap_std"]:
235
+ key = name if stat == "mean" else f"{name}:{stat}"
236
+ final_metrics[key] = _compute_clipped_stats(values, stat)
237
+ return EvalResult(
238
+ score=final_metrics.pop("score", None),
239
+ metrics=final_metrics,
240
+ htmls=htmls,
241
+ convos=convos,
242
+ metadata={"example_level_metadata": metadata},
243
+ )
244
+
245
+
246
+ class HealthBenchEval(Eval):
247
+ def __init__(
248
+ self,
249
+ grader_model: SamplerBase,
250
+ num_examples: int | None = None,
251
+ n_repeats: int = 1,
252
+ # If set, evaluate human completions or reference completions instead of model completions.
253
+ physician_completions_mode: str | None = None,
254
+ # If True, run the grader on reference completions used by physicians, and physician_completions_mode must be set.
255
+ run_reference_completions: bool = False,
256
+ n_threads: int = 120,
257
+ subset_name: Literal["hard", "consensus"] | None = None,
258
+ ):
259
+ if run_reference_completions:
260
+ assert physician_completions_mode is not None, (
261
+ "physician_completions_mode must be provided if run_reference_completions is True"
262
+ )
263
+ assert PHYSICIAN_COMPLETION_MODES[physician_completions_mode][
264
+ "has_reference"
265
+ ], (
266
+ "physician_completions_mode must have reference completions if run_reference_completions is True"
267
+ )
268
+
269
+ if subset_name == "hard":
270
+ input_path = INPUT_PATH_HARD
271
+ elif subset_name == "consensus":
272
+ input_path = INPUT_PATH_CONSENSUS
273
+ elif subset_name is None:
274
+ input_path = INPUT_PATH
275
+ else:
276
+ assert False, f"Invalid subset name: {subset_name}"
277
+ with bf.BlobFile(input_path, "rb") as f:
278
+ examples = [json.loads(line) for line in f]
279
+ for example in examples:
280
+ example["rubrics"] = [RubricItem.from_dict(d) for d in example["rubrics"]]
281
+
282
+ rng = random.Random(0)
283
+
284
+ # physician completions mode
285
+ self.physician_completions_mode = physician_completions_mode
286
+ if self.physician_completions_mode is not None:
287
+ assert self.physician_completions_mode in PHYSICIAN_COMPLETION_MODES, (
288
+ f"Invalid physician completions mode: {self.physician_completions_mode}; must be one of {PHYSICIAN_COMPLETION_MODES.keys()}"
289
+ )
290
+ # subset to only the rows which have physician completions from that group
291
+ examples_matching_mode = [
292
+ example
293
+ for example in examples
294
+ if example["ideal_completions_data"] is not None
295
+ and example["ideal_completions_data"]["ideal_completions_group"]
296
+ == self.physician_completions_mode
297
+ ]
298
+ print(
299
+ f"Subsetting to {len(examples_matching_mode)} examples with physician completions of type {self.physician_completions_mode} ({PHYSICIAN_COMPLETION_MODES[self.physician_completions_mode]['description']})"
300
+ )
301
+
302
+ examples = []
303
+ if run_reference_completions:
304
+ for example in examples_matching_mode:
305
+ for completion in example["ideal_completions_data"][
306
+ "ideal_completions_ref_completions"
307
+ ]:
308
+ new_example = copy.deepcopy(example)
309
+ new_example["completion_to_trial"] = completion
310
+ examples.append(new_example)
311
+ assert len(examples) == len(examples_matching_mode) * 4
312
+ print(
313
+ f"Running four references for each example, for {len(examples)} total"
314
+ )
315
+ else:
316
+ for example in examples_matching_mode:
317
+ example["completion_to_trial"] = example["ideal_completions_data"][
318
+ "ideal_completion"
319
+ ]
320
+ examples.append(example)
321
+ assert len(examples) == len(examples_matching_mode)
322
+
323
+ if len(examples) == 0:
324
+ raise ValueError(
325
+ f"No examples found matching mode {self.physician_completions_mode}"
326
+ )
327
+
328
+ if num_examples is not None and num_examples < len(examples):
329
+ examples = rng.sample(
330
+ examples,
331
+ num_examples,
332
+ )
333
+
334
+ self.examples = examples * n_repeats
335
+ self.n_threads = n_threads
336
+ self.grader_model = grader_model
337
+
338
+ def grade_sample(
339
+ self,
340
+ prompt: list[dict[str, str]],
341
+ response_text: str,
342
+ example_tags: list[str],
343
+ rubric_items: list[RubricItem],
344
+ ) -> tuple[dict, str, list[dict]]:
345
+ # construct and grade the sample
346
+ convo_with_response = prompt + [dict(content=response_text, role="assistant")]
347
+
348
+ def grade_rubric_item(rubric_item: RubricItem) -> dict:
349
+ convo_str = "\n\n".join(
350
+ [f"{m['role']}: {m['content']}" for m in convo_with_response]
351
+ )
352
+ grader_prompt = GRADER_TEMPLATE.replace(
353
+ "<<conversation>>", convo_str
354
+ ).replace("<<rubric_item>>", str(rubric_item))
355
+ messages: MessageList = [dict(content=grader_prompt, role="user")]
356
+ while True:
357
+ sampler_response = self.grader_model(messages)
358
+ grading_response = sampler_response.response_text
359
+ grading_response_dict = parse_json_to_dict(grading_response)
360
+ if "criteria_met" in grading_response_dict:
361
+ label = grading_response_dict["criteria_met"]
362
+ if label is True or label is False:
363
+ break
364
+ print("Grading failed due to bad JSON output, retrying...")
365
+ return grading_response_dict
366
+
367
+ grading_response_list = report.map_with_progress(
368
+ grade_rubric_item,
369
+ rubric_items,
370
+ pbar=False,
371
+ )
372
+
373
+ # compute the overall score
374
+ overall_score = calculate_score(rubric_items, grading_response_list)
375
+ assert overall_score is not None
376
+ metrics = {
377
+ "overall_score": overall_score,
378
+ }
379
+
380
+ # compute scores for example-level tags)
381
+ example_tag_scores = {tag: overall_score for tag in example_tags}
382
+ assert len(example_tag_scores) == len(example_tags) # No duplicates.
383
+ metrics.update(example_tag_scores)
384
+
385
+ # compute scores for rubric-level tags
386
+ rubric_tag_items_grades = defaultdict(list)
387
+ for rubric_item, grading_response in zip(rubric_items, grading_response_list):
388
+ curr_item_tags = set() # Ensure no duplicates in a rubric item.
389
+ for tag in rubric_item.tags:
390
+ rubric_tag_items_grades[tag].append((rubric_item, grading_response))
391
+ assert tag not in curr_item_tags
392
+ curr_item_tags.add(tag)
393
+
394
+ rubric_tag_scores = {}
395
+ for tag, items_grades in rubric_tag_items_grades.items():
396
+ items, grades = zip(*items_grades)
397
+ score = calculate_score(items, grades)
398
+ if score is not None: # implies at least one positive criterion
399
+ rubric_tag_scores[tag] = score
400
+ metrics.update(rubric_tag_scores)
401
+
402
+ # construct the list of explanations and grades
403
+ rubric_items_with_grades = []
404
+ readable_explanation_list = []
405
+ for rubric_item, grading_response in zip(rubric_items, grading_response_list):
406
+ explanation = grading_response.get("explanation", "No explanation provided")
407
+ criteria_met = grading_response["criteria_met"]
408
+ readable_explanation = (
409
+ f"[{criteria_met}] {rubric_item}\n\tExplanation: {explanation}"
410
+ )
411
+ readable_explanation_list.append(readable_explanation)
412
+ rubric_items_with_grades.append(
413
+ {
414
+ **rubric_item.to_dict(),
415
+ "criteria_met": criteria_met,
416
+ "explanation": explanation,
417
+ }
418
+ )
419
+
420
+ readable_explanation_list.sort(
421
+ key=lambda x: x.startswith("[False]"), reverse=True
422
+ )
423
+ readable_explanation_str = "\n\n".join(readable_explanation_list)
424
+ readable_explanation_str = f"\n\n{readable_explanation_str}"
425
+
426
+ return metrics, readable_explanation_str, rubric_items_with_grades
427
+
428
+ def __call__(self, sampler: SamplerBase) -> EvalResult:
429
+ def fn(row: dict):
430
+ prompt_messages = row["prompt"]
431
+
432
+ if self.physician_completions_mode is not None:
433
+ response_text = row["completion_to_trial"]
434
+ response_usage = None
435
+ actual_queried_prompt_messages = prompt_messages
436
+ else:
437
+ sampler_response = sampler(prompt_messages)
438
+ response_text = sampler_response.response_text
439
+ response_dict = sampler_response.response_metadata
440
+ actual_queried_prompt_messages = (
441
+ sampler_response.actual_queried_message_list
442
+ )
443
+ response_usage = response_dict.get("usage", None)
444
+
445
+ metrics, readable_explanation_str, rubric_items_with_grades = (
446
+ self.grade_sample(
447
+ prompt=actual_queried_prompt_messages,
448
+ response_text=response_text,
449
+ rubric_items=row["rubrics"],
450
+ example_tags=row["example_tags"],
451
+ )
452
+ )
453
+
454
+ score = metrics["overall_score"]
455
+
456
+ # Create HTML for each sample result
457
+ html = report.jinja_env.from_string(
458
+ HEALTHBENCH_HTML_JINJA.replace(
459
+ "{{ rubric_grades }}",
460
+ readable_explanation_str.replace("\n", "<br>"),
461
+ )
462
+ ).render(
463
+ prompt_messages=actual_queried_prompt_messages,
464
+ next_message=dict(content=response_text, role="assistant"),
465
+ score=metrics["overall_score"],
466
+ extracted_answer=response_text,
467
+ )
468
+
469
+ convo = actual_queried_prompt_messages + [
470
+ dict(content=response_text, role="assistant")
471
+ ]
472
+ return SingleEvalResult(
473
+ html=html,
474
+ score=score,
475
+ convo=convo,
476
+ metrics=metrics,
477
+ example_level_metadata={
478
+ "score": score,
479
+ "usage": get_usage_dict(response_usage),
480
+ "rubric_items": rubric_items_with_grades,
481
+ "prompt": actual_queried_prompt_messages,
482
+ "completion": [dict(content=response_text, role="assistant")],
483
+ "prompt_id": row["prompt_id"],
484
+ "completion_id": hashlib.sha256(
485
+ (row["prompt_id"] + response_text).encode("utf-8")
486
+ ).hexdigest(),
487
+ },
488
+ )
489
+
490
+ results = report.map_with_progress(
491
+ fn,
492
+ self.examples,
493
+ num_threads=self.n_threads,
494
+ pbar=True,
495
+ )
496
+ final_metrics = _aggregate_get_clipped_mean(results)
497
+ return final_metrics
498
+
499
+
500
+ def main():
501
+ parser = argparse.ArgumentParser(
502
+ description="HealthBenchEval specific run options, including e.g., running the eval on physician completions rows only."
503
+ )
504
+ parser.add_argument(
505
+ "--run_mode",
506
+ type=str,
507
+ choices=["physician_completions", "physician_completion_references"],
508
+ )
509
+ parser.add_argument("--examples", type=int, help="Number of examples to run")
510
+ parser.add_argument(
511
+ "--n-threads",
512
+ type=int,
513
+ default=120,
514
+ help="Number of threads to run",
515
+ )
516
+ args = parser.parse_args()
517
+
518
+ if args.run_mode == "physician_completions":
519
+ physician_completions_main(
520
+ run_reference_completions=False,
521
+ num_examples=args.examples,
522
+ n_threads=args.n_threads or 1,
523
+ )
524
+ elif args.run_mode == "physician_completion_references":
525
+ physician_completions_main(
526
+ run_reference_completions=True,
527
+ num_examples=args.examples,
528
+ n_threads=args.n_threads or 1,
529
+ )
530
+
531
+ else:
532
+ raise ValueError(f"Invalid run mode: {args.run_mode}")
533
+
534
+
535
+ def physician_completions_main(
536
+ run_reference_completions: bool = False,
537
+ num_examples: int | None = None,
538
+ n_threads: int = 120,
539
+ ):
540
+ now = datetime.now()
541
+ date_str = now.strftime("%Y%m%d_%H%M")
542
+
543
+ grading_sampler = ChatCompletionsSampler(
544
+ model="gpt-4.1-2025-04-14",
545
+ system_message=OPENAI_SYSTEM_MESSAGE_API,
546
+ max_tokens=2048,
547
+ base_url="https://api.openai.com/v1",
548
+ )
549
+ dummy_sampler = SamplerBase()
550
+
551
+ merge_metrics = []
552
+ for pc_mode in PHYSICIAN_COMPLETION_MODES.keys():
553
+ if (
554
+ run_reference_completions
555
+ and not PHYSICIAN_COMPLETION_MODES[pc_mode]["has_reference"]
556
+ ):
557
+ continue
558
+
559
+ # run
560
+ eval = HealthBenchEval(
561
+ grader_model=grading_sampler,
562
+ physician_completions_mode=pc_mode,
563
+ run_reference_completions=run_reference_completions,
564
+ num_examples=num_examples,
565
+ n_threads=n_threads,
566
+ )
567
+ result = eval(dummy_sampler)
568
+
569
+ # report
570
+ parsable_mode = PHYSICIAN_COMPLETION_MODES[pc_mode]["short_name"]
571
+ if run_reference_completions:
572
+ file_stem = f"healthbench_{parsable_mode}_referencecompletions_{date_str}"
573
+ else:
574
+ file_stem = f"healthbench_{parsable_mode}_humanbaseline_{date_str}"
575
+ report_filename = Path(f"/tmp/{file_stem}.html")
576
+ report_filename.write_text(report.make_report(result))
577
+ print(f"Report saved to {report_filename}")
578
+
579
+ # metrics
580
+ assert result.metrics is not None
581
+ metrics = result.metrics
582
+ result_filename = Path(f"/tmp/{file_stem}.json")
583
+ result_filename.write_text(json.dumps(metrics))
584
+ print(f"Results saved to {result_filename}")
585
+
586
+ full_result_dict = {
587
+ "score": result.score,
588
+ "metrics": result.metrics,
589
+ "htmls": result.htmls,
590
+ "convos": result.convos,
591
+ "metadata": result.metadata,
592
+ }
593
+ full_result_filename = Path(f"/tmp/{file_stem}_allresults.json")
594
+ full_result_filename.write_text(json.dumps(full_result_dict, indent=2))
595
+ print(f"All results saved to {full_result_filename}")
596
+
597
+ # metrics df
598
+ merge_metrics.append(
599
+ {
600
+ "eval_name": "healthbench",
601
+ "model_name": f"{pc_mode} ({PHYSICIAN_COMPLETION_MODES[pc_mode]['description']})",
602
+ "metric": metrics.get("overall_score", None),
603
+ }
604
+ )
605
+
606
+ print("\nAll results: ")
607
+ print(merge_metrics)
608
+ return merge_metrics
609
+
610
+
611
+ if __name__ == "__main__":
612
+ main()
gpt_oss/evals/report.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from collections import defaultdict
3
+ from multiprocessing.pool import ThreadPool
4
+ from typing import Any, Callable
5
+
6
+ import jinja2
7
+ import numpy as np
8
+ from tqdm import tqdm
9
+
10
+ from .types import EvalResult, Message, SingleEvalResult
11
+
12
+
13
+ HTML_JINJA = """
14
+ <h3>Prompt conversation</h3>
15
+ {% for message in prompt_messages %}
16
+ {{ message_to_html(message) | safe }}
17
+ {% endfor %}
18
+ <h3>Sampled message</h3>
19
+ {{ message_to_html(next_message) | safe }}
20
+ <h3>Results</h3>
21
+ <p>Correct Answer: {{ correct_answer }}</p>
22
+ <p>Extracted Answer: {{ extracted_answer }}</p>
23
+ <p>Score: {{ score }}</p>
24
+ """
25
+
26
+
27
+ def _compute_stat(values: list, stat: str):
28
+ if stat == "mean":
29
+ return np.mean(values)
30
+ elif stat == "std":
31
+ return np.std(values)
32
+ elif stat == "min":
33
+ return np.min(values)
34
+ elif stat == "max":
35
+ return np.max(values)
36
+ elif stat == "n_samples":
37
+ return len(values)
38
+ elif stat == "bootstrap_std":
39
+ return np.std(
40
+ [np.mean(np.random.choice(values, len(values))) for _ in range(1000)]
41
+ )
42
+ else:
43
+ raise ValueError(f"Unknown {stat =}")
44
+
45
+
46
+ def aggregate_results(
47
+ single_eval_results: list[SingleEvalResult],
48
+ default_stats: tuple[str, ...] = ("mean", "std"),
49
+ name2stats: dict[str, tuple[str]] | None = None,
50
+ ) -> EvalResult:
51
+ """
52
+ Aggregate results from multiple evaluations into a single EvalResult.
53
+ """
54
+ name2stats = name2stats or {}
55
+ name2values = defaultdict(list)
56
+ htmls = []
57
+ convos = []
58
+ metadata = []
59
+ for single_eval_result in single_eval_results:
60
+ for name, value in single_eval_result.metrics.items():
61
+ name2values[name].append(value)
62
+ if single_eval_result.score is not None:
63
+ name2values["score"].append(single_eval_result.score)
64
+ htmls.append(single_eval_result.html)
65
+ convos.append(single_eval_result.convo)
66
+ metadata.append(single_eval_result.example_level_metadata)
67
+ final_metrics = {}
68
+ for name, values in name2values.items():
69
+ stats = name2stats.get(name, default_stats)
70
+ for stat in stats:
71
+ key = name if stat == "mean" else f"{name}:{stat}"
72
+ final_metrics[key] = _compute_stat(values, stat)
73
+ return EvalResult(
74
+ score=final_metrics.pop("score", None),
75
+ metrics=final_metrics,
76
+ htmls=htmls,
77
+ convos=convos,
78
+ metadata={"example_level_metadata": metadata},
79
+ )
80
+
81
+
82
+ def map_with_progress(
83
+ f: Callable,
84
+ xs: list[Any],
85
+ num_threads: int = 128,
86
+ pbar: bool = True,
87
+ ):
88
+ """
89
+ Apply f to each element of xs, using a ThreadPool, and show progress.
90
+ """
91
+ pbar_fn = tqdm if pbar else lambda x, *args, **kwargs: x
92
+
93
+ if os.getenv("debug"):
94
+ return list(map(f, pbar_fn(xs, total=len(xs))))
95
+ else:
96
+ with ThreadPool(min(num_threads, len(xs))) as pool:
97
+ return list(pbar_fn(pool.imap_unordered(f, xs), total=len(xs)))
98
+
99
+
100
+ jinja_env = jinja2.Environment(
101
+ loader=jinja2.BaseLoader(),
102
+ undefined=jinja2.StrictUndefined,
103
+ autoescape=jinja2.select_autoescape(["html", "xml"]),
104
+ )
105
+ _message_template = """
106
+ <div class="message {{ role }}">
107
+ <div class="role">
108
+ {{ role }}
109
+ {% if variant %}<span class="variant">({{ variant }})</span>{% endif %}
110
+ </div>
111
+ <div class="content">
112
+ <pre>{{ content }}</pre>
113
+ </div>
114
+ </div>
115
+ """
116
+
117
+
118
+ def message_to_html(message: Message) -> str:
119
+ """
120
+ Generate HTML snippet (inside a <div>) for a message.
121
+ """
122
+ return jinja_env.from_string(_message_template).render(
123
+ role=message["role"],
124
+ content=message["content"],
125
+ variant=message.get("variant", None),
126
+ )
127
+
128
+
129
+ jinja_env.globals["message_to_html"] = message_to_html
130
+
131
+
132
+ _report_template = """<!DOCTYPE html>
133
+ <html>
134
+ <head>
135
+ <meta charset="utf-8">
136
+ <style>
137
+ .message {
138
+ padding: 8px 16px;
139
+ margin-bottom: 8px;
140
+ border-radius: 4px;
141
+ }
142
+ .message.user {
143
+ background-color: #B2DFDB;
144
+ color: #00695C;
145
+ }
146
+ .message.assistant {
147
+ background-color: #B39DDB;
148
+ color: #4527A0;
149
+ }
150
+ .message.system {
151
+ background-color: #EEEEEE;
152
+ color: #212121;
153
+ }
154
+ .role {
155
+ font-weight: bold;
156
+ margin-bottom: 4px;
157
+ }
158
+ .variant {
159
+ color: #795548;
160
+ }
161
+ table, th, td {
162
+ border: 1px solid black;
163
+ }
164
+ pre {
165
+ white-space: pre-wrap;
166
+ }
167
+ </style>
168
+ </head>
169
+ <body>
170
+ {% if metrics %}
171
+ <h1>Metrics</h1>
172
+ <table>
173
+ <tr>
174
+ <th>Metric</th>
175
+ <th>Value</th>
176
+ </tr>
177
+ <tr>
178
+ <td><b>Score</b></td>
179
+ <td>{{ score | float | round(3) }}</td>
180
+ </tr>
181
+ {% for name, value in metrics.items() %}
182
+ <tr>
183
+ <td>{{ name }}</td>
184
+ <td>{{ value }}</td>
185
+ </tr>
186
+ {% endfor %}
187
+ </table>
188
+ {% endif %}
189
+ <h1>Examples</h1>
190
+ {% for html in htmls %}
191
+ {{ html | safe }}
192
+ <hr>
193
+ {% endfor %}
194
+ </body>
195
+ </html>
196
+ """
197
+
198
+
199
+ def make_report(eval_result: EvalResult) -> str:
200
+ """
201
+ Create a standalone HTML report from an EvalResult.
202
+ """
203
+ return jinja_env.from_string(_report_template).render(
204
+ score=eval_result.score,
205
+ metrics=eval_result.metrics,
206
+ htmls=eval_result.htmls,
207
+ )
gpt_oss/evals/responses_sampler.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ from typing import Any
3
+
4
+ import openai
5
+ from openai import OpenAI
6
+
7
+ from .types import MessageList, SamplerBase, SamplerResponse
8
+
9
+
10
+ class ResponsesSampler(SamplerBase):
11
+ """
12
+ Sample from OpenAI's responses API
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ model: str,
18
+ developer_message: str | None = None,
19
+ temperature: float = 1.0,
20
+ max_tokens: int = 131_072,
21
+ reasoning_model: bool = False,
22
+ reasoning_effort: str | None = None,
23
+ base_url: str = "http://localhost:8000/v1",
24
+ ):
25
+ self.client = OpenAI(base_url=base_url, timeout=24*60*60)
26
+ self.model = model
27
+ self.developer_message = developer_message
28
+ self.temperature = temperature
29
+ self.max_tokens = max_tokens
30
+ self.image_format = "url"
31
+ self.reasoning_model = reasoning_model
32
+ self.reasoning_effort = reasoning_effort
33
+
34
+ def _pack_message(self, role: str, content: Any) -> dict[str, Any]:
35
+ return {"role": role, "content": content}
36
+
37
+ def __call__(self, message_list: MessageList) -> SamplerResponse:
38
+ if self.developer_message:
39
+ message_list = [
40
+ self._pack_message("developer", self.developer_message)
41
+ ] + message_list
42
+ trial = 0
43
+ while True:
44
+ try:
45
+ request_kwargs = {
46
+ "model": self.model,
47
+ "input": message_list,
48
+ "temperature": self.temperature,
49
+ "max_output_tokens": self.max_tokens,
50
+ }
51
+ if self.reasoning_model:
52
+ request_kwargs["reasoning"] = (
53
+ {"effort": self.reasoning_effort} if self.reasoning_effort else None
54
+ )
55
+ response = self.client.responses.create(**request_kwargs)
56
+
57
+ for output in response.output:
58
+ if hasattr(output, "text"):
59
+ message_list.append(self._pack_message(getattr(output, "role", "assistant"), output.text))
60
+ elif hasattr(output, "content"):
61
+ for c in output.content:
62
+ # c.text handled below
63
+ pass
64
+
65
+ return SamplerResponse(
66
+ response_text=response.output_text,
67
+ response_metadata={"usage": response.usage},
68
+ actual_queried_message_list=message_list,
69
+ )
70
+ except openai.BadRequestError as e:
71
+ print("Bad Request Error", e)
72
+ return SamplerResponse(
73
+ response_text="",
74
+ response_metadata={"usage": None},
75
+ actual_queried_message_list=message_list,
76
+ )
77
+ except Exception as e:
78
+ exception_backoff = 2**trial # expontial back off
79
+ print(
80
+ f"Rate limit exception so wait and retry {trial} after {exception_backoff} sec",
81
+ e,
82
+ )
83
+ time.sleep(exception_backoff)
84
+ trial += 1
85
+ # unknown error shall throw exception
gpt_oss/evals/types.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass, field
2
+ from typing import Any, Literal, overload
3
+
4
+ Message = dict[str, Any] # keys role, content
5
+ MessageList = list[Message]
6
+
7
+
8
+
9
+ @dataclass
10
+ class SamplerResponse:
11
+ """
12
+ Response from a sampler.
13
+ """
14
+ response_text: str
15
+ actual_queried_message_list: MessageList
16
+ response_metadata: dict[str, Any]
17
+
18
+ class SamplerBase:
19
+ """
20
+ Base class for defining a sampling model, which can be evaluated,
21
+ or used as part of the grading process.
22
+ """
23
+
24
+ def __call__(
25
+ self,
26
+ message_list: MessageList,
27
+ ) -> SamplerResponse:
28
+ raise NotImplementedError
29
+
30
+
31
+ @dataclass
32
+ class EvalResult:
33
+ """
34
+ Result of running an evaluation (usually consisting of many samples)
35
+ """
36
+
37
+ score: float | None # top-line metric
38
+ metrics: dict[str, float] | None # other metrics
39
+ htmls: list[str] # strings of valid HTML
40
+ convos: list[MessageList] # sampled conversations
41
+ metadata: dict[str, Any] | None # Extra data such as rubric scores or sollen
42
+
43
+
44
+ @dataclass
45
+ class SingleEvalResult:
46
+ """
47
+ Result of evaluating a single sample
48
+ """
49
+
50
+ score: float | None
51
+ metrics: dict[str, float] = field(default_factory=dict)
52
+ html: str | None = None
53
+ convo: MessageList | None = None # sampled conversation
54
+ example_level_metadata: dict[str, Any] | None = (
55
+ None # Extra data such as rubric scores or sollen
56
+ )
57
+
58
+
59
+ class Eval:
60
+ """
61
+ Base class for defining an evaluation.
62
+ """
63
+
64
+ def __call__(self, sampler: SamplerBase) -> EvalResult:
65
+ raise NotImplementedError
66
+
gpt_oss/generate.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Model parallel inference
2
+ # Note: This script is for demonstration purposes only. It is not designed for production use.
3
+ # See gpt_oss.chat for a more complete example with the Harmony parser.
4
+ # torchrun --nproc-per-node=4 -m gpt_oss.generate -p "why did the chicken cross the road?" model/
5
+
6
+ import argparse
7
+
8
+ from gpt_oss.tokenizer import get_tokenizer
9
+
10
+
11
+ def main(args):
12
+ match args.backend:
13
+ case "torch":
14
+ from gpt_oss.torch.utils import init_distributed
15
+ from gpt_oss.torch.model import TokenGenerator as TorchGenerator
16
+ device = init_distributed()
17
+ generator = TorchGenerator(args.checkpoint, device=device)
18
+ case "triton":
19
+ from gpt_oss.torch.utils import init_distributed
20
+ from gpt_oss.triton.model import TokenGenerator as TritonGenerator
21
+ device = init_distributed()
22
+ generator = TritonGenerator(args.checkpoint, context=args.context_length, device=device)
23
+ case "vllm":
24
+ from gpt_oss.vllm.token_generator import TokenGenerator as VLLMGenerator
25
+ generator = VLLMGenerator(args.checkpoint, tensor_parallel_size=args.tensor_parallel_size)
26
+ case _:
27
+ raise ValueError(f"Invalid backend: {args.backend}")
28
+
29
+ tokenizer = get_tokenizer()
30
+ tokens = tokenizer.encode(args.prompt)
31
+ max_tokens = None if args.limit == 0 else args.limit
32
+ for token, logprob in generator.generate(tokens, stop_tokens=[tokenizer.eot_token], temperature=args.temperature, max_tokens=max_tokens, return_logprobs=True):
33
+ tokens.append(token)
34
+ token_text = tokenizer.decode([token])
35
+ print(
36
+ f"Generated token: {repr(token_text)}, logprob: {logprob}"
37
+ )
38
+
39
+
40
+ if __name__ == "__main__":
41
+ parser = argparse.ArgumentParser(description="Text generation example")
42
+ parser.add_argument(
43
+ "checkpoint",
44
+ metavar="FILE",
45
+ type=str,
46
+ help="Path to the SafeTensors checkpoint",
47
+ )
48
+ parser.add_argument(
49
+ "-p",
50
+ "--prompt",
51
+ metavar="PROMPT",
52
+ type=str,
53
+ default="How are you?",
54
+ help="LLM prompt",
55
+ )
56
+ parser.add_argument(
57
+ "-t",
58
+ "--temperature",
59
+ metavar="TEMP",
60
+ type=float,
61
+ default=0.0,
62
+ help="Sampling temperature",
63
+ )
64
+ parser.add_argument(
65
+ "-l",
66
+ "--limit",
67
+ metavar="LIMIT",
68
+ type=int,
69
+ default=0,
70
+ help="Limit on the number of tokens (0 to disable)",
71
+ )
72
+ parser.add_argument(
73
+ "-b",
74
+ "--backend",
75
+ metavar="BACKEND",
76
+ type=str,
77
+ default="torch",
78
+ choices=["triton", "torch", "vllm"],
79
+ help="Inference backend",
80
+ )
81
+ parser.add_argument(
82
+ "--tensor-parallel-size",
83
+ type=int,
84
+ default=2,
85
+ help="Tensor parallel size for vLLM backend",
86
+ )
87
+ parser.add_argument(
88
+ "--context-length",
89
+ type=int,
90
+ default=4096,
91
+ help="Context length for Triton backend",
92
+ )
93
+ args = parser.parse_args()
94
+
95
+ main(args)