@dawn-ai/langchain 0.2.0 → 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.
Files changed (36) hide show
  1. package/dist/agent-adapter.d.ts +13 -0
  2. package/dist/agent-adapter.d.ts.map +1 -1
  3. package/dist/agent-adapter.js +45 -12
  4. package/dist/chat-model-factory.d.ts.map +1 -1
  5. package/dist/chat-model-factory.js +6 -0
  6. package/dist/index.d.ts +8 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +5 -1
  9. package/dist/offload/offload-store.d.ts +30 -0
  10. package/dist/offload/offload-store.d.ts.map +1 -0
  11. package/dist/offload/offload-store.js +105 -0
  12. package/dist/offload/offload-tool-output.d.ts +10 -0
  13. package/dist/offload/offload-tool-output.d.ts.map +1 -0
  14. package/dist/offload/offload-tool-output.js +18 -0
  15. package/dist/offload/stub.d.ts +8 -0
  16. package/dist/offload/stub.d.ts.map +1 -0
  17. package/dist/offload/stub.js +14 -0
  18. package/dist/summarization/hook.d.ts +31 -0
  19. package/dist/summarization/hook.d.ts.map +1 -0
  20. package/dist/summarization/hook.js +44 -0
  21. package/dist/summarization/index.d.ts +5 -0
  22. package/dist/summarization/index.d.ts.map +1 -0
  23. package/dist/summarization/index.js +4 -0
  24. package/dist/summarization/split.d.ts +12 -0
  25. package/dist/summarization/split.d.ts.map +1 -0
  26. package/dist/summarization/split.js +27 -0
  27. package/dist/summarization/summarize.d.ts +10 -0
  28. package/dist/summarization/summarize.d.ts.map +1 -0
  29. package/dist/summarization/summarize.js +30 -0
  30. package/dist/summarization/token-counter.d.ts +10 -0
  31. package/dist/summarization/token-counter.d.ts.map +1 -0
  32. package/dist/summarization/token-counter.js +29 -0
  33. package/dist/tool-converter.d.ts +2 -1
  34. package/dist/tool-converter.d.ts.map +1 -1
  35. package/dist/tool-converter.js +7 -4
  36. package/package.json +6 -4
@@ -4,6 +4,8 @@ import { type BaseMessageLike } from "@langchain/core/messages";
4
4
  import type { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
5
5
  import { type ResolvedStateField } from "./state-adapter.js";
6
6
  import { type SubagentResolverResult } from "./subagent-tool-bridge.js";
7
+ import { type ResolvedSummarizationConfig } from "./summarization/index.js";
8
+ import { type OffloadFn } from "./tool-converter.js";
7
9
  export type SubagentResolver = (leafName: string) => SubagentResolverResult | undefined;
8
10
  export interface DawnToolDefinition {
9
11
  readonly description?: string;
@@ -14,6 +16,14 @@ export interface DawnToolDefinition {
14
16
  }) => Promise<unknown> | unknown;
15
17
  readonly schema?: unknown;
16
18
  }
19
+ /**
20
+ * Test-only escape hatch: reset the materialized-agents cache so the next
21
+ * harness run creates a fresh LLM instance (e.g. pointing at a new aimock
22
+ * port). Exported (and re-exported via `@dawn-ai/cli/runtime`) so the
23
+ * `@dawn-ai/testing` harness can clear the cache on teardown. Not for
24
+ * production use; the `__`/`ForTests` name marks it internal-by-convention.
25
+ */
26
+ export declare function __resetMaterializedAgentsForTests(): void;
17
27
  export declare function composePromptMessages(systemPrompt: string, promptFragments: readonly PromptFragment[], state: Record<string, unknown>): BaseMessageLike[];
