gcf-python 0.3.1__py3-none-any.whl → 0.4.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.
gcf/__init__.py CHANGED
@@ -40,6 +40,7 @@ from .delta import encode_delta
40
40
  from .encode import encode
41
41
  from .generic import encode_generic
42
42
  from .session import Session, encode_with_session
43
+ from .stream import StreamEncoder
43
44
  from .types import Components, DeltaPayload, Edge, Payload, Symbol
44
45
 
45
46
  __all__ = [
@@ -51,6 +52,7 @@ __all__ = [
51
52
  "KIND_EXPAND",
52
53
  "Payload",
53
54
  "Session",
55
+ "StreamEncoder",
54
56
  "Symbol",
55
57
  "decode",
56
58
  "encode",
gcf/stream.py ADDED
@@ -0,0 +1,151 @@
1
+ """GCF streaming encoder: zero-buffering encode to any writable."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ from typing import Any, Protocol
7
+
8
+ from .constants import KIND_ABBREV
9
+ from .types import Edge, Symbol
10
+
11
+
12
+ class StreamWriter(Protocol):
13
+ """Any object with a write(s: str) method."""
14
+
15
+ def write(self, s: str) -> Any: ...
16
+
17
+
18
+ class StreamEncoder:
19
+ """Writes GCF output incrementally as symbols and edges arrive.
20
+
21
+ Zero buffering: each symbol/edge is written immediately. A trailer summary
22
+ is emitted on close() with the final counts.
23
+
24
+ Example::
25
+
26
+ enc = StreamEncoder(sys.stdout, "context_for_task", token_budget=5000)
27
+ enc.write_symbol(sym1) # emitted immediately
28
+ enc.write_edge(edge1) # emitted immediately
29
+ enc.close() # emits ## _summary trailer
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ writer: StreamWriter,
35
+ tool: str,
36
+ *,
37
+ token_budget: int = 0,
38
+ tokens_used: int = 0,
39
+ pack_root: str = "",
40
+ session: bool = False,
41
+ ) -> None:
42
+ self._w = writer
43
+ self._lock = threading.Lock()
44
+ self._sym_index: dict[str, int] = {}
45
+ self._next_id = 0
46
+ self._current_group = ""
47
+ self._group_counts: dict[str, int] = {}
48
+ self._edge_count = 0
49
+ self._edges_started = False
50
+
51
+ # Emit header immediately.
52
+ parts = [f"GCF tool={tool}"]
53
+ if token_budget:
54
+ parts.append(f"budget={token_budget}")
55
+ if tokens_used:
56
+ parts.append(f"tokens={tokens_used}")
57
+ if pack_root:
58
+ parts.append(f"pack_root={pack_root}")
59
+ if session:
60
+ parts.append("session=true")
61
+ self._w.write(" ".join(parts) + "\n")
62
+
63
+ def write_symbol(self, s: Symbol) -> None:
64
+ """Emit a symbol line immediately. Group headers auto-managed."""
65
+ with self._lock:
66
+ group_names = ["targets", "related", "extended"]
67
+ if s.distance < len(group_names):
68
+ group_name = group_names[s.distance]
69
+ else:
70
+ group_name = f"distance_{s.distance}"
71
+
72
+ if group_name != self._current_group:
73
+ self._w.write(f"## {group_name}\n")
74
+ self._current_group = group_name
75
+
76
+ idx = self._next_id
77
+ self._sym_index[s.qualified_name] = idx
78
+ self._next_id += 1
79
+
80
+ kind = KIND_ABBREV.get(s.kind, s.kind)
81
+ self._w.write(f"@{idx} {kind} {s.qualified_name} {s.score:.2f} {s.provenance}\n")
82
+
83
+ self._group_counts[group_name] = self._group_counts.get(group_name, 0) + 1
84
+
85
+ def write_edge(self, e: Edge) -> None:
86
+ """Emit an edge line immediately. Edges section header auto-emitted on first edge."""
87
+ with self._lock:
88
+ src_idx = self._sym_index.get(e.source)
89
+ tgt_idx = self._sym_index.get(e.target)
90
+ if src_idx is None or tgt_idx is None:
91
+ return
92
+
93
+ if not self._edges_started:
94
+ self._w.write("## edges [?]\n")
95
+ self._edges_started = True
96
+
97
+ line = f"@{tgt_idx}<@{src_idx} {e.edge_type}"
98
+ if e.status and e.status != "unchanged":
99
+ line += f" {e.status}"
100
+ self._w.write(line + "\n")
101
+ self._edge_count += 1
102
+
103
+ def write_bare_ref(self, qname: str, distance: int) -> None:
104
+ """Emit a bare reference for a previously-transmitted symbol (session mode)."""
105
+ with self._lock:
106
+ group_names = ["targets", "related", "extended"]
107
+ if distance < len(group_names):
108
+ group_name = group_names[distance]
109
+ else:
110
+ group_name = f"distance_{distance}"
111
+
112
+ if group_name != self._current_group:
113
+ self._w.write(f"## {group_name}\n")
114
+ self._current_group = group_name
115
+
116
+ idx = self._next_id
117
+ self._sym_index[qname] = idx
118
+ self._next_id += 1
119
+ self._w.write(f"@{idx} # previously transmitted\n")
120
+ self._group_counts[group_name] = self._group_counts.get(group_name, 0) + 1
121
+
122
+ def close(self) -> None:
123
+ """Emit ## _summary trailer with final counts."""
124
+ with self._lock:
125
+ sections: list[str] = []
126
+ group_order = ["targets", "related", "extended"]
127
+
128
+ for g in group_order:
129
+ c = self._group_counts.get(g, 0)
130
+ if c > 0:
131
+ sections.append(f"{g}:{c}")
132
+ for g, c in self._group_counts.items():
133
+ if g not in group_order and c > 0:
134
+ sections.append(f"{g}:{c}")
135
+ if self._edge_count > 0:
136
+ sections.append(f"edges:{self._edge_count}")
137
+
138
+ self._w.write(
139
+ f"## _summary symbols={self._next_id} edges={self._edge_count}"
140
+ f" sections={','.join(sections)}\n"
141
+ )
142
+
143
+ @property
144
+ def symbol_count(self) -> int:
145
+ """Number of symbols written so far."""
146
+ return self._next_id
147
+
148
+ @property
149
+ def edge_count(self) -> int:
150
+ """Number of edges written so far."""
151
+ return self._edge_count
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gcf-python
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Python implementation of GCF (Graph Compact Format): token-optimized wire format for LLM tool responses
5
5
  Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
6
6
  Project-URL: Documentation, https://blackwell-systems.github.io/gcf/
@@ -119,6 +119,35 @@ out2 = encode_with_session(payload2, sess) # reused symbols as "@N # previousl
119
119
 
120
120
  By the 5th call in a session: 92.7% token savings vs JSON.
121
121
 
122
+ ## Streaming Encode
123
+
124
+ Write GCF output incrementally as symbols and edges arrive. Zero buffering, O(1) memory per row:
125
+
126
+ ```python
127
+ from gcf import StreamEncoder, Symbol, Edge
128
+
129
+ enc = StreamEncoder(sys.stdout, "context_for_task", token_budget=5000)
130
+
131
+ enc.write_symbol(Symbol(qualified_name="pkg.Auth", kind="function", score=0.95, provenance="lsp", distance=0))
132
+ enc.write_symbol(Symbol(qualified_name="pkg.Server", kind="function", score=0.60, provenance="lsp", distance=1))
133
+ enc.write_edge(Edge(source="pkg.Server", target="pkg.Auth", edge_type="calls"))
134
+ enc.close() # emits ## _summary trailer
135
+ ```
136
+
137
+ Output:
138
+ ```
139
+ GCF tool=context_for_task budget=5000
140
+ ## targets
141
+ @0 fn pkg.Auth 0.95 lsp
142
+ ## related
143
+ @1 fn pkg.Server 0.60 lsp
144
+ ## edges [?]
145
+ @0<@1 calls
146
+ ## _summary symbols=2 edges=1 sections=targets:1,related:1,edges:1
147
+ ```
148
+
149
+ The writer is any object with a `write(s: str)` method. Thread-safe. Standard `decode()` handles streaming output with no changes.
150
+
122
151
  ## Delta Encoding
123
152
 
124
153
  When the consumer already has a prior context pack, send only what changed:
@@ -1,4 +1,4 @@
1
- gcf/__init__.py,sha256=epW-CoQh3RBrnF3-lWSiPS-DI4HUlRRe8q9JeLPRxwE,1541
1
+ gcf/__init__.py,sha256=K4n34s5a0cd0MnYng5UvoTaxT1wIjidv7KgUThELmP4,1596
2
2
  gcf/cli.py,sha256=2hSTBqiYcn1_EgGXuO65MHiEGh0C4DRMvspTd2zUaso,4258
3
3
  gcf/constants.py,sha256=cmZ8YJSOB0im_eyfN8v4UvrLpBC6Fuf4cfcKZGbutxY,638
4
4
  gcf/decode.py,sha256=48G6XmBilGYl0-c_Xy7MX0iTwOxGW9bn3wC4UyNApc4,5465
@@ -6,9 +6,10 @@ gcf/delta.py,sha256=xU0ujtSq1iF7yU8yk_WNQKh8iove-WUV_nKSuvW1XVk,1656
6
6
  gcf/encode.py,sha256=Oljb1r5b7SHmng1XYvcvuJMpaRfJVJ81VOyocPf_kAs,2915
7
7
  gcf/generic.py,sha256=Nf1Ii0pYS0dgZ4o2ghl-3Qhnms-kCKY2D3q-3-oGpe8,5454
8
8
  gcf/session.py,sha256=4_ARRL06Tg2CI8D2eyi0V5nFphFAFMfOKBXYnAbI6Nk,4690
9
+ gcf/stream.py,sha256=DBzZrb9t5ldVEsvcRX90SY59Fm0PM4aPt4XBIrJ1Gro,5186
9
10
  gcf/types.py,sha256=AWm-LQoSqLHAYtEjcAxWQZqJ4JXqNreLUKO2mJFgNMA,1465
10
- gcf_python-0.3.1.dist-info/METADATA,sha256=xQxbAQEk1jr_c_GTsFQM1zyCmuz_MmgcbSxc1hNcKz0,8127
11
- gcf_python-0.3.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
12
- gcf_python-0.3.1.dist-info/entry_points.txt,sha256=aFT6gqlkh8iGfM8cblE-LUMxHH08_v71IIoZtDdRIVA,37
13
- gcf_python-0.3.1.dist-info/licenses/LICENSE,sha256=txSvg3E4LugiB7MOOTci6WKd6wMOrOJTvaITeFJ2SgU,1074
14
- gcf_python-0.3.1.dist-info/RECORD,,
11
+ gcf_python-0.4.0.dist-info/METADATA,sha256=pucirOGE46EL70gW8PjeRe1uY4WyzSihRZrn-zryNqk,9074
12
+ gcf_python-0.4.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
13
+ gcf_python-0.4.0.dist-info/entry_points.txt,sha256=aFT6gqlkh8iGfM8cblE-LUMxHH08_v71IIoZtDdRIVA,37
14
+ gcf_python-0.4.0.dist-info/licenses/LICENSE,sha256=txSvg3E4LugiB7MOOTci6WKd6wMOrOJTvaITeFJ2SgU,1074
15
+ gcf_python-0.4.0.dist-info/RECORD,,