@tyvm/knowhow 0.0.153 → 0.0.155
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 +1 -1
- package/src/agents/base/base.ts +72 -28
- package/src/agents/base/prompt.ts +3 -2
- package/src/agents/tools/list.ts +31 -28
- package/src/auth/keyAuth.ts +35 -3
- package/src/chat/modules/AgentModule.ts +98 -42
- package/src/clients/anthropic.ts +3 -1
- package/src/clients/contextLimits.ts +3 -1
- package/src/clients/gemini.ts +16 -4
- package/src/clients/http.ts +8 -5
- package/src/clients/index.ts +7 -4
- package/src/clients/knowhowMcp.ts +2 -2
- package/src/clients/openai.ts +44 -8
- package/src/clients/pricing/google.ts +11 -2
- package/src/clients/pricing/xai.ts +5 -1
- package/src/clients/xai.ts +6 -3
- package/src/services/KnowhowClient.ts +6 -5
- package/src/services/watchers/FsSyncer.ts +40 -2
- package/src/services/watchers/RemoteSyncer.ts +40 -2
- package/src/tunnel.ts +18 -12
- package/src/worker.ts +62 -12
- package/tests/clients/reasoningEffort.test.ts +62 -1
- package/tests/unit/auth/keyAuth.test.ts +31 -2
- package/tests/unit/services/KnowhowClient.test.ts +84 -0
- package/tests/unit/services/SyncedAgentWatcher.test.ts +89 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +2 -1
- package/ts_build/src/agents/base/base.js +39 -26
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/base/prompt.d.ts +1 -1
- package/ts_build/src/agents/base/prompt.js +3 -2
- package/ts_build/src/agents/base/prompt.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +0 -26
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/auth/keyAuth.d.ts +1 -0
- package/ts_build/src/auth/keyAuth.js +19 -2
- package/ts_build/src/auth/keyAuth.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +3 -0
- package/ts_build/src/chat/modules/AgentModule.js +74 -34
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +3 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +2 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/gemini.js +14 -3
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.js +8 -5
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.js +7 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/knowhowMcp.js +2 -2
- package/ts_build/src/clients/knowhowMcp.js.map +1 -1
- package/ts_build/src/clients/openai.js +36 -8
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/google.d.ts +1 -0
- package/ts_build/src/clients/pricing/google.js +7 -0
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +1 -0
- package/ts_build/src/clients/pricing/xai.js +4 -0
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/xai.js +6 -3
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.js +5 -3
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/watchers/FsSyncer.d.ts +4 -0
- package/ts_build/src/services/watchers/FsSyncer.js +36 -2
- package/ts_build/src/services/watchers/FsSyncer.js.map +1 -1
- package/ts_build/src/services/watchers/RemoteSyncer.d.ts +4 -0
- package/ts_build/src/services/watchers/RemoteSyncer.js +36 -2
- package/ts_build/src/services/watchers/RemoteSyncer.js.map +1 -1
- package/ts_build/src/tunnel.js +11 -8
- package/ts_build/src/tunnel.js.map +1 -1
- package/ts_build/src/types.d.ts +2 -0
- package/ts_build/src/worker.js +47 -10
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/clients/reasoningEffort.test.js +49 -1
- package/ts_build/tests/clients/reasoningEffort.test.js.map +1 -1
- package/ts_build/tests/unit/auth/keyAuth.test.js +22 -2
- package/ts_build/tests/unit/auth/keyAuth.test.js.map +1 -1
- package/ts_build/tests/unit/services/KnowhowClient.test.d.ts +1 -0
- package/ts_build/tests/unit/services/KnowhowClient.test.js +69 -0
- package/ts_build/tests/unit/services/KnowhowClient.test.js.map +1 -0
- package/ts_build/tests/unit/services/SyncedAgentWatcher.test.d.ts +1 -0
- package/ts_build/tests/unit/services/SyncedAgentWatcher.test.js +78 -0
- package/ts_build/tests/unit/services/SyncedAgentWatcher.test.js.map +1 -0
package/package.json
CHANGED
package/src/agents/base/base.ts
CHANGED
|
@@ -1107,6 +1107,14 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1107
1107
|
|
|
1108
1108
|
// Typically, there's only one choice in the array, but you could have many
|
|
1109
1109
|
// If you set `n` to more than 1, you will get multiple choices
|
|
1110
|
+
// Collect all tool calls across all choices up front.
|
|
1111
|
+
// This is used by detectTruncatedToolCalls so that the size heuristic
|
|
1112
|
+
// compares the full completion_tokens against ALL argument content,
|
|
1113
|
+
// rather than just the current choice's single tool call (Anthropic maps
|
|
1114
|
+
// each parallel tool_use block to a separate choice).
|
|
1115
|
+
const allResponseToolCalls = response.choices.flatMap(
|
|
1116
|
+
(c) => c.message.tool_calls ?? []
|
|
1117
|
+
);
|
|
1110
1118
|
for (const choice of response.choices) {
|
|
1111
1119
|
messages.push(choice.message);
|
|
1112
1120
|
|
|
@@ -1136,7 +1144,8 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1136
1144
|
|
|
1137
1145
|
const truncationWarning = this.detectTruncatedToolCalls(
|
|
1138
1146
|
toolCalls,
|
|
1139
|
-
response
|
|
1147
|
+
response,
|
|
1148
|
+
allResponseToolCalls
|
|
1140
1149
|
);
|
|
1141
1150
|
if (truncationWarning) {
|
|
1142
1151
|
messages.push(truncationWarning as Message);
|
|
@@ -1589,11 +1598,14 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1589
1598
|
* 2. The model reported many output tokens but the total argument content received is tiny
|
|
1590
1599
|
* relative to what those tokens should represent (soft/silent truncation).
|
|
1591
1600
|
*
|
|
1601
|
+
* @param toolCalls - The tool calls from the CURRENT choice being checked.
|
|
1602
|
+
* @param allToolCalls - ALL tool calls across ALL choices in the response (used for the size heuristic).
|
|
1592
1603
|
* Returns a warning system message if truncation is detected, or null otherwise.
|
|
1593
1604
|
*/
|
|
1594
1605
|
detectTruncatedToolCalls(
|
|
1595
1606
|
toolCalls: ToolCall[],
|
|
1596
|
-
response: CompletionResponse
|
|
1607
|
+
response: CompletionResponse,
|
|
1608
|
+
allToolCalls?: ToolCall[]
|
|
1597
1609
|
): { role: string; content: string } | null {
|
|
1598
1610
|
// Subtract thinking/reasoning tokens — they're billed as output tokens but
|
|
1599
1611
|
// don't produce visible argument content, so including them inflates the heuristic.
|
|
@@ -1602,7 +1614,13 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1602
1614
|
(response?.usage?.output_tokens_details?.thinking_tokens ?? 0) +
|
|
1603
1615
|
(response?.usage?.output_tokens_details?.reasoning_tokens ?? 0);
|
|
1604
1616
|
const outputTokens = Math.max(0, rawOutputTokens - thinkingTokens);
|
|
1605
|
-
|
|
1617
|
+
// Use allToolCalls for the size heuristic so that parallel tool calls from
|
|
1618
|
+
// Anthropic (each mapped to a separate choice) are counted together. Without
|
|
1619
|
+
// this, a response with N parallel tool calls would compare the full
|
|
1620
|
+
// completion_tokens count against only 1/N of the actual argument content,
|
|
1621
|
+
// causing false-positive truncation warnings.
|
|
1622
|
+
const callsForSizeCheck = allToolCalls ?? toolCalls;
|
|
1623
|
+
const totalArgLength = callsForSizeCheck.reduce(
|
|
1606
1624
|
(sum, tc) => sum + (tc.function?.arguments?.length || 0),
|
|
1607
1625
|
0
|
|
1608
1626
|
);
|
|
@@ -1642,6 +1660,20 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1642
1660
|
return null;
|
|
1643
1661
|
}
|
|
1644
1662
|
|
|
1663
|
+
extractContentFromMessages(messages: Message[]): string {
|
|
1664
|
+
const content = messages
|
|
1665
|
+
.filter((m) => typeof m.content === "string" && m.content.trim())
|
|
1666
|
+
.map((m) => m.content);
|
|
1667
|
+
|
|
1668
|
+
const finalAnswer = messages
|
|
1669
|
+
.filter((m) => m.tool_calls?.length)
|
|
1670
|
+
.flatMap((t) => t.tool_calls)
|
|
1671
|
+
.filter((t) => t.function.name === "finalAnswer")
|
|
1672
|
+
.map((t) => t.function.arguments);
|
|
1673
|
+
|
|
1674
|
+
return [...content, ...finalAnswer].join("\n\n");
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1645
1677
|
async getTaskBreakdown(messages: Message[]) {
|
|
1646
1678
|
if (this.taskBreakdown) {
|
|
1647
1679
|
return this.taskBreakdown;
|
|
@@ -1668,29 +1700,26 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1668
1700
|
] as Message[];
|
|
1669
1701
|
|
|
1670
1702
|
const response = await this.createAgentCompletion({
|
|
1671
|
-
model,
|
|
1672
1703
|
messages: taskBreakdownMessages,
|
|
1673
1704
|
tool_choice: "none",
|
|
1674
|
-
long_ttl_cache: this.runTime() > 300_000,
|
|
1675
|
-
...(this.reasoningEffort !== undefined && {
|
|
1676
|
-
reasoning_effort: this.reasoningEffort,
|
|
1677
|
-
}),
|
|
1678
|
-
...(this.summarizeReasoning !== undefined && {
|
|
1679
|
-
reasoning_summary: this.summarizeReasoning,
|
|
1680
|
-
}),
|
|
1681
1705
|
});
|
|
1682
1706
|
|
|
1707
|
+
const breakdownContent = this.extractContentFromMessages(
|
|
1708
|
+
response.choices?.map((c) => c.message)
|
|
1709
|
+
);
|
|
1683
1710
|
// The model may return null content (e.g. when it responds with tool calls
|
|
1684
1711
|
// instead of text). Guard against storing a literal null so that template
|
|
1685
1712
|
// strings don't render "null".
|
|
1686
|
-
|
|
1687
|
-
.
|
|
1688
|
-
|
|
1689
|
-
|
|
1713
|
+
if (!breakdownContent) {
|
|
1714
|
+
console.log(JSON.stringify(response.choices, null, 2));
|
|
1715
|
+
throw new Error(
|
|
1716
|
+
"Compaction task-breakdown request returned no text content"
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1690
1719
|
|
|
1691
|
-
this.taskBreakdown = breakdownContent
|
|
1720
|
+
this.taskBreakdown = breakdownContent;
|
|
1692
1721
|
this.log(`task breakdown cost: ${response.usd_cost}`);
|
|
1693
|
-
return this.taskBreakdown
|
|
1722
|
+
return this.taskBreakdown;
|
|
1694
1723
|
}
|
|
1695
1724
|
|
|
1696
1725
|
async compressMessages(messages: Message[]) {
|
|
@@ -1698,10 +1727,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1698
1727
|
// at the final assistant message keeps its tool calls, every tool response,
|
|
1699
1728
|
// and any subsequent user messages together as one protocol-valid unit.
|
|
1700
1729
|
let resumeIndex = messages.length - 1;
|
|
1701
|
-
while (
|
|
1702
|
-
resumeIndex >= 0 &&
|
|
1703
|
-
messages[resumeIndex].role !== "assistant"
|
|
1704
|
-
) {
|
|
1730
|
+
while (resumeIndex >= 0 && messages[resumeIndex].role !== "assistant") {
|
|
1705
1731
|
resumeIndex--;
|
|
1706
1732
|
}
|
|
1707
1733
|
const compressionEnd = resumeIndex === -1 ? messages.length : resumeIndex;
|
|
@@ -1720,7 +1746,11 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1720
1746
|
3. Next Steps - what we're about to do next to continue the user's original request.
|
|
1721
1747
|
4. Tasks remaining - what tasks are left from the initial task breakdown.
|
|
1722
1748
|
|
|
1723
|
-
${
|
|
1749
|
+
${
|
|
1750
|
+
this.taskBreakdown
|
|
1751
|
+
? `Our initial task breakdown: ${this.taskBreakdown}`
|
|
1752
|
+
: ""
|
|
1753
|
+
}
|
|
1724
1754
|
|
|
1725
1755
|
This summary will replace the older history. The latest agent interaction will remain verbatim:
|
|
1726
1756
|
|
|
@@ -1739,19 +1769,33 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1739
1769
|
tool_choice: "none",
|
|
1740
1770
|
});
|
|
1741
1771
|
|
|
1742
|
-
const
|
|
1743
|
-
.map((c) => c.message
|
|
1744
|
-
|
|
1745
|
-
|
|
1772
|
+
const summary = this.extractContentFromMessages(
|
|
1773
|
+
response.choices?.map((c) => c.message)
|
|
1774
|
+
);
|
|
1775
|
+
|
|
1776
|
+
if (summary.length === 0) {
|
|
1777
|
+
console.log(JSON.stringify(response.choices, null, 2));
|
|
1778
|
+
throw new Error("Compaction summary request returned no text content");
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
this.summaries.push(summary);
|
|
1746
1782
|
|
|
1747
1783
|
const startMessages = [
|
|
1748
1784
|
{
|
|
1749
1785
|
role: "user",
|
|
1750
1786
|
content: `
|
|
1751
|
-
${
|
|
1787
|
+
${
|
|
1788
|
+
this.taskBreakdown
|
|
1789
|
+
? `Initial task breakdown:\n ${this.taskBreakdown}`
|
|
1790
|
+
: "(No task breakdown available — summarize what you know from context above)"
|
|
1791
|
+
}
|
|
1752
1792
|
|
|
1753
1793
|
We have just compressed the conversation to save memory:
|
|
1754
|
-
${
|
|
1794
|
+
${
|
|
1795
|
+
summary.length > 0
|
|
1796
|
+
? JSON.stringify(summary)
|
|
1797
|
+
: "(summary unavailable — please continue from the task breakdown above)"
|
|
1798
|
+
}
|
|
1755
1799
|
|
|
1756
1800
|
Please continue the task from where we left off
|
|
1757
1801
|
`,
|
|
@@ -4,8 +4,9 @@ You use the tools to read and write code, to help the developer implement featur
|
|
|
4
4
|
|
|
5
5
|
# Completing your work
|
|
6
6
|
|
|
7
|
-
You
|
|
8
|
-
|
|
7
|
+
You may answer questions and provide requested information directly in your assistant message content. Content responses are valid and visible to the user; you do not need to use a tool merely to answer a question.
|
|
8
|
+
Call finalAnswer only once the overall task is complete and you are ready to end the agent run. Put your final result or completion summary in that tool call.
|
|
9
|
+
As an agent you may receive multiple rounds of input before you call finalAnswer.
|
|
9
10
|
You are not able to request feedback from the user, so proceed with your plans and the developer will contact you afterwards if they need more help.
|
|
10
11
|
|
|
11
12
|
## Modification Guidelines
|
package/src/agents/tools/list.ts
CHANGED
|
@@ -31,34 +31,37 @@ function getPluginNames(): string {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export const includedTools = [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
/*
|
|
35
|
+
* This doesn't use messageProcessors etc, needs rewrite
|
|
36
|
+
*{
|
|
37
|
+
* type: "function",
|
|
38
|
+
* function: {
|
|
39
|
+
* name: "agentCall",
|
|
40
|
+
* description:
|
|
41
|
+
* "Run another registered agent in-process as a blocking call and get its final answer back as a string. Unlike a plain llm() call, the target agent can use tools, loop, and maintain its own context. Useful as a graph node that delegates to a specialized agent (e.g. Researcher, Patcher). For long-running / parallel / detached work prefer startAgentTask.",
|
|
42
|
+
* parameters: {
|
|
43
|
+
* type: "object",
|
|
44
|
+
* positional: true,
|
|
45
|
+
* properties: {
|
|
46
|
+
* agentName: {
|
|
47
|
+
* type: "string",
|
|
48
|
+
* description:
|
|
49
|
+
* "The name of the agent to call (e.g. Researcher, Developer, Patcher).",
|
|
50
|
+
* },
|
|
51
|
+
* query: {
|
|
52
|
+
* type: "string",
|
|
53
|
+
* description: "The query / task to send to the agent.",
|
|
54
|
+
* },
|
|
55
|
+
* },
|
|
56
|
+
* required: ["agentName", "query"],
|
|
57
|
+
* },
|
|
58
|
+
* returns: {
|
|
59
|
+
* type: "string",
|
|
60
|
+
* description: "The agent's final answer text.",
|
|
61
|
+
* },
|
|
62
|
+
* },
|
|
63
|
+
*},
|
|
64
|
+
*/
|
|
62
65
|
{
|
|
63
66
|
type: "function",
|
|
64
67
|
function: {
|
package/src/auth/keyAuth.ts
CHANGED
|
@@ -3,7 +3,12 @@ import os from "os";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import http from "../utils/http";
|
|
5
5
|
import { getCliUserAgent } from "./browserLogin";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getDefaultPrivateKeyPath,
|
|
8
|
+
keyPairExists,
|
|
9
|
+
loadKeyPair,
|
|
10
|
+
signMessage,
|
|
11
|
+
} from "./keyManager";
|
|
7
12
|
|
|
8
13
|
const JWT_FILE_PATH = path.join(process.cwd(), ".knowhow", ".jwt");
|
|
9
14
|
const DEFAULT_API_URL = process.env.KNOWHOW_API_URL || "https://api.knowhow.tyvm.ai";
|
|
@@ -62,15 +67,42 @@ export async function exchangePublicKeyJwt(
|
|
|
62
67
|
return response.data.jwt;
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Try the configured identity first, then the default CLI identity. Project
|
|
72
|
+
* configs can outlive temporary/custom keys, while the globally registered
|
|
73
|
+
* default identity remains valid.
|
|
74
|
+
*/
|
|
75
|
+
export async function exchangeAvailablePublicKeyJwt(
|
|
76
|
+
orgId: string,
|
|
77
|
+
apiUrl: string = DEFAULT_API_URL,
|
|
78
|
+
preferredPrivateKeyPath?: string
|
|
79
|
+
): Promise<string> {
|
|
80
|
+
const defaultPath = getDefaultPrivateKeyPath();
|
|
81
|
+
const candidates = Array.from(
|
|
82
|
+
new Set([preferredPrivateKeyPath, defaultPath].filter((value): value is string => Boolean(value)))
|
|
83
|
+
).filter((candidate) => keyPairExists(candidate));
|
|
84
|
+
if (candidates.length === 0) throw new Error("CLI identity is unavailable");
|
|
85
|
+
|
|
86
|
+
let lastError: unknown;
|
|
87
|
+
for (const candidate of candidates) {
|
|
88
|
+
try {
|
|
89
|
+
return await exchangePublicKeyJwt(orgId, apiUrl, candidate);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
lastError = error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw lastError;
|
|
95
|
+
}
|
|
96
|
+
|
|
65
97
|
export async function authenticateWithKey(
|
|
66
98
|
orgId: string,
|
|
67
99
|
apiUrl: string = DEFAULT_API_URL,
|
|
68
100
|
privateKeyPath?: string
|
|
69
101
|
): Promise<boolean> {
|
|
70
|
-
if (!keyPairExists(privateKeyPath)) return false;
|
|
102
|
+
if (!keyPairExists(privateKeyPath) && !keyPairExists()) return false;
|
|
71
103
|
|
|
72
104
|
try {
|
|
73
|
-
storeJwt(await
|
|
105
|
+
storeJwt(await exchangeAvailablePublicKeyJwt(orgId, apiUrl, privateKeyPath));
|
|
74
106
|
return true;
|
|
75
107
|
} catch (error: unknown) {
|
|
76
108
|
// The identity is not registered for this environment/org (or was revoked).
|
|
@@ -67,6 +67,9 @@ export class AgentModule extends BaseChatModule {
|
|
|
67
67
|
/** Whether to request a reasoning summary from supporting models */
|
|
68
68
|
private summarizeReasoning: boolean = true;
|
|
69
69
|
|
|
70
|
+
/** Delayed finalAnswer output, cancelled when the task's done event arrives. */
|
|
71
|
+
private pendingFinalAnswers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
72
|
+
|
|
70
73
|
/** Stored wire params for rewireAgentRendering */
|
|
71
74
|
private _wireAgentEvents: EventService | undefined;
|
|
72
75
|
private _wireTaskId: string | undefined;
|
|
@@ -514,6 +517,28 @@ export class AgentModule extends BaseChatModule {
|
|
|
514
517
|
this.activeAgentTaskId = id;
|
|
515
518
|
}
|
|
516
519
|
|
|
520
|
+
private scheduleFinalAnswer(taskId: string, answer: unknown): void {
|
|
521
|
+
this.cancelFinalAnswer(taskId);
|
|
522
|
+
const renderedAnswer = typeof answer === "string"
|
|
523
|
+
? answer
|
|
524
|
+
: JSON.stringify(answer) ?? String(answer);
|
|
525
|
+
const timeout = setTimeout(() => {
|
|
526
|
+
this.pendingFinalAnswers.delete(taskId);
|
|
527
|
+
console.log(Marked.parse(renderedAnswer));
|
|
528
|
+
}, 50);
|
|
529
|
+
this.pendingFinalAnswers.set(taskId, timeout);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private cancelFinalAnswer(taskId: string): void {
|
|
533
|
+
const timeout = this.pendingFinalAnswers.get(taskId);
|
|
534
|
+
if (!timeout) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
clearTimeout(timeout);
|
|
539
|
+
this.pendingFinalAnswers.delete(taskId);
|
|
540
|
+
}
|
|
541
|
+
|
|
517
542
|
/**
|
|
518
543
|
* Wire up agentEvents to the renderer for a given task.
|
|
519
544
|
* Uses setListener with stable keys so re-calling automatically replaces old listeners.
|
|
@@ -549,16 +574,27 @@ export class AgentModule extends BaseChatModule {
|
|
|
549
574
|
if (eventTypes.toolUsed) {
|
|
550
575
|
agentEvents.setListener(
|
|
551
576
|
{ key: "agentModule:render:toolUsed", event: eventTypes.toolUsed },
|
|
552
|
-
(data: any) =>
|
|
577
|
+
(data: any) => {
|
|
553
578
|
this.renderer.render({
|
|
554
579
|
type: "toolResult",
|
|
555
580
|
taskId,
|
|
556
581
|
agentName,
|
|
557
582
|
toolCall: data.toolCall,
|
|
558
583
|
result: data.functionResp,
|
|
559
|
-
})
|
|
584
|
+
});
|
|
585
|
+
// Give the done event a chance to render the same answer first. If the
|
|
586
|
+
// agent continues, the tool result becomes the fallback output.
|
|
587
|
+
const toolName: string = data.toolCall?.function?.name ?? "";
|
|
588
|
+
if (toolName === "finalAnswer" || toolName.endsWith("finalAnswer")) {
|
|
589
|
+
this.scheduleFinalAnswer(taskId, data.functionResp);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
560
592
|
);
|
|
561
593
|
}
|
|
594
|
+
agentEvents.setListener(
|
|
595
|
+
{ key: "agentModule:render:done", event: eventTypes.done },
|
|
596
|
+
() => this.cancelFinalAnswer(taskId)
|
|
597
|
+
);
|
|
562
598
|
if (eventTypes.agentSay) {
|
|
563
599
|
agentEvents.setListener(
|
|
564
600
|
{ key: "agentModule:render:agentSay", event: eventTypes.agentSay },
|
|
@@ -589,6 +625,9 @@ export class AgentModule extends BaseChatModule {
|
|
|
589
625
|
* Clears the active task on the renderer.
|
|
590
626
|
*/
|
|
591
627
|
public unwireAgentRendering(): void {
|
|
628
|
+
if (this._wireTaskId) {
|
|
629
|
+
this.cancelFinalAnswer(this._wireTaskId);
|
|
630
|
+
}
|
|
592
631
|
if (this._wireAgentEvents) {
|
|
593
632
|
this._wireAgentEvents.removeManagedListenersByPrefix(
|
|
594
633
|
"agentModule:render:"
|
|
@@ -681,8 +720,14 @@ export class AgentModule extends BaseChatModule {
|
|
|
681
720
|
console.error(`Session ${sessionId} not found.`);
|
|
682
721
|
return;
|
|
683
722
|
}
|
|
723
|
+
// Session files from older runs may not contain model/provider settings.
|
|
724
|
+
// Prefer task metadata, which is updated from the live agent on every
|
|
725
|
+
// thread update and is therefore the authoritative resume configuration.
|
|
726
|
+
const savedSettings = await this.loadTaskSettings(
|
|
727
|
+
session.taskId || sessionId
|
|
728
|
+
);
|
|
684
729
|
console.log(`\n🔄 Resuming session: ${sessionId}`);
|
|
685
|
-
console.log(`Agent: ${session.agentName}`);
|
|
730
|
+
console.log(`Agent: ${savedSettings.agentName || session.agentName}`);
|
|
686
731
|
console.log(`Original task: ${session.initialInput}`);
|
|
687
732
|
console.log(`Status: ${session.status}`);
|
|
688
733
|
|
|
@@ -726,7 +771,8 @@ export class AgentModule extends BaseChatModule {
|
|
|
726
771
|
|
|
727
772
|
console.log("🚀 Session resuming...");
|
|
728
773
|
const context = this.chatService?.getContext();
|
|
729
|
-
const agentName =
|
|
774
|
+
const agentName =
|
|
775
|
+
savedSettings.agentName || session.agentName || context.currentAgent;
|
|
730
776
|
const previousAgentMode = context?.agentMode;
|
|
731
777
|
|
|
732
778
|
if (!agentName) {
|
|
@@ -738,7 +784,9 @@ export class AgentModule extends BaseChatModule {
|
|
|
738
784
|
// not reject config-defined agents here before it gets that opportunity.
|
|
739
785
|
|
|
740
786
|
// Start agent with Knowhow task context and restored message history
|
|
741
|
-
const remoteTaskId = asRemoteTaskId(
|
|
787
|
+
const remoteTaskId = asRemoteTaskId(
|
|
788
|
+
savedSettings.remoteTaskId || session.knowhowTaskId
|
|
789
|
+
);
|
|
742
790
|
const { agent, taskId } = await this.setupAgent({
|
|
743
791
|
agentName,
|
|
744
792
|
input: resumePrompt,
|
|
@@ -751,12 +799,12 @@ export class AgentModule extends BaseChatModule {
|
|
|
751
799
|
taskId: session.taskId || sessionId,
|
|
752
800
|
chatHistory: [],
|
|
753
801
|
run: false, // Don't run yet, we need to set up event listeners first
|
|
754
|
-
//
|
|
755
|
-
model: session.model,
|
|
756
|
-
provider: session.provider,
|
|
757
|
-
reasoningEffort: session.reasoningEffort,
|
|
758
|
-
summarizeReasoning: session.summarizeReasoning,
|
|
759
|
-
enabledTools: session.enabledTools,
|
|
802
|
+
// Prefer authoritative task metadata over potentially incomplete sessions.
|
|
803
|
+
model: savedSettings.model || session.model,
|
|
804
|
+
provider: savedSettings.provider || session.provider,
|
|
805
|
+
reasoningEffort: savedSettings.reasoningEffort ?? session.reasoningEffort,
|
|
806
|
+
summarizeReasoning: savedSettings.summarizeReasoning ?? session.summarizeReasoning,
|
|
807
|
+
enabledTools: savedSettings.enabledTools || session.enabledTools,
|
|
760
808
|
});
|
|
761
809
|
|
|
762
810
|
// Match new interactive threads: local-only sessions get a fresh remote
|
|
@@ -871,6 +919,34 @@ export class AgentModule extends BaseChatModule {
|
|
|
871
919
|
agent.restoreEnabledTools(options.enabledTools);
|
|
872
920
|
}
|
|
873
921
|
|
|
922
|
+
// Restore persisted configuration before registering or saving this task.
|
|
923
|
+
// Otherwise the newly constructed agent's defaults can overwrite the
|
|
924
|
+
// original run settings during resume setup.
|
|
925
|
+
if (options.model) {
|
|
926
|
+
console.log("Setting model:", options.model);
|
|
927
|
+
agent.setModel(options.model);
|
|
928
|
+
agent.setModelPreferences([
|
|
929
|
+
{ model: options.model, provider: options.provider as any },
|
|
930
|
+
]);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (options.provider) {
|
|
934
|
+
agent.setProvider(options.provider as any);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const effectiveReasoningEffort =
|
|
938
|
+
options.reasoningEffort !== undefined
|
|
939
|
+
? options.reasoningEffort
|
|
940
|
+
: this.reasoningEffort;
|
|
941
|
+
if (effectiveReasoningEffort !== undefined) {
|
|
942
|
+
agent.setReasoningEffort(effectiveReasoningEffort);
|
|
943
|
+
}
|
|
944
|
+
agent.setSummarizeReasoning(
|
|
945
|
+
options.summarizeReasoning !== undefined
|
|
946
|
+
? options.summarizeReasoning
|
|
947
|
+
: this.summarizeReasoning
|
|
948
|
+
);
|
|
949
|
+
|
|
874
950
|
let done = false;
|
|
875
951
|
let output = "Done";
|
|
876
952
|
const taskId = options.taskId || this.sessionManager.generateTaskId(input);
|
|
@@ -907,6 +983,10 @@ export class AgentModule extends BaseChatModule {
|
|
|
907
983
|
startTime: Date.now(),
|
|
908
984
|
totalCost: 0,
|
|
909
985
|
enabledTools: agent.getEnabledToolNames(),
|
|
986
|
+
model: agent.getModel(),
|
|
987
|
+
provider: agent.getProvider(),
|
|
988
|
+
reasoningEffort: agent.getReasoningEffort?.(),
|
|
989
|
+
summarizeReasoning: agent.getSummarizeReasoning?.(),
|
|
910
990
|
};
|
|
911
991
|
|
|
912
992
|
// Add to task registry
|
|
@@ -967,37 +1047,6 @@ export class AgentModule extends BaseChatModule {
|
|
|
967
1047
|
agent.setParentTaskId(options.parentTaskId);
|
|
968
1048
|
}
|
|
969
1049
|
|
|
970
|
-
if (options.model) {
|
|
971
|
-
console.log("Setting model:", options.model);
|
|
972
|
-
agent.setModel(options.model);
|
|
973
|
-
agent.setModelPreferences([
|
|
974
|
-
{ model: options.model, provider: options.provider as any },
|
|
975
|
-
]);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// Restore the provider explicitly (e.g. on resume) so the agent talks to
|
|
979
|
-
// the same client the original run used, not the model's default provider.
|
|
980
|
-
if (options.provider) {
|
|
981
|
-
agent.setProvider(options.provider as any);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Apply reasoning settings to the agent. Explicit per-task settings
|
|
985
|
-
// (e.g. restored from a resumed task's metadata) take precedence over
|
|
986
|
-
// the module-level CLI defaults so a /resume keeps the same reasoning
|
|
987
|
-
// effort the original run used.
|
|
988
|
-
const effectiveReasoningEffort =
|
|
989
|
-
options.reasoningEffort !== undefined
|
|
990
|
-
? options.reasoningEffort
|
|
991
|
-
: this.reasoningEffort;
|
|
992
|
-
if (effectiveReasoningEffort !== undefined) {
|
|
993
|
-
agent.setReasoningEffort(effectiveReasoningEffort);
|
|
994
|
-
}
|
|
995
|
-
agent.setSummarizeReasoning(
|
|
996
|
-
options.summarizeReasoning !== undefined
|
|
997
|
-
? options.summarizeReasoning
|
|
998
|
-
: this.summarizeReasoning
|
|
999
|
-
);
|
|
1000
|
-
|
|
1001
1050
|
// Set up message processors like in original startAgent
|
|
1002
1051
|
|
|
1003
1052
|
// Register an override for askHuman to use CLI input method
|
|
@@ -1097,6 +1146,12 @@ export class AgentModule extends BaseChatModule {
|
|
|
1097
1146
|
toolCall: data.toolCall,
|
|
1098
1147
|
result: data.functionResp,
|
|
1099
1148
|
});
|
|
1149
|
+
// Give the done event a chance to render the same answer first. If the
|
|
1150
|
+
// agent continues, the tool result becomes the fallback output.
|
|
1151
|
+
const toolName: string = data.toolCall?.function?.name ?? "";
|
|
1152
|
+
if (toolName === "finalAnswer" || toolName.endsWith("finalAnswer")) {
|
|
1153
|
+
this.scheduleFinalAnswer(taskId, data.functionResp);
|
|
1154
|
+
}
|
|
1100
1155
|
};
|
|
1101
1156
|
const agentSayHandler = (data: any) => {
|
|
1102
1157
|
this.renderer.render({
|
|
@@ -1130,6 +1185,7 @@ export class AgentModule extends BaseChatModule {
|
|
|
1130
1185
|
const taskCompleted = new Promise<string>((resolve) => {
|
|
1131
1186
|
agent.agentEvents.once(agent.eventTypes.done, async (doneMsg) => {
|
|
1132
1187
|
console.log("🎯 [AgentModule] Task Completed");
|
|
1188
|
+
this.cancelFinalAnswer(taskId);
|
|
1133
1189
|
done = true;
|
|
1134
1190
|
output = doneMsg || "No response from the AI";
|
|
1135
1191
|
// Remove threadUpdate listener to prevent cost sharing across tasks
|
package/src/clients/anthropic.ts
CHANGED
|
@@ -472,7 +472,9 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
472
472
|
...(thinkingParam && { thinking: thinkingParam }),
|
|
473
473
|
...(effortOutputConfig && { output_config: effortOutputConfig }),
|
|
474
474
|
...(tools.length && {
|
|
475
|
-
tool_choice: {
|
|
475
|
+
tool_choice: {
|
|
476
|
+
type: options.tool_choice ?? "auto",
|
|
477
|
+
},
|
|
476
478
|
tools,
|
|
477
479
|
}),
|
|
478
480
|
}, { signal: options.signal });
|
|
@@ -123,7 +123,8 @@ export const ContextLimits: Record<string, number> = {
|
|
|
123
123
|
[Models.anthropic.Sonnet4_0]: 200_000,
|
|
124
124
|
|
|
125
125
|
// ─── Google ───────────────────────────────────────────────────────────────
|
|
126
|
-
// Gemini 3.6 / 3.5 (text)
|
|
126
|
+
// Gemini 3.7 / 3.6 / 3.5 (text)
|
|
127
|
+
[Models.google.Gemini_37_Flash]: 1_048_576,
|
|
127
128
|
[Models.google.Gemini_36_Flash]: 1_000_000,
|
|
128
129
|
[Models.google.Gemini_35_Flash]: 1_000_000,
|
|
129
130
|
[Models.google.Gemini_35_Flash_Lite]: 1_000_000,
|
|
@@ -178,6 +179,7 @@ export const ContextLimits: Record<string, number> = {
|
|
|
178
179
|
[Models.google.Gemini_15_Pro]: 2_000_000,
|
|
179
180
|
|
|
180
181
|
// ─── xAI ──────────────────────────────────────────────────────────────────
|
|
182
|
+
[Models.xai.Grok4_6]: 500_000,
|
|
181
183
|
[Models.xai.Grok4_1_Fast_Reasoning]: 2_000_000,
|
|
182
184
|
[Models.xai.Grok4_1_Fast_NonReasoning]: 2_000_000,
|
|
183
185
|
[Models.xai.Grok_4_20_Reasoning]: 131_072,
|
package/src/clients/gemini.ts
CHANGED
|
@@ -420,7 +420,7 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
420
420
|
|
|
421
421
|
/**
|
|
422
422
|
* Builds the thinkingConfig for Gemini models that support it.
|
|
423
|
-
* - Gemini 3.x models use thinkingLevel
|
|
423
|
+
* - Gemini 3.x models use thinkingLevel (supported levels vary by model)
|
|
424
424
|
* - Gemini 2.5 models use thinkingBudget: number (0 = off, -1 = dynamic)
|
|
425
425
|
*
|
|
426
426
|
* Maps CompletionOptions.reasoning_effort to provider-specific values.
|
|
@@ -433,9 +433,11 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
433
433
|
|
|
434
434
|
// Gemini 3.x — use thinkingLevel
|
|
435
435
|
if (GoogleThinkingLevelModels.includes(model)) {
|
|
436
|
+
// Gemini 3.7 Flash rejects "minimal"; low is its least intensive level.
|
|
437
|
+
const minimumLevel = model === Models.google.Gemini_37_Flash ? "low" : "minimal";
|
|
436
438
|
const levelMap: Record<string, string> = {
|
|
437
|
-
none:
|
|
438
|
-
minimal:
|
|
439
|
+
none: minimumLevel,
|
|
440
|
+
minimal: minimumLevel,
|
|
439
441
|
low: "low",
|
|
440
442
|
medium: "medium",
|
|
441
443
|
high: "high",
|
|
@@ -474,6 +476,7 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
474
476
|
const { systemInstruction, contents } = this.transformMessages(
|
|
475
477
|
options.messages
|
|
476
478
|
);
|
|
479
|
+
const tools = this.transformTools(options.tools);
|
|
477
480
|
|
|
478
481
|
try {
|
|
479
482
|
await wait(2000);
|
|
@@ -483,7 +486,16 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
483
486
|
config: {
|
|
484
487
|
systemInstruction,
|
|
485
488
|
thinkingConfig,
|
|
486
|
-
tools
|
|
489
|
+
tools,
|
|
490
|
+
...(tools.length && {
|
|
491
|
+
toolConfig: {
|
|
492
|
+
functionCallingConfig: {
|
|
493
|
+
mode: options.tool_choice === "none"
|
|
494
|
+
? FunctionCallingConfigMode.NONE
|
|
495
|
+
: FunctionCallingConfigMode.AUTO,
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
}),
|
|
487
499
|
maxOutputTokens: options.max_tokens,
|
|
488
500
|
},
|
|
489
501
|
});
|