@tangle-network/agent-app 0.6.0 → 0.7.1

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,45 @@
1
+ import {
2
+ DEFAULT_MISSION_STEP_KINDS,
3
+ MISSION_CONTROL_CHANNEL_ID,
4
+ MissionConcurrencyError,
5
+ RetryableStepError,
6
+ applyMissionEvent,
7
+ asMissionStreamEvent,
8
+ budgetGateProposalId,
9
+ buildAgentMissionPlan,
10
+ createInMemoryMissionStore,
11
+ createMissionEngine,
12
+ createMissionService,
13
+ isMissionStopRequested,
14
+ isMissionTerminal,
15
+ mergeMissionState,
16
+ noopEventSink,
17
+ parseMissionBlocks,
18
+ parseSessionStreamEnvelope,
19
+ reduceMissionEvents,
20
+ stepGateProposalId,
21
+ volumeGateProposalId
22
+ } from "../chunk-UIWB2F6N.js";
23
+ export {
24
+ DEFAULT_MISSION_STEP_KINDS,
25
+ MISSION_CONTROL_CHANNEL_ID,
26
+ MissionConcurrencyError,
27
+ RetryableStepError,
28
+ applyMissionEvent,
29
+ asMissionStreamEvent,
30
+ budgetGateProposalId,
31
+ buildAgentMissionPlan,
32
+ createInMemoryMissionStore,
33
+ createMissionEngine,
34
+ createMissionService,
35
+ isMissionStopRequested,
36
+ isMissionTerminal,
37
+ mergeMissionState,
38
+ noopEventSink,
39
+ parseMissionBlocks,
40
+ parseSessionStreamEnvelope,
41
+ reduceMissionEvents,
42
+ stepGateProposalId,
43
+ volumeGateProposalId
44
+ };
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,6 +1,6 @@
1
1
  import { KeyProvisioner, KeyCrypto, WorkspaceKeyManager, WorkspaceKeyStore } from '../billing/index.js';
2
2
  import { KnowledgeStateAccessor } from '../knowledge/index.js';
3
- import { c as AppToolHandlers } from '../types-CTOaTNtU.js';
3
+ import { c as AppToolHandlers } from '../types-By4B3K37.js';
4
4
  import { KvLike } from '../web/index.js';
5
5
  import '@tangle-network/agent-eval';
6
6
 
@@ -1,6 +1,6 @@
1
1
  export { C as CatalogModel, M as ModelCatalog, R as RouterModel, _ as __resetCatalogCache, b as buildCatalog, f as fetchModelCatalog, n as normalizeModelId } from '../model-catalog-BEAEVDaa.js';
2
2
  export { C as CreateTangleRouterModelConfigOptions, D as DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR, a as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, b as ResolveUserTangleExecutionKeyForUserOptions, c as ResolveUserTangleExecutionKeyOptions, d as ResolvedTangleExecutionKey, T as TangleBillingEnforcementOptions, e as TangleExecutionEnvironment, f as TangleExecutionKeyError, g as TangleExecutionKeyErrorCode, h as TangleExecutionKeyHttpError, i as TangleExecutionKeySource, j as TangleModelConfig, k as createTangleRouterModelConfig, l as isTangleBillingEnforcementDisabled, m as isTangleExecutionKeyError, r as resolveTangleExecutionEnvironment, n as resolveTangleModelConfig, o as resolveUserTangleExecutionKey, p as resolveUserTangleExecutionKeyForUser, t as tangleExecutionKeyHttpError } from '../model-CKzniMMr.js';
3
- import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-CTOaTNtU.js';
3
+ import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-By4B3K37.js';
4
4
 
5
5
  /**
6
6
  * OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.
@@ -129,7 +129,8 @@ interface CreateAgentRuntimeOptions {
129
129
  handlers: AppToolHandlers;
130
130
  /** Default agent identity / system prompt. A turn may override it. */
131
131
  systemPrompt: string;
132
- /** Max tool-driven re-runs per turn. Default 8. */
132
+ /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.
133
+ * For per-workflow limits use `deadlineMs` or `maxCostUsd` on the loop options. */
133
134
  maxToolTurns?: number;
