@notionhq/workers 0.0.85 → 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.
@@ -7,12 +7,8 @@ import {
7
7
  type Mock,
8
8
  vi,
9
9
  } from "vitest";
10
- import {
11
- createToolCapability,
12
- InvalidToolInputError,
13
- InvalidToolOutputError,
14
- ToolExecutionError,
15
- } from "./tool.js";
10
+ import { j } from "../schema-builder.js";
11
+ import { createToolCapability } from "./tool.js";
16
12
 
17
13
  describe("Worker.tool", () => {
18
14
  let stdoutSpy: Mock<typeof process.stdout.write>;
@@ -33,14 +29,7 @@ describe("Worker.tool", () => {
33
29
  {
34
30
  title: "Say Hello",
35
31
  description: "Greet a user",
36
- schema: {
37
- type: "object",
38
- properties: {
39
- name: { type: "string" },
40
- },
41
- required: ["name"],
42
- additionalProperties: false,
43
- },
32
+ schema: j.object({ name: j.string() }),
44
33
  execute: (input: { name: string }) => {
45
34
  return `Hello, ${input.name}!`;
46
35
  },
@@ -52,7 +41,7 @@ describe("Worker.tool", () => {
52
41
  expect(result).toEqual({ _tag: "success", value: "Hello, Alice!" });
53
42
 
54
43
  expect(stdoutSpy).toHaveBeenCalledWith(
55
- `\n<output>"Hello, Alice!"</output>\n`,
44
+ `\n<output>{"_tag":"success","value":"Hello, Alice!"}</output>\n`,
56
45
  );
57
46
  });
58
47
 
@@ -62,14 +51,7 @@ describe("Worker.tool", () => {
62
51
  {
63
52
  title: "Fetch Data",
64
53
  description: "Fetch data asynchronously",
65
- schema: {
66
- type: "object",
67
- properties: {
68
- id: { type: "number" },
69
- },
70
- required: ["id"],
71
- additionalProperties: false,
72
- },
54
+ schema: j.object({ id: j.number() }),
73
55
  execute: async (input: { id: number }) => {
74
56
  // Simulate async operation
75
57
  await new Promise((resolve) => setTimeout(resolve, 10));
@@ -86,7 +68,7 @@ describe("Worker.tool", () => {
86
68
  });
87
69
 
88
70
  expect(stdoutSpy).toHaveBeenCalledWith(
89
- `\n<output>{"data":"Data for ID 42"}</output>\n`,
71
+ `\n<output>{"_tag":"success","value":{"data":"Data for ID 42"}}</output>\n`,
90
72
  );
91
73
  });
92
74
 
@@ -96,12 +78,7 @@ describe("Worker.tool", () => {
96
78
  {
97
79
  title: "Throw Error",
98
80
  description: "Throws an error",
99
- schema: {
100
- type: "object",
101
- properties: {},
102
- required: [],
103
- additionalProperties: false,
104
- },
81
+ schema: j.object({}),
105
82
  execute: () => {
106
83
  throw new Error("Something went wrong");
107
84
  },
@@ -110,15 +87,17 @@ describe("Worker.tool", () => {
110
87
 
111
88
  const result = (await capability.handler({})) as {
112
89
  _tag: "error";
113
- error: ToolExecutionError;
90
+ error: { name: string; message: string; trace: string | undefined };
114
91
  };
115
92
 
116
93
  expect(result._tag).toBe("error");
117
- expect(result.error).toBeInstanceOf(ToolExecutionError);
94
+ expect(result.error.name).toBe("ToolExecutionError");
118
95
  expect(result.error.message).toBe("Something went wrong");
119
96
 
120
97
  expect(stdoutSpy).toHaveBeenCalledWith(
121
- `\n<output>{"_tag":"error","error":{"name":"ToolExecutionError","message":"Something went wrong"}}</output>\n`,
98
+ expect.stringContaining(
99
+ `"_tag":"error","error":{"name":"ToolExecutionError","message":"Something went wrong"`,
100
+ ),
122
101
  );
123
102
  });
124
103
 
@@ -128,14 +107,7 @@ describe("Worker.tool", () => {
128
107
  {
129
108
  title: "Say Hello",
130
109
  description: "Requires a name property",
131
- schema: {
132
- type: "object",
133
- properties: {
134
- name: { type: "string" },
135
- },
136
- required: ["name"],
137
- additionalProperties: false,
138
- },
110
+ schema: j.object({ name: j.string() }),
139
111
  execute: (input: { name: string }) => {
140
112
  return `Hello, ${input.name}!`;
141
113
  },
@@ -144,11 +116,11 @@ describe("Worker.tool", () => {
144
116
 
145
117
  const result = (await capability.handler({ age: 25 })) as {
146
118
  _tag: "error";
147
- error: InvalidToolInputError;
119
+ error: { name: string; message: string; trace: string | undefined };
148
120
  };
149
121
 
150
122
  expect(result._tag).toBe("error");
151
- expect(result.error).toBeInstanceOf(InvalidToolInputError);
123
+ expect(result.error.name).toBe("InvalidToolInputError");
152
124
  expect(result.error.message).toContain("name");
153
125
 
154
126
  expect(stdoutSpy).toHaveBeenCalledWith(
@@ -165,20 +137,8 @@ describe("Worker.tool", () => {
165
137
  >("invalidOutput", {
166
138
  title: "Return Invalid Output",
167
139
  description: "Returns output that doesn't match schema",
168
- schema: {
169
- type: "object",
170
- properties: {},
171
- required: [],
172
- additionalProperties: false,
173
- },
174
- outputSchema: {
175
- type: "object",
176
- properties: {
177
- result: { type: "string" },
178
- },
179
- required: ["result"],
180
- additionalProperties: false,
181
- },
140
+ schema: j.object({}),
141
+ outputSchema: j.object({ result: j.string() }),
182
142
  // @ts-expect-error Testing invalid output - intentionally returning number instead of string
183
143
  execute: () => {
184
144
  return { result: 123 };
@@ -187,68 +147,20 @@ describe("Worker.tool", () => {
187
147
 
188
148
  const result = (await capability.handler({})) as {
189
149
  _tag: "error";
190
- error: InvalidToolOutputError;
150
+ error: { name: string; message: string; trace: string | undefined };
191
151
  };
192
152
 
193
153
  expect(result._tag).toBe("error");
194
- expect(result.error).toBeInstanceOf(InvalidToolOutputError);
154
+ expect(result.error.name).toBe("InvalidToolOutputError");
195
155
  expect(result.error.message).toContain("result");
196
156
 
197
157
  expect(stdoutSpy).toHaveBeenCalledWith(
198
158
  expect.stringContaining(
199
- `\n<output>{"_tag":"error","error":{"name":"InvalidToolOutputError"`,
159
+ `"_tag":"error","error":{"name":"InvalidToolOutputError"`,
200
160
  ),
201
161
  );
202
162
  });
203
163
 
204
- it("rejects schema where required is missing properties", () => {
205
- expect(() =>
206
- createToolCapability<{ name: string; age: number }, string>("bad", {
207
- title: "Bad",
208
- description: "Missing required",
209
- schema: {
210
- type: "object",
211
- properties: {
212
- name: { type: "string" },
213
- age: { type: "number" },
214
- },
215
- required: ["name"],
216
- additionalProperties: false,
217
- } as never,
218
- execute: () => "",
219
- }),
220
- ).toThrow(/properties \[age\] must be listed in "required"/);
221
- });
222
-
223
- it("rejects nested schema where required is missing properties", () => {
224
- expect(() =>
225
- createToolCapability<{ user: { name: string; email: string } }, string>(
226
- "bad",
227
- {
228
- title: "Bad",
229
- description: "Nested missing required",
230
- schema: {
231
- type: "object",
232
- properties: {
233
- user: {
234
- type: "object",
235
- properties: {
236
- name: { type: "string" },
237
- email: { type: "string" },
238
- },
239
- required: ["name"],
240
- additionalProperties: false,
241
- } as never,
242
- },
243
- required: ["user"],
244
- additionalProperties: false,
245
- },
246
- execute: () => "",
247
- },
248
- ),
249
- ).toThrow(/at properties\.user.*properties \[email\]/);
250
- });
251
-
252
164
  it("accepts schema with nullable (string | null) properties", () => {
253
165
  const capability = createToolCapability<
254
166
  { name: string; nickname: string | null },
@@ -256,51 +168,16 @@ describe("Worker.tool", () => {
256
168
  >("nullable", {
257
169
  title: "Nullable",
258
170
  description: "Has nullable properties",
259
- schema: {
260
- type: "object",
261
- properties: {
262
- name: { type: "string" },
263
- nickname: {
264
- anyOf: [{ type: "string" }, { type: "null" }],
265
- },
266
- },
267
- required: ["name", "nickname"],
268
- additionalProperties: false,
269
- },
171
+ schema: j.object({
172
+ name: j.string(),
173
+ nickname: j.string().nullable(),
174
+ }),
270
175
  execute: ({ name, nickname }) => `${name} (${nickname ?? "no nickname"})`,
271
176
  });
272
177
 
273
178
  expect(capability.config.schema).toBeDefined();
274
179
  });
275
180
 
276
- it("rejects output schema where required is missing properties", () => {
277
- expect(() =>
278
- createToolCapability<
279
- Record<string, never>,
280
- { result: string; count: number }
281
- >("bad", {
282
- title: "Bad",
283
- description: "Output missing required",
284
- schema: {
285
- type: "object",
286
- properties: {},
287
- required: [],
288
- additionalProperties: false,
289
- },
290
- outputSchema: {
291
- type: "object",
292
- properties: {
293
- result: { type: "string" },
294
- count: { type: "number" },
295
- },
296
- required: ["result"],
297
- additionalProperties: false,
298
- } as never,
299
- execute: () => ({ result: "", count: 0 }),
300
- }),
301
- ).toThrow(/properties \[count\] must be listed in "required"/);
302
- });
303
-
304
181
  it("invalid output with custom output schema", async () => {
305
182
  const capability = createToolCapability<
306
183
  { value: number },
@@ -308,23 +185,11 @@ describe("Worker.tool", () => {
308
185
  >("customOutput", {
309
186
  title: "Custom Output",
310
187
  description: "Has custom output schema",
311
- schema: {
312
- type: "object",
313
- properties: {
314
- value: { type: "number" },
315
- },
316
- required: ["value"],
317
- additionalProperties: false,
318
- },
319
- outputSchema: {
320
- type: "object",
321
- properties: {
322
- doubled: { type: "number" },
323
- message: { type: "string" },
324
- },
325
- required: ["doubled", "message"],
326
- additionalProperties: false,
327
- },
188
+ schema: j.object({ value: j.number() }),
189
+ outputSchema: j.object({
190
+ doubled: j.number(),
191
+ message: j.string(),
192
+ }),
328
193
  // @ts-expect-error Testing invalid output - intentionally missing message property
329
194
  execute: (input: { value: number }) => {
330
195
  return {
@@ -335,16 +200,16 @@ describe("Worker.tool", () => {
335
200
 
336
201
  const result = (await capability.handler({ value: 5 })) as {
337
202
  _tag: "error";
338
- error: InvalidToolOutputError;
203
+ error: { name: string; message: string; trace: string | undefined };
339
204
  };
340
205
 
341
206
  expect(result._tag).toBe("error");
342
- expect(result.error).toBeInstanceOf(InvalidToolOutputError);
207
+ expect(result.error.name).toBe("InvalidToolOutputError");
343
208
  expect(result.error.message).toContain("message");
344
209
 
345
210
  expect(stdoutSpy).toHaveBeenCalledWith(
346
211
  expect.stringContaining(
347
- `\n<output>{"_tag":"error","error":{"name":"InvalidToolOutputError"`,
212
+ `"_tag":"error","error":{"name":"InvalidToolOutputError"`,
348
213
  ),
349
214
  );
350
215
  });
@@ -1,5 +1,7 @@
1
1
  import { Ajv } from "ajv";
2
- import type { AnyJSONSchema, JSONSchema } from "../json-schema.js";
2
+ import type { AnyJSONSchema } from "../json-schema.js";
3
+ import type { SchemaBuilder } from "../schema-builder.js";
4
+ import { getSchema } from "../schema-builder.js";
3
5
  import type { HandlerOptions, JSONValue } from "../types.js";
4
6
  import type { CapabilityContext } from "./context.js";
5
7
  import { createCapabilityContext } from "./context.js";
@@ -72,8 +74,8 @@ export interface ToolConfiguration<
72
74
  > {
73
75
  title: string;
74
76
  description: string;
75
- schema: JSONSchema<I>;
76
- outputSchema?: JSONSchema<O>;
77
+ schema: SchemaBuilder<I>;
78
+ outputSchema?: SchemaBuilder<O>;
77
79
  execute: (input: I, context: CapabilityContext) => O | Promise<O>;
78
80
  }
79
81
 
@@ -90,6 +92,7 @@ export class InvalidToolInputError extends Error {
90
92
  return {
91
93
  name: this.name,
92
94
  message: this.message,
95
+ trace: this.stack,
93
96
  };
94
97
  }
95
98
  }
@@ -107,6 +110,7 @@ export class InvalidToolOutputError extends Error {
107
110
  return {
108
111
  name: this.name,
109
112
  message: this.message,
113
+ trace: this.stack,
110
114
  };
111
115
  }
112
116
  }
@@ -124,6 +128,7 @@ export class ToolExecutionError extends Error {
124
128
  return {
125
129
  name: this.name,
126
130
  message: this.message,
131
+ trace: this.stack,
127
132
  };
128
133
  }
129
134
  }
@@ -143,16 +148,20 @@ export function createToolCapability<
143
148
  I extends JSONValue,
144
149
  O extends JSONValue = JSONValue,
145
150
  >(key: string, config: ToolConfiguration<I, O>) {
146
- validateRequiredProperties(config.schema as AnyJSONSchema);
147
- if (config.outputSchema) {
148
- validateRequiredProperties(config.outputSchema as AnyJSONSchema);
151
+ const inputSchema = getSchema(config.schema);
152
+ validateRequiredProperties(inputSchema as AnyJSONSchema);
153
+
154
+ const outputSchema = config.outputSchema
155
+ ? getSchema(config.outputSchema)
156
+ : undefined;
157
+
158
+ if (outputSchema) {
159
+ validateRequiredProperties(outputSchema as AnyJSONSchema);
149
160
  }
150
161
 
151
162
  const ajv = new Ajv();
152
- const validateInput = ajv.compile(config.schema);
153
- const validateOutput = config.outputSchema
154
- ? ajv.compile(config.outputSchema)
155
- : null;
163
+ const validateInput = ajv.compile(inputSchema);
164
+ const validateOutput = outputSchema ? ajv.compile(outputSchema) : null;
156
165
 
157
166
  async function handler(input: JSONValue, options: HandlerOptions): Promise<O>;
158
167
  async function handler(input: JSONValue): Promise<
@@ -162,10 +171,7 @@ export function createToolCapability<
162
171
  }
163
172
  | {
164
173
  _tag: "error";
165
- error:
166
- | InvalidToolInputError
167
- | InvalidToolOutputError
168
- | ToolExecutionError;
174
+ error: { name: string; message: string; trace: string | undefined };
169
175
  }
170
176
  >;
171
177
  async function handler(
@@ -179,10 +185,7 @@ export function createToolCapability<
179
185
  }
180
186
  | {
181
187
  _tag: "error";
182
- error:
183
- | InvalidToolInputError
184
- | InvalidToolOutputError
185
- | ToolExecutionError;
188
+ error: { name: string; message: string; trace: string | undefined };
186
189
  }
187
190
  > {
188
191
  if (!validateInput(input)) {
@@ -203,7 +206,7 @@ export function createToolCapability<
203
206
 
204
207
  const result = {
205
208
  _tag: "error" as const,
206
- error,
209
+ error: { name: error.name, message: error.message, trace: error.stack },
207
210
  };
208
211
 
209
212
  process.stdout.write(`\n<output>${JSON.stringify(result)}</output>\n`);
@@ -225,9 +228,11 @@ export function createToolCapability<
225
228
 
226
229
  const result = {
227
230
  _tag: "error" as const,
228
- error: new InvalidToolOutputError(
229
- JSON.stringify(validateOutput.errors, null, 2),
230
- ),
231
+ error: {
232
+ name: error.name,
233
+ message: error.message,
234
+ trace: error.stack,
235
+ },
231
236
  };
232
237
 
233
238
  process.stdout.write(`\n<output>${JSON.stringify(result)}</output>\n`);
@@ -239,7 +244,9 @@ export function createToolCapability<
239
244
  return result;
240
245
  }
241
246
 
242
- process.stdout.write(`\n<output>${JSON.stringify(result)}</output>\n`);
247
+ process.stdout.write(
248
+ `\n<output>${JSON.stringify({ _tag: "success", value: result })}</output>\n`,
249
+ );
243
250
 
244
251
  return {
245
252
  _tag: "success" as const,
@@ -256,7 +263,7 @@ export function createToolCapability<
256
263
 
257
264
  const result = {
258
265
  _tag: "error" as const,
259
- error,
266
+ error: { name: error.name, message: error.message, trace: error.stack },
260
267
  };
261
268
 
262
269
  process.stdout.write(`\n<output>${JSON.stringify(result)}</output>\n`);
@@ -271,8 +278,8 @@ export function createToolCapability<
271
278
  config: {
272
279
  title: config.title,
273
280
  description: config.description,
274
- schema: config.schema,
275
- outputSchema: config.outputSchema,
281
+ schema: inputSchema,
282
+ outputSchema: outputSchema,
276
283
  },
277
284
  handler,
278
285
  };
package/src/index.ts CHANGED
@@ -23,6 +23,8 @@ export type {
23
23
  } from "./capabilities/sync.js";
24
24
  export type { ToolCapability, ToolConfiguration } from "./capabilities/tool.js";
25
25
  export type { AnyJSONSchema, JSONSchema } from "./json-schema.js";
26
+ export type { Infer, SchemaBuilder } from "./schema-builder.js";
27
+ export { getSchema, j } from "./schema-builder.js";
26
28
  export type {
27
29
  Icon,
28
30
  ImageIcon,