| author | |
| committer | |
| log | 22384ae977a362c547d6f57d4e3d92bbe55ee5db |
| tree | 69abe70575df5c0862210ab80b2ec4dc027ee2a2 |
| parent | 499f1463d5fe80350b34a6d511bdd873dd3d564f |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>3 files changed, 38 insertions(+), 24 deletions(-)
packages/effect-codex-app-server/src/protocol.test.ts+8-4| ... | @@ -2,6 +2,7 @@ import * as Deferred from "effect/Deferred"; | ... | @@ -2,6 +2,7 @@ import * as Deferred from "effect/Deferred"; |
| 2 | import * as Effect from "effect/Effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Fiber from "effect/Fiber"; | 3 | import * as Fiber from "effect/Fiber"; |
| 4 | import * as Queue from "effect/Queue"; | 4 | import * as Queue from "effect/Queue"; |
| 5 | import * as Schema from "effect/Schema"; | ||
| 5 | import * as Stream from "effect/Stream"; | 6 | import * as Stream from "effect/Stream"; |
| 6 | 7 | ||
| 7 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 8 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| ... | @@ -13,7 +14,10 @@ import { makeInMemoryStdio } from "./_internal/stdio.ts"; | ... | @@ -13,7 +14,10 @@ import { makeInMemoryStdio } from "./_internal/stdio.ts"; |
| 13 | 14 | ||
| 14 | const encoder = new TextEncoder(); | 15 | const encoder = new TextEncoder(); |
| 15 | 16 | ||
| 16 | const encodeJsonl = (value: unknown) => encoder.encode(`${JSON.stringify(value)}\n`); | 17 | const encodeJsonl = (value: unknown) => |
| 18 | encoder.encode(`${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(value)}\n`); | ||
| 19 | |||
| 20 | const decodeJson = Schema.decodeEffect(Schema.UnknownFromJsonString); | ||
| 17 | 21 | ||
| 18 | it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { | 22 | it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { |
| 19 | it.effect( | 23 | it.effect( |
| ... | @@ -60,7 +64,7 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { | ... | @@ -60,7 +64,7 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { |
| 60 | const pendingInitialize = yield* transport | 64 | const pendingInitialize = yield* transport |
| 61 | .request("initialize", initializeParams) | 65 | .request("initialize", initializeParams) |
| 62 | .pipe(Effect.forkScoped); | 66 | .pipe(Effect.forkScoped); |
| 63 | assert.deepEqual(JSON.parse(yield* Queue.take(output)), { | 67 | assert.deepEqual(yield* decodeJson(yield* Queue.take(output)), { |
| 64 | id: 1, | 68 | id: 1, |
| 65 | method: "initialize", | 69 | method: "initialize", |
| 66 | params: initializeParams, | 70 | params: initializeParams, |
| ... | @@ -153,7 +157,7 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { | ... | @@ -153,7 +157,7 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { |
| 153 | }, | 157 | }, |
| 154 | }, | 158 | }, |
| 155 | }); | 159 | }); |
| 156 | assert.deepEqual(JSON.parse(yield* Queue.take(output)), { | 160 | assert.deepEqual(yield* decodeJson(yield* Queue.take(output)), { |
| 157 | id: 77, | 161 | id: 77, |
| 158 | result: { | 162 | result: { |
| 159 | answers: { | 163 | answers: { |
| ... | @@ -168,7 +172,7 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { | ... | @@ -168,7 +172,7 @@ it.layer(NodeServices.layer)("effect-codex-app-server protocol", (it) => { |
| 168 | 78, | 172 | 78, |
| 169 | CodexError.CodexAppServerRequestError.methodNotFound("x/test"), | 173 | CodexError.CodexAppServerRequestError.methodNotFound("x/test"), |
| 170 | ); | 174 | ); |
| 171 | assert.deepEqual(JSON.parse(yield* Queue.take(output)), { | 175 | assert.deepEqual(yield* decodeJson(yield* Queue.take(output)), { |
| 172 | id: 78, | 176 | id: 78, |
| 173 | error: { | 177 | error: { |
| 174 | code: -32601, | 178 | code: -32601, |
packages/effect-codex-app-server/src/protocol.ts+27-18| ... | @@ -83,17 +83,35 @@ function isIncomingResponse(value: unknown): value is typeof JsonRpcResponseEnve | ... | @@ -83,17 +83,35 @@ function isIncomingResponse(value: unknown): value is typeof JsonRpcResponseEnve |
| 83 | return Schema.is(JsonRpcResponseEnvelope)(value); | 83 | return Schema.is(JsonRpcResponseEnvelope)(value); |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | const encodeJsonString = Schema.encodeUnknownEffect(Schema.UnknownFromJsonString); | ||
| 87 | const decodeJsonString = Schema.decodeUnknownEffect(Schema.UnknownFromJsonString); | ||
| 88 | |||
| 86 | const encodeWireMessage = ( | 89 | const encodeWireMessage = ( |
| 87 | message: Record<string, unknown>, | 90 | message: Record<string, unknown>, |
| 88 | ): Effect.Effect<string, CodexError.CodexAppServerProtocolParseError> => | 91 | ): Effect.Effect<string, CodexError.CodexAppServerProtocolParseError> => |
| 89 | Effect.try({ | 92 | encodeJsonString(message).pipe( |
| 90 | try: () => `${JSON.stringify(message)}\n`, | 93 | Effect.map((encoded) => `${encoded}\n`), |
| 91 | catch: (cause) => | 94 | Effect.mapError( |
| 92 | new CodexError.CodexAppServerProtocolParseError({ | 95 | (cause) => |
| 93 | detail: "Failed to encode Codex App Server message", | 96 | new CodexError.CodexAppServerProtocolParseError({ |
| 94 | cause, | 97 | detail: "Failed to encode Codex App Server message", |
| 95 | }), | 98 | cause, |
| 96 | }); | 99 | }), |
| 100 | ), | ||
| 101 | ); | ||
| 102 | |||
| 103 | const decodeWireMessage = ( | ||
| 104 | line: string, | ||
| 105 | ): Effect.Effect<unknown, CodexError.CodexAppServerProtocolParseError> => | ||
| 106 | decodeJsonString(line).pipe( | ||
| 107 | Effect.mapError( | ||
| 108 | (cause) => | ||
| 109 | new CodexError.CodexAppServerProtocolParseError({ | ||
| 110 | detail: "Failed to decode Codex App Server wire message", | ||
| 111 | cause, | ||
| 112 | }), | ||
| 113 | ), | ||
| 114 | ); | ||
| 97 | 115 | ||
| 98 | const normalizeIncomingError = (error: unknown, detail: string): CodexError.CodexAppServerError => | 116 | const normalizeIncomingError = (error: unknown, detail: string): CodexError.CodexAppServerError => |
| 99 | Schema.is(CodexError.CodexAppServerError)(error) | 117 | Schema.is(CodexError.CodexAppServerError)(error) |
| ... | @@ -284,16 +302,7 @@ export const makeCodexAppServerPatchedProtocol = Effect.fn("makeCodexAppServerPa | ... | @@ -284,16 +302,7 @@ export const makeCodexAppServerPatchedProtocol = Effect.fn("makeCodexAppServerPa |
| 284 | stage: "raw", | 302 | stage: "raw", |
| 285 | payload: line, | 303 | payload: line, |
| 286 | }).pipe( | 304 | }).pipe( |
| 287 | Effect.flatMap(() => | 305 | Effect.flatMap(() => decodeWireMessage(line)), |
| 288 | Effect.try({ | ||
| 289 | try: () => JSON.parse(line), | ||
| 290 | catch: (cause) => | ||
| 291 | new CodexError.CodexAppServerProtocolParseError({ | ||
| 292 | detail: "Failed to decode Codex App Server wire message", | ||
| 293 | cause, | ||
| 294 | }), | ||
| 295 | }), | ||
| 296 | ), | ||
| 297 | Effect.tap((decoded) => | 306 | Effect.tap((decoded) => |
| 298 | logProtocol({ | 307 | logProtocol({ |
| 299 | direction: "incoming", | 308 | direction: "incoming", |
scripts/notify-discord-release.ts+3-2| ... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | ||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Config, Data, Effect, Layer, Logger, Schema } from "effect"; | 5 | import { Config, Data, DateTime, Effect, Layer, Logger, Schema } from "effect"; |
| 6 | import { Argument, Command, Flag } from "effect/unstable/cli"; | 6 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 7 | import { | 7 | import { |
| 8 | FetchHttpClient, | 8 | FetchHttpClient, |
| ... | @@ -201,6 +201,7 @@ export const notifyDiscordReleaseCommand = Command.make( | ... | @@ -201,6 +201,7 @@ export const notifyDiscordReleaseCommand = Command.make( |
| 201 | ); | 201 | ); |
| 202 | 202 | ||
| 203 | const webhookUrl = yield* DiscordWebhookUrl; | 203 | const webhookUrl = yield* DiscordWebhookUrl; |
| 204 | const timestamp = DateTime.formatIso(yield* DateTime.now); | ||
| 204 | const payload = buildDiscordReleaseAnnouncement({ | 205 | const payload = buildDiscordReleaseAnnouncement({ |
| 205 | target, | 206 | target, |
| 206 | roleId, | 207 | roleId, |
| ... | @@ -208,7 +209,7 @@ export const notifyDiscordReleaseCommand = Command.make( | ... | @@ -208,7 +209,7 @@ export const notifyDiscordReleaseCommand = Command.make( |
| 208 | version: releaseVersion, | 209 | version: releaseVersion, |
| 209 | tag, | 210 | tag, |
| 210 | releaseUrl, | 211 | releaseUrl, |
| 211 | timestamp: new Date().toISOString(), | 212 | timestamp, |
| 212 | }); | 213 | }); |
| 213 | 214 | ||
| 214 | yield* Effect.logInfo("discord release announcement payload built").pipe( | 215 | yield* Effect.logInfo("discord release announcement payload built").pipe( |