sessionfs 0.3.2__tar.gz → 0.3.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.
Files changed (131) hide show
  1. {sessionfs-0.3.2 → sessionfs-0.3.3}/.gitignore +1 -0
  2. {sessionfs-0.3.2 → sessionfs-0.3.3}/PKG-INFO +4 -4
  3. {sessionfs-0.3.2 → sessionfs-0.3.3}/README.md +1 -1
  4. {sessionfs-0.3.2 → sessionfs-0.3.3}/pyproject.toml +3 -3
  5. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/__init__.py +1 -1
  6. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/judge/providers.py +35 -12
  7. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/audit.py +0 -0
  8. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/__init__.py +0 -0
  9. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_admin.py +0 -0
  10. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_audit.py +0 -0
  11. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_cloud.py +0 -0
  12. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_config.py +0 -0
  13. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_daemon.py +0 -0
  14. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_io.py +0 -0
  15. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_mcp.py +0 -0
  16. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_ops.py +0 -0
  17. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_search.py +0 -0
  18. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cmd_sessions.py +0 -0
  19. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/common.py +0 -0
  20. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/cost.py +0 -0
  21. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/main.py +0 -0
  22. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/sfs_to_cc.py +0 -0
  23. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/sfs_to_md.py +0 -0
  24. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/cli/titles.py +0 -0
  25. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/__init__.py +0 -0
  26. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/amp_to_sfs.py +0 -0
  27. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/cline_to_sfs.py +0 -0
  28. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/codex_injector.py +0 -0
  29. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/copilot_injector.py +0 -0
  30. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/copilot_to_sfs.py +0 -0
  31. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/cursor_to_sfs.py +0 -0
  32. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/gemini_injector.py +0 -0
  33. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/gemini_to_sfs.py +0 -0
  34. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/sfs_to_codex.py +0 -0
  35. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/sfs_to_copilot.py +0 -0
  36. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/converters/sfs_to_gemini.py +0 -0
  37. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/daemon/__init__.py +0 -0
  38. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/daemon/config.py +0 -0
  39. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/daemon/main.py +0 -0
  40. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/daemon/status.py +0 -0
  41. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/judge/__init__.py +0 -0
  42. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/judge/evidence.py +0 -0
  43. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/judge/export.py +0 -0
  44. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/judge/extractor.py +0 -0
  45. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/judge/judge.py +0 -0
  46. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/judge/report.py +0 -0
  47. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/mcp/__init__.py +0 -0
  48. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/mcp/cloud_client.py +0 -0
  49. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/mcp/remote_server.py +0 -0
  50. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/mcp/search.py +0 -0
  51. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/mcp/server.py +0 -0
  52. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/security/__init__.py +0 -0
  53. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/security/secrets.py +0 -0
  54. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/__init__.py +0 -0
  55. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/app.py +0 -0
  56. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/auth/__init__.py +0 -0
  57. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/auth/dependencies.py +0 -0
  58. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/auth/keys.py +0 -0
  59. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/auth/rate_limit.py +0 -0
  60. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/config.py +0 -0
  61. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/__init__.py +0 -0
  62. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/engine.py +0 -0
  63. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/env.py +0 -0
  64. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/versions/001_initial_schema.py +0 -0
  65. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/versions/002_verification_and_tiers.py +0 -0
  66. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/versions/003_search_index.py +0 -0
  67. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/versions/004_bigint_tokens.py +0 -0
  68. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/versions/005_handoffs.py +0 -0
  69. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/versions/006_judge_settings.py +0 -0
  70. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/migrations/versions/007_admin_audit_log.py +0 -0
  71. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/db/models.py +0 -0
  72. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/email.py +0 -0
  73. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/email_templates.py +0 -0
  74. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/errors.py +0 -0
  75. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/__init__.py +0 -0
  76. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/admin.py +0 -0
  77. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/audit.py +0 -0
  78. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/auth.py +0 -0
  79. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/handoffs.py +0 -0
  80. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/health.py +0 -0
  81. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/sessions.py +0 -0
  82. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/routes/settings.py +0 -0
  83. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/schemas/__init__.py +0 -0
  84. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/schemas/auth.py +0 -0
  85. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/schemas/errors.py +0 -0
  86. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/schemas/handoffs.py +0 -0
  87. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/schemas/sessions.py +0 -0
  88. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/storage/__init__.py +0 -0
  89. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/storage/base.py +0 -0
  90. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/storage/gcs.py +0 -0
  91. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/storage/local.py +0 -0
  92. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/server/storage/s3.py +0 -0
  93. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/session_id.py +0 -0
  94. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/__init__.py +0 -0
  95. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/convert_cc.py +0 -0
  96. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/complete/manifest.json +0 -0
  97. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/complete/messages.jsonl +0 -0
  98. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/complete/tools.json +0 -0
  99. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/complete/workspace.json +0 -0
  100. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/minimal/manifest.json +0 -0
  101. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/minimal/messages.jsonl +0 -0
  102. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/subagent/manifest.json +0 -0
  103. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/subagent/messages.jsonl +0 -0
  104. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/subagent/tools.json +0 -0
  105. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/examples/subagent/workspace.json +0 -0
  106. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/schemas/manifest.schema.json +0 -0
  107. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/schemas/message.schema.json +0 -0
  108. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/schemas/tools.schema.json +0 -0
  109. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/schemas/workspace.schema.json +0 -0
  110. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/validate.py +0 -0
  111. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/spec/version.py +0 -0
  112. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/store/__init__.py +0 -0
  113. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/store/index.py +0 -0
  114. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/store/local.py +0 -0
  115. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/sync/__init__.py +0 -0
  116. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/sync/archive.py +0 -0
  117. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/sync/client.py +0 -0
  118. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/utils/__init__.py +0 -0
  119. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/utils/title_utils.py +0 -0
  120. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/__init__.py +0 -0
  121. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/amp.py +0 -0
  122. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/base.py +0 -0
  123. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/claude_code.py +0 -0
  124. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/cline.py +0 -0
  125. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/codex.py +0 -0
  126. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/copilot.py +0 -0
  127. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/cursor.py +0 -0
  128. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/gemini.py +0 -0
  129. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/watchers/roo.py +0 -0
  130. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/workspace/__init__.py +0 -0
  131. {sessionfs-0.3.2 → sessionfs-0.3.3}/src/sessionfs/workspace/resolver.py +0 -0
