@tyvm/knowhow 0.0.156 → 0.0.158
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/package.json +1 -1
- package/src/agents/base/base.ts +176 -39
- package/src/auth/browserLogin.ts +2 -13
- package/src/auth/environmentAuth.ts +51 -0
- package/src/auth/jwtStore.ts +70 -0
- package/src/auth/keyAuth.ts +4 -15
- package/src/auth/registerIdentity.ts +8 -8
- package/src/chat/modules/AgentModule.ts +3 -3
- package/src/chat/modules/RemoteSyncModule.ts +45 -17
- package/src/cli.ts +2 -0
- package/src/clients/anthropic.ts +156 -79
- package/src/clients/attribution.ts +53 -0
- package/src/clients/http.ts +211 -84
- package/src/clients/index.ts +45 -13
- package/src/clients/openai.ts +441 -220
- package/src/clients/openrouter.ts +10 -3
- package/src/clients/pricing/fireworks.ts +32 -2
- package/src/clients/pricing/openai.ts +15 -1
- package/src/clients/pricing/types.ts +15 -0
- package/src/clients/responseMetadata.ts +221 -0
- package/src/clients/types.ts +49 -4
- package/src/commands/mcp.ts +1 -0
- package/src/commands/misc.ts +1 -1
- package/src/commands/remote.ts +77 -0
- package/src/config.ts +9 -1
- package/src/login.ts +54 -42
- package/src/remotes.ts +110 -0
- package/src/services/KnowhowClient.ts +12 -16
- package/src/services/Mcp.ts +94 -7
- package/src/services/McpWebsocketTransport.ts +14 -11
- package/src/types.ts +12 -0
- package/tests/unit/McpWebsocketTransport.test.ts +56 -0
- package/tests/unit/auth/jwtStore.test.ts +55 -0
- package/tests/unit/auth/registerIdentity.test.ts +8 -2
- package/tests/unit/clients/anthropic.responseMetadata.test.ts +81 -0
- package/tests/unit/clients/attribution.test.ts +185 -0
- package/tests/unit/clients/http.responseMetadata.test.ts +86 -0
- package/tests/unit/clients/openai.responseMetadata.test.ts +90 -0
- package/tests/unit/clients/responseMetadata.test.ts +123 -0
- package/tests/unit/commands/remote.test.ts +79 -0
- package/tests/unit/login.test.ts +64 -3
- package/tests/unit/services/KnowhowClient.test.ts +2 -2
- package/tests/unit/services/Mcp.test.ts +2 -2
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +8 -3
- package/ts_build/src/agents/base/base.js +112 -26
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/auth/browserLogin.js +2 -7
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/auth/environmentAuth.d.ts +5 -0
- package/ts_build/src/auth/environmentAuth.js +48 -0
- package/ts_build/src/auth/environmentAuth.js.map +1 -0
- package/ts_build/src/auth/jwtStore.d.ts +4 -0
- package/ts_build/src/auth/jwtStore.js +65 -0
- package/ts_build/src/auth/jwtStore.js.map +1 -0
- package/ts_build/src/auth/keyAuth.d.ts +1 -1
- package/ts_build/src/auth/keyAuth.js +4 -16
- package/ts_build/src/auth/keyAuth.js.map +1 -1
- package/ts_build/src/auth/registerIdentity.js +6 -7
- package/ts_build/src/auth/registerIdentity.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +3 -3
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +1 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -7
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
- package/ts_build/src/cli.js +2 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +1 -0
- package/ts_build/src/clients/anthropic.js +83 -38
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/attribution.d.ts +2 -0
- package/ts_build/src/clients/attribution.js +32 -0
- package/ts_build/src/clients/attribution.js.map +1 -0
- package/ts_build/src/clients/http.js +100 -20
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +2 -1
- package/ts_build/src/clients/index.js +24 -6
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.d.ts +2 -1
- package/ts_build/src/clients/openai.js +225 -99
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/openrouter.d.ts +2 -2
- package/ts_build/src/clients/openrouter.js +6 -2
- package/ts_build/src/clients/openrouter.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +4 -0
- package/ts_build/src/clients/pricing/fireworks.js +14 -2
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +15 -1
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/types.d.ts +6 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -1
- package/ts_build/src/clients/responseMetadata.d.ts +15 -0
- package/ts_build/src/clients/responseMetadata.js +141 -0
- package/ts_build/src/clients/responseMetadata.js.map +1 -0
- package/ts_build/src/clients/types.d.ts +18 -1
- package/ts_build/src/clients/types.js +2 -1
- package/ts_build/src/clients/types.js.map +1 -1
- package/ts_build/src/commands/mcp.js.map +1 -1
- package/ts_build/src/commands/misc.js +1 -1
- package/ts_build/src/commands/misc.js.map +1 -1
- package/ts_build/src/commands/remote.d.ts +2 -0
- package/ts_build/src/commands/remote.js +70 -0
- package/ts_build/src/commands/remote.js.map +1 -0
- package/ts_build/src/config.js +8 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/login.d.ts +4 -3
- package/ts_build/src/login.js +34 -35
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/remotes.d.ts +14 -0
- package/ts_build/src/remotes.js +117 -0
- package/ts_build/src/remotes.js.map +1 -0
- package/ts_build/src/services/KnowhowClient.d.ts +1 -1
- package/ts_build/src/services/KnowhowClient.js +12 -14
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/Mcp.d.ts +1 -1
- package/ts_build/src/services/Mcp.js +68 -7
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/services/McpWebsocketTransport.d.ts +1 -0
- package/ts_build/src/services/McpWebsocketTransport.js +11 -6
- package/ts_build/src/services/McpWebsocketTransport.js.map +1 -1
- package/ts_build/src/types.d.ts +13 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/tests/unit/McpWebsocketTransport.test.d.ts +1 -0
- package/ts_build/tests/unit/McpWebsocketTransport.test.js +47 -0
- package/ts_build/tests/unit/McpWebsocketTransport.test.js.map +1 -0
- package/ts_build/tests/unit/auth/jwtStore.test.d.ts +1 -0
- package/ts_build/tests/unit/auth/jwtStore.test.js +46 -0
- package/ts_build/tests/unit/auth/jwtStore.test.js.map +1 -0
- package/ts_build/tests/unit/auth/registerIdentity.test.js +8 -2
- package/ts_build/tests/unit/auth/registerIdentity.test.js.map +1 -1
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js +77 -0
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/attribution.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/attribution.test.js +153 -0
- package/ts_build/tests/unit/clients/attribution.test.js.map +1 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.js +77 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.js +86 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.js +96 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/commands/remote.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/remote.test.js +70 -0
- package/ts_build/tests/unit/commands/remote.test.js.map +1 -0
- package/ts_build/tests/unit/login.test.js +44 -3
- package/ts_build/tests/unit/login.test.js.map +1 -1
- package/ts_build/tests/unit/services/KnowhowClient.test.js +2 -2
- package/ts_build/tests/unit/services/KnowhowClient.test.js.map +1 -1
- package/ts_build/tests/unit/services/Mcp.test.js +2 -2
- package/ts_build/tests/unit/services/Mcp.test.js.map +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
const getConfig = jest.fn();
|
|
4
|
+
const updateConfig = jest.fn();
|
|
5
|
+
|
|
6
|
+
jest.mock("../../../src/config", () => ({ getConfig, updateConfig }));
|
|
7
|
+
|
|
8
|
+
import { addRemoteCommand } from "../../../src/commands/remote";
|
|
9
|
+
|
|
10
|
+
describe("remote command", () => {
|
|
11
|
+
let program: Command;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
jest.spyOn(console, "log").mockImplementation(() => undefined);
|
|
16
|
+
program = new Command();
|
|
17
|
+
program.exitOverride();
|
|
18
|
+
addRemoteCommand(program);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => jest.restoreAllMocks());
|
|
22
|
+
|
|
23
|
+
it("adds a named remote with an independent JWT path", async () => {
|
|
24
|
+
const config: any = {
|
|
25
|
+
orgIds: {
|
|
26
|
+
"http://localhost:4000": "org-local",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
getConfig.mockResolvedValue(config);
|
|
30
|
+
|
|
31
|
+
await program.parseAsync([
|
|
32
|
+
"node",
|
|
33
|
+
"knowhow",
|
|
34
|
+
"remote",
|
|
35
|
+
"add",
|
|
36
|
+
"dev",
|
|
37
|
+
"https://api.dev.knowhow.tyvm.ai/",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
expect(updateConfig).toHaveBeenCalledWith(config);
|
|
41
|
+
expect(config.remotes.dev).toEqual({
|
|
42
|
+
name: "dev",
|
|
43
|
+
apiUrl: "https://api.dev.knowhow.tyvm.ai",
|
|
44
|
+
jwtPath: ".knowhow/.jwt.dev",
|
|
45
|
+
});
|
|
46
|
+
expect(config.orgIds).toBeUndefined();
|
|
47
|
+
expect(config.remotes.local).toEqual({
|
|
48
|
+
name: "local",
|
|
49
|
+
apiUrl: "http://localhost:4000",
|
|
50
|
+
jwtPath: ".knowhow/.jwt.local",
|
|
51
|
+
orgId: "org-local",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("selects a configured remote for subsequent commands", async () => {
|
|
56
|
+
const config: any = {
|
|
57
|
+
remotes: {
|
|
58
|
+
local: {
|
|
59
|
+
name: "local",
|
|
60
|
+
apiUrl: "http://localhost:4000",
|
|
61
|
+
jwtPath: ".knowhow/.jwt.local",
|
|
62
|
+
orgId: "org-local",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
getConfig.mockResolvedValue(config);
|
|
67
|
+
|
|
68
|
+
await program.parseAsync([
|
|
69
|
+
"node",
|
|
70
|
+
"knowhow",
|
|
71
|
+
"remote",
|
|
72
|
+
"use",
|
|
73
|
+
"local",
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
expect(config.activeRemote).toBe("local");
|
|
77
|
+
expect(updateConfig).toHaveBeenCalledWith(config);
|
|
78
|
+
});
|
|
79
|
+
});
|
package/tests/unit/login.test.ts
CHANGED
|
@@ -48,7 +48,7 @@ describe("login public-key bootstrap", () => {
|
|
|
48
48
|
jest.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
49
49
|
workingDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "knowhow-login-"));
|
|
50
50
|
fs.mkdirSync(path.join(workingDirectory, ".knowhow"));
|
|
51
|
-
fs.writeFileSync(path.join(workingDirectory, ".knowhow", ".jwt"), "raw-jwt");
|
|
51
|
+
fs.writeFileSync(path.join(workingDirectory, ".knowhow", ".jwt.api.example"), "raw-jwt");
|
|
52
52
|
process.chdir(workingDirectory);
|
|
53
53
|
hasKeyPair.mockReturnValue(false);
|
|
54
54
|
browserLogin.mockResolvedValue(undefined);
|
|
@@ -87,7 +87,42 @@ describe("login public-key bootstrap", () => {
|
|
|
87
87
|
"https://api.example"
|
|
88
88
|
);
|
|
89
89
|
expect(updateConfig).toHaveBeenCalledWith(
|
|
90
|
-
expect.objectContaining({
|
|
90
|
+
expect.objectContaining({
|
|
91
|
+
activeRemote: "api.example",
|
|
92
|
+
modelProviders: [{ provider: "knowhow" }],
|
|
93
|
+
remotes: expect.objectContaining({
|
|
94
|
+
"api.example": {
|
|
95
|
+
name: "api.example",
|
|
96
|
+
apiUrl: "https://api.example",
|
|
97
|
+
jwtPath: ".knowhow/.jwt.api.example",
|
|
98
|
+
orgId: "org-1",
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("persists an explicit --api-url as the authenticated remote", async () => {
|
|
106
|
+
fs.writeFileSync(path.join(workingDirectory, ".knowhow", ".jwt.local"), "local-jwt");
|
|
107
|
+
|
|
108
|
+
await login(false, undefined, "http://localhost:4000/", "local");
|
|
109
|
+
|
|
110
|
+
expect(updateConfig).toHaveBeenCalledWith(
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
activeRemote: "local",
|
|
113
|
+
remotes: expect.objectContaining({
|
|
114
|
+
local: {
|
|
115
|
+
name: "local",
|
|
116
|
+
apiUrl: "http://localhost:4000",
|
|
117
|
+
jwtPath: ".knowhow/.jwt.local",
|
|
118
|
+
orgId: "org-1",
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
expect(httpGet).toHaveBeenCalledWith(
|
|
124
|
+
"http://localhost:4000/api/users/me",
|
|
125
|
+
expect.any(Object)
|
|
91
126
|
);
|
|
92
127
|
});
|
|
93
128
|
|
|
@@ -119,9 +154,9 @@ describe("login public-key bootstrap", () => {
|
|
|
119
154
|
expect(getOrCreatePublicKey).toHaveBeenCalledWith("/keys/default");
|
|
120
155
|
expect(updateConfig).toHaveBeenCalledWith(
|
|
121
156
|
expect.objectContaining({
|
|
122
|
-
orgId: "org-1",
|
|
123
157
|
cliIdentityPath: "/keys/default",
|
|
124
158
|
modelProviders: [{ provider: "knowhow" }],
|
|
159
|
+
remotes: expect.objectContaining({ "api.example": expect.objectContaining({ orgId: "org-1" }) }),
|
|
125
160
|
})
|
|
126
161
|
);
|
|
127
162
|
});
|
|
@@ -143,6 +178,32 @@ describe("login public-key bootstrap", () => {
|
|
|
143
178
|
expect(getOrCreatePublicKey).toHaveBeenCalledWith("/keys/dev");
|
|
144
179
|
});
|
|
145
180
|
|
|
181
|
+
it("uses the selected API's JWT org instead of an org saved by another environment", async () => {
|
|
182
|
+
const devJwt = `${Buffer.from("{}").toString("base64url")}.${Buffer.from(
|
|
183
|
+
JSON.stringify({ org: "org-dev" })
|
|
184
|
+
).toString("base64url")}.signature`;
|
|
185
|
+
fs.writeFileSync(
|
|
186
|
+
path.join(workingDirectory, ".knowhow", ".jwt.api.example"),
|
|
187
|
+
devJwt
|
|
188
|
+
);
|
|
189
|
+
hasKeyPair.mockReturnValue(true);
|
|
190
|
+
getConfig.mockResolvedValue({
|
|
191
|
+
orgId: "org-local",
|
|
192
|
+
orgIds: { "http://localhost:4000": "org-local" },
|
|
193
|
+
modelProviders: [],
|
|
194
|
+
});
|
|
195
|
+
authenticateWithKey.mockResolvedValueOnce(true);
|
|
196
|
+
|
|
197
|
+
await expect(login(false, "/keys/default")).resolves.toBeUndefined();
|
|
198
|
+
|
|
199
|
+
expect(authenticateWithKey).toHaveBeenCalledWith(
|
|
200
|
+
"org-dev",
|
|
201
|
+
"https://api.example",
|
|
202
|
+
"/keys/default"
|
|
203
|
+
);
|
|
204
|
+
expect(browserLogin).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
|
|
146
207
|
it("does not attempt key authentication without a configured organization", async () => {
|
|
147
208
|
hasKeyPair.mockReturnValue(true);
|
|
148
209
|
getConfig.mockResolvedValue({ modelProviders: [] });
|
|
@@ -54,7 +54,7 @@ describe("KnowhowSimpleClient identity renewal", () => {
|
|
|
54
54
|
"https://api.example.test",
|
|
55
55
|
"/tmp/identity"
|
|
56
56
|
);
|
|
57
|
-
expect(mockStoreJwt).toHaveBeenCalledWith("fresh-jwt");
|
|
57
|
+
expect(mockStoreJwt).toHaveBeenCalledWith("fresh-jwt", "https://api.example.test");
|
|
58
58
|
expect(mockGet.mock.calls[0][1].headers.Authorization).toBe("Bearer fresh-jwt");
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -77,7 +77,7 @@ describe("KnowhowSimpleClient identity renewal", () => {
|
|
|
77
77
|
"https://api.example.test",
|
|
78
78
|
"/tmp/identity"
|
|
79
79
|
);
|
|
80
|
-
expect(mockStoreJwt).toHaveBeenCalledWith("fresh-jwt");
|
|
80
|
+
expect(mockStoreJwt).toHaveBeenCalledWith("fresh-jwt", "https://api.example.test");
|
|
81
81
|
expect(mockGet).toHaveBeenCalledTimes(1);
|
|
82
82
|
expect(mockGet.mock.calls[0][1].headers.Authorization).toBe("Bearer fresh-jwt");
|
|
83
83
|
});
|
|
@@ -33,11 +33,11 @@ describe("McpService JWT file transport", () => {
|
|
|
33
33
|
fs.rmSync(temporaryDirectory, { recursive: true, force: true });
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
it("reads the refreshed JWT when the transport is created", () => {
|
|
36
|
+
it("reads the refreshed JWT when the transport is created", async () => {
|
|
37
37
|
fs.writeFileSync(tokenFile, "expired-jwt");
|
|
38
38
|
fs.writeFileSync(tokenFile, "fresh-jwt");
|
|
39
39
|
|
|
40
|
-
new McpService().createTransport({
|
|
40
|
+
await new McpService().createTransport({
|
|
41
41
|
name: "authenticated-remote",
|
|
42
42
|
url: "https://api.example.test/mcp",
|
|
43
43
|
authorization_token_file: tokenFile,
|
package/ts_build/package.json
CHANGED
|
@@ -70,6 +70,9 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
70
70
|
protected compressThreshold: number;
|
|
71
71
|
protected compressMinMessages: number;
|
|
72
72
|
protected _forceCompact: boolean;
|
|
73
|
+
private _compactionPromise;
|
|
74
|
+
private _completedCompaction;
|
|
75
|
+
private _compactionGeneration;
|
|
73
76
|
private _interruptResolve;
|
|
74
77
|
private _interruptToken;
|
|
75
78
|
protected threads: Message[][];
|
|
@@ -147,7 +150,7 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
147
150
|
getTurnCount(): number;
|
|
148
151
|
adjustTotalCostUsd(cost: number): void;
|
|
149
152
|
getTotalCostUsd(): number;
|
|
150
|
-
adjustTokenUsage(usage: any, messages?: Message[]): void;
|
|
153
|
+
adjustTokenUsage(usage: any, messages?: Message[], updateLastPromptTokens?: boolean): void;
|
|
151
154
|
getTokenUsage(): {
|
|
152
155
|
totalInputTokens: number;
|
|
153
156
|
totalOutputTokens: number;
|
|
@@ -156,6 +159,7 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
156
159
|
};
|
|
157
160
|
protected createAgentCompletion(params: CachedChatCompletion, options?: {
|
|
158
161
|
interruptValue?: CompletionResponse;
|
|
162
|
+
updateLastPromptTokens?: boolean;
|
|
159
163
|
}): Promise<CompletionResponse>;
|
|
160
164
|
startNewThread(messages: Message[]): void;
|
|
161
165
|
updateCurrentThread(messages: Message[]): void;
|
|
@@ -192,6 +196,7 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
192
196
|
content: string;
|
|
193
197
|
} | null;
|
|
194
198
|
extractContentFromMessages(messages: Message[]): string;
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
private startBackgroundCompaction;
|
|
200
|
+
getTaskBreakdown(messages: Message[], cacheResult?: boolean): Promise<string>;
|
|
201
|
+
compressMessages(messages: Message[], onSummary?: (summary: string) => void, taskBreakdown?: string): Promise<Message[]>;
|
|
197
202
|
}
|
|
@@ -44,6 +44,9 @@ let BaseAgent = class BaseAgent {
|
|
|
44
44
|
compressThreshold = 30000;
|
|
45
45
|
compressMinMessages = 30;
|
|
46
46
|
_forceCompact = false;
|
|
47
|
+
_compactionPromise = null;
|
|
48
|
+
_completedCompaction = null;
|
|
49
|
+
_compactionGeneration = 0;
|
|
47
50
|
_interruptResolve = null;
|
|
48
51
|
_interruptToken = 0;
|
|
49
52
|
threads = [];
|
|
@@ -141,6 +144,9 @@ let BaseAgent = class BaseAgent {
|
|
|
141
144
|
this.maxRunTimeMs = maxRunTimeMs;
|
|
142
145
|
}
|
|
143
146
|
newTask(taskId) {
|
|
147
|
+
this._compactionGeneration++;
|
|
148
|
+
this._compactionPromise = null;
|
|
149
|
+
this._completedCompaction = null;
|
|
144
150
|
this.currentThread = 0;
|
|
145
151
|
this.threads = [];
|
|
146
152
|
this.taskBreakdown = "";
|
|
@@ -348,7 +354,7 @@ let BaseAgent = class BaseAgent {
|
|
|
348
354
|
getTotalCostUsd() {
|
|
349
355
|
return this.totalCostUsd;
|
|
350
356
|
}
|
|
351
|
-
adjustTokenUsage(usage, messages) {
|
|
357
|
+
adjustTokenUsage(usage, messages, updateLastPromptTokens = true) {
|
|
352
358
|
if (!usage)
|
|
353
359
|
return;
|
|
354
360
|
const inputTokens = usage.input_tokens ?? usage.prompt_tokens ?? 0;
|
|
@@ -359,7 +365,7 @@ let BaseAgent = class BaseAgent {
|
|
|
359
365
|
0;
|
|
360
366
|
const cacheWriteTokens = usage.cache_creation_input_tokens ?? usage.cache_write_tokens ?? 0;
|
|
361
367
|
const promptTokens = inputTokens + cacheReadTokens + cacheWriteTokens;
|
|
362
|
-
if (promptTokens > 0) {
|
|
368
|
+
if (updateLastPromptTokens && promptTokens > 0) {
|
|
363
369
|
this.lastPromptTokens = promptTokens;
|
|
364
370
|
}
|
|
365
371
|
this.totalInputTokens += inputTokens;
|
|
@@ -388,7 +394,7 @@ let BaseAgent = class BaseAgent {
|
|
|
388
394
|
};
|
|
389
395
|
}
|
|
390
396
|
async createAgentCompletion(params, options = {}) {
|
|
391
|
-
const { interruptValue } = options;
|
|
397
|
+
const { interruptValue, updateLastPromptTokens = true } = options;
|
|
392
398
|
const defaultedParams = {
|
|
393
399
|
model: this.getModel(),
|
|
394
400
|
tools: this.getEnabledTools(),
|
|
@@ -407,7 +413,7 @@ let BaseAgent = class BaseAgent {
|
|
|
407
413
|
? await this.makeInterruptible(callPromise, interruptValue)
|
|
408
414
|
: await callPromise;
|
|
409
415
|
this.adjustTotalCostUsd(response?.usd_cost);
|
|
410
|
-
this.adjustTokenUsage(response?.usage, params.messages);
|
|
416
|
+
this.adjustTokenUsage(response?.usage, params.messages, updateLastPromptTokens);
|
|
411
417
|
return response;
|
|
412
418
|
}
|
|
413
419
|
startNewThread(messages) {
|
|
@@ -774,25 +780,56 @@ let BaseAgent = class BaseAgent {
|
|
|
774
780
|
this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
|
|
775
781
|
return firstMessage.content;
|
|
776
782
|
}
|
|
783
|
+
const completedCompaction = this._completedCompaction;
|
|
784
|
+
this._completedCompaction = null;
|
|
785
|
+
let appliedCompaction = false;
|
|
786
|
+
if (completedCompaction) {
|
|
787
|
+
const currentPrefix = messages.slice(0, completedCompaction.compressionEnd);
|
|
788
|
+
const prefixIsUnchanged = currentPrefix.every((message, index) => message === completedCompaction.originalPrefix[index] ||
|
|
789
|
+
JSON.stringify(message) ===
|
|
790
|
+
JSON.stringify(completedCompaction.originalPrefix[index]));
|
|
791
|
+
if (currentPrefix.length === completedCompaction.compressionEnd &&
|
|
792
|
+
prefixIsUnchanged) {
|
|
793
|
+
messages = [
|
|
794
|
+
...completedCompaction.replacementPrefix,
|
|
795
|
+
...messages.slice(completedCompaction.compressionEnd),
|
|
796
|
+
];
|
|
797
|
+
this.summaries.push(completedCompaction.summary);
|
|
798
|
+
this.startNewThread(messages);
|
|
799
|
+
this.lastPromptTokens = 0;
|
|
800
|
+
appliedCompaction = true;
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
this.log("Discarding completed compaction because its source prefix changed; a later turn will retry", "warn");
|
|
804
|
+
}
|
|
805
|
+
}
|
|
777
806
|
const contextTokens = this.getContextTokenCount(messages);
|
|
778
807
|
const overThreshold = contextTokens > this.getCompressThreshold() &&
|
|
779
808
|
messages.length > this.compressMinMessages;
|
|
780
|
-
if (
|
|
781
|
-
|
|
809
|
+
if (!appliedCompaction &&
|
|
810
|
+
!this._compactionPromise &&
|
|
811
|
+
(overThreshold || this._forceCompact)) {
|
|
782
812
|
this.log(this._forceCompact
|
|
783
|
-
? `
|
|
784
|
-
: `
|
|
813
|
+
? `Starting background compaction (manual /compact request): ${contextTokens} tokens`
|
|
814
|
+
: `Starting background compaction: ${contextTokens} tokens exceeds ${this.getCompressThreshold()}`);
|
|
785
815
|
this._forceCompact = false;
|
|
786
|
-
|
|
787
|
-
this.startNewThread(messages);
|
|
788
|
-
this.lastPromptTokens = 0;
|
|
816
|
+
this.startBackgroundCompaction(messages);
|
|
789
817
|
}
|
|
790
818
|
if (["assistant", "tool"].includes(messages[messages.length - 1].role)) {
|
|
791
819
|
const statusMessage = this.getStatusMessage();
|
|
792
820
|
this.logStatus();
|
|
793
821
|
const continuation = `<Workflow>
|
|
794
|
-
|
|
795
|
-
|
|
822
|
+
This is an automated runtime instruction, not a new user message.
|
|
823
|
+
The original user request remains active. Do not say that the user's
|
|
824
|
+
message was empty, and do not ask the user to repeat the request.
|
|
825
|
+
|
|
826
|
+
Your previous response did not terminate the task because it did not
|
|
827
|
+
call one of the required tools: ${JSON.stringify(this.requiredToolNames)}.
|
|
828
|
+
If your previous response was intended as the final response, call the
|
|
829
|
+
appropriate required tool now with that response. Otherwise, continue
|
|
830
|
+
working on the original request and call an appropriate required tool
|
|
831
|
+
when the task is complete.
|
|
832
|
+
|
|
796
833
|
<TaskStatus>${statusMessage}</TaskStatus>
|
|
797
834
|
</Workflow>`;
|
|
798
835
|
messages.push({
|
|
@@ -1028,12 +1065,53 @@ let BaseAgent = class BaseAgent {
|
|
|
1028
1065
|
.map((t) => t.function.arguments);
|
|
1029
1066
|
return [...content, ...finalAnswer].join("\n\n");
|
|
1030
1067
|
}
|
|
1031
|
-
|
|
1068
|
+
startBackgroundCompaction(messages) {
|
|
1069
|
+
const snapshot = messages.slice();
|
|
1070
|
+
let resumeIndex = snapshot.length - 1;
|
|
1071
|
+
while (resumeIndex >= 0 && snapshot[resumeIndex].role !== "assistant") {
|
|
1072
|
+
resumeIndex--;
|
|
1073
|
+
}
|
|
1074
|
+
const compressionEnd = resumeIndex === -1 ? snapshot.length : resumeIndex;
|
|
1075
|
+
const generation = this._compactionGeneration;
|
|
1076
|
+
const promise = (async () => {
|
|
1077
|
+
const taskBreakdown = await this.getTaskBreakdown(snapshot, false);
|
|
1078
|
+
if (generation !== this._compactionGeneration)
|
|
1079
|
+
return;
|
|
1080
|
+
this.taskBreakdown = taskBreakdown;
|
|
1081
|
+
let summary = "";
|
|
1082
|
+
const compactedSnapshot = await this.compressMessages(snapshot, (generatedSummary) => {
|
|
1083
|
+
summary = generatedSummary;
|
|
1084
|
+
}, taskBreakdown);
|
|
1085
|
+
if (generation !== this._compactionGeneration)
|
|
1086
|
+
return;
|
|
1087
|
+
const preservedSnapshotLength = snapshot.length - compressionEnd;
|
|
1088
|
+
this._completedCompaction = {
|
|
1089
|
+
compressionEnd,
|
|
1090
|
+
originalPrefix: snapshot.slice(0, compressionEnd),
|
|
1091
|
+
replacementPrefix: compactedSnapshot.slice(0, compactedSnapshot.length - preservedSnapshotLength),
|
|
1092
|
+
summary,
|
|
1093
|
+
};
|
|
1094
|
+
this.log("Background compaction completed; it will be applied next turn");
|
|
1095
|
+
})();
|
|
1096
|
+
this._compactionPromise = promise;
|
|
1097
|
+
promise
|
|
1098
|
+
.catch((error) => {
|
|
1099
|
+
if (generation === this._compactionGeneration) {
|
|
1100
|
+
this.log(`Background compaction failed; a later turn will retry: ${error}`, "warn");
|
|
1101
|
+
}
|
|
1102
|
+
})
|
|
1103
|
+
.finally(() => {
|
|
1104
|
+
if (this._compactionPromise === promise) {
|
|
1105
|
+
this._compactionPromise = null;
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
async getTaskBreakdown(messages, cacheResult = true) {
|
|
1032
1110
|
if (this.taskBreakdown) {
|
|
1033
1111
|
return this.taskBreakdown;
|
|
1034
1112
|
}
|
|
1035
1113
|
const taskPrompt = `
|
|
1036
|
-
Analyze all previous messages.
|
|
1114
|
+
TASK INTERRUPT. CONTEXT COMPACTION NEEDED. Analyze all previous messages.
|
|
1037
1115
|
|
|
1038
1116
|
Generate a detailed task breakdown for this conversation, include a section for the following:
|
|
1039
1117
|
1. Task List
|
|
@@ -1052,17 +1130,19 @@ let BaseAgent = class BaseAgent {
|
|
|
1052
1130
|
const response = await this.createAgentCompletion({
|
|
1053
1131
|
messages: taskBreakdownMessages,
|
|
1054
1132
|
tool_choice: "none",
|
|
1055
|
-
});
|
|
1133
|
+
}, { updateLastPromptTokens: false });
|
|
1056
1134
|
const breakdownContent = this.extractContentFromMessages(response.choices?.map((c) => c.message));
|
|
1057
1135
|
if (!breakdownContent) {
|
|
1058
1136
|
console.log(JSON.stringify(response.choices, null, 2));
|
|
1059
1137
|
throw new Error("Compaction task-breakdown request returned no text content");
|
|
1060
1138
|
}
|
|
1061
|
-
|
|
1139
|
+
if (cacheResult) {
|
|
1140
|
+
this.taskBreakdown = breakdownContent;
|
|
1141
|
+
}
|
|
1062
1142
|
this.log(`task breakdown cost: ${response.usd_cost}`);
|
|
1063
|
-
return
|
|
1143
|
+
return breakdownContent;
|
|
1064
1144
|
}
|
|
1065
|
-
async compressMessages(messages) {
|
|
1145
|
+
async compressMessages(messages, onSummary, taskBreakdown = this.taskBreakdown) {
|
|
1066
1146
|
let resumeIndex = messages.length - 1;
|
|
1067
1147
|
while (resumeIndex >= 0 && messages[resumeIndex].role !== "assistant") {
|
|
1068
1148
|
resumeIndex--;
|
|
@@ -1071,7 +1151,8 @@ let BaseAgent = class BaseAgent {
|
|
|
1071
1151
|
const toCompress = messages.slice(0, compressionEnd);
|
|
1072
1152
|
const resumeMessages = messages.slice(compressionEnd);
|
|
1073
1153
|
this.log(`Compressing messages from 0 to ${compressionEnd}, resuming from ${resumeIndex}, total messages: ${messages.length}`);
|
|
1074
|
-
const toCompressPrompt = `
|
|
1154
|
+
const toCompressPrompt = `TASK INTERRUPT. CONTEXT COMPACTION NEEDED.
|
|
1155
|
+
We are compressing our conversation to save memory.
|
|
1075
1156
|
Please summarize the conversation so far, so that we may continue the original task with a smaller context
|
|
1076
1157
|
|
|
1077
1158
|
Include the following sections:
|
|
@@ -1080,8 +1161,8 @@ let BaseAgent = class BaseAgent {
|
|
|
1080
1161
|
3. Next Steps - what we're about to do next to continue the user's original request.
|
|
1081
1162
|
4. Tasks remaining - what tasks are left from the initial task breakdown.
|
|
1082
1163
|
|
|
1083
|
-
${
|
|
1084
|
-
? `Our initial task breakdown: ${
|
|
1164
|
+
${taskBreakdown
|
|
1165
|
+
? `Our initial task breakdown: ${taskBreakdown}`
|
|
1085
1166
|
: ""}
|
|
1086
1167
|
|
|
1087
1168
|
This summary will replace the older history. The latest agent interaction will remain verbatim:
|
|
@@ -1097,19 +1178,24 @@ let BaseAgent = class BaseAgent {
|
|
|
1097
1178
|
const response = await this.createAgentCompletion({
|
|
1098
1179
|
messages: compressMessagesPayload,
|
|
1099
1180
|
tool_choice: "none",
|
|
1100
|
-
});
|
|
1181
|
+
}, { updateLastPromptTokens: false });
|
|
1101
1182
|
const summary = this.extractContentFromMessages(response.choices?.map((c) => c.message));
|
|
1102
1183
|
if (summary.length === 0) {
|
|
1103
1184
|
console.log(JSON.stringify(response.choices, null, 2));
|
|
1104
1185
|
throw new Error("Compaction summary request returned no text content");
|
|
1105
1186
|
}
|
|
1106
|
-
|
|
1187
|
+
if (onSummary) {
|
|
1188
|
+
onSummary(summary);
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
this.summaries.push(summary);
|
|
1192
|
+
}
|
|
1107
1193
|
const startMessages = [
|
|
1108
1194
|
{
|
|
1109
1195
|
role: "user",
|
|
1110
1196
|
content: `
|
|
1111
|
-
${
|
|
1112
|
-
? `Initial task breakdown:\n ${
|
|
1197
|
+
${taskBreakdown
|
|
1198
|
+
? `Initial task breakdown:\n ${taskBreakdown}`
|
|
1113
1199
|
: "(No task breakdown available — summarize what you know from context above)"}
|
|
1114
1200
|
|
|
1115
1201
|
We have just compressed the conversation to save memory:
|