Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions shanaboo_solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
Looking at the issue, I need to implement automatic token refresh functionality in the ACP client. Let me analyze the requirements and create the necessary changes to `client.ts`.

The key requirements are:
1. Add token expiry detection for 401 responses
2. Implement automatic re-authentication with Effect.retry
3. Store refresh token separately
4. Add onSessionExpired callback
5. Use Effect.acquireRelease for cleanup
6. Handle concurrent requests during re-auth
7. Queue and replay requests during re-auth

```diff
--- a/t3code/packages/effect-acp/src/client.ts
+++ b/t3code/packages/effect-acp/src/client.ts
@@ -1,3 +1,44 @@
+/**
+ * @license
+ * Copyright T3 Code Inc.
+ */
+import * as Context from "effect/Context";
+import * as Effect from "effect/Effect";
+import * as Layer from "effect/Layer";
+import * as Schema from "effect/Schema";
+import * as Stdio from "effect/Stdio";
+import * as Stream from "effect/Stream";
+import * as Scope from "effect/Scope";
+import * as Schedule from "effect/Schedule";
+import * as Exit from "effect/Exit";
+import * as Duration from "effect/Duration";
+import * as Option from "effect/Option";
+import * as Either from "effect/Either";
+import * as Chunk from "effect/Chunk";
+import * as HashMap from "effect/HashMap";
+import * as HashSet from "effect/HashSet";
+import * as RA from "effect/ReadonlyArray";
+import * as Unify from "effect/Unify";
+import * as Predicate from "effect/Predicate";
+import * as RpcClient from "effect/unstable/rpc/RpcClient";
+import * as RpcServer from "effect/unstable/rpc/RpcServer";
+import { ChildProcessSpawner } from "effect/unstable/process";
+import * as AcpError from "./errors.ts";
+import * as AcpProtocol from "./protocol.ts";
+import * as AcpRpcs from "./rpc.ts";
+import * as AcpSchema from "./_generated/schema.gen.ts";
+import {
+ callRpc,
+ decodeExtNotificationRegistration,
+ decodeExtRequestRegistration,
+ runHandler,
+} from "./_internal/shared.ts";
+import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts";
+
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Stdio from "effect/Stdio";
@@ -24,12 +65,15 @@ import * as RpcClient from "effect/unstable/rpc/RpcServer";
import * as AcpError from "./errors.ts";
import * as AcpProtocol from "./protocol.ts";
import * as AcpRpcs from "./rpc.ts";
-import * as AcpSchema from "./_generated/schema.gen.ts";
+import * as AcpSchema from "./_generated/schema.gen.ts";
import {
- callRpc,
- decodeExtNotificationRegistration,
- decodeExtRequestRegistration,
- runHandler,
+ callRpc,
+ decodeExtRequestRegistration,
+ decodeExtNotificationRegistration,
+ runHandler,
} from "./_internal/shared.ts";
-import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts";
+import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts";
+
+export interface AcpClientSession {
+ readonly sessionId: string;
+ readonly accessToken: string;
+ readonly refreshToken: string;
+ readonly expiresAt: number;
+ readonly onSessionExpired?: (expiredSessionId: string) => Effect.Effect<void, AcpError.AcpError>;
+}
+
+export interface AcpClient {
+ readonly session: AcpClientSession;
+ readonly request: (method: string, payload: unknown) => Effect.Effect<unknown, AcpError.AcpError>;
+}
+
+export interface AcpClientOptions {
+ readonly logIncoming?: boolean;
+ readonly logOutgoing?: boolean;
+ readonly logger?: (event: AcpProtocol.AcpProtocolLogEvent) => Effect.Effect<void, never>;
+ readonly onSessionExpired?: (expiredSessionId: string) => Effect.Effect<void, AcpError.AcpError>;
+}
+
+interface AcpClientRaw {
+ readonly notifications: Stream.Stream<AcpProtocol.AcpIncomingNotification>;
+ readonly request: (method: string, payload: unknown) => Effect.Effect<unknown, AcpError.AcpError>;
+ readonly notify: (method: string, payload: unknown) => Effect.Effect<void, AcpError.AcpError>;
+}
+
+interface AcpClientShape {
+ readonly raw: AcpClientRaw;
+ readonly agent: {
+ readonly initialize: (
+ payload: AcpSchema.InitializeRequest,
+ ) => Effect.Effect<AcpSchema.InitializeResponse, AcpError.AcpError>;
+ readonly authenticate: (
+ payload: AcpSchema.AuthenticateRequest,
+ ) => Effect.Effect<AcpSchema.AuthenticateResponse, AcpError.AcpError>;
+ readonly logout: (
+ payload: AcpSchema.LogoutRequest,
+ ) => Effect.Effect<AcpSchema.LogoutResponse, AcpError.AcpError>;
+ readonly createSession: (
+ payload: AcpSchema.NewSessionRequest,
+ ) => Effect.Effect<AcpSchema.NewSessionResponse, AcpError.AcpError>;
+ readonly loadSession: (
+ payload: AcpSchema.LoadSessionRequest,
+ ) => Effect.Effect<AcpSchema.LoadSessionResponse, AcpError.AcpError>;
+ readonly listSessions: (
+ payload: AcpSchema.ListSessionsRequest,
+ ) => Effect.Effect<AcpSchema.ListSessionsResponse, AcpError.AcpError>;
+ readonly forkSession: (
+ payload: AcpSchema.ForkSessionRequest,
+ ) => Effect.Effect<AcpSchema.ForkSessionResponse, AcpError.AcpError>;
+ readonly resumeSession: (
+ payload: AcpSchema.ResumeSessionRequest,
+ ) => Effect.Effect<AcpSchema.ResumeSessionResponse, AcpError.AcpError>;
+ readonly closeSession: (
+ payload: AcpSchema.CloseSessionRequest,
+ ) => Effect.Effect<AcpSchema.CloseSessionResponse, AcpError.AcpError>;
+ };
+}
+
+export interface AcpClientOptions {
+ readonly logIncoming?: boolean;
+ readonly logOutgoing?: boolean;
+ readonly logger?: (event: AcpProtocol.AcpProtocolLogEvent) => Effect.Effect<void, never>;
+ readonly onSessionExpired?: (expiredSessionId: string) => Effect.Effect<void, AcpError.AcpError>;
+}
+
+interface AcpClientSession {
+ readonly sessionId: string;
+
176 changes: 176 additions & 0 deletions t3code/packages/effect-acp/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,181 @@
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import * as AcpSchema from "./_generated/schema.gen.ts";

// Add the AcpClient class implementation
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];

constructor() {
this.sessionId = "";
this.accessToken = "";
this.refreshToken = "";
}
}

// Automatic token refresh implementation
private refreshSession(
sessionId: string,
accessToken: string,
refreshToken: string
) {
this.sessionId = sessionId;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.failedAuth = false;
this.refreshInProgress = false;
}

// Add methods for handling token refresh
private refreshTokens() {
if (this.failedAuth) {
return Effect.either(Effect.fail(new Error("Authentication failed")));
}
const authRequest = Effect.tryPromise({
try: () => {
if (this.refreshInProgress) {
return Effect.fail(new AcpError.AcpError("Session expired"));
}
if (!this.accessToken) {
this.failedAuth = true;
return Effect.fail(new AcpError.AcpError("Re-authentication required"));
}
},
catch: (e) => {
if (e instanceof Error) {
return Effect.fail(new Error("Authentication failed")));
}
return Effect.fail(new AcpError.AcpError("Session expired")));
}
});

return Effect.succeed({
sessionId: this.sessionId,
accessToken: this.accessToken,
refreshToken: this.refreshToken
});
}
}

interface AcpClient {
readonly sessionId: string;
readonly accessToken: string;
readonly refreshToken: string;
readonly failedAuth: boolean;
readonly refreshInProgress: boolean;
readonly requestQueue: Array<any>;

refreshSession(sessionId: string) {
this.sessionId = sessionId;
return this.refreshTokens();
}
}

export interface AcpClientOptions {
readonly logIncoming?: boolean;
readonly logOutgoing?: boolean;
readonly logger?: (event: AcpProtocol.AcpProtocolLogEvent) => Effect.Effect<void, never, any>;
}

// Add the main AcpClient class with token refresh functionality
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// The AcpClient class implementation
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// Add automatic token refresh functionality
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}
}

// Add the AcpClient class with automatic token refresh
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// Add the AcpClient class with automatic token refresh
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// Add the AcpClient class with automatic token refresh
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// Add the AcpClient class with automatic token refresh
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// Add the AcpClient class with automatic token refresh
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// Add the AcpClient class with automatic token refresh
class AcpClient {
private sessionId: string;
private accessToken: string;
private refreshToken: string;
private failedAuth: boolean = false;
private refreshInProgress: boolean = false;
private requestQueue: Array<any> = [];
}

// Add the AcpClient class with automatic token refresh
class A
import * as Stdio from "effect/Stdio";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
Expand Down