wcgw 2.8.7__py3-none-any.whl → 2.8.10__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.

Potentially problematic release.


This version of wcgw might be problematic. Click here for more details.

mcp_wcgw/types.py CHANGED
@@ -336,7 +336,7 @@ class CancellationNotification(Notification):
336
336
  long-running request.
337
337
  """
338
338
 
339
- method: Literal["cancelled"]
339
+ method: Literal["notifications/cancelled"]
340
340
  params: CancelledParams
341
341
 
342
342
 
wcgw/client/tools.py CHANGED
@@ -6,8 +6,10 @@ import importlib.metadata
6
6
  import json
7
7
  import mimetypes
8
8
  import os
9
+ import platform
9
10
  import re
10
11
  import shlex
12
+ import subprocess
11
13
  import time
12
14
  import traceback
13
15
  import uuid
@@ -133,19 +135,43 @@ def ask_confirmation(prompt: Confirmation) -> str:
133
135
  PROMPT_CONST = "#" + "@wcgw@#"
134
136
 
135
137
 
136
- def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: # type: ignore
138
+ def is_mac() -> bool:
139
+ return platform.system() == "Darwin"
140
+
141
+
142
+ def get_tmpdir() -> str:
143
+ current_tmpdir = os.environ.get("TMPDIR", "")
144
+ if current_tmpdir or not is_mac():
145
+ return current_tmpdir
146
+ try:
147
+ # Fix issue while running ocrmypdf -> tesseract -> leptonica, set TMPDIR
148
+ # https://github.com/tesseract-ocr/tesseract/issues/4333
149
+ result = subprocess.check_output(
150
+ ["getconf", "DARWIN_USER_TEMP_DIR"],
151
+ text=True,
152
+ ).strip()
153
+ return result
154
+ except subprocess.CalledProcessError:
155
+ return "//tmp"
156
+ except Exception:
157
+ return ""
158
+
159
+
160
+ def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: # type: ignore[type-arg]
137
161
  cmd = "/bin/bash"
138
162
  if is_restricted_mode:
139
163
  cmd += " -r"
140
164
 
165
+ overrideenv = {**os.environ, "PS1": PROMPT_CONST, "TMPDIR": get_tmpdir()}
141
166
  try:
142
167
  shell = pexpect.spawn(
143
168
  cmd,
144
- env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
169
+ env=overrideenv, # type: ignore[arg-type]
145
170
  echo=False,
146
171
  encoding="utf-8",
147
172
  timeout=TIMEOUT,
148
173
  cwd=initial_dir,
174
+ codec_errors="backslashreplace",
149
175
  )
150
176
  shell.sendline(
151
177
  f"export PROMPT_COMMAND= PS1={PROMPT_CONST}"
@@ -157,10 +183,11 @@ def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: #
157
183
 
158
184
  shell = pexpect.spawn(
159
185
  "/bin/bash --noprofile --norc",
160
- env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
186
+ env=overrideenv, # type: ignore[arg-type]
161
187
  echo=False,
162
188
  encoding="utf-8",
163
189
  timeout=TIMEOUT,
190
+ codec_errors="backslashreplace",
164
191
  )
165
192
  shell.sendline(f"export PS1={PROMPT_CONST}")
166
193
  shell.expect(PROMPT_CONST, timeout=TIMEOUT)
@@ -256,7 +283,9 @@ class BashState:
256
283
  before = "\n".join(before_lines).strip()
257
284
  counts += 1
258
285
  if counts > 100:
259
- raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
286
+ raise ValueError(
287
+ "Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it"
288
+ )
260
289
 
261
290
  try:
262
291
  return int(before)
@@ -273,7 +302,7 @@ class BashState:
273
302
  self._bash_command_mode.bash_mode == "restricted_mode",
274
303
  self._cwd,
275
304
  )
276
-
305
+
277
306
  self._pending_output = ""
278
307
 
279
308
  # Get exit info to ensure shell is ready
@@ -414,7 +443,9 @@ class BashState:
414
443
  index = self.shell.expect([self._prompt, pexpect.TIMEOUT], timeout=0.2)
415
444
  counts += 1
416
445
  if counts > 100:
417
- raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
446
+ raise ValueError(
447
+ "Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it"
448
+ )
418
449
  console.print(f"Prompt updated to: {self._prompt}")
419
450
  return True
420
451
  return False
@@ -457,6 +488,12 @@ def initialize(
457
488
  folder_to_start = None
458
489
  if any_workspace_path:
459
490
  if os.path.exists(any_workspace_path):
491
+ if os.path.isfile(any_workspace_path):
492
+ # Set any_workspace_path to the directory containing the file
493
+ # Add the file to read_files_ only if empty to avoid duplicates
494
+ if not read_files_:
495
+ read_files_ = [any_workspace_path]
496
+ any_workspace_path = os.path.dirname(any_workspace_path)
460
497
  repo_context, folder_to_start = get_repo_context(any_workspace_path, 200)
461
498
 
462
499
  repo_context = f"---\n# Workspace structure\n{repo_context}\n---\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 2.8.7
3
+ Version: 2.8.10
4
4
  Summary: Shell and coding agent on claude and chatgpt
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -7,7 +7,7 @@ wcgw/client/diff-instructions.txt,sha256=tmJ9Fu9XdO_72lYXQQNY9RZyx91bjxrXJf9d_KB
7
7
  wcgw/client/memory.py,sha256=8LdYsOhvCOoC1kfvDr85kNy07WnhPMvE6B2FRM2w85Y,2902
8
8
  wcgw/client/modes.py,sha256=FkDJIgjKrlJEufLq3abWfqV25BdF2pH-HnoHafy9LrA,10484
9
9
  wcgw/client/sys_utils.py,sha256=GajPntKhaTUMn6EOmopENWZNR2G_BJyuVbuot0x6veI,1376
10
- wcgw/client/tools.py,sha256=ndIvlJlULCxqRlks9cfDJK8JqMK0Ry85Mt4kZQ0IZhQ,52216
10
+ wcgw/client/tools.py,sha256=c-LipooLb6XBF9l-qwJlzBLzznlqQQuCXRB9HSZJFT0,53450
11
11
  wcgw/client/file_ops/diff_edit.py,sha256=OlJCpPSE_3T41q9H0yDORm6trjm3w6zh1EkuPTxik2A,16832
12
12
  wcgw/client/file_ops/search_replace.py,sha256=Napa7IWaYPGMNdttunKyRDkb90elZE7r23B_o_htRxo,5585
13
13
  wcgw/client/mcp_server/Readme.md,sha256=I8N4dHkTUVGNQ63BQkBMBhCCBTgqGOSF_pUR6iOEiUk,2495
@@ -22,13 +22,13 @@ wcgw/relay/serve.py,sha256=Z5EwtaCAtKFBSnUw4mPYw0sze3Coc4Fa8gObRRG_bT0,9525
22
22
  wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
23
23
  wcgw_cli/__init__.py,sha256=TNxXsTPgb52OhakIda9wTRh91cqoBqgQRx5TxjzQQFU,21
24
24
  wcgw_cli/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
25
- wcgw_cli/anthropic_client.py,sha256=lZWEoX_qDOJIjzbG-EKxTjJyvTSw1Y5odtv7YUUIL7k,21054
25
+ wcgw_cli/anthropic_client.py,sha256=ZMFHD6g6h_PAReL8tubI8LnxVeRA3hcFLGNitjt9XhQ,24047
26
26
  wcgw_cli/cli.py,sha256=GEje9ZBIaD5_-HK3zxZCGYaeDF8bfFxImloOR3O66Fw,1019
27
27
  wcgw_cli/openai_client.py,sha256=wp4XDf3t3W6XG5LHgr6bFckePyty24BGtsOEjOrIrk0,17955
28
28
  wcgw_cli/openai_utils.py,sha256=xGOb3W5ALrIozV7oszfGYztpj0FnXdD7jAxm5lEIVKY,2439
29
29
  mcp_wcgw/__init__.py,sha256=fKCgOdN7cn7gR3YGFaGyV5Goe8A2sEyllLcsRkN0i-g,2601
30
30
  mcp_wcgw/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- mcp_wcgw/types.py,sha256=Enq5vqOPaQdObK9OQuafdLuMxb5RsLQ3k_k613fK41k,30556
31
+ mcp_wcgw/types.py,sha256=f4nENSEc2Zu9YJkrXcSPgwUbc9pPhqhzy-_CBGGttoQ,30570
32
32
  mcp_wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  mcp_wcgw/client/__main__.py,sha256=9ne6Gk1udW_GSwq7O7aZSYwwqSBTgyMVjNVc6s6GJoQ,2280
34
34
  mcp_wcgw/client/session.py,sha256=RqdIN6ov9vklKY2B0-hvjIM5JtUetbyhhrF7a0HKAJ8,8044
@@ -48,8 +48,8 @@ mcp_wcgw/shared/memory.py,sha256=dBsOghxHz8-tycdSVo9kSujbsC8xb_tYsGmuJobuZnw,281
48
48
  mcp_wcgw/shared/progress.py,sha256=ymxOsb8XO5Mhlop7fRfdbmvPodANj7oq6O4dD0iUcnw,1048
49
49
  mcp_wcgw/shared/session.py,sha256=e44a0LQOW8gwdLs9_DE9oDsxqW2U8mXG3d5KT95bn5o,10393
50
50
  mcp_wcgw/shared/version.py,sha256=d2LZii-mgsPIxpshjkXnOTUmk98i0DT4ff8VpA_kAvE,111
51
- wcgw-2.8.7.dist-info/METADATA,sha256=xXsXNlWtzj7OgpCgknOCaiiX1qG0KkOc8IbxsLW2k1A,13053
52
- wcgw-2.8.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
- wcgw-2.8.7.dist-info/entry_points.txt,sha256=vd3tj1_Kzfp55LscJ8-6WFMM5hm9cWTfNGFCrWBnH3Q,124
54
- wcgw-2.8.7.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
55
- wcgw-2.8.7.dist-info/RECORD,,
51
+ wcgw-2.8.10.dist-info/METADATA,sha256=-bjtkmro5UQVMBYYzxbakEIADO-VOpONetLtwxb1vX4,13054
52
+ wcgw-2.8.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
+ wcgw-2.8.10.dist-info/entry_points.txt,sha256=vd3tj1_Kzfp55LscJ8-6WFMM5hm9cWTfNGFCrWBnH3Q,124
54
+ wcgw-2.8.10.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
55
+ wcgw-2.8.10.dist-info/RECORD,,
@@ -14,15 +14,17 @@ from anthropic import Anthropic
14
14
  from anthropic.types import (
15
15
  ImageBlockParam,
16
16
  MessageParam,
17
+ ModelParam,
17
18
  TextBlockParam,
18
19
  ToolParam,
19
20
  ToolResultBlockParam,
20
21
  ToolUseBlockParam,
21
22
  )
22
23
  from dotenv import load_dotenv
24
+ from pydantic import BaseModel
23
25
  from typer import Typer
24
26
 
25
- from wcgw.client.common import discard_input
27
+ from wcgw.client.common import CostData, discard_input
26
28
  from wcgw.client.memory import load_memory
27
29
  from wcgw.client.tools import (
28
30
  DoneFlag,
@@ -47,6 +49,14 @@ from wcgw.types_ import (
47
49
  WriteIfEmpty,
48
50
  )
49
51
 
52
+
53
+ class Config(BaseModel):
54
+ model: ModelParam
55
+ cost_limit: float
56
+ cost_file: dict[ModelParam, CostData]
57
+ cost_unit: str = "$"
58
+
59
+
50
60
  History = list[MessageParam]
51
61
 
52
62
 
@@ -150,7 +160,51 @@ def loop(
150
160
  first_message = ""
151
161
  waiting_for_assistant = history[-1]["role"] != "assistant"
152
162
 
153
- limit = 1
163
+ config = Config(
164
+ model="claude-3-5-sonnet-20241022",
165
+ cost_limit=0.1,
166
+ cost_unit="$",
167
+ cost_file={
168
+ # Claude 3.5 Haiku
169
+ "claude-3-5-haiku-latest": CostData(
170
+ cost_per_1m_input_tokens=0.80, cost_per_1m_output_tokens=4
171
+ ),
172
+ "claude-3-5-haiku-20241022": CostData(
173
+ cost_per_1m_input_tokens=0.80, cost_per_1m_output_tokens=4
174
+ ),
175
+ # Claude 3.5 Sonnet
176
+ "claude-3-5-sonnet-latest": CostData(
177
+ cost_per_1m_input_tokens=3.0, cost_per_1m_output_tokens=15.0
178
+ ),
179
+ "claude-3-5-sonnet-20241022": CostData(
180
+ cost_per_1m_input_tokens=3.0, cost_per_1m_output_tokens=15.0
181
+ ),
182
+ "claude-3-5-sonnet-20240620": CostData(
183
+ cost_per_1m_input_tokens=3.0, cost_per_1m_output_tokens=15.0
184
+ ),
185
+ # Claude 3 Opus
186
+ "claude-3-opus-latest": CostData(
187
+ cost_per_1m_input_tokens=15.0, cost_per_1m_output_tokens=75.0
188
+ ),
189
+ "claude-3-opus-20240229": CostData(
190
+ cost_per_1m_input_tokens=15.0, cost_per_1m_output_tokens=75.0
191
+ ),
192
+ # Legacy Models
193
+ "claude-3-haiku-20240307": CostData(
194
+ cost_per_1m_input_tokens=0.25, cost_per_1m_output_tokens=1.25
195
+ ),
196
+ "claude-2.1": CostData(
197
+ cost_per_1m_input_tokens=8.0, cost_per_1m_output_tokens=24.0
198
+ ),
199
+ "claude-2.0": CostData(
200
+ cost_per_1m_input_tokens=8.0, cost_per_1m_output_tokens=24.0
201
+ ),
202
+ },
203
+ )
204
+
205
+ if limit is not None:
206
+ config.cost_limit = limit
207
+ limit = config.cost_limit
154
208
 
155
209
  tools = [
156
210
  ToolParam(
@@ -321,9 +375,15 @@ Saves provided description and file contents of all the relevant file paths or g
321
375
  while True:
322
376
  if cost > limit:
323
377
  system_console.print(
324
- f"\nCost limit exceeded. Current cost: {cost}, input tokens: {input_toks}, output tokens: {output_toks}"
378
+ f"\nCost limit exceeded. Current cost: {config.cost_unit}{cost:.4f}, "
379
+ f"input tokens: {input_toks}"
380
+ f"output tokens: {output_toks}"
325
381
  )
326
382
  break
383
+ else:
384
+ system_console.print(
385
+ f"\nTotal cost: {config.cost_unit}{cost:.4f}, input tokens: {input_toks}, output tokens: {output_toks}"
386
+ )
327
387
 
328
388
  if not waiting_for_assistant:
329
389
  if first_message:
@@ -335,13 +395,8 @@ Saves provided description and file contents of all the relevant file paths or g
335
395
  history.append(parse_user_message_special(msg))
336
396
  else:
337
397
  waiting_for_assistant = False
338
-
339
- cost_, input_toks_ = 0, 0
340
- cost += cost_
341
- input_toks += input_toks_
342
-
343
398
  stream = client.messages.stream(
344
- model="claude-3-5-sonnet-20241022",
399
+ model=config.model,
345
400
  messages=history,
346
401
  tools=tools,
347
402
  max_tokens=8096,
@@ -361,7 +416,24 @@ Saves provided description and file contents of all the relevant file paths or g
361
416
  with stream as stream_:
362
417
  for chunk in stream_:
363
418
  type_ = chunk.type
364
- if type_ in {"message_start", "message_stop"}:
419
+ if type_ == "message_start":
420
+ message_start = chunk.message
421
+ # Update cost based on token usage from the API response
422
+ input_tokens = message_start.usage.input_tokens
423
+ input_toks += input_tokens
424
+ cost += (
425
+ input_tokens
426
+ * config.cost_file[config.model].cost_per_1m_input_tokens
427
+ ) / 1_000_000
428
+ elif type_ == "message_stop":
429
+ message_stop = chunk.message
430
+ # Update cost based on output tokens
431
+ output_tokens = message_stop.usage.output_tokens
432
+ output_toks += output_tokens
433
+ cost += (
434
+ output_tokens
435
+ * config.cost_file[config.model].cost_per_1m_output_tokens
436
+ ) / 1_000_000
365
437
  continue
366
438
  elif type_ == "content_block_start" and hasattr(
367
439
  chunk, "content_block"
File without changes