@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,79 @@
1
+ import { type Message, type MessagePart } from '@salesforce/sfdx-agent-sdk';
2
+ import type { SessionRecord } from './openai-session-store.js';
3
+ /**
4
+ * Bridges the `@openai/agents` flat `AgentInputItem[]` history and the SDK's
5
+ * `Message[]` contract in both directions.
6
+ *
7
+ * **Read** ({@link recordsToMessages}): the OpenAI run loop persists a completed
8
+ * tool call as a `function_call` item and a separate `function_call_result` item
9
+ * sharing `callId`. Each item maps to its own `Message`;
10
+ * the shared {@link splitToolResultsIntoToolMessages} normalizer then produces
11
+ * the canonical two-message layout (`assistant`(tool-call) + separate
12
+ * `role:'tool'`(tool-result), #647). Running the shared normalizer — rather than
13
+ * hand-emitting the layout — is the single-site contract that keeps the three
14
+ * harnesses from drifting. `createdAt` rides on the {@link SessionRecord}; a
15
+ * tool result's `isError` rides on the record too and is restored onto the
16
+ * `tool-result` part.
17
+ *
18
+ * **Write** ({@link messagesToRecords}): the `addContext` path. Each `Message`
19
+ * part lowers to a flat item — a `tool-call` part → `function_call`, a
20
+ * `tool-result` part → `function_call_result`, text/reasoning → message /
21
+ * reasoning items. OpenAI stores these separately natively, so — unlike the
22
+ * Mastra harness, which coalesces call+result into one object and needs
23
+ * `mergeToolResultsIntoAssistant` on write — no merge step is required; the split
24
+ * IS the native representation, and a `getMessages()` → `addContext()` round-trip
25
+ * is lossless.
26
+ *
27
+ * **Supported parts:** text, reasoning, and tool parts. Multimodal
28
+ * (`image` / `file`) parts are rejected with `INVALID_MESSAGE_CONTENT`, matching
29
+ * the text-only stream input the harness accepts; multimodal is not yet
30
+ * implemented.
31
+ */
32
+ /**
33
+ * Map persisted records to the canonical `Message[]` layout. Does NOT sort — the
34
+ * caller sorts ascending by `createdAt` after the normalizer (the #464 read
35
+ * contract), so the normalizer's inherited-`createdAt` tool messages land
36
+ * adjacent to their originating call under a stable sort.
37
+ */
38
+ export declare function recordsToMessages(records: SessionRecord[]): Message[];
39
+ /**
40
+ * Lower `Message[]` (already `createdAt`-backfilled by the caller) to
41
+ * {@link SessionRecord}s. Each message's parts expand to one or more flat items;
42
+ * every record inherits its source message's `createdAt`, so a message that
43
+ * expands to a `function_call` + `function_call_result` pair keeps them adjacent
44
+ * under the read-side stable sort (equal `createdAt` → insertion order).
45
+ */
46
+ export declare function messagesToRecords(messages: Message[]): SessionRecord[];
47
+ /**
48
+ * Render a {@link MessagePart} as a plain-text transcript fragment. Byte-identical
49
+ * to the Mastra and Claude harnesses' copies — the shared
50
+ * `@salesforce/harness-conformance` `TRANSCRIPT_FIXTURES` table is the drift-guard
51
+ * (cross-harness imports are lint-blocked, so each harness keeps its own copy).
52
+ *
53
+ * Tool args/results render verbatim (the compaction char budget is the safety
54
+ * net, not per-part truncation). Image/file parts render as a size-aware
55
+ * placeholder and NEVER inline their base64 blob.
56
+ */
57
+ export declare function partToTranscriptText(part: MessagePart): string;
58
+ /**
59
+ * Maximum flattened-transcript length (in characters) sent to the model in a
60
+ * single `compactThread` summarization call. Transcripts above this size are
61
+ * split into roughly-equal chunks, each chunk is summarized separately, and the
62
+ * concatenated chunk-summaries are then summarized into the final summary.
63
+ *
64
+ * ~400k characters maps to ~100k tokens at a conservative 4:1 chars-per-token
65
+ * ratio, leaving headroom under typical 200k-token model context windows for the
66
+ * prompt scaffolding and the model's own response. A byte-identical copy of the
67
+ * Mastra and Claude harnesses' constant — cross-harness imports are lint-blocked,
68
+ * so each harness keeps its own copy.
69
+ */
70
+ export declare const MAX_TRANSCRIPT_CHARS = 400000;
71
+ /**
72
+ * Splits a string into ≤ `maxChars`-sized chunks, preferring line boundaries so
73
+ * message turns are not torn across chunks. Falls back to a hard slice if a
74
+ * single line itself exceeds the budget. A byte-identical copy of the sibling
75
+ * harnesses' `splitIntoChunks` (drift-guarded socially, not by a shared table —
76
+ * the transcript conformance fixtures cover `partToTranscriptText`, not the
77
+ * chunker).
78
+ */
79
+ export declare function splitIntoChunks(text: string, maxChars: number): string[];
@@ -0,0 +1,374 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ import { AgentSDKError, AgentSDKErrorType } from '@salesforce/sfdx-agent-sdk';
6
+ import { splitToolResultsIntoToolMessages } from '@salesforce/sfdx-agent-sdk/harness';
7
+ /**
8
+ * Bridges the `@openai/agents` flat `AgentInputItem[]` history and the SDK's
9
+ * `Message[]` contract in both directions.
10
+ *
11
+ * **Read** ({@link recordsToMessages}): the OpenAI run loop persists a completed
12
+ * tool call as a `function_call` item and a separate `function_call_result` item
13
+ * sharing `callId`. Each item maps to its own `Message`;
14
+ * the shared {@link splitToolResultsIntoToolMessages} normalizer then produces
15
+ * the canonical two-message layout (`assistant`(tool-call) + separate
16
+ * `role:'tool'`(tool-result), #647). Running the shared normalizer — rather than
17
+ * hand-emitting the layout — is the single-site contract that keeps the three
18
+ * harnesses from drifting. `createdAt` rides on the {@link SessionRecord}; a
19
+ * tool result's `isError` rides on the record too and is restored onto the
20
+ * `tool-result` part.
21
+ *
22
+ * **Write** ({@link messagesToRecords}): the `addContext` path. Each `Message`
23
+ * part lowers to a flat item — a `tool-call` part → `function_call`, a
24
+ * `tool-result` part → `function_call_result`, text/reasoning → message /
25
+ * reasoning items. OpenAI stores these separately natively, so — unlike the
26
+ * Mastra harness, which coalesces call+result into one object and needs
27
+ * `mergeToolResultsIntoAssistant` on write — no merge step is required; the split
28
+ * IS the native representation, and a `getMessages()` → `addContext()` round-trip
29
+ * is lossless.
30
+ *
31
+ * **Supported parts:** text, reasoning, and tool parts. Multimodal
32
+ * (`image` / `file`) parts are rejected with `INVALID_MESSAGE_CONTENT`, matching
33
+ * the text-only stream input the harness accepts; multimodal is not yet
34
+ * implemented.
35
+ */
36
+ // ── Read path ────────────────────────────────────────────────────────────────
37
+ /**
38
+ * Map persisted records to the canonical `Message[]` layout. Does NOT sort — the
39
+ * caller sorts ascending by `createdAt` after the normalizer (the #464 read
40
+ * contract), so the normalizer's inherited-`createdAt` tool messages land
41
+ * adjacent to their originating call under a stable sort.
42
+ */
43
+ export function recordsToMessages(records) {
44
+ const mapped = [];
45
+ records.forEach((record, index) => {
46
+ const message = itemToMessage(record, index);
47
+ if (message !== undefined)
48
+ mapped.push(message);
49
+ });
50
+ return splitToolResultsIntoToolMessages(mapped);
51
+ }
52
+ /**
53
+ * Map one persisted record to a `Message`, or `undefined` for an item type with
54
+ * no SDK-message equivalent (compaction markers, hosted-tool calls, etc.) so
55
+ * history retrieval never crashes on an unmapped upstream item.
56
+ */
57
+ function itemToMessage(record, index) {
58
+ const item = record.item;
59
+ const createdAt = new Date(record.createdAt);
60
+ // Message items carry a `role`; every other item type is discriminated on
61
+ // `type`. (A `function_call` has no `role`, so this order is unambiguous.)
62
+ if (item.role === 'user' || item.role === 'assistant' || item.role === 'system') {
63
+ return messageItemToMessage(item, item.role, createdAt, index);
64
+ }
65
+ switch (item.type) {
66
+ case 'function_call': {
67
+ if (item.callId === undefined || item.name === undefined)
68
+ return undefined;
69
+ const part = {
70
+ type: 'tool-call',
71
+ toolCallId: item.callId,
72
+ toolName: item.name,
73
+ args: parseArgs(item.arguments),
74
+ };
75
+ return { id: item.id ?? `toolcall-${item.callId}`, role: 'assistant', content: [part], createdAt };
76
+ }
77
+ case 'function_call_result': {
78
+ if (item.callId === undefined || item.name === undefined)
79
+ return undefined;
80
+ const part = {
81
+ type: 'tool-result',
82
+ toolCallId: item.callId,
83
+ toolName: item.name,
84
+ result: normalizeToolOutput(item.output),
85
+ ...(record.isError === true ? { isError: true } : {}),
86
+ };
87
+ // Emit on a non-`tool` message so the shared normalizer hoists it
88
+ // onto its own trailing `role:'tool'` message (the single-site
89
+ // layout contract). Assistant is the pre-normalizer carrier here;
90
+ // the normalizer drops this now-empty message and emits the tool one.
91
+ return { id: item.id ?? `toolresult-${item.callId}`, role: 'assistant', content: [part], createdAt };
92
+ }
93
+ case 'reasoning': {
94
+ const text = readTextContent(item.content);
95
+ return {
96
+ id: item.id ?? `reasoning-${index}`,
97
+ role: 'assistant',
98
+ content: [{ type: 'reasoning', text }],
99
+ createdAt,
100
+ };
101
+ }
102
+ default:
103
+ // Unmapped item type (hosted_tool_call, computer_call, compaction,
104
+ // unknown, …): drop it from history rather than surface an
105
+ // un-renderable message.
106
+ return undefined;
107
+ }
108
+ }
109
+ function messageItemToMessage(item, role, createdAt, index) {
110
+ const id = item.id ?? `msg-${index}`;
111
+ if (typeof item.content === 'string') {
112
+ return { id, role, content: item.content, createdAt };
113
+ }
114
+ const parts = [];
115
+ for (const c of item.content ?? []) {
116
+ if (typeof c !== 'object' || c === null)
117
+ continue;
118
+ const block = c;
119
+ // Both input (`input_text`) and output (`output_text`) text carry `text`.
120
+ if ((block.type === 'input_text' || block.type === 'output_text') && typeof block.text === 'string') {
121
+ parts.push({ type: 'text', text: block.text });
122
+ }
123
+ else if (block.type === 'refusal' && typeof block.refusal === 'string') {
124
+ parts.push({ type: 'text', text: block.refusal });
125
+ }
126
+ // Non-text content (image/audio/file) is not mapped.
127
+ }
128
+ // Collapse a lone text part to string content (the shape consumers wrote and
129
+ // the shape #464's history test asserts via `typeof content === 'string'`).
130
+ if (parts.length === 1 && parts[0].type === 'text') {
131
+ return { id, role, content: parts[0].text, createdAt };
132
+ }
133
+ return { id, role, content: parts, createdAt };
134
+ }
135
+ /** Join the `text` fields of a `reasoning` item's content array. */
136
+ function readTextContent(content) {
137
+ if (typeof content === 'string' || content === undefined)
138
+ return typeof content === 'string' ? content : '';
139
+ return content
140
+ .map((c) => typeof c === 'object' && c !== null && typeof c.text === 'string'
141
+ ? c.text
142
+ : '')
143
+ .join('');
144
+ }
145
+ // ── Write path (addContext) ────────────────────────────────────────────────────
146
+ /**
147
+ * Lower `Message[]` (already `createdAt`-backfilled by the caller) to
148
+ * {@link SessionRecord}s. Each message's parts expand to one or more flat items;
149
+ * every record inherits its source message's `createdAt`, so a message that
150
+ * expands to a `function_call` + `function_call_result` pair keeps them adjacent
151
+ * under the read-side stable sort (equal `createdAt` → insertion order).
152
+ */
153
+ export function messagesToRecords(messages) {
154
+ const records = [];
155
+ for (const message of messages) {
156
+ if (message.createdAt === undefined) {
157
+ // Internal contract: the caller (`addContext`) runs the shared
158
+ // `backfillCreatedAt` before reaching this mapper, so every message
159
+ // carries a `createdAt`. Throwing here surfaces a missing-backfill
160
+ // bug at its real call site rather than persisting a record whose
161
+ // timestamp would break the `getMessages` ascending sort. Mirrors
162
+ // Mastra's `mapMessageToMastra` guard.
163
+ throw new AgentSDKError(`messagesToRecords: message "${message.id}" is missing createdAt (caller must backfill before mapping).`, AgentSDKErrorType.INVALID_MESSAGE_CONTENT);
164
+ }
165
+ const createdAt = message.createdAt.toISOString();
166
+ for (const record of messageToItems(message)) {
167
+ records.push({ ...record, createdAt });
168
+ }
169
+ }
170
+ return records;
171
+ }
172
+ /** Lower one message to flat items, dropping `createdAt` (added by the caller). */
173
+ function messageToItems(message) {
174
+ if (typeof message.content === 'string') {
175
+ return [{ item: buildMessageItem(message.role, message.content) }];
176
+ }
177
+ const out = [];
178
+ const textRun = [];
179
+ const flushText = () => {
180
+ if (textRun.length === 0)
181
+ return;
182
+ out.push({ item: buildMessageItem(message.role, textRun.join('')) });
183
+ textRun.length = 0;
184
+ };
185
+ for (const part of message.content) {
186
+ switch (part.type) {
187
+ case 'text':
188
+ textRun.push(part.text);
189
+ break;
190
+ case 'reasoning':
191
+ flushText();
192
+ out.push({
193
+ item: { type: 'reasoning', content: [{ type: 'input_text', text: part.text }] },
194
+ });
195
+ break;
196
+ case 'tool-call':
197
+ flushText();
198
+ out.push({
199
+ item: {
200
+ type: 'function_call',
201
+ callId: part.toolCallId,
202
+ name: part.toolName,
203
+ arguments: JSON.stringify(part.args ?? {}),
204
+ status: 'completed',
205
+ },
206
+ });
207
+ break;
208
+ case 'tool-result':
209
+ flushText();
210
+ out.push({
211
+ item: {
212
+ type: 'function_call_result',
213
+ callId: part.toolCallId,
214
+ name: part.toolName,
215
+ status: 'completed',
216
+ output: toOutputString(part.result),
217
+ },
218
+ ...(part.isError === true ? { isError: true } : {}),
219
+ });
220
+ break;
221
+ case 'image':
222
+ case 'file':
223
+ throw new AgentSDKError(`The OpenAI harness does not yet support "${part.type}" message parts in addContext (multimodal is not yet implemented).`, AgentSDKErrorType.INVALID_MESSAGE_CONTENT);
224
+ }
225
+ }
226
+ flushText();
227
+ return out;
228
+ }
229
+ /** Build the message-shaped item for a role + plain text content. */
230
+ function buildMessageItem(role, text) {
231
+ switch (role) {
232
+ case 'assistant':
233
+ return {
234
+ role: 'assistant',
235
+ status: 'completed',
236
+ content: [{ type: 'output_text', text }],
237
+ };
238
+ case 'system':
239
+ return { role: 'system', content: text };
240
+ case 'user':
241
+ case 'tool':
242
+ // A `role:'tool'` message carries only tool-result parts (handled
243
+ // above); its own text content, if any, lowers to a user turn so the
244
+ // model sees it. Plain user text is the common case.
245
+ return { role: 'user', content: text };
246
+ }
247
+ }
248
+ // ── Transcript rendering (compaction + conformance drift-guard) ────────────────
249
+ /**
250
+ * Render a {@link MessagePart} as a plain-text transcript fragment. Byte-identical
251
+ * to the Mastra and Claude harnesses' copies — the shared
252
+ * `@salesforce/harness-conformance` `TRANSCRIPT_FIXTURES` table is the drift-guard
253
+ * (cross-harness imports are lint-blocked, so each harness keeps its own copy).
254
+ *
255
+ * Tool args/results render verbatim (the compaction char budget is the safety
256
+ * net, not per-part truncation). Image/file parts render as a size-aware
257
+ * placeholder and NEVER inline their base64 blob.
258
+ */
259
+ export function partToTranscriptText(part) {
260
+ switch (part.type) {
261
+ case 'text':
262
+ case 'reasoning':
263
+ return part.text;
264
+ case 'tool-call': {
265
+ const argsStr = JSON.stringify(part.args);
266
+ return `[tool-call: ${part.toolName}(${argsStr})]`;
267
+ }
268
+ case 'tool-result': {
269
+ const resultStr = typeof part.result === 'string' ? part.result : JSON.stringify(part.result);
270
+ return `[tool-result: ${part.toolName} → ${resultStr}]`;
271
+ }
272
+ case 'image':
273
+ case 'file': {
274
+ const name = part.fileName ? `, name="${part.fileName}"` : '';
275
+ return `[${part.type}: ${part.mimeType}${name}, ~${formatApproxSize(part.data)}]`;
276
+ }
277
+ }
278
+ }
279
+ /**
280
+ * Approximate decoded size of base64 `data` as a short human-readable string
281
+ * (`768 B` / `1.2 KB` / `15.0 MB`). Uses the `length * 0.75` approximation the
282
+ * gateway SDK uses; the transcript only needs a ballpark, never the exact count.
283
+ */
284
+ function formatApproxSize(base64Data) {
285
+ const bytes = Math.floor(base64Data.length * 0.75);
286
+ if (bytes < 1024)
287
+ return `${bytes} B`;
288
+ const kib = bytes / 1024;
289
+ if (kib < 1024)
290
+ return `${kib.toFixed(1)} KB`;
291
+ return `${(kib / 1024).toFixed(1)} MB`;
292
+ }
293
+ /**
294
+ * Maximum flattened-transcript length (in characters) sent to the model in a
295
+ * single `compactThread` summarization call. Transcripts above this size are
296
+ * split into roughly-equal chunks, each chunk is summarized separately, and the
297
+ * concatenated chunk-summaries are then summarized into the final summary.
298
+ *
299
+ * ~400k characters maps to ~100k tokens at a conservative 4:1 chars-per-token
300
+ * ratio, leaving headroom under typical 200k-token model context windows for the
301
+ * prompt scaffolding and the model's own response. A byte-identical copy of the
302
+ * Mastra and Claude harnesses' constant — cross-harness imports are lint-blocked,
303
+ * so each harness keeps its own copy.
304
+ */
305
+ export const MAX_TRANSCRIPT_CHARS = 400_000;
306
+ /**
307
+ * Splits a string into ≤ `maxChars`-sized chunks, preferring line boundaries so
308
+ * message turns are not torn across chunks. Falls back to a hard slice if a
309
+ * single line itself exceeds the budget. A byte-identical copy of the sibling
310
+ * harnesses' `splitIntoChunks` (drift-guarded socially, not by a shared table —
311
+ * the transcript conformance fixtures cover `partToTranscriptText`, not the
312
+ * chunker).
313
+ */
314
+ export function splitIntoChunks(text, maxChars) {
315
+ const chunks = [];
316
+ const lines = text.split('\n');
317
+ let current = '';
318
+ for (const line of lines) {
319
+ if (line.length > maxChars) {
320
+ if (current.length > 0) {
321
+ chunks.push(current);
322
+ current = '';
323
+ }
324
+ for (let i = 0; i < line.length; i += maxChars) {
325
+ chunks.push(line.slice(i, i + maxChars));
326
+ }
327
+ continue;
328
+ }
329
+ const candidate = current.length === 0 ? line : current + '\n' + line;
330
+ if (candidate.length > maxChars) {
331
+ chunks.push(current);
332
+ current = line;
333
+ }
334
+ else {
335
+ current = candidate;
336
+ }
337
+ }
338
+ if (current.length > 0) {
339
+ chunks.push(current);
340
+ }
341
+ return chunks;
342
+ }
343
+ /** Parse a `function_call` item's JSON `arguments` string into an args object. */
344
+ function parseArgs(raw) {
345
+ if (typeof raw !== 'string')
346
+ return {};
347
+ try {
348
+ const parsed = JSON.parse(raw);
349
+ return typeof parsed === 'object' && parsed !== null ? parsed : {};
350
+ }
351
+ catch {
352
+ return {};
353
+ }
354
+ }
355
+ /**
356
+ * Normalize a `function_call_result` `output` (which the protocol types as
357
+ * `string | { type:'text'; text } | image | file | array`) to the value carried
358
+ * on the SDK `tool-result` part. A bare string or `{ type:'text' }` collapses to
359
+ * its text; any other structured shape passes through as-is (`result` is
360
+ * `unknown` on the SDK contract, so consumers keep whatever the tool produced).
361
+ */
362
+ function normalizeToolOutput(output) {
363
+ if (typeof output === 'string')
364
+ return output;
365
+ if (typeof output === 'object' && output !== null && output.type === 'text') {
366
+ return output.text ?? '';
367
+ }
368
+ return output;
369
+ }
370
+ /** Coerce a `tool-result` part's `result` to the string `output` an item stores. */
371
+ function toOutputString(result) {
372
+ return typeof result === 'string' ? result : JSON.stringify(result);
373
+ }
374
+ //# sourceMappingURL=openai-message-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-message-mapper.js","sourceRoot":"","sources":["../src/openai-message-mapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { type ModelProvider } from '@openai/agents';
2
+ import { type Clock } from '@salesforce/agentic-common';
3
+ import type { ModelConnectivityInfo, WireCommunicationEvent } from '@salesforce/sfdx-agent-sdk';
4
+ /**
5
+ * Per-request wire-communication hooks. When supplied to
6
+ * {@link buildOpenAIModelProvider}, the custom fetch emits one
7
+ * {@link WireCommunicationEvent} `llm-request` before each outbound call and one
8
+ * `llm-response` after the upstream replies (or the call rejects). The harness
9
+ * forwards each event onto its `HarnessBusOwner.wireBus`, which is what
10
+ * `AgentManager.onWireCommunication` aggregates. Omit to disable emission
11
+ * entirely (zero cost — the fetch takes its plain header-merge path).
12
+ */
13
+ export type WireHooks = {
14
+ /** Called once with each emitted event (the harness's disposal-guarded emitter). */
15
+ emitWireCommunication: (event: WireCommunicationEvent) => void;
16
+ /** Time source for the event `timestamp`s; defaults to a fresh `RealClock`. */
17
+ clock?: Clock;
18
+ };
19
+ /**
20
+ * Builds a `@openai/agents` {@link ModelProvider} bound to the live
21
+ * {@link ModelConnectivityInfo} bag.
22
+ *
23
+ * The provider constructs one `OpenAI` client pointed at `info.baseUrl` with a
24
+ * custom `fetch` that merges `await info.getHeaders()` into every outbound
25
+ * request — the per-request JWT-rotation seam.
26
+ * `getInfo` is read on each `getModel()` call so a within-shape `modelId` swap
27
+ * or JWT rotation between turns lands without rebuilding anything; the fetch
28
+ * wrapper additionally re-reads `getHeaders()` per HTTP call, so credential
29
+ * rotation lands on the next request even within a single turn.
30
+ *
31
+ * HTTP transport is pinned via {@link setOpenAIResponsesTransport} so the custom
32
+ * `fetch` is the only wire seam — a WebSocket transport would bypass the
33
+ * per-request header hook and auth would not rotate. That single wire seam is
34
+ * also where wire-communication events emit (when {@link WireHooks} is supplied).
35
+ *
36
+ * Mirrors the shipping Mastra `pass-through-openai.ts` connectivity seam; the
37
+ * only difference is that this harness hands the client to the OpenAI Agents
38
+ * SDK's own `OpenAIProvider` rather than wrapping it in a `LanguageModelV3`.
39
+ */
40
+ export declare function buildOpenAIModelProvider(opts: {
41
+ getInfo: () => ModelConnectivityInfo;
42
+ /** Proxy-aware inner fetch from the factory (honors HTTPS_PROXY / NO_PROXY). */
43
+ innerFetch?: typeof fetch;
44
+ /** Wire-communication emission hooks. Omit to disable emission. */
45
+ wireHooks?: WireHooks;
46
+ }): ModelProvider;
@@ -0,0 +1,144 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ import OpenAI from 'openai';
6
+ import { OpenAIProvider, setOpenAIResponsesTransport } from '@openai/agents';
7
+ import { RealClock } from '@salesforce/agentic-common';
8
+ /**
9
+ * Builds a `@openai/agents` {@link ModelProvider} bound to the live
10
+ * {@link ModelConnectivityInfo} bag.
11
+ *
12
+ * The provider constructs one `OpenAI` client pointed at `info.baseUrl` with a
13
+ * custom `fetch` that merges `await info.getHeaders()` into every outbound
14
+ * request — the per-request JWT-rotation seam.
15
+ * `getInfo` is read on each `getModel()` call so a within-shape `modelId` swap
16
+ * or JWT rotation between turns lands without rebuilding anything; the fetch
17
+ * wrapper additionally re-reads `getHeaders()` per HTTP call, so credential
18
+ * rotation lands on the next request even within a single turn.
19
+ *
20
+ * HTTP transport is pinned via {@link setOpenAIResponsesTransport} so the custom
21
+ * `fetch` is the only wire seam — a WebSocket transport would bypass the
22
+ * per-request header hook and auth would not rotate. That single wire seam is
23
+ * also where wire-communication events emit (when {@link WireHooks} is supplied).
24
+ *
25
+ * Mirrors the shipping Mastra `pass-through-openai.ts` connectivity seam; the
26
+ * only difference is that this harness hands the client to the OpenAI Agents
27
+ * SDK's own `OpenAIProvider` rather than wrapping it in a `LanguageModelV3`.
28
+ */
29
+ export function buildOpenAIModelProvider(opts) {
30
+ const { getInfo, innerFetch, wireHooks } = opts;
31
+ // Pin the HTTP transport (never WebSocket) so the custom `fetch` below is the
32
+ // connectivity seam. Idempotent and safe to call per provider construction.
33
+ setOpenAIResponsesTransport('http');
34
+ return {
35
+ getModel(modelName) {
36
+ const info = getInfo();
37
+ const client = new OpenAI({
38
+ baseURL: info.baseUrl,
39
+ // The custom fetch injects the resolver-built `Authorization` header
40
+ // per request. The SDK still wants a non-empty `apiKey`; passing an
41
+ // opaque sentinel keeps it from reading `process.env['OPENAI_API_KEY']`.
42
+ apiKey: 'salesforce-gateway-passthrough',
43
+ fetch: buildGatewayFetch(getInfo, innerFetch, wireHooks),
44
+ });
45
+ const provider = new OpenAIProvider({ openAIClient: client });
46
+ // Prefer the caller-requested model name (the run loop passes the
47
+ // agent's configured model); fall back to the live bag's native id.
48
+ return provider.getModel(modelName ?? info.nativeModelId);
49
+ },
50
+ };
51
+ }
52
+ /** Bounds `llm-request` body capture so a large payload can't blow a consumer's log budget. */
53
+ const TRACE_BODY_BYTE_BUDGET = 256 * 1024;
54
+ /**
55
+ * Returns a `fetch` that merges the resolver's fresh headers into every
56
+ * outbound request. `getHeaders()` is re-evaluated per call so JWT rotation /
57
+ * premium-rate-limit gate flips land on the next request without rebuilding the
58
+ * client. Delegates to the proxy-aware `innerFetch` when supplied (so
59
+ * `HTTPS_PROXY` is honored), else the global `fetch`.
60
+ *
61
+ * When `wireHooks` is supplied, every outbound call emits one `llm-request`
62
+ * event before the fetch and one `llm-response` after it resolves — or, on a
63
+ * transport failure, an `llm-response` with `status: 0` + the captured error,
64
+ * then rethrows (a wire hook never swallows the underlying fetch). Body capture
65
+ * is best-effort and bounded ({@link TRACE_BODY_BYTE_BUDGET}); a decode failure
66
+ * never blocks the call.
67
+ */
68
+ function buildGatewayFetch(getInfo, innerFetch, wireHooks) {
69
+ const baseFetch = innerFetch ?? fetch;
70
+ return (async (input, init) => {
71
+ const info = getInfo();
72
+ const fresh = await info.getHeaders();
73
+ const headers = new Headers(init?.headers);
74
+ for (const [key, value] of Object.entries(fresh)) {
75
+ if (value != null)
76
+ headers.set(key, value);
77
+ }
78
+ const finalInit = { ...init, headers };
79
+ if (wireHooks === undefined) {
80
+ return baseFetch(input, finalInit);
81
+ }
82
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
83
+ const method = (finalInit.method ?? 'GET').toUpperCase();
84
+ const model = info.nativeModelId;
85
+ const clock = wireHooks.clock ?? new RealClock();
86
+ const requestStart = performance.now();
87
+ const body = tryDecodeBody(finalInit.body);
88
+ wireHooks.emitWireCommunication({
89
+ type: 'llm-request',
90
+ timestamp: clock.now(),
91
+ url,
92
+ method,
93
+ model,
94
+ ...(body !== undefined ? { body } : {}),
95
+ });
96
+ try {
97
+ const response = await baseFetch(input, finalInit);
98
+ wireHooks.emitWireCommunication({
99
+ type: 'llm-response',
100
+ timestamp: clock.now(),
101
+ model,
102
+ status: response.status,
103
+ totalDurationMs: Math.round(performance.now() - requestStart),
104
+ xClientTraceId: response.headers.get('x-client-trace-id') ?? undefined,
105
+ });
106
+ return response;
107
+ }
108
+ catch (error) {
109
+ wireHooks.emitWireCommunication({
110
+ type: 'llm-response',
111
+ timestamp: clock.now(),
112
+ model,
113
+ status: 0,
114
+ error: error instanceof Error ? error : new Error(String(error)),
115
+ totalDurationMs: Math.round(performance.now() - requestStart),
116
+ });
117
+ throw error;
118
+ }
119
+ });
120
+ }
121
+ /**
122
+ * Best-effort decode of a request body for the `llm-request` event. Only string
123
+ * bodies are decoded (streams / FormData / Blob are skipped); a body over
124
+ * {@link TRACE_BODY_BYTE_BUDGET} is recorded as `{ truncated, byteLength }`
125
+ * rather than parsed. Returns `undefined` when there's nothing worth capturing.
126
+ */
127
+ function tryDecodeBody(body) {
128
+ if (body === undefined || body === null)
129
+ return undefined;
130
+ if (typeof body !== 'string')
131
+ return undefined;
132
+ const byteLength = Buffer.byteLength(body, 'utf8');
133
+ if (byteLength > TRACE_BODY_BYTE_BUDGET) {
134
+ return { truncated: true, byteLength };
135
+ }
136
+ try {
137
+ const parsed = JSON.parse(body);
138
+ return typeof parsed === 'object' && parsed !== null ? parsed : { value: parsed };
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ //# sourceMappingURL=openai-model-provider.js.map