agent-lattice 0.9.25 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,11 @@ Install it from npm as `agent-lattice`. It works with Anthropic and
7
7
  Anthropic-compatible providers such as DeepSeek, without installing the Claude
8
8
  Code CLI runtime.
9
9
 
10
+ > **Integrating this SDK from an AI agent?** Start at
11
+ > <https://docs.claude-code-sdk.com/llms.txt> for an index of the documentation,
12
+ > where every page is served as clean Markdown. Read
13
+ > <https://docs.claude-code-sdk.com/llms-full.txt> to take it all in one request.
14
+
10
15
  ## Install
11
16
 
12
17
  ```bash
@@ -50,6 +55,24 @@ const agent = createBareAgent({
50
55
  });
51
56
  ```
52
57
 
58
+ `agent.query()` yields `stream_event` messages while the model is still
59
+ responding, so a host can render output incrementally:
60
+
61
+ ```ts
62
+ for await (const message of agent.query("Say hello")) {
63
+ if (message.type === "stream_event") {
64
+ const event = message.event as { type: string; delta?: { text?: string } };
65
+ if (event.type === "content_block_delta" && event.delta?.text) {
66
+ process.stdout.write(event.delta.text);
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ The SDK produces the next event only after the loop takes the current one.
73
+ Slow work in the loop body delays later events without dropping or reordering
74
+ them, so keep expensive handling off the loop itself.
75
+
53
76
  Pass `{ stream: false }` to disable model streaming for a query:
54
77
 
55
78
  ```ts
@@ -103,6 +126,30 @@ Use `{ type: "adaptive" }` for models that support adaptive thinking. Use
103
126
  budget is capped at `maxTokens - 1` to satisfy the Anthropic API constraint.
104
127
  When omitted, the SDK does not send a thinking configuration.
105
128
 
129
+ For Kimi K3 through an Anthropic-compatible endpoint or gateway, use
130
+ `reasoningEffort` to send the provider's top-level `reasoning_effort` parameter:
131
+
132
+ ```ts
133
+ const agent = createAgent({
134
+ apiKey: process.env.MOONSHOT_API_KEY,
135
+ baseURL: process.env.KIMI_ANTHROPIC_BASE_URL,
136
+ model: "kimi-k3",
137
+ reasoningEffort: "high",
138
+ });
139
+
140
+ const result = await agent.prompt("Solve this carefully.", {
141
+ reasoningEffort: "low",
142
+ });
143
+ ```
144
+
145
+ The supported values are `"low"`, `"high"`, and `"max"`. A query-level value
146
+ overrides the agent default. When omitted, the SDK does not send
147
+ `reasoning_effort`, so the provider applies its own default (`"max"` for Kimi
148
+ K3). Kimi K3 does not accept `thinkingConfig`; use `reasoningEffort` instead.
149
+ The Kimi Open Platform endpoint at `https://api.moonshot.cn/v1` uses the OpenAI
150
+ Chat Completions protocol and is not a valid `baseURL` for the SDK's built-in
151
+ Anthropic client.
152
+
106
153
  ## Multimodal Input
107
154
 
108
155
  Pass Anthropic-compatible content blocks for image or document prompts:
@@ -369,6 +416,44 @@ policy, tool execution is unchanged. A policy prevents known bad combinations
369
416
  inside one model response, but it does not replace database transactions or
370
417
  revision checks against concurrent external updates.
371
418
 
419
+ ## Hooks
420
+
421
+ `permission` and `toolBatchPolicy` decide whether something runs. Hooks decide
422
+ what it looks like — redacting tool output, trimming context before a request,
423
+ or injecting retrieved documents:
424
+
425
+ ```ts
426
+ const agent = createAgent({
427
+ apiKey: process.env.ANTHROPIC_API_KEY,
428
+ model: "claude-sonnet-4-6",
429
+ tools: [queryDatabase],
430
+ hooks: {
431
+ async onToolResult({ toolName, result, error }) {
432
+ if (toolName !== "queryDatabase") return; // undefined: leave unchanged
433
+ return { ...result, content: await redact(result.content) };
434
+ },
435
+ onModelRequest({ messages, turn }) {
436
+ if (messages.length < 40) return;
437
+ return { messages: compact(messages) };
438
+ },
439
+ },
440
+ });
441
+ ```
442
+
443
+ `onToolResult` sees every result on its way to the model, including handler
444
+ failures, aborted calls, and calls blocked by `toolBatchPolicy`. `onModelRequest`
445
+ shapes a single request; the stored conversation is untouched, so trimming
446
+ context does not destroy history.
447
+
448
+ A hook returns a replacement or nothing, and must not mutate what it receives. A
449
+ hook that throws propagates out of `query()` rather than becoming an error
450
+ `result` — a redaction hook that failed quietly would leak the data it exists to
451
+ protect. Hooks run before the matching trace event, so traces record what was
452
+ actually sent.
453
+
454
+ Compose independent concerns with `createCompositeAgentHooks([a, b, c])`, which
455
+ chains them in order, each receiving the previous one's output.
456
+
372
457
  ## Business Context For Tools
