@salesforce/sfdx-agent-sdk 0.5.0 → 0.7.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 +170 -19
- package/dist/agent-manager.d.ts +37 -7
- package/dist/agent-manager.js +15 -1
- package/dist/agent.d.ts +12 -2
- package/dist/agent.js +18 -2
- package/dist/harness/agent-harness.d.ts +45 -1
- package/dist/harness/harness-config.d.ts +10 -0
- package/dist/harness/harness-config.js +10 -0
- package/dist/harness/harness-factory.d.ts +2 -2
- package/dist/harness/index.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/mcp-config.d.ts +64 -1
- package/dist/types/usage.d.ts +3 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ await manager.shutdown();
|
|
|
58
58
|
|
|
59
59
|
## API Reference
|
|
60
60
|
|
|
61
|
-
### `createAgentManager(storageRootFolder, harnessFactory, connectivityResolver?): Promise<AgentManager
|
|
61
|
+
### `createAgentManager<F>(storageRootFolder, harnessFactory, connectivityResolver?): Promise<AgentManager<H>>`
|
|
62
62
|
|
|
63
63
|
Factory function that creates an `AgentManager` backed by the provided `HarnessFactory`. The `storageRootFolder` must be
|
|
64
64
|
an existing directory and is used for persistent state (the harness's runtime data plus the SDK's per-agent identity
|
|
@@ -67,26 +67,46 @@ protocol version, replays any persisted agents the harness can still serve, and
|
|
|
67
67
|
`connectivityResolver` overrides the default sf-CLI-based org resolution — used by e2e tests and custom-auth
|
|
68
68
|
deployments; production callers leave it unset.
|
|
69
69
|
|
|
70
|
+
The harness type `H` is **inferred from the factory's `create()` return type**, so consumers don't pass an explicit type
|
|
71
|
+
argument:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
75
|
+
|
|
76
|
+
const manager = await createAgentManager(storageRoot, new MastraHarnessFactory());
|
|
77
|
+
// ^? AgentManager<MastraAgentHarness>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
When the factory's `create()` is typed as `Promise<AgentHarness>` (the default), the manager is
|
|
81
|
+
`AgentManager<AgentHarness>` and behaves exactly as before. Harness packages that ship a branded subtype (e.g.
|
|
82
|
+
`MastraAgentHarness`) lift consumers into that subtype automatically — see "Harness Extensibility" below.
|
|
83
|
+
|
|
70
84
|
Restore failures (a persisted record the SDK could not bring back online — e.g. missing project directory, harness
|
|
71
85
|
rejection, thread rehydration failure) are queryable on the returned manager via `getRestoreFailures()`. Soft skips
|
|
72
86
|
inside the persistence directory (corrupt JSON, harness-id mismatch) are silently dropped from the restore pass and emit
|
|
73
87
|
a `warn` on the SDK's log bus.
|
|
74
88
|
|
|
75
|
-
### `AgentManager
|
|
89
|
+
### `AgentManager<H extends AgentHarness = AgentHarness>`
|
|
76
90
|
|
|
77
91
|
Top-level orchestrator that owns the harness and manages agent lifecycle. `AgentManager` is an interface; the concrete
|
|
78
92
|
implementation is internal — `createAgentManager` is the only public entry point.
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
94
|
+
The optional `H` type parameter (default `AgentHarness`) lets harness-aware consumers reach harness-specific features
|
|
95
|
+
through a typed `manager.extensions` slot. `createAgentManager` infers `H` from the factory; you usually don't write it
|
|
96
|
+
explicitly. The `createAgent` config parameter narrows automatically when the harness brands itself with
|
|
97
|
+
`WithAgentConfig` — see "Harness Extensibility" below.
|
|
98
|
+
|
|
99
|
+
| Property / Method | Signature | Description |
|
|
100
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
101
|
+
| `extensions` | `H['extensions']` | Harness-specific extensions namespace (read-only). Re-exposes the harness's `extensions` slot typed off `H`. Per-agent accessors take the agent id as their first argument. The SDK never reads or interprets this — see "Harness Extensibility" below. |
|
|
102
|
+
| `createAgent` | `(projectRoot: string, config?: ConfigOf<H> & { agentId?: string }, options?: { abortSignal?: AbortSignal }) => Promise<Agent<H>>` | Create and register a new agent and persist its identity triple. `projectRoot` must be an existing directory. If `agentId` is omitted a UUID is generated. The config type is inferred from the harness — see `ConfigOf<H>`. |
|
|
103
|
+
| `getAgent` | `(agentId: string) => Agent<H>` | Retrieve a live agent by ID. Throws `AgentSDKError` (`AGENT_NOT_FOUND`) for unknown ids and for ids that are only present in `getRestoreFailures()`. |
|
|
104
|
+
| `getAgentIds` | `() => string[]` | List all live agent IDs (successful + successfully restored). Failed-restore agents are not included — query `getRestoreFailures()` separately. |
|
|
105
|
+
| `destroyAgent` | `(agentId: string) => Promise<void>` | Destroy an agent, remove its identity record from disk, and clear any matching `getRestoreFailures()` entry. Failed-restore-only ids are accepted (no harness call made). |
|
|
106
|
+
| `shutdown` | `() => Promise<void>` | Destroy all live agents and shut down the harness. Identity files survive (that's the whole point) — restart `createAgentManager` over the same root to bring them back. |
|
|
107
|
+
| `onTelemetry` | `(callback: TelemetryEventCallback) => Unsubscribe` | Subscribe to telemetry across all managed agents. |
|
|
108
|
+
| `onLog` | `(callback: (record: LogRecord) => void) => Unsubscribe` | Subscribe to structured logs across all managed agents. Bridge this into your host logger to observe restore-failure events + soft-skip warnings. |
|
|
109
|
+
| `getRestoreFailures` | `() => RestoreFailure[]` | Snapshot of agents the SDK could not restore on this boot. Each entry carries the persisted `{ agentId, projectRoot, config }` plus the underlying error. |
|
|
90
110
|
|
|
91
111
|
#### `RestoreFailure`
|
|
92
112
|
|
|
@@ -103,9 +123,11 @@ Returned by `AgentManager.getRestoreFailures()`. Use it to seed `error`-state pl
|
|
|
103
123
|
**not** iterate it for logging — the SDK already emitted each failure via `onLog` at `error` level during the restore
|
|
104
124
|
pass, before this function returned.
|
|
105
125
|
|
|
106
|
-
### `Agent
|
|
126
|
+
### `Agent<H extends AgentHarness = AgentHarness>`
|
|
107
127
|
|
|
108
|
-
A configured AI agent. Factory for chat sessions.
|
|
128
|
+
A configured AI agent. Factory for chat sessions. The optional `H` type parameter is currently informational on `Agent`
|
|
129
|
+
— harness-specific features are reached through `manager.extensions`, not `agent.extensions`. The default `AgentHarness`
|
|
130
|
+
keeps unparameterized call sites working.
|
|
109
131
|
|
|
110
132
|
| Method | Signature | Description |
|
|
111
133
|
| ---------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
@@ -226,9 +248,53 @@ type MCPRemoteServerConfig = {
|
|
|
226
248
|
| -------- | ------------------------------------------------------ | --------------------------------------- |
|
|
227
249
|
| `name` | `string` | Server identifier. |
|
|
228
250
|
| `status` | `'connected' \| 'connecting' \| 'disabled' \| 'error'` | Connection state. |
|
|
229
|
-
| `tools` | `
|
|
251
|
+
| `tools` | [`McpToolInfo[]`](#mcptoolinfo) | Discovered tools (name + metadata). |
|
|
230
252
|
| `error?` | `string` | Error message when status is `'error'`. |
|
|
231
253
|
|
|
254
|
+
#### `McpToolInfo`
|
|
255
|
+
|
|
256
|
+
Runtime metadata for a single MCP-discovered tool. Optional fields are populated when the underlying harness can supply
|
|
257
|
+
them from its MCP client; harnesses whose runtime does not expose a given field leave it `undefined`. Consumers must
|
|
258
|
+
treat every field except `name` as optional.
|
|
259
|
+
|
|
260
|
+
| Field | Type | Description |
|
|
261
|
+
| -------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
262
|
+
| `name` | `string` | Tool name as exposed to the LLM, including any harness-applied namespacing. |
|
|
263
|
+
| `description?` | `string` | Human-readable description of what the tool does. |
|
|
264
|
+
| `inputSchema?` | `Record<string, unknown>` | Tool input parameters as a [**JSON Schema**](https://json-schema.org/) object (the MCP wire format). |
|
|
265
|
+
| `annotations?` | [`McpToolAnnotations`](#mcptoolannotations) | Behavioral / UI-presentation hints declared by the MCP server. |
|
|
266
|
+
|
|
267
|
+
**`inputSchema` is a JSON Schema object, not a Zod schema.** It is typed as `Record<string, unknown>` so this package
|
|
268
|
+
incurs no `zod` or `@types/json-schema` dependency. If you need a Zod schema at runtime, convert with a library such as
|
|
269
|
+
[`json-schema-to-zod`](https://www.npmjs.com/package/json-schema-to-zod); for runtime validation, feed the schema to a
|
|
270
|
+
JSON Schema validator such as [AJV](https://ajv.js.org/).
|
|
271
|
+
|
|
272
|
+
The exact set of keys present on `inputSchema` depends on the harness's normalization step. The Mastra harness, for
|
|
273
|
+
example, reaches consumers after passing through `@mastra/schema-compat`'s converter, which adds a `$schema` annotation
|
|
274
|
+
(typically `http://json-schema.org/draft-07/schema#`) and `additionalProperties: false` even when the source MCP server
|
|
275
|
+
did not declare them. Both are valid JSON Schema annotations and are forwarded untouched.
|
|
276
|
+
|
|
277
|
+
**Why no `outputSchema` field?** The MCP protocol carries an optional `outputSchema` per tool, but neither shipped
|
|
278
|
+
harness can supply it: Mastra's `@mastra/mcp` strips it from each wrapped tool before the harness sees it (to keep
|
|
279
|
+
`CallToolResult` validation correct), and the Claude Agent SDK's MCP status surface omits the field entirely. We
|
|
280
|
+
deliberately keep it off the SDK contract rather than ship an always-`undefined` field consumers would have to ignore;
|
|
281
|
+
adding it later if a harness gains the data is non-breaking.
|
|
282
|
+
|
|
283
|
+
#### `McpToolAnnotations`
|
|
284
|
+
|
|
285
|
+
Mirrors the
|
|
286
|
+
[MCP protocol's `Tool.annotations` shape](https://spec.modelcontextprotocol.io/specification/server/tools/#tool-annotations).
|
|
287
|
+
Each field is optional because MCP servers populate annotations à la carte; absence means "the server did not declare
|
|
288
|
+
this hint," not "false."
|
|
289
|
+
|
|
290
|
+
| Field | Type | Description |
|
|
291
|
+
| ------------------ | --------- | ------------------------------------------------------------------------------ |
|
|
292
|
+
| `title?` | `string` | Human-readable label suitable for UI display (vs. the machine `name`). |
|
|
293
|
+
| `readOnlyHint?` | `boolean` | When `true`, the tool only reads data and has no side effects. |
|
|
294
|
+
| `destructiveHint?` | `boolean` | When `true`, the tool may perform destructive updates to its environment. |
|
|
295
|
+
| `idempotentHint?` | `boolean` | When `true`, repeated calls with the same arguments have no additional effect. |
|
|
296
|
+
| `openWorldHint?` | `boolean` | When `true`, the tool may interact with an open world of external entities. |
|
|
297
|
+
|
|
232
298
|
### Tool Types
|
|
233
299
|
|
|
234
300
|
```typescript
|
|
@@ -338,7 +404,14 @@ const agent = await manager.createAgent('/project', {
|
|
|
338
404
|
// Poll until connected
|
|
339
405
|
const servers = agent.getMcpServerInfo();
|
|
340
406
|
for (const s of servers) {
|
|
341
|
-
|
|
407
|
+
const toolNames = s.tools.map((t) => t.name).join(', ');
|
|
408
|
+
console.log(`${s.name}: ${s.status}, tools: ${toolNames}`);
|
|
409
|
+
|
|
410
|
+
// Each tool also carries description / inputSchema / annotations when available.
|
|
411
|
+
for (const tool of s.tools) {
|
|
412
|
+
if (tool.description) console.log(` ${tool.name} — ${tool.description}`);
|
|
413
|
+
// tool.inputSchema is a JSON Schema object; convert with json-schema-to-zod if you need Zod.
|
|
414
|
+
}
|
|
342
415
|
}
|
|
343
416
|
```
|
|
344
417
|
|
|
@@ -419,6 +492,84 @@ Returns `true` if the URL matches a Salesforce Hosted MCP Server endpoint (prod,
|
|
|
419
492
|
The SDK exposes typed `TelemetryEvent` / `LogRecord` streams at the manager, agent, and session scopes. See
|
|
420
493
|
[Telemetry & Logs](#telemetry--logs) below for the event catalog, emit contract, and structured-log table.
|
|
421
494
|
|
|
495
|
+
### Harness Extensibility
|
|
496
|
+
|
|
497
|
+
The `AgentHarness` contract is the SDK's lowest-common-denominator surface — every harness implements it, and the SDK
|
|
498
|
+
consumes only that. Features a single harness has but the contract can't generalize over (Mastra workflows, Mastra's
|
|
499
|
+
`ToolSearchProcessor`, structured working memory, etc.) live on a typed `extensions` slot the harness package owns. The
|
|
500
|
+
SDK never reads or interprets `extensions`; it just threads the type through.
|
|
501
|
+
|
|
502
|
+
For most consumers, the inference machinery means **you never write the type parameters**:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { createAgentManager } from '@salesforce/sfdx-agent-sdk';
|
|
506
|
+
import { MastraHarnessFactory } from '@salesforce/sfdx-agent-harness-mastra';
|
|
507
|
+
|
|
508
|
+
const manager = await createAgentManager(storageRoot, new MastraHarnessFactory());
|
|
509
|
+
// ^? AgentManager<MastraAgentHarness> (inferred from the factory)
|
|
510
|
+
|
|
511
|
+
const agent = await manager.createAgent(projectRoot, {
|
|
512
|
+
instructions: '...',
|
|
513
|
+
toolSearch: { pool: '*' }, // ← Mastra-specific config; autocompletes via WithAgentConfig
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const processor = manager.extensions.mastra.getToolSearchProcessor(agent.getId());
|
|
517
|
+
// ^? ToolSearchHandle | undefined
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Per-agent accessors (like `getToolSearchProcessor` above) take the agent id as their first argument; orchestrator-level
|
|
521
|
+
accessors (e.g. workflow registries) don't. Everything lives on `manager.extensions` regardless of scope — the SDK chose
|
|
522
|
+
the harness-scoped shape for simplicity over a per-feature scope split. See `ARCHITECTURE.md` → "Harness Extensibility"
|
|
523
|
+
for the rationale.
|
|
524
|
+
|
|
525
|
+
#### `AgentHarness.extensions: Record<string, unknown>`
|
|
526
|
+
|
|
527
|
+
Passthrough slot on the harness contract. Concrete harnesses narrow it on their branded subtype (e.g.
|
|
528
|
+
`MastraAgentHarness` declares `extensions: { mastra: { ... } }`). The SDK never reads this; it surfaces it as
|
|
529
|
+
`AgentManager.extensions` typed off the harness type.
|
|
530
|
+
|
|
531
|
+
#### `WithAgentConfig<H extends AgentHarness, C extends AgentConfig>`
|
|
532
|
+
|
|
533
|
+
Opt-in helper for harnesses that accept extra `AgentConfig` fields. Wrap your branded harness type with this helper and
|
|
534
|
+
`AgentManager.createAgent`'s config parameter narrows to `C` automatically:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import type { AgentHarness, WithAgentConfig, AgentConfig } from '@salesforce/sfdx-agent-sdk';
|
|
538
|
+
|
|
539
|
+
type MyAgentConfig = AgentConfig & { custom?: { ... } };
|
|
540
|
+
|
|
541
|
+
type MyAgentHarness = WithAgentConfig<
|
|
542
|
+
AgentHarness & {
|
|
543
|
+
readonly harnessId: 'my-harness';
|
|
544
|
+
readonly extensions: { 'my-harness': { ... } };
|
|
545
|
+
},
|
|
546
|
+
MyAgentConfig
|
|
547
|
+
>;
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
The brand is type-only; it never exists at runtime. Harnesses that don't accept extra config fields leave
|
|
551
|
+
`WithAgentConfig` unused — the base `AgentHarness` interface stays untouched.
|
|
552
|
+
|
|
553
|
+
#### `ConfigOf<H extends AgentHarness>`
|
|
554
|
+
|
|
555
|
+
Resolves the `AgentConfig` subtype declared by a harness via `WithAgentConfig`. Falls back to plain `AgentConfig` when
|
|
556
|
+
the harness wasn't branded. Used internally by `AgentManager.createAgent`'s parameter type; harness-package authors
|
|
557
|
+
don't usually reference it directly.
|
|
558
|
+
|
|
559
|
+
#### Decision rule: cross-harness contract vs. extension
|
|
560
|
+
|
|
561
|
+
When deciding where a new harness-package feature should live:
|
|
562
|
+
|
|
563
|
+
| Question | Answer → Where it goes |
|
|
564
|
+
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
565
|
+
| Could a second harness implement this with similar semantics? | Likely cross-harness contract |
|
|
566
|
+
| Is this an operational handle on a harness-internal runtime object (e.g. processor, workspace)? | Extension namespace |
|
|
567
|
+
| Does this require harness-specific config that doesn't generalize? | Extension (extra fields on a `WithAgentConfig`-branded type) |
|
|
568
|
+
| Is the polyfill cheap and faithful across harnesses? | Cross-harness contract with a polyfilled default |
|
|
569
|
+
|
|
570
|
+
Extensions are the default for "Mastra has a thing nothing else has." See `ARCHITECTURE.md` → "Harness Extensibility"
|
|
571
|
+
for the full design including the `WithAgentConfig` rationale.
|
|
572
|
+
|
|
422
573
|
## Writing a Harness
|
|
423
574
|
|
|
424
575
|
The SDK ships no harness implementation. Consumers pick one by passing a `HarnessFactory` instance to
|
|
@@ -430,7 +581,7 @@ Harness authors implement two interfaces and can compose one helper class, all e
|
|
|
430
581
|
|
|
431
582
|
| Export | Role |
|
|
432
583
|
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
433
|
-
| `HarnessFactory
|
|
584
|
+
| `HarnessFactory<H>` | Construct a harness of type `H` bound to a storage root. Declares `harnessId` and `protocolVersion`. Default `H = AgentHarness`. |
|
|
434
585
|
| `AgentHarness` | Runtime contract: agent / thread / stream / tool / message lifecycle. Declares its own `harnessId` and `protocolVersion`. |
|
|
435
586
|
| `SUPPORTED_PROTOCOL_VERSIONS` | Readonly list of harness protocol versions this SDK accepts. `createAgentManager` checks both the factory and the constructed harness. |
|
|
436
587
|
| `HarnessBusOwner` | Composition helper owning telemetry + log buses with `dispose()` semantics. Reuse it instead of reimplementing bus plumbing. |
|
|
@@ -461,10 +612,10 @@ class MyHarness implements AgentHarness {
|
|
|
461
612
|
// implement the remaining AgentHarness methods (createAgent, stream, …)
|
|
462
613
|
}
|
|
463
614
|
|
|
464
|
-
export class MyHarnessFactory implements HarnessFactory {
|
|
615
|
+
export class MyHarnessFactory implements HarnessFactory<MyHarness> {
|
|
465
616
|
readonly harnessId = 'my-harness';
|
|
466
617
|
readonly protocolVersion = 1;
|
|
467
|
-
async create(storageRootFolder: string): Promise<
|
|
618
|
+
async create(storageRootFolder: string): Promise<MyHarness> {
|
|
468
619
|
return new MyHarness(storageRootFolder);
|
|
469
620
|
}
|
|
470
621
|
}
|
package/dist/agent-manager.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Clock, LogBus, type LogRecord, type Unsubscribe, type UniqueIDGenerator } from '@salesforce/agentic-common';
|
|
2
|
-
import { type AgentHarness } from './harness/agent-harness.js';
|
|
2
|
+
import { type AgentHarness, type ConfigOf } from './harness/agent-harness.js';
|
|
3
3
|
import type { HarnessFactory } from './harness/harness-factory.js';
|
|
4
4
|
import { type AgentConfig } from './harness/harness-config.js';
|
|
5
5
|
import { type Agent } from './agent.js';
|
|
@@ -35,17 +35,23 @@ export type RestoreFailure = {
|
|
|
35
35
|
* host logger; restore failures are emitted at `error` level and soft skips
|
|
36
36
|
* (corrupt JSON, harness-id mismatch) at `warn`.
|
|
37
37
|
*/
|
|
38
|
-
export interface AgentManager {
|
|
38
|
+
export interface AgentManager<H extends AgentHarness = AgentHarness> {
|
|
39
39
|
/**
|
|
40
40
|
* Creates a new {@link Agent} with the given configuration and persists
|
|
41
41
|
* its identity triple `{ agentId, projectRoot, config }` so it can be
|
|
42
42
|
* restored on the next boot.
|
|
43
43
|
*
|
|
44
|
+
* The config type is inferred from the harness type `H`. Harnesses that
|
|
45
|
+
* declare a subtype via `__agentConfig` (e.g. `MastraAgentHarness`
|
|
46
|
+
* declares `MastraAgentConfig`) get their extra fields autocompleted
|
|
47
|
+
* without an explicit generic at the call site. Extra fields thread
|
|
48
|
+
* through opaquely to the harness, which narrows what it cares about.
|
|
49
|
+
*
|
|
44
50
|
* @throws If `projectRoot` does not exist or is not a directory.
|
|
45
51
|
* @throws If `config.agentId` is provided and an agent with that ID is
|
|
46
52
|
* already registered (live or in restore-failure state).
|
|
47
53
|
*/
|
|
48
|
-
createAgent(projectRoot: string, config?:
|
|
54
|
+
createAgent(projectRoot: string, config?: ConfigOf<H> & {
|
|
49
55
|
agentId?: string;
|
|
50
56
|
}, options?: {
|
|
51
57
|
abortSignal?: AbortSignal;
|
|
@@ -78,6 +84,21 @@ export interface AgentManager {
|
|
|
78
84
|
destroyAgent(agentId: string): Promise<void>;
|
|
79
85
|
/** Shuts down the harness and destroys all live agents. */
|
|
80
86
|
shutdown(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Harness-specific extensions namespace, typed off the harness subtype `H`.
|
|
89
|
+
*
|
|
90
|
+
* For the default `AgentHarness`, this is the opaque `Record<string, unknown>`
|
|
91
|
+
* declared on the contract — no IDE help. Consumers parameterize the manager
|
|
92
|
+
* over a harness-specific subtype (e.g. `MastraAgentHarness`) to get a typed
|
|
93
|
+
* slot like `manager.extensions.mastra.getToolSearchProcessor(agentId)`.
|
|
94
|
+
*
|
|
95
|
+
* Per-agent extension accessors take the agent id as their first argument;
|
|
96
|
+
* orchestrator-level accessors (e.g. workflow registries) don't.
|
|
97
|
+
*
|
|
98
|
+
* The SDK never reads or interprets this object; it is a passthrough surface
|
|
99
|
+
* owned by the harness package.
|
|
100
|
+
*/
|
|
101
|
+
readonly extensions: H['extensions'];
|
|
81
102
|
/** Subscribe to telemetry events across every managed agent. */
|
|
82
103
|
onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
|
|
83
104
|
/** Subscribe to structured log records across every managed agent. */
|
|
@@ -99,7 +120,7 @@ export interface AgentManager {
|
|
|
99
120
|
* pattern mirrors {@link Workspace.create}: a private constructor for sync
|
|
100
121
|
* field assignment, then a static async builder that calls `init()`.
|
|
101
122
|
*/
|
|
102
|
-
export declare class DefaultAgentManager implements AgentManager {
|
|
123
|
+
export declare class DefaultAgentManager<H extends AgentHarness = AgentHarness> implements AgentManager<H> {
|
|
103
124
|
private readonly harness;
|
|
104
125
|
private readonly agentIdGenerator;
|
|
105
126
|
private readonly agentConnectivityResolver;
|
|
@@ -123,10 +144,10 @@ export declare class DefaultAgentManager implements AgentManager {
|
|
|
123
144
|
* is private, so this is the only way to obtain an instance, but
|
|
124
145
|
* consumers should always go through {@link createAgentManager}.
|
|
125
146
|
*/
|
|
126
|
-
static __build(harness:
|
|
147
|
+
static __build<H extends AgentHarness>(harness: H, agentConnectivityResolver: AgentConnectivityResolver, storageRootFolder: string, agentIdGenerator: UniqueIDGenerator, clock: Clock, logBus: LogBus): Promise<DefaultAgentManager<H>>;
|
|
127
148
|
private init;
|
|
128
149
|
shutdown(): Promise<void>;
|
|
129
|
-
createAgent(projectRoot: string, config?:
|
|
150
|
+
createAgent(projectRoot: string, config?: ConfigOf<H> & {
|
|
130
151
|
agentId?: string;
|
|
131
152
|
}, options?: {
|
|
132
153
|
abortSignal?: AbortSignal;
|
|
@@ -150,6 +171,15 @@ export declare class DefaultAgentManager implements AgentManager {
|
|
|
150
171
|
onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
|
|
151
172
|
onLog(callback: (record: LogRecord) => void): Unsubscribe;
|
|
152
173
|
getRestoreFailures(): RestoreFailure[];
|
|
174
|
+
/**
|
|
175
|
+
* Re-exposes `harness.extensions` typed off `H`. Read-only; the SDK never
|
|
176
|
+
* leaks the harness reference itself, only its declared extensions surface.
|
|
177
|
+
* `assertNotDisposed` is intentionally NOT called here — the extensions
|
|
178
|
+
* object is reachable on the harness, and reading it after shutdown is the
|
|
179
|
+
* harness's prerogative to handle (its own disposal mechanics may have
|
|
180
|
+
* already torn down the underlying state).
|
|
181
|
+
*/
|
|
182
|
+
get extensions(): H['extensions'];
|
|
153
183
|
private assertNotDisposed;
|
|
154
184
|
}
|
|
155
185
|
/**
|
|
@@ -169,4 +199,4 @@ export declare class DefaultAgentManager implements AgentManager {
|
|
|
169
199
|
* {@link SUPPORTED_PROTOCOL_VERSIONS}, or when the harness's reported
|
|
170
200
|
* version disagrees with the factory's.
|
|
171
201
|
*/
|
|
172
|
-
export declare function createAgentManager(storageRootFolder: string, harnessFactory: HarnessFactory
|
|
202
|
+
export declare function createAgentManager<H extends AgentHarness = AgentHarness>(storageRootFolder: string, harnessFactory: HarnessFactory<H>, connectivityResolver?: AgentConnectivityResolver): Promise<AgentManager<H>>;
|
package/dist/agent-manager.js
CHANGED
|
@@ -157,7 +157,7 @@ export class DefaultAgentManager {
|
|
|
157
157
|
const runtime = await this.agentConnectivityResolver.resolve(projectRoot, config);
|
|
158
158
|
await this.harness.createAgent(agentId, projectRoot, runtime.llmGatewayClient, toHarnessConfig(config, runtime.orgJwt), options.abortSignal !== undefined ? { abortSignal: options.abortSignal } : undefined);
|
|
159
159
|
const agentSlice = this.router.registerAgent(agentId);
|
|
160
|
-
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.llmGatewayClient, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
|
|
160
|
+
const agent = new DefaultAgent(this.harness, agentId, projectRoot, config, runtime.llmGatewayClient, runtime.orgConnection, runtime.orgJwt, this.agentConnectivityResolver, this.identityStore, this.router, agentSlice, { telemetry: this.telemetryBus, log: this.logBus }, this.clock, this.agentIdGenerator);
|
|
161
161
|
this.agents.set(agentId, agent);
|
|
162
162
|
this.telemetryBus.emit({
|
|
163
163
|
type: 'agent-created',
|
|
@@ -235,6 +235,20 @@ export class DefaultAgentManager {
|
|
|
235
235
|
this.assertNotDisposed();
|
|
236
236
|
return [...this.restoreFailures];
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Re-exposes `harness.extensions` typed off `H`. Read-only; the SDK never
|
|
240
|
+
* leaks the harness reference itself, only its declared extensions surface.
|
|
241
|
+
* `assertNotDisposed` is intentionally NOT called here — the extensions
|
|
242
|
+
* object is reachable on the harness, and reading it after shutdown is the
|
|
243
|
+
* harness's prerogative to handle (its own disposal mechanics may have
|
|
244
|
+
* already torn down the underlying state).
|
|
245
|
+
*/
|
|
246
|
+
get extensions() {
|
|
247
|
+
// The harness reference is constrained to `H`, so `harness.extensions`
|
|
248
|
+
// is structurally `H['extensions']` — but TS widens the property access
|
|
249
|
+
// through the constraint, requiring this cast.
|
|
250
|
+
return this.harness.extensions;
|
|
251
|
+
}
|
|
238
252
|
assertNotDisposed() {
|
|
239
253
|
if (this.disposed) {
|
|
240
254
|
throw new AgentSDKError('AgentManager has been shut down.', AgentSDKErrorType.DISPOSED);
|
package/dist/agent.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type ChatSession } from './chat-session.js';
|
|
|
5
5
|
import type { McpServerInfo } from './mcp-config.js';
|
|
6
6
|
import { type JSONWebToken, type LLMGatewayClient } from '@salesforce/llm-gateway-sdk';
|
|
7
7
|
import type { AgentConnectivityResolver } from './agent-connectivity-resolver.js';
|
|
8
|
+
import type { AgentIdentityStore } from './internal/agent-identity-store.js';
|
|
8
9
|
import type { TelemetryRouter, TelemetrySlice } from './internal/telemetry-router.js';
|
|
9
10
|
import type { TelemetryBus, TelemetryEventCallback } from './types/telemetry-events.js';
|
|
10
11
|
/**
|
|
@@ -20,6 +21,10 @@ export type AgentParentBuses = {
|
|
|
20
21
|
* Each `Agent` wraps a single agent in the underlying harness. It
|
|
21
22
|
* provides agent-level operations (configuration, MCP, lifecycle) and serves
|
|
22
23
|
* as the factory for {@link ChatSession} instances.
|
|
24
|
+
*
|
|
25
|
+
* Harness-specific features (tool-search processor handles, workflows, etc.)
|
|
26
|
+
* are reached through {@link AgentManager.extensions}, not `Agent`. Per-agent
|
|
27
|
+
* accessors take the agent id as their first argument.
|
|
23
28
|
*/
|
|
24
29
|
export interface Agent {
|
|
25
30
|
/** Returns the unique agent identifier. */
|
|
@@ -106,6 +111,7 @@ export declare class DefaultAgent implements Agent {
|
|
|
106
111
|
private orgConnection;
|
|
107
112
|
private orgJwt;
|
|
108
113
|
private readonly agentConnectivityResolver;
|
|
114
|
+
private readonly identityStore;
|
|
109
115
|
private readonly sessions;
|
|
110
116
|
private readonly sessionSliceUnregisters;
|
|
111
117
|
private readonly router;
|
|
@@ -125,11 +131,13 @@ export declare class DefaultAgent implements Agent {
|
|
|
125
131
|
* @param orgConnection - Authenticated org connection carrying identity and env inference.
|
|
126
132
|
* @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
|
|
127
133
|
* @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
|
|
134
|
+
* @param identityStore - SDK-owned persistence for the `{ agentId, projectRoot, AgentConfig }` triple. The agent
|
|
135
|
+
* calls `write()` on a successful `updateAgentConfig` so disk state and in-memory state stay in lockstep.
|
|
128
136
|
* @param router - Telemetry router used to obtain session slices when sessions are created.
|
|
129
137
|
* @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
|
|
130
138
|
* @param parent - Manager's bus pair; this agent forwards its events upward into them.
|
|
131
139
|
*/
|
|
132
|
-
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, llmGatewayClient: LLMGatewayClient, orgConnection: OrgConnection, orgJwt: JSONWebToken, agentConnectivityResolver: AgentConnectivityResolver, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
|
|
140
|
+
constructor(harness: AgentHarness, agentId: string, projectRoot: string, config: AgentConfig, llmGatewayClient: LLMGatewayClient, orgConnection: OrgConnection, orgJwt: JSONWebToken, agentConnectivityResolver: AgentConnectivityResolver, identityStore: AgentIdentityStore, router: TelemetryRouter, inbound: TelemetrySlice, parent: AgentParentBuses, clock?: Clock, idGenerator?: UniqueIDGenerator);
|
|
133
141
|
/**
|
|
134
142
|
* @requirements
|
|
135
143
|
* - MUST return the agent's ID.
|
|
@@ -150,7 +158,9 @@ export declare class DefaultAgent implements Agent {
|
|
|
150
158
|
* - MUST guarantee that the `agentId` remains unchanged during the merge.
|
|
151
159
|
* - MUST destroy the existing agent in the harness by delegating to `this.harness.destroyAgent(this.getId())`.
|
|
152
160
|
* - MUST recreate the agent in the harness with the newly merged configuration by delegating to `this.harness.createAgent(...)`.
|
|
153
|
-
* - MUST
|
|
161
|
+
* - MUST persist the merged config via `this.identityStore.write(...)` after the harness recreate succeeds and
|
|
162
|
+
* before the in-memory swaps, so a write failure rolls back through the same catch path as a recreate failure.
|
|
163
|
+
* - MUST preserve the previous in-memory config state if recreation or persistence fails.
|
|
154
164
|
*/
|
|
155
165
|
updateAgentConfig(config?: AgentConfig, options?: {
|
|
156
166
|
abortSignal?: AbortSignal;
|
package/dist/agent.js
CHANGED
|
@@ -20,6 +20,7 @@ export class DefaultAgent {
|
|
|
20
20
|
orgConnection;
|
|
21
21
|
orgJwt;
|
|
22
22
|
agentConnectivityResolver;
|
|
23
|
+
identityStore;
|
|
23
24
|
sessions = new Map();
|
|
24
25
|
sessionSliceUnregisters = new Map();
|
|
25
26
|
router;
|
|
@@ -39,11 +40,13 @@ export class DefaultAgent {
|
|
|
39
40
|
* @param orgConnection - Authenticated org connection carrying identity and env inference.
|
|
40
41
|
* @param orgJwt - Self-refreshing JWT for the resolved org (used for MCP auth injection).
|
|
41
42
|
* @param agentConnectivityResolver - Used to re-resolve org connectivity when the org or model changes.
|
|
43
|
+
* @param identityStore - SDK-owned persistence for the `{ agentId, projectRoot, AgentConfig }` triple. The agent
|
|
44
|
+
* calls `write()` on a successful `updateAgentConfig` so disk state and in-memory state stay in lockstep.
|
|
42
45
|
* @param router - Telemetry router used to obtain session slices when sessions are created.
|
|
43
46
|
* @param inbound - Router slice delivering harness events routed to this agent (non-session-scoped).
|
|
44
47
|
* @param parent - Manager's bus pair; this agent forwards its events upward into them.
|
|
45
48
|
*/
|
|
46
|
-
constructor(harness, agentId, projectRoot, config, llmGatewayClient, orgConnection, orgJwt, agentConnectivityResolver, router, inbound, parent, clock = new RealClock(), idGenerator = new UUIDGenerator()) {
|
|
49
|
+
constructor(harness, agentId, projectRoot, config, llmGatewayClient, orgConnection, orgJwt, agentConnectivityResolver, identityStore, router, inbound, parent, clock = new RealClock(), idGenerator = new UUIDGenerator()) {
|
|
47
50
|
this.harness = harness;
|
|
48
51
|
this.agentId = agentId;
|
|
49
52
|
this.projectRoot = projectRoot;
|
|
@@ -52,6 +55,7 @@ export class DefaultAgent {
|
|
|
52
55
|
this.orgConnection = orgConnection;
|
|
53
56
|
this.orgJwt = orgJwt;
|
|
54
57
|
this.agentConnectivityResolver = agentConnectivityResolver;
|
|
58
|
+
this.identityStore = identityStore;
|
|
55
59
|
this.router = router;
|
|
56
60
|
this.clock = clock;
|
|
57
61
|
this.idGenerator = idGenerator;
|
|
@@ -93,7 +97,9 @@ export class DefaultAgent {
|
|
|
93
97
|
* - MUST guarantee that the `agentId` remains unchanged during the merge.
|
|
94
98
|
* - MUST destroy the existing agent in the harness by delegating to `this.harness.destroyAgent(this.getId())`.
|
|
95
99
|
* - MUST recreate the agent in the harness with the newly merged configuration by delegating to `this.harness.createAgent(...)`.
|
|
96
|
-
* - MUST
|
|
100
|
+
* - MUST persist the merged config via `this.identityStore.write(...)` after the harness recreate succeeds and
|
|
101
|
+
* before the in-memory swaps, so a write failure rolls back through the same catch path as a recreate failure.
|
|
102
|
+
* - MUST preserve the previous in-memory config state if recreation or persistence fails.
|
|
97
103
|
*/
|
|
98
104
|
async updateAgentConfig(config = {}, options) {
|
|
99
105
|
this.assertNotDisposed();
|
|
@@ -121,6 +127,10 @@ export class DefaultAgent {
|
|
|
121
127
|
await this.harness.destroyAgent(this.agentId);
|
|
122
128
|
try {
|
|
123
129
|
await this.harness.createAgent(this.agentId, this.projectRoot, nextClient, toHarnessConfig(nextConfig, nextOrgJwt), options);
|
|
130
|
+
// Persist before the in-memory swaps so a write failure flows through the same
|
|
131
|
+
// catch block as a recreate failure: the rollback restores the harness with
|
|
132
|
+
// previousConfig and disk state remains the pre-update record.
|
|
133
|
+
await this.identityStore.write(this.agentId, this.projectRoot, nextConfig);
|
|
124
134
|
this.config = nextConfig;
|
|
125
135
|
this.llmGatewayClient = nextClient;
|
|
126
136
|
this.orgConnection = nextConnection;
|
|
@@ -138,6 +148,12 @@ export class DefaultAgent {
|
|
|
138
148
|
if (nextClient === previousClient) {
|
|
139
149
|
previousClient.setModel(Models.getByName(previousModelName));
|
|
140
150
|
}
|
|
151
|
+
// Clear any nextConfig registration left behind by a successful harness recreate
|
|
152
|
+
// before the rollback createAgent runs. On the harness-recreate-failure path this
|
|
153
|
+
// is a no-op (the agent was never registered with nextConfig); on the
|
|
154
|
+
// identityStore.write-failure path it removes the live nextConfig so the rollback
|
|
155
|
+
// doesn't trip the harness's duplicate-registration guard.
|
|
156
|
+
await this.harness.destroyAgent(this.agentId);
|
|
141
157
|
await this.harness.createAgent(this.agentId, this.projectRoot, previousClient, toHarnessConfig(previousConfig, previousOrgJwt));
|
|
142
158
|
}
|
|
143
159
|
catch {
|
|
@@ -4,9 +4,42 @@ import type { ChatStreamResult } from '../types/events.js';
|
|
|
4
4
|
import type { Message } from '../types/messages.js';
|
|
5
5
|
import type { TelemetryEventCallback } from '../types/telemetry-events.js';
|
|
6
6
|
import type { ToolResultInfo } from '../types/tools.js';
|
|
7
|
-
import type { HarnessAgentConfig, StreamOptions } from './harness-config.js';
|
|
7
|
+
import type { AgentConfig, HarnessAgentConfig, StreamOptions } from './harness-config.js';
|
|
8
8
|
import type { LLMGatewayClient } from '@salesforce/llm-gateway-sdk';
|
|
9
9
|
export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly [1];
|
|
10
|
+
/**
|
|
11
|
+
* Opt-in helper that brands a harness type with the {@link AgentConfig}
|
|
12
|
+
* subtype it expects. Harness authors wrap their branded subtype with
|
|
13
|
+
* this helper when they want consumers to get the harness-specific
|
|
14
|
+
* config shape inferred at the `createAgent` call site:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* type MastraAgentHarness = WithAgentConfig<
|
|
18
|
+
* AgentHarness & {
|
|
19
|
+
* readonly harnessId: 'mastra';
|
|
20
|
+
* readonly extensions: { mastra: { ... } };
|
|
21
|
+
* },
|
|
22
|
+
* MastraAgentConfig
|
|
23
|
+
* >;
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The helper attaches an optional, type-only `__agentConfig` slot that
|
|
27
|
+
* never exists at runtime. {@link ConfigOf} reads it. Harnesses that
|
|
28
|
+
* don't need extra config fields don't use the helper, and the base
|
|
29
|
+
* `AgentHarness` interface stays free of the phantom.
|
|
30
|
+
*/
|
|
31
|
+
export type WithAgentConfig<H extends AgentHarness, C extends AgentConfig> = H & {
|
|
32
|
+
readonly __agentConfig?: C;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Resolves the {@link AgentConfig} subtype declared by a harness via the
|
|
36
|
+
* {@link WithAgentConfig} helper. Falls back to `AgentConfig` when the
|
|
37
|
+
* harness wasn't branded. Used by `AgentManager.createAgent` so consumers
|
|
38
|
+
* don't have to pass `<MastraAgentConfig>` at the call site.
|
|
39
|
+
*/
|
|
40
|
+
export type ConfigOf<H extends AgentHarness> = H extends {
|
|
41
|
+
readonly __agentConfig?: infer C;
|
|
42
|
+
} ? unknown extends C ? AgentConfig : C : AgentConfig;
|
|
10
43
|
/**
|
|
11
44
|
* Harness-agnostic interface abstracting the agentic runtime.
|
|
12
45
|
*
|
|
@@ -25,6 +58,17 @@ export interface AgentHarness {
|
|
|
25
58
|
readonly harnessId: string;
|
|
26
59
|
/** Version of the SDK-to-harness protocol implemented by this harness. */
|
|
27
60
|
readonly protocolVersion: number;
|
|
61
|
+
/**
|
|
62
|
+
* Harness-specific extensions namespace.
|
|
63
|
+
*
|
|
64
|
+
* Concrete harnesses narrow this to a typed shape (e.g.
|
|
65
|
+
* `MastraAgentHarness` declares `extensions.mastra.getToolSearchProcessor`).
|
|
66
|
+
* The SDK core never reads or interprets this object; it is a passthrough
|
|
67
|
+
* surface owned by the harness package. Consumers reach harness-specific
|
|
68
|
+
* features through `manager.extensions` (typed off the harness subtype);
|
|
69
|
+
* per-agent accessors take the agent id as their first argument.
|
|
70
|
+
*/
|
|
71
|
+
readonly extensions: Record<string, unknown>;
|
|
28
72
|
/**
|
|
29
73
|
* Shut down the harness gracefully, releasing all resources.
|
|
30
74
|
* Disconnects MCP servers, closes storage connections, and
|
|
@@ -73,6 +73,16 @@ export type HarnessAgentConfig = Omit<AgentConfig, 'orgAlias'> & {
|
|
|
73
73
|
* Strips `orgAlias` since org resolution is handled above the harness and attaches
|
|
74
74
|
* the resolved `orgJwt` so harness implementations can use {@link resolveMcpServerHeaders}
|
|
75
75
|
* to inject auth for Salesforce Hosted MCP Servers.
|
|
76
|
+
*
|
|
77
|
+
* **Contract — extra fields survive the round-trip.** Harness packages that brand
|
|
78
|
+
* their harness type via {@link WithAgentConfig} extend `AgentConfig` with extra
|
|
79
|
+
* fields (e.g. `MastraAgentConfig.toolSearch`). Those fields are not declared on
|
|
80
|
+
* `AgentConfig` or `HarnessAgentConfig`; they ride through the spread below and the
|
|
81
|
+
* harness reads them at its own boundary via runtime narrowing
|
|
82
|
+
* (`(config as { toolSearch? }).toolSearch`). The spread-pass-through is part of
|
|
83
|
+
* the contract — refactoring to `pick` known fields would silently drop those
|
|
84
|
+
* fields and break harness extensibility. There is a regression test in
|
|
85
|
+
* `test/harness/harness-config.test.ts` that asserts unknown fields survive.
|
|
76
86
|
*/
|
|
77
87
|
export declare function toHarnessConfig(config: AgentConfig, orgJwt?: JSONWebToken): HarnessAgentConfig;
|
|
78
88
|
/**
|
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
* Strips `orgAlias` since org resolution is handled above the harness and attaches
|
|
10
10
|
* the resolved `orgJwt` so harness implementations can use {@link resolveMcpServerHeaders}
|
|
11
11
|
* to inject auth for Salesforce Hosted MCP Servers.
|
|
12
|
+
*
|
|
13
|
+
* **Contract — extra fields survive the round-trip.** Harness packages that brand
|
|
14
|
+
* their harness type via {@link WithAgentConfig} extend `AgentConfig` with extra
|
|
15
|
+
* fields (e.g. `MastraAgentConfig.toolSearch`). Those fields are not declared on
|
|
16
|
+
* `AgentConfig` or `HarnessAgentConfig`; they ride through the spread below and the
|
|
17
|
+
* harness reads them at its own boundary via runtime narrowing
|
|
18
|
+
* (`(config as { toolSearch? }).toolSearch`). The spread-pass-through is part of
|
|
19
|
+
* the contract — refactoring to `pick` known fields would silently drop those
|
|
20
|
+
* fields and break harness extensibility. There is a regression test in
|
|
21
|
+
* `test/harness/harness-config.test.ts` that asserts unknown fields survive.
|
|
12
22
|
*/
|
|
13
23
|
export function toHarnessConfig(config, orgJwt) {
|
|
14
24
|
const { orgAlias: _, ...rest } = config;
|
|
@@ -12,10 +12,10 @@ import type { AgentHarness } from './agent-harness.js';
|
|
|
12
12
|
* guards against a factory that lies about its version or a harness package
|
|
13
13
|
* whose runtime drifts from its packaging metadata.
|
|
14
14
|
*/
|
|
15
|
-
export interface HarnessFactory {
|
|
15
|
+
export interface HarnessFactory<H extends AgentHarness = AgentHarness> {
|
|
16
16
|
/** Unique identifier for the harness type this factory builds (e.g., `'mastra'`). */
|
|
17
17
|
readonly harnessId: string;
|
|
18
18
|
/** SDK-to-harness protocol version implemented by the harness this factory builds. */
|
|
19
19
|
readonly protocolVersion: number;
|
|
20
|
-
create(storageRootFolder: string): Promise<
|
|
20
|
+
create(storageRootFolder: string): Promise<H>;
|
|
21
21
|
}
|
package/dist/harness/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AgentHarness } from './agent-harness.js';
|
|
1
|
+
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';
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type { ToolDefinition, ToolCallInfo, ToolResultInfo } from './types/tools
|
|
|
4
4
|
export type { FinishReason, UsageMetadata } from './types/usage.js';
|
|
5
5
|
export type { AgentConfig, HarnessAgentConfig, StreamOptions } from './harness/harness-config.js';
|
|
6
6
|
export { DEFAULT_MAX_STEPS } from './harness/harness-config.js';
|
|
7
|
-
export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpServerInfo, } from './mcp-config.js';
|
|
7
|
+
export type { MCPConfiguration, MCPServerConfig, MCPStdioServerConfig, MCPRemoteServerConfig, McpServerInfo, McpToolInfo, McpToolAnnotations, } from './mcp-config.js';
|
|
8
8
|
export { McpServerStatus } from './mcp-config.js';
|
|
9
9
|
export { ModelName } from '@salesforce/llm-gateway-sdk';
|
|
10
10
|
export { SfApiEnv } from '@salesforce/agentic-common';
|
|
@@ -12,7 +12,7 @@ export { type AgentManager, type RestoreFailure, createAgentManager } from './ag
|
|
|
12
12
|
export { type Agent } from './agent.js';
|
|
13
13
|
export { type ChatSession, type ChatOptions } from './chat-session.js';
|
|
14
14
|
export type { AgentConnectivityResolver, ResolvedConnectivity } from './agent-connectivity-resolver.js';
|
|
15
|
-
export type { AgentHarness, HarnessFactory } from './harness/index.js';
|
|
15
|
+
export type { AgentHarness, HarnessFactory, WithAgentConfig, ConfigOf } from './harness/index.js';
|
|
16
16
|
export { SUPPORTED_PROTOCOL_VERSIONS } from './harness/agent-harness.js';
|
|
17
17
|
export { HarnessBusOwner } from './harness/harness-bus-owner.js';
|
|
18
18
|
export { AgentSDKError, AgentSDKErrorType } from './errors.js';
|
package/dist/mcp-config.d.ts
CHANGED
|
@@ -43,10 +43,73 @@ export declare enum McpServerStatus {
|
|
|
43
43
|
Disabled = "disabled",
|
|
44
44
|
Error = "error"
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Behavioral / UI-presentation hints for an MCP-discovered tool.
|
|
48
|
+
*
|
|
49
|
+
* Mirrors the MCP protocol's `Tool.annotations` shape
|
|
50
|
+
* (https://spec.modelcontextprotocol.io/specification/server/tools/#tool-annotations)
|
|
51
|
+
* without importing `@modelcontextprotocol/sdk`, keeping this package
|
|
52
|
+
* harness-runtime-free. Each field is optional because MCP servers populate
|
|
53
|
+
* annotations à la carte; absence means "the server did not declare this
|
|
54
|
+
* hint," not "false."
|
|
55
|
+
*/
|
|
56
|
+
export type McpToolAnnotations = {
|
|
57
|
+
/** Human-readable label suitable for UI display (vs. the machine `name`). */
|
|
58
|
+
title?: string;
|
|
59
|
+
/** When `true`, the tool only reads data and has no side effects. */
|
|
60
|
+
readOnlyHint?: boolean;
|
|
61
|
+
/** When `true`, the tool may perform destructive updates to its environment. */
|
|
62
|
+
destructiveHint?: boolean;
|
|
63
|
+
/** When `true`, repeated calls with the same arguments have no additional effect. */
|
|
64
|
+
idempotentHint?: boolean;
|
|
65
|
+
/** When `true`, the tool may interact with an open world of external entities (e.g. the public web). */
|
|
66
|
+
openWorldHint?: boolean;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Runtime metadata for a single MCP-discovered tool.
|
|
70
|
+
*
|
|
71
|
+
* The optional fields are populated when the underlying harness can supply
|
|
72
|
+
* them from its MCP client. A harness whose MCP runtime does not expose a
|
|
73
|
+
* given field leaves it `undefined` — consumers must treat every field
|
|
74
|
+
* except `name` as optional.
|
|
75
|
+
*
|
|
76
|
+
* **Why no `outputSchema` field?** The MCP protocol's `tools/list` response
|
|
77
|
+
* carries an optional `outputSchema` per tool, and a maximally honest mirror
|
|
78
|
+
* of that protocol shape would expose it here. We deliberately do not, because
|
|
79
|
+
* neither harness today can populate it: Mastra's `@mastra/mcp` strips
|
|
80
|
+
* `outputSchema` from each wrapped tool before the harness sees it (a
|
|
81
|
+
* deliberate choice in Mastra's MCP client to keep `CallToolResult`
|
|
82
|
+
* validation correct — passing the schema to `createTool` would cause Zod
|
|
83
|
+
* to strip unrecognized keys from the envelope), and the Claude Agent SDK's
|
|
84
|
+
* MCP status surface omits the field entirely. Adding `outputSchema?` to the
|
|
85
|
+
* SDK contract today would mean shipping a field no harness fills — exactly
|
|
86
|
+
* the "field a consumer should ignore" anti-pattern called out in this
|
|
87
|
+
* package's design principles. If a future harness gains access to
|
|
88
|
+
* `outputSchema` (or one of the existing harnesses adds it), expanding the
|
|
89
|
+
* contract is a non-breaking additive change at that point.
|
|
90
|
+
*/
|
|
91
|
+
export type McpToolInfo = {
|
|
92
|
+
/** Tool name as exposed to the LLM, including any harness-applied namespacing. */
|
|
93
|
+
name: string;
|
|
94
|
+
/** Human-readable description of what the tool does. */
|
|
95
|
+
description?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Tool input parameters as a **JSON Schema** object (the MCP wire format).
|
|
98
|
+
*
|
|
99
|
+
* This is a plain JSON Schema, not a Zod schema. Consumers that want a
|
|
100
|
+
* Zod schema at runtime can convert with a library such as
|
|
101
|
+
* `json-schema-to-zod`; consumers that want runtime validation can feed
|
|
102
|
+
* it to AJV. Typed as `Record<string, unknown>` so this package incurs
|
|
103
|
+
* no `zod` or `@types/json-schema` dependency.
|
|
104
|
+
*/
|
|
105
|
+
inputSchema?: Record<string, unknown>;
|
|
106
|
+
/** Behavioral / UI-presentation hints declared by the MCP server. */
|
|
107
|
+
annotations?: McpToolAnnotations;
|
|
108
|
+
};
|
|
46
109
|
/** Runtime status of a configured MCP server, including its discovered tools. */
|
|
47
110
|
export type McpServerInfo = {
|
|
48
111
|
name: string;
|
|
49
112
|
status: McpServerStatus;
|
|
50
|
-
tools:
|
|
113
|
+
tools: McpToolInfo[];
|
|
51
114
|
error?: string;
|
|
52
115
|
};
|
package/dist/types/usage.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export type UsageMetadata = {
|
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* Reason the model stopped generating.
|
|
19
|
-
* Aligned with AI SDK
|
|
19
|
+
* Aligned with AI SDK V3's unified finish-reason set; harnesses normalize provider-specific
|
|
20
|
+
* shapes (e.g. V3's `LanguageModelV3FinishReason` object with `{ unified, raw }`) down to this
|
|
21
|
+
* union so SDK consumers see a stable string regardless of the underlying AI SDK version.
|
|
20
22
|
*
|
|
21
23
|
* - `stop` — The model finished generating naturally (complete response).
|
|
22
24
|
* - `length` — The model hit the maximum output token limit; the response was truncated mid-generation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/sfdx-agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Harness-agnostic agentic infrastructure for Salesforce developer experience tooling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -35,28 +35,28 @@
|
|
|
35
35
|
"LICENSE.txt"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@salesforce/agentic-common": "0.
|
|
39
|
-
"@salesforce/llm-gateway-sdk": "0.
|
|
38
|
+
"@salesforce/agentic-common": "0.4.0",
|
|
39
|
+
"@salesforce/llm-gateway-sdk": "0.4.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@eslint/js": "^10.0.1",
|
|
43
|
-
"@salesforce/sfdx-agent-harness-mastra": "0.
|
|
43
|
+
"@salesforce/sfdx-agent-harness-mastra": "0.6.0",
|
|
44
44
|
"@types/node": "^22.19.17",
|
|
45
|
-
"@vitest/coverage-istanbul": "^4.1.
|
|
46
|
-
"@vitest/eslint-plugin": "^1.6.
|
|
47
|
-
"eslint": "^10.
|
|
45
|
+
"@vitest/coverage-istanbul": "^4.1.7",
|
|
46
|
+
"@vitest/eslint-plugin": "^1.6.17",
|
|
47
|
+
"eslint": "^10.4.0",
|
|
48
48
|
"eslint-config-prettier": "^10.1.8",
|
|
49
49
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
50
50
|
"eslint-plugin-import": "^2.32.0",
|
|
51
51
|
"eslint-plugin-n": "^18.0.1",
|
|
52
52
|
"globals": "^17.6.0",
|
|
53
|
-
"lint-staged": "^17.0.
|
|
53
|
+
"lint-staged": "^17.0.5",
|
|
54
54
|
"prettier": "^3.8.3",
|
|
55
55
|
"rimraf": "^6.1.3",
|
|
56
|
-
"tsx": "^4.
|
|
56
|
+
"tsx": "^4.22.3",
|
|
57
57
|
"typescript": "^6.0.3",
|
|
58
|
-
"typescript-eslint": "^8.59.
|
|
59
|
-
"vitest": "^4.1.
|
|
58
|
+
"typescript-eslint": "^8.59.4",
|
|
59
|
+
"vitest": "^4.1.7"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": ">=22.19.0"
|