axio 0.3.4__tar.gz → 0.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.
- {axio-0.3.4 → axio-0.4.0}/PKG-INFO +3 -2
- {axio-0.3.4 → axio-0.4.0}/README.md +2 -1
- {axio-0.3.4 → axio-0.4.0}/pyproject.toml +1 -1
- {axio-0.3.4 → axio-0.4.0}/src/axio/agent.py +11 -3
- {axio-0.3.4 → axio-0.4.0}/src/axio/events.py +33 -1
- {axio-0.3.4 → axio-0.4.0}/src/axio/models.py +0 -9
- axio-0.4.0/src/axio/selector.py +10 -0
- axio-0.4.0/src/axio/tool_args.py +243 -0
- axio-0.4.0/tests/test_tool_args.py +753 -0
- axio-0.3.4/src/axio/selector.py +0 -121
- axio-0.3.4/tests/test_selector.py +0 -237
- {axio-0.3.4 → axio-0.4.0}/.github/workflows/publish.yml +0 -0
- {axio-0.3.4 → axio-0.4.0}/.github/workflows/tests.yml +0 -0
- {axio-0.3.4 → axio-0.4.0}/.gitignore +0 -0
- {axio-0.3.4 → axio-0.4.0}/LICENSE +0 -0
- {axio-0.3.4 → axio-0.4.0}/Makefile +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/__init__.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/blocks.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/context.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/exceptions.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/messages.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/permission.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/py.typed +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/stream.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/testing.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/tool.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/transport.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/src/axio/types.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/conftest.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_agent_branch.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_agent_permission.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_agent_run.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_agent_stream.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_agent_tools.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_blocks.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_context.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_events.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_exceptions.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_permission.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_stream.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_tool.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_transport.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/tests/test_types.py +0 -0
- {axio-0.3.4 → axio-0.4.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axio
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Minimal, streaming-first, protocol-driven foundation for LLM-powered agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/axio-agent/axio
|
|
6
6
|
Project-URL: Repository, https://github.com/axio-agent/axio
|
|
@@ -77,7 +77,7 @@ asyncio.run(main())
|
|
|
77
77
|
▼
|
|
78
78
|
┌─────────┐ stream() ┌─────────────────────┐
|
|
79
79
|
│ Agent │ ────────────▶ │ CompletionTransport │
|
|
80
|
-
│ loop │ ◀──────────── │ (
|
|
80
|
+
│ loop │ ◀──────────── │ (Anthropic, OpenAI, …) │
|
|
81
81
|
└─────────┘ StreamEvent └─────────────────────┘
|
|
82
82
|
│
|
|
83
83
|
│ tool_use?
|
|
@@ -202,6 +202,7 @@ my_guard = "my_package:MyGuard"
|
|
|
202
202
|
|
|
203
203
|
| Package | Purpose |
|
|
204
204
|
|---|---|
|
|
205
|
+
| [axio-transport-anthropic](https://github.com/axio-agent/axio-transport-anthropic) | Anthropic Claude transport |
|
|
205
206
|
| [axio-transport-openai](https://github.com/axio-agent/axio-transport-openai) | OpenAI-compatible transport |
|
|
206
207
|
| [axio-transport-nebius](https://github.com/axio-agent/axio-transport-nebius) | Nebius AI Studio transport |
|
|
207
208
|
| [axio-transport-codex](https://github.com/axio-agent/axio-transport-codex) | ChatGPT OAuth transport |
|
|
@@ -64,7 +64,7 @@ asyncio.run(main())
|
|
|
64
64
|
▼
|
|
65
65
|
┌─────────┐ stream() ┌─────────────────────┐
|
|
66
66
|
│ Agent │ ────────────▶ │ CompletionTransport │
|
|
67
|
-
│ loop │ ◀──────────── │ (
|
|
67
|
+
│ loop │ ◀──────────── │ (Anthropic, OpenAI, …) │
|
|
68
68
|
└─────────┘ StreamEvent └─────────────────────┘
|
|
69
69
|
│
|
|
70
70
|
│ tool_use?
|
|
@@ -189,6 +189,7 @@ my_guard = "my_package:MyGuard"
|
|
|
189
189
|
|
|
190
190
|
| Package | Purpose |
|
|
191
191
|
|---|---|
|
|
192
|
+
| [axio-transport-anthropic](https://github.com/axio-agent/axio-transport-anthropic) | Anthropic Claude transport |
|
|
192
193
|
| [axio-transport-openai](https://github.com/axio-agent/axio-transport-openai) | OpenAI-compatible transport |
|
|
193
194
|
| [axio-transport-nebius](https://github.com/axio-agent/axio-transport-nebius) | Nebius AI Studio transport |
|
|
194
195
|
| [axio-transport-codex](https://github.com/axio-agent/axio-transport-codex) | ChatGPT OAuth transport |
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
-
from collections.abc import AsyncGenerator
|
|
8
|
+
from collections.abc import AsyncGenerator, Iterable
|
|
9
9
|
from dataclasses import dataclass, field
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
@@ -22,6 +22,7 @@ from axio.events import (
|
|
|
22
22
|
ToolUseStart,
|
|
23
23
|
)
|
|
24
24
|
from axio.messages import Message
|
|
25
|
+
from axio.selector import ToolSelector
|
|
25
26
|
from axio.stream import AgentStream
|
|
26
27
|
from axio.tool import Tool
|
|
27
28
|
from axio.transport import CompletionTransport
|
|
@@ -35,6 +36,7 @@ class Agent:
|
|
|
35
36
|
system: str
|
|
36
37
|
tools: list[Tool]
|
|
37
38
|
transport: CompletionTransport
|
|
39
|
+
selector: ToolSelector | None = field(default=None)
|
|
38
40
|
max_iterations: int = field(default=50)
|
|
39
41
|
|
|
40
42
|
def run_stream(self, user_message: str, context: ContextStore) -> AgentStream:
|
|
@@ -120,6 +122,13 @@ class Agent:
|
|
|
120
122
|
blocks.append(ToolUseBlock(id=tid, name=info["name"], input=inp))
|
|
121
123
|
return blocks, malformed
|
|
122
124
|
|
|
125
|
+
async def _select_tools(self, history: list[Message], tools: list[Tool]) -> Iterable[Tool]:
|
|
126
|
+
if not tools:
|
|
127
|
+
return []
|
|
128
|
+
if not self.selector:
|
|
129
|
+
return tools
|
|
130
|
+
return await self.selector.select(history, tools)
|
|
131
|
+
|
|
123
132
|
async def _run_loop(self, user_message: str, context: ContextStore) -> AsyncGenerator[StreamEvent, None]:
|
|
124
133
|
total_usage = Usage(0, 0)
|
|
125
134
|
session_end_emitted = False
|
|
@@ -129,8 +138,7 @@ class Agent:
|
|
|
129
138
|
for iteration in range(1, self.max_iterations + 1):
|
|
130
139
|
history = await context.get_history()
|
|
131
140
|
logger.info("Iteration %d, history length=%d", iteration, len(history))
|
|
132
|
-
active_tools = self.tools
|
|
133
|
-
|
|
141
|
+
active_tools = list(await self._select_tools(history, self.tools))
|
|
134
142
|
content: list[TextBlock | ToolUseBlock] = []
|
|
135
143
|
pending: dict[str, dict[str, Any]] = {}
|
|
136
144
|
stop_reason = StopReason.end_turn
|
|
@@ -34,6 +34,28 @@ class ToolInputDelta:
|
|
|
34
34
|
partial_json: str
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class ToolFieldStart:
|
|
39
|
+
index: int
|
|
40
|
+
tool_use_id: ToolCallID
|
|
41
|
+
key: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class ToolFieldDelta:
|
|
46
|
+
index: int
|
|
47
|
+
tool_use_id: ToolCallID
|
|
48
|
+
key: str
|
|
49
|
+
text: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True)
|
|
53
|
+
class ToolFieldEnd:
|
|
54
|
+
index: int
|
|
55
|
+
tool_use_id: ToolCallID
|
|
56
|
+
key: str
|
|
57
|
+
|
|
58
|
+
|
|
37
59
|
@dataclass(frozen=True, slots=True)
|
|
38
60
|
class ToolResult:
|
|
39
61
|
tool_use_id: ToolCallID
|
|
@@ -62,5 +84,15 @@ class SessionEndEvent:
|
|
|
62
84
|
|
|
63
85
|
|
|
64
86
|
type StreamEvent = (
|
|
65
|
-
ReasoningDelta
|
|
87
|
+
ReasoningDelta
|
|
88
|
+
| TextDelta
|
|
89
|
+
| ToolUseStart
|
|
90
|
+
| ToolInputDelta
|
|
91
|
+
| ToolFieldStart
|
|
92
|
+
| ToolFieldDelta
|
|
93
|
+
| ToolFieldEnd
|
|
94
|
+
| ToolResult
|
|
95
|
+
| IterationEnd
|
|
96
|
+
| Error
|
|
97
|
+
| SessionEndEvent
|
|
66
98
|
)
|
|
@@ -7,15 +7,6 @@ from dataclasses import dataclass
|
|
|
7
7
|
from enum import StrEnum
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@dataclass(frozen=True, slots=True)
|
|
11
|
-
class TransportMeta:
|
|
12
|
-
"""Metadata a transport plugin declares about itself."""
|
|
13
|
-
|
|
14
|
-
label: str
|
|
15
|
-
api_key_env: str
|
|
16
|
-
role_defaults: dict[str, str]
|
|
17
|
-
|
|
18
|
-
|
|
19
10
|
class Capability(StrEnum):
|
|
20
11
|
text = "text"
|
|
21
12
|
vision = "vision"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from typing import Protocol, runtime_checkable
|
|
3
|
+
|
|
4
|
+
from axio.messages import Message
|
|
5
|
+
from axio.tool import Tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@runtime_checkable
|
|
9
|
+
class ToolSelector(Protocol):
|
|
10
|
+
async def select(self, messages: Iterable[Message], tools: Iterable[Tool]) -> Iterable[Tool]: ...
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Incremental streaming parser for tool call JSON arguments.
|
|
2
|
+
|
|
3
|
+
Feeds partial JSON chunks (from ``ToolInputDelta.partial_json``) and emits
|
|
4
|
+
structured ``ToolField*`` events as top-level object fields are discovered.
|
|
5
|
+
|
|
6
|
+
Top-level *string* values are decoded (escape sequences resolved, quotes
|
|
7
|
+
stripped). All other top-level values are emitted as raw JSON fragments.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Mapping
|
|
13
|
+
from enum import IntEnum
|
|
14
|
+
from types import MappingProxyType
|
|
15
|
+
|
|
16
|
+
from axio.events import ToolFieldDelta, ToolFieldEnd, ToolFieldStart
|
|
17
|
+
|
|
18
|
+
type ToolFieldEvent = ToolFieldStart | ToolFieldDelta | ToolFieldEnd
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class State(IntEnum):
|
|
22
|
+
INIT = 0
|
|
23
|
+
OBJ = 1
|
|
24
|
+
KEY = 2
|
|
25
|
+
COLON = 3
|
|
26
|
+
VAL = 4
|
|
27
|
+
STR = 5
|
|
28
|
+
RAW = 6
|
|
29
|
+
AFTER = 7
|
|
30
|
+
ESC = 8
|
|
31
|
+
UESC = 9
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
ESCAPES: Mapping[str, str] = MappingProxyType(
|
|
35
|
+
{
|
|
36
|
+
"n": "\n",
|
|
37
|
+
"t": "\t",
|
|
38
|
+
"r": "\r",
|
|
39
|
+
"b": "\b",
|
|
40
|
+
"f": "\f",
|
|
41
|
+
'"': '"',
|
|
42
|
+
"\\": "\\",
|
|
43
|
+
"/": "/",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ToolArgStream:
|
|
49
|
+
"""O(1)-per-character streaming parser for tool argument JSON.
|
|
50
|
+
|
|
51
|
+
Usage::
|
|
52
|
+
|
|
53
|
+
stream = ToolArgStream("call_1")
|
|
54
|
+
events = stream.feed('{"path":"/tmp/f')
|
|
55
|
+
# [ToolFieldStart(0, "call_1", "path"), ToolFieldDelta(0, "call_1", "path", "/tmp/f")]
|
|
56
|
+
events = stream.feed('oo.py"}')
|
|
57
|
+
# [ToolFieldDelta(0, "call_1", "path", "oo.py"), ToolFieldEnd(0, "call_1", "path")]
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
__slots__ = (
|
|
61
|
+
"_id",
|
|
62
|
+
"_idx",
|
|
63
|
+
"_st",
|
|
64
|
+
"_key_chars",
|
|
65
|
+
"_key",
|
|
66
|
+
"_buf",
|
|
67
|
+
"_u",
|
|
68
|
+
"_high",
|
|
69
|
+
"_depth",
|
|
70
|
+
"_raw_str",
|
|
71
|
+
"_raw_esc",
|
|
72
|
+
"_esc_key",
|
|
73
|
+
"_esc_ret",
|
|
74
|
+
"_events",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def __init__(self, tool_use_id: str, index: int = 0) -> None:
|
|
78
|
+
self._id = tool_use_id
|
|
79
|
+
self._idx = index
|
|
80
|
+
self._st = State.INIT
|
|
81
|
+
self._key_chars: list[str] = []
|
|
82
|
+
self._key = ""
|
|
83
|
+
self._buf: list[str] = []
|
|
84
|
+
self._u: list[str] = []
|
|
85
|
+
self._high = 0
|
|
86
|
+
self._depth = 0
|
|
87
|
+
self._raw_str = False
|
|
88
|
+
self._raw_esc = False
|
|
89
|
+
self._esc_key = False
|
|
90
|
+
self._esc_ret = State.KEY
|
|
91
|
+
self._events: list[ToolFieldEvent] = []
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def current_key(self) -> str:
|
|
95
|
+
"""The field currently being streamed, or ``""``."""
|
|
96
|
+
return self._key
|
|
97
|
+
|
|
98
|
+
def feed(self, chunk: str) -> list[ToolFieldEvent]:
|
|
99
|
+
"""Process a partial JSON chunk and return any field events produced."""
|
|
100
|
+
self._events = []
|
|
101
|
+
for ch in chunk:
|
|
102
|
+
self._step(ch)
|
|
103
|
+
self._flush()
|
|
104
|
+
return self._events
|
|
105
|
+
|
|
106
|
+
def _flush(self) -> None:
|
|
107
|
+
if self._buf:
|
|
108
|
+
self._events.append(ToolFieldDelta(self._idx, self._id, self._key, "".join(self._buf)))
|
|
109
|
+
self._buf.clear()
|
|
110
|
+
|
|
111
|
+
def _start(self) -> None:
|
|
112
|
+
self._flush()
|
|
113
|
+
self._events.append(ToolFieldStart(self._idx, self._id, self._key))
|
|
114
|
+
|
|
115
|
+
def _end(self) -> None:
|
|
116
|
+
self._flush()
|
|
117
|
+
self._events.append(ToolFieldEnd(self._idx, self._id, self._key))
|
|
118
|
+
|
|
119
|
+
def _step(self, ch: str) -> None: # noqa: PLR0912
|
|
120
|
+
match self._st:
|
|
121
|
+
case State.INIT:
|
|
122
|
+
if ch == "{":
|
|
123
|
+
self._st = State.OBJ
|
|
124
|
+
|
|
125
|
+
case State.OBJ:
|
|
126
|
+
if ch == '"':
|
|
127
|
+
self._key_chars.clear()
|
|
128
|
+
self._st = State.KEY
|
|
129
|
+
elif ch == "}":
|
|
130
|
+
self._st = State.INIT
|
|
131
|
+
|
|
132
|
+
case State.KEY:
|
|
133
|
+
if ch == "\\":
|
|
134
|
+
self._esc_key = True
|
|
135
|
+
self._esc_ret = State.KEY
|
|
136
|
+
self._st = State.ESC
|
|
137
|
+
elif ch == '"':
|
|
138
|
+
self._key = "".join(self._key_chars)
|
|
139
|
+
self._st = State.COLON
|
|
140
|
+
else:
|
|
141
|
+
self._key_chars.append(ch)
|
|
142
|
+
|
|
143
|
+
case State.COLON:
|
|
144
|
+
if ch == ":":
|
|
145
|
+
self._start()
|
|
146
|
+
self._st = State.VAL
|
|
147
|
+
|
|
148
|
+
case State.VAL:
|
|
149
|
+
if ch in " \t\r\n":
|
|
150
|
+
pass
|
|
151
|
+
elif ch == '"':
|
|
152
|
+
self._st = State.STR
|
|
153
|
+
else:
|
|
154
|
+
self._buf.append(ch)
|
|
155
|
+
self._depth = 1 if ch in "{[" else 0
|
|
156
|
+
self._raw_str = False
|
|
157
|
+
self._raw_esc = False
|
|
158
|
+
self._st = State.RAW
|
|
159
|
+
|
|
160
|
+
case State.STR:
|
|
161
|
+
if ch == "\\":
|
|
162
|
+
self._esc_key = False
|
|
163
|
+
self._esc_ret = State.STR
|
|
164
|
+
self._st = State.ESC
|
|
165
|
+
elif ch == '"':
|
|
166
|
+
if self._high:
|
|
167
|
+
self._buf.append("\ufffd")
|
|
168
|
+
self._high = 0
|
|
169
|
+
self._end()
|
|
170
|
+
self._st = State.AFTER
|
|
171
|
+
else:
|
|
172
|
+
if self._high:
|
|
173
|
+
self._buf.append("\ufffd")
|
|
174
|
+
self._high = 0
|
|
175
|
+
self._buf.append(ch)
|
|
176
|
+
|
|
177
|
+
case State.RAW:
|
|
178
|
+
if self._raw_str:
|
|
179
|
+
self._buf.append(ch)
|
|
180
|
+
if self._raw_esc:
|
|
181
|
+
self._raw_esc = False
|
|
182
|
+
elif ch == "\\":
|
|
183
|
+
self._raw_esc = True
|
|
184
|
+
elif ch == '"':
|
|
185
|
+
self._raw_str = False
|
|
186
|
+
elif self._depth == 0 and ch in " \t\r\n,}":
|
|
187
|
+
# simple value (number/bool/null) ends on whitespace or delimiter
|
|
188
|
+
self._end()
|
|
189
|
+
self._st = State.AFTER
|
|
190
|
+
if ch in ",}":
|
|
191
|
+
self._step(ch) # reprocess delimiter
|
|
192
|
+
elif ch == '"':
|
|
193
|
+
self._buf.append(ch)
|
|
194
|
+
self._raw_str = True
|
|
195
|
+
elif ch in "{[":
|
|
196
|
+
self._buf.append(ch)
|
|
197
|
+
self._depth += 1
|
|
198
|
+
elif ch in "}]":
|
|
199
|
+
self._buf.append(ch)
|
|
200
|
+
self._depth -= 1
|
|
201
|
+
if self._depth == 0:
|
|
202
|
+
self._end()
|
|
203
|
+
self._st = State.AFTER
|
|
204
|
+
else:
|
|
205
|
+
self._buf.append(ch)
|
|
206
|
+
|
|
207
|
+
case State.AFTER:
|
|
208
|
+
if ch == ",":
|
|
209
|
+
self._st = State.OBJ
|
|
210
|
+
elif ch == "}":
|
|
211
|
+
self._st = State.INIT
|
|
212
|
+
|
|
213
|
+
case State.ESC:
|
|
214
|
+
if ch == "u":
|
|
215
|
+
self._u.clear()
|
|
216
|
+
self._st = State.UESC
|
|
217
|
+
else:
|
|
218
|
+
dec = ESCAPES.get(ch, ch)
|
|
219
|
+
if self._esc_key:
|
|
220
|
+
self._key_chars.append(dec)
|
|
221
|
+
else:
|
|
222
|
+
self._buf.append(dec)
|
|
223
|
+
self._st = self._esc_ret
|
|
224
|
+
|
|
225
|
+
case State.UESC:
|
|
226
|
+
self._u.append(ch)
|
|
227
|
+
if len(self._u) == 4:
|
|
228
|
+
code = int("".join(self._u), 16)
|
|
229
|
+
if self._esc_key:
|
|
230
|
+
self._key_chars.append(chr(code))
|
|
231
|
+
elif self._high:
|
|
232
|
+
if 0xDC00 <= code <= 0xDFFF:
|
|
233
|
+
full = 0x10000 + (self._high - 0xD800) * 0x400 + (code - 0xDC00)
|
|
234
|
+
self._buf.append(chr(full))
|
|
235
|
+
else:
|
|
236
|
+
self._buf.append("\ufffd")
|
|
237
|
+
self._buf.append(chr(code))
|
|
238
|
+
self._high = 0
|
|
239
|
+
elif 0xD800 <= code <= 0xDBFF:
|
|
240
|
+
self._high = code
|
|
241
|
+
else:
|
|
242
|
+
self._buf.append(chr(code))
|
|
243
|
+
self._st = self._esc_ret
|