bootstrap new electron app

This commit is contained in:
Ramnique Singh 2025-12-29 15:30:57 +05:30
parent 2491bacea1
commit 505e3ea620
89 changed files with 12397 additions and 8435 deletions

View file

@ -0,0 +1,35 @@
import { z } from "zod";
export const BaseTool = z.object({
name: z.string(),
});
export const BuiltinTool = BaseTool.extend({
type: z.literal("builtin"),
});
export const McpTool = BaseTool.extend({
type: z.literal("mcp"),
description: z.string(),
inputSchema: z.any(),
mcpServerName: z.string(),
});
export const AgentAsATool = BaseTool.extend({
type: z.literal("agent"),
});
export const ToolAttachment = z.discriminatedUnion("type", [
BuiltinTool,
McpTool,
AgentAsATool,
]);
export const Agent = z.object({
name: z.string(),
provider: z.string().optional(),
model: z.string().optional(),
description: z.string().optional(),
instructions: z.string(),
tools: z.record(z.string(), ToolAttachment).optional(),
});

View file

@ -0,0 +1,12 @@
import z from "zod"
import { Agent } from "./agent.js"
import { McpServerDefinition } from "./mcp.js";
export const Example = z.object({
id: z.string(),
instructions: z.string().optional(),
description: z.string().optional(),
entryAgent: z.string().optional(),
agents: z.array(Agent).optional(),
mcpServers: z.record(z.string(), McpServerDefinition).optional(),
});

View file

@ -0,0 +1,6 @@
import { PrefixLogger } from './prefix-logger.js';
export * as ipc from './ipc.js';
export * as workspace from './workspace.js';
export * as mcp from './mcp.js';
export { PrefixLogger };

View file

@ -0,0 +1,212 @@
import { z } from 'zod';
import { RelPath, Encoding, Stat, DirEntry, ReaddirOptions, ReadFileResult, WorkspaceChangeEvent, WriteFileOptions, WriteFileResult, RemoveOptions } from './workspace.js';
import { ListToolsResponse } from './mcp.js';
import { AskHumanResponsePayload, CreateRunOptions, Run, ToolPermissionAuthorizePayload } from './runs.js';
// ============================================================================
// Runtime Validation Schemas (Single Source of Truth)
// ============================================================================
const ipcSchemas = {
'app:getVersions': {
req: z.null(),
res: z.object({
chrome: z.string(),
node: z.string(),
electron: z.string(),
}),
},
'workspace:getRoot': {
req: z.null(),
res: z.object({
root: z.string(),
}),
},
'workspace:exists': {
req: z.object({
path: RelPath,
}),
res: z.object({
exists: z.boolean(),
}),
},
'workspace:stat': {
req: z.object({
path: RelPath,
}),
res: Stat,
},
'workspace:readdir': {
req: z.object({
path: z.string(), // Empty string allowed for root directory
opts: ReaddirOptions.optional(),
}),
res: z.array(DirEntry),
},
'workspace:readFile': {
req: z.object({
path: RelPath,
encoding: Encoding.optional(),
}),
res: ReadFileResult,
},
'workspace:writeFile': {
req: z.object({
path: RelPath,
data: z.string(),
opts: WriteFileOptions.optional(),
}),
res: WriteFileResult,
},
'workspace:mkdir': {
req: z.object({
path: RelPath,
recursive: z.boolean().optional(),
}),
res: z.object({
ok: z.literal(true),
}),
},
'workspace:rename': {
req: z.object({
from: RelPath,
to: RelPath,
overwrite: z.boolean().optional(),
}),
res: z.object({
ok: z.literal(true),
}),
},
'workspace:copy': {
req: z.object({
from: RelPath,
to: RelPath,
overwrite: z.boolean().optional(),
}),
res: z.object({
ok: z.literal(true),
}),
},
'workspace:remove': {
req: z.object({
path: RelPath,
opts: RemoveOptions.optional(),
}),
res: z.object({
ok: z.literal(true),
}),
},
'workspace:didChange': {
req: WorkspaceChangeEvent,
res: z.null(),
},
'mcp:listTools': {
req: z.object({
serverName: z.string(),
cursor: z.string().optional(),
}),
res: ListToolsResponse,
},
'mcp:executeTool': {
req: z.object({
serverName: z.string(),
toolName: z.string(),
input: z.record(z.string(), z.unknown()),
}),
res: z.object({
result: z.unknown(),
}),
},
'runs:create': {
req: CreateRunOptions,
res: Run,
},
'runs:createMessage': {
req: z.object({
runId: z.string(),
message: z.string(),
}),
res: z.object({
messageId: z.string(),
}),
},
'runs:authorizePermission': {
req: z.object({
runId: z.string(),
authorization: ToolPermissionAuthorizePayload,
}),
res: z.object({
success: z.literal(true),
}),
},
'runs:provideHumanInput': {
req: z.object({
runId: z.string(),
reply: AskHumanResponsePayload,
}),
res: z.object({
success: z.literal(true),
}),
},
'runs:stop': {
req: z.object({
runId: z.string(),
}),
res: z.object({
success: z.literal(true),
}),
},
'runs:events': {
req: z.null(),
res: z.null(),
}
} as const;
// ============================================================================
// Type Helpers
// ============================================================================
export type IPCChannels = {
[K in keyof typeof ipcSchemas]: {
req: z.infer<typeof ipcSchemas[K]['req']>;
res: z.infer<typeof ipcSchemas[K]['res']>;
};
};
/**
* Channels that use invoke/handle (request/response pattern)
* These are channels with non-null responses
*/
export type InvokeChannels = {
[K in keyof IPCChannels]:
IPCChannels[K]['res'] extends null ? never : K
}[keyof IPCChannels];
/**
* Channels that use send/on (fire-and-forget pattern)
* These are channels with null responses (no response expected)
*/
export type SendChannels = {
[K in keyof IPCChannels]:
IPCChannels[K]['res'] extends null ? K : never
}[keyof IPCChannels];
// ============================================================================
// Type Guards
// ============================================================================
export function validateRequest<K extends keyof IPCChannels>(
channel: K,
data: unknown
): IPCChannels[K]['req'] {
const schema = ipcSchemas[channel].req;
return schema.parse(data) as IPCChannels[K]['req'];
}
export function validateResponse<K extends keyof IPCChannels>(
channel: K,
data: unknown
): IPCChannels[K]['res'] {
const schema = ipcSchemas[channel].res;
return schema.parse(data) as IPCChannels[K]['res'];
}

