@stdiobus/workers-registry 1.5.0-beta.1 → 1.5.0-beta.2
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/README.md
CHANGED
|
@@ -21,9 +21,15 @@ Worker implementations for [stdio Bus kernel](https://github.com/stdiobus/stdiob
|
|
|
21
21
|
npm install @stdiobus/workers-registry
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
For embedded usage (no Docker/binary needed):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @stdiobus/node @stdiobus/workers-registry
|
|
28
|
+
```
|
|
29
|
+
|
|
24
30
|
**Requirements:**
|
|
25
31
|
- Node.js ≥20.0.0
|
|
26
|
-
- [stdio Bus kernel](https://github.com/stdiobus/stdiobus) (Docker or binary)
|
|
32
|
+
- [stdio Bus kernel](https://github.com/stdiobus/stdiobus) (Docker or binary) — or [`@stdiobus/node`](https://www.npmjs.com/package/@stdiobus/node) for embedded mode
|
|
27
33
|
|
|
28
34
|
**Keywords:** `stdiobus`, `protocol`, `acp`, `mcp`, `agent`, `transport`, `json-rpc`, `stdio-bus`, `worker`
|
|
29
35
|
|
|
@@ -61,22 +67,26 @@ graph TB
|
|
|
61
67
|
|
|
62
68
|
## Prerequisites
|
|
63
69
|
|
|
64
|
-
-
|
|
65
|
-
-
|
|
70
|
+
- Node.js 20.0.0 or later
|
|
71
|
+
- One of:
|
|
72
|
+
- [`@stdiobus/node`](https://www.npmjs.com/package/@stdiobus/node) — embedded mode, no external dependencies
|
|
73
|
+
- [stdio Bus kernel via Docker](https://hub.docker.com/r/stdiobus/stdiobus) — TCP/Unix socket mode
|
|
74
|
+
- [stdio Bus kernel binary](https://github.com/stdiobus/stdiobus) — build from source
|
|
66
75
|
|
|
67
76
|
## Workers
|
|
68
77
|
|
|
69
78
|
| Worker | Description | Protocol | Command |
|
|
70
79
|
|--------|-------------|----------|---------|
|
|
71
|
-
| `acp-registry` | Registry Launcher worker that routes to ACP Registry agents (requires `api-keys.json`) | ACP | `
|
|
72
|
-
| `acp-worker` | Full ACP protocol implementation (standalone agent; does **not** route to ACP Registry) | ACP | `
|
|
80
|
+
| `acp-registry` | Registry Launcher worker that routes to ACP Registry agents (requires `api-keys.json`) | ACP | `npx @stdiobus/workers-registry acp-registry` |
|
|
81
|
+
| `acp-worker` | Full ACP protocol implementation (standalone agent; does **not** route to ACP Registry) | ACP | `npx @stdiobus/workers-registry acp-worker` |
|
|
73
82
|
| `registry-launcher` | Registry Launcher implementation module used by `acp-registry` (not a launch target) | ACP | Use `acp-registry` |
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
83
|
+
| `openai-agent` | OpenAI Chat Completions API agent (bridges ACP to any OpenAI-compatible endpoint) | ACP | `npx @stdiobus/workers-registry openai-agent` |
|
|
84
|
+
| `mcp-to-acp-proxy` | Bridges MCP clients (like IDEs) to ACP agents | MCP → ACP | `npx @stdiobus/workers-registry mcp-to-acp-proxy` |
|
|
85
|
+
| `echo-worker` | Simple echo worker for testing NDJSON protocol | NDJSON | `npx @stdiobus/workers-registry echo-worker` |
|
|
86
|
+
| `mcp-echo-server` | MCP server example for testing | MCP | `npx @stdiobus/workers-registry mcp-echo-server` |
|
|
77
87
|
|
|
78
|
-
**Note:** The universal launcher is `@stdiobus/workers-registry/launch`.
|
|
79
|
-
`node ./launch/index.js <worker-name>` after `npm run build`.
|
|
88
|
+
**Note:** The universal launcher is `@stdiobus/workers-registry/launch`. For local
|
|
89
|
+
development in this repo, use `node ./launch/index.js <worker-name>` after `npm run build`.
|
|
80
90
|
|
|
81
91
|
## Package API
|
|
82
92
|
|
|
@@ -111,13 +121,75 @@ import type { MCPServer } from '@stdiobus/workers-registry/workers/mcp-echo-serv
|
|
|
111
121
|
|
|
112
122
|
## Quick Start
|
|
113
123
|
|
|
114
|
-
###
|
|
124
|
+
### Option A: Embedded via `@stdiobus/node` (simplest)
|
|
125
|
+
|
|
126
|
+
No Docker, no binary, no TCP — just npm packages. The bus runs inside your Node.js process.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npm install @stdiobus/node @stdiobus/workers-registry
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Create `config.json`:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"pools": [
|
|
137
|
+
{
|
|
138
|
+
"id": "openai-agent",
|
|
139
|
+
"command": "npx",
|
|
140
|
+
"args": ["@stdiobus/workers-registry", "openai-agent"],
|
|
141
|
+
"instances": 1
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Use it in code:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
import { StdioBus } from '@stdiobus/node';
|
|
151
|
+
|
|
152
|
+
const bus = new StdioBus({ configPath: './config.json' });
|
|
153
|
+
await bus.start();
|
|
154
|
+
|
|
155
|
+
// Send ACP initialize request
|
|
156
|
+
const result = await bus.request('initialize', {
|
|
157
|
+
protocolVersion: 1,
|
|
158
|
+
clientInfo: { name: 'my-app', version: '1.0.0' },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
console.log(result.agentInfo.name); // 'openai-agent'
|
|
162
|
+
console.log(result.authMethods); // [{ id: 'oauth2', ... }]
|
|
163
|
+
|
|
164
|
+
await bus.stop();
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Or run any other worker the same way — just change the pool config:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"pools": [
|
|
172
|
+
{
|
|
173
|
+
"id": "acp-registry",
|
|
174
|
+
"command": "npx",
|
|
175
|
+
"args": ["@stdiobus/workers-registry", "acp-registry"],
|
|
176
|
+
"instances": 1
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
See [`@stdiobus/node` on npm](https://www.npmjs.com/package/@stdiobus/node) for TCP mode, Unix socket mode, Docker backend, and full API reference.
|
|
183
|
+
|
|
184
|
+
### Option B: Docker / Binary (TCP mode)
|
|
185
|
+
|
|
186
|
+
#### 1. Install Package
|
|
115
187
|
|
|
116
188
|
```bash
|
|
117
189
|
npm install @stdiobus/workers-registry
|
|
118
190
|
```
|
|
119
191
|
|
|
120
|
-
|
|
192
|
+
#### 2. Get stdio Bus kernel
|
|
121
193
|
|
|
122
194
|
**Option A: Using Docker (recommended)**
|
|
123
195
|
|
|
@@ -129,7 +201,7 @@ docker pull stdiobus/stdiobus:latest
|
|
|
129
201
|
|
|
130
202
|
See [stdio Bus kernel repository](https://github.com/stdiobus/stdiobus) for build instructions.
|
|
131
203
|
|
|
132
|
-
|
|
204
|
+
#### 3. Run with ACP Registry (recommended for real agents)
|
|
133
205
|
|
|
134
206
|
**Create config.json:**
|
|
135
207
|
```json
|
|
@@ -155,7 +227,7 @@ or pass a custom config file (third arg to `launch acp-registry`) with an absolu
|
|
|
155
227
|
Use the same Docker/binary commands below (they run `config.json`), and ensure
|
|
156
228
|
`api-keys.json` is mounted into the container when using Docker.
|
|
157
229
|
|
|
158
|
-
|
|
230
|
+
#### 4. Run with ACP Worker
|
|
159
231
|
|
|
160
232
|
**Note:** `acp-worker` is a standalone ACP agent for SDK/protocol testing. It does **not**
|
|
161
233
|
route to the ACP Registry. Use `acp-registry` when you need real registry agents.
|
|
@@ -191,7 +263,7 @@ docker run -p 9000:9000 \
|
|
|
191
263
|
./stdio_bus --config config.json --tcp 0.0.0.0:9000
|
|
192
264
|
```
|
|
193
265
|
|
|
194
|
-
|
|
266
|
+
#### 5. Test Connection
|
|
195
267
|
|
|
196
268
|
```bash
|
|
197
269
|
# ACP worker (standalone)
|
|
@@ -210,8 +282,8 @@ echo '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"agentId":"claud
|
|
|
210
282
|
The simplest way to run any worker:
|
|
211
283
|
|
|
212
284
|
```bash
|
|
213
|
-
# Run any worker by name (
|
|
214
|
-
|
|
285
|
+
# Run any worker by name (recommended)
|
|
286
|
+
npx @stdiobus/workers-registry <worker-name>
|
|
215
287
|
|
|
216
288
|
# Run any worker by name (this repo, after build)
|
|
217
289
|
node ./launch/index.js <worker-name>
|
|
@@ -222,9 +294,10 @@ node ./launch/index.js <worker-name>
|
|
|
222
294
|
# - echo-worker
|
|
223
295
|
# - mcp-echo-server
|
|
224
296
|
# - mcp-to-acp-proxy
|
|
297
|
+
# - openai-agent
|
|
225
298
|
|
|
226
299
|
# Example: Run echo worker for testing
|
|
227
|
-
|
|
300
|
+
npx @stdiobus/workers-registry echo-worker
|
|
228
301
|
```
|
|
229
302
|
|
|
230
303
|
### Using in stdio Bus Configuration
|
|
@@ -814,14 +887,35 @@ rl.on('close', () => process.exit(0));
|
|
|
814
887
|
|
|
815
888
|
```
|
|
816
889
|
workers-registry/
|
|
890
|
+
├── index.ts # Package entry point
|
|
891
|
+
├── launch/ # Universal launcher (npx entry point)
|
|
817
892
|
├── acp-worker/ # Full ACP protocol implementation
|
|
818
893
|
│ ├── src/
|
|
819
894
|
│ │ ├── agent.ts # ACP Agent implementation
|
|
820
895
|
│ │ ├── index.ts # Main entry point
|
|
896
|
+
│ │ ├── acp/ # ACP protocol layer
|
|
821
897
|
│ │ ├── mcp/ # MCP server integration
|
|
898
|
+
│ │ ├── mcp-proxy/ # MCP-to-ACP proxy logic
|
|
822
899
|
│ │ ├── session/ # Session management
|
|
823
|
-
│ │
|
|
900
|
+
│ │ ├── stdio/ # Session ID routing
|
|
901
|
+
│ │ └── test-utils/ # Testing utilities
|
|
824
902
|
│ └── tests/ # Test suites
|
|
903
|
+
├── registry-launcher/ # Registry Launcher (agent discovery + routing)
|
|
904
|
+
│ └── src/
|
|
905
|
+
│ ├── auth/ # OAuth 2.1 authentication
|
|
906
|
+
│ ├── config/ # Configuration management
|
|
907
|
+
│ ├── registry/ # ACP Registry resolution
|
|
908
|
+
│ ├── router/ # Message routing
|
|
909
|
+
│ ├── runtime/ # Agent runtime management
|
|
910
|
+
│ ├── stream/ # NDJSON stream handling
|
|
911
|
+
│ └── test-utils/ # Testing utilities
|
|
912
|
+
├── openai-agent/ # OpenAI Chat Completions API agent
|
|
913
|
+
│ └── src/
|
|
914
|
+
│ ├── agent.ts # ACP Agent bridging to OpenAI API
|
|
915
|
+
│ ├── client.ts # Chat Completions HTTP + SSE client
|
|
916
|
+
│ ├── sse-parser.ts # SSE line parser
|
|
917
|
+
│ ├── session.ts # Session state management
|
|
918
|
+
│ └── config.ts # Environment-based configuration
|
|
825
919
|
├── acp-registry/ # ACP Registry worker entrypoint + configs
|
|
826
920
|
├── echo-worker/ # Simple echo worker example
|
|
827
921
|
├── mcp-echo-server/ # MCP server example
|
|
@@ -884,8 +978,8 @@ nvm install 20 # If using nvm
|
|
|
884
978
|
**Permission errors:**
|
|
885
979
|
```bash
|
|
886
980
|
npm install -g @stdiobus/workers-registry # Global install (may need sudo)
|
|
887
|
-
# Or use
|
|
888
|
-
|
|
981
|
+
# Or use npx
|
|
982
|
+
npx @stdiobus/workers-registry acp-worker
|
|
889
983
|
```
|
|
890
984
|
|
|
891
985
|
### Runtime Issues
|
|
@@ -958,27 +1052,27 @@ Registry Launcher supports OAuth 2.1 with PKCE for secure browser-based authenti
|
|
|
958
1052
|
|
|
959
1053
|
| Provider | OAuth 2.1 | API Key | Status |
|
|
960
1054
|
|----------|-----------|---------|--------|
|
|
961
|
-
| OpenAI |
|
|
962
|
-
| Anthropic |
|
|
963
|
-
| GitHub |
|
|
964
|
-
| Google |
|
|
965
|
-
| Azure AD |
|
|
966
|
-
| AWS Cognito |
|
|
1055
|
+
| OpenAI | ✓ | ✓ | Production |
|
|
1056
|
+
| Anthropic | ✓ | ✓ | Production |
|
|
1057
|
+
| GitHub | ✓ | ✓ | Production |
|
|
1058
|
+
| Google | ✓ | ✓ | Production |
|
|
1059
|
+
| Azure AD | ✓ | ✓ | Production |
|
|
1060
|
+
| AWS Cognito | ✓ | ✓ | Production |
|
|
967
1061
|
|
|
968
1062
|
### Quick Start
|
|
969
1063
|
|
|
970
1064
|
```bash
|
|
971
1065
|
# Check current authentication status
|
|
972
|
-
|
|
1066
|
+
npx @stdiobus/workers-registry acp-registry --auth-status
|
|
973
1067
|
|
|
974
1068
|
# Login with browser OAuth (opens browser)
|
|
975
|
-
|
|
1069
|
+
npx @stdiobus/workers-registry acp-registry --login openai
|
|
976
1070
|
|
|
977
1071
|
# Interactive setup wizard
|
|
978
|
-
|
|
1072
|
+
npx @stdiobus/workers-registry acp-registry --setup
|
|
979
1073
|
|
|
980
1074
|
# Logout from all providers
|
|
981
|
-
|
|
1075
|
+
npx @stdiobus/workers-registry acp-registry --logout
|
|
982
1076
|
```
|
|
983
1077
|
|
|
984
1078
|
### Backward Compatibility
|
|
@@ -1001,7 +1095,7 @@ echo '{"claude-acp":{"apiKey":"sk-..."}}' > api-keys.json
|
|
|
1001
1095
|
export ANTHROPIC_API_KEY=sk-...
|
|
1002
1096
|
|
|
1003
1097
|
# Option 3: Interactive setup (if TTY available)
|
|
1004
|
-
|
|
1098
|
+
npx @stdiobus/workers-registry acp-registry --setup
|
|
1005
1099
|
```
|
|
1006
1100
|
|
|
1007
1101
|
### Documentation
|
|
@@ -1018,6 +1112,7 @@ node ./launch/index.js acp-registry --setup
|
|
|
1018
1112
|
## Resources
|
|
1019
1113
|
|
|
1020
1114
|
- [stdio Bus kernel](https://github.com/stdiobus/stdiobus) - Core protocol and daemon (source code)
|
|
1115
|
+
- [`@stdiobus/node`](https://www.npmjs.com/package/@stdiobus/node) - Embedded Node.js binding (no Docker/binary needed)
|
|
1021
1116
|
- [stdio Bus on Docker Hub](https://hub.docker.com/r/stdiobus/stdiobus) - Docker images for easy deployment
|
|
1022
1117
|
- [stdio Bus Full Documentation](https://stdiobus.com) – Core protocol documentation
|
|
1023
1118
|
- [ACP Registry](https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json) - Available ACP agents
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{Readable,Writable,Transform}from"node:stream";import{AgentSideConnection,ndJsonStream}from"@agentclientprotocol/sdk";import{PROTOCOL_VERSION}from"@agentclientprotocol/sdk";function loadConfig(){const baseUrl=process.env.OPENAI_BASE_URL||"https://api.openai.com/v1";const apiKey=process.env.OPENAI_API_KEY||"";const model=process.env.OPENAI_MODEL||"gpt-4o";const systemPrompt=process.env.OPENAI_SYSTEM_PROMPT||void 0;let maxTokens;const maxTokensStr=process.env.OPENAI_MAX_TOKENS;if(maxTokensStr!==void 0){const parsed=parseInt(maxTokensStr,10);maxTokens=Number.isNaN(parsed)?void 0:parsed}let temperature;const temperatureStr=process.env.OPENAI_TEMPERATURE;if(temperatureStr!==void 0){const parsed=parseFloat(temperatureStr);temperature=Number.isNaN(parsed)?void 0:parsed}if(!apiKey){console.error("[openai-agent] Warning: OPENAI_API_KEY is not set. This may be fine for local endpoints like Ollama.")}return{baseUrl,apiKey,model,systemPrompt,maxTokens,temperature}}import crypto from"node:crypto";var Session=class{id;cwd;_history=[];_abortController;_cancelled=false;constructor(id,cwd){this.id=id;this.cwd=cwd;this._abortController=new AbortController}getAbortSignal(){return this._abortController.signal}cancel(){this._cancelled=true;this._abortController.abort()}isCancelled(){return this._cancelled}resetCancellation(){this._cancelled=false;this._abortController=new AbortController}addHistoryEntry(role,content){this._history.push({role,content})}getHistory(){return[...this._history]}};var SessionManager=class{sessions=new Map;createSession(cwd){const id=crypto.randomUUID();const session=new Session(id,cwd);this.sessions.set(id,session);return session}getSession(id){return this.sessions.get(id)}cancelSession(id){const session=this.sessions.get(id);if(session){session.cancel();return true}return false}};var DATA_PREFIX="data: ";var DONE_MARKER="[DONE]";function parseLine(line){if(!line.trim()){return{type:"skip"}}if(line.startsWith(":")){return{type:"skip"}}if(!line.startsWith(DATA_PREFIX)){return{type:"skip"}}const data=line.slice(DATA_PREFIX.length);if(data===DONE_MARKER){return{type:"done"}}try{const payload=JSON.parse(data);return{type:"data",payload}}catch{console.error("[openai-agent] Failed to parse SSE JSON:",data);return{type:"skip"}}}function classifyHttpError(status,url){if(status===401||status===403){return`Authentication error (HTTP ${status}) calling ${url}. Check your OPENAI_API_KEY.`}if(status===429){return`Rate limit exceeded (HTTP 429) calling ${url}. Please retry later.`}if(status>=500){return`Server error (HTTP ${status}) from ${url}.`}return`HTTP error (${status}) from ${url}.`}var ChatCompletionsClient=class{config;constructor(config){this.config=config}async streamCompletion(messages,signal,onChunk){const url=`${this.config.baseUrl}/chat/completions`;const body={model:this.config.model,messages,stream:true};if(this.config.maxTokens!==void 0){body.max_tokens=this.config.maxTokens}if(this.config.temperature!==void 0){body.temperature=this.config.temperature}let response;try{response=await fetch(url,{method:"POST",headers:{"Authorization":`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(body),signal})}catch(error){if(error instanceof Error&&error.name==="AbortError"){return{stopReason:"cancelled",fullResponse:""}}const message=error instanceof Error?error.message:String(error);throw new Error(`Network error connecting to ${url}: ${message}`)}if(!response.ok){throw new Error(classifyHttpError(response.status,url))}if(!response.body){throw new Error(`No response body from ${url}.`)}let fullResponse="";const reader=response.body.getReader();const decoder=new TextDecoder;let buffer="";try{while(true){const{done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:true});const lines=buffer.split("\n");buffer=lines.pop()||"";for(const line of lines){const event=parseLine(line);if(event.type==="done"){return{stopReason:"end_turn",fullResponse}}if(event.type==="data"){const chunk=event.payload;const content=chunk.choices?.[0]?.delta?.content;if(content){fullResponse+=content;await onChunk(content)}}}}}catch(error){if(error instanceof Error&&error.name==="AbortError"){return{stopReason:"cancelled",fullResponse}}throw error}return{stopReason:"end_turn",fullResponse}}};function convertContentBlocks(blocks){const parts=[];for(const block of blocks){if(block.type==="text"){parts.push(block.text)}else if(block.type==="resource_link"){parts.push(`[Resource: ${block.name}] ${block.uri}`)}else if(block.type==="resource"){const text="text"in block.resource?block.resource.text:"";parts.push(`[Resource: ${block.resource.uri}]
|
|
3
|
-
${text}`)}else if(block.type==="image"){parts.push(`[Image: ${block.mimeType}]`)}}return parts.join("\n")}function buildMessages(systemPrompt,history,userMessage){const messages=[];if(systemPrompt){messages.push({role:"system",content:systemPrompt})}for(const entry of history){messages.push({role:entry.role,content:entry.content})}messages.push({role:"user",content:userMessage});return messages}var OpenAIAgent=class{connection;sessionManager;client;config;constructor(connection2){this.connection=connection2;this.sessionManager=new SessionManager;this.config=loadConfig();this.client=new ChatCompletionsClient(this.config)}async initialize(_params){return{protocolVersion:PROTOCOL_VERSION,agentInfo:{name:"openai-agent",version:"1.0.0"},agentCapabilities:{promptCapabilities:{embeddedContext:true}},authMethods:[]}}async newSession(params){const session=this.sessionManager.createSession(params.cwd);return{sessionId:session.id}}async loadSession(_params){throw new Error("Session loading is not supported")}async authenticate(_params){}async prompt(params){const session=this.sessionManager.getSession(params.sessionId);if(!session){throw new Error(`Session not found: ${params.sessionId}`)}if(session.isCancelled()){return{stopReason:"cancelled"}}session.resetCancellation();const userMessage=convertContentBlocks(params.prompt);session.addHistoryEntry("user",userMessage);const messages=buildMessages(this.config.systemPrompt,session.getHistory().slice(0,-1),userMessage);try{const result=await this.client.streamCompletion(messages,session.getAbortSignal(),async text=>{await this.connection.sessionUpdate({sessionId:params.sessionId,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text}}})});if(result.stopReason==="cancelled"){return{stopReason:"cancelled"}}if(result.fullResponse){session.addHistoryEntry("assistant",result.fullResponse)}return{stopReason:"end_turn"}}catch(error){const errorMessage=error instanceof Error?error.message:String(error);await this.connection.sessionUpdate({sessionId:params.sessionId,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:errorMessage}}});return{stopReason:"end_turn"}}}async cancel(params){this.sessionManager.cancelSession(params.sessionId)}};var SessionIdRouter=class{requestSessionIdMap=new Map;acpSessionIdMap=new Map;processIncomingLine(line){if(!line.trim()){return line}try{const msg=JSON.parse(line);const routingSessionId=this.readSessionId(msg.sessionId);const hasId=msg.id!==void 0&&msg.id!==null;if(hasId&&routingSessionId){this.requestSessionIdMap.set(msg.id,routingSessionId);console.error(`[worker] Saved sessionId="${routingSessionId}" for request id=${msg.id}`)}const paramsSessionId=this.readSessionId(msg.params?.sessionId);if(routingSessionId&¶msSessionId){this.setAcpSessionMapping(paramsSessionId,routingSessionId,"request")}if(hasId&&routingSessionId){const{sessionId:_sessionId,...msgWithoutSession}=msg;return JSON.stringify(msgWithoutSession)}return line}catch{return line}}processOutgoingLine(line){if(!line.trim()){return line}try{const msg=JSON.parse(line);const hasId=msg.id!==void 0&&msg.id!==null;if(hasId&&this.requestSessionIdMap.has(msg.id)){const routingSessionId=this.requestSessionIdMap.get(msg.id);this.requestSessionIdMap.delete(msg.id);if(routingSessionId){const resultSessionId=this.readSessionId(msg.result?.sessionId);if(resultSessionId){this.setAcpSessionMapping(resultSessionId,routingSessionId,"response")}const msgWithSession={...msg,sessionId:routingSessionId};console.error(`[worker] Restored sessionId="${routingSessionId}" for response id=${msg.id}`);return JSON.stringify(msgWithSession)}}if(!hasId&&!this.readSessionId(msg.sessionId)){const paramsSessionId=this.readSessionId(msg.params?.sessionId);if(paramsSessionId){const routingSessionId=this.acpSessionIdMap.get(paramsSessionId);if(routingSessionId){const msgWithSession={...msg,sessionId:routingSessionId};return JSON.stringify(msgWithSession)}}}return line}catch{return line}}readSessionId(value){return typeof value==="string"&&value.length>0?value:null}setAcpSessionMapping(acpSessionId,routingSessionId,source){const existing=this.acpSessionIdMap.get(acpSessionId);if(existing===routingSessionId){return}this.acpSessionIdMap.set(acpSessionId,routingSessionId);console.error(`[worker] Mapped ACP sessionId="${acpSessionId}" to routing sessionId="${routingSessionId}" (${source})`)}};console.error("[openai-agent] Starting OpenAI Agent Worker...");var sessionIdRouter=new SessionIdRouter;var stdinTransform=new Transform({objectMode:false,transform(chunk,_encoding,callback){const lines=chunk.toString().split("\n");const processedLines=[];for(const line of lines){processedLines.push(sessionIdRouter.processIncomingLine(line))}callback(null,Buffer.from(processedLines.join("\n")))}});var stdoutTransform=new Transform({objectMode:false,transform(chunk,_encoding,callback){const lines=chunk.toString().split("\n");const processedLines=[];for(const line of lines){processedLines.push(sessionIdRouter.processOutgoingLine(line))}callback(null,Buffer.from(processedLines.join("\n")))}});process.stdin.pipe(stdinTransform);var inputStream=Readable.toWeb(stdinTransform);var outputStream=Writable.toWeb(stdoutTransform);stdoutTransform.pipe(process.stdout);var stream=ndJsonStream(outputStream,inputStream);var connection=new AgentSideConnection(conn=>new OpenAIAgent(conn),stream);console.error("[openai-agent] AgentSideConnection established, ready for messages");process.on("SIGTERM",async()=>{console.error("[openai-agent] Received SIGTERM, shutting down...");await connection.closed;process.exit(0)});process.on("SIGINT",async()=>{console.error("[openai-agent] Received SIGINT, shutting down...");await connection.closed;process.exit(0)});process.on("uncaughtException",error=>{console.error("[openai-agent] Uncaught exception:",error);process.exit(1)});process.on("unhandledRejection",(reason,promise)=>{console.error("[openai-agent] Unhandled rejection at:",promise,"reason:",reason)});connection.closed.then(()=>{console.error("[openai-agent] Connection closed");process.exit(0)}).catch(error=>{console.error("[openai-agent] Connection error:",error);process.exit(1)});
|
|
3
|
+
${text}`)}else if(block.type==="image"){parts.push(`[Image: ${block.mimeType}]`)}}return parts.join("\n")}function buildMessages(systemPrompt,history,userMessage){const messages=[];if(systemPrompt){messages.push({role:"system",content:systemPrompt})}for(const entry of history){messages.push({role:entry.role,content:entry.content})}messages.push({role:"user",content:userMessage});return messages}var OpenAIAgent=class{connection;sessionManager;client;config;constructor(connection2){this.connection=connection2;this.sessionManager=new SessionManager;this.config=loadConfig();this.client=new ChatCompletionsClient(this.config)}async initialize(_params){return{protocolVersion:PROTOCOL_VERSION,agentInfo:{name:"openai-agent",version:"1.0.0"},agentCapabilities:{promptCapabilities:{embeddedContext:true}},authMethods:[{id:"oauth2",name:"OAuth 2.1 Authentication",description:"Authenticate through agent via OAuth 2.1 flow",_meta:{"agent-auth":true}}]}}async newSession(params){const session=this.sessionManager.createSession(params.cwd);return{sessionId:session.id}}async loadSession(_params){throw new Error("Session loading is not supported")}async authenticate(_params){}async prompt(params){const session=this.sessionManager.getSession(params.sessionId);if(!session){throw new Error(`Session not found: ${params.sessionId}`)}if(session.isCancelled()){return{stopReason:"cancelled"}}session.resetCancellation();const userMessage=convertContentBlocks(params.prompt);session.addHistoryEntry("user",userMessage);const messages=buildMessages(this.config.systemPrompt,session.getHistory().slice(0,-1),userMessage);try{const result=await this.client.streamCompletion(messages,session.getAbortSignal(),async text=>{await this.connection.sessionUpdate({sessionId:params.sessionId,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text}}})});if(result.stopReason==="cancelled"){return{stopReason:"cancelled"}}if(result.fullResponse){session.addHistoryEntry("assistant",result.fullResponse)}return{stopReason:"end_turn"}}catch(error){const errorMessage=error instanceof Error?error.message:String(error);await this.connection.sessionUpdate({sessionId:params.sessionId,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:errorMessage}}});return{stopReason:"end_turn"}}}async cancel(params){this.sessionManager.cancelSession(params.sessionId)}};var SessionIdRouter=class{requestSessionIdMap=new Map;acpSessionIdMap=new Map;processIncomingLine(line){if(!line.trim()){return line}try{const msg=JSON.parse(line);const routingSessionId=this.readSessionId(msg.sessionId);const hasId=msg.id!==void 0&&msg.id!==null;if(hasId&&routingSessionId){this.requestSessionIdMap.set(msg.id,routingSessionId);console.error(`[worker] Saved sessionId="${routingSessionId}" for request id=${msg.id}`)}const paramsSessionId=this.readSessionId(msg.params?.sessionId);if(routingSessionId&¶msSessionId){this.setAcpSessionMapping(paramsSessionId,routingSessionId,"request")}if(hasId&&routingSessionId){const{sessionId:_sessionId,...msgWithoutSession}=msg;return JSON.stringify(msgWithoutSession)}return line}catch{return line}}processOutgoingLine(line){if(!line.trim()){return line}try{const msg=JSON.parse(line);const hasId=msg.id!==void 0&&msg.id!==null;if(hasId&&this.requestSessionIdMap.has(msg.id)){const routingSessionId=this.requestSessionIdMap.get(msg.id);this.requestSessionIdMap.delete(msg.id);if(routingSessionId){const resultSessionId=this.readSessionId(msg.result?.sessionId);if(resultSessionId){this.setAcpSessionMapping(resultSessionId,routingSessionId,"response")}const msgWithSession={...msg,sessionId:routingSessionId};console.error(`[worker] Restored sessionId="${routingSessionId}" for response id=${msg.id}`);return JSON.stringify(msgWithSession)}}if(!hasId&&!this.readSessionId(msg.sessionId)){const paramsSessionId=this.readSessionId(msg.params?.sessionId);if(paramsSessionId){const routingSessionId=this.acpSessionIdMap.get(paramsSessionId);if(routingSessionId){const msgWithSession={...msg,sessionId:routingSessionId};return JSON.stringify(msgWithSession)}}}return line}catch{return line}}readSessionId(value){return typeof value==="string"&&value.length>0?value:null}setAcpSessionMapping(acpSessionId,routingSessionId,source){const existing=this.acpSessionIdMap.get(acpSessionId);if(existing===routingSessionId){return}this.acpSessionIdMap.set(acpSessionId,routingSessionId);console.error(`[worker] Mapped ACP sessionId="${acpSessionId}" to routing sessionId="${routingSessionId}" (${source})`)}};console.error("[openai-agent] Starting OpenAI Agent Worker...");var sessionIdRouter=new SessionIdRouter;var stdinTransform=new Transform({objectMode:false,transform(chunk,_encoding,callback){const lines=chunk.toString().split("\n");const processedLines=[];for(const line of lines){processedLines.push(sessionIdRouter.processIncomingLine(line))}callback(null,Buffer.from(processedLines.join("\n")))}});var stdoutTransform=new Transform({objectMode:false,transform(chunk,_encoding,callback){const lines=chunk.toString().split("\n");const processedLines=[];for(const line of lines){processedLines.push(sessionIdRouter.processOutgoingLine(line))}callback(null,Buffer.from(processedLines.join("\n")))}});process.stdin.pipe(stdinTransform);var inputStream=Readable.toWeb(stdinTransform);var outputStream=Writable.toWeb(stdoutTransform);stdoutTransform.pipe(process.stdout);var stream=ndJsonStream(outputStream,inputStream);var connection=new AgentSideConnection(conn=>new OpenAIAgent(conn),stream);console.error("[openai-agent] AgentSideConnection established, ready for messages");process.on("SIGTERM",async()=>{console.error("[openai-agent] Received SIGTERM, shutting down...");await connection.closed;process.exit(0)});process.on("SIGINT",async()=>{console.error("[openai-agent] Received SIGINT, shutting down...");await connection.closed;process.exit(0)});process.on("uncaughtException",error=>{console.error("[openai-agent] Uncaught exception:",error);process.exit(1)});process.on("unhandledRejection",(reason,promise)=>{console.error("[openai-agent] Unhandled rejection at:",promise,"reason:",reason)});connection.closed.then(()=>{console.error("[openai-agent] Connection closed");process.exit(0)}).catch(error=>{console.error("[openai-agent] Connection error:",error);process.exit(1)});
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../workers-registry/openai-agent/src/index.ts", "../../../../workers-registry/openai-agent/src/agent.ts", "../../../../workers-registry/openai-agent/src/config.ts", "../../../../workers-registry/openai-agent/src/session-manager.ts", "../../../../workers-registry/openai-agent/src/session.ts", "../../../../workers-registry/openai-agent/src/sse-parser.ts", "../../../../workers-registry/openai-agent/src/client.ts", "../../../../workers-registry/openai-agent/src/session-id-router.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * OpenAI Agent Worker for stdio Bus kernel\n *\n * This worker implements the Agent Client Protocol (ACP) and bridges\n * to any OpenAI Chat Completions API-compatible endpoint.\n *\n * It runs as a child process of stdio Bus kernel, communicating via stdin/stdout NDJSON.\n *\n * @module index\n */\n\nimport { Readable, Writable, Transform } from 'node:stream';\nimport { AgentSideConnection, ndJsonStream } from '@agentclientprotocol/sdk';\nimport { OpenAIAgent } from './agent.js';\nimport { SessionIdRouter } from './session-id-router.js';\n\n// Log startup message to stderr (not stdout - stdout is for protocol messages)\nconsole.error('[openai-agent] Starting OpenAI Agent Worker...');\n\nconst sessionIdRouter = new SessionIdRouter();\n\n/**\n * Transform stream to intercept stdin and save sessionId from requests.\n * Removes sessionId before passing to ACP SDK (SDK doesn't know about it).\n */\nconst stdinTransform = new Transform({\n objectMode: false,\n transform(chunk: Buffer, _encoding, callback) {\n const lines = chunk.toString().split('\\n');\n const processedLines: string[] = [];\n\n for (const line of lines) {\n processedLines.push(sessionIdRouter.processIncomingLine(line));\n }\n\n callback(null, Buffer.from(processedLines.join('\\n')));\n },\n});\n\n/**\n * Transform stream to intercept stdout and restore sessionId in responses.\n * Adds sessionId back for stdio_bus routing.\n */\nconst stdoutTransform = new Transform({\n objectMode: false,\n transform(chunk: Buffer, _encoding, callback) {\n const lines = chunk.toString().split('\\n');\n const processedLines: string[] = [];\n\n for (const line of lines) {\n processedLines.push(sessionIdRouter.processOutgoingLine(line));\n }\n\n callback(null, Buffer.from(processedLines.join('\\n')));\n },\n});\n\n// Pipe stdin through transform before SDK\nprocess.stdin.pipe(stdinTransform);\n\n/**\n * Convert transformed stdin to web ReadableStream for SDK.\n */\nconst inputStream = Readable.toWeb(stdinTransform) as ReadableStream<Uint8Array>;\n\n/**\n * Convert stdout transform to web WritableStream for SDK.\n */\nconst outputStream = Writable.toWeb(stdoutTransform) as WritableStream<Uint8Array>;\n\n// Pipe transform output to actual stdout\nstdoutTransform.pipe(process.stdout);\n\n/**\n * Create the NDJSON stream for ACP communication.\n * The SDK handles all NDJSON framing and JSON-RPC protocol details automatically.\n */\nconst stream = ndJsonStream(outputStream, inputStream);\n\n/**\n * Create the AgentSideConnection with stdio transport.\n *\n * The SDK pattern uses a factory function that receives the connection\n * and returns an Agent instance. The SDK handles all NDJSON framing\n * and JSON-RPC protocol details automatically.\n */\nconst connection = new AgentSideConnection(\n (conn) => new OpenAIAgent(conn),\n stream,\n);\n\n// Log that connection is established\nconsole.error('[openai-agent] AgentSideConnection established, ready for messages');\n\n/**\n * Handle graceful shutdown on SIGTERM.\n *\n * When stdio Bus kernel sends SIGTERM, we should wait for the connection to close\n * and allow pending operations to complete.\n */\nprocess.on('SIGTERM', async () => {\n console.error('[openai-agent] Received SIGTERM, shutting down...');\n await connection.closed;\n process.exit(0);\n});\n\n/**\n * Handle SIGINT for development convenience.\n */\nprocess.on('SIGINT', async () => {\n console.error('[openai-agent] Received SIGINT, shutting down...');\n await connection.closed;\n process.exit(0);\n});\n\n/**\n * Handle uncaught exceptions to prevent silent failures.\n */\nprocess.on('uncaughtException', (error) => {\n console.error('[openai-agent] Uncaught exception:', error);\n process.exit(1);\n});\n\n/**\n * Handle unhandled promise rejections.\n */\nprocess.on('unhandledRejection', (reason, promise) => {\n console.error('[openai-agent] Unhandled rejection at:', promise, 'reason:', reason);\n});\n\n/**\n * Wait for the connection to close (either normally or due to error).\n * This keeps the process running until the connection ends.\n */\nconnection.closed.then(() => {\n console.error('[openai-agent] Connection closed');\n process.exit(0);\n}).catch((error) => {\n console.error('[openai-agent] Connection error:', error);\n process.exit(1);\n});\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n Agent,\n AgentSideConnection,\n AuthenticateRequest,\n AuthenticateResponse,\n CancelNotification,\n ContentBlock,\n InitializeRequest,\n InitializeResponse,\n LoadSessionRequest,\n LoadSessionResponse,\n NewSessionRequest,\n NewSessionResponse,\n PromptRequest,\n PromptResponse,\n} from '@agentclientprotocol/sdk';\nimport { PROTOCOL_VERSION } from '@agentclientprotocol/sdk';\nimport { loadConfig } from './config.js';\nimport { SessionManager } from './session-manager.js';\nimport { ChatCompletionsClient } from './client.js';\nimport type { OpenAIMessage } from './types.js';\n\n/**\n * Convert ACP content blocks to a single user message string.\n */\nexport function convertContentBlocks(blocks: ContentBlock[]): string {\n const parts: string[] = [];\n for (const block of blocks) {\n if (block.type === 'text') {\n parts.push(block.text);\n } else if (block.type === 'resource_link') {\n parts.push(`[Resource: ${block.name}] ${block.uri}`);\n } else if (block.type === 'resource') {\n const text = 'text' in block.resource ? block.resource.text : '';\n parts.push(`[Resource: ${block.resource.uri}]\\n${text}`);\n } else if (block.type === 'image') {\n parts.push(`[Image: ${block.mimeType}]`);\n }\n }\n return parts.join('\\n');\n}\n\n/**\n * Build the full messages array for the Chat Completions API.\n */\nexport function buildMessages(\n systemPrompt: string | undefined,\n history: Array<{ role: 'user' | 'assistant'; content: string }>,\n userMessage: string,\n): OpenAIMessage[] {\n const messages: OpenAIMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n for (const entry of history) {\n messages.push({ role: entry.role, content: entry.content });\n }\n messages.push({ role: 'user', content: userMessage });\n return messages;\n}\n\nexport class OpenAIAgent implements Agent {\n private readonly connection: AgentSideConnection;\n private readonly sessionManager: SessionManager;\n private readonly client: ChatCompletionsClient;\n private readonly config;\n\n constructor(connection: AgentSideConnection) {\n this.connection = connection;\n this.sessionManager = new SessionManager();\n this.config = loadConfig();\n this.client = new ChatCompletionsClient(this.config);\n }\n\n async initialize(_params: InitializeRequest): Promise<InitializeResponse> {\n return {\n protocolVersion: PROTOCOL_VERSION,\n agentInfo: {\n name: 'openai-agent',\n version: '1.0.0',\n },\n agentCapabilities: {\n promptCapabilities: {\n embeddedContext: true,\n },\n },\n authMethods: [],\n };\n }\n\n async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {\n const session = this.sessionManager.createSession(params.cwd);\n return { sessionId: session.id };\n }\n\n async loadSession(_params: LoadSessionRequest): Promise<LoadSessionResponse> {\n throw new Error('Session loading is not supported');\n }\n\n async authenticate(_params: AuthenticateRequest): Promise<AuthenticateResponse | void> {\n // No authentication needed at ACP level\n }\n\n async prompt(params: PromptRequest): Promise<PromptResponse> {\n const session = this.sessionManager.getSession(params.sessionId);\n if (!session) {\n throw new Error(`Session not found: ${params.sessionId}`);\n }\n\n if (session.isCancelled()) {\n return { stopReason: 'cancelled' };\n }\n\n session.resetCancellation();\n\n const userMessage = convertContentBlocks(params.prompt);\n session.addHistoryEntry('user', userMessage);\n\n const messages = buildMessages(\n this.config.systemPrompt,\n session.getHistory().slice(0, -1),\n userMessage,\n );\n\n try {\n const result = await this.client.streamCompletion(\n messages,\n session.getAbortSignal(),\n async (text) => {\n await this.connection.sessionUpdate({\n sessionId: params.sessionId,\n update: {\n sessionUpdate: 'agent_message_chunk',\n content: { type: 'text', text },\n },\n });\n },\n );\n\n if (result.stopReason === 'cancelled') {\n return { stopReason: 'cancelled' };\n }\n\n if (result.fullResponse) {\n session.addHistoryEntry('assistant', result.fullResponse);\n }\n\n return { stopReason: 'end_turn' };\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.connection.sessionUpdate({\n sessionId: params.sessionId,\n update: {\n sessionUpdate: 'agent_message_chunk',\n content: { type: 'text', text: errorMessage },\n },\n });\n return { stopReason: 'end_turn' };\n }\n }\n\n async cancel(params: CancelNotification): Promise<void> {\n this.sessionManager.cancelSession(params.sessionId);\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { AgentConfig } from './types.js';\n\nexport function loadConfig(): AgentConfig {\n const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';\n const apiKey = process.env.OPENAI_API_KEY || '';\n const model = process.env.OPENAI_MODEL || 'gpt-4o';\n const systemPrompt = process.env.OPENAI_SYSTEM_PROMPT || undefined;\n\n let maxTokens: number | undefined;\n const maxTokensStr = process.env.OPENAI_MAX_TOKENS;\n if (maxTokensStr !== undefined) {\n const parsed = parseInt(maxTokensStr, 10);\n maxTokens = Number.isNaN(parsed) ? undefined : parsed;\n }\n\n let temperature: number | undefined;\n const temperatureStr = process.env.OPENAI_TEMPERATURE;\n if (temperatureStr !== undefined) {\n const parsed = parseFloat(temperatureStr);\n temperature = Number.isNaN(parsed) ? undefined : parsed;\n }\n\n if (!apiKey) {\n console.error('[openai-agent] Warning: OPENAI_API_KEY is not set. This may be fine for local endpoints like Ollama.');\n }\n\n return { baseUrl, apiKey, model, systemPrompt, maxTokens, temperature };\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport crypto from 'node:crypto';\nimport { Session } from './session.js';\n\nexport class SessionManager {\n private readonly sessions = new Map<string, Session>();\n\n createSession(cwd: string): Session {\n const id = crypto.randomUUID();\n const session = new Session(id, cwd);\n this.sessions.set(id, session);\n return session;\n }\n\n getSession(id: string): Session | undefined {\n return this.sessions.get(id);\n }\n\n cancelSession(id: string): boolean {\n const session = this.sessions.get(id);\n if (session) {\n session.cancel();\n return true;\n }\n return false;\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nexport interface HistoryEntry {\n role: 'user' | 'assistant';\n content: string;\n}\n\nexport class Session {\n readonly id: string;\n readonly cwd: string;\n private _history: HistoryEntry[] = [];\n private _abortController: AbortController;\n private _cancelled = false;\n\n constructor(id: string, cwd: string) {\n this.id = id;\n this.cwd = cwd;\n this._abortController = new AbortController();\n }\n\n getAbortSignal(): AbortSignal {\n return this._abortController.signal;\n }\n\n cancel(): void {\n this._cancelled = true;\n this._abortController.abort();\n }\n\n isCancelled(): boolean {\n return this._cancelled;\n }\n\n resetCancellation(): void {\n this._cancelled = false;\n this._abortController = new AbortController();\n }\n\n addHistoryEntry(role: 'user' | 'assistant', content: string): void {\n this._history.push({ role, content });\n }\n\n getHistory(): HistoryEntry[] {\n return [...this._history];\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { SSEEvent } from './types.js';\n\nconst DATA_PREFIX = 'data: ';\nconst DONE_MARKER = '[DONE]';\n\nexport function parseLine(line: string): SSEEvent {\n // Skip empty lines and whitespace-only lines\n if (!line.trim()) {\n return { type: 'skip' };\n }\n\n // Skip SSE comments (lines starting with :)\n if (line.startsWith(':')) {\n return { type: 'skip' };\n }\n\n // Check for data: prefix\n if (!line.startsWith(DATA_PREFIX)) {\n return { type: 'skip' };\n }\n\n const data = line.slice(DATA_PREFIX.length);\n\n // Check for [DONE] marker\n if (data === DONE_MARKER) {\n return { type: 'done' };\n }\n\n // Try to parse JSON\n try {\n const payload = JSON.parse(data);\n return { type: 'data', payload };\n } catch {\n console.error('[openai-agent] Failed to parse SSE JSON:', data);\n return { type: 'skip' };\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { AgentConfig, ChatCompletionResult, ChatCompletionsRequest, OpenAIMessage, SSEChunk } from './types.js';\nimport { parseLine } from './sse-parser.js';\n\nexport function classifyHttpError(status: number, url: string): string {\n if (status === 401 || status === 403) {\n return `Authentication error (HTTP ${status}) calling ${url}. Check your OPENAI_API_KEY.`;\n }\n if (status === 429) {\n return `Rate limit exceeded (HTTP 429) calling ${url}. Please retry later.`;\n }\n if (status >= 500) {\n return `Server error (HTTP ${status}) from ${url}.`;\n }\n return `HTTP error (${status}) from ${url}.`;\n}\n\nexport class ChatCompletionsClient {\n private readonly config: AgentConfig;\n\n constructor(config: AgentConfig) {\n this.config = config;\n }\n\n async streamCompletion(\n messages: OpenAIMessage[],\n signal: AbortSignal,\n onChunk: (text: string) => Promise<void>,\n ): Promise<ChatCompletionResult> {\n const url = `${this.config.baseUrl}/chat/completions`;\n const body: ChatCompletionsRequest = {\n model: this.config.model,\n messages,\n stream: true,\n };\n if (this.config.maxTokens !== undefined) {\n body.max_tokens = this.config.maxTokens;\n }\n if (this.config.temperature !== undefined) {\n body.temperature = this.config.temperature;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n });\n } catch (error: unknown) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { stopReason: 'cancelled', fullResponse: '' };\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Network error connecting to ${url}: ${message}`);\n }\n\n if (!response.ok) {\n throw new Error(classifyHttpError(response.status, url));\n }\n\n if (!response.body) {\n throw new Error(`No response body from ${url}.`);\n }\n\n let fullResponse = '';\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n // Keep the last potentially incomplete line in the buffer\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n const event = parseLine(line);\n if (event.type === 'done') {\n return { stopReason: 'end_turn', fullResponse };\n }\n if (event.type === 'data') {\n const chunk = event.payload as SSEChunk;\n const content = chunk.choices?.[0]?.delta?.content;\n if (content) {\n fullResponse += content;\n await onChunk(content);\n }\n }\n }\n }\n } catch (error: unknown) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { stopReason: 'cancelled', fullResponse };\n }\n throw error;\n }\n\n return { stopReason: 'end_turn', fullResponse };\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Session ID routing helper for stdio Bus \u2194 ACP messages.\n *\n * Tracks request IDs to restore stdio Bus sessionId on responses,\n * and maps ACP sessionId to stdio Bus sessionId for notifications.\n */\n\ntype JsonRecord = Record<string, unknown>;\n\nexport class SessionIdRouter {\n private readonly requestSessionIdMap = new Map<string | number, string>();\n private readonly acpSessionIdMap = new Map<string, string>();\n\n /**\n * Process a single inbound (stdin) line.\n *\n * - Saves stdio Bus sessionId for request/response correlation\n * - Tracks ACP sessionId \u2194 stdio Bus sessionId mapping when available\n * - Strips stdio Bus sessionId before passing to ACP SDK\n */\n processIncomingLine(line: string): string {\n if (!line.trim()) {\n return line;\n }\n\n try {\n const msg = JSON.parse(line) as JsonRecord;\n const routingSessionId = this.readSessionId(msg.sessionId);\n const hasId = msg.id !== undefined && msg.id !== null;\n\n if (hasId && routingSessionId) {\n this.requestSessionIdMap.set(msg.id as string | number, routingSessionId);\n console.error(`[worker] Saved sessionId=\"${routingSessionId}\" for request id=${msg.id}`);\n }\n\n const paramsSessionId = this.readSessionId((msg.params as JsonRecord | undefined)?.sessionId);\n if (routingSessionId && paramsSessionId) {\n this.setAcpSessionMapping(paramsSessionId, routingSessionId, 'request');\n }\n\n if (hasId && routingSessionId) {\n const { sessionId: _sessionId, ...msgWithoutSession } = msg;\n return JSON.stringify(msgWithoutSession);\n }\n\n return line;\n } catch {\n return line;\n }\n }\n\n /**\n * Process a single outbound (stdout) line.\n *\n * - Restores stdio Bus sessionId on responses using request mapping\n * - Maps ACP sessionId to stdio Bus sessionId for notifications\n */\n processOutgoingLine(line: string): string {\n if (!line.trim()) {\n return line;\n }\n\n try {\n const msg = JSON.parse(line) as JsonRecord;\n const hasId = msg.id !== undefined && msg.id !== null;\n\n if (hasId && this.requestSessionIdMap.has(msg.id as string | number)) {\n const routingSessionId = this.requestSessionIdMap.get(msg.id as string | number);\n this.requestSessionIdMap.delete(msg.id as string | number);\n\n if (routingSessionId) {\n const resultSessionId = this.readSessionId(\n (msg.result as JsonRecord | undefined)?.sessionId,\n );\n if (resultSessionId) {\n this.setAcpSessionMapping(resultSessionId, routingSessionId, 'response');\n }\n\n const msgWithSession = { ...msg, sessionId: routingSessionId };\n console.error(\n `[worker] Restored sessionId=\"${routingSessionId}\" for response id=${msg.id}`,\n );\n return JSON.stringify(msgWithSession);\n }\n }\n\n if (!hasId && !this.readSessionId(msg.sessionId)) {\n const paramsSessionId = this.readSessionId(\n (msg.params as JsonRecord | undefined)?.sessionId,\n );\n if (paramsSessionId) {\n const routingSessionId = this.acpSessionIdMap.get(paramsSessionId);\n if (routingSessionId) {\n const msgWithSession = { ...msg, sessionId: routingSessionId };\n return JSON.stringify(msgWithSession);\n }\n }\n }\n\n return line;\n } catch {\n return line;\n }\n }\n\n private readSessionId(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n }\n\n private setAcpSessionMapping(\n acpSessionId: string,\n routingSessionId: string,\n source: 'request' | 'response',\n ): void {\n const existing = this.acpSessionIdMap.get(acpSessionId);\n if (existing === routingSessionId) {\n return;\n }\n\n this.acpSessionIdMap.set(acpSessionId, routingSessionId);\n console.error(\n `[worker] Mapped ACP sessionId=\"${acpSessionId}\" ` +\n `to routing sessionId=\"${routingSessionId}\" (${source})`,\n );\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * OpenAI Agent Worker for stdio Bus kernel\n *\n * This worker implements the Agent Client Protocol (ACP) and bridges\n * to any OpenAI Chat Completions API-compatible endpoint.\n *\n * It runs as a child process of stdio Bus kernel, communicating via stdin/stdout NDJSON.\n *\n * @module index\n */\n\nimport { Readable, Writable, Transform } from 'node:stream';\nimport { AgentSideConnection, ndJsonStream } from '@agentclientprotocol/sdk';\nimport { OpenAIAgent } from './agent.js';\nimport { SessionIdRouter } from './session-id-router.js';\n\n// Log startup message to stderr (not stdout - stdout is for protocol messages)\nconsole.error('[openai-agent] Starting OpenAI Agent Worker...');\n\nconst sessionIdRouter = new SessionIdRouter();\n\n/**\n * Transform stream to intercept stdin and save sessionId from requests.\n * Removes sessionId before passing to ACP SDK (SDK doesn't know about it).\n */\nconst stdinTransform = new Transform({\n objectMode: false,\n transform(chunk: Buffer, _encoding, callback) {\n const lines = chunk.toString().split('\\n');\n const processedLines: string[] = [];\n\n for (const line of lines) {\n processedLines.push(sessionIdRouter.processIncomingLine(line));\n }\n\n callback(null, Buffer.from(processedLines.join('\\n')));\n },\n});\n\n/**\n * Transform stream to intercept stdout and restore sessionId in responses.\n * Adds sessionId back for stdio_bus routing.\n */\nconst stdoutTransform = new Transform({\n objectMode: false,\n transform(chunk: Buffer, _encoding, callback) {\n const lines = chunk.toString().split('\\n');\n const processedLines: string[] = [];\n\n for (const line of lines) {\n processedLines.push(sessionIdRouter.processOutgoingLine(line));\n }\n\n callback(null, Buffer.from(processedLines.join('\\n')));\n },\n});\n\n// Pipe stdin through transform before SDK\nprocess.stdin.pipe(stdinTransform);\n\n/**\n * Convert transformed stdin to web ReadableStream for SDK.\n */\nconst inputStream = Readable.toWeb(stdinTransform) as ReadableStream<Uint8Array>;\n\n/**\n * Convert stdout transform to web WritableStream for SDK.\n */\nconst outputStream = Writable.toWeb(stdoutTransform) as WritableStream<Uint8Array>;\n\n// Pipe transform output to actual stdout\nstdoutTransform.pipe(process.stdout);\n\n/**\n * Create the NDJSON stream for ACP communication.\n * The SDK handles all NDJSON framing and JSON-RPC protocol details automatically.\n */\nconst stream = ndJsonStream(outputStream, inputStream);\n\n/**\n * Create the AgentSideConnection with stdio transport.\n *\n * The SDK pattern uses a factory function that receives the connection\n * and returns an Agent instance. The SDK handles all NDJSON framing\n * and JSON-RPC protocol details automatically.\n */\nconst connection = new AgentSideConnection(\n (conn) => new OpenAIAgent(conn),\n stream,\n);\n\n// Log that connection is established\nconsole.error('[openai-agent] AgentSideConnection established, ready for messages');\n\n/**\n * Handle graceful shutdown on SIGTERM.\n *\n * When stdio Bus kernel sends SIGTERM, we should wait for the connection to close\n * and allow pending operations to complete.\n */\nprocess.on('SIGTERM', async () => {\n console.error('[openai-agent] Received SIGTERM, shutting down...');\n await connection.closed;\n process.exit(0);\n});\n\n/**\n * Handle SIGINT for development convenience.\n */\nprocess.on('SIGINT', async () => {\n console.error('[openai-agent] Received SIGINT, shutting down...');\n await connection.closed;\n process.exit(0);\n});\n\n/**\n * Handle uncaught exceptions to prevent silent failures.\n */\nprocess.on('uncaughtException', (error) => {\n console.error('[openai-agent] Uncaught exception:', error);\n process.exit(1);\n});\n\n/**\n * Handle unhandled promise rejections.\n */\nprocess.on('unhandledRejection', (reason, promise) => {\n console.error('[openai-agent] Unhandled rejection at:', promise, 'reason:', reason);\n});\n\n/**\n * Wait for the connection to close (either normally or due to error).\n * This keeps the process running until the connection ends.\n */\nconnection.closed.then(() => {\n console.error('[openai-agent] Connection closed');\n process.exit(0);\n}).catch((error) => {\n console.error('[openai-agent] Connection error:', error);\n process.exit(1);\n});\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n Agent,\n AgentSideConnection,\n AuthenticateRequest,\n AuthenticateResponse,\n CancelNotification,\n ContentBlock,\n InitializeRequest,\n InitializeResponse,\n LoadSessionRequest,\n LoadSessionResponse,\n NewSessionRequest,\n NewSessionResponse,\n PromptRequest,\n PromptResponse,\n} from '@agentclientprotocol/sdk';\nimport { PROTOCOL_VERSION } from '@agentclientprotocol/sdk';\nimport { loadConfig } from './config.js';\nimport { SessionManager } from './session-manager.js';\nimport { ChatCompletionsClient } from './client.js';\nimport type { OpenAIMessage } from './types.js';\n\n/**\n * Convert ACP content blocks to a single user message string.\n */\nexport function convertContentBlocks(blocks: ContentBlock[]): string {\n const parts: string[] = [];\n for (const block of blocks) {\n if (block.type === 'text') {\n parts.push(block.text);\n } else if (block.type === 'resource_link') {\n parts.push(`[Resource: ${block.name}] ${block.uri}`);\n } else if (block.type === 'resource') {\n const text = 'text' in block.resource ? block.resource.text : '';\n parts.push(`[Resource: ${block.resource.uri}]\\n${text}`);\n } else if (block.type === 'image') {\n parts.push(`[Image: ${block.mimeType}]`);\n }\n }\n return parts.join('\\n');\n}\n\n/**\n * Build the full messages array for the Chat Completions API.\n */\nexport function buildMessages(\n systemPrompt: string | undefined,\n history: Array<{ role: 'user' | 'assistant'; content: string }>,\n userMessage: string,\n): OpenAIMessage[] {\n const messages: OpenAIMessage[] = [];\n if (systemPrompt) {\n messages.push({ role: 'system', content: systemPrompt });\n }\n for (const entry of history) {\n messages.push({ role: entry.role, content: entry.content });\n }\n messages.push({ role: 'user', content: userMessage });\n return messages;\n}\n\nexport class OpenAIAgent implements Agent {\n private readonly connection: AgentSideConnection;\n private readonly sessionManager: SessionManager;\n private readonly client: ChatCompletionsClient;\n private readonly config;\n\n constructor(connection: AgentSideConnection) {\n this.connection = connection;\n this.sessionManager = new SessionManager();\n this.config = loadConfig();\n this.client = new ChatCompletionsClient(this.config);\n }\n\n async initialize(_params: InitializeRequest): Promise<InitializeResponse> {\n return {\n protocolVersion: PROTOCOL_VERSION,\n agentInfo: {\n name: 'openai-agent',\n version: '1.0.0',\n },\n agentCapabilities: {\n promptCapabilities: {\n embeddedContext: true,\n },\n },\n authMethods: [\n {\n id: 'oauth2',\n name: 'OAuth 2.1 Authentication',\n description: 'Authenticate through agent via OAuth 2.1 flow',\n _meta: { 'agent-auth': true },\n },\n ],\n };\n }\n\n async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {\n const session = this.sessionManager.createSession(params.cwd);\n return { sessionId: session.id };\n }\n\n async loadSession(_params: LoadSessionRequest): Promise<LoadSessionResponse> {\n throw new Error('Session loading is not supported');\n }\n\n async authenticate(_params: AuthenticateRequest): Promise<AuthenticateResponse | void> {\n // No authentication needed at ACP level\n }\n\n async prompt(params: PromptRequest): Promise<PromptResponse> {\n const session = this.sessionManager.getSession(params.sessionId);\n if (!session) {\n throw new Error(`Session not found: ${params.sessionId}`);\n }\n\n if (session.isCancelled()) {\n return { stopReason: 'cancelled' };\n }\n\n session.resetCancellation();\n\n const userMessage = convertContentBlocks(params.prompt);\n session.addHistoryEntry('user', userMessage);\n\n const messages = buildMessages(\n this.config.systemPrompt,\n session.getHistory().slice(0, -1),\n userMessage,\n );\n\n try {\n const result = await this.client.streamCompletion(\n messages,\n session.getAbortSignal(),\n async (text) => {\n await this.connection.sessionUpdate({\n sessionId: params.sessionId,\n update: {\n sessionUpdate: 'agent_message_chunk',\n content: { type: 'text', text },\n },\n });\n },\n );\n\n if (result.stopReason === 'cancelled') {\n return { stopReason: 'cancelled' };\n }\n\n if (result.fullResponse) {\n session.addHistoryEntry('assistant', result.fullResponse);\n }\n\n return { stopReason: 'end_turn' };\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.connection.sessionUpdate({\n sessionId: params.sessionId,\n update: {\n sessionUpdate: 'agent_message_chunk',\n content: { type: 'text', text: errorMessage },\n },\n });\n return { stopReason: 'end_turn' };\n }\n }\n\n async cancel(params: CancelNotification): Promise<void> {\n this.sessionManager.cancelSession(params.sessionId);\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { AgentConfig } from './types.js';\n\nexport function loadConfig(): AgentConfig {\n const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';\n const apiKey = process.env.OPENAI_API_KEY || '';\n const model = process.env.OPENAI_MODEL || 'gpt-4o';\n const systemPrompt = process.env.OPENAI_SYSTEM_PROMPT || undefined;\n\n let maxTokens: number | undefined;\n const maxTokensStr = process.env.OPENAI_MAX_TOKENS;\n if (maxTokensStr !== undefined) {\n const parsed = parseInt(maxTokensStr, 10);\n maxTokens = Number.isNaN(parsed) ? undefined : parsed;\n }\n\n let temperature: number | undefined;\n const temperatureStr = process.env.OPENAI_TEMPERATURE;\n if (temperatureStr !== undefined) {\n const parsed = parseFloat(temperatureStr);\n temperature = Number.isNaN(parsed) ? undefined : parsed;\n }\n\n if (!apiKey) {\n console.error('[openai-agent] Warning: OPENAI_API_KEY is not set. This may be fine for local endpoints like Ollama.');\n }\n\n return { baseUrl, apiKey, model, systemPrompt, maxTokens, temperature };\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport crypto from 'node:crypto';\nimport { Session } from './session.js';\n\nexport class SessionManager {\n private readonly sessions = new Map<string, Session>();\n\n createSession(cwd: string): Session {\n const id = crypto.randomUUID();\n const session = new Session(id, cwd);\n this.sessions.set(id, session);\n return session;\n }\n\n getSession(id: string): Session | undefined {\n return this.sessions.get(id);\n }\n\n cancelSession(id: string): boolean {\n const session = this.sessions.get(id);\n if (session) {\n session.cancel();\n return true;\n }\n return false;\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nexport interface HistoryEntry {\n role: 'user' | 'assistant';\n content: string;\n}\n\nexport class Session {\n readonly id: string;\n readonly cwd: string;\n private _history: HistoryEntry[] = [];\n private _abortController: AbortController;\n private _cancelled = false;\n\n constructor(id: string, cwd: string) {\n this.id = id;\n this.cwd = cwd;\n this._abortController = new AbortController();\n }\n\n getAbortSignal(): AbortSignal {\n return this._abortController.signal;\n }\n\n cancel(): void {\n this._cancelled = true;\n this._abortController.abort();\n }\n\n isCancelled(): boolean {\n return this._cancelled;\n }\n\n resetCancellation(): void {\n this._cancelled = false;\n this._abortController = new AbortController();\n }\n\n addHistoryEntry(role: 'user' | 'assistant', content: string): void {\n this._history.push({ role, content });\n }\n\n getHistory(): HistoryEntry[] {\n return [...this._history];\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { SSEEvent } from './types.js';\n\nconst DATA_PREFIX = 'data: ';\nconst DONE_MARKER = '[DONE]';\n\nexport function parseLine(line: string): SSEEvent {\n // Skip empty lines and whitespace-only lines\n if (!line.trim()) {\n return { type: 'skip' };\n }\n\n // Skip SSE comments (lines starting with :)\n if (line.startsWith(':')) {\n return { type: 'skip' };\n }\n\n // Check for data: prefix\n if (!line.startsWith(DATA_PREFIX)) {\n return { type: 'skip' };\n }\n\n const data = line.slice(DATA_PREFIX.length);\n\n // Check for [DONE] marker\n if (data === DONE_MARKER) {\n return { type: 'done' };\n }\n\n // Try to parse JSON\n try {\n const payload = JSON.parse(data);\n return { type: 'data', payload };\n } catch {\n console.error('[openai-agent] Failed to parse SSE JSON:', data);\n return { type: 'skip' };\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { AgentConfig, ChatCompletionResult, ChatCompletionsRequest, OpenAIMessage, SSEChunk } from './types.js';\nimport { parseLine } from './sse-parser.js';\n\nexport function classifyHttpError(status: number, url: string): string {\n if (status === 401 || status === 403) {\n return `Authentication error (HTTP ${status}) calling ${url}. Check your OPENAI_API_KEY.`;\n }\n if (status === 429) {\n return `Rate limit exceeded (HTTP 429) calling ${url}. Please retry later.`;\n }\n if (status >= 500) {\n return `Server error (HTTP ${status}) from ${url}.`;\n }\n return `HTTP error (${status}) from ${url}.`;\n}\n\nexport class ChatCompletionsClient {\n private readonly config: AgentConfig;\n\n constructor(config: AgentConfig) {\n this.config = config;\n }\n\n async streamCompletion(\n messages: OpenAIMessage[],\n signal: AbortSignal,\n onChunk: (text: string) => Promise<void>,\n ): Promise<ChatCompletionResult> {\n const url = `${this.config.baseUrl}/chat/completions`;\n const body: ChatCompletionsRequest = {\n model: this.config.model,\n messages,\n stream: true,\n };\n if (this.config.maxTokens !== undefined) {\n body.max_tokens = this.config.maxTokens;\n }\n if (this.config.temperature !== undefined) {\n body.temperature = this.config.temperature;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n signal,\n });\n } catch (error: unknown) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { stopReason: 'cancelled', fullResponse: '' };\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Network error connecting to ${url}: ${message}`);\n }\n\n if (!response.ok) {\n throw new Error(classifyHttpError(response.status, url));\n }\n\n if (!response.body) {\n throw new Error(`No response body from ${url}.`);\n }\n\n let fullResponse = '';\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n // Keep the last potentially incomplete line in the buffer\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n const event = parseLine(line);\n if (event.type === 'done') {\n return { stopReason: 'end_turn', fullResponse };\n }\n if (event.type === 'data') {\n const chunk = event.payload as SSEChunk;\n const content = chunk.choices?.[0]?.delta?.content;\n if (content) {\n fullResponse += content;\n await onChunk(content);\n }\n }\n }\n }\n } catch (error: unknown) {\n if (error instanceof Error && error.name === 'AbortError') {\n return { stopReason: 'cancelled', fullResponse };\n }\n throw error;\n }\n\n return { stopReason: 'end_turn', fullResponse };\n }\n}\n", "/*\n * Apache License 2.0\n * Copyright (c) 2025\u2013present Raman Marozau, Target Insight Function.\n * Contact: raman@worktif.com\n *\n * This file is part of the stdio bus protocol reference implementation:\n * stdio_bus_kernel_workers (target: <target_stdio_bus_kernel_workers>).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * Session ID routing helper for stdio Bus \u2194 ACP messages.\n *\n * Tracks request IDs to restore stdio Bus sessionId on responses,\n * and maps ACP sessionId to stdio Bus sessionId for notifications.\n */\n\ntype JsonRecord = Record<string, unknown>;\n\nexport class SessionIdRouter {\n private readonly requestSessionIdMap = new Map<string | number, string>();\n private readonly acpSessionIdMap = new Map<string, string>();\n\n /**\n * Process a single inbound (stdin) line.\n *\n * - Saves stdio Bus sessionId for request/response correlation\n * - Tracks ACP sessionId \u2194 stdio Bus sessionId mapping when available\n * - Strips stdio Bus sessionId before passing to ACP SDK\n */\n processIncomingLine(line: string): string {\n if (!line.trim()) {\n return line;\n }\n\n try {\n const msg = JSON.parse(line) as JsonRecord;\n const routingSessionId = this.readSessionId(msg.sessionId);\n const hasId = msg.id !== undefined && msg.id !== null;\n\n if (hasId && routingSessionId) {\n this.requestSessionIdMap.set(msg.id as string | number, routingSessionId);\n console.error(`[worker] Saved sessionId=\"${routingSessionId}\" for request id=${msg.id}`);\n }\n\n const paramsSessionId = this.readSessionId((msg.params as JsonRecord | undefined)?.sessionId);\n if (routingSessionId && paramsSessionId) {\n this.setAcpSessionMapping(paramsSessionId, routingSessionId, 'request');\n }\n\n if (hasId && routingSessionId) {\n const { sessionId: _sessionId, ...msgWithoutSession } = msg;\n return JSON.stringify(msgWithoutSession);\n }\n\n return line;\n } catch {\n return line;\n }\n }\n\n /**\n * Process a single outbound (stdout) line.\n *\n * - Restores stdio Bus sessionId on responses using request mapping\n * - Maps ACP sessionId to stdio Bus sessionId for notifications\n */\n processOutgoingLine(line: string): string {\n if (!line.trim()) {\n return line;\n }\n\n try {\n const msg = JSON.parse(line) as JsonRecord;\n const hasId = msg.id !== undefined && msg.id !== null;\n\n if (hasId && this.requestSessionIdMap.has(msg.id as string | number)) {\n const routingSessionId = this.requestSessionIdMap.get(msg.id as string | number);\n this.requestSessionIdMap.delete(msg.id as string | number);\n\n if (routingSessionId) {\n const resultSessionId = this.readSessionId(\n (msg.result as JsonRecord | undefined)?.sessionId,\n );\n if (resultSessionId) {\n this.setAcpSessionMapping(resultSessionId, routingSessionId, 'response');\n }\n\n const msgWithSession = { ...msg, sessionId: routingSessionId };\n console.error(\n `[worker] Restored sessionId=\"${routingSessionId}\" for response id=${msg.id}`,\n );\n return JSON.stringify(msgWithSession);\n }\n }\n\n if (!hasId && !this.readSessionId(msg.sessionId)) {\n const paramsSessionId = this.readSessionId(\n (msg.params as JsonRecord | undefined)?.sessionId,\n );\n if (paramsSessionId) {\n const routingSessionId = this.acpSessionIdMap.get(paramsSessionId);\n if (routingSessionId) {\n const msgWithSession = { ...msg, sessionId: routingSessionId };\n return JSON.stringify(msgWithSession);\n }\n }\n }\n\n return line;\n } catch {\n return line;\n }\n }\n\n private readSessionId(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n }\n\n private setAcpSessionMapping(\n acpSessionId: string,\n routingSessionId: string,\n source: 'request' | 'response',\n ): void {\n const existing = this.acpSessionIdMap.get(acpSessionId);\n if (existing === routingSessionId) {\n return;\n }\n\n this.acpSessionIdMap.set(acpSessionId, routingSessionId);\n console.error(\n `[worker] Mapped ACP sessionId=\"${acpSessionId}\" ` +\n `to routing sessionId=\"${routingSessionId}\" (${source})`,\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["connection"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stdiobus/workers-registry",
|
|
3
|
-
"version": "1.5.0-beta.
|
|
3
|
+
"version": "1.5.0-beta.2",
|
|
4
4
|
"description": "Worker implementations for stdio Bus kernel - ACP, MCP, and protocol bridges",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./out/dist/workers-registry/acp-registry/index.js",
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
|
+
"@stdiobus/node": "^0.1.0",
|
|
105
106
|
"@types/jest": "^29.5.0",
|
|
106
107
|
"@types/node": "^20.0.0",
|
|
107
108
|
"esbuild": "^0.20.0",
|