Repository navigation
Breaking: fail the Worker when an OpenAI Agents plugin Activity would replace another Activity or be replaced by one #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
campoy
merged 4 commits into
main
from
fix/openai-agents-typescript-same-name-activities-replaced
Nov 2, 2026
+225
−4
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
6cbe37a
Breaking: fail Worker creation when an OpenAI Agents plugin Activity …
campoy eadab02
Merge branch 'main' into fix/openai-agents-typescript-same-name-activ…
campoy 64c6e89
Refuse a second OpenAIAgentsPlugin with different sources, and later …
campoy 7d69938
Name a stateful provider as passed, document wrapping plugins, test t…
campoy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import test from 'ava'; | ||
| import type { NativeConnection, WorkerOptions } from '@temporalio/worker'; | ||
| import { StatelessMCPServerProvider } from '../worker/mcp-provider'; | ||
| import { StatefulMCPServerProvider } from '../worker/stateful-mcp-provider'; | ||
| import { OpenAIAgentsPlugin } from '../worker/plugin'; | ||
| import { FakeModelProvider } from './stubs/openai-agents'; | ||
|
|
||
| type Activities = Record<string, unknown>; | ||
|
|
||
| const callerActivity = async () => 'caller'; | ||
|
|
||
| const tail = | ||
| 'or remove the other Activity, run the other integration on its own Worker and task queue, or use one ' + | ||
| 'OpenAIAgentsPlugin instance on this Worker.'; | ||
|
|
||
| test('configureWorker refuses to replace a different Activity with the same name', (t) => { | ||
| const options = { | ||
| modelProvider: new FakeModelProvider([]), | ||
| mcpServerProviders: [ | ||
| new StatelessMCPServerProvider('docs', () => { | ||
| throw new Error('not called'); | ||
| }), | ||
| new StatefulMCPServerProvider( | ||
| 'notes', | ||
| () => { | ||
| throw new Error('not called'); | ||
| }, | ||
| {} as NativeConnection | ||
| ), | ||
| ], | ||
| }; | ||
| const plugin = new OpenAIAgentsPlugin(options); | ||
|
|
||
| const refusals: Record<string, string> = { | ||
| invokeModelActivity: | ||
| "Duplicate registration of Activity 'invokeModelActivity': OpenAIAgentsPlugin would replace a different " + | ||
| `Activity with this name. Rename ${tail}`, | ||
| 'docs-stateless-call-tool': | ||
| "Duplicate registration of Activity 'docs-stateless-call-tool': OpenAIAgentsPlugin provider 'docs' would " + | ||
| `replace a different Activity with this name. Rename the provider, rename ${tail}`, | ||
| // The name the caller passed, without the stateful provider's internal suffix. | ||
| 'notes-stateful-server-session': | ||
| "Duplicate registration of Activity 'notes-stateful-server-session': OpenAIAgentsPlugin provider 'notes' " + | ||
| `would replace a different Activity with this name. Rename the provider, rename ${tail}`, | ||
| }; | ||
| for (const [name, message] of Object.entries(refusals)) { | ||
| t.throws( | ||
| () => plugin.configureWorker({ taskQueue: 'test', activities: { [name]: callerActivity } } as WorkerOptions), | ||
| { instanceOf: TypeError, message } | ||
| ); | ||
| } | ||
|
|
||
| // Caller Activities with other names stay beside the plugin's. | ||
| const once = plugin.configureWorker({ taskQueue: 'test', activities: { lookUp: callerActivity } } as WorkerOptions); | ||
| t.is((once.activities as Activities).lookUp, callerActivity); | ||
|
|
||
| // Worker.create runs configureWorker twice when one instance is on both the NativeConnection | ||
| // and the Worker. | ||
| const twice = plugin.configureWorker(once); | ||
| t.is((twice.activities as Activities).invokeModelActivity, (once.activities as Activities).invokeModelActivity); | ||
|
|
||
| // A second instance built from the same options is the same plugin. | ||
| const same = new OpenAIAgentsPlugin(options).configureWorker(once); | ||
| t.is((same.activities as Activities).lookUp, callerActivity); | ||
|
|
||
| // A second instance with another model provider would replace the first one's Activities. | ||
| t.throws(() => new OpenAIAgentsPlugin({ modelProvider: new FakeModelProvider([]) }).configureWorker(once), { | ||
| instanceOf: TypeError, | ||
| message: | ||
| "Duplicate registration of Activity 'invokeModelActivity': OpenAIAgentsPlugin would replace a different " + | ||
| `Activity with this name. Rename ${tail}`, | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3 (non-blocking, tests): neither new test puts a plugin on the
NativeConnection, so nothing pins the layout that runs the check twice (one instance on the connection and the Worker, with the later plugin on the connection), and a refactor to the publicworker.shutdown()would pass this test while breaking that layout. In the compiled build I swapped theinitiateShutdown()call and theawaitatplugin.ts:259-260forconst running = next(worker); worker.shutdown(); await running.catch(() => undefined);. This test still passes, and so do the single-instance layouts, but in the double layout the inner check stops the Worker first: the outerworker.shutdown()throwsIllegalStateError: Not running. Current state: DRAININGinstead of theTypeError, the Worker is leftDRAINING, and an immediateconnection.close()fails withCannot close connection while Workers hold a reference to it. At this head that layout is refused correctly (3 ms,STOPPED, close succeeds), so this is only about keeping it that way. #339 pins the same layout forGoogleAdkPlugin. This case, added after this test, passes at the head (307 ms) and fails with that swap:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added your case in 7d69938, inside the existing
runWorkertest: one instance on the connection and the Worker, with the later plugin on the connection. It expects theTypeError, aSTOPPEDWorker and a cleanconnection.close(), and it passes at the head (make test, 247 tests).