Skip to content

Commit 9e21239

Browse files
feat: add protocol TIER-2 schemas (FactoryDroidMessage, SessionSource, MissionModelSettings) (#53)
Continue the protocol source-of-truth migration with the clean TIER-2 schemas — those needing only a small bundle of already-migrated leaf enums. - messages.ts: add FactoryDroidMessageSchema + FactoryDroidMessageWithCaching (all content-block + enum deps landed in TIER-1) - session-source.ts: SessionSourceSchema + platform sources (needs SessionPlatform) - model-settings.ts: MissionModelSettingsSchema + SubagentModelSettingsSchema (symbol-only extraction; needs ReasoningEffort) mission-decomposition is deferred: MissionFeatureSchema transitively pulls CustomModelsSchema, which carries an apiKey credential field — not safe to mirror publicly without trimming. Shapes mirror the private monorepo exactly. Adds round-trip tests; src/protocol/ still not wired into the public API. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 60621c2 commit 9e21239

5 files changed

Lines changed: 271 additions & 6 deletions

File tree

src/protocol/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
// Barrel for the new protocol source-of-truth modules.
1+
// Barrel for the protocol source-of-truth modules.
22
// These files mirror the private factory-mono-alpha protocol exactly and are
33
// intentionally self-contained (zod-only). NOT yet wired into the public
4-
// package API (src/index.ts) — this is the first migration step.
4+
// package API (src/index.ts).
55

66
export * from './constants.js';
77
export * from './enums.js';
88
export * from './json-rpc.js';
99
export * from './host.js';
1010
export * from './session.js';
11+
export * from './session-source.js';
1112
export * from './messages.js';
13+
export * from './model-settings.js';
1214
export * from './loop.js';
1315
export * from './selectable-list-item.js';

src/protocol/messages.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
// Source-of-truth mirror of factory-mono-alpha message content-block schemas.
2-
// Faithful copy of the content-block stack from
2+
// Faithful copy of the message schemas from
33
// packages/common/src/sessionV2/messages/schemas.ts
4-
// EXCLUDING FactoryDroidMessageSchema / FactoryDroidMessageWithCachingSchema,
5-
// which are deferred to a later (TIER-2) migration step.
64

75
import { z } from 'zod';
86

97
import {
8+
ChatCompletionReasoningField,
109
DocumentSourceType,
1110
MessageContentBlockType,
11+
MessageRole,
12+
MessageVisibility,
1213
ModelProvider,
14+
OpenAIPhase,
15+
SessionOrigin,
1316
} from './enums.js';
1417

1518
// Base content block with optional id field
@@ -124,6 +127,44 @@ export const CacheLabelSchema = z.object({
124127
.optional(),
125128
});
126129

130+
// FactoryDroidMessage schema (used in JSON-RPC protocol and application)
131+
export const FactoryDroidMessageSchema = z.object({
132+
id: z.string(),
133+
role: z.nativeEnum(MessageRole),
134+
content: z.array(ContentBlockSchema),
135+
createdAt: z.number(),
136+
updatedAt: z.number(),
137+
parentId: z.string().optional(),
138+
userMessageSource: z.nativeEnum(SessionOrigin).optional(),
139+
visibility: z.nativeEnum(MessageVisibility).optional(),
140+
openaiMessageId: z.string().optional(),
141+
openaiPhase: z
142+
.enum([OpenAIPhase.Commentary, OpenAIPhase.FinalAnswer])
143+
.nullable()
144+
.optional(),
145+
openaiEncryptedContent: z.string().optional(),
146+
openaiReasoningId: z.string().optional(),
147+
openaiReasoningSummary: z.string().optional(),
148+
geminiThoughtSignature: z
149+
.string()
150+
.optional()
151+
.describe(
152+
'@deprecated Use thinking block `signature` fields with `signatureProvider: "google"` instead. Do not use in new code.'
153+
),
154+
chatCompletionReasoningField: z
155+
.nativeEnum(ChatCompletionReasoningField)
156+
.optional(),
157+
chatCompletionReasoningContent: z.string().optional(),
158+
isUserVisible: z.boolean().optional(), // deprecated
159+
isError: z.boolean().optional(),
160+
});
161+
162+
// FactoryDroidMessageWithCaching schema
163+
export const FactoryDroidMessageWithCachingSchema =
164+
FactoryDroidMessageSchema.extend({
165+
content: z.array(ContentBlockSchema.and(CacheLabelSchema)),
166+
});
167+
127168
export type TextBlock = z.infer<typeof TextBlockSchema>;
128169
export type Base64ImageSource = z.infer<typeof Base64ImageSourceSchema>;
129170
export type ImageBlock = z.infer<typeof ImageBlockSchema>;
@@ -137,3 +178,7 @@ export type DocumentBlock = z.infer<typeof DocumentBlockSchema>;
137178
export type ToolResult = z.infer<typeof ToolResultSchema>;
138179
export type ContentBlock = z.infer<typeof ContentBlockSchema>;
139180
export type CacheLabel = z.infer<typeof CacheLabelSchema>;
181+
export type FactoryDroidMessage = z.infer<typeof FactoryDroidMessageSchema>;
182+
export type FactoryDroidMessageWithCaching = z.infer<
183+
typeof FactoryDroidMessageWithCachingSchema
184+
>;

src/protocol/model-settings.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Source-of-truth mirror of factory-mono-alpha model-settings schemas (symbol-only).
2+
// Faithful copies of MissionModelSettingsSchema and SubagentModelSettingsSchema
3+
// from packages/common/src/settings/schema.ts. Only these leaf symbols are
4+
// mirrored here (not the full, TIER-3 settings file).
5+
6+
import { z } from 'zod';
7+
8+
import { ReasoningEffort } from './enums.js';
9+
10+
const ReasoningEffortSchema = z.nativeEnum(ReasoningEffort);
11+
12+
export const MissionModelSettingsSchema = z.object({
13+
workerModel: z.string().optional(),
14+
workerReasoningEffort: ReasoningEffortSchema.optional(),
15+
validationWorkerModel: z.string().optional(),
16+
validationWorkerReasoningEffort: ReasoningEffortSchema.optional(),
17+
skipScrutiny: z.boolean().optional(),
18+
skipUserTesting: z.boolean().optional(),
19+
});
20+
21+
export const SubagentModelSettingsSchema = z.object({
22+
lightModel: z.string().optional(),
23+
lightReasoningEffort: ReasoningEffortSchema.optional(),
24+
mediumModel: z.string().optional(),
25+
mediumReasoningEffort: ReasoningEffortSchema.optional(),
26+
heavyModel: z.string().optional(),
27+
heavyReasoningEffort: ReasoningEffortSchema.optional(),
28+
});
29+
30+
export type MissionModelSettings = z.infer<typeof MissionModelSettingsSchema>;
31+
export type SubagentModelSettings = z.infer<typeof SubagentModelSettingsSchema>;

src/protocol/session-source.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Source-of-truth mirror of factory-mono-alpha session source schemas.
2+
// Faithful copy of packages/common/src/session/sources/schema.ts.
3+
4+
import { z } from 'zod';
5+
6+
import { SessionPlatform } from './enums.js';
7+
8+
// -------------------------------------------------------------
9+
// Platform source schemas (used for messages and sessions)
10+
// -------------------------------------------------------------
11+
12+
const SlackSourceSchema = z.object({
13+
platform: z.literal(SessionPlatform.Slack),
14+
teamId: z.string().nullish(),
15+
channel: z.string().nullish(),
16+
threadTs: z.string().nullish(),
17+
userId: z.string().nullish(),
18+
});
19+
20+
const WebSourceSchema = z.object({
21+
platform: z.literal(SessionPlatform.Web),
22+
});
23+
24+
const ApiSourceSchema = z.object({
25+
platform: z.literal(SessionPlatform.Api),
26+
});
27+
28+
const SessionsApiSourceSchema = z.object({
29+
platform: z.literal(SessionPlatform.SessionsApi),
30+
});
31+
32+
const LinearSourceSchema = z.object({
33+
platform: z.literal(SessionPlatform.Linear),
34+
agentSessionId: z.string(),
35+
issueId: z.string().nullish(),
36+
issueUrl: z.string().nullish(),
37+
issueIdentifier: z.string().nullish(),
38+
organizationId: z.string().nullish(),
39+
userId: z.string().nullish(),
40+
});
41+
42+
const ReadinessRemediationSourceSchema = z.object({
43+
platform: z.literal(SessionPlatform.ReadinessRemediation),
44+
reportId: z.string(),
45+
repoUrl: z.string(),
46+
criterionId: z.string(),
47+
});
48+
49+
const ReadinessEvaluationSourceSchema = z.object({
50+
platform: z.literal(SessionPlatform.ReadinessEvaluation),
51+
repoUrl: z.string(),
52+
});
53+
54+
const AutomationSourceSchema = z.object({
55+
platform: z.literal(SessionPlatform.Automation),
56+
automationId: z.string(),
57+
computerId: z.string(),
58+
});
59+
60+
const WikiGenerationSourceSchema = z.object({
61+
platform: z.literal(SessionPlatform.WikiGeneration),
62+
repoUrl: z.string(),
63+
});
64+
65+
const WikiCISetupSourceSchema = z.object({
66+
platform: z.literal(SessionPlatform.WikiCISetup),
67+
repoUrl: z.string(),
68+
});
69+
70+
// -------------------------------------------------------------
71+
// Session-specific source schemas (adds delegationSessionId)
72+
// -------------------------------------------------------------
73+
74+
const SlackSessionSourceSchema = SlackSourceSchema.extend({
75+
delegationSessionId: z.string(),
76+
});
77+
78+
const WebSessionSourceSchema = WebSourceSchema.extend({
79+
delegationSessionId: z.string(),
80+
});
81+
82+
const ApiSessionSourceSchema = ApiSourceSchema.extend({
83+
delegationSessionId: z.string(),
84+
});
85+
86+
const SessionsApiSessionSourceSchema = SessionsApiSourceSchema.extend({
87+
delegationSessionId: z.string(),
88+
});
89+
90+
const LinearSessionSourceSchema = LinearSourceSchema.extend({
91+
delegationSessionId: z.string(),
92+
});
93+
94+
export const SessionSourceSchema = z.discriminatedUnion('platform', [
95+
SlackSessionSourceSchema,
96+
WebSessionSourceSchema,
97+
ApiSessionSourceSchema,
98+
SessionsApiSessionSourceSchema,
99+
LinearSessionSourceSchema,
100+
ReadinessRemediationSourceSchema, // No delegationSessionId needed
101+
ReadinessEvaluationSourceSchema, // No delegationSessionId needed
102+
AutomationSourceSchema, // No delegationSessionId needed
103+
WikiGenerationSourceSchema, // No delegationSessionId needed
104+
WikiCISetupSourceSchema, // No delegationSessionId needed
105+
]);
106+
107+
// -------------------------------------------------------------
108+
// Inferred types (single source of truth)
109+
// -------------------------------------------------------------
110+
111+
export type SlackSource = z.infer<typeof SlackSourceSchema>;
112+
export type WebSource = z.infer<typeof WebSourceSchema>;
113+
export type ApiSource = z.infer<typeof ApiSourceSchema>;
114+
export type SessionsApiSource = z.infer<typeof SessionsApiSourceSchema>;
115+
export type LinearSource = z.infer<typeof LinearSourceSchema>;
116+
export type ReadinessRemediationSource = z.infer<
117+
typeof ReadinessRemediationSourceSchema
118+
>;
119+
export type ReadinessEvaluationSource = z.infer<
120+
typeof ReadinessEvaluationSourceSchema
121+
>;
122+
export type AutomationSource = z.infer<typeof AutomationSourceSchema>;
123+
export type WikiGenerationSource = z.infer<typeof WikiGenerationSourceSchema>;
124+
export type WikiCISetupSource = z.infer<typeof WikiCISetupSourceSchema>;
125+
export type PlatformSource =
126+
| SlackSource
127+
| WebSource
128+
| ApiSource
129+
| SessionsApiSource
130+
| LinearSource
131+
| ReadinessRemediationSource
132+
| ReadinessEvaluationSource
133+
| AutomationSource
134+
| WikiGenerationSource
135+
| WikiCISetupSource;
136+
export type SessionSource = z.infer<typeof SessionSourceSchema>;

tests/protocol-dir.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import {
1010
JsonRpcErrorCode,
1111
JsonRpcMessageType,
1212
MessageContentBlockType,
13+
MessageRole,
14+
ReasoningEffort,
15+
SessionPlatform,
1316
} from '../src/protocol/enums.js';
1417
import { HostIdSchema } from '../src/protocol/host.js';
1518
import {
@@ -18,7 +21,13 @@ import {
1821
JsonRpcMessageSchema,
1922
} from '../src/protocol/json-rpc.js';
2023
import { LoopStateSchema } from '../src/protocol/loop.js';
21-
import { ContentBlockSchema, ToolUseSchema } from '../src/protocol/messages.js';
24+
import {
25+
ContentBlockSchema,
26+
FactoryDroidMessageSchema,
27+
ToolUseSchema,
28+
} from '../src/protocol/messages.js';
29+
import { MissionModelSettingsSchema } from '../src/protocol/model-settings.js';
30+
import { SessionSourceSchema } from '../src/protocol/session-source.js';
2231
import { SessionTagSchema, TokenUsageSchema } from '../src/protocol/session.js';
2332

2433
const envelope = {
@@ -143,3 +152,45 @@ describe('protocol/host + session + loop', () => {
143152
).toThrow();
144153
});
145154
});
155+
156+
describe('protocol TIER-2', () => {
157+
it('parses a FactoryDroidMessage with content blocks', () => {
158+
const message = FactoryDroidMessageSchema.parse({
159+
id: 'msg-1',
160+
role: MessageRole.Assistant,
161+
content: [{ type: MessageContentBlockType.Text, text: 'hello' }],
162+
createdAt: 1,
163+
updatedAt: 2,
164+
});
165+
expect(message.role).toBe(MessageRole.Assistant);
166+
expect(message.content).toHaveLength(1);
167+
});
168+
169+
it('rejects a FactoryDroidMessage missing required fields', () => {
170+
expect(() =>
171+
FactoryDroidMessageSchema.parse({ id: 'msg-1', role: MessageRole.User })
172+
).toThrow();
173+
});
174+
175+
it('parses a discriminated SessionSource', () => {
176+
const source = SessionSourceSchema.parse({
177+
platform: SessionPlatform.Web,
178+
delegationSessionId: 'deleg-1',
179+
});
180+
expect(source.platform).toBe(SessionPlatform.Web);
181+
});
182+
183+
it('rejects a SessionSource with an unknown platform', () => {
184+
expect(() => SessionSourceSchema.parse({ platform: 'nope' })).toThrow();
185+
});
186+
187+
it('parses MissionModelSettings (all fields optional)', () => {
188+
expect(MissionModelSettingsSchema.parse({})).toEqual({});
189+
const settings = MissionModelSettingsSchema.parse({
190+
workerModel: 'gpt-x',
191+
workerReasoningEffort: ReasoningEffort.High,
192+
skipScrutiny: true,
193+
});
194+
expect(settings.workerReasoningEffort).toBe(ReasoningEffort.High);
195+
});
196+
});

0 commit comments

Comments
 (0)