373
458
 
374
459
  Pass host application data through `context`. The SDK gives that context to
@@ -897,3 +982,62 @@ console.log(result.result);
897
982
  The SDK stores conversation state in memory for the lifetime of the `Agent`
898
983
  instance. Persistent transcripts and resume support are intentionally out of
899
984
  scope for the first release.
985
+
986
+ An `Agent` is a conversation, not a reusable client. Because the history is
987
+ instance state, starting a query while another is still running would interleave
988
+ both conversations; the SDK rejects the second one with `ConcurrentQueryError`.
989
+ Create one Agent per concurrent conversation — in a server, per request or per
990
+ user session rather than a shared module-level instance. Sequential reuse, as
991
+ above, is the intended pattern.
992
+
993
+ ## Deadlines
994
+
995
+ `QueryOptions.signal` bounds a whole query — every model request, tool call, and
996
+ turn together. `requestTimeoutMs` bounds each single model request, so an agent
997
+ that legitimately runs many tool-using turns does not have to fit them all into
998
+ one budget:
999
+
1000
+ ```ts
1001
+ const agent = createAgent({
1002
+ apiKey: process.env.ANTHROPIC_API_KEY,
1003
+ model: "claude-sonnet-4-6",
1004
+ requestTimeoutMs: 120_000,
1005
+ });
1006
+
1007
+ const result = await agent.prompt("Audit this repository.", {
1008
+ signal: AbortSignal.timeout(600_000),
1009
+ requestTimeoutMs: 60_000, // overrides the agent default for this query
1010
+ });
1011
+ ```
1012
+
1013
+ A request deadline produces `subtype: "error_timeout"` with a `TimeoutError`,
1014
+ distinct from the `"error_abort"` of a caller-initiated cancellation, so hosts
1015
+ can retry timeouts without retrying deliberate cancellations.
1016
+
1017
+ Both limits are enforced by the SDK rather than delegated. `ModelRequest` carries
1018
+ `signal` and `timeoutMs` so a client can cancel its own work, but the agent loop
1019
+ also races the call, so a `ModelClient` that honours neither cannot stall the
1020
+ loop indefinitely. Losing that race abandons the call rather than cancelling it.
1021
+
1022
+ ## Token Usage And Truncation
1023
+
1024
+ Every `result` message reports `usage`, summed over the model requests in that
1025
+ query, plus the `stop_reason` of the last response:
1026
+
1027
+ ```ts
1028
+ const result = await agent.prompt("Summarize this file.");
1029
+ console.log(result.usage);
1030
+ // { input_tokens: 1200, output_tokens: 512, cache_read_input_tokens: 800 }
1031
+
1032
+ if (result.stop_reason === "max_tokens") {
1033
+ // subtype is still "success", but result is a fragment, not an answer.
1034
+ }
1035
+ ```
1036
+
1037
+ `stop_reason: "max_tokens"` means the model hit its output budget mid-response.
1038
+ The SDK does not treat that as an error, so checking this field is the only way
1039
+ to distinguish a complete answer from a truncated one.
1040
+
1041
+ Usage comes from the model client. The built-in Anthropic client fills it in from
1042
+ the response, including the streaming path; a custom `ModelClient` that omits
1043
+ `usage` produces zeroed counts rather than an error.
package/dist/index.d.ts CHANGED
@@ -150,9 +150,23 @@ export type ModelMessage = {
150
150
  role: "user" | "assistant";
151
151
  content: string | ContentBlock[];
152
152
  };
153
+ export type TokenUsage = {
154
+ input_tokens: number;
155
+ output_tokens: number;
156
+ cache_creation_input_tokens?: number;
157
+ cache_read_input_tokens?: number;
158
+ };
159
+ /**
160
+ * Why the model stopped. `"max_tokens"` means the response was cut off mid-way:
161
+ * the text is a fragment, not an answer. Left open because providers add values.
162
+ */
163
+ export type StopReason = "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | "pause_turn" | "refusal" | (string & {});
153
164
  export type AssistantModelMessage = {
154
165
  role: "assistant";
155
166
  content: ContentBlock[];
167
+ /** Absent when a custom ModelClient does not report it. */
168
+ usage?: TokenUsage;
169
+ stopReason?: StopReason;
156
170
  };
