@overmux/pi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +118 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +40 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config-2jNv4tol.d.ts +34 -0
  6. package/dist/config-2jNv4tol.d.ts.map +1 -0
  7. package/dist/config.d.ts +2 -0
  8. package/dist/config.js +47 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/extension.d.ts +19 -0
  11. package/dist/extension.d.ts.map +1 -0
  12. package/dist/extension.js +265 -0
  13. package/dist/extension.js.map +1 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +16 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/jsonl-tail.d.ts +13 -0
  19. package/dist/jsonl-tail.d.ts.map +1 -0
  20. package/dist/jsonl-tail.js +83 -0
  21. package/dist/jsonl-tail.js.map +1 -0
  22. package/dist/live-events-ClhFOMGW.js +497 -0
  23. package/dist/live-events-ClhFOMGW.js.map +1 -0
  24. package/dist/live-events-DAmf6RRx.d.ts +107 -0
  25. package/dist/live-events-DAmf6RRx.d.ts.map +1 -0
  26. package/dist/live-events.d.ts +2 -0
  27. package/dist/live-events.js +2 -0
  28. package/dist/notification-DzOd9cRc.d.ts +20 -0
  29. package/dist/notification-DzOd9cRc.d.ts.map +1 -0
  30. package/dist/notification.d.ts +2 -0
  31. package/dist/notification.js +54 -0
  32. package/dist/notification.js.map +1 -0
  33. package/dist/plugin.d.ts +90 -0
  34. package/dist/plugin.d.ts.map +1 -0
  35. package/dist/plugin.js +213 -0
  36. package/dist/plugin.js.map +1 -0
  37. package/dist/projection.d.ts +101 -0
  38. package/dist/projection.d.ts.map +1 -0
  39. package/dist/projection.js +550 -0
  40. package/dist/projection.js.map +1 -0
  41. package/dist/protocol-CsrnSPOv.d.ts +115 -0
  42. package/dist/protocol-CsrnSPOv.d.ts.map +1 -0
  43. package/dist/protocol.d.ts +2 -0
  44. package/dist/protocol.js +365 -0
  45. package/dist/protocol.js.map +1 -0
  46. package/dist/react.d.ts +139 -0
  47. package/dist/react.d.ts.map +1 -0
  48. package/dist/react.js +796 -0
  49. package/dist/react.js.map +1 -0
  50. package/dist/server.d.ts +5 -0
  51. package/dist/server.js +4 -0
  52. package/dist/session-status-CZLTo8Km.d.ts +71 -0
  53. package/dist/session-status-CZLTo8Km.d.ts.map +1 -0
  54. package/dist/styles.css +619 -0
  55. package/docs/index.md +10 -0
  56. package/package.json +115 -0
  57. package/src/cli.ts +59 -0
  58. package/src/config.ts +87 -0
  59. package/src/extension.ts +486 -0
  60. package/src/index.ts +14 -0
  61. package/src/jsonl-tail.ts +110 -0
  62. package/src/live-events.ts +578 -0
  63. package/src/notification.ts +109 -0
  64. package/src/plugin.ts +369 -0
  65. package/src/projection.ts +995 -0
  66. package/src/protocol.ts +805 -0
  67. package/src/react.tsx +1293 -0
  68. package/src/server.ts +15 -0
  69. package/src/session-status.ts +379 -0
  70. package/src/styles.css +619 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport {\n defineOperation,\n type HandlerContext,\n type StreamHandlerDefinition,\n} from \"overmux\";\nimport {\n defineResourceContract,\n defineStreamContract,\n noInputSchema,\n} from \"overmux\";\nimport { z } from \"zod\";\n\nimport {\n sendAbort,\n sendSetModel,\n sendSetThinkingLevel,\n sendUserMessage,\n type AbortResponse,\n type UserMessageResponse,\n} from \"./protocol.js\";\nimport {\n createPiAgentConversationService,\n piConversationSnapshotSchema,\n piSessionMetadataSchema,\n type PiAgentSession,\n type PiConversationSnapshot,\n} from \"./projection.js\";\n\ntype Awaitable<T> = T | Promise<T>;\ntype Dispose = () => void;\nexport type PiSessionSource = {\n list: () => Awaitable<readonly PiAgentSession[]>;\n subscribe: (\n invalidate: () => void,\n options: { signal: AbortSignal },\n ) => Dispose | void;\n};\ntype PiAgentListener = () => void;\n\nexport type PiAgents = {\n get: (agentId: string) => Promise<PiAgentSession | undefined>;\n list: () => Promise<readonly PiAgentSession[]>;\n subscribe: (listener: PiAgentListener) => Dispose;\n};\n\nconst piAgentSessionSchema = z\n .object({\n id: z.string().min(1),\n liveEventsDir: z.string().min(1).optional(),\n sessionFile: z.string().min(1),\n sessionMetadata: piSessionMetadataSchema.optional(),\n })\n .strict();\n\nconst piSessionSchema = z.object({ agentId: z.string().min(1) }).strict();\nconst piMessageInputSchema = z\n .object({\n agentId: z.string().min(1),\n deliverAs: z.enum([\"steer\", \"followUp\"]),\n message: z.string().trim().min(1),\n })\n .strict();\nconst piMessageResultSchema = z.object({\n delivery: z.enum([\"immediate\", \"steer\", \"followUp\"]),\n requestId: z.string().min(1),\n});\nconst piStopInputSchema = z.object({ agentId: z.string().min(1) }).strict();\nconst piStopResultSchema = z.object({ requestId: z.string().min(1) });\nconst piModelInputSchema = z\n .object({\n agentId: z.string().min(1),\n provider: z.string().min(1),\n id: z.string().min(1),\n })\n .strict();\nconst piThinkingInputSchema = z\n .object({\n agentId: z.string().min(1),\n level: z.enum([\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]),\n })\n .strict();\nconst piControlResultSchema = z.object({ requestId: z.string().min(1) });\n\nconst once = (dispose: Dispose): Dispose => {\n let disposed = false;\n return () => {\n if (disposed) {\n return;\n }\n disposed = true;\n dispose();\n };\n};\n\nconst sameSessions = (\n left: readonly PiAgentSession[],\n right: readonly PiAgentSession[],\n): boolean =>\n left.length === right.length &&\n left.every(\n (session, index) =>\n session.id === right[index]?.id &&\n session.sessionFile === right[index]?.sessionFile &&\n session.liveEventsDir === right[index]?.liveEventsDir &&\n JSON.stringify(session.sessionMetadata) ===\n JSON.stringify(right[index]?.sessionMetadata),\n );\n\nexport const definePiAgents = ({\n liveEventsDir,\n sessions,\n}: {\n liveEventsDir: string;\n sessions: PiSessionSource;\n}): PiAgents => {\n const listeners = new Set<PiAgentListener>();\n let cached: PiAgentSession[] = [];\n let sourceDispose: Dispose | undefined;\n let refreshQueue = Promise.resolve();\n\n const refresh = async () => {\n const result = refreshQueue.then(async () => {\n const next = (await sessions.list())\n .map((session) =>\n piAgentSessionSchema.parse({ ...session, liveEventsDir }),\n )\n .sort((left, right) => left.id.localeCompare(right.id));\n if (new Set(next.map(({ id }) => id)).size !== next.length) {\n throw new Error(\"Pi agent discovery returned duplicate IDs\");\n }\n if (!sameSessions(cached, next)) {\n cached = next;\n listeners.forEach((listener) => listener());\n }\n });\n refreshQueue = result.catch(() => undefined);\n await result;\n return cached;\n };\n\n const stopSource = () => {\n sourceDispose?.();\n sourceDispose = undefined;\n };\n\n const startSource = () => {\n if (sourceDispose) {\n return;\n }\n const controller = new AbortController();\n const dispose = sessions.subscribe(\n () => void refresh().catch(() => undefined),\n { signal: controller.signal },\n );\n sourceDispose = once(() => {\n controller.abort();\n dispose?.();\n });\n };\n\n return {\n get: async (agentId) => (await refresh()).find(({ id }) => id === agentId),\n list: refresh,\n subscribe: (listener) => {\n listeners.add(listener);\n startSource();\n return () => {\n listeners.delete(listener);\n if (!listeners.size) {\n stopSource();\n }\n };\n },\n };\n};\n\nconst piSessionsContract = defineResourceContract({\n input: noInputSchema,\n output: z.array(piSessionSchema),\n});\n\nexport const piSessionsResource = ({ agents }: { agents: PiAgents }) => ({\n contract: piSessionsContract,\n kind: \"subscription\" as const,\n read: async (_input: void, _context: HandlerContext) =>\n (await agents.list()).map(({ id }) => ({ agentId: id })),\n subscribe: (\n _input: void,\n invalidate: () => void,\n context: HandlerContext,\n ) => {\n const dispose = once(agents.subscribe(invalidate));\n context.signal.addEventListener(\"abort\", dispose, { once: true });\n return once(() => {\n context.signal.removeEventListener(\"abort\", dispose);\n dispose();\n });\n },\n});\n\nconst piConversationContract = defineStreamContract({\n clientMessage: z.never(),\n input: z.object({ agentId: z.string().min(1) }).strict(),\n serverMessage: piConversationSnapshotSchema,\n});\n\nconst limitConversationEntries = (\n snapshot: PiConversationSnapshot,\n maxEntries: number | undefined,\n): PiConversationSnapshot =>\n maxEntries === undefined\n ? snapshot\n : { ...snapshot, entries: snapshot.entries.slice(-maxEntries) };\n\nexport const piConversationStream = ({\n agents,\n maxEntries,\n}: {\n agents: PiAgents;\n maxEntries?: number;\n}): StreamHandlerDefinition<\n typeof piConversationContract.input,\n typeof piConversationContract.clientMessage,\n typeof piConversationContract.serverMessage\n> => {\n if (\n maxEntries !== undefined &&\n (!Number.isSafeInteger(maxEntries) || maxEntries < 1)\n ) {\n throw new Error(\"Pi conversation maxEntries must be a positive integer\");\n }\n return {\n contract: piConversationContract,\n open: async ({ agentId }, context) => {\n const service = createPiAgentConversationService({\n resolveAgent: async (id) => {\n const agent = await agents.get(id);\n if (!agent) {\n throw new Error(`Pi agent not found: ${id}`);\n }\n return { ...agent, sessionId: agent.id };\n },\n });\n try {\n context.emit(\n limitConversationEntries(\n await service.getSnapshot(agentId),\n maxEntries,\n ),\n );\n const unsubscribe = service.subscribe(agentId, (snapshot) =>\n context.emit(limitConversationEntries(snapshot, maxEntries)),\n );\n return {\n dispose: once(() => {\n unsubscribe();\n service.dispose();\n }),\n };\n } catch (error) {\n service.dispose();\n throw error;\n }\n },\n };\n};\n\nconst piMessageResult = async (\n agent: PiAgentSession | undefined,\n input: z.infer<typeof piMessageInputSchema>,\n): Promise<z.infer<typeof piMessageResultSchema>> => {\n if (!agent) {\n throw new Error(`Pi agent not found: ${input.agentId}`);\n }\n const response = await sendUserMessage(agent.id, {\n deliverAs: input.deliverAs,\n message: input.message,\n requestId: randomUUID(),\n });\n if (!response?.ok) {\n throw new Error(\"Pi agent is unavailable\");\n }\n return response;\n};\n\nconst piStopResult = async (\n agent: PiAgentSession | undefined,\n input: z.infer<typeof piStopInputSchema>,\n): Promise<z.infer<typeof piStopResultSchema>> => {\n if (!agent) {\n throw new Error(`Pi agent not found: ${input.agentId}`);\n }\n const requestId = randomUUID();\n const response = await sendAbort(agent.id, { requestId });\n if (!response?.ok) {\n throw new Error(\"Pi agent is unavailable\");\n }\n return { requestId };\n};\n\nconst piControlResult = async (\n agent: PiAgentSession | undefined,\n input: { agentId: string },\n send: (\n agentId: string,\n requestId: string,\n ) => Promise<{ ok: boolean; error?: string } | undefined>,\n): Promise<z.infer<typeof piControlResultSchema>> => {\n if (!agent) {\n throw new Error(`Pi agent not found: ${input.agentId}`);\n }\n const requestId = randomUUID();\n const response = await send(agent.id, requestId);\n if (response?.ok) {\n return { requestId };\n }\n if (response?.error === \"not_found\") {\n throw new Error(\"Pi model was not found\");\n }\n if (response?.error === \"no_key\") {\n throw new Error(\"No API key is available for this Pi model\");\n }\n throw new Error(\"Pi agent is unavailable\");\n};\n\nexport const piOperationHandlers = ({ agents }: { agents: PiAgents }) => ({\n sendPiMessage: defineOperation({\n handle: async (input) =>\n piMessageResult(await agents.get(input.agentId), input),\n input: piMessageInputSchema,\n output: piMessageResultSchema,\n }),\n stopPiAgent: defineOperation({\n handle: async (input) =>\n piStopResult(await agents.get(input.agentId), input),\n input: piStopInputSchema,\n output: piStopResultSchema,\n }),\n setPiModel: defineOperation({\n handle: async (input) =>\n piControlResult(\n await agents.get(input.agentId),\n input,\n (agentId, requestId) =>\n sendSetModel(agentId, {\n id: input.id,\n provider: input.provider,\n requestId,\n }),\n ),\n input: piModelInputSchema,\n output: piControlResultSchema,\n }),\n setPiThinkingLevel: defineOperation({\n handle: async (input) =>\n piControlResult(\n await agents.get(input.agentId),\n input,\n (agentId, requestId) =>\n sendSetThinkingLevel(agentId, { level: input.level, requestId }),\n ),\n input: piThinkingInputSchema,\n output: piControlResultSchema,\n }),\n});\n\nexport type { AbortResponse, UserMessageResponse };\n"],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ import { S as UserMessageResponse, b as UserMessageInput, c as ProbeOptions } from "./protocol-CsrnSPOv.js";