View file

@ -0,0 +1,63 @@
import { z } from "zod";
import { ProviderOptions } from "./message.js";
const BaseEvent = z.object({
providerOptions: ProviderOptions.optional(),
})
export const LlmStepStreamReasoningStartEvent = BaseEvent.extend({
type: z.literal("reasoning-start"),
});
export const LlmStepStreamReasoningDeltaEvent = BaseEvent.extend({
type: z.literal("reasoning-delta"),
delta: z.string(),
});
export const LlmStepStreamReasoningEndEvent = BaseEvent.extend({
type: z.literal("reasoning-end"),
});
export const LlmStepStreamTextStartEvent = BaseEvent.extend({
type: z.literal("text-start"),
});
export const LlmStepStreamTextDeltaEvent = BaseEvent.extend({
type: z.literal("text-delta"),
delta: z.string(),
});
export const LlmStepStreamTextEndEvent = BaseEvent.extend({
type: z.literal("text-end"),
});
export const LlmStepStreamToolCallEvent = BaseEvent.extend({
type: z.literal("tool-call"),
toolCallId: z.string(),
toolName: z.string(),
input: z.any(),
});
export const LlmStepStreamFinishStepEvent = z.object({
type: z.literal("finish-step"),
finishReason: z.enum(["stop", "tool-calls", "length", "content-filter", "error", "other", "unknown"]),
usage: z.object({
inputTokens: z.number().optional(),
outputTokens: z.number().optional(),
totalTokens: z.number().optional(),
reasoningTokens: z.number().optional(),
cachedInputTokens: z.number().optional(),
}),
providerOptions: ProviderOptions.optional(),
});
export const LlmStepStreamEvent = z.union([
LlmStepStreamReasoningStartEvent,
LlmStepStreamReasoningDeltaEvent,
LlmStepStreamReasoningEndEvent,
LlmStepStreamTextStartEvent,
LlmStepStreamTextDeltaEvent,
LlmStepStreamTextEndEvent,
LlmStepStreamToolCallEvent,
LlmStepStreamFinishStepEvent,
]);

View file

@ -0,0 +1,50 @@
import z from "zod";
export const StdioMcpServerConfig = z.object({
type: z.literal("stdio").optional(),
command: z.string(),
args: z.array(z.string()).optional(),
env: z.record(z.string(), z.string()).optional(),
});
export const HttpMcpServerConfig = z.object({
type: z.literal("http").optional(),
url: z.string(),
headers: z.record(z.string(), z.string()).optional(),
});
export const McpServerDefinition = z.union([StdioMcpServerConfig, HttpMcpServerConfig]);
export const McpServerConfig = z.object({
mcpServers: z.record(z.string(), McpServerDefinition),
});
export const connectionState = z.enum(["disconnected", "connected", "error"]);
export const McpServerList = z.object({
mcpServers: z.record(z.string(), z.object({
config: McpServerDefinition,
state: connectionState,
error: z.string().nullable(),
})),
});
export const Tool = z.object({
name: z.string(),
description: z.string().optional(),
inputSchema: z.object({
type: z.literal("object"),
properties: z.record(z.string(), z.any()).optional(),
required: z.array(z.string()).optional(),
}),
outputSchema: z.object({
type: z.literal("object"),
properties: z.record(z.string(), z.any()).optional(),
required: z.array(z.string()).optional(),
}).optional(),
});
export const ListToolsResponse = z.object({
tools: z.array(Tool),
nextCursor: z.string().optional(),
});

View file

