@jskit-ai/assistant-core 0.2.39 → 0.2.41

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/CHANGELOG.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - `assistantConversationListItemOutputValidator` (from `@jskit-ai/assistant-core/shared`) is one conversation of `assistantContract`'s `conversationsList`, the record without its `metadata`; the contract's list output is built from it, and `@jskit-ai/assistant-runtime`'s conversations list route serializes its items with it.
5
6
  - `createConversationStorage`'s `write` saves nothing when its callback called no write method: the adapter's `writeRecord` does not run, so file storage no longer rewrites and flushes the whole conversation file, or creates an empty one for a missing conversation, when a write only looked (a delivery check, a message already saved), and the saved record stays cached.
6
7
  - Checking or recovering one message's delivery (`inspectDelivery`, and a send that finds its message already saved) finds that message's turn by its id instead of reading and cloning every turn of the conversation to find it.
7
8
  - With completed envelopes, a streamed turn's saved completed-envelope marker is read once per turn instead of once per delta: the marker is written with the turn's authored message and never changes, and a reply's hundreds of deltas each opened a storage read. A turn not saved yet is read again until it is.
@@ -8,14 +8,14 @@
8
8
  "build": "vite build"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/assistant-core": "0.2.39",
12
- "@jskit-ai/http-runtime": "0.2.18",
13
- "@jskit-ai/kernel": "0.2.18",
11
+ "@jskit-ai/assistant-core": "0.2.41",
12
+ "@jskit-ai/http-runtime": "0.2.19",
13
+ "@jskit-ai/kernel": "0.2.19",
14
14
  "@jskit-ai/material-web": "0.1.5",
15
15
  "vue": "^3.5.13",
16
16
  "vuetify": "^4.0.0",
17
- "@jskit-ai/connectors-core": "0.2.25",
18
- "@jskit-ai/connectors-catalog": "0.2.25"
17
+ "@jskit-ai/connectors-core": "0.2.27",
18
+ "@jskit-ai/connectors-catalog": "0.2.27"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@vitejs/plugin-vue": "^6.0.7",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant-core",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "jskit test"
@@ -52,7 +52,7 @@
52
52
  "@ai-sdk/togetherai": "^3.0.49",
53
53
  "@ai-sdk/xai": "^4.0.58",
54
54
  "@jskit-ai/agent": "0.1.3",
55
- "@jskit-ai/resource-core": "0.1.209",
55
+ "@jskit-ai/resource-core": "0.1.210",
56
56
  "@openrouter/ai-sdk-provider": "^3.0.0",
57
57
  "ai": "^7.0.152",
58
58
  "json-rest-schema": "^1.0.20",
@@ -60,8 +60,8 @@
60
60
  "ws": "^8.20.1"
61
61
  },
62
62
  "peerDependencies": {
63
- "@jskit-ai/http-runtime": "0.2.18",
64
- "@jskit-ai/kernel": "0.2.18",
63
+ "@jskit-ai/http-runtime": "0.2.19",
64
+ "@jskit-ai/kernel": "0.2.19",
65
65
  "vue": "^3.5.13",
66
66
  "vuetify": "^4.3.0"
67
67
  },
@@ -272,6 +272,8 @@ const conversationMessagesListOutputSchema = createSchema({
272
272
  });
273
273
 
274
274
  const assistantConversationOutputValidator = createSchemaDefinition(conversationRecordSchema, "replace");
275
+ // One conversation of a list, as the conversations list route answers each item: the record without its metadata.
276
+ const assistantConversationListItemOutputValidator = createSchemaDefinition(conversationListItemSchema, "replace");
275
277
 
