open-agents-ai 0.107.0 → 0.108.0

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 CHANGED
@@ -7557,6 +7557,12 @@ var init_repl = __esm({
7557
7557
  setLlmQueryHandler(handler) {
7558
7558
  this.llmQueryHandler = handler;
7559
7559
  }
7560
+ /** Callback for retrieving externalized handle content (RLM Context OS) */
7561
+ handleResolver = null;
7562
+ /** Set the handle resolver for retrieve() in the REPL (COHERE Layer 2) */
7563
+ setHandleResolver(resolver) {
7564
+ this.handleResolver = resolver;
7565
+ }
7560
7566
  /** Get the number of sub-calls made this session */
7561
7567
  getSubCallCount() {
7562
7568
  return this.subCallCount;
@@ -7684,6 +7690,45 @@ def llm_query(prompt, context=""):
7684
7690
  finally:
7685
7691
  sock.close()
7686
7692
 
7693
+ def retrieve(handle_id):
7694
+ """Retrieve full content of an externalized tool output by its handle ID.
7695
+ Use this to access large outputs that were stored as handles instead of
7696
+ being shown inline. Returns the full content as a string.
7697
+ Args:
7698
+ handle_id: The handle ID (e.g., 'a1b2c3d4')
7699
+ Returns: Full content string, or error message if not found
7700
+ """
7701
+ sock_path = os.environ.get("OA_LLM_QUERY_SOCKET", "")
7702
+ if not sock_path:
7703
+ return "[retrieve unavailable: no IPC socket]"
7704
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
7705
+ try:
7706
+ sock.connect(sock_path)
7707
+ request = json.dumps({"op": "retrieve", "handle_id": str(handle_id)})
7708
+ data = request.encode("utf-8")
7709
+ sock.sendall(len(data).to_bytes(4, "big") + data)
7710
+ length_bytes = b""
7711
+ while len(length_bytes) < 4:
7712
+ chunk = sock.recv(4 - len(length_bytes))
7713
+ if not chunk:
7714
+ return "[retrieve: connection closed]"
7715
+ length_bytes += chunk
7716
+ resp_len = int.from_bytes(length_bytes, "big")
7717
+ resp_data = b""
7718
+ while len(resp_data) < resp_len:
7719
+ chunk = sock.recv(min(resp_len - len(resp_data), 65536))
7720
+ if not chunk:
7721
+ break
7722
+ resp_data += chunk
7723
+ result = json.loads(resp_data.decode("utf-8"))
7724
+ if "error" in result:
7725
+ return f"[retrieve error: {result['error']}]"
7726
+ return result.get("content", "")
7727
+ except Exception as e:
7728
+ return f"[retrieve error: {e}]"
7729
+ finally:
7730
+ sock.close()
7731
+
7687
7732
  # Signal REPL ready
7688
7733
  print("__OA_REPL_READY__")
7689
7734
  `.trim();
@@ -7725,14 +7770,27 @@ print("__OA_REPL_READY__")
7725
7770
  let response;
7726
7771
  try {
7727
7772
  const req = JSON.parse(msgData);
7728
- if (!this.llmQueryHandler) {
7729
- response = { error: "llm_query handler not configured" };
7730
- } else if (this.subCallCount >= this.maxSubCalls) {
7731
- response = { error: `Sub-call limit reached (${this.maxSubCalls})` };
7773
+ if (req.op === "retrieve") {
7774
+ if (!this.handleResolver) {
7775
+ response = { error: "Handle resolver not configured" };
7776
+ } else {
7777
+ const content = this.handleResolver(req.handle_id ?? "");
7778
+ if (content !== null) {
7779
+ response = { content };
7780
+ } else {
7781
+ response = { error: `Handle '${req.handle_id}' not found` };
7782
+ }
7783
+ }
7732
7784
  } else {
7733
- this.subCallCount++;
7734
- const result = await this.llmQueryHandler(req.prompt, req.context);
7735
- response = { response: result };
7785
+ if (!this.llmQueryHandler) {
7786
+ response = { error: "llm_query handler not configured" };
7787
+ } else if (this.subCallCount >= this.maxSubCalls) {
7788
+ response = { error: `Sub-call limit reached (${this.maxSubCalls})` };
7789
+ } else {
7790
+ this.subCallCount++;
7791
+ const result = await this.llmQueryHandler(req.prompt ?? "", req.context);
7792
+ response = { response: result };
7793
+ }
7736
7794
  }
7737
7795
  } catch (err) {
7738
7796
  response = { error: err instanceof Error ? err.message : String(err) };
@@ -19992,8 +20050,29 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
19992
20050
  }
19993
20051
  }
19994
20052
  const { toolOutputMaxChars: maxLen } = this.contextLimits();
19995
- const output = result.success ? result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : result.output : `Error: ${result.error || "unknown error"}
20053
+ let output;
20054
+ if (!result.success) {
20055
+ output = `Error: ${result.error || "unknown error"}
19996
20056
  ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : result.output}`;
20057
+ } else if (result.output.length > maxLen) {
20058
+ const handleId = this.quickHash(tc.name + String(tc.arguments?.["path"] ?? "") + String(turn));
20059
+ const lineCount = (result.output.match(/\n/g) || []).length + 1;
20060
+ const preview = result.output.slice(0, 500).replace(/\n/g, " ");
20061
+ this._memexArchive.set(handleId, {
20062
+ id: handleId,
20063
+ toolName: tc.name,
20064
+ summary: `${tc.name} output (${lineCount} lines, ${result.output.length} chars)`,
20065
+ fullContent: result.output,
20066
+ turn,
20067
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20068
+ });
20069
+ output = `[Output externalized \u2014 ${result.output.length} chars, ${lineCount} lines]
20070
+ Handle: ${handleId}
20071
+ Preview: ${preview}...
20072
+ Full content available via: repl_exec(code="data = retrieve('${handleId}')") or memex_retrieve(id="${handleId}")`;
20073
+ } else {
20074
+ output = result.output;
20075
+ }
19997
20076
  this.emit({
19998
20077
  type: "tool_result",
19999
20078
  toolName: tc.name,
@@ -20383,9 +20462,29 @@ Integrate this guidance into your current approach. Continue working on the task
20383
20462
  }
20384
20463
  }
20385
20464
  const { toolOutputMaxChars: maxLen2 } = this.contextLimits();
20386
- const output = result.success ? result.output.length > maxLen2 ? result.output.slice(0, maxLen2) + `
20387
- ...(truncated)` : result.output : `Error: ${result.error || "unknown error"}
20465
+ let output;
20466
+ if (!result.success) {
20467
+ output = `Error: ${result.error || "unknown error"}
20388
20468
  ${result.output}`;
20469
+ } else if (result.output.length > maxLen2) {
20470
+ const handleId = this.quickHash(tc.name + String(tc.arguments?.["path"] ?? "") + String(turn));
20471
+ const lineCount = (result.output.match(/\n/g) || []).length + 1;
20472
+ const preview = result.output.slice(0, 500).replace(/\n/g, " ");
20473
+ this._memexArchive.set(handleId, {
20474
+ id: handleId,
20475
+ toolName: tc.name,
20476
+ summary: `${tc.name} output (${lineCount} lines, ${result.output.length} chars)`,
20477
+ fullContent: result.output,
20478
+ turn,
20479
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20480
+ });
20481
+ output = `[Output externalized \u2014 ${result.output.length} chars, ${lineCount} lines]
20482
+ Handle: ${handleId}
20483
+ Preview: ${preview}...
20484
+ Full content available via: repl_exec(code="data = retrieve('${handleId}')") or memex_retrieve(id="${handleId}")`;
20485
+ } else {
20486
+ output = result.output;
20487
+ }
20389
20488
  this.emit({ type: "tool_result", toolName: tc.name, content: output.slice(0, 200), success: result.success, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
20390
20489
  const enoentTools2 = /* @__PURE__ */ new Set(["file_read", "list_directory", "find_files"]);
20391
20490
  if (!result.success && enoentTools2.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(result.error ?? result.output)) {
@@ -50290,6 +50389,14 @@ ${context}` : prompt;
50290
50389
  });
50291
50390
  }
