@jasonews/agent-squad-gateway 1.0.0
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/LICENSE +22 -0
- package/README.md +200 -0
- package/SECURITY.md +44 -0
- package/dist/bin/agent-squad-gateway.js +55 -0
- package/dist/bin/agent-squad-gateway.js.map +1 -0
- package/dist/config/config.js +124 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/paths.js +16 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/control-plane/clients.js +77 -0
- package/dist/control-plane/clients.js.map +1 -0
- package/dist/control-plane/core-connection.js +54 -0
- package/dist/control-plane/core-connection.js.map +1 -0
- package/dist/control-plane/credentials.js +134 -0
- package/dist/control-plane/credentials.js.map +1 -0
- package/dist/control-plane/db.js +33 -0
- package/dist/control-plane/db.js.map +1 -0
- package/dist/control-plane/extensions.js +59 -0
- package/dist/control-plane/extensions.js.map +1 -0
- package/dist/control-plane/grants.js +107 -0
- package/dist/control-plane/grants.js.map +1 -0
- package/dist/control-plane/idempotency.js +132 -0
- package/dist/control-plane/idempotency.js.map +1 -0
- package/dist/control-plane/migrations.js +133 -0
- package/dist/control-plane/migrations.js.map +1 -0
- package/dist/control-plane/response-sessions.js +359 -0
- package/dist/control-plane/response-sessions.js.map +1 -0
- package/dist/control-plane/retention.js +138 -0
- package/dist/control-plane/retention.js.map +1 -0
- package/dist/control-plane/runs.js +156 -0
- package/dist/control-plane/runs.js.map +1 -0
- package/dist/control-plane/targets.js +449 -0
- package/dist/control-plane/targets.js.map +1 -0
- package/dist/control-plane/types.js +2 -0
- package/dist/control-plane/types.js.map +1 -0
- package/dist/core-client/client.js +275 -0
- package/dist/core-client/client.js.map +1 -0
- package/dist/core-client/event-proxy.js +105 -0
- package/dist/core-client/event-proxy.js.map +1 -0
- package/dist/core-client/types.js +2 -0
- package/dist/core-client/types.js.map +1 -0
- package/dist/extensions/contract.js +2 -0
- package/dist/extensions/contract.js.map +1 -0
- package/dist/extensions/openai/auth.js +16 -0
- package/dist/extensions/openai/auth.js.map +1 -0
- package/dist/extensions/openai/chat.js +403 -0
- package/dist/extensions/openai/chat.js.map +1 -0
- package/dist/extensions/openai/errors.js +79 -0
- package/dist/extensions/openai/errors.js.map +1 -0
- package/dist/extensions/openai/image-input.js +79 -0
- package/dist/extensions/openai/image-input.js.map +1 -0
- package/dist/extensions/openai/models.js +40 -0
- package/dist/extensions/openai/models.js.map +1 -0
- package/dist/extensions/openai/responses.js +731 -0
- package/dist/extensions/openai/responses.js.map +1 -0
- package/dist/extensions/openai/routes.js +81 -0
- package/dist/extensions/openai/routes.js.map +1 -0
- package/dist/extensions/openai/run-attempt.js +96 -0
- package/dist/extensions/openai/run-attempt.js.map +1 -0
- package/dist/extensions/openai/schemas.js +176 -0
- package/dist/extensions/openai/schemas.js.map +1 -0
- package/dist/extensions/openai/sse.js +421 -0
- package/dist/extensions/openai/sse.js.map +1 -0
- package/dist/extensions/openai/tools.js +727 -0
- package/dist/extensions/openai/tools.js.map +1 -0
- package/dist/extensions/registry.js +30 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/lifecycle/daemon.js +437 -0
- package/dist/lifecycle/daemon.js.map +1 -0
- package/dist/lifecycle/pidfile.js +34 -0
- package/dist/lifecycle/pidfile.js.map +1 -0
- package/dist/provider-runtime/antigravity/adapter.js +398 -0
- package/dist/provider-runtime/antigravity/adapter.js.map +1 -0
- package/dist/provider-runtime/antigravity/conversation.js +31 -0
- package/dist/provider-runtime/antigravity/conversation.js.map +1 -0
- package/dist/provider-runtime/capability-service.js +191 -0
- package/dist/provider-runtime/capability-service.js.map +1 -0
- package/dist/provider-runtime/claude/adapter.js +397 -0
- package/dist/provider-runtime/claude/adapter.js.map +1 -0
- package/dist/provider-runtime/claude/protocol.js +143 -0
- package/dist/provider-runtime/claude/protocol.js.map +1 -0
- package/dist/provider-runtime/codex/adapter.js +440 -0
- package/dist/provider-runtime/codex/adapter.js.map +1 -0
- package/dist/provider-runtime/codex/json-rpc-client.js +182 -0
- package/dist/provider-runtime/codex/json-rpc-client.js.map +1 -0
- package/dist/provider-runtime/codex/protocol.js +128 -0
- package/dist/provider-runtime/codex/protocol.js.map +1 -0
- package/dist/provider-runtime/command-discovery.js +106 -0
- package/dist/provider-runtime/command-discovery.js.map +1 -0
- package/dist/provider-runtime/cursor/adapter.js +427 -0
- package/dist/provider-runtime/cursor/adapter.js.map +1 -0
- package/dist/provider-runtime/cursor/protocol.js +81 -0
- package/dist/provider-runtime/cursor/protocol.js.map +1 -0
- package/dist/provider-runtime/fake/adapter.js +138 -0
- package/dist/provider-runtime/fake/adapter.js.map +1 -0
- package/dist/provider-runtime/image-assets.js +409 -0
- package/dist/provider-runtime/image-assets.js.map +1 -0
- package/dist/provider-runtime/image-support.js +5 -0
- package/dist/provider-runtime/image-support.js.map +1 -0
- package/dist/provider-runtime/invocation-service.js +503 -0
- package/dist/provider-runtime/invocation-service.js.map +1 -0
- package/dist/provider-runtime/kimi/adapter.js +657 -0
- package/dist/provider-runtime/kimi/adapter.js.map +1 -0
- package/dist/provider-runtime/opencode/adapter.js +610 -0
- package/dist/provider-runtime/opencode/adapter.js.map +1 -0
- package/dist/provider-runtime/opencode/server.js +46 -0
- package/dist/provider-runtime/opencode/server.js.map +1 -0
- package/dist/provider-runtime/process/line-reader.js +49 -0
- package/dist/provider-runtime/process/line-reader.js.map +1 -0
- package/dist/provider-runtime/process/managed-process.js +151 -0
- package/dist/provider-runtime/process/managed-process.js.map +1 -0
- package/dist/provider-runtime/provider-input.js +77 -0
- package/dist/provider-runtime/provider-input.js.map +1 -0
- package/dist/provider-runtime/register-providers.js +26 -0
- package/dist/provider-runtime/register-providers.js.map +1 -0
- package/dist/provider-runtime/registry.js +35 -0
- package/dist/provider-runtime/registry.js.map +1 -0
- package/dist/provider-runtime/replay-buffer.js +168 -0
- package/dist/provider-runtime/replay-buffer.js.map +1 -0
- package/dist/provider-runtime/scheduler.js +155 -0
- package/dist/provider-runtime/scheduler.js.map +1 -0
- package/dist/provider-runtime/types.js +2 -0
- package/dist/provider-runtime/types.js.map +1 -0
- package/dist/provider-runtime/workspaces.js +814 -0
- package/dist/provider-runtime/workspaces.js.map +1 -0
- package/dist/security/admin-auth.js +109 -0
- package/dist/security/admin-auth.js.map +1 -0
- package/dist/security/crypto.js +52 -0
- package/dist/security/crypto.js.map +1 -0
- package/dist/security/secret-files.js +33 -0
- package/dist/security/secret-files.js.map +1 -0
- package/dist/server/admin-routes.js +610 -0
- package/dist/server/admin-routes.js.map +1 -0
- package/dist/server/app.js +178 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/auth-hooks.js +43 -0
- package/dist/server/auth-hooks.js.map +1 -0
- package/dist/server/core-routes.js +127 -0
- package/dist/server/core-routes.js.map +1 -0
- package/dist/server/errors.js +19 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/ui-routes.js +77 -0
- package/dist/server/ui-routes.js.map +1 -0
- package/dist/web/assets/index-BhqNoPpE.js +206 -0
- package/dist/web/assets/index-BhqNoPpE.js.map +1 -0
- package/dist/web/assets/index-C5nnaBfZ.css +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +74 -0
- package/translations/README.zh-CN.md +175 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { IdempotencyConflictError, } from '../../control-plane/idempotency.js';
|
|
3
|
+
import { reserveRunId } from '../../control-plane/runs.js';
|
|
4
|
+
import { deserializeOpenAIReplay, OpenAIError, providerFailureError, serializeOpenAIFailure, serializeOpenAISuccess, } from './errors.js';
|
|
5
|
+
import { assertImageInputSupported, normalizeResponsesInput, } from './image-input.js';
|
|
6
|
+
import { OPENAI_EXTENSION_ID } from './models.js';
|
|
7
|
+
import { ResponsesRequestSchema, } from './schemas.js';
|
|
8
|
+
import { writeResponsesStream } from './sse.js';
|
|
9
|
+
import { AdapterProtocolError, assignGatewayCallIds, buildToolOutputSchema, FunctionCallMetadataStore, StructuredEnvelopeDecoder, ToolInputError, } from './tools.js';
|
|
10
|
+
const responseCallMetadata = new FunctionCallMetadataStore();
|
|
11
|
+
function invalidRequest() {
|
|
12
|
+
return new OpenAIError(400, 'Invalid request', 'invalid_request_error', null, 'invalid_request');
|
|
13
|
+
}
|
|
14
|
+
function toolsNotSupported() {
|
|
15
|
+
return new OpenAIError(400, 'The selected model does not support function tools', 'invalid_request_error', 'tools', 'tools_not_supported');
|
|
16
|
+
}
|
|
17
|
+
function protocolError() {
|
|
18
|
+
return new OpenAIError(502, 'The provider returned an invalid response', 'server_error', null, 'adapter_protocol_error');
|
|
19
|
+
}
|
|
20
|
+
function providerError(code = 'provider_error') {
|
|
21
|
+
return providerFailureError(code, 'input');
|
|
22
|
+
}
|
|
23
|
+
function workspaceError() {
|
|
24
|
+
return new OpenAIError(500, 'The response workspace operation failed', 'server_error', null, 'workspace_error');
|
|
25
|
+
}
|
|
26
|
+
function repositoryError(error) {
|
|
27
|
+
const code = error instanceof Error ? error.message : '';
|
|
28
|
+
switch (code) {
|
|
29
|
+
case 'response_not_found':
|
|
30
|
+
return new OpenAIError(404, 'Previous response not found', 'invalid_request_error', null, code);
|
|
31
|
+
case 'response_target_mismatch':
|
|
32
|
+
return new OpenAIError(400, 'Previous response used a different model', 'invalid_request_error', null, code);
|
|
33
|
+
case 'response_expired':
|
|
34
|
+
return new OpenAIError(404, 'Previous response expired', 'invalid_request_error', null, code);
|
|
35
|
+
case 'response_not_stored':
|
|
36
|
+
return new OpenAIError(400, 'Previous response was not stored', 'invalid_request_error', null, code);
|
|
37
|
+
case 'response_in_progress':
|
|
38
|
+
return new OpenAIError(409, 'Previous response is already continuing', 'invalid_request_error', null, code);
|
|
39
|
+
case 'response_already_continued':
|
|
40
|
+
return new OpenAIError(409, 'Previous response was already continued', 'invalid_request_error', null, code);
|
|
41
|
+
case 'response_terminal_failure':
|
|
42
|
+
return new OpenAIError(409, 'Previous response cannot be continued', 'invalid_request_error', null, code);
|
|
43
|
+
default:
|
|
44
|
+
return new OpenAIError(500, 'The response state operation failed', 'server_error', null, 'response_state_error');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function parseRequest(body) {
|
|
48
|
+
const result = ResponsesRequestSchema.safeParse(body);
|
|
49
|
+
if (!result.success)
|
|
50
|
+
throw invalidRequest();
|
|
51
|
+
if (result.data.previous_response_id !== undefined && result.data.store === false) {
|
|
52
|
+
throw invalidRequest();
|
|
53
|
+
}
|
|
54
|
+
return result.data;
|
|
55
|
+
}
|
|
56
|
+
function authorizeTarget(clientId, model, deps, attempt) {
|
|
57
|
+
const configuredTarget = deps.targets.get(model);
|
|
58
|
+
if (!configuredTarget) {
|
|
59
|
+
throw new OpenAIError(404, 'Model not found', 'invalid_request_error', null, 'model_not_found');
|
|
60
|
+
}
|
|
61
|
+
attempt.setTarget(configuredTarget.id);
|
|
62
|
+
try {
|
|
63
|
+
return deps.grants.authorize(clientId, OPENAI_EXTENSION_ID, model);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof Error && error.message === 'authorization_denied') {
|
|
67
|
+
throw new OpenAIError(403, 'The model is not available to this client', 'permission_error', null, 'model_not_allowed');
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function responseToolBridge(request, targetToolBridge) {
|
|
73
|
+
const tools = (request.tools ?? []).map((tool) => ({
|
|
74
|
+
type: 'function',
|
|
75
|
+
function: {
|
|
76
|
+
name: tool.name,
|
|
77
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
78
|
+
...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
const choice = typeof request.tool_choice === 'object'
|
|
82
|
+
? { type: 'function', function: { name: request.tool_choice.name } }
|
|
83
|
+
: request.tool_choice;
|
|
84
|
+
try {
|
|
85
|
+
const schema = buildToolOutputSchema(tools, choice);
|
|
86
|
+
if (schema !== null && targetToolBridge !== 'structured_output')
|
|
87
|
+
throw toolsNotSupported();
|
|
88
|
+
return schema;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof OpenAIError)
|
|
92
|
+
throw error;
|
|
93
|
+
if (error instanceof ToolInputError)
|
|
94
|
+
throw invalidRequest();
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function inputItems(request) {
|
|
99
|
+
if (typeof request.input === 'string') {
|
|
100
|
+
if (request.previous_response_id !== undefined) {
|
|
101
|
+
const expected = responseCallMetadata.find(request.previous_response_id);
|
|
102
|
+
if (expected && expected.size > 0)
|
|
103
|
+
throw invalidRequest();
|
|
104
|
+
}
|
|
105
|
+
return normalizeResponsesInput(request);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const hasOutputs = request.input.some((item) => 'type' in item && item.type === 'function_call_output');
|
|
109
|
+
const expected = request.previous_response_id === undefined
|
|
110
|
+
? undefined
|
|
111
|
+
: hasOutputs
|
|
112
|
+
? responseCallMetadata.expected(request.previous_response_id)
|
|
113
|
+
: responseCallMetadata.find(request.previous_response_id);
|
|
114
|
+
if (!hasOutputs && expected && expected.size > 0)
|
|
115
|
+
throw invalidRequest();
|
|
116
|
+
return normalizeResponsesInput(request, expected && expected.size > 0 ? expected : undefined);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error instanceof ToolInputError)
|
|
120
|
+
throw invalidRequest();
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function collectInvocation(invocation, onSessionStarted, outputSchema) {
|
|
125
|
+
let text = '';
|
|
126
|
+
let nativeSessionId;
|
|
127
|
+
let nativeStateAdvanced = false;
|
|
128
|
+
let completed = 0;
|
|
129
|
+
let terminalSeen = false;
|
|
130
|
+
const decoder = outputSchema === null ? undefined : new StructuredEnvelopeDecoder(outputSchema);
|
|
131
|
+
try {
|
|
132
|
+
for await (const event of invocation) {
|
|
133
|
+
if (terminalSeen)
|
|
134
|
+
throw protocolError();
|
|
135
|
+
switch (event.type) {
|
|
136
|
+
case 'session_started':
|
|
137
|
+
if (nativeSessionId !== undefined)
|
|
138
|
+
throw protocolError();
|
|
139
|
+
nativeSessionId = event.nativeSessionId;
|
|
140
|
+
nativeStateAdvanced = true;
|
|
141
|
+
onSessionStarted(nativeSessionId);
|
|
142
|
+
break;
|
|
143
|
+
case 'text_delta':
|
|
144
|
+
if (nativeSessionId === undefined)
|
|
145
|
+
throw protocolError();
|
|
146
|
+
if (decoder)
|
|
147
|
+
throw protocolError();
|
|
148
|
+
text += event.delta;
|
|
149
|
+
break;
|
|
150
|
+
case 'structured_delta':
|
|
151
|
+
if (nativeSessionId === undefined || !decoder)
|
|
152
|
+
throw protocolError();
|
|
153
|
+
decoder.push(event.delta);
|
|
154
|
+
break;
|
|
155
|
+
case 'completed':
|
|
156
|
+
if (nativeSessionId === undefined)
|
|
157
|
+
throw protocolError();
|
|
158
|
+
completed += 1;
|
|
159
|
+
terminalSeen = true;
|
|
160
|
+
break;
|
|
161
|
+
case 'failed':
|
|
162
|
+
nativeStateAdvanced ||= event.nativeStateAdvanced;
|
|
163
|
+
if (event.code === 'adapter_protocol_error')
|
|
164
|
+
throw protocolError();
|
|
165
|
+
throw providerError(event.code);
|
|
166
|
+
case 'cancelled':
|
|
167
|
+
throw new OpenAIError(500, 'The request was cancelled', 'server_error', null, 'request_cancelled');
|
|
168
|
+
default:
|
|
169
|
+
throw protocolError();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof OpenAIError) {
|
|
175
|
+
Object.assign(error, { nativeStateAdvanced });
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
if (error instanceof AdapterProtocolError) {
|
|
179
|
+
const safe = protocolError();
|
|
180
|
+
Object.assign(safe, { nativeStateAdvanced });
|
|
181
|
+
throw safe;
|
|
182
|
+
}
|
|
183
|
+
const safe = providerError();
|
|
184
|
+
Object.assign(safe, { nativeStateAdvanced });
|
|
185
|
+
throw safe;
|
|
186
|
+
}
|
|
187
|
+
if (completed !== 1 || nativeSessionId === undefined) {
|
|
188
|
+
const error = protocolError();
|
|
189
|
+
Object.assign(error, { nativeStateAdvanced });
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
let envelope = { type: 'assistant_text', content: text };
|
|
193
|
+
if (decoder) {
|
|
194
|
+
try {
|
|
195
|
+
envelope = decoder.finish();
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
const safe = error instanceof AdapterProtocolError ? protocolError() : providerError();
|
|
199
|
+
Object.assign(safe, { nativeStateAdvanced });
|
|
200
|
+
throw safe;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { envelope, nativeSessionId, nativeStateAdvanced };
|
|
204
|
+
}
|
|
205
|
+
function advancedFrom(error) {
|
|
206
|
+
return typeof error === 'object'
|
|
207
|
+
&& error !== null
|
|
208
|
+
&& 'nativeStateAdvanced' in error
|
|
209
|
+
&& error.nativeStateAdvanced === true;
|
|
210
|
+
}
|
|
211
|
+
function acquireParent(previousResponseId, targetId, sessions) {
|
|
212
|
+
try {
|
|
213
|
+
const parent = sessions.acquireContinuation(previousResponseId, targetId);
|
|
214
|
+
if (parent.nativeSessionId === null || parent.workspacePath === null) {
|
|
215
|
+
throw new Error('response_chain_invalid');
|
|
216
|
+
}
|
|
217
|
+
return parent;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
throw repositoryError(error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function settleContinuationFailure(parentResponseId, advanced, sessions) {
|
|
224
|
+
try {
|
|
225
|
+
if (advanced)
|
|
226
|
+
sessions.failTerminal(parentResponseId);
|
|
227
|
+
else
|
|
228
|
+
sessions.releaseBeforeStart(parentResponseId);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw repositoryError(error);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function settleNewStoredFailure(responseId, targetId, sessions) {
|
|
235
|
+
try {
|
|
236
|
+
sessions.acquireContinuation(responseId, targetId);
|
|
237
|
+
sessions.failTerminal(responseId);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
throw repositoryError(error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function executeResponse(clientId, request, target, responseId, input, images, outputSchema, deps, runId) {
|
|
244
|
+
const previousResponseId = request.previous_response_id;
|
|
245
|
+
const stored = request.store !== false;
|
|
246
|
+
const parent = previousResponseId === undefined
|
|
247
|
+
? undefined
|
|
248
|
+
: acquireParent(previousResponseId, target.id, deps.responseSessions);
|
|
249
|
+
let workspaceLease;
|
|
250
|
+
let workspacePath;
|
|
251
|
+
let sessionPersisted = false;
|
|
252
|
+
let cleanupAbandoned = false;
|
|
253
|
+
let nativeAdvanced = false;
|
|
254
|
+
let result;
|
|
255
|
+
try {
|
|
256
|
+
if (stored && parent === undefined) {
|
|
257
|
+
try {
|
|
258
|
+
workspaceLease = await deps.responseWorkspaces.createResponse(target, responseId);
|
|
259
|
+
workspacePath = workspaceLease.path;
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
throw workspaceError();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (parent !== undefined) {
|
|
266
|
+
workspacePath = parent.workspacePath;
|
|
267
|
+
}
|
|
268
|
+
let invocation;
|
|
269
|
+
try {
|
|
270
|
+
invocation = deps.invocationService.invoke({
|
|
271
|
+
...(runId === undefined ? {} : { runId }),
|
|
272
|
+
clientId,
|
|
273
|
+
extensionId: OPENAI_EXTENSION_ID,
|
|
274
|
+
targetId: target.id,
|
|
275
|
+
endpoint: 'responses',
|
|
276
|
+
responseId,
|
|
277
|
+
input,
|
|
278
|
+
...(images.length === 0 ? {} : { images }),
|
|
279
|
+
sessionMode: parent === undefined && !stored ? 'ephemeral' : 'persistent',
|
|
280
|
+
...(outputSchema === null ? {} : { outputSchema }),
|
|
281
|
+
...(workspacePath === undefined ? {} : { workspacePath }),
|
|
282
|
+
...(parent === undefined ? {} : { nativeSessionId: parent.nativeSessionId }),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
throw providerError();
|
|
287
|
+
}
|
|
288
|
+
result = await collectInvocation(invocation, (nativeSessionId) => {
|
|
289
|
+
if (parent !== undefined)
|
|
290
|
+
return;
|
|
291
|
+
try {
|
|
292
|
+
deps.responseSessions.create({
|
|
293
|
+
responseId,
|
|
294
|
+
targetId: target.id,
|
|
295
|
+
nativeSessionId,
|
|
296
|
+
workspacePath,
|
|
297
|
+
store: stored,
|
|
298
|
+
});
|
|
299
|
+
sessionPersisted = true;
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
throw repositoryError(error);
|
|
303
|
+
}
|
|
304
|
+
}, outputSchema);
|
|
305
|
+
nativeAdvanced = result.nativeStateAdvanced;
|
|
306
|
+
if (parent !== undefined) {
|
|
307
|
+
try {
|
|
308
|
+
deps.responseSessions.completeContinuation({
|
|
309
|
+
parentResponseId: parent.responseId,
|
|
310
|
+
childResponseId: responseId,
|
|
311
|
+
nativeSessionId: result.nativeSessionId,
|
|
312
|
+
workspacePath: parent.workspacePath,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
throw repositoryError(error);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (!sessionPersisted) {
|
|
320
|
+
throw protocolError();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const advanced = nativeAdvanced || advancedFrom(error);
|
|
325
|
+
if (parent !== undefined) {
|
|
326
|
+
settleContinuationFailure(parent.responseId, advanced, deps.responseSessions);
|
|
327
|
+
}
|
|
328
|
+
else if (stored && sessionPersisted && advanced) {
|
|
329
|
+
settleNewStoredFailure(responseId, target.id, deps.responseSessions);
|
|
330
|
+
}
|
|
331
|
+
else if (stored && !sessionPersisted && workspacePath !== undefined) {
|
|
332
|
+
cleanupAbandoned = true;
|
|
333
|
+
}
|
|
334
|
+
if (error instanceof OpenAIError)
|
|
335
|
+
throw error;
|
|
336
|
+
throw providerError();
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
if (workspaceLease !== undefined) {
|
|
340
|
+
try {
|
|
341
|
+
await workspaceLease.release();
|
|
342
|
+
if (cleanupAbandoned && workspacePath !== undefined) {
|
|
343
|
+
await deps.responseWorkspaces.cleanupExpired([workspacePath]);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
throw workspaceError();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const response = responseFromEnvelope(responseId, request.model, result.envelope);
|
|
352
|
+
if (stored)
|
|
353
|
+
responseCallMetadata.remember(responseId, response.output
|
|
354
|
+
.filter((item) => item.type === 'function_call')
|
|
355
|
+
.map((item) => item.call_id));
|
|
356
|
+
return response;
|
|
357
|
+
}
|
|
358
|
+
export async function handleResponse(clientId, body, deps, attempt, idempotencyKey) {
|
|
359
|
+
const request = parseRequest(body);
|
|
360
|
+
const target = authorizeTarget(clientId, request.model, deps, attempt);
|
|
361
|
+
const outputSchema = responseToolBridge(request, target.toolBridge);
|
|
362
|
+
const normalized = inputItems(request);
|
|
363
|
+
assertImageInputSupported(target.cli, normalized.images, 'input');
|
|
364
|
+
const responseId = `resp_${randomUUID()}`;
|
|
365
|
+
attempt.setResponseId(responseId);
|
|
366
|
+
if (idempotencyKey === undefined) {
|
|
367
|
+
return executeResponse(clientId, request, target, responseId, normalized.input, normalized.images, outputSchema, deps, attempt.reserve());
|
|
368
|
+
}
|
|
369
|
+
if (!deps.idempotency)
|
|
370
|
+
throw new Error('idempotency_service_unavailable');
|
|
371
|
+
let decision;
|
|
372
|
+
try {
|
|
373
|
+
const runId = reserveRunId();
|
|
374
|
+
decision = deps.idempotency.begin({
|
|
375
|
+
clientId,
|
|
376
|
+
key: idempotencyKey,
|
|
377
|
+
endpoint: 'responses',
|
|
378
|
+
request: body,
|
|
379
|
+
runId,
|
|
380
|
+
responseId,
|
|
381
|
+
run: {
|
|
382
|
+
clientId,
|
|
383
|
+
extensionId: OPENAI_EXTENSION_ID,
|
|
384
|
+
targetId: target.id,
|
|
385
|
+
endpoint: 'responses',
|
|
386
|
+
responseId,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
if (error instanceof IdempotencyConflictError) {
|
|
392
|
+
throw new OpenAIError(409, 'Idempotency key was reused with a different request', 'invalid_request_error', null, 'idempotency_conflict');
|
|
393
|
+
}
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
if (decision.type === 'owner')
|
|
397
|
+
attempt.claim(decision.runId);
|
|
398
|
+
else
|
|
399
|
+
attempt.ignore();
|
|
400
|
+
if (decision.type === 'unavailable') {
|
|
401
|
+
throw new OpenAIError(409, 'Idempotency replay is unavailable', 'invalid_request_error', null, 'idempotency_replay_unavailable');
|
|
402
|
+
}
|
|
403
|
+
if (decision.type !== 'owner') {
|
|
404
|
+
try {
|
|
405
|
+
return deserializeOpenAIReplay(await deps.idempotency.attach(decision));
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
if (error instanceof OpenAIError)
|
|
409
|
+
throw error;
|
|
410
|
+
throw new OpenAIError(409, 'Idempotency replay is unavailable', 'invalid_request_error', null, 'idempotency_replay_unavailable');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const response = await executeResponse(clientId, request, target, responseId, normalized.input, normalized.images, outputSchema, deps, decision.runId);
|
|
415
|
+
deps.idempotency.complete(decision, serializeOpenAISuccess(response));
|
|
416
|
+
return response;
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
attempt.fail(error);
|
|
420
|
+
deps.idempotency.fail(decision, serializeOpenAIFailure(error));
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function responseFromEnvelope(responseId, model, envelope, options) {
|
|
425
|
+
if (envelope.type === 'tool_calls') {
|
|
426
|
+
const calls = options?.toolCalls ?? assignGatewayCallIds(envelope.toolCalls);
|
|
427
|
+
return {
|
|
428
|
+
id: responseId,
|
|
429
|
+
object: 'response',
|
|
430
|
+
status: 'completed',
|
|
431
|
+
model,
|
|
432
|
+
output: calls.map((call) => {
|
|
433
|
+
const itemId = call.itemId;
|
|
434
|
+
return {
|
|
435
|
+
id: itemId ?? `fc_${randomUUID()}`,
|
|
436
|
+
type: 'function_call',
|
|
437
|
+
status: 'completed',
|
|
438
|
+
arguments: JSON.stringify(call.arguments),
|
|
439
|
+
call_id: call.id,
|
|
440
|
+
name: call.name,
|
|
441
|
+
};
|
|
442
|
+
}),
|
|
443
|
+
output_text: '',
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
id: responseId,
|
|
448
|
+
object: 'response',
|
|
449
|
+
status: 'completed',
|
|
450
|
+
model,
|
|
451
|
+
output: [{
|
|
452
|
+
id: options?.messageId ?? `msg_${randomUUID()}`,
|
|
453
|
+
type: 'message',
|
|
454
|
+
role: 'assistant',
|
|
455
|
+
status: 'completed',
|
|
456
|
+
content: [{ type: 'output_text', text: envelope.content, annotations: [] }],
|
|
457
|
+
}],
|
|
458
|
+
output_text: envelope.content,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function replayResponseEvents(response) {
|
|
462
|
+
return (async function* () {
|
|
463
|
+
yield { type: 'session_started', nativeSessionId: 'replay' };
|
|
464
|
+
const functionCalls = response.output.filter((item) => item.type === 'function_call');
|
|
465
|
+
if (functionCalls.length > 0) {
|
|
466
|
+
yield {
|
|
467
|
+
type: 'structured_delta',
|
|
468
|
+
delta: JSON.stringify({
|
|
469
|
+
type: 'tool_calls',
|
|
470
|
+
tool_calls: functionCalls.map((item) => ({
|
|
471
|
+
name: item.name,
|
|
472
|
+
arguments: JSON.parse(item.arguments),
|
|
473
|
+
})),
|
|
474
|
+
}),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
yield { type: 'text_delta', delta: response.output_text };
|
|
479
|
+
}
|
|
480
|
+
yield { type: 'completed' };
|
|
481
|
+
})();
|
|
482
|
+
}
|
|
483
|
+
async function executeResponseStream(reply, clientId, request, target, responseId, outputId, input, images, outputSchema, deps, runId, finalizeSuccess) {
|
|
484
|
+
const previousResponseId = request.previous_response_id;
|
|
485
|
+
const stored = request.store !== false;
|
|
486
|
+
const parent = previousResponseId === undefined
|
|
487
|
+
? undefined
|
|
488
|
+
: acquireParent(previousResponseId, target.id, deps.responseSessions);
|
|
489
|
+
let workspaceLease;
|
|
490
|
+
let workspacePath;
|
|
491
|
+
let sessionPersisted = false;
|
|
492
|
+
let cleanupAbandoned = false;
|
|
493
|
+
let nativeAdvanced = false;
|
|
494
|
+
let workspaceReleaseAttempted = false;
|
|
495
|
+
let parentTransitionFinalized = false;
|
|
496
|
+
let successFinalized = false;
|
|
497
|
+
let finalResponse;
|
|
498
|
+
const releaseWorkspace = async (cleanup) => {
|
|
499
|
+
if (workspaceLease === undefined || workspaceReleaseAttempted)
|
|
500
|
+
return;
|
|
501
|
+
workspaceReleaseAttempted = true;
|
|
502
|
+
try {
|
|
503
|
+
await workspaceLease.release();
|
|
504
|
+
if (cleanup && workspacePath !== undefined) {
|
|
505
|
+
await deps.responseWorkspaces.cleanupExpired([workspacePath]);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
throw workspaceError();
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
try {
|
|
513
|
+
if (stored && parent === undefined) {
|
|
514
|
+
try {
|
|
515
|
+
workspaceLease = await deps.responseWorkspaces.createResponse(target, responseId);
|
|
516
|
+
workspacePath = workspaceLease.path;
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
throw workspaceError();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else if (parent !== undefined) {
|
|
523
|
+
workspacePath = parent.workspacePath;
|
|
524
|
+
}
|
|
525
|
+
let invocation;
|
|
526
|
+
try {
|
|
527
|
+
invocation = deps.invocationService.invoke({
|
|
528
|
+
...(runId === undefined ? {} : { runId }),
|
|
529
|
+
clientId,
|
|
530
|
+
extensionId: OPENAI_EXTENSION_ID,
|
|
531
|
+
targetId: target.id,
|
|
532
|
+
endpoint: 'responses',
|
|
533
|
+
responseId,
|
|
534
|
+
input,
|
|
535
|
+
...(images.length === 0 ? {} : { images }),
|
|
536
|
+
sessionMode: parent === undefined && !stored ? 'ephemeral' : 'persistent',
|
|
537
|
+
...(outputSchema === null ? {} : { outputSchema }),
|
|
538
|
+
...(workspacePath === undefined ? {} : { workspacePath }),
|
|
539
|
+
...(parent === undefined ? {} : { nativeSessionId: parent.nativeSessionId }),
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
throw providerError();
|
|
544
|
+
}
|
|
545
|
+
const result = await writeResponsesStream(reply, invocation, {
|
|
546
|
+
responseId,
|
|
547
|
+
outputId,
|
|
548
|
+
model: request.model,
|
|
549
|
+
outputSchema,
|
|
550
|
+
onSessionStarted: (nativeSessionId) => {
|
|
551
|
+
if (parent !== undefined)
|
|
552
|
+
return;
|
|
553
|
+
try {
|
|
554
|
+
deps.responseSessions.create({
|
|
555
|
+
responseId,
|
|
556
|
+
targetId: target.id,
|
|
557
|
+
nativeSessionId,
|
|
558
|
+
workspacePath,
|
|
559
|
+
store: stored,
|
|
560
|
+
});
|
|
561
|
+
sessionPersisted = true;
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
throw repositoryError(error);
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
beforeTerminal: async (result) => {
|
|
568
|
+
nativeAdvanced = result.nativeStateAdvanced;
|
|
569
|
+
if (parent !== undefined) {
|
|
570
|
+
try {
|
|
571
|
+
deps.responseSessions.completeContinuation({
|
|
572
|
+
parentResponseId: parent.responseId,
|
|
573
|
+
childResponseId: responseId,
|
|
574
|
+
nativeSessionId: result.nativeSessionId,
|
|
575
|
+
workspacePath: parent.workspacePath,
|
|
576
|
+
});
|
|
577
|
+
parentTransitionFinalized = true;
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
throw repositoryError(error);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
else if (!sessionPersisted) {
|
|
584
|
+
throw protocolError();
|
|
585
|
+
}
|
|
586
|
+
const envelope = result.toolCalls
|
|
587
|
+
? { type: 'tool_calls', toolCalls: result.toolCalls }
|
|
588
|
+
: { type: 'assistant_text', content: result.text };
|
|
589
|
+
const response = responseFromEnvelope(responseId, request.model, envelope, {
|
|
590
|
+
messageId: outputId,
|
|
591
|
+
...(result.toolCalls === undefined ? {} : { toolCalls: result.toolCalls }),
|
|
592
|
+
});
|
|
593
|
+
if (stored)
|
|
594
|
+
responseCallMetadata.remember(responseId, response.output
|
|
595
|
+
.filter((item) => item.type === 'function_call')
|
|
596
|
+
.map((item) => item.call_id));
|
|
597
|
+
await releaseWorkspace(false);
|
|
598
|
+
await finalizeSuccess?.(response);
|
|
599
|
+
finalResponse = response;
|
|
600
|
+
successFinalized = true;
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
nativeAdvanced = result.nativeStateAdvanced;
|
|
604
|
+
return finalResponse;
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
const advanced = nativeAdvanced || advancedFrom(error);
|
|
608
|
+
if (!successFinalized) {
|
|
609
|
+
if (parent !== undefined && !parentTransitionFinalized) {
|
|
610
|
+
settleContinuationFailure(parent.responseId, advanced, deps.responseSessions);
|
|
611
|
+
}
|
|
612
|
+
else if (parent === undefined && stored && sessionPersisted && advanced) {
|
|
613
|
+
settleNewStoredFailure(responseId, target.id, deps.responseSessions);
|
|
614
|
+
}
|
|
615
|
+
else if (parent === undefined && stored && !sessionPersisted && workspacePath !== undefined) {
|
|
616
|
+
cleanupAbandoned = true;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (error instanceof OpenAIError)
|
|
620
|
+
throw error;
|
|
621
|
+
throw error;
|
|
622
|
+
}
|
|
623
|
+
finally {
|
|
624
|
+
await releaseWorkspace(cleanupAbandoned);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function failResponseOwner(deps, decision, attempt, error) {
|
|
628
|
+
attempt.fail(error);
|
|
629
|
+
if (decision?.type !== 'owner')
|
|
630
|
+
return;
|
|
631
|
+
try {
|
|
632
|
+
deps.idempotency.fail(decision, serializeOpenAIFailure(error));
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
// A finalization callback may have settled before surfacing its failure.
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
export async function handleResponseStream(reply, clientId, body, deps, attempt, idempotencyKey) {
|
|
639
|
+
const request = parseRequest(body);
|
|
640
|
+
const target = authorizeTarget(clientId, request.model, deps, attempt);
|
|
641
|
+
const outputSchema = responseToolBridge(request, target.toolBridge);
|
|
642
|
+
const normalized = inputItems(request);
|
|
643
|
+
assertImageInputSupported(target.cli, normalized.images, 'input');
|
|
644
|
+
const responseId = `resp_${randomUUID()}`;
|
|
645
|
+
attempt.setResponseId(responseId);
|
|
646
|
+
const outputId = `msg_${randomUUID()}`;
|
|
647
|
+
let decision;
|
|
648
|
+
let ownerSettled = false;
|
|
649
|
+
if (idempotencyKey !== undefined) {
|
|
650
|
+
if (!deps.idempotency)
|
|
651
|
+
throw new Error('idempotency_service_unavailable');
|
|
652
|
+
try {
|
|
653
|
+
const runId = reserveRunId();
|
|
654
|
+
decision = deps.idempotency.begin({
|
|
655
|
+
clientId,
|
|
656
|
+
key: idempotencyKey,
|
|
657
|
+
endpoint: 'responses',
|
|
658
|
+
request: body,
|
|
659
|
+
runId,
|
|
660
|
+
responseId,
|
|
661
|
+
run: {
|
|
662
|
+
clientId,
|
|
663
|
+
extensionId: OPENAI_EXTENSION_ID,
|
|
664
|
+
targetId: target.id,
|
|
665
|
+
endpoint: 'responses',
|
|
666
|
+
responseId,
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
if (error instanceof IdempotencyConflictError) {
|
|
672
|
+
throw new OpenAIError(409, 'Idempotency key was reused with a different request', 'invalid_request_error', null, 'idempotency_conflict');
|
|
673
|
+
}
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
if (decision.type === 'owner')
|
|
677
|
+
attempt.claim(decision.runId);
|
|
678
|
+
else
|
|
679
|
+
attempt.ignore();
|
|
680
|
+
if (decision.type === 'unavailable') {
|
|
681
|
+
throw new OpenAIError(409, 'Idempotency replay is unavailable', 'invalid_request_error', null, 'idempotency_replay_unavailable');
|
|
682
|
+
}
|
|
683
|
+
if (decision.type !== 'owner') {
|
|
684
|
+
let response;
|
|
685
|
+
try {
|
|
686
|
+
response = deserializeOpenAIReplay(await deps.idempotency.attach(decision));
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
if (error instanceof OpenAIError)
|
|
690
|
+
throw error;
|
|
691
|
+
throw new OpenAIError(409, 'Idempotency replay is unavailable', 'invalid_request_error', null, 'idempotency_replay_unavailable');
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
await writeResponsesStream(reply, replayResponseEvents(response), {
|
|
695
|
+
responseId: response.id,
|
|
696
|
+
outputId: response.output[0].id,
|
|
697
|
+
model: response.model,
|
|
698
|
+
outputSchema,
|
|
699
|
+
...(response.output.some((item) => item.type === 'function_call')
|
|
700
|
+
? {
|
|
701
|
+
replayToolItems: response.output
|
|
702
|
+
.filter((item) => item.type === 'function_call')
|
|
703
|
+
.map((item) => ({ id: item.id, callId: item.call_id })),
|
|
704
|
+
}
|
|
705
|
+
: {}),
|
|
706
|
+
onSessionStarted: () => { },
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
catch (error) {
|
|
710
|
+
if (!reply.raw.headersSent)
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
await executeResponseStream(reply, clientId, request, target, responseId, outputId, normalized.input, normalized.images, outputSchema, deps, decision?.type === 'owner' ? decision.runId : attempt.reserve(), decision?.type === 'owner'
|
|
718
|
+
? (response) => {
|
|
719
|
+
deps.idempotency.complete(decision, serializeOpenAISuccess(response));
|
|
720
|
+
ownerSettled = true;
|
|
721
|
+
}
|
|
722
|
+
: undefined);
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
if (!ownerSettled)
|
|
726
|
+
failResponseOwner(deps, decision, attempt, error);
|
|
727
|
+
if (!reply.raw.headersSent)
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
//# sourceMappingURL=responses.js.map
|