276
278
  const assistantContract = defineApiContract({
277
279
  namespace: "assistant",
@@ -283,7 +285,7 @@ const assistantContract = defineApiContract({
283
285
  conversationsList: {
284
286
  method: "GET",
285
287
  query: conversationsListQuerySchema,
286
- output: createCursorListValidator(createSchemaDefinition(conversationListItemSchema, "replace"))
288
+ output: createCursorListValidator(assistantConversationListItemOutputValidator)
287
289
  },
288
290
  conversationMessagesList: {
289
291
  method: "GET",
@@ -299,5 +301,6 @@ export {
299
301
  MAX_MESSAGE_PAGE_SIZE,
300
302
  attachmentIdsField,
301
303
  assistantContract,
304
+ assistantConversationListItemOutputValidator,
302
305
  assistantConversationOutputValidator
303
306
  };
@@ -30,6 +30,7 @@ export {
30
30
  MAX_MESSAGE_PAGE_SIZE,
31
31
  attachmentIdsField,
32
32
  assistantContract,
33
+ assistantConversationListItemOutputValidator,
33
34
  assistantConversationOutputValidator
34
35
  } from "./assistantResource.js";
35
36
 
@@ -3,6 +3,7 @@ import test from "node:test";
3
3
  import { createSchema } from "json-rest-schema";
4
4
 
5
5
  import { createActionCatalogue } from "@jskit-ai/kernel/server/actions";
6
+ import { createValidationError } from "@jskit-ai/kernel/server/runtime/errors";
6
7
  import { createServiceToolCatalog } from "../src/server/lib/serviceToolCatalog.js";
7
8
 
8
9
  function schema(fields = {}, mode = "patch") {
@@ -29,6 +30,7 @@ function action({
29
30
  input = schema({}),
30
31
  output = schema({ ok: { type: "boolean", required: true } }),
31
32
  extensions = {},
33
+ checkInput = null,
32
34
  execute = async () => ({ ok: true })
33
35
  } = {}) {
34
36
  return {
@@ -41,6 +43,7 @@ function action({
41
43
  input,
42
44
  output,
43
45
  extensions,
46
+ checkInput,
44
47
  execute
45
48
  };
46
49
  }
@@ -270,6 +273,48 @@ test("assistant tools expose safe field-level input guidance to the model", asyn
270
273
  });
271
274
  });
272
275
 
276
+ test("assistant tools give the model the field and the type to use when an action's own input check refuses a key", async () => {
277
+ let executed = false;
278
+ const actions = createActions([action({
279
+ input: schema({ fields: { type: "object", required: false, additionalProperties: true } }),
280
+ // A check of what the schema cannot judge, such as a fieldset key only the application's tables can name.
281
+ checkInput(input) {
282
+ if (input.fields?.pet) {
283
+ throw createValidationError({
284
+ "fields.pet": {
285
+ code: "demo.fieldsKeys",
286
+ params: { type: "pets" },
287
+ message: "fields keys must be resource types. Allowed keys: \"books\", \"pets\"; use \"pets\" instead of \"pet\"."
288
+ }
289
+ });
290
+ }
291
+ },
292
+ execute: async () => {
293
+ executed = true;
294
+ return { ok: true };
295
+ }
296
+ })]);
297
+ const catalog = createServiceToolCatalog(actions);
298
+ const context = { actor: { id: "7" }, surface: "admin" };
299
+ const toolSet = catalog.resolveToolSet(context);
300
+
301
+ assert.deepEqual(await catalog.executeToolCall({
302
+ toolName: toolSet.tools[0].name,
303
+ argumentsText: JSON.stringify({ fields: { pet: ["name"] } }),
304
+ context,
305
+ toolSet
306
+ }), {
307
+ ok: false,
308
+ error: {
309
+ code: "ACTION_VALIDATION_FAILED",
310
+ message: "Validation failed. fields.pet: fields keys must be resource types. Allowed keys: \"books\", \"pets\"; " +
311
+ "use \"pets\" instead of \"pet\".",
312
+ status: 400
313
+ }
314
+ });
315
+ assert.equal(executed, false);
316
+ });
317
+
273
318
  test("large authorized catalogs use compact paged discovery, exact contracts, and gated execution", async () => {
274
319
  const executions = [];
275
320
  let clockExecution = null;