@salesforce/sfdx-agent-sdk 0.15.0 → 0.16.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
@@ -47,7 +47,8 @@ for await (const event of eventStream) {
47
47
  process.stdout.write(event.text);
48
48
  } else if (event.type === 'error') {
49
49
  console.error(event.error.message);
50
- break;
50
+ // Don't break — the SDK invariant guarantees a synthetic
51
+ // `FinishEvent('error')` follows; the loop exits naturally next tick.
51
52
  }
52
53
  }
53
54
 
@@ -152,31 +153,33 @@ keeps unparameterized call sites working.
152
153
 
153
154
  A single conversation thread.
154
155
 
155
- | Method | Signature | Description |
156
- | ------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
157
- | `getId` | `() => string` | Session/thread identifier. |
158
- | `chat` | `(message: string, options?: ChatOptions) => Promise<ChatStreamResult>` | Send a message and stream the response. |
159
- | `submitToolResult` | `(toolResult: ToolResultInfo) => Promise<ChatStreamResult>` | Return a consumer-executed tool result and resume the stream. |
160
- | `approveToolCall` | `(toolCallId: string, options?: { remember?: boolean }) => Promise<ChatStreamResult>` | Approve a pending tool call. |
161
- | `declineToolCall` | `(toolCallId: string) => Promise<ChatStreamResult>` | Decline a pending tool call. |
162
- | `getMessageHistory` | `() => Promise<Message[]>` | Retrieve all messages in chronological order. |
163
- | `clearHistory` | `() => Promise<void>` | Delete all messages. |
164
- | `getContextUsage` | `() => ContextUsage` | Snapshot of how much of the model's context window the most recent turn used. |
165
- | `addContext` | `(message: string \| Message[]) => Promise<void>` | Inject context without triggering an LLM response. |
166
- | `subscribe` | `(callback: (event: ChatEvent) => void) => void` | Register a real-time event listener. |
167
- | `unsubscribe` | `(callback: (event: ChatEvent) => void) => void` | Remove a listener. |
168
- | `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry scoped to this session. |
169
- | `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to logs scoped to this session. |
170
- | `dispose` | `() => void` | Release session-level event resources. Idempotent. |
156
+ | Method | Signature | Description |
157
+ | ------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
158
+ | `getId` | `() => string` | Session/thread identifier. |
159
+ | `chat` | `(message: string, options?: ChatOptions) => Promise<ChatStreamResult>` | Send a message and stream the response. The returned `eventStream` is the single iterator for the entire chat turn. |
160
+ | `submitToolResult` | `(toolResult: ToolResultInfo) => Promise<void>` | Return a consumer-executed tool result. Control message on the existing turn — post-resume events flow on the same stream. |
161
+ | `approveToolCall` | `(toolCallId: string, options?: { remember?: boolean }) => Promise<void>` | Approve a pending tool call. Control message on the existing turn — post-resume events flow on the same stream. |
162
+ | `declineToolCall` | `(toolCallId: string) => Promise<void>` | Decline a pending tool call. Control message on the existing turn — post-resume events flow on the same stream. |
163
+ | `getMessageHistory` | `() => Promise<Message[]>` | Retrieve all messages in chronological order. |
164
+ | `clearHistory` | `() => Promise<void>` | Delete all messages. |
165
+ | `getContextUsage` | `() => ContextUsage` | Snapshot of how much of the model's context window the most recent turn used. |
166
+ | `addContext` | `(message: string \| Message[]) => Promise<void>` | Inject context without triggering an LLM response. |
167
+ | `subscribe` | `(callback: (event: ChatEvent) => void) => void` | Register a real-time event listener. |
168
+ | `unsubscribe` | `(callback: (event: ChatEvent) => void) => void` | Remove a listener. |
169
+ | `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry scoped to this session. |
170
+ | `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to logs scoped to this session. |
171
+ | `dispose` | `() => void` | Release session-level event resources. Idempotent. |
171
172
 
172
173
  ### `ChatStreamResult`
173
174
 
174
- Returned by `chat()`, `submitToolResult()`, `approveToolCall()`, and `declineToolCall()`.
175
+ Returned by `chat()`. The single `eventStream` covers the entire chat turn — including post-resume events from
176
+ `submitToolResult` / `approveToolCall` / `declineToolCall`. Settle methods return `Promise<void>`; the consumer keeps
177
+ iterating the same `eventStream` until it sees a terminal `finish` event.
175
178
 
176
- | Property | Type | Description |
177
- | ------------- | --------------------------- | --------------------------------------- |
178
- | `eventStream` | `AsyncGenerator<ChatEvent>` | Full lifecycle event stream. |
179
- | `textStream` | `AsyncGenerator<string>` | Convenience stream of text-only tokens. |
179
+ | Property | Type | Description |
180
+ | ------------- | --------------------------- | ---------------------------------------------------------------------------- |
181
+ | `eventStream` | `AsyncGenerator<ChatEvent>` | Full lifecycle event stream for the turn (one stream per `chat()` call). |
182
+ | `textStream` | `AsyncGenerator<string>` | Convenience stream of text-only tokens, derived from the same `eventStream`. |
180
183
 
181
184
  ### `ChatEvent`
182
185
 
