@dawn-ai/langchain 0.1.8 → 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/LICENSE +1 -1
- package/README.md +16 -2
- package/dist/agent-adapter.d.ts +60 -4
- package/dist/agent-adapter.d.ts.map +1 -1
- package/dist/agent-adapter.js +396 -77
- package/dist/chat-model-factory.d.ts +11 -0
- package/dist/chat-model-factory.d.ts.map +1 -0
- package/dist/chat-model-factory.js +55 -0
- package/dist/index.d.ts +19 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/model-provider-resolver.d.ts +8 -0
- package/dist/model-provider-resolver.d.ts.map +1 -0
- package/dist/model-provider-resolver.js +40 -0
- package/dist/offload/offload-store.d.ts +30 -0
- package/dist/offload/offload-store.d.ts.map +1 -0
- package/dist/offload/offload-store.js +105 -0
- package/dist/offload/offload-tool-output.d.ts +10 -0
- package/dist/offload/offload-tool-output.d.ts.map +1 -0
- package/dist/offload/offload-tool-output.js +18 -0
- package/dist/offload/stub.d.ts +8 -0
- package/dist/offload/stub.d.ts.map +1 -0
- package/dist/offload/stub.js +14 -0
- package/dist/subagent-dispatcher.d.ts +33 -0
- package/dist/subagent-dispatcher.d.ts.map +1 -0
- package/dist/subagent-dispatcher.js +182 -0
- package/dist/subagent-tool-bridge.d.ts +27 -0
- package/dist/subagent-tool-bridge.d.ts.map +1 -0
- package/dist/subagent-tool-bridge.js +32 -0
- package/dist/summarization/hook.d.ts +31 -0
- package/dist/summarization/hook.d.ts.map +1 -0
- package/dist/summarization/hook.js +44 -0
- package/dist/summarization/index.d.ts +5 -0
- package/dist/summarization/index.d.ts.map +1 -0
- package/dist/summarization/index.js +4 -0
- package/dist/summarization/split.d.ts +12 -0
- package/dist/summarization/split.d.ts.map +1 -0
- package/dist/summarization/split.js +27 -0
- package/dist/summarization/summarize.d.ts +10 -0
- package/dist/summarization/summarize.d.ts.map +1 -0
- package/dist/summarization/summarize.js +30 -0
- package/dist/summarization/token-counter.d.ts +10 -0
- package/dist/summarization/token-counter.d.ts.map +1 -0
- package/dist/summarization/token-counter.js +29 -0
- package/dist/tool-converter.d.ts +5 -1
- package/dist/tool-converter.d.ts.map +1 -1
- package/dist/tool-converter.js +74 -21
- package/dist/unwrap-tool-result.d.ts +35 -0
- package/dist/unwrap-tool-result.d.ts.map +1 -0
- package/dist/unwrap-tool-result.js +42 -0
- package/package.json +55 -10
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BaseMessage } from "@langchain/core/messages";
|
|
2
|
+
/**
|
|
3
|
+
* Split into { toSummarize, recent }, keeping the last `keepRecentTurns` turns
|
|
4
|
+
* verbatim. A turn begins at a HumanMessage; slicing on a HumanMessage boundary
|
|
5
|
+
* keeps every tool round (AIMessage-with-tool_calls + its ToolMessages) intact,
|
|
6
|
+
* so `recent` never starts mid-round. Fewer turns than requested -> all recent.
|
|
7
|
+
*/
|
|
8
|
+
export declare function splitForSummary(messages: readonly BaseMessage[], keepRecentTurns: number): {
|
|
9
|
+
toSummarize: BaseMessage[];
|
|
10
|
+
recent: BaseMessage[];
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=split.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../src/summarization/split.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAS3D;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,eAAe,EAAE,MAAM,GACtB;IAAE,WAAW,EAAE,WAAW,EAAE,CAAC;IAAC,MAAM,EAAE,WAAW,EAAE,CAAA;CAAE,CAWvD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function isHuman(m) {
|
|
2
|
+
const getType = m.getType;
|
|
3
|
+
if (typeof getType === "function")
|
|
4
|
+
return getType.call(m) === "human";
|
|
5
|
+
const legacy = m._getType;
|
|
6
|
+
return typeof legacy === "function" ? legacy.call(m) === "human" : false;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Split into { toSummarize, recent }, keeping the last `keepRecentTurns` turns
|
|
10
|
+
* verbatim. A turn begins at a HumanMessage; slicing on a HumanMessage boundary
|
|
11
|
+
* keeps every tool round (AIMessage-with-tool_calls + its ToolMessages) intact,
|
|
12
|
+
* so `recent` never starts mid-round. Fewer turns than requested -> all recent.
|
|
13
|
+
*/
|
|
14
|
+
export function splitForSummary(messages, keepRecentTurns) {
|
|
15
|
+
if (keepRecentTurns <= 0)
|
|
16
|
+
return { toSummarize: [...messages], recent: [] };
|
|
17
|
+
const humanIdx = [];
|
|
18
|
+
messages.forEach((m, i) => {
|
|
19
|
+
if (isHuman(m))
|
|
20
|
+
humanIdx.push(i);
|
|
21
|
+
});
|
|
22
|
+
if (humanIdx.length <= keepRecentTurns) {
|
|
23
|
+
return { toSummarize: [], recent: [...messages] };
|
|
24
|
+
}
|
|
25
|
+
const cut = humanIdx[humanIdx.length - keepRecentTurns];
|
|
26
|
+
return { toSummarize: messages.slice(0, cut), recent: messages.slice(cut) };
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BaseMessage } from "@langchain/core/messages";
|
|
2
|
+
export declare function defaultSummarize(args: {
|
|
3
|
+
readonly messages: readonly BaseMessage[];
|
|
4
|
+
readonly model: string;
|
|
5
|
+
readonly previousSummary?: string;
|
|
6
|
+
readonly signal: AbortSignal;
|
|
7
|
+
/** Test seam: override the model invocation. */
|
|
8
|
+
readonly invokeModel?: (prompt: string) => Promise<string>;
|
|
9
|
+
}): Promise<string>;
|
|
10
|
+
//# sourceMappingURL=summarize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.d.ts","sourceRoot":"","sources":["../../src/summarization/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AA4B3D,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC3D,GAAG,OAAO,CAAC,MAAM,CAAC,CAUlB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createChatModel } from "../chat-model-factory.js";
|
|
2
|
+
import { resolveProvider } from "../model-provider-resolver.js";
|
|
3
|
+
function renderMessages(messages) {
|
|
4
|
+
return messages
|
|
5
|
+
.map((m) => {
|
|
6
|
+
const getType = m.getType;
|
|
7
|
+
const role = typeof getType === "function" ? getType.call(m) : "message";
|
|
8
|
+
const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
9
|
+
const toolCalls = m.tool_calls;
|
|
10
|
+
const tc = toolCalls && toolCalls.length > 0 ? `\n[tool_calls: ${JSON.stringify(toolCalls)}]` : "";
|
|
11
|
+
return `${role}: ${content}${tc}`;
|
|
12
|
+
})
|
|
13
|
+
.join("\n");
|
|
14
|
+
}
|
|
15
|
+
function buildPrompt(messages, previousSummary) {
|
|
16
|
+
const prior = previousSummary ? `Existing running summary so far:\n${previousSummary}\n\n` : "";
|
|
17
|
+
return (`${prior}New conversation messages to fold into the summary:\n${renderMessages(messages)}\n\n` +
|
|
18
|
+
`Write an updated, concise running summary of the ENTIRE conversation so far. ` +
|
|
19
|
+
`Preserve concrete facts, decisions, user goals, tool results, identifiers, and open questions. ` +
|
|
20
|
+
`Do not invent information. Output only the summary text.`);
|
|
21
|
+
}
|
|
22
|
+
export async function defaultSummarize(args) {
|
|
23
|
+
const prompt = buildPrompt(args.messages, args.previousSummary);
|
|
24
|
+
if (args.invokeModel)
|
|
25
|
+
return args.invokeModel(prompt);
|
|
26
|
+
const provider = resolveProvider({ model: args.model });
|
|
27
|
+
const llm = (await createChatModel({ model: args.model, provider }));
|
|
28
|
+
const res = await llm.invoke([{ role: "user", content: prompt }], { signal: args.signal });
|
|
29
|
+
return typeof res.content === "string" ? res.content : JSON.stringify(res.content);
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BaseMessage } from "@langchain/core/messages";
|
|
2
|
+
/**
|
|
3
|
+
* Default token counter. Lazily imports a single gpt-tokenizer encoding
|
|
4
|
+
* (o200k_base — current OpenAI models) so apps that don't enable summarization
|
|
5
|
+
* never load the large encoding tables. Async because of the lazy import.
|
|
6
|
+
*/
|
|
7
|
+
export declare function defaultTokenCounter(text: string): Promise<number>;
|
|
8
|
+
/** Sum a (possibly async) token counter across a message list. */
|
|
9
|
+
export declare function countMessagesTokens(messages: readonly BaseMessage[], counter: (text: string) => number | Promise<number>): Promise<number>;
|
|
10
|
+
//# sourceMappingURL=token-counter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-counter.d.ts","sourceRoot":"","sources":["../../src/summarization/token-counter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAI3D;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQvE;AAUD,kEAAkE;AAClE,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAClD,OAAO,CAAC,MAAM,CAAC,CAMjB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
let encodeFn;
|
|
2
|
+
/**
|
|
3
|
+
* Default token counter. Lazily imports a single gpt-tokenizer encoding
|
|
4
|
+
* (o200k_base — current OpenAI models) so apps that don't enable summarization
|
|
5
|
+
* never load the large encoding tables. Async because of the lazy import.
|
|
6
|
+
*/
|
|
7
|
+
export async function defaultTokenCounter(text) {
|
|
8
|
+
if (!encodeFn) {
|
|
9
|
+
const mod = (await import("gpt-tokenizer/encoding/o200k_base"));
|
|
10
|
+
encodeFn = mod.encode;
|
|
11
|
+
}
|
|
12
|
+
return encodeFn(text).length;
|
|
13
|
+
}
|
|
14
|
+
function messageToText(m) {
|
|
15
|
+
const parts = [];
|
|
16
|
+
parts.push(typeof m.content === "string" ? m.content : JSON.stringify(m.content));
|
|
17
|
+
const toolCalls = m.tool_calls;
|
|
18
|
+
if (toolCalls)
|
|
19
|
+
parts.push(JSON.stringify(toolCalls));
|
|
20
|
+
return parts.join("\n");
|
|
21
|
+
}
|
|
22
|
+
/** Sum a (possibly async) token counter across a message list. */
|
|
23
|
+
export async function countMessagesTokens(messages, counter) {
|
|
24
|
+
let total = 0;
|
|
25
|
+
for (const m of messages) {
|
|
26
|
+
total += await counter(messageToText(m));
|
|
27
|
+
}
|
|
28
|
+
return total;
|
|
29
|
+
}
|
package/dist/tool-converter.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { JsonSchemaProperty } from "@dawn-ai/core";
|
|
1
2
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
|
3
|
+
import { z } from "zod";
|
|
2
4
|
interface DawnToolDefinition {
|
|
3
5
|
readonly description?: string;
|
|
4
6
|
readonly name: string;
|
|
@@ -8,6 +10,8 @@ interface DawnToolDefinition {
|
|
|
8
10
|
}) => Promise<unknown> | unknown;
|
|
9
11
|
readonly schema?: unknown;
|
|
10
12
|
}
|
|
11
|
-
export
|
|
13
|
+
export type OffloadFn = (content: string, toolName: string, toolCallId?: string) => Promise<string>;
|
|
14
|
+
export declare function convertToolToLangChain(tool: DawnToolDefinition, middlewareContext?: Readonly<Record<string, unknown>>, offload?: OffloadFn): DynamicStructuredTool;
|
|
15
|
+
export declare function jsonSchemaToZod(schema: JsonSchemaProperty): z.ZodObject<z.ZodRawShape>;
|
|
12
16
|
export {};
|
|
13
17
|
//# sourceMappingURL=tool-converter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-converter.d.ts","sourceRoot":"","sources":["../src/tool-converter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"tool-converter.d.ts","sourceRoot":"","sources":["../src/tool-converter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAE7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,CACZ,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;QACP,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;QACvD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;KAC7B,KACE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAEnG,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,kBAAkB,EACxB,iBAAiB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACrD,OAAO,CAAC,EAAE,SAAS,GAClB,qBAAqB,CAqCvB;AA6BD,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAEtF"}
|
package/dist/tool-converter.js
CHANGED
|
@@ -1,20 +1,40 @@
|
|
|
1
|
+
import { ToolMessage } from "@langchain/core/messages";
|
|
1
2
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
|
3
|
+
import { Command } from "@langchain/langgraph";
|
|
2
4
|
import { z } from "zod";
|
|
3
|
-
|
|
5
|
+
import { unwrapToolResult } from "./unwrap-tool-result.js";
|
|
6
|
+
export function convertToolToLangChain(tool, middlewareContext, offload) {
|
|
4
7
|
const schema = toZodSchema(tool.schema);
|
|
5
|
-
// Cast through unknown to bridge the dual-Zod version type incompatibility
|
|
6
|
-
// (package uses zod@3.24.4; @langchain/core uses zod@3.25.x — structurally identical at runtime)
|
|
7
8
|
return new DynamicStructuredTool({
|
|
8
9
|
name: tool.name,
|
|
9
10
|
description: tool.description ?? "",
|
|
10
|
-
schema
|
|
11
|
+
schema,
|
|
11
12
|
func: async (input, _runManager, config) => {
|
|
12
13
|
const signal = config?.signal ?? new AbortController().signal;
|
|
13
|
-
const
|
|
14
|
+
const rawResult = await tool.run(input, {
|
|
14
15
|
...(middlewareContext ? { middleware: middlewareContext } : {}),
|
|
15
16
|
signal,
|
|
16
17
|
});
|
|
17
|
-
|
|
18
|
+
const { content, stateUpdates } = unwrapToolResult(rawResult);
|
|
19
|
+
const toolCallId = extractToolCallId(config);
|
|
20
|
+
const finalContent = offload
|
|
21
|
+
? await offload(content, tool.name, toolCallId || undefined)
|
|
22
|
+
: content;
|
|
23
|
+
if (stateUpdates) {
|
|
24
|
+
return new Command({
|
|
25
|
+
update: {
|
|
26
|
+
...stateUpdates,
|
|
27
|
+
messages: [
|
|
28
|
+
new ToolMessage({
|
|
29
|
+
content: finalContent,
|
|
30
|
+
tool_call_id: toolCallId,
|
|
31
|
+
name: tool.name,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return finalContent;
|
|
18
38
|
},
|
|
19
39
|
});
|
|
20
40
|
}
|
|
@@ -38,22 +58,42 @@ function isJsonSchemaObject(value) {
|
|
|
38
58
|
value.type === "object" &&
|
|
39
59
|
typeof value.properties === "object");
|
|
40
60
|
}
|
|
41
|
-
|
|
61
|
+
const MAX_ZOD_DEPTH = 8;
|
|
62
|
+
export function jsonSchemaToZod(schema) {
|
|
63
|
+
return objectToZod(schema, 0);
|
|
64
|
+
}
|
|
65
|
+
function objectToZod(prop, depth) {
|
|
66
|
+
// Record<string,T>: schema-valued additionalProperties and no named properties.
|
|
67
|
+
if (typeof prop.additionalProperties === "object" &&
|
|
68
|
+
prop.additionalProperties !== null &&
|
|
69
|
+
(!prop.properties || Object.keys(prop.properties).length === 0)) {
|
|
70
|
+
return z.record(z.string(), jsonSchemaFieldToZod(prop.additionalProperties, depth + 1));
|
|
71
|
+
}
|
|
42
72
|
const shape = {};
|
|
43
|
-
const required = new Set(
|
|
44
|
-
for (const [key,
|
|
45
|
-
let field = jsonSchemaFieldToZod(
|
|
46
|
-
if (!required.has(key))
|
|
73
|
+
const required = new Set(prop.required ?? []);
|
|
74
|
+
for (const [key, sub] of Object.entries(prop.properties ?? {})) {
|
|
75
|
+
let field = jsonSchemaFieldToZod(sub, depth + 1);
|
|
76
|
+
if (!required.has(key))
|
|
47
77
|
field = field.optional();
|
|
48
|
-
}
|
|
49
78
|
shape[key] = field;
|
|
50
79
|
}
|
|
51
80
|
return z.object(shape);
|
|
52
81
|
}
|
|
53
|
-
function jsonSchemaFieldToZod(prop) {
|
|
82
|
+
function jsonSchemaFieldToZod(prop, depth = 0) {
|
|
83
|
+
if (depth > MAX_ZOD_DEPTH)
|
|
84
|
+
return z.string();
|
|
85
|
+
// Object unions are emitted as anyOf (no `type`); map to z.union.
|
|
86
|
+
if (prop.anyOf && prop.anyOf.length > 0) {
|
|
87
|
+
const members = prop.anyOf.map((m) => jsonSchemaFieldToZod(m, depth + 1));
|
|
88
|
+
if (members.length === 1)
|
|
89
|
+
return members[0] ?? z.unknown();
|
|
90
|
+
return z.union(members);
|
|
91
|
+
}
|
|
54
92
|
switch (prop.type) {
|
|
55
93
|
case "string":
|
|
56
|
-
return
|
|
94
|
+
return prop.enum && prop.enum.length > 0
|
|
95
|
+
? z.enum([...prop.enum])
|
|
96
|
+
: z.string();
|
|
57
97
|
case "number":
|
|
58
98
|
case "integer":
|
|
59
99
|
return z.number();
|
|
@@ -61,15 +101,28 @@ function jsonSchemaFieldToZod(prop) {
|
|
|
61
101
|
return z.boolean();
|
|
62
102
|
case "array": {
|
|
63
103
|
const items = prop.items;
|
|
64
|
-
|
|
65
|
-
return z.array(z.string());
|
|
66
|
-
if (items?.type === "number" || items?.type === "integer")
|
|
67
|
-
return z.array(z.number());
|
|
68
|
-
if (items?.type === "boolean")
|
|
69
|
-
return z.array(z.boolean());
|
|
70
|
-
return z.array(z.unknown());
|
|
104
|
+
return items ? z.array(jsonSchemaFieldToZod(items, depth + 1)) : z.array(z.unknown());
|
|
71
105
|
}
|
|
106
|
+
case "object":
|
|
107
|
+
return objectToZod(prop, depth);
|
|
72
108
|
default:
|
|
73
109
|
return z.unknown();
|
|
74
110
|
}
|
|
75
111
|
}
|
|
112
|
+
function extractToolCallId(config) {
|
|
113
|
+
if (typeof config !== "object" || config === null)
|
|
114
|
+
return "";
|
|
115
|
+
const c = config;
|
|
116
|
+
// LangGraph 1.x exposes the tool call id in different ways depending on
|
|
117
|
+
// the calling code path; try the most likely locations.
|
|
118
|
+
const direct = c.toolCall?.id;
|
|
119
|
+
if (typeof direct === "string")
|
|
120
|
+
return direct;
|
|
121
|
+
const configurable = c.configurable;
|
|
122
|
+
if (typeof configurable?.toolCallId === "string")
|
|
123
|
+
return configurable.toolCallId;
|
|
124
|
+
const metadata = c.metadata;
|
|
125
|
+
if (typeof metadata?.tool_call_id === "string")
|
|
126
|
+
return metadata.tool_call_id;
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of unwrapping a tool's return value.
|
|
3
|
+
*
|
|
4
|
+
* - `content` is the string that becomes the ToolMessage content the agent sees.
|
|
5
|
+
* Built rules:
|
|
6
|
+
* • If the tool returned a wrapped `{result}` shape and `result` is a string,
|
|
7
|
+
* `content` is that string verbatim (no JSON quoting).
|
|
8
|
+
* • If `result` is any other value, `content` is `JSON.stringify(result)`.
|
|
9
|
+
* • If the tool returned a plain value (no wrapper), `content` is
|
|
10
|
+
* `JSON.stringify(value)`.
|
|
11
|
+
*
|
|
12
|
+
* - `stateUpdates` is the partial state-channel update object to apply, or
|
|
13
|
+
* undefined if the tool didn't request any state mutation.
|
|
14
|
+
*/
|
|
15
|
+
export interface UnwrappedToolResult {
|
|
16
|
+
readonly content: string;
|
|
17
|
+
readonly stateUpdates: Record<string, unknown> | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Detect whether a tool's return value uses the Dawn wrapper shape
|
|
21
|
+
* `{result, state?}` and split it into the agent-facing `content` and the
|
|
22
|
+
* optional `stateUpdates` for the route's state channels.
|
|
23
|
+
*
|
|
24
|
+
* The wrapper shape is recognized strictly: the value must be a non-null
|
|
25
|
+
* plain object whose own enumerable keys are exactly `result`, or exactly
|
|
26
|
+
* `result` and `state`. Any other shape (including objects with extra keys,
|
|
27
|
+
* missing `result`, or arrays) falls through to plain-return handling.
|
|
28
|
+
*
|
|
29
|
+
* Edge case: if a tool returns `{result: undefined}`, the wrapper IS detected
|
|
30
|
+
* structurally but the resulting `content` would be the string "undefined"
|
|
31
|
+
* (JSON.stringify(undefined) === undefined). We treat this as plain — capability
|
|
32
|
+
* authors should never return undefined as the agent-facing result.
|
|
33
|
+
*/
|
|
34
|
+
export declare function unwrapToolResult(value: unknown): UnwrappedToolResult;
|
|
35
|
+
//# sourceMappingURL=unwrap-tool-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwrap-tool-result.d.ts","sourceRoot":"","sources":["../src/unwrap-tool-result.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CAC3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAoBpE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect whether a tool's return value uses the Dawn wrapper shape
|
|
3
|
+
* `{result, state?}` and split it into the agent-facing `content` and the
|
|
4
|
+
* optional `stateUpdates` for the route's state channels.
|
|
5
|
+
*
|
|
6
|
+
* The wrapper shape is recognized strictly: the value must be a non-null
|
|
7
|
+
* plain object whose own enumerable keys are exactly `result`, or exactly
|
|
8
|
+
* `result` and `state`. Any other shape (including objects with extra keys,
|
|
9
|
+
* missing `result`, or arrays) falls through to plain-return handling.
|
|
10
|
+
*
|
|
11
|
+
* Edge case: if a tool returns `{result: undefined}`, the wrapper IS detected
|
|
12
|
+
* structurally but the resulting `content` would be the string "undefined"
|
|
13
|
+
* (JSON.stringify(undefined) === undefined). We treat this as plain — capability
|
|
14
|
+
* authors should never return undefined as the agent-facing result.
|
|
15
|
+
*/
|
|
16
|
+
export function unwrapToolResult(value) {
|
|
17
|
+
if (!isWrapperShape(value)) {
|
|
18
|
+
return { content: JSON.stringify(value), stateUpdates: undefined };
|
|
19
|
+
}
|
|
20
|
+
const { result, state } = value;
|
|
21
|
+
// Defensive: if result is undefined, fall back to plain (the wrapper shape
|
|
22
|
+
// was structurally present but the content would be meaningless).
|
|
23
|
+
if (result === undefined) {
|
|
24
|
+
return { content: JSON.stringify(value), stateUpdates: undefined };
|
|
25
|
+
}
|
|
26
|
+
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
27
|
+
const stateUpdates = state !== undefined && state !== null && typeof state === "object"
|
|
28
|
+
? state
|
|
29
|
+
: undefined;
|
|
30
|
+
return { content, stateUpdates };
|
|
31
|
+
}
|
|
32
|
+
function isWrapperShape(value) {
|
|
33
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const keys = Object.keys(value);
|
|
37
|
+
if (keys.length === 1)
|
|
38
|
+
return keys[0] === "result";
|
|
39
|
+
if (keys.length === 2)
|
|
40
|
+
return keys.includes("result") && keys.includes("state");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dawn-ai/langchain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,20 +30,65 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@langchain/langgraph": "^
|
|
34
|
-
"@langchain/openai": "^
|
|
35
|
-
"
|
|
33
|
+
"@langchain/langgraph": "^1.3.0",
|
|
34
|
+
"@langchain/openai": "^1.4.5",
|
|
35
|
+
"gpt-tokenizer": "^3.4.0",
|
|
36
|
+
"@dawn-ai/core": "0.3.0",
|
|
37
|
+
"@dawn-ai/sdk": "0.3.0",
|
|
38
|
+
"@dawn-ai/workspace": "0.2.0"
|
|
36
39
|
},
|
|
37
40
|
"peerDependencies": {
|
|
38
|
-
"@langchain/core": "
|
|
41
|
+
"@langchain/core": "^1.1.47",
|
|
42
|
+
"@langchain/langgraph-checkpoint": "^1.0.2",
|
|
43
|
+
"@langchain/anthropic": "^1.4.0",
|
|
44
|
+
"@langchain/google-genai": "^2.1.31",
|
|
45
|
+
"@langchain/mistralai": "^1.0.8",
|
|
46
|
+
"@langchain/groq": "^1.2.1",
|
|
47
|
+
"@langchain/ollama": "^1.2.7",
|
|
48
|
+
"@langchain/xai": "^1.3.18",
|
|
49
|
+
"@langchain/openrouter": "^0.2.5"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"@langchain/langgraph-checkpoint": {
|
|
53
|
+
"optional": false
|
|
54
|
+
},
|
|
55
|
+
"@langchain/anthropic": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"@langchain/google-genai": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"@langchain/mistralai": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@langchain/groq": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@langchain/ollama": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@langchain/xai": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@langchain/openrouter": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
39
76
|
},
|
|
40
77
|
"devDependencies": {
|
|
41
|
-
"@langchain/
|
|
42
|
-
"@langchain/langgraph": "0.2
|
|
43
|
-
"@langchain/
|
|
78
|
+
"@langchain/anthropic": "^1.4.0",
|
|
79
|
+
"@langchain/langgraph-checkpoint": "^1.0.2",
|
|
80
|
+
"@langchain/core": "1.1.47",
|
|
81
|
+
"@langchain/google-genai": "^2.1.31",
|
|
82
|
+
"@langchain/groq": "^1.2.1",
|
|
83
|
+
"@langchain/langgraph": "1.3.0",
|
|
84
|
+
"@langchain/mistralai": "^1.0.8",
|
|
85
|
+
"@langchain/ollama": "^1.2.7",
|
|
86
|
+
"@langchain/openai": "1.4.5",
|
|
87
|
+
"@langchain/openrouter": "^0.2.5",
|
|
88
|
+
"@langchain/xai": "^1.3.18",
|
|
44
89
|
"@types/node": "25.6.0",
|
|
45
|
-
"zod": "
|
|
46
|
-
"@dawn-ai/config-typescript": "0.
|
|
90
|
+
"zod": "4.4.3",
|
|
91
|
+
"@dawn-ai/config-typescript": "0.3.0"
|
|
47
92
|
},
|
|
48
93
|
"scripts": {
|
|
49
94
|
"build": "tsc -b tsconfig.json",
|