134
135
  /** Extra OpenAI tool definitions advertised ALONGSIDE the four app tools
135
136
  * (e.g. `integration_invoke`). Pair with {@link executeOtherTool}. */
@@ -228,6 +229,12 @@ type LoopEvent = {
228
229
  type: 'other';
229
230
  event: unknown;
230
231
  };
232
+ /** Why the loop stopped. `completed` = model finished naturally; `stuck-loop` =
233
+ * ≥3 consecutive identical tool calls (same tool + args); `backstop` = hit the
234
+ * runaway-backstop cap (200 by default); `deadline` = wall-clock deadlineMs
235
+ * exceeded; `budget` = maxCostUsd exhausted. Non-`completed` stops are infra /
236
+ * resource outcomes — eval scoring must distinguish them from capability failure. */
237
+ type ToolLoopStopReason = 'completed' | 'stuck-loop' | 'backstop' | 'deadline' | 'budget';
231
238
  interface ToolLoopResult {
232
239
  /** The model's final text across the loop. */
233
240
  finalText: string;
@@ -239,7 +246,9 @@ interface ToolLoopResult {
239
246
  }>;
240
247
  /** Number of model turns run (1 + tool-driven re-runs). */
241
248
  turns: number;
242
- /** True when the loop stopped because it hit `maxToolTurns` with calls still pending. */
249
+ /** Why the loop stopped. */
250
+ stopReason: ToolLoopStopReason;
251
+ /** @deprecated Use `stopReason !== 'completed'` instead. */
243
252
  cappedOut: boolean;
244
253
  }
