@guangnao/agent-os 1.0.0 → 1.0.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.
- package/dist/index.js +97 -8
- package/dist/kernel/blob.d.ts +21 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1958,13 +1958,98 @@ async function lookup(sys, names, name) {
|
|
|
1958
1958
|
}
|
|
1959
1959
|
}
|
|
1960
1960
|
|
|
1961
|
+
// src/kernel/blob.ts
|
|
1962
|
+
var PAGE = 8 * 1024;
|
|
1963
|
+
var GREP_MAX = 50;
|
|
1964
|
+
var BlobStore = class {
|
|
1965
|
+
constructor(cap = 4 * 1024 * 1024) {
|
|
1966
|
+
this.cap = cap;
|
|
1967
|
+
}
|
|
1968
|
+
map = /* @__PURE__ */ new Map();
|
|
1969
|
+
// insertion order = LRU age (re-set on touch → newest)
|
|
1970
|
+
seq = 0;
|
|
1971
|
+
bytes = 0;
|
|
1972
|
+
// 4 MB total
|
|
1973
|
+
/** Store content; return a fresh handle. Evict the oldest blobs while over the char cap. */
|
|
1974
|
+
put(content) {
|
|
1975
|
+
const handle = `blob_${(++this.seq).toString(36)}`;
|
|
1976
|
+
this.map.set(handle, content);
|
|
1977
|
+
this.bytes += content.length;
|
|
1978
|
+
while (this.bytes > this.cap && this.map.size > 1) {
|
|
1979
|
+
const [old, v] = this.map.entries().next().value;
|
|
1980
|
+
this.map.delete(old);
|
|
1981
|
+
this.bytes -= v.length;
|
|
1982
|
+
}
|
|
1983
|
+
return handle;
|
|
1984
|
+
}
|
|
1985
|
+
touch(handle) {
|
|
1986
|
+
const v = this.map.get(handle);
|
|
1987
|
+
if (v === void 0) return void 0;
|
|
1988
|
+
this.map.delete(handle);
|
|
1989
|
+
this.map.set(handle, v);
|
|
1990
|
+
return v;
|
|
1991
|
+
}
|
|
1992
|
+
/** A character window of a blob (paged like read_file). */
|
|
1993
|
+
read(handle, offset = 0, limit = PAGE) {
|
|
1994
|
+
const v = this.touch(handle);
|
|
1995
|
+
if (v === void 0) return { error: `unknown blob handle: ${handle}` };
|
|
1996
|
+
const total = v.length;
|
|
1997
|
+
const start = Math.max(0, Math.floor(offset));
|
|
1998
|
+
if (start >= total) return { handle, content: "", total, note: `offset ${start} is at/past end (${total} chars)` };
|
|
1999
|
+
const slice = v.slice(start, start + Math.min(Math.max(1, Math.floor(limit)), PAGE));
|
|
2000
|
+
const end = start + slice.length;
|
|
2001
|
+
return end < total ? { handle, content: slice, truncated: true, total, offset: start, nextOffset: end, note: `chars ${start}\u2013${end} of ${total}; call blob_read with offset=${end} for more` } : { handle, content: slice, total, offset: start };
|
|
2002
|
+
}
|
|
2003
|
+
/** Search a blob for a JS regex; return a short SNIPPET (char offset + surrounding window) per match. Snippet
|
|
2004
|
+
* rather than whole line because a blob is often one long line (JSON.stringify escapes newlines) — a line view
|
|
2005
|
+
* would return the entire blob and just overflow again. The offset lets the model blob_read around it. */
|
|
2006
|
+
grep(handle, pattern, max = GREP_MAX) {
|
|
2007
|
+
const v = this.touch(handle);
|
|
2008
|
+
if (v === void 0) return { error: `unknown blob handle: ${handle}` };
|
|
2009
|
+
let re;
|
|
2010
|
+
try {
|
|
2011
|
+
re = new RegExp(pattern, "g");
|
|
2012
|
+
} catch {
|
|
2013
|
+
return { error: `invalid pattern: ${pattern}` };
|
|
2014
|
+
}
|
|
2015
|
+
const PAD = 80, matches = [];
|
|
2016
|
+
let m;
|
|
2017
|
+
while ((m = re.exec(v)) && matches.length < max) {
|
|
2018
|
+
const at = m.index, from = Math.max(0, at - PAD), to = Math.min(v.length, at + m[0].length + PAD);
|
|
2019
|
+
matches.push(`@${at}: ${from > 0 ? "\u2026" : ""}${v.slice(from, to)}${to < v.length ? "\u2026" : ""}`);
|
|
2020
|
+
if (re.lastIndex === at) re.lastIndex++;
|
|
2021
|
+
}
|
|
2022
|
+
return { handle, total: v.length, matches, ...matches.length >= max ? { truncated: true } : {} };
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
var BLOB_TOOLS = [
|
|
2026
|
+
{ name: "blob_read", description: "read a window of a large stored tool output by its blob handle (paged like read_file; use when a result said it was stored as blob_\u2026)", parameters: { type: "object", required: ["handle"], properties: {
|
|
2027
|
+
handle: { type: "string", description: "the blob_\u2026 handle from a truncated result" },
|
|
2028
|
+
offset: { type: "number", description: "char index to start at (default 0; use the previous result\u2019s nextOffset)" },
|
|
2029
|
+
limit: { type: "number", description: `max chars (default & cap ${PAGE})` }
|
|
2030
|
+
} } },
|
|
2031
|
+
{ name: "blob_grep", description: "find lines matching a JS regex inside a large stored tool output, by blob handle (search without re-reading it all)", parameters: { type: "object", required: ["handle", "pattern"], properties: {
|
|
2032
|
+
handle: { type: "string", description: "the blob_\u2026 handle" },
|
|
2033
|
+
pattern: { type: "string", description: "JS regex, matched per line" }
|
|
2034
|
+
} } }
|
|
2035
|
+
];
|
|
2036
|
+
var BLOB_NAMES = new Set(BLOB_TOOLS.map((t) => t.name));
|
|
2037
|
+
var isBlobTool = (name) => BLOB_NAMES.has(name);
|
|
2038
|
+
function execBlobTool(store, name, input) {
|
|
2039
|
+
const a = input ?? {};
|
|
2040
|
+
if (!a.handle) return { error: "handle is required" };
|
|
2041
|
+
if (name === "blob_read") return store.read(a.handle, a.offset, a.limit);
|
|
2042
|
+
if (name === "blob_grep") return a.pattern ? store.grep(a.handle, a.pattern) : { error: "pattern is required" };
|
|
2043
|
+
return { error: `not a blob tool: ${name}` };
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1961
2046
|
// src/userland/agent.ts
|
|
1962
2047
|
var DEFAULT_SYSTEM = "You are a capable autonomous agent. Break the task into steps and keep going \u2014 investigate with tools, then act \u2014 until it is fully done; never stop at a plan or a partial result. Verify before answering, then reply concisely.";
|
|
1963
2048
|
var HARD_MAX = 384;
|
|
1964
2049
|
var MAX_TOOL_CHARS = 8e3;
|
|
1965
2050
|
var isMessage = (b) => !!b && typeof b.role === "string";
|
|
1966
|
-
var capResult = (s) => s.length
|
|
1967
|
-
\u2026[
|
|
2051
|
+
var capResult = (s, blobs) => s.length <= MAX_TOOL_CHARS ? s : s.slice(0, MAX_TOOL_CHARS) + `
|
|
2052
|
+
\u2026[+${s.length - MAX_TOOL_CHARS} chars \u2014 full result stored as ${blobs.put(s)}; call blob_read(handle, offset=${MAX_TOOL_CHARS}) or blob_grep(handle, pattern) for the rest]`;
|
|
1968
2053
|
function orderForCache(segs) {
|
|
1969
2054
|
const lead = [], sums = [], rest = [];
|
|
1970
2055
|
for (const s of segs) (s.pinned && !s.summary ? lead : s.summary ? sums : rest).push(s);
|
|
@@ -1992,7 +2077,7 @@ function unproductive(out) {
|
|
|
1992
2077
|
const st = o["status"];
|
|
1993
2078
|
return typeof st === "number" && (st < 200 || st >= 300);
|
|
1994
2079
|
}
|
|
1995
|
-
async function runTurn(sys, llm, tools, spec) {
|
|
2080
|
+
async function runTurn(sys, llm, tools, spec, blobs) {
|
|
1996
2081
|
const ceil = Math.max(1, Math.min(spec.maxSteps ?? HARD_MAX, HARD_MAX));
|
|
1997
2082
|
const base = Math.max(1, spec.baseSteps ?? 16);
|
|
1998
2083
|
const stallMax = Math.max(1, spec.stallLimit ?? 3);
|
|
@@ -2015,6 +2100,7 @@ async function runTurn(sys, llm, tools, spec) {
|
|
|
2015
2100
|
}
|
|
2016
2101
|
const messages = promptMessages();
|
|
2017
2102
|
let offer = noTools ? void 0 : spec.tools;
|
|
2103
|
+
if (offer && tools) offer = [...offer, ...BLOB_TOOLS];
|
|
2018
2104
|
if (steer && offer && del) offer = offer.filter((t) => !del.withhold.includes(t.name));
|
|
2019
2105
|
const reply = await sys.read(llm, { messages, tools: offer, maxTokens: spec.maxTokens });
|
|
2020
2106
|
for (const s of sys.workingSet()) {
|
|
@@ -2035,13 +2121,15 @@ async function runTurn(sys, llm, tools, spec) {
|
|
|
2035
2121
|
}
|
|
2036
2122
|
}
|
|
2037
2123
|
if (fresh.length) {
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2124
|
+
const viaDevice = fresh.filter((f) => !isBlobTool(f.req.tool));
|
|
2125
|
+
const devOuts = viaDevice.length ? await sys.readAll(tools, viaDevice.map((f) => f.req)) : [];
|
|
2126
|
+
let di = 0;
|
|
2127
|
+
for (const f of fresh) cache.set(f.key, isBlobTool(f.req.tool) ? execBlobTool(blobs, f.req.tool, f.req.input) : devOuts[di++]);
|
|
2040
2128
|
}
|
|
2041
2129
|
let progressed = false;
|
|
2042
2130
|
for (let i = 0; i < calls.length; i++) {
|
|
2043
2131
|
const out = cache.get(keys[i]);
|
|
2044
|
-
await sys.remember({ role: "tool", toolCallId: calls[i].id, name: calls[i].name, content: capResult(JSON.stringify(out)) });
|
|
2132
|
+
await sys.remember({ role: "tool", toolCallId: calls[i].id, name: calls[i].name, content: capResult(JSON.stringify(out), blobs) });
|
|
2045
2133
|
if (ran.has(keys[i]) && !unproductive(out)) progressed = true;
|
|
2046
2134
|
}
|
|
2047
2135
|
if (del) {
|
|
@@ -2092,7 +2180,7 @@ function reactAgent(spec) {
|
|
|
2092
2180
|
const { llm, tools, parent } = await openDevices(sys);
|
|
2093
2181
|
await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
|
|
2094
2182
|
await sys.remember({ role: "user", content: spec.task ?? "" }, { pin: true });
|
|
2095
|
-
const r = await runTurn(sys, llm, tools, spec);
|
|
2183
|
+
const r = await runTurn(sys, llm, tools, spec, new BlobStore());
|
|
2096
2184
|
if (parent) await sys.send(parent, { $: "result", answer: r.answer, steps: r.steps });
|
|
2097
2185
|
} };
|
|
2098
2186
|
}
|
|
@@ -2110,13 +2198,14 @@ function conversationAgent(spec = {}) {
|
|
|
2110
2198
|
} else {
|
|
2111
2199
|
await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
|
|
2112
2200
|
}
|
|
2201
|
+
const blobs = new BlobStore();
|
|
2113
2202
|
for (; ; ) {
|
|
2114
2203
|
const msg = await sys.recv();
|
|
2115
2204
|
const userTurn = { role: "user", content: msg.body.task, ...msg.body.images?.length ? { images: msg.body.images } : {} };
|
|
2116
2205
|
await sys.remember(userTurn);
|
|
2117
2206
|
let r;
|
|
2118
2207
|
try {
|
|
2119
|
-
r = await runTurn(sys, llm, tools, spec);
|
|
2208
|
+
r = await runTurn(sys, llm, tools, spec, blobs);
|
|
2120
2209
|
} catch (e) {
|
|
2121
2210
|
r = { answer: "", steps: 0, error: e instanceof Error ? e.message : String(e) };
|
|
2122
2211
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ToolSchema } from '../drivers/llm';
|
|
2
|
+
export declare class BlobStore {
|
|
3
|
+
private readonly cap;
|
|
4
|
+
private readonly map;
|
|
5
|
+
private seq;
|
|
6
|
+
private bytes;
|
|
7
|
+
constructor(cap?: number);
|
|
8
|
+
/** Store content; return a fresh handle. Evict the oldest blobs while over the char cap. */
|
|
9
|
+
put(content: string): string;
|
|
10
|
+
private touch;
|
|
11
|
+
/** A character window of a blob (paged like read_file). */
|
|
12
|
+
read(handle: string, offset?: number, limit?: number): unknown;
|
|
13
|
+
/** Search a blob for a JS regex; return a short SNIPPET (char offset + surrounding window) per match. Snippet
|
|
14
|
+
* rather than whole line because a blob is often one long line (JSON.stringify escapes newlines) — a line view
|
|
15
|
+
* would return the entire blob and just overflow again. The offset lets the model blob_read around it. */
|
|
16
|
+
grep(handle: string, pattern: string, max?: number): unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare const BLOB_TOOLS: readonly ToolSchema[];
|
|
19
|
+
export declare const isBlobTool: (name: string) => boolean;
|
|
20
|
+
/** Run a blob_* tool against the store — invoked directly by the agent loop, not the tools device. */
|
|
21
|
+
export declare function execBlobTool(store: BlobStore, name: string, input: unknown): unknown;
|