@asterxsk/kiln 0.1.0 → 0.2.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/LICENSE +21 -21
- package/README.md +170 -170
- package/agent/AGENTS.md +67 -67
- package/agent/README.md +5 -5
- package/agent/extensions/AGENTS.md +68 -68
- package/agent/extensions/ask-user/index.ts +418 -418
- package/agent/extensions/ask-user/package-lock.json +769 -769
- package/agent/extensions/ask-user/package.json +19 -19
- package/agent/extensions/ask-user/prompt.ts +45 -45
- package/agent/extensions/ask-user/tsconfig.json +7 -7
- package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
- package/agent/extensions/background-terminals/index.ts +627 -627
- package/agent/extensions/background-terminals/manager.test.ts +735 -735
- package/agent/extensions/background-terminals/output.test.ts +109 -109
- package/agent/extensions/background-terminals/package-lock.json +769 -769
- package/agent/extensions/background-terminals/package.json +17 -17
- package/agent/extensions/background-terminals/prompt.test.ts +125 -125
- package/agent/extensions/background-terminals/ps.test.ts +82 -82
- package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
- package/agent/extensions/background-terminals/src/domain.ts +87 -87
- package/agent/extensions/background-terminals/src/manager.ts +907 -907
- package/agent/extensions/background-terminals/src/output.ts +84 -84
- package/agent/extensions/background-terminals/src/prompt.ts +142 -142
- package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
- package/agent/extensions/background-terminals/src/runtime.ts +36 -36
- package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
- package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
- package/agent/extensions/background-terminals/tsconfig.json +7 -7
- package/agent/extensions/file-search/index.spec.ts +443 -443
- package/agent/extensions/file-search/index.ts +459 -459
- package/agent/extensions/file-search/package-lock.json +2253 -2253
- package/agent/extensions/file-search/package.json +23 -23
- package/agent/extensions/file-search/src/args.ts +122 -122
- package/agent/extensions/file-search/src/binaries.ts +422 -422
- package/agent/extensions/file-search/src/output.ts +126 -126
- package/agent/extensions/file-search/src/process.ts +146 -146
- package/agent/extensions/file-search/src/prompt.ts +52 -52
- package/agent/extensions/file-search/tsconfig.json +7 -7
- package/agent/extensions/modelconf/PLAN.md +915 -915
- package/agent/extensions/modelconf/index.ts +296 -296
- package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
- package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
- package/agent/extensions/pi-web-access/LICENSE +21 -21
- package/agent/extensions/pi-web-access/README.md +470 -470
- package/agent/extensions/pi-web-access/SECURITY.md +5 -5
- package/agent/extensions/pi-web-access/activity.ts +101 -101
- package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
- package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
- package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
- package/agent/extensions/pi-web-access/content-find.ts +139 -139
- package/agent/extensions/pi-web-access/credential-source.ts +191 -191
- package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
- package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
- package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
- package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
- package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
- package/agent/extensions/pi-web-access/exa.ts +526 -526
- package/agent/extensions/pi-web-access/extract.ts +1196 -1196
- package/agent/extensions/pi-web-access/feature-config.ts +29 -29
- package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
- package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
- package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
- package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
- package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
- package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
- package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
- package/agent/extensions/pi-web-access/github-api.ts +197 -197
- package/agent/extensions/pi-web-access/github-extract.ts +746 -746
- package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
- package/agent/extensions/pi-web-access/index.ts +1737 -1737
- package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
- package/agent/extensions/pi-web-access/package.json +64 -64
- package/agent/extensions/pi-web-access/page-query.ts +96 -96
- package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
- package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
- package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
- package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
- package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
- package/agent/extensions/pi-web-access/source-check.ts +282 -282
- package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
- package/agent/extensions/pi-web-access/storage.ts +521 -521
- package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
- package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
- package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
- package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
- package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
- package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
- package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
- package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
- package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
- package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
- package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
- package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
- package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
- package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
- package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
- package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
- package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
- package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
- package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
- package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
- package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
- package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
- package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
- package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
- package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
- package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
- package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
- package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
- package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
- package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
- package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
- package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
- package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
- package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
- package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
- package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
- package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
- package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
- package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
- package/agent/extensions/pi-web-access/tsconfig.json +11 -11
- package/agent/extensions/pi-web-access/utils.ts +451 -451
- package/agent/extensions/pi-web-access/video-extract.ts +392 -392
- package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
- package/agent/extensions/shared/activity-status.ts +31 -31
- package/agent/extensions/shared/child-session.test.ts +270 -270
- package/agent/extensions/shared/child-session.ts +148 -148
- package/agent/extensions/shared/context-utilization.test.ts +48 -48
- package/agent/extensions/shared/context-utilization.ts +47 -47
- package/agent/extensions/shared/dashboard-state.ts +99 -99
- package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
- package/agent/extensions/shared/tool-call-timeout.ts +104 -104
- package/agent/extensions/subagents/by-the-way.test.ts +29 -29
- package/agent/extensions/subagents/claude.test.ts +119 -119
- package/agent/extensions/subagents/codex.test.ts +102 -102
- package/agent/extensions/subagents/context-usage.test.ts +107 -107
- package/agent/extensions/subagents/docs/design-plan.md +568 -568
- package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
- package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
- package/agent/extensions/subagents/index.ts +779 -779
- package/agent/extensions/subagents/manager.test.ts +276 -276
- package/agent/extensions/subagents/package-lock.json +2244 -2244
- package/agent/extensions/subagents/package.json +19 -19
- package/agent/extensions/subagents/result-delivery.test.ts +27 -27
- package/agent/extensions/subagents/src/backend.ts +73 -73
- package/agent/extensions/subagents/src/backends/claude.ts +701 -701
- package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
- package/agent/extensions/subagents/src/backends/pi.ts +575 -575
- package/agent/extensions/subagents/src/backends/stub.ts +300 -300
- package/agent/extensions/subagents/src/by-the-way.ts +21 -21
- package/agent/extensions/subagents/src/domain.ts +253 -253
- package/agent/extensions/subagents/src/format.ts +74 -74
- package/agent/extensions/subagents/src/manager.ts +736 -736
- package/agent/extensions/subagents/src/prompt.ts +92 -92
- package/agent/extensions/subagents/src/result-delivery.ts +20 -20
- package/agent/extensions/subagents/src/runtime.ts +53 -53
- package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
- package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
- package/agent/extensions/subagents/takeover.test.ts +29 -29
- package/agent/extensions/subagents/tsconfig.json +7 -7
- package/agent/extensions/todo/AGENTS.md +38 -38
- package/agent/extensions/todo/LICENSE +21 -21
- package/agent/extensions/todo/config.ts +55 -55
- package/agent/extensions/todo/index.ts +151 -151
- package/agent/extensions/todo/locales/de.json +17 -17
- package/agent/extensions/todo/locales/en.json +15 -15
- package/agent/extensions/todo/locales/es.json +17 -17
- package/agent/extensions/todo/locales/fr.json +17 -17
- package/agent/extensions/todo/locales/pt-BR.json +17 -17
- package/agent/extensions/todo/locales/pt.json +17 -17
- package/agent/extensions/todo/locales/ru.json +17 -17
- package/agent/extensions/todo/locales/uk.json +17 -17
- package/agent/extensions/todo/locales/zh.json +17 -17
- package/agent/extensions/todo/package-lock.json +3358 -3358
- package/agent/extensions/todo/package.json +67 -67
- package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
- package/agent/extensions/todo/state/invariants.ts +20 -20
- package/agent/extensions/todo/state/replay.ts +38 -38
- package/agent/extensions/todo/state/selectors.ts +107 -107
- package/agent/extensions/todo/state/state-reducer.ts +326 -326
- package/agent/extensions/todo/state/state.ts +18 -18
- package/agent/extensions/todo/state/store.ts +82 -82
- package/agent/extensions/todo/state/task-graph.ts +57 -57
- package/agent/extensions/todo/todo-overlay.ts +200 -200
- package/agent/extensions/todo/todo.ts +155 -155
- package/agent/extensions/todo/tool/response-envelope.ts +109 -109
- package/agent/extensions/todo/tool/types.ts +206 -206
- package/agent/extensions/todo/view/format.ts +177 -177
- package/agent/install.ps1 +637 -527
- package/agent/install.sh +620 -511
- package/agent/keybindings.json +7 -7
- package/bin/kiln.js +124 -11
- package/package.json +8 -2
- package/agent/extensions/taste/index.ts +0 -443
- package/agent/extensions/taste/install.ps1 +0 -23
- package/agent/extensions/taste/install.sh +0 -21
- /package/agent/extensions/{status line → statusline}/index.ts +0 -0
- /package/agent/extensions/{status line → statusline}/install.ps1 +0 -0
- /package/agent/extensions/{status line → statusline}/install.sh +0 -0
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
export const MODEL_INFO_CHANNEL = "dashboard:model-info";
|
|
2
|
-
export const GIT_INFO_CHANNEL = "dashboard:git-info";
|
|
3
|
-
export const REFRESH_CHANNEL = "dashboard:refresh";
|
|
4
|
-
|
|
5
|
-
export interface ModelInfoState {
|
|
6
|
-
provider: string;
|
|
7
|
-
modelId: string;
|
|
8
|
-
modelName: string;
|
|
9
|
-
thinking: string;
|
|
10
|
-
contextTokens: number | null;
|
|
11
|
-
contextWindow: number;
|
|
12
|
-
contextPercent: number | null;
|
|
13
|
-
cost: number;
|
|
14
|
-
tokensPerSecond: number | null;
|
|
15
|
-
generating: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface PullRequestInfo {
|
|
19
|
-
number: number;
|
|
20
|
-
url: string;
|
|
21
|
-
isDraft: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface GitInfoState {
|
|
25
|
-
isRepository: boolean;
|
|
26
|
-
branch: string | null;
|
|
27
|
-
changedFiles: number;
|
|
28
|
-
pullRequest: PullRequestInfo | null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function emptyModelInfoState(): ModelInfoState {
|
|
32
|
-
return {
|
|
33
|
-
provider: "",
|
|
34
|
-
modelId: "no-model",
|
|
35
|
-
modelName: "No model",
|
|
36
|
-
thinking: "off",
|
|
37
|
-
contextTokens: null,
|
|
38
|
-
contextWindow: 0,
|
|
39
|
-
contextPercent: null,
|
|
40
|
-
cost: 0,
|
|
41
|
-
tokensPerSecond: null,
|
|
42
|
-
generating: false,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function emptyGitInfoState(): GitInfoState {
|
|
47
|
-
return {
|
|
48
|
-
isRepository: false,
|
|
49
|
-
branch: null,
|
|
50
|
-
changedFiles: 0,
|
|
51
|
-
pullRequest: null,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
56
|
-
return typeof value === "object" && value !== null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isNullableNumber(value: unknown) {
|
|
60
|
-
return value === null || typeof value === "number";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function isModelInfoState(value: unknown): value is ModelInfoState {
|
|
64
|
-
if (!isRecord(value)) return false;
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
typeof value.provider === "string" &&
|
|
68
|
-
typeof value.modelId === "string" &&
|
|
69
|
-
typeof value.modelName === "string" &&
|
|
70
|
-
typeof value.thinking === "string" &&
|
|
71
|
-
isNullableNumber(value.contextTokens) &&
|
|
72
|
-
typeof value.contextWindow === "number" &&
|
|
73
|
-
isNullableNumber(value.contextPercent) &&
|
|
74
|
-
typeof value.cost === "number" &&
|
|
75
|
-
isNullableNumber(value.tokensPerSecond) &&
|
|
76
|
-
typeof value.generating === "boolean"
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isPullRequestInfo(value: unknown): value is PullRequestInfo {
|
|
81
|
-
if (!isRecord(value)) return false;
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
typeof value.number === "number" &&
|
|
85
|
-
typeof value.url === "string" &&
|
|
86
|
-
typeof value.isDraft === "boolean"
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function isGitInfoState(value: unknown): value is GitInfoState {
|
|
91
|
-
if (!isRecord(value)) return false;
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
typeof value.isRepository === "boolean" &&
|
|
95
|
-
(value.branch === null || typeof value.branch === "string") &&
|
|
96
|
-
typeof value.changedFiles === "number" &&
|
|
97
|
-
(value.pullRequest === null || isPullRequestInfo(value.pullRequest))
|
|
98
|
-
);
|
|
99
|
-
}
|
|
1
|
+
export const MODEL_INFO_CHANNEL = "dashboard:model-info";
|
|
2
|
+
export const GIT_INFO_CHANNEL = "dashboard:git-info";
|
|
3
|
+
export const REFRESH_CHANNEL = "dashboard:refresh";
|
|
4
|
+
|
|
5
|
+
export interface ModelInfoState {
|
|
6
|
+
provider: string;
|
|
7
|
+
modelId: string;
|
|
8
|
+
modelName: string;
|
|
9
|
+
thinking: string;
|
|
10
|
+
contextTokens: number | null;
|
|
11
|
+
contextWindow: number;
|
|
12
|
+
contextPercent: number | null;
|
|
13
|
+
cost: number;
|
|
14
|
+
tokensPerSecond: number | null;
|
|
15
|
+
generating: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PullRequestInfo {
|
|
19
|
+
number: number;
|
|
20
|
+
url: string;
|
|
21
|
+
isDraft: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface GitInfoState {
|
|
25
|
+
isRepository: boolean;
|
|
26
|
+
branch: string | null;
|
|
27
|
+
changedFiles: number;
|
|
28
|
+
pullRequest: PullRequestInfo | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function emptyModelInfoState(): ModelInfoState {
|
|
32
|
+
return {
|
|
33
|
+
provider: "",
|
|
34
|
+
modelId: "no-model",
|
|
35
|
+
modelName: "No model",
|
|
36
|
+
thinking: "off",
|
|
37
|
+
contextTokens: null,
|
|
38
|
+
contextWindow: 0,
|
|
39
|
+
contextPercent: null,
|
|
40
|
+
cost: 0,
|
|
41
|
+
tokensPerSecond: null,
|
|
42
|
+
generating: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function emptyGitInfoState(): GitInfoState {
|
|
47
|
+
return {
|
|
48
|
+
isRepository: false,
|
|
49
|
+
branch: null,
|
|
50
|
+
changedFiles: 0,
|
|
51
|
+
pullRequest: null,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
56
|
+
return typeof value === "object" && value !== null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isNullableNumber(value: unknown) {
|
|
60
|
+
return value === null || typeof value === "number";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isModelInfoState(value: unknown): value is ModelInfoState {
|
|
64
|
+
if (!isRecord(value)) return false;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
typeof value.provider === "string" &&
|
|
68
|
+
typeof value.modelId === "string" &&
|
|
69
|
+
typeof value.modelName === "string" &&
|
|
70
|
+
typeof value.thinking === "string" &&
|
|
71
|
+
isNullableNumber(value.contextTokens) &&
|
|
72
|
+
typeof value.contextWindow === "number" &&
|
|
73
|
+
isNullableNumber(value.contextPercent) &&
|
|
74
|
+
typeof value.cost === "number" &&
|
|
75
|
+
isNullableNumber(value.tokensPerSecond) &&
|
|
76
|
+
typeof value.generating === "boolean"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isPullRequestInfo(value: unknown): value is PullRequestInfo {
|
|
81
|
+
if (!isRecord(value)) return false;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
typeof value.number === "number" &&
|
|
85
|
+
typeof value.url === "string" &&
|
|
86
|
+
typeof value.isDraft === "boolean"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function isGitInfoState(value: unknown): value is GitInfoState {
|
|
91
|
+
if (!isRecord(value)) return false;
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
typeof value.isRepository === "boolean" &&
|
|
95
|
+
(value.branch === null || typeof value.branch === "string") &&
|
|
96
|
+
typeof value.changedFiles === "number" &&
|
|
97
|
+
(value.pullRequest === null || isPullRequestInfo(value.pullRequest))
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Type } from "typebox";
|
|
5
|
-
import {
|
|
6
|
-
createToolCallTimeoutGuard,
|
|
7
|
-
runWithToolCallTimeout,
|
|
8
|
-
CHILD_TOOL_CALL_TIMEOUT_MS,
|
|
9
|
-
ToolCallTimeoutError,
|
|
10
|
-
} from "./tool-call-timeout.ts";
|
|
11
|
-
|
|
12
|
-
test("the production timeout error names the tool and three-minute limit", () => {
|
|
13
|
-
assert.equal(
|
|
14
|
-
new ToolCallTimeoutError("fixture_tool", CHILD_TOOL_CALL_TIMEOUT_MS)
|
|
15
|
-
.message,
|
|
16
|
-
'Tool call "fixture_tool" timed out after 3 minutes.',
|
|
17
|
-
);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("a hung tool call fails clearly and receives an abort signal", async () => {
|
|
21
|
-
let executionSignal: AbortSignal | undefined;
|
|
22
|
-
|
|
23
|
-
await assert.rejects(
|
|
24
|
-
runWithToolCallTimeout("hung_fixture", 10, undefined, (signal) => {
|
|
25
|
-
executionSignal = signal;
|
|
26
|
-
return new Promise(() => {});
|
|
27
|
-
}),
|
|
28
|
-
(error: unknown) => {
|
|
29
|
-
assert.equal(error instanceof ToolCallTimeoutError, true);
|
|
30
|
-
assert.equal(
|
|
31
|
-
error instanceof Error ? error.message : "",
|
|
32
|
-
'Tool call "hung_fixture" timed out after 10 ms.',
|
|
33
|
-
);
|
|
34
|
-
return true;
|
|
35
|
-
},
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
assert.equal(executionSignal?.aborted, true);
|
|
39
|
-
assert.equal(executionSignal?.reason instanceof ToolCallTimeoutError, true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("parent cancellation still stops the timeout wrapper immediately", async () => {
|
|
43
|
-
const controller = new AbortController();
|
|
44
|
-
const reason = new Error("cancelled fixture");
|
|
45
|
-
const pending = runWithToolCallTimeout(
|
|
46
|
-
"hung_fixture",
|
|
47
|
-
60_000,
|
|
48
|
-
controller.signal,
|
|
49
|
-
() => new Promise(() => {}),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
controller.abort(reason);
|
|
53
|
-
|
|
54
|
-
await assert.rejects(pending, (error: unknown) => error === reason);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("the guard wraps each definition once and can discover later tools", () => {
|
|
58
|
-
const definitions = new Map<string, ToolDefinition>();
|
|
59
|
-
const createDefinition = (name: string): ToolDefinition => ({
|
|
60
|
-
name,
|
|
61
|
-
label: name,
|
|
62
|
-
description: name,
|
|
63
|
-
parameters: Type.Object({}),
|
|
64
|
-
async execute() {
|
|
65
|
-
return { content: [{ type: "text", text: "done" }], details: {} };
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
const first = createDefinition("first");
|
|
69
|
-
definitions.set(first.name, first);
|
|
70
|
-
const registry = {
|
|
71
|
-
getAllTools: () => [...definitions.keys()].map((name) => ({ name })),
|
|
72
|
-
getToolDefinition: (name: string) => definitions.get(name),
|
|
73
|
-
};
|
|
74
|
-
const guard = createToolCallTimeoutGuard(10);
|
|
75
|
-
|
|
76
|
-
const firstExecute = first.execute;
|
|
77
|
-
guard.apply(registry);
|
|
78
|
-
const firstWrappedExecute = first.execute;
|
|
79
|
-
assert.notEqual(firstWrappedExecute, firstExecute);
|
|
80
|
-
|
|
81
|
-
const second = createDefinition("second");
|
|
82
|
-
const secondExecute = second.execute;
|
|
83
|
-
definitions.set(second.name, second);
|
|
84
|
-
guard.apply(registry);
|
|
85
|
-
|
|
86
|
-
assert.equal(first.execute, firstWrappedExecute);
|
|
87
|
-
assert.notEqual(second.execute, secondExecute);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("successful and terminating tool results pass through unchanged", async () => {
|
|
91
|
-
const result = {
|
|
92
|
-
content: [{ type: "text" as const, text: "recorded" }],
|
|
93
|
-
details: { value: "fixture" },
|
|
94
|
-
terminate: true,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
assert.equal(
|
|
98
|
-
await runWithToolCallTimeout(
|
|
99
|
-
"structured_output",
|
|
100
|
-
10,
|
|
101
|
-
undefined,
|
|
102
|
-
async () => result,
|
|
103
|
-
),
|
|
104
|
-
result,
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("the timeout is fresh for each tool call, not shared across calls", async () => {
|
|
109
|
-
const execute = () =>
|
|
110
|
-
runWithToolCallTimeout("slow_fixture", 100, undefined, async () => {
|
|
111
|
-
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
112
|
-
return "done";
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
assert.equal(await execute(), "done");
|
|
116
|
-
assert.equal(await execute(), "done");
|
|
117
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import {
|
|
6
|
+
createToolCallTimeoutGuard,
|
|
7
|
+
runWithToolCallTimeout,
|
|
8
|
+
CHILD_TOOL_CALL_TIMEOUT_MS,
|
|
9
|
+
ToolCallTimeoutError,
|
|
10
|
+
} from "./tool-call-timeout.ts";
|
|
11
|
+
|
|
12
|
+
test("the production timeout error names the tool and three-minute limit", () => {
|
|
13
|
+
assert.equal(
|
|
14
|
+
new ToolCallTimeoutError("fixture_tool", CHILD_TOOL_CALL_TIMEOUT_MS)
|
|
15
|
+
.message,
|
|
16
|
+
'Tool call "fixture_tool" timed out after 3 minutes.',
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("a hung tool call fails clearly and receives an abort signal", async () => {
|
|
21
|
+
let executionSignal: AbortSignal | undefined;
|
|
22
|
+
|
|
23
|
+
await assert.rejects(
|
|
24
|
+
runWithToolCallTimeout("hung_fixture", 10, undefined, (signal) => {
|
|
25
|
+
executionSignal = signal;
|
|
26
|
+
return new Promise(() => {});
|
|
27
|
+
}),
|
|
28
|
+
(error: unknown) => {
|
|
29
|
+
assert.equal(error instanceof ToolCallTimeoutError, true);
|
|
30
|
+
assert.equal(
|
|
31
|
+
error instanceof Error ? error.message : "",
|
|
32
|
+
'Tool call "hung_fixture" timed out after 10 ms.',
|
|
33
|
+
);
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
assert.equal(executionSignal?.aborted, true);
|
|
39
|
+
assert.equal(executionSignal?.reason instanceof ToolCallTimeoutError, true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("parent cancellation still stops the timeout wrapper immediately", async () => {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const reason = new Error("cancelled fixture");
|
|
45
|
+
const pending = runWithToolCallTimeout(
|
|
46
|
+
"hung_fixture",
|
|
47
|
+
60_000,
|
|
48
|
+
controller.signal,
|
|
49
|
+
() => new Promise(() => {}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
controller.abort(reason);
|
|
53
|
+
|
|
54
|
+
await assert.rejects(pending, (error: unknown) => error === reason);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("the guard wraps each definition once and can discover later tools", () => {
|
|
58
|
+
const definitions = new Map<string, ToolDefinition>();
|
|
59
|
+
const createDefinition = (name: string): ToolDefinition => ({
|
|
60
|
+
name,
|
|
61
|
+
label: name,
|
|
62
|
+
description: name,
|
|
63
|
+
parameters: Type.Object({}),
|
|
64
|
+
async execute() {
|
|
65
|
+
return { content: [{ type: "text", text: "done" }], details: {} };
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
const first = createDefinition("first");
|
|
69
|
+
definitions.set(first.name, first);
|
|
70
|
+
const registry = {
|
|
71
|
+
getAllTools: () => [...definitions.keys()].map((name) => ({ name })),
|
|
72
|
+
getToolDefinition: (name: string) => definitions.get(name),
|
|
73
|
+
};
|
|
74
|
+
const guard = createToolCallTimeoutGuard(10);
|
|
75
|
+
|
|
76
|
+
const firstExecute = first.execute;
|
|
77
|
+
guard.apply(registry);
|
|
78
|
+
const firstWrappedExecute = first.execute;
|
|
79
|
+
assert.notEqual(firstWrappedExecute, firstExecute);
|
|
80
|
+
|
|
81
|
+
const second = createDefinition("second");
|
|
82
|
+
const secondExecute = second.execute;
|
|
83
|
+
definitions.set(second.name, second);
|
|
84
|
+
guard.apply(registry);
|
|
85
|
+
|
|
86
|
+
assert.equal(first.execute, firstWrappedExecute);
|
|
87
|
+
assert.notEqual(second.execute, secondExecute);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("successful and terminating tool results pass through unchanged", async () => {
|
|
91
|
+
const result = {
|
|
92
|
+
content: [{ type: "text" as const, text: "recorded" }],
|
|
93
|
+
details: { value: "fixture" },
|
|
94
|
+
terminate: true,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
assert.equal(
|
|
98
|
+
await runWithToolCallTimeout(
|
|
99
|
+
"structured_output",
|
|
100
|
+
10,
|
|
101
|
+
undefined,
|
|
102
|
+
async () => result,
|
|
103
|
+
),
|
|
104
|
+
result,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("the timeout is fresh for each tool call, not shared across calls", async () => {
|
|
109
|
+
const execute = () =>
|
|
110
|
+
runWithToolCallTimeout("slow_fixture", 100, undefined, async () => {
|
|
111
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
112
|
+
return "done";
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
assert.equal(await execute(), "done");
|
|
116
|
+
assert.equal(await execute(), "done");
|
|
117
|
+
});
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
export const CHILD_TOOL_CALL_TIMEOUT_MS = 3 * 60 * 1_000;
|
|
4
|
-
|
|
5
|
-
interface ToolRegistry {
|
|
6
|
-
getAllTools(): Array<{ name: string }>;
|
|
7
|
-
getToolDefinition(name: string): ToolDefinition | undefined;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function formatTimeout(timeoutMs: number) {
|
|
11
|
-
if (timeoutMs % 60_000 === 0) {
|
|
12
|
-
const minutes = timeoutMs / 60_000;
|
|
13
|
-
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
14
|
-
}
|
|
15
|
-
if (timeoutMs % 1_000 === 0) {
|
|
16
|
-
const seconds = timeoutMs / 1_000;
|
|
17
|
-
return `${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
18
|
-
}
|
|
19
|
-
return `${timeoutMs} ms`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class ToolCallTimeoutError extends Error {
|
|
23
|
-
constructor(toolName: string, timeoutMs: number) {
|
|
24
|
-
super(
|
|
25
|
-
`Tool call "${toolName}" timed out after ${formatTimeout(timeoutMs)}.`,
|
|
26
|
-
);
|
|
27
|
-
this.name = "ToolCallTimeoutError";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function runWithToolCallTimeout<T>(
|
|
32
|
-
toolName: string,
|
|
33
|
-
timeoutMs: number,
|
|
34
|
-
signal: AbortSignal | undefined,
|
|
35
|
-
execute: (signal: AbortSignal) => Promise<T>,
|
|
36
|
-
) {
|
|
37
|
-
const timeoutController = new AbortController();
|
|
38
|
-
const executionSignal = signal
|
|
39
|
-
? AbortSignal.any([signal, timeoutController.signal])
|
|
40
|
-
: timeoutController.signal;
|
|
41
|
-
const timeoutError = new ToolCallTimeoutError(toolName, timeoutMs);
|
|
42
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
43
|
-
const timeout = new Promise<never>((_resolve, reject) => {
|
|
44
|
-
timer = setTimeout(() => {
|
|
45
|
-
reject(timeoutError);
|
|
46
|
-
timeoutController.abort(timeoutError);
|
|
47
|
-
}, timeoutMs);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
let removeAbortListener: (() => void) | undefined;
|
|
51
|
-
const aborted = new Promise<never>((_resolve, reject) => {
|
|
52
|
-
if (!signal) return;
|
|
53
|
-
const onAbort = () => {
|
|
54
|
-
reject(
|
|
55
|
-
signal.reason instanceof Error
|
|
56
|
-
? signal.reason
|
|
57
|
-
: new Error(`Tool call "${toolName}" was aborted.`),
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
if (signal.aborted) {
|
|
61
|
-
onAbort();
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
65
|
-
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
return await Promise.race([execute(executionSignal), timeout, aborted]);
|
|
70
|
-
} finally {
|
|
71
|
-
if (timer) clearTimeout(timer);
|
|
72
|
-
removeAbortListener?.();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Wrap every currently registered child tool with an independent execution
|
|
78
|
-
* timeout. Calling apply() again is safe and picks up tools registered later.
|
|
79
|
-
*/
|
|
80
|
-
export function createToolCallTimeoutGuard(
|
|
81
|
-
timeoutMs = CHILD_TOOL_CALL_TIMEOUT_MS,
|
|
82
|
-
) {
|
|
83
|
-
const wrapped = new WeakSet<ToolDefinition>();
|
|
84
|
-
|
|
85
|
-
const wrap = (definition: ToolDefinition) => {
|
|
86
|
-
if (wrapped.has(definition)) return;
|
|
87
|
-
wrapped.add(definition);
|
|
88
|
-
|
|
89
|
-
const execute = definition.execute;
|
|
90
|
-
definition.execute = async (toolCallId, params, signal, onUpdate, ctx) =>
|
|
91
|
-
runWithToolCallTimeout(definition.name, timeoutMs, signal, (signal) =>
|
|
92
|
-
execute.call(definition, toolCallId, params, signal, onUpdate, ctx),
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
apply(session: ToolRegistry) {
|
|
98
|
-
for (const { name } of session.getAllTools()) {
|
|
99
|
-
const definition = session.getToolDefinition(name);
|
|
100
|
-
if (definition) wrap(definition);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
}
|
|
1
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export const CHILD_TOOL_CALL_TIMEOUT_MS = 3 * 60 * 1_000;
|
|
4
|
+
|
|
5
|
+
interface ToolRegistry {
|
|
6
|
+
getAllTools(): Array<{ name: string }>;
|
|
7
|
+
getToolDefinition(name: string): ToolDefinition | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatTimeout(timeoutMs: number) {
|
|
11
|
+
if (timeoutMs % 60_000 === 0) {
|
|
12
|
+
const minutes = timeoutMs / 60_000;
|
|
13
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
14
|
+
}
|
|
15
|
+
if (timeoutMs % 1_000 === 0) {
|
|
16
|
+
const seconds = timeoutMs / 1_000;
|
|
17
|
+
return `${seconds} second${seconds === 1 ? "" : "s"}`;
|
|
18
|
+
}
|
|
19
|
+
return `${timeoutMs} ms`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ToolCallTimeoutError extends Error {
|
|
23
|
+
constructor(toolName: string, timeoutMs: number) {
|
|
24
|
+
super(
|
|
25
|
+
`Tool call "${toolName}" timed out after ${formatTimeout(timeoutMs)}.`,
|
|
26
|
+
);
|
|
27
|
+
this.name = "ToolCallTimeoutError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runWithToolCallTimeout<T>(
|
|
32
|
+
toolName: string,
|
|
33
|
+
timeoutMs: number,
|
|
34
|
+
signal: AbortSignal | undefined,
|
|
35
|
+
execute: (signal: AbortSignal) => Promise<T>,
|
|
36
|
+
) {
|
|
37
|
+
const timeoutController = new AbortController();
|
|
38
|
+
const executionSignal = signal
|
|
39
|
+
? AbortSignal.any([signal, timeoutController.signal])
|
|
40
|
+
: timeoutController.signal;
|
|
41
|
+
const timeoutError = new ToolCallTimeoutError(toolName, timeoutMs);
|
|
42
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
43
|
+
const timeout = new Promise<never>((_resolve, reject) => {
|
|
44
|
+
timer = setTimeout(() => {
|
|
45
|
+
reject(timeoutError);
|
|
46
|
+
timeoutController.abort(timeoutError);
|
|
47
|
+
}, timeoutMs);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let removeAbortListener: (() => void) | undefined;
|
|
51
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
52
|
+
if (!signal) return;
|
|
53
|
+
const onAbort = () => {
|
|
54
|
+
reject(
|
|
55
|
+
signal.reason instanceof Error
|
|
56
|
+
? signal.reason
|
|
57
|
+
: new Error(`Tool call "${toolName}" was aborted.`),
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
if (signal.aborted) {
|
|
61
|
+
onAbort();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
65
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return await Promise.race([execute(executionSignal), timeout, aborted]);
|
|
70
|
+
} finally {
|
|
71
|
+
if (timer) clearTimeout(timer);
|
|
72
|
+
removeAbortListener?.();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Wrap every currently registered child tool with an independent execution
|
|
78
|
+
* timeout. Calling apply() again is safe and picks up tools registered later.
|
|
79
|
+
*/
|
|
80
|
+
export function createToolCallTimeoutGuard(
|
|
81
|
+
timeoutMs = CHILD_TOOL_CALL_TIMEOUT_MS,
|
|
82
|
+
) {
|
|
83
|
+
const wrapped = new WeakSet<ToolDefinition>();
|
|
84
|
+
|
|
85
|
+
const wrap = (definition: ToolDefinition) => {
|
|
86
|
+
if (wrapped.has(definition)) return;
|
|
87
|
+
wrapped.add(definition);
|
|
88
|
+
|
|
89
|
+
const execute = definition.execute;
|
|
90
|
+
definition.execute = async (toolCallId, params, signal, onUpdate, ctx) =>
|
|
91
|
+
runWithToolCallTimeout(definition.name, timeoutMs, signal, (signal) =>
|
|
92
|
+
execute.call(definition, toolCallId, params, signal, onUpdate, ctx),
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
apply(session: ToolRegistry) {
|
|
98
|
+
for (const { name } of session.getAllTools()) {
|
|
99
|
+
const definition = session.getToolDefinition(name);
|
|
100
|
+
if (definition) wrap(definition);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|