@anchrd/intel-contract 0.7.0 → 0.9.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.
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  export declare const IntelId: z.ZodString;
3
3
  export declare const IsoDateTime: z.ZodISODateTime;
4
+ export declare const UI_LANGUAGES: readonly ["en", "de", "es"];
5
+ export type UiLanguage = (typeof UI_LANGUAGES)[number];
4
6
  export declare const ProblemDetails: z.ZodObject<{
5
7
  type: z.ZodString;
6
8
  status: z.ZodNumber;
@@ -468,6 +470,41 @@ export declare const AgentReference: z.ZodObject<{
468
470
  }>;
469
471
  }, z.core.$strict>;
470
472
  export type AgentReference = z.infer<typeof AgentReference>;
473
+ /**
474
+ * Which kinds of node each role can actually be given (#255).
475
+ *
476
+ * ⚠️ Not every role takes every kind, and the reasons are about what the runtime DOES with a
477
+ * reference rather than about tidiness:
478
+ *
479
+ * `memory` is a folder because the agent WRITES there — `agent_remember` creates a note inside
480
+ * it. A single document as memory would mean the agent overwrites the document it was given.
481
+ *
482
+ * `semantic-context` is a folder because it is a search SPACE, searched per folder by
483
+ * `loop/scoped-search`. A single document is not a narrower search space; reading it whole is a
484
+ * different behaviour, and one that gets named before it is introduced, not slipped in.
485
+ *
486
+ * `system-message` reads single nodes already and takes a document or a table as well as a
487
+ * folder. A document is the natural case — a skill somebody wrote as ordinary text — and a table
488
+ * is the same read: the runtime asks intel for the node and prepends its content, which for a
489
+ * table is its CSV.
490
+ *
491
+ * ⚠️ `folder` stays on `system-message` although a folder carries no content of its own. Every
492
+ * definition written before #255 could only name folders, and taking the combination away here
493
+ * would refuse the next save of an agent that has been working for months — "existing definitions
494
+ * stay valid" is not only about reading them.
495
+ *
496
+ * ⚠️ This is the ONE place the rule lives. The screen offers what it says and the write path
497
+ * refuses what it forbids; a surface that made its own list would eventually disagree with the
498
+ * other, and the one that matters is whichever runs last.
499
+ */
500
+ export declare const AgentReferenceKinds: {
501
+ readonly "system-message": readonly ["folder", "document", "table"];
502
+ readonly "semantic-context": readonly ["folder"];
503
+ readonly memory: readonly ["folder"];
504
+ };
505
+ export declare function agentReferenceAccepts(role: AgentReferenceRole, kind: NodeKind): boolean;
506
+ /** The roles a node of this kind may be given — the same rule, read from the other side. */
507
+ export declare function agentReferenceRolesFor(kind: NodeKind): AgentReferenceRole[];
471
508
  export declare const AgentScheduleTarget: z.ZodObject<{
472
509
  kind: z.ZodEnum<{
473
510
  document: "document";
@@ -478,6 +515,7 @@ export declare const AgentScheduleTarget: z.ZodObject<{
478
515
  export type AgentScheduleTarget = z.infer<typeof AgentScheduleTarget>;
479
516
  export declare const AgentSchedule: z.ZodObject<{
480
517
  cron: z.ZodString;
518
+ timezone: z.ZodDefault<z.ZodString>;
481
519
  target: z.ZodObject<{
482
520
  kind: z.ZodEnum<{
483
521
  document: "document";
@@ -553,6 +591,7 @@ export declare const AgentDefinition: z.ZodObject<{
553
591
  }, z.core.$strict>>>;
554
592
  schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
555
593
  cron: z.ZodString;
594
+ timezone: z.ZodDefault<z.ZodString>;
556
595
  target: z.ZodObject<{
557
596
  kind: z.ZodEnum<{
558
597
  document: "document";
@@ -595,6 +634,7 @@ export declare const AgentDefinitionInput: z.ZodObject<{
595
634
  }, z.core.$strict>>>;
596
635
  schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
597
636
  cron: z.ZodString;
637
+ timezone: z.ZodDefault<z.ZodString>;
598
638
  target: z.ZodObject<{
599
639
  kind: z.ZodEnum<{
600
640
  document: "document";
@@ -629,6 +669,7 @@ export declare const SaveAgentDefinitionInput: z.ZodObject<{
629
669
  }, z.core.$strict>>>;
630
670
  schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
631
671
  cron: z.ZodString;
672
+ timezone: z.ZodDefault<z.ZodString>;
632
673
  target: z.ZodObject<{
633
674
  kind: z.ZodEnum<{
634
675
  document: "document";
@@ -667,6 +708,116 @@ export declare const RunAgentNowInput: z.ZodObject<{
667
708
  }, z.core.$strict>;
668
709
  }, z.core.$strict>;
669
710
  export type RunAgentNowInput = z.infer<typeof RunAgentNowInput>;
711
+ /**
712
+ * What one agent has actually cost, read out of Cloudflare's AI Gateway log (#251).
713
+ *
714
+ * ⚠️ Intel computes none of this from tokens and a price table. The gateway publishes the billed
715
+ * figure per call, and that figure is the debit from the Cloudflare balance 1:1 — Cloudflare takes
716
+ * its 5 % when the balance is loaded and passes inference through unchanged (measured 2026-08-07).
717
+ * A second, self-maintained answer beside it would be wrong on the day the two disagreed, and the
718
+ * wrong one would be the one on screen.
719
+ *
720
+ * ⚠️ `status` travels with the numbers and may never be dropped. `runs: []` means "cost nothing"
721
+ * only when `status` is `read`; under `not_configured` or `unreadable` it means "not known", and a
722
+ * screen that renders the two alike reports an outage as a saving.
723
+ */
724
+ export declare const AgentCostStatus: z.ZodEnum<{
725
+ read: "read";
726
+ not_configured: "not_configured";
727
+ unreadable: "unreadable";
728
+ }>;
729
+ export type AgentCostStatus = z.infer<typeof AgentCostStatus>;
730
+ export declare const AgentRunCost: z.ZodObject<{
731
+ runId: z.ZodString;
732
+ cost: z.ZodNumber;
733
+ calls: z.ZodNumber;
734
+ }, z.core.$strict>;
735
+ export type AgentRunCost = z.infer<typeof AgentRunCost>;
736
+ export declare const AgentCostWindow: z.ZodObject<{
737
+ days: z.ZodNumber;
738
+ cost: z.ZodNumber;
739
+ calls: z.ZodNumber;
740
+ models: z.ZodArray<z.ZodString>;
741
+ }, z.core.$strict>;
742
+ export type AgentCostWindow = z.infer<typeof AgentCostWindow>;
743
+ export declare const AgentCosts: z.ZodObject<{
744
+ status: z.ZodEnum<{
745
+ read: "read";
746
+ not_configured: "not_configured";
747
+ unreadable: "unreadable";
748
+ }>;
749
+ currency: z.ZodLiteral<"USD">;
750
+ runs: z.ZodArray<z.ZodObject<{
751
+ runId: z.ZodString;
752
+ cost: z.ZodNumber;
753
+ calls: z.ZodNumber;
754
+ }, z.core.$strict>>;
755
+ windows: z.ZodArray<z.ZodObject<{
756
+ days: z.ZodNumber;
757
+ cost: z.ZodNumber;
758
+ calls: z.ZodNumber;
759
+ models: z.ZodArray<z.ZodString>;
760
+ }, z.core.$strict>>;
761
+ partial: z.ZodBoolean;
762
+ }, z.core.$strict>;
763
+ export type AgentCosts = z.infer<typeof AgentCosts>;
764
+ /**
765
+ * What the models on offer cost and how much they hold (#257).
766
+ *
767
+ * ⚠️ `source` is per ENTRY and not per response, and that is not over-engineering. Cloudflare
768
+ * publishes figures for the models it serves itself and none at all for the Anthropic models it
769
+ * resells through Unified Billing — so a perfectly healthy read still leaves half the list on a
770
+ * written-out table, and one flag for the whole answer would call either the read stale or the
771
+ * table live.
772
+ */
773
+ export declare const ModelPrice: z.ZodObject<{
774
+ inputPerMillion: z.ZodNumber;
775
+ outputPerMillion: z.ZodNumber;
776
+ }, z.core.$strict>;
777
+ export type ModelPrice = z.infer<typeof ModelPrice>;
778
+ export declare const ModelCatalogEntry: z.ZodObject<{
779
+ provider: z.ZodEnum<{
780
+ "workers-ai": "workers-ai";
781
+ anthropic: "anthropic";
782
+ }>;
783
+ model: z.ZodString;
784
+ name: z.ZodString;
785
+ contextTokens: z.ZodNullable<z.ZodNumber>;
786
+ price: z.ZodNullable<z.ZodObject<{
787
+ inputPerMillion: z.ZodNumber;
788
+ outputPerMillion: z.ZodNumber;
789
+ }, z.core.$strict>>;
790
+ source: z.ZodEnum<{
791
+ cloudflare: "cloudflare";
792
+ builtin: "builtin";
793
+ }>;
794
+ }, z.core.$strict>;
795
+ export type ModelCatalogEntry = z.infer<typeof ModelCatalogEntry>;
796
+ export declare const ModelCatalog: z.ZodObject<{
797
+ entries: z.ZodArray<z.ZodObject<{
798
+ provider: z.ZodEnum<{
799
+ "workers-ai": "workers-ai";
800
+ anthropic: "anthropic";
801
+ }>;
802
+ model: z.ZodString;
803
+ name: z.ZodString;
804
+ contextTokens: z.ZodNullable<z.ZodNumber>;
805
+ price: z.ZodNullable<z.ZodObject<{
806
+ inputPerMillion: z.ZodNumber;
807
+ outputPerMillion: z.ZodNumber;
808
+ }, z.core.$strict>>;
809
+ source: z.ZodEnum<{
810
+ cloudflare: "cloudflare";
811
+ builtin: "builtin";
812
+ }>;
813
+ }, z.core.$strict>>;
814
+ liveStatus: z.ZodEnum<{
815
+ read: "read";
816
+ not_configured: "not_configured";
817
+ unreadable: "unreadable";
818
+ }>;
819
+ }, z.core.$strict>;
820
+ export type ModelCatalog = z.infer<typeof ModelCatalog>;
670
821
  export declare const ListAgentsInput: z.ZodObject<{
671
822
  parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
672
823
  includeArchived: z.ZodDefault<z.ZodBoolean>;
@@ -690,6 +841,7 @@ export declare const CreateAgentInput: z.ZodObject<{
690
841
  }, z.core.$strict>>>;
691
842
  schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
692
843
  cron: z.ZodString;
844
+ timezone: z.ZodDefault<z.ZodString>;
693
845
  target: z.ZodObject<{
694
846
  kind: z.ZodEnum<{
695
847
  document: "document";
@@ -759,6 +911,7 @@ export declare const NodeAgent: z.ZodObject<{
759
911
  }, z.core.$strict>>>;
760
912
  schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
761
913
  cron: z.ZodString;
914
+ timezone: z.ZodDefault<z.ZodString>;
762
915
  target: z.ZodObject<{
763
916
  kind: z.ZodEnum<{
764
917
  document: "document";
@@ -842,6 +995,7 @@ export declare const CreatedAgent: z.ZodObject<{
842
995
  }, z.core.$strict>>>;
843
996
  schedules: z.ZodDefault<z.ZodArray<z.ZodObject<{
844
997
  cron: z.ZodString;
998
+ timezone: z.ZodDefault<z.ZodString>;
845
999
  target: z.ZodObject<{
846
1000
  kind: z.ZodEnum<{
847
1001
  document: "document";
@@ -1,6 +1,14 @@
1
1
  import { z } from "zod";
2
2
  export const IntelId = z.string().min(1).max(128);
3
3
  export const IsoDateTime = z.iso.datetime({ offset: true });
4
+ // The languages whose UI catalog ships inside intel-ui. They live here rather than only in the UI
5
+ // because `intel build` has to know them: a built-in language may be chosen as `ui.defaultLanguage`
6
+ // without the customer listing a copy under `ui.languages` that would rot at every UI update.
7
+ //
8
+ // ⚠️ The list is a claim about the contents of packages/ui/src/i18n and cannot be checked here.
9
+ // `i18n.unit.ts` compares it against the catalogs actually built in; a language listed here without
10
+ // a file gives a red run there instead of a UI that starts on a catalog which does not exist.
11
+ export const UI_LANGUAGES = ["en", "de", "es"];
4
12
  export const ProblemDetails = z.strictObject({
5
13
  type: z.string(),
6
14
  status: z.number().int().min(400).max(599),
@@ -273,6 +281,45 @@ export const AgentMediaType = "application/vnd.anchrd.agent+json";
273
281
  // memory write target — ordinary Knowledge, versioned and readable like everything else
274
282
  export const AgentReferenceRole = z.enum(["system-message", "semantic-context", "memory"]);
275
283
  export const AgentReference = z.strictObject({ nodeId: IntelId, role: AgentReferenceRole });
284
+ /**
285
+ * Which kinds of node each role can actually be given (#255).
286
+ *
287
+ * ⚠️ Not every role takes every kind, and the reasons are about what the runtime DOES with a
288
+ * reference rather than about tidiness:
289
+ *
290
+ * `memory` is a folder because the agent WRITES there — `agent_remember` creates a note inside
291
+ * it. A single document as memory would mean the agent overwrites the document it was given.
292
+ *
293
+ * `semantic-context` is a folder because it is a search SPACE, searched per folder by
294
+ * `loop/scoped-search`. A single document is not a narrower search space; reading it whole is a
295
+ * different behaviour, and one that gets named before it is introduced, not slipped in.
296
+ *
297
+ * `system-message` reads single nodes already and takes a document or a table as well as a
298
+ * folder. A document is the natural case — a skill somebody wrote as ordinary text — and a table
299
+ * is the same read: the runtime asks intel for the node and prepends its content, which for a
300
+ * table is its CSV.
301
+ *
302
+ * ⚠️ `folder` stays on `system-message` although a folder carries no content of its own. Every
303
+ * definition written before #255 could only name folders, and taking the combination away here
304
+ * would refuse the next save of an agent that has been working for months — "existing definitions
305
+ * stay valid" is not only about reading them.
306
+ *
307
+ * ⚠️ This is the ONE place the rule lives. The screen offers what it says and the write path
308
+ * refuses what it forbids; a surface that made its own list would eventually disagree with the
309
+ * other, and the one that matters is whichever runs last.
310
+ */
311
+ export const AgentReferenceKinds = {
312
+ "system-message": ["folder", "document", "table"],
313
+ "semantic-context": ["folder"],
314
+ memory: ["folder"],
315
+ };
316
+ export function agentReferenceAccepts(role, kind) {
317
+ return AgentReferenceKinds[role].includes(kind);
318
+ }
319
+ /** The roles a node of this kind may be given — the same rule, read from the other side. */
320
+ export function agentReferenceRolesFor(kind) {
321
+ return AgentReferenceRole.options.filter((role) => agentReferenceAccepts(role, kind));
322
+ }
276
323
  // A `document` target means the content of that document is the instruction — a "skill" somebody
277
324
  // wrote as ordinary text; a `flow` target means a run is started through Intel MCP and worked step
278
325
  // by step. Both are references, so nothing in here goes stale (ADR-0005 §4).
@@ -283,8 +330,42 @@ export const AgentScheduleTarget = z.strictObject({
283
330
  kind: z.enum(["document", "flow"]),
284
331
  id: IntelId,
285
332
  });
333
+ /**
334
+ * ⚠️ `timezone` is what the cron expression is READ IN, and it belongs to the schedule rather than
335
+ * to whoever is looking at it (#228). "Every morning at eight" means eight o'clock where the person
336
+ * who wrote it sits — in Berlin that is 06:00 UTC in summer and 07:00 in winter, and a field that
337
+ * does not carry the zone cannot express that difference. A UTC cron is an hour wrong twice a year
338
+ * and nobody sees why.
339
+ *
340
+ * The UI suggests the reader's own zone when a schedule is created, but it is not a per-user
341
+ * setting: an agent's schedule would otherwise move whenever its owner travelled, and it would mean
342
+ * different times to two people reading the same definition. What is stored is the answer.
343
+ *
344
+ * ⚠️ The default is `"UTC"`, and it is load-bearing rather than tidy: every definition written
345
+ * before this field parses to it and therefore keeps firing exactly when it did. A default of
346
+ * "whatever the writer's browser says" would silently move every existing schedule at the next save.
347
+ *
348
+ * The name is validated against this runtime's own tz database rather than a pattern. A regular
349
+ * expression would accept `Mars/Olympus`, and the failure would surface inside a Durable Object
350
+ * alarm — the place where nobody is watching.
351
+ */
352
+ const IanaTimezone = z
353
+ .string()
354
+ .trim()
355
+ .min(1)
356
+ .max(64)
357
+ .refine((zone) => {
358
+ try {
359
+ new Intl.DateTimeFormat("en-US", { timeZone: zone });
360
+ return true;
361
+ }
362
+ catch {
363
+ return false;
364
+ }
365
+ }, { message: "must be an IANA timezone name this runtime knows, for example Europe/Berlin" });
286
366
  export const AgentSchedule = z.strictObject({
287
367
  cron: z.string().trim().min(1).max(120),
368
+ timezone: IanaTimezone.default("UTC"),
288
369
  target: AgentScheduleTarget,
289
370
  });
290
371
  export const AgentModel = z.strictObject({
@@ -400,6 +481,68 @@ export const RunAgentNowInput = z.strictObject({
400
481
  nodeId: IntelId,
401
482
  target: AgentScheduleTarget,
402
483
  });
484
+ /**
485
+ * What one agent has actually cost, read out of Cloudflare's AI Gateway log (#251).
486
+ *
487
+ * ⚠️ Intel computes none of this from tokens and a price table. The gateway publishes the billed
488
+ * figure per call, and that figure is the debit from the Cloudflare balance 1:1 — Cloudflare takes
489
+ * its 5 % when the balance is loaded and passes inference through unchanged (measured 2026-08-07).
490
+ * A second, self-maintained answer beside it would be wrong on the day the two disagreed, and the
491
+ * wrong one would be the one on screen.
492
+ *
493
+ * ⚠️ `status` travels with the numbers and may never be dropped. `runs: []` means "cost nothing"
494
+ * only when `status` is `read`; under `not_configured` or `unreadable` it means "not known", and a
495
+ * screen that renders the two alike reports an outage as a saving.
496
+ */
497
+ export const AgentCostStatus = z.enum(["read", "not_configured", "unreadable"]);
498
+ export const AgentRunCost = z.strictObject({
499
+ runId: z.string(),
500
+ cost: z.number(),
501
+ calls: z.number(),
502
+ });
503
+ export const AgentCostWindow = z.strictObject({
504
+ days: z.number(),
505
+ cost: z.number(),
506
+ calls: z.number(),
507
+ // Which models produced this figure. It is here so the model select can say the number is about
508
+ // the PAST (#257) — a reader who switched model would otherwise take it for a forecast.
509
+ models: z.array(z.string()),
510
+ });
511
+ export const AgentCosts = z.strictObject({
512
+ status: AgentCostStatus,
513
+ currency: z.literal("USD"),
514
+ runs: z.array(AgentRunCost),
515
+ windows: z.array(AgentCostWindow),
516
+ // The read hit its page limit, so every total above is a floor rather than a total.
517
+ partial: z.boolean(),
518
+ });
519
+ /**
520
+ * What the models on offer cost and how much they hold (#257).
521
+ *
522
+ * ⚠️ `source` is per ENTRY and not per response, and that is not over-engineering. Cloudflare
523
+ * publishes figures for the models it serves itself and none at all for the Anthropic models it
524
+ * resells through Unified Billing — so a perfectly healthy read still leaves half the list on a
525
+ * written-out table, and one flag for the whole answer would call either the read stale or the
526
+ * table live.
527
+ */
528
+ export const ModelPrice = z.strictObject({
529
+ inputPerMillion: z.number(),
530
+ outputPerMillion: z.number(),
531
+ });
532
+ export const ModelCatalogEntry = z.strictObject({
533
+ provider: z.enum(["workers-ai", "anthropic"]),
534
+ model: z.string(),
535
+ name: z.string(),
536
+ contextTokens: z.number().nullable(),
537
+ // `null` where this installation has no figure. Never zero and never a guess — an invented number
538
+ // is a false statement about money.
539
+ price: ModelPrice.nullable(),
540
+ source: z.enum(["cloudflare", "builtin"]),
541
+ });
542
+ export const ModelCatalog = z.strictObject({
543
+ entries: z.array(ModelCatalogEntry),
544
+ liveStatus: z.enum(["read", "not_configured", "unreadable"]),
545
+ });
403
546
  // ⚠️ Three states, not two, and the same three the flow list makes: omitted is the whole tree,
404
547
  // `null` is the root level, an ID is that folder. "Which agents may I use" is a question about the
405
548
  // tree rather than about one folder, so the useful answer has to be reachable without knowing where
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anchrd/intel-contract",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {