openclaw-server 0.1.0
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/package.json +29 -0
- package/packs/default/faq.yaml +8 -0
- package/packs/default/intents.yaml +19 -0
- package/packs/default/pack.yaml +12 -0
- package/packs/default/policies.yaml +1 -0
- package/packs/default/scenarios.yaml +1 -0
- package/packs/default/synonyms.yaml +1 -0
- package/packs/default/templates.yaml +16 -0
- package/packs/default/tools.yaml +1 -0
- package/readme.md +1219 -0
- package/src/auth.ts +24 -0
- package/src/better-sqlite3.d.ts +17 -0
- package/src/config.ts +63 -0
- package/src/core/matcher.ts +214 -0
- package/src/core/normalizer.test.ts +37 -0
- package/src/core/normalizer.ts +183 -0
- package/src/core/pack-loader.ts +97 -0
- package/src/core/reply-engine.test.ts +76 -0
- package/src/core/reply-engine.ts +256 -0
- package/src/core/request-adapter.ts +65 -0
- package/src/core/session-store.ts +48 -0
- package/src/core/stream-renderer.ts +237 -0
- package/src/core/tool-engine.ts +60 -0
- package/src/debug-log.ts +211 -0
- package/src/index.ts +23 -0
- package/src/openai.ts +79 -0
- package/src/response-api.ts +107 -0
- package/src/routes/admin.ts +32 -0
- package/src/routes/chat-completions.ts +173 -0
- package/src/routes/health.ts +7 -0
- package/src/routes/models.ts +21 -0
- package/src/routes/request-validation.ts +33 -0
- package/src/routes/responses.ts +182 -0
- package/src/routes/tasks.ts +138 -0
- package/src/runtime-stats.ts +80 -0
- package/src/server.test.ts +776 -0
- package/src/server.ts +108 -0
- package/src/tasks/chat-integration.ts +70 -0
- package/src/tasks/service.ts +320 -0
- package/src/tasks/store.test.ts +183 -0
- package/src/tasks/store.ts +602 -0
- package/src/tasks/time-parser.test.ts +94 -0
- package/src/tasks/time-parser.ts +610 -0
- package/src/tasks/timezone.ts +171 -0
- package/src/tasks/types.ts +128 -0
- package/src/types.ts +202 -0
- package/src/weather/chat-integration.ts +56 -0
- package/src/weather/location-catalog.ts +166 -0
- package/src/weather/open-meteo-provider.ts +221 -0
- package/src/weather/parser.test.ts +23 -0
- package/src/weather/parser.ts +102 -0
- package/src/weather/service.test.ts +54 -0
- package/src/weather/service.ts +188 -0
- package/src/weather/types.ts +56 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { extractWeatherDay, extractWeatherLocation, parseWeatherQuery } from "./parser.js";
|
|
2
|
+
import type {
|
|
3
|
+
WeatherChatResult,
|
|
4
|
+
WeatherConversationState,
|
|
5
|
+
WeatherDay,
|
|
6
|
+
WeatherMessageInspection,
|
|
7
|
+
WeatherMissingSlot,
|
|
8
|
+
WeatherProvider,
|
|
9
|
+
} from "./types.js";
|
|
10
|
+
|
|
11
|
+
function resolveMissingSlot(location?: string, day?: WeatherDay): WeatherMissingSlot | undefined {
|
|
12
|
+
if (!location && !day) {
|
|
13
|
+
return "both";
|
|
14
|
+
}
|
|
15
|
+
if (!location) {
|
|
16
|
+
return "location";
|
|
17
|
+
}
|
|
18
|
+
if (!day) {
|
|
19
|
+
return "day";
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function dayLabel(day: WeatherDay): string {
|
|
25
|
+
switch (day) {
|
|
26
|
+
case "today":
|
|
27
|
+
return "今天";
|
|
28
|
+
case "tomorrow":
|
|
29
|
+
return "明天";
|
|
30
|
+
case "day_after_tomorrow":
|
|
31
|
+
return "后天";
|
|
32
|
+
case "in_three_days":
|
|
33
|
+
return "大后天";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderClarifyReply(
|
|
38
|
+
missing: WeatherMissingSlot,
|
|
39
|
+
params: { location?: string; day?: WeatherDay },
|
|
40
|
+
): string {
|
|
41
|
+
if (missing === "both") {
|
|
42
|
+
return "你想查哪个城市、哪一天的天气?可以直接说“天津明天天气如何”。";
|
|
43
|
+
}
|
|
44
|
+
if (missing === "location") {
|
|
45
|
+
const dayText = params.day ? `${dayLabel(params.day)}` : "";
|
|
46
|
+
return `你想查${dayText}哪个城市的天气?`;
|
|
47
|
+
}
|
|
48
|
+
return `你想查${params.location ?? "这个城市"}哪一天的天气?可以说今天、明天、后天。`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function renderForecastReply(params: {
|
|
52
|
+
location: string;
|
|
53
|
+
day: WeatherDay;
|
|
54
|
+
summary: string;
|
|
55
|
+
max: number;
|
|
56
|
+
min: number;
|
|
57
|
+
precipitationProbabilityMax?: number;
|
|
58
|
+
precipitationSum?: number;
|
|
59
|
+
}): string {
|
|
60
|
+
const parts = [
|
|
61
|
+
`${params.location}${dayLabel(params.day)}${params.summary}`,
|
|
62
|
+
`最高 ${Math.round(params.max)}C`,
|
|
63
|
+
`最低 ${Math.round(params.min)}C`,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
if (typeof params.precipitationProbabilityMax === "number") {
|
|
67
|
+
parts.push(`降水概率 ${Math.round(params.precipitationProbabilityMax)}%`);
|
|
68
|
+
} else if (
|
|
69
|
+
typeof params.precipitationSum === "number" &&
|
|
70
|
+
Number.isFinite(params.precipitationSum)
|
|
71
|
+
) {
|
|
72
|
+
parts.push(`预计降水 ${params.precipitationSum.toFixed(1)} mm`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return `${parts.join(",")}。`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderLookupError(location: string, day: WeatherDay, error: unknown): string {
|
|
79
|
+
const message = error instanceof Error ? error.message : "";
|
|
80
|
+
if (message.startsWith("Location not found:")) {
|
|
81
|
+
return `我还没识别出“${location}”是哪个城市,你可以换个更完整的地名再试一次。`;
|
|
82
|
+
}
|
|
83
|
+
return `暂时查不到${location}${dayLabel(day)}的天气,请稍后再试。`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class WeatherService {
|
|
87
|
+
private readonly pending = new Map<string, WeatherConversationState>();
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
private readonly provider: WeatherProvider,
|
|
91
|
+
private readonly options: { pendingTtlMs?: number } = {},
|
|
92
|
+
) {}
|
|
93
|
+
|
|
94
|
+
inspectMessage(params: { userId: string; text: string }): WeatherMessageInspection {
|
|
95
|
+
const pending = this.getPending(params.userId);
|
|
96
|
+
if (pending) {
|
|
97
|
+
const location = extractWeatherLocation(params.text) ?? pending.location;
|
|
98
|
+
const day = extractWeatherDay(params.text) ?? pending.day;
|
|
99
|
+
return {
|
|
100
|
+
shouldHandle: true,
|
|
101
|
+
reason: "conversation",
|
|
102
|
+
missing: resolveMissingSlot(location, day),
|
|
103
|
+
location,
|
|
104
|
+
day,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const query = parseWeatherQuery(params.text);
|
|
109
|
+
if (!query.isWeatherQuery) {
|
|
110
|
+
return {
|
|
111
|
+
shouldHandle: false,
|
|
112
|
+
reason: "no_match",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
shouldHandle: true,
|
|
118
|
+
reason: "query",
|
|
119
|
+
missing: resolveMissingSlot(query.location, query.day),
|
|
120
|
+
location: query.location,
|
|
121
|
+
day: query.day,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async processMessage(params: { userId: string; text: string }): Promise<WeatherChatResult> {
|
|
126
|
+
const pending = this.getPending(params.userId);
|
|
127
|
+
const query = parseWeatherQuery(params.text);
|
|
128
|
+
const location = extractWeatherLocation(params.text) ?? pending?.location ?? query.location;
|
|
129
|
+
const day = extractWeatherDay(params.text) ?? pending?.day ?? query.day;
|
|
130
|
+
const missing = resolveMissingSlot(location, day);
|
|
131
|
+
|
|
132
|
+
if (missing) {
|
|
133
|
+
this.pending.set(params.userId, {
|
|
134
|
+
userId: params.userId,
|
|
135
|
+
location,
|
|
136
|
+
day,
|
|
137
|
+
updatedAt: Date.now(),
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
reply: renderClarifyReply(missing, { location, day }),
|
|
141
|
+
intent: "clarify",
|
|
142
|
+
missing,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.pending.delete(params.userId);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const forecast = await this.provider.lookupForecast({
|
|
150
|
+
locationQuery: location!,
|
|
151
|
+
day: day!,
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
reply: renderForecastReply({
|
|
155
|
+
location: forecast.location.name,
|
|
156
|
+
day: forecast.day,
|
|
157
|
+
summary: forecast.summary,
|
|
158
|
+
max: forecast.temperatureMax,
|
|
159
|
+
min: forecast.temperatureMin,
|
|
160
|
+
precipitationProbabilityMax: forecast.precipitationProbabilityMax,
|
|
161
|
+
precipitationSum: forecast.precipitationSum,
|
|
162
|
+
}),
|
|
163
|
+
intent: "weather_forecast",
|
|
164
|
+
forecast,
|
|
165
|
+
};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
reply: renderLookupError(location!, day!, error),
|
|
169
|
+
intent: "clarify",
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private getPending(userId: string): WeatherConversationState | undefined {
|
|
175
|
+
const pending = this.pending.get(userId);
|
|
176
|
+
if (!pending) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const ttlMs = this.options.pendingTtlMs ?? 30 * 60_000;
|
|
181
|
+
if (Date.now() - pending.updatedAt <= ttlMs) {
|
|
182
|
+
return pending;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.pending.delete(userId);
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type WeatherDay = "today" | "tomorrow" | "day_after_tomorrow" | "in_three_days";
|
|
2
|
+
|
|
3
|
+
export type WeatherLocation = {
|
|
4
|
+
name: string;
|
|
5
|
+
latitude: number;
|
|
6
|
+
longitude: number;
|
|
7
|
+
timezone?: string;
|
|
8
|
+
country?: string;
|
|
9
|
+
admin1?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type WeatherForecast = {
|
|
13
|
+
day: WeatherDay;
|
|
14
|
+
localDate: string;
|
|
15
|
+
summary: string;
|
|
16
|
+
temperatureMin: number;
|
|
17
|
+
temperatureMax: number;
|
|
18
|
+
precipitationProbabilityMax?: number;
|
|
19
|
+
precipitationSum?: number;
|
|
20
|
+
location: WeatherLocation;
|
|
21
|
+
source: "open-meteo";
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type WeatherQuery = {
|
|
25
|
+
isWeatherQuery: boolean;
|
|
26
|
+
location?: string;
|
|
27
|
+
day?: WeatherDay;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type WeatherMissingSlot = "location" | "day" | "both";
|
|
31
|
+
|
|
32
|
+
export type WeatherMessageInspection = {
|
|
33
|
+
shouldHandle: boolean;
|
|
34
|
+
reason: "conversation" | "query" | "no_match";
|
|
35
|
+
missing?: WeatherMissingSlot;
|
|
36
|
+
location?: string;
|
|
37
|
+
day?: WeatherDay;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type WeatherChatResult = {
|
|
41
|
+
reply: string;
|
|
42
|
+
intent: "weather_forecast" | "clarify";
|
|
43
|
+
forecast?: WeatherForecast;
|
|
44
|
+
missing?: WeatherMissingSlot;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type WeatherProvider = {
|
|
48
|
+
lookupForecast(params: { locationQuery: string; day: WeatherDay }): Promise<WeatherForecast>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type WeatherConversationState = {
|
|
52
|
+
userId: string;
|
|
53
|
+
location?: string;
|
|
54
|
+
day?: WeatherDay;
|
|
55
|
+
updatedAt: number;
|
|
56
|
+
};
|