bazaar-compute-node 0.1.3__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.
- bazaar_compute_node/__init__.py +3 -0
- bazaar_compute_node/app/__init__.py +1 -0
- bazaar_compute_node/app/application.py +398 -0
- bazaar_compute_node/app/attachments.py +154 -0
- bazaar_compute_node/app/command.py +342 -0
- bazaar_compute_node/app/config.py +121 -0
- bazaar_compute_node/app/registry.py +120 -0
- bazaar_compute_node/app/transport.py +264 -0
- bazaar_compute_node/app/windows_pipe.py +463 -0
- bazaar_compute_node/app/wrapper.py +63 -0
- bazaar_compute_node/bcc.py +524 -0
- bazaar_compute_node/cli.py +382 -0
- bazaar_compute_node/contrib/__init__.py +1 -0
- bazaar_compute_node/contrib/codex_app_server/__init__.py +63 -0
- bazaar_compute_node/contrib/codex_app_server/approval.py +168 -0
- bazaar_compute_node/contrib/codex_app_server/client.py +408 -0
- bazaar_compute_node/contrib/codex_app_server/events.py +431 -0
- bazaar_compute_node/contrib/codex_app_server/plugin.py +15 -0
- bazaar_compute_node/contrib/codex_app_server/process.py +583 -0
- bazaar_compute_node/contrib/codex_app_server/protocol.py +103 -0
- bazaar_compute_node/contrib/codex_app_server/runtime.py +513 -0
- bazaar_compute_node/contrib/logging/__init__.py +5 -0
- bazaar_compute_node/contrib/logging/audit.py +61 -0
- bazaar_compute_node/contrib/logging/plugin.py +11 -0
- bazaar_compute_node/contrib/sqlite/__init__.py +14 -0
- bazaar_compute_node/contrib/sqlite/codec.py +768 -0
- bazaar_compute_node/contrib/sqlite/database.py +282 -0
- bazaar_compute_node/contrib/sqlite/migrations.py +646 -0
- bazaar_compute_node/contrib/sqlite/plugin.py +11 -0
- bazaar_compute_node/contrib/sqlite/repository.py +1059 -0
- bazaar_compute_node/contrib/wecom/__init__.py +1 -0
- bazaar_compute_node/contrib/wecom/channel.py +960 -0
- bazaar_compute_node/contrib/wecom/markdown.py +146 -0
- bazaar_compute_node/contrib/wecom/plugin.py +29 -0
- bazaar_compute_node/core/__init__.py +5 -0
- bazaar_compute_node/core/approval.py +51 -0
- bazaar_compute_node/core/audit.py +101 -0
- bazaar_compute_node/core/channel.py +121 -0
- bazaar_compute_node/core/client.py +30 -0
- bazaar_compute_node/core/command.py +85 -0
- bazaar_compute_node/core/concurrency.py +29 -0
- bazaar_compute_node/core/correlation.py +48 -0
- bazaar_compute_node/core/instruction.py +224 -0
- bazaar_compute_node/core/lifecycle.py +48 -0
- bazaar_compute_node/core/models/__init__.py +63 -0
- bazaar_compute_node/core/models/entities.py +514 -0
- bazaar_compute_node/core/models/states.py +369 -0
- bazaar_compute_node/core/observability.py +47 -0
- bazaar_compute_node/core/orchestration/__init__.py +5 -0
- bazaar_compute_node/core/orchestration/command.py +614 -0
- bazaar_compute_node/core/orchestration/services.py +135 -0
- bazaar_compute_node/core/orchestration/session.py +891 -0
- bazaar_compute_node/core/orchestration/turn.py +451 -0
- bazaar_compute_node/core/outcomes.py +51 -0
- bazaar_compute_node/core/paths.py +19 -0
- bazaar_compute_node/core/runtime.py +118 -0
- bazaar_compute_node/core/storage.py +167 -0
- bazaar_compute_node-0.1.3.dist-info/METADATA +178 -0
- bazaar_compute_node-0.1.3.dist-info/RECORD +62 -0
- bazaar_compute_node-0.1.3.dist-info/WHEEL +4 -0
- bazaar_compute_node-0.1.3.dist-info/entry_points.txt +15 -0
- bazaar_compute_node-0.1.3.dist-info/licenses/LICENSE +613 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ...core.client import ClientInfo
|
|
8
|
+
from .process import JsonlProcessSupervisor
|
|
9
|
+
from .protocol import (
|
|
10
|
+
CodexAppServerProtocolError,
|
|
11
|
+
JsonlMessage,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class CodexThreadInfo:
|
|
17
|
+
"""Provider-local thread identity returned by App Server."""
|
|
18
|
+
|
|
19
|
+
thread_id: str
|
|
20
|
+
session_id: str | None = None
|
|
21
|
+
path: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class CodexTurnInfo:
|
|
26
|
+
"""Provider-local turn identity and terminal status."""
|
|
27
|
+
|
|
28
|
+
turn_id: str
|
|
29
|
+
status: str
|
|
30
|
+
error_message: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class CodexErrorInfo:
|
|
35
|
+
"""Provider-local error notification fields needed by the runtime adapter."""
|
|
36
|
+
|
|
37
|
+
thread_id: str
|
|
38
|
+
turn_id: str
|
|
39
|
+
will_retry: bool
|
|
40
|
+
message: str
|
|
41
|
+
error_type: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_initialize_params(
|
|
45
|
+
client_info: ClientInfo,
|
|
46
|
+
) -> dict[str, object]:
|
|
47
|
+
return {
|
|
48
|
+
"clientInfo": {
|
|
49
|
+
"name": client_info.name,
|
|
50
|
+
"version": client_info.version,
|
|
51
|
+
},
|
|
52
|
+
"capabilities": {"experimentalApi": True},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_thread_start_params(
|
|
57
|
+
developer_instructions: str,
|
|
58
|
+
*,
|
|
59
|
+
model: str | None = None,
|
|
60
|
+
approval_policy: str | None = None,
|
|
61
|
+
cwd: Path | None = None,
|
|
62
|
+
ephemeral: bool | None = None,
|
|
63
|
+
) -> dict[str, object]:
|
|
64
|
+
"""Build provider-local parameters for a Codex ``thread/start`` request."""
|
|
65
|
+
|
|
66
|
+
_validate_non_empty_string("developer_instructions", developer_instructions)
|
|
67
|
+
params: dict[str, object] = {
|
|
68
|
+
"developerInstructions": developer_instructions,
|
|
69
|
+
}
|
|
70
|
+
if model is not None:
|
|
71
|
+
_validate_non_empty_string("model", model)
|
|
72
|
+
params["model"] = model
|
|
73
|
+
if approval_policy is not None:
|
|
74
|
+
_validate_non_empty_string("approval_policy", approval_policy)
|
|
75
|
+
params["approvalPolicy"] = approval_policy
|
|
76
|
+
if cwd is not None:
|
|
77
|
+
params["cwd"] = _path_text(cwd, "cwd")
|
|
78
|
+
if ephemeral is not None:
|
|
79
|
+
if not isinstance(ephemeral, bool):
|
|
80
|
+
raise TypeError("ephemeral must be a bool or None")
|
|
81
|
+
params["ephemeral"] = ephemeral
|
|
82
|
+
return params
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_thread_resume_params(
|
|
86
|
+
thread_id: str,
|
|
87
|
+
*,
|
|
88
|
+
model: str | None = None,
|
|
89
|
+
approval_policy: str | None = None,
|
|
90
|
+
cwd: Path | None = None,
|
|
91
|
+
) -> dict[str, object]:
|
|
92
|
+
"""Build provider-local parameters for a persisted thread resume."""
|
|
93
|
+
|
|
94
|
+
_validate_non_empty_string("thread_id", thread_id)
|
|
95
|
+
params: dict[str, object] = {
|
|
96
|
+
"threadId": thread_id,
|
|
97
|
+
"excludeTurns": True,
|
|
98
|
+
}
|
|
99
|
+
if model is not None:
|
|
100
|
+
_validate_non_empty_string("model", model)
|
|
101
|
+
params["model"] = model
|
|
102
|
+
if approval_policy is not None:
|
|
103
|
+
_validate_non_empty_string("approval_policy", approval_policy)
|
|
104
|
+
params["approvalPolicy"] = approval_policy
|
|
105
|
+
if cwd is not None:
|
|
106
|
+
params["cwd"] = _path_text(cwd, "cwd")
|
|
107
|
+
return params
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_turn_start_params(
|
|
111
|
+
thread_id: str,
|
|
112
|
+
input_text: str,
|
|
113
|
+
*,
|
|
114
|
+
client_user_message_id: str | None = None,
|
|
115
|
+
model: str | None = None,
|
|
116
|
+
effort: str | None = None,
|
|
117
|
+
approval_policy: str | None = None,
|
|
118
|
+
cwd: Path | None = None,
|
|
119
|
+
sandbox_policy: Mapping[str, object] | None = None,
|
|
120
|
+
) -> dict[str, object]:
|
|
121
|
+
"""Build a text turn request with optional model and reasoning effort."""
|
|
122
|
+
|
|
123
|
+
_validate_non_empty_string("thread_id", thread_id)
|
|
124
|
+
_validate_non_empty_string("input_text", input_text)
|
|
125
|
+
params: dict[str, object] = {
|
|
126
|
+
"threadId": thread_id,
|
|
127
|
+
"input": [{"type": "text", "text": input_text}],
|
|
128
|
+
}
|
|
129
|
+
if client_user_message_id is not None:
|
|
130
|
+
_validate_non_empty_string("client_user_message_id", client_user_message_id)
|
|
131
|
+
params["clientUserMessageId"] = client_user_message_id
|
|
132
|
+
if model is not None:
|
|
133
|
+
_validate_non_empty_string("model", model)
|
|
134
|
+
params["model"] = model
|
|
135
|
+
if effort is not None:
|
|
136
|
+
_validate_non_empty_string("effort", effort)
|
|
137
|
+
params["effort"] = effort
|
|
138
|
+
if approval_policy is not None:
|
|
139
|
+
_validate_non_empty_string("approval_policy", approval_policy)
|
|
140
|
+
params["approvalPolicy"] = approval_policy
|
|
141
|
+
if cwd is not None:
|
|
142
|
+
params["cwd"] = _path_text(cwd, "cwd")
|
|
143
|
+
if sandbox_policy is not None:
|
|
144
|
+
if not sandbox_policy:
|
|
145
|
+
raise ValueError("sandbox_policy must not be empty")
|
|
146
|
+
params["sandboxPolicy"] = dict(sandbox_policy)
|
|
147
|
+
return params
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def build_turn_interrupt_params(thread_id: str, turn_id: str) -> dict[str, object]:
|
|
151
|
+
_validate_non_empty_string("thread_id", thread_id)
|
|
152
|
+
_validate_non_empty_string("turn_id", turn_id)
|
|
153
|
+
return {"threadId": thread_id, "turnId": turn_id}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class CodexAppServerClient:
|
|
157
|
+
"""Typed facade over the adapter-local Codex JSONL supervisor."""
|
|
158
|
+
|
|
159
|
+
def __init__(self, supervisor: JsonlProcessSupervisor) -> None:
|
|
160
|
+
self.supervisor = supervisor
|
|
161
|
+
|
|
162
|
+
async def initialize(
|
|
163
|
+
self,
|
|
164
|
+
*,
|
|
165
|
+
client_info: ClientInfo,
|
|
166
|
+
timeout: float,
|
|
167
|
+
) -> JsonlMessage:
|
|
168
|
+
response = await self.supervisor.request(
|
|
169
|
+
"initialize",
|
|
170
|
+
build_initialize_params(client_info),
|
|
171
|
+
timeout=timeout,
|
|
172
|
+
)
|
|
173
|
+
await self.supervisor.notify("initialized", timeout=timeout)
|
|
174
|
+
return response
|
|
175
|
+
|
|
176
|
+
async def start_thread(
|
|
177
|
+
self,
|
|
178
|
+
developer_instructions: str,
|
|
179
|
+
*,
|
|
180
|
+
model: str | None = None,
|
|
181
|
+
approval_policy: str | None = None,
|
|
182
|
+
cwd: Path | None = None,
|
|
183
|
+
ephemeral: bool | None = None,
|
|
184
|
+
timeout: float,
|
|
185
|
+
) -> JsonlMessage:
|
|
186
|
+
return await self.supervisor.request(
|
|
187
|
+
"thread/start",
|
|
188
|
+
build_thread_start_params(
|
|
189
|
+
developer_instructions,
|
|
190
|
+
model=model,
|
|
191
|
+
approval_policy=approval_policy,
|
|
192
|
+
cwd=cwd,
|
|
193
|
+
ephemeral=ephemeral,
|
|
194
|
+
),
|
|
195
|
+
timeout=timeout,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
async def resume_thread(
|
|
199
|
+
self,
|
|
200
|
+
thread_id: str,
|
|
201
|
+
*,
|
|
202
|
+
model: str | None = None,
|
|
203
|
+
approval_policy: str | None = None,
|
|
204
|
+
cwd: Path | None = None,
|
|
205
|
+
timeout: float,
|
|
206
|
+
) -> JsonlMessage:
|
|
207
|
+
return await self.supervisor.request(
|
|
208
|
+
"thread/resume",
|
|
209
|
+
build_thread_resume_params(
|
|
210
|
+
thread_id,
|
|
211
|
+
model=model,
|
|
212
|
+
approval_policy=approval_policy,
|
|
213
|
+
cwd=cwd,
|
|
214
|
+
),
|
|
215
|
+
timeout=timeout,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
async def read_thread(
|
|
219
|
+
self,
|
|
220
|
+
thread_id: str,
|
|
221
|
+
*,
|
|
222
|
+
include_turns: bool = True,
|
|
223
|
+
timeout: float,
|
|
224
|
+
) -> JsonlMessage:
|
|
225
|
+
_validate_non_empty_string("thread_id", thread_id)
|
|
226
|
+
return await self.supervisor.request(
|
|
227
|
+
"thread/read",
|
|
228
|
+
{"threadId": thread_id, "includeTurns": include_turns},
|
|
229
|
+
timeout=timeout,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
async def start_turn(
|
|
233
|
+
self,
|
|
234
|
+
thread_id: str,
|
|
235
|
+
input_text: str,
|
|
236
|
+
*,
|
|
237
|
+
client_user_message_id: str | None = None,
|
|
238
|
+
model: str | None = None,
|
|
239
|
+
effort: str | None = None,
|
|
240
|
+
approval_policy: str | None = None,
|
|
241
|
+
cwd: Path | None = None,
|
|
242
|
+
sandbox_policy: Mapping[str, object] | None = None,
|
|
243
|
+
timeout: float,
|
|
244
|
+
) -> JsonlMessage:
|
|
245
|
+
return await self.supervisor.request(
|
|
246
|
+
"turn/start",
|
|
247
|
+
build_turn_start_params(
|
|
248
|
+
thread_id,
|
|
249
|
+
input_text,
|
|
250
|
+
client_user_message_id=client_user_message_id,
|
|
251
|
+
model=model,
|
|
252
|
+
effort=effort,
|
|
253
|
+
approval_policy=approval_policy,
|
|
254
|
+
cwd=cwd,
|
|
255
|
+
sandbox_policy=sandbox_policy,
|
|
256
|
+
),
|
|
257
|
+
timeout=timeout,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
async def interrupt_turn(
|
|
261
|
+
self,
|
|
262
|
+
thread_id: str,
|
|
263
|
+
turn_id: str,
|
|
264
|
+
*,
|
|
265
|
+
timeout: float,
|
|
266
|
+
) -> JsonlMessage:
|
|
267
|
+
return await self.supervisor.request(
|
|
268
|
+
"turn/interrupt",
|
|
269
|
+
build_turn_interrupt_params(thread_id, turn_id),
|
|
270
|
+
timeout=timeout,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
async def receive(self, *, timeout: float | None = None) -> JsonlMessage:
|
|
274
|
+
return await self.supervisor.receive(timeout=timeout)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def parse_thread_response(response: Mapping[str, object]) -> CodexThreadInfo:
|
|
278
|
+
result = _require_mapping(response, "result")
|
|
279
|
+
thread = _require_mapping(result, "thread")
|
|
280
|
+
thread_id = _require_text(thread, "id", "thread.id")
|
|
281
|
+
return CodexThreadInfo(
|
|
282
|
+
thread_id=thread_id,
|
|
283
|
+
session_id=_optional_text(thread.get("sessionId"), "thread.sessionId"),
|
|
284
|
+
path=_optional_text(thread.get("path"), "thread.path"),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def parse_turn_response(response: Mapping[str, object]) -> CodexTurnInfo:
|
|
289
|
+
result = _require_mapping(response, "result")
|
|
290
|
+
return _parse_turn(_require_mapping(result, "turn"))
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def parse_turn_notification(message: Mapping[str, object]) -> tuple[str, CodexTurnInfo]:
|
|
294
|
+
params = _require_mapping(message, "params")
|
|
295
|
+
thread_id = _require_text(params, "threadId", "params.threadId")
|
|
296
|
+
turn = _parse_turn(_require_mapping(params, "turn"))
|
|
297
|
+
return thread_id, turn
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def parse_error_notification(message: Mapping[str, object]) -> CodexErrorInfo:
|
|
301
|
+
params = _require_mapping(message, "params")
|
|
302
|
+
thread_id = _require_text(params, "threadId", "params.threadId")
|
|
303
|
+
turn_id = _require_text(params, "turnId", "params.turnId")
|
|
304
|
+
will_retry = params.get("willRetry")
|
|
305
|
+
if not isinstance(will_retry, bool):
|
|
306
|
+
raise CodexAppServerProtocolError("params.willRetry must be a bool")
|
|
307
|
+
error = _require_mapping(params, "error")
|
|
308
|
+
message_text = _require_text(error, "message", "params.error.message")
|
|
309
|
+
error_type = _error_type(error.get("codexErrorInfo"))
|
|
310
|
+
return CodexErrorInfo(
|
|
311
|
+
thread_id=thread_id,
|
|
312
|
+
turn_id=turn_id,
|
|
313
|
+
will_retry=will_retry,
|
|
314
|
+
message=message_text,
|
|
315
|
+
error_type=error_type,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _parse_turn(value: Mapping[str, object]) -> CodexTurnInfo:
|
|
320
|
+
turn_id = _require_text(value, "id", "turn.id")
|
|
321
|
+
status = _require_text(value, "status", "turn.status")
|
|
322
|
+
error_value = value.get("error")
|
|
323
|
+
error_message = None
|
|
324
|
+
if error_value is not None:
|
|
325
|
+
if not isinstance(error_value, Mapping):
|
|
326
|
+
raise CodexAppServerProtocolError("turn.error must be an object")
|
|
327
|
+
error_message = _require_text(
|
|
328
|
+
error_value,
|
|
329
|
+
"message",
|
|
330
|
+
"turn.error.message",
|
|
331
|
+
)
|
|
332
|
+
return CodexTurnInfo(
|
|
333
|
+
turn_id=turn_id,
|
|
334
|
+
status=status,
|
|
335
|
+
error_message=error_message,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _error_type(value: object) -> str | None:
|
|
340
|
+
if value is None:
|
|
341
|
+
return None
|
|
342
|
+
if isinstance(value, str) and value:
|
|
343
|
+
return value
|
|
344
|
+
if isinstance(value, Mapping):
|
|
345
|
+
if len(value) != 1:
|
|
346
|
+
raise CodexAppServerProtocolError(
|
|
347
|
+
"params.error.codexErrorInfo must contain one error type"
|
|
348
|
+
)
|
|
349
|
+
key = next(iter(value))
|
|
350
|
+
if not isinstance(key, str) or not key:
|
|
351
|
+
raise CodexAppServerProtocolError(
|
|
352
|
+
"params.error.codexErrorInfo has an invalid error type"
|
|
353
|
+
)
|
|
354
|
+
return key
|
|
355
|
+
raise CodexAppServerProtocolError(
|
|
356
|
+
"params.error.codexErrorInfo must be text, an object, or null"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _require_mapping(value: Mapping[str, object], key: str) -> Mapping[str, object]:
|
|
361
|
+
nested = value.get(key)
|
|
362
|
+
if not isinstance(nested, Mapping):
|
|
363
|
+
raise CodexAppServerProtocolError(f"{key} must be an object")
|
|
364
|
+
return nested
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _require_text(value: Mapping[str, object], key: str, field_name: str) -> str:
|
|
368
|
+
nested = value.get(key)
|
|
369
|
+
if not isinstance(nested, str) or not nested:
|
|
370
|
+
raise CodexAppServerProtocolError(f"{field_name} must be non-empty text")
|
|
371
|
+
return nested
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _optional_text(value: object, field_name: str) -> str | None:
|
|
375
|
+
if value is None:
|
|
376
|
+
return None
|
|
377
|
+
if not isinstance(value, str) or not value:
|
|
378
|
+
raise CodexAppServerProtocolError(f"{field_name} must be non-empty text")
|
|
379
|
+
return value
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _path_text(value: Path, field_name: str) -> str:
|
|
383
|
+
if not isinstance(value, Path):
|
|
384
|
+
raise TypeError(f"{field_name} must be a Path")
|
|
385
|
+
return str(value)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _validate_non_empty_string(name: str, value: str) -> None:
|
|
389
|
+
if not isinstance(value, str) or not value:
|
|
390
|
+
raise ValueError(f"{name} must be a non-empty string")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
__all__ = [
|
|
394
|
+
"CodexAppServerClient",
|
|
395
|
+
"CodexAppServerProtocolError",
|
|
396
|
+
"CodexErrorInfo",
|
|
397
|
+
"CodexThreadInfo",
|
|
398
|
+
"CodexTurnInfo",
|
|
399
|
+
"build_initialize_params",
|
|
400
|
+
"build_thread_resume_params",
|
|
401
|
+
"build_thread_start_params",
|
|
402
|
+
"build_turn_interrupt_params",
|
|
403
|
+
"build_turn_start_params",
|
|
404
|
+
"parse_error_notification",
|
|
405
|
+
"parse_thread_response",
|
|
406
|
+
"parse_turn_notification",
|
|
407
|
+
"parse_turn_response",
|
|
408
|
+
]
|