@salesforce/sfdx-agent-harness-openai 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE.txt +21 -0
  3. package/README.md +55 -0
  4. package/dist/gen-sink.d.ts +8 -0
  5. package/dist/gen-sink.js +13 -0
  6. package/dist/gen-sink.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +16 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/mcp-error-classifier.d.ts +36 -0
  11. package/dist/mcp-error-classifier.js +166 -0
  12. package/dist/mcp-error-classifier.js.map +1 -0
  13. package/dist/openai-agents-harness-factory.d.ts +36 -0
  14. package/dist/openai-agents-harness-factory.js +39 -0
  15. package/dist/openai-agents-harness-factory.js.map +1 -0
  16. package/dist/openai-agents-harness.d.ts +302 -0
  17. package/dist/openai-agents-harness.js +1014 -0
  18. package/dist/openai-agents-harness.js.map +1 -0
  19. package/dist/openai-approval-coordinator.d.ts +231 -0
  20. package/dist/openai-approval-coordinator.js +422 -0
  21. package/dist/openai-approval-coordinator.js.map +1 -0
  22. package/dist/openai-built-in-policies.d.ts +29 -0
  23. package/dist/openai-built-in-policies.js +33 -0
  24. package/dist/openai-built-in-policies.js.map +1 -0
  25. package/dist/openai-event-adapter.d.ts +119 -0
  26. package/dist/openai-event-adapter.js +322 -0
  27. package/dist/openai-event-adapter.js.map +1 -0
  28. package/dist/openai-mcp-config-mapper.d.ts +58 -0
  29. package/dist/openai-mcp-config-mapper.js +133 -0
  30. package/dist/openai-mcp-config-mapper.js.map +1 -0
  31. package/dist/openai-mcp-state.d.ts +67 -0
  32. package/dist/openai-mcp-state.js +6 -0
  33. package/dist/openai-mcp-state.js.map +1 -0
  34. package/dist/openai-message-mapper.d.ts +79 -0
  35. package/dist/openai-message-mapper.js +374 -0
  36. package/dist/openai-message-mapper.js.map +1 -0
  37. package/dist/openai-model-provider.d.ts +46 -0
  38. package/dist/openai-model-provider.js +144 -0
  39. package/dist/openai-model-provider.js.map +1 -0
  40. package/dist/openai-session-store.d.ts +149 -0
  41. package/dist/openai-session-store.js +328 -0
  42. package/dist/openai-session-store.js.map +1 -0
  43. package/dist/openai-tool-mapper.d.ts +121 -0
  44. package/dist/openai-tool-mapper.js +231 -0
  45. package/dist/openai-tool-mapper.js.map +1 -0
  46. package/dist/openai-tool-redaction.d.ts +55 -0
  47. package/dist/openai-tool-redaction.js +82 -0
  48. package/dist/openai-tool-redaction.js.map +1 -0
  49. package/dist/test/tsconfig.tsbuildinfo +1 -0
  50. package/dist/text-stream.d.ts +30 -0
  51. package/dist/text-stream.js +103 -0
  52. package/dist/text-stream.js.map +1 -0
  53. package/package.json +66 -0
