Skip to content

Commit 7c07fed

Browse files
author
Joshua
committed
Address API introspection review feedback
1 parent 63f513d commit 7c07fed

9 files changed

Lines changed: 48 additions & 8 deletions

File tree

docs/TOOLS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ without creating an instance. By default it returns direct class members only,
9090
with each returned section capped at 100 items. Pass `sections` (`properties`,
9191
`methods`, `signals`, `enums`, `constants`, `inheritors`),
9292
`include_inherited=true`, `include_inheritors=true`, `offset`, or `limit=0`
93-
when a fuller class reference is needed.
93+
when a fuller class reference is needed. When paginating, request one section
94+
at a time so `offset`/`limit` apply only to the list you are paging.
9495

9596
Every rolled-up tool also accepts an optional top-level `session_id` for
9697
per-call multi-editor routing (sibling of `op` and `params`, *not* nested

plugin/addons/godot_ai/handlers/api_handler.gd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ func get_class_info(params: Dictionary) -> Dictionary:
1919
if not script_class.is_empty():
2020
return _script_class_error(requested_class, script_class)
2121
return _unknown_class_error(requested_class)
22+
if params.has("limit") and int(params.get("limit")) < 0:
23+
return ErrorCodes.make(
24+
ErrorCodes.INVALID_PARAMS,
25+
"limit must be >= 0; use limit=0 only when an unlimited section is needed"
26+
)
2227
var section_check := ClassIntrospection.validate_sections(
2328
params.get("sections", ClassIntrospection.DEFAULT_SECTIONS)
2429
)

plugin/addons/godot_ai/utils/class_introspection.gd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ static func build(type_name: String, options: Dictionary = {}) -> Dictionary:
1717
var offset := max(0, int(options.get("offset", 0)))
1818
var limit := int(options.get("limit", MAX_DEFAULT_ITEMS))
1919
if limit < 0:
20-
limit = 0
20+
limit = MAX_DEFAULT_ITEMS
2121
var can_instantiate := ClassDB.can_instantiate(type_name)
2222

2323
var data := {
@@ -26,7 +26,7 @@ static func build(type_name: String, options: Dictionary = {}) -> Dictionary:
2626
"parent_class": str(ClassDB.get_parent_class(type_name)),
2727
"inheritance_chain": _inheritance_chain(type_name),
2828
"can_instantiate": can_instantiate,
29-
"is_abstract": not can_instantiate,
29+
"is_singleton": Engine.has_singleton(type_name),
3030
"include_inherited": include_inherited,
3131
"offset": offset,
3232
"limit": limit,

plugin/addons/godot_ai/utils/variant_serializer.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ static func serialize(value: Variant) -> Variant:
5353
}
5454
TYPE_NODE_PATH:
5555
return str(value)
56-
TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY, TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY, TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_COLOR_ARRAY:
56+
TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY, TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY, TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_VECTOR4_ARRAY, TYPE_PACKED_COLOR_ARRAY:
5757
var arr: Array = []
5858
for item in value:
5959
arr.append(serialize(item))

src/godot_ai/handlers/editor.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,6 @@ async def editor_reload_plugin(runtime: DirectRuntime) -> dict:
321321

322322

323323
async def _dispatch_reload_async(runtime: DirectRuntime, old_id: str) -> None:
324-
## Start the grace delay on the next event-loop turn. That gives the
325-
## caller's response path a chance to continue before this background task
326-
## begins counting down toward the WebSocket reload command.
327-
await asyncio.sleep(0)
328324
if PLUGIN_MANAGED_RELOAD_DELAY_SEC > 0:
329325
await asyncio.sleep(PLUGIN_MANAGED_RELOAD_DELAY_SEC)
330326
try:

src/godot_ai/tools/api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
Return selected class-reference sections without creating a scene
2222
instance. sections may be a comma-separated string or list containing
2323
properties, methods, signals, enums, constants, inheritors.
24+
For pagination, request one section at a time so offset/limit apply
25+
only to the list you are paging.
2426
"""
2527

2628

test_project/tests/test_api.gd

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@ func test_get_class_info_invalid_section_suggests_plural() -> void:
4646
assert_contains(result.error.data.suggestions.method, "methods")
4747

4848

49+
func test_get_class_info_negative_limit_errors() -> void:
50+
var result := _handler.get_class_info({
51+
"class_name": "CharacterBody3D",
52+
"limit": -1,
53+
})
54+
assert_is_error(result, ErrorCodes.INVALID_PARAMS)
55+
assert_contains(result.error.message, "limit")
56+
57+
58+
func test_get_class_info_singleton_does_not_report_abstract() -> void:
59+
var result := _handler.get_class_info({
60+
"class_name": "Input",
61+
"sections": ["methods"],
62+
"limit": 1,
63+
})
64+
assert_has_key(result, "data")
65+
assert_false(result.data.has("is_abstract"))
66+
assert_false(result.data.can_instantiate)
67+
assert_true(result.data.is_singleton)
68+
69+
4970
func test_get_class_info_character_body_3d() -> void:
5071
var result := _handler.get_class_info({
5172
"class_name": "CharacterBody3D",
@@ -54,6 +75,7 @@ func test_get_class_info_character_body_3d() -> void:
5475
})
5576
assert_has_key(result, "data")
5677
assert_eq(result.data.class_name, "CharacterBody3D")
78+
assert_false(result.data.has("is_abstract"))
5779
assert_contains(result.data.inheritance_chain, "PhysicsBody3D")
5880
assert_contains(result.data.inheritance_chain, "Node")
5981
assert_gt(result.data.property_count, 0)

test_project/tests/test_node.gd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,13 @@ func test_serialize_packed_vector3_array_returns_xyz_dicts() -> void:
11461146
assert_eq(result[0]["z"], 3.0)
11471147

11481148

1149+
func test_serialize_packed_vector4_array_returns_xyzw_dicts() -> void:
1150+
var result = NodeHandler._serialize_value(PackedVector4Array([Vector4(1, 2, 3, 4)]))
1151+
assert_true(result is Array)
1152+
assert_eq(result[0]["x"], 1.0)
1153+
assert_eq(result[0]["w"], 4.0)
1154+
1155+
11491156
func test_serialize_packed_color_array_returns_rgba_dicts() -> void:
11501157
var result = NodeHandler._serialize_value(PackedColorArray([Color(1, 0, 0, 0.5)]))
11511158
assert_true(result is Array)

test_project/tests/test_resource.gd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,16 @@ func test_get_resource_info_concrete_type_box_mesh() -> void:
347347
assert_false(result.data.is_abstract)
348348
assert_gt(result.data.property_count, 0)
349349
var prop_names: Array = []
350+
var size_prop: Dictionary = {}
350351
for p in result.data.properties:
351352
prop_names.append(p.name)
353+
if p.name == "size":
354+
size_prop = p
352355
assert_contains(prop_names, "size", "BoxMesh.size must appear in properties")
356+
assert_has_key(size_prop, "default")
357+
assert_eq(size_prop.default.x, 1.0)
358+
assert_eq(size_prop.default.y, 1.0)
359+
assert_eq(size_prop.default.z, 1.0)
353360

354361

355362
func test_get_resource_info_concrete_type_cylinder_mesh() -> void:

0 commit comments

Comments
 (0)