@ -0,0 +1,67 @@
import { z } from "zod";
export const ProviderOptions = z.record(z.string(), z.record(z.string(), z.json()));
export const TextPart = z.object({
type: z.literal("text"),
text: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const ReasoningPart = z.object({
type: z.literal("reasoning"),
text: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const ToolCallPart = z.object({
type: z.literal("tool-call"),
toolCallId: z.string(),
toolName: z.string(),
arguments: z.any(),
providerOptions: ProviderOptions.optional(),
});
export const AssistantContentPart = z.union([
TextPart,
ReasoningPart,
ToolCallPart,
]);
export const UserMessage = z.object({
role: z.literal("user"),
content: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const AssistantMessage = z.object({
role: z.literal("assistant"),
content: z.union([
z.string(),
z.array(AssistantContentPart),
]),
providerOptions: ProviderOptions.optional(),
});
export const SystemMessage = z.object({
role: z.literal("system"),
content: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const ToolMessage = z.object({
role: z.literal("tool"),
content: z.string(),
toolCallId: z.string(),
toolName: z.string(),
providerOptions: ProviderOptions.optional(),
});
export const Message = z.discriminatedUnion("role", [
AssistantMessage,
SystemMessage,
ToolMessage,
UserMessage,
]);
export const MessageList = z.array(Message);

View file

@ -0,0 +1,26 @@
// create a PrefixLogger class that wraps console.log with a prefix
// and allows chaining with a parent logger
export class PrefixLogger {
private prefix: string;
private parent: PrefixLogger | null;
constructor(prefix: string, parent: PrefixLogger | null = null) {
this.prefix = prefix;
this.parent = parent;
}
log(...args: unknown[]) {
const timestamp = new Date().toISOString();
const prefix = '[' + this.prefix + ']';
if (this.parent) {
this.parent.log(prefix, ...args);
} else {
console.log(timestamp, prefix, ...args);
}
}
child(childPrefix: string): PrefixLogger {
return new PrefixLogger(childPrefix, this);
}
}

View file

@ -0,0 +1,129 @@
import { LlmStepStreamEvent } from "./llm-step-events.js";
import { Message, ToolCallPart } from "./message.js";
import z from "zod";
const BaseRunEvent = z.object({
runId: z.string(),
ts: z.iso.datetime().optional(),
subflow: z.array(z.string()),
});
export const RunProcessingStartEvent = BaseRunEvent.extend({
type: z.literal("run-processing-start"),
});
export const RunProcessingEndEvent = BaseRunEvent.extend({
type: z.literal("run-processing-end"),
});
export const StartEvent = BaseRunEvent.extend({
type: z.literal("start"),
agentName: z.string(),
});
export const SpawnSubFlowEvent = BaseRunEvent.extend({
type: z.literal("spawn-subflow"),
agentName: z.string(),
toolCallId: z.string(),
});
export const LlmStreamEvent = BaseRunEvent.extend({
type: z.literal("llm-stream-event"),
event: LlmStepStreamEvent,
});
export const MessageEvent = BaseRunEvent.extend({
type: z.literal("message"),
messageId: z.string(),
message: Message,
});
export const ToolInvocationEvent = BaseRunEvent.extend({
type: z.literal("tool-invocation"),
toolCallId: z.string().optional(),
toolName: z.string(),
input: z.string(),
});
export const ToolResultEvent = BaseRunEvent.extend({
type: z.literal("tool-result"),
toolCallId: z.string().optional(),
toolName: z.string(),
result: z.any(),
});
export const AskHumanRequestEvent = BaseRunEvent.extend({
type: z.literal("ask-human-request"),
toolCallId: z.string(),
query: z.string(),
});
export const AskHumanResponseEvent = BaseRunEvent.extend({
type: z.literal("ask-human-response"),
toolCallId: z.string(),
response: z.string(),
});
export const ToolPermissionRequestEvent = BaseRunEvent.extend({
type: z.literal("tool-permission-request"),
toolCall: ToolCallPart,
});
export const ToolPermissionResponseEvent = BaseRunEvent.extend({
type: z.literal("tool-permission-response"),
toolCallId: z.string(),
response: z.enum(["approve", "deny"]),
});
export const RunErrorEvent = BaseRunEvent.extend({
type: z.literal("error"),
error: z.string(),
});
export const RunEvent = z.union([
RunProcessingStartEvent,
RunProcessingEndEvent,
StartEvent,
SpawnSubFlowEvent,
LlmStreamEvent,
MessageEvent,
ToolInvocationEvent,
ToolResultEvent,
AskHumanRequestEvent,
AskHumanResponseEvent,
ToolPermissionRequestEvent,
ToolPermissionResponseEvent,
RunErrorEvent,
]);
export const ToolPermissionAuthorizePayload = ToolPermissionResponseEvent.pick({
subflow: true,
toolCallId: true,
response: true,
});
export const AskHumanResponsePayload = AskHumanResponseEvent.pick({
subflow: true,
toolCallId: true,
response: true,
});
export const Run = z.object({
id: z.string(),
createdAt: z.iso.datetime(),
agentId: z.string(),
log: z.array(RunEvent),
});
export const ListRunsResponse = z.object({
runs: z.array(Run.pick({
id: true,
createdAt: true,
agentId: true,
})),
nextCursor: z.string().optional(),
});
export const CreateRunOptions = Run.pick({
agentId: true,
});

View file

@ -0,0 +1,93 @@
import { z } from 'zod';
// ============================================================================
// Workspace Filesystem Schema Definitions
// ============================================================================
// All paths are workspace-relative POSIX strings
export const RelPath = z.string().min(1);
export const NodeKind = z.enum(['file', 'dir']);
export const Encoding = z.enum(['utf8', 'base64', 'binary']);
export const Stat = z.object({
kind: NodeKind,
size: z.number().min(0),
mtimeMs: z.number().min(0),
ctimeMs: z.number().min(0),
isSymlink: z.boolean().optional(),
});
export const DirEntry = z.object({
name: z.string(),
path: RelPath,
kind: NodeKind,
stat: z
.object({
size: z.number().min(0),
mtimeMs: z.number().min(0),
})
.optional(),
});
export const ReaddirOptions = z.object({
recursive: z.boolean().optional(),
includeStats: z.boolean().optional(),
includeHidden: z.boolean().optional(),
allowedExtensions: z.array(z.string()).optional(),
});
export const ReadFileResult = z.object({
path: RelPath,
encoding: Encoding,
data: z.string(),
stat: Stat,
etag: z.string(),
});
export const WriteFileOptions = z.object({
encoding: Encoding.optional(),
atomic: z.boolean().optional(),
mkdirp: z.boolean().optional(),
expectedEtag: z.string().optional(),
});
export const WriteFileResult = ReadFileResult.pick({
path: true,
stat: true,
etag: true,
});
export const RemoveOptions = z.object({
recursive: z.boolean().optional(),
trash: z.boolean().optional(),
});
export const WorkspaceChangeEvent = z.discriminatedUnion('type', [
z.object({
type: z.literal('created'),
path: RelPath,
kind: NodeKind.optional(),
}),
z.object({
type: z.literal('deleted'),
path: RelPath,
kind: NodeKind.optional(),
}),
z.object({
type: z.literal('changed'),
path: RelPath,
kind: NodeKind.optional(),
}),
z.object({
type: z.literal('moved'),
from: RelPath,
to: RelPath,
kind: NodeKind.optional(),
}),
z.object({
type: z.literal('bulkChanged'),
paths: z.array(RelPath).optional(),
}),
]);