@folio-agent/handler 0.4.0 → 0.4.1
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/dist/chat/graph.d.ts +7 -7
- package/dist/chat/graph.js +7 -8
- package/dist/chat/handler.js +3 -1
- package/dist/chat/rate-limit.js +5 -5
- package/dist/chat/types.d.ts +4 -2
- package/dist/chat/types.js +3 -2
- package/dist/ingest/cli.d.ts +1 -1
- package/dist/ingest/cli.js +13 -5
- package/dist/init/cli.d.ts +1 -0
- package/dist/init/cli.js +6 -3
- package/dist/sync/cli.js +8 -2
- package/package.json +6 -6
package/dist/chat/graph.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare function buildChatGraph(deps: ChatGraphDeps): import("@langchain/
|
|
|
11
11
|
route?: ChatRoute | undefined;
|
|
12
12
|
overLimit?: boolean | undefined;
|
|
13
13
|
answer?: string | undefined;
|
|
14
|
-
}, "__start__" | "
|
|
14
|
+
}, "__start__" | "generate" | "input_guard" | "log" | "route_message", {
|
|
15
15
|
input: import("@langchain/langgraph").LastValue<string>;
|
|
16
16
|
ip: import("@langchain/langgraph").LastValue<string>;
|
|
17
17
|
route: import("@langchain/langgraph").LastValue<ChatRoute | undefined>;
|
|
@@ -24,18 +24,18 @@ export declare function buildChatGraph(deps: ChatGraphDeps): import("@langchain/
|
|
|
24
24
|
overLimit: import("@langchain/langgraph").LastValue<boolean>;
|
|
25
25
|
answer: import("@langchain/langgraph").LastValue<string | undefined>;
|
|
26
26
|
}, import("@langchain/langgraph").StateDefinition, {
|
|
27
|
+
generate: {
|
|
28
|
+
answer: string;
|
|
29
|
+
};
|
|
27
30
|
input_guard: {
|
|
28
31
|
overLimit: true;
|
|
29
32
|
answer: string;
|
|
30
33
|
} | {
|
|
31
|
-
overLimit: false;
|
|
32
34
|
answer?: undefined;
|
|
35
|
+
overLimit: false;
|
|
33
36
|
};
|
|
37
|
+
log: {};
|
|
34
38
|
route_message: {
|
|
35
|
-
route: "
|
|
39
|
+
route: "inquiry" | "thoughts" | "works";
|
|
36
40
|
};
|
|
37
|
-
generate: {
|
|
38
|
-
answer: string;
|
|
39
|
-
};
|
|
40
|
-
log: {};
|
|
41
41
|
}, unknown, unknown, []>;
|
package/dist/chat/graph.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Annotation, END, START, StateGraph } from "@langchain/langgraph";
|
|
2
2
|
import { classifyRoute } from "./route.js";
|
|
3
3
|
import { DEFAULT_LANGUAGE } from "./types.js";
|
|
4
|
-
const RATE_LIMIT_REASON_TEXT = {
|
|
5
|
-
ja: { daily: "本日の上限", shortWindow: "直近の質問数の上限" },
|
|
6
|
-
en: { daily: "today's limit", shortWindow: "the recent question limit" },
|
|
7
|
-
};
|
|
8
4
|
const OVER_LIMIT_MESSAGE = {
|
|
9
|
-
ja: (
|
|
10
|
-
|
|
5
|
+
ja: (reason, config) => reason === "short_window"
|
|
6
|
+
? `${config.shortWindowMinutes}分間に${config.shortWindowMax}件までのご質問上限に達しました。少し時間を置いてから再度お試しください。お急ぎの場合はContactからお問い合わせください。`
|
|
7
|
+
: `${config.longWindowHours}時間に${config.longWindowMax}件までのご質問上限に達しました。時間を置いてから再度お試しください。お急ぎの場合はContactからお問い合わせください。`,
|
|
8
|
+
en: (reason, config) => reason === "short_window"
|
|
9
|
+
? `You've reached the limit of ${config.shortWindowMax} questions per ${config.shortWindowMinutes} minutes. Please try again in a little while. If it's urgent, please contact us via the Contact page.`
|
|
10
|
+
: `You've reached the limit of ${config.longWindowMax} questions per ${config.longWindowHours} hours. Please try again later. If it's urgent, please contact us via the Contact page.`,
|
|
11
11
|
};
|
|
12
12
|
const GENERATION_FAILED_MESSAGE = {
|
|
13
13
|
ja: "本日の受付上限に達したか、一時的な不具合が発生しています。恐れ入りますがContactからお問い合わせください。",
|
|
@@ -26,10 +26,9 @@ export function buildChatGraph(deps) {
|
|
|
26
26
|
.addNode("input_guard", async (state) => {
|
|
27
27
|
const result = await deps.checkRateLimit(state.ip);
|
|
28
28
|
if (!result.allowed) {
|
|
29
|
-
const reasonText = result.reason === "daily" ? RATE_LIMIT_REASON_TEXT[language].daily : RATE_LIMIT_REASON_TEXT[language].shortWindow;
|
|
30
29
|
return {
|
|
31
30
|
overLimit: true,
|
|
32
|
-
answer: OVER_LIMIT_MESSAGE[language](
|
|
31
|
+
answer: OVER_LIMIT_MESSAGE[language](result.reason, deps.rateLimitConfig),
|
|
33
32
|
};
|
|
34
33
|
}
|
|
35
34
|
return { overLimit: false };
|
package/dist/chat/handler.js
CHANGED
|
@@ -4,10 +4,12 @@ import { checkRateLimit } from "./rate-limit.js";
|
|
|
4
4
|
import { DEFAULT_RATE_LIMIT_CONFIG } from "./types.js";
|
|
5
5
|
const MAX_INPUT_LENGTH = 1000;
|
|
6
6
|
export function createChatHandler(config) {
|
|
7
|
+
const rateLimitConfig = config.rateLimitConfig ?? DEFAULT_RATE_LIMIT_CONFIG;
|
|
7
8
|
const graph = buildChatGraph({
|
|
8
|
-
checkRateLimit: (ip) => checkRateLimit(config.db, ip, new Date(),
|
|
9
|
+
checkRateLimit: (ip) => checkRateLimit(config.db, ip, new Date(), rateLimitConfig),
|
|
9
10
|
generateAnswer: config.generateAnswer,
|
|
10
11
|
logChat: (entry) => logChat(config.db, entry),
|
|
12
|
+
rateLimitConfig,
|
|
11
13
|
language: config.language,
|
|
12
14
|
});
|
|
13
15
|
return async (request) => {
|
package/dist/chat/rate-limit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_RATE_LIMIT_CONFIG } from "./types.js";
|
|
2
2
|
const MINUTE_MS = 60_000;
|
|
3
|
-
const
|
|
3
|
+
const HOUR_MS = 60 * MINUTE_MS;
|
|
4
4
|
async function countSince(db, ip, sinceIso) {
|
|
5
5
|
const row = await db
|
|
6
6
|
.prepare("SELECT COUNT(*) as count FROM chat_logs WHERE ip = ? AND created_at >= ? AND over_limit = 0")
|
|
@@ -14,10 +14,10 @@ export async function checkRateLimit(db, ip, now = new Date(), config = DEFAULT_
|
|
|
14
14
|
if (shortCount >= config.shortWindowMax) {
|
|
15
15
|
return { allowed: false, reason: "short_window" };
|
|
16
16
|
}
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
return { allowed: false, reason: "
|
|
17
|
+
const longWindowStart = new Date(now.getTime() - config.longWindowHours * HOUR_MS).toISOString();
|
|
18
|
+
const longWindowCount = await countSince(db, ip, longWindowStart);
|
|
19
|
+
if (longWindowCount >= config.longWindowMax) {
|
|
20
|
+
return { allowed: false, reason: "long_window" };
|
|
21
21
|
}
|
|
22
22
|
return { allowed: true };
|
|
23
23
|
}
|
package/dist/chat/types.d.ts
CHANGED
|
@@ -4,10 +4,11 @@ export declare const DEFAULT_LANGUAGE: Language;
|
|
|
4
4
|
export interface RateLimitConfig {
|
|
5
5
|
shortWindowMinutes: number;
|
|
6
6
|
shortWindowMax: number;
|
|
7
|
-
|
|
7
|
+
longWindowHours: number;
|
|
8
|
+
longWindowMax: number;
|
|
8
9
|
}
|
|
9
10
|
export declare const DEFAULT_RATE_LIMIT_CONFIG: RateLimitConfig;
|
|
10
|
-
export type RateLimitReason = "short_window" | "
|
|
11
|
+
export type RateLimitReason = "short_window" | "long_window";
|
|
11
12
|
export interface RateLimitResult {
|
|
12
13
|
allowed: boolean;
|
|
13
14
|
reason?: RateLimitReason;
|
|
@@ -23,5 +24,6 @@ export interface ChatGraphDeps {
|
|
|
23
24
|
checkRateLimit: (ip: string) => Promise<RateLimitResult>;
|
|
24
25
|
generateAnswer: (input: string, route: ChatRoute) => Promise<string>;
|
|
25
26
|
logChat: (entry: ChatLogEntry) => Promise<void>;
|
|
27
|
+
rateLimitConfig: RateLimitConfig;
|
|
26
28
|
language?: Language;
|
|
27
29
|
}
|
package/dist/chat/types.js
CHANGED
package/dist/ingest/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare function main(): Promise<void>;
|
package/dist/ingest/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { generateKnowledge } from "./generate.js";
|
|
4
|
-
async function main() {
|
|
6
|
+
export async function main() {
|
|
5
7
|
const configPath = process.argv[2];
|
|
6
8
|
const outputPath = process.argv[3];
|
|
7
9
|
if (!configPath || !outputPath) {
|
|
@@ -17,7 +19,13 @@ async function main() {
|
|
|
17
19
|
console.warn(`warning: ${warning}`);
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Only run when executed directly (e.g. `folio-agent-ingest ...`), not when imported by tests.
|
|
23
|
+
// Compares realpaths, not raw paths: npm's node_modules/.bin/ entries are symlinks, so
|
|
24
|
+
// process.argv[1] stays the symlink path while import.meta.url is already the resolved target
|
|
25
|
+
// (see sync/cli.ts for the fuller rationale).
|
|
26
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === realpathSync(process.argv[1])) {
|
|
27
|
+
main().catch((error) => {
|
|
28
|
+
console.error(error);
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
});
|
|
31
|
+
}
|
package/dist/init/cli.d.ts
CHANGED
|
@@ -13,3 +13,4 @@ export interface DevVarsAndGitignorePlan {
|
|
|
13
13
|
* (see `geminiApiKeySkipNote`), and by then the file must already be gitignored.
|
|
14
14
|
*/
|
|
15
15
|
export declare function planDevVarsAndGitignore(geminiApiKey: string | undefined, devVarsContent: string, gitignoreContent: string): DevVarsAndGitignorePlan;
|
|
16
|
+
export declare function main(): Promise<void>;
|
package/dist/init/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -96,7 +96,7 @@ function seedDefaults(previous) {
|
|
|
96
96
|
apiRoutePath: undefined,
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
|
-
async function main() {
|
|
99
|
+
export async function main() {
|
|
100
100
|
const previousConfig = await readJson(CONFIG_PATH);
|
|
101
101
|
const answers = await runWizard(seedDefaults(previousConfig));
|
|
102
102
|
const text = CLI_TEXT[answers.language];
|
|
@@ -184,7 +184,10 @@ async function main() {
|
|
|
184
184
|
}
|
|
185
185
|
// Only run the wizard when executed directly (the bin entry point); importing this module for
|
|
186
186
|
// unit tests (e.g. `planDevVarsAndGitignore`) must not trigger the interactive CLI as a side effect.
|
|
187
|
-
|
|
187
|
+
// Compares realpaths, not raw paths: npm's node_modules/.bin/ entries are symlinks, so
|
|
188
|
+
// process.argv[1] stays the symlink path while import.meta.url is already the resolved target
|
|
189
|
+
// (see sync/cli.ts for the fuller rationale).
|
|
190
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === realpathSync(process.argv[1])) {
|
|
188
191
|
main().catch((error) => {
|
|
189
192
|
console.error(error);
|
|
190
193
|
process.exitCode = 1;
|
package/dist/sync/cli.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { scanZennArticles } from "../ingest/zenn.js";
|
|
4
6
|
/**
|
|
5
7
|
* Reads `config.zenn` from `configPath`, scans the configured articles/
|
|
@@ -27,8 +29,12 @@ async function main() {
|
|
|
27
29
|
console.log(`wrote ${pages.length} zenn page(s) -> ${outputPath}`);
|
|
28
30
|
}
|
|
29
31
|
// Only run when executed directly (e.g. `folio-agent-sync-zenn ...`), not
|
|
30
|
-
// when imported by tests.
|
|
31
|
-
|
|
32
|
+
// when imported by tests. Compares realpaths: npm's node_modules/.bin/
|
|
33
|
+
// entries are symlinks, so process.argv[1] stays the symlink path while
|
|
34
|
+
// import.meta.url is already the resolved target — a plain string/URL
|
|
35
|
+
// comparison of the two (matching ingest/cli.ts as-is) never matches, and
|
|
36
|
+
// main() silently never runs when invoked via the published bin name.
|
|
37
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === realpathSync(process.argv[1])) {
|
|
32
38
|
main().catch((error) => {
|
|
33
39
|
console.error(error);
|
|
34
40
|
process.exitCode = 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@folio-agent/handler",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Chat handler (LangGraph.js on Cloudflare Workers) + knowledge ingestion CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@clack/prompts": "^1.7.0",
|
|
43
|
-
"@google/genai": "^2.
|
|
44
|
-
"@langchain/langgraph": "^1.4.
|
|
45
|
-
"node-html-parser": "^
|
|
43
|
+
"@google/genai": "^2.15.0",
|
|
44
|
+
"@langchain/langgraph": "^1.4.8",
|
|
45
|
+
"node-html-parser": "^9.0.1",
|
|
46
46
|
"picomatch": "^4.0.5"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@cloudflare/workers-types": "^5.
|
|
49
|
+
"@cloudflare/workers-types": "^5.20260801.1",
|
|
50
50
|
"@types/picomatch": "^4.0.2",
|
|
51
|
-
"wrangler": "^4.
|
|
51
|
+
"wrangler": "^4.118.0"
|
|
52
52
|
}
|
|
53
53
|
}
|