@crewhaus/gateway-protocol 0.1.4 → 0.1.6
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.
- package/dist/index.d.ts +249 -0
- package/dist/index.js +177 -0
- package/package.json +9 -6
- package/src/index.test.ts +0 -259
- package/src/index.ts +0 -228
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog R16 `gateway-protocol` — wire contract for the managed-daemon
|
|
3
|
+
* gateway.
|
|
4
|
+
*
|
|
5
|
+
* Every request and response is wrapped in a versioned envelope so
|
|
6
|
+
* future protocol revisions can fan out on `protocol`. v1 carries:
|
|
7
|
+
*
|
|
8
|
+
* { protocol: "crewhaus.v1", id: <opaque>, method: <string>, params: <obj> }
|
|
9
|
+
*
|
|
10
|
+
* All schemas are exported as Zod runtime validators; the inferred
|
|
11
|
+
* types are the canonical TS API. Reference clients (TS, Python) ship
|
|
12
|
+
* alongside this package — see `clients/` for snippets external app
|
|
13
|
+
* servers can paste into their own build.
|
|
14
|
+
*
|
|
15
|
+
* Methods (v1):
|
|
16
|
+
* runs.create — start a new run, return runId
|
|
17
|
+
* runs.continue — append a user turn to an existing session
|
|
18
|
+
* runs.cancel — abort an in-flight run
|
|
19
|
+
* runs.subscribe — SSE stream of trace events for a runId
|
|
20
|
+
* sessions.list — list per-tenant sessions
|
|
21
|
+
* sessions.fork — branch a session at a specific event
|
|
22
|
+
* audit.tail — stream the per-tenant audit log
|
|
23
|
+
*
|
|
24
|
+
* Layer R16. Pairs with `gateway-server` (R16 — Bun.serve daemon) and
|
|
25
|
+
* `target-managed` (F2 — codegen).
|
|
26
|
+
*/
|
|
27
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
export declare const PROTOCOL_VERSION: "crewhaus.v1";
|
|
30
|
+
export declare class GatewayProtocolError extends CrewhausError {
|
|
31
|
+
readonly name = "GatewayProtocolError";
|
|
32
|
+
constructor(message: string, cause?: unknown);
|
|
33
|
+
}
|
|
34
|
+
export declare const ResponseEnvelope: z.ZodUnion<[z.ZodObject<{
|
|
35
|
+
protocol: z.ZodLiteral<"crewhaus.v1">;
|
|
36
|
+
id: z.ZodString;
|
|
37
|
+
result: z.ZodUnknown;
|
|
38
|
+
}, "strict", z.ZodTypeAny, {
|
|
39
|
+
protocol: "crewhaus.v1";
|
|
40
|
+
id: string;
|
|
41
|
+
result?: unknown;
|
|
42
|
+
}, {
|
|
43
|
+
protocol: "crewhaus.v1";
|
|
44
|
+
id: string;
|
|
45
|
+
result?: unknown;
|
|
46
|
+
}>, z.ZodObject<{
|
|
47
|
+
protocol: z.ZodLiteral<"crewhaus.v1">;
|
|
48
|
+
id: z.ZodString;
|
|
49
|
+
error: z.ZodObject<{
|
|
50
|
+
code: z.ZodString;
|
|
51
|
+
message: z.ZodString;
|
|
52
|
+
data: z.ZodOptional<z.ZodUnknown>;
|
|
53
|
+
}, "strict", z.ZodTypeAny, {
|
|
54
|
+
code: string;
|
|
55
|
+
message: string;
|
|
56
|
+
data?: unknown;
|
|
57
|
+
}, {
|
|
58
|
+
code: string;
|
|
59
|
+
message: string;
|
|
60
|
+
data?: unknown;
|
|
61
|
+
}>;
|
|
62
|
+
}, "strict", z.ZodTypeAny, {
|
|
63
|
+
protocol: "crewhaus.v1";
|
|
64
|
+
id: string;
|
|
65
|
+
error: {
|
|
66
|
+
code: string;
|
|
67
|
+
message: string;
|
|
68
|
+
data?: unknown;
|
|
69
|
+
};
|
|
70
|
+
}, {
|
|
71
|
+
protocol: "crewhaus.v1";
|
|
72
|
+
id: string;
|
|
73
|
+
error: {
|
|
74
|
+
code: string;
|
|
75
|
+
message: string;
|
|
76
|
+
data?: unknown;
|
|
77
|
+
};
|
|
78
|
+
}>]>;
|
|
79
|
+
export declare const RequestEnvelope: z.ZodObject<{
|
|
80
|
+
protocol: z.ZodLiteral<"crewhaus.v1">;
|
|
81
|
+
id: z.ZodString;
|
|
82
|
+
method: z.ZodString;
|
|
83
|
+
params: z.ZodUnknown;
|
|
84
|
+
}, "strict", z.ZodTypeAny, {
|
|
85
|
+
protocol: "crewhaus.v1";
|
|
86
|
+
id: string;
|
|
87
|
+
method: string;
|
|
88
|
+
params?: unknown;
|
|
89
|
+
}, {
|
|
90
|
+
protocol: "crewhaus.v1";
|
|
91
|
+
id: string;
|
|
92
|
+
method: string;
|
|
93
|
+
params?: unknown;
|
|
94
|
+
}>;
|
|
95
|
+
export type RequestEnvelopeT = z.infer<typeof RequestEnvelope>;
|
|
96
|
+
export type ResponseEnvelopeT = z.infer<typeof ResponseEnvelope>;
|
|
97
|
+
export declare const RunsCreateParams: z.ZodObject<{
|
|
98
|
+
spec: z.ZodString;
|
|
99
|
+
input: z.ZodString;
|
|
100
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
101
|
+
}, "strict", z.ZodTypeAny, {
|
|
102
|
+
spec: string;
|
|
103
|
+
input: string;
|
|
104
|
+
sessionId?: string | undefined;
|
|
105
|
+
}, {
|
|
106
|
+
spec: string;
|
|
107
|
+
input: string;
|
|
108
|
+
sessionId?: string | undefined;
|
|
109
|
+
}>;
|
|
110
|
+
export declare const RunsCreateResult: z.ZodObject<{
|
|
111
|
+
runId: z.ZodString;
|
|
112
|
+
sessionId: z.ZodString;
|
|
113
|
+
tenantId: z.ZodString;
|
|
114
|
+
}, "strict", z.ZodTypeAny, {
|
|
115
|
+
sessionId: string;
|
|
116
|
+
runId: string;
|
|
117
|
+
tenantId: string;
|
|
118
|
+
}, {
|
|
119
|
+
sessionId: string;
|
|
120
|
+
runId: string;
|
|
121
|
+
tenantId: string;
|
|
122
|
+
}>;
|
|
123
|
+
export type RunsCreateParamsT = z.infer<typeof RunsCreateParams>;
|
|
124
|
+
export type RunsCreateResultT = z.infer<typeof RunsCreateResult>;
|
|
125
|
+
export declare const RunsContinueParams: z.ZodObject<{
|
|
126
|
+
sessionId: z.ZodString;
|
|
127
|
+
input: z.ZodString;
|
|
128
|
+
}, "strict", z.ZodTypeAny, {
|
|
129
|
+
input: string;
|
|
130
|
+
sessionId: string;
|
|
131
|
+
}, {
|
|
132
|
+
input: string;
|
|
133
|
+
sessionId: string;
|
|
134
|
+
}>;
|
|
135
|
+
export declare const RunsContinueResult: z.ZodObject<{
|
|
136
|
+
runId: z.ZodString;
|
|
137
|
+
sessionId: z.ZodString;
|
|
138
|
+
tenantId: z.ZodString;
|
|
139
|
+
}, "strict", z.ZodTypeAny, {
|
|
140
|
+
sessionId: string;
|
|
141
|
+
runId: string;
|
|
142
|
+
tenantId: string;
|
|
143
|
+
}, {
|
|
144
|
+
sessionId: string;
|
|
145
|
+
runId: string;
|
|
146
|
+
tenantId: string;
|
|
147
|
+
}>;
|
|
148
|
+
export type RunsContinueParamsT = z.infer<typeof RunsContinueParams>;
|
|
149
|
+
export type RunsContinueResultT = z.infer<typeof RunsContinueResult>;
|
|
150
|
+
export declare const RunsCancelParams: z.ZodObject<{
|
|
151
|
+
runId: z.ZodString;
|
|
152
|
+
}, "strict", z.ZodTypeAny, {
|
|
153
|
+
runId: string;
|
|
154
|
+
}, {
|
|
155
|
+
runId: string;
|
|
156
|
+
}>;
|
|
157
|
+
export declare const RunsCancelResult: z.ZodObject<{
|
|
158
|
+
ok: z.ZodBoolean;
|
|
159
|
+
}, "strict", z.ZodTypeAny, {
|
|
160
|
+
ok: boolean;
|
|
161
|
+
}, {
|
|
162
|
+
ok: boolean;
|
|
163
|
+
}>;
|
|
164
|
+
export type RunsCancelParamsT = z.infer<typeof RunsCancelParams>;
|
|
165
|
+
export type RunsCancelResultT = z.infer<typeof RunsCancelResult>;
|
|
166
|
+
export declare const RunsSubscribeParams: z.ZodObject<{
|
|
167
|
+
runId: z.ZodString;
|
|
168
|
+
}, "strict", z.ZodTypeAny, {
|
|
169
|
+
runId: string;
|
|
170
|
+
}, {
|
|
171
|
+
runId: string;
|
|
172
|
+
}>;
|
|
173
|
+
export type RunsSubscribeParamsT = z.infer<typeof RunsSubscribeParams>;
|
|
174
|
+
export declare const SessionsListParams: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
175
|
+
export declare const SessionsListResult: z.ZodObject<{
|
|
176
|
+
sessions: z.ZodArray<z.ZodObject<{
|
|
177
|
+
id: z.ZodString;
|
|
178
|
+
tenantId: z.ZodString;
|
|
179
|
+
updatedAt: z.ZodString;
|
|
180
|
+
}, "strict", z.ZodTypeAny, {
|
|
181
|
+
id: string;
|
|
182
|
+
tenantId: string;
|
|
183
|
+
updatedAt: string;
|
|
184
|
+
}, {
|
|
185
|
+
id: string;
|
|
186
|
+
tenantId: string;
|
|
187
|
+
updatedAt: string;
|
|
188
|
+
}>, "many">;
|
|
189
|
+
}, "strict", z.ZodTypeAny, {
|
|
190
|
+
sessions: {
|
|
191
|
+
id: string;
|
|
192
|
+
tenantId: string;
|
|
193
|
+
updatedAt: string;
|
|
194
|
+
}[];
|
|
195
|
+
}, {
|
|
196
|
+
sessions: {
|
|
197
|
+
id: string;
|
|
198
|
+
tenantId: string;
|
|
199
|
+
updatedAt: string;
|
|
200
|
+
}[];
|
|
201
|
+
}>;
|
|
202
|
+
export type SessionsListParamsT = z.infer<typeof SessionsListParams>;
|
|
203
|
+
export type SessionsListResultT = z.infer<typeof SessionsListResult>;
|
|
204
|
+
export declare const SessionsForkParams: z.ZodObject<{
|
|
205
|
+
sessionId: z.ZodString;
|
|
206
|
+
atEventTs: z.ZodNumber;
|
|
207
|
+
}, "strict", z.ZodTypeAny, {
|
|
208
|
+
sessionId: string;
|
|
209
|
+
atEventTs: number;
|
|
210
|
+
}, {
|
|
211
|
+
sessionId: string;
|
|
212
|
+
atEventTs: number;
|
|
213
|
+
}>;
|
|
214
|
+
export declare const SessionsForkResult: z.ZodObject<{
|
|
215
|
+
newSessionId: z.ZodString;
|
|
216
|
+
}, "strict", z.ZodTypeAny, {
|
|
217
|
+
newSessionId: string;
|
|
218
|
+
}, {
|
|
219
|
+
newSessionId: string;
|
|
220
|
+
}>;
|
|
221
|
+
export type SessionsForkParamsT = z.infer<typeof SessionsForkParams>;
|
|
222
|
+
export type SessionsForkResultT = z.infer<typeof SessionsForkResult>;
|
|
223
|
+
export declare const AuditTailParams: z.ZodObject<{
|
|
224
|
+
tenantId: z.ZodString;
|
|
225
|
+
sinceTs: z.ZodOptional<z.ZodNumber>;
|
|
226
|
+
}, "strict", z.ZodTypeAny, {
|
|
227
|
+
tenantId: string;
|
|
228
|
+
sinceTs?: number | undefined;
|
|
229
|
+
}, {
|
|
230
|
+
tenantId: string;
|
|
231
|
+
sinceTs?: number | undefined;
|
|
232
|
+
}>;
|
|
233
|
+
export type AuditTailParamsT = z.infer<typeof AuditTailParams>;
|
|
234
|
+
export declare const Method: z.ZodEnum<["runs.create", "runs.continue", "runs.cancel", "runs.subscribe", "sessions.list", "sessions.fork", "audit.tail"]>;
|
|
235
|
+
export type MethodT = z.infer<typeof Method>;
|
|
236
|
+
export declare function decodeRequest(raw: unknown): RequestEnvelopeT & {
|
|
237
|
+
method: MethodT;
|
|
238
|
+
};
|
|
239
|
+
export declare function encodeSuccess(id: string, result: unknown): ResponseEnvelopeT;
|
|
240
|
+
export declare function encodeError(id: string, code: string, message: string, data?: unknown): ResponseEnvelopeT;
|
|
241
|
+
export declare const ErrorCode: {
|
|
242
|
+
readonly Unauthorized: "unauthorized";
|
|
243
|
+
readonly Forbidden: "forbidden";
|
|
244
|
+
readonly NotFound: "not_found";
|
|
245
|
+
readonly BadRequest: "bad_request";
|
|
246
|
+
readonly BudgetExceeded: "budget_exceeded";
|
|
247
|
+
readonly InternalError: "internal_error";
|
|
248
|
+
};
|
|
249
|
+
export type ErrorCodeT = (typeof ErrorCode)[keyof typeof ErrorCode];
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog R16 `gateway-protocol` — wire contract for the managed-daemon
|
|
3
|
+
* gateway.
|
|
4
|
+
*
|
|
5
|
+
* Every request and response is wrapped in a versioned envelope so
|
|
6
|
+
* future protocol revisions can fan out on `protocol`. v1 carries:
|
|
7
|
+
*
|
|
8
|
+
* { protocol: "crewhaus.v1", id: <opaque>, method: <string>, params: <obj> }
|
|
9
|
+
*
|
|
10
|
+
* All schemas are exported as Zod runtime validators; the inferred
|
|
11
|
+
* types are the canonical TS API. Reference clients (TS, Python) ship
|
|
12
|
+
* alongside this package — see `clients/` for snippets external app
|
|
13
|
+
* servers can paste into their own build.
|
|
14
|
+
*
|
|
15
|
+
* Methods (v1):
|
|
16
|
+
* runs.create — start a new run, return runId
|
|
17
|
+
* runs.continue — append a user turn to an existing session
|
|
18
|
+
* runs.cancel — abort an in-flight run
|
|
19
|
+
* runs.subscribe — SSE stream of trace events for a runId
|
|
20
|
+
* sessions.list — list per-tenant sessions
|
|
21
|
+
* sessions.fork — branch a session at a specific event
|
|
22
|
+
* audit.tail — stream the per-tenant audit log
|
|
23
|
+
*
|
|
24
|
+
* Layer R16. Pairs with `gateway-server` (R16 — Bun.serve daemon) and
|
|
25
|
+
* `target-managed` (F2 — codegen).
|
|
26
|
+
*/
|
|
27
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
export const PROTOCOL_VERSION = "crewhaus.v1";
|
|
30
|
+
export class GatewayProtocolError extends CrewhausError {
|
|
31
|
+
name = "GatewayProtocolError";
|
|
32
|
+
constructor(message, cause) {
|
|
33
|
+
super("config", message, cause);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const protocolField = z.literal(PROTOCOL_VERSION);
|
|
37
|
+
const requestEnvelope = z
|
|
38
|
+
.object({
|
|
39
|
+
protocol: protocolField,
|
|
40
|
+
id: z.string().min(1),
|
|
41
|
+
method: z.string().min(1),
|
|
42
|
+
params: z.unknown(),
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
const successEnvelope = z
|
|
46
|
+
.object({
|
|
47
|
+
protocol: protocolField,
|
|
48
|
+
id: z.string().min(1),
|
|
49
|
+
result: z.unknown(),
|
|
50
|
+
})
|
|
51
|
+
.strict();
|
|
52
|
+
const errorEnvelope = z
|
|
53
|
+
.object({
|
|
54
|
+
protocol: protocolField,
|
|
55
|
+
id: z.string().min(1),
|
|
56
|
+
error: z
|
|
57
|
+
.object({
|
|
58
|
+
code: z.string().min(1),
|
|
59
|
+
message: z.string().min(1),
|
|
60
|
+
data: z.unknown().optional(),
|
|
61
|
+
})
|
|
62
|
+
.strict(),
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
export const ResponseEnvelope = z.union([successEnvelope, errorEnvelope]);
|
|
66
|
+
export const RequestEnvelope = requestEnvelope;
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Method-specific schemas.
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
export const RunsCreateParams = z
|
|
71
|
+
.object({
|
|
72
|
+
spec: z.string().min(1),
|
|
73
|
+
input: z.string(),
|
|
74
|
+
sessionId: z.string().min(1).optional(),
|
|
75
|
+
})
|
|
76
|
+
.strict();
|
|
77
|
+
export const RunsCreateResult = z
|
|
78
|
+
.object({
|
|
79
|
+
runId: z.string().min(1),
|
|
80
|
+
sessionId: z.string().min(1),
|
|
81
|
+
tenantId: z.string().min(1),
|
|
82
|
+
})
|
|
83
|
+
.strict();
|
|
84
|
+
export const RunsContinueParams = z
|
|
85
|
+
.object({
|
|
86
|
+
sessionId: z.string().min(1),
|
|
87
|
+
input: z.string(),
|
|
88
|
+
})
|
|
89
|
+
.strict();
|
|
90
|
+
export const RunsContinueResult = z
|
|
91
|
+
.object({
|
|
92
|
+
runId: z.string().min(1),
|
|
93
|
+
sessionId: z.string().min(1),
|
|
94
|
+
tenantId: z.string().min(1),
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
export const RunsCancelParams = z.object({ runId: z.string().min(1) }).strict();
|
|
98
|
+
export const RunsCancelResult = z.object({ ok: z.boolean() }).strict();
|
|
99
|
+
export const RunsSubscribeParams = z.object({ runId: z.string().min(1) }).strict();
|
|
100
|
+
export const SessionsListParams = z.object({}).strict();
|
|
101
|
+
export const SessionsListResult = z
|
|
102
|
+
.object({
|
|
103
|
+
sessions: z.array(z
|
|
104
|
+
.object({
|
|
105
|
+
id: z.string().min(1),
|
|
106
|
+
tenantId: z.string().min(1),
|
|
107
|
+
updatedAt: z.string().min(1),
|
|
108
|
+
})
|
|
109
|
+
.strict()),
|
|
110
|
+
})
|
|
111
|
+
.strict();
|
|
112
|
+
export const SessionsForkParams = z
|
|
113
|
+
.object({ sessionId: z.string().min(1), atEventTs: z.number().int().nonnegative() })
|
|
114
|
+
.strict();
|
|
115
|
+
export const SessionsForkResult = z.object({ newSessionId: z.string().min(1) }).strict();
|
|
116
|
+
export const AuditTailParams = z
|
|
117
|
+
.object({ tenantId: z.string().min(1), sinceTs: z.number().int().nonnegative().optional() })
|
|
118
|
+
.strict();
|
|
119
|
+
export const Method = z.enum([
|
|
120
|
+
"runs.create",
|
|
121
|
+
"runs.continue",
|
|
122
|
+
"runs.cancel",
|
|
123
|
+
"runs.subscribe",
|
|
124
|
+
"sessions.list",
|
|
125
|
+
"sessions.fork",
|
|
126
|
+
"audit.tail",
|
|
127
|
+
]);
|
|
128
|
+
const PARAM_SCHEMAS = {
|
|
129
|
+
"runs.create": RunsCreateParams,
|
|
130
|
+
"runs.continue": RunsContinueParams,
|
|
131
|
+
"runs.cancel": RunsCancelParams,
|
|
132
|
+
"runs.subscribe": RunsSubscribeParams,
|
|
133
|
+
"sessions.list": SessionsListParams,
|
|
134
|
+
"sessions.fork": SessionsForkParams,
|
|
135
|
+
"audit.tail": AuditTailParams,
|
|
136
|
+
};
|
|
137
|
+
export function decodeRequest(raw) {
|
|
138
|
+
const parsed = RequestEnvelope.safeParse(raw);
|
|
139
|
+
if (!parsed.success) {
|
|
140
|
+
throw new GatewayProtocolError(`invalid envelope: ${parsed.error.issues
|
|
141
|
+
.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
142
|
+
.join("; ")}`);
|
|
143
|
+
}
|
|
144
|
+
const m = Method.safeParse(parsed.data.method);
|
|
145
|
+
if (!m.success) {
|
|
146
|
+
throw new GatewayProtocolError(`unknown method "${parsed.data.method}"`);
|
|
147
|
+
}
|
|
148
|
+
const paramSchema = PARAM_SCHEMAS[m.data];
|
|
149
|
+
const params = paramSchema.safeParse(parsed.data.params);
|
|
150
|
+
if (!params.success) {
|
|
151
|
+
throw new GatewayProtocolError(`invalid params for ${m.data}: ${params.error.issues
|
|
152
|
+
.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
153
|
+
.join("; ")}`);
|
|
154
|
+
}
|
|
155
|
+
return { ...parsed.data, method: m.data, params: params.data };
|
|
156
|
+
}
|
|
157
|
+
export function encodeSuccess(id, result) {
|
|
158
|
+
return { protocol: PROTOCOL_VERSION, id, result };
|
|
159
|
+
}
|
|
160
|
+
export function encodeError(id, code, message, data) {
|
|
161
|
+
return {
|
|
162
|
+
protocol: PROTOCOL_VERSION,
|
|
163
|
+
id,
|
|
164
|
+
error: { code, message, ...(data !== undefined ? { data } : {}) },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Standard error codes — wire-stable so reference clients can switch on them.
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
export const ErrorCode = {
|
|
171
|
+
Unauthorized: "unauthorized",
|
|
172
|
+
Forbidden: "forbidden",
|
|
173
|
+
NotFound: "not_found",
|
|
174
|
+
BadRequest: "bad_request",
|
|
175
|
+
BudgetExceeded: "budget_exceeded",
|
|
176
|
+
InternalError: "internal_error",
|
|
177
|
+
};
|
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/gateway-protocol",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JSON-RPC wire protocol for the managed-daemon gateway — versioned envelope + Zod schemas",
|
|
6
|
-
"main": "
|
|
7
|
-
"types": "
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
11
14
|
"scripts": {
|
|
12
15
|
"test": "bun test src"
|
|
13
16
|
},
|
|
14
17
|
"dependencies": {
|
|
15
|
-
"@crewhaus/errors": "0.1.
|
|
18
|
+
"@crewhaus/errors": "0.1.6",
|
|
16
19
|
"zod": "^3.23.8"
|
|
17
20
|
},
|
|
18
21
|
"license": "Apache-2.0",
|
|
@@ -33,5 +36,5 @@
|
|
|
33
36
|
"publishConfig": {
|
|
34
37
|
"access": "public"
|
|
35
38
|
},
|
|
36
|
-
"files": ["
|
|
39
|
+
"files": ["dist", "README.md", "LICENSE", "NOTICE"]
|
|
37
40
|
}
|
package/src/index.test.ts
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
ErrorCode,
|
|
4
|
-
GatewayProtocolError,
|
|
5
|
-
Method,
|
|
6
|
-
PROTOCOL_VERSION,
|
|
7
|
-
RequestEnvelope,
|
|
8
|
-
ResponseEnvelope,
|
|
9
|
-
decodeRequest,
|
|
10
|
-
encodeError,
|
|
11
|
-
encodeSuccess,
|
|
12
|
-
} from "./index";
|
|
13
|
-
|
|
14
|
-
describe("envelope", () => {
|
|
15
|
-
test("PROTOCOL_VERSION is the v1 string", () => {
|
|
16
|
-
expect(PROTOCOL_VERSION).toBe("crewhaus.v1");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("decodeRequest accepts a well-formed runs.create", () => {
|
|
20
|
-
const raw = {
|
|
21
|
-
protocol: "crewhaus.v1",
|
|
22
|
-
id: "abc",
|
|
23
|
-
method: "runs.create",
|
|
24
|
-
params: { spec: "x", input: "hi" },
|
|
25
|
-
};
|
|
26
|
-
const r = decodeRequest(raw);
|
|
27
|
-
expect(r.method).toBe("runs.create");
|
|
28
|
-
expect((r.params as { spec: string }).spec).toBe("x");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("rejects wrong protocol version", () => {
|
|
32
|
-
const raw = {
|
|
33
|
-
protocol: "crewhaus.v0",
|
|
34
|
-
id: "abc",
|
|
35
|
-
method: "runs.create",
|
|
36
|
-
params: { spec: "x", input: "" },
|
|
37
|
-
};
|
|
38
|
-
expect(() => decodeRequest(raw)).toThrow(GatewayProtocolError);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("rejects unknown method", () => {
|
|
42
|
-
const raw = {
|
|
43
|
-
protocol: "crewhaus.v1",
|
|
44
|
-
id: "abc",
|
|
45
|
-
method: "runs.nuke",
|
|
46
|
-
params: {},
|
|
47
|
-
};
|
|
48
|
-
expect(() => decodeRequest(raw)).toThrow(/unknown method "runs.nuke"/);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("rejects invalid params for known method", () => {
|
|
52
|
-
const raw = {
|
|
53
|
-
protocol: "crewhaus.v1",
|
|
54
|
-
id: "abc",
|
|
55
|
-
method: "runs.create",
|
|
56
|
-
params: { spec: 1 }, // wrong type
|
|
57
|
-
};
|
|
58
|
-
expect(() => decodeRequest(raw)).toThrow(/invalid params/);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("rejects extra unknown fields on the envelope (.strict)", () => {
|
|
62
|
-
const raw = {
|
|
63
|
-
protocol: "crewhaus.v1",
|
|
64
|
-
id: "abc",
|
|
65
|
-
method: "runs.create",
|
|
66
|
-
params: { spec: "x", input: "" },
|
|
67
|
-
foo: "bar",
|
|
68
|
-
};
|
|
69
|
-
expect(() => decodeRequest(raw)).toThrow(GatewayProtocolError);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe("response encoders", () => {
|
|
74
|
-
test("encodeSuccess shape", () => {
|
|
75
|
-
const r = encodeSuccess("id-1", { ok: true });
|
|
76
|
-
expect(r).toEqual({ protocol: "crewhaus.v1", id: "id-1", result: { ok: true } });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("encodeError shape with no data", () => {
|
|
80
|
-
const r = encodeError("id-1", ErrorCode.NotFound, "missing");
|
|
81
|
-
expect(r).toEqual({
|
|
82
|
-
protocol: "crewhaus.v1",
|
|
83
|
-
id: "id-1",
|
|
84
|
-
error: { code: "not_found", message: "missing" },
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("encodeError shape with data", () => {
|
|
89
|
-
const r = encodeError("id-1", ErrorCode.BadRequest, "bad", { field: "x" });
|
|
90
|
-
expect(r).toEqual({
|
|
91
|
-
protocol: "crewhaus.v1",
|
|
92
|
-
id: "id-1",
|
|
93
|
-
error: { code: "bad_request", message: "bad", data: { field: "x" } },
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe("standard error codes are wire-stable", () => {
|
|
99
|
-
test("expected codes exist", () => {
|
|
100
|
-
expect(ErrorCode.Unauthorized).toBe("unauthorized");
|
|
101
|
-
expect(ErrorCode.Forbidden).toBe("forbidden");
|
|
102
|
-
expect(ErrorCode.NotFound).toBe("not_found");
|
|
103
|
-
expect(ErrorCode.BadRequest).toBe("bad_request");
|
|
104
|
-
expect(ErrorCode.BudgetExceeded).toBe("budget_exceeded");
|
|
105
|
-
expect(ErrorCode.InternalError).toBe("internal_error");
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe("error-message path formatting", () => {
|
|
110
|
-
// The issue-formatter uses `i.path.join(".") || "<root>"`. A top-level
|
|
111
|
-
// failure (raw is not an object at all) yields an empty Zod path, which
|
|
112
|
-
// must surface as "<root>" rather than an empty string.
|
|
113
|
-
test("envelope failure on a non-object surfaces <root>", () => {
|
|
114
|
-
expect(() => decodeRequest("not-an-object")).toThrow(/<root>: /);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("param failure on a non-object surfaces <root>", () => {
|
|
118
|
-
const raw = {
|
|
119
|
-
protocol: "crewhaus.v1",
|
|
120
|
-
id: "abc",
|
|
121
|
-
method: "runs.cancel",
|
|
122
|
-
params: null,
|
|
123
|
-
};
|
|
124
|
-
expect(() => decodeRequest(raw)).toThrow(/invalid params for runs.cancel: <root>: /);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("named-field failure surfaces the field path, not <root>", () => {
|
|
128
|
-
const raw = {
|
|
129
|
-
protocol: "crewhaus.v1",
|
|
130
|
-
id: "abc",
|
|
131
|
-
method: "runs.create",
|
|
132
|
-
params: { spec: 1, input: "hi" },
|
|
133
|
-
};
|
|
134
|
-
// `spec` is the offending path; it must appear and <root> must not.
|
|
135
|
-
expect(() => decodeRequest(raw)).toThrow(/spec: /);
|
|
136
|
-
try {
|
|
137
|
-
decodeRequest(raw);
|
|
138
|
-
} catch (e) {
|
|
139
|
-
expect((e as Error).message).not.toContain("<root>");
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe("decodeRequest accepts every declared method", () => {
|
|
145
|
-
const cases: Array<[string, unknown]> = [
|
|
146
|
-
["runs.create", { spec: "s", input: "" }],
|
|
147
|
-
["runs.continue", { sessionId: "sess", input: "more" }],
|
|
148
|
-
["runs.cancel", { runId: "r1" }],
|
|
149
|
-
["runs.subscribe", { runId: "r1" }],
|
|
150
|
-
["sessions.list", {}],
|
|
151
|
-
["sessions.fork", { sessionId: "sess", atEventTs: 0 }],
|
|
152
|
-
["audit.tail", { tenantId: "t1" }],
|
|
153
|
-
];
|
|
154
|
-
for (const [method, params] of cases) {
|
|
155
|
-
test(`decodes ${method}`, () => {
|
|
156
|
-
const r = decodeRequest({ protocol: "crewhaus.v1", id: "id", method, params });
|
|
157
|
-
expect(r.method).toBe(method as (typeof r)["method"]);
|
|
158
|
-
expect(r.protocol).toBe("crewhaus.v1");
|
|
159
|
-
expect(r.params).toEqual(params);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
test("Method enum lists exactly the routed methods", () => {
|
|
164
|
-
expect([...Method.options].sort()).toEqual(
|
|
165
|
-
[...cases.map(([m]) => m as (typeof Method.options)[number])].sort(),
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe("envelope id/method minimums", () => {
|
|
171
|
-
test("rejects empty id", () => {
|
|
172
|
-
const raw = {
|
|
173
|
-
protocol: "crewhaus.v1",
|
|
174
|
-
id: "",
|
|
175
|
-
method: "runs.create",
|
|
176
|
-
params: { spec: "x", input: "" },
|
|
177
|
-
};
|
|
178
|
-
expect(() => decodeRequest(raw)).toThrow(GatewayProtocolError);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test("rejects empty method", () => {
|
|
182
|
-
const raw = { protocol: "crewhaus.v1", id: "abc", method: "", params: {} };
|
|
183
|
-
expect(() => decodeRequest(raw)).toThrow(GatewayProtocolError);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("optional sessionId is accepted on runs.create", () => {
|
|
187
|
-
const r = decodeRequest({
|
|
188
|
-
protocol: "crewhaus.v1",
|
|
189
|
-
id: "abc",
|
|
190
|
-
method: "runs.create",
|
|
191
|
-
params: { spec: "x", input: "hi", sessionId: "sess" },
|
|
192
|
-
});
|
|
193
|
-
expect((r.params as { sessionId?: string }).sessionId).toBe("sess");
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe("encoder ↔ schema round-trips", () => {
|
|
198
|
-
test("encodeSuccess output validates against ResponseEnvelope", () => {
|
|
199
|
-
expect(ResponseEnvelope.safeParse(encodeSuccess("i", { ok: true })).success).toBe(true);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test("encodeError output validates against ResponseEnvelope (no data)", () => {
|
|
203
|
-
expect(ResponseEnvelope.safeParse(encodeError("i", "not_found", "missing")).success).toBe(true);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("encodeError output validates against ResponseEnvelope (with data)", () => {
|
|
207
|
-
const parsed = ResponseEnvelope.safeParse(encodeError("i", "bad_request", "bad", { f: 1 }));
|
|
208
|
-
expect(parsed.success).toBe(true);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// Distinct from the no-arg call: passing `data: undefined` explicitly must
|
|
212
|
-
// still omit the key (the `data !== undefined` guard), keeping the wire
|
|
213
|
-
// form identical to the success/no-data shape.
|
|
214
|
-
test("encodeError with explicit undefined data omits the data key", () => {
|
|
215
|
-
const r = encodeError("id-1", ErrorCode.InternalError, "boom", undefined);
|
|
216
|
-
expect(r).toEqual({
|
|
217
|
-
protocol: "crewhaus.v1",
|
|
218
|
-
id: "id-1",
|
|
219
|
-
error: { code: "internal_error", message: "boom" },
|
|
220
|
-
});
|
|
221
|
-
expect("data" in (r as { error: Record<string, unknown> }).error).toBe(false);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("encodeError preserves a falsy-but-defined data value", () => {
|
|
225
|
-
const r = encodeError("id-1", ErrorCode.BadRequest, "bad", null);
|
|
226
|
-
expect((r as { error: { data?: unknown } }).error.data).toBeNull();
|
|
227
|
-
expect("data" in (r as { error: Record<string, unknown> }).error).toBe(true);
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
describe("RequestEnvelope is the exported request schema", () => {
|
|
232
|
-
test("validates a well-formed envelope shape directly", () => {
|
|
233
|
-
const parsed = RequestEnvelope.safeParse({
|
|
234
|
-
protocol: "crewhaus.v1",
|
|
235
|
-
id: "abc",
|
|
236
|
-
method: "anything.goes",
|
|
237
|
-
params: 42,
|
|
238
|
-
});
|
|
239
|
-
expect(parsed.success).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe("GatewayProtocolError", () => {
|
|
244
|
-
test("carries the config code and preserves its cause", () => {
|
|
245
|
-
const cause = new Error("underlying");
|
|
246
|
-
const err = new GatewayProtocolError("nope", cause);
|
|
247
|
-
expect(err).toBeInstanceOf(GatewayProtocolError);
|
|
248
|
-
expect(err.name).toBe("GatewayProtocolError");
|
|
249
|
-
expect(err.code).toBe("config");
|
|
250
|
-
expect(err.message).toBe("nope");
|
|
251
|
-
expect(err.cause).toBe(cause);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test("is constructible without a cause", () => {
|
|
255
|
-
const err = new GatewayProtocolError("solo");
|
|
256
|
-
expect(err.cause).toBeUndefined();
|
|
257
|
-
expect(err.code).toBe("config");
|
|
258
|
-
});
|
|
259
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Catalog R16 `gateway-protocol` — wire contract for the managed-daemon
|
|
3
|
-
* gateway.
|
|
4
|
-
*
|
|
5
|
-
* Every request and response is wrapped in a versioned envelope so
|
|
6
|
-
* future protocol revisions can fan out on `protocol`. v1 carries:
|
|
7
|
-
*
|
|
8
|
-
* { protocol: "crewhaus.v1", id: <opaque>, method: <string>, params: <obj> }
|
|
9
|
-
*
|
|
10
|
-
* All schemas are exported as Zod runtime validators; the inferred
|
|
11
|
-
* types are the canonical TS API. Reference clients (TS, Python) ship
|
|
12
|
-
* alongside this package — see `clients/` for snippets external app
|
|
13
|
-
* servers can paste into their own build.
|
|
14
|
-
*
|
|
15
|
-
* Methods (v1):
|
|
16
|
-
* runs.create — start a new run, return runId
|
|
17
|
-
* runs.continue — append a user turn to an existing session
|
|
18
|
-
* runs.cancel — abort an in-flight run
|
|
19
|
-
* runs.subscribe — SSE stream of trace events for a runId
|
|
20
|
-
* sessions.list — list per-tenant sessions
|
|
21
|
-
* sessions.fork — branch a session at a specific event
|
|
22
|
-
* audit.tail — stream the per-tenant audit log
|
|
23
|
-
*
|
|
24
|
-
* Layer R16. Pairs with `gateway-server` (R16 — Bun.serve daemon) and
|
|
25
|
-
* `target-managed` (F2 — codegen).
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { CrewhausError } from "@crewhaus/errors";
|
|
29
|
-
import { z } from "zod";
|
|
30
|
-
|
|
31
|
-
export const PROTOCOL_VERSION = "crewhaus.v1" as const;
|
|
32
|
-
|
|
33
|
-
export class GatewayProtocolError extends CrewhausError {
|
|
34
|
-
override readonly name = "GatewayProtocolError";
|
|
35
|
-
constructor(message: string, cause?: unknown) {
|
|
36
|
-
super("config", message, cause);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const protocolField = z.literal(PROTOCOL_VERSION);
|
|
41
|
-
|
|
42
|
-
const requestEnvelope = z
|
|
43
|
-
.object({
|
|
44
|
-
protocol: protocolField,
|
|
45
|
-
id: z.string().min(1),
|
|
46
|
-
method: z.string().min(1),
|
|
47
|
-
params: z.unknown(),
|
|
48
|
-
})
|
|
49
|
-
.strict();
|
|
50
|
-
|
|
51
|
-
const successEnvelope = z
|
|
52
|
-
.object({
|
|
53
|
-
protocol: protocolField,
|
|
54
|
-
id: z.string().min(1),
|
|
55
|
-
result: z.unknown(),
|
|
56
|
-
})
|
|
57
|
-
.strict();
|
|
58
|
-
|
|
59
|
-
const errorEnvelope = z
|
|
60
|
-
.object({
|
|
61
|
-
protocol: protocolField,
|
|
62
|
-
id: z.string().min(1),
|
|
63
|
-
error: z
|
|
64
|
-
.object({
|
|
65
|
-
code: z.string().min(1),
|
|
66
|
-
message: z.string().min(1),
|
|
67
|
-
data: z.unknown().optional(),
|
|
68
|
-
})
|
|
69
|
-
.strict(),
|
|
70
|
-
})
|
|
71
|
-
.strict();
|
|
72
|
-
|
|
73
|
-
export const ResponseEnvelope = z.union([successEnvelope, errorEnvelope]);
|
|
74
|
-
export const RequestEnvelope = requestEnvelope;
|
|
75
|
-
export type RequestEnvelopeT = z.infer<typeof RequestEnvelope>;
|
|
76
|
-
export type ResponseEnvelopeT = z.infer<typeof ResponseEnvelope>;
|
|
77
|
-
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// Method-specific schemas.
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
export const RunsCreateParams = z
|
|
83
|
-
.object({
|
|
84
|
-
spec: z.string().min(1),
|
|
85
|
-
input: z.string(),
|
|
86
|
-
sessionId: z.string().min(1).optional(),
|
|
87
|
-
})
|
|
88
|
-
.strict();
|
|
89
|
-
export const RunsCreateResult = z
|
|
90
|
-
.object({
|
|
91
|
-
runId: z.string().min(1),
|
|
92
|
-
sessionId: z.string().min(1),
|
|
93
|
-
tenantId: z.string().min(1),
|
|
94
|
-
})
|
|
95
|
-
.strict();
|
|
96
|
-
export type RunsCreateParamsT = z.infer<typeof RunsCreateParams>;
|
|
97
|
-
export type RunsCreateResultT = z.infer<typeof RunsCreateResult>;
|
|
98
|
-
|
|
99
|
-
export const RunsContinueParams = z
|
|
100
|
-
.object({
|
|
101
|
-
sessionId: z.string().min(1),
|
|
102
|
-
input: z.string(),
|
|
103
|
-
})
|
|
104
|
-
.strict();
|
|
105
|
-
export const RunsContinueResult = z
|
|
106
|
-
.object({
|
|
107
|
-
runId: z.string().min(1),
|
|
108
|
-
sessionId: z.string().min(1),
|
|
109
|
-
tenantId: z.string().min(1),
|
|
110
|
-
})
|
|
111
|
-
.strict();
|
|
112
|
-
export type RunsContinueParamsT = z.infer<typeof RunsContinueParams>;
|
|
113
|
-
export type RunsContinueResultT = z.infer<typeof RunsContinueResult>;
|
|
114
|
-
|
|
115
|
-
export const RunsCancelParams = z.object({ runId: z.string().min(1) }).strict();
|
|
116
|
-
export const RunsCancelResult = z.object({ ok: z.boolean() }).strict();
|
|
117
|
-
export type RunsCancelParamsT = z.infer<typeof RunsCancelParams>;
|
|
118
|
-
export type RunsCancelResultT = z.infer<typeof RunsCancelResult>;
|
|
119
|
-
|
|
120
|
-
export const RunsSubscribeParams = z.object({ runId: z.string().min(1) }).strict();
|
|
121
|
-
export type RunsSubscribeParamsT = z.infer<typeof RunsSubscribeParams>;
|
|
122
|
-
|
|
123
|
-
export const SessionsListParams = z.object({}).strict();
|
|
124
|
-
export const SessionsListResult = z
|
|
125
|
-
.object({
|
|
126
|
-
sessions: z.array(
|
|
127
|
-
z
|
|
128
|
-
.object({
|
|
129
|
-
id: z.string().min(1),
|
|
130
|
-
tenantId: z.string().min(1),
|
|
131
|
-
updatedAt: z.string().min(1),
|
|
132
|
-
})
|
|
133
|
-
.strict(),
|
|
134
|
-
),
|
|
135
|
-
})
|
|
136
|
-
.strict();
|
|
137
|
-
export type SessionsListParamsT = z.infer<typeof SessionsListParams>;
|
|
138
|
-
export type SessionsListResultT = z.infer<typeof SessionsListResult>;
|
|
139
|
-
|
|
140
|
-
export const SessionsForkParams = z
|
|
141
|
-
.object({ sessionId: z.string().min(1), atEventTs: z.number().int().nonnegative() })
|
|
142
|
-
.strict();
|
|
143
|
-
export const SessionsForkResult = z.object({ newSessionId: z.string().min(1) }).strict();
|
|
144
|
-
export type SessionsForkParamsT = z.infer<typeof SessionsForkParams>;
|
|
145
|
-
export type SessionsForkResultT = z.infer<typeof SessionsForkResult>;
|
|
146
|
-
|
|
147
|
-
export const AuditTailParams = z
|
|
148
|
-
.object({ tenantId: z.string().min(1), sinceTs: z.number().int().nonnegative().optional() })
|
|
149
|
-
.strict();
|
|
150
|
-
export type AuditTailParamsT = z.infer<typeof AuditTailParams>;
|
|
151
|
-
|
|
152
|
-
export const Method = z.enum([
|
|
153
|
-
"runs.create",
|
|
154
|
-
"runs.continue",
|
|
155
|
-
"runs.cancel",
|
|
156
|
-
"runs.subscribe",
|
|
157
|
-
"sessions.list",
|
|
158
|
-
"sessions.fork",
|
|
159
|
-
"audit.tail",
|
|
160
|
-
]);
|
|
161
|
-
export type MethodT = z.infer<typeof Method>;
|
|
162
|
-
|
|
163
|
-
const PARAM_SCHEMAS: Record<MethodT, z.ZodType<unknown>> = {
|
|
164
|
-
"runs.create": RunsCreateParams,
|
|
165
|
-
"runs.continue": RunsContinueParams,
|
|
166
|
-
"runs.cancel": RunsCancelParams,
|
|
167
|
-
"runs.subscribe": RunsSubscribeParams,
|
|
168
|
-
"sessions.list": SessionsListParams,
|
|
169
|
-
"sessions.fork": SessionsForkParams,
|
|
170
|
-
"audit.tail": AuditTailParams,
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
export function decodeRequest(raw: unknown): RequestEnvelopeT & { method: MethodT } {
|
|
174
|
-
const parsed = RequestEnvelope.safeParse(raw);
|
|
175
|
-
if (!parsed.success) {
|
|
176
|
-
throw new GatewayProtocolError(
|
|
177
|
-
`invalid envelope: ${parsed.error.issues
|
|
178
|
-
.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
179
|
-
.join("; ")}`,
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
const m = Method.safeParse(parsed.data.method);
|
|
183
|
-
if (!m.success) {
|
|
184
|
-
throw new GatewayProtocolError(`unknown method "${parsed.data.method}"`);
|
|
185
|
-
}
|
|
186
|
-
const paramSchema = PARAM_SCHEMAS[m.data];
|
|
187
|
-
const params = paramSchema.safeParse(parsed.data.params);
|
|
188
|
-
if (!params.success) {
|
|
189
|
-
throw new GatewayProtocolError(
|
|
190
|
-
`invalid params for ${m.data}: ${params.error.issues
|
|
191
|
-
.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
192
|
-
.join("; ")}`,
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
return { ...parsed.data, method: m.data, params: params.data };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function encodeSuccess(id: string, result: unknown): ResponseEnvelopeT {
|
|
199
|
-
return { protocol: PROTOCOL_VERSION, id, result };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function encodeError(
|
|
203
|
-
id: string,
|
|
204
|
-
code: string,
|
|
205
|
-
message: string,
|
|
206
|
-
data?: unknown,
|
|
207
|
-
): ResponseEnvelopeT {
|
|
208
|
-
return {
|
|
209
|
-
protocol: PROTOCOL_VERSION,
|
|
210
|
-
id,
|
|
211
|
-
error: { code, message, ...(data !== undefined ? { data } : {}) },
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
// Standard error codes — wire-stable so reference clients can switch on them.
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
|
|
219
|
-
export const ErrorCode = {
|
|
220
|
-
Unauthorized: "unauthorized",
|
|
221
|
-
Forbidden: "forbidden",
|
|
222
|
-
NotFound: "not_found",
|
|
223
|
-
BadRequest: "bad_request",
|
|
224
|
-
BudgetExceeded: "budget_exceeded",
|
|
225
|
-
InternalError: "internal_error",
|
|
226
|
-
} as const;
|
|
227
|
-
|
|
228
|
-
export type ErrorCodeT = (typeof ErrorCode)[keyof typeof ErrorCode];
|