Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": {
"editor.defaultFormatter": "biomejs.biome"
Expand All @@ -14,5 +13,6 @@
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"js/ts.tsdk.path": "node_modules/typescript/lib"
"js/ts.tsdk.path": "./node_modules/typescript/lib",
"js/ts.tsdk.promptToUseWorkspaceVersion": true
}
2 changes: 1 addition & 1 deletion apps/client/src/lib/atoms/tick-atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const tickAtom: Atom.AtomResultFn<
> = runtime.fn(({ abort = false }: { readonly abort?: boolean }) =>
Stream.unwrap(
Effect.gen(function* () {
yield* Effect.log("Starting Tick Atom Stream");
yield* Effect.logDebug("Starting Tick Atom Stream");
const rpc = yield* RpcClient;
return rpc.client.tick({ ticks: 10 });
}).pipe((self) => (abort ? Effect.interrupt : self)),
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/lib/web-socket-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const presenceSubscriptionAtom: Atom.AtomResultFn<
RpcClientError | Cause.NoSuchElementException
> = WebSocketClient.runtime.fn(() =>
Effect.gen(function* () {
yield* Effect.log("Starting presence subscription stream");
yield* Effect.logDebug("Starting presence subscription stream");
const client = yield* WebSocketClient;
return client("subscribe", {});
}).pipe(
Expand Down
7 changes: 1 addition & 6 deletions apps/client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@
"paths": {
"@/*": ["./src/*"],
"~/*": ["./public/*"]
},
"plugins": [
{
"name": "@effect/language-service"
}
]
}
},
"references": [{ "path": "./tsconfig.config.json" }],
"include": ["src", "test"],
Expand Down
2 changes: 1 addition & 1 deletion apps/server-mcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const DevToolsLive = Effect.gen(function* () {
if (!config.enableDevTools) {
return Layer.empty;
}
yield* Effect.log("Enabling DevTools Layer");
yield* Effect.logInfo("Enabling DevTools Layer");
return DevTools.layer();
}).pipe(Layer.unwrapEffect);

Expand Down
7 changes: 1 addition & 6 deletions apps/server-mcp/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
"compilerOptions": {
"outDir": "dist",
"noEmit": true,
"types": ["@types/bun"],
"plugins": [
{
"name": "@effect/language-service"
}
]
"types": ["@types/bun"]
},
"references": [{ "path": "../../packages/domain" }],
"include": ["src/**/*"],
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/Rpc/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { Effect, Mailbox } from "effect";
export const EventRpcLive = EventRpc.toLayer(
Effect.gen(function* () {
const bot = yield* ChatService;
yield* Effect.log("Starting Event RPC Live Implementation");
yield* Effect.logInfo("Starting Event RPC Live Implementation");
return {
tick: Effect.fn(function* (payload) {
yield* Effect.log("Creating new tick stream");
yield* Effect.logDebug("Creating new tick stream");
const mailbox = yield* Mailbox.make<typeof TickEvent.Type>();
yield* Effect.forkScoped(
Effect.gen(function* () {
Expand All @@ -20,7 +20,7 @@ export const EventRpcLive = EventRpc.toLayer(
yield* mailbox.offer({ _tag: "tick" });
}
yield* mailbox.offer({ _tag: "end" });
yield* Effect.log("End event sent");
yield* Effect.logDebug("End event sent");
}).pipe(Effect.ensuring(mailbox.end)),
);
return mailbox;
Expand Down
14 changes: 7 additions & 7 deletions apps/server/src/Rpc/Presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import {
WebSocketRpc,
} from "@repo/domain/WebSocket";
import { PresenceService } from "@repo/presence";
import { Effect, Mailbox, Queue, Stream } from "effect";
import { DateTime, Effect, Mailbox, Queue, Stream } from "effect";

export const PresenceRpcLive = WebSocketRpc.toLayer(
Effect.gen(function* () {
const presence = yield* PresenceService;
yield* Effect.log("Starting Presence RPC Live Implementation");
yield* Effect.logInfo("Starting Presence RPC Live Implementation");

return {
subscribe: Effect.fn(function* () {
yield* Effect.log("New presence subscription");
yield* Effect.logDebug("New presence subscription");

const clientId = presence.generateClientId();
const connectedAt = Date.now();
const connectedAt = yield* DateTime.now;
const clientInfo: ClientInfo = {
clientId,
status: "online",
Expand Down Expand Up @@ -49,7 +49,7 @@ export const PresenceRpcLive = WebSocketRpc.toLayer(
yield* Queue.shutdown(subscription);
yield* presence.removeClient(clientId);
yield* mailbox.end;
yield* Effect.log(
yield* Effect.logDebug(
`Presence subscription ended for ${clientId}`,
);
}),
Expand Down Expand Up @@ -82,7 +82,7 @@ export const PresenceRpcLive = WebSocketRpc.toLayer(
}),

setStatus: Effect.fn(function* (payload) {
yield* Effect.log(
yield* Effect.logDebug(
`Setting status for ${payload.clientId} to ${payload.status}`,
);
yield* presence.setStatus(payload.clientId, payload.status);
Expand All @@ -91,7 +91,7 @@ export const PresenceRpcLive = WebSocketRpc.toLayer(

getPresence: Effect.fn(function* () {
const clients = yield* presence.getClients();
yield* Effect.log(`Returning ${clients.length} clients`);
yield* Effect.logDebug(`Returning ${clients.length} clients`);
return { clients: [...clients] };
}),
};
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("Server", () => {
it.effect("can log messages", () =>
Effect.gen(function* () {
// Act: Run an effect that logs (logs are suppressed in test context)
yield* Effect.log("Test log message");
yield* Effect.logDebug("Test log message");

// Assert: Effect completes successfully
expect(true).toBe(true);
Expand Down
12 changes: 6 additions & 6 deletions apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,19 @@ const DevToolsLive = Effect.gen(function* () {
if (!config.enableDevTools) {
return Layer.empty;
}
yield* Effect.log("Enabling DevTools Layer");
yield* Effect.logInfo("Enabling DevTools Layer");
return DevTools.layer();
}).pipe(Layer.unwrapEffect);

const HttpLive = Effect.gen(function* () {
const config = yield* ServerConfig;
const allowedOrigins = config.allowedOrigins.split(",").map((o) => o.trim());

yield* Effect.log(`CORS allowed origins: ${allowedOrigins.join(", ")}`);
yield* Effect.log("Starting server with:");
yield* Effect.log(" - HTTP API at /");
yield* Effect.log(" - HTTP RPC at /rpc (EventRpc)");
yield* Effect.log(" - WebSocket RPC at /ws (PresenceRpc)");
yield* Effect.logInfo(`CORS allowed origins: ${allowedOrigins.join(", ")}`);
yield* Effect.logInfo("Starting server with:");
yield* Effect.logInfo(" - HTTP API at /");
yield* Effect.logInfo(" - HTTP RPC at /rpc (EventRpc)");
yield* Effect.logInfo(" - WebSocket RPC at /ws (PresenceRpc)");

const AllRouters = Layer.mergeAll(
ApiRouter,
Expand Down
7 changes: 1 addition & 6 deletions apps/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
"rootDir": "../..",
"outDir": "dist",
"noEmit": true,
"types": ["@types/bun"],
"plugins": [
{
"name": "@effect/language-service"
}
]
"types": ["@types/bun"]
},
"include": ["src/**/*", "../../packages/ai/src/LanguageModel.ts"],
"exclude": ["node_modules", "dist"]
Expand Down
9 changes: 8 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
"lint": "biome lint .",
"format": "biome check --write .",
"format:check": "biome check .",
"prepare": "effect-language-service patch",
"test": "turbo run test",
"test:e2e": "playwright test",
"type-check": "turbo run type-check"
},
"devDependencies": {
"@effect/language-service": "0.85.1",
"@biomejs/biome": "2.4.8",
"@playwright/test": "^1.58.2",
"@types/bun": "^1.3.11",
Expand Down
6 changes: 3 additions & 3 deletions packages/ai/src/services/ChatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class ChatService extends Effect.Service<ChatService>()("ChatService", {
// Fork the agentic loop to run in background
yield* Effect.forkScoped(
Effect.gen(function* () {
yield* Effect.log(
yield* Effect.logInfo(
`[craftsman] Creating chat with ${1 + history.length} messages`,
);
const systemMessage = String.stripMargin(`
Expand All @@ -28,10 +28,10 @@ export class ChatService extends Effect.Service<ChatService>()("ChatService", {
Prompt.make(history).pipe(Prompt.setSystem(systemMessage)),
);

yield* Effect.log(
yield* Effect.logTrace(
Prompt.make(history).pipe(Prompt.setSystem(systemMessage)),
);
yield* Effect.log(yield* session.exportJson);
yield* Effect.logTrace(yield* session.exportJson);

const toolkit = yield* Toolkit.merge(SampleToolkit);

Expand Down
18 changes: 10 additions & 8 deletions packages/ai/src/toolkits/SampleToolkit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Tool, Toolkit } from "@effect/ai";
import { Effect, Schema } from "effect";
import { DateTime, Effect, Schema } from "effect";

/**
* Calculator Tool - Safely evaluates mathematical expressions
Expand Down Expand Up @@ -42,7 +42,7 @@ export const SampleToolkitLive = SampleToolkit.toLayer(
return {
calculate: (params) =>
Effect.gen(function* () {
yield* Effect.log(`Calculating: ${params.expression}`);
yield* Effect.logDebug(`Calculating: ${params.expression}`);

// Simple safe evaluation for basic math
// Whitelist allowed characters
Expand Down Expand Up @@ -71,19 +71,21 @@ export const SampleToolkitLive = SampleToolkit.toLayer(

echo: (params) =>
Effect.gen(function* () {
yield* Effect.log(`Echo: ${params.message}`);
yield* Effect.logDebug(`Echo: ${params.message}`);
return yield* Effect.succeed(`Echo: ${params.message}`);
}),

getCurrentTime: () =>
Effect.gen(function* () {
const now = new Date();
const timeString = now.toLocaleString("en-US", {
timeZone: "UTC",
const now = yield* DateTime.now;
const timeString = DateTime.formatUtc(now, {
locale: "en-US",
dateStyle: "medium",
timeStyle: "medium",
});
yield* Effect.log(`Current time (UTC): ${timeString}`);
yield* Effect.logDebug(`Current time (UTC): ${timeString}`);
return yield* Effect.succeed(
`Current time in UTC: ${timeString} (ISO: ${now.toISOString()})`,
`Current time in UTC: ${timeString} (ISO: ${DateTime.formatIso(now)})`,
);
}),
};
Expand Down
31 changes: 17 additions & 14 deletions packages/ai/src/workflow/AgenticLoop.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import type { Chat, Tool, Toolkit } from "@effect/ai";
import type { ChatStreamPart } from "@repo/domain/Chat";
import { Effect, type Mailbox, Ref, Schema, Stream } from "effect";
import { Effect, Inspectable, type Mailbox, Ref, Schema, Stream } from "effect";
import { createMailboxEvents } from "./MailboxEvents";

export const AgenticLoopState = Schema.Struct({
finishReason: Schema.String,
iteration: Schema.Number,
});
// Schema for parsing tool parameters (JSON string -> object with unknown keys/values)

export const ToolParamsSchema = Schema.parseJson(
Schema.Record({ key: Schema.String, value: Schema.Unknown }),
);
Expand Down Expand Up @@ -109,7 +103,7 @@ const loop = <TR extends Record<string, Tool.Any>>({
)(toolCall.params?.trim() || "{}").pipe(
Effect.tapError((error) =>
Effect.logError(
`Failed to parse tool arguments for ${toolCall.name}: ${JSON.stringify(error)}`,
`Failed to parse tool arguments for ${toolCall.name}: ${Inspectable.toStringUnknown(error, 2)}`,
),
),
Effect.orElseSucceed(() => ({})),
Expand All @@ -127,11 +121,15 @@ const loop = <TR extends Record<string, Tool.Any>>({
}

case "tool-result": {
const resultText = part.isFailure
? part.result
: typeof part.result === "string"
const resultText =
part.isFailure || typeof part.result === "string"
? part.result
: JSON.stringify(part.result);
: yield* Effect.orElseSucceed(
Schema.encode(Schema.parseJson({ space: 2 }))(
part.result,
),
() => Inspectable.toStringUnknown(part.result, 2),
);

if (part.isFailure) {
yield* Effect.logError(
Expand Down Expand Up @@ -162,7 +160,12 @@ const loop = <TR extends Record<string, Tool.Any>>({
yield* events.error(
typeof part.error === "string"
? part.error
: JSON.stringify(part.error),
: yield* Effect.orElseSucceed(
Schema.encode(Schema.parseJson({ space: 2 }))(
part.error,
),
() => Inspectable.toStringUnknown(part.error, 2),
),
false,
);
break;
Expand Down Expand Up @@ -210,7 +213,7 @@ export const runAgenticLoop = <TR extends Record<string, Tool.Any>>({

const finishReason = yield* loop({ chat, mailbox, toolkit });

yield* Effect.log(
yield* Effect.logDebug(
`Iteration ${iteration} completed with finishReason: ${finishReason}`,
);

Expand Down
7 changes: 5 additions & 2 deletions packages/ai/src/workflow/MailboxEvents.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ChatStreamPart } from "@repo/domain/Chat";
import { Effect, type Mailbox } from "effect";
import { Effect, Inspectable, type Mailbox, Schema } from "effect";

/**
* MailboxEvents - Typed event emitter for ChatStreamPart
Expand Down Expand Up @@ -34,7 +34,10 @@ export const createMailboxEvents = (
});

// Delta (stream arguments as JSON)
const argsJson = JSON.stringify(params.arguments, null, 2);
const argsJson = yield* Effect.orElseSucceed(
Schema.encode(Schema.parseJson({ space: 2 }))(params.arguments),
() => Inspectable.toStringUnknown(params.arguments, 2),
);
yield* mailbox.offer({
_tag: "tool-call-delta",
id,
Expand Down
Loading
Loading