pi-durable-python 1.3.0__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.
Files changed (90) hide show
  1. pi_durable/__init__.py +320 -0
  2. pi_durable/context.py +328 -0
  3. pi_durable/delta.py +269 -0
  4. pi_durable/documents.py +366 -0
  5. pi_durable/entries.py +66 -0
  6. pi_durable/env/__init__.py +408 -0
  7. pi_durable/env/decode.py +48 -0
  8. pi_durable/env/line_scan.py +190 -0
  9. pi_durable/env/posix.py +1547 -0
  10. pi_durable/errors.py +89 -0
  11. pi_durable/harness/__init__.py +1 -0
  12. pi_durable/harness/agent.py +344 -0
  13. pi_durable/harness/compaction.py +673 -0
  14. pi_durable/harness/context.py +515 -0
  15. pi_durable/harness/define.py +111 -0
  16. pi_durable/harness/events.py +766 -0
  17. pi_durable/harness/generation.py +1079 -0
  18. pi_durable/harness/harness.py +570 -0
  19. pi_durable/harness/inbox.py +255 -0
  20. pi_durable/harness/live.py +374 -0
  21. pi_durable/harness/output.py +467 -0
  22. pi_durable/harness/prompt.py +288 -0
  23. pi_durable/harness/provider.py +78 -0
  24. pi_durable/harness/registry.py +136 -0
  25. pi_durable/harness/scheduler.py +1977 -0
  26. pi_durable/harness/submissions.py +357 -0
  27. pi_durable/harness/task_graph.py +334 -0
  28. pi_durable/harness/tool.py +1365 -0
  29. pi_durable/harness/types.py +772 -0
  30. pi_durable/harness/usage.py +88 -0
  31. pi_durable/harness/util.py +204 -0
  32. pi_durable/harness/validation.py +460 -0
  33. pi_durable/harness/view.py +324 -0
  34. pi_durable/ids.py +63 -0
  35. pi_durable/json.py +186 -0
  36. pi_durable/providers/__init__.py +362 -0
  37. pi_durable/providers/anthropic.py +603 -0
  38. pi_durable/providers/base.py +97 -0
  39. pi_durable/providers/json_parse.py +117 -0
  40. pi_durable/providers/openai.py +574 -0
  41. pi_durable/providers/request.py +35 -0
  42. pi_durable/providers/retry.py +127 -0
  43. pi_durable/providers/stream.py +183 -0
  44. pi_durable/providers/transcript.py +224 -0
  45. pi_durable/providers/usage.py +58 -0
  46. pi_durable/py.typed +0 -0
  47. pi_durable/search.py +115 -0
  48. pi_durable/session/__init__.py +5 -0
  49. pi_durable/session/drafts.py +762 -0
  50. pi_durable/session/forks.py +109 -0
  51. pi_durable/session/observation.py +374 -0
  52. pi_durable/session/session.py +1193 -0
  53. pi_durable/session/transaction.py +1088 -0
  54. pi_durable/storage/__init__.py +5 -0
  55. pi_durable/storage/jsonl/__init__.py +15 -0
  56. pi_durable/storage/jsonl/storage.py +958 -0
  57. pi_durable/storage/memory.py +889 -0
  58. pi_durable/storage/scan.py +57 -0
  59. pi_durable/storage/sqlite/__init__.py +26 -0
  60. pi_durable/storage/sqlite/database.py +62 -0
  61. pi_durable/storage/sqlite/migrations.py +191 -0
  62. pi_durable/storage/sqlite/posix.py +314 -0
  63. pi_durable/storage/sqlite/schema.py +97 -0
  64. pi_durable/storage/sqlite/storage.py +1130 -0
  65. pi_durable/tasks.py +89 -0
  66. pi_durable/testing/__init__.py +41 -0
  67. pi_durable/testing/assertions.py +44 -0
  68. pi_durable/testing/env_conformance.py +828 -0
  69. pi_durable/testing/runner.py +64 -0
  70. pi_durable/testing/storage_conformance.py +2008 -0
  71. pi_durable/testing/types.py +99 -0
  72. pi_durable/tool_search/__init__.py +159 -0
  73. pi_durable/tools/__init__.py +81 -0
  74. pi_durable/tools/_values.py +43 -0
  75. pi_durable/tools/bash.py +250 -0
  76. pi_durable/tools/edit.py +214 -0
  77. pi_durable/tools/edit_diff.py +452 -0
  78. pi_durable/tools/env.py +19 -0
  79. pi_durable/tools/file_mutation_queue.py +68 -0
  80. pi_durable/tools/image.py +248 -0
  81. pi_durable/tools/image_processor.py +77 -0
  82. pi_durable/tools/path_utils.py +111 -0
  83. pi_durable/tools/read.py +506 -0
  84. pi_durable/tools/write.py +71 -0
  85. pi_durable/truncate.py +185 -0
  86. pi_durable/types.py +1297 -0
  87. pi_durable_python-1.3.0.dist-info/METADATA +276 -0
  88. pi_durable_python-1.3.0.dist-info/RECORD +90 -0
  89. pi_durable_python-1.3.0.dist-info/WHEEL +4 -0
  90. pi_durable_python-1.3.0.dist-info/licenses/LICENSE +21 -0
