@akanjs/devkit 3.1.1-rc.11 → 3.1.1-rc.13
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/codeAgent/agent/CodeAgent.ts +4 -0
- package/codeAgent/agent/SubagentPool.ts +5 -0
- package/codeAgent/agent/codeAgent.test.ts +41 -2
- package/codeAgent/skills/akan-code/SKILL.md +1 -0
- package/codeAgent/tools/AkanCodePlugins.ts +4 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +48 -0
- package/package.json +2 -2
|
@@ -114,6 +114,10 @@ export class CodeAgent {
|
|
|
114
114
|
profile: options.profile,
|
|
115
115
|
apps: options.apps ?? [],
|
|
116
116
|
canSeeImages: akanCodeModelSupportsImages(model),
|
|
117
|
+
model: () => {
|
|
118
|
+
const current = agent.#session?.model;
|
|
119
|
+
return current ? { provider: current.provider, id: current.id } : undefined;
|
|
120
|
+
},
|
|
117
121
|
deepseek: await DeepSeekWeb.fromRuntime(modelRuntime, !!proxy),
|
|
118
122
|
depth: options.depth ?? 0,
|
|
119
123
|
...(options.subagentSpend ? { subagentSpend: options.subagentSpend } : {}),
|
|
@@ -2,11 +2,14 @@ import type { ExtensionAPI, InlineExtension } from "@earendil-works/pi-coding-ag
|
|
|
2
2
|
import { type CodeAgentProfile, type CodeAgentSubagent, codeAgentReadOnlyBuiltins } from "akanjs/common";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
4
|
import type { Workspace } from "../../commandDecorators";
|
|
5
|
+
import type { CodeAgentModelRef } from "./akanCodeModel";
|
|
5
6
|
|
|
6
7
|
export interface SubagentPoolOptions {
|
|
7
8
|
workspace: Workspace;
|
|
8
9
|
cwd: string;
|
|
9
10
|
parent: CodeAgentProfile;
|
|
11
|
+
/** The parent's model, read when a task starts: a child runs on the model the parent runs on now. */
|
|
12
|
+
model?: () => CodeAgentModelRef | undefined;
|
|
10
13
|
depth: number;
|
|
11
14
|
/** What the tree above this pool has spent and has running; a root pool starts its own. */
|
|
12
15
|
spend?: SubagentSpend;
|
|
@@ -164,10 +167,12 @@ export class SubagentPool {
|
|
|
164
167
|
const { CodeAgent } = await import("./CodeAgent");
|
|
165
168
|
const budget = this.#options.parent.tools.subagent;
|
|
166
169
|
const child = SubagentPool.childOf(this.#options.parent, kind);
|
|
170
|
+
const model = this.#options.model?.();
|
|
167
171
|
const agent = await CodeAgent.create({
|
|
168
172
|
workspace: this.#options.workspace,
|
|
169
173
|
cwd: this.#options.cwd,
|
|
170
174
|
profile: child,
|
|
175
|
+
...(model ? { model } : {}),
|
|
171
176
|
apps: [],
|
|
172
177
|
depth: this.#options.depth + 1,
|
|
173
178
|
subagentSpend: this.#spend,
|
|
@@ -6,7 +6,7 @@ import { type CodeAgentEvent, type CodeAgentProfile, codeAgentPresets } from "ak
|
|
|
6
6
|
import { WorkspaceExecutor } from "../../executors";
|
|
7
7
|
import { isolateEnv, tempDirs } from "../../testHelpers";
|
|
8
8
|
import { AkanCodePlugins } from "../tools/AkanCodePlugins";
|
|
9
|
-
import { akanCodeDefaultModel } from "./akanCodeModel";
|
|
9
|
+
import { akanCodeDefaultModel, type CodeAgentModelRef } from "./akanCodeModel";
|
|
10
10
|
import { akanCodePaths } from "./akanCodePaths";
|
|
11
11
|
import { CodeAgent, type CodeAgentOptions } from "./CodeAgent";
|
|
12
12
|
import { CodeAgentSuspended } from "./CodeAgentSuspended";
|
|
@@ -37,11 +37,14 @@ const usageChunk = (promptTokens: number) =>
|
|
|
37
37
|
let llm: ReturnType<typeof Bun.serve>;
|
|
38
38
|
let llmCalls = 0;
|
|
39
39
|
let reportedTokens = 0;
|
|
40
|
+
const requestedModels: string[] = [];
|
|
40
41
|
beforeAll(() => {
|
|
41
42
|
llm = Bun.serve({
|
|
42
43
|
port: 0,
|
|
43
|
-
fetch: () => {
|
|
44
|
+
fetch: async (request) => {
|
|
44
45
|
llmCalls += 1;
|
|
46
|
+
const body = (await request.json().catch(() => ({}))) as { model?: string };
|
|
47
|
+
requestedModels.push(body.model ?? "");
|
|
45
48
|
const usage = reportedTokens ? usageChunk(reportedTokens) : "";
|
|
46
49
|
return new Response(
|
|
47
50
|
`${chunk({ role: "assistant", content: "ok" })}${chunk({}, "stop")}${usage}data: [DONE]\n\n`,
|
|
@@ -160,6 +163,42 @@ describe("CodeAgent", () => {
|
|
|
160
163
|
spy.mockRestore();
|
|
161
164
|
}
|
|
162
165
|
});
|
|
166
|
+
|
|
167
|
+
test("a task child sends its requests on the parent's model, and on the one the parent switches to", async () => {
|
|
168
|
+
const root = await makeTempRoot();
|
|
169
|
+
const build = AkanCodePlugins.build;
|
|
170
|
+
let parentModel: (() => CodeAgentModelRef | undefined) | undefined;
|
|
171
|
+
const spy = spyOn(AkanCodePlugins, "build").mockImplementation(async (options) => {
|
|
172
|
+
parentModel = options.model;
|
|
173
|
+
return await build.call(AkanCodePlugins, options);
|
|
174
|
+
});
|
|
175
|
+
const agent = await createAgent(root, { model: { provider: "deepseek", id: "deepseek-v4-pro" } });
|
|
176
|
+
spy.mockRestore();
|
|
177
|
+
const profile = quietProfile(root);
|
|
178
|
+
const pool = SubagentPool.extensionFor({
|
|
179
|
+
workspace: new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" }),
|
|
180
|
+
cwd: root,
|
|
181
|
+
parent: { ...profile, tools: { ...profile.tools, subagent: { maxDepth: 2, maxConcurrent: 3, budget: 100_000 } } },
|
|
182
|
+
...(parentModel ? { model: parentModel } : {}),
|
|
183
|
+
depth: 0,
|
|
184
|
+
});
|
|
185
|
+
let execute: ((id: string, params: never) => Promise<unknown>) | undefined;
|
|
186
|
+
if (pool && "factory" in pool)
|
|
187
|
+
pool.factory({
|
|
188
|
+
registerTool: (tool: { execute: typeof execute }) => {
|
|
189
|
+
execute = tool.execute;
|
|
190
|
+
},
|
|
191
|
+
} as never);
|
|
192
|
+
try {
|
|
193
|
+
requestedModels.length = 0;
|
|
194
|
+
await execute?.("c1", { description: "d", prompt: "p" } as never);
|
|
195
|
+
await agent.setModel(akanCodeDefaultModel);
|
|
196
|
+
await execute?.("c2", { description: "d", prompt: "p" } as never);
|
|
197
|
+
expect(requestedModels).toEqual(["deepseek-v4-pro", akanCodeDefaultModel.id]);
|
|
198
|
+
} finally {
|
|
199
|
+
agent.dispose();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
163
202
|
});
|
|
164
203
|
|
|
165
204
|
describe("a sub-agent its parent stops before the sub-agent's run is live", () => {
|
|
@@ -85,6 +85,7 @@ Stored under `.akan/code/sessions` — project history, so a second checkout of
|
|
|
85
85
|
## Model, effort, keys
|
|
86
86
|
|
|
87
87
|
- `/model <provider>/<id>`, e.g. `/model deepseek/deepseek-v4-pro`. `/effort <level>` for how hard it thinks.
|
|
88
|
+
A `task` sub-agent starts on the model the session is on at that moment.
|
|
88
89
|
- A provider is authorized by **`<PROVIDER>_API_KEY`** — `DEEPSEEK_API_KEY`, `ANTHROPIC_API_KEY`. The
|
|
89
90
|
workspace's own `.env` is read as well as the environment, and a key found there is used for the run and
|
|
90
91
|
never copied anywhere.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { InlineExtension } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { CodeAgentMcpStatus, CodeAgentProfile, CodeAgentQuestion, CodeAgentSubagent } from "akanjs/common";
|
|
3
3
|
import type { Workspace } from "../../commandDecorators";
|
|
4
|
+
import type { CodeAgentModelRef } from "../agent/akanCodeModel";
|
|
4
5
|
import type { CodeMailbox } from "../agent/CodeMailbox";
|
|
5
6
|
import { SubagentPool, type SubagentSpend } from "../agent/SubagentPool";
|
|
6
7
|
import { DevLogFeedback } from "../feedback/DevLogFeedback";
|
|
@@ -23,6 +24,8 @@ export interface AkanCodePluginsOptions {
|
|
|
23
24
|
/** Apps whose dev server should be watched, and whose page should be probed. */
|
|
24
25
|
apps: string[];
|
|
25
26
|
canSeeImages: boolean;
|
|
27
|
+
/** The owning agent's model as it is now; a `task` child runs on it. */
|
|
28
|
+
model?: () => CodeAgentModelRef | undefined;
|
|
26
29
|
deepseek?: DeepSeekWeb | undefined;
|
|
27
30
|
/** How many `task` levels deep the owning agent is. */
|
|
28
31
|
depth: number;
|
|
@@ -92,6 +95,7 @@ export class AkanCodePlugins {
|
|
|
92
95
|
workspace: options.workspace,
|
|
93
96
|
cwd: options.cwd,
|
|
94
97
|
parent: options.profile,
|
|
98
|
+
...(options.model ? { model: options.model } : {}),
|
|
95
99
|
depth: options.depth,
|
|
96
100
|
...(options.subagentSpend ? { spend: options.subagentSpend } : {}),
|
|
97
101
|
...(options.onNotice ? { onNotice: options.onNotice } : {}),
|
|
@@ -519,6 +519,54 @@ describe("SubagentPool", () => {
|
|
|
519
519
|
}
|
|
520
520
|
});
|
|
521
521
|
|
|
522
|
+
test("a child runs on the model its parent runs on when the task starts", async () => {
|
|
523
|
+
const { opened, spy } = openFakeAgents([]);
|
|
524
|
+
let current = { provider: "deepseek", id: "deepseek-v4-pro" };
|
|
525
|
+
try {
|
|
526
|
+
const root = SubagentPool.extensionFor({
|
|
527
|
+
workspace,
|
|
528
|
+
cwd: "/tmp/akan",
|
|
529
|
+
parent: budgeted(1_000),
|
|
530
|
+
model: () => current,
|
|
531
|
+
depth: 0,
|
|
532
|
+
});
|
|
533
|
+
await runTask(root, "c1");
|
|
534
|
+
current = { provider: "openai", id: "gpt-5.5" };
|
|
535
|
+
await runTask(root, "c2");
|
|
536
|
+
expect(opened.map((options) => options.model)).toEqual([
|
|
537
|
+
{ provider: "deepseek", id: "deepseek-v4-pro" },
|
|
538
|
+
{ provider: "openai", id: "gpt-5.5" },
|
|
539
|
+
]);
|
|
540
|
+
} finally {
|
|
541
|
+
spy.mockRestore();
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("the plugins hand their pool the owning agent's model", async () => {
|
|
546
|
+
const { opened, spy } = openFakeAgents([]);
|
|
547
|
+
try {
|
|
548
|
+
const plugins = await AkanCodePlugins.build({
|
|
549
|
+
workspace,
|
|
550
|
+
cwd: "/tmp/akan",
|
|
551
|
+
profile: budgeted(1_000),
|
|
552
|
+
apps: [],
|
|
553
|
+
canSeeImages: false,
|
|
554
|
+
model: () => ({ provider: "deepseek", id: "deepseek-v4-pro" }),
|
|
555
|
+
depth: 0,
|
|
556
|
+
currentSessionId: () => "",
|
|
557
|
+
mailbox: () => undefined,
|
|
558
|
+
});
|
|
559
|
+
await runTask(
|
|
560
|
+
plugins.extensions.find((extension) => extension.name === "akan-subagent"),
|
|
561
|
+
"c1",
|
|
562
|
+
);
|
|
563
|
+
expect(opened[0]?.model).toEqual({ provider: "deepseek", id: "deepseek-v4-pro" });
|
|
564
|
+
plugins.dispose();
|
|
565
|
+
} finally {
|
|
566
|
+
spy.mockRestore();
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
522
570
|
test("a child keeps no session and cannot ask a human", () => {
|
|
523
571
|
const child = SubagentPool.childOf(codeAgentPresets.local("/tmp/akan"), "explore");
|
|
524
572
|
expect(child.session).toEqual({ store: "memory", crossSession: false });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "3.1.1-rc.
|
|
3
|
+
"version": "3.1.1-rc.13",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@earendil-works/pi-tui": "1.3.0",
|
|
49
49
|
"@inquirer/prompts": "^8.7.3",
|
|
50
50
|
"@tailwindcss/node": "^4.3.3",
|
|
51
|
-
"akanjs": "3.1.1-rc.
|
|
51
|
+
"akanjs": "3.1.1-rc.13",
|
|
52
52
|
"chalk": "^6.0.1",
|
|
53
53
|
"commander": "^15.0.0",
|
|
54
54
|
"dayjs": "^1.11.23",
|