ummaya 0.2.3 → 0.2.4
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.
- package/README.md +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/prompts/manifest.yaml +2 -2
- package/prompts/session_guidance_v1.md +3 -1
- package/prompts/system_v1.md +8 -7
- package/pyproject.toml +2 -7
- package/src/ummaya/context/builder.py +17 -11
- package/src/ummaya/engine/engine.py +27 -7
- package/src/ummaya/engine/query.py +20 -0
- package/src/ummaya/evidence/__init__.py +25 -0
- package/src/ummaya/evidence/__main__.py +7 -0
- package/src/ummaya/evidence/models.py +58 -0
- package/src/ummaya/evidence/runner.py +308 -0
- package/src/ummaya/evidence/task_registry.py +264 -0
- package/src/ummaya/ipc/frame_schema.py +47 -0
- package/src/ummaya/ipc/stdio.py +1287 -54
- package/src/ummaya/llm/client.py +132 -56
- package/src/ummaya/llm/reasoning.py +84 -0
- package/src/ummaya/tools/discovery_bridge.py +17 -1
- package/src/ummaya/tools/executor.py +32 -12
- package/src/ummaya/tools/geocoding/kakao_client.py +1 -2
- package/src/ummaya/tools/kma/apihub_catalog.py +984 -1
- package/src/ummaya/tools/kma/apihub_structured_adapter.py +86 -6
- package/src/ummaya/tools/kma/apihub_url_adapter.py +593 -0
- package/src/ummaya/tools/kma/apihub_url_catalog.py +296 -0
- package/src/ummaya/tools/location_adapters.py +8 -6
- package/src/ummaya/tools/manifest_metadata.py +16 -3
- package/src/ummaya/tools/mvp_surface.py +2 -2
- package/src/ummaya/tools/nmc/emergency_search.py +8 -6
- package/src/ummaya/tools/register_all.py +9 -0
- package/src/ummaya/tools/resolve_location.py +4 -4
- package/src/ummaya/tools/search.py +664 -18
- package/src/ummaya/tools/verified_data_go_kr/_manifest.py +115 -25
- package/src/ummaya/tools/verified_data_go_kr/airkorea_air_quality.py +109 -4
- package/src/ummaya/tools/verified_data_go_kr/nmc_aed_site.py +108 -2
- package/src/ummaya/tools/verified_data_go_kr/pps_bid_public_info.py +174 -9
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_arrival.py +66 -3
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_location.py +12 -2
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_route.py +8 -2
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_route_station.py +114 -0
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_station.py +14 -3
- package/src/ummaya/tools/verify_canonical_map.py +21 -0
- package/tui/package.json +1 -2
- package/tui/src/QueryEngine.ts +4 -0
- package/tui/src/cli/handlers/auth.ts +1 -1
- package/tui/src/cli/handlers/mcp.tsx +3 -3
- package/tui/src/cli/print.ts +69 -18
- package/tui/src/cli/update.ts +13 -13
- package/tui/src/commands/copy/index.ts +1 -1
- package/tui/src/commands/cost/cost.ts +2 -2
- package/tui/src/commands/init-verifiers.ts +5 -5
- package/tui/src/commands/init.ts +30 -30
- package/tui/src/commands/insights.ts +43 -43
- package/tui/src/commands/install-github-app/install-github-app.tsx +2 -2
- package/tui/src/commands/install-github-app/setupGitHubActions.ts +3 -3
- package/tui/src/commands/install.tsx +5 -5
- package/tui/src/commands/mcp/addCommand.ts +5 -5
- package/tui/src/commands/mcp/xaaIdpCommand.ts +2 -2
- package/tui/src/commands/plugin/ManageMarketplaces.tsx +2 -2
- package/tui/src/commands/reasoning/index.ts +13 -0
- package/tui/src/commands/reasoning/reasoning.tsx +177 -0
- package/tui/src/commands/thinkback/thinkback.tsx +3 -3
- package/tui/src/commands.ts +2 -0
- package/tui/src/components/Messages.tsx +2 -1
- package/tui/src/components/Spinner.tsx +2 -2
- package/tui/src/components/design-system/LoadingState.tsx +2 -2
- package/tui/src/ipc/codec.ts +26 -0
- package/tui/src/ipc/frames.generated.ts +398 -303
- package/tui/src/ipc/llmClient.ts +130 -51
- package/tui/src/ipc/llmTypes.ts +16 -1
- package/tui/src/ipc/schema/frame.schema.json +1 -3475
- package/tui/src/main.tsx +3 -0
- package/tui/src/query.ts +467 -2
- package/tui/src/screens/REPL.tsx +3 -3
- package/tui/src/services/api/claude.ts +48 -18
- package/tui/src/services/api/client.ts +33 -12
- package/tui/src/services/api/ummaya.ts +70 -16
- package/tui/src/skills/bundled/stuck.ts +12 -12
- package/tui/src/state/AppStateStore.ts +7 -0
- package/tui/src/tools/AdapterTool/AdapterTool.ts +590 -7
- package/tui/src/tools/LookupPrimitive/LookupPrimitive.ts +43 -17
- package/tui/src/tools/LookupPrimitive/prompt.ts +7 -6
- package/tui/src/tools/ResolveLocationPrimitive/ResolveLocationPrimitive.ts +40 -19
- package/tui/src/tools/SubmitPrimitive/SubmitPrimitive.ts +25 -9
- package/tui/src/tools/VerifyPrimitive/VerifyPrimitive.ts +25 -9
- package/tui/src/tools/_shared/citizenUserText.ts +49 -0
- package/tui/src/tools/_shared/directPublicDataGuard.ts +362 -0
- package/tui/src/tools/_shared/kmaAnalysisGuard.ts +197 -0
- package/tui/src/tools/_shared/kmaAviationGuard.ts +70 -0
- package/tui/src/tools/_shared/locationInputRepair.ts +112 -0
- package/tui/src/tools/_shared/nmcAedGuard.ts +234 -0
- package/tui/src/tools/_shared/protectedCheckGuard.ts +207 -0
- package/tui/src/tools/_shared/rootPrimitiveInput.ts +67 -0
- package/tui/src/tools/_shared/textToolCallGuard.ts +91 -0
- package/tui/src/tools/_shared/toolChoiceRepair.ts +866 -0
- package/tui/src/utils/attachments.ts +1 -1
- package/tui/src/utils/kExaoneReasoning.ts +138 -0
- package/tui/src/utils/messages.ts +1 -0
- package/tui/src/utils/multiToolLayout.ts +13 -0
- package/tui/src/utils/processUserInput/processSlashCommand.tsx +2 -2
- package/tui/src/utils/processUserInput/processUserInput.ts +26 -0
- package/tui/src/utils/settings/applySettingsChange.ts +4 -0
- package/tui/src/utils/settings/types.ts +9 -3
- package/tui/src/utils/stats.ts +1 -1
- package/uv.lock +1 -15
- package/assets/copilot-gate-logo.svg +0 -58
- package/assets/govon-logo.svg +0 -40
- package/src/ummaya/eval/__init__.py +0 -5
- package/src/ummaya/eval/retrieval.py +0 -713
- package/tui/src/utils/messageStream.ts +0 -186
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
from datetime import datetime, timedelta
|
|
6
7
|
from typing import Literal
|
|
7
8
|
|
|
8
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
9
10
|
|
|
10
11
|
from ummaya.tools.executor import ToolExecutor
|
|
11
12
|
from ummaya.tools.models import GovAPITool
|
|
@@ -17,27 +18,113 @@ from ummaya.tools.verified_data_go_kr._factory import (
|
|
|
17
18
|
)
|
|
18
19
|
from ummaya.tools.verified_data_go_kr._manifest import require_spec
|
|
19
20
|
|
|
21
|
+
_PPS_DATETIME_FORMAT = "%Y%m%d%H%M"
|
|
22
|
+
_PPS_MAX_SEARCH_WINDOW = timedelta(days=31)
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
class PpsBidPublicInfoInput(BaseModel):
|
|
22
26
|
"""Input for PPS bid public information."""
|
|
23
27
|
|
|
24
28
|
model_config = ConfigDict(extra="forbid")
|
|
25
29
|
|
|
26
|
-
inqry_div: Literal["2"] = Field(
|
|
27
|
-
default="
|
|
30
|
+
inqry_div: Literal["1", "2"] = Field(
|
|
31
|
+
default="1",
|
|
32
|
+
description=(
|
|
33
|
+
"Official PPS inqryDiv. Use '1' for bid notice publication datetime "
|
|
34
|
+
"(pblancDate) searches such as 'this week posted notices'; use '2' "
|
|
35
|
+
"for bid opening datetime (opengDt) searches."
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
inqry_bgn_dt: str = Field(
|
|
39
|
+
...,
|
|
40
|
+
pattern=r"^\d{12}$",
|
|
28
41
|
description=(
|
|
29
|
-
"PPS
|
|
30
|
-
"
|
|
42
|
+
"Official PPS inqryBgnDt search start datetime in YYYYMMDDHHMM. "
|
|
43
|
+
"Required when inqry_div is '1' or '2'. Keep each PPS request window "
|
|
44
|
+
"within 31 days."
|
|
31
45
|
),
|
|
32
46
|
)
|
|
33
|
-
|
|
47
|
+
inqry_end_dt: str = Field(
|
|
34
48
|
...,
|
|
35
|
-
|
|
36
|
-
description=
|
|
49
|
+
pattern=r"^\d{12}$",
|
|
50
|
+
description=(
|
|
51
|
+
"Official PPS inqryEndDt search end datetime in YYYYMMDDHHMM. "
|
|
52
|
+
"Required when inqry_div is '1' or '2'. Keep each PPS request window "
|
|
53
|
+
"within 31 days."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
bid_ntce_nm: str | None = Field(
|
|
57
|
+
default=None,
|
|
58
|
+
max_length=1000,
|
|
59
|
+
description=(
|
|
60
|
+
"Official PPS bidNtceNm notice-name keyword. Partial names are allowed; "
|
|
61
|
+
"use this for citizen keywords such as 전기공사."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
ntce_instt_nm: str | None = Field(
|
|
65
|
+
default=None,
|
|
66
|
+
max_length=400,
|
|
67
|
+
description=(
|
|
68
|
+
"Official PPS ntceInsttNm public notice agency name filter. "
|
|
69
|
+
"Partial agency names are allowed."
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
dminstt_nm: str | None = Field(
|
|
73
|
+
default=None,
|
|
74
|
+
max_length=400,
|
|
75
|
+
description=(
|
|
76
|
+
"Official PPS dminsttNm demand agency name filter. Partial agency names are allowed."
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
region_name: str | None = Field(
|
|
80
|
+
default=None,
|
|
81
|
+
max_length=100,
|
|
82
|
+
description=(
|
|
83
|
+
"UMMAYA client-side region relevance filter copied from citizen wording. "
|
|
84
|
+
"This is not sent to PPS upstream; after the official response, UMMAYA "
|
|
85
|
+
"keeps rows whose documented region/agency fields such as cnstrtsiteRgnNm, "
|
|
86
|
+
"prtcptLmtRgnNm, ntceInsttNm, or dminsttNm match this value."
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
prtcpt_lmt_rgn_nm: str | None = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
max_length=100,
|
|
92
|
+
description=(
|
|
93
|
+
"Official PPS prtcptLmtRgnNm participation-limit region name. "
|
|
94
|
+
"For Busan-region notices use 부산광역시 when the citizen says 부산시."
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
indstryty_nm: str | None = Field(
|
|
98
|
+
default=None,
|
|
99
|
+
max_length=100,
|
|
100
|
+
description=(
|
|
101
|
+
"Official PPS indstrytyNm industry/license name. Use 전기공사업 "
|
|
102
|
+
"for electrical-construction qualification searches when requested."
|
|
103
|
+
),
|
|
37
104
|
)
|
|
38
105
|
page_no: int = Field(default=1, ge=1, description="Page number.")
|
|
39
106
|
num_of_rows: int = Field(default=10, ge=1, le=100, description="Rows per page.")
|
|
40
107
|
|
|
108
|
+
@model_validator(mode="after")
|
|
109
|
+
def validate_official_search_window(self) -> PpsBidPublicInfoInput:
|
|
110
|
+
"""Keep PPS searches inside the observed upstream contract window."""
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
start = datetime.strptime(self.inqry_bgn_dt, _PPS_DATETIME_FORMAT)
|
|
114
|
+
end = datetime.strptime(self.inqry_end_dt, _PPS_DATETIME_FORMAT)
|
|
115
|
+
except ValueError as exc:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
"PPS inqry_bgn_dt and inqry_end_dt must be valid YYYYMMDDHHMM datetimes."
|
|
118
|
+
) from exc
|
|
119
|
+
if end < start:
|
|
120
|
+
raise ValueError("PPS inqry_end_dt must be greater than or equal to inqry_bgn_dt.")
|
|
121
|
+
if end - start > _PPS_MAX_SEARCH_WINDOW:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
"PPS Nara Market bid searches must be split into 31-day-or-smaller "
|
|
124
|
+
"inqry_bgn_dt/inqry_end_dt windows before calling the upstream API."
|
|
125
|
+
)
|
|
126
|
+
return self
|
|
127
|
+
|
|
41
128
|
|
|
42
129
|
SPEC = require_spec("pps_bid_public_info")
|
|
43
130
|
INPUT_SCHEMA = PpsBidPublicInfoInput
|
|
@@ -51,7 +138,85 @@ async def handle(
|
|
|
51
138
|
) -> dict[str, object]:
|
|
52
139
|
"""Fetch or replay PPS bid public information rows."""
|
|
53
140
|
|
|
54
|
-
|
|
141
|
+
output = await handle_verified_input(input_model, SPEC, fixture_body=fixture_body)
|
|
142
|
+
return _filter_output_by_region_name(output, input_model.region_name)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
_REGION_RELEVANCE_FIELDS: tuple[str, ...] = (
|
|
146
|
+
"cnstrtsiteRgnNm",
|
|
147
|
+
"prtcptLmtRgnNm",
|
|
148
|
+
"ntceInsttNm",
|
|
149
|
+
"dminsttNm",
|
|
150
|
+
"jntcontrctDutyRgnNm1",
|
|
151
|
+
"jntcontrctDutyRgnNm2",
|
|
152
|
+
"jntcontrctDutyRgnNm3",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _filter_output_by_region_name(
|
|
157
|
+
output: dict[str, object],
|
|
158
|
+
region_name: str | None,
|
|
159
|
+
) -> dict[str, object]:
|
|
160
|
+
"""Filter PPS rows by documented region-bearing response fields."""
|
|
161
|
+
|
|
162
|
+
terms = _region_terms(region_name)
|
|
163
|
+
if not terms:
|
|
164
|
+
return output
|
|
165
|
+
|
|
166
|
+
raw_items = output.get("items")
|
|
167
|
+
if not isinstance(raw_items, list):
|
|
168
|
+
return output
|
|
169
|
+
|
|
170
|
+
filtered: list[dict[str, object]] = []
|
|
171
|
+
for raw_item in raw_items:
|
|
172
|
+
if not isinstance(raw_item, dict):
|
|
173
|
+
continue
|
|
174
|
+
if _item_matches_region(raw_item, terms):
|
|
175
|
+
filtered.append(raw_item)
|
|
176
|
+
|
|
177
|
+
next_output = dict(output)
|
|
178
|
+
next_output["items"] = filtered
|
|
179
|
+
next_output["total_count"] = len(filtered)
|
|
180
|
+
raw_meta = output.get("meta")
|
|
181
|
+
meta = dict(raw_meta) if isinstance(raw_meta, dict) else {}
|
|
182
|
+
meta["upstream_total_count"] = output.get("total_count")
|
|
183
|
+
meta["client_filter"] = {
|
|
184
|
+
"field": "region_name",
|
|
185
|
+
"value": region_name,
|
|
186
|
+
"matched_count": len(filtered),
|
|
187
|
+
}
|
|
188
|
+
next_output["meta"] = meta
|
|
189
|
+
return next_output
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _region_terms(region_name: str | None) -> tuple[str, ...]:
|
|
193
|
+
if region_name is None:
|
|
194
|
+
return ()
|
|
195
|
+
normalized = " ".join(region_name.split())
|
|
196
|
+
if not normalized:
|
|
197
|
+
return ()
|
|
198
|
+
terms = [normalized]
|
|
199
|
+
for suffix in ("특별자치시", "특별자치도", "특별시", "광역시", "도"):
|
|
200
|
+
if normalized.endswith(suffix):
|
|
201
|
+
short = normalized[: -len(suffix)]
|
|
202
|
+
if short:
|
|
203
|
+
terms.append(short)
|
|
204
|
+
break
|
|
205
|
+
return tuple(dict.fromkeys(terms))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _item_matches_region(item: dict[str, object], terms: tuple[str, ...]) -> bool:
|
|
209
|
+
raw_record = item.get("record")
|
|
210
|
+
record = raw_record if isinstance(raw_record, dict) else item
|
|
211
|
+
for field_name in _REGION_RELEVANCE_FIELDS:
|
|
212
|
+
value = record.get(field_name)
|
|
213
|
+
if not isinstance(value, str):
|
|
214
|
+
continue
|
|
215
|
+
compact_value = value.replace(" ", "")
|
|
216
|
+
for term in terms:
|
|
217
|
+
if term in value or term.replace(" ", "") in compact_value:
|
|
218
|
+
return True
|
|
219
|
+
return False
|
|
55
220
|
|
|
56
221
|
|
|
57
222
|
def register(registry: ToolRegistry, executor: ToolExecutor) -> None:
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
6
8
|
from pydantic import BaseModel, ConfigDict, Field
|
|
7
9
|
|
|
8
10
|
from ummaya.tools.executor import ToolExecutor
|
|
@@ -15,14 +17,41 @@ from ummaya.tools.verified_data_go_kr._factory import (
|
|
|
15
17
|
)
|
|
16
18
|
from ummaya.tools.verified_data_go_kr._manifest import require_spec
|
|
17
19
|
|
|
20
|
+
_TAGO_CITY_CODE_DESCRIPTION = (
|
|
21
|
+
"Official TAGO cityCode from the provider getCtyCodeList contract. "
|
|
22
|
+
"Common metropolitan examples: Busan=21, Daegu=22, Incheon=23, "
|
|
23
|
+
"Gwangju=24, Daejeon=25, Ulsan=26."
|
|
24
|
+
)
|
|
25
|
+
|
|
18
26
|
|
|
19
27
|
class TagoBusArrivalInput(BaseModel):
|
|
20
28
|
"""Input for TAGO bus arrival search."""
|
|
21
29
|
|
|
22
30
|
model_config = ConfigDict(extra="forbid")
|
|
23
31
|
|
|
24
|
-
city_code: str = Field(..., min_length=1, description=
|
|
25
|
-
node_id: str = Field(
|
|
32
|
+
city_code: str = Field(..., min_length=1, description=_TAGO_CITY_CODE_DESCRIPTION)
|
|
33
|
+
node_id: str = Field(
|
|
34
|
+
...,
|
|
35
|
+
min_length=1,
|
|
36
|
+
description="Official TAGO nodeId returned by tago_bus_station_search.",
|
|
37
|
+
)
|
|
38
|
+
route_no: str | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
min_length=1,
|
|
41
|
+
description=(
|
|
42
|
+
"Optional client-side filter against the official TAGO response field routeno. "
|
|
43
|
+
"It is not sent upstream; use it when the citizen names a visible bus route "
|
|
44
|
+
"such as 1001."
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
route_id: str | None = Field(
|
|
48
|
+
default=None,
|
|
49
|
+
min_length=1,
|
|
50
|
+
description=(
|
|
51
|
+
"Optional client-side filter against the official TAGO response field routeid. "
|
|
52
|
+
"Get it from tago_bus_route_search when route_no alone is ambiguous."
|
|
53
|
+
),
|
|
54
|
+
)
|
|
26
55
|
page_no: int = Field(default=1, ge=1, description="Page number.")
|
|
27
56
|
num_of_rows: int = Field(default=10, ge=1, le=100, description="Rows per page.")
|
|
28
57
|
|
|
@@ -39,7 +68,41 @@ async def handle(
|
|
|
39
68
|
) -> dict[str, object]:
|
|
40
69
|
"""Fetch or replay TAGO bus arrival rows."""
|
|
41
70
|
|
|
42
|
-
|
|
71
|
+
output = await handle_verified_input(input_model, SPEC, fixture_body=fixture_body)
|
|
72
|
+
if input_model.route_no is None and input_model.route_id is None:
|
|
73
|
+
return output
|
|
74
|
+
return _filter_arrivals(output, route_no=input_model.route_no, route_id=input_model.route_id)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _filter_arrivals(
|
|
78
|
+
output: dict[str, object],
|
|
79
|
+
*,
|
|
80
|
+
route_no: str | None,
|
|
81
|
+
route_id: str | None,
|
|
82
|
+
) -> dict[str, object]:
|
|
83
|
+
items = output.get("items")
|
|
84
|
+
if not isinstance(items, list):
|
|
85
|
+
return output
|
|
86
|
+
|
|
87
|
+
filtered: list[dict[str, object]] = []
|
|
88
|
+
for item in items:
|
|
89
|
+
if not isinstance(item, dict):
|
|
90
|
+
continue
|
|
91
|
+
record = item.get("record")
|
|
92
|
+
if not isinstance(record, dict):
|
|
93
|
+
continue
|
|
94
|
+
typed_record = cast(dict[str, object], record)
|
|
95
|
+
if route_no is not None and str(typed_record.get("routeno", "")) != route_no:
|
|
96
|
+
continue
|
|
97
|
+
if route_id is not None and str(typed_record.get("routeid", "")) != route_id:
|
|
98
|
+
continue
|
|
99
|
+
filtered.append(cast(dict[str, object], item))
|
|
100
|
+
|
|
101
|
+
filtered_output = dict(output)
|
|
102
|
+
filtered_output["items"] = filtered
|
|
103
|
+
filtered_output["total_count"] = len(filtered)
|
|
104
|
+
filtered_output["next_cursor"] = None
|
|
105
|
+
return filtered_output
|
|
43
106
|
|
|
44
107
|
|
|
45
108
|
def register(registry: ToolRegistry, executor: ToolExecutor) -> None:
|
|
@@ -15,14 +15,24 @@ from ummaya.tools.verified_data_go_kr._factory import (
|
|
|
15
15
|
)
|
|
16
16
|
from ummaya.tools.verified_data_go_kr._manifest import require_spec
|
|
17
17
|
|
|
18
|
+
_TAGO_CITY_CODE_DESCRIPTION = (
|
|
19
|
+
"Official TAGO cityCode from the provider getCtyCodeList contract. "
|
|
20
|
+
"Common metropolitan examples: Busan=21, Daegu=22, Incheon=23, "
|
|
21
|
+
"Gwangju=24, Daejeon=25, Ulsan=26."
|
|
22
|
+
)
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class TagoBusLocationInput(BaseModel):
|
|
20
26
|
"""Input for TAGO bus location search."""
|
|
21
27
|
|
|
22
28
|
model_config = ConfigDict(extra="forbid")
|
|
23
29
|
|
|
24
|
-
city_code: str = Field(..., min_length=1, description=
|
|
25
|
-
route_id: str = Field(
|
|
30
|
+
city_code: str = Field(..., min_length=1, description=_TAGO_CITY_CODE_DESCRIPTION)
|
|
31
|
+
route_id: str = Field(
|
|
32
|
+
...,
|
|
33
|
+
min_length=1,
|
|
34
|
+
description="Official TAGO routeId returned by tago_bus_route_search.",
|
|
35
|
+
)
|
|
26
36
|
page_no: int = Field(default=1, ge=1, description="Page number.")
|
|
27
37
|
num_of_rows: int = Field(default=10, ge=1, le=100, description="Rows per page.")
|
|
28
38
|
|
|
@@ -15,14 +15,20 @@ from ummaya.tools.verified_data_go_kr._factory import (
|
|
|
15
15
|
)
|
|
16
16
|
from ummaya.tools.verified_data_go_kr._manifest import require_spec
|
|
17
17
|
|
|
18
|
+
_TAGO_CITY_CODE_DESCRIPTION = (
|
|
19
|
+
"Official TAGO cityCode from the provider getCtyCodeList contract. "
|
|
20
|
+
"Common metropolitan examples: Busan=21, Daegu=22, Incheon=23, "
|
|
21
|
+
"Gwangju=24, Daejeon=25, Ulsan=26."
|
|
22
|
+
)
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class TagoBusRouteInput(BaseModel):
|
|
20
26
|
"""Input for TAGO bus route search."""
|
|
21
27
|
|
|
22
28
|
model_config = ConfigDict(extra="forbid")
|
|
23
29
|
|
|
24
|
-
city_code: str = Field(..., min_length=1, description=
|
|
25
|
-
route_no: str = Field(..., min_length=1, description="Bus route number.")
|
|
30
|
+
city_code: str = Field(..., min_length=1, description=_TAGO_CITY_CODE_DESCRIPTION)
|
|
31
|
+
route_no: str = Field(..., min_length=1, description="Bus route number visible to citizens.")
|
|
26
32
|
page_no: int = Field(default=1, ge=1, description="Page number.")
|
|
27
33
|
num_of_rows: int = Field(default=10, ge=1, le=100, description="Rows per page.")
|
|
28
34
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""TAGO bus route-station search adapter."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
from ummaya.tools.executor import ToolExecutor
|
|
11
|
+
from ummaya.tools.models import GovAPITool
|
|
12
|
+
from ummaya.tools.registry import ToolRegistry
|
|
13
|
+
from ummaya.tools.verified_data_go_kr._factory import (
|
|
14
|
+
build_tool,
|
|
15
|
+
handle_verified_input,
|
|
16
|
+
register_module,
|
|
17
|
+
)
|
|
18
|
+
from ummaya.tools.verified_data_go_kr._manifest import require_spec
|
|
19
|
+
|
|
20
|
+
_TAGO_CITY_CODE_DESCRIPTION = (
|
|
21
|
+
"Official TAGO cityCode from the provider getCtyCodeList contract. "
|
|
22
|
+
"Common metropolitan examples: Busan=21, Daegu=22, Incheon=23, "
|
|
23
|
+
"Gwangju=24, Daejeon=25, Ulsan=26."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TagoBusRouteStationInput(BaseModel):
|
|
28
|
+
"""Input for TAGO route-station search."""
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(extra="forbid")
|
|
31
|
+
|
|
32
|
+
city_code: str = Field(..., min_length=1, description=_TAGO_CITY_CODE_DESCRIPTION)
|
|
33
|
+
route_id: str = Field(
|
|
34
|
+
...,
|
|
35
|
+
min_length=1,
|
|
36
|
+
description="Official TAGO routeId returned by tago_bus_route_search.",
|
|
37
|
+
)
|
|
38
|
+
node_nm: str | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
min_length=1,
|
|
41
|
+
description=(
|
|
42
|
+
"Optional client-side filter against the official TAGO response field nodenm. "
|
|
43
|
+
"Use it to narrow a route's passing stops to a citizen-named place such as 부산역."
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
updown_cd: str | None = Field(
|
|
47
|
+
default=None,
|
|
48
|
+
min_length=1,
|
|
49
|
+
description=(
|
|
50
|
+
"Optional client-side filter against the official TAGO response field updowncd "
|
|
51
|
+
"when a direction is already known."
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
page_no: int = Field(default=1, ge=1, description="Page number.")
|
|
55
|
+
num_of_rows: int = Field(default=100, ge=1, le=100, description="Rows per page.")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
SPEC = require_spec("tago_bus_route_station_search")
|
|
59
|
+
INPUT_SCHEMA = TagoBusRouteStationInput
|
|
60
|
+
TOOL: GovAPITool = build_tool(SPEC, INPUT_SCHEMA)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def handle(
|
|
64
|
+
input_model: TagoBusRouteStationInput,
|
|
65
|
+
*,
|
|
66
|
+
fixture_body: bytes | None = None,
|
|
67
|
+
) -> dict[str, object]:
|
|
68
|
+
"""Fetch or replay TAGO route-station rows."""
|
|
69
|
+
|
|
70
|
+
output = await handle_verified_input(input_model, SPEC, fixture_body=fixture_body)
|
|
71
|
+
if input_model.node_nm is None and input_model.updown_cd is None:
|
|
72
|
+
return output
|
|
73
|
+
return _filter_route_stations(
|
|
74
|
+
output,
|
|
75
|
+
node_nm=input_model.node_nm,
|
|
76
|
+
updown_cd=input_model.updown_cd,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _filter_route_stations(
|
|
81
|
+
output: dict[str, object],
|
|
82
|
+
*,
|
|
83
|
+
node_nm: str | None,
|
|
84
|
+
updown_cd: str | None,
|
|
85
|
+
) -> dict[str, object]:
|
|
86
|
+
items = output.get("items")
|
|
87
|
+
if not isinstance(items, list):
|
|
88
|
+
return output
|
|
89
|
+
|
|
90
|
+
filtered: list[dict[str, object]] = []
|
|
91
|
+
for item in items:
|
|
92
|
+
if not isinstance(item, dict):
|
|
93
|
+
continue
|
|
94
|
+
record = item.get("record")
|
|
95
|
+
if not isinstance(record, dict):
|
|
96
|
+
continue
|
|
97
|
+
typed_record = cast(dict[str, object], record)
|
|
98
|
+
if node_nm is not None and node_nm not in str(typed_record.get("nodenm", "")):
|
|
99
|
+
continue
|
|
100
|
+
if updown_cd is not None and str(typed_record.get("updowncd", "")) != updown_cd:
|
|
101
|
+
continue
|
|
102
|
+
filtered.append(cast(dict[str, object], item))
|
|
103
|
+
|
|
104
|
+
filtered_output = dict(output)
|
|
105
|
+
filtered_output["items"] = filtered
|
|
106
|
+
filtered_output["total_count"] = len(filtered)
|
|
107
|
+
filtered_output["next_cursor"] = None
|
|
108
|
+
return filtered_output
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def register(registry: ToolRegistry, executor: ToolExecutor) -> None:
|
|
112
|
+
"""Register this adapter."""
|
|
113
|
+
|
|
114
|
+
register_module(registry, executor, tool=TOOL, input_schema=INPUT_SCHEMA, handler=handle)
|
|
@@ -15,15 +15,26 @@ from ummaya.tools.verified_data_go_kr._factory import (
|
|
|
15
15
|
)
|
|
16
16
|
from ummaya.tools.verified_data_go_kr._manifest import require_spec
|
|
17
17
|
|
|
18
|
+
_TAGO_CITY_CODE_DESCRIPTION = (
|
|
19
|
+
"Official TAGO cityCode from the provider getCtyCodeList contract. "
|
|
20
|
+
"Common metropolitan examples: Busan=21, Daegu=22, Incheon=23, "
|
|
21
|
+
"Gwangju=24, Daejeon=25, Ulsan=26."
|
|
22
|
+
)
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class TagoBusStationInput(BaseModel):
|
|
20
26
|
"""Input for TAGO bus station search."""
|
|
21
27
|
|
|
22
28
|
model_config = ConfigDict(extra="forbid")
|
|
23
29
|
|
|
24
|
-
city_code: str = Field(..., min_length=1, description=
|
|
25
|
-
node_nm: str | None = Field(
|
|
26
|
-
|
|
30
|
+
city_code: str = Field(..., min_length=1, description=_TAGO_CITY_CODE_DESCRIPTION)
|
|
31
|
+
node_nm: str | None = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description=(
|
|
34
|
+
"Bus stop name fragment, for example 부산역 when the citizen asks near Busan Station."
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
node_no: str | None = Field(default=None, description="Bus stop number printed at the stop.")
|
|
27
38
|
page_no: int = Field(default=1, ge=1, description="Page number.")
|
|
28
39
|
num_of_rows: int = Field(default=10, ge=1, le=100, description="Rows per page.")
|
|
29
40
|
|
|
@@ -11,6 +11,11 @@ Public API
|
|
|
11
11
|
Return the ``family_hint`` string for the given check tool_id, or ``None``
|
|
12
12
|
if the tool_id is not recognised.
|
|
13
13
|
|
|
14
|
+
``resolve_tool_id(identifier)`` → ``str | None``
|
|
15
|
+
Return the canonical ``mock_verify_*`` tool_id when *identifier* is either a
|
|
16
|
+
canonical check tool_id or an internal family_hint alias such as
|
|
17
|
+
``mobile_id`` / ``simple_auth_module``.
|
|
18
|
+
|
|
14
19
|
``get_canonical_map()`` → ``Mapping[str, str]``
|
|
15
20
|
Return the full frozen ``{tool_id: family_hint}`` mapping.
|
|
16
21
|
|
|
@@ -99,6 +104,22 @@ def resolve_family(tool_id: str) -> str | None:
|
|
|
99
104
|
return _load_map().get(tool_id)
|
|
100
105
|
|
|
101
106
|
|
|
107
|
+
def resolve_tool_id(identifier: str) -> str | None:
|
|
108
|
+
"""Return the canonical check tool_id for *identifier*.
|
|
109
|
+
|
|
110
|
+
This is the runtime guard for model-facing alias drift. The manifest can
|
|
111
|
+
include internal verify-family entries for backward compatibility, but
|
|
112
|
+
adapter dispatch is owned by canonical ``mock_verify_*`` tool ids.
|
|
113
|
+
"""
|
|
114
|
+
mapping = _load_map()
|
|
115
|
+
if identifier in mapping:
|
|
116
|
+
return identifier
|
|
117
|
+
for tool_id, family in mapping.items():
|
|
118
|
+
if family == identifier:
|
|
119
|
+
return tool_id
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
102
123
|
def get_canonical_map() -> Mapping[str, str]:
|
|
103
124
|
"""Return the full ``{tool_id: family_hint}`` frozen mapping (read-only)."""
|
|
104
125
|
return _load_map()
|
package/tui/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ummaya",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"gen:ipc": "bun run scripts/gen-ipc-types.ts",
|
|
12
12
|
"gen:pipa-hash": "bun run scripts/gen-pipa-hash.ts",
|
|
13
13
|
"diff:upstream": "bun run scripts/diff-upstream.ts",
|
|
14
|
-
"tui:smoke": "bun run scripts/tui-smoke.ts",
|
|
15
14
|
"test:soak": "bun test --timeout 600000 tests/soak",
|
|
16
15
|
"test": "bun test --max-concurrency=1 tests/entrypoints tests/hooks tests/i18n tests/ink tests/ipc tests/memdir tests/permissions tests/primitive tests/store tests/theme tests/unit",
|
|
17
16
|
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
package/tui/src/QueryEngine.ts
CHANGED
|
@@ -53,6 +53,10 @@ import type { AppState } from './state/AppState.js'
|
|
|
53
53
|
import { type Tools, type ToolUseContext, toolMatchesName } from './Tool.js'
|
|
54
54
|
import type { AgentDefinition } from './tools/AgentTool/loadAgentsDir.js'
|
|
55
55
|
import { SYNTHETIC_OUTPUT_TOOL_NAME } from './tools/SyntheticOutputTool/SyntheticOutputTool.js'
|
|
56
|
+
import {
|
|
57
|
+
stripTextToolCallBlocks,
|
|
58
|
+
textContainsToolCall,
|
|
59
|
+
} from './tools/_shared/textToolCallGuard.js'
|
|
56
60
|
import type { Message } from './types/message.js'
|
|
57
61
|
import type { OrphanedPermission } from './types/textInputTypes.js'
|
|
58
62
|
import { createAbortController } from './utils/abortController.js'
|
|
@@ -326,6 +326,6 @@ export async function authLogout(): Promise<void> {
|
|
|
326
326
|
process.stderr.write('Failed to log out.\n')
|
|
327
327
|
process.exit(1)
|
|
328
328
|
}
|
|
329
|
-
process.stdout.write('Successfully logged out from
|
|
329
|
+
process.stdout.write('Successfully logged out from FriendliAI.\n')
|
|
330
330
|
process.exit(0)
|
|
331
331
|
}
|