cellcog 2.1.2__tar.gz → 2.3.0__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 (32) hide show
  1. {cellcog-2.1.2 → cellcog-2.3.0}/PKG-INFO +24 -18
  2. {cellcog-2.1.2 → cellcog-2.3.0}/README.md +22 -16
  3. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/__init__.py +1 -1
  4. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/chat.py +53 -8
  5. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/client.py +6 -1
  6. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/docs/api_reference.md +4 -3
  7. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/files.py +14 -4
  8. {cellcog-2.1.2 → cellcog-2.3.0}/pyproject.toml +1 -1
  9. cellcog-2.3.0/tests/test_gs_url_handling.py +54 -0
  10. {cellcog-2.1.2 → cellcog-2.3.0}/.convex/.gitignore +0 -0
  11. {cellcog-2.1.2 → cellcog-2.3.0}/.convex/local/default/config.json +0 -0
  12. {cellcog-2.1.2 → cellcog-2.3.0}/.convex/local/default/convex_local_backend.sqlite3 +0 -0
  13. {cellcog-2.1.2 → cellcog-2.3.0}/.convex/local/default/convex_local_storage/modules/18330a2b-2cf5-4f51-89a5-8d97a422e7a2.blob +0 -0
  14. {cellcog-2.1.2 → cellcog-2.3.0}/.gitignore +0 -0
  15. {cellcog-2.1.2 → cellcog-2.3.0}/LICENSE +0 -0
  16. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/auth.py +0 -0
  17. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/config.py +0 -0
  18. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/daemon/__init__.py +0 -0
  19. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/daemon/delivery.py +0 -0
  20. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/daemon/main.py +0 -0
  21. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/daemon/state.py +0 -0
  22. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/docs/__init__.py +0 -0
  23. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/docs/support.md +0 -0
  24. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/exceptions.py +0 -0
  25. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/message_formatter.py +0 -0
  26. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/message_processor.py +0 -0
  27. {cellcog-2.1.2 → cellcog-2.3.0}/cellcog/version_detection.py +0 -0
  28. {cellcog-2.1.2 → cellcog-2.3.0}/examples/basic_usage.py +0 -0
  29. {cellcog-2.1.2 → cellcog-2.3.0}/examples/file_handling.py +0 -0
  30. {cellcog-2.1.2 → cellcog-2.3.0}/examples/streaming_usage.py +0 -0
  31. {cellcog-2.1.2 → cellcog-2.3.0}/examples/sync_usage.py +0 -0
  32. {cellcog-2.1.2 → cellcog-2.3.0}/examples/workflow_usage.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cellcog
3
- Version: 2.1.2
3
+ Version: 2.3.0
4
4
  Summary: Python SDK for CellCog - Any-to-Any AI for agents. Fire-and-forget pattern with WebSocket notifications.
5
5
  Project-URL: Homepage, https://cellcog.ai
6
6
  Project-URL: Documentation, https://cellcog.ai/developer/docs
@@ -115,7 +115,8 @@ export CELLCOG_API_KEY="sk_..."
115
115
  result = client.create_chat(
116
116
  prompt="Your task...",
117
117
  task_label="my-task",
118
- chat_mode="agent", # "agent" | "agent core" | "agent team" | "agent team max"
118
+ chat_mode="agent", # "agent" | "creative" | "team" (legacy names still work)
119
+ chat_tier="core", # "flash" | "core" | "max" (omit for the mode's default)
119
120
  timeout=1800, # 30 min default; use 3600 for complex jobs
120
121
  )
121
122
 
