@danypops/vehicle-core 0.17.1 → 0.18.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.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The Grant primitive's own wire-neutral shape: a resource budget bounding how much an
3
+ * already-authorized, already-running long-running operation may still do before it must ask
4
+ * for more. Mirrors VehicleJobWakeBudget's own convention (a plain, optional-per-dimension
5
+ * object, not a class) -- see wake-log.ts's own doc comment for that precedent. Every dimension
6
+ * is independently optional: a caller states only the ceilings that matter for its own operation
7
+ * (an operation with no real notion of "tokens" simply never sets maxTokens).
8
+ *
9
+ * Deliberately agent-flavored (turns/tool-calls/tokens) rather than a generic {maxCount, maxBytes}
10
+ * pair the way VehicleJobWakeBudget is -- Vehicle was built specifically for agent consumers
11
+ * (vehicle-client-pi exists precisely to project operations as Pi tools), so this vocabulary
12
+ * belongs here rather than being reinvented per-consumer.
13
+ */
14
+ export interface VehicleGrantBudget {
15
+ readonly maxTurns?: number;
16
+ readonly maxToolCalls?: number;
17
+ readonly maxTokens?: number;
18
+ readonly maxWallClockMs?: number;
19
+ }
20
+ /**
21
+ * True the moment any one *set* dimension reaches zero or below -- the tightest dimension
22
+ * governs, not an average or a sum. A dimension the caller never set imposes no ceiling of its
23
+ * own. An entirely empty budget ({}) is never exhausted: that's an unbounded grant (every
24
+ * dimension omitted), not a zero one -- a caller that wants "no more of anything" states at
25
+ * least one dimension as 0, it doesn't rely on {} meaning that.
26
+ */
27
+ export declare function grantBudgetExhausted(remaining: VehicleGrantBudget): boolean;
28
+ /**
29
+ * Adds a top-up onto the current remaining budget, dimension by dimension. A dimension absent
30
+ * from the top-up is left exactly as it was; a dimension the current budget never had but the
31
+ * top-up introduces is taken as-is (not added to an implicit 0, since the current budget's own
32
+ * "never set" already means unbounded for that dimension -- introducing a ceiling for the first
33
+ * time via a top-up is a real, deliberate narrowing a caller must do explicitly, not an artifact
34
+ * of the merge itself).
35
+ */
36
+ export declare function mergeGrantBudget(current: VehicleGrantBudget, additional: VehicleGrantBudget): VehicleGrantBudget;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * True the moment any one *set* dimension reaches zero or below -- the tightest dimension
3
+ * governs, not an average or a sum. A dimension the caller never set imposes no ceiling of its
4
+ * own. An entirely empty budget ({}) is never exhausted: that's an unbounded grant (every
5
+ * dimension omitted), not a zero one -- a caller that wants "no more of anything" states at
6
+ * least one dimension as 0, it doesn't rely on {} meaning that.
7
+ */
8
+ export function grantBudgetExhausted(remaining) {
9
+ return ((remaining.maxTurns !== undefined && remaining.maxTurns <= 0) ||
10
+ (remaining.maxToolCalls !== undefined && remaining.maxToolCalls <= 0) ||
11
+ (remaining.maxTokens !== undefined && remaining.maxTokens <= 0) ||
12
+ (remaining.maxWallClockMs !== undefined && remaining.maxWallClockMs <= 0));
13
+ }
14
+ /**
15
+ * Adds a top-up onto the current remaining budget, dimension by dimension. A dimension absent
16
+ * from the top-up is left exactly as it was; a dimension the current budget never had but the
17
+ * top-up introduces is taken as-is (not added to an implicit 0, since the current budget's own
18
+ * "never set" already means unbounded for that dimension -- introducing a ceiling for the first
19
+ * time via a top-up is a real, deliberate narrowing a caller must do explicitly, not an artifact
20
+ * of the merge itself).
21
+ */
22
+ export function mergeGrantBudget(current, additional) {
23
+ const merged = { ...current };
24
+ for (const key of ["maxTurns", "maxToolCalls", "maxTokens", "maxWallClockMs"]) {
25
+ const addition = additional[key];
26
+ if (addition === undefined)
27
+ continue;
28
+ merged[key] = (current[key] ?? 0) + addition;
29
+ }
30
+ return merged;
31
+ }
@@ -1,3 +1,4 @@
1
+ export * from "./grant.js";
1
2
  export * from "./identity.js";
2
3
  export * from "./replay.js";
3
4
  export * from "./retention.js";
@@ -1,3 +1,4 @@
1
+ export * from "./grant.js";
1
2
  export * from "./identity.js";
2
3
  export * from "./replay.js";