@@ -32,6 +32,7 @@ Thumbs.db
32
32
 
33
33
  # Internal — never commit to main (see .release/private-files.txt)
34
34
  .agents/
35
+ brand/
35
36
  .claude/commands/
36
37
  .claude/settings.local.json
37
38
  .release/
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sessionfs
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Capture, sync, and resume AI coding sessions across 8 tools — Claude Code, Codex, Gemini, Copilot, Cursor, Amp, Cline, and Roo Code.
5
5
  Project-URL: Homepage, https://sessionfs.dev
6
- Project-URL: Repository, https://github.com/sessionfs/sessionfs
6
+ Project-URL: Repository, https://github.com/SessionFS/sessionfs
7
7
  Project-URL: Documentation, https://sessionfs.dev/docs
8
- Project-URL: Issues, https://github.com/sessionfs/sessionfs/issues
8
+ Project-URL: Issues, https://github.com/SessionFS/sessionfs/issues
9
9
  Author: SessionFS Contributors
10
10
  License: Apache-2.0
11
11
  Keywords: agents,ai,claude,codex,cursor,gemini,sessions,sync
@@ -223,7 +223,7 @@ All file paths are relative to workspace root. Sessions are append-only — conf
223
223
 
224
224
  ## Status
225
225
 
226
- **v0.3.2 — Public Beta.** 673 tests passing.
226
+ **v0.3.3 — Public Beta.** 673 tests passing.
227
227
 
228
228
  What works today:
