sakura-agent 0.1.0 → 0.1.1

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.
Files changed (91) hide show
  1. package/README.md +144 -78
  2. package/README.zh-CN.md +145 -79
  3. package/THIRD_PARTY_NOTICES.md +8 -58
  4. package/dist/packages/agent/src/index.d.ts +49 -11
  5. package/dist/packages/agent/src/index.js +50 -55
  6. package/dist/packages/agent/src/restrict-tools.d.ts +6 -0
  7. package/dist/packages/agent/src/restrict-tools.js +11 -0
  8. package/dist/packages/agent/src/system-prompt.d.ts +5 -0
  9. package/dist/packages/agent/src/system-prompt.js +1 -1
  10. package/dist/packages/agent/src/wake.d.ts +41 -0
  11. package/dist/packages/agent/src/wake.js +71 -0
  12. package/dist/packages/env/src/confine.d.ts +13 -0
  13. package/dist/packages/env/src/confine.js +40 -0
  14. package/dist/packages/env/src/connect-ssh.d.ts +33 -0
  15. package/dist/packages/env/src/connect-ssh.js +132 -0
  16. package/dist/packages/env/src/index.d.ts +5 -1
  17. package/dist/packages/env/src/index.js +3 -1
  18. package/dist/packages/env/src/local.d.ts +11 -2
  19. package/dist/packages/env/src/local.js +42 -10
  20. package/dist/packages/env/src/remote.d.ts +17 -1
  21. package/dist/packages/env/src/remote.js +54 -4
  22. package/dist/packages/evals/src/bfcl.d.ts +24 -0
  23. package/dist/packages/evals/src/bfcl.js +71 -0
  24. package/dist/packages/evals/src/index.d.ts +3 -0
  25. package/dist/packages/evals/src/index.js +2 -0
  26. package/dist/packages/evals/src/png.d.ts +2 -0
  27. package/dist/packages/evals/src/png.js +30 -0
  28. package/dist/packages/evals/src/pool.d.ts +3 -0
  29. package/dist/packages/evals/src/pool.js +22 -0
  30. package/dist/packages/evals/src/providers.d.ts +13 -0
  31. package/dist/packages/evals/src/providers.js +23 -0
  32. package/dist/packages/kernel/src/compaction.d.ts +19 -0
  33. package/dist/packages/kernel/src/compaction.js +12 -2
  34. package/dist/packages/kernel/src/goal.d.ts +10 -1
  35. package/dist/packages/kernel/src/goal.js +28 -0
  36. package/dist/packages/kernel/src/hooks.d.ts +5 -0
  37. package/dist/packages/kernel/src/hooks.js +21 -1
  38. package/dist/packages/kernel/src/index.d.ts +3 -2
  39. package/dist/packages/kernel/src/index.js +1 -0
  40. package/dist/packages/kernel/src/loop.d.ts +28 -4
  41. package/dist/packages/kernel/src/loop.js +69 -47
  42. package/dist/packages/kernel/src/types.d.ts +25 -4
  43. package/dist/packages/kernel/src/usage.d.ts +3 -0
  44. package/dist/packages/kernel/src/usage.js +16 -0
  45. package/dist/packages/llm/src/chat/request.js +22 -4
  46. package/dist/packages/llm/src/chat/retry.d.ts +1 -1
  47. package/dist/packages/llm/src/chat/retry.js +1 -1
  48. package/dist/packages/llm/src/index.js +7 -2
  49. package/dist/packages/plugins/context/src/index.d.ts +2 -1
  50. package/dist/packages/plugins/context/src/index.js +2 -1
  51. package/dist/packages/plugins/limits/src/index.d.ts +4 -1
  52. package/dist/packages/plugins/redaction/src/index.d.ts +3 -0
  53. package/dist/packages/plugins/redaction/src/index.js +3 -0
  54. package/dist/packages/plugins/redaction/src/rules.gen.js +1 -1
  55. package/dist/packages/plugins/review/src/chain.d.ts +2 -0
  56. package/dist/packages/plugins/review/src/chain.js +2 -1
  57. package/dist/packages/plugins/review/src/hard-rules.d.ts +6 -0
  58. package/dist/packages/plugins/review/src/hard-rules.js +58 -25
  59. package/dist/packages/plugins/review/src/index.d.ts +13 -0
  60. package/dist/packages/plugins/review/src/index.js +9 -5
  61. package/dist/packages/plugins/tools/src/builtin/bash.js +24 -10
  62. package/dist/packages/plugins/toolset/src/codemode.d.ts +17 -6
  63. package/dist/packages/plugins/toolset/src/codemode.js +84 -26
  64. package/dist/packages/plugins/toolset/src/index.d.ts +1 -1
  65. package/dist/packages/plugins/toolset/src/index.js +1 -1
  66. package/dist/packages/plugins/trace/src/index.d.ts +27 -0
  67. package/dist/packages/plugins/trace/src/index.js +29 -0
  68. package/dist/packages/plugins/trace/src/modules.d.ts +6 -0
  69. package/dist/packages/plugins/trace/src/modules.js +28 -0
  70. package/dist/packages/session/src/file-log.d.ts +6 -1
  71. package/dist/packages/session/src/file-log.js +45 -25
  72. package/dist/packages/session/src/fork.d.ts +16 -0
  73. package/dist/packages/session/src/fork.js +70 -0
  74. package/dist/packages/session/src/index.d.ts +2 -0
  75. package/dist/packages/session/src/index.js +1 -0
  76. package/dist/packages/session/src/log-core.d.ts +37 -0
  77. package/dist/packages/session/src/log-core.js +47 -0
  78. package/dist/packages/session/src/memory-log.d.ts +3 -12
  79. package/dist/packages/session/src/memory-log.js +3 -23
  80. package/dist/packages/session/src/projection.js +2 -0
  81. package/dist/packages/testing/src/faux.d.ts +36 -0
  82. package/dist/packages/testing/src/faux.js +69 -0
  83. package/dist/packages/testing/src/index.d.ts +4 -0
  84. package/dist/packages/testing/src/index.js +2 -0
  85. package/dist/packages/testing/src/rng.d.ts +7 -0
  86. package/dist/packages/testing/src/rng.js +17 -0
  87. package/dist/sdk/index.d.ts +11 -3
  88. package/dist/sdk/index.js +5 -1
  89. package/dist/sdk/testing.d.ts +2 -0
  90. package/dist/sdk/testing.js +2 -0
  91. package/package.json +5 -1
