@akanjs/cli 3.1.0-rc.12 → 3.1.0-rc.14
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/.build-stamp +1 -1
- package/{CodeAgent-fhrb6xyw.js → CodeAgent-n3tzjm91.js} +2 -2
- package/{CodeTui-ntnda5dn.js → CodeTui-fnm9ft4d.js} +1 -1
- package/akanCode.js +2 -2
- package/{code.command-3sxektnr.js → code.command-130bdmhe.js} +4 -4
- package/guidelines/agentRule/agentRule.instruction.md +22 -7
- package/guidelines/conventions/conventions.instruction.md +6 -2
- package/guidelines/modelStore/modelStore.instruction.md +7 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +2 -0
- package/{index-zjgs0t7s.js → index-1djcjcre.js} +218 -41
- package/{index-ht15pm08.js → index-dye8pnxx.js} +1 -1
- package/index.js +1 -1
- package/package.json +2 -2
package/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
950cf5d3be113577366b2ad2bee64408ec72e632a6e3a15290c14be5cef218fb
|
package/akanCode.js
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
CodeMailbox2,
|
|
29
29
|
CodeSessionFork2,
|
|
30
30
|
CodeAgent2
|
|
31
|
-
} from "./index-
|
|
31
|
+
} from "./index-1djcjcre.js";
|
|
32
32
|
import {
|
|
33
33
|
akanCodeDefaultModel2,
|
|
34
34
|
akanCodeModel2,
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
SubagentPool2,
|
|
41
41
|
akanCodePaths2,
|
|
42
42
|
AkanCodeCompaction2
|
|
43
|
-
} from "./index-
|
|
43
|
+
} from "./index-dye8pnxx.js";
|
|
44
44
|
import"./index-7md2g9vw.js";
|
|
45
45
|
import {
|
|
46
46
|
CodeAgentRpcHost2
|
|
@@ -19,7 +19,7 @@ class CodeRunner extends runner("code") {
|
|
|
19
19
|
async serve(options, listen) {
|
|
20
20
|
await import("./consoleToStderr-h5bryj94.js");
|
|
21
21
|
const [{ CodeAgent }, { CodeAgentRpcHost }, { CodeAgentRpcListener }] = await Promise.all([
|
|
22
|
-
import("./CodeAgent-
|
|
22
|
+
import("./CodeAgent-n3tzjm91.js"),
|
|
23
23
|
import("./CodeAgentRpcHost-9g27hmer.js"),
|
|
24
24
|
import("./CodeAgentRpcListener-7p7z5dd7.js")
|
|
25
25
|
]);
|
|
@@ -34,8 +34,8 @@ class CodeRunner extends runner("code") {
|
|
|
34
34
|
}
|
|
35
35
|
async tui(options, seed) {
|
|
36
36
|
const [{ CodeAgent }, { CodeTui }] = await Promise.all([
|
|
37
|
-
import("./CodeAgent-
|
|
38
|
-
import("./CodeTui-
|
|
37
|
+
import("./CodeAgent-n3tzjm91.js"),
|
|
38
|
+
import("./CodeTui-fnm9ft4d.js")
|
|
39
39
|
]);
|
|
40
40
|
const agentOptions = await CodeRunner.#agentOptions(options, false);
|
|
41
41
|
let resume = options.resume;
|
|
@@ -67,7 +67,7 @@ class CodeRunner extends runner("code") {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
async run(prompt, options) {
|
|
70
|
-
const { CodeAgent } = await import("./CodeAgent-
|
|
70
|
+
const { CodeAgent } = await import("./CodeAgent-n3tzjm91.js");
|
|
71
71
|
const agent = await CodeAgent.create(await CodeRunner.#agentOptions(options, false));
|
|
72
72
|
const printer = new CodeAgentStreamPrinter2({ json: options.json, thinking: options.thinking });
|
|
73
73
|
agent.on((event) => printer.print(event));
|
|
@@ -180,6 +180,14 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
180
180
|
stores its uploads keeps a map beside the transcript keyed on name and size — the same guess `Attachment.same`
|
|
181
181
|
has to make, and the one that is wrong for two crops of one export. So when both sides carry a `ref`, it *is* the
|
|
182
182
|
answer to "the same file twice", and the heuristic is what is left for when they do not.
|
|
183
|
+
- **A tool hands the model a file with `ToolReturn.attach(result, [attachment])`** (`akanjs/store`), which lands in
|
|
184
|
+
`ToolCallResult.attachments` beside the value — a `result` reaches the model as JSON text, so a URL in it is read
|
|
185
|
+
as letters and base64 as a wall of them. It takes a user attachment's shape and meets the same `accepts` check and
|
|
186
|
+
type allow-list; Anthropic carries it inside the `tool_result`, the chat-completions dialect (whose `tool` message
|
|
187
|
+
holds text alone) in one user message after the turn's tool messages. It is outside the 20,000-character ceiling
|
|
188
|
+
and held to the 4 MB per-file one, and only the latest turn's ride as bytes: an earlier one becomes a note telling
|
|
189
|
+
the model to call the tool again while the picture is on screen, since one picture costs over a thousand tokens on
|
|
190
|
+
every turn it rides.
|
|
183
191
|
- **Speech is one engine contract and the framework's own policy.** `<Agent.Chat voice={engine} />` takes a
|
|
184
192
|
`VoiceEngine` — `listen(handlers)` and `speak(sentence)`, both cancellable — and the chat decides everything
|
|
185
193
|
else: a press-to-talk microphone whose transcript lands in the composer to be corrected, one utterance per
|
|
@@ -398,6 +406,10 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
398
406
|
renders one editor per row, and at most one of them is ever open, so one name is registered rather than fifty.
|
|
399
407
|
It also means the verb is absent from the catalogue while nothing is open, which is honest — and costs the
|
|
400
408
|
agent nothing, because the catalogue is re-read on the turn that follows the tool call that opened the form.
|
|
409
|
+
- **A shell's `submit<Model>` refuses while an upload is in flight.** `Model.EditModal` (also while its submit is
|
|
410
|
+
disabled) and `Model.ViewEditModal` disable the button and answer `The <model> form is not ready to submit.` while
|
|
411
|
+
`<model>FormUploading` is not empty. That key is read `{ agent: false }`, so the refusal is how an agent learns of
|
|
412
|
+
the upload: call `submit<Model>` again once the file has landed, rather than setting the field a second time.
|
|
401
413
|
|
|
402
414
|
## Forms
|
|
403
415
|
- **A form control publishes its own setter, and reading a form publishes one tool that fills several at once.**
|
|
@@ -614,14 +626,17 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
614
626
|
row, Tab completing its name, and Escape closing the menu and then, pressed again, the panel.
|
|
615
627
|
|
|
616
628
|
## Built-In Tools
|
|
617
|
-
- The framework publishes
|
|
629
|
+
- The framework publishes six built-ins on every store surface: `navigate` (internal paths only, the same router
|
|
618
630
|
`Link` rides), `goBack` (this session's history — global, because history is not a control a page owns and a page
|
|
619
631
|
that draws no back link is not one you may not leave — a screen that must not be left withholds them with `builtins`,
|
|
620
632
|
not with prose), `readScreen` (the rendered DOM as compact text — headings, links, control values, and `(disabled)`
|
|
621
633
|
on a control or button that has it; the chat's own UI is skipped via `data-agent-ui`, and a password value is never
|
|
622
634
|
read), `readState(key)` (one masked store key — a key that is unknown or that this screen does not read is refused
|
|
623
|
-
*with the keys it can* read, the way `readScreen` names its sections),
|
|
624
|
-
|
|
635
|
+
*with the keys it can* read, the way `readScreen` names its sections), `highlight(target)`, and `viewImage(src)` (one
|
|
636
|
+
`<img>` on screen, redrawn as a JPEG at most 1568px long and attached to the result — `src` must be one this view
|
|
637
|
+
shows, as `readScreen({ images: true })` printed it, so the agent cannot have the browser fetch an address of its
|
|
638
|
+
own, and a `data:` one is refused). Declaring a hook tool under one of those names shadows the built-in, so reuse
|
|
639
|
+
them only to mean that. **There is no general-purpose wait**:
|
|
625
640
|
a built-in one was reachable on every screen and a model spent it on whatever key it liked, parking turns nobody
|
|
626
641
|
asked to park. Waiting belongs to the screen that knows what is worth waiting for — publish an `st.tool` beside the
|
|
627
642
|
control that starts the work, and let it await the work.
|
|
@@ -630,8 +645,8 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
630
645
|
awaits `ScreenSettle.wait()` — DOM quiescence, bounded, because the client router hands its promise to nobody —
|
|
631
646
|
and the session awaits it after every tool that did not declare `settle: false` before taking the change report.
|
|
632
647
|
Without it the report describes the moment before the change landed and the `readScreen` that follows reads the
|
|
633
|
-
page the user left. The reading built-ins (`readScreen`, `readState`, `highlight`) declare it,
|
|
634
|
-
costs 120ms of quiet at the very least and they change nothing a resource holds.
|
|
648
|
+
page the user left. The reading built-ins (`readScreen`, `readState`, `highlight`, `viewImage`) declare it,
|
|
649
|
+
because a wait costs 120ms of quiet at the very least and they change nothing a resource holds.
|
|
635
650
|
New tools and state from a fresh route are still only listed from the next turn: the catalogue is snapshotted
|
|
636
651
|
when the turn starts.
|
|
637
652
|
- **A tool that waits for its own work costs no model turns; one that returns early costs one round trip per
|
|
@@ -665,8 +680,8 @@ pages declaring one name never clash, and a registry kept outside the surface re
|
|
|
665
680
|
- **An image is always named, and its address rides only when asked for.** `readScreen` prints `[image: <alt>]`,
|
|
666
681
|
and `[image]` for one with no `alt` — dropping the unlabelled one left a card that renders a picture reading
|
|
667
682
|
exactly like a card that renders nothing, and "this page shows no image" is the one answer the screen cannot
|
|
668
|
-
support. `readScreen({ images: true })` appends `(<src>)` to each, for handing one to
|
|
669
|
-
picture; it is off by default because a gallery is one long URL per thumbnail, which is the read's whole budget
|
|
683
|
+
support. `readScreen({ images: true })` appends `(<src>)` to each, for handing one to `viewImage` or another tool
|
|
684
|
+
that takes a picture; it is off by default because a gallery is one long URL per thumbnail, which is the read's whole budget
|
|
670
685
|
spent on the part of the screen it can say the least about. A `data:` URL is never printed — that is the bytes
|
|
671
686
|
themselves rather than somewhere to fetch them.
|
|
672
687
|
- **A screen is only aimable if its names are printed.** `readScreen` writes `(#anchor)` beside a heading that
|
|
@@ -374,8 +374,8 @@ orchestration in `service`, client state in `store`; `Template` is forms, `Unit`
|
|
|
374
374
|
### Calls, Queries, Agents (summaries)
|
|
375
375
|
|
|
376
376
|
- **Wire (`transportRule`)**: a deadline is the endpoint's `{ timeout: <ms> }` (undeclared, the client quits at
|
|
377
|
-
30 s; the handler is not cancelled); `{ cache }` only on a `query` with no internal argument,
|
|
378
|
-
`invalidate(endpoint, args?)` on a plugged `EndpointCacheControl`; `{ rateLimit }` per endpoint,
|
|
377
|
+
30 s; the handler is not cancelled); `{ cache }` only on a `query` with no internal argument, capped at one day,
|
|
378
|
+
dropped early by `invalidate(endpoint, args?)` on a plugged `EndpointCacheControl`; `{ rateLimit }` per endpoint,
|
|
379
379
|
`option.setRateLimit` per app, `plug(RateLimit)` for a business key. `socketId` names a connection, not a caller. A
|
|
380
380
|
device proves itself with a `MachineToken`. Server to server, `fetch.clone({ origin })` opens no socket
|
|
381
381
|
without `connect: true`, retries through `retry`, and rethrows a remote `Err` as is — never wrapped or re-keyed.
|
|
@@ -417,6 +417,10 @@ orchestration in `service`, client state in `store`; `Template` is forms, `Unit`
|
|
|
417
417
|
`plug(StorageAdaptorRole)` · `use<T>()` only for a legacy `option.ts` singleton · `env(...)` for config.
|
|
418
418
|
- `memory(...)` takes a scalar or model class (`memory(Map, { of: CallStateInput })`); never hand-encode JSON into a
|
|
419
419
|
`String` memory.
|
|
420
|
+
- A file your own code stored through `plug(StorageAdaptorRole)` has its disk path at
|
|
421
|
+
`storage.resolveLocalPath?.(path, access)` (`fieldRule`): `null` means refused (answer 404, never fall back to
|
|
422
|
+
`readData()`), `undefined` means the adaptor keeps no local files, and a key that comes from a request passes
|
|
423
|
+
`access: "public"`. A `File` field is not one: `libs/shared` stores it through its own `StorageApi`.
|
|
420
424
|
- **A new adapter is an `adapt()` singleton in `srvkit/`**, injected with `plug(TheClass)`, key `as const`. Never
|
|
421
425
|
register it in `lib/option.ts` (it self-registers); never construct a `Logger` in it (`this.logger` exists);
|
|
422
426
|
lifecycle work in `override async onInit()`. A per-use value object is a plain class. Legacy
|
|
@@ -42,6 +42,13 @@ Generate client state coordination for a module. Store connects generated fetch
|
|
|
42
42
|
- A model whose form holds a large `field.visual` body can pass the 256KB per-draft cap, and a draft over it is
|
|
43
43
|
dropped with a warning rather than truncated.
|
|
44
44
|
|
|
45
|
+
## File Uploads
|
|
46
|
+
- `upload<Field>On<Model>(fileList, idx?)` is generated for every `File` field. `Field.Img` / `Imgs` / `File` /
|
|
47
|
+
`Files` handed the generated `set<Field>On<Model>` upload through it, so do not write an upload action.
|
|
48
|
+
- `<model>FormUploading` holds the uploads in flight per field (`{}` when idle). The generated submit paths save
|
|
49
|
+
nothing until it is empty; a hand-written submit reads `Object.keys(st.use.<model>FormUploading()).length`. Full
|
|
50
|
+
contract: `fieldRule`.
|
|
51
|
+
|
|
45
52
|
## Review Checklist
|
|
46
53
|
- The instruction points to current docs pages, not removed docs routes.
|
|
47
54
|
- Generated examples use current Akan builder APIs and scanner-friendly filenames.
|
|
@@ -571,6 +571,8 @@ akan sync automatically generates APIs across all layers. Only write custom logi
|
|
|
571
571
|
| `[slice]List`, `[slice]InitList`, `[slice]Insight`, `[slice]Selection`, `hasMoreOf[Slice]` | Slice states |
|
|
572
572
|
| `init[Slice](args)`, `refresh[Slice]()`, `setPageOf[Slice](page)`, `loadMoreOf[Slice]()` | Slice actions |
|
|
573
573
|
| `set[Field]On[Model](value)` | Auto-setters for each model field |
|
|
574
|
+
| `upload[Field]On[Model](fileList, idx?)` | Upload into a `File` field, scoped to the form that started it |
|
|
575
|
+
| `[model]FormUploading` | Pending uploads per `File` field (`{}` when idle); submits save nothing until empty |
|
|
574
576
|
|
|
575
577
|
**Rule**: Write custom store actions only for toast messages (`msg.loading`/`msg.success`) or multi-step workflows. State fields and CRUD actions are already auto-generated.
|
|
576
578
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
McpServerConfig,
|
|
6
6
|
McpSignIn,
|
|
7
7
|
AkanCodeCompaction2
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-dye8pnxx.js";
|
|
9
9
|
import {
|
|
10
10
|
stripAnsi
|
|
11
11
|
} from "./index-7md2g9vw.js";
|
|
@@ -319,7 +319,7 @@ import {
|
|
|
319
319
|
DefaultResourceLoader
|
|
320
320
|
} from "@earendil-works/pi-coding-agent";
|
|
321
321
|
import {
|
|
322
|
-
codeAgentClip as
|
|
322
|
+
codeAgentClip as codeAgentClip6,
|
|
323
323
|
codeAgentLabelChars as codeAgentLabelChars2,
|
|
324
324
|
codeAgentRenderAnswer,
|
|
325
325
|
codeAgentSessionName as codeAgentSessionName2
|
|
@@ -1396,11 +1396,120 @@ class SessionToolPack2 {
|
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
1398
|
// pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
|
|
1399
|
-
import {
|
|
1399
|
+
import { isIP } from "net";
|
|
1400
|
+
import { codeAgentClip as codeAgentClip4 } from "akanjs/common";
|
|
1400
1401
|
import { Type as Type6 } from "typebox";
|
|
1401
1402
|
|
|
1403
|
+
// pkgs/@akanjs/devkit/codeAgent/tools/DeepSeekWeb.ts
|
|
1404
|
+
import { codeAgentClip as codeAgentClip3 } from "akanjs/common";
|
|
1405
|
+
|
|
1406
|
+
class DeepSeekWeb {
|
|
1407
|
+
static baseUrl = "https://api.deepseek.com/v1/tools";
|
|
1408
|
+
static maxQueryChars = 128;
|
|
1409
|
+
static summaryChars = 600;
|
|
1410
|
+
static #retryable = {
|
|
1411
|
+
search: [408, 429, 500, 502, 503, 504],
|
|
1412
|
+
fetch: [429]
|
|
1413
|
+
};
|
|
1414
|
+
static #attempts = 3;
|
|
1415
|
+
#key;
|
|
1416
|
+
#baseUrl;
|
|
1417
|
+
constructor(key, baseUrl = DeepSeekWeb.baseUrl) {
|
|
1418
|
+
this.#key = key;
|
|
1419
|
+
this.#baseUrl = baseUrl;
|
|
1420
|
+
}
|
|
1421
|
+
static async fromRuntime(runtime, proxied) {
|
|
1422
|
+
if (proxied)
|
|
1423
|
+
return;
|
|
1424
|
+
const key = (await runtime.getAuth("deepseek").catch(() => {
|
|
1425
|
+
return;
|
|
1426
|
+
}))?.auth.apiKey;
|
|
1427
|
+
return key ? new DeepSeekWeb(key) : undefined;
|
|
1428
|
+
}
|
|
1429
|
+
async search(query, count, signal) {
|
|
1430
|
+
if ([...query].length > DeepSeekWeb.maxQueryChars)
|
|
1431
|
+
throw new Error(`DeepSeek search takes at most ${DeepSeekWeb.maxQueryChars} characters; shorten the query.`);
|
|
1432
|
+
const body = await this.#call("search", { query, count }, 20000, signal);
|
|
1433
|
+
const byUrl = new Map;
|
|
1434
|
+
for (const result of body.results ?? []) {
|
|
1435
|
+
const url = DeepSeekWeb.#cleanUrl(result.url);
|
|
1436
|
+
if (!byUrl.has(url))
|
|
1437
|
+
byUrl.set(url, { ...result, url });
|
|
1438
|
+
}
|
|
1439
|
+
return [...byUrl.values()].map((result) => {
|
|
1440
|
+
const date = result.date ? ` \xB7 ${result.date.slice(0, 10)}` : "";
|
|
1441
|
+
const summary = DeepSeekWeb.unescape(result.summary || result.snippet || "").replace(/\s+/g, " ").trim();
|
|
1442
|
+
return `- ${result.title}
|
|
1443
|
+
${result.url}${date}
|
|
1444
|
+
${codeAgentClip3(summary, DeepSeekWeb.summaryChars)}`;
|
|
1445
|
+
}).join(`
|
|
1446
|
+
`);
|
|
1447
|
+
}
|
|
1448
|
+
async fetch(url, signal) {
|
|
1449
|
+
const page = await this.#call("fetch", { url }, 30000, signal);
|
|
1450
|
+
return { title: page.title ?? "", content: DeepSeekWeb.unescape(page.content ?? "") };
|
|
1451
|
+
}
|
|
1452
|
+
static unescape(markdown) {
|
|
1453
|
+
const escaped = markdown.match(/\\[!-/:-@[-`{-~]/g)?.length ?? 0;
|
|
1454
|
+
const punctuation = markdown.match(/[!-/:-@[-`{-~]/g)?.length ?? 0;
|
|
1455
|
+
if (!escaped || escaped < (punctuation - escaped) / 2)
|
|
1456
|
+
return markdown;
|
|
1457
|
+
return markdown.replace(/\\([!-/:-@[-`{-~])/g, "$1");
|
|
1458
|
+
}
|
|
1459
|
+
async#call(tool, body, timeoutMs, signal) {
|
|
1460
|
+
for (let attempt = 1;; attempt++) {
|
|
1461
|
+
const response = await fetch(`${this.#baseUrl}/${tool}`, {
|
|
1462
|
+
method: "POST",
|
|
1463
|
+
headers: { authorization: `Bearer ${this.#key}`, "content-type": "application/json" },
|
|
1464
|
+
body: JSON.stringify(body),
|
|
1465
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs)
|
|
1466
|
+
});
|
|
1467
|
+
if (response.ok)
|
|
1468
|
+
return await response.json();
|
|
1469
|
+
if (!DeepSeekWeb.#retryable[tool].includes(response.status) || attempt >= DeepSeekWeb.#attempts) {
|
|
1470
|
+
const error = await response.json().catch(() => {
|
|
1471
|
+
return;
|
|
1472
|
+
});
|
|
1473
|
+
throw new Error(`DeepSeek ${tool} answered ${response.status}: ${error?.error?.message ?? response.statusText}`);
|
|
1474
|
+
}
|
|
1475
|
+
await DeepSeekWeb.#wait(DeepSeekWeb.#delayOf(response, attempt), signal);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
static #delayOf(response, attempt) {
|
|
1479
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
1480
|
+
return Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 5) * 1000 : 1000 * 2 ** (attempt - 1);
|
|
1481
|
+
}
|
|
1482
|
+
static #wait(ms, signal) {
|
|
1483
|
+
return new Promise((resolve) => {
|
|
1484
|
+
const timer = setTimeout(resolve, ms);
|
|
1485
|
+
signal?.addEventListener("abort", () => {
|
|
1486
|
+
clearTimeout(timer);
|
|
1487
|
+
resolve();
|
|
1488
|
+
}, { once: true });
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
static #cleanUrl(url) {
|
|
1492
|
+
try {
|
|
1493
|
+
const parsed = new URL(url);
|
|
1494
|
+
for (const name of [...parsed.searchParams.keys()])
|
|
1495
|
+
if (name.startsWith("utm_"))
|
|
1496
|
+
parsed.searchParams.delete(name);
|
|
1497
|
+
if (/^#\d+$/.test(parsed.hash))
|
|
1498
|
+
parsed.hash = "";
|
|
1499
|
+
return parsed.toString();
|
|
1500
|
+
} catch {
|
|
1501
|
+
return url;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
|
|
1402
1507
|
class WebToolPack2 {
|
|
1508
|
+
static pageChars = 1e4;
|
|
1509
|
+
static maxResults = 10;
|
|
1510
|
+
static #rememberedPages = 16;
|
|
1403
1511
|
#options;
|
|
1512
|
+
#pages = new Map;
|
|
1404
1513
|
constructor(options) {
|
|
1405
1514
|
this.#options = options;
|
|
1406
1515
|
}
|
|
@@ -1439,7 +1548,7 @@ class WebToolPack2 {
|
|
|
1439
1548
|
const names = [];
|
|
1440
1549
|
if (this.#options.profile.tools.web.fetch)
|
|
1441
1550
|
names.push("web_fetch");
|
|
1442
|
-
if (this.#options.profile.tools.web.search &&
|
|
1551
|
+
if (this.#options.profile.tools.web.search && this.#searcher())
|
|
1443
1552
|
names.push("web_search");
|
|
1444
1553
|
return names;
|
|
1445
1554
|
}
|
|
@@ -1451,58 +1560,124 @@ class WebToolPack2 {
|
|
|
1451
1560
|
#register(pi) {
|
|
1452
1561
|
if (this.#options.profile.tools.web.fetch)
|
|
1453
1562
|
this.#registerFetch(pi);
|
|
1454
|
-
const
|
|
1455
|
-
if (this.#options.profile.tools.web.search &&
|
|
1456
|
-
this.#registerSearch(pi,
|
|
1563
|
+
const searcher = this.#searcher();
|
|
1564
|
+
if (this.#options.profile.tools.web.search && searcher)
|
|
1565
|
+
this.#registerSearch(pi, searcher);
|
|
1457
1566
|
}
|
|
1458
1567
|
#registerFetch(pi) {
|
|
1459
1568
|
const allowHosts = this.#options.profile.network.allowHosts;
|
|
1460
1569
|
pi.registerTool({
|
|
1461
1570
|
name: "web_fetch",
|
|
1462
1571
|
label: "Fetch",
|
|
1463
|
-
description:
|
|
1572
|
+
description: `Fetch a URL and return its readable text, ${WebToolPack2.pageChars} characters at a time. A longer page ends with the offset that continues it.`,
|
|
1464
1573
|
promptSnippet: "web_fetch: fetch a URL and return its readable text",
|
|
1465
|
-
parameters: Type6.Object({
|
|
1466
|
-
|
|
1574
|
+
parameters: Type6.Object({
|
|
1575
|
+
url: Type6.String({ description: "Absolute http(s) URL." }),
|
|
1576
|
+
offset: Type6.Optional(Type6.Number({ description: "Character offset a previous call named, to read on." }))
|
|
1577
|
+
}),
|
|
1578
|
+
execute: async (_id, params, signal) => {
|
|
1467
1579
|
const refusal = WebToolPack2.#refuseHost(params.url, allowHosts);
|
|
1468
1580
|
if (refusal)
|
|
1469
1581
|
return { content: [{ type: "text", text: refusal }], details: undefined, isError: true };
|
|
1470
|
-
const
|
|
1471
|
-
const
|
|
1582
|
+
const offset = Math.max(0, Math.floor(params.offset ?? 0));
|
|
1583
|
+
const cached = offset ? this.#pages.get(params.url) : undefined;
|
|
1584
|
+
const page = cached ?? await this.#read(params.url, signal);
|
|
1585
|
+
if (page.ok)
|
|
1586
|
+
this.#remember(params.url, page);
|
|
1472
1587
|
return {
|
|
1473
|
-
content: [
|
|
1474
|
-
{ type: "text", text: `${response.status} ${params.url}
|
|
1475
|
-
|
|
1476
|
-
${codeAgentClip3(text, codeAgentOutputChars3)}` }
|
|
1477
|
-
],
|
|
1588
|
+
content: [{ type: "text", text: WebToolPack2.#window(params.url, page, offset) }],
|
|
1478
1589
|
details: undefined,
|
|
1479
|
-
isError: !
|
|
1590
|
+
isError: !page.ok
|
|
1480
1591
|
};
|
|
1481
1592
|
}
|
|
1482
1593
|
});
|
|
1483
1594
|
}
|
|
1484
|
-
#registerSearch(pi,
|
|
1595
|
+
#registerSearch(pi, searcher) {
|
|
1596
|
+
const hint = searcher.name === "deepseek" ? ` Keep the query within ${DeepSeekWeb.maxQueryChars} characters; site:, -site: and quoted phrases work.` : "";
|
|
1485
1597
|
pi.registerTool({
|
|
1486
1598
|
name: "web_search",
|
|
1487
1599
|
label: "Search",
|
|
1488
|
-
description: `Search the web (${
|
|
1600
|
+
description: `Search the web (${searcher.name}) and return the top results as title, url and summary.${hint}`,
|
|
1489
1601
|
promptSnippet: "web_search: search the web and return titles, urls and snippets",
|
|
1490
1602
|
parameters: Type6.Object({
|
|
1491
1603
|
query: Type6.String(),
|
|
1492
|
-
count: Type6.Optional(Type6.Number({ description:
|
|
1604
|
+
count: Type6.Optional(Type6.Number({ description: `How many results, default 5, at most ${WebToolPack2.maxResults}.` }))
|
|
1493
1605
|
}),
|
|
1494
|
-
execute: async (_id, params) => {
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1606
|
+
execute: async (_id, params, signal) => {
|
|
1607
|
+
const count = Math.min(Math.max(Math.round(params.count ?? 5), 1), WebToolPack2.maxResults);
|
|
1608
|
+
try {
|
|
1609
|
+
const results = await searcher.search(params.query, count, signal);
|
|
1610
|
+
return {
|
|
1611
|
+
content: [{ type: "text", text: codeAgentClip4(results || "(no results)", WebToolPack2.pageChars) }],
|
|
1612
|
+
details: undefined
|
|
1613
|
+
};
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1616
|
+
return { content: [{ type: "text", text: message }], details: undefined, isError: true };
|
|
1617
|
+
}
|
|
1501
1618
|
}
|
|
1502
1619
|
});
|
|
1503
1620
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1621
|
+
#searcher() {
|
|
1622
|
+
const provider = WebToolPack2.providers.find((entry) => !!process.env[entry.envKey]);
|
|
1623
|
+
if (provider)
|
|
1624
|
+
return {
|
|
1625
|
+
name: provider.name,
|
|
1626
|
+
search: (query, count) => provider.search(process.env[provider.envKey] ?? "", query, count)
|
|
1627
|
+
};
|
|
1628
|
+
const deepseek = this.#options.deepseek;
|
|
1629
|
+
return deepseek ? { name: "deepseek", search: (query, count, signal) => deepseek.search(query, count, signal) } : undefined;
|
|
1630
|
+
}
|
|
1631
|
+
async#read(url, signal) {
|
|
1632
|
+
const deepseek = this.#options.deepseek;
|
|
1633
|
+
if (deepseek && !WebToolPack2.#isLocal(new URL(url).hostname)) {
|
|
1634
|
+
try {
|
|
1635
|
+
const page = await deepseek.fetch(url, signal);
|
|
1636
|
+
return { status: 200, ok: true, title: page.title, text: page.content };
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
if (signal?.aborted)
|
|
1639
|
+
throw error;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
const response = await fetch(url, {
|
|
1643
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(20000)]) : AbortSignal.timeout(20000),
|
|
1644
|
+
redirect: "follow"
|
|
1645
|
+
});
|
|
1646
|
+
return { status: response.status, ok: response.ok, title: "", text: WebToolPack2.#readable(await response.text()) };
|
|
1647
|
+
}
|
|
1648
|
+
#remember(url, page) {
|
|
1649
|
+
this.#pages.delete(url);
|
|
1650
|
+
this.#pages.set(url, page);
|
|
1651
|
+
const oldest = this.#pages.keys().next().value;
|
|
1652
|
+
if (this.#pages.size > WebToolPack2.#rememberedPages && oldest)
|
|
1653
|
+
this.#pages.delete(oldest);
|
|
1654
|
+
}
|
|
1655
|
+
static #window(url, page, offset) {
|
|
1656
|
+
const head = [`${page.status} ${url}`, page.title].filter(Boolean).join(`
|
|
1657
|
+
`);
|
|
1658
|
+
const total = page.text.length;
|
|
1659
|
+
if (offset && offset >= total)
|
|
1660
|
+
return `${head}
|
|
1661
|
+
|
|
1662
|
+
Offset ${offset} is past the end; the page has ${total} characters.`;
|
|
1663
|
+
const end = Math.min(offset + WebToolPack2.pageChars, total);
|
|
1664
|
+
const rest = end < total ? `
|
|
1665
|
+
|
|
1666
|
+
[characters ${offset}\u2013${end} of ${total}; offset ${end} reads on]` : "";
|
|
1667
|
+
return `${head}
|
|
1668
|
+
|
|
1669
|
+
${page.text.slice(offset, end)}${rest}`;
|
|
1670
|
+
}
|
|
1671
|
+
static #isLocal(hostname) {
|
|
1672
|
+
const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
1673
|
+
const family = isIP(host);
|
|
1674
|
+
if (family === 6)
|
|
1675
|
+
return host === "::1" || /^(f[cd]|fe[89ab])/.test(host);
|
|
1676
|
+
if (family === 4) {
|
|
1677
|
+
const [a = 0, b = 0] = host.split(".").map(Number);
|
|
1678
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
|
|
1679
|
+
}
|
|
1680
|
+
return !host.includes(".") || /\.(localhost|local|internal|lan|home\.arpa)$/.test(host);
|
|
1506
1681
|
}
|
|
1507
1682
|
static #refuseHost(url, allowHosts) {
|
|
1508
1683
|
let parsed;
|
|
@@ -1534,7 +1709,7 @@ class AkanCodePlugins2 {
|
|
|
1534
1709
|
});
|
|
1535
1710
|
const dispose = () => mcp?.close();
|
|
1536
1711
|
const mcpStatus = mcp?.status ?? [];
|
|
1537
|
-
const web = new WebToolPack2({ profile: options.profile });
|
|
1712
|
+
const web = new WebToolPack2({ profile: options.profile, deepseek: options.deepseek });
|
|
1538
1713
|
const sessions = new SessionToolPack2({
|
|
1539
1714
|
workspaceRoot: options.workspace.workspaceRoot,
|
|
1540
1715
|
cwd: options.cwd,
|
|
@@ -1669,9 +1844,9 @@ class CodeAgentAsks2 {
|
|
|
1669
1844
|
|
|
1670
1845
|
// pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentEventMapper.ts
|
|
1671
1846
|
import {
|
|
1672
|
-
codeAgentClip as
|
|
1847
|
+
codeAgentClip as codeAgentClip5,
|
|
1673
1848
|
codeAgentLabelChars,
|
|
1674
|
-
codeAgentOutputChars as
|
|
1849
|
+
codeAgentOutputChars as codeAgentOutputChars3
|
|
1675
1850
|
} from "akanjs/common";
|
|
1676
1851
|
|
|
1677
1852
|
// pkgs/@akanjs/devkit/codeAgent/agent/stringArg.ts
|
|
@@ -1731,7 +1906,7 @@ class CodeAgentEventMapper2 {
|
|
|
1731
1906
|
type: "tool_progress",
|
|
1732
1907
|
turnId: this.#turnId,
|
|
1733
1908
|
toolCallId: event.toolCallId,
|
|
1734
|
-
text:
|
|
1909
|
+
text: codeAgentClip5(CodeAgentEventMapper2.#renderToolText(event.partialResult), codeAgentLabelChars)
|
|
1735
1910
|
}
|
|
1736
1911
|
];
|
|
1737
1912
|
case "tool_execution_end":
|
|
@@ -1802,8 +1977,8 @@ class CodeAgentEventMapper2 {
|
|
|
1802
1977
|
turnId: this.#turnId,
|
|
1803
1978
|
tool,
|
|
1804
1979
|
outcome: reason ? "blocked" : event.isError ? "error" : "ok",
|
|
1805
|
-
output:
|
|
1806
|
-
truncated: full.length >
|
|
1980
|
+
output: codeAgentClip5(full, codeAgentOutputChars3),
|
|
1981
|
+
truncated: full.length > codeAgentOutputChars3
|
|
1807
1982
|
};
|
|
1808
1983
|
}
|
|
1809
1984
|
noteOutcome(reason) {
|
|
@@ -1832,7 +2007,7 @@ class CodeAgentEventMapper2 {
|
|
|
1832
2007
|
name,
|
|
1833
2008
|
op: toolOps[name] ?? "other",
|
|
1834
2009
|
...path ? { path } : {},
|
|
1835
|
-
title:
|
|
2010
|
+
title: codeAgentClip5(`${name}${CodeAgentEventMapper2.#renderArgs(args)}`, codeAgentLabelChars)
|
|
1836
2011
|
};
|
|
1837
2012
|
}
|
|
1838
2013
|
#mapDelta(event) {
|
|
@@ -1859,7 +2034,7 @@ class CodeAgentEventMapper2 {
|
|
|
1859
2034
|
if (!entries.length)
|
|
1860
2035
|
return "";
|
|
1861
2036
|
const rendered = entries.map(([key, value]) => `${key}=${CodeAgentEventMapper2.#renderArgValue(value)}`).join(" ");
|
|
1862
|
-
return `(${
|
|
2037
|
+
return `(${codeAgentClip5(rendered, 120)})`;
|
|
1863
2038
|
}
|
|
1864
2039
|
static #renderArgValue(value) {
|
|
1865
2040
|
if (Array.isArray(value))
|
|
@@ -1868,7 +2043,7 @@ class CodeAgentEventMapper2 {
|
|
|
1868
2043
|
const keys = Object.keys(value).length;
|
|
1869
2044
|
return `{${keys} key${keys === 1 ? "" : "s"}}`;
|
|
1870
2045
|
}
|
|
1871
|
-
return
|
|
2046
|
+
return codeAgentClip5(String(value), 60);
|
|
1872
2047
|
}
|
|
1873
2048
|
static #renderContent(content) {
|
|
1874
2049
|
if (typeof content === "string")
|
|
@@ -2320,7 +2495,8 @@ class CodeAgent2 {
|
|
|
2320
2495
|
const agent = new CodeAgent2(options.profile);
|
|
2321
2496
|
const autoCompactAt = AkanCodeCompaction2.normalize(options.autoCompactAt);
|
|
2322
2497
|
const settingsManager = AkanCodeServices2.settings();
|
|
2323
|
-
const
|
|
2498
|
+
const proxy = CodeAgentProxy.fromEnv(options.profile);
|
|
2499
|
+
const modelRuntime = await AkanCodeServices2.runtime(workspaceRoot, proxy);
|
|
2324
2500
|
const model = await akanCodeModel2(modelRuntime, options.model);
|
|
2325
2501
|
const compaction = settingsManager.getCompactionSettings(model);
|
|
2326
2502
|
const modelWarnings = akanCodeModelWarnings2(model, compaction.reserveTokens + compaction.keepRecentTokens);
|
|
@@ -2330,6 +2506,7 @@ class CodeAgent2 {
|
|
|
2330
2506
|
profile: options.profile,
|
|
2331
2507
|
apps: options.apps ?? [],
|
|
2332
2508
|
canSeeImages: akanCodeModelSupportsImages2(model),
|
|
2509
|
+
deepseek: await DeepSeekWeb.fromRuntime(modelRuntime, !!proxy),
|
|
2333
2510
|
depth: options.depth ?? 0,
|
|
2334
2511
|
...options.subagentSpend ? { subagentSpend: options.subagentSpend } : {},
|
|
2335
2512
|
currentSessionId: () => agent.sessionId,
|
|
@@ -2724,7 +2901,7 @@ Continue the task.`);
|
|
|
2724
2901
|
approvalId,
|
|
2725
2902
|
toolCallId,
|
|
2726
2903
|
name,
|
|
2727
|
-
summary:
|
|
2904
|
+
summary: codeAgentClip6(summary, codeAgentLabelChars2),
|
|
2728
2905
|
policy: this.#profile.approval
|
|
2729
2906
|
};
|
|
2730
2907
|
this.#emit({ type: "approval", request });
|
|
@@ -179,7 +179,7 @@ class SubagentPool2 {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
async#run(id, kind, prompt, signal) {
|
|
182
|
-
const { CodeAgent } = await import("./CodeAgent-
|
|
182
|
+
const { CodeAgent } = await import("./CodeAgent-n3tzjm91.js");
|
|
183
183
|
const budget = this.#options.parent.tools.subagent;
|
|
184
184
|
const child = SubagentPool2.childOf(this.#options.parent, kind);
|
|
185
185
|
const agent = await CodeAgent.create({
|
package/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import path from "path";
|
|
|
15
15
|
var commandModules = {
|
|
16
16
|
workspace: async () => (await import("./workspace.command-6qfgq4b4.js")).WorkspaceCommand,
|
|
17
17
|
agent: async () => (await import("./agent.command-3qxf4xcj.js")).AgentCommand,
|
|
18
|
-
code: async () => (await import("./code.command-
|
|
18
|
+
code: async () => (await import("./code.command-130bdmhe.js")).CodeCommand,
|
|
19
19
|
application: async () => (await import("./application.command-m1kcd7jf.js")).ApplicationCommand,
|
|
20
20
|
library: async () => (await import("./library.command-erp2f56n.js")).LibraryCommand,
|
|
21
21
|
localRegistry: async () => (await import("./localRegistry.command-y176p558.js")).LocalRegistryCommand,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/cli",
|
|
3
|
-
"version": "3.1.0-rc.
|
|
3
|
+
"version": "3.1.0-rc.14",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@earendil-works/pi-tui": "1.3.0",
|
|
40
40
|
"@inquirer/prompts": "^8.7.3",
|
|
41
41
|
"@tailwindcss/node": "^4.3.3",
|
|
42
|
-
"akanjs": "3.1.0-rc.
|
|
42
|
+
"akanjs": "3.1.0-rc.14",
|
|
43
43
|
"chalk": "^6.0.1",
|
|
44
44
|
"commander": "^15.0.0",
|
|
45
45
|
"dayjs": "^1.11.23",
|