229
229
  - Eight-tool session capture (Claude Code, Codex, Gemini, Cursor, Copilot CLI, Amp, Cline, Roo Code)
@@ -158,7 +158,7 @@ All file paths are relative to workspace root. Sessions are append-only — conf
158
158
 
159
159
  ## Status
160
160
 
161
- **v0.3.2 — Public Beta.** 673 tests passing.
161
+ **v0.3.3 — Public Beta.** 673 tests passing.
162
162
 
163
163
  What works today:
164
164
  - Eight-tool session capture (Claude Code, Codex, Gemini, Cursor, Copilot CLI, Amp, Cline, Roo Code)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sessionfs"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Capture, sync, and resume AI coding sessions across 8 tools — Claude Code, Codex, Gemini, Copilot, Cursor, Amp, Cline, and Roo Code."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -40,9 +40,9 @@ dependencies = [
40
40
 
41
41
  [project.urls]
42
42
  Homepage = "https://sessionfs.dev"
43
- Repository = "https://github.com/sessionfs/sessionfs"
43
+ Repository = "https://github.com/SessionFS/sessionfs"
44
44
  Documentation = "https://sessionfs.dev/docs"
45
- Issues = "https://github.com/sessionfs/sessionfs/issues"
45
+ Issues = "https://github.com/SessionFS/sessionfs/issues"
46
46
 
47
47
  [project.optional-dependencies]
48
48
  server = [
@@ -5,4 +5,4 @@ from importlib.metadata import version, PackageNotFoundError
5
5
  try:
6
6
  __version__ = version("sessionfs")
7
7
  except PackageNotFoundError:
8
- __version__ = "0.3.2" # fallback for development
8
+ __version__ = "0.3.3" # fallback for development
@@ -21,6 +21,9 @@ _PROVIDER_DETECT = [
21
21
  ("gemini-", "google"),
22
22
  ]
23
23
 
24
+ # Reasoning models that reject temperature — use reasoning effort instead
25
+ _REASONING_MODELS = {"o1", "o1-pro", "o3", "o3-mini", "o4-mini"}
26
+
24
27
  _ANTHROPIC_URL = "https://api.anthropic.com/v1/messages"
25
28
  _OPENAI_URL = "https://api.openai.com/v1/chat/completions"
26
29
  _GOOGLE_URL_TEMPLATE = (
@@ -47,6 +50,12 @@ def _detect_provider(model: str) -> str:
47
50
  return "openrouter"
48
51
 
49
52
 
53
+ def _is_reasoning_model(model: str) -> bool:
54
+ """Check if a model is a reasoning model that rejects temperature."""
55
+ base = model.lower().split("-2")[0] # strip date suffix like -2025-04-16
56
+ return base in _REASONING_MODELS
57
+
58
+
50
59
  async def _call_anthropic(model: str, system: str, prompt: str, api_key: str, temperature: float = 0) -> str:
51
60
  """Call the Anthropic Messages API."""
52
61
  headers = {
@@ -54,7 +63,7 @@ async def _call_anthropic(model: str, system: str, prompt: str, api_key: str, te
54
63
  "anthropic-version": "2023-06-01",
55
64
  "content-type": "application/json",
56
65
  }
57
- body = {
66
+ body: dict = {
58
67
  "model": model,
59
68
  "max_tokens": 4096,
60
69
  "temperature": temperature,
@@ -65,7 +74,6 @@ async def _call_anthropic(model: str, system: str, prompt: str, api_key: str, te
65
74
  resp = await client.post(_ANTHROPIC_URL, json=body, headers=headers)
66
75
  resp.raise_for_status()
67
76
  data = resp.json()
68
- # Extract text from content blocks
69
77
  content = data.get("content", [])
70
78
  parts: list[str] = []
71
79
  for block in content:
@@ -80,15 +88,20 @@ async def _call_openai(model: str, system: str, prompt: str, api_key: str, tempe
80
88
  "Authorization": f"Bearer {api_key}",
81
89
  "Content-Type": "application/json",
82
90
  }
83
- body = {
91
+ body: dict = {
84
92
  "model": model,
85
93
  "messages": [
86
94
  {"role": "system", "content": system},
87
95
  {"role": "user", "content": prompt},
88
96
  ],
89
- "max_tokens": 4096,
90
- "temperature": temperature,
97
+ "max_completion_tokens": 4096,
91
98
  }
99
+ # Reasoning models (o3, o4-mini) reject temperature
100
+ if _is_reasoning_model(model):
101
+ body["reasoning"] = {"effort": "medium"}
102
+ else:
103
+ body["temperature"] = temperature
104
+
92
105
  async with httpx.AsyncClient(timeout=120) as client:
93
106
  resp = await client.post(_OPENAI_URL, json=body, headers=headers)
94
107
  resp.raise_for_status()
@@ -99,14 +112,17 @@ async def _call_openai(model: str, system: str, prompt: str, api_key: str, tempe
99
112
  async def _call_google(model: str, system: str, prompt: str, api_key: str, temperature: float = 0) -> str:
100
113
  """Call the Google Generative Language API."""
101
114
  url = _GOOGLE_URL_TEMPLATE.format(model=model)
102
- params = {"key": api_key}
115
+ headers = {
116
+ "x-goog-api-key": api_key,
117
+ "Content-Type": "application/json",
118
+ }
103
119
  body = {
104
120
  "system_instruction": {"parts": [{"text": system}]},
105
121
  "contents": [{"parts": [{"text": prompt}]}],
106
122
  "generationConfig": {"maxOutputTokens": 4096, "temperature": temperature},
107
123
  }
108
124
  async with httpx.AsyncClient(timeout=120) as client:
109
- resp = await client.post(url, json=body, params=params)
125
+ resp = await client.post(url, json=body, headers=headers)
110
126
  resp.raise_for_status()
111
127
  data = resp.json()
112
128
  candidates = data.get("candidates", [])
@@ -124,16 +140,22 @@ async def _call_openrouter(model: str, system: str, prompt: str, api_key: str, t
124
140
  "HTTP-Referer": "https://sessionfs.dev",
125
141
  "X-Title": "SessionFS",
126
142
  }
127
- body = {
143
+ # Extract the base model name for reasoning check (e.g., "openai/o3" -> "o3")
144
+ base_model = model.split("/")[-1] if "/" in model else model
145
+
146
+ body: dict = {
128
147
  "model": model,
129
148
  "messages": [
130
149
  {"role": "system", "content": system},
131
150
  {"role": "user", "content": prompt},
132
151
  ],
133
152
  "max_tokens": 4096,
134
- "temperature": temperature,
135
- "response_format": {"type": "json_object"},
136
153
  }
154
+ if _is_reasoning_model(base_model):
155
+ body["reasoning"] = {"effort": "medium"}
156
+ else:
157
+ body["temperature"] = temperature
158
+
137
159
  async with httpx.AsyncClient(timeout=120) as client:
138
160
  resp = await client.post(_OPENROUTER_URL, json=body, headers=headers)
139
161
  resp.raise_for_status()
@@ -153,14 +175,15 @@ async def call_llm(
153
175
 
154
176
  Auto-detects provider from model name if not specified:
155
177
  - claude-* -> anthropic
156
- - gpt-*, o1*, o3* -> openai
178
+ - gpt-*, o1*, o3*, o4* -> openai
157
179
  - gemini-* -> google
158
180
  - models containing "/" -> openrouter
159
181
  - unknown models -> openrouter (fallback)
160
182
 
161
183
  Uses httpx directly — no SDK dependencies. The API key is used for
162
184
  this single request only and is never persisted. Temperature defaults
163
- to 0 for deterministic judge output.
185
+ to 0 for deterministic judge output. Reasoning models (o3, o4-mini)
186
+ use reasoning effort instead of temperature.
164
187
  """
165
188
  if provider is None:
166
189
  provider = _detect_provider(model)