2
+ import { z } from "zod";
3
+ //#region src/projection.d.ts
4
+ type PiContentBlock = {
5
+ arguments?: unknown;
6
+ data?: string;
7
+ id?: string;
8
+ mimeType?: string;
9
+ name?: string;
10
+ text?: string;
11
+ thinking?: string;
12
+ tool?: PiToolProjection;
13
+ type: string;
14
+ };
15
+ type PiToolResultProjection = {
16
+ content: PiContentBlock[];
17
+ details?: unknown;
18
+ isError: boolean;
19
+ toolCallId: string;
20
+ toolName: string;
21
+ };
22
+ type PiToolProjection = {
23
+ result?: PiToolResultProjection;
24
+ status: "error" | "pending" | "success";
25
+ };
26
+ type PiConversationEntry = {
27
+ content: PiContentBlock[];
28
+ details?: unknown;
29
+ errorMessage?: string;
30
+ id: string;
31
+ isError?: boolean;
32
+ role: "assistant" | "toolResult" | "user";
33
+ source: "canonical" | "live";
34
+ status: "complete" | "error" | "pending";
35
+ stopReason?: string;
36
+ timestamp?: number;
37
+ toolCallId?: string;
38
+ toolName?: string;
39
+ };
40
+ declare const piContentBlockSchema: z.ZodType<PiContentBlock>;
41
+ declare const piToolResultProjectionSchema: z.ZodType<PiToolResultProjection>;
42
+ declare const piToolProjectionSchema: z.ZodType<PiToolProjection>;
43
+ declare const piSessionMetadataSchema: z.ZodObject<{
44
+ contextUsage: z.ZodOptional<z.ZodObject<{
45
+ tokens: z.ZodNumber;
46
+ contextWindow: z.ZodNumber;
47
+ percent: z.ZodNumber;
48
+ }, z.core.$strict>>;
49
+ model: z.ZodOptional<z.ZodObject<{
50
+ provider: z.ZodString;
51
+ id: z.ZodString;
52
+ name: z.ZodString;
53
+ }, z.core.$strict>>;
54
+ thinkingLevel: z.ZodString;
55
+ modelOptions: z.ZodOptional<z.ZodArray<z.ZodObject<{
56
+ provider: z.ZodString;
57
+ id: z.ZodString;
58
+ name: z.ZodString;
59
+ }, z.core.$strict>>>;
60
+ }, z.core.$strict>;
61
+ type PiSessionMetadata = z.infer<typeof piSessionMetadataSchema>;
62
+ type PiConversationSnapshot = {
63
+ agentAvailable: boolean;
64
+ entries: PiConversationEntry[];
65
+ sessionMetadata?: PiSessionMetadata;
66
+ status: "busy" | "degraded" | "idle" | "offline";
67
+ };
68
+ declare const piConversationSnapshotSchema: z.ZodType<PiConversationSnapshot>;
69
+ type PiAgentSession = {
70
+ id: string;
71
+ liveEventsDir?: string;
72
+ sessionFile: string;
73
+ sessionMetadata?: PiSessionMetadata;
74
+ };
75
+ type PiAgentResource = PiAgentSession & {
76
+ sessionId: string;
77
+ };
78
+ type SnapshotListener = (snapshot: PiConversationSnapshot) => void;
79
+ type PiAgentConversationService = {
80
+ dispose: () => void;
81
+ getSnapshot: (agentId: string) => Promise<PiConversationSnapshot>;
82
+ subscribe: (agentId: string, listener: SnapshotListener) => () => void;
83
+ };
84
+ type PiMessageResponse = Extract<UserMessageResponse, {
85
+ ok: true;
86
+ }>;
87
+ type PiMessageSender = (sessionId: string, request: UserMessageInput) => Promise<UserMessageResponse | undefined>;
88
+ declare const probePiAgent: (sessionId: string, options?: ProbeOptions) => Promise<boolean>;
89
+ declare const createPiAgentConversationService: ({ resolveAgent, pollIntervalMs, probe }: {
90
+ resolveAgent: (agentId: string) => Promise<PiAgentResource>;
91
+ pollIntervalMs?: number;
92
+ probe?: (sessionId: string) => Promise<boolean>;
93
+ }) => PiAgentConversationService;
94
+ declare const sendPiAgentMessage: ({ deliverAs, message, sessionId }: {
95
+ deliverAs: "followUp" | "steer";
96
+ message: string;
97
+ sessionId: string;
98
+ }, send?: PiMessageSender) => Promise<PiMessageResponse>;
99
+ //#endregion
100
+ export { PiAgentConversationService, PiAgentSession, PiContentBlock, PiConversationEntry, PiConversationSnapshot, PiMessageSender, PiSessionMetadata, PiToolProjection, PiToolResultProjection, createPiAgentConversationService, piContentBlockSchema, piConversationSnapshotSchema, piSessionMetadataSchema, piToolProjectionSchema, piToolResultProjectionSchema, probePiAgent, sendPiAgentMessage };
101
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","names":[],"sources":["../src/projection.ts"],"mappings":";;;KAiBY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;;KAEU;EACV,SAAS;EACT;EACA;EACA;EACA;;KAEU;EACV,SAAS;EACT;;KAEU;EACV,SAAS;EACT;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;cAGW,sBAAsB,EAAE,QAAQ;cAchC,8BAA8B,EAAE,QAAQ;cASxC,wBAAwB,EAAE,QAAQ;cAMlC,yBAAuB,EAAA;;;;;;;;;;;;;;;;;GAgCzB,EAAA,KAAA;KAEC,oBAAoB,EAAE,aAAa;KACnC;EACV;EACA,SAAS;EACT,kBAAkB;EAClB;;cAGW,8BAA8B,EAAE,QAAQ;KAuBzC;EACV;EACA;EACA;EACA,kBAAkB;;KAGf,kBAAkB;EAAmB;;KA+CrC,oBAAoB,UAAU;KAEvB;EACV;EACA,cAAc,oBAAoB,QAAQ;EAC1C,YAAY,iBAAiB,UAAU;;KAGpC,oBAAoB,QAAQ;EAAuB;;KAE5C,mBACV,mBACA,SAAS,qBACN,QAAQ;cA6nBA,eAAY,mBAAA,UAAZ,iBAAY;cAEZ,qCAAoC,cAAA,gBAAA;EAK/C,eAAe,oBAAoB,QAAQ;EAC3C;EACA,SAAS,sBAAsB;MAC7B;cA+GS,uBACX,WAAA,SAAA;EAKE;EACA;EACA;GAEF,OAAM,oBACL,QAAQ"}
@@ -0,0 +1,550 @@
1
+ import { probePiSession, sendUserMessage } from "./protocol.js";
2
+ import { o as parseLiveEventRecord } from "./live-events-ClhFOMGW.js";
3
+ import { createJsonlTail } from "./jsonl-tail.js";
4
+ import { randomUUID } from "node:crypto";
5
+ import { readdir } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import { z } from "zod";
8
+ //#region src/projection.ts
9
+ const piContentBlockSchema = z.lazy(() => z.object({
10
+ arguments: z.unknown().optional(),
11
+ data: z.string().optional(),
12
+ id: z.string().optional(),
13
+ mimeType: z.string().optional(),
14
+ name: z.string().optional(),
15
+ text: z.string().optional(),
16
+ thinking: z.string().optional(),
17
+ tool: piToolProjectionSchema.optional(),
18
+ type: z.string()
19
+ }));
20
+ const piToolResultProjectionSchema = z.object({
21
+ content: z.array(piContentBlockSchema),
22
+ details: z.unknown().optional(),
23
+ isError: z.boolean(),
24
+ toolCallId: z.string(),
25
+ toolName: z.string()
26
+ });
27
+ const piToolProjectionSchema = z.lazy(() => z.object({
28
+ result: piToolResultProjectionSchema.optional(),
29
+ status: z.enum([
30
+ "error",
31
+ "pending",
32
+ "success"
33
+ ])
34
+ }));
35
+ const piSessionMetadataSchema = z.object({
36
+ contextUsage: z.object({
37
+ tokens: z.number().nonnegative(),
38
+ contextWindow: z.number().positive(),
39
+ percent: z.number().nonnegative()
40
+ }).strict().optional(),
41
+ model: z.object({
42
+ provider: z.string().min(1),
43
+ id: z.string().min(1),
44
+ name: z.string().min(1)
45
+ }).strict().optional(),
46
+ thinkingLevel: z.string().min(1),
47
+ modelOptions: z.array(z.object({
48
+ provider: z.string().min(1),
49
+ id: z.string().min(1),
50
+ name: z.string().min(1)
51
+ }).strict()).max(256).optional()
52
+ }).strict();
53
+ const piConversationSnapshotSchema = z.object({
54
+ agentAvailable: z.boolean(),
55
+ entries: z.array(z.object({
56
+ content: z.array(piContentBlockSchema),
57
+ details: z.unknown().optional(),
58
+ errorMessage: z.string().optional(),
59
+ id: z.string(),
60
+ isError: z.boolean().optional(),
61
+ role: z.enum([
62
+ "assistant",
63
+ "toolResult",
64
+ "user"
65
+ ]),
66
+ source: z.enum(["canonical", "live"]),
67
+ status: z.enum([
68
+ "complete",
69
+ "error",
70
+ "pending"
71
+ ]),
72
+ stopReason: z.string().optional(),
73
+ timestamp: z.number().optional(),
74
+ toolCallId: z.string().optional(),
75
+ toolName: z.string().optional()
76
+ })),
77
+ sessionMetadata: piSessionMetadataSchema.optional(),
78
+ status: z.enum([
79
+ "busy",
80
+ "degraded",
81
+ "idle",
82
+ "offline"
83
+ ])
84
+ });
85
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
86
+ const timestampOf = (value) => {
87
+ if (typeof value === "number" && Number.isFinite(value)) return value;
88
+ if (typeof value !== "string") return;
89
+ const timestamp = Date.parse(value);
90
+ return Number.isNaN(timestamp) ? void 0 : timestamp;
91
+ };
92
+ const contentBlocksOf = (content) => {
93
+ if (typeof content === "string") return [{
94
+ text: content,
95
+ type: "text"
96
+ }];
97
+ if (!Array.isArray(content)) return [];
98
+ return content.flatMap((value) => {
99
+ if (!isObject(value) || typeof value.type !== "string") return [];
100
+ return [{
101
+ ...value,
102
+ type: value.type
103
+ }];
104
+ });
105
+ };
106
+ const browserToolDetails = (details) => {
107
+ if (!isObject(details)) return details;
108
+ const { fullOutputPath: _fullOutputPath, ...safeDetails } = details;
109
+ return safeDetails;
110
+ };
111
+ const toolResultOf = (message) => {
112
+ if (message.role !== "toolResult" || typeof message.toolCallId !== "string" || typeof message.toolName !== "string") return;
113
+ return {
114
+ content: contentBlocksOf(message.content),
115
+ details: browserToolDetails(message.details),
116
+ isError: message.isError === true,
117
+ toolCallId: message.toolCallId,
118
+ toolName: message.toolName
119
+ };
120
+ };
121
+ const entryOf = (value, source) => {
122
+ if (!isObject(value) || value.type !== "message" || !isObject(value.message)) return;
123
+ if (typeof value.id !== "string") return;
124
+ const message = value.message;
125
+ if (message.role !== "assistant" && message.role !== "toolResult" && message.role !== "user") return;
126
+ return {
127
+ content: contentBlocksOf(message.content),
128
+ details: message.details,
129
+ errorMessage: message.errorMessage,
130
+ id: value.id,
131
+ isError: message.isError,
132
+ role: message.role,
133
+ source,
134
+ status: message.isError === true || message.stopReason === "error" ? "error" : "complete",
135
+ stopReason: message.stopReason,
136
+ timestamp: timestampOf(value.timestamp) ?? timestampOf(message.timestamp),
137
+ toolCallId: message.toolCallId,
138
+ toolName: message.toolName
139
+ };
140
+ };
141
+ const appendDelta = (blocks, event) => {
142
+ const update = event.assistantMessageEvent;
143
+ if (!isObject(update) || !Number.isSafeInteger(update.contentIndex)) return;
144
+ const index = update.contentIndex;
145
+ if (index < 0) return;
146
+ const current = blocks[index] ?? { type: "text" };
147
+ if (update.type === "text_start") blocks[index] = { type: "text" };
148
+ if (update.type === "thinking_start") blocks[index] = { type: "thinking" };
149
+ if (update.type === "toolcall_start") blocks[index] = { type: "toolCall" };
150
+ if (update.type === "text_delta") blocks[index] = {
151
+ ...current,
152
+ text: `${current.text ?? ""}${typeof update.delta === "string" ? update.delta : ""}`,
153
+ type: "text"
154
+ };
155
+ if (update.type === "thinking_delta") blocks[index] = {
156
+ ...current,
157
+ thinking: `${current.thinking ?? ""}${typeof update.delta === "string" ? update.delta : ""}`,
158
+ type: "thinking"
159
+ };
160
+ if (update.type === "toolcall_delta") blocks[index] = {
161
+ ...current,
162
+ arguments: `${typeof current.arguments === "string" ? current.arguments : ""}${typeof update.delta === "string" ? update.delta : ""}`,
163
+ type: "toolCall"
164
+ };
165
+ };
166
+ const updateMessage = (messages, record, sourceOrder) => {
167
+ const event = record.event;
168
+ if (typeof event.messageId !== "string") return;
169
+ const key = `${record.streamId}\0${event.messageId}`;
170
+ const current = messages.get(key) ?? {
171
+ blocks: [],
172
+ ended: false,
173
+ id: key,
174
+ messageSequence: typeof event.messageSequence === "number" ? event.messageSequence : sourceOrder,
175
+ sourceOrder,
176
+ status: "pending",
177
+ streamId: record.streamId
178
+ };
179
+ current.timestamp = record.timestamp;
180
+ if (event.type === "message_start" && isObject(event.message)) current.message = event.message;
181
+ if (event.type === "message_update") {
182
+ appendDelta(current.blocks, event);
183
+ const update = event.assistantMessageEvent;
184
+ if (isObject(update) && update.type === "done") current.status = "complete";
185
+ if (isObject(update) && update.type === "error") {
186
+ current.status = "error";
187
+ current.message = {
188
+ ...current.message,
189
+ errorMessage: typeof update.errorMessage === "string" ? update.errorMessage : void 0
190
+ };
191
+ }
192
+ }
193
+ if (event.type === "message_end" && isObject(event.message)) {
194
+ current.ended = true;
195
+ current.message = event.message;
196
+ current.blocks = contentBlocksOf(current.message.content);
197
+ current.status = current.message.isError === true || current.message.stopReason === "error" ? "error" : "complete";
198
+ }
199
+ messages.set(key, current);
200
+ };
201
+ const updateTool = (tools, event) => {
202
+ if (event.type !== "tool_execution_start" && event.type !== "tool_execution_end" || typeof event.toolCallId !== "string" || typeof event.toolName !== "string") return;
203
+ tools.set(event.toolCallId, {
204
+ isError: event.type === "tool_execution_end" && event.isError === true,
205
+ result: event.type === "tool_execution_end" ? event.result : void 0,
206
+ status: event.type === "tool_execution_start" ? "pending" : event.isError === true ? "error" : "success",
207
+ toolCallId: event.toolCallId,
208
+ toolName: event.toolName
209
+ });
210
+ };
211
+ const liveEntryOf = (state) => {
212
+ const message = state.message;
213
+ if (!message) return;
214
+ const role = message.role;
215
+ if (role !== "assistant" && role !== "toolResult" && role !== "user") return;
216
+ return {
217
+ content: state.ended ? contentBlocksOf(message.content) : state.blocks.length ? state.blocks : contentBlocksOf(message.content),
218
+ details: message.details,
219
+ errorMessage: message.errorMessage,
220
+ id: state.id,
221
+ isError: message.isError,
222
+ role,
223
+ source: "live",
224
+ status: state.status,
225
+ stopReason: message.stopReason,
226
+ timestamp: state.timestamp ?? timestampOf(message.timestamp),
227
+ toolCallId: message.toolCallId,
228
+ toolName: message.toolName
229
+ };
230
+ };
231
+ const projectLive = (records) => {
232
+ const messages = /* @__PURE__ */ new Map();
233
+ const tools = /* @__PURE__ */ new Map();
234
+ let active = false;
235
+ let shutdown = false;
236
+ records.forEach((record, index) => {
237
+ updateMessage(messages, record, index);
238
+ updateTool(tools, record.event);
239
+ if (record.event.type === "agent_start") active = true;
240
+ if (record.event.type === "agent_settled") active = false;
241
+ if (record.event.type === "session_shutdown") shutdown = true;
242
+ if (record.event.type === "session_start") shutdown = false;
243
+ });
244
+ return {
245
+ active,
246
+ entries: [...messages.values()].sort((left, right) => (left.timestamp ?? 0) - (right.timestamp ?? 0) || left.streamId.localeCompare(right.streamId) || left.messageSequence - right.messageSequence).flatMap((message) => {
247
+ const entry = liveEntryOf(message);
248
+ return entry ? [entry] : [];
249
+ }),
250
+ shutdown,
251
+ tools
252
+ };
253
+ };
254
+ const fingerprint = (entry) => JSON.stringify({
255
+ content: entry.content.map(({ tool: _tool, ...block }) => block),
256
+ details: entry.details,
257
+ isError: entry.isError,
258
+ role: entry.role,
259
+ toolCallId: entry.toolCallId,
260
+ toolName: entry.toolName
261
+ });
262
+ const matchedLiveIndexes = (canonical, live) => {
263
+ const liveByFingerprint = /* @__PURE__ */ new Map();
264
+ live.forEach((entry, index) => {
265
+ if (entry.status === "pending") return;
266
+ const key = fingerprint(entry);
267
+ const indexes = liveByFingerprint.get(key) ?? [];
268
+ indexes.push(index);
269
+ liveByFingerprint.set(key, indexes);
270
+ });
271
+ const consumed = /* @__PURE__ */ new Map();
272
+ const matched = /* @__PURE__ */ new Set();
273
+ canonical.forEach((entry) => {
274
+ const key = fingerprint(entry);
275
+ const offset = consumed.get(key) ?? 0;
276
+ const index = liveByFingerprint.get(key)?.[offset];
277
+ if (index !== void 0) {
278
+ matched.add(index);
279
+ consumed.set(key, offset + 1);
280
+ }
281
+ });
282
+ return matched;
283
+ };
284
+ const contentFromToolEnd = (result) => {
285
+ if (isObject(result) && "content" in result) return contentBlocksOf(result.content);
286
+ if (typeof result === "string") return [{
287
+ text: result,
288
+ type: "text"
289
+ }];
290
+ return result === void 0 ? [] : [{
291
+ text: JSON.stringify(result, null, 2),
292
+ type: "text"
293
+ }];
294
+ };
295
+ const pairTools = (entries, liveTools) => {
296
+ const callIds = new Set(entries.flatMap((entry) => entry.content.flatMap((block) => block.type === "toolCall" && typeof block.id === "string" ? [block.id] : [])));
297
+ const results = /* @__PURE__ */ new Map();
298
+ entries.forEach((entry) => {
299
+ if (entry.role !== "toolResult") return;
300
+ const result = toolResultOf({
301
+ content: entry.content,
302
+ details: entry.details,
303
+ isError: entry.isError,
304
+ role: entry.role,
305
+ toolCallId: entry.toolCallId,
306
+ toolName: entry.toolName
307
+ });
308
+ if (result) results.set(result.toolCallId, result);
309
+ });
310
+ liveTools.forEach((tool) => {
311
+ if (results.has(tool.toolCallId) || tool.result === void 0) return;
312
+ results.set(tool.toolCallId, {
313
+ content: contentFromToolEnd(tool.result),
314
+ details: isObject(tool.result) ? browserToolDetails(tool.result.details) : void 0,
315
+ isError: tool.isError === true,
316
+ toolCallId: tool.toolCallId,
317
+ toolName: tool.toolName
318
+ });
319
+ });
320
+ return entries.map((entry) => ({
321
+ ...entry,
322
+ content: entry.content.map((block) => {
323
+ if (block.type !== "toolCall" || typeof block.id !== "string") return block;
324
+ const result = results.get(block.id);
325
+ const live = liveTools.get(block.id);
326
+ return {
327
+ ...block,
328
+ tool: {
329
+ result,
330
+ status: result ? result.isError ? "error" : "success" : live?.status ?? "pending"
331
+ }
332
+ };
333
+ })
334
+ })).filter((entry) => entry.role !== "toolResult" || !entry.toolCallId || !callIds.has(entry.toolCallId));
335
+ };
336
+ const recordsSafeToProject = (stream) => {
337
+ const records = [...stream.records.values()].sort((left, right) => left.sequence - right.sequence);
338
+ const gapIndex = records.findIndex((record, index) => record.sequence !== index + 1);
339
+ if (gapIndex < 0) return records;
340
+ return [...records.slice(0, gapIndex), ...records.slice(gapIndex).filter((record) => record.event.type === "message_end")];
341
+ };
342
+ const sortedStreamRecords = (streams) => [...streams.values()].flatMap(recordsSafeToProject).sort((left, right) => left.timestamp - right.timestamp || left.streamId.localeCompare(right.streamId) || left.sequence - right.sequence);
343
+ const streamHasIssue = (stream) => {
344
+ if (stream.conflict) return true;
345
+ return [...stream.records.keys()].sort((left, right) => left - right).some((sequence, index) => sequence !== index + 1);
346
+ };
347
+ const refreshCanonical = async (state, sessionFile) => {
348
+ if (!sessionFile) {
349
+ const changed = state.canonicalRecords.length > 0;
350
+ state.canonicalRecords = [];
351
+ return changed;
352
+ }
353
+ const update = await state.canonicalTail.read(sessionFile);
354
+ if (update.reset) state.canonicalRecords = [];
355
+ state.canonicalRecords.push(...update.records);
356
+ return update.reset || update.records.length > 0;
357
+ };
358
+ const refreshStreams = async (state, data) => {
359
+ if (!data.liveEventsDir) {
360
+ const changed = state.streams.size > 0;
361
+ state.streams.clear();
362
+ return changed;
363
+ }
364
+ const directory = join(data.liveEventsDir, data.sessionId);
365
+ const files = (await readdir(directory).catch(() => [])).filter((file) => file.endsWith(".jsonl"));
366
+ const present = new Set(files);
367
+ let removed = false;
368
+ [...state.streams.keys()].forEach((file) => {
369
+ if (!present.has(file)) {
370
+ const stream = state.streams.get(file);
371
+ removed = removed || Boolean(stream?.conflict || stream?.records.size);
372
+ state.streams.delete(file);
373
+ }
374
+ });
375
+ const updates = await Promise.all(files.map(async (file) => {
376
+ const stream = state.streams.get(file) ?? {
377
+ conflict: false,
378
+ records: /* @__PURE__ */ new Map(),
379
+ tail: createJsonlTail()
380
+ };
381
+ state.streams.set(file, stream);
382
+ const update = await stream.tail.read(join(directory, file));
383
+ let changed = update.reset && Boolean(stream.conflict || stream.records.size);
384
+ if (update.reset) {
385
+ stream.conflict = false;
386
+ stream.records.clear();
387
+ }
388
+ const expectedStreamId = file.slice(0, -6);
389
+ update.records.forEach((value) => {
390
+ const record = parseLiveEventRecord(value, data.sessionId);
391
+ if (!record) return;
392
+ if (record.streamId !== expectedStreamId) {
393
+ changed = changed || !stream.conflict;
394
+ stream.conflict = true;
395
+ return;
396
+ }
397
+ const previous = stream.records.get(record.sequence);
398
+ if (previous && JSON.stringify(previous) !== JSON.stringify(record)) {
399
+ changed = changed || !stream.conflict;
400
+ stream.conflict = true;
401
+ return;
402
+ }
403
+ changed = changed || !previous;
404
+ stream.records.set(record.sequence, record);
405
+ });
406
+ return changed;
407
+ }));
408
+ return removed || updates.some(Boolean);
409
+ };
410
+ const activeCanonicalBranch = (records) => {
411
+ const treeEntries = records.flatMap((record) => {
412
+ if (!isObject(record) || typeof record.id !== "string" || record.parentId !== null && typeof record.parentId !== "string") return [];
413
+ return [record];
414
+ });
415
+ const leaf = treeEntries.at(-1);
416
+ if (!leaf) return records;
417
+ const byId = new Map(treeEntries.map((entry) => [entry.id, entry]));
418
+ const ancestry = /* @__PURE__ */ new Set();
419
+ for (let current = leaf; current;) {
420
+ if (ancestry.has(current.id)) return [];
421
+ ancestry.add(current.id);
422
+ current = typeof current.parentId === "string" ? byId.get(current.parentId) : void 0;
423
+ }
424
+ return records.filter((record) => isObject(record) && typeof record.id === "string" && ancestry.has(record.id));
425
+ };
426
+ const projectSnapshot = (state, agentAvailable) => {
427
+ const canonical = activeCanonicalBranch(state.canonicalRecords).flatMap((value) => {
428
+ const entry = entryOf(value, "canonical");
429
+ return entry ? [entry] : [];
430
+ });
431
+ const live = projectLive(sortedStreamRecords(state.streams));
432
+ const matched = matchedLiveIndexes(canonical, live.entries);
433
+ const entries = pairTools([...canonical, ...live.entries.filter((_entry, index) => !matched.has(index))].sort((left, right) => (left.timestamp ?? 0) - (right.timestamp ?? 0) || left.id.localeCompare(right.id)), live.tools);
434
+ const degraded = [...state.streams.values()].some(streamHasIssue);
435
+ return piConversationSnapshotSchema.parse({
436
+ entries,
437
+ agentAvailable,
438
+ ...state.sessionMetadata ? { sessionMetadata: state.sessionMetadata } : {},
439
+ status: degraded ? "degraded" : live.shutdown || !agentAvailable && !live.active ? "offline" : live.active || entries.some((entry) => entry.status === "pending") ? "busy" : "idle"
440
+ });
441
+ };
442
+ const configKeyOf = (data) => JSON.stringify([
443
+ data.sessionId,
444
+ data.sessionFile,
445
+ data.liveEventsDir
446
+ ]);
447
+ const createProjectionState = (data) => ({
448
+ canonicalRecords: [],
449
+ canonicalTail: createJsonlTail(),
450
+ configKey: configKeyOf(data),
451
+ refreshQueue: Promise.resolve(),
452
+ sessionMetadata: data.sessionMetadata,
453
+ streams: /* @__PURE__ */ new Map()
454
+ });
455
+ const queueProjectionRefresh = (state, operation) => {
456
+ const result = state.refreshQueue.then(operation);
457
+ state.refreshQueue = result.then(() => void 0, () => void 0);
458
+ return result;
459
+ };
460
+ const probePiAgent = probePiSession;
461
+ const createPiAgentConversationService = ({ resolveAgent, pollIntervalMs = 250, probe = probePiAgent }) => {
462
+ const projections = /* @__PURE__ */ new Map();
463
+ const subscriptions = /* @__PURE__ */ new Map();
464
+ let timer;
465
+ let polling = false;
466
+ const refresh = async (agentId) => {
467
+ const data = await resolveAgent(agentId);
468
+ const key = agentId;
469
+ const previous = projections.get(key);
470
+ const state = previous?.configKey === configKeyOf(data) ? previous : createProjectionState(data);
471
+ projections.set(key, state);
472
+ return queueProjectionRefresh(state, async () => {
473
+ const metadataChanged = JSON.stringify(state.sessionMetadata) !== JSON.stringify(data.sessionMetadata);
474
+ state.sessionMetadata = data.sessionMetadata;
475
+ const [canonicalChanged, streamsChanged, agentAvailable] = await Promise.all([
476
+ refreshCanonical(state, data.sessionFile),
477
+ refreshStreams(state, data),
478
+ probe(data.sessionId)
479
+ ]);
480
+ if (state.snapshot && !canonicalChanged && !streamsChanged && !metadataChanged && state.agentAvailable === agentAvailable) return state.snapshot;
481
+ const snapshot = projectSnapshot(state, agentAvailable);
482
+ state.agentAvailable = agentAvailable;
483
+ state.snapshot = snapshot;
484
+ return snapshot;
485
+ });
486
+ };
487
+ const poll = async () => {
488
+ if (polling) return;
489
+ polling = true;
490
+ try {
491
+ await Promise.all([...subscriptions.entries()].map(async ([key, listeners]) => {
492
+ const id = key;
493
+ try {
494
+ const before = projections.get(key)?.snapshot;
495
+ const snapshot = await refresh(id);
496
+ if (snapshot !== before) listeners.forEach((listener) => listener(snapshot));
497
+ } catch {
498
+ const state = projections.get(key);
499
+ if (!state || state.agentAvailable === false) return;
500
+ const snapshot = projectSnapshot(state, false);
501
+ state.agentAvailable = false;
502
+ state.snapshot = snapshot;
503
+ listeners.forEach((listener) => listener(snapshot));
504
+ }
505
+ }));
506
+ } finally {
507
+ polling = false;
508
+ }
509
+ };
510
+ const startPolling = () => {
511
+ if (!timer) timer = setInterval(() => void poll(), pollIntervalMs);
512
+ };
513
+ return {
514
+ dispose: () => {
515
+ if (timer) clearInterval(timer);
516
+ timer = void 0;
517
+ subscriptions.clear();
518
+ projections.clear();
519
+ },
520
+ getSnapshot: (agentId) => refresh(agentId),
521
+ subscribe: (agentId, listener) => {
522
+ const key = agentId;
523
+ const listeners = subscriptions.get(key) ?? /* @__PURE__ */ new Set();
524
+ listeners.add(listener);
525
+ subscriptions.set(key, listeners);
526
+ startPolling();
527
+ return () => {
528
+ listeners.delete(listener);
529
+ if (!listeners.size) subscriptions.delete(key);
530
+ if (!subscriptions.size && timer) {
531
+ clearInterval(timer);
532
+ timer = void 0;
533
+ }
534
+ };
535
+ }
536
+ };
537
+ };
538
+ const sendPiAgentMessage = async ({ deliverAs, message, sessionId }, send = sendUserMessage) => {
539
+ const response = await send(sessionId, {
540
+ deliverAs,
541
+ message,
542
+ requestId: randomUUID()
543
+ });
544
+ if (!response?.ok) throw new Error("Pi agent is unavailable");
545
+ return response;
546
+ };
547
+ //#endregion
548
+ export { createPiAgentConversationService, piContentBlockSchema, piConversationSnapshotSchema, piSessionMetadataSchema, piToolProjectionSchema, piToolResultProjectionSchema, probePiAgent, sendPiAgentMessage };
549
+
550
+ //# sourceMappingURL=projection.js.map