3
4
  export * from "./retention.js";
@@ -2,3 +2,4 @@ export * from "./codec.js";
2
2
  export * from "./json.js";
3
3
  export * from "./loose-object.js";
4
4
  export * from "./presentation.js";
5
+ export * from "./primitives.js";
@@ -2,3 +2,4 @@ export * from "./codec.js";
2
2
  export * from "./json.js";
3
3
  export * from "./loose-object.js";
4
4
  export * from "./presentation.js";
5
+ export * from "./primitives.js";
@@ -0,0 +1,37 @@
1
+ import type { VehicleSchemaIssue } from "./codec.js";
2
+ /** The `{ success: false, issues }` half of VehicleSchemaResult -- named on its own since every hand-written safeParse's failure branch is this exact shape, never a bare Error. */
3
+ export interface VehicleSchemaFailure {
4
+ readonly success: false;
5
+ readonly issues: readonly VehicleSchemaIssue[];
6
+ }
7
+ /**
8
+ * A safeParse's own first, universal check: the value wasn't even an object. Every hand-written
9
+ * object-shaped VehicleSchemaCodec across the ecosystem re-derived this exact literal before this
10
+ * existed as a shared primitive -- one canonical wording now, not five near-identical copies.
11
+ */
12
+ export declare function notAnObjectIssue(): VehicleSchemaFailure;
13
+ /**
14
+ * One field-scoped failure. `path` accepts a single key (the common case: a top-level field) or
15
+ * a full path segment array (for a nested/array-indexed field, matching VehicleSchemaIssue.path's
16
+ * own `readonly (string | number)[]` shape directly) -- both real shapes existing hand-written
17
+ * safeParse implementations across the ecosystem already needed.
18
+ */
19
+ export declare function schemaIssue(path: string | number | readonly (string | number)[], message: string): VehicleSchemaFailure;
20
+ /** True for a real object value -- not null, not an array (JSON Schema's own object/array distinction; `typeof [] === "object"` is not what a caller checking "is this a plain object" means). */
21
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
22
+ /** A non-empty string -- the shape every identifier-like field (workspaceId, ref, path, ...) across the ecosystem actually requires; an empty string is a real, distinct failure from "not a string at all". */
23
+ export declare function isNonEmptyString(value: unknown): value is string;
24
+ /** A real integer safely representable in a double -- the base every bounded-count/size field below builds on. */
25
+ export declare function isSafeInteger(value: unknown): value is number;
26
+ /** A safe integer that is zero or more -- e.g. an offset/cursor field where zero is a real, valid value, unlike a positive-only count. */
27
+ export declare function isNonNegativeSafeInteger(value: unknown): value is number;
28
+ /**
29
+ * A safe integer that is at least 1 -- the shape every bounded-count/size field (maxBytes,
30
+ * maxCount, maxResults, maxMatches, ...) across the ecosystem actually requires. `maximum`, when
31
+ * given, additionally caps the accepted value inclusively -- a field that must be positive AND
32
+ * never exceed some hard ceiling (e.g. a page size capped well below an unbounded read) is a real,
33
+ * recurring combination, not a hypothetical one.
34
+ */
35
+ export declare function isPositiveSafeInteger(value: unknown, maximum?: number): value is number;
36
+ /** An array whose every element is a string -- the shape every string-list field (labels, tags, pathspecs, ...) across the ecosystem actually requires; a mixed-type array is a real, distinct failure from "not an array at all". */
37
+ export declare function isStringArray(value: unknown): value is string[];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * A safeParse's own first, universal check: the value wasn't even an object. Every hand-written
3
+ * object-shaped VehicleSchemaCodec across the ecosystem re-derived this exact literal before this
4
+ * existed as a shared primitive -- one canonical wording now, not five near-identical copies.
5
+ */
6
+ export function notAnObjectIssue() {
7
+ return { success: false, issues: [{ path: [], message: "input must be an object" }] };
8
+ }
9
+ /**
10
+ * One field-scoped failure. `path` accepts a single key (the common case: a top-level field) or
11
+ * a full path segment array (for a nested/array-indexed field, matching VehicleSchemaIssue.path's
12
+ * own `readonly (string | number)[]` shape directly) -- both real shapes existing hand-written
13
+ * safeParse implementations across the ecosystem already needed.
14
+ */
15
+ export function schemaIssue(path, message) {
16
+ return { success: false, issues: [{ path: Array.isArray(path) ? path : [path], message }] };
17
+ }
18
+ /** True for a real object value -- not null, not an array (JSON Schema's own object/array distinction; `typeof [] === "object"` is not what a caller checking "is this a plain object" means). */
19
+ export function isPlainObject(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21
+ }
22
+ /** A non-empty string -- the shape every identifier-like field (workspaceId, ref, path, ...) across the ecosystem actually requires; an empty string is a real, distinct failure from "not a string at all". */
23
+ export function isNonEmptyString(value) {
24
+ return typeof value === "string" && value.length > 0;
25
+ }
26
+ /** A real integer safely representable in a double -- the base every bounded-count/size field below builds on. */
27
+ export function isSafeInteger(value) {
28
+ return typeof value === "number" && Number.isSafeInteger(value);
29
+ }
30
+ /** A safe integer that is zero or more -- e.g. an offset/cursor field where zero is a real, valid value, unlike a positive-only count. */
31
+ export function isNonNegativeSafeInteger(value) {
32
+ return isSafeInteger(value) && value >= 0;
33
+ }
34
+ /**
35
+ * A safe integer that is at least 1 -- the shape every bounded-count/size field (maxBytes,
36
+ * maxCount, maxResults, maxMatches, ...) across the ecosystem actually requires. `maximum`, when
37
+ * given, additionally caps the accepted value inclusively -- a field that must be positive AND
38
+ * never exceed some hard ceiling (e.g. a page size capped well below an unbounded read) is a real,
39
+ * recurring combination, not a hypothetical one.
40
+ */
41
+ export function isPositiveSafeInteger(value, maximum) {
42
+ return isSafeInteger(value) && value >= 1 && (maximum === undefined || value <= maximum);
43
+ }
44
+ /** An array whose every element is a string -- the shape every string-list field (labels, tags, pathspecs, ...) across the ecosystem actually requires; a mixed-type array is a real, distinct failure from "not an array at all". */
45
+ export function isStringArray(value) {
46
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/vehicle-core",
3
- "version": "0.17.1",
3
+ "version": "0.18.1",
4
4
  "description": "Vehicle's runtime-neutral wire contract: operation descriptors, schema codecs, failure shapes. Zero runtime dependencies, zero Bun-specific code -- the one thing every Vehicle client and server package depends on.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The Grant primitive's own wire-neutral shape: a resource budget bounding how much an
3
+ * already-authorized, already-running long-running operation may still do before it must ask
4
+ * for more. Mirrors VehicleJobWakeBudget's own convention (a plain, optional-per-dimension
5
+ * object, not a class) -- see wake-log.ts's own doc comment for that precedent. Every dimension
6
+ * is independently optional: a caller states only the ceilings that matter for its own operation
7
+ * (an operation with no real notion of "tokens" simply never sets maxTokens).
8
+ *
9
+ * Deliberately agent-flavored (turns/tool-calls/tokens) rather than a generic {maxCount, maxBytes}
10
+ * pair the way VehicleJobWakeBudget is -- Vehicle was built specifically for agent consumers
11
+ * (vehicle-client-pi exists precisely to project operations as Pi tools), so this vocabulary
12
+ * belongs here rather than being reinvented per-consumer.
13
+ */
14
+ export interface VehicleGrantBudget {
15
+ readonly maxTurns?: number;
16
+ readonly maxToolCalls?: number;
17
+ readonly maxTokens?: number;
18
+ readonly maxWallClockMs?: number;
19
+ }
20
+
21
+ /**
22
+ * True the moment any one *set* dimension reaches zero or below -- the tightest dimension
23
+ * governs, not an average or a sum. A dimension the caller never set imposes no ceiling of its
24
+ * own. An entirely empty budget ({}) is never exhausted: that's an unbounded grant (every
25
+ * dimension omitted), not a zero one -- a caller that wants "no more of anything" states at
26
+ * least one dimension as 0, it doesn't rely on {} meaning that.
27
+ */
28
+ export function grantBudgetExhausted(remaining: VehicleGrantBudget): boolean {
29
+ return (
30
+ (remaining.maxTurns !== undefined && remaining.maxTurns <= 0) ||
31
+ (remaining.maxToolCalls !== undefined && remaining.maxToolCalls <= 0) ||
32
+ (remaining.maxTokens !== undefined && remaining.maxTokens <= 0) ||
33
+ (remaining.maxWallClockMs !== undefined && remaining.maxWallClockMs <= 0)
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Adds a top-up onto the current remaining budget, dimension by dimension. A dimension absent
39
+ * from the top-up is left exactly as it was; a dimension the current budget never had but the
40
+ * top-up introduces is taken as-is (not added to an implicit 0, since the current budget's own
41
+ * "never set" already means unbounded for that dimension -- introducing a ceiling for the first
42
+ * time via a top-up is a real, deliberate narrowing a caller must do explicitly, not an artifact
43
+ * of the merge itself).
44
+ */
45
+ export function mergeGrantBudget(current: VehicleGrantBudget, additional: VehicleGrantBudget): VehicleGrantBudget {
46
+ const merged: { -readonly [K in keyof VehicleGrantBudget]?: number } = { ...current };
47
+ for (const key of ["maxTurns", "maxToolCalls", "maxTokens", "maxWallClockMs"] as const) {
48
+ const addition = additional[key];
49
+ if (addition === undefined) continue;
50
+ merged[key] = (current[key] ?? 0) + addition;
51
+ }
52
+ return merged;
53
+ }
package/src/jobs/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./grant.js";
1
2
  export * from "./identity.js";
2
3
  export * from "./replay.js";
3
4
  export * from "./retention.js";
@@ -2,3 +2,4 @@ export * from "./codec.js";
2
2
  export * from "./json.js";
3
3
  export * from "./loose-object.js";
4
4
  export * from "./presentation.js";
5
+ export * from "./primitives.js";
@@ -0,0 +1,62 @@
1
+ import type { VehicleSchemaIssue } from "./codec.js";
2
+
3
+ /** The `{ success: false, issues }` half of VehicleSchemaResult -- named on its own since every hand-written safeParse's failure branch is this exact shape, never a bare Error. */
4
+ export interface VehicleSchemaFailure {
5
+ readonly success: false;
6
+ readonly issues: readonly VehicleSchemaIssue[];
7
+ }
8
+
9
+ /**
10
+ * A safeParse's own first, universal check: the value wasn't even an object. Every hand-written
11
+ * object-shaped VehicleSchemaCodec across the ecosystem re-derived this exact literal before this
12
+ * existed as a shared primitive -- one canonical wording now, not five near-identical copies.
13
+ */
14
+ export function notAnObjectIssue(): VehicleSchemaFailure {
15
+ return { success: false, issues: [{ path: [], message: "input must be an object" }] };
16
+ }
17
+
18
+ /**
19
+ * One field-scoped failure. `path` accepts a single key (the common case: a top-level field) or
20
+ * a full path segment array (for a nested/array-indexed field, matching VehicleSchemaIssue.path's
21
+ * own `readonly (string | number)[]` shape directly) -- both real shapes existing hand-written
22
+ * safeParse implementations across the ecosystem already needed.
23
+ */
24
+ export function schemaIssue(path: string | number | readonly (string | number)[], message: string): VehicleSchemaFailure {
25
+ return { success: false, issues: [{ path: Array.isArray(path) ? path : [path], message }] };
26
+ }
27
+
28
+ /** True for a real object value -- not null, not an array (JSON Schema's own object/array distinction; `typeof [] === "object"` is not what a caller checking "is this a plain object" means). */
29
+ export function isPlainObject(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+
33
+ /** A non-empty string -- the shape every identifier-like field (workspaceId, ref, path, ...) across the ecosystem actually requires; an empty string is a real, distinct failure from "not a string at all". */
34
+ export function isNonEmptyString(value: unknown): value is string {
35
+ return typeof value === "string" && value.length > 0;
36
+ }
37
+
38
+ /** A real integer safely representable in a double -- the base every bounded-count/size field below builds on. */
39
+ export function isSafeInteger(value: unknown): value is number {
40
+ return typeof value === "number" && Number.isSafeInteger(value);
41
+ }
42
+
43
+ /** A safe integer that is zero or more -- e.g. an offset/cursor field where zero is a real, valid value, unlike a positive-only count. */
44
+ export function isNonNegativeSafeInteger(value: unknown): value is number {
45
+ return isSafeInteger(value) && value >= 0;
46
+ }
47
+
48
+ /**
49
+ * A safe integer that is at least 1 -- the shape every bounded-count/size field (maxBytes,
50
+ * maxCount, maxResults, maxMatches, ...) across the ecosystem actually requires. `maximum`, when
51
+ * given, additionally caps the accepted value inclusively -- a field that must be positive AND
52
+ * never exceed some hard ceiling (e.g. a page size capped well below an unbounded read) is a real,
53
+ * recurring combination, not a hypothetical one.
54
+ */
55
+ export function isPositiveSafeInteger(value: unknown, maximum?: number): value is number {
56
+ return isSafeInteger(value) && value >= 1 && (maximum === undefined || value <= maximum);
57
+ }
58
+
59
+ /** An array whose every element is a string -- the shape every string-list field (labels, tags, pathspecs, ...) across the ecosystem actually requires; a mixed-type array is a real, distinct failure from "not an array at all". */
60
+ export function isStringArray(value: unknown): value is string[] {
61
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
62
+ }