python-delphi-lsp 3.6.0__cp310-abi3-win_amd64.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.
- delphi_lsp/__init__.py +184 -0
- delphi_lsp/_version.py +1 -0
- delphi_lsp/agent_cache.py +1574 -0
- delphi_lsp/agent_cli.py +902 -0
- delphi_lsp/agent_context.py +3907 -0
- delphi_lsp/agent_cpg.py +349 -0
- delphi_lsp/agent_cpg_builder.py +666 -0
- delphi_lsp/agent_layers.py +1410 -0
- delphi_lsp/agent_metrics.py +526 -0
- delphi_lsp/agent_protocol.py +445 -0
- delphi_lsp/agent_relations.py +1881 -0
- delphi_lsp/agent_templates.py +614 -0
- delphi_lsp/agent_wiki.py +2853 -0
- delphi_lsp/agent_workspace.py +860 -0
- delphi_lsp/ast_serialize.py +126 -0
- delphi_lsp/binary.py +250 -0
- delphi_lsp/comment_builder.py +28 -0
- delphi_lsp/consts.py +345 -0
- delphi_lsp/delphi_lsp_native.pyd +0 -0
- delphi_lsp/delphiast_lexer.py +399 -0
- delphi_lsp/delphiast_parser.py +2189 -0
- delphi_lsp/delphiast_tokens.py +41 -0
- delphi_lsp/grammar.py +459 -0
- delphi_lsp/incremental.py +1139 -0
- delphi_lsp/lark_builder.py +2684 -0
- delphi_lsp/lark_tokens.py +236 -0
- delphi_lsp/lexical_scanner.py +30 -0
- delphi_lsp/lsp_server.py +6698 -0
- delphi_lsp/metrics.py +1452 -0
- delphi_lsp/native_bridge.py +300 -0
- delphi_lsp/navigation_cache.py +189 -0
- delphi_lsp/nodes.py +462 -0
- delphi_lsp/parallel_outline.py +662 -0
- delphi_lsp/parse_artifacts.py +969 -0
- delphi_lsp/parser.py +342 -0
- delphi_lsp/parser_backend.py +139 -0
- delphi_lsp/preprocessor.py +1122 -0
- delphi_lsp/progress.py +26 -0
- delphi_lsp/project_config.py +285 -0
- delphi_lsp/project_discovery.py +932 -0
- delphi_lsp/project_indexer.py +470 -0
- delphi_lsp/py.typed +1 -0
- delphi_lsp/response_cache.py +465 -0
- delphi_lsp/semantic.py +394 -0
- delphi_lsp/semantic_builder.py +2302 -0
- delphi_lsp/source_reader.py +17 -0
- delphi_lsp/wiki_external_sort.py +392 -0
- delphi_lsp/wiki_memory.py +548 -0
- delphi_lsp/workspace.py +83 -0
- delphi_lsp/writer.py +73 -0
- python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
- python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
- python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
- python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
- python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
- python_delphi_lsp-3.6.0.dist-info/sboms/python-delphi-lsp.cyclonedx.json +770 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import binascii
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import posixpath
|
|
8
|
+
import unicodedata
|
|
9
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
SCHEMA_VERSION = 3
|
|
13
|
+
|
|
14
|
+
SUPPORTED_ACTIONS = (
|
|
15
|
+
'open',
|
|
16
|
+
'find',
|
|
17
|
+
'inspect',
|
|
18
|
+
'trace',
|
|
19
|
+
'focus',
|
|
20
|
+
'problems',
|
|
21
|
+
'metrics',
|
|
22
|
+
'cpg',
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
SUPPORTED_DETAILS = (
|
|
26
|
+
'summary',
|
|
27
|
+
'declaration',
|
|
28
|
+
'members',
|
|
29
|
+
'context',
|
|
30
|
+
'body',
|
|
31
|
+
'implementations',
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
SUPPORTED_RELATIONS = (
|
|
35
|
+
'references',
|
|
36
|
+
'callers',
|
|
37
|
+
'callees',
|
|
38
|
+
'uses',
|
|
39
|
+
'used_by',
|
|
40
|
+
'inherits',
|
|
41
|
+
'implements',
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
SUPPORTED_GRAPHS = (
|
|
45
|
+
'ast',
|
|
46
|
+
'cfg',
|
|
47
|
+
'dfg',
|
|
48
|
+
'call',
|
|
49
|
+
'full',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
SUPPORTED_DIRECTIONS = (
|
|
53
|
+
'out',
|
|
54
|
+
'in',
|
|
55
|
+
'both',
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
_REQUEST_FIELDS = frozenset(
|
|
59
|
+
{
|
|
60
|
+
'action',
|
|
61
|
+
'query',
|
|
62
|
+
'target_id',
|
|
63
|
+
'project_id',
|
|
64
|
+
'detail',
|
|
65
|
+
'relation',
|
|
66
|
+
'cursor',
|
|
67
|
+
'max_items',
|
|
68
|
+
'max_chars',
|
|
69
|
+
'graph',
|
|
70
|
+
'direction',
|
|
71
|
+
'depth',
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AgentProtocolError(ValueError):
|
|
77
|
+
def __init__(self, code: str, message: str) -> None:
|
|
78
|
+
self.code = code
|
|
79
|
+
self.message = message
|
|
80
|
+
super().__init__(message)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class AgentRequest:
|
|
85
|
+
action: str
|
|
86
|
+
query: str = ''
|
|
87
|
+
target_id: str = ''
|
|
88
|
+
project_id: str = ''
|
|
89
|
+
detail: str = 'summary'
|
|
90
|
+
relation: str | None = None
|
|
91
|
+
cursor: str = ''
|
|
92
|
+
max_items: int = 12
|
|
93
|
+
max_chars: int = 12000
|
|
94
|
+
graph: str = 'full'
|
|
95
|
+
direction: str = 'out'
|
|
96
|
+
depth: int = 4
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_mapping(cls, mapping: Mapping[str, object]) -> AgentRequest:
|
|
100
|
+
if not isinstance(mapping, Mapping):
|
|
101
|
+
raise AgentProtocolError('invalid_request', 'Request must be a mapping.')
|
|
102
|
+
|
|
103
|
+
unknown_fields = sorted(set(mapping) - _REQUEST_FIELDS, key=str)
|
|
104
|
+
if unknown_fields:
|
|
105
|
+
names = ', '.join(str(field) for field in unknown_fields)
|
|
106
|
+
noun = 'field' if len(unknown_fields) == 1 else 'fields'
|
|
107
|
+
raise AgentProtocolError('unknown_field', f'Unknown request {noun}: {names}.')
|
|
108
|
+
if 'action' not in mapping:
|
|
109
|
+
raise AgentProtocolError('missing_field', 'Missing required request field: action.')
|
|
110
|
+
|
|
111
|
+
values = {
|
|
112
|
+
'action': mapping['action'],
|
|
113
|
+
'query': mapping.get('query', ''),
|
|
114
|
+
'target_id': mapping.get('target_id', ''),
|
|
115
|
+
'project_id': mapping.get('project_id', ''),
|
|
116
|
+
'detail': mapping.get('detail', 'summary'),
|
|
117
|
+
'relation': mapping.get('relation'),
|
|
118
|
+
'cursor': mapping.get('cursor', ''),
|
|
119
|
+
'max_items': mapping.get('max_items', 12),
|
|
120
|
+
'max_chars': mapping.get('max_chars', 12000),
|
|
121
|
+
'graph': mapping.get('graph', 'full'),
|
|
122
|
+
'direction': mapping.get('direction', 'out'),
|
|
123
|
+
'depth': mapping.get('depth', 4),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for field_name in (
|
|
127
|
+
'action',
|
|
128
|
+
'query',
|
|
129
|
+
'target_id',
|
|
130
|
+
'project_id',
|
|
131
|
+
'detail',
|
|
132
|
+
'cursor',
|
|
133
|
+
'graph',
|
|
134
|
+
'direction',
|
|
135
|
+
):
|
|
136
|
+
if not isinstance(values[field_name], str):
|
|
137
|
+
raise AgentProtocolError('invalid_type', f"Field '{field_name}' must be a string.")
|
|
138
|
+
|
|
139
|
+
relation = values['relation']
|
|
140
|
+
if relation is not None and not isinstance(relation, str):
|
|
141
|
+
raise AgentProtocolError('invalid_type', "Field 'relation' must be a string or null.")
|
|
142
|
+
|
|
143
|
+
for field_name in ('max_items', 'max_chars', 'depth'):
|
|
144
|
+
value = values[field_name]
|
|
145
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
146
|
+
raise AgentProtocolError('invalid_type', f"Field '{field_name}' must be an integer.")
|
|
147
|
+
|
|
148
|
+
action = values['action']
|
|
149
|
+
detail = values['detail']
|
|
150
|
+
graph = values['graph']
|
|
151
|
+
direction = values['direction']
|
|
152
|
+
if action not in SUPPORTED_ACTIONS:
|
|
153
|
+
raise AgentProtocolError('invalid_action', f'Unsupported action value: {action!r}.')
|
|
154
|
+
if detail not in SUPPORTED_DETAILS:
|
|
155
|
+
raise AgentProtocolError('invalid_detail', f'Unsupported detail value: {detail!r}.')
|
|
156
|
+
if relation is not None and relation not in SUPPORTED_RELATIONS:
|
|
157
|
+
raise AgentProtocolError('invalid_relation', f'Unsupported relation value: {relation!r}.')
|
|
158
|
+
if graph not in SUPPORTED_GRAPHS:
|
|
159
|
+
raise AgentProtocolError('invalid_graph', f'Unsupported graph value: {graph!r}.')
|
|
160
|
+
if direction not in SUPPORTED_DIRECTIONS:
|
|
161
|
+
raise AgentProtocolError(
|
|
162
|
+
'invalid_direction',
|
|
163
|
+
f'Unsupported direction value: {direction!r}.',
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
max_items = values['max_items']
|
|
167
|
+
max_chars = values['max_chars']
|
|
168
|
+
depth = values['depth']
|
|
169
|
+
if not 1 <= max_items <= 50:
|
|
170
|
+
raise AgentProtocolError(
|
|
171
|
+
'max_items_out_of_range',
|
|
172
|
+
"Field 'max_items' must be between 1 and 50.",
|
|
173
|
+
)
|
|
174
|
+
if not 256 <= max_chars <= 40000:
|
|
175
|
+
raise AgentProtocolError(
|
|
176
|
+
'max_chars_out_of_range',
|
|
177
|
+
"Field 'max_chars' must be between 256 and 40000.",
|
|
178
|
+
)
|
|
179
|
+
if not 1 <= depth <= 16:
|
|
180
|
+
raise AgentProtocolError(
|
|
181
|
+
'depth_out_of_range',
|
|
182
|
+
"Field 'depth' must be between 1 and 16.",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return cls(
|
|
186
|
+
action=action,
|
|
187
|
+
query=values['query'],
|
|
188
|
+
target_id=values['target_id'],
|
|
189
|
+
project_id=values['project_id'],
|
|
190
|
+
detail=detail,
|
|
191
|
+
relation=relation,
|
|
192
|
+
cursor=values['cursor'],
|
|
193
|
+
max_items=max_items,
|
|
194
|
+
max_chars=max_chars,
|
|
195
|
+
graph=graph,
|
|
196
|
+
direction=direction,
|
|
197
|
+
depth=depth,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def to_mapping(self) -> dict[str, object]:
|
|
201
|
+
return {
|
|
202
|
+
'action': self.action,
|
|
203
|
+
'query': self.query,
|
|
204
|
+
'target_id': self.target_id,
|
|
205
|
+
'project_id': self.project_id,
|
|
206
|
+
'detail': self.detail,
|
|
207
|
+
'relation': self.relation,
|
|
208
|
+
'cursor': self.cursor,
|
|
209
|
+
'max_items': self.max_items,
|
|
210
|
+
'max_chars': self.max_chars,
|
|
211
|
+
'graph': self.graph,
|
|
212
|
+
'direction': self.direction,
|
|
213
|
+
'depth': self.depth,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass(frozen=True)
|
|
218
|
+
class Focus:
|
|
219
|
+
project_id: str = ''
|
|
220
|
+
unit_id: str = ''
|
|
221
|
+
target_id: str = ''
|
|
222
|
+
|
|
223
|
+
def to_mapping(self) -> dict[str, str]:
|
|
224
|
+
return {
|
|
225
|
+
'project_id': self.project_id,
|
|
226
|
+
'unit_id': self.unit_id,
|
|
227
|
+
'target_id': self.target_id,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass(frozen=True)
|
|
232
|
+
class Page:
|
|
233
|
+
returned: int = 0
|
|
234
|
+
total: int = 0
|
|
235
|
+
truncated: bool = False
|
|
236
|
+
next_cursor: str = ''
|
|
237
|
+
|
|
238
|
+
def to_mapping(self) -> dict[str, object]:
|
|
239
|
+
return {
|
|
240
|
+
'returned': self.returned,
|
|
241
|
+
'total': self.total,
|
|
242
|
+
'truncated': self.truncated,
|
|
243
|
+
'next_cursor': self.next_cursor,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass(frozen=True)
|
|
248
|
+
class ContextBudget:
|
|
249
|
+
chars: int
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def approx_tokens(self) -> int:
|
|
253
|
+
return (self.chars + 3) // 4
|
|
254
|
+
|
|
255
|
+
def to_mapping(self) -> dict[str, int]:
|
|
256
|
+
return {
|
|
257
|
+
'chars': self.chars,
|
|
258
|
+
'approx_tokens': self.approx_tokens,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass(frozen=True)
|
|
263
|
+
class AgentResponse:
|
|
264
|
+
workspace_revision: str
|
|
265
|
+
focus: Focus
|
|
266
|
+
result: object
|
|
267
|
+
page: Page
|
|
268
|
+
context: ContextBudget
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def schema(self) -> int:
|
|
272
|
+
return SCHEMA_VERSION
|
|
273
|
+
|
|
274
|
+
def to_mapping(self) -> dict[str, object]:
|
|
275
|
+
return {
|
|
276
|
+
'schema': self.schema,
|
|
277
|
+
'workspace_revision': self.workspace_revision,
|
|
278
|
+
'focus': self.focus.to_mapping(),
|
|
279
|
+
'result': self.result,
|
|
280
|
+
'page': self.page.to_mapping(),
|
|
281
|
+
'context': self.context.to_mapping(),
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def make_target_id(
|
|
286
|
+
kind: str,
|
|
287
|
+
relative_path: str,
|
|
288
|
+
qualified_name: str,
|
|
289
|
+
ordinal: int = 0,
|
|
290
|
+
) -> str:
|
|
291
|
+
normalized_path = posixpath.normpath(relative_path.replace('\\', '/'))
|
|
292
|
+
if normalized_path == '.':
|
|
293
|
+
normalized_path = ''
|
|
294
|
+
identity = [
|
|
295
|
+
unicodedata.normalize('NFC', kind).casefold(),
|
|
296
|
+
unicodedata.normalize('NFC', normalized_path).casefold(),
|
|
297
|
+
unicodedata.normalize('NFC', qualified_name).casefold(),
|
|
298
|
+
ordinal,
|
|
299
|
+
]
|
|
300
|
+
encoded = json.dumps(identity, ensure_ascii=False, separators=(',', ':')).encode('utf-8')
|
|
301
|
+
return f'target_v2_{hashlib.sha256(encoded).hexdigest()}'
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def encode_cursor(revision: str, offset: int, fingerprint: str) -> str:
|
|
305
|
+
if not isinstance(revision, str) or not isinstance(fingerprint, str):
|
|
306
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
307
|
+
if isinstance(offset, bool) or not isinstance(offset, int):
|
|
308
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
309
|
+
if offset < 0:
|
|
310
|
+
raise AgentProtocolError('negative_cursor', 'Cursor offset cannot be negative.')
|
|
311
|
+
|
|
312
|
+
payload = {
|
|
313
|
+
'fingerprint': fingerprint,
|
|
314
|
+
'offset': offset,
|
|
315
|
+
'revision': revision,
|
|
316
|
+
'schema': SCHEMA_VERSION,
|
|
317
|
+
}
|
|
318
|
+
encoded = json.dumps(
|
|
319
|
+
payload,
|
|
320
|
+
ensure_ascii=False,
|
|
321
|
+
sort_keys=True,
|
|
322
|
+
separators=(',', ':'),
|
|
323
|
+
).encode('utf-8')
|
|
324
|
+
return base64.urlsafe_b64encode(encoded).decode('ascii').rstrip('=')
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def decode_cursor(cursor: str, expected_revision: str, expected_fingerprint: str) -> int:
|
|
328
|
+
if (
|
|
329
|
+
not isinstance(cursor, str)
|
|
330
|
+
or not cursor
|
|
331
|
+
or not cursor.isascii()
|
|
332
|
+
or any(not (character.isalnum() or character in '-_') for character in cursor)
|
|
333
|
+
):
|
|
334
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
padding = '=' * (-len(cursor) % 4)
|
|
338
|
+
decoded = base64.b64decode(cursor + padding, altchars=b'-_', validate=True)
|
|
339
|
+
payload = json.loads(decoded.decode('utf-8'))
|
|
340
|
+
except (binascii.Error, UnicodeDecodeError, ValueError):
|
|
341
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.') from None
|
|
342
|
+
|
|
343
|
+
expected_fields = {'fingerprint', 'offset', 'revision', 'schema'}
|
|
344
|
+
if not isinstance(payload, dict) or set(payload) != expected_fields:
|
|
345
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
346
|
+
if type(payload['schema']) is not int or payload['schema'] != SCHEMA_VERSION:
|
|
347
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
348
|
+
if not isinstance(payload['revision'], str) or not isinstance(payload['fingerprint'], str):
|
|
349
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
350
|
+
|
|
351
|
+
offset = payload['offset']
|
|
352
|
+
if isinstance(offset, bool) or not isinstance(offset, int):
|
|
353
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
354
|
+
if offset < 0:
|
|
355
|
+
raise AgentProtocolError('negative_cursor', 'Cursor offset cannot be negative.')
|
|
356
|
+
if payload['revision'] != expected_revision:
|
|
357
|
+
raise AgentProtocolError('stale_cursor', 'Cursor workspace revision is stale.')
|
|
358
|
+
if payload['fingerprint'] != expected_fingerprint:
|
|
359
|
+
raise AgentProtocolError('cursor_mismatch', 'Cursor fingerprint does not match.')
|
|
360
|
+
return offset
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def paginate_items(
|
|
364
|
+
items: Iterable[object],
|
|
365
|
+
revision: str,
|
|
366
|
+
fingerprint: str,
|
|
367
|
+
max_items: int,
|
|
368
|
+
max_chars: int,
|
|
369
|
+
cursor: str = '',
|
|
370
|
+
) -> tuple[Page, list[object]]:
|
|
371
|
+
for field_name, value in (('max_items', max_items), ('max_chars', max_chars)):
|
|
372
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
373
|
+
raise AgentProtocolError('invalid_type', f"Field '{field_name}' must be an integer.")
|
|
374
|
+
if not 1 <= max_items <= 50:
|
|
375
|
+
raise AgentProtocolError(
|
|
376
|
+
'max_items_out_of_range',
|
|
377
|
+
"Field 'max_items' must be between 1 and 50.",
|
|
378
|
+
)
|
|
379
|
+
if not 256 <= max_chars <= 40000:
|
|
380
|
+
raise AgentProtocolError(
|
|
381
|
+
'max_chars_out_of_range',
|
|
382
|
+
"Field 'max_chars' must be between 256 and 40000.",
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
offset = decode_cursor(cursor, revision, fingerprint) if cursor else 0
|
|
386
|
+
all_items = items if isinstance(items, Sequence) else tuple(items)
|
|
387
|
+
if offset > len(all_items):
|
|
388
|
+
raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
|
|
389
|
+
|
|
390
|
+
selected: list[object] = []
|
|
391
|
+
selected_chars = 2 # Compact JSON brackets around the selected list.
|
|
392
|
+
for index in range(offset, len(all_items)):
|
|
393
|
+
if len(selected) >= max_items:
|
|
394
|
+
break
|
|
395
|
+
item = all_items[index]
|
|
396
|
+
try:
|
|
397
|
+
serialized = json.dumps(
|
|
398
|
+
item,
|
|
399
|
+
ensure_ascii=False,
|
|
400
|
+
sort_keys=True,
|
|
401
|
+
separators=(',', ':'),
|
|
402
|
+
allow_nan=False,
|
|
403
|
+
)
|
|
404
|
+
except (TypeError, ValueError):
|
|
405
|
+
raise AgentProtocolError('invalid_item', 'Item is not JSON-compatible.') from None
|
|
406
|
+
|
|
407
|
+
item_chars = len(serialized)
|
|
408
|
+
separator_chars = 1 if selected else 0
|
|
409
|
+
if item_chars > max_chars:
|
|
410
|
+
raise AgentProtocolError('item_too_large', 'Serialized item exceeds max_chars.')
|
|
411
|
+
if selected_chars + separator_chars + item_chars > max_chars:
|
|
412
|
+
if not selected:
|
|
413
|
+
raise AgentProtocolError('item_too_large', 'Serialized item exceeds max_chars.')
|
|
414
|
+
break
|
|
415
|
+
selected.append(item)
|
|
416
|
+
selected_chars += separator_chars + item_chars
|
|
417
|
+
|
|
418
|
+
next_offset = offset + len(selected)
|
|
419
|
+
truncated = next_offset < len(all_items)
|
|
420
|
+
next_cursor = encode_cursor(revision, next_offset, fingerprint) if truncated else ''
|
|
421
|
+
page = Page(
|
|
422
|
+
returned=len(selected),
|
|
423
|
+
total=len(all_items),
|
|
424
|
+
truncated=truncated,
|
|
425
|
+
next_cursor=next_cursor,
|
|
426
|
+
)
|
|
427
|
+
return page, selected
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
__all__ = [
|
|
431
|
+
'SCHEMA_VERSION',
|
|
432
|
+
'SUPPORTED_ACTIONS',
|
|
433
|
+
'SUPPORTED_DETAILS',
|
|
434
|
+
'SUPPORTED_RELATIONS',
|
|
435
|
+
'AgentProtocolError',
|
|
436
|
+
'AgentRequest',
|
|
437
|
+
'Focus',
|
|
438
|
+
'Page',
|
|
439
|
+
'ContextBudget',
|
|
440
|
+
'AgentResponse',
|
|
441
|
+
'make_target_id',
|
|
442
|
+
'encode_cursor',
|
|
443
|
+
'decode_cursor',
|
|
444
|
+
'paginate_items',
|
|
445
|
+
]
|