@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@salesforce/sfdx-agent-harness-openai` are documented in this file. Format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [0.0.1] - 2026-07-22
7
+
8
+ ### Features
9
+ - **harness-openai**: full conformance gate + e2e closeout @W-23447579@ ([#684](https://github.com/forcedotcom/agentic-dx/pull/684))
10
+ - **harness-openai**: compaction, telemetry, hooks @W-23447576@ ([#683](https://github.com/forcedotcom/agentic-dx/pull/683))
11
+ - **harness-openai**: MCP lifecycle + updateAgent preservation @W-23447574@ ([#678](https://github.com/forcedotcom/agentic-dx/pull/678))
12
+ - **harness-openai**: approval + consumer tools @W-23447572@ ([#677](https://github.com/forcedotcom/agentic-dx/pull/677))
13
+ - **harness-openai**: sessions, tools, history + isError sidecar @W-23447571@ ([#676](https://github.com/forcedotcom/agentic-dx/pull/676))
14
+ - **harness-openai**: scaffold OpenAI Agents SDK harness with connectivity + streamed turn @W-23447570@ ([#675](https://github.com/forcedotcom/agentic-dx/pull/675))
15
+
16
+ ### Fixes
17
+ - **ci**: bump openai harness off seed version so it can publish @W-23447579@ ([#686](https://github.com/forcedotcom/agentic-dx/pull/686))
18
+
19
+ ### Chores
20
+ - **deps-dev**: bump eslint from 10.6.0 to 10.7.0 in the eslint group ([#680](https://github.com/forcedotcom/agentic-dx/pull/680))
21
+
22
+ ## [Unreleased]
23
+
24
+ ### Features
25
+
26
+ - Scaffold the package: `OpenAIAgentsHarnessFactory` (`harnessId: 'openai-agents'`, `protocolVersion: 1`,
27
+ `supportedProviderHints: ['openai-responses', 'openai']`), proxy-aware inner fetch, and connectivity via a custom
28
+ `ModelProvider` / `Model` bound to the live `ModelConnectivityInfo` bag.
29
+ - Milestone A: a bare, tool-free streamed turn against the Salesforce LLM Gateway `/responses` endpoint, with
30
+ mid-stream-error synthesis (`error` + terminal `finish`).
31
+ - Milestone D: MCP server lifecycle + `updateAgent` preservation. `createAgent` constructs, connects, and
32
+ background-discovers the agent's configured MCP servers; `getMcpServerInfo` returns a synchronous status snapshot;
33
+ `reconnectMcpServer` cycles a server's transport and re-discovers its tools; `updateAgent` preserves MCP clients whose
34
+ config is structurally unchanged (no reconnect, no `tools/list` — #541) and cycles only the changed / added / removed
35
+ servers. MCP-sourced `tool-call` / `tool-result` events are enriched with `serverName` / `bareToolName` /
36
+ `annotations`. The reconnection-options field-merge is wired into the cross-harness conformance gate. MCP-tool
37
+ approval gating and MCP telemetry are deferred to later milestones.
package/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ Terms of Use
2
+
3
+ Copyright 2026 Salesforce, Inc. All rights reserved.
4
+
5
+ These Terms of Use govern the download, installation, and/or use of this software provided by Salesforce, Inc. ("Salesforce") (the "Software"), were last updated on March 24, 2026, and constitute a legally binding agreement between you and Salesforce. If you do not agree to these Terms of Use, do not install or use the Software. The Software may link to third-party software components licensed under various open source licenses ("Open Source Components"). These Terms of Use pertain solely to Salesforce's proprietary code in the Software. It does not alter or extend any rights or obligations regarding the Open Source Components. For clarity, your use of the Open Source Components is governed by the terms of the applicable open source license(s). You are solely responsible for complying with the terms and conditions of those open source licenses.
6
+
7
+ Salesforce grants you a worldwide, non-exclusive, no-charge, royalty-free copyright license to reproduce, revocable, publicly display, publicly perform, sublicense, and distribute the Software and derivative works subject to these Terms. These Terms shall be included in all copies or substantial portions of the Software.
8
+
9
+ Subject to the limited rights expressly granted hereunder, Salesforce reserves all rights, title, and interest in and to all intellectual property subsisting in the Software. No rights are granted to you hereunder other than as expressly set forth herein. Users residing in countries on the United States Office of Foreign Assets Control sanction list, or which are otherwise subject to a US export embargo, may not use the Software.
10
+
11
+ Implementation of the Software may require development work, for which you are responsible. The Software may contain bugs, errors and incompatibilities and is made available on an AS IS basis without support, updates, or service level commitments.
12
+
13
+ Salesforce reserves the right at any time to modify, suspend, or discontinue, the Software (or any part thereof) with or without notice. You agree that Salesforce shall not be liable to you or to any third party for any modification, suspension, or discontinuance.
14
+
15
+ You agree to defend Salesforce against any claim, demand, suit or proceeding made or brought against Salesforce by a third party arising out of or accruing from (a) your use of the Software, and (b) any application you develop with the Software that infringes any copyright, trademark, trade secret, trade dress, patent, or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy (each a "Claim Against Salesforce"), and will indemnify Salesforce from any damages, attorney fees, and costs finally awarded against Salesforce as a result of, or for any amounts paid by Salesforce under a settlement approved by you in writing of, a Claim Against Salesforce, provided Salesforce (x) promptly gives you written notice of the Claim Against Salesforce, (y) gives you sole control of the defense and settlement of the Claim Against Salesforce (except that you may not settle any Claim Against Salesforce unless it unconditionally releases Salesforce of all liability), and (z) gives you all reasonable assistance, at your expense.
16
+
17
+ WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE SOFTWARE IS NOT SUPPORTED AND IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL SALESFORCE HAVE ANY LIABILITY FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON LOST PROFITS, DATA, OR USE, IN CONNECTION WITH THE SOFTWARE, HOWEVER CAUSED AND WHETHER IN CONTRACT, TORT, OR UNDER ANY OTHER THEORY OF LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
18
+
19
+ These Terms of Use shall be governed exclusively by the internal laws of the State of California, without regard to its conflicts of laws rules. Each party hereby consents to the exclusive jurisdiction of the state and federal courts located in San Francisco County, California to adjudicate any dispute arising out of or relating to these Terms of Use and the download, installation, and/or use of the Software. Except as expressly stated herein, these Terms of Use constitute the entire agreement between the parties, and supersede all prior and contemporaneous agreements, proposals, or representations, written or oral, concerning their subject matter. No modification, amendment, or waiver of any provision of these Terms of Use shall be effective unless it is by an update to these Terms of Use that Salesforce makes available, or is in writing and signed by the party against whom the modification, amendment, or waiver is to be asserted.
20
+
21
+ Data Privacy: Salesforce may collect, process, and store device, system, and other information related to your use of the Software. This information includes, but is not limited to, IP address, user metrics, and other data ("Usage Data"). Salesforce may use Usage Data for analytics, product development, and marketing purposes. You acknowledge that files generated in conjunction with the Software may contain sensitive or confidential data, and you are solely responsible for anonymizing and protecting such data.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @salesforce/sfdx-agent-harness-openai
2
+
3
+ OpenAI Agents SDK-backed `AgentHarness` implementation for [`@salesforce/sfdx-agent-sdk`](../sfdx-agent-sdk).
4
+
5
+ > **Closed source.** This package is published to npm under the [Salesforce Public Code License](../../LICENSE.txt) and
6
+ > is for use by Salesforce only.
7
+
8
+ > **Milestone F — closeout (this release).** All six milestones (A–F) have landed: A's connectivity + streamed turn, B's
9
+ > disk-backed sessions / thread lifecycle / message history, C's tool approval + consumer-executed tools, D's MCP server
10
+ > lifecycle + `updateAgent` preservation (#541), E's compaction / telemetry / hooks, and F's full conformance-gate + e2e
11
+ > closeout. Every `AgentHarness` method has a working implementation, the shared cross-harness conformance gate is
12
+ > green, and the harness passes the SDK's `describe.each` e2e matrix under `--harness openai` against the real gateway.
13
+ > **Known deferred gaps** (each excluded from the e2e matrix with a documented rationale, not a silent skip): MCP-tool
14
+ > **approval gating** (the `@openai/agents` native `Agent({ mcpServers })` attach exposes no per-tool `needsApproval`
15
+ > hook), **multimodal** input (the harness is text-only), `AgentConfig.rules` / `skills` composition, and MCP
16
+ > `tool-progress`. Consumer-tool approval + `submitToolResult`, and tool-result redaction, ARE supported.
17
+
18
+ ## What it is
19
+
20
+ A third implementation of the SDK's `AgentHarness` contract, alongside the Mastra and Claude harnesses. It is backed by
21
+ the OpenAI Agents SDK — an agentic _framework_ (it owns the run loop, tool orchestration, and sessions), peer to Mastra
22
+ and the Claude Agent SDK, not a model. GPT models are already served through the Mastra harness's `/responses`
23
+ pass-through; this harness exists to provide an OpenAI-maintained run loop and a genuine third implementation that
24
+ stress-validates the `AgentHarness` contract's harness-agnosticism.
25
+
26
+ The harness advertises `supportedProviderHints: ['openai-responses', 'openai']`.
27
+
28
+ ## Quick start
29
+
30
+ ```ts
31
+ import { createAgentManager } from '@salesforce/sfdx-agent-sdk';
32
+ import { OpenAIAgentsHarnessFactory } from '@salesforce/sfdx-agent-harness-openai';
33
+
34
+ const manager = await createAgentManager(storageRootFolder, new OpenAIAgentsHarnessFactory());
35
+ ```
36
+
37
+ The SDK's `AgentConnectivityResolver` supplies the gateway URL, native model id, provider hint, and per-request auth
38
+ headers as a `ModelConnectivityInfo` bag; the factory carries only harness-internal concerns.
39
+
40
+ ## Public API
41
+
42
+ | Export | Kind | Description |
43
+ | ---------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
44
+ | `OpenAIAgentsHarnessFactory` | class | `HarnessFactory` that constructs the harness. Pass to `createAgentManager`. |
45
+ | `OpenAIAgentsHarnessFactoryConfig` | type | Optional factory configuration. Carries `toolApprovalTimeoutMs` (per-`toolCallId` approval timeout, default 600_000 ms); connectivity flows in from the SDK's resolver, not the factory. |
46
+ | `OPENAI_BUILT_IN_TOOL_POLICIES` | const | The harness's built-in tool-approval tier fed to `resolveToolApprovalPolicy` (the `tiers.harness` slice). Empty today — the harness registers no runtime built-in tools of its own. Mirrors the Mastra / Claude `*_BUILT_IN_TOOL_POLICIES` surfaces. |
47
+
48
+ Connectivity flows in from the SDK as a `ModelConnectivityInfo` bag — for Salesforce-org gateway routing use
49
+ `DefaultAgentConnectivityResolver` from `@salesforce/sfdx-agent-sdk` (or any custom resolver); for direct-OpenAI / BYOK
50
+ paths use `ApiKeyConnectivityResolver`.
51
+
52
+ ## Development
53
+
54
+ See [DEVELOPING.md](DEVELOPING.md) for build, test, and packaging commands, and [ARCHITECTURE.md](ARCHITECTURE.md) for
55
+ internal design.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export of the `GenSink` primitive from the SDK's harness-implementation
3
+ * subpath (`@salesforce/sfdx-agent-sdk/harness`). See that surface for the
4
+ * single-iteration invariant; issue #529 is the rationale for the
5
+ * cross-harness lift and for the subpath split (consumer applications can no
6
+ * longer import `GenSink` from the bare specifier — they have no use for it).
7
+ */
8
+ export { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
@@ -0,0 +1,13 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ /**
6
+ * Re-export of the `GenSink` primitive from the SDK's harness-implementation
7
+ * subpath (`@salesforce/sfdx-agent-sdk/harness`). See that surface for the
8
+ * single-iteration invariant; issue #529 is the rationale for the
9
+ * cross-harness lift and for the subpath split (consumer applications can no
10
+ * longer import `GenSink` from the bare specifier — they have no use for it).
11
+ */
12
+ export { GenSink } from '@salesforce/sfdx-agent-sdk/harness';
13
+ //# sourceMappingURL=gen-sink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gen-sink.js","sourceRoot":"","sources":["../src/gen-sink.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { OpenAIAgentsHarnessFactory, type OpenAIAgentsHarnessFactoryConfig } from './openai-agents-harness-factory.js';
2
+ export { type OpenAIAgentsAgentHarness } from './openai-agents-harness.js';
3
+ export { OPENAI_BUILT_IN_TOOL_POLICIES } from './openai-built-in-policies.js';
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ export { OpenAIAgentsHarnessFactory } from './openai-agents-harness-factory.js';
6
+ export {} from './openai-agents-harness.js';
7
+ // The OpenAI harness's built-in tool-approval tier (the `tiers.harness` slice
8
+ // fed to `resolveToolApprovalPolicy`). Exported for parity with the Mastra /
9
+ // Claude `*_BUILT_IN_TOOL_POLICIES` surfaces so consumers can inspect it; empty
10
+ // today (the harness registers no runtime built-in tools of its own).
11
+ export { OPENAI_BUILT_IN_TOOL_POLICIES } from './openai-built-in-policies.js';
12
+ // Connectivity flows in from the SDK as a `ModelConnectivityInfo` bag — there's
13
+ // no per-harness transport-strategy seam. For Salesforce-org gateway routing use
14
+ // `DefaultAgentConnectivityResolver` from `@salesforce/sfdx-agent-sdk` (or any
15
+ // custom resolver); for direct-OpenAI / BYOK paths use `ApiKeyConnectivityResolver`.
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,0BAA0B,EAAyC,MAAM,oCAAoC,CAAC;AACvH,OAAO,EAAiC,MAAM,4BAA4B,CAAC;AAC3E,8EAA8E;AAC9E,6EAA6E;AAC7E,gFAAgF;AAChF,sEAAsE;AACtE,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAC9E,gFAAgF;AAChF,iFAAiF;AACjF,+EAA+E;AAC/E,qFAAqF"}
@@ -0,0 +1,36 @@
1
+ import type { McpServerErrorCategory, McpServerErrorDetail } from '@salesforce/sfdx-agent-sdk';
2
+ /**
3
+ * Strip V8 stack frames (" at <frame>") and any framing that arrived inside
4
+ * the message string. The result is a single, human-readable summary line safe
5
+ * for UI banners, telemetry tags, and Sentry fingerprints. The full stack is
6
+ * still available on the underlying `Error` instance (`error.stack`) for
7
+ * debug-mode log surfaces.
8
+ *
9
+ * Inputs are loose on purpose: a string passes through, a non-Error object's
10
+ * `String(...)` form is sanitized the same way, and `undefined`/`null` round-trip
11
+ * through `String(...)`.
12
+ */
13
+ export declare function sanitizeMcpErrorMessage(err: unknown): string;
14
+ /**
15
+ * Map a caught error to the SDK's stable {@link McpServerErrorCategory}. The
16
+ * lookup is layered:
17
+ *
18
+ * 1. `DOMException` `AbortError` and Node's `code === 'ABORT_ERR'` map to
19
+ * `'aborted'` regardless of the `.message`.
20
+ * 2. MCP-shaped errors (an `Error` carrying a numeric `code`, see
21
+ * {@link isMcpErrorLike}) match by code: a positive code in `[100, 599]` is
22
+ * an HTTP status (see {@link categoryForHttpStatus}); a negative code is a
23
+ * JSON-RPC 2.0 error (`-32001` etc.).
24
+ * 3. Free-form `.message` text is matched against substrings the underlying
25
+ * transports surface (`401`, `403`, `5xx`, `ECONNREFUSED`, etc.). This is
26
+ * intentionally a small lookup — categories are stable, message shapes
27
+ * are not, so the heuristic stays narrow and falls back to `'unknown'`.
28
+ */
29
+ export declare function classifyMcpError(err: unknown): McpServerErrorCategory;
30
+ /**
31
+ * Build the SDK-side {@link McpServerErrorDetail} from a caught error. The
32
+ * underlying JSON-RPC `code` (when the error is MCP-shaped, see
33
+ * {@link isMcpErrorLike}) is preserved so consumers can route on the code
34
+ * without having to introspect `cause`.
35
+ */
36
+ export declare function buildMcpErrorDetail(err: unknown): McpServerErrorDetail;
@@ -0,0 +1,166 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ import { getErrorMessage, isAbortError } from '@salesforce/agentic-common';
6
+ /**
7
+ * Structural duck-type for MCP-shaped errors. Matches the upstream
8
+ * `@modelcontextprotocol/sdk` `McpError` class (`extends Error`, numeric
9
+ * `code`) without an `instanceof` check. We avoid the import for two reasons:
10
+ *
11
+ * 1. **Dual-package hazard.** If a transport ever pins
12
+ * `@modelcontextprotocol/sdk` to a version that diverges from one this
13
+ * package would pin, npm hoists two copies and `instanceof` returns false
14
+ * across realms. Structural matching survives that case.
15
+ * 2. **JSON-RPC codes are the contract, not the class.** The numeric codes
16
+ * (`-32001 RequestTimeout`, `-32603 InternalError`, …) are stable per the
17
+ * JSON-RPC 2.0 spec. Any error with `code: number` originating from
18
+ * MCP-shaped infrastructure will match — which is what consumers want.
19
+ *
20
+ * Node's `SystemError` (e.g. `ECONNREFUSED`) also has a `.code` field but as
21
+ * a `string`, so the `typeof === 'number'` check disambiguates.
22
+ *
23
+ * This classifier is a copy of the Claude and Mastra harnesses' copies —
24
+ * cross-harness imports are lint-blocked (`harnessIsolationPack`), so the code
25
+ * is duplicated on purpose and the shared `@salesforce/harness-conformance`
26
+ * `MCP_ERROR_FIXTURES` table is the drift-guard that keeps the three in
27
+ * lockstep. If a third-and-beyond harness ships, this should graduate to a
28
+ * shared package.
29
+ */
30
+ function isMcpErrorLike(err) {
31
+ return err instanceof Error && typeof err.code === 'number';
32
+ }
33
+ /**
34
+ * Strip V8 stack frames (" at <frame>") and any framing that arrived inside
35
+ * the message string. The result is a single, human-readable summary line safe
36
+ * for UI banners, telemetry tags, and Sentry fingerprints. The full stack is
37
+ * still available on the underlying `Error` instance (`error.stack`) for
38
+ * debug-mode log surfaces.
39
+ *
40
+ * Inputs are loose on purpose: a string passes through, a non-Error object's
41
+ * `String(...)` form is sanitized the same way, and `undefined`/`null` round-trip
42
+ * through `String(...)`.
43
+ */
44
+ export function sanitizeMcpErrorMessage(err) {
45
+ const raw = getErrorMessage(err);
46
+ const lines = raw.split(/\r?\n/);
47
+ // Keep the first line that is NOT a V8 stack frame. V8 frames start with
48
+ // arbitrary leading whitespace followed by "at " (matched at line start).
49
+ for (const line of lines) {
50
+ if (!/^\s*at\s/.test(line)) {
51
+ return line.trim();
52
+ }
53
+ }
54
+ // All lines were stack frames — fall back to the raw single-line input.
55
+ return lines[0]?.trim() ?? '';
56
+ }
57
+ /**
58
+ * Map an HTTP status code to the SDK's stable {@link McpServerErrorCategory}.
59
+ * HTTP status codes are positive (100–599); JSON-RPC error codes are negative,
60
+ * so the two never collide on the numeric `code` field. The MCP SDK's
61
+ * `StreamableHTTPError` carries the HTTP status verbatim on `code`
62
+ * (`@modelcontextprotocol/sdk` `streamableHttp.js`), so a 404 "org not enabled"
63
+ * surfaces as `http-4xx` + `code: 404` rather than collapsing to `unknown`
64
+ * (W-23231658). Kept in lockstep with the Mastra and Claude harnesses' copies.
65
+ */
66
+ function categoryForHttpStatus(status) {
67
+ if (status === 401)
68
+ return 'http-401';
69
+ if (status === 403)
70
+ return 'http-403';
71
+ if (status >= 400 && status <= 499)
72
+ return 'http-4xx';
73
+ if (status >= 500 && status <= 599)
74
+ return 'http-5xx';
75
+ return 'unknown';
76
+ }
77
+ /**
78
+ * Map a caught error to the SDK's stable {@link McpServerErrorCategory}. The
79
+ * lookup is layered:
80
+ *
81
+ * 1. `DOMException` `AbortError` and Node's `code === 'ABORT_ERR'` map to
82
+ * `'aborted'` regardless of the `.message`.
83
+ * 2. MCP-shaped errors (an `Error` carrying a numeric `code`, see
84
+ * {@link isMcpErrorLike}) match by code: a positive code in `[100, 599]` is
85
+ * an HTTP status (see {@link categoryForHttpStatus}); a negative code is a
86
+ * JSON-RPC 2.0 error (`-32001` etc.).
87
+ * 3. Free-form `.message` text is matched against substrings the underlying
88
+ * transports surface (`401`, `403`, `5xx`, `ECONNREFUSED`, etc.). This is
89
+ * intentionally a small lookup — categories are stable, message shapes
90
+ * are not, so the heuristic stays narrow and falls back to `'unknown'`.
91
+ */
92
+ export function classifyMcpError(err) {
93
+ if (isAbortError(err))
94
+ return 'aborted';
95
+ if (isMcpErrorLike(err)) {
96
+ // HTTP status (positive, 100–599) before the JSON-RPC switch — the two
97
+ // code spaces are disjoint (JSON-RPC codes are negative), so an HTTP
98
+ // 404 no longer falls through the JSON-RPC `default` to `unknown`.
99
+ if (err.code >= 100 && err.code <= 599) {
100
+ return categoryForHttpStatus(err.code);
101
+ }
102
+ switch (err.code) {
103
+ case -32001: // RequestTimeout
104
+ return 'connect-timeout';
105
+ case -32700: // ParseError
106
+ case -32600: // InvalidRequest
107
+ case -32601: // MethodNotFound
108
+ case -32602: // InvalidParams
109
+ case -32603: // InternalError
110
+ return 'protocol-error';
111
+ case -32000: // ConnectionClosed
112
+ return 'transport-eof';
113
+ default:
114
+ return 'unknown';
115
+ }
116
+ }
117
+ const message = err instanceof Error ? err.message : typeof err === 'string' ? err : '';
118
+ if (/\b401\b/.test(message))
119
+ return 'http-401';
120
+ if (/\b403\b/.test(message))
121
+ return 'http-403';
122
+ if (/\b4\d\d\b/.test(message))
123
+ return 'http-4xx';
124
+ if (/\b5\d\d\b/.test(message))
125
+ return 'http-5xx';
126
+ if (/ECONNREFUSED|ECONNRESET|EPIPE|socket hang up/i.test(message))
127
+ return 'transport-eof';
128
+ if (/timed out|timeout/i.test(message))
129
+ return 'connect-timeout';
130
+ return 'unknown';
131
+ }
132
+ /**
133
+ * Whether the SDK considers the failure transient (worth a retry via
134
+ * `Agent.reconnectMcpServer` / `Agent.refreshMcpAuth`). Aborted operations and
135
+ * permanent protocol violations are non-retriable; everything else is retriable
136
+ * by default — failing-open here is correct because the consumer always has
137
+ * the option to ignore the hint.
138
+ */
139
+ function isCategoryRetriable(category) {
140
+ switch (category) {
141
+ case 'aborted':
142
+ case 'protocol-error':
143
+ case 'config-error':
144
+ return false;
145
+ default:
146
+ return true;
147
+ }
148
+ }
149
+ /**
150
+ * Build the SDK-side {@link McpServerErrorDetail} from a caught error. The
151
+ * underlying JSON-RPC `code` (when the error is MCP-shaped, see
152
+ * {@link isMcpErrorLike}) is preserved so consumers can route on the code
153
+ * without having to introspect `cause`.
154
+ */
155
+ export function buildMcpErrorDetail(err) {
156
+ const category = classifyMcpError(err);
157
+ const detail = {
158
+ category,
159
+ retriable: isCategoryRetriable(category),
160
+ };
161
+ if (isMcpErrorLike(err)) {
162
+ detail.code = err.code;
163
+ }
164
+ return detail;
165
+ }
166
+ //# sourceMappingURL=mcp-error-classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-error-classifier.js","sourceRoot":"","sources":["../src/mcp-error-classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { HarnessFactory, ProviderHint } from '@salesforce/sfdx-agent-sdk';
2
+ import { type OpenAIAgentsAgentHarness } from './openai-agents-harness.js';
3
+ /**
4
+ * Factory configuration for the OpenAI Agents harness.
5
+ *
6
+ * Connectivity flows in from the SDK's `AgentConnectivityResolver` as a
7
+ * `ModelConnectivityInfo` bag, not from the factory — so the config carries only
8
+ * harness-internal knobs.
9
+ */
10
+ export interface OpenAIAgentsHarnessFactoryConfig {
11
+ /**
12
+ * Per-`toolCallId` tool-approval timeout, in milliseconds, forwarded to each
13
+ * turn's `OpenAIApprovalCoordinator`. Defaults to 600_000 (10 minutes) when
14
+ * unset — the same default the coordinator applies. Mirrors the Claude /
15
+ * Mastra factories' `toolApprovalTimeoutMs` so timeout-sensitive e2e
16
+ * scenarios can dial it in.
17
+ */
18
+ readonly toolApprovalTimeoutMs?: number;
19
+ }
20
+ /**
21
+ * Constructs an {@link OpenAIAgentsHarness} bound to a storage root.
22
+ *
23
+ * The OpenAI Agents SDK (`@openai/agents`) speaks the OpenAI Responses wire
24
+ * shape only, so the harness advertises `supportedProviderHints:
25
+ * ['openai-responses', 'openai']`. Routing it at a non-OpenAI model is a
26
+ * programming error caught at `createAgent` / `updateAgentConfig` by the
27
+ * manager's provider-hint validation.
28
+ */
29
+ export declare class OpenAIAgentsHarnessFactory implements HarnessFactory<OpenAIAgentsAgentHarness> {
30
+ private readonly config;
31
+ readonly harnessId = "openai-agents";
32
+ readonly protocolVersion = 1;
33
+ readonly supportedProviderHints: readonly ProviderHint[];
34
+ constructor(config?: OpenAIAgentsHarnessFactoryConfig);
35
+ create(storageRootFolder: string): Promise<OpenAIAgentsAgentHarness>;
36
+ }
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright 2026, Salesforce, Inc. All rights reserved.
3
+ * See LICENSE.txt for license terms.
4
+ */
5
+ import { createProxyAwareFetch, resolveProxyDispatcher } from '@salesforce/agentic-common';
6
+ import { OpenAIAgentsHarness } from './openai-agents-harness.js';
7
+ /**
8
+ * Constructs an {@link OpenAIAgentsHarness} bound to a storage root.
9
+ *
10
+ * The OpenAI Agents SDK (`@openai/agents`) speaks the OpenAI Responses wire
11
+ * shape only, so the harness advertises `supportedProviderHints:
12
+ * ['openai-responses', 'openai']`. Routing it at a non-OpenAI model is a
13
+ * programming error caught at `createAgent` / `updateAgentConfig` by the
14
+ * manager's provider-hint validation.
15
+ */
16
+ export class OpenAIAgentsHarnessFactory {
17
+ config;
18
+ harnessId = 'openai-agents';
19
+ protocolVersion = 1;
20
+ supportedProviderHints = ['openai-responses', 'openai'];
21
+ constructor(config = {}) {
22
+ this.config = config;
23
+ }
24
+ async create(storageRootFolder) {
25
+ // Build a proxy-aware inner fetch once per harness and thread it into the
26
+ // per-request gateway fetch, mirroring the Mastra and Claude factories.
27
+ // `resolveProxyDispatcher()` returns an `EnvHttpProxyAgent` when
28
+ // `HTTPS_PROXY` / `HTTP_PROXY` is set and `undefined` otherwise;
29
+ // `createProxyAwareFetch` wraps it into a `typeof fetch` (or returns
30
+ // `globalThis.fetch` unchanged when no dispatcher is supplied). No
31
+ // `globalThis` mutation.
32
+ const innerFetch = createProxyAwareFetch(resolveProxyDispatcher());
33
+ return new OpenAIAgentsHarness(storageRootFolder, {
34
+ innerFetch,
35
+ toolApprovalTimeoutMs: this.config.toolApprovalTimeoutMs,
36
+ });
37
+ }
38
+ }
39
+ //# sourceMappingURL=openai-agents-harness-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-agents-harness-factory.js","sourceRoot":"","sources":["../src/openai-agents-harness-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE3F,OAAO,EAAE,mBAAmB,EAAiC,MAAM,4BAA4B,CAAC;AAoBhG;;;;;;;;GAQG;AACH,MAAM,OAAO,0BAA0B;IAKN;IAJpB,SAAS,GAAG,eAAe,CAAC;IAC5B,eAAe,GAAG,CAAC,CAAC;IACpB,sBAAsB,GAA4B,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAE1F,YAA6B,SAA2C,EAAE;QAA7C,WAAM,GAAN,MAAM,CAAuC;IAAG,CAAC;IAE9E,KAAK,CAAC,MAAM,CAAC,iBAAyB;QAClC,0EAA0E;QAC1E,wEAAwE;QACxE,iEAAiE;QACjE,iEAAiE;QACjE,qEAAqE;QACrE,mEAAmE;QACnE,yBAAyB;QACzB,MAAM,UAAU,GAAG,qBAAqB,CAAC,sBAAsB,EAAE,CAAC,CAAC;QACnE,OAAO,IAAI,mBAAmB,CAAC,iBAAiB,EAAE;YAC9C,UAAU;YACV,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,qBAAqB;SAC3D,CAAC,CAAC;IACP,CAAC;CACJ"}