@collegium/sdk 0.0.1-beta.8 → 0.0.1-beta.9
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/README.md +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +111 -8
- package/dist/{tool-CN2DS668.d.ts → tool-Vqe6D48m.d.ts} +51 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,10 +38,10 @@ export default defineTool({
|
|
|
38
38
|
approval: (args) => ({ body: `save contact "${args.id}": ${args.name}`, presentation: 'verbatim' }),
|
|
39
39
|
description: 'Save or update a contact.',
|
|
40
40
|
execute: async (args, { err, settings, storage }) => {
|
|
41
|
-
if ((await storage.contacts.
|
|
41
|
+
if ((await storage.contacts.findMany()).length >= settings.maxContacts) {
|
|
42
42
|
err.invalidArguments('contact limit reached; delete one first');
|
|
43
43
|
}
|
|
44
|
-
await storage.contacts.
|
|
44
|
+
await storage.contacts.create({ email: args.email, id: args.id, name: args.name });
|
|
45
45
|
return `contact ${args.id} saved`;
|
|
46
46
|
},
|
|
47
47
|
parameters: z.object({ email: z.email(), id: z.string().min(1), name: z.string().min(1) })
|
|
@@ -50,6 +50,8 @@ export default defineTool({
|
|
|
50
50
|
|
|
51
51
|
A tool with `approval` always stops for a human, who sees the full payload before it runs; one without never gates. The channel and the trace disclose both, line by line. `execute` returns the text the model reads, and raises the two failures a tool controls through `err`: `invalidArguments` continues the turn, `unresolved` ends it as an unconfirmed side effect.
|
|
52
52
|
|
|
53
|
+
**Storage.** Each declared collection is a set of records: the schema's output plus `id`, `createdAt`, and `updatedAt`, which the store stamps. The handle has `create`, `findMany`, `findById`, `updateById`, and `deleteById`. `create` takes the schema's input with an optional `id`, minting a cuid2 when none is given. `findMany` with no argument lists everything; with a `where` over the schema's top-level scalar fields and `id` — a value for equality, `{ in: [...] }` for membership, `{ contains: text }` for a case-insensitive substring on a string — and an optional `limit`, it filters. Field names and value types come from the schema, so a bad query does not compile.
|
|
54
|
+
|
|
53
55
|
**Testing.** `@collegium/sdk/testing` builds the context `execute` receives, over in-memory storage that validates and parses as the deployment's store does. Pass your config; settings go through your schema, so defaults apply.
|
|
54
56
|
|
|
55
57
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { c as ToolTurnScope, d as defineConfig, i as defineTool, n as ToolContext, o as ToolApprovalPayload, s as ToolDisclosure, t as PluginTool, u as Register } from "./tool-
|
|
1
|
+
import { c as ToolTurnScope, d as defineConfig, i as defineTool, n as ToolContext, o as ToolApprovalPayload, s as ToolDisclosure, t as PluginTool, u as Register } from "./tool-Vqe6D48m.js";
|
|
2
2
|
export { type PluginTool, type Register, type ToolApprovalPayload, type ToolContext, type ToolDisclosure, type ToolTurnScope, defineConfig, defineTool };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as PluginToolFailureError, c as ToolTurnScope, l as PluginConfig, r as ToolContextFor } from "./tool-
|
|
1
|
+
import { a as PluginToolFailureError, c as ToolTurnScope, l as PluginConfig, r as ToolContextFor } from "./tool-Vqe6D48m.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/testing.d.ts
|
|
4
4
|
/** `settings` as the declared schema accepts them, so defaults apply as they do at boot; `turn` overrides the four facts */
|
package/dist/testing.js
CHANGED
|
@@ -8,6 +8,82 @@ var PluginToolFailureError = class extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
//#endregion
|
|
11
|
+
//#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a given value is a plain object.
|
|
14
|
+
*
|
|
15
|
+
* @param value - The value to check.
|
|
16
|
+
* @returns True if the value is a plain object, otherwise false.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // ✅👇 True
|
|
21
|
+
*
|
|
22
|
+
* isPlainObject({ }); // ✅
|
|
23
|
+
* isPlainObject({ key: 'value' }); // ✅
|
|
24
|
+
* isPlainObject({ key: new Date() }); // ✅
|
|
25
|
+
* isPlainObject(new Object()); // ✅
|
|
26
|
+
* isPlainObject(Object.create(null)); // ✅
|
|
27
|
+
* isPlainObject({ nested: { key: true} }); // ✅
|
|
28
|
+
* isPlainObject(new Proxy({}, {})); // ✅
|
|
29
|
+
* isPlainObject({ [Symbol('tag')]: 'A' }); // ✅
|
|
30
|
+
*
|
|
31
|
+
* // ✅👇 (cross-realms, node context, workers, ...)
|
|
32
|
+
* const runInNewContext = await import('node:vm').then(
|
|
33
|
+
* (mod) => mod.runInNewContext
|
|
34
|
+
* );
|
|
35
|
+
* isPlainObject(runInNewContext('({})')); // ✅
|
|
36
|
+
*
|
|
37
|
+
* // ❌👇 False
|
|
38
|
+
*
|
|
39
|
+
* class Test { };
|
|
40
|
+
* isPlainObject(new Test()) // ❌
|
|
41
|
+
* isPlainObject(10); // ❌
|
|
42
|
+
* isPlainObject(null); // ❌
|
|
43
|
+
* isPlainObject('hello'); // ❌
|
|
44
|
+
* isPlainObject([]); // ❌
|
|
45
|
+
* isPlainObject(new Date()); // ❌
|
|
46
|
+
* isPlainObject(new Uint8Array([1])); // ❌
|
|
47
|
+
* isPlainObject(Buffer.from('ABC')); // ❌
|
|
48
|
+
* isPlainObject(Promise.resolve({})); // ❌
|
|
49
|
+
* isPlainObject(Object.create({})); // ❌
|
|
50
|
+
* isPlainObject(new (class Cls {})); // ❌
|
|
51
|
+
* isPlainObject(globalThis); // ❌,
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
function isPlainObject(value) {
|
|
55
|
+
if (!value || typeof value !== "object") return false;
|
|
56
|
+
const proto = Object.getPrototypeOf(value);
|
|
57
|
+
if (!(proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null)) return false;
|
|
58
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region ../core/dist/toolsets/storage/collection-query.utils.js
|
|
62
|
+
/** folds ASCII letters only, matching SQLite's `lower()` without ICU, so the store and this evaluator agree on every string */
|
|
63
|
+
const foldAsciiCase = (text) => text.replace(/[A-Z]+/g, (upper) => upper.toLowerCase());
|
|
64
|
+
const readField = (record, field) => isPlainObject(record) ? record[field] : void 0;
|
|
65
|
+
const matchesScalar = (actual, expected) => expected === null ? actual === null || actual === void 0 : actual === expected;
|
|
66
|
+
const matchesCondition = (actual, condition) => {
|
|
67
|
+
if (condition === null || typeof condition !== "object") return matchesScalar(actual, condition);
|
|
68
|
+
if ("in" in condition) return condition.in.some((candidate) => matchesScalar(actual, candidate));
|
|
69
|
+
return typeof actual === "string" && foldAsciiCase(actual).includes(foldAsciiCase(condition.contains));
|
|
70
|
+
};
|
|
71
|
+
const readConditions = (where) => {
|
|
72
|
+
const conditions = [];
|
|
73
|
+
for (const [field, condition] of Object.entries(where ?? {})) if (condition !== void 0) conditions.push([field, condition]);
|
|
74
|
+
return conditions;
|
|
75
|
+
};
|
|
76
|
+
/** the stated conditions of a query, by field, an undefined one dropped as unstated */
|
|
77
|
+
function collectionQueryConditions(query) {
|
|
78
|
+
return readConditions(query.where);
|
|
79
|
+
}
|
|
80
|
+
/** the query over records already in memory, in their own order — the semantics the store's compiled form must reproduce */
|
|
81
|
+
function applyCollectionQuery(records, query) {
|
|
82
|
+
const conditions = collectionQueryConditions(query);
|
|
83
|
+
const matching = records.filter((record) => conditions.every(([field, condition]) => matchesCondition(readField(record, field), condition)));
|
|
84
|
+
return query.limit === void 0 ? matching : matching.slice(0, query.limit);
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
11
87
|
//#region ../core/dist/plugins/plugins.utils.js
|
|
12
88
|
/** what a plugin tool receives as `err`; shared with the SDK's testing entry, so a test raises the failure a deployment would */
|
|
13
89
|
const PLUGIN_TOOL_ERR = {
|
|
@@ -32,17 +108,44 @@ const DEFAULT_TURN = {
|
|
|
32
108
|
triggeringPostId: null,
|
|
33
109
|
turnId: "test-turn"
|
|
34
110
|
};
|
|
111
|
+
/** the deployment's store mints a cuid2; here any unique string serves, and a test that needs a known id passes one */
|
|
35
112
|
function createCollection(schema) {
|
|
36
113
|
const rows = /* @__PURE__ */ new Map();
|
|
114
|
+
const toRecord = ({ createdAt, id, updatedAt, ...value }) => ({
|
|
115
|
+
...schema.parse(value),
|
|
116
|
+
createdAt,
|
|
117
|
+
id,
|
|
118
|
+
updatedAt
|
|
119
|
+
});
|
|
37
120
|
return {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
121
|
+
create: ({ id = crypto.randomUUID(), ...data }) => Promise.try(() => {
|
|
122
|
+
if (rows.has(id)) throw new Error(`storage collection already holds a record with id "${id}"`);
|
|
123
|
+
const now = /* @__PURE__ */ new Date();
|
|
124
|
+
rows.set(id, {
|
|
125
|
+
...schema.parse(data),
|
|
126
|
+
createdAt: now,
|
|
127
|
+
id,
|
|
128
|
+
updatedAt: now
|
|
129
|
+
});
|
|
130
|
+
return toRecord(rows.get(id));
|
|
131
|
+
}),
|
|
132
|
+
deleteById: (id) => Promise.try(() => rows.delete(id)),
|
|
133
|
+
findById: (id) => Promise.try(() => rows.has(id) ? toRecord(rows.get(id)) : null),
|
|
134
|
+
findMany: (query = {}) => Promise.try(() => applyCollectionQuery([...rows.values()].map(toRecord), query)),
|
|
135
|
+
updateById: (id, patch) => Promise.try(() => {
|
|
136
|
+
const row = rows.get(id);
|
|
137
|
+
if (row === void 0) return null;
|
|
138
|
+
const { createdAt, id: _id, updatedAt: _updatedAt, ...value } = row;
|
|
139
|
+
rows.set(id, {
|
|
140
|
+
...schema.parse({
|
|
141
|
+
...schema.parse(value),
|
|
142
|
+
...patch
|
|
143
|
+
}),
|
|
144
|
+
createdAt,
|
|
145
|
+
id,
|
|
146
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
147
|
+
});
|
|
148
|
+
return toRecord(rows.get(id));
|
|
46
149
|
})
|
|
47
150
|
};
|
|
48
151
|
}
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { Promisable } from "type-fest";
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
type CollectionsDeclaration$1 = {
|
|
5
|
-
readonly [key: string]: z.
|
|
5
|
+
readonly [key: string]: z.ZodObject;
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
8
|
* The config file's default export: the settings schema agents are configured by, and the storage
|
|
@@ -143,6 +143,54 @@ type PluginToolDeclaration<TContext, TParams extends z.ZodType> = {
|
|
|
143
143
|
traceDetail?(args: z.infer<TParams>): string;
|
|
144
144
|
};
|
|
145
145
|
//#endregion
|
|
146
|
+
//#region ../core/src/toolsets/storage/collection-query.types.d.ts
|
|
147
|
+
/** the JSON scalars a query may compare; an object, array, or date field is not queryable */
|
|
148
|
+
type Scalar = boolean | null | number | string;
|
|
149
|
+
/**
|
|
150
|
+
* The top-level scalar fields of a record, each stripped of the undefined an optional field
|
|
151
|
+
* carries. The intersection with the scalar union changes nothing for a concrete record and is what
|
|
152
|
+
* lets the compiler see, over a generic one, that every condition is a scalar condition.
|
|
153
|
+
*/
|
|
154
|
+
type QueryableFields<TRecord> = { readonly [K in keyof TRecord as Exclude<TRecord[K], undefined> extends Scalar ? K : never]-?: Exclude<TRecord[K], undefined> & Scalar; };
|
|
155
|
+
/** one field's condition: equality, membership, or — on a string field — a case-insensitive substring */
|
|
156
|
+
type CollectionFieldCondition<TField> = ([Extract<TField, string>] extends [never] ? never : {
|
|
157
|
+
readonly contains: string;
|
|
158
|
+
}) | TField | {
|
|
159
|
+
readonly in: readonly TField[];
|
|
160
|
+
};
|
|
161
|
+
/** an AND over field conditions, keyed by the record's own scalar fields */
|
|
162
|
+
type CollectionWhere<TRecord> = { readonly [K in keyof QueryableFields<TRecord>]?: CollectionFieldCondition<QueryableFields<TRecord>[K]>; };
|
|
163
|
+
type CollectionQuery<TRecord> = {
|
|
164
|
+
readonly limit?: number;
|
|
165
|
+
readonly where?: CollectionWhere<TRecord>;
|
|
166
|
+
};
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region ../core/src/toolsets/storage/collection.types.d.ts
|
|
169
|
+
/** what the store stamps on every record beside the declared fields; a schema may not declare any of these */
|
|
170
|
+
type CollectionRecordStamp = {
|
|
171
|
+
readonly createdAt: Date;
|
|
172
|
+
readonly id: string;
|
|
173
|
+
readonly updatedAt: Date;
|
|
174
|
+
};
|
|
175
|
+
type CollectionRecord<TValue> = CollectionRecordStamp & TValue;
|
|
176
|
+
/**
|
|
177
|
+
* A toolset-scoped handle over one declared storage collection. Every record is the schema's
|
|
178
|
+
* output plus the stamp; `create` parses through the schema, so defaults apply, and mints a
|
|
179
|
+
* cuid2 unless an id is given. Reads parse each stored value, so a row an older schema wrote
|
|
180
|
+
* fails loudly rather than leaking a stale shape into the tool.
|
|
181
|
+
*/
|
|
182
|
+
type ToolsetCollection<TSchema extends z.ZodObject> = {
|
|
183
|
+
create(data: z.input<TSchema> & {
|
|
184
|
+
readonly id?: string;
|
|
185
|
+
}): Promise<CollectionRecord<z.output<TSchema>>>;
|
|
186
|
+
deleteById(id: string): Promise<boolean>;
|
|
187
|
+
findById(id: string): Promise<CollectionRecord<z.output<TSchema>> | null>;
|
|
188
|
+
/** without a query, every record in insertion order */
|
|
189
|
+
findMany(query?: CollectionQuery<CollectionRecord<z.output<TSchema>>>): Promise<CollectionRecord<z.output<TSchema>>[]>;
|
|
190
|
+
/** the patch merged over the stored fields and the whole parsed again, so a patch cannot leave an invalid record */
|
|
191
|
+
updateById(id: string, patch: Partial<z.input<TSchema>>): Promise<CollectionRecord<z.output<TSchema>> | null>;
|
|
192
|
+
};
|
|
193
|
+
//#endregion
|
|
146
194
|
//#region ../core/src/toolsets/toolsets.types.d.ts
|
|
147
195
|
/** `settings`, `storage`, and `turn` are the context's own keys, so a service may not claim them */
|
|
148
196
|
type ServicesDeclaration = {
|
|
@@ -153,19 +201,9 @@ type ServicesDeclaration = {
|
|
|
153
201
|
readonly turn?: never;
|
|
154
202
|
};
|
|
155
203
|
type CollectionsDeclaration = {
|
|
156
|
-
readonly [key: string]: z.
|
|
204
|
+
readonly [key: string]: z.ZodObject;
|
|
157
205
|
};
|
|
158
206
|
type EmptyDeclaration = {};
|
|
159
|
-
/** a toolset-scoped handle over one declared storage collection; rows are validated on write and parsed on read */
|
|
160
|
-
type ToolsetCollection<TValue> = {
|
|
161
|
-
delete(key: string): Promise<boolean>;
|
|
162
|
-
get(key: string): Promise<null | TValue>;
|
|
163
|
-
list(): Promise<{
|
|
164
|
-
key: string;
|
|
165
|
-
value: TValue;
|
|
166
|
-
}[]>;
|
|
167
|
-
put(key: string, value: TValue): Promise<void>;
|
|
168
|
-
};
|
|
169
207
|
/**
|
|
170
208
|
* What `execute` receives, assembled from exactly what the toolset declared (§4): each service
|
|
171
209
|
* under its own name, `settings` and `storage` only when declared, and always the turn. Reaching
|
|
@@ -174,7 +212,7 @@ type ToolsetCollection<TValue> = {
|
|
|
174
212
|
type ToolsetContext<TServices extends ServicesDeclaration = EmptyDeclaration, TSettings extends undefined | z.ZodType = undefined, TCollections extends CollectionsDeclaration = EmptyDeclaration> = { readonly [K in keyof TServices]: TServices[K] extends ServiceToken<infer TInstance> ? TInstance : never; } & {
|
|
175
213
|
readonly turn: ToolTurnScope;
|
|
176
214
|
} & (keyof TCollections extends never ? unknown : {
|
|
177
|
-
readonly storage: { readonly [K in keyof TCollections]: ToolsetCollection<
|
|
215
|
+
readonly storage: { readonly [K in keyof TCollections]: ToolsetCollection<TCollections[K]>; };
|
|
178
216
|
}) & (TSettings extends z.ZodType ? {
|
|
179
217
|
readonly settings: z.infer<TSettings>;
|
|
180
218
|
} : unknown);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@collegium/sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.1-beta.
|
|
4
|
+
"version": "0.0.1-beta.9",
|
|
5
5
|
"description": "Write a Collegium plugin: declare a toolset with its tools, settings, storage, and skills.",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"homepage": "https://collegium.sh",
|