Skip to content

Commit b33a853

Browse files
committed
feat: Introduce A2A lifecycle management, add type safety to ComfyUI and stream handling, and update various handlers and translators.
1 parent cbd0b1c commit b33a853

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1424
-250
lines changed

open-sse/config/codexInstructions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ When you are running with \`approval_policy == on-request\`, and sandboxing enab
5555
- You are about to take a potentially destructive action such as an \`rm\` or \`git reset\` that the user did not explicitly ask for
5656
- (for all of these, you should weigh alternative paths that do not require approval)
5757
58-
When \`sandbox_mode\` is set to read-only, you'll need to request approval for any command that isn't a read.
58+
When \`sandbox_mode\` is set to read-only, you'll need to request approval for each command that isn't a read.
5959
6060
You will be told what filesystem sandboxing, network sandboxing, and approval mode are active in a developer or user message. If you are not told about this, assume that you are running with workspace-write, network sandboxing enabled, and approval on-failure.
6161
@@ -68,7 +68,7 @@ When requesting approval to execute a command that will require escalated privil
6868
## Special user requests
6969
7070
- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as \`date\`), you should do so.
71-
- If the user asks for a "review", default to a code review mindset: prioritise identifying bugs, risks, behavioural regressions, and missing tests. Findings must be the primary focus of the response - keep summaries or overviews brief and only after enumerating the issues. Present findings first (ordered by severity with file/line references), follow with open questions or assumptions, and offer a change-summary only as a secondary detail. If no findings are discovered, state that explicitly and mention any residual risks or testing gaps.
71+
- If the user asks for a "review", default to a code review mindset: prioritise identifying bugs, risks, behavioural regressions, and missing tests. Findings must be the primary focus of the response - keep summaries or overviews brief and only after enumerating the issues. Present findings first (ordered by severity with file/line references), follow with open questions or assumptions, and offer a change-summary only as a secondary detail. If no findings are discovered, state that explicitly and mention residual risks or testing gaps.
7272
7373
## Frontend tasks
7474
When doing frontend design tasks, avoid collapsing into "AI slop" or safe, average-looking layouts.

open-sse/config/imageRegistry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export function parseImageModel(modelStr) {
157157
}
158158
}
159159

160-
// No provider prefix — try to find the model in any provider
160+
// No provider prefix — try to find the model in every provider
161161
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
162162
if (config.models.some((m) => m.id === modelStr)) {
163163
return { provider: providerId, model: modelStr };

open-sse/config/registryUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function parseModelFromRegistry<P extends BaseProvider>(
3737
}
3838
}
3939

40-
// No provider prefix — try to find the model in any provider
40+
// No provider prefix — try to find the model in every provider
4141
for (const [providerId, config] of Object.entries(registry)) {
4242
if (config.models.some((m) => m.id === modelStr)) {
4343
return { provider: providerId, model: modelStr };

open-sse/executors/antigravity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { PROVIDERS, OAUTH_ENDPOINTS, HTTP_STATUS } from "../config/constants.ts"
55
const MAX_RETRY_AFTER_MS = 10000;
66

77
/**
8-
* Strip any provider prefix (e.g. "antigravity/model" → "model").
8+
* Strip provider prefixes (e.g. "antigravity/model" → "model").
99
* Ensures the model name sent to the upstream API never contains a routing prefix.
1010
*/
1111
function cleanModelName(model: string): string {

open-sse/executors/base.ts

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,64 @@
11
import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.ts";
22

3+
type JsonRecord = Record<string, unknown>;
4+
5+
type ProviderConfig = {
6+
id?: string;
7+
baseUrl?: string;
8+
baseUrls?: string[];
9+
headers?: Record<string, string>;
10+
};
11+
12+
type ProviderCredentials = {
13+
accessToken?: string;
14+
apiKey?: string;
15+
expiresAt?: string;
16+
providerSpecificData?: JsonRecord;
17+
};
18+
19+
type ExecutorLog = {
20+
debug?: (tag: string, message: string) => void;
21+
warn?: (tag: string, message: string) => void;
22+
};
23+
24+
type ExecuteInput = {
25+
model: string;
26+
body: unknown;
27+
stream: boolean;
28+
credentials: ProviderCredentials;
29+
signal?: AbortSignal | null;
30+
log?: ExecutorLog | null;
31+
};
32+
33+
function mergeAbortSignals(primary: AbortSignal, secondary: AbortSignal): AbortSignal {
34+
const controller = new AbortController();
35+
36+
const abortBoth = () => {
37+
if (!controller.signal.aborted) {
38+
controller.abort();
39+
}
40+
};
41+
42+
if (primary.aborted || secondary.aborted) {
43+
abortBoth();
44+
return controller.signal;
45+
}
46+
47+
primary.addEventListener("abort", abortBoth, { once: true });
48+
secondary.addEventListener("abort", abortBoth, { once: true });
49+
return controller.signal;
50+
}
51+
352
/**
453
* BaseExecutor - Base class for provider executors.
554
* Implements the Strategy pattern: subclasses override specific methods
655
* (buildUrl, buildHeaders, transformRequest, etc.) for each provider.
756
*/
857
export class BaseExecutor {
9-
provider: any;
10-
config: any;
58+
provider: string;
59+
config: ProviderConfig;
1160

12-
constructor(provider: any, config: any) {
61+
constructor(provider: string, config: ProviderConfig) {
1362
this.provider = provider;
1463
this.config = config;
1564
}
@@ -26,9 +75,19 @@ export class BaseExecutor {
2675
return this.getBaseUrls().length || 1;
2776
}
2877

29-
buildUrl(model, stream, urlIndex = 0, credentials = null) {
78+
buildUrl(
79+
model: string,
80+
stream: boolean,
81+
urlIndex = 0,
82+
credentials: ProviderCredentials | null = null
83+
) {
84+
void model;
85+
void stream;
3086
if (this.provider?.startsWith?.("openai-compatible-")) {
31-
const baseUrl = credentials?.providerSpecificData?.baseUrl || "https://api.openai.com/v1";
87+
const baseUrl =
88+
typeof credentials?.providerSpecificData?.baseUrl === "string"
89+
? credentials.providerSpecificData.baseUrl
90+
: "https://api.openai.com/v1";
3291
const normalized = baseUrl.replace(/\/$/, "");
3392
const path = this.provider.includes("responses") ? "/responses" : "/chat/completions";
3493
return `${normalized}${path}`;
@@ -37,8 +96,8 @@ export class BaseExecutor {
3796
return baseUrls[urlIndex] || baseUrls[0] || this.config.baseUrl;
3897
}
3998

40-
buildHeaders(credentials, stream = true) {
41-
const headers = {
99+
buildHeaders(credentials: ProviderCredentials, stream = true): Record<string, string> {
100+
const headers: Record<string, string> = {
42101
"Content-Type": "application/json",
43102
...this.config.headers,
44103
};
@@ -70,32 +129,42 @@ export class BaseExecutor {
70129
}
71130

72131
// Override in subclass for provider-specific transformations
73-
transformRequest(model, body, stream, credentials) {
132+
transformRequest(
133+
model: string,
134+
body: unknown,
135+
stream: boolean,
136+
credentials: ProviderCredentials
137+
): unknown {
138+
void model;
139+
void stream;
140+
void credentials;
74141
return body;
75142
}
76143

77-
shouldRetry(status, urlIndex) {
144+
shouldRetry(status: number, urlIndex: number) {
78145
return status === HTTP_STATUS.RATE_LIMITED && urlIndex + 1 < this.getFallbackCount();
79146
}
80147

81148
// Override in subclass for provider-specific refresh
82-
async refreshCredentials(credentials, log) {
149+
async refreshCredentials(credentials: ProviderCredentials, log: ExecutorLog | null) {
150+
void credentials;
151+
void log;
83152
return null;
84153
}
85154

86-
needsRefresh(credentials) {
155+
needsRefresh(credentials: ProviderCredentials) {
87156
if (!credentials.expiresAt) return false;
88157
const expiresAtMs = new Date(credentials.expiresAt).getTime();
89158
return expiresAtMs - Date.now() < 5 * 60 * 1000;
90159
}
91160

92-
parseError(response, bodyText) {
161+
parseError(response: Response, bodyText: string) {
93162
return { status: response.status, message: bodyText || `HTTP ${response.status}` };
94163
}
95164

96-
async execute({ model, body, stream, credentials, signal, log }) {
165+
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput) {
97166
const fallbackCount = this.getFallbackCount();
98-
let lastError = null;
167+
let lastError: unknown = null;
99168
let lastStatus = 0;
100169

101170
for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) {
@@ -109,10 +178,10 @@ export class BaseExecutor {
109178
const timeoutSignal = !stream ? AbortSignal.timeout(FETCH_TIMEOUT_MS) : null;
110179
const combinedSignal =
111180
signal && timeoutSignal
112-
? AbortSignal.any([signal, timeoutSignal])
181+
? mergeAbortSignals(signal, timeoutSignal)
113182
: signal || timeoutSignal;
114183

115-
const fetchOptions: Record<string, any> = {
184+
const fetchOptions: RequestInit = {
116185
method: "POST",
117186
headers,
118187
body: JSON.stringify(transformedBody),
@@ -130,15 +199,16 @@ export class BaseExecutor {
130199
return { response, url, headers, transformedBody };
131200
} catch (error) {
132201
// Distinguish timeout errors from other abort errors
133-
if (error.name === "TimeoutError") {
202+
const err = error instanceof Error ? error : new Error(String(error));
203+
if (err.name === "TimeoutError") {
134204
log?.warn?.("TIMEOUT", `Fetch timeout after ${FETCH_TIMEOUT_MS}ms on ${url}`);
135205
}
136-
lastError = error;
206+
lastError = err;
137207
if (urlIndex + 1 < fallbackCount) {
138208
log?.debug?.("RETRY", `Error on ${url}, trying fallback ${urlIndex + 1}`);
139209
continue;
140210
}
141-
throw error;
211+
throw err;
142212
}
143213
}
144214

open-sse/executors/cursor.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
declare var EdgeRuntime: any;
1+
declare const EdgeRuntime: string | undefined;
22
/**
33
* CursorExecutor — Handles communication with the Cursor IDE API.
44
*
@@ -121,13 +121,19 @@ function createErrorResponse(jsonError) {
121121
);
122122
}
123123

124+
type CursorHttpResponse = {
125+
status: number;
126+
headers: Record<string, unknown>;
127+
body: Buffer;
128+
};
129+
124130
export class CursorExecutor extends BaseExecutor {
125131
constructor() {
126132
super("cursor", PROVIDERS.cursor);
127133
}
128134

129135
buildUrl() {
130-
return `${this.config.baseUrl}${this.config.chatPath}`;
136+
return `${this.config.baseUrl}${this.config.chatPath || ""}`;
131137
}
132138

133139
// Jyh cipher checksum for Cursor API authentication
@@ -217,7 +223,12 @@ export class CursorExecutor extends BaseExecutor {
217223
return generateCursorBody(messages, model, tools, reasoningEffort);
218224
}
219225

220-
async makeFetchRequest(url, headers, body, signal) {
226+
async makeFetchRequest(
227+
url: string,
228+
headers: Record<string, string>,
229+
body: Uint8Array,
230+
signal?: AbortSignal
231+
): Promise<CursorHttpResponse> {
221232
const response = await fetch(url, {
222233
method: "POST",
223234
headers,
@@ -227,17 +238,22 @@ export class CursorExecutor extends BaseExecutor {
227238

228239
return {
229240
status: response.status,
230-
headers: Object.fromEntries((response.headers as any).entries()),
241+
headers: Object.fromEntries(response.headers.entries()),
231242
body: Buffer.from(await response.arrayBuffer()),
232243
};
233244
}
234245

235-
makeHttp2Request(url, headers, body, signal) {
246+
makeHttp2Request(
247+
url: string,
248+
headers: Record<string, string>,
249+
body: Uint8Array,
250+
signal?: AbortSignal
251+
): Promise<CursorHttpResponse> {
236252
if (!http2) {
237253
throw new Error("http2 module not available");
238254
}
239255

240-
return new Promise((resolve, reject) => {
256+
return new Promise<CursorHttpResponse>((resolve, reject) => {
241257
const urlObj = new URL(url);
242258
const client = http2.connect(`https://${urlObj.host}`);
243259
const chunks = [];
@@ -262,7 +278,10 @@ export class CursorExecutor extends BaseExecutor {
262278
req.on("end", () => {
263279
client.close();
264280
resolve({
265-
status: responseHeaders[":status"],
281+
status:
282+
typeof responseHeaders[":status"] === "number"
283+
? responseHeaders[":status"]
284+
: Number(responseHeaders[":status"] || HTTP_STATUS.SERVER_ERROR),
266285
headers: responseHeaders,
267286
body: Buffer.concat(chunks),
268287
});
@@ -291,7 +310,7 @@ export class CursorExecutor extends BaseExecutor {
291310
const transformedBody = this.transformRequest(model, body, stream, credentials);
292311

293312
try {
294-
const response: any = http2
313+
const response: CursorHttpResponse = http2
295314
? await this.makeHttp2Request(url, headers, transformedBody, signal)
296315
: await this.makeFetchRequest(url, headers, transformedBody, signal);
297316

@@ -459,7 +478,8 @@ export class CursorExecutor extends BaseExecutor {
459478

460479
console.log(`[CURSOR BUFFER] Final toolCalls count: ${toolCalls.length}`);
461480

462-
const message: Record<string, any> = { role: "assistant",
481+
const message: Record<string, unknown> = {
482+
role: "assistant",
463483
content: totalContent || null,
464484
};
465485

open-sse/executors/default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class DefaultExecutor extends BaseExecutor {
7474

7575
/**
7676
* For compatible providers, ensure the model name sent upstream
77-
* is the clean model name without any internal routing prefix.
77+
* is the clean model name without internal routing prefixes.
7878
* e.g. "openapi-chat-anti/claude-opus-4-6-thinking" → "claude-opus-4-6-thinking"
7979
*/
8080
transformRequest(model, body, stream, credentials) {

open-sse/executors/iflow.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import crypto from "crypto";
22
import { BaseExecutor } from "./base.ts";
33
import { PROVIDERS } from "../config/constants.ts";
44

5+
type IFlowCredentials = {
6+
apiKey?: string;
7+
accessToken?: string;
8+
};
9+
510
/**
611
* IFlowExecutor - Executor for iFlow API with HMAC-SHA256 signature.
712
*
@@ -41,7 +46,7 @@ export class IFlowExecutor extends BaseExecutor {
4146
* Build headers with iFlow-specific HMAC-SHA256 signature.
4247
* Includes session-id, x-iflow-timestamp, and x-iflow-signature.
4348
*/
44-
buildHeaders(credentials: any, stream = true) {
49+
buildHeaders(credentials: IFlowCredentials, stream = true) {
4550
// Generate session ID and timestamp
4651
const sessionID = `session-${crypto.randomUUID()}`;
4752
const timestamp = Date.now();
@@ -82,14 +87,26 @@ export class IFlowExecutor extends BaseExecutor {
8287
/**
8388
* Build URL for iFlow API — uses baseUrl directly.
8489
*/
85-
buildUrl(model: string, stream: boolean, urlIndex = 0, credentials: any = null) {
90+
buildUrl(
91+
model: string,
92+
stream: boolean,
93+
urlIndex = 0,
94+
credentials: IFlowCredentials | null = null
95+
) {
96+
void model;
97+
void stream;
98+
void urlIndex;
99+
void credentials;
86100
return this.config.baseUrl;
87101
}
88102

89103
/**
90104
* Transform request body (passthrough for iFlow).
91105
*/
92-
transformRequest(model: string, body: any, stream: boolean, credentials: any) {
106+
transformRequest(model: string, body: unknown, stream: boolean, credentials: IFlowCredentials) {
107+
void model;
108+
void stream;
109+
void credentials;
93110
return body;
94111
}
95112
}

0 commit comments

Comments
 (0)