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
35 changes: 18 additions & 17 deletions docs/modules/client/src.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface AgentClientOptions {

Configures agent client.

### [`ContextOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L131)
### [`ContextOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L126)

_Interface_

Expand All @@ -56,7 +56,7 @@ export interface ContextOptions {

Configures context.

### [`ConversationMeta`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L123)
### [`ConversationMeta`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/presentation/state.ts#L25)

_Interface_

Expand Down Expand Up @@ -154,7 +154,7 @@ export declare class MoltZapAgentClient extends ProtocolClientLifecycle<AgentCal

Implements molt zap agent client.

### [`MoltZapService`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L262)
### [`MoltZapService`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L233)

_Class_

Expand All @@ -174,22 +174,10 @@ export class MoltZapService {
*/
private serviceScope: Scope.CloseableScope | null = null;

private readonly conversationsRef: Ref.Ref<
HashMap.HashMap<string, ConversationMeta>
> = Effect.runSync(Ref.make(HashMap.empty<string, ConversationMeta>()));
private readonly messagesRef: Ref.Ref<
HashMap.HashMap<string, readonly Message[]>
> = Effect.runSync(Ref.make(HashMap.empty<string, readonly Message[]>()));
private readonly agentNamesRef: Ref.Ref<HashMap.HashMap<string, string>> =
Effect.runSync(Ref.make(HashMap.empty<string, string>()));
private readonly presentationState = new PresentationState();
private readonly agentConversationCacheRef: Ref.Ref<
HashMap.HashMap<string, ConversationId>
> = Effect.runSync(Ref.make(HashMap.empty<string, ConversationId>()));
private readonly lastNotifiedRef: Ref.Ref<
HashMap.HashMap<string, HashMap.HashMap<string, string>>
> = Effect.runSync(
Ref.make(HashMap.empty<string, HashMap.HashMap<string, string>>()),
);
private readonly lastReadRef: Ref.Ref<
HashMap.HashMap<string, HashMap.HashMap<string, ReadonlySet<string>>>
> = Effect.runSync(
Expand Down Expand Up @@ -279,6 +267,18 @@ export class MoltZapService {
agentKey: this.opts.agentKey,
// The body doesn't branch on close metadata today; the signature is
// kept explicit so a future disconnect-handler chain can plumb
// code/reason through.
onDisconnect: () => {
this.connectedValue = false;
fanout(this.handlers.disconnect, undefined);
},
});
this.client = client;

// `subscribeAll().pipe(Stream.runForEach, …)` is forked into a
// service-owned scope. The Stream is materialized BEFORE `connect()` so
// subscriptions are registered with the registry pre-handshake (a
// pre-connect-legal operation).
```

Stateful MoltZap client that manages connection, conversation tracking,
Expand All @@ -301,7 +301,7 @@ export interface RpcCallOptions {

Configures rpc call.

### [`ServiceRpcError`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L111)
### [`ServiceRpcError`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L114)

_TypeAlias_

Expand All @@ -319,4 +319,5 @@ to that method's errors at the `call` site.
## Files

- `harness-client.ts`
- `state.ts`
- `service.ts`
35 changes: 18 additions & 17 deletions packages/client/src/MODULE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export interface AgentClientOptions {

Configures agent client.

### [`ContextOptions`](./service.ts#L131)
### [`ContextOptions`](./service.ts#L126)

_Interface_

Expand All @@ -51,7 +51,7 @@ export interface ContextOptions {

Configures context.

### [`ConversationMeta`](./service.ts#L123)
### [`ConversationMeta`](./presentation/state.ts#L25)

_Interface_

Expand Down Expand Up @@ -149,7 +149,7 @@ export declare class MoltZapAgentClient extends ProtocolClientLifecycle<AgentCal

Implements molt zap agent client.

### [`MoltZapService`](./service.ts#L262)
### [`MoltZapService`](./service.ts#L233)

_Class_

Expand All @@ -169,22 +169,10 @@ export class MoltZapService {
*/
private serviceScope: Scope.CloseableScope | null = null;

private readonly conversationsRef: Ref.Ref<
HashMap.HashMap<string, ConversationMeta>
> = Effect.runSync(Ref.make(HashMap.empty<string, ConversationMeta>()));
private readonly messagesRef: Ref.Ref<
HashMap.HashMap<string, readonly Message[]>
> = Effect.runSync(Ref.make(HashMap.empty<string, readonly Message[]>()));
private readonly agentNamesRef: Ref.Ref<HashMap.HashMap<string, string>> =
Effect.runSync(Ref.make(HashMap.empty<string, string>()));
private readonly presentationState = new PresentationState();
private readonly agentConversationCacheRef: Ref.Ref<
HashMap.HashMap<string, ConversationId>
> = Effect.runSync(Ref.make(HashMap.empty<string, ConversationId>()));
private readonly lastNotifiedRef: Ref.Ref<
HashMap.HashMap<string, HashMap.HashMap<string, string>>
> = Effect.runSync(
Ref.make(HashMap.empty<string, HashMap.HashMap<string, string>>()),
);
private readonly lastReadRef: Ref.Ref<
HashMap.HashMap<string, HashMap.HashMap<string, ReadonlySet<string>>>
> = Effect.runSync(
Expand Down Expand Up @@ -274,6 +262,18 @@ export class MoltZapService {
agentKey: this.opts.agentKey,
// The body doesn't branch on close metadata today; the signature is
// kept explicit so a future disconnect-handler chain can plumb
// code/reason through.
onDisconnect: () => {
this.connectedValue = false;
fanout(this.handlers.disconnect, undefined);
},
});
this.client = client;

// `subscribeAll().pipe(Stream.runForEach, …)` is forked into a
// service-owned scope. The Stream is materialized BEFORE `connect()` so
// subscriptions are registered with the registry pre-handshake (a
// pre-connect-legal operation).
```

Stateful MoltZap client that manages connection, conversation tracking,
Expand All @@ -296,7 +296,7 @@ export interface RpcCallOptions {

Configures rpc call.

### [`ServiceRpcError`](./service.ts#L111)
### [`ServiceRpcError`](./service.ts#L114)

_TypeAlias_

Expand All @@ -314,4 +314,5 @@ to that method's errors at the `call` site.
## Files

- `harness-client.ts`
- `state.ts`
- `service.ts`
59 changes: 59 additions & 0 deletions packages/client/src/notification/trace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { NotificationDelivery } from "@moltzap/protocol/rpc";
import type { AnyNotificationDefinition } from "@moltzap/protocol/socket/catalog";

const isPlainRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);

const recordOrEmpty = (value: unknown): Record<string, unknown> =>
isPlainRecord(value) ? value : {};

const recordProperty = (
record: Record<string, unknown>,
key: string,
): Record<string, unknown> | undefined => {
const value = record[key];
return isPlainRecord(value) ? value : undefined;
};

const stringProperty = (
record: Record<string, unknown>,
key: string,
): string | undefined => {
const value = record[key];
return typeof value === "string" ? value : undefined;
};

const traceConversationId = (
conversation?: Record<string, unknown>,
fallback?: string,
): unknown =>
conversation === undefined ? fallback : (conversation.id ?? fallback);

/**
* Builds the stable diagnostic projection for one inbound notification.
* @param notification Notification delivered by the active socket client.
* @param agentId Agent receiving the notification when identity is available.
* @returns A JSON-safe trace record.
*/
export function notificationTraceRecord(
notification: NotificationDelivery<AnyNotificationDefinition>,
agentId?: string,
): Record<string, unknown> {
const params = recordOrEmpty(notification.params);
const message = recordProperty(params, "message");
const conversation = recordProperty(params, "conversation");
const notificationConversationId = stringProperty(params, "conversationId");
return {
ts: new Date().toISOString(),
agentId: agentId ?? "unknown",
notification: notification.method,
messageId: message?.id,
messageConversationId: message?.conversationId,
messageSenderId: message?.senderId,
conversationId: traceConversationId(
conversation,
notificationConversationId,
),
conversationName: conversation?.name,
};
}
7 changes: 7 additions & 0 deletions packages/client/src/presentation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @internal */
export {
PresentationState,
type ConversationMeta,
type CrossConversationEntry,
type CrossConvMessage,
} from "./state.js";
Loading
Loading