pi_durable/search.py ADDED
@@ -0,0 +1,115 @@
1
+ """BM25 tool search for the ``tool_search`` tool.
2
+
3
+ Port of the v1.3.0 ``src/search.ts``, the ranking the coding agent uses and durable keeps its own
4
+ copy of. Terms are lowercased, split at camel-case boundaries and non-alphanumerics, stripped of
5
+ stop words, and stemmed with the same naive singular rule. Okapi BM25 (``k1`` 1.2, ``b`` 0.75)
6
+ orders documents by score; equal scores keep document order.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import math
12
+ import re
13
+ from collections.abc import Sequence
14
+ from typing import TypedDict
15
+
16
+ K1 = 1.2
17
+ B = 0.75
18
+
19
+ STOP_WORDS = frozenset(
20
+ {
21
+ "a",
22
+ "an",
23
+ "and",
24
+ "are",
25
+ "as",
26
+ "at",
27
+ "be",
28
+ "by",
29
+ "for",
30
+ "from",
31
+ "in",
32
+ "is",
33
+ "it",
34
+ "of",
35
+ "on",
36
+ "or",
37
+ "that",
38
+ "the",
39
+ "this",
40
+ "to",
41
+ "with",
42
+ }
43
+ )
44
+
45
+ _CAMEL_BOUNDARY = re.compile(r"([a-z0-9])([A-Z])")
46
+ _ACRONYM_BOUNDARY = re.compile(r"([A-Z]+)([A-Z][a-z])")
47
+ _PLURAL_ES = re.compile(r"(ches|shes|sses|xes|zes)$")
48
+
49
+
50
+ class SearchDocument(TypedDict):
51
+ """One searchable tool: its name and all text the ranker reads."""
52
+
53
+ name: str
54
+ text: str
55
+
56
+
57
+ def _stem(term: str) -> str:
58
+ """Naive singular form, so ``issues`` matches ``issue`` and ``searches`` matches ``search``."""
59
+
60
+ if len(term) > 4 and term.endswith("ies"):
61
+ return f"{term[:-3]}y"
62
+ if len(term) > 4 and _PLURAL_ES.search(term):
63
+ return term[:-2]
64
+ if len(term) > 3 and term.endswith("s") and not term.endswith("ss"):
65
+ return term[:-1]
66
+ return term
67
+
68
+
69
+ def tokenize(text: str) -> list[str]:
70
+ """Lowercase terms, split at camelCase boundaries and non-alphanumerics, without stop words."""
71
+
72
+ separated = _CAMEL_BOUNDARY.sub(r"\1 \2", text)
73
+ separated = _ACRONYM_BOUNDARY.sub(r"\1 \2", separated)
74
+ return [
75
+ _stem(term) for term in re.split(r"[^a-z0-9]+", separated.lower()) if term and term not in STOP_WORDS
76
+ ]
77
+
78
+
79
+ def rank_documents(query: str, documents: Sequence[SearchDocument], limit: int) -> list[str]:
80
+ """Okapi BM25 names of the best matches, best first; ties keep document order."""
81
+
82
+ query_terms = list(dict.fromkeys(tokenize(query)))
83
+ if not query_terms or not documents or limit <= 0:
84
+ return []
85
+ term_counts = [_counts(tokenize(document["text"])) for document in documents]
86
+ lengths = [sum(counts.values()) for counts in term_counts]
87
+ total_length = sum(lengths)
88
+ average_length = total_length / len(documents) if total_length else 1.0
89
+ idf: dict[str, float] = {}
90
+ for term in query_terms:
91
+ frequency = sum(1 for counts in term_counts if term in counts)
92
+ idf[term] = math.log(1 + (len(documents) - frequency + 0.5) / (frequency + 0.5))
93
+ matches: list[tuple[str, float]] = []
94
+ for index, document in enumerate(documents):
95
+ score = 0.0
96
+ norm = K1 * (1 - B + (B * lengths[index]) / average_length)
97
+ for term in query_terms:
98
+ count = term_counts[index].get(term)
99
+ if not count:
100
+ continue
101
+ score += idf[term] * ((count * (K1 + 1)) / (count + norm))
102
+ if score > 0:
103
+ matches.append((document["name"], score))
104
+ matches.sort(key=lambda match: -match[1])
105
+ return [name for name, _score in matches[:limit]]
106
+
107
+
108
+ def _counts(terms: Sequence[str]) -> dict[str, int]:
109
+ counts: dict[str, int] = {}
110
+ for term in terms:
111
+ counts[term] = counts.get(term, 0) + 1
112
+ return counts
113
+
114
+
115
+ __all__ = ["SearchDocument", "rank_documents", "tokenize"]
@@ -0,0 +1,5 @@
1
+ """Session kernel exports."""
2
+
3
+ from .session import DocumentState, SessionImpl, create_session
4
+
5
+ __all__ = ["DocumentState", "SessionImpl", "create_session"]