50292
50391
  }
50392
+ for (const tool of tools) {
50393
+ if ("setHandleResolver" in tool && typeof tool.setHandleResolver === "function") {
50394
+ tool.setHandleResolver((handleId) => {
50395
+ const entry = runner.getMemexEntry(handleId);
50396
+ return entry?.fullContent ?? null;
50397
+ });
50398
+ }
50399
+ }
50293
50400
  const replToolForFinalVar = tools.find((t) => t.name === "repl_exec");
50294
50401
  if (replToolForFinalVar && "readVariable" in replToolForFinalVar) {
50295
50402
  runner.options.finalVarResolver = async (varName) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.107.0",
3
+ "version": "0.108.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -462,3 +462,28 @@ Entries in a directory listing are RELATIVE to the directory you listed.
462
462
  - If an entry is marked "d" (directory), use list_directory on it — NOT file_read
463
463
  - list_directory output includes full relative paths you can copy directly into your next tool call
464
464
  - Prefer list_directory over shell ls — it shows full relative paths you can copy directly into your next tool call
465
+
466
+ ## RLM Context Operating System
467
+
468
+ The repl_exec tool provides a persistent Python REPL where variables persist between calls. Use it for:
469
+
470
+ **Data Processing**: When you need to process, transform, or analyze data across multiple steps, use repl_exec. Variables, functions, and imports survive between calls.
471
+
472
+ **Recursive LLM Calls**: Inside the REPL, `llm_query(prompt, context="")` invokes the language model on a sub-prompt. Use it in loops to analyze chunks of large content:
473
+ ```python
474
+ # Example: analyze each file in a list
475
+ results = []
476
+ for filename in filenames:
477
+ with open(filename) as f:
478
+ content = f.read()
479
+ summary = llm_query("Summarize the key purpose of this code", content)
480
+ results.append(f"{filename}: {summary}")
481
+ ```
482
+
483
+ **Externalized Context**: When your input is very large, it may be stored as the `context` variable in the REPL. Use `print(context[:1000])` to examine it, slice it, and process chunks with llm_query().
484
+
485
+ **Handle Retrieval**: When a tool output is too large for context, it's stored as a handle. Access it via `data = retrieve('handle_id')` in the REPL.
486
+
487
+ **Output Construction**: For very long outputs, build the result as a REPL variable and return it with FINAL_VAR(variable_name) instead of autoregressive generation.
488
+
489
+ **Provenance**: Based on "Recursive Language Models" (Zhang, Kraska, Khattab — MIT CSAIL, arxiv:2512.24601) and Project COHERE Layer 2 architecture.