dc-python-sdk 1.5.22__tar.gz → 1.5.24__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.
- {dc_python_sdk-1.5.22/src/dc_python_sdk.egg-info → dc_python_sdk-1.5.24}/PKG-INFO +1 -1
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/pyproject.toml +1 -1
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/setup.cfg +1 -1
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24/src/dc_python_sdk.egg-info}/PKG-INFO +1 -1
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/cli.py +3 -4
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/ai_http.py +240 -232
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/server.py +17 -7
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/LICENSE +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/README.md +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/SOURCES.txt +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/dependency_links.txt +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/entry_points.txt +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/requires.txt +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/top_level.txt +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/__init__.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/app.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/errors.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/handler.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/__init__.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/ai.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/mapping.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/__init__.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/enums.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/errors.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/log_templates.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/models/pipeline_details.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/pipeline.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/__init__.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/api.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/aws.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/environment.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/loader.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/logger.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/src/services/session.py +0 -0
- {dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_sdk/types.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from importlib.metadata import version
|
|
3
|
+
import os
|
|
3
4
|
|
|
4
5
|
def get_arg(name, default=None):
|
|
5
6
|
if name in sys.argv:
|
|
@@ -15,15 +16,13 @@ def main():
|
|
|
15
16
|
|
|
16
17
|
command = sys.argv[1]
|
|
17
18
|
|
|
18
|
-
port = int(get_arg("--port", 5000))
|
|
19
|
-
|
|
20
19
|
print("version: ", version("dc-python-sdk"))
|
|
21
20
|
|
|
22
21
|
print(f"[DC SDK] Command: {command}")
|
|
23
22
|
|
|
24
23
|
if command == "http":
|
|
25
24
|
from dc_sdk.src.server import start_server
|
|
26
|
-
start_server(
|
|
25
|
+
start_server()
|
|
27
26
|
|
|
28
27
|
elif command == "ai":
|
|
29
28
|
from dc_sdk.src.ai import start_ai
|
|
@@ -31,7 +30,7 @@ def main():
|
|
|
31
30
|
|
|
32
31
|
elif command == "ai-http":
|
|
33
32
|
from dc_sdk.src.ai_http import start_ai_http
|
|
34
|
-
start_ai_http(
|
|
33
|
+
start_ai_http()
|
|
35
34
|
|
|
36
35
|
else:
|
|
37
36
|
print(f"Unknown command: {command}")
|
|
@@ -18,15 +18,25 @@ logging.basicConfig(level=logging.INFO)
|
|
|
18
18
|
|
|
19
19
|
app = FastAPI()
|
|
20
20
|
|
|
21
|
+
_DEFAULT_PROCESS_TTL_SECONDS = 600.0
|
|
22
|
+
|
|
23
|
+
# Default ports: connector HTTP (dc-sdk http) 5000, AI FastAPI 5001, code-server 5002.
|
|
24
|
+
CONNECTOR_PORT = os.getenv("CONNECTOR_PORT", 5000)
|
|
25
|
+
AI_PORT = os.getenv("AI_PORT", 5001)
|
|
26
|
+
CODE_SERVER_PORT = os.getenv("CODE_SERVER_PORT", 5002)
|
|
27
|
+
|
|
21
28
|
client = None
|
|
22
29
|
workspace = os.getenv("WORKSPACE", "/workspace")
|
|
23
|
-
port_for_connector = 5000
|
|
24
30
|
|
|
25
31
|
# Process-wide TTL for ai-http (see DC_SDK_TTL_SECONDS); exposed in /session-info.
|
|
26
32
|
process_ttl_seconds: Optional[float] = None
|
|
27
33
|
process_ttl_deadline_unix: Optional[float] = None
|
|
28
34
|
_process_ttl_timer: Optional[threading.Timer] = None
|
|
29
35
|
|
|
36
|
+
# Long-lived `dc-sdk http` subprocess (uvicorn with CONNECTOR_HTTP_RELOAD reloads connector.py).
|
|
37
|
+
_connector_proc: Optional[subprocess.Popen] = None
|
|
38
|
+
_connector_lock = threading.Lock()
|
|
39
|
+
|
|
30
40
|
# -----------------------------
|
|
31
41
|
# MODELS
|
|
32
42
|
# -----------------------------
|
|
@@ -65,21 +75,6 @@ class RunRequest(BaseModel):
|
|
|
65
75
|
test_object_ids: Optional[List[str]] = None
|
|
66
76
|
|
|
67
77
|
|
|
68
|
-
# -----------------------------
|
|
69
|
-
# UTIL
|
|
70
|
-
# -----------------------------
|
|
71
|
-
def get_free_port():
|
|
72
|
-
import socket
|
|
73
|
-
s = socket.socket()
|
|
74
|
-
s.bind(('', 0))
|
|
75
|
-
port = s.getsockname()[1]
|
|
76
|
-
s.close()
|
|
77
|
-
return port
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
_DEFAULT_PROCESS_TTL_SECONDS = 600.0
|
|
81
|
-
|
|
82
|
-
|
|
83
78
|
def _parse_process_ttl_seconds() -> float:
|
|
84
79
|
"""
|
|
85
80
|
Wall-clock lifetime for the whole ai-http process from start_ai_http() entry.
|
|
@@ -104,43 +99,68 @@ def _parse_process_ttl_seconds() -> float:
|
|
|
104
99
|
return 0.0
|
|
105
100
|
return v
|
|
106
101
|
|
|
102
|
+
def _wait_connector_healthy() -> None:
|
|
103
|
+
for _ in range(50):
|
|
104
|
+
try:
|
|
105
|
+
res = requests.get(f"http://localhost:{CONNECTOR_PORT}/health", timeout=2)
|
|
106
|
+
if res.status_code == 200:
|
|
107
|
+
logger.info("Connector ready on port %s", CONNECTOR_PORT)
|
|
108
|
+
return
|
|
109
|
+
except Exception:
|
|
110
|
+
logger.debug("Health check not ready yet on port %s", CONNECTOR_PORT)
|
|
111
|
+
time.sleep(0.1)
|
|
112
|
+
raise RuntimeError(
|
|
113
|
+
f"Connector failed to become healthy on port {CONNECTOR_PORT} within timeout"
|
|
114
|
+
)
|
|
115
|
+
|
|
107
116
|
|
|
108
|
-
def
|
|
109
|
-
global workspace
|
|
110
|
-
|
|
117
|
+
def _start_connector_subprocess_unlocked() -> None:
|
|
118
|
+
global _connector_proc, workspace
|
|
119
|
+
env = os.environ.copy()
|
|
120
|
+
env["CONNECTOR_HTTP_RELOAD"] = "1"
|
|
111
121
|
proc = subprocess.Popen(
|
|
112
|
-
["dc-sdk", "http"
|
|
122
|
+
["dc-sdk", "http"],
|
|
113
123
|
cwd=workspace,
|
|
124
|
+
env=env,
|
|
114
125
|
stdout=subprocess.DEVNULL,
|
|
115
|
-
stderr=subprocess.DEVNULL
|
|
126
|
+
stderr=subprocess.DEVNULL,
|
|
116
127
|
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
try:
|
|
129
|
+
_wait_connector_healthy()
|
|
130
|
+
except Exception:
|
|
131
|
+
proc.terminate()
|
|
120
132
|
try:
|
|
121
|
-
|
|
122
|
-
if res.status_code == 200:
|
|
123
|
-
logger.info("Connector ready on port %s", port_for_connector)
|
|
124
|
-
return {"process": proc, "port": port_for_connector}
|
|
133
|
+
proc.wait(timeout=5)
|
|
125
134
|
except Exception:
|
|
126
|
-
|
|
127
|
-
|
|
135
|
+
pass
|
|
136
|
+
raise
|
|
137
|
+
_connector_proc = proc
|
|
128
138
|
|
|
129
|
-
logger.error("Connector failed to become healthy on port %s within timeout", port_for_connector)
|
|
130
|
-
proc.terminate()
|
|
131
|
-
raise Exception("Failed to start connector")
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
def _stop_connector_subprocess_unlocked() -> None:
|
|
141
|
+
global _connector_proc
|
|
142
|
+
proc = _connector_proc
|
|
143
|
+
_connector_proc = None
|
|
144
|
+
if proc is None:
|
|
145
|
+
return
|
|
135
146
|
try:
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
proc.terminate()
|
|
148
|
+
proc.wait(timeout=15)
|
|
138
149
|
logger.debug("Connector subprocess stopped")
|
|
139
150
|
except Exception:
|
|
140
151
|
logger.debug("Error stopping connector subprocess", exc_info=True)
|
|
141
152
|
|
|
142
153
|
|
|
143
|
-
def
|
|
154
|
+
def start_connector_http_server() -> None:
|
|
155
|
+
"""Start the connector HTTP child once; restart if the previous child exited."""
|
|
156
|
+
with _connector_lock:
|
|
157
|
+
if _connector_proc is not None and _connector_proc.poll() is None:
|
|
158
|
+
return
|
|
159
|
+
_stop_connector_subprocess_unlocked()
|
|
160
|
+
_start_connector_subprocess_unlocked()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def invoke(method, session_id=None, credentials=None, params=None):
|
|
144
164
|
payload = {
|
|
145
165
|
"method": method,
|
|
146
166
|
"session_id": session_id,
|
|
@@ -148,21 +168,19 @@ def invoke(port, method, session_id=None, credentials=None, params=None):
|
|
|
148
168
|
"params": params or {}
|
|
149
169
|
}
|
|
150
170
|
|
|
151
|
-
logger.debug("invoke port=%s method=%s session_id=%s",
|
|
152
|
-
res = requests.post(f"http://localhost:{
|
|
171
|
+
logger.debug("invoke port=%s method=%s session_id=%s", CONNECTOR_PORT, method, session_id)
|
|
172
|
+
res = requests.post(f"http://localhost:{CONNECTOR_PORT}/invoke", json=payload)
|
|
153
173
|
if res.status_code != 200:
|
|
154
174
|
logger.warning(
|
|
155
175
|
"invoke returned HTTP %s for method=%s", res.status_code, method
|
|
156
176
|
)
|
|
157
177
|
return res.json()
|
|
158
178
|
|
|
159
|
-
|
|
160
179
|
def read_connector():
|
|
161
180
|
global workspace
|
|
162
181
|
with open(f"{workspace}/src/connector.py", "r") as f:
|
|
163
182
|
return f.read()
|
|
164
183
|
|
|
165
|
-
|
|
166
184
|
def _resolved_connector_context(opt: Optional[ConnectorContext]) -> ConnectorContext:
|
|
167
185
|
from dc_sdk.src.ai import CONNECTOR_CONTEXT as defaults
|
|
168
186
|
|
|
@@ -179,7 +197,6 @@ def _resolved_connector_context(opt: Optional[ConnectorContext]) -> ConnectorCon
|
|
|
179
197
|
merged[key] = value
|
|
180
198
|
return ConnectorContext(**merged)
|
|
181
199
|
|
|
182
|
-
|
|
183
200
|
def _chat_messages_to_openai(messages: Optional[List[ChatMessage]]) -> List[Dict[str, str]]:
|
|
184
201
|
if not messages:
|
|
185
202
|
return []
|
|
@@ -190,7 +207,6 @@ def _chat_messages_to_openai(messages: Optional[List[ChatMessage]]) -> List[Dict
|
|
|
190
207
|
out.append({"role": m.role, "content": m.content})
|
|
191
208
|
return out
|
|
192
209
|
|
|
193
|
-
|
|
194
210
|
def _coerce_prior_dicts(prior: Optional[List[Dict[str, str]]]) -> List[Dict[str, str]]:
|
|
195
211
|
out: List[Dict[str, str]] = []
|
|
196
212
|
for m in prior or []:
|
|
@@ -200,19 +216,16 @@ def _coerce_prior_dicts(prior: Optional[List[Dict[str, str]]]) -> List[Dict[str,
|
|
|
200
216
|
out.append({"role": role, "content": content})
|
|
201
217
|
return out
|
|
202
218
|
|
|
203
|
-
|
|
204
219
|
def write_connector(code):
|
|
205
220
|
global workspace
|
|
206
221
|
with open(f"{workspace}/src/connector.py", "w") as f:
|
|
207
222
|
f.write(code)
|
|
208
223
|
|
|
209
|
-
|
|
210
224
|
def _canon_object_id(value: Any) -> str:
|
|
211
225
|
if value is None:
|
|
212
226
|
return ""
|
|
213
227
|
return str(value).strip()
|
|
214
228
|
|
|
215
|
-
|
|
216
229
|
def _object_ids_from_get_objects_results(objects: List[Any]) -> List[str]:
|
|
217
230
|
seen = set()
|
|
218
231
|
out: List[str] = []
|
|
@@ -225,176 +238,165 @@ def _object_ids_from_get_objects_results(objects: List[Any]) -> List[str]:
|
|
|
225
238
|
out.append(oid)
|
|
226
239
|
return out
|
|
227
240
|
|
|
228
|
-
|
|
229
|
-
# -----------------------------
|
|
230
|
-
# TEST FLOW
|
|
231
|
-
# -----------------------------
|
|
232
241
|
def run_test(
|
|
233
242
|
credentials,
|
|
234
243
|
*,
|
|
235
244
|
connector_context: ConnectorContext,
|
|
236
245
|
test_object_ids: Optional[List[str]] = None,
|
|
237
246
|
):
|
|
238
|
-
runtime = start_connector()
|
|
239
247
|
requested_ids = [_canon_object_id(x) for x in (test_object_ids or []) if _canon_object_id(x)]
|
|
240
248
|
print(requested_ids)
|
|
241
249
|
|
|
242
|
-
|
|
243
|
-
session_id = None
|
|
250
|
+
session_id = None
|
|
244
251
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
# authenticate
|
|
253
|
+
res = invoke("authenticate", None, credentials)
|
|
254
|
+
if res.get("results") is not True:
|
|
255
|
+
logger.warning("run_test failed at stage=authenticate")
|
|
256
|
+
return False, "authenticate", res
|
|
250
257
|
|
|
251
|
-
|
|
258
|
+
session_id = res.get("session_id")
|
|
252
259
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
logger.warning(
|
|
270
|
-
"run_test failed: test_object_ids disjoint from get_objects"
|
|
271
|
-
)
|
|
272
|
-
return False, "get_objects", {
|
|
273
|
-
"message": (
|
|
274
|
-
f"objects_dynamic is true: none of test_object_ids {requested_ids} "
|
|
275
|
-
f"appear in get_objects results {api_ids}."
|
|
276
|
-
),
|
|
277
|
-
}
|
|
278
|
-
ids_for_fields = wanted
|
|
279
|
-
else:
|
|
280
|
-
ids_for_fields = list(api_ids)
|
|
281
|
-
else:
|
|
282
|
-
if not requested_ids:
|
|
260
|
+
# get_objects
|
|
261
|
+
res = invoke("get_objects", session_id)
|
|
262
|
+
if not isinstance(res.get("results"), list):
|
|
263
|
+
logger.warning("run_test failed at stage=get_objects (invalid results type)")
|
|
264
|
+
return False, "get_objects", res
|
|
265
|
+
|
|
266
|
+
objects = res["results"]
|
|
267
|
+
api_ids = _object_ids_from_get_objects_results(objects)
|
|
268
|
+
dynamic = connector_context.objects_dynamic
|
|
269
|
+
|
|
270
|
+
if dynamic:
|
|
271
|
+
if api_ids:
|
|
272
|
+
if requested_ids:
|
|
273
|
+
api_set = set(api_ids)
|
|
274
|
+
wanted = [x for x in requested_ids if x in api_set]
|
|
275
|
+
if not wanted:
|
|
283
276
|
logger.warning(
|
|
284
|
-
"run_test failed:
|
|
277
|
+
"run_test failed: test_object_ids disjoint from get_objects"
|
|
285
278
|
)
|
|
286
279
|
return False, "get_objects", {
|
|
287
280
|
"message": (
|
|
288
|
-
"objects_dynamic is true:
|
|
289
|
-
"
|
|
281
|
+
f"objects_dynamic is true: none of test_object_ids {requested_ids} "
|
|
282
|
+
f"appear in get_objects results {api_ids}."
|
|
290
283
|
),
|
|
291
284
|
}
|
|
292
|
-
ids_for_fields =
|
|
285
|
+
ids_for_fields = wanted
|
|
286
|
+
else:
|
|
287
|
+
ids_for_fields = list(api_ids)
|
|
293
288
|
else:
|
|
294
|
-
print(requested_ids)
|
|
295
289
|
if not requested_ids:
|
|
296
|
-
logger.warning(
|
|
290
|
+
logger.warning(
|
|
291
|
+
"run_test failed: objects_dynamic with empty get_objects and no test_object_ids"
|
|
292
|
+
)
|
|
297
293
|
return False, "get_objects", {
|
|
298
294
|
"message": (
|
|
299
|
-
"objects_dynamic is
|
|
300
|
-
"
|
|
295
|
+
"objects_dynamic is true: get_objects returned no objects. "
|
|
296
|
+
"Provide test_object_ids in the request to probe get_fields and get_data."
|
|
301
297
|
),
|
|
302
298
|
}
|
|
303
|
-
if not objects:
|
|
304
|
-
logger.warning("run_test failed at stage=get_objects (empty list)")
|
|
305
|
-
return False, "get_objects", {"message": "No objects returned from get_objects"}
|
|
306
|
-
|
|
307
299
|
ids_for_fields = list(requested_ids)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
300
|
+
else:
|
|
301
|
+
print(requested_ids)
|
|
302
|
+
if not requested_ids:
|
|
303
|
+
logger.warning("run_test failed: static objects require test_object_ids")
|
|
311
304
|
return False, "get_objects", {
|
|
312
|
-
"message": "No object ids available to test get_fields (empty candidate list).",
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
objects_with_fields: List[Tuple[str, List[Any]]] = []
|
|
316
|
-
last_fields_payload: Any = None
|
|
317
|
-
|
|
318
|
-
print(ids_for_fields)
|
|
319
|
-
|
|
320
|
-
for object_id in ids_for_fields:
|
|
321
|
-
res = invoke(
|
|
322
|
-
runtime["port"],
|
|
323
|
-
"get_fields",
|
|
324
|
-
session_id,
|
|
325
|
-
params={"object_id": object_id},
|
|
326
|
-
)
|
|
327
|
-
if not isinstance(res.get("results"), list):
|
|
328
|
-
logger.warning(
|
|
329
|
-
"run_test failed at stage=get_fields (invalid results type) object_id=%s",
|
|
330
|
-
object_id,
|
|
331
|
-
)
|
|
332
|
-
return False, "get_fields", res
|
|
333
|
-
|
|
334
|
-
fields = res["results"]
|
|
335
|
-
last_fields_payload = res
|
|
336
|
-
logger.info("get_fields results for object_id=%s: %s", object_id, len(fields))
|
|
337
|
-
if fields:
|
|
338
|
-
fids = [
|
|
339
|
-
f.get("field_id") or f.get("id")
|
|
340
|
-
for f in fields[:5]
|
|
341
|
-
if (f.get("field_id") or f.get("id")) is not None
|
|
342
|
-
]
|
|
343
|
-
if fids:
|
|
344
|
-
objects_with_fields.append((object_id, fids))
|
|
345
|
-
|
|
346
|
-
if not objects_with_fields:
|
|
347
|
-
logger.warning(
|
|
348
|
-
"run_test failed: no fields on any object tried ids=%s",
|
|
349
|
-
ids_for_fields,
|
|
350
|
-
)
|
|
351
|
-
return False, "get_fields", {
|
|
352
305
|
"message": (
|
|
353
|
-
|
|
354
|
-
"
|
|
306
|
+
"objects_dynamic is false: test_object_ids is required and must list "
|
|
307
|
+
"object ids you expect get_objects to return."
|
|
355
308
|
),
|
|
356
|
-
"last_invoke": last_fields_payload,
|
|
357
309
|
}
|
|
310
|
+
if not objects:
|
|
311
|
+
logger.warning("run_test failed at stage=get_objects (empty list)")
|
|
312
|
+
return False, "get_objects", {"message": "No objects returned from get_objects"}
|
|
358
313
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
},
|
|
370
|
-
)
|
|
371
|
-
last_data_res = res
|
|
372
|
-
if not isinstance(res.get("results"), dict):
|
|
373
|
-
logger.warning(
|
|
374
|
-
"run_test failed at stage=get_data (invalid results type) object_id=%s",
|
|
375
|
-
object_id,
|
|
376
|
-
)
|
|
377
|
-
return False, "get_data", res
|
|
314
|
+
ids_for_fields = list(requested_ids)
|
|
315
|
+
|
|
316
|
+
if not ids_for_fields:
|
|
317
|
+
logger.warning("run_test failed: no object ids to probe")
|
|
318
|
+
return False, "get_objects", {
|
|
319
|
+
"message": "No object ids available to test get_fields (empty candidate list).",
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
objects_with_fields: List[Tuple[str, List[Any]]] = []
|
|
323
|
+
last_fields_payload: Any = None
|
|
378
324
|
|
|
379
|
-
|
|
380
|
-
rows = payload.get("data")
|
|
381
|
-
if isinstance(rows, list) and len(rows) > 0:
|
|
382
|
-
logger.info("run_test completed successfully object_id=%s, rows retrieved=%s", object_id, len(rows))
|
|
383
|
-
return True, None, None
|
|
325
|
+
print(ids_for_fields)
|
|
384
326
|
|
|
327
|
+
for object_id in ids_for_fields:
|
|
328
|
+
res = invoke(
|
|
329
|
+
"get_fields",
|
|
330
|
+
session_id,
|
|
331
|
+
params={"object_id": object_id},
|
|
332
|
+
)
|
|
333
|
+
if not isinstance(res.get("results"), list):
|
|
334
|
+
logger.warning(
|
|
335
|
+
"run_test failed at stage=get_fields (invalid results type) object_id=%s",
|
|
336
|
+
object_id,
|
|
337
|
+
)
|
|
338
|
+
return False, "get_fields", res
|
|
339
|
+
|
|
340
|
+
fields = res["results"]
|
|
341
|
+
last_fields_payload = res
|
|
342
|
+
logger.info("get_fields results for object_id=%s: %s", object_id, len(fields))
|
|
343
|
+
if fields:
|
|
344
|
+
fids = [
|
|
345
|
+
f.get("field_id") or f.get("id")
|
|
346
|
+
for f in fields[:5]
|
|
347
|
+
if (f.get("field_id") or f.get("id")) is not None
|
|
348
|
+
]
|
|
349
|
+
if fids:
|
|
350
|
+
objects_with_fields.append((object_id, fids))
|
|
351
|
+
|
|
352
|
+
if not objects_with_fields:
|
|
385
353
|
logger.warning(
|
|
386
|
-
"run_test failed:
|
|
354
|
+
"run_test failed: no fields on any object tried ids=%s",
|
|
355
|
+
ids_for_fields,
|
|
387
356
|
)
|
|
388
|
-
return False, "
|
|
357
|
+
return False, "get_fields", {
|
|
389
358
|
"message": (
|
|
390
|
-
f"
|
|
391
|
-
|
|
359
|
+
f"get_fields returned no usable fields for any tested object_id(s): {ids_for_fields}. "
|
|
360
|
+
"Verify object ids and connector field discovery."
|
|
392
361
|
),
|
|
393
|
-
"last_invoke":
|
|
362
|
+
"last_invoke": last_fields_payload,
|
|
394
363
|
}
|
|
395
364
|
|
|
396
|
-
|
|
397
|
-
|
|
365
|
+
last_data_res: Any = None
|
|
366
|
+
for object_id, field_ids in objects_with_fields:
|
|
367
|
+
res = invoke(
|
|
368
|
+
"get_data",
|
|
369
|
+
session_id,
|
|
370
|
+
params={
|
|
371
|
+
"object_id": object_id,
|
|
372
|
+
"field_ids": field_ids,
|
|
373
|
+
"n_rows": 5,
|
|
374
|
+
},
|
|
375
|
+
)
|
|
376
|
+
last_data_res = res
|
|
377
|
+
if not isinstance(res.get("results"), dict):
|
|
378
|
+
logger.warning(
|
|
379
|
+
"run_test failed at stage=get_data (invalid results type) object_id=%s",
|
|
380
|
+
object_id,
|
|
381
|
+
)
|
|
382
|
+
return False, "get_data", res
|
|
383
|
+
|
|
384
|
+
payload = res["results"]
|
|
385
|
+
rows = payload.get("data")
|
|
386
|
+
if isinstance(rows, list) and len(rows) > 0:
|
|
387
|
+
logger.info("run_test completed successfully object_id=%s, rows retrieved=%s", object_id, len(rows))
|
|
388
|
+
return True, None, None
|
|
389
|
+
|
|
390
|
+
logger.warning(
|
|
391
|
+
"run_test failed: get_data returned no rows for any object with fields"
|
|
392
|
+
)
|
|
393
|
+
return False, "get_data", {
|
|
394
|
+
"message": (
|
|
395
|
+
f"get_data returned no rows for any object that had fields "
|
|
396
|
+
f"(tried {len(objects_with_fields)} object(s))."
|
|
397
|
+
),
|
|
398
|
+
"last_invoke": last_data_res,
|
|
399
|
+
}
|
|
398
400
|
|
|
399
401
|
|
|
400
402
|
def _validate_static_requires_object_ids(
|
|
@@ -413,7 +415,6 @@ def _validate_static_requires_object_ids(
|
|
|
413
415
|
),
|
|
414
416
|
)
|
|
415
417
|
|
|
416
|
-
|
|
417
418
|
def _build_full_fix_prompt(
|
|
418
419
|
code: str,
|
|
419
420
|
error_message: str,
|
|
@@ -738,16 +739,57 @@ def clone_repo():
|
|
|
738
739
|
subprocess.run(["git", "-C", workspace, "checkout", branch], check=True)
|
|
739
740
|
subprocess.run(["git", "-C", workspace, "pull"], check=True)
|
|
740
741
|
|
|
741
|
-
def
|
|
742
|
+
def run_code_server():
|
|
743
|
+
logger.info("code-server path: %s", shutil.which("code-server"))
|
|
744
|
+
|
|
745
|
+
# ✅ Ensure config dir exists
|
|
746
|
+
os.makedirs("/root/.config/code-server", exist_ok=True)
|
|
747
|
+
|
|
748
|
+
# ✅ Write code-server config
|
|
749
|
+
with open("/root/.config/code-server/config.yaml", "w") as f:
|
|
750
|
+
f.write(
|
|
751
|
+
f"bind-addr: 0.0.0.0:{CODE_SERVER_PORT}\n"
|
|
752
|
+
"auth: none\n"
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
with open("/root/.config/code-server/config.yaml", "r") as f:
|
|
756
|
+
logger.info("code-server config:\n%s", f.read())
|
|
757
|
+
|
|
758
|
+
# ✅ Ensure workspace + vscode dir exists
|
|
759
|
+
os.makedirs(workspace, exist_ok=True)
|
|
760
|
+
os.makedirs(f"{workspace}/.vscode", exist_ok=True)
|
|
761
|
+
|
|
762
|
+
# ✅ Write VS Code settings
|
|
763
|
+
with open(f"{workspace}/.vscode/settings.json", "w") as f:
|
|
764
|
+
f.write("""{
|
|
765
|
+
"chat.disableAIFeatures": true,
|
|
766
|
+
"workbench.colorTheme": "Default Dark+",
|
|
767
|
+
"outline.showFiles": false,
|
|
768
|
+
"timeline.enabled": false,
|
|
769
|
+
"python.defaultInterpreterPath": "/usr/local/bin/python"
|
|
770
|
+
}""")
|
|
771
|
+
|
|
772
|
+
# ✅ Start code-server (NO extension install here)
|
|
773
|
+
code_server_proc = subprocess.Popen([
|
|
774
|
+
"code-server",
|
|
775
|
+
workspace,
|
|
776
|
+
"--config", "/root/.config/code-server/config.yaml"
|
|
777
|
+
])
|
|
778
|
+
|
|
779
|
+
time.sleep(3)
|
|
780
|
+
|
|
781
|
+
if code_server_proc.poll() is not None:
|
|
782
|
+
raise Exception("code-server failed to start")
|
|
783
|
+
|
|
784
|
+
return code_server_proc
|
|
785
|
+
|
|
786
|
+
def start_ai_http(start_code_server=True):
|
|
742
787
|
global client
|
|
743
788
|
global workspace
|
|
744
|
-
global port_for_connector
|
|
745
789
|
global process_ttl_seconds
|
|
746
790
|
global process_ttl_deadline_unix
|
|
747
791
|
global _process_ttl_timer
|
|
748
792
|
|
|
749
|
-
port_for_connector = connector_port
|
|
750
|
-
|
|
751
793
|
process_ttl_seconds = None
|
|
752
794
|
process_ttl_deadline_unix = None
|
|
753
795
|
_process_ttl_timer = None
|
|
@@ -786,51 +828,13 @@ def start_ai_http(connector_port=5000):
|
|
|
786
828
|
)
|
|
787
829
|
|
|
788
830
|
api_key = os.getenv("OPENAI_API_KEY")
|
|
789
|
-
ai_port = int(os.getenv("AI_PORT", 5050))
|
|
790
|
-
code_port = int(os.getenv("CODE_SERVER_PORT", 5002))
|
|
791
831
|
|
|
792
832
|
clone_repo()
|
|
833
|
+
start_connector_http_server()
|
|
793
834
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
os.makedirs("/root/.config/code-server", exist_ok=True)
|
|
798
|
-
|
|
799
|
-
# ✅ Write code-server config
|
|
800
|
-
with open("/root/.config/code-server/config.yaml", "w") as f:
|
|
801
|
-
f.write(
|
|
802
|
-
f"bind-addr: 0.0.0.0:{code_port}\n"
|
|
803
|
-
"auth: none\n"
|
|
804
|
-
)
|
|
805
|
-
|
|
806
|
-
with open("/root/.config/code-server/config.yaml", "r") as f:
|
|
807
|
-
logger.info("code-server config:\n%s", f.read())
|
|
808
|
-
|
|
809
|
-
# ✅ Ensure workspace + vscode dir exists
|
|
810
|
-
os.makedirs(workspace, exist_ok=True)
|
|
811
|
-
os.makedirs(f"{workspace}/.vscode", exist_ok=True)
|
|
812
|
-
|
|
813
|
-
# ✅ Write VS Code settings
|
|
814
|
-
with open(f"{workspace}/.vscode/settings.json", "w") as f:
|
|
815
|
-
f.write("""{
|
|
816
|
-
"chat.disableAIFeatures": true,
|
|
817
|
-
"workbench.colorTheme": "Default Dark+",
|
|
818
|
-
"outline.showFiles": false,
|
|
819
|
-
"timeline.enabled": false,
|
|
820
|
-
"python.defaultInterpreterPath": "/usr/local/bin/python"
|
|
821
|
-
}""")
|
|
822
|
-
|
|
823
|
-
# ✅ Start code-server (NO extension install here)
|
|
824
|
-
code_server_proc = subprocess.Popen([
|
|
825
|
-
"code-server",
|
|
826
|
-
workspace,
|
|
827
|
-
"--config", "/root/.config/code-server/config.yaml"
|
|
828
|
-
])
|
|
829
|
-
|
|
830
|
-
time.sleep(3)
|
|
831
|
-
|
|
832
|
-
if code_server_proc.poll() is not None:
|
|
833
|
-
raise Exception("code-server failed to start")
|
|
835
|
+
code_server_proc = None
|
|
836
|
+
if start_code_server:
|
|
837
|
+
code_server_proc = run_code_server()
|
|
834
838
|
|
|
835
839
|
def shutdown():
|
|
836
840
|
global _process_ttl_timer
|
|
@@ -839,8 +843,11 @@ def start_ai_http(connector_port=5000):
|
|
|
839
843
|
if tm is not None:
|
|
840
844
|
tm.cancel()
|
|
841
845
|
logger.info("Shutting down code-server...")
|
|
842
|
-
if code_server_proc:
|
|
846
|
+
if code_server_proc is not None:
|
|
843
847
|
code_server_proc.terminate()
|
|
848
|
+
logger.info("Shutting down connector HTTP...")
|
|
849
|
+
with _connector_lock:
|
|
850
|
+
_stop_connector_subprocess_unlocked()
|
|
844
851
|
|
|
845
852
|
ttl_shutdown_holder["fn"] = shutdown
|
|
846
853
|
|
|
@@ -858,18 +865,19 @@ def start_ai_http(connector_port=5000):
|
|
|
858
865
|
|
|
859
866
|
client = OpenAI(api_key=api_key)
|
|
860
867
|
|
|
861
|
-
logger.info("Starting AI HTTP controller on 0.0.0.0:%s",
|
|
868
|
+
logger.info("Starting AI HTTP controller on 0.0.0.0:%s", AI_PORT)
|
|
862
869
|
|
|
863
870
|
import uvicorn
|
|
864
|
-
uvicorn.run(app, host="0.0.0.0", port=
|
|
871
|
+
uvicorn.run(app, host="0.0.0.0", port=AI_PORT)
|
|
865
872
|
|
|
866
873
|
@app.get("/session-info")
|
|
867
874
|
def session_info():
|
|
868
875
|
out: Dict[str, Any] = {
|
|
869
876
|
"workspace": workspace,
|
|
870
|
-
"
|
|
871
|
-
"
|
|
872
|
-
"
|
|
877
|
+
"connector_port": CONNECTOR_PORT,
|
|
878
|
+
"ai_port": AI_PORT,
|
|
879
|
+
"code_server_port": CODE_SERVER_PORT,
|
|
880
|
+
"api_base": f"http://localhost:{AI_PORT}",
|
|
873
881
|
}
|
|
874
882
|
if process_ttl_seconds is not None and process_ttl_deadline_unix is not None:
|
|
875
883
|
out["ttl_seconds"] = process_ttl_seconds
|
|
@@ -88,11 +88,21 @@ def invoke(req: InvokeRequest):
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def start_server(
|
|
91
|
+
def start_server():
|
|
92
92
|
import uvicorn
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
|
|
94
|
+
listen = int(os.getenv("CONNECTOR_PORT", 5000))
|
|
95
|
+
workspace = os.getenv("WORKSPACE", os.getcwd())
|
|
96
|
+
connector_src = os.path.join(workspace, "src")
|
|
97
|
+
use_reload = os.getenv("CONNECTOR_HTTP_RELOAD", "0").lower() in ("1", "true", "yes")
|
|
98
|
+
|
|
99
|
+
kw: Dict[str, Any] = {
|
|
100
|
+
"host": "0.0.0.0",
|
|
101
|
+
"port": listen,
|
|
102
|
+
}
|
|
103
|
+
if use_reload:
|
|
104
|
+
kw["reload"] = True
|
|
105
|
+
watch = connector_src if os.path.isdir(connector_src) else workspace
|
|
106
|
+
kw["reload_dirs"] = [os.path.abspath(watch)]
|
|
107
|
+
|
|
108
|
+
uvicorn.run("dc_sdk.src.server:app", **kw)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dc_python_sdk-1.5.22 → dc_python_sdk-1.5.24}/src/dc_python_sdk.egg-info/dependency_links.txt
RENAMED
|
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
|