@@ -0,0 +1,47 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { projectMessages } from "./projection.js";
3
+ /**
4
+ * The one implementation of "a log": entries in a tree, one leaf, appends in order. The log is only as durable as its storage, and a log and its
5
+ * storage never disagree: an entry joins the tree after it is saved, and an append that fails changes nothing and does not stop the ones after it.
6
+ */
7
+ export function createLog(tree, storage, options = {}) {
8
+ const now = options.now ?? Date.now;
9
+ const newId = options.newId ?? (() => randomUUID().slice(0, 8));
10
+ // Appends run one after another, each starting from where the one before ended. A failed one is reported to its caller and not to the next.
11
+ let queue = Promise.resolve();
12
+ return {
13
+ /** Resolves when everything started so far has been saved or has failed. */
14
+ settled: () => queue.then(() => undefined, () => undefined),
15
+ append(body) {
16
+ const run = queue.then(async () => {
17
+ const entry = { ...body, id: newId(), parentId: tree.leaf(), ts: now() };
18
+ await storage.saveEntry(entry);
19
+ tree.add(entry);
20
+ return entry;
21
+ });
22
+ queue = run.catch(() => undefined);
23
+ return run;
24
+ },
25
+ async branch() {
26
+ return tree.branch();
27
+ },
28
+ async project(upTo) {
29
+ const branch = tree.branch();
30
+ return projectMessages(upTo === undefined
31
+ ? branch
32
+ : branch.slice(0, Math.max(0, branch.findIndex((entry) => entry.id === upTo))));
33
+ },
34
+ /**
35
+ * Takes effect at once. Entries that were already waiting to be saved follow the new end, so do not rewind while appends are in flight
36
+ * (the kernel does not: one run at a time, and it waits for each append).
37
+ */
38
+ rewindTo(entryId) {
39
+ const before = tree.leaf();
40
+ tree.moveLeaf(entryId);
41
+ if (entryId === before)
42
+ return;
43
+ queue = queue.then(() => storage.saveRewind(entryId)).catch(() => undefined);
44
+ },
45
+ leaf: () => tree.leaf(),
46
+ };
47
+ }
@@ -1,15 +1,6 @@
1
- import type { Log, LogEntry } from "../../kernel/src/index.js";
2
- export interface LogOptions {
3
- now?: () => number;
4
- newId?: () => string;
5
- }
6
- /** A session log that can go back to an earlier entry and carry on from there. */
7
- export interface RewindableLog extends Log {
8
- /** Moves the end of the conversation back to an earlier entry (or to the very start with null). Later entries start a new branch. */
9
- rewindTo(entryId: string | null): void;
10
- /** Id of the entry the next one will follow. */
11
- leaf(): string | null;
12
- }
1
+ import type { LogEntry } from "../../kernel/src/index.js";
2
+ import type { LogOptions, RewindableLog } from "./log-core.js";
3
+ export type { LogOptions, RewindableLog } from "./log-core.js";
13
4
  /** A log that lives only in memory (or wraps entries that were loaded elsewhere). */
