loopengine 0.1.53 → 0.1.55
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/core/known-urls.d.ts +27 -0
- package/dist/core/known-urls.js +136 -0
- package/package.json +3 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Message, ModelContentBlock } from './run-agent.js';
|
|
2
|
+
/** Seeds a fresh known-URL map from a session's full message history —
|
|
3
|
+
* every tool_result block's own content, parsed as JSON where possible
|
|
4
|
+
* (plain-text tool results just contribute nothing, not an error). Call
|
|
5
|
+
* once at the top of a run; toollane:result handling in run-agent.ts
|
|
6
|
+
* itself keeps it updated live as new results come in during the run,
|
|
7
|
+
* same map, not a fresh one each turn. */
|
|
8
|
+
export declare function collectKnownUrls(messages: Message[]): Map<string, string>;
|
|
9
|
+
/** Same JSON.parse-then-walk as collectKnownUrls above, for a single
|
|
10
|
+
* fresh tool result (toollane:result's own summary) — called right
|
|
11
|
+
* after each call resolves so a later call in the same run sees it
|
|
12
|
+
* immediately, without waiting for the next collectKnownUrls seed
|
|
13
|
+
* (there isn't one; the same map lives for the whole run). */
|
|
14
|
+
export declare function recordKnownUrlsFromResult(summary: string, knownUrlsByBase: Map<string, string>): void;
|
|
15
|
+
/** Recursively rebuilds a tool_use block's own `input`, snapping any
|
|
16
|
+
* string that looks like a signed URL back to the known-good one for
|
|
17
|
+
* its base whenever they differ. Returns the same reference when
|
|
18
|
+
* nothing needed correcting (the overwhelmingly common case) rather
|
|
19
|
+
* than always cloning, so call sites can skip a write-back when the
|
|
20
|
+
* value is unchanged. */
|
|
21
|
+
export declare function correctKnownUrls<T>(value: T, knownUrlsByBase: Map<string, string>): T;
|
|
22
|
+
/** Applies correctKnownUrls to a tool_use block's own `input` in place —
|
|
23
|
+
* the one call site run-agent.ts actually needs, right before a block's
|
|
24
|
+
* input is used for anything (gate.evaluate, logging, execute alike all
|
|
25
|
+
* need to agree on the same, corrected value). No-ops for a block with
|
|
26
|
+
* no input (a tool that takes no arguments). */
|
|
27
|
+
export declare function correctToolUseInput(block: ModelContentBlock, knownUrlsByBase: Map<string, string>): void;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// A model reproducing a long, opaque, high-entropy string (a cloud
|
|
2
|
+
// storage signed URL's own Signature query param can run to ~300 base64
|
|
3
|
+
// characters) in its own generated output is prone to single-character
|
|
4
|
+
// transcription slips — no semantic pattern to anchor on, unlike normal
|
|
5
|
+
// prose. That's harmless when it only breaks a link the model itself
|
|
6
|
+
// writes into its final chat reply (cosmetic — see web/playground.ts's
|
|
7
|
+
// own client-side correctKnownUrls for that case), but the exact same
|
|
8
|
+
// slip can also land inside a *tool call's own arguments* — a model
|
|
9
|
+
// passing an earlier tool's result URL into a later tool call (e.g.
|
|
10
|
+
// handing image URLs from one ability to a zip-archiving ability's own
|
|
11
|
+
// `files` argument) — where it's not cosmetic at all: the later tool
|
|
12
|
+
// actually fetches that broken URL and fails.
|
|
13
|
+
//
|
|
14
|
+
// collectKnownUrls/correctKnownUrls fix that at the one place both
|
|
15
|
+
// failure modes share: run-agent.ts's own tool-dispatch loop, which has
|
|
16
|
+
// already seen every tool result in this session (via `messages`) by
|
|
17
|
+
// the time it's about to hand a new tool_use block's `input` to
|
|
18
|
+
// gate.evaluate()/execute(). A URL a model just wrote into a tool call's
|
|
19
|
+
// arguments gets snapped back to the last known-good one sharing the
|
|
20
|
+
// same base (the object path — descriptive, not random, so far less
|
|
21
|
+
// likely to get mistyped) whenever they differ, the same
|
|
22
|
+
// correct-the-query-not-the-path heuristic web/playground.ts's own copy
|
|
23
|
+
// already uses. Two independent, non-shared implementations by
|
|
24
|
+
// necessity — that one runs in a browser, parsing a chat reply's
|
|
25
|
+
// markdown text; this one runs in Node, walking structured tool_use
|
|
26
|
+
// input — not the same runtime, not the same data shape, nothing real
|
|
27
|
+
// to share between them.
|
|
28
|
+
const URL_PATTERN = /^https?:\/\//;
|
|
29
|
+
/** Recursively walks an arbitrary JSON value collecting every string
|
|
30
|
+
* that looks like a signed URL — has a "?" (nothing to correct in a
|
|
31
|
+
* bare URL with no query to corrupt). Last one seen for a given base
|
|
32
|
+
* wins: a re-signed URL for the same object is just as valid a
|
|
33
|
+
* correction target as the first one seen. */
|
|
34
|
+
function recordKnownUrls(value, knownUrlsByBase) {
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
if (URL_PATTERN.test(value)) {
|
|
37
|
+
const qIndex = value.indexOf('?');
|
|
38
|
+
if (qIndex !== -1)
|
|
39
|
+
knownUrlsByBase.set(value.slice(0, qIndex), value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (Array.isArray(value)) {
|
|
43
|
+
for (const item of value)
|
|
44
|
+
recordKnownUrls(item, knownUrlsByBase);
|
|
45
|
+
}
|
|
46
|
+
else if (value && typeof value === 'object') {
|
|
47
|
+
for (const key of Object.keys(value))
|
|
48
|
+
recordKnownUrls(value[key], knownUrlsByBase);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Seeds a fresh known-URL map from a session's full message history —
|
|
52
|
+
* every tool_result block's own content, parsed as JSON where possible
|
|
53
|
+
* (plain-text tool results just contribute nothing, not an error). Call
|
|
54
|
+
* once at the top of a run; toollane:result handling in run-agent.ts
|
|
55
|
+
* itself keeps it updated live as new results come in during the run,
|
|
56
|
+
* same map, not a fresh one each turn. */
|
|
57
|
+
export function collectKnownUrls(messages) {
|
|
58
|
+
const knownUrlsByBase = new Map();
|
|
59
|
+
for (const message of messages) {
|
|
60
|
+
if (!Array.isArray(message.content))
|
|
61
|
+
continue;
|
|
62
|
+
for (const block of message.content) {
|
|
63
|
+
if (block.type !== 'tool_result' || typeof block.content !== 'string')
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
recordKnownUrls(JSON.parse(block.content), knownUrlsByBase);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Not JSON — nothing to correct against.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return knownUrlsByBase;
|
|
74
|
+
}
|
|
75
|
+
/** Same JSON.parse-then-walk as collectKnownUrls above, for a single
|
|
76
|
+
* fresh tool result (toollane:result's own summary) — called right
|
|
77
|
+
* after each call resolves so a later call in the same run sees it
|
|
78
|
+
* immediately, without waiting for the next collectKnownUrls seed
|
|
79
|
+
* (there isn't one; the same map lives for the whole run). */
|
|
80
|
+
export function recordKnownUrlsFromResult(summary, knownUrlsByBase) {
|
|
81
|
+
try {
|
|
82
|
+
recordKnownUrls(JSON.parse(summary), knownUrlsByBase);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Not JSON — nothing to correct against.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Recursively rebuilds a tool_use block's own `input`, snapping any
|
|
89
|
+
* string that looks like a signed URL back to the known-good one for
|
|
90
|
+
* its base whenever they differ. Returns the same reference when
|
|
91
|
+
* nothing needed correcting (the overwhelmingly common case) rather
|
|
92
|
+
* than always cloning, so call sites can skip a write-back when the
|
|
93
|
+
* value is unchanged. */
|
|
94
|
+
export function correctKnownUrls(value, knownUrlsByBase) {
|
|
95
|
+
if (typeof value === 'string') {
|
|
96
|
+
if (!URL_PATTERN.test(value))
|
|
97
|
+
return value;
|
|
98
|
+
const qIndex = value.indexOf('?');
|
|
99
|
+
if (qIndex === -1)
|
|
100
|
+
return value;
|
|
101
|
+
const known = knownUrlsByBase.get(value.slice(0, qIndex));
|
|
102
|
+
return (known !== undefined && known !== value ? known : value);
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
let changed = false;
|
|
106
|
+
const next = value.map((item) => {
|
|
107
|
+
const corrected = correctKnownUrls(item, knownUrlsByBase);
|
|
108
|
+
if (corrected !== item)
|
|
109
|
+
changed = true;
|
|
110
|
+
return corrected;
|
|
111
|
+
});
|
|
112
|
+
return (changed ? next : value);
|
|
113
|
+
}
|
|
114
|
+
if (value && typeof value === 'object') {
|
|
115
|
+
let changed = false;
|
|
116
|
+
const next = {};
|
|
117
|
+
for (const key of Object.keys(value)) {
|
|
118
|
+
const corrected = correctKnownUrls(value[key], knownUrlsByBase);
|
|
119
|
+
if (corrected !== value[key])
|
|
120
|
+
changed = true;
|
|
121
|
+
next[key] = corrected;
|
|
122
|
+
}
|
|
123
|
+
return (changed ? next : value);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
/** Applies correctKnownUrls to a tool_use block's own `input` in place —
|
|
128
|
+
* the one call site run-agent.ts actually needs, right before a block's
|
|
129
|
+
* input is used for anything (gate.evaluate, logging, execute alike all
|
|
130
|
+
* need to agree on the same, corrected value). No-ops for a block with
|
|
131
|
+
* no input (a tool that takes no arguments). */
|
|
132
|
+
export function correctToolUseInput(block, knownUrlsByBase) {
|
|
133
|
+
if (!block.input)
|
|
134
|
+
return;
|
|
135
|
+
block.input = correctKnownUrls(block.input, knownUrlsByBase);
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loopengine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"dist/bin/ability-manager.d.ts",
|
|
28
28
|
"dist/core/run-agent.js",
|
|
29
29
|
"dist/core/run-agent.d.ts",
|
|
30
|
+
"dist/core/known-urls.js",
|
|
31
|
+
"dist/core/known-urls.d.ts",
|
|
30
32
|
"dist/core/loop-events.js",
|
|
31
33
|
"dist/core/loop-events.d.ts",
|
|
32
34
|
"dist/core/client.js",
|