@@ -0,0 +1,322 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ /**
6
+ * Per-stream stateful mapper from `@openai/agents` `RunStreamEvent`s to the
7
+ * SDK's `ChatEvent` variants. A new instance is constructed per `stream()` call
8
+ * because it tracks `stepIndex` and the last-seen per-step usage.
9
+ *
10
+ * **Text + tool events.** The run loop emits three event families:
11
+ * `raw_model_stream_event` (the underlying model stream, carrying
12
+ * `output_text_delta` / `response_started` / `response_done`),
13
+ * `run_item_stream_event` (tool calls / outputs), and
14
+ * `agent_updated_stream_event` (handoffs — not used). This adapter maps the
15
+ * text and step-boundary events plus the `tool_called` / `tool_output` run-item
16
+ * events to `tool-call` / `tool-result` ChatEvents. The terminal `finish` is
17
+ * synthesized by the harness pump when the run stream completes, since the run
18
+ * loop does not emit a dedicated "run finished" event and mid-stream errors are
19
+ * thrown, not emitted.
20
+ *
21
+ * The adapter never emits an event whose `type` is not a member of the
22
+ * `ChatEvent` union. Unrecognized events are skipped.
23
+ */
24
+ export class OpenAIEventAdapter {
25
+ mcpCatalog;
26
+ stepIndex = 0;
27
+ started = false;
28
+ lastStepUsage;
29
+ /**
30
+ * A `step-finish` buffered because its step produced tool calls. The
31
+ * `@openai/agents` run loop emits `response_done` for a tool-calling step
32
+ * BEFORE the `tool_called` / `tool_output` run-items for that step, so
33
+ * emitting `step-finish` inline would place the `tool-result` AFTER its
34
+ * step's `step-finish` — outside the step. We hold the `step-finish` until
35
+ * the step's tool outputs drain (or a new step starts / the run ends), so a
36
+ * `tool-result` always lands inside the `step-start`/`step-finish` bracket
37
+ * that produced it. Mirrors the Claude adapter's deferred-`step-finish`
38
+ * ordering (`pendingToolUseIds`).
39
+ */
40
+ deferredStepFinish;
41
+ /** Outstanding `tool_output`s for the deferred step; `step-finish` flushes at 0. */
42
+ pendingToolOutputs = 0;
43
+ /**
44
+ * Optional MCP tool catalog (bare tool name → `{ serverName, annotations }`),
45
+ * used to enrich `tool-call` / `tool-result` events for MCP-sourced tools
46
+ * with `serverName` / `bareToolName` / `annotations`. Read live so a mid-turn
47
+ * discovery settle enriches later events. `undefined` (or a miss) leaves the
48
+ * fields unset — the contract for consumer / built-in tools.
49
+ */
50
+ constructor(mcpCatalog) {
51
+ this.mcpCatalog = mcpCatalog;
52
+ }
53
+ /**
54
+ * Map one `RunStreamEvent` to zero or more `ChatEvent`s. Emits a synthetic
55
+ * `start` before the first mapped event.
56
+ */
57
+ map(event) {
58
+ const out = [];
59
+ if (!this.started) {
60
+ this.started = true;
61
+ out.push({ type: 'start' });
62
+ }
63
+ if (event.type === 'raw_model_stream_event') {
64
+ this.mapRaw(event.data, out);
65
+ }
66
+ else if (event.type === 'run_item_stream_event') {
67
+ this.mapRunItem(event, out);
68
+ }
69
+ // `agent_updated_stream_event` (handoffs) is not used by this harness.
70
+ return out;
71
+ }
72
+ /**
73
+ * Map a `run_item_stream_event` to a `tool-call` / `tool-result` ChatEvent.
74
+ * The run loop fires `tool_called` with a `RunToolCallItem` (the model's
75
+ * request) and `tool_output` with a `RunToolCallOutputItem` (the result).
76
+ *
77
+ * **MCP enrichment.** When the tool's (bare) name is in the {@link mcpCatalog},
78
+ * the event carries `serverName` / `bareToolName` / `annotations` so a consumer
79
+ * can build a `{ type: 'mcp', serverName, toolName }` policy matcher without
80
+ * string-splitting the display name. Consumer / built-in tools miss the
81
+ * catalog and surface with `serverName === undefined`, per the SDK contract.
82
+ * `isError` is not carried on the live `tool-result` event — the OpenAI
83
+ * `tool_output` item has no `isError` field; a consumer-reported failure is
84
+ * stamped onto the persisted record instead (see the session store). This
85
+ * maps the raw call/result the loop emits so a tool turn is observable and
86
+ * round-trips through history.
87
+ */
88
+ mapRunItem(event, out) {
89
+ if (event.name === 'tool_called') {
90
+ const item = event.item;
91
+ const raw = item.rawItem ?? {};
92
+ const toolName = item.toolName ?? raw.name;
93
+ const toolCallId = item.callId ?? raw.callId;
94
+ if (toolName === undefined || toolCallId === undefined)
95
+ return;
96
+ out.push({
97
+ type: 'tool-call',
98
+ toolCallId,
99
+ toolName,
100
+ args: parseArgs(raw.arguments),
101
+ ...this.enrich(toolName),
102
+ });
103
+ }
104
+ else if (event.name === 'tool_output') {
105
+ const item = event.item;
106
+ const raw = item.rawItem ?? {};
107
+ const toolCallId = item.callId ?? raw.callId;
108
+ const toolName = raw.name;
109
+ if (toolName === undefined || toolCallId === undefined)
110
+ return;
111
+ out.push({
112
+ type: 'tool-result',
113
+ toolCallId,
114
+ toolName,
115
+ result: normalizeOutput(item.output ?? raw.output),
116
+ ...this.enrich(toolName),
117
+ });
118
+ // The tool-result is now emitted INSIDE its step. Once every tool
119
+ // output of the deferred step has drained, flush the held
120
+ // `step-finish` so it closes the step after its results.
121
+ if (this.pendingToolOutputs > 0) {
122
+ this.pendingToolOutputs -= 1;
123
+ if (this.pendingToolOutputs === 0)
124
+ this.flushDeferredStepFinish(out);
125
+ }
126
+ }
127
+ // Other run-item names (message_output_created, reasoning_item_created,
128
+ // handoff_*, tool_search_*, tool_approval_requested) are not mapped —
129
+ // skipped.
130
+ }
131
+ /**
132
+ * Look up the (bare) tool name in the MCP catalog and return the enrichment
133
+ * fields for a `tool-call` / `tool-result` event, or an empty object when the
134
+ * tool is not MCP-sourced. `bareToolName` equals the display `toolName` here
135
+ * because `@openai/agents` registers MCP tools under their bare name.
136
+ */
137
+ enrich(toolName) {
138
+ const entry = this.mcpCatalog?.get(toolName);
139
+ if (entry === undefined)
140
+ return {};
141
+ return {
142
+ serverName: entry.serverName,
143
+ bareToolName: toolName,
144
+ ...(entry.annotations !== undefined ? { annotations: entry.annotations } : {}),
145
+ };
146
+ }
147
+ /**
148
+ * The last per-step usage observed, used to populate the terminal `finish`
149
+ * event the pump synthesizes when the run stream completes cleanly.
150
+ */
151
+ lastUsage() {
152
+ return this.lastStepUsage;
153
+ }
154
+ /**
155
+ * Emit a synthetic `start` if the stream produced no events at all (e.g. a
156
+ * turn that failed before the first delta). Called by the pump on the error
157
+ * path so a consumer always sees a `start` before the terminal pair.
158
+ */
159
+ ensureStarted() {
160
+ if (this.started)
161
+ return [];
162
+ this.started = true;
163
+ return [{ type: 'start' }];
164
+ }
165
+ mapRaw(data, out) {
166
+ // The raw model stream is the OpenAI Agents SDK's protocol `StreamEvent`
167
+ // union: `output_text_delta` | `response_started` | `response_done` |
168
+ // generic item events. We map the three text/step shapes.
169
+ switch (data.type) {
170
+ case 'output_text_delta': {
171
+ out.push({ type: 'text-delta', text: data.delta });
172
+ break;
173
+ }
174
+ case 'response_started': {
175
+ // Defensive: if a prior step's tool outputs never fully drained
176
+ // (a tool call with no matching `tool_output`), flush its held
177
+ // `step-finish` before opening the next step so steps never nest.
178
+ this.flushDeferredStepFinish(out);
179
+ out.push({ type: 'step-start', stepIndex: this.stepIndex });
180
+ break;
181
+ }
182
+ case 'response_done': {
183
+ const usage = mapUsage(data.response?.usage);
184
+ this.lastStepUsage = usage;
185
+ const stepFinish = {
186
+ type: 'step-finish',
187
+ stepIndex: this.stepIndex,
188
+ // A completed model response is reported as a natural stop; the
189
+ // step-level `finishReason` does not distinguish a tool-call step.
190
+ finishReason: 'stop',
191
+ ...(usage !== undefined ? { usage } : {}),
192
+ };
193
+ this.stepIndex += 1;
194
+ // A tool-calling step emits its `tool_called` / `tool_output`
195
+ // run-items AFTER this `response_done`. Hold the `step-finish`
196
+ // until those outputs drain so the `tool-result` lands inside the
197
+ // step; a step with no tool calls closes inline as before.
198
+ const toolCallCount = countFunctionCalls(data.response?.output);
199
+ if (toolCallCount > 0) {
200
+ this.deferredStepFinish = stepFinish;
201
+ this.pendingToolOutputs = toolCallCount;
202
+ }
203
+ else {
204
+ out.push(stepFinish);
205
+ }
206
+ break;
207
+ }
208
+ // Generic item events (reasoning, tool-search, etc.) are not mapped
209
+ // to a ChatEvent — skipped.
210
+ }
211
+ }
212
+ /**
213
+ * Emit a held `step-finish` (if any) and reset the deferral state. Called
214
+ * when the step's tool outputs have drained, when a new step starts, and by
215
+ * the pump before the terminal `finish` so a deferred step-finish can never
216
+ * be dropped.
217
+ */
218
+ flushDeferredStepFinish(out) {
219
+ if (this.deferredStepFinish === undefined)
220
+ return;
221
+ out.push(this.deferredStepFinish);
222
+ this.deferredStepFinish = undefined;
223
+ this.pendingToolOutputs = 0;
224
+ }
225
+ /** Emit any held `step-finish` as a standalone event list (pump terminal path). */
226
+ drainDeferredStepFinish() {
227
+ const out = [];
228
+ this.flushDeferredStepFinish(out);
229
+ return out;
230
+ }
231
+ }
232
+ /**
233
+ * Maps the OpenAI Agents SDK's `response_done` usage shape (camelCase
234
+ * `inputTokens` / `outputTokens` / `totalTokens`, plus optional
235
+ * `inputTokensDetails` / `outputTokensDetails`) onto the SDK's
236
+ * {@link UsageMetadata}. Returns `undefined` when no usage was reported at all,
237
+ * so consumers see an honest "usage not reported" signal rather than a fake
238
+ * all-zero object (the missing-usage-faithfulness contract, W-22692131).
239
+ */
240
+ export function mapUsage(usage) {
241
+ if (usage === undefined)
242
+ return undefined;
243
+ const inputTokens = usage.inputTokens;
244
+ const outputTokens = usage.outputTokens;
245
+ const totalTokens = usage.totalTokens;
246
+ const cachedInputTokens = pickDetail(usage.inputTokensDetails, 'cached_tokens');
247
+ const reasoningTokens = pickDetail(usage.outputTokensDetails, 'reasoning_tokens');
248
+ const mapped = {};
249
+ if (inputTokens)
250
+ mapped.inputTokens = inputTokens;
251
+ if (outputTokens)
252
+ mapped.outputTokens = outputTokens;
253
+ if (totalTokens)
254
+ mapped.totalTokens = totalTokens;
255
+ if (cachedInputTokens)
256
+ mapped.cachedInputTokens = cachedInputTokens;
257
+ if (reasoningTokens)
258
+ mapped.reasoningTokens = reasoningTokens;
259
+ // If every field is falsy the stream reported no usage — collapse to
260
+ // undefined so a downstream `getContextUsage` doesn't clobber a prior
261
+ // reading with a populated-but-empty object.
262
+ return Object.keys(mapped).length > 0 ? mapped : undefined;
263
+ }
264
+ /**
265
+ * Reads a numeric detail (e.g. `cached_tokens`, `reasoning_tokens`) from the
266
+ * details shape, which may be a single record or an array of records (the SDK
267
+ * types it as a union). Sums across array entries.
268
+ */
269
+ function pickDetail(details, key) {
270
+ if (details === undefined)
271
+ return undefined;
272
+ const records = Array.isArray(details) ? details : [details];
273
+ let sum = 0;
274
+ let seen = false;
275
+ for (const record of records) {
276
+ const value = record[key];
277
+ if (typeof value === 'number') {
278
+ sum += value;
279
+ seen = true;
280
+ }
281
+ }
282
+ return seen ? sum : undefined;
283
+ }
284
+ /**
285
+ * Count the `function_call` items on a `response_done` output. A tool-calling
286
+ * step carries one `function_call` per tool the model requested; the run loop
287
+ * then emits a matching `tool_output` run-item per call after the response
288
+ * completes. The count tells the adapter how many `tool_output`s to wait for
289
+ * before flushing the step's deferred `step-finish`.
290
+ */
291
+ function countFunctionCalls(output) {
292
+ if (!Array.isArray(output))
293
+ return 0;
294
+ return output.filter((item) => typeof item === 'object' && item !== null && item.type === 'function_call').length;
295
+ }
296
+ /** Parse a `function_call` item's JSON `arguments` string into an args object. */
297
+ function parseArgs(raw) {
298
+ if (typeof raw !== 'string')
299
+ return {};
300
+ try {
301
+ const parsed = JSON.parse(raw);
302
+ return typeof parsed === 'object' && parsed !== null ? parsed : {};
303
+ }
304
+ catch {
305
+ return {};
306
+ }
307
+ }
308
+ /**
309
+ * Normalize a tool result's `output` (a bare string, a `{ type:'text'; text }`
310
+ * object, or another structured shape) to the value carried on the SDK
311
+ * `tool-result` ChatEvent's `result`. Mirrors the message mapper's read-path
312
+ * normalization so a result surfaces identically on the stream and in history.
313
+ */
314
+ function normalizeOutput(output) {
315
+ if (typeof output === 'string')
316
+ return output;
317
+ if (typeof output === 'object' && output !== null && output.type === 'text') {
318
+ return output.text ?? '';
319
+ }
320
+ return output;
321
+ }
322
+ //# sourceMappingURL=openai-event-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-event-adapter.js","sourceRoot":"","sources":["../src/openai-event-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { JSONWebToken } from '@salesforce/agentic-common';
2
+ import { type MCPConfiguration, type MCPServerConfig } from '@salesforce/sfdx-agent-sdk';
3
+ import { MCPServerStdio, MCPServerStreamableHttp, type MCPServer } from '@openai/agents';
4
+ /**
5
+ * The `@openai/agents` MCP server constructor options. The SDK exports the
6
+ * server classes from its top-level entry but not their option interfaces, so
7
+ * we derive each from its constructor rather than reaching through a `dist/`
8
+ * deep import (which the SDK-import invariant forbids).
9
+ */
10
+ type MCPServerStdioOptions = ConstructorParameters<typeof MCPServerStdio>[0];
11
+ type MCPServerStreamableHttpOptions = ConstructorParameters<typeof MCPServerStreamableHttp>[0];
12
+ /**
13
+ * Returns `true` if `config` contains at least one server that is not explicitly
14
+ * disabled. A server is enabled when its `enabled` property is `true` or absent.
15
+ * Mirrors the sibling harnesses' `hasEnabledServers` guard so a config of all
16
+ * disabled servers allocates no `@openai/agents` MCP client.
17
+ */
18
+ export declare function hasEnabledServers(config: MCPConfiguration | undefined): config is MCPConfiguration;
19
+ /** A discriminated, ready-to-construct options bag for one `@openai/agents` MCP server. */
20
+ export type OpenAIMcpServerOptions = {
21
+ readonly kind: 'stdio';
22
+ readonly options: MCPServerStdioOptions;
23
+ } | {
24
+ readonly kind: 'remote';
25
+ readonly options: MCPServerStreamableHttpOptions;
26
+ };
27
+ /**
28
+ * Map one SDK {@link McpServerConfig} to the plain `@openai/agents` constructor
29
+ * options bag — WITHOUT constructing the client. Splitting the pure mapping from
30
+ * the instance construction serves two callers:
31
+ *
32
+ * 1. {@link mapToOpenAIMcpServers} constructs the live clients from it.
33
+ * 2. The conformance seam (`readReconnectionOptions`) reads the forwarded
34
+ * `reconnectionOptions` off `.options` deterministically — no reliance on the
35
+ * constructed `MCPServer` surfacing the field, mirroring how the Mastra seam
36
+ * reads its mapped server-definition object rather than the client.
37
+ *
38
+ * Every server carries `cacheToolsList: true` and `name: <configKey>` — the
39
+ * #541 keystone: the tool-list cache lives on the instance and is keyed by
40
+ * `server.name`, so a rebuilt `Agent` reusing the same connected instance skips
41
+ * `tools/list`.
42
+ *
43
+ * Remote servers get an auth-injecting `fetch` (via {@link resolveMcpServerHeaders})
44
+ * when `orgJwt` is supplied, so a rotating Salesforce JWT lands per request; the
45
+ * config's own `headers` ride through `requestInit`. Stdio env vars are frozen
46
+ * at spawn (the stdio transport has no per-request hook — see
47
+ * `MCPStdioServerConfig.env`).
48
+ */
49
+ export declare function toOpenAIMcpServerOptions(name: string, config: MCPServerConfig, orgJwt?: JSONWebToken, innerFetch?: typeof fetch): OpenAIMcpServerOptions;
50
+ /**
51
+ * Converts an {@link MCPConfiguration} into live `@openai/agents` {@link MCPServer}
52
+ * instances keyed by config server name. Servers with `enabled === false` are
53
+ * omitted so no client is constructed for them. Construction does no I/O
54
+ * (`@openai/agents` connects lazily); the harness owns the `connect()` /
55
+ * `close()` lifecycle.
56
+ */
57
+ export declare function mapToOpenAIMcpServers(config: MCPConfiguration, orgJwt?: JSONWebToken, innerFetch?: typeof fetch): Map<string, MCPServer>;
58
+ export {};
@@ -0,0 +1,133 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ import { resolveMcpServerHeaders } from '@salesforce/sfdx-agent-sdk';
6
+ import { MCPServerStdio, MCPServerStreamableHttp } from '@openai/agents';
7
+ /**
8
+ * MCP SDK's built-in `StreamableHTTPReconnectionOptions` defaults. This is a
9
+ * byte-identical copy of the Mastra harness's `DEFAULT_RECONNECTION_OPTIONS`
10
+ * (`mcp-config-mapper.ts`) and the Claude harness's copy
11
+ * (`mcp-client-factory.ts`) — cross-harness imports are lint-blocked
12
+ * (`harnessIsolationPack`), so the constant is duplicated on purpose and the
13
+ * shared `@salesforce/harness-conformance` `RECONNECTION_DEFAULTS` table is the
14
+ * drift-guard that keeps the three in lockstep.
15
+ *
16
+ * The field-wise merge below (`{ ...DEFAULT_RECONNECTION_OPTIONS, ...input }`)
17
+ * is required because the underlying transport replaces the entire defaults
18
+ * object when `reconnectionOptions` is set — a partial override would otherwise
19
+ * zero out the unspecified fields.
20
+ */
21
+ const DEFAULT_RECONNECTION_OPTIONS = {
22
+ maxRetries: 2,
23
+ initialReconnectionDelay: 1000,
24
+ maxReconnectionDelay: 30000,
25
+ reconnectionDelayGrowFactor: 1.5,
26
+ };
27
+ /**
28
+ * Returns `true` if `config` contains at least one server that is not explicitly
29
+ * disabled. A server is enabled when its `enabled` property is `true` or absent.
30
+ * Mirrors the sibling harnesses' `hasEnabledServers` guard so a config of all
31
+ * disabled servers allocates no `@openai/agents` MCP client.
32
+ */
33
+ export function hasEnabledServers(config) {
34
+ if (!config)
35
+ return false;
36
+ return Object.values(config).some((s) => s.enabled !== false);
37
+ }
38
+ /**
39
+ * Map one SDK {@link McpServerConfig} to the plain `@openai/agents` constructor
40
+ * options bag — WITHOUT constructing the client. Splitting the pure mapping from
41
+ * the instance construction serves two callers:
42
+ *
43
+ * 1. {@link mapToOpenAIMcpServers} constructs the live clients from it.
44
+ * 2. The conformance seam (`readReconnectionOptions`) reads the forwarded
45
+ * `reconnectionOptions` off `.options` deterministically — no reliance on the
46
+ * constructed `MCPServer` surfacing the field, mirroring how the Mastra seam
47
+ * reads its mapped server-definition object rather than the client.
48
+ *
49
+ * Every server carries `cacheToolsList: true` and `name: <configKey>` — the
50
+ * #541 keystone: the tool-list cache lives on the instance and is keyed by
51
+ * `server.name`, so a rebuilt `Agent` reusing the same connected instance skips
52
+ * `tools/list`.
53
+ *
54
+ * Remote servers get an auth-injecting `fetch` (via {@link resolveMcpServerHeaders})
55
+ * when `orgJwt` is supplied, so a rotating Salesforce JWT lands per request; the
56
+ * config's own `headers` ride through `requestInit`. Stdio env vars are frozen
57
+ * at spawn (the stdio transport has no per-request hook — see
58
+ * `MCPStdioServerConfig.env`).
59
+ */
60
+ export function toOpenAIMcpServerOptions(name, config, orgJwt, innerFetch = fetch) {
61
+ if (config.type === 'stdio') {
62
+ return {
63
+ kind: 'stdio',
64
+ options: {
65
+ name,
66
+ command: config.command,
67
+ cacheToolsList: true,
68
+ ...(config.args !== undefined ? { args: config.args } : {}),
69
+ ...(config.env !== undefined ? { env: config.env } : {}),
70
+ ...(config.timeout !== undefined ? { timeout: config.timeout } : {}),
71
+ },
72
+ };
73
+ }
74
+ if (config.type === 'remote') {
75
+ const url = String(config.url);
76
+ return {
77
+ kind: 'remote',
78
+ options: {
79
+ name,
80
+ url,
81
+ cacheToolsList: true,
82
+ ...(config.timeout !== undefined ? { timeout: config.timeout } : {}),
83
+ ...(config.reconnectionOptions !== undefined
84
+ ? { reconnectionOptions: { ...DEFAULT_RECONNECTION_OPTIONS, ...config.reconnectionOptions } }
85
+ : {}),
86
+ // When an orgJwt is present, inject fresh auth per request for
87
+ // Salesforce Platform URLs (a no-op passthrough for other hosts);
88
+ // the config's static headers ride through `requestInit`.
89
+ ...(orgJwt !== undefined ? { fetch: createAuthFetch(url, orgJwt, innerFetch, config.headers) } : {}),
90
+ ...(config.headers !== undefined ? { requestInit: { headers: config.headers } } : {}),
91
+ },
92
+ };
93
+ }
94
+ // Exhaustiveness guard: a new discriminant must be handled here explicitly.
95
+ throw new Error(`Unsupported MCP server config type "${config.type ?? 'unknown'}". ` +
96
+ 'Update toOpenAIMcpServerOptions to handle this type.');
97
+ }
98
+ /**
99
+ * Converts an {@link MCPConfiguration} into live `@openai/agents` {@link MCPServer}
100
+ * instances keyed by config server name. Servers with `enabled === false` are
101
+ * omitted so no client is constructed for them. Construction does no I/O
102
+ * (`@openai/agents` connects lazily); the harness owns the `connect()` /
103
+ * `close()` lifecycle.
104
+ */
105
+ export function mapToOpenAIMcpServers(config, orgJwt, innerFetch = fetch) {
106
+ const servers = new Map();
107
+ for (const [name, serverConfig] of Object.entries(config)) {
108
+ if (serverConfig.enabled === false)
109
+ continue;
110
+ const mapped = toOpenAIMcpServerOptions(name, serverConfig, orgJwt, innerFetch);
111
+ const server = mapped.kind === 'stdio' ? new MCPServerStdio(mapped.options) : new MCPServerStreamableHttp(mapped.options);
112
+ servers.set(name, server);
113
+ }
114
+ return servers;
115
+ }
116
+ /**
117
+ * Build a `fetch` that resolves fresh MCP auth headers per request. Copied from
118
+ * the Mastra harness's `createAuthFetch` — {@link resolveMcpServerHeaders}
119
+ * injects the rotating JWT only for Salesforce Platform MCP URLs and passes
120
+ * every other URL through unchanged, so this is safe to wire on any remote
121
+ * server whenever an `orgJwt` is available.
122
+ */
123
+ function createAuthFetch(serverUrl, orgJwt, innerFetch, existingHeaders) {
124
+ return async (url, init) => {
125
+ const authHeaders = await resolveMcpServerHeaders(serverUrl, orgJwt, existingHeaders);
126
+ const merged = new Headers(init?.headers);
127
+ for (const [key, value] of Object.entries(authHeaders)) {
128
+ merged.set(key, value);
129
+ }
130
+ return innerFetch(url, { ...init, headers: merged });
131
+ };
132
+ }
133
+ //# sourceMappingURL=openai-mcp-config-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-mcp-config-mapper.js","sourceRoot":"","sources":["../src/openai-mcp-config-mapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { MCPServer } from '@openai/agents';
2
+ import type { MCPServerConfig, McpServerErrorDetail, McpServerStatus, McpToolAnnotations, McpToolInfo } from '@salesforce/sfdx-agent-sdk';
3
+ /**
4
+ * Per-agent, per-server MCP state the harness tracks, keyed by the server name
5
+ * (the key in `AgentConfig.mcpServers`). Holds the live `@openai/agents`
6
+ * {@link MCPServer} instance — the unit `updateAgent` preserves when the config
7
+ * is structurally unchanged (#541) — alongside the discovery snapshot
8
+ * `getMcpServerInfo` reads.
9
+ *
10
+ * Deliberately simpler than the Mastra harness's `McpServerState`:
11
+ * - **No `epoch` counter.** Orphaned writes from a superseded discovery are
12
+ * guarded by an instance-identity check (`state.mcpServers.get(name)?.server
13
+ * === capturedServer`) rather than a monotonic epoch — a cycle always
14
+ * constructs a fresh instance, so identity is sufficient.
15
+ * - **No `httpErrorCapture`.** The `@mastra/mcp` transport discards the HTTP
16
+ * status on its SSE-fallback path (#617), forcing Mastra to wrap the fetch to
17
+ * recover it; the `@openai/agents` `MCPServerStreamableHttp` transport does
18
+ * not have that wart, so the classifier works from the thrown error's
19
+ * message/code alone (and the same message/code feeds the discovery telemetry).
20
+ * - **No `settled` flag.** Folded into {@link status} (`Connecting` until the
21
+ * background discovery resolves to `Connected` or `Error`).
22
+ */
23
+ export type OpenAIMcpServerState = {
24
+ /**
25
+ * The live `@openai/agents` MCP client. Harness-owned (the SDK never closes
26
+ * it independently); a rebuilt `Agent` reusing this same connected instance
27
+ * skips `tools/list` because `cacheToolsList: true` caches on the instance.
28
+ * This is the preserve unit for the #541 `updateAgent` contract.
29
+ */
30
+ readonly server: MCPServer;
31
+ /**
32
+ * The `MCPServerConfig` that produced this instance. Retained so
33
+ * `updateAgent` can diff the next config against it via
34
+ * `mcpServerConfigEqual` to decide preserve vs. cycle.
35
+ */
36
+ config: MCPServerConfig;
37
+ /** Source-of-truth connection status for `getMcpServerInfo`. */
38
+ status: McpServerStatus;
39
+ /** Discovered tools (bare names) as `McpToolInfo[]`; populated on discovery settle. */
40
+ tools: McpToolInfo[];
41
+ /** Sanitized, single-line error message; set when {@link status} is `Error`. */
42
+ error?: string;
43
+ /** Structured error projection for programmatic routing; set alongside {@link error}. */
44
+ errorDetail?: McpServerErrorDetail;
45
+ /**
46
+ * The in-flight (or settled) background discovery promise. Awaited by
47
+ * `destroyAgent` / `shutdown` before `close()` so teardown never races an
48
+ * in-flight `connect()` / `listTools()`.
49
+ */
50
+ ready: Promise<void>;
51
+ };
52
+ /**
53
+ * One entry in the per-agent MCP tool catalog: the mapping from a bare tool name
54
+ * to its originating server + declared annotations. The per-turn event adapter
55
+ * reads this to enrich `tool-call` / `tool-result` ChatEvents with `serverName`
56
+ * / `bareToolName` / `annotations` for MCP-sourced tools.
57
+ *
58
+ * The `@openai/agents` native MCP attach registers tools under their **bare**
59
+ * name (no `${server}_` namespacing), so the catalog key IS the bare tool name
60
+ * and equals the display `toolName` on the run-item events.
61
+ */
62
+ export type McpCatalogEntry = {
63
+ /** The originating MCP server name (the `AgentConfig.mcpServers` key). */
64
+ readonly serverName: string;
65
+ /** Behavioral / UI-presentation hints the server declared for the tool, when present. */
66
+ readonly annotations?: McpToolAnnotations;
67
+ };
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=openai-mcp-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-mcp-state.js","sourceRoot":"","sources":["../src/openai-mcp-state.ts"],"names":[],"mappings":"AAAA;;;GAGG"}