power-loop 3.2.0__tar.gz → 3.4.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.
- {power_loop-3.2.0 → power_loop-3.4.0}/PKG-INFO +1 -1
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/__init__.py +3 -1
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/representation.py +111 -33
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop.egg-info/PKG-INFO +1 -1
- {power_loop-3.2.0 → power_loop-3.4.0}/LICENSE +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/README.md +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/anthropic_factory.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/capabilities.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/interface.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/llm_factory.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/llm_tooling.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/llm_utils.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/_vendor/llm_client/multimodal.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/agent/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/agent/follow_up.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/agent/sink.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/agent/stateful_loop.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/agent/system_prompt.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/agent/types.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/errors.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/event_payloads.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/events.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/handlers.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/hook_contexts.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/hooks.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/messages.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/protocols.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contracts/tools.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contrib/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contrib/_redact.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contrib/jsonl_sink.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contrib/logging_sink.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contrib/mcp.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contrib/metrics_sink.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/contrib/otel_sink.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/core/agent_context.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/core/events.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/core/hooks.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/core/phase.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/core/pipeline.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/core/runner.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/core/state.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/py.typed +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/blackboard.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/budget.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/cancellation.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/compact.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/env.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/exec_backend.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/fold.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/fold_adapter.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/history_projector.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/history_sanitize.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/human_input.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/memory.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/notes.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/provider.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/retry.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/runtime_state.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/session_store.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/skills.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/spec.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/mysql.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/postgres.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/backends/sqlite.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/capabilities.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/db.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/dialect.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/factory.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/schema.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/store.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/store/types.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/structured.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/stub_provider.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/runtime/timers.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/tools/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/tools/blackboard.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/tools/default_manifest.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/tools/default_tools.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/tools/registry.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/tools/spawn_agent.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/__init__.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/api.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/engine.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/introspect.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/journal.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/result.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/resume.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/runner.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/spec.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/subprocess_executor.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/tool.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop/workflow/worker.py +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop.egg-info/SOURCES.txt +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop.egg-info/dependency_links.txt +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop.egg-info/requires.txt +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/power_loop.egg-info/top_level.txt +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/pyproject.toml +0 -0
- {power_loop-3.2.0 → power_loop-3.4.0}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ Stability tiers
|
|
|
15
15
|
无版本承诺,可随时变更或删除。
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "3.
|
|
18
|
+
__version__ = "3.4.0"
|
|
19
19
|
|
|
20
20
|
# Public LLM contract (SDK-free) re-exported so callers (e.g. writing llm.* hooks or
|
|
21
21
|
# a custom LLMService) don't reach into the internal vendored transport package (H3.4).
|
|
@@ -177,6 +177,7 @@ from power_loop.runtime.representation import (
|
|
|
177
177
|
ProjectedRepresentation,
|
|
178
178
|
ProjectedRow,
|
|
179
179
|
ProjectedSend,
|
|
180
|
+
ProjectionRenderConfig,
|
|
180
181
|
Representation,
|
|
181
182
|
VerbatimRepresentation,
|
|
182
183
|
)
|
|
@@ -338,6 +339,7 @@ __all__ = [
|
|
|
338
339
|
"Representation",
|
|
339
340
|
"VerbatimRepresentation",
|
|
340
341
|
"ProjectedRepresentation",
|
|
342
|
+
"ProjectionRenderConfig",
|
|
341
343
|
"FoldStrategy",
|
|
342
344
|
"FoldContext",
|
|
343
345
|
"FoldResult",
|
|
@@ -23,6 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
import json
|
|
24
24
|
from collections import deque
|
|
25
25
|
from dataclasses import dataclass, field
|
|
26
|
+
from dataclasses import fields as dataclass_fields
|
|
26
27
|
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
27
28
|
|
|
28
29
|
from power_loop.runtime.store.types import MessageRow, ProjectMessageRow
|
|
@@ -40,6 +41,7 @@ __all__ = [
|
|
|
40
41
|
"Representation",
|
|
41
42
|
"VerbatimRepresentation",
|
|
42
43
|
"ProjectedRepresentation",
|
|
44
|
+
"ProjectionRenderConfig",
|
|
43
45
|
"ProjectedRow",
|
|
44
46
|
"ProjectedSend",
|
|
45
47
|
]
|
|
@@ -175,21 +177,60 @@ class VerbatimRepresentation:
|
|
|
175
177
|
# ── Projection (generic structured summary; no LLM) ─────────────────────────────
|
|
176
178
|
|
|
177
179
|
|
|
180
|
+
@dataclass
|
|
181
|
+
class ProjectionRenderConfig:
|
|
182
|
+
"""Tunable format knobs for :meth:`ProjectedRepresentation.render` — they change the SHAPE of the
|
|
183
|
+
rendered text only, never what is stored. Every field is a plain scalar so the whole config
|
|
184
|
+
round-trips through JSON (a host can surface it in an admin UI and retune the rendered context
|
|
185
|
+
live, then pass it back via ``ProjectedRepresentation(render_config=…)``). Placeholders: ``{n}`` in
|
|
186
|
+
a tag → the send_index (empty tag or ``None`` index → no tag); ``{range}`` in ``fold_note`` → the
|
|
187
|
+
folded ``#lo–#hi`` span. The defaults reproduce the built-in rendering exactly."""
|
|
188
|
+
|
|
189
|
+
user_tag: str = "[#{n}] " # prefix on a user/input row; {n}=send_index
|
|
190
|
+
project_tag: str = "#{n} " # prefix on a project/assistant row
|
|
191
|
+
tools_header: str = "[tools] " # before the tool list in a project row
|
|
192
|
+
tool_sep: str = "; " # between tools
|
|
193
|
+
tool_arg_sep: str = ", " # between a tool's k=v fields
|
|
194
|
+
include_tools: bool = True # render the tool list at all
|
|
195
|
+
include_final_text: bool = True # render the project row's trailing final_text
|
|
196
|
+
empty_project: str = "(no output)" # a project row that renders to nothing
|
|
197
|
+
fold_note: str = "[older sends {range} folded — recall_send(send_index=N) to expand]"
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def from_dict(cls, data: dict[str, Any] | None) -> ProjectionRenderConfig:
|
|
201
|
+
"""Build from a plain dict (e.g. JSON config), silently ignoring unknown keys."""
|
|
202
|
+
if not data:
|
|
203
|
+
return cls()
|
|
204
|
+
names = {f.name for f in dataclass_fields(cls)}
|
|
205
|
+
return cls(**{k: v for k, v in data.items() if k in names})
|
|
206
|
+
|
|
207
|
+
|
|
178
208
|
@dataclass
|
|
179
209
|
class ProjectedRepresentation:
|
|
180
210
|
"""Generic, deterministic, no-LLM per-send projection. Each send →
|
|
181
|
-
``user`` row: ``{"
|
|
211
|
+
``user`` row: ``{"input": [<user/trigger inputs, verbatim>]}`` (a LIST — folded follow-ups
|
|
212
|
+
preserved; pre-3.3 rows used the key ``human``) +
|
|
182
213
|
``project`` row: ``{"tools": [...], "final_text": ...}``. Each tool call is summarized via its
|
|
183
214
|
``ToolDefinition.project`` hook when present, else a truncating fallback. Rendered to terse
|
|
184
215
|
plain text with NO tool-protocol structure. (This is the old ``DefaultDeterministicProjector``
|
|
185
|
-
MINUS its fold knobs/``compact()`` — folding now lives on :class:`FoldStrategy`.)
|
|
216
|
+
MINUS its fold knobs/``compact()`` — folding now lives on :class:`FoldStrategy`.)
|
|
217
|
+
|
|
218
|
+
The render is customizable two ways without copy-pasting it: pass a :class:`ProjectionRenderConfig`
|
|
219
|
+
(``render_config=`` — JSON-friendly format knobs, retunable from config/an admin UI) and/or
|
|
220
|
+
override one of the small per-row methods (``render_row`` → ``render_user_row`` /
|
|
221
|
+
``render_project_row`` / ``render_compact_row``). Defaults reproduce the prior output exactly."""
|
|
186
222
|
|
|
187
223
|
kind: str = "projection"
|
|
188
224
|
version: int = 1
|
|
189
225
|
max_chars: int = 300 # per-field truncation budget
|
|
226
|
+
#: Format knobs for render() — see :class:`ProjectionRenderConfig`. A plain dict is coerced (so a
|
|
227
|
+
#: host can pass JSON config straight through). Subclasses can ALSO override the render_* methods.
|
|
228
|
+
render_config: ProjectionRenderConfig = field(default_factory=ProjectionRenderConfig)
|
|
190
229
|
|
|
191
230
|
def __post_init__(self) -> None:
|
|
192
231
|
_validate_representation_params(version=self.version, max_chars=self.max_chars)
|
|
232
|
+
if isinstance(self.render_config, dict):
|
|
233
|
+
self.render_config = ProjectionRenderConfig.from_dict(self.render_config)
|
|
193
234
|
|
|
194
235
|
def project_send(
|
|
195
236
|
self, send_rows: list[MessageRow], *, send_index: int, tool_registry: ToolRegistry | None
|
|
@@ -220,9 +261,13 @@ class ProjectedRepresentation:
|
|
|
220
261
|
seqs = [r.seq for r in send_rows]
|
|
221
262
|
rows: list[ProjectedRow] = []
|
|
222
263
|
if users:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
264
|
+
# The INPUT side of a send (the user/trigger turn) is kept VERBATIM — it is the actual
|
|
265
|
+
# conversation content, it is short relative to tool output, and truncating it would drop
|
|
266
|
+
# context the model genuinely needs. Only the assistant's WORK (tool args/results +
|
|
267
|
+
# final_text) is compressed, which is where the token savings actually are. Key is
|
|
268
|
+
# ``input`` (the input turn — not necessarily a human; a multi-agent host feeds another
|
|
269
|
+
# agent's message here); pre-3.3 rows used ``human`` and are still read (see render()).
|
|
270
|
+
rows.append(ProjectedRow("user", {"input": [u.content for u in users]}))
|
|
226
271
|
rows.append(
|
|
227
272
|
ProjectedRow(
|
|
228
273
|
"project",
|
|
@@ -259,50 +304,83 @@ class ProjectedRepresentation:
|
|
|
259
304
|
return {"name": name, "result": _truncate(result_str, self.max_chars)}
|
|
260
305
|
|
|
261
306
|
# rendering ----------------------------------------------------------------
|
|
307
|
+
# Orchestration only; the per-row shapes live in small overridable methods (override exactly the
|
|
308
|
+
# one you want instead of copy-pasting render) and the format lives in render_config (retune from
|
|
309
|
+
# config, no code). Each rendered send is tagged with its ``#N`` send_index so the model can call
|
|
310
|
+
# recall_send(send_index=N) on a folded earlier turn — the tool docstring + the host's
|
|
311
|
+
# RECALL_SEND_NOTE tell it to use "the #N the summary shows", so the tags MUST be emitted (else
|
|
312
|
+
# recall_send is undiscoverable); changing user_tag/project_tag away from a ``{n}`` form is at the
|
|
313
|
+
# host's own risk.
|
|
262
314
|
def render(self, rows: list[ProjectMessageRow]) -> list[LoopMessage]:
|
|
263
|
-
# Each rendered send is tagged with its ``#N`` send_index so the model can call
|
|
264
|
-
# recall_send(send_index=N) on a folded/compacted earlier turn — the tool docstring and the
|
|
265
|
-
# host's RECALL_SEND_NOTE both tell it to use "the #N the summary shows", so render MUST
|
|
266
|
-
# actually emit them (else recall_send is undiscoverable). The folded compact carries its
|
|
267
|
-
# covered range.
|
|
268
315
|
out: list[LoopMessage] = []
|
|
269
316
|
for r in rows:
|
|
270
|
-
|
|
271
|
-
if
|
|
272
|
-
humans = (r.content or {}).get("human") or []
|
|
273
|
-
tag = f"[#{si}] " if si is not None else ""
|
|
274
|
-
out.append({"role": "user", "content": tag + "\n".join(str(h) for h in humans)})
|
|
275
|
-
elif r.kind == "project":
|
|
276
|
-
tag = f"#{si} " if si is not None else ""
|
|
277
|
-
out.append({"role": "assistant", "content": tag + self._render_project(r.content)})
|
|
278
|
-
elif r.kind == "compact":
|
|
279
|
-
msg = _render_compact_row(r)
|
|
280
|
-
lo, hi = r.compact_from_send, r.compact_to_send
|
|
281
|
-
if lo is not None and hi is not None and hi >= lo > 0:
|
|
282
|
-
rng = f"#{lo}" if lo == hi else f"#{lo}–#{hi}"
|
|
283
|
-
msg = {
|
|
284
|
-
"role": "user",
|
|
285
|
-
"content": f"[older sends {rng} folded — recall_send(send_index=N) to "
|
|
286
|
-
f"expand]\n{msg['content']}",
|
|
287
|
-
}
|
|
317
|
+
msg = self.render_row(r)
|
|
318
|
+
if msg is not None:
|
|
288
319
|
out.append(msg)
|
|
289
320
|
return out
|
|
290
321
|
|
|
322
|
+
def render_row(self, r: ProjectMessageRow) -> LoopMessage | None:
|
|
323
|
+
"""Dispatch one stored row to its per-kind renderer. Override to add a kind / change routing;
|
|
324
|
+
an unhandled kind returns None (skipped)."""
|
|
325
|
+
if r.kind == "user":
|
|
326
|
+
return self.render_user_row(r)
|
|
327
|
+
if r.kind == "project":
|
|
328
|
+
return self.render_project_row(r)
|
|
329
|
+
if r.kind == "compact":
|
|
330
|
+
return self.render_compact_row(r)
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
def render_user_row(self, r: ProjectMessageRow) -> LoopMessage:
|
|
334
|
+
content = r.content or {}
|
|
335
|
+
# ``input`` since 3.3; ``human`` is the pre-3.3 key — read both so old rows still render.
|
|
336
|
+
inputs = content.get("input")
|
|
337
|
+
if inputs is None:
|
|
338
|
+
inputs = content.get("human") or []
|
|
339
|
+
return {
|
|
340
|
+
"role": "user",
|
|
341
|
+
"content": self._send_tag(self.render_config.user_tag, r.send_index)
|
|
342
|
+
+ "\n".join(str(h) for h in inputs),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
def render_project_row(self, r: ProjectMessageRow) -> LoopMessage:
|
|
346
|
+
return {
|
|
347
|
+
"role": "assistant",
|
|
348
|
+
"content": self._send_tag(self.render_config.project_tag, r.send_index)
|
|
349
|
+
+ self._render_project(r.content),
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
def render_compact_row(self, r: ProjectMessageRow) -> LoopMessage:
|
|
353
|
+
msg = _render_compact_row(r)
|
|
354
|
+
lo, hi = r.compact_from_send, r.compact_to_send
|
|
355
|
+
if lo is not None and hi is not None and hi >= lo > 0:
|
|
356
|
+
rng = f"#{lo}" if lo == hi else f"#{lo}–#{hi}"
|
|
357
|
+
note = self.render_config.fold_note.replace("{range}", rng)
|
|
358
|
+
return {"role": "user", "content": f"{note}\n{msg['content']}"}
|
|
359
|
+
return msg
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def _send_tag(template: str, send_index: int | None) -> str:
|
|
363
|
+
"""A send_index tag from a ``{n}`` template; empty template or ``None`` index → no tag."""
|
|
364
|
+
if send_index is None or not template:
|
|
365
|
+
return ""
|
|
366
|
+
return template.replace("{n}", str(send_index))
|
|
367
|
+
|
|
291
368
|
def _render_tool(self, t: dict[str, Any]) -> str:
|
|
292
369
|
name = t.get("name", "?")
|
|
293
370
|
rest = {k: v for k, v in (t or {}).items() if k != "name"}
|
|
294
371
|
if not rest:
|
|
295
372
|
return str(name)
|
|
296
|
-
body =
|
|
373
|
+
body = self.render_config.tool_arg_sep.join(f"{k}={v}" for k, v in rest.items())
|
|
297
374
|
return f"{name}({body})"
|
|
298
375
|
|
|
299
376
|
def _render_project(self, content: dict[str, Any] | None) -> str:
|
|
300
377
|
content = content or {}
|
|
378
|
+
cfg = self.render_config
|
|
301
379
|
parts: list[str] = []
|
|
302
380
|
tools = content.get("tools") or []
|
|
303
|
-
if tools:
|
|
304
|
-
parts.append(
|
|
381
|
+
if tools and cfg.include_tools:
|
|
382
|
+
parts.append(cfg.tools_header + cfg.tool_sep.join(self._render_tool(t) for t in tools))
|
|
305
383
|
ft = content.get("final_text")
|
|
306
|
-
if ft:
|
|
384
|
+
if ft and cfg.include_final_text:
|
|
307
385
|
parts.append(str(ft))
|
|
308
|
-
return "\n".join(parts) if parts else
|
|
386
|
+
return "\n".join(parts) if parts else cfg.empty_project
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|