@dawn-ai/langchain 0.1.8 → 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.
@@ -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 { AgentStreamChunk } from "./agent-adapter.js";
2
- export { executeAgent, streamAgent } from "./agent-adapter.js";
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,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,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA"}
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 { executeAgent, streamAgent } from "./agent-adapter.js";
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
+ }
@@ -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;AAG7D,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,CAkBvB"}
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"}
@@ -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: schema,
11
+ schema,
11
12
  func: async (input, _runManager, config) => {
12
13
  const signal = config?.signal ?? new AbortController().signal;
13
- const result = await tool.run(input, {
14
+ const rawResult = await tool.run(input, {
14
15
  ...(middlewareContext ? { middleware: middlewareContext } : {}),
15
16
  signal,
16
17
  });
17
- return JSON.stringify(result);
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
- function jsonSchemaToZod(schema) {
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(schema.required ?? []);
44
- for (const [key, prop] of Object.entries(schema.properties ?? {})) {
45
- let field = jsonSchemaFieldToZod(prop);
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 z.string();
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
- if (items?.type === "string")
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"}