@ixo/oracle-runtime-workers 0.29.0 → 0.29.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ixo/oracle-runtime-workers",
3
- "version": "0.29.0",
3
+ "version": "0.29.1",
4
4
  "description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -65,9 +65,9 @@
65
65
  "@ixo/impactxclient-sdk": "^3.0.0",
66
66
  "protobufjs": "^7.6.5",
67
67
  "@ixo/topic-protocol": "1.0.0-rc.8",
68
- "@ixo/ixo-transaction": "0.2.0",
69
68
  "@ixo/common": "1.7.1",
70
- "@ixo/ucan": "2.3.0"
69
+ "@ixo/ucan": "2.3.0",
70
+ "@ixo/ixo-transaction": "0.2.0"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@cloudflare/vitest-pool-workers": "^0.22.0",
@@ -257,34 +257,70 @@ const memoSchema = z
257
257
  .describe('An optional note shown to the owner and kept on chain.');
258
258
 
259
259
  /**
260
- * The schema the model is given: constant in size whatever the catalogue
261
- * holds (a type adds one enum value). Each message's own fields are
262
- * checked by the handler against its catalogue schema.
260
+ * A catalogue field reduced to its basic type: no descriptions, patterns or
261
+ * limits (those stay in describe_chain_messages and the handler's check).
263
262
  */
264
- export function chainTransactionSchemaFor<
265
- const T extends readonly [string, ...string[]],
266
- >(types: T) {
263
+ export function fieldsOnly(schema: z.core.$ZodType): z.ZodType {
264
+ if (schema instanceof z.ZodObject)
265
+ return z.strictObject(
266
+ Object.fromEntries(
267
+ Object.entries(schema.shape).map(([key, field]) => [
268
+ key,
269
+ fieldsOnly(field),
270
+ ]),
271
+ ),
272
+ );
273
+ if (schema instanceof z.ZodOptional)
274
+ return fieldsOnly(schema.unwrap()).optional();
275
+ if (schema instanceof z.ZodArray) return z.array(fieldsOnly(schema.element));
276
+ if (schema instanceof z.ZodString) return z.string();
277
+ if (schema instanceof z.ZodNumber) return z.number();
278
+ if (schema instanceof z.ZodBoolean) return z.boolean();
279
+ if (schema instanceof z.ZodLiteral) return z.literal([...schema.values]);
280
+ throw new Error(
281
+ `No model-facing form for a ${schema._zod.def.type} field in the chain transaction catalogue`,
282
+ );
283
+ }
284
+
285
+ /**
286
+ * One message type as the model is given it: its field names and basic
287
+ * types, so a provider that holds a tool call to its schema sends every
288
+ * field. Loose, so a field the type does not have reaches the handler,
289
+ * which names it.
290
+ */
291
+ function messageFieldsOnly(schema: z.ZodObject) {
292
+ return z.looseObject(
293
+ Object.fromEntries(
294
+ Object.entries(schema.shape).map(([key, field]) => [
295
+ key,
296
+ fieldsOnly(field),
297
+ ]),
298
+ ),
299
+ );
300
+ }
301
+
302
+ /**
303
+ * The schema the model is given: per type, its fields (messageFieldsOnly),
304
+ * so a type adds its short field list and the rules are fetched with
305
+ * describe_chain_messages when needed. Each message is then checked by the
306
+ * handler against its full catalogue schema.
307
+ */
308
+ export function chainTransactionSchemaFor(
309
+ catalog: Readonly<Record<string, { schema: z.ZodObject }>>,
310
+ ) {
311
+ const [first, ...rest] = Object.values(catalog).map((entry) =>
312
+ messageFieldsOnly(entry.schema),
313
+ );
314
+ if (!first) throw new Error('The chain transaction catalogue is empty');
267
315
  return z.strictObject({
268
316
  messages: z
269
- .array(
270
- z
271
- .object({
272
- type: z
273
- .enum(types)
274
- .describe(
275
- 'The message type; describe_chain_messages lists its fields.',
276
- ),
277
- })
278
- .catchall(z.unknown()),
279
- )
317
+ .array(z.discriminatedUnion('type', [first, ...rest]))
280
318
  .min(1)
281
319
  .max(CHAIN_TX_MAX_MESSAGES),
282
320
  memo: memoSchema,
283
321
  });
284
322
  }
285
- const chainTransactionSchema = chainTransactionSchemaFor(
286
- CHAIN_TX_MESSAGE_TYPES,
287
- );
323
+ const chainTransactionSchema = chainTransactionSchemaFor(CHAIN_TX_CATALOG);
288
324
 
289
325
  /**
290
326
  * The request tool's description: what it does, the discovery step, one
@@ -511,22 +547,35 @@ async function requestChainTransaction(
511
547
  /**
512
548
  * Each message against its type's catalogue schema (strict: a field the
513
549
  * type does not have, the owner's identity included, is refused). The
514
- * model-facing schema only checks the type, so this is where a message's
515
- * fields are checked, on a model turn as on a direct call.
550
+ * model-facing schema only names the fields and their basic types, so this
551
+ * is where a message's formats and limits are checked, on a model turn as on
552
+ * a direct call.
516
553
  */
517
554
  function strictChainTxMessages(
518
- messages: readonly ({ type: ChainTxMessageType } & Record<string, unknown>)[],
555
+ messages: readonly unknown[],
519
556
  ): { messages: ChainTxMessageInput[] } | { issues: string[] } {
520
557
  const checked: ChainTxMessageInput[] = [];
521
558
  const issues: string[] = [];
522
559
  messages.forEach((message, index) => {
523
- const result = CHAIN_TX_CATALOG[message.type].schema.safeParse(message);
560
+ const type = chainTxTypeOf(message);
561
+ if (!type) {
562
+ issues.push(`messages[${index}].type: not a known message type`);
563
+ return;
564
+ }
565
+ const result = CHAIN_TX_CATALOG[type].schema.safeParse(message);
524
566
  if (result.success) checked.push(result.data);
525
567
  else issues.push(...issuesOf(result.error, ['messages', index]));
526
568
  });
527
569
  return issues.length ? { issues } : { messages: checked };
528
570
  }
529
571
 
572
+ function chainTxTypeOf(message: unknown): ChainTxMessageType | null {
573
+ if (typeof message !== 'object' || message === null || !('type' in message))
574
+ return null;
575
+ const { type } = message;
576
+ return CHAIN_TX_MESSAGE_TYPES.find((known) => known === type) ?? null;
577
+ }
578
+
530
579
  function chainTxShapeRefusal(
531
580
  issues: readonly string[],
532
581
  ): HubActionRequestResult {