ostruct-cli 0.8.2__tar.gz → 0.8.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/PKG-INFO +88 -2
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/README.md +85 -1
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/pyproject.toml +10 -4
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/click_options.py +111 -8
- ostruct_cli-0.8.3/src/ostruct/cli/code_interpreter.py +431 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/run.py +56 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/config.py +20 -1
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/errors.py +2 -30
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_info.py +55 -20
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_utils.py +19 -3
- ostruct_cli-0.8.3/src/ostruct/cli/json_extract.py +75 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/model_creation.py +1 -1
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/runner.py +461 -180
- ostruct_cli-0.8.3/src/ostruct/cli/sentinel.py +29 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_optimizer.py +11 -7
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_processor.py +243 -115
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_rendering.py +41 -1
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_validation.py +41 -3
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/types.py +14 -1
- ostruct_cli-0.8.2/src/ostruct/cli/code_interpreter.py +0 -238
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/LICENSE +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/__init__.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/__init__.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/base_errors.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/cache_manager.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/cli.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/__init__.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/list_models.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/quick_ref.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/update_registry.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/cost_estimation.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/exit_codes.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/explicit_file_processor.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/field_utils.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_list.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_search.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/mcp_integration.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/model_validation.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/path_utils.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/progress.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/progress_reporting.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/registry_updates.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/schema_utils.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/schema_validation.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/__init__.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/allowed_checker.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/base.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/case_manager.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/errors.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/normalization.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/safe_joiner.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/security_manager.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/symlink_resolver.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/types.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/windows_paths.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/serialization.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/services.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_debug.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_debug_help.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_env.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_extensions.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_filters.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_io.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_schema.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_utils.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/token_utils.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/token_validation.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/unattended_operation.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/utils.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/validators.py +0 -0
- {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/py.typed +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: ostruct-cli
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.3
|
4
4
|
Summary: CLI for OpenAI Structured Output with Multi-Tool Integration
|
5
5
|
Author: Yaniv Golan
|
6
6
|
Author-email: yaniv@golan.name
|
@@ -22,6 +22,7 @@ Requires-Dist: chardet (>=5.0.0,<6.0)
|
|
22
22
|
Requires-Dist: click (>=8.1.7,<9.0)
|
23
23
|
Requires-Dist: flake8 (>=6.0,<7.0) ; extra == "dev"
|
24
24
|
Requires-Dist: flake8-pyproject (>=1.2.3,<2.0) ; extra == "dev"
|
25
|
+
Requires-Dist: hypothesis (>=6.0.0,<7.0) ; extra == "dev"
|
25
26
|
Requires-Dist: ijson (>=3.2.3,<4.0)
|
26
27
|
Requires-Dist: jinja2 (>=3.1.2,<4.0)
|
27
28
|
Requires-Dist: jsonschema (>=4.23.0,<5.0)
|
@@ -37,6 +38,7 @@ Requires-Dist: pygments (>=2.15.0,<3.0)
|
|
37
38
|
Requires-Dist: pytest (>=8.3.4,<9.0) ; extra == "dev"
|
38
39
|
Requires-Dist: pytest-asyncio (>=0.25.2,<1.0) ; extra == "dev"
|
39
40
|
Requires-Dist: pytest-mock (>=3.14.0,<4.0) ; extra == "dev"
|
41
|
+
Requires-Dist: pytest-rerunfailures (>=12.0,<13.0) ; extra == "dev"
|
40
42
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0) ; extra == "dev"
|
41
43
|
Requires-Dist: pyyaml (>=6.0.2,<7.0)
|
42
44
|
Requires-Dist: sphinx (>=7.0,<8.0) ; extra == "dev"
|
@@ -302,7 +304,7 @@ ostruct run analysis.j2 schema.json -fc data.csv
|
|
302
304
|
ostruct run search.j2 schema.json -fs documentation.pdf
|
303
305
|
|
304
306
|
# Web Search (real-time information)
|
305
|
-
ostruct run research.j2 schema.json --web-search -V topic="latest AI developments"
|
307
|
+
ostruct run research.j2 schema.json --enable-tool web-search -V topic="latest AI developments"
|
306
308
|
|
307
309
|
# Multiple tools with one file
|
308
310
|
ostruct run template.j2 schema.json --file-for code-interpreter shared.json --file-for file-search shared.json
|
@@ -370,6 +372,7 @@ tools:
|
|
370
372
|
code_interpreter:
|
371
373
|
auto_download: true
|
372
374
|
output_directory: "./output"
|
375
|
+
download_strategy: "two_pass_sentinel" # Enable reliable file downloads
|
373
376
|
|
374
377
|
mcp:
|
375
378
|
custom_server: "https://my-mcp-server.com"
|
@@ -384,6 +387,35 @@ Load custom configuration:
|
|
384
387
|
ostruct --config my-config.yaml run template.j2 schema.json
|
385
388
|
```
|
386
389
|
|
390
|
+
### Code Interpreter File Downloads
|
391
|
+
|
392
|
+
**Important**: If you're using Code Interpreter with structured output (JSON schemas), you may need to enable the two-pass download strategy to ensure files are downloaded reliably.
|
393
|
+
|
394
|
+
#### Option 1: CLI Flags (Recommended for one-off usage)
|
395
|
+
|
396
|
+
```bash
|
397
|
+
# Enable reliable file downloads for this run
|
398
|
+
ostruct run template.j2 schema.json -fc data.csv --enable-feature ci-download-hack
|
399
|
+
|
400
|
+
# Force single-pass mode (override config)
|
401
|
+
ostruct run template.j2 schema.json -fc data.csv --disable-feature ci-download-hack
|
402
|
+
```
|
403
|
+
|
404
|
+
#### Option 2: Configuration File (Recommended for persistent settings)
|
405
|
+
|
406
|
+
```yaml
|
407
|
+
# ostruct.yaml
|
408
|
+
tools:
|
409
|
+
code_interpreter:
|
410
|
+
download_strategy: "two_pass_sentinel" # Enables reliable file downloads
|
411
|
+
auto_download: true
|
412
|
+
output_directory: "./downloads"
|
413
|
+
```
|
414
|
+
|
415
|
+
**Why this is needed**: OpenAI's structured output mode can prevent file download annotations from being generated. The two-pass strategy works around this by making two API calls: one to generate files (without structured output), then another to ensure schema compliance. For detailed technical information, see [docs/known-issues/2025-06-responses-ci-file-output.md](docs/known-issues/2025-06-responses-ci-file-output.md).
|
416
|
+
|
417
|
+
**Performance**: The two-pass strategy approximately doubles token usage but ensures reliable file downloads when using structured output with Code Interpreter.
|
418
|
+
|
387
419
|
## Get Started Quickly
|
388
420
|
|
389
421
|
🚀 **New to ostruct?** Follow our [step-by-step quickstart guide](https://ostruct.readthedocs.io/en/latest/user-guide/quickstart.html) featuring Juno the beagle for a hands-on introduction.
|
@@ -628,6 +660,60 @@ The registry file is stored at `~/.openai_structured/config/models.yml` and is a
|
|
628
660
|
|
629
661
|
The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
|
630
662
|
|
663
|
+
# Testing
|
664
|
+
|
665
|
+
## Running Tests
|
666
|
+
|
667
|
+
The test suite is divided into two categories:
|
668
|
+
|
669
|
+
### Regular Tests (Default)
|
670
|
+
|
671
|
+
```bash
|
672
|
+
# Run all tests (skips live tests by default)
|
673
|
+
pytest
|
674
|
+
|
675
|
+
# Run specific test file
|
676
|
+
pytest tests/test_config.py
|
677
|
+
|
678
|
+
# Run with verbose output
|
679
|
+
pytest -v
|
680
|
+
```
|
681
|
+
|
682
|
+
### Live Tests
|
683
|
+
|
684
|
+
Live tests make real API calls to OpenAI and require a valid API key. They are skipped by default.
|
685
|
+
|
686
|
+
```bash
|
687
|
+
# Run only live tests (requires OPENAI_API_KEY)
|
688
|
+
pytest -m live
|
689
|
+
|
690
|
+
# Run all tests including live tests
|
691
|
+
pytest -m "live or not live"
|
692
|
+
|
693
|
+
# Run specific live test
|
694
|
+
pytest tests/test_responses_annotations.py -m live
|
695
|
+
```
|
696
|
+
|
697
|
+
**Live tests include:**
|
698
|
+
|
699
|
+
- Tests that make actual OpenAI API calls
|
700
|
+
- Tests that run `ostruct` commands via subprocess
|
701
|
+
- Tests that verify real API behavior and file downloads
|
702
|
+
|
703
|
+
**Requirements for live tests:**
|
704
|
+
|
705
|
+
- Valid `OPENAI_API_KEY` environment variable
|
706
|
+
- Internet connection
|
707
|
+
- May incur API costs
|
708
|
+
|
709
|
+
## Test Markers
|
710
|
+
|
711
|
+
- `@pytest.mark.live` - Tests that make real API calls or run actual commands
|
712
|
+
- `@pytest.mark.no_fs` - Tests that need real filesystem (not pyfakefs)
|
713
|
+
- `@pytest.mark.slow` - Performance/stress tests
|
714
|
+
- `@pytest.mark.flaky` - Tests that may need reruns
|
715
|
+
- `@pytest.mark.mock_openai` - Tests using mocked OpenAI client
|
716
|
+
|
631
717
|
<!--
|
632
718
|
MAINTAINER NOTE: After editing this README, please test GitHub rendering by:
|
633
719
|
1. Creating a draft PR or pushing to a test branch
|
@@ -241,7 +241,7 @@ ostruct run analysis.j2 schema.json -fc data.csv
|
|
241
241
|
ostruct run search.j2 schema.json -fs documentation.pdf
|
242
242
|
|
243
243
|
# Web Search (real-time information)
|
244
|
-
ostruct run research.j2 schema.json --web-search -V topic="latest AI developments"
|
244
|
+
ostruct run research.j2 schema.json --enable-tool web-search -V topic="latest AI developments"
|
245
245
|
|
246
246
|
# Multiple tools with one file
|
247
247
|
ostruct run template.j2 schema.json --file-for code-interpreter shared.json --file-for file-search shared.json
|
@@ -309,6 +309,7 @@ tools:
|
|
309
309
|
code_interpreter:
|
310
310
|
auto_download: true
|
311
311
|
output_directory: "./output"
|
312
|
+
download_strategy: "two_pass_sentinel" # Enable reliable file downloads
|
312
313
|
|
313
314
|
mcp:
|
314
315
|
custom_server: "https://my-mcp-server.com"
|
@@ -323,6 +324,35 @@ Load custom configuration:
|
|
323
324
|
ostruct --config my-config.yaml run template.j2 schema.json
|
324
325
|
```
|
325
326
|
|
327
|
+
### Code Interpreter File Downloads
|
328
|
+
|
329
|
+
**Important**: If you're using Code Interpreter with structured output (JSON schemas), you may need to enable the two-pass download strategy to ensure files are downloaded reliably.
|
330
|
+
|
331
|
+
#### Option 1: CLI Flags (Recommended for one-off usage)
|
332
|
+
|
333
|
+
```bash
|
334
|
+
# Enable reliable file downloads for this run
|
335
|
+
ostruct run template.j2 schema.json -fc data.csv --enable-feature ci-download-hack
|
336
|
+
|
337
|
+
# Force single-pass mode (override config)
|
338
|
+
ostruct run template.j2 schema.json -fc data.csv --disable-feature ci-download-hack
|
339
|
+
```
|
340
|
+
|
341
|
+
#### Option 2: Configuration File (Recommended for persistent settings)
|
342
|
+
|
343
|
+
```yaml
|
344
|
+
# ostruct.yaml
|
345
|
+
tools:
|
346
|
+
code_interpreter:
|
347
|
+
download_strategy: "two_pass_sentinel" # Enables reliable file downloads
|
348
|
+
auto_download: true
|
349
|
+
output_directory: "./downloads"
|
350
|
+
```
|
351
|
+
|
352
|
+
**Why this is needed**: OpenAI's structured output mode can prevent file download annotations from being generated. The two-pass strategy works around this by making two API calls: one to generate files (without structured output), then another to ensure schema compliance. For detailed technical information, see [docs/known-issues/2025-06-responses-ci-file-output.md](docs/known-issues/2025-06-responses-ci-file-output.md).
|
353
|
+
|
354
|
+
**Performance**: The two-pass strategy approximately doubles token usage but ensures reliable file downloads when using structured output with Code Interpreter.
|
355
|
+
|
326
356
|
## Get Started Quickly
|
327
357
|
|
328
358
|
🚀 **New to ostruct?** Follow our [step-by-step quickstart guide](https://ostruct.readthedocs.io/en/latest/user-guide/quickstart.html) featuring Juno the beagle for a hands-on introduction.
|
@@ -567,6 +597,60 @@ The registry file is stored at `~/.openai_structured/config/models.yml` and is a
|
|
567
597
|
|
568
598
|
The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
|
569
599
|
|
600
|
+
# Testing
|
601
|
+
|
602
|
+
## Running Tests
|
603
|
+
|
604
|
+
The test suite is divided into two categories:
|
605
|
+
|
606
|
+
### Regular Tests (Default)
|
607
|
+
|
608
|
+
```bash
|
609
|
+
# Run all tests (skips live tests by default)
|
610
|
+
pytest
|
611
|
+
|
612
|
+
# Run specific test file
|
613
|
+
pytest tests/test_config.py
|
614
|
+
|
615
|
+
# Run with verbose output
|
616
|
+
pytest -v
|
617
|
+
```
|
618
|
+
|
619
|
+
### Live Tests
|
620
|
+
|
621
|
+
Live tests make real API calls to OpenAI and require a valid API key. They are skipped by default.
|
622
|
+
|
623
|
+
```bash
|
624
|
+
# Run only live tests (requires OPENAI_API_KEY)
|
625
|
+
pytest -m live
|
626
|
+
|
627
|
+
# Run all tests including live tests
|
628
|
+
pytest -m "live or not live"
|
629
|
+
|
630
|
+
# Run specific live test
|
631
|
+
pytest tests/test_responses_annotations.py -m live
|
632
|
+
```
|
633
|
+
|
634
|
+
**Live tests include:**
|
635
|
+
|
636
|
+
- Tests that make actual OpenAI API calls
|
637
|
+
- Tests that run `ostruct` commands via subprocess
|
638
|
+
- Tests that verify real API behavior and file downloads
|
639
|
+
|
640
|
+
**Requirements for live tests:**
|
641
|
+
|
642
|
+
- Valid `OPENAI_API_KEY` environment variable
|
643
|
+
- Internet connection
|
644
|
+
- May incur API costs
|
645
|
+
|
646
|
+
## Test Markers
|
647
|
+
|
648
|
+
- `@pytest.mark.live` - Tests that make real API calls or run actual commands
|
649
|
+
- `@pytest.mark.no_fs` - Tests that need real filesystem (not pyfakefs)
|
650
|
+
- `@pytest.mark.slow` - Performance/stress tests
|
651
|
+
- `@pytest.mark.flaky` - Tests that may need reruns
|
652
|
+
- `@pytest.mark.mock_openai` - Tests using mocked OpenAI client
|
653
|
+
|
570
654
|
<!--
|
571
655
|
MAINTAINER NOTE: After editing this README, please test GitHub rendering by:
|
572
656
|
1. Creating a draft PR or pushing to a test branch
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "ostruct-cli"
|
7
|
-
version = "0.8.
|
7
|
+
version = "0.8.3"
|
8
8
|
description = "CLI for OpenAI Structured Output with Multi-Tool Integration"
|
9
9
|
authors = [{name = "Yaniv Golan", email = "yaniv@golan.name"}]
|
10
10
|
readme = "README.md"
|
@@ -33,6 +33,7 @@ ostruct = "ostruct.cli.cli:main"
|
|
33
33
|
[project.optional-dependencies]
|
34
34
|
dev = [
|
35
35
|
"pytest>=8.3.4,<9.0",
|
36
|
+
"pytest-rerunfailures>=12.0,<13.0",
|
36
37
|
"flake8>=6.0,<7.0",
|
37
38
|
"flake8-pyproject>=1.2.3,<2.0",
|
38
39
|
"black==24.8.0",
|
@@ -54,6 +55,7 @@ dev = [
|
|
54
55
|
"types-requests>=2.32.0.20241016",
|
55
56
|
"pre-commit>=4.1.0,<5.0",
|
56
57
|
"psutil>=7.0.0,<8.0",
|
58
|
+
"hypothesis>=6.0.0,<7.0",
|
57
59
|
]
|
58
60
|
docs = [
|
59
61
|
"sphinx>=7.0,<8.0",
|
@@ -111,12 +113,16 @@ include = ["py.typed"]
|
|
111
113
|
testpaths = ["tests"]
|
112
114
|
python_files = ["test_*.py"]
|
113
115
|
markers = [
|
114
|
-
"live: mark test as a live test that
|
116
|
+
"live: mark test as a live test that makes real API calls or runs actual ostruct commands",
|
115
117
|
"asyncio: mark test as requiring async loop",
|
116
|
-
"
|
117
|
-
"slow: mark test as slow performance/stress test"
|
118
|
+
"no_fs: mark test to disable pyfakefs and use real filesystem",
|
119
|
+
"slow: mark test as slow performance/stress test",
|
120
|
+
"flaky: mark test as flaky (may need reruns)",
|
121
|
+
"mock_openai: mark test to use mock OpenAI client"
|
118
122
|
]
|
119
123
|
asyncio_default_fixture_loop_scope = "function"
|
124
|
+
# By default, skip live tests unless explicitly requested
|
125
|
+
addopts = "-m 'not live'"
|
120
126
|
|
121
127
|
[tool.ruff]
|
122
128
|
target-version = "py310"
|
@@ -29,6 +29,56 @@ CommandDecorator = Callable[[F], Command]
|
|
29
29
|
DecoratedCommand = Union[Command, Callable[..., Any]]
|
30
30
|
|
31
31
|
|
32
|
+
def parse_feature_flags(
|
33
|
+
enabled_features: tuple[str, ...], disabled_features: tuple[str, ...]
|
34
|
+
) -> dict[str, str]:
|
35
|
+
"""Parse feature flags from CLI arguments.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
enabled_features: Tuple of feature names to enable
|
39
|
+
disabled_features: Tuple of feature names to disable
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
Dictionary mapping feature names to "on" or "off"
|
43
|
+
|
44
|
+
Raises:
|
45
|
+
click.BadParameter: If flag format is invalid or conflicts exist
|
46
|
+
"""
|
47
|
+
parsed = {}
|
48
|
+
|
49
|
+
# Process enabled features
|
50
|
+
for feature in enabled_features:
|
51
|
+
feature = feature.strip()
|
52
|
+
if not feature:
|
53
|
+
raise click.BadParameter("Feature name cannot be empty")
|
54
|
+
|
55
|
+
# Validate known feature flags
|
56
|
+
if feature == "ci-download-hack":
|
57
|
+
parsed[feature] = "on"
|
58
|
+
else:
|
59
|
+
raise click.BadParameter(f"Unknown feature: {feature}")
|
60
|
+
|
61
|
+
# Process disabled features
|
62
|
+
for feature in disabled_features:
|
63
|
+
feature = feature.strip()
|
64
|
+
if not feature:
|
65
|
+
raise click.BadParameter("Feature name cannot be empty")
|
66
|
+
|
67
|
+
# Check for conflicts
|
68
|
+
if feature in parsed:
|
69
|
+
raise click.BadParameter(
|
70
|
+
f"Feature '{feature}' cannot be both enabled and disabled"
|
71
|
+
)
|
72
|
+
|
73
|
+
# Validate known feature flags
|
74
|
+
if feature == "ci-download-hack":
|
75
|
+
parsed[feature] = "off"
|
76
|
+
else:
|
77
|
+
raise click.BadParameter(f"Unknown feature: {feature}")
|
78
|
+
|
79
|
+
return parsed
|
80
|
+
|
81
|
+
|
32
82
|
def debug_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
33
83
|
"""Add debug-related CLI options."""
|
34
84
|
# Initial conversion to Command if needed
|
@@ -573,6 +623,31 @@ def code_interpreter_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
573
623
|
help="""Clean up uploaded files after execution to save storage quota.""",
|
574
624
|
)(cmd)
|
575
625
|
|
626
|
+
# Feature flags for experimental features
|
627
|
+
cmd = click.option(
|
628
|
+
"--enable-feature",
|
629
|
+
"enabled_features",
|
630
|
+
multiple=True,
|
631
|
+
metavar="<FEATURE>",
|
632
|
+
help="""🔧 [EXPERIMENTAL] Enable experimental features.
|
633
|
+
Available features:
|
634
|
+
• ci-download-hack - Enable two-pass sentinel mode for reliable Code Interpreter
|
635
|
+
file downloads with structured output. Overrides config file setting.
|
636
|
+
Example: --enable-feature ci-download-hack""",
|
637
|
+
)(cmd)
|
638
|
+
|
639
|
+
cmd = click.option(
|
640
|
+
"--disable-feature",
|
641
|
+
"disabled_features",
|
642
|
+
multiple=True,
|
643
|
+
metavar="<FEATURE>",
|
644
|
+
help="""🔧 [EXPERIMENTAL] Disable experimental features.
|
645
|
+
Available features:
|
646
|
+
• ci-download-hack - Force single-pass mode for Code Interpreter downloads.
|
647
|
+
Overrides config file setting.
|
648
|
+
Example: --disable-feature ci-download-hack""",
|
649
|
+
)(cmd)
|
650
|
+
|
576
651
|
return cast(Command, cmd)
|
577
652
|
|
578
653
|
|
@@ -685,13 +760,17 @@ def web_search_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
685
760
|
is_flag=True,
|
686
761
|
help="""🌐 [WEB SEARCH] Enable OpenAI web search tool for up-to-date information.
|
687
762
|
Allows the model to search the web for current events, recent updates, and real-time data.
|
688
|
-
Note: Search queries may be sent to external services via OpenAI.
|
763
|
+
Note: Search queries may be sent to external services via OpenAI.
|
764
|
+
|
765
|
+
⚠️ DEPRECATED: Use --enable-tool web-search instead. Will be removed in v0.9.0.""",
|
689
766
|
)(cmd)
|
690
767
|
|
691
768
|
cmd = click.option(
|
692
769
|
"--no-web-search",
|
693
770
|
is_flag=True,
|
694
|
-
help="""Explicitly disable web search even if enabled by default in configuration.
|
771
|
+
help="""Explicitly disable web search even if enabled by default in configuration.
|
772
|
+
|
773
|
+
⚠️ DEPRECATED: Use --disable-tool web-search instead. Will be removed in v0.9.0.""",
|
695
774
|
)(cmd)
|
696
775
|
|
697
776
|
cmd = click.option(
|
@@ -725,6 +804,35 @@ def web_search_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
725
804
|
return cast(Command, cmd)
|
726
805
|
|
727
806
|
|
807
|
+
def tool_toggle_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
808
|
+
"""Add universal tool toggle CLI options."""
|
809
|
+
cmd: Any = f if isinstance(f, Command) else f
|
810
|
+
|
811
|
+
cmd = click.option(
|
812
|
+
"--enable-tool",
|
813
|
+
"enabled_tools",
|
814
|
+
multiple=True,
|
815
|
+
metavar="<TOOL>",
|
816
|
+
help="""🔧 [TOOL TOGGLES] Enable a tool for this run (repeatable).
|
817
|
+
Overrides configuration file and implicit activation.
|
818
|
+
Available tools: code-interpreter, file-search, web-search, mcp
|
819
|
+
Example: --enable-tool code-interpreter --enable-tool web-search""",
|
820
|
+
)(cmd)
|
821
|
+
|
822
|
+
cmd = click.option(
|
823
|
+
"--disable-tool",
|
824
|
+
"disabled_tools",
|
825
|
+
multiple=True,
|
826
|
+
metavar="<TOOL>",
|
827
|
+
help="""🔧 [TOOL TOGGLES] Disable a tool for this run (repeatable).
|
828
|
+
Overrides configuration file and implicit activation.
|
829
|
+
Available tools: code-interpreter, file-search, web-search, mcp
|
830
|
+
Example: --disable-tool web-search --disable-tool mcp""",
|
831
|
+
)(cmd)
|
832
|
+
|
833
|
+
return cast(Command, cmd)
|
834
|
+
|
835
|
+
|
728
836
|
def debug_progress_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
729
837
|
"""Add debugging and progress CLI options."""
|
730
838
|
cmd: Any = f if isinstance(f, Command) else f
|
@@ -746,12 +854,6 @@ def debug_progress_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
746
854
|
"--verbose", is_flag=True, help="Enable verbose logging"
|
747
855
|
)(cmd)
|
748
856
|
|
749
|
-
cmd = click.option(
|
750
|
-
"--debug-openai-stream",
|
751
|
-
is_flag=True,
|
752
|
-
help="Debug OpenAI streaming process",
|
753
|
-
)(cmd)
|
754
|
-
|
755
857
|
cmd = click.option(
|
756
858
|
"--timeout",
|
757
859
|
type=int,
|
@@ -777,6 +879,7 @@ def all_options(f: Union[Command, Callable[..., Any]]) -> Command:
|
|
777
879
|
cmd = code_interpreter_options(cmd)
|
778
880
|
cmd = file_search_options(cmd)
|
779
881
|
cmd = web_search_options(cmd)
|
882
|
+
cmd = tool_toggle_options(cmd)
|
780
883
|
cmd = debug_options(cmd)
|
781
884
|
cmd = debug_progress_options(cmd)
|
782
885
|
|