@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
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';
|
package/dist/gen-sink.js
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|