@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.
@@ -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.11",
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.11",
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",