Skip to content

Commit

Permalink
refactor(iframe): enhance method exchange and logging in thread creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Sma1lboy committed Jan 21, 2025
1 parent 4b83730 commit 9d8fbbe
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 30 deletions.
8 changes: 6 additions & 2 deletions clients/tabby-threads/source/targets/iframe/iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export async function createThreadFromIframe<
send(message, transfer) {
if (!connected) {
console.log(
"[createThreadFromIframe] Message queued until connection:",
"[createThreadFromIframe] Queuing message until connected:",
message
);
return connectedPromise.then(() => {
Expand All @@ -115,7 +115,6 @@ export async function createThreadFromIframe<
);
});
}

return sendMessage(message, transfer);
},
listen(listen, { signal }) {
Expand Down Expand Up @@ -147,5 +146,10 @@ export async function createThreadFromIframe<
);

console.log("[createThreadFromIframe] Thread created successfully");

// After connection is established and thread is created, exchange methods
console.log("[createThreadFromIframe] Connection ready, exchanging methods");
thread.exchangeMethods();

return thread;
}
11 changes: 11 additions & 0 deletions clients/tabby-threads/source/targets/iframe/nested.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,16 @@ export async function createThreadFromInsideIframe<
);

console.log("[createThreadFromInsideIframe] Thread created successfully");

// After connection is established and thread is created, request methods from outside
console.log(
"[createThreadFromInsideIframe] Connection ready, requesting methods from outside"
);
const methods = await thread.requestMethods();
console.log(
"[createThreadFromInsideIframe] Received methods from outside:",
methods
);

return thread;
}
104 changes: 80 additions & 24 deletions clients/tabby-threads/source/targets/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ export async function createThread<
const idsToFunction = new Map<string, AnyFunction>();
const idsToProxy = new Map<string, AnyFunction>();

// Cache for the other side's methods
let theirMethodsCache: string[] | null = null;

