@dawn-ai/langchain 0.1.7 → 0.2.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 +17 -3
- package/dist/agent-adapter.d.ts +47 -4
- package/dist/agent-adapter.d.ts.map +1 -1
- package/dist/agent-adapter.js +359 -73
- 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 +49 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -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/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/tool-converter.d.ts +3 -0
- package/dist/tool-converter.d.ts.map +1 -1
- package/dist/tool-converter.js +70 -20
- 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 +53 -10
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const providerSpecs = {
|
|
2
|
+
openai: { packageName: "@langchain/openai", exportName: "ChatOpenAI" },
|
|
3
|
+
anthropic: { packageName: "@langchain/anthropic", exportName: "ChatAnthropic" },
|
|
4
|
+
// Official LangChain JS docs and current npm availability support this stable package/class.
|
|
5
|
+
google: { packageName: "@langchain/google-genai", exportName: "ChatGoogleGenerativeAI" },
|
|
6
|
+
mistral: { packageName: "@langchain/mistralai", exportName: "ChatMistralAI" },
|
|
7
|
+
groq: { packageName: "@langchain/groq", exportName: "ChatGroq" },
|
|
8
|
+
ollama: { packageName: "@langchain/ollama", exportName: "ChatOllama" },
|
|
9
|
+
xai: { packageName: "@langchain/xai", exportName: "ChatXAI" },
|
|
10
|
+
openrouter: { packageName: "@langchain/openrouter", exportName: "ChatOpenRouter" },
|
|
11
|
+
};
|
|
12
|
+
export function missingProviderPackageMessage(provider, packageName) {
|
|
13
|
+
return `Provider "${provider}" requires ${packageName}. Install it with: pnpm add ${packageName}`;
|
|
14
|
+
}
|
|
15
|
+
export async function createChatModel(options) {
|
|
16
|
+
const spec = providerSpecs[options.provider];
|
|
17
|
+
const importer = options.importer ??
|
|
18
|
+
((specifier) => import(specifier));
|
|
19
|
+
let moduleExports;
|
|
20
|
+
try {
|
|
21
|
+
moduleExports = await importer(spec.packageName);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (isMissingModuleError(error, spec.packageName)) {
|
|
25
|
+
throw new Error(missingProviderPackageMessage(options.provider, spec.packageName));
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
const Constructor = moduleExports[spec.exportName];
|
|
30
|
+
if (typeof Constructor !== "function") {
|
|
31
|
+
throw new Error(`Provider "${options.provider}" package ${spec.packageName} does not export ${spec.exportName}.`);
|
|
32
|
+
}
|
|
33
|
+
const constructorOptions = { model: options.model };
|
|
34
|
+
if (options.provider === "openai" && options.reasoning?.effort) {
|
|
35
|
+
constructorOptions.reasoningEffort = options.reasoning.effort;
|
|
36
|
+
}
|
|
37
|
+
return new Constructor(constructorOptions);
|
|
38
|
+
}
|
|
39
|
+
function isMissingModuleError(error, expectedPackageName) {
|
|
40
|
+
return (error instanceof Error &&
|
|
41
|
+
("code" in error ? error.code === "ERR_MODULE_NOT_FOUND" : true) &&
|
|
42
|
+
referencesPackageSpecifier(error.message, expectedPackageName) &&
|
|
43
|
+
/Cannot find (package|module)|ERR_MODULE_NOT_FOUND/i.test(error.message));
|
|
44
|
+
}
|
|
45
|
+
function referencesPackageSpecifier(message, packageName) {
|
|
46
|
+
return (message.includes(`'${packageName}'`) ||
|
|
47
|
+
message.includes(`"${packageName}"`) ||
|
|
48
|
+
message.includes(`\`${packageName}\``));
|
|
49
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export {
|
|
1
|
+
export type { BuiltInModelProviderId, ModelProviderId } from "@dawn-ai/sdk";
|
|
2
|
+
export { Command } from "@langchain/langgraph";
|
|
3
|
+
export type { AgentStreamChunk, DawnToolDefinition, SubagentResolver, } from "./agent-adapter.js";
|
|
4
|
+
export { executeAgent, materializeAgentGraph, streamAgent, } from "./agent-adapter.js";
|
|
3
5
|
export { chainAdapter } from "./chain-adapter.js";
|
|
6
|
+
export { createChatModel } from "./chat-model-factory.js";
|
|
7
|
+
export { inferProvider, resolveProvider } from "./model-provider-resolver.js";
|
|
4
8
|
export type { RetryOptions } from "./retry.js";
|
|
5
9
|
export { isRetryableError, withRetry } from "./retry.js";
|
|
6
10
|
export { materializeStateSchema } from "./state-adapter.js";
|
|
11
|
+
export type { SubagentEvent, SubagentStreamContext, } from "./subagent-dispatcher.js";
|
|
12
|
+
export { createSubagentStreamContext, dispatchSubagent, MAX_SUBAGENT_DEPTH, } from "./subagent-dispatcher.js";
|
|
13
|
+
export type { SubagentResolverResult } from "./subagent-tool-bridge.js";
|
|
14
|
+
export { bridgeSubagentTool } from "./subagent-tool-bridge.js";
|
|
7
15
|
export { convertToolToLangChain } from "./tool-converter.js";
|
|
8
16
|
export { executeWithToolLoop } from "./tool-loop.js";
|
|
17
|
+
export type { UnwrappedToolResult } from "./unwrap-tool-result.js";
|
|
18
|
+
export { unwrapToolResult } from "./unwrap-tool-result.js";
|
|
9
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAC9C,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,WAAW,GACZ,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC7E,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,YAAY,EACV,aAAa,EACb,qBAAqB,GACtB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,2BAA2B,EAC3B,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,0BAA0B,CAAA;AACjC,YAAY,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AACpD,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { Command } from "@langchain/langgraph";
|
|
2
|
+
export { executeAgent, materializeAgentGraph, streamAgent, } from "./agent-adapter.js";
|
|
2
3
|
export { chainAdapter } from "./chain-adapter.js";
|
|
4
|
+
export { createChatModel } from "./chat-model-factory.js";
|
|
5
|
+
export { inferProvider, resolveProvider } from "./model-provider-resolver.js";
|
|
3
6
|
export { isRetryableError, withRetry } from "./retry.js";
|
|
4
7
|
export { materializeStateSchema } from "./state-adapter.js";
|
|
8
|
+
export { createSubagentStreamContext, dispatchSubagent, MAX_SUBAGENT_DEPTH, } from "./subagent-dispatcher.js";
|
|
9
|
+
export { bridgeSubagentTool } from "./subagent-tool-bridge.js";
|
|
5
10
|
export { convertToolToLangChain } from "./tool-converter.js";
|
|
6
11
|
export { executeWithToolLoop } from "./tool-loop.js";
|
|
12
|
+
export { unwrapToolResult } from "./unwrap-tool-result.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BuiltInModelProviderId, ModelProviderId } from "@dawn-ai/sdk";
|
|
2
|
+
export declare const SUPPORTED_AGENT_PROVIDERS: readonly ["openai", "anthropic", "google", "mistral", "groq", "ollama", "xai", "openrouter"];
|
|
3
|
+
export declare function inferProvider(model: string): BuiltInModelProviderId | undefined;
|
|
4
|
+
export declare function resolveProvider(options: {
|
|
5
|
+
readonly model: string;
|
|
6
|
+
readonly provider?: ModelProviderId;
|
|
7
|
+
}): BuiltInModelProviderId;
|
|
8
|
+
//# sourceMappingURL=model-provider-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-provider-resolver.d.ts","sourceRoot":"","sources":["../src/model-provider-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE3E,eAAO,MAAM,yBAAyB,8FASgB,CAAA;AAItD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAgB/E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;CACpC,GAAG,sBAAsB,CAgBzB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const SUPPORTED_AGENT_PROVIDERS = [
|
|
2
|
+
"openai",
|
|
3
|
+
"anthropic",
|
|
4
|
+
"google",
|
|
5
|
+
"mistral",
|
|
6
|
+
"groq",
|
|
7
|
+
"ollama",
|
|
8
|
+
"xai",
|
|
9
|
+
"openrouter",
|
|
10
|
+
];
|
|
11
|
+
const supportedProviderSet = new Set(SUPPORTED_AGENT_PROVIDERS);
|
|
12
|
+
export function inferProvider(model) {
|
|
13
|
+
const normalized = model.trim().toLowerCase();
|
|
14
|
+
if (/^(gpt-|o3|o4)/.test(normalized))
|
|
15
|
+
return "openai";
|
|
16
|
+
if (normalized.startsWith("claude-"))
|
|
17
|
+
return "anthropic";
|
|
18
|
+
if (normalized.startsWith("gemini-"))
|
|
19
|
+
return "google";
|
|
20
|
+
if (normalized.startsWith("mistral-") ||
|
|
21
|
+
normalized.startsWith("mixtral-") ||
|
|
22
|
+
normalized.startsWith("codestral-")) {
|
|
23
|
+
return "mistral";
|
|
24
|
+
}
|
|
25
|
+
if (normalized.startsWith("grok-"))
|
|
26
|
+
return "xai";
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
export function resolveProvider(options) {
|
|
30
|
+
if (options.provider !== undefined) {
|
|
31
|
+
if (supportedProviderSet.has(options.provider)) {
|
|
32
|
+
return options.provider;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unsupported agent provider "${options.provider}". Supported providers: ${SUPPORTED_AGENT_PROVIDERS.join(", ")}.`);
|
|
35
|
+
}
|
|
36
|
+
const inferred = inferProvider(options.model);
|
|
37
|
+
if (inferred)
|
|
38
|
+
return inferred;
|
|
39
|
+
throw new Error(`Could not infer a LangChain provider for model "${options.model}". Set provider explicitly on agent({ provider: "...", model: "${options.model}", ... }).`);
|
|
40
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface SubagentEvent {
|
|
2
|
+
readonly event: string;
|
|
3
|
+
readonly data: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface SubagentStreamContext {
|
|
6
|
+
/**
|
|
7
|
+
* Active child-run depth. While > 0, the parent's streamFromRunnable should
|
|
8
|
+
* suppress on_chat_model_stream emissions because those events are firing
|
|
9
|
+
* for the child run via LangChain v2 streamEvents async-local-storage
|
|
10
|
+
* tracing. The dispatcher already emits a `subagent.message` envelope for
|
|
11
|
+
* each child token, so suppressing avoids duplicates on the parent stream.
|
|
12
|
+
*/
|
|
13
|
+
activeChildRuns: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function createSubagentStreamContext(): SubagentStreamContext;
|
|
16
|
+
export interface DispatchArgs {
|
|
17
|
+
readonly childGraph: {
|
|
18
|
+
invoke: (input: unknown, config: unknown) => Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
readonly input: string;
|
|
21
|
+
readonly parentConfig: Record<string, unknown>;
|
|
22
|
+
readonly writer: (event: SubagentEvent) => void;
|
|
23
|
+
readonly callId: string;
|
|
24
|
+
readonly childRouteId: string;
|
|
25
|
+
readonly subagentName: string;
|
|
26
|
+
readonly streamContext?: SubagentStreamContext;
|
|
27
|
+
}
|
|
28
|
+
export interface DispatchResult {
|
|
29
|
+
readonly finalText: string;
|
|
30
|
+
}
|
|
31
|
+
export declare const MAX_SUBAGENT_DEPTH = 3;
|
|
32
|
+
export declare function dispatchSubagent(args: DispatchArgs): Promise<DispatchResult>;
|
|
33
|
+
//# sourceMappingURL=subagent-dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-dispatcher.d.ts","sourceRoot":"","sources":["../src/subagent-dispatcher.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACvC;AA2BD,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,2BAA2B,IAAI,qBAAqB,CAEnE;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE;QAAE,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IACtF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAA;CAC/C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,eAAO,MAAM,kBAAkB,IAAI,CAAA;AA4CnC,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CA4BlF"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export function createSubagentStreamContext() {
|
|
2
|
+
return { activeChildRuns: 0 };
|
|
3
|
+
}
|
|
4
|
+
export const MAX_SUBAGENT_DEPTH = 3;
|
|
5
|
+
function readDepth(config) {
|
|
6
|
+
const meta = config?.metadata ?? {};
|
|
7
|
+
const dawn = meta.dawn ?? {};
|
|
8
|
+
const depth = dawn.subagent_depth;
|
|
9
|
+
return typeof depth === "number" ? depth : 0;
|
|
10
|
+
}
|
|
11
|
+
function buildChildConfig(parentConfig, nextDepth, callId) {
|
|
12
|
+
const parentMeta = parentConfig.metadata ?? {};
|
|
13
|
+
const parentDawn = parentMeta.dawn ?? {};
|
|
14
|
+
// Build a CLEAN child config: only Dawn metadata + abort signal.
|
|
15
|
+
// We deliberately omit `callbacks`, `runId`, `tags`, `runName`, etc. that
|
|
16
|
+
// LangChain's streamEvents v2 uses to propagate child events to the parent
|
|
17
|
+
// listener. Without this, every child token would also appear as a raw
|
|
18
|
+
// chunk on the parent stream (duplicating subagent.message envelopes).
|
|
19
|
+
const childConfig = {
|
|
20
|
+
metadata: {
|
|
21
|
+
// Preserve Dawn-namespaced metadata so depth + thread continuity work,
|
|
22
|
+
// but drop everything else.
|
|
23
|
+
dawn: { ...parentDawn, subagent_depth: nextDepth, parent_call_id: callId },
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
// Forward the abort signal if the parent provided one — the child should
|
|
27
|
+
// cancel when the parent cancels.
|
|
28
|
+
if (parentConfig.signal)
|
|
29
|
+
childConfig.signal = parentConfig.signal;
|
|
30
|
+
return childConfig;
|
|
31
|
+
}
|
|
32
|
+
function extractFinalText(graphOutput) {
|
|
33
|
+
const messages = graphOutput?.messages ?? [];
|
|
34
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
35
|
+
const m = messages[i];
|
|
36
|
+
const kind = typeof m.getType === "function" ? m.getType() : m.type;
|
|
37
|
+
if (kind === "ai" && typeof m.content === "string")
|
|
38
|
+
return m.content;
|
|
39
|
+
}
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
export async function dispatchSubagent(args) {
|
|
43
|
+
const currentDepth = readDepth(args.parentConfig);
|
|
44
|
+
const nextDepth = currentDepth + 1;
|
|
45
|
+
if (nextDepth > MAX_SUBAGENT_DEPTH) {
|
|
46
|
+
return {
|
|
47
|
+
finalText: `subagent_depth_exceeded: cannot dispatch '${args.subagentName}' at depth ${nextDepth} (max ${MAX_SUBAGENT_DEPTH}).`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const childConfig = buildChildConfig(args.parentConfig, nextDepth, args.callId);
|
|
51
|
+
args.writer({
|
|
52
|
+
event: "subagent.start",
|
|
53
|
+
data: {
|
|
54
|
+
call_id: args.callId,
|
|
55
|
+
subagent: args.subagentName,
|
|
56
|
+
route_id: args.childRouteId,
|
|
57
|
+
depth: nextDepth,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (args.streamContext)
|
|
61
|
+
args.streamContext.activeChildRuns++;
|
|
62
|
+
try {
|
|
63
|
+
return await runChild(args, childConfig);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (args.streamContext)
|
|
67
|
+
args.streamContext.activeChildRuns--;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function runChild(args, childConfig) {
|
|
71
|
+
let output;
|
|
72
|
+
try {
|
|
73
|
+
const streamable = args.childGraph;
|
|
74
|
+
if (typeof streamable.dawnStream === "function") {
|
|
75
|
+
for await (const chunk of streamable.dawnStream({ messages: [{ role: "user", content: args.input }] }, childConfig)) {
|
|
76
|
+
switch (chunk.type) {
|
|
77
|
+
case "done":
|
|
78
|
+
output = chunk.output;
|
|
79
|
+
break;
|
|
80
|
+
case "tool_call":
|
|
81
|
+
args.writer({
|
|
82
|
+
event: "subagent.tool_call",
|
|
83
|
+
data: {
|
|
84
|
+
call_id: args.callId,
|
|
85
|
+
tool: chunk.name,
|
|
86
|
+
input: chunk.input,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
case "tool_result":
|
|
91
|
+
args.writer({
|
|
92
|
+
event: "subagent.tool_result",
|
|
93
|
+
data: {
|
|
94
|
+
call_id: args.callId,
|
|
95
|
+
tool: chunk.name,
|
|
96
|
+
output: chunk.output,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
case "chunk": {
|
|
101
|
+
const data = chunk.data;
|
|
102
|
+
if (typeof data === "string" && data.length > 0) {
|
|
103
|
+
args.writer({
|
|
104
|
+
event: "subagent.message",
|
|
105
|
+
data: { call_id: args.callId, chunk: data },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
default: {
|
|
111
|
+
// Capability-contributed events (e.g. plan_update). Prefix with
|
|
112
|
+
// subagent. and forward the chunk's data payload verbatim,
|
|
113
|
+
// attaching call_id.
|
|
114
|
+
const data = chunk.data;
|
|
115
|
+
args.writer({
|
|
116
|
+
event: `subagent.${chunk.type}`,
|
|
117
|
+
data: {
|
|
118
|
+
call_id: args.callId,
|
|
119
|
+
...(typeof data === "object" && data !== null ? data : { value: data }),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (typeof streamable.streamEvents === "function") {
|
|
128
|
+
for await (const event of streamable.streamEvents({ messages: [{ role: "user", content: args.input }] }, { ...childConfig, version: "v2" })) {
|
|
129
|
+
switch (event.event) {
|
|
130
|
+
case "on_tool_start":
|
|
131
|
+
args.writer({
|
|
132
|
+
event: "subagent.tool_call",
|
|
133
|
+
data: {
|
|
134
|
+
call_id: args.callId,
|
|
135
|
+
tool: event.name,
|
|
136
|
+
input: event.data.chunk ?? event.data.output,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
break;
|
|
140
|
+
case "on_tool_end":
|
|
141
|
+
args.writer({
|
|
142
|
+
event: "subagent.tool_result",
|
|
143
|
+
data: { call_id: args.callId, tool: event.name, output: event.data.output },
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
case "on_chat_model_stream": {
|
|
147
|
+
const content = event.data.chunk?.content;
|
|
148
|
+
if (typeof content === "string" && content.length > 0) {
|
|
149
|
+
args.writer({
|
|
150
|
+
event: "subagent.message",
|
|
151
|
+
data: { call_id: args.callId, chunk: content },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "on_chain_end":
|
|
157
|
+
if (event.name === "LangGraph") {
|
|
158
|
+
output = event.data.output;
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
output = await streamable.invoke({ messages: [{ role: "user", content: args.input }] }, childConfig);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
+
args.writer({
|
|
171
|
+
event: "subagent.end",
|
|
172
|
+
data: { call_id: args.callId, error: message },
|
|
173
|
+
});
|
|
174
|
+
return { finalText: `subagent_failed: ${message}` };
|
|
175
|
+
}
|
|
176
|
+
const finalText = extractFinalText(output);
|
|
177
|
+
args.writer({
|
|
178
|
+
event: "subagent.end",
|
|
179
|
+
data: { call_id: args.callId, final_message: finalText },
|
|
180
|
+
});
|
|
181
|
+
return { finalText };
|
|
182
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SubagentEvent, type SubagentStreamContext } from "./subagent-dispatcher.js";
|
|
2
|
+
export interface SubagentResolverResult {
|
|
3
|
+
readonly routeId: string;
|
|
4
|
+
readonly graph: {
|
|
5
|
+
readonly invoke: (input: unknown, config: unknown) => Promise<unknown>;
|
|
6
|
+
readonly streamEvents?: (input: unknown, options: Record<string, unknown>) => AsyncIterable<unknown>;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface BridgeOptions {
|
|
10
|
+
readonly subagentResolver: (leafName: string) => SubagentResolverResult | undefined;
|
|
11
|
+
readonly writer: (event: SubagentEvent) => void;
|
|
12
|
+
readonly parentConfig?: Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Shared counter that gates the parent's `on_chat_model_stream` emissions
|
|
15
|
+
* while a child run is active. See `SubagentStreamContext`.
|
|
16
|
+
*/
|
|
17
|
+
readonly streamContext?: SubagentStreamContext;
|
|
18
|
+
}
|
|
19
|
+
export interface BridgedTaskTool {
|
|
20
|
+
readonly name: "task";
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly run: (input: unknown, context: {
|
|
23
|
+
readonly signal: AbortSignal;
|
|
24
|
+
}) => Promise<string>;
|
|
25
|
+
}
|
|
26
|
+
export declare function bridgeSubagentTool(options: BridgeOptions): BridgedTaskTool;
|
|
27
|
+
//# sourceMappingURL=subagent-tool-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-tool-bridge.d.ts","sourceRoot":"","sources":["../src/subagent-tool-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC3B,MAAM,0BAA0B,CAAA;AAEjC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;QACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,CACtB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,aAAa,CAAC,OAAO,CAAC,CAAA;KAC5B,CAAA;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,sBAAsB,GAAG,SAAS,CAAA;IACnF,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/C;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAA;CAC/C;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC7F;AASD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,eAAe,CA0B1E"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { dispatchSubagent, } from "./subagent-dispatcher.js";
|
|
2
|
+
const TASK_TOOL_DESCRIPTION = "Dispatch a sub-task to a specialized subagent. See the # Subagents section of your system prompt for available agents and when to use each.";
|
|
3
|
+
function generateCallId() {
|
|
4
|
+
return `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5
|
+
}
|
|
6
|
+
export function bridgeSubagentTool(options) {
|
|
7
|
+
return {
|
|
8
|
+
name: "task",
|
|
9
|
+
description: TASK_TOOL_DESCRIPTION,
|
|
10
|
+
run: async (input, _ctx) => {
|
|
11
|
+
const { subagent, input: taskInput } = input;
|
|
12
|
+
const resolved = options.subagentResolver(subagent);
|
|
13
|
+
if (!resolved) {
|
|
14
|
+
return `subagent_unknown: no subagent named '${subagent}' (resolver returned undefined)`;
|
|
15
|
+
}
|
|
16
|
+
// Cast through `never`: dispatcher's `childGraph` type expects a `Streamable`-shaped
|
|
17
|
+
// graph; resolver returns a slightly different shape with optional streamEvents. The
|
|
18
|
+
// dispatcher narrows internally via its own type guards.
|
|
19
|
+
const result = await dispatchSubagent({
|
|
20
|
+
childGraph: resolved.graph,
|
|
21
|
+
input: taskInput,
|
|
22
|
+
parentConfig: options.parentConfig ?? {},
|
|
23
|
+
writer: options.writer,
|
|
24
|
+
callId: generateCallId(),
|
|
25
|
+
childRouteId: resolved.routeId,
|
|
26
|
+
subagentName: subagent,
|
|
27
|
+
...(options.streamContext ? { streamContext: options.streamContext } : {}),
|
|
28
|
+
});
|
|
29
|
+
return result.finalText;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
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;
|
|
@@ -9,5 +11,6 @@ interface DawnToolDefinition {
|
|
|
9
11
|
readonly schema?: unknown;
|
|
10
12
|
}
|
|
11
13
|
export declare function convertToolToLangChain(tool: DawnToolDefinition, middlewareContext?: Readonly<Record<string, unknown>>): DynamicStructuredTool;
|
|
14
|
+
export declare function jsonSchemaToZod(schema: JsonSchemaProperty): z.ZodObject<z.ZodRawShape>;
|
|
12
15
|
export {};
|
|
13
16
|
//# 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,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,kBAAkB,EACxB,iBAAiB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACpD,qBAAqB,CAkCvB;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,37 @@
|
|
|
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";
|
|
5
|
+
import { unwrapToolResult } from "./unwrap-tool-result.js";
|
|
3
6
|
export function convertToolToLangChain(tool, middlewareContext) {
|
|
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
|
+
if (stateUpdates) {
|
|
20
|
+
const toolCallId = extractToolCallId(config);
|
|
21
|
+
return new Command({
|
|
22
|
+
update: {
|
|
23
|
+
...stateUpdates,
|
|
24
|
+
messages: [
|
|
25
|
+
new ToolMessage({
|
|
26
|
+
content,
|
|
27
|
+
tool_call_id: toolCallId,
|
|
28
|
+
name: tool.name,
|
|
29
|
+
}),
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return content;
|
|
18
35
|
},
|
|
19
36
|
});
|
|
20
37
|
}
|
|
@@ -38,22 +55,42 @@ function isJsonSchemaObject(value) {
|
|
|
38
55
|
value.type === "object" &&
|
|
39
56
|
typeof value.properties === "object");
|
|
40
57
|
}
|
|
41
|
-
|
|
58
|
+
const MAX_ZOD_DEPTH = 8;
|
|
59
|
+
export function jsonSchemaToZod(schema) {
|
|
60
|
+
return objectToZod(schema, 0);
|
|
61
|
+
}
|
|
62
|
+
function objectToZod(prop, depth) {
|
|
63
|
+
// Record<string,T>: schema-valued additionalProperties and no named properties.
|
|
64
|
+
if (typeof prop.additionalProperties === "object" &&
|
|
65
|
+
prop.additionalProperties !== null &&
|
|
66
|
+
(!prop.properties || Object.keys(prop.properties).length === 0)) {
|
|
67
|
+
return z.record(z.string(), jsonSchemaFieldToZod(prop.additionalProperties, depth + 1));
|
|
68
|
+
}
|
|
42
69
|
const shape = {};
|
|
43
|
-
const required = new Set(
|
|
44
|
-
for (const [key,
|
|
45
|
-
let field = jsonSchemaFieldToZod(
|
|
46
|
-
if (!required.has(key))
|
|
70
|
+
const required = new Set(prop.required ?? []);
|
|
71
|
+
for (const [key, sub] of Object.entries(prop.properties ?? {})) {
|
|
72
|
+
let field = jsonSchemaFieldToZod(sub, depth + 1);
|
|
73
|
+
if (!required.has(key))
|
|
47
74
|
field = field.optional();
|
|
48
|
-
}
|
|
49
75
|
shape[key] = field;
|
|
50
76
|
}
|
|
51
77
|
return z.object(shape);
|
|
52
78
|
}
|
|
53
|
-
function jsonSchemaFieldToZod(prop) {
|
|
79
|
+
function jsonSchemaFieldToZod(prop, depth = 0) {
|
|
80
|
+
if (depth > MAX_ZOD_DEPTH)
|
|
81
|
+
return z.string();
|
|
82
|
+
// Object unions are emitted as anyOf (no `type`); map to z.union.
|
|
83
|
+
if (prop.anyOf && prop.anyOf.length > 0) {
|
|
84
|
+
const members = prop.anyOf.map((m) => jsonSchemaFieldToZod(m, depth + 1));
|
|
85
|
+
if (members.length === 1)
|
|
86
|
+
return members[0] ?? z.unknown();
|
|
87
|
+
return z.union(members);
|
|
88
|
+
}
|
|
54
89
|
switch (prop.type) {
|
|
55
90
|
case "string":
|
|
56
|
-
return
|
|
91
|
+
return prop.enum && prop.enum.length > 0
|
|
92
|
+
? z.enum([...prop.enum])
|
|
93
|
+
: z.string();
|
|
57
94
|
case "number":
|
|
58
95
|
case "integer":
|
|
59
96
|
return z.number();
|
|
@@ -61,15 +98,28 @@ function jsonSchemaFieldToZod(prop) {
|
|
|
61
98
|
return z.boolean();
|
|
62
99
|
case "array": {
|
|
63
100
|
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());
|
|
101
|
+
return items ? z.array(jsonSchemaFieldToZod(items, depth + 1)) : z.array(z.unknown());
|
|
71
102
|
}
|
|
103
|
+
case "object":
|
|
104
|
+
return objectToZod(prop, depth);
|
|
72
105
|
default:
|
|
73
106
|
return z.unknown();
|
|
74
107
|
}
|
|
75
108
|
}
|
|
109
|
+
function extractToolCallId(config) {
|
|
110
|
+
if (typeof config !== "object" || config === null)
|
|
111
|
+
return "";
|
|
112
|
+
const c = config;
|
|
113
|
+
// LangGraph 1.x exposes the tool call id in different ways depending on
|
|
114
|
+
// the calling code path; try the most likely locations.
|
|
115
|
+
const direct = c.toolCall?.id;
|
|
116
|
+
if (typeof direct === "string")
|
|
117
|
+
return direct;
|
|
118
|
+
const configurable = c.configurable;
|
|
119
|
+
if (typeof configurable?.toolCallId === "string")
|
|
120
|
+
return configurable.toolCallId;
|
|
121
|
+
const metadata = c.metadata;
|
|
122
|
+
if (typeof metadata?.tool_call_id === "string")
|
|
123
|
+
return metadata.tool_call_id;
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
@@ -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"}
|