ostruct-cli 0.8.29__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ostruct/cli/__init__.py +3 -15
- ostruct/cli/attachment_processor.py +455 -0
- ostruct/cli/attachment_template_bridge.py +973 -0
- ostruct/cli/cli.py +157 -33
- ostruct/cli/click_options.py +775 -692
- ostruct/cli/code_interpreter.py +195 -12
- ostruct/cli/commands/__init__.py +0 -3
- ostruct/cli/commands/run.py +289 -62
- ostruct/cli/config.py +23 -22
- ostruct/cli/constants.py +89 -0
- ostruct/cli/errors.py +175 -5
- ostruct/cli/explicit_file_processor.py +0 -15
- ostruct/cli/file_info.py +97 -15
- ostruct/cli/file_list.py +43 -1
- ostruct/cli/file_search.py +68 -2
- ostruct/cli/help_json.py +235 -0
- ostruct/cli/mcp_integration.py +13 -16
- ostruct/cli/params.py +217 -0
- ostruct/cli/plan_assembly.py +335 -0
- ostruct/cli/plan_printing.py +385 -0
- ostruct/cli/progress_reporting.py +8 -56
- ostruct/cli/quick_ref_help.py +128 -0
- ostruct/cli/rich_config.py +299 -0
- ostruct/cli/runner.py +397 -190
- ostruct/cli/security/__init__.py +2 -0
- ostruct/cli/security/allowed_checker.py +41 -0
- ostruct/cli/security/normalization.py +13 -9
- ostruct/cli/security/security_manager.py +558 -17
- ostruct/cli/security/types.py +15 -0
- ostruct/cli/template_debug.py +283 -261
- ostruct/cli/template_debug_help.py +233 -142
- ostruct/cli/template_env.py +46 -5
- ostruct/cli/template_filters.py +415 -8
- ostruct/cli/template_processor.py +240 -619
- ostruct/cli/template_rendering.py +49 -73
- ostruct/cli/template_validation.py +2 -1
- ostruct/cli/token_validation.py +35 -15
- ostruct/cli/types.py +15 -19
- ostruct/cli/unicode_compat.py +283 -0
- ostruct/cli/upload_manager.py +448 -0
- ostruct/cli/validators.py +255 -54
- {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.1.dist-info}/METADATA +231 -128
- ostruct_cli-1.0.1.dist-info/RECORD +80 -0
- ostruct/cli/commands/quick_ref.py +0 -54
- ostruct/cli/template_optimizer.py +0 -478
- ostruct_cli-0.8.29.dist-info/RECORD +0 -71
- {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.1.dist-info}/LICENSE +0 -0
- {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.1.dist-info}/WHEEL +0 -0
- {ostruct_cli-0.8.29.dist-info → ostruct_cli-1.0.1.dist-info}/entry_points.txt +0 -0
@@ -3,14 +3,14 @@
|
|
3
3
|
import json
|
4
4
|
import logging
|
5
5
|
import re
|
6
|
-
import sys
|
7
6
|
from pathlib import Path
|
8
|
-
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
7
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
9
8
|
|
10
9
|
import click
|
11
10
|
import jinja2
|
12
11
|
import yaml
|
13
12
|
|
13
|
+
from .constants import DefaultConfig
|
14
14
|
from .errors import (
|
15
15
|
DirectoryNotFoundError,
|
16
16
|
DuplicateFileMappingError,
|
@@ -23,101 +23,44 @@ from .errors import (
|
|
23
23
|
VariableNameError,
|
24
24
|
)
|
25
25
|
from .explicit_file_processor import ProcessingResult
|
26
|
-
from .
|
27
|
-
from .file_utils import FileInfoList, collect_files
|
26
|
+
from .file_utils import FileInfoList
|
28
27
|
from .path_utils import validate_path_mapping
|
29
28
|
from .security import SecurityManager
|
30
|
-
from .
|
31
|
-
|
32
|
-
|
29
|
+
from .template_debug import (
|
30
|
+
TDCap,
|
31
|
+
is_capacity_active,
|
32
|
+
td_log_if_active,
|
33
|
+
td_log_preview,
|
34
|
+
td_log_vars,
|
33
35
|
)
|
34
36
|
from .template_utils import render_template
|
35
37
|
from .types import CLIParams
|
36
38
|
|
37
39
|
logger = logging.getLogger(__name__)
|
38
40
|
|
39
|
-
DEFAULT_SYSTEM_PROMPT =
|
41
|
+
DEFAULT_SYSTEM_PROMPT = DefaultConfig.TEMPLATE["system_prompt"]
|
40
42
|
|
41
43
|
|
42
44
|
def _render_template_with_debug(
|
43
45
|
template_content: str,
|
44
46
|
context: Dict[str, Any],
|
45
47
|
env: jinja2.Environment,
|
46
|
-
|
47
|
-
show_optimization_diff: bool = False,
|
48
|
-
show_optimization_steps: bool = False,
|
49
|
-
optimization_step_detail: str = "summary",
|
48
|
+
debug_capacities: Optional[Set[TDCap]] = None,
|
50
49
|
) -> str:
|
51
|
-
"""Render template with
|
50
|
+
"""Render template with debugging support.
|
52
51
|
|
53
52
|
Args:
|
54
53
|
template_content: Template content to render
|
55
54
|
context: Template context variables
|
56
55
|
env: Jinja2 environment
|
57
|
-
|
58
|
-
show_optimization_diff: Show before/after optimization comparison
|
59
|
-
show_optimization_steps: Show detailed optimization step tracking
|
60
|
-
optimization_step_detail: Level of detail for optimization steps
|
56
|
+
debug_capacities: Set of active debug capacities (unused but kept for compatibility)
|
61
57
|
|
62
58
|
Returns:
|
63
59
|
Rendered template string
|
64
60
|
"""
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
# Skip optimization entirely - render directly
|
69
|
-
template = env.from_string(template_content)
|
70
|
-
return template.render(**context)
|
71
|
-
|
72
|
-
# Handle optimization debugging (diff and/or steps)
|
73
|
-
if show_optimization_diff or show_optimization_steps:
|
74
|
-
# Check if optimization would be beneficial
|
75
|
-
if is_optimization_beneficial(template_content):
|
76
|
-
# Create step tracker if step tracking is enabled
|
77
|
-
step_tracker = None
|
78
|
-
if show_optimization_steps:
|
79
|
-
from .template_debug import OptimizationStepTracker
|
80
|
-
|
81
|
-
step_tracker = OptimizationStepTracker(enabled=True)
|
82
|
-
|
83
|
-
# Get optimization result with optional step tracking
|
84
|
-
optimization_result = optimize_template_for_llm(
|
85
|
-
template_content, step_tracker
|
86
|
-
)
|
87
|
-
|
88
|
-
if optimization_result.has_optimizations:
|
89
|
-
# Show the diff if requested
|
90
|
-
if show_optimization_diff:
|
91
|
-
show_diff(
|
92
|
-
template_content,
|
93
|
-
optimization_result.optimized_template,
|
94
|
-
)
|
95
|
-
|
96
|
-
# Show optimization steps if requested
|
97
|
-
if show_optimization_steps and step_tracker:
|
98
|
-
if optimization_step_detail == "detailed":
|
99
|
-
step_tracker.show_detailed_steps()
|
100
|
-
else:
|
101
|
-
step_tracker.show_step_summary()
|
102
|
-
|
103
|
-
# Render the optimized version
|
104
|
-
template = env.from_string(
|
105
|
-
optimization_result.optimized_template
|
106
|
-
)
|
107
|
-
return template.render(**context)
|
108
|
-
|
109
|
-
# No optimization was applied, show that too
|
110
|
-
if show_optimization_diff:
|
111
|
-
show_diff(template_content, template_content)
|
112
|
-
if show_optimization_steps:
|
113
|
-
from .template_debug import (
|
114
|
-
show_optimization_steps as show_steps_func,
|
115
|
-
)
|
116
|
-
|
117
|
-
show_steps_func([], optimization_step_detail)
|
118
|
-
|
119
|
-
# Fall back to standard rendering (which includes optimization)
|
120
|
-
return render_template(template_content, context, env)
|
61
|
+
# Simple template rendering without optimization
|
62
|
+
template = env.from_string(template_content)
|
63
|
+
return template.render(**context)
|
121
64
|
|
122
65
|
|
123
66
|
def process_system_prompt(
|
@@ -128,7 +71,7 @@ def process_system_prompt(
|
|
128
71
|
env: jinja2.Environment,
|
129
72
|
ignore_task_sysprompt: bool = False,
|
130
73
|
template_path: Optional[str] = None,
|
131
|
-
) -> str:
|
74
|
+
) -> Tuple[str, bool]:
|
132
75
|
"""Process system prompt from various sources.
|
133
76
|
|
134
77
|
Args:
|
@@ -141,7 +84,7 @@ def process_system_prompt(
|
|
141
84
|
template_path: Optional path to template file for include_system resolution
|
142
85
|
|
143
86
|
Returns:
|
144
|
-
|
87
|
+
Tuple of (final system prompt string, template_has_system_prompt)
|
145
88
|
|
146
89
|
Raises:
|
147
90
|
SystemPromptError: If the system prompt cannot be loaded or rendered
|
@@ -156,6 +99,27 @@ def process_system_prompt(
|
|
156
99
|
|
157
100
|
# CLI system prompt takes precedence and stops further processing
|
158
101
|
if system_prompt_file is not None:
|
102
|
+
# Check for conflict with YAML frontmatter system_prompt and warn
|
103
|
+
template_has_system_prompt = False
|
104
|
+
if task_template.startswith("---\n"):
|
105
|
+
end = task_template.find("\n---\n", 4)
|
106
|
+
if end != -1:
|
107
|
+
frontmatter = task_template[4:end]
|
108
|
+
try:
|
109
|
+
metadata = yaml.safe_load(frontmatter)
|
110
|
+
if (
|
111
|
+
isinstance(metadata, dict)
|
112
|
+
and "system_prompt" in metadata
|
113
|
+
):
|
114
|
+
template_has_system_prompt = True
|
115
|
+
logger.warning(
|
116
|
+
"Template has YAML frontmatter with 'system_prompt' field, but --sys-file was also provided. "
|
117
|
+
"Using --sys-file and ignoring YAML frontmatter system_prompt."
|
118
|
+
)
|
119
|
+
except yaml.YAMLError:
|
120
|
+
# If YAML is invalid, we'll catch it later in template processing
|
121
|
+
pass
|
122
|
+
|
159
123
|
try:
|
160
124
|
name, path = validate_path_mapping(
|
161
125
|
f"system_prompt={system_prompt_file}"
|
@@ -175,6 +139,9 @@ def process_system_prompt(
|
|
175
139
|
except jinja2.TemplateError as e:
|
176
140
|
raise SystemPromptError(f"Error rendering system prompt: {e}")
|
177
141
|
|
142
|
+
# Return the warning information along with the prompt
|
143
|
+
return base_prompt, template_has_system_prompt
|
144
|
+
|
178
145
|
elif system_prompt is not None:
|
179
146
|
try:
|
180
147
|
template = env.from_string(system_prompt)
|
@@ -289,7 +256,7 @@ def process_system_prompt(
|
|
289
256
|
|
290
257
|
base_prompt += ci_download_instructions
|
291
258
|
|
292
|
-
return base_prompt
|
259
|
+
return base_prompt, False
|
293
260
|
|
294
261
|
|
295
262
|
def validate_task_template(
|
@@ -362,133 +329,63 @@ async def process_templates(
|
|
362
329
|
env: jinja2.Environment,
|
363
330
|
template_path: Optional[str] = None,
|
364
331
|
) -> Tuple[str, str]:
|
365
|
-
"""Process system
|
332
|
+
"""Process system and user prompt templates.
|
366
333
|
|
367
334
|
Args:
|
368
|
-
args:
|
369
|
-
task_template:
|
370
|
-
template_context: Template context
|
371
|
-
env: Jinja2 environment
|
335
|
+
args: CLI parameters
|
336
|
+
task_template: Task template content
|
337
|
+
template_context: Template context variables
|
338
|
+
env: Jinja2 environment with file reference support already configured
|
339
|
+
template_path: Path to template file (for debugging)
|
372
340
|
|
373
341
|
Returns:
|
374
342
|
Tuple of (system_prompt, user_prompt)
|
375
343
|
|
376
344
|
Raises:
|
377
|
-
|
345
|
+
TemplateError: If template processing fails
|
346
|
+
ValidationError: If template validation fails
|
378
347
|
"""
|
379
|
-
|
348
|
+
# Show original template content if PRE_EXPAND capacity is active
|
349
|
+
td_log_if_active(TDCap.PRE_EXPAND, "--- original template content ---")
|
350
|
+
td_log_if_active(TDCap.PRE_EXPAND, task_template)
|
351
|
+
td_log_if_active(TDCap.PRE_EXPAND, "--- end original template ---")
|
380
352
|
|
381
|
-
#
|
353
|
+
# Check for debug mode
|
382
354
|
debug_enabled = args.get("debug", False)
|
383
|
-
debug_templates_enabled = args.get("debug_templates", False)
|
384
|
-
show_context = args.get("show_context", False)
|
385
|
-
show_context_detailed = args.get("show_context_detailed", False)
|
386
|
-
show_pre_optimization = args.get("show_pre_optimization", False)
|
387
|
-
show_optimization_diff = args.get("show_optimization_diff", False)
|
388
|
-
no_optimization = args.get("no_optimization", False)
|
389
|
-
show_optimization_steps = args.get("show_optimization_steps", False)
|
390
|
-
optimization_step_detail = args.get("optimization_step_detail", "summary")
|
391
|
-
|
392
355
|
debugger = None
|
393
|
-
if debug_enabled or debug_templates_enabled:
|
394
|
-
from .template_debug import (
|
395
|
-
TemplateDebugger,
|
396
|
-
log_template_expansion,
|
397
|
-
show_file_content_expansions,
|
398
|
-
)
|
399
|
-
|
400
|
-
# Initialize template debugger
|
401
|
-
debugger = TemplateDebugger(enabled=True)
|
402
|
-
|
403
|
-
# Log template context
|
404
|
-
show_file_content_expansions(template_context)
|
405
356
|
|
406
|
-
|
407
|
-
|
408
|
-
logger.debug(task_template)
|
357
|
+
if debug_enabled or is_capacity_active(TDCap.POST_EXPAND):
|
358
|
+
from .template_debug import TemplateDebugger
|
409
359
|
|
410
|
-
|
411
|
-
debugger.log_expansion_step(
|
412
|
-
"Initial template loaded",
|
413
|
-
"",
|
414
|
-
task_template,
|
415
|
-
{"template_path": template_path},
|
416
|
-
)
|
417
|
-
|
418
|
-
# Show context inspection if requested
|
419
|
-
if show_context or show_context_detailed:
|
420
|
-
from .template_debug import (
|
421
|
-
display_context_detailed,
|
422
|
-
display_context_summary,
|
423
|
-
)
|
424
|
-
|
425
|
-
if show_context_detailed:
|
426
|
-
display_context_detailed(template_context)
|
427
|
-
elif show_context:
|
428
|
-
display_context_summary(template_context)
|
429
|
-
|
430
|
-
# Check for undefined variables if context inspection is enabled
|
431
|
-
from .template_debug import detect_undefined_variables
|
432
|
-
|
433
|
-
undefined_vars = detect_undefined_variables(
|
434
|
-
task_template, template_context
|
435
|
-
)
|
436
|
-
if undefined_vars:
|
437
|
-
click.echo(
|
438
|
-
f"⚠️ Potentially undefined variables: {', '.join(undefined_vars)}",
|
439
|
-
err=True,
|
440
|
-
)
|
441
|
-
click.echo(
|
442
|
-
f" Available variables: {', '.join(sorted(template_context.keys()))}",
|
443
|
-
err=True,
|
444
|
-
)
|
360
|
+
debugger = TemplateDebugger()
|
445
361
|
|
446
|
-
|
362
|
+
# System prompt processing
|
363
|
+
system_prompt, _ = process_system_prompt(
|
447
364
|
task_template,
|
448
365
|
args.get("system_prompt"),
|
449
366
|
args.get("system_prompt_file"),
|
450
367
|
template_context,
|
451
368
|
env,
|
452
|
-
args.get("ignore_task_sysprompt", False),
|
453
|
-
template_path,
|
369
|
+
ignore_task_sysprompt=args.get("ignore_task_sysprompt", False),
|
370
|
+
template_path=template_path,
|
454
371
|
)
|
455
372
|
|
456
|
-
#
|
457
|
-
|
458
|
-
debugger.log_expansion_step(
|
459
|
-
"System prompt processed",
|
460
|
-
task_template,
|
461
|
-
system_prompt,
|
462
|
-
{
|
463
|
-
"system_prompt_source": (
|
464
|
-
"task_template"
|
465
|
-
if not args.get("system_prompt")
|
466
|
-
else "custom"
|
467
|
-
)
|
468
|
-
},
|
469
|
-
)
|
373
|
+
# Render user prompt template
|
374
|
+
user_prompt = render_template(task_template, template_context, env)
|
470
375
|
|
471
|
-
#
|
472
|
-
|
473
|
-
|
376
|
+
# Generate XML appendix for referenced files if alias manager is available
|
377
|
+
alias_manager = args.get("_alias_manager")
|
378
|
+
if alias_manager:
|
379
|
+
from .template_filters import AliasManager, XMLAppendixBuilder
|
474
380
|
|
475
|
-
|
381
|
+
# Type assertion since we know this is an AliasManager
|
382
|
+
assert isinstance(alias_manager, AliasManager)
|
383
|
+
appendix_builder = XMLAppendixBuilder(alias_manager)
|
384
|
+
appendix_content = appendix_builder.build_appendix()
|
476
385
|
|
477
|
-
#
|
478
|
-
|
479
|
-
|
480
|
-
user_prompt = _render_template_with_debug(
|
481
|
-
task_template,
|
482
|
-
template_context,
|
483
|
-
env,
|
484
|
-
no_optimization=bool(no_optimization),
|
485
|
-
show_optimization_diff=bool(show_optimization_diff),
|
486
|
-
show_optimization_steps=bool(show_optimization_steps),
|
487
|
-
optimization_step_detail=str(optimization_step_detail),
|
488
|
-
)
|
489
|
-
else:
|
490
|
-
# Standard rendering with optimization
|
491
|
-
user_prompt = render_template(task_template, template_context, env)
|
386
|
+
# Append XML content if any aliases were referenced
|
387
|
+
if appendix_content:
|
388
|
+
user_prompt = user_prompt + "\n\n" + appendix_content
|
492
389
|
|
493
390
|
# Log user prompt rendering step
|
494
391
|
if debugger:
|
@@ -499,16 +396,23 @@ async def process_templates(
|
|
499
396
|
template_context,
|
500
397
|
)
|
501
398
|
|
399
|
+
# Add post-expand logging
|
400
|
+
td_log_if_active(TDCap.POST_EXPAND, "--- prompt 1/2 (system) ---")
|
401
|
+
td_log_if_active(TDCap.POST_EXPAND, system_prompt)
|
402
|
+
td_log_if_active(TDCap.POST_EXPAND, "--- prompt 2/2 (user) ---")
|
403
|
+
td_log_if_active(TDCap.POST_EXPAND, user_prompt)
|
404
|
+
|
502
405
|
# Log template expansion if debug enabled
|
503
|
-
if debug_enabled or
|
406
|
+
if debug_enabled or is_capacity_active(TDCap.STEPS):
|
504
407
|
from .template_debug import log_template_expansion
|
505
408
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
409
|
+
if debug_enabled:
|
410
|
+
log_template_expansion(
|
411
|
+
template_content=task_template,
|
412
|
+
context=template_context,
|
413
|
+
expanded=user_prompt,
|
414
|
+
template_file=template_path,
|
415
|
+
)
|
512
416
|
|
513
417
|
# Show expansion summary and detailed steps
|
514
418
|
if debugger:
|
@@ -615,78 +519,6 @@ def collect_json_variables(args: CLIParams) -> Dict[str, Any]:
|
|
615
519
|
return variables
|
616
520
|
|
617
521
|
|
618
|
-
def collect_template_files(
|
619
|
-
args: CLIParams,
|
620
|
-
security_manager: SecurityManager,
|
621
|
-
) -> Dict[str, Union[FileInfoList, str, List[str], Dict[str, str]]]:
|
622
|
-
"""Collect files from command line arguments.
|
623
|
-
|
624
|
-
Args:
|
625
|
-
args: Command line arguments
|
626
|
-
security_manager: Security manager for path validation
|
627
|
-
|
628
|
-
Returns:
|
629
|
-
Dictionary mapping variable names to file info objects
|
630
|
-
|
631
|
-
Raises:
|
632
|
-
PathSecurityError: If any file paths violate security constraints
|
633
|
-
ValueError: If file mappings are invalid or files cannot be accessed
|
634
|
-
"""
|
635
|
-
try:
|
636
|
-
# Get files, directories, and patterns from args - they are already tuples from Click's nargs=2
|
637
|
-
files = list(
|
638
|
-
args.get("files", [])
|
639
|
-
) # List of (name, path) tuples from Click
|
640
|
-
dirs = args.get("dir", []) # List of (name, dir) tuples from Click
|
641
|
-
patterns = args.get(
|
642
|
-
"patterns", []
|
643
|
-
) # List of (name, pattern) tuples from Click
|
644
|
-
|
645
|
-
# Collect files from directories and patterns
|
646
|
-
dir_files = collect_files(
|
647
|
-
file_mappings=cast(List[Tuple[str, Union[str, Path]]], files),
|
648
|
-
dir_mappings=cast(List[Tuple[str, Union[str, Path]]], dirs),
|
649
|
-
pattern_mappings=cast(
|
650
|
-
List[Tuple[str, Union[str, Path]]], patterns
|
651
|
-
),
|
652
|
-
dir_recursive=args.get("recursive", False),
|
653
|
-
security_manager=security_manager,
|
654
|
-
routing_type="template", # Indicate these are primarily for template access
|
655
|
-
)
|
656
|
-
|
657
|
-
# Combine results
|
658
|
-
return cast(
|
659
|
-
Dict[str, Union[FileInfoList, str, List[str], Dict[str, str]]],
|
660
|
-
dir_files,
|
661
|
-
)
|
662
|
-
|
663
|
-
except Exception as e:
|
664
|
-
# Check for nested security errors
|
665
|
-
if hasattr(e, "__cause__") and hasattr(e.__cause__, "__class__"):
|
666
|
-
if "SecurityError" in str(e.__cause__.__class__) and isinstance(
|
667
|
-
e.__cause__, BaseException
|
668
|
-
):
|
669
|
-
raise e.__cause__
|
670
|
-
if "PathSecurityError" in str(
|
671
|
-
e.__cause__.__class__
|
672
|
-
) and isinstance(e.__cause__, BaseException):
|
673
|
-
raise e.__cause__
|
674
|
-
# Check if this is a wrapped security error
|
675
|
-
if isinstance(e.__cause__, PathSecurityError):
|
676
|
-
raise e.__cause__
|
677
|
-
# Don't wrap InvalidJSONError
|
678
|
-
if isinstance(e, InvalidJSONError):
|
679
|
-
raise
|
680
|
-
# Don't wrap DuplicateFileMappingError
|
681
|
-
if isinstance(e, DuplicateFileMappingError):
|
682
|
-
raise
|
683
|
-
# Catch broader exceptions and re-raise
|
684
|
-
logger.error(
|
685
|
-
"Error collecting template files: %s", str(e), exc_info=True
|
686
|
-
)
|
687
|
-
raise
|
688
|
-
|
689
|
-
|
690
522
|
def extract_template_file_paths(template_context: Dict[str, Any]) -> List[str]:
|
691
523
|
"""Extract actual file paths from template context for token validation.
|
692
524
|
|
@@ -746,6 +578,89 @@ def create_template_context(
|
|
746
578
|
return context
|
747
579
|
|
748
580
|
|
581
|
+
def _build_tool_context(
|
582
|
+
args: CLIParams, routing_result: ProcessingResult
|
583
|
+
) -> Dict[str, Any]:
|
584
|
+
"""Build tool-related context variables.
|
585
|
+
|
586
|
+
Args:
|
587
|
+
args: Command line arguments
|
588
|
+
routing_result: File routing result
|
589
|
+
|
590
|
+
Returns:
|
591
|
+
Dictionary with tool enablement and configuration context
|
592
|
+
"""
|
593
|
+
from .config import OstructConfig
|
594
|
+
|
595
|
+
context: Dict[str, Any] = {}
|
596
|
+
|
597
|
+
# Load configuration
|
598
|
+
config_path = args.get("config")
|
599
|
+
config = OstructConfig.load(config_path) # type: ignore[arg-type]
|
600
|
+
|
601
|
+
# Universal tool toggle overrides
|
602
|
+
enabled_tools: set[str] = args.get("_enabled_tools", set()) # type: ignore[assignment]
|
603
|
+
disabled_tools: set[str] = args.get("_disabled_tools", set()) # type: ignore[assignment]
|
604
|
+
|
605
|
+
# Web search configuration
|
606
|
+
web_search_config = config.get_web_search_config()
|
607
|
+
|
608
|
+
if "web-search" in enabled_tools:
|
609
|
+
web_search_enabled = True
|
610
|
+
elif "web-search" in disabled_tools:
|
611
|
+
web_search_enabled = False
|
612
|
+
else:
|
613
|
+
web_search_enabled = web_search_config.enable_by_default
|
614
|
+
|
615
|
+
context["web_search_enabled"] = web_search_enabled
|
616
|
+
|
617
|
+
# Code interpreter configuration
|
618
|
+
ci_enabled_by_routing = "code-interpreter" in routing_result.enabled_tools
|
619
|
+
|
620
|
+
if "code-interpreter" in enabled_tools:
|
621
|
+
code_interpreter_enabled = True
|
622
|
+
elif "code-interpreter" in disabled_tools:
|
623
|
+
code_interpreter_enabled = False
|
624
|
+
else:
|
625
|
+
code_interpreter_enabled = ci_enabled_by_routing
|
626
|
+
|
627
|
+
context["code_interpreter_enabled"] = code_interpreter_enabled
|
628
|
+
|
629
|
+
# Add CI config if enabled
|
630
|
+
if code_interpreter_enabled:
|
631
|
+
ci_config = config.get_code_interpreter_config()
|
632
|
+
context["auto_download_enabled"] = ci_config.get("auto_download", True)
|
633
|
+
|
634
|
+
# Handle feature flags for CI config
|
635
|
+
effective_ci_config = dict(ci_config)
|
636
|
+
enabled_features = args.get("enabled_features", [])
|
637
|
+
disabled_features = args.get("disabled_features", [])
|
638
|
+
|
639
|
+
if enabled_features or disabled_features:
|
640
|
+
try:
|
641
|
+
from .click_options import parse_feature_flags
|
642
|
+
|
643
|
+
parsed_flags = parse_feature_flags(
|
644
|
+
tuple(enabled_features), tuple(disabled_features)
|
645
|
+
)
|
646
|
+
ci_hack_flag = parsed_flags.get("ci-download-hack")
|
647
|
+
if ci_hack_flag == "on":
|
648
|
+
effective_ci_config["download_strategy"] = (
|
649
|
+
"two_pass_sentinel"
|
650
|
+
)
|
651
|
+
elif ci_hack_flag == "off":
|
652
|
+
effective_ci_config["download_strategy"] = "single_pass"
|
653
|
+
except Exception as e:
|
654
|
+
logger.warning(f"Failed to parse feature flags: {e}")
|
655
|
+
|
656
|
+
context["code_interpreter_config"] = effective_ci_config
|
657
|
+
else:
|
658
|
+
context["auto_download_enabled"] = False
|
659
|
+
context["code_interpreter_config"] = {}
|
660
|
+
|
661
|
+
return context
|
662
|
+
|
663
|
+
|
749
664
|
def _generate_template_variable_name(file_path: str) -> str:
|
750
665
|
"""Generate a template variable name from a file path.
|
751
666
|
|
@@ -796,393 +711,99 @@ async def create_template_context_from_routing(
|
|
796
711
|
ValueError: If file mappings are invalid or files cannot be accessed
|
797
712
|
"""
|
798
713
|
try:
|
799
|
-
#
|
800
|
-
|
801
|
-
|
802
|
-
|
714
|
+
# Template context creation from new attachment system (T3.1)
|
715
|
+
logger.debug("Creating template context from new attachment system")
|
716
|
+
|
717
|
+
from .attachment_processor import (
|
718
|
+
AttachmentProcessor,
|
719
|
+
ProcessedAttachments,
|
720
|
+
_extract_attachments_from_args,
|
721
|
+
_has_new_attachment_syntax,
|
803
722
|
)
|
804
|
-
|
805
|
-
|
723
|
+
from .attachment_template_bridge import (
|
724
|
+
build_template_context_from_attachments,
|
806
725
|
)
|
807
726
|
|
808
|
-
#
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
FileRoutingIntent.FILE_SEARCH: [],
|
819
|
-
}
|
820
|
-
|
821
|
-
dirs_by_intent: Dict[FileRoutingIntent, List[Tuple[str, str]]] = {
|
822
|
-
FileRoutingIntent.TEMPLATE_ONLY: [],
|
823
|
-
FileRoutingIntent.CODE_INTERPRETER: [],
|
824
|
-
FileRoutingIntent.FILE_SEARCH: [],
|
825
|
-
}
|
826
|
-
|
827
|
-
# Track processed files to avoid duplicates between CLI args and routing result
|
828
|
-
seen_files: Set[str] = set()
|
829
|
-
|
830
|
-
# Categorize files by their source and assign appropriate intent
|
831
|
-
# Template files from CLI args
|
832
|
-
template_file_paths = args.get("template_files", [])
|
833
|
-
for template_file_path in template_file_paths:
|
834
|
-
if isinstance(template_file_path, (str, Path)):
|
835
|
-
file_name = _generate_template_variable_name(
|
836
|
-
str(template_file_path)
|
837
|
-
)
|
838
|
-
file_path_str = str(template_file_path)
|
839
|
-
if file_path_str not in seen_files:
|
840
|
-
files_by_intent[FileRoutingIntent.TEMPLATE_ONLY].append(
|
841
|
-
(file_name, file_path_str)
|
842
|
-
)
|
843
|
-
seen_files.add(file_path_str)
|
844
|
-
|
845
|
-
# Template file aliases from CLI args
|
846
|
-
template_file_aliases = args.get("template_file_aliases", [])
|
847
|
-
for name_path_tuple in template_file_aliases:
|
848
|
-
if (
|
849
|
-
isinstance(name_path_tuple, tuple)
|
850
|
-
and len(name_path_tuple) == 2
|
851
|
-
):
|
852
|
-
custom_name, file_path_raw = name_path_tuple
|
853
|
-
file_path_str = str(file_path_raw)
|
854
|
-
if file_path_str not in seen_files:
|
855
|
-
files_by_intent[FileRoutingIntent.TEMPLATE_ONLY].append(
|
856
|
-
(str(custom_name), file_path_str)
|
857
|
-
)
|
858
|
-
seen_files.add(file_path_str)
|
859
|
-
|
860
|
-
# Code interpreter files from CLI args
|
861
|
-
code_interpreter_file_paths = args.get("code_interpreter_files", [])
|
862
|
-
for ci_file_path in code_interpreter_file_paths:
|
863
|
-
if isinstance(ci_file_path, (str, Path)):
|
864
|
-
file_name = _generate_template_variable_name(str(ci_file_path))
|
865
|
-
file_path_str = str(ci_file_path)
|
866
|
-
if file_path_str not in seen_files:
|
867
|
-
files_by_intent[FileRoutingIntent.CODE_INTERPRETER].append(
|
868
|
-
(file_name, file_path_str)
|
869
|
-
)
|
870
|
-
seen_files.add(file_path_str)
|
871
|
-
|
872
|
-
# Code interpreter file aliases from CLI args
|
873
|
-
code_interpreter_file_aliases = args.get(
|
874
|
-
"code_interpreter_file_aliases", []
|
875
|
-
)
|
876
|
-
for name_path_tuple in code_interpreter_file_aliases:
|
877
|
-
if (
|
878
|
-
isinstance(name_path_tuple, tuple)
|
879
|
-
and len(name_path_tuple) == 2
|
880
|
-
):
|
881
|
-
custom_name, file_path_raw = name_path_tuple
|
882
|
-
file_path_str = str(file_path_raw)
|
883
|
-
if file_path_str not in seen_files:
|
884
|
-
files_by_intent[FileRoutingIntent.CODE_INTERPRETER].append(
|
885
|
-
(str(custom_name), file_path_str)
|
886
|
-
)
|
887
|
-
seen_files.add(file_path_str)
|
888
|
-
|
889
|
-
# File search files from CLI args
|
890
|
-
file_search_file_paths = args.get("file_search_files", [])
|
891
|
-
for fs_file_path in file_search_file_paths:
|
892
|
-
if isinstance(fs_file_path, (str, Path)):
|
893
|
-
file_name = _generate_template_variable_name(str(fs_file_path))
|
894
|
-
file_path_str = str(fs_file_path)
|
895
|
-
if file_path_str not in seen_files:
|
896
|
-
files_by_intent[FileRoutingIntent.FILE_SEARCH].append(
|
897
|
-
(file_name, file_path_str)
|
898
|
-
)
|
899
|
-
seen_files.add(file_path_str)
|
900
|
-
|
901
|
-
# File search file aliases from CLI args
|
902
|
-
file_search_file_aliases = args.get("file_search_file_aliases", [])
|
903
|
-
for name_path_tuple in file_search_file_aliases:
|
904
|
-
if (
|
905
|
-
isinstance(name_path_tuple, tuple)
|
906
|
-
and len(name_path_tuple) == 2
|
907
|
-
):
|
908
|
-
custom_name, file_path_raw = name_path_tuple
|
909
|
-
file_path_str = str(file_path_raw)
|
910
|
-
if file_path_str not in seen_files:
|
911
|
-
files_by_intent[FileRoutingIntent.FILE_SEARCH].append(
|
912
|
-
(str(custom_name), file_path_str)
|
913
|
-
)
|
914
|
-
seen_files.add(file_path_str)
|
915
|
-
|
916
|
-
# Process files from routing result and map to their proper intents
|
917
|
-
# Only add files that haven't been processed from CLI args
|
918
|
-
for template_file_item in template_files:
|
919
|
-
if isinstance(template_file_item, (str, Path)):
|
920
|
-
file_name = _generate_template_variable_name(
|
921
|
-
str(template_file_item)
|
922
|
-
)
|
923
|
-
file_path_str = str(template_file_item)
|
924
|
-
if file_path_str not in seen_files:
|
925
|
-
files_by_intent[FileRoutingIntent.TEMPLATE_ONLY].append(
|
926
|
-
(file_name, file_path_str)
|
927
|
-
)
|
928
|
-
seen_files.add(file_path_str)
|
929
|
-
elif (
|
930
|
-
isinstance(template_file_item, tuple)
|
931
|
-
and len(template_file_item) == 2
|
932
|
-
):
|
933
|
-
_, template_file_path = template_file_item
|
934
|
-
file_path_str = str(template_file_path)
|
935
|
-
file_name = _generate_template_variable_name(file_path_str)
|
936
|
-
if file_path_str not in seen_files:
|
937
|
-
files_by_intent[FileRoutingIntent.TEMPLATE_ONLY].append(
|
938
|
-
(file_name, file_path_str)
|
939
|
-
)
|
940
|
-
seen_files.add(file_path_str)
|
941
|
-
|
942
|
-
for ci_file_item in code_interpreter_files:
|
943
|
-
if isinstance(ci_file_item, (str, Path)):
|
944
|
-
file_name = _generate_template_variable_name(str(ci_file_item))
|
945
|
-
file_path_str = str(ci_file_item)
|
946
|
-
if file_path_str not in seen_files:
|
947
|
-
files_by_intent[FileRoutingIntent.CODE_INTERPRETER].append(
|
948
|
-
(file_name, file_path_str)
|
949
|
-
)
|
950
|
-
seen_files.add(file_path_str)
|
951
|
-
elif isinstance(ci_file_item, tuple) and len(ci_file_item) == 2:
|
952
|
-
_, ci_file_path = ci_file_item
|
953
|
-
file_path_str = str(ci_file_path)
|
954
|
-
file_name = _generate_template_variable_name(file_path_str)
|
955
|
-
if file_path_str not in seen_files:
|
956
|
-
files_by_intent[FileRoutingIntent.CODE_INTERPRETER].append(
|
957
|
-
(file_name, file_path_str)
|
958
|
-
)
|
959
|
-
seen_files.add(file_path_str)
|
960
|
-
|
961
|
-
for fs_file_item in file_search_files:
|
962
|
-
if isinstance(fs_file_item, (str, Path)):
|
963
|
-
file_name = _generate_template_variable_name(str(fs_file_item))
|
964
|
-
file_path_str = str(fs_file_item)
|
965
|
-
if file_path_str not in seen_files:
|
966
|
-
files_by_intent[FileRoutingIntent.FILE_SEARCH].append(
|
967
|
-
(file_name, file_path_str)
|
968
|
-
)
|
969
|
-
seen_files.add(file_path_str)
|
970
|
-
elif isinstance(fs_file_item, tuple) and len(fs_file_item) == 2:
|
971
|
-
_, fs_file_path = fs_file_item
|
972
|
-
file_path_str = str(fs_file_path)
|
973
|
-
file_name = _generate_template_variable_name(file_path_str)
|
974
|
-
if file_path_str not in seen_files:
|
975
|
-
files_by_intent[FileRoutingIntent.FILE_SEARCH].append(
|
976
|
-
(file_name, file_path_str)
|
977
|
-
)
|
978
|
-
seen_files.add(file_path_str)
|
979
|
-
|
980
|
-
# Categorize directories by their intent
|
981
|
-
routing = routing_result.routing
|
982
|
-
for alias_name, dir_path in routing.template_dir_aliases:
|
983
|
-
dirs_by_intent[FileRoutingIntent.TEMPLATE_ONLY].append(
|
984
|
-
(alias_name, str(dir_path))
|
985
|
-
)
|
986
|
-
for alias_name, dir_path in routing.code_interpreter_dir_aliases:
|
987
|
-
dirs_by_intent[FileRoutingIntent.CODE_INTERPRETER].append(
|
988
|
-
(alias_name, str(dir_path))
|
989
|
-
)
|
990
|
-
for alias_name, dir_path in routing.file_search_dir_aliases:
|
991
|
-
dirs_by_intent[FileRoutingIntent.FILE_SEARCH].append(
|
992
|
-
(alias_name, str(dir_path))
|
993
|
-
)
|
994
|
-
|
995
|
-
# Auto-naming directories from CLI args
|
996
|
-
template_dirs = args.get("template_dirs", [])
|
997
|
-
for dir_path in template_dirs:
|
998
|
-
dir_name = _generate_template_variable_name(str(dir_path))
|
999
|
-
dirs_by_intent[FileRoutingIntent.TEMPLATE_ONLY].append(
|
1000
|
-
(dir_name, str(dir_path))
|
1001
|
-
)
|
1002
|
-
|
1003
|
-
code_interpreter_dirs = args.get("code_interpreter_dirs", [])
|
1004
|
-
for dir_path in code_interpreter_dirs:
|
1005
|
-
dir_name = _generate_template_variable_name(str(dir_path))
|
1006
|
-
dirs_by_intent[FileRoutingIntent.CODE_INTERPRETER].append(
|
1007
|
-
(dir_name, str(dir_path))
|
1008
|
-
)
|
1009
|
-
|
1010
|
-
file_search_dirs = args.get("file_search_dirs", [])
|
1011
|
-
for dir_path in file_search_dirs:
|
1012
|
-
dir_name = _generate_template_variable_name(str(dir_path))
|
1013
|
-
dirs_by_intent[FileRoutingIntent.FILE_SEARCH].append(
|
1014
|
-
(dir_name, str(dir_path))
|
1015
|
-
)
|
1016
|
-
|
1017
|
-
# Process files by intent groups with appropriate routing_intent
|
1018
|
-
for intent, file_mappings in files_by_intent.items():
|
1019
|
-
if file_mappings or dirs_by_intent[intent]:
|
1020
|
-
intent_files_dict = collect_files(
|
1021
|
-
file_mappings=cast(
|
1022
|
-
List[Tuple[str, Union[str, Path]]], file_mappings
|
1023
|
-
),
|
1024
|
-
dir_mappings=cast(
|
1025
|
-
List[Tuple[str, Union[str, Path]]],
|
1026
|
-
dirs_by_intent[intent],
|
1027
|
-
),
|
1028
|
-
dir_recursive=args.get("recursive", False),
|
1029
|
-
security_manager=security_manager,
|
1030
|
-
routing_type="template", # Keep routing_type for template context accessibility
|
1031
|
-
routing_intent=intent, # Use intent for warning logic
|
1032
|
-
)
|
1033
|
-
files_dict.update(intent_files_dict)
|
1034
|
-
|
1035
|
-
# Handle legacy files and directories separately to preserve variable names
|
1036
|
-
legacy_files = args.get("files", [])
|
1037
|
-
legacy_dirs = args.get("dir", [])
|
1038
|
-
legacy_patterns = args.get("patterns", [])
|
727
|
+
# Check if we have new attachment syntax
|
728
|
+
if _has_new_attachment_syntax(args):
|
729
|
+
# Re-process attachments for template context creation
|
730
|
+
# This ensures we have the full ProcessedAttachments structure
|
731
|
+
processor = AttachmentProcessor(security_manager)
|
732
|
+
attachments = _extract_attachments_from_args(args)
|
733
|
+
processed_attachments = processor.process_attachments(attachments)
|
734
|
+
else:
|
735
|
+
# No attachments specified - create empty processed attachments
|
736
|
+
processed_attachments = ProcessedAttachments()
|
1039
737
|
|
1040
|
-
|
1041
|
-
|
1042
|
-
file_mappings=cast(
|
1043
|
-
List[Tuple[str, Union[str, Path]]], legacy_files
|
1044
|
-
),
|
1045
|
-
dir_mappings=cast(
|
1046
|
-
List[Tuple[str, Union[str, Path]]], legacy_dirs
|
1047
|
-
),
|
1048
|
-
pattern_mappings=cast(
|
1049
|
-
List[Tuple[str, Union[str, Path]]], legacy_patterns
|
1050
|
-
),
|
1051
|
-
dir_recursive=args.get("recursive", False),
|
1052
|
-
security_manager=security_manager,
|
1053
|
-
routing_type="template", # Legacy flags are considered template-only
|
1054
|
-
routing_intent=FileRoutingIntent.TEMPLATE_ONLY, # Legacy files use template-only intent
|
1055
|
-
)
|
1056
|
-
# Merge legacy results into the main template context
|
1057
|
-
files_dict.update(legacy_files_dict)
|
738
|
+
# Build base context from variables
|
739
|
+
base_context = {}
|
1058
740
|
|
1059
741
|
# Collect simple variables
|
1060
742
|
variables = collect_simple_variables(args)
|
743
|
+
base_context.update(variables)
|
1061
744
|
|
1062
745
|
# Collect JSON variables
|
1063
746
|
json_variables = collect_json_variables(args)
|
747
|
+
base_context.update(json_variables)
|
748
|
+
|
749
|
+
# Add standard context variables
|
750
|
+
import sys
|
1064
751
|
|
1065
|
-
# Get stdin content if available
|
1066
752
|
stdin_content = None
|
1067
753
|
try:
|
1068
754
|
if not sys.stdin.isatty():
|
1069
755
|
stdin_content = sys.stdin.read()
|
1070
756
|
except (OSError, IOError):
|
1071
|
-
# Skip stdin if it can't be read
|
1072
757
|
pass
|
1073
758
|
|
1074
|
-
|
1075
|
-
|
1076
|
-
Dict[str, Union[FileInfoList, str, List[str], Dict[str, str]]],
|
1077
|
-
files_dict,
|
1078
|
-
),
|
1079
|
-
variables=variables,
|
1080
|
-
json_variables=json_variables,
|
1081
|
-
security_manager=security_manager,
|
1082
|
-
stdin_content=stdin_content,
|
1083
|
-
)
|
759
|
+
if stdin_content is not None:
|
760
|
+
base_context["stdin"] = stdin_content
|
1084
761
|
|
1085
762
|
# Add current model to context
|
1086
|
-
|
1087
|
-
|
1088
|
-
# Add
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
# Apply universal tool toggle overrides (Step 3: Config override hook)
|
1101
|
-
enabled_tools: set[str] = args.get("_enabled_tools", set()) # type: ignore[assignment]
|
1102
|
-
disabled_tools: set[str] = args.get("_disabled_tools", set()) # type: ignore[assignment]
|
1103
|
-
|
1104
|
-
# Determine if web search should be enabled
|
1105
|
-
if "web-search" in enabled_tools:
|
1106
|
-
# Universal --enable-tool web-search takes highest precedence
|
1107
|
-
web_search_enabled = True
|
1108
|
-
elif "web-search" in disabled_tools:
|
1109
|
-
# Universal --disable-tool web-search takes highest precedence
|
1110
|
-
web_search_enabled = False
|
1111
|
-
elif web_search_from_cli:
|
1112
|
-
# Explicit --web-search flag takes precedence
|
1113
|
-
web_search_enabled = True
|
1114
|
-
elif no_web_search_from_cli:
|
1115
|
-
# Explicit --no-web-search flag disables
|
1116
|
-
web_search_enabled = False
|
1117
|
-
else:
|
1118
|
-
# Use config default
|
1119
|
-
web_search_enabled = web_search_config.enable_by_default
|
1120
|
-
|
1121
|
-
context["web_search_enabled"] = web_search_enabled
|
1122
|
-
|
1123
|
-
# Add Code Interpreter context variables
|
1124
|
-
# Check if Code Interpreter is enabled by looking for CI files or tools
|
1125
|
-
ci_enabled_by_routing = bool(
|
1126
|
-
args.get("code_interpreter_files")
|
1127
|
-
or args.get("code_interpreter_file_aliases")
|
1128
|
-
or args.get("code_interpreter_dirs")
|
1129
|
-
or args.get("code_interpreter_dir_aliases")
|
1130
|
-
or args.get("code_interpreter", False)
|
763
|
+
base_context["current_model"] = args["model"]
|
764
|
+
|
765
|
+
# Add tool enablement flags
|
766
|
+
base_context.update(_build_tool_context(args, routing_result))
|
767
|
+
|
768
|
+
# Build attachment-based context
|
769
|
+
# In dry-run mode, use strict mode to fail fast on binary file errors
|
770
|
+
strict_mode = args.get("dry_run", False)
|
771
|
+
context = build_template_context_from_attachments(
|
772
|
+
processed_attachments,
|
773
|
+
security_manager,
|
774
|
+
base_context,
|
775
|
+
strict_mode=strict_mode,
|
1131
776
|
)
|
1132
777
|
|
1133
|
-
#
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
778
|
+
# Add debugging support for new attachment system
|
779
|
+
debug_enabled = args.get("debug", False)
|
780
|
+
|
781
|
+
if (
|
782
|
+
debug_enabled
|
783
|
+
or is_capacity_active(TDCap.VARS)
|
784
|
+
or is_capacity_active(TDCap.PREVIEW)
|
785
|
+
):
|
786
|
+
from .attachment_template_bridge import AttachmentTemplateContext
|
787
|
+
|
788
|
+
context_builder = AttachmentTemplateContext(security_manager)
|
789
|
+
context_builder.debug_attachment_context(
|
790
|
+
context,
|
791
|
+
processed_attachments,
|
792
|
+
show_detailed=(
|
793
|
+
debug_enabled or is_capacity_active(TDCap.PREVIEW)
|
794
|
+
),
|
1150
795
|
)
|
1151
796
|
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
) # Copy to avoid modifying original
|
1156
|
-
enabled_features = args.get("enabled_features", [])
|
1157
|
-
disabled_features = args.get("disabled_features", [])
|
797
|
+
# Add proper template debug capacity logging with prefixes
|
798
|
+
if is_capacity_active(TDCap.VARS):
|
799
|
+
td_log_vars(context)
|
1158
800
|
|
1159
|
-
|
1160
|
-
|
1161
|
-
from .click_options import parse_feature_flags
|
1162
|
-
|
1163
|
-
parsed_flags = parse_feature_flags(
|
1164
|
-
tuple(enabled_features), tuple(disabled_features)
|
1165
|
-
)
|
1166
|
-
ci_hack_flag = parsed_flags.get("ci-download-hack")
|
1167
|
-
if ci_hack_flag == "on":
|
1168
|
-
effective_ci_config["download_strategy"] = (
|
1169
|
-
"two_pass_sentinel"
|
1170
|
-
)
|
1171
|
-
elif ci_hack_flag == "off":
|
1172
|
-
effective_ci_config["download_strategy"] = (
|
1173
|
-
"single_pass"
|
1174
|
-
)
|
1175
|
-
except Exception as e:
|
1176
|
-
logger.warning(
|
1177
|
-
f"Failed to parse feature flags in template processor: {e}"
|
1178
|
-
)
|
1179
|
-
|
1180
|
-
# Add the effective CI config for template processing
|
1181
|
-
context["code_interpreter_config"] = effective_ci_config
|
1182
|
-
else:
|
1183
|
-
context["auto_download_enabled"] = False
|
1184
|
-
context["code_interpreter_config"] = {}
|
801
|
+
if is_capacity_active(TDCap.PREVIEW):
|
802
|
+
td_log_preview(context)
|
1185
803
|
|
804
|
+
logger.debug(
|
805
|
+
f"Built attachment-based template context with {len(context)} variables"
|
806
|
+
)
|
1186
807
|
return context
|
1187
808
|
|
1188
809
|
except PathSecurityError:
|