@@ -560,17 +563,17 @@ try {
560
563
 
561
564
  #### Streaming method failures
562
565
 
563
- The four streaming methods on `ChatSession` `chat()`, `submitToolResult()`, `approveToolCall()`, and
564
- `declineToolCall()` share a single failure contract. Subscribers registered via `subscribe()` are guaranteed to
566
+ `chat()` opens a turn; `submitToolResult()` / `approveToolCall()` / `declineToolCall()` are control messages on the
567
+ existing turn. All four share a single failure contract. Subscribers registered via `subscribe()` are guaranteed to
565
568
  receive a terminal event for every turn:
566
569
 
567
- - **Pre-stream failure** (the harness rejects before producing a stream): subscribers receive an `ErrorEvent` followed
568
- by a `FinishEvent(finishReason: 'error')`, and the returned promise then rejects with the original error. Wrap the
569
- call in `try/catch` if you need to handle this in the caller.
570
- - **In-stream failure** (the stream yields an `error` event or the underlying generator throws): the `eventStream`
571
- yields the `ErrorEvent` (synthesizing one from a thrown exception if needed) and, if no `FinishEvent` was already
572
- emitted, appends a synthetic `FinishEvent(finishReason: 'error')` at the end. The returned promise resolves normally;
573
- iterate the stream to observe the failure.
570
+ - **Pre-stream failure** (the harness rejects before producing a stream, or a settle call rejects before it routes the
571
+ decision): subscribers receive an `ErrorEvent` followed by a `FinishEvent(finishReason: 'error')`, and the returned
572
+ promise then rejects with the original error. Wrap the call in `try/catch` if you need to handle this in the caller.
573
+ - **In-stream failure** (the active `eventStream` yields an `error` event or the underlying generator throws): the
574
+ stream yields the `ErrorEvent` (synthesizing one from a thrown exception if needed) and, if no `FinishEvent` was
575
+ already emitted, appends a synthetic `FinishEvent(finishReason: 'error')` at the end. The originating `chat()` promise
576
+ resolves normally; iterate the stream to observe the failure.
574
577
  - **Calling a disposed session** throws `AgentSDKError('DISPOSED')` synchronously without notifying subscribers.
575
578
 
576
579
  ### MCP Server Configuration
@@ -613,99 +616,158 @@ reparsing namespaced tool names. `annotations` is `undefined` when the source di
613
616
  `requireToolApproval` accepts a `boolean` (`true` is shorthand for `'serial'`) or one of the mode strings exposed via
614
617
  the `ToolApprovalMode` type alias (`'serial' | 'batch'`):
615
618
 
616
- - **`'serial'`** (the safe default) — each stream surfaces one `tool-approval-request` at a time. The next request, if
617
- any, appears on the continuation stream returned by `approveToolCall` / `declineToolCall`. Works with both consumer
618
- iterator patterns below.
619
- - **`'batch'`** when the model emits parallel `tool_use` blocks, all approval-requests surface on the same stream so
620
- the consumer can render a batch-approval card. **Pattern A iterators only** — a `break`-on-first-approval loop will
621
- miss the subsequent approvals and hang the chat. Opt into `'batch'` only after the consumer iterator collects all
622
- approvals before settling.
619
+ - **`'serial'`** (the safe default) — `tool-approval-request` events surface one at a time. The next request (if any)
620
+ arrives later on the same `eventStream` after the consumer settles the current one.
621
+ - **`'batch'`** — when the model emits parallel `tool_use` blocks, all approval-requests for that batch surface on the
622
+ same `eventStream` together, so the consumer can render a batch-approval card. The consumer must gather all approvals
623
+ before settling — a `break`-on-first-approval loop will hang the turn because Mastra/Claude won't continue emitting
624
+ tool-results until the consumer has settled the entire batch.
623
625
 
624
626
  The SDK also exports `resolveToolApprovalMode(value)` — the canonical
625
627
  `boolean | ToolApprovalMode | undefined → ToolApprovalMode | undefined` normalizer harness implementations should use to
626
628
  dispatch (`undefined` / `false` → `undefined`, `true` → `'serial'`, strings pass through). Reject unknown strings via
627
629
  `throw` instead of reimplementing the boolean-vs-string branch — the helper's defensive throw catches `as any` consumers
628
- passing invalid values that would otherwise silently degrade to "approval gating on but no broker allocated."
630
+ passing invalid values that would otherwise silently degrade to "approval gating on but no coordinator allocated."
631
+
632
+ #### Single stream per turn
629
633
 
630
- > **Glossary:** _Pattern A_ = "collect-all-then-settle" (iterate until natural park, gather all approval-requests, then
631
- > call `approveToolCall`/`declineToolCall`). _Pattern B_ = "return-on-first-approval" (settle the first approval
632
- > mid-iteration, then re-iterate the continuation stream). `'serial'` works with either; `'batch'` requires Pattern A.
634
+ Issue [#529](https://github.com/forcedotcom/agentic-dx/issues/529) settled the harness contract: **one `eventStream` per
635
+ chat turn.** Settle calls (`approveToolCall` / `declineToolCall` / `submitToolResult`) return `Promise<void>` and behave
636
+ as control messages on the existing turn; the post-resume events (`tool-result`, follow-up `text-delta`, `step-finish`,
637
+ terminal `finish`) flow through the **same** `eventStream` the `chat()` call returned. The consumer should iterate that
638
+ one stream until it sees a terminal `finish` event, calling `approveToolCall` / `declineToolCall` / `submitToolResult`
639
+ in-line as approval-requests / consumer-tool-calls arrive.
633
640
 
634
- #### Pattern B return on first approval (works with `'serial'`)
641
+ #### Pattern: settle in-line, continue iterating
642
+
643
+ The same shape works for `'serial'` and `'batch'` mode:
635
644
 
636
645
  ```typescript
637
646
  const { eventStream } = await session.chat('Run the deployment', {
638
- requireToolApproval: true, // or 'serial'
647
+ requireToolApproval: true, // or 'serial' / 'batch'
639
648
  });
640
649
 
641
650
  for await (const event of eventStream) {
642
651
  if (event.type === 'tool-approval-request') {
643
652
  const approved = await promptUser(event);
644
- const continuation = approved
645
- ? await session.approveToolCall(event.toolCall.toolCallId)
646
- : await session.declineToolCall(event.toolCall.toolCallId);
647
- for await (const e of continuation.eventStream) {
648
- // process continuation
653
+ if (approved) {
654
+ await session.approveToolCall(event.toolCall.toolCallId);
655
+ } else {
656
+ await session.declineToolCall(event.toolCall.toolCallId);
649
657
  }
650
- break; // safe: serial mode surfaces at most one approval per stream
658
+ // Do NOT break keep iterating; tool-result and follow-up text arrive on the same stream.
659
+ } else if (event.type === 'tool-call' && event.serverName === undefined && isConsumerTool(event.toolName)) {
660
+ // Consumer-executed tool: run locally, return the result via submitToolResult.
661
+ const result = await runLocally(event.args);
662
+ await session.submitToolResult({
663
+ toolCallId: event.toolCallId,
664
+ toolName: event.toolName,
665
+ result,
666
+ });
667
+ } else if (event.type === 'text-delta') {
668
+ process.stdout.write(event.text);
669
+ } else if (event.type === 'finish') {
670
+ // Don't break on `'error'` directly — the SDK invariant guarantees an
671
+ // `error` event is followed by a synthetic `FinishEvent('error')` so
672
+ // the loop exits naturally on the next iteration. Breaking on `error`
673
+ // would finalize the iterator before the synthetic `finish` lands and
674
+ // bypass any per-turn cleanup the harness wires onto the source's
675
+ // close/return path.
676
+ break;
651
677
  }
652
678
  }
653
679
  ```
654
680
 
655
- #### Pattern A collect all approvals, then settle (required for `'batch'`)
681
+ #### Pattern variant: batch-collect approvals before settling
682
+
683
+ Same single-stream loop, just gather the batch before deciding (useful for "approve these N tools?" UI cards under
684
+ `requireToolApproval: 'batch'`):
656
685
 
657
686
  ```typescript
658
687
  const { eventStream } = await session.chat('Run the deployment', {
659
688
  requireToolApproval: 'batch',
660
689
  });
661
690
 
662
- const requests: ToolApprovalRequestEvent[] = [];
691
+ const pendingBatch: ToolApprovalRequestEvent[] = [];
692
+
663
693
  for await (const event of eventStream) {
664
694
  if (event.type === 'tool-approval-request') {
665
- requests.push(event); // do NOT break — collect the whole parallel batch
695
+ pendingBatch.push(event);
696
+ // Heuristic: settle the batch on a quiet window OR after the expected count.
697
+ // Don't break — the harness needs us to settle so it can continue.
698
+ continue;
666
699
  }
667
- if (event.type === 'finish' || event.type === 'error') break;
668
- }
669
-
670
- // Render a batch-approval card; settle each decision.
671
- const decisions = await promptUserForBatch(requests);
672
- let continuation: ChatStreamResult | undefined;
673
- for (const event of requests) {
674
- continuation = decisions.get(event.toolCall.toolCallId)
675
- ? await session.approveToolCall(event.toolCall.toolCallId)
676
- : await session.declineToolCall(event.toolCall.toolCallId);
677
- }
678
- if (continuation) {
679
- for await (const e of continuation.eventStream) {
680
- // process the model's follow-up turn
700
+ if (event.type === 'tool-call') {
701
+ // The harness emits `tool-approval-request` BEFORE `tool-call` in batch mode.
702
+ // Once we see a tool-call, the batch for this assistant message is complete.
703
+ if (pendingBatch.length > 0) {
704
+ const decisions = await promptUserForBatch(pendingBatch);
705
+ await Promise.all(
706
+ pendingBatch.map((req) =>
707
+ decisions.get(req.toolCall.toolCallId)
708
+ ? session.approveToolCall(req.toolCall.toolCallId)
709
+ : session.declineToolCall(req.toolCall.toolCallId),
710
+ ),
711
+ );
712
+ pendingBatch.length = 0;
713
+ }
714
+ } else if (event.type === 'text-delta') {
715
+ process.stdout.write(event.text);
716
+ } else if (event.type === 'finish') {
717
+ // Don't break on `'error'` directly — the SDK invariant guarantees an
718
+ // `error` event is followed by a synthetic `FinishEvent('error')` so
719
+ // the loop exits naturally on the next iteration. Breaking on `error`
720
+ // would finalize the iterator before the synthetic `finish` lands and
721
+ // bypass any per-turn cleanup the harness wires onto the source's
722
+ // close/return path.
723
+ break;
681
724
  }
682
725
  }
683
726
  ```
684
727
 
685
- #### `textStream` and `'batch'` mode
686
-
687
- `ChatStreamResult.textStream` is empty on the initial stream when `requireToolApproval: 'batch'` surfaces approval
688
- requests the model emitted `tool_use` blocks, not text. Read text deltas from the continuation stream returned by
689
- `approveToolCall` / `declineToolCall` (the model's follow-up turn after tool execution).
728
+ > **AsyncIterator gotcha:** the `for await` loop is the supported way to drain `eventStream`. If you instead drive the
729
+ > iterator manually (`iter.next()`), call **at most one** `next()` at a time and `await` the result before the next
730
+ > call. Calling `iter.next()` twice without awaiting the first orphans the first request — JS settles `.next()` calls
731
+ > FIFO, so the next yielded value satisfies the orphaned request and is invisible to the caller. Stick to `for await`
732
+ > unless you have a specific reason not to.
690
733
 
691
734
  ### Consumer-Executed Tools
692
735
 
736
+ Tools declared via `AgentConfig.tools` (no `execute` function) run on the consumer side: the model emits a `tool-call`
737
+ event, the consumer runs the tool locally, then calls `session.submitToolResult(...)` to feed the result back.
738
+ `submitToolResult` returns `Promise<void>`; the model's follow-up turn (and any subsequent `tool-call`s) flows through
739
+ the same `eventStream`:
740
+
693
741
  ```typescript
742
+ const { eventStream } = await session.chat('Look up the customer record for tenant T-42.');
743
+
694
744
  for await (const event of eventStream) {
695
- if (event.type === 'tool-call' && event.toolName === 'my-tool') {
745
+ if (event.type === 'tool-call' && event.toolName === 'lookup_customer') {
696
746
  const result = await runLocally(event.args);
697
- const continuation = await session.submitToolResult({
747
+ await session.submitToolResult({
698
748
  toolCallId: event.toolCallId,
699
749
  toolName: event.toolName,
700
750
  result,
701
751
  });
702
- for await (const e of continuation.eventStream) {
703
- // process continuation
704
- }
752
+ // Do NOT break — the model's follow-up turn (text, more tool-calls, finish) arrives on the same stream.
753
+ } else if (event.type === 'text-delta') {
754
+ process.stdout.write(event.text);
755
+ } else if (event.type === 'finish') {
756
+ // Don't break on `'error'` directly — the SDK invariant guarantees an
757
+ // `error` event is followed by a synthetic `FinishEvent('error')` so
758
+ // the loop exits naturally on the next iteration. Breaking on `error`
759
+ // would finalize the iterator before the synthetic `finish` lands and
760
+ // bypass any per-turn cleanup the harness wires onto the source's
761
+ // close/return path.
762
+ break;
705
763
  }
706
764
  }
707
765
  ```
708
766
 
767
+ When `requireToolApproval: true` is also set, consumer-executed tools bypass the approval gate by construction (per
768
+ `StreamOptions.requireToolApproval` JSDoc). They surface as a normal `tool-call` event without a preceding
769
+ `tool-approval-request`. Built-in / MCP tools still gate normally.
770
+
709
771
  ### Connectivity Resolution
710
772
 
711
773
  #### `ResolvedConnectivity`
@@ -843,15 +905,33 @@ The SDK ships no harness implementation. Consumers pick one by passing a `Harnes
843
905
  [`@salesforce/sfdx-agent-harness-mastra`](../sfdx-agent-harness-mastra); additional harnesses can ship as independent
844
906
  npm packages that depend on this SDK as a `peerDependency`.
845
907
 
846
- Harness authors implement two interfaces and can compose one helper class, all exported from this package:
847
-
848
- | Export | Role |
849
- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
850
- | `HarnessFactory<H>` | Construct a harness of type `H` bound to a storage root. Declares `harnessId` and `protocolVersion`. Default `H = AgentHarness`. |
851
- | `AgentHarness` | Runtime contract: agent / thread / stream / tool / message lifecycle. Declares its own `harnessId` and `protocolVersion`. |
852
- | `SUPPORTED_PROTOCOL_VERSIONS` | Readonly list of harness protocol versions this SDK accepts. `createAgentManager` checks both the factory and the constructed harness. |
853
- | `HarnessBusOwner` | Composition helper owning telemetry + log buses with `dispose()` semantics. Reuse it instead of reimplementing bus plumbing. |
854
- | `lowerStreamInput` | Validates a `MessagePart[]` and lowers each input part to your runtime's content-block shape. Use it in `stream()` so multimodal caps and `MULTIMODAL_NOT_SUPPORTED` / `INVALID_MESSAGE_CONTENT` semantics match every other harness. |
908
+ ### Two import surfaces
909
+
910
+ This package publishes two ESM entry points:
911
+
912
+ - **`@salesforce/sfdx-agent-sdk`** consumer surface. Everything an application calling `createAgentManager` needs:
913
+ `Agent`, `ChatSession`, `ChatEvent`, `MessagePart`, `AgentConfig`, `AgentSDKError`, etc. The harness contract
914
+ interfaces (`AgentHarness`, `HarnessFactory`, `WithAgentConfig`, `ConfigOf`) also appear here as **type-only**
915
+ references so consumer code can write `AgentManager<H extends AgentHarness>` without reaching into the
916
+ harness-implementation surface.
917
+ - **`@salesforce/sfdx-agent-sdk/harness`** — harness-implementation surface. Harness authors import the contract types
918
+ and the helpers from here. Consumer applications cannot reach these symbols from the bare specifier — the bundler / TS
919
+ resolver errors at the import site, which is the right outcome for any consumer who reaches for a primitive they don't
920
+ need.
921
+
922
+ > **Build-tool requirement:** the `/harness` subpath is resolved via the `package.json` `exports` field, which requires
923
+ > `moduleResolution: "node16" | "nodenext" | "bundler"` in `tsconfig.json`. Legacy `"node"` resolution silently won't
924
+ > see the subpath. Modern bundlers (Vite, esbuild, Webpack 5+, tsup, Rollup with `@rollup/plugin-node-resolve` v15+)
925
+ > resolve it natively. This is a harness-author concern only; consumer applications never touch the subpath.
926
+
927
+ | Export | Surface | Role |
928
+ | ----------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
929
+ | `HarnessFactory<H>` | Type only on bare; value+type on `/harness` | Construct a harness of type `H` bound to a storage root. Declares `harnessId` and `protocolVersion`. Default `H = AgentHarness`. |
930
+ | `AgentHarness` | Type only on bare; type on `/harness` | Runtime contract: agent / thread / stream / tool / message lifecycle. Declares its own `harnessId` and `protocolVersion`. |
931
+ | `SUPPORTED_PROTOCOL_VERSIONS` | `/harness` only | Readonly list of harness protocol versions this SDK accepts. `createAgentManager` checks both the factory and the constructed harness. |
932
+ | `HarnessBusOwner` | `/harness` only | Composition helper owning telemetry + log buses with `dispose()` semantics. Reuse it instead of reimplementing bus plumbing. |
933
+ | `lowerStreamInput` | `/harness` only | Validates a `MessagePart[]` and lowers each input part to your runtime's content-block shape. Use it in `stream()` so multimodal caps and `MULTIMODAL_NOT_SUPPORTED` / `INVALID_MESSAGE_CONTENT` semantics match every other harness. |
934
+ | `GenSink<T>` | `/harness` only | Buffered async-generator wrapper for routing `ChatEvent`s to a consumer's `ChatStreamResult.eventStream`. Single-iteration: calling `generator()` twice throws — sinks have one waiter slot and one buffer, two iterators race on both. |
855
935
 
856
936
  Minimal skeleton:
857
937
 
@@ -861,7 +941,7 @@ import {
861
941
  type HarnessFactory,
862
942
  HarnessBusOwner,
863
943
  SUPPORTED_PROTOCOL_VERSIONS,
864
- } from '@salesforce/sfdx-agent-sdk';
944
+ } from '@salesforce/sfdx-agent-sdk/harness';
865
945
 
866
946
  class MyHarness implements AgentHarness {
867
947
  readonly harnessId = 'my-harness';
@@ -62,11 +62,17 @@ export interface ChatSession {
62
62
  *
63
63
  * "Client-side tool" means a tool you declared in {@link AgentConfig.tools}
64
64
  * without an `execute` function — the SDK registers its name + schema with the
65
- * model but does not run it. When the model calls one, the chat eventStream
66
- * emits a `tool-call` event and ends with `finishReason: 'tool-calls'`. Your
67
- * application runs the tool however it likes (HTTP call, DB query, UI prompt,
68
- * etc.) and calls this method with the result; the agent loop resumes and
69
- * produces its next turn on the returned `ChatStreamResult.eventStream`.
65
+ * model but does not run it. When the model calls one, the chat `eventStream`
66
+ * emits a `tool-call` event. Your application runs the tool however it likes
67
+ * (HTTP call, DB query, UI prompt, etc.) and calls this method with the result;
68
+ * the agent loop resumes and the post-resume events (`tool-result`, model
69
+ * follow-up text, terminal `finish`) arrive on the **same** `eventStream`
70
+ * the original {@link chat} call returned. The consumer keeps iterating it.
71
+ *
72
+ * Returns `Promise<void>` once the harness has accepted the result. The
73
+ * promise rejects on pre-stream failure (the `chat()`-returned subscribers
74
+ * still observe `ErrorEvent` + `FinishEvent` before the rejection so the
75
+ * subscribe-side contract holds).
70
76
  *
71
77
  * Use this method ONLY for client-side tools. Tools provided via
72
78
  * {@link AgentConfig.mcpServers} are executed by the harness — their results
@@ -74,23 +80,22 @@ export interface ChatSession {
74
80
  * Human-in-the-loop approval of harness-executed tools uses
75
81
  * {@link approveToolCall} / {@link declineToolCall}, not this method.
76
82
  *
77
- * On pre-stream failure, subscribers are notified with `ErrorEvent` + `FinishEvent` before
78
- * the returned promise rejects. See the interface-level "Failure handling" notes for details.
79
- *
80
83
  * @param toolResult - The completed tool execution result. `toolCallId` and
81
84
  * `toolName` MUST match the values from the originating `tool-call` event.
82
85
  */
83
- submitToolResult(toolResult: ToolResultInfo): Promise<ChatStreamResult>;
86
+ submitToolResult(toolResult: ToolResultInfo): Promise<void>;
84
87
  /**
85
88
  * Approve a pending tool call, allowing the harness to execute it.
86
89
  * Called after receiving a `tool-approval-request` event from the stream.
87
90
  *
88
- * Returns a `ChatStreamResult` containing the continuation stream the harness
89
- * executes the approved tool, generates the model's follow-up response, and
90
- * streams both the text and events back to the caller.
91
+ * Returns `Promise<void>` once the harness has accepted the approval. The
92
+ * harness then executes the tool and emits the resulting events
93
+ * (`tool-result`, model follow-up text, terminal `finish`) on the **same**
94
+ * `eventStream` the original {@link chat} call returned. The consumer keeps
95
+ * iterating it.
91
96
  *
92
- * On pre-stream failure, subscribers are notified with `ErrorEvent` + `FinishEvent` before
93
- * the returned promise rejects. See the interface-level "Failure handling" notes for details.
97
+ * The promise rejects on pre-stream failure; subscribers still observe
98
+ * `ErrorEvent` + `FinishEvent` on the chat stream before the rejection.
94
99
  *
95
100
  * @param toolCallId - ID of the pending tool call to approve.
96
101
  * @param options - Optional approval metadata.
@@ -101,21 +106,20 @@ export interface ChatSession {
101
106
  */
102
107
  approveToolCall(toolCallId: string, options?: {
103
108
  remember?: boolean;
104
- }): Promise<ChatStreamResult>;
109
+ }): Promise<void>;
105
110
  /**
106
111
  * Decline a pending tool call. The stream resumes with the model
107
- * acknowledging the decline and potentially suggesting alternatives.
112
+ * acknowledging the decline and potentially suggesting alternatives
113
+ * those events arrive on the **same** `eventStream` the original
114
+ * {@link chat} call returned.
108
115
  *
109
- * Returns a `ChatStreamResult` containing the continuation stream the harness
110
- * cancels the pending tool call, generates the model's acknowledgement response,
111
- * and streams both the text and events back to the caller.
112
- *
113
- * On pre-stream failure, subscribers are notified with `ErrorEvent` + `FinishEvent` before
114
- * the returned promise rejects. See the interface-level "Failure handling" notes for details.
116
+ * Returns `Promise<void>` once the harness has accepted the decline. The
117
+ * promise rejects on pre-stream failure; subscribers still observe
118
+ * `ErrorEvent` + `FinishEvent` on the chat stream before the rejection.
115
119
  *
116
120
  * @param toolCallId - ID of the pending tool call to decline.
117
121
  */
118
- declineToolCall(toolCallId: string): Promise<ChatStreamResult>;
122
+ declineToolCall(toolCallId: string): Promise<void>;
119
123
  /**
120
124
  * Retrieve message history for this session.
121
125
  *
@@ -246,7 +250,7 @@ export declare class DefaultChatSession implements ChatSession {
246
250
  * - MUST notify listeners with `ErrorEvent` + `FinishEvent` and re-throw if the harness throws
247
251
  * before returning a stream result.
248
252
  */
249
- submitToolResult(toolResult: ToolResultInfo): Promise<ChatStreamResult>;
253
+ submitToolResult(toolResult: ToolResultInfo): Promise<void>;
250
254
  /**
251
255
  * @requirements
252
256
  * - MUST yield each event from the provided `stream`.
@@ -290,7 +294,7 @@ export declare class DefaultChatSession implements ChatSession {
290
294
  */
291
295
  approveToolCall(toolCallId: string, _options?: {
292
296
  remember?: boolean;
293
- }): Promise<ChatStreamResult>;
297
+ }): Promise<void>;
294
298
  /**
295
299
  * @requirements
296
300
  * - MUST delegate to `this.harness.declineToolCall()`, passing `this.agentId`, `this.threadId`, and `toolCallId`.
@@ -302,7 +306,7 @@ export declare class DefaultChatSession implements ChatSession {
302
306
  * - MUST notify listeners with `ErrorEvent` + `FinishEvent` and re-throw if the harness throws
303
307
  * before returning a stream result.
304
308
  */
305
- declineToolCall(toolCallId: string): Promise<ChatStreamResult>;
309
+ declineToolCall(toolCallId: string): Promise<void>;
306
310
  /**
307
311
  * @requirements
308
312
  * - MUST delegate to `this.harness.getMessages()`, passing `this.agentId` and `this.threadId`.
@@ -395,5 +399,18 @@ export declare class DefaultChatSession implements ChatSession {
395
399
  * measures real elapsed time even for pre-stream rejections.
396
400
  */
397
401
  private notifyPreStreamError;
402
+ /**
403
+ * issue #529 contract change: a settle call (`approveToolCall` /
404
+ * `declineToolCall` / `submitToolResult`) rejected. The settle's
405
+ * Promise is the consumer's primary failure surface, but subscribers
406
+ * registered via {@link ChatSession.subscribe} also expect to observe
407
+ * `error + finish` events so a UI bound to the chat stream can
408
+ * render the failure. Emit those without firing
409
+ * `chat-stream-error` telemetry — chat-stream-* telemetry is owned
410
+ * by the chat() lifecycle, not by settle calls (issue #529: one
411
+ * chat-stream-started/completed/error pair per turn, not per
412
+ * settle).
413
+ */
414
+ private notifySettleRejection;
398
415
  private assertNotDisposed;
399
416
  }
@@ -109,16 +109,16 @@ export class DefaultChatSession {
109
109
  */
110
110
  async submitToolResult(toolResult) {
111
111
  this.assertNotDisposed();
112
- const startedAt = this.emitChatStreamStarted('submit-tool-result');
112
+ // issue #529 contract change: settle calls are control messages on the
113
+ // existing chat() turn's stream — they don't open a new stream and
114
+ // they don't emit chat-stream-started/completed. The post-resume
115
+ // events flow through the harness's existing turn sink, which the
116
+ // consumer's chat()-returned eventStream is already iterating.
113
117
  try {
114
- const result = await this.harness.submitToolResult(this.agentId, this.threadId, toolResult);
115
- return {
116
- textStream: result.textStream,
117
- eventStream: this.wrapEventStream(result.eventStream, startedAt),
118
- };
118
+ await this.harness.submitToolResult(this.agentId, this.threadId, toolResult);
119
119
  }
120
120
  catch (err) {
121
- this.notifyPreStreamError(err, startedAt);
121
+ this.notifySettleRejection(err);
122
122
  throw err;
123
123
  }
124
124
  }
@@ -246,19 +246,17 @@ export class DefaultChatSession {
246
246
  */
247
247
  async approveToolCall(toolCallId, _options) {
248
248
  this.assertNotDisposed();
249
- const startedAt = this.emitChatStreamStarted('approve-tool-call');
249
+ // issue #529 contract change: see `submitToolResult` for the rationale.
250
+ // Settle is a control message on the existing turn; events flow on
251
+ // the chat()-returned stream.
250
252
  try {
251
- const result = await this.harness.approveToolCall(this.agentId, this.threadId, toolCallId);
252
- this.emitToolApprovalResolved(toolCallId, true);
253
- return {
254
- textStream: result.textStream,
255
- eventStream: this.wrapEventStream(result.eventStream, startedAt),
256
- };
253
+ await this.harness.approveToolCall(this.agentId, this.threadId, toolCallId);
257
254
  }
258
255
  catch (err) {
259
- this.notifyPreStreamError(err, startedAt);
256
+ this.notifySettleRejection(err);
260
257
  throw err;
261
258
  }
259
+ this.emitToolApprovalResolved(toolCallId, true);
262
260
  }
263
261
  /**
264
262
  * @requirements
@@ -273,19 +271,15 @@ export class DefaultChatSession {
273
271
  */
274
272
  async declineToolCall(toolCallId) {
275
273
  this.assertNotDisposed();
276
- const startedAt = this.emitChatStreamStarted('decline-tool-call');
274
+ // issue #529 contract change: see `submitToolResult` for the rationale.
277
275
  try {
278
- const result = await this.harness.declineToolCall(this.agentId, this.threadId, toolCallId);
279
- this.emitToolApprovalResolved(toolCallId, false);
280
- return {
281
- textStream: result.textStream,
282
- eventStream: this.wrapEventStream(result.eventStream, startedAt),
283
- };
276
+ await this.harness.declineToolCall(this.agentId, this.threadId, toolCallId);
284
277
  }
285
278
  catch (err) {
286
- this.notifyPreStreamError(err, startedAt);
279
+ this.notifySettleRejection(err);
287
280
  throw err;
288
281
  }
282
+ this.emitToolApprovalResolved(toolCallId, false);
289
283
  }
290
284
  /**
291
285
  * @requirements
@@ -527,6 +521,23 @@ export class DefaultChatSession {
527
521
  error,
528
522
  });
529
523
  }
524
+ /**
525
+ * issue #529 contract change: a settle call (`approveToolCall` /
526
+ * `declineToolCall` / `submitToolResult`) rejected. The settle's
527
+ * Promise is the consumer's primary failure surface, but subscribers
528
+ * registered via {@link ChatSession.subscribe} also expect to observe
529
+ * `error + finish` events so a UI bound to the chat stream can
530
+ * render the failure. Emit those without firing
531
+ * `chat-stream-error` telemetry — chat-stream-* telemetry is owned
532
+ * by the chat() lifecycle, not by settle calls (issue #529: one
533
+ * chat-stream-started/completed/error pair per turn, not per
534
+ * settle).
535
+ */
536
+ notifySettleRejection(err) {
537
+ const error = err instanceof Error ? err : new Error(String(err));
538
+ this.chatEventBus.emit({ type: 'error', error });
539
+ this.chatEventBus.emit({ type: 'finish', finishReason: 'error' });
540
+ }
530
541
  assertNotDisposed() {
531
542
  if (this.disposed) {
532
543
  throw new AgentSDKError('ChatSession has been disposed.', AgentSDKErrorType.DISPOSED);
@@ -238,49 +238,56 @@ export interface AgentHarness {
238
238
  stream(agentId: string, threadId: string, message: string | MessagePart[], options?: StreamOptions): Promise<ChatStreamResult>;
239
239
  /**
240
240
  * Feed the result of a **consumer-executed (client-side) tool** back into the
241
- * conversation and resume stream generation. Implements the consumer-facing
241
+ * conversation. Implements the consumer-facing
242
242
  * {@link ChatSession.submitToolResult} contract: see that JSDoc for the
243
243
  * full consumer-level semantics.
244
244
  *
245
245
  * Called only for tools declared in {@link HarnessAgentConfig.tools} (no
246
246
  * `execute` function). Tools provided via `mcpServers` are executed by the
247
- * harness directly and never reach this method. The harness resumes the
248
- * suspended agentic loop from the most recent `stream()` call on the given
249
- * thread, feeds `toolResult` into the loop, and returns a continuation
250
- * stream the consumer iterates for the model's next turn.
247
+ * harness directly and never reach this method.
248
+ *
249
+ * Returns `Promise<void>` once the harness has accepted the tool result
250
+ * (the parked bridge handler resolved). Post-resume events
251
+ * (`tool-result`, model follow-up text, terminal `finish`) flow through
252
+ * the SAME `ChatStreamResult.eventStream` returned by the most recent
253
+ * {@link stream} call on the thread — the consumer is still iterating
254
+ * that stream and sees the events arrive.
251
255
  *
252
256
  * @param agentId - ID of the agent.
253
257
  * @param threadId - ID of the conversation thread.
254
258
  * @param toolResult - The completed tool execution result; `toolCallId`
255
259
  * matches the id from the originating `tool-call` event.
256
260
  */
257
- submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<ChatStreamResult>;
261
+ submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<void>;
258
262
  /**
259
263
  * Approve a pending tool call, allowing the harness to execute it.
260
264
  * Called after receiving a `tool-approval-request` event.
261
265
  *
262
- * Returns a `ChatStreamResult` containing the continuation stream the harness
263
- * executes the approved tool, generates the model's follow-up response, and
264
- * streams both the text and events back to the caller.
266
+ * Returns `Promise<void>` once the harness has accepted the approval
267
+ * (the parked PreToolUse hook resolved on Claude; the resume call was
268
+ * dispatched on Mastra). Post-resume events flow through the SAME
269
+ * `ChatStreamResult.eventStream` returned by the most recent
270
+ * {@link stream} call on the thread.
265
271
  *
266
272
  * @param agentId - ID of the agent.
267
273
  * @param threadId - ID of the conversation thread (needed for harness-internal state lookup).
268
274
  * @param toolCallId - ID of the tool call to approve.
269
275
  */
270
- approveToolCall(agentId: string, threadId: string, toolCallId: string): Promise<ChatStreamResult>;
276
+ approveToolCall(agentId: string, threadId: string, toolCallId: string): Promise<void>;
271
277
  /**
272
- * Decline a pending tool call. The harness resumes the stream with the
273
- * model acknowledging the decline and potentially suggesting alternatives.
278
+ * Decline a pending tool call. The harness resumes the agentic loop with
279
+ * the model acknowledging the decline and potentially suggesting
280
+ * alternatives.
274
281
  *
275
- * Returns a `ChatStreamResult` containing the continuation stream the harness
276
- * cancels the pending tool call, generates the model's acknowledgement response,
277
- * and streams both the text and events back to the caller.
282
+ * Returns `Promise<void>` once the harness has accepted the decline.
283
+ * Post-resume events flow through the SAME `ChatStreamResult.eventStream`
284
+ * returned by the most recent {@link stream} call on the thread.
278
285
  *
279
286
  * @param agentId - ID of the agent.
280
287
  * @param threadId - ID of the conversation thread.
281
288
  * @param toolCallId - ID of the tool call to decline.
282
289
  */
283
- declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<ChatStreamResult>;
290
+ declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<void>;
284
291
  /**
285
292
  * Retrieve message history for a thread.
286
293
  *
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Buffered async-generator wrapper used by `AgentHarness` implementations to
3
+ * deliver `ChatEvent`s to a consumer's `ChatStreamResult.eventStream`.
4
+ *
5
+ * The buffer holds events pushed before the consumer starts iterating, so the
6
+ * harness can begin populating the sink eagerly without coordinating with the
7
+ * consumer's `for await` loop. Events pushed after iteration starts unblock
8
+ * the awaiting consumer via an internal Promise waiter.
9
+ *
10
+ * # Single-iteration invariant
11
+ *
12
+ * `generator()` may be called **at most once** per sink. Sinks are stateful
13
+ * (single waiter slot, single buffer); two iterators on the same sink race on
14
+ * both, so two iterators is always a bug. Calling `generator()` a second time
15
+ * throws — the alternative (silently allowing the second call to share the
16
+ * buffer / waiter with the first) produced the deadlock-class bug catalogued
17
+ * in [issue #529](https://github.com/forcedotcom/agentic-dx/issues/529)
18
+ * Finding 4. Surface failures at construction-time, not at deadlock-time.
19
+ *
20
+ * # `isEnded()`
21
+ *
22
+ * Exposes the closed state for routing logic that needs to skip pushes onto
23
+ * a sink that's already been retired. Routing for a closed sink should
24
+ * silently no-op with a debug log rather than throw — the late event is
25
+ * upstream noise, not a programmer error.
26
+ *
27
+ * Exported from the public `index.ts` so third-party `AgentHarness`
28
+ * implementations in sibling packages can reuse it.
29
+ */
30
+ export declare class GenSink<T> {
31
+ private readonly buffered;
32
+ private ended;
33
+ private waiter;
34
+ private generated;
35
+ push(event: T): void;
36
+ end(): void;
37
+ isEnded(): boolean;
38
+ generator(): AsyncGenerator<T, void>;
39
+ private iterate;
40
+ private wake;
41
+ }
@@ -0,0 +1,88 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ /**
6
+ * Buffered async-generator wrapper used by `AgentHarness` implementations to
7
+ * deliver `ChatEvent`s to a consumer's `ChatStreamResult.eventStream`.
8
+ *
9
+ * The buffer holds events pushed before the consumer starts iterating, so the
10
+ * harness can begin populating the sink eagerly without coordinating with the
11
+ * consumer's `for await` loop. Events pushed after iteration starts unblock
12
+ * the awaiting consumer via an internal Promise waiter.
13
+ *
14
+ * # Single-iteration invariant
15
+ *
16
+ * `generator()` may be called **at most once** per sink. Sinks are stateful
17
+ * (single waiter slot, single buffer); two iterators on the same sink race on
18
+ * both, so two iterators is always a bug. Calling `generator()` a second time
19
+ * throws — the alternative (silently allowing the second call to share the
20
+ * buffer / waiter with the first) produced the deadlock-class bug catalogued
21
+ * in [issue #529](https://github.com/forcedotcom/agentic-dx/issues/529)
22
+ * Finding 4. Surface failures at construction-time, not at deadlock-time.
23
+ *
24
+ * # `isEnded()`
25
+ *
26
+ * Exposes the closed state for routing logic that needs to skip pushes onto
27
+ * a sink that's already been retired. Routing for a closed sink should
28
+ * silently no-op with a debug log rather than throw — the late event is
29
+ * upstream noise, not a programmer error.
30
+ *
31
+ * Exported from the public `index.ts` so third-party `AgentHarness`
32
+ * implementations in sibling packages can reuse it.
33
+ */
34
+ export class GenSink {
35
+ buffered = [];
36
+ ended = false;
37
+ waiter = null;
38
+ generated = false;
39
+ push(event) {
40
+ if (this.ended)
41
+ return;
42
+ this.buffered.push(event);
43
+ this.wake();
44
+ }
45
+ end() {
46
+ if (this.ended)
47
+ return;
48
+ this.ended = true;
49
+ this.wake();
50
+ }
51
+ isEnded() {
52
+ return this.ended;
53
+ }
54
+ generator() {
55
+ // Synchronous guard. Must live outside the async generator body —
56
+ // async generators are lazy (the body does not execute until the
57
+ // first `next()`), so a `throw` inside the body would not surface
58
+ // at the second `generator()` call site as expected.
59
+ if (this.generated) {
60
+ throw new Error('GenSink.generator(): sinks are single-iteration. ' +
61
+ 'Two iterators on one sink race on the buffer and waiter slot. ' +
62
+ 'See https://github.com/forcedotcom/agentic-dx/issues/529 (Finding 4).');
63
+ }
64
+ this.generated = true;
65
+ return this.iterate();
66
+ }
67
+ async *iterate() {
68
+ while (true) {
69
+ if (this.buffered.length > 0) {
70
+ yield this.buffered.shift();
71
+ continue;
72
+ }
73
+ if (this.ended)
74
+ return;
75
+ await new Promise((resolve) => {
76
+ this.waiter = { resolve };
77
+ });
78
+ }
79
+ }
80
+ wake() {
81
+ const w = this.waiter;
82
+ if (w) {
83
+ this.waiter = null;
84
+ w.resolve();
85
+ }
86
+ }
87
+ }
88
+ //# sourceMappingURL=gen-sink.js.map
@@ -2,3 +2,4 @@ export type { AgentHarness, WithAgentConfig, ConfigOf } from './agent-harness.js
2
2
  export type { HarnessFactory } from './harness-factory.js';
3
3
  export type { AgentConfig, StreamOptions } from './harness-config.js';
4
4
  export { toHarnessConfig, DEFAULT_MAX_STEPS } from './harness-config.js';
5
+ export { GenSink } from './gen-sink.js';
@@ -3,4 +3,5 @@
3
3
  * See LICENSE.txt for license terms.
4
4
  */
5
5
  export { toHarnessConfig, DEFAULT_MAX_STEPS } from './harness-config.js';
6
+ export { GenSink } from './gen-sink.js';
6
7
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Harness-implementation public surface.
3
+ *
4
+ * This module is reachable as `@salesforce/sfdx-agent-sdk/harness` —
5
+ * **never** import directly from `dist/` or via deep relative paths.
6
+ *
7
+ * # When to import from here
8
+ *
9
+ * Use this surface when implementing an `AgentHarness` (e.g.
10
+ * `@salesforce/sfdx-agent-harness-claude`,
11
+ * `@salesforce/sfdx-agent-harness-mastra`). It re-exports both the
12
+ * harness contract types (`AgentHarness`, `HarnessFactory`) and the
13
+ * harness-implementation helpers consumer applications must not touch
14
+ * (`HarnessBusOwner`, `GenSink`, `lowerStreamInput`,
15
+ * `SUPPORTED_PROTOCOL_VERSIONS`).
16
+ *
17
+ * # Why this is a separate subpath
18
+ *
19
+ * Consumer applications (anything that calls `createAgentManager` and
20
+ * iterates `ChatStreamResult`) import from the bare specifier:
21
+ *
22
+ * import { createAgentManager, Agent } from '@salesforce/sfdx-agent-sdk';
23
+ *
24
+ * Harness implementations import from the subpath:
25
+ *
26
+ * import { HarnessBusOwner, GenSink } from '@salesforce/sfdx-agent-sdk/harness';
27
+ *
28
+ * The split makes the boundary mechanical: a consumer who tries to import
29
+ * `HarnessBusOwner` from the bare specifier gets an immediate
30
+ * "not exported" error from the bundler / TS resolver, instead of
31
+ * accidentally reaching for a primitive they shouldn't be using.
32
+ *
33
+ * Type-only references to the contract interface (`AgentHarness`,
34
+ * `HarnessFactory`) ALSO appear on the bare specifier so consumer code
35
+ * that needs `AgentManager<H extends AgentHarness>` typing still works
36
+ * without a subpath import — but their runtime / constructible
37
+ * counterparts live only here.
38
+ *
39
+ * Symbols that consumers and harnesses both need (`AgentConfig`,
40
+ * `StreamOptions`, `DEFAULT_MAX_STEPS`, `resolveToolApprovalMode`,
41
+ * `ChatEvent` types, `Message` types, `MCPConfiguration`, etc.) stay on
42
+ * the bare specifier. The split is "harness-only" vs. "consumer-AND-harness",
43
+ * not "harness vs. consumer."
44
+ */
45
+ export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './index.js';
46
+ export { SUPPORTED_PROTOCOL_VERSIONS } from './agent-harness.js';
47
+ export { HarnessBusOwner } from './harness-bus-owner.js';
48
+ export { lowerStreamInput, type InputMessagePart } from './stream-input.js';
49
+ export { GenSink } from './gen-sink.js';
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ export { SUPPORTED_PROTOCOL_VERSIONS } from './agent-harness.js';
6
+ // ── Harness-implementation helpers ──────────────────────────────────
7
+ export { HarnessBusOwner } from './harness-bus-owner.js';
8
+ export { lowerStreamInput } from './stream-input.js';
9
+ export { GenSink } from './gen-sink.js';
10
+ //# sourceMappingURL=public.js.map
package/dist/index.d.ts CHANGED
@@ -14,9 +14,6 @@ export { type Agent } from './agent.js';
14
14
  export { type ChatSession, type ChatOptions } from './chat-session.js';
15
15
  export type { AgentConnectivityResolver, ResolvedConnectivity } from './agent-connectivity-resolver.js';
16
16
  export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './harness/index.js';
17
- export { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
18
- export { HarnessBusOwner } from './harness/harness-bus-owner.js';
19
- export { lowerStreamInput, type InputMessagePart } from './harness/stream-input.js';
20
17
  export { AgentSDKError, AgentSDKErrorType } from './errors.js';
21
18
  export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent, ChatStreamErrorEvent, ChatStreamStartedEvent, ChatStreamTrigger, McpServerDiscoveryCompletedEvent, McpServerDiscoveryFailedEvent, McpServerDiscoveryStartedEvent, McpServerStatusChangedEvent, SessionCreatedEvent, SessionDestroyedEvent, TelemetryEvent, TelemetryEventCallback, ToolApprovalRequestedEvent, ToolApprovalResolvedEvent, ToolExecutionCompletedEvent, ToolExecutionStartedEvent, } from './types/telemetry-events.js';
22
19
  export type { LogLevel, LogRecord, Unsubscribe } from '@salesforce/agentic-common';
package/dist/index.js CHANGED
@@ -10,9 +10,6 @@ export { inferSfApiEnv, SfApiEnv } from '@salesforce/agentic-common';
10
10
  export { createAgentManager } from './agent-manager.js';
11
11
  export {} from './agent.js';
12
12
  export {} from './chat-session.js';
13
- export { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
14
- export { HarnessBusOwner } from './harness/harness-bus-owner.js';
15
- export { lowerStreamInput } from './harness/stream-input.js';
16
13
  // ── Errors ───────────────────────────────────────────────────────────
17
14
  export { AgentSDKError, AgentSDKErrorType } from './errors.js';
18
15
  // ── MCP Auth ────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/sfdx-agent-sdk",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,10 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
12
  },
13
+ "./harness": {
14
+ "types": "./dist/harness/public.d.ts",
15
+ "default": "./dist/harness/public.js"
16
+ },
13
17
  "./package.json": "./package.json"
14
18
  },
15
19
  "scripts": {
@@ -35,13 +39,13 @@
35
39
  "LICENSE.txt"
36
40
  ],
37
41
  "dependencies": {
38
- "@salesforce/agentic-common": "0.7.0",
39
- "@salesforce/llm-gateway-sdk": "0.11.0"
42
+ "@salesforce/agentic-common": "0.8.0",
43
+ "@salesforce/llm-gateway-sdk": "0.12.0"
40
44
  },
41
45
  "devDependencies": {
42
46
  "@eslint/js": "^10.0.1",
43
- "@salesforce/sfdx-agent-harness-claude": "0.11.0",
44
- "@salesforce/sfdx-agent-harness-mastra": "0.14.0",
47
+ "@salesforce/sfdx-agent-harness-claude": "0.12.0",
48
+ "@salesforce/sfdx-agent-harness-mastra": "0.15.0",
45
49
  "@types/node": "^22.19.17",
46
50
  "@vitest/coverage-istanbul": "^4.1.7",
47
51
  "@vitest/eslint-plugin": "^1.6.17",