@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.
- package/CHANGELOG.md +37 -0
- package/LICENSE.txt +21 -0
- package/README.md +55 -0
- package/dist/gen-sink.d.ts +8 -0
- package/dist/gen-sink.js +13 -0
- package/dist/gen-sink.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-error-classifier.d.ts +36 -0
- package/dist/mcp-error-classifier.js +166 -0
- package/dist/mcp-error-classifier.js.map +1 -0
- package/dist/openai-agents-harness-factory.d.ts +36 -0
- package/dist/openai-agents-harness-factory.js +39 -0
- package/dist/openai-agents-harness-factory.js.map +1 -0
- package/dist/openai-agents-harness.d.ts +302 -0
- package/dist/openai-agents-harness.js +1014 -0
- package/dist/openai-agents-harness.js.map +1 -0
- package/dist/openai-approval-coordinator.d.ts +231 -0
- package/dist/openai-approval-coordinator.js +422 -0
- package/dist/openai-approval-coordinator.js.map +1 -0
- package/dist/openai-built-in-policies.d.ts +29 -0
- package/dist/openai-built-in-policies.js +33 -0
- package/dist/openai-built-in-policies.js.map +1 -0
- package/dist/openai-event-adapter.d.ts +119 -0
- package/dist/openai-event-adapter.js +322 -0
- package/dist/openai-event-adapter.js.map +1 -0
- package/dist/openai-mcp-config-mapper.d.ts +58 -0
- package/dist/openai-mcp-config-mapper.js +133 -0
- package/dist/openai-mcp-config-mapper.js.map +1 -0
- package/dist/openai-mcp-state.d.ts +67 -0
- package/dist/openai-mcp-state.js +6 -0
- package/dist/openai-mcp-state.js.map +1 -0
- package/dist/openai-message-mapper.d.ts +79 -0
- package/dist/openai-message-mapper.js +374 -0
- package/dist/openai-message-mapper.js.map +1 -0
- package/dist/openai-model-provider.d.ts +46 -0
- package/dist/openai-model-provider.js +144 -0
- package/dist/openai-model-provider.js.map +1 -0
- package/dist/openai-session-store.d.ts +149 -0
- package/dist/openai-session-store.js +328 -0
- package/dist/openai-session-store.js.map +1 -0
- package/dist/openai-tool-mapper.d.ts +121 -0
- package/dist/openai-tool-mapper.js +231 -0
- package/dist/openai-tool-mapper.js.map +1 -0
- package/dist/openai-tool-redaction.d.ts +55 -0
- package/dist/openai-tool-redaction.js +82 -0
- package/dist/openai-tool-redaction.js.map +1 -0
- package/dist/test/tsconfig.tsbuildinfo +1 -0
- package/dist/text-stream.d.ts +30 -0
- package/dist/text-stream.js +103 -0
- package/dist/text-stream.js.map +1 -0
- 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":"AAAA;;;GAGG;AAMH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,kBAAkB;IA2BE;IA1BrB,SAAS,GAAG,CAAC,CAAC;IACd,OAAO,GAAG,KAAK,CAAC;IAChB,aAAa,CAA4B;IAEjD;;;;;;;;;;OAUG;IACK,kBAAkB,CAAwB;IAClD,oFAAoF;IAC5E,kBAAkB,GAAG,CAAC,CAAC;IAE/B;;;;;;OAMG;IACH,YAA6B,UAAiD;QAAjD,eAAU,GAAV,UAAU,CAAuC;IAAG,CAAC;IAElF;;;OAGG;IACH,GAAG,CAAC,KAAqB;QACrB,MAAM,GAAG,GAAgB,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAChC,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,wBAAwB,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YAChD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,uEAAuE;QACvE,OAAO,GAAG,CAAC;IACf,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACK,UAAU,CAAC,KAAyB,EAAE,GAAgB;QAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAyB,CAAC;YAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC;YAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC;YAC7C,IAAI,QAAQ,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO;YAC/D,GAAG,CAAC,IAAI,CAAC;gBACL,IAAI,EAAE,WAAW;gBACjB,UAAU;gBACV,QAAQ;gBACR,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC;gBAC9B,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;aAC3B,CAAC,CAAC;QACP,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,IAA2B,CAAC;YAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;YAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC;YAC7C,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC;YAC1B,IAAI,QAAQ,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO;YAC/D,GAAG,CAAC,IAAI,CAAC;gBACL,IAAI,EAAE,aAAa;gBACnB,UAAU;gBACV,QAAQ;gBACR,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC;gBAClD,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;aAC3B,CAAC,CAAC;YACH,kEAAkE;YAClE,0DAA0D;YAC1D,yDAAyD;YACzD,IAAI,IAAI,CAAC,kBAAkB,GAAG,CAAC,EAAE,CAAC;gBAC9B,IAAI,CAAC,kBAAkB,IAAI,CAAC,CAAC;gBAC7B,IAAI,IAAI,CAAC,kBAAkB,KAAK,CAAC;oBAAE,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC;YACzE,CAAC;QACL,CAAC;QACD,wEAAwE;QACxE,sEAAsE;QACtE,WAAW;IACf,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,QAAgB;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QACnC,OAAO;YACH,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,YAAY,EAAE,QAAQ;YACtB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjF,CAAC;IACN,CAAC;IAED;;;OAGG;IACH,SAAS;QACL,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,aAAa;QACT,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/B,CAAC;IAEO,MAAM,CAAC,IAAiB,EAAE,GAAgB;QAC9C,yEAAyE;QACzE,sEAAsE;QACtE,0DAA0D;QAC1D,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,mBAAmB,CAAC,CAAC,CAAC;gBACvB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;gBACnD,MAAM;YACV,CAAC;YACD,KAAK,kBAAkB,CAAC,CAAC,CAAC;gBACtB,gEAAgE;gBAChE,+DAA+D;gBAC/D,kEAAkE;gBAClE,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC;gBAClC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBAC5D,MAAM;YACV,CAAC;YACD,KAAK,eAAe,CAAC,CAAC,CAAC;gBACnB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBAC7C,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;gBAC3B,MAAM,UAAU,GAAc;oBAC1B,IAAI,EAAE,aAAa;oBACnB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,gEAAgE;oBAChE,mEAAmE;oBACnE,YAAY,EAAE,MAAM;oBACpB,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5C,CAAC;gBACF,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;gBACpB,8DAA8D;gBAC9D,+DAA+D;gBAC/D,kEAAkE;gBAClE,2DAA2D;gBAC3D,MAAM,aAAa,GAAG,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAChE,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;oBACpB,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC;oBACrC,IAAI,CAAC,kBAAkB,GAAG,aAAa,CAAC;gBAC5C,CAAC;qBAAM,CAAC;oBACJ,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACzB,CAAC;gBACD,MAAM;YACV,CAAC;YACD,oEAAoE;YACpE,4BAA4B;QAChC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,uBAAuB,CAAC,GAAgB;QACpC,IAAI,IAAI,CAAC,kBAAkB,KAAK,SAAS;YAAE,OAAO;QAClD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAClC,IAAI,CAAC,kBAAkB,GAAG,SAAS,CAAC;QACpC,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,mFAAmF;IACnF,uBAAuB;QACnB,MAAM,GAAG,GAAgB,EAAE,CAAC;QAC5B,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC;QAClC,OAAO,GAAG,CAAC;IACf,CAAC;CACJ;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAoC;IACzD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IACxC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,MAAM,iBAAiB,GAAG,UAAU,CAAC,KAAK,CAAC,kBAAkB,EAAE,eAAe,CAAC,CAAC;IAChF,MAAM,eAAe,GAAG,UAAU,CAAC,KAAK,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;IAElF,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,IAAI,WAAW;QAAE,MAAM,CAAC,WAAW,GAAG,WAAW,CAAC;IAClD,IAAI,YAAY;QAAE,MAAM,CAAC,YAAY,GAAG,YAAY,CAAC;IACrD,IAAI,WAAW;QAAE,MAAM,CAAC,WAAW,GAAG,WAAW,CAAC;IAClD,IAAI,iBAAiB;QAAE,MAAM,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;IACpE,IAAI,eAAe;QAAE,MAAM,CAAC,eAAe,GAAG,eAAe,CAAC;IAE9D,qEAAqE;IACrE,sEAAsE;IACtE,6CAA6C;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAWD;;;;GAIG;AACH,SAAS,UAAU,CACf,OAA2E,EAC3E,GAAW;IAEX,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC7D,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,GAAG,IAAI,KAAK,CAAC;YACb,IAAI,GAAG,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAClC,CAAC;AAoBD;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,MAAe;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,CAAC;IACrC,OAAO,MAAM,CAAC,MAAM,CAChB,CAAC,IAAI,EAA4B,EAAE,CAC/B,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAK,IAA2B,CAAC,IAAI,KAAK,eAAe,CACzG,CAAC,MAAM,CAAC;AACb,CAAC;AAED,kFAAkF;AAClF,SAAS,SAAS,CAAC,GAAY;IAC3B,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACvC,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpG,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,MAAe;IACpC,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAK,MAA6B,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAClG,OAAQ,MAA6B,CAAC,IAAI,IAAI,EAAE,CAAC;IACrD,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"openai-mcp-state.js","sourceRoot":"","sources":["../src/openai-mcp-state.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|