@halo-format/langgraph 0.3.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/README.md +31 -0
- package/dist/accumulate.d.ts +8 -0
- package/dist/accumulate.d.ts.map +1 -0
- package/dist/accumulate.js +51 -0
- package/dist/accumulate.js.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +15 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +23 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +73 -0
- package/dist/middleware.js.map +1 -0
- package/dist/nav-tool.d.ts +17 -0
- package/dist/nav-tool.d.ts.map +1 -0
- package/dist/nav-tool.js +47 -0
- package/dist/nav-tool.js.map +1 -0
- package/dist/serialize.d.ts +29 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +161 -0
- package/dist/serialize.js.map +1 -0
- package/dist/session.d.ts +70 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +184 -0
- package/dist/session.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @halo-format/langgraph
|
|
2
|
+
|
|
3
|
+
Halo host adapter for [LangChain](https://github.com/langchain-ai/langchainjs) agents and
|
|
4
|
+
[LangGraph](https://github.com/langchain-ai/langgraphjs). `installHalo()` wires it in one call:
|
|
5
|
+
|
|
6
|
+
- a **`wrapToolCall` encode middleware** — the deterministic wrap-the-tool-call hook, LangChain's
|
|
7
|
+
analog of the Claude SDK's PostToolUse — that replaces a large tool result with a halo **shape map**
|
|
8
|
+
(root kind + one line per field: ref, kind, and a bounded preview), so the payload stays out of the
|
|
9
|
+
model's context while it still sees what's there. The full envelope rides in the `ToolMessage`
|
|
10
|
+
**`artifact`** (kept in graph state, never sent to the model) for audit/replay;
|
|
11
|
+
- a single plain LangChain **`halo_fetch`** tool the model uses to pull back only the leaves it needs,
|
|
12
|
+
verified on read — a ref that lands on a branch returns that branch's sub-refs, so one batch API both
|
|
13
|
+
pulls and expands (there is no separate `halo_walk`).
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createAgent } from "langchain";
|
|
17
|
+
import { installHalo } from "@halo-format/langgraph";
|
|
18
|
+
|
|
19
|
+
const { tools, middleware, session } = installHalo({ tools: myTools });
|
|
20
|
+
const agent = createAgent({ model, tools, middleware });
|
|
21
|
+
// session holds the shared store for audit/inspection
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The middleware is deterministic plumbing (it always fires, for every tool); the Halo Skill (or
|
|
25
|
+
prompt-mode guidance) is the navigation behavior. Pass `store: new FileStore(dir)` for the
|
|
26
|
+
heavy/persistent deployment. The core engine is
|
|
27
|
+
[`@halo-format/halo`](https://www.npmjs.com/package/@halo-format/halo); this package is only the shim.
|
|
28
|
+
|
|
29
|
+
> **JS vs. Python note.** The JS `ToolNode` does not accept a wrap option, so on this host the
|
|
30
|
+
> middleware is the only clean interception surface — there is no `haloToolNode()` the way there is in
|
|
31
|
+
> the Python adapter.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type KeyOf = (toolName: string, toolInput: unknown) => string | null;
|
|
2
|
+
/**
|
|
3
|
+
* Default policy: group by the first scalar argument value. Objects are scanned in key order, arrays
|
|
4
|
+
* in index order; a bare scalar input is its own key. Returns null when no scalar argument is
|
|
5
|
+
* present (e.g. a no-arg call), so that call gets a fresh synthetic map id.
|
|
6
|
+
*/
|
|
7
|
+
export declare const argJoin: KeyOf;
|
|
8
|
+
//# sourceMappingURL=accumulate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accumulate.d.ts","sourceRoot":"","sources":["../src/accumulate.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;AAc5E;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,KAiBrB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Entity accumulation policy: which calls fold into one growing map (SDK architecture, Section 9.1).
|
|
2
|
+
//
|
|
3
|
+
// keyOf(toolName, toolInput) returns an entity key, or null to give the call its own fresh map. The
|
|
4
|
+
// default, argJoin, keys on the first scalar argument value, so normalized REST detail calls about
|
|
5
|
+
// one entity group together: get_customer({id:123}) and get_appointments({customerId:123}) both key
|
|
6
|
+
// on 123 and fold into one map, while search({query:"smith"}) keys on "smith" and stays its own list
|
|
7
|
+
// map until the first detail call. It keys on the argument VALUE, not the field name or any result
|
|
8
|
+
// id, which is what lets a list result land under the entity instead of fragmenting.
|
|
9
|
+
//
|
|
10
|
+
// argJoin's one real gap is value collision across entity types (customer 123 vs invoice 123, both
|
|
11
|
+
// passing 123). That is the single thing a generic heuristic cannot resolve alone; supply a domain
|
|
12
|
+
// keyOf (e.g. one that prefixes the resource type) when it matters, as it usually does for money or
|
|
13
|
+
// medical data.
|
|
14
|
+
// A short scalar (number, or string up to this many chars) is treated as an id-like argument worth
|
|
15
|
+
// grouping on. Longer strings are typically free text (a query, a body) and are skipped.
|
|
16
|
+
const MAX_KEY_STRING_LENGTH = 128;
|
|
17
|
+
function scalarKey(value) {
|
|
18
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
19
|
+
return String(value);
|
|
20
|
+
if (typeof value === "string" && value.length > 0 && value.length <= MAX_KEY_STRING_LENGTH) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Default policy: group by the first scalar argument value. Objects are scanned in key order, arrays
|
|
27
|
+
* in index order; a bare scalar input is its own key. Returns null when no scalar argument is
|
|
28
|
+
* present (e.g. a no-arg call), so that call gets a fresh synthetic map id.
|
|
29
|
+
*/
|
|
30
|
+
export const argJoin = (_toolName, toolInput) => {
|
|
31
|
+
const direct = scalarKey(toolInput);
|
|
32
|
+
if (direct !== null)
|
|
33
|
+
return direct;
|
|
34
|
+
if (Array.isArray(toolInput)) {
|
|
35
|
+
for (const item of toolInput) {
|
|
36
|
+
const k = scalarKey(item);
|
|
37
|
+
if (k !== null)
|
|
38
|
+
return k;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (toolInput && typeof toolInput === "object") {
|
|
43
|
+
for (const value of Object.values(toolInput)) {
|
|
44
|
+
const k = scalarKey(value);
|
|
45
|
+
if (k !== null)
|
|
46
|
+
return k;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=accumulate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accumulate.js","sourceRoot":"","sources":["../src/accumulate.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,EAAE;AACF,oGAAoG;AACpG,mGAAmG;AACnG,oGAAoG;AACpG,qGAAqG;AACrG,mGAAmG;AACnG,qFAAqF;AACrF,EAAE;AACF,mGAAmG;AACnG,mGAAmG;AACnG,oGAAoG;AACpG,gBAAgB;AAMhB,mGAAmG;AACnG,yFAAyF;AACzF,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,qBAAqB,EAAE,CAAC;QAC3F,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAU,CAAC,SAAS,EAAE,SAAS,EAAE,EAAE;IACrD,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IACnC,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,SAAsC,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YAC3B,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,eAAe,eAAe,CAAC;AAE5C,iGAAiG;AACjG,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The one name shared between the middleware (which must exclude it) and the navigation tool (which
|
|
2
|
+
// defines it). Kept in one place so the exclusion and the registration can never drift apart.
|
|
3
|
+
//
|
|
4
|
+
// Unlike the Claude adapter, the navigation tool here is a plain LangChain tool, not an in-process MCP
|
|
5
|
+
// server, so its name has no `mcp__<server>__` prefix — it is simply `halo_fetch` and the exclusion is
|
|
6
|
+
// a single string comparison.
|
|
7
|
+
// One navigation tool only. halo_fetch is the single batch API the model ever sees: it pulls leaves
|
|
8
|
+
// AND, when a ref lands on a branch, returns that branch's sub-shape — so there is no second tool
|
|
9
|
+
// (no halo_walk) to choose between or confuse a leaf ref with.
|
|
10
|
+
export const HALO_FETCH_TOOL = "halo_fetch";
|
|
11
|
+
/** True for the adapter's own navigation tool — the result the middleware must NOT re-encode. */
|
|
12
|
+
export function isHaloNavTool(toolName) {
|
|
13
|
+
return toolName === HALO_FETCH_TOOL;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,8FAA8F;AAC9F,EAAE;AACF,uGAAuG;AACvG,uGAAuG;AACvG,8BAA8B;AAE9B,oGAAoG;AACpG,kGAAkG;AAClG,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC;AAE5C,iGAAiG;AACjG,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,QAAQ,KAAK,eAAe,CAAC;AACtC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { HaloSession, type SessionOptions } from "./session.js";
|
|
2
|
+
export type InstallHaloOptions = SessionOptions & {
|
|
3
|
+
/** Your tools; the halo_fetch navigation tool is appended. */
|
|
4
|
+
tools?: unknown[];
|
|
5
|
+
/** Your middleware; the Halo encode middleware is appended. */
|
|
6
|
+
middleware?: unknown[];
|
|
7
|
+
/** Below this many bytes a tool result passes through unchanged (no encode). Default 2048. */
|
|
8
|
+
threshold?: number;
|
|
9
|
+
/** A pre-built session to share state with, instead of creating one. */
|
|
10
|
+
session?: HaloSession;
|
|
11
|
+
};
|
|
12
|
+
export type InstallHaloResult = {
|
|
13
|
+
/** Your tools plus the halo_fetch tool — pass to `createAgent({ tools })`. */
|
|
14
|
+
tools: unknown[];
|
|
15
|
+
/** Your middleware plus the Halo encode middleware — pass to `createAgent({ middleware })`. */
|
|
16
|
+
middleware: unknown[];
|
|
17
|
+
/** The shared session (store + map registry), for audit swaps or direct inspection. */
|
|
18
|
+
session: HaloSession;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Wire the Halo adapter into a createAgent setup. Append the returned `tools` and `middleware` to the
|
|
22
|
+
* call. Returns the shared session for the audit/persistence swaps and inspection.
|
|
23
|
+
*/
|
|
24
|
+
export declare function installHalo(opts?: InstallHaloOptions): InstallHaloResult;
|
|
25
|
+
export { HaloSession } from "./session.js";
|
|
26
|
+
export type { SessionOptions, IngestResult, FetchEntry } from "./session.js";
|
|
27
|
+
export { createHaloMiddleware, rewrite } from "./middleware.js";
|
|
28
|
+
export { createHaloFetchTool, haloFetch } from "./nav-tool.js";
|
|
29
|
+
export { argJoin } from "./accumulate.js";
|
|
30
|
+
export type { KeyOf } from "./accumulate.js";
|
|
31
|
+
export { sizeOf, parseToolOutput, serializeEnvelope, shapeOf, previewOf } from "./serialize.js";
|
|
32
|
+
export type { FieldHint } from "./serialize.js";
|
|
33
|
+
export { HALO_FETCH_TOOL, isHaloNavTool } from "./constants.js";
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAIhE,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG;IAChD,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC;IACvB,8FAA8F;IAC9F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8EAA8E;IAC9E,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,+FAA+F;IAC/F,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,uFAAuF;IACvF,OAAO,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,GAAE,kBAAuB,GAAG,iBAAiB,CAU5E;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,YAAY,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChG,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @halo-format/langgraph — Halo host adapter for LangChain agents and LangGraph graphs.
|
|
2
|
+
//
|
|
3
|
+
// The interception point is LangChain's `wrapToolCall` middleware — a deterministic wrap-the-tool-call
|
|
4
|
+
// hook, the direct analog of the Claude Agent SDK's PostToolUse. Above a size threshold it encodes a
|
|
5
|
+
// tool result into a shared store and rewrites the ToolMessage the model sees: `content` becomes a
|
|
6
|
+
// SHAPE MAP (not the blob), and the full envelope rides in `artifact` (kept in graph state, never sent
|
|
7
|
+
// to the model). One plain LangChain tool, halo_fetch(refs[]), backed by the same store, lets the
|
|
8
|
+
// model pull back the withheld leaves (and expand branches), verified on read.
|
|
9
|
+
//
|
|
10
|
+
// installHalo(opts) returns the augmented `tools` and `middleware` arrays to splice into createAgent,
|
|
11
|
+
// plus the shared session:
|
|
12
|
+
//
|
|
13
|
+
// const { tools, middleware, session } = installHalo({ tools: myTools });
|
|
14
|
+
// const agent = createAgent({ model, tools, middleware });
|
|
15
|
+
//
|
|
16
|
+
// Note a JS/Python divergence: the JS `ToolNode` does not accept a wrap option, so on this host the
|
|
17
|
+
// middleware is the only clean surface — there is no haloToolNode() the way there is in Python.
|
|
18
|
+
//
|
|
19
|
+
// MANDATORY correctness rule: the middleware must NOT re-encode the navigation tool's own output, or a
|
|
20
|
+
// fetched leaf would be replaced by a new map and the model would never see the value. Enforced by an
|
|
21
|
+
// isHaloNavTool name check at the top of the wrapper.
|
|
22
|
+
//
|
|
23
|
+
// Light vs. heavy: pass `store: new FileStore(dir)` for cross-process persistence, and wrap navigation
|
|
24
|
+
// with the L2 chain, without touching the control flow here.
|
|
25
|
+
import { HaloSession } from "./session.js";
|
|
26
|
+
import { createHaloMiddleware } from "./middleware.js";
|
|
27
|
+
import { createHaloFetchTool } from "./nav-tool.js";
|
|
28
|
+
/**
|
|
29
|
+
* Wire the Halo adapter into a createAgent setup. Append the returned `tools` and `middleware` to the
|
|
30
|
+
* call. Returns the shared session for the audit/persistence swaps and inspection.
|
|
31
|
+
*/
|
|
32
|
+
export function installHalo(opts = {}) {
|
|
33
|
+
const session = opts.session ?? new HaloSession(opts);
|
|
34
|
+
const middleware = createHaloMiddleware(session, opts.threshold);
|
|
35
|
+
const fetchTool = createHaloFetchTool(session);
|
|
36
|
+
return {
|
|
37
|
+
tools: [...(opts.tools ?? []), fetchTool],
|
|
38
|
+
middleware: [...(opts.middleware ?? []), middleware],
|
|
39
|
+
session,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export { HaloSession } from "./session.js";
|
|
43
|
+
export { createHaloMiddleware, rewrite } from "./middleware.js";
|
|
44
|
+
export { createHaloFetchTool, haloFetch } from "./nav-tool.js";
|
|
45
|
+
export { argJoin } from "./accumulate.js";
|
|
46
|
+
export { sizeOf, parseToolOutput, serializeEnvelope, shapeOf, previewOf } from "./serialize.js";
|
|
47
|
+
export { HALO_FETCH_TOOL, isHaloNavTool } from "./constants.js";
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,EAAE;AACF,uGAAuG;AACvG,qGAAqG;AACrG,mGAAmG;AACnG,uGAAuG;AACvG,kGAAkG;AAClG,+EAA+E;AAC/E,EAAE;AACF,sGAAsG;AACtG,2BAA2B;AAC3B,EAAE;AACF,4EAA4E;AAC5E,6DAA6D;AAC7D,EAAE;AACF,oGAAoG;AACpG,gGAAgG;AAChG,EAAE;AACF,uGAAuG;AACvG,sGAAsG;AACtG,sDAAsD;AACtD,EAAE;AACF,uGAAuG;AACvG,6DAA6D;AAE7D,OAAO,EAAE,WAAW,EAAuB,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAsBpD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B,EAAE;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE/C,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,SAAS,CAAC;QACzC,UAAU,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC;QACpD,OAAO;KACR,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HaloSession } from "./session.js";
|
|
2
|
+
type HasToolCall = {
|
|
3
|
+
toolCall: {
|
|
4
|
+
name: string;
|
|
5
|
+
args: unknown;
|
|
6
|
+
id?: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Encode a tool's ToolMessage and return a rewritten copy: content -> shape map, artifact -> envelope.
|
|
11
|
+
* Returns the value untouched when it should pass through (not a ToolMessage, small, or non-JSON).
|
|
12
|
+
* Builds a NEW ToolMessage rather than mutating, so a handler that returns a shared/cached message is
|
|
13
|
+
* never clobbered and the rewrite survives serialization (LangChain messages serialize from their
|
|
14
|
+
* constructor args). The original tool_call_id, name and status are preserved.
|
|
15
|
+
*/
|
|
16
|
+
export declare function rewrite(session: HaloSession, threshold: number, msg: unknown, request: HasToolCall): Promise<unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Build the Halo encode middleware bound to a session, for `createAgent({ middleware: [...] })`.
|
|
19
|
+
* Provides the sync/async-agnostic `wrapToolCall` hook (the handler is awaited).
|
|
20
|
+
*/
|
|
21
|
+
export declare function createHaloMiddleware(session: HaloSession, threshold?: number): import("langchain").AgentMiddleware<undefined, undefined, unknown, readonly (import("@langchain/core/tools").ClientTool | import("@langchain/core/tools").ServerTool)[], readonly []>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAahD,KAAK,WAAW,GAAG;IAAE,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE9E;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,GAAE,MAA0B,yLAS/F"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The encode middleware — the plumbing. It wraps every tool call: it runs the tool via the handler,
|
|
2
|
+
// and above a size threshold rewrites the resulting ToolMessage so the model sees a SHAPE MAP instead
|
|
3
|
+
// of the blob, while the full payload goes to the store and the full envelope rides in the
|
|
4
|
+
// ToolMessage's `artifact` (kept in graph state, never serialized into the model's context). This is
|
|
5
|
+
// deterministic and model-independent: the payload is out of context before the model is involved, so
|
|
6
|
+
// the model cannot leak a full result by forgetting to navigate.
|
|
7
|
+
//
|
|
8
|
+
// The interception point is LangChain's `wrapToolCall` middleware hook (createMiddleware), the direct
|
|
9
|
+
// analog of the Claude SDK's PostToolUse. Note a JS/Python divergence: the JS `ToolNode` does not
|
|
10
|
+
// accept a wrap option, so on this host the middleware is the ONLY clean surface — there is no
|
|
11
|
+
// ToolNode-param path the way there is in Python.
|
|
12
|
+
//
|
|
13
|
+
// Two passthrough conditions, in order (mirrors the Claude encode hook):
|
|
14
|
+
// 1. The tool IS the halo navigation tool. MANDATORY: re-encoding a fetched leaf would replace it
|
|
15
|
+
// with a new map and the model would never receive the value.
|
|
16
|
+
// 2. The ToolMessage content is below the size threshold, or is not JSON-ish — pass through.
|
|
17
|
+
//
|
|
18
|
+
// A non-ToolMessage return (a Command that updates graph state directly) always passes through: there
|
|
19
|
+
// is nothing to encode.
|
|
20
|
+
import { ToolMessage } from "@langchain/core/messages";
|
|
21
|
+
import { createMiddleware } from "langchain";
|
|
22
|
+
import { isHaloNavTool } from "./constants.js";
|
|
23
|
+
import { sizeOf, parseToolOutput, serializeEnvelope } from "./serialize.js";
|
|
24
|
+
const DEFAULT_THRESHOLD = 2048;
|
|
25
|
+
// Where an upstream tool's own artifact is preserved if Halo needs the slot for its envelope, so a
|
|
26
|
+
// tool that already used content_and_artifact does not silently lose its data.
|
|
27
|
+
const PRIOR_ARTIFACT_KEY = "halo_prior_artifact";
|
|
28
|
+
/**
|
|
29
|
+
* Encode a tool's ToolMessage and return a rewritten copy: content -> shape map, artifact -> envelope.
|
|
30
|
+
* Returns the value untouched when it should pass through (not a ToolMessage, small, or non-JSON).
|
|
31
|
+
* Builds a NEW ToolMessage rather than mutating, so a handler that returns a shared/cached message is
|
|
32
|
+
* never clobbered and the rewrite survives serialization (LangChain messages serialize from their
|
|
33
|
+
* constructor args). The original tool_call_id, name and status are preserved.
|
|
34
|
+
*/
|
|
35
|
+
export async function rewrite(session, threshold, msg, request) {
|
|
36
|
+
if (!(msg instanceof ToolMessage))
|
|
37
|
+
return msg; // a Command (state update) or other return
|
|
38
|
+
if (sizeOf(msg.content) < threshold)
|
|
39
|
+
return msg;
|
|
40
|
+
const value = parseToolOutput(msg.content);
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
return msg;
|
|
43
|
+
const { envelope } = await session.ingest(request.toolCall.name, request.toolCall.args, value);
|
|
44
|
+
const hints = await session.describe(envelope);
|
|
45
|
+
const additional_kwargs = msg.artifact !== undefined && msg.artifact !== null
|
|
46
|
+
? { ...msg.additional_kwargs, [PRIOR_ARTIFACT_KEY]: msg.artifact }
|
|
47
|
+
: msg.additional_kwargs;
|
|
48
|
+
return new ToolMessage({
|
|
49
|
+
content: serializeEnvelope(envelope, hints), // the SHAPE MAP (model-visible)
|
|
50
|
+
artifact: envelope, // full envelope (out of context, persisted in state)
|
|
51
|
+
tool_call_id: msg.tool_call_id,
|
|
52
|
+
name: msg.name,
|
|
53
|
+
status: msg.status,
|
|
54
|
+
id: msg.id,
|
|
55
|
+
additional_kwargs,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the Halo encode middleware bound to a session, for `createAgent({ middleware: [...] })`.
|
|
60
|
+
* Provides the sync/async-agnostic `wrapToolCall` hook (the handler is awaited).
|
|
61
|
+
*/
|
|
62
|
+
export function createHaloMiddleware(session, threshold = DEFAULT_THRESHOLD) {
|
|
63
|
+
return createMiddleware({
|
|
64
|
+
name: "HaloMiddleware",
|
|
65
|
+
wrapToolCall: async (request, handler) => {
|
|
66
|
+
if (isHaloNavTool(request.toolCall.name))
|
|
67
|
+
return handler(request); // never re-encode a leaf
|
|
68
|
+
const msg = await handler(request);
|
|
69
|
+
return (await rewrite(session, threshold, msg, request));
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,sGAAsG;AACtG,2FAA2F;AAC3F,qGAAqG;AACrG,sGAAsG;AACtG,iEAAiE;AACjE,EAAE;AACF,sGAAsG;AACtG,kGAAkG;AAClG,+FAA+F;AAC/F,kDAAkD;AAClD,EAAE;AACF,yEAAyE;AACzE,oGAAoG;AACpG,mEAAmE;AACnE,+FAA+F;AAC/F,EAAE;AACF,sGAAsG;AACtG,wBAAwB;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE5E,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,mGAAmG;AACnG,+EAA+E;AAC/E,MAAM,kBAAkB,GAAG,qBAAqB,CAAC;AAOjD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,OAAoB,EACpB,SAAiB,EACjB,GAAY,EACZ,OAAoB;IAEpB,IAAI,CAAC,CAAC,GAAG,YAAY,WAAW,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC,2CAA2C;IAC1F,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,SAAS;QAAE,OAAO,GAAG,CAAC;IAChD,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IAEpC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/F,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAE/C,MAAM,iBAAiB,GACrB,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI;QACjD,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,iBAAiB,EAAE,CAAC,kBAAkB,CAAC,EAAE,GAAG,CAAC,QAAQ,EAAE;QAClE,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAE5B,OAAO,IAAI,WAAW,CAAC;QACrB,OAAO,EAAE,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,gCAAgC;QAC7E,QAAQ,EAAE,QAAQ,EAAE,qDAAqD;QACzE,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,iBAAiB;KAClB,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAoB,EAAE,YAAoB,iBAAiB;IAC9F,OAAO,gBAAgB,CAAC;QACtB,IAAI,EAAE,gBAAgB;QACtB,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;YACvC,IAAI,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB;YAC5F,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,CAAe,CAAC;QACzE,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { HaloSession, FetchEntry } from "./session.js";
|
|
3
|
+
/** Fetch several refs at once, verified, with a per-ref result; branch refs return their sub-shape. */
|
|
4
|
+
export declare function haloFetch(session: HaloSession, refs: string[]): Promise<Record<string, FetchEntry>>;
|
|
5
|
+
/** Build the single halo_fetch LangChain tool over the given session. */
|
|
6
|
+
export declare function createHaloFetchTool(session: HaloSession): import("@langchain/core/tools").DynamicStructuredTool<z.ZodObject<{
|
|
7
|
+
refs: z.ZodArray<z.ZodString, "many">;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
refs: string[];
|
|
10
|
+
}, {
|
|
11
|
+
refs: string[];
|
|
12
|
+
}>, {
|
|
13
|
+
refs: string[];
|
|
14
|
+
}, {
|
|
15
|
+
refs: string[];
|
|
16
|
+
}, [string, Record<string, FetchEntry>], unknown, "halo_fetch">;
|
|
17
|
+
//# sourceMappingURL=nav-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-tool.d.ts","sourceRoot":"","sources":["../src/nav-tool.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG5D,uGAAuG;AACvG,wBAAsB,SAAS,CAC7B,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAErC;AASD,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;gEAiBvD"}
|
package/dist/nav-tool.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// The navigation surface — how the model pulls back what the middleware withheld. A single plain
|
|
2
|
+
// LangChain tool, halo_fetch, backed by the session store and verified on read. No MCP server: it is
|
|
3
|
+
// an ordinary tool, appended to the agent's tools list and bound to the model like any other.
|
|
4
|
+
//
|
|
5
|
+
// One tool on purpose: an earlier design (in the Claude adapter's history) also exposed halo_walk and
|
|
6
|
+
// the model wasted turns choosing between them and fetching a branch ref through the leaf tool.
|
|
7
|
+
// halo_fetch now does both — a leaf ref returns its value, a branch ref returns its sub-shape — so
|
|
8
|
+
// there is a single batch API and nothing to disambiguate.
|
|
9
|
+
//
|
|
10
|
+
// halo_fetch takes an ARRAY of refs: each fetch is a separate model round trip, the dominant latency
|
|
11
|
+
// in the loop, so the model is nudged to gather every ref a step needs and pull them in one call. It
|
|
12
|
+
// returns a per-ref result, so one unknown or tampered entry (surfaced as a HashMismatch) never sinks
|
|
13
|
+
// the batch.
|
|
14
|
+
//
|
|
15
|
+
// It is declared `responseFormat: "content_and_artifact"`: the model reads the per-ref values from the
|
|
16
|
+
// message content, and the same structured result is attached as the artifact (kept in graph state for
|
|
17
|
+
// audit/replay, not re-sent to the model). The pure handler (haloFetch) is split from the tool wrapping
|
|
18
|
+
// so it can be tested without the LangChain runtime.
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { tool } from "@langchain/core/tools";
|
|
21
|
+
import { HALO_FETCH_TOOL } from "./constants.js";
|
|
22
|
+
/** Fetch several refs at once, verified, with a per-ref result; branch refs return their sub-shape. */
|
|
23
|
+
export async function haloFetch(session, refs) {
|
|
24
|
+
return session.fetch(refs);
|
|
25
|
+
}
|
|
26
|
+
const FETCH_DESC = "The one tool for reading a halo map. Pass ALL the refs a step needs in one call (refs is an " +
|
|
27
|
+
"array) rather than one at a time — each call is a separate round trip. A ref like `m1.income` " +
|
|
28
|
+
"(or a raw `h:` handle) that points at a value returns it as `{ok:true,value}`; a ref that points " +
|
|
29
|
+
"at a [branch] returns `{ok:true,kind:'branch',fields:[…]}` listing its sub-refs to fetch next. " +
|
|
30
|
+
"An entry with `ok:false` (e.g. HashMismatch) means that data must not be trusted.";
|
|
31
|
+
/** Build the single halo_fetch LangChain tool over the given session. */
|
|
32
|
+
export function createHaloFetchTool(session) {
|
|
33
|
+
return tool(async ({ refs }) => {
|
|
34
|
+
const results = await haloFetch(session, refs);
|
|
35
|
+
// content: the JSON the model reads (the fetched values / branch sub-shapes).
|
|
36
|
+
// artifact: the same structured result, kept in graph state for audit/replay.
|
|
37
|
+
return [JSON.stringify(results), results];
|
|
38
|
+
}, {
|
|
39
|
+
name: HALO_FETCH_TOOL,
|
|
40
|
+
description: FETCH_DESC,
|
|
41
|
+
schema: z.object({
|
|
42
|
+
refs: z.array(z.string()).describe("The halo refs to fetch together in one call."),
|
|
43
|
+
}),
|
|
44
|
+
responseFormat: "content_and_artifact",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=nav-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-tool.js","sourceRoot":"","sources":["../src/nav-tool.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,qGAAqG;AACrG,8FAA8F;AAC9F,EAAE;AACF,sGAAsG;AACtG,gGAAgG;AAChG,mGAAmG;AACnG,2DAA2D;AAC3D,EAAE;AACF,qGAAqG;AACrG,qGAAqG;AACrG,sGAAsG;AACtG,aAAa;AACb,EAAE;AACF,uGAAuG;AACvG,uGAAuG;AACvG,wGAAwG;AACxG,qDAAqD;AAErD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAoB,EACpB,IAAc;IAEd,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,GACd,8FAA8F;IAC9F,gGAAgG;IAChG,mGAAmG;IACnG,iGAAiG;IACjG,mFAAmF,CAAC;AAEtF,yEAAyE;AACzE,MAAM,UAAU,mBAAmB,CAAC,OAAoB;IACtD,OAAO,IAAI,CACT,KAAK,EAAE,EAAE,IAAI,EAAE,EAAiD,EAAE;QAChE,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC/C,8EAA8E;QAC9E,8EAA8E;QAC9E,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC,EACD;QACE,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,UAAU;QACvB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;YACf,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,8CAA8C,CAAC;SACnF,CAAC;QACF,cAAc,EAAE,sBAAsB;KACvC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HaloEnvelope, JsonValue } from "@halo-format/halo";
|
|
2
|
+
export type FieldHint = {
|
|
3
|
+
ref: string;
|
|
4
|
+
kind: "leaf" | "branch";
|
|
5
|
+
shape: string;
|
|
6
|
+
preview?: string;
|
|
7
|
+
};
|
|
8
|
+
export type RawToolOutput = unknown;
|
|
9
|
+
/** Byte length of a raw tool output, used for the size threshold. Never throws. */
|
|
10
|
+
export declare function sizeOf(raw: RawToolOutput): number;
|
|
11
|
+
/**
|
|
12
|
+
* Coerce a raw tool output into a JSON value to encode. Strings are parsed as JSON when possible
|
|
13
|
+
* and otherwise kept as a string leaf; the MCP `{ content: [{ text }] }` block is unwrapped to its
|
|
14
|
+
* concatenated text (parsed when it is itself JSON). Anything already-structured is returned as is.
|
|
15
|
+
* Returns `undefined` when there is nothing meaningful to encode.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseToolOutput(raw: RawToolOutput): JsonValue | undefined;
|
|
18
|
+
/** A short structural tag for a leaf value: its kind plus a size for strings/arrays/objects. */
|
|
19
|
+
export declare function shapeOf(value: JsonValue): string;
|
|
20
|
+
/** A bounded, single-line JSON sample of a value — enough to recognise it, never the whole payload. */
|
|
21
|
+
export declare function previewOf(value: JsonValue, max?: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* Serialize an envelope into the string the model receives in place of the blob: the map id and root
|
|
24
|
+
* kind stated up front, then (when `hints` are supplied) one line per field with its kind and a
|
|
25
|
+
* bounded preview. With no hints it degrades to a bare ref list — still no hashes, still names the
|
|
26
|
+
* root kind, just without the per-field previews the session computes from the store.
|
|
27
|
+
*/
|
|
28
|
+
export declare function serializeEnvelope(envelope: HaloEnvelope, hints?: FieldHint[]): string;
|
|
29
|
+
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAMjE,MAAM,MAAM,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAMlG,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC;AAEpC,mFAAmF;AACnF,wBAAgB,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAQjD;AAQD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,GAAG,SAAS,CAWzE;AAqCD,gGAAgG;AAChG,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAOhD;AAED,uGAAuG;AACvG,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,GAAE,MAAoB,GAAG,MAAM,CAS7E;AA4BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,CAkBrF"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Serialization at the SDK boundary: measure a raw tool result, parse it into a JSON value the
|
|
2
|
+
// core can encode, and serialize a halo envelope back into the string the model sees.
|
|
3
|
+
//
|
|
4
|
+
// updatedToolOutput carries the tool's visible output. What the model sees in place of the blob is a
|
|
5
|
+
// SHAPE MAP, not the raw envelope: the root kind stated up front, then one line per field giving its
|
|
6
|
+
// ref, its kind (leaf vs [branch]), and a bounded preview. This is deliberately not the hashed
|
|
7
|
+
// envelope JSON — the 64-char branch handles are dead weight to the model (it navigates by
|
|
8
|
+
// `<mapId>.<field>` refs, which the session's navigator resolves from its own registry), and showing
|
|
9
|
+
// only names without kinds is exactly what made the model guess the root kind and fetch a branch as
|
|
10
|
+
// if it were a leaf. The hints close both gaps. The envelope object itself is unchanged and still
|
|
11
|
+
// held verbatim in the session for verified reads.
|
|
12
|
+
const PREVIEW_MAX = 80;
|
|
13
|
+
/** Byte length of a raw tool output, used for the size threshold. Never throws. */
|
|
14
|
+
export function sizeOf(raw) {
|
|
15
|
+
if (raw === undefined || raw === null)
|
|
16
|
+
return 0;
|
|
17
|
+
if (typeof raw === "string")
|
|
18
|
+
return byteLength(raw);
|
|
19
|
+
try {
|
|
20
|
+
return byteLength(JSON.stringify(raw));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return 0; // unserializable (e.g. a cycle) — treat as below threshold, pass through
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function byteLength(s) {
|
|
27
|
+
// Prefer a real byte count; fall back to length if TextEncoder is unavailable.
|
|
28
|
+
if (typeof TextEncoder !== "undefined")
|
|
29
|
+
return new TextEncoder().encode(s).length;
|
|
30
|
+
return s.length;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Coerce a raw tool output into a JSON value to encode. Strings are parsed as JSON when possible
|
|
34
|
+
* and otherwise kept as a string leaf; the MCP `{ content: [{ text }] }` block is unwrapped to its
|
|
35
|
+
* concatenated text (parsed when it is itself JSON). Anything already-structured is returned as is.
|
|
36
|
+
* Returns `undefined` when there is nothing meaningful to encode.
|
|
37
|
+
*/
|
|
38
|
+
export function parseToolOutput(raw) {
|
|
39
|
+
if (raw === undefined || raw === null)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (typeof raw === "string")
|
|
42
|
+
return parseMaybeJson(raw);
|
|
43
|
+
if (typeof raw === "object") {
|
|
44
|
+
// The MCP result may arrive as `{ content: [{type:text,text}] }` OR as the bare content-block
|
|
45
|
+
// array `[{type:text,text}]` (the shape some SDK hosts hand the hook). Unwrap either.
|
|
46
|
+
const text = extractContentText(raw);
|
|
47
|
+
if (text !== undefined)
|
|
48
|
+
return parseMaybeJson(text);
|
|
49
|
+
return raw;
|
|
50
|
+
}
|
|
51
|
+
return raw; // number | boolean
|
|
52
|
+
}
|
|
53
|
+
function parseMaybeJson(s) {
|
|
54
|
+
const trimmed = s.trim();
|
|
55
|
+
if (trimmed === "")
|
|
56
|
+
return s;
|
|
57
|
+
const first = trimmed[0];
|
|
58
|
+
// Only attempt a parse on something that looks like JSON, so plain prose stays a string leaf.
|
|
59
|
+
if (first === "{" || first === "[" || first === '"') {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(trimmed);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return s;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return s;
|
|
68
|
+
}
|
|
69
|
+
// Unwrap the MCP content-block shape to its text, concatenating multiple text blocks. Accepts both
|
|
70
|
+
// the `{ content: [...] }` object and a bare `[{type:text,text}]` array. Returns undefined when the
|
|
71
|
+
// value is not content blocks (a genuine array/object result), so it is encoded as-is.
|
|
72
|
+
function extractContentText(value) {
|
|
73
|
+
const content = Array.isArray(value)
|
|
74
|
+
? value
|
|
75
|
+
: value && typeof value === "object" && Array.isArray(value.content)
|
|
76
|
+
? (value.content)
|
|
77
|
+
: null;
|
|
78
|
+
if (!content)
|
|
79
|
+
return undefined;
|
|
80
|
+
const texts = [];
|
|
81
|
+
for (const block of content) {
|
|
82
|
+
if (block && typeof block === "object" && block.type === "text") {
|
|
83
|
+
const t = block.text;
|
|
84
|
+
if (typeof t === "string")
|
|
85
|
+
texts.push(t);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return texts.length > 0 ? texts.join("") : undefined;
|
|
89
|
+
}
|
|
90
|
+
/** A short structural tag for a leaf value: its kind plus a size for strings/arrays/objects. */
|
|
91
|
+
export function shapeOf(value) {
|
|
92
|
+
if (value === null)
|
|
93
|
+
return "null";
|
|
94
|
+
if (typeof value === "boolean")
|
|
95
|
+
return "boolean";
|
|
96
|
+
if (typeof value === "number")
|
|
97
|
+
return "number";
|
|
98
|
+
if (typeof value === "string")
|
|
99
|
+
return `string[${value.length}]`;
|
|
100
|
+
if (Array.isArray(value))
|
|
101
|
+
return `array[${value.length}]`;
|
|
102
|
+
return `object{${Object.keys(value).length}}`;
|
|
103
|
+
}
|
|
104
|
+
/** A bounded, single-line JSON sample of a value — enough to recognise it, never the whole payload. */
|
|
105
|
+
export function previewOf(value, max = PREVIEW_MAX) {
|
|
106
|
+
let s;
|
|
107
|
+
try {
|
|
108
|
+
s = JSON.stringify(value);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
if (s === undefined)
|
|
114
|
+
return "";
|
|
115
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}…`;
|
|
116
|
+
}
|
|
117
|
+
// Render one field line: ref, structural tag, then the preview/child-names. A leaf primitive reads
|
|
118
|
+
// "ref number = 1843"; an object leaf "ref object{4} {…}"; a branch "ref [branch] object{7} ↳ a, b".
|
|
119
|
+
function hintLine(h) {
|
|
120
|
+
const tail = h.preview ? ` ${h.kind === "leaf" && isScalarShape(h.shape) ? "= " : ""}${h.preview}` : "";
|
|
121
|
+
return ` ${h.ref} ${h.shape}${tail}`;
|
|
122
|
+
}
|
|
123
|
+
function isScalarShape(shape) {
|
|
124
|
+
return shape === "null" || shape === "boolean" || shape === "number";
|
|
125
|
+
}
|
|
126
|
+
// "object, 7 fields" / "array, 4 chunks" for a branch root; for a leaf root reuse the view summary.
|
|
127
|
+
function rootShape(names) {
|
|
128
|
+
const n = names.length;
|
|
129
|
+
const arrayLike = n > 0 && names.every((name) => /^\d+$/.test(name));
|
|
130
|
+
return arrayLike ? `array, ${n} chunks` : `object, ${n} field${n === 1 ? "" : "s"}`;
|
|
131
|
+
}
|
|
132
|
+
function shortTool(tool) {
|
|
133
|
+
if (tool.startsWith("mcp__")) {
|
|
134
|
+
const parts = tool.split("__");
|
|
135
|
+
if (parts.length >= 3)
|
|
136
|
+
return parts.slice(2).join("__");
|
|
137
|
+
}
|
|
138
|
+
return tool;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Serialize an envelope into the string the model receives in place of the blob: the map id and root
|
|
142
|
+
* kind stated up front, then (when `hints` are supplied) one line per field with its kind and a
|
|
143
|
+
* bounded preview. With no hints it degrades to a bare ref list — still no hashes, still names the
|
|
144
|
+
* root kind, just without the per-field previews the session computes from the store.
|
|
145
|
+
*/
|
|
146
|
+
export function serializeEnvelope(envelope, hints) {
|
|
147
|
+
const id = envelope.source?.id ?? "?";
|
|
148
|
+
const names = Object.keys(envelope.view.branches);
|
|
149
|
+
const isLeafRoot = names.length === 0;
|
|
150
|
+
const root = isLeafRoot ? envelope.view.summary.replace(/^leaf:\s*/, "") : rootShape(names);
|
|
151
|
+
const from = envelope.source?.tool ? ` from ${shortTool(envelope.source.tool)}` : "";
|
|
152
|
+
const head = `[halo] map "${id}" — ${root}${from}, stored out of context. ` +
|
|
153
|
+
`Pull only the fields you need with ONE halo_fetch call — batch every ref into that one call; ` +
|
|
154
|
+
`each call is a round trip. A [branch] ref expands to its sub-refs when fetched; every other ` +
|
|
155
|
+
`ref returns its value.`;
|
|
156
|
+
if (isLeafRoot)
|
|
157
|
+
return `${head}\nFetch "${id}" itself to read the value.`;
|
|
158
|
+
const lines = hints && hints.length ? hints.map(hintLine) : names.map((n) => ` ${id}.${n}`);
|
|
159
|
+
return `${head}\nFields:\n${lines.join("\n")}`;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.js","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type HaloEnvelope, type JsonValue, type Store, type WalkResult, type Alg } from "@halo-format/halo";
|
|
2
|
+
import { type KeyOf } from "./accumulate.js";
|
|
3
|
+
import { type FieldHint } from "./serialize.js";
|
|
4
|
+
export type SessionOptions = {
|
|
5
|
+
store?: Store;
|
|
6
|
+
keyOf?: KeyOf;
|
|
7
|
+
alg?: Alg;
|
|
8
|
+
now?: () => string;
|
|
9
|
+
};
|
|
10
|
+
export type IngestResult = {
|
|
11
|
+
envelope: HaloEnvelope;
|
|
12
|
+
id: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* One batch-fetch entry. A leaf ref returns its value. A ref that lands on a BRANCH does not fail —
|
|
16
|
+
* it returns that branch's sub-shape (its child refs + hints), so the single halo_fetch tool both
|
|
17
|
+
* pulls leaves and expands branches and the model never needs a separate walk tool. A bad ref is a
|
|
18
|
+
* per-entry error and never sinks the rest of the batch.
|
|
19
|
+
*/
|
|
20
|
+
export type FetchEntry = {
|
|
21
|
+
ok: true;
|
|
22
|
+
value: JsonValue;
|
|
23
|
+
} | {
|
|
24
|
+
ok: true;
|
|
25
|
+
kind: "branch";
|
|
26
|
+
note: string;
|
|
27
|
+
fields: FieldHint[];
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
32
|
+
export declare class HaloSession {
|
|
33
|
+
readonly store: Store;
|
|
34
|
+
private readonly keyOf;
|
|
35
|
+
private readonly alg;
|
|
36
|
+
private readonly now;
|
|
37
|
+
private readonly maps;
|
|
38
|
+
private readonly entityTools;
|
|
39
|
+
private navigator;
|
|
40
|
+
private synthetic;
|
|
41
|
+
constructor(opts?: SessionOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Encode a tool result into the store and return the resulting envelope.
|
|
44
|
+
*
|
|
45
|
+
* A map's FIRST result is encoded flat — the value's own fields become the top-level branches, so
|
|
46
|
+
* the model sees them in the envelope and can batch-fetch leaves with no extra walk. Only when a
|
|
47
|
+
* SECOND tool result accumulates into the same entity map (per keyOf) do we namespace each tool's
|
|
48
|
+
* tree under its (short) name, since then the field names would otherwise collide.
|
|
49
|
+
*/
|
|
50
|
+
ingest(toolName: string, toolInput: unknown, value: JsonValue): Promise<IngestResult>;
|
|
51
|
+
private registerEnvelope;
|
|
52
|
+
/** Verified walk of a branch ref (raw handle or `mapId.branch[.child]`). Internal — not a tool. */
|
|
53
|
+
walk(ref: string): Promise<WalkResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Verified batch fetch — the single navigation surface. Leaves return their value; a ref that
|
|
56
|
+
* lands on a branch returns the branch's sub-shape instead of a WrongKind error, so one tool
|
|
57
|
+
* covers both pulling and expanding. One bad ref never sinks the others.
|
|
58
|
+
*/
|
|
59
|
+
fetch(refs: string[]): Promise<Record<string, FetchEntry>>;
|
|
60
|
+
/**
|
|
61
|
+
* Describe an envelope's top-level fields for the shape map the model sees: one FieldHint per
|
|
62
|
+
* branch, classified (leaf vs branch) and previewed by reading each child node. Sorted by ref so
|
|
63
|
+
* the listing order is deterministic (matching the structural summary's sorted names).
|
|
64
|
+
*/
|
|
65
|
+
describe(envelope: HaloEnvelope): Promise<FieldHint[]>;
|
|
66
|
+
private expand;
|
|
67
|
+
private hintFor;
|
|
68
|
+
private read;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,SAAS,EAEd,KAAK,KAAK,EACV,KAAK,UAAU,EACf,KAAK,GAAG,EAcT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAW,KAAK,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,gBAAgB,CAAC;AAWpE,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,EAAE,YAAY,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,EAAE,CAAA;CAAE,GAC/D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,qBAAa,WAAW;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAGnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmC;IAExD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6C;IAEzE,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,SAAS,CAAK;gBAEV,IAAI,GAAE,cAAmB;IAOrC;;;;;;;OAOG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC;IA4B3F,OAAO,CAAC,gBAAgB;IAKxB,mGAAmG;IAC7F,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAK5C;;;;OAIG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IA4BhE;;;;OAIG;IACG,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;YAiB9C,MAAM;YAkBN,OAAO;YAeP,IAAI;CAKnB"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// HaloSession holds the one piece of shared state in the adapter — the store and the per-entity map
|
|
2
|
+
// registry — and exposes the three operations the hook and the navigation tools sit on:
|
|
3
|
+
//
|
|
4
|
+
// ingest(toolName, toolInput, value) -> envelope (producer; folds into the entity's map)
|
|
5
|
+
// walk(ref) -> branch summary + child handles
|
|
6
|
+
// fetch(refs[]) -> verified leaves, per-ref
|
|
7
|
+
//
|
|
8
|
+
// One entity, one map (SDK architecture, Section 9.1): keyOf decides which calls share a map id. A
|
|
9
|
+
// first call for an id `encode`s `{ [tool]: value }`; later calls for the same id `extend` that
|
|
10
|
+
// map's root with a branch named after the tool, so a customer assembled across several endpoints
|
|
11
|
+
// becomes one growing map rather than several fragments. Because extend reuses unchanged child
|
|
12
|
+
// handles and retains the prior root, each entity map also carries its own version history for free.
|
|
13
|
+
//
|
|
14
|
+
// Every read is verified by the core Navigator, so the store stays untrusted. The Navigator resolves
|
|
15
|
+
// map-scoped refs (`m1.income`, `123.get_appointments`) against the registered envelopes; raw
|
|
16
|
+
// handles always work without registration.
|
|
17
|
+
import { MemoryStore, Navigator, HashMismatch, WrongKind, encode, branchNode, serialize, deriveSummary, decode, isBranch, isLeaf, hashBytes, buildEnvelope, } from "@halo-format/halo";
|
|
18
|
+
import { argJoin } from "./accumulate.js";
|
|
19
|
+
import { shapeOf, previewOf } from "./serialize.js";
|
|
20
|
+
/** A short branch label for an accumulated tool: strip the `mcp__<server>__` prefix. */
|
|
21
|
+
function branchName(toolName) {
|
|
22
|
+
if (toolName.startsWith("mcp__")) {
|
|
23
|
+
const parts = toolName.split("__");
|
|
24
|
+
if (parts.length >= 3)
|
|
25
|
+
return parts.slice(2).join("__");
|
|
26
|
+
}
|
|
27
|
+
return toolName;
|
|
28
|
+
}
|
|
29
|
+
export class HaloSession {
|
|
30
|
+
store;
|
|
31
|
+
keyOf;
|
|
32
|
+
alg;
|
|
33
|
+
now;
|
|
34
|
+
// id -> latest envelope for that entity, for ref resolution and accumulation.
|
|
35
|
+
maps = new Map();
|
|
36
|
+
// id -> { branchName: subtreeRootHandle }: the per-tool trees folded into each entity map.
|
|
37
|
+
entityTools = new Map();
|
|
38
|
+
// One navigator, seeded by the first map and extended with register() as maps appear.
|
|
39
|
+
navigator = null;
|
|
40
|
+
synthetic = 0;
|
|
41
|
+
constructor(opts = {}) {
|
|
42
|
+
this.store = opts.store ?? new MemoryStore();
|
|
43
|
+
this.keyOf = opts.keyOf ?? argJoin;
|
|
44
|
+
this.alg = opts.alg ?? "sha256";
|
|
45
|
+
this.now = opts.now ?? (() => new Date().toISOString());
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Encode a tool result into the store and return the resulting envelope.
|
|
49
|
+
*
|
|
50
|
+
* A map's FIRST result is encoded flat — the value's own fields become the top-level branches, so
|
|
51
|
+
* the model sees them in the envelope and can batch-fetch leaves with no extra walk. Only when a
|
|
52
|
+
* SECOND tool result accumulates into the same entity map (per keyOf) do we namespace each tool's
|
|
53
|
+
* tree under its (short) name, since then the field names would otherwise collide.
|
|
54
|
+
*/
|
|
55
|
+
async ingest(toolName, toolInput, value) {
|
|
56
|
+
const id = this.keyOf(toolName, toolInput) ?? `m${++this.synthetic}`;
|
|
57
|
+
const opts = { store: this.store, alg: this.alg };
|
|
58
|
+
// Encode the value's own tree once; reuse its root as this tool's subtree under the entity.
|
|
59
|
+
const sub = await encode(value, opts);
|
|
60
|
+
const tools = this.entityTools.get(id) ?? {};
|
|
61
|
+
tools[branchName(toolName)] = sub.handle;
|
|
62
|
+
this.entityTools.set(id, tools);
|
|
63
|
+
let envelope;
|
|
64
|
+
if (Object.keys(tools).length === 1) {
|
|
65
|
+
// Single result: the map IS the value's tree — fields are top-level, no walk needed.
|
|
66
|
+
envelope = sub.envelope;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Accumulation: namespace each tool's tree under its name (reusing every subtree handle; only
|
|
70
|
+
// the new root node is stored).
|
|
71
|
+
const root = await this.store.put(serialize(branchNode(deriveSummary(null, tools), tools)));
|
|
72
|
+
envelope = buildEnvelope(root, decode(await this.store.get(root)), this.alg);
|
|
73
|
+
}
|
|
74
|
+
// source identifies the map and traces it to the call; it is envelope-only and never hashed.
|
|
75
|
+
envelope.source = { id, tool: toolName, args: toolInput, ts: this.now() };
|
|
76
|
+
this.maps.set(id, envelope);
|
|
77
|
+
this.registerEnvelope(envelope);
|
|
78
|
+
return { envelope, id };
|
|
79
|
+
}
|
|
80
|
+
registerEnvelope(envelope) {
|
|
81
|
+
if (this.navigator)
|
|
82
|
+
this.navigator.register(envelope);
|
|
83
|
+
else
|
|
84
|
+
this.navigator = new Navigator(envelope, this.store);
|
|
85
|
+
}
|
|
86
|
+
/** Verified walk of a branch ref (raw handle or `mapId.branch[.child]`). Internal — not a tool. */
|
|
87
|
+
async walk(ref) {
|
|
88
|
+
if (!this.navigator)
|
|
89
|
+
throw new Error("no halo maps in this session yet");
|
|
90
|
+
return this.navigator.walk(ref);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Verified batch fetch — the single navigation surface. Leaves return their value; a ref that
|
|
94
|
+
* lands on a branch returns the branch's sub-shape instead of a WrongKind error, so one tool
|
|
95
|
+
* covers both pulling and expanding. One bad ref never sinks the others.
|
|
96
|
+
*/
|
|
97
|
+
async fetch(refs) {
|
|
98
|
+
const out = {};
|
|
99
|
+
if (!this.navigator) {
|
|
100
|
+
for (const r of refs)
|
|
101
|
+
out[r] = { ok: false, error: "UnknownHandle" };
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
// The core batch verifies every leaf in one round trip; branch refs come back WrongKind, which
|
|
105
|
+
// we turn into a sub-shape expansion below rather than surfacing as an error.
|
|
106
|
+
const base = await this.navigator.fetchMany(refs);
|
|
107
|
+
await Promise.all(refs.map(async (r) => {
|
|
108
|
+
const res = base[r];
|
|
109
|
+
if (res.ok) {
|
|
110
|
+
out[r] = { ok: true, value: res.value };
|
|
111
|
+
}
|
|
112
|
+
else if (res.error === "WrongKind") {
|
|
113
|
+
try {
|
|
114
|
+
out[r] = await this.expand(r);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
out[r] = res;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
out[r] = res;
|
|
122
|
+
}
|
|
123
|
+
}));
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Describe an envelope's top-level fields for the shape map the model sees: one FieldHint per
|
|
128
|
+
* branch, classified (leaf vs branch) and previewed by reading each child node. Sorted by ref so
|
|
129
|
+
* the listing order is deterministic (matching the structural summary's sorted names).
|
|
130
|
+
*/
|
|
131
|
+
async describe(envelope) {
|
|
132
|
+
const id = envelope.source?.id ?? "";
|
|
133
|
+
const hints = await Promise.all(Object.entries(envelope.view.branches).map(async ([name, handle]) => {
|
|
134
|
+
const ref = id ? `${id}.${name}` : name;
|
|
135
|
+
try {
|
|
136
|
+
return await this.hintFor(ref, handle);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return { ref, kind: "leaf", shape: "?" };
|
|
140
|
+
}
|
|
141
|
+
}));
|
|
142
|
+
return hints.sort((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
|
|
143
|
+
}
|
|
144
|
+
// Expand a branch ref into a FetchEntry carrying its child refs + hints (the in-fetch fallback so
|
|
145
|
+
// a mis-fetched branch self-corrects in the same call instead of dead-ending on WrongKind).
|
|
146
|
+
async expand(ref) {
|
|
147
|
+
const handle = await this.navigator.resolve(ref);
|
|
148
|
+
const node = await this.read(handle);
|
|
149
|
+
if (!isBranch(node))
|
|
150
|
+
throw new WrongKind(`expand expects a branch: ${ref}`);
|
|
151
|
+
const fields = await Promise.all(Object.entries(node.branches).map(([name, h]) => this.hintFor(`${ref}.${name}`, h)));
|
|
152
|
+
fields.sort((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
|
|
153
|
+
return {
|
|
154
|
+
ok: true,
|
|
155
|
+
kind: "branch",
|
|
156
|
+
note: "This ref is a branch, not a value. halo_fetch the leaf refs below to read it.",
|
|
157
|
+
fields,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Classify one child handle into a FieldHint: a leaf gets its shape + a bounded value preview; a
|
|
161
|
+
// branch gets a "[branch]" shape and its child names as the preview (one level, bounded).
|
|
162
|
+
async hintFor(ref, handle) {
|
|
163
|
+
const node = await this.read(handle);
|
|
164
|
+
if (isLeaf(node)) {
|
|
165
|
+
return { ref, kind: "leaf", shape: shapeOf(node.value), preview: previewOf(node.value) };
|
|
166
|
+
}
|
|
167
|
+
const childNames = Object.keys(node.branches);
|
|
168
|
+
const arrayLike = childNames.length > 0 && childNames.every((n) => /^\d+$/.test(n));
|
|
169
|
+
const shape = `[branch] ${arrayLike ? "array" : "object"}{${childNames.length}}`;
|
|
170
|
+
const shown = childNames.slice(0, MAX_CHILD_NAMES).join(", ");
|
|
171
|
+
const more = childNames.length > MAX_CHILD_NAMES ? ", …" : "";
|
|
172
|
+
return { ref, kind: "branch", shape, preview: `↳ ${shown}${more}` };
|
|
173
|
+
}
|
|
174
|
+
// A verified read of a handle from the store: re-hash the bytes under the bound alg before decode,
|
|
175
|
+
// so the shape map can never describe substituted bytes. Same check the Navigator makes on fetch.
|
|
176
|
+
async read(handle) {
|
|
177
|
+
const bytes = await this.store.get(handle);
|
|
178
|
+
if (hashBytes(bytes, this.alg) !== handle)
|
|
179
|
+
throw new HashMismatch(handle);
|
|
180
|
+
return decode(bytes);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const MAX_CHILD_NAMES = 10;
|
|
184
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,wFAAwF;AACxF,EAAE;AACF,6FAA6F;AAC7F,yEAAyE;AACzE,mEAAmE;AACnE,EAAE;AACF,mGAAmG;AACnG,gGAAgG;AAChG,kGAAkG;AAClG,+FAA+F;AAC/F,qGAAqG;AACrG,EAAE;AACF,qGAAqG;AACrG,8FAA8F;AAC9F,4CAA4C;AAE5C,OAAO,EAQL,WAAW,EACX,SAAS,EACT,YAAY,EACZ,SAAS,EACT,MAAM,EACN,UAAU,EACV,SAAS,EACT,aAAa,EACb,MAAM,EACN,QAAQ,EACR,MAAM,EACN,SAAS,EACT,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,OAAO,EAAc,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAkB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEpE,wFAAwF;AACxF,SAAS,UAAU,CAAC,QAAgB;IAClC,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAsBD,MAAM,OAAO,WAAW;IACb,KAAK,CAAQ;IACL,KAAK,CAAQ;IACb,GAAG,CAAM;IACT,GAAG,CAAe;IAEnC,8EAA8E;IAC7D,IAAI,GAAG,IAAI,GAAG,EAAwB,CAAC;IACxD,2FAA2F;IAC1E,WAAW,GAAG,IAAI,GAAG,EAAkC,CAAC;IACzE,sFAAsF;IAC9E,SAAS,GAAqB,IAAI,CAAC;IACnC,SAAS,GAAG,CAAC,CAAC;IAEtB,YAAY,OAAuB,EAAE;QACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC;QACnC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,QAAQ,CAAC;QAChC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,SAAkB,EAAE,KAAgB;QACjE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QACrE,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;QAElD,4FAA4F;QAC5F,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAC7C,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;QACzC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAEhC,IAAI,QAAsB,CAAC;QAC3B,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,qFAAqF;YACrF,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,8FAA8F;YAC9F,gCAAgC;YAChC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5F,QAAQ,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/E,CAAC;QAED,6FAA6F;QAC7F,QAAQ,CAAC,MAAM,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAsB,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACvF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC5B,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1B,CAAC;IAEO,gBAAgB,CAAC,QAAsB;QAC7C,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;;YACjD,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC;IAED,mGAAmG;IACnG,KAAK,CAAC,IAAI,CAAC,GAAW;QACpB,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,IAAc;QACxB,MAAM,GAAG,GAA+B,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,KAAK,MAAM,CAAC,IAAI,IAAI;gBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;YACrE,OAAO,GAAG,CAAC;QACb,CAAC;QACD,+FAA+F;QAC/F,8EAA8E;QAC9E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,OAAO,CAAC,GAAG,CACf,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;YACnB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;YACrB,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACX,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;YAC1C,CAAC;iBAAM,IAAI,GAAG,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;gBACrC,IAAI,CAAC;oBACH,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAChC,CAAC;gBAAC,MAAM,CAAC;oBACP,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;gBACf,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QACF,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,QAAsB;QACnC,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE;YAClE,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YACxC,IAAI,CAAC;gBACH,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAe,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;YACpD,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,kGAAkG;IAClG,4FAA4F;IACpF,KAAK,CAAC,MAAM,CAAC,GAAW;QAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAC;QAC5E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CACpF,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,OAAO;YACL,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,+EAA+E;YACrF,MAAM;SACP,CAAC;IACJ,CAAC;IAED,iGAAiG;IACjG,0FAA0F;IAClF,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,MAAc;QAC/C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACjB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3F,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACpF,MAAM,KAAK,GAAG,YAAY,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QACjF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,KAAK,GAAG,IAAI,EAAE,EAAE,CAAC;IACtE,CAAC;IAED,mGAAmG;IACnG,kGAAkG;IAC1F,KAAK,CAAC,IAAI,CAAC,MAAc;QAC/B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,MAAM;YAAE,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;QAC1E,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;CACF;AAED,MAAM,eAAe,GAAG,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@halo-format/langgraph",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Halo host adapter for LangChain agents and LangGraph graphs: a wrapToolCall encode middleware (results become a shape map, out of context) plus a single halo_fetch navigation tool. installHalo() wires it in one call.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"zod": "^3.25.0",
|
|
17
|
+
"@halo-format/halo": "0.3.0"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@langchain/core": ">=1.0.0",
|
|
21
|
+
"langchain": ">=1.0.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependenciesMeta": {
|
|
24
|
+
"@langchain/langgraph": {
|
|
25
|
+
"optional": true
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@langchain/core": "^1.0.0",
|
|
30
|
+
"@langchain/langgraph": "^1.0.0",
|
|
31
|
+
"langchain": "^1.0.0",
|
|
32
|
+
"vitest": "^1.5.0"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public",
|
|
37
|
+
"provenance": true
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"halo",
|
|
41
|
+
"langchain",
|
|
42
|
+
"langgraph",
|
|
43
|
+
"middleware",
|
|
44
|
+
"ai-agents",
|
|
45
|
+
"tool-results"
|
|
46
|
+
],
|
|
47
|
+
"homepage": "https://github.com/halo-format/halo/tree/main/ts/packages/langgraph#readme",
|
|
48
|
+
"bugs": "https://github.com/halo-format/halo/issues",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/halo-format/halo.git",
|
|
52
|
+
"directory": "ts/packages/langgraph"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc -p tsconfig.json",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
57
|
+
"test": "vitest run --passWithNoTests"
|
|
58
|
+
}
|
|
59
|
+
}
|