@@ -172,27 +173,32 @@ result = client.create_chat(
172
173
 
173
174
  Generated files auto-download to `~/.cellcog/chats/{chat_id}/` or to `GENERATE_FILE` paths if specified.
174
175
 
175
- ## Chat Modes
176
+ ## Chat Modes & Tiers
176
177
 
177
- | Mode | Speed | Min Credits | Best For |
178
- |------|-------|-------------|----------|
179
- | `"agent"` | Fast | 100 | Most tasks — research, images, audio, documents |
180
- | `"agent core"` | Fast | 50 | Coding, co-work, terminal operations |
181
- | `"agent team"` | 5–60 min | 500 | Deep research & multi-angled reasoning |
182
- | `"agent team max"` | Slowest | 2,000 | High-stakes — legal, financial, academic |
178
+ Every chat runs at a (mode, tier) operating point. Pick a mode for the KIND of work,
179
+ a tier for the DEPTH (omit `chat_tier` for the mode's default).
180
+
181
+ | Mode | Best For | Tiers |
182
+ |------|----------|-------|
183
+ | `"agent"` | Most tasks — building, research, images, audio, documents, coding/co-work | `flash` · `core` · `max` (default) |
184
+ | `"creative"` | Design, brand, frontend craft — taste-first work | `core` (default) · `max` |
185
+ | `"team"` | Deep multi-angled research & analysis (multi-agent) | `flash` · `core` (default) · `max` |
186
+
187
+ Legacy mode names (`"agent core"`, `"agent team"`, `"agent team max"`) keep working
188
+ forever — the server normalizes them to their historical operating points.
183
189
 
184
190
  ## 35 Skills — The Cog Family
185
191
 
186
192
  | Category | Skills |
187
193
  |----------|--------|
188
- | **Research & Analysis** | `research-cog` `fin-cog` `crypto-cog` `data-cog` `news-cog` |
189
- | **Video & Cinema** | `video-cog` `cine-cog` `insta-cog` `tube-cog` `seedance-cog` |
190
- | **Images & Design** | `image-cog` `brand-cog` `meme-cog` `banana-cog` `3d-cog` |
191
- | **Audio & Music** | `audio-cog` `music-cog` `pod-cog` |
192
- | **Documents & Slides** | `docs-cog` `slides-cog` `spreadsheets-cog` `resume-cog` `legal-cog` |
193
- | **Apps & Prototypes** | `dash-cog` `game-cog` `proto-cog` |
194
- | **Creative** | `comi-cog` `story-cog` `learn-cog` `travel-cog` |
195
- | **Development** | `code-cog` `cowork-cog` `project-cog` `think-cog` |
194
+ | **Research & Analysis** | `deep-research-cellcog` `stock-analysis-cellcog` `crypto-research-cellcog` `data-analysis-cellcog` `news-briefing-cellcog` |
195
+ | **Video & Cinema** | `video-generation-cellcog` `cinematic-video-cellcog` `instagram-reels-tiktok-cellcog` `youtube-video-cellcog` `seedance-video-generation-cellcog` |
196
+ | **Images & Design** | `image-generation-cellcog` `logo-brand-identity-cellcog` `meme-generator-cellcog` `nano-banana-image-cellcog` `3d-model-generation-cellcog` |
197
+ | **Audio & Music** | `audio-generation-cellcog` `music-generation-cellcog` `podcast-generation-cellcog` |
198
+ | **Documents & Slides** | `pdf-document-generation-cellcog` `presentation-slides-cellcog` `excel-spreadsheet-cellcog` `resume-cover-letter-cellcog` `legal-documents-cellcog` |
199
+ | **Apps & Prototypes** | `dashboard-web-app-cellcog` `game-asset-generation-cellcog` `ui-prototype-wireframe-cellcog` |
200
+ | **Creative** | `comic-manga-generator-cellcog` `creative-writing-cellcog` `tutoring-education-cellcog` `travel-planning-cellcog` |
201
+ | **Development** | `coding-agent-cellcog` `pair-programming-cellcog` `project-management-cellcog` `brainstorming-strategy-cellcog` |
196
202
 
197
203
  Browse all skills: https://cellcog.ai/skills
198
204
 
@@ -83,7 +83,8 @@ export CELLCOG_API_KEY="sk_..."
83
83
  result = client.create_chat(
84
84
  prompt="Your task...",
85
85
  task_label="my-task",
86
- chat_mode="agent", # "agent" | "agent core" | "agent team" | "agent team max"
86
+ chat_mode="agent", # "agent" | "creative" | "team" (legacy names still work)
87
+ chat_tier="core", # "flash" | "core" | "max" (omit for the mode's default)
87
88
  timeout=1800, # 30 min default; use 3600 for complex jobs
88
89
  )
89
90
 
@@ -140,27 +141,32 @@ result = client.create_chat(
140
141
 
141
142
  Generated files auto-download to `~/.cellcog/chats/{chat_id}/` or to `GENERATE_FILE` paths if specified.
142
143
 
143
- ## Chat Modes
144
+ ## Chat Modes & Tiers
144
145
 
145
- | Mode | Speed | Min Credits | Best For |
146
- |------|-------|-------------|----------|
147
- | `"agent"` | Fast | 100 | Most tasks — research, images, audio, documents |
148
- | `"agent core"` | Fast | 50 | Coding, co-work, terminal operations |
149
- | `"agent team"` | 5–60 min | 500 | Deep research & multi-angled reasoning |
150
- | `"agent team max"` | Slowest | 2,000 | High-stakes — legal, financial, academic |
146
+ Every chat runs at a (mode, tier) operating point. Pick a mode for the KIND of work,
147
+ a tier for the DEPTH (omit `chat_tier` for the mode's default).
148
+
149
+ | Mode | Best For | Tiers |
150
+ |------|----------|-------|
151
+ | `"agent"` | Most tasks — building, research, images, audio, documents, coding/co-work | `flash` · `core` · `max` (default) |
152
+ | `"creative"` | Design, brand, frontend craft — taste-first work | `core` (default) · `max` |
153
+ | `"team"` | Deep multi-angled research & analysis (multi-agent) | `flash` · `core` (default) · `max` |
154
+
155
+ Legacy mode names (`"agent core"`, `"agent team"`, `"agent team max"`) keep working
156
+ forever — the server normalizes them to their historical operating points.
151
157
 
152
158
  ## 35 Skills — The Cog Family
153
159
 
154
160
  | Category | Skills |
155
161
  |----------|--------|
156
- | **Research & Analysis** | `research-cog` `fin-cog` `crypto-cog` `data-cog` `news-cog` |
157
- | **Video & Cinema** | `video-cog` `cine-cog` `insta-cog` `tube-cog` `seedance-cog` |
158
- | **Images & Design** | `image-cog` `brand-cog` `meme-cog` `banana-cog` `3d-cog` |
159
- | **Audio & Music** | `audio-cog` `music-cog` `pod-cog` |
160
- | **Documents & Slides** | `docs-cog` `slides-cog` `spreadsheets-cog` `resume-cog` `legal-cog` |
161
- | **Apps & Prototypes** | `dash-cog` `game-cog` `proto-cog` |
162
- | **Creative** | `comi-cog` `story-cog` `learn-cog` `travel-cog` |
163
- | **Development** | `code-cog` `cowork-cog` `project-cog` `think-cog` |
162
+ | **Research & Analysis** | `deep-research-cellcog` `stock-analysis-cellcog` `crypto-research-cellcog` `data-analysis-cellcog` `news-briefing-cellcog` |
163
+ | **Video & Cinema** | `video-generation-cellcog` `cinematic-video-cellcog` `instagram-reels-tiktok-cellcog` `youtube-video-cellcog` `seedance-video-generation-cellcog` |
164
+ | **Images & Design** | `image-generation-cellcog` `logo-brand-identity-cellcog` `meme-generator-cellcog` `nano-banana-image-cellcog` `3d-model-generation-cellcog` |
165
+ | **Audio & Music** | `audio-generation-cellcog` `music-generation-cellcog` `podcast-generation-cellcog` |
166
+ | **Documents & Slides** | `pdf-document-generation-cellcog` `presentation-slides-cellcog` `excel-spreadsheet-cellcog` `resume-cover-letter-cellcog` `legal-documents-cellcog` |
167
+ | **Apps & Prototypes** | `dashboard-web-app-cellcog` `game-asset-generation-cellcog` `ui-prototype-wireframe-cellcog` |
168
+ | **Creative** | `comic-manga-generator-cellcog` `creative-writing-cellcog` `tutoring-education-cellcog` `travel-planning-cellcog` |
169
+ | **Development** | `coding-agent-cellcog` `pair-programming-cellcog` `project-management-cellcog` `brainstorming-strategy-cellcog` |
164
170
 
165
171
  Browse all skills: https://cellcog.ai/skills
166
172
 
@@ -48,7 +48,7 @@ from .exceptions import (
48
48
  SDKUpgradeRequiredError,
49
49
  )
50
50
 
51
- __version__ = "2.1.1"
51
+ __version__ = "2.3.0"
52
52
  __all__ = [
53
53
  "CellCogClient",
54
54
  "CellCogError",
@@ -20,6 +20,48 @@ from .exceptions import (
20
20
  )
21
21
  from .files import FileProcessor
22
22
 
23
+ # ── Mode × Tier resolution (Mode × Tier redesign, 2026-08) ──────────────────
24
+ # Friendly SDK names -> API chat_mode values. The server accepts BOTH current
25
+ # API values and legacy ones forever (they normalize server-side to their
26
+ # historical operating points), so unknown strings pass through untouched and
27
+ # the server returns a clear 400 for genuinely invalid values.
28
+ _MODE_ALIASES = {
29
+ # Current modes
30
+ "agent": "human_in_the_loop",
31
+ "creative": "agent_creative",
32
+ "agent creative": "agent_creative",
33
+ "team": "agent_in_the_loop",
34
+ "agent team": "agent_in_the_loop",
35
+ # Legacy names (kept working forever; server normalizes)
36
+ "agent core": "agent_core",
37
+ "agent team max": "agent_team_max",
38
+ }
39
+
40
+ _VALID_TIERS = ("flash", "core", "max")
41
+
42
+
43
+ def resolve_chat_mode(chat_mode: Optional[str]) -> str:
44
+ """Map a friendly mode name to its API value; pass raw API/unknown values through."""
45
+ if not chat_mode:
46
+ return "human_in_the_loop"
47
+ key = str(chat_mode).strip().lower()
48
+ return _MODE_ALIASES.get(key, chat_mode)
49
+
50
+
51
+ def resolve_chat_tier(chat_tier: Optional[str]) -> Optional[str]:
52
+ """Normalize a tier string ('flash' | 'core' | 'max'); None passes through (server
53
+ applies the mode's default tier). Unknown values raise immediately — a typo'd tier
54
+ silently falling back to a default would misroute spend."""
55
+ if chat_tier is None or str(chat_tier).strip() == "":
56
+ return None
57
+ tier = str(chat_tier).strip().lower()
58
+ if tier not in _VALID_TIERS:
59
+ raise ValueError(
60
+ f"Invalid chat_tier: {chat_tier!r}. Use 'flash', 'core', or 'max' "
61
+ "(or omit it for the mode's default). Note: 'creative' has no 'flash' tier."
62
+ )
63
+ return tier
64
+
23
65
 
24
66
  class ChatManager:
25
67
  """
@@ -48,6 +90,7 @@ class ChatManager:
48
90
  agent_role_id: Optional[str] = None,
49
91
  hc_enabled: bool = False,
50
92
  hc_working_directory: Optional[str] = None,
93
+ chat_tier: Optional[str] = None,
51
94
  ) -> dict:
52
95
  """
53
96
  Create a new CellCog chat (without streaming).
@@ -55,7 +98,12 @@ class ChatManager:
55
98
  Args:
56
99
  prompt: Initial prompt (can include SHOW_FILE tags for file uploads)
57
100
  project_id: Optional CellCog project ID for document context
58
- chat_mode: "agent" (fast, iterative), "agent core" (coding/co-work), "agent team" (deep reasoning), or "agent team max" (high-stakes)
101
+ chat_mode: "agent" (fast, iterative), "creative" (design/craft-first), or
102
+ "team" (deep multi-angled research). Legacy names — "agent core",
103
+ "agent team", "agent team max" — keep working forever (normalized
104
+ server-side to their historical operating points).
105
+ chat_tier: "flash" | "core" | "max" — depth/spend within the mode.
106
+ None -> the mode's default tier. "creative" has no "flash" tier.
59
107
  agent_role_id: Optional agent role ID within the project. Requires project_id.
60
108
  hc_enabled: Enable Human Computer (co-work) for this chat
61
109
  hc_working_directory: Working directory on user's machine for HC commands
@@ -69,17 +117,14 @@ class ChatManager:
69
117
  """
70
118
  self.config.require_configured()
71
119
 
72
- mode_mapping = {
73
- "agent team max": "agent_team_max",
74
- "agent team": "agent_in_the_loop",
75
- "agent core": "agent_core",
76
- "agent": "human_in_the_loop",
77
- }
78
- api_chat_mode = mode_mapping.get(chat_mode, chat_mode)
120
+ api_chat_mode = resolve_chat_mode(chat_mode)
121
+ api_chat_tier = resolve_chat_tier(chat_tier)
79
122
 
80
123
  transformed, uploaded = self.files.transform_outgoing(prompt)
81
124
 
82
125
  data = {"message": transformed, "chat_mode": api_chat_mode}
126
+ if api_chat_tier:
127
+ data["chat_tier"] = api_chat_tier
83
128
  if project_id:
84
129
  data["project_id"] = project_id
85
130
  if agent_role_id:
@@ -279,6 +279,7 @@ class CellCogClient:
279
279
  chat_mode: str = "agent",
280
280
  enable_cowork: bool = False,
281
281
  cowork_working_directory: Optional[str] = None,
282
+ chat_tier: Optional[str] = None,
282
283
  ) -> dict:
283
284
  """
284
285
  Create a CellCog chat.
@@ -301,7 +302,10 @@ class CellCogClient:
301
302
  gateway_url: OpenClaw Gateway URL (notify_on_completion only)
302
303
  project_id: Optional CellCog project ID for document context
303
304
  agent_role_id: Optional agent role ID within the project
304
- chat_mode: "agent", "agent core", "agent team", or "agent team max"
305
+ chat_mode: "agent", "creative", or "team". Legacy names — "agent core",
306
+ "agent team", "agent team max" — keep working forever.
307
+ chat_tier: "flash" | "core" | "max" — depth/spend within the mode.
308
+ None -> the mode's default tier. "creative" has no "flash" tier.
305
309
  enable_cowork: Enable co-work on user's PC
306
310
  cowork_working_directory: Working directory for co-work commands
307
311
 
@@ -369,6 +373,7 @@ class CellCogClient:
369
373
  # ── Create the chat via API ──
370
374
  api_result = self._chat.create(
371
375
  prompt, project_id, chat_mode,
376
+ chat_tier=chat_tier,
372
377
  agent_role_id=agent_role_id,
373
378
  hc_enabled=enable_cowork,
374
379
  hc_working_directory=cowork_working_directory,
@@ -25,10 +25,11 @@ result = client.create_chat(
25
25
  timeout: int = 1800, # Max wait seconds (wait mode only)
26
26
  notify_session_key: str = None, # OpenClaw session key (notify mode only)
27
27
  gateway_url: str = None, # OpenClaw Gateway URL (notify mode only)
28
- chat_mode: str = "agent", # "agent", "agent core", "agent team", "agent team max"
29
- project_id: str = None, # Project context (see project-cog)
28
+ chat_mode: str = "agent", # "agent", "creative", "team" (legacy names still work)
29
+ chat_tier: str = None, # "flash" | "core" | "max" (None = mode default; creative has no flash)
30
+ project_id: str = None, # Project context (see project-management-cellcog)
30
31
  agent_role_id: str = None, # Specialized role (requires project_id)
31
- enable_cowork: bool = False, # Direct machine access (see cowork-cog)
32
+ enable_cowork: bool = False, # Direct machine access (see pair-programming-cellcog)
32
33
  cowork_working_directory: str = None, # Working dir for co-work
33
34
  )
34
35
  ```
@@ -66,8 +66,10 @@ class FileProcessor:
66
66
  attrs = match.group(1)
67
67
  file_path = match.group(2).strip()
68
68
 
69
- # Skip empty paths and URLs — not local files
70
- if not file_path or file_path.startswith(("http://", "https://")):
69
+ # Skip empty paths and URLs — not local files. gs:// is a CellCog
70
+ # storage reference (already-uploaded file); leave it untouched so it
71
+ # is not mistaken for a missing local file (which would raise).
72
+ if not file_path or file_path.startswith(("http://", "https://", "gs://")):
71
73
  return match.group(0)
72
74
 
73
75
  # Cross-platform local file detection:
@@ -272,17 +274,25 @@ class FileProcessor:
272
274
  """
273
275
  Generate safe download path under ~/.cellcog/chats/{chat_id}/.
274
276
 
275
- Handles two blob_name formats:
277
+ Handles these blob_name formats:
278
+ - gs://bucket/{chat_id}/path/to/file.ext (canonical explicit GS URL)
276
279
  - {chat_id}//home/app/path/to/file.ext (double slash = from CellCog's /home/app/)
277
280
  - {chat_id}/path/to/file.ext (single slash = relative path)
278
281
 
279
282
  Args:
280
- blob_name: Full blob name from CellCog
283
+ blob_name: Full blob name (or gs:// URL) from CellCog
281
284
  chat_id: Chat ID for organizing downloads
282
285
 
283
286
  Returns:
284
287
  Local path like ~/.cellcog/chats/{chat_id}/{path}
285
288
  """
289
+ # Strip a canonical gs://bucket/ prefix down to the bare blob name
290
+ # ({chat_id}/path...) so the existing chat_id-prefix logic applies.
291
+ if blob_name.startswith("gs://"):
292
+ # gs://bucket/{chat_id}/path → drop scheme + bucket (first 3 segments)
293
+ parts = blob_name.split("/", 3)
294
+ blob_name = parts[3] if len(parts) > 3 else parts[-1]
295
+
286
296
  if "/" not in blob_name:
287
297
  # Malformed - use blob_name as filename
288
298
  return str(self.default_download_dir / chat_id / blob_name)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cellcog"
7
- version = "2.1.2"
7
+ version = "2.3.0"
8
8
  description = "Python SDK for CellCog - Any-to-Any AI for agents. Fire-and-forget pattern with WebSocket notifications."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,54 @@
1
+ """Standalone tests for SDK gs:// SHOW_FILE handling.
2
+
3
+ Run: python3 tests/test_gs_url_handling.py
4
+ """
5
+
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10
+
11
+ from cellcog.config import Config
12
+ from cellcog.files import FileProcessor
13
+
14
+ CHAT = "687a8aa4fcf12067f5a5b9e0"
15
+ failed = 0
16
+
17
+
18
+ def check(label, got, want):
19
+ global failed
20
+ ok = got == want
21
+ if not ok:
22
+ failed += 1
23
+ print(f"[FAIL] {label}\n got : {got}\n want: {want}")
24
+ else:
25
+ print(f"[OK ] {label}")
26
+
27
+
28
+ fp = FileProcessor(Config())
29
+
30
+ # --- download-path derivation strips gs://bucket/ then chat_id ---
31
+ # Real CellCog blobs from /home/app are {chat_id}//home/app/... (double slash:
32
+ # {chat_id}/ + the absolute /home/app/... path). The /home/app/ prefix is stripped.
33
+ p = fp._generate_auto_download_path(f"gs://cellcog_files/{CHAT}//home/app/out.png", CHAT)
34
+ check("gs:// download path strips scheme+bucket+chat+/home/app",
35
+ p, str(fp.default_download_dir / CHAT / "out.png"))
36
+
37
+ # A non-/home/app relative path keeps its sub-path after the chat_id.
38
+ p = fp._generate_auto_download_path(f"gs://cellcog_files/{CHAT}/sub/dir/file.pdf", CHAT)
39
+ check("gs:// download path nested",
40
+ p, str(fp.default_download_dir / CHAT / "sub/dir/file.pdf"))
41
+
42
+ # --- legacy bare blob still works (back-compat), double-slash /home/app form ---
43
+ p = fp._generate_auto_download_path(f"{CHAT}//home/app/out.png", CHAT)
44
+ check("legacy bare blob still derives correctly",
45
+ p, str(fp.default_download_dir / CHAT / "out.png"))
46
+
47
+ # --- outgoing: gs:// is left untouched (not treated as missing local file) ---
48
+ msg = f"Here <SHOW_FILE>gs://cellcog_files/{CHAT}/a.png</SHOW_FILE> done"
49
+ transformed, uploaded = fp.transform_outgoing(msg)
50
+ check("outgoing gs:// unchanged", transformed, msg)
51
+ check("outgoing gs:// nothing uploaded", uploaded, [])
52
+
53
+ print("\n" + ("ALL PASS" if failed == 0 else f"{failed} FAILURE(S)"))
54
+ sys.exit(1 if failed else 0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes