@chatu-ai/builder-sdk 0.1.0

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.
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Builder 事件 schema —— 单一事实源(003.技术方案/03 §2 的代码化)
3
+ * 服务端 C# 侧使用由此导出的 JSON Schema 做对偶校验。
4
+ * seq 作用域:单个 xid(任务)内单调递增(评审记录 R1)。
5
+ */
6
+ import { z } from 'zod';
7
+ export declare const SandboxState: z.ZodEnum<["requested", "creating", "warming", "ready", "busy", "snapshotting", "hibernated", "resuming", "recycled", "failed"]>;
8
+ export type SandboxState = z.infer<typeof SandboxState>;
9
+ /** 流前确认事件(服务端自产,固定 seq=0,不参与去重排序——R17) */
10
+ export declare const AckEvent: z.ZodObject<{
11
+ xid: z.ZodString;
12
+ seq: z.ZodNumber;
13
+ } & {
14
+ kind: z.ZodLiteral<"ack">;
15
+ sandbox: z.ZodObject<{
16
+ sandboxId: z.ZodString;
17
+ state: z.ZodEnum<["requested", "creating", "warming", "ready", "busy", "snapshotting", "hibernated", "resuming", "recycled", "failed"]>;
18
+ previewUrl: z.ZodOptional<z.ZodString>;
19
+ }, "strip", z.ZodTypeAny, {
20
+ sandboxId: string;
21
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
22
+ previewUrl?: string | undefined;
23
+ }, {
24
+ sandboxId: string;
25
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
26
+ previewUrl?: string | undefined;
27
+ }>;
28
+ }, "strip", z.ZodTypeAny, {
29
+ xid: string;
30
+ seq: number;
31
+ kind: "ack";
32
+ sandbox: {
33
+ sandboxId: string;
34
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
35
+ previewUrl?: string | undefined;
36
+ };
37
+ }, {
38
+ xid: string;
39
+ seq: number;
40
+ kind: "ack";
41
+ sandbox: {
42
+ sandboxId: string;
43
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
44
+ previewUrl?: string | undefined;
45
+ };
46
+ }>;
47
+ export declare const MessageEvent: z.ZodObject<{
48
+ xid: z.ZodString;
49
+ seq: z.ZodNumber;
50
+ } & {
51
+ kind: z.ZodLiteral<"message">;
52
+ role: z.ZodEnum<["assistant", "system"]>;
53
+ text: z.ZodString;
54
+ }, "strip", z.ZodTypeAny, {
55
+ xid: string;
56
+ seq: number;
57
+ kind: "message";
58
+ role: "assistant" | "system";
59
+ text: string;
60
+ }, {
61
+ xid: string;
62
+ seq: number;
63
+ kind: "message";
64
+ role: "assistant" | "system";
65
+ text: string;
66
+ }>;
67
+ export declare const TaskCardEvent: z.ZodObject<{
68
+ xid: z.ZodString;
69
+ seq: z.ZodNumber;
70
+ } & {
71
+ kind: z.ZodLiteral<"taskCard">;
72
+ id: z.ZodString;
73
+ label: z.ZodString;
74
+ state: z.ZodEnum<["running", "done", "failed"]>;
75
+ detail: z.ZodOptional<z.ZodString>;
76
+ }, "strip", z.ZodTypeAny, {
77
+ xid: string;
78
+ seq: number;
79
+ kind: "taskCard";
80
+ state: "failed" | "running" | "done";
81
+ id: string;
82
+ label: string;
83
+ detail?: string | undefined;
84
+ }, {
85
+ xid: string;
86
+ seq: number;
87
+ kind: "taskCard";
88
+ state: "failed" | "running" | "done";
89
+ id: string;
90
+ label: string;
91
+ detail?: string | undefined;
92
+ }>;
93
+ export declare const FileDiffEvent: z.ZodObject<{
94
+ xid: z.ZodString;
95
+ seq: z.ZodNumber;
96
+ } & {
97
+ kind: z.ZodLiteral<"fileDiff">;
98
+ path: z.ZodString;
99
+ action: z.ZodEnum<["create", "modify", "delete"]>;
100
+ diff: z.ZodOptional<z.ZodString>;
101
+ bytes: z.ZodNumber;
102
+ truncated: z.ZodDefault<z.ZodBoolean>;
103
+ }, "strip", z.ZodTypeAny, {
104
+ path: string;
105
+ xid: string;
106
+ seq: number;
107
+ kind: "fileDiff";
108
+ action: "create" | "modify" | "delete";
109
+ bytes: number;
110
+ truncated: boolean;
111
+ diff?: string | undefined;
112
+ }, {
113
+ path: string;
114
+ xid: string;
115
+ seq: number;
116
+ kind: "fileDiff";
117
+ action: "create" | "modify" | "delete";
118
+ bytes: number;
119
+ diff?: string | undefined;
120
+ truncated?: boolean | undefined;
121
+ }>;
122
+ export declare const PreviewEvent: z.ZodObject<{
123
+ xid: z.ZodString;
124
+ seq: z.ZodNumber;
125
+ } & {
126
+ kind: z.ZodLiteral<"preview">;
127
+ state: z.ZodEnum<["starting", "ready", "crashed"]>;
128
+ url: z.ZodOptional<z.ZodString>;
129
+ error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
130
+ }, "strip", z.ZodTypeAny, {
131
+ xid: string;
132
+ seq: number;
133
+ kind: "preview";
134
+ state: "ready" | "starting" | "crashed";
135
+ url?: string | undefined;
136
+ error?: string | null | undefined;
137
+ }, {
138
+ xid: string;
139
+ seq: number;
140
+ kind: "preview";
141
+ state: "ready" | "starting" | "crashed";
142
+ url?: string | undefined;
143
+ error?: string | null | undefined;
144
+ }>;
145
+ export declare const VersionEvent: z.ZodObject<{
146
+ xid: z.ZodString;
147
+ seq: z.ZodNumber;
148
+ } & {
149
+ kind: z.ZodLiteral<"version">;
150
+ sha: z.ZodString;
151
+ message: z.ZodString;
152
+ filesChanged: z.ZodNumber;
153
+ }, "strip", z.ZodTypeAny, {
154
+ message: string;
155
+ xid: string;
156
+ seq: number;
157
+ kind: "version";
158
+ sha: string;
159
+ filesChanged: number;
160
+ }, {
161
+ message: string;
162
+ xid: string;
163
+ seq: number;
164
+ kind: "version";
165
+ sha: string;
166
+ filesChanged: number;
167
+ }>;
168
+ export declare const DoneEvent: z.ZodObject<{
169
+ xid: z.ZodString;
170
+ seq: z.ZodNumber;
171
+ } & {
172
+ kind: z.ZodLiteral<"done">;
173
+ state: z.ZodEnum<["completed", "failed", "canceled"]>;
174
+ error: z.ZodOptional<z.ZodString>;
175
+ }, "strip", z.ZodTypeAny, {
176
+ xid: string;
177
+ seq: number;
178
+ kind: "done";
179
+ state: "failed" | "completed" | "canceled";
180
+ error?: string | undefined;
181
+ }, {
182
+ xid: string;
183
+ seq: number;
184
+ kind: "done";
185
+ state: "failed" | "completed" | "canceled";
186
+ error?: string | undefined;
187
+ }>;
188
+ export declare const BuilderEvent: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
189
+ xid: z.ZodString;
190
+ seq: z.ZodNumber;
191
+ } & {
192
+ kind: z.ZodLiteral<"ack">;
193
+ sandbox: z.ZodObject<{
194
+ sandboxId: z.ZodString;
195
+ state: z.ZodEnum<["requested", "creating", "warming", "ready", "busy", "snapshotting", "hibernated", "resuming", "recycled", "failed"]>;
196
+ previewUrl: z.ZodOptional<z.ZodString>;
197
+ }, "strip", z.ZodTypeAny, {
198
+ sandboxId: string;
199
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
200
+ previewUrl?: string | undefined;
201
+ }, {
202
+ sandboxId: string;
203
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
204
+ previewUrl?: string | undefined;
205
+ }>;
206
+ }, "strip", z.ZodTypeAny, {
207
+ xid: string;
208
+ seq: number;
209
+ kind: "ack";
210
+ sandbox: {
211
+ sandboxId: string;
212
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
213
+ previewUrl?: string | undefined;
214
+ };
215
+ }, {
216
+ xid: string;
217
+ seq: number;
218
+ kind: "ack";
219
+ sandbox: {
220
+ sandboxId: string;
221
+ state: "requested" | "creating" | "warming" | "ready" | "busy" | "snapshotting" | "hibernated" | "resuming" | "recycled" | "failed";
222
+ previewUrl?: string | undefined;
223
+ };
224
+ }>, z.ZodObject<{
225
+ xid: z.ZodString;
226
+ seq: z.ZodNumber;
227
+ } & {
228
+ kind: z.ZodLiteral<"message">;
229
+ role: z.ZodEnum<["assistant", "system"]>;
230
+ text: z.ZodString;
231
+ }, "strip", z.ZodTypeAny, {
232
+ xid: string;
233
+ seq: number;
234
+ kind: "message";
235
+ role: "assistant" | "system";
236
+ text: string;
237
+ }, {
238
+ xid: string;
239
+ seq: number;
240
+ kind: "message";
241
+ role: "assistant" | "system";
242
+ text: string;
243
+ }>, z.ZodObject<{
244
+ xid: z.ZodString;
245
+ seq: z.ZodNumber;
246
+ } & {
247
+ kind: z.ZodLiteral<"taskCard">;
248
+ id: z.ZodString;
249
+ label: z.ZodString;
250
+ state: z.ZodEnum<["running", "done", "failed"]>;
251
+ detail: z.ZodOptional<z.ZodString>;
252
+ }, "strip", z.ZodTypeAny, {
253
+ xid: string;
254
+ seq: number;
255
+ kind: "taskCard";
256
+ state: "failed" | "running" | "done";
257
+ id: string;
258
+ label: string;
259
+ detail?: string | undefined;
260
+ }, {
261
+ xid: string;
262
+ seq: number;
263
+ kind: "taskCard";
264
+ state: "failed" | "running" | "done";
265
+ id: string;
266
+ label: string;
267
+ detail?: string | undefined;
268
+ }>, z.ZodObject<{
269
+ xid: z.ZodString;
270
+ seq: z.ZodNumber;
271
+ } & {
272
+ kind: z.ZodLiteral<"fileDiff">;
273
+ path: z.ZodString;
274
+ action: z.ZodEnum<["create", "modify", "delete"]>;
275
+ diff: z.ZodOptional<z.ZodString>;
276
+ bytes: z.ZodNumber;
277
+ truncated: z.ZodDefault<z.ZodBoolean>;
278
+ }, "strip", z.ZodTypeAny, {
279
+ path: string;
280
+ xid: string;
281
+ seq: number;
282
+ kind: "fileDiff";
283
+ action: "create" | "modify" | "delete";
284
+ bytes: number;
285
+ truncated: boolean;
286
+ diff?: string | undefined;
287
+ }, {
288
+ path: string;
289
+ xid: string;
290
+ seq: number;
291
+ kind: "fileDiff";
292
+ action: "create" | "modify" | "delete";
293
+ bytes: number;
294
+ diff?: string | undefined;
295
+ truncated?: boolean | undefined;
296
+ }>, z.ZodObject<{
297
+ xid: z.ZodString;
298
+ seq: z.ZodNumber;
299
+ } & {
300
+ kind: z.ZodLiteral<"preview">;
301
+ state: z.ZodEnum<["starting", "ready", "crashed"]>;
302
+ url: z.ZodOptional<z.ZodString>;
303
+ error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
304
+ }, "strip", z.ZodTypeAny, {
305
+ xid: string;
306
+ seq: number;
307
+ kind: "preview";
308
+ state: "ready" | "starting" | "crashed";
309
+ url?: string | undefined;
310
+ error?: string | null | undefined;
311
+ }, {
312
+ xid: string;
313
+ seq: number;
314
+ kind: "preview";
315
+ state: "ready" | "starting" | "crashed";
316
+ url?: string | undefined;
317
+ error?: string | null | undefined;
318
+ }>, z.ZodObject<{
319
+ xid: z.ZodString;
320
+ seq: z.ZodNumber;
321
+ } & {
322
+ kind: z.ZodLiteral<"version">;
323
+ sha: z.ZodString;
324
+ message: z.ZodString;
325
+ filesChanged: z.ZodNumber;
326
+ }, "strip", z.ZodTypeAny, {
327
+ message: string;
328
+ xid: string;
329
+ seq: number;
330
+ kind: "version";
331
+ sha: string;
332
+ filesChanged: number;
333
+ }, {
334
+ message: string;
335
+ xid: string;
336
+ seq: number;
337
+ kind: "version";
338
+ sha: string;
339
+ filesChanged: number;
340
+ }>, z.ZodObject<{
341
+ xid: z.ZodString;
342
+ seq: z.ZodNumber;
343
+ } & {
344
+ kind: z.ZodLiteral<"done">;
345
+ state: z.ZodEnum<["completed", "failed", "canceled"]>;
346
+ error: z.ZodOptional<z.ZodString>;
347
+ }, "strip", z.ZodTypeAny, {
348
+ xid: string;
349
+ seq: number;
350
+ kind: "done";
351
+ state: "failed" | "completed" | "canceled";
352
+ error?: string | undefined;
353
+ }, {
354
+ xid: string;
355
+ seq: number;
356
+ kind: "done";
357
+ state: "failed" | "completed" | "canceled";
358
+ error?: string | undefined;
359
+ }>]>;
360
+ export type BuilderEvent = z.infer<typeof BuilderEvent>;
package/dist/events.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Builder 事件 schema —— 单一事实源(003.技术方案/03 §2 的代码化)
3
+ * 服务端 C# 侧使用由此导出的 JSON Schema 做对偶校验。
4
+ * seq 作用域:单个 xid(任务)内单调递增(评审记录 R1)。
5
+ */
6
+ import { z } from 'zod';
7
+ export const SandboxState = z.enum([
8
+ 'requested', 'creating', 'warming', 'ready', 'busy',
9
+ 'snapshotting', 'hibernated', 'resuming', 'recycled', 'failed',
10
+ ]);
11
+ const base = z.object({
12
+ xid: z.string(),
13
+ seq: z.number().int().nonnegative(),
14
+ });
15
+ /** 流前确认事件(服务端自产,固定 seq=0,不参与去重排序——R17) */
16
+ export const AckEvent = base.extend({
17
+ kind: z.literal('ack'),
18
+ sandbox: z.object({
19
+ sandboxId: z.string(),
20
+ state: SandboxState,
21
+ previewUrl: z.string().url().optional(),
22
+ }),
23
+ });
24
+ export const MessageEvent = base.extend({
25
+ kind: z.literal('message'),
26
+ role: z.enum(['assistant', 'system']),
27
+ text: z.string(),
28
+ });
29
+ export const TaskCardEvent = base.extend({
30
+ kind: z.literal('taskCard'),
31
+ id: z.string(),
32
+ label: z.string(),
33
+ state: z.enum(['running', 'done', 'failed']),
34
+ detail: z.string().optional(),
35
+ });
36
+ export const FileDiffEvent = base.extend({
37
+ kind: z.literal('fileDiff'),
38
+ path: z.string(),
39
+ action: z.enum(['create', 'modify', 'delete']),
40
+ diff: z.string().optional(),
41
+ bytes: z.number().int().nonnegative(),
42
+ truncated: z.boolean().default(false),
43
+ });
44
+ export const PreviewEvent = base.extend({
45
+ kind: z.literal('preview'),
46
+ state: z.enum(['starting', 'ready', 'crashed']),
47
+ url: z.string().url().optional(),
48
+ error: z.string().nullable().optional(),
49
+ });
50
+ export const VersionEvent = base.extend({
51
+ kind: z.literal('version'),
52
+ sha: z.string(),
53
+ message: z.string(),
54
+ filesChanged: z.number().int().nonnegative(),
55
+ });
56
+ export const DoneEvent = base.extend({
57
+ kind: z.literal('done'),
58
+ state: z.enum(['completed', 'failed', 'canceled']),
59
+ error: z.string().optional(),
60
+ });
61
+ export const BuilderEvent = z.discriminatedUnion('kind', [
62
+ AckEvent, MessageEvent, TaskCardEvent, FileDiffEvent, PreviewEvent, VersionEvent, DoneEvent,
63
+ ]);
@@ -0,0 +1,8 @@
1
+ export * from './events';
2
+ export * from './auth';
3
+ export * from './client';
4
+ export * from './parse';
5
+ export * from './resume';
6
+ export * from './agent3';
7
+ export * from './agent3Transport';
8
+ export * from './sse';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './events';
2
+ export * from './auth';
3
+ export * from './client';
4
+ export * from './parse';
5
+ export * from './resume';
6
+ export * from './agent3';
7
+ export * from './agent3Transport';
8
+ export * from './sse';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A2A 原始事件 → BuilderEvent 解析(03 §2 冻结 schema 的映射实现)。
3
+ * 未知/不完整事件返回 null(前向兼容:忽略而非报错——N4 版本偏斜策略)。
4
+ */
5
+ import { BuilderEvent } from './events';
6
+ export declare function parseBuilderEvent(raw: unknown): BuilderEvent | null;
package/dist/parse.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * A2A 原始事件 → BuilderEvent 解析(03 §2 冻结 schema 的映射实现)。
3
+ * 未知/不完整事件返回 null(前向兼容:忽略而非报错——N4 版本偏斜策略)。
4
+ */
5
+ import { BuilderEvent } from './events';
6
+ export function parseBuilderEvent(raw) {
7
+ if (typeof raw !== 'object' || raw === null)
8
+ return null;
9
+ const r = raw;
10
+ const md = r.metadata ?? {};
11
+ const xid = r.taskId ?? r.task?.id;
12
+ if (!xid)
13
+ return null;
14
+ const seq = typeof md.seq === 'number' ? md.seq : 0;
15
+ let candidate = null;
16
+ if (md.sandbox) {
17
+ candidate = { kind: 'ack', xid, seq: 0, sandbox: md.sandbox };
18
+ }
19
+ else if (md.taskCard) {
20
+ candidate = { kind: 'taskCard', xid, seq, ...md.taskCard };
21
+ }
22
+ else if (md.preview) {
23
+ candidate = { kind: 'preview', xid, seq, ...md.preview };
24
+ }
25
+ else if (md.version) {
26
+ candidate = { kind: 'version', xid, seq, ...md.version };
27
+ }
28
+ else if (md.file && r.artifact) {
29
+ candidate = {
30
+ kind: 'fileDiff', xid, seq,
31
+ path: r.artifact.name ?? '',
32
+ action: md.file.action,
33
+ diff: r.artifact.parts?.find(p => p.kind === 'text')?.text,
34
+ bytes: md.file.bytes ?? 0,
35
+ truncated: md.file.truncated ?? false,
36
+ };
37
+ }
38
+ else if (md.text) {
39
+ candidate = { kind: 'message', xid, seq, role: md.role ?? 'assistant', text: md.text };
40
+ }
41
+ else {
42
+ const state = r.status?.state ?? r.task?.status?.state;
43
+ if (state === 'completed' || state === 'failed' || state === 'canceled') {
44
+ candidate = { kind: 'done', xid, seq, state, error: md.error };
45
+ }
46
+ }
47
+ if (candidate === null)
48
+ return null;
49
+ const parsed = BuilderEvent.safeParse(candidate);
50
+ return parsed.success ? parsed.data : null;
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseBuilderEvent } from './parse';
3
+ describe('parseBuilderEvent (03 §2 shapes)', () => {
4
+ it('parses create ack (seq forced to 0)', () => {
5
+ const e = parseBuilderEvent({
6
+ task: { id: 'xid-1', status: { state: 'submitted' } },
7
+ metadata: { seq: 1, sandbox: { sandboxId: 'sbx_a', state: 'resuming', previewUrl: 'https://sbx-a.n.y.com' } },
8
+ });
9
+ expect(e).toMatchObject({ kind: 'ack', xid: 'xid-1', seq: 0, sandbox: { sandboxId: 'sbx_a' } });
10
+ });
11
+ it('parses taskCard status update', () => {
12
+ const e = parseBuilderEvent({
13
+ taskId: 'xid-1', status: { state: 'working' },
14
+ metadata: { seq: 42, taskCard: { id: 'tc_3', label: '安装依赖', state: 'running', detail: 'pnpm add recharts' } },
15
+ });
16
+ expect(e).toMatchObject({ kind: 'taskCard', seq: 42, label: '安装依赖' });
17
+ });
18
+ it('parses fileDiff artifact', () => {
19
+ const e = parseBuilderEvent({
20
+ taskId: 'xid-1',
21
+ artifact: { artifactId: 'file:app/page.tsx', name: 'app/page.tsx', parts: [{ kind: 'text', text: '--- diff' }] },
22
+ metadata: { seq: 43, file: { action: 'modify', bytes: 2048, truncated: false } },
23
+ });
24
+ expect(e).toMatchObject({ kind: 'fileDiff', path: 'app/page.tsx', action: 'modify', diff: '--- diff' });
25
+ });
26
+ it('parses version artifact and done', () => {
27
+ expect(parseBuilderEvent({
28
+ taskId: 'xid-1', artifact: { artifactId: 'version:abc' },
29
+ metadata: { seq: 99, version: { sha: 'abc123', message: 'add landing page', filesChanged: 7 } },
30
+ })).toMatchObject({ kind: 'version', sha: 'abc123' });
31
+ expect(parseBuilderEvent({
32
+ taskId: 'xid-1', status: { state: 'completed' }, metadata: { seq: 100 },
33
+ })).toMatchObject({ kind: 'done', state: 'completed' });
34
+ });
35
+ it('returns null for unknown/malformed events (forward compat)', () => {
36
+ expect(parseBuilderEvent({ metadata: { seq: 1 } })).toBeNull(); // 无 xid
37
+ expect(parseBuilderEvent({ taskId: 'x', metadata: { seq: 1 } })).toBeNull(); // 无可识别载荷
38
+ expect(parseBuilderEvent('not-an-object')).toBeNull();
39
+ expect(parseBuilderEvent({ taskId: 'x', metadata: { seq: 1, taskCard: { bad: true } } })).toBeNull(); // schema 不符
40
+ });
41
+ });
42
+ import { createBuilderClient, CookieAuth } from './index';
43
+ describe('client req envelope unwrap', () => {
44
+ const mkClient = (body, ct = 'application/json') => createBuilderClient({
45
+ restBase: 'https://api.test/web/Builder',
46
+ auth: new CookieAuth(),
47
+ transport: { stream: async function* () { }, resubscribe: async function* () { }, cancel: async () => { } },
48
+ fetchImpl: (async () => new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': ct } })),
49
+ });
50
+ it('unwraps {code:0,data} envelope', async () => {
51
+ const c = mkClient({ code: 0, data: { token: 't', previewUrl: 'https://x/?t=t' } });
52
+ expect(await c.sandbox.previewToken('c1')).toEqual({ token: 't', previewUrl: 'https://x/?t=t' });
53
+ });
54
+ it('passes through raw shapes (ContentResult endpoints)', async () => {
55
+ const c = mkClient({ versions: [{ sha: 'a', message: 'm', filesChanged: 1 }] });
56
+ expect((await c.versions.list('c1'))[0].sha).toBe('a');
57
+ });
58
+ it('throws on non-zero code', async () => {
59
+ const c = mkClient({ code: 400, data: null, message: '会话不存在' });
60
+ await expect(c.sandbox.status('c1')).rejects.toThrow('会话不存在');
61
+ });
62
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 弹性事件流:seq 去重(xid 作用域)+ 断线自动 resubscribe(指数退避)。
3
+ * 保证下游拿到的流有序无重;ack(seq=0) 不参与去重。
4
+ */
5
+ import type { BuilderEvent } from './events';
6
+ export interface ResumeSource {
7
+ /** 初始流 */
8
+ open(): AsyncIterable<BuilderEvent | null>;
9
+ /** 断线后按 lastSeq 重订阅 */
10
+ reopen(lastSeq: number): AsyncIterable<BuilderEvent | null>;
11
+ }
12
+ export interface ResilienceOptions {
13
+ maxRetries?: number;
14
+ baseDelayMs?: number;
15
+ sleep?: (ms: number) => Promise<void>;
16
+ }
17
+ export declare function resilientStream(source: ResumeSource, opts?: ResilienceOptions): AsyncIterable<BuilderEvent>;
package/dist/resume.js ADDED
@@ -0,0 +1,33 @@
1
+ export async function* resilientStream(source, opts = {}) {
2
+ const maxRetries = opts.maxRetries ?? 3;
3
+ const baseDelay = opts.baseDelayMs ?? 500;
4
+ const sleep = opts.sleep ?? (ms => new Promise(r => setTimeout(r, ms)));
5
+ let lastSeq = 0;
6
+ let retries = 0;
7
+ let iterable = source.open();
8
+ for (;;) {
9
+ try {
10
+ for await (const ev of iterable) {
11
+ if (ev === null)
12
+ continue;
13
+ if (ev.kind !== 'ack') {
14
+ if (ev.seq <= lastSeq)
15
+ continue; // 去重/乱序丢弃
16
+ lastSeq = ev.seq;
17
+ }
18
+ retries = 0; // 有数据即重置退避
19
+ yield ev;
20
+ if (ev.kind === 'done')
21
+ return;
22
+ }
23
+ return; // 流正常结束
24
+ }
25
+ catch (err) {
26
+ if (retries >= maxRetries)
27
+ throw err;
28
+ await sleep(baseDelay * 2 ** retries);
29
+ retries++;
30
+ iterable = source.reopen(lastSeq);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ export {};