Skip to content

Commit f0c54ca

Browse files
authored
Merge commit a54a070 from parent
2 parents 990dddb + 92ebe5e commit f0c54ca

File tree

23 files changed

+2899
-113
lines changed

23 files changed

+2899
-113
lines changed

docs/api-reference/core/Codebase.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ Get all modified symbols in a pull request
468468
<GithubLinkNote link="https://github.com/codegen-sh/codegen/blob/develop/src/codegen/sdk/core/codebase.py#L1530-L1536" />
469469

470470

471-
<Return return_type={ <code className="text-sm bg-gray-100 px-2 py-0.5 rounded">tuple[str, dict[str, str], list[str]]</code> } description=""/>
471+
<Return return_type={ <code className="text-sm bg-gray-100 px-2 py-0.5 rounded">tuple[str, dict[str, str], list[str], str]</code> } description=""/>
472472

473473

474474
### <span className="text-primary">get_relative_path</span>

docs/changelog/changelog.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ icon: "clock"
44
iconType: "solid"
55
---
66

7+
<Update label="v0.52.15" description="March 20, 2025">
8+
### [fixes token limit inversion bug.](https://github.com/codegen-sh/codegen-sdk/releases/tag/v0.52.15)
9+
- Fix token limit inversion bug
10+
</Update>
11+
12+
<Update label="v0.52.14" description="March 19, 2025">
13+
### [Fixes a maximum observation length error.](https://github.com/codegen-sh/codegen-sdk/releases/tag/v0.52.14)
14+
- Fix maximum observation length error
15+
</Update>
16+
17+
18+
<Update label="v0.52.13" description="March 20, 2025">
19+
### [Fixes summarization error for images.](https://github.com/codegen-sh/codegen-sdk/releases/tag/v0.52.13)
20+
- Fix summarization error for images
21+
</Update>
22+
723
<Update label="v0.52.12" description="March 19, 2025">
824
### [Renames search functionality](https://github.com/codegen-sh/codegen-sdk/releases/tag/v0.52.12)
925
- Renamed search functionality to ripgrep

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
# renovate: datasource=python-version depName=python
77
requires-python = ">=3.12, <3.14"
88
dependencies = [
9-
"openai==1.67.0",
9+
"openai==1.68.0",
1010
"tiktoken<1.0.0,>=0.5.1",
1111
"tabulate>=0.9.0,<1.0.0",
1212
"codeowners<1.0.0,>=0.6.0",

src/codegen/extensions/tools/observation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def render_as_string(self, max_tokens: int = 8000) -> str:
5656
their string output format.
5757
"""
5858
rendered = json.dumps(self.model_dump(), indent=2)
59-
if 3 * len(rendered) > max_tokens:
59+
if len(rendered) > (max_tokens * 3):
6060
logger.error(f"Observation is too long to render: {len(rendered) * 3} tokens")
6161
return rendered[:max_tokens] + "\n\n...truncated...\n\n"
6262
return rendered

src/codegen/sdk/codebase/transaction_manager.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import time
23
from collections.abc import Callable
34
from pathlib import Path
@@ -289,6 +290,22 @@ def get_transactions_at_range(self, file_path: Path, start_byte: int, end_byte:
289290

290291
return matching_transactions
291292

293+
def get_transaction_containing_range(self, file_path: Path, start_byte: int, end_byte: int, transaction_order: TransactionPriority | None = None) -> Transaction | None:
294+
"""Returns the nearest transaction that includes the range specified given the filtering criteria."""
295+
if file_path not in self.queued_transactions:
296+
return None
297+
298+
smallest_difference = math.inf
299+
best_fit_transaction = None
300+
for t in self.queued_transactions[file_path]:
301+
if t.start_byte <= start_byte and t.end_byte >= end_byte:
302+
if transaction_order is None or t.transaction_order == transaction_order:
303+
smallest_difference = min(smallest_difference, abs(t.start_byte - start_byte) + abs(t.end_byte - end_byte))
304+
if smallest_difference == 0:
305+
return t
306+
best_fit_transaction = t
307+
return best_fit_transaction
308+
292309
def _get_conflicts(self, transaction: Transaction) -> list[Transaction]:
293310
"""Returns all transactions that overlap with the given transaction"""
294311
overlapping_transactions = []

src/codegen/sdk/core/codebase.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,13 +1527,13 @@ def from_files(
15271527
logger.info("Codebase initialization complete")
15281528
return codebase
15291529

1530-
def get_modified_symbols_in_pr(self, pr_id: int) -> tuple[str, dict[str, str], list[str]]:
1530+
def get_modified_symbols_in_pr(self, pr_id: int) -> tuple[str, dict[str, str], list[str], str]:
15311531
"""Get all modified symbols in a pull request"""
15321532
pr = self._op.get_pull_request(pr_id)
15331533
cg_pr = CodegenPR(self._op, self, pr)
15341534
patch = cg_pr.get_pr_diff()
15351535
commit_sha = cg_pr.get_file_commit_shas()
1536-
return patch, commit_sha, cg_pr.modified_symbols
1536+
return patch, commit_sha, cg_pr.modified_symbols, pr.head.ref
15371537

15381538
def create_pr_comment(self, pr_number: int, body: str) -> None:
15391539
"""Create a comment on a pull request"""

src/codegen/sdk/core/file.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,13 @@ def remove_unused_exports(self) -> None:
943943
None
944944
"""
945945

946+
def remove_unused_imports(self) -> None:
947+
# Process each import statement
948+
for import_stmt in self.imports:
949+
# Don't remove imports we can't be sure about
950+
if import_stmt.usage_is_ascertainable():
951+
import_stmt.remove_if_unused()
952+
946953
####################################################################################################################
947954
# MANIPULATIONS
948955
####################################################################################################################

src/codegen/sdk/core/import_resolution.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import TYPE_CHECKING, ClassVar, Generic, Literal, Self, TypeVar, override
66

77
from codegen.sdk.codebase.resolution_stack import ResolutionStack
8-
from codegen.sdk.codebase.transactions import TransactionPriority
98
from codegen.sdk.core.autocommit import commiter, reader, remover, writer
109
from codegen.sdk.core.dataclasses.usage import UsageKind
1110
from codegen.sdk.core.expressions.name import Name
@@ -221,6 +220,17 @@ def is_symbol_import(self) -> bool:
221220
"""
222221
return not self.is_module_import()
223222

223+
@reader
224+
def usage_is_ascertainable(self) -> bool:
225+
"""Returns True if we can determine for sure whether the import is unused or not.
226+
227+
Returns:
228+
bool: True if the usage can be ascertained for the import, False otherwise.
229+
"""
230+
if self.is_wildcard_import() or self.is_sideffect_import():
231+
return False
232+
return True
233+
224234
@reader
225235
def is_wildcard_import(self) -> bool:
226236
"""Returns True if the import symbol is a wildcard import.
@@ -234,6 +244,16 @@ def is_wildcard_import(self) -> bool:
234244
"""
235245
return self.import_type == ImportType.WILDCARD
236246

247+
@reader
248+
def is_sideffect_import(self) -> bool:
249+
# Maybe better name for this
250+
"""Determines if this is a sideffect.
251+
252+
Returns:
253+
bool: True if this is a sideffect import, False otherwise
254+
"""
255+
return self.import_type == ImportType.SIDE_EFFECT
256+
237257
@property
238258
@abstractmethod
239259
def namespace(self) -> str | None:
@@ -661,12 +681,21 @@ def __eq__(self, other: object):
661681

662682
@noapidoc
663683
@reader
664-
def remove_if_unused(self) -> None:
665-
if all(
666-
self.transaction_manager.get_transactions_at_range(self.filepath, start_byte=usage.match.start_byte, end_byte=usage.match.end_byte, transaction_order=TransactionPriority.Remove)
667-
for usage in self.usages
668-
):
684+
def remove_if_unused(self, force: bool = False) -> bool:
685+
"""Removes import if it is not being used. Considers current transaction removals.
686+
687+
Args:
688+
force (bool, optional): If true removes the import even if we cannot ascertain the usage for sure. Defaults to False.
689+
690+
Returns:
691+
bool: True if removed, False if not
692+
"""
693+
if all(usage.match.get_transaction_if_pending_removal() for usage in self.usages):
694+
if not force and not self.usage_is_ascertainable():
695+
return False
669696
self.remove()
697+
return True
698+
return False
670699

671700
@noapidoc
672701
@reader

src/codegen/sdk/core/interfaces/editable.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from rich.pretty import Pretty
1111

1212
from codegen.sdk.codebase.span import Span
13-
from codegen.sdk.codebase.transactions import EditTransaction, InsertTransaction, RemoveTransaction, TransactionPriority
13+
from codegen.sdk.codebase.transactions import EditTransaction, InsertTransaction, RemoveTransaction, Transaction, TransactionPriority
1414
from codegen.sdk.core.autocommit import commiter, reader, remover, repr_func, writer
1515
from codegen.sdk.core.placeholder.placeholder import Placeholder
1616
from codegen.sdk.extensions.utils import get_all_identifiers
@@ -1156,6 +1156,15 @@ def parent_class(self) -> Class | None:
11561156

11571157
return self.parent_of_type(Class)
11581158

1159+
@noapidoc
1160+
def get_transaction_if_pending_removal(self) -> Transaction | None:
1161+
"""Checks if this editable is being removed by some transaction and if so returns it.
1162+
1163+
Returns:
1164+
Transaction|None: The transaction removing the editable
1165+
"""
1166+
return self.transaction_manager.get_transaction_containing_range(self.file.path, self.start_byte, self.end_byte, TransactionPriority.Remove)
1167+
11591168
def _get_ast_children(self) -> list[tuple[str | None, AST]]:
11601169
children = []
11611170
names = {}

src/codegen/sdk/core/symbol.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from rich.markup import escape
77

8+
from codegen.sdk.codebase.transactions import TransactionPriority
89
from codegen.sdk.core.autocommit import commiter, reader, writer
910
from codegen.sdk.core.dataclasses.usage import UsageKind, UsageType
1011
from codegen.sdk.core.detached_symbols.argument import Argument
@@ -266,11 +267,38 @@ def insert_before(self, new_src: str, fix_indentation: bool = False, newline: bo
266267
return first_node.insert_before(new_src, fix_indentation, newline, priority, dedupe)
267268
return super().insert_before(new_src, fix_indentation, newline, priority, dedupe)
268269

270+
def _post_move_import_cleanup(self, encountered_symbols, strategy):
271+
# =====[ Remove any imports that are no longer used ]=====
272+
from codegen.sdk.core.import_resolution import Import
273+
274+
for dep in self.dependencies:
275+
if strategy != "duplicate_dependencies":
276+
other_usages = [usage.usage_symbol for usage in dep.usages if usage.usage_symbol not in encountered_symbols]
277+
else:
278+
other_usages = [usage.usage_symbol for usage in dep.usages]
279+
if isinstance(dep, Import):
280+
dep.remove_if_unused()
281+
282+
elif isinstance(dep, Symbol):
283+
usages_in_file = [symb for symb in other_usages if symb.file == self.file and not symb.get_transaction_if_pending_removal()]
284+
if dep.get_transaction_if_pending_removal():
285+
if not usages_in_file and strategy != "add_back_edge":
286+
# We are going to assume there is only one such import
287+
if imp_list := [import_str for import_str in self.file._pending_imports if dep.name and dep.name in import_str]:
288+
if insert_import_list := [
289+
transaction
290+
for transaction in self.transaction_manager.queued_transactions[self.file.path]
291+
if imp_list[0] and transaction.new_content and imp_list[0] in transaction.new_content and transaction.transaction_order == TransactionPriority.Insert
292+
]:
293+
self.transaction_manager.queued_transactions[self.file.path].remove(insert_import_list[0])
294+
self.file._pending_imports.remove(imp_list[0])
295+
269296
def move_to_file(
270297
self,
271298
file: SourceFile,
272299
include_dependencies: bool = True,
273300
strategy: Literal["add_back_edge", "update_all_imports", "duplicate_dependencies"] = "update_all_imports",
301+
cleanup_unused_imports: bool = True,
274302
) -> None:
275303
"""Moves the given symbol to a new file and updates its imports and references.
276304
@@ -290,7 +318,7 @@ def move_to_file(
290318
AssertionError: If an invalid strategy is provided.
291319
"""
292320
encountered_symbols = {self}
293-
self._move_to_file(file, encountered_symbols, include_dependencies, strategy)
321+
self._move_to_file(file, encountered_symbols, include_dependencies, strategy, cleanup_unused_imports)
294322

295323
@noapidoc
296324
def _move_to_file(
@@ -299,6 +327,7 @@ def _move_to_file(
299327
encountered_symbols: set[Symbol | Import],
300328
include_dependencies: bool = True,
301329
strategy: Literal["add_back_edge", "update_all_imports", "duplicate_dependencies"] = "update_all_imports",
330+
cleanup_unused_imports: bool = True,
302331
) -> tuple[NodeId, NodeId]:
303332
"""Helper recursive function for `move_to_file`"""
304333
from codegen.sdk.core.import_resolution import Import
@@ -391,6 +420,9 @@ def _move_to_file(
391420
# Delete the original symbol
392421
self.remove()
393422

423+
if cleanup_unused_imports:
424+
self._post_move_import_cleanup(encountered_symbols, strategy)
425+
394426
@property
395427
@reader
396428
@noapidoc

src/codegen/sdk/typescript/symbol.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,17 @@ def _move_to_file(
261261
encountered_symbols: set[Symbol | Import],
262262
include_dependencies: bool = True,
263263
strategy: Literal["add_back_edge", "update_all_imports", "duplicate_dependencies"] = "update_all_imports",
264+
cleanup_unused_imports: bool = True,
264265
) -> tuple[NodeId, NodeId]:
265266
# TODO: Prevent creation of import loops (!) - raise a ValueError and make the agent fix it
266267
# =====[ Arg checking ]=====
267268
if file == self.file:
268269
return file.file_node_id, self.node_id
269270

271+
if imp := file.get_import(self.name):
272+
encountered_symbols.add(imp)
273+
imp.remove()
274+
270275
# =====[ Move over dependencies recursively ]=====
271276
if include_dependencies:
272277
try:
@@ -319,7 +324,12 @@ def _move_to_file(
319324

320325
# =====[ Make a new symbol in the new file ]=====
321326
# This will update all edges etc.
322-
file.add_symbol(self)
327+
should_export = False
328+
329+
if self.is_exported or [usage for usage in self.usages if usage.usage_symbol not in encountered_symbols and not usage.usage_symbol.get_transaction_if_pending_removal()]:
330+
should_export = True
331+
332+
file.add_symbol(self, should_export=should_export)
323333
import_line = self.get_import_string(module=file.import_module_name)
324334

325335
# =====[ Checks if symbol is used in original file ]=====
@@ -329,16 +339,18 @@ def _move_to_file(
329339
# ======[ Strategy: Duplicate Dependencies ]=====
330340
if strategy == "duplicate_dependencies":
331341
# If not used in the original file. or if not imported from elsewhere, we can just remove the original symbol
342+
is_used_in_file = any(usage.file == self.file and usage.node_type == NodeType.SYMBOL for usage in self.symbol_usages)
332343
if not is_used_in_file and not any(usage.kind is UsageKind.IMPORTED and usage.usage_symbol not in encountered_symbols for usage in self.usages):
333344
self.remove()
334345

335346
# ======[ Strategy: Add Back Edge ]=====
336347
# Here, we will add a "back edge" to the old file importing the self
337348
elif strategy == "add_back_edge":
338349
if is_used_in_file:
339-
self.file.add_import(import_line)
350+
back_edge_line = import_line
340351
if self.is_exported:
341-
self.file.add_import(f"export {{ {self.name} }}")
352+
back_edge_line = back_edge_line.replace("import", "export")
353+
self.file.add_import(back_edge_line)
342354
elif self.is_exported:
343355
module_name = file.name
344356
self.file.add_import(f"export {{ {self.name} }} from '{module_name}'")
@@ -349,23 +361,26 @@ def _move_to_file(
349361
# Update the imports in all the files which use this symbol to get it from the new file now
350362
elif strategy == "update_all_imports":
351363
for usage in self.usages:
352-
if isinstance(usage.usage_symbol, TSImport):
364+
if isinstance(usage.usage_symbol, TSImport) and usage.usage_symbol.file != file:
353365
# Add updated import
354-
if usage.usage_symbol.resolved_symbol is not None and usage.usage_symbol.resolved_symbol.node_type == NodeType.SYMBOL and usage.usage_symbol.resolved_symbol == self:
355-
usage.usage_symbol.file.add_import(import_line)
356-
usage.usage_symbol.remove()
366+
usage.usage_symbol.file.add_import(import_line)
367+
usage.usage_symbol.remove()
357368
elif usage.usage_type == UsageType.CHAINED:
358369
# Update all previous usages of import * to the new import name
359370
if usage.match and "." + self.name in usage.match:
360-
if isinstance(usage.match, FunctionCall):
371+
if isinstance(usage.match, FunctionCall) and self.name in usage.match.get_name():
361372
usage.match.get_name().edit(self.name)
362373
if isinstance(usage.match, ChainedAttribute):
363374
usage.match.edit(self.name)
364-
usage.usage_symbol.file.add_import(import_line)
375+
usage.usage_symbol.file.add_import(imp=import_line)
376+
377+
# Add the import to the original file
365378
if is_used_in_file:
366-
self.file.add_import(import_line)
379+
self.file.add_import(imp=import_line)
367380
# Delete the original symbol
368381
self.remove()
382+
if cleanup_unused_imports:
383+
self._post_move_import_cleanup(encountered_symbols, strategy)
369384

370385
def _convert_proptype_to_typescript(self, prop_type: Editable, param: Parameter | None, level: int) -> str:
371386
"""Converts a PropType definition to its TypeScript equivalent."""

0 commit comments

Comments
 (0)