157
171
  export type ModelToolDefinition = {
158
172
  name: string;
@@ -168,6 +182,9 @@ export type ModelRequest = {
168
182
  stream: boolean;
169
183
  outputFormat?: OutputFormat;
170
184
  thinkingConfig?: ThinkingConfig;
185
+ reasoningEffort?: ReasoningEffort;
186
+ /** Deadline for this single request. Clients should honour it; the SDK also enforces it. */
187
+ timeoutMs?: number;
171
188
  onStreamEvent?: (event: Record<string, unknown>) => void;
172
189
  signal?: AbortSignal;
173
190
  };
@@ -210,6 +227,54 @@ export type ToolExecutionContext<TContext = unknown> = {
210
227
  agentRuntime?: AgentRuntimeContext;
211
228
  permissions?: RuntimePermissions;
212
229
  };
230
+ export type ToolResultHookContext<TContext = unknown> = {
231
+ toolName: string;
232
+ toolUseId: string;
233
+ /** Raw input as the model sent it, before the tool schema parsed it. */
234
+ input: unknown;
235
+ /** What the SDK would send back to the model. */
236
+ result: ToolResultBlock;
237
+ /** Present when the handler failed, was denied, or was cancelled. */
238
+ error?: Error;
239
+ context?: TContext;
240
+ source?: AgentRuntimeSource;
241
+ signal?: AbortSignal;
242
+ };
243
+ export type ModelRequestHookContext<TContext = unknown> = {
244
+ /** What the SDK would send. Not the stored history; see AgentHooks. */
245
+ messages: ModelMessage[];
246
+ systemPrompt?: string;
247
+ /** 1 for the first model request of the query. */
248
+ turn: number;
249
+ context?: TContext;
250
+ source?: AgentRuntimeSource;
251
+ signal?: AbortSignal;
252
+ };
253
+ export type ModelRequestHookResult = {
254
+ messages?: ModelMessage[];
255
+ systemPrompt?: string;
256
+ };
257
+ /**
258
+ * Lifecycle callbacks that can rewrite what crosses the agent loop's boundaries,
259
+ * as opposed to `permission` and `toolBatchPolicy`, which can only allow or deny.
260
+ *
261
+ * A hook returns a replacement, or nothing to leave the value unchanged; it must
262
+ * not mutate what it receives.
263
+ *
264
+ * A hook that throws propagates out of `query()` rather than being swallowed the
265
+ * way a tracer error is, or being reported as an error `result`. A hook failure
266
+ * is host code failing, like `ConcurrentQueryError`, not an upstream failure the
267
+ * loop can describe to the model — and a redaction hook that failed quietly
268
+ * would leak the data it exists to protect.
269
+ *
270
+ * Hooks run before the matching trace event, so traces record what was actually
271
+ * sent. `onModelRequest` shapes one request only and never edits the stored
272
+ * conversation, so trimming context for a long turn does not destroy history.
273
+ */
274
+ export type AgentHooks<TContext = unknown> = {
275
+ onToolResult?(context: ToolResultHookContext<TContext>): ToolResultBlock | void | Promise<ToolResultBlock | void>;
276
+ onModelRequest?(context: ModelRequestHookContext<TContext>): ModelRequestHookResult | void | Promise<ModelRequestHookResult | void>;
277
+ };
213
278
  export type ToolHandler<TInput = unknown, TContext = unknown> = (input: TInput, context: ToolExecutionContext<TContext>) => Promise<ToolResult> | ToolResult;
214
279
  export type ToolOptions<TInput = unknown> = {
215
280
  isConcurrencySafe?: (input: TInput) => boolean;
@@ -421,8 +486,13 @@ export type AgentOptions<TContext = unknown> = {
421
486
  maxTokens?: number;
422
487
  maxTurns?: number;
423
488
  thinkingConfig?: ThinkingConfig;
489
+ reasoningEffort?: ReasoningEffort;
490
+ /** Deadline for each single model request, in milliseconds. Unset means no SDK-side limit. */
491
+ requestTimeoutMs?: number;
424
492
  tools?: Array<ToolDefinition<any, TContext>>;
425
493
  toolBatchPolicy?: ToolBatchPolicy<TContext>;
494
+ /** Lifecycle callbacks that rewrite tool results and outgoing model requests. */
495
+ hooks?: AgentHooks<TContext>;
426
496
  toolConcurrency?: ToolConcurrencyOptions;
427
497
  skills?: SkillDefinition[];
428
498
  workspace?: AgentWorkspaceOptions;
@@ -440,6 +510,9 @@ export type QueryOptions<TContext = unknown> = {
440
510
  stream?: boolean;
441
511
  outputFormat?: OutputFormat;
442
512
  thinkingConfig?: ThinkingConfig;
513
+ reasoningEffort?: ReasoningEffort;
514
+ /** Overrides the agent's per-request deadline for this query. */
515
+ requestTimeoutMs?: number;
443
516
  signal?: AbortSignal;
444
517
  context?: TContext;
445
518
  agentRuntime?: AgentRuntimeContext;
@@ -455,6 +528,7 @@ export type ThinkingConfig = {
455
528
  } | {
456
529
  type: "disabled";
457
530
  };
531
+ export type ReasoningEffort = "low" | "high" | "max";
458
532
  export type SDKSystemInitMessage = {
459
533
  type: "system";
460
534
  subtype: "init";
@@ -481,12 +555,19 @@ export type SDKStreamEventMessage = {
481
555
  };
482
556
  export type SDKResultMessage = {
483
557
  type: "result";
484
- subtype: "success" | "error" | "error_max_turns" | "error_abort";
558
+ subtype: "success" | "error" | "error_max_turns" | "error_abort" | "error_timeout";
485
559
  is_error: boolean;
486
560
  result: string;
487
561
  session_id: string;
488
562
  num_turns: number;
489
563
  error?: Error;
564
+ /** Summed over every model request in the query. Zeroed when unreported. */
565
+ usage: TokenUsage;
566
+ /**
567
+ * From the last model response. Check for `"max_tokens"`: `subtype` is still
568
+ * `"success"` there, but `result` is a truncated fragment.
569
+ */
570
+ stop_reason?: StopReason;
490
571
  };
491
572
  export type SDKMessage = SDKSystemInitMessage | SDKStreamEventMessage | SDKAssistantMessage | SDKUserMessage | SDKResultMessage;
492
573
  export type TeamRunnerSource = AgentRuntimeSource;
@@ -528,6 +609,12 @@ export declare class MaxTurnsError extends AgentSDKError {
528
609
  }
529
610
  export declare class AbortError extends AgentSDKError {
530
611
  }
612
+ /** A second query was started on an Agent that was still running one. */
613
+ export declare class ConcurrentQueryError extends AgentSDKError {
614
+ }
615
+ /** A model request exceeded `requestTimeoutMs`. */
616
+ export declare class TimeoutError extends AgentSDKError {
617
+ }
531
618
  export declare class ToolBatchRejectedError extends AgentSDKError {
532
619
  readonly rejection: ToolBatchPolicyRejection;
533
620
  constructor(rejection: ToolBatchPolicyRejection);
@@ -575,6 +662,12 @@ export declare function createAgent<TContext = unknown>(options: AgentOptions<TC
575
662
  export declare function createBareAgent<TContext = unknown>(options: BareAgentOptions<TContext>): Agent<TContext>;
576
663
  export declare function createBuiltinTools(options?: AgentWorkspaceToolsOptions): Array<ToolDefinition<any, any>>;
577
664
  export declare function createJsonlContextTracer(options: JsonlContextTracerOptions): ContextTracer;
665
+ /**
666
+ * Chains hooks in array order: each one sees the previous one's output, so
667
+ * redaction, truncation, and auditing can be written separately and combined.
668
+ * Unlike the composite tracer, a failure is not swallowed — see `AgentHooks`.
669
+ */
670
+ export declare function createCompositeAgentHooks<TContext = unknown>(hooks: Array<AgentHooks<TContext> | undefined | null>): AgentHooks<TContext>;
578
671
  export declare function createCompositeContextTracer(tracers: Array<ContextTracer | undefined | null>): ContextTracer;
579
672
  export declare function createLangSmithContextTracer(options: LangSmithContextTracerOptions): ContextTracer;
580
673
  export declare function skill(input: SkillInput): SkillDefinition;
@@ -593,6 +686,7 @@ export declare function query<TContext = unknown>(params: AgentOptions<TContext>
593
686
  stream?: boolean;
594
687
  outputFormat?: OutputFormat;
595
688
  thinkingConfig?: ThinkingConfig;
689
+ reasoningEffort?: ReasoningEffort;
596
690
  signal?: AbortSignal;
597
691
  context?: TContext;
598
692
  }): AsyncGenerator<SDKMessage>;
@@ -602,8 +696,15 @@ export declare class Agent<TContext = unknown> {
602
696
  private readonly messages;
603
697
  private readonly sessionId;
604
698
  private readonly toolConcurrency;
699
+ private running;
605
700
  constructor(options: AgentOptions<TContext>);
701
+ /**
702
+ * One Agent owns one conversation. Overlapping queries would interleave writes
703
+ * into the shared history, so the second caller is rejected rather than served
704
+ * a conversation containing someone else's turns.
705
+ */
606
706
  query(prompt: string | ContentBlock[], options?: QueryOptions<TContext>): AsyncGenerator<SDKMessage>;
707
+ private runQuery;
607
708
  prompt(prompt: string | ContentBlock[], options?: QueryOptions<TContext>): Promise<SDKResultMessage>;
608
709
  addTools(tools: Array<ToolDefinition<any, TContext>>): void;
609
710
  private initMessage;