if (expose) {
for (const key of Object.keys(expose)) {
const value = expose[key as keyof typeof expose];
Expand All @@ -131,33 +134,73 @@ export async function createThread<
) => void
>();

console.log("[createThread] Starting expose list exchange");
// Create functions for method exchange
const exchangeMethods = () => {
console.log("[createThread] Starting expose list exchange");
const ourMethods = Array.from(activeApi.keys()).map(String);
console.log("[createThread] Our expose list:", ourMethods);

const id = uuid();
console.log("[createThread] Setting up expose list resolver");

// This will be called when we receive the RESULT with other side's methods
callIdsToResolver.set(id, (_, __, value) => {
const theirMethods = encoder.decode(value, encoderApi) as string[];
console.log(
"[createThread] Got RESULT with other side's methods:",
theirMethods
);
// Cache their methods
theirMethodsCache = theirMethods;
console.log("[createThread] Expose list exchange completed");
});

// Send our expose list to the other side
const ourMethods = Array.from(activeApi.keys()).map(String);
console.log("[createThread] Our expose list:", ourMethods);
// Send EXPOSE_LIST with our methods
console.log("[createThread] Sending EXPOSE_LIST with our methods");
send(EXPOSE_LIST, [id, ourMethods]);
};

const id = uuid();
console.log("[createThread] Setting up expose list resolver");
// Create a function to request methods from the other side
const requestMethods = async () => {
// If we have cached methods and connection is still active, return them
if (theirMethodsCache !== null && !terminated) {
console.log(
"[createThread] Returning cached methods:",
theirMethodsCache
);
return theirMethodsCache;
}

// This will be called when we receive the RESULT with other side's methods
callIdsToResolver.set(id, (_, __, value) => {
const theirMethods = encoder.decode(value, encoderApi) as string[];
console.log(
"[createThread] Got RESULT with other side's methods:",
theirMethods
);
// Store their methods for future use if needed
console.log("[createThread] Expose list exchange completed");
});
console.log("[createThread] Requesting methods from other side");
const id = uuid();

// Send EXPOSE_LIST with our methods
console.log("[createThread] Sending EXPOSE_LIST with our methods");
send(EXPOSE_LIST, [id, ourMethods]);
// Create a promise that will resolve with the other side's methods
const methodsPromise = new Promise<string[]>((resolve) => {
callIdsToResolver.set(id, (_, __, value) => {
const theirMethods = encoder.decode(value, encoderApi) as string[];
console.log(
"[createThread] Got RESULT with other side's methods:",
theirMethods
);
// Cache the methods for future use
theirMethodsCache = theirMethods;
resolve(theirMethods);
});
});

// Send EXPOSE_LIST with empty methods array to request other side's methods
console.log("[createThread] Sending method request");
send(EXPOSE_LIST, [id, []]);

return methodsPromise;
};

// Create proxy without waiting for response
// Create proxy for method calls
console.log("[createThread] Creating proxy without waiting for response");
const call = createCallable<Thread<Target>>(handlerForCall, callable);
const call = createCallable<Thread<Target>>(handlerForCall, callable, {
exchangeMethods,
requestMethods,
});

const encoderApi: ThreadEncoderApi = {
functions: {
Expand Down Expand Up @@ -243,6 +286,7 @@ export async function createThread<
functionsToId.clear();
idsToFunction.clear();
idsToProxy.clear();
theirMethodsCache = null; // Clear the cache when connection is terminated
};

signal?.addEventListener(
Expand All @@ -256,7 +300,7 @@ export async function createThread<

target.listen(listener, { signal });

return Promise.resolve(call);
return call;

function send<Type extends keyof MessageMap>(
type: Type,
Expand All @@ -278,6 +322,7 @@ export async function createThread<
async function listener(rawData: unknown) {
console.log("[createThread] Received raw data:", rawData);

// FIXME: don't ignore anything, just for testing now
const isThreadMessageData =
Array.isArray(rawData) &&
typeof rawData[0] === "number" &&
Expand Down Expand Up @@ -433,7 +478,6 @@ export async function createThread<
}

function handlerForCall(property: string | number | symbol) {
if (property === "then") return undefined;
return (...args: any[]) => {
try {
if (terminated) {
Expand Down Expand Up @@ -527,7 +571,11 @@ function createCallable<T>(
handlerForCall: (
property: string | number | symbol
) => AnyFunction | undefined,
callable?: (keyof T)[]
callable?: (keyof T)[],
methods?: {
exchangeMethods: () => void;
requestMethods: () => Promise<string[]>;
}
): T {
console.log("[createCallable] Creating callable with methods:", callable);
let call: any;
Expand All @@ -546,6 +594,14 @@ function createCallable<T>(
{
get(_target, property) {
if (property === "then") return undefined;
if (property === "exchangeMethods") {
console.log("[createCallable] Accessing exchangeMethods");
return methods?.exchangeMethods;
}
if (property === "requestMethods") {
console.log("[createCallable] Accessing requestMethods");
return methods?.requestMethods;
}
console.log("[createCallable] Accessing property:", property);
if (cache.has(property)) {
console.log("[createCallable] Using cached handler for:", property);
Expand Down
26 changes: 22 additions & 4 deletions clients/tabby-threads/source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
RETAIN_METHOD,
ENCODE_METHOD,
RETAINED_BY,
} from './constants.ts';
} from "./constants.ts";

/**
* A thread represents a target JavaScript environment that exposes a set
Expand All @@ -18,6 +18,21 @@ export type Thread<Target> = {
? Target[K]
: never
: never;
} & {
/**
* Exchange method lists between threads after connection is established.
* This should be called only after the connection is ready to ensure
* proper method exchange.
*/
exchangeMethods(): void;

/**
* Request methods from the other side and wait for response.
* Returns a promise that resolves with the list of available methods.
* This is useful when you want to get the methods list from the other side
* without sending your own methods.
*/
requestMethods(): Promise<string[]>;
};

/**
Expand All @@ -40,7 +55,10 @@ export interface ThreadTarget {
* and handle its content. This method may be passed an `AbortSignal` to abort the
* listening process.
*/
listen(listener: (value: any) => void, options: {signal?: AbortSignal}): void;
listen(
listener: (value: any) => void,
options: { signal?: AbortSignal }
): void;
}

/**
Expand Down Expand Up @@ -82,7 +100,7 @@ export interface ThreadEncoder {
decode(
value: unknown,
api: ThreadEncoderApi,
retainedBy?: Iterable<MemoryRetainer>,
retainedBy?: Iterable<MemoryRetainer>
): unknown;
}

Expand Down Expand Up @@ -112,7 +130,7 @@ export interface ThreadEncoderApi {
* An object that provides a custom process to encode its value.
*/
export interface ThreadEncodable {
[ENCODE_METHOD](api: {encode(value: any): unknown}): any;
[ENCODE_METHOD](api: { encode(value: any): unknown }): any;
}

export type AnyFunction = Function;
1 change: 1 addition & 0 deletions clients/vscode/src/chat/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ async function createThreadFromWebview<Self = Record<string, never>, Target = Re
},
options,
);
setTimeout(() => thread.exchangeMethods(), 3000);
getLogger().info("Thread created");
return thread;
}
Expand Down

0 comments on commit 9d8fbbe

Please sign in to comment.