245
254
  interface AppToolLoopOptions {
@@ -260,8 +269,16 @@ interface AppToolLoopOptions {
260
269
  /** Which emitted tool names are executable (others are ignored — e.g. a UI-only
261
270
  * tool the app renders but doesn't run here). */
262
271
  isExecutableTool: (toolName: string) => boolean;
263
- /** Max tool-driven re-runs. Default 8. */
272
+ /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.
273
+ * For per-workflow limits use `maxCostUsd` or `deadlineMs` instead. */
264
274
  maxToolTurns?: number;
275
+ /** Wall-clock deadline in ms since epoch (Date.now()-based). When exceeded the
276
+ * loop stops with stopReason `deadline`. */
277
+ deadlineMs?: number;
278
+ /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */
279
+ maxCostUsd?: number;
280
+ /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */
281
+ costOf?: (call: LoopToolCall, outcome: AppToolOutcome) => number;
265
282
  /** Render one tool outcome as the `content` of its `role: 'tool'` message.
266
283
  * Default is a compact `<label> → ok/failed: …`. */
267
284
  renderResult?: (label: string, outcome: AppToolOutcome) => string;
@@ -288,6 +305,7 @@ type StreamLoopYield<Raw> = {
288
305
  } | {
289
306
  kind: 'capped';
290
307
  pending: number;
308
+ stopReason: Exclude<ToolLoopStopReason, 'completed'>;
291
309
  };
292
310
  interface StreamAppToolLoopOptions<Raw> {
293
311
  systemPrompt: string;
@@ -309,16 +327,23 @@ interface StreamAppToolLoopOptions<Raw> {
309
327
  isExecutableTool: (toolName: string) => boolean;
310
328
  /** Execute one call — the app routes to its integration / app-tool executor. */
311
329
  executeToolCall: (call: LoopToolCall) => Promise<AppToolOutcome>;
330
+ /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */
312
331
  maxToolTurns?: number;
332
+ /** Wall-clock deadline in ms since epoch (Date.now()-based). */
333
+ deadlineMs?: number;
334
+ /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */
335
+ maxCostUsd?: number;
336
+ /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */
337
+ costOf?: (call: LoopToolCall, outcome: AppToolOutcome) => number;
313
338
  renderResult?: (label: string, outcome: AppToolOutcome) => string;
314
339
  labelFor?: (call: LoopToolCall) => string;
315
340
  }
316
341
  /**
317
342
  * The streaming bounded tool loop. Yields `event` for each raw turn event and
318
- * `tool_result` for each executed tool; emits a single `capped` when it stops at
319
- * the turn limit with calls still pending. The app drives telemetry + UI
343
+ * `tool_result` for each executed tool; emits a single `capped` (with stopReason)
344
+ * when it stops for any non-completed reason. The app drives telemetry + UI
320
345
  * emission off the yielded items.
321
346
  */
322
347
  declare function streamAppToolLoop<Raw>(opts: StreamAppToolLoopOptions<Raw>): AsyncGenerator<StreamLoopYield<Raw>, void, unknown>;
323
348
 
324
- export { type AgentRuntime, type AgentRuntimeModelConfig, type AgentTurnOptions, type AppToolLoopOptions, type CreateAgentRuntimeOptions, type LoopAssistantToolCall, type LoopEvent, type LoopMessage, type LoopToolCall, type OpenAICompatStreamTurnOptions, type OpenAIStreamChunk, type StreamAppToolLoopOptions, type StreamLoopYield, type ToolLoopResult, createAgentRuntime, createOpenAICompatStreamTurn, runAppToolLoop, streamAppToolLoop, toLoopEvents };
349
+ export { type AgentRuntime, type AgentRuntimeModelConfig, type AgentTurnOptions, type AppToolLoopOptions, type CreateAgentRuntimeOptions, type LoopAssistantToolCall, type LoopEvent, type LoopMessage, type LoopToolCall, type OpenAICompatStreamTurnOptions, type OpenAIStreamChunk, type StreamAppToolLoopOptions, type StreamLoopYield, type ToolLoopResult, type ToolLoopStopReason, createAgentRuntime, createOpenAICompatStreamTurn, runAppToolLoop, streamAppToolLoop, toLoopEvents };
@@ -8,7 +8,7 @@ import {
8
8
  runAppToolLoop,
9
9
  streamAppToolLoop,
10
10
  toLoopEvents
11
- } from "../chunk-EO4IGDQD.js";
11
+ } from "../chunk-4YTWB5MG.js";
12
12
  import {
13
13
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
14
14
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -22,7 +22,7 @@ import {
22
22
  resolveUserTangleExecutionKeyForUser,
23
23
  tangleExecutionKeyHttpError
24
24
  } from "../chunk-EHPK7GKR.js";
25
- import "../chunk-JANT2G2E.js";
25
+ import "../chunk-QAQBR6KQ.js";
26
26
  export {
27
27
  DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
28
28
  DEFAULT_TANGLE_ROUTER_BASE_URL,
@@ -36,4 +36,91 @@ declare function resolveChatTurn(input: {
36
36
  turnId?: string;
37
37
  }): ResolvedChatTurn;
38
38
 
39
- export { type JsonRecord, type PersistedChatMessageForTurn, type ResolvedChatTurn, type StreamEvent, asRecord, asString, buildUserTextParts, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, resolveChatTurn, resolveToolId, resolveToolName };
39
+ /**
40
+ * Resumable chat turns — the router-path answer to "streams resume on
41
+ * disconnect" (issue #27). A turn's loop events are teed into a store as they
42
+ * stream; the turn keeps running under `ctx.waitUntil` when the client drops;
43
+ * a reconnecting client replays the buffered tail by sequence number and
44
+ * keeps following until the turn completes.
45
+ *
46
+ * POST /chat/stream → pumpBufferedTurn(...) + live NDJSON
47
+ * GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON
48
+ *
49
+ * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation
50
+ * ships here because that's what Cloudflare products have (KV is unsuitable:
51
+ * eventually consistent cross-isolate). Per-token deltas would mean hundreds
52
+ * of rows per turn, so consecutive text/reasoning deltas are coalesced within
53
+ * a flush window before they are persisted — replay yields slightly chunkier
54
+ * deltas with identical concatenation.
55
+ */
56
+ type TurnStatus = 'running' | 'complete' | 'error';
57
+ interface BufferedTurnEvent {
58
+ seq: number;
59
+ /** The serialized event line (JSON string, no trailing newline). */
60
+ event: string;
61
+ }
62
+ interface TurnEventStore {
63
+ append(turnId: string, events: BufferedTurnEvent[]): Promise<void>;
64
+ read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>;
65
+ setStatus(turnId: string, status: TurnStatus): Promise<void>;
66
+ getStatus(turnId: string): Promise<TurnStatus | null>;
67
+ }
68
+ /** Merge consecutive text/reasoning deltas of the same type into one event.
69
+ * Concatenation-preserving: replaying the coalesced stream produces the same
70
+ * accumulated text as the original. */
71
+ declare function coalesceDeltas(events: unknown[]): unknown[];
72
+ interface PumpBufferedTurnOptions {
73
+ source: AsyncIterable<unknown>;
74
+ store: TurnEventStore;
75
+ turnId: string;
76
+ /** Deliver one serialized line (with seq) to the live client. Throwing here
77
+ * (client disconnected) does NOT stop the turn — events keep buffering. */
78
+ write?: (line: string) => Promise<void> | void;
79
+ /** Flush buffered events to the store at most this often. Default 400ms. */
80
+ flushIntervalMs?: number;
81
+ }
82
+ /**
83
+ * Drive a turn to completion regardless of the live client: every source
84
+ * event is sequence-numbered, delivered to `write` (best-effort), and flushed
85
+ * to the store in coalesced batches. Returns a promise that resolves when the
86
+ * turn finishes — hand it to `ctx.waitUntil` so a disconnect can't kill the
87
+ * turn. Never rejects on client-write failure; a source error marks the turn
88
+ * status 'error' (after flushing what was produced) and rethrows.
89
+ */
90
+ declare function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void>;
91
+ interface ReplayTurnEventsOptions {
92
+ store: TurnEventStore;
93
+ turnId: string;
94
+ /** Replay strictly after this sequence number (0 = from the beginning). */
95
+ fromSeq?: number;
96
+ /** Poll cadence while the turn is still running. Default 500ms. */
97
+ pollMs?: number;
98
+ /** Give up following a 'running' turn after this long. Default 120s. */
99
+ timeoutMs?: number;
100
+ }
101
+ /**
102
+ * Yield buffered events after `fromSeq`, then keep polling while the turn is
103
+ * still 'running' until it completes, errors, or times out. Terminates with a
104
+ * final `{seq: -1, event: '{"type":"turn_status",...}'}` marker so clients
105
+ * know why the replay ended.
106
+ */
107
+ declare function replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent>;
108
+ /** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */
109
+ interface D1LikeForTurns {
110
+ prepare(sql: string): {
111
+ bind(...values: unknown[]): {
112
+ run(): Promise<unknown>;
113
+ all<T = Record<string, unknown>>(): Promise<{
114
+ results: T[];
115
+ }>;
116
+ first<T = Record<string, unknown>>(): Promise<T | null>;
117
+ };
118
+ };
119
+ }
120
+ /** Schema for the D1 store — append to the product's migrations. */
121
+ declare const TURN_EVENTS_MIGRATION_SQL = "\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n updatedAt TEXT NOT NULL\n);\n";
122
+ declare function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore;
123
+ /** In-memory store for tests and keyless local dev. */
124
+ declare function createMemoryTurnEventStore(): TurnEventStore;
125
+
126
+ export { type BufferedTurnEvent, type D1LikeForTurns, type JsonRecord, type PersistedChatMessageForTurn, type PumpBufferedTurnOptions, type ReplayTurnEventsOptions, type ResolvedChatTurn, type StreamEvent, TURN_EVENTS_MIGRATION_SQL, type TurnEventStore, type TurnStatus, asRecord, asString, buildUserTextParts, coalesceDeltas, createD1TurnEventStore, createMemoryTurnEventStore, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, pumpBufferedTurn, replayTurnEvents, resolveChatTurn, resolveToolId, resolveToolName };
@@ -1,7 +1,11 @@
1
1
  import {
2
+ TURN_EVENTS_MIGRATION_SQL,
2
3
  asRecord,
3
4
  asString,
4
5
  buildUserTextParts,
6
+ coalesceDeltas,
7
+ createD1TurnEventStore,
8
+ createMemoryTurnEventStore,
5
9
  encodeEvent,
6
10
  finalizeAssistantParts,
7
11
  getPartKey,
@@ -11,14 +15,20 @@ import {
11
15
  normalizePersistedPart,
12
16
  normalizeTime,
13
17
  normalizeToolEvent,
18
+ pumpBufferedTurn,
19
+ replayTurnEvents,
14
20
  resolveChatTurn,
15
21
  resolveToolId,
16
22
  resolveToolName
17
- } from "../chunk-GMFPCCQZ.js";
23
+ } from "../chunk-SDOT7RNB.js";
18
24
  export {
25
+ TURN_EVENTS_MIGRATION_SQL,
19
26
  asRecord,
20
27
  asString,
21
28
  buildUserTextParts,
29
+ coalesceDeltas,
30
+ createD1TurnEventStore,
31
+ createMemoryTurnEventStore,
22
32
  encodeEvent,
23
33
  finalizeAssistantParts,
24
34
  getPartKey,
@@ -28,6 +38,8 @@ export {
28
38
  normalizePersistedPart,
29
39
  normalizeTime,
30
40
  normalizeToolEvent,
41
+ pumpBufferedTurn,
42
+ replayTurnEvents,
31
43
  resolveChatTurn,
32
44
  resolveToolId,
33
45
  resolveToolName
@@ -1,5 +1,5 @@
1
- import { b as AppToolContext, f as AppToolTaxonomy, c as AppToolHandlers, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-CTOaTNtU.js';
2
- export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-CTOaTNtU.js';
1
+ import { b as AppToolContext, f as AppToolTaxonomy, c as AppToolHandlers, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-By4B3K37.js';
2
+ export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-By4B3K37.js';
3
3
 
4
4
  /** A correctable bad-input error a tool handler throws; the HTTP layer maps it
5
5
  * to a 4xx with the code, the runtime layer to a failed tool_result. So the
@@ -102,6 +102,14 @@ declare function buildAppToolOpenAITools(taxonomy: AppToolTaxonomy): OpenAIFunct
102
102
  interface DispatchOptions {
103
103
  handlers: AppToolHandlers;
104
104
  taxonomy: AppToolTaxonomy;
105
+ /** Per-call approval policy. When provided it OVERRIDES the static
106
+ * `taxonomy.regulatedTypes` membership check, so products can gate by
107
+ * cost threshold, environment, or first-use instead of always/never.
108
+ * Fail-closed: a predicate that throws counts as "approval required". */
109
+ needsApproval?: (type: string, args: {
110
+ title: string;
111
+ description: string | null;
112
+ }, ctx: AppToolContext) => boolean | Promise<boolean>;
105
113
  /** Called at the real side-effect site for proposals (proposal_created) and
106
114
  * generated views (artifact) so a consumer's completion oracle credits
107
115
  * persisted state. Omit when produced state isn't tracked. */
@@ -8,7 +8,7 @@ import {
8
8
  handleAppToolRequest,
9
9
  readToolArgs,
10
10
  verifyCapabilityToken
11
- } from "../chunk-HZZD3ZYD.js";
11
+ } from "../chunk-OLCVUGGI.js";
12
12
  import {
13
13
  APP_TOOL_NAMES,
14
14
  ToolInputError,
@@ -17,7 +17,7 @@ import {
17
17
  dispatchAppTool,
18
18
  isAppToolName,
19
19
  outcomeStatus
20
- } from "../chunk-JANT2G2E.js";
20
+ } from "../chunk-QAQBR6KQ.js";
21
21
  export {
22
22
  APP_TOOL_NAMES,
23
23
  DEFAULT_APP_TOOL_PATHS,
@@ -41,6 +41,10 @@ interface SubmitProposalArgs {
41
41
  type: string;
42
42
  title: string;
43
43
  description?: string | null;
44
+ /** Stamped by dispatch from the approval policy (needsApproval predicate or
45
+ * taxonomy.regulatedTypes). Handlers MUST queue (never auto-execute) when
46
+ * true. Products don't set this; dispatch owns it — fail-closed. */
47
+ regulated?: boolean;
44
48
  }
45
49
  interface SubmitProposalResult {
46
50
  proposalId: string;
@@ -2,6 +2,79 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { C as CatalogModel } from '../model-catalog-BEAEVDaa.js';
4
4
 
5
+ /**
6
+ * Client-side chat-stream consumption — the NDJSON parse loop every agent
7
+ * app's chat UI hand-rolls (and breaks). Normalizes the three line shapes the
8
+ * agent-app chat routes emit:
9
+ *
10
+ * {kind:'event', event:{type:'text'|'reasoning'|'tool_call'|'usage', ...}}
11
+ * {kind:'tool_result', toolCallId, toolName, label, outcome}
12
+ * {type:'turn'|'metadata'|'error'|'turn_status', ...} (route-level)
13
+ *
14
+ * Replayed lines carry an extra `seq` — transparently ignored. Works for
15
+ * router-backed and sandbox-backed chats alike: anything producing these
16
+ * lines (live pump, queued follow, resume replay) feeds the same callbacks.
17
+ */
18
+ interface ChatStreamToolCall {
19
+ toolCallId?: string;
20
+ toolName: string;
21
+ args: Record<string, unknown>;
22
+ }
23
+ interface ChatStreamToolResult {
24
+ toolCallId?: string;
25
+ toolName?: string;
26
+ label?: string;
27
+ outcome: {
28
+ ok: boolean;
29
+ result?: unknown;
30
+ code?: string;
31
+ message?: string;
32
+ };
33
+ }
34
+ interface ChatStreamCallbacks {
35
+ onTurnId?: (turnId: string) => void;
36
+ onText?: (delta: string) => void;
37
+ onReasoning?: (delta: string) => void;
38
+ onToolCall?: (call: ChatStreamToolCall) => void;
39
+ onToolResult?: (result: ChatStreamToolResult) => void;
40
+ onUsage?: (usage: {
41
+ promptTokens: number;
42
+ completionTokens: number;
43
+ }) => void;
44
+ onMetadata?: (data: Record<string, unknown>) => void;
45
+ /** A loop-level error event (the turn failed server-side). */
46
+ onErrorEvent?: (message: string) => void;
47
+ }
48
+ interface ConsumeChatStreamResult {
49
+ turnId: string | null;
50
+ /** True when any text/reasoning/tool activity was received. */
51
+ receivedContent: boolean;
52
+ }
53
+ /** Parse one NDJSON line into the callbacks. Exposed for tests. */
54
+ declare function dispatchChatStreamLine(line: string, cb: ChatStreamCallbacks): {
55
+ turnId?: string;
56
+ receivedContent: boolean;
57
+ };
58
+ /** Drain one NDJSON body into the callbacks. Throws on transport failure
59
+ * (caller decides whether to resume). */
60
+ declare function consumeChatStream(body: ReadableStream<Uint8Array>, cb: ChatStreamCallbacks): Promise<ConsumeChatStreamResult>;
61
+ interface StreamChatOptions {
62
+ /** Start the turn (POST the chat request); must return a streaming Response. */
63
+ start: () => Promise<Response>;
64
+ /** Re-attach to a turn after a transport drop (GET the resume route). */
65
+ resume?: (turnId: string, fromSeq: number) => Promise<Response>;
66
+ callbacks: ChatStreamCallbacks;
67
+ /** Called before a resume replays from 0 so the UI can reset accumulated
68
+ * turn state (text, reasoning, tool chips). */
69
+ onResetForResume?: () => void;
70
+ }
71
+ /**
72
+ * Run one chat turn with automatic single-shot resume: if the transport drops
73
+ * mid-turn and the server announced a turnId, reset and replay the buffered
74
+ * turn. Server-side the turn keeps running either way (queued runner).
75
+ */
76
+ declare function streamChatTurn(opts: StreamChatOptions): Promise<ConsumeChatStreamResult>;
77
+
5
78
  interface ChatMessageMetrics {
6
79
  modelUsed?: string;
7
80
  promptTokens?: number;
@@ -37,11 +110,46 @@ interface EffortPickerProps {
37
110
  /** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show
38
111
  * it only when the selected model `supportsReasoning`. */
39
112
  declare function EffortPicker({ value, onChange }: EffortPickerProps): react.JSX.Element;
113
+ /** One step of a retained tool run (e.g. a sandbox command + its output). */
114
+ interface ToolRunStep {
115
+ at: string;
116
+ label: string;
117
+ detail?: string;
118
+ status?: 'ok' | 'error';
119
+ }
120
+ /** A retained tool run keyed by the parent message's toolCallId. The product
121
+ * persists these server-side (fail-closed: only ids its own loop created)
122
+ * and serves them to the drill-in panel. */
123
+ interface ToolRunRecord {
124
+ toolCallId: string;
125
+ toolName: string;
126
+ title: string;
127
+ status: 'running' | 'complete' | 'error';
128
+ steps: ToolRunStep[];
129
+ }
130
+ interface RunDrillInProps {
131
+ run: ToolRunRecord;
132
+ onClose: () => void;
133
+ }
134
+ /**
135
+ * Readonly side panel showing a retained tool run's transcript — the
136
+ * "drill into what the sandbox actually did" view. Follow-ups happen in the
137
+ * main chat, never here.
138
+ */
139
+ declare function RunDrillIn({ run, onClose }: RunDrillInProps): react.JSX.Element;
40
140
  interface ChatToolCallInfo {
41
141
  id: string;
42
142
  name: string;
43
143
  status: 'running' | 'done' | 'error';
144
+ /** The tool outcome (`{ok, result}` shape). When `result.status` is
145
+ * 'queued_for_approval' the chip renders the approval state. */
146
+ result?: unknown;
44
147
  }
148
+ /** Extract `{proposalId, status}` from a tool outcome when it is a proposal
149
+ * awaiting human approval; null otherwise. */
150
+ declare function pendingApprovalOf(call: ChatToolCallInfo): {
151
+ proposalId: string;
152
+ } | null;
45
153
  interface ChatUiMessage extends ChatMessageMetrics {
46
154
  id: string;
47
155
  role: 'user' | 'assistant' | 'system';
@@ -61,6 +169,15 @@ interface ChatMessagesProps {
61
169
  agentLabel?: string;
62
170
  /** Render the trailing "agent is thinking" row. */
63
171
  loading?: boolean;
172
+ /** Approve/Reject handlers for proposals awaiting approval. When omitted the
173
+ * chip still shows "awaiting approval" but without action buttons. */
174
+ approval?: ProposalApprovalHandlers;
175
+ /** Make tool chips clickable (e.g. open a {@link RunDrillIn} panel). */
176
+ onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void;
177
+ }
178
+ interface ProposalApprovalHandlers {
179
+ onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>;
180
+ onReject: (proposalId: string, toolCallId: string) => void | Promise<void>;
64
181
  }
65
182
  /**
66
183
  * The message thread: one centered column; user messages are right-aligned
@@ -68,6 +185,6 @@ interface ChatMessagesProps {
68
185
  * model id, tokens/sec, and cost, plus a collapsible thinking section and
69
186
  * tool-call chips.
70
187
  */
71
- declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, }: ChatMessagesProps): react.JSX.Element;
188
+ declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, }: ChatMessagesProps): react.JSX.Element;
72
189
 
73
- export { type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatToolCallInfo, type ChatUiMessage, EffortPicker, type EffortPickerProps, ModelPicker, type ModelPickerProps, formatModelCost, formatTokensPerSecond };
190
+ export { type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ConsumeChatStreamResult, EffortPicker, type EffortPickerProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, RunDrillIn, type RunDrillInProps, type StreamChatOptions, type ToolRunRecord, type ToolRunStep, consumeChatStream, dispatchChatStreamLine, formatModelCost, formatTokensPerSecond, pendingApprovalOf, streamChatTurn };