18
28
  export declare function materializeAgentGraph(options: {
19
29
  readonly checkpointer: BaseCheckpointSaver;
@@ -21,6 +31,7 @@ export declare function materializeAgentGraph(options: {
21
31
  readonly tools?: readonly DawnToolDefinition[];
22
32
  readonly stateFields?: readonly ResolvedStateField[];
23
33
  readonly promptFragments?: readonly PromptFragment[];
34
+ readonly summarization?: ResolvedSummarizationConfig;
24
35
  }): Promise<unknown>;
25
36
  export interface AgentStreamChunk {
26
37
  readonly type: "token" | "tool_call" | "tool_result" | "interrupt" | "done" | (string & {});
@@ -43,6 +54,7 @@ export interface AgentOptions {
43
54
  */
44
55
  readonly input: unknown;
45
56
  readonly middlewareContext?: Readonly<Record<string, unknown>>;
57
+ readonly offload?: OffloadFn;
46
58
  readonly retry?: RetryConfig;
47
59
  readonly routeParamNames: readonly string[];
48
60
  readonly signal: AbortSignal;
@@ -66,6 +78,7 @@ export interface AgentOptions {
66
78
  * replay.
67
79
  */
68
80
  readonly threadId?: string;
81
+ readonly summarization?: ResolvedSummarizationConfig;
69
82
  }
70
83
  export declare function executeAgent(options: AgentOptions): Promise<unknown>;
71
84
  export declare function streamAgent(options: AgentOptions): AsyncGenerator<AgentStreamChunk>;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-adapter.d.ts","sourceRoot":"","sources":["../src/agent-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-adapter.d.ts","sourceRoot":"","sources":["../src/agent-adapter.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { isRetryableError, withRetry } from "./retry.js";
7
7
  import { materializeStateSchema } from "./state-adapter.js";
8
8
  import { createSubagentStreamContext, } from "./subagent-dispatcher.js";
9
9
  import { bridgeSubagentTool } from "./subagent-tool-bridge.js";
10
+ import { buildSummarizationHook } from "./summarization/index.js";
10
11
  import { convertToolToLangChain } from "./tool-converter.js";
11
12
  function assertAgentLike(entry) {
12
13
  if (typeof entry !== "object" ||
@@ -20,7 +21,17 @@ function assertAgentLike(entry) {
20
21
  // invoked with the same capability contributions (prompt fragments come from
21
22
  // the route directory, which is stable per descriptor). If that assumption
22
23
  // changes, the cache key must include a hash of the fragments/transformers.
23
- const materializedAgents = new WeakMap();
24
+ let materializedAgents = new WeakMap();
25
+ /**
26
+ * Test-only escape hatch: reset the materialized-agents cache so the next
27
+ * harness run creates a fresh LLM instance (e.g. pointing at a new aimock
28
+ * port). Exported (and re-exported via `@dawn-ai/cli/runtime`) so the
29
+ * `@dawn-ai/testing` harness can clear the cache on teardown. Not for
30
+ * production use; the `__`/`ForTests` name marks it internal-by-convention.
31
+ */
32
+ export function __resetMaterializedAgentsForTests() {
33
+ materializedAgents = new WeakMap();
34
+ }
24
35
  export function composePromptMessages(systemPrompt, promptFragments, state) {
25
36
  const rendered = promptFragments
26
37
  .filter((f) => f.placement === "after_user_prompt")
@@ -30,15 +41,15 @@ export function composePromptMessages(systemPrompt, promptFragments, state) {
30
41
  const messages = Array.isArray(state.messages) ? state.messages : [];
31
42
  return [{ role: "system", content: composed }, ...messages];
32
43
  }
33
- async function materializeAgent(descriptor, tools, checkpointer, stateFields, middlewareContext, promptFragments, options) {
34
- if (!options?.bypassCache) {
44
+ async function materializeAgent(descriptor, tools, checkpointer, opts = {}) {
45
+ if (!opts.bypassCache) {
35
46
  const cached = materializedAgents.get(descriptor);
36
47
  if (cached) {
37
48
  return cached;
38
49
  }
39
50
  }
40
51
  const { createReactAgent } = await import("@langchain/langgraph/prebuilt");
41
- const langchainTools = tools.map((tool) => convertToolToLangChain(tool, middlewareContext));
52
+ const langchainTools = tools.map((tool) => convertToolToLangChain(tool, opts.middlewareContext, opts.offload));
42
53
  const provider = resolveProvider({
43
54
  model: descriptor.model,
44
55
  ...(descriptor.provider !== undefined ? { provider: descriptor.provider } : {}),
@@ -48,7 +59,7 @@ async function materializeAgent(descriptor, tools, checkpointer, stateFields, mi
48
59
  provider,
49
60
  ...(descriptor.reasoning ? { reasoning: descriptor.reasoning } : {}),
50
61
  });
51
- const fragments = promptFragments ?? [];
62
+ const fragments = opts.promptFragments ?? [];
52
63
  const agentOptions = {
53
64
  llm,
54
65
  tools: langchainTools,
@@ -61,18 +72,33 @@ async function materializeAgent(descriptor, tools, checkpointer, stateFields, mi
61
72
  // can replay it. Paired with `config.configurable.thread_id`.
62
73
  checkpointer,
63
74
  };
64
- if (stateFields && stateFields.length > 0) {
65
- agentOptions.stateSchema = materializeStateSchema(stateFields);
75
+ const runningSummaryField = {
76
+ name: "runningSummary",
77
+ reducer: "replace",
78
+ default: undefined,
79
+ };
80
+ const effectiveStateFields = opts.summarization
81
+ ? [...(opts.stateFields ?? []).filter((f) => f.name !== "runningSummary"), runningSummaryField]
82
+ : (opts.stateFields ?? []);
83
+ if (effectiveStateFields.length > 0) {
84
+ agentOptions.stateSchema = materializeStateSchema(effectiveStateFields);
85
+ }
86
+ if (opts.summarization) {
87
+ agentOptions.preModelHook = buildSummarizationHook(opts.summarization);
66
88
  }
67
89
  // biome-ignore lint/suspicious/noExplicitAny: dynamically-built options don't satisfy strict StateDefinition type
68
90
  const compiled = createReactAgent(agentOptions);
69
- if (!options?.bypassCache) {
91
+ if (!opts.bypassCache) {
70
92
  materializedAgents.set(descriptor, compiled);
71
93
  }
72
94
  return compiled;
73
95
  }
74
96
  export async function materializeAgentGraph(options) {
75
- return materializeAgent(options.descriptor, options.tools ?? [], options.checkpointer, options.stateFields, undefined, options.promptFragments);
97
+ return materializeAgent(options.descriptor, options.tools ?? [], options.checkpointer, {
98
+ ...(options.stateFields ? { stateFields: options.stateFields } : {}),
99
+ ...(options.promptFragments ? { promptFragments: options.promptFragments } : {}),
100
+ ...(options.summarization ? { summarization: options.summarization } : {}),
101
+ });
76
102
  }
77
103
  /**
78
104
  * LangGraph 1.x's `interrupt()` throws a `GraphInterrupt` from inside the tool
@@ -243,7 +269,14 @@ export async function* streamAgent(options) {
243
269
  // Bypass the per-descriptor cache when a resolver is wired: the bridged
244
270
  // tool closes over the per-call queue + parent config, so caching would
245
271
  // bind those to a single call.
246
- const materializedAgent = await materializeAgent(options.entry, effectiveTools, options.checkpointer, options.stateFields, options.middlewareContext, options.promptFragments, resolver && hasTaskTool ? { bypassCache: true } : undefined);
272
+ const materializedAgent = await materializeAgent(options.entry, effectiveTools, options.checkpointer, {
273
+ ...(options.stateFields ? { stateFields: options.stateFields } : {}),
274
+ ...(options.middlewareContext ? { middlewareContext: options.middlewareContext } : {}),
275
+ ...(options.promptFragments ? { promptFragments: options.promptFragments } : {}),
276
+ ...(resolver && hasTaskTool ? { bypassCache: true } : {}),
277
+ ...(options.offload ? { offload: options.offload } : {}),
278
+ ...(options.summarization ? { summarization: options.summarization } : {}),
279
+ });
247
280
  const retryConfig = options.entry.retry;
248
281
  const runnableInput = isCommandInput ? options.input : { messages };
249
282
  yield* streamFromRunnable(materializedAgent, runnableInput, config, retryConfig, options.streamTransformers, subagentEvents, streamContext);
@@ -251,7 +284,7 @@ export async function* streamAgent(options) {
251
284
  }
252
285
  // Legacy path — raw Runnable with .invoke()
253
286
  assertAgentLike(options.entry);
254
- const langchainTools = effectiveTools.map((tool) => convertToolToLangChain(tool, options.middlewareContext));
287
+ const langchainTools = effectiveTools.map((tool) => convertToolToLangChain(tool, options.middlewareContext, options.offload));
255
288
  if (langchainTools.length > 0) {
256
289
  config.tools = langchainTools;
257
290
  }
@@ -347,7 +380,7 @@ async function* streamFromRunnable(runnable, input, config, retryConfig, streamT
347
380
  type: "tool_call",
348
381
  data: {
349
382
  name: event.name,
350
- input: event.data.chunk ?? event.data.output,
383
+ input: event.data.input ?? event.data.chunk ?? event.data.output,
351
384
  },
352
385
  };
353
386
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"chat-model-factory.d.ts","sourceRoot":"","sources":["../src/chat-model-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE3E,KAAK,QAAQ,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAoBvE,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAA;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAA;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAC7B,GAAG,OAAO,CAAC,OAAO,CAAC,CA6BnB"}
1
+ {"version":3,"file":"chat-model-factory.d.ts","sourceRoot":"","sources":["../src/chat-model-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE3E,KAAK,QAAQ,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAoBvE,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,sBAAsB,EAChC,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAA;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAA;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAC7B,GAAG,OAAO,CAAC,OAAO,CAAC,CAoCnB"}
@@ -34,6 +34,12 @@ export async function createChatModel(options) {
34
34
  if (options.provider === "openai" && options.reasoning?.effort) {
35
35
  constructorOptions.reasoningEffort = options.reasoning.effort;
36
36
  }
37
+ if (options.provider === "openai") {
38
+ const baseURL = process.env.OPENAI_BASE_URL;
39
+ if (baseURL) {
40
+ constructorOptions.configuration = { baseURL };
41
+ }
42
+ }
37
43
  return new Constructor(constructorOptions);
38
44
  }
39
45
  function isMissingModuleError(error, expectedPackageName) {
package/dist/index.d.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  export type { BuiltInModelProviderId, ModelProviderId } from "@dawn-ai/sdk";
2
2
  export { Command } from "@langchain/langgraph";
3
3
  export type { AgentStreamChunk, DawnToolDefinition, SubagentResolver, } from "./agent-adapter.js";
4
- export { executeAgent, materializeAgentGraph, streamAgent, } from "./agent-adapter.js";
4
+ export { __resetMaterializedAgentsForTests, executeAgent, materializeAgentGraph, streamAgent, } from "./agent-adapter.js";
5
5
  export { chainAdapter } from "./chain-adapter.js";
6
6
  export { createChatModel } from "./chat-model-factory.js";
7
7
  export { inferProvider, resolveProvider } from "./model-provider-resolver.js";
8
+ export type { OffloadStoreOptions } from "./offload/offload-store.js";
9
+ export { buildOffloadFileName, OffloadStore } from "./offload/offload-store.js";
10
+ export type { OffloadToolOutputCtx } from "./offload/offload-tool-output.js";
11
+ export { offloadToolOutput } from "./offload/offload-tool-output.js";
12
+ export { buildStub } from "./offload/stub.js";
8
13
  export type { RetryOptions } from "./retry.js";
9
14
  export { isRetryableError, withRetry } from "./retry.js";
10
15
  export { materializeStateSchema } from "./state-adapter.js";
@@ -12,6 +17,8 @@ export type { SubagentEvent, SubagentStreamContext, } from "./subagent-dispatche
12
17
  export { createSubagentStreamContext, dispatchSubagent, MAX_SUBAGENT_DEPTH, } from "./subagent-dispatcher.js";
13
18
  export type { SubagentResolverResult } from "./subagent-tool-bridge.js";
14
19
  export { bridgeSubagentTool } from "./subagent-tool-bridge.js";
20
+ export * from "./summarization/index.js";
21
+ export type { OffloadFn } from "./tool-converter.js";
15
22
  export { convertToolToLangChain } from "./tool-converter.js";
16
23
  export { executeWithToolLoop } from "./tool-loop.js";
17
24
  export type { UnwrappedToolResult } from "./unwrap-tool-result.js";
@@ -1 +1 @@
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"}
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,iCAAiC,EACjC,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,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAC/E,YAAY,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAA;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,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,cAAc,0BAA0B,CAAA;AACxC,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACpD,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,12 +1,16 @@
1
1
  export { Command } from "@langchain/langgraph";
2
- export { executeAgent, materializeAgentGraph, streamAgent, } from "./agent-adapter.js";
2
+ export { __resetMaterializedAgentsForTests, executeAgent, materializeAgentGraph, streamAgent, } from "./agent-adapter.js";
3
3
  export { chainAdapter } from "./chain-adapter.js";
4
4
  export { createChatModel } from "./chat-model-factory.js";
5
5
  export { inferProvider, resolveProvider } from "./model-provider-resolver.js";
6
+ export { buildOffloadFileName, OffloadStore } from "./offload/offload-store.js";
7
+ export { offloadToolOutput } from "./offload/offload-tool-output.js";
8
+ export { buildStub } from "./offload/stub.js";
6
9
  export { isRetryableError, withRetry } from "./retry.js";
7
10
  export { materializeStateSchema } from "./state-adapter.js";
8
11
  export { createSubagentStreamContext, dispatchSubagent, MAX_SUBAGENT_DEPTH, } from "./subagent-dispatcher.js";
9
12
  export { bridgeSubagentTool } from "./subagent-tool-bridge.js";
13
+ export * from "./summarization/index.js";
10
14
  export { convertToolToLangChain } from "./tool-converter.js";
11
15
  export { executeWithToolLoop } from "./tool-loop.js";
12
16
  export { unwrapToolResult } from "./unwrap-tool-result.js";
@@ -0,0 +1,30 @@
1
+ import type { FilesystemBackend } from "@dawn-ai/workspace";
2
+ /**
3
+ * Deterministic offload filename. Keyed on the tool_call_id when present
4
+ * (unique per call in production; fixture-controlled in replay tests). Falls
5
+ * back to a content hash when no id is available — still deterministic and
6
+ * reproducible, since the caller controls the content.
7
+ */
8
+ export declare function buildOffloadFileName(toolName: string, content: string, toolCallId?: string): string;
9
+ export interface OffloadStoreOptions {
10
+ readonly backend: FilesystemBackend;
11
+ readonly workspaceRoot: string;
12
+ readonly signal: AbortSignal;
13
+ readonly maxBytes: number;
14
+ readonly ttlMs: number;
15
+ readonly gcThrottleMs: number;
16
+ /** Injectable clock for tests. Defaults to Date.now. */
17
+ readonly now?: () => number;
18
+ }
19
+ export declare class OffloadStore {
20
+ private readonly opts;
21
+ private lastGcAt;
22
+ constructor(opts: OffloadStoreOptions);
23
+ private get ctx();
24
+ private now;
25
+ /** Persist full content; returns the workspace-relative path. Runs throttled GC. */
26
+ write(toolName: string, content: string, toolCallId?: string): Promise<string>;
27
+ private maybeGc;
28
+ private safeRemove;
29
+ }
30
+ //# sourceMappingURL=offload-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offload-store.d.ts","sourceRoot":"","sources":["../../src/offload/offload-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAS3D;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAOR;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED,qBAAa,YAAY;IAEX,OAAO,CAAC,QAAQ,CAAC,IAAI;IADjC,OAAO,CAAC,QAAQ,CAAQ;gBACK,IAAI,EAAE,mBAAmB;IAItD,OAAO,KAAK,GAAG,GAEd;IACD,OAAO,CAAC,GAAG;IAIX,oFAAoF;IAC9E,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAWtE,OAAO;YA+CP,UAAU;CAQzB"}
@@ -0,0 +1,105 @@
1
+ import { createHash } from "node:crypto";
2
+ import { join } from "node:path";
3
+ // NOTE: must match the tool-outputs/ predicate in @dawn-ai/core workspace capability readFile.
4
+ const SUBDIR = "tool-outputs";
5
+ function sanitizeSegment(value) {
6
+ return value.replace(/[^A-Za-z0-9._-]/g, "_");
7
+ }
8
+ /**
9
+ * Deterministic offload filename. Keyed on the tool_call_id when present
10
+ * (unique per call in production; fixture-controlled in replay tests). Falls
11
+ * back to a content hash when no id is available — still deterministic and
12
+ * reproducible, since the caller controls the content.
13
+ */
14
+ export function buildOffloadFileName(toolName, content, toolCallId) {
15
+ const name = sanitizeSegment(toolName);
16
+ if (toolCallId && toolCallId.length > 0) {
17
+ return `${name}-${sanitizeSegment(toolCallId)}.txt`;
18
+ }
19
+ const hash = createHash("sha256").update(content).digest("hex").slice(0, 16);
20
+ return `${name}-${hash}.txt`;
21
+ }
22
+ export class OffloadStore {
23
+ opts;
24
+ lastGcAt;
25
+ constructor(opts) {
26
+ this.opts = opts;
27
+ this.lastGcAt = (opts.now ?? Date.now)();
28
+ }
29
+ get ctx() {
30
+ return { signal: this.opts.signal, workspaceRoot: this.opts.workspaceRoot };
31
+ }
32
+ now() {
33
+ return (this.opts.now ?? Date.now)();
34
+ }
35
+ /** Persist full content; returns the workspace-relative path. Runs throttled GC. */
36
+ async write(toolName, content, toolCallId) {
37
+ const fileName = buildOffloadFileName(toolName, content, toolCallId);
38
+ const relPath = `${SUBDIR}/${fileName}`;
39
+ const dirAbs = join(this.opts.workspaceRoot, SUBDIR);
40
+ await this.opts.backend.mkdir?.(dirAbs, this.ctx);
41
+ const absPath = join(this.opts.workspaceRoot, relPath);
42
+ await this.opts.backend.writeFile(absPath, content, this.ctx);
43
+ await this.maybeGc();
44
+ return relPath;
45
+ }
46
+ async maybeGc() {
47
+ const now = this.now();
48
+ if (now - this.lastGcAt < this.opts.gcThrottleMs)
49
+ return;
50
+ this.lastGcAt = now;
51
+ const { backend } = this.opts;
52
+ if (!backend.statFile || !backend.removeFile)
53
+ return; // GC unsupported by backend
54
+ const dirAbs = join(this.opts.workspaceRoot, SUBDIR);
55
+ let names;
56
+ try {
57
+ names = await backend.listDir(dirAbs, this.ctx);
58
+ }
59
+ catch {
60
+ return; // dir not created yet / unreadable
61
+ }
62
+ const entries = [];
63
+ for (const name of names) {
64
+ const abs = join(dirAbs, name);
65
+ try {
66
+ const s = await backend.statFile(abs, this.ctx);
67
+ entries.push({ abs, size: s.size, mtimeMs: s.mtimeMs });
68
+ }
69
+ catch {
70
+ /* skip unstattable */
71
+ }
72
+ }
73
+ // TTL pass
74
+ const ttlCutoff = now - this.opts.ttlMs;
75
+ const survivors = [];
76
+ for (const e of entries) {
77
+ if (e.mtimeMs < ttlCutoff) {
78
+ await this.safeRemove(e.abs);
79
+ }
80
+ else {
81
+ survivors.push(e);
82
+ }
83
+ }
84
+ // Size pass: oldest-first until under maxBytes
85
+ let total = survivors.reduce((sum, e) => sum + e.size, 0);
86
+ if (total <= this.opts.maxBytes)
87
+ return;
88
+ survivors.sort((a, b) => a.mtimeMs - b.mtimeMs);
89
+ for (const e of survivors) {
90
+ if (total <= this.opts.maxBytes)
91
+ break;
92
+ if (await this.safeRemove(e.abs))
93
+ total -= e.size;
94
+ }
95
+ }
96
+ async safeRemove(abs) {
97
+ try {
98
+ await this.opts.backend.removeFile?.(abs, this.ctx);
99
+ return true;
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,10 @@
1
+ import type { OffloadStore } from "./offload-store.js";
2
+ export interface OffloadToolOutputCtx {
3
+ readonly toolName: string;
4
+ readonly thresholdChars: number;
5
+ readonly previewLines: number;
6
+ readonly store: Pick<OffloadStore, "write">;
7
+ readonly toolCallId?: string;
8
+ }
9
+ export declare function offloadToolOutput(content: string, ctx: OffloadToolOutputCtx): Promise<string>;
10
+ //# sourceMappingURL=offload-tool-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offload-tool-output.d.ts","sourceRoot":"","sources":["../../src/offload/offload-tool-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGtD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,oBAAoB,GACxB,OAAO,CAAC,MAAM,CAAC,CAcjB"}
@@ -0,0 +1,18 @@
1
+ import { buildStub } from "./stub.js";
2
+ export async function offloadToolOutput(content, ctx) {
3
+ if (content.length <= ctx.thresholdChars)
4
+ return content;
5
+ try {
6
+ const relPath = await ctx.store.write(ctx.toolName, content, ctx.toolCallId);
7
+ return buildStub({
8
+ content,
9
+ relPath,
10
+ previewLines: ctx.previewLines,
11
+ thresholdChars: ctx.thresholdChars,
12
+ });
13
+ }
14
+ catch {
15
+ // Never break a tool because offloading failed; keep the original content.
16
+ return content;
17
+ }
18
+ }
@@ -0,0 +1,8 @@
1
+ export interface BuildStubArgs {
2
+ readonly content: string;
3
+ readonly relPath: string;
4
+ readonly previewLines: number;
5
+ readonly thresholdChars: number;
6
+ }
7
+ export declare function buildStub(args: BuildStubArgs): string;
8
+ //# sourceMappingURL=stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../src/offload/stub.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAChC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAarD"}
@@ -0,0 +1,14 @@
1
+ export function buildStub(args) {
2
+ const lines = args.content.split("\n").slice(0, args.previewLines);
3
+ const shown = lines.length;
4
+ const preview = lines.join("\n");
5
+ const chars = args.content.length.toLocaleString("en-US");
6
+ const threshold = args.thresholdChars.toLocaleString("en-US");
7
+ return [
8
+ `[Tool output offloaded — ${chars} chars exceeded the ${threshold}-char limit.`,
9
+ `Full output saved to: ${args.relPath}`,
10
+ `Preview (first ${shown} lines):`,
11
+ preview,
12
+ `Read the full output with the readFile tool at the path above.]`,
13
+ ].join("\n");
14
+ }
@@ -0,0 +1,31 @@
1
+ import { type BaseMessage } from "@langchain/core/messages";
2
+ export interface RunningSummary {
3
+ readonly summary: string;
4
+ readonly coveredCount: number;
5
+ }
6
+ export type TokenCounter = (text: string) => number | Promise<number>;
7
+ export type SummarizeFn = (args: {
8
+ readonly messages: readonly BaseMessage[];
9
+ readonly model: string;
10
+ readonly previousSummary?: string;
11
+ readonly signal: AbortSignal;
12
+ }) => Promise<string>;
13
+ export interface ResolvedSummarizationConfig {
14
+ readonly maxTokens: number;
15
+ readonly keepRecentTurns: number;
16
+ readonly model: string;
17
+ readonly tokenCounter: TokenCounter;
18
+ readonly summarize: SummarizeFn;
19
+ }
20
+ export interface PreModelHookState {
21
+ readonly messages: BaseMessage[];
22
+ readonly runningSummary?: RunningSummary;
23
+ }
24
+ export interface PreModelHookResult {
25
+ llmInputMessages?: BaseMessage[];
26
+ runningSummary?: RunningSummary;
27
+ }
28
+ export declare function buildSummarizationHook(cfg: ResolvedSummarizationConfig): (state: PreModelHookState, nodeConfig?: {
29
+ readonly signal?: AbortSignal;
30
+ }) => Promise<PreModelHookResult>;
31
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../src/summarization/hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,0BAA0B,CAAA;AAI1E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AAErE,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE;IAC/B,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;CAC7B,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAErB,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAA;IACnC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAA;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,CAAC,EAAE,WAAW,EAAE,CAAA;IAChC,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,2BAA2B,IAEnE,OAAO,iBAAiB,EACxB,aAAa;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAC7C,OAAO,CAAC,kBAAkB,CAAC,CA0C/B"}
@@ -0,0 +1,44 @@
1
+ import { SystemMessage } from "@langchain/core/messages";
2
+ import { splitForSummary } from "./split.js";
3
+ import { countMessagesTokens } from "./token-counter.js";
4
+ export function buildSummarizationHook(cfg) {
5
+ return async (state, nodeConfig) => {
6
+ const messages = state.messages ?? [];
7
+ const total = await countMessagesTokens(messages, cfg.tokenCounter);
8
+ if (total <= cfg.maxTokens)
9
+ return {};
10
+ const prev = state.runningSummary;
11
+ const coveredCount = prev?.coveredCount ?? 0;
12
+ const { toSummarize, recent } = splitForSummary(messages, cfg.keepRecentTurns);
13
+ const newlyAged = toSummarize.slice(coveredCount);
14
+ let summary = prev?.summary ?? "";
15
+ if (newlyAged.length > 0) {
16
+ try {
17
+ summary = await cfg.summarize({
18
+ messages: newlyAged,
19
+ model: cfg.model,
20
+ ...(prev?.summary ? { previousSummary: prev.summary } : {}),
21
+ signal: nodeConfig?.signal ?? new AbortController().signal,
22
+ });
23
+ }
24
+ catch (error) {
25
+ // Summarization failed this turn — fall back to the FULL history.
26
+ // We must explicitly set llmInputMessages to the full messages (not
27
+ // return {}), otherwise a stale condensed view from a prior turn would
28
+ // remain in the channel and the model would answer without the latest
29
+ // turn's messages.
30
+ if (process.env.DAWN_DEBUG_SUMMARIZATION === "1") {
31
+ console.warn("[dawn] summarization failed — falling back to full history this turn:", error instanceof Error ? error.message : String(error));
32
+ }
33
+ return { llmInputMessages: messages };
34
+ }
35
+ }
36
+ if (!summary)
37
+ return {};
38
+ const summaryMessage = new SystemMessage(`Summary of earlier conversation:\n${summary}`);
39
+ return {
40
+ llmInputMessages: [summaryMessage, ...recent],
41
+ runningSummary: { summary, coveredCount: toSummarize.length },
42
+ };
43
+ };
44
+ }
@@ -0,0 +1,5 @@
1
+ export { buildSummarizationHook, type PreModelHookResult, type PreModelHookState, type ResolvedSummarizationConfig, type RunningSummary, type SummarizeFn, type TokenCounter, } from "./hook.js";
2
+ export { splitForSummary } from "./split.js";
3
+ export { defaultSummarize } from "./summarize.js";
4
+ export { countMessagesTokens, defaultTokenCounter } from "./token-counter.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/summarization/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { buildSummarizationHook, } from "./hook.js";
2
+ export { splitForSummary } from "./split.js";
3
+ export { defaultSummarize } from "./summarize.js";
4
+ export { countMessagesTokens, defaultTokenCounter } from "./token-counter.js";
@@ -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
+ }
@@ -10,7 +10,8 @@ interface DawnToolDefinition {
10
10
  }) => Promise<unknown> | unknown;
11
11
  readonly schema?: unknown;
12
12
  }
13
- export declare function convertToolToLangChain(tool: DawnToolDefinition, middlewareContext?: Readonly<Record<string, unknown>>): DynamicStructuredTool;
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;
14
15
  export declare function jsonSchemaToZod(schema: JsonSchemaProperty): z.ZodObject<z.ZodRawShape>;
15
16
  export {};
16
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,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
+ {"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"}
@@ -3,7 +3,7 @@ import { DynamicStructuredTool } from "@langchain/core/tools";
3
3
  import { Command } from "@langchain/langgraph";
4
4
  import { z } from "zod";
5
5
  import { unwrapToolResult } from "./unwrap-tool-result.js";
6
- export function convertToolToLangChain(tool, middlewareContext) {
6
+ export function convertToolToLangChain(tool, middlewareContext, offload) {
7
7
  const schema = toZodSchema(tool.schema);
8
8
  return new DynamicStructuredTool({
9
9
  name: tool.name,
@@ -16,14 +16,17 @@ export function convertToolToLangChain(tool, middlewareContext) {
16
16
  signal,
17
17
  });
18
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;
19
23
  if (stateUpdates) {
20
- const toolCallId = extractToolCallId(config);
21
24
  return new Command({
22
25
  update: {
23
26
  ...stateUpdates,
24
27
  messages: [
25
28
  new ToolMessage({
26
- content,
29
+ content: finalContent,
27
30
  tool_call_id: toolCallId,
28
31
  name: tool.name,
29
32
  }),
@@ -31,7 +34,7 @@ export function convertToolToLangChain(tool, middlewareContext) {
31
34
  },
32
35
  });
33
36
  }
34
- return content;
37
+ return finalContent;
35
38
  },
36
39
  });
37
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dawn-ai/langchain",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,8 +32,10 @@
32
32
  "dependencies": {
33
33
  "@langchain/langgraph": "^1.3.0",
34
34
  "@langchain/openai": "^1.4.5",
35
- "@dawn-ai/core": "0.2.0",
36
- "@dawn-ai/sdk": "0.2.0"
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"
37
39
  },
38
40
  "peerDependencies": {
39
41
  "@langchain/core": "^1.1.47",
@@ -86,7 +88,7 @@
86
88
  "@langchain/xai": "^1.3.18",
87
89
  "@types/node": "25.6.0",
88
90
  "zod": "4.4.3",
89
- "@dawn-ai/config-typescript": "0.2.0"
91
+ "@dawn-ai/config-typescript": "0.3.0"
90
92
  },
91
93
  "scripts": {
92
94
  "build": "tsc -b tsconfig.json",