@salesforce/sfdx-agent-sdk 0.39.0 → 0.41.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/CHANGELOG.md +10 -0
- package/README.md +55 -24
- package/dist/agent-manager.js +10 -2
- package/dist/agent.js +16 -2
- package/dist/chat-session.js +81 -5
- package/dist/harness/agent-harness.d.ts +8 -5
- package/dist/index.d.ts +2 -2
- package/dist/internal/telemetry-router.js +6 -4
- package/dist/types/telemetry-events.d.ts +15 -1
- package/dist/types/wire-communication-event.d.ts +57 -3
- package/dist/wire-communication-file-writer.js +28 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to `@salesforce/sfdx-agent-sdk` are documented in this file.
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [0.41.0] - 2026-08-03
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
- **agent-sdk,harnesses**: emit Tier-1 logBus siblings for lifecycle telemetry @W-23547150@ ([#716](https://github.com/forcedotcom/agentic-dx/pull/716))
|
|
10
|
+
|
|
11
|
+
## [0.40.0] - 2026-08-03
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
- **agent-sdk,harness-mastra,harness-claude,harness-openai**: observability gaps — mcp-tool-call-completed, llm-retry, responseText/TTFT @W-23528008@ ([#717](https://github.com/forcedotcom/agentic-dx/pull/717))
|
|
15
|
+
|
|
6
16
|
## [0.39.0] - 2026-07-30
|
|
7
17
|
|
|
8
18
|
### Features
|
package/README.md
CHANGED
|
@@ -1192,6 +1192,9 @@ const unsubscribe = manager.onWireCommunication((event) => {
|
|
|
1192
1192
|
case 'llm-response':
|
|
1193
1193
|
// matching response (or terminal failure)
|
|
1194
1194
|
break;
|
|
1195
|
+
case 'mcp-tool-call-completed':
|
|
1196
|
+
// completed MCP tool call; redact args/result before persisting
|
|
1197
|
+
break;
|
|
1195
1198
|
case 'wire-monitoring-not-supported':
|
|
1196
1199
|
// harness can't emit per-call events for this stream;
|
|
1197
1200
|
// event.message points at the workaround (typically a debug log file path)
|
|
@@ -1200,17 +1203,28 @@ const unsubscribe = manager.onWireCommunication((event) => {
|
|
|
1200
1203
|
});
|
|
1201
1204
|
```
|
|
1202
1205
|
|
|
1203
|
-
**Discriminated union —
|
|
1206
|
+
**Discriminated union — four variants keyed on `type`:**
|
|
1207
|
+
|
|
1208
|
+
| `type` | Fields |
|
|
1209
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1210
|
+
| `llm-request` | `timestamp`, `url`, `method`, `model`, `correlationId?`, `xClientTraceId?`, `body?` |
|
|
1211
|
+
| `llm-response` | `timestamp`, `model`, `correlationId?`, `status`, `xClientTraceId?`, `totalDurationMs?`, `timeToFirstTokenMs?`, `usage?` (`inputTokens?`, `outputTokens?`, `totalTokens?`, `reasoningTokens?`), `responseText?`, `error?` ({ `message` }) |
|
|
1212
|
+
| `mcp-tool-call-completed` | `timestamp`, `serverName`, `toolName`, `bareToolName`, `args`, `result`, `durationMs?` |
|
|
1213
|
+
| `wire-monitoring-not-supported` | `timestamp`, `harnessId`, `message` (free-form text — typically explains why direct wire monitoring isn't available and points at a fallback log file the harness wrote) |
|
|
1204
1214
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
| `llm-response` | `timestamp`, `model`, `status`, `xClientTraceId?`, `totalDurationMs?`, `timeToFirstTokenMs?`, `usage?` (`inputTokens?`, `outputTokens?`, `totalTokens?`, `reasoningTokens?`), `responseText?`, `error?` ({ `message` }) |
|
|
1209
|
-
| `wire-monitoring-not-supported` | `timestamp`, `harnessId`, `message` (free-form text — typically explains why direct wire monitoring isn't available and points at a fallback log file the harness wrote) |
|
|
1215
|
+
`correlationId` is the harness-guaranteed pairing key stamped identically on a matching `llm-request` / `llm-response`
|
|
1216
|
+
pair. Pair requests and responses on it, **not** on their positions in the event stream. `xClientTraceId` is an optional
|
|
1217
|
+
header value for cross-checking, not the pairing contract.
|
|
1210
1218
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
`
|
|
1219
|
+
`mcp-tool-call-completed` is a **DEBUG-ONLY** diagnostic event: it is opt-in and subscriber-gated like the rest of this
|
|
1220
|
+
channel, is never a `TelemetryEvent`, and is never shipped through telemetry or other externally-shipped event surfaces.
|
|
1221
|
+
Its `args` and `result` are captured verbatim and MAY contain PII. Apply your own redaction / scrubbing before
|
|
1222
|
+
persisting or sharing. `durationMs` is harness-specific: Claude reports RPC latency, while Mastra and OpenAI report an
|
|
1223
|
+
adapter-observed window. Do not compare `durationMs` across harnesses.
|
|
1224
|
+
|
|
1225
|
+
**Privacy contract.** LLM events MAY contain user prompts and model output in `body` / `responseText`; MCP completion
|
|
1226
|
+
events MAY contain PII in verbatim `args` / `result`. These events are **not** flowed to the structured-log channel by
|
|
1227
|
+
default — consumers opt in to wire-level visibility explicitly via `onWireCommunication`. Note that request HTTP headers
|
|
1214
1228
|
(e.g. `Authorization`) are **not** captured on the events; the harness's fetch wrapper sees them but doesn't forward
|
|
1215
1229
|
them onto the channel.
|
|
1216
1230
|
|
|
@@ -1221,17 +1235,26 @@ harness in particular skips writing the subprocess's debug log when nobody is li
|
|
|
1221
1235
|
`chat()` you want to inspect: subscriptions added mid-stream miss the in-flight subprocess on Claude (the listener count
|
|
1222
1236
|
is captured at stream-start).
|
|
1223
1237
|
|
|
1224
|
-
|
|
1238
|
+
#### Cross-harness coverage
|
|
1239
|
+
|
|
1240
|
+
This table is the canonical compatibility reference for the SDK's public observability and wire-diagnostic surfaces.
|
|
1241
|
+
Feature availability is intentionally asymmetric where a runtime does not expose a faithful observation seam.
|
|
1225
1242
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1243
|
+
| Feature | Claude | Mastra | OpenAI |
|
|
1244
|
+
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1245
|
+
| Structured `llm-request` / `llm-response` wire events | **Not implemented.** The opaque native subprocess provides no capturable, parseable per-call format. When subscribed, emits `wire-monitoring-not-supported` and writes a subprocess debug-log file. | **Delivered.** Emits one request/response pair per outbound LLM HTTP call. | **Delivered.** Emits one request/response pair per outbound LLM HTTP call. |
|
|
1246
|
+
| Pairing structured LLM events | N/A — no structured LLM wire events. | **Delivered.** Matching request/response events share a harness-generated `correlationId`; pair on it, never event position or `xClientTraceId` alone. | **Delivered.** Matching request/response events share a harness-generated `correlationId`; pair on it, never event position or `xClientTraceId` alone. |
|
|
1247
|
+
| `responseText` and `timeToFirstTokenMs` on `llm-response` | N/A — no structured LLM wire events. | **Delivered.** A streaming-body tee produces one HTTP-body-terminal response event with best-effort response text and first-token latency. | **Delivered.** A streaming-body tee produces one HTTP-body-terminal response event with best-effort response text and first-token latency. |
|
|
1248
|
+
| `mcp-tool-call-completed` debug event | **Delivered.** External-MCP bridge observation with true RPC latency in `durationMs`. | **Delivered.** Adapter/coordinator-observed `durationMs`, which can include a model round-trip. | **Delivered.** Adapter/coordinator-observed `durationMs`, which can include a model round-trip. |
|
|
1249
|
+
| `llm-retry` telemetry | **Delivered.** Emits numeric-only, PII-free data when the Claude Agent SDK reports an actual `api_retry`. | **Documented limitation:** does not emit; the stateless fetch layer has no retry loop or attempt counter. A `429` / `retry-after` response is a failed attempt, not a retry. | **Documented limitation:** does not emit; the stateless fetch layer has no retry loop or attempt counter. A `429` / `retry-after` response is a failed attempt, not a retry. |
|
|
1250
|
+
|
|
1251
|
+
Claude's debug log is written to `${storageRoot}/claude-debug-logs/${agentId}__${threadId}.log` when at least one
|
|
1252
|
+
consumer is subscribed at stream-start (one file per thread; turns of the same conversation accumulate, separated by
|
|
1253
|
+
greppable `# Turn started: <ISO>` banners). It is left in place; the consumer is responsible for retention and cleanup.
|
|
1254
|
+
|
|
1255
|
+
Mastra and OpenAI callers can identify any upstream-retried requests only as distinct `llm-request` / `llm-response`
|
|
1256
|
+
pairs, using `correlationId`, `xClientTraceId`, and timestamps. Neither harness synthesizes a partial `llm-retry` event
|
|
1257
|
+
from an HTTP response header.
|
|
1235
1258
|
|
|
1236
1259
|
#### `WireCommunicationFileWriter`
|
|
1237
1260
|
|
|
@@ -1253,11 +1276,11 @@ const writer = new WireCommunicationFileWriter(manager, {
|
|
|
1253
1276
|
writer.detach();
|
|
1254
1277
|
```
|
|
1255
1278
|
|
|
1256
|
-
The output file is plain Markdown with one `### <iso-timestamp> [llm-request]`, `[llm-response]`,
|
|
1257
|
-
`[wire-monitoring-not-supported]` header per event followed by the populated fields.
|
|
1258
|
-
when set — sparse responses do not pad the file with empty `**Field**: N/A` lines. On
|
|
1259
|
-
`wire-monitoring-not-supported` block links to the per-thread debug log file where raw wire
|
|
1260
|
-
directly.
|
|
1279
|
+
The output file is plain Markdown with one `### <iso-timestamp> [llm-request]`, `[llm-response]`,
|
|
1280
|
+
`[mcp-tool-call-completed]`, or `[wire-monitoring-not-supported]` header per event followed by the populated fields.
|
|
1281
|
+
Optional fields are emitted only when set — sparse responses do not pad the file with empty `**Field**: N/A` lines. On
|
|
1282
|
+
the Claude harness, the `wire-monitoring-not-supported` block links to the per-thread debug log file where raw wire
|
|
1283
|
+
detail can be inspected directly.
|
|
1261
1284
|
|
|
1262
1285
|
The same privacy contract applies: the resulting file may contain prompts, tool arguments, and model output. Treat it as
|
|
1263
1286
|
sensitive and apply your own retention / scrubbing rules before sharing.
|
|
@@ -1491,6 +1514,7 @@ All variants share `{ type: <discriminant>, timestamp: Date }` plus the fields b
|
|
|
1491
1514
|
| `mcp-server-discovery-completed` | `agentId`, `serverName`, `toolCount`, `durationMs` |
|
|
1492
1515
|
| `mcp-server-discovery-failed` | `agentId`, `serverName`, `durationMs`, `error`, `errorDetail?` ([`McpServerErrorDetail`](#mcpservererrordetail)) |
|
|
1493
1516
|
| `mcp-server-status-changed` | `agentId`, `serverName`, `previousStatus`, `nextStatus`, `error?` |
|
|
1517
|
+
| `llm-retry` | `attempt`, `maxRetries`, `retryDelayMs`, `errorStatus?` — Claude-only; numeric-only and PII-free |
|
|
1494
1518
|
|
|
1495
1519
|
Each variant is also exported as a named type so consumers can write narrowed handlers: `AgentCreatedEvent`,
|
|
1496
1520
|
`AgentDestroyedEvent`, `SessionCreatedEvent`, `SessionDestroyedEvent`, `ChatStreamStartedEvent`,
|
|
@@ -1522,6 +1546,13 @@ Abandoning the iterator mid-stream emits no terminal telemetry — the stream di
|
|
|
1522
1546
|
Harness implementations may emit additional structured logs through their own `LogBus`. Those records surface to
|
|
1523
1547
|
consumers via the same `onLog` channel.
|
|
1524
1548
|
|
|
1549
|
+
Selected lifecycle moments are reported on **both** channels: the typed telemetry event, plus a log record carrying the
|
|
1550
|
+
same facts for log sinks. On those records `message` is human-readable prose intended for display and may be reworded;
|
|
1551
|
+
`context.event_type` carries the matching `TelemetryEvent` type and is the stable field to filter, alert, and join on.
|
|
1552
|
+
Records paired this way today are `agent-created`, `agent-destroyed`, `session-created`, `session-destroyed`,
|
|
1553
|
+
`chat-stream-completed`, `chat-stream-error`, `tool-execution-completed` (only when `isError` is true), and the harness
|
|
1554
|
+
MCP events `mcp-server-discovery-completed`, `mcp-server-discovery-failed`, and `mcp-server-status-changed`.
|
|
1555
|
+
|
|
1525
1556
|
## Development
|
|
1526
1557
|
|
|
1527
1558
|
See [DEVELOPING.md](DEVELOPING.md) for build-from-source setup, scripts, E2E testing, and packaging.
|
package/dist/agent-manager.js
CHANGED
|
@@ -189,12 +189,20 @@ export class DefaultAgentManager {
|
|
|
189
189
|
this.wireRouter.registerAgent(agentId);
|
|
190
190
|
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.modelConnectivityInfo, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.harnessSupportedProviderHints, this.hooksForAgent, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
|
|
191
191
|
this.agents.set(agentId, agent);
|
|
192
|
+
const agentCreatedAt = this.clock.now();
|
|
193
|
+
const modelName = runtime.modelConnectivityInfo.model.name;
|
|
192
194
|
this.telemetryBus.emit({
|
|
193
195
|
type: 'agent-created',
|
|
194
|
-
timestamp:
|
|
196
|
+
timestamp: agentCreatedAt,
|
|
195
197
|
agentId,
|
|
196
198
|
projectRoot,
|
|
197
|
-
modelName
|
|
199
|
+
modelName,
|
|
200
|
+
});
|
|
201
|
+
this.logBus.emit({
|
|
202
|
+
level: 'info',
|
|
203
|
+
message: 'Agent created',
|
|
204
|
+
timestamp: agentCreatedAt,
|
|
205
|
+
context: { event_type: 'agent-created', agentId, projectRoot, modelName },
|
|
198
206
|
});
|
|
199
207
|
if (options.rehydrateThreads) {
|
|
200
208
|
// If thread enumeration or session attachment fails, the restore is a full failure
|
package/dist/agent.js
CHANGED
|
@@ -283,11 +283,18 @@ export class DefaultAgent {
|
|
|
283
283
|
}
|
|
284
284
|
this.sessions.clear();
|
|
285
285
|
await this.harness.destroyAgent(this.agentId);
|
|
286
|
+
const agentDestroyedAt = this.clock.now();
|
|
286
287
|
this.telemetryBus.emit({
|
|
287
288
|
type: 'agent-destroyed',
|
|
288
|
-
timestamp:
|
|
289
|
+
timestamp: agentDestroyedAt,
|
|
289
290
|
agentId: this.agentId,
|
|
290
291
|
});
|
|
292
|
+
this.logBus.emit({
|
|
293
|
+
level: 'info',
|
|
294
|
+
message: 'Agent destroyed',
|
|
295
|
+
timestamp: agentDestroyedAt,
|
|
296
|
+
context: { event_type: 'agent-destroyed', agentId: this.agentId },
|
|
297
|
+
});
|
|
291
298
|
for (const unsub of this.inboundUnsubs)
|
|
292
299
|
unsub();
|
|
293
300
|
for (const unsub of this.parentUnsubs)
|
|
@@ -347,12 +354,19 @@ export class DefaultAgent {
|
|
|
347
354
|
}, getContextWindow, { clock: this.clock, idGenerator: this.idGenerator, persistRememberedRule });
|
|
348
355
|
this.sessions.set(threadId, session);
|
|
349
356
|
this.sessionSliceUnregisters.set(threadId, () => this.router.unregisterSession(threadId));
|
|
357
|
+
const sessionCreatedAt = this.clock.now();
|
|
350
358
|
this.telemetryBus.emit({
|
|
351
359
|
type: 'session-created',
|
|
352
|
-
timestamp:
|
|
360
|
+
timestamp: sessionCreatedAt,
|
|
353
361
|
agentId: this.agentId,
|
|
354
362
|
threadId,
|
|
355
363
|
});
|
|
364
|
+
this.logBus.emit({
|
|
365
|
+
level: 'info',
|
|
366
|
+
message: 'Chat session created',
|
|
367
|
+
timestamp: sessionCreatedAt,
|
|
368
|
+
context: { event_type: 'session-created', agentId: this.agentId, threadId },
|
|
369
|
+
});
|
|
356
370
|
return session;
|
|
357
371
|
}
|
|
358
372
|
detachSession(threadId, session) {
|
package/dist/chat-session.js
CHANGED
|
@@ -237,6 +237,18 @@ export class DefaultChatSession {
|
|
|
237
237
|
durationMs,
|
|
238
238
|
error: lastError,
|
|
239
239
|
});
|
|
240
|
+
this.logBus.emit({
|
|
241
|
+
level: 'error',
|
|
242
|
+
message: 'Chat stream failed',
|
|
243
|
+
timestamp: finishedAt,
|
|
244
|
+
context: {
|
|
245
|
+
event_type: 'chat-stream-error',
|
|
246
|
+
agentId: this.agentId,
|
|
247
|
+
threadId: this.threadId,
|
|
248
|
+
durationMs,
|
|
249
|
+
},
|
|
250
|
+
error: lastError,
|
|
251
|
+
});
|
|
240
252
|
}
|
|
241
253
|
else {
|
|
242
254
|
this.telemetryBus.emit({
|
|
@@ -247,6 +259,18 @@ export class DefaultChatSession {
|
|
|
247
259
|
durationMs,
|
|
248
260
|
usage: finishUsage,
|
|
249
261
|
});
|
|
262
|
+
this.logBus.emit({
|
|
263
|
+
level: 'info',
|
|
264
|
+
message: 'Chat stream completed',
|
|
265
|
+
timestamp: finishedAt,
|
|
266
|
+
context: {
|
|
267
|
+
event_type: 'chat-stream-completed',
|
|
268
|
+
agentId: this.agentId,
|
|
269
|
+
threadId: this.threadId,
|
|
270
|
+
durationMs,
|
|
271
|
+
...(finishUsage !== undefined ? { usage: finishUsage } : {}),
|
|
272
|
+
},
|
|
273
|
+
});
|
|
250
274
|
}
|
|
251
275
|
}
|
|
252
276
|
/**
|
|
@@ -420,12 +444,23 @@ export class DefaultChatSession {
|
|
|
420
444
|
if (this.disposed) {
|
|
421
445
|
return;
|
|
422
446
|
}
|
|
447
|
+
const sessionDestroyedAt = this.clock.now();
|
|
423
448
|
this.telemetryBus.emit({
|
|
424
449
|
type: 'session-destroyed',
|
|
425
|
-
timestamp:
|
|
450
|
+
timestamp: sessionDestroyedAt,
|
|
426
451
|
agentId: this.agentId,
|
|
427
452
|
threadId: this.threadId,
|
|
428
453
|
});
|
|
454
|
+
this.logBus.emit({
|
|
455
|
+
level: 'info',
|
|
456
|
+
message: 'Chat session destroyed',
|
|
457
|
+
timestamp: sessionDestroyedAt,
|
|
458
|
+
context: {
|
|
459
|
+
event_type: 'session-destroyed',
|
|
460
|
+
agentId: this.agentId,
|
|
461
|
+
threadId: this.threadId,
|
|
462
|
+
},
|
|
463
|
+
});
|
|
429
464
|
for (const unsub of this.inboundUnsubs)
|
|
430
465
|
unsub();
|
|
431
466
|
for (const unsub of this.parentUnsubs)
|
|
@@ -436,6 +471,8 @@ export class DefaultChatSession {
|
|
|
436
471
|
this.disposed = true;
|
|
437
472
|
}
|
|
438
473
|
emitToolApprovalResolved(toolCallId, approved, policyWritten) {
|
|
474
|
+
// No Tier-1 logBus sibling: tool-approval-resolved fires per gated
|
|
475
|
+
// tool call — interactive-UI signal, not on-call triage
|
|
439
476
|
this.telemetryBus.emit({
|
|
440
477
|
type: 'tool-approval-resolved',
|
|
441
478
|
timestamp: this.clock.now(),
|
|
@@ -476,6 +513,8 @@ export class DefaultChatSession {
|
|
|
476
513
|
deriveToolTelemetry(event) {
|
|
477
514
|
if (event.type === 'tool-call') {
|
|
478
515
|
this.toolStartMs.set(event.toolCallId, this.clock.now().getTime());
|
|
516
|
+
// No Tier-1 logBus sibling: tool-execution-started pairs 1:1 with
|
|
517
|
+
// tool-execution-completed; the completion (isError=true) is the triage signal
|
|
479
518
|
this.telemetryBus.emit({
|
|
480
519
|
type: 'tool-execution-started',
|
|
481
520
|
timestamp: this.clock.now(),
|
|
@@ -492,19 +531,42 @@ export class DefaultChatSession {
|
|
|
492
531
|
if (start === undefined)
|
|
493
532
|
return;
|
|
494
533
|
this.toolStartMs.delete(event.toolCallId);
|
|
534
|
+
const completedAt = this.clock.now();
|
|
535
|
+
const durationMs = completedAt.getTime() - start;
|
|
536
|
+
const isError = event.isError === true;
|
|
495
537
|
this.telemetryBus.emit({
|
|
496
538
|
type: 'tool-execution-completed',
|
|
497
|
-
timestamp:
|
|
539
|
+
timestamp: completedAt,
|
|
498
540
|
agentId: this.agentId,
|
|
499
541
|
threadId: this.threadId,
|
|
500
542
|
toolCallId: event.toolCallId,
|
|
501
543
|
toolName: event.toolName,
|
|
502
|
-
durationMs
|
|
503
|
-
isError
|
|
544
|
+
durationMs,
|
|
545
|
+
isError,
|
|
504
546
|
...(event.error ? { error: event.error } : {}),
|
|
505
547
|
...(event.annotations ? { annotations: event.annotations } : {}),
|
|
506
548
|
...(event.serverName ? { serverName: event.serverName } : {}),
|
|
507
549
|
});
|
|
550
|
+
// Tier-1 logBus sibling only when isError=true.
|
|
551
|
+
if (isError) {
|
|
552
|
+
this.logBus.emit({
|
|
553
|
+
level: 'error',
|
|
554
|
+
message: 'Tool execution failed',
|
|
555
|
+
timestamp: completedAt,
|
|
556
|
+
context: {
|
|
557
|
+
event_type: 'tool-execution-completed',
|
|
558
|
+
agentId: this.agentId,
|
|
559
|
+
threadId: this.threadId,
|
|
560
|
+
toolCallId: event.toolCallId,
|
|
561
|
+
toolName: event.toolName,
|
|
562
|
+
durationMs,
|
|
563
|
+
isError: true,
|
|
564
|
+
...(event.annotations ? { annotations: event.annotations } : {}),
|
|
565
|
+
...(event.serverName ? { serverName: event.serverName } : {}),
|
|
566
|
+
},
|
|
567
|
+
...(event.error ? { error: event.error } : {}),
|
|
568
|
+
});
|
|
569
|
+
}
|
|
508
570
|
}
|
|
509
571
|
else if (event.type === 'tool-approval-request') {
|
|
510
572
|
// Record the (toolName, serverName?) so a later settle with
|
|
@@ -574,6 +636,7 @@ export class DefaultChatSession {
|
|
|
574
636
|
*/
|
|
575
637
|
emitChatStreamStarted(trigger) {
|
|
576
638
|
const startedAt = this.clock.now();
|
|
639
|
+
// chat-stream-started pairs 1:1 with chat-stream-completed, which already carries outcomes
|
|
577
640
|
this.telemetryBus.emit({
|
|
578
641
|
type: 'chat-stream-started',
|
|
579
642
|
timestamp: startedAt,
|
|
@@ -597,12 +660,25 @@ export class DefaultChatSession {
|
|
|
597
660
|
this.chatEventBus.emit({ type: 'error', error });
|
|
598
661
|
this.chatEventBus.emit({ type: 'finish', finishReason: 'error' });
|
|
599
662
|
const finishedAt = this.clock.now();
|
|
663
|
+
const durationMs = finishedAt.getTime() - startedAt.getTime();
|
|
600
664
|
this.telemetryBus.emit({
|
|
601
665
|
type: 'chat-stream-error',
|
|
602
666
|
timestamp: finishedAt,
|
|
603
667
|
agentId: this.agentId,
|
|
604
668
|
threadId: this.threadId,
|
|
605
|
-
durationMs
|
|
669
|
+
durationMs,
|
|
670
|
+
error,
|
|
671
|
+
});
|
|
672
|
+
this.logBus.emit({
|
|
673
|
+
level: 'error',
|
|
674
|
+
message: 'Chat stream failed',
|
|
675
|
+
timestamp: finishedAt,
|
|
676
|
+
context: {
|
|
677
|
+
event_type: 'chat-stream-error',
|
|
678
|
+
agentId: this.agentId,
|
|
679
|
+
threadId: this.threadId,
|
|
680
|
+
durationMs,
|
|
681
|
+
},
|
|
606
682
|
error,
|
|
607
683
|
});
|
|
608
684
|
}
|
|
@@ -100,11 +100,14 @@ export interface AgentHarness {
|
|
|
100
100
|
*
|
|
101
101
|
* Fidelity may differ between harnesses: the Mastra (in-process) path
|
|
102
102
|
* emits one `request` + one `response` event per outbound HTTP call,
|
|
103
|
-
* with the full body and status visible. The Claude
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* at the
|
|
107
|
-
*
|
|
103
|
+
* with the full body and status visible. The Claude path runs the model
|
|
104
|
+
* client in an opaque native subprocess with no capturable, parseable
|
|
105
|
+
* per-call debug format, so it emits `wire-monitoring-not-supported`
|
|
106
|
+
* pointing at the subprocess debug-log file rather than structured
|
|
107
|
+
* `llm-request` / `llm-response` events. Structured parsing is deferred
|
|
108
|
+
* pending a stable, capturable debug format. Consumers subscribe once at
|
|
109
|
+
* the `AgentManager` layer; the SDK forwards events upward through the
|
|
110
|
+
* bus hierarchy the same way it does for telemetry and log records.
|
|
108
111
|
*/
|
|
109
112
|
onWireCommunication(callback: WireCommunicationEventCallback): Unsubscribe;
|
|
110
113
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -19,11 +19,11 @@ export { type ChatSession, type ChatOptions } from './chat-session.js';
|
|
|
19
19
|
export type { AgentConnectivityResolver, ResolvedConnectivity } from './agent-connectivity-resolver.js';
|
|
20
20
|
export { ApiKeyConnectivityResolver, type ApiKeyConnectivityResolverConfig } from './api-key-connectivity-resolver.js';
|
|
21
21
|
export type { ModelConnectivityInfo, ProviderHint } from './types/model-connectivity-info.js';
|
|
22
|
-
export type { LlmRequestEvent, LlmResponseEvent, WireCommunicationEvent, WireCommunicationEventCallback, WireMonitoringNotSupportedEvent, } from './types/wire-communication-event.js';
|
|
22
|
+
export type { LlmRequestEvent, LlmResponseEvent, McpToolCallCompletedEvent, WireCommunicationEvent, WireCommunicationEventCallback, WireMonitoringNotSupportedEvent, } from './types/wire-communication-event.js';
|
|
23
23
|
export { WireCommunicationFileWriter, type WireCommunicationEmitter, type WireCommunicationFileWriterOptions, } from './wire-communication-file-writer.js';
|
|
24
24
|
export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './harness/index.js';
|
|
25
25
|
export { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
|
26
|
-
export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent, ChatStreamErrorEvent, ChatStreamStartedEvent, ChatStreamTrigger, McpServerDiscoveryCompletedEvent, McpServerDiscoveryFailedEvent, McpServerDiscoveryStartedEvent, McpServerStatusChangedEvent, SessionCreatedEvent, SessionDestroyedEvent, TelemetryEvent, TelemetryEventCallback, ToolApprovalPolicyResolvedEvent, ToolApprovalRequestedEvent, ToolApprovalResolvedEvent, ToolExecutionCompletedEvent, ToolExecutionStartedEvent, } from './types/telemetry-events.js';
|
|
26
|
+
export type { AgentCreatedEvent, AgentDestroyedEvent, ChatStreamCompletedEvent, ChatStreamErrorEvent, ChatStreamStartedEvent, ChatStreamTrigger, LlmRetryEvent, McpServerDiscoveryCompletedEvent, McpServerDiscoveryFailedEvent, McpServerDiscoveryStartedEvent, McpServerStatusChangedEvent, SessionCreatedEvent, SessionDestroyedEvent, TelemetryEvent, TelemetryEventCallback, ToolApprovalPolicyResolvedEvent, ToolApprovalRequestedEvent, ToolApprovalResolvedEvent, ToolExecutionCompletedEvent, ToolExecutionStartedEvent, } from './types/telemetry-events.js';
|
|
27
27
|
export type { LogLevel, LogRecord, Unsubscribe } from '@salesforce/agentic-common';
|
|
28
28
|
export { resolveMcpServerHeaders } from './mcp-auth.js';
|
|
29
29
|
export type { OrgConnection, OrgConnectionFactory } from '@salesforce/agentic-common';
|
|
@@ -91,10 +91,12 @@ export class TelemetryRouter {
|
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
agentSlice
|
|
97
|
-
|
|
94
|
+
if ('agentId' in event) {
|
|
95
|
+
const agentSlice = this.agentSlices.get(event.agentId);
|
|
96
|
+
if (agentSlice) {
|
|
97
|
+
agentSlice.telemetry.emit(event);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
98
100
|
}
|
|
99
101
|
this.unroutedSlice.telemetry.emit(event);
|
|
100
102
|
}
|
|
@@ -178,7 +178,21 @@ export type McpServerStatusChangedEvent = Base<'mcp-server-status-changed'> & {
|
|
|
178
178
|
/** Populated when `nextStatus === 'error'`. */
|
|
179
179
|
error?: string;
|
|
180
180
|
};
|
|
181
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Emitted for each retry attempt initiated by the Claude harness.
|
|
183
|
+
*
|
|
184
|
+
* This event is Claude-only by contract: Mastra and OpenAI expose no observable
|
|
185
|
+
* attempt counter. The type has no harness discriminant, but only the Claude
|
|
186
|
+
* harness emits it. Its required retry fields intentionally remain complete so
|
|
187
|
+
* consumers never receive a partial retry record.
|
|
188
|
+
*/
|
|
189
|
+
export type LlmRetryEvent = Base<'llm-retry'> & {
|
|
190
|
+
attempt: number;
|
|
191
|
+
maxRetries: number;
|
|
192
|
+
retryDelayMs: number;
|
|
193
|
+
errorStatus?: number;
|
|
194
|
+
};
|
|
195
|
+
export type TelemetryEvent = AgentCreatedEvent | AgentDestroyedEvent | SessionCreatedEvent | SessionDestroyedEvent | ChatStreamStartedEvent | ChatStreamCompletedEvent | ChatStreamErrorEvent | ToolExecutionStartedEvent | ToolExecutionCompletedEvent | ToolApprovalRequestedEvent | ToolApprovalResolvedEvent | ToolApprovalPolicyResolvedEvent | McpServerDiscoveryStartedEvent | McpServerDiscoveryCompletedEvent | McpServerDiscoveryFailedEvent | McpServerStatusChangedEvent | LlmRetryEvent;
|
|
182
196
|
export type TelemetryEventCallback = (event: TelemetryEvent) => void;
|
|
183
197
|
export type TelemetryBus = EventBus<TelemetryEvent>;
|
|
184
198
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { UsageMetadata } from './usage.js';
|
|
2
2
|
/**
|
|
3
3
|
* Wire-level communication events, emitted by the harness layer so consumers
|
|
4
|
-
* see the same shape regardless of harness. The union has
|
|
4
|
+
* see the same shape regardless of harness. The union has four members today:
|
|
5
5
|
* - `LlmRequestEvent` (`type: 'llm-request'`) — one outbound HTTP request to
|
|
6
6
|
* the LLM provider.
|
|
7
7
|
* - `LlmResponseEvent` (`type: 'llm-response'`) — the matching inbound
|
|
@@ -12,6 +12,8 @@ import type { UsageMetadata } from './usage.js';
|
|
|
12
12
|
* subprocess owns the HTTP traffic). The event carries a free-form
|
|
13
13
|
* `message` explaining the situation and (when applicable) where the
|
|
14
14
|
* underlying data was written instead.
|
|
15
|
+
* - `McpToolCallCompletedEvent` (`type: 'mcp-tool-call-completed'`) — an
|
|
16
|
+
* opt-in debug record of an MCP tool call's completed input and output.
|
|
15
17
|
*
|
|
16
18
|
* **Privacy contract.** Wire-communication events may contain user prompts,
|
|
17
19
|
* tool arguments, model output, and other PII. They are opt-in (subscribed
|
|
@@ -55,6 +57,18 @@ export type LlmRequestEvent = {
|
|
|
55
57
|
method: string;
|
|
56
58
|
/** Native model id sent on the wire (per `ModelConnectivityInfo.nativeModelId`). */
|
|
57
59
|
model: string;
|
|
60
|
+
/**
|
|
61
|
+
* Harness-generated per-fetch pairing key. Stamped identically on the matching
|
|
62
|
+
* `LlmResponseEvent`; consumers MUST use it as the reliable pairing key rather
|
|
63
|
+
* than positional event ordering. Producers obtain it from the injected
|
|
64
|
+
* `UniqueIDGenerator` in `@salesforce/agentic-common`, never `randomUUID()`.
|
|
65
|
+
*/
|
|
66
|
+
correlationId?: string;
|
|
67
|
+
/**
|
|
68
|
+
* `x-client-trace-id` header value attached to this request. Consumers pair on
|
|
69
|
+
* `correlationId` (the harness-guaranteed key) and may cross-check this value.
|
|
70
|
+
*/
|
|
71
|
+
xClientTraceId?: string;
|
|
58
72
|
/** Request body. Best-effort: partial / absent on harnesses that can't observe it. */
|
|
59
73
|
body?: Record<string, unknown>;
|
|
60
74
|
};
|
|
@@ -65,6 +79,13 @@ export type LlmResponseEvent = {
|
|
|
65
79
|
timestamp: Date;
|
|
66
80
|
/** Native model id from the matching request. */
|
|
67
81
|
model: string;
|
|
82
|
+
/**
|
|
83
|
+
* Harness-generated per-fetch pairing key. Stamped identically on the matching
|
|
84
|
+
* `LlmRequestEvent`; consumers MUST use it as the reliable pairing key rather
|
|
85
|
+
* than positional event ordering. Producers obtain it from the injected
|
|
86
|
+
* `UniqueIDGenerator` in `@salesforce/agentic-common`, never `randomUUID()`.
|
|
87
|
+
*/
|
|
88
|
+
correlationId?: string;
|
|
68
89
|
/** HTTP status code; `0` for harness-internal terminal failures with no transport response. */
|
|
69
90
|
status: number;
|
|
70
91
|
/** Error captured from a transport / parse failure, if any. */
|
|
@@ -85,9 +106,42 @@ export type LlmResponseEvent = {
|
|
|
85
106
|
totalDurationMs?: number;
|
|
86
107
|
/** Best-effort extracted response text (may be omitted by the harness). */
|
|
87
108
|
responseText?: string;
|
|
88
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* `x-client-trace-id` header value attached to the matching request. Consumers
|
|
111
|
+
* pair on `correlationId` (the harness-guaranteed key) and may cross-check this value.
|
|
112
|
+
*/
|
|
89
113
|
xClientTraceId?: string;
|
|
90
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* Debug-only, opt-in, subscriber-gated record of a completed MCP tool call.
|
|
117
|
+
* `args` and `result` are captured verbatim and MAY contain PII, so consumers
|
|
118
|
+
* must redact before persisting or sharing. This is not a telemetry event and
|
|
119
|
+
* MUST NOT be added to `TelemetryEvent` or any externally-shipped surface.
|
|
120
|
+
*
|
|
121
|
+
* `toolName` is the harness-namespaced display name and `bareToolName` is the
|
|
122
|
+
* un-namespaced leaf. `durationMs` semantics differ by harness: the Claude
|
|
123
|
+
* bridge reports true RPC latency, while Mastra and OpenAI report an
|
|
124
|
+
* adapter/coordinator-observed window that may include a model round-trip.
|
|
125
|
+
* Consumers MUST NOT compare durations across harnesses.
|
|
126
|
+
*/
|
|
127
|
+
export type McpToolCallCompletedEvent = {
|
|
128
|
+
/** Direction discriminator. */
|
|
129
|
+
type: 'mcp-tool-call-completed';
|
|
130
|
+
/** Wall-clock timestamp when the tool call completed. */
|
|
131
|
+
timestamp: Date;
|
|
132
|
+
/** Logical MCP server name from the configured server catalog. */
|
|
133
|
+
serverName: string;
|
|
134
|
+
/** Harness-namespaced display name presented to the model. */
|
|
135
|
+
toolName: string;
|
|
136
|
+
/** Un-namespaced tool-name leaf. */
|
|
137
|
+
bareToolName: string;
|
|
138
|
+
/** Verbatim arguments passed to the tool. May contain PII. */
|
|
139
|
+
args: Record<string, unknown>;
|
|
140
|
+
/** Verbatim result returned by the tool. May contain PII. */
|
|
141
|
+
result: unknown;
|
|
142
|
+
/** Harness-dependent observed duration; do not compare across harnesses. */
|
|
143
|
+
durationMs?: number;
|
|
144
|
+
};
|
|
91
145
|
/**
|
|
92
146
|
* Emitted by harnesses whose runtime doesn't expose a programmatic seam for
|
|
93
147
|
* per-call wire monitoring. The event signals to consumers that
|
|
@@ -120,5 +174,5 @@ export type WireMonitoringNotSupportedEvent = {
|
|
|
120
174
|
* Union over every wire-communication event the harness layer can emit.
|
|
121
175
|
* Discriminate on the `type` field.
|
|
122
176
|
*/
|
|
123
|
-
export type WireCommunicationEvent = LlmRequestEvent | LlmResponseEvent | WireMonitoringNotSupportedEvent;
|
|
177
|
+
export type WireCommunicationEvent = LlmRequestEvent | LlmResponseEvent | McpToolCallCompletedEvent | WireMonitoringNotSupportedEvent;
|
|
124
178
|
export type WireCommunicationEventCallback = (event: WireCommunicationEvent) => void;
|
|
@@ -68,6 +68,8 @@ function formatAsMarkdown(event) {
|
|
|
68
68
|
return formatRequestMarkdown(event);
|
|
69
69
|
case 'llm-response':
|
|
70
70
|
return formatResponseMarkdown(event);
|
|
71
|
+
case 'mcp-tool-call-completed':
|
|
72
|
+
return formatMcpToolCallCompletedMarkdown(event);
|
|
71
73
|
case 'wire-monitoring-not-supported':
|
|
72
74
|
return formatWireMonitoringNotSupportedMarkdown(event);
|
|
73
75
|
}
|
|
@@ -135,6 +137,32 @@ function formatResponseMarkdown(event) {
|
|
|
135
137
|
}
|
|
136
138
|
return lines.join('\n');
|
|
137
139
|
}
|
|
140
|
+
function formatMcpToolCallCompletedMarkdown(event) {
|
|
141
|
+
const lines = [
|
|
142
|
+
`### ${event.timestamp.toISOString()} [${event.type}]`,
|
|
143
|
+
``,
|
|
144
|
+
`**Server**: ${event.serverName}`,
|
|
145
|
+
``,
|
|
146
|
+
`**Tool**: ${event.toolName}`,
|
|
147
|
+
``,
|
|
148
|
+
`**Bare tool**: ${event.bareToolName}`,
|
|
149
|
+
``,
|
|
150
|
+
`**Args**:`,
|
|
151
|
+
'```json',
|
|
152
|
+
JSON.stringify(event.args, null, 2),
|
|
153
|
+
'```',
|
|
154
|
+
``,
|
|
155
|
+
`**Result**:`,
|
|
156
|
+
'```json',
|
|
157
|
+
JSON.stringify(event.result, null, 2),
|
|
158
|
+
'```',
|
|
159
|
+
``,
|
|
160
|
+
];
|
|
161
|
+
if (event.durationMs !== undefined) {
|
|
162
|
+
lines.push(`**Duration**: ${event.durationMs}`, ``);
|
|
163
|
+
}
|
|
164
|
+
return lines.join('\n');
|
|
165
|
+
}
|
|
138
166
|
function formatWireMonitoringNotSupportedMarkdown(event) {
|
|
139
167
|
const dateTime = event.timestamp.toISOString();
|
|
140
168
|
return [`### ${dateTime} [${event.type}]`, ``, `**Harness**: ${event.harnessId}`, ``, event.message, ``, ``].join('\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@eslint/js": "^10.0.1",
|
|
50
|
-
"@salesforce/sfdx-agent-harness-claude": "0.
|
|
51
|
-
"@salesforce/sfdx-agent-harness-mastra": "0.
|
|
52
|
-
"@salesforce/sfdx-agent-harness-openai": "0.
|
|
50
|
+
"@salesforce/sfdx-agent-harness-claude": "0.37.0",
|
|
51
|
+
"@salesforce/sfdx-agent-harness-mastra": "0.40.0",
|
|
52
|
+
"@salesforce/sfdx-agent-harness-openai": "0.6.0",
|
|
53
53
|
"@types/node": "^22.20.0",
|
|
54
54
|
"@vitest/coverage-istanbul": "^4.1.10",
|
|
55
55
|
"@vitest/eslint-plugin": "^1.6.22",
|