@zoowork-ai/sdk 0.4.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,88 @@
1
+ /**
2
+ * Session event normalization.
3
+ *
4
+ * The wire presents the SAME event in different shapes depending on where you read it:
5
+ *
6
+ * unified lane (default) → { id, seq, event_type, run_id, turn, payload, processed_at, created_at }
7
+ * on BOTH transports; the SSE `id:` line carries a `pse1:` resume token
8
+ * deprecated `after` lane → REST { seq, run_id, ... } stays snake_case, SSE frames arrive
9
+ * camelCase { seq, runId, eventType, createdAt, ... }
10
+ *
11
+ * No shape carries a top-level `type`. `normalizeEvent` absorbs all of them once, so callers
12
+ * switch on a single field. Legacy shapes verified against staging 2026-08-05; unified lane
13
+ * (input echo, pse1 cursors, idempotent postEvents) verified 2026-08-19.
14
+ *
15
+ * The vocabulary is SESSION_EVENT_TYPES plus PUBLIC_INPUT_EVENT_TYPES, mirrored from the API.
16
+ * Unknown types pass through unchanged rather than throwing — the API is Developer Preview
17
+ * and may add types within a version.
18
+ */
19
+ /** SESSION_EVENT_TYPES, mirrored from the API. */
20
+ export declare const SESSION_EVENT_TYPES: readonly ["run.started", "run.finished", "chat.delta", "chat.final", "chat.aborted", "chat.error", "agent.lifecycle", "agent.assistant", "agent.thinking", "agent.tool", "agent.item", "agent.plan", "agent.approval", "agent.command_output", "agent.patch", "agent.compaction", "agent.error", "attachment.created", "message.outbound"];
21
+ export type SessionEventType = (typeof SESSION_EVENT_TYPES)[number];
22
+ /** Your own inputs, echoed back in the unified event history. */
23
+ export declare const PUBLIC_INPUT_EVENT_TYPES: readonly ["user.message", "user.interrupt", "user.tool_confirmation", "system.message"];
24
+ export type PublicInputEventType = (typeof PUBLIC_INPUT_EVENT_TYPES)[number];
25
+ /** A durable session event, normalized across the REST and SSE shapes. */
26
+ export interface SessionEvent {
27
+ /** Durable per-session sequence: strictly increasing, not necessarily contiguous. */
28
+ seq: number;
29
+ eventType: SessionEventType | PublicInputEventType | string;
30
+ payload: Record<string, unknown>;
31
+ runId?: string;
32
+ turn?: number;
33
+ createdAt?: string;
34
+ /** Event id, when the server sends one. */
35
+ id?: string;
36
+ /** Input events only: `null` while queued, set once the agent has consumed it. */
37
+ processedAt?: string | null;
38
+ /** Resume token for `streamEvents({ cursor })`, present on streamed events. */
39
+ cursor?: string;
40
+ }
41
+ /** Accepts either wire shape (and an SSE `id:` line as the seq fallback). */
42
+ export declare function normalizeEvent(raw: unknown, sseId?: string): SessionEvent;
43
+ /**
44
+ * A run ends with `run.finished`. Its `payload.status` is `succeeded` | `failed` | `aborted`.
45
+ *
46
+ * Note that a run can finish `succeeded` even when individual tool calls errored — an
47
+ * `agent.tool` event with `payload.isError === true` does not fail the run. Do not infer
48
+ * turn success from the absence of tool errors.
49
+ */
50
+ export declare function isRunFinished(e: SessionEvent): boolean;
51
+ export declare function runOutcome(e: SessionEvent): 'succeeded' | 'failed' | 'aborted' | undefined;
52
+ /**
53
+ * Text of one chat message — the `{ role, content }` shape that appears both as an
54
+ * `agent.assistant` event's `payload.message` and as a transcript row's `entry.message`.
55
+ *
56
+ * `content` is normally an array of blocks; only `{ type: 'text', text }` blocks carry
57
+ * text (tool-call blocks don't), and one message may hold several. A plain string is
58
+ * accepted too — that is how write-side `user.message` content comes back.
59
+ */
60
+ export declare function messageText(message: unknown): string;
61
+ /**
62
+ * Assistant text for an `agent.assistant` event; '' for every other event type.
63
+ *
64
+ * The text lives at `payload.message.content[]` — see `messageText`.
65
+ */
66
+ export declare function assistantText(e: SessionEvent): string;
67
+ /** Reasoning text for an `agent.thinking` event; '' for every other type. */
68
+ export declare function thinkingText(e: SessionEvent): string;
69
+ export interface ToolCall {
70
+ phase: 'start' | 'end' | 'blocked';
71
+ toolName: string;
72
+ toolCallId: string;
73
+ args?: Record<string, unknown>;
74
+ isError?: boolean;
75
+ resultPreview?: string;
76
+ }
77
+ /**
78
+ * Tool activity for an `agent.tool` event; undefined for every other type.
79
+ *
80
+ * One tool call produces TWO events sharing a `toolCallId`: `phase: 'start'` carries `args`,
81
+ * `phase: 'end'` carries `isError` and `resultPreview`. Pair them by `toolCallId` — they are
82
+ * NOT adjacent in the stream when calls run concurrently.
83
+ *
84
+ * `phase: 'blocked'` is a third state (see the Events reference): the call is waiting on an
85
+ * approval and has NOT run. Treat it as pending, not as an end — the matching `agent.approval`
86
+ * event carries the request, and an `end` still follows once it resolves.
87
+ */
88
+ export declare function toolCall(e: SessionEvent): ToolCall | undefined;
package/dist/events.js ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Session event normalization.
3
+ *
4
+ * The wire presents the SAME event in different shapes depending on where you read it:
5
+ *
6
+ * unified lane (default) → { id, seq, event_type, run_id, turn, payload, processed_at, created_at }
7
+ * on BOTH transports; the SSE `id:` line carries a `pse1:` resume token
8
+ * deprecated `after` lane → REST { seq, run_id, ... } stays snake_case, SSE frames arrive
9
+ * camelCase { seq, runId, eventType, createdAt, ... }
10
+ *
11
+ * No shape carries a top-level `type`. `normalizeEvent` absorbs all of them once, so callers
12
+ * switch on a single field. Legacy shapes verified against staging 2026-08-05; unified lane
13
+ * (input echo, pse1 cursors, idempotent postEvents) verified 2026-08-19.
14
+ *
15
+ * The vocabulary is SESSION_EVENT_TYPES plus PUBLIC_INPUT_EVENT_TYPES, mirrored from the API.
16
+ * Unknown types pass through unchanged rather than throwing — the API is Developer Preview
17
+ * and may add types within a version.
18
+ */
19
+ /** SESSION_EVENT_TYPES, mirrored from the API. */
20
+ export const SESSION_EVENT_TYPES = [
21
+ 'run.started',
22
+ 'run.finished',
23
+ 'chat.delta',
24
+ 'chat.final',
25
+ 'chat.aborted',
26
+ 'chat.error',
27
+ 'agent.lifecycle',
28
+ 'agent.assistant',
29
+ 'agent.thinking',
30
+ 'agent.tool',
31
+ 'agent.item',
32
+ 'agent.plan',
33
+ 'agent.approval',
34
+ 'agent.command_output',
35
+ 'agent.patch',
36
+ 'agent.compaction',
37
+ 'agent.error',
38
+ 'attachment.created',
39
+ 'message.outbound',
40
+ ];
41
+ /** Your own inputs, echoed back in the unified event history. */
42
+ export const PUBLIC_INPUT_EVENT_TYPES = [
43
+ 'user.message',
44
+ 'user.interrupt',
45
+ 'user.tool_confirmation',
46
+ 'system.message',
47
+ ];
48
+ const isObj = (v) => !!v && typeof v === 'object';
49
+ const str = (v) => (typeof v === 'string' ? v : undefined);
50
+ /** Accepts either wire shape (and an SSE `id:` line as the seq fallback). */
51
+ export function normalizeEvent(raw, sseId) {
52
+ const r = isObj(raw) ? raw : {};
53
+ let seq = typeof r.seq === 'number' ? r.seq : -1;
54
+ // A bare-integer id is the deprecated lane's seq; any other id is an opaque resume token,
55
+ // so a future token version keeps stamping `cursor` (only the seq fallback pins `pse1:`).
56
+ const numericId = sseId !== undefined ? Number(sseId) : Number.NaN;
57
+ const cursor = sseId !== undefined && !Number.isFinite(numericId) ? sseId : undefined;
58
+ if (seq < 0 && sseId !== undefined) {
59
+ const n = cursor === undefined ? numericId : Number(sseId.startsWith('pse1:') ? sseId.slice(5) : Number.NaN);
60
+ if (Number.isFinite(n))
61
+ seq = n;
62
+ }
63
+ const turn = typeof r.turn === 'number' ? r.turn : undefined;
64
+ const processedAt = 'processed_at' in r ? (str(r.processed_at) ?? null) : undefined;
65
+ return {
66
+ seq,
67
+ eventType: str(r.eventType) ?? str(r.event_type) ?? '',
68
+ payload: isObj(r.payload) ? r.payload : {},
69
+ ...(str(r.runId) ?? str(r.run_id) ? { runId: (str(r.runId) ?? str(r.run_id)) } : {}),
70
+ ...(turn !== undefined ? { turn } : {}),
71
+ ...(str(r.createdAt) ?? str(r.created_at) ? { createdAt: (str(r.createdAt) ?? str(r.created_at)) } : {}),
72
+ ...(str(r.id) ? { id: str(r.id) } : {}),
73
+ ...(processedAt !== undefined ? { processedAt } : {}),
74
+ ...(cursor !== undefined ? { cursor } : {}),
75
+ };
76
+ }
77
+ /**
78
+ * A run ends with `run.finished`. Its `payload.status` is `succeeded` | `failed` | `aborted`.
79
+ *
80
+ * Note that a run can finish `succeeded` even when individual tool calls errored — an
81
+ * `agent.tool` event with `payload.isError === true` does not fail the run. Do not infer
82
+ * turn success from the absence of tool errors.
83
+ */
84
+ export function isRunFinished(e) {
85
+ return e.eventType === 'run.finished';
86
+ }
87
+ export function runOutcome(e) {
88
+ if (!isRunFinished(e))
89
+ return undefined;
90
+ const s = e.payload.status;
91
+ return s === 'succeeded' || s === 'failed' || s === 'aborted' ? s : undefined;
92
+ }
93
+ /**
94
+ * Text of one chat message — the `{ role, content }` shape that appears both as an
95
+ * `agent.assistant` event's `payload.message` and as a transcript row's `entry.message`.
96
+ *
97
+ * `content` is normally an array of blocks; only `{ type: 'text', text }` blocks carry
98
+ * text (tool-call blocks don't), and one message may hold several. A plain string is
99
+ * accepted too — that is how write-side `user.message` content comes back.
100
+ */
101
+ export function messageText(message) {
102
+ if (!isObj(message))
103
+ return '';
104
+ const c = message.content;
105
+ if (typeof c === 'string')
106
+ return c;
107
+ if (!Array.isArray(c))
108
+ return '';
109
+ return c.map((b) => (isObj(b) && b.type === 'text' && typeof b.text === 'string' ? b.text : '')).join('');
110
+ }
111
+ /**
112
+ * Assistant text for an `agent.assistant` event; '' for every other event type.
113
+ *
114
+ * The text lives at `payload.message.content[]` — see `messageText`.
115
+ */
116
+ export function assistantText(e) {
117
+ if (e.eventType !== 'agent.assistant')
118
+ return '';
119
+ return messageText(e.payload.message);
120
+ }
121
+ /** Reasoning text for an `agent.thinking` event; '' for every other type. */
122
+ export function thinkingText(e) {
123
+ if (e.eventType !== 'agent.thinking')
124
+ return '';
125
+ return typeof e.payload.text === 'string' ? e.payload.text : '';
126
+ }
127
+ /**
128
+ * Tool activity for an `agent.tool` event; undefined for every other type.
129
+ *
130
+ * One tool call produces TWO events sharing a `toolCallId`: `phase: 'start'` carries `args`,
131
+ * `phase: 'end'` carries `isError` and `resultPreview`. Pair them by `toolCallId` — they are
132
+ * NOT adjacent in the stream when calls run concurrently.
133
+ *
134
+ * `phase: 'blocked'` is a third state (see the Events reference): the call is waiting on an
135
+ * approval and has NOT run. Treat it as pending, not as an end — the matching `agent.approval`
136
+ * event carries the request, and an `end` still follows once it resolves.
137
+ */
138
+ export function toolCall(e) {
139
+ if (e.eventType !== 'agent.tool')
140
+ return undefined;
141
+ const p = e.payload;
142
+ const phase = p.phase === 'end' ? 'end' : p.phase === 'blocked' ? 'blocked' : 'start';
143
+ return {
144
+ phase,
145
+ toolName: typeof p.toolName === 'string' ? p.toolName : '',
146
+ toolCallId: typeof p.toolCallId === 'string' ? p.toolCallId : '',
147
+ ...(isObj(p.args) ? { args: p.args } : {}),
148
+ ...(typeof p.isError === 'boolean' ? { isError: p.isError } : {}),
149
+ ...(typeof p.resultPreview === 'string' ? { resultPreview: p.resultPreview } : {}),
150
+ };
151
+ }
@@ -0,0 +1,3 @@
1
+ export { createZooworkClient, DEFAULT_BASE_URL, ZooworkError, type ZooworkClient, type ZooworkConfig, type ZooworkAuth, type Ownership, type ModelInfo, type AgentResource, type AgentRecord, type AgentStatus, type AgentSkill, type AgentChannel, type ChannelPlatform, type AddChannelInput, type UpdateChannelInput, type FeishuSetupInput, type FeishuSetupSession, type FeishuPollResult, type McpServerDeclaration, type SkillRecord, type SessionRecord, type SessionHistoryEntry, type SessionEvent, type SessionEventPage, type OutboundEvent, type PostEventReceipt, type ApprovalDecision, type ApprovalRecord, type ArtifactPage, type ArtifactRecord, type ArtifactStatus, type OutcomeConfig, type OutcomeEvaluator, type SystemPromptDeclaration, type SystemPromptInfo, type SystemPromptPreview, type SystemPromptPreviewInput, type SystemPromptUpgrade, type ScheduleSpec, type SchedulePayload, type ScheduleInput, type ScheduleUpdate, type ScheduleRecord, type ScheduleRun, type WakeResult, type ExecResult, type EnvironmentConfig, type EnvironmentResource, type EnvironmentRecord, type EnvironmentVersionRecord, } from './client.js';
2
+ export { SESSION_EVENT_TYPES, type SessionEventType, PUBLIC_INPUT_EVENT_TYPES, type PublicInputEventType, normalizeEvent, isRunFinished, runOutcome, messageText, assistantText, thinkingText, toolCall, type ToolCall, } from './events.js';
3
+ export { parseSSE, type SSEMessage } from './sse.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createZooworkClient, DEFAULT_BASE_URL, ZooworkError, } from './client.js';
2
+ export { SESSION_EVENT_TYPES, PUBLIC_INPUT_EVENT_TYPES, normalizeEvent, isRunFinished, runOutcome, messageText, assistantText, thinkingText, toolCall, } from './events.js';
3
+ export { parseSSE } from './sse.js';
package/dist/sse.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SSE line parser for the `/events/stream` endpoint endpoint.
3
+ *
4
+ * Ported from zoowork-app-kit server/zooclaw/sse.ts — that parser is correct against the
5
+ * live wire and needed no changes. The `id:` field matters: The server frames each durable
6
+ * event as `id: <seq>` + `data: <json>`, so dropping the id line would freeze the resume
7
+ * cursor. Web Streams + TextDecoder only, so this runs in workers and browsers as well as
8
+ * Node.
9
+ */
10
+ export interface SSEMessage {
11
+ event: string;
12
+ /** The SSE `id:` field — the durable seq for the API event frames. */
13
+ id?: string;
14
+ data: unknown;
15
+ }
16
+ export declare const isObj: (v: unknown) => v is Record<string, unknown>;
17
+ export declare function parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<SSEMessage>;
package/dist/sse.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * SSE line parser for the `/events/stream` endpoint endpoint.
3
+ *
4
+ * Ported from zoowork-app-kit server/zooclaw/sse.ts — that parser is correct against the
5
+ * live wire and needed no changes. The `id:` field matters: The server frames each durable
6
+ * event as `id: <seq>` + `data: <json>`, so dropping the id line would freeze the resume
7
+ * cursor. Web Streams + TextDecoder only, so this runs in workers and browsers as well as
8
+ * Node.
9
+ */
10
+ export const isObj = (v) => !!v && typeof v === 'object';
11
+ export async function* parseSSE(body) {
12
+ const reader = body.getReader();
13
+ const dec = new TextDecoder('utf-8');
14
+ let buf = '';
15
+ let event = 'message';
16
+ let id;
17
+ let dataLines = [];
18
+ const flush = () => {
19
+ if (!dataLines.length && event === 'message' && id === undefined)
20
+ return null;
21
+ const s = dataLines.join('\n');
22
+ let data = s;
23
+ if (s) {
24
+ try {
25
+ data = JSON.parse(s);
26
+ }
27
+ catch {
28
+ /* not JSON — keep the raw string */
29
+ }
30
+ }
31
+ const msg = { event, data, ...(id !== undefined ? { id } : {}) };
32
+ event = 'message';
33
+ id = undefined;
34
+ dataLines = [];
35
+ return msg;
36
+ };
37
+ for (;;) {
38
+ const { done, value } = await reader.read();
39
+ if (done)
40
+ break;
41
+ buf += dec.decode(value, { stream: true });
42
+ let i;
43
+ while ((i = buf.indexOf('\n')) >= 0) {
44
+ const raw = buf.slice(0, i);
45
+ buf = buf.slice(i + 1);
46
+ const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
47
+ if (line === '') {
48
+ const m = flush();
49
+ if (m)
50
+ yield m;
51
+ continue;
52
+ }
53
+ if (line.startsWith(':'))
54
+ continue;
55
+ const c = line.indexOf(':');
56
+ const field = c === -1 ? line : line.slice(0, c);
57
+ const val = c === -1 ? '' : line.slice(c + 1).replace(/^ /, '');
58
+ if (field === 'event')
59
+ event = val;
60
+ else if (field === 'data')
61
+ dataLines.push(val);
62
+ else if (field === 'id')
63
+ id = val;
64
+ }
65
+ }
66
+ const m = flush();
67
+ if (m)
68
+ yield m;
69
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@zoowork-ai/sdk",
3
+ "version": "0.4.0",
4
+ "description": "TypeScript SDK for the ZooWork Managed Agents API (Developer Preview)",
5
+ "keywords": [
6
+ "zoowork",
7
+ "agents",
8
+ "sdk",
9
+ "llm",
10
+ "sse",
11
+ "streaming"
12
+ ],
13
+ "homepage": "https://github.com/SerendipityOneInc/zoowork-sdk-typescript#readme",
14
+ "bugs": "https://github.com/SerendipityOneInc/zoowork-sdk-typescript/issues",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/SerendipityOneInc/zoowork-sdk-typescript.git"
18
+ },
19
+ "license": "MIT",
20
+ "type": "module",
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "CHANGELOG.md",
33
+ "LICENSE"
34
+ ],
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "sideEffects": false,
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.build.json",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "tsc --noEmit && vitest run",
46
+ "test:watch": "vitest",
47
+ "prepublishOnly": "pnpm run typecheck && pnpm run build"
48
+ },
49
+ "devDependencies": {
50
+ "tsx": "^4.23.7",
51
+ "typescript": "^5.6.0",
52
+ "vitest": "^3.0.0"
53
+ }
54
+ }