14
5
  export declare function createMemoryLog(options?: LogOptions & {
15
6
  entries?: readonly LogEntry[];
@@ -1,27 +1,7 @@
1
- import { randomUUID } from "node:crypto";
2
- import { projectMessages } from "./projection.js";
1
+ import { createLog } from "./log-core.js";
3
2
  import { createTree } from "./tree.js";
4
3
  /** A log that lives only in memory (or wraps entries that were loaded elsewhere). */
5
4
  export function createMemoryLog(options = {}) {
6
- const now = options.now ?? Date.now;
7
- const newId = options.newId ?? (() => randomUUID().slice(0, 8));
8
- const tree = createTree(options.entries ?? []);
9
- return {
10
- async append(body) {
11
- const entry = { ...body, id: newId(), parentId: tree.leaf(), ts: now() };
12
- tree.add(entry);
13
- return entry;
14
- },
15
- async branch() {
16
- return tree.branch();
17
- },
18
- async project(upTo) {
19
- const branch = tree.branch();
20
- return projectMessages(upTo === undefined
21
- ? branch
22
- : branch.slice(0, Math.max(0, branch.findIndex((entry) => entry.id === upTo))));
23
- },
24
- rewindTo: (entryId) => tree.moveLeaf(entryId),
25
- leaf: () => tree.leaf(),
26
- };
5
+ const { settled: _settled, ...log } = createLog(createTree(options.entries ?? []), { saveEntry: async () => undefined, saveRewind: async () => undefined }, options);
6
+ return log;
27
7
  }
@@ -64,6 +64,8 @@ export function projectMessages(branch) {
64
64
  const entry = branch[index];
65
65
  if (entry?.type !== "custom_message" || !entry.pinned)
66
66
  continue;
67
+ if (edits.get(entry.id) === null)
68
+ continue; // a pinned message can be withdrawn too (an earlier goal)
67
69
  const message = entryToMessage(entry);
68
70
  if (message)
69
71
  messages.push(message);
@@ -0,0 +1,36 @@
1
+ import type { LlmClient, LlmRequest, ProviderError, StopReason, Usage } from "../../kernel/src/index.js";
2
+ export interface FauxTurn {
3
+ text?: string;
4
+ reasoning?: string;
5
+ toolCalls?: Array<{
6
+ name: string;
7
+ args: unknown;
8
+ id?: string;
9
+ }>;
10
+ /** Cut the output off in the middle of this tool call (finish_reason "length"). */
11
+ truncatedToolCall?: {
12
+ name: string;
13
+ id?: string;
14
+ };
15
+ usage?: Partial<Usage>;
16
+ stop?: StopReason;
17
+ /** Fail the request instead of answering. */
18
+ error?: ProviderError;
19
+ /** Never answer; wait until the request is aborted. */
20
+ hang?: boolean;
21
+ /** Send this text, then a restart (as an adapter does after a dropped connection), then the real reply. */
22
+ droppedFirst?: string;
23
+ /** Split text into pieces of this many characters (default: one piece). */
24
+ chunkSize?: number;
25
+ }
26
+ export type FauxScript = FauxTurn[] | ((request: LlmRequest, index: number) => FauxTurn);
27
+ export interface FauxClient extends LlmClient {
28
+ /** Every request received, in order. */
29
+ requests: LlmRequest[];
30
+ }
31
+ export interface FauxOptions {
32
+ contextWindow?: number;
33
+ supportsImages?: boolean;
34
+ }
35
+ /** A scripted stand-in for a model: replays the given turns, one per request. */
36
+ export declare function createFauxClient(script: FauxScript, options?: FauxOptions): FauxClient;
@@ -0,0 +1,69 @@
1
+ function abortError() {
2
+ const error = new Error("aborted");
3
+ error.name = "AbortError";
4
+ return error;
5
+ }
6
+ function waitForAbort(signal) {
7
+ return new Promise((_, reject) => {
8
+ if (signal.aborted)
9
+ reject(abortError());
10
+ signal.addEventListener("abort", () => reject(abortError()), { once: true });
11
+ });
12
+ }
13
+ function estimateTokens(value) {
14
+ return Math.ceil(JSON.stringify(value).length / 4);
15
+ }
16
+ function* pieces(value, size) {
17
+ if (!size || size >= value.length) {
18
+ yield value;
19
+ return;
20
+ }
21
+ for (let start = 0; start < value.length; start += size)
22
+ yield value.slice(start, start + size);
23
+ }
24
+ /** A scripted stand-in for a model: replays the given turns, one per request. */
25
+ export function createFauxClient(script, options = {}) {
26
+ const requests = [];
27
+ let callCount = 0;
28
+ return {
29
+ requests,
30
+ model: { id: "faux", contextWindow: options.contextWindow ?? 100_000, supportsImages: options.supportsImages ?? true },
31
+ async *stream(request, signal) {
32
+ requests.push(request);
33
+ const index = callCount++;
34
+ const turn = typeof script === "function" ? script(request, index) : script[index];
35
+ if (!turn)
36
+ throw new Error(`faux script has no turn for request ${index + 1}`);
37
+ if (signal.aborted)
38
+ throw abortError();
39
+ if (turn.error)
40
+ throw turn.error;
41
+ if (turn.hang)
42
+ await waitForAbort(signal);
43
+ if (turn.droppedFirst) {
44
+ yield { type: "text_delta", delta: turn.droppedFirst };
45
+ yield { type: "restart" };
46
+ }
47
+ if (turn.reasoning)
48
+ yield { type: "reasoning_delta", delta: turn.reasoning };
49
+ if (turn.text)
50
+ for (const piece of pieces(turn.text, turn.chunkSize))
51
+ yield { type: "text_delta", delta: piece };
52
+ let toolCount = 0;
53
+ for (const call of turn.toolCalls ?? []) {
54
+ toolCount++;
55
+ yield { type: "tool_call", call: { id: call.id ?? `call_${index + 1}_${toolCount}`, name: call.name, args: call.args } };
56
+ }
57
+ if (turn.truncatedToolCall) {
58
+ yield { type: "tool_call_truncated", id: turn.truncatedToolCall.id ?? `call_${index + 1}_cut`, name: turn.truncatedToolCall.name };
59
+ }
60
+ const output = estimateTokens([turn.text, turn.reasoning, turn.toolCalls]);
61
+ yield {
62
+ type: "usage",
63
+ usage: { input: estimateTokens(request.messages), output, cacheRead: 0, cacheWrite: 0, ...turn.usage },
64
+ };
65
+ const stop = turn.stop ?? (turn.truncatedToolCall ? "length" : toolCount > 0 ? "tool_use" : "stop");
66
+ yield { type: "done", stop };
67
+ },
68
+ };
69
+ }
@@ -0,0 +1,4 @@
1
+ export { createRng } from "./rng.js";
2
+ export type { Rng } from "./rng.js";
3
+ export { createFauxClient } from "./faux.js";
4
+ export type { FauxClient, FauxOptions, FauxScript, FauxTurn } from "./faux.js";
@@ -0,0 +1,2 @@
1
+ export { createRng } from "./rng.js";
2
+ export { createFauxClient } from "./faux.js";
@@ -0,0 +1,7 @@
1
+ export interface Rng {
2
+ next(): number;
3
+ int(minInclusive: number, maxInclusive: number): number;
4
+ pick<T>(items: readonly T[]): T;
5
+ chance(probability: number): boolean;
6
+ }
7
+ export declare function createRng(seed: number): Rng;
@@ -0,0 +1,17 @@
1
+ // Small seeded random generator (mulberry32) so generated test cases are reproducible.
2
+ export function createRng(seed) {
3
+ let state = seed >>> 0;
4
+ const next = () => {
5
+ state = (state + 0x6d2b79f5) >>> 0;
6
+ let t = state;
7
+ t = Math.imul(t ^ (t >>> 15), t | 1);
8
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
9
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
10
+ };
11
+ return {
12
+ next,
13
+ int: (min, max) => min + Math.floor(next() * (max - min + 1)),
14
+ pick: (items) => items[Math.floor(next() * items.length)],
15
+ chance: (probability) => next() < probability,
16
+ };
17
+ }
@@ -1,10 +1,18 @@
1
1
  export { buildSystemPrompt, bundle, compose, CompositionError, corePlugins, createAgent } from "../packages/agent/src/index.js";
2
2
  export type { Agent, AgentConfig, Composable, ModelConfig } from "../packages/agent/src/index.js";
3
- export { createLocalEnvironment, createRemoteEnvironment } from "../packages/env/src/index.js";
4
- export type { RemoteRunner } from "../packages/env/src/index.js";
5
- export type { Content, Environment, Event, LlmClient, Plugin, RunHandle, RunResult, Tool, ToolContext, ToolResult } from "../packages/kernel/src/index.js";
3
+ export { createLocalEnvironment, createRemoteEnvironment, createSshEnvironment, OutsideDirectoryError, RemoteConnectionError } from "../packages/env/src/index.js";
4
+ export type { LocalOptions, RemoteRunner, SshOptions } from "../packages/env/src/index.js";
5
+ export type { CompactionConfig, Content, ContextStatus, Decision, EntryBody, Environment, Event, EventTransform, ExecResult, FileInfo, GoalRecord, GoalStatus, HookContext, HookName, Hooks, LlmClient, LlmEvent, LlmRequest, Log, LogEntry, Message, ModelInfo, PauseReason, Plugin, PluginApi, PrepareContext, ProcessHandle, Prompts, ProviderErrorKind, Role, RunEndReason, RunHandle, RunOptions, RunResult, StopReason, ThinkingLevel, Tool, ToolCall, ToolContext, ToolDecl, ToolEffect, ToolResult, TurnInfo, Usage, UsageTotals, UserInput, } from "../packages/kernel/src/index.js";
6
+ export { FatalHookError, ProviderError } from "../packages/kernel/src/index.js";
6
7
  export { compatPresets, createChatClient } from "../packages/llm/src/index.js";
7
8
  export type { ChatClientOptions, ChatCompat } from "../packages/llm/src/index.js";
9
+ export { contextPlugin } from "../packages/plugins/context/src/index.js";
10
+ export type { ContextOptions, ContextPlugin } from "../packages/plugins/context/src/index.js";
8
11
  export { limitsPlugin } from "../packages/plugins/limits/src/index.js";
12
+ export { resiliencePlugin } from "../packages/plugins/resilience/src/index.js";
13
+ export type { FixOptions, ResilienceOptions } from "../packages/plugins/resilience/src/index.js";
9
14
  export { redactionPlugin } from "../packages/plugins/redaction/src/index.js";
10
15
  export { reviewPlugin } from "../packages/plugins/review/src/index.js";
16
+ export type { McpServerConfig, McpServerStatus } from "../packages/plugins/toolset/src/index.js";
17
+ export { toolsPlugin } from "../packages/plugins/tools/src/index.js";
18
+ export type { ToolsPluginOptions } from "../packages/plugins/tools/src/index.js";
package/dist/sdk/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  export { buildSystemPrompt, bundle, compose, CompositionError, corePlugins, createAgent } from "../packages/agent/src/index.js";
2
- export { createLocalEnvironment, createRemoteEnvironment } from "../packages/env/src/index.js";
2
+ export { createLocalEnvironment, createRemoteEnvironment, createSshEnvironment, OutsideDirectoryError, RemoteConnectionError } from "../packages/env/src/index.js";
3
+ export { FatalHookError, ProviderError } from "../packages/kernel/src/index.js";
3
4
  export { compatPresets, createChatClient } from "../packages/llm/src/index.js";
5
+ export { contextPlugin } from "../packages/plugins/context/src/index.js";
4
6
  export { limitsPlugin } from "../packages/plugins/limits/src/index.js";
7
+ export { resiliencePlugin } from "../packages/plugins/resilience/src/index.js";
5
8
  export { redactionPlugin } from "../packages/plugins/redaction/src/index.js";
6
9
  export { reviewPlugin } from "../packages/plugins/review/src/index.js";
10
+ export { toolsPlugin } from "../packages/plugins/tools/src/index.js";
@@ -0,0 +1,2 @@
1
+ export { createFauxClient } from "../packages/testing/src/index.js";
2
+ export type { FauxClient, FauxOptions, FauxScript, FauxTurn } from "../packages/testing/src/index.js";
@@ -0,0 +1,2 @@
1
+ // A scripted model for testing an application built on the SDK: the replies are written in advance, so a test needs no provider, no key and no network.
2
+ export { createFauxClient } from "../packages/testing/src/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sakura-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -55,6 +55,10 @@
55
55
  ".": {
56
56
  "types": "./dist/sdk/index.d.ts",
57
57
  "import": "./dist/sdk/index.js"
58
+ },
59
+ "./testing": {
60
+ "types": "./dist/sdk/testing.d.ts",
61
+ "import": "./dist/sdk/testing.js"
58
62
  }
59
63
  },
60
64
  "